]> CyberLeo.Net >> Repos - FreeBSD/releng/10.3.git/blob - contrib/subversion/subversion/libsvn_client/externals.c
- Copy stable/10@296371 to releng/10.3 in preparation for 10.3-RC1
[FreeBSD/releng/10.3.git] / contrib / subversion / subversion / libsvn_client / externals.c
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 *
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_rename(local_abspath, new_path, 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 *
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_client_ctx_t *ctx,
155                     apr_pool_t *pool)
156 {
157   svn_node_kind_t kind;
158   svn_error_t *err;
159   svn_revnum_t external_peg_rev = SVN_INVALID_REVNUM;
160   svn_revnum_t external_rev = SVN_INVALID_REVNUM;
161   apr_pool_t *subpool = svn_pool_create(pool);
162   const char *repos_root_url;
163   const char *repos_uuid;
164
165   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
166
167   if (peg_revision->kind == svn_opt_revision_number)
168     external_peg_rev = peg_revision->value.number;
169
170   if (revision->kind == svn_opt_revision_number)
171     external_rev = revision->value.number;
172
173   /* 
174    * The code below assumes existing versioned paths are *not* part of
175    * the external's defining working copy.
176    * The working copy library does not support registering externals
177    * on top of existing BASE nodes and will error out if we try.
178    * So if the external target is part of the defining working copy's
179    * BASE tree, don't attempt to create the external. Doing so would
180    * leave behind a switched path instead of an external (since the
181    * switch succeeds but registration of the external in the DB fails).
182    * The working copy then cannot be updated until the path is switched back.
183    * See issue #4085.
184    */
185   SVN_ERR(svn_wc__node_get_base(&kind, NULL, NULL,
186                                 &repos_root_url, &repos_uuid,
187                                 NULL, ctx->wc_ctx, local_abspath,
188                                 TRUE, /* ignore_enoent */
189                                 TRUE, /* show hidden */
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           /* If we have what appears to be a version controlled
235              subdir, and its top-level URL matches that of our
236              externals definition, perform an update. */
237           if (strcmp(node_url, url) == 0)
238             {
239               SVN_ERR(svn_client__update_internal(NULL, local_abspath,
240                                                   revision, svn_depth_unknown,
241                                                   FALSE, FALSE, FALSE, TRUE,
242                                                   FALSE, TRUE,
243                                                   timestamp_sleep,
244                                                   ctx, subpool));
245
246               /* We just decided that this existing directory is an external,
247                  so update the external registry with this information, like
248                  when checking out an external */
249               SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
250                                     defining_abspath,
251                                     local_abspath, svn_node_dir,
252                                     repos_root_url, repos_uuid,
253                                     svn_uri_skip_ancestor(repos_root_url,
254                                                           url, pool),
255                                     external_peg_rev,
256                                     external_rev,
257                                     pool));
258
259               svn_pool_destroy(subpool);
260               goto cleanup;
261             }
262
263           /* We'd really prefer not to have to do a brute-force
264              relegation -- blowing away the current external working
265              copy and checking it out anew -- so we'll first see if we
266              can get away with a generally cheaper relocation (if
267              required) and switch-style update.
268
269              To do so, we need to know the repository root URL of the
270              external working copy as it currently sits. */
271           err = svn_wc__node_get_repos_info(NULL, NULL,
272                                             &repos_root_url, &repos_uuid,
273                                             ctx->wc_ctx, local_abspath,
274                                             pool, subpool);
275           if (err)
276             {
277               if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
278                   && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
279                 return svn_error_trace(err);
280
281               svn_error_clear(err);
282               repos_root_url = NULL;
283               repos_uuid = NULL;
284             }
285
286           if (repos_root_url)
287             {
288               /* If the new external target URL is not obviously a
289                  child of the external working copy's current
290                  repository root URL... */
291               if (! svn_uri__is_ancestor(repos_root_url, url))
292                 {
293                   const char *repos_root;
294
295                   /* ... then figure out precisely which repository
296                       root URL that target URL *is* a child of ... */
297                   SVN_ERR(svn_client_get_repos_root(&repos_root, NULL, url,
298                                                     ctx, subpool, subpool));
299
300                   /* ... and use that to try to relocate the external
301                      working copy to the target location.  */
302                   err = svn_client_relocate2(local_abspath, repos_root_url,
303                                              repos_root, FALSE, ctx, subpool);
304
305                   /* If the relocation failed because the new URL
306                      points to a totally different repository, we've
307                      no choice but to relegate and check out a new WC. */
308                   if (err
309                       && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION
310                           || (err->apr_err
311                               == SVN_ERR_CLIENT_INVALID_RELOCATION)))
312                     {
313                       svn_error_clear(err);
314                       goto relegate;
315                     }
316                   else if (err)
317                     return svn_error_trace(err);
318
319                   /* If the relocation went without a hitch, we should
320                      have a new repository root URL. */
321                   repos_root_url = repos_root;
322                 }
323
324               SVN_ERR(svn_client__switch_internal(NULL, local_abspath, url,
325                                                   peg_revision, revision,
326                                                   svn_depth_infinity,
327                                                   TRUE, FALSE, FALSE,
328                                                   TRUE /* ignore_ancestry */,
329                                                   timestamp_sleep,
330                                                   ctx, subpool));
331
332               SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
333                                                 defining_abspath,
334                                                 local_abspath, svn_node_dir,
335                                                 repos_root_url, repos_uuid,
336                                                 svn_uri_skip_ancestor(
337                                                             repos_root_url,
338                                                             url, subpool),
339                                                 external_peg_rev,
340                                                 external_rev,
341                                                 subpool));
342
343               svn_pool_destroy(subpool);
344               goto cleanup;
345             }
346         }
347     }
348
349  relegate:
350
351   /* Fall back on removing the WC and checking out a new one. */
352
353   /* Ensure that we don't have any RA sessions or WC locks from failed
354      operations above. */
355   svn_pool_destroy(subpool);
356
357   if (kind == svn_node_dir)
358     {
359       /* Buh-bye, old and busted ... */
360       SVN_ERR(relegate_dir_external(ctx->wc_ctx, defining_abspath,
361                                     local_abspath,
362                                     ctx->cancel_func, ctx->cancel_baton,
363                                     ctx->notify_func2, ctx->notify_baton2,
364                                     pool));
365     }
366   else
367     {
368       /* The target dir might have multiple components.  Guarantee
369          the path leading down to the last component. */
370       const char *parent = svn_dirent_dirname(local_abspath, pool);
371       SVN_ERR(svn_io_make_dir_recursively(parent, pool));
372     }
373
374   /* ... Hello, new hotness. */
375   SVN_ERR(svn_client__checkout_internal(NULL, url, local_abspath, peg_revision,
376                                         revision, svn_depth_infinity,
377                                         FALSE, FALSE, timestamp_sleep,
378                                         ctx, pool));
379
380   SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL,
381                                       &repos_root_url,
382                                       &repos_uuid,
383                                       ctx->wc_ctx, local_abspath,
384                                       pool, pool));
385
386   SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
387                                     defining_abspath,
388                                     local_abspath, svn_node_dir,
389                                     repos_root_url, repos_uuid,
390                                     svn_uri_skip_ancestor(repos_root_url,
391                                                           url, pool),
392                                     external_peg_rev,
393                                     external_rev,
394                                     pool));
395
396  cleanup:
397   /* Issues #4123 and #4130: We don't need to keep the newly checked
398      out external's DB open. */
399   SVN_ERR(svn_wc__close_db(local_abspath, ctx->wc_ctx, pool));
400
401   return SVN_NO_ERROR;
402 }
403
404 /* Try to update a file external at LOCAL_ABSPATH to URL at REVISION using a
405    access baton that has a write lock.  Use SCRATCH_POOL for temporary
406    allocations, and use the client context CTX. */
407 static svn_error_t *
408 switch_file_external(const char *local_abspath,
409                      const char *url,
410                      const svn_opt_revision_t *peg_revision,
411                      const svn_opt_revision_t *revision,
412                      const char *def_dir_abspath,
413                      svn_ra_session_t *ra_session,
414                      svn_client_ctx_t *ctx,
415                      apr_pool_t *scratch_pool)
416 {
417   svn_config_t *cfg = ctx->config
418                       ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
419                       : NULL;
420   svn_boolean_t use_commit_times;
421   const char *diff3_cmd;
422   const char *preserved_exts_str;
423   const apr_array_header_t *preserved_exts;
424   svn_node_kind_t kind, external_kind;
425
426   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
427
428   /* See if the user wants last-commit timestamps instead of current ones. */
429   SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
430                               SVN_CONFIG_SECTION_MISCELLANY,
431                               SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
432
433   /* Get the external diff3, if any. */
434   svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
435                  SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
436
437   if (diff3_cmd != NULL)
438     SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool));
439
440   /* See which files the user wants to preserve the extension of when
441      conflict files are made. */
442   svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
443                  SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
444   preserved_exts = *preserved_exts_str
445     ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool)
446     : NULL;
447
448   {
449     const char *wcroot_abspath;
450
451     SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath,
452                                scratch_pool, scratch_pool));
453
454     /* File externals can only be installed inside the current working copy.
455        So verify if the working copy that contains/will contain the target
456        is the defining abspath, or one of its ancestors */
457
458     if (!svn_dirent_is_ancestor(wcroot_abspath, def_dir_abspath))
459         return svn_error_createf(
460                         SVN_ERR_WC_BAD_PATH, NULL,
461                         _("Cannot insert a file external defined on '%s' "
462                           "into the working copy '%s'."),
463                         svn_dirent_local_style(def_dir_abspath,
464                                                scratch_pool),
465                         svn_dirent_local_style(wcroot_abspath,
466                                                scratch_pool));
467   }
468
469   SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath,
470                             TRUE, FALSE, scratch_pool));
471
472   SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
473                                      ctx->wc_ctx, local_abspath, local_abspath,
474                                      TRUE, scratch_pool, scratch_pool));
475
476   /* If there is a versioned item with this name, ensure it's a file
477      external before working with it.  If there is no entry in the
478      working copy, then create an empty file and add it to the working
479      copy. */
480   if (kind != svn_node_none && kind != svn_node_unknown)
481     {
482       if (external_kind != svn_node_file)
483         {
484           return svn_error_createf(
485               SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0,
486              _("The file external from '%s' cannot overwrite the existing "
487                "versioned item at '%s'"),
488              url, svn_dirent_local_style(local_abspath, scratch_pool));
489         }
490     }
491   else
492     {
493       svn_node_kind_t disk_kind;
494
495       SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
496
497       if (kind == svn_node_file || kind == svn_node_dir)
498         return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL,
499                                  _("The file external '%s' can not be "
500                                    "created because the node exists."),
501                                  svn_dirent_local_style(local_abspath,
502                                                         scratch_pool));
503     }
504
505   {
506     const svn_ra_reporter3_t *reporter;
507     void *report_baton;
508     const svn_delta_editor_t *switch_editor;
509     void *switch_baton;
510     svn_client__pathrev_t *switch_loc;
511     svn_revnum_t revnum;
512     apr_array_header_t *inherited_props;
513     const char *dir_abspath;
514     const char *target;
515
516     svn_dirent_split(&dir_abspath, &target, local_abspath, scratch_pool);
517
518     /* ### Why do we open a new session?  RA_SESSION is a valid
519        ### session -- the caller used it to call svn_ra_check_path on
520        ### this very URL, the caller also did the resolving and
521        ### reparenting that is repeated here. */
522     SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc,
523                                               url, dir_abspath,
524                                               peg_revision, revision,
525                                               ctx, scratch_pool));
526     /* Get the external file's iprops. */
527     SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "",
528                                        switch_loc->rev,
529                                        scratch_pool, scratch_pool));
530
531     SVN_ERR(svn_ra_reparent(ra_session, svn_uri_dirname(url, scratch_pool),
532                             scratch_pool));
533
534     SVN_ERR(svn_wc__get_file_external_editor(&switch_editor, &switch_baton,
535                                              &revnum, ctx->wc_ctx,
536                                              local_abspath,
537                                              def_dir_abspath,
538                                              switch_loc->url,
539                                              switch_loc->repos_root_url,
540                                              switch_loc->repos_uuid,
541                                              inherited_props,
542                                              use_commit_times,
543                                              diff3_cmd, preserved_exts,
544                                              def_dir_abspath,
545                                              url, peg_revision, revision,
546                                              ctx->conflict_func2,
547                                              ctx->conflict_baton2,
548                                              ctx->cancel_func,
549                                              ctx->cancel_baton,
550                                              ctx->notify_func2,
551                                              ctx->notify_baton2,
552                                              scratch_pool, scratch_pool));
553
554     /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
555      invalid revnum, that means RA will use the latest revision. */
556     SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton,
557                               switch_loc->rev,
558                               target, svn_depth_unknown, switch_loc->url,
559                               FALSE /* send_copyfrom */,
560                               TRUE /* ignore_ancestry */,
561                               switch_editor, switch_baton,
562                               scratch_pool, scratch_pool));
563
564     SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath,
565                                         reporter, report_baton,
566                                         TRUE,  use_commit_times,
567                                         ctx->cancel_func, ctx->cancel_baton,
568                                         ctx->notify_func2, ctx->notify_baton2,
569                                         scratch_pool));
570
571     if (ctx->notify_func2)
572       {
573         svn_wc_notify_t *notify
574           = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
575                                  scratch_pool);
576         notify->kind = svn_node_none;
577         notify->content_state = notify->prop_state
578           = svn_wc_notify_state_inapplicable;
579         notify->lock_state = svn_wc_notify_lock_state_inapplicable;
580         notify->revision = revnum;
581         (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
582       }
583   }
584
585   return SVN_NO_ERROR;
586 }
587
588 /* Wrappers around svn_wc__external_remove, obtaining and releasing a lock for
589    directory externals */
590 static svn_error_t *
591 remove_external2(svn_boolean_t *removed,
592                 svn_wc_context_t *wc_ctx,
593                 const char *wri_abspath,
594                 const char *local_abspath,
595                 svn_node_kind_t external_kind,
596                 svn_cancel_func_t cancel_func,
597                 void *cancel_baton,
598                 apr_pool_t *scratch_pool)
599 {
600   SVN_ERR(svn_wc__external_remove(wc_ctx, wri_abspath,
601                                   local_abspath,
602                                   (external_kind == svn_node_none),
603                                   cancel_func, cancel_baton,
604                                   scratch_pool));
605
606   *removed = TRUE;
607   return SVN_NO_ERROR;
608 }
609
610
611 static svn_error_t *
612 remove_external(svn_boolean_t *removed,
613                 svn_wc_context_t *wc_ctx,
614                 const char *wri_abspath,
615                 const char *local_abspath,
616                 svn_node_kind_t external_kind,
617                 svn_cancel_func_t cancel_func,
618                 void *cancel_baton,
619                 apr_pool_t *scratch_pool)
620 {
621   *removed = FALSE;
622   switch (external_kind)
623     {
624       case svn_node_dir:
625         SVN_WC__CALL_WITH_WRITE_LOCK(
626             remove_external2(removed,
627                              wc_ctx, wri_abspath,
628                              local_abspath, external_kind,
629                              cancel_func, cancel_baton,
630                              scratch_pool),
631             wc_ctx, local_abspath, FALSE, scratch_pool);
632         break;
633       case svn_node_file:
634       default:
635         SVN_ERR(remove_external2(removed,
636                                  wc_ctx, wri_abspath,
637                                  local_abspath, external_kind,
638                                  cancel_func, cancel_baton,
639                                  scratch_pool));
640         break;
641     }
642
643   return SVN_NO_ERROR;
644 }
645
646 /* Called when an external that is in the EXTERNALS table is no longer
647    referenced from an svn:externals property */
648 static svn_error_t *
649 handle_external_item_removal(const svn_client_ctx_t *ctx,
650                              const char *defining_abspath,
651                              const char *local_abspath,
652                              apr_pool_t *scratch_pool)
653 {
654   svn_error_t *err;
655   svn_node_kind_t external_kind;
656   svn_node_kind_t kind;
657   svn_boolean_t removed = FALSE;
658
659   /* local_abspath should be a wcroot or a file external */
660   SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
661                                      ctx->wc_ctx, defining_abspath,
662                                      local_abspath, FALSE,
663                                      scratch_pool, scratch_pool));
664
665   SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE,
666                            scratch_pool));
667
668   if (external_kind != kind)
669     external_kind = svn_node_none; /* Only remove the registration */
670
671   err = remove_external(&removed,
672                         ctx->wc_ctx, defining_abspath, local_abspath,
673                         external_kind,
674                         ctx->cancel_func, ctx->cancel_baton,
675                         scratch_pool);
676
677   if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED && removed)
678     {
679       svn_error_clear(err);
680       err = NULL; /* We removed the working copy, so we can't release the
681                      lock that was stored inside */
682     }
683
684   if (ctx->notify_func2)
685     {
686       svn_wc_notify_t *notify =
687           svn_wc_create_notify(local_abspath,
688                                svn_wc_notify_update_external_removed,
689                                scratch_pool);
690
691       notify->kind = kind;
692       notify->err = err;
693
694       (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
695
696       if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
697         {
698           notify = svn_wc_create_notify(local_abspath,
699                                       svn_wc_notify_left_local_modifications,
700                                       scratch_pool);
701           notify->kind = svn_node_dir;
702           notify->err = err;
703
704           (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool);
705         }
706     }
707
708   if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
709     {
710       svn_error_clear(err);
711       err = NULL;
712     }
713
714   return svn_error_trace(err);
715 }
716
717 static svn_error_t *
718 handle_external_item_change(svn_client_ctx_t *ctx,
719                             const char *repos_root_url,
720                             const char *parent_dir_abspath,
721                             const char *parent_dir_url,
722                             const char *local_abspath,
723                             const char *old_defining_abspath,
724                             const svn_wc_external_item2_t *new_item,
725                             svn_boolean_t *timestamp_sleep,
726                             apr_pool_t *scratch_pool)
727 {
728   svn_ra_session_t *ra_session;
729   svn_client__pathrev_t *new_loc;
730   const char *new_url;
731   svn_node_kind_t ext_kind;
732
733   SVN_ERR_ASSERT(repos_root_url && parent_dir_url);
734   SVN_ERR_ASSERT(new_item != NULL);
735
736   /* Don't bother to check status, since we'll get that for free by
737      attempting to retrieve the hash values anyway.  */
738
739   /* When creating the absolute URL, use the pool and not the
740      iterpool, since the hash table values outlive the iterpool and
741      any pointers they have should also outlive the iterpool.  */
742
743   SVN_ERR(svn_wc__resolve_relative_external_url(&new_url,
744                                                 new_item, repos_root_url,
745                                                 parent_dir_url,
746                                                 scratch_pool, scratch_pool));
747
748   /* Determine if the external is a file or directory. */
749   /* Get the RA connection. */
750   SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
751                                             new_url, NULL,
752                                             &(new_item->peg_revision),
753                                             &(new_item->revision), ctx,
754                                             scratch_pool));
755
756   SVN_ERR(svn_ra_check_path(ra_session, "", new_loc->rev, &ext_kind,
757                             scratch_pool));
758
759   if (svn_node_none == ext_kind)
760     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
761                              _("URL '%s' at revision %ld doesn't exist"),
762                              new_loc->url, new_loc->rev);
763
764   if (svn_node_dir != ext_kind && svn_node_file != ext_kind)
765     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
766                              _("URL '%s' at revision %ld is not a file "
767                                "or a directory"),
768                              new_loc->url, new_loc->rev);
769
770
771   /* Not protecting against recursive externals.  Detecting them in
772      the global case is hard, and it should be pretty obvious to a
773      user when it happens.  Worst case: your disk fills up :-). */
774
775   /* First notify that we're about to handle an external. */
776   if (ctx->notify_func2)
777     {
778       (*ctx->notify_func2)(
779          ctx->notify_baton2,
780          svn_wc_create_notify(local_abspath,
781                               svn_wc_notify_update_external,
782                               scratch_pool),
783          scratch_pool);
784     }
785
786   if (! old_defining_abspath)
787     {
788       /* The target dir might have multiple components.  Guarantee the path
789          leading down to the last component. */
790       SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath,
791                                                              scratch_pool),
792                                           scratch_pool));
793     }
794
795   switch (ext_kind)
796     {
797       case svn_node_dir:
798         SVN_ERR(switch_dir_external(local_abspath, new_loc->url,
799                                     new_item->url,
800                                     &(new_item->peg_revision),
801                                     &(new_item->revision),
802                                     parent_dir_abspath,
803                                     timestamp_sleep, ctx,
804                                     scratch_pool));
805         break;
806       case svn_node_file:
807         if (strcmp(repos_root_url, new_loc->repos_root_url))
808           {
809             const char *local_repos_root_url;
810             const char *local_repos_uuid;
811             const char *ext_repos_relpath;
812             svn_error_t *err;
813
814             /*
815              * The working copy library currently requires that all files
816              * in the working copy have the same repository root URL.
817              * The URL from the file external's definition differs from the
818              * one used by the working copy. As a workaround, replace the
819              * root URL portion of the file external's URL, after making
820              * sure both URLs point to the same repository. See issue #4087.
821              */
822
823             err = svn_wc__node_get_repos_info(NULL, NULL,
824                                               &local_repos_root_url,
825                                               &local_repos_uuid,
826                                               ctx->wc_ctx, parent_dir_abspath,
827                                               scratch_pool, scratch_pool);
828             if (err)
829               {
830                 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
831                     && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
832                   return svn_error_trace(err);
833
834                 svn_error_clear(err);
835                 local_repos_root_url = NULL;
836                 local_repos_uuid = NULL;
837               }
838
839             ext_repos_relpath = svn_uri_skip_ancestor(new_loc->repos_root_url,
840                                                       new_url, scratch_pool);
841             if (local_repos_uuid == NULL || local_repos_root_url == NULL ||
842                 ext_repos_relpath == NULL ||
843                 strcmp(local_repos_uuid, new_loc->repos_uuid) != 0)
844               return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
845                         _("Unsupported external: URL of file external '%s' "
846                           "is not in repository '%s'"),
847                         new_url, repos_root_url);
848
849             new_url = svn_path_url_add_component2(local_repos_root_url,
850                                                   ext_repos_relpath,
851                                                   scratch_pool);
852             SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
853                                                       new_url,
854                                                       NULL,
855                                                       &(new_item->peg_revision),
856                                                       &(new_item->revision),
857                                                       ctx, scratch_pool));
858           }
859
860         SVN_ERR(switch_file_external(local_abspath,
861                                      new_url,
862                                      &new_item->peg_revision,
863                                      &new_item->revision,
864                                      parent_dir_abspath,
865                                      ra_session,
866                                      ctx,
867                                      scratch_pool));
868         break;
869
870       default:
871         SVN_ERR_MALFUNCTION();
872         break;
873     }
874
875   return SVN_NO_ERROR;
876 }
877
878 static svn_error_t *
879 wrap_external_error(const svn_client_ctx_t *ctx,
880                     const char *target_abspath,
881                     svn_error_t *err,
882                     apr_pool_t *scratch_pool)
883 {
884   if (err && err->apr_err != SVN_ERR_CANCELLED)
885     {
886       if (ctx->notify_func2)
887         {
888           svn_wc_notify_t *notifier = svn_wc_create_notify(
889                                             target_abspath,
890                                             svn_wc_notify_failed_external,
891                                             scratch_pool);
892           notifier->err = err;
893           ctx->notify_func2(ctx->notify_baton2, notifier, scratch_pool);
894         }
895       svn_error_clear(err);
896       return SVN_NO_ERROR;
897     }
898
899   return err;
900 }
901
902 static svn_error_t *
903 handle_externals_change(svn_client_ctx_t *ctx,
904                         const char *repos_root_url,
905                         svn_boolean_t *timestamp_sleep,
906                         const char *local_abspath,
907                         const char *new_desc_text,
908                         apr_hash_t *old_externals,
909                         svn_depth_t ambient_depth,
910                         svn_depth_t requested_depth,
911                         apr_pool_t *scratch_pool)
912 {
913   apr_array_header_t *new_desc;
914   int i;
915   apr_pool_t *iterpool;
916   const char *url;
917
918   iterpool = svn_pool_create(scratch_pool);
919
920   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
921
922   /* Bag out if the depth here is too shallow for externals action. */
923   if ((requested_depth < svn_depth_infinity
924        && requested_depth != svn_depth_unknown)
925       || (ambient_depth < svn_depth_infinity
926           && requested_depth < svn_depth_infinity))
927     return SVN_NO_ERROR;
928
929   if (new_desc_text)
930     SVN_ERR(svn_wc_parse_externals_description3(&new_desc, local_abspath,
931                                                 new_desc_text,
932                                                 FALSE, scratch_pool));
933   else
934     new_desc = NULL;
935
936   SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, local_abspath,
937                                scratch_pool, iterpool));
938
939   SVN_ERR_ASSERT(url);
940
941   for (i = 0; new_desc && (i < new_desc->nelts); i++)
942     {
943       const char *old_defining_abspath;
944       svn_wc_external_item2_t *new_item;
945       const char *target_abspath;
946       svn_boolean_t under_root;
947
948       new_item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *);
949
950       svn_pool_clear(iterpool);
951
952       if (ctx->cancel_func)
953         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
954
955       SVN_ERR(svn_dirent_is_under_root(&under_root, &target_abspath,
956                                        local_abspath, new_item->target_dir,
957                                        iterpool));
958
959       if (! under_root)
960         {
961           return svn_error_createf(
962                     SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
963                     _("Path '%s' is not in the working copy"),
964                     svn_dirent_local_style(
965                         svn_dirent_join(local_abspath, new_item->target_dir,
966                                         iterpool),
967                         iterpool));
968         }
969
970       old_defining_abspath = svn_hash_gets(old_externals, target_abspath);
971
972       SVN_ERR(wrap_external_error(
973                       ctx, target_abspath,
974                       handle_external_item_change(ctx,
975                                                   repos_root_url,
976                                                   local_abspath, url,
977                                                   target_abspath,
978                                                   old_defining_abspath,
979                                                   new_item,
980                                                   timestamp_sleep,
981                                                   iterpool),
982                       iterpool));
983
984       /* And remove already processed items from the to-remove hash */
985       if (old_defining_abspath)
986         svn_hash_sets(old_externals, target_abspath, NULL);
987     }
988
989   svn_pool_destroy(iterpool);
990
991   return SVN_NO_ERROR;
992 }
993
994
995 svn_error_t *
996 svn_client__handle_externals(apr_hash_t *externals_new,
997                              apr_hash_t *ambient_depths,
998                              const char *repos_root_url,
999                              const char *target_abspath,
1000                              svn_depth_t requested_depth,
1001                              svn_boolean_t *timestamp_sleep,
1002                              svn_client_ctx_t *ctx,
1003                              apr_pool_t *scratch_pool)
1004 {
1005   apr_hash_t *old_external_defs;
1006   apr_hash_index_t *hi;
1007   apr_pool_t *iterpool;
1008
1009   SVN_ERR_ASSERT(repos_root_url);
1010
1011   iterpool = svn_pool_create(scratch_pool);
1012
1013   SVN_ERR(svn_wc__externals_defined_below(&old_external_defs,
1014                                           ctx->wc_ctx, target_abspath,
1015                                           scratch_pool, iterpool));
1016
1017   for (hi = apr_hash_first(scratch_pool, externals_new);
1018        hi;
1019        hi = apr_hash_next(hi))
1020     {
1021       const char *local_abspath = svn__apr_hash_index_key(hi);
1022       const char *desc_text = svn__apr_hash_index_val(hi);
1023       svn_depth_t ambient_depth = svn_depth_infinity;
1024
1025       svn_pool_clear(iterpool);
1026
1027       if (ambient_depths)
1028         {
1029           const char *ambient_depth_w;
1030
1031           ambient_depth_w = apr_hash_get(ambient_depths, local_abspath,
1032                                          svn__apr_hash_index_klen(hi));
1033
1034           if (ambient_depth_w == NULL)
1035             {
1036               return svn_error_createf(
1037                         SVN_ERR_WC_CORRUPT, NULL,
1038                         _("Traversal of '%s' found no ambient depth"),
1039                         svn_dirent_local_style(local_abspath, scratch_pool));
1040             }
1041           else
1042             {
1043               ambient_depth = svn_depth_from_word(ambient_depth_w);
1044             }
1045         }
1046
1047       SVN_ERR(handle_externals_change(ctx, repos_root_url, timestamp_sleep,
1048                                       local_abspath,
1049                                       desc_text, old_external_defs,
1050                                       ambient_depth, requested_depth,
1051                                       iterpool));
1052     }
1053
1054   /* Remove the remaining externals */
1055   for (hi = apr_hash_first(scratch_pool, old_external_defs);
1056        hi;
1057        hi = apr_hash_next(hi))
1058     {
1059       const char *item_abspath = svn__apr_hash_index_key(hi);
1060       const char *defining_abspath = svn__apr_hash_index_val(hi);
1061       const char *parent_abspath;
1062
1063       svn_pool_clear(iterpool);
1064
1065       SVN_ERR(wrap_external_error(
1066                           ctx, item_abspath,
1067                           handle_external_item_removal(ctx, defining_abspath,
1068                                                        item_abspath, iterpool),
1069                           iterpool));
1070
1071       /* Are there any unversioned directories between the removed
1072        * external and the DEFINING_ABSPATH which we can remove? */
1073       parent_abspath = item_abspath;
1074       do {
1075         svn_node_kind_t kind;
1076
1077         parent_abspath = svn_dirent_dirname(parent_abspath, iterpool);
1078         SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, parent_abspath,
1079                                   FALSE /* show_deleted*/,
1080                                   FALSE /* show_hidden */,
1081                                   iterpool));
1082         if (kind == svn_node_none)
1083           {
1084             svn_error_t *err;
1085
1086             err = svn_io_dir_remove_nonrecursive(parent_abspath, iterpool);
1087             if (err)
1088               {
1089                 if (APR_STATUS_IS_ENOTEMPTY(err->apr_err))
1090                   {
1091                     svn_error_clear(err);
1092                     break; /* No parents to delete */
1093                   }
1094                 else if (APR_STATUS_IS_ENOENT(err->apr_err)
1095                          || APR_STATUS_IS_ENOTDIR(err->apr_err))
1096                   {
1097                     svn_error_clear(err);
1098                     /* Fall through; parent dir might be unversioned */
1099                   }
1100                 else
1101                   return svn_error_trace(err);
1102               }
1103           }
1104       } while (strcmp(parent_abspath, defining_abspath) != 0);
1105     }
1106
1107
1108   svn_pool_destroy(iterpool);
1109   return SVN_NO_ERROR;
1110 }
1111
1112
1113 svn_error_t *
1114 svn_client__export_externals(apr_hash_t *externals,
1115                              const char *from_url,
1116                              const char *to_abspath,
1117                              const char *repos_root_url,
1118                              svn_depth_t requested_depth,
1119                              const char *native_eol,
1120                              svn_boolean_t ignore_keywords,
1121                              svn_client_ctx_t *ctx,
1122                              apr_pool_t *scratch_pool)
1123 {
1124   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1125   apr_pool_t *sub_iterpool = svn_pool_create(scratch_pool);
1126   apr_hash_index_t *hi;
1127
1128   SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath));
1129
1130   for (hi = apr_hash_first(scratch_pool, externals);
1131        hi;
1132        hi = apr_hash_next(hi))
1133     {
1134       const char *local_abspath = svn__apr_hash_index_key(hi);
1135       const char *desc_text = svn__apr_hash_index_val(hi);
1136       const char *local_relpath;
1137       const char *dir_url;
1138       apr_array_header_t *items;
1139       int i;
1140
1141       svn_pool_clear(iterpool);
1142
1143       SVN_ERR(svn_wc_parse_externals_description3(&items, local_abspath,
1144                                                   desc_text, FALSE,
1145                                                   iterpool));
1146
1147       if (! items->nelts)
1148         continue;
1149
1150       local_relpath = svn_dirent_skip_ancestor(to_abspath, local_abspath);
1151
1152       dir_url = svn_path_url_add_component2(from_url, local_relpath,
1153                                             scratch_pool);
1154
1155       for (i = 0; i < items->nelts; i++)
1156         {
1157           const char *item_abspath;
1158           const char *new_url;
1159           svn_boolean_t under_root;
1160           svn_wc_external_item2_t *item = APR_ARRAY_IDX(items, i,
1161                                                 svn_wc_external_item2_t *);
1162
1163           svn_pool_clear(sub_iterpool);
1164
1165           SVN_ERR(svn_dirent_is_under_root(&under_root, &item_abspath,
1166                                            local_abspath, item->target_dir,
1167                                            sub_iterpool));
1168
1169           if (! under_root)
1170             {
1171               return svn_error_createf(
1172                         SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1173                         _("Path '%s' is not in the working copy"),
1174                         svn_dirent_local_style(
1175                             svn_dirent_join(local_abspath, item->target_dir,
1176                                             sub_iterpool),
1177                             sub_iterpool));
1178             }
1179
1180           SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, item,
1181                                                         repos_root_url,
1182                                                         dir_url, sub_iterpool,
1183                                                         sub_iterpool));
1184
1185           /* The target dir might have multiple components.  Guarantee
1186              the path leading down to the last component. */
1187           SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(item_abspath,
1188                                                                  sub_iterpool),
1189                                               sub_iterpool));
1190
1191           SVN_ERR(wrap_external_error(
1192                           ctx, item_abspath,
1193                           svn_client_export5(NULL, new_url, item_abspath,
1194                                              &item->peg_revision,
1195                                              &item->revision,
1196                                              TRUE, FALSE, ignore_keywords,
1197                                              svn_depth_infinity,
1198                                              native_eol,
1199                                              ctx, sub_iterpool),
1200                           sub_iterpool));
1201         }
1202     }
1203
1204   svn_pool_destroy(sub_iterpool);
1205   svn_pool_destroy(iterpool);
1206
1207   return SVN_NO_ERROR;
1208 }
1209