1251881Speter/*
2251881Speter * switch.c:  implement 'switch' feature via WC & RA interfaces.
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 "svn_client.h"
31251881Speter#include "svn_error.h"
32251881Speter#include "svn_hash.h"
33251881Speter#include "svn_time.h"
34251881Speter#include "svn_dirent_uri.h"
35251881Speter#include "svn_path.h"
36251881Speter#include "svn_config.h"
37251881Speter#include "svn_pools.h"
38251881Speter#include "client.h"
39251881Speter
40251881Speter#include "svn_private_config.h"
41251881Speter#include "private/svn_wc_private.h"
42251881Speter
43251881Speter
44251881Speter/*** Code. ***/
45251881Speter
46251881Speter/* This feature is essentially identical to 'svn update' (see
47251881Speter   ./update.c), but with two differences:
48251881Speter
49251881Speter     - the reporter->finish_report() routine needs to make the server
50251881Speter       run delta_dirs() on two *different* paths, rather than on two
51251881Speter       identical paths.
52251881Speter
53251881Speter     - after the update runs, we need to more than just
54251881Speter       ensure_uniform_revision;  we need to rewrite all the entries'
55251881Speter       URL attributes.
56251881Speter*/
57251881Speter
58251881Speter
59251881Speter/* A conflict callback that simply records the conflicted path in BATON.
60251881Speter
61251881Speter   Implements svn_wc_conflict_resolver_func2_t.
62251881Speter*/
63251881Speterstatic svn_error_t *
64251881Speterrecord_conflict(svn_wc_conflict_result_t **result,
65251881Speter                const svn_wc_conflict_description2_t *description,
66251881Speter                void *baton,
67251881Speter                apr_pool_t *result_pool,
68251881Speter                apr_pool_t *scratch_pool)
69251881Speter{
70251881Speter  apr_hash_t *conflicted_paths = baton;
71251881Speter
72251881Speter  svn_hash_sets(conflicted_paths,
73251881Speter                apr_pstrdup(apr_hash_pool_get(conflicted_paths),
74251881Speter                            description->local_abspath), "");
75251881Speter  *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
76251881Speter                                          NULL, result_pool);
77251881Speter  return SVN_NO_ERROR;
78251881Speter}
79251881Speter
80251881Speter/* ...
81251881Speter
82251881Speter   Add the paths of any conflict victims to CONFLICTED_PATHS, if that
83251881Speter   is not null.
84251881Speter*/
85251881Speterstatic svn_error_t *
86251881Speterswitch_internal(svn_revnum_t *result_rev,
87251881Speter                apr_hash_t *conflicted_paths,
88251881Speter                const char *local_abspath,
89251881Speter                const char *anchor_abspath,
90251881Speter                const char *switch_url,
91251881Speter                const svn_opt_revision_t *peg_revision,
92251881Speter                const svn_opt_revision_t *revision,
93251881Speter                svn_depth_t depth,
94251881Speter                svn_boolean_t depth_is_sticky,
95251881Speter                svn_boolean_t ignore_externals,
96251881Speter                svn_boolean_t allow_unver_obstructions,
97251881Speter                svn_boolean_t ignore_ancestry,
98251881Speter                svn_boolean_t *timestamp_sleep,
99251881Speter                svn_client_ctx_t *ctx,
100251881Speter                apr_pool_t *pool)
101251881Speter{
102251881Speter  const svn_ra_reporter3_t *reporter;
103251881Speter  void *report_baton;
104251881Speter  const char *anchor_url, *target;
105251881Speter  svn_client__pathrev_t *switch_loc;
106251881Speter  svn_ra_session_t *ra_session;
107251881Speter  svn_revnum_t revnum;
108251881Speter  const char *diff3_cmd;
109251881Speter  apr_hash_t *wcroot_iprops;
110251881Speter  apr_array_header_t *inherited_props;
111251881Speter  svn_boolean_t use_commit_times;
112251881Speter  const svn_delta_editor_t *switch_editor;
113251881Speter  void *switch_edit_baton;
114251881Speter  const char *preserved_exts_str;
115251881Speter  apr_array_header_t *preserved_exts;
116251881Speter  svn_boolean_t server_supports_depth;
117251881Speter  struct svn_client__dirent_fetcher_baton_t dfb;
118251881Speter  svn_config_t *cfg = ctx->config
119251881Speter                      ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
120251881Speter                      : NULL;
121251881Speter
122251881Speter  /* An unknown depth can't be sticky. */
123251881Speter  if (depth == svn_depth_unknown)
124251881Speter    depth_is_sticky = FALSE;
125251881Speter
126251881Speter  /* Do not support the situation of both exclude and switch a target. */
127251881Speter  if (depth == svn_depth_exclude)
128251881Speter    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
129251881Speter                             _("Cannot both exclude and switch a path"));
130251881Speter
131251881Speter  /* Get the external diff3, if any. */
132251881Speter  svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
133251881Speter                 SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
134251881Speter
135251881Speter  if (diff3_cmd != NULL)
136251881Speter    SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool));
137251881Speter
138251881Speter  /* See if the user wants last-commit timestamps instead of current ones. */
139251881Speter  SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
140251881Speter                              SVN_CONFIG_SECTION_MISCELLANY,
141251881Speter                              SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
142251881Speter
143251881Speter  {
144251881Speter    svn_boolean_t has_working;
145251881Speter    SVN_ERR(svn_wc__node_has_working(&has_working, ctx->wc_ctx, local_abspath,
146251881Speter                                     pool));
147251881Speter
148251881Speter    if (has_working)
149251881Speter      return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
150251881Speter                               _("Cannot switch '%s' because it is not in the "
151251881Speter                                 "repository yet"),
152251881Speter                               svn_dirent_local_style(local_abspath, pool));
153251881Speter  }
154251881Speter
155251881Speter  /* See which files the user wants to preserve the extension of when
156251881Speter     conflict files are made. */
157251881Speter  svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
158251881Speter                 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
159251881Speter  preserved_exts = *preserved_exts_str
160251881Speter    ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool)
161251881Speter    : NULL;
162251881Speter
163251881Speter  /* Sanity check.  Without these, the switch is meaningless. */
164251881Speter  SVN_ERR_ASSERT(switch_url && (switch_url[0] != '\0'));
165251881Speter
166251881Speter  if (strcmp(local_abspath, anchor_abspath))
167251881Speter    target = svn_dirent_basename(local_abspath, pool);
168251881Speter  else
169251881Speter    target = "";
170251881Speter
171251881Speter  SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath,
172251881Speter                               pool, pool));
173251881Speter  if (! anchor_url)
174251881Speter    return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL,
175251881Speter                             _("Directory '%s' has no URL"),
176251881Speter                             svn_dirent_local_style(anchor_abspath, pool));
177251881Speter
178251881Speter  /* We may need to crop the tree if the depth is sticky */
179251881Speter  if (depth_is_sticky && depth < svn_depth_infinity)
180251881Speter    {
181251881Speter      svn_node_kind_t target_kind;
182251881Speter
183251881Speter      if (depth == svn_depth_exclude)
184251881Speter        {
185251881Speter          SVN_ERR(svn_wc_exclude(ctx->wc_ctx,
186251881Speter                                 local_abspath,
187251881Speter                                 ctx->cancel_func, ctx->cancel_baton,
188251881Speter                                 ctx->notify_func2, ctx->notify_baton2,
189251881Speter                                 pool));
190251881Speter
191251881Speter          /* Target excluded, we are done now */
192251881Speter          return SVN_NO_ERROR;
193251881Speter        }
194251881Speter
195251881Speter      SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath,
196251881Speter                                TRUE, TRUE, pool));
197251881Speter
198251881Speter      if (target_kind == svn_node_dir)
199251881Speter        SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth,
200251881Speter                                  ctx->cancel_func, ctx->cancel_baton,
201251881Speter                                  ctx->notify_func2, ctx->notify_baton2,
202251881Speter                                  pool));
203251881Speter    }
204251881Speter
205251881Speter  /* Open an RA session to 'source' URL */
206251881Speter  SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc,
207251881Speter                                            switch_url, anchor_abspath,
208251881Speter                                            peg_revision, revision,
209251881Speter                                            ctx, pool));
210251881Speter
211251881Speter  /* Disallow a switch operation to change the repository root of the
212251881Speter     target. */
213251881Speter  if (! svn_uri__is_ancestor(switch_loc->repos_root_url, anchor_url))
214251881Speter    return svn_error_createf(SVN_ERR_WC_INVALID_SWITCH, NULL,
215251881Speter                             _("'%s'\nis not the same repository as\n'%s'"),
216251881Speter                             anchor_url, switch_loc->repos_root_url);
217251881Speter
218251881Speter  /* If we're not ignoring ancestry, then error out if the switch
219251881Speter     source and target don't have a common ancestory.
220251881Speter
221251881Speter     ### We're acting on the anchor here, not the target.  Is that
222251881Speter     ### okay? */
223251881Speter  if (! ignore_ancestry)
224251881Speter    {
225251881Speter      svn_client__pathrev_t *target_base_loc, *yca;
226251881Speter
227251881Speter      SVN_ERR(svn_client__wc_node_get_base(&target_base_loc, local_abspath,
228251881Speter                                           ctx->wc_ctx, pool, pool));
229251881Speter
230251881Speter      if (!target_base_loc)
231251881Speter        yca = NULL; /* Not versioned */
232251881Speter      else
233251881Speter        {
234251881Speter          SVN_ERR(svn_client__get_youngest_common_ancestor(
235251881Speter                  &yca, switch_loc, target_base_loc, ra_session, ctx,
236251881Speter                  pool, pool));
237251881Speter        }
238251881Speter      if (! yca)
239251881Speter        return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
240251881Speter                                 _("'%s' shares no common ancestry with '%s'"),
241251881Speter                                 switch_url,
242253734Speter                                 svn_dirent_local_style(local_abspath, pool));
243251881Speter    }
244251881Speter
245251881Speter  wcroot_iprops = apr_hash_make(pool);
246251881Speter
247251881Speter  /* Will the base of LOCAL_ABSPATH require an iprop cache post-switch?
248251881Speter     If we are switching LOCAL_ABSPATH to the root of the repository then
249251881Speter     we don't need to cache inherited properties.  In all other cases we
250251881Speter     *might* need to cache iprops. */
251251881Speter  if (strcmp(switch_loc->repos_root_url, switch_loc->url) != 0)
252251881Speter    {
253251881Speter      svn_boolean_t wc_root;
254251881Speter      svn_boolean_t needs_iprop_cache = TRUE;
255251881Speter
256251881Speter      SVN_ERR(svn_wc__is_wcroot(&wc_root, ctx->wc_ctx, local_abspath,
257251881Speter                                pool));
258251881Speter
259251881Speter      /* Switching the WC root to anything but the repos root means
260251881Speter         we need an iprop cache. */
261251881Speter      if (!wc_root)
262251881Speter        {
263251881Speter          /* We know we are switching a subtree to something other than the
264251881Speter             repos root, but if we are unswitching that subtree we don't
265251881Speter             need an iprops cache. */
266251881Speter          const char *target_parent_url;
267251881Speter          const char *unswitched_url;
268251881Speter
269251881Speter          /* Calculate the URL LOCAL_ABSPATH would have if it was unswitched
270251881Speter             relative to its parent. */
271251881Speter          SVN_ERR(svn_wc__node_get_url(&target_parent_url, ctx->wc_ctx,
272251881Speter                                       svn_dirent_dirname(local_abspath,
273251881Speter                                                          pool),
274251881Speter                                       pool, pool));
275251881Speter          unswitched_url = svn_path_url_add_component2(
276251881Speter            target_parent_url,
277251881Speter            svn_dirent_basename(local_abspath, pool),
278251881Speter            pool);
279251881Speter
280251881Speter          /* If LOCAL_ABSPATH will be unswitched relative to its parent, then
281251881Speter             it doesn't need an iprop cache.  Note: It doesn't matter if
282251881Speter             LOCAL_ABSPATH is withing a switched subtree, only if it's the
283251881Speter             *root* of a switched subtree.*/
284251881Speter          if (strcmp(unswitched_url, switch_loc->url) == 0)
285251881Speter            needs_iprop_cache = FALSE;
286251881Speter      }
287251881Speter
288251881Speter
289251881Speter      if (needs_iprop_cache)
290251881Speter        {
291251881Speter          SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props,
292251881Speter                                             "", switch_loc->rev, pool,
293251881Speter                                             pool));
294251881Speter          svn_hash_sets(wcroot_iprops, local_abspath, inherited_props);
295251881Speter        }
296251881Speter    }
297251881Speter
298251881Speter  SVN_ERR(svn_ra_reparent(ra_session, anchor_url, pool));
299251881Speter
300251881Speter  /* Fetch the switch (update) editor.  If REVISION is invalid, that's
301251881Speter     okay; the RA driver will call editor->set_target_revision() later on. */
302251881Speter  SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
303251881Speter                                SVN_RA_CAPABILITY_DEPTH, pool));
304251881Speter
305251881Speter  dfb.ra_session = ra_session;
306251881Speter  dfb.anchor_url = anchor_url;
307251881Speter  dfb.target_revision = switch_loc->rev;
308251881Speter
309251881Speter  SVN_ERR(svn_wc__get_switch_editor(&switch_editor, &switch_edit_baton,
310251881Speter                                    &revnum, ctx->wc_ctx, anchor_abspath,
311251881Speter                                    target, switch_loc->url, wcroot_iprops,
312251881Speter                                    use_commit_times, depth,
313251881Speter                                    depth_is_sticky, allow_unver_obstructions,
314251881Speter                                    server_supports_depth,
315251881Speter                                    diff3_cmd, preserved_exts,
316251881Speter                                    svn_client__dirent_fetcher, &dfb,
317251881Speter                                    conflicted_paths ? record_conflict : NULL,
318251881Speter                                    conflicted_paths,
319251881Speter                                    NULL, NULL,
320251881Speter                                    ctx->cancel_func, ctx->cancel_baton,
321251881Speter                                    ctx->notify_func2, ctx->notify_baton2,
322251881Speter                                    pool, pool));
323251881Speter
324251881Speter  /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
325251881Speter     invalid revnum, that means RA will use the latest revision. */
326251881Speter  SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton,
327251881Speter                            switch_loc->rev,
328251881Speter                            target,
329251881Speter                            depth_is_sticky ? depth : svn_depth_unknown,
330251881Speter                            switch_loc->url,
331251881Speter                            FALSE /* send_copyfrom_args */,
332251881Speter                            ignore_ancestry,
333251881Speter                            switch_editor, switch_edit_baton,
334251881Speter                            pool, pool));
335251881Speter
336251881Speter  /* Past this point, we assume the WC is going to be modified so we will
337251881Speter   * need to sleep for timestamps. */
338251881Speter  *timestamp_sleep = TRUE;
339251881Speter
340251881Speter  /* Drive the reporter structure, describing the revisions within
341289180Speter     LOCAL_ABSPATH.  When this calls reporter->finish_report, the
342289180Speter     reporter will drive the switch_editor. */
343251881Speter  SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter,
344251881Speter                                  report_baton, TRUE,
345251881Speter                                  depth, (! depth_is_sticky),
346251881Speter                                  (! server_supports_depth),
347251881Speter                                  use_commit_times,
348251881Speter                                  ctx->cancel_func, ctx->cancel_baton,
349251881Speter                                  ctx->notify_func2, ctx->notify_baton2,
350251881Speter                                  pool));
351251881Speter
352251881Speter  /* We handle externals after the switch is complete, so that
353251881Speter     handling external items (and any errors therefrom) doesn't delay
354251881Speter     the primary operation. */
355251881Speter  if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals))
356251881Speter    {
357251881Speter      apr_hash_t *new_externals;
358251881Speter      apr_hash_t *new_depths;
359251881Speter      SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
360251881Speter                                                   &new_depths,
361251881Speter                                                   ctx->wc_ctx, local_abspath,
362251881Speter                                                   depth, pool, pool));
363251881Speter
364251881Speter      SVN_ERR(svn_client__handle_externals(new_externals,
365251881Speter                                           new_depths,
366251881Speter                                           switch_loc->repos_root_url,
367251881Speter                                           local_abspath,
368289180Speter                                           depth, timestamp_sleep, ra_session,
369251881Speter                                           ctx, pool));
370251881Speter    }
371251881Speter
372251881Speter  /* Let everyone know we're finished here. */
373251881Speter  if (ctx->notify_func2)
374251881Speter    {
375251881Speter      svn_wc_notify_t *notify
376251881Speter        = svn_wc_create_notify(anchor_abspath, svn_wc_notify_update_completed,
377251881Speter                               pool);
378251881Speter      notify->kind = svn_node_none;
379251881Speter      notify->content_state = notify->prop_state
380251881Speter        = svn_wc_notify_state_inapplicable;
381251881Speter      notify->lock_state = svn_wc_notify_lock_state_inapplicable;
382251881Speter      notify->revision = revnum;
383289180Speter      ctx->notify_func2(ctx->notify_baton2, notify, pool);
384251881Speter    }
385251881Speter
386251881Speter  /* If the caller wants the result revision, give it to them. */
387251881Speter  if (result_rev)
388251881Speter    *result_rev = revnum;
389251881Speter
390251881Speter  return SVN_NO_ERROR;
391251881Speter}
392251881Speter
393251881Spetersvn_error_t *
394251881Spetersvn_client__switch_internal(svn_revnum_t *result_rev,
395251881Speter                            const char *path,
396251881Speter                            const char *switch_url,
397251881Speter                            const svn_opt_revision_t *peg_revision,
398251881Speter                            const svn_opt_revision_t *revision,
399251881Speter                            svn_depth_t depth,
400251881Speter                            svn_boolean_t depth_is_sticky,
401251881Speter                            svn_boolean_t ignore_externals,
402251881Speter                            svn_boolean_t allow_unver_obstructions,
403251881Speter                            svn_boolean_t ignore_ancestry,
404251881Speter                            svn_boolean_t *timestamp_sleep,
405251881Speter                            svn_client_ctx_t *ctx,
406251881Speter                            apr_pool_t *pool)
407251881Speter{
408251881Speter  const char *local_abspath, *anchor_abspath;
409251881Speter  svn_boolean_t acquired_lock;
410251881Speter  svn_error_t *err, *err1, *err2;
411251881Speter  apr_hash_t *conflicted_paths
412251881Speter    = ctx->conflict_func2 ? apr_hash_make(pool) : NULL;
413251881Speter
414251881Speter  SVN_ERR_ASSERT(path);
415251881Speter
416251881Speter  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, pool));
417251881Speter
418251881Speter  /* Rely on svn_wc__acquire_write_lock setting ANCHOR_ABSPATH even
419251881Speter     when it returns SVN_ERR_WC_LOCKED */
420251881Speter  err = svn_wc__acquire_write_lock(&anchor_abspath,
421251881Speter                                   ctx->wc_ctx, local_abspath, TRUE,
422251881Speter                                   pool, pool);
423251881Speter  if (err && err->apr_err != SVN_ERR_WC_LOCKED)
424251881Speter    return svn_error_trace(err);
425251881Speter
426251881Speter  acquired_lock = (err == SVN_NO_ERROR);
427251881Speter  svn_error_clear(err);
428251881Speter
429251881Speter  err1 = switch_internal(result_rev, conflicted_paths,
430251881Speter                         local_abspath, anchor_abspath,
431251881Speter                         switch_url, peg_revision, revision,
432251881Speter                         depth, depth_is_sticky,
433251881Speter                         ignore_externals,
434251881Speter                         allow_unver_obstructions, ignore_ancestry,
435251881Speter                         timestamp_sleep, ctx, pool);
436251881Speter
437251881Speter  /* Give the conflict resolver callback the opportunity to
438251881Speter   * resolve any conflicts that were raised. */
439251881Speter  if (! err1 && ctx->conflict_func2)
440251881Speter    {
441251881Speter      err1 = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool);
442251881Speter    }
443251881Speter
444251881Speter  if (acquired_lock)
445251881Speter    err2 = svn_wc__release_write_lock(ctx->wc_ctx, anchor_abspath, pool);
446251881Speter  else
447251881Speter    err2 = SVN_NO_ERROR;
448251881Speter
449251881Speter  return svn_error_compose_create(err1, err2);
450251881Speter}
451251881Speter
452251881Spetersvn_error_t *
453251881Spetersvn_client_switch3(svn_revnum_t *result_rev,
454251881Speter                   const char *path,
455251881Speter                   const char *switch_url,
456251881Speter                   const svn_opt_revision_t *peg_revision,
457251881Speter                   const svn_opt_revision_t *revision,
458251881Speter                   svn_depth_t depth,
459251881Speter                   svn_boolean_t depth_is_sticky,
460251881Speter                   svn_boolean_t ignore_externals,
461251881Speter                   svn_boolean_t allow_unver_obstructions,
462251881Speter                   svn_boolean_t ignore_ancestry,
463251881Speter                   svn_client_ctx_t *ctx,
464251881Speter                   apr_pool_t *pool)
465251881Speter{
466251881Speter  svn_error_t *err;
467251881Speter  svn_boolean_t sleep_here = FALSE;
468251881Speter
469251881Speter  if (svn_path_is_url(path))
470251881Speter    return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
471251881Speter                             _("'%s' is not a local path"), path);
472251881Speter
473251881Speter  err = svn_client__switch_internal(result_rev, path, switch_url,
474251881Speter                                    peg_revision, revision, depth,
475251881Speter                                    depth_is_sticky, ignore_externals,
476251881Speter                                    allow_unver_obstructions,
477251881Speter                                    ignore_ancestry, &sleep_here, ctx, pool);
478251881Speter
479251881Speter  /* Sleep to ensure timestamp integrity (we do this regardless of
480251881Speter     errors in the actual switch operation(s)). */
481251881Speter  if (sleep_here)
482251881Speter    svn_io_sleep_for_timestamps(path, pool);
483251881Speter
484251881Speter  return svn_error_trace(err);
485251881Speter}
486