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