1251881Speter/*
2251881Speter * externals.c:  handle the svn:externals property
3251881Speter *
4251881Speter * ====================================================================
5251881Speter *    Licensed to the Apache Software Foundation (ASF) under one
6251881Speter *    or more contributor license agreements.  See the NOTICE file
7251881Speter *    distributed with this work for additional information
8251881Speter *    regarding copyright ownership.  The ASF licenses this file
9251881Speter *    to you under the Apache License, Version 2.0 (the
10251881Speter *    "License"); you may not use this file except in compliance
11251881Speter *    with the License.  You may obtain a copy of the License at
12251881Speter *
13251881Speter *      http://www.apache.org/licenses/LICENSE-2.0
14251881Speter *
15251881Speter *    Unless required by applicable law or agreed to in writing,
16251881Speter *    software distributed under the License is distributed on an
17251881Speter *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18251881Speter *    KIND, either express or implied.  See the License for the
19251881Speter *    specific language governing permissions and limitations
20251881Speter *    under the License.
21251881Speter * ====================================================================
22251881Speter */
23251881Speter
24251881Speter/* ==================================================================== */
25251881Speter
26251881Speter
27251881Speter
28251881Speter/*** Includes. ***/
29251881Speter
30251881Speter#include <apr_uri.h>
31251881Speter#include "svn_hash.h"
32251881Speter#include "svn_wc.h"
33251881Speter#include "svn_pools.h"
34251881Speter#include "svn_client.h"
35251881Speter#include "svn_types.h"
36251881Speter#include "svn_error.h"
37251881Speter#include "svn_dirent_uri.h"
38251881Speter#include "svn_path.h"
39251881Speter#include "svn_props.h"
40251881Speter#include "svn_config.h"
41251881Speter#include "client.h"
42251881Speter
43251881Speter#include "svn_private_config.h"
44251881Speter#include "private/svn_wc_private.h"
45251881Speter
46251881Speter
47251881Speter/* Remove the directory at LOCAL_ABSPATH from revision control, and do the
48251881Speter * same to any revision controlled directories underneath LOCAL_ABSPATH
49251881Speter * (including directories not referred to by parent svn administrative areas);
50251881Speter * then if LOCAL_ABSPATH is empty afterwards, remove it, else rename it to a
51251881Speter * unique name in the same parent directory.
52251881Speter *
53251881Speter * Pass CANCEL_FUNC, CANCEL_BATON to svn_wc_remove_from_revision_control.
54251881Speter *
55251881Speter * Use SCRATCH_POOL for all temporary allocation.
56251881Speter */
57251881Speterstatic svn_error_t *
58251881Speterrelegate_dir_external(svn_wc_context_t *wc_ctx,
59251881Speter                      const char *wri_abspath,
60251881Speter                      const char *local_abspath,
61251881Speter                      svn_cancel_func_t cancel_func,
62251881Speter                      void *cancel_baton,
63251881Speter                      svn_wc_notify_func2_t notify_func,
64251881Speter                      void *notify_baton,
65251881Speter                      apr_pool_t *scratch_pool)
66251881Speter{
67251881Speter  svn_error_t *err = SVN_NO_ERROR;
68251881Speter
69251881Speter  SVN_ERR(svn_wc__acquire_write_lock(NULL, wc_ctx, local_abspath,
70251881Speter                                     FALSE, scratch_pool, scratch_pool));
71251881Speter
72251881Speter  err = svn_wc__external_remove(wc_ctx, wri_abspath, local_abspath, FALSE,
73251881Speter                                cancel_func, cancel_baton, scratch_pool);
74251881Speter  if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD))
75251881Speter    {
76251881Speter      const char *parent_dir;
77251881Speter      const char *dirname;
78251881Speter      const char *new_path;
79251881Speter
80251881Speter      svn_error_clear(err);
81251881Speter      err = SVN_NO_ERROR;
82251881Speter
83251881Speter      svn_dirent_split(&parent_dir, &dirname, local_abspath, scratch_pool);
84251881Speter
85251881Speter      /* Reserve the new dir name. */
86251881Speter      SVN_ERR(svn_io_open_uniquely_named(NULL, &new_path,
87251881Speter                                         parent_dir, dirname, ".OLD",
88251881Speter                                         svn_io_file_del_none,
89251881Speter                                         scratch_pool, scratch_pool));
90251881Speter
91251881Speter      /* Sigh...  We must fall ever so slightly from grace.
92251881Speter
93251881Speter         Ideally, there would be no window, however brief, when we
94251881Speter         don't have a reservation on the new name.  Unfortunately,
95251881Speter         at least in the Unix (Linux?) version of apr_file_rename(),
96251881Speter         you can't rename a directory over a file, because it's just
97251881Speter         calling stdio rename(), which says:
98251881Speter
99251881Speter            ENOTDIR
100251881Speter              A  component used as a directory in oldpath or newpath
101251881Speter              path is not, in fact, a directory.  Or, oldpath  is
102251881Speter              a directory, and newpath exists but is not a directory
103251881Speter
104251881Speter         So instead, we get the name, then remove the file (ugh), then
105251881Speter         rename the directory, hoping that nobody has gotten that name
106251881Speter         in the meantime -- which would never happen in real life, so
107251881Speter         no big deal.
108251881Speter      */
109251881Speter      /* Do our best, but no biggy if it fails. The rename will fail. */
110251881Speter      svn_error_clear(svn_io_remove_file2(new_path, TRUE, scratch_pool));
111251881Speter
112251881Speter      /* Rename. If this is still a working copy we should use the working
113251881Speter         copy rename function (to release open handles) */
114251881Speter      err = svn_wc__rename_wc(wc_ctx, local_abspath, new_path,
115251881Speter                              scratch_pool);
116251881Speter
117251881Speter      if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
118251881Speter        {
119251881Speter          svn_error_clear(err);
120251881Speter
121251881Speter          /* And if it is no longer a working copy, we should just rename
122251881Speter             it */
123251881Speter          err = svn_io_file_rename(local_abspath, new_path, scratch_pool);
124251881Speter        }
125251881Speter
126251881Speter      /* ### TODO: We should notify the user about the rename */
127251881Speter      if (notify_func)
128251881Speter        {
129251881Speter          svn_wc_notify_t *notify;
130251881Speter
131251881Speter          notify = svn_wc_create_notify(err ? local_abspath : new_path,
132251881Speter                                        svn_wc_notify_left_local_modifications,
133251881Speter                                        scratch_pool);
134251881Speter          notify->kind = svn_node_dir;
135251881Speter          notify->err = err;
136251881Speter
137251881Speter          notify_func(notify_baton, notify, scratch_pool);
138251881Speter        }
139251881Speter    }
140251881Speter
141251881Speter  return svn_error_trace(err);
142251881Speter}
143251881Speter
144251881Speter/* Try to update a directory external at PATH to URL at REVISION.
145251881Speter   Use POOL for temporary allocations, and use the client context CTX. */
146251881Speterstatic svn_error_t *
147251881Speterswitch_dir_external(const char *local_abspath,
148251881Speter                    const char *url,
149289166Speter                    const char *url_from_externals_definition,
150251881Speter                    const svn_opt_revision_t *peg_revision,
151251881Speter                    const svn_opt_revision_t *revision,
152251881Speter                    const char *defining_abspath,
153251881Speter                    svn_boolean_t *timestamp_sleep,
154299742Sdim                    svn_ra_session_t *ra_session,
155251881Speter                    svn_client_ctx_t *ctx,
156251881Speter                    apr_pool_t *pool)
157251881Speter{
158251881Speter  svn_node_kind_t kind;
159251881Speter  svn_error_t *err;
160251881Speter  svn_revnum_t external_peg_rev = SVN_INVALID_REVNUM;
161251881Speter  svn_revnum_t external_rev = SVN_INVALID_REVNUM;
162251881Speter  apr_pool_t *subpool = svn_pool_create(pool);
163251881Speter  const char *repos_root_url;
164251881Speter  const char *repos_uuid;
165251881Speter
166251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
167251881Speter
168251881Speter  if (peg_revision->kind == svn_opt_revision_number)
169251881Speter    external_peg_rev = peg_revision->value.number;
170251881Speter
171251881Speter  if (revision->kind == svn_opt_revision_number)
172251881Speter    external_rev = revision->value.number;
173251881Speter
174299742Sdim  /*
175289166Speter   * The code below assumes existing versioned paths are *not* part of
176289166Speter   * the external's defining working copy.
177289166Speter   * The working copy library does not support registering externals
178289166Speter   * on top of existing BASE nodes and will error out if we try.
179289166Speter   * So if the external target is part of the defining working copy's
180289166Speter   * BASE tree, don't attempt to create the external. Doing so would
181289166Speter   * leave behind a switched path instead of an external (since the
182289166Speter   * switch succeeds but registration of the external in the DB fails).
183289166Speter   * The working copy then cannot be updated until the path is switched back.
184289166Speter   * See issue #4085.
185289166Speter   */
186289166Speter  SVN_ERR(svn_wc__node_get_base(&kind, NULL, NULL,
187289166Speter                                &repos_root_url, &repos_uuid,
188289166Speter                                NULL, ctx->wc_ctx, local_abspath,
189289166Speter                                TRUE, /* ignore_enoent */
190289166Speter                                pool, pool));
191289166Speter  if (kind != svn_node_unknown)
192289166Speter    {
193289166Speter      const char *wcroot_abspath;
194289166Speter      const char *defining_wcroot_abspath;
195289166Speter
196289166Speter      SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx,
197289166Speter                                 local_abspath, pool, pool));
198289166Speter      SVN_ERR(svn_wc__get_wcroot(&defining_wcroot_abspath, ctx->wc_ctx,
199289166Speter                                 defining_abspath, pool, pool));
200289166Speter      if (strcmp(wcroot_abspath, defining_wcroot_abspath) == 0)
201289166Speter        return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
202289166Speter                                 _("The external '%s' defined in %s at '%s' "
203289166Speter                                   "cannot be checked out because '%s' is "
204289166Speter                                   "already a versioned path."),
205289166Speter                                   url_from_externals_definition,
206289166Speter                                   SVN_PROP_EXTERNALS,
207289166Speter                                   svn_dirent_local_style(defining_abspath,
208289166Speter                                                          pool),
209289166Speter                                   svn_dirent_local_style(local_abspath,
210289166Speter                                                          pool));
211289166Speter    }
212289166Speter
213251881Speter  /* If path is a directory, try to update/switch to the correct URL
214251881Speter     and revision. */
215251881Speter  SVN_ERR(svn_io_check_path(local_abspath, &kind, pool));
216251881Speter  if (kind == svn_node_dir)
217251881Speter    {
218251881Speter      const char *node_url;
219251881Speter
220251881Speter      /* Doubles as an "is versioned" check. */
221251881Speter      err = svn_wc__node_get_url(&node_url, ctx->wc_ctx, local_abspath,
222251881Speter                                 pool, subpool);
223251881Speter      if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
224251881Speter        {
225251881Speter          svn_error_clear(err);
226251881Speter          err = SVN_NO_ERROR;
227251881Speter          goto relegate;
228251881Speter        }
229251881Speter      else if (err)
230251881Speter        return svn_error_trace(err);
231251881Speter
232251881Speter      if (node_url)
233251881Speter        {
234299742Sdim          svn_boolean_t is_wcroot;
235299742Sdim
236299742Sdim          SVN_ERR(svn_wc__is_wcroot(&is_wcroot, ctx->wc_ctx, local_abspath,
237299742Sdim                                    pool));
238299742Sdim
239299742Sdim          if (! is_wcroot)
240299742Sdim          {
241299742Sdim            /* This can't be a directory external! */
242299742Sdim
243299742Sdim            err = svn_wc__external_remove(ctx->wc_ctx, defining_abspath,
244299742Sdim                                          local_abspath,
245299742Sdim                                          TRUE /* declaration_only */,
246299742Sdim                                          ctx->cancel_func, ctx->cancel_baton,
247299742Sdim                                          pool);
248299742Sdim
249299742Sdim            if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
250299742Sdim              {
251299742Sdim                /* New external... No problem that we can't remove it */
252299742Sdim                svn_error_clear(err);
253299742Sdim                err = NULL;
254299742Sdim              }
255299742Sdim            else if (err)
256299742Sdim              return svn_error_trace(err);
257299742Sdim
258299742Sdim            return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
259299742Sdim                                     _("The external '%s' defined in %s at '%s' "
260299742Sdim                                       "cannot be checked out because '%s' is "
261299742Sdim                                       "already a versioned path."),
262299742Sdim                                     url_from_externals_definition,
263299742Sdim                                     SVN_PROP_EXTERNALS,
264299742Sdim                                     svn_dirent_local_style(defining_abspath,
265299742Sdim                                                            pool),
266299742Sdim                                     svn_dirent_local_style(local_abspath,
267299742Sdim                                                            pool));
268299742Sdim          }
269299742Sdim
270251881Speter          /* If we have what appears to be a version controlled
271251881Speter             subdir, and its top-level URL matches that of our
272251881Speter             externals definition, perform an update. */
273251881Speter          if (strcmp(node_url, url) == 0)
274251881Speter            {
275299742Sdim              SVN_ERR(svn_client__update_internal(NULL, timestamp_sleep,
276299742Sdim                                                  local_abspath,
277251881Speter                                                  revision, svn_depth_unknown,
278251881Speter                                                  FALSE, FALSE, FALSE, TRUE,
279251881Speter                                                  FALSE, TRUE,
280299742Sdim                                                  ra_session, ctx, subpool));
281289166Speter
282289166Speter              /* We just decided that this existing directory is an external,
283289166Speter                 so update the external registry with this information, like
284289166Speter                 when checking out an external */
285289166Speter              SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
286289166Speter                                    defining_abspath,
287289166Speter                                    local_abspath, svn_node_dir,
288289166Speter                                    repos_root_url, repos_uuid,
289289166Speter                                    svn_uri_skip_ancestor(repos_root_url,
290289166Speter                                                          url, pool),
291289166Speter                                    external_peg_rev,
292289166Speter                                    external_rev,
293289166Speter                                    pool));
294289166Speter
295251881Speter              svn_pool_destroy(subpool);
296251881Speter              goto cleanup;
297251881Speter            }
298251881Speter
299251881Speter          /* We'd really prefer not to have to do a brute-force
300251881Speter             relegation -- blowing away the current external working
301251881Speter             copy and checking it out anew -- so we'll first see if we
302251881Speter             can get away with a generally cheaper relocation (if
303251881Speter             required) and switch-style update.
304251881Speter
305251881Speter             To do so, we need to know the repository root URL of the
306251881Speter             external working copy as it currently sits. */
307251881Speter          err = svn_wc__node_get_repos_info(NULL, NULL,
308251881Speter                                            &repos_root_url, &repos_uuid,
309251881Speter                                            ctx->wc_ctx, local_abspath,
310251881Speter                                            pool, subpool);
311251881Speter          if (err)
312251881Speter            {
313251881Speter              if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
314251881Speter                  && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
315251881Speter                return svn_error_trace(err);
316251881Speter
317251881Speter              svn_error_clear(err);
318251881Speter              repos_root_url = NULL;
319251881Speter              repos_uuid = NULL;
320251881Speter            }
321251881Speter
322251881Speter          if (repos_root_url)
323251881Speter            {
324251881Speter              /* If the new external target URL is not obviously a
325251881Speter                 child of the external working copy's current
326251881Speter                 repository root URL... */
327251881Speter              if (! svn_uri__is_ancestor(repos_root_url, url))
328251881Speter                {
329251881Speter                  const char *repos_root;
330251881Speter
331251881Speter                  /* ... then figure out precisely which repository
332251881Speter                      root URL that target URL *is* a child of ... */
333251881Speter                  SVN_ERR(svn_client_get_repos_root(&repos_root, NULL, url,
334251881Speter                                                    ctx, subpool, subpool));
335251881Speter
336251881Speter                  /* ... and use that to try to relocate the external
337251881Speter                     working copy to the target location.  */
338251881Speter                  err = svn_client_relocate2(local_abspath, repos_root_url,
339251881Speter                                             repos_root, FALSE, ctx, subpool);
340251881Speter
341251881Speter                  /* If the relocation failed because the new URL
342251881Speter                     points to a totally different repository, we've
343251881Speter                     no choice but to relegate and check out a new WC. */
344251881Speter                  if (err
345251881Speter                      && (err->apr_err == SVN_ERR_WC_INVALID_RELOCATION
346251881Speter                          || (err->apr_err
347251881Speter                              == SVN_ERR_CLIENT_INVALID_RELOCATION)))
348251881Speter                    {
349251881Speter                      svn_error_clear(err);
350251881Speter                      goto relegate;
351251881Speter                    }
352251881Speter                  else if (err)
353251881Speter                    return svn_error_trace(err);
354251881Speter
355251881Speter                  /* If the relocation went without a hitch, we should
356251881Speter                     have a new repository root URL. */
357251881Speter                  repos_root_url = repos_root;
358251881Speter                }
359251881Speter
360251881Speter              SVN_ERR(svn_client__switch_internal(NULL, local_abspath, url,
361251881Speter                                                  peg_revision, revision,
362251881Speter                                                  svn_depth_infinity,
363251881Speter                                                  TRUE, FALSE, FALSE,
364251881Speter                                                  TRUE /* ignore_ancestry */,
365251881Speter                                                  timestamp_sleep,
366251881Speter                                                  ctx, subpool));
367251881Speter
368251881Speter              SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
369251881Speter                                                defining_abspath,
370251881Speter                                                local_abspath, svn_node_dir,
371251881Speter                                                repos_root_url, repos_uuid,
372251881Speter                                                svn_uri_skip_ancestor(
373251881Speter                                                            repos_root_url,
374251881Speter                                                            url, subpool),
375251881Speter                                                external_peg_rev,
376251881Speter                                                external_rev,
377251881Speter                                                subpool));
378251881Speter
379251881Speter              svn_pool_destroy(subpool);
380251881Speter              goto cleanup;
381251881Speter            }
382251881Speter        }
383251881Speter    }
384251881Speter
385251881Speter relegate:
386251881Speter
387251881Speter  /* Fall back on removing the WC and checking out a new one. */
388251881Speter
389251881Speter  /* Ensure that we don't have any RA sessions or WC locks from failed
390251881Speter     operations above. */
391251881Speter  svn_pool_destroy(subpool);
392251881Speter
393251881Speter  if (kind == svn_node_dir)
394251881Speter    {
395251881Speter      /* Buh-bye, old and busted ... */
396251881Speter      SVN_ERR(relegate_dir_external(ctx->wc_ctx, defining_abspath,
397251881Speter                                    local_abspath,
398251881Speter                                    ctx->cancel_func, ctx->cancel_baton,
399251881Speter                                    ctx->notify_func2, ctx->notify_baton2,
400251881Speter                                    pool));
401251881Speter    }
402251881Speter  else
403251881Speter    {
404251881Speter      /* The target dir might have multiple components.  Guarantee
405251881Speter         the path leading down to the last component. */
406251881Speter      const char *parent = svn_dirent_dirname(local_abspath, pool);
407251881Speter      SVN_ERR(svn_io_make_dir_recursively(parent, pool));
408251881Speter    }
409251881Speter
410251881Speter  /* ... Hello, new hotness. */
411299742Sdim  SVN_ERR(svn_client__checkout_internal(NULL, timestamp_sleep,
412299742Sdim                                        url, local_abspath, peg_revision,
413251881Speter                                        revision, svn_depth_infinity,
414299742Sdim                                        FALSE, FALSE,
415299742Sdim                                        ra_session,
416251881Speter                                        ctx, pool));
417251881Speter
418251881Speter  SVN_ERR(svn_wc__node_get_repos_info(NULL, NULL,
419251881Speter                                      &repos_root_url,
420251881Speter                                      &repos_uuid,
421251881Speter                                      ctx->wc_ctx, local_abspath,
422251881Speter                                      pool, pool));
423251881Speter
424251881Speter  SVN_ERR(svn_wc__external_register(ctx->wc_ctx,
425251881Speter                                    defining_abspath,
426251881Speter                                    local_abspath, svn_node_dir,
427251881Speter                                    repos_root_url, repos_uuid,
428251881Speter                                    svn_uri_skip_ancestor(repos_root_url,
429251881Speter                                                          url, pool),
430251881Speter                                    external_peg_rev,
431251881Speter                                    external_rev,
432251881Speter                                    pool));
433251881Speter
434251881Speter cleanup:
435251881Speter  /* Issues #4123 and #4130: We don't need to keep the newly checked
436251881Speter     out external's DB open. */
437251881Speter  SVN_ERR(svn_wc__close_db(local_abspath, ctx->wc_ctx, pool));
438251881Speter
439251881Speter  return SVN_NO_ERROR;
440251881Speter}
441251881Speter
442299742Sdim/* Try to update a file external at LOCAL_ABSPATH to SWITCH_LOC. This function
443299742Sdim   assumes caller has a write lock in CTX.  Use SCRATCH_POOL for temporary
444251881Speter   allocations, and use the client context CTX. */
445251881Speterstatic svn_error_t *
446251881Speterswitch_file_external(const char *local_abspath,
447299742Sdim                     const svn_client__pathrev_t *switch_loc,
448299742Sdim                     const char *record_url,
449299742Sdim                     const svn_opt_revision_t *record_peg_revision,
450299742Sdim                     const svn_opt_revision_t *record_revision,
451251881Speter                     const char *def_dir_abspath,
452251881Speter                     svn_ra_session_t *ra_session,
453251881Speter                     svn_client_ctx_t *ctx,
454251881Speter                     apr_pool_t *scratch_pool)
455251881Speter{
456251881Speter  svn_config_t *cfg = ctx->config
457251881Speter                      ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
458251881Speter                      : NULL;
459251881Speter  svn_boolean_t use_commit_times;
460251881Speter  const char *diff3_cmd;
461251881Speter  const char *preserved_exts_str;
462251881Speter  const apr_array_header_t *preserved_exts;
463251881Speter  svn_node_kind_t kind, external_kind;
464251881Speter
465251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
466251881Speter
467251881Speter  /* See if the user wants last-commit timestamps instead of current ones. */
468251881Speter  SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
469251881Speter                              SVN_CONFIG_SECTION_MISCELLANY,
470251881Speter                              SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
471251881Speter
472251881Speter  /* Get the external diff3, if any. */
473251881Speter  svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
474251881Speter                 SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
475251881Speter
476251881Speter  if (diff3_cmd != NULL)
477251881Speter    SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool));
478251881Speter
479251881Speter  /* See which files the user wants to preserve the extension of when
480251881Speter     conflict files are made. */
481251881Speter  svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
482251881Speter                 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
483251881Speter  preserved_exts = *preserved_exts_str
484251881Speter    ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool)
485251881Speter    : NULL;
486251881Speter
487251881Speter  {
488251881Speter    const char *wcroot_abspath;
489251881Speter
490251881Speter    SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath,
491251881Speter                               scratch_pool, scratch_pool));
492251881Speter
493251881Speter    /* File externals can only be installed inside the current working copy.
494251881Speter       So verify if the working copy that contains/will contain the target
495251881Speter       is the defining abspath, or one of its ancestors */
496251881Speter
497251881Speter    if (!svn_dirent_is_ancestor(wcroot_abspath, def_dir_abspath))
498251881Speter        return svn_error_createf(
499251881Speter                        SVN_ERR_WC_BAD_PATH, NULL,
500251881Speter                        _("Cannot insert a file external defined on '%s' "
501251881Speter                          "into the working copy '%s'."),
502251881Speter                        svn_dirent_local_style(def_dir_abspath,
503251881Speter                                               scratch_pool),
504251881Speter                        svn_dirent_local_style(wcroot_abspath,
505251881Speter                                               scratch_pool));
506251881Speter  }
507251881Speter
508251881Speter  SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath,
509251881Speter                            TRUE, FALSE, scratch_pool));
510251881Speter
511251881Speter  SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
512251881Speter                                     ctx->wc_ctx, local_abspath, local_abspath,
513251881Speter                                     TRUE, scratch_pool, scratch_pool));
514251881Speter
515251881Speter  /* If there is a versioned item with this name, ensure it's a file
516251881Speter     external before working with it.  If there is no entry in the
517251881Speter     working copy, then create an empty file and add it to the working
518251881Speter     copy. */
519251881Speter  if (kind != svn_node_none && kind != svn_node_unknown)
520251881Speter    {
521251881Speter      if (external_kind != svn_node_file)
522251881Speter        {
523251881Speter          return svn_error_createf(
524251881Speter              SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0,
525251881Speter             _("The file external from '%s' cannot overwrite the existing "
526251881Speter               "versioned item at '%s'"),
527299742Sdim             switch_loc->url,
528299742Sdim             svn_dirent_local_style(local_abspath, scratch_pool));
529251881Speter        }
530251881Speter    }
531251881Speter  else
532251881Speter    {
533251881Speter      svn_node_kind_t disk_kind;
534251881Speter
535251881Speter      SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool));
536251881Speter
537251881Speter      if (kind == svn_node_file || kind == svn_node_dir)
538251881Speter        return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL,
539251881Speter                                 _("The file external '%s' can not be "
540251881Speter                                   "created because the node exists."),
541251881Speter                                 svn_dirent_local_style(local_abspath,
542251881Speter                                                        scratch_pool));
543251881Speter    }
544251881Speter
545251881Speter  {
546251881Speter    const svn_ra_reporter3_t *reporter;
547251881Speter    void *report_baton;
548251881Speter    const svn_delta_editor_t *switch_editor;
549251881Speter    void *switch_baton;
550251881Speter    svn_revnum_t revnum;
551251881Speter    apr_array_header_t *inherited_props;
552299742Sdim    const char *target = svn_dirent_basename(local_abspath, scratch_pool);
553251881Speter
554251881Speter    /* Get the external file's iprops. */
555251881Speter    SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "",
556251881Speter                                       switch_loc->rev,
557251881Speter                                       scratch_pool, scratch_pool));
558251881Speter
559299742Sdim    SVN_ERR(svn_ra_reparent(ra_session,
560299742Sdim                            svn_uri_dirname(switch_loc->url, scratch_pool),
561251881Speter                            scratch_pool));
562251881Speter
563251881Speter    SVN_ERR(svn_wc__get_file_external_editor(&switch_editor, &switch_baton,
564251881Speter                                             &revnum, ctx->wc_ctx,
565251881Speter                                             local_abspath,
566251881Speter                                             def_dir_abspath,
567251881Speter                                             switch_loc->url,
568251881Speter                                             switch_loc->repos_root_url,
569251881Speter                                             switch_loc->repos_uuid,
570251881Speter                                             inherited_props,
571251881Speter                                             use_commit_times,
572251881Speter                                             diff3_cmd, preserved_exts,
573251881Speter                                             def_dir_abspath,
574299742Sdim                                             record_url,
575299742Sdim                                             record_peg_revision,
576299742Sdim                                             record_revision,
577251881Speter                                             ctx->cancel_func,
578251881Speter                                             ctx->cancel_baton,
579251881Speter                                             ctx->notify_func2,
580251881Speter                                             ctx->notify_baton2,
581251881Speter                                             scratch_pool, scratch_pool));
582251881Speter
583251881Speter    /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
584251881Speter     invalid revnum, that means RA will use the latest revision. */
585251881Speter    SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton,
586251881Speter                              switch_loc->rev,
587289166Speter                              target, svn_depth_unknown, switch_loc->url,
588251881Speter                              FALSE /* send_copyfrom */,
589251881Speter                              TRUE /* ignore_ancestry */,
590251881Speter                              switch_editor, switch_baton,
591251881Speter                              scratch_pool, scratch_pool));
592251881Speter
593251881Speter    SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath,
594251881Speter                                        reporter, report_baton,
595251881Speter                                        TRUE,  use_commit_times,
596251881Speter                                        ctx->cancel_func, ctx->cancel_baton,
597251881Speter                                        ctx->notify_func2, ctx->notify_baton2,
598251881Speter                                        scratch_pool));
599251881Speter
600251881Speter    if (ctx->notify_func2)
601251881Speter      {
602251881Speter        svn_wc_notify_t *notify
603251881Speter          = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
604251881Speter                                 scratch_pool);
605251881Speter        notify->kind = svn_node_none;
606251881Speter        notify->content_state = notify->prop_state
607251881Speter          = svn_wc_notify_state_inapplicable;
608251881Speter        notify->lock_state = svn_wc_notify_lock_state_inapplicable;
609251881Speter        notify->revision = revnum;
610299742Sdim        ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
611251881Speter      }
612251881Speter  }
613251881Speter
614251881Speter  return SVN_NO_ERROR;
615251881Speter}
616251881Speter
617251881Speter/* Wrappers around svn_wc__external_remove, obtaining and releasing a lock for
618251881Speter   directory externals */
619251881Speterstatic svn_error_t *
620251881Speterremove_external2(svn_boolean_t *removed,
621251881Speter                svn_wc_context_t *wc_ctx,
622251881Speter                const char *wri_abspath,
623251881Speter                const char *local_abspath,
624251881Speter                svn_node_kind_t external_kind,
625251881Speter                svn_cancel_func_t cancel_func,
626251881Speter                void *cancel_baton,
627251881Speter                apr_pool_t *scratch_pool)
628251881Speter{
629251881Speter  SVN_ERR(svn_wc__external_remove(wc_ctx, wri_abspath,
630251881Speter                                  local_abspath,
631251881Speter                                  (external_kind == svn_node_none),
632251881Speter                                  cancel_func, cancel_baton,
633251881Speter                                  scratch_pool));
634251881Speter
635251881Speter  *removed = TRUE;
636251881Speter  return SVN_NO_ERROR;
637251881Speter}
638251881Speter
639251881Speter
640251881Speterstatic svn_error_t *
641251881Speterremove_external(svn_boolean_t *removed,
642251881Speter                svn_wc_context_t *wc_ctx,
643251881Speter                const char *wri_abspath,
644251881Speter                const char *local_abspath,
645251881Speter                svn_node_kind_t external_kind,
646251881Speter                svn_cancel_func_t cancel_func,
647251881Speter                void *cancel_baton,
648251881Speter                apr_pool_t *scratch_pool)
649251881Speter{
650251881Speter  *removed = FALSE;
651251881Speter  switch (external_kind)
652251881Speter    {
653251881Speter      case svn_node_dir:
654251881Speter        SVN_WC__CALL_WITH_WRITE_LOCK(
655251881Speter            remove_external2(removed,
656251881Speter                             wc_ctx, wri_abspath,
657251881Speter                             local_abspath, external_kind,
658251881Speter                             cancel_func, cancel_baton,
659251881Speter                             scratch_pool),
660251881Speter            wc_ctx, local_abspath, FALSE, scratch_pool);
661251881Speter        break;
662251881Speter      case svn_node_file:
663251881Speter      default:
664251881Speter        SVN_ERR(remove_external2(removed,
665251881Speter                                 wc_ctx, wri_abspath,
666251881Speter                                 local_abspath, external_kind,
667251881Speter                                 cancel_func, cancel_baton,
668251881Speter                                 scratch_pool));
669251881Speter        break;
670251881Speter    }
671251881Speter
672251881Speter  return SVN_NO_ERROR;
673251881Speter}
674251881Speter
675251881Speter/* Called when an external that is in the EXTERNALS table is no longer
676251881Speter   referenced from an svn:externals property */
677251881Speterstatic svn_error_t *
678251881Speterhandle_external_item_removal(const svn_client_ctx_t *ctx,
679251881Speter                             const char *defining_abspath,
680251881Speter                             const char *local_abspath,
681251881Speter                             apr_pool_t *scratch_pool)
682251881Speter{
683251881Speter  svn_error_t *err;
684251881Speter  svn_node_kind_t external_kind;
685251881Speter  svn_node_kind_t kind;
686251881Speter  svn_boolean_t removed = FALSE;
687251881Speter
688251881Speter  /* local_abspath should be a wcroot or a file external */
689251881Speter  SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL,
690251881Speter                                     ctx->wc_ctx, defining_abspath,
691251881Speter                                     local_abspath, FALSE,
692251881Speter                                     scratch_pool, scratch_pool));
693251881Speter
694251881Speter  SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE,
695251881Speter                           scratch_pool));
696251881Speter
697251881Speter  if (external_kind != kind)
698251881Speter    external_kind = svn_node_none; /* Only remove the registration */
699251881Speter
700251881Speter  err = remove_external(&removed,
701251881Speter                        ctx->wc_ctx, defining_abspath, local_abspath,
702251881Speter                        external_kind,
703251881Speter                        ctx->cancel_func, ctx->cancel_baton,
704251881Speter                        scratch_pool);
705251881Speter
706251881Speter  if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED && removed)
707251881Speter    {
708251881Speter      svn_error_clear(err);
709251881Speter      err = NULL; /* We removed the working copy, so we can't release the
710251881Speter                     lock that was stored inside */
711251881Speter    }
712251881Speter
713251881Speter  if (ctx->notify_func2)
714251881Speter    {
715251881Speter      svn_wc_notify_t *notify =
716251881Speter          svn_wc_create_notify(local_abspath,
717251881Speter                               svn_wc_notify_update_external_removed,
718251881Speter                               scratch_pool);
719251881Speter
720251881Speter      notify->kind = kind;
721251881Speter      notify->err = err;
722251881Speter
723299742Sdim      ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
724251881Speter
725251881Speter      if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
726251881Speter        {
727251881Speter          notify = svn_wc_create_notify(local_abspath,
728251881Speter                                      svn_wc_notify_left_local_modifications,
729251881Speter                                      scratch_pool);
730251881Speter          notify->kind = svn_node_dir;
731251881Speter          notify->err = err;
732251881Speter
733299742Sdim          ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
734251881Speter        }
735251881Speter    }
736251881Speter
737251881Speter  if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)
738251881Speter    {
739251881Speter      svn_error_clear(err);
740251881Speter      err = NULL;
741251881Speter    }
742251881Speter
743251881Speter  return svn_error_trace(err);
744251881Speter}
745251881Speter
746251881Speterstatic svn_error_t *
747251881Speterhandle_external_item_change(svn_client_ctx_t *ctx,
748251881Speter                            const char *repos_root_url,
749251881Speter                            const char *parent_dir_abspath,
750251881Speter                            const char *parent_dir_url,
751251881Speter                            const char *local_abspath,
752251881Speter                            const char *old_defining_abspath,
753251881Speter                            const svn_wc_external_item2_t *new_item,
754299742Sdim                            svn_ra_session_t *ra_session,
755251881Speter                            svn_boolean_t *timestamp_sleep,
756251881Speter                            apr_pool_t *scratch_pool)
757251881Speter{
758251881Speter  svn_client__pathrev_t *new_loc;
759251881Speter  const char *new_url;
760251881Speter  svn_node_kind_t ext_kind;
761251881Speter
762251881Speter  SVN_ERR_ASSERT(repos_root_url && parent_dir_url);
763251881Speter  SVN_ERR_ASSERT(new_item != NULL);
764251881Speter
765251881Speter  /* Don't bother to check status, since we'll get that for free by
766251881Speter     attempting to retrieve the hash values anyway.  */
767251881Speter
768251881Speter  /* When creating the absolute URL, use the pool and not the
769251881Speter     iterpool, since the hash table values outlive the iterpool and
770251881Speter     any pointers they have should also outlive the iterpool.  */
771251881Speter
772251881Speter  SVN_ERR(svn_wc__resolve_relative_external_url(&new_url,
773251881Speter                                                new_item, repos_root_url,
774251881Speter                                                parent_dir_url,
775251881Speter                                                scratch_pool, scratch_pool));
776251881Speter
777251881Speter  /* Determine if the external is a file or directory. */
778299742Sdim  /* Get the RA connection, if needed. */
779299742Sdim  if (ra_session)
780299742Sdim    {
781299742Sdim      svn_error_t *err = svn_ra_reparent(ra_session, new_url, scratch_pool);
782251881Speter
783299742Sdim      if (err)
784299742Sdim        {
785299742Sdim          if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
786299742Sdim            {
787299742Sdim              svn_error_clear(err);
788299742Sdim              ra_session = NULL;
789299742Sdim            }
790299742Sdim          else
791299742Sdim            return svn_error_trace(err);
792299742Sdim        }
793299742Sdim      else
794299742Sdim        {
795299742Sdim          SVN_ERR(svn_client__resolve_rev_and_url(&new_loc,
796299742Sdim                                                  ra_session, new_url,
797299742Sdim                                                  &(new_item->peg_revision),
798299742Sdim                                                  &(new_item->revision), ctx,
799299742Sdim                                                  scratch_pool));
800299742Sdim
801299742Sdim          SVN_ERR(svn_ra_reparent(ra_session, new_loc->url, scratch_pool));
802299742Sdim        }
803299742Sdim    }
804299742Sdim
805299742Sdim  if (!ra_session)
806299742Sdim    SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
807299742Sdim                                              new_url, NULL,
808299742Sdim                                              &(new_item->peg_revision),
809299742Sdim                                              &(new_item->revision), ctx,
810299742Sdim                                              scratch_pool));
811299742Sdim
812251881Speter  SVN_ERR(svn_ra_check_path(ra_session, "", new_loc->rev, &ext_kind,
813251881Speter                            scratch_pool));
814251881Speter
815251881Speter  if (svn_node_none == ext_kind)
816251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
817251881Speter                             _("URL '%s' at revision %ld doesn't exist"),
818251881Speter                             new_loc->url, new_loc->rev);
819251881Speter
820251881Speter  if (svn_node_dir != ext_kind && svn_node_file != ext_kind)
821251881Speter    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
822251881Speter                             _("URL '%s' at revision %ld is not a file "
823251881Speter                               "or a directory"),
824251881Speter                             new_loc->url, new_loc->rev);
825251881Speter
826251881Speter
827251881Speter  /* Not protecting against recursive externals.  Detecting them in
828251881Speter     the global case is hard, and it should be pretty obvious to a
829251881Speter     user when it happens.  Worst case: your disk fills up :-). */
830251881Speter
831251881Speter  /* First notify that we're about to handle an external. */
832251881Speter  if (ctx->notify_func2)
833251881Speter    {
834299742Sdim      ctx->notify_func2(
835251881Speter         ctx->notify_baton2,
836251881Speter         svn_wc_create_notify(local_abspath,
837251881Speter                              svn_wc_notify_update_external,
838251881Speter                              scratch_pool),
839251881Speter         scratch_pool);
840251881Speter    }
841251881Speter
842251881Speter  if (! old_defining_abspath)
843251881Speter    {
844251881Speter      /* The target dir might have multiple components.  Guarantee the path
845251881Speter         leading down to the last component. */
846251881Speter      SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath,
847251881Speter                                                             scratch_pool),
848251881Speter                                          scratch_pool));
849251881Speter    }
850251881Speter
851251881Speter  switch (ext_kind)
852251881Speter    {
853251881Speter      case svn_node_dir:
854262253Speter        SVN_ERR(switch_dir_external(local_abspath, new_loc->url,
855289166Speter                                    new_item->url,
856251881Speter                                    &(new_item->peg_revision),
857251881Speter                                    &(new_item->revision),
858251881Speter                                    parent_dir_abspath,
859299742Sdim                                    timestamp_sleep, ra_session, ctx,
860251881Speter                                    scratch_pool));
861251881Speter        break;
862251881Speter      case svn_node_file:
863251881Speter        if (strcmp(repos_root_url, new_loc->repos_root_url))
864251881Speter          {
865251881Speter            const char *local_repos_root_url;
866251881Speter            const char *local_repos_uuid;
867251881Speter            const char *ext_repos_relpath;
868251881Speter            svn_error_t *err;
869251881Speter
870251881Speter            /*
871251881Speter             * The working copy library currently requires that all files
872251881Speter             * in the working copy have the same repository root URL.
873251881Speter             * The URL from the file external's definition differs from the
874251881Speter             * one used by the working copy. As a workaround, replace the
875251881Speter             * root URL portion of the file external's URL, after making
876251881Speter             * sure both URLs point to the same repository. See issue #4087.
877251881Speter             */
878251881Speter
879251881Speter            err = svn_wc__node_get_repos_info(NULL, NULL,
880251881Speter                                              &local_repos_root_url,
881251881Speter                                              &local_repos_uuid,
882251881Speter                                              ctx->wc_ctx, parent_dir_abspath,
883251881Speter                                              scratch_pool, scratch_pool);
884251881Speter            if (err)
885251881Speter              {
886251881Speter                if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND
887251881Speter                    && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
888251881Speter                  return svn_error_trace(err);
889251881Speter
890251881Speter                svn_error_clear(err);
891251881Speter                local_repos_root_url = NULL;
892251881Speter                local_repos_uuid = NULL;
893251881Speter              }
894251881Speter
895251881Speter            ext_repos_relpath = svn_uri_skip_ancestor(new_loc->repos_root_url,
896251881Speter                                                      new_url, scratch_pool);
897251881Speter            if (local_repos_uuid == NULL || local_repos_root_url == NULL ||
898251881Speter                ext_repos_relpath == NULL ||
899251881Speter                strcmp(local_repos_uuid, new_loc->repos_uuid) != 0)
900251881Speter              return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
901251881Speter                        _("Unsupported external: URL of file external '%s' "
902251881Speter                          "is not in repository '%s'"),
903251881Speter                        new_url, repos_root_url);
904251881Speter
905251881Speter            new_url = svn_path_url_add_component2(local_repos_root_url,
906251881Speter                                                  ext_repos_relpath,
907251881Speter                                                  scratch_pool);
908251881Speter            SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc,
909251881Speter                                                      new_url,
910251881Speter                                                      NULL,
911251881Speter                                                      &(new_item->peg_revision),
912251881Speter                                                      &(new_item->revision),
913251881Speter                                                      ctx, scratch_pool));
914251881Speter          }
915251881Speter
916251881Speter        SVN_ERR(switch_file_external(local_abspath,
917299742Sdim                                     new_loc,
918251881Speter                                     new_url,
919251881Speter                                     &new_item->peg_revision,
920251881Speter                                     &new_item->revision,
921251881Speter                                     parent_dir_abspath,
922251881Speter                                     ra_session,
923251881Speter                                     ctx,
924251881Speter                                     scratch_pool));
925251881Speter        break;
926251881Speter
927251881Speter      default:
928251881Speter        SVN_ERR_MALFUNCTION();
929251881Speter        break;
930251881Speter    }
931251881Speter
932251881Speter  return SVN_NO_ERROR;
933251881Speter}
934251881Speter
935251881Speterstatic svn_error_t *
936251881Speterwrap_external_error(const svn_client_ctx_t *ctx,
937251881Speter                    const char *target_abspath,
938251881Speter                    svn_error_t *err,
939251881Speter                    apr_pool_t *scratch_pool)
940251881Speter{
941251881Speter  if (err && err->apr_err != SVN_ERR_CANCELLED)
942251881Speter    {
943251881Speter      if (ctx->notify_func2)
944251881Speter        {
945251881Speter          svn_wc_notify_t *notifier = svn_wc_create_notify(
946251881Speter                                            target_abspath,
947251881Speter                                            svn_wc_notify_failed_external,
948251881Speter                                            scratch_pool);
949251881Speter          notifier->err = err;
950251881Speter          ctx->notify_func2(ctx->notify_baton2, notifier, scratch_pool);
951251881Speter        }
952251881Speter      svn_error_clear(err);
953251881Speter      return SVN_NO_ERROR;
954251881Speter    }
955251881Speter
956251881Speter  return err;
957251881Speter}
958251881Speter
959251881Speterstatic svn_error_t *
960251881Speterhandle_externals_change(svn_client_ctx_t *ctx,
961251881Speter                        const char *repos_root_url,
962251881Speter                        svn_boolean_t *timestamp_sleep,
963251881Speter                        const char *local_abspath,
964251881Speter                        const char *new_desc_text,
965251881Speter                        apr_hash_t *old_externals,
966251881Speter                        svn_depth_t ambient_depth,
967251881Speter                        svn_depth_t requested_depth,
968299742Sdim                        svn_ra_session_t *ra_session,
969251881Speter                        apr_pool_t *scratch_pool)
970251881Speter{
971251881Speter  apr_array_header_t *new_desc;
972251881Speter  int i;
973251881Speter  apr_pool_t *iterpool;
974251881Speter  const char *url;
975251881Speter
976251881Speter  iterpool = svn_pool_create(scratch_pool);
977251881Speter
978251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
979251881Speter
980251881Speter  /* Bag out if the depth here is too shallow for externals action. */
981251881Speter  if ((requested_depth < svn_depth_infinity
982251881Speter       && requested_depth != svn_depth_unknown)
983251881Speter      || (ambient_depth < svn_depth_infinity
984251881Speter          && requested_depth < svn_depth_infinity))
985251881Speter    return SVN_NO_ERROR;
986251881Speter
987251881Speter  if (new_desc_text)
988251881Speter    SVN_ERR(svn_wc_parse_externals_description3(&new_desc, local_abspath,
989251881Speter                                                new_desc_text,
990251881Speter                                                FALSE, scratch_pool));
991251881Speter  else
992251881Speter    new_desc = NULL;
993251881Speter
994251881Speter  SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, local_abspath,
995251881Speter                               scratch_pool, iterpool));
996251881Speter
997251881Speter  SVN_ERR_ASSERT(url);
998251881Speter
999251881Speter  for (i = 0; new_desc && (i < new_desc->nelts); i++)
1000251881Speter    {
1001251881Speter      const char *old_defining_abspath;
1002251881Speter      svn_wc_external_item2_t *new_item;
1003251881Speter      const char *target_abspath;
1004251881Speter      svn_boolean_t under_root;
1005251881Speter
1006251881Speter      new_item = APR_ARRAY_IDX(new_desc, i, svn_wc_external_item2_t *);
1007251881Speter
1008251881Speter      svn_pool_clear(iterpool);
1009251881Speter
1010251881Speter      if (ctx->cancel_func)
1011251881Speter        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
1012251881Speter
1013251881Speter      SVN_ERR(svn_dirent_is_under_root(&under_root, &target_abspath,
1014251881Speter                                       local_abspath, new_item->target_dir,
1015251881Speter                                       iterpool));
1016251881Speter
1017251881Speter      if (! under_root)
1018251881Speter        {
1019251881Speter          return svn_error_createf(
1020251881Speter                    SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1021251881Speter                    _("Path '%s' is not in the working copy"),
1022251881Speter                    svn_dirent_local_style(
1023251881Speter                        svn_dirent_join(local_abspath, new_item->target_dir,
1024251881Speter                                        iterpool),
1025251881Speter                        iterpool));
1026251881Speter        }
1027251881Speter
1028251881Speter      old_defining_abspath = svn_hash_gets(old_externals, target_abspath);
1029251881Speter
1030251881Speter      SVN_ERR(wrap_external_error(
1031251881Speter                      ctx, target_abspath,
1032251881Speter                      handle_external_item_change(ctx,
1033251881Speter                                                  repos_root_url,
1034251881Speter                                                  local_abspath, url,
1035251881Speter                                                  target_abspath,
1036251881Speter                                                  old_defining_abspath,
1037299742Sdim                                                  new_item, ra_session,
1038251881Speter                                                  timestamp_sleep,
1039251881Speter                                                  iterpool),
1040251881Speter                      iterpool));
1041251881Speter
1042251881Speter      /* And remove already processed items from the to-remove hash */
1043251881Speter      if (old_defining_abspath)
1044251881Speter        svn_hash_sets(old_externals, target_abspath, NULL);
1045251881Speter    }
1046251881Speter
1047251881Speter  svn_pool_destroy(iterpool);
1048251881Speter
1049251881Speter  return SVN_NO_ERROR;
1050251881Speter}
1051251881Speter
1052251881Speter
1053251881Spetersvn_error_t *
1054251881Spetersvn_client__handle_externals(apr_hash_t *externals_new,
1055251881Speter                             apr_hash_t *ambient_depths,
1056251881Speter                             const char *repos_root_url,
1057251881Speter                             const char *target_abspath,
1058251881Speter                             svn_depth_t requested_depth,
1059251881Speter                             svn_boolean_t *timestamp_sleep,
1060299742Sdim                             svn_ra_session_t *ra_session,
1061251881Speter                             svn_client_ctx_t *ctx,
1062251881Speter                             apr_pool_t *scratch_pool)
1063251881Speter{
1064251881Speter  apr_hash_t *old_external_defs;
1065251881Speter  apr_hash_index_t *hi;
1066251881Speter  apr_pool_t *iterpool;
1067251881Speter
1068251881Speter  SVN_ERR_ASSERT(repos_root_url);
1069251881Speter
1070251881Speter  iterpool = svn_pool_create(scratch_pool);
1071251881Speter
1072251881Speter  SVN_ERR(svn_wc__externals_defined_below(&old_external_defs,
1073251881Speter                                          ctx->wc_ctx, target_abspath,
1074251881Speter                                          scratch_pool, iterpool));
1075251881Speter
1076251881Speter  for (hi = apr_hash_first(scratch_pool, externals_new);
1077251881Speter       hi;
1078251881Speter       hi = apr_hash_next(hi))
1079251881Speter    {
1080299742Sdim      const char *local_abspath = apr_hash_this_key(hi);
1081299742Sdim      const char *desc_text = apr_hash_this_val(hi);
1082251881Speter      svn_depth_t ambient_depth = svn_depth_infinity;
1083251881Speter
1084251881Speter      svn_pool_clear(iterpool);
1085251881Speter
1086251881Speter      if (ambient_depths)
1087251881Speter        {
1088251881Speter          const char *ambient_depth_w;
1089251881Speter
1090251881Speter          ambient_depth_w = apr_hash_get(ambient_depths, local_abspath,
1091299742Sdim                                         apr_hash_this_key_len(hi));
1092251881Speter
1093251881Speter          if (ambient_depth_w == NULL)
1094251881Speter            {
1095251881Speter              return svn_error_createf(
1096251881Speter                        SVN_ERR_WC_CORRUPT, NULL,
1097251881Speter                        _("Traversal of '%s' found no ambient depth"),
1098251881Speter                        svn_dirent_local_style(local_abspath, scratch_pool));
1099251881Speter            }
1100251881Speter          else
1101251881Speter            {
1102251881Speter              ambient_depth = svn_depth_from_word(ambient_depth_w);
1103251881Speter            }
1104251881Speter        }
1105251881Speter
1106251881Speter      SVN_ERR(handle_externals_change(ctx, repos_root_url, timestamp_sleep,
1107251881Speter                                      local_abspath,
1108251881Speter                                      desc_text, old_external_defs,
1109251881Speter                                      ambient_depth, requested_depth,
1110299742Sdim                                      ra_session, iterpool));
1111251881Speter    }
1112251881Speter
1113251881Speter  /* Remove the remaining externals */
1114251881Speter  for (hi = apr_hash_first(scratch_pool, old_external_defs);
1115251881Speter       hi;
1116251881Speter       hi = apr_hash_next(hi))
1117251881Speter    {
1118299742Sdim      const char *item_abspath = apr_hash_this_key(hi);
1119299742Sdim      const char *defining_abspath = apr_hash_this_val(hi);
1120251881Speter      const char *parent_abspath;
1121251881Speter
1122251881Speter      svn_pool_clear(iterpool);
1123251881Speter
1124251881Speter      SVN_ERR(wrap_external_error(
1125251881Speter                          ctx, item_abspath,
1126251881Speter                          handle_external_item_removal(ctx, defining_abspath,
1127251881Speter                                                       item_abspath, iterpool),
1128251881Speter                          iterpool));
1129251881Speter
1130251881Speter      /* Are there any unversioned directories between the removed
1131251881Speter       * external and the DEFINING_ABSPATH which we can remove? */
1132251881Speter      parent_abspath = item_abspath;
1133251881Speter      do {
1134251881Speter        svn_node_kind_t kind;
1135251881Speter
1136251881Speter        parent_abspath = svn_dirent_dirname(parent_abspath, iterpool);
1137251881Speter        SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, parent_abspath,
1138269847Speter                                  FALSE /* show_deleted*/,
1139269847Speter                                  FALSE /* show_hidden */,
1140269847Speter                                  iterpool));
1141251881Speter        if (kind == svn_node_none)
1142251881Speter          {
1143251881Speter            svn_error_t *err;
1144251881Speter
1145251881Speter            err = svn_io_dir_remove_nonrecursive(parent_abspath, iterpool);
1146269847Speter            if (err)
1147251881Speter              {
1148269847Speter                if (APR_STATUS_IS_ENOTEMPTY(err->apr_err))
1149269847Speter                  {
1150269847Speter                    svn_error_clear(err);
1151269847Speter                    break; /* No parents to delete */
1152269847Speter                  }
1153269847Speter                else if (APR_STATUS_IS_ENOENT(err->apr_err)
1154269847Speter                         || APR_STATUS_IS_ENOTDIR(err->apr_err))
1155269847Speter                  {
1156269847Speter                    svn_error_clear(err);
1157269847Speter                    /* Fall through; parent dir might be unversioned */
1158269847Speter                  }
1159269847Speter                else
1160269847Speter                  return svn_error_trace(err);
1161251881Speter              }
1162251881Speter          }
1163251881Speter      } while (strcmp(parent_abspath, defining_abspath) != 0);
1164251881Speter    }
1165251881Speter
1166251881Speter
1167251881Speter  svn_pool_destroy(iterpool);
1168251881Speter  return SVN_NO_ERROR;
1169251881Speter}
1170251881Speter
1171251881Speter
1172251881Spetersvn_error_t *
1173251881Spetersvn_client__export_externals(apr_hash_t *externals,
1174251881Speter                             const char *from_url,
1175251881Speter                             const char *to_abspath,
1176251881Speter                             const char *repos_root_url,
1177251881Speter                             svn_depth_t requested_depth,
1178251881Speter                             const char *native_eol,
1179251881Speter                             svn_boolean_t ignore_keywords,
1180251881Speter                             svn_client_ctx_t *ctx,
1181251881Speter                             apr_pool_t *scratch_pool)
1182251881Speter{
1183251881Speter  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1184251881Speter  apr_pool_t *sub_iterpool = svn_pool_create(scratch_pool);
1185251881Speter  apr_hash_index_t *hi;
1186251881Speter
1187251881Speter  SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath));
1188251881Speter
1189251881Speter  for (hi = apr_hash_first(scratch_pool, externals);
1190251881Speter       hi;
1191251881Speter       hi = apr_hash_next(hi))
1192251881Speter    {
1193299742Sdim      const char *local_abspath = apr_hash_this_key(hi);
1194299742Sdim      const char *desc_text = apr_hash_this_val(hi);
1195251881Speter      const char *local_relpath;
1196251881Speter      const char *dir_url;
1197251881Speter      apr_array_header_t *items;
1198251881Speter      int i;
1199251881Speter
1200251881Speter      svn_pool_clear(iterpool);
1201251881Speter
1202251881Speter      SVN_ERR(svn_wc_parse_externals_description3(&items, local_abspath,
1203251881Speter                                                  desc_text, FALSE,
1204251881Speter                                                  iterpool));
1205251881Speter
1206251881Speter      if (! items->nelts)
1207251881Speter        continue;
1208251881Speter
1209251881Speter      local_relpath = svn_dirent_skip_ancestor(to_abspath, local_abspath);
1210251881Speter
1211251881Speter      dir_url = svn_path_url_add_component2(from_url, local_relpath,
1212251881Speter                                            scratch_pool);
1213251881Speter
1214251881Speter      for (i = 0; i < items->nelts; i++)
1215251881Speter        {
1216251881Speter          const char *item_abspath;
1217251881Speter          const char *new_url;
1218251881Speter          svn_boolean_t under_root;
1219251881Speter          svn_wc_external_item2_t *item = APR_ARRAY_IDX(items, i,
1220251881Speter                                                svn_wc_external_item2_t *);
1221251881Speter
1222251881Speter          svn_pool_clear(sub_iterpool);
1223251881Speter
1224251881Speter          SVN_ERR(svn_dirent_is_under_root(&under_root, &item_abspath,
1225251881Speter                                           local_abspath, item->target_dir,
1226251881Speter                                           sub_iterpool));
1227251881Speter
1228251881Speter          if (! under_root)
1229251881Speter            {
1230251881Speter              return svn_error_createf(
1231251881Speter                        SVN_ERR_WC_OBSTRUCTED_UPDATE, NULL,
1232251881Speter                        _("Path '%s' is not in the working copy"),
1233251881Speter                        svn_dirent_local_style(
1234251881Speter                            svn_dirent_join(local_abspath, item->target_dir,
1235251881Speter                                            sub_iterpool),
1236251881Speter                            sub_iterpool));
1237251881Speter            }
1238251881Speter
1239251881Speter          SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, item,
1240251881Speter                                                        repos_root_url,
1241251881Speter                                                        dir_url, sub_iterpool,
1242251881Speter                                                        sub_iterpool));
1243251881Speter
1244251881Speter          /* The target dir might have multiple components.  Guarantee
1245251881Speter             the path leading down to the last component. */
1246251881Speter          SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(item_abspath,
1247251881Speter                                                                 sub_iterpool),
1248251881Speter                                              sub_iterpool));
1249251881Speter
1250299742Sdim          /* First notify that we're about to handle an external. */
1251299742Sdim          if (ctx->notify_func2)
1252299742Sdim            {
1253299742Sdim              ctx->notify_func2(
1254299742Sdim                       ctx->notify_baton2,
1255299742Sdim                       svn_wc_create_notify(item_abspath,
1256299742Sdim                                            svn_wc_notify_update_external,
1257299742Sdim                                            sub_iterpool),
1258299742Sdim                       sub_iterpool);
1259299742Sdim            }
1260299742Sdim
1261251881Speter          SVN_ERR(wrap_external_error(
1262251881Speter                          ctx, item_abspath,
1263251881Speter                          svn_client_export5(NULL, new_url, item_abspath,
1264251881Speter                                             &item->peg_revision,
1265251881Speter                                             &item->revision,
1266251881Speter                                             TRUE, FALSE, ignore_keywords,
1267251881Speter                                             svn_depth_infinity,
1268251881Speter                                             native_eol,
1269251881Speter                                             ctx, sub_iterpool),
1270251881Speter                          sub_iterpool));
1271251881Speter        }
1272251881Speter    }
1273251881Speter
1274251881Speter  svn_pool_destroy(sub_iterpool);
1275251881Speter  svn_pool_destroy(iterpool);
1276251881Speter
1277251881Speter  return SVN_NO_ERROR;
1278251881Speter}
1279251881Speter
1280