source: valtobtest/subversion-1.6.2/subversion/libsvn_client/externals.c @ 3

Last change on this file since 3 was 3, checked in by valtob, 15 years ago

subversion source 1.6.2 as test

File size: 51.3 KB
Line 
1/*
2 * externals.c:  handle the svn:externals property
3 *
4 * ====================================================================
5 * Copyright (c) 2000-2009 CollabNet.  All rights reserved.
6 *
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.
12 *
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 * ====================================================================
17 */
18
19/* ==================================================================== */
20
21
22
23/*** Includes. ***/
24
25#include <apr_uri.h>
26#include "svn_wc.h"
27#include "svn_pools.h"
28#include "svn_client.h"
29#include "svn_hash.h"
30#include "svn_types.h"
31#include "svn_error.h"
32#include "svn_path.h"
33#include "svn_config.h"
34#include "client.h"
35
36#include "svn_private_config.h"
37#include "private/svn_wc_private.h"
38
39
40/* Closure for handle_external_item_change. */
41struct handle_external_item_change_baton
42{
43  /* As returned by svn_wc_parse_externals_description(). */
44  apr_hash_t *new_desc;
45  apr_hash_t *old_desc;
46
47  /* The directory that has this externals property. */
48  const char *parent_dir;
49
50  /* Access baton for parent_dir.  If the external is an export, this
51     this must be NULL, otherwise it must be non-NULL. */
52  svn_wc_adm_access_t *adm_access;
53
54  /* The URL for the directory that has this externals property. */
55  const char *parent_dir_url;
56
57  /* The URL for the repository root. */
58  const char *repos_root_url;
59
60  /* Passed through to svn_client_* functions. */
61  svn_client_ctx_t *ctx;
62
63  svn_boolean_t *timestamp_sleep;
64  svn_boolean_t is_export;
65
66  /* A long lived pool.  Put anything in here that needs to outlive
67     the hash diffing callback, such as updates to the hash
68     entries. */
69  apr_pool_t *pool;
70
71  /* A scratchwork pool -- do not put anything in here that needs to
72     outlive the hash diffing callback! */
73  apr_pool_t *iter_pool;
74};
75
76
77/* Remove the directory at PATH from revision control, and do the same
78 * to any revision controlled directories underneath PATH (including
79 * directories not referred to by parent svn administrative areas);
80 * then if PATH is empty afterwards, remove it, else rename it to a
81 * unique name in the same parent directory.
82 *
83 * Pass CANCEL_FUNC, CANCEL_BATON to svn_wc_remove_from_revision_control.
84 *
85 * Use POOL for all temporary allocation.
86 *
87 * Note: this function is not passed a svn_wc_adm_access_t.  Instead,
88 * it separately opens the object being deleted, so that if there is a
89 * lock on that object, the object cannot be deleted.
90 */
91static svn_error_t *
92relegate_dir_external(const char *path,
93                      svn_cancel_func_t cancel_func,
94                      void *cancel_baton,
95                      apr_pool_t *pool)
96{
97  svn_error_t *err = SVN_NO_ERROR;
98  svn_wc_adm_access_t *adm_access;
99
100  SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, path, TRUE, -1, cancel_func,
101                           cancel_baton, pool));
102  err = svn_wc_remove_from_revision_control(adm_access,
103                                            SVN_WC_ENTRY_THIS_DIR,
104                                            TRUE, FALSE,
105                                            cancel_func,
106                                            cancel_baton,
107                                            pool);
108
109  /* ### Ugly. Unlock only if not going to return an error. Revisit */
110  if (!err || err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
111    SVN_ERR(svn_wc_adm_close2(adm_access, pool));
112
113  if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD))
114    {
115      const char *parent_dir;
116      const char *dirname;
117      const char *new_path;
118
119      svn_error_clear(err);
120      err = SVN_NO_ERROR;
121
122      svn_path_split(path, &parent_dir, &dirname, pool);
123
124      /* Reserve the new dir name. */
125      SVN_ERR(svn_io_open_uniquely_named(NULL, &new_path,
126                                         parent_dir, dirname, ".OLD",
127                                         svn_io_file_del_none, pool, pool));
128
129      /* Sigh...  We must fall ever so slightly from grace.
130
131         Ideally, there would be no window, however brief, when we
132         don't have a reservation on the new name.  Unfortunately,
133         at least in the Unix (Linux?) version of apr_file_rename(),
134         you can't rename a directory over a file, because it's just
135         calling stdio rename(), which says:
136
137            ENOTDIR
138              A  component used as a directory in oldpath or newpath
139              path is not, in fact, a directory.  Or, oldpath  is
140              a directory, and newpath exists but is not a directory
141
142         So instead, we get the name, then remove the file (ugh), then
143         rename the directory, hoping that nobody has gotten that name
144         in the meantime -- which would never happen in real life, so
145         no big deal.
146      */
147      /* Do our best, but no biggy if it fails. The rename will fail. */
148      svn_error_clear(svn_io_remove_file(new_path, pool));
149
150      /* Rename. */
151      SVN_ERR(svn_io_file_rename(path, new_path, pool));
152    }
153
154  return err;
155}
156
157/* Try to update a directory external at PATH to URL at REVISION.
158   Use POOL for temporary allocations, and use the client context CTX. */
159static svn_error_t *
160switch_dir_external(const char *path,
161                    const char *url,
162                    const svn_opt_revision_t *revision,
163                    const svn_opt_revision_t *peg_revision,
164                    svn_boolean_t *timestamp_sleep,
165                    svn_client_ctx_t *ctx,
166                    apr_pool_t *pool)
167{
168  svn_node_kind_t kind;
169  svn_error_t *err;
170  apr_pool_t *subpool = svn_pool_create(pool);
171
172  /* If path is a directory, try to update/switch to the correct URL
173     and revision. */
174  SVN_ERR(svn_io_check_path(path, &kind, pool));
175  if (kind == svn_node_dir)
176    {
177      svn_wc_adm_access_t *adm_access;
178      const svn_wc_entry_t *entry;
179
180      SVN_ERR(svn_wc_adm_open3(&adm_access, NULL, path, TRUE, 0,
181                               ctx->cancel_func, ctx->cancel_baton, subpool));
182      SVN_ERR(svn_wc_entry(&entry, path, adm_access,
183                           FALSE, subpool));
184      SVN_ERR(svn_wc_adm_close2(adm_access, subpool));
185
186      if (entry && entry->url)
187        {
188          /* If we have what appears to be a version controlled
189             subdir, and its top-level URL matches that of our
190             externals definition, perform an update. */
191          if (strcmp(entry->url, url) == 0)
192            {
193              SVN_ERR(svn_client__update_internal(NULL, path, revision,
194                                                  svn_depth_unknown, FALSE,
195                                                  FALSE, FALSE,
196                                                  timestamp_sleep, TRUE,
197                                                  ctx, subpool));
198              svn_pool_destroy(subpool);
199              return SVN_NO_ERROR;
200            }
201          else if (entry->repos)
202            {
203              /* URLs don't match.  Try to relocate (if necessary) and then
204                 switch. */
205              if (! svn_path_is_ancestor(entry->repos, url))
206                {
207                  const char *repos_root;
208                  svn_ra_session_t *ra_session;
209
210                  /* Get the repos root of the new URL. */
211                  SVN_ERR(svn_client__open_ra_session_internal
212                          (&ra_session, url, NULL, NULL, NULL, FALSE, TRUE,
213                           ctx, subpool));
214                  SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root,
215                                                 subpool));
216
217                  err = svn_client_relocate(path, entry->repos, repos_root,
218                                            TRUE, ctx, subpool);
219                  /* If the relocation failed because the new URL points
220                     to another repository, then we need to relegate and
221                     check out a new WC. */
222                  if (err
223                      && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION
224                          || (err->apr_err
225                              == SVN_ERR_CLIENT_INVALID_RELOCATION)))
226                    {
227                      svn_error_clear(err);
228                      goto relegate;
229                    }
230                  else if (err)
231                    return err;
232                }
233
234              SVN_ERR(svn_client__switch_internal(NULL, path, url,
235                                                  peg_revision, revision,
236                                                  NULL, svn_depth_infinity,
237                                                  TRUE, timestamp_sleep,
238                                                  FALSE, FALSE, ctx, subpool));
239
240              svn_pool_destroy(subpool);
241              return SVN_NO_ERROR;
242            }
243        }
244    }
245
246 relegate:
247
248  /* Fall back on removing the WC and checking out a new one. */
249
250  /* Ensure that we don't have any RA sessions or WC locks from failed
251     operations above. */
252  svn_pool_destroy(subpool);
253
254  if (kind == svn_node_dir)
255    /* Buh-bye, old and busted ... */
256    SVN_ERR(relegate_dir_external(path,
257                                  ctx->cancel_func,
258                                  ctx->cancel_baton,
259                                  pool));
260  else
261    {
262      /* The target dir might have multiple components.  Guarantee
263         the path leading down to the last component. */
264      const char *parent = svn_path_dirname(path, pool);
265      SVN_ERR(svn_io_make_dir_recursively(parent, pool));
266    }
267
268  /* ... Hello, new hotness. */
269  return svn_client__checkout_internal(NULL, url, path, peg_revision,
270                                       revision, NULL,
271                                       SVN_DEPTH_INFINITY_OR_FILES(TRUE),
272                                       FALSE, FALSE, timestamp_sleep,
273                                       ctx, pool);
274}
275
276/* Try to update a file external at PATH to URL at REVISION using a
277   access baton that has a write lock.  Use POOL for temporary
278   allocations, and use the client context CTX. */
279static svn_error_t *
280switch_file_external(const char *path,
281                     const char *url,
282                     const svn_opt_revision_t *peg_revision,
283                     const svn_opt_revision_t *revision,
284                     svn_wc_adm_access_t *adm_access,
285                     svn_ra_session_t *ra_session,
286                     const char *ra_session_url,
287                     svn_revnum_t ra_revnum,
288                     const char *repos_root_url,
289                     svn_boolean_t *timestamp_sleep,
290                     svn_client_ctx_t *ctx,
291                     apr_pool_t *pool)
292{
293  apr_pool_t *subpool = svn_pool_create(pool);
294  svn_wc_adm_access_t *target_adm_access;
295  const char *anchor;
296  const char *target;
297  const svn_wc_entry_t *entry;
298  svn_config_t *cfg = ctx->config ? apr_hash_get(ctx->config,
299                                                 SVN_CONFIG_CATEGORY_CONFIG,
300                                                 APR_HASH_KEY_STRING) : NULL;
301  svn_boolean_t use_commit_times;
302  svn_boolean_t unlink_file = FALSE;
303  svn_boolean_t revert_file = FALSE;
304  svn_boolean_t remove_from_revision_control = FALSE;
305  svn_boolean_t close_adm_access = FALSE;
306  svn_error_t *err = NULL;
307
308  /* There must be a working copy to place the file external into. */
309  SVN_ERR(svn_wc_get_actual_target(path, &anchor, &target, subpool));
310
311  /* Try to get a access baton for the anchor using the input access
312     baton.  If this fails and returns SVN_ERR_WC_NOT_LOCKED, then try
313     to get a new access baton to support inserting a file external
314     into a directory external. */
315  err = svn_wc_adm_retrieve(&target_adm_access, adm_access, anchor, subpool);
316  if (err)
317    {
318      if (err->apr_err == SVN_ERR_WC_NOT_LOCKED)
319        {
320          const char *dest_wc_repos_root_url;
321          svn_opt_revision_t peg_rev;
322
323          svn_error_clear(err);
324          close_adm_access = TRUE;
325          SVN_ERR(svn_wc_adm_open3(&target_adm_access, NULL, anchor, TRUE, 1,
326                                   ctx->cancel_func, ctx->cancel_baton,
327                                   subpool));
328
329          /* Check that the repository root URL for the newly opened
330             wc is the same as the file external. */
331          peg_rev.kind = svn_opt_revision_base;
332          SVN_ERR(svn_client__get_repos_root(&dest_wc_repos_root_url,
333                                             anchor, &peg_rev,
334                                             target_adm_access, ctx, subpool));
335
336          if (0 != strcmp(repos_root_url, dest_wc_repos_root_url))
337            return svn_error_createf
338              (SVN_ERR_RA_REPOS_ROOT_URL_MISMATCH, NULL,
339               _("Cannot insert a file external from '%s' into a working "
340                 "copy from a different repository rooted at '%s'"),
341               url, dest_wc_repos_root_url);
342        }
343      else
344        return err;
345    }
346
347  SVN_ERR(svn_wc_entry(&entry, path, target_adm_access, FALSE, subpool));
348
349  /* Only one notification is done for the external, so don't notify
350     for any following steps.  Use the following trick to add the file
351     then switch it to the external URL. */
352
353  /* See if the user wants last-commit timestamps instead of current ones. */
354  SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
355                              SVN_CONFIG_SECTION_MISCELLANY,
356                              SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
357
358  /* If there is a versioned item with this name, ensure it's a file
359     external before working with it.  If there is no entry in the
360     working copy, then create an empty file and add it to the working
361     copy. */
362  if (entry)
363    {
364      if (! entry->file_external_path)
365        {
366          if (close_adm_access)
367            SVN_ERR(svn_wc_adm_close2(target_adm_access, subpool));
368
369          return svn_error_createf
370            (SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0,
371             _("The file external from '%s' cannot overwrite the existing "
372               "versioned item at '%s'"),
373             url, path);
374        }
375    }
376  else
377    {
378      const svn_wc_entry_t *anchor_dir_entry;
379      apr_file_t *f;
380      svn_boolean_t text_conflicted;
381      svn_boolean_t prop_conflicted;
382      svn_boolean_t tree_conflicted;
383
384      /* Check for a conflict on the containing directory.  Because a
385         switch is done on the added file later, it will leave a
386         conflict on the directory.  To prevent resolving a conflict
387         due to another change on the directory, do not allow a file
388         external to be added when one exists. */
389      SVN_ERR(svn_wc__entry_versioned(&anchor_dir_entry, anchor,
390                                      target_adm_access, FALSE, subpool));
391      SVN_ERR(svn_wc_conflicted_p2(&text_conflicted, &prop_conflicted,
392                                   &tree_conflicted, anchor, target_adm_access,
393                                   subpool));
394      if (text_conflicted || prop_conflicted || tree_conflicted)
395        return svn_error_createf
396          (SVN_ERR_WC_FOUND_CONFLICT, 0,
397           _("The file external from '%s' cannot be written to '%s' while "
398             "'%s' remains in conflict"),
399          url, path, anchor);
400
401      /* Try to create an empty file.  If there is a file already
402         there, then don't touch it. */
403      SVN_ERR(svn_io_file_open(&f,
404                               path,
405                               APR_WRITE | APR_CREATE | APR_EXCL,
406                               APR_OS_DEFAULT,
407                               subpool));
408      unlink_file = TRUE;
409      err = svn_io_file_close(f, pool);
410      if (err)
411        goto cleanup;
412
413      err = svn_wc_add3(path, target_adm_access, svn_depth_infinity,
414                        NULL, /* const char *copyfrom_url */
415                        SVN_INVALID_REVNUM, /* svn_revnum_t copyfrom_rev */
416                        ctx->cancel_func, ctx->cancel_baton,
417                        NULL, /* svn_wc_notify_func2_t notify_func */
418                        NULL, /* void *notify_baton */
419                        subpool);
420      if (err)
421        goto cleanup;
422      revert_file = TRUE;
423
424      err = svn_wc__set_file_external_location(target_adm_access, target,
425                                               url, peg_revision, revision,
426                                               repos_root_url, subpool);
427      if (err)
428        goto cleanup;
429    }
430
431  err = svn_client__switch_internal(NULL, path, url, peg_revision, revision,
432                                    target_adm_access, svn_depth_empty,
433                                    FALSE, /* depth_is_sticky */
434                                    timestamp_sleep,
435                                    TRUE, /* ignore_externals */
436                                    FALSE, /* allow_unver_obstructions */
437                                    ctx,
438                                    pool);
439  if (err)
440    goto cleanup;
441
442  /* Do some additional work if the file external is newly added to
443     the wc. */
444  if (unlink_file)
445    {
446      /* At this point the newly created file external is switched, so
447         if there is an error, to back everything out the file cannot
448         be reverted, it needs to be removed forcibly from the wc.. */
449      revert_file = FALSE;
450      remove_from_revision_control = TRUE;
451
452      if (err)
453        goto cleanup;
454  }
455
456  if (close_adm_access)
457    SVN_ERR(svn_wc_adm_close2(target_adm_access, subpool));
458
459  /* ### should destroy the subpool... */
460
461  return SVN_NO_ERROR;
462
463 cleanup:
464  if (revert_file)
465    {
466      svn_error_t *e =
467        svn_wc_revert3(path, target_adm_access, svn_depth_empty,
468                       use_commit_times,
469                       NULL, /* apr_array_header_t *changelists */
470                       ctx->cancel_func,
471                       ctx->cancel_baton,
472                       NULL, /* svn_wc_notify_func2_t */
473                       NULL, /* void *notify_baton */
474                       subpool);
475      if (e)
476        svn_error_clear(e);
477    }
478
479  if (remove_from_revision_control)
480    {
481      svn_error_t * e = svn_wc_remove_from_revision_control(target_adm_access,
482                                                            target,
483                                                            TRUE, FALSE,
484                                                            ctx->cancel_func,
485                                                            ctx->cancel_baton,
486                                                            subpool);
487      if (e)
488        svn_error_clear(e);
489    }
490
491  if (unlink_file)
492    {
493      svn_error_t *e = svn_io_remove_file(path, subpool);
494      if (e)
495        svn_error_clear(e);
496    }
497
498  if (close_adm_access)
499    SVN_ERR(svn_wc_adm_close2(target_adm_access, subpool));
500
501  /* ### should destroy the subpool */
502
503  return err;
504}
505
506/* Return the scheme of @a uri in @a scheme allocated from @a pool.
507   If @a uri does not appear to be a valid URI, then @a scheme will
508   not be updated.  */
509static svn_error_t *
510uri_scheme(const char **scheme, const char *uri, apr_pool_t *pool)
511{
512  apr_size_t i;
513
514  for (i = 0; uri[i] && uri[i] != ':'; ++i)
515    if (uri[i] == '/')
516      goto error;
517
518  if (i > 0 && uri[i] == ':' && uri[i+1] == '/' && uri[i+2] == '/')
519    {
520      *scheme = apr_pstrmemdup(pool, uri, i);
521      return SVN_NO_ERROR;
522    }
523
524error:
525  return svn_error_createf(SVN_ERR_BAD_URL, 0,
526                           _("URL '%s' does not begin with a scheme"),
527                           uri);
528}
529
530/* If the URL for @a item is relative, then using the repository root
531   URL @a repos_root_url and the parent directory URL @parent_dir_url,
532   resolve it into an absolute URL and save it in @a item.
533
534   Regardless if the URL is absolute or not, if there are no errors,
535   the URL in @a item will be canonicalized.
536
537   The following relative URL formats are supported:
538
539     ../    relative to the parent directory of the external
540     ^/     relative to the repository root
541     //     relative to the scheme
542     /      relative to the server's hostname
543
544   The ../ and ^/ relative URLs may use .. to remove path elements up
545   to the server root.
546
547   The external URL should not be canonicalized otherwise the scheme
548   relative URL '//host/some/path' would have been canonicalized to
549   '/host/some/path' and we would not be able to match on the leading
550   '//'. */
551static svn_error_t *
552resolve_relative_external_url(svn_wc_external_item2_t *item,
553                              const char *repos_root_url,
554                              const char *parent_dir_url,
555                              apr_pool_t *pool)
556{
557  const char *uncanonicalized_url = item->url;
558  const char *canonicalized_url;
559  apr_uri_t parent_dir_parsed_uri;
560  apr_status_t status;
561
562  canonicalized_url = svn_path_canonicalize(uncanonicalized_url, pool);
563
564  /* If the URL is already absolute, there is nothing to do. */
565  if (svn_path_is_url(canonicalized_url))
566    {
567      item->url = canonicalized_url;
568      return SVN_NO_ERROR;
569    }
570
571  /* Parse the parent directory URL into its parts. */
572  status = apr_uri_parse(pool, parent_dir_url, &parent_dir_parsed_uri);
573  if (status)
574    return svn_error_createf(SVN_ERR_BAD_URL, 0,
575                             _("Illegal parent directory URL '%s'"),
576                             parent_dir_url);
577
578  /* If the parent directory URL is at the server root, then the URL
579     may have no / after the hostname so apr_uri_parse() will leave
580     the URL's path as NULL. */
581  if (! parent_dir_parsed_uri.path)
582    parent_dir_parsed_uri.path = apr_pstrmemdup(pool, "/", 1);
583
584  /* Handle URLs relative to the current directory or to the
585     repository root.  The backpaths may only remove path elements,
586     not the hostname.  This allows an external to refer to another
587     repository in the same server relative to the location of this
588     repository, say using SVNParentPath. */
589  if ((0 == strncmp("../", uncanonicalized_url, 3)) ||
590      (0 == strncmp("^/", uncanonicalized_url, 2)))
591    {
592      apr_array_header_t *base_components;
593      apr_array_header_t *relative_components;
594      int i;
595
596      /* Decompose either the parent directory's URL path or the
597         repository root's URL path into components.  */
598      if (0 == strncmp("../", uncanonicalized_url, 3))
599        {
600          base_components = svn_path_decompose(parent_dir_parsed_uri.path,
601                                               pool);
602          relative_components = svn_path_decompose(canonicalized_url, pool);
603        }
604      else
605        {
606          apr_uri_t repos_root_parsed_uri;
607
608          status = apr_uri_parse(pool, repos_root_url, &repos_root_parsed_uri);
609          if (status)
610            return svn_error_createf(SVN_ERR_BAD_URL, 0,
611                                     _("Illegal repository root URL '%s'"),
612                                     repos_root_url);
613
614          /* If the repository root URL is at the server root, then
615             the URL may have no / after the hostname so
616             apr_uri_parse() will leave the URL's path as NULL. */
617          if (! repos_root_parsed_uri.path)
618            repos_root_parsed_uri.path = apr_pstrmemdup(pool, "/", 1);
619
620          base_components = svn_path_decompose(repos_root_parsed_uri.path,
621                                               pool);
622          relative_components = svn_path_decompose(canonicalized_url + 2,
623                                                   pool);
624        }
625
626      for (i = 0; i < relative_components->nelts; ++i)
627        {
628          const char *component = APR_ARRAY_IDX(relative_components,
629                                                i,
630                                                const char *);
631          if (0 == strcmp("..", component))
632            {
633              /* Constructing the final absolute URL together with
634                 apr_uri_unparse() requires that the path be absolute,
635                 so only pop a component if the component being popped
636                 is not the component for the root directory. */
637              if (base_components->nelts > 1)
638                apr_array_pop(base_components);
639            }
640          else
641            APR_ARRAY_PUSH(base_components, const char *) = component;
642        }
643
644      parent_dir_parsed_uri.path = (char *)svn_path_compose(base_components,
645                                                            pool);
646      parent_dir_parsed_uri.query = NULL;
647      parent_dir_parsed_uri.fragment = NULL;
648
649      item->url = apr_uri_unparse(pool, &parent_dir_parsed_uri, 0);
650
651      return SVN_NO_ERROR;
652    }
653
654  /* The remaining URLs are relative to the either the scheme or
655     server root and can only refer to locations inside that scope, so
656     backpaths are not allowed. */
657  if (svn_path_is_backpath_present(canonicalized_url + 2))
658    return svn_error_createf(SVN_ERR_BAD_URL, 0,
659                             _("The external relative URL '%s' cannot have "
660                               "backpaths, i.e. '..'"),
661                             uncanonicalized_url);
662
663  /* Relative to the scheme. */
664  if (0 == strncmp("//", uncanonicalized_url, 2))
665    {
666      const char *scheme;
667
668      SVN_ERR(uri_scheme(&scheme, repos_root_url, pool));
669      item->url = svn_path_canonicalize(apr_pstrcat(pool,
670                                                    scheme,
671                                                    ":",
672                                                    uncanonicalized_url,
673                                                    NULL),
674                                        pool);
675      return SVN_NO_ERROR;
676    }
677
678  /* Relative to the server root. */
679  if (uncanonicalized_url[0] == '/')
680    {
681      parent_dir_parsed_uri.path = (char *)canonicalized_url;
682      parent_dir_parsed_uri.query = NULL;
683      parent_dir_parsed_uri.fragment = NULL;
684
685      item->url = apr_uri_unparse(pool, &parent_dir_parsed_uri, 0);
686
687      return SVN_NO_ERROR;
688    }
689
690  return svn_error_createf(SVN_ERR_BAD_URL, 0,
691                           _("Unrecognized format for the relative external "
692                             "URL '%s'"),
693                           uncanonicalized_url);
694}
695
696/* This implements the 'svn_hash_diff_func_t' interface.
697   BATON is of type 'struct handle_external_item_change_baton *'.  */
698static svn_error_t *
699handle_external_item_change(const void *key, apr_ssize_t klen,
700                            enum svn_hash_diff_key_status status,
701                            void *baton)
702{
703  struct handle_external_item_change_baton *ib = baton;
704  svn_wc_external_item2_t *old_item, *new_item;
705  const char *parent;
706  const char *path = svn_path_join(ib->parent_dir,
707                                   (const char *) key, ib->iter_pool);
708  svn_ra_session_t *ra_session;
709  svn_node_kind_t kind;
710  svn_client__ra_session_from_path_results ra_cache = { 0 };
711
712  /* Don't bother to check status, since we'll get that for free by
713     attempting to retrieve the hash values anyway.  */
714
715  /* When creating the absolute URL, use the pool and not the
716     iterpool, since the hash table values outlive the iterpool and
717     any pointers they have should also outlive the iterpool.  */
718  if ((ib->old_desc) && (! ib->is_export))
719    {
720      old_item = apr_hash_get(ib->old_desc, key, klen);
721      if (old_item)
722        SVN_ERR(resolve_relative_external_url(old_item, ib->repos_root_url,
723                                              ib->parent_dir_url,
724                                              ib->pool));
725    }
726  else
727    old_item = NULL;
728
729  if (ib->new_desc)
730    {
731      new_item = apr_hash_get(ib->new_desc, key, klen);
732      if (new_item)
733        SVN_ERR(resolve_relative_external_url(new_item, ib->repos_root_url,
734                                              ib->parent_dir_url,
735                                              ib->pool));
736    }
737  else
738    new_item = NULL;
739
740  /* We couldn't possibly be here if both values were null, right? */
741  SVN_ERR_ASSERT(old_item || new_item);
742
743  /* There's one potential ugliness.  If a target subdir changed, but
744     its URL did not, then ideally we'd just rename the subdir, rather
745     than remove the old subdir only to do a new checkout into the new
746     subdir.
747
748     We could solve this by "sneaking around the back" and looking in
749     ib->new_desc, ib->old_desc to check if anything else in this
750     parent_dir has the same URL.  Of course, if an external gets
751     moved into some other directory, then we'd lose anyway.  The only
752     way to fully handle this would be to harvest a global list based
753     on urls/revs, and consult the list every time we're about to
754     delete an external subdir: whenever a deletion is really part of
755     a rename, then we'd do the rename on the spot.
756
757     IMHO, renames aren't going to be frequent enough to make the
758     extra bookkeeping worthwhile.
759  */
760
761  /* If the external is being checked out, exported or updated,
762     determine if the external is a file or directory. */
763  if (new_item)
764    {
765      /* Get the RA connection. */
766      SVN_ERR(svn_client__ra_session_from_path(&ra_session,
767                                               &ra_cache.ra_revnum,
768                                               &ra_cache.ra_session_url,
769                                               new_item->url, NULL,
770                                               &(new_item->peg_revision),
771                                               &(new_item->revision), ib->ctx,
772                                               ib->pool));
773
774      SVN_ERR(svn_ra_get_uuid2(ra_session, &ra_cache.repos_uuid, ib->pool));
775      SVN_ERR(svn_ra_get_repos_root2(ra_session, &ra_cache.repos_root_url,
776                                     ib->pool));
777      SVN_ERR(svn_ra_check_path(ra_session, "", ra_cache.ra_revnum, &kind,
778                                ib->pool));
779
780      if (svn_node_none == kind)
781        return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
782                                 _("URL '%s' at revision %ld doesn't exist"),
783                                 ra_cache.ra_session_url,
784                                 ra_cache.ra_revnum);
785
786      if (svn_node_dir != kind && svn_node_file != kind)
787        return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
788                                 _("URL '%s' at revision %ld is not a file "
789                                   "or a directory"),
790                                 ra_cache.ra_session_url,
791                                 ra_cache.ra_revnum);
792
793      ra_cache.kind_p = &kind;
794    }
795
796  /* Not protecting against recursive externals.  Detecting them in
797     the global case is hard, and it should be pretty obvious to a
798     user when it happens.  Worst case: your disk fills up :-). */
799
800  if (! old_item)
801    {
802      /* This branch is only used during a checkout or an export. */
803
804      /* First notify that we're about to handle an external. */
805      if (ib->ctx->notify_func2)
806        (*ib->ctx->notify_func2)
807          (ib->ctx->notify_baton2,
808           svn_wc_create_notify(path, svn_wc_notify_update_external,
809                                ib->iter_pool), ib->iter_pool);
810
811      switch (*ra_cache.kind_p)
812        {
813        case svn_node_dir:
814          /* The target dir might have multiple components.  Guarantee
815             the path leading down to the last component. */
816          svn_path_split(path, &parent, NULL, ib->iter_pool);
817          SVN_ERR(svn_io_make_dir_recursively(parent, ib->iter_pool));
818
819          /* If we were handling renames the fancy way, then before
820             checking out a new subdir here, we would somehow learn if
821             it's really just a rename of an old one.  That would work in
822             tandem with the next case -- this case would do nothing,
823             knowing that the next case either already has, or soon will,
824             rename the external subdirectory. */
825
826          if (ib->is_export)
827            /* ### It should be okay to "force" this export.  Externals
828               only get created in subdirectories of versioned
829               directories, so an external directory couldn't already
830               exist before the parent export process unless a versioned
831               directory above it did, which means the user would have
832               already had to force these creations to occur. */
833            SVN_ERR(svn_client_export4(NULL, new_item->url, path,
834                                       &(new_item->peg_revision),
835                                       &(new_item->revision),
836                                       TRUE, FALSE, svn_depth_infinity, NULL,
837                                       ib->ctx, ib->iter_pool));
838          else
839            SVN_ERR(svn_client__checkout_internal
840                    (NULL, new_item->url, path,
841                     &(new_item->peg_revision), &(new_item->revision),
842                     &ra_cache,
843                     SVN_DEPTH_INFINITY_OR_FILES(TRUE),
844                     FALSE, FALSE, ib->timestamp_sleep, ib->ctx,
845                     ib->iter_pool));
846          break;
847        case svn_node_file:
848          if (ib->is_export)
849            /* Do not overwrite an existing file with this file
850               external. */
851            SVN_ERR(svn_client_export4(NULL, new_item->url, path,
852                                       &(new_item->peg_revision),
853                                       &(new_item->revision),
854                                       FALSE, TRUE, svn_depth_infinity, NULL,
855                                       ib->ctx, ib->iter_pool));
856          else
857            SVN_ERR(switch_file_external(path,
858                                         new_item->url,
859                                         &new_item->peg_revision,
860                                         &new_item->revision,
861                                         ib->adm_access,
862                                         ra_session,
863                                         ra_cache.ra_session_url,
864                                         ra_cache.ra_revnum,
865                                         ra_cache.repos_root_url,
866                                         ib->timestamp_sleep, ib->ctx,
867                                         ib->iter_pool));
868          break;
869        default:
870          SVN_ERR_MALFUNCTION();
871          break;
872        }
873    }
874  else if (! new_item)
875    {
876      /* This branch is only used when an external is deleted from the
877         repository and the working copy is updated. */
878
879      /* See comment in above case about fancy rename handling.  Here,
880         before removing an old subdir, we would see if it wants to
881         just be renamed to a new one. */
882
883      svn_error_t *err;
884      svn_wc_adm_access_t *adm_access;
885      svn_boolean_t close_access_baton_when_done;
886
887      const char *what_to_remove;
888
889      /* Determine if a directory or file external is being removed.
890         Try to handle the case when the user deletes the external by
891         hand or it is missing.  First try to open the directory for a
892         directory external.  If that doesn't work, get the access
893         baton for the parent directory and remove the entry for the
894         external. */
895      err = svn_wc_adm_open3(&adm_access, NULL, path, TRUE, -1,
896                             ib->ctx->cancel_func, ib->ctx->cancel_baton,
897                             ib->iter_pool);
898      if (err)
899        {
900          const char *anchor;
901          const char *target;
902          svn_error_t *err2;
903
904          SVN_ERR(svn_wc_get_actual_target(path, &anchor, &target,
905                                           ib->iter_pool));
906
907          err2 = svn_wc_adm_retrieve(&adm_access, ib->adm_access, anchor,
908                                     ib->iter_pool);
909          if (err2)
910            {
911              svn_error_clear(err2);
912              return err;
913            }
914          else
915            {
916              svn_error_clear(err);
917            }
918          close_access_baton_when_done = FALSE;
919          what_to_remove = target;
920        }
921      else
922        {
923          close_access_baton_when_done = TRUE;
924          what_to_remove = SVN_WC_ENTRY_THIS_DIR;
925        }
926
927      /* We don't use relegate_dir_external() here, because we know that
928         nothing else in this externals description (at least) is
929         going to need this directory, and therefore it's better to
930         leave stuff where the user expects it. */
931      err = svn_wc_remove_from_revision_control
932        (adm_access, what_to_remove, TRUE, FALSE,
933         ib->ctx->cancel_func, ib->ctx->cancel_baton, ib->iter_pool);
934
935      /* ### Ugly. Unlock only if not going to return an error. Revisit */
936      if (close_access_baton_when_done &&
937          (!err || err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD))
938        {
939          svn_error_t *err2 = svn_wc_adm_close2(adm_access, ib->iter_pool);
940          if (err2)
941            {
942              if (!err)
943                err = err2;
944              else
945                svn_error_clear(err2);
946            }
947        }
948
949      if (err && (err->apr_err != SVN_ERR_WC_LEFT_LOCAL_MOD))
950        return err;
951      svn_error_clear(err);
952
953      /* ### If there were multiple path components leading down to
954         that wc, we could try to remove them too. */
955    }
956  else
957    {
958      /* This branch handles all other changes. */
959
960      /* First notify that we're about to handle an external. */
961      if (ib->ctx->notify_func2)
962        (*ib->ctx->notify_func2)
963          (ib->ctx->notify_baton2,
964           svn_wc_create_notify(path, svn_wc_notify_update_external,
965                                ib->iter_pool), ib->iter_pool);
966
967      /* Either the URL changed, or the exact same item is present in
968         both hashes, and caller wants to update such unchanged items.
969         In the latter case, the call below will try to make sure that
970         the external really is a WC pointing to the correct
971         URL/revision. */
972      switch (*ra_cache.kind_p)
973        {
974        case svn_node_dir:
975          SVN_ERR(switch_dir_external(path, new_item->url,
976                                      &(new_item->revision),
977                                      &(new_item->peg_revision),
978                                      ib->timestamp_sleep, ib->ctx,
979                                      ib->iter_pool));
980          break;
981        case svn_node_file:
982          SVN_ERR(switch_file_external(path,
983                                       new_item->url,
984                                       &new_item->peg_revision,
985                                       &new_item->revision,
986                                       ib->adm_access,
987                                       ra_session,
988                                       ra_cache.ra_session_url,
989                                       ra_cache.ra_revnum,
990                                       ra_cache.repos_root_url,
991                                       ib->timestamp_sleep, ib->ctx,
992                                       ib->iter_pool));
993          break;
994        default:
995          SVN_ERR_MALFUNCTION();
996          break;
997        }
998    }
999
1000  /* Clear ib->iter_pool -- we only use it for scratchwork (and this will
1001     close any RA sessions still open in this pool). */
1002  svn_pool_clear(ib->iter_pool);
1003
1004  return SVN_NO_ERROR;
1005}
1006
1007
1008static svn_error_t *
1009handle_external_item_change_wrapper(const void *key, apr_ssize_t klen,
1010                                    enum svn_hash_diff_key_status status,
1011                                    void *baton)
1012{
1013  struct handle_external_item_change_baton *ib = baton;
1014  svn_error_t *err = handle_external_item_change(key, klen, status, baton);
1015  if (err && ib->ctx->notify_func2)
1016    {
1017      const char *path = svn_path_join(ib->parent_dir, key, ib->iter_pool);
1018      svn_wc_notify_t *notifier =
1019        svn_wc_create_notify(path,
1020                             svn_wc_notify_failed_external,
1021                             ib->pool);
1022      notifier->err = err;
1023      ib->ctx->notify_func2(ib->ctx->notify_baton2, notifier, ib->pool);
1024    }
1025  svn_error_clear(err);
1026  return SVN_NO_ERROR;
1027}
1028
1029
1030/* Closure for handle_externals_change. */
1031struct handle_externals_desc_change_baton
1032{
1033  /* As returned by svn_wc_edited_externals(). */
1034  apr_hash_t *externals_new;
1035  apr_hash_t *externals_old;
1036
1037  /* The requested depth of the driving operation (e.g., update, switch). */
1038  svn_depth_t requested_depth;
1039
1040  /* As returned by svn_wc_traversed_depths().  NULL means no ambient
1041     depths available (e.g., svn export). */
1042  apr_hash_t *ambient_depths;
1043
1044  /* These two map a URL to a path where the URL is either checked out
1045     to or exported to.  The to_path must be a substring of the
1046     external item parent directory path. */
1047  const char *from_url;
1048  const char *to_path;
1049
1050  /* Passed through to handle_external_item_change_baton. */
1051  svn_wc_adm_access_t *adm_access;
1052  svn_client_ctx_t *ctx;
1053  const char *repos_root_url;
1054  svn_boolean_t *timestamp_sleep;
1055  svn_boolean_t is_export;
1056
1057  apr_pool_t *pool;
1058};
1059
1060
1061/* This implements the 'svn_hash_diff_func_t' interface.
1062   BATON is of type 'struct handle_externals_desc_change_baton *'.
1063*/
1064static svn_error_t *
1065handle_externals_desc_change(const void *key, apr_ssize_t klen,
1066                             enum svn_hash_diff_key_status status,
1067                             void *baton)
1068{
1069  struct handle_externals_desc_change_baton *cb = baton;
1070  struct handle_external_item_change_baton ib = { 0 };
1071  const char *old_desc_text, *new_desc_text;
1072  apr_array_header_t *old_desc, *new_desc;
1073  apr_hash_t *old_desc_hash, *new_desc_hash;
1074  apr_size_t len;
1075  int i;
1076  svn_wc_external_item2_t *item;
1077  const char *ambient_depth_w;
1078  svn_depth_t ambient_depth;
1079
1080  if (cb->is_export)
1081    SVN_ERR_ASSERT(!cb->adm_access);
1082  else
1083    SVN_ERR_ASSERT(cb->adm_access);
1084
1085  if (cb->ambient_depths)
1086    {
1087      ambient_depth_w = apr_hash_get(cb->ambient_depths, key, klen);
1088      if (ambient_depth_w == NULL)
1089        {
1090          return svn_error_createf
1091            (SVN_ERR_WC_CORRUPT, NULL,
1092             _("Traversal of '%s' found no ambient depth"),
1093             (const char *) key);
1094        }
1095      else
1096        {
1097          ambient_depth = svn_depth_from_word(ambient_depth_w);
1098        }
1099    }
1100  else
1101    {
1102      ambient_depth = svn_depth_infinity;
1103    }
1104
1105  /* Bag out if the depth here is too shallow for externals action. */
1106  if ((cb->requested_depth < svn_depth_infinity
1107       && cb->requested_depth != svn_depth_unknown)
1108      || (ambient_depth < svn_depth_infinity
1109          && cb->requested_depth < svn_depth_infinity))
1110    return SVN_NO_ERROR;
1111
1112  if ((old_desc_text = apr_hash_get(cb->externals_old, key, klen)))
1113    SVN_ERR(svn_wc_parse_externals_description3(&old_desc, key, old_desc_text,
1114                                                FALSE, cb->pool));
1115  else
1116    old_desc = NULL;
1117
1118  if ((new_desc_text = apr_hash_get(cb->externals_new, key, klen)))
1119    SVN_ERR(svn_wc_parse_externals_description3(&new_desc, key, new_desc_text,
1120                                                FALSE, cb->pool));
1121  else
1122    new_desc = NULL;
1123
1124  old_desc_hash = apr_hash_make(cb->pool);
1125  new_desc_hash = apr_hash_make(cb->pool);
1126
1127  /* Create hashes of our two externals arrays so that we can
1128     efficiently generate a diff for them. */
1129  for (i = 0; old_desc && (i < old_desc->nelts); i++)
1130    {
1131      item = APR_ARRAY_IDX(old_desc, i, svn_wc_external_item2_t *);
1132
1133      apr_hash_set(old_desc_hash, item->target_dir,
1134                   APR_HASH_KEY_STRING, item);
1135    }
1136
1137  for (i = 0; new_desc && (i < new_desc->nelts); i++)
1138    {
1139      item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *);
1140
1141      apr_hash_set(new_desc_hash, item->target_dir,
1142                   APR_HASH_KEY_STRING, item);
1143    }
1144
1145  ib.old_desc          = old_desc_hash;
1146  ib.new_desc          = new_desc_hash;
1147  ib.parent_dir        = (const char *) key;
1148  ib.repos_root_url    = cb->repos_root_url;
1149  ib.adm_access        = cb->adm_access;
1150  ib.ctx               = cb->ctx;
1151  ib.is_export         = cb->is_export;
1152  ib.timestamp_sleep   = cb->timestamp_sleep;
1153  ib.pool              = cb->pool;
1154  ib.iter_pool         = svn_pool_create(cb->pool);
1155
1156  /* Get the URL of the parent directory by appending a portion of
1157     parent_dir to from_url.  from_url is the URL for to_path and
1158     to_path is a substring of parent_dir, so append any characters in
1159     parent_dir past strlen(to_path) to from_url (making sure to move
1160     past a '/' in parent_dir, otherwise svn_path_url_add_component()
1161     will error. */
1162  len = strlen(cb->to_path);
1163  if (ib.parent_dir[len] == '/')
1164    ++len;
1165  ib.parent_dir_url = svn_path_url_add_component2(cb->from_url,
1166                                                  ib.parent_dir + len,
1167                                                  cb->pool);
1168
1169  /* We must use a custom version of svn_hash_diff so that the diff
1170     entries are processed in the order they were originally specified
1171     in the svn:externals properties. */
1172
1173  for (i = 0; old_desc && (i < old_desc->nelts); i++)
1174    {
1175      item = APR_ARRAY_IDX(old_desc, i, svn_wc_external_item2_t *);
1176
1177      if (apr_hash_get(new_desc_hash, item->target_dir, APR_HASH_KEY_STRING))
1178        SVN_ERR(handle_external_item_change_wrapper(item->target_dir,
1179                                                    APR_HASH_KEY_STRING,
1180                                                    svn_hash_diff_key_both,
1181                                                    &ib));
1182      else
1183        SVN_ERR(handle_external_item_change_wrapper(item->target_dir,
1184                                                    APR_HASH_KEY_STRING,
1185                                                    svn_hash_diff_key_a,
1186                                                    &ib));
1187    }
1188  for (i = 0; new_desc && (i < new_desc->nelts); i++)
1189    {
1190      item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *);
1191      if (! apr_hash_get(old_desc_hash, item->target_dir, APR_HASH_KEY_STRING))
1192        SVN_ERR(handle_external_item_change_wrapper(item->target_dir,
1193                                                    APR_HASH_KEY_STRING,
1194                                                    svn_hash_diff_key_b,
1195                                                    &ib));
1196    }
1197
1198  /* Now destroy the subpool we pass to the hash differ.  This will
1199     close any remaining RA sessions used by the hash diff callback. */
1200  svn_pool_destroy(ib.iter_pool);
1201
1202  return SVN_NO_ERROR;
1203}
1204
1205
1206svn_error_t *
1207svn_client__handle_externals(svn_wc_adm_access_t *adm_access,
1208                             svn_wc_traversal_info_t *traversal_info,
1209                             const char *from_url,
1210                             const char *to_path,
1211                             const char *repos_root_url,
1212                             svn_depth_t requested_depth,
1213                             svn_boolean_t *timestamp_sleep,
1214                             svn_client_ctx_t *ctx,
1215                             apr_pool_t *pool)
1216{
1217  apr_hash_t *externals_old, *externals_new, *ambient_depths;
1218  struct handle_externals_desc_change_baton cb = { 0 };
1219
1220  svn_wc_edited_externals(&externals_old, &externals_new, traversal_info);
1221  svn_wc_traversed_depths(&ambient_depths, traversal_info);
1222
1223  /* Sanity check; see r30124. */
1224  if (! svn_path_is_url(from_url))
1225    return svn_error_createf
1226      (SVN_ERR_BAD_URL, NULL, _("'%s' is not a URL"), from_url);
1227
1228  cb.externals_new     = externals_new;
1229  cb.externals_old     = externals_old;
1230  cb.requested_depth   = requested_depth;
1231  cb.ambient_depths    = ambient_depths;
1232  cb.from_url          = from_url;
1233  cb.to_path           = to_path;
1234  cb.repos_root_url    = repos_root_url;
1235  cb.adm_access        = adm_access;
1236  cb.ctx               = ctx;
1237  cb.timestamp_sleep   = timestamp_sleep;
1238  cb.is_export         = FALSE;
1239  cb.pool              = pool;
1240
1241  return svn_hash_diff(cb.externals_old, cb.externals_new,
1242                       handle_externals_desc_change, &cb, pool);
1243}
1244
1245
1246svn_error_t *
1247svn_client__fetch_externals(apr_hash_t *externals,
1248                            const char *from_url,
1249                            const char *to_path,
1250                            const char *repos_root_url,
1251                            svn_depth_t requested_depth,
1252                            svn_boolean_t is_export,
1253                            svn_boolean_t *timestamp_sleep,
1254                            svn_client_ctx_t *ctx,
1255                            apr_pool_t *pool)
1256{
1257  struct handle_externals_desc_change_baton cb = { 0 };
1258
1259  cb.externals_new     = externals;
1260  cb.externals_old     = apr_hash_make(pool);
1261  cb.requested_depth   = requested_depth;
1262  cb.ambient_depths    = NULL;
1263  cb.adm_access        = NULL;
1264  cb.ctx               = ctx;
1265  cb.from_url          = from_url;
1266  cb.to_path           = to_path;
1267  cb.repos_root_url    = repos_root_url;
1268  cb.timestamp_sleep   = timestamp_sleep;
1269  cb.is_export         = is_export;
1270  cb.pool              = pool;
1271
1272  return svn_hash_diff(cb.externals_old, cb.externals_new,
1273                       handle_externals_desc_change, &cb, pool);
1274}
1275
1276
1277svn_error_t *
1278svn_client__do_external_status(svn_wc_traversal_info_t *traversal_info,
1279                               svn_wc_status_func3_t status_func,
1280                               void *status_baton,
1281                               svn_depth_t depth,
1282                               svn_boolean_t get_all,
1283                               svn_boolean_t update,
1284                               svn_boolean_t no_ignore,
1285                               svn_client_ctx_t *ctx,
1286                               apr_pool_t *pool)
1287{
1288  apr_hash_t *externals_old, *externals_new;
1289  apr_hash_index_t *hi;
1290  apr_pool_t *subpool = svn_pool_create(pool);
1291
1292  /* Get the values of the svn:externals properties. */
1293  svn_wc_edited_externals(&externals_old, &externals_new, traversal_info);
1294
1295  /* Loop over the hash of new values (we don't care about the old
1296     ones).  This is a mapping of versioned directories to property
1297     values. */
1298  for (hi = apr_hash_first(pool, externals_new);
1299       hi;
1300       hi = apr_hash_next(hi))
1301    {
1302      apr_array_header_t *exts;
1303      const void *key;
1304      void *val;
1305      const char *path;
1306      const char *propval;
1307      apr_pool_t *iterpool;
1308      int i;
1309
1310      /* Clear the subpool. */
1311      svn_pool_clear(subpool);
1312
1313      apr_hash_this(hi, &key, NULL, &val);
1314      path = key;
1315      propval = val;
1316
1317      /* Parse the svn:externals property value.  This results in a
1318         hash mapping subdirectories to externals structures. */
1319      SVN_ERR(svn_wc_parse_externals_description3(&exts, path, propval,
1320                                                  FALSE, subpool));
1321
1322      /* Make a sub-pool of SUBPOOL. */
1323      iterpool = svn_pool_create(subpool);
1324
1325      /* Loop over the subdir array. */
1326      for (i = 0; exts && (i < exts->nelts); i++)
1327        {
1328          const char *fullpath;
1329          svn_wc_external_item2_t *external;
1330          svn_node_kind_t kind;
1331
1332          svn_pool_clear(iterpool);
1333
1334          external = APR_ARRAY_IDX(exts, i, svn_wc_external_item2_t *);
1335          fullpath = svn_path_join(path, external->target_dir, iterpool);
1336
1337          /* If the external target directory doesn't exist on disk,
1338             just skip it. */
1339          SVN_ERR(svn_io_check_path(fullpath, &kind, iterpool));
1340          if (kind != svn_node_dir)
1341            continue;
1342
1343          /* Tell the client we're staring an external status set. */
1344          if (ctx->notify_func2)
1345            (ctx->notify_func2)
1346              (ctx->notify_baton2,
1347               svn_wc_create_notify(fullpath, svn_wc_notify_status_external,
1348                                    iterpool), iterpool);
1349
1350          /* And then do the status. */
1351          SVN_ERR(svn_client_status4(NULL, fullpath,
1352                                     &(external->revision),
1353                                     status_func, status_baton,
1354                                     depth, get_all, update,
1355                                     no_ignore, FALSE, NULL, ctx, iterpool));
1356        }
1357    }
1358
1359  /* Destroy SUBPOOL and (implicitly) ITERPOOL. */
1360  svn_pool_destroy(subpool);
1361
1362  return SVN_NO_ERROR;
1363}
Note: See TracBrowser for help on using the repository browser.