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_rename2(local_abspath
, new_path
, FALSE
, 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 (disk_kind
== svn_node_file
|| disk_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
,
578 ctx
->conflict_baton2
,
583 scratch_pool
, scratch_pool
));
585 /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
586 invalid revnum, that means RA will use the latest revision. */
587 SVN_ERR(svn_ra_do_switch3(ra_session
, &reporter
, &report_baton
,
589 target
, svn_depth_unknown
, switch_loc
->url
,
590 FALSE
/* send_copyfrom */,
591 TRUE
/* ignore_ancestry */,
592 switch_editor
, switch_baton
,
593 scratch_pool
, scratch_pool
));
595 SVN_ERR(svn_wc__crawl_file_external(ctx
->wc_ctx
, local_abspath
,
596 reporter
, report_baton
,
597 TRUE
, use_commit_times
,
598 ctx
->cancel_func
, ctx
->cancel_baton
,
599 ctx
->notify_func2
, ctx
->notify_baton2
,
602 if (ctx
->notify_func2
)
604 svn_wc_notify_t
*notify
605 = svn_wc_create_notify(local_abspath
, svn_wc_notify_update_completed
,
607 notify
->kind
= svn_node_none
;
608 notify
->content_state
= notify
->prop_state
609 = svn_wc_notify_state_inapplicable
;
610 notify
->lock_state
= svn_wc_notify_lock_state_inapplicable
;
611 notify
->revision
= revnum
;
612 ctx
->notify_func2(ctx
->notify_baton2
, notify
, scratch_pool
);
619 /* Wrappers around svn_wc__external_remove, obtaining and releasing a lock for
620 directory externals */
622 remove_external2(svn_boolean_t
*removed
,
623 svn_wc_context_t
*wc_ctx
,
624 const char *wri_abspath
,
625 const char *local_abspath
,
626 svn_node_kind_t external_kind
,
627 svn_cancel_func_t cancel_func
,
629 apr_pool_t
*scratch_pool
)
631 SVN_ERR(svn_wc__external_remove(wc_ctx
, wri_abspath
,
633 (external_kind
== svn_node_none
),
634 cancel_func
, cancel_baton
,
643 remove_external(svn_boolean_t
*removed
,
644 svn_wc_context_t
*wc_ctx
,
645 const char *wri_abspath
,
646 const char *local_abspath
,
647 svn_node_kind_t external_kind
,
648 svn_cancel_func_t cancel_func
,
650 apr_pool_t
*scratch_pool
)
653 switch (external_kind
)
656 SVN_WC__CALL_WITH_WRITE_LOCK(
657 remove_external2(removed
,
659 local_abspath
, external_kind
,
660 cancel_func
, cancel_baton
,
662 wc_ctx
, local_abspath
, FALSE
, scratch_pool
);
666 SVN_ERR(remove_external2(removed
,
668 local_abspath
, external_kind
,
669 cancel_func
, cancel_baton
,
677 /* Called when an external that is in the EXTERNALS table is no longer
678 referenced from an svn:externals property */
680 handle_external_item_removal(const svn_client_ctx_t
*ctx
,
681 const char *defining_abspath
,
682 const char *local_abspath
,
683 apr_pool_t
*scratch_pool
)
686 svn_node_kind_t external_kind
;
687 svn_node_kind_t kind
;
688 svn_boolean_t removed
= FALSE
;
690 /* local_abspath should be a wcroot or a file external */
691 SVN_ERR(svn_wc__read_external_info(&external_kind
, NULL
, NULL
, NULL
, NULL
,
692 ctx
->wc_ctx
, defining_abspath
,
693 local_abspath
, FALSE
,
694 scratch_pool
, scratch_pool
));
696 SVN_ERR(svn_wc_read_kind2(&kind
, ctx
->wc_ctx
, local_abspath
, TRUE
, FALSE
,
699 if (external_kind
!= kind
)
700 external_kind
= svn_node_none
; /* Only remove the registration */
702 err
= remove_external(&removed
,
703 ctx
->wc_ctx
, defining_abspath
, local_abspath
,
705 ctx
->cancel_func
, ctx
->cancel_baton
,
708 if (err
&& err
->apr_err
== SVN_ERR_WC_NOT_LOCKED
&& removed
)
710 svn_error_clear(err
);
711 err
= NULL
; /* We removed the working copy, so we can't release the
712 lock that was stored inside */
715 if (ctx
->notify_func2
)
717 svn_wc_notify_t
*notify
=
718 svn_wc_create_notify(local_abspath
,
719 svn_wc_notify_update_external_removed
,
725 ctx
->notify_func2(ctx
->notify_baton2
, notify
, scratch_pool
);
727 if (err
&& err
->apr_err
== SVN_ERR_WC_LEFT_LOCAL_MOD
)
729 notify
= svn_wc_create_notify(local_abspath
,
730 svn_wc_notify_left_local_modifications
,
732 notify
->kind
= svn_node_dir
;
735 ctx
->notify_func2(ctx
->notify_baton2
, notify
, scratch_pool
);
739 if (err
&& err
->apr_err
== SVN_ERR_WC_LEFT_LOCAL_MOD
)
741 svn_error_clear(err
);
745 return svn_error_trace(err
);
749 handle_external_item_change(svn_client_ctx_t
*ctx
,
750 const char *repos_root_url
,
751 const char *parent_dir_abspath
,
752 const char *parent_dir_url
,
753 const char *local_abspath
,
754 const char *old_defining_abspath
,
755 const svn_wc_external_item2_t
*new_item
,
756 svn_ra_session_t
*ra_session
,
757 svn_boolean_t
*timestamp_sleep
,
758 apr_pool_t
*scratch_pool
)
760 svn_client__pathrev_t
*new_loc
;
762 svn_node_kind_t ext_kind
;
764 SVN_ERR_ASSERT(repos_root_url
&& parent_dir_url
);
765 SVN_ERR_ASSERT(new_item
!= NULL
);
767 /* Don't bother to check status, since we'll get that for free by
768 attempting to retrieve the hash values anyway. */
770 /* When creating the absolute URL, use the pool and not the
771 iterpool, since the hash table values outlive the iterpool and
772 any pointers they have should also outlive the iterpool. */
774 SVN_ERR(svn_wc__resolve_relative_external_url(&new_url
,
775 new_item
, repos_root_url
,
777 scratch_pool
, scratch_pool
));
779 /* Determine if the external is a file or directory. */
780 /* Get the RA connection, if needed. */
783 svn_error_t
*err
= svn_ra_reparent(ra_session
, new_url
, scratch_pool
);
787 if (err
->apr_err
== SVN_ERR_RA_ILLEGAL_URL
)
789 svn_error_clear(err
);
793 return svn_error_trace(err
);
797 SVN_ERR(svn_client__resolve_rev_and_url(&new_loc
,
799 &(new_item
->peg_revision
),
800 &(new_item
->revision
), ctx
,
803 SVN_ERR(svn_ra_reparent(ra_session
, new_loc
->url
, scratch_pool
));
808 SVN_ERR(svn_client__ra_session_from_path2(&ra_session
, &new_loc
,
810 &(new_item
->peg_revision
),
811 &(new_item
->revision
), ctx
,
814 SVN_ERR(svn_ra_check_path(ra_session
, "", new_loc
->rev
, &ext_kind
,
817 if (svn_node_none
== ext_kind
)
818 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
819 _("URL '%s' at revision %ld doesn't exist"),
820 new_loc
->url
, new_loc
->rev
);
822 if (svn_node_dir
!= ext_kind
&& svn_node_file
!= ext_kind
)
823 return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL
, NULL
,
824 _("URL '%s' at revision %ld is not a file "
826 new_loc
->url
, new_loc
->rev
);
829 /* Not protecting against recursive externals. Detecting them in
830 the global case is hard, and it should be pretty obvious to a
831 user when it happens. Worst case: your disk fills up :-). */
833 /* First notify that we're about to handle an external. */
834 if (ctx
->notify_func2
)
838 svn_wc_create_notify(local_abspath
,
839 svn_wc_notify_update_external
,
844 if (! old_defining_abspath
)
846 /* The target dir might have multiple components. Guarantee the path
847 leading down to the last component. */
848 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath
,
856 SVN_ERR(switch_dir_external(local_abspath
, new_loc
->url
,
858 &(new_item
->peg_revision
),
859 &(new_item
->revision
),
861 timestamp_sleep
, ra_session
, ctx
,
865 if (strcmp(repos_root_url
, new_loc
->repos_root_url
))
867 const char *local_repos_root_url
;
868 const char *local_repos_uuid
;
869 const char *ext_repos_relpath
;
873 * The working copy library currently requires that all files
874 * in the working copy have the same repository root URL.
875 * The URL from the file external's definition differs from the
876 * one used by the working copy. As a workaround, replace the
877 * root URL portion of the file external's URL, after making
878 * sure both URLs point to the same repository. See issue #4087.
881 err
= svn_wc__node_get_repos_info(NULL
, NULL
,
882 &local_repos_root_url
,
884 ctx
->wc_ctx
, parent_dir_abspath
,
885 scratch_pool
, scratch_pool
);
888 if (err
->apr_err
!= SVN_ERR_WC_PATH_NOT_FOUND
889 && err
->apr_err
!= SVN_ERR_WC_NOT_WORKING_COPY
)
890 return svn_error_trace(err
);
892 svn_error_clear(err
);
893 local_repos_root_url
= NULL
;
894 local_repos_uuid
= NULL
;
897 ext_repos_relpath
= svn_uri_skip_ancestor(new_loc
->repos_root_url
,
898 new_url
, scratch_pool
);
899 if (local_repos_uuid
== NULL
|| local_repos_root_url
== NULL
||
900 ext_repos_relpath
== NULL
||
901 strcmp(local_repos_uuid
, new_loc
->repos_uuid
) != 0)
902 return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE
, NULL
,
903 _("Unsupported external: URL of file external '%s' "
904 "is not in repository '%s'"),
905 new_url
, repos_root_url
);
907 new_url
= svn_path_url_add_component2(local_repos_root_url
,
910 SVN_ERR(svn_client__ra_session_from_path2(&ra_session
, &new_loc
,
913 &(new_item
->peg_revision
),
914 &(new_item
->revision
),
918 SVN_ERR(switch_file_external(local_abspath
,
921 &new_item
->peg_revision
,
930 SVN_ERR_MALFUNCTION();
938 wrap_external_error(const svn_client_ctx_t
*ctx
,
939 const char *target_abspath
,
941 apr_pool_t
*scratch_pool
)
943 if (err
&& err
->apr_err
!= SVN_ERR_CANCELLED
)
945 if (ctx
->notify_func2
)
947 svn_wc_notify_t
*notifier
= svn_wc_create_notify(
949 svn_wc_notify_failed_external
,
952 ctx
->notify_func2(ctx
->notify_baton2
, notifier
, scratch_pool
);
954 svn_error_clear(err
);
962 handle_externals_change(svn_client_ctx_t
*ctx
,
963 const char *repos_root_url
,
964 svn_boolean_t
*timestamp_sleep
,
965 const char *local_abspath
,
966 const char *new_desc_text
,
967 apr_hash_t
*old_externals
,
968 svn_depth_t ambient_depth
,
969 svn_depth_t requested_depth
,
970 svn_ra_session_t
*ra_session
,
971 apr_pool_t
*scratch_pool
)
973 apr_array_header_t
*new_desc
;
975 apr_pool_t
*iterpool
;
978 iterpool
= svn_pool_create(scratch_pool
);
980 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath
));
982 /* Bag out if the depth here is too shallow for externals action. */
983 if ((requested_depth
< svn_depth_infinity
984 && requested_depth
!= svn_depth_unknown
)
985 || (ambient_depth
< svn_depth_infinity
986 && requested_depth
< svn_depth_infinity
))
990 SVN_ERR(svn_wc_parse_externals_description3(&new_desc
, local_abspath
,
992 FALSE
, scratch_pool
));
996 SVN_ERR(svn_wc__node_get_url(&url
, ctx
->wc_ctx
, local_abspath
,
997 scratch_pool
, iterpool
));
1001 for (i
= 0; new_desc
&& (i
< new_desc
->nelts
); i
++)
1003 const char *old_defining_abspath
;
1004 svn_wc_external_item2_t
*new_item
;
1005 const char *target_abspath
;
1006 svn_boolean_t under_root
;
1008 new_item
= APR_ARRAY_IDX(new_desc
, i
, svn_wc_external_item2_t
*);
1010 svn_pool_clear(iterpool
);
1012 if (ctx
->cancel_func
)
1013 SVN_ERR(ctx
->cancel_func(ctx
->cancel_baton
));
1015 SVN_ERR(svn_dirent_is_under_root(&under_root
, &target_abspath
,
1016 local_abspath
, new_item
->target_dir
,
1021 return svn_error_createf(
1022 SVN_ERR_WC_OBSTRUCTED_UPDATE
, NULL
,
1023 _("Path '%s' is not in the working copy"),
1024 svn_dirent_local_style(
1025 svn_dirent_join(local_abspath
, new_item
->target_dir
,
1030 old_defining_abspath
= svn_hash_gets(old_externals
, target_abspath
);
1032 SVN_ERR(wrap_external_error(
1033 ctx
, target_abspath
,
1034 handle_external_item_change(ctx
,
1038 old_defining_abspath
,
1039 new_item
, ra_session
,
1044 /* And remove already processed items from the to-remove hash */
1045 if (old_defining_abspath
)
1046 svn_hash_sets(old_externals
, target_abspath
, NULL
);
1049 svn_pool_destroy(iterpool
);
1051 return SVN_NO_ERROR
;
1056 svn_client__handle_externals(apr_hash_t
*externals_new
,
1057 apr_hash_t
*ambient_depths
,
1058 const char *repos_root_url
,
1059 const char *target_abspath
,
1060 svn_depth_t requested_depth
,
1061 svn_boolean_t
*timestamp_sleep
,
1062 svn_ra_session_t
*ra_session
,
1063 svn_client_ctx_t
*ctx
,
1064 apr_pool_t
*scratch_pool
)
1066 apr_hash_t
*old_external_defs
;
1067 apr_hash_index_t
*hi
;
1068 apr_pool_t
*iterpool
;
1070 SVN_ERR_ASSERT(repos_root_url
);
1072 iterpool
= svn_pool_create(scratch_pool
);
1074 SVN_ERR(svn_wc__externals_defined_below(&old_external_defs
,
1075 ctx
->wc_ctx
, target_abspath
,
1076 scratch_pool
, iterpool
));
1078 for (hi
= apr_hash_first(scratch_pool
, externals_new
);
1080 hi
= apr_hash_next(hi
))
1082 const char *local_abspath
= apr_hash_this_key(hi
);
1083 const char *desc_text
= apr_hash_this_val(hi
);
1084 svn_depth_t ambient_depth
= svn_depth_infinity
;
1086 svn_pool_clear(iterpool
);
1090 const char *ambient_depth_w
;
1092 ambient_depth_w
= apr_hash_get(ambient_depths
, local_abspath
,
1093 apr_hash_this_key_len(hi
));
1095 if (ambient_depth_w
== NULL
)
1097 return svn_error_createf(
1098 SVN_ERR_WC_CORRUPT
, NULL
,
1099 _("Traversal of '%s' found no ambient depth"),
1100 svn_dirent_local_style(local_abspath
, scratch_pool
));
1104 ambient_depth
= svn_depth_from_word(ambient_depth_w
);
1108 SVN_ERR(handle_externals_change(ctx
, repos_root_url
, timestamp_sleep
,
1110 desc_text
, old_external_defs
,
1111 ambient_depth
, requested_depth
,
1112 ra_session
, iterpool
));
1115 /* Remove the remaining externals */
1116 for (hi
= apr_hash_first(scratch_pool
, old_external_defs
);
1118 hi
= apr_hash_next(hi
))
1120 const char *item_abspath
= apr_hash_this_key(hi
);
1121 const char *defining_abspath
= apr_hash_this_val(hi
);
1122 const char *parent_abspath
;
1124 svn_pool_clear(iterpool
);
1126 SVN_ERR(wrap_external_error(
1128 handle_external_item_removal(ctx
, defining_abspath
,
1129 item_abspath
, iterpool
),
1132 /* Are there any unversioned directories between the removed
1133 * external and the DEFINING_ABSPATH which we can remove? */
1134 parent_abspath
= item_abspath
;
1136 svn_node_kind_t kind
;
1138 parent_abspath
= svn_dirent_dirname(parent_abspath
, iterpool
);
1139 SVN_ERR(svn_wc_read_kind2(&kind
, ctx
->wc_ctx
, parent_abspath
,
1140 FALSE
/* show_deleted*/,
1141 FALSE
/* show_hidden */,
1143 if (kind
== svn_node_none
)
1147 err
= svn_io_dir_remove_nonrecursive(parent_abspath
, iterpool
);
1150 if (APR_STATUS_IS_ENOTEMPTY(err
->apr_err
))
1152 svn_error_clear(err
);
1153 break; /* No parents to delete */
1155 else if (APR_STATUS_IS_ENOENT(err
->apr_err
)
1156 || APR_STATUS_IS_ENOTDIR(err
->apr_err
))
1158 svn_error_clear(err
);
1159 /* Fall through; parent dir might be unversioned */
1162 return svn_error_trace(err
);
1165 } while (strcmp(parent_abspath
, defining_abspath
) != 0);
1169 svn_pool_destroy(iterpool
);
1170 return SVN_NO_ERROR
;
1175 svn_client__export_externals(apr_hash_t
*externals
,
1176 const char *from_url
,
1177 const char *to_abspath
,
1178 const char *repos_root_url
,
1179 svn_depth_t requested_depth
,
1180 const char *native_eol
,
1181 svn_boolean_t ignore_keywords
,
1182 svn_client_ctx_t
*ctx
,
1183 apr_pool_t
*scratch_pool
)
1185 apr_pool_t
*iterpool
= svn_pool_create(scratch_pool
);
1186 apr_pool_t
*sub_iterpool
= svn_pool_create(scratch_pool
);
1187 apr_hash_index_t
*hi
;
1189 SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath
));
1191 for (hi
= apr_hash_first(scratch_pool
, externals
);
1193 hi
= apr_hash_next(hi
))
1195 const char *local_abspath
= apr_hash_this_key(hi
);
1196 const char *desc_text
= apr_hash_this_val(hi
);
1197 const char *local_relpath
;
1198 const char *dir_url
;
1199 apr_array_header_t
*items
;
1202 svn_pool_clear(iterpool
);
1204 SVN_ERR(svn_wc_parse_externals_description3(&items
, local_abspath
,
1211 local_relpath
= svn_dirent_skip_ancestor(to_abspath
, local_abspath
);
1213 dir_url
= svn_path_url_add_component2(from_url
, local_relpath
,
1216 for (i
= 0; i
< items
->nelts
; i
++)
1218 const char *item_abspath
;
1219 const char *new_url
;
1220 svn_boolean_t under_root
;
1221 svn_wc_external_item2_t
*item
= APR_ARRAY_IDX(items
, i
,
1222 svn_wc_external_item2_t
*);
1224 svn_pool_clear(sub_iterpool
);
1226 SVN_ERR(svn_dirent_is_under_root(&under_root
, &item_abspath
,
1227 local_abspath
, item
->target_dir
,
1232 return svn_error_createf(
1233 SVN_ERR_WC_OBSTRUCTED_UPDATE
, NULL
,
1234 _("Path '%s' is not in the working copy"),
1235 svn_dirent_local_style(
1236 svn_dirent_join(local_abspath
, item
->target_dir
,
1241 SVN_ERR(svn_wc__resolve_relative_external_url(&new_url
, item
,
1243 dir_url
, sub_iterpool
,
1246 /* The target dir might have multiple components. Guarantee
1247 the path leading down to the last component. */
1248 SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(item_abspath
,
1252 /* First notify that we're about to handle an external. */
1253 if (ctx
->notify_func2
)
1257 svn_wc_create_notify(item_abspath
,
1258 svn_wc_notify_update_external
,
1263 SVN_ERR(wrap_external_error(
1265 svn_client_export5(NULL
, new_url
, item_abspath
,
1266 &item
->peg_revision
,
1268 TRUE
, FALSE
, ignore_keywords
,
1276 svn_pool_destroy(sub_iterpool
);
1277 svn_pool_destroy(iterpool
);
1279 return SVN_NO_ERROR
;