svn_wc_notify_t * svn_wc_create_notify_url(const char *url, svn_wc_notify_action_t action, apr_pool_t *pool) { svn_wc_notify_t *ret = svn_wc_create_notify(".", action, pool); ret->url = url; return ret; }
/* Notify that the node at PATH is 'missing'. * An svn_delta_editor_t function. */ static svn_error_t * absent_file(const char *path, void *parent_baton, apr_pool_t *pool) { struct dir_baton *pb = parent_baton; struct edit_baton *eb = pb->edit_baton; /* ### TODO: Raise a tree-conflict?? I sure hope not.*/ if (eb->notify_func) { svn_wc_notify_t *notify = svn_wc_create_notify(path, svn_wc_notify_skip, pool); notify->kind = svn_node_file; notify->content_state = notify->prop_state = svn_wc_notify_state_missing; (*eb->notify_func)(eb->notify_baton, notify, pool); } return SVN_NO_ERROR; }
static svn_error_t * wrap_external_error(const svn_client_ctx_t *ctx, const char *target_abspath, svn_error_t *err, apr_pool_t *scratch_pool) { if (err && err->apr_err != SVN_ERR_CANCELLED) { if (ctx->notify_func2) { svn_wc_notify_t *notifier = svn_wc_create_notify( target_abspath, svn_wc_notify_failed_external, scratch_pool); notifier->err = err; ctx->notify_func2(ctx->notify_baton2, notifier, scratch_pool); } svn_error_clear(err); return SVN_NO_ERROR; } return err; }
/* (Note: All arguments are in the baton above.) Attempt to revert LOCAL_ABSPATH. If DEPTH is svn_depth_empty, revert just the properties on the directory; else if svn_depth_files, revert the properties and any files immediately under the directory; else if svn_depth_immediates, revert all of the preceding plus properties on immediate subdirectories; else if svn_depth_infinity, revert path and everything under it fully recursively. CHANGELISTS is an array of const char * changelist names, used as a restrictive filter on items reverted; that is, don't revert any item unless it's a member of one of those changelists. If CHANGELISTS is empty (or altogether NULL), no changelist filtering occurs. Consult CTX to determine whether or not to revert timestamp to the time of last commit ('use-commit-times = yes'). If PATH is unversioned, return SVN_ERR_UNVERSIONED_RESOURCE. */ static svn_error_t * revert(void *baton, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { struct revert_with_write_lock_baton *b = baton; svn_error_t *err; err = svn_wc_revert4(b->ctx->wc_ctx, b->local_abspath, b->depth, b->use_commit_times, b->changelists, b->ctx->cancel_func, b->ctx->cancel_baton, b->ctx->notify_func2, b->ctx->notify_baton2, scratch_pool); if (err) { /* If target isn't versioned, just send a 'skip' notification and move on. */ if (err->apr_err == SVN_ERR_ENTRY_NOT_FOUND || err->apr_err == SVN_ERR_UNVERSIONED_RESOURCE || err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) { if (b->ctx->notify_func2) (*b->ctx->notify_func2)( b->ctx->notify_baton2, svn_wc_create_notify(b->local_abspath, svn_wc_notify_skip, scratch_pool), scratch_pool); svn_error_clear(err); } else return svn_error_trace(err); } return SVN_NO_ERROR; }
/* This is a helper for svn_client__update_internal(), which see for an explanation of most of these parameters. Some stuff that's unique is as follows: ANCHOR_ABSPATH is the local absolute path of the update anchor. This is typically either the same as LOCAL_ABSPATH, or the immediate parent of LOCAL_ABSPATH. If NOTIFY_SUMMARY is set (and there's a notification handler in CTX), transmit the final update summary upon successful completion of the update. Add the paths of any conflict victims to CONFLICTED_PATHS, if that is not null. */ static svn_error_t * update_internal(svn_revnum_t *result_rev, apr_hash_t *conflicted_paths, 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 *timestamp_sleep, svn_boolean_t notify_summary, svn_client_ctx_t *ctx, apr_pool_t *pool) { const svn_delta_editor_t *update_editor; void *update_edit_baton; const svn_ra_reporter3_t *reporter; void *report_baton; const char *corrected_url; const char *target; const char *repos_root_url; const char *repos_relpath; const char *repos_uuid; const char *anchor_url; svn_revnum_t revnum; svn_boolean_t use_commit_times; svn_boolean_t clean_checkout = FALSE; const char *diff3_cmd; apr_hash_t *wcroot_iprops; svn_opt_revision_t opt_rev; svn_ra_session_t *ra_session; const char *preserved_exts_str; apr_array_header_t *preserved_exts; struct svn_client__dirent_fetcher_baton_t dfb; svn_boolean_t server_supports_depth; svn_boolean_t cropping_target; svn_boolean_t target_conflicted = FALSE; svn_config_t *cfg = ctx->config ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; if (result_rev) *result_rev = SVN_INVALID_REVNUM; /* An unknown depth can't be sticky. */ if (depth == svn_depth_unknown) depth_is_sticky = FALSE; if (strcmp(local_abspath, anchor_abspath)) target = svn_dirent_basename(local_abspath, pool); else target = ""; /* Check if our anchor exists in BASE. If it doesn't we can't update. */ SVN_ERR(svn_wc__node_get_base(NULL, NULL, &repos_relpath, &repos_root_url, &repos_uuid, NULL, ctx->wc_ctx, anchor_abspath, TRUE, FALSE, pool, pool)); /* It does not make sense to update conflict victims. */ if (repos_relpath) { svn_error_t *err; svn_boolean_t text_conflicted, prop_conflicted; anchor_url = svn_path_url_add_component2(repos_root_url, repos_relpath, pool); err = svn_wc_conflicted_p3(&text_conflicted, &prop_conflicted, NULL, ctx->wc_ctx, local_abspath, pool); if (err && err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND) return svn_error_trace(err); svn_error_clear(err); /* tree-conflicts are handled by the update editor */ if (!err && (text_conflicted || prop_conflicted)) target_conflicted = TRUE; } else anchor_url = NULL; if (! anchor_url || target_conflicted) { if (ctx->notify_func2) { svn_wc_notify_t *nt; nt = svn_wc_create_notify(local_abspath, target_conflicted ? svn_wc_notify_skip_conflicted : svn_wc_notify_update_skip_working_only, pool); ctx->notify_func2(ctx->notify_baton2, nt, pool); } return SVN_NO_ERROR; } /* We may need to crop the tree if the depth is sticky */ cropping_target = (depth_is_sticky && depth < svn_depth_infinity); if (cropping_target) { svn_node_kind_t target_kind; if (depth == svn_depth_exclude) { SVN_ERR(svn_wc_exclude(ctx->wc_ctx, local_abspath, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); /* Target excluded, we are done now */ return SVN_NO_ERROR; } SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath, TRUE, TRUE, pool)); if (target_kind == svn_node_dir) { SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); } } /* check whether the "clean c/o" optimization is applicable */ SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath, pool)); /* Get the external diff3, if any. */ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF3_CMD, NULL); if (diff3_cmd != NULL) SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool)); /* See if the user wants last-commit timestamps instead of current ones. */ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); /* See which files the user wants to preserve the extension of when conflict files are made. */ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); preserved_exts = *preserved_exts_str ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool) : NULL; /* Let everyone know we're starting a real update (unless we're asked not to). */ if (ctx->notify_func2 && notify_summary) { svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started, pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } /* Open an RA session for the URL */ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, anchor_url, anchor_abspath, NULL, TRUE, TRUE, ctx, pool, pool)); /* If we got a corrected URL from the RA subsystem, we'll need to relocate our working copy first. */ if (corrected_url) { const char *new_repos_root_url; /* To relocate everything inside our repository we need the old and new repos root. */ SVN_ERR(svn_ra_get_repos_root2(ra_session, &new_repos_root_url, pool)); /* svn_client_relocate2() will check the uuid */ SVN_ERR(svn_client_relocate2(anchor_abspath, repos_root_url, new_repos_root_url, ignore_externals, ctx, pool)); /* Store updated repository root for externals */ repos_root_url = new_repos_root_url; /* ### We should update anchor_loc->repos_uuid too, although currently * we don't use it. */ anchor_url = corrected_url; } /* Resolve unspecified REVISION now, because we need to retrieve the correct inherited props prior to the editor drive and we need to use the same value of HEAD for both. */ opt_rev.kind = revision->kind; opt_rev.value = revision->value; if (opt_rev.kind == svn_opt_revision_unspecified) opt_rev.kind = svn_opt_revision_head; /* ### todo: shouldn't svn_client__get_revision_number be able to take a URL as easily as a local path? */ SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx, local_abspath, ra_session, &opt_rev, pool)); SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); dfb.ra_session = ra_session; dfb.target_revision = revnum; dfb.anchor_url = anchor_url; SVN_ERR(svn_client__get_inheritable_props(&wcroot_iprops, local_abspath, revnum, depth, ra_session, ctx, pool, pool)); /* Fetch the update editor. If REVISION is invalid, that's okay; the RA driver will call editor->set_target_revision later on. */ SVN_ERR(svn_wc__get_update_editor(&update_editor, &update_edit_baton, &revnum, ctx->wc_ctx, anchor_abspath, target, wcroot_iprops, use_commit_times, depth, depth_is_sticky, allow_unver_obstructions, adds_as_modification, server_supports_depth, clean_checkout, diff3_cmd, preserved_exts, svn_client__dirent_fetcher, &dfb, conflicted_paths ? record_conflict : NULL, conflicted_paths, NULL, NULL, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool, pool)); /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an invalid revnum, that means RA will use the latest revision. */ SVN_ERR(svn_ra_do_update3(ra_session, &reporter, &report_baton, revnum, target, (!server_supports_depth || depth_is_sticky ? depth : svn_depth_unknown), FALSE /* send_copyfrom_args */, FALSE /* ignore_ancestry */, update_editor, update_edit_baton, pool, pool)); /* Past this point, we assume the WC is going to be modified so we will * need to sleep for timestamps. */ *timestamp_sleep = TRUE; /* Drive the reporter structure, describing the revisions within PATH. When we call reporter->finish_report, the update_editor will be driven by svn_repos_dir_delta2. */ SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter, report_baton, TRUE, depth, (! depth_is_sticky), (! server_supports_depth), use_commit_times, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); /* We handle externals after the update is complete, so that handling external items (and any errors therefrom) doesn't delay the primary operation. */ if ((SVN_DEPTH_IS_RECURSIVE(depth) || cropping_target) && (! ignore_externals)) { apr_hash_t *new_externals; apr_hash_t *new_depths; SVN_ERR(svn_wc__externals_gather_definitions(&new_externals, &new_depths, ctx->wc_ctx, local_abspath, depth, pool, pool)); SVN_ERR(svn_client__handle_externals(new_externals, new_depths, repos_root_url, local_abspath, depth, timestamp_sleep, ctx, pool)); } /* Let everyone know we're finished here (unless we're asked not to). */ if (ctx->notify_func2 && notify_summary) { svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed, pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; notify->revision = revnum; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = revnum; return SVN_NO_ERROR; }
svn_error_t * svn_client_propset_local(const char *propname, const svn_string_t *propval, const apr_array_header_t *targets, svn_depth_t depth, svn_boolean_t skip_checks, const apr_array_header_t *changelists, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { apr_pool_t *iterpool = svn_pool_create(scratch_pool); svn_boolean_t targets_are_urls; int i; if (targets->nelts == 0) return SVN_NO_ERROR; /* Check for homogeneity among our targets. */ targets_are_urls = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)); SVN_ERR(svn_client__assert_homogeneous_target_type(targets)); if (targets_are_urls) return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, _("Targets must be working copy paths")); SVN_ERR(check_prop_name(propname, propval)); for (i = 0; i < targets->nelts; i++) { svn_node_kind_t kind; const char *target_abspath; svn_error_t *err; struct set_props_baton baton; const char *target = APR_ARRAY_IDX(targets, i, const char *); svn_pool_clear(iterpool); /* Check for cancellation */ if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, iterpool)); err = svn_wc_read_kind(&kind, ctx->wc_ctx, target_abspath, FALSE, iterpool); if ((err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) || (!err && (kind == svn_node_unknown || kind == svn_node_none))) { if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify( target_abspath, svn_wc_notify_path_nonexistent, iterpool); ctx->notify_func2(ctx->notify_baton2, notify, iterpool); } svn_error_clear(err); } else SVN_ERR(err); baton.ctx = ctx; baton.local_abspath = target_abspath; baton.depth = depth; baton.kind = kind; baton.propname = propname; baton.propval = propval; baton.skip_checks = skip_checks; baton.changelist_filter = changelists; SVN_ERR(svn_wc__call_with_write_lock(set_props_cb, &baton, ctx->wc_ctx, target_abspath, FALSE, iterpool, iterpool)); } svn_pool_destroy(iterpool); return SVN_NO_ERROR; }
/* ... Add the paths of any conflict victims to CONFLICTED_PATHS, if that is not null. */ static svn_error_t * switch_internal(svn_revnum_t *result_rev, apr_hash_t *conflicted_paths, const char *local_abspath, const char *anchor_abspath, const char *switch_url, const svn_opt_revision_t *peg_revision, 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 ignore_ancestry, svn_boolean_t *timestamp_sleep, svn_client_ctx_t *ctx, apr_pool_t *pool) { const svn_ra_reporter3_t *reporter; void *report_baton; const char *anchor_url, *target; svn_client__pathrev_t *switch_loc; svn_ra_session_t *ra_session; svn_revnum_t revnum; const char *diff3_cmd; apr_hash_t *wcroot_iprops; apr_array_header_t *inherited_props; svn_boolean_t use_commit_times; const svn_delta_editor_t *switch_editor; void *switch_edit_baton; const char *preserved_exts_str; apr_array_header_t *preserved_exts; svn_boolean_t server_supports_depth; struct svn_client__dirent_fetcher_baton_t dfb; svn_config_t *cfg = ctx->config ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; /* An unknown depth can't be sticky. */ if (depth == svn_depth_unknown) depth_is_sticky = FALSE; /* Do not support the situation of both exclude and switch a target. */ if (depth == svn_depth_exclude) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot both exclude and switch a path")); /* Get the external diff3, if any. */ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF3_CMD, NULL); if (diff3_cmd != NULL) SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool)); /* See if the user wants last-commit timestamps instead of current ones. */ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); { svn_boolean_t has_working; SVN_ERR(svn_wc__node_has_working(&has_working, ctx->wc_ctx, local_abspath, pool)); if (has_working) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot switch '%s' because it is not in the " "repository yet"), svn_dirent_local_style(local_abspath, pool)); } /* See which files the user wants to preserve the extension of when conflict files are made. */ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); preserved_exts = *preserved_exts_str ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool) : NULL; /* Sanity check. Without these, the switch is meaningless. */ SVN_ERR_ASSERT(switch_url && (switch_url[0] != '\0')); if (strcmp(local_abspath, anchor_abspath)) target = svn_dirent_basename(local_abspath, pool); else target = ""; SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath, pool, pool)); if (! anchor_url) return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, _("Directory '%s' has no URL"), svn_dirent_local_style(anchor_abspath, pool)); /* We may need to crop the tree if the depth is sticky */ if (depth_is_sticky && depth < svn_depth_infinity) { svn_node_kind_t target_kind; if (depth == svn_depth_exclude) { SVN_ERR(svn_wc_exclude(ctx->wc_ctx, local_abspath, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); /* Target excluded, we are done now */ return SVN_NO_ERROR; } SVN_ERR(svn_wc_read_kind2(&target_kind, ctx->wc_ctx, local_abspath, TRUE, TRUE, pool)); if (target_kind == svn_node_dir) SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); } /* Open an RA session to 'source' URL */ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc, switch_url, anchor_abspath, peg_revision, revision, ctx, pool)); /* Disallow a switch operation to change the repository root of the target. */ if (! svn_uri__is_ancestor(switch_loc->repos_root_url, anchor_url)) return svn_error_createf(SVN_ERR_WC_INVALID_SWITCH, NULL, _("'%s'\nis not the same repository as\n'%s'"), anchor_url, switch_loc->repos_root_url); /* If we're not ignoring ancestry, then error out if the switch source and target don't have a common ancestory. ### We're acting on the anchor here, not the target. Is that ### okay? */ if (! ignore_ancestry) { svn_client__pathrev_t *target_base_loc, *yca; SVN_ERR(svn_client__wc_node_get_base(&target_base_loc, local_abspath, ctx->wc_ctx, pool, pool)); if (!target_base_loc) yca = NULL; /* Not versioned */ else { SVN_ERR(svn_client__get_youngest_common_ancestor( &yca, switch_loc, target_base_loc, ra_session, ctx, pool, pool)); } if (! yca) return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, _("'%s' shares no common ancestry with '%s'"), switch_url, svn_dirent_local_style(local_abspath, pool)); } wcroot_iprops = apr_hash_make(pool); /* Will the base of LOCAL_ABSPATH require an iprop cache post-switch? If we are switching LOCAL_ABSPATH to the root of the repository then we don't need to cache inherited properties. In all other cases we *might* need to cache iprops. */ if (strcmp(switch_loc->repos_root_url, switch_loc->url) != 0) { svn_boolean_t wc_root; svn_boolean_t needs_iprop_cache = TRUE; SVN_ERR(svn_wc__is_wcroot(&wc_root, ctx->wc_ctx, local_abspath, pool)); /* Switching the WC root to anything but the repos root means we need an iprop cache. */ if (!wc_root) { /* We know we are switching a subtree to something other than the repos root, but if we are unswitching that subtree we don't need an iprops cache. */ const char *target_parent_url; const char *unswitched_url; /* Calculate the URL LOCAL_ABSPATH would have if it was unswitched relative to its parent. */ SVN_ERR(svn_wc__node_get_url(&target_parent_url, ctx->wc_ctx, svn_dirent_dirname(local_abspath, pool), pool, pool)); unswitched_url = svn_path_url_add_component2( target_parent_url, svn_dirent_basename(local_abspath, pool), pool); /* If LOCAL_ABSPATH will be unswitched relative to its parent, then it doesn't need an iprop cache. Note: It doesn't matter if LOCAL_ABSPATH is withing a switched subtree, only if it's the *root* of a switched subtree.*/ if (strcmp(unswitched_url, switch_loc->url) == 0) needs_iprop_cache = FALSE; } if (needs_iprop_cache) { SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "", switch_loc->rev, pool, pool)); svn_hash_sets(wcroot_iprops, local_abspath, inherited_props); } } SVN_ERR(svn_ra_reparent(ra_session, anchor_url, pool)); /* Fetch the switch (update) editor. If REVISION is invalid, that's okay; the RA driver will call editor->set_target_revision() later on. */ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); dfb.ra_session = ra_session; dfb.anchor_url = anchor_url; dfb.target_revision = switch_loc->rev; SVN_ERR(svn_wc__get_switch_editor(&switch_editor, &switch_edit_baton, &revnum, ctx->wc_ctx, anchor_abspath, target, switch_loc->url, wcroot_iprops, use_commit_times, depth, depth_is_sticky, allow_unver_obstructions, server_supports_depth, diff3_cmd, preserved_exts, svn_client__dirent_fetcher, &dfb, conflicted_paths ? record_conflict : NULL, conflicted_paths, NULL, NULL, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool, pool)); /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an invalid revnum, that means RA will use the latest revision. */ SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton, switch_loc->rev, target, depth_is_sticky ? depth : svn_depth_unknown, switch_loc->url, FALSE /* send_copyfrom_args */, ignore_ancestry, switch_editor, switch_edit_baton, pool, pool)); /* Past this point, we assume the WC is going to be modified so we will * need to sleep for timestamps. */ *timestamp_sleep = TRUE; /* Drive the reporter structure, describing the revisions within LOCAL_ABSPATH. When this calls reporter->finish_report, the reporter will drive the switch_editor. */ SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter, report_baton, TRUE, depth, (! depth_is_sticky), (! server_supports_depth), use_commit_times, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); /* We handle externals after the switch is complete, so that handling external items (and any errors therefrom) doesn't delay the primary operation. */ if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals)) { apr_hash_t *new_externals; apr_hash_t *new_depths; SVN_ERR(svn_wc__externals_gather_definitions(&new_externals, &new_depths, ctx->wc_ctx, local_abspath, depth, pool, pool)); SVN_ERR(svn_client__handle_externals(new_externals, new_depths, switch_loc->repos_root_url, local_abspath, depth, timestamp_sleep, ctx, pool)); } /* Let everyone know we're finished here. */ if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(anchor_abspath, svn_wc_notify_update_completed, pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; notify->revision = revnum; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = revnum; return SVN_NO_ERROR; }
/* An svn_delta_editor_t function. When the file is closed we have a temporary * file containing a pristine version of the repository file. This can * be compared against the working copy. * * ### Ignore TEXT_CHECKSUM for now. Someday we can use it to verify * ### the integrity of the file being diffed. Done efficiently, this * ### would probably involve calculating the checksum as the data is * ### received, storing the final checksum in the file_baton, and * ### comparing against it here. */ static svn_error_t * close_file(void *file_baton, const char *expected_md5_digest, apr_pool_t *pool) { struct file_baton *b = file_baton; struct edit_baton *eb = b->edit_baton; svn_wc_notify_state_t content_state = svn_wc_notify_state_unknown; svn_wc_notify_state_t prop_state = svn_wc_notify_state_unknown; apr_pool_t *scratch_pool; /* Skip *everything* within a newly tree-conflicted directory. */ if (b->skip) { svn_pool_destroy(b->pool); return SVN_NO_ERROR; } scratch_pool = b->pool; if (expected_md5_digest && eb->text_deltas) { svn_checksum_t *expected_md5_checksum; SVN_ERR(svn_checksum_parse_hex(&expected_md5_checksum, svn_checksum_md5, expected_md5_digest, scratch_pool)); if (!svn_checksum_match(expected_md5_checksum, b->result_md5_checksum)) return svn_error_trace(svn_checksum_mismatch_err( expected_md5_checksum, b->result_md5_checksum, pool, _("Checksum mismatch for '%s'"), b->path)); } if (!b->added && b->propchanges->nelts > 0) { if (!b->pristine_props) { /* We didn't receive a text change, so we have no pristine props. Retrieve just the props now. */ SVN_ERR(get_file_from_ra(b, TRUE, scratch_pool)); } remove_non_prop_changes(b->pristine_props, b->propchanges); } if (b->path_end_revision || b->propchanges->nelts > 0) { const char *mimetype1, *mimetype2; get_file_mime_types(&mimetype1, &mimetype2, b); if (b->added) SVN_ERR(eb->diff_callbacks->file_added( &content_state, &prop_state, &b->tree_conflicted, b->path, b->path_end_revision ? b->path_start_revision : NULL, b->path_end_revision, 0, b->edit_baton->target_revision, mimetype1, mimetype2, NULL, SVN_INVALID_REVNUM, b->propchanges, b->pristine_props, b->edit_baton->diff_cmd_baton, scratch_pool)); else SVN_ERR(eb->diff_callbacks->file_changed( &content_state, &prop_state, &b->tree_conflicted, b->path, b->path_end_revision ? b->path_start_revision : NULL, b->path_end_revision, b->edit_baton->revision, b->edit_baton->target_revision, mimetype1, mimetype2, b->propchanges, b->pristine_props, b->edit_baton->diff_cmd_baton, scratch_pool)); } if (eb->notify_func) { deleted_path_notify_t *dpn; svn_wc_notify_t *notify; svn_wc_notify_action_t action; svn_node_kind_t kind = svn_node_file; /* Find out if a pending delete notification for this path is * still around. */ dpn = apr_hash_get(eb->deleted_paths, b->path, APR_HASH_KEY_STRING); if (dpn) { /* If any was found, we will handle the pending 'deleted path * notification' (DPN) here. Remove it from the list. */ apr_hash_set(eb->deleted_paths, b->path, APR_HASH_KEY_STRING, NULL); /* the pending delete might be on a different node kind. */ kind = dpn->kind; content_state = prop_state = dpn->state; } /* Determine what the notification (ACTION) should be. * In case of a pending 'delete', this might become a 'replace'. */ if (b->tree_conflicted) action = svn_wc_notify_tree_conflict; else if (dpn) { if (dpn->action == svn_wc_notify_update_delete && b->added) action = svn_wc_notify_update_replace; else /* Note: dpn->action might be svn_wc_notify_tree_conflict */ action = dpn->action; } else if ((content_state == svn_wc_notify_state_missing) || (content_state == svn_wc_notify_state_obstructed)) action = svn_wc_notify_skip; else if (b->added) action = svn_wc_notify_update_add; else action = svn_wc_notify_update_update; notify = svn_wc_create_notify(b->path, action, scratch_pool); notify->kind = kind; notify->content_state = content_state; notify->prop_state = prop_state; (*eb->notify_func)(eb->notify_baton, notify, scratch_pool); } svn_pool_destroy(b->pool); /* Destroy file and scratch pool */ return SVN_NO_ERROR; }
static svn_error_t * file_rev_handler(void *baton, const char *path, svn_revnum_t revnum, apr_hash_t *rev_props, svn_boolean_t merged_revision, svn_txdelta_window_handler_t *content_delta_handler, void **content_delta_baton, apr_array_header_t *prop_diffs, apr_pool_t *pool) { struct file_rev_baton *frb = baton; svn_stream_t *last_stream; svn_stream_t *cur_stream; struct delta_baton *delta_baton; apr_pool_t *filepool; /* Clear the current pool. */ svn_pool_clear(frb->currpool); /* If this file has a non-textual mime-type, bail out. */ if (! frb->ignore_mime_type) SVN_ERR(check_mimetype(prop_diffs, frb->target, frb->currpool)); if (frb->ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(path, svn_wc_notify_blame_revision, pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; notify->revision = revnum; frb->ctx->notify_func2(frb->ctx->notify_baton2, notify, pool); } if (frb->ctx->cancel_func) SVN_ERR(frb->ctx->cancel_func(frb->ctx->cancel_baton)); /* If there were no content changes, we couldn't care less about this revision now. Note that we checked the mime type above, so things work if the user just changes the mime type in a commit. Also note that we don't switch the pools in this case. This is important, since the tempfile will be removed by the pool and we need the tempfile from the last revision with content changes. */ if (!content_delta_handler) return SVN_NO_ERROR; frb->merged_revision = merged_revision; /* Create delta baton. */ delta_baton = apr_palloc(frb->currpool, sizeof(*delta_baton)); /* Prepare the text delta window handler. */ if (frb->last_filename) SVN_ERR(svn_io_file_open(&delta_baton->source_file, frb->last_filename, APR_READ, APR_OS_DEFAULT, frb->currpool)); else /* Means empty stream below. */ delta_baton->source_file = NULL; last_stream = svn_stream_from_aprfile(delta_baton->source_file, pool); if (frb->include_merged_revisions && !frb->merged_revision) filepool = frb->filepool; else filepool = frb->currpool; SVN_ERR(svn_io_open_unique_file2(&delta_baton->file, &delta_baton->filename, frb->tmp_path, ".tmp", svn_io_file_del_on_pool_cleanup, filepool)); cur_stream = svn_stream_from_aprfile(delta_baton->file, frb->currpool); /* Get window handler for applying delta. */ svn_txdelta_apply(last_stream, cur_stream, NULL, NULL, frb->currpool, &delta_baton->wrapped_handler, &delta_baton->wrapped_baton); /* Wrap the window handler with our own. */ delta_baton->file_rev_baton = frb; *content_delta_handler = window_handler; *content_delta_baton = delta_baton; /* Create the rev structure. */ frb->rev = apr_palloc(frb->mainpool, sizeof(struct rev)); if (revnum < frb->start_rev) { /* We shouldn't get more than one revision before start. */ assert(frb->last_filename == NULL); /* The file existed before start_rev; generate no blame info for lines from this revision (or before). */ frb->rev->revision = SVN_INVALID_REVNUM; frb->rev->author = NULL; frb->rev->date = NULL; } else { svn_string_t *str; assert(revnum <= frb->end_rev); /* Set values from revision props. */ frb->rev->revision = revnum; if ((str = apr_hash_get(rev_props, SVN_PROP_REVISION_AUTHOR, sizeof(SVN_PROP_REVISION_AUTHOR) - 1))) frb->rev->author = apr_pstrdup(frb->mainpool, str->data); else frb->rev->author = NULL; if ((str = apr_hash_get(rev_props, SVN_PROP_REVISION_DATE, sizeof(SVN_PROP_REVISION_DATE) - 1))) frb->rev->date = apr_pstrdup(frb->mainpool, str->data); else frb->rev->date = NULL; } if (frb->include_merged_revisions) frb->rev->path = apr_pstrdup(frb->mainpool, path); return SVN_NO_ERROR; }
svn_error_t * svn_client_upgrade(const char *path, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { const char *local_abspath; apr_hash_t *externals; apr_hash_index_t *hi; apr_pool_t *iterpool; apr_pool_t *iterpool2; svn_opt_revision_t rev = {svn_opt_revision_unspecified, {0}}; struct repos_info_baton info_baton; info_baton.state_pool = scratch_pool; info_baton.ctx = ctx; info_baton.last_repos = NULL; info_baton.last_uuid = NULL; if (svn_path_is_url(path)) return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("'%s' is not a local path"), path); SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool)); SVN_ERR(svn_wc_upgrade(ctx->wc_ctx, local_abspath, fetch_repos_info, &info_baton, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, scratch_pool)); /* Now it's time to upgrade the externals too. We do it after the wc upgrade to avoid that errors in the externals causes the wc upgrade to fail. Thanks to caching the performance penalty of walking the wc a second time shouldn't be too severe */ SVN_ERR(svn_client_propget5(&externals, NULL, SVN_PROP_EXTERNALS, local_abspath, &rev, &rev, NULL, svn_depth_infinity, NULL, ctx, scratch_pool, scratch_pool)); iterpool = svn_pool_create(scratch_pool); iterpool2 = svn_pool_create(scratch_pool); for (hi = apr_hash_first(scratch_pool, externals); hi; hi = apr_hash_next(hi)) { int i; const char *externals_parent_abspath; const char *externals_parent_url; const char *externals_parent_repos_root_url; const char *externals_parent_repos_relpath; const char *externals_parent = svn__apr_hash_index_key(hi); svn_string_t *external_desc = svn__apr_hash_index_val(hi); apr_array_header_t *externals_p; svn_error_t *err; svn_pool_clear(iterpool); externals_p = apr_array_make(iterpool, 1, sizeof(svn_wc_external_item2_t*)); /* In this loop, an error causes the respective externals definition, or * the external (inner loop), to be skipped, so that upgrade carries on * with the other externals. */ err = svn_dirent_get_absolute(&externals_parent_abspath, externals_parent, iterpool); if (!err) err = svn_wc__node_get_repos_info(NULL, &externals_parent_repos_relpath, &externals_parent_repos_root_url, NULL, ctx->wc_ctx, externals_parent_abspath, iterpool, iterpool); if (!err) externals_parent_url = svn_path_url_add_component2( externals_parent_repos_root_url, externals_parent_repos_relpath, iterpool); if (!err) err = svn_wc_parse_externals_description3( &externals_p, svn_dirent_dirname(path, iterpool), external_desc->data, FALSE, iterpool); if (err) { svn_wc_notify_t *notify = svn_wc_create_notify(externals_parent, svn_wc_notify_failed_external, scratch_pool); notify->err = err; ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); svn_error_clear(err); /* Next externals definition, please... */ continue; } for (i = 0; i < externals_p->nelts; i++) { svn_wc_external_item2_t *item; const char *resolved_url; const char *external_abspath; const char *repos_relpath; const char *repos_root_url; const char *repos_uuid; svn_node_kind_t external_kind; svn_revnum_t peg_revision; svn_revnum_t revision; item = APR_ARRAY_IDX(externals_p, i, svn_wc_external_item2_t*); svn_pool_clear(iterpool2); external_abspath = svn_dirent_join(externals_parent_abspath, item->target_dir, iterpool2); err = svn_wc__resolve_relative_external_url( &resolved_url, item, externals_parent_repos_root_url, externals_parent_url, scratch_pool, scratch_pool); if (err) goto handle_error; /* This is a hack. We only need to call svn_wc_upgrade() on external * dirs, as file externals are upgraded along with their defining * WC. Reading the kind will throw an exception on an external dir, * saying that the wc must be upgraded. If it's a file, the lookup * is done in an adm_dir belonging to the defining wc (which has * already been upgraded) and no error is returned. If it doesn't * exist (external that isn't checked out yet), we'll just get * svn_node_none. */ err = svn_wc_read_kind2(&external_kind, ctx->wc_ctx, external_abspath, TRUE, FALSE, iterpool2); if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) { svn_error_clear(err); err = svn_client_upgrade(external_abspath, ctx, iterpool2); if (err) goto handle_error; } else if (err) goto handle_error; /* The upgrade of any dir should be done now, get the now reliable * kind. */ err = svn_wc_read_kind2(&external_kind, ctx->wc_ctx, external_abspath, TRUE, FALSE, iterpool2); if (err) goto handle_error; /* Update the EXTERNALS table according to the root URL, * relpath and uuid known in the upgraded external WC. */ /* We should probably have a function that provides all three * of root URL, repos relpath and uuid at once, but here goes... */ /* First get the relpath, as that returns SVN_ERR_WC_PATH_NOT_FOUND * when the node is not present in the file system. * svn_wc__node_get_repos_info() would try to derive the URL. */ err = svn_wc__node_get_repos_info(NULL, &repos_relpath, &repos_root_url, &repos_uuid, ctx->wc_ctx, external_abspath, iterpool2, iterpool2); if (err) goto handle_error; /* If we haven't got any information from the checked out external, * or if the URL information mismatches the external's definition, * ask fetch_repos_info() to find out the repos root. */ if (0 != strcmp(resolved_url, svn_path_url_add_component2(repos_root_url, repos_relpath, scratch_pool))) { err = fetch_repos_info(&repos_root_url, &repos_uuid, &info_baton, resolved_url, scratch_pool, scratch_pool); if (err) goto handle_error; repos_relpath = svn_uri_skip_ancestor(repos_root_url, resolved_url, iterpool2); /* There's just the URL, no idea what kind the external is. * That's fine, as the external isn't even checked out yet. * The kind will be set during the next 'update'. */ external_kind = svn_node_unknown; } if (err) goto handle_error; peg_revision = (item->peg_revision.kind == svn_opt_revision_number ? item->peg_revision.value.number : SVN_INVALID_REVNUM); revision = (item->revision.kind == svn_opt_revision_number ? item->revision.value.number : SVN_INVALID_REVNUM); err = svn_wc__upgrade_add_external_info(ctx->wc_ctx, external_abspath, external_kind, externals_parent, repos_relpath, repos_root_url, repos_uuid, peg_revision, revision, iterpool2); handle_error: if (err) { svn_wc_notify_t *notify = svn_wc_create_notify(external_abspath, svn_wc_notify_failed_external, scratch_pool); notify->err = err; ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); svn_error_clear(err); /* Next external node, please... */ } } } svn_pool_destroy(iterpool); svn_pool_destroy(iterpool2); return SVN_NO_ERROR; }
/* This callback is called by the ra_layer for each path locked. * BATON is a 'struct lock_baton *', PATH is the path being locked, * and LOCK is the lock itself. * * If BATON->base_path is not null, then this function either stores * the LOCK on REL_URL or removes any lock tokens from REL_URL * (depending on whether DO_LOCK is true or false respectively), but * only if RA_ERR is null, or (in the unlock case) is something other * than SVN_ERR_FS_LOCK_OWNER_MISMATCH. * * Implements svn_ra_lock_callback_t. */ static svn_error_t * store_locks_callback(void *baton, const char *rel_url, svn_boolean_t do_lock, const svn_lock_t *lock, svn_error_t *ra_err, apr_pool_t *pool) { struct lock_baton *lb = baton; svn_wc_notify_t *notify; /* Create the notify struct first, so we can tweak it below. */ notify = svn_wc_create_notify(rel_url, do_lock ? (ra_err ? svn_wc_notify_failed_lock : svn_wc_notify_locked) : (ra_err ? svn_wc_notify_failed_unlock : svn_wc_notify_unlocked), pool); notify->lock = lock; notify->err = ra_err; if (lb->base_path) { char *path = apr_hash_get(lb->urls_to_paths, rel_url, APR_HASH_KEY_STRING); const char *local_abspath; SVN_ERR(svn_dirent_get_absolute(&local_abspath, svn_dirent_join(lb->base_path, path, pool), pool)); /* Notify a valid working copy path */ notify->path = local_abspath; notify->path_prefix = lb->base_path; if (do_lock) { if (!ra_err) { SVN_ERR(svn_wc_add_lock2(lb->ctx->wc_ctx, local_abspath, lock, lb->pool)); notify->lock_state = svn_wc_notify_lock_state_locked; } else notify->lock_state = svn_wc_notify_lock_state_unchanged; } else /* unlocking */ { /* Remove our wc lock token either a) if we got no error, or b) if we got any error except for owner mismatch. Note that the only errors that are handed to this callback will be locking-related errors. */ if (!ra_err || (ra_err && (ra_err->apr_err != SVN_ERR_FS_LOCK_OWNER_MISMATCH))) { SVN_ERR(svn_wc_remove_lock2(lb->ctx->wc_ctx, local_abspath, lb->pool)); notify->lock_state = svn_wc_notify_lock_state_unlocked; } else notify->lock_state = svn_wc_notify_lock_state_unchanged; } } else notify->url = rel_url; /* Notify that path is actually a url */ if (lb->ctx->notify_func2) lb->ctx->notify_func2(lb->ctx->notify_baton2, notify, pool); return SVN_NO_ERROR; }
/* Called when an external that is in the EXTERNALS table is no longer referenced from an svn:externals property */ static svn_error_t * handle_external_item_removal(const svn_client_ctx_t *ctx, const char *defining_abspath, const char *local_abspath, apr_pool_t *scratch_pool) { svn_error_t *err; svn_node_kind_t external_kind; svn_node_kind_t kind; svn_boolean_t removed = FALSE; /* local_abspath should be a wcroot or a file external */ SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, ctx->wc_ctx, defining_abspath, local_abspath, FALSE, scratch_pool, scratch_pool)); SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE, scratch_pool)); if (external_kind != kind) external_kind = svn_node_none; /* Only remove the registration */ err = remove_external(&removed, ctx->wc_ctx, defining_abspath, local_abspath, external_kind, ctx->cancel_func, ctx->cancel_baton, scratch_pool); if (err && err->apr_err == SVN_ERR_WC_NOT_LOCKED && removed) { svn_error_clear(err); err = NULL; /* We removed the working copy, so we can't release the lock that was stored inside */ } if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_external_removed, scratch_pool); notify->kind = kind; notify->err = err; (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD) { notify = svn_wc_create_notify(local_abspath, svn_wc_notify_left_local_modifications, scratch_pool); notify->kind = svn_node_dir; notify->err = err; (ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); } } if (err && err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD) { svn_error_clear(err); err = NULL; } return svn_error_trace(err); }
/* Copy the versioned file SRC_ABSPATH in DB to the path DST_ABSPATH in DB. If METADATA_ONLY is true, copy only the versioned metadata, otherwise copy both the versioned metadata and the filesystem node (even if it is the wrong kind, and recursively if it is a dir). If IS_MOVE is true, record move information in working copy meta data in addition to copying the file. If the versioned file has a text conflict, and the .mine file exists in the filesystem, copy the .mine file to DST_ABSPATH. Otherwise, copy the versioned file itself. This also works for versioned symlinks that are stored in the db as svn_node_file with svn:special set. */ static svn_error_t * copy_versioned_file(svn_wc__db_t *db, const char *src_abspath, const char *dst_abspath, const char *dst_op_root_abspath, const char *tmpdir_abspath, svn_boolean_t metadata_only, svn_boolean_t conflicted, svn_boolean_t is_move, svn_cancel_func_t cancel_func, void *cancel_baton, svn_wc_notify_func2_t notify_func, void *notify_baton, apr_pool_t *scratch_pool) { svn_skel_t *work_items = NULL; /* In case we are copying from one WC to another (e.g. an external dir), ensure the destination WC has a copy of the pristine text. */ /* Prepare a temp copy of the filesystem node. It is usually a file, but copy recursively if it's a dir. */ if (!metadata_only) { const char *my_src_abspath = NULL; svn_boolean_t handle_as_unversioned = FALSE; /* By default, take the copy source as given. */ my_src_abspath = src_abspath; if (conflicted) { svn_skel_t *conflict; const char *conflict_working; svn_error_t *err; /* Is there a text conflict at the source path? */ SVN_ERR(svn_wc__db_read_conflict(&conflict, db, src_abspath, scratch_pool, scratch_pool)); err = svn_wc__conflict_read_text_conflict(&conflict_working, NULL, NULL, db, src_abspath, conflict, scratch_pool, scratch_pool); if (err && err->apr_err == SVN_ERR_WC_MISSING) { /* not text conflicted */ svn_error_clear(err); conflict_working = NULL; } else SVN_ERR(err); if (conflict_working) { svn_node_kind_t working_kind; /* Does the ".mine" file exist? */ SVN_ERR(svn_io_check_path(conflict_working, &working_kind, scratch_pool)); if (working_kind == svn_node_file) { /* Don't perform unmodified/pristine optimization */ handle_as_unversioned = TRUE; my_src_abspath = conflict_working; } } } SVN_ERR(copy_to_tmpdir(&work_items, NULL, db, my_src_abspath, dst_abspath, tmpdir_abspath, TRUE /* file_copy */, handle_as_unversioned /* unversioned */, cancel_func, cancel_baton, scratch_pool, scratch_pool)); } /* Copy the (single) node's metadata, and move the new filesystem node into place. */ SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath, dst_op_root_abspath, is_move, work_items, scratch_pool)); if (notify_func) { svn_wc_notify_t *notify = svn_wc_create_notify(dst_abspath, svn_wc_notify_add, scratch_pool); notify->kind = svn_node_file; /* When we notify that we performed a copy, make sure we already did */ if (work_items != NULL) SVN_ERR(svn_wc__wq_run(db, dst_abspath, cancel_func, cancel_baton, scratch_pool)); (*notify_func)(notify_baton, notify, scratch_pool); } return SVN_NO_ERROR; }
svn_error_t * svn_wc_exclude(svn_wc_context_t *wc_ctx, const char *local_abspath, svn_cancel_func_t cancel_func, void *cancel_baton, svn_wc_notify_func2_t notify_func, void *notify_baton, apr_pool_t *scratch_pool) { svn_boolean_t is_root, is_switched; svn_wc__db_status_t status; svn_wc__db_kind_t kind; svn_revnum_t revision; const char *repos_relpath, *repos_root, *repos_uuid; SVN_ERR(svn_wc__check_wc_root(&is_root, NULL, &is_switched, wc_ctx->db, local_abspath, scratch_pool)); if (is_root) { return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot exclude '%s': " "it is a working copy root"), svn_dirent_local_style(local_abspath, scratch_pool)); } if (is_switched) { return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot exclude '%s': " "it is a switched path"), svn_dirent_local_style(local_abspath, scratch_pool)); } SVN_ERR(svn_wc__db_read_info(&status, &kind, &revision, &repos_relpath, &repos_root, &repos_uuid, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, wc_ctx->db, local_abspath, scratch_pool, scratch_pool)); switch (status) { case svn_wc__db_status_server_excluded: case svn_wc__db_status_excluded: case svn_wc__db_status_not_present: return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL, _("The node '%s' was not found."), svn_dirent_local_style(local_abspath, scratch_pool)); case svn_wc__db_status_added: /* Would have to check parents if we want to allow this */ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot exclude '%s': it is to be added " "to the repository. Try commit instead"), svn_dirent_local_style(local_abspath, scratch_pool)); case svn_wc__db_status_deleted: /* Would have to check parents if we want to allow this */ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot exclude '%s': it is to be deleted " "from the repository. Try commit instead"), svn_dirent_local_style(local_abspath, scratch_pool)); case svn_wc__db_status_normal: case svn_wc__db_status_incomplete: default: break; /* Ok to exclude */ } /* ### This could use some kind of transaction */ /* Remove all working copy data below local_abspath */ IGNORE_LOCAL_MOD(svn_wc__internal_remove_from_revision_control( wc_ctx->db, local_abspath, TRUE, FALSE, cancel_func, cancel_baton, scratch_pool)); SVN_ERR(svn_wc__db_base_add_excluded_node(wc_ctx->db, local_abspath, repos_relpath, repos_root, repos_uuid, revision, kind, svn_wc__db_status_excluded, NULL, NULL, scratch_pool)); if (notify_func) { svn_wc_notify_t *notify; notify = svn_wc_create_notify(local_abspath, svn_wc_notify_exclude, scratch_pool); notify_func(notify_baton, notify, scratch_pool); } return SVN_NO_ERROR; }
/* Helper function that crops the children of the LOCAL_ABSPATH, under the * constraint of NEW_DEPTH. The DIR_PATH itself will never be cropped. The * whole subtree should have been locked. * * DIR_DEPTH is the current depth of LOCAL_ABSPATH as stored in DB. * * If NOTIFY_FUNC is not null, each file and ROOT of subtree will be reported * upon remove. */ static svn_error_t * crop_children(svn_wc__db_t *db, const char *local_abspath, svn_depth_t dir_depth, svn_depth_t new_depth, svn_wc_notify_func2_t notify_func, void *notify_baton, svn_cancel_func_t cancel_func, void *cancel_baton, apr_pool_t *pool) { const apr_array_header_t *children; apr_pool_t *iterpool; int i; SVN_ERR_ASSERT(new_depth >= svn_depth_empty && new_depth <= svn_depth_infinity); if (cancel_func) SVN_ERR(cancel_func(cancel_baton)); iterpool = svn_pool_create(pool); if (dir_depth == svn_depth_unknown) dir_depth = svn_depth_infinity; /* Update the depth of target first, if needed. */ if (dir_depth > new_depth) SVN_ERR(svn_wc__db_op_set_base_depth(db, local_abspath, new_depth, iterpool)); /* Looping over current directory's SVN entries: */ SVN_ERR(svn_wc__db_read_children(&children, db, local_abspath, pool, iterpool)); for (i = 0; i < children->nelts; i++) { const char *child_name = APR_ARRAY_IDX(children, i, const char *); const char *child_abspath; svn_wc__db_status_t child_status; svn_wc__db_kind_t kind; svn_depth_t child_depth; svn_pool_clear(iterpool); /* Get the next node */ child_abspath = svn_dirent_join(local_abspath, child_name, iterpool); SVN_ERR(svn_wc__db_read_info(&child_status, &kind, NULL, NULL, NULL, NULL,NULL, NULL, NULL, &child_depth, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL, db, child_abspath, iterpool, iterpool)); if (child_status == svn_wc__db_status_server_excluded || child_status == svn_wc__db_status_excluded || child_status == svn_wc__db_status_not_present) { svn_depth_t remove_below = (kind == svn_wc__db_kind_dir) ? svn_depth_immediates : svn_depth_files; if (new_depth < remove_below) SVN_ERR(svn_wc__db_op_remove_node(db, local_abspath, SVN_INVALID_REVNUM, svn_wc__db_kind_unknown, iterpool)); continue; } else if (kind == svn_wc__db_kind_file) { /* We currently crop on a directory basis. So don't worry about svn_depth_exclude here. And even we permit excluding a single file in the future, svn_wc_remove_from_revision_control() can also handle it. We only need to skip the notification in that case. */ if (new_depth == svn_depth_empty) IGNORE_LOCAL_MOD( svn_wc__internal_remove_from_revision_control( db, child_abspath, TRUE, /* destroy */ FALSE, /* instant error */ cancel_func, cancel_baton, iterpool)); else continue; } else if (kind == svn_wc__db_kind_dir) { if (new_depth < svn_depth_immediates) { IGNORE_LOCAL_MOD( svn_wc__internal_remove_from_revision_control( db, child_abspath, TRUE, /* destroy */ FALSE, /* instant error */ cancel_func, cancel_baton, iterpool)); } else { SVN_ERR(crop_children(db, child_abspath, child_depth, svn_depth_empty, notify_func, notify_baton, cancel_func, cancel_baton, iterpool)); continue; } } else { return svn_error_createf (SVN_ERR_NODE_UNKNOWN_KIND, NULL, _("Unknown node kind for '%s'"), svn_dirent_local_style(child_abspath, iterpool)); } if (notify_func) { svn_wc_notify_t *notify; notify = svn_wc_create_notify(child_abspath, svn_wc_notify_delete, iterpool); (*notify_func)(notify_baton, notify, iterpool); } } svn_pool_destroy(iterpool); return SVN_NO_ERROR; }
/* This is a helper for svn_client__update_internal(), which see for an explanation of most of these parameters. Some stuff that's unique is as follows: ANCHOR_ABSPATH is the local absolute path of the update anchor. This is typically either the same as LOCAL_ABSPATH, or the immediate parent of LOCAL_ABSPATH. If NOTIFY_SUMMARY is set (and there's a notification handler in CTX), transmit the final update summary upon successful completion of the update. */ static svn_error_t * update_internal(svn_revnum_t *result_rev, 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 *timestamp_sleep, svn_boolean_t notify_summary, svn_client_ctx_t *ctx, apr_pool_t *pool) { const svn_delta_editor_t *update_editor; void *update_edit_baton; const svn_ra_reporter3_t *reporter; void *report_baton; const char *anchor_url; const char *corrected_url; const char *target; const char *repos_root; svn_error_t *err; svn_revnum_t revnum; svn_boolean_t use_commit_times; svn_boolean_t sleep_here = FALSE; svn_boolean_t *use_sleep = timestamp_sleep ? timestamp_sleep : &sleep_here; svn_boolean_t clean_checkout = FALSE; const char *diff3_cmd; svn_ra_session_t *ra_session; const char *preserved_exts_str; apr_array_header_t *preserved_exts; struct svn_client__dirent_fetcher_baton_t dfb; svn_boolean_t server_supports_depth; svn_boolean_t tree_conflicted; svn_config_t *cfg = ctx->config ? apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG, APR_HASH_KEY_STRING) : NULL; /* An unknown depth can't be sticky. */ if (depth == svn_depth_unknown) depth_is_sticky = FALSE; if (strcmp(local_abspath, anchor_abspath)) target = svn_dirent_basename(local_abspath, pool); else target = ""; /* Get full URL from the ANCHOR. */ SVN_ERR(svn_wc__node_get_url(&anchor_url, ctx->wc_ctx, anchor_abspath, pool, pool)); if (! anchor_url) return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, _("'%s' has no URL"), svn_dirent_local_style(anchor_abspath, pool)); /* Check if our anchor exists in BASE. If it doesn't we can't update. ### For performance reasons this should be handled with the same query ### as retrieving the anchor url. */ SVN_ERR(svn_wc__node_get_base_rev(&revnum, ctx->wc_ctx, anchor_abspath, pool)); /* It does not make sense to update tree-conflict victims. */ err = svn_wc_conflicted_p3(NULL, NULL, &tree_conflicted, ctx->wc_ctx, local_abspath, pool); if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) { svn_error_clear(err); tree_conflicted = FALSE; } else SVN_ERR(err); if (!SVN_IS_VALID_REVNUM(revnum) || tree_conflicted) { if (ctx->notify_func2) { svn_wc_notify_t *nt; nt = svn_wc_create_notify(local_abspath, tree_conflicted ? svn_wc_notify_skip_conflicted : svn_wc_notify_update_skip_working_only, pool); ctx->notify_func2(ctx->notify_baton2, nt, pool); } return SVN_NO_ERROR; } /* We may need to crop the tree if the depth is sticky */ if (depth_is_sticky && depth < svn_depth_infinity) { svn_node_kind_t target_kind; if (depth == svn_depth_exclude) { SVN_ERR(svn_wc_exclude(ctx->wc_ctx, local_abspath, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); /* Target excluded, we are done now */ return SVN_NO_ERROR; } SVN_ERR(svn_wc_read_kind(&target_kind, ctx->wc_ctx, local_abspath, TRUE, pool)); if (target_kind == svn_node_dir) { SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); } } /* check whether the "clean c/o" optimization is applicable */ SVN_ERR(is_empty_wc(&clean_checkout, local_abspath, anchor_abspath, pool)); /* Get the external diff3, if any. */ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF3_CMD, NULL); if (diff3_cmd != NULL) SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool)); /* See if the user wants last-commit timestamps instead of current ones. */ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); /* See which files the user wants to preserve the extension of when conflict files are made. */ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); preserved_exts = *preserved_exts_str ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool) : NULL; /* Let everyone know we're starting a real update (unless we're asked not to). */ if (ctx->notify_func2 && notify_summary) { svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_started, pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } /* Open an RA session for the URL */ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, &corrected_url, anchor_url, anchor_abspath, NULL, TRUE, TRUE, ctx, pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root, pool)); /* If we got a corrected URL from the RA subsystem, we'll need to relocate our working copy first. */ if (corrected_url) { const char *current_repos_root; const char *current_uuid; /* To relocate everything inside our repository we need the old and new repos root. ### And we should only perform relocates on the wcroot */ SVN_ERR(svn_wc__node_get_repos_info(¤t_repos_root, ¤t_uuid, ctx->wc_ctx, anchor_abspath, pool, pool)); /* ### Check uuid here before calling relocate? */ SVN_ERR(svn_client_relocate2(anchor_abspath, current_repos_root, repos_root, ignore_externals, ctx, pool)); anchor_url = corrected_url; } /* ### todo: shouldn't svn_client__get_revision_number be able to take a URL as easily as a local path? */ SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx, local_abspath, ra_session, revision, pool)); SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); dfb.ra_session = ra_session; dfb.target_revision = revnum; dfb.anchor_url = anchor_url; /* Fetch the update editor. If REVISION is invalid, that's okay; the RA driver will call editor->set_target_revision later on. */ SVN_ERR(svn_wc_get_update_editor4(&update_editor, &update_edit_baton, &revnum, ctx->wc_ctx, anchor_abspath, target, use_commit_times, depth, depth_is_sticky, allow_unver_obstructions, adds_as_modification, server_supports_depth, clean_checkout, diff3_cmd, preserved_exts, svn_client__dirent_fetcher, &dfb, ctx->conflict_func2, ctx->conflict_baton2, NULL, NULL, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool, pool)); /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an invalid revnum, that means RA will use the latest revision. */ SVN_ERR(svn_ra_do_update2(ra_session, &reporter, &report_baton, revnum, target, (!server_supports_depth || depth_is_sticky ? depth : svn_depth_unknown), FALSE, update_editor, update_edit_baton, pool)); /* Drive the reporter structure, describing the revisions within PATH. When we call reporter->finish_report, the update_editor will be driven by svn_repos_dir_delta2. */ err = svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter, report_baton, TRUE, depth, (! depth_is_sticky), (! server_supports_depth), use_commit_times, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool); if (err) { /* Don't rely on the error handling to handle the sleep later, do it now */ svn_io_sleep_for_timestamps(local_abspath, pool); return svn_error_trace(err); } *use_sleep = TRUE; /* We handle externals after the update is complete, so that handling external items (and any errors therefrom) doesn't delay the primary operation. */ if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals)) { apr_hash_t *new_externals; apr_hash_t *new_depths; SVN_ERR(svn_wc__externals_gather_definitions(&new_externals, &new_depths, ctx->wc_ctx, local_abspath, depth, pool, pool)); SVN_ERR(svn_client__handle_externals(new_externals, new_depths, repos_root, local_abspath, depth, use_sleep, ctx, pool)); } if (sleep_here) svn_io_sleep_for_timestamps(local_abspath, pool); /* Let everyone know we're finished here (unless we're asked not to). */ if (ctx->notify_func2 && notify_summary) { svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed, pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; notify->revision = revnum; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = revnum; return SVN_NO_ERROR; }
svn_error_t * svn_client__switch_internal(svn_revnum_t *result_rev, const char *path, const char *switch_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, svn_wc_adm_access_t *adm_access, svn_depth_t depth, svn_boolean_t depth_is_sticky, svn_boolean_t *timestamp_sleep, svn_boolean_t ignore_externals, svn_boolean_t allow_unver_obstructions, svn_client_ctx_t *ctx, apr_pool_t *pool) { const svn_ra_reporter3_t *reporter; void *report_baton; const svn_wc_entry_t *entry; const char *URL, *anchor, *target, *source_root, *switch_rev_url; svn_ra_session_t *ra_session; svn_revnum_t revnum; svn_error_t *err = SVN_NO_ERROR; svn_wc_adm_access_t *dir_access; const svn_boolean_t close_adm_access = ! adm_access; const char *diff3_cmd; svn_boolean_t use_commit_times; svn_boolean_t sleep_here; svn_boolean_t *use_sleep = timestamp_sleep ? timestamp_sleep : &sleep_here; const svn_delta_editor_t *switch_editor; void *switch_edit_baton; svn_wc_traversal_info_t *traversal_info = svn_wc_init_traversal_info(pool); const char *preserved_exts_str; apr_array_header_t *preserved_exts; svn_boolean_t server_supports_depth; svn_config_t *cfg = ctx->config ? apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG, APR_HASH_KEY_STRING) : NULL; /* An unknown depth can't be sticky. */ if (depth == svn_depth_unknown) depth_is_sticky = FALSE; /* Do not support the situation of both exclude and switch a target. */ if (depth_is_sticky && depth == svn_depth_exclude) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot both exclude and switch a path")); /* Get the external diff3, if any. */ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF3_CMD, NULL); /* See if the user wants last-commit timestamps instead of current ones. */ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); /* See which files the user wants to preserve the extension of when conflict files are made. */ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); preserved_exts = *preserved_exts_str ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool) : NULL; /* Sanity check. Without these, the switch is meaningless. */ SVN_ERR_ASSERT(path); SVN_ERR_ASSERT(switch_url && (switch_url[0] != '\0')); /* ### Need to lock the whole target tree to invalidate wcprops. Does non-recursive switch really need to invalidate the whole tree? */ if (adm_access) { svn_wc_adm_access_t *a = adm_access; const char *dir_access_path; /* This is a little hacky, but open two new read-only access baton's to get the anchor and target access batons that would be used if a locked access baton was not available. */ SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &dir_access, &target, path, FALSE, -1, ctx->cancel_func, ctx->cancel_baton, pool)); anchor = svn_wc_adm_access_path(adm_access); dir_access_path = svn_wc_adm_access_path(dir_access); SVN_ERR(svn_wc_adm_close2(adm_access, pool)); SVN_ERR(svn_wc_adm_retrieve(&adm_access, a, anchor, pool)); SVN_ERR(svn_wc_adm_retrieve(&dir_access, a, dir_access_path, pool)); } else { SVN_ERR(svn_wc_adm_open_anchor(&adm_access, &dir_access, &target, path, TRUE, -1, ctx->cancel_func, ctx->cancel_baton, pool)); anchor = svn_wc_adm_access_path(adm_access); } SVN_ERR(svn_wc__entry_versioned(&entry, anchor, adm_access, FALSE, pool)); if (! entry->url) return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, _("Directory '%s' has no URL"), svn_path_local_style(anchor, pool)); URL = apr_pstrdup(pool, entry->url); /* Open an RA session to 'source' URL */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &revnum, &switch_rev_url, switch_url, adm_access, peg_revision, revision, ctx, pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &source_root, pool)); /* Disallow a switch operation to change the repository root of the target. */ if (! svn_path_is_ancestor(source_root, URL)) return svn_error_createf (SVN_ERR_WC_INVALID_SWITCH, NULL, _("'%s'\n" "is not the same repository as\n" "'%s'"), URL, source_root); /* We may need to crop the tree if the depth is sticky */ if (depth_is_sticky && depth < svn_depth_infinity) { const svn_wc_entry_t *target_entry; SVN_ERR(svn_wc_entry( &target_entry, svn_dirent_join(svn_wc_adm_access_path(adm_access), target, pool), adm_access, TRUE, pool)); if (target_entry && target_entry->kind == svn_node_dir) { SVN_ERR(svn_wc_crop_tree(adm_access, target, depth, ctx->notify_func2, ctx->notify_baton2, ctx->cancel_func, ctx->cancel_baton, pool)); } } SVN_ERR(svn_ra_reparent(ra_session, URL, pool)); /* Fetch the switch (update) editor. If REVISION is invalid, that's okay; the RA driver will call editor->set_target_revision() later on. */ SVN_ERR(svn_wc_get_switch_editor3(&revnum, adm_access, target, switch_rev_url, use_commit_times, depth, depth_is_sticky, allow_unver_obstructions, ctx->notify_func2, ctx->notify_baton2, ctx->cancel_func, ctx->cancel_baton, ctx->conflict_func, ctx->conflict_baton, diff3_cmd, preserved_exts, &switch_editor, &switch_edit_baton, traversal_info, pool)); /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an invalid revnum, that means RA will use the latest revision. */ SVN_ERR(svn_ra_do_switch2(ra_session, &reporter, &report_baton, revnum, target, depth, switch_rev_url, switch_editor, switch_edit_baton, pool)); SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); /* Drive the reporter structure, describing the revisions within PATH. When we call reporter->finish_report, the update_editor will be driven by svn_repos_dir_delta2. We pass in a traversal_info for recording all externals. It shouldn't be needed for a switch if it wasn't for the relative externals of type '../path'. All of those must be resolved to the new location. */ err = svn_wc_crawl_revisions4(path, dir_access, reporter, report_baton, TRUE, depth, (! depth_is_sticky), (! server_supports_depth), use_commit_times, ctx->notify_func2, ctx->notify_baton2, traversal_info, pool); if (err) { /* Don't rely on the error handling to handle the sleep later, do it now */ svn_io_sleep_for_timestamps(path, pool); return err; } *use_sleep = TRUE; /* We handle externals after the switch is complete, so that handling external items (and any errors therefrom) doesn't delay the primary operation. */ if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals)) err = svn_client__handle_externals(adm_access, traversal_info, switch_url, path, source_root, depth, use_sleep, ctx, pool); /* Sleep to ensure timestamp integrity (we do this regardless of errors in the actual switch operation(s)). */ if (sleep_here) svn_io_sleep_for_timestamps(path, pool); /* Return errors we might have sustained. */ if (err) return err; if (close_adm_access) SVN_ERR(svn_wc_adm_close2(adm_access, pool)); /* Let everyone know we're finished here. */ if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(anchor, svn_wc_notify_update_completed, pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; notify->revision = revnum; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = revnum; return SVN_NO_ERROR; }
svn_error_t * svn_client_upgrade(const char *path, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { const char *local_abspath; apr_hash_t *externals; apr_hash_index_t *hi; apr_pool_t *iterpool; apr_pool_t *iterpool2; svn_opt_revision_t rev = {svn_opt_revision_unspecified, {0}}; struct repos_info_baton info_baton; info_baton.state_pool = scratch_pool; info_baton.ctx = ctx; info_baton.last_repos = NULL; info_baton.last_uuid = NULL; if (svn_path_is_url(path)) return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("'%s' is not a local path"), path); SVN_ERR(svn_dirent_get_absolute(&local_abspath, path, scratch_pool)); SVN_ERR(svn_wc_upgrade(ctx->wc_ctx, local_abspath, fetch_repos_info, &info_baton, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, scratch_pool)); /* Now it's time to upgrade the externals too. We do it after the wc upgrade to avoid that errors in the externals causes the wc upgrade to fail. Thanks to caching the performance penalty of walking the wc a second time shouldn't be too severe */ SVN_ERR(svn_client_propget4(&externals, SVN_PROP_EXTERNALS, local_abspath, &rev, &rev, NULL, svn_depth_infinity, NULL, ctx, scratch_pool, scratch_pool)); iterpool = svn_pool_create(scratch_pool); iterpool2 = svn_pool_create(scratch_pool); for (hi = apr_hash_first(scratch_pool, externals); hi; hi = apr_hash_next(hi)) { int i; const char *externals_parent = svn__apr_hash_index_key(hi); svn_string_t *external_desc = svn__apr_hash_index_val(hi); apr_array_header_t *externals_p; svn_pool_clear(iterpool); externals_p = apr_array_make(iterpool, 1, sizeof(svn_wc_external_item2_t*)); SVN_ERR(svn_wc_parse_externals_description3( &externals_p, svn_dirent_dirname(path, iterpool), external_desc->data, TRUE, iterpool)); for (i = 0; i < externals_p->nelts; i++) { svn_wc_external_item2_t *item; const char *external_abspath; const char *external_path; const char *repos_relpath; const char *repos_root_url; const char *repos_uuid; svn_node_kind_t kind; svn_revnum_t peg_revision; svn_revnum_t revision; svn_error_t *err; item = APR_ARRAY_IDX(externals_p, i, svn_wc_external_item2_t*); svn_pool_clear(iterpool2); external_path = svn_dirent_join(externals_parent, item->target_dir, iterpool2); err = svn_dirent_get_absolute(&external_abspath, external_path, iterpool2); if (err) goto handle_error; /* This is hack. We can only send dirs to svn_wc_upgrade(). This way we will get an exception saying that the wc must be upgraded if it's a dir. If it's a file then the lookup is done in an adm_dir belonging to the real wc and since that was updated before the externals no error is returned. */ err = svn_wc_read_kind(&kind, ctx->wc_ctx, external_abspath, FALSE, iterpool2); if (err && err->apr_err == SVN_ERR_WC_UPGRADE_REQUIRED) { svn_error_clear(err); err = svn_client_upgrade(external_abspath, ctx, iterpool2); } if (err) goto handle_error; /* The upgrade of any dir should be done now, get the (supposedly * now reliable) kind. */ err = svn_wc_read_kind(&kind, ctx->wc_ctx, external_abspath, FALSE, iterpool2); if (err) goto handle_error; /* Update the EXTERNALS table according to the root URL, * relpath and uuid known in the upgraded external WC. */ /* We should probably have a function that provides all three * of root URL, repos relpath and uuid at once, but here goes... */ /* First get the relpath, as that returns SVN_ERR_WC_PATH_NOT_FOUND * when the node is not present in the file system. * (svn_wc__node_get_repos_info() would try to derive the URL). */ err = svn_wc__node_get_repos_relpath(&repos_relpath, ctx->wc_ctx, external_abspath, iterpool2, iterpool2); if (! err) { /* We got a repos relpath from a WC. So also get the root. */ err = svn_wc__node_get_repos_info(&repos_root_url, &repos_uuid, ctx->wc_ctx, external_abspath, iterpool2, iterpool2); } else if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND) { /* The external is not currently checked out. Try to figure out * the URL parts via the defined URL and fetch_repos_info(). */ svn_error_clear(err); /* The repos root / uuid from above get_repos_info() call, if it * was successful, has returned the URL as derived from the WC's * parent path, which is not what we want for the external. Only * makes sense for added/deleted/not-present files. So make sure * those values are not used. */ repos_root_url = NULL; repos_relpath = NULL; err = fetch_repos_info(&repos_root_url, &repos_uuid, &info_baton, item->url, scratch_pool, scratch_pool); if (err) goto handle_error; repos_relpath = svn_uri_skip_ancestor(repos_root_url, item->url, iterpool2); /* There's just this URL, no idea what kind it is. */ kind = svn_node_unknown; } if (err) goto handle_error; peg_revision = (item->peg_revision.kind == svn_opt_revision_number ? item->peg_revision.value.number : SVN_INVALID_REVNUM); revision = (item->revision.kind == svn_opt_revision_number ? item->revision.value.number : SVN_INVALID_REVNUM); err = svn_wc__upgrade_add_external_info(ctx->wc_ctx, external_abspath, kind, externals_parent, repos_relpath, repos_root_url, repos_uuid, peg_revision, revision, iterpool2); handle_error: if (err) { svn_wc_notify_t *notify = svn_wc_create_notify(external_abspath, svn_wc_notify_failed_external, scratch_pool); notify->err = err; ctx->notify_func2(ctx->notify_baton2, notify, scratch_pool); svn_error_clear(err); } } } svn_pool_destroy(iterpool); svn_pool_destroy(iterpool2); return SVN_NO_ERROR; }
/* Try to update a file external at LOCAL_ABSPATH to URL at REVISION using a access baton that has a write lock. Use SCRATCH_POOL for temporary allocations, and use the client context CTX. */ static svn_error_t * switch_file_external(const char *local_abspath, const char *url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, const char *def_dir_abspath, svn_ra_session_t *ra_session, svn_client_ctx_t *ctx, apr_pool_t *scratch_pool) { svn_config_t *cfg = ctx->config ? svn_hash_gets(ctx->config, SVN_CONFIG_CATEGORY_CONFIG) : NULL; svn_boolean_t use_commit_times; const char *diff3_cmd; const char *preserved_exts_str; const apr_array_header_t *preserved_exts; svn_node_kind_t kind, external_kind; SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath)); /* See if the user wants last-commit timestamps instead of current ones. */ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); /* Get the external diff3, if any. */ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF3_CMD, NULL); if (diff3_cmd != NULL) SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, scratch_pool)); /* See which files the user wants to preserve the extension of when conflict files are made. */ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); preserved_exts = *preserved_exts_str ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, scratch_pool) : NULL; { const char *wcroot_abspath; SVN_ERR(svn_wc__get_wcroot(&wcroot_abspath, ctx->wc_ctx, local_abspath, scratch_pool, scratch_pool)); /* File externals can only be installed inside the current working copy. So verify if the working copy that contains/will contain the target is the defining abspath, or one of its ancestors */ if (!svn_dirent_is_ancestor(wcroot_abspath, def_dir_abspath)) return svn_error_createf( SVN_ERR_WC_BAD_PATH, NULL, _("Cannot insert a file external defined on '%s' " "into the working copy '%s'."), svn_dirent_local_style(def_dir_abspath, scratch_pool), svn_dirent_local_style(wcroot_abspath, scratch_pool)); } SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, TRUE, FALSE, scratch_pool)); SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, ctx->wc_ctx, local_abspath, local_abspath, TRUE, scratch_pool, scratch_pool)); /* If there is a versioned item with this name, ensure it's a file external before working with it. If there is no entry in the working copy, then create an empty file and add it to the working copy. */ if (kind != svn_node_none && kind != svn_node_unknown) { if (external_kind != svn_node_file) { return svn_error_createf( SVN_ERR_CLIENT_FILE_EXTERNAL_OVERWRITE_VERSIONED, 0, _("The file external from '%s' cannot overwrite the existing " "versioned item at '%s'"), url, svn_dirent_local_style(local_abspath, scratch_pool)); } } else { svn_node_kind_t disk_kind; SVN_ERR(svn_io_check_path(local_abspath, &disk_kind, scratch_pool)); if (kind == svn_node_file || kind == svn_node_dir) return svn_error_createf(SVN_ERR_WC_PATH_FOUND, NULL, _("The file external '%s' can not be " "created because the node exists."), svn_dirent_local_style(local_abspath, scratch_pool)); } { const svn_ra_reporter3_t *reporter; void *report_baton; const svn_delta_editor_t *switch_editor; void *switch_baton; svn_client__pathrev_t *switch_loc; svn_revnum_t revnum; apr_array_header_t *inherited_props; const char *dir_abspath; const char *target; svn_dirent_split(&dir_abspath, &target, local_abspath, scratch_pool); /* ### Why do we open a new session? RA_SESSION is a valid ### session -- the caller used it to call svn_ra_check_path on ### this very URL, the caller also did the resolving and ### reparenting that is repeated here. */ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &switch_loc, url, dir_abspath, peg_revision, revision, ctx, scratch_pool)); /* Get the external file's iprops. */ SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, "", switch_loc->rev, scratch_pool, scratch_pool)); SVN_ERR(svn_ra_reparent(ra_session, svn_uri_dirname(url, scratch_pool), scratch_pool)); SVN_ERR(svn_wc__get_file_external_editor(&switch_editor, &switch_baton, &revnum, ctx->wc_ctx, local_abspath, def_dir_abspath, switch_loc->url, switch_loc->repos_root_url, switch_loc->repos_uuid, inherited_props, use_commit_times, diff3_cmd, preserved_exts, def_dir_abspath, url, peg_revision, revision, ctx->conflict_func2, ctx->conflict_baton2, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, scratch_pool, scratch_pool)); /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an invalid revnum, that means RA will use the latest revision. */ SVN_ERR(svn_ra_do_switch3(ra_session, &reporter, &report_baton, switch_loc->rev, target, svn_depth_unknown, switch_loc->url, FALSE /* send_copyfrom */, TRUE /* ignore_ancestry */, switch_editor, switch_baton, scratch_pool, scratch_pool)); SVN_ERR(svn_wc__crawl_file_external(ctx->wc_ctx, local_abspath, reporter, report_baton, TRUE, use_commit_times, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, scratch_pool)); if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, svn_wc_notify_update_completed, scratch_pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; notify->revision = revnum; (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); } } return SVN_NO_ERROR; }
/* Make an unversioned copy of the versioned file at FROM_ABSPATH. Copy it * to the destination path TO_ABSPATH. * * If REVISION is svn_opt_revision_working, copy the working version, * otherwise copy the base version. * * Expand the file's keywords according to the source file's 'svn:keywords' * property, if present. If copying a locally modified working version, * append 'M' to the revision number and use '(local)' for the author. * * Translate the file's line endings according to the source file's * 'svn:eol-style' property, if present. If NATIVE_EOL is not NULL, use it * in place of the native EOL style. Throw an error if the source file has * inconsistent line endings and EOL translation is attempted. * * Set the destination file's modification time to the source file's * modification time if copying the working version and the working version * is locally modified; otherwise set it to the versioned file's last * changed time. * * Set the destination file's 'executable' flag according to the source * file's 'svn:executable' property. */ static svn_error_t * copy_one_versioned_file(const char *from_abspath, const char *to_abspath, svn_client_ctx_t *ctx, const svn_opt_revision_t *revision, const char *native_eol, svn_boolean_t ignore_keywords, apr_pool_t *scratch_pool) { apr_hash_t *kw = NULL; svn_subst_eol_style_t style; apr_hash_t *props; svn_string_t *eol_style, *keywords, *executable, *special; const char *eol = NULL; svn_boolean_t local_mod = FALSE; apr_time_t tm; svn_stream_t *source; svn_stream_t *dst_stream; const char *dst_tmp; svn_error_t *err; svn_boolean_t is_deleted; svn_wc_context_t *wc_ctx = ctx->wc_ctx; SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, wc_ctx, from_abspath, scratch_pool)); /* Don't export 'deleted' files and directories unless it's a revision other than WORKING. These files and directories don't really exist in WORKING. */ if (revision->kind == svn_opt_revision_working && is_deleted) return SVN_NO_ERROR; if (revision->kind != svn_opt_revision_working) { /* Only export 'added' files when the revision is WORKING. This is not WORKING, so skip the 'added' files, since they didn't exist in the BASE revision and don't have an associated text-base. 'replaced' files are technically the same as 'added' files. ### TODO: Handle replaced nodes properly. ### svn_opt_revision_base refers to the "new" ### base of the node. That means, if a node is locally ### replaced, export skips this node, as if it was locally ### added, because svn_opt_revision_base refers to the base ### of the added node, not to the node that was deleted. ### In contrast, when the node is copied-here or moved-here, ### the copy/move source's content will be exported. ### It is currently not possible to export the revert-base ### when a node is locally replaced. We need a new ### svn_opt_revision_ enum value for proper distinction ### between revert-base and commit-base. Copied-/moved-here nodes have a base, so export both added and replaced files when they involve a copy-/move-here. We get all this for free from evaluating SOURCE == NULL: */ SVN_ERR(svn_wc_get_pristine_contents2(&source, wc_ctx, from_abspath, scratch_pool, scratch_pool)); if (source == NULL) return SVN_NO_ERROR; SVN_ERR(svn_wc_get_pristine_props(&props, wc_ctx, from_abspath, scratch_pool, scratch_pool)); } else { svn_wc_status3_t *status; /* ### hmm. this isn't always a specialfile. this will simply open ### the file readonly if it is a regular file. */ SVN_ERR(svn_subst_read_specialfile(&source, from_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_wc_prop_list2(&props, wc_ctx, from_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_wc_status3(&status, wc_ctx, from_abspath, scratch_pool, scratch_pool)); if (status->text_status != svn_wc_status_normal) local_mod = TRUE; } /* We can early-exit if we're creating a special file. */ special = apr_hash_get(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING); if (special != NULL) { /* Create the destination as a special file, and copy the source details into the destination stream. */ SVN_ERR(svn_subst_create_specialfile(&dst_stream, to_abspath, scratch_pool, scratch_pool)); return svn_error_trace( svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool)); } eol_style = apr_hash_get(props, SVN_PROP_EOL_STYLE, APR_HASH_KEY_STRING); keywords = apr_hash_get(props, SVN_PROP_KEYWORDS, APR_HASH_KEY_STRING); executable = apr_hash_get(props, SVN_PROP_EXECUTABLE, APR_HASH_KEY_STRING); if (eol_style) SVN_ERR(get_eol_style(&style, &eol, eol_style->data, native_eol)); if (local_mod) { /* Use the modified time from the working copy of the file */ SVN_ERR(svn_io_file_affected_time(&tm, from_abspath, scratch_pool)); } else { SVN_ERR(svn_wc__node_get_changed_info(NULL, &tm, NULL, wc_ctx, from_abspath, scratch_pool, scratch_pool)); } if (keywords) { svn_revnum_t changed_rev; const char *suffix; const char *url; const char *author; SVN_ERR(svn_wc__node_get_changed_info(&changed_rev, NULL, &author, wc_ctx, from_abspath, scratch_pool, scratch_pool)); if (local_mod) { /* For locally modified files, we'll append an 'M' to the revision number, and set the author to "(local)" since we can't always determine the current user's username */ suffix = "M"; author = _("(local)"); } else { suffix = ""; } SVN_ERR(svn_wc__node_get_url(&url, wc_ctx, from_abspath, scratch_pool, scratch_pool)); SVN_ERR(svn_subst_build_keywords2 (&kw, keywords->data, apr_psprintf(scratch_pool, "%ld%s", changed_rev, suffix), url, tm, author, scratch_pool)); } /* For atomicity, we translate to a tmp file and then rename the tmp file over the real destination. */ SVN_ERR(svn_stream_open_unique(&dst_stream, &dst_tmp, svn_dirent_dirname(to_abspath, scratch_pool), svn_io_file_del_none, scratch_pool, scratch_pool)); /* If some translation is needed, then wrap the output stream (this is more efficient than wrapping the input). */ if (eol || (kw && (apr_hash_count(kw) > 0))) dst_stream = svn_subst_stream_translated(dst_stream, eol, FALSE /* repair */, kw, ! ignore_keywords /* expand */, scratch_pool); /* ###: use cancel func/baton in place of NULL/NULL below. */ err = svn_stream_copy3(source, dst_stream, NULL, NULL, scratch_pool); if (!err && executable) err = svn_io_set_file_executable(dst_tmp, TRUE, FALSE, scratch_pool); if (!err) err = svn_io_set_file_affected_time(tm, dst_tmp, scratch_pool); if (err) return svn_error_compose_create(err, svn_io_remove_file2(dst_tmp, FALSE, scratch_pool)); /* Now that dst_tmp contains the translated data, do the atomic rename. */ SVN_ERR(svn_io_file_rename(dst_tmp, to_abspath, scratch_pool)); if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(to_abspath, svn_wc_notify_update_add, scratch_pool); notify->kind = svn_node_file; (*ctx->notify_func2)(ctx->notify_baton2, notify, scratch_pool); } return SVN_NO_ERROR; }
/* Remove the directory at LOCAL_ABSPATH from revision control, and do the * same to any revision controlled directories underneath LOCAL_ABSPATH * (including directories not referred to by parent svn administrative areas); * then if LOCAL_ABSPATH is empty afterwards, remove it, else rename it to a * unique name in the same parent directory. * * Pass CANCEL_FUNC, CANCEL_BATON to svn_wc_remove_from_revision_control. * * Use SCRATCH_POOL for all temporary allocation. */ static svn_error_t * relegate_dir_external(svn_wc_context_t *wc_ctx, const char *wri_abspath, const char *local_abspath, svn_cancel_func_t cancel_func, void *cancel_baton, svn_wc_notify_func2_t notify_func, void *notify_baton, apr_pool_t *scratch_pool) { svn_error_t *err = SVN_NO_ERROR; SVN_ERR(svn_wc__acquire_write_lock(NULL, wc_ctx, local_abspath, FALSE, scratch_pool, scratch_pool)); err = svn_wc__external_remove(wc_ctx, wri_abspath, local_abspath, FALSE, cancel_func, cancel_baton, scratch_pool); if (err && (err->apr_err == SVN_ERR_WC_LEFT_LOCAL_MOD)) { const char *parent_dir; const char *dirname; const char *new_path; svn_error_clear(err); err = SVN_NO_ERROR; svn_dirent_split(&parent_dir, &dirname, local_abspath, scratch_pool); /* Reserve the new dir name. */ SVN_ERR(svn_io_open_uniquely_named(NULL, &new_path, parent_dir, dirname, ".OLD", svn_io_file_del_none, scratch_pool, scratch_pool)); /* Sigh... We must fall ever so slightly from grace. Ideally, there would be no window, however brief, when we don't have a reservation on the new name. Unfortunately, at least in the Unix (Linux?) version of apr_file_rename(), you can't rename a directory over a file, because it's just calling stdio rename(), which says: ENOTDIR A component used as a directory in oldpath or newpath path is not, in fact, a directory. Or, oldpath is a directory, and newpath exists but is not a directory So instead, we get the name, then remove the file (ugh), then rename the directory, hoping that nobody has gotten that name in the meantime -- which would never happen in real life, so no big deal. */ /* Do our best, but no biggy if it fails. The rename will fail. */ svn_error_clear(svn_io_remove_file2(new_path, TRUE, scratch_pool)); /* Rename. If this is still a working copy we should use the working copy rename function (to release open handles) */ err = svn_wc__rename_wc(wc_ctx, local_abspath, new_path, scratch_pool); if (err && err->apr_err == SVN_ERR_WC_PATH_UNEXPECTED_STATUS) { svn_error_clear(err); /* And if it is no longer a working copy, we should just rename it */ err = svn_io_file_rename(local_abspath, new_path, scratch_pool); } /* ### TODO: We should notify the user about the rename */ if (notify_func) { svn_wc_notify_t *notify; notify = svn_wc_create_notify(err ? local_abspath : new_path, svn_wc_notify_left_local_modifications, scratch_pool); notify->kind = svn_node_dir; notify->err = err; notify_func(notify_baton, notify, scratch_pool); } } return svn_error_trace(err); }
/* Copy the versioned dir SRC_ABSPATH in DB to the path DST_ABSPATH in DB, recursively. If METADATA_ONLY is true, copy only the versioned metadata, otherwise copy both the versioned metadata and the filesystem nodes (even if they are the wrong kind, and including unversioned children). If IS_MOVE is true, record move information in working copy meta data in addition to copying the directory. WITHIN_ONE_WC is TRUE if the copy/move is within a single working copy (root) */ static svn_error_t * copy_versioned_dir(svn_wc__db_t *db, const char *src_abspath, const char *dst_abspath, const char *dst_op_root_abspath, const char *tmpdir_abspath, svn_boolean_t metadata_only, svn_boolean_t is_move, svn_cancel_func_t cancel_func, void *cancel_baton, svn_wc_notify_func2_t notify_func, void *notify_baton, apr_pool_t *scratch_pool) { svn_skel_t *work_items = NULL; const char *dir_abspath = svn_dirent_dirname(dst_abspath, scratch_pool); apr_hash_t *versioned_children; apr_hash_t *conflicted_children; apr_hash_t *disk_children; apr_hash_index_t *hi; svn_node_kind_t disk_kind; apr_pool_t *iterpool; /* Prepare a temp copy of the single filesystem node (usually a dir). */ if (!metadata_only) { SVN_ERR(copy_to_tmpdir(&work_items, &disk_kind, db, src_abspath, dst_abspath, tmpdir_abspath, FALSE /* file_copy */, FALSE /* unversioned */, cancel_func, cancel_baton, scratch_pool, scratch_pool)); } /* Copy the (single) node's metadata, and move the new filesystem node into place. */ SVN_ERR(svn_wc__db_op_copy(db, src_abspath, dst_abspath, dst_op_root_abspath, is_move, work_items, scratch_pool)); if (notify_func) { svn_wc_notify_t *notify = svn_wc_create_notify(dst_abspath, svn_wc_notify_add, scratch_pool); notify->kind = svn_node_dir; /* When we notify that we performed a copy, make sure we already did */ if (work_items != NULL) SVN_ERR(svn_wc__wq_run(db, dir_abspath, cancel_func, cancel_baton, scratch_pool)); (*notify_func)(notify_baton, notify, scratch_pool); } if (!metadata_only && disk_kind == svn_node_dir) /* All filesystem children, versioned and unversioned. We're only interested in their names, so we can pass TRUE as the only_check_type param. */ SVN_ERR(svn_io_get_dirents3(&disk_children, src_abspath, TRUE, scratch_pool, scratch_pool)); else disk_children = NULL; /* Copy all the versioned children */ iterpool = svn_pool_create(scratch_pool); SVN_ERR(svn_wc__db_read_children_info(&versioned_children, &conflicted_children, db, src_abspath, scratch_pool, iterpool)); for (hi = apr_hash_first(scratch_pool, versioned_children); hi; hi = apr_hash_next(hi)) { const char *child_name, *child_src_abspath, *child_dst_abspath; struct svn_wc__db_info_t *info; svn_pool_clear(iterpool); if (cancel_func) SVN_ERR(cancel_func(cancel_baton)); child_name = svn__apr_hash_index_key(hi); info = svn__apr_hash_index_val(hi); child_src_abspath = svn_dirent_join(src_abspath, child_name, iterpool); child_dst_abspath = svn_dirent_join(dst_abspath, child_name, iterpool); if (info->op_root) SVN_ERR(svn_wc__db_op_copy_shadowed_layer(db, child_src_abspath, child_dst_abspath, is_move, scratch_pool)); if (info->status == svn_wc__db_status_normal || info->status == svn_wc__db_status_added) { /* We have more work to do than just changing the DB */ if (info->kind == svn_node_file) { /* We should skip this node if this child is a file external (issues #3589, #4000) */ if (!info->file_external) SVN_ERR(copy_versioned_file(db, child_src_abspath, child_dst_abspath, dst_op_root_abspath, tmpdir_abspath, metadata_only, info->conflicted, is_move, cancel_func, cancel_baton, NULL, NULL, iterpool)); } else if (info->kind == svn_node_dir) SVN_ERR(copy_versioned_dir(db, child_src_abspath, child_dst_abspath, dst_op_root_abspath, tmpdir_abspath, metadata_only, is_move, cancel_func, cancel_baton, NULL, NULL, iterpool)); else return svn_error_createf(SVN_ERR_NODE_UNEXPECTED_KIND, NULL, _("cannot handle node kind for '%s'"), svn_dirent_local_style(child_src_abspath, scratch_pool)); } else if (info->status == svn_wc__db_status_deleted || info->status == svn_wc__db_status_not_present || info->status == svn_wc__db_status_excluded) { /* This will be copied as some kind of deletion. Don't touch any actual files */ SVN_ERR(svn_wc__db_op_copy(db, child_src_abspath, child_dst_abspath, dst_op_root_abspath, is_move, NULL, iterpool)); /* Don't recurse on children while all we do is creating not-present children */ } else if (info->status == svn_wc__db_status_incomplete) { /* Should go ahead and copy incomplete to incomplete? Try to copy as much as possible, or give up early? */ return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, _("Cannot handle status of '%s'"), svn_dirent_local_style(child_src_abspath, iterpool)); } else { SVN_ERR_ASSERT(info->status == svn_wc__db_status_server_excluded); return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL, _("Cannot copy '%s' excluded by server"), svn_dirent_local_style(child_src_abspath, iterpool)); } if (disk_children && (info->status == svn_wc__db_status_normal || info->status == svn_wc__db_status_added)) { /* Remove versioned child as it has been handled */ svn_hash_sets(disk_children, child_name, NULL); } } /* Copy the remaining filesystem children, which are unversioned, skipping any conflict-marker files. */ if (disk_children && apr_hash_count(disk_children)) { apr_hash_t *marker_files; SVN_ERR(svn_wc__db_get_conflict_marker_files(&marker_files, db, src_abspath, scratch_pool, scratch_pool)); work_items = NULL; for (hi = apr_hash_first(scratch_pool, disk_children); hi; hi = apr_hash_next(hi)) { const char *name = svn__apr_hash_index_key(hi); const char *unver_src_abspath, *unver_dst_abspath; svn_skel_t *work_item; if (svn_wc_is_adm_dir(name, iterpool)) continue; if (cancel_func) SVN_ERR(cancel_func(cancel_baton)); svn_pool_clear(iterpool); unver_src_abspath = svn_dirent_join(src_abspath, name, iterpool); unver_dst_abspath = svn_dirent_join(dst_abspath, name, iterpool); if (marker_files && svn_hash_gets(marker_files, unver_src_abspath)) continue; SVN_ERR(copy_to_tmpdir(&work_item, NULL, db, unver_src_abspath, unver_dst_abspath, tmpdir_abspath, TRUE /* recursive */, TRUE /* unversioned */, cancel_func, cancel_baton, scratch_pool, iterpool)); if (work_item) work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool); } SVN_ERR(svn_wc__db_wq_add(db, dst_abspath, work_items, iterpool)); } svn_pool_destroy(iterpool); return SVN_NO_ERROR; }
static svn_error_t * handle_external_item_change(svn_client_ctx_t *ctx, const char *repos_root_url, const char *parent_dir_abspath, const char *parent_dir_url, const char *local_abspath, const char *old_defining_abspath, const svn_wc_external_item2_t *new_item, svn_boolean_t *timestamp_sleep, apr_pool_t *scratch_pool) { svn_ra_session_t *ra_session; svn_client__pathrev_t *new_loc; const char *new_url; svn_node_kind_t ext_kind; SVN_ERR_ASSERT(repos_root_url && parent_dir_url); SVN_ERR_ASSERT(new_item != NULL); /* Don't bother to check status, since we'll get that for free by attempting to retrieve the hash values anyway. */ /* When creating the absolute URL, use the pool and not the iterpool, since the hash table values outlive the iterpool and any pointers they have should also outlive the iterpool. */ SVN_ERR(svn_wc__resolve_relative_external_url(&new_url, new_item, repos_root_url, parent_dir_url, scratch_pool, scratch_pool)); /* Determine if the external is a file or directory. */ /* Get the RA connection. */ SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc, new_url, NULL, &(new_item->peg_revision), &(new_item->revision), ctx, scratch_pool)); SVN_ERR(svn_ra_check_path(ra_session, "", new_loc->rev, &ext_kind, scratch_pool)); if (svn_node_none == ext_kind) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' at revision %ld doesn't exist"), new_loc->url, new_loc->rev); if (svn_node_dir != ext_kind && svn_node_file != ext_kind) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' at revision %ld is not a file " "or a directory"), new_loc->url, new_loc->rev); /* Not protecting against recursive externals. Detecting them in the global case is hard, and it should be pretty obvious to a user when it happens. Worst case: your disk fills up :-). */ /* First notify that we're about to handle an external. */ if (ctx->notify_func2) { (*ctx->notify_func2)( ctx->notify_baton2, svn_wc_create_notify(local_abspath, svn_wc_notify_update_external, scratch_pool), scratch_pool); } if (! old_defining_abspath) { /* The target dir might have multiple components. Guarantee the path leading down to the last component. */ SVN_ERR(svn_io_make_dir_recursively(svn_dirent_dirname(local_abspath, scratch_pool), scratch_pool)); } switch (ext_kind) { case svn_node_dir: SVN_ERR(switch_dir_external(local_abspath, new_loc->url, new_item->url, &(new_item->peg_revision), &(new_item->revision), parent_dir_abspath, timestamp_sleep, ctx, scratch_pool)); break; case svn_node_file: if (strcmp(repos_root_url, new_loc->repos_root_url)) { const char *local_repos_root_url; const char *local_repos_uuid; const char *ext_repos_relpath; svn_error_t *err; /* * The working copy library currently requires that all files * in the working copy have the same repository root URL. * The URL from the file external's definition differs from the * one used by the working copy. As a workaround, replace the * root URL portion of the file external's URL, after making * sure both URLs point to the same repository. See issue #4087. */ err = svn_wc__node_get_repos_info(NULL, NULL, &local_repos_root_url, &local_repos_uuid, ctx->wc_ctx, parent_dir_abspath, scratch_pool, scratch_pool); if (err) { if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND && err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY) return svn_error_trace(err); svn_error_clear(err); local_repos_root_url = NULL; local_repos_uuid = NULL; } ext_repos_relpath = svn_uri_skip_ancestor(new_loc->repos_root_url, new_url, scratch_pool); if (local_repos_uuid == NULL || local_repos_root_url == NULL || ext_repos_relpath == NULL || strcmp(local_repos_uuid, new_loc->repos_uuid) != 0) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Unsupported external: URL of file external '%s' " "is not in repository '%s'"), new_url, repos_root_url); new_url = svn_path_url_add_component2(local_repos_root_url, ext_repos_relpath, scratch_pool); SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &new_loc, new_url, NULL, &(new_item->peg_revision), &(new_item->revision), ctx, scratch_pool)); } SVN_ERR(switch_file_external(local_abspath, new_url, &new_item->peg_revision, &new_item->revision, parent_dir_abspath, ra_session, ctx, scratch_pool)); break; default: SVN_ERR_MALFUNCTION(); break; } return SVN_NO_ERROR; }
svn_error_t * svn_client_status4(svn_revnum_t *result_rev, const char *path, const svn_opt_revision_t *revision, svn_wc_status_func3_t status_func, void *status_baton, svn_depth_t depth, svn_boolean_t get_all, svn_boolean_t update, svn_boolean_t no_ignore, svn_boolean_t ignore_externals, const apr_array_header_t *changelists, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_wc_adm_access_t *anchor_access, *target_access; svn_wc_traversal_info_t *traversal_info = svn_wc_init_traversal_info(pool); const char *anchor, *target; const svn_delta_editor_t *editor; void *edit_baton, *set_locks_baton; const svn_wc_entry_t *entry = NULL; struct status_baton sb; apr_array_header_t *ignores; svn_error_t *err; apr_hash_t *changelist_hash = NULL; svn_revnum_t edit_revision = SVN_INVALID_REVNUM; if (changelists && changelists->nelts) SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, pool)); sb.real_status_func = status_func; sb.real_status_baton = status_baton; sb.deleted_in_repos = FALSE; sb.changelist_hash = changelist_hash; /* Try to open the target directory. If the target is a file or an unversioned directory, open the parent directory instead */ err = svn_wc_adm_open3(&anchor_access, NULL, path, FALSE, SVN_DEPTH_IS_RECURSIVE(depth) ? -1 : 1, ctx->cancel_func, ctx->cancel_baton, pool); if (err && err->apr_err == SVN_ERR_WC_NOT_DIRECTORY) { svn_error_clear(err); SVN_ERR(svn_wc_adm_open_anchor(&anchor_access, &target_access, &target, path, FALSE, SVN_DEPTH_IS_RECURSIVE(depth) ? -1 : 1, ctx->cancel_func, ctx->cancel_baton, pool)); } else if (!err) { target = ""; target_access = anchor_access; } else return err; anchor = svn_wc_adm_access_path(anchor_access); /* Get the status edit, and use our wrapping status function/baton as the callback pair. */ SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool)); SVN_ERR(svn_wc_get_status_editor4(&editor, &edit_baton, &set_locks_baton, &edit_revision, anchor_access, target, depth, get_all, no_ignore, ignores, tweak_status, &sb, ctx->cancel_func, ctx->cancel_baton, traversal_info, pool)); /* If we want to know about out-of-dateness, we crawl the working copy and let the RA layer drive the editor for real. Otherwise, we just close the edit. :-) */ if (update) { svn_ra_session_t *ra_session; const char *URL; svn_node_kind_t kind; svn_boolean_t server_supports_depth; /* Get full URL from the ANCHOR. */ if (! entry) SVN_ERR(svn_wc__entry_versioned(&entry, anchor, anchor_access, FALSE, pool)); if (! entry->url) return svn_error_createf (SVN_ERR_ENTRY_MISSING_URL, NULL, _("Entry '%s' has no URL"), svn_path_local_style(anchor, pool)); URL = apr_pstrdup(pool, entry->url); /* Open a repository session to the URL. */ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, URL, anchor, anchor_access, NULL, FALSE, TRUE, ctx, pool)); /* Verify that URL exists in HEAD. If it doesn't, this can save us a whole lot of hassle; if it does, the cost of this request should be minimal compared to the size of getting back the average amount of "out-of-date" information. */ SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind, pool)); if (kind == svn_node_none) { /* Our status target does not exist in HEAD of the repository. If we're just adding this thing, that's fine. But if it was previously versioned, then it must have been deleted from the repository. */ if (entry->schedule != svn_wc_schedule_add) sb.deleted_in_repos = TRUE; /* And now close the edit. */ SVN_ERR(editor->close_edit(edit_baton, pool)); } else { svn_revnum_t revnum; report_baton_t rb; if (revision->kind == svn_opt_revision_head) { /* Cause the revision number to be omitted from the request, which implies HEAD. */ revnum = SVN_INVALID_REVNUM; } else { /* Get a revision number for our status operation. */ SVN_ERR(svn_client__get_revision_number (&revnum, NULL, ra_session, revision, target, pool)); } /* Do the deed. Let the RA layer drive the status editor. */ SVN_ERR(svn_ra_do_status2(ra_session, &rb.wrapped_reporter, &rb.wrapped_report_baton, target, revnum, depth, editor, edit_baton, pool)); /* Init the report baton. */ rb.ancestor = apr_pstrdup(pool, URL); rb.set_locks_baton = set_locks_baton; rb.ctx = ctx; rb.pool = pool; SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); /* Drive the reporter structure, describing the revisions within PATH. When we call reporter->finish_report, EDITOR will be driven to describe differences between our working copy and HEAD. */ SVN_ERR(svn_wc_crawl_revisions4(path, target_access, &lock_fetch_reporter, &rb, FALSE, depth, TRUE, (! server_supports_depth), FALSE, NULL, NULL, NULL, pool)); } } else { SVN_ERR(editor->close_edit(edit_baton, pool)); } if (ctx->notify_func2 && update) { svn_wc_notify_t *notify = svn_wc_create_notify(path, svn_wc_notify_status_completed, pool); notify->revision = edit_revision; (ctx->notify_func2)(ctx->notify_baton2, notify, pool); } /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = edit_revision; /* Close the access baton here, as svn_client__do_external_status() calls back into this function and thus will be re-opening the working copy. */ SVN_ERR(svn_wc_adm_close2(anchor_access, pool)); /* If there are svn:externals set, we don't want those to show up as unversioned or unrecognized, so patch up the hash. If caller wants all the statuses, we will change unversioned status items that are interesting to an svn:externals property to svn_wc_status_unversioned, otherwise we'll just remove the status item altogether. We only descend into an external if depth is svn_depth_infinity or svn_depth_unknown. However, there are conceivable behaviors that would involve descending under other circumstances; thus, we pass depth anyway, so the code will DTRT if we change the conditional in the future. */ if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals)) SVN_ERR(svn_client__do_external_status(traversal_info, status_func, status_baton, depth, get_all, update, no_ignore, ctx, pool)); return SVN_NO_ERROR; }
/* Only call this if the on-disk node kind is a file. */ static svn_error_t * add_file(const char *local_abspath, svn_magic__cookie_t *magic_cookie, svn_client_ctx_t *ctx, apr_pool_t *pool) { apr_hash_t* properties; apr_hash_index_t *hi; const char *mimetype; svn_node_kind_t kind; svn_boolean_t is_special; /* Check to see if this is a special file. */ SVN_ERR(svn_io_check_special_path(local_abspath, &kind, &is_special, pool)); if (is_special) mimetype = NULL; else /* Get automatic properties */ /* This may fail on write-only files: we open them to estimate file type. That's why we postpone the add until after this step. */ SVN_ERR(svn_client__get_auto_props(&properties, &mimetype, local_abspath, magic_cookie, ctx, pool)); /* Add the file */ SVN_ERR(svn_wc_add_from_disk(ctx->wc_ctx, local_abspath, NULL, NULL, pool)); if (is_special) /* This must be a special file. */ SVN_ERR(svn_wc_prop_set4(ctx->wc_ctx, local_abspath, SVN_PROP_SPECIAL, svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool), svn_depth_empty, FALSE, NULL, NULL, NULL /* cancellation */, NULL, NULL /* notification */, pool)); else if (properties) { /* loop through the hashtable and add the properties */ for (hi = apr_hash_first(pool, properties); hi != NULL; hi = apr_hash_next(hi)) { const char *pname = svn__apr_hash_index_key(hi); const svn_string_t *pval = svn__apr_hash_index_val(hi); svn_error_t *err; /* It's probably best to pass 0 for force, so that if the autoprops say to set some weird combination, we just error and let the user sort it out. */ err = svn_wc_prop_set4(ctx->wc_ctx, local_abspath, pname, pval, svn_depth_empty, FALSE, NULL, NULL, NULL /* cancellation */, NULL, NULL /* notification */, pool); if (err) { /* Don't leave the job half-done. If we fail to set a property, * (try to) un-add the file. */ svn_error_clear(svn_wc_revert4(ctx->wc_ctx, local_abspath, svn_depth_empty, FALSE /* use_commit_times */, NULL /* changelists */, NULL, NULL, NULL, NULL, pool)); return svn_error_trace(err); } } } /* Report the addition to the caller. */ if (ctx->notify_func2 != NULL) { svn_wc_notify_t *notify = svn_wc_create_notify(local_abspath, svn_wc_notify_add, pool); notify->kind = svn_node_file; notify->mime_type = mimetype; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } return SVN_NO_ERROR; }
static svn_error_t * add_file(const char *path, svn_client_ctx_t *ctx, svn_wc_adm_access_t *adm_access, apr_pool_t *pool) { apr_hash_t* properties; apr_hash_index_t *hi; const char *mimetype; svn_node_kind_t kind; svn_boolean_t is_special; /* Check to see if this is a special file. */ SVN_ERR(svn_io_check_special_path(path, &kind, &is_special, pool)); if (is_special) mimetype = NULL; else /* Get automatic properties */ /* This may fail on write-only files: we open them to estimate file type. That's why we postpone the add until after this step. */ SVN_ERR(svn_client__get_auto_props(&properties, &mimetype, path, ctx, pool)); /* Add the file */ SVN_ERR(svn_wc_add2(path, adm_access, NULL, SVN_INVALID_REVNUM, ctx->cancel_func, ctx->cancel_baton, NULL, NULL, pool)); if (is_special) /* This must be a special file. */ SVN_ERR(svn_wc_prop_set2 (SVN_PROP_SPECIAL, svn_string_create(SVN_PROP_BOOLEAN_TRUE, pool), path, adm_access, FALSE, pool)); else if (properties) { /* loop through the hashtable and add the properties */ for (hi = apr_hash_first(pool, properties); hi != NULL; hi = apr_hash_next(hi)) { const void *pname; void *pval; apr_hash_this(hi, &pname, NULL, &pval); /* It's probably best to pass 0 for force, so that if the autoprops say to set some weird combination, we just error and let the user sort it out. */ SVN_ERR(svn_wc_prop_set2(pname, pval, path, adm_access, FALSE, pool)); } } /* Report the addition to the caller. */ if (ctx->notify_func2 != NULL) { svn_wc_notify_t *notify = svn_wc_create_notify(path, svn_wc_notify_add, pool); notify->kind = svn_node_file; notify->mime_type = mimetype; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } return SVN_NO_ERROR; }
/* Make an unversioned copy of the versioned file or directory tree at the * source path FROM_ABSPATH. Copy it to the destination path TO_ABSPATH. * * If REVISION is svn_opt_revision_working, copy the working version, * otherwise copy the base version. * * See copy_one_versioned_file() for details of file copying behaviour, * including IGNORE_KEYWORDS and NATIVE_EOL. * * Include externals unless IGNORE_EXTERNALS is true. * * Recurse according to DEPTH. * */ static svn_error_t * copy_versioned_files(const char *from_abspath, const char *to_abspath, const svn_opt_revision_t *revision, svn_boolean_t force, svn_boolean_t ignore_externals, svn_boolean_t ignore_keywords, svn_depth_t depth, const char *native_eol, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_error_t *err; apr_pool_t *iterpool; const apr_array_header_t *children; svn_node_kind_t from_kind; svn_depth_t node_depth; SVN_ERR_ASSERT(svn_dirent_is_absolute(from_abspath)); SVN_ERR_ASSERT(svn_dirent_is_absolute(to_abspath)); /* Only export 'added' and 'replaced' files when the revision is WORKING; when the revision is BASE (i.e. != WORKING), only export 'added' and 'replaced' files when they are part of a copy-/move-here. Otherwise, skip them, since they don't have an associated text-base. This condition for added/replaced simply is an optimization. Added and replaced files would be handled similarly by svn_wc_get_pristine_contents2(), which would return NULL if they have no base associated. TODO: We may prefer not to duplicate this condition and rather use svn_wc_get_pristine_contents2() or a dedicated new function instead. Don't export 'deleted' files and directories unless it's a revision other than WORKING. These files and directories don't really exist in WORKING. */ if (revision->kind != svn_opt_revision_working) { svn_boolean_t is_added; const char *repos_relpath; SVN_ERR(svn_wc__node_get_origin(&is_added, NULL, &repos_relpath, NULL, NULL, NULL, ctx->wc_ctx, from_abspath, FALSE, pool, pool)); if (is_added && !repos_relpath) return SVN_NO_ERROR; /* Local addition */ } else { svn_boolean_t is_deleted; SVN_ERR(svn_wc__node_is_status_deleted(&is_deleted, ctx->wc_ctx, from_abspath, pool)); if (is_deleted) return SVN_NO_ERROR; } SVN_ERR(svn_wc_read_kind(&from_kind, ctx->wc_ctx, from_abspath, FALSE, pool)); if (from_kind == svn_node_dir) { apr_fileperms_t perm = APR_OS_DEFAULT; int j; /* Try to make the new directory. If this fails because the directory already exists, check our FORCE flag to see if we care. */ /* Keep the source directory's permissions if applicable. Skip retrieving the umask on windows. Apr does not implement setting filesystem privileges on Windows. Retrieving the file permissions with APR_FINFO_PROT | APR_FINFO_OWNER is documented to be 'incredibly expensive' */ #ifndef WIN32 if (revision->kind == svn_opt_revision_working) { apr_finfo_t finfo; SVN_ERR(svn_io_stat(&finfo, from_abspath, APR_FINFO_PROT, pool)); perm = finfo.protection; } #endif err = svn_io_dir_make(to_abspath, perm, pool); if (err) { if (! APR_STATUS_IS_EEXIST(err->apr_err)) return svn_error_trace(err); if (! force) SVN_ERR_W(err, _("Destination directory exists, and will not be " "overwritten unless forced")); else svn_error_clear(err); } SVN_ERR(svn_wc__node_get_children(&children, ctx->wc_ctx, from_abspath, FALSE, pool, pool)); iterpool = svn_pool_create(pool); for (j = 0; j < children->nelts; j++) { const char *child_abspath = APR_ARRAY_IDX(children, j, const char *); const char *child_name = svn_dirent_basename(child_abspath, NULL); const char *target_abspath; svn_node_kind_t child_kind; svn_pool_clear(iterpool); if (ctx->cancel_func) SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); target_abspath = svn_dirent_join(to_abspath, child_name, iterpool); SVN_ERR(svn_wc_read_kind(&child_kind, ctx->wc_ctx, child_abspath, FALSE, iterpool)); if (child_kind == svn_node_dir) { if (depth == svn_depth_infinity || depth == svn_depth_immediates) { if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(target_abspath, svn_wc_notify_update_add, pool); notify->kind = svn_node_dir; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } if (depth == svn_depth_infinity) SVN_ERR(copy_versioned_files(child_abspath, target_abspath, revision, force, ignore_externals, ignore_keywords, depth, native_eol, ctx, iterpool)); else SVN_ERR(svn_io_make_dir_recursively(target_abspath, iterpool)); } } else if (child_kind == svn_node_file && depth >= svn_depth_files) { svn_node_kind_t external_kind; SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, NULL, ctx->wc_ctx, child_abspath, child_abspath, TRUE, pool, pool)); if (external_kind != svn_node_file) SVN_ERR(copy_one_versioned_file(child_abspath, target_abspath, ctx, revision, native_eol, ignore_keywords, iterpool)); } } SVN_ERR(svn_wc__node_get_depth(&node_depth, ctx->wc_ctx, from_abspath, pool)); /* Handle externals. */ if (! ignore_externals && depth == svn_depth_infinity && node_depth == svn_depth_infinity) { apr_array_header_t *ext_items; const svn_string_t *prop_val; SVN_ERR(svn_wc_prop_get2(&prop_val, ctx->wc_ctx, from_abspath, SVN_PROP_EXTERNALS, pool, pool)); if (prop_val != NULL) { int i; SVN_ERR(svn_wc_parse_externals_description3(&ext_items, from_abspath, prop_val->data, FALSE, pool)); for (i = 0; i < ext_items->nelts; ++i) { svn_wc_external_item2_t *ext_item; const char *new_from, *new_to; svn_pool_clear(iterpool); ext_item = APR_ARRAY_IDX(ext_items, i, svn_wc_external_item2_t *); new_from = svn_dirent_join(from_abspath, ext_item->target_dir, iterpool); new_to = svn_dirent_join(to_abspath, ext_item->target_dir, iterpool); /* The target dir might have parents that don't exist. Guarantee the path upto the last component. */ if (!svn_dirent_is_root(ext_item->target_dir, strlen(ext_item->target_dir))) { const char *parent = svn_dirent_dirname(new_to, iterpool); SVN_ERR(svn_io_make_dir_recursively(parent, iterpool)); } SVN_ERR(copy_versioned_files(new_from, new_to, revision, force, FALSE, ignore_keywords, svn_depth_infinity, native_eol, ctx, iterpool)); } }
/* Perform status operations on each external in EXTERNAL_MAP, a const char * local_abspath of all externals mapping to the const char* defining_abspath. All other options are the same as those passed to svn_client_status(). If ANCHOR_ABSPATH and ANCHOR-RELPATH are not null, use them to provide properly formatted relative paths */ static svn_error_t * do_external_status(svn_client_ctx_t *ctx, apr_hash_t *external_map, svn_depth_t depth, svn_boolean_t get_all, svn_boolean_t check_out_of_date, svn_boolean_t check_working_copy, svn_boolean_t no_ignore, const apr_array_header_t *changelists, const char *anchor_abspath, const char *anchor_relpath, svn_client_status_func_t status_func, void *status_baton, apr_pool_t *scratch_pool) { apr_pool_t *iterpool = svn_pool_create(scratch_pool); apr_array_header_t *externals; int i; externals = svn_sort__hash(external_map, svn_sort_compare_items_lexically, scratch_pool); /* Loop over the hash of new values (we don't care about the old ones). This is a mapping of versioned directories to property values. */ for (i = 0; i < externals->nelts; i++) { svn_node_kind_t external_kind; svn_sort__item_t item = APR_ARRAY_IDX(externals, i, svn_sort__item_t); const char *local_abspath = item.key; const char *defining_abspath = item.value; svn_node_kind_t kind; svn_opt_revision_t opt_rev; const char *status_path; svn_pool_clear(iterpool); /* Obtain information on the expected external. */ SVN_ERR(svn_wc__read_external_info(&external_kind, NULL, NULL, NULL, &opt_rev.value.number, ctx->wc_ctx, defining_abspath, local_abspath, FALSE, iterpool, iterpool)); if (external_kind != svn_node_dir) continue; SVN_ERR(svn_io_check_path(local_abspath, &kind, iterpool)); if (kind != svn_node_dir) continue; if (SVN_IS_VALID_REVNUM(opt_rev.value.number)) opt_rev.kind = svn_opt_revision_number; else opt_rev.kind = svn_opt_revision_unspecified; /* Tell the client we're starting an external status set. */ if (ctx->notify_func2) ctx->notify_func2( ctx->notify_baton2, svn_wc_create_notify(local_abspath, svn_wc_notify_status_external, iterpool), iterpool); status_path = local_abspath; if (anchor_abspath) { status_path = svn_dirent_join(anchor_relpath, svn_dirent_skip_ancestor(anchor_abspath, status_path), iterpool); } /* And then do the status. */ SVN_ERR(svn_client_status6(NULL, ctx, status_path, &opt_rev, depth, get_all, check_out_of_date, check_working_copy, no_ignore, FALSE /* ignore_exernals */, FALSE /* depth_as_sticky */, changelists, status_func, status_baton, iterpool)); } /* Destroy SUBPOOL and (implicitly) ITERPOOL. */ svn_pool_destroy(iterpool); return SVN_NO_ERROR; }
static svn_error_t * switch_internal(svn_revnum_t *result_rev, const char *local_abspath, const char *anchor_abspath, const char *switch_url, const svn_opt_revision_t *peg_revision, 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 ignore_ancestry, svn_boolean_t *timestamp_sleep, svn_client_ctx_t *ctx, apr_pool_t *pool) { const svn_ra_reporter3_t *reporter; void *report_baton; const char *url, *target, *source_root, *switch_rev_url; svn_ra_session_t *ra_session; svn_revnum_t revnum; svn_error_t *err = SVN_NO_ERROR; const char *diff3_cmd; svn_boolean_t use_commit_times; svn_boolean_t sleep_here = FALSE; svn_boolean_t *use_sleep = timestamp_sleep ? timestamp_sleep : &sleep_here; const svn_delta_editor_t *switch_editor; void *switch_edit_baton; const char *preserved_exts_str; apr_array_header_t *preserved_exts; svn_boolean_t server_supports_depth; struct svn_client__dirent_fetcher_baton_t dfb; svn_config_t *cfg = ctx->config ? apr_hash_get(ctx->config, SVN_CONFIG_CATEGORY_CONFIG, APR_HASH_KEY_STRING) : NULL; /* An unknown depth can't be sticky. */ if (depth == svn_depth_unknown) depth_is_sticky = FALSE; /* Do not support the situation of both exclude and switch a target. */ if (depth == svn_depth_exclude) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot both exclude and switch a path")); /* Get the external diff3, if any. */ svn_config_get(cfg, &diff3_cmd, SVN_CONFIG_SECTION_HELPERS, SVN_CONFIG_OPTION_DIFF3_CMD, NULL); if (diff3_cmd != NULL) SVN_ERR(svn_path_cstring_to_utf8(&diff3_cmd, diff3_cmd, pool)); /* See if the user wants last-commit timestamps instead of current ones. */ SVN_ERR(svn_config_get_bool(cfg, &use_commit_times, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_USE_COMMIT_TIMES, FALSE)); { svn_boolean_t has_working; SVN_ERR(svn_wc__node_has_working(&has_working, ctx->wc_ctx, local_abspath, pool)); if (has_working) return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Cannot switch '%s' because it is not in the " "repository yet"), svn_dirent_local_style(local_abspath, pool)); } /* See which files the user wants to preserve the extension of when conflict files are made. */ svn_config_get(cfg, &preserved_exts_str, SVN_CONFIG_SECTION_MISCELLANY, SVN_CONFIG_OPTION_PRESERVED_CF_EXTS, ""); preserved_exts = *preserved_exts_str ? svn_cstring_split(preserved_exts_str, "\n\r\t\v ", FALSE, pool) : NULL; /* Sanity check. Without these, the switch is meaningless. */ SVN_ERR_ASSERT(switch_url && (switch_url[0] != '\0')); if (strcmp(local_abspath, anchor_abspath)) target = svn_dirent_basename(local_abspath, pool); else target = ""; SVN_ERR(svn_wc__node_get_url(&url, ctx->wc_ctx, anchor_abspath, pool, pool)); if (! url) return svn_error_createf(SVN_ERR_ENTRY_MISSING_URL, NULL, _("Directory '%s' has no URL"), svn_dirent_local_style(anchor_abspath, pool)); /* We may need to crop the tree if the depth is sticky */ if (depth_is_sticky && depth < svn_depth_infinity) { svn_node_kind_t target_kind; if (depth == svn_depth_exclude) { SVN_ERR(svn_wc_exclude(ctx->wc_ctx, local_abspath, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); /* Target excluded, we are done now */ return SVN_NO_ERROR; } SVN_ERR(svn_wc_read_kind(&target_kind, ctx->wc_ctx, local_abspath, TRUE, pool)); if (target_kind == svn_node_dir) SVN_ERR(svn_wc_crop_tree2(ctx->wc_ctx, local_abspath, depth, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool)); } /* Open an RA session to 'source' URL */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &revnum, &switch_rev_url, switch_url, anchor_abspath, peg_revision, revision, ctx, pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &source_root, pool)); /* Disallow a switch operation to change the repository root of the target. */ if (! svn_uri__is_ancestor(source_root, url)) return svn_error_createf(SVN_ERR_WC_INVALID_SWITCH, NULL, _("'%s'\nis not the same repository as\n'%s'"), url, source_root); /* If we're not ignoring ancestry, then error out if the switch source and target don't have a common ancestory. ### We're acting on the anchor here, not the target. Is that ### okay? */ if (! ignore_ancestry) { const char *target_url, *yc_path; svn_revnum_t target_rev, yc_rev; SVN_ERR(svn_wc__node_get_url(&target_url, ctx->wc_ctx, local_abspath, pool, pool)); SVN_ERR(svn_wc__node_get_base_rev(&target_rev, ctx->wc_ctx, local_abspath, pool)); /* ### It would be nice if this function could reuse the existing ra session instead of opening two for its own use. */ SVN_ERR(svn_client__get_youngest_common_ancestor(&yc_path, &yc_rev, switch_rev_url, revnum, target_url, target_rev, ctx, pool)); if (! (yc_path && SVN_IS_VALID_REVNUM(yc_rev))) return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL, _("'%s' shares no common ancestry with '%s'"), switch_url, local_abspath); } SVN_ERR(svn_ra_reparent(ra_session, url, pool)); /* Fetch the switch (update) editor. If REVISION is invalid, that's okay; the RA driver will call editor->set_target_revision() later on. */ SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); dfb.ra_session = ra_session; SVN_ERR(svn_ra_get_session_url(ra_session, &dfb.anchor_url, pool)); dfb.target_revision = revnum; SVN_ERR(svn_wc_get_switch_editor4(&switch_editor, &switch_edit_baton, &revnum, ctx->wc_ctx, anchor_abspath, target, switch_rev_url, use_commit_times, depth, depth_is_sticky, allow_unver_obstructions, server_supports_depth, diff3_cmd, preserved_exts, svn_client__dirent_fetcher, &dfb, ctx->conflict_func2, ctx->conflict_baton2, NULL, NULL, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool, pool)); /* Tell RA to do an update of URL+TARGET to REVISION; if we pass an invalid revnum, that means RA will use the latest revision. */ SVN_ERR(svn_ra_do_switch2(ra_session, &reporter, &report_baton, revnum, target, depth_is_sticky ? depth : svn_depth_unknown, switch_rev_url, switch_editor, switch_edit_baton, pool)); /* Drive the reporter structure, describing the revisions within PATH. When we call reporter->finish_report, the update_editor will be driven by svn_repos_dir_delta2. We pass in an external_func for recording all externals. It shouldn't be needed for a switch if it wasn't for the relative externals of type '../path'. All of those must be resolved to the new location. */ err = svn_wc_crawl_revisions5(ctx->wc_ctx, local_abspath, reporter, report_baton, TRUE, depth, (! depth_is_sticky), (! server_supports_depth), use_commit_times, ctx->cancel_func, ctx->cancel_baton, ctx->notify_func2, ctx->notify_baton2, pool); if (err) { /* Don't rely on the error handling to handle the sleep later, do it now */ svn_io_sleep_for_timestamps(local_abspath, pool); return svn_error_trace(err); } *use_sleep = TRUE; /* We handle externals after the switch is complete, so that handling external items (and any errors therefrom) doesn't delay the primary operation. */ if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals)) { apr_hash_t *new_externals; apr_hash_t *new_depths; SVN_ERR(svn_wc__externals_gather_definitions(&new_externals, &new_depths, ctx->wc_ctx, local_abspath, depth, pool, pool)); SVN_ERR(svn_client__handle_externals(new_externals, new_depths, source_root, local_abspath, depth, use_sleep, ctx, pool)); } /* Sleep to ensure timestamp integrity (we do this regardless of errors in the actual switch operation(s)). */ if (sleep_here) svn_io_sleep_for_timestamps(local_abspath, pool); /* Return errors we might have sustained. */ if (err) return svn_error_trace(err); /* Let everyone know we're finished here. */ if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(anchor_abspath, svn_wc_notify_update_completed, pool); notify->kind = svn_node_none; notify->content_state = notify->prop_state = svn_wc_notify_state_inapplicable; notify->lock_state = svn_wc_notify_lock_state_inapplicable; notify->revision = revnum; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = revnum; return SVN_NO_ERROR; }
svn_error_t * svn_client_status6(svn_revnum_t *result_rev, svn_client_ctx_t *ctx, const char *path, const svn_opt_revision_t *revision, svn_depth_t depth, svn_boolean_t get_all, svn_boolean_t check_out_of_date, svn_boolean_t check_working_copy, svn_boolean_t no_ignore, svn_boolean_t ignore_externals, svn_boolean_t depth_as_sticky, const apr_array_header_t *changelists, svn_client_status_func_t status_func, void *status_baton, apr_pool_t *pool) /* ### aka scratch_pool */ { struct status_baton sb; const char *dir, *dir_abspath; const char *target_abspath; const char *target_basename; apr_array_header_t *ignores; svn_error_t *err; apr_hash_t *changelist_hash = NULL; /* Override invalid combinations of the check_out_of_date and check_working_copy flags. */ if (!check_out_of_date) check_working_copy = TRUE; if (svn_path_is_url(path)) return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("'%s' is not a local path"), path); if (changelists && changelists->nelts) SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, changelists, pool)); if (result_rev) *result_rev = SVN_INVALID_REVNUM; sb.real_status_func = status_func; sb.real_status_baton = status_baton; sb.deleted_in_repos = FALSE; sb.changelist_hash = changelist_hash; sb.wc_ctx = ctx->wc_ctx; SVN_ERR(svn_dirent_get_absolute(&target_abspath, path, pool)); if (check_out_of_date) { /* The status editor only works on directories, so get the ancestor if necessary */ svn_node_kind_t kind; SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath, TRUE, FALSE, pool)); /* Dir must be a working copy directory or the status editor fails */ if (kind == svn_node_dir) { dir_abspath = target_abspath; target_basename = ""; dir = path; } else { dir_abspath = svn_dirent_dirname(target_abspath, pool); target_basename = svn_dirent_basename(target_abspath, NULL); dir = svn_dirent_dirname(path, pool); if (kind == svn_node_file) { if (depth == svn_depth_empty) depth = svn_depth_files; } else { err = svn_wc_read_kind2(&kind, ctx->wc_ctx, dir_abspath, FALSE, FALSE, pool); svn_error_clear(err); if (err || kind != svn_node_dir) { return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, _("'%s' is not a working copy"), svn_dirent_local_style(path, pool)); } } } } else { dir = path; dir_abspath = target_abspath; } if (svn_dirent_is_absolute(dir)) { sb.anchor_abspath = NULL; sb.anchor_relpath = NULL; } else { sb.anchor_abspath = dir_abspath; sb.anchor_relpath = dir; } /* Get the status edit, and use our wrapping status function/baton as the callback pair. */ SVN_ERR(svn_wc_get_default_ignores(&ignores, ctx->config, pool)); /* If we want to know about out-of-dateness, we crawl the working copy and let the RA layer drive the editor for real. Otherwise, we just close the edit. :-) */ if (check_out_of_date) { svn_ra_session_t *ra_session; const char *URL; svn_node_kind_t kind; svn_boolean_t server_supports_depth; const svn_delta_editor_t *editor; void *edit_baton, *set_locks_baton; svn_revnum_t edit_revision = SVN_INVALID_REVNUM; /* Get full URL from the ANCHOR. */ SVN_ERR(svn_client_url_from_path2(&URL, dir_abspath, ctx, pool, pool)); if (!URL) return svn_error_createf (SVN_ERR_ENTRY_MISSING_URL, NULL, _("Entry '%s' has no URL"), svn_dirent_local_style(dir, pool)); /* Open a repository session to the URL. */ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL, dir_abspath, NULL, FALSE, TRUE, ctx, pool, pool)); SVN_ERR(svn_ra_has_capability(ra_session, &server_supports_depth, SVN_RA_CAPABILITY_DEPTH, pool)); SVN_ERR(svn_wc__get_status_editor(&editor, &edit_baton, &set_locks_baton, &edit_revision, ctx->wc_ctx, dir_abspath, target_basename, depth, get_all, check_working_copy, no_ignore, depth_as_sticky, server_supports_depth, ignores, tweak_status, &sb, ctx->cancel_func, ctx->cancel_baton, pool, pool)); /* Verify that URL exists in HEAD. If it doesn't, this can save us a whole lot of hassle; if it does, the cost of this request should be minimal compared to the size of getting back the average amount of "out-of-date" information. */ SVN_ERR(svn_ra_check_path(ra_session, "", SVN_INVALID_REVNUM, &kind, pool)); if (kind == svn_node_none) { svn_boolean_t added; /* Our status target does not exist in HEAD. If we've got it locally added, that's okay. But if it was previously versioned, then it must have since been deleted from the repository. (Note that "locally replaced" doesn't count as "added" in this case.) */ SVN_ERR(svn_wc__node_is_added(&added, ctx->wc_ctx, dir_abspath, pool)); if (! added) sb.deleted_in_repos = TRUE; /* And now close the edit. */ SVN_ERR(editor->close_edit(edit_baton, pool)); } else { svn_revnum_t revnum; report_baton_t rb; svn_depth_t status_depth; if (revision->kind == svn_opt_revision_head) { /* Cause the revision number to be omitted from the request, which implies HEAD. */ revnum = SVN_INVALID_REVNUM; } else { /* Get a revision number for our status operation. */ SVN_ERR(svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx, target_abspath, ra_session, revision, pool)); } if (depth_as_sticky || !server_supports_depth) status_depth = depth; else status_depth = svn_depth_unknown; /* Use depth from WC */ /* Do the deed. Let the RA layer drive the status editor. */ SVN_ERR(svn_ra_do_status2(ra_session, &rb.wrapped_reporter, &rb.wrapped_report_baton, target_basename, revnum, status_depth, editor, edit_baton, pool)); /* Init the report baton. */ rb.ancestor = apr_pstrdup(pool, URL); /* Edited later */ rb.set_locks_baton = set_locks_baton; rb.ctx = ctx; rb.pool = pool; if (depth == svn_depth_unknown) rb.depth = svn_depth_infinity; else rb.depth = depth; /* Drive the reporter structure, describing the revisions within PATH. When we call reporter->finish_report, EDITOR will be driven to describe differences between our working copy and HEAD. */ SVN_ERR(svn_wc_crawl_revisions5(ctx->wc_ctx, target_abspath, &lock_fetch_reporter, &rb, FALSE /* restore_files */, depth, (! depth_as_sticky), (! server_supports_depth), FALSE /* use_commit_times */, ctx->cancel_func, ctx->cancel_baton, NULL, NULL, pool)); } if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify(target_abspath, svn_wc_notify_status_completed, pool); notify->revision = edit_revision; ctx->notify_func2(ctx->notify_baton2, notify, pool); } /* If the caller wants the result revision, give it to them. */ if (result_rev) *result_rev = edit_revision; } else { err = svn_wc_walk_status(ctx->wc_ctx, target_abspath, depth, get_all, no_ignore, FALSE, ignores, tweak_status, &sb, ctx->cancel_func, ctx->cancel_baton, pool); if (err && err->apr_err == SVN_ERR_WC_MISSING) { /* This error code is checked for in svn to continue after this error */ svn_error_clear(err); return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL, _("'%s' is not a working copy"), svn_dirent_local_style(path, pool)); } SVN_ERR(err); } /* We only descend into an external if depth is svn_depth_infinity or svn_depth_unknown. However, there are conceivable behaviors that would involve descending under other circumstances; thus, we pass depth anyway, so the code will DTRT if we change the conditional in the future. */ if (SVN_DEPTH_IS_RECURSIVE(depth) && (! ignore_externals)) { apr_hash_t *external_map; SVN_ERR(svn_wc__externals_defined_below(&external_map, ctx->wc_ctx, target_abspath, pool, pool)); SVN_ERR(do_external_status(ctx, external_map, depth, get_all, check_out_of_date, check_working_copy, no_ignore, changelists, sb.anchor_abspath, sb.anchor_relpath, status_func, status_baton, pool)); } return SVN_NO_ERROR; }