]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_client/update.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.git] / contrib / subversion / subversion / libsvn_client / update.c
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 \f
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 */
47 svn_error_t *
48 svn_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 \f
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  */
103 static svn_error_t *
104 is_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 */
168 static svn_error_t *
169 record_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 */
200 static svn_error_t *
201 update_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
509 svn_error_t *
510 svn_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
618 svn_error_t *
619 svn_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     svn_io_sleep_for_timestamps((paths->nelts == 1) ? path : NULL, pool);
705
706   return svn_error_trace(err);
707 }