2 * externals.c: handle the svn:externals property
4 * ====================================================================
5 * Copyright (c) 2000-2007 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 /* ==================================================================== */
28 #include "svn_pools.h"
29 #include "svn_client.h"
31 #include "svn_types.h"
32 #include "svn_error.h"
36 #include "svn_private_config.h"
39 /* Closure for handle_external_item_change. */
40 struct handle_external_item_change_baton
42 /* As returned by svn_wc_parse_externals_description(). */
46 /* The directory that has this externals property. */
47 const char *parent_dir
;
49 /* The URL for the directory that has this externals property. */
50 const char *parent_dir_url
;
52 /* The URL for the repository root. */
53 const char *repos_root_url
;
55 /* Passed through to svn_client_* functions. */
56 svn_client_ctx_t
*ctx
;
58 /* If set, then run update on items that didn't change. */
59 svn_boolean_t update_unchanged
;
60 svn_boolean_t
*timestamp_sleep
;
61 svn_boolean_t is_export
;
63 /* A scratchwork pool -- do not put anything in here that needs to
64 outlive the hash diffing callback! */
69 /* Return true if NEW_ITEM and OLD_ITEM represent the same external
70 item at the same revision checked out into the same target subdir,
73 ### If this returned the nature of the difference, we could use it
74 to update externals more efficiently. For example, if we know
75 that only the revision number changed, but the target URL did not,
76 we could get away with an "update -r" on the external, instead of
79 compare_external_items(svn_wc_external_item2_t
*new_item
,
80 svn_wc_external_item2_t
*old_item
)
82 if ((strcmp(new_item
->target_dir
, old_item
->target_dir
) != 0)
83 || (strcmp(new_item
->url
, old_item
->url
) != 0)
84 || (! svn_client__compare_revisions(&(new_item
->revision
),
85 &(old_item
->revision
)))
86 || (! svn_client__compare_revisions(&(new_item
->peg_revision
),
87 &(old_item
->peg_revision
))))
95 /* Remove PATH from revision control, and do the same to any revision
96 * controlled directories underneath PATH (including directories not
97 * referred to by parent svn administrative areas); then if PATH is
98 * empty afterwards, remove it, else rename it to a unique name in the
99 * same parent directory.
101 * Pass CANCEL_FUNC, CANCEL_BATON to svn_wc_remove_from_revision_control.
103 * Use POOL for all temporary allocation.
106 relegate_external(const char *path
,
107 svn_cancel_func_t cancel_func
,
112 svn_wc_adm_access_t
*adm_access
;
114 SVN_ERR(svn_wc_adm_open3(&adm_access
, NULL
, path
, TRUE
, -1, cancel_func
,
115 cancel_baton
, pool
));
116 err
= svn_wc_remove_from_revision_control(adm_access
,
117 SVN_WC_ENTRY_THIS_DIR
,
123 /* ### Ugly. Unlock only if not going to return an error. Revisit */
124 if (!err
|| err
->apr_err
== SVN_ERR_WC_LEFT_LOCAL_MOD
)
125 SVN_ERR(svn_wc_adm_close(adm_access
));
127 if (err
&& (err
->apr_err
== SVN_ERR_WC_LEFT_LOCAL_MOD
))
129 const char *new_path
;
131 svn_error_clear(err
);
133 /* Reserve the new dir name. */
134 SVN_ERR(svn_io_open_unique_file2
135 (NULL
, &new_path
, path
, ".OLD", svn_io_file_del_none
, pool
));
137 /* Sigh... We must fall ever so slightly from grace.
139 Ideally, there would be no window, however brief, when we
140 don't have a reservation on the new name. Unfortunately,
141 at least in the Unix (Linux?) version of apr_file_rename(),
142 you can't rename a directory over a file, because it's just
143 calling stdio rename(), which says:
146 A component used as a directory in oldpath or newpath
147 path is not, in fact, a directory. Or, oldpath is
148 a directory, and newpath exists but is not a directory
150 So instead, we get the name, then remove the file (ugh), then
151 rename the directory, hoping that nobody has gotten that name
152 in the meantime -- which would never happen in real life, so
155 err
= svn_io_remove_file(new_path
, pool
);
156 svn_error_clear(err
); /* It's not clear why this is ignored, is
157 it because the rename will catch it? */
160 SVN_ERR(svn_io_file_rename(path
, new_path
, pool
));
168 /* Try to update an external PATH to URL at REVISION.
169 Use POOL for temporary allocations, and use the client context CTX. */
171 switch_external(const char *path
,
173 const svn_opt_revision_t
*revision
,
174 const svn_opt_revision_t
*peg_revision
,
175 svn_boolean_t
*timestamp_sleep
,
176 svn_client_ctx_t
*ctx
,
179 svn_node_kind_t kind
;
181 apr_pool_t
*subpool
= svn_pool_create(pool
);
183 /* First notify that we're about to handle an external. */
184 if (ctx
->notify_func2
)
185 ctx
->notify_func2(ctx
->notify_baton2
,
186 svn_wc_create_notify(path
, svn_wc_notify_update_external
,
189 /* If path is a directory, try to update/switch to the correct URL
191 SVN_ERR(svn_io_check_path(path
, &kind
, pool
));
192 if (kind
== svn_node_dir
)
194 svn_wc_adm_access_t
*adm_access
;
195 const svn_wc_entry_t
*entry
;
197 SVN_ERR(svn_wc_adm_open3(&adm_access
, NULL
, path
, TRUE
, 0,
198 ctx
->cancel_func
, ctx
->cancel_baton
, subpool
));
199 SVN_ERR(svn_wc_entry(&entry
, path
, adm_access
,
201 SVN_ERR(svn_wc_adm_close(adm_access
));
203 if (entry
&& entry
->url
)
205 /* If we have what appears to be a version controlled
206 subdir, and its top-level URL matches that of our
207 externals definition, perform an update. */
208 if (strcmp(entry
->url
, url
) == 0)
210 SVN_ERR(svn_client__update_internal(NULL
, path
, revision
,
213 timestamp_sleep
, TRUE
,
215 svn_pool_destroy(subpool
);
218 else if (entry
->repos
)
220 /* URLs don't match. Try to relocate (if necessary) and then
222 if (! svn_path_is_ancestor(entry
->repos
, url
))
224 const char *repos_root
;
225 svn_ra_session_t
*ra_session
;
227 /* Get the repos root of the new URL. */
228 SVN_ERR(svn_client__open_ra_session_internal
229 (&ra_session
, url
, NULL
, NULL
, NULL
, FALSE
, TRUE
,
231 SVN_ERR(svn_ra_get_repos_root(ra_session
, &repos_root
,
234 err
= svn_client_relocate(path
, entry
->repos
, repos_root
,
236 /* If the relocation failed because the new URL points
237 to another repository, then we need to relegate and
238 check out a new WC. */
240 && (err
->apr_err
== SVN_ERR_WC_INVALID_RELOCATION
242 == SVN_ERR_CLIENT_INVALID_RELOCATION
)))
244 svn_error_clear(err
);
251 SVN_ERR(svn_client__switch_internal(NULL
, path
, url
,
252 peg_revision
, revision
,
255 FALSE
, FALSE
, ctx
, subpool
));
257 svn_pool_destroy(subpool
);
265 /* Fall back on removing the WC and checking out a new one. */
267 /* Ensure that we don't have any RA sessions or WC locks from failed
269 svn_pool_destroy(subpool
);
271 if (kind
== svn_node_dir
)
272 /* Buh-bye, old and busted ... */
273 SVN_ERR(relegate_external(path
,
279 /* The target dir might have multiple components. Guarantee
280 the path leading down to the last component. */
281 const char *parent
= svn_path_dirname(path
, pool
);
282 SVN_ERR(svn_io_make_dir_recursively(parent
, pool
));
285 /* ... Hello, new hotness. */
286 SVN_ERR(svn_client__checkout_internal(NULL
, url
, path
, peg_revision
,
288 SVN_DEPTH_INFINITY_OR_FILES(TRUE
),
289 FALSE
, FALSE
, timestamp_sleep
,
295 /* Return the scheme of @a uri in @a scheme allocated from @a pool.
296 If @a uri does not appear to be a valid URI, then @a scheme will
299 uri_scheme(const char **scheme
, const char *uri
, apr_pool_t
*pool
)
303 for (i
= 0; uri
[i
] && uri
[i
] != ':'; ++i
)
307 if (i
> 0 && uri
[i
] == ':' && uri
[i
+1] == '/' && uri
[i
+2] == '/')
309 *scheme
= apr_pstrmemdup(pool
, uri
, i
);
314 return svn_error_createf(SVN_ERR_BAD_URL
, 0,
315 _("URL '%s' does not begin with a scheme"),
319 /* If the URL for @a item is relative, then using the repository root
320 URL @a repos_root_url and the parent directory URL @parent_dir_url,
321 resolve it into an absolute URL and save it in @a item.
323 Regardless if the URL is absolute or not, if there are no errors,
324 the URL in @a item will be canonicalized.
326 The following relative URL formats are supported:
328 ../ relative to the parent directory of the external
329 ^/ relative to the repository root
330 // relative to the scheme
331 / relative to the server's hostname
333 The ../ and ^/ relative URLs may use .. to remove path elements up
336 The external URL should not be canonicalized otherwise the scheme
337 relative URL '//host/some/path' would have been canonicalized to
338 '/host/some/path' and we would not be able to match on the leading
341 resolve_relative_external_url(svn_wc_external_item2_t
*item
,
342 const char *repos_root_url
,
343 const char *parent_dir_url
,
346 const char *uncanonicalized_url
= item
->url
;
347 const char *canonicalized_url
;
348 apr_uri_t parent_dir_parsed_uri
;
351 canonicalized_url
= svn_path_canonicalize(uncanonicalized_url
, pool
);
353 /* If the URL is already absolute, there is nothing to do. */
354 if (svn_path_is_url(canonicalized_url
))
356 item
->url
= canonicalized_url
;
360 /* Parse the parent directory URL into its parts. */
361 status
= apr_uri_parse(pool
, parent_dir_url
, &parent_dir_parsed_uri
);
363 return svn_error_createf(SVN_ERR_BAD_URL
, 0,
364 _("Illegal parent directory URL '%s'."),
367 /* Handle URLs relative to the current directory or to the
368 repository root. The backpaths may only remove path elements,
369 not the hostname. This allows an external to refer to another
370 repository in the same server relative to the location of this
371 repository, say using SVNParentPath. */
372 if ((0 == strncmp("../", uncanonicalized_url
, 3)) ||
373 (0 == strncmp("^/", uncanonicalized_url
, 2)))
375 apr_array_header_t
*base_components
;
376 apr_array_header_t
*relative_components
;
379 /* Decompose either the parent directory's URL path or the
380 repository root's URL path into components. */
381 if (0 == strncmp("../", uncanonicalized_url
, 3))
383 base_components
= svn_path_decompose(parent_dir_parsed_uri
.path
,
385 relative_components
= svn_path_decompose(canonicalized_url
, pool
);
389 apr_uri_t repos_root_parsed_uri
;
391 status
= apr_uri_parse(pool
, repos_root_url
, &repos_root_parsed_uri
);
393 return svn_error_createf(SVN_ERR_BAD_URL
, 0,
394 _("Illegal repository root URL '%s'."),
397 base_components
= svn_path_decompose(repos_root_parsed_uri
.path
,
399 relative_components
= svn_path_decompose(canonicalized_url
+ 2,
403 for (i
= 0; i
< relative_components
->nelts
; ++i
)
405 const char *component
= APR_ARRAY_IDX(relative_components
,
408 if (0 == strcmp("..", component
))
410 /* Constructing the final absolute URL together with
411 apr_uri_unparse() requires that the path be absolute,
412 so only pop a component if the component being popped
413 is not the component for the root directory. */
414 if (base_components
->nelts
> 1)
415 apr_array_pop(base_components
);
418 APR_ARRAY_PUSH(base_components
, const char *) = component
;
421 parent_dir_parsed_uri
.path
= (char *)svn_path_compose(base_components
,
423 parent_dir_parsed_uri
.query
= NULL
;
424 parent_dir_parsed_uri
.fragment
= NULL
;
426 item
->url
= apr_uri_unparse(pool
, &parent_dir_parsed_uri
, 0);
431 /* The remaining URLs are relative to the either the scheme or
432 server root and can only refer to locations inside that scope, so
433 backpaths are not allowed. */
434 if (svn_path_is_backpath_present(canonicalized_url
+ 2))
435 return svn_error_createf(SVN_ERR_BAD_URL
, 0,
436 _("The external relative URL '%s' cannot have "
437 "backpaths, i.e. '..'."),
438 uncanonicalized_url
);
440 /* Relative to the scheme. */
441 if (0 == strncmp("//", uncanonicalized_url
, 2))
445 SVN_ERR(uri_scheme(&scheme
, repos_root_url
, pool
));
446 item
->url
= svn_path_canonicalize(apr_pstrcat(pool
,
455 /* Relative to the server root. */
456 if (uncanonicalized_url
[0] == '/')
458 parent_dir_parsed_uri
.path
= (char *)canonicalized_url
;
459 parent_dir_parsed_uri
.query
= NULL
;
460 parent_dir_parsed_uri
.fragment
= NULL
;
462 item
->url
= apr_uri_unparse(pool
, &parent_dir_parsed_uri
, 0);
467 return svn_error_createf(SVN_ERR_BAD_URL
, 0,
468 _("Unrecognized format for the relative external "
470 uncanonicalized_url
);
473 /* This implements the 'svn_hash_diff_func_t' interface.
474 BATON is of type 'struct handle_external_item_change_baton *'. */
476 handle_external_item_change(const void *key
, apr_ssize_t klen
,
477 enum svn_hash_diff_key_status status
,
480 struct handle_external_item_change_baton
*ib
= baton
;
481 svn_wc_external_item2_t
*old_item
, *new_item
;
483 const char *path
= svn_path_join(ib
->parent_dir
,
484 (const char *) key
, ib
->pool
);
486 /* Don't bother to check status, since we'll get that for free by
487 attempting to retrieve the hash values anyway. */
489 if ((ib
->old_desc
) && (! ib
->is_export
))
491 old_item
= apr_hash_get(ib
->old_desc
, key
, klen
);
493 SVN_ERR(resolve_relative_external_url(old_item
, ib
->repos_root_url
,
494 ib
->parent_dir_url
, ib
->pool
));
501 new_item
= apr_hash_get(ib
->new_desc
, key
, klen
);
503 SVN_ERR(resolve_relative_external_url(new_item
, ib
->repos_root_url
,
504 ib
->parent_dir_url
, ib
->pool
));
509 /* We couldn't possibly be here if both values were null, right? */
510 assert(old_item
|| new_item
);
512 /* There's one potential ugliness. If a target subdir changed, but
513 its URL did not, then ideally we'd just rename the subdir, rather
514 than remove the old subdir only to do a new checkout into the new
517 We could solve this by "sneaking around the back" and looking in
518 ib->new_desc, ib->old_desc to check if anything else in this
519 parent_dir has the same URL. Of course, if an external gets
520 moved into some other directory, then we'd lose anyway. The only
521 way to fully handle this would be to harvest a global list based
522 on urls/revs, and consult the list every time we're about to
523 delete an external subdir: whenever a deletion is really part of
524 a rename, then we'd do the rename on the spot.
526 IMHO, renames aren't going to be frequent enough to make the
527 extra bookkeeping worthwhile.
530 /* Not protecting against recursive externals. Detecting them in
531 the global case is hard, and it should be pretty obvious to a
532 user when it happens. Worst case: your disk fills up :-). */
536 /* The target dir might have multiple components. Guarantee
537 the path leading down to the last component. */
538 svn_path_split(path
, &parent
, NULL
, ib
->pool
);
539 SVN_ERR(svn_io_make_dir_recursively(parent
, ib
->pool
));
541 /* If we were handling renames the fancy way, then before
542 checking out a new subdir here, we would somehow learn if
543 it's really just a rename of an old one. That would work in
544 tandem with the next case -- this case would do nothing,
545 knowing that the next case either already has, or soon will,
546 rename the external subdirectory. */
548 /* First notify that we're about to handle an external. */
549 if (ib
->ctx
->notify_func2
)
550 (*ib
->ctx
->notify_func2
)
551 (ib
->ctx
->notify_baton2
,
552 svn_wc_create_notify(path
, svn_wc_notify_update_external
,
553 ib
->pool
), ib
->pool
);
556 /* ### It should be okay to "force" this export. Externals
557 only get created in subdirectories of versioned
558 directories, so an external directory couldn't already
559 exist before the parent export process unless a versioned
560 directory above it did, which means the user would have
561 already had to force these creations to occur. */
562 SVN_ERR(svn_client_export4(NULL
, new_item
->url
, path
,
563 &(new_item
->peg_revision
),
564 &(new_item
->revision
),
565 TRUE
, FALSE
, svn_depth_infinity
, NULL
,
568 SVN_ERR(svn_client__checkout_internal
569 (NULL
, new_item
->url
, path
,
570 &(new_item
->peg_revision
), &(new_item
->revision
),
571 SVN_DEPTH_INFINITY_OR_FILES(TRUE
),
572 FALSE
, FALSE
, ib
->timestamp_sleep
, ib
->ctx
, ib
->pool
));
576 /* See comment in above case about fancy rename handling. Here,
577 before removing an old subdir, we would see if it wants to
578 just be renamed to a new one. */
580 svn_error_t
*err
, *err2
;
581 svn_wc_adm_access_t
*adm_access
;
583 SVN_ERR(svn_wc_adm_open3(&adm_access
, NULL
, path
, TRUE
, -1,
584 ib
->ctx
->cancel_func
, ib
->ctx
->cancel_baton
,
587 /* We don't use relegate_external() here, because we know that
588 nothing else in this externals description (at least) is
589 going to need this directory, and therefore it's better to
590 leave stuff where the user expects it. */
591 err
= svn_wc_remove_from_revision_control
592 (adm_access
, SVN_WC_ENTRY_THIS_DIR
, TRUE
, FALSE
,
593 ib
->ctx
->cancel_func
, ib
->ctx
->cancel_baton
, ib
->pool
);
595 /* ### Ugly. Unlock only if not going to return an error. Revisit */
596 if (!err
|| err
->apr_err
== SVN_ERR_WC_LEFT_LOCAL_MOD
)
597 if ((err2
= svn_wc_adm_close(adm_access
)))
602 svn_error_clear(err2
);
605 if (err
&& (err
->apr_err
!= SVN_ERR_WC_LEFT_LOCAL_MOD
))
607 svn_error_clear(err
);
609 /* ### If there were multiple path components leading down to
610 that wc, we could try to remove them too. */
612 else if (! compare_external_items(new_item
, old_item
)
613 || ib
->update_unchanged
)
615 /* Either the URL changed, or the exact same item is present in
616 both hashes, and caller wants to update such unchanged items.
617 In the latter case, the call below will try to make sure that
618 the external really is a WC pointing to the correct
620 SVN_ERR(switch_external(path
, new_item
->url
, &(new_item
->revision
),
621 &(new_item
->peg_revision
),
622 ib
->timestamp_sleep
, ib
->ctx
, ib
->pool
));
625 /* Clear IB->pool -- we only use it for scratchwork (and this will
626 close any RA sessions still open in this pool). */
627 svn_pool_clear(ib
->pool
);
633 /* Closure for handle_externals_change. */
634 struct handle_externals_desc_change_baton
636 /* As returned by svn_wc_edited_externals(). */
637 apr_hash_t
*externals_new
;
638 apr_hash_t
*externals_old
;
640 /* The requested depth of the driving operation (e.g., update, switch). */
641 svn_depth_t requested_depth
;
643 /* As returned by svn_wc_traversed_depths(). NULL means no ambient
644 depths available (e.g., svn export). */
645 apr_hash_t
*ambient_depths
;
647 /* These two map a URL to a path where the URL is either checked out
648 to or exported to. The to_path must be a substring of the
649 external item parent directory path. */
650 const char *from_url
;
653 /* Passed through to handle_external_item_change_baton. */
654 svn_client_ctx_t
*ctx
;
655 const char *repos_root_url
;
656 svn_boolean_t update_unchanged
;
657 svn_boolean_t
*timestamp_sleep
;
658 svn_boolean_t is_export
;
664 /* This implements the 'svn_hash_diff_func_t' interface.
665 BATON is of type 'struct handle_externals_desc_change_baton *'.
668 handle_externals_desc_change(const void *key
, apr_ssize_t klen
,
669 enum svn_hash_diff_key_status status
,
672 struct handle_externals_desc_change_baton
*cb
= baton
;
673 struct handle_external_item_change_baton ib
;
674 const char *old_desc_text
, *new_desc_text
;
675 apr_array_header_t
*old_desc
, *new_desc
;
676 apr_hash_t
*old_desc_hash
, *new_desc_hash
;
679 svn_wc_external_item2_t
*item
;
680 const char *ambient_depth_w
;
681 svn_depth_t ambient_depth
;
683 if (cb
->ambient_depths
)
685 ambient_depth_w
= apr_hash_get(cb
->ambient_depths
, key
, klen
);
686 if (ambient_depth_w
== NULL
)
688 return svn_error_createf
689 (SVN_ERR_WC_CORRUPT
, NULL
,
690 _("Traversal of '%s' found no ambient depth"),
695 ambient_depth
= svn_depth_from_word(ambient_depth_w
);
700 ambient_depth
= svn_depth_infinity
;
703 /* Bag out if the depth here is too shallow for externals action. */
704 if ((cb
->requested_depth
< svn_depth_infinity
705 && cb
->requested_depth
!= svn_depth_unknown
)
706 || (ambient_depth
< svn_depth_infinity
707 && cb
->requested_depth
< svn_depth_infinity
))
710 if ((old_desc_text
= apr_hash_get(cb
->externals_old
, key
, klen
)))
711 SVN_ERR(svn_wc_parse_externals_description3(&old_desc
, key
, old_desc_text
,
716 if ((new_desc_text
= apr_hash_get(cb
->externals_new
, key
, klen
)))
717 SVN_ERR(svn_wc_parse_externals_description3(&new_desc
, key
, new_desc_text
,
722 old_desc_hash
= apr_hash_make(cb
->pool
);
723 new_desc_hash
= apr_hash_make(cb
->pool
);
725 /* Create hashes of our two externals arrays so that we can
726 efficiently generate a diff for them. */
727 for (i
= 0; old_desc
&& (i
< old_desc
->nelts
); i
++)
729 item
= APR_ARRAY_IDX(old_desc
, i
, svn_wc_external_item2_t
*);
731 apr_hash_set(old_desc_hash
, item
->target_dir
,
732 APR_HASH_KEY_STRING
, item
);
735 for (i
= 0; new_desc
&& (i
< new_desc
->nelts
); i
++)
737 item
= APR_ARRAY_IDX(new_desc
, i
, svn_wc_external_item2_t
*);
739 apr_hash_set(new_desc_hash
, item
->target_dir
,
740 APR_HASH_KEY_STRING
, item
);
743 ib
.old_desc
= old_desc_hash
;
744 ib
.new_desc
= new_desc_hash
;
745 ib
.parent_dir
= (const char *) key
;
746 ib
.repos_root_url
= cb
->repos_root_url
;
748 ib
.update_unchanged
= cb
->update_unchanged
;
749 ib
.is_export
= cb
->is_export
;
750 ib
.timestamp_sleep
= cb
->timestamp_sleep
;
751 ib
.pool
= svn_pool_create(cb
->pool
);
753 /* Get the URL of the parent directory by appending a portion of
754 parent_dir to from_url. from_url is the URL for to_path and
755 to_path is a substring of parent_dir, so append any characters in
756 parent_dir past strlen(to_path) to from_url, making sure to move
757 past a '/' in parent_dir, otherwise svn_path_join() will use the
758 absolute path in parent_dir instead of joining from_url with the
759 parent_dir substring. */
760 len
= strlen(cb
->to_path
);
761 if (ib
.parent_dir
[len
] == '/')
763 ib
.parent_dir_url
= svn_path_join(cb
->from_url
,
767 /* We must use a custom version of svn_hash_diff so that the diff
768 entries are processed in the order they were originally specified
769 in the svn:externals properties. */
771 for (i
= 0; old_desc
&& (i
< old_desc
->nelts
); i
++)
773 item
= APR_ARRAY_IDX(old_desc
, i
, svn_wc_external_item2_t
*);
775 if (apr_hash_get(new_desc_hash
, item
->target_dir
, APR_HASH_KEY_STRING
))
776 SVN_ERR(handle_external_item_change(item
->target_dir
,
778 svn_hash_diff_key_both
, &ib
));
780 SVN_ERR(handle_external_item_change(item
->target_dir
,
782 svn_hash_diff_key_a
, &ib
));
784 for (i
= 0; new_desc
&& (i
< new_desc
->nelts
); i
++)
786 item
= APR_ARRAY_IDX(new_desc
, i
, svn_wc_external_item2_t
*);
787 if (! apr_hash_get(old_desc_hash
, item
->target_dir
, APR_HASH_KEY_STRING
))
788 SVN_ERR(handle_external_item_change(item
->target_dir
,
790 svn_hash_diff_key_b
, &ib
));
793 /* Now destroy the subpool we pass to the hash differ. This will
794 close any remaining RA sessions used by the hash diff callback. */
795 svn_pool_destroy(ib
.pool
);
802 svn_client__handle_externals(svn_wc_traversal_info_t
*traversal_info
,
803 const char *from_url
,
805 const char *repos_root_url
,
806 svn_depth_t requested_depth
,
807 svn_boolean_t update_unchanged
,
808 svn_boolean_t
*timestamp_sleep
,
809 svn_client_ctx_t
*ctx
,
812 apr_hash_t
*externals_old
, *externals_new
, *ambient_depths
;
813 struct handle_externals_desc_change_baton cb
;
815 svn_wc_edited_externals(&externals_old
, &externals_new
, traversal_info
);
816 svn_wc_traversed_depths(&ambient_depths
, traversal_info
);
818 cb
.externals_new
= externals_new
;
819 cb
.externals_old
= externals_old
;
820 cb
.requested_depth
= requested_depth
;
821 cb
.ambient_depths
= ambient_depths
;
822 cb
.from_url
= from_url
;
823 cb
.to_path
= to_path
;
824 cb
.repos_root_url
= repos_root_url
;
826 cb
.update_unchanged
= update_unchanged
;
827 cb
.timestamp_sleep
= timestamp_sleep
;
828 cb
.is_export
= FALSE
;
831 SVN_ERR(svn_hash_diff(cb
.externals_old
, cb
.externals_new
,
832 handle_externals_desc_change
, &cb
, pool
));
839 svn_client__fetch_externals(apr_hash_t
*externals
,
840 const char *from_url
,
842 const char *repos_root_url
,
843 svn_depth_t requested_depth
,
844 svn_boolean_t is_export
,
845 svn_boolean_t
*timestamp_sleep
,
846 svn_client_ctx_t
*ctx
,
849 struct handle_externals_desc_change_baton cb
;
851 cb
.externals_new
= externals
;
852 cb
.externals_old
= apr_hash_make(pool
);
853 cb
.requested_depth
= requested_depth
;
854 cb
.ambient_depths
= NULL
;
856 cb
.from_url
= from_url
;
857 cb
.to_path
= to_path
;
858 cb
.repos_root_url
= repos_root_url
;
859 cb
.update_unchanged
= TRUE
;
860 cb
.timestamp_sleep
= timestamp_sleep
;
861 cb
.is_export
= is_export
;
864 SVN_ERR(svn_hash_diff(cb
.externals_old
, cb
.externals_new
,
865 handle_externals_desc_change
, &cb
, pool
));
872 svn_client__do_external_status(svn_wc_traversal_info_t
*traversal_info
,
873 svn_wc_status_func2_t status_func
,
876 svn_boolean_t get_all
,
877 svn_boolean_t update
,
878 svn_boolean_t no_ignore
,
879 svn_client_ctx_t
*ctx
,
882 apr_hash_t
*externals_old
, *externals_new
;
883 apr_hash_index_t
*hi
;
884 apr_pool_t
*subpool
= svn_pool_create(pool
);
886 /* Get the values of the svn:externals properties. */
887 svn_wc_edited_externals(&externals_old
, &externals_new
, traversal_info
);
889 /* Loop over the hash of new values (we don't care about the old
890 ones). This is a mapping of versioned directories to property
892 for (hi
= apr_hash_first(pool
, externals_new
);
894 hi
= apr_hash_next(hi
))
896 apr_array_header_t
*exts
;
901 apr_pool_t
*iterpool
;
904 /* Clear the subpool. */
905 svn_pool_clear(subpool
);
907 apr_hash_this(hi
, &key
, NULL
, &val
);
911 /* Parse the svn:externals property value. This results in a
912 hash mapping subdirectories to externals structures. */
913 SVN_ERR(svn_wc_parse_externals_description3(&exts
, path
, propval
,
916 /* Make a sub-pool of SUBPOOL. */
917 iterpool
= svn_pool_create(subpool
);
919 /* Loop over the subdir array. */
920 for (i
= 0; exts
&& (i
< exts
->nelts
); i
++)
922 const char *fullpath
;
923 svn_wc_external_item2_t
*external
;
924 svn_node_kind_t kind
;
926 svn_pool_clear(iterpool
);
928 external
= APR_ARRAY_IDX(exts
, i
, svn_wc_external_item2_t
*);
929 fullpath
= svn_path_join(path
, external
->target_dir
, iterpool
);
931 /* If the external target directory doesn't exist on disk,
933 SVN_ERR(svn_io_check_path(fullpath
, &kind
, iterpool
));
934 if (kind
!= svn_node_dir
)
937 /* Tell the client we're staring an external status set. */
938 if (ctx
->notify_func2
)
941 svn_wc_create_notify(fullpath
, svn_wc_notify_status_external
,
942 iterpool
), iterpool
);
944 /* And then do the status. */
945 SVN_ERR(svn_client_status3(NULL
, fullpath
,
946 &(external
->revision
),
947 status_func
, status_baton
,
948 depth
, get_all
, update
,
949 no_ignore
, FALSE
, ctx
, iterpool
));
953 /* Destroy SUBPOOL and (implicitly) ITERPOOL. */
954 svn_pool_destroy(subpool
);