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 */
47 svn_error_t *
svn_client__dirent_fetcher(void * baton,apr_hash_t ** dirents,const char * repos_root_url,const char * repos_relpath,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 
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 *
is_empty_wc(svn_boolean_t * clean_checkout,const char * local_abspath,const char * anchor_abspath,apr_pool_t * pool)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 *
record_conflict(svn_wc_conflict_result_t ** result,const svn_wc_conflict_description2_t * description,void * baton,apr_pool_t * result_pool,apr_pool_t * scratch_pool)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 /* Perform post-update processing of externals defined below LOCAL_ABSPATH. */
186 static svn_error_t *
handle_externals(svn_boolean_t * timestamp_sleep,const char * local_abspath,svn_depth_t depth,const char * repos_root_url,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * scratch_pool)187 handle_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. */
218 static svn_error_t *
reuse_ra_session(svn_ra_session_t ** ra_session_p,const char ** corrected_url,const char * anchor_url,const char * anchor_abspath,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)219 reuse_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 */
286 static svn_error_t *
update_internal(svn_revnum_t * result_rev,svn_boolean_t * timestamp_sleep,apr_hash_t * conflicted_paths,svn_ra_session_t ** ra_session_p,const char * local_abspath,const char * anchor_abspath,const svn_opt_revision_t * revision,svn_depth_t depth,svn_boolean_t depth_is_sticky,svn_boolean_t ignore_externals,svn_boolean_t allow_unver_obstructions,svn_boolean_t adds_as_modification,svn_boolean_t notify_summary,svn_client_ctx_t * ctx,apr_pool_t * result_pool,apr_pool_t * scratch_pool)287 update_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 
600 svn_error_t *
svn_client__update_internal(svn_revnum_t * result_rev,svn_boolean_t * timestamp_sleep,const char * local_abspath,const svn_opt_revision_t * revision,svn_depth_t depth,svn_boolean_t depth_is_sticky,svn_boolean_t ignore_externals,svn_boolean_t allow_unver_obstructions,svn_boolean_t adds_as_modification,svn_boolean_t make_parents,svn_boolean_t innerupdate,svn_ra_session_t * ra_session,svn_client_ctx_t * ctx,apr_pool_t * pool)601 svn_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 
720 svn_error_t *
svn_client_update4(apr_array_header_t ** result_revs,const apr_array_header_t * paths,const svn_opt_revision_t * revision,svn_depth_t depth,svn_boolean_t depth_is_sticky,svn_boolean_t ignore_externals,svn_boolean_t allow_unver_obstructions,svn_boolean_t adds_as_modification,svn_boolean_t make_parents,svn_client_ctx_t * ctx,apr_pool_t * pool)721 svn_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