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