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_ra_session_t *ra_session,
155 svn_client_ctx_t *ctx,
158 svn_node_kind_t kind;
160 svn_revnum_t external_peg_rev = SVN_INVALID_REVNUM;
161 svn_revnum_t external_rev = SVN_INVALID_REVNUM;
162 apr_pool_t *subpool = svn_pool_create(pool);
163 const char *repos_root_url;
164 const char *repos_uuid;
166 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
168 if (peg_revision->kind == svn_opt_revision_number)
169 external_peg_rev = peg_revision->value.number;
171 if (revision->kind == svn_opt_revision_number)
172 external_rev = revision->value.number;
175 * The code below assumes existing versioned paths are *not* part of
176 * the external's defining working copy.
177 * The working copy library does not support registering externals
178 * on top of existing BASE nodes and will error out if we try.
179 * So if the external target is part of the defining working copy's
180 * BASE tree, don't attempt to create the external. Doing so would
181 * leave behind a switched path instead of an external (since the
182 * switch succeeds but registration of the external in the DB fails).
183 * The working copy then cannot be updated until the path is switched back.
186 SVN_ERR(svn_wc__node_get_base(&kind, NULL, NULL,
187 &repos_root_url, &repos_uuid,
188 NULL, ctx->wc_ctx, local_abspath,
189 TRUE, /* ignore_enoent */
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 svn_boolean_t is_wcroot;
236 SVN_ERR(svn_wc__is_wcroot(&is_wcroot, ctx->wc_ctx, local_abspath,
241 /* This can't be a directory external! */
243 err = svn_wc__external_remove(ctx->wc_ctx, defining_abspath,
245 TRUE /* declaration_only */,
246 ctx->cancel_func, ctx->cancel_baton,
249 if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
251 /* New external... No problem that we can't remove it */
252 svn_error_clear(err);
256 return svn_error_trace(err);
258 return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
259 _("The external '%s' defined in %s at '%s' "
260 "cannot be checked out because '%s' is "
261 "already a versioned path."),
262 url_from_externals_definition,
264 svn_dirent_local_style(defining_abspath,
266 svn_dirent_local_style(local_abspath,
270 /* If we have what appears to be a version controlled
271 subdir, and its top-level URL matches that of our
272 externals definition, perform an update. */
273 if (strcmp(node_url, url) == 0)
275 SVN_ERR(svn_client__update_internal(NULL, timestamp_sleep,
277 revision, svn_depth_unknown,
278 FALSE, FALSE, FALSE, TRUE,
280 ra_session, ctx, subpool));
282 /* We just decided that this existing directory is an external,
283 so update the external registry with this information, like
284 when checking out an external */
285 SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
287 local_abspath, svn_node_dir,
288 repos_root_url, repos_uuid,
289 svn_uri_skip_ancestor(repos_root_url,
295 svn_pool_destroy(subpool);
299 /* We'd really prefer not to have to do a brute-force
300 relegation -- blowing away the current external working
301 copy and checking it out anew -- so we'll first see if we
302 can get away with a generally cheaper relocation (if
303 required) and switch-style update.
305 To do so, we need to know the repository root URL of the
306 external working copy as it currently sits. */
307 err = svn_wc__node_get_repos_info(NULL, NULL,
308 &repos_root_url, &repos_uuid,
309 ctx->wc_ctx, local_abspath,
313 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
314 && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
315 return svn_error_trace(err);
317 svn_error_clear(err);
318 repos_root_url = NULL;
324 /* If the new external target URL is not obviously a
325 child of the external working copy's current
326 repository root URL... */
327 if (! svn_uri__is_ancestor(repos_root_url, url))
329 const char *repos_root;
331 /* ... then figure out precisely which repository
332 root URL that target URL *is* a child of ... */
333 SVN_ERR(svn_client_get_repos_root(&repos_root, NULL, url,
334 ctx, subpool, subpool));
336 /* ... and use that to try to relocate the external
337 working copy to the target location. */
338 err = svn_client_relocate2(local_abspath, repos_root_url,
339 repos_root, FALSE, ctx, subpool);
341 /* If the relocation failed because the new URL
342 points to a totally different repository, we've
343 no choice but to relegate and check out a new WC. */
345 && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION
347 == SVN_ERR_CLIENT_INVALID_RELOCATION)))
349 svn_error_clear(err);
353 return svn_error_trace(err);
355 /* If the relocation went without a hitch, we should
356 have a new repository root URL. */
357 repos_root_url = repos_root;
360 SVN_ERR(svn_client__switch_internal(NULL, local_abspath, url,
361 peg_revision, revision,
364 TRUE /* ignore_ancestry */,
368 SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
370 local_abspath, svn_node_dir,
371 repos_root_url, repos_uuid,
372 svn_uri_skip_ancestor(
379 svn_pool_destroy(subpool);
387 /* Fall back on removing the WC and checking out a new one. */
389 /* Ensure that we don't have any RA sessions or WC locks from failed
391 svn_pool_destroy(subpool);
393 if (kind == svn_node_dir)
395 /* Buh-bye, old and busted ... */
396 SVN_ERR(relegate_dir_external(ctx->wc_ctx, defining_abspath,
398 ctx->cancel_func, ctx->cancel_baton,
399 ctx->notify_func2, ctx->notify_baton2,
404 /* The target dir might have multiple components. Guarantee
405 the path leading down to the last component. */
406 const char *parent = svn_dirent_dirname(local_abspath, pool);
407 SVN_ERR(svn_io_make_dir_recursively(parent, pool));
410 /* ... Hello, new hotness. */
411 SVN_ERR(svn_client__checkout_internal(NULL, timestamp_sleep,
412 url, local_abspath, peg_revision,
413 revision, svn_depth_infinity,
418 SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL,
421 ctx->wc_ctx, local_abspath,
424 SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
426 local_abspath, svn_node_dir,
427 repos_root_url, repos_uuid,
428 svn_uri_skip_ancestor(repos_root_url,
435 /* Issues #4123 and #4130: We don't need to keep the newly checked
436 out external's DB open. */
437 SVN_ERR(svn_wc__close_db(local_abspath, ctx->wc_ctx, pool));
442 /* Try to update a file external at LOCAL_ABSPATH to SWITCH_LOC. This function
443 assumes caller has a write lock in CTX. Use SCRATCH_POOL for temporary
444 allocations, and use the client context CTX. */
446 switch_file_external(const char *local_abspath,
447 const svn_client__pathrev_t *switch_loc,
448 const char *record_url,
449 const svn_opt_revision_t *record_peg_revision,
450 const svn_opt_revision_t *record_revision,
451 const char *def_dir_abspath,
452 svn_ra_session_t *ra_session,
453 svn_client_ctx_t *ctx,
454 apr_pool_t *scratch_pool)
456 svn_config_t *cfg = ctx->config
457 ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
459 svn_boolean_t use_commit_times;
460 const char *diff3_cmd;
461 const char *preserved_exts_str;
462 const apr_array_header_t *preserved_exts;
463 svn_node_kind_t kind, external_kind;
465 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
467 /* See if the user wants last-commit timestamps instead of current ones. */
468 SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
469 SVN_CONFIG_SECTION_MISCELLANY,
470 SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
472 /* Get the external diff3, if any. */
473 svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
474 SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
476 if (diff3_cmd != NULL)
477 SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool));
479 /* See which files the user wants to preserve the extension of when
480 conflict files are made. */
481 svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
482 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
483 preserved_exts = *preserved_exts_str
484 ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool)
488 const char *wcroot_abspath;
490 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath,
491 scratch_pool, scratch_pool));
493 /* File externals can only be installed inside the current working copy.
494 So verify if the working copy that contains/will contain the target
495 is the defining abspath, or one of its ancestors */
497 if (!svn_dirent_is_ancestor(wcroot_abspath, def_dir_abspath))
498 return svn_error_createf(
499 SVN_ERR_WC_BAD_PATH, NULL,
500 _("Cannot insert a file external defined on '%s' "
501 "into the working copy '%s'."),
502 svn_dirent_local_style(def_dir_abspath,
504 svn_dirent_local_style(wcroot_abspath,
508 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath,
509 TRUE, FALSE, scratch_pool));
511 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
512 ctx->wc_ctx, local_abspath, local_abspath,
513 TRUE, scratch_pool, scratch_pool));
515 /* If there is a versioned item with this name, ensure it's a file
516 external before working with it. If there is no entry in the
517 working copy, then create an empty file and add it to the working
519 if (kind != svn_node_none && kind != svn_node_unknown)
521 if (external_kind != svn_node_file)
523 return svn_error_createf(
524 SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0,
525 _("The file external from '%s' cannot overwrite the existing "
526 "versioned item at '%s'"),
528 svn_dirent_local_style(local_abspath, scratch_pool));
533 svn_node_kind_t disk_kind;
535 SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
537 if (kind == svn_node_file || kind == svn_node_dir)
538 return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL,
539 _("The file external '%s' can not be "
540 "created because the node exists."),
541 svn_dirent_local_style(local_abspath,
546 const svn_ra_reporter3_t *reporter;
548 const svn_delta_editor_t *switch_editor;
551 apr_array_header_t *inherited_props;
552 const char *target = svn_dirent_basename(local_abspath, scratch_pool);
554 /* Get the external file's iprops. */
555 SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "",
557 scratch_pool, scratch_pool));
559 SVN_ERR(svn_ra_reparent(ra_session,
560 svn_uri_dirname(switch_loc->url, scratch_pool),
563 SVN_ERR(svn_wc__get_file_external_editor(&switch_editor, &switch_baton,
564 &revnum, ctx->wc_ctx,
568 switch_loc->repos_root_url,
569 switch_loc->repos_uuid,
572 diff3_cmd, preserved_exts,
581 scratch_pool, scratch_pool));
583 /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
584 invalid revnum, that means RA will use the latest revision. */
585 SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton,
587 target, svn_depth_unknown, switch_loc->url,
588 FALSE /* send_copyfrom */,
589 TRUE /* ignore_ancestry */,
590 switch_editor, switch_baton,
591 scratch_pool, scratch_pool));
593 SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath,
594 reporter, report_baton,
595 TRUE, use_commit_times,
596 ctx->cancel_func, ctx->cancel_baton,
597 ctx->notify_func2, ctx->notify_baton2,
600 if (ctx->notify_func2)
602 svn_wc_notify_t *notify
603 = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
605 notify->kind = svn_node_none;
606 notify->content_state = notify->prop_state
607 = svn_wc_notify_state_inapplicable;
608 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
609 notify->revision = revnum;
610 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
617 /* Wrappers around svn_wc__external_remove, obtaining and releasing a lock for
618 directory externals */
620 remove_external2(svn_boolean_t *removed,
621 svn_wc_context_t *wc_ctx,
622 const char *wri_abspath,
623 const char *local_abspath,
624 svn_node_kind_t external_kind,
625 svn_cancel_func_t cancel_func,
627 apr_pool_t *scratch_pool)
629 SVN_ERR(svn_wc__external_remove(wc_ctx, wri_abspath,
631 (external_kind == svn_node_none),
632 cancel_func, cancel_baton,
641 remove_external(svn_boolean_t *removed,
642 svn_wc_context_t *wc_ctx,
643 const char *wri_abspath,
644 const char *local_abspath,
645 svn_node_kind_t external_kind,
646 svn_cancel_func_t cancel_func,
648 apr_pool_t *scratch_pool)
651 switch (external_kind)
654 SVN_WC__CALL_WITH_WRITE_LOCK(
655 remove_external2(removed,
657 local_abspath, external_kind,
658 cancel_func, cancel_baton,
660 wc_ctx, local_abspath, FALSE, scratch_pool);
664 SVN_ERR(remove_external2(removed,
666 local_abspath, external_kind,
667 cancel_func, cancel_baton,
675 /* Called when an external that is in the EXTERNALS table is no longer
676 referenced from an svn:externals property */
678 handle_external_item_removal(const svn_client_ctx_t *ctx,
679 const char *defining_abspath,
680 const char *local_abspath,
681 apr_pool_t *scratch_pool)
684 svn_node_kind_t external_kind;
685 svn_node_kind_t kind;
686 svn_boolean_t removed = FALSE;
688 /* local_abspath should be a wcroot or a file external */
689 SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
690 ctx->wc_ctx, defining_abspath,
691 local_abspath, FALSE,
692 scratch_pool, scratch_pool));
694 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE,
697 if (external_kind != kind)
698 external_kind = svn_node_none; /* Only remove the registration */
700 err = remove_external(&removed,
701 ctx->wc_ctx, defining_abspath, local_abspath,
703 ctx->cancel_func, ctx->cancel_baton,
706 if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED && removed)
708 svn_error_clear(err);
709 err = NULL; /* We removed the working copy, so we can't release the
710 lock that was stored inside */
713 if (ctx->notify_func2)
715 svn_wc_notify_t *notify =
716 svn_wc_create_notify(local_abspath,
717 svn_wc_notify_update_external_removed,
723 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
725 if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
727 notify = svn_wc_create_notify(local_abspath,
728 svn_wc_notify_left_local_modifications,
730 notify->kind = svn_node_dir;
733 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
737 if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
739 svn_error_clear(err);
743 return svn_error_trace(err);
747 handle_external_item_change(svn_client_ctx_t *ctx,
748 const char *repos_root_url,
749 const char *parent_dir_abspath,
750 const char *parent_dir_url,
751 const char *local_abspath,
752 const char *old_defining_abspath,
753 const svn_wc_external_item2_t *new_item,
754 svn_ra_session_t *ra_session,
755 svn_boolean_t *timestamp_sleep,
756 apr_pool_t *scratch_pool)
758 svn_client__pathrev_t *new_loc;
760 svn_node_kind_t ext_kind;
762 SVN_ERR_ASSERT(repos_root_url && parent_dir_url);
763 SVN_ERR_ASSERT(new_item != NULL);
765 /* Don't bother to check status, since we'll get that for free by
766 attempting to retrieve the hash values anyway. */
768 /* When creating the absolute URL, use the pool and not the
769 iterpool, since the hash table values outlive the iterpool and
770 any pointers they have should also outlive the iterpool. */
772 SVN_ERR(svn_wc__resolve_relative_external_url(&new_url,
773 new_item, repos_root_url,
775 scratch_pool, scratch_pool));
777 /* Determine if the external is a file or directory. */
778 /* Get the RA connection, if needed. */
781 svn_error_t *err = svn_ra_reparent(ra_session, new_url, scratch_pool);
785 if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
787 svn_error_clear(err);
791 return svn_error_trace(err);
795 SVN_ERR(svn_client__resolve_rev_and_url(&new_loc,
797 &(new_item->peg_revision),
798 &(new_item->revision), ctx,
801 SVN_ERR(svn_ra_reparent(ra_session, new_loc->url, scratch_pool));
806 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
808 &(new_item->peg_revision),
809 &(new_item->revision), ctx,
812 SVN_ERR(svn_ra_check_path(ra_session, "", new_loc->rev, &ext_kind,
815 if (svn_node_none == ext_kind)
816 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
817 _("URL '%s' at revision %ld doesn't exist"),
818 new_loc->url, new_loc->rev);
820 if (svn_node_dir != ext_kind && svn_node_file != ext_kind)
821 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
822 _("URL '%s' at revision %ld is not a file "
824 new_loc->url, new_loc->rev);
827 /* Not protecting against recursive externals. Detecting them in
828 the global case is hard, and it should be pretty obvious to a
829 user when it happens. Worst case: your disk fills up :-). */
831 /* First notify that we're about to handle an external. */
832 if (ctx->notify_func2)
836 svn_wc_create_notify(local_abspath,
837 svn_wc_notify_update_external,
842 if (! old_defining_abspath)
844 /* The target dir might have multiple components. Guarantee the path
845 leading down to the last component. */
846 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath,
854 SVN_ERR(switch_dir_external(local_abspath, new_loc->url,
856 &(new_item->peg_revision),
857 &(new_item->revision),
859 timestamp_sleep, ra_session, ctx,
863 if (strcmp(repos_root_url, new_loc->repos_root_url))
865 const char *local_repos_root_url;
866 const char *local_repos_uuid;
867 const char *ext_repos_relpath;
871 * The working copy library currently requires that all files
872 * in the working copy have the same repository root URL.
873 * The URL from the file external's definition differs from the
874 * one used by the working copy. As a workaround, replace the
875 * root URL portion of the file external's URL, after making
876 * sure both URLs point to the same repository. See issue #4087.
879 err = svn_wc__node_get_repos_info(NULL, NULL,
880 &local_repos_root_url,
882 ctx->wc_ctx, parent_dir_abspath,
883 scratch_pool, scratch_pool);
886 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
887 && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
888 return svn_error_trace(err);
890 svn_error_clear(err);
891 local_repos_root_url = NULL;
892 local_repos_uuid = NULL;
895 ext_repos_relpath = svn_uri_skip_ancestor(new_loc->repos_root_url,
896 new_url, scratch_pool);
897 if (local_repos_uuid == NULL || local_repos_root_url == NULL ||
898 ext_repos_relpath == NULL ||
899 strcmp(local_repos_uuid, new_loc->repos_uuid) != 0)
900 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
901 _("Unsupported external: URL of file external '%s' "
902 "is not in repository '%s'"),
903 new_url, repos_root_url);
905 new_url = svn_path_url_add_component2(local_repos_root_url,
908 SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
911 &(new_item->peg_revision),
912 &(new_item->revision),
916 SVN_ERR(switch_file_external(local_abspath,
919 &new_item->peg_revision,
928 SVN_ERR_MALFUNCTION();
936 wrap_external_error(const svn_client_ctx_t *ctx,
937 const char *target_abspath,
939 apr_pool_t *scratch_pool)
941 if (err && err->apr_err != SVN_ERR_CANCELLED)
943 if (ctx->notify_func2)
945 svn_wc_notify_t *notifier = svn_wc_create_notify(
947 svn_wc_notify_failed_external,
950 ctx->notify_func2(ctx->notify_baton2, notifier, scratch_pool);
952 svn_error_clear(err);
960 handle_externals_change(svn_client_ctx_t *ctx,
961 const char *repos_root_url,
962 svn_boolean_t *timestamp_sleep,
963 const char *local_abspath,
964 const char *new_desc_text,
965 apr_hash_t *old_externals,
966 svn_depth_t ambient_depth,
967 svn_depth_t requested_depth,
968 svn_ra_session_t *ra_session,
969 apr_pool_t *scratch_pool)
971 apr_array_header_t *new_desc;
973 apr_pool_t *iterpool;
976 iterpool = svn_pool_create(scratch_pool);
978 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
980 /* Bag out if the depth here is too shallow for externals action. */
981 if ((requested_depth < svn_depth_infinity
982 && requested_depth != svn_depth_unknown)
983 || (ambient_depth < svn_depth_infinity
984 && requested_depth < svn_depth_infinity))
988 SVN_ERR(svn_wc_parse_externals_description3(&new_desc, local_abspath,
990 FALSE, scratch_pool));
994 SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, local_abspath,
995 scratch_pool, iterpool));
999 for (i = 0; new_desc && (i < new_desc->nelts); i++)
1001 const char *old_defining_abspath;
1002 svn_wc_external_item2_t *new_item;
1003 const char *target_abspath;
1004 svn_boolean_t under_root;
1006 new_item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *);
1008 svn_pool_clear(iterpool);
1010 if (ctx->cancel_func)
1011 SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1013 SVN_ERR(svn_dirent_is_under_root(&under_root, &target_abspath,
1014 local_abspath, new_item->target_dir,
1019 return svn_error_createf(
1020 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1021 _("Path '%s' is not in the working copy"),
1022 svn_dirent_local_style(
1023 svn_dirent_join(local_abspath, new_item->target_dir,
1028 old_defining_abspath = svn_hash_gets(old_externals, target_abspath);
1030 SVN_ERR(wrap_external_error(
1031 ctx, target_abspath,
1032 handle_external_item_change(ctx,
1036 old_defining_abspath,
1037 new_item, ra_session,
1042 /* And remove already processed items from the to-remove hash */
1043 if (old_defining_abspath)
1044 svn_hash_sets(old_externals, target_abspath, NULL);
1047 svn_pool_destroy(iterpool);
1049 return SVN_NO_ERROR;
1054 svn_client__handle_externals(apr_hash_t *externals_new,
1055 apr_hash_t *ambient_depths,
1056 const char *repos_root_url,
1057 const char *target_abspath,
1058 svn_depth_t requested_depth,
1059 svn_boolean_t *timestamp_sleep,
1060 svn_ra_session_t *ra_session,
1061 svn_client_ctx_t *ctx,
1062 apr_pool_t *scratch_pool)
1064 apr_hash_t *old_external_defs;
1065 apr_hash_index_t *hi;
1066 apr_pool_t *iterpool;
1068 SVN_ERR_ASSERT(repos_root_url);
1070 iterpool = svn_pool_create(scratch_pool);
1072 SVN_ERR(svn_wc__externals_defined_below(&old_external_defs,
1073 ctx->wc_ctx, target_abspath,
1074 scratch_pool, iterpool));
1076 for (hi = apr_hash_first(scratch_pool, externals_new);
1078 hi = apr_hash_next(hi))
1080 const char *local_abspath = apr_hash_this_key(hi);
1081 const char *desc_text = apr_hash_this_val(hi);
1082 svn_depth_t ambient_depth = svn_depth_infinity;
1084 svn_pool_clear(iterpool);
1088 const char *ambient_depth_w;
1090 ambient_depth_w = apr_hash_get(ambient_depths, local_abspath,
1091 apr_hash_this_key_len(hi));
1093 if (ambient_depth_w == NULL)
1095 return svn_error_createf(
1096 SVN_ERR_WC_CORRUPT, NULL,
1097 _("Traversal of '%s' found no ambient depth"),
1098 svn_dirent_local_style(local_abspath, scratch_pool));
1102 ambient_depth = svn_depth_from_word(ambient_depth_w);
1106 SVN_ERR(handle_externals_change(ctx, repos_root_url, timestamp_sleep,
1108 desc_text, old_external_defs,
1109 ambient_depth, requested_depth,
1110 ra_session, iterpool));
1113 /* Remove the remaining externals */
1114 for (hi = apr_hash_first(scratch_pool, old_external_defs);
1116 hi = apr_hash_next(hi))
1118 const char *item_abspath = apr_hash_this_key(hi);
1119 const char *defining_abspath = apr_hash_this_val(hi);
1120 const char *parent_abspath;
1122 svn_pool_clear(iterpool);
1124 SVN_ERR(wrap_external_error(
1126 handle_external_item_removal(ctx, defining_abspath,
1127 item_abspath, iterpool),
1130 /* Are there any unversioned directories between the removed
1131 * external and the DEFINING_ABSPATH which we can remove? */
1132 parent_abspath = item_abspath;
1134 svn_node_kind_t kind;
1136 parent_abspath = svn_dirent_dirname(parent_abspath, iterpool);
1137 SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, parent_abspath,
1138 FALSE /* show_deleted*/,
1139 FALSE /* show_hidden */,
1141 if (kind == svn_node_none)
1145 err = svn_io_dir_remove_nonrecursive(parent_abspath, iterpool);
1148 if (APR_STATUS_IS_ENOTEMPTY(err->apr_err))
1150 svn_error_clear(err);
1151 break; /* No parents to delete */
1153 else if (APR_STATUS_IS_ENOENT(err->apr_err)
1154 || APR_STATUS_IS_ENOTDIR(err->apr_err))
1156 svn_error_clear(err);
1157 /* Fall through; parent dir might be unversioned */
1160 return svn_error_trace(err);
1163 } while (strcmp(parent_abspath, defining_abspath) != 0);
1167 svn_pool_destroy(iterpool);
1168 return SVN_NO_ERROR;
1173 svn_client__export_externals(apr_hash_t *externals,
1174 const char *from_url,
1175 const char *to_abspath,
1176 const char *repos_root_url,
1177 svn_depth_t requested_depth,
1178 const char *native_eol,
1179 svn_boolean_t ignore_keywords,
1180 svn_client_ctx_t *ctx,
1181 apr_pool_t *scratch_pool)
1183 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1184 apr_pool_t *sub_iterpool = svn_pool_create(scratch_pool);
1185 apr_hash_index_t *hi;
1187 SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath));
1189 for (hi = apr_hash_first(scratch_pool, externals);
1191 hi = apr_hash_next(hi))
1193 const char *local_abspath = apr_hash_this_key(hi);
1194 const char *desc_text = apr_hash_this_val(hi);
1195 const char *local_relpath;
1196 const char *dir_url;
1197 apr_array_header_t *items;
1200 svn_pool_clear(iterpool);
1202 SVN_ERR(svn_wc_parse_externals_description3(&items, local_abspath,
1209 local_relpath = svn_dirent_skip_ancestor(to_abspath, local_abspath);
1211 dir_url = svn_path_url_add_component2(from_url, local_relpath,
1214 for (i = 0; i < items->nelts; i++)
1216 const char *item_abspath;
1217 const char *new_url;
1218 svn_boolean_t under_root;
1219 svn_wc_external_item2_t *item = APR_ARRAY_IDX(items, i,
1220 svn_wc_external_item2_t *);
1222 svn_pool_clear(sub_iterpool);
1224 SVN_ERR(svn_dirent_is_under_root(&under_root, &item_abspath,
1225 local_abspath, item->target_dir,
1230 return svn_error_createf(
1231 SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1232 _("Path '%s' is not in the working copy"),
1233 svn_dirent_local_style(
1234 svn_dirent_join(local_abspath, item->target_dir,
1239 SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, item,
1241 dir_url, sub_iterpool,
1244 /* The target dir might have multiple components. Guarantee
1245 the path leading down to the last component. */
1246 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(item_abspath,
1250 /* First notify that we're about to handle an external. */
1251 if (ctx->notify_func2)
1255 svn_wc_create_notify(item_abspath,
1256 svn_wc_notify_update_external,
1261 SVN_ERR(wrap_external_error(
1263 svn_client_export5(NULL, new_url, item_abspath,
1264 &item->peg_revision,
1266 TRUE, FALSE, ignore_keywords,
1274 svn_pool_destroy(sub_iterpool);
1275 svn_pool_destroy(iterpool);
1277 return SVN_NO_ERROR;