2 * externals.c: handle the svn:externals property
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 /* ==================================================================== */
33 #include "svn_pools.h"
34 #include "svn_client.h"
35 #include "svn_types.h"
36 #include "svn_error.h"
37 #include "svn_dirent_uri.h"
39 #include "svn_props.h"
40 #include "svn_config.h"
43 #include "svn_private_config.h"
44 #include "private/svn_wc_private.h"
47 /* Remove the directory at LOCAL_ABSPATH from revision control, and do the
48 * same to any revision controlled directories underneath LOCAL_ABSPATH
49 * (including directories not referred to by parent svn administrative areas);
50 * then if LOCAL_ABSPATH is empty afterwards, remove it, else rename it to a
51 * unique name in the same parent directory.
53 * Pass CANCEL_FUNC, CANCEL_BATON to svn_wc_remove_from_revision_control.
55 * Use SCRATCH_POOL for all temporary allocation.
58 relegate_dir_external(svn_wc_context_t *wc_ctx,
59 const char *wri_abspath,
60 const char *local_abspath,
61 svn_cancel_func_t cancel_func,
63 svn_wc_notify_func2_t notify_func,
65 apr_pool_t *scratch_pool)
67 svn_error_t *err = SVN_NO_ERROR;
69 SVN_ERR(svn_wc__acquire_write_lock(NULL, wc_ctx, local_abspath,
70 FALSE, scratch_pool, scratch_pool));
72 err = svn_wc__external_remove(wc_ctx, wri_abspath, local_abspath, FALSE,
73 cancel_func, cancel_baton, scratch_pool);
74 if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD))
76 const char *parent_dir;
83 svn_dirent_split(&parent_dir, &dirname, local_abspath, scratch_pool);
85 /* Reserve the new dir name. */
86 SVN_ERR(svn_io_open_uniquely_named(NULL, &new_path,
87 parent_dir, dirname, ".OLD",
89 scratch_pool, scratch_pool));
91 /* Sigh... We must fall ever so slightly from grace.
93 Ideally, there would be no window, however brief, when we
94 don't have a reservation on the new name. Unfortunately,
95 at least in the Unix (Linux?) version of apr_file_rename(),
96 you can't rename a directory over a file, because it's just
97 calling stdio rename(), which says:
100 A component used as a directory in oldpath or newpath
101 path is not, in fact, a directory. Or, oldpath is
102 a directory, and newpath exists but is not a directory
104 So instead, we get the name, then remove the file (ugh), then
105 rename the directory, hoping that nobody has gotten that name
106 in the meantime -- which would never happen in real life, so
109 /* Do our best, but no biggy if it fails. The rename will fail. */
110 svn_error_clear(svn_io_remove_file2(new_path, TRUE, scratch_pool));
112 /* Rename. If this is still a working copy we should use the working
113 copy rename function (to release open handles) */
114 err = svn_wc__rename_wc(wc_ctx, local_abspath, new_path,
117 if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
119 svn_error_clear(err);
121 /* And if it is no longer a working copy, we should just rename
123 err = svn_io_file_rename(local_abspath, new_path, scratch_pool);
126 /* ### TODO: We should notify the user about the rename */
129 svn_wc_notify_t *notify;
131 notify = svn_wc_create_notify(err ? local_abspath : new_path,
132 svn_wc_notify_left_local_modifications,
134 notify->kind = svn_node_dir;
137 notify_func(notify_baton, notify, scratch_pool);
141 return svn_error_trace(err);
144 /* Try to update a directory external at PATH to URL at REVISION.
145 Use POOL for temporary allocations, and use the client context CTX. */
147 switch_dir_external(const char *local_abspath,
149 const char *url_from_externals_definition,
150 const svn_opt_revision_t *peg_revision,
151 const svn_opt_revision_t *revision,
152 const char *defining_abspath,
153 svn_boolean_t *timestamp_sleep,
154 svn_client_ctx_t *ctx,
157 svn_node_kind_t kind;
159 svn_revnum_t external_peg_rev = SVN_INVALID_REVNUM;
160 svn_revnum_t external_rev = SVN_INVALID_REVNUM;
161 apr_pool_t *subpool = svn_pool_create(pool);
162 const char *repos_root_url;
163 const char *repos_uuid;
165 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
167 if (peg_revision->kind == svn_opt_revision_number)
168 external_peg_rev = peg_revision->value.number;
170 if (revision->kind == svn_opt_revision_number)
171 external_rev = revision->value.number;
174 * The code below assumes existing versioned paths are *not* part of
175 * the external's defining working copy.
176 * The working copy library does not support registering externals
177 * on top of existing BASE nodes and will error out if we try.
178 * So if the external target is part of the defining working copy's
179 * BASE tree, don't attempt to create the external. Doing so would
180 * leave behind a switched path instead of an external (since the
181 * switch succeeds but registration of the external in the DB fails).
182 * The working copy then cannot be updated until the path is switched back.
185 SVN_ERR(svn_wc__node_get_base(&kind, NULL, NULL,
186 &repos_root_url, &repos_uuid,
187 NULL, ctx->wc_ctx, local_abspath,
188 TRUE, /* ignore_enoent */
189 TRUE, /* show hidden */
191 if (kind != svn_node_unknown)
193 const char *wcroot_abspath;
194 const char *defining_wcroot_abspath;
196 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
197 local_abspath, pool, pool));
198 SVN_ERR(svn_wc__get_wcroot(&defining_wcroot_abspath, ctx->wc_ctx,
199 defining_abspath, pool, pool));
200 if (strcmp(wcroot_abspath, defining_wcroot_abspath) == 0)
201 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
202 _("The external '%s' defined in %s at '%s' "
203 "cannot be checked out because '%s' is "
204 "already a versioned path."),
205 url_from_externals_definition,
207 svn_dirent_local_style(defining_abspath,
209 svn_dirent_local_style(local_abspath,
213 /* If path is a directory, try to update/switch to the correct URL
215 SVN_ERR(svn_io_check_path(local_abspath, &kind, pool));
216 if (kind == svn_node_dir)
218 const char *node_url;
220 /* Doubles as an "is versioned" check. */
221 err = svn_wc__node_get_url(&node_url, ctx->wc_ctx, local_abspath,
223 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
225 svn_error_clear(err);
230 return svn_error_trace(err);
234 /* If we have what appears to be a version controlled
235 subdir, and its top-level URL matches that of our
236 externals definition, perform an update. */
237 if (strcmp(node_url, url) == 0)
239 SVN_ERR(svn_client__update_internal(NULL, local_abspath,
240 revision, svn_depth_unknown,
241 FALSE, FALSE, FALSE, TRUE,
246 /* We just decided that this existing directory is an external,
247 so update the external registry with this information, like
248 when checking out an external */
249 SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
251 local_abspath, svn_node_dir,
252 repos_root_url, repos_uuid,
253 svn_uri_skip_ancestor(repos_root_url,
259 svn_pool_destroy(subpool);
263 /* We'd really prefer not to have to do a brute-force
264 relegation -- blowing away the current external working
265 copy and checking it out anew -- so we'll first see if we
266 can get away with a generally cheaper relocation (if
267 required) and switch-style update.
269 To do so, we need to know the repository root URL of the
270 external working copy as it currently sits. */
271 err = svn_wc__node_get_repos_info(NULL, NULL,
272 &repos_root_url, &repos_uuid,
273 ctx->wc_ctx, local_abspath,
277 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
278 && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
279 return svn_error_trace(err);
281 svn_error_clear(err);
282 repos_root_url = NULL;
288 /* If the new external target URL is not obviously a
289 child of the external working copy's current
290 repository root URL... */
291 if (! svn_uri__is_ancestor(repos_root_url, url))
293 const char *repos_root;
295 /* ... then figure out precisely which repository
296 root URL that target URL *is* a child of ... */
297 SVN_ERR(svn_client_get_repos_root(&repos_root, NULL, url,
298 ctx, subpool, subpool));
300 /* ... and use that to try to relocate the external
301 working copy to the target location. */
302 err = svn_client_relocate2(local_abspath, repos_root_url,
303 repos_root, FALSE, ctx, subpool);
305 /* If the relocation failed because the new URL
306 points to a totally different repository, we've
307 no choice but to relegate and check out a new WC. */
309 && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION
311 == SVN_ERR_CLIENT_INVALID_RELOCATION)))
313 svn_error_clear(err);
317 return svn_error_trace(err);
319 /* If the relocation went without a hitch, we should
320 have a new repository root URL. */
321 repos_root_url = repos_root;
324 SVN_ERR(svn_client__switch_internal(NULL, local_abspath, url,
325 peg_revision, revision,
328 TRUE /* ignore_ancestry */,
332 SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
334 local_abspath, svn_node_dir,
335 repos_root_url, repos_uuid,
336 svn_uri_skip_ancestor(
343 svn_pool_destroy(subpool);
351 /* Fall back on removing the WC and checking out a new one. */
353 /* Ensure that we don't have any RA sessions or WC locks from failed
355 svn_pool_destroy(subpool);
357 if (kind == svn_node_dir)
359 /* Buh-bye, old and busted ... */
360 SVN_ERR(relegate_dir_external(ctx->wc_ctx, defining_abspath,
362 ctx->cancel_func, ctx->cancel_baton,
363 ctx->notify_func2, ctx->notify_baton2,
368 /* The target dir might have multiple components. Guarantee
369 the path leading down to the last component. */
370 const char *parent = svn_dirent_dirname(local_abspath, pool);
371 SVN_ERR(svn_io_make_dir_recursively(parent, pool));
374 /* ... Hello, new hotness. */
375 SVN_ERR(svn_client__checkout_internal(NULL, url, local_abspath, peg_revision,
376 revision, svn_depth_infinity,
377 FALSE, FALSE, timestamp_sleep,
380 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL,
383 ctx->wc_ctx, local_abspath,
386 SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
388 local_abspath, svn_node_dir,
389 repos_root_url, repos_uuid,
390 svn_uri_skip_ancestor(repos_root_url,
397 /* Issues #4123 and #4130: We don't need to keep the newly checked
398 out external's DB open. */
399 SVN_ERR(svn_wc__close_db(local_abspath, ctx->wc_ctx, pool));
404 /* Try to update a file external at LOCAL_ABSPATH to URL at REVISION using a
405 access baton that has a write lock. Use SCRATCH_POOL for temporary
406 allocations, and use the client context CTX. */
408 switch_file_external(const char *local_abspath,
410 const svn_opt_revision_t *peg_revision,
411 const svn_opt_revision_t *revision,
412 const char *def_dir_abspath,
413 svn_ra_session_t *ra_session,
414 svn_client_ctx_t *ctx,
415 apr_pool_t *scratch_pool)
417 svn_config_t *cfg = ctx->config
418 ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
420 svn_boolean_t use_commit_times;
421 const char *diff3_cmd;
422 const char *preserved_exts_str;
423 const apr_array_header_t *preserved_exts;
424 svn_node_kind_t kind, external_kind;
426 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
428 /* See if the user wants last-commit timestamps instead of current ones. */
429 SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
430 SVN_CONFIG_SECTION_MISCELLANY,
431 SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
433 /* Get the external diff3, if any. */
434 svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
435 SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
437 if (diff3_cmd != NULL)
438 SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool));
440 /* See which files the user wants to preserve the extension of when
441 conflict files are made. */
442 svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
443 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
444 preserved_exts = *preserved_exts_str
445 ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool)
449 const char *wcroot_abspath;
451 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath,
452 scratch_pool, scratch_pool));
454 /* File externals can only be installed inside the current working copy.
455 So verify if the working copy that contains/will contain the target
456 is the defining abspath, or one of its ancestors */
458 if (!svn_dirent_is_ancestor(wcroot_abspath, def_dir_abspath))
459 return svn_error_createf(
460 SVN_ERR_WC_BAD_PATH, NULL,
461 _("Cannot insert a file external defined on '%s' "
462 "into the working copy '%s'."),
463 svn_dirent_local_style(def_dir_abspath,
465 svn_dirent_local_style(wcroot_abspath,
469 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath,
470 TRUE, FALSE, scratch_pool));
472 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
473 ctx->wc_ctx, local_abspath, local_abspath,
474 TRUE, scratch_pool, scratch_pool));
476 /* If there is a versioned item with this name, ensure it's a file
477 external before working with it. If there is no entry in the
478 working copy, then create an empty file and add it to the working
480 if (kind != svn_node_none && kind != svn_node_unknown)
482 if (external_kind != svn_node_file)
484 return svn_error_createf(
485 SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0,
486 _("The file external from '%s' cannot overwrite the existing "
487 "versioned item at '%s'"),
488 url, svn_dirent_local_style(local_abspath, scratch_pool));
493 svn_node_kind_t disk_kind;
495 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
497 if (kind == svn_node_file || kind == svn_node_dir)
498 return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL,
499 _("The file external '%s' can not be "
500 "created because the node exists."),
501 svn_dirent_local_style(local_abspath,
506 const svn_ra_reporter3_t *reporter;
508 const svn_delta_editor_t *switch_editor;
510 svn_client__pathrev_t *switch_loc;
512 apr_array_header_t *inherited_props;
513 const char *dir_abspath;
516 svn_dirent_split(&dir_abspath, &target, local_abspath, scratch_pool);
518 /* ### Why do we open a new session? RA_SESSION is a valid
519 ### session -- the caller used it to call svn_ra_check_path on
520 ### this very URL, the caller also did the resolving and
521 ### reparenting that is repeated here. */
522 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc,
524 peg_revision, revision,
526 /* Get the external file's iprops. */
527 SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "",
529 scratch_pool, scratch_pool));
531 SVN_ERR(svn_ra_reparent(ra_session, svn_uri_dirname(url, scratch_pool),
534 SVN_ERR(svn_wc__get_file_external_editor(&switch_editor, &switch_baton,
535 &revnum, ctx->wc_ctx,
539 switch_loc->repos_root_url,
540 switch_loc->repos_uuid,
543 diff3_cmd, preserved_exts,
545 url, peg_revision, revision,
547 ctx->conflict_baton2,
552 scratch_pool, scratch_pool));
554 /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
555 invalid revnum, that means RA will use the latest revision. */
556 SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton,
558 target, svn_depth_unknown, switch_loc->url,
559 FALSE /* send_copyfrom */,
560 TRUE /* ignore_ancestry */,
561 switch_editor, switch_baton,
562 scratch_pool, scratch_pool));
564 SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath,
565 reporter, report_baton,
566 TRUE, use_commit_times,
567 ctx->cancel_func, ctx->cancel_baton,
568 ctx->notify_func2, ctx->notify_baton2,
571 if (ctx->notify_func2)
573 svn_wc_notify_t *notify
574 = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
576 notify->kind = svn_node_none;
577 notify->content_state = notify->prop_state
578 = svn_wc_notify_state_inapplicable;
579 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
580 notify->revision = revnum;
581 (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
588 /* Wrappers around svn_wc__external_remove, obtaining and releasing a lock for
589 directory externals */
591 remove_external2(svn_boolean_t *removed,
592 svn_wc_context_t *wc_ctx,
593 const char *wri_abspath,
594 const char *local_abspath,
595 svn_node_kind_t external_kind,
596 svn_cancel_func_t cancel_func,
598 apr_pool_t *scratch_pool)
600 SVN_ERR(svn_wc__external_remove(wc_ctx, wri_abspath,
602 (external_kind == svn_node_none),
603 cancel_func, cancel_baton,
612 remove_external(svn_boolean_t *removed,
613 svn_wc_context_t *wc_ctx,
614 const char *wri_abspath,
615 const char *local_abspath,
616 svn_node_kind_t external_kind,
617 svn_cancel_func_t cancel_func,
619 apr_pool_t *scratch_pool)
622 switch (external_kind)
625 SVN_WC__CALL_WITH_WRITE_LOCK(
626 remove_external2(removed,
628 local_abspath, external_kind,
629 cancel_func, cancel_baton,
631 wc_ctx, local_abspath, FALSE, scratch_pool);
635 SVN_ERR(remove_external2(removed,
637 local_abspath, external_kind,
638 cancel_func, cancel_baton,
646 /* Called when an external that is in the EXTERNALS table is no longer
647 referenced from an svn:externals property */
649 handle_external_item_removal(const svn_client_ctx_t *ctx,
650 const char *defining_abspath,
651 const char *local_abspath,
652 apr_pool_t *scratch_pool)
655 svn_node_kind_t external_kind;
656 svn_node_kind_t kind;
657 svn_boolean_t removed = FALSE;
659 /* local_abspath should be a wcroot or a file external */
660 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
661 ctx->wc_ctx, defining_abspath,
662 local_abspath, FALSE,
663 scratch_pool, scratch_pool));
665 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE,
668 if (external_kind != kind)
669 external_kind = svn_node_none; /* Only remove the registration */
671 err = remove_external(&removed,
672 ctx->wc_ctx, defining_abspath, local_abspath,
674 ctx->cancel_func, ctx->cancel_baton,
677 if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED && removed)
679 svn_error_clear(err);
680 err = NULL; /* We removed the working copy, so we can't release the
681 lock that was stored inside */
684 if (ctx->notify_func2)
686 svn_wc_notify_t *notify =
687 svn_wc_create_notify(local_abspath,
688 svn_wc_notify_update_external_removed,
694 (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
696 if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
698 notify = svn_wc_create_notify(local_abspath,
699 svn_wc_notify_left_local_modifications,
701 notify->kind = svn_node_dir;
704 (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
708 if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
710 svn_error_clear(err);
714 return svn_error_trace(err);
718 handle_external_item_change(svn_client_ctx_t *ctx,
719 const char *repos_root_url,
720 const char *parent_dir_abspath,
721 const char *parent_dir_url,
722 const char *local_abspath,
723 const char *old_defining_abspath,
724 const svn_wc_external_item2_t *new_item,
725 svn_boolean_t *timestamp_sleep,
726 apr_pool_t *scratch_pool)
728 svn_ra_session_t *ra_session;
729 svn_client__pathrev_t *new_loc;
731 svn_node_kind_t ext_kind;
733 SVN_ERR_ASSERT(repos_root_url && parent_dir_url);
734 SVN_ERR_ASSERT(new_item != NULL);
736 /* Don't bother to check status, since we'll get that for free by
737 attempting to retrieve the hash values anyway. */
739 /* When creating the absolute URL, use the pool and not the
740 iterpool, since the hash table values outlive the iterpool and
741 any pointers they have should also outlive the iterpool. */
743 SVN_ERR(svn_wc__resolve_relative_external_url(&new_url,
744 new_item, repos_root_url,
746 scratch_pool, scratch_pool));
748 /* Determine if the external is a file or directory. */
749 /* Get the RA connection. */
750 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
752 &(new_item->peg_revision),
753 &(new_item->revision), ctx,
756 SVN_ERR(svn_ra_check_path(ra_session, "", new_loc->rev, &ext_kind,
759 if (svn_node_none == ext_kind)
760 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
761 _("URL '%s' at revision %ld doesn't exist"),
762 new_loc->url, new_loc->rev);
764 if (svn_node_dir != ext_kind && svn_node_file != ext_kind)
765 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
766 _("URL '%s' at revision %ld is not a file "
768 new_loc->url, new_loc->rev);
771 /* Not protecting against recursive externals. Detecting them in
772 the global case is hard, and it should be pretty obvious to a
773 user when it happens. Worst case: your disk fills up :-). */
775 /* First notify that we're about to handle an external. */
776 if (ctx->notify_func2)
778 (*ctx->notify_func2)(
780 svn_wc_create_notify(local_abspath,
781 svn_wc_notify_update_external,
786 if (! old_defining_abspath)
788 /* The target dir might have multiple components. Guarantee the path
789 leading down to the last component. */
790 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath,
798 SVN_ERR(switch_dir_external(local_abspath, new_loc->url,
800 &(new_item->peg_revision),
801 &(new_item->revision),
803 timestamp_sleep, ctx,
807 if (strcmp(repos_root_url, new_loc->repos_root_url))
809 const char *local_repos_root_url;
810 const char *local_repos_uuid;
811 const char *ext_repos_relpath;
815 * The working copy library currently requires that all files
816 * in the working copy have the same repository root URL.
817 * The URL from the file external's definition differs from the
818 * one used by the working copy. As a workaround, replace the
819 * root URL portion of the file external's URL, after making
820 * sure both URLs point to the same repository. See issue #4087.
823 err = svn_wc__node_get_repos_info(NULL, NULL,
824 &local_repos_root_url,
826 ctx->wc_ctx, parent_dir_abspath,
827 scratch_pool, scratch_pool);
830 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
831 && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
832 return svn_error_trace(err);
834 svn_error_clear(err);
835 local_repos_root_url = NULL;
836 local_repos_uuid = NULL;
839 ext_repos_relpath = svn_uri_skip_ancestor(new_loc->repos_root_url,
840 new_url, scratch_pool);
841 if (local_repos_uuid == NULL || local_repos_root_url == NULL ||
842 ext_repos_relpath == NULL ||
843 strcmp(local_repos_uuid, new_loc->repos_uuid) != 0)
844 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
845 _("Unsupported external: URL of file external '%s' "
846 "is not in repository '%s'"),
847 new_url, repos_root_url);
849 new_url = svn_path_url_add_component2(local_repos_root_url,
852 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
855 &(new_item->peg_revision),
856 &(new_item->revision),
860 SVN_ERR(switch_file_external(local_abspath,
862 &new_item->peg_revision,
871 SVN_ERR_MALFUNCTION();
879 wrap_external_error(const svn_client_ctx_t *ctx,
880 const char *target_abspath,
882 apr_pool_t *scratch_pool)
884 if (err && err->apr_err != SVN_ERR_CANCELLED)
886 if (ctx->notify_func2)
888 svn_wc_notify_t *notifier = svn_wc_create_notify(
890 svn_wc_notify_failed_external,
893 ctx->notify_func2(ctx->notify_baton2, notifier, scratch_pool);
895 svn_error_clear(err);
903 handle_externals_change(svn_client_ctx_t *ctx,
904 const char *repos_root_url,
905 svn_boolean_t *timestamp_sleep,
906 const char *local_abspath,
907 const char *new_desc_text,
908 apr_hash_t *old_externals,
909 svn_depth_t ambient_depth,
910 svn_depth_t requested_depth,
911 apr_pool_t *scratch_pool)
913 apr_array_header_t *new_desc;
915 apr_pool_t *iterpool;
918 iterpool = svn_pool_create(scratch_pool);
920 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
922 /* Bag out if the depth here is too shallow for externals action. */
923 if ((requested_depth < svn_depth_infinity
924 && requested_depth != svn_depth_unknown)
925 || (ambient_depth < svn_depth_infinity
926 && requested_depth < svn_depth_infinity))
930 SVN_ERR(svn_wc_parse_externals_description3(&new_desc, local_abspath,
932 FALSE, scratch_pool));
936 SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, local_abspath,
937 scratch_pool, iterpool));
941 for (i = 0; new_desc && (i < new_desc->nelts); i++)
943 const char *old_defining_abspath;
944 svn_wc_external_item2_t *new_item;
945 const char *target_abspath;
946 svn_boolean_t under_root;
948 new_item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *);
950 svn_pool_clear(iterpool);
952 if (ctx->cancel_func)
953 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
955 SVN_ERR(svn_dirent_is_under_root(&under_root, &target_abspath,
956 local_abspath, new_item->target_dir,
961 return svn_error_createf(
962 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
963 _("Path '%s' is not in the working copy"),
964 svn_dirent_local_style(
965 svn_dirent_join(local_abspath, new_item->target_dir,
970 old_defining_abspath = svn_hash_gets(old_externals, target_abspath);
972 SVN_ERR(wrap_external_error(
974 handle_external_item_change(ctx,
978 old_defining_abspath,
984 /* And remove already processed items from the to-remove hash */
985 if (old_defining_abspath)
986 svn_hash_sets(old_externals, target_abspath, NULL);
989 svn_pool_destroy(iterpool);
996 svn_client__handle_externals(apr_hash_t *externals_new,
997 apr_hash_t *ambient_depths,
998 const char *repos_root_url,
999 const char *target_abspath,
1000 svn_depth_t requested_depth,
1001 svn_boolean_t *timestamp_sleep,
1002 svn_client_ctx_t *ctx,
1003 apr_pool_t *scratch_pool)
1005 apr_hash_t *old_external_defs;
1006 apr_hash_index_t *hi;
1007 apr_pool_t *iterpool;
1009 SVN_ERR_ASSERT(repos_root_url);
1011 iterpool = svn_pool_create(scratch_pool);
1013 SVN_ERR(svn_wc__externals_defined_below(&old_external_defs,
1014 ctx->wc_ctx, target_abspath,
1015 scratch_pool, iterpool));
1017 for (hi = apr_hash_first(scratch_pool, externals_new);
1019 hi = apr_hash_next(hi))
1021 const char *local_abspath = svn__apr_hash_index_key(hi);
1022 const char *desc_text = svn__apr_hash_index_val(hi);
1023 svn_depth_t ambient_depth = svn_depth_infinity;
1025 svn_pool_clear(iterpool);
1029 const char *ambient_depth_w;
1031 ambient_depth_w = apr_hash_get(ambient_depths, local_abspath,
1032 svn__apr_hash_index_klen(hi));
1034 if (ambient_depth_w == NULL)
1036 return svn_error_createf(
1037 SVN_ERR_WC_CORRUPT, NULL,
1038 _("Traversal of '%s' found no ambient depth"),
1039 svn_dirent_local_style(local_abspath, scratch_pool));
1043 ambient_depth = svn_depth_from_word(ambient_depth_w);
1047 SVN_ERR(handle_externals_change(ctx, repos_root_url, timestamp_sleep,
1049 desc_text, old_external_defs,
1050 ambient_depth, requested_depth,
1054 /* Remove the remaining externals */
1055 for (hi = apr_hash_first(scratch_pool, old_external_defs);
1057 hi = apr_hash_next(hi))
1059 const char *item_abspath = svn__apr_hash_index_key(hi);
1060 const char *defining_abspath = svn__apr_hash_index_val(hi);
1061 const char *parent_abspath;
1063 svn_pool_clear(iterpool);
1065 SVN_ERR(wrap_external_error(
1067 handle_external_item_removal(ctx, defining_abspath,
1068 item_abspath, iterpool),
1071 /* Are there any unversioned directories between the removed
1072 * external and the DEFINING_ABSPATH which we can remove? */
1073 parent_abspath = item_abspath;
1075 svn_node_kind_t kind;
1077 parent_abspath = svn_dirent_dirname(parent_abspath, iterpool);
1078 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, parent_abspath,
1079 FALSE /* show_deleted*/,
1080 FALSE /* show_hidden */,
1082 if (kind == svn_node_none)
1086 err = svn_io_dir_remove_nonrecursive(parent_abspath, iterpool);
1089 if (APR_STATUS_IS_ENOTEMPTY(err->apr_err))
1091 svn_error_clear(err);
1092 break; /* No parents to delete */
1094 else if (APR_STATUS_IS_ENOENT(err->apr_err)
1095 || APR_STATUS_IS_ENOTDIR(err->apr_err))
1097 svn_error_clear(err);
1098 /* Fall through; parent dir might be unversioned */
1101 return svn_error_trace(err);
1104 } while (strcmp(parent_abspath, defining_abspath) != 0);
1108 svn_pool_destroy(iterpool);
1109 return SVN_NO_ERROR;
1114 svn_client__export_externals(apr_hash_t *externals,
1115 const char *from_url,
1116 const char *to_abspath,
1117 const char *repos_root_url,
1118 svn_depth_t requested_depth,
1119 const char *native_eol,
1120 svn_boolean_t ignore_keywords,
1121 svn_client_ctx_t *ctx,
1122 apr_pool_t *scratch_pool)
1124 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1125 apr_pool_t *sub_iterpool = svn_pool_create(scratch_pool);
1126 apr_hash_index_t *hi;
1128 SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath));
1130 for (hi = apr_hash_first(scratch_pool, externals);
1132 hi = apr_hash_next(hi))
1134 const char *local_abspath = svn__apr_hash_index_key(hi);
1135 const char *desc_text = svn__apr_hash_index_val(hi);
1136 const char *local_relpath;
1137 const char *dir_url;
1138 apr_array_header_t *items;
1141 svn_pool_clear(iterpool);
1143 SVN_ERR(svn_wc_parse_externals_description3(&items, local_abspath,
1150 local_relpath = svn_dirent_skip_ancestor(to_abspath, local_abspath);
1152 dir_url = svn_path_url_add_component2(from_url, local_relpath,
1155 for (i = 0; i < items->nelts; i++)
1157 const char *item_abspath;
1158 const char *new_url;
1159 svn_boolean_t under_root;
1160 svn_wc_external_item2_t *item = APR_ARRAY_IDX(items, i,
1161 svn_wc_external_item2_t *);
1163 svn_pool_clear(sub_iterpool);
1165 SVN_ERR(svn_dirent_is_under_root(&under_root, &item_abspath,
1166 local_abspath, item->target_dir,
1171 return svn_error_createf(
1172 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1173 _("Path '%s' is not in the working copy"),
1174 svn_dirent_local_style(
1175 svn_dirent_join(local_abspath, item->target_dir,
1180 SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, item,
1182 dir_url, sub_iterpool,
1185 /* The target dir might have multiple components. Guarantee
1186 the path leading down to the last component. */
1187 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(item_abspath,
1191 SVN_ERR(wrap_external_error(
1193 svn_client_export5(NULL, new_url, item_abspath,
1194 &item->peg_revision,
1196 TRUE, FALSE, ignore_keywords,
1204 svn_pool_destroy(sub_iterpool);
1205 svn_pool_destroy(iterpool);
1207 return SVN_NO_ERROR;