]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/externals.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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_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 *
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 (kind == svn_node_file || 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->cancel_func,
578                                              ctx->cancel_baton,
579                                              ctx->notify_func2,
580                                              ctx->notify_baton2,
581                                              scratch_pool, scratch_pool));
582
583     /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
584      invalid revnum, that means RA will use the latest revision. */
585     SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton,
586                               switch_loc->rev,
587                               target, svn_depth_unknown, switch_loc->url,
588                               FALSE /* send_copyfrom */,
589                               TRUE /* ignore_ancestry */,
590                               switch_editor, switch_baton,
591                               scratch_pool, scratch_pool));
592
593     SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath,
594                                         reporter, report_baton,
595                                         TRUE,  use_commit_times,
596                                         ctx->cancel_func, ctx->cancel_baton,
597                                         ctx->notify_func2, ctx->notify_baton2,
598                                         scratch_pool));
599
600     if (ctx->notify_func2)
601       {
602         svn_wc_notify_t *notify
603           = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
604                                  scratch_pool);
605         notify->kind = svn_node_none;
606         notify->content_state = notify->prop_state
607           = svn_wc_notify_state_inapplicable;
608         notify->lock_state = svn_wc_notify_lock_state_inapplicable;
609         notify->revision = revnum;
610         ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
611       }
612   }
613
614   return SVN_NO_ERROR;
615 }
616
617 /* Wrappers around svn_wc__external_remove, obtaining and releasing a lock for
618    directory externals */
619 static svn_error_t *
620 remove_external2(svn_boolean_t *removed,
621                 svn_wc_context_t *wc_ctx,
622                 const char *wri_abspath,
623                 const char *local_abspath,
624                 svn_node_kind_t external_kind,
625                 svn_cancel_func_t cancel_func,
626                 void *cancel_baton,
627                 apr_pool_t *scratch_pool)
628 {
629   SVN_ERR(svn_wc__external_remove(wc_ctx, wri_abspath,
630                                   local_abspath,
631                                   (external_kind == svn_node_none),
632                                   cancel_func, cancel_baton,
633                                   scratch_pool));
634
635   *removed = TRUE;
636   return SVN_NO_ERROR;
637 }
638
639
640 static svn_error_t *
641 remove_external(svn_boolean_t *removed,
642                 svn_wc_context_t *wc_ctx,
643                 const char *wri_abspath,
644                 const char *local_abspath,
645                 svn_node_kind_t external_kind,
646                 svn_cancel_func_t cancel_func,
647                 void *cancel_baton,
648                 apr_pool_t *scratch_pool)
649 {
650   *removed = FALSE;
651   switch (external_kind)
652     {
653       case svn_node_dir:
654         SVN_WC__CALL_WITH_WRITE_LOCK(
655             remove_external2(removed,
656                              wc_ctx, wri_abspath,
657                              local_abspath, external_kind,
658                              cancel_func, cancel_baton,
659                              scratch_pool),
660             wc_ctx, local_abspath, FALSE, scratch_pool);
661         break;
662       case svn_node_file:
663       default:
664         SVN_ERR(remove_external2(removed,
665                                  wc_ctx, wri_abspath,
666                                  local_abspath, external_kind,
667                                  cancel_func, cancel_baton,
668                                  scratch_pool));
669         break;
670     }
671
672   return SVN_NO_ERROR;
673 }
674
675 /* Called when an external that is in the EXTERNALS table is no longer
676    referenced from an svn:externals property */
677 static svn_error_t *
678 handle_external_item_removal(const svn_client_ctx_t *ctx,
679                              const char *defining_abspath,
680                              const char *local_abspath,
681                              apr_pool_t *scratch_pool)
682 {
683   svn_error_t *err;
684   svn_node_kind_t external_kind;
685   svn_node_kind_t kind;
686   svn_boolean_t removed = FALSE;
687
688   /* local_abspath should be a wcroot or a file external */
689   SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
690                                      ctx->wc_ctx, defining_abspath,
691                                      local_abspath, FALSE,
692                                      scratch_pool, scratch_pool));
693
694   SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE,
695                            scratch_pool));
696
697   if (external_kind != kind)
698     external_kind = svn_node_none; /* Only remove the registration */
699
700   err = remove_external(&removed,
701                         ctx->wc_ctx, defining_abspath, local_abspath,
702                         external_kind,
703                         ctx->cancel_func, ctx->cancel_baton,
704                         scratch_pool);
705
706   if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED && removed)
707     {
708       svn_error_clear(err);
709       err = NULL; /* We removed the working copy, so we can't release the
710                      lock that was stored inside */
711     }
712
713   if (ctx->notify_func2)
714     {
715       svn_wc_notify_t *notify =
716           svn_wc_create_notify(local_abspath,
717                                svn_wc_notify_update_external_removed,
718                                scratch_pool);
719
720       notify->kind = kind;
721       notify->err = err;
722
723       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
724
725       if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
726         {
727           notify = svn_wc_create_notify(local_abspath,
728                                       svn_wc_notify_left_local_modifications,
729                                       scratch_pool);
730           notify->kind = svn_node_dir;
731           notify->err = err;
732
733           ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
734         }
735     }
736
737   if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
738     {
739       svn_error_clear(err);
740       err = NULL;
741     }
742
743   return svn_error_trace(err);
744 }
745
746 static svn_error_t *
747 handle_external_item_change(svn_client_ctx_t *ctx,
748                             const char *repos_root_url,
749                             const char *parent_dir_abspath,
750                             const char *parent_dir_url,
751                             const char *local_abspath,
752                             const char *old_defining_abspath,
753                             const svn_wc_external_item2_t *new_item,
754                             svn_ra_session_t *ra_session,
755                             svn_boolean_t *timestamp_sleep,
756                             apr_pool_t *scratch_pool)
757 {
758   svn_client__pathrev_t *new_loc;
759   const char *new_url;
760   svn_node_kind_t ext_kind;
761
762   SVN_ERR_ASSERT(repos_root_url && parent_dir_url);
763   SVN_ERR_ASSERT(new_item != NULL);
764
765   /* Don't bother to check status, since we'll get that for free by
766      attempting to retrieve the hash values anyway.  */
767
768   /* When creating the absolute URL, use the pool and not the
769      iterpool, since the hash table values outlive the iterpool and
770      any pointers they have should also outlive the iterpool.  */
771
772   SVN_ERR(svn_wc__resolve_relative_external_url(&new_url,
773                                                 new_item, repos_root_url,
774                                                 parent_dir_url,
775                                                 scratch_pool, scratch_pool));
776
777   /* Determine if the external is a file or directory. */
778   /* Get the RA connection, if needed. */
779   if (ra_session)
780     {
781       svn_error_t *err = svn_ra_reparent(ra_session, new_url, scratch_pool);
782
783       if (err)
784         {
785           if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
786             {
787               svn_error_clear(err);
788               ra_session = NULL;
789             }
790           else
791             return svn_error_trace(err);
792         }
793       else
794         {
795           SVN_ERR(svn_client__resolve_rev_and_url(&new_loc,
796                                                   ra_session, new_url,
797                                                   &(new_item->peg_revision),
798                                                   &(new_item->revision), ctx,
799                                                   scratch_pool));
800
801           SVN_ERR(svn_ra_reparent(ra_session, new_loc->url, scratch_pool));
802         }
803     }
804
805   if (!ra_session)
806     SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
807                                               new_url, NULL,
808                                               &(new_item->peg_revision),
809                                               &(new_item->revision), ctx,
810                                               scratch_pool));
811
812   SVN_ERR(svn_ra_check_path(ra_session, "", new_loc->rev, &ext_kind,
813                             scratch_pool));
814
815   if (svn_node_none == ext_kind)
816     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
817                              _("URL '%s' at revision %ld doesn't exist"),
818                              new_loc->url, new_loc->rev);
819
820   if (svn_node_dir != ext_kind && svn_node_file != ext_kind)
821     return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
822                              _("URL '%s' at revision %ld is not a file "
823                                "or a directory"),
824                              new_loc->url, new_loc->rev);
825
826
827   /* Not protecting against recursive externals.  Detecting them in
828      the global case is hard, and it should be pretty obvious to a
829      user when it happens.  Worst case: your disk fills up :-). */
830
831   /* First notify that we're about to handle an external. */
832   if (ctx->notify_func2)
833     {
834       ctx->notify_func2(
835          ctx->notify_baton2,
836          svn_wc_create_notify(local_abspath,
837                               svn_wc_notify_update_external,
838                               scratch_pool),
839          scratch_pool);
840     }
841
842   if (! old_defining_abspath)
843     {
844       /* The target dir might have multiple components.  Guarantee the path
845          leading down to the last component. */
846       SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath,
847                                                              scratch_pool),
848                                           scratch_pool));
849     }
850
851   switch (ext_kind)
852     {
853       case svn_node_dir:
854         SVN_ERR(switch_dir_external(local_abspath, new_loc->url,
855                                     new_item->url,
856                                     &(new_item->peg_revision),
857                                     &(new_item->revision),
858                                     parent_dir_abspath,
859                                     timestamp_sleep, ra_session, ctx,
860                                     scratch_pool));
861         break;
862       case svn_node_file:
863         if (strcmp(repos_root_url, new_loc->repos_root_url))
864           {
865             const char *local_repos_root_url;
866             const char *local_repos_uuid;
867             const char *ext_repos_relpath;
868             svn_error_t *err;
869
870             /*
871              * The working copy library currently requires that all files
872              * in the working copy have the same repository root URL.
873              * The URL from the file external's definition differs from the
874              * one used by the working copy. As a workaround, replace the
875              * root URL portion of the file external's URL, after making
876              * sure both URLs point to the same repository. See issue #4087.
877              */
878
879             err = svn_wc__node_get_repos_info(NULL, NULL,
880                                               &local_repos_root_url,
881                                               &local_repos_uuid,
882                                               ctx->wc_ctx, parent_dir_abspath,
883                                               scratch_pool, scratch_pool);
884             if (err)
885               {
886                 if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
887                     && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
888                   return svn_error_trace(err);
889
890                 svn_error_clear(err);
891                 local_repos_root_url = NULL;
892                 local_repos_uuid = NULL;
893               }
894
895             ext_repos_relpath = svn_uri_skip_ancestor(new_loc->repos_root_url,
896                                                       new_url, scratch_pool);
897             if (local_repos_uuid == NULL || local_repos_root_url == NULL ||
898                 ext_repos_relpath == NULL ||
899                 strcmp(local_repos_uuid, new_loc->repos_uuid) != 0)
900               return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
901                         _("Unsupported external: URL of file external '%s' "
902                           "is not in repository '%s'"),
903                         new_url, repos_root_url);
904
905             new_url = svn_path_url_add_component2(local_repos_root_url,
906                                                   ext_repos_relpath,
907                                                   scratch_pool);
908             SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
909                                                       new_url,
910                                                       NULL,
911                                                       &(new_item->peg_revision),
912                                                       &(new_item->revision),
913                                                       ctx, scratch_pool));
914           }
915
916         SVN_ERR(switch_file_external(local_abspath,
917                                      new_loc,
918                                      new_url,
919                                      &new_item->peg_revision,
920                                      &new_item->revision,
921                                      parent_dir_abspath,
922                                      ra_session,
923                                      ctx,
924                                      scratch_pool));
925         break;
926
927       default:
928         SVN_ERR_MALFUNCTION();
929         break;
930     }
931
932   return SVN_NO_ERROR;
933 }
934
935 static svn_error_t *
936 wrap_external_error(const svn_client_ctx_t *ctx,
937                     const char *target_abspath,
938                     svn_error_t *err,
939                     apr_pool_t *scratch_pool)
940 {
941   if (err && err->apr_err != SVN_ERR_CANCELLED)
942     {
943       if (ctx->notify_func2)
944         {
945           svn_wc_notify_t *notifier = svn_wc_create_notify(
946                                             target_abspath,
947                                             svn_wc_notify_failed_external,
948                                             scratch_pool);
949           notifier->err = err;
950           ctx->notify_func2(ctx->notify_baton2, notifier, scratch_pool);
951         }
952       svn_error_clear(err);
953       return SVN_NO_ERROR;
954     }
955
956   return err;
957 }
958
959 static svn_error_t *
960 handle_externals_change(svn_client_ctx_t *ctx,
961                         const char *repos_root_url,
962                         svn_boolean_t *timestamp_sleep,
963                         const char *local_abspath,
964                         const char *new_desc_text,
965                         apr_hash_t *old_externals,
966                         svn_depth_t ambient_depth,
967                         svn_depth_t requested_depth,
968                         svn_ra_session_t *ra_session,
969                         apr_pool_t *scratch_pool)
970 {
971   apr_array_header_t *new_desc;
972   int i;
973   apr_pool_t *iterpool;
974   const char *url;
975
976   iterpool = svn_pool_create(scratch_pool);
977
978   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
979
980   /* Bag out if the depth here is too shallow for externals action. */
981   if ((requested_depth < svn_depth_infinity
982        && requested_depth != svn_depth_unknown)
983       || (ambient_depth < svn_depth_infinity
984           && requested_depth < svn_depth_infinity))
985     return SVN_NO_ERROR;
986
987   if (new_desc_text)
988     SVN_ERR(svn_wc_parse_externals_description3(&new_desc, local_abspath,
989                                                 new_desc_text,
990                                                 FALSE, scratch_pool));
991   else
992     new_desc = NULL;
993
994   SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, local_abspath,
995                                scratch_pool, iterpool));
996
997   SVN_ERR_ASSERT(url);
998
999   for (i = 0; new_desc && (i < new_desc->nelts); i++)
1000     {
1001       const char *old_defining_abspath;
1002       svn_wc_external_item2_t *new_item;
1003       const char *target_abspath;
1004       svn_boolean_t under_root;
1005
1006       new_item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *);
1007
1008       svn_pool_clear(iterpool);
1009
1010       if (ctx->cancel_func)
1011         SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1012
1013       SVN_ERR(svn_dirent_is_under_root(&under_root, &target_abspath,
1014                                        local_abspath, new_item->target_dir,
1015                                        iterpool));
1016
1017       if (! under_root)
1018         {
1019           return svn_error_createf(
1020                     SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1021                     _("Path '%s' is not in the working copy"),
1022                     svn_dirent_local_style(
1023                         svn_dirent_join(local_abspath, new_item->target_dir,
1024                                         iterpool),
1025                         iterpool));
1026         }
1027
1028       old_defining_abspath = svn_hash_gets(old_externals, target_abspath);
1029
1030       SVN_ERR(wrap_external_error(
1031                       ctx, target_abspath,
1032                       handle_external_item_change(ctx,
1033                                                   repos_root_url,
1034                                                   local_abspath, url,
1035                                                   target_abspath,
1036                                                   old_defining_abspath,
1037                                                   new_item, ra_session,
1038                                                   timestamp_sleep,
1039                                                   iterpool),
1040                       iterpool));
1041
1042       /* And remove already processed items from the to-remove hash */
1043       if (old_defining_abspath)
1044         svn_hash_sets(old_externals, target_abspath, NULL);
1045     }
1046
1047   svn_pool_destroy(iterpool);
1048
1049   return SVN_NO_ERROR;
1050 }
1051
1052
1053 svn_error_t *
1054 svn_client__handle_externals(apr_hash_t *externals_new,
1055                              apr_hash_t *ambient_depths,
1056                              const char *repos_root_url,
1057                              const char *target_abspath,
1058                              svn_depth_t requested_depth,
1059                              svn_boolean_t *timestamp_sleep,
1060                              svn_ra_session_t *ra_session,
1061                              svn_client_ctx_t *ctx,
1062                              apr_pool_t *scratch_pool)
1063 {
1064   apr_hash_t *old_external_defs;
1065   apr_hash_index_t *hi;
1066   apr_pool_t *iterpool;
1067
1068   SVN_ERR_ASSERT(repos_root_url);
1069
1070   iterpool = svn_pool_create(scratch_pool);
1071
1072   SVN_ERR(svn_wc__externals_defined_below(&old_external_defs,
1073                                           ctx->wc_ctx, target_abspath,
1074                                           scratch_pool, iterpool));
1075
1076   for (hi = apr_hash_first(scratch_pool, externals_new);
1077        hi;
1078        hi = apr_hash_next(hi))
1079     {
1080       const char *local_abspath = apr_hash_this_key(hi);
1081       const char *desc_text = apr_hash_this_val(hi);
1082       svn_depth_t ambient_depth = svn_depth_infinity;
1083
1084       svn_pool_clear(iterpool);
1085
1086       if (ambient_depths)
1087         {
1088           const char *ambient_depth_w;
1089
1090           ambient_depth_w = apr_hash_get(ambient_depths, local_abspath,
1091                                          apr_hash_this_key_len(hi));
1092
1093           if (ambient_depth_w == NULL)
1094             {
1095               return svn_error_createf(
1096                         SVN_ERR_WC_CORRUPT, NULL,
1097                         _("Traversal of '%s' found no ambient depth"),
1098                         svn_dirent_local_style(local_abspath, scratch_pool));
1099             }
1100           else
1101             {
1102               ambient_depth = svn_depth_from_word(ambient_depth_w);
1103             }
1104         }
1105
1106       SVN_ERR(handle_externals_change(ctx, repos_root_url, timestamp_sleep,
1107                                       local_abspath,
1108                                       desc_text, old_external_defs,
1109                                       ambient_depth, requested_depth,
1110                                       ra_session, iterpool));
1111     }
1112
1113   /* Remove the remaining externals */
1114   for (hi = apr_hash_first(scratch_pool, old_external_defs);
1115        hi;
1116        hi = apr_hash_next(hi))
1117     {
1118       const char *item_abspath = apr_hash_this_key(hi);
1119       const char *defining_abspath = apr_hash_this_val(hi);
1120       const char *parent_abspath;
1121
1122       svn_pool_clear(iterpool);
1123
1124       SVN_ERR(wrap_external_error(
1125                           ctx, item_abspath,
1126                           handle_external_item_removal(ctx, defining_abspath,
1127                                                        item_abspath, iterpool),
1128                           iterpool));
1129
1130       /* Are there any unversioned directories between the removed
1131        * external and the DEFINING_ABSPATH which we can remove? */
1132       parent_abspath = item_abspath;
1133       do {
1134         svn_node_kind_t kind;
1135
1136         parent_abspath = svn_dirent_dirname(parent_abspath, iterpool);
1137         SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, parent_abspath,
1138                                   FALSE /* show_deleted*/,
1139                                   FALSE /* show_hidden */,
1140                                   iterpool));
1141         if (kind == svn_node_none)
1142           {
1143             svn_error_t *err;
1144
1145             err = svn_io_dir_remove_nonrecursive(parent_abspath, iterpool);
1146             if (err)
1147               {
1148                 if (APR_STATUS_IS_ENOTEMPTY(err->apr_err))
1149                   {
1150                     svn_error_clear(err);
1151                     break; /* No parents to delete */
1152                   }
1153                 else if (APR_STATUS_IS_ENOENT(err->apr_err)
1154                          || APR_STATUS_IS_ENOTDIR(err->apr_err))
1155                   {
1156                     svn_error_clear(err);
1157                     /* Fall through; parent dir might be unversioned */
1158                   }
1159                 else
1160                   return svn_error_trace(err);
1161               }
1162           }
1163       } while (strcmp(parent_abspath, defining_abspath) != 0);
1164     }
1165
1166
1167   svn_pool_destroy(iterpool);
1168   return SVN_NO_ERROR;
1169 }
1170
1171
1172 svn_error_t *
1173 svn_client__export_externals(apr_hash_t *externals,
1174                              const char *from_url,
1175                              const char *to_abspath,
1176                              const char *repos_root_url,
1177                              svn_depth_t requested_depth,
1178                              const char *native_eol,
1179                              svn_boolean_t ignore_keywords,
1180                              svn_client_ctx_t *ctx,
1181                              apr_pool_t *scratch_pool)
1182 {
1183   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1184   apr_pool_t *sub_iterpool = svn_pool_create(scratch_pool);
1185   apr_hash_index_t *hi;
1186
1187   SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath));
1188
1189   for (hi = apr_hash_first(scratch_pool, externals);
1190        hi;
1191        hi = apr_hash_next(hi))
1192     {
1193       const char *local_abspath = apr_hash_this_key(hi);
1194       const char *desc_text = apr_hash_this_val(hi);
1195       const char *local_relpath;
1196       const char *dir_url;
1197       apr_array_header_t *items;
1198       int i;
1199
1200       svn_pool_clear(iterpool);
1201
1202       SVN_ERR(svn_wc_parse_externals_description3(&items, local_abspath,
1203                                                   desc_text, FALSE,
1204                                                   iterpool));
1205
1206       if (! items->nelts)
1207         continue;
1208
1209       local_relpath = svn_dirent_skip_ancestor(to_abspath, local_abspath);
1210
1211       dir_url = svn_path_url_add_component2(from_url, local_relpath,
1212                                             scratch_pool);
1213
1214       for (i = 0; i < items->nelts; i++)
1215         {
1216           const char *item_abspath;
1217           const char *new_url;
1218           svn_boolean_t under_root;
1219           svn_wc_external_item2_t *item = APR_ARRAY_IDX(items, i,
1220                                                 svn_wc_external_item2_t *);
1221
1222           svn_pool_clear(sub_iterpool);
1223
1224           SVN_ERR(svn_dirent_is_under_root(&under_root, &item_abspath,
1225                                            local_abspath, item->target_dir,
1226                                            sub_iterpool));
1227
1228           if (! under_root)
1229             {
1230               return svn_error_createf(
1231                         SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1232                         _("Path '%s' is not in the working copy"),
1233                         svn_dirent_local_style(
1234                             svn_dirent_join(local_abspath, item->target_dir,
1235                                             sub_iterpool),
1236                             sub_iterpool));
1237             }
1238
1239           SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, item,
1240                                                         repos_root_url,
1241                                                         dir_url, sub_iterpool,
1242                                                         sub_iterpool));
1243
1244           /* The target dir might have multiple components.  Guarantee
1245              the path leading down to the last component. */
1246           SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(item_abspath,
1247                                                                  sub_iterpool),
1248                                               sub_iterpool));
1249
1250           /* First notify that we're about to handle an external. */
1251           if (ctx->notify_func2)
1252             {
1253               ctx->notify_func2(
1254                        ctx->notify_baton2,
1255                        svn_wc_create_notify(item_abspath,
1256                                             svn_wc_notify_update_external,
1257                                             sub_iterpool),
1258                        sub_iterpool);
1259             }
1260
1261           SVN_ERR(wrap_external_error(
1262                           ctx, item_abspath,
1263                           svn_client_export5(NULL, new_url, item_abspath,
1264                                              &item->peg_revision,
1265                                              &item->revision,
1266                                              TRUE, FALSE, ignore_keywords,
1267                                              svn_depth_infinity,
1268                                              native_eol,
1269                                              ctx, sub_iterpool),
1270                           sub_iterpool));
1271         }
1272     }
1273
1274   svn_pool_destroy(sub_iterpool);
1275   svn_pool_destroy(iterpool);
1276
1277   return SVN_NO_ERROR;
1278 }
1279