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
180251881Speter
181251881Speter/* The guts of do_wc_to_wc_copies */
182251881Speterstatic svn_error_t *
183251881Speterdo_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep,
184251881Speter                                   const apr_array_header_t *copy_pairs,
185251881Speter                                   const char *dst_parent,
186251881Speter                                   svn_client_ctx_t *ctx,
187251881Speter                                   apr_pool_t *scratch_pool)
188251881Speter{
189251881Speter  int i;
190251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
191251881Speter  svn_error_t *err = SVN_NO_ERROR;
192251881Speter
193251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
194251881Speter    {
195251881Speter      const char *dst_abspath;
196251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
197251881Speter                                                    svn_client__copy_pair_t *);
198251881Speter      svn_pool_clear(iterpool);
199251881Speter
200251881Speter      /* Check for cancellation */
201251881Speter      if (ctx->cancel_func)
202251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
203251881Speter
204251881Speter      /* Perform the copy */
205251881Speter      dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name,
206251881Speter                                    iterpool);
207251881Speter      *timestamp_sleep = TRUE;
208251881Speter      err = svn_wc_copy3(ctx->wc_ctx, pair->src_abspath_or_url, dst_abspath,
209251881Speter                         FALSE /* metadata_only */,
210251881Speter                         ctx->cancel_func, ctx->cancel_baton,
211251881Speter                         ctx->notify_func2, ctx->notify_baton2, iterpool);
212251881Speter      if (err)
213251881Speter        break;
214251881Speter    }
215251881Speter  svn_pool_destroy(iterpool);
216251881Speter
217251881Speter  SVN_ERR(err);
218251881Speter  return SVN_NO_ERROR;
219251881Speter}
220251881Speter
221251881Speter/* Copy each COPY_PAIR->SRC into COPY_PAIR->DST.  Use POOL for temporary
222251881Speter   allocations. */
223251881Speterstatic svn_error_t *
224251881Speterdo_wc_to_wc_copies(svn_boolean_t *timestamp_sleep,
225251881Speter                   const apr_array_header_t *copy_pairs,
226251881Speter                   svn_client_ctx_t *ctx,
227251881Speter                   apr_pool_t *pool)
228251881Speter{
229251881Speter  const char *dst_parent, *dst_parent_abspath;
230251881Speter
231251881Speter  SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &dst_parent, NULL, pool));
232251881Speter  if (copy_pairs->nelts == 1)
233251881Speter    dst_parent = svn_dirent_dirname(dst_parent, pool);
234251881Speter
235251881Speter  SVN_ERR(svn_dirent_get_absolute(&dst_parent_abspath, dst_parent, pool));
236251881Speter
237251881Speter  SVN_WC__CALL_WITH_WRITE_LOCK(
238251881Speter    do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent,
239251881Speter                                       ctx, pool),
240251881Speter    ctx->wc_ctx, dst_parent_abspath, FALSE, pool);
241251881Speter
242251881Speter  return SVN_NO_ERROR;
243251881Speter}
244251881Speter
245251881Speter/* The locked bit of do_wc_to_wc_moves. */
246251881Speterstatic svn_error_t *
247251881Speterdo_wc_to_wc_moves_with_locks2(svn_client__copy_pair_t *pair,
248251881Speter                              const char *dst_parent_abspath,
249251881Speter                              svn_boolean_t lock_src,
250251881Speter                              svn_boolean_t lock_dst,
251251881Speter                              svn_boolean_t allow_mixed_revisions,
252251881Speter                              svn_boolean_t metadata_only,
253251881Speter                              svn_client_ctx_t *ctx,
254251881Speter                              apr_pool_t *scratch_pool)
255251881Speter{
256251881Speter  const char *dst_abspath;
257251881Speter
258251881Speter  dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name,
259251881Speter                                scratch_pool);
260251881Speter
261251881Speter  SVN_ERR(svn_wc__move2(ctx->wc_ctx, pair->src_abspath_or_url,
262251881Speter                        dst_abspath, metadata_only,
263251881Speter                        allow_mixed_revisions,
264251881Speter                        ctx->cancel_func, ctx->cancel_baton,
265251881Speter                        ctx->notify_func2, ctx->notify_baton2,
266251881Speter                        scratch_pool));
267251881Speter
268251881Speter  return SVN_NO_ERROR;
269251881Speter}
270251881Speter
271251881Speter/* Wrapper to add an optional second lock */
272251881Speterstatic svn_error_t *
273251881Speterdo_wc_to_wc_moves_with_locks1(svn_client__copy_pair_t *pair,
274251881Speter                              const char *dst_parent_abspath,
275251881Speter                              svn_boolean_t lock_src,
276251881Speter                              svn_boolean_t lock_dst,
277251881Speter                              svn_boolean_t allow_mixed_revisions,
278251881Speter                              svn_boolean_t metadata_only,
279251881Speter                              svn_client_ctx_t *ctx,
280251881Speter                              apr_pool_t *scratch_pool)
281251881Speter{
282251881Speter  if (lock_dst)
283251881Speter    SVN_WC__CALL_WITH_WRITE_LOCK(
284251881Speter      do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
285251881Speter                                    lock_dst, allow_mixed_revisions,
286251881Speter                                    metadata_only,
287251881Speter                                    ctx, scratch_pool),
288251881Speter      ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool);
289251881Speter  else
290251881Speter    SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
291251881Speter                                          lock_dst, allow_mixed_revisions,
292251881Speter                                          metadata_only,
293251881Speter                                          ctx, scratch_pool));
294251881Speter
295251881Speter  return SVN_NO_ERROR;
296251881Speter}
297251881Speter
298251881Speter/* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC
299251881Speter   afterwards.  Use POOL for temporary allocations. */
300251881Speterstatic svn_error_t *
301251881Speterdo_wc_to_wc_moves(svn_boolean_t *timestamp_sleep,
302251881Speter                  const apr_array_header_t *copy_pairs,
303251881Speter                  const char *dst_path,
304251881Speter                  svn_boolean_t allow_mixed_revisions,
305251881Speter                  svn_boolean_t metadata_only,
306251881Speter                  svn_client_ctx_t *ctx,
307251881Speter                  apr_pool_t *pool)
308251881Speter{
309251881Speter  int i;
310251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
311251881Speter  svn_error_t *err = SVN_NO_ERROR;
312251881Speter
313251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
314251881Speter    {
315251881Speter      const char *src_parent_abspath;
316251881Speter      svn_boolean_t lock_src, lock_dst;
317251881Speter
318251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
319251881Speter                                                    svn_client__copy_pair_t *);
320251881Speter      svn_pool_clear(iterpool);
321251881Speter
322251881Speter      /* Check for cancellation */
323251881Speter      if (ctx->cancel_func)
324251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
325251881Speter
326251881Speter      src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url,
327251881Speter                                              iterpool);
328251881Speter
329251881Speter      /* We now need to lock the right combination of batons.
330251881Speter         Four cases:
331251881Speter           1) src_parent == dst_parent
332251881Speter           2) src_parent is parent of dst_parent
333251881Speter           3) dst_parent is parent of src_parent
334251881Speter           4) src_parent and dst_parent are disjoint
335251881Speter         We can handle 1) as either 2) or 3) */
336251881Speter      if (strcmp(src_parent_abspath, pair->dst_parent_abspath) == 0
337251881Speter          || svn_dirent_is_child(src_parent_abspath, pair->dst_parent_abspath,
338251881Speter                                 iterpool))
339251881Speter        {
340251881Speter          lock_src = TRUE;
341251881Speter          lock_dst = FALSE;
342251881Speter        }
343251881Speter      else if (svn_dirent_is_child(pair->dst_parent_abspath,
344251881Speter                                   src_parent_abspath,
345251881Speter                                   iterpool))
346251881Speter        {
347251881Speter          lock_src = FALSE;
348251881Speter          lock_dst = TRUE;
349251881Speter        }
350251881Speter      else
351251881Speter        {
352251881Speter          lock_src = TRUE;
353251881Speter          lock_dst = TRUE;
354251881Speter        }
355251881Speter
356251881Speter      *timestamp_sleep = TRUE;
357251881Speter
358251881Speter      /* Perform the copy and then the delete. */
359251881Speter      if (lock_src)
360251881Speter        SVN_WC__CALL_WITH_WRITE_LOCK(
361251881Speter          do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
362251881Speter                                        lock_src, lock_dst,
363251881Speter                                        allow_mixed_revisions,
364251881Speter                                        metadata_only,
365251881Speter                                        ctx, iterpool),
366251881Speter          ctx->wc_ctx, src_parent_abspath,
367251881Speter          FALSE, iterpool);
368251881Speter      else
369251881Speter        SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
370251881Speter                                              lock_src, lock_dst,
371251881Speter                                              allow_mixed_revisions,
372251881Speter                                              metadata_only,
373251881Speter                                              ctx, iterpool));
374251881Speter
375251881Speter    }
376251881Speter  svn_pool_destroy(iterpool);
377251881Speter
378251881Speter  return svn_error_trace(err);
379251881Speter}
380251881Speter
381251881Speter/* Verify that the destinations stored in COPY_PAIRS are valid working copy
382251881Speter   destinations and set pair->dst_parent_abspath and pair->base_name for each
383251881Speter   item to the resulting location if they do */
384251881Speterstatic svn_error_t *
385251881Speterverify_wc_dsts(const apr_array_header_t *copy_pairs,
386251881Speter               svn_boolean_t make_parents,
387251881Speter               svn_boolean_t is_move,
388253734Speter               svn_boolean_t metadata_only,
389251881Speter               svn_client_ctx_t *ctx,
390251881Speter               apr_pool_t *result_pool,
391251881Speter               apr_pool_t *scratch_pool)
392251881Speter{
393251881Speter  int i;
394251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
395251881Speter
396251881Speter  /* Check that DST does not exist, but its parent does */
397251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
398251881Speter    {
399251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
400251881Speter                                                    svn_client__copy_pair_t *);
401251881Speter      svn_node_kind_t dst_kind, dst_parent_kind;
402251881Speter
403251881Speter      svn_pool_clear(iterpool);
404251881Speter
405251881Speter      /* If DST_PATH does not exist, then its basename will become a new
406251881Speter         file or dir added to its parent (possibly an implicit '.').
407251881Speter         Else, just error out. */
408251881Speter      SVN_ERR(svn_wc_read_kind2(&dst_kind, ctx->wc_ctx,
409251881Speter                                pair->dst_abspath_or_url,
410251881Speter                                FALSE /* show_deleted */,
411251881Speter                                TRUE /* show_hidden */,
412251881Speter                                iterpool));
413251881Speter      if (dst_kind != svn_node_none)
414251881Speter        {
415251881Speter          svn_boolean_t is_excluded;
416251881Speter          svn_boolean_t is_server_excluded;
417251881Speter
418251881Speter          SVN_ERR(svn_wc__node_is_not_present(NULL, &is_excluded,
419251881Speter                                              &is_server_excluded, ctx->wc_ctx,
420251881Speter                                              pair->dst_abspath_or_url, FALSE,
421251881Speter                                              iterpool));
422251881Speter
423251881Speter          if (is_excluded || is_server_excluded)
424251881Speter            {
425251881Speter              return svn_error_createf(
426251881Speter                  SVN_ERR_WC_OBSTRUCTED_UPDATE,
427251881Speter                  NULL, _("Path '%s' exists, but is excluded"),
428251881Speter                  svn_dirent_local_style(pair->dst_abspath_or_url, iterpool));
429251881Speter            }
430251881Speter          else
431251881Speter            return svn_error_createf(
432251881Speter                            SVN_ERR_ENTRY_EXISTS, NULL,
433251881Speter                            _("Path '%s' already exists"),
434251881Speter                            svn_dirent_local_style(pair->dst_abspath_or_url,
435251881Speter                                                   scratch_pool));
436251881Speter        }
437251881Speter
438251881Speter      /* Check that there is no unversioned obstruction */
439253734Speter      if (metadata_only)
440253734Speter        dst_kind = svn_node_none;
441253734Speter      else
442253734Speter        SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
443253734Speter                                  iterpool));
444251881Speter
445251881Speter      if (dst_kind != svn_node_none)
446251881Speter        {
447251881Speter          if (is_move
448251881Speter              && copy_pairs->nelts == 1
449251881Speter              && strcmp(svn_dirent_dirname(pair->src_abspath_or_url, iterpool),
450251881Speter                        svn_dirent_dirname(pair->dst_abspath_or_url,
451251881Speter                                           iterpool)) == 0)
452251881Speter            {
453251881Speter              const char *dst;
454251881Speter              char *dst_apr;
455251881Speter              apr_status_t apr_err;
456251881Speter              /* We have a rename inside a directory, which might collide
457251881Speter                 just because the case insensivity of the filesystem makes
458251881Speter                 the source match the destination. */
459251881Speter
460251881Speter              SVN_ERR(svn_path_cstring_from_utf8(&dst,
461251881Speter                                                 pair->dst_abspath_or_url,
462251881Speter                                                 scratch_pool));
463251881Speter
464251881Speter              apr_err = apr_filepath_merge(&dst_apr, NULL, dst,
465251881Speter                                           APR_FILEPATH_TRUENAME, iterpool);
466251881Speter
467251881Speter              if (!apr_err)
468251881Speter                {
469251881Speter                  /* And now bring it back to our canonical format */
470251881Speter                  SVN_ERR(svn_path_cstring_to_utf8(&dst, dst_apr, iterpool));
471251881Speter                  dst = svn_dirent_canonicalize(dst, iterpool);
472251881Speter                }
473251881Speter              /* else: Don't report this error; just report the normal error */
474251881Speter
475251881Speter              if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0)
476251881Speter                {
477251881Speter                  /* Ok, we have a single case only rename. Get out of here */
478251881Speter                  svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
479251881Speter                                   pair->dst_abspath_or_url, result_pool);
480251881Speter
481251881Speter                  svn_pool_destroy(iterpool);
482251881Speter                  return SVN_NO_ERROR;
483251881Speter                }
484251881Speter            }
485251881Speter
486251881Speter          return svn_error_createf(
487251881Speter                            SVN_ERR_ENTRY_EXISTS, NULL,
488251881Speter                            _("Path '%s' already exists as unversioned node"),
489251881Speter                            svn_dirent_local_style(pair->dst_abspath_or_url,
490251881Speter                                                   scratch_pool));
491251881Speter        }
492251881Speter
493251881Speter      svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
494251881Speter                       pair->dst_abspath_or_url, result_pool);
495251881Speter
496251881Speter      /* Make sure the destination parent is a directory and produce a clear
497251881Speter         error message if it is not. */
498251881Speter      SVN_ERR(svn_wc_read_kind2(&dst_parent_kind,
499251881Speter                                ctx->wc_ctx, pair->dst_parent_abspath,
500251881Speter                                FALSE, TRUE,
501251881Speter                                iterpool));
502251881Speter      if (make_parents && dst_parent_kind == svn_node_none)
503251881Speter        {
504251881Speter          SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath,
505251881Speter                                                 TRUE, ctx, iterpool));
506251881Speter        }
507251881Speter      else if (dst_parent_kind != svn_node_dir)
508251881Speter        {
509251881Speter          return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
510251881Speter                                   _("Path '%s' is not a directory"),
511251881Speter                                   svn_dirent_local_style(
512251881Speter                                     pair->dst_parent_abspath, scratch_pool));
513251881Speter        }
514251881Speter
515251881Speter      SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
516251881Speter                                &dst_parent_kind, scratch_pool));
517251881Speter
518251881Speter      if (dst_parent_kind != svn_node_dir)
519251881Speter        return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
520251881Speter                                 _("Path '%s' is not a directory"),
521251881Speter                                 svn_dirent_local_style(
522251881Speter                                     pair->dst_parent_abspath, scratch_pool));
523251881Speter    }
524251881Speter
525251881Speter  svn_pool_destroy(iterpool);
526251881Speter
527251881Speter  return SVN_NO_ERROR;
528251881Speter}
529251881Speter
530251881Speterstatic svn_error_t *
531251881Speterverify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
532251881Speter                        svn_boolean_t make_parents,
533251881Speter                        svn_boolean_t is_move,
534253734Speter                        svn_boolean_t metadata_only,
535251881Speter                        svn_client_ctx_t *ctx,
536251881Speter                        apr_pool_t *result_pool,
537251881Speter                        apr_pool_t *scratch_pool)
538251881Speter{
539251881Speter  int i;
540251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
541251881Speter
542251881Speter  /* Check that all of our SRCs exist. */
543251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
544251881Speter    {
545251881Speter      svn_boolean_t deleted_ok;
546251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
547251881Speter                                                    svn_client__copy_pair_t *);
548251881Speter      svn_pool_clear(iterpool);
549251881Speter
550251881Speter      deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base
551251881Speter                    || pair->src_op_revision.kind == svn_opt_revision_base);
552251881Speter
553251881Speter      /* Verify that SRC_PATH exists. */
554251881Speter      SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx,
555251881Speter                               pair->src_abspath_or_url,
556251881Speter                               deleted_ok, FALSE, iterpool));
557251881Speter      if (pair->src_kind == svn_node_none)
558251881Speter        return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
559251881Speter                                 _("Path '%s' does not exist"),
560251881Speter                                 svn_dirent_local_style(
561251881Speter                                        pair->src_abspath_or_url,
562251881Speter                                        scratch_pool));
563251881Speter    }
564251881Speter
565253734Speter  SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx,
566251881Speter                         result_pool, iterpool));
567251881Speter
568251881Speter  svn_pool_destroy(iterpool);
569251881Speter
570251881Speter  return SVN_NO_ERROR;
571251881Speter}
572251881Speter
573251881Speter
574251881Speter/* Path-specific state used as part of path_driver_cb_baton. */
575251881Spetertypedef struct path_driver_info_t
576251881Speter{
577251881Speter  const char *src_url;
578251881Speter  const char *src_path;
579251881Speter  const char *dst_path;
580251881Speter  svn_node_kind_t src_kind;
581251881Speter  svn_revnum_t src_revnum;
582251881Speter  svn_boolean_t resurrection;
583251881Speter  svn_boolean_t dir_add;
584251881Speter  svn_string_t *mergeinfo;  /* the new mergeinfo for the target */
585251881Speter} path_driver_info_t;
586251881Speter
587251881Speter
588251881Speter/* The baton used with the path_driver_cb_func() callback for a copy
589251881Speter   or move operation. */
590251881Speterstruct path_driver_cb_baton
591251881Speter{
592251881Speter  /* The editor (and its state) used to perform the operation. */
593251881Speter  const svn_delta_editor_t *editor;
594251881Speter  void *edit_baton;
595251881Speter
596251881Speter  /* A hash of path -> path_driver_info_t *'s. */
597251881Speter  apr_hash_t *action_hash;
598251881Speter
599251881Speter  /* Whether the operation is a move or copy. */
600251881Speter  svn_boolean_t is_move;
601251881Speter};
602251881Speter
603251881Speterstatic svn_error_t *
604251881Speterpath_driver_cb_func(void **dir_baton,
605251881Speter                    void *parent_baton,
606251881Speter                    void *callback_baton,
607251881Speter                    const char *path,
608251881Speter                    apr_pool_t *pool)
609251881Speter{
610251881Speter  struct path_driver_cb_baton *cb_baton = callback_baton;
611251881Speter  svn_boolean_t do_delete = FALSE, do_add = FALSE;
612251881Speter  path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path);
613251881Speter
614251881Speter  /* Initialize return value. */
615251881Speter  *dir_baton = NULL;
616251881Speter
617251881Speter  /* This function should never get an empty PATH.  We can neither
618251881Speter     create nor delete the empty PATH, so if someone is calling us
619251881Speter     with such, the code is just plain wrong. */
620251881Speter  SVN_ERR_ASSERT(! svn_path_is_empty(path));
621251881Speter
622251881Speter  /* Check to see if we need to add the path as a directory. */
623251881Speter  if (path_info->dir_add)
624251881Speter    {
625251881Speter      return cb_baton->editor->add_directory(path, parent_baton, NULL,
626251881Speter                                             SVN_INVALID_REVNUM, pool,
627251881Speter                                             dir_baton);
628251881Speter    }
629251881Speter
630251881Speter  /* If this is a resurrection, we know the source and dest paths are
631251881Speter     the same, and that our driver will only be calling us once.  */
632251881Speter  if (path_info->resurrection)
633251881Speter    {
634251881Speter      /* If this is a move, we do nothing.  Otherwise, we do the copy.  */
635251881Speter      if (! cb_baton->is_move)
636251881Speter        do_add = TRUE;
637251881Speter    }
638251881Speter  /* Not a resurrection. */
639251881Speter  else
640251881Speter    {
641251881Speter      /* If this is a move, we check PATH to see if it is the source
642251881Speter         or the destination of the move. */
643251881Speter      if (cb_baton->is_move)
644251881Speter        {
645251881Speter          if (strcmp(path_info->src_path, path) == 0)
646251881Speter            do_delete = TRUE;
647251881Speter          else
648251881Speter            do_add = TRUE;
649251881Speter        }
650251881Speter      /* Not a move?  This must just be the copy addition. */
651251881Speter      else
652251881Speter        {
653251881Speter          do_add = TRUE;
654251881Speter        }
655251881Speter    }
656251881Speter
657251881Speter  if (do_delete)
658251881Speter    {
659251881Speter      SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM,
660251881Speter                                             parent_baton, pool));
661251881Speter    }
662251881Speter  if (do_add)
663251881Speter    {
664251881Speter      SVN_ERR(svn_path_check_valid(path, pool));
665251881Speter
666251881Speter      if (path_info->src_kind == svn_node_file)
667251881Speter        {
668251881Speter          void *file_baton;
669251881Speter          SVN_ERR(cb_baton->editor->add_file(path, parent_baton,
670251881Speter                                             path_info->src_url,
671251881Speter                                             path_info->src_revnum,
672251881Speter                                             pool, &file_baton));
673251881Speter          if (path_info->mergeinfo)
674251881Speter            SVN_ERR(cb_baton->editor->change_file_prop(file_baton,
675251881Speter                                                       SVN_PROP_MERGEINFO,
676251881Speter                                                       path_info->mergeinfo,
677251881Speter                                                       pool));
678251881Speter          SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool));
679251881Speter        }
680251881Speter      else
681251881Speter        {
682251881Speter          SVN_ERR(cb_baton->editor->add_directory(path, parent_baton,
683251881Speter                                                  path_info->src_url,
684251881Speter                                                  path_info->src_revnum,
685251881Speter                                                  pool, dir_baton));
686251881Speter          if (path_info->mergeinfo)
687251881Speter            SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton,
688251881Speter                                                      SVN_PROP_MERGEINFO,
689251881Speter                                                      path_info->mergeinfo,
690251881Speter                                                      pool));
691251881Speter        }
692251881Speter    }
693251881Speter  return SVN_NO_ERROR;
694251881Speter}
695251881Speter
696251881Speter
697251881Speter/* Starting with the path DIR relative to the RA_SESSION's session
698251881Speter   URL, work up through DIR's parents until an existing node is found.
699251881Speter   Push each nonexistent path onto the array NEW_DIRS, allocating in
700251881Speter   POOL.  Raise an error if the existing node is not a directory.
701251881Speter
702251881Speter   ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
703251881Speter   ### implementation susceptible to race conditions.  */
704251881Speterstatic svn_error_t *
705251881Speterfind_absent_parents1(svn_ra_session_t *ra_session,
706251881Speter                     const char *dir,
707251881Speter                     apr_array_header_t *new_dirs,
708251881Speter                     apr_pool_t *pool)
709251881Speter{
710251881Speter  svn_node_kind_t kind;
711251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
712251881Speter
713251881Speter  SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
714251881Speter                            iterpool));
715251881Speter
716251881Speter  while (kind == svn_node_none)
717251881Speter    {
718251881Speter      svn_pool_clear(iterpool);
719251881Speter
720251881Speter      APR_ARRAY_PUSH(new_dirs, const char *) = dir;
721251881Speter      dir = svn_dirent_dirname(dir, pool);
722251881Speter
723251881Speter      SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM,
724251881Speter                                &kind, iterpool));
725251881Speter    }
726251881Speter
727251881Speter  if (kind != svn_node_dir)
728251881Speter    return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
729251881Speter                             _("Path '%s' already exists, but is not a "
730251881Speter                               "directory"), dir);
731251881Speter
732251881Speter  svn_pool_destroy(iterpool);
733251881Speter  return SVN_NO_ERROR;
734251881Speter}
735251881Speter
736251881Speter/* Starting with the URL *TOP_DST_URL which is also the root of
737251881Speter   RA_SESSION, work up through its parents until an existing node is
738251881Speter   found. Push each nonexistent URL onto the array NEW_DIRS,
739251881Speter   allocating in POOL.  Raise an error if the existing node is not a
740251881Speter   directory.
741251881Speter
742251881Speter   Set *TOP_DST_URL and the RA session's root to the existing node's URL.
743251881Speter
744251881Speter   ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
745251881Speter   ### implementation susceptible to race conditions.  */
746251881Speterstatic svn_error_t *
747251881Speterfind_absent_parents2(svn_ra_session_t *ra_session,
748251881Speter                     const char **top_dst_url,
749251881Speter                     apr_array_header_t *new_dirs,
750251881Speter                     apr_pool_t *pool)
751251881Speter{
752251881Speter  const char *root_url = *top_dst_url;
753251881Speter  svn_node_kind_t kind;
754251881Speter
755251881Speter  SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
756251881Speter                            pool));
757251881Speter
758251881Speter  while (kind == svn_node_none)
759251881Speter    {
760251881Speter      APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
761251881Speter      root_url = svn_uri_dirname(root_url, pool);
762251881Speter
763251881Speter      SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
764251881Speter      SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
765251881Speter                                pool));
766251881Speter    }
767251881Speter
768251881Speter  if (kind != svn_node_dir)
769251881Speter    return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
770251881Speter                _("Path '%s' already exists, but is not a directory"),
771251881Speter                root_url);
772251881Speter
773251881Speter  *top_dst_url = root_url;
774251881Speter  return SVN_NO_ERROR;
775251881Speter}
776251881Speter
777251881Speterstatic svn_error_t *
778251881Speterrepos_to_repos_copy(const apr_array_header_t *copy_pairs,
779251881Speter                    svn_boolean_t make_parents,
780251881Speter                    const apr_hash_t *revprop_table,
781251881Speter                    svn_commit_callback2_t commit_callback,
782251881Speter                    void *commit_baton,
783251881Speter                    svn_client_ctx_t *ctx,
784251881Speter                    svn_boolean_t is_move,
785251881Speter                    apr_pool_t *pool)
786251881Speter{
787251881Speter  svn_error_t *err;
788251881Speter  apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
789251881Speter                                             sizeof(const char *));
790251881Speter  apr_hash_t *action_hash = apr_hash_make(pool);
791251881Speter  apr_array_header_t *path_infos;
792251881Speter  const char *top_url, *top_url_all, *top_url_dst;
793251881Speter  const char *message, *repos_root;
794251881Speter  svn_ra_session_t *ra_session = NULL;
795251881Speter  const svn_delta_editor_t *editor;
796251881Speter  void *edit_baton;
797251881Speter  struct path_driver_cb_baton cb_baton;
798251881Speter  apr_array_header_t *new_dirs = NULL;
799251881Speter  apr_hash_t *commit_revprops;
800251881Speter  int i;
801251881Speter  svn_client__copy_pair_t *first_pair =
802251881Speter    APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
803251881Speter
804251881Speter  /* Open an RA session to the first copy pair's destination.  We'll
805251881Speter     be verifying that every one of our copy source and destination
806251881Speter     URLs is or is beneath this sucker's repository root URL as a form
807251881Speter     of a cheap(ish) sanity check.  */
808251881Speter  SVN_ERR(svn_client_open_ra_session2(&ra_session,
809251881Speter                                      first_pair->src_abspath_or_url, NULL,
810251881Speter                                      ctx, pool, pool));
811251881Speter  SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
812251881Speter
813251881Speter  /* Verify that sources and destinations are all at or under
814251881Speter     REPOS_ROOT.  While here, create a path_info struct for each
815251881Speter     src/dst pair and initialize portions of it with normalized source
816251881Speter     location information.  */
817251881Speter  path_infos = apr_array_make(pool, copy_pairs->nelts,
818251881Speter                              sizeof(path_driver_info_t *));
819251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
820251881Speter    {
821251881Speter      path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
822251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
823251881Speter                                                    svn_client__copy_pair_t *);
824251881Speter      apr_hash_t *mergeinfo;
825251881Speter
826251881Speter      /* Are the source and destination URLs at or under REPOS_ROOT? */
827251881Speter      if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url)
828251881Speter             && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url)))
829251881Speter        return svn_error_create
830251881Speter          (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
831251881Speter           _("Source and destination URLs appear not to point to the "
832251881Speter             "same repository."));
833251881Speter
834251881Speter      /* Run the history function to get the source's URL and revnum in the
835251881Speter         operational revision. */
836251881Speter      SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
837251881Speter      SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url,
838251881Speter                                          &pair->src_revnum,
839251881Speter                                          NULL, NULL,
840251881Speter                                          ra_session,
841251881Speter                                          pair->src_abspath_or_url,
842251881Speter                                          &pair->src_peg_revision,
843251881Speter                                          &pair->src_op_revision, NULL,
844251881Speter                                          ctx, pool));
845251881Speter
846251881Speter      /* Go ahead and grab mergeinfo from the source, too. */
847251881Speter      SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
848251881Speter      SVN_ERR(svn_client__get_repos_mergeinfo(
849251881Speter                &mergeinfo, ra_session,
850251881Speter                pair->src_abspath_or_url, pair->src_revnum,
851251881Speter                svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
852251881Speter      if (mergeinfo)
853251881Speter        SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool));
854251881Speter
855251881Speter      /* Plop an INFO structure onto our array thereof. */
856251881Speter      info->src_url = pair->src_abspath_or_url;
857251881Speter      info->src_revnum = pair->src_revnum;
858251881Speter      info->resurrection = FALSE;
859251881Speter      APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
860251881Speter    }
861251881Speter
862251881Speter  /* If this is a move, we have to open our session to the longest
863251881Speter     path common to all SRC_URLS and DST_URLS in the repository so we
864251881Speter     can do existence checks on all paths, and so we can operate on
865251881Speter     all paths in the case of a move.  But if this is *not* a move,
866251881Speter     then opening our session at the longest path common to sources
867251881Speter     *and* destinations might be an optimization when the user is
868251881Speter     authorized to access all that stuff, but could cause the
869251881Speter     operation to fail altogether otherwise.  See issue #3242.  */
870251881Speter  SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all,
871251881Speter                                  pool));
872251881Speter  top_url = is_move ? top_url_all : top_url_dst;
873251881Speter
874251881Speter  /* Check each src/dst pair for resurrection, and verify that TOP_URL
875251881Speter     is anchored high enough to cover all the editor_t activities
876251881Speter     required for this operation.  */
877251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
878251881Speter    {
879251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
880251881Speter                                                    svn_client__copy_pair_t *);
881251881Speter      path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
882251881Speter                                               path_driver_info_t *);
883251881Speter
884251881Speter      /* Source and destination are the same?  It's a resurrection. */
885251881Speter      if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0)
886251881Speter        info->resurrection = TRUE;
887251881Speter
888251881Speter      /* We need to add each dst_URL, and (in a move) we'll need to
889251881Speter         delete each src_URL.  Our selection of TOP_URL so far ensures
890251881Speter         that all our destination URLs (and source URLs, for moves)
891251881Speter         are at least as deep as TOP_URL, but we need to make sure
892251881Speter         that TOP_URL is an *ancestor* of all our to-be-edited paths.
893251881Speter
894251881Speter         Issue #683 is demonstrates this scenario.  If you're
895251881Speter         resurrecting a deleted item like this: 'svn cp -rN src_URL
896251881Speter         dst_URL', then src_URL == dst_URL == top_url.  In this
897251881Speter         situation, we want to open an RA session to be at least the
898251881Speter         *parent* of all three. */
899251881Speter      if ((strcmp(top_url, pair->dst_abspath_or_url) == 0)
900251881Speter          && (strcmp(top_url, repos_root) != 0))
901251881Speter        {
902251881Speter          top_url = svn_uri_dirname(top_url, pool);
903251881Speter        }
904251881Speter      if (is_move
905251881Speter          && (strcmp(top_url, pair->src_abspath_or_url) == 0)
906251881Speter          && (strcmp(top_url, repos_root) != 0))
907251881Speter        {
908251881Speter          top_url = svn_uri_dirname(top_url, pool);
909251881Speter        }
910251881Speter    }
911251881Speter
912251881Speter  /* Point the RA session to our current TOP_URL. */
913251881Speter  SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
914251881Speter
915251881Speter  /* If we're allowed to create nonexistent parent directories of our
916251881Speter     destinations, then make a list in NEW_DIRS of the parent
917251881Speter     directories of the destination that don't yet exist.  */
918251881Speter  if (make_parents)
919251881Speter    {
920251881Speter      new_dirs = apr_array_make(pool, 0, sizeof(const char *));
921251881Speter
922251881Speter      /* If this is a move, TOP_URL is at least the common ancestor of
923251881Speter         all the paths (sources and destinations) involved.  Assuming
924251881Speter         the sources exist (which is fair, because if they don't, this
925251881Speter         whole operation will fail anyway), TOP_URL must also exist.
926251881Speter         So it's the paths between TOP_URL and the destinations which
927251881Speter         we have to check for existence.  But here, we take advantage
928251881Speter         of the knowledge of our caller.  We know that if there are
929251881Speter         multiple copy/move operations being requested, then the
930251881Speter         destinations of the copies/moves will all be siblings of one
931251881Speter         another.  Therefore, we need only to check for the
932251881Speter         nonexistent paths between TOP_URL and *one* of our
933251881Speter         destinations to find nonexistent parents of all of them.  */
934251881Speter      if (is_move)
935251881Speter        {
936251881Speter          /* Imagine a situation where the user tries to copy an
937251881Speter             existing source directory to nonexistent directory with
938251881Speter             --parents options specified:
939251881Speter
940251881Speter                svn copy --parents URL/src URL/dst
941251881Speter
942251881Speter             where src exists and dst does not.  If the dirname of the
943251881Speter             destination path is equal to TOP_URL,
944251881Speter             do not try to add dst to the NEW_DIRS list since it
945251881Speter             will be added to the commit items array later in this
946251881Speter             function. */
947251881Speter          const char *dir = svn_uri_skip_ancestor(
948251881Speter                              top_url,
949251881Speter                              svn_uri_dirname(first_pair->dst_abspath_or_url,
950251881Speter                                              pool),
951251881Speter                              pool);
952251881Speter          if (dir && *dir)
953251881Speter            SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool));
954251881Speter        }
955251881Speter      /* If, however, this is *not* a move, TOP_URL only points to the
956251881Speter         common ancestor of our destination path(s), or possibly one
957251881Speter         level higher.  We'll need to do an existence crawl toward the
958251881Speter         root of the repository, starting with one of our destinations
959251881Speter         (see "... take advantage of the knowledge of our caller ..."
960251881Speter         above), and possibly adjusting TOP_URL as we go. */
961251881Speter      else
962251881Speter        {
963251881Speter          apr_array_header_t *new_urls =
964251881Speter            apr_array_make(pool, 0, sizeof(const char *));
965251881Speter          SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool));
966251881Speter
967251881Speter          /* Convert absolute URLs into relpaths relative to TOP_URL. */
968251881Speter          for (i = 0; i < new_urls->nelts; i++)
969251881Speter            {
970251881Speter              const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *);
971251881Speter              const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool);
972251881Speter
973251881Speter              APR_ARRAY_PUSH(new_dirs, const char *) = dir;
974251881Speter            }
975251881Speter        }
976251881Speter    }
977251881Speter
978251881Speter  /* For each src/dst pair, check to see if that SRC_URL is a child of
979251881Speter     the DST_URL (excepting the case where DST_URL is the repo root).
980251881Speter     If it is, and the parent of DST_URL is the current TOP_URL, then we
981251881Speter     need to reparent the session one directory higher, the parent of
982251881Speter     the DST_URL. */
983251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
984251881Speter    {
985251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
986251881Speter                                                    svn_client__copy_pair_t *);
987251881Speter      path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
988251881Speter                                               path_driver_info_t *);
989251881Speter      const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url,
990251881Speter                                                  pair->src_abspath_or_url,
991251881Speter                                                  pool);
992251881Speter
993251881Speter      if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0)
994251881Speter          && (relpath != NULL && *relpath != '\0'))
995251881Speter        {
996251881Speter          info->resurrection = TRUE;
997251881Speter          top_url = svn_uri_dirname(top_url, pool);
998251881Speter          SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
999251881Speter        }
1000251881Speter    }
1001251881Speter
1002251881Speter  /* Get the portions of the SRC and DST URLs that are relative to
1003251881Speter     TOP_URL (URI-decoding them while we're at it), verify that the
1004251881Speter     source exists and the proposed destination does not, and toss
1005251881Speter     what we've learned into the INFO array.  (For copies -- that is,
1006251881Speter     non-moves -- the relative source URL NULL because it isn't a
1007251881Speter     child of the TOP_URL at all.  That's okay, we'll deal with
1008251881Speter     it.)  */
1009251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
1010251881Speter    {
1011251881Speter      svn_client__copy_pair_t *pair =
1012251881Speter        APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1013251881Speter      path_driver_info_t *info =
1014251881Speter        APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1015251881Speter      svn_node_kind_t dst_kind;
1016251881Speter      const char *src_rel, *dst_rel;
1017251881Speter
1018251881Speter      src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool);
1019251881Speter      if (src_rel)
1020251881Speter        {
1021251881Speter          SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1022251881Speter                                    &info->src_kind, pool));
1023251881Speter        }
1024251881Speter      else
1025251881Speter        {
1026251881Speter          const char *old_url;
1027251881Speter
1028251881Speter          src_rel = NULL;
1029251881Speter          SVN_ERR_ASSERT(! is_move);
1030251881Speter
1031251881Speter          SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
1032251881Speter                                                    pair->src_abspath_or_url,
1033251881Speter                                                    pool));
1034251881Speter          SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum,
1035251881Speter                                    &info->src_kind, pool));
1036251881Speter          SVN_ERR(svn_ra_reparent(ra_session, old_url, pool));
1037251881Speter        }
1038251881Speter      if (info->src_kind == svn_node_none)
1039251881Speter        return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1040251881Speter                                 _("Path '%s' does not exist in revision %ld"),
1041251881Speter                                 pair->src_abspath_or_url, pair->src_revnum);
1042251881Speter
1043251881Speter      /* Figure out the basename that will result from this operation,
1044251881Speter         and ensure that we aren't trying to overwrite existing paths.  */
1045251881Speter      dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool);
1046251881Speter      SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1047251881Speter                                &dst_kind, pool));
1048251881Speter      if (dst_kind != svn_node_none)
1049251881Speter        return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1050251881Speter                                 _("Path '%s' already exists"), dst_rel);
1051251881Speter
1052251881Speter      /* More info for our INFO structure.  */
1053251881Speter      info->src_path = src_rel;
1054251881Speter      info->dst_path = dst_rel;
1055251881Speter
1056251881Speter      svn_hash_sets(action_hash, info->dst_path, info);
1057251881Speter      if (is_move && (! info->resurrection))
1058251881Speter        svn_hash_sets(action_hash, info->src_path, info);
1059251881Speter    }
1060251881Speter
1061251881Speter  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1062251881Speter    {
1063251881Speter      /* Produce a list of new paths to add, and provide it to the
1064251881Speter         mechanism used to acquire a log message. */
1065251881Speter      svn_client_commit_item3_t *item;
1066251881Speter      const char *tmp_file;
1067251881Speter      apr_array_header_t *commit_items
1068251881Speter        = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item));
1069251881Speter
1070251881Speter      /* Add any intermediate directories to the message */
1071251881Speter      if (make_parents)
1072251881Speter        {
1073251881Speter          for (i = 0; i < new_dirs->nelts; i++)
1074251881Speter            {
1075251881Speter              const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1076251881Speter
1077251881Speter              item = svn_client_commit_item3_create(pool);
1078251881Speter              item->url = svn_path_url_add_component2(top_url, relpath, pool);
1079251881Speter              item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1080251881Speter              APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1081251881Speter            }
1082251881Speter        }
1083251881Speter
1084251881Speter      for (i = 0; i < path_infos->nelts; i++)
1085251881Speter        {
1086251881Speter          path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1087251881Speter                                                   path_driver_info_t *);
1088251881Speter
1089251881Speter          item = svn_client_commit_item3_create(pool);
1090251881Speter          item->url = svn_path_url_add_component2(top_url, info->dst_path,
1091251881Speter                                                  pool);
1092251881Speter          item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1093251881Speter          APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1094251881Speter
1095251881Speter          if (is_move && (! info->resurrection))
1096251881Speter            {
1097251881Speter              item = apr_pcalloc(pool, sizeof(*item));
1098251881Speter              item->url = svn_path_url_add_component2(top_url, info->src_path,
1099251881Speter                                                      pool);
1100251881Speter              item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1101251881Speter              APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1102251881Speter            }
1103251881Speter        }
1104251881Speter
1105251881Speter      SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1106251881Speter                                      ctx, pool));
1107251881Speter      if (! message)
1108251881Speter        return SVN_NO_ERROR;
1109251881Speter    }
1110251881Speter  else
1111251881Speter    message = "";
1112251881Speter
1113251881Speter  /* Setup our PATHS for the path-based editor drive. */
1114251881Speter  /* First any intermediate directories. */
1115251881Speter  if (make_parents)
1116251881Speter    {
1117251881Speter      for (i = 0; i < new_dirs->nelts; i++)
1118251881Speter        {
1119251881Speter          const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1120251881Speter          path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1121251881Speter
1122251881Speter          info->dst_path = relpath;
1123251881Speter          info->dir_add = TRUE;
1124251881Speter
1125251881Speter          APR_ARRAY_PUSH(paths, const char *) = relpath;
1126251881Speter          svn_hash_sets(action_hash, relpath, info);
1127251881Speter        }
1128251881Speter    }
1129251881Speter
1130251881Speter  /* Then our copy destinations and move sources (if any). */
1131251881Speter  for (i = 0; i < path_infos->nelts; i++)
1132251881Speter    {
1133251881Speter      path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1134251881Speter                                               path_driver_info_t *);
1135251881Speter
1136251881Speter      APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1137251881Speter      if (is_move && (! info->resurrection))
1138251881Speter        APR_ARRAY_PUSH(paths, const char *) = info->src_path;
1139251881Speter    }
1140251881Speter
1141251881Speter  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1142251881Speter                                           message, ctx, pool));
1143251881Speter
1144251881Speter  /* Fetch RA commit editor. */
1145251881Speter  SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1146251881Speter                        svn_client__get_shim_callbacks(ctx->wc_ctx,
1147251881Speter                                                       NULL, pool)));
1148251881Speter  SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1149251881Speter                                    commit_revprops,
1150251881Speter                                    commit_callback,
1151251881Speter                                    commit_baton,
1152251881Speter                                    NULL, TRUE, /* No lock tokens */
1153251881Speter                                    pool));
1154251881Speter
1155251881Speter  /* Setup the callback baton. */
1156251881Speter  cb_baton.editor = editor;
1157251881Speter  cb_baton.edit_baton = edit_baton;
1158251881Speter  cb_baton.action_hash = action_hash;
1159251881Speter  cb_baton.is_move = is_move;
1160251881Speter
1161251881Speter  /* Call the path-based editor driver. */
1162251881Speter  err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1163251881Speter                               path_driver_cb_func, &cb_baton, pool);
1164251881Speter  if (err)
1165251881Speter    {
1166251881Speter      /* At least try to abort the edit (and fs txn) before throwing err. */
1167251881Speter      return svn_error_compose_create(
1168251881Speter                    err,
1169251881Speter                    editor->abort_edit(edit_baton, pool));
1170251881Speter    }
1171251881Speter
1172251881Speter  /* Close the edit. */
1173251881Speter  return svn_error_trace(editor->close_edit(edit_baton, pool));
1174251881Speter}
1175251881Speter
1176251881Speter/* Baton for check_url_kind */
1177251881Speterstruct check_url_kind_baton
1178251881Speter{
1179251881Speter  svn_ra_session_t *session;
1180251881Speter  const char *repos_root_url;
1181251881Speter  svn_boolean_t should_reparent;
1182251881Speter};
1183251881Speter
1184251881Speter/* Implements svn_client__check_url_kind_t for wc_to_repos_copy */
1185251881Speterstatic svn_error_t *
1186251881Spetercheck_url_kind(void *baton,
1187251881Speter               svn_node_kind_t *kind,
1188251881Speter               const char *url,
1189251881Speter               svn_revnum_t revision,
1190251881Speter               apr_pool_t *scratch_pool)
1191251881Speter{
1192251881Speter  struct check_url_kind_baton *cukb = baton;
1193251881Speter
1194251881Speter  /* If we don't have a session or can't use the session, get one */
1195251881Speter  if (!svn_uri__is_ancestor(cukb->repos_root_url, url))
1196251881Speter    *kind = svn_node_none;
1197251881Speter  else
1198251881Speter    {
1199251881Speter      cukb->should_reparent = TRUE;
1200251881Speter
1201251881Speter      SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
1202251881Speter
1203251881Speter      SVN_ERR(svn_ra_check_path(cukb->session, "", revision,
1204251881Speter                                kind, scratch_pool));
1205251881Speter    }
1206251881Speter
1207251881Speter  return SVN_NO_ERROR;
1208251881Speter}
1209251881Speter
1210251881Speter/* ### Copy ...
1211251881Speter * COMMIT_INFO_P is ...
1212251881Speter * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath
1213251881Speter * and each 'dst_abspath_or_url' is a URL.
1214251881Speter * MAKE_PARENTS is ...
1215251881Speter * REVPROP_TABLE is ...
1216251881Speter * CTX is ... */
1217251881Speterstatic svn_error_t *
1218251881Speterwc_to_repos_copy(const apr_array_header_t *copy_pairs,
1219251881Speter                 svn_boolean_t make_parents,
1220251881Speter                 const apr_hash_t *revprop_table,
1221251881Speter                 svn_commit_callback2_t commit_callback,
1222251881Speter                 void *commit_baton,
1223251881Speter                 svn_client_ctx_t *ctx,
1224251881Speter                 apr_pool_t *scratch_pool)
1225251881Speter{
1226251881Speter  const char *message;
1227251881Speter  const char *top_src_path, *top_dst_url;
1228251881Speter  struct check_url_kind_baton cukb;
1229251881Speter  const char *top_src_abspath;
1230251881Speter  svn_ra_session_t *ra_session;
1231251881Speter  const svn_delta_editor_t *editor;
1232251881Speter  apr_hash_t *relpath_map = NULL;
1233251881Speter  void *edit_baton;
1234251881Speter  svn_client__committables_t *committables;
1235251881Speter  apr_array_header_t *commit_items;
1236251881Speter  apr_pool_t *iterpool;
1237251881Speter  apr_array_header_t *new_dirs = NULL;
1238251881Speter  apr_hash_t *commit_revprops;
1239251881Speter  svn_client__copy_pair_t *first_pair;
1240251881Speter  apr_pool_t *session_pool = svn_pool_create(scratch_pool);
1241251881Speter  int i;
1242251881Speter
1243251881Speter  /* Find the common root of all the source paths */
1244251881Speter  SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL,
1245251881Speter                                  scratch_pool));
1246251881Speter
1247251881Speter  /* Do we need to lock the working copy?  1.6 didn't take a write
1248251881Speter     lock, but what happens if the working copy changes during the copy
1249251881Speter     operation? */
1250251881Speter
1251251881Speter  iterpool = svn_pool_create(scratch_pool);
1252251881Speter
1253251881Speter  /* Determine the longest common ancestor for the destinations, and open an RA
1254251881Speter     session to that location. */
1255251881Speter  /* ### But why start by getting the _parent_ of the first one? */
1256251881Speter  /* --- That works because multiple destinations always point to the same
1257251881Speter   *     directory. I'm rather wondering why we need to find a common
1258251881Speter   *     destination parent here at all, instead of simply getting
1259251881Speter   *     top_dst_url from get_copy_pair_ancestors() above?
1260251881Speter   *     It looks like the entire block of code hanging off this comment
1261251881Speter   *     is redundant. */
1262251881Speter  first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
1263251881Speter  top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool);
1264251881Speter  for (i = 1; i < copy_pairs->nelts; i++)
1265251881Speter    {
1266251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1267251881Speter                                                    svn_client__copy_pair_t *);
1268251881Speter      top_dst_url = svn_uri_get_longest_ancestor(top_dst_url,
1269251881Speter                                                 pair->dst_abspath_or_url,
1270251881Speter                                                 scratch_pool);
1271251881Speter    }
1272251881Speter
1273251881Speter  SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool));
1274251881Speter
1275251881Speter  /* Open a session to help while determining the exact targets */
1276251881Speter  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
1277251881Speter                                               top_src_abspath, NULL,
1278251881Speter                                               FALSE /* write_dav_props */,
1279251881Speter                                               TRUE /* read_dav_props */,
1280251881Speter                                               ctx,
1281251881Speter                                               session_pool, session_pool));
1282251881Speter
1283251881Speter  /* If requested, determine the nearest existing parent of the destination,
1284251881Speter     and reparent the ra session there. */
1285251881Speter  if (make_parents)
1286251881Speter    {
1287251881Speter      new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *));
1288251881Speter      SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs,
1289251881Speter                                   scratch_pool));
1290251881Speter    }
1291251881Speter
1292251881Speter  /* Figure out the basename that will result from each copy and check to make
1293251881Speter     sure it doesn't exist already. */
1294251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
1295251881Speter    {
1296251881Speter      svn_node_kind_t dst_kind;
1297251881Speter      const char *dst_rel;
1298251881Speter      svn_client__copy_pair_t *pair =
1299251881Speter        APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1300251881Speter
1301251881Speter      svn_pool_clear(iterpool);
1302251881Speter      dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url,
1303251881Speter                                      iterpool);
1304251881Speter      SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1305251881Speter                                &dst_kind, iterpool));
1306251881Speter      if (dst_kind != svn_node_none)
1307251881Speter        {
1308251881Speter          return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1309251881Speter                                   _("Path '%s' already exists"),
1310251881Speter                                   pair->dst_abspath_or_url);
1311251881Speter        }
1312251881Speter    }
1313251881Speter
1314251881Speter  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1315251881Speter    {
1316251881Speter      /* Produce a list of new paths to add, and provide it to the
1317251881Speter         mechanism used to acquire a log message. */
1318251881Speter      svn_client_commit_item3_t *item;
1319251881Speter      const char *tmp_file;
1320251881Speter      commit_items = apr_array_make(scratch_pool, copy_pairs->nelts,
1321251881Speter                                    sizeof(item));
1322251881Speter
1323251881Speter      /* Add any intermediate directories to the message */
1324251881Speter      if (make_parents)
1325251881Speter        {
1326251881Speter          for (i = 0; i < new_dirs->nelts; i++)
1327251881Speter            {
1328251881Speter              const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
1329251881Speter
1330251881Speter              item = svn_client_commit_item3_create(scratch_pool);
1331251881Speter              item->url = url;
1332251881Speter              item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1333251881Speter              APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1334251881Speter            }
1335251881Speter        }
1336251881Speter
1337251881Speter      for (i = 0; i < copy_pairs->nelts; i++)
1338251881Speter        {
1339251881Speter          svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1340251881Speter                                            svn_client__copy_pair_t *);
1341251881Speter
1342251881Speter          item = svn_client_commit_item3_create(scratch_pool);
1343251881Speter          item->url = pair->dst_abspath_or_url;
1344251881Speter          item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1345251881Speter          APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1346251881Speter        }
1347251881Speter
1348251881Speter      SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1349251881Speter                                      ctx, scratch_pool));
1350251881Speter      if (! message)
1351251881Speter        {
1352251881Speter          svn_pool_destroy(iterpool);
1353251881Speter          svn_pool_destroy(session_pool);
1354251881Speter          return SVN_NO_ERROR;
1355251881Speter        }
1356251881Speter    }
1357251881Speter  else
1358251881Speter    message = "";
1359251881Speter
1360251881Speter  cukb.session = ra_session;
1361251881Speter  SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool));
1362251881Speter  cukb.should_reparent = FALSE;
1363251881Speter
1364251881Speter  /* Crawl the working copy for commit items. */
1365251881Speter  /* ### TODO: Pass check_url_func for issue #3314 handling */
1366251881Speter  SVN_ERR(svn_client__get_copy_committables(&committables,
1367251881Speter                                            copy_pairs,
1368251881Speter                                            check_url_kind, &cukb,
1369251881Speter                                            ctx, scratch_pool, iterpool));
1370251881Speter
1371251881Speter  /* The committables are keyed by the repository root */
1372251881Speter  commit_items = svn_hash_gets(committables->by_repository,
1373251881Speter                               cukb.repos_root_url);
1374251881Speter  SVN_ERR_ASSERT(commit_items != NULL);
1375251881Speter
1376251881Speter  if (cukb.should_reparent)
1377251881Speter    SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
1378251881Speter
1379251881Speter  /* If we are creating intermediate directories, tack them onto the list
1380251881Speter     of committables. */
1381251881Speter  if (make_parents)
1382251881Speter    {
1383251881Speter      for (i = 0; i < new_dirs->nelts; i++)
1384251881Speter        {
1385251881Speter          const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
1386251881Speter          svn_client_commit_item3_t *item;
1387251881Speter
1388251881Speter          item = svn_client_commit_item3_create(scratch_pool);
1389251881Speter          item->url = url;
1390251881Speter          item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1391251881Speter          item->incoming_prop_changes = apr_array_make(scratch_pool, 1,
1392251881Speter                                                       sizeof(svn_prop_t *));
1393251881Speter          APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1394251881Speter        }
1395251881Speter    }
1396251881Speter
1397251881Speter  /* ### TODO: This extra loop would be unnecessary if this code lived
1398251881Speter     ### in svn_client__get_copy_committables(), which is incidentally
1399251881Speter     ### only used above (so should really be in this source file). */
1400251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
1401251881Speter    {
1402251881Speter      apr_hash_t *mergeinfo, *wc_mergeinfo;
1403251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1404251881Speter                                                    svn_client__copy_pair_t *);
1405251881Speter      svn_client_commit_item3_t *item =
1406251881Speter        APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1407251881Speter      svn_client__pathrev_t *src_origin;
1408251881Speter
1409251881Speter      svn_pool_clear(iterpool);
1410251881Speter
1411251881Speter      SVN_ERR(svn_client__wc_node_get_origin(&src_origin,
1412251881Speter                                             pair->src_abspath_or_url,
1413251881Speter                                             ctx, iterpool, iterpool));
1414251881Speter
1415251881Speter      /* Set the mergeinfo for the destination to the combined merge
1416251881Speter         info known to the WC and the repository. */
1417251881Speter      item->outgoing_prop_changes = apr_array_make(scratch_pool, 1,
1418251881Speter                                                   sizeof(svn_prop_t *));
1419251881Speter      /* Repository mergeinfo (or NULL if it's locally added)... */
1420251881Speter      if (src_origin)
1421251881Speter        SVN_ERR(svn_client__get_repos_mergeinfo(
1422251881Speter                  &mergeinfo, ra_session, src_origin->url, src_origin->rev,
1423251881Speter                  svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool));
1424251881Speter      else
1425251881Speter        mergeinfo = NULL;
1426251881Speter      /* ... and WC mergeinfo. */
1427251881Speter      SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
1428251881Speter                                          pair->src_abspath_or_url,
1429251881Speter                                          iterpool, iterpool));
1430251881Speter      if (wc_mergeinfo && mergeinfo)
1431251881Speter        SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool,
1432251881Speter                                     iterpool));
1433251881Speter      else if (! mergeinfo)
1434251881Speter        mergeinfo = wc_mergeinfo;
1435251881Speter      if (mergeinfo)
1436251881Speter        {
1437251881Speter          /* Push a mergeinfo prop representing MERGEINFO onto the
1438251881Speter           * OUTGOING_PROP_CHANGES array. */
1439251881Speter
1440251881Speter          svn_prop_t *mergeinfo_prop
1441251881Speter            = apr_palloc(item->outgoing_prop_changes->pool,
1442251881Speter                         sizeof(svn_prop_t));
1443251881Speter          svn_string_t *prop_value;
1444251881Speter
1445251881Speter          SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo,
1446251881Speter                                          item->outgoing_prop_changes->pool));
1447251881Speter
1448251881Speter          mergeinfo_prop->name = SVN_PROP_MERGEINFO;
1449251881Speter          mergeinfo_prop->value = prop_value;
1450251881Speter          APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *)
1451251881Speter            = mergeinfo_prop;
1452251881Speter        }
1453251881Speter    }
1454251881Speter
1455251881Speter  /* Sort and condense our COMMIT_ITEMS. */
1456251881Speter  SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
1457251881Speter                                            commit_items, scratch_pool));
1458251881Speter
1459251881Speter#ifdef ENABLE_EV2_SHIMS
1460251881Speter  if (commit_items)
1461251881Speter    {
1462251881Speter      relpath_map = apr_hash_make(pool);
1463251881Speter      for (i = 0; i < commit_items->nelts; i++)
1464251881Speter        {
1465251881Speter          svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
1466251881Speter                                                  svn_client_commit_item3_t *);
1467251881Speter          const char *relpath;
1468251881Speter
1469251881Speter          if (!item->path)
1470251881Speter            continue;
1471251881Speter
1472251881Speter          svn_pool_clear(iterpool);
1473251881Speter          SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL, NULL,
1474251881Speter                                          ctx->wc_ctx, item->path, FALSE,
1475251881Speter                                          scratch_pool, iterpool));
1476251881Speter          if (relpath)
1477251881Speter            svn_hash_sets(relpath_map, relpath, item->path);
1478251881Speter        }
1479251881Speter    }
1480251881Speter#endif
1481251881Speter
1482251881Speter  /* Close the initial session, to reopen a new session with commit handling */
1483251881Speter  svn_pool_clear(session_pool);
1484251881Speter
1485251881Speter  /* Open a new RA session to DST_URL. */
1486251881Speter  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
1487251881Speter                                               NULL, commit_items,
1488251881Speter                                               FALSE, FALSE, ctx,
1489251881Speter                                               session_pool, session_pool));
1490251881Speter
1491251881Speter  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1492251881Speter                                           message, ctx, session_pool));
1493251881Speter
1494251881Speter  /* Fetch RA commit editor. */
1495251881Speter  SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1496251881Speter                        svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map,
1497251881Speter                                                       session_pool)));
1498251881Speter  SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1499251881Speter                                    commit_revprops,
1500251881Speter                                    commit_callback,
1501251881Speter                                    commit_baton, NULL,
1502251881Speter                                    TRUE, /* No lock tokens */
1503251881Speter                                    session_pool));
1504251881Speter
1505251881Speter  /* Perform the commit. */
1506251881Speter  SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items,
1507251881Speter                                  editor, edit_baton,
1508251881Speter                                  0, /* ### any notify_path_offset needed? */
1509251881Speter                                  NULL, ctx, session_pool, session_pool),
1510251881Speter            _("Commit failed (details follow):"));
1511251881Speter
1512251881Speter  svn_pool_destroy(iterpool);
1513251881Speter  svn_pool_destroy(session_pool);
1514251881Speter
1515251881Speter  return SVN_NO_ERROR;
1516251881Speter}
1517251881Speter
1518251881Speter/* A baton for notification_adjust_func(). */
1519251881Speterstruct notification_adjust_baton
1520251881Speter{
1521251881Speter  svn_wc_notify_func2_t inner_func;
1522251881Speter  void *inner_baton;
1523251881Speter  const char *checkout_abspath;
1524251881Speter  const char *final_abspath;
1525251881Speter};
1526251881Speter
1527251881Speter/* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
1528251881Speter * baton is BATON->inner_baton) and adjusts the notification paths that
1529251881Speter * start with BATON->checkout_abspath to start instead with
1530251881Speter * BATON->final_abspath. */
1531251881Speterstatic void
1532251881Speternotification_adjust_func(void *baton,
1533251881Speter                         const svn_wc_notify_t *notify,
1534251881Speter                         apr_pool_t *pool)
1535251881Speter{
1536251881Speter  struct notification_adjust_baton *nb = baton;
1537251881Speter  svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
1538251881Speter  const char *relpath;
1539251881Speter
1540251881Speter  relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
1541251881Speter  inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
1542251881Speter
1543251881Speter  if (nb->inner_func)
1544251881Speter    nb->inner_func(nb->inner_baton, inner_notify, pool);
1545251881Speter}
1546251881Speter
1547251881Speter/* Peform each individual copy operation for a repos -> wc copy.  A
1548251881Speter   helper for repos_to_wc_copy().
1549251881Speter
1550251881Speter   Resolve PAIR->src_revnum to a real revision number if it isn't already. */
1551251881Speterstatic svn_error_t *
1552251881Speterrepos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
1553251881Speter                        svn_client__copy_pair_t *pair,
1554251881Speter                        svn_boolean_t same_repositories,
1555251881Speter                        svn_boolean_t ignore_externals,
1556251881Speter                        svn_ra_session_t *ra_session,
1557251881Speter                        svn_client_ctx_t *ctx,
1558251881Speter                        apr_pool_t *pool)
1559251881Speter{
1560251881Speter  apr_hash_t *src_mergeinfo;
1561251881Speter  const char *dst_abspath = pair->dst_abspath_or_url;
1562251881Speter
1563251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
1564251881Speter
1565251881Speter  if (!same_repositories && ctx->notify_func2)
1566251881Speter    {
1567251881Speter      svn_wc_notify_t *notify;
1568251881Speter      notify = svn_wc_create_notify_url(
1569251881Speter                            pair->src_abspath_or_url,
1570251881Speter                            svn_wc_notify_foreign_copy_begin,
1571251881Speter                            pool);
1572251881Speter      notify->kind = pair->src_kind;
1573251881Speter      ctx->notify_func2(ctx->notify_baton2, notify, pool);
1574251881Speter
1575251881Speter      /* Allow a theoretical cancel to get through. */
1576251881Speter      if (ctx->cancel_func)
1577251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1578251881Speter    }
1579251881Speter
1580251881Speter  if (pair->src_kind == svn_node_dir)
1581251881Speter    {
1582251881Speter      if (same_repositories)
1583251881Speter        {
1584251881Speter          svn_boolean_t sleep_needed = FALSE;
1585251881Speter          const char *tmpdir_abspath, *tmp_abspath;
1586251881Speter
1587251881Speter          /* Find a temporary location in which to check out the copy source. */
1588251881Speter          SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
1589251881Speter                                     pool, pool));
1590251881Speter
1591251881Speter          SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
1592251881Speter                                           svn_io_file_del_on_close, pool, pool));
1593251881Speter
1594251881Speter          /* Make a new checkout of the requested source. While doing so,
1595251881Speter           * resolve pair->src_revnum to an actual revision number in case it
1596251881Speter           * was until now 'invalid' meaning 'head'.  Ask this function not to
1597251881Speter           * sleep for timestamps, by passing a sleep_needed output param.
1598251881Speter           * Send notifications for all nodes except the root node, and adjust
1599251881Speter           * them to refer to the destination rather than this temporary path. */
1600251881Speter          {
1601251881Speter            svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
1602251881Speter            void *old_notify_baton2 = ctx->notify_baton2;
1603251881Speter            struct notification_adjust_baton nb;
1604251881Speter            svn_error_t *err;
1605251881Speter
1606251881Speter            nb.inner_func = ctx->notify_func2;
1607251881Speter            nb.inner_baton = ctx->notify_baton2;
1608251881Speter            nb.checkout_abspath = tmp_abspath;
1609251881Speter            nb.final_abspath = dst_abspath;
1610251881Speter            ctx->notify_func2 = notification_adjust_func;
1611251881Speter            ctx->notify_baton2 = &nb;
1612251881Speter
1613251881Speter            err = svn_client__checkout_internal(&pair->src_revnum,
1614251881Speter                                                pair->src_original,
1615251881Speter                                                tmp_abspath,
1616251881Speter                                                &pair->src_peg_revision,
1617251881Speter                                                &pair->src_op_revision,
1618251881Speter                                                svn_depth_infinity,
1619251881Speter                                                ignore_externals, FALSE,
1620251881Speter                                                &sleep_needed, ctx, pool);
1621251881Speter
1622251881Speter            ctx->notify_func2 = old_notify_func2;
1623251881Speter            ctx->notify_baton2 = old_notify_baton2;
1624251881Speter
1625251881Speter            SVN_ERR(err);
1626251881Speter          }
1627251881Speter
1628251881Speter          *timestamp_sleep = TRUE;
1629251881Speter
1630251881Speter      /* Schedule dst_path for addition in parent, with copy history.
1631251881Speter         Don't send any notification here.
1632251881Speter         Then remove the temporary checkout's .svn dir in preparation for
1633251881Speter         moving the rest of it into the final destination. */
1634251881Speter          SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
1635251881Speter                               TRUE /* metadata_only */,
1636251881Speter                               ctx->cancel_func, ctx->cancel_baton,
1637251881Speter                               NULL, NULL, pool));
1638251881Speter          SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
1639251881Speter                                             FALSE, pool, pool));
1640251881Speter          SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
1641251881Speter                                                       tmp_abspath,
1642251881Speter                                                       FALSE, FALSE,
1643251881Speter                                                       ctx->cancel_func,
1644251881Speter                                                       ctx->cancel_baton,
1645251881Speter                                                       pool));
1646251881Speter
1647251881Speter          /* Move the temporary disk tree into place. */
1648251881Speter          SVN_ERR(svn_io_file_rename(tmp_abspath, dst_abspath, pool));
1649251881Speter        }
1650251881Speter      else
1651251881Speter        {
1652251881Speter          *timestamp_sleep = TRUE;
1653251881Speter
1654251881Speter          SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url,
1655251881Speter                                           dst_abspath,
1656251881Speter                                           &pair->src_peg_revision,
1657251881Speter                                           &pair->src_op_revision,
1658251881Speter                                           svn_depth_infinity,
1659251881Speter                                           FALSE /* make_parents */,
1660251881Speter                                           TRUE /* already_locked */,
1661251881Speter                                           ctx, pool));
1662251881Speter
1663251881Speter          return SVN_NO_ERROR;
1664251881Speter        }
1665251881Speter    } /* end directory case */
1666251881Speter
1667251881Speter  else if (pair->src_kind == svn_node_file)
1668251881Speter    {
1669251881Speter      apr_hash_t *new_props;
1670251881Speter      const char *src_rel;
1671251881Speter      svn_stream_t *new_base_contents = svn_stream_buffered(pool);
1672251881Speter
1673251881Speter      SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
1674251881Speter                                                  pair->src_abspath_or_url,
1675251881Speter                                                  pool));
1676251881Speter      /* Fetch the file content. While doing so, resolve pair->src_revnum
1677251881Speter       * to an actual revision number if it's 'invalid' meaning 'head'. */
1678251881Speter      SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum,
1679251881Speter                              new_base_contents,
1680251881Speter                              &pair->src_revnum, &new_props, pool));
1681251881Speter
1682251881Speter      if (new_props && ! same_repositories)
1683251881Speter        svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
1684251881Speter
1685251881Speter      *timestamp_sleep = TRUE;
1686251881Speter
1687251881Speter      SVN_ERR(svn_wc_add_repos_file4(
1688251881Speter         ctx->wc_ctx, dst_abspath,
1689251881Speter         new_base_contents, NULL, new_props, NULL,
1690251881Speter         same_repositories ? pair->src_abspath_or_url : NULL,
1691251881Speter         same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM,
1692251881Speter         ctx->cancel_func, ctx->cancel_baton,
1693251881Speter         pool));
1694251881Speter    }
1695251881Speter
1696251881Speter  /* Record the implied mergeinfo (before the notification callback
1697251881Speter     is invoked for the root node). */
1698251881Speter  SVN_ERR(svn_client__get_repos_mergeinfo(
1699251881Speter            &src_mergeinfo, ra_session,
1700251881Speter            pair->src_abspath_or_url, pair->src_revnum,
1701251881Speter            svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
1702251881Speter  SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
1703251881Speter
1704251881Speter  /* Do our own notification for the root node, even if we could possibly
1705251881Speter     have delegated it.  See also issue #1552.
1706251881Speter
1707251881Speter     ### Maybe this notification should mention the mergeinfo change. */
1708251881Speter  if (ctx->notify_func2)
1709251881Speter    {
1710251881Speter      svn_wc_notify_t *notify = svn_wc_create_notify(
1711251881Speter                                  dst_abspath, svn_wc_notify_add, pool);
1712251881Speter      notify->kind = pair->src_kind;
1713251881Speter      (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1714251881Speter    }
1715251881Speter
1716251881Speter  return SVN_NO_ERROR;
1717251881Speter}
1718251881Speter
1719251881Speterstatic svn_error_t *
1720251881Speterrepos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
1721251881Speter                        const apr_array_header_t *copy_pairs,
1722251881Speter                        const char *top_dst_path,
1723251881Speter                        svn_boolean_t ignore_externals,
1724251881Speter                        svn_ra_session_t *ra_session,
1725251881Speter                        svn_client_ctx_t *ctx,
1726251881Speter                        apr_pool_t *scratch_pool)
1727251881Speter{
1728251881Speter  int i;
1729251881Speter  svn_boolean_t same_repositories;
1730251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1731251881Speter
1732251881Speter  /* We've already checked for physical obstruction by a working file.
1733251881Speter     But there could also be logical obstruction by an entry whose
1734251881Speter     working file happens to be missing.*/
1735253734Speter  SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */,
1736253734Speter                         ctx, scratch_pool, iterpool));
1737251881Speter
1738251881Speter  /* Decide whether the two repositories are the same or not. */
1739251881Speter  {
1740251881Speter    svn_error_t *src_err, *dst_err;
1741251881Speter    const char *parent;
1742251881Speter    const char *parent_abspath;
1743251881Speter    const char *src_uuid, *dst_uuid;
1744251881Speter
1745251881Speter    /* Get the repository uuid of SRC_URL */
1746251881Speter    src_err = svn_ra_get_uuid2(ra_session, &src_uuid, iterpool);
1747251881Speter    if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
1748251881Speter      return svn_error_trace(src_err);
1749251881Speter
1750251881Speter    /* Get repository uuid of dst's parent directory, since dst may
1751251881Speter       not exist.  ### TODO:  we should probably walk up the wc here,
1752251881Speter       in case the parent dir has an imaginary URL.  */
1753251881Speter    if (copy_pairs->nelts == 1)
1754251881Speter      parent = svn_dirent_dirname(top_dst_path, scratch_pool);
1755251881Speter    else
1756251881Speter      parent = top_dst_path;
1757251881Speter
1758251881Speter    SVN_ERR(svn_dirent_get_absolute(&parent_abspath, parent, scratch_pool));
1759251881Speter    dst_err = svn_client_get_repos_root(NULL /* root_url */, &dst_uuid,
1760251881Speter                                        parent_abspath, ctx,
1761251881Speter                                        iterpool, iterpool);
1762251881Speter    if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
1763251881Speter      return dst_err;
1764251881Speter
1765251881Speter    /* If either of the UUIDs are nonexistent, then at least one of
1766251881Speter       the repositories must be very old.  Rather than punish the
1767251881Speter       user, just assume the repositories are different, so no
1768251881Speter       copy-history is attempted. */
1769251881Speter    if (src_err || dst_err || (! src_uuid) || (! dst_uuid))
1770251881Speter      same_repositories = FALSE;
1771251881Speter    else
1772251881Speter      same_repositories = (strcmp(src_uuid, dst_uuid) == 0);
1773251881Speter  }
1774251881Speter
1775251881Speter  /* Perform the move for each of the copy_pairs. */
1776251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
1777251881Speter    {
1778251881Speter      /* Check for cancellation */
1779251881Speter      if (ctx->cancel_func)
1780251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1781251881Speter
1782251881Speter      svn_pool_clear(iterpool);
1783251881Speter
1784251881Speter      SVN_ERR(repos_to_wc_copy_single(timestamp_sleep,
1785251881Speter                                      APR_ARRAY_IDX(copy_pairs, i,
1786251881Speter                                                    svn_client__copy_pair_t *),
1787251881Speter                                      same_repositories,
1788251881Speter                                      ignore_externals,
1789251881Speter                                      ra_session, ctx, iterpool));
1790251881Speter    }
1791251881Speter  svn_pool_destroy(iterpool);
1792251881Speter
1793251881Speter  return SVN_NO_ERROR;
1794251881Speter}
1795251881Speter
1796251881Speterstatic svn_error_t *
1797251881Speterrepos_to_wc_copy(svn_boolean_t *timestamp_sleep,
1798251881Speter                 const apr_array_header_t *copy_pairs,
1799251881Speter                 svn_boolean_t make_parents,
1800251881Speter                 svn_boolean_t ignore_externals,
1801251881Speter                 svn_client_ctx_t *ctx,
1802251881Speter                 apr_pool_t *pool)
1803251881Speter{
1804251881Speter  svn_ra_session_t *ra_session;
1805251881Speter  const char *top_src_url, *top_dst_path;
1806251881Speter  apr_pool_t *iterpool = svn_pool_create(pool);
1807251881Speter  const char *lock_abspath;
1808251881Speter  int i;
1809251881Speter
1810251881Speter  /* Get the real path for the source, based upon its peg revision. */
1811251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
1812251881Speter    {
1813251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1814251881Speter                                                    svn_client__copy_pair_t *);
1815251881Speter      const char *src;
1816251881Speter
1817251881Speter      svn_pool_clear(iterpool);
1818251881Speter
1819251881Speter      SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL,
1820251881Speter                                          NULL,
1821251881Speter                                          pair->src_abspath_or_url,
1822251881Speter                                          &pair->src_peg_revision,
1823251881Speter                                          &pair->src_op_revision, NULL,
1824251881Speter                                          ctx, iterpool));
1825251881Speter
1826251881Speter      pair->src_original = pair->src_abspath_or_url;
1827251881Speter      pair->src_abspath_or_url = apr_pstrdup(pool, src);
1828251881Speter    }
1829251881Speter
1830251881Speter  SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_path,
1831251881Speter                                  NULL, pool));
1832251881Speter  lock_abspath = top_dst_path;
1833251881Speter  if (copy_pairs->nelts == 1)
1834251881Speter    {
1835251881Speter      top_src_url = svn_uri_dirname(top_src_url, pool);
1836251881Speter      lock_abspath = svn_dirent_dirname(top_dst_path, pool);
1837251881Speter    }
1838251881Speter
1839251881Speter  /* Open a repository session to the longest common src ancestor.  We do not
1840251881Speter     (yet) have a working copy, so we don't have a corresponding path and
1841251881Speter     tempfiles cannot go into the admin area. */
1842251881Speter  SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath,
1843251881Speter                                      ctx, pool, pool));
1844251881Speter
1845251881Speter  /* Get the correct src path for the peg revision used, and verify that we
1846251881Speter     aren't overwriting an existing path. */
1847251881Speter  for (i = 0; i < copy_pairs->nelts; i++)
1848251881Speter    {
1849251881Speter      svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1850251881Speter                                                    svn_client__copy_pair_t *);
1851251881Speter      svn_node_kind_t dst_parent_kind, dst_kind;
1852251881Speter      const char *dst_parent;
1853251881Speter      const char *src_rel;
1854251881Speter
1855251881Speter      svn_pool_clear(iterpool);
1856251881Speter
1857251881Speter      /* Next, make sure that the path exists in the repository. */
1858251881Speter      SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
1859251881Speter                                                  pair->src_abspath_or_url,
1860251881Speter                                                  iterpool));
1861251881Speter      SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1862251881Speter                                &pair->src_kind, pool));
1863251881Speter      if (pair->src_kind == svn_node_none)
1864251881Speter        {
1865251881Speter          if (SVN_IS_VALID_REVNUM(pair->src_revnum))
1866251881Speter            return svn_error_createf
1867251881Speter              (SVN_ERR_FS_NOT_FOUND, NULL,
1868251881Speter               _("Path '%s' not found in revision %ld"),
1869251881Speter               pair->src_abspath_or_url, pair->src_revnum);
1870251881Speter          else
1871251881Speter            return svn_error_createf
1872251881Speter              (SVN_ERR_FS_NOT_FOUND, NULL,
1873251881Speter               _("Path '%s' not found in head revision"),
1874251881Speter               pair->src_abspath_or_url);
1875251881Speter        }
1876251881Speter
1877251881Speter      /* Figure out about dst. */
1878251881Speter      SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
1879251881Speter                                iterpool));
1880251881Speter      if (dst_kind != svn_node_none)
1881251881Speter        {
1882251881Speter          return svn_error_createf(
1883251881Speter            SVN_ERR_ENTRY_EXISTS, NULL,
1884251881Speter            _("Path '%s' already exists"),
1885251881Speter            svn_dirent_local_style(pair->dst_abspath_or_url, pool));
1886251881Speter        }
1887251881Speter
1888251881Speter      /* Make sure the destination parent is a directory and produce a clear
1889251881Speter         error message if it is not. */
1890251881Speter      dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool);
1891251881Speter      SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool));
1892251881Speter      if (make_parents && dst_parent_kind == svn_node_none)
1893251881Speter        {
1894251881Speter          SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx,
1895251881Speter                                                 iterpool));
1896251881Speter        }
1897251881Speter      else if (dst_parent_kind != svn_node_dir)
1898251881Speter        {
1899251881Speter          return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
1900251881Speter                                   _("Path '%s' is not a directory"),
1901251881Speter                                   svn_dirent_local_style(dst_parent, pool));
1902251881Speter        }
1903251881Speter    }
1904251881Speter  svn_pool_destroy(iterpool);
1905251881Speter
1906251881Speter  SVN_WC__CALL_WITH_WRITE_LOCK(
1907251881Speter    repos_to_wc_copy_locked(timestamp_sleep,
1908251881Speter                            copy_pairs, top_dst_path, ignore_externals,
1909251881Speter                            ra_session, ctx, pool),
1910251881Speter    ctx->wc_ctx, lock_abspath, FALSE, pool);
1911251881Speter  return SVN_NO_ERROR;
1912251881Speter}
1913251881Speter
1914251881Speter#define NEED_REPOS_REVNUM(revision) \
1915251881Speter        ((revision.kind != svn_opt_revision_unspecified) \
1916251881Speter          && (revision.kind != svn_opt_revision_working))
1917251881Speter
1918251881Speter/* ...
1919251881Speter *
1920251881Speter * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
1921251881Speter * change *TIMESTAMP_SLEEP.  This output will be valid even if the
1922251881Speter * function returns an error.
1923251881Speter *
1924251881Speter * Perform all allocations in POOL.
1925251881Speter */
1926251881Speterstatic svn_error_t *
1927251881Spetertry_copy(svn_boolean_t *timestamp_sleep,
1928251881Speter         const apr_array_header_t *sources,
1929251881Speter         const char *dst_path_in,
1930251881Speter         svn_boolean_t is_move,
1931251881Speter         svn_boolean_t allow_mixed_revisions,
1932251881Speter         svn_boolean_t metadata_only,
1933251881Speter         svn_boolean_t make_parents,
1934251881Speter         svn_boolean_t ignore_externals,
1935251881Speter         const apr_hash_t *revprop_table,
1936251881Speter         svn_commit_callback2_t commit_callback,
1937251881Speter         void *commit_baton,
1938251881Speter         svn_client_ctx_t *ctx,
1939251881Speter         apr_pool_t *pool)
1940251881Speter{
1941251881Speter  apr_array_header_t *copy_pairs =
1942251881Speter                        apr_array_make(pool, sources->nelts,
1943251881Speter                                       sizeof(svn_client__copy_pair_t *));
1944251881Speter  svn_boolean_t srcs_are_urls, dst_is_url;
1945251881Speter  int i;
1946251881Speter
1947251881Speter  /* Are either of our paths URLs?  Just check the first src_path.  If
1948251881Speter     there are more than one, we'll check for homogeneity among them
1949251881Speter     down below. */
1950251881Speter  srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
1951251881Speter                                  svn_client_copy_source_t *)->path);
1952251881Speter  dst_is_url = svn_path_is_url(dst_path_in);
1953251881Speter  if (!dst_is_url)
1954251881Speter    SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool));
1955251881Speter
1956251881Speter  /* If we have multiple source paths, it implies the dst_path is a
1957251881Speter     directory we are moving or copying into.  Populate the COPY_PAIRS
1958251881Speter     array to contain a destination path for each of the source paths. */
1959251881Speter  if (sources->nelts > 1)
1960251881Speter    {
1961251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
1962251881Speter
1963251881Speter      for (i = 0; i < sources->nelts; i++)
1964251881Speter        {
1965251881Speter          svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
1966251881Speter                                               svn_client_copy_source_t *);
1967251881Speter          svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
1968251881Speter          const char *src_basename;
1969251881Speter          svn_boolean_t src_is_url = svn_path_is_url(source->path);
1970251881Speter
1971251881Speter          svn_pool_clear(iterpool);
1972251881Speter
1973251881Speter          if (src_is_url)
1974251881Speter            {
1975251881Speter              pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
1976251881Speter              src_basename = svn_uri_basename(pair->src_abspath_or_url,
1977251881Speter                                              iterpool);
1978251881Speter            }
1979251881Speter          else
1980251881Speter            {
1981251881Speter              SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
1982251881Speter                                              source->path, pool));
1983251881Speter              src_basename = svn_dirent_basename(pair->src_abspath_or_url,
1984251881Speter                                                 iterpool);
1985251881Speter            }
1986251881Speter
1987251881Speter          pair->src_op_revision = *source->revision;
1988251881Speter          pair->src_peg_revision = *source->peg_revision;
1989251881Speter
1990251881Speter          SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
1991251881Speter                                            &pair->src_op_revision,
1992251881Speter                                            src_is_url,
1993251881Speter                                            TRUE,
1994251881Speter                                            iterpool));
1995251881Speter
1996251881Speter          /* Check to see if all the sources are urls or all working copy
1997251881Speter           * paths. */
1998251881Speter          if (src_is_url != srcs_are_urls)
1999251881Speter            return svn_error_create
2000251881Speter              (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2001251881Speter               _("Cannot mix repository and working copy sources"));
2002251881Speter
2003251881Speter          if (dst_is_url)
2004251881Speter            pair->dst_abspath_or_url =
2005251881Speter              svn_path_url_add_component2(dst_path_in, src_basename, pool);
2006251881Speter          else
2007251881Speter            pair->dst_abspath_or_url = svn_dirent_join(dst_path_in,
2008251881Speter                                                       src_basename, pool);
2009251881Speter          APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2010251881Speter        }
2011251881Speter
2012251881Speter      svn_pool_destroy(iterpool);
2013251881Speter    }
2014251881Speter  else
2015251881Speter    {
2016251881Speter      /* Only one source path. */
2017251881Speter      svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
2018251881Speter      svn_client_copy_source_t *source =
2019251881Speter        APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
2020251881Speter      svn_boolean_t src_is_url = svn_path_is_url(source->path);
2021251881Speter
2022251881Speter      if (src_is_url)
2023251881Speter        pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2024251881Speter      else
2025251881Speter        SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2026251881Speter                                        source->path, pool));
2027251881Speter      pair->src_op_revision = *source->revision;
2028251881Speter      pair->src_peg_revision = *source->peg_revision;
2029251881Speter
2030251881Speter      SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2031251881Speter                                        &pair->src_op_revision,
2032251881Speter                                        src_is_url, TRUE, pool));
2033251881Speter
2034251881Speter      pair->dst_abspath_or_url = dst_path_in;
2035251881Speter      APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2036251881Speter    }
2037251881Speter
2038251881Speter  if (!srcs_are_urls && !dst_is_url)
2039251881Speter    {
2040251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
2041251881Speter
2042251881Speter      for (i = 0; i < copy_pairs->nelts; i++)
2043251881Speter        {
2044251881Speter          svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2045251881Speter                                            svn_client__copy_pair_t *);
2046251881Speter
2047251881Speter          svn_pool_clear(iterpool);
2048251881Speter
2049251881Speter          if (svn_dirent_is_child(pair->src_abspath_or_url,
2050251881Speter                                  pair->dst_abspath_or_url, iterpool))
2051251881Speter            return svn_error_createf
2052251881Speter              (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2053251881Speter               _("Cannot copy path '%s' into its own child '%s'"),
2054251881Speter               svn_dirent_local_style(pair->src_abspath_or_url, pool),
2055251881Speter               svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2056251881Speter        }
2057251881Speter
2058251881Speter      svn_pool_destroy(iterpool);
2059251881Speter    }
2060251881Speter
2061251881Speter  /* A file external should not be moved since the file external is
2062251881Speter     implemented as a switched file and it would delete the file the
2063251881Speter     file external is switched to, which is not the behavior the user
2064251881Speter     would probably want. */
2065251881Speter  if (is_move && !srcs_are_urls)
2066251881Speter    {
2067251881Speter      apr_pool_t *iterpool = svn_pool_create(pool);
2068251881Speter
2069251881Speter      for (i = 0; i < copy_pairs->nelts; i++)
2070251881Speter        {
2071251881Speter          svn_client__copy_pair_t *pair =
2072251881Speter            APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2073251881Speter          svn_node_kind_t external_kind;
2074251881Speter          const char *defining_abspath;
2075251881Speter
2076251881Speter          svn_pool_clear(iterpool);
2077251881Speter
2078251881Speter          SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
2079251881Speter          SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath,
2080251881Speter                                             NULL, NULL, NULL, ctx->wc_ctx,
2081251881Speter                                             pair->src_abspath_or_url,
2082251881Speter                                             pair->src_abspath_or_url, TRUE,
2083251881Speter                                             iterpool, iterpool));
2084251881Speter
2085251881Speter          if (external_kind != svn_node_none)
2086251881Speter            return svn_error_createf(
2087251881Speter                     SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL,
2088251881Speter                     NULL,
2089251881Speter                     _("Cannot move the external at '%s'; please "
2090251881Speter                       "edit the svn:externals property on '%s'."),
2091251881Speter                     svn_dirent_local_style(pair->src_abspath_or_url, pool),
2092251881Speter                     svn_dirent_local_style(defining_abspath, pool));
2093251881Speter        }
2094251881Speter      svn_pool_destroy(iterpool);
2095251881Speter    }
2096251881Speter
2097251881Speter  if (is_move)
2098251881Speter    {
2099251881Speter      /* Disallow moves between the working copy and the repository. */
2100251881Speter      if (srcs_are_urls != dst_is_url)
2101251881Speter        {
2102251881Speter          return svn_error_create
2103251881Speter            (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2104251881Speter             _("Moves between the working copy and the repository are not "
2105251881Speter               "supported"));
2106251881Speter        }
2107251881Speter
2108251881Speter      /* Disallow moving any path/URL onto or into itself. */
2109251881Speter      for (i = 0; i < copy_pairs->nelts; i++)
2110251881Speter        {
2111251881Speter          svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2112251881Speter                                            svn_client__copy_pair_t *);
2113251881Speter
2114251881Speter          if (strcmp(pair->src_abspath_or_url,
2115251881Speter                     pair->dst_abspath_or_url) == 0)
2116251881Speter            return svn_error_createf(
2117251881Speter              SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2118251881Speter              srcs_are_urls ?
2119251881Speter                _("Cannot move URL '%s' into itself") :
2120251881Speter                _("Cannot move path '%s' into itself"),
2121251881Speter              srcs_are_urls ?
2122251881Speter                pair->src_abspath_or_url :
2123251881Speter                svn_dirent_local_style(pair->src_abspath_or_url, pool));
2124251881Speter        }
2125251881Speter    }
2126251881Speter  else
2127251881Speter    {
2128251881Speter      if (!srcs_are_urls)
2129251881Speter        {
2130251881Speter          /* If we are doing a wc->* copy, but with an operational revision
2131251881Speter             other than the working copy revision, we are really doing a
2132251881Speter             repo->* copy, because we're going to need to get the rev from the
2133251881Speter             repo. */
2134251881Speter
2135251881Speter          svn_boolean_t need_repos_op_rev = FALSE;
2136251881Speter          svn_boolean_t need_repos_peg_rev = FALSE;
2137251881Speter
2138251881Speter          /* Check to see if any revision is something other than
2139251881Speter             svn_opt_revision_unspecified or svn_opt_revision_working. */
2140251881Speter          for (i = 0; i < copy_pairs->nelts; i++)
2141251881Speter            {
2142251881Speter              svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2143251881Speter                                                svn_client__copy_pair_t *);
2144251881Speter
2145251881Speter              if (NEED_REPOS_REVNUM(pair->src_op_revision))
2146251881Speter                need_repos_op_rev = TRUE;
2147251881Speter
2148251881Speter              if (NEED_REPOS_REVNUM(pair->src_peg_revision))
2149251881Speter                need_repos_peg_rev = TRUE;
2150251881Speter
2151251881Speter              if (need_repos_op_rev || need_repos_peg_rev)
2152251881Speter                break;
2153251881Speter            }
2154251881Speter
2155251881Speter          if (need_repos_op_rev || need_repos_peg_rev)
2156251881Speter            {
2157251881Speter              apr_pool_t *iterpool = svn_pool_create(pool);
2158251881Speter
2159251881Speter              for (i = 0; i < copy_pairs->nelts; i++)
2160251881Speter                {
2161251881Speter                  const char *copyfrom_repos_root_url;
2162251881Speter                  const char *copyfrom_repos_relpath;
2163251881Speter                  const char *url;
2164251881Speter                  svn_revnum_t copyfrom_rev;
2165251881Speter                  svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2166251881Speter                                                    svn_client__copy_pair_t *);
2167251881Speter
2168251881Speter                  svn_pool_clear(iterpool);
2169251881Speter
2170251881Speter                  SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
2171251881Speter
2172251881Speter                  SVN_ERR(svn_wc__node_get_origin(NULL, &copyfrom_rev,
2173251881Speter                                                  &copyfrom_repos_relpath,
2174251881Speter                                                  &copyfrom_repos_root_url,
2175251881Speter                                                  NULL, NULL,
2176251881Speter                                                  ctx->wc_ctx,
2177251881Speter                                                  pair->src_abspath_or_url,
2178251881Speter                                                  TRUE, iterpool, iterpool));
2179251881Speter
2180251881Speter                  if (copyfrom_repos_relpath)
2181251881Speter                    url = svn_path_url_add_component2(copyfrom_repos_root_url,
2182251881Speter                                                      copyfrom_repos_relpath,
2183251881Speter                                                      pool);
2184251881Speter                  else
2185251881Speter                    return svn_error_createf
2186251881Speter                      (SVN_ERR_ENTRY_MISSING_URL, NULL,
2187251881Speter                       _("'%s' does not have a URL associated with it"),
2188251881Speter                       svn_dirent_local_style(pair->src_abspath_or_url, pool));
2189251881Speter
2190251881Speter                  pair->src_abspath_or_url = url;
2191251881Speter
2192251881Speter                  if (!need_repos_peg_rev
2193251881Speter                      || pair->src_peg_revision.kind == svn_opt_revision_base)
2194251881Speter                    {
2195251881Speter                      /* Default the peg revision to that of the WC entry. */
2196251881Speter                      pair->src_peg_revision.kind = svn_opt_revision_number;
2197251881Speter                      pair->src_peg_revision.value.number = copyfrom_rev;
2198251881Speter                    }
2199251881Speter
2200251881Speter                  if (pair->src_op_revision.kind == svn_opt_revision_base)
2201251881Speter                    {
2202251881Speter                      /* Use the entry's revision as the operational rev. */
2203251881Speter                      pair->src_op_revision.kind = svn_opt_revision_number;
2204251881Speter                      pair->src_op_revision.value.number = copyfrom_rev;
2205251881Speter                    }
2206251881Speter                }
2207251881Speter
2208251881Speter              svn_pool_destroy(iterpool);
2209251881Speter              srcs_are_urls = TRUE;
2210251881Speter            }
2211251881Speter        }
2212251881Speter    }
2213251881Speter
2214251881Speter  /* Now, call the right handler for the operation. */
2215251881Speter  if ((! srcs_are_urls) && (! dst_is_url))
2216251881Speter    {
2217251881Speter      SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move,
2218253734Speter                                      metadata_only, ctx, pool, pool));
2219251881Speter
2220251881Speter      /* Copy or move all targets. */
2221251881Speter      if (is_move)
2222251881Speter        return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep,
2223251881Speter                                                 copy_pairs, dst_path_in,
2224251881Speter                                                 allow_mixed_revisions,
2225251881Speter                                                 metadata_only,
2226251881Speter                                                 ctx, pool));
2227251881Speter      else
2228251881Speter        {
2229251881Speter          /* We ignore these values, so assert the default value */
2230251881Speter          SVN_ERR_ASSERT(allow_mixed_revisions && !metadata_only);
2231251881Speter          return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
2232251881Speter                                                    copy_pairs, ctx, pool));
2233251881Speter        }
2234251881Speter    }
2235251881Speter  else if ((! srcs_are_urls) && (dst_is_url))
2236251881Speter    {
2237251881Speter      return svn_error_trace(
2238251881Speter        wc_to_repos_copy(copy_pairs, make_parents, revprop_table,
2239251881Speter                         commit_callback, commit_baton, ctx, pool));
2240251881Speter    }
2241251881Speter  else if ((srcs_are_urls) && (! dst_is_url))
2242251881Speter    {
2243251881Speter      return svn_error_trace(
2244251881Speter        repos_to_wc_copy(timestamp_sleep,
2245251881Speter                         copy_pairs, make_parents, ignore_externals,
2246251881Speter                         ctx, pool));
2247251881Speter    }
2248251881Speter  else
2249251881Speter    {
2250251881Speter      return svn_error_trace(
2251251881Speter        repos_to_repos_copy(copy_pairs, make_parents, revprop_table,
2252251881Speter                            commit_callback, commit_baton, ctx, is_move,
2253251881Speter                            pool));
2254251881Speter    }
2255251881Speter}
2256251881Speter
2257251881Speter
2258251881Speter
2259251881Speter/* Public Interfaces */
2260251881Spetersvn_error_t *
2261251881Spetersvn_client_copy6(const apr_array_header_t *sources,
2262251881Speter                 const char *dst_path,
2263251881Speter                 svn_boolean_t copy_as_child,
2264251881Speter                 svn_boolean_t make_parents,
2265251881Speter                 svn_boolean_t ignore_externals,
2266251881Speter                 const apr_hash_t *revprop_table,
2267251881Speter                 svn_commit_callback2_t commit_callback,
2268251881Speter                 void *commit_baton,
2269251881Speter                 svn_client_ctx_t *ctx,
2270251881Speter                 apr_pool_t *pool)
2271251881Speter{
2272251881Speter  svn_error_t *err;
2273251881Speter  svn_boolean_t timestamp_sleep = FALSE;
2274251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
2275251881Speter
2276251881Speter  if (sources->nelts > 1 && !copy_as_child)
2277251881Speter    return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
2278251881Speter                            NULL, NULL);
2279251881Speter
2280251881Speter  err = try_copy(&timestamp_sleep,
2281251881Speter                 sources, dst_path,
2282251881Speter                 FALSE /* is_move */,
2283251881Speter                 TRUE /* allow_mixed_revisions */,
2284251881Speter                 FALSE /* metadata_only */,
2285251881Speter                 make_parents,
2286251881Speter                 ignore_externals,
2287251881Speter                 revprop_table,
2288251881Speter                 commit_callback, commit_baton,
2289251881Speter                 ctx,
2290251881Speter                 subpool);
2291251881Speter
2292251881Speter  /* If the destination exists, try to copy the sources as children of the
2293251881Speter     destination. */
2294251881Speter  if (copy_as_child && err && (sources->nelts == 1)
2295251881Speter        && (err->apr_err == SVN_ERR_ENTRY_EXISTS
2296251881Speter            || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
2297251881Speter    {
2298251881Speter      const char *src_path = APR_ARRAY_IDX(sources, 0,
2299251881Speter                                           svn_client_copy_source_t *)->path;
2300251881Speter      const char *src_basename;
2301251881Speter      svn_boolean_t src_is_url = svn_path_is_url(src_path);
2302251881Speter      svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
2303251881Speter
2304251881Speter      svn_error_clear(err);
2305251881Speter      svn_pool_clear(subpool);
2306251881Speter
2307251881Speter      src_basename = src_is_url ? svn_uri_basename(src_path, subpool)
2308251881Speter                                : svn_dirent_basename(src_path, subpool);
2309251881Speter      dst_path
2310251881Speter        = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
2311251881Speter                                                   subpool)
2312251881Speter                     : svn_dirent_join(dst_path, src_basename, subpool);
2313251881Speter
2314251881Speter      err = try_copy(&timestamp_sleep,
2315251881Speter                     sources, dst_path,
2316251881Speter                     FALSE /* is_move */,
2317251881Speter                     TRUE /* allow_mixed_revisions */,
2318251881Speter                     FALSE /* metadata_only */,
2319251881Speter                     make_parents,
2320251881Speter                     ignore_externals,
2321251881Speter                     revprop_table,
2322251881Speter                     commit_callback, commit_baton,
2323251881Speter                     ctx,
2324251881Speter                     subpool);
2325251881Speter    }
2326251881Speter
2327251881Speter  /* Sleep if required.  DST_PATH is not a URL in these cases. */
2328251881Speter  if (timestamp_sleep)
2329251881Speter    svn_io_sleep_for_timestamps(dst_path, subpool);
2330251881Speter
2331251881Speter  svn_pool_destroy(subpool);
2332251881Speter  return svn_error_trace(err);
2333251881Speter}
2334251881Speter
2335251881Speter
2336251881Spetersvn_error_t *
2337251881Spetersvn_client_move7(const apr_array_header_t *src_paths,
2338251881Speter                 const char *dst_path,
2339251881Speter                 svn_boolean_t move_as_child,
2340251881Speter                 svn_boolean_t make_parents,
2341251881Speter                 svn_boolean_t allow_mixed_revisions,
2342251881Speter                 svn_boolean_t metadata_only,
2343251881Speter                 const apr_hash_t *revprop_table,
2344251881Speter                 svn_commit_callback2_t commit_callback,
2345251881Speter                 void *commit_baton,
2346251881Speter                 svn_client_ctx_t *ctx,
2347251881Speter                 apr_pool_t *pool)
2348251881Speter{
2349251881Speter  const svn_opt_revision_t head_revision
2350251881Speter    = { svn_opt_revision_head, { 0 } };
2351251881Speter  svn_error_t *err;
2352251881Speter  svn_boolean_t timestamp_sleep = FALSE;
2353251881Speter  int i;
2354251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
2355251881Speter  apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
2356251881Speter                                  sizeof(const svn_client_copy_source_t *));
2357251881Speter
2358251881Speter  if (src_paths->nelts > 1 && !move_as_child)
2359251881Speter    return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
2360251881Speter                            NULL, NULL);
2361251881Speter
2362251881Speter  for (i = 0; i < src_paths->nelts; i++)
2363251881Speter    {
2364251881Speter      const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
2365251881Speter      svn_client_copy_source_t *copy_source = apr_palloc(pool,
2366251881Speter                                                         sizeof(*copy_source));
2367251881Speter
2368251881Speter      copy_source->path = src_path;
2369251881Speter      copy_source->revision = &head_revision;
2370251881Speter      copy_source->peg_revision = &head_revision;
2371251881Speter
2372251881Speter      APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
2373251881Speter    }
2374251881Speter
2375251881Speter  err = try_copy(&timestamp_sleep,
2376251881Speter                 sources, dst_path,
2377251881Speter                 TRUE /* is_move */,
2378251881Speter                 allow_mixed_revisions,
2379251881Speter                 metadata_only,
2380251881Speter                 make_parents,
2381251881Speter                 FALSE /* ignore_externals */,
2382251881Speter                 revprop_table,
2383251881Speter                 commit_callback, commit_baton,
2384251881Speter                 ctx,
2385251881Speter                 subpool);
2386251881Speter
2387251881Speter  /* If the destination exists, try to move the sources as children of the
2388251881Speter     destination. */
2389251881Speter  if (move_as_child && err && (src_paths->nelts == 1)
2390251881Speter        && (err->apr_err == SVN_ERR_ENTRY_EXISTS
2391251881Speter            || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
2392251881Speter    {
2393251881Speter      const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
2394251881Speter      const char *src_basename;
2395251881Speter      svn_boolean_t src_is_url = svn_path_is_url(src_path);
2396251881Speter      svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
2397251881Speter
2398251881Speter      svn_error_clear(err);
2399251881Speter      svn_pool_clear(subpool);
2400251881Speter
2401251881Speter      src_basename = src_is_url ? svn_uri_basename(src_path, pool)
2402251881Speter                                : svn_dirent_basename(src_path, pool);
2403251881Speter      dst_path
2404251881Speter        = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
2405251881Speter                                                   subpool)
2406251881Speter                     : svn_dirent_join(dst_path, src_basename, subpool);
2407251881Speter
2408251881Speter      err = try_copy(&timestamp_sleep,
2409251881Speter                     sources, dst_path,
2410251881Speter                     TRUE /* is_move */,
2411251881Speter                     allow_mixed_revisions,
2412251881Speter                     metadata_only,
2413251881Speter                     make_parents,
2414251881Speter                     FALSE /* ignore_externals */,
2415251881Speter                     revprop_table,
2416251881Speter                     commit_callback, commit_baton,
2417251881Speter                     ctx,
2418251881Speter                     subpool);
2419251881Speter    }
2420251881Speter
2421251881Speter  /* Sleep if required.  DST_PATH is not a URL in these cases. */
2422251881Speter  if (timestamp_sleep)
2423251881Speter    svn_io_sleep_for_timestamps(dst_path, subpool);
2424251881Speter
2425251881Speter  svn_pool_destroy(subpool);
2426251881Speter  return svn_error_trace(err);
2427251881Speter}
2428