update.c revision 299742
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
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*/
204static svn_error_t *
205update_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
552svn_error_t *
553svn_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
672svn_error_t *
673svn_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}
784