2 * update.c: wrappers around wc update functionality
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
24 /* ==================================================================== */
32 #include "svn_client.h"
33 #include "svn_error.h"
34 #include "svn_config.h"
36 #include "svn_dirent_uri.h"
38 #include "svn_pools.h"
42 #include "svn_private_config.h"
43 #include "private/svn_wc_private.h"
45 /* Implements svn_wc_dirents_func_t for update and switch handling. Assumes
46 a struct svn_client__dirent_fetcher_baton_t * baton */
48 svn_client__dirent_fetcher(void *baton,
50 const char *repos_root_url,
51 const char *repos_relpath,
52 apr_pool_t *result_pool,
53 apr_pool_t *scratch_pool)
55 struct svn_client__dirent_fetcher_baton_t *dfb = baton;
56 const char *old_url = NULL;
57 const char *session_relpath;
61 url = svn_path_url_add_component2(repos_root_url, repos_relpath,
64 if (!svn_uri__is_ancestor(dfb->anchor_url, url))
66 SVN_ERR(svn_client__ensure_ra_session_url(&old_url, dfb->ra_session,
71 SVN_ERR(svn_ra_get_path_relative_to_session(dfb->ra_session,
72 &session_relpath, url,
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));
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));
87 SVN_ERR(svn_ra_reparent(dfb->ra_session, old_url, scratch_pool));
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.
104 is_empty_wc(svn_boolean_t *clean_checkout,
105 const char *local_abspath,
106 const char *anchor_abspath,
113 /* "clean" until found dirty */
114 *clean_checkout = TRUE;
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);
121 if (! APR_STATUS_IS_ENOENT(err->apr_err))
122 *clean_checkout = FALSE;
124 svn_error_clear(err);
128 for (err = svn_io_dir_read(&finfo, APR_FINFO_NAME, dir, pool);
130 err = svn_io_dir_read(&finfo, APR_FINFO_NAME, dir, pool))
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'))))
140 if ( ! svn_wc_is_adm_dir(finfo.name, pool)
141 || strcmp(local_abspath, anchor_abspath) != 0)
143 *clean_checkout = FALSE;
151 if (! APR_STATUS_IS_ENOENT(err->apr_err))
153 /* There was some issue reading the folder content.
154 * We better disable optimizations in that case. */
155 *clean_checkout = FALSE;
158 svn_error_clear(err);
161 return svn_io_dir_close(dir);
164 /* A conflict callback that simply records the conflicted path in BATON.
166 Implements svn_wc_conflict_resolver_func2_t.
169 record_conflict(svn_wc_conflict_result_t **result,
170 const svn_wc_conflict_description2_t *description,
172 apr_pool_t *result_pool,
173 apr_pool_t *scratch_pool)
175 apr_hash_t *conflicted_paths = baton;
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,
185 /* This is a helper for svn_client__update_internal(), which see for
186 an explanation of most of these parameters. Some stuff that's
187 unique is as follows:
189 ANCHOR_ABSPATH is the local absolute path of the update anchor.
190 This is typically either the same as LOCAL_ABSPATH, or the
191 immediate parent of LOCAL_ABSPATH.
193 If NOTIFY_SUMMARY is set (and there's a notification handler in
194 CTX), transmit the final update summary upon successful
195 completion of the update.
197 Add the paths of any conflict victims to CONFLICTED_PATHS, if that
201 update_internal(svn_revnum_t *result_rev,
202 apr_hash_t *conflicted_paths,
203 const char *local_abspath,
204 const char *anchor_abspath,
205 const svn_opt_revision_t *revision,
207 svn_boolean_t depth_is_sticky,
208 svn_boolean_t ignore_externals,
209 svn_boolean_t allow_unver_obstructions,
210 svn_boolean_t adds_as_modification,
211 svn_boolean_t *timestamp_sleep,
212 svn_boolean_t notify_summary,
213 svn_client_ctx_t *ctx,
216 const svn_delta_editor_t *update_editor;
217 void *update_edit_baton;
218 const svn_ra_reporter3_t *reporter;
220 const char *corrected_url;
222 const char *repos_root_url;
223 const char *repos_relpath;
224 const char *repos_uuid;
225 const char *anchor_url;
227 svn_boolean_t use_commit_times;
228 svn_boolean_t clean_checkout = FALSE;
229 const char *diff3_cmd;
230 apr_hash_t *wcroot_iprops;
231 svn_opt_revision_t opt_rev;
232 svn_ra_session_t *ra_session;
233 const char *preserved_exts_str;
234 apr_array_header_t *preserved_exts;
235 struct svn_client__dirent_fetcher_baton_t dfb;
236 svn_boolean_t server_supports_depth;
237 svn_boolean_t cropping_target;
238 svn_boolean_t target_conflicted = FALSE;
239 svn_config_t *cfg = ctx->config
240 ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
244 *result_rev = SVN_INVALID_REVNUM;
246 /* An unknown depth can't be sticky. */
247 if (depth == svn_depth_unknown)
248 depth_is_sticky = FALSE;
250 if (strcmp(local_abspath, anchor_abspath))
251 target = svn_dirent_basename(local_abspath, pool);
255 /* Check if our anchor exists in BASE. If it doesn't we can't update. */
256 SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath, &repos_root_url,
258 ctx->wc_ctx, anchor_abspath,
262 /* It does not make sense to update conflict victims. */
266 svn_boolean_t text_conflicted, prop_conflicted;
268 anchor_url = svn_path_url_add_component2(repos_root_url, repos_relpath,
271 err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted,
273 ctx->wc_ctx, local_abspath, pool);
275 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
276 return svn_error_trace(err);
277 svn_error_clear(err);
279 /* tree-conflicts are handled by the update editor */
280 if (!err && (text_conflicted || prop_conflicted))
281 target_conflicted = TRUE;
286 if (! anchor_url || target_conflicted)
288 if (ctx->notify_func2)
292 nt = svn_wc_create_notify(local_abspath,
294 ? svn_wc_notify_skip_conflicted
295 : svn_wc_notify_update_skip_working_only,
298 ctx->notify_func2(ctx->notify_baton2, nt, pool);
303 /* We may need to crop the tree if the depth is sticky */
304 cropping_target = (depth_is_sticky && depth < svn_depth_infinity);
307 svn_node_kind_t target_kind;
309 if (depth == svn_depth_exclude)
311 SVN_ERR(svn_wc_exclude(ctx->wc_ctx,
313 ctx->cancel_func, ctx->cancel_baton,
314 ctx->notify_func2, ctx->notify_baton2,
317 /* Target excluded, we are done now */
321 SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath,
323 if (target_kind == svn_node_dir)
325 SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth,
326 ctx->cancel_func, ctx->cancel_baton,
327 ctx->notify_func2, ctx->notify_baton2,
332 /* check whether the "clean c/o" optimization is applicable */
333 SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath, pool));
335 /* Get the external diff3, if any. */
336 svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
337 SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
339 if (diff3_cmd != NULL)
340 SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool));
342 /* See if the user wants last-commit timestamps instead of current ones. */
343 SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
344 SVN_CONFIG_SECTION_MISCELLANY,
345 SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
347 /* See which files the user wants to preserve the extension of when
348 conflict files are made. */
349 svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
350 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
351 preserved_exts = *preserved_exts_str
352 ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool)
355 /* Let everyone know we're starting a real update (unless we're
357 if (ctx->notify_func2 && notify_summary)
359 svn_wc_notify_t *notify
360 = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started,
362 notify->kind = svn_node_none;
363 notify->content_state = notify->prop_state
364 = svn_wc_notify_state_inapplicable;
365 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
366 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
369 /* Open an RA session for the URL */
370 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
372 anchor_abspath, NULL, TRUE,
373 TRUE, ctx, pool, pool));
375 /* If we got a corrected URL from the RA subsystem, we'll need to
376 relocate our working copy first. */
379 const char *new_repos_root_url;
381 /* To relocate everything inside our repository we need the old and new
383 SVN_ERR(svn_ra_get_repos_root2(ra_session, &new_repos_root_url, pool));
385 /* svn_client_relocate2() will check the uuid */
386 SVN_ERR(svn_client_relocate2(anchor_abspath, repos_root_url,
387 new_repos_root_url, ignore_externals,
390 /* Store updated repository root for externals */
391 repos_root_url = new_repos_root_url;
392 /* ### We should update anchor_loc->repos_uuid too, although currently
393 * we don't use it. */
394 anchor_url = corrected_url;
397 /* Resolve unspecified REVISION now, because we need to retrieve the
398 correct inherited props prior to the editor drive and we need to
399 use the same value of HEAD for both. */
400 opt_rev.kind = revision->kind;
401 opt_rev.value = revision->value;
402 if (opt_rev.kind == svn_opt_revision_unspecified)
403 opt_rev.kind = svn_opt_revision_head;
405 /* ### todo: shouldn't svn_client__get_revision_number be able
406 to take a URL as easily as a local path? */
407 SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx,
408 local_abspath, ra_session, &opt_rev,
411 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
412 SVN_RA_CAPABILITY_DEPTH, pool));
414 dfb.ra_session = ra_session;
415 dfb.target_revision = revnum;
416 dfb.anchor_url = anchor_url;
418 SVN_ERR(svn_client__get_inheritable_props(&wcroot_iprops, local_abspath,
419 revnum, depth, ra_session,
422 /* Fetch the update editor. If REVISION is invalid, that's okay;
423 the RA driver will call editor->set_target_revision later on. */
424 SVN_ERR(svn_wc__get_update_editor(&update_editor, &update_edit_baton,
425 &revnum, ctx->wc_ctx, anchor_abspath,
426 target, wcroot_iprops, use_commit_times,
427 depth, depth_is_sticky,
428 allow_unver_obstructions,
429 adds_as_modification,
430 server_supports_depth,
432 diff3_cmd, preserved_exts,
433 svn_client__dirent_fetcher, &dfb,
434 conflicted_paths ? record_conflict : NULL,
437 ctx->cancel_func, ctx->cancel_baton,
438 ctx->notify_func2, ctx->notify_baton2,
441 /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
442 invalid revnum, that means RA will use the latest revision. */
443 SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &report_baton,
445 (!server_supports_depth || depth_is_sticky
447 : svn_depth_unknown),
448 FALSE /* send_copyfrom_args */,
449 FALSE /* ignore_ancestry */,
450 update_editor, update_edit_baton, pool, pool));
452 /* Past this point, we assume the WC is going to be modified so we will
453 * need to sleep for timestamps. */
454 *timestamp_sleep = TRUE;
456 /* Drive the reporter structure, describing the revisions within
457 PATH. When we call reporter->finish_report, the
458 update_editor will be driven by svn_repos_dir_delta2. */
459 SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter,
461 depth, (! depth_is_sticky),
462 (! server_supports_depth),
464 ctx->cancel_func, ctx->cancel_baton,
465 ctx->notify_func2, ctx->notify_baton2,
468 /* We handle externals after the update is complete, so that
469 handling external items (and any errors therefrom) doesn't delay
470 the primary operation. */
471 if ((SVN_DEPTH_IS_RECURSIVE(depth) || cropping_target)
472 && (! ignore_externals))
474 apr_hash_t *new_externals;
475 apr_hash_t *new_depths;
476 SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
478 ctx->wc_ctx, local_abspath,
481 SVN_ERR(svn_client__handle_externals(new_externals,
483 repos_root_url, local_abspath,
484 depth, timestamp_sleep,
488 /* Let everyone know we're finished here (unless we're asked not to). */
489 if (ctx->notify_func2 && notify_summary)
491 svn_wc_notify_t *notify
492 = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
494 notify->kind = svn_node_none;
495 notify->content_state = notify->prop_state
496 = svn_wc_notify_state_inapplicable;
497 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
498 notify->revision = revnum;
499 (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
502 /* If the caller wants the result revision, give it to them. */
504 *result_rev = revnum;
510 svn_client__update_internal(svn_revnum_t *result_rev,
511 const char *local_abspath,
512 const svn_opt_revision_t *revision,
514 svn_boolean_t depth_is_sticky,
515 svn_boolean_t ignore_externals,
516 svn_boolean_t allow_unver_obstructions,
517 svn_boolean_t adds_as_modification,
518 svn_boolean_t make_parents,
519 svn_boolean_t innerupdate,
520 svn_boolean_t *timestamp_sleep,
521 svn_client_ctx_t *ctx,
524 const char *anchor_abspath, *lockroot_abspath;
526 svn_opt_revision_t peg_revision = *revision;
527 apr_hash_t *conflicted_paths
528 = ctx->conflict_func2 ? apr_hash_make(pool) : NULL;
530 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
531 SVN_ERR_ASSERT(! (innerupdate && make_parents));
536 const char *parent_abspath = local_abspath;
537 apr_array_header_t *missing_parents =
538 apr_array_make(pool, 4, sizeof(const char *));
542 /* Try to lock. If we can't lock because our target (or its
543 parent) isn't a working copy, we'll try to walk up the
544 tree to find a working copy, remembering this path's
545 parent as one we need to flesh out. */
546 err = svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
547 parent_abspath, !innerupdate,
551 if ((err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
552 || svn_dirent_is_root(parent_abspath, strlen(parent_abspath)))
554 svn_error_clear(err);
556 /* Remember the parent of our update target as a missing
558 parent_abspath = svn_dirent_dirname(parent_abspath, pool);
559 APR_ARRAY_PUSH(missing_parents, const char *) = parent_abspath;
562 /* Run 'svn up --depth=empty' (effectively) on the missing
564 anchor_abspath = lockroot_abspath;
565 for (i = missing_parents->nelts - 1; i >= 0; i--)
567 const char *missing_parent =
568 APR_ARRAY_IDX(missing_parents, i, const char *);
570 err = update_internal(result_rev, conflicted_paths,
571 missing_parent, anchor_abspath,
572 &peg_revision, svn_depth_empty, FALSE,
573 ignore_externals, allow_unver_obstructions,
574 adds_as_modification, timestamp_sleep,
578 anchor_abspath = missing_parent;
580 /* If we successfully updated a missing parent, let's re-use
581 the returned revision number for future updates for the
582 sake of consistency. */
583 peg_revision.kind = svn_opt_revision_number;
584 peg_revision.value.number = *result_rev;
589 SVN_ERR(svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
590 local_abspath, !innerupdate,
592 anchor_abspath = lockroot_abspath;
595 err = update_internal(result_rev, conflicted_paths,
596 local_abspath, anchor_abspath,
597 &peg_revision, depth, depth_is_sticky,
598 ignore_externals, allow_unver_obstructions,
599 adds_as_modification, timestamp_sleep,
602 /* Give the conflict resolver callback the opportunity to
603 * resolve any conflicts that were raised. */
604 if (! err && ctx->conflict_func2)
606 err = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool);
610 err = svn_error_compose_create(
612 svn_wc__release_write_lock(ctx->wc_ctx, lockroot_abspath, pool));
614 return svn_error_trace(err);
619 svn_client_update4(apr_array_header_t **result_revs,
620 const apr_array_header_t *paths,
621 const svn_opt_revision_t *revision,
623 svn_boolean_t depth_is_sticky,
624 svn_boolean_t ignore_externals,
625 svn_boolean_t allow_unver_obstructions,
626 svn_boolean_t adds_as_modification,
627 svn_boolean_t make_parents,
628 svn_client_ctx_t *ctx,
632 apr_pool_t *iterpool = svn_pool_create(pool);
633 const char *path = NULL;
634 svn_boolean_t sleep = FALSE;
635 svn_error_t *err = SVN_NO_ERROR;
638 *result_revs = apr_array_make(pool, paths->nelts, sizeof(svn_revnum_t));
640 for (i = 0; i < paths->nelts; ++i)
642 path = APR_ARRAY_IDX(paths, i, const char *);
644 if (svn_path_is_url(path))
645 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
646 _("'%s' is not a local path"), path);
649 for (i = 0; i < paths->nelts; ++i)
651 svn_revnum_t result_rev;
652 const char *local_abspath;
653 path = APR_ARRAY_IDX(paths, i, const char *);
655 svn_pool_clear(iterpool);
657 if (ctx->cancel_func)
659 err = ctx->cancel_func(ctx->cancel_baton);
664 err = svn_dirent_get_absolute(&local_abspath, path, iterpool);
667 err = svn_client__update_internal(&result_rev, local_abspath,
668 revision, depth, depth_is_sticky,
670 allow_unver_obstructions,
671 adds_as_modification,
679 if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
682 svn_error_clear(err);
685 /* SVN_ERR_WC_NOT_WORKING_COPY: it's not versioned */
687 result_rev = SVN_INVALID_REVNUM;
688 if (ctx->notify_func2)
690 svn_wc_notify_t *notify;
691 notify = svn_wc_create_notify(path,
694 (*ctx->notify_func2)(ctx->notify_baton2, notify, iterpool);
698 APR_ARRAY_PUSH(*result_revs, svn_revnum_t) = result_rev;
700 svn_pool_destroy(iterpool);
705 const char *wcroot_abspath;
707 if (paths->nelts == 1)
711 /* PATH iteslf may have been removed by the update. */
712 SVN_ERR(svn_dirent_get_absolute(&abspath, path, pool));
713 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, abspath,
717 wcroot_abspath = NULL;
719 svn_io_sleep_for_timestamps(wcroot_abspath, pool);
722 return svn_error_trace(err);