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 svn_opt_revision_t *peg_revision,
150 const svn_opt_revision_t *revision,
151 const char *defining_abspath,
152 svn_boolean_t *timestamp_sleep,
153 svn_client_ctx_t *ctx,
156 svn_node_kind_t kind;
158 svn_revnum_t external_peg_rev = SVN_INVALID_REVNUM;
159 svn_revnum_t external_rev = SVN_INVALID_REVNUM;
160 apr_pool_t *subpool = svn_pool_create(pool);
161 const char *repos_root_url;
162 const char *repos_uuid;
164 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
166 if (peg_revision->kind == svn_opt_revision_number)
167 external_peg_rev = peg_revision->value.number;
169 if (revision->kind == svn_opt_revision_number)
170 external_rev = revision->value.number;
172 /* If path is a directory, try to update/switch to the correct URL
174 SVN_ERR(svn_io_check_path(local_abspath, &kind, pool));
175 if (kind == svn_node_dir)
177 const char *node_url;
179 /* Doubles as an "is versioned" check. */
180 err = svn_wc__node_get_url(&node_url, ctx->wc_ctx, local_abspath,
182 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
184 svn_error_clear(err);
189 return svn_error_trace(err);
193 /* If we have what appears to be a version controlled
194 subdir, and its top-level URL matches that of our
195 externals definition, perform an update. */
196 if (strcmp(node_url, url) == 0)
198 SVN_ERR(svn_client__update_internal(NULL, local_abspath,
199 revision, svn_depth_unknown,
200 FALSE, FALSE, FALSE, TRUE,
204 svn_pool_destroy(subpool);
208 /* We'd really prefer not to have to do a brute-force
209 relegation -- blowing away the current external working
210 copy and checking it out anew -- so we'll first see if we
211 can get away with a generally cheaper relocation (if
212 required) and switch-style update.
214 To do so, we need to know the repository root URL of the
215 external working copy as it currently sits. */
216 err = svn_wc__node_get_repos_info(NULL, NULL,
217 &repos_root_url, &repos_uuid,
218 ctx->wc_ctx, local_abspath,
222 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
223 && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
224 return svn_error_trace(err);
226 svn_error_clear(err);
227 repos_root_url = NULL;
233 /* If the new external target URL is not obviously a
234 child of the external working copy's current
235 repository root URL... */
236 if (! svn_uri__is_ancestor(repos_root_url, url))
238 const char *repos_root;
240 /* ... then figure out precisely which repository
241 root URL that target URL *is* a child of ... */
242 SVN_ERR(svn_client_get_repos_root(&repos_root, NULL, url,
243 ctx, subpool, subpool));
245 /* ... and use that to try to relocate the external
246 working copy to the target location. */
247 err = svn_client_relocate2(local_abspath, repos_root_url,
248 repos_root, FALSE, ctx, subpool);
250 /* If the relocation failed because the new URL
251 points to a totally different repository, we've
252 no choice but to relegate and check out a new WC. */
254 && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION
256 == SVN_ERR_CLIENT_INVALID_RELOCATION)))
258 svn_error_clear(err);
262 return svn_error_trace(err);
264 /* If the relocation went without a hitch, we should
265 have a new repository root URL. */
266 repos_root_url = repos_root;
269 SVN_ERR(svn_client__switch_internal(NULL, local_abspath, url,
270 peg_revision, revision,
273 TRUE /* ignore_ancestry */,
277 SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
279 local_abspath, svn_node_dir,
280 repos_root_url, repos_uuid,
281 svn_uri_skip_ancestor(
288 svn_pool_destroy(subpool);
296 /* Fall back on removing the WC and checking out a new one. */
298 /* Ensure that we don't have any RA sessions or WC locks from failed
300 svn_pool_destroy(subpool);
302 if (kind == svn_node_dir)
304 /* Buh-bye, old and busted ... */
305 SVN_ERR(relegate_dir_external(ctx->wc_ctx, defining_abspath,
307 ctx->cancel_func, ctx->cancel_baton,
308 ctx->notify_func2, ctx->notify_baton2,
313 /* The target dir might have multiple components. Guarantee
314 the path leading down to the last component. */
315 const char *parent = svn_dirent_dirname(local_abspath, pool);
316 SVN_ERR(svn_io_make_dir_recursively(parent, pool));
319 /* ... Hello, new hotness. */
320 SVN_ERR(svn_client__checkout_internal(NULL, url, local_abspath, peg_revision,
321 revision, svn_depth_infinity,
322 FALSE, FALSE, timestamp_sleep,
325 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL,
328 ctx->wc_ctx, local_abspath,
331 SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
333 local_abspath, svn_node_dir,
334 repos_root_url, repos_uuid,
335 svn_uri_skip_ancestor(repos_root_url,
342 /* Issues #4123 and #4130: We don't need to keep the newly checked
343 out external's DB open. */
344 SVN_ERR(svn_wc__close_db(local_abspath, ctx->wc_ctx, pool));
349 /* Try to update a file external at LOCAL_ABSPATH to URL at REVISION using a
350 access baton that has a write lock. Use SCRATCH_POOL for temporary
351 allocations, and use the client context CTX. */
353 switch_file_external(const char *local_abspath,
355 const svn_opt_revision_t *peg_revision,
356 const svn_opt_revision_t *revision,
357 const char *def_dir_abspath,
358 svn_ra_session_t *ra_session,
359 svn_client_ctx_t *ctx,
360 apr_pool_t *scratch_pool)
362 svn_config_t *cfg = ctx->config
363 ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
365 svn_boolean_t use_commit_times;
366 const char *diff3_cmd;
367 const char *preserved_exts_str;
368 const apr_array_header_t *preserved_exts;
369 svn_node_kind_t kind, external_kind;
371 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
373 /* See if the user wants last-commit timestamps instead of current ones. */
374 SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
375 SVN_CONFIG_SECTION_MISCELLANY,
376 SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
378 /* Get the external diff3, if any. */
379 svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
380 SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
382 if (diff3_cmd != NULL)
383 SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool));
385 /* See which files the user wants to preserve the extension of when
386 conflict files are made. */
387 svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
388 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
389 preserved_exts = *preserved_exts_str
390 ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool)
394 const char *wcroot_abspath;
396 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath,
397 scratch_pool, scratch_pool));
399 /* File externals can only be installed inside the current working copy.
400 So verify if the working copy that contains/will contain the target
401 is the defining abspath, or one of its ancestors */
403 if (!svn_dirent_is_ancestor(wcroot_abspath, def_dir_abspath))
404 return svn_error_createf(
405 SVN_ERR_WC_BAD_PATH, NULL,
406 _("Cannot insert a file external defined on '%s' "
407 "into the working copy '%s'."),
408 svn_dirent_local_style(def_dir_abspath,
410 svn_dirent_local_style(wcroot_abspath,
414 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath,
415 TRUE, FALSE, scratch_pool));
417 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
418 ctx->wc_ctx, local_abspath, local_abspath,
419 TRUE, scratch_pool, scratch_pool));
421 /* If there is a versioned item with this name, ensure it's a file
422 external before working with it. If there is no entry in the
423 working copy, then create an empty file and add it to the working
425 if (kind != svn_node_none && kind != svn_node_unknown)
427 if (external_kind != svn_node_file)
429 return svn_error_createf(
430 SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0,
431 _("The file external from '%s' cannot overwrite the existing "
432 "versioned item at '%s'"),
433 url, svn_dirent_local_style(local_abspath, scratch_pool));
438 svn_node_kind_t disk_kind;
440 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
442 if (kind == svn_node_file || kind == svn_node_dir)
443 return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL,
444 _("The file external '%s' can not be "
445 "created because the node exists."),
446 svn_dirent_local_style(local_abspath,
451 const svn_ra_reporter3_t *reporter;
453 const svn_delta_editor_t *switch_editor;
455 svn_client__pathrev_t *switch_loc;
457 apr_array_header_t *inherited_props;
458 const char *dir_abspath;
461 svn_dirent_split(&dir_abspath, &target, local_abspath, scratch_pool);
463 /* Open an RA session to 'source' URL */
464 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc,
466 peg_revision, revision,
468 /* Get the external file's iprops. */
469 SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "",
471 scratch_pool, scratch_pool));
473 SVN_ERR(svn_ra_reparent(ra_session, svn_uri_dirname(url, scratch_pool),
476 SVN_ERR(svn_wc__get_file_external_editor(&switch_editor, &switch_baton,
477 &revnum, ctx->wc_ctx,
481 switch_loc->repos_root_url,
482 switch_loc->repos_uuid,
485 diff3_cmd, preserved_exts,
487 url, peg_revision, revision,
489 ctx->conflict_baton2,
494 scratch_pool, scratch_pool));
496 /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
497 invalid revnum, that means RA will use the latest revision. */
498 SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton,
500 target, svn_depth_unknown, url,
501 FALSE /* send_copyfrom */,
502 TRUE /* ignore_ancestry */,
503 switch_editor, switch_baton,
504 scratch_pool, scratch_pool));
506 SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath,
507 reporter, report_baton,
508 TRUE, use_commit_times,
509 ctx->cancel_func, ctx->cancel_baton,
510 ctx->notify_func2, ctx->notify_baton2,
513 if (ctx->notify_func2)
515 svn_wc_notify_t *notify
516 = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
518 notify->kind = svn_node_none;
519 notify->content_state = notify->prop_state
520 = svn_wc_notify_state_inapplicable;
521 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
522 notify->revision = revnum;
523 (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
530 /* Wrappers around svn_wc__external_remove, obtaining and releasing a lock for
531 directory externals */
533 remove_external2(svn_boolean_t *removed,
534 svn_wc_context_t *wc_ctx,
535 const char *wri_abspath,
536 const char *local_abspath,
537 svn_node_kind_t external_kind,
538 svn_cancel_func_t cancel_func,
540 apr_pool_t *scratch_pool)
542 SVN_ERR(svn_wc__external_remove(wc_ctx, wri_abspath,
544 (external_kind == svn_node_none),
545 cancel_func, cancel_baton,
554 remove_external(svn_boolean_t *removed,
555 svn_wc_context_t *wc_ctx,
556 const char *wri_abspath,
557 const char *local_abspath,
558 svn_node_kind_t external_kind,
559 svn_cancel_func_t cancel_func,
561 apr_pool_t *scratch_pool)
564 switch (external_kind)
567 SVN_WC__CALL_WITH_WRITE_LOCK(
568 remove_external2(removed,
570 local_abspath, external_kind,
571 cancel_func, cancel_baton,
573 wc_ctx, local_abspath, FALSE, scratch_pool);
577 SVN_ERR(remove_external2(removed,
579 local_abspath, external_kind,
580 cancel_func, cancel_baton,
588 /* Called when an external that is in the EXTERNALS table is no longer
589 referenced from an svn:externals property */
591 handle_external_item_removal(const svn_client_ctx_t *ctx,
592 const char *defining_abspath,
593 const char *local_abspath,
594 apr_pool_t *scratch_pool)
597 svn_node_kind_t external_kind;
598 svn_node_kind_t kind;
599 svn_boolean_t removed = FALSE;
601 /* local_abspath should be a wcroot or a file external */
602 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
603 ctx->wc_ctx, defining_abspath,
604 local_abspath, FALSE,
605 scratch_pool, scratch_pool));
607 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE,
610 if (external_kind != kind)
611 external_kind = svn_node_none; /* Only remove the registration */
613 err = remove_external(&removed,
614 ctx->wc_ctx, defining_abspath, local_abspath,
616 ctx->cancel_func, ctx->cancel_baton,
619 if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED && removed)
621 svn_error_clear(err);
622 err = NULL; /* We removed the working copy, so we can't release the
623 lock that was stored inside */
626 if (ctx->notify_func2)
628 svn_wc_notify_t *notify =
629 svn_wc_create_notify(local_abspath,
630 svn_wc_notify_update_external_removed,
636 (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
638 if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
640 notify = svn_wc_create_notify(local_abspath,
641 svn_wc_notify_left_local_modifications,
643 notify->kind = svn_node_dir;
646 (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
650 if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
652 svn_error_clear(err);
656 return svn_error_trace(err);
660 handle_external_item_change(svn_client_ctx_t *ctx,
661 const char *repos_root_url,
662 const char *parent_dir_abspath,
663 const char *parent_dir_url,
664 const char *local_abspath,
665 const char *old_defining_abspath,
666 const svn_wc_external_item2_t *new_item,
667 svn_boolean_t *timestamp_sleep,
668 apr_pool_t *scratch_pool)
670 svn_ra_session_t *ra_session;
671 svn_client__pathrev_t *new_loc;
673 svn_node_kind_t ext_kind;
675 SVN_ERR_ASSERT(repos_root_url && parent_dir_url);
676 SVN_ERR_ASSERT(new_item != NULL);
678 /* Don't bother to check status, since we'll get that for free by
679 attempting to retrieve the hash values anyway. */
681 /* When creating the absolute URL, use the pool and not the
682 iterpool, since the hash table values outlive the iterpool and
683 any pointers they have should also outlive the iterpool. */
685 SVN_ERR(svn_wc__resolve_relative_external_url(&new_url,
686 new_item, repos_root_url,
688 scratch_pool, scratch_pool));
690 /* Determine if the external is a file or directory. */
691 /* Get the RA connection. */
692 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
694 &(new_item->peg_revision),
695 &(new_item->revision), ctx,
698 SVN_ERR(svn_ra_check_path(ra_session, "", new_loc->rev, &ext_kind,
701 if (svn_node_none == ext_kind)
702 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
703 _("URL '%s' at revision %ld doesn't exist"),
704 new_loc->url, new_loc->rev);
706 if (svn_node_dir != ext_kind && svn_node_file != ext_kind)
707 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
708 _("URL '%s' at revision %ld is not a file "
710 new_loc->url, new_loc->rev);
713 /* Not protecting against recursive externals. Detecting them in
714 the global case is hard, and it should be pretty obvious to a
715 user when it happens. Worst case: your disk fills up :-). */
717 /* First notify that we're about to handle an external. */
718 if (ctx->notify_func2)
720 (*ctx->notify_func2)(
722 svn_wc_create_notify(local_abspath,
723 svn_wc_notify_update_external,
728 if (! old_defining_abspath)
730 /* The target dir might have multiple components. Guarantee the path
731 leading down to the last component. */
732 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath,
740 SVN_ERR(switch_dir_external(local_abspath, new_url,
741 &(new_item->peg_revision),
742 &(new_item->revision),
744 timestamp_sleep, ctx,
748 if (strcmp(repos_root_url, new_loc->repos_root_url))
750 const char *local_repos_root_url;
751 const char *local_repos_uuid;
752 const char *ext_repos_relpath;
756 * The working copy library currently requires that all files
757 * in the working copy have the same repository root URL.
758 * The URL from the file external's definition differs from the
759 * one used by the working copy. As a workaround, replace the
760 * root URL portion of the file external's URL, after making
761 * sure both URLs point to the same repository. See issue #4087.
764 err = svn_wc__node_get_repos_info(NULL, NULL,
765 &local_repos_root_url,
767 ctx->wc_ctx, parent_dir_abspath,
768 scratch_pool, scratch_pool);
771 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
772 && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
773 return svn_error_trace(err);
775 svn_error_clear(err);
776 local_repos_root_url = NULL;
777 local_repos_uuid = NULL;
780 ext_repos_relpath = svn_uri_skip_ancestor(new_loc->repos_root_url,
781 new_url, scratch_pool);
782 if (local_repos_uuid == NULL || local_repos_root_url == NULL ||
783 ext_repos_relpath == NULL ||
784 strcmp(local_repos_uuid, new_loc->repos_uuid) != 0)
785 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
786 _("Unsupported external: URL of file external '%s' "
787 "is not in repository '%s'"),
788 new_url, repos_root_url);
790 new_url = svn_path_url_add_component2(local_repos_root_url,
793 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
796 &(new_item->peg_revision),
797 &(new_item->revision),
801 SVN_ERR(switch_file_external(local_abspath,
803 &new_item->peg_revision,
812 SVN_ERR_MALFUNCTION();
820 wrap_external_error(const svn_client_ctx_t *ctx,
821 const char *target_abspath,
823 apr_pool_t *scratch_pool)
825 if (err && err->apr_err != SVN_ERR_CANCELLED)
827 if (ctx->notify_func2)
829 svn_wc_notify_t *notifier = svn_wc_create_notify(
831 svn_wc_notify_failed_external,
834 ctx->notify_func2(ctx->notify_baton2, notifier, scratch_pool);
836 svn_error_clear(err);
844 handle_externals_change(svn_client_ctx_t *ctx,
845 const char *repos_root_url,
846 svn_boolean_t *timestamp_sleep,
847 const char *local_abspath,
848 const char *new_desc_text,
849 apr_hash_t *old_externals,
850 svn_depth_t ambient_depth,
851 svn_depth_t requested_depth,
852 apr_pool_t *scratch_pool)
854 apr_array_header_t *new_desc;
856 apr_pool_t *iterpool;
859 iterpool = svn_pool_create(scratch_pool);
861 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
863 /* Bag out if the depth here is too shallow for externals action. */
864 if ((requested_depth < svn_depth_infinity
865 && requested_depth != svn_depth_unknown)
866 || (ambient_depth < svn_depth_infinity
867 && requested_depth < svn_depth_infinity))
871 SVN_ERR(svn_wc_parse_externals_description3(&new_desc, local_abspath,
873 FALSE, scratch_pool));
877 SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, local_abspath,
878 scratch_pool, iterpool));
882 for (i = 0; new_desc && (i < new_desc->nelts); i++)
884 const char *old_defining_abspath;
885 svn_wc_external_item2_t *new_item;
886 const char *target_abspath;
887 svn_boolean_t under_root;
889 new_item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *);
891 svn_pool_clear(iterpool);
893 if (ctx->cancel_func)
894 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
896 SVN_ERR(svn_dirent_is_under_root(&under_root, &target_abspath,
897 local_abspath, new_item->target_dir,
902 return svn_error_createf(
903 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
904 _("Path '%s' is not in the working copy"),
905 svn_dirent_local_style(
906 svn_dirent_join(local_abspath, new_item->target_dir,
911 old_defining_abspath = svn_hash_gets(old_externals, target_abspath);
913 SVN_ERR(wrap_external_error(
915 handle_external_item_change(ctx,
919 old_defining_abspath,
925 /* And remove already processed items from the to-remove hash */
926 if (old_defining_abspath)
927 svn_hash_sets(old_externals, target_abspath, NULL);
930 svn_pool_destroy(iterpool);
937 svn_client__handle_externals(apr_hash_t *externals_new,
938 apr_hash_t *ambient_depths,
939 const char *repos_root_url,
940 const char *target_abspath,
941 svn_depth_t requested_depth,
942 svn_boolean_t *timestamp_sleep,
943 svn_client_ctx_t *ctx,
944 apr_pool_t *scratch_pool)
946 apr_hash_t *old_external_defs;
947 apr_hash_index_t *hi;
948 apr_pool_t *iterpool;
950 SVN_ERR_ASSERT(repos_root_url);
952 iterpool = svn_pool_create(scratch_pool);
954 SVN_ERR(svn_wc__externals_defined_below(&old_external_defs,
955 ctx->wc_ctx, target_abspath,
956 scratch_pool, iterpool));
958 for (hi = apr_hash_first(scratch_pool, externals_new);
960 hi = apr_hash_next(hi))
962 const char *local_abspath = svn__apr_hash_index_key(hi);
963 const char *desc_text = svn__apr_hash_index_val(hi);
964 svn_depth_t ambient_depth = svn_depth_infinity;
966 svn_pool_clear(iterpool);
970 const char *ambient_depth_w;
972 ambient_depth_w = apr_hash_get(ambient_depths, local_abspath,
973 svn__apr_hash_index_klen(hi));
975 if (ambient_depth_w == NULL)
977 return svn_error_createf(
978 SVN_ERR_WC_CORRUPT, NULL,
979 _("Traversal of '%s' found no ambient depth"),
980 svn_dirent_local_style(local_abspath, scratch_pool));
984 ambient_depth = svn_depth_from_word(ambient_depth_w);
988 SVN_ERR(handle_externals_change(ctx, repos_root_url, timestamp_sleep,
990 desc_text, old_external_defs,
991 ambient_depth, requested_depth,
995 /* Remove the remaining externals */
996 for (hi = apr_hash_first(scratch_pool, old_external_defs);
998 hi = apr_hash_next(hi))
1000 const char *item_abspath = svn__apr_hash_index_key(hi);
1001 const char *defining_abspath = svn__apr_hash_index_val(hi);
1002 const char *parent_abspath;
1004 svn_pool_clear(iterpool);
1006 SVN_ERR(wrap_external_error(
1008 handle_external_item_removal(ctx, defining_abspath,
1009 item_abspath, iterpool),
1012 /* Are there any unversioned directories between the removed
1013 * external and the DEFINING_ABSPATH which we can remove? */
1014 parent_abspath = item_abspath;
1016 svn_node_kind_t kind;
1018 parent_abspath = svn_dirent_dirname(parent_abspath, iterpool);
1019 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, parent_abspath,
1020 TRUE, FALSE, iterpool));
1021 if (kind == svn_node_none)
1025 err = svn_io_dir_remove_nonrecursive(parent_abspath, iterpool);
1026 if (err && APR_STATUS_IS_ENOTEMPTY(err->apr_err))
1028 svn_error_clear(err);
1034 } while (strcmp(parent_abspath, defining_abspath) != 0);
1038 svn_pool_destroy(iterpool);
1039 return SVN_NO_ERROR;
1044 svn_client__export_externals(apr_hash_t *externals,
1045 const char *from_url,
1046 const char *to_abspath,
1047 const char *repos_root_url,
1048 svn_depth_t requested_depth,
1049 const char *native_eol,
1050 svn_boolean_t ignore_keywords,
1051 svn_client_ctx_t *ctx,
1052 apr_pool_t *scratch_pool)
1054 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1055 apr_pool_t *sub_iterpool = svn_pool_create(scratch_pool);
1056 apr_hash_index_t *hi;
1058 SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath));
1060 for (hi = apr_hash_first(scratch_pool, externals);
1062 hi = apr_hash_next(hi))
1064 const char *local_abspath = svn__apr_hash_index_key(hi);
1065 const char *desc_text = svn__apr_hash_index_val(hi);
1066 const char *local_relpath;
1067 const char *dir_url;
1068 apr_array_header_t *items;
1071 svn_pool_clear(iterpool);
1073 SVN_ERR(svn_wc_parse_externals_description3(&items, local_abspath,
1080 local_relpath = svn_dirent_skip_ancestor(to_abspath, local_abspath);
1082 dir_url = svn_path_url_add_component2(from_url, local_relpath,
1085 for (i = 0; i < items->nelts; i++)
1087 const char *item_abspath;
1088 const char *new_url;
1089 svn_boolean_t under_root;
1090 svn_wc_external_item2_t *item = APR_ARRAY_IDX(items, i,
1091 svn_wc_external_item2_t *);
1093 svn_pool_clear(sub_iterpool);
1095 SVN_ERR(svn_dirent_is_under_root(&under_root, &item_abspath,
1096 local_abspath, item->target_dir,
1101 return svn_error_createf(
1102 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1103 _("Path '%s' is not in the working copy"),
1104 svn_dirent_local_style(
1105 svn_dirent_join(local_abspath, item->target_dir,
1110 SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, item,
1112 dir_url, sub_iterpool,
1115 /* The target dir might have multiple components. Guarantee
1116 the path leading down to the last component. */
1117 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(item_abspath,
1121 SVN_ERR(wrap_external_error(
1123 svn_client_export5(NULL, new_url, item_abspath,
1124 &item->peg_revision,
1126 TRUE, FALSE, ignore_keywords,
1134 svn_pool_destroy(sub_iterpool);
1135 svn_pool_destroy(iterpool);
1137 return SVN_NO_ERROR;