* subversion/mod_dav_svn/reports/replay.c
[svn.git] / subversion / libsvn_client / copy.c
blob9ed59650b51565ca1778a54a0a9ce0993b5c65fb
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. */
71 static svn_error_t *
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,
79 apr_pool_t *pool)
81 const svn_wc_entry_t *entry = NULL;
82 svn_boolean_t locally_added = FALSE;
83 const char *src_url;
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
88 bother checking. */
89 if (adm_access)
91 SVN_ERR(svn_wc__entry_versioned(&entry, src_path_or_url, adm_access,
92 FALSE, pool));
93 if (entry->schedule == svn_wc_schedule_add && (! entry->copied))
95 locally_added = TRUE;
97 else
99 SVN_ERR(svn_client__entry_location(&src_url, &src_revnum,
100 src_path_or_url,
101 svn_opt_revision_working, entry,
102 pool));
105 else
107 src_url = src_path_or_url;
110 if (! locally_added)
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,
119 FALSE, ra_session,
120 adm_access, pool));
121 SVN_ERR(svn_client__get_repos_mergeinfo(ra_session, &src_mergeinfo,
122 mergeinfo_path, src_revnum,
123 svn_mergeinfo_inherited, TRUE,
124 pool));
126 else
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;
137 return SVN_NO_ERROR;
140 /* Extend the mergeinfo for the single WC path TARGET_WCPATH, adding
141 MERGEINFO to any mergeinfo pre-existing in the WC. */
142 static svn_error_t *
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
150 updating it. */
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,
161 adm_access, pool);
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. */
168 static svn_error_t *
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,
202 ctx, pool));
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. */
208 if (! mergeinfo)
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,
215 pool));
217 return extend_wc_mergeinfo(pair->dst, entry, mergeinfo, dst_access,
218 ctx, pool);
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,
230 pool);
232 else
233 return SVN_NO_ERROR;
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.
241 static svn_error_t *
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,
246 apr_pool_t *pool)
248 apr_pool_t *subpool = svn_pool_create(pool);
249 const char *top_dst;
250 char *top_src;
251 int i;
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);
261 else
262 top_dst = svn_path_dirname(APR_ARRAY_IDX(copy_pairs, 0,
263 svn_client__copy_pair_t *)->dst,
264 subpool);
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
273 space. */
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);
282 if (src_ancestor)
283 *src_ancestor = apr_pstrdup(pool, top_src);
285 if (dst_ancestor)
286 *dst_ancestor = apr_pstrdup(pool, top_dst);
288 if (common_ancestor)
289 *common_ancestor = svn_path_get_longest_ancestor(top_src, top_dst, pool);
291 svn_pool_destroy(subpool);
293 return SVN_NO_ERROR;
297 /* Copy each COPY_PAIR->SRC into COPY_PAIR->DST. Use POOL for temporary
298 allocations. */
299 static svn_error_t *
300 do_wc_to_wc_copies(const apr_array_header_t *copy_pairs,
301 svn_client_ctx_t *ctx,
302 apr_pool_t *pool)
304 int i;
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,
344 iterpool));
345 else
346 src_access = dst_access;
348 else
350 SVN_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,
353 iterpool));
356 /* Perform the copy */
358 /* ### This is not a move, so we won't have locked the source, so we
359 ### won't detect any outstanding locks. If the source is locked and
360 ### requires cleanup should we abort the copy? */
362 err = svn_wc_copy2(pair->src, dst_access, pair->base_name,
363 ctx->cancel_func, ctx->cancel_baton,
364 ctx->notify_func2, ctx->notify_baton2, iterpool);
365 if (err)
366 break;
368 err = propagate_mergeinfo_within_wc(pair, src_access, dst_access,
369 ctx, pool);
370 if (err)
371 break;
373 if (src_access != dst_access)
374 SVN_ERR(svn_wc_adm_close(src_access));
377 svn_sleep_for_timestamps();
378 SVN_ERR(err);
380 SVN_ERR(svn_wc_adm_close(dst_access));
381 svn_pool_destroy(iterpool);
383 return SVN_NO_ERROR;
387 /* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC
388 afterwards. Use POOL for temporary allocations. */
389 static svn_error_t *
390 do_wc_to_wc_moves(const apr_array_header_t *copy_pairs,
391 svn_client_ctx_t *ctx,
392 apr_pool_t *pool)
394 int i;
395 apr_pool_t *iterpool = svn_pool_create(pool);
396 svn_error_t *err = SVN_NO_ERROR;
398 for (i = 0; i < copy_pairs->nelts; i++)
400 svn_wc_adm_access_t *src_access, *dst_access;
401 const char *src_parent;
402 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
403 svn_client__copy_pair_t *);
404 svn_pool_clear(iterpool);
406 /* Check for cancellation */
407 if (ctx->cancel_func)
408 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
410 svn_path_split(pair->src, &src_parent, NULL, iterpool);
412 SVN_ERR(svn_wc_adm_open3(&src_access, NULL, src_parent, TRUE,
413 pair->src_kind == svn_node_dir ? -1 : 0,
414 ctx->cancel_func, ctx->cancel_baton,
415 iterpool));
417 /* Need to avoid attempting to open the same dir twice when source
418 and destination overlap. */
419 if (strcmp(src_parent, pair->dst_parent) == 0)
421 dst_access = src_access;
423 else
425 const char *src_parent_abs, *dst_parent_abs;
427 SVN_ERR(svn_path_get_absolute(&src_parent_abs, src_parent,
428 iterpool));
429 SVN_ERR(svn_path_get_absolute(&dst_parent_abs, pair->dst_parent,
430 iterpool));
432 if ((pair->src_kind == svn_node_dir)
433 && (svn_path_is_child(src_parent_abs, dst_parent_abs,
434 iterpool)))
436 SVN_ERR(svn_wc_adm_retrieve(&dst_access, src_access,
437 pair->dst_parent, iterpool));
439 else
441 SVN_ERR(svn_wc_adm_open3(&dst_access, NULL, pair->dst_parent,
442 TRUE, 0, ctx->cancel_func,
443 ctx->cancel_baton,
444 iterpool));
448 /* ### Ideally, we'd lookup the mergeinfo here, before
449 ### performing the copy. However, as an implementation
450 ### shortcut, we perform the lookup after the copy. */
452 /* Perform the copy with mergeinfo, and then the delete. */
453 err = svn_wc_copy2(pair->src, dst_access, pair->base_name,
454 ctx->cancel_func, ctx->cancel_baton,
455 ctx->notify_func2, ctx->notify_baton2, iterpool);
456 if (err)
457 break;
459 err = propagate_mergeinfo_within_wc(pair, src_access, dst_access,
460 ctx, pool);
461 if (err)
462 break;
464 /* Perform the delete. */
465 SVN_ERR(svn_wc_delete3(pair->src, src_access,
466 ctx->cancel_func, ctx->cancel_baton,
467 ctx->notify_func2, ctx->notify_baton2, FALSE,
468 iterpool));
470 if (dst_access != src_access)
471 SVN_ERR(svn_wc_adm_close(dst_access));
472 SVN_ERR(svn_wc_adm_close(src_access));
475 svn_sleep_for_timestamps();
476 SVN_ERR(err);
478 svn_pool_destroy(iterpool);
480 return SVN_NO_ERROR;
484 static svn_error_t *
485 wc_to_wc_copy(const apr_array_header_t *copy_pairs,
486 svn_boolean_t is_move,
487 svn_boolean_t make_parents,
488 svn_client_ctx_t *ctx,
489 apr_pool_t *pool)
491 int i;
492 apr_pool_t *iterpool = svn_pool_create(pool);
494 /* Check that all of our SRCs exist, and all the DSTs don't. */
495 for (i = 0; i < copy_pairs->nelts; i++)
497 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
498 svn_client__copy_pair_t *);
499 svn_node_kind_t dst_kind, dst_parent_kind;
501 svn_pool_clear(iterpool);
503 /* Verify that SRC_PATH exists. */
504 SVN_ERR(svn_io_check_path(pair->src, &pair->src_kind, iterpool));
505 if (pair->src_kind == svn_node_none)
506 return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
507 _("Path '%s' does not exist"),
508 svn_path_local_style(pair->src, pool));
510 /* If DST_PATH does not exist, then its basename will become a new
511 file or dir added to its parent (possibly an implicit '.').
512 Else, just error out. */
513 SVN_ERR(svn_io_check_path(pair->dst, &dst_kind, iterpool));
514 if (dst_kind != svn_node_none)
515 return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
516 _("Path '%s' already exists"),
517 svn_path_local_style(pair->dst, pool));
519 svn_path_split(pair->dst, &pair->dst_parent, &pair->base_name, pool);
521 /* Make sure the destination parent is a directory and produce a clear
522 error message if it is not. */
523 SVN_ERR(svn_io_check_path(pair->dst_parent, &dst_parent_kind, iterpool));
524 if (make_parents && dst_parent_kind == svn_node_none)
526 SVN_ERR(svn_client__make_local_parents(pair->dst_parent, TRUE, ctx,
527 iterpool));
529 else if (dst_parent_kind != svn_node_dir)
531 return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL,
532 _("Path '%s' is not a directory"),
533 svn_path_local_style(pair->dst_parent,
534 pool));
538 svn_pool_destroy(iterpool);
540 /* Copy or move all targets. */
541 if (is_move)
542 return do_wc_to_wc_moves(copy_pairs, ctx, pool);
543 else
544 return do_wc_to_wc_copies(copy_pairs, ctx, pool);
548 /* Path-specific state used as part of path_driver_cb_baton. */
549 typedef struct
551 const char *src_url;
552 const char *src_path;
553 const char *dst_path;
554 svn_node_kind_t src_kind;
555 svn_revnum_t src_revnum;
556 svn_boolean_t resurrection;
557 svn_boolean_t dir_add;
558 svn_string_t *mergeinfo; /* the new mergeinfo for the target */
559 } path_driver_info_t;
562 /* The baton used with the path_driver_cb_func() callback for a copy
563 or move operation. */
564 struct path_driver_cb_baton
566 /* The editor (and its state) used to perform the operation. */
567 const svn_delta_editor_t *editor;
568 void *edit_baton;
570 /* A hash of path -> path_driver_info_t *'s. */
571 apr_hash_t *action_hash;
573 /* Whether the operation is a move or copy. */
574 svn_boolean_t is_move;
577 static svn_error_t *
578 path_driver_cb_func(void **dir_baton,
579 void *parent_baton,
580 void *callback_baton,
581 const char *path,
582 apr_pool_t *pool)
584 struct path_driver_cb_baton *cb_baton = callback_baton;
585 svn_boolean_t do_delete = FALSE, do_add = FALSE;
586 path_driver_info_t *path_info = apr_hash_get(cb_baton->action_hash,
587 path,
588 APR_HASH_KEY_STRING);
590 /* Initialize return value. */
591 *dir_baton = NULL;
593 /* This function should never get an empty PATH. We can neither
594 create nor delete the empty PATH, so if someone is calling us
595 with such, the code is just plain wrong. */
596 assert(! svn_path_is_empty(path));
598 /* Check to see if we need to add the path as a directory. */
599 if (path_info->dir_add)
601 SVN_ERR(cb_baton->editor->add_directory(path, parent_baton, NULL,
602 SVN_INVALID_REVNUM, pool,
603 dir_baton));
604 return SVN_NO_ERROR;
607 /* If this is a resurrection, we know the source and dest paths are
608 the same, and that our driver will only be calling us once. */
609 if (path_info->resurrection)
611 /* If this is a move, we do nothing. Otherwise, we do the copy. */
612 if (! cb_baton->is_move)
613 do_add = TRUE;
615 /* Not a resurrection. */
616 else
618 /* If this is a move, we check PATH to see if it is the source
619 or the destination of the move. */
620 if (cb_baton->is_move)
622 if (strcmp(path_info->src_path, path) == 0)
623 do_delete = TRUE;
624 else
625 do_add = TRUE;
627 /* Not a move? This must just be the copy addition. */
628 else
630 do_add = TRUE;
634 if (do_delete)
636 SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM,
637 parent_baton, pool));
639 if (do_add)
641 SVN_ERR(svn_path_check_valid(path, pool));
643 if (path_info->src_kind == svn_node_file)
645 void *file_baton;
646 SVN_ERR(cb_baton->editor->add_file(path, parent_baton,
647 path_info->src_url,
648 path_info->src_revnum,
649 pool, &file_baton));
650 if (path_info->mergeinfo)
651 SVN_ERR(cb_baton->editor->change_file_prop(file_baton,
652 SVN_PROP_MERGEINFO,
653 path_info->mergeinfo,
654 pool));
655 SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool));
657 else
659 SVN_ERR(cb_baton->editor->add_directory(path, parent_baton,
660 path_info->src_url,
661 path_info->src_revnum,
662 pool, dir_baton));
663 if (path_info->mergeinfo)
664 SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton,
665 SVN_PROP_MERGEINFO,
666 path_info->mergeinfo,
667 pool));
670 return SVN_NO_ERROR;
674 static svn_error_t *
675 repos_to_repos_copy(svn_commit_info_t **commit_info_p,
676 const apr_array_header_t *copy_pairs,
677 svn_boolean_t make_parents,
678 svn_client_ctx_t *ctx,
679 svn_boolean_t is_move,
680 apr_pool_t *pool)
682 apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
683 sizeof(const char *));
684 apr_hash_t *action_hash = apr_hash_make(pool);
685 apr_array_header_t *path_infos;
686 const char *top_url, *message, *repos_root;
687 apr_hash_t *revprop_table;
688 svn_revnum_t youngest;
689 svn_ra_session_t *ra_session;
690 const svn_delta_editor_t *editor;
691 void *edit_baton;
692 void *commit_baton;
693 struct path_driver_cb_baton cb_baton;
694 apr_array_header_t *new_dirs = NULL;
695 apr_pool_t *iterpool;
696 int i;
697 svn_error_t *err;
699 /* Create a path_info struct for each src/dst pair, and initialize it. */
700 path_infos = apr_array_make(pool, copy_pairs->nelts,
701 sizeof(path_driver_info_t *));
702 for (i = 0; i < copy_pairs->nelts; i++)
704 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
705 info->resurrection = FALSE;
706 APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
709 /* We have to open our session to the longest path common to all
710 SRC_URLS and DST_URLS in the repository so we can do existence
711 checks on all paths, and so we can operate on all paths in the
712 case of a move. */
713 get_copy_pair_ancestors(copy_pairs, NULL, NULL, &top_url, pool);
715 /* Check each src/dst pair for resurrection. */
716 for (i = 0; i < copy_pairs->nelts; i++)
718 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
719 svn_client__copy_pair_t *);
720 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
721 path_driver_info_t *);
723 if (strcmp(pair->src, pair->dst) == 0)
725 info->resurrection = TRUE;
727 /* Special edge-case! (issue #683) If you're resurrecting a
728 deleted item like this: 'svn cp -rN src_URL dst_URL', then
729 it's possible for src_URL == dst_URL == top_url. In this
730 situation, we want to open an RA session to be at least the
731 *parent* of all three. */
732 if (strcmp(pair->src, top_url) == 0)
734 top_url = svn_path_dirname(top_url, pool);
739 /* Open an RA session for the URL. Note that we don't have a local
740 directory, nor a place to put temp files. */
741 err = svn_client__open_ra_session_internal(&ra_session, top_url,
742 NULL, NULL, NULL, FALSE, TRUE,
743 ctx, pool);
745 /* If the two URLs appear not to be in the same repository, then
746 top_url will be empty and the call to svn_ra_open2()
747 above will have failed. Below we check for that, and propagate a
748 descriptive error back to the user.
750 Ideally, we'd contact the repositories and compare their UUIDs to
751 determine whether or not src and dst are in the same repository,
752 instead of depending on an essentially textual comparison.
753 However, it is simpler to assume that if someone is using the
754 same repository, then they will use the same hostname/path to
755 refer to it both times. Conversely, if the repositories are
756 different, then they can't share a non-empty prefix, so top_url
757 would still be "" and svn_ra_get_library() would still error.
758 Thus we can get this check without extra network turnarounds to
759 fetch the UUIDs.
761 if (err)
763 if ((err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
764 && ((top_url == NULL) || (top_url[0] == '\0')))
766 svn_client__copy_pair_t *first_pair =
767 APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
768 svn_error_clear(err);
770 return svn_error_createf
771 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
772 _("Source and dest appear not to be in the same repository "
773 "(src: '%s'; dst: '%s')"),
774 first_pair->src, first_pair->dst);
776 else
777 return err;
780 iterpool = svn_pool_create(pool);
782 /* Iterate over the parents of the destination directory, and make a list
783 of the ones that don't yet exist. We do not have to worry about
784 reparenting the ra session because top_url is a common ancestor of the
785 destination and sources. The sources exist, so therefore top_url must
786 also exist. */
787 if (make_parents)
789 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, 0,
790 svn_client__copy_pair_t *);
791 svn_node_kind_t kind;
792 const char *dir;
794 new_dirs = apr_array_make(pool, 0, sizeof(const char *));
795 dir = svn_path_is_child(top_url, svn_path_dirname(pair->dst, pool),
796 pool);
797 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
798 iterpool));
800 while (kind == svn_node_none)
802 svn_pool_clear(iterpool);
803 APR_ARRAY_PUSH(new_dirs, const char *) = dir;
805 svn_path_split(dir, &dir, NULL, pool);
806 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
807 iterpool));
811 svn_pool_destroy(iterpool);
813 SVN_ERR(svn_ra_get_repos_root(ra_session, &repos_root, pool));
815 /* For each src/dst pair, check to see if that SRC_URL is a child of
816 the DST_URL (excepting the case where DST_URL is the repo root).
817 If it is, and the parent of DST_URL is the current TOP_URL, then we
818 need to reparent the session one directory higher, the parent of
819 the DST_URL. */
820 for (i = 0; i < copy_pairs->nelts; i++)
822 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
823 svn_client__copy_pair_t *);
824 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
825 path_driver_info_t *);
827 if (strcmp(pair->dst, repos_root) != 0
828 && svn_path_is_child(pair->dst, pair->src, pool) != NULL)
830 info->resurrection = TRUE;
831 top_url = svn_path_dirname(top_url, pool);
833 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
837 /* Fetch the youngest revision. */
838 SVN_ERR(svn_ra_get_latest_revnum(ra_session, &youngest, pool));
840 for (i = 0; i < copy_pairs->nelts; i++)
842 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
843 svn_client__copy_pair_t *);
844 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
845 path_driver_info_t *);
846 svn_node_kind_t dst_kind;
847 const char *src_rel, *dst_rel;
848 svn_opt_revision_t *new_rev, *ignored_rev, dead_end_rev;
849 const char *ignored_url;
851 /* Pass NULL for the path, to ensure error if trying to get a
852 revision based on the working copy. */
853 SVN_ERR(svn_client__get_revision_number
854 (&pair->src_revnum, NULL, ra_session, &pair->src_op_revision,
855 NULL, pool));
857 info->src_revnum = pair->src_revnum;
859 dead_end_rev.kind = svn_opt_revision_unspecified;
861 /* Run the history function to get the object's url in the operational
862 revision. */
863 SVN_ERR(svn_client__repos_locations(&pair->src, &new_rev,
864 &ignored_url, &ignored_rev,
865 NULL,
866 pair->src, &pair->src_peg_revision,
867 &pair->src_op_revision, &dead_end_rev,
868 ctx, pool));
870 /* Get the portions of the SRC and DST URLs that are relative to
871 TOP_URL, and URI-decode those sections. */
872 src_rel = svn_path_is_child(top_url, pair->src, pool);
873 if (src_rel)
874 src_rel = svn_path_uri_decode(src_rel, pool);
875 else
876 src_rel = "";
878 dst_rel = svn_path_is_child(top_url, pair->dst, pool);
879 if (dst_rel)
880 dst_rel = svn_path_uri_decode(dst_rel, pool);
881 else
882 dst_rel = "";
884 /* We can't move something into itself, period. */
885 if (svn_path_is_empty(src_rel) && is_move)
886 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
887 _("Cannot move URL '%s' into itself"),
888 pair->src);
890 /* Verify that SRC_URL exists in the repository. */
891 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
892 &info->src_kind, pool));
893 if (info->src_kind == svn_node_none)
894 return svn_error_createf
895 (SVN_ERR_FS_NOT_FOUND, NULL,
896 _("Path '%s' does not exist in revision %ld"),
897 pair->src, pair->src_revnum);
899 /* Figure out the basename that will result from this operation. */
900 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, youngest, &dst_kind,
901 pool));
902 if (dst_kind != svn_node_none)
904 /* We disallow the overwriting of existing paths. */
905 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
906 _("Path '%s' already exists"), dst_rel);
909 info->src_url = pair->src;
910 info->src_path = src_rel;
911 info->dst_path = dst_rel;
914 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
916 /* Produce a list of new paths to add, and provide it to the
917 mechanism used to acquire a log message. */
918 svn_client_commit_item3_t *item;
919 const char *tmp_file;
920 apr_array_header_t *commit_items
921 = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item));
923 /* Add any intermediate directories to the message */
924 if (make_parents)
926 for (i = 0; i < new_dirs->nelts; i++)
928 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
930 SVN_ERR(svn_client_commit_item_create
931 ((const svn_client_commit_item3_t **) &item, pool));
933 item->url = svn_path_join(top_url, url, pool);
934 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
935 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
939 for (i = 0; i < path_infos->nelts; i++)
941 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
942 path_driver_info_t *);
944 SVN_ERR(svn_client_commit_item_create
945 ((const svn_client_commit_item3_t **) &item, pool));
947 item->url = svn_path_join(top_url, info->dst_path, pool);
948 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
949 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
950 apr_hash_set(action_hash, info->dst_path, APR_HASH_KEY_STRING,
951 info);
953 if (is_move && (! info->resurrection))
955 item = apr_pcalloc(pool, sizeof(*item));
956 item->url = svn_path_join(top_url, info->src_path, pool);
957 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
958 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
959 apr_hash_set(action_hash, info->src_path, APR_HASH_KEY_STRING,
960 info);
964 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
965 ctx, pool));
966 if (! message)
967 return SVN_NO_ERROR;
969 else
970 message = "";
972 /* Setup our PATHS for the path-based editor drive. */
973 /* First any intermediate directories. */
974 if (make_parents)
976 for (i = 0; i < new_dirs->nelts; i++)
978 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
979 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
981 info->dst_path = url;
982 info->dir_add = TRUE;
984 APR_ARRAY_PUSH(paths, const char *) = url;
985 apr_hash_set(action_hash, url, APR_HASH_KEY_STRING, info);
989 /* Then, copy destinations, and possibly move sources. */
990 for (i = 0; i < path_infos->nelts; i++)
992 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
993 path_driver_info_t *);
994 apr_hash_t *mergeinfo;
995 SVN_ERR(calculate_target_mergeinfo(ra_session, &mergeinfo, NULL,
996 info->src_url, info->src_revnum,
997 FALSE, ctx, pool));
998 if (mergeinfo)
999 SVN_ERR(svn_mergeinfo__to_string(&info->mergeinfo, mergeinfo, pool));
1001 APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1002 if (is_move && (! info->resurrection))
1003 APR_ARRAY_PUSH(paths, const char *) = info->src_path;
1006 SVN_ERR(svn_client__get_revprop_table(&revprop_table, message, ctx, pool));
1008 /* Fetch RA commit editor. */
1009 SVN_ERR(svn_client__commit_get_baton(&commit_baton, commit_info_p, pool));
1010 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1011 revprop_table,
1012 svn_client__commit_callback,
1013 commit_baton,
1014 NULL, TRUE, /* No lock tokens */
1015 pool));
1017 /* Setup the callback baton. */
1018 cb_baton.editor = editor;
1019 cb_baton.edit_baton = edit_baton;
1020 cb_baton.action_hash = action_hash;
1021 cb_baton.is_move = is_move;
1023 /* Call the path-based editor driver. */
1024 err = svn_delta_path_driver(editor, edit_baton, youngest, paths,
1025 path_driver_cb_func, &cb_baton, pool);
1026 if (err)
1028 /* At least try to abort the edit (and fs txn) before throwing err. */
1029 svn_error_clear(editor->abort_edit(edit_baton, pool));
1030 return err;
1033 /* Close the edit. */
1034 SVN_ERR(editor->close_edit(edit_baton, pool));
1036 return SVN_NO_ERROR;
1041 static svn_error_t *
1042 wc_to_repos_copy(svn_commit_info_t **commit_info_p,
1043 const apr_array_header_t *copy_pairs,
1044 svn_boolean_t make_parents,
1045 svn_client_ctx_t *ctx,
1046 apr_pool_t *pool)
1048 const char *message;
1049 apr_hash_t *revprop_table;
1050 const char *top_src_path, *top_dst_url, *repos_root;
1051 svn_ra_session_t *ra_session;
1052 const svn_delta_editor_t *editor;
1053 void *edit_baton;
1054 svn_node_kind_t base_kind;
1055 void *commit_baton;
1056 apr_hash_t *committables;
1057 svn_wc_adm_access_t *adm_access, *dir_access;
1058 apr_array_header_t *commit_items;
1059 const svn_wc_entry_t *entry;
1060 apr_pool_t *iterpool;
1061 apr_array_header_t *new_dirs = NULL;
1062 int i;
1064 /* The commit process uses absolute paths, so we need to open the access
1065 baton using absolute paths, and so we really need to use absolute
1066 paths everywhere. */
1067 for (i = 0; i < copy_pairs->nelts; i++)
1069 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1070 svn_client__copy_pair_t *);
1071 SVN_ERR(svn_path_get_absolute(&pair->src_abs, pair->src, pool));
1074 /*Find the common root of all the source paths, and probe the wc. */
1075 get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL, pool);
1076 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, top_src_path,
1077 FALSE, -1, ctx->cancel_func,
1078 ctx->cancel_baton, pool));
1080 /* Determine the least common ancesor for the destinations, and open an RA
1081 session to that location. */
1082 svn_path_split(APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *)->dst,
1083 &top_dst_url,
1084 NULL, pool);
1085 for (i = 1; i < copy_pairs->nelts; i++)
1087 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1088 svn_client__copy_pair_t *);
1089 top_dst_url = svn_path_get_longest_ancestor(top_dst_url, pair->dst, pool);
1092 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, top_dst_url,
1093 svn_wc_adm_access_path
1094 (adm_access),
1095 adm_access, NULL, TRUE, TRUE,
1096 ctx, pool));
1098 /* If requested, determine the nearest existing parent of the destination,
1099 and reparent the ra session there. */
1100 if (make_parents)
1102 const char *root_url = top_dst_url;
1103 svn_node_kind_t kind;
1105 new_dirs = apr_array_make(pool, 0, sizeof(const char *));
1106 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1107 pool));
1109 while (kind == svn_node_none)
1111 APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
1112 svn_path_split(root_url, &root_url, NULL, pool);
1114 SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
1115 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1116 pool));
1119 top_dst_url = root_url;
1122 /* Figure out the basename that will result from each copy and check to make
1123 sure it doesn't exist already. */
1124 iterpool = svn_pool_create(pool);
1126 for (i = 0; i < copy_pairs->nelts; i++)
1128 svn_node_kind_t dst_kind;
1129 const char *dst_rel;
1130 svn_client__copy_pair_t *pair =
1131 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1133 svn_pool_clear(iterpool);
1135 SVN_ERR(svn_wc_entry(&entry, pair->src, adm_access, FALSE, iterpool));
1136 pair->src_revnum = entry->revision;
1138 dst_rel = svn_path_uri_decode(svn_path_is_child(top_dst_url,
1139 pair->dst,
1140 iterpool),
1141 iterpool);
1142 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1143 &dst_kind, iterpool));
1144 if (dst_kind != svn_node_none)
1146 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1147 _("Path '%s' already exists"), pair->dst);
1151 svn_pool_destroy(iterpool);
1153 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1155 /* Produce a list of new paths to add, and provide it to the
1156 mechanism used to acquire a log message. */
1157 svn_client_commit_item3_t *item;
1158 const char *tmp_file;
1159 commit_items = apr_array_make(pool, copy_pairs->nelts, sizeof(item));
1161 /* Add any intermediate directories to the message */
1162 if (make_parents)
1164 for (i = 0; i < new_dirs->nelts; i++)
1166 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
1168 SVN_ERR(svn_client_commit_item_create
1169 ((const svn_client_commit_item3_t **) &item, pool));
1171 item->url = url;
1172 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1173 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1177 for (i = 0; i < copy_pairs->nelts; i++ )
1179 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1180 svn_client__copy_pair_t *);
1182 SVN_ERR(svn_client_commit_item_create
1183 ((const svn_client_commit_item3_t **) &item, pool));
1185 item->url = pair->dst;
1186 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1187 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1190 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1191 ctx, pool));
1192 if (! message)
1194 SVN_ERR(svn_wc_adm_close(adm_access));
1195 return SVN_NO_ERROR;
1198 else
1199 message = "";
1201 SVN_ERR(svn_client__get_revprop_table(&revprop_table, message, ctx, pool));
1203 /* Crawl the working copy for commit items. */
1204 SVN_ERR(svn_io_check_path(top_src_path, &base_kind, pool));
1205 if (base_kind == svn_node_dir)
1206 SVN_ERR(svn_wc_adm_retrieve(&dir_access, adm_access, top_src_path, pool));
1207 else
1208 dir_access = adm_access;
1210 SVN_ERR(svn_client__get_copy_committables(&committables,
1211 copy_pairs, dir_access,
1212 ctx, pool));
1214 /* ### todo: There should be only one hash entry, which currently
1215 has a hacked name until we have the entries files storing
1216 canonical repository URLs. Then, the hacked name can go away and
1217 be replaced with a entry->repos (or whereever the entry's
1218 canonical repos URL is stored). */
1219 if (! (commit_items = apr_hash_get(committables,
1220 SVN_CLIENT__SINGLE_REPOS_NAME,
1221 APR_HASH_KEY_STRING)))
1223 SVN_ERR(svn_wc_adm_close(adm_access));
1224 return SVN_NO_ERROR;
1227 /* If we are creating intermediate directories, tack them onto the list
1228 of committables. */
1229 if (make_parents)
1231 for (i = 0; i < new_dirs->nelts; i++)
1233 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
1234 svn_client_commit_item3_t *item;
1236 SVN_ERR(svn_client_commit_item_create
1237 ((const svn_client_commit_item3_t **) &item, pool));
1239 item->url = url;
1240 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1241 item->incoming_prop_changes = apr_array_make(pool, 1,
1242 sizeof(svn_prop_t *));
1243 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1247 /* Reparent the ra_session to repos_root. So that 'svn_ra_get_log'
1248 on paths relative to repos_root would work fine. */
1249 SVN_ERR(svn_ra_get_repos_root(ra_session, &repos_root, pool));
1250 SVN_ERR(svn_ra_reparent(ra_session, repos_root, pool));
1252 /* ### TODO: This extra loop would be unnecessary if this code lived
1253 ### in svn_client__get_copy_committables(), which is incidentally
1254 ### only used above (so should really be in this source file). */
1255 for (i = 0; i < copy_pairs->nelts; i++)
1257 svn_prop_t *mergeinfo_prop;
1258 apr_hash_t *mergeinfo, *wc_mergeinfo;
1259 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1260 svn_client__copy_pair_t *);
1261 svn_client_commit_item3_t *item =
1262 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
1264 /* Set the mergeinfo for the destination to the combined merge
1265 info known to the WC and the repository. */
1266 item->outgoing_prop_changes = apr_array_make(pool, 1,
1267 sizeof(svn_prop_t *));
1268 mergeinfo_prop = apr_palloc(item->outgoing_prop_changes->pool,
1269 sizeof(svn_prop_t));
1270 mergeinfo_prop->name = SVN_PROP_MERGEINFO;
1271 SVN_ERR(calculate_target_mergeinfo(ra_session, &mergeinfo, adm_access,
1272 pair->src, pair->src_revnum,
1273 FALSE, ctx, pool));
1274 SVN_ERR(svn_wc_entry(&entry, pair->src, adm_access, FALSE, pool));
1275 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, entry,
1276 pair->src, FALSE, adm_access, ctx,
1277 pool));
1278 if (wc_mergeinfo && mergeinfo)
1279 SVN_ERR(svn_mergeinfo_merge(mergeinfo, wc_mergeinfo, pool));
1280 else if (! mergeinfo)
1281 mergeinfo = wc_mergeinfo;
1282 if (mergeinfo)
1284 SVN_ERR(svn_mergeinfo__to_string((svn_string_t **)
1285 &mergeinfo_prop->value,
1286 mergeinfo, pool));
1287 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) =
1288 mergeinfo_prop;
1292 /* Sort and condense our COMMIT_ITEMS. */
1293 SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
1294 commit_items, pool));
1296 /* Open an RA session to DST_URL. */
1297 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, top_dst_url,
1298 NULL, NULL, commit_items,
1299 FALSE, FALSE, ctx, pool));
1301 /* Fetch RA commit editor. */
1302 SVN_ERR(svn_client__commit_get_baton(&commit_baton, commit_info_p, pool));
1303 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1304 revprop_table, svn_client__commit_callback,
1305 commit_baton, NULL,
1306 TRUE, /* No lock tokens */
1307 pool));
1309 /* Perform the commit. */
1310 SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items, adm_access,
1311 editor, edit_baton,
1312 0, /* ### any notify_path_offset needed? */
1313 NULL, NULL, ctx, pool),
1314 _("Commit failed (details follow):"));
1316 /* Sleep to ensure timestamp integrity. */
1317 svn_sleep_for_timestamps();
1319 /* It's only a read lock, so unlocking is harmless. */
1320 SVN_ERR(svn_wc_adm_close(adm_access));
1323 return SVN_NO_ERROR;
1326 /* Peform each individual copy operation for a repos -> wc copy. A
1327 helper for repos_to_wc_copy(). */
1328 static svn_error_t *
1329 repos_to_wc_copy_single(svn_client__copy_pair_t *pair,
1330 svn_boolean_t same_repositories,
1331 svn_ra_session_t *ra_session,
1332 svn_wc_adm_access_t *adm_access,
1333 svn_client_ctx_t *ctx,
1334 apr_pool_t *pool)
1336 svn_revnum_t src_revnum = pair->src_revnum;
1337 apr_hash_t *src_mergeinfo;
1338 const svn_wc_entry_t *dst_entry;
1340 if (pair->src_kind == svn_node_dir)
1342 SVN_ERR(svn_client__checkout_internal
1343 (NULL, pair->src_original, pair->dst, &pair->src_peg_revision,
1344 &pair->src_op_revision,
1345 SVN_DEPTH_INFINITY_OR_FILES(TRUE),
1346 FALSE, FALSE, NULL, ctx, pool));
1348 /* Rewrite URLs recursively, remove wcprops, and mark everything
1349 as 'copied' -- assuming that the src and dst are from the
1350 same repository. (It's kind of weird that svn_wc_add() is the
1351 way to do this; see its doc for more about the controversy.) */
1352 if (same_repositories)
1354 svn_wc_adm_access_t *dst_access;
1355 SVN_ERR(svn_wc_adm_open3(&dst_access, adm_access, pair->dst, TRUE,
1356 -1, ctx->cancel_func, ctx->cancel_baton,
1357 pool));
1358 SVN_ERR(svn_wc_entry(&dst_entry, pair->dst, dst_access, FALSE,
1359 pool));
1361 if (pair->src_op_revision.kind == svn_opt_revision_head)
1363 /* If we just checked out from the "head" revision,
1364 that's fine, but we don't want to pass a '-1' as a
1365 copyfrom_rev to svn_wc_add(). That function will
1366 dump it right into the entry, and when we try to
1367 commit later on, the 'add-dir-with-history' step will
1368 be -very- unhappy; it only accepts specific
1369 revisions.
1371 On the other hand, we *could* say that -1 is a
1372 legitimate copyfrom_rev, but I think that's bogus.
1373 Somebody made a copy from a particular revision; if
1374 they wait a long time to commit, it would be terrible
1375 if the copied happened from a newer revision!! */
1377 /* We just did a checkout; whatever revision we just
1378 got, that should be the copyfrom_revision when we
1379 commit later. */
1380 src_revnum = dst_entry->revision;
1383 /* Schedule dst_path for addition in parent, with copy history.
1384 (This function also recursively puts a 'copied' flag on every
1385 entry). */
1386 SVN_ERR(svn_wc_add2(pair->dst, adm_access, pair->src,
1387 src_revnum,
1388 ctx->cancel_func, ctx->cancel_baton,
1389 ctx->notify_func2, ctx->notify_baton2, pool));
1391 /* ### Recording of implied mergeinfo should really occur
1392 ### *before* the notification callback is invoked by
1393 ### svn_wc_add2(), but can't occur before we add the new
1394 ### source path. */
1395 SVN_ERR(calculate_target_mergeinfo(ra_session, &src_mergeinfo, NULL,
1396 pair->src, src_revnum,
1397 FALSE, ctx, pool));
1398 SVN_ERR(extend_wc_mergeinfo(pair->dst, dst_entry, src_mergeinfo,
1399 dst_access, ctx, pool));
1401 else /* different repositories */
1403 /* ### Someday, we would just call svn_wc_add(), as above,
1404 but with no copyfrom args. I.e. in the
1405 directory-foreign-UUID case, we still want everything
1406 scheduled for addition, URLs rewritten, and wcprop cache
1407 deleted, but WITHOUT any copied flags or copyfrom urls.
1408 Unfortunately, svn_wc_add() is such a mess that it chokes
1409 at the moment when we pass a NULL copyfromurl. */
1411 return svn_error_createf
1412 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1413 _("Source URL '%s' is from foreign repository; "
1414 "leaving it as a disjoint WC"), pair->src);
1416 } /* end directory case */
1418 else if (pair->src_kind == svn_node_file)
1420 apr_file_t *fp;
1421 svn_stream_t *fstream;
1422 svn_revnum_t real_rev;
1423 const char *new_text_path;
1424 apr_hash_t *new_props;
1425 svn_error_t *err;
1426 const char *src_rel;
1428 SVN_ERR(svn_io_open_unique_file2(&fp, &new_text_path, pair->dst, ".tmp",
1429 svn_io_file_del_none, pool));
1431 fstream = svn_stream_from_aprfile2(fp, FALSE, pool);
1432 SVN_ERR(svn_client__path_relative_to_session(&src_rel, ra_session,
1433 pair->src, pool));
1434 SVN_ERR(svn_ra_get_file(ra_session, src_rel, src_revnum, fstream,
1435 &real_rev, &new_props, pool));
1436 SVN_ERR(svn_stream_close(fstream));
1438 /* If SRC_REVNUM is invalid (HEAD), then REAL_REV is now the
1439 revision that was actually retrieved. This is the value we
1440 want to use as 'copyfrom_rev' below. */
1441 if (! SVN_IS_VALID_REVNUM(src_revnum))
1442 src_revnum = real_rev;
1444 err = svn_wc_add_repos_file2
1445 (pair->dst, adm_access,
1446 new_text_path, NULL, new_props, NULL,
1447 same_repositories ? pair->src : NULL,
1448 same_repositories ? src_revnum : SVN_INVALID_REVNUM,
1449 pool);
1451 SVN_ERR(svn_wc_entry(&dst_entry, pair->dst, adm_access, FALSE, pool));
1452 SVN_ERR(calculate_target_mergeinfo(ra_session, &src_mergeinfo,
1453 NULL, pair->src, src_revnum,
1454 FALSE, ctx, pool));
1455 SVN_ERR(extend_wc_mergeinfo(pair->dst, dst_entry, src_mergeinfo,
1456 adm_access, ctx, pool));
1458 /* Ideally, svn_wc_add_repos_file() would take a notify function
1459 and baton, and we wouldn't have to make this call here.
1460 However, the situation is... complicated. See issue #1552
1461 for the full story. */
1462 if (!err && ctx->notify_func2)
1464 svn_wc_notify_t *notify = svn_wc_create_notify(pair->dst,
1465 svn_wc_notify_add,
1466 pool);
1467 notify->kind = pair->src_kind;
1468 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
1471 svn_sleep_for_timestamps();
1472 SVN_ERR(err);
1475 return SVN_NO_ERROR;
1478 static svn_error_t *
1479 repos_to_wc_copy(const apr_array_header_t *copy_pairs,
1480 svn_boolean_t make_parents,
1481 svn_client_ctx_t *ctx,
1482 apr_pool_t *pool)
1484 svn_ra_session_t *ra_session;
1485 svn_wc_adm_access_t *adm_access;
1486 const char *top_src_url, *top_dst_path;
1487 const char *src_uuid = NULL, *dst_uuid = NULL;
1488 svn_boolean_t same_repositories;
1489 apr_pool_t *iterpool = svn_pool_create(pool);
1490 int i;
1492 /* Get the real path for the source, based upon its peg revision. */
1493 for (i = 0; i < copy_pairs->nelts; i++)
1495 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1496 svn_client__copy_pair_t *);
1497 const char *src, *ignored_url;
1498 svn_opt_revision_t *new_rev, *ignored_rev, dead_end_rev;
1500 svn_pool_clear(iterpool);
1501 dead_end_rev.kind = svn_opt_revision_unspecified;
1503 SVN_ERR(svn_client__repos_locations(&src, &new_rev,
1504 &ignored_url, &ignored_rev,
1505 NULL,
1506 pair->src,
1507 &pair->src_peg_revision,
1508 &pair->src_op_revision,
1509 &dead_end_rev,
1510 ctx, iterpool));
1512 pair->src_original = pair->src;
1513 pair->src = apr_pstrdup(pool, src);
1516 get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_path, NULL, pool);
1517 if (copy_pairs->nelts == 1)
1518 top_src_url = svn_path_dirname(top_src_url, pool);
1520 /* Open a repository session to the longest common src ancestor. We do not
1521 (yet) have a working copy, so we don't have a corresponding path and
1522 tempfiles cannot go into the admin area. */
1523 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, top_src_url, NULL,
1524 NULL, NULL, FALSE, TRUE,
1525 ctx, pool));
1527 /* Pass null for the path, to ensure error if trying to get a
1528 revision based on the working copy. */
1529 for (i = 0; i < copy_pairs->nelts; i++)
1531 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1532 svn_client__copy_pair_t *);
1534 SVN_ERR(svn_client__get_revision_number
1535 (&pair->src_revnum, NULL, ra_session, &pair->src_op_revision,
1536 NULL, pool));
1539 /* Get the correct src path for the peg revision used, and verify that we
1540 aren't overwriting an existing path. */
1541 for (i = 0; i < copy_pairs->nelts; i++)
1543 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1544 svn_client__copy_pair_t *);
1545 svn_node_kind_t dst_parent_kind, dst_kind;
1546 const char *dst_parent;
1547 const char *src_rel;
1549 svn_pool_clear(iterpool);
1551 /* Next, make sure that the path exists in the repository. */
1552 SVN_ERR(svn_client__path_relative_to_session(&src_rel, ra_session,
1553 pair->src, iterpool));
1554 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1555 &pair->src_kind, pool));
1556 if (pair->src_kind == svn_node_none)
1558 if (SVN_IS_VALID_REVNUM(pair->src_revnum))
1559 return svn_error_createf
1560 (SVN_ERR_FS_NOT_FOUND, NULL,
1561 _("Path '%s' not found in revision %ld"),
1562 pair->src, pair->src_revnum);
1563 else
1564 return svn_error_createf
1565 (SVN_ERR_FS_NOT_FOUND, NULL,
1566 _("Path '%s' not found in head revision"), pair->src);
1569 /* Figure out about dst. */
1570 SVN_ERR(svn_io_check_path(pair->dst, &dst_kind, iterpool));
1571 if (dst_kind != svn_node_none)
1573 return svn_error_createf(SVN_ERR_ENTRY_EXISTS, NULL,
1574 _("Path '%s' already exists"),
1575 svn_path_local_style(pair->dst, pool));
1578 /* Make sure the destination parent is a directory and produce a clear
1579 error message if it is not. */
1580 dst_parent = svn_path_dirname(pair->dst, iterpool);
1581 SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool));
1582 if (make_parents && dst_parent_kind == svn_node_none)
1584 SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx,
1585 iterpool));
1587 else if (dst_parent_kind != svn_node_dir)
1589 return svn_error_createf(SVN_ERR_WC_NOT_DIRECTORY, NULL,
1590 _("Path '%s' is not a directory"),
1591 svn_path_local_style(dst_parent, pool));
1595 /* Probe the wc at the longest common dst ancestor. */
1596 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, top_dst_path, TRUE,
1597 0, ctx->cancel_func, ctx->cancel_baton,
1598 pool));
1600 /* We've already checked for physical obstruction by a working file.
1601 But there could also be logical obstruction by an entry whose
1602 working file happens to be missing.*/
1603 for (i = 0; i < copy_pairs->nelts; i++)
1605 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1606 svn_client__copy_pair_t *);
1607 const svn_wc_entry_t *ent;
1609 svn_pool_clear(iterpool);
1611 SVN_ERR(svn_wc_entry(&ent, pair->dst, adm_access, FALSE, iterpool));
1612 if (ent && (ent->kind != svn_node_dir) &&
1613 (ent->schedule != svn_wc_schedule_delete))
1614 return svn_error_createf
1615 (SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1616 _("Entry for '%s' exists (though the working file is missing)"),
1617 svn_path_local_style(pair->dst, pool));
1620 /* Decide whether the two repositories are the same or not. */
1622 svn_error_t *src_err, *dst_err;
1623 const char *parent;
1625 /* Get the repository uuid of SRC_URL */
1626 src_err = svn_ra_get_uuid(ra_session, &src_uuid, pool);
1627 if (src_err && src_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
1628 return src_err;
1630 /* Get repository uuid of dst's parent directory, since dst may
1631 not exist. ### TODO: we should probably walk up the wc here,
1632 in case the parent dir has an imaginary URL. */
1633 if (copy_pairs->nelts == 1)
1634 svn_path_split(top_dst_path, &parent, NULL, pool);
1635 else
1636 parent = top_dst_path;
1637 dst_err = svn_client_uuid_from_path(&dst_uuid, parent, adm_access,
1638 ctx, pool);
1639 if (dst_err && dst_err->apr_err != SVN_ERR_RA_NO_REPOS_UUID)
1640 return dst_err;
1642 /* If either of the UUIDs are nonexistent, then at least one of
1643 the repositories must be very old. Rather than punish the
1644 user, just assume the repositories are different, so no
1645 copy-history is attempted. */
1646 if (src_err || dst_err || (! src_uuid) || (! dst_uuid))
1647 same_repositories = FALSE;
1649 else
1650 same_repositories = (strcmp(src_uuid, dst_uuid) == 0) ? TRUE : FALSE;
1653 /* Perform the move for each of the copy_pairs. */
1654 for (i = 0; i < copy_pairs->nelts; i++)
1656 /* Check for cancellation */
1657 if (ctx->cancel_func)
1658 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1660 svn_pool_clear(iterpool);
1662 SVN_ERR(repos_to_wc_copy_single(APR_ARRAY_IDX(copy_pairs, i,
1663 svn_client__copy_pair_t *),
1664 same_repositories,
1665 ra_session, adm_access,
1666 ctx, iterpool));
1669 SVN_ERR(svn_wc_adm_close(adm_access));
1671 svn_pool_destroy(iterpool);
1673 return SVN_NO_ERROR;
1676 #define NEED_REPOS_REVNUM(revision) \
1677 ((revision.kind != svn_opt_revision_unspecified) \
1678 && (revision.kind != svn_opt_revision_working))
1680 /* Perform all allocations in POOL. */
1681 static svn_error_t *
1682 setup_copy(svn_commit_info_t **commit_info_p,
1683 const apr_array_header_t *sources,
1684 const char *dst_path_in,
1685 svn_boolean_t is_move,
1686 svn_boolean_t force,
1687 svn_boolean_t make_parents,
1688 svn_client_ctx_t *ctx,
1689 apr_pool_t *pool)
1691 apr_array_header_t *copy_pairs = apr_array_make(pool, sources->nelts,
1692 sizeof(struct copy_pair *));
1693 svn_boolean_t srcs_are_urls, dst_is_url;
1694 int i;
1696 /* Check to see if the supplied peg revisions make sense. */
1697 for (i = 0; i < sources->nelts; i++)
1699 svn_client_copy_source_t *source =
1700 ((svn_client_copy_source_t **) (sources->elts))[i];
1702 if (svn_path_is_url(source->path)
1703 && (source->peg_revision->kind == svn_opt_revision_base
1704 || source->peg_revision->kind == svn_opt_revision_committed
1705 || source->peg_revision->kind == svn_opt_revision_previous))
1706 return svn_error_create
1707 (SVN_ERR_CLIENT_BAD_REVISION, NULL,
1708 _("Revision type requires a working copy path, not a URL"));
1711 /* Are either of our paths URLs?
1712 * Just check the first src_path. If there are more than one, we'll check
1713 * for homogeneity amoung them down below. */
1714 srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
1715 svn_client_copy_source_t *)->path);
1716 dst_is_url = svn_path_is_url(dst_path_in);
1718 /* If we have multiple source paths, it implies the dst_path is a directory
1719 * we are moving or copying into. Populate the dst_paths array to contain
1720 * a destination path for each of the source paths. */
1721 if (sources->nelts > 1)
1723 apr_pool_t *iterpool = svn_pool_create(pool);
1725 for (i = 0; i < sources->nelts; i++)
1727 svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
1728 svn_client_copy_source_t *);
1729 svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
1730 const char *src_basename;
1731 svn_boolean_t src_is_url = svn_path_is_url(source->path);
1733 svn_pool_clear(iterpool);
1735 pair->src = apr_pstrdup(pool, source->path);
1736 pair->src_op_revision = *source->revision;
1737 pair->src_peg_revision = *source->peg_revision;
1739 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
1740 &pair->src_op_revision,
1741 src_is_url,
1742 TRUE,
1743 iterpool));
1744 src_basename = svn_path_basename(pair->src, iterpool);
1745 if (srcs_are_urls && ! dst_is_url)
1746 src_basename = svn_path_uri_decode(src_basename, pool);
1748 /* Check to see if all the sources are urls or all working copy
1749 * paths. */
1750 if (src_is_url != srcs_are_urls)
1751 return svn_error_create
1752 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1753 _("Cannot mix repository and working copy sources"));
1755 pair->dst = svn_path_join(dst_path_in, src_basename, pool);
1756 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
1759 svn_pool_destroy(iterpool);
1761 else
1763 /* Only one source path. */
1764 svn_client__copy_pair_t *pair = apr_palloc(pool, sizeof(*pair));
1765 svn_client_copy_source_t *source =
1766 APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
1768 pair->src = apr_pstrdup(pool, source->path);
1769 pair->src_op_revision = *source->revision;
1770 pair->src_peg_revision = *source->peg_revision;
1772 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
1773 &pair->src_op_revision,
1774 svn_path_is_url(pair->src),
1775 TRUE,
1776 pool));
1778 pair->dst = dst_path_in;
1779 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
1782 if (!srcs_are_urls && !dst_is_url)
1784 apr_pool_t *iterpool = svn_pool_create(pool);
1786 for (i = 0; i < copy_pairs->nelts; i++ )
1788 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1789 svn_client__copy_pair_t *);
1791 svn_pool_clear(iterpool);
1793 if (svn_path_is_child(pair->src, pair->dst, iterpool))
1794 return svn_error_createf
1795 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1796 _("Cannot copy path '%s' into its own child '%s'"),
1797 svn_path_local_style(pair->src, pool),
1798 svn_path_local_style(pair->dst, pool));
1801 svn_pool_destroy(iterpool);
1804 if (is_move)
1806 if (srcs_are_urls == dst_is_url)
1808 for (i = 0; i < copy_pairs->nelts; i++)
1810 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1811 svn_client__copy_pair_t *);
1813 if (strcmp(pair->src, pair->dst) == 0)
1814 return svn_error_createf
1815 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1816 _("Cannot move path '%s' into itself"),
1817 svn_path_local_style(pair->src, pool));
1820 else
1822 /* Disallow moves between the working copy and the repository. */
1823 return svn_error_create
1824 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1825 _("Moves between the working copy and the repository are not "
1826 "supported"));
1829 else
1831 if (!srcs_are_urls)
1833 /* If we are doing a wc->* move, but with an operational revision
1834 other than the working copy revision, we are really doing a
1835 repo->* move, because we're going to need to get the rev from the
1836 repo. */
1838 svn_boolean_t need_repos_op_rev = FALSE;
1839 svn_boolean_t need_repos_peg_rev = FALSE;
1841 /* Check to see if any revision is something other than
1842 svn_opt_revision_unspecified or svn_opt_revision_working. */
1843 for (i = 0; i < copy_pairs->nelts; i++)
1845 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1846 svn_client__copy_pair_t *);
1848 if (NEED_REPOS_REVNUM(pair->src_op_revision))
1849 need_repos_op_rev = TRUE;
1851 if (NEED_REPOS_REVNUM(pair->src_peg_revision))
1852 need_repos_peg_rev = TRUE;
1854 if (need_repos_op_rev || need_repos_peg_rev)
1855 break;
1858 if (need_repos_op_rev || need_repos_peg_rev)
1860 apr_pool_t *iterpool = svn_pool_create(pool);
1862 for (i = 0; i < copy_pairs->nelts; i++)
1864 const char *url;
1865 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1866 svn_client__copy_pair_t *);
1868 /* We can convert the working copy path to a URL based on the
1869 entries file. */
1870 svn_wc_adm_access_t *adm_access; /* ### FIXME local */
1871 const svn_wc_entry_t *entry;
1873 svn_pool_clear(iterpool);
1875 SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL,
1876 pair->src, FALSE, 0,
1877 ctx->cancel_func,
1878 ctx->cancel_baton,
1879 iterpool));
1880 SVN_ERR(svn_wc__entry_versioned(&entry, pair->src, adm_access,
1881 FALSE, iterpool));
1882 SVN_ERR(svn_wc_adm_close(adm_access));
1884 url = (entry->copied ? entry->copyfrom_url : entry->url);
1885 if (url == NULL)
1886 return svn_error_createf
1887 (SVN_ERR_ENTRY_MISSING_URL, NULL,
1888 _("'%s' does not have a URL associated with it"),
1889 svn_path_local_style(pair->src, pool));
1891 pair->src = apr_pstrdup(pool, url);
1893 if (!need_repos_peg_rev
1894 || pair->src_peg_revision.kind == svn_opt_revision_base)
1896 /* Default the peg revision to that of the WC entry. */
1897 pair->src_peg_revision.kind = svn_opt_revision_number;
1898 pair->src_peg_revision.value.number =
1899 (entry->copied ? entry->copyfrom_rev : entry->revision);
1902 if (pair->src_op_revision.kind == svn_opt_revision_base)
1904 /* Use the entry's revision as the operational rev. */
1905 pair->src_op_revision.kind = svn_opt_revision_number;
1906 pair->src_op_revision.value.number =
1907 (entry->copied ? entry->copyfrom_rev : entry->revision);
1911 svn_pool_destroy(iterpool);
1912 srcs_are_urls = TRUE;
1917 /* Now, call the right handler for the operation. */
1918 if ((! srcs_are_urls) && (! dst_is_url))
1920 *commit_info_p = NULL;
1921 SVN_ERR(wc_to_wc_copy(copy_pairs, is_move, make_parents,
1922 ctx, pool));
1924 else if ((! srcs_are_urls) && (dst_is_url))
1926 SVN_ERR(wc_to_repos_copy(commit_info_p, copy_pairs, make_parents,
1927 ctx, pool));
1929 else if ((srcs_are_urls) && (! dst_is_url))
1931 *commit_info_p = NULL;
1932 SVN_ERR(repos_to_wc_copy(copy_pairs, make_parents, ctx, pool));
1934 else
1936 SVN_ERR(repos_to_repos_copy(commit_info_p, copy_pairs, make_parents,
1937 ctx, is_move, pool));
1940 return SVN_NO_ERROR;
1945 /* Public Interfaces */
1946 svn_error_t *
1947 svn_client_copy4(svn_commit_info_t **commit_info_p,
1948 apr_array_header_t *sources,
1949 const char *dst_path,
1950 svn_boolean_t copy_as_child,
1951 svn_boolean_t make_parents,
1952 svn_client_ctx_t *ctx,
1953 apr_pool_t *pool)
1955 svn_error_t *err;
1956 svn_commit_info_t *commit_info = NULL;
1957 apr_pool_t *subpool = svn_pool_create(pool);
1959 if (sources->nelts > 1 && !copy_as_child)
1960 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
1961 NULL, NULL);
1963 err = setup_copy(&commit_info,
1964 sources, dst_path,
1965 FALSE /* is_move */,
1966 TRUE /* force, set to avoid deletion check */,
1967 make_parents,
1968 ctx,
1969 subpool);
1971 /* If the destination exists, try to copy the sources as children of the
1972 destination. */
1973 if (copy_as_child && err && (sources->nelts == 1)
1974 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
1975 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
1977 const char *src_path = APR_ARRAY_IDX(sources, 0,
1978 svn_client_copy_source_t *)->path;
1979 const char *src_basename;
1981 svn_error_clear(err);
1982 svn_pool_clear(subpool);
1984 src_basename = svn_path_basename(src_path, subpool);
1985 if (svn_path_is_url(src_path) && ! svn_path_is_url(dst_path))
1986 src_basename = svn_path_uri_decode(src_basename, pool);
1988 err = setup_copy(&commit_info,
1989 sources,
1990 svn_path_join(dst_path, src_basename, pool),
1991 FALSE /* is_move */,
1992 TRUE /* force, set to avoid deletion check */,
1993 make_parents,
1994 ctx,
1995 subpool);
1998 if (commit_info_p != NULL)
2000 if (commit_info)
2001 *commit_info_p = svn_commit_info_dup(commit_info, pool);
2002 else
2003 *commit_info_p = commit_info;
2006 svn_pool_destroy(subpool);
2007 return err;
2011 svn_error_t *
2012 svn_client_copy3(svn_commit_info_t **commit_info_p,
2013 const char *src_path,
2014 const svn_opt_revision_t *src_revision,
2015 const char *dst_path,
2016 svn_client_ctx_t *ctx,
2017 apr_pool_t *pool)
2019 apr_array_header_t *sources = apr_array_make(pool, 1,
2020 sizeof(const svn_client_copy_source_t *));
2021 svn_client_copy_source_t copy_source;
2023 copy_source.path = src_path;
2024 copy_source.revision = src_revision;
2025 copy_source.peg_revision = src_revision;
2027 APR_ARRAY_PUSH(sources, const svn_client_copy_source_t *) = &copy_source;
2029 return svn_client_copy4(commit_info_p,
2030 sources,
2031 dst_path,
2032 FALSE, FALSE,
2033 ctx,
2034 pool);
2038 svn_error_t *
2039 svn_client_copy2(svn_commit_info_t **commit_info_p,
2040 const char *src_path,
2041 const svn_opt_revision_t *src_revision,
2042 const char *dst_path,
2043 svn_client_ctx_t *ctx,
2044 apr_pool_t *pool)
2046 svn_error_t *err;
2048 err = svn_client_copy3(commit_info_p, src_path, src_revision,
2049 dst_path, ctx, pool);
2051 /* If the target exists, try to copy the source as a child of the target.
2052 This will obviously fail if target is not a directory, but that's exactly
2053 what we want. */
2054 if (err && (err->apr_err == SVN_ERR_ENTRY_EXISTS
2055 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
2057 const char *src_basename = svn_path_basename(src_path, pool);
2059 svn_error_clear(err);
2061 return svn_client_copy3(commit_info_p, src_path, src_revision,
2062 svn_path_join(dst_path, src_basename, pool),
2063 ctx, pool);
2066 return err;
2069 svn_error_t *
2070 svn_client_copy(svn_client_commit_info_t **commit_info_p,
2071 const char *src_path,
2072 const svn_opt_revision_t *src_revision,
2073 const char *dst_path,
2074 svn_client_ctx_t *ctx,
2075 apr_pool_t *pool)
2077 svn_commit_info_t *commit_info = NULL;
2078 svn_error_t *err;
2080 err = svn_client_copy2(&commit_info, src_path, src_revision, dst_path,
2081 ctx, pool);
2082 /* These structs have the same layout for the common fields. */
2083 *commit_info_p = (svn_client_commit_info_t *) commit_info;
2084 return err;
2088 svn_error_t *
2089 svn_client_move5(svn_commit_info_t **commit_info_p,
2090 apr_array_header_t *src_paths,
2091 const char *dst_path,
2092 svn_boolean_t force,
2093 svn_boolean_t move_as_child,
2094 svn_boolean_t make_parents,
2095 svn_client_ctx_t *ctx,
2096 apr_pool_t *pool)
2098 svn_commit_info_t *commit_info = NULL;
2099 const svn_opt_revision_t head_revision
2100 = { svn_opt_revision_head, { 0 } };
2101 svn_error_t *err;
2102 int i;
2103 apr_pool_t *subpool = svn_pool_create(pool);
2104 apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
2105 sizeof(const svn_client_copy_source_t *));
2107 if (src_paths->nelts > 1 && !move_as_child)
2108 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
2109 NULL, NULL);
2111 for (i = 0; i < src_paths->nelts; i++)
2113 const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
2114 svn_client_copy_source_t *copy_source = apr_palloc(pool,
2115 sizeof(*copy_source));
2117 copy_source->path = src_path;
2118 copy_source->revision = &head_revision;
2119 copy_source->peg_revision = &head_revision;
2121 APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
2124 err = setup_copy(&commit_info, sources, dst_path,
2125 TRUE /* is_move */,
2126 force,
2127 make_parents,
2128 ctx,
2129 subpool);
2131 /* If the destination exists, try to move the sources as children of the
2132 destination. */
2133 if (move_as_child && err && (src_paths->nelts == 1)
2134 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
2135 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
2137 const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
2138 const char *src_basename;
2140 svn_error_clear(err);
2141 svn_pool_clear(subpool);
2143 src_basename = svn_path_basename(src_path, pool);
2145 err = setup_copy(&commit_info, sources,
2146 svn_path_join(dst_path, src_basename, pool),
2147 TRUE /* is_move */,
2148 force,
2149 make_parents,
2150 ctx,
2151 subpool);
2154 if (commit_info_p != NULL)
2156 if (commit_info)
2157 *commit_info_p = svn_commit_info_dup(commit_info, pool);
2158 else
2159 *commit_info_p = commit_info;
2162 svn_pool_destroy(subpool);
2163 return err;
2166 svn_error_t *
2167 svn_client_move4(svn_commit_info_t **commit_info_p,
2168 const char *src_path,
2169 const char *dst_path,
2170 svn_boolean_t force,
2171 svn_client_ctx_t *ctx,
2172 apr_pool_t *pool)
2174 apr_array_header_t *src_paths =
2175 apr_array_make(pool, 1, sizeof(const char *));
2176 APR_ARRAY_PUSH(src_paths, const char *) = src_path;
2178 return svn_client_move5(commit_info_p,
2179 src_paths, dst_path, force, FALSE,
2180 FALSE, ctx, pool);
2183 svn_error_t *
2184 svn_client_move3(svn_commit_info_t **commit_info_p,
2185 const char *src_path,
2186 const char *dst_path,
2187 svn_boolean_t force,
2188 svn_client_ctx_t *ctx,
2189 apr_pool_t *pool)
2191 svn_error_t *err;
2193 err = svn_client_move4(commit_info_p, src_path, dst_path, force, ctx, pool);
2195 /* If the target exists, try to move the source as a child of the target.
2196 This will obviously fail if target is not a directory, but that's exactly
2197 what we want. */
2198 if (err && (err->apr_err == SVN_ERR_ENTRY_EXISTS
2199 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
2201 const char *src_basename = svn_path_basename(src_path, pool);
2203 svn_error_clear(err);
2205 return svn_client_move4(commit_info_p, src_path,
2206 svn_path_join(dst_path, src_basename, pool),
2207 force, ctx, pool);
2210 return err;
2213 svn_error_t *
2214 svn_client_move2(svn_client_commit_info_t **commit_info_p,
2215 const char *src_path,
2216 const char *dst_path,
2217 svn_boolean_t force,
2218 svn_client_ctx_t *ctx,
2219 apr_pool_t *pool)
2221 svn_commit_info_t *commit_info = NULL;
2222 svn_error_t *err;
2224 err = svn_client_move3(&commit_info, src_path, dst_path, force, ctx, pool);
2225 /* These structs have the same layout for the common fields. */
2226 *commit_info_p = (svn_client_commit_info_t *) commit_info;
2227 return err;
2231 svn_error_t *
2232 svn_client_move(svn_client_commit_info_t **commit_info_p,
2233 const char *src_path,
2234 const svn_opt_revision_t *src_revision,
2235 const char *dst_path,
2236 svn_boolean_t force,
2237 svn_client_ctx_t *ctx,
2238 apr_pool_t *pool)
2240 svn_commit_info_t *commit_info = NULL;
2241 svn_error_t *err;
2242 svn_client_copy_source_t copy_source;
2243 apr_array_header_t *sources = apr_array_make(pool, 1,
2244 sizeof(const svn_client_copy_source_t *));
2246 /* It doesn't make sense to specify revisions in a move. */
2248 /* ### todo: this check could fail wrongly. For example,
2249 someone could pass in an svn_opt_revision_number that just
2250 happens to be the HEAD. It's fair enough to punt then, IMHO,
2251 and just demand that the user not specify a revision at all;
2252 beats mucking up this function with RA calls and such. */
2253 if (src_revision->kind != svn_opt_revision_unspecified
2254 && src_revision->kind != svn_opt_revision_head)
2256 return svn_error_create
2257 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2258 _("Cannot specify revisions (except HEAD) with move operations"));
2261 copy_source.path = src_path;
2262 copy_source.revision = src_revision;
2263 copy_source.peg_revision = src_revision;
2265 APR_ARRAY_PUSH(sources, const svn_client_copy_source_t *) = &copy_source;
2267 err = setup_copy(&commit_info,
2268 sources, dst_path,
2269 TRUE /* is_move */,
2270 force,
2271 FALSE /* make_parents */,
2272 ctx,
2273 pool);
2274 /* These structs have the same layout for the common fields. */
2275 *commit_info_p = (svn_client_commit_info_t *) commit_info;
2276 return err;