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 /* ==================================================================== */
27 #include "svn_client.h"
28 #include "svn_error.h"
29 #include "svn_error_codes.h"
33 #include "svn_props.h"
34 #include "svn_mergeinfo.h"
35 #include "svn_pools.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"
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
54 * return ERR_OBSTRUCTION error
56 * copy src_path into parent_of_dst_path as basename (dst_path)
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. */
72 calculate_target_mergeinfo(svn_ra_session_t
*ra_session
,
73 apr_hash_t
**target_mergeinfo
,
74 svn_wc_adm_access_t
*adm_access
,
75 const char *src_path_or_url
,
76 svn_revnum_t src_revnum
,
77 svn_boolean_t no_repos_access
,
78 svn_client_ctx_t
*ctx
,
81 const svn_wc_entry_t
*entry
= NULL
;
82 svn_boolean_t locally_added
= FALSE
;
84 apr_hash_t
*src_mergeinfo
= NULL
;
86 /* If we have a schedule-add WC path (which was not copied from
87 elsewhere), it doesn't have any repository mergeinfo, so don't
91 SVN_ERR(svn_wc__entry_versioned(&entry
, src_path_or_url
, adm_access
,
93 if (entry
->schedule
== svn_wc_schedule_add
&& (! entry
->copied
))
99 SVN_ERR(svn_client__entry_location(&src_url
, &src_revnum
,
101 svn_opt_revision_working
, entry
,
107 src_url
= src_path_or_url
;
112 const char *mergeinfo_path
;
114 if (! no_repos_access
)
116 /* Fetch any existing (explicit) mergeinfo. */
117 SVN_ERR(svn_client__path_relative_to_root(&mergeinfo_path
, src_url
,
118 entry
? entry
->repos
: NULL
,
121 SVN_ERR(svn_client__get_repos_mergeinfo(ra_session
, &src_mergeinfo
,
122 mergeinfo_path
, src_revnum
,
123 svn_mergeinfo_inherited
, TRUE
,
128 svn_boolean_t inherited
;
129 SVN_ERR(svn_client__get_wc_mergeinfo(&src_mergeinfo
, &inherited
,
130 FALSE
, svn_mergeinfo_inherited
,
131 entry
, src_path_or_url
, NULL
,
132 NULL
, adm_access
, ctx
, pool
));
136 *target_mergeinfo
= src_mergeinfo
;
140 /* Extend the mergeinfo for the single WC path TARGET_WCPATH, adding
141 MERGEINFO to any mergeinfo pre-existing in the WC. */
143 extend_wc_mergeinfo(const char *target_wcpath
, const svn_wc_entry_t
*entry
,
144 apr_hash_t
*mergeinfo
, svn_wc_adm_access_t
*adm_access
,
145 svn_client_ctx_t
*ctx
, apr_pool_t
*pool
)
147 apr_hash_t
*wc_mergeinfo
;
149 /* Get a fresh copy of the pre-existing state of the WC's mergeinfo
151 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo
, entry
, target_wcpath
,
152 FALSE
, adm_access
, ctx
, pool
));
154 /* Combine the provided mergeinfo with any mergeinfo from the WC. */
155 if (wc_mergeinfo
&& mergeinfo
)
156 SVN_ERR(svn_mergeinfo_merge(wc_mergeinfo
, mergeinfo
, pool
));
157 else if (! wc_mergeinfo
)
158 wc_mergeinfo
= mergeinfo
;
160 return svn_client__record_wc_mergeinfo(target_wcpath
, wc_mergeinfo
,
164 /* If WITH_MERGE_HISTORY is TRUE, propagate implied and explicit
165 mergeinfo for WC-local copy/move operations. Otherwise, either
166 propagate PAIR->dst's explicit (only) mergeinfo, or set empty
167 mergeinfo on PAIR->dst. Use POOL for temporary allocations. */
169 propagate_mergeinfo_within_wc(svn_client__copy_pair_t
*pair
,
170 svn_wc_adm_access_t
*src_access
,
171 svn_wc_adm_access_t
*dst_access
,
172 svn_client_ctx_t
*ctx
, apr_pool_t
*pool
)
174 apr_hash_t
*mergeinfo
;
175 const svn_wc_entry_t
*entry
;
177 SVN_ERR(svn_wc__entry_versioned(&entry
, pair
->src
, src_access
, FALSE
, pool
));
179 /* Don't attempt to figure out implied mergeinfo for a locally
180 added/replaced PAIR->src without histroy (if its deleted we
181 should never even get this far). */
182 if (entry
->schedule
== svn_wc_schedule_normal
183 || (entry
->schedule
== svn_wc_schedule_add
&& entry
->copied
))
185 svn_ra_session_t
*ra_session
;
187 /* Obtain mergeinfo from source. */
188 SVN_ERR(svn_client__open_ra_session_internal(&ra_session
, entry
->url
,
189 "", src_access
, NULL
,
190 TRUE
, TRUE
, ctx
, pool
));
191 pair
->src_revnum
= entry
->revision
;
193 /* ASSUMPTION: Non-numeric operative and peg revisions --
194 other than working or unspecified -- won't be encountered
195 here. For those cases, WC paths will have already been
196 transformed into repository URLs (as done towards the end
197 of the setup_copy() routine), and be handled by a
198 different code path. */
199 SVN_ERR(calculate_target_mergeinfo(ra_session
, &mergeinfo
,
200 src_access
, pair
->src
,
201 pair
->src_revnum
, TRUE
,
204 /* NULL mergeinfo could be due to there being no mergeinfo. But
205 it could also be due to us not being able to query the server
206 about mergeinfo on some parent directory of ours. We need to
207 turn "no mergeinfo" into "empty mergeinfo", just in case. */
209 mergeinfo
= apr_hash_make(pool
);
211 /* Because any local mergeinfo from the copy source will have
212 already been propagated to the destination, we can avoid
213 looking at WC-local mergeinfo for the source. */
214 SVN_ERR(svn_wc__entry_versioned(&entry
, pair
->dst
, dst_access
, FALSE
,
217 return extend_wc_mergeinfo(pair
->dst
, entry
, mergeinfo
, dst_access
,
221 /* If the source had no explicit mergeinfo, set empty explicit
222 mergeinfo for PAIR->dst, as it almost certainly won't be correct
223 for that path to inherit the mergeinfo of its parent. */
224 SVN_ERR(svn_client__parse_mergeinfo(&mergeinfo
, entry
, pair
->src
, FALSE
,
225 src_access
, ctx
, pool
));
226 if (mergeinfo
== NULL
)
228 mergeinfo
= apr_hash_make(pool
);
229 return svn_client__record_wc_mergeinfo(pair
->dst
, mergeinfo
, dst_access
,
236 /* Find the longest common ancestor for all the SRCs and DSTs in COPY_PAIRS.
237 If SRC_ANCESTOR or DST_ANCESTOR is NULL, nothing will be returned in it.
238 COMMON_ANCESTOR will be the common ancestor of both the SRC_ANCESTOR and
239 DST_ANCESTOR, and will only be set if it is not NULL.
242 get_copy_pair_ancestors(const apr_array_header_t
*copy_pairs
,
243 const char **src_ancestor
,
244 const char **dst_ancestor
,
245 const char **common_ancestor
,
248 apr_pool_t
*subpool
= svn_pool_create(pool
);
253 top_src
= apr_pstrdup(subpool
, APR_ARRAY_IDX(copy_pairs
, 0,
254 svn_client__copy_pair_t
*)->src
);
256 /* Because all the destinations are in the same directory, we can easily
257 determine their common ancestor. */
258 if (copy_pairs
->nelts
== 1)
259 top_dst
= apr_pstrdup(subpool
, APR_ARRAY_IDX(copy_pairs
, 0,
260 svn_client__copy_pair_t
*)->dst
);
262 top_dst
= svn_path_dirname(APR_ARRAY_IDX(copy_pairs
, 0,
263 svn_client__copy_pair_t
*)->dst
,
266 /* We don't need to clear the subpool here for several reasons:
267 1) If we do, we can't use it to allocate the initial versions of
268 top_src and top_dst (above).
269 2) We don't return any errors in the following loop, so we are guanteed
270 to destory the subpool at the end of this function.
271 3) The number of iterations is likely to be few, and the loop will be
272 through quickly, so memory leakage will not be significant, in time or
274 for (i
= 1; i
< copy_pairs
->nelts
; i
++)
276 const svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
277 svn_client__copy_pair_t
*);
279 top_src
= svn_path_get_longest_ancestor(top_src
, pair
->src
, subpool
);
283 *src_ancestor
= apr_pstrdup(pool
, top_src
);
286 *dst_ancestor
= apr_pstrdup(pool
, top_dst
);
289 *common_ancestor
= svn_path_get_longest_ancestor(top_src
, top_dst
, pool
);
291 svn_pool_destroy(subpool
);
297 /* Copy each COPY_PAIR->SRC into COPY_PAIR->DST. Use POOL for temporary
300 do_wc_to_wc_copies(const apr_array_header_t
*copy_pairs
,
301 svn_client_ctx_t
*ctx
,
305 apr_pool_t
*iterpool
= svn_pool_create(pool
);
306 const char *dst_parent
;
307 svn_wc_adm_access_t
*dst_access
;
308 svn_error_t
*err
= SVN_NO_ERROR
;
310 get_copy_pair_ancestors(copy_pairs
, NULL
, &dst_parent
, NULL
, pool
);
311 if (copy_pairs
->nelts
== 1)
312 dst_parent
= svn_path_dirname(dst_parent
, pool
);
314 /* Because all copies are to the same destination directory, we can open
315 the directory once, and use it for each copy. */
316 /* ### If we didn't potentially use DST_ACCESS as the SRC_ACCESS, we
317 ### could use a read lock here. */
318 SVN_ERR(svn_wc_adm_open3(&dst_access
, NULL
, dst_parent
, TRUE
, 0,
319 ctx
->cancel_func
, ctx
->cancel_baton
, pool
));
321 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
323 svn_wc_adm_access_t
*src_access
;
324 const char *src_parent
;
325 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
326 svn_client__copy_pair_t
*);
327 svn_pool_clear(iterpool
);
329 /* Check for cancellation */
330 if (ctx
->cancel_func
)
331 SVN_ERR(ctx
->cancel_func(ctx
->cancel_baton
));
333 svn_path_split(pair
->src
, &src_parent
, NULL
, pool
);
335 /* Need to avoid attempting to open the same dir twice when source
336 and destination overlap. */
337 if (strcmp(src_parent
, pair
->dst_parent
) == 0)
339 /* For directories, extend our lock depth so that we can
340 access the source's entry fields. */
341 if (pair
->src_kind
== svn_node_dir
)
342 SVN_ERR(svn_wc_adm_open3(&src_access
, NULL
, pair
->src
, FALSE
,
343 -1, ctx
->cancel_func
, ctx
->cancel_baton
,
346 src_access
= dst_access
;
350 err
= svn_wc_adm_open3(&src_access
, NULL
, src_parent
, FALSE
,
351 pair
->src_kind
== svn_node_dir
? -1 : 0,
352 ctx
->cancel_func
, ctx
->cancel_baton
,
354 /* The parent of a copy src might not be versioned at all. */
355 if (err
&& err
->apr_err
== SVN_ERR_WC_NOT_DIRECTORY
)
358 svn_error_clear(err
);
364 /* Perform the copy */
366 /* ### This is not a move, so we won't have locked the source, so we
367 ### won't detect any outstanding locks. If the source is locked and
368 ### requires cleanup should we abort the copy? */
370 err
= svn_wc_copy2(pair
->src
, dst_access
, pair
->base_name
,
371 ctx
->cancel_func
, ctx
->cancel_baton
,
372 ctx
->notify_func2
, ctx
->notify_baton2
, iterpool
);
378 err
= propagate_mergeinfo_within_wc(pair
, src_access
, dst_access
,
383 if (src_access
!= dst_access
)
384 SVN_ERR(svn_wc_adm_close(src_access
));
388 svn_sleep_for_timestamps();
391 SVN_ERR(svn_wc_adm_close(dst_access
));
392 svn_pool_destroy(iterpool
);
398 /* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC
399 afterwards. Use POOL for temporary allocations. */
401 do_wc_to_wc_moves(const apr_array_header_t
*copy_pairs
,
402 svn_client_ctx_t
*ctx
,
406 apr_pool_t
*iterpool
= svn_pool_create(pool
);
407 svn_error_t
*err
= SVN_NO_ERROR
;
409 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
411 svn_wc_adm_access_t
*src_access
, *dst_access
;
412 const char *src_parent
;
413 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
414 svn_client__copy_pair_t
*);
415 svn_pool_clear(iterpool
);
417 /* Check for cancellation */
418 if (ctx
->cancel_func
)
419 SVN_ERR(ctx
->cancel_func(ctx
->cancel_baton
));
421 svn_path_split(pair
->src
, &src_parent
, NULL
, iterpool
);
423 SVN_ERR(svn_wc_adm_open3(&src_access
, NULL
, src_parent
, TRUE
,
424 pair
->src_kind
== svn_node_dir
? -1 : 0,
425 ctx
->cancel_func
, ctx
->cancel_baton
,
428 /* Need to avoid attempting to open the same dir twice when source
429 and destination overlap. */
430 if (strcmp(src_parent
, pair
->dst_parent
) == 0)
432 dst_access
= src_access
;
436 const char *src_parent_abs
, *dst_parent_abs
;
438 SVN_ERR(svn_path_get_absolute(&src_parent_abs
, src_parent
,
440 SVN_ERR(svn_path_get_absolute(&dst_parent_abs
, pair
->dst_parent
,
443 if ((pair
->src_kind
== svn_node_dir
)
444 && (svn_path_is_child(src_parent_abs
, dst_parent_abs
,
447 SVN_ERR(svn_wc_adm_retrieve(&dst_access
, src_access
,
448 pair
->dst_parent
, iterpool
));
452 SVN_ERR(svn_wc_adm_open3(&dst_access
, NULL
, pair
->dst_parent
,
453 TRUE
, 0, ctx
->cancel_func
,
459 /* ### Ideally, we'd lookup the mergeinfo here, before
460 ### performing the copy. However, as an implementation
461 ### shortcut, we perform the lookup after the copy. */
463 /* Perform the copy with mergeinfo, and then the delete. */
464 err
= svn_wc_copy2(pair
->src
, dst_access
, pair
->base_name
,
465 ctx
->cancel_func
, ctx
->cancel_baton
,
466 ctx
->notify_func2
, ctx
->notify_baton2
, iterpool
);
470 err
= propagate_mergeinfo_within_wc(pair
, src_access
, dst_access
,
475 /* Perform the delete. */
476 SVN_ERR(svn_wc_delete3(pair
->src
, src_access
,
477 ctx
->cancel_func
, ctx
->cancel_baton
,
478 ctx
->notify_func2
, ctx
->notify_baton2
, FALSE
,
481 if (dst_access
!= src_access
)
482 SVN_ERR(svn_wc_adm_close(dst_access
));
483 SVN_ERR(svn_wc_adm_close(src_access
));
486 svn_sleep_for_timestamps();
489 svn_pool_destroy(iterpool
);
496 wc_to_wc_copy(const apr_array_header_t
*copy_pairs
,
497 svn_boolean_t is_move
,
498 svn_boolean_t make_parents
,
499 svn_client_ctx_t
*ctx
,
503 apr_pool_t
*iterpool
= svn_pool_create(pool
);
505 /* Check that all of our SRCs exist, and all the DSTs don't. */
506 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
508 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
509 svn_client__copy_pair_t
*);
510 svn_node_kind_t dst_kind
, dst_parent_kind
;
512 svn_pool_clear(iterpool
);
514 /* Verify that SRC_PATH exists. */
515 SVN_ERR(svn_io_check_path(pair
->src
, &pair
->src_kind
, iterpool
));
516 if (pair
->src_kind
== svn_node_none
)
517 return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND
, NULL
,
518 _("Path '%s' does not exist"),
519 svn_path_local_style(pair
->src
, pool
));
521 /* If DST_PATH does not exist, then its basename will become a new
522 file or dir added to its parent (possibly an implicit '.').
523 Else, just error out. */
524 SVN_ERR(svn_io_check_path(pair
->dst
, &dst_kind
, iterpool
));
525 if (dst_kind
!= svn_node_none
)
526 return svn_error_createf(SVN_ERR_ENTRY_EXISTS
, NULL
,
527 _("Path '%s' already exists"),
528 svn_path_local_style(pair
->dst
, pool
));
530 svn_path_split(pair
->dst
, &pair
->dst_parent
, &pair
->base_name
, pool
);
532 /* Make sure the destination parent is a directory and produce a clear
533 error message if it is not. */
534 SVN_ERR(svn_io_check_path(pair
->dst_parent
, &dst_parent_kind
, iterpool
));
535 if (make_parents
&& dst_parent_kind
== svn_node_none
)
537 SVN_ERR(svn_client__make_local_parents(pair
->dst_parent
, TRUE
, ctx
,
540 else if (dst_parent_kind
!= svn_node_dir
)
542 return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY
, NULL
,
543 _("Path '%s' is not a directory"),
544 svn_path_local_style(pair
->dst_parent
,
549 svn_pool_destroy(iterpool
);
551 /* Copy or move all targets. */
553 return do_wc_to_wc_moves(copy_pairs
, ctx
, pool
);
555 return do_wc_to_wc_copies(copy_pairs
, ctx
, pool
);
559 /* Path-specific state used as part of path_driver_cb_baton. */
563 const char *src_path
;
564 const char *dst_path
;
565 svn_node_kind_t src_kind
;
566 svn_revnum_t src_revnum
;
567 svn_boolean_t resurrection
;
568 svn_boolean_t dir_add
;
569 svn_string_t
*mergeinfo
; /* the new mergeinfo for the target */
570 } path_driver_info_t
;
573 /* The baton used with the path_driver_cb_func() callback for a copy
574 or move operation. */
575 struct path_driver_cb_baton
577 /* The editor (and its state) used to perform the operation. */
578 const svn_delta_editor_t
*editor
;
581 /* A hash of path -> path_driver_info_t *'s. */
582 apr_hash_t
*action_hash
;
584 /* Whether the operation is a move or copy. */
585 svn_boolean_t is_move
;
589 path_driver_cb_func(void **dir_baton
,
591 void *callback_baton
,
595 struct path_driver_cb_baton
*cb_baton
= callback_baton
;
596 svn_boolean_t do_delete
= FALSE
, do_add
= FALSE
;
597 path_driver_info_t
*path_info
= apr_hash_get(cb_baton
->action_hash
,
599 APR_HASH_KEY_STRING
);
601 /* Initialize return value. */
604 /* This function should never get an empty PATH. We can neither
605 create nor delete the empty PATH, so if someone is calling us
606 with such, the code is just plain wrong. */
607 assert(! svn_path_is_empty(path
));
609 /* Check to see if we need to add the path as a directory. */
610 if (path_info
->dir_add
)
612 SVN_ERR(cb_baton
->editor
->add_directory(path
, parent_baton
, NULL
,
613 SVN_INVALID_REVNUM
, pool
,
618 /* If this is a resurrection, we know the source and dest paths are
619 the same, and that our driver will only be calling us once. */
620 if (path_info
->resurrection
)
622 /* If this is a move, we do nothing. Otherwise, we do the copy. */
623 if (! cb_baton
->is_move
)
626 /* Not a resurrection. */
629 /* If this is a move, we check PATH to see if it is the source
630 or the destination of the move. */
631 if (cb_baton
->is_move
)
633 if (strcmp(path_info
->src_path
, path
) == 0)
638 /* Not a move? This must just be the copy addition. */
647 SVN_ERR(cb_baton
->editor
->delete_entry(path
, SVN_INVALID_REVNUM
,
648 parent_baton
, pool
));
652 SVN_ERR(svn_path_check_valid(path
, pool
));
654 if (path_info
->src_kind
== svn_node_file
)
657 SVN_ERR(cb_baton
->editor
->add_file(path
, parent_baton
,
659 path_info
->src_revnum
,
661 if (path_info
->mergeinfo
)
662 SVN_ERR(cb_baton
->editor
->change_file_prop(file_baton
,
664 path_info
->mergeinfo
,
666 SVN_ERR(cb_baton
->editor
->close_file(file_baton
, NULL
, pool
));
670 SVN_ERR(cb_baton
->editor
->add_directory(path
, parent_baton
,
672 path_info
->src_revnum
,
674 if (path_info
->mergeinfo
)
675 SVN_ERR(cb_baton
->editor
->change_dir_prop(*dir_baton
,
677 path_info
->mergeinfo
,
686 repos_to_repos_copy(svn_commit_info_t
**commit_info_p
,
687 const apr_array_header_t
*copy_pairs
,
688 svn_boolean_t make_parents
,
689 svn_client_ctx_t
*ctx
,
690 svn_boolean_t is_move
,
693 apr_array_header_t
*paths
= apr_array_make(pool
, 2 * copy_pairs
->nelts
,
694 sizeof(const char *));
695 apr_hash_t
*action_hash
= apr_hash_make(pool
);
696 apr_array_header_t
*path_infos
;
697 const char *top_url
, *message
, *repos_root
;
698 apr_hash_t
*revprop_table
;
699 svn_revnum_t youngest
;
700 svn_ra_session_t
*ra_session
;
701 const svn_delta_editor_t
*editor
;
704 struct path_driver_cb_baton cb_baton
;
705 apr_array_header_t
*new_dirs
= NULL
;
706 apr_pool_t
*iterpool
;
710 /* Create a path_info struct for each src/dst pair, and initialize it. */
711 path_infos
= apr_array_make(pool
, copy_pairs
->nelts
,
712 sizeof(path_driver_info_t
*));
713 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
715 path_driver_info_t
*info
= apr_pcalloc(pool
, sizeof(*info
));
716 info
->resurrection
= FALSE
;
717 APR_ARRAY_PUSH(path_infos
, path_driver_info_t
*) = info
;
720 /* We have to open our session to the longest path common to all
721 SRC_URLS and DST_URLS in the repository so we can do existence
722 checks on all paths, and so we can operate on all paths in the
724 get_copy_pair_ancestors(copy_pairs
, NULL
, NULL
, &top_url
, pool
);
726 /* Check each src/dst pair for resurrection. */
727 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
729 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
730 svn_client__copy_pair_t
*);
731 path_driver_info_t
*info
= APR_ARRAY_IDX(path_infos
, i
,
732 path_driver_info_t
*);
734 if (strcmp(pair
->src
, pair
->dst
) == 0)
736 info
->resurrection
= TRUE
;
738 /* Special edge-case! (issue #683) If you're resurrecting a
739 deleted item like this: 'svn cp -rN src_URL dst_URL', then
740 it's possible for src_URL == dst_URL == top_url. In this
741 situation, we want to open an RA session to be at least the
742 *parent* of all three. */
743 if (strcmp(pair
->src
, top_url
) == 0)
745 top_url
= svn_path_dirname(top_url
, pool
);
750 /* Open an RA session for the URL. Note that we don't have a local
751 directory, nor a place to put temp files. */
752 err
= svn_client__open_ra_session_internal(&ra_session
, top_url
,
753 NULL
, NULL
, NULL
, FALSE
, TRUE
,
756 /* If the two URLs appear not to be in the same repository, then
757 top_url will be empty and the call to svn_ra_open2()
758 above will have failed. Below we check for that, and propagate a
759 descriptive error back to the user.
761 Ideally, we'd contact the repositories and compare their UUIDs to
762 determine whether or not src and dst are in the same repository,
763 instead of depending on an essentially textual comparison.
764 However, it is simpler to assume that if someone is using the
765 same repository, then they will use the same hostname/path to
766 refer to it both times. Conversely, if the repositories are
767 different, then they can't share a non-empty prefix, so top_url
768 would still be "" and svn_ra_get_library() would still error.
769 Thus we can get this check without extra network turnarounds to
774 if ((err
->apr_err
== SVN_ERR_RA_ILLEGAL_URL
)
775 && ((top_url
== NULL
) || (top_url
[0] == '\0')))
777 svn_client__copy_pair_t
*first_pair
=
778 APR_ARRAY_IDX(copy_pairs
, 0, svn_client__copy_pair_t
*);
779 svn_error_clear(err
);
781 return svn_error_createf
782 (SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
783 _("Source and dest appear not to be in the same repository "
784 "(src: '%s'; dst: '%s')"),
785 first_pair
->src
, first_pair
->dst
);
791 iterpool
= svn_pool_create(pool
);
793 /* Iterate over the parents of the destination directory, and make a list
794 of the ones that don't yet exist. We do not have to worry about
795 reparenting the ra session because top_url is a common ancestor of the
796 destination and sources. The sources exist, so therefore top_url must
800 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, 0,
801 svn_client__copy_pair_t
*);
802 svn_node_kind_t kind
;
805 new_dirs
= apr_array_make(pool
, 0, sizeof(const char *));
806 dir
= svn_path_is_child(top_url
, svn_path_dirname(pair
->dst
, pool
),
808 SVN_ERR(svn_ra_check_path(ra_session
, dir
, SVN_INVALID_REVNUM
, &kind
,
811 while (kind
== svn_node_none
)
813 svn_pool_clear(iterpool
);
814 APR_ARRAY_PUSH(new_dirs
, const char *) = dir
;
816 svn_path_split(dir
, &dir
, NULL
, pool
);
817 SVN_ERR(svn_ra_check_path(ra_session
, dir
, SVN_INVALID_REVNUM
, &kind
,
822 svn_pool_destroy(iterpool
);
824 SVN_ERR(svn_ra_get_repos_root2(ra_session
, &repos_root
, pool
));
826 /* For each src/dst pair, check to see if that SRC_URL is a child of
827 the DST_URL (excepting the case where DST_URL is the repo root).
828 If it is, and the parent of DST_URL is the current TOP_URL, then we
829 need to reparent the session one directory higher, the parent of
831 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
833 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
834 svn_client__copy_pair_t
*);
835 path_driver_info_t
*info
= APR_ARRAY_IDX(path_infos
, i
,
836 path_driver_info_t
*);
838 if (strcmp(pair
->dst
, repos_root
) != 0
839 && svn_path_is_child(pair
->dst
, pair
->src
, pool
) != NULL
)
841 info
->resurrection
= TRUE
;
842 top_url
= svn_path_dirname(top_url
, pool
);
844 SVN_ERR(svn_ra_reparent(ra_session
, top_url
, pool
));
848 /* Fetch the youngest revision. */
849 SVN_ERR(svn_ra_get_latest_revnum(ra_session
, &youngest
, pool
));
851 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
853 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
854 svn_client__copy_pair_t
*);
855 path_driver_info_t
*info
= APR_ARRAY_IDX(path_infos
, i
,
856 path_driver_info_t
*);
857 svn_node_kind_t dst_kind
;
858 const char *src_rel
, *dst_rel
;
859 svn_opt_revision_t
*new_rev
, *ignored_rev
, dead_end_rev
;
860 const char *ignored_url
;
862 /* Pass NULL for the path, to ensure error if trying to get a
863 revision based on the working copy. */
864 SVN_ERR(svn_client__get_revision_number
865 (&pair
->src_revnum
, NULL
, ra_session
, &pair
->src_op_revision
,
868 info
->src_revnum
= pair
->src_revnum
;
870 dead_end_rev
.kind
= svn_opt_revision_unspecified
;
872 /* Run the history function to get the object's url in the operational
874 SVN_ERR(svn_client__repos_locations(&pair
->src
, &new_rev
,
875 &ignored_url
, &ignored_rev
,
877 pair
->src
, &pair
->src_peg_revision
,
878 &pair
->src_op_revision
, &dead_end_rev
,
881 /* Get the portions of the SRC and DST URLs that are relative to
882 TOP_URL, and URI-decode those sections. */
883 src_rel
= svn_path_is_child(top_url
, pair
->src
, pool
);
885 src_rel
= svn_path_uri_decode(src_rel
, pool
);
889 dst_rel
= svn_path_is_child(top_url
, pair
->dst
, pool
);
891 dst_rel
= svn_path_uri_decode(dst_rel
, pool
);
895 /* We can't move something into itself, period. */
896 if (svn_path_is_empty(src_rel
) && is_move
)
897 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
898 _("Cannot move URL '%s' into itself"),
901 /* Verify that SRC_URL exists in the repository. */
902 SVN_ERR(svn_ra_check_path(ra_session
, src_rel
, pair
->src_revnum
,
903 &info
->src_kind
, pool
));
904 if (info
->src_kind
== svn_node_none
)
905 return svn_error_createf
906 (SVN_ERR_FS_NOT_FOUND
, NULL
,
907 _("Path '%s' does not exist in revision %ld"),
908 pair
->src
, pair
->src_revnum
);
910 /* Figure out the basename that will result from this operation. */
911 SVN_ERR(svn_ra_check_path(ra_session
, dst_rel
, youngest
, &dst_kind
,
913 if (dst_kind
!= svn_node_none
)
915 /* We disallow the overwriting of existing paths. */
916 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS
, NULL
,
917 _("Path '%s' already exists"), dst_rel
);
920 info
->src_url
= pair
->src
;
921 info
->src_path
= src_rel
;
922 info
->dst_path
= dst_rel
;
925 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx
))
927 /* Produce a list of new paths to add, and provide it to the
928 mechanism used to acquire a log message. */
929 svn_client_commit_item3_t
*item
;
930 const char *tmp_file
;
931 apr_array_header_t
*commit_items
932 = apr_array_make(pool
, 2 * copy_pairs
->nelts
, sizeof(item
));
934 /* Add any intermediate directories to the message */
937 for (i
= 0; i
< new_dirs
->nelts
; i
++)
939 const char *url
= APR_ARRAY_IDX(new_dirs
, i
, const char *);
941 SVN_ERR(svn_client_commit_item_create
942 ((const svn_client_commit_item3_t
**) &item
, pool
));
944 item
->url
= svn_path_join(top_url
, url
, pool
);
945 item
->state_flags
= SVN_CLIENT_COMMIT_ITEM_ADD
;
946 APR_ARRAY_PUSH(commit_items
, svn_client_commit_item3_t
*) = item
;
950 for (i
= 0; i
< path_infos
->nelts
; i
++)
952 path_driver_info_t
*info
= APR_ARRAY_IDX(path_infos
, i
,
953 path_driver_info_t
*);
955 SVN_ERR(svn_client_commit_item_create
956 ((const svn_client_commit_item3_t
**) &item
, pool
));
958 item
->url
= svn_path_join(top_url
, info
->dst_path
, pool
);
959 item
->state_flags
= SVN_CLIENT_COMMIT_ITEM_ADD
;
960 APR_ARRAY_PUSH(commit_items
, svn_client_commit_item3_t
*) = item
;
961 apr_hash_set(action_hash
, info
->dst_path
, APR_HASH_KEY_STRING
,
964 if (is_move
&& (! info
->resurrection
))
966 item
= apr_pcalloc(pool
, sizeof(*item
));
967 item
->url
= svn_path_join(top_url
, info
->src_path
, pool
);
968 item
->state_flags
= SVN_CLIENT_COMMIT_ITEM_DELETE
;
969 APR_ARRAY_PUSH(commit_items
, svn_client_commit_item3_t
*) = item
;
970 apr_hash_set(action_hash
, info
->src_path
, APR_HASH_KEY_STRING
,
975 SVN_ERR(svn_client__get_log_msg(&message
, &tmp_file
, commit_items
,
983 /* Setup our PATHS for the path-based editor drive. */
984 /* First any intermediate directories. */
987 for (i
= 0; i
< new_dirs
->nelts
; i
++)
989 const char *url
= APR_ARRAY_IDX(new_dirs
, i
, const char *);
990 path_driver_info_t
*info
= apr_pcalloc(pool
, sizeof(*info
));
992 info
->dst_path
= url
;
993 info
->dir_add
= TRUE
;
995 APR_ARRAY_PUSH(paths
, const char *) = url
;
996 apr_hash_set(action_hash
, url
, APR_HASH_KEY_STRING
, info
);
1000 /* Then, copy destinations, and possibly move sources. */
1001 for (i
= 0; i
< path_infos
->nelts
; i
++)
1003 path_driver_info_t
*info
= APR_ARRAY_IDX(path_infos
, i
,
1004 path_driver_info_t
*);
1005 apr_hash_t
*mergeinfo
;
1006 SVN_ERR(calculate_target_mergeinfo(ra_session
, &mergeinfo
, NULL
,
1007 info
->src_url
, info
->src_revnum
,
1010 SVN_ERR(svn_mergeinfo_to_string(&info
->mergeinfo
, mergeinfo
, pool
));
1012 APR_ARRAY_PUSH(paths
, const char *) = info
->dst_path
;
1013 if (is_move
&& (! info
->resurrection
))
1014 APR_ARRAY_PUSH(paths
, const char *) = info
->src_path
;
1017 SVN_ERR(svn_client__get_revprop_table(&revprop_table
, message
, ctx
, pool
));
1019 /* Fetch RA commit editor. */
1020 SVN_ERR(svn_client__commit_get_baton(&commit_baton
, commit_info_p
, pool
));
1021 SVN_ERR(svn_ra_get_commit_editor3(ra_session
, &editor
, &edit_baton
,
1023 svn_client__commit_callback
,
1025 NULL
, TRUE
, /* No lock tokens */
1028 /* Setup the callback baton. */
1029 cb_baton
.editor
= editor
;
1030 cb_baton
.edit_baton
= edit_baton
;
1031 cb_baton
.action_hash
= action_hash
;
1032 cb_baton
.is_move
= is_move
;
1034 /* Call the path-based editor driver. */
1035 err
= svn_delta_path_driver(editor
, edit_baton
, youngest
, paths
,
1036 path_driver_cb_func
, &cb_baton
, pool
);
1039 /* At least try to abort the edit (and fs txn) before throwing err. */
1040 svn_error_clear(editor
->abort_edit(edit_baton
, pool
));
1044 /* Close the edit. */
1045 SVN_ERR(editor
->close_edit(edit_baton
, pool
));
1047 return SVN_NO_ERROR
;
1052 static svn_error_t
*
1053 wc_to_repos_copy(svn_commit_info_t
**commit_info_p
,
1054 const apr_array_header_t
*copy_pairs
,
1055 svn_boolean_t make_parents
,
1056 svn_client_ctx_t
*ctx
,
1059 const char *message
;
1060 apr_hash_t
*revprop_table
;
1061 const char *top_src_path
, *top_dst_url
, *repos_root
;
1062 svn_ra_session_t
*ra_session
;
1063 const svn_delta_editor_t
*editor
;
1065 svn_node_kind_t base_kind
;
1067 apr_hash_t
*committables
;
1068 svn_wc_adm_access_t
*adm_access
, *dir_access
;
1069 apr_array_header_t
*commit_items
;
1070 const svn_wc_entry_t
*entry
;
1071 apr_pool_t
*iterpool
;
1072 apr_array_header_t
*new_dirs
= NULL
;
1075 /* The commit process uses absolute paths, so we need to open the access
1076 baton using absolute paths, and so we really need to use absolute
1077 paths everywhere. */
1078 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
1080 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
1081 svn_client__copy_pair_t
*);
1082 SVN_ERR(svn_path_get_absolute(&pair
->src_abs
, pair
->src
, pool
));
1085 /*Find the common root of all the source paths, and probe the wc. */
1086 get_copy_pair_ancestors(copy_pairs
, &top_src_path
, NULL
, NULL
, pool
);
1087 SVN_ERR(svn_wc_adm_probe_open3(&adm_access
, NULL
, top_src_path
,
1088 FALSE
, -1, ctx
->cancel_func
,
1089 ctx
->cancel_baton
, pool
));
1091 /* Determine the least common ancesor for the destinations, and open an RA
1092 session to that location. */
1093 svn_path_split(APR_ARRAY_IDX(copy_pairs
, 0, svn_client__copy_pair_t
*)->dst
,
1096 for (i
= 1; i
< copy_pairs
->nelts
; i
++)
1098 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
1099 svn_client__copy_pair_t
*);
1100 top_dst_url
= svn_path_get_longest_ancestor(top_dst_url
, pair
->dst
, pool
);
1103 SVN_ERR(svn_client__open_ra_session_internal(&ra_session
, top_dst_url
,
1104 svn_wc_adm_access_path
1106 adm_access
, NULL
, TRUE
, TRUE
,
1109 /* If requested, determine the nearest existing parent of the destination,
1110 and reparent the ra session there. */
1113 const char *root_url
= top_dst_url
;
1114 svn_node_kind_t kind
;
1116 new_dirs
= apr_array_make(pool
, 0, sizeof(const char *));
1117 SVN_ERR(svn_ra_check_path(ra_session
, "", SVN_INVALID_REVNUM
, &kind
,
1120 while (kind
== svn_node_none
)
1122 APR_ARRAY_PUSH(new_dirs
, const char *) = root_url
;
1123 svn_path_split(root_url
, &root_url
, NULL
, pool
);
1125 SVN_ERR(svn_ra_reparent(ra_session
, root_url
, pool
));
1126 SVN_ERR(svn_ra_check_path(ra_session
, "", SVN_INVALID_REVNUM
, &kind
,
1130 top_dst_url
= root_url
;
1133 /* Figure out the basename that will result from each copy and check to make
1134 sure it doesn't exist already. */
1135 iterpool
= svn_pool_create(pool
);
1137 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
1139 svn_node_kind_t dst_kind
;
1140 const char *dst_rel
;
1141 svn_client__copy_pair_t
*pair
=
1142 APR_ARRAY_IDX(copy_pairs
, i
, svn_client__copy_pair_t
*);
1144 svn_pool_clear(iterpool
);
1146 SVN_ERR(svn_wc_entry(&entry
, pair
->src
, adm_access
, FALSE
, iterpool
));
1147 pair
->src_revnum
= entry
->revision
;
1149 dst_rel
= svn_path_uri_decode(svn_path_is_child(top_dst_url
,
1153 SVN_ERR(svn_ra_check_path(ra_session
, dst_rel
, SVN_INVALID_REVNUM
,
1154 &dst_kind
, iterpool
));
1155 if (dst_kind
!= svn_node_none
)
1157 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS
, NULL
,
1158 _("Path '%s' already exists"), pair
->dst
);
1162 svn_pool_destroy(iterpool
);
1164 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx
))
1166 /* Produce a list of new paths to add, and provide it to the
1167 mechanism used to acquire a log message. */
1168 svn_client_commit_item3_t
*item
;
1169 const char *tmp_file
;
1170 commit_items
= apr_array_make(pool
, copy_pairs
->nelts
, sizeof(item
));
1172 /* Add any intermediate directories to the message */
1175 for (i
= 0; i
< new_dirs
->nelts
; i
++)
1177 const char *url
= APR_ARRAY_IDX(new_dirs
, i
, const char *);
1179 SVN_ERR(svn_client_commit_item_create
1180 ((const svn_client_commit_item3_t
**) &item
, pool
));
1183 item
->state_flags
= SVN_CLIENT_COMMIT_ITEM_ADD
;
1184 APR_ARRAY_PUSH(commit_items
, svn_client_commit_item3_t
*) = item
;
1188 for (i
= 0; i
< copy_pairs
->nelts
; i
++ )
1190 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
1191 svn_client__copy_pair_t
*);
1193 SVN_ERR(svn_client_commit_item_create
1194 ((const svn_client_commit_item3_t
**) &item
, pool
));
1196 item
->url
= pair
->dst
;
1197 item
->state_flags
= SVN_CLIENT_COMMIT_ITEM_ADD
;
1198 APR_ARRAY_PUSH(commit_items
, svn_client_commit_item3_t
*) = item
;
1201 SVN_ERR(svn_client__get_log_msg(&message
, &tmp_file
, commit_items
,
1205 SVN_ERR(svn_wc_adm_close(adm_access
));
1206 return SVN_NO_ERROR
;
1212 SVN_ERR(svn_client__get_revprop_table(&revprop_table
, message
, ctx
, pool
));
1214 /* Crawl the working copy for commit items. */
1215 SVN_ERR(svn_io_check_path(top_src_path
, &base_kind
, pool
));
1216 if (base_kind
== svn_node_dir
)
1217 SVN_ERR(svn_wc_adm_retrieve(&dir_access
, adm_access
, top_src_path
, pool
));
1219 dir_access
= adm_access
;
1221 SVN_ERR(svn_client__get_copy_committables(&committables
,
1222 copy_pairs
, dir_access
,
1225 /* ### todo: There should be only one hash entry, which currently
1226 has a hacked name until we have the entries files storing
1227 canonical repository URLs. Then, the hacked name can go away and
1228 be replaced with a entry->repos (or whereever the entry's
1229 canonical repos URL is stored). */
1230 if (! (commit_items
= apr_hash_get(committables
,
1231 SVN_CLIENT__SINGLE_REPOS_NAME
,
1232 APR_HASH_KEY_STRING
)))
1234 SVN_ERR(svn_wc_adm_close(adm_access
));
1235 return SVN_NO_ERROR
;
1238 /* If we are creating intermediate directories, tack them onto the list
1242 for (i
= 0; i
< new_dirs
->nelts
; i
++)
1244 const char *url
= APR_ARRAY_IDX(new_dirs
, i
, const char *);
1245 svn_client_commit_item3_t
*item
;
1247 SVN_ERR(svn_client_commit_item_create
1248 ((const svn_client_commit_item3_t
**) &item
, pool
));
1251 item
->state_flags
= SVN_CLIENT_COMMIT_ITEM_ADD
;
1252 item
->incoming_prop_changes
= apr_array_make(pool
, 1,
1253 sizeof(svn_prop_t
*));
1254 APR_ARRAY_PUSH(commit_items
, svn_client_commit_item3_t
*) = item
;
1258 /* Reparent the ra_session to repos_root. So that 'svn_ra_get_log'
1259 on paths relative to repos_root would work fine. */
1260 SVN_ERR(svn_ra_get_repos_root2(ra_session
, &repos_root
, pool
));
1261 SVN_ERR(svn_ra_reparent(ra_session
, repos_root
, pool
));
1263 /* ### TODO: This extra loop would be unnecessary if this code lived
1264 ### in svn_client__get_copy_committables(), which is incidentally
1265 ### only used above (so should really be in this source file). */
1266 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
1268 svn_prop_t
*mergeinfo_prop
;
1269 apr_hash_t
*mergeinfo
, *wc_mergeinfo
;
1270 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
1271 svn_client__copy_pair_t
*);
1272 svn_client_commit_item3_t
*item
=
1273 APR_ARRAY_IDX(commit_items
, i
, svn_client_commit_item3_t
*);
1275 /* Set the mergeinfo for the destination to the combined merge
1276 info known to the WC and the repository. */
1277 item
->outgoing_prop_changes
= apr_array_make(pool
, 1,
1278 sizeof(svn_prop_t
*));
1279 mergeinfo_prop
= apr_palloc(item
->outgoing_prop_changes
->pool
,
1280 sizeof(svn_prop_t
));
1281 mergeinfo_prop
->name
= SVN_PROP_MERGEINFO
;
1282 SVN_ERR(calculate_target_mergeinfo(ra_session
, &mergeinfo
, adm_access
,
1283 pair
->src
, pair
->src_revnum
,
1285 SVN_ERR(svn_wc_entry(&entry
, pair
->src
, adm_access
, FALSE
, pool
));
1286 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo
, entry
,
1287 pair
->src
, FALSE
, adm_access
, ctx
,
1289 if (wc_mergeinfo
&& mergeinfo
)
1290 SVN_ERR(svn_mergeinfo_merge(mergeinfo
, wc_mergeinfo
, pool
));
1291 else if (! mergeinfo
)
1292 mergeinfo
= wc_mergeinfo
;
1295 SVN_ERR(svn_mergeinfo_to_string((svn_string_t
**)
1296 &mergeinfo_prop
->value
,
1298 APR_ARRAY_PUSH(item
->outgoing_prop_changes
, svn_prop_t
*) =
1303 /* Sort and condense our COMMIT_ITEMS. */
1304 SVN_ERR(svn_client__condense_commit_items(&top_dst_url
,
1305 commit_items
, pool
));
1307 /* Open an RA session to DST_URL. */
1308 SVN_ERR(svn_client__open_ra_session_internal(&ra_session
, top_dst_url
,
1309 NULL
, NULL
, commit_items
,
1310 FALSE
, FALSE
, ctx
, pool
));
1312 /* Fetch RA commit editor. */
1313 SVN_ERR(svn_client__commit_get_baton(&commit_baton
, commit_info_p
, pool
));
1314 SVN_ERR(svn_ra_get_commit_editor3(ra_session
, &editor
, &edit_baton
,
1315 revprop_table
, svn_client__commit_callback
,
1317 TRUE
, /* No lock tokens */
1320 /* Perform the commit. */
1321 SVN_ERR_W(svn_client__do_commit(top_dst_url
, commit_items
, adm_access
,
1323 0, /* ### any notify_path_offset needed? */
1324 NULL
, NULL
, ctx
, pool
),
1325 _("Commit failed (details follow):"));
1327 /* Sleep to ensure timestamp integrity. */
1328 svn_sleep_for_timestamps();
1330 /* It's only a read lock, so unlocking is harmless. */
1331 SVN_ERR(svn_wc_adm_close(adm_access
));
1334 return SVN_NO_ERROR
;
1337 /* Peform each individual copy operation for a repos -> wc copy. A
1338 helper for repos_to_wc_copy(). */
1339 static svn_error_t
*
1340 repos_to_wc_copy_single(svn_client__copy_pair_t
*pair
,
1341 svn_boolean_t same_repositories
,
1342 svn_ra_session_t
*ra_session
,
1343 svn_wc_adm_access_t
*adm_access
,
1344 svn_client_ctx_t
*ctx
,
1347 svn_revnum_t src_revnum
= pair
->src_revnum
;
1348 apr_hash_t
*src_mergeinfo
;
1349 const svn_wc_entry_t
*dst_entry
;
1351 if (pair
->src_kind
== svn_node_dir
)
1353 SVN_ERR(svn_client__checkout_internal
1354 (NULL
, pair
->src_original
, pair
->dst
, &pair
->src_peg_revision
,
1355 &pair
->src_op_revision
,
1356 SVN_DEPTH_INFINITY_OR_FILES(TRUE
),
1357 FALSE
, FALSE
, NULL
, ctx
, pool
));
1359 /* Rewrite URLs recursively, remove wcprops, and mark everything
1360 as 'copied' -- assuming that the src and dst are from the
1361 same repository. (It's kind of weird that svn_wc_add() is the
1362 way to do this; see its doc for more about the controversy.) */
1363 if (same_repositories
)
1365 svn_wc_adm_access_t
*dst_access
;
1366 SVN_ERR(svn_wc_adm_open3(&dst_access
, adm_access
, pair
->dst
, TRUE
,
1367 -1, ctx
->cancel_func
, ctx
->cancel_baton
,
1369 SVN_ERR(svn_wc_entry(&dst_entry
, pair
->dst
, dst_access
, FALSE
,
1372 if (pair
->src_op_revision
.kind
== svn_opt_revision_head
)
1374 /* If we just checked out from the "head" revision,
1375 that's fine, but we don't want to pass a '-1' as a
1376 copyfrom_rev to svn_wc_add(). That function will
1377 dump it right into the entry, and when we try to
1378 commit later on, the 'add-dir-with-history' step will
1379 be -very- unhappy; it only accepts specific
1382 On the other hand, we *could* say that -1 is a
1383 legitimate copyfrom_rev, but I think that's bogus.
1384 Somebody made a copy from a particular revision; if
1385 they wait a long time to commit, it would be terrible
1386 if the copied happened from a newer revision!! */
1388 /* We just did a checkout; whatever revision we just
1389 got, that should be the copyfrom_revision when we
1391 src_revnum
= dst_entry
->revision
;
1394 /* Schedule dst_path for addition in parent, with copy history.
1395 (This function also recursively puts a 'copied' flag on every
1397 SVN_ERR(svn_wc_add2(pair
->dst
, adm_access
, pair
->src
,
1399 ctx
->cancel_func
, ctx
->cancel_baton
,
1400 ctx
->notify_func2
, ctx
->notify_baton2
, pool
));
1402 /* ### Recording of implied mergeinfo should really occur
1403 ### *before* the notification callback is invoked by
1404 ### svn_wc_add2(), but can't occur before we add the new
1406 SVN_ERR(calculate_target_mergeinfo(ra_session
, &src_mergeinfo
, NULL
,
1407 pair
->src
, src_revnum
,
1409 SVN_ERR(extend_wc_mergeinfo(pair
->dst
, dst_entry
, src_mergeinfo
,
1410 dst_access
, ctx
, pool
));
1412 else /* different repositories */
1414 /* ### Someday, we would just call svn_wc_add(), as above,
1415 but with no copyfrom args. I.e. in the
1416 directory-foreign-UUID case, we still want everything
1417 scheduled for addition, URLs rewritten, and wcprop cache
1418 deleted, but WITHOUT any copied flags or copyfrom urls.
1419 Unfortunately, svn_wc_add() is such a mess that it chokes
1420 at the moment when we pass a NULL copyfromurl. */
1422 return svn_error_createf
1423 (SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
1424 _("Source URL '%s' is from foreign repository; "
1425 "leaving it as a disjoint WC"), pair
->src
);
1427 } /* end directory case */
1429 else if (pair
->src_kind
== svn_node_file
)
1432 svn_stream_t
*fstream
;
1433 svn_revnum_t real_rev
;
1434 const char *new_text_path
;
1435 apr_hash_t
*new_props
;
1437 const char *src_rel
;
1439 SVN_ERR(svn_io_open_unique_file2(&fp
, &new_text_path
, pair
->dst
, ".tmp",
1440 svn_io_file_del_none
, pool
));
1442 fstream
= svn_stream_from_aprfile2(fp
, FALSE
, pool
);
1443 SVN_ERR(svn_client__path_relative_to_session(&src_rel
, ra_session
,
1445 SVN_ERR(svn_ra_get_file(ra_session
, src_rel
, src_revnum
, fstream
,
1446 &real_rev
, &new_props
, pool
));
1447 SVN_ERR(svn_stream_close(fstream
));
1449 /* If SRC_REVNUM is invalid (HEAD), then REAL_REV is now the
1450 revision that was actually retrieved. This is the value we
1451 want to use as 'copyfrom_rev' below. */
1452 if (! SVN_IS_VALID_REVNUM(src_revnum
))
1453 src_revnum
= real_rev
;
1455 err
= svn_wc_add_repos_file2
1456 (pair
->dst
, adm_access
,
1457 new_text_path
, NULL
, new_props
, NULL
,
1458 same_repositories
? pair
->src
: NULL
,
1459 same_repositories
? src_revnum
: SVN_INVALID_REVNUM
,
1462 SVN_ERR(svn_wc_entry(&dst_entry
, pair
->dst
, adm_access
, FALSE
, pool
));
1463 SVN_ERR(calculate_target_mergeinfo(ra_session
, &src_mergeinfo
,
1464 NULL
, pair
->src
, src_revnum
,
1466 SVN_ERR(extend_wc_mergeinfo(pair
->dst
, dst_entry
, src_mergeinfo
,
1467 adm_access
, ctx
, pool
));
1469 /* Ideally, svn_wc_add_repos_file() would take a notify function
1470 and baton, and we wouldn't have to make this call here.
1471 However, the situation is... complicated. See issue #1552
1472 for the full story. */
1473 if (!err
&& ctx
->notify_func2
)
1475 svn_wc_notify_t
*notify
= svn_wc_create_notify(pair
->dst
,
1478 notify
->kind
= pair
->src_kind
;
1479 (*ctx
->notify_func2
)(ctx
->notify_baton2
, notify
, pool
);
1482 svn_sleep_for_timestamps();
1486 return SVN_NO_ERROR
;
1489 static svn_error_t
*
1490 repos_to_wc_copy(const apr_array_header_t
*copy_pairs
,
1491 svn_boolean_t make_parents
,
1492 svn_client_ctx_t
*ctx
,
1495 svn_ra_session_t
*ra_session
;
1496 svn_wc_adm_access_t
*adm_access
;
1497 const char *top_src_url
, *top_dst_path
;
1498 const char *src_uuid
= NULL
, *dst_uuid
= NULL
;
1499 svn_boolean_t same_repositories
;
1500 apr_pool_t
*iterpool
= svn_pool_create(pool
);
1503 /* Get the real path for the source, based upon its peg revision. */
1504 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
1506 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
1507 svn_client__copy_pair_t
*);
1508 const char *src
, *ignored_url
;
1509 svn_opt_revision_t
*new_rev
, *ignored_rev
, dead_end_rev
;
1511 svn_pool_clear(iterpool
);
1512 dead_end_rev
.kind
= svn_opt_revision_unspecified
;
1514 SVN_ERR(svn_client__repos_locations(&src
, &new_rev
,
1515 &ignored_url
, &ignored_rev
,
1518 &pair
->src_peg_revision
,
1519 &pair
->src_op_revision
,
1523 pair
->src_original
= pair
->src
;
1524 pair
->src
= apr_pstrdup(pool
, src
);
1527 get_copy_pair_ancestors(copy_pairs
, &top_src_url
, &top_dst_path
, NULL
, pool
);
1528 if (copy_pairs
->nelts
== 1)
1529 top_src_url
= svn_path_dirname(top_src_url
, pool
);
1531 /* Open a repository session to the longest common src ancestor. We do not
1532 (yet) have a working copy, so we don't have a corresponding path and
1533 tempfiles cannot go into the admin area. */
1534 SVN_ERR(svn_client__open_ra_session_internal(&ra_session
, top_src_url
, NULL
,
1535 NULL
, NULL
, FALSE
, TRUE
,
1538 /* Pass null for the path, to ensure error if trying to get a
1539 revision based on the working copy. */
1540 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
1542 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
1543 svn_client__copy_pair_t
*);
1545 SVN_ERR(svn_client__get_revision_number
1546 (&pair
->src_revnum
, NULL
, ra_session
, &pair
->src_op_revision
,
1550 /* Get the correct src path for the peg revision used, and verify that we
1551 aren't overwriting an existing path. */
1552 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
1554 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
1555 svn_client__copy_pair_t
*);
1556 svn_node_kind_t dst_parent_kind
, dst_kind
;
1557 const char *dst_parent
;
1558 const char *src_rel
;
1560 svn_pool_clear(iterpool
);
1562 /* Next, make sure that the path exists in the repository. */
1563 SVN_ERR(svn_client__path_relative_to_session(&src_rel
, ra_session
,
1564 pair
->src
, iterpool
));
1565 SVN_ERR(svn_ra_check_path(ra_session
, src_rel
, pair
->src_revnum
,
1566 &pair
->src_kind
, pool
));
1567 if (pair
->src_kind
== svn_node_none
)
1569 if (SVN_IS_VALID_REVNUM(pair
->src_revnum
))
1570 return svn_error_createf
1571 (SVN_ERR_FS_NOT_FOUND
, NULL
,
1572 _("Path '%s' not found in revision %ld"),
1573 pair
->src
, pair
->src_revnum
);
1575 return svn_error_createf
1576 (SVN_ERR_FS_NOT_FOUND
, NULL
,
1577 _("Path '%s' not found in head revision"), pair
->src
);
1580 /* Figure out about dst. */
1581 SVN_ERR(svn_io_check_path(pair
->dst
, &dst_kind
, iterpool
));
1582 if (dst_kind
!= svn_node_none
)
1584 return svn_error_createf(SVN_ERR_ENTRY_EXISTS
, NULL
,
1585 _("Path '%s' already exists"),
1586 svn_path_local_style(pair
->dst
, pool
));
1589 /* Make sure the destination parent is a directory and produce a clear
1590 error message if it is not. */
1591 dst_parent
= svn_path_dirname(pair
->dst
, iterpool
);
1592 SVN_ERR(svn_io_check_path(dst_parent
, &dst_parent_kind
, iterpool
));
1593 if (make_parents
&& dst_parent_kind
== svn_node_none
)
1595 SVN_ERR(svn_client__make_local_parents(dst_parent
, TRUE
, ctx
,
1598 else if (dst_parent_kind
!= svn_node_dir
)
1600 return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY
, NULL
,
1601 _("Path '%s' is not a directory"),
1602 svn_path_local_style(dst_parent
, pool
));
1606 /* Probe the wc at the longest common dst ancestor. */
1607 SVN_ERR(svn_wc_adm_probe_open3(&adm_access
, NULL
, top_dst_path
, TRUE
,
1608 0, ctx
->cancel_func
, ctx
->cancel_baton
,
1611 /* We've already checked for physical obstruction by a working file.
1612 But there could also be logical obstruction by an entry whose
1613 working file happens to be missing.*/
1614 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
1616 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
1617 svn_client__copy_pair_t
*);
1618 const svn_wc_entry_t
*ent
;
1620 svn_pool_clear(iterpool
);
1622 SVN_ERR(svn_wc_entry(&ent
, pair
->dst
, adm_access
, FALSE
, iterpool
));
1623 if (ent
&& (ent
->kind
!= svn_node_dir
) &&
1624 (ent
->schedule
!= svn_wc_schedule_delete
))
1625 return svn_error_createf
1626 (SVN_ERR_WC_OBSTRUCTED_UPDATE
, NULL
,
1627 _("Entry for '%s' exists (though the working file is missing)"),
1628 svn_path_local_style(pair
->dst
, pool
));
1631 /* Decide whether the two repositories are the same or not. */
1633 svn_error_t
*src_err
, *dst_err
;
1636 /* Get the repository uuid of SRC_URL */
1637 src_err
= svn_ra_get_uuid2(ra_session
, &src_uuid
, pool
);
1638 if (src_err
&& src_err
->apr_err
!= SVN_ERR_RA_NO_REPOS_UUID
)
1641 /* Get repository uuid of dst's parent directory, since dst may
1642 not exist. ### TODO: we should probably walk up the wc here,
1643 in case the parent dir has an imaginary URL. */
1644 if (copy_pairs
->nelts
== 1)
1645 svn_path_split(top_dst_path
, &parent
, NULL
, pool
);
1647 parent
= top_dst_path
;
1648 dst_err
= svn_client_uuid_from_path(&dst_uuid
, parent
, adm_access
,
1650 if (dst_err
&& dst_err
->apr_err
!= SVN_ERR_RA_NO_REPOS_UUID
)
1653 /* If either of the UUIDs are nonexistent, then at least one of
1654 the repositories must be very old. Rather than punish the
1655 user, just assume the repositories are different, so no
1656 copy-history is attempted. */
1657 if (src_err
|| dst_err
|| (! src_uuid
) || (! dst_uuid
))
1658 same_repositories
= FALSE
;
1661 same_repositories
= (strcmp(src_uuid
, dst_uuid
) == 0) ? TRUE
: FALSE
;
1664 /* Perform the move for each of the copy_pairs. */
1665 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
1667 /* Check for cancellation */
1668 if (ctx
->cancel_func
)
1669 SVN_ERR(ctx
->cancel_func(ctx
->cancel_baton
));
1671 svn_pool_clear(iterpool
);
1673 SVN_ERR(repos_to_wc_copy_single(APR_ARRAY_IDX(copy_pairs
, i
,
1674 svn_client__copy_pair_t
*),
1676 ra_session
, adm_access
,
1680 SVN_ERR(svn_wc_adm_close(adm_access
));
1682 svn_pool_destroy(iterpool
);
1684 return SVN_NO_ERROR
;
1687 #define NEED_REPOS_REVNUM(revision) \
1688 ((revision.kind != svn_opt_revision_unspecified) \
1689 && (revision.kind != svn_opt_revision_working))
1691 /* Perform all allocations in POOL. */
1692 static svn_error_t
*
1693 setup_copy(svn_commit_info_t
**commit_info_p
,
1694 const apr_array_header_t
*sources
,
1695 const char *dst_path_in
,
1696 svn_boolean_t is_move
,
1697 svn_boolean_t force
,
1698 svn_boolean_t make_parents
,
1699 svn_client_ctx_t
*ctx
,
1702 apr_array_header_t
*copy_pairs
= apr_array_make(pool
, sources
->nelts
,
1703 sizeof(struct copy_pair
*));
1704 svn_boolean_t srcs_are_urls
, dst_is_url
;
1707 /* Check to see if the supplied peg revisions make sense. */
1708 for (i
= 0; i
< sources
->nelts
; i
++)
1710 svn_client_copy_source_t
*source
=
1711 ((svn_client_copy_source_t
**) (sources
->elts
))[i
];
1713 if (svn_path_is_url(source
->path
)
1714 && (source
->peg_revision
->kind
== svn_opt_revision_base
1715 || source
->peg_revision
->kind
== svn_opt_revision_committed
1716 || source
->peg_revision
->kind
== svn_opt_revision_previous
))
1717 return svn_error_create
1718 (SVN_ERR_CLIENT_BAD_REVISION
, NULL
,
1719 _("Revision type requires a working copy path, not a URL"));
1722 /* Are either of our paths URLs?
1723 * Just check the first src_path. If there are more than one, we'll check
1724 * for homogeneity amoung them down below. */
1725 srcs_are_urls
= svn_path_is_url(APR_ARRAY_IDX(sources
, 0,
1726 svn_client_copy_source_t
*)->path
);
1727 dst_is_url
= svn_path_is_url(dst_path_in
);
1729 /* If we have multiple source paths, it implies the dst_path is a directory
1730 * we are moving or copying into. Populate the dst_paths array to contain
1731 * a destination path for each of the source paths. */
1732 if (sources
->nelts
> 1)
1734 apr_pool_t
*iterpool
= svn_pool_create(pool
);
1736 for (i
= 0; i
< sources
->nelts
; i
++)
1738 svn_client_copy_source_t
*source
= APR_ARRAY_IDX(sources
, i
,
1739 svn_client_copy_source_t
*);
1740 svn_client__copy_pair_t
*pair
= apr_palloc(pool
, sizeof(*pair
));
1741 const char *src_basename
;
1742 svn_boolean_t src_is_url
= svn_path_is_url(source
->path
);
1744 svn_pool_clear(iterpool
);
1746 pair
->src
= apr_pstrdup(pool
, source
->path
);
1747 pair
->src_op_revision
= *source
->revision
;
1748 pair
->src_peg_revision
= *source
->peg_revision
;
1750 SVN_ERR(svn_opt_resolve_revisions(&pair
->src_peg_revision
,
1751 &pair
->src_op_revision
,
1755 src_basename
= svn_path_basename(pair
->src
, iterpool
);
1756 if (srcs_are_urls
&& ! dst_is_url
)
1757 src_basename
= svn_path_uri_decode(src_basename
, pool
);
1759 /* Check to see if all the sources are urls or all working copy
1761 if (src_is_url
!= srcs_are_urls
)
1762 return svn_error_create
1763 (SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
1764 _("Cannot mix repository and working copy sources"));
1766 pair
->dst
= svn_path_join(dst_path_in
, src_basename
, pool
);
1767 APR_ARRAY_PUSH(copy_pairs
, svn_client__copy_pair_t
*) = pair
;
1770 svn_pool_destroy(iterpool
);
1774 /* Only one source path. */
1775 svn_client__copy_pair_t
*pair
= apr_palloc(pool
, sizeof(*pair
));
1776 svn_client_copy_source_t
*source
=
1777 APR_ARRAY_IDX(sources
, 0, svn_client_copy_source_t
*);
1779 pair
->src
= apr_pstrdup(pool
, source
->path
);
1780 pair
->src_op_revision
= *source
->revision
;
1781 pair
->src_peg_revision
= *source
->peg_revision
;
1783 SVN_ERR(svn_opt_resolve_revisions(&pair
->src_peg_revision
,
1784 &pair
->src_op_revision
,
1785 svn_path_is_url(pair
->src
),
1789 pair
->dst
= dst_path_in
;
1790 APR_ARRAY_PUSH(copy_pairs
, svn_client__copy_pair_t
*) = pair
;
1793 if (!srcs_are_urls
&& !dst_is_url
)
1795 apr_pool_t
*iterpool
= svn_pool_create(pool
);
1797 for (i
= 0; i
< copy_pairs
->nelts
; i
++ )
1799 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
1800 svn_client__copy_pair_t
*);
1802 svn_pool_clear(iterpool
);
1804 if (svn_path_is_child(pair
->src
, pair
->dst
, iterpool
))
1805 return svn_error_createf
1806 (SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
1807 _("Cannot copy path '%s' into its own child '%s'"),
1808 svn_path_local_style(pair
->src
, pool
),
1809 svn_path_local_style(pair
->dst
, pool
));
1812 svn_pool_destroy(iterpool
);
1817 if (srcs_are_urls
== dst_is_url
)
1819 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
1821 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
1822 svn_client__copy_pair_t
*);
1824 if (strcmp(pair
->src
, pair
->dst
) == 0)
1825 return svn_error_createf
1826 (SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
1827 _("Cannot move path '%s' into itself"),
1828 svn_path_local_style(pair
->src
, pool
));
1833 /* Disallow moves between the working copy and the repository. */
1834 return svn_error_create
1835 (SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
1836 _("Moves between the working copy and the repository are not "
1844 /* If we are doing a wc->* move, but with an operational revision
1845 other than the working copy revision, we are really doing a
1846 repo->* move, because we're going to need to get the rev from the
1849 svn_boolean_t need_repos_op_rev
= FALSE
;
1850 svn_boolean_t need_repos_peg_rev
= FALSE
;
1852 /* Check to see if any revision is something other than
1853 svn_opt_revision_unspecified or svn_opt_revision_working. */
1854 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
1856 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
1857 svn_client__copy_pair_t
*);
1859 if (NEED_REPOS_REVNUM(pair
->src_op_revision
))
1860 need_repos_op_rev
= TRUE
;
1862 if (NEED_REPOS_REVNUM(pair
->src_peg_revision
))
1863 need_repos_peg_rev
= TRUE
;
1865 if (need_repos_op_rev
|| need_repos_peg_rev
)
1869 if (need_repos_op_rev
|| need_repos_peg_rev
)
1871 apr_pool_t
*iterpool
= svn_pool_create(pool
);
1873 for (i
= 0; i
< copy_pairs
->nelts
; i
++)
1876 svn_client__copy_pair_t
*pair
= APR_ARRAY_IDX(copy_pairs
, i
,
1877 svn_client__copy_pair_t
*);
1879 /* We can convert the working copy path to a URL based on the
1881 svn_wc_adm_access_t
*adm_access
; /* ### FIXME local */
1882 const svn_wc_entry_t
*entry
;
1884 svn_pool_clear(iterpool
);
1886 SVN_ERR(svn_wc_adm_probe_open3(&adm_access
, NULL
,
1887 pair
->src
, FALSE
, 0,
1891 SVN_ERR(svn_wc__entry_versioned(&entry
, pair
->src
, adm_access
,
1893 SVN_ERR(svn_wc_adm_close(adm_access
));
1895 url
= (entry
->copied
? entry
->copyfrom_url
: entry
->url
);
1897 return svn_error_createf
1898 (SVN_ERR_ENTRY_MISSING_URL
, NULL
,
1899 _("'%s' does not have a URL associated with it"),
1900 svn_path_local_style(pair
->src
, pool
));
1902 pair
->src
= apr_pstrdup(pool
, url
);
1904 if (!need_repos_peg_rev
1905 || pair
->src_peg_revision
.kind
== svn_opt_revision_base
)
1907 /* Default the peg revision to that of the WC entry. */
1908 pair
->src_peg_revision
.kind
= svn_opt_revision_number
;
1909 pair
->src_peg_revision
.value
.number
=
1910 (entry
->copied
? entry
->copyfrom_rev
: entry
->revision
);
1913 if (pair
->src_op_revision
.kind
== svn_opt_revision_base
)
1915 /* Use the entry's revision as the operational rev. */
1916 pair
->src_op_revision
.kind
= svn_opt_revision_number
;
1917 pair
->src_op_revision
.value
.number
=
1918 (entry
->copied
? entry
->copyfrom_rev
: entry
->revision
);
1922 svn_pool_destroy(iterpool
);
1923 srcs_are_urls
= TRUE
;
1928 /* Now, call the right handler for the operation. */
1929 if ((! srcs_are_urls
) && (! dst_is_url
))
1931 *commit_info_p
= NULL
;
1932 SVN_ERR(wc_to_wc_copy(copy_pairs
, is_move
, make_parents
,
1935 else if ((! srcs_are_urls
) && (dst_is_url
))
1937 SVN_ERR(wc_to_repos_copy(commit_info_p
, copy_pairs
, make_parents
,
1940 else if ((srcs_are_urls
) && (! dst_is_url
))
1942 *commit_info_p
= NULL
;
1943 SVN_ERR(repos_to_wc_copy(copy_pairs
, make_parents
, ctx
, pool
));
1947 SVN_ERR(repos_to_repos_copy(commit_info_p
, copy_pairs
, make_parents
,
1948 ctx
, is_move
, pool
));
1951 return SVN_NO_ERROR
;
1956 /* Public Interfaces */
1958 svn_client_copy4(svn_commit_info_t
**commit_info_p
,
1959 apr_array_header_t
*sources
,
1960 const char *dst_path
,
1961 svn_boolean_t copy_as_child
,
1962 svn_boolean_t make_parents
,
1963 svn_client_ctx_t
*ctx
,
1967 svn_commit_info_t
*commit_info
= NULL
;
1968 apr_pool_t
*subpool
= svn_pool_create(pool
);
1970 if (sources
->nelts
> 1 && !copy_as_child
)
1971 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED
,
1974 err
= setup_copy(&commit_info
,
1976 FALSE
/* is_move */,
1977 TRUE
/* force, set to avoid deletion check */,
1982 /* If the destination exists, try to copy the sources as children of the
1984 if (copy_as_child
&& err
&& (sources
->nelts
== 1)
1985 && (err
->apr_err
== SVN_ERR_ENTRY_EXISTS
1986 || err
->apr_err
== SVN_ERR_FS_ALREADY_EXISTS
))
1988 const char *src_path
= APR_ARRAY_IDX(sources
, 0,
1989 svn_client_copy_source_t
*)->path
;
1990 const char *src_basename
;
1992 svn_error_clear(err
);
1993 svn_pool_clear(subpool
);
1995 src_basename
= svn_path_basename(src_path
, subpool
);
1996 if (svn_path_is_url(src_path
) && ! svn_path_is_url(dst_path
))
1997 src_basename
= svn_path_uri_decode(src_basename
, pool
);
1999 err
= setup_copy(&commit_info
,
2001 svn_path_join(dst_path
, src_basename
, pool
),
2002 FALSE
/* is_move */,
2003 TRUE
/* force, set to avoid deletion check */,
2009 if (commit_info_p
!= NULL
)
2012 *commit_info_p
= svn_commit_info_dup(commit_info
, pool
);
2014 *commit_info_p
= commit_info
;
2017 svn_pool_destroy(subpool
);
2023 svn_client_copy3(svn_commit_info_t
**commit_info_p
,
2024 const char *src_path
,
2025 const svn_opt_revision_t
*src_revision
,
2026 const char *dst_path
,
2027 svn_client_ctx_t
*ctx
,
2030 apr_array_header_t
*sources
= apr_array_make(pool
, 1,
2031 sizeof(const svn_client_copy_source_t
*));
2032 svn_client_copy_source_t copy_source
;
2034 copy_source
.path
= src_path
;
2035 copy_source
.revision
= src_revision
;
2036 copy_source
.peg_revision
= src_revision
;
2038 APR_ARRAY_PUSH(sources
, const svn_client_copy_source_t
*) = ©_source
;
2040 return svn_client_copy4(commit_info_p
,
2050 svn_client_copy2(svn_commit_info_t
**commit_info_p
,
2051 const char *src_path
,
2052 const svn_opt_revision_t
*src_revision
,
2053 const char *dst_path
,
2054 svn_client_ctx_t
*ctx
,
2059 err
= svn_client_copy3(commit_info_p
, src_path
, src_revision
,
2060 dst_path
, ctx
, pool
);
2062 /* If the target exists, try to copy the source as a child of the target.
2063 This will obviously fail if target is not a directory, but that's exactly
2065 if (err
&& (err
->apr_err
== SVN_ERR_ENTRY_EXISTS
2066 || err
->apr_err
== SVN_ERR_FS_ALREADY_EXISTS
))
2068 const char *src_basename
= svn_path_basename(src_path
, pool
);
2070 svn_error_clear(err
);
2072 return svn_client_copy3(commit_info_p
, src_path
, src_revision
,
2073 svn_path_join(dst_path
, src_basename
, pool
),
2081 svn_client_copy(svn_client_commit_info_t
**commit_info_p
,
2082 const char *src_path
,
2083 const svn_opt_revision_t
*src_revision
,
2084 const char *dst_path
,
2085 svn_client_ctx_t
*ctx
,
2088 svn_commit_info_t
*commit_info
= NULL
;
2091 err
= svn_client_copy2(&commit_info
, src_path
, src_revision
, dst_path
,
2093 /* These structs have the same layout for the common fields. */
2094 *commit_info_p
= (svn_client_commit_info_t
*) commit_info
;
2100 svn_client_move5(svn_commit_info_t
**commit_info_p
,
2101 apr_array_header_t
*src_paths
,
2102 const char *dst_path
,
2103 svn_boolean_t force
,
2104 svn_boolean_t move_as_child
,
2105 svn_boolean_t make_parents
,
2106 svn_client_ctx_t
*ctx
,
2109 svn_commit_info_t
*commit_info
= NULL
;
2110 const svn_opt_revision_t head_revision
2111 = { svn_opt_revision_head
, { 0 } };
2114 apr_pool_t
*subpool
= svn_pool_create(pool
);
2115 apr_array_header_t
*sources
= apr_array_make(pool
, src_paths
->nelts
,
2116 sizeof(const svn_client_copy_source_t
*));
2118 if (src_paths
->nelts
> 1 && !move_as_child
)
2119 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED
,
2122 for (i
= 0; i
< src_paths
->nelts
; i
++)
2124 const char *src_path
= APR_ARRAY_IDX(src_paths
, i
, const char *);
2125 svn_client_copy_source_t
*copy_source
= apr_palloc(pool
,
2126 sizeof(*copy_source
));
2128 copy_source
->path
= src_path
;
2129 copy_source
->revision
= &head_revision
;
2130 copy_source
->peg_revision
= &head_revision
;
2132 APR_ARRAY_PUSH(sources
, svn_client_copy_source_t
*) = copy_source
;
2135 err
= setup_copy(&commit_info
, sources
, dst_path
,
2142 /* If the destination exists, try to move the sources as children of the
2144 if (move_as_child
&& err
&& (src_paths
->nelts
== 1)
2145 && (err
->apr_err
== SVN_ERR_ENTRY_EXISTS
2146 || err
->apr_err
== SVN_ERR_FS_ALREADY_EXISTS
))
2148 const char *src_path
= APR_ARRAY_IDX(src_paths
, 0, const char *);
2149 const char *src_basename
;
2151 svn_error_clear(err
);
2152 svn_pool_clear(subpool
);
2154 src_basename
= svn_path_basename(src_path
, pool
);
2156 err
= setup_copy(&commit_info
, sources
,
2157 svn_path_join(dst_path
, src_basename
, pool
),
2165 if (commit_info_p
!= NULL
)
2168 *commit_info_p
= svn_commit_info_dup(commit_info
, pool
);
2170 *commit_info_p
= commit_info
;
2173 svn_pool_destroy(subpool
);
2178 svn_client_move4(svn_commit_info_t
**commit_info_p
,
2179 const char *src_path
,
2180 const char *dst_path
,
2181 svn_boolean_t force
,
2182 svn_client_ctx_t
*ctx
,
2185 apr_array_header_t
*src_paths
=
2186 apr_array_make(pool
, 1, sizeof(const char *));
2187 APR_ARRAY_PUSH(src_paths
, const char *) = src_path
;
2189 return svn_client_move5(commit_info_p
,
2190 src_paths
, dst_path
, force
, FALSE
,
2195 svn_client_move3(svn_commit_info_t
**commit_info_p
,
2196 const char *src_path
,
2197 const char *dst_path
,
2198 svn_boolean_t force
,
2199 svn_client_ctx_t
*ctx
,
2204 err
= svn_client_move4(commit_info_p
, src_path
, dst_path
, force
, ctx
, pool
);
2206 /* If the target exists, try to move the source as a child of the target.
2207 This will obviously fail if target is not a directory, but that's exactly
2209 if (err
&& (err
->apr_err
== SVN_ERR_ENTRY_EXISTS
2210 || err
->apr_err
== SVN_ERR_FS_ALREADY_EXISTS
))
2212 const char *src_basename
= svn_path_basename(src_path
, pool
);
2214 svn_error_clear(err
);
2216 return svn_client_move4(commit_info_p
, src_path
,
2217 svn_path_join(dst_path
, src_basename
, pool
),
2225 svn_client_move2(svn_client_commit_info_t
**commit_info_p
,
2226 const char *src_path
,
2227 const char *dst_path
,
2228 svn_boolean_t force
,
2229 svn_client_ctx_t
*ctx
,
2232 svn_commit_info_t
*commit_info
= NULL
;
2235 err
= svn_client_move3(&commit_info
, src_path
, dst_path
, force
, ctx
, pool
);
2236 /* These structs have the same layout for the common fields. */
2237 *commit_info_p
= (svn_client_commit_info_t
*) commit_info
;
2243 svn_client_move(svn_client_commit_info_t
**commit_info_p
,
2244 const char *src_path
,
2245 const svn_opt_revision_t
*src_revision
,
2246 const char *dst_path
,
2247 svn_boolean_t force
,
2248 svn_client_ctx_t
*ctx
,
2251 svn_commit_info_t
*commit_info
= NULL
;
2253 svn_client_copy_source_t copy_source
;
2254 apr_array_header_t
*sources
= apr_array_make(pool
, 1,
2255 sizeof(const svn_client_copy_source_t
*));
2257 /* It doesn't make sense to specify revisions in a move. */
2259 /* ### todo: this check could fail wrongly. For example,
2260 someone could pass in an svn_opt_revision_number that just
2261 happens to be the HEAD. It's fair enough to punt then, IMHO,
2262 and just demand that the user not specify a revision at all;
2263 beats mucking up this function with RA calls and such. */
2264 if (src_revision
->kind
!= svn_opt_revision_unspecified
2265 && src_revision
->kind
!= svn_opt_revision_head
)
2267 return svn_error_create
2268 (SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
2269 _("Cannot specify revisions (except HEAD) with move operations"));
2272 copy_source
.path
= src_path
;
2273 copy_source
.revision
= src_revision
;
2274 copy_source
.peg_revision
= src_revision
;
2276 APR_ARRAY_PUSH(sources
, const svn_client_copy_source_t
*) = ©_source
;
2278 err
= setup_copy(&commit_info
,
2282 FALSE
/* make_parents */,
2285 /* These structs have the same layout for the common fields. */
2286 *commit_info_p
= (svn_client_commit_info_t
*) commit_info
;