1 /*
2 * copy.c: copy/move wrappers around wc 'copy' functionality.
3 *
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
12 *
13 * http://www.apache.org/licenses/LICENSE-2.0
14 *
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
20 * under the License.
21 * ====================================================================
22 */
23
24 /* ==================================================================== */
25
26
27
28 /*** Includes. ***/
29
30 #include <string.h>
31 #include "svn_hash.h"
32 #include "svn_client.h"
33 #include "svn_error.h"
34 #include "svn_error_codes.h"
35 #include "svn_dirent_uri.h"
36 #include "svn_path.h"
37 #include "svn_opt.h"
38 #include "svn_time.h"
39 #include "svn_props.h"
40 #include "svn_mergeinfo.h"
41 #include "svn_pools.h"
42
43 #include "client.h"
44 #include "mergeinfo.h"
45
46 #include "svn_private_config.h"
47 #include "private/svn_wc_private.h"
48 #include "private/svn_ra_private.h"
49 #include "private/svn_mergeinfo_private.h"
50 #include "private/svn_client_private.h"
51
52
53 /*
54 * OUR BASIC APPROACH TO COPIES
55 * ============================
56 *
57 * for each source/destination pair
58 * if (not exist src_path)
59 * return ERR_BAD_SRC error
60 *
61 * if (exist dst_path)
62 * return ERR_OBSTRUCTION error
63 * else
64 * copy src_path into parent_of_dst_path as basename (dst_path)
65 *
66 * if (this is a move)
67 * delete src_path
68 */
69
70
71
72 /*** Code. ***/
73
74 /* Extend the mergeinfo for the single WC path TARGET_WCPATH, adding
75 MERGEINFO to any mergeinfo pre-existing in the WC. */
76 static svn_error_t *
extend_wc_mergeinfo(const char * target_abspath,apr_hash_t * mergeinfo,svn_client_ctx_t * ctx,apr_pool_t * pool)77 extend_wc_mergeinfo(const char *target_abspath,
78 apr_hash_t *mergeinfo,
79 svn_client_ctx_t *ctx,
80 apr_pool_t *pool)
81 {
82 apr_hash_t *wc_mergeinfo;
83
84 /* Get a fresh copy of the pre-existing state of the WC's mergeinfo
85 updating it. */
86 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
87 target_abspath, pool, pool));
88
89 /* Combine the provided mergeinfo with any mergeinfo from the WC. */
90 if (wc_mergeinfo && mergeinfo)
91 SVN_ERR(svn_mergeinfo_merge2(wc_mergeinfo, mergeinfo, pool, pool));
92 else if (! wc_mergeinfo)
93 wc_mergeinfo = mergeinfo;
94
95 return svn_error_trace(
96 svn_client__record_wc_mergeinfo(target_abspath, wc_mergeinfo,
97 FALSE, ctx, pool));
98 }
99
100 /* Find the longest common ancestor of paths in COPY_PAIRS. If
101 SRC_ANCESTOR is NULL, ignore source paths in this calculation. If
102 DST_ANCESTOR is NULL, ignore destination paths in this calculation.
103 COMMON_ANCESTOR will be the common ancestor of both the
104 SRC_ANCESTOR and DST_ANCESTOR, and will only be set if it is not
105 NULL.
106 */
107 static svn_error_t *
get_copy_pair_ancestors(const apr_array_header_t * copy_pairs,const char ** src_ancestor,const char ** dst_ancestor,const char ** common_ancestor,apr_pool_t * pool)108 get_copy_pair_ancestors(const apr_array_header_t *copy_pairs,
109 const char **src_ancestor,
110 const char **dst_ancestor,
111 const char **common_ancestor,
112 apr_pool_t *pool)
113 {
114 apr_pool_t *subpool = svn_pool_create(pool);
115 svn_client__copy_pair_t *first;
116 const char *first_dst;
117 const char *first_src;
118 const char *top_dst;
119 svn_boolean_t src_is_url;
120 svn_boolean_t dst_is_url;
121 char *top_src;
122 int i;
123
124 first = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
125
126 /* Because all the destinations are in the same directory, we can easily
127 determine their common ancestor. */
128 first_dst = first->dst_abspath_or_url;
129 dst_is_url = svn_path_is_url(first_dst);
130
131 if (copy_pairs->nelts == 1)
132 top_dst = apr_pstrdup(subpool, first_dst);
133 else
134 top_dst = dst_is_url ? svn_uri_dirname(first_dst, subpool)
135 : svn_dirent_dirname(first_dst, subpool);
136
137 /* Sources can came from anywhere, so we have to actually do some
138 work for them. */
139 first_src = first->src_abspath_or_url;
140 src_is_url = svn_path_is_url(first_src);
141 top_src = apr_pstrdup(subpool, first_src);
142 for (i = 1; i < copy_pairs->nelts; i++)
143 {
144 /* We don't need to clear the subpool here for several reasons:
145 1) If we do, we can't use it to allocate the initial versions of
146 top_src and top_dst (above).
147 2) We don't return any errors in the following loop, so we
148 are guanteed to destroy the subpool at the end of this function.
149 3) The number of iterations is likely to be few, and the loop will
150 be through quickly, so memory leakage will not be significant,
151 in time or space.
152 */
153 const svn_client__copy_pair_t *pair =
154 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
155
156 top_src = src_is_url
157 ? svn_uri_get_longest_ancestor(top_src, pair->src_abspath_or_url,
158 subpool)
159 : svn_dirent_get_longest_ancestor(top_src, pair->src_abspath_or_url,
160 subpool);
161 }
162
163 if (src_ancestor)
164 *src_ancestor = apr_pstrdup(pool, top_src);
165
166 if (dst_ancestor)
167 *dst_ancestor = apr_pstrdup(pool, top_dst);
168
169 if (common_ancestor)
170 *common_ancestor =
171 src_is_url
172 ? svn_uri_get_longest_ancestor(top_src, top_dst, pool)
173 : svn_dirent_get_longest_ancestor(top_src, top_dst, pool);
174
175 svn_pool_destroy(subpool);
176
177 return SVN_NO_ERROR;
178 }
179
180 /* Quote a string if it would be handled as multiple or different tokens
181 during externals parsing */
182 static const char *
maybe_quote(const char * value,apr_pool_t * result_pool)183 maybe_quote(const char *value,
184 apr_pool_t *result_pool)
185 {
186 apr_status_t status;
187 char **argv;
188
189 status = apr_tokenize_to_argv(value, &argv, result_pool);
190
191 if (!status && argv[0] && !argv[1] && strcmp(argv[0], value) == 0)
192 return apr_pstrdup(result_pool, value);
193
194 {
195 svn_stringbuf_t *sb = svn_stringbuf_create_empty(result_pool);
196 const char *c;
197
198 svn_stringbuf_appendbyte(sb, '\"');
199
200 for (c = value; *c; c++)
201 {
202 if (*c == '\\' || *c == '\"' || *c == '\'')
203 svn_stringbuf_appendbyte(sb, '\\');
204
205 svn_stringbuf_appendbyte(sb, *c);
206 }
207
208 svn_stringbuf_appendbyte(sb, '\"');
209
210 #ifdef SVN_DEBUG
211 status = apr_tokenize_to_argv(sb->data, &argv, result_pool);
212
213 SVN_ERR_ASSERT_NO_RETURN(!status && argv[0] && !argv[1]
214 && !strcmp(argv[0], value));
215 #endif
216
217 return sb->data;
218 }
219 }
220
221 /* In *NEW_EXTERNALS_DESCRIPTION, return a new external description for
222 * use as a line in an svn:externals property, based on the external item
223 * ITEM and the additional parser information in INFO. Pin the external
224 * to EXTERNAL_PEGREV. Use POOL for all allocations. */
225 static svn_error_t *
make_external_description(const char ** new_external_description,const char * local_abspath_or_url,svn_wc_external_item2_t * item,svn_wc__externals_parser_info_t * info,svn_opt_revision_t external_pegrev,apr_pool_t * pool)226 make_external_description(const char **new_external_description,
227 const char *local_abspath_or_url,
228 svn_wc_external_item2_t *item,
229 svn_wc__externals_parser_info_t *info,
230 svn_opt_revision_t external_pegrev,
231 apr_pool_t *pool)
232 {
233 const char *rev_str;
234 const char *peg_rev_str;
235
236 switch (info->format)
237 {
238 case svn_wc__external_description_format_1:
239 if (external_pegrev.kind == svn_opt_revision_unspecified)
240 {
241 /* If info->rev_str is NULL, this yields an empty string. */
242 rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL);
243 }
244 else if (info->rev_str && item->revision.kind != svn_opt_revision_head)
245 rev_str = apr_psprintf(pool, "%s ", info->rev_str);
246 else
247 {
248 /* ### can't handle svn_opt_revision_date without info->rev_str */
249 SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number);
250 rev_str = apr_psprintf(pool, "-r%ld ",
251 external_pegrev.value.number);
252 }
253
254 *new_external_description =
255 apr_psprintf(pool, "%s %s%s\n", maybe_quote(item->target_dir, pool),
256 rev_str,
257 maybe_quote(item->url, pool));
258 break;
259
260 case svn_wc__external_description_format_2:
261 if (external_pegrev.kind == svn_opt_revision_unspecified)
262 {
263 /* If info->rev_str is NULL, this yields an empty string. */
264 rev_str = apr_pstrcat(pool, info->rev_str, " ", SVN_VA_NULL);
265 }
266 else if (info->rev_str && item->revision.kind != svn_opt_revision_head)
267 rev_str = apr_psprintf(pool, "%s ", info->rev_str);
268 else
269 rev_str = "";
270
271 if (external_pegrev.kind == svn_opt_revision_unspecified)
272 peg_rev_str = info->peg_rev_str ? info->peg_rev_str : "";
273 else if (info->peg_rev_str &&
274 item->peg_revision.kind != svn_opt_revision_head)
275 peg_rev_str = info->peg_rev_str;
276 else
277 {
278 /* ### can't handle svn_opt_revision_date without info->rev_str */
279 SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_number);
280 peg_rev_str = apr_psprintf(pool, "@%ld",
281 external_pegrev.value.number);
282 }
283
284 *new_external_description =
285 apr_psprintf(pool, "%s%s %s\n", rev_str,
286 maybe_quote(apr_psprintf(pool, "%s%s", item->url,
287 peg_rev_str),
288 pool),
289 maybe_quote(item->target_dir, pool));
290 break;
291
292 default:
293 return svn_error_createf(
294 SVN_ERR_CLIENT_INVALID_EXTERNALS_DESCRIPTION, NULL,
295 _("%s property defined at '%s' is using an unsupported "
296 "syntax"), SVN_PROP_EXTERNALS,
297 svn_dirent_local_style(local_abspath_or_url, pool));
298 }
299
300 return SVN_NO_ERROR;
301 }
302
303 /* Pin all externals listed in EXTERNALS_PROP_VAL to their
304 * last-changed revision. Set *PINNED_EXTERNALS to a new property
305 * value allocated in RESULT_POOL, or to NULL if none of the externals
306 * in EXTERNALS_PROP_VAL were changed. LOCAL_ABSPATH_OR_URL is the
307 * path or URL defining the svn:externals property. Use SCRATCH_POOL
308 * for temporary allocations.
309 */
310 static svn_error_t *
pin_externals_prop(svn_string_t ** pinned_externals,svn_string_t * externals_prop_val,const apr_hash_t * externals_to_pin,const char * repos_root_url,const char * local_abspath_or_url,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)311 pin_externals_prop(svn_string_t **pinned_externals,
312 svn_string_t *externals_prop_val,
313 const apr_hash_t *externals_to_pin,
314 const char *repos_root_url,
315 const char *local_abspath_or_url,
316 svn_client_ctx_t *ctx,
317 apr_pool_t *result_pool,
318 apr_pool_t *scratch_pool)
319 {
320 svn_stringbuf_t *buf;
321 apr_array_header_t *external_items;
322 apr_array_header_t *parser_infos;
323 apr_array_header_t *items_to_pin;
324 int pinned_items;
325 int i;
326 apr_pool_t *iterpool;
327
328 SVN_ERR(svn_wc__parse_externals_description(&external_items,
329 &parser_infos,
330 local_abspath_or_url,
331 externals_prop_val->data,
332 FALSE /* canonicalize_url */,
333 scratch_pool));
334
335 if (externals_to_pin)
336 {
337 items_to_pin = svn_hash_gets((apr_hash_t *)externals_to_pin,
338 local_abspath_or_url);
339 if (!items_to_pin)
340 {
341 /* No pinning at all for this path. */
342 *pinned_externals = NULL;
343 return SVN_NO_ERROR;
344 }
345 }
346 else
347 items_to_pin = NULL;
348
349 buf = svn_stringbuf_create_empty(scratch_pool);
350 iterpool = svn_pool_create(scratch_pool);
351 pinned_items = 0;
352 for (i = 0; i < external_items->nelts; i++)
353 {
354 svn_wc_external_item2_t *item;
355 svn_wc__externals_parser_info_t *info;
356 svn_opt_revision_t external_pegrev;
357 const char *pinned_desc;
358
359 svn_pool_clear(iterpool);
360
361 item = APR_ARRAY_IDX(external_items, i, svn_wc_external_item2_t *);
362 info = APR_ARRAY_IDX(parser_infos, i, svn_wc__externals_parser_info_t *);
363
364 if (items_to_pin)
365 {
366 int j;
367 svn_wc_external_item2_t *item_to_pin = NULL;
368
369 for (j = 0; j < items_to_pin->nelts; j++)
370 {
371 svn_wc_external_item2_t *const current =
372 APR_ARRAY_IDX(items_to_pin, j, svn_wc_external_item2_t *);
373
374
375 if (current
376 && 0 == strcmp(item->url, current->url)
377 && 0 == strcmp(item->target_dir, current->target_dir))
378 {
379 item_to_pin = current;
380 break;
381 }
382 }
383
384 /* If this item is not in our list of external items to pin then
385 * simply keep the external at its original value. */
386 if (!item_to_pin)
387 {
388 const char *desc;
389
390 external_pegrev.kind = svn_opt_revision_unspecified;
391 SVN_ERR(make_external_description(&desc, local_abspath_or_url,
392 item, info, external_pegrev,
393 iterpool));
394 svn_stringbuf_appendcstr(buf, desc);
395 continue;
396 }
397 }
398
399 if (item->peg_revision.kind == svn_opt_revision_date)
400 {
401 /* Already pinned ... copy the peg date. */
402 external_pegrev.kind = svn_opt_revision_date;
403 external_pegrev.value.date = item->peg_revision.value.date;
404 }
405 else if (item->peg_revision.kind == svn_opt_revision_number)
406 {
407 /* Already pinned ... copy the peg revision number. */
408 external_pegrev.kind = svn_opt_revision_number;
409 external_pegrev.value.number = item->peg_revision.value.number;
410 }
411 else
412 {
413 SVN_ERR_ASSERT(
414 item->peg_revision.kind == svn_opt_revision_head ||
415 item->peg_revision.kind == svn_opt_revision_unspecified);
416
417 /* We're actually going to change the peg revision. */
418 ++pinned_items;
419
420 if (svn_path_is_url(local_abspath_or_url))
421 {
422 const char *resolved_url;
423 svn_ra_session_t *external_ra_session;
424 svn_revnum_t latest_revnum;
425
426 SVN_ERR(svn_wc__resolve_relative_external_url(
427 &resolved_url, item, repos_root_url,
428 local_abspath_or_url, iterpool, iterpool));
429 SVN_ERR(svn_client__open_ra_session_internal(&external_ra_session,
430 NULL, resolved_url,
431 NULL, NULL, FALSE,
432 FALSE, ctx,
433 iterpool,
434 iterpool));
435 SVN_ERR(svn_ra_get_latest_revnum(external_ra_session,
436 &latest_revnum,
437 iterpool));
438
439 external_pegrev.kind = svn_opt_revision_number;
440 external_pegrev.value.number = latest_revnum;
441 }
442 else
443 {
444 const char *external_abspath;
445 svn_node_kind_t external_kind;
446 svn_revnum_t external_checked_out_rev;
447
448 external_abspath = svn_dirent_join(local_abspath_or_url,
449 item->target_dir,
450 iterpool);
451 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL,
452 NULL, NULL, ctx->wc_ctx,
453 local_abspath_or_url,
454 external_abspath, TRUE,
455 iterpool,
456 iterpool));
457 if (external_kind == svn_node_none)
458 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
459 NULL,
460 _("Cannot pin external '%s' defined "
461 "in %s at '%s' because it is not "
462 "checked out in the working copy "
463 "at '%s'"),
464 item->url, SVN_PROP_EXTERNALS,
465 svn_dirent_local_style(
466 local_abspath_or_url, iterpool),
467 svn_dirent_local_style(
468 external_abspath, iterpool));
469 else if (external_kind == svn_node_dir)
470 {
471 svn_boolean_t is_switched;
472 svn_boolean_t is_modified;
473 svn_revnum_t min_rev;
474 svn_revnum_t max_rev;
475
476 /* Perform some sanity checks on the checked-out external. */
477
478 SVN_ERR(svn_wc__has_switched_subtrees(&is_switched,
479 ctx->wc_ctx,
480 external_abspath, NULL,
481 iterpool));
482 if (is_switched)
483 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
484 NULL,
485 _("Cannot pin external '%s' defined "
486 "in %s at '%s' because '%s' has "
487 "switched subtrees (switches "
488 "cannot be represented in %s)"),
489 item->url, SVN_PROP_EXTERNALS,
490 svn_dirent_local_style(
491 local_abspath_or_url, iterpool),
492 svn_dirent_local_style(
493 external_abspath, iterpool),
494 SVN_PROP_EXTERNALS);
495
496 SVN_ERR(svn_wc__has_local_mods(&is_modified, ctx->wc_ctx,
497 external_abspath, TRUE,
498 ctx->cancel_func,
499 ctx->cancel_baton,
500 iterpool));
501 if (is_modified)
502 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
503 NULL,
504 _("Cannot pin external '%s' defined "
505 "in %s at '%s' because '%s' has "
506 "local modifications (local "
507 "modifications cannot be "
508 "represented in %s)"),
509 item->url, SVN_PROP_EXTERNALS,
510 svn_dirent_local_style(
511 local_abspath_or_url, iterpool),
512 svn_dirent_local_style(
513 external_abspath, iterpool),
514 SVN_PROP_EXTERNALS);
515
516 SVN_ERR(svn_wc__min_max_revisions(&min_rev, &max_rev, ctx->wc_ctx,
517 external_abspath, FALSE,
518 iterpool));
519 if (min_rev != max_rev)
520 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
521 NULL,
522 _("Cannot pin external '%s' defined "
523 "in %s at '%s' because '%s' is a "
524 "mixed-revision working copy "
525 "(mixed-revisions cannot be "
526 "represented in %s)"),
527 item->url, SVN_PROP_EXTERNALS,
528 svn_dirent_local_style(
529 local_abspath_or_url, iterpool),
530 svn_dirent_local_style(
531 external_abspath, iterpool),
532 SVN_PROP_EXTERNALS);
533 external_checked_out_rev = min_rev;
534 }
535 else
536 {
537 SVN_ERR_ASSERT(external_kind == svn_node_file);
538 SVN_ERR(svn_wc__node_get_repos_info(&external_checked_out_rev,
539 NULL, NULL, NULL,
540 ctx->wc_ctx, external_abspath,
541 iterpool, iterpool));
542 }
543
544 external_pegrev.kind = svn_opt_revision_number;
545 external_pegrev.value.number = external_checked_out_rev;
546 }
547 }
548
549 SVN_ERR_ASSERT(external_pegrev.kind == svn_opt_revision_date ||
550 external_pegrev.kind == svn_opt_revision_number);
551
552 SVN_ERR(make_external_description(&pinned_desc, local_abspath_or_url,
553 item, info, external_pegrev, iterpool));
554
555 svn_stringbuf_appendcstr(buf, pinned_desc);
556 }
557 svn_pool_destroy(iterpool);
558
559 if (pinned_items > 0)
560 *pinned_externals = svn_string_create_from_buf(buf, result_pool);
561 else
562 *pinned_externals = NULL;
563
564 return SVN_NO_ERROR;
565 }
566
567 /* Return, in *PINNED_EXTERNALS, a new hash mapping URLs or local abspaths
568 * to svn:externals property values (as const char *), where some or all
569 * external references have been pinned.
570 * If EXTERNALS_TO_PIN is NULL, pin all externals, else pin the externals
571 * mentioned in EXTERNALS_TO_PIN.
572 * The pinning operation takes place as part of the copy operation for
573 * the source/destination pair PAIR. Use RA_SESSION and REPOS_ROOT_URL
574 * to contact the repository containing the externals definition, if neccesary.
575 * Use CX to fopen additional RA sessions to external repositories, if
576 * neccessary. Allocate *NEW_EXTERNALS in RESULT_POOL.
577 * Use SCRATCH_POOL for temporary allocations. */
578 static svn_error_t *
resolve_pinned_externals(apr_hash_t ** pinned_externals,const apr_hash_t * externals_to_pin,svn_client__copy_pair_t * pair,svn_ra_session_t * ra_session,const char * repos_root_url,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)579 resolve_pinned_externals(apr_hash_t **pinned_externals,
580 const apr_hash_t *externals_to_pin,
581 svn_client__copy_pair_t *pair,
582 svn_ra_session_t *ra_session,
583 const char *repos_root_url,
584 svn_client_ctx_t *ctx,
585 apr_pool_t *result_pool,
586 apr_pool_t *scratch_pool)
587 {
588 const char *old_url = NULL;
589 apr_hash_t *externals_props;
590 apr_hash_index_t *hi;
591 apr_pool_t *iterpool;
592
593 *pinned_externals = apr_hash_make(result_pool);
594
595 if (svn_path_is_url(pair->src_abspath_or_url))
596 {
597 SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
598 pair->src_abspath_or_url,
599 scratch_pool));
600 externals_props = apr_hash_make(scratch_pool);
601 SVN_ERR(svn_client__remote_propget(externals_props, NULL,
602 SVN_PROP_EXTERNALS,
603 pair->src_abspath_or_url, "",
604 svn_node_dir,
605 pair->src_revnum,
606 ra_session,
607 svn_depth_infinity,
608 scratch_pool,
609 scratch_pool));
610 }
611 else
612 {
613 SVN_ERR(svn_wc__externals_gather_definitions(&externals_props, NULL,
614 ctx->wc_ctx,
615 pair->src_abspath_or_url,
616 svn_depth_infinity,
617 scratch_pool, scratch_pool));
618
619 /* ### gather_definitions returns propvals as const char * */
620 for (hi = apr_hash_first(scratch_pool, externals_props);
621 hi;
622 hi = apr_hash_next(hi))
623 {
624 const char *local_abspath_or_url = apr_hash_this_key(hi);
625 const char *propval = apr_hash_this_val(hi);
626 svn_string_t *new_propval = svn_string_create(propval, scratch_pool);
627
628 svn_hash_sets(externals_props, local_abspath_or_url, new_propval);
629 }
630 }
631
632 if (apr_hash_count(externals_props) == 0)
633 {
634 if (old_url)
635 SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool));
636 return SVN_NO_ERROR;
637 }
638
639 iterpool = svn_pool_create(scratch_pool);
640 for (hi = apr_hash_first(scratch_pool, externals_props);
641 hi;
642 hi = apr_hash_next(hi))
643 {
644 const char *local_abspath_or_url = apr_hash_this_key(hi);
645 svn_string_t *externals_propval = apr_hash_this_val(hi);
646 const char *relpath;
647 svn_string_t *new_propval;
648
649 svn_pool_clear(iterpool);
650
651 SVN_ERR(pin_externals_prop(&new_propval, externals_propval,
652 externals_to_pin,
653 repos_root_url, local_abspath_or_url, ctx,
654 result_pool, iterpool));
655 if (new_propval)
656 {
657 if (svn_path_is_url(pair->src_abspath_or_url))
658 relpath = svn_uri_skip_ancestor(pair->src_abspath_or_url,
659 local_abspath_or_url,
660 result_pool);
661 else
662 relpath = svn_dirent_skip_ancestor(pair->src_abspath_or_url,
663 local_abspath_or_url);
664 SVN_ERR_ASSERT(relpath);
665
666 svn_hash_sets(*pinned_externals, relpath, new_propval);
667 }
668 }
669 svn_pool_destroy(iterpool);
670
671 if (old_url)
672 SVN_ERR(svn_ra_reparent(ra_session, old_url, scratch_pool));
673
674 return SVN_NO_ERROR;
675 }
676
677
678
679 /* The guts of do_wc_to_wc_copies */
680 static svn_error_t *
do_wc_to_wc_copies_with_write_lock(svn_boolean_t * timestamp_sleep,const apr_array_header_t * copy_pairs,const char * dst_parent,svn_boolean_t metadata_only,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)681 do_wc_to_wc_copies_with_write_lock(svn_boolean_t *timestamp_sleep,
682 const apr_array_header_t *copy_pairs,
683 const char *dst_parent,
684 svn_boolean_t metadata_only,
685 svn_boolean_t pin_externals,
686 const apr_hash_t *externals_to_pin,
687 svn_client_ctx_t *ctx,
688 apr_pool_t *scratch_pool)
689 {
690 int i;
691 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
692 svn_error_t *err = SVN_NO_ERROR;
693
694 for (i = 0; i < copy_pairs->nelts; i++)
695 {
696 const char *dst_abspath;
697 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
698 svn_client__copy_pair_t *);
699 apr_hash_t *pinned_externals = NULL;
700
701 svn_pool_clear(iterpool);
702
703 /* Check for cancellation */
704 if (ctx->cancel_func)
705 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
706
707 if (pin_externals)
708 {
709 const char *repos_root_url;
710
711 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, NULL, &repos_root_url,
712 NULL, NULL, NULL, ctx->wc_ctx,
713 pair->src_abspath_or_url, FALSE,
714 scratch_pool, iterpool));
715 SVN_ERR(resolve_pinned_externals(&pinned_externals,
716 externals_to_pin, pair, NULL,
717 repos_root_url, ctx,
718 iterpool, iterpool));
719 }
720
721 /* Perform the copy */
722 dst_abspath = svn_dirent_join(pair->dst_parent_abspath, pair->base_name,
723 iterpool);
724 *timestamp_sleep = TRUE;
725 err = svn_wc_copy3(ctx->wc_ctx, pair->src_abspath_or_url, dst_abspath,
726 metadata_only,
727 ctx->cancel_func, ctx->cancel_baton,
728 ctx->notify_func2, ctx->notify_baton2, iterpool);
729 if (err)
730 break;
731
732 if (pinned_externals)
733 {
734 apr_hash_index_t *hi;
735
736 for (hi = apr_hash_first(iterpool, pinned_externals);
737 hi;
738 hi = apr_hash_next(hi))
739 {
740 const char *dst_relpath = apr_hash_this_key(hi);
741 svn_string_t *externals_propval = apr_hash_this_val(hi);
742 const char *local_abspath;
743
744 local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
745 dst_relpath, iterpool);
746 /* ### use a work queue? */
747 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
748 SVN_PROP_EXTERNALS, externals_propval,
749 svn_depth_empty, TRUE /* skip_checks */,
750 NULL /* changelist_filter */,
751 ctx->cancel_func, ctx->cancel_baton,
752 NULL, NULL, /* no extra notification */
753 iterpool));
754 }
755 }
756 }
757 svn_pool_destroy(iterpool);
758
759 SVN_ERR(err);
760 return SVN_NO_ERROR;
761 }
762
763 /* Copy each COPY_PAIR->SRC into COPY_PAIR->DST. Use POOL for temporary
764 allocations. */
765 static svn_error_t *
do_wc_to_wc_copies(svn_boolean_t * timestamp_sleep,const apr_array_header_t * copy_pairs,svn_boolean_t metadata_only,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,svn_client_ctx_t * ctx,apr_pool_t * pool)766 do_wc_to_wc_copies(svn_boolean_t *timestamp_sleep,
767 const apr_array_header_t *copy_pairs,
768 svn_boolean_t metadata_only,
769 svn_boolean_t pin_externals,
770 const apr_hash_t *externals_to_pin,
771 svn_client_ctx_t *ctx,
772 apr_pool_t *pool)
773 {
774 const char *dst_parent, *dst_parent_abspath;
775
776 SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &dst_parent, NULL, pool));
777 if (copy_pairs->nelts == 1)
778 dst_parent = svn_dirent_dirname(dst_parent, pool);
779
780 SVN_ERR(svn_dirent_get_absolute(&dst_parent_abspath, dst_parent, pool));
781
782 SVN_WC__CALL_WITH_WRITE_LOCK(
783 do_wc_to_wc_copies_with_write_lock(timestamp_sleep, copy_pairs, dst_parent,
784 metadata_only, pin_externals,
785 externals_to_pin, ctx, pool),
786 ctx->wc_ctx, dst_parent_abspath, FALSE, pool);
787
788 return SVN_NO_ERROR;
789 }
790
791 /* The locked bit of do_wc_to_wc_moves. */
792 static svn_error_t *
do_wc_to_wc_moves_with_locks2(svn_client__copy_pair_t * pair,const char * dst_parent_abspath,svn_boolean_t lock_src,svn_boolean_t lock_dst,svn_boolean_t allow_mixed_revisions,svn_boolean_t metadata_only,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)793 do_wc_to_wc_moves_with_locks2(svn_client__copy_pair_t *pair,
794 const char *dst_parent_abspath,
795 svn_boolean_t lock_src,
796 svn_boolean_t lock_dst,
797 svn_boolean_t allow_mixed_revisions,
798 svn_boolean_t metadata_only,
799 svn_client_ctx_t *ctx,
800 apr_pool_t *scratch_pool)
801 {
802 const char *dst_abspath;
803
804 dst_abspath = svn_dirent_join(dst_parent_abspath, pair->base_name,
805 scratch_pool);
806
807 SVN_ERR(svn_wc__move2(ctx->wc_ctx, pair->src_abspath_or_url,
808 dst_abspath, metadata_only,
809 allow_mixed_revisions,
810 ctx->cancel_func, ctx->cancel_baton,
811 ctx->notify_func2, ctx->notify_baton2,
812 scratch_pool));
813
814 return SVN_NO_ERROR;
815 }
816
817 /* Wrapper to add an optional second lock */
818 static svn_error_t *
do_wc_to_wc_moves_with_locks1(svn_client__copy_pair_t * pair,const char * dst_parent_abspath,svn_boolean_t lock_src,svn_boolean_t lock_dst,svn_boolean_t allow_mixed_revisions,svn_boolean_t metadata_only,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)819 do_wc_to_wc_moves_with_locks1(svn_client__copy_pair_t *pair,
820 const char *dst_parent_abspath,
821 svn_boolean_t lock_src,
822 svn_boolean_t lock_dst,
823 svn_boolean_t allow_mixed_revisions,
824 svn_boolean_t metadata_only,
825 svn_client_ctx_t *ctx,
826 apr_pool_t *scratch_pool)
827 {
828 if (lock_dst)
829 SVN_WC__CALL_WITH_WRITE_LOCK(
830 do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
831 lock_dst, allow_mixed_revisions,
832 metadata_only,
833 ctx, scratch_pool),
834 ctx->wc_ctx, dst_parent_abspath, FALSE, scratch_pool);
835 else
836 SVN_ERR(do_wc_to_wc_moves_with_locks2(pair, dst_parent_abspath, lock_src,
837 lock_dst, allow_mixed_revisions,
838 metadata_only,
839 ctx, scratch_pool));
840
841 return SVN_NO_ERROR;
842 }
843
844 /* Move each COPY_PAIR->SRC into COPY_PAIR->DST, deleting COPY_PAIR->SRC
845 afterwards. Use POOL for temporary allocations. */
846 static svn_error_t *
do_wc_to_wc_moves(svn_boolean_t * timestamp_sleep,const apr_array_header_t * copy_pairs,const char * dst_path,svn_boolean_t allow_mixed_revisions,svn_boolean_t metadata_only,svn_client_ctx_t * ctx,apr_pool_t * pool)847 do_wc_to_wc_moves(svn_boolean_t *timestamp_sleep,
848 const apr_array_header_t *copy_pairs,
849 const char *dst_path,
850 svn_boolean_t allow_mixed_revisions,
851 svn_boolean_t metadata_only,
852 svn_client_ctx_t *ctx,
853 apr_pool_t *pool)
854 {
855 int i;
856 apr_pool_t *iterpool = svn_pool_create(pool);
857 svn_error_t *err = SVN_NO_ERROR;
858
859 for (i = 0; i < copy_pairs->nelts; i++)
860 {
861 const char *src_parent_abspath;
862 svn_boolean_t lock_src, lock_dst;
863 const char *src_wcroot_abspath;
864 const char *dst_wcroot_abspath;
865
866 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
867 svn_client__copy_pair_t *);
868 svn_pool_clear(iterpool);
869
870 /* Check for cancellation */
871 if (ctx->cancel_func)
872 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
873
874 src_parent_abspath = svn_dirent_dirname(pair->src_abspath_or_url,
875 iterpool);
876
877 SVN_ERR(svn_wc__get_wcroot(&src_wcroot_abspath,
878 ctx->wc_ctx, src_parent_abspath,
879 iterpool, iterpool));
880 SVN_ERR(svn_wc__get_wcroot(&dst_wcroot_abspath,
881 ctx->wc_ctx, pair->dst_parent_abspath,
882 iterpool, iterpool));
883
884 /* We now need to lock the right combination of batons.
885 Four cases:
886 1) src_parent == dst_parent
887 2) src_parent is parent of dst_parent
888 3) dst_parent is parent of src_parent
889 4) src_parent and dst_parent are disjoint
890 We can handle 1) as either 2) or 3) */
891 if (strcmp(src_parent_abspath, pair->dst_parent_abspath) == 0
892 || (svn_dirent_is_child(src_parent_abspath, pair->dst_parent_abspath,
893 NULL)
894 && !svn_dirent_is_child(src_parent_abspath, dst_wcroot_abspath,
895 NULL)))
896 {
897 lock_src = TRUE;
898 lock_dst = FALSE;
899 }
900 else if (svn_dirent_is_child(pair->dst_parent_abspath,
901 src_parent_abspath, NULL)
902 && !svn_dirent_is_child(pair->dst_parent_abspath,
903 src_wcroot_abspath, NULL))
904 {
905 lock_src = FALSE;
906 lock_dst = TRUE;
907 }
908 else
909 {
910 lock_src = TRUE;
911 lock_dst = TRUE;
912 }
913
914 *timestamp_sleep = TRUE;
915
916 /* Perform the copy and then the delete. */
917 if (lock_src)
918 SVN_WC__CALL_WITH_WRITE_LOCK(
919 do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
920 lock_src, lock_dst,
921 allow_mixed_revisions,
922 metadata_only,
923 ctx, iterpool),
924 ctx->wc_ctx, src_parent_abspath,
925 FALSE, iterpool);
926 else
927 SVN_ERR(do_wc_to_wc_moves_with_locks1(pair, pair->dst_parent_abspath,
928 lock_src, lock_dst,
929 allow_mixed_revisions,
930 metadata_only,
931 ctx, iterpool));
932
933 }
934 svn_pool_destroy(iterpool);
935
936 return svn_error_trace(err);
937 }
938
939 /* Verify that the destinations stored in COPY_PAIRS are valid working copy
940 destinations and set pair->dst_parent_abspath and pair->base_name for each
941 item to the resulting location if they do */
942 static svn_error_t *
verify_wc_dsts(const apr_array_header_t * copy_pairs,svn_boolean_t make_parents,svn_boolean_t is_move,svn_boolean_t metadata_only,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)943 verify_wc_dsts(const apr_array_header_t *copy_pairs,
944 svn_boolean_t make_parents,
945 svn_boolean_t is_move,
946 svn_boolean_t metadata_only,
947 svn_client_ctx_t *ctx,
948 apr_pool_t *result_pool,
949 apr_pool_t *scratch_pool)
950 {
951 int i;
952 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
953
954 /* Check that DST does not exist, but its parent does */
955 for (i = 0; i < copy_pairs->nelts; i++)
956 {
957 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
958 svn_client__copy_pair_t *);
959 svn_node_kind_t dst_kind, dst_parent_kind;
960
961 svn_pool_clear(iterpool);
962
963 /* If DST_PATH does not exist, then its basename will become a new
964 file or dir added to its parent (possibly an implicit '.').
965 Else, just error out. */
966 SVN_ERR(svn_wc_read_kind2(&dst_kind, ctx->wc_ctx,
967 pair->dst_abspath_or_url,
968 FALSE /* show_deleted */,
969 TRUE /* show_hidden */,
970 iterpool));
971 if (dst_kind != svn_node_none)
972 {
973 svn_boolean_t is_excluded;
974 svn_boolean_t is_server_excluded;
975
976 SVN_ERR(svn_wc__node_is_not_present(NULL, &is_excluded,
977 &is_server_excluded, ctx->wc_ctx,
978 pair->dst_abspath_or_url, FALSE,
979 iterpool));
980
981 if (is_excluded || is_server_excluded)
982 {
983 return svn_error_createf(
984 SVN_ERR_WC_OBSTRUCTED_UPDATE,
985 NULL, _("Path '%s' exists, but is excluded"),
986 svn_dirent_local_style(pair->dst_abspath_or_url, iterpool));
987 }
988 else
989 return svn_error_createf(
990 SVN_ERR_ENTRY_EXISTS, NULL,
991 _("Path '%s' already exists"),
992 svn_dirent_local_style(pair->dst_abspath_or_url,
993 scratch_pool));
994 }
995
996 /* Check that there is no unversioned obstruction */
997 if (metadata_only)
998 dst_kind = svn_node_none;
999 else
1000 SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
1001 iterpool));
1002
1003 if (dst_kind != svn_node_none)
1004 {
1005 if (is_move
1006 && copy_pairs->nelts == 1
1007 && strcmp(svn_dirent_dirname(pair->src_abspath_or_url, iterpool),
1008 svn_dirent_dirname(pair->dst_abspath_or_url,
1009 iterpool)) == 0)
1010 {
1011 const char *dst;
1012 char *dst_apr;
1013 apr_status_t apr_err;
1014 /* We have a rename inside a directory, which might collide
1015 just because the case insensivity of the filesystem makes
1016 the source match the destination. */
1017
1018 SVN_ERR(svn_path_cstring_from_utf8(&dst,
1019 pair->dst_abspath_or_url,
1020 scratch_pool));
1021
1022 apr_err = apr_filepath_merge(&dst_apr, NULL, dst,
1023 APR_FILEPATH_TRUENAME, iterpool);
1024
1025 if (!apr_err)
1026 {
1027 /* And now bring it back to our canonical format */
1028 SVN_ERR(svn_path_cstring_to_utf8(&dst, dst_apr, iterpool));
1029 dst = svn_dirent_canonicalize(dst, iterpool);
1030 }
1031 /* else: Don't report this error; just report the normal error */
1032
1033 if (!apr_err && strcmp(dst, pair->src_abspath_or_url) == 0)
1034 {
1035 /* Ok, we have a single case only rename. Get out of here */
1036 svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
1037 pair->dst_abspath_or_url, result_pool);
1038
1039 svn_pool_destroy(iterpool);
1040 return SVN_NO_ERROR;
1041 }
1042 }
1043
1044 return svn_error_createf(
1045 SVN_ERR_ENTRY_EXISTS, NULL,
1046 _("Path '%s' already exists as unversioned node"),
1047 svn_dirent_local_style(pair->dst_abspath_or_url,
1048 scratch_pool));
1049 }
1050
1051 svn_dirent_split(&pair->dst_parent_abspath, &pair->base_name,
1052 pair->dst_abspath_or_url, result_pool);
1053
1054 /* Make sure the destination parent is a directory and produce a clear
1055 error message if it is not. */
1056 SVN_ERR(svn_wc_read_kind2(&dst_parent_kind,
1057 ctx->wc_ctx, pair->dst_parent_abspath,
1058 FALSE, TRUE,
1059 iterpool));
1060 if (dst_parent_kind == svn_node_none)
1061 {
1062 if (make_parents)
1063 SVN_ERR(svn_client__make_local_parents(pair->dst_parent_abspath,
1064 TRUE, ctx, iterpool));
1065 else
1066 {
1067 SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
1068 &dst_parent_kind, scratch_pool));
1069 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1070 (dst_parent_kind == svn_node_dir)
1071 ? _("Directory '%s' is not under "
1072 "version control")
1073 : _("Path '%s' is not a directory"),
1074 svn_dirent_local_style(
1075 pair->dst_parent_abspath,
1076 scratch_pool));
1077 }
1078 }
1079 else if (dst_parent_kind != svn_node_dir)
1080 {
1081 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1082 _("Path '%s' is not a directory"),
1083 svn_dirent_local_style(
1084 pair->dst_parent_abspath, scratch_pool));
1085 }
1086
1087 SVN_ERR(svn_io_check_path(pair->dst_parent_abspath,
1088 &dst_parent_kind, scratch_pool));
1089
1090 if (dst_parent_kind != svn_node_dir)
1091 return svn_error_createf(SVN_ERR_WC_MISSING, NULL,
1092 _("Path '%s' is not a directory"),
1093 svn_dirent_local_style(
1094 pair->dst_parent_abspath, scratch_pool));
1095 }
1096
1097 svn_pool_destroy(iterpool);
1098
1099 return SVN_NO_ERROR;
1100 }
1101
1102 static svn_error_t *
verify_wc_srcs_and_dsts(const apr_array_header_t * copy_pairs,svn_boolean_t make_parents,svn_boolean_t is_move,svn_boolean_t metadata_only,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1103 verify_wc_srcs_and_dsts(const apr_array_header_t *copy_pairs,
1104 svn_boolean_t make_parents,
1105 svn_boolean_t is_move,
1106 svn_boolean_t metadata_only,
1107 svn_client_ctx_t *ctx,
1108 apr_pool_t *result_pool,
1109 apr_pool_t *scratch_pool)
1110 {
1111 int i;
1112 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1113
1114 /* Check that all of our SRCs exist. */
1115 for (i = 0; i < copy_pairs->nelts; i++)
1116 {
1117 svn_boolean_t deleted_ok;
1118 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1119 svn_client__copy_pair_t *);
1120 svn_pool_clear(iterpool);
1121
1122 deleted_ok = (pair->src_peg_revision.kind == svn_opt_revision_base
1123 || pair->src_op_revision.kind == svn_opt_revision_base);
1124
1125 /* Verify that SRC_PATH exists. */
1126 SVN_ERR(svn_wc_read_kind2(&pair->src_kind, ctx->wc_ctx,
1127 pair->src_abspath_or_url,
1128 deleted_ok, FALSE, iterpool));
1129 if (pair->src_kind == svn_node_none)
1130 return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
1131 _("Path '%s' does not exist"),
1132 svn_dirent_local_style(
1133 pair->src_abspath_or_url,
1134 scratch_pool));
1135 }
1136
1137 SVN_ERR(verify_wc_dsts(copy_pairs, make_parents, is_move, metadata_only, ctx,
1138 result_pool, iterpool));
1139
1140 svn_pool_destroy(iterpool);
1141
1142 return SVN_NO_ERROR;
1143 }
1144
1145
1146 /* Path-specific state used as part of path_driver_cb_baton. */
1147 typedef struct path_driver_info_t
1148 {
1149 const char *src_url;
1150 const char *src_path;
1151 const char *dst_path;
1152 svn_node_kind_t src_kind;
1153 svn_revnum_t src_revnum;
1154 svn_boolean_t resurrection;
1155 svn_boolean_t dir_add;
1156 svn_string_t *mergeinfo; /* the new mergeinfo for the target */
1157 svn_string_t *externals; /* new externals definitions for the target */
1158 svn_boolean_t only_pin_externals;
1159 } path_driver_info_t;
1160
1161
1162 /* The baton used with the path_driver_cb_func() callback for a copy
1163 or move operation. */
1164 struct path_driver_cb_baton
1165 {
1166 /* The editor (and its state) used to perform the operation. */
1167 const svn_delta_editor_t *editor;
1168 void *edit_baton;
1169
1170 /* A hash of path -> path_driver_info_t *'s. */
1171 apr_hash_t *action_hash;
1172
1173 /* Whether the operation is a move or copy. */
1174 svn_boolean_t is_move;
1175 };
1176
1177 static svn_error_t *
path_driver_cb_func(void ** dir_baton,void * parent_baton,void * callback_baton,const char * path,apr_pool_t * pool)1178 path_driver_cb_func(void **dir_baton,
1179 void *parent_baton,
1180 void *callback_baton,
1181 const char *path,
1182 apr_pool_t *pool)
1183 {
1184 struct path_driver_cb_baton *cb_baton = callback_baton;
1185 svn_boolean_t do_delete = FALSE, do_add = FALSE;
1186 path_driver_info_t *path_info = svn_hash_gets(cb_baton->action_hash, path);
1187
1188 /* Initialize return value. */
1189 *dir_baton = NULL;
1190
1191 /* This function should never get an empty PATH. We can neither
1192 create nor delete the empty PATH, so if someone is calling us
1193 with such, the code is just plain wrong. */
1194 SVN_ERR_ASSERT(! svn_path_is_empty(path));
1195
1196 /* Check to see if we need to add the path as a parent directory. */
1197 if (path_info->dir_add)
1198 {
1199 return cb_baton->editor->add_directory(path, parent_baton, NULL,
1200 SVN_INVALID_REVNUM, pool,
1201 dir_baton);
1202 }
1203
1204 /* If this is a resurrection, we know the source and dest paths are
1205 the same, and that our driver will only be calling us once. */
1206 if (path_info->resurrection)
1207 {
1208 /* If this is a move, we do nothing. Otherwise, we do the copy. */
1209 if (! cb_baton->is_move)
1210 do_add = TRUE;
1211 }
1212 /* Not a resurrection. */
1213 else
1214 {
1215 /* If this is a move, we check PATH to see if it is the source
1216 or the destination of the move. */
1217 if (cb_baton->is_move)
1218 {
1219 if (strcmp(path_info->src_path, path) == 0)
1220 do_delete = TRUE;
1221 else
1222 do_add = TRUE;
1223 }
1224 /* Not a move? This must just be the copy addition. */
1225 else
1226 {
1227 do_add = !path_info->only_pin_externals;
1228 }
1229 }
1230
1231 if (do_delete)
1232 {
1233 SVN_ERR(cb_baton->editor->delete_entry(path, SVN_INVALID_REVNUM,
1234 parent_baton, pool));
1235 }
1236 if (do_add)
1237 {
1238 SVN_ERR(svn_path_check_valid(path, pool));
1239
1240 if (path_info->src_kind == svn_node_file)
1241 {
1242 void *file_baton;
1243 SVN_ERR(cb_baton->editor->add_file(path, parent_baton,
1244 path_info->src_url,
1245 path_info->src_revnum,
1246 pool, &file_baton));
1247 if (path_info->mergeinfo)
1248 SVN_ERR(cb_baton->editor->change_file_prop(file_baton,
1249 SVN_PROP_MERGEINFO,
1250 path_info->mergeinfo,
1251 pool));
1252 SVN_ERR(cb_baton->editor->close_file(file_baton, NULL, pool));
1253 }
1254 else
1255 {
1256 SVN_ERR(cb_baton->editor->add_directory(path, parent_baton,
1257 path_info->src_url,
1258 path_info->src_revnum,
1259 pool, dir_baton));
1260 if (path_info->mergeinfo)
1261 SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton,
1262 SVN_PROP_MERGEINFO,
1263 path_info->mergeinfo,
1264 pool));
1265 }
1266 }
1267
1268 if (path_info->externals)
1269 {
1270 if (*dir_baton == NULL)
1271 SVN_ERR(cb_baton->editor->open_directory(path, parent_baton,
1272 SVN_INVALID_REVNUM,
1273 pool, dir_baton));
1274
1275 SVN_ERR(cb_baton->editor->change_dir_prop(*dir_baton, SVN_PROP_EXTERNALS,
1276 path_info->externals, pool));
1277 }
1278
1279 return SVN_NO_ERROR;
1280 }
1281
1282
1283 /* Starting with the path DIR relative to the RA_SESSION's session
1284 URL, work up through DIR's parents until an existing node is found.
1285 Push each nonexistent path onto the array NEW_DIRS, allocating in
1286 POOL. Raise an error if the existing node is not a directory.
1287
1288 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
1289 ### implementation susceptible to race conditions. */
1290 static svn_error_t *
find_absent_parents1(svn_ra_session_t * ra_session,const char * dir,apr_array_header_t * new_dirs,apr_pool_t * pool)1291 find_absent_parents1(svn_ra_session_t *ra_session,
1292 const char *dir,
1293 apr_array_header_t *new_dirs,
1294 apr_pool_t *pool)
1295 {
1296 svn_node_kind_t kind;
1297 apr_pool_t *iterpool = svn_pool_create(pool);
1298
1299 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM, &kind,
1300 iterpool));
1301
1302 while (kind == svn_node_none)
1303 {
1304 svn_pool_clear(iterpool);
1305
1306 APR_ARRAY_PUSH(new_dirs, const char *) = dir;
1307 dir = svn_dirent_dirname(dir, pool);
1308
1309 SVN_ERR(svn_ra_check_path(ra_session, dir, SVN_INVALID_REVNUM,
1310 &kind, iterpool));
1311 }
1312
1313 if (kind != svn_node_dir)
1314 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1315 _("Path '%s' already exists, but is not a "
1316 "directory"), dir);
1317
1318 svn_pool_destroy(iterpool);
1319 return SVN_NO_ERROR;
1320 }
1321
1322 /* Starting with the URL *TOP_DST_URL which is also the root of
1323 RA_SESSION, work up through its parents until an existing node is
1324 found. Push each nonexistent URL onto the array NEW_DIRS,
1325 allocating in POOL. Raise an error if the existing node is not a
1326 directory.
1327
1328 Set *TOP_DST_URL and the RA session's root to the existing node's URL.
1329
1330 ### Multiple requests for HEAD (SVN_INVALID_REVNUM) make this
1331 ### implementation susceptible to race conditions. */
1332 static svn_error_t *
find_absent_parents2(svn_ra_session_t * ra_session,const char ** top_dst_url,apr_array_header_t * new_dirs,apr_pool_t * pool)1333 find_absent_parents2(svn_ra_session_t *ra_session,
1334 const char **top_dst_url,
1335 apr_array_header_t *new_dirs,
1336 apr_pool_t *pool)
1337 {
1338 const char *root_url = *top_dst_url;
1339 svn_node_kind_t kind;
1340
1341 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1342 pool));
1343
1344 while (kind == svn_node_none)
1345 {
1346 APR_ARRAY_PUSH(new_dirs, const char *) = root_url;
1347 root_url = svn_uri_dirname(root_url, pool);
1348
1349 SVN_ERR(svn_ra_reparent(ra_session, root_url, pool));
1350 SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind,
1351 pool));
1352 }
1353
1354 if (kind != svn_node_dir)
1355 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1356 _("Path '%s' already exists, but is not a directory"),
1357 root_url);
1358
1359 *top_dst_url = root_url;
1360 return SVN_NO_ERROR;
1361 }
1362
1363 /* Queue property changes for pinning svn:externals properties set on
1364 * descendants of the path corresponding to PARENT_INFO. PINNED_EXTERNALS
1365 * is keyed by the relative path of each descendant which should have some
1366 * or all of its externals pinned, with the corresponding pinned svn:externals
1367 * properties as values. Property changes are queued in a new list of path
1368 * infos *NEW_PATH_INFOS, or in an existing item of the PATH_INFOS list if an
1369 * existing item is found for the descendant. Allocate results in RESULT_POOL.
1370 * Use SCRATCH_POOL for temporary allocations. */
1371 static svn_error_t *
queue_externals_change_path_infos(apr_array_header_t * new_path_infos,apr_array_header_t * path_infos,apr_hash_t * pinned_externals,path_driver_info_t * parent_info,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1372 queue_externals_change_path_infos(apr_array_header_t *new_path_infos,
1373 apr_array_header_t *path_infos,
1374 apr_hash_t *pinned_externals,
1375 path_driver_info_t *parent_info,
1376 apr_pool_t *result_pool,
1377 apr_pool_t *scratch_pool)
1378 {
1379 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1380 apr_hash_index_t *hi;
1381
1382 for (hi = apr_hash_first(scratch_pool, pinned_externals);
1383 hi;
1384 hi = apr_hash_next(hi))
1385 {
1386 const char *dst_relpath = apr_hash_this_key(hi);
1387 svn_string_t *externals_prop = apr_hash_this_val(hi);
1388 const char *src_url;
1389 path_driver_info_t *info;
1390 int i;
1391
1392 svn_pool_clear(iterpool);
1393
1394 src_url = svn_path_url_add_component2(parent_info->src_url,
1395 dst_relpath, iterpool);
1396
1397 /* Try to find a path info the external change can be applied to. */
1398 info = NULL;
1399 for (i = 0; i < path_infos->nelts; i++)
1400 {
1401 path_driver_info_t *existing_info;
1402
1403 existing_info = APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1404 if (strcmp(src_url, existing_info->src_url) == 0)
1405 {
1406 info = existing_info;
1407 break;
1408 }
1409 }
1410
1411 if (info == NULL)
1412 {
1413 /* A copied-along child needs its externals pinned.
1414 Create a new path info for this property change. */
1415 info = apr_pcalloc(result_pool, sizeof(*info));
1416 info->src_url = svn_path_url_add_component2(
1417 parent_info->src_url, dst_relpath,
1418 result_pool);
1419 info->src_path = NULL; /* Only needed on copied dirs */
1420 info->dst_path = svn_relpath_join(parent_info->dst_path,
1421 dst_relpath,
1422 result_pool);
1423 info->src_kind = svn_node_dir;
1424 info->only_pin_externals = TRUE;
1425 APR_ARRAY_PUSH(new_path_infos, path_driver_info_t *) = info;
1426 }
1427
1428 info->externals = externals_prop;
1429 }
1430
1431 svn_pool_destroy(iterpool);
1432
1433 return SVN_NO_ERROR;
1434 }
1435
1436 static svn_error_t *
repos_to_repos_copy(const apr_array_header_t * copy_pairs,svn_boolean_t make_parents,const apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_client_ctx_t * ctx,svn_boolean_t is_move,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,apr_pool_t * pool)1437 repos_to_repos_copy(const apr_array_header_t *copy_pairs,
1438 svn_boolean_t make_parents,
1439 const apr_hash_t *revprop_table,
1440 svn_commit_callback2_t commit_callback,
1441 void *commit_baton,
1442 svn_client_ctx_t *ctx,
1443 svn_boolean_t is_move,
1444 svn_boolean_t pin_externals,
1445 const apr_hash_t *externals_to_pin,
1446 apr_pool_t *pool)
1447 {
1448 svn_error_t *err;
1449 apr_array_header_t *paths = apr_array_make(pool, 2 * copy_pairs->nelts,
1450 sizeof(const char *));
1451 apr_hash_t *action_hash = apr_hash_make(pool);
1452 apr_array_header_t *path_infos;
1453 const char *top_url, *top_url_all, *top_url_dst;
1454 const char *message, *repos_root;
1455 svn_ra_session_t *ra_session = NULL;
1456 const svn_delta_editor_t *editor;
1457 void *edit_baton;
1458 struct path_driver_cb_baton cb_baton;
1459 apr_array_header_t *new_dirs = NULL;
1460 apr_hash_t *commit_revprops;
1461 apr_array_header_t *pin_externals_only_infos = NULL;
1462 int i;
1463 svn_client__copy_pair_t *first_pair =
1464 APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
1465
1466 /* Open an RA session to the first copy pair's destination. We'll
1467 be verifying that every one of our copy source and destination
1468 URLs is or is beneath this sucker's repository root URL as a form
1469 of a cheap(ish) sanity check. */
1470 SVN_ERR(svn_client_open_ra_session2(&ra_session,
1471 first_pair->src_abspath_or_url, NULL,
1472 ctx, pool, pool));
1473 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool));
1474
1475 /* Verify that sources and destinations are all at or under
1476 REPOS_ROOT. While here, create a path_info struct for each
1477 src/dst pair and initialize portions of it with normalized source
1478 location information. */
1479 path_infos = apr_array_make(pool, copy_pairs->nelts,
1480 sizeof(path_driver_info_t *));
1481 for (i = 0; i < copy_pairs->nelts; i++)
1482 {
1483 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1484 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1485 svn_client__copy_pair_t *);
1486 apr_hash_t *mergeinfo;
1487
1488 /* Are the source and destination URLs at or under REPOS_ROOT? */
1489 if (! (svn_uri__is_ancestor(repos_root, pair->src_abspath_or_url)
1490 && svn_uri__is_ancestor(repos_root, pair->dst_abspath_or_url)))
1491 return svn_error_create
1492 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
1493 _("Source and destination URLs appear not to point to the "
1494 "same repository."));
1495
1496 /* Run the history function to get the source's URL and revnum in the
1497 operational revision. */
1498 SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
1499 SVN_ERR(svn_client__repos_locations(&pair->src_abspath_or_url,
1500 &pair->src_revnum,
1501 NULL, NULL,
1502 ra_session,
1503 pair->src_abspath_or_url,
1504 &pair->src_peg_revision,
1505 &pair->src_op_revision, NULL,
1506 ctx, pool));
1507
1508 /* Go ahead and grab mergeinfo from the source, too. */
1509 SVN_ERR(svn_ra_reparent(ra_session, pair->src_abspath_or_url, pool));
1510 SVN_ERR(svn_client__get_repos_mergeinfo(
1511 &mergeinfo, ra_session,
1512 pair->src_abspath_or_url, pair->src_revnum,
1513 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
1514 if (mergeinfo)
1515 SVN_ERR(svn_mergeinfo_to_string(&info->mergeinfo, mergeinfo, pool));
1516
1517 /* Plop an INFO structure onto our array thereof. */
1518 info->src_url = pair->src_abspath_or_url;
1519 info->src_revnum = pair->src_revnum;
1520 info->resurrection = FALSE;
1521 APR_ARRAY_PUSH(path_infos, path_driver_info_t *) = info;
1522 }
1523
1524 /* If this is a move, we have to open our session to the longest
1525 path common to all SRC_URLS and DST_URLS in the repository so we
1526 can do existence checks on all paths, and so we can operate on
1527 all paths in the case of a move. But if this is *not* a move,
1528 then opening our session at the longest path common to sources
1529 *and* destinations might be an optimization when the user is
1530 authorized to access all that stuff, but could cause the
1531 operation to fail altogether otherwise. See issue #3242. */
1532 SVN_ERR(get_copy_pair_ancestors(copy_pairs, NULL, &top_url_dst, &top_url_all,
1533 pool));
1534 top_url = is_move ? top_url_all : top_url_dst;
1535
1536 /* Check each src/dst pair for resurrection, and verify that TOP_URL
1537 is anchored high enough to cover all the editor_t activities
1538 required for this operation. */
1539 for (i = 0; i < copy_pairs->nelts; i++)
1540 {
1541 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1542 svn_client__copy_pair_t *);
1543 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1544 path_driver_info_t *);
1545
1546 /* Source and destination are the same? It's a resurrection. */
1547 if (strcmp(pair->src_abspath_or_url, pair->dst_abspath_or_url) == 0)
1548 info->resurrection = TRUE;
1549
1550 /* We need to add each dst_URL, and (in a move) we'll need to
1551 delete each src_URL. Our selection of TOP_URL so far ensures
1552 that all our destination URLs (and source URLs, for moves)
1553 are at least as deep as TOP_URL, but we need to make sure
1554 that TOP_URL is an *ancestor* of all our to-be-edited paths.
1555
1556 Issue #683 is demonstrates this scenario. If you're
1557 resurrecting a deleted item like this: 'svn cp -rN src_URL
1558 dst_URL', then src_URL == dst_URL == top_url. In this
1559 situation, we want to open an RA session to be at least the
1560 *parent* of all three. */
1561 if ((strcmp(top_url, pair->dst_abspath_or_url) == 0)
1562 && (strcmp(top_url, repos_root) != 0))
1563 {
1564 top_url = svn_uri_dirname(top_url, pool);
1565 }
1566 if (is_move
1567 && (strcmp(top_url, pair->src_abspath_or_url) == 0)
1568 && (strcmp(top_url, repos_root) != 0))
1569 {
1570 top_url = svn_uri_dirname(top_url, pool);
1571 }
1572 }
1573
1574 /* Point the RA session to our current TOP_URL. */
1575 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1576
1577 /* If we're allowed to create nonexistent parent directories of our
1578 destinations, then make a list in NEW_DIRS of the parent
1579 directories of the destination that don't yet exist. */
1580 if (make_parents)
1581 {
1582 new_dirs = apr_array_make(pool, 0, sizeof(const char *));
1583
1584 /* If this is a move, TOP_URL is at least the common ancestor of
1585 all the paths (sources and destinations) involved. Assuming
1586 the sources exist (which is fair, because if they don't, this
1587 whole operation will fail anyway), TOP_URL must also exist.
1588 So it's the paths between TOP_URL and the destinations which
1589 we have to check for existence. But here, we take advantage
1590 of the knowledge of our caller. We know that if there are
1591 multiple copy/move operations being requested, then the
1592 destinations of the copies/moves will all be siblings of one
1593 another. Therefore, we need only to check for the
1594 nonexistent paths between TOP_URL and *one* of our
1595 destinations to find nonexistent parents of all of them. */
1596 if (is_move)
1597 {
1598 /* Imagine a situation where the user tries to copy an
1599 existing source directory to nonexistent directory with
1600 --parents options specified:
1601
1602 svn copy --parents URL/src URL/dst
1603
1604 where src exists and dst does not. If the dirname of the
1605 destination path is equal to TOP_URL,
1606 do not try to add dst to the NEW_DIRS list since it
1607 will be added to the commit items array later in this
1608 function. */
1609 const char *dir = svn_uri_skip_ancestor(
1610 top_url,
1611 svn_uri_dirname(first_pair->dst_abspath_or_url,
1612 pool),
1613 pool);
1614 if (dir && *dir)
1615 SVN_ERR(find_absent_parents1(ra_session, dir, new_dirs, pool));
1616 }
1617 /* If, however, this is *not* a move, TOP_URL only points to the
1618 common ancestor of our destination path(s), or possibly one
1619 level higher. We'll need to do an existence crawl toward the
1620 root of the repository, starting with one of our destinations
1621 (see "... take advantage of the knowledge of our caller ..."
1622 above), and possibly adjusting TOP_URL as we go. */
1623 else
1624 {
1625 apr_array_header_t *new_urls =
1626 apr_array_make(pool, 0, sizeof(const char *));
1627 SVN_ERR(find_absent_parents2(ra_session, &top_url, new_urls, pool));
1628
1629 /* Convert absolute URLs into relpaths relative to TOP_URL. */
1630 for (i = 0; i < new_urls->nelts; i++)
1631 {
1632 const char *new_url = APR_ARRAY_IDX(new_urls, i, const char *);
1633 const char *dir = svn_uri_skip_ancestor(top_url, new_url, pool);
1634
1635 APR_ARRAY_PUSH(new_dirs, const char *) = dir;
1636 }
1637 }
1638 }
1639
1640 /* For each src/dst pair, check to see if that SRC_URL is a child of
1641 the DST_URL (excepting the case where DST_URL is the repo root).
1642 If it is, and the parent of DST_URL is the current TOP_URL, then we
1643 need to reparent the session one directory higher, the parent of
1644 the DST_URL. */
1645 for (i = 0; i < copy_pairs->nelts; i++)
1646 {
1647 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
1648 svn_client__copy_pair_t *);
1649 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1650 path_driver_info_t *);
1651 const char *relpath = svn_uri_skip_ancestor(pair->dst_abspath_or_url,
1652 pair->src_abspath_or_url,
1653 pool);
1654
1655 if ((strcmp(pair->dst_abspath_or_url, repos_root) != 0)
1656 && (relpath != NULL && *relpath != '\0'))
1657 {
1658 info->resurrection = TRUE;
1659 top_url = svn_uri_get_longest_ancestor(
1660 top_url,
1661 svn_uri_dirname(pair->dst_abspath_or_url, pool),
1662 pool);
1663 SVN_ERR(svn_ra_reparent(ra_session, top_url, pool));
1664 }
1665 }
1666
1667 /* Get the portions of the SRC and DST URLs that are relative to
1668 TOP_URL (URI-decoding them while we're at it), verify that the
1669 source exists and the proposed destination does not, and toss
1670 what we've learned into the INFO array. (For copies -- that is,
1671 non-moves -- the relative source URL NULL because it isn't a
1672 child of the TOP_URL at all. That's okay, we'll deal with
1673 it.) */
1674 for (i = 0; i < copy_pairs->nelts; i++)
1675 {
1676 svn_client__copy_pair_t *pair =
1677 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
1678 path_driver_info_t *info =
1679 APR_ARRAY_IDX(path_infos, i, path_driver_info_t *);
1680 svn_node_kind_t dst_kind;
1681 const char *src_rel, *dst_rel;
1682
1683 src_rel = svn_uri_skip_ancestor(top_url, pair->src_abspath_or_url, pool);
1684 if (src_rel)
1685 {
1686 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
1687 &info->src_kind, pool));
1688 }
1689 else
1690 {
1691 const char *old_url;
1692
1693 src_rel = NULL;
1694 SVN_ERR_ASSERT(! is_move);
1695
1696 SVN_ERR(svn_client__ensure_ra_session_url(&old_url, ra_session,
1697 pair->src_abspath_or_url,
1698 pool));
1699 SVN_ERR(svn_ra_check_path(ra_session, "", pair->src_revnum,
1700 &info->src_kind, pool));
1701 SVN_ERR(svn_ra_reparent(ra_session, old_url, pool));
1702 }
1703 if (info->src_kind == svn_node_none)
1704 return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
1705 _("Path '%s' does not exist in revision %ld"),
1706 pair->src_abspath_or_url, pair->src_revnum);
1707
1708 /* Figure out the basename that will result from this operation,
1709 and ensure that we aren't trying to overwrite existing paths. */
1710 dst_rel = svn_uri_skip_ancestor(top_url, pair->dst_abspath_or_url, pool);
1711 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
1712 &dst_kind, pool));
1713 if (dst_kind != svn_node_none)
1714 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
1715 _("Path '%s' already exists"),
1716 pair->dst_abspath_or_url);
1717
1718 /* More info for our INFO structure. */
1719 info->src_path = src_rel; /* May be NULL, if outside RA session scope */
1720 info->dst_path = dst_rel;
1721
1722 svn_hash_sets(action_hash, info->dst_path, info);
1723 if (is_move && (! info->resurrection))
1724 svn_hash_sets(action_hash, info->src_path, info);
1725
1726 if (pin_externals)
1727 {
1728 apr_hash_t *pinned_externals;
1729
1730 SVN_ERR(resolve_pinned_externals(&pinned_externals,
1731 externals_to_pin, pair,
1732 ra_session, repos_root,
1733 ctx, pool, pool));
1734 if (pin_externals_only_infos == NULL)
1735 {
1736 pin_externals_only_infos =
1737 apr_array_make(pool, 0, sizeof(path_driver_info_t *));
1738 }
1739 SVN_ERR(queue_externals_change_path_infos(pin_externals_only_infos,
1740 path_infos,
1741 pinned_externals,
1742 info, pool, pool));
1743 }
1744 }
1745
1746 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
1747 {
1748 /* Produce a list of new paths to add, and provide it to the
1749 mechanism used to acquire a log message. */
1750 svn_client_commit_item3_t *item;
1751 const char *tmp_file;
1752 apr_array_header_t *commit_items
1753 = apr_array_make(pool, 2 * copy_pairs->nelts, sizeof(item));
1754
1755 /* Add any intermediate directories to the message */
1756 if (make_parents)
1757 {
1758 for (i = 0; i < new_dirs->nelts; i++)
1759 {
1760 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1761
1762 item = svn_client_commit_item3_create(pool);
1763 item->url = svn_path_url_add_component2(top_url, relpath, pool);
1764 item->kind = svn_node_dir;
1765 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
1766 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1767 }
1768 }
1769
1770 for (i = 0; i < path_infos->nelts; i++)
1771 {
1772 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1773 path_driver_info_t *);
1774
1775 item = svn_client_commit_item3_create(pool);
1776 item->url = svn_path_url_add_component2(top_url, info->dst_path,
1777 pool);
1778 item->kind = info->src_kind;
1779 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD
1780 | SVN_CLIENT_COMMIT_ITEM_IS_COPY;
1781 item->copyfrom_url = info->src_url;
1782 item->copyfrom_rev = info->src_revnum;
1783 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1784
1785 if (is_move && (! info->resurrection))
1786 {
1787 item = svn_client_commit_item3_create(pool);
1788 item->url = svn_path_url_add_component2(top_url, info->src_path,
1789 pool);
1790 item->kind = info->src_kind;
1791 item->state_flags = SVN_CLIENT_COMMIT_ITEM_DELETE;
1792 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1793 }
1794 }
1795
1796 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
1797 ctx, pool));
1798 if (! message)
1799 return SVN_NO_ERROR;
1800 }
1801 else
1802 message = "";
1803
1804 /* Setup our PATHS for the path-based editor drive. */
1805 /* First any intermediate directories. */
1806 if (make_parents)
1807 {
1808 for (i = 0; i < new_dirs->nelts; i++)
1809 {
1810 const char *relpath = APR_ARRAY_IDX(new_dirs, i, const char *);
1811 path_driver_info_t *info = apr_pcalloc(pool, sizeof(*info));
1812
1813 info->dst_path = relpath;
1814 info->dir_add = TRUE;
1815
1816 APR_ARRAY_PUSH(paths, const char *) = relpath;
1817 svn_hash_sets(action_hash, relpath, info);
1818 }
1819 }
1820
1821 /* Then our copy destinations and move sources (if any). */
1822 for (i = 0; i < path_infos->nelts; i++)
1823 {
1824 path_driver_info_t *info = APR_ARRAY_IDX(path_infos, i,
1825 path_driver_info_t *);
1826
1827 APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1828 if (is_move && (! info->resurrection))
1829 APR_ARRAY_PUSH(paths, const char *) = info->src_path;
1830 }
1831
1832 /* Add any items which only need their externals pinned. */
1833 if (pin_externals_only_infos)
1834 {
1835 for (i = 0; i < pin_externals_only_infos->nelts; i++)
1836 {
1837 path_driver_info_t *info;
1838
1839 info = APR_ARRAY_IDX(pin_externals_only_infos, i, path_driver_info_t *);
1840 APR_ARRAY_PUSH(paths, const char *) = info->dst_path;
1841 svn_hash_sets(action_hash, info->dst_path, info);
1842 }
1843 }
1844
1845 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
1846 message, ctx, pool));
1847
1848 /* Fetch RA commit editor. */
1849 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
1850 svn_client__get_shim_callbacks(ctx->wc_ctx,
1851 NULL, pool)));
1852 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
1853 commit_revprops,
1854 commit_callback,
1855 commit_baton,
1856 NULL, TRUE, /* No lock tokens */
1857 pool));
1858
1859 /* Setup the callback baton. */
1860 cb_baton.editor = editor;
1861 cb_baton.edit_baton = edit_baton;
1862 cb_baton.action_hash = action_hash;
1863 cb_baton.is_move = is_move;
1864
1865 /* Call the path-based editor driver. */
1866 err = svn_delta_path_driver2(editor, edit_baton, paths, TRUE,
1867 path_driver_cb_func, &cb_baton, pool);
1868 if (err)
1869 {
1870 /* At least try to abort the edit (and fs txn) before throwing err. */
1871 return svn_error_compose_create(
1872 err,
1873 editor->abort_edit(edit_baton, pool));
1874 }
1875
1876 if (ctx->notify_func2)
1877 {
1878 svn_wc_notify_t *notify;
1879 notify = svn_wc_create_notify_url(top_url,
1880 svn_wc_notify_commit_finalizing,
1881 pool);
1882 ctx->notify_func2(ctx->notify_baton2, notify, pool);
1883 }
1884
1885 /* Close the edit. */
1886 return svn_error_trace(editor->close_edit(edit_baton, pool));
1887 }
1888
1889 /* Baton for check_url_kind */
1890 struct check_url_kind_baton
1891 {
1892 svn_ra_session_t *session;
1893 const char *repos_root_url;
1894 svn_boolean_t should_reparent;
1895 };
1896
1897 /* Implements svn_client__check_url_kind_t for wc_to_repos_copy */
1898 static svn_error_t *
check_url_kind(void * baton,svn_node_kind_t * kind,const char * url,svn_revnum_t revision,apr_pool_t * scratch_pool)1899 check_url_kind(void *baton,
1900 svn_node_kind_t *kind,
1901 const char *url,
1902 svn_revnum_t revision,
1903 apr_pool_t *scratch_pool)
1904 {
1905 struct check_url_kind_baton *cukb = baton;
1906
1907 /* If we don't have a session or can't use the session, get one */
1908 if (!svn_uri__is_ancestor(cukb->repos_root_url, url))
1909 *kind = svn_node_none;
1910 else
1911 {
1912 cukb->should_reparent = TRUE;
1913
1914 SVN_ERR(svn_ra_reparent(cukb->session, url, scratch_pool));
1915
1916 SVN_ERR(svn_ra_check_path(cukb->session, "", revision,
1917 kind, scratch_pool));
1918 }
1919
1920 return SVN_NO_ERROR;
1921 }
1922
1923 /* Queue a property change on a copy of LOCAL_ABSPATH to COMMIT_URL
1924 * in the COMMIT_ITEMS list.
1925 * If the list does not already have a commit item for COMMIT_URL
1926 * add a new commit item for the property change.
1927 * Allocate results in RESULT_POOL.
1928 * Use SCRATCH_POOL for temporary allocations. */
1929 static svn_error_t *
queue_prop_change_commit_items(const char * local_abspath,const char * commit_url,apr_array_header_t * commit_items,const char * propname,svn_string_t * propval,apr_pool_t * result_pool,apr_pool_t * scratch_pool)1930 queue_prop_change_commit_items(const char *local_abspath,
1931 const char *commit_url,
1932 apr_array_header_t *commit_items,
1933 const char *propname,
1934 svn_string_t *propval,
1935 apr_pool_t *result_pool,
1936 apr_pool_t *scratch_pool)
1937 {
1938 svn_client_commit_item3_t *item = NULL;
1939 svn_prop_t *prop;
1940 int i;
1941
1942 for (i = 0; i < commit_items->nelts; i++)
1943 {
1944 svn_client_commit_item3_t *existing_item;
1945
1946 existing_item = APR_ARRAY_IDX(commit_items, i,
1947 svn_client_commit_item3_t *);
1948 if (strcmp(existing_item->url, commit_url) == 0)
1949 {
1950 item = existing_item;
1951 break;
1952 }
1953 }
1954
1955 if (item == NULL)
1956 {
1957 item = svn_client_commit_item3_create(result_pool);
1958 item->path = local_abspath;
1959 item->url = commit_url;
1960 item->kind = svn_node_dir;
1961 item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1962
1963 item->incoming_prop_changes = apr_array_make(result_pool, 1,
1964 sizeof(svn_prop_t *));
1965 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
1966 }
1967 else
1968 item->state_flags |= SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
1969
1970 if (item->outgoing_prop_changes == NULL)
1971 item->outgoing_prop_changes = apr_array_make(result_pool, 1,
1972 sizeof(svn_prop_t *));
1973
1974 prop = apr_palloc(result_pool, sizeof(*prop));
1975 prop->name = propname;
1976 prop->value = propval;
1977 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *) = prop;
1978
1979 return SVN_NO_ERROR;
1980 }
1981
1982 /* ### Copy ...
1983 * COMMIT_INFO_P is ...
1984 * COPY_PAIRS is ... such that each 'src_abspath_or_url' is a local abspath
1985 * and each 'dst_abspath_or_url' is a URL.
1986 * MAKE_PARENTS is ...
1987 * REVPROP_TABLE is ...
1988 * CTX is ... */
1989 static svn_error_t *
wc_to_repos_copy(const apr_array_header_t * copy_pairs,svn_boolean_t make_parents,const apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1990 wc_to_repos_copy(const apr_array_header_t *copy_pairs,
1991 svn_boolean_t make_parents,
1992 const apr_hash_t *revprop_table,
1993 svn_commit_callback2_t commit_callback,
1994 void *commit_baton,
1995 svn_boolean_t pin_externals,
1996 const apr_hash_t *externals_to_pin,
1997 svn_client_ctx_t *ctx,
1998 apr_pool_t *scratch_pool)
1999 {
2000 const char *message;
2001 const char *top_src_path, *top_dst_url;
2002 struct check_url_kind_baton cukb;
2003 const char *top_src_abspath;
2004 svn_ra_session_t *ra_session;
2005 const svn_delta_editor_t *editor;
2006 #ifdef ENABLE_EV2_SHIMS
2007 apr_hash_t *relpath_map = NULL;
2008 #endif
2009 void *edit_baton;
2010 svn_client__committables_t *committables;
2011 apr_array_header_t *commit_items;
2012 apr_pool_t *iterpool;
2013 apr_array_header_t *new_dirs = NULL;
2014 apr_hash_t *commit_revprops;
2015 svn_client__copy_pair_t *first_pair;
2016 apr_pool_t *session_pool = svn_pool_create(scratch_pool);
2017 apr_array_header_t *commit_items_for_dav;
2018 int i;
2019
2020 /* Find the common root of all the source paths */
2021 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_path, NULL, NULL,
2022 scratch_pool));
2023
2024 /* Do we need to lock the working copy? 1.6 didn't take a write
2025 lock, but what happens if the working copy changes during the copy
2026 operation? */
2027
2028 iterpool = svn_pool_create(scratch_pool);
2029
2030 /* Determine the longest common ancestor for the destinations, and open an RA
2031 session to that location. */
2032 /* ### But why start by getting the _parent_ of the first one? */
2033 /* --- That works because multiple destinations always point to the same
2034 * directory. I'm rather wondering why we need to find a common
2035 * destination parent here at all, instead of simply getting
2036 * top_dst_url from get_copy_pair_ancestors() above?
2037 * It looks like the entire block of code hanging off this comment
2038 * is redundant. */
2039 first_pair = APR_ARRAY_IDX(copy_pairs, 0, svn_client__copy_pair_t *);
2040 top_dst_url = svn_uri_dirname(first_pair->dst_abspath_or_url, scratch_pool);
2041 for (i = 1; i < copy_pairs->nelts; i++)
2042 {
2043 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2044 svn_client__copy_pair_t *);
2045 top_dst_url = svn_uri_get_longest_ancestor(top_dst_url,
2046 pair->dst_abspath_or_url,
2047 scratch_pool);
2048 }
2049
2050 SVN_ERR(svn_dirent_get_absolute(&top_src_abspath, top_src_path, scratch_pool));
2051
2052 commit_items_for_dav = apr_array_make(session_pool, 0,
2053 sizeof(svn_client_commit_item3_t*));
2054
2055 /* Open a session to help while determining the exact targets */
2056 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, top_dst_url,
2057 top_src_abspath,
2058 commit_items_for_dav,
2059 FALSE /* write_dav_props */,
2060 TRUE /* read_dav_props */,
2061 ctx,
2062 session_pool, session_pool));
2063
2064 /* If requested, determine the nearest existing parent of the destination,
2065 and reparent the ra session there. */
2066 if (make_parents)
2067 {
2068 new_dirs = apr_array_make(scratch_pool, 0, sizeof(const char *));
2069 SVN_ERR(find_absent_parents2(ra_session, &top_dst_url, new_dirs,
2070 scratch_pool));
2071 }
2072
2073 /* Figure out the basename that will result from each copy and check to make
2074 sure it doesn't exist already. */
2075 for (i = 0; i < copy_pairs->nelts; i++)
2076 {
2077 svn_node_kind_t dst_kind;
2078 const char *dst_rel;
2079 svn_client__copy_pair_t *pair =
2080 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2081
2082 svn_pool_clear(iterpool);
2083 dst_rel = svn_uri_skip_ancestor(top_dst_url, pair->dst_abspath_or_url,
2084 iterpool);
2085 SVN_ERR(svn_ra_check_path(ra_session, dst_rel, SVN_INVALID_REVNUM,
2086 &dst_kind, iterpool));
2087 if (dst_kind != svn_node_none)
2088 {
2089 return svn_error_createf(SVN_ERR_FS_ALREADY_EXISTS, NULL,
2090 _("Path '%s' already exists"),
2091 pair->dst_abspath_or_url);
2092 }
2093 }
2094
2095 cukb.session = ra_session;
2096 SVN_ERR(svn_ra_get_repos_root2(ra_session, &cukb.repos_root_url, session_pool));
2097 cukb.should_reparent = FALSE;
2098
2099 /* Crawl the working copy for commit items. */
2100 /* ### TODO: Pass check_url_func for issue #3314 handling */
2101 SVN_ERR(svn_client__get_copy_committables(&committables,
2102 copy_pairs,
2103 check_url_kind, &cukb,
2104 ctx, scratch_pool, iterpool));
2105
2106 /* The committables are keyed by the repository root */
2107 commit_items = svn_hash_gets(committables->by_repository,
2108 cukb.repos_root_url);
2109 SVN_ERR_ASSERT(commit_items != NULL);
2110
2111 if (cukb.should_reparent)
2112 SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
2113
2114 /* If we are creating intermediate directories, tack them onto the list
2115 of committables. */
2116 if (make_parents)
2117 {
2118 for (i = 0; i < new_dirs->nelts; i++)
2119 {
2120 const char *url = APR_ARRAY_IDX(new_dirs, i, const char *);
2121 svn_client_commit_item3_t *item;
2122
2123 item = svn_client_commit_item3_create(scratch_pool);
2124 item->url = url;
2125 item->kind = svn_node_dir;
2126 item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD;
2127 item->incoming_prop_changes = apr_array_make(scratch_pool, 1,
2128 sizeof(svn_prop_t *));
2129 APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
2130 }
2131 }
2132
2133 /* ### TODO: This extra loop would be unnecessary if this code lived
2134 ### in svn_client__get_copy_committables(), which is incidentally
2135 ### only used above (so should really be in this source file). */
2136 for (i = 0; i < copy_pairs->nelts; i++)
2137 {
2138 apr_hash_t *mergeinfo, *wc_mergeinfo;
2139 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2140 svn_client__copy_pair_t *);
2141 svn_client_commit_item3_t *item =
2142 APR_ARRAY_IDX(commit_items, i, svn_client_commit_item3_t *);
2143 svn_client__pathrev_t *src_origin;
2144
2145 svn_pool_clear(iterpool);
2146
2147 SVN_ERR(svn_client__wc_node_get_origin(&src_origin,
2148 pair->src_abspath_or_url,
2149 ctx, iterpool, iterpool));
2150
2151 /* Set the mergeinfo for the destination to the combined merge
2152 info known to the WC and the repository. */
2153 /* Repository mergeinfo (or NULL if it's locally added)... */
2154 if (src_origin)
2155 SVN_ERR(svn_client__get_repos_mergeinfo(
2156 &mergeinfo, ra_session, src_origin->url, src_origin->rev,
2157 svn_mergeinfo_inherited, TRUE /*sqelch_inc.*/, iterpool));
2158 else
2159 mergeinfo = NULL;
2160 /* ... and WC mergeinfo. */
2161 SVN_ERR(svn_client__parse_mergeinfo(&wc_mergeinfo, ctx->wc_ctx,
2162 pair->src_abspath_or_url,
2163 iterpool, iterpool));
2164 if (wc_mergeinfo && mergeinfo)
2165 SVN_ERR(svn_mergeinfo_merge2(mergeinfo, wc_mergeinfo, iterpool,
2166 iterpool));
2167 else if (! mergeinfo)
2168 mergeinfo = wc_mergeinfo;
2169
2170 if (mergeinfo)
2171 {
2172 /* Push a mergeinfo prop representing MERGEINFO onto the
2173 * OUTGOING_PROP_CHANGES array. */
2174
2175 svn_prop_t *mergeinfo_prop
2176 = apr_palloc(scratch_pool, sizeof(*mergeinfo_prop));
2177 svn_string_t *prop_value;
2178
2179 SVN_ERR(svn_mergeinfo_to_string(&prop_value, mergeinfo,
2180 scratch_pool));
2181
2182 if (!item->outgoing_prop_changes)
2183 {
2184 item->outgoing_prop_changes = apr_array_make(scratch_pool, 1,
2185 sizeof(svn_prop_t *));
2186 }
2187
2188 mergeinfo_prop->name = SVN_PROP_MERGEINFO;
2189 mergeinfo_prop->value = prop_value;
2190 APR_ARRAY_PUSH(item->outgoing_prop_changes, svn_prop_t *)
2191 = mergeinfo_prop;
2192 }
2193
2194 if (pin_externals)
2195 {
2196 apr_hash_t *pinned_externals;
2197 apr_hash_index_t *hi;
2198
2199 SVN_ERR(resolve_pinned_externals(&pinned_externals,
2200 externals_to_pin, pair,
2201 ra_session, cukb.repos_root_url,
2202 ctx, scratch_pool, iterpool));
2203 for (hi = apr_hash_first(scratch_pool, pinned_externals);
2204 hi;
2205 hi = apr_hash_next(hi))
2206 {
2207 const char *dst_relpath = apr_hash_this_key(hi);
2208 svn_string_t *externals_propval = apr_hash_this_val(hi);
2209 const char *dst_url;
2210 const char *commit_url;
2211 const char *src_abspath;
2212
2213 if (svn_path_is_url(pair->dst_abspath_or_url))
2214 dst_url = pair->dst_abspath_or_url;
2215 else
2216 SVN_ERR(svn_wc__node_get_url(&dst_url, ctx->wc_ctx,
2217 pair->dst_abspath_or_url,
2218 scratch_pool, iterpool));
2219 commit_url = svn_path_url_add_component2(dst_url, dst_relpath,
2220 scratch_pool);
2221 src_abspath = svn_dirent_join(pair->src_abspath_or_url,
2222 dst_relpath, iterpool);
2223 SVN_ERR(queue_prop_change_commit_items(src_abspath,
2224 commit_url, commit_items,
2225 SVN_PROP_EXTERNALS,
2226 externals_propval,
2227 scratch_pool, iterpool));
2228 }
2229 }
2230 }
2231
2232 if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
2233 {
2234 const char *tmp_file;
2235
2236 SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
2237 ctx, scratch_pool));
2238 if (! message)
2239 {
2240 svn_pool_destroy(iterpool);
2241 svn_pool_destroy(session_pool);
2242 return SVN_NO_ERROR;
2243 }
2244 }
2245 else
2246 message = "";
2247
2248 /* Sort and condense our COMMIT_ITEMS. */
2249 SVN_ERR(svn_client__condense_commit_items(&top_dst_url,
2250 commit_items, scratch_pool));
2251
2252 /* Add the commit items to the DAV commit item list to provide access
2253 to dav properties (for pre http-v2 DAV) */
2254 apr_array_cat(commit_items_for_dav, commit_items);
2255
2256 #ifdef ENABLE_EV2_SHIMS
2257 if (commit_items)
2258 {
2259 relpath_map = apr_hash_make(scratch_pool);
2260 for (i = 0; i < commit_items->nelts; i++)
2261 {
2262 svn_client_commit_item3_t *item = APR_ARRAY_IDX(commit_items, i,
2263 svn_client_commit_item3_t *);
2264 const char *relpath;
2265
2266 if (!item->path)
2267 continue;
2268
2269 svn_pool_clear(iterpool);
2270 SVN_ERR(svn_wc__node_get_origin(NULL, NULL, &relpath, NULL, NULL,
2271 NULL, NULL,
2272 ctx->wc_ctx, item->path, FALSE,
2273 scratch_pool, iterpool));
2274 if (relpath)
2275 svn_hash_sets(relpath_map, relpath, item->path);
2276 }
2277 }
2278 #endif
2279
2280 SVN_ERR(svn_ra_reparent(ra_session, top_dst_url, session_pool));
2281
2282 SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
2283 message, ctx, session_pool));
2284
2285 /* Fetch RA commit editor. */
2286 #ifdef ENABLE_EV2_SHIMS
2287 SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
2288 svn_client__get_shim_callbacks(ctx->wc_ctx, relpath_map,
2289 session_pool)));
2290 #endif
2291 SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
2292 commit_revprops,
2293 commit_callback,
2294 commit_baton, NULL,
2295 TRUE, /* No lock tokens */
2296 session_pool));
2297
2298 /* Perform the commit. */
2299 SVN_ERR_W(svn_client__do_commit(top_dst_url, commit_items,
2300 editor, edit_baton,
2301 NULL /* notify_path_prefix */,
2302 NULL, ctx, session_pool, session_pool),
2303 _("Commit failed (details follow):"));
2304
2305 svn_pool_destroy(iterpool);
2306 svn_pool_destroy(session_pool);
2307
2308 return SVN_NO_ERROR;
2309 }
2310
2311 /* A baton for notification_adjust_func(). */
2312 struct notification_adjust_baton
2313 {
2314 svn_wc_notify_func2_t inner_func;
2315 void *inner_baton;
2316 const char *checkout_abspath;
2317 const char *final_abspath;
2318 };
2319
2320 /* A svn_wc_notify_func2_t function that wraps BATON->inner_func (whose
2321 * baton is BATON->inner_baton) and adjusts the notification paths that
2322 * start with BATON->checkout_abspath to start instead with
2323 * BATON->final_abspath. */
2324 static void
notification_adjust_func(void * baton,const svn_wc_notify_t * notify,apr_pool_t * pool)2325 notification_adjust_func(void *baton,
2326 const svn_wc_notify_t *notify,
2327 apr_pool_t *pool)
2328 {
2329 struct notification_adjust_baton *nb = baton;
2330 svn_wc_notify_t *inner_notify = svn_wc_dup_notify(notify, pool);
2331 const char *relpath;
2332
2333 relpath = svn_dirent_skip_ancestor(nb->checkout_abspath, notify->path);
2334 inner_notify->path = svn_dirent_join(nb->final_abspath, relpath, pool);
2335
2336 if (nb->inner_func)
2337 nb->inner_func(nb->inner_baton, inner_notify, pool);
2338 }
2339
2340 /* Peform each individual copy operation for a repos -> wc copy. A
2341 helper for repos_to_wc_copy().
2342
2343 Resolve PAIR->src_revnum to a real revision number if it isn't already. */
2344 static svn_error_t *
repos_to_wc_copy_single(svn_boolean_t * timestamp_sleep,svn_client__copy_pair_t * pair,svn_boolean_t same_repositories,svn_boolean_t ignore_externals,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * pool)2345 repos_to_wc_copy_single(svn_boolean_t *timestamp_sleep,
2346 svn_client__copy_pair_t *pair,
2347 svn_boolean_t same_repositories,
2348 svn_boolean_t ignore_externals,
2349 svn_boolean_t pin_externals,
2350 const apr_hash_t *externals_to_pin,
2351 svn_ra_session_t *ra_session,
2352 svn_client_ctx_t *ctx,
2353 apr_pool_t *pool)
2354 {
2355 apr_hash_t *src_mergeinfo;
2356 const char *dst_abspath = pair->dst_abspath_or_url;
2357
2358 SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
2359
2360 if (!same_repositories && ctx->notify_func2)
2361 {
2362 svn_wc_notify_t *notify;
2363 notify = svn_wc_create_notify_url(
2364 pair->src_abspath_or_url,
2365 svn_wc_notify_foreign_copy_begin,
2366 pool);
2367 notify->kind = pair->src_kind;
2368 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2369
2370 /* Allow a theoretical cancel to get through. */
2371 if (ctx->cancel_func)
2372 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2373 }
2374
2375 if (pair->src_kind == svn_node_dir)
2376 {
2377 if (same_repositories)
2378 {
2379 const char *tmpdir_abspath, *tmp_abspath;
2380
2381 /* Find a temporary location in which to check out the copy source. */
2382 SVN_ERR(svn_wc__get_tmpdir(&tmpdir_abspath, ctx->wc_ctx, dst_abspath,
2383 pool, pool));
2384
2385 SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_abspath, tmpdir_abspath,
2386 svn_io_file_del_on_close, pool, pool));
2387
2388 /* Make a new checkout of the requested source. While doing so,
2389 * resolve pair->src_revnum to an actual revision number in case it
2390 * was until now 'invalid' meaning 'head'. Ask this function not to
2391 * sleep for timestamps, by passing a sleep_needed output param.
2392 * Send notifications for all nodes except the root node, and adjust
2393 * them to refer to the destination rather than this temporary path. */
2394 {
2395 svn_wc_notify_func2_t old_notify_func2 = ctx->notify_func2;
2396 void *old_notify_baton2 = ctx->notify_baton2;
2397 struct notification_adjust_baton nb;
2398 svn_error_t *err;
2399
2400 nb.inner_func = ctx->notify_func2;
2401 nb.inner_baton = ctx->notify_baton2;
2402 nb.checkout_abspath = tmp_abspath;
2403 nb.final_abspath = dst_abspath;
2404 ctx->notify_func2 = notification_adjust_func;
2405 ctx->notify_baton2 = &nb;
2406
2407 /* Avoid a chicken-and-egg problem:
2408 * If pinning externals we'll need to adjust externals
2409 * properties before checking out any externals.
2410 * But copy needs to happen before pinning because else there
2411 * are no svn:externals properties to pin. */
2412 if (pin_externals)
2413 ignore_externals = TRUE;
2414
2415 err = svn_client__checkout_internal(&pair->src_revnum, timestamp_sleep,
2416 pair->src_original,
2417 tmp_abspath,
2418 &pair->src_peg_revision,
2419 &pair->src_op_revision,
2420 svn_depth_infinity,
2421 ignore_externals, FALSE,
2422 ra_session, ctx, pool);
2423
2424 ctx->notify_func2 = old_notify_func2;
2425 ctx->notify_baton2 = old_notify_baton2;
2426
2427 SVN_ERR(err);
2428 }
2429
2430 *timestamp_sleep = TRUE;
2431
2432 /* Schedule dst_path for addition in parent, with copy history.
2433 Don't send any notification here.
2434 Then remove the temporary checkout's .svn dir in preparation for
2435 moving the rest of it into the final destination. */
2436 SVN_ERR(svn_wc_copy3(ctx->wc_ctx, tmp_abspath, dst_abspath,
2437 TRUE /* metadata_only */,
2438 ctx->cancel_func, ctx->cancel_baton,
2439 NULL, NULL, pool));
2440 SVN_ERR(svn_wc__acquire_write_lock(NULL, ctx->wc_ctx, tmp_abspath,
2441 FALSE, pool, pool));
2442 SVN_ERR(svn_wc_remove_from_revision_control2(ctx->wc_ctx,
2443 tmp_abspath,
2444 FALSE, FALSE,
2445 ctx->cancel_func,
2446 ctx->cancel_baton,
2447 pool));
2448
2449 /* Move the temporary disk tree into place. */
2450 SVN_ERR(svn_io_file_rename2(tmp_abspath, dst_abspath, FALSE, pool));
2451 }
2452 else
2453 {
2454 *timestamp_sleep = TRUE;
2455
2456 SVN_ERR(svn_client__copy_foreign(pair->src_abspath_or_url,
2457 dst_abspath,
2458 &pair->src_peg_revision,
2459 &pair->src_op_revision,
2460 svn_depth_infinity,
2461 FALSE /* make_parents */,
2462 TRUE /* already_locked */,
2463 ctx, pool));
2464
2465 return SVN_NO_ERROR;
2466 }
2467
2468 if (pin_externals)
2469 {
2470 apr_hash_t *pinned_externals;
2471 apr_hash_index_t *hi;
2472 apr_pool_t *iterpool;
2473 const char *repos_root_url;
2474 apr_hash_t *new_externals;
2475 apr_hash_t *new_depths;
2476
2477 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, pool));
2478 SVN_ERR(resolve_pinned_externals(&pinned_externals,
2479 externals_to_pin, pair,
2480 ra_session, repos_root_url,
2481 ctx, pool, pool));
2482
2483 iterpool = svn_pool_create(pool);
2484 for (hi = apr_hash_first(pool, pinned_externals);
2485 hi;
2486 hi = apr_hash_next(hi))
2487 {
2488 const char *dst_relpath = apr_hash_this_key(hi);
2489 svn_string_t *externals_propval = apr_hash_this_val(hi);
2490 const char *local_abspath;
2491
2492 svn_pool_clear(iterpool);
2493
2494 local_abspath = svn_dirent_join(pair->dst_abspath_or_url,
2495 dst_relpath, iterpool);
2496 /* ### use a work queue? */
2497 SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath,
2498 SVN_PROP_EXTERNALS, externals_propval,
2499 svn_depth_empty, TRUE /* skip_checks */,
2500 NULL /* changelist_filter */,
2501 ctx->cancel_func, ctx->cancel_baton,
2502 NULL, NULL, /* no extra notification */
2503 iterpool));
2504 }
2505
2506 /* Now update all externals in the newly created copy. */
2507 SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
2508 &new_depths,
2509 ctx->wc_ctx,
2510 dst_abspath,
2511 svn_depth_infinity,
2512 iterpool, iterpool));
2513 SVN_ERR(svn_client__handle_externals(new_externals,
2514 new_depths,
2515 repos_root_url, dst_abspath,
2516 svn_depth_infinity,
2517 timestamp_sleep,
2518 ra_session,
2519 ctx, iterpool));
2520 svn_pool_destroy(iterpool);
2521 }
2522 } /* end directory case */
2523
2524 else if (pair->src_kind == svn_node_file)
2525 {
2526 apr_hash_t *new_props;
2527 const char *src_rel;
2528 svn_stream_t *new_base_contents = svn_stream_buffered(pool);
2529
2530 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
2531 pair->src_abspath_or_url,
2532 pool));
2533 /* Fetch the file content. While doing so, resolve pair->src_revnum
2534 * to an actual revision number if it's 'invalid' meaning 'head'. */
2535 SVN_ERR(svn_ra_get_file(ra_session, src_rel, pair->src_revnum,
2536 new_base_contents,
2537 &pair->src_revnum, &new_props, pool));
2538
2539 if (new_props && ! same_repositories)
2540 svn_hash_sets(new_props, SVN_PROP_MERGEINFO, NULL);
2541
2542 *timestamp_sleep = TRUE;
2543
2544 SVN_ERR(svn_wc_add_repos_file4(
2545 ctx->wc_ctx, dst_abspath,
2546 new_base_contents, NULL, new_props, NULL,
2547 same_repositories ? pair->src_abspath_or_url : NULL,
2548 same_repositories ? pair->src_revnum : SVN_INVALID_REVNUM,
2549 ctx->cancel_func, ctx->cancel_baton,
2550 pool));
2551 }
2552
2553 /* Record the implied mergeinfo (before the notification callback
2554 is invoked for the root node). */
2555 SVN_ERR(svn_client__get_repos_mergeinfo(
2556 &src_mergeinfo, ra_session,
2557 pair->src_abspath_or_url, pair->src_revnum,
2558 svn_mergeinfo_inherited, TRUE /*squelch_incapable*/, pool));
2559 SVN_ERR(extend_wc_mergeinfo(dst_abspath, src_mergeinfo, ctx, pool));
2560
2561 /* Do our own notification for the root node, even if we could possibly
2562 have delegated it. See also issue #1552.
2563
2564 ### Maybe this notification should mention the mergeinfo change. */
2565 if (ctx->notify_func2)
2566 {
2567 svn_wc_notify_t *notify = svn_wc_create_notify(
2568 dst_abspath, svn_wc_notify_add, pool);
2569 notify->kind = pair->src_kind;
2570 ctx->notify_func2(ctx->notify_baton2, notify, pool);
2571 }
2572
2573 return SVN_NO_ERROR;
2574 }
2575
2576 static svn_error_t *
repos_to_wc_copy_locked(svn_boolean_t * timestamp_sleep,const apr_array_header_t * copy_pairs,const char * top_dst_abspath,svn_boolean_t ignore_externals,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)2577 repos_to_wc_copy_locked(svn_boolean_t *timestamp_sleep,
2578 const apr_array_header_t *copy_pairs,
2579 const char *top_dst_abspath,
2580 svn_boolean_t ignore_externals,
2581 svn_boolean_t pin_externals,
2582 const apr_hash_t *externals_to_pin,
2583 svn_ra_session_t *ra_session,
2584 svn_client_ctx_t *ctx,
2585 apr_pool_t *scratch_pool)
2586 {
2587 int i;
2588 svn_boolean_t same_repositories;
2589 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2590
2591 /* We've already checked for physical obstruction by a working file.
2592 But there could also be logical obstruction by an entry whose
2593 working file happens to be missing.*/
2594 SVN_ERR(verify_wc_dsts(copy_pairs, FALSE, FALSE, FALSE /* metadata_only */,
2595 ctx, scratch_pool, iterpool));
2596
2597 /* Decide whether the two repositories are the same or not. */
2598 {
2599 const char *parent_abspath;
2600 const char *src_uuid, *dst_uuid;
2601
2602 /* Get the repository uuid of SRC_URL */
2603 SVN_ERR(svn_ra_get_uuid2(ra_session, &src_uuid, iterpool));
2604
2605 /* Get repository uuid of dst's parent directory, since dst may
2606 not exist. ### TODO: we should probably walk up the wc here,
2607 in case the parent dir has an imaginary URL. */
2608 if (copy_pairs->nelts == 1)
2609 parent_abspath = svn_dirent_dirname(top_dst_abspath, scratch_pool);
2610 else
2611 parent_abspath = top_dst_abspath;
2612
2613 SVN_ERR(svn_client_get_repos_root(NULL /* root_url */, &dst_uuid,
2614 parent_abspath, ctx,
2615 iterpool, iterpool));
2616 /* ### Also check repos_root_url? */
2617 same_repositories = (strcmp(src_uuid, dst_uuid) == 0);
2618 }
2619
2620 /* Perform the move for each of the copy_pairs. */
2621 for (i = 0; i < copy_pairs->nelts; i++)
2622 {
2623 /* Check for cancellation */
2624 if (ctx->cancel_func)
2625 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
2626
2627 svn_pool_clear(iterpool);
2628
2629 SVN_ERR(repos_to_wc_copy_single(timestamp_sleep,
2630 APR_ARRAY_IDX(copy_pairs, i,
2631 svn_client__copy_pair_t *),
2632 same_repositories,
2633 ignore_externals,
2634 pin_externals, externals_to_pin,
2635 ra_session, ctx, iterpool));
2636 }
2637 svn_pool_destroy(iterpool);
2638
2639 return SVN_NO_ERROR;
2640 }
2641
2642 static svn_error_t *
repos_to_wc_copy(svn_boolean_t * timestamp_sleep,const apr_array_header_t * copy_pairs,svn_boolean_t make_parents,svn_boolean_t ignore_externals,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,svn_client_ctx_t * ctx,apr_pool_t * pool)2643 repos_to_wc_copy(svn_boolean_t *timestamp_sleep,
2644 const apr_array_header_t *copy_pairs,
2645 svn_boolean_t make_parents,
2646 svn_boolean_t ignore_externals,
2647 svn_boolean_t pin_externals,
2648 const apr_hash_t *externals_to_pin,
2649 svn_client_ctx_t *ctx,
2650 apr_pool_t *pool)
2651 {
2652 svn_ra_session_t *ra_session;
2653 const char *top_src_url, *top_dst_abspath;
2654 apr_pool_t *iterpool = svn_pool_create(pool);
2655 const char *lock_abspath;
2656 int i;
2657
2658 /* Get the real path for the source, based upon its peg revision. */
2659 for (i = 0; i < copy_pairs->nelts; i++)
2660 {
2661 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2662 svn_client__copy_pair_t *);
2663 const char *src;
2664
2665 svn_pool_clear(iterpool);
2666
2667 SVN_ERR(svn_client__repos_locations(&src, &pair->src_revnum, NULL, NULL,
2668 NULL,
2669 pair->src_abspath_or_url,
2670 &pair->src_peg_revision,
2671 &pair->src_op_revision, NULL,
2672 ctx, iterpool));
2673
2674 pair->src_original = pair->src_abspath_or_url;
2675 pair->src_abspath_or_url = apr_pstrdup(pool, src);
2676 }
2677
2678 SVN_ERR(get_copy_pair_ancestors(copy_pairs, &top_src_url, &top_dst_abspath,
2679 NULL, pool));
2680 lock_abspath = top_dst_abspath;
2681 if (copy_pairs->nelts == 1)
2682 {
2683 top_src_url = svn_uri_dirname(top_src_url, pool);
2684 lock_abspath = svn_dirent_dirname(top_dst_abspath, pool);
2685 }
2686
2687 /* Open a repository session to the longest common src ancestor. We do not
2688 (yet) have a working copy, so we don't have a corresponding path and
2689 tempfiles cannot go into the admin area. */
2690 SVN_ERR(svn_client_open_ra_session2(&ra_session, top_src_url, lock_abspath,
2691 ctx, pool, pool));
2692
2693 /* Get the correct src path for the peg revision used, and verify that we
2694 aren't overwriting an existing path. */
2695 for (i = 0; i < copy_pairs->nelts; i++)
2696 {
2697 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2698 svn_client__copy_pair_t *);
2699 svn_node_kind_t dst_parent_kind, dst_kind;
2700 const char *dst_parent;
2701 const char *src_rel;
2702
2703 svn_pool_clear(iterpool);
2704
2705 /* Next, make sure that the path exists in the repository. */
2706 SVN_ERR(svn_ra_get_path_relative_to_session(ra_session, &src_rel,
2707 pair->src_abspath_or_url,
2708 iterpool));
2709 SVN_ERR(svn_ra_check_path(ra_session, src_rel, pair->src_revnum,
2710 &pair->src_kind, pool));
2711 if (pair->src_kind == svn_node_none)
2712 {
2713 if (SVN_IS_VALID_REVNUM(pair->src_revnum))
2714 return svn_error_createf
2715 (SVN_ERR_FS_NOT_FOUND, NULL,
2716 _("Path '%s' not found in revision %ld"),
2717 pair->src_abspath_or_url, pair->src_revnum);
2718 else
2719 return svn_error_createf
2720 (SVN_ERR_FS_NOT_FOUND, NULL,
2721 _("Path '%s' not found in head revision"),
2722 pair->src_abspath_or_url);
2723 }
2724
2725 /* Figure out about dst. */
2726 SVN_ERR(svn_io_check_path(pair->dst_abspath_or_url, &dst_kind,
2727 iterpool));
2728 if (dst_kind != svn_node_none)
2729 {
2730 return svn_error_createf(
2731 SVN_ERR_ENTRY_EXISTS, NULL,
2732 _("Path '%s' already exists"),
2733 svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2734 }
2735
2736 /* Make sure the destination parent is a directory and produce a clear
2737 error message if it is not. */
2738 dst_parent = svn_dirent_dirname(pair->dst_abspath_or_url, iterpool);
2739 SVN_ERR(svn_io_check_path(dst_parent, &dst_parent_kind, iterpool));
2740 if (make_parents && dst_parent_kind == svn_node_none)
2741 {
2742 SVN_ERR(svn_client__make_local_parents(dst_parent, TRUE, ctx,
2743 iterpool));
2744 }
2745 else if (dst_parent_kind != svn_node_dir)
2746 {
2747 return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
2748 _("Path '%s' is not a directory"),
2749 svn_dirent_local_style(dst_parent, pool));
2750 }
2751 }
2752 svn_pool_destroy(iterpool);
2753
2754 SVN_WC__CALL_WITH_WRITE_LOCK(
2755 repos_to_wc_copy_locked(timestamp_sleep,
2756 copy_pairs, top_dst_abspath, ignore_externals,
2757 pin_externals, externals_to_pin,
2758 ra_session, ctx, pool),
2759 ctx->wc_ctx, lock_abspath, FALSE, pool);
2760 return SVN_NO_ERROR;
2761 }
2762
2763 #define NEED_REPOS_REVNUM(revision) \
2764 ((revision.kind != svn_opt_revision_unspecified) \
2765 && (revision.kind != svn_opt_revision_working))
2766
2767 /* ...
2768 *
2769 * Set *TIMESTAMP_SLEEP to TRUE if a sleep is required; otherwise do not
2770 * change *TIMESTAMP_SLEEP. This output will be valid even if the
2771 * function returns an error.
2772 *
2773 * Perform all allocations in POOL.
2774 */
2775 static svn_error_t *
try_copy(svn_boolean_t * timestamp_sleep,const apr_array_header_t * sources,const char * dst_path_in,svn_boolean_t is_move,svn_boolean_t allow_mixed_revisions,svn_boolean_t metadata_only,svn_boolean_t make_parents,svn_boolean_t ignore_externals,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,const apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)2776 try_copy(svn_boolean_t *timestamp_sleep,
2777 const apr_array_header_t *sources,
2778 const char *dst_path_in,
2779 svn_boolean_t is_move,
2780 svn_boolean_t allow_mixed_revisions,
2781 svn_boolean_t metadata_only,
2782 svn_boolean_t make_parents,
2783 svn_boolean_t ignore_externals,
2784 svn_boolean_t pin_externals,
2785 const apr_hash_t *externals_to_pin,
2786 const apr_hash_t *revprop_table,
2787 svn_commit_callback2_t commit_callback,
2788 void *commit_baton,
2789 svn_client_ctx_t *ctx,
2790 apr_pool_t *pool)
2791 {
2792 apr_array_header_t *copy_pairs =
2793 apr_array_make(pool, sources->nelts,
2794 sizeof(svn_client__copy_pair_t *));
2795 svn_boolean_t srcs_are_urls, dst_is_url;
2796 int i;
2797
2798 /* Assert instead of crashing if the sources list is empty. */
2799 SVN_ERR_ASSERT(sources->nelts > 0);
2800
2801 /* Are either of our paths URLs? Just check the first src_path. If
2802 there are more than one, we'll check for homogeneity among them
2803 down below. */
2804 srcs_are_urls = svn_path_is_url(APR_ARRAY_IDX(sources, 0,
2805 svn_client_copy_source_t *)->path);
2806 dst_is_url = svn_path_is_url(dst_path_in);
2807 if (!dst_is_url)
2808 SVN_ERR(svn_dirent_get_absolute(&dst_path_in, dst_path_in, pool));
2809
2810 /* If we have multiple source paths, it implies the dst_path is a
2811 directory we are moving or copying into. Populate the COPY_PAIRS
2812 array to contain a destination path for each of the source paths. */
2813 if (sources->nelts > 1)
2814 {
2815 apr_pool_t *iterpool = svn_pool_create(pool);
2816
2817 for (i = 0; i < sources->nelts; i++)
2818 {
2819 svn_client_copy_source_t *source = APR_ARRAY_IDX(sources, i,
2820 svn_client_copy_source_t *);
2821 svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
2822 const char *src_basename;
2823 svn_boolean_t src_is_url = svn_path_is_url(source->path);
2824
2825 svn_pool_clear(iterpool);
2826
2827 if (src_is_url)
2828 {
2829 pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2830 src_basename = svn_uri_basename(pair->src_abspath_or_url,
2831 iterpool);
2832 }
2833 else
2834 {
2835 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2836 source->path, pool));
2837 src_basename = svn_dirent_basename(pair->src_abspath_or_url,
2838 iterpool);
2839 }
2840
2841 pair->src_op_revision = *source->revision;
2842 pair->src_peg_revision = *source->peg_revision;
2843 pair->src_kind = svn_node_unknown;
2844
2845 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2846 &pair->src_op_revision,
2847 src_is_url,
2848 TRUE,
2849 iterpool));
2850
2851 /* Check to see if all the sources are urls or all working copy
2852 * paths. */
2853 if (src_is_url != srcs_are_urls)
2854 return svn_error_create
2855 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2856 _("Cannot mix repository and working copy sources"));
2857
2858 if (dst_is_url)
2859 pair->dst_abspath_or_url =
2860 svn_path_url_add_component2(dst_path_in, src_basename, pool);
2861 else
2862 pair->dst_abspath_or_url = svn_dirent_join(dst_path_in,
2863 src_basename, pool);
2864 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2865 }
2866
2867 svn_pool_destroy(iterpool);
2868 }
2869 else
2870 {
2871 /* Only one source path. */
2872 svn_client__copy_pair_t *pair = apr_pcalloc(pool, sizeof(*pair));
2873 svn_client_copy_source_t *source =
2874 APR_ARRAY_IDX(sources, 0, svn_client_copy_source_t *);
2875 svn_boolean_t src_is_url = svn_path_is_url(source->path);
2876
2877 if (src_is_url)
2878 pair->src_abspath_or_url = apr_pstrdup(pool, source->path);
2879 else
2880 SVN_ERR(svn_dirent_get_absolute(&pair->src_abspath_or_url,
2881 source->path, pool));
2882 pair->src_op_revision = *source->revision;
2883 pair->src_peg_revision = *source->peg_revision;
2884 pair->src_kind = svn_node_unknown;
2885
2886 SVN_ERR(svn_opt_resolve_revisions(&pair->src_peg_revision,
2887 &pair->src_op_revision,
2888 src_is_url, TRUE, pool));
2889
2890 pair->dst_abspath_or_url = dst_path_in;
2891 APR_ARRAY_PUSH(copy_pairs, svn_client__copy_pair_t *) = pair;
2892 }
2893
2894 if (!srcs_are_urls && !dst_is_url)
2895 {
2896 apr_pool_t *iterpool = svn_pool_create(pool);
2897
2898 for (i = 0; i < copy_pairs->nelts; i++)
2899 {
2900 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2901 svn_client__copy_pair_t *);
2902
2903 svn_pool_clear(iterpool);
2904
2905 if (svn_dirent_is_child(pair->src_abspath_or_url,
2906 pair->dst_abspath_or_url, iterpool))
2907 return svn_error_createf
2908 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2909 _("Cannot copy path '%s' into its own child '%s'"),
2910 svn_dirent_local_style(pair->src_abspath_or_url, pool),
2911 svn_dirent_local_style(pair->dst_abspath_or_url, pool));
2912 }
2913
2914 svn_pool_destroy(iterpool);
2915 }
2916
2917 /* A file external should not be moved since the file external is
2918 implemented as a switched file and it would delete the file the
2919 file external is switched to, which is not the behavior the user
2920 would probably want. */
2921 if (is_move && !srcs_are_urls)
2922 {
2923 apr_pool_t *iterpool = svn_pool_create(pool);
2924
2925 for (i = 0; i < copy_pairs->nelts; i++)
2926 {
2927 svn_client__copy_pair_t *pair =
2928 APR_ARRAY_IDX(copy_pairs, i, svn_client__copy_pair_t *);
2929 svn_node_kind_t external_kind;
2930 const char *defining_abspath;
2931
2932 svn_pool_clear(iterpool);
2933
2934 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
2935 SVN_ERR(svn_wc__read_external_info(&external_kind, &defining_abspath,
2936 NULL, NULL, NULL, ctx->wc_ctx,
2937 pair->src_abspath_or_url,
2938 pair->src_abspath_or_url, TRUE,
2939 iterpool, iterpool));
2940
2941 if (external_kind != svn_node_none)
2942 return svn_error_createf(
2943 SVN_ERR_WC_CANNOT_MOVE_FILE_EXTERNAL,
2944 NULL,
2945 _("Cannot move the external at '%s'; please "
2946 "edit the svn:externals property on '%s'."),
2947 svn_dirent_local_style(pair->src_abspath_or_url, pool),
2948 svn_dirent_local_style(defining_abspath, pool));
2949 }
2950 svn_pool_destroy(iterpool);
2951 }
2952
2953 if (is_move)
2954 {
2955 /* Disallow moves between the working copy and the repository. */
2956 if (srcs_are_urls != dst_is_url)
2957 {
2958 return svn_error_create
2959 (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2960 _("Moves between the working copy and the repository are not "
2961 "supported"));
2962 }
2963
2964 /* Disallow moving any path/URL onto or into itself. */
2965 for (i = 0; i < copy_pairs->nelts; i++)
2966 {
2967 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2968 svn_client__copy_pair_t *);
2969
2970 if (strcmp(pair->src_abspath_or_url,
2971 pair->dst_abspath_or_url) == 0)
2972 return svn_error_createf(
2973 SVN_ERR_UNSUPPORTED_FEATURE, NULL,
2974 srcs_are_urls ?
2975 _("Cannot move URL '%s' into itself") :
2976 _("Cannot move path '%s' into itself"),
2977 srcs_are_urls ?
2978 pair->src_abspath_or_url :
2979 svn_dirent_local_style(pair->src_abspath_or_url, pool));
2980 }
2981 }
2982 else
2983 {
2984 if (!srcs_are_urls)
2985 {
2986 /* If we are doing a wc->* copy, but with an operational revision
2987 other than the working copy revision, we are really doing a
2988 repo->* copy, because we're going to need to get the rev from the
2989 repo. */
2990
2991 svn_boolean_t need_repos_op_rev = FALSE;
2992 svn_boolean_t need_repos_peg_rev = FALSE;
2993
2994 /* Check to see if any revision is something other than
2995 svn_opt_revision_unspecified or svn_opt_revision_working. */
2996 for (i = 0; i < copy_pairs->nelts; i++)
2997 {
2998 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
2999 svn_client__copy_pair_t *);
3000
3001 if (NEED_REPOS_REVNUM(pair->src_op_revision))
3002 need_repos_op_rev = TRUE;
3003
3004 if (NEED_REPOS_REVNUM(pair->src_peg_revision))
3005 need_repos_peg_rev = TRUE;
3006
3007 if (need_repos_op_rev || need_repos_peg_rev)
3008 break;
3009 }
3010
3011 if (need_repos_op_rev || need_repos_peg_rev)
3012 {
3013 apr_pool_t *iterpool = svn_pool_create(pool);
3014
3015 for (i = 0; i < copy_pairs->nelts; i++)
3016 {
3017 const char *copyfrom_repos_root_url;
3018 const char *copyfrom_repos_relpath;
3019 const char *url;
3020 svn_revnum_t copyfrom_rev;
3021 svn_client__copy_pair_t *pair = APR_ARRAY_IDX(copy_pairs, i,
3022 svn_client__copy_pair_t *);
3023
3024 svn_pool_clear(iterpool);
3025
3026 SVN_ERR_ASSERT(svn_dirent_is_absolute(pair->src_abspath_or_url));
3027
3028 SVN_ERR(svn_wc__node_get_origin(NULL, ©from_rev,
3029 ©from_repos_relpath,
3030 ©from_repos_root_url,
3031 NULL, NULL, NULL,
3032 ctx->wc_ctx,
3033 pair->src_abspath_or_url,
3034 TRUE, iterpool, iterpool));
3035
3036 if (copyfrom_repos_relpath)
3037 url = svn_path_url_add_component2(copyfrom_repos_root_url,
3038 copyfrom_repos_relpath,
3039 pool);
3040 else
3041 return svn_error_createf
3042 (SVN_ERR_ENTRY_MISSING_URL, NULL,
3043 _("'%s' does not have a URL associated with it"),
3044 svn_dirent_local_style(pair->src_abspath_or_url, pool));
3045
3046 pair->src_abspath_or_url = url;
3047
3048 if (!need_repos_peg_rev
3049 || pair->src_peg_revision.kind == svn_opt_revision_base)
3050 {
3051 /* Default the peg revision to that of the WC entry. */
3052 pair->src_peg_revision.kind = svn_opt_revision_number;
3053 pair->src_peg_revision.value.number = copyfrom_rev;
3054 }
3055
3056 if (pair->src_op_revision.kind == svn_opt_revision_base)
3057 {
3058 /* Use the entry's revision as the operational rev. */
3059 pair->src_op_revision.kind = svn_opt_revision_number;
3060 pair->src_op_revision.value.number = copyfrom_rev;
3061 }
3062 }
3063
3064 svn_pool_destroy(iterpool);
3065 srcs_are_urls = TRUE;
3066 }
3067 }
3068 }
3069
3070 /* Now, call the right handler for the operation. */
3071 if ((! srcs_are_urls) && (! dst_is_url))
3072 {
3073 SVN_ERR(verify_wc_srcs_and_dsts(copy_pairs, make_parents, is_move,
3074 metadata_only, ctx, pool, pool));
3075
3076 /* Copy or move all targets. */
3077 if (is_move)
3078 return svn_error_trace(do_wc_to_wc_moves(timestamp_sleep,
3079 copy_pairs, dst_path_in,
3080 allow_mixed_revisions,
3081 metadata_only,
3082 ctx, pool));
3083 else
3084 {
3085 /* We ignore these values, so assert the default value */
3086 SVN_ERR_ASSERT(allow_mixed_revisions);
3087 return svn_error_trace(do_wc_to_wc_copies(timestamp_sleep,
3088 copy_pairs,
3089 metadata_only,
3090 pin_externals,
3091 externals_to_pin,
3092 ctx, pool));
3093 }
3094 }
3095 else if ((! srcs_are_urls) && (dst_is_url))
3096 {
3097 return svn_error_trace(
3098 wc_to_repos_copy(copy_pairs, make_parents, revprop_table,
3099 commit_callback, commit_baton,
3100 pin_externals, externals_to_pin, ctx, pool));
3101 }
3102 else if ((srcs_are_urls) && (! dst_is_url))
3103 {
3104 return svn_error_trace(
3105 repos_to_wc_copy(timestamp_sleep,
3106 copy_pairs, make_parents, ignore_externals,
3107 pin_externals, externals_to_pin, ctx, pool));
3108 }
3109 else
3110 {
3111 return svn_error_trace(
3112 repos_to_repos_copy(copy_pairs, make_parents, revprop_table,
3113 commit_callback, commit_baton, ctx, is_move,
3114 pin_externals, externals_to_pin, pool));
3115 }
3116 }
3117
3118
3119
3120 /* Public Interfaces */
3121 svn_error_t *
svn_client_copy7(const apr_array_header_t * sources,const char * dst_path,svn_boolean_t copy_as_child,svn_boolean_t make_parents,svn_boolean_t ignore_externals,svn_boolean_t metadata_only,svn_boolean_t pin_externals,const apr_hash_t * externals_to_pin,const apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)3122 svn_client_copy7(const apr_array_header_t *sources,
3123 const char *dst_path,
3124 svn_boolean_t copy_as_child,
3125 svn_boolean_t make_parents,
3126 svn_boolean_t ignore_externals,
3127 svn_boolean_t metadata_only,
3128 svn_boolean_t pin_externals,
3129 const apr_hash_t *externals_to_pin,
3130 const apr_hash_t *revprop_table,
3131 svn_commit_callback2_t commit_callback,
3132 void *commit_baton,
3133 svn_client_ctx_t *ctx,
3134 apr_pool_t *pool)
3135 {
3136 svn_error_t *err;
3137 svn_boolean_t timestamp_sleep = FALSE;
3138 apr_pool_t *subpool = svn_pool_create(pool);
3139
3140 if (sources->nelts > 1 && !copy_as_child)
3141 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
3142 NULL, NULL);
3143
3144 err = try_copy(×tamp_sleep,
3145 sources, dst_path,
3146 FALSE /* is_move */,
3147 TRUE /* allow_mixed_revisions */,
3148 metadata_only,
3149 make_parents,
3150 ignore_externals,
3151 pin_externals,
3152 externals_to_pin,
3153 revprop_table,
3154 commit_callback, commit_baton,
3155 ctx,
3156 subpool);
3157
3158 /* If the destination exists, try to copy the sources as children of the
3159 destination. */
3160 if (copy_as_child && err && (sources->nelts == 1)
3161 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
3162 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
3163 {
3164 const char *src_path = APR_ARRAY_IDX(sources, 0,
3165 svn_client_copy_source_t *)->path;
3166 const char *src_basename;
3167 svn_boolean_t src_is_url = svn_path_is_url(src_path);
3168 svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
3169
3170 svn_error_clear(err);
3171 svn_pool_clear(subpool);
3172
3173 src_basename = src_is_url ? svn_uri_basename(src_path, subpool)
3174 : svn_dirent_basename(src_path, subpool);
3175 dst_path
3176 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
3177 subpool)
3178 : svn_dirent_join(dst_path, src_basename, subpool);
3179
3180 err = try_copy(×tamp_sleep,
3181 sources, dst_path,
3182 FALSE /* is_move */,
3183 TRUE /* allow_mixed_revisions */,
3184 metadata_only,
3185 make_parents,
3186 ignore_externals,
3187 pin_externals,
3188 externals_to_pin,
3189 revprop_table,
3190 commit_callback, commit_baton,
3191 ctx,
3192 subpool);
3193 }
3194
3195 /* Sleep if required. DST_PATH is not a URL in these cases. */
3196 if (timestamp_sleep)
3197 svn_io_sleep_for_timestamps(dst_path, subpool);
3198
3199 svn_pool_destroy(subpool);
3200 return svn_error_trace(err);
3201 }
3202
3203
3204 svn_error_t *
svn_client_move7(const apr_array_header_t * src_paths,const char * dst_path,svn_boolean_t move_as_child,svn_boolean_t make_parents,svn_boolean_t allow_mixed_revisions,svn_boolean_t metadata_only,const apr_hash_t * revprop_table,svn_commit_callback2_t commit_callback,void * commit_baton,svn_client_ctx_t * ctx,apr_pool_t * pool)3205 svn_client_move7(const apr_array_header_t *src_paths,
3206 const char *dst_path,
3207 svn_boolean_t move_as_child,
3208 svn_boolean_t make_parents,
3209 svn_boolean_t allow_mixed_revisions,
3210 svn_boolean_t metadata_only,
3211 const apr_hash_t *revprop_table,
3212 svn_commit_callback2_t commit_callback,
3213 void *commit_baton,
3214 svn_client_ctx_t *ctx,
3215 apr_pool_t *pool)
3216 {
3217 const svn_opt_revision_t head_revision
3218 = { svn_opt_revision_head, { 0 } };
3219 svn_error_t *err;
3220 svn_boolean_t timestamp_sleep = FALSE;
3221 int i;
3222 apr_pool_t *subpool = svn_pool_create(pool);
3223 apr_array_header_t *sources = apr_array_make(pool, src_paths->nelts,
3224 sizeof(const svn_client_copy_source_t *));
3225
3226 if (src_paths->nelts > 1 && !move_as_child)
3227 return svn_error_create(SVN_ERR_CLIENT_MULTIPLE_SOURCES_DISALLOWED,
3228 NULL, NULL);
3229
3230 for (i = 0; i < src_paths->nelts; i++)
3231 {
3232 const char *src_path = APR_ARRAY_IDX(src_paths, i, const char *);
3233 svn_client_copy_source_t *copy_source = apr_palloc(pool,
3234 sizeof(*copy_source));
3235
3236 copy_source->path = src_path;
3237 copy_source->revision = &head_revision;
3238 copy_source->peg_revision = &head_revision;
3239
3240 APR_ARRAY_PUSH(sources, svn_client_copy_source_t *) = copy_source;
3241 }
3242
3243 err = try_copy(×tamp_sleep,
3244 sources, dst_path,
3245 TRUE /* is_move */,
3246 allow_mixed_revisions,
3247 metadata_only,
3248 make_parents,
3249 FALSE /* ignore_externals */,
3250 FALSE /* pin_externals */,
3251 NULL /* externals_to_pin */,
3252 revprop_table,
3253 commit_callback, commit_baton,
3254 ctx,
3255 subpool);
3256
3257 /* If the destination exists, try to move the sources as children of the
3258 destination. */
3259 if (move_as_child && err && (src_paths->nelts == 1)
3260 && (err->apr_err == SVN_ERR_ENTRY_EXISTS
3261 || err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
3262 {
3263 const char *src_path = APR_ARRAY_IDX(src_paths, 0, const char *);
3264 const char *src_basename;
3265 svn_boolean_t src_is_url = svn_path_is_url(src_path);
3266 svn_boolean_t dst_is_url = svn_path_is_url(dst_path);
3267
3268 svn_error_clear(err);
3269 svn_pool_clear(subpool);
3270
3271 src_basename = src_is_url ? svn_uri_basename(src_path, pool)
3272 : svn_dirent_basename(src_path, pool);
3273 dst_path
3274 = dst_is_url ? svn_path_url_add_component2(dst_path, src_basename,
3275 subpool)
3276 : svn_dirent_join(dst_path, src_basename, subpool);
3277
3278 err = try_copy(×tamp_sleep,
3279 sources, dst_path,
3280 TRUE /* is_move */,
3281 allow_mixed_revisions,
3282 metadata_only,
3283 make_parents,
3284 FALSE /* ignore_externals */,
3285 FALSE /* pin_externals */,
3286 NULL /* externals_to_pin */,
3287 revprop_table,
3288 commit_callback, commit_baton,
3289 ctx,
3290 subpool);
3291 }
3292
3293 /* Sleep if required. DST_PATH is not a URL in these cases. */
3294 if (timestamp_sleep)
3295 svn_io_sleep_for_timestamps(dst_path, subpool);
3296
3297 svn_pool_destroy(subpool);
3298 return svn_error_trace(err);
3299 }
3300