Fix compiler warning due to missing function prototype.
[svn.git] / subversion / libsvn_client / copy.c
blob9f69bccff7fb710874a39048707599baf3615879
1 /*
2 * copy.c: copy/move wrappers around wc 'copy' functionality.
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 /* ==================================================================== */
23 /*** Includes. ***/
25 #include <string.h>
26 #include <assert.h>
27 #include "svn_client.h"
28 #include "svn_error.h"
29 #include "svn_error_codes.h"
30 #include "svn_path.h"
31 #include "svn_opt.h"
32 #include "svn_time.h"
33 #include "svn_props.h"
34 #include "svn_mergeinfo.h"
35 #include "svn_pools.h"
37 #include "client.h"
38 #include "mergeinfo.h"
40 #include "svn_private_config.h"
41 #include "private/svn_wc_private.h"
42 #include "private/svn_mergeinfo_private.h"
45 /*
46 * OUR BASIC APPROACH TO COPIES
47 * ============================
49 * for each source/destination pair
50 * if (not exist src_path)
51 * return ERR_BAD_SRC error
53 * if (exist dst_path)
54 * return ERR_OBSTRUCTION error
55 * else
56 * copy src_path into parent_of_dst_path as basename (dst_path)
58 * if (this is a move)
59 * delete src_path
64 /*** Code. ***/
66 /* Obtain the implied mergeinfo and the existing mergeinfo of the
67 source path, combine them and return the result in
68 *TARGET_MERGEINFO. ADM_ACCESS may be NULL, if SRC_PATH_OR_URL is an
69 URL. If NO_REPOS_ACCESS is set, this function is disallowed from
70 consulting the repository about anything. RA_SESSION may be NULL but
71 only if NO_REPOS_ACCESS is true. */
72 static svn_error_t *
73 calculate_target_mergeinfo(svn_ra_session_t *ra_session,
74 apr_hash_t **target_mergeinfo,
75 svn_wc_adm_access_t *adm_access,
76 const char *src_path_or_url,
77 svn_revnum_t src_revnum,
78 svn_boolean_t no_repos_access,
79 svn_client_ctx_t *ctx,
80 apr_pool_t *pool)
82 const svn_wc_entry_t *entry = NULL;
83 svn_boolean_t locally_added = FALSE;
84 const char *src_url;
85 apr_hash_t *src_mergeinfo = NULL;
87 /* If we have a schedule-add WC path (which was not copied from
88 elsewhere), it doesn't have any repository mergeinfo, so don't
89 bother checking. */
90 if (adm_access)
92 SVN_ERR(svn_wc__entry_versioned(&entry, src_path_or_url, adm_access,
93 FALSE, pool));
94 if (entry->schedule == svn_wc_schedule_add && (! entry->copied))
96 locally_added = TRUE;
98 else
100 SVN_ERR(svn_client__entry_location(&src_url, &src_revnum,
101 src_path_or_url,
102 svn_opt_revision_working, entry,
103 pool));
106 else
108 src_url = src_path_or_url;
111 if (! locally_added)
113 const char *mergeinfo_path;
115 if (! no_repos_access)
117 /* Fetch any existing (explicit) mergeinfo. */
118 SVN_ERR(svn_client__path_relative_to_root(&mergeinfo_path, src_url,
119 entry ? entry->repos : NULL,
120 FALSE, ra_session,
121 adm_access, pool));
122 SVN_ERR(svn_client__get_repos_mergeinfo(ra_session, &src_mergeinfo,
123 mergeinfo_path, src_revnum,
124 svn_mergeinfo_inherited, TRUE,
125 pool));
127 else
129 svn_boolean_t inherited;
130 SVN_ERR(svn_client__get_wc_mergeinfo(&src_mergeinfo, &inherited,
131 FALSE, svn_mergeinfo_inherited,
132 entry, src_path_or_url, NULL,
133 NULL, adm_access, ctx, pool));
137 *target_mergeinfo = src_mergeinfo;
138 return SVN_NO_ERROR;
141 /* Extend the mergeinfo for the single WC path TARGET_WCPATH, adding
142 MERGEINFO to any mergeinfo pre-existing in the WC. */
143 static svn_error_t *
144 extend_wc_mergeinfo(const char *target_wcpath, const svn_wc_entry_t *entry,
145 apr_hash_t *mergeinfo, svn_wc_adm_access_t *adm_access,
146 svn_client_ctx_t *ctx, apr_pool_t *pool)
148 apr_hash_t *wc_mergeinfo;
150 /* Get a fresh copy of the pre-existing state of the WC's mergeinfo
151 updating it. */
152 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, entry, target_wcpath,
153 FALSE, adm_access, ctx, pool));
155 /* Combine the provided mergeinfo with any mergeinfo from the WC. */
156 if (wc_mergeinfo && mergeinfo)
157 SVN_ERR(svn_mergeinfo_merge(wc_mergeinfo, mergeinfo, pool));
158 else if (! wc_mergeinfo)
159 wc_mergeinfo = mergeinfo;
161 return svn_client__record_wc_mergeinfo(target_wcpath, wc_mergeinfo,
162 adm_access, pool);
165 /* Propagate implied and explicit mergeinfo for WC-local copy/move
166 operations. Otherwise, either propagate PAIR->dst's explicit (only)
167 mergeinfo, or set empty mergeinfo on PAIR->dst. Use POOL for
168 temporary allocations. */
169 static svn_error_t *
170 propagate_mergeinfo_within_wc(svn_client__copy_pair_t *pair,
171 svn_wc_adm_access_t *src_access,
172 svn_wc_adm_access_t *dst_access,
173 svn_client_ctx_t *ctx, apr_pool_t *pool)
175 apr_hash_t *mergeinfo;
176 const svn_wc_entry_t *entry;
178 SVN_ERR(svn_wc__entry_versioned(&entry, pair->src, src_access, FALSE, pool));
180 /* Don't attempt to figure out implied mergeinfo for a locally
181 added/replaced PAIR->src without history (if its deleted we
182 should never even get this far). */
183 if (entry->schedule == svn_wc_schedule_normal
184 || (entry->schedule == svn_wc_schedule_add && entry->copied))
186 pair->src_revnum = entry->revision;
188 /* ASSUMPTION: Non-numeric operative and peg revisions --
189 other than working or unspecified -- won't be encountered
190 here. For those cases, WC paths will have already been
191 transformed into repository URLs (as done towards the end
192 of the setup_copy() routine), and be handled by a
193 different code path. */
194 SVN_ERR(calculate_target_mergeinfo(NULL, &mergeinfo,
195 src_access, pair->src,
196 pair->src_revnum, TRUE,
197 ctx, pool));
199 /* NULL mergeinfo could be due to there being no mergeinfo. But
200 it could also be due to us not being able to query the server
201 about mergeinfo on some parent directory of ours. We need to
202 turn "no mergeinfo" into "empty mergeinfo", just in case. */
203 if (! mergeinfo)
204 mergeinfo = apr_hash_make(pool);
206 /* Because any local mergeinfo from the copy source will have
207 already been propagated to the destination, we can avoid
208 looking at WC-local mergeinfo for the source. */
209 SVN_ERR(svn_wc__entry_versioned(&entry, pair->dst, dst_access, FALSE,
210 pool));
212 return extend_wc_mergeinfo(pair->dst, entry, mergeinfo, dst_access,
213 ctx, pool);
216 /* If the source had no explicit mergeinfo, set empty explicit
217 mergeinfo for PAIR->dst, as it almost certainly won't be correct
218 for that path to inherit the mergeinfo of its parent. */
219 SVN_ERR(svn_client__parse_mergeinfo(&mergeinfo, entry, pair->src, FALSE,
220 src_access, ctx, pool));
221 if (mergeinfo == NULL)
223 mergeinfo = apr_hash_make(pool);
224 return svn_client__record_wc_mergeinfo(pair->dst, mergeinfo, dst_access,
225 pool);
227 else
228 return SVN_NO_ERROR;
231 /* Find the longest common ancestor for all the SRCs and DSTs in COPY_PAIRS.
232 If SRC_ANCESTOR or DST_ANCESTOR is NULL, nothing will be returned in it.
233 COMMON_ANCESTOR will be the common ancestor of both the SRC_ANCESTOR and
234 DST_ANCESTOR, and will only be set if it is not NULL.
236 static svn_error_t *
237 get_copy_pair_ancestors(const apr_array_header_t *copy_pairs,
238 const char **src_ancestor,
239 const char **dst_ancestor,
240 const char **common_ancestor,
241 apr_pool_t *pool)
243 apr_pool_t *subpool = svn_pool_create(pool);
244 const char *top_dst;
245 char *top_src;
246 int i;
248 top_src = apr_pstrdup(subpool, APR_ARRAY_IDX(copy_pairs, 0,
249 svn_client__copy_pair_t *)->src);
251 /* Because all the destinations are in the same directory, we can easily
252 determine their common ancestor. */
253 if (copy_pairs->nelts == 1)
254 top_dst = apr_pstrdup(subpool, APR_ARRAY_IDX(copy_pairs, 0,
255 svn_client__copy_pair_t *)->dst);
256 else
257 top_dst = svn_path_dirname(APR_ARRAY_IDX(copy_pairs, 0,
258 svn_client__copy_pair_t *)->dst,
259 subpool);
261 /* We don't need to clear the subpool here for several reasons:
262 1) If we do, we can't use it to allocate the initial versions of
263 top_src and top_dst (above).
264 2) We don't return any errors in the following loop, so we are guanteed
265 to destroy the subpool at the end of this function.
266 3) The number of iterations is likely to be few, and the loop will be
267 through quickly, so memory leakage will not be significant, in time or
268 space. */
269 for (i = 1; i < copy_pairs->nelts; i++)
271 const svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
272 svn_client__copy_pair_t *);
274 top_src = svn_path_get_longest_ancestor(top_src, pair->src, subpool);
277 if (src_ancestor)
278 *src_ancestor = apr_pstrdup(pool, top_src);
280 if (dst_ancestor)
281 *dst_ancestor = apr_pstrdup(pool, top_dst);
283 if (common_ancestor)
284 *common_ancestor = svn_path_get_longest_ancestor(top_src, top_dst, pool);
286 svn_pool_destroy(subpool);
288 return SVN_NO_ERROR;
292 /* Copy each COPY_PAIR->SRC into COPY_PAIR->DST. Use POOL for temporary
293 allocations. */
294 static svn_error_t *
295 do_wc_to_wc_copies(const apr_array_header_t *copy_pairs,
296 svn_client_ctx_t *ctx,
297 apr_pool_t *pool)
299 int i;
300 apr_pool_t *iterpool = svn_pool_create(pool);
301 const char *dst_parent;
302 svn_wc_adm_access_t *dst_access;
303 svn_error_t *err = SVN_NO_ERROR;
305 get_copy_pair_ancestors(copy_pairs, NULL, &dst_parent, NULL, pool);
306 if (copy_pairs->nelts == 1)
307 dst_parent = svn_path_dirname(dst_parent, pool);
309 /* Because all copies are to the same destination directory, we can open
310 the directory once, and use it for each copy. */
311 /* ### If we didn't potentially use DST_ACCESS as the SRC_ACCESS, we
312 ### could use a read lock here. */
313 SVN_ERR(svn_wc_adm_open3(&dst_access, NULL, dst_parent, TRUE, 0,
314 ctx->cancel_func, ctx->cancel_baton, pool));
316 for (i = 0; i < copy_pairs->nelts; i++)
318 svn_wc_adm_access_t *src_access;
319 const char *src_parent;
320 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
321 svn_client__copy_pair_t *);
322 svn_pool_clear(iterpool);
324 /* Check for cancellation */
325 if (ctx->cancel_func)
326 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
328 svn_path_split(pair->src, &src_parent, NULL, pool);
330 /* Need to avoid attempting to open the same dir twice when source
331 and destination overlap. */
332 if (strcmp(src_parent, pair->dst_parent) == 0)
334 /* For directories, extend our lock depth so that we can
335 access the source's entry fields. */
336 if (pair->src_kind == svn_node_dir)
337 SVN_ERR(svn_wc_adm_open3(&src_access, NULL, pair->src, FALSE,
338 -1, ctx->cancel_func, ctx->cancel_baton,
339 iterpool));
340 else
341 src_access = dst_access;
343 else
345 err = svn_wc_adm_open3(&src_access, NULL, src_parent, FALSE,
346 pair->src_kind == svn_node_dir ? -1 : 0,
347 ctx->cancel_func, ctx->cancel_baton,
348 iterpool);
349 /* The parent of a copy src might not be versioned at all. */
350 if (err && err->apr_err == SVN_ERR_WC_NOT_DIRECTORY)
352 src_access = NULL;
353 svn_error_clear(err);
354 err = NULL;
356 SVN_ERR(err);
359 /* Perform the copy */
361 /* ### This is not a move, so we won't have locked the source, so we
362 ### won't detect any outstanding locks. If the source is locked and
363 ### requires cleanup should we abort the copy? */
365 err = svn_wc_copy2(pair->src, dst_access, pair->base_name,
366 ctx->cancel_func, ctx->cancel_baton,
367 ctx->notify_func2, ctx->notify_baton2, iterpool);
368 if (err)
369 break;
371 if (src_access)
373 err = propagate_mergeinfo_within_wc(pair, src_access, dst_access,
374 ctx, pool);
375 if (err)
376 break;
378 if (src_access != dst_access)
379 SVN_ERR(svn_wc_adm_close(src_access));
383 svn_sleep_for_timestamps();
384 SVN_ERR(err);
386 SVN_ERR(svn_wc_adm_close(dst_access));
387 svn_pool_destroy(iterpool);
389 return SVN_NO_ERROR;
393 /* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC
394 afterwards. Use POOL for temporary allocations. */
395 static svn_error_t *
396 do_wc_to_wc_moves(const apr_array_header_t *copy_pairs,
397 svn_client_ctx_t *ctx,
398 apr_pool_t *pool)
400 int i;
401 apr_pool_t *iterpool = svn_pool_create(pool);
402 svn_error_t *err = SVN_NO_ERROR;
404 for (i = 0; i < copy_pairs->nelts; i++)
406 svn_wc_adm_access_t *src_access, *dst_access;
407 const char *src_parent;
408 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
409 svn_client__copy_pair_t *);
410 svn_pool_clear(iterpool);
412 /* Check for cancellation */
413 if (ctx->cancel_func)
414 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
416 svn_path_split(pair->src, &src_parent, NULL, iterpool);
418 SVN_ERR(svn_wc_adm_open3(&src_access, NULL, src_parent, TRUE,
419 pair->src_kind == svn_node_dir ? -1 : 0,
420 ctx->cancel_func, ctx->cancel_baton,
421 iterpool));
423 /* Need to avoid attempting to open the same dir twice when source
424 and destination overlap. */
425 if (strcmp(src_parent, pair->dst_parent) == 0)
427 dst_access = src_access;
429 else
431 const char *src_parent_abs, *dst_parent_abs;
433 SVN_ERR(svn_path_get_absolute(&src_parent_abs, src_parent,
434 iterpool));
435 SVN_ERR(svn_path_get_absolute(&dst_parent_abs, pair->dst_parent,
436 iterpool));
438 if ((pair->src_kind == svn_node_dir)
439 && (svn_path_is_child(src_parent_abs, dst_parent_abs,
440 iterpool)))
442 SVN_ERR(svn_wc_adm_retrieve(&dst_access, src_access,
443 pair->dst_parent, iterpool));
445 else
447 SVN_ERR(svn_wc_adm_open3(&dst_access, NULL, pair->dst_parent,
448 TRUE, 0, ctx->cancel_func,
449 ctx->cancel_baton,
450 iterpool));
454 /* ### Ideally, we'd lookup the mergeinfo here, before
455 ### performing the copy. However, as an implementation
456 ### shortcut, we perform the lookup after the copy. */
458 /* Perform the copy with mergeinfo, and then the delete. */
459 err = svn_wc_copy2(pair->src, dst_access, pair->base_name,
460 ctx->cancel_func, ctx->cancel_baton,
461 ctx->notify_func2, ctx->notify_baton2, iterpool);
462 if (err)
463 break;
465 err = propagate_mergeinfo_within_wc(pair, src_access, dst_access,
466 ctx, pool);
467 if (err)
468 break;
470 /* Perform the delete. */
471 SVN_ERR(svn_wc_delete3(pair->src, src_access,
472 ctx->cancel_func, ctx->cancel_baton,
473 ctx->notify_func2, ctx->notify_baton2, FALSE,
474 iterpool));
476 if (dst_access != src_access)
477 SVN_ERR(svn_wc_adm_close(dst_access));
478 SVN_ERR(svn_wc_adm_close(src_access));
481 svn_sleep_for_timestamps();
482 SVN_ERR(err);
484 svn_pool_destroy(iterpool);
486 return SVN_NO_ERROR;
490 static svn_error_t *
491 wc_to_wc_copy(const apr_array_header_t *copy_pairs,
492 svn_boolean_t is_move,
493 svn_boolean_t make_parents,
494 svn_client_ctx_t *ctx,
495 apr_pool_t *pool)
497 int i;
498 apr_pool_t *iterpool = svn_pool_create(pool);
500 /* Check that all of our SRCs exist, and all the DSTs don't. */
501 for (i = 0; i < copy_pairs->nelts; i++)
503 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
504 svn_client__copy_pair_t *);
505 svn_node_kind_t dst_kind, dst_parent_kind;
507 svn_pool_clear(iterpool);
509 /* Verify that SRC_PATH exists. */
510 SVN_ERR(svn_io_check_path(pair->src, &pair->src_kind, iterpool));
511 if (pair->src_kind == svn_node_none)
512 return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
513 _("Path '%s' does not exist"),
514 svn_path_local_style(pair->src, pool));
516 /* If DST_PATH does not exist, then its basename will become a new
517 file or dir added to its parent (possibly an implicit '.').
518 Else, just error out. */
519 SVN_ERR(svn_io_check_path(pair->dst, &dst_kind, iterpool));
520 if (dst_kind != svn_node_none)
521 return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
522 _("Path '%s' already exists"),
523 svn_path_local_style(pair->dst, pool));
525 svn_path_split(pair->dst, &pair->dst_parent, &pair->base_name, pool);
527 /* Make sure the destination parent is a directory and produce a clear
528 error message if it is not. */
529 SVN_ERR(svn_io_check_path(pair->dst_parent, &dst_parent_kind, iterpool));
530 if (make_parents && dst_parent_kind == svn_node_none)
532 SVN_ERR(svn_client__make_local_parents(pair->dst_parent, TRUE, ctx,
533 iterpool));
535 else if (dst_parent_kind != svn_node_dir)
537 return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL,
538 _("Path '%s' is not a directory"),
539 svn_path_local_style(pair->dst_parent,
540 pool));
544 svn_pool_destroy(iterpool);
546 /* Copy or move all targets. */
547 if (is_move)
548 return do_wc_to_wc_moves(copy_pairs, ctx, pool);
549 else
550 return do_wc_to_wc_copies(copy_pairs, ctx, pool);
554 /* Path-specific state used as part of path_driver_cb_baton. */
555 typedef struct
557 const char *src_url;
558 const char *src_path;
559 const char *dst_path;
560 svn_node_kind_t src_kind;
561 svn_revnum_t src_revnum;
562 svn_boolean_t resurrection;
563 svn_boolean_t dir_add;
564 svn_string_t *mergeinfo; /* the new mergeinfo for the target */
565 } path_driver_info_t;
568 /* The baton used with the path_driver_cb_func() callback for a copy
569 or move operation. */
570 struct path_driver_cb_baton
572 /* The editor (and its state) used to perform the operation. */
573 const svn_delta_editor_t *editor;
574 void *edit_baton;
576 /* A hash of path -> path_driver_info_t *'s. */
577 apr_hash_t *action_hash;
579 /* Whether the operation is a move or copy. */
580 svn_boolean_t is_move;
583 static svn_error_t *
584 path_driver_cb_func(void **dir_baton,
585 void *parent_baton,
586 void *callback_baton,
587 const char *path,
588 apr_pool_t *pool)
590 struct path_driver_cb_baton *cb_baton = callback_baton;
591 svn_boolean_t do_delete = FALSE, do_add = FALSE;
592 path_driver_info_t *path_info = apr_hash_get(cb_baton->action_hash,
593 path,
594 APR_HASH_KEY_STRING);
596 /* Initialize return value. */
597 *dir_baton = NULL;
599 /* This function should never get an empty PATH. We can neither
600 create nor delete the empty PATH, so if someone is calling us
601 with such, the code is just plain wrong. */
602 assert(! svn_path_is_empty(path));
604 /* Check to see if we need to add the path as a directory. */
605 if (path_info->dir_add)
607 SVN_ERR(cb_baton->editor->add_directory(path, parent_baton, NULL,
608 SVN_INVALID_REVNUM, pool,
609 dir_baton));
610 return SVN_NO_ERROR;
613 /* If this is a resurrection, we know the source and dest paths are
614 the same, and that our driver will only be calling us once. */
615 if (path_info->resurrection)
617 /* If this is a move, we do nothing. Otherwise, we do the copy. */
618 if (! cb_baton->is_move)
619 do_add = TRUE;
621 /* Not a resurrection. */
622 else
624 /* If this is a move, we check PATH to see if it is the source
625 or the destination of the move. */
626 if (cb_baton->is_move)
628 if (strcmp(path_info->src_path, path) == 0)
629 do_delete = TRUE;
630 else
631 do_add = TRUE;
633 /* Not a move? This must just be the copy addition. */
634 else
636 do_add = TRUE;
640 if (do_delete)
642 SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM,
643 parent_baton, pool));
645 if (do_add)
647 SVN_ERR(svn_path_check_valid(path, pool));
649 if (path_info->src_kind == svn_node_file)
651 void *file_baton;
652 SVN_ERR(cb_baton->editor->add_file(path, parent_baton,
653 path_info->src_url,
654 path_info->src_revnum,
655 pool, &file_baton));
656 if (path_info->mergeinfo)
657 SVN_ERR(cb_baton->editor->change_file_prop(file_baton,
658 SVN_PROP_MERGEINFO,
659 path_info->mergeinfo,
660 pool));
661 SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool));
663 else
665 SVN_ERR(cb_baton->editor->add_directory(path, parent_baton,
666 path_info->src_url,
667 path_info->src_revnum,
668 pool, dir_baton));
669 if (path_info->mergeinfo)
670 SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton,
671 SVN_PROP_MERGEINFO,
672 path_info->mergeinfo,
673 pool));
676 return SVN_NO_ERROR;
680 static svn_error_t *
681 repos_to_repos_copy(svn_commit_info_t **commit_info_p,
682 const apr_array_header_t *copy_pairs,
683 svn_boolean_t make_parents,
684 svn_client_ctx_t *ctx,
685 svn_boolean_t is_move,
686 apr_pool_t *pool)
688 apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
689 sizeof(const char *));
690 apr_hash_t *action_hash = apr_hash_make(pool);
691 apr_array_header_t *path_infos;
692 const char *top_url, *message, *repos_root;
693 apr_hash_t *revprop_table;
694 svn_revnum_t youngest;
695 svn_ra_session_t *ra_session;
696 const svn_delta_editor_t *editor;
697 void *edit_baton;
698 void *commit_baton;
699 struct path_driver_cb_baton cb_baton;
700 apr_array_header_t *new_dirs = NULL;
701 apr_pool_t *iterpool;
702 int i;
703 svn_error_t *err;
705 /* Create a path_info struct for each src/dst pair, and initialize it. */
706 path_infos = apr_array_make(pool, copy_pairs->nelts,
707 sizeof(path_driver_info_t *));
708 for (i = 0; i < copy_pairs->nelts; i++)
710 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
711 info->resurrection = FALSE;
712 APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
715 /* We have to open our session to the longest path common to all
716 SRC_URLS and DST_URLS in the repository so we can do existence
717 checks on all paths, and so we can operate on all paths in the
718 case of a move. */
719 get_copy_pair_ancestors(copy_pairs, NULL, NULL, &top_url, pool);
721 /* Check each src/dst pair for resurrection. */
722 for (i = 0; i < copy_pairs->nelts; i++)
724 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
725 svn_client__copy_pair_t *);
726 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
727 path_driver_info_t *);
729 if (strcmp(pair->src, pair->dst) == 0)
731 info->resurrection = TRUE;
733 /* Special edge-case! (issue #683) If you're resurrecting a
734 deleted item like this: 'svn cp -rN src_URL dst_URL', then
735 it's possible for src_URL == dst_URL == top_url. In this
736 situation, we want to open an RA session to be at least the
737 *parent* of all three. */
738 if (strcmp(pair->src, top_url) == 0)
740 top_url = svn_path_dirname(top_url, pool);
745 /* Open an RA session for the URL. Note that we don't have a local
746 directory, nor a place to put temp files. */
747 err = svn_client__open_ra_session_internal(&ra_session, top_url,
748 NULL, NULL, NULL, FALSE, TRUE,
749 ctx, pool);
751 /* If the two URLs appear not to be in the same repository, then
752 top_url will be empty and the call to svn_ra_open3()
753 above will have failed. Below we check for that, and propagate a
754 descriptive error back to the user.
756 Ideally, we'd contact the repositories and compare their UUIDs to
757 determine whether or not src and dst are in the same repository,
758 instead of depending on an essentially textual comparison.
759 However, it is simpler to assume that if someone is using the
760 same repository, then they will use the same hostname/path to
761 refer to it both times. Conversely, if the repositories are
762 different, then they can't share a non-empty prefix, so top_url
763 would still be "" and svn_ra_get_library() would still error.
764 Thus we can get this check without extra network turnarounds to
765 fetch the UUIDs.
767 if (err)
769 if ((err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
770 && ((top_url == NULL) || (top_url[0] == '\0')))
772 svn_client__copy_pair_t *first_pair =
773 APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
774 svn_error_clear(err);
776 return svn_error_createf
777 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
778 _("Source and dest appear not to be in the same repository "
779 "(src: '%s'; dst: '%s')"),
780 first_pair->src, first_pair->dst);
782 else
783 return err;
786 iterpool = svn_pool_create(pool);
788 /* Iterate over the parents of the destination directory, and make a list
789 of the ones that don't yet exist. We do not have to worry about
790 reparenting the ra session because top_url is a common ancestor of the
791 destination and sources. The sources exist, so therefore top_url must
792 also exist. */
793 if (make_parents)
795 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, 0,
796 svn_client__copy_pair_t *);
797 svn_node_kind_t kind;
798 const char *dir;
800 new_dirs = apr_array_make(pool, 0, sizeof(const char *));
801 dir = svn_path_is_child(top_url, svn_path_dirname(pair->dst, pool),
802 pool);
803 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
804 iterpool));
806 while (kind == svn_node_none)
808 svn_pool_clear(iterpool);
809 APR_ARRAY_PUSH(new_dirs, const char *) = dir;
811 svn_path_split(dir, &dir, NULL, pool);
812 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
813 iterpool));
817 svn_pool_destroy(iterpool);
819 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
821 /* For each src/dst pair, check to see if that SRC_URL is a child of
822 the DST_URL (excepting the case where DST_URL is the repo root).
823 If it is, and the parent of DST_URL is the current TOP_URL, then we
824 need to reparent the session one directory higher, the parent of
825 the DST_URL. */
826 for (i = 0; i < copy_pairs->nelts; i++)
828 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
829 svn_client__copy_pair_t *);
830 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
831 path_driver_info_t *);
833 if (strcmp(pair->dst, repos_root) != 0
834 && svn_path_is_child(pair->dst, pair->src, pool) != NULL)
836 info->resurrection = TRUE;
837 top_url = svn_path_dirname(top_url, pool);
839 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
843 /* Fetch the youngest revision. */
844 SVN_ERR(svn_ra_get_latest_revnum(ra_session, &youngest, pool));
846 for (i = 0; i < copy_pairs->nelts; i++)
848 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
849 svn_client__copy_pair_t *);
850 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
851 path_driver_info_t *);
852 svn_node_kind_t dst_kind;
853 const char *src_rel, *dst_rel;
854 svn_opt_revision_t *new_rev, *ignored_rev, dead_end_rev;
855 const char *ignored_url;
857 /* Pass NULL for the path, to ensure error if trying to get a
858 revision based on the working copy. */
859 SVN_ERR(svn_client__get_revision_number
860 (&pair->src_revnum, NULL, ra_session, &pair->src_op_revision,
861 NULL, pool));
863 info->src_revnum = pair->src_revnum;
865 dead_end_rev.kind = svn_opt_revision_unspecified;
867 /* Run the history function to get the object's url in the operational
868 revision. */
869 SVN_ERR(svn_client__repos_locations(&pair->src, &new_rev,
870 &ignored_url, &ignored_rev,
871 NULL,
872 pair->src, &pair->src_peg_revision,
873 &pair->src_op_revision, &dead_end_rev,
874 ctx, pool));
876 /* Get the portions of the SRC and DST URLs that are relative to
877 TOP_URL, and URI-decode those sections. */
878 src_rel = svn_path_is_child(top_url, pair->src, pool);
879 if (src_rel)
880 src_rel = svn_path_uri_decode(src_rel, pool);
881 else
882 src_rel = "";
884 dst_rel = svn_path_is_child(top_url, pair->dst, pool);
885 if (dst_rel)
886 dst_rel = svn_path_uri_decode(dst_rel, pool);
887 else
888 dst_rel = "";
890 /* We can't move something into itself, period. */
891 if (svn_path_is_empty(src_rel) && is_move)
892 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
893 _("Cannot move URL '%s' into itself"),
894 pair->src);
896 /* Verify that SRC_URL exists in the repository. */
897 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
898 &info->src_kind, pool));
899 if (info->src_kind == svn_node_none)
900 return svn_error_createf
901 (SVN_ERR_FS_NOT_FOUND, NULL,
902 _("Path '%s' does not exist in revision %ld"),
903 pair->src, pair->src_revnum);
905 /* Figure out the basename that will result from this operation. */
906 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, youngest, &dst_kind,
907 pool));
908 if (dst_kind != svn_node_none)
910 /* We disallow the overwriting of existing paths. */
911 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
912 _("Path '%s' already exists"), dst_rel);
915 info->src_url = pair->src;
916 info->src_path = src_rel;
917 info->dst_path = dst_rel;
920 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
922 /* Produce a list of new paths to add, and provide it to the
923 mechanism used to acquire a log message. */
924 svn_client_commit_item3_t *item;
925 const char *tmp_file;
926 apr_array_header_t *commit_items
927 = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item));
929 /* Add any intermediate directories to the message */
930 if (make_parents)
932 for (i = 0; i < new_dirs->nelts; i++)
934 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
936 SVN_ERR(svn_client_commit_item_create
937 ((const svn_client_commit_item3_t **) &item, pool));
939 item->url = svn_path_join(top_url, url, pool);
940 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
941 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
945 for (i = 0; i < path_infos->nelts; i++)
947 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
948 path_driver_info_t *);
950 SVN_ERR(svn_client_commit_item_create
951 ((const svn_client_commit_item3_t **) &item, pool));
953 item->url = svn_path_join(top_url, info->dst_path, pool);
954 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
955 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
956 apr_hash_set(action_hash, info->dst_path, APR_HASH_KEY_STRING,
957 info);
959 if (is_move && (! info->resurrection))
961 item = apr_pcalloc(pool, sizeof(*item));
962 item->url = svn_path_join(top_url, info->src_path, pool);
963 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
964 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
965 apr_hash_set(action_hash, info->src_path, APR_HASH_KEY_STRING,
966 info);
970 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
971 ctx, pool));
972 if (! message)
973 return SVN_NO_ERROR;
975 else
976 message = "";
978 /* Setup our PATHS for the path-based editor drive. */
979 /* First any intermediate directories. */
980 if (make_parents)
982 for (i = 0; i < new_dirs->nelts; i++)
984 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
985 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
987 info->dst_path = url;
988 info->dir_add = TRUE;
990 APR_ARRAY_PUSH(paths, const char *) = url;
991 apr_hash_set(action_hash, url, APR_HASH_KEY_STRING, info);
995 /* Then, copy destinations, and possibly move sources. */
996 for (i = 0; i < path_infos->nelts; i++)
998 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
999 path_driver_info_t *);
1000 apr_hash_t *mergeinfo;
1001 SVN_ERR(calculate_target_mergeinfo(ra_session, &mergeinfo, NULL,
1002 info->src_url, info->src_revnum,
1003 FALSE, ctx, pool));
1004 if (mergeinfo)
1005 SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool));
1007 APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1008 if (is_move && (! info->resurrection))
1009 APR_ARRAY_PUSH(paths, const char *) = info->src_path;
1012 SVN_ERR(svn_client__get_revprop_table(&revprop_table, message, ctx, pool));
1014 /* Fetch RA commit editor. */
1015 SVN_ERR(svn_client__commit_get_baton(&commit_baton, commit_info_p, pool));
1016 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1017 revprop_table,
1018 svn_client__commit_callback,
1019 commit_baton,
1020 NULL, TRUE, /* No lock tokens */
1021 pool));
1023 /* Setup the callback baton. */
1024 cb_baton.editor = editor;
1025 cb_baton.edit_baton = edit_baton;
1026 cb_baton.action_hash = action_hash;
1027 cb_baton.is_move = is_move;
1029 /* Call the path-based editor driver. */
1030 err = svn_delta_path_driver(editor, edit_baton, youngest, paths,
1031 path_driver_cb_func, &cb_baton, pool);
1032 if (err)
1034 /* At least try to abort the edit (and fs txn) before throwing err. */
1035 svn_error_clear(editor->abort_edit(edit_baton, pool));
1036 return err;
1039 /* Close the edit. */
1040 SVN_ERR(editor->close_edit(edit_baton, pool));
1042 return SVN_NO_ERROR;
1047 static svn_error_t *
1048 wc_to_repos_copy(svn_commit_info_t **commit_info_p,
1049 const apr_array_header_t *copy_pairs,
1050 svn_boolean_t make_parents,
1051 svn_client_ctx_t *ctx,
1052 apr_pool_t *pool)
1054 const char *message;
1055 apr_hash_t *revprop_table;
1056 const char *top_src_path, *top_dst_url, *repos_root;
1057 svn_ra_session_t *ra_session;
1058 const svn_delta_editor_t *editor;
1059 void *edit_baton;
1060 svn_node_kind_t base_kind;
1061 void *commit_baton;
1062 apr_hash_t *committables;
1063 svn_wc_adm_access_t *adm_access, *dir_access;
1064 apr_array_header_t *commit_items;
1065 const svn_wc_entry_t *entry;
1066 apr_pool_t *iterpool;
1067 apr_array_header_t *new_dirs = NULL;
1068 int i;
1070 /* The commit process uses absolute paths, so we need to open the access
1071 baton using absolute paths, and so we really need to use absolute
1072 paths everywhere. */
1073 for (i = 0; i < copy_pairs->nelts; i++)
1075 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1076 svn_client__copy_pair_t *);
1077 SVN_ERR(svn_path_get_absolute(&pair->src_abs, pair->src, pool));
1080 /*Find the common root of all the source paths, and probe the wc. */
1081 get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL, pool);
1082 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, top_src_path,
1083 FALSE, -1, ctx->cancel_func,
1084 ctx->cancel_baton, pool));
1086 /* Determine the least common ancesor for the destinations, and open an RA
1087 session to that location. */
1088 svn_path_split(APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *)->dst,
1089 &top_dst_url,
1090 NULL, pool);
1091 for (i = 1; i < copy_pairs->nelts; i++)
1093 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1094 svn_client__copy_pair_t *);
1095 top_dst_url = svn_path_get_longest_ancestor(top_dst_url, pair->dst, pool);
1098 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, top_dst_url,
1099 svn_wc_adm_access_path
1100 (adm_access),
1101 adm_access, NULL, TRUE, TRUE,
1102 ctx, pool));
1104 /* If requested, determine the nearest existing parent of the destination,
1105 and reparent the ra session there. */
1106 if (make_parents)
1108 const char *root_url = top_dst_url;
1109 svn_node_kind_t kind;
1111 new_dirs = apr_array_make(pool, 0, sizeof(const char *));
1112 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1113 pool));
1115 while (kind == svn_node_none)
1117 APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
1118 svn_path_split(root_url, &root_url, NULL, pool);
1120 SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
1121 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1122 pool));
1125 top_dst_url = root_url;
1128 /* Figure out the basename that will result from each copy and check to make
1129 sure it doesn't exist already. */
1130 iterpool = svn_pool_create(pool);
1132 for (i = 0; i < copy_pairs->nelts; i++)
1134 svn_node_kind_t dst_kind;
1135 const char *dst_rel;
1136 svn_client__copy_pair_t *pair =
1137 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1139 svn_pool_clear(iterpool);
1141 SVN_ERR(svn_wc_entry(&entry, pair->src, adm_access, FALSE, iterpool));
1142 pair->src_revnum = entry->revision;
1144 dst_rel = svn_path_uri_decode(svn_path_is_child(top_dst_url,
1145 pair->dst,
1146 iterpool),
1147 iterpool);
1148 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1149 &dst_kind, iterpool));
1150 if (dst_kind != svn_node_none)
1152 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1153 _("Path '%s' already exists"), pair->dst);
1157 svn_pool_destroy(iterpool);
1159 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1161 /* Produce a list of new paths to add, and provide it to the
1162 mechanism used to acquire a log message. */
1163 svn_client_commit_item3_t *item;
1164 const char *tmp_file;
1165 commit_items = apr_array_make(pool, copy_pairs->nelts, sizeof(item));
1167 /* Add any intermediate directories to the message */
1168 if (make_parents)
1170 for (i = 0; i < new_dirs->nelts; i++)
1172 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
1174 SVN_ERR(svn_client_commit_item_create
1175 ((const svn_client_commit_item3_t **) &item, pool));
1177 item->url = url;
1178 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1179 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1183 for (i = 0; i < copy_pairs->nelts; i++ )
1185 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1186 svn_client__copy_pair_t *);
1188 SVN_ERR(svn_client_commit_item_create
1189 ((const svn_client_commit_item3_t **) &item, pool));
1191 item->url = pair->dst;
1192 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1193 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1196 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1197 ctx, pool));
1198 if (! message)
1200 SVN_ERR(svn_wc_adm_close(adm_access));
1201 return SVN_NO_ERROR;
1204 else
1205 message = "";
1207 SVN_ERR(svn_client__get_revprop_table(&revprop_table, message, ctx, pool));
1209 /* Crawl the working copy for commit items. */
1210 SVN_ERR(svn_io_check_path(top_src_path, &base_kind, pool));
1211 if (base_kind == svn_node_dir)
1212 SVN_ERR(svn_wc_adm_retrieve(&dir_access, adm_access, top_src_path, pool));
1213 else
1214 dir_access = adm_access;
1216 SVN_ERR(svn_client__get_copy_committables(&committables,
1217 copy_pairs, dir_access,
1218 ctx, pool));
1220 /* ### todo: There should be only one hash entry, which currently
1221 has a hacked name until we have the entries files storing
1222 canonical repository URLs. Then, the hacked name can go away and
1223 be replaced with a entry->repos (or wherever the entry's
1224 canonical repos URL is stored). */
1225 if (! (commit_items = apr_hash_get(committables,
1226 SVN_CLIENT__SINGLE_REPOS_NAME,
1227 APR_HASH_KEY_STRING)))
1229 SVN_ERR(svn_wc_adm_close(adm_access));
1230 return SVN_NO_ERROR;
1233 /* If we are creating intermediate directories, tack them onto the list
1234 of committables. */
1235 if (make_parents)
1237 for (i = 0; i < new_dirs->nelts; i++)
1239 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
1240 svn_client_commit_item3_t *item;
1242 SVN_ERR(svn_client_commit_item_create
1243 ((const svn_client_commit_item3_t **) &item, pool));
1245 item->url = url;
1246 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1247 item->incoming_prop_changes = apr_array_make(pool, 1,
1248 sizeof(svn_prop_t *));
1249 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1253 /* Reparent the ra_session to repos_root. So that 'svn_ra_get_log'
1254 on paths relative to repos_root would work fine. */
1255 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
1256 SVN_ERR(svn_ra_reparent(ra_session, repos_root, pool));
1258 /* ### TODO: This extra loop would be unnecessary if this code lived
1259 ### in svn_client__get_copy_committables(), which is incidentally
1260 ### only used above (so should really be in this source file). */
1261 for (i = 0; i < copy_pairs->nelts; i++)
1263 svn_prop_t *mergeinfo_prop;
1264 apr_hash_t *mergeinfo, *wc_mergeinfo;
1265 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1266 svn_client__copy_pair_t *);
1267 svn_client_commit_item3_t *item =
1268 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1270 /* Set the mergeinfo for the destination to the combined merge
1271 info known to the WC and the repository. */
1272 item->outgoing_prop_changes = apr_array_make(pool, 1,
1273 sizeof(svn_prop_t *));
1274 mergeinfo_prop = apr_palloc(item->outgoing_prop_changes->pool,
1275 sizeof(svn_prop_t));
1276 mergeinfo_prop->name = SVN_PROP_MERGEINFO;
1277 SVN_ERR(calculate_target_mergeinfo(ra_session, &mergeinfo, adm_access,
1278 pair->src, pair->src_revnum,
1279 FALSE, ctx, pool));
1280 SVN_ERR(svn_wc_entry(&entry, pair->src, adm_access, FALSE, pool));
1281 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, entry,
1282 pair->src, FALSE, adm_access, ctx,
1283 pool));
1284 if (wc_mergeinfo && mergeinfo)
1285 SVN_ERR(svn_mergeinfo_merge(mergeinfo, wc_mergeinfo, pool));
1286 else if (! mergeinfo)
1287 mergeinfo = wc_mergeinfo;
1288 if (mergeinfo)
1290 SVN_ERR(svn_mergeinfo_to_string((svn_string_t **)
1291 &mergeinfo_prop->value,
1292 mergeinfo, pool));
1293 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) =
1294 mergeinfo_prop;
1298 /* Sort and condense our COMMIT_ITEMS. */
1299 SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
1300 commit_items, pool));
1302 /* Open an RA session to DST_URL. */
1303 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, top_dst_url,
1304 NULL, NULL, commit_items,
1305 FALSE, FALSE, ctx, pool));
1307 /* Fetch RA commit editor. */
1308 SVN_ERR(svn_client__commit_get_baton(&commit_baton, commit_info_p, pool));
1309 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1310 revprop_table, svn_client__commit_callback,
1311 commit_baton, NULL,
1312 TRUE, /* No lock tokens */
1313 pool));
1315 /* Perform the commit. */
1316 SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items, adm_access,
1317 editor, edit_baton,
1318 0, /* ### any notify_path_offset needed? */
1319 NULL, NULL, ctx, pool),
1320 _("Commit failed (details follow):"));
1322 /* Sleep to ensure timestamp integrity. */
1323 svn_sleep_for_timestamps();
1325 /* It's only a read lock, so unlocking is harmless. */
1326 SVN_ERR(svn_wc_adm_close(adm_access));
1329 return SVN_NO_ERROR;
1332 /* Peform each individual copy operation for a repos -> wc copy. A
1333 helper for repos_to_wc_copy(). */
1334 static svn_error_t *
1335 repos_to_wc_copy_single(svn_client__copy_pair_t *pair,
1336 svn_boolean_t same_repositories,
1337 svn_ra_session_t *ra_session,
1338 svn_wc_adm_access_t *adm_access,
1339 svn_client_ctx_t *ctx,
1340 apr_pool_t *pool)
1342 svn_revnum_t src_revnum = pair->src_revnum;
1343 apr_hash_t *src_mergeinfo;
1344 const svn_wc_entry_t *dst_entry;
1346 if (pair->src_kind == svn_node_dir)
1348 SVN_ERR(svn_client__checkout_internal
1349 (NULL, pair->src_original, pair->dst, &pair->src_peg_revision,
1350 &pair->src_op_revision,
1351 SVN_DEPTH_INFINITY_OR_FILES(TRUE),
1352 FALSE, FALSE, NULL, ctx, pool));
1354 /* Rewrite URLs recursively, remove wcprops, and mark everything
1355 as 'copied' -- assuming that the src and dst are from the
1356 same repository. (It's kind of weird that svn_wc_add() is the
1357 way to do this; see its doc for more about the controversy.) */
1358 if (same_repositories)
1360 svn_wc_adm_access_t *dst_access;
1361 SVN_ERR(svn_wc_adm_open3(&dst_access, adm_access, pair->dst, TRUE,
1362 -1, ctx->cancel_func, ctx->cancel_baton,
1363 pool));
1364 SVN_ERR(svn_wc_entry(&dst_entry, pair->dst, dst_access, FALSE,
1365 pool));
1367 if (pair->src_op_revision.kind == svn_opt_revision_head)
1369 /* If we just checked out from the "head" revision,
1370 that's fine, but we don't want to pass a '-1' as a
1371 copyfrom_rev to svn_wc_add(). That function will
1372 dump it right into the entry, and when we try to
1373 commit later on, the 'add-dir-with-history' step will
1374 be -very- unhappy; it only accepts specific
1375 revisions.
1377 On the other hand, we *could* say that -1 is a
1378 legitimate copyfrom_rev, but I think that's bogus.
1379 Somebody made a copy from a particular revision; if
1380 they wait a long time to commit, it would be terrible
1381 if the copied happened from a newer revision!! */
1383 /* We just did a checkout; whatever revision we just
1384 got, that should be the copyfrom_revision when we
1385 commit later. */
1386 src_revnum = dst_entry->revision;
1389 /* Schedule dst_path for addition in parent, with copy history.
1390 (This function also recursively puts a 'copied' flag on every
1391 entry). */
1392 SVN_ERR(svn_wc_add2(pair->dst, adm_access, pair->src,
1393 src_revnum,
1394 ctx->cancel_func, ctx->cancel_baton,
1395 ctx->notify_func2, ctx->notify_baton2, pool));
1397 /* ### Recording of implied mergeinfo should really occur
1398 ### *before* the notification callback is invoked by
1399 ### svn_wc_add2(), but can't occur before we add the new
1400 ### source path. */
1401 SVN_ERR(calculate_target_mergeinfo(ra_session, &src_mergeinfo, NULL,
1402 pair->src, src_revnum,
1403 FALSE, ctx, pool));
1404 SVN_ERR(extend_wc_mergeinfo(pair->dst, dst_entry, src_mergeinfo,
1405 dst_access, ctx, pool));
1407 else /* different repositories */
1409 /* ### Someday, we would just call svn_wc_add(), as above,
1410 but with no copyfrom args. I.e. in the
1411 directory-foreign-UUID case, we still want everything
1412 scheduled for addition, URLs rewritten, and wcprop cache
1413 deleted, but WITHOUT any copied flags or copyfrom urls.
1414 Unfortunately, svn_wc_add() is such a mess that it chokes
1415 at the moment when we pass a NULL copyfromurl. */
1417 return svn_error_createf
1418 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1419 _("Source URL '%s' is from foreign repository; "
1420 "leaving it as a disjoint WC"), pair->src);
1422 } /* end directory case */
1424 else if (pair->src_kind == svn_node_file)
1426 apr_file_t *fp;
1427 svn_stream_t *fstream;
1428 svn_revnum_t real_rev;
1429 const char *new_text_path;
1430 apr_hash_t *new_props;
1431 svn_error_t *err;
1432 const char *src_rel;
1434 SVN_ERR(svn_io_open_unique_file2(&fp, &new_text_path, pair->dst, ".tmp",
1435 svn_io_file_del_none, pool));
1437 fstream = svn_stream_from_aprfile2(fp, FALSE, pool);
1438 SVN_ERR(svn_client__path_relative_to_session(&src_rel, ra_session,
1439 pair->src, pool));
1440 SVN_ERR(svn_ra_get_file(ra_session, src_rel, src_revnum, fstream,
1441 &real_rev, &new_props, pool));
1442 SVN_ERR(svn_stream_close(fstream));
1444 /* If SRC_REVNUM is invalid (HEAD), then REAL_REV is now the
1445 revision that was actually retrieved. This is the value we
1446 want to use as 'copyfrom_rev' below. */
1447 if (! SVN_IS_VALID_REVNUM(src_revnum))
1448 src_revnum = real_rev;
1450 err = svn_wc_add_repos_file2
1451 (pair->dst, adm_access,
1452 new_text_path, NULL, new_props, NULL,
1453 same_repositories ? pair->src : NULL,
1454 same_repositories ? src_revnum : SVN_INVALID_REVNUM,
1455 pool);
1457 SVN_ERR(svn_wc_entry(&dst_entry, pair->dst, adm_access, FALSE, pool));
1458 SVN_ERR(calculate_target_mergeinfo(ra_session, &src_mergeinfo,
1459 NULL, pair->src, src_revnum,
1460 FALSE, ctx, pool));
1461 SVN_ERR(extend_wc_mergeinfo(pair->dst, dst_entry, src_mergeinfo,
1462 adm_access, ctx, pool));
1464 /* Ideally, svn_wc_add_repos_file() would take a notify function
1465 and baton, and we wouldn't have to make this call here.
1466 However, the situation is... complicated. See issue #1552
1467 for the full story. */
1468 if (!err && ctx->notify_func2)
1470 svn_wc_notify_t *notify = svn_wc_create_notify(pair->dst,
1471 svn_wc_notify_add,
1472 pool);
1473 notify->kind = pair->src_kind;
1474 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1477 svn_sleep_for_timestamps();
1478 SVN_ERR(err);
1481 return SVN_NO_ERROR;
1484 static svn_error_t *
1485 repos_to_wc_copy(const apr_array_header_t *copy_pairs,
1486 svn_boolean_t make_parents,
1487 svn_client_ctx_t *ctx,
1488 apr_pool_t *pool)
1490 svn_ra_session_t *ra_session;
1491 svn_wc_adm_access_t *adm_access;
1492 const char *top_src_url, *top_dst_path;
1493 const char *src_uuid = NULL, *dst_uuid = NULL;
1494 svn_boolean_t same_repositories;
1495 apr_pool_t *iterpool = svn_pool_create(pool);
1496 int i;
1498 /* Get the real path for the source, based upon its peg revision. */
1499 for (i = 0; i < copy_pairs->nelts; i++)
1501 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1502 svn_client__copy_pair_t *);
1503 const char *src, *ignored_url;
1504 svn_opt_revision_t *new_rev, *ignored_rev, dead_end_rev;
1506 svn_pool_clear(iterpool);
1507 dead_end_rev.kind = svn_opt_revision_unspecified;
1509 SVN_ERR(svn_client__repos_locations(&src, &new_rev,
1510 &ignored_url, &ignored_rev,
1511 NULL,
1512 pair->src,
1513 &pair->src_peg_revision,
1514 &pair->src_op_revision,
1515 &dead_end_rev,
1516 ctx, iterpool));
1518 pair->src_original = pair->src;
1519 pair->src = apr_pstrdup(pool, src);
1522 get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_path, NULL, pool);
1523 if (copy_pairs->nelts == 1)
1524 top_src_url = svn_path_dirname(top_src_url, pool);
1526 /* Open a repository session to the longest common src ancestor. We do not
1527 (yet) have a working copy, so we don't have a corresponding path and
1528 tempfiles cannot go into the admin area. */
1529 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, top_src_url, NULL,
1530 NULL, NULL, FALSE, TRUE,
1531 ctx, pool));
1533 /* Pass null for the path, to ensure error if trying to get a
1534 revision based on the working copy. */
1535 for (i = 0; i < copy_pairs->nelts; i++)
1537 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1538 svn_client__copy_pair_t *);
1540 SVN_ERR(svn_client__get_revision_number
1541 (&pair->src_revnum, NULL, ra_session, &pair->src_op_revision,
1542 NULL, pool));
1545 /* Get the correct src path for the peg revision used, and verify that we
1546 aren't overwriting an existing path. */
1547 for (i = 0; i < copy_pairs->nelts; i++)
1549 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1550 svn_client__copy_pair_t *);
1551 svn_node_kind_t dst_parent_kind, dst_kind;
1552 const char *dst_parent;
1553 const char *src_rel;
1555 svn_pool_clear(iterpool);
1557 /* Next, make sure that the path exists in the repository. */
1558 SVN_ERR(svn_client__path_relative_to_session(&src_rel, ra_session,
1559 pair->src, iterpool));
1560 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1561 &pair->src_kind, pool));
1562 if (pair->src_kind == svn_node_none)
1564 if (SVN_IS_VALID_REVNUM(pair->src_revnum))
1565 return svn_error_createf
1566 (SVN_ERR_FS_NOT_FOUND, NULL,
1567 _("Path '%s' not found in revision %ld"),
1568 pair->src, pair->src_revnum);
1569 else
1570 return svn_error_createf
1571 (SVN_ERR_FS_NOT_FOUND, NULL,
1572 _("Path '%s' not found in head revision"), pair->src);
1575 /* Figure out about dst. */
1576 SVN_ERR(svn_io_check_path(pair->dst, &dst_kind, iterpool));
1577 if (dst_kind != svn_node_none)
1579 return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
1580 _("Path '%s' already exists"),
1581 svn_path_local_style(pair->dst, pool));
1584 /* Make sure the destination parent is a directory and produce a clear
1585 error message if it is not. */
1586 dst_parent = svn_path_dirname(pair->dst, iterpool);
1587 SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool));
1588 if (make_parents && dst_parent_kind == svn_node_none)
1590 SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx,
1591 iterpool));
1593 else if (dst_parent_kind != svn_node_dir)
1595 return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL,
1596 _("Path '%s' is not a directory"),
1597 svn_path_local_style(dst_parent, pool));
1601 /* Probe the wc at the longest common dst ancestor. */
1602 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, top_dst_path, TRUE,
1603 0, ctx->cancel_func, ctx->cancel_baton,
1604 pool));
1606 /* We've already checked for physical obstruction by a working file.
1607 But there could also be logical obstruction by an entry whose
1608 working file happens to be missing.*/
1609 for (i = 0; i < copy_pairs->nelts; i++)
1611 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1612 svn_client__copy_pair_t *);
1613 const svn_wc_entry_t *ent;
1615 svn_pool_clear(iterpool);
1617 SVN_ERR(svn_wc_entry(&ent, pair->dst, adm_access, FALSE, iterpool));
1618 if (ent && (ent->kind != svn_node_dir) &&
1619 (ent->schedule != svn_wc_schedule_delete))
1620 return svn_error_createf
1621 (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1622 _("Entry for '%s' exists (though the working file is missing)"),
1623 svn_path_local_style(pair->dst, pool));
1626 /* Decide whether the two repositories are the same or not. */
1628 svn_error_t *src_err, *dst_err;
1629 const char *parent;
1631 /* Get the repository uuid of SRC_URL */
1632 src_err = svn_ra_get_uuid2(ra_session, &src_uuid, pool);
1633 if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
1634 return src_err;
1636 /* Get repository uuid of dst's parent directory, since dst may
1637 not exist. ### TODO: we should probably walk up the wc here,
1638 in case the parent dir has an imaginary URL. */
1639 if (copy_pairs->nelts == 1)
1640 svn_path_split(top_dst_path, &parent, NULL, pool);
1641 else
1642 parent = top_dst_path;
1643 dst_err = svn_client_uuid_from_path(&dst_uuid, parent, adm_access,
1644 ctx, pool);
1645 if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
1646 return dst_err;
1648 /* If either of the UUIDs are nonexistent, then at least one of
1649 the repositories must be very old. Rather than punish the
1650 user, just assume the repositories are different, so no
1651 copy-history is attempted. */
1652 if (src_err || dst_err || (! src_uuid) || (! dst_uuid))
1653 same_repositories = FALSE;
1655 else
1656 same_repositories = (strcmp(src_uuid, dst_uuid) == 0) ? TRUE : FALSE;
1659 /* Perform the move for each of the copy_pairs. */
1660 for (i = 0; i < copy_pairs->nelts; i++)
1662 /* Check for cancellation */
1663 if (ctx->cancel_func)
1664 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1666 svn_pool_clear(iterpool);
1668 SVN_ERR(repos_to_wc_copy_single(APR_ARRAY_IDX(copy_pairs, i,
1669 svn_client__copy_pair_t *),
1670 same_repositories,
1671 ra_session, adm_access,
1672 ctx, iterpool));
1675 SVN_ERR(svn_wc_adm_close(adm_access));
1677 svn_pool_destroy(iterpool);
1679 return SVN_NO_ERROR;
1682 #define NEED_REPOS_REVNUM(revision) \
1683 ((revision.kind != svn_opt_revision_unspecified) \
1684 && (revision.kind != svn_opt_revision_working))
1686 /* Perform all allocations in POOL. */
1687 static svn_error_t *
1688 setup_copy(svn_commit_info_t **commit_info_p,
1689 const apr_array_header_t *sources,
1690 const char *dst_path_in,
1691 svn_boolean_t is_move,
1692 svn_boolean_t force,
1693 svn_boolean_t make_parents,
1694 svn_client_ctx_t *ctx,
1695 apr_pool_t *pool)
1697 apr_array_header_t *copy_pairs = apr_array_make(pool, sources->nelts,
1698 sizeof(struct copy_pair *));
1699 svn_boolean_t srcs_are_urls, dst_is_url;
1700 int i;
1702 /* Check to see if the supplied peg revisions make sense. */
1703 for (i = 0; i < sources->nelts; i++)
1705 svn_client_copy_source_t *source =
1706 ((svn_client_copy_source_t **) (sources->elts))[i];
1708 if (svn_path_is_url(source->path)
1709 && (SVN_CLIENT__REVKIND_NEEDS_WC(source->peg_revision->kind)))
1710 return svn_error_create
1711 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
1712 _("Revision type requires a working copy path, not a URL"));
1715 /* Are either of our paths URLs?
1716 * Just check the first src_path. If there are more than one, we'll check
1717 * for homogeneity among them down below. */
1718 srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
1719 svn_client_copy_source_t *)->path);
1720 dst_is_url = svn_path_is_url(dst_path_in);
1722 /* If we have multiple source paths, it implies the dst_path is a directory
1723 * we are moving or copying into. Populate the dst_paths array to contain
1724 * a destination path for each of the source paths. */
1725 if (sources->nelts > 1)
1727 apr_pool_t *iterpool = svn_pool_create(pool);
1729 for (i = 0; i < sources->nelts; i++)
1731 svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
1732 svn_client_copy_source_t *);
1733 svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
1734 const char *src_basename;
1735 svn_boolean_t src_is_url = svn_path_is_url(source->path);
1737 svn_pool_clear(iterpool);
1739 pair->src = apr_pstrdup(pool, source->path);
1740 pair->src_op_revision = *source->revision;
1741 pair->src_peg_revision = *source->peg_revision;
1743 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
1744 &pair->src_op_revision,
1745 src_is_url,
1746 TRUE,
1747 iterpool));
1748 src_basename = svn_path_basename(pair->src, iterpool);
1749 if (srcs_are_urls && ! dst_is_url)
1750 src_basename = svn_path_uri_decode(src_basename, pool);
1752 /* Check to see if all the sources are urls or all working copy
1753 * paths. */
1754 if (src_is_url != srcs_are_urls)
1755 return svn_error_create
1756 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1757 _("Cannot mix repository and working copy sources"));
1759 pair->dst = svn_path_join(dst_path_in, src_basename, pool);
1760 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
1763 svn_pool_destroy(iterpool);
1765 else
1767 /* Only one source path. */
1768 svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
1769 svn_client_copy_source_t *source =
1770 APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
1772 pair->src = apr_pstrdup(pool, source->path);
1773 pair->src_op_revision = *source->revision;
1774 pair->src_peg_revision = *source->peg_revision;
1776 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
1777 &pair->src_op_revision,
1778 svn_path_is_url(pair->src),
1779 TRUE,
1780 pool));
1782 pair->dst = dst_path_in;
1783 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
1786 if (!srcs_are_urls && !dst_is_url)
1788 apr_pool_t *iterpool = svn_pool_create(pool);
1790 for (i = 0; i < copy_pairs->nelts; i++ )
1792 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1793 svn_client__copy_pair_t *);
1795 svn_pool_clear(iterpool);
1797 if (svn_path_is_child(pair->src, pair->dst, iterpool))
1798 return svn_error_createf
1799 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1800 _("Cannot copy path '%s' into its own child '%s'"),
1801 svn_path_local_style(pair->src, pool),
1802 svn_path_local_style(pair->dst, pool));
1805 svn_pool_destroy(iterpool);
1808 if (is_move)
1810 if (srcs_are_urls == dst_is_url)
1812 for (i = 0; i < copy_pairs->nelts; i++)
1814 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1815 svn_client__copy_pair_t *);
1817 if (strcmp(pair->src, pair->dst) == 0)
1818 return svn_error_createf
1819 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1820 _("Cannot move path '%s' into itself"),
1821 svn_path_local_style(pair->src, pool));
1824 else
1826 /* Disallow moves between the working copy and the repository. */
1827 return svn_error_create
1828 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1829 _("Moves between the working copy and the repository are not "
1830 "supported"));
1833 else
1835 if (!srcs_are_urls)
1837 /* If we are doing a wc->* move, but with an operational revision
1838 other than the working copy revision, we are really doing a
1839 repo->* move, because we're going to need to get the rev from the
1840 repo. */
1842 svn_boolean_t need_repos_op_rev = FALSE;
1843 svn_boolean_t need_repos_peg_rev = FALSE;
1845 /* Check to see if any revision is something other than
1846 svn_opt_revision_unspecified or svn_opt_revision_working. */
1847 for (i = 0; i < copy_pairs->nelts; i++)
1849 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1850 svn_client__copy_pair_t *);
1852 if (NEED_REPOS_REVNUM(pair->src_op_revision))
1853 need_repos_op_rev = TRUE;
1855 if (NEED_REPOS_REVNUM(pair->src_peg_revision))
1856 need_repos_peg_rev = TRUE;
1858 if (need_repos_op_rev || need_repos_peg_rev)
1859 break;
1862 if (need_repos_op_rev || need_repos_peg_rev)
1864 apr_pool_t *iterpool = svn_pool_create(pool);
1866 for (i = 0; i < copy_pairs->nelts; i++)
1868 const char *url;
1869 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1870 svn_client__copy_pair_t *);
1872 /* We can convert the working copy path to a URL based on the
1873 entries file. */
1874 svn_wc_adm_access_t *adm_access; /* ### FIXME local */
1875 const svn_wc_entry_t *entry;
1877 svn_pool_clear(iterpool);
1879 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL,
1880 pair->src, FALSE, 0,
1881 ctx->cancel_func,
1882 ctx->cancel_baton,
1883 iterpool));
1884 SVN_ERR(svn_wc__entry_versioned(&entry, pair->src, adm_access,
1885 FALSE, iterpool));
1886 SVN_ERR(svn_wc_adm_close(adm_access));
1888 url = (entry->copied ? entry->copyfrom_url : entry->url);
1889 if (url == NULL)
1890 return svn_error_createf
1891 (SVN_ERR_ENTRY_MISSING_URL, NULL,
1892 _("'%s' does not have a URL associated with it"),
1893 svn_path_local_style(pair->src, pool));
1895 pair->src = apr_pstrdup(pool, url);
1897 if (!need_repos_peg_rev
1898 || pair->src_peg_revision.kind == svn_opt_revision_base)
1900 /* Default the peg revision to that of the WC entry. */
1901 pair->src_peg_revision.kind = svn_opt_revision_number;
1902 pair->src_peg_revision.value.number =
1903 (entry->copied ? entry->copyfrom_rev : entry->revision);
1906 if (pair->src_op_revision.kind == svn_opt_revision_base)
1908 /* Use the entry's revision as the operational rev. */
1909 pair->src_op_revision.kind = svn_opt_revision_number;
1910 pair->src_op_revision.value.number =
1911 (entry->copied ? entry->copyfrom_rev : entry->revision);
1915 svn_pool_destroy(iterpool);
1916 srcs_are_urls = TRUE;
1921 /* Now, call the right handler for the operation. */
1922 if ((! srcs_are_urls) && (! dst_is_url))
1924 *commit_info_p = NULL;
1925 SVN_ERR(wc_to_wc_copy(copy_pairs, is_move, make_parents,
1926 ctx, pool));
1928 else if ((! srcs_are_urls) && (dst_is_url))
1930 SVN_ERR(wc_to_repos_copy(commit_info_p, copy_pairs, make_parents,
1931 ctx, pool));
1933 else if ((srcs_are_urls) && (! dst_is_url))
1935 *commit_info_p = NULL;
1936 SVN_ERR(repos_to_wc_copy(copy_pairs, make_parents, ctx, pool));
1938 else
1940 SVN_ERR(repos_to_repos_copy(commit_info_p, copy_pairs, make_parents,
1941 ctx, is_move, pool));
1944 return SVN_NO_ERROR;
1949 /* Public Interfaces */
1950 svn_error_t *
1951 svn_client_copy4(svn_commit_info_t **commit_info_p,
1952 apr_array_header_t *sources,
1953 const char *dst_path,
1954 svn_boolean_t copy_as_child,
1955 svn_boolean_t make_parents,
1956 svn_client_ctx_t *ctx,
1957 apr_pool_t *pool)
1959 svn_error_t *err;
1960 svn_commit_info_t *commit_info = NULL;
1961 apr_pool_t *subpool = svn_pool_create(pool);
1963 if (sources->nelts > 1 && !copy_as_child)
1964 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
1965 NULL, NULL);
1967 err = setup_copy(&commit_info,
1968 sources, dst_path,
1969 FALSE /* is_move */,
1970 TRUE /* force, set to avoid deletion check */,
1971 make_parents,
1972 ctx,
1973 subpool);
1975 /* If the destination exists, try to copy the sources as children of the
1976 destination. */
1977 if (copy_as_child && err && (sources->nelts == 1)
1978 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
1979 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
1981 const char *src_path = APR_ARRAY_IDX(sources, 0,
1982 svn_client_copy_source_t *)->path;
1983 const char *src_basename;
1985 svn_error_clear(err);
1986 svn_pool_clear(subpool);
1988 src_basename = svn_path_basename(src_path, subpool);
1989 if (svn_path_is_url(src_path) && ! svn_path_is_url(dst_path))
1990 src_basename = svn_path_uri_decode(src_basename, pool);
1992 err = setup_copy(&commit_info,
1993 sources,
1994 svn_path_join(dst_path, src_basename, pool),
1995 FALSE /* is_move */,
1996 TRUE /* force, set to avoid deletion check */,
1997 make_parents,
1998 ctx,
1999 subpool);
2002 if (commit_info_p != NULL)
2004 if (commit_info)
2005 *commit_info_p = svn_commit_info_dup(commit_info, pool);
2006 else
2007 *commit_info_p = commit_info;
2010 svn_pool_destroy(subpool);
2011 return err;
2015 svn_error_t *
2016 svn_client_copy3(svn_commit_info_t **commit_info_p,
2017 const char *src_path,
2018 const svn_opt_revision_t *src_revision,
2019 const char *dst_path,
2020 svn_client_ctx_t *ctx,
2021 apr_pool_t *pool)
2023 apr_array_header_t *sources = apr_array_make(pool, 1,
2024 sizeof(const svn_client_copy_source_t *));
2025 svn_client_copy_source_t copy_source;
2027 copy_source.path = src_path;
2028 copy_source.revision = src_revision;
2029 copy_source.peg_revision = src_revision;
2031 APR_ARRAY_PUSH(sources, const svn_client_copy_source_t *) = &copy_source;
2033 return svn_client_copy4(commit_info_p,
2034 sources,
2035 dst_path,
2036 FALSE, FALSE,
2037 ctx,
2038 pool);
2042 svn_error_t *
2043 svn_client_copy2(svn_commit_info_t **commit_info_p,
2044 const char *src_path,
2045 const svn_opt_revision_t *src_revision,
2046 const char *dst_path,
2047 svn_client_ctx_t *ctx,
2048 apr_pool_t *pool)
2050 svn_error_t *err;
2052 err = svn_client_copy3(commit_info_p, src_path, src_revision,
2053 dst_path, ctx, pool);
2055 /* If the target exists, try to copy the source as a child of the target.
2056 This will obviously fail if target is not a directory, but that's exactly
2057 what we want. */
2058 if (err && (err->apr_err == SVN_ERR_ENTRY_EXISTS
2059 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
2061 const char *src_basename = svn_path_basename(src_path, pool);
2063 svn_error_clear(err);
2065 return svn_client_copy3(commit_info_p, src_path, src_revision,
2066 svn_path_join(dst_path, src_basename, pool),
2067 ctx, pool);
2070 return err;
2073 svn_error_t *
2074 svn_client_copy(svn_client_commit_info_t **commit_info_p,
2075 const char *src_path,
2076 const svn_opt_revision_t *src_revision,
2077 const char *dst_path,
2078 svn_client_ctx_t *ctx,
2079 apr_pool_t *pool)
2081 svn_commit_info_t *commit_info = NULL;
2082 svn_error_t *err;
2084 err = svn_client_copy2(&commit_info, src_path, src_revision, dst_path,
2085 ctx, pool);
2086 /* These structs have the same layout for the common fields. */
2087 *commit_info_p = (svn_client_commit_info_t *) commit_info;
2088 return err;
2092 svn_error_t *
2093 svn_client_move5(svn_commit_info_t **commit_info_p,
2094 apr_array_header_t *src_paths,
2095 const char *dst_path,
2096 svn_boolean_t force,
2097 svn_boolean_t move_as_child,
2098 svn_boolean_t make_parents,
2099 svn_client_ctx_t *ctx,
2100 apr_pool_t *pool)
2102 svn_commit_info_t *commit_info = NULL;
2103 const svn_opt_revision_t head_revision
2104 = { svn_opt_revision_head, { 0 } };
2105 svn_error_t *err;
2106 int i;
2107 apr_pool_t *subpool = svn_pool_create(pool);
2108 apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
2109 sizeof(const svn_client_copy_source_t *));
2111 if (src_paths->nelts > 1 && !move_as_child)
2112 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
2113 NULL, NULL);
2115 for (i = 0; i < src_paths->nelts; i++)
2117 const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
2118 svn_client_copy_source_t *copy_source = apr_palloc(pool,
2119 sizeof(*copy_source));
2121 copy_source->path = src_path;
2122 copy_source->revision = &head_revision;
2123 copy_source->peg_revision = &head_revision;
2125 APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
2128 err = setup_copy(&commit_info, sources, dst_path,
2129 TRUE /* is_move */,
2130 force,
2131 make_parents,
2132 ctx,
2133 subpool);
2135 /* If the destination exists, try to move the sources as children of the
2136 destination. */
2137 if (move_as_child && err && (src_paths->nelts == 1)
2138 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
2139 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
2141 const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
2142 const char *src_basename;
2144 svn_error_clear(err);
2145 svn_pool_clear(subpool);
2147 src_basename = svn_path_basename(src_path, pool);
2149 err = setup_copy(&commit_info, sources,
2150 svn_path_join(dst_path, src_basename, pool),
2151 TRUE /* is_move */,
2152 force,
2153 make_parents,
2154 ctx,
2155 subpool);
2158 if (commit_info_p != NULL)
2160 if (commit_info)
2161 *commit_info_p = svn_commit_info_dup(commit_info, pool);
2162 else
2163 *commit_info_p = commit_info;
2166 svn_pool_destroy(subpool);
2167 return err;
2170 svn_error_t *
2171 svn_client_move4(svn_commit_info_t **commit_info_p,
2172 const char *src_path,
2173 const char *dst_path,
2174 svn_boolean_t force,
2175 svn_client_ctx_t *ctx,
2176 apr_pool_t *pool)
2178 apr_array_header_t *src_paths =
2179 apr_array_make(pool, 1, sizeof(const char *));
2180 APR_ARRAY_PUSH(src_paths, const char *) = src_path;
2182 return svn_client_move5(commit_info_p,
2183 src_paths, dst_path, force, FALSE,
2184 FALSE, ctx, pool);
2187 svn_error_t *
2188 svn_client_move3(svn_commit_info_t **commit_info_p,
2189 const char *src_path,
2190 const char *dst_path,
2191 svn_boolean_t force,
2192 svn_client_ctx_t *ctx,
2193 apr_pool_t *pool)
2195 svn_error_t *err;
2197 err = svn_client_move4(commit_info_p, src_path, dst_path, force, ctx, pool);
2199 /* If the target exists, try to move the source as a child of the target.
2200 This will obviously fail if target is not a directory, but that's exactly
2201 what we want. */
2202 if (err && (err->apr_err == SVN_ERR_ENTRY_EXISTS
2203 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
2205 const char *src_basename = svn_path_basename(src_path, pool);
2207 svn_error_clear(err);
2209 return svn_client_move4(commit_info_p, src_path,
2210 svn_path_join(dst_path, src_basename, pool),
2211 force, ctx, pool);
2214 return err;
2217 svn_error_t *
2218 svn_client_move2(svn_client_commit_info_t **commit_info_p,
2219 const char *src_path,
2220 const char *dst_path,
2221 svn_boolean_t force,
2222 svn_client_ctx_t *ctx,
2223 apr_pool_t *pool)
2225 svn_commit_info_t *commit_info = NULL;
2226 svn_error_t *err;
2228 err = svn_client_move3(&commit_info, src_path, dst_path, force, ctx, pool);
2229 /* These structs have the same layout for the common fields. */
2230 *commit_info_p = (svn_client_commit_info_t *) commit_info;
2231 return err;
2235 svn_error_t *
2236 svn_client_move(svn_client_commit_info_t **commit_info_p,
2237 const char *src_path,
2238 const svn_opt_revision_t *src_revision,
2239 const char *dst_path,
2240 svn_boolean_t force,
2241 svn_client_ctx_t *ctx,
2242 apr_pool_t *pool)
2244 svn_commit_info_t *commit_info = NULL;
2245 svn_error_t *err;
2246 svn_client_copy_source_t copy_source;
2247 apr_array_header_t *sources = apr_array_make(pool, 1,
2248 sizeof(const svn_client_copy_source_t *));
2250 /* It doesn't make sense to specify revisions in a move. */
2252 /* ### todo: this check could fail wrongly. For example,
2253 someone could pass in an svn_opt_revision_number that just
2254 happens to be the HEAD. It's fair enough to punt then, IMHO,
2255 and just demand that the user not specify a revision at all;
2256 beats mucking up this function with RA calls and such. */
2257 if (src_revision->kind != svn_opt_revision_unspecified
2258 && src_revision->kind != svn_opt_revision_head)
2260 return svn_error_create
2261 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2262 _("Cannot specify revisions (except HEAD) with move operations"));
2265 copy_source.path = src_path;
2266 copy_source.revision = src_revision;
2267 copy_source.peg_revision = src_revision;
2269 APR_ARRAY_PUSH(sources, const svn_client_copy_source_t *) = &copy_source;
2271 err = setup_copy(&commit_info,
2272 sources, dst_path,
2273 TRUE /* is_move */,
2274 force,
2275 FALSE /* make_parents */,
2276 ctx,
2277 pool);
2278 /* These structs have the same layout for the common fields. */
2279 *commit_info_p = (svn_client_commit_info_t *) commit_info;
2280 return err;