1 /*
2  * externals.c:  handle the svn:externals property
3  *
4  * ====================================================================
5  *    Licensed to the Apache Software Foundation (ASF) under one
6  *    or more contributor license agreements.  See the NOTICE file
7  *    distributed with this work for additional information
8  *    regarding copyright ownership.  The ASF licenses this file
9  *    to you under the Apache License, Version 2.0 (the
10  *    "License"); you may not use this file except in compliance
11  *    with the License.  You may obtain a copy of the License at
12  *
13  *      http://www.apache.org/licenses/LICENSE-2.0
14  *
15  *    Unless required by applicable law or agreed to in writing,
16  *    software distributed under the License is distributed on an
17  *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18  *    KIND, either express or implied.  See the License for the
19  *    specific language governing permissions and limitations
20  *    under the License.
21  * ====================================================================
22  */
23 
24 /* ==================================================================== */
25 
26 
27 
28 /*** Includes. ***/
29 
30 #include <apr_uri.h>
31 #include "svn_hash.h"
32 #include "svn_wc.h"
33 #include "svn_pools.h"
34 #include "svn_client.h"
35 #include "svn_types.h"
36 #include "svn_error.h"
37 #include "svn_dirent_uri.h"
38 #include "svn_path.h"
39 #include "svn_props.h"
40 #include "svn_config.h"
41 #include "client.h"
42 
43 #include "svn_private_config.h"
44 #include "private/svn_wc_private.h"
45 
46 
47 /* Remove the directory at LOCAL_ABSPATH from revision control, and do the
48  * same to any revision controlled directories underneath LOCAL_ABSPATH
49  * (including directories not referred to by parent svn administrative areas);
50  * then if LOCAL_ABSPATH is empty afterwards, remove it, else rename it to a
51  * unique name in the same parent directory.
52  *
53  * Pass CANCEL_FUNC, CANCEL_BATON to svn_wc_remove_from_revision_control.
54  *
55  * Use SCRATCH_POOL for all temporary allocation.
56  */
57 static svn_error_t *
relegate_dir_external(svn_wc_context_t * wc_ctx,const char * wri_abspath,const char * local_abspath,svn_cancel_func_t cancel_func,void * cancel_baton,svn_wc_notify_func2_t notify_func,void * notify_baton,apr_pool_t * scratch_pool)58 relegate_dir_external(svn_wc_context_t *wc_ctx,
59                       const char *wri_abspath,
60                       const char *local_abspath,
61                       svn_cancel_func_t cancel_func,
62                       void *cancel_baton,
63                       svn_wc_notify_func2_t notify_func,
64                       void *notify_baton,
65                       apr_pool_t *scratch_pool)
66 {
67   svn_error_t *err = SVN_NO_ERROR;
68 
69   SVN_ERR(svn_wc__acquire_write_lock(NULL, wc_ctx, local_abspath,
70                                      FALSE, scratch_pool, scratch_pool));
71 
72   err = svn_wc__external_remove(wc_ctx, wri_abspath, local_abspath, FALSE,
73                                 cancel_func, cancel_baton, scratch_pool);
74   if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD))
75     {
76       const char *parent_dir;
77       const char *dirname;
78       const char *new_path;
79 
80       svn_error_clear(err);
81       err = SVN_NO_ERROR;
82 
83       svn_dirent_split(&parent_dir, &dirname, local_abspath, scratch_pool);
84 
85       /* Reserve the new dir name. */
86       SVN_ERR(svn_io_open_uniquely_named(NULL, &new_path,
87                                          parent_dir, dirname, ".OLD",
88                                          svn_io_file_del_none,
89                                          scratch_pool, scratch_pool));
90 
91       /* Sigh...  We must fall ever so slightly from grace.
92 
93          Ideally, there would be no window, however brief, when we
94          don't have a reservation on the new name.  Unfortunately,
95          at least in the Unix (Linux?) version of apr_file_rename(),
96          you can't rename a directory over a file, because it's just
97          calling stdio rename(), which says:
98 
99             ENOTDIR
100               A  component used as a directory in oldpath or newpath
101               path is not, in fact, a directory.  Or, oldpath  is
102               a directory, and newpath exists but is not a directory
103 
104          So instead, we get the name, then remove the file (ugh), then
105          rename the directory, hoping that nobody has gotten that name
106          in the meantime -- which would never happen in real life, so
107          no big deal.
108       */
109       /* Do our best, but no biggy if it fails. The rename will fail. */
110       svn_error_clear(svn_io_remove_file2(new_path, TRUE, scratch_pool));
111 
112       /* Rename. If this is still a working copy we should use the working
113          copy rename function (to release open handles) */
114       err = svn_wc__rename_wc(wc_ctx, local_abspath, new_path,
115                               scratch_pool);
116 
117       if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
118         {
119           svn_error_clear(err);
120 
121           /* And if it is no longer a working copy, we should just rename
122              it */
123           err = svn_io_file_rename2(local_abspath, new_path, FALSE, scratch_pool);
124         }
125 
126       /* ### TODO: We should notify the user about the rename */
127       if (notify_func)
128         {
129           svn_wc_notify_t *notify;
130 
131           notify = svn_wc_create_notify(err ? local_abspath : new_path,
132                                         svn_wc_notify_left_local_modifications,
133                                         scratch_pool);
134           notify->kind = svn_node_dir;
135           notify->err = err;
136 
137           notify_func(notify_baton, notify, scratch_pool);
138         }
139     }
140 
141   return svn_error_trace(err);
142 }
143 
144 /* Try to update a directory external at PATH to URL at REVISION.
145    Use POOL for temporary allocations, and use the client context CTX. */
146 static svn_error_t *
switch_dir_external(const char * local_abspath,const char * url,const char * url_from_externals_definition,const svn_opt_revision_t * peg_revision,const svn_opt_revision_t * revision,const char * defining_abspath,svn_boolean_t * timestamp_sleep,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * pool)147 switch_dir_external(const char *local_abspath,
148                     const char *url,
149                     const char *url_from_externals_definition,
150                     const svn_opt_revision_t *peg_revision,
151                     const svn_opt_revision_t *revision,
152                     const char *defining_abspath,
153                     svn_boolean_t *timestamp_sleep,
154                     svn_ra_session_t *ra_session,
155                     svn_client_ctx_t *ctx,
156                     apr_pool_t *pool)
157 {
158   svn_node_kind_t kind;
159   svn_error_t *err;
160   svn_revnum_t external_peg_rev = SVN_INVALID_REVNUM;
161   svn_revnum_t external_rev = SVN_INVALID_REVNUM;
162   apr_pool_t *subpool = svn_pool_create(pool);
163   const char *repos_root_url;
164   const char *repos_uuid;
165 
166   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
167 
168   if (peg_revision->kind == svn_opt_revision_number)
169     external_peg_rev = peg_revision->value.number;
170 
171   if (revision->kind == svn_opt_revision_number)
172     external_rev = revision->value.number;
173 
174   /*
175    * The code below assumes existing versioned paths are *not* part of
176    * the external's defining working copy.
177    * The working copy library does not support registering externals
178    * on top of existing BASE nodes and will error out if we try.
179    * So if the external target is part of the defining working copy's
180    * BASE tree, don't attempt to create the external. Doing so would
181    * leave behind a switched path instead of an external (since the
182    * switch succeeds but registration of the external in the DB fails).
183    * The working copy then cannot be updated until the path is switched back.
184    * See issue #4085.
185    */
186   SVN_ERR(svn_wc__node_get_base(&kind, NULL, NULL,
187                                 &repos_root_url, &repos_uuid,
188                                 NULL, ctx->wc_ctx, local_abspath,
189                                 TRUE, /* ignore_enoent */
190                                 pool, pool));
191   if (kind != svn_node_unknown)
192     {
193       const char *wcroot_abspath;
194       const char *defining_wcroot_abspath;
195 
196       SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
197                                  local_abspath, pool, pool));
198       SVN_ERR(svn_wc__get_wcroot(&defining_wcroot_abspath, ctx->wc_ctx,
199                                  defining_abspath, pool, pool));
200       if (strcmp(wcroot_abspath, defining_wcroot_abspath) == 0)
201         return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
202                                  _("The external '%s' defined in %s at '%s' "
203                                    "cannot be checked out because '%s' is "
204                                    "already a versioned path."),
205                                    url_from_externals_definition,
206                                    SVN_PROP_EXTERNALS,
207                                    svn_dirent_local_style(defining_abspath,
208                                                           pool),
209                                    svn_dirent_local_style(local_abspath,
210                                                           pool));
211     }
212 
213   /* If path is a directory, try to update/switch to the correct URL
214      and revision. */
215   SVN_ERR(svn_io_check_path(local_abspath, &kind, pool));
216   if (kind == svn_node_dir)
217     {
218       const char *node_url;
219 
220       /* Doubles as an "is versioned" check. */
221       err = svn_wc__node_get_url(&node_url, ctx->wc_ctx, local_abspath,
222                                  pool, subpool);
223       if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
224         {
225           svn_error_clear(err);
226           err = SVN_NO_ERROR;
227           goto relegate;
228         }
229       else if (err)
230         return svn_error_trace(err);
231 
232       if (node_url)
233         {
234           svn_boolean_t is_wcroot;
235 
236           SVN_ERR(svn_wc__is_wcroot(&is_wcroot, ctx->wc_ctx, local_abspath,
237                                     pool));
238 
239           if (! is_wcroot)
240           {
241             /* This can't be a directory external! */
242 
243             err = svn_wc__external_remove(ctx->wc_ctx, defining_abspath,
244                                           local_abspath,
245                                           TRUE /* declaration_only */,
246                                           ctx->cancel_func, ctx->cancel_baton,
247                                           pool);
248 
249             if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
250               {
251                 /* New external... No problem that we can't remove it */
252                 svn_error_clear(err);
253                 err = NULL;
254               }
255             else if (err)
256               return svn_error_trace(err);
257 
258             return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
259                                      _("The external '%s' defined in %s at '%s' "
260                                        "cannot be checked out because '%s' is "
261                                        "already a versioned path."),
262                                      url_from_externals_definition,
263                                      SVN_PROP_EXTERNALS,
264                                      svn_dirent_local_style(defining_abspath,
265                                                             pool),
266                                      svn_dirent_local_style(local_abspath,
267                                                             pool));
268           }
269 
270           /* If we have what appears to be a version controlled
271              subdir, and its top-level URL matches that of our
272              externals definition, perform an update. */
273           if (strcmp(node_url, url) == 0)
274             {
275               SVN_ERR(svn_client__update_internal(NULL, timestamp_sleep,
276                                                   local_abspath,
277                                                   revision, svn_depth_unknown,
278                                                   FALSE, FALSE, FALSE, TRUE,
279                                                   FALSE, TRUE,
280                                                   ra_session, ctx, subpool));
281 
282               /* We just decided that this existing directory is an external,
283                  so update the external registry with this information, like
284                  when checking out an external */
285               SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
286                                     defining_abspath,
287                                     local_abspath, svn_node_dir,
288                                     repos_root_url, repos_uuid,
289                                     svn_uri_skip_ancestor(repos_root_url,
290                                                           url, pool),
291                                     external_peg_rev,
292                                     external_rev,
293                                     pool));
294 
295               svn_pool_destroy(subpool);
296               goto cleanup;
297             }
298 
299           /* We'd really prefer not to have to do a brute-force
300              relegation -- blowing away the current external working
301              copy and checking it out anew -- so we'll first see if we
302              can get away with a generally cheaper relocation (if
303              required) and switch-style update.
304 
305              To do so, we need to know the repository root URL of the
306              external working copy as it currently sits. */
307           err = svn_wc__node_get_repos_info(NULL, NULL,
308                                             &repos_root_url, &repos_uuid,
309                                             ctx->wc_ctx, local_abspath,
310                                             pool, subpool);
311           if (err)
312             {
313               if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
314                   && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
315                 return svn_error_trace(err);
316 
317               svn_error_clear(err);
318               repos_root_url = NULL;
319               repos_uuid = NULL;
320             }
321 
322           if (repos_root_url)
323             {
324               /* If the new external target URL is not obviously a
325                  child of the external working copy's current
326                  repository root URL... */
327               if (! svn_uri__is_ancestor(repos_root_url, url))
328                 {
329                   const char *repos_root;
330 
331                   /* ... then figure out precisely which repository
332                       root URL that target URL *is* a child of ... */
333                   SVN_ERR(svn_client_get_repos_root(&repos_root, NULL, url,
334                                                     ctx, subpool, subpool));
335 
336                   /* ... and use that to try to relocate the external
337                      working copy to the target location.  */
338                   err = svn_client_relocate2(local_abspath, repos_root_url,
339                                              repos_root, FALSE, ctx, subpool);
340 
341                   /* If the relocation failed because the new URL
342                      points to a totally different repository, we've
343                      no choice but to relegate and check out a new WC. */
344                   if (err
345                       && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION
346                           || (err->apr_err
347                               == SVN_ERR_CLIENT_INVALID_RELOCATION)))
348                     {
349                       svn_error_clear(err);
350                       goto relegate;
351                     }
352                   else if (err)
353                     return svn_error_trace(err);
354 
355                   /* If the relocation went without a hitch, we should
356                      have a new repository root URL. */
357                   repos_root_url = repos_root;
358                 }
359 
360               SVN_ERR(svn_client__switch_internal(NULL, local_abspath, url,
361                                                   peg_revision, revision,
362                                                   svn_depth_infinity,
363                                                   TRUE, FALSE, FALSE,
364                                                   TRUE /* ignore_ancestry */,
365                                                   timestamp_sleep,
366                                                   ctx, subpool));
367 
368               SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
369                                                 defining_abspath,
370                                                 local_abspath, svn_node_dir,
371                                                 repos_root_url, repos_uuid,
372                                                 svn_uri_skip_ancestor(
373                                                             repos_root_url,
374                                                             url, subpool),
375                                                 external_peg_rev,
376                                                 external_rev,
377                                                 subpool));
378 
379               svn_pool_destroy(subpool);
380               goto cleanup;
381             }
382         }
383     }
384 
385  relegate:
386 
387   /* Fall back on removing the WC and checking out a new one. */
388 
389   /* Ensure that we don't have any RA sessions or WC locks from failed
390      operations above. */
391   svn_pool_destroy(subpool);
392 
393   if (kind == svn_node_dir)
394     {
395       /* Buh-bye, old and busted ... */
396       SVN_ERR(relegate_dir_external(ctx->wc_ctx, defining_abspath,
397                                     local_abspath,
398                                     ctx->cancel_func, ctx->cancel_baton,
399                                     ctx->notify_func2, ctx->notify_baton2,
400                                     pool));
401     }
402   else
403     {
404       /* The target dir might have multiple components.  Guarantee
405          the path leading down to the last component. */
406       const char *parent = svn_dirent_dirname(local_abspath, pool);
407       SVN_ERR(svn_io_make_dir_recursively(parent, pool));
408     }
409 
410   /* ... Hello, new hotness. */
411   SVN_ERR(svn_client__checkout_internal(NULL, timestamp_sleep,
412                                         url, local_abspath, peg_revision,
413                                         revision, svn_depth_infinity,
414                                         FALSE, FALSE,
415                                         ra_session,
416                                         ctx, pool));
417 
418   SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL,
419                                       &repos_root_url,
420                                       &repos_uuid,
421                                       ctx->wc_ctx, local_abspath,
422                                       pool, pool));
423 
424   SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
425                                     defining_abspath,
426                                     local_abspath, svn_node_dir,
427                                     repos_root_url, repos_uuid,
428                                     svn_uri_skip_ancestor(repos_root_url,
429                                                           url, pool),
430                                     external_peg_rev,
431                                     external_rev,
432                                     pool));
433 
434  cleanup:
435   /* Issues #4123 and #4130: We don't need to keep the newly checked
436      out external's DB open. */
437   SVN_ERR(svn_wc__close_db(local_abspath, ctx->wc_ctx, pool));
438 
439   return SVN_NO_ERROR;
440 }
441 
442 /* Try to update a file external at LOCAL_ABSPATH to SWITCH_LOC. This function
443    assumes caller has a write lock in CTX.  Use SCRATCH_POOL for temporary
444    allocations, and use the client context CTX. */
445 static svn_error_t *
switch_file_external(const char * local_abspath,const svn_client__pathrev_t * switch_loc,const char * record_url,const svn_opt_revision_t * record_peg_revision,const svn_opt_revision_t * record_revision,const char * def_dir_abspath,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)446 switch_file_external(const char *local_abspath,
447                      const svn_client__pathrev_t *switch_loc,
448                      const char *record_url,
449                      const svn_opt_revision_t *record_peg_revision,
450                      const svn_opt_revision_t *record_revision,
451                      const char *def_dir_abspath,
452                      svn_ra_session_t *ra_session,
453                      svn_client_ctx_t *ctx,
454                      apr_pool_t *scratch_pool)
455 {
456   svn_config_t *cfg = ctx->config
457                       ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
458                       : NULL;
459   svn_boolean_t use_commit_times;
460   const char *diff3_cmd;
461   const char *preserved_exts_str;
462   const apr_array_header_t *preserved_exts;
463   svn_node_kind_t kind, external_kind;
464 
465   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
466 
467   /* See if the user wants last-commit timestamps instead of current ones. */
468   SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
469                               SVN_CONFIG_SECTION_MISCELLANY,
470                               SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
471 
472   /* Get the external diff3, if any. */
473   svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
474                  SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
475 
476   if (diff3_cmd != NULL)
477     SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool));
478 
479   /* See which files the user wants to preserve the extension of when
480      conflict files are made. */
481   svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
482                  SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
483   preserved_exts = *preserved_exts_str
484     ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool)
485     : NULL;
486 
487   {
488     const char *wcroot_abspath;
489 
490     SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath,
491                                scratch_pool, scratch_pool));
492 
493     /* File externals can only be installed inside the current working copy.
494        So verify if the working copy that contains/will contain the target
495        is the defining abspath, or one of its ancestors */
496 
497     if (!svn_dirent_is_ancestor(wcroot_abspath, def_dir_abspath))
498         return svn_error_createf(
499                         SVN_ERR_WC_BAD_PATH, NULL,
500                         _("Cannot insert a file external defined on '%s' "
501                           "into the working copy '%s'."),
502                         svn_dirent_local_style(def_dir_abspath,
503                                                scratch_pool),
504                         svn_dirent_local_style(wcroot_abspath,
505                                                scratch_pool));
506   }
507 
508   SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath,
509                             TRUE, FALSE, scratch_pool));
510 
511   SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
512                                      ctx->wc_ctx, local_abspath, local_abspath,
513                                      TRUE, scratch_pool, scratch_pool));
514 
515   /* If there is a versioned item with this name, ensure it's a file
516      external before working with it.  If there is no entry in the
517      working copy, then create an empty file and add it to the working
518      copy. */
519   if (kind != svn_node_none && kind != svn_node_unknown)
520     {
521       if (external_kind != svn_node_file)
522         {
523           return svn_error_createf(
524               SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0,
525              _("The file external from '%s' cannot overwrite the existing "
526                "versioned item at '%s'"),
527              switch_loc->url,
528              svn_dirent_local_style(local_abspath, scratch_pool));
529         }
530     }
531   else
532     {
533       svn_node_kind_t disk_kind;
534 
535       SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
536 
537       if (disk_kind == svn_node_file || disk_kind == svn_node_dir)
538         return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL,
539                                  _("The file external '%s' can not be "
540                                    "created because the node exists."),
541                                  svn_dirent_local_style(local_abspath,
542                                                         scratch_pool));
543     }
544 
545   {
546     const svn_ra_reporter3_t *reporter;
547     void *report_baton;
548     const svn_delta_editor_t *switch_editor;
549     void *switch_baton;
550     svn_revnum_t revnum;
551     apr_array_header_t *inherited_props;
552     const char *target = svn_dirent_basename(local_abspath, scratch_pool);
553 
554     /* Get the external file's iprops. */
555     SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "",
556                                        switch_loc->rev,
557                                        scratch_pool, scratch_pool));
558 
559     SVN_ERR(svn_ra_reparent(ra_session,
560                             svn_uri_dirname(switch_loc->url, scratch_pool),
561                             scratch_pool));
562 
563     SVN_ERR(svn_wc__get_file_external_editor(&switch_editor, &switch_baton,
564                                              &revnum, ctx->wc_ctx,
565                                              local_abspath,
566                                              def_dir_abspath,
567                                              switch_loc->url,
568                                              switch_loc->repos_root_url,
569                                              switch_loc->repos_uuid,
570                                              inherited_props,
571                                              use_commit_times,
572                                              diff3_cmd, preserved_exts,
573                                              def_dir_abspath,
574                                              record_url,
575                                              record_peg_revision,
576                                              record_revision,
577                                              ctx->conflict_func2,
578                                              ctx->conflict_baton2,
579                                              ctx->cancel_func,
580                                              ctx->cancel_baton,
581                                              ctx->notify_func2,
582                                              ctx->notify_baton2,
583                                              scratch_pool, scratch_pool));
584 
585     /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
586      invalid revnum, that means RA will use the latest revision. */
587     SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton,
588                               switch_loc->rev,
589                               target, svn_depth_unknown, switch_loc->url,
590                               FALSE /* send_copyfrom */,
591                               TRUE /* ignore_ancestry */,
592                               switch_editor, switch_baton,
593                               scratch_pool, scratch_pool));
594 
595     SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath,
596                                         reporter, report_baton,
597                                         TRUE,  use_commit_times,
598                                         ctx->cancel_func, ctx->cancel_baton,
599                                         ctx->notify_func2, ctx->notify_baton2,
600                                         scratch_pool));
601 
602     if (ctx->notify_func2)
603       {
604         svn_wc_notify_t *notify
605           = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
606                                  scratch_pool);
607         notify->kind = svn_node_none;
608         notify->content_state = notify->prop_state
609           = svn_wc_notify_state_inapplicable;
610         notify->lock_state = svn_wc_notify_lock_state_inapplicable;
611         notify->revision = revnum;
612         ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
613       }
614   }
615 
616   return SVN_NO_ERROR;
617 }
618 
619 /* Wrappers around svn_wc__external_remove, obtaining and releasing a lock for
620    directory externals */
621 static svn_error_t *
remove_external2(svn_boolean_t * removed,svn_wc_context_t * wc_ctx,const char * wri_abspath,const char * local_abspath,svn_node_kind_t external_kind,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)622 remove_external2(svn_boolean_t *removed,
623                 svn_wc_context_t *wc_ctx,
624                 const char *wri_abspath,
625                 const char *local_abspath,
626                 svn_node_kind_t external_kind,
627                 svn_cancel_func_t cancel_func,
628                 void *cancel_baton,
629                 apr_pool_t *scratch_pool)
630 {
631   SVN_ERR(svn_wc__external_remove(wc_ctx, wri_abspath,
632                                   local_abspath,
633                                   (external_kind == svn_node_none),
634                                   cancel_func, cancel_baton,
635                                   scratch_pool));
636 
637   *removed = TRUE;
638   return SVN_NO_ERROR;
639 }
640 
641 
642 static svn_error_t *
remove_external(svn_boolean_t * removed,svn_wc_context_t * wc_ctx,const char * wri_abspath,const char * local_abspath,svn_node_kind_t external_kind,svn_cancel_func_t cancel_func,void * cancel_baton,apr_pool_t * scratch_pool)643 remove_external(svn_boolean_t *removed,
644                 svn_wc_context_t *wc_ctx,
645                 const char *wri_abspath,
646                 const char *local_abspath,
647                 svn_node_kind_t external_kind,
648                 svn_cancel_func_t cancel_func,
649                 void *cancel_baton,
650                 apr_pool_t *scratch_pool)
651 {
652   *removed = FALSE;
653   switch (external_kind)
654     {
655       case svn_node_dir:
656         SVN_WC__CALL_WITH_WRITE_LOCK(
657             remove_external2(removed,
658                              wc_ctx, wri_abspath,
659                              local_abspath, external_kind,
660                              cancel_func, cancel_baton,
661                              scratch_pool),
662             wc_ctx, local_abspath, FALSE, scratch_pool);
663         break;
664       case svn_node_file:
665       default:
666         SVN_ERR(remove_external2(removed,
667                                  wc_ctx, wri_abspath,
668                                  local_abspath, external_kind,
669                                  cancel_func, cancel_baton,
670                                  scratch_pool));
671         break;
672     }
673 
674   return SVN_NO_ERROR;
675 }
676 
677 /* Called when an external that is in the EXTERNALS table is no longer
678    referenced from an svn:externals property */
679 static svn_error_t *
handle_external_item_removal(const svn_client_ctx_t * ctx,const char * defining_abspath,const char * local_abspath,apr_pool_t * scratch_pool)680 handle_external_item_removal(const svn_client_ctx_t *ctx,
681                              const char *defining_abspath,
682                              const char *local_abspath,
683                              apr_pool_t *scratch_pool)
684 {
685   svn_error_t *err;
686   svn_node_kind_t external_kind;
687   svn_node_kind_t kind;
688   svn_boolean_t removed = FALSE;
689 
690   /* local_abspath should be a wcroot or a file external */
691   SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
692                                      ctx->wc_ctx, defining_abspath,
693                                      local_abspath, FALSE,
694                                      scratch_pool, scratch_pool));
695 
696   SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE,
697                            scratch_pool));
698 
699   if (external_kind != kind)
700     external_kind = svn_node_none; /* Only remove the registration */
701 
702   err = remove_external(&removed,
703                         ctx->wc_ctx, defining_abspath, local_abspath,
704                         external_kind,
705                         ctx->cancel_func, ctx->cancel_baton,
706                         scratch_pool);
707 
708   if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED && removed)
709     {
710       svn_error_clear(err);
711       err = NULL; /* We removed the working copy, so we can't release the
712                      lock that was stored inside */
713     }
714 
715   if (ctx->notify_func2)
716     {
717       svn_wc_notify_t *notify =
718           svn_wc_create_notify(local_abspath,
719                                svn_wc_notify_update_external_removed,
720                                scratch_pool);
721 
722       notify->kind = kind;
723       notify->err = err;
724 
725       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
726 
727       if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
728         {
729           notify = svn_wc_create_notify(local_abspath,
730                                       svn_wc_notify_left_local_modifications,
731                                       scratch_pool);
732           notify->kind = svn_node_dir;
733           notify->err = err;
734 
735           ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
736         }
737     }
738 
739   if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
740     {
741       svn_error_clear(err);
742       err = NULL;
743     }
744 
745   return svn_error_trace(err);
746 }
747 
748 static svn_error_t *
handle_external_item_change(svn_client_ctx_t * ctx,const char * repos_root_url,const char * parent_dir_abspath,const char * parent_dir_url,const char * local_abspath,const char * old_defining_abspath,const svn_wc_external_item2_t * new_item,svn_ra_session_t * ra_session,svn_boolean_t * timestamp_sleep,apr_pool_t * scratch_pool)749 handle_external_item_change(svn_client_ctx_t *ctx,
750                             const char *repos_root_url,
751                             const char *parent_dir_abspath,
752                             const char *parent_dir_url,
753                             const char *local_abspath,
754                             const char *old_defining_abspath,
755                             const svn_wc_external_item2_t *new_item,
756                             svn_ra_session_t *ra_session,
757                             svn_boolean_t *timestamp_sleep,
758                             apr_pool_t *scratch_pool)
759 {
760   svn_client__pathrev_t *new_loc;
761   const char *new_url;
762   svn_node_kind_t ext_kind;
763 
764   SVN_ERR_ASSERT(repos_root_url && parent_dir_url);
765   SVN_ERR_ASSERT(new_item != NULL);
766 
767   /* Don't bother to check status, since we'll get that for free by
768      attempting to retrieve the hash values anyway.  */
769 
770   /* When creating the absolute URL, use the pool and not the
771      iterpool, since the hash table values outlive the iterpool and
772      any pointers they have should also outlive the iterpool.  */
773 
774   SVN_ERR(svn_wc__resolve_relative_external_url(&new_url,
775                                                 new_item, repos_root_url,
776                                                 parent_dir_url,
777                                                 scratch_pool, scratch_pool));
778 
779   /* Determine if the external is a file or directory. */
780   /* Get the RA connection, if needed. */
781   if (ra_session)
782     {
783       svn_error_t *err = svn_ra_reparent(ra_session, new_url, scratch_pool);
784 
785       if (err)
786         {
787           if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
788             {
789               svn_error_clear(err);
790               ra_session = NULL;
791             }
792           else
793             return svn_error_trace(err);
794         }
795       else
796         {
797           SVN_ERR(svn_client__resolve_rev_and_url(&new_loc,
798                                                   ra_session, new_url,
799                                                   &(new_item->peg_revision),
800                                                   &(new_item->revision), ctx,
801                                                   scratch_pool));
802 
803           SVN_ERR(svn_ra_reparent(ra_session, new_loc->url, scratch_pool));
804         }
805     }
806 
807   if (!ra_session)
808     SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
809                                               new_url, NULL,
810                                               &(new_item->peg_revision),
811                                               &(new_item->revision), ctx,
812                                               scratch_pool));
813 
814   SVN_ERR(svn_ra_check_path(ra_session, "", new_loc->rev, &ext_kind,
815                             scratch_pool));
816 
817   if (svn_node_none == ext_kind)
818     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
819                              _("URL '%s' at revision %ld doesn't exist"),
820                              new_loc->url, new_loc->rev);
821 
822   if (svn_node_dir != ext_kind && svn_node_file != ext_kind)
823     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
824                              _("URL '%s' at revision %ld is not a file "
825                                "or a directory"),
826                              new_loc->url, new_loc->rev);
827 
828 
829   /* Not protecting against recursive externals.  Detecting them in
830      the global case is hard, and it should be pretty obvious to a
831      user when it happens.  Worst case: your disk fills up :-). */
832 
833   /* First notify that we're about to handle an external. */
834   if (ctx->notify_func2)
835     {
836       ctx->notify_func2(
837          ctx->notify_baton2,
838          svn_wc_create_notify(local_abspath,
839                               svn_wc_notify_update_external,
840                               scratch_pool),
841          scratch_pool);
842     }
843 
844   if (! old_defining_abspath)
845     {
846       /* The target dir might have multiple components.  Guarantee the path
847          leading down to the last component. */
848       SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath,
849                                                              scratch_pool),
850                                           scratch_pool));
851     }
852 
853   switch (ext_kind)
854     {
855       case svn_node_dir:
856         SVN_ERR(switch_dir_external(local_abspath, new_loc->url,
857                                     new_item->url,
858                                     &(new_item->peg_revision),
859                                     &(new_item->revision),
860                                     parent_dir_abspath,
861                                     timestamp_sleep, ra_session, ctx,
862                                     scratch_pool));
863         break;
864       case svn_node_file:
865         if (strcmp(repos_root_url, new_loc->repos_root_url))
866           {
867             const char *local_repos_root_url;
868             const char *local_repos_uuid;
869             const char *ext_repos_relpath;
870             svn_error_t *err;
871 
872             /*
873              * The working copy library currently requires that all files
874              * in the working copy have the same repository root URL.
875              * The URL from the file external's definition differs from the
876              * one used by the working copy. As a workaround, replace the
877              * root URL portion of the file external's URL, after making
878              * sure both URLs point to the same repository. See issue #4087.
879              */
880 
881             err = svn_wc__node_get_repos_info(NULL, NULL,
882                                               &local_repos_root_url,
883                                               &local_repos_uuid,
884                                               ctx->wc_ctx, parent_dir_abspath,
885                                               scratch_pool, scratch_pool);
886             if (err)
887               {
888                 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
889                     && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
890                   return svn_error_trace(err);
891 
892                 svn_error_clear(err);
893                 local_repos_root_url = NULL;
894                 local_repos_uuid = NULL;
895               }
896 
897             ext_repos_relpath = svn_uri_skip_ancestor(new_loc->repos_root_url,
898                                                       new_url, scratch_pool);
899             if (local_repos_uuid == NULL || local_repos_root_url == NULL ||
900                 ext_repos_relpath == NULL ||
901                 strcmp(local_repos_uuid, new_loc->repos_uuid) != 0)
902               return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
903                         _("Unsupported external: URL of file external '%s' "
904                           "is not in repository '%s'"),
905                         new_url, repos_root_url);
906 
907             new_url = svn_path_url_add_component2(local_repos_root_url,
908                                                   ext_repos_relpath,
909                                                   scratch_pool);
910             SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
911                                                       new_url,
912                                                       NULL,
913                                                       &(new_item->peg_revision),
914                                                       &(new_item->revision),
915                                                       ctx, scratch_pool));
916           }
917 
918         SVN_ERR(switch_file_external(local_abspath,
919                                      new_loc,
920                                      new_url,
921                                      &new_item->peg_revision,
922                                      &new_item->revision,
923                                      parent_dir_abspath,
924                                      ra_session,
925                                      ctx,
926                                      scratch_pool));
927         break;
928 
929       default:
930         SVN_ERR_MALFUNCTION();
931         break;
932     }
933 
934   return SVN_NO_ERROR;
935 }
936 
937 static svn_error_t *
wrap_external_error(const svn_client_ctx_t * ctx,const char * target_abspath,svn_error_t * err,apr_pool_t * scratch_pool)938 wrap_external_error(const svn_client_ctx_t *ctx,
939                     const char *target_abspath,
940                     svn_error_t *err,
941                     apr_pool_t *scratch_pool)
942 {
943   if (err && err->apr_err != SVN_ERR_CANCELLED)
944     {
945       if (ctx->notify_func2)
946         {
947           svn_wc_notify_t *notifier = svn_wc_create_notify(
948                                             target_abspath,
949                                             svn_wc_notify_failed_external,
950                                             scratch_pool);
951           notifier->err = err;
952           ctx->notify_func2(ctx->notify_baton2, notifier, scratch_pool);
953         }
954       svn_error_clear(err);
955       return SVN_NO_ERROR;
956     }
957 
958   return err;
959 }
960 
961 static svn_error_t *
handle_externals_change(svn_client_ctx_t * ctx,const char * repos_root_url,svn_boolean_t * timestamp_sleep,const char * local_abspath,const char * new_desc_text,apr_hash_t * old_externals,svn_depth_t ambient_depth,svn_depth_t requested_depth,svn_ra_session_t * ra_session,apr_pool_t * scratch_pool)962 handle_externals_change(svn_client_ctx_t *ctx,
963                         const char *repos_root_url,
964                         svn_boolean_t *timestamp_sleep,
965                         const char *local_abspath,
966                         const char *new_desc_text,
967                         apr_hash_t *old_externals,
968                         svn_depth_t ambient_depth,
969                         svn_depth_t requested_depth,
970                         svn_ra_session_t *ra_session,
971                         apr_pool_t *scratch_pool)
972 {
973   apr_array_header_t *new_desc;
974   int i;
975   apr_pool_t *iterpool;
976   const char *url;
977 
978   iterpool = svn_pool_create(scratch_pool);
979 
980   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
981 
982   /* Bag out if the depth here is too shallow for externals action. */
983   if ((requested_depth < svn_depth_infinity
984        && requested_depth != svn_depth_unknown)
985       || (ambient_depth < svn_depth_infinity
986           && requested_depth < svn_depth_infinity))
987     return SVN_NO_ERROR;
988 
989   if (new_desc_text)
990     SVN_ERR(svn_wc_parse_externals_description3(&new_desc, local_abspath,
991                                                 new_desc_text,
992                                                 FALSE, scratch_pool));
993   else
994     new_desc = NULL;
995 
996   SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, local_abspath,
997                                scratch_pool, iterpool));
998 
999   SVN_ERR_ASSERT(url);
1000 
1001   for (i = 0; new_desc && (i < new_desc->nelts); i++)
1002     {
1003       const char *old_defining_abspath;
1004       svn_wc_external_item2_t *new_item;
1005       const char *target_abspath;
1006       svn_boolean_t under_root;
1007 
1008       new_item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *);
1009 
1010       svn_pool_clear(iterpool);
1011 
1012       if (ctx->cancel_func)
1013         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1014 
1015       SVN_ERR(svn_dirent_is_under_root(&under_root, &target_abspath,
1016                                        local_abspath, new_item->target_dir,
1017                                        iterpool));
1018 
1019       if (! under_root)
1020         {
1021           return svn_error_createf(
1022                     SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1023                     _("Path '%s' is not in the working copy"),
1024                     svn_dirent_local_style(
1025                         svn_dirent_join(local_abspath, new_item->target_dir,
1026                                         iterpool),
1027                         iterpool));
1028         }
1029 
1030       old_defining_abspath = svn_hash_gets(old_externals, target_abspath);
1031 
1032       SVN_ERR(wrap_external_error(
1033                       ctx, target_abspath,
1034                       handle_external_item_change(ctx,
1035                                                   repos_root_url,
1036                                                   local_abspath, url,
1037                                                   target_abspath,
1038                                                   old_defining_abspath,
1039                                                   new_item, ra_session,
1040                                                   timestamp_sleep,
1041                                                   iterpool),
1042                       iterpool));
1043 
1044       /* And remove already processed items from the to-remove hash */
1045       if (old_defining_abspath)
1046         svn_hash_sets(old_externals, target_abspath, NULL);
1047     }
1048 
1049   svn_pool_destroy(iterpool);
1050 
1051   return SVN_NO_ERROR;
1052 }
1053 
1054 
1055 svn_error_t *
svn_client__handle_externals(apr_hash_t * externals_new,apr_hash_t * ambient_depths,const char * repos_root_url,const char * target_abspath,svn_depth_t requested_depth,svn_boolean_t * timestamp_sleep,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1056 svn_client__handle_externals(apr_hash_t *externals_new,
1057                              apr_hash_t *ambient_depths,
1058                              const char *repos_root_url,
1059                              const char *target_abspath,
1060                              svn_depth_t requested_depth,
1061                              svn_boolean_t *timestamp_sleep,
1062                              svn_ra_session_t *ra_session,
1063                              svn_client_ctx_t *ctx,
1064                              apr_pool_t *scratch_pool)
1065 {
1066   apr_hash_t *old_external_defs;
1067   apr_hash_index_t *hi;
1068   apr_pool_t *iterpool;
1069 
1070   SVN_ERR_ASSERT(repos_root_url);
1071 
1072   iterpool = svn_pool_create(scratch_pool);
1073 
1074   SVN_ERR(svn_wc__externals_defined_below(&old_external_defs,
1075                                           ctx->wc_ctx, target_abspath,
1076                                           scratch_pool, iterpool));
1077 
1078   for (hi = apr_hash_first(scratch_pool, externals_new);
1079        hi;
1080        hi = apr_hash_next(hi))
1081     {
1082       const char *local_abspath = apr_hash_this_key(hi);
1083       const char *desc_text = apr_hash_this_val(hi);
1084       svn_depth_t ambient_depth = svn_depth_infinity;
1085 
1086       svn_pool_clear(iterpool);
1087 
1088       if (ambient_depths)
1089         {
1090           const char *ambient_depth_w;
1091 
1092           ambient_depth_w = apr_hash_get(ambient_depths, local_abspath,
1093                                          apr_hash_this_key_len(hi));
1094 
1095           if (ambient_depth_w == NULL)
1096             {
1097               return svn_error_createf(
1098                         SVN_ERR_WC_CORRUPT, NULL,
1099                         _("Traversal of '%s' found no ambient depth"),
1100                         svn_dirent_local_style(local_abspath, scratch_pool));
1101             }
1102           else
1103             {
1104               ambient_depth = svn_depth_from_word(ambient_depth_w);
1105             }
1106         }
1107 
1108       SVN_ERR(handle_externals_change(ctx, repos_root_url, timestamp_sleep,
1109                                       local_abspath,
1110                                       desc_text, old_external_defs,
1111                                       ambient_depth, requested_depth,
1112                                       ra_session, iterpool));
1113     }
1114 
1115   /* Remove the remaining externals */
1116   for (hi = apr_hash_first(scratch_pool, old_external_defs);
1117        hi;
1118        hi = apr_hash_next(hi))
1119     {
1120       const char *item_abspath = apr_hash_this_key(hi);
1121       const char *defining_abspath = apr_hash_this_val(hi);
1122       const char *parent_abspath;
1123 
1124       svn_pool_clear(iterpool);
1125 
1126       SVN_ERR(wrap_external_error(
1127                           ctx, item_abspath,
1128                           handle_external_item_removal(ctx, defining_abspath,
1129                                                        item_abspath, iterpool),
1130                           iterpool));
1131 
1132       /* Are there any unversioned directories between the removed
1133        * external and the DEFINING_ABSPATH which we can remove? */
1134       parent_abspath = item_abspath;
1135       do {
1136         svn_node_kind_t kind;
1137 
1138         parent_abspath = svn_dirent_dirname(parent_abspath, iterpool);
1139         SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, parent_abspath,
1140                                   FALSE /* show_deleted*/,
1141                                   FALSE /* show_hidden */,
1142                                   iterpool));
1143         if (kind == svn_node_none)
1144           {
1145             svn_error_t *err;
1146 
1147             err = svn_io_dir_remove_nonrecursive(parent_abspath, iterpool);
1148             if (err)
1149               {
1150                 if (APR_STATUS_IS_ENOTEMPTY(err->apr_err))
1151                   {
1152                     svn_error_clear(err);
1153                     break; /* No parents to delete */
1154                   }
1155                 else if (APR_STATUS_IS_ENOENT(err->apr_err)
1156                          || APR_STATUS_IS_ENOTDIR(err->apr_err))
1157                   {
1158                     svn_error_clear(err);
1159                     /* Fall through; parent dir might be unversioned */
1160                   }
1161                 else
1162                   return svn_error_trace(err);
1163               }
1164           }
1165       } while (strcmp(parent_abspath, defining_abspath) != 0);
1166     }
1167 
1168 
1169   svn_pool_destroy(iterpool);
1170   return SVN_NO_ERROR;
1171 }
1172 
1173 
1174 svn_error_t *
svn_client__export_externals(apr_hash_t * externals,const char * from_url,const char * to_abspath,const char * repos_root_url,svn_depth_t requested_depth,const char * native_eol,svn_boolean_t ignore_keywords,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)1175 svn_client__export_externals(apr_hash_t *externals,
1176                              const char *from_url,
1177                              const char *to_abspath,
1178                              const char *repos_root_url,
1179                              svn_depth_t requested_depth,
1180                              const char *native_eol,
1181                              svn_boolean_t ignore_keywords,
1182                              svn_client_ctx_t *ctx,
1183                              apr_pool_t *scratch_pool)
1184 {
1185   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1186   apr_pool_t *sub_iterpool = svn_pool_create(scratch_pool);
1187   apr_hash_index_t *hi;
1188 
1189   SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath));
1190 
1191   for (hi = apr_hash_first(scratch_pool, externals);
1192        hi;
1193        hi = apr_hash_next(hi))
1194     {
1195       const char *local_abspath = apr_hash_this_key(hi);
1196       const char *desc_text = apr_hash_this_val(hi);
1197       const char *local_relpath;
1198       const char *dir_url;
1199       apr_array_header_t *items;
1200       int i;
1201 
1202       svn_pool_clear(iterpool);
1203 
1204       SVN_ERR(svn_wc_parse_externals_description3(&items, local_abspath,
1205                                                   desc_text, FALSE,
1206                                                   iterpool));
1207 
1208       if (! items->nelts)
1209         continue;
1210 
1211       local_relpath = svn_dirent_skip_ancestor(to_abspath, local_abspath);
1212 
1213       dir_url = svn_path_url_add_component2(from_url, local_relpath,
1214                                             scratch_pool);
1215 
1216       for (i = 0; i < items->nelts; i++)
1217         {
1218           const char *item_abspath;
1219           const char *new_url;
1220           svn_boolean_t under_root;
1221           svn_wc_external_item2_t *item = APR_ARRAY_IDX(items, i,
1222                                                 svn_wc_external_item2_t *);
1223 
1224           svn_pool_clear(sub_iterpool);
1225 
1226           SVN_ERR(svn_dirent_is_under_root(&under_root, &item_abspath,
1227                                            local_abspath, item->target_dir,
1228                                            sub_iterpool));
1229 
1230           if (! under_root)
1231             {
1232               return svn_error_createf(
1233                         SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1234                         _("Path '%s' is not in the working copy"),
1235                         svn_dirent_local_style(
1236                             svn_dirent_join(local_abspath, item->target_dir,
1237                                             sub_iterpool),
1238                             sub_iterpool));
1239             }
1240 
1241           SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, item,
1242                                                         repos_root_url,
1243                                                         dir_url, sub_iterpool,
1244                                                         sub_iterpool));
1245 
1246           /* The target dir might have multiple components.  Guarantee
1247              the path leading down to the last component. */
1248           SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(item_abspath,
1249                                                                  sub_iterpool),
1250                                               sub_iterpool));
1251 
1252           /* First notify that we're about to handle an external. */
1253           if (ctx->notify_func2)
1254             {
1255               ctx->notify_func2(
1256                        ctx->notify_baton2,
1257                        svn_wc_create_notify(item_abspath,
1258                                             svn_wc_notify_update_external,
1259                                             sub_iterpool),
1260                        sub_iterpool);
1261             }
1262 
1263           SVN_ERR(wrap_external_error(
1264                           ctx, item_abspath,
1265                           svn_client_export5(NULL, new_url, item_abspath,
1266                                              &item->peg_revision,
1267                                              &item->revision,
1268                                              TRUE, FALSE, ignore_keywords,
1269                                              svn_depth_infinity,
1270                                              native_eol,
1271                                              ctx, sub_iterpool),
1272                           sub_iterpool));
1273         }
1274     }
1275 
1276   svn_pool_destroy(sub_iterpool);
1277   svn_pool_destroy(iterpool);
1278 
1279   return SVN_NO_ERROR;
1280 }
1281 
1282