Change the format of the revprops block sent in svnserve for
[svn.git] / subversion / libsvn_client / externals.c
bloba8e2443aef7a2de36190bb16df25bbaac1d51f4a
1 /*
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 /* ==================================================================== */
23 /*** Includes. ***/
25 #include <assert.h>
26 #include <apr_uri.h>
27 #include "svn_wc.h"
28 #include "svn_pools.h"
29 #include "svn_client.h"
30 #include "svn_hash.h"
31 #include "svn_types.h"
32 #include "svn_error.h"
33 #include "svn_path.h"
34 #include "client.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(). */
43 apr_hash_t *new_desc;
44 apr_hash_t *old_desc;
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! */
65 apr_pool_t *pool;
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,
71 else return false.
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
77 a re-checkout. */
78 static svn_boolean_t
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))))
88 return FALSE;
90 /* Else. */
91 return TRUE;
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.
105 static svn_error_t *
106 relegate_external(const char *path,
107 svn_cancel_func_t cancel_func,
108 void *cancel_baton,
109 apr_pool_t *pool)
111 svn_error_t *err;
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,
118 TRUE, FALSE,
119 cancel_func,
120 cancel_baton,
121 pool);
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:
145 ENOTDIR
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
153 no big deal.
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? */
159 /* Rename. */
160 SVN_ERR(svn_io_file_rename(path, new_path, pool));
162 else if (err)
163 return err;
165 return SVN_NO_ERROR;
168 /* Try to update an external PATH to URL at REVISION.
169 Use POOL for temporary allocations, and use the client context CTX. */
170 static svn_error_t *
171 switch_external(const char *path,
172 const char *url,
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,
177 apr_pool_t *pool)
179 svn_node_kind_t kind;
180 svn_error_t *err;
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,
187 pool), pool);
189 /* If path is a directory, try to update/switch to the correct URL
190 and revision. */
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,
200 FALSE, subpool));
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,
211 svn_depth_unknown, FALSE,
212 FALSE, FALSE,
213 timestamp_sleep, TRUE,
214 ctx, subpool));
215 svn_pool_destroy(subpool);
216 return SVN_NO_ERROR;
218 else if (entry->repos)
220 /* URLs don't match. Try to relocate (if necessary) and then
221 switch. */
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,
230 ctx, subpool));
231 SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root,
232 subpool));
234 err = svn_client_relocate(path, entry->repos, repos_root,
235 TRUE, ctx, subpool);
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. */
239 if (err
240 && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION
241 || (err->apr_err
242 == SVN_ERR_CLIENT_INVALID_RELOCATION)))
244 svn_error_clear(err);
245 goto relegate;
247 else if (err)
248 return err;
251 SVN_ERR(svn_client__switch_internal(NULL, path, url,
252 peg_revision, revision,
253 svn_depth_infinity, TRUE,
254 timestamp_sleep,
255 FALSE, FALSE, ctx, subpool));
257 svn_pool_destroy(subpool);
258 return SVN_NO_ERROR;
263 relegate:
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
268 operations above. */
269 svn_pool_destroy(subpool);
271 if (kind == svn_node_dir)
272 /* Buh-bye, old and busted ... */
273 SVN_ERR(relegate_external(path,
274 ctx->cancel_func,
275 ctx->cancel_baton,
276 pool));
277 else
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,
287 revision,
288 SVN_DEPTH_INFINITY_OR_FILES(TRUE),
289 FALSE, FALSE, timestamp_sleep,
290 ctx, pool));
292 return SVN_NO_ERROR;
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
297 not be updated. */
298 static svn_error_t *
299 uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
301 apr_size_t i;
303 for (i = 0; uri[i] && uri[i] != ':'; ++i)
304 if (uri[i] == '/')
305 goto error;
307 if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
309 *scheme = apr_pstrmemdup(pool, uri, i);
310 return SVN_NO_ERROR;
313 error:
314 return svn_error_createf(SVN_ERR_BAD_URL, 0,
315 _("URL '%s' does not begin with a scheme"),
316 uri);
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
334 to the server root.
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
339 '//'. */
340 static svn_error_t *
341 resolve_relative_external_url(svn_wc_external_item2_t *item,
342 const char *repos_root_url,
343 const char *parent_dir_url,
344 apr_pool_t *pool)
346 const char *uncanonicalized_url = item->url;
347 const char *canonicalized_url;
348 apr_uri_t parent_dir_parsed_uri;
349 apr_status_t status;
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;
357 return SVN_NO_ERROR;
360 /* Parse the parent directory URL into its parts. */
361 status = apr_uri_parse(pool, parent_dir_url, &parent_dir_parsed_uri);
362 if (status)
363 return svn_error_createf(SVN_ERR_BAD_URL, 0,
364 _("Illegal parent directory URL '%s'."),
365 parent_dir_url);
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;
377 int i;
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,
384 pool);
385 relative_components = svn_path_decompose(canonicalized_url, pool);
387 else
389 apr_uri_t repos_root_parsed_uri;
391 status = apr_uri_parse(pool, repos_root_url, &repos_root_parsed_uri);
392 if (status)
393 return svn_error_createf(SVN_ERR_BAD_URL, 0,
394 _("Illegal repository root URL '%s'."),
395 repos_root_url);
397 base_components = svn_path_decompose(repos_root_parsed_uri.path,
398 pool);
399 relative_components = svn_path_decompose(canonicalized_url + 2,
400 pool);
403 for (i = 0; i < relative_components->nelts; ++i)
405 const char *component = APR_ARRAY_IDX(relative_components,
407 const char *);
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);
417 else
418 APR_ARRAY_PUSH(base_components, const char *) = component;
421 parent_dir_parsed_uri.path = (char *)svn_path_compose(base_components,
422 pool);
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);
428 return SVN_NO_ERROR;
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))
443 const char *scheme;
445 SVN_ERR(uri_scheme(&scheme, repos_root_url, pool));
446 item->url = svn_path_canonicalize(apr_pstrcat(pool,
447 scheme,
448 ":",
449 uncanonicalized_url,
450 NULL),
451 pool);
452 return SVN_NO_ERROR;
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);
464 return SVN_NO_ERROR;
467 return svn_error_createf(SVN_ERR_BAD_URL, 0,
468 _("Unrecognized format for the relative external "
469 "URL '%s'."),
470 uncanonicalized_url);
473 /* This implements the 'svn_hash_diff_func_t' interface.
474 BATON is of type 'struct handle_external_item_change_baton *'. */
475 static svn_error_t *
476 handle_external_item_change(const void *key, apr_ssize_t klen,
477 enum svn_hash_diff_key_status status,
478 void *baton)
480 struct handle_external_item_change_baton *ib = baton;
481 svn_wc_external_item2_t *old_item, *new_item;
482 const char *parent;
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);
492 if (old_item)
493 SVN_ERR(resolve_relative_external_url(old_item, ib->repos_root_url,
494 ib->parent_dir_url, ib->pool));
496 else
497 old_item = NULL;
499 if (ib->new_desc)
501 new_item = apr_hash_get(ib->new_desc, key, klen);
502 if (new_item)
503 SVN_ERR(resolve_relative_external_url(new_item, ib->repos_root_url,
504 ib->parent_dir_url, ib->pool));
506 else
507 new_item = NULL;
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
515 subdir.
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 :-). */
534 if (! old_item)
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);
555 if (ib->is_export)
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,
566 ib->ctx, ib->pool));
567 else
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));
574 else if (! new_item)
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,
585 ib->pool));
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)))
599 if (!err)
600 err = err2;
601 else
602 svn_error_clear(err2);
605 if (err && (err->apr_err != SVN_ERR_WC_LEFT_LOCAL_MOD))
606 return err;
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
619 URL/revision. */
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);
629 return SVN_NO_ERROR;
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;
651 const char *to_path;
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;
660 apr_pool_t *pool;
664 /* This implements the 'svn_hash_diff_func_t' interface.
665 BATON is of type 'struct handle_externals_desc_change_baton *'.
667 static svn_error_t *
668 handle_externals_desc_change(const void *key, apr_ssize_t klen,
669 enum svn_hash_diff_key_status status,
670 void *baton)
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;
677 apr_size_t len;
678 int i;
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"),
691 (const char *) key);
693 else
695 ambient_depth = svn_depth_from_word(ambient_depth_w);
698 else
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))
708 return SVN_NO_ERROR;
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,
712 FALSE, cb->pool));
713 else
714 old_desc = NULL;
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,
718 FALSE, cb->pool));
719 else
720 new_desc = NULL;
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;
747 ib.ctx = cb->ctx;
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] == '/')
762 ++len;
763 ib.parent_dir_url = svn_path_join(cb->from_url,
764 ib.parent_dir + len,
765 cb->pool);
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,
777 APR_HASH_KEY_STRING,
778 svn_hash_diff_key_both, &ib));
779 else
780 SVN_ERR(handle_external_item_change(item->target_dir,
781 APR_HASH_KEY_STRING,
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,
789 APR_HASH_KEY_STRING,
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);
797 return SVN_NO_ERROR;
801 svn_error_t *
802 svn_client__handle_externals(svn_wc_traversal_info_t *traversal_info,
803 const char *from_url,
804 const char *to_path,
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,
810 apr_pool_t *pool)
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;
825 cb.ctx = ctx;
826 cb.update_unchanged = update_unchanged;
827 cb.timestamp_sleep = timestamp_sleep;
828 cb.is_export = FALSE;
829 cb.pool = pool;
831 SVN_ERR(svn_hash_diff(cb.externals_old, cb.externals_new,
832 handle_externals_desc_change, &cb, pool));
834 return SVN_NO_ERROR;
838 svn_error_t *
839 svn_client__fetch_externals(apr_hash_t *externals,
840 const char *from_url,
841 const char *to_path,
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,
847 apr_pool_t *pool)
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;
855 cb.ctx = ctx;
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;
862 cb.pool = pool;
864 SVN_ERR(svn_hash_diff(cb.externals_old, cb.externals_new,
865 handle_externals_desc_change, &cb, pool));
867 return SVN_NO_ERROR;
871 svn_error_t *
872 svn_client__do_external_status(svn_wc_traversal_info_t *traversal_info,
873 svn_wc_status_func2_t status_func,
874 void *status_baton,
875 svn_depth_t depth,
876 svn_boolean_t get_all,
877 svn_boolean_t update,
878 svn_boolean_t no_ignore,
879 svn_client_ctx_t *ctx,
880 apr_pool_t *pool)
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
891 values. */
892 for (hi = apr_hash_first(pool, externals_new);
894 hi = apr_hash_next(hi))
896 apr_array_header_t *exts;
897 const void *key;
898 void *val;
899 const char *path;
900 const char *propval;
901 apr_pool_t *iterpool;
902 int i;
904 /* Clear the subpool. */
905 svn_pool_clear(subpool);
907 apr_hash_this(hi, &key, NULL, &val);
908 path = key;
909 propval = 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,
914 FALSE, subpool));
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,
932 just skip it. */
933 SVN_ERR(svn_io_check_path(fullpath, &kind, iterpool));
934 if (kind != svn_node_dir)
935 continue;
937 /* Tell the client we're staring an external status set. */
938 if (ctx->notify_func2)
939 (ctx->notify_func2)
940 (ctx->notify_baton2,
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, NULL, ctx, iterpool));
953 /* Destroy SUBPOOL and (implicitly) ITERPOOL. */
954 svn_pool_destroy(subpool);
956 return SVN_NO_ERROR;