update.c revision 257936
1/*
2 * update.c:  wrappers around wc update functionality
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* ==================================================================== */
25
26
27
28/*** Includes. ***/
29
30#include "svn_hash.h"
31#include "svn_wc.h"
32#include "svn_client.h"
33#include "svn_error.h"
34#include "svn_config.h"
35#include "svn_time.h"
36#include "svn_dirent_uri.h"
37#include "svn_path.h"
38#include "svn_pools.h"
39#include "svn_io.h"
40#include "client.h"
41
42#include "svn_private_config.h"
43#include "private/svn_wc_private.h"
44
45/* Implements svn_wc_dirents_func_t for update and switch handling. Assumes
46   a struct svn_client__dirent_fetcher_baton_t * baton */
47svn_error_t *
48svn_client__dirent_fetcher(void *baton,
49                           apr_hash_t **dirents,
50                           const char *repos_root_url,
51                           const char *repos_relpath,
52                           apr_pool_t *result_pool,
53                           apr_pool_t *scratch_pool)
54{
55  struct svn_client__dirent_fetcher_baton_t *dfb = baton;
56  const char *old_url = NULL;
57  const char *session_relpath;
58  svn_node_kind_t kind;
59  const char *url;
60
61  url = svn_path_url_add_component2(repos_root_url, repos_relpath,
62                                    scratch_pool);
63
64  if (!svn_uri__is_ancestor(dfb->anchor_url, url))
65    {
66      SVN_ERR(svn_client__ensure_ra_session_url(&old_url, dfb->ra_session,
67                                                url, scratch_pool));
68      session_relpath = "";
69    }
70  else
71    SVN_ERR(svn_ra_get_path_relative_to_session(dfb->ra_session,
72                                                &session_relpath, url,
73                                                scratch_pool));
74
75  /* Is session_relpath still a directory? */
76  SVN_ERR(svn_ra_check_path(dfb->ra_session, session_relpath,
77                            dfb->target_revision, &kind, scratch_pool));
78
79  if (kind == svn_node_dir)
80    SVN_ERR(svn_ra_get_dir2(dfb->ra_session, dirents, NULL, NULL,
81                            session_relpath, dfb->target_revision,
82                            SVN_DIRENT_KIND, result_pool));
83  else
84    *dirents = NULL;
85
86  if (old_url)
87    SVN_ERR(svn_ra_reparent(dfb->ra_session, old_url, scratch_pool));
88
89  return SVN_NO_ERROR;
90}
91
92
93/*** Code. ***/
94
95/* Set *CLEAN_CHECKOUT to FALSE only if LOCAL_ABSPATH is a non-empty
96   folder. ANCHOR_ABSPATH is the w/c root and LOCAL_ABSPATH will still
97   be considered empty, if it is equal to ANCHOR_ABSPATH and only
98   contains the admin sub-folder.
99   If the w/c folder already exists but cannot be openend, we return
100   "unclean" - just in case. Most likely, the caller will have to bail
101   out later due to the same error we got here.
102 */
103static svn_error_t *
104is_empty_wc(svn_boolean_t *clean_checkout,
105            const char *local_abspath,
106            const char *anchor_abspath,
107            apr_pool_t *pool)
108{
109  apr_dir_t *dir;
110  apr_finfo_t finfo;
111  svn_error_t *err;
112
113  /* "clean" until found dirty */
114  *clean_checkout = TRUE;
115
116  /* open directory. If it does not exist, yet, a clean one will
117     be created by the caller. */
118  err = svn_io_dir_open(&dir, local_abspath, pool);
119  if (err)
120    {
121      if (! APR_STATUS_IS_ENOENT(err->apr_err))
122        *clean_checkout = FALSE;
123
124      svn_error_clear(err);
125      return SVN_NO_ERROR;
126    }
127
128  for (err = svn_io_dir_read(&finfo, APR_FINFO_NAME, dir, pool);
129       err == SVN_NO_ERROR;
130       err = svn_io_dir_read(&finfo, APR_FINFO_NAME, dir, pool))
131    {
132      /* Ignore entries for this dir and its parent, robustly.
133         (APR promises that they'll come first, so technically
134         this guard could be moved outside the loop.  But Ryan Bloom
135         says he doesn't believe it, and I believe him. */
136      if (! (finfo.name[0] == '.'
137             && (finfo.name[1] == '\0'
138                 || (finfo.name[1] == '.' && finfo.name[2] == '\0'))))
139        {
140          if (   ! svn_wc_is_adm_dir(finfo.name, pool)
141              || strcmp(local_abspath, anchor_abspath) != 0)
142            {
143              *clean_checkout = FALSE;
144              break;
145            }
146        }
147    }
148
149  if (err)
150    {
151      if (! APR_STATUS_IS_ENOENT(err->apr_err))
152        {
153          /* There was some issue reading the folder content.
154           * We better disable optimizations in that case. */
155          *clean_checkout = FALSE;
156        }
157
158      svn_error_clear(err);
159    }
160
161  return svn_io_dir_close(dir);
162}
163
164/* A conflict callback that simply records the conflicted path in BATON.
165
166   Implements svn_wc_conflict_resolver_func2_t.
167*/
168static svn_error_t *
169record_conflict(svn_wc_conflict_result_t **result,
170                const svn_wc_conflict_description2_t *description,
171                void *baton,
172                apr_pool_t *result_pool,
173                apr_pool_t *scratch_pool)
174{
175  apr_hash_t *conflicted_paths = baton;
176
177  svn_hash_sets(conflicted_paths,
178                apr_pstrdup(apr_hash_pool_get(conflicted_paths),
179                            description->local_abspath), "");
180  *result = svn_wc_create_conflict_result(svn_wc_conflict_choose_postpone,
181                                          NULL, result_pool);
182  return SVN_NO_ERROR;
183}
184
185/* This is a helper for svn_client__update_internal(), which see for
186   an explanation of most of these parameters.  Some stuff that's
187   unique is as follows:
188
189   ANCHOR_ABSPATH is the local absolute path of the update anchor.
190   This is typically either the same as LOCAL_ABSPATH, or the
191   immediate parent of LOCAL_ABSPATH.
192
193   If NOTIFY_SUMMARY is set (and there's a notification handler in
194   CTX), transmit the final update summary upon successful
195   completion of the update.
196
197   Add the paths of any conflict victims to CONFLICTED_PATHS, if that
198   is not null.
199*/
200static svn_error_t *
201update_internal(svn_revnum_t *result_rev,
202                apr_hash_t *conflicted_paths,
203                const char *local_abspath,
204                const char *anchor_abspath,
205                const svn_opt_revision_t *revision,
206                svn_depth_t depth,
207                svn_boolean_t depth_is_sticky,
208                svn_boolean_t ignore_externals,
209                svn_boolean_t allow_unver_obstructions,
210                svn_boolean_t adds_as_modification,
211                svn_boolean_t *timestamp_sleep,
212                svn_boolean_t notify_summary,
213                svn_client_ctx_t *ctx,
214                apr_pool_t *pool)
215{
216  const svn_delta_editor_t *update_editor;
217  void *update_edit_baton;
218  const svn_ra_reporter3_t *reporter;
219  void *report_baton;
220  const char *corrected_url;
221  const char *target;
222  const char *repos_root_url;
223  const char *repos_relpath;
224  const char *repos_uuid;
225  const char *anchor_url;
226  svn_revnum_t revnum;
227  svn_boolean_t use_commit_times;
228  svn_boolean_t clean_checkout = FALSE;
229  const char *diff3_cmd;
230  apr_hash_t *wcroot_iprops;
231  svn_opt_revision_t opt_rev;
232  svn_ra_session_t *ra_session;
233  const char *preserved_exts_str;
234  apr_array_header_t *preserved_exts;
235  struct svn_client__dirent_fetcher_baton_t dfb;
236  svn_boolean_t server_supports_depth;
237  svn_boolean_t cropping_target;
238  svn_boolean_t target_conflicted = FALSE;
239  svn_config_t *cfg = ctx->config
240                      ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
241                      : NULL;
242
243  if (result_rev)
244    *result_rev = SVN_INVALID_REVNUM;
245
246  /* An unknown depth can't be sticky. */
247  if (depth == svn_depth_unknown)
248    depth_is_sticky = FALSE;
249
250  if (strcmp(local_abspath, anchor_abspath))
251    target = svn_dirent_basename(local_abspath, pool);
252  else
253    target = "";
254
255  /* Check if our anchor exists in BASE. If it doesn't we can't update. */
256  SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath, &repos_root_url,
257                                &repos_uuid, NULL,
258                                ctx->wc_ctx, anchor_abspath,
259                                TRUE, FALSE,
260                                pool, pool));
261
262  /* It does not make sense to update conflict victims. */
263  if (repos_relpath)
264    {
265      svn_error_t *err;
266      svn_boolean_t text_conflicted, prop_conflicted;
267
268      anchor_url = svn_path_url_add_component2(repos_root_url, repos_relpath,
269                                               pool);
270
271      err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted,
272                                 NULL,
273                                 ctx->wc_ctx, local_abspath, pool);
274
275      if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
276        return svn_error_trace(err);
277      svn_error_clear(err);
278
279      /* tree-conflicts are handled by the update editor */
280      if (!err && (text_conflicted || prop_conflicted))
281        target_conflicted = TRUE;
282    }
283  else
284    anchor_url = NULL;
285
286  if (! anchor_url || target_conflicted)
287    {
288      if (ctx->notify_func2)
289        {
290          svn_wc_notify_t *nt;
291
292          nt = svn_wc_create_notify(local_abspath,
293                                    target_conflicted
294                                      ? svn_wc_notify_skip_conflicted
295                                      : svn_wc_notify_update_skip_working_only,
296                                    pool);
297
298          ctx->notify_func2(ctx->notify_baton2, nt, pool);
299        }
300      return SVN_NO_ERROR;
301    }
302
303  /* We may need to crop the tree if the depth is sticky */
304  cropping_target = (depth_is_sticky && depth < svn_depth_infinity);
305  if (cropping_target)
306    {
307      svn_node_kind_t target_kind;
308
309      if (depth == svn_depth_exclude)
310        {
311          SVN_ERR(svn_wc_exclude(ctx->wc_ctx,
312                                 local_abspath,
313                                 ctx->cancel_func, ctx->cancel_baton,
314                                 ctx->notify_func2, ctx->notify_baton2,
315                                 pool));
316
317          /* Target excluded, we are done now */
318          return SVN_NO_ERROR;
319        }
320
321      SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath,
322                                TRUE, TRUE, pool));
323      if (target_kind == svn_node_dir)
324        {
325          SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth,
326                                    ctx->cancel_func, ctx->cancel_baton,
327                                    ctx->notify_func2, ctx->notify_baton2,
328                                    pool));
329        }
330    }
331
332  /* check whether the "clean c/o" optimization is applicable */
333  SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath, pool));
334
335  /* Get the external diff3, if any. */
336  svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
337                 SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
338
339  if (diff3_cmd != NULL)
340    SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool));
341
342  /* See if the user wants last-commit timestamps instead of current ones. */
343  SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
344                              SVN_CONFIG_SECTION_MISCELLANY,
345                              SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
346
347  /* See which files the user wants to preserve the extension of when
348     conflict files are made. */
349  svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
350                 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
351  preserved_exts = *preserved_exts_str
352    ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool)
353    : NULL;
354
355  /* Let everyone know we're starting a real update (unless we're
356     asked not to). */
357  if (ctx->notify_func2 && notify_summary)
358    {
359      svn_wc_notify_t *notify
360        = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started,
361                               pool);
362      notify->kind = svn_node_none;
363      notify->content_state = notify->prop_state
364        = svn_wc_notify_state_inapplicable;
365      notify->lock_state = svn_wc_notify_lock_state_inapplicable;
366      (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
367    }
368
369  /* Open an RA session for the URL */
370  SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
371                                               anchor_url,
372                                               anchor_abspath, NULL, TRUE,
373                                               TRUE, ctx, pool, pool));
374
375  /* If we got a corrected URL from the RA subsystem, we'll need to
376     relocate our working copy first. */
377  if (corrected_url)
378    {
379      const char *new_repos_root_url;
380
381      /* To relocate everything inside our repository we need the old and new
382         repos root. */
383      SVN_ERR(svn_ra_get_repos_root2(ra_session, &new_repos_root_url, pool));
384
385      /* svn_client_relocate2() will check the uuid */
386      SVN_ERR(svn_client_relocate2(anchor_abspath, anchor_url,
387                                   new_repos_root_url, ignore_externals,
388                                   ctx, pool));
389
390      /* Store updated repository root for externals */
391      repos_root_url = new_repos_root_url;
392      /* ### We should update anchor_loc->repos_uuid too, although currently
393       * we don't use it. */
394      anchor_url = corrected_url;
395    }
396
397  /* Resolve unspecified REVISION now, because we need to retrieve the
398     correct inherited props prior to the editor drive and we need to
399     use the same value of HEAD for both. */
400  opt_rev.kind = revision->kind;
401  opt_rev.value = revision->value;
402  if (opt_rev.kind == svn_opt_revision_unspecified)
403    opt_rev.kind = svn_opt_revision_head;
404
405  /* ### todo: shouldn't svn_client__get_revision_number be able
406     to take a URL as easily as a local path?  */
407  SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx,
408                                          local_abspath, ra_session, &opt_rev,
409                                          pool));
410
411  SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
412                                SVN_RA_CAPABILITY_DEPTH, pool));
413
414  dfb.ra_session = ra_session;
415  dfb.target_revision = revnum;
416  dfb.anchor_url = anchor_url;
417
418  SVN_ERR(svn_client__get_inheritable_props(&wcroot_iprops, local_abspath,
419                                            revnum, depth, ra_session,
420                                            ctx, pool, pool));
421
422  /* Fetch the update editor.  If REVISION is invalid, that's okay;
423     the RA driver will call editor->set_target_revision later on. */
424  SVN_ERR(svn_wc__get_update_editor(&update_editor, &update_edit_baton,
425                                    &revnum, ctx->wc_ctx, anchor_abspath,
426                                    target, wcroot_iprops, use_commit_times,
427                                    depth, depth_is_sticky,
428                                    allow_unver_obstructions,
429                                    adds_as_modification,
430                                    server_supports_depth,
431                                    clean_checkout,
432                                    diff3_cmd, preserved_exts,
433                                    svn_client__dirent_fetcher, &dfb,
434                                    conflicted_paths ? record_conflict : NULL,
435                                    conflicted_paths,
436                                    NULL, NULL,
437                                    ctx->cancel_func, ctx->cancel_baton,
438                                    ctx->notify_func2, ctx->notify_baton2,
439                                    pool, pool));
440
441  /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
442     invalid revnum, that means RA will use the latest revision.  */
443  SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &report_baton,
444                            revnum, target,
445                            (!server_supports_depth || depth_is_sticky
446                             ? depth
447                             : svn_depth_unknown),
448                            FALSE /* send_copyfrom_args */,
449                            FALSE /* ignore_ancestry */,
450                            update_editor, update_edit_baton, pool, pool));
451
452  /* Past this point, we assume the WC is going to be modified so we will
453   * need to sleep for timestamps. */
454  *timestamp_sleep = TRUE;
455
456  /* Drive the reporter structure, describing the revisions within
457     PATH.  When we call reporter->finish_report, the
458     update_editor will be driven by svn_repos_dir_delta2. */
459  SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter,
460                                  report_baton, TRUE,
461                                  depth, (! depth_is_sticky),
462                                  (! server_supports_depth),
463                                  use_commit_times,
464                                  ctx->cancel_func, ctx->cancel_baton,
465                                  ctx->notify_func2, ctx->notify_baton2,
466                                  pool));
467
468  /* We handle externals after the update is complete, so that
469     handling external items (and any errors therefrom) doesn't delay
470     the primary operation.  */
471  if ((SVN_DEPTH_IS_RECURSIVE(depth) || cropping_target)
472      && (! ignore_externals))
473    {
474      apr_hash_t *new_externals;
475      apr_hash_t *new_depths;
476      SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
477                                                   &new_depths,
478                                                   ctx->wc_ctx, local_abspath,
479                                                   depth, pool, pool));
480
481      SVN_ERR(svn_client__handle_externals(new_externals,
482                                           new_depths,
483                                           repos_root_url, local_abspath,
484                                           depth, timestamp_sleep,
485                                           ctx, pool));
486    }
487
488  /* Let everyone know we're finished here (unless we're asked not to). */
489  if (ctx->notify_func2 && notify_summary)
490    {
491      svn_wc_notify_t *notify
492        = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
493                               pool);
494      notify->kind = svn_node_none;
495      notify->content_state = notify->prop_state
496        = svn_wc_notify_state_inapplicable;
497      notify->lock_state = svn_wc_notify_lock_state_inapplicable;
498      notify->revision = revnum;
499      (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
500    }
501
502  /* If the caller wants the result revision, give it to them. */
503  if (result_rev)
504    *result_rev = revnum;
505
506  return SVN_NO_ERROR;
507}
508
509svn_error_t *
510svn_client__update_internal(svn_revnum_t *result_rev,
511                            const char *local_abspath,
512                            const svn_opt_revision_t *revision,
513                            svn_depth_t depth,
514                            svn_boolean_t depth_is_sticky,
515                            svn_boolean_t ignore_externals,
516                            svn_boolean_t allow_unver_obstructions,
517                            svn_boolean_t adds_as_modification,
518                            svn_boolean_t make_parents,
519                            svn_boolean_t innerupdate,
520                            svn_boolean_t *timestamp_sleep,
521                            svn_client_ctx_t *ctx,
522                            apr_pool_t *pool)
523{
524  const char *anchor_abspath, *lockroot_abspath;
525  svn_error_t *err;
526  svn_opt_revision_t peg_revision = *revision;
527  apr_hash_t *conflicted_paths
528    = ctx->conflict_func2 ? apr_hash_make(pool) : NULL;
529
530  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
531  SVN_ERR_ASSERT(! (innerupdate && make_parents));
532
533  if (make_parents)
534    {
535      int i;
536      const char *parent_abspath = local_abspath;
537      apr_array_header_t *missing_parents =
538        apr_array_make(pool, 4, sizeof(const char *));
539
540      while (1)
541        {
542          /* Try to lock.  If we can't lock because our target (or its
543             parent) isn't a working copy, we'll try to walk up the
544             tree to find a working copy, remembering this path's
545             parent as one we need to flesh out.  */
546          err = svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
547                                           parent_abspath, !innerupdate,
548                                           pool, pool);
549          if (!err)
550            break;
551          if ((err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
552              || svn_dirent_is_root(parent_abspath, strlen(parent_abspath)))
553            return err;
554          svn_error_clear(err);
555
556          /* Remember the parent of our update target as a missing
557             parent. */
558          parent_abspath = svn_dirent_dirname(parent_abspath, pool);
559          APR_ARRAY_PUSH(missing_parents, const char *) = parent_abspath;
560        }
561
562      /* Run 'svn up --depth=empty' (effectively) on the missing
563         parents, if any. */
564      anchor_abspath = lockroot_abspath;
565      for (i = missing_parents->nelts - 1; i >= 0; i--)
566        {
567          const char *missing_parent =
568            APR_ARRAY_IDX(missing_parents, i, const char *);
569
570          err = update_internal(result_rev, conflicted_paths,
571                                missing_parent, anchor_abspath,
572                                &peg_revision, svn_depth_empty, FALSE,
573                                ignore_externals, allow_unver_obstructions,
574                                adds_as_modification, timestamp_sleep,
575                                FALSE, ctx, pool);
576          if (err)
577            goto cleanup;
578          anchor_abspath = missing_parent;
579
580          /* If we successfully updated a missing parent, let's re-use
581             the returned revision number for future updates for the
582             sake of consistency. */
583          peg_revision.kind = svn_opt_revision_number;
584          peg_revision.value.number = *result_rev;
585        }
586    }
587  else
588    {
589      SVN_ERR(svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
590                                         local_abspath, !innerupdate,
591                                         pool, pool));
592      anchor_abspath = lockroot_abspath;
593    }
594
595  err = update_internal(result_rev, conflicted_paths,
596                        local_abspath, anchor_abspath,
597                        &peg_revision, depth, depth_is_sticky,
598                        ignore_externals, allow_unver_obstructions,
599                        adds_as_modification, timestamp_sleep,
600                        TRUE, ctx, pool);
601
602  /* Give the conflict resolver callback the opportunity to
603   * resolve any conflicts that were raised. */
604  if (! err && ctx->conflict_func2)
605    {
606      err = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool);
607    }
608
609 cleanup:
610  err = svn_error_compose_create(
611            err,
612            svn_wc__release_write_lock(ctx->wc_ctx, lockroot_abspath, pool));
613
614  return svn_error_trace(err);
615}
616
617
618svn_error_t *
619svn_client_update4(apr_array_header_t **result_revs,
620                   const apr_array_header_t *paths,
621                   const svn_opt_revision_t *revision,
622                   svn_depth_t depth,
623                   svn_boolean_t depth_is_sticky,
624                   svn_boolean_t ignore_externals,
625                   svn_boolean_t allow_unver_obstructions,
626                   svn_boolean_t adds_as_modification,
627                   svn_boolean_t make_parents,
628                   svn_client_ctx_t *ctx,
629                   apr_pool_t *pool)
630{
631  int i;
632  apr_pool_t *iterpool = svn_pool_create(pool);
633  const char *path = NULL;
634  svn_boolean_t sleep = FALSE;
635  svn_error_t *err = SVN_NO_ERROR;
636
637  if (result_revs)
638    *result_revs = apr_array_make(pool, paths->nelts, sizeof(svn_revnum_t));
639
640  for (i = 0; i < paths->nelts; ++i)
641    {
642      path = APR_ARRAY_IDX(paths, i, const char *);
643
644      if (svn_path_is_url(path))
645        return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
646                                 _("'%s' is not a local path"), path);
647    }
648
649  for (i = 0; i < paths->nelts; ++i)
650    {
651      svn_revnum_t result_rev;
652      const char *local_abspath;
653      path = APR_ARRAY_IDX(paths, i, const char *);
654
655      svn_pool_clear(iterpool);
656
657      if (ctx->cancel_func)
658        {
659          err = ctx->cancel_func(ctx->cancel_baton);
660          if (err)
661            goto cleanup;
662        }
663
664      err = svn_dirent_get_absolute(&local_abspath, path, iterpool);
665      if (err)
666        goto cleanup;
667      err = svn_client__update_internal(&result_rev, local_abspath,
668                                        revision, depth, depth_is_sticky,
669                                        ignore_externals,
670                                        allow_unver_obstructions,
671                                        adds_as_modification,
672                                        make_parents,
673                                        FALSE, &sleep,
674                                        ctx,
675                                        iterpool);
676
677      if (err)
678        {
679          if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
680            goto cleanup;
681
682          svn_error_clear(err);
683          err = SVN_NO_ERROR;
684
685          /* SVN_ERR_WC_NOT_WORKING_COPY: it's not versioned */
686
687          result_rev = SVN_INVALID_REVNUM;
688          if (ctx->notify_func2)
689            {
690              svn_wc_notify_t *notify;
691              notify = svn_wc_create_notify(path,
692                                            svn_wc_notify_skip,
693                                            iterpool);
694              (*ctx->notify_func2)(ctx->notify_baton2, notify, iterpool);
695            }
696        }
697      if (result_revs)
698        APR_ARRAY_PUSH(*result_revs, svn_revnum_t) = result_rev;
699    }
700  svn_pool_destroy(iterpool);
701
702 cleanup:
703  if (sleep)
704    {
705      const char *wcroot_abspath;
706
707      if (paths->nelts == 1)
708        {
709          const char *abspath;
710
711          /* PATH iteslf may have been removed by the update. */
712          SVN_ERR(svn_dirent_get_absolute(&abspath, path, pool));
713          SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, abspath,
714                                     pool, pool));
715        }
716      else
717        wcroot_abspath = NULL;
718
719      svn_io_sleep_for_timestamps(wcroot_abspath, pool);
720    }
721
722  return svn_error_trace(err);
723}
724