]> CyberLeo.Net >> Repos - FreeBSD/stable/10.git/blob - contrib/subversion/subversion/libsvn_client/update.c
MFC r275385 (by bapt):
[FreeBSD/stable/10.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    Use RA_SESSION_P to run the update if it is not NULL.  If it is then
201    open a new ra session and place it in RA_SESSION_P.  This allows
202    repeated calls to update_internal to reuse the same session.
203 */
204 static svn_error_t *
205 update_internal(svn_revnum_t *result_rev,
206                 svn_boolean_t *timestamp_sleep,
207                 apr_hash_t *conflicted_paths,
208                 svn_ra_session_t **ra_session_p,
209                 const char *local_abspath,
210                 const char *anchor_abspath,
211                 const svn_opt_revision_t *revision,
212                 svn_depth_t depth,
213                 svn_boolean_t depth_is_sticky,
214                 svn_boolean_t ignore_externals,
215                 svn_boolean_t allow_unver_obstructions,
216                 svn_boolean_t adds_as_modification,
217                 svn_boolean_t notify_summary,
218                 svn_client_ctx_t *ctx,
219                 apr_pool_t *result_pool,
220                 apr_pool_t *scratch_pool)
221 {
222   const svn_delta_editor_t *update_editor;
223   void *update_edit_baton;
224   const svn_ra_reporter3_t *reporter;
225   void *report_baton;
226   const char *corrected_url;
227   const char *target;
228   const char *repos_root_url;
229   const char *repos_relpath;
230   const char *repos_uuid;
231   const char *anchor_url;
232   svn_revnum_t revnum;
233   svn_boolean_t use_commit_times;
234   svn_boolean_t clean_checkout = FALSE;
235   const char *diff3_cmd;
236   apr_hash_t *wcroot_iprops;
237   svn_opt_revision_t opt_rev;
238   svn_ra_session_t *ra_session = *ra_session_p;
239   const char *preserved_exts_str;
240   apr_array_header_t *preserved_exts;
241   struct svn_client__dirent_fetcher_baton_t dfb;
242   svn_boolean_t server_supports_depth;
243   svn_boolean_t cropping_target;
244   svn_boolean_t target_conflicted = FALSE;
245   svn_config_t *cfg = ctx->config
246                       ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
247                       : NULL;
248
249   if (result_rev)
250     *result_rev = SVN_INVALID_REVNUM;
251
252   /* An unknown depth can't be sticky. */
253   if (depth == svn_depth_unknown)
254     depth_is_sticky = FALSE;
255
256   if (strcmp(local_abspath, anchor_abspath))
257     target = svn_dirent_basename(local_abspath, scratch_pool);
258   else
259     target = "";
260
261   /* Check if our anchor exists in BASE. If it doesn't we can't update. */
262   SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath, &repos_root_url,
263                                 &repos_uuid, NULL,
264                                 ctx->wc_ctx, anchor_abspath,
265                                 TRUE /* ignore_enoent */,
266                                 scratch_pool, scratch_pool));
267
268   /* It does not make sense to update conflict victims. */
269   if (repos_relpath)
270     {
271       svn_error_t *err;
272       svn_boolean_t text_conflicted, prop_conflicted;
273
274       anchor_url = svn_path_url_add_component2(repos_root_url, repos_relpath,
275                                                scratch_pool);
276
277       err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted,
278                                  NULL,
279                                  ctx->wc_ctx, local_abspath, scratch_pool);
280
281       if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
282         return svn_error_trace(err);
283       svn_error_clear(err);
284
285       /* tree-conflicts are handled by the update editor */
286       if (!err && (text_conflicted || prop_conflicted))
287         target_conflicted = TRUE;
288     }
289   else
290     anchor_url = NULL;
291
292   if (! anchor_url || target_conflicted)
293     {
294       if (ctx->notify_func2)
295         {
296           svn_wc_notify_t *nt;
297
298           nt = svn_wc_create_notify(local_abspath,
299                                     target_conflicted
300                                       ? svn_wc_notify_skip_conflicted
301                                       : svn_wc_notify_update_skip_working_only,
302                                     scratch_pool);
303
304           ctx->notify_func2(ctx->notify_baton2, nt, scratch_pool);
305         }
306       return SVN_NO_ERROR;
307     }
308
309   /* We may need to crop the tree if the depth is sticky */
310   cropping_target = (depth_is_sticky && depth < svn_depth_infinity);
311   if (cropping_target)
312     {
313       svn_node_kind_t target_kind;
314
315       if (depth == svn_depth_exclude)
316         {
317           SVN_ERR(svn_wc_exclude(ctx->wc_ctx,
318                                  local_abspath,
319                                  ctx->cancel_func, ctx->cancel_baton,
320                                  ctx->notify_func2, ctx->notify_baton2,
321                                  scratch_pool));
322
323           /* Target excluded, we are done now */
324           return SVN_NO_ERROR;
325         }
326
327       SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath,
328                                 TRUE, TRUE, scratch_pool));
329       if (target_kind == svn_node_dir)
330         {
331           SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth,
332                                     ctx->cancel_func, ctx->cancel_baton,
333                                     ctx->notify_func2, ctx->notify_baton2,
334                                     scratch_pool));
335         }
336     }
337
338   /* check whether the "clean c/o" optimization is applicable */
339   SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath,
340                       scratch_pool));
341
342   /* Get the external diff3, if any. */
343   svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
344                  SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
345
346   if (diff3_cmd != NULL)
347     SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool));
348
349   /* See if the user wants last-commit timestamps instead of current ones. */
350   SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
351                               SVN_CONFIG_SECTION_MISCELLANY,
352                               SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
353
354   /* See which files the user wants to preserve the extension of when
355      conflict files are made. */
356   svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
357                  SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
358   preserved_exts = *preserved_exts_str
359     ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool)
360     : NULL;
361
362   /* Let everyone know we're starting a real update (unless we're
363      asked not to). */
364   if (ctx->notify_func2 && notify_summary)
365     {
366       svn_wc_notify_t *notify
367         = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started,
368                                scratch_pool);
369       notify->kind = svn_node_none;
370       notify->content_state = notify->prop_state
371         = svn_wc_notify_state_inapplicable;
372       notify->lock_state = svn_wc_notify_lock_state_inapplicable;
373       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
374     }
375
376   /* Try to reuse the RA session by reparenting it to the anchor_url.
377    * This code is probably overly cautious since we only use this
378    * currently when parents are missing and so all the anchor_urls
379    * have to be in the same repo. */
380   if (ra_session)
381     {
382       svn_error_t *err = svn_ra_reparent(ra_session, anchor_url, scratch_pool);
383       if (err)
384         {
385           if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
386             {
387             /* session changed repos, can't reuse it */
388               svn_error_clear(err);
389               ra_session = NULL;
390             }
391           else
392             {
393               return svn_error_trace(err);
394             }
395         }
396       else
397         {
398           corrected_url = NULL;
399         }
400     }
401
402   /* Open an RA session for the URL if one isn't already available */
403   if (!ra_session)
404     {
405       SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
406                                                    anchor_url,
407                                                    anchor_abspath, NULL,
408                                                    TRUE /* write_dav_props */,
409                                                    TRUE /* read_dav_props */,
410                                                    ctx,
411                                                    result_pool, scratch_pool));
412       *ra_session_p = ra_session;
413     }
414
415   /* If we got a corrected URL from the RA subsystem, we'll need to
416      relocate our working copy first. */
417   if (corrected_url)
418     {
419       const char *new_repos_root_url;
420
421       /* To relocate everything inside our repository we need the old and new
422          repos root. */
423       SVN_ERR(svn_ra_get_repos_root2(ra_session, &new_repos_root_url,
424                                      scratch_pool));
425
426       /* svn_client_relocate2() will check the uuid */
427       SVN_ERR(svn_client_relocate2(anchor_abspath, repos_root_url,
428                                    new_repos_root_url, ignore_externals,
429                                    ctx, scratch_pool));
430
431       /* Store updated repository root for externals */
432       repos_root_url = new_repos_root_url;
433       /* ### We should update anchor_loc->repos_uuid too, although currently
434        * we don't use it. */
435       anchor_url = corrected_url;
436     }
437
438   /* Resolve unspecified REVISION now, because we need to retrieve the
439      correct inherited props prior to the editor drive and we need to
440      use the same value of HEAD for both. */
441   opt_rev.kind = revision->kind;
442   opt_rev.value = revision->value;
443   if (opt_rev.kind == svn_opt_revision_unspecified)
444     opt_rev.kind = svn_opt_revision_head;
445
446   /* ### todo: shouldn't svn_client__get_revision_number be able
447      to take a URL as easily as a local path?  */
448   SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx,
449                                           local_abspath, ra_session, &opt_rev,
450                                           scratch_pool));
451
452   SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
453                                 SVN_RA_CAPABILITY_DEPTH, scratch_pool));
454
455   dfb.ra_session = ra_session;
456   dfb.target_revision = revnum;
457   dfb.anchor_url = anchor_url;
458
459   SVN_ERR(svn_client__get_inheritable_props(&wcroot_iprops, local_abspath,
460                                             revnum, depth, ra_session,
461                                             ctx, scratch_pool, scratch_pool));
462
463   /* Fetch the update editor.  If REVISION is invalid, that's okay;
464      the RA driver will call editor->set_target_revision later on. */
465   SVN_ERR(svn_wc__get_update_editor(&update_editor, &update_edit_baton,
466                                     &revnum, ctx->wc_ctx, anchor_abspath,
467                                     target, wcroot_iprops, use_commit_times,
468                                     depth, depth_is_sticky,
469                                     allow_unver_obstructions,
470                                     adds_as_modification,
471                                     server_supports_depth,
472                                     clean_checkout,
473                                     diff3_cmd, preserved_exts,
474                                     svn_client__dirent_fetcher, &dfb,
475                                     conflicted_paths ? record_conflict : NULL,
476                                     conflicted_paths,
477                                     NULL, NULL,
478                                     ctx->cancel_func, ctx->cancel_baton,
479                                     ctx->notify_func2, ctx->notify_baton2,
480                                     scratch_pool, scratch_pool));
481
482   /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
483      invalid revnum, that means RA will use the latest revision.  */
484   SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &report_baton,
485                             revnum, target,
486                             (!server_supports_depth || depth_is_sticky
487                              ? depth
488                              : svn_depth_unknown),
489                             FALSE /* send_copyfrom_args */,
490                             FALSE /* ignore_ancestry */,
491                             update_editor, update_edit_baton,
492                             scratch_pool, scratch_pool));
493
494   /* Past this point, we assume the WC is going to be modified so we will
495    * need to sleep for timestamps. */
496   *timestamp_sleep = TRUE;
497
498   /* Drive the reporter structure, describing the revisions within
499      LOCAL_ABSPATH.  When this calls reporter->finish_report, the
500      reporter will drive the update_editor. */
501   SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter,
502                                   report_baton, TRUE,
503                                   depth, (! depth_is_sticky),
504                                   (! server_supports_depth),
505                                   use_commit_times,
506                                   ctx->cancel_func, ctx->cancel_baton,
507                                   ctx->notify_func2, ctx->notify_baton2,
508                                   scratch_pool));
509
510   /* We handle externals after the update is complete, so that
511      handling external items (and any errors therefrom) doesn't delay
512      the primary operation.  */
513   if ((SVN_DEPTH_IS_RECURSIVE(depth) || cropping_target)
514       && (! ignore_externals))
515     {
516       apr_hash_t *new_externals;
517       apr_hash_t *new_depths;
518       SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
519                                                    &new_depths,
520                                                    ctx->wc_ctx, local_abspath,
521                                                    depth,
522                                                    scratch_pool, scratch_pool));
523
524       SVN_ERR(svn_client__handle_externals(new_externals,
525                                            new_depths,
526                                            repos_root_url, local_abspath,
527                                            depth, timestamp_sleep, ra_session,
528                                            ctx, scratch_pool));
529     }
530
531   /* Let everyone know we're finished here (unless we're asked not to). */
532   if (ctx->notify_func2 && notify_summary)
533     {
534       svn_wc_notify_t *notify
535         = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
536                                scratch_pool);
537       notify->kind = svn_node_none;
538       notify->content_state = notify->prop_state
539         = svn_wc_notify_state_inapplicable;
540       notify->lock_state = svn_wc_notify_lock_state_inapplicable;
541       notify->revision = revnum;
542       ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
543     }
544
545   /* If the caller wants the result revision, give it to them. */
546   if (result_rev)
547     *result_rev = revnum;
548
549   return SVN_NO_ERROR;
550 }
551
552 svn_error_t *
553 svn_client__update_internal(svn_revnum_t *result_rev,
554                             svn_boolean_t *timestamp_sleep,
555                             const char *local_abspath,
556                             const svn_opt_revision_t *revision,
557                             svn_depth_t depth,
558                             svn_boolean_t depth_is_sticky,
559                             svn_boolean_t ignore_externals,
560                             svn_boolean_t allow_unver_obstructions,
561                             svn_boolean_t adds_as_modification,
562                             svn_boolean_t make_parents,
563                             svn_boolean_t innerupdate,
564                             svn_ra_session_t *ra_session,
565                             svn_client_ctx_t *ctx,
566                             apr_pool_t *pool)
567 {
568   const char *anchor_abspath, *lockroot_abspath;
569   svn_error_t *err;
570   svn_opt_revision_t peg_revision = *revision;
571   apr_hash_t *conflicted_paths
572     = ctx->conflict_func2 ? apr_hash_make(pool) : NULL;
573
574   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
575   SVN_ERR_ASSERT(! (innerupdate && make_parents));
576
577   if (make_parents)
578     {
579       int i;
580       const char *parent_abspath = local_abspath;
581       apr_array_header_t *missing_parents =
582         apr_array_make(pool, 4, sizeof(const char *));
583       apr_pool_t *iterpool;
584
585       iterpool = svn_pool_create(pool);
586
587       while (1)
588         {
589           svn_pool_clear(iterpool);
590
591           /* Try to lock.  If we can't lock because our target (or its
592              parent) isn't a working copy, we'll try to walk up the
593              tree to find a working copy, remembering this path's
594              parent as one we need to flesh out.  */
595           err = svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
596                                            parent_abspath, !innerupdate,
597                                            pool, iterpool);
598           if (!err)
599             break;
600           if ((err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
601               || svn_dirent_is_root(parent_abspath, strlen(parent_abspath)))
602             return err;
603           svn_error_clear(err);
604
605           /* Remember the parent of our update target as a missing
606              parent. */
607           parent_abspath = svn_dirent_dirname(parent_abspath, pool);
608           APR_ARRAY_PUSH(missing_parents, const char *) = parent_abspath;
609         }
610
611       /* Run 'svn up --depth=empty' (effectively) on the missing
612          parents, if any. */
613       anchor_abspath = lockroot_abspath;
614       for (i = missing_parents->nelts - 1; i >= 0; i--)
615         {
616           const char *missing_parent =
617             APR_ARRAY_IDX(missing_parents, i, const char *);
618
619           svn_pool_clear(iterpool);
620
621           err = update_internal(result_rev, timestamp_sleep, conflicted_paths,
622                                 &ra_session, missing_parent,
623                                 anchor_abspath, &peg_revision, svn_depth_empty,
624                                 FALSE, ignore_externals,
625                                 allow_unver_obstructions, adds_as_modification,
626                                 FALSE, ctx, pool, iterpool);
627           if (err)
628             goto cleanup;
629           anchor_abspath = missing_parent;
630
631           /* If we successfully updated a missing parent, let's re-use
632              the returned revision number for future updates for the
633              sake of consistency. */
634           peg_revision.kind = svn_opt_revision_number;
635           peg_revision.value.number = *result_rev;
636         }
637
638       svn_pool_destroy(iterpool);
639     }
640   else
641     {
642       SVN_ERR(svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
643                                          local_abspath, !innerupdate,
644                                          pool, pool));
645       anchor_abspath = lockroot_abspath;
646     }
647
648   err = update_internal(result_rev, timestamp_sleep, conflicted_paths,
649                         &ra_session,
650                         local_abspath, anchor_abspath,
651                         &peg_revision, depth, depth_is_sticky,
652                         ignore_externals, allow_unver_obstructions,
653                         adds_as_modification,
654                         TRUE, ctx, pool, pool);
655
656   /* Give the conflict resolver callback the opportunity to
657    * resolve any conflicts that were raised. */
658   if (! err && ctx->conflict_func2 && apr_hash_count(conflicted_paths))
659     {
660       err = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool);
661     }
662
663  cleanup:
664   err = svn_error_compose_create(
665             err,
666             svn_wc__release_write_lock(ctx->wc_ctx, lockroot_abspath, pool));
667
668   return svn_error_trace(err);
669 }
670
671
672 svn_error_t *
673 svn_client_update4(apr_array_header_t **result_revs,
674                    const apr_array_header_t *paths,
675                    const svn_opt_revision_t *revision,
676                    svn_depth_t depth,
677                    svn_boolean_t depth_is_sticky,
678                    svn_boolean_t ignore_externals,
679                    svn_boolean_t allow_unver_obstructions,
680                    svn_boolean_t adds_as_modification,
681                    svn_boolean_t make_parents,
682                    svn_client_ctx_t *ctx,
683                    apr_pool_t *pool)
684 {
685   int i;
686   apr_pool_t *iterpool = svn_pool_create(pool);
687   const char *path = NULL;
688   svn_boolean_t sleep = FALSE;
689   svn_error_t *err = SVN_NO_ERROR;
690   svn_boolean_t found_valid_target = FALSE;
691
692   if (result_revs)
693     *result_revs = apr_array_make(pool, paths->nelts, sizeof(svn_revnum_t));
694
695   for (i = 0; i < paths->nelts; ++i)
696     {
697       path = APR_ARRAY_IDX(paths, i, const char *);
698
699       if (svn_path_is_url(path))
700         return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
701                                  _("'%s' is not a local path"), path);
702     }
703
704   for (i = 0; i < paths->nelts; ++i)
705     {
706       svn_revnum_t result_rev;
707       const char *local_abspath;
708       path = APR_ARRAY_IDX(paths, i, const char *);
709
710       svn_pool_clear(iterpool);
711
712       if (ctx->cancel_func)
713         {
714           err = ctx->cancel_func(ctx->cancel_baton);
715           if (err)
716             goto cleanup;
717         }
718
719       err = svn_dirent_get_absolute(&local_abspath, path, iterpool);
720       if (err)
721         goto cleanup;
722       err = svn_client__update_internal(&result_rev, &sleep, local_abspath,
723                                         revision, depth, depth_is_sticky,
724                                         ignore_externals,
725                                         allow_unver_obstructions,
726                                         adds_as_modification,
727                                         make_parents,
728                                         FALSE, NULL, ctx,
729                                         iterpool);
730
731       if (err)
732         {
733           if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
734             goto cleanup;
735
736           svn_error_clear(err);
737           err = SVN_NO_ERROR;
738
739           /* SVN_ERR_WC_NOT_WORKING_COPY: it's not versioned */
740
741           result_rev = SVN_INVALID_REVNUM;
742           if (ctx->notify_func2)
743             {
744               svn_wc_notify_t *notify;
745               notify = svn_wc_create_notify(path,
746                                             svn_wc_notify_skip,
747                                             iterpool);
748               ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
749             }
750         }
751       else
752         found_valid_target = TRUE;
753
754       if (result_revs)
755         APR_ARRAY_PUSH(*result_revs, svn_revnum_t) = result_rev;
756     }
757   svn_pool_destroy(iterpool);
758
759  cleanup:
760   if (!err && !found_valid_target)
761     return svn_error_create(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
762                             _("None of the targets are working copies"));
763   if (sleep)
764     {
765       const char *wcroot_abspath;
766
767       if (paths->nelts == 1)
768         {
769           const char *abspath;
770
771           /* PATH iteslf may have been removed by the update. */
772           SVN_ERR(svn_dirent_get_absolute(&abspath, path, pool));
773           SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, abspath,
774                                      pool, pool));
775         }
776       else
777         wcroot_abspath = NULL;
778
779       svn_io_sleep_for_timestamps(wcroot_abspath, pool);
780     }
781
782   return svn_error_trace(err);
783 }