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
200 Use RA_SESSION_P to run the update if it is not NULL. If it is then
201 open a new ra session and place it in RA_SESSION_P. This allows
202 repeated calls to update_internal to reuse the same session.
205 update_internal(svn_revnum_t *result_rev,
206 svn_boolean_t *timestamp_sleep,
207 apr_hash_t *conflicted_paths,
208 svn_ra_session_t **ra_session_p,
209 const char *local_abspath,
210 const char *anchor_abspath,
211 const svn_opt_revision_t *revision,
213 svn_boolean_t depth_is_sticky,
214 svn_boolean_t ignore_externals,
215 svn_boolean_t allow_unver_obstructions,
216 svn_boolean_t adds_as_modification,
217 svn_boolean_t notify_summary,
218 svn_client_ctx_t *ctx,
219 apr_pool_t *result_pool,
220 apr_pool_t *scratch_pool)
222 const svn_delta_editor_t *update_editor;
223 void *update_edit_baton;
224 const svn_ra_reporter3_t *reporter;
226 const char *corrected_url;
228 const char *repos_root_url;
229 const char *repos_relpath;
230 const char *repos_uuid;
231 const char *anchor_url;
233 svn_boolean_t use_commit_times;
234 svn_boolean_t clean_checkout = FALSE;
235 const char *diff3_cmd;
236 apr_hash_t *wcroot_iprops;
237 svn_opt_revision_t opt_rev;
238 svn_ra_session_t *ra_session = *ra_session_p;
239 const char *preserved_exts_str;
240 apr_array_header_t *preserved_exts;
241 struct svn_client__dirent_fetcher_baton_t dfb;
242 svn_boolean_t server_supports_depth;
243 svn_boolean_t cropping_target;
244 svn_boolean_t target_conflicted = FALSE;
245 svn_config_t *cfg = ctx->config
246 ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG)
250 *result_rev = SVN_INVALID_REVNUM;
252 /* An unknown depth can't be sticky. */
253 if (depth == svn_depth_unknown)
254 depth_is_sticky = FALSE;
256 if (strcmp(local_abspath, anchor_abspath))
257 target = svn_dirent_basename(local_abspath, scratch_pool);
261 /* Check if our anchor exists in BASE. If it doesn't we can't update. */
262 SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath, &repos_root_url,
264 ctx->wc_ctx, anchor_abspath,
265 TRUE /* ignore_enoent */,
266 scratch_pool, scratch_pool));
268 /* It does not make sense to update conflict victims. */
272 svn_boolean_t text_conflicted, prop_conflicted;
274 anchor_url = svn_path_url_add_component2(repos_root_url, repos_relpath,
277 err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted,
279 ctx->wc_ctx, local_abspath, scratch_pool);
281 if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
282 return svn_error_trace(err);
283 svn_error_clear(err);
285 /* tree-conflicts are handled by the update editor */
286 if (!err && (text_conflicted || prop_conflicted))
287 target_conflicted = TRUE;
292 if (! anchor_url || target_conflicted)
294 if (ctx->notify_func2)
298 nt = svn_wc_create_notify(local_abspath,
300 ? svn_wc_notify_skip_conflicted
301 : svn_wc_notify_update_skip_working_only,
304 ctx->notify_func2(ctx->notify_baton2, nt, scratch_pool);
309 /* We may need to crop the tree if the depth is sticky */
310 cropping_target = (depth_is_sticky && depth < svn_depth_infinity);
313 svn_node_kind_t target_kind;
315 if (depth == svn_depth_exclude)
317 SVN_ERR(svn_wc_exclude(ctx->wc_ctx,
319 ctx->cancel_func, ctx->cancel_baton,
320 ctx->notify_func2, ctx->notify_baton2,
323 /* Target excluded, we are done now */
327 SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath,
328 TRUE, TRUE, scratch_pool));
329 if (target_kind == svn_node_dir)
331 SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth,
332 ctx->cancel_func, ctx->cancel_baton,
333 ctx->notify_func2, ctx->notify_baton2,
338 /* check whether the "clean c/o" optimization is applicable */
339 SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath,
342 /* Get the external diff3, if any. */
343 svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS,
344 SVN_CONFIG_OPTION_DIFF3_CMD, NULL);
346 if (diff3_cmd != NULL)
347 SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool));
349 /* See if the user wants last-commit timestamps instead of current ones. */
350 SVN_ERR(svn_config_get_bool(cfg, &use_commit_times,
351 SVN_CONFIG_SECTION_MISCELLANY,
352 SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE));
354 /* See which files the user wants to preserve the extension of when
355 conflict files are made. */
356 svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY,
357 SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, "");
358 preserved_exts = *preserved_exts_str
359 ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool)
362 /* Let everyone know we're starting a real update (unless we're
364 if (ctx->notify_func2 && notify_summary)
366 svn_wc_notify_t *notify
367 = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started,
369 notify->kind = svn_node_none;
370 notify->content_state = notify->prop_state
371 = svn_wc_notify_state_inapplicable;
372 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
373 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
376 /* Try to reuse the RA session by reparenting it to the anchor_url.
377 * This code is probably overly cautious since we only use this
378 * currently when parents are missing and so all the anchor_urls
379 * have to be in the same repo. */
382 svn_error_t *err = svn_ra_reparent(ra_session, anchor_url, scratch_pool);
385 if (err->apr_err == SVN_ERR_RA_ILLEGAL_URL)
387 /* session changed repos, can't reuse it */
388 svn_error_clear(err);
393 return svn_error_trace(err);
398 corrected_url = NULL;
402 /* Open an RA session for the URL if one isn't already available */
405 SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url,
407 anchor_abspath, NULL,
408 TRUE /* write_dav_props */,
409 TRUE /* read_dav_props */,
411 result_pool, scratch_pool));
412 *ra_session_p = ra_session;
415 /* If we got a corrected URL from the RA subsystem, we'll need to
416 relocate our working copy first. */
419 const char *new_repos_root_url;
421 /* To relocate everything inside our repository we need the old and new
423 SVN_ERR(svn_ra_get_repos_root2(ra_session, &new_repos_root_url,
426 /* svn_client_relocate2() will check the uuid */
427 SVN_ERR(svn_client_relocate2(anchor_abspath, repos_root_url,
428 new_repos_root_url, ignore_externals,
431 /* Store updated repository root for externals */
432 repos_root_url = new_repos_root_url;
433 /* ### We should update anchor_loc->repos_uuid too, although currently
434 * we don't use it. */
435 anchor_url = corrected_url;
438 /* Resolve unspecified REVISION now, because we need to retrieve the
439 correct inherited props prior to the editor drive and we need to
440 use the same value of HEAD for both. */
441 opt_rev.kind = revision->kind;
442 opt_rev.value = revision->value;
443 if (opt_rev.kind == svn_opt_revision_unspecified)
444 opt_rev.kind = svn_opt_revision_head;
446 /* ### todo: shouldn't svn_client__get_revision_number be able
447 to take a URL as easily as a local path? */
448 SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx,
449 local_abspath, ra_session, &opt_rev,
452 SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth,
453 SVN_RA_CAPABILITY_DEPTH, scratch_pool));
455 dfb.ra_session = ra_session;
456 dfb.target_revision = revnum;
457 dfb.anchor_url = anchor_url;
459 SVN_ERR(svn_client__get_inheritable_props(&wcroot_iprops, local_abspath,
460 revnum, depth, ra_session,
461 ctx, scratch_pool, scratch_pool));
463 /* Fetch the update editor. If REVISION is invalid, that's okay;
464 the RA driver will call editor->set_target_revision later on. */
465 SVN_ERR(svn_wc__get_update_editor(&update_editor, &update_edit_baton,
466 &revnum, ctx->wc_ctx, anchor_abspath,
467 target, wcroot_iprops, use_commit_times,
468 depth, depth_is_sticky,
469 allow_unver_obstructions,
470 adds_as_modification,
471 server_supports_depth,
473 diff3_cmd, preserved_exts,
474 svn_client__dirent_fetcher, &dfb,
475 conflicted_paths ? record_conflict : NULL,
478 ctx->cancel_func, ctx->cancel_baton,
479 ctx->notify_func2, ctx->notify_baton2,
480 scratch_pool, scratch_pool));
482 /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an
483 invalid revnum, that means RA will use the latest revision. */
484 SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &report_baton,
486 (!server_supports_depth || depth_is_sticky
488 : svn_depth_unknown),
489 FALSE /* send_copyfrom_args */,
490 FALSE /* ignore_ancestry */,
491 update_editor, update_edit_baton,
492 scratch_pool, scratch_pool));
494 /* Past this point, we assume the WC is going to be modified so we will
495 * need to sleep for timestamps. */
496 *timestamp_sleep = TRUE;
498 /* Drive the reporter structure, describing the revisions within
499 LOCAL_ABSPATH. When this calls reporter->finish_report, the
500 reporter will drive the update_editor. */
501 SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter,
503 depth, (! depth_is_sticky),
504 (! server_supports_depth),
506 ctx->cancel_func, ctx->cancel_baton,
507 ctx->notify_func2, ctx->notify_baton2,
510 /* We handle externals after the update is complete, so that
511 handling external items (and any errors therefrom) doesn't delay
512 the primary operation. */
513 if ((SVN_DEPTH_IS_RECURSIVE(depth) || cropping_target)
514 && (! ignore_externals))
516 apr_hash_t *new_externals;
517 apr_hash_t *new_depths;
518 SVN_ERR(svn_wc__externals_gather_definitions(&new_externals,
520 ctx->wc_ctx, local_abspath,
522 scratch_pool, scratch_pool));
524 SVN_ERR(svn_client__handle_externals(new_externals,
526 repos_root_url, local_abspath,
527 depth, timestamp_sleep, ra_session,
531 /* Let everyone know we're finished here (unless we're asked not to). */
532 if (ctx->notify_func2 && notify_summary)
534 svn_wc_notify_t *notify
535 = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed,
537 notify->kind = svn_node_none;
538 notify->content_state = notify->prop_state
539 = svn_wc_notify_state_inapplicable;
540 notify->lock_state = svn_wc_notify_lock_state_inapplicable;
541 notify->revision = revnum;
542 ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool);
545 /* If the caller wants the result revision, give it to them. */
547 *result_rev = revnum;
553 svn_client__update_internal(svn_revnum_t *result_rev,
554 svn_boolean_t *timestamp_sleep,
555 const char *local_abspath,
556 const svn_opt_revision_t *revision,
558 svn_boolean_t depth_is_sticky,
559 svn_boolean_t ignore_externals,
560 svn_boolean_t allow_unver_obstructions,
561 svn_boolean_t adds_as_modification,
562 svn_boolean_t make_parents,
563 svn_boolean_t innerupdate,
564 svn_ra_session_t *ra_session,
565 svn_client_ctx_t *ctx,
568 const char *anchor_abspath, *lockroot_abspath;
570 svn_opt_revision_t peg_revision = *revision;
571 apr_hash_t *conflicted_paths
572 = ctx->conflict_func2 ? apr_hash_make(pool) : NULL;
574 SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
575 SVN_ERR_ASSERT(! (innerupdate && make_parents));
580 const char *parent_abspath = local_abspath;
581 apr_array_header_t *missing_parents =
582 apr_array_make(pool, 4, sizeof(const char *));
583 apr_pool_t *iterpool;
585 iterpool = svn_pool_create(pool);
589 svn_pool_clear(iterpool);
591 /* Try to lock. If we can't lock because our target (or its
592 parent) isn't a working copy, we'll try to walk up the
593 tree to find a working copy, remembering this path's
594 parent as one we need to flesh out. */
595 err = svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
596 parent_abspath, !innerupdate,
600 if ((err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
601 || svn_dirent_is_root(parent_abspath, strlen(parent_abspath)))
603 svn_error_clear(err);
605 /* Remember the parent of our update target as a missing
607 parent_abspath = svn_dirent_dirname(parent_abspath, pool);
608 APR_ARRAY_PUSH(missing_parents, const char *) = parent_abspath;
611 /* Run 'svn up --depth=empty' (effectively) on the missing
613 anchor_abspath = lockroot_abspath;
614 for (i = missing_parents->nelts - 1; i >= 0; i--)
616 const char *missing_parent =
617 APR_ARRAY_IDX(missing_parents, i, const char *);
619 svn_pool_clear(iterpool);
621 err = update_internal(result_rev, timestamp_sleep, conflicted_paths,
622 &ra_session, missing_parent,
623 anchor_abspath, &peg_revision, svn_depth_empty,
624 FALSE, ignore_externals,
625 allow_unver_obstructions, adds_as_modification,
626 FALSE, ctx, pool, iterpool);
629 anchor_abspath = missing_parent;
631 /* If we successfully updated a missing parent, let's re-use
632 the returned revision number for future updates for the
633 sake of consistency. */
634 peg_revision.kind = svn_opt_revision_number;
635 peg_revision.value.number = *result_rev;
638 svn_pool_destroy(iterpool);
642 SVN_ERR(svn_wc__acquire_write_lock(&lockroot_abspath, ctx->wc_ctx,
643 local_abspath, !innerupdate,
645 anchor_abspath = lockroot_abspath;
648 err = update_internal(result_rev, timestamp_sleep, conflicted_paths,
650 local_abspath, anchor_abspath,
651 &peg_revision, depth, depth_is_sticky,
652 ignore_externals, allow_unver_obstructions,
653 adds_as_modification,
654 TRUE, ctx, pool, pool);
656 /* Give the conflict resolver callback the opportunity to
657 * resolve any conflicts that were raised. */
658 if (! err && ctx->conflict_func2 && apr_hash_count(conflicted_paths))
660 err = svn_client__resolve_conflicts(NULL, conflicted_paths, ctx, pool);
664 err = svn_error_compose_create(
666 svn_wc__release_write_lock(ctx->wc_ctx, lockroot_abspath, pool));
668 return svn_error_trace(err);
673 svn_client_update4(apr_array_header_t **result_revs,
674 const apr_array_header_t *paths,
675 const svn_opt_revision_t *revision,
677 svn_boolean_t depth_is_sticky,
678 svn_boolean_t ignore_externals,
679 svn_boolean_t allow_unver_obstructions,
680 svn_boolean_t adds_as_modification,
681 svn_boolean_t make_parents,
682 svn_client_ctx_t *ctx,
686 apr_pool_t *iterpool = svn_pool_create(pool);
687 const char *path = NULL;
688 svn_boolean_t sleep = FALSE;
689 svn_error_t *err = SVN_NO_ERROR;
690 svn_boolean_t found_valid_target = FALSE;
693 *result_revs = apr_array_make(pool, paths->nelts, sizeof(svn_revnum_t));
695 for (i = 0; i < paths->nelts; ++i)
697 path = APR_ARRAY_IDX(paths, i, const char *);
699 if (svn_path_is_url(path))
700 return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL,
701 _("'%s' is not a local path"), path);
704 for (i = 0; i < paths->nelts; ++i)
706 svn_revnum_t result_rev;
707 const char *local_abspath;
708 path = APR_ARRAY_IDX(paths, i, const char *);
710 svn_pool_clear(iterpool);
712 if (ctx->cancel_func)
714 err = ctx->cancel_func(ctx->cancel_baton);
719 err = svn_dirent_get_absolute(&local_abspath, path, iterpool);
722 err = svn_client__update_internal(&result_rev, &sleep, local_abspath,
723 revision, depth, depth_is_sticky,
725 allow_unver_obstructions,
726 adds_as_modification,
733 if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
736 svn_error_clear(err);
739 /* SVN_ERR_WC_NOT_WORKING_COPY: it's not versioned */
741 result_rev = SVN_INVALID_REVNUM;
742 if (ctx->notify_func2)
744 svn_wc_notify_t *notify;
745 notify = svn_wc_create_notify(path,
748 ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
752 found_valid_target = TRUE;
755 APR_ARRAY_PUSH(*result_revs, svn_revnum_t) = result_rev;
757 svn_pool_destroy(iterpool);
760 if (!err && !found_valid_target)
761 return svn_error_create(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
762 _("None of the targets are working copies"));
765 const char *wcroot_abspath;
767 if (paths->nelts == 1)
771 /* PATH iteslf may have been removed by the update. */
772 SVN_ERR(svn_dirent_get_absolute(&abspath, path, pool));
773 SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, abspath,
777 wcroot_abspath = NULL;
779 svn_io_sleep_for_timestamps(wcroot_abspath, pool);
782 return svn_error_trace(err);