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