svn_error_t * svn_client_revprop_list(apr_hash_t **props, const char *URL, const svn_opt_revision_t *revision, svn_revnum_t *set_rev, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_ra_session_t *ra_session; apr_hash_t *proplist; /* Open an RA session for the URL. Note that we don't have a local directory, nor a place to put temp files. */ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL, NULL, NULL, FALSE, TRUE, ctx, pool)); /* Resolve the revision into something real, and return that to the caller as well. */ SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL, ra_session, revision, pool)); /* The actual RA call. */ SVN_ERR(svn_ra_rev_proplist(ra_session, *set_rev, &proplist, pool)); *props = proplist; return SVN_NO_ERROR; }
svn_error_t * svn_client_get_repos_root(const char **repos_root, const char **repos_uuid, const char *abspath_or_url, svn_client_ctx_t *ctx, apr_pool_t *result_pool, apr_pool_t *scratch_pool) { svn_ra_session_t *ra_session; /* If PATH_OR_URL is a local path we can fetch the repos root locally. */ if (!svn_path_is_url(abspath_or_url)) { SVN_ERR(svn_wc__node_get_repos_info(repos_root, repos_uuid, ctx->wc_ctx, abspath_or_url, result_pool, scratch_pool)); return SVN_NO_ERROR; } /* If PATH_OR_URL was a URL, we use the RA layer to look it up. */ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, abspath_or_url, NULL, NULL, FALSE, TRUE, ctx, scratch_pool)); if (repos_root) SVN_ERR(svn_ra_get_repos_root2(ra_session, repos_root, result_pool)); if (repos_uuid) SVN_ERR(svn_ra_get_uuid2(ra_session, repos_uuid, result_pool)); return SVN_NO_ERROR; }
/* Implements svn_ra_reporter3_t->finish_report. */ static svn_error_t * reporter_finish_report(void *report_baton, apr_pool_t *pool) { report_baton_t *rb = report_baton; svn_ra_session_t *ras; apr_hash_t *locks; const char *repos_root; apr_pool_t *subpool = svn_pool_create(pool); svn_error_t *err = SVN_NO_ERROR; /* Open an RA session to our common ancestor and grab the locks under it. */ SVN_ERR(svn_client__open_ra_session_internal(&ras, rb->ancestor, NULL, NULL, NULL, FALSE, TRUE, rb->ctx, subpool)); /* The locks need to live throughout the edit. Note that if the server doesn't support lock discovery, we'll just not do locky stuff. */ err = svn_ra_get_locks(ras, &locks, "", rb->pool); if (err && ((err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) || (err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE))) { svn_error_clear(err); err = SVN_NO_ERROR; locks = apr_hash_make(rb->pool); } SVN_ERR(err); SVN_ERR(svn_ra_get_repos_root2(ras, &repos_root, rb->pool)); /* Close the RA session. */ svn_pool_destroy(subpool); SVN_ERR(svn_wc_status_set_repos_locks(rb->set_locks_baton, locks, repos_root, rb->pool)); return rb->wrapped_reporter->finish_report(rb->wrapped_report_baton, pool); }
static svn_error_t * mkdir_urls(const apr_array_header_t *urls, svn_boolean_t make_parents, const apr_hash_t *revprop_table, svn_commit_callback2_t commit_callback, void *commit_baton, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_ra_session_t *ra_session = NULL; const svn_delta_editor_t *editor; void *edit_baton; const char *log_msg; apr_array_header_t *targets; apr_hash_t *targets_hash; apr_hash_t *commit_revprops; svn_error_t *err; const char *common; int i; /* Find any non-existent parent directories */ if (make_parents) { apr_array_header_t *all_urls = apr_array_make(pool, urls->nelts, sizeof(const char *)); const char *first_url = APR_ARRAY_IDX(urls, 0, const char *); apr_pool_t *iterpool = svn_pool_create(pool); SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, first_url, NULL, NULL, FALSE, TRUE, ctx, pool)); for (i = 0; i < urls->nelts; i++) { const char *url = APR_ARRAY_IDX(urls, i, const char *); svn_pool_clear(iterpool); SVN_ERR(add_url_parents(ra_session, url, all_urls, iterpool, pool)); } svn_pool_destroy(iterpool); urls = all_urls; } /* Condense our list of mkdir targets. */ SVN_ERR(svn_uri_condense_targets(&common, &targets, urls, FALSE, pool, pool)); /*Remove duplicate targets introduced by make_parents with more targets. */ SVN_ERR(svn_hash_from_cstring_keys(&targets_hash, targets, pool)); SVN_ERR(svn_hash_keys(&targets, targets_hash, pool)); if (! targets->nelts) { const char *bname; svn_uri_split(&common, &bname, common, pool); APR_ARRAY_PUSH(targets, const char *) = bname; if (*bname == '\0') return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("There is no valid uri above '%s'"), common); } else { svn_boolean_t resplit = FALSE; /* We can't "mkdir" the root of an editor drive, so if one of our targets is the empty string, we need to back everything up by a path component. */ for (i = 0; i < targets->nelts; i++) { const char *path = APR_ARRAY_IDX(targets, i, const char *); if (! *path) { resplit = TRUE; break; } } if (resplit) { const char *bname; svn_uri_split(&common, &bname, common, pool); if (*bname == '\0') return svn_error_createf(SVN_ERR_ILLEGAL_TARGET, NULL, _("There is no valid uri above '%s'"), common); for (i = 0; i < targets->nelts; i++) { const char *path = APR_ARRAY_IDX(targets, i, const char *); path = svn_relpath_join(bname, path, pool); APR_ARRAY_IDX(targets, i, const char *) = path; } } } qsort(targets->elts, targets->nelts, targets->elt_size, svn_sort_compare_paths); /* ### This reparent may be problematic in limited-authz-to-common-parent ### scenarios (compare issue #3242). See also issue #3649. */ if (ra_session) SVN_ERR(svn_ra_reparent(ra_session, common, pool)); /* Create new commit items and add them to the array. */ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) { svn_client_commit_item3_t *item; const char *tmp_file; apr_array_header_t *commit_items = apr_array_make(pool, targets->nelts, sizeof(item)); for (i = 0; i < targets->nelts; i++) { const char *path = APR_ARRAY_IDX(targets, i, const char *); item = svn_client_commit_item3_create(pool); item->url = svn_path_url_add_component2(common, path, pool); item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; } SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items, ctx, pool)); if (! log_msg) return SVN_NO_ERROR; } else
/* Like svn_ra_stat() but with a compatibility hack for pre-1.2 svnserve. */ static svn_error_t * ra_stat_compatible(svn_ra_session_t *ra_session, svn_revnum_t rev, svn_dirent_t **dirent_p, apr_uint32_t dirent_fields, svn_client_ctx_t *ctx, apr_pool_t *pool) { const char *repos_root_URL, *url; svn_error_t *err; svn_dirent_t *the_ent; SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_URL, pool)); SVN_ERR(svn_ra_get_session_url(ra_session, &url, pool)); err = svn_ra_stat(ra_session, "", rev, &the_ent, pool); /* svn_ra_stat() will work against old versions of mod_dav_svn, but not old versions of svnserve. In the case of a pre-1.2 svnserve, catch the specific error it throws:*/ if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) { /* Fall back to pre-1.2 strategy for fetching dirent's URL. */ svn_node_kind_t url_kind; svn_ra_session_t *parent_ra_session; apr_hash_t *parent_ents; const char *parent_url, *base_name; if (strcmp(url, repos_root_URL) == 0) { /* In this universe, there's simply no way to fetch information about the repository's root directory! */ return err; } svn_error_clear(err); SVN_ERR(svn_ra_check_path(ra_session, "", rev, &url_kind, pool)); if (url_kind == svn_node_none) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' non-existent in revision %ld"), url, rev); /* Open a new RA session to the item's parent. */ svn_uri_split(&parent_url, &base_name, url, pool); SVN_ERR(svn_client__open_ra_session_internal(&parent_ra_session, NULL, parent_url, NULL, NULL, FALSE, TRUE, ctx, pool)); /* Get all parent's entries, and find the item's dirent in the hash. */ SVN_ERR(svn_ra_get_dir2(parent_ra_session, &parent_ents, NULL, NULL, "", rev, dirent_fields, pool)); the_ent = apr_hash_get(parent_ents, base_name, APR_HASH_KEY_STRING); if (the_ent == NULL) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' non-existent in revision %ld"), url, rev); } else if (err) { return svn_error_trace(err); } *dirent_p = the_ent; return SVN_NO_ERROR; }
svn_error_t * svn_client_info3(const char *abspath_or_url, const svn_opt_revision_t *peg_revision, const svn_opt_revision_t *revision, svn_depth_t depth, svn_boolean_t fetch_excluded, svn_boolean_t fetch_actual_only, const apr_array_header_t *changelists, svn_client_info_receiver2_t receiver, void *receiver_baton, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_ra_session_t *ra_session, *parent_ra_session; svn_revnum_t rev; const char *url; svn_node_kind_t url_kind; const char *repos_root_URL, *repos_UUID; svn_lock_t *lock; svn_boolean_t related; apr_hash_t *parent_ents; const char *parent_url, *base_name; svn_dirent_t *the_ent; svn_client_info2_t *info; svn_error_t *err; if (depth == svn_depth_unknown) depth = svn_depth_empty; if ((revision == NULL || revision->kind == svn_opt_revision_unspecified) && (peg_revision == NULL || peg_revision->kind == svn_opt_revision_unspecified)) { /* Do all digging in the working copy. */ wc_info_receiver_baton_t b = { receiver, receiver_baton }; return svn_error_trace( svn_wc__get_info(ctx->wc_ctx, abspath_or_url, depth, fetch_excluded, fetch_actual_only, changelists, wc_info_receiver, &b, ctx->cancel_func, ctx->cancel_baton, pool)); } /* Go repository digging instead. */ /* Trace rename history (starting at path_or_url@peg_revision) and return RA session to the possibly-renamed URL as it exists in REVISION. The ra_session returned will be anchored on this "final" URL. */ SVN_ERR(svn_client__ra_session_from_path(&ra_session, &rev, &url, abspath_or_url, NULL, peg_revision, revision, ctx, pool)); SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_URL, pool)); SVN_ERR(svn_ra_get_uuid2(ra_session, &repos_UUID, pool)); svn_uri_split(&parent_url, &base_name, url, pool); /* Get the dirent for the URL itself. */ err = svn_ra_stat(ra_session, "", rev, &the_ent, pool); /* svn_ra_stat() will work against old versions of mod_dav_svn, but not old versions of svnserve. In the case of a pre-1.2 svnserve, catch the specific error it throws:*/ if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) { /* Fall back to pre-1.2 strategy for fetching dirent's URL. */ svn_error_clear(err); if (strcmp(url, repos_root_URL) == 0) { /* In this universe, there's simply no way to fetch information about the repository's root directory! If we're recursing, degrade gracefully: rather than throw an error, return no information about the repos root. */ if (depth > svn_depth_empty) goto pre_1_2_recurse; /* Otherwise, we really are stuck. Better tell the user what's going on. */ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, _("Server does not support retrieving " "information about the repository root")); } SVN_ERR(svn_ra_check_path(ra_session, "", rev, &url_kind, pool)); if (url_kind == svn_node_none) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' non-existent in revision %ld"), url, rev); /* Open a new RA session to the item's parent. */ SVN_ERR(svn_client__open_ra_session_internal(&parent_ra_session, NULL, parent_url, NULL, NULL, FALSE, TRUE, ctx, pool)); /* Get all parent's entries, and find the item's dirent in the hash. */ SVN_ERR(svn_ra_get_dir2(parent_ra_session, &parent_ents, NULL, NULL, "", rev, DIRENT_FIELDS, pool)); the_ent = apr_hash_get(parent_ents, base_name, APR_HASH_KEY_STRING); if (the_ent == NULL) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' non-existent in revision %ld"), url, rev); } else if (err) { return svn_error_trace(err); } if (! the_ent) return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL, _("URL '%s' non-existent in revision %ld"), url, rev); /* Check if the URL exists in HEAD and refers to the same resource. In this case, we check the repository for a lock on this URL. ### There is a possible race here, since HEAD might have changed since ### we checked it. A solution to this problem could be to do the below ### check in a loop which only terminates if the HEAD revision is the same ### before and after this check. That could, however, lead to a ### starvation situation instead. */ SVN_ERR(same_resource_in_head(&related, url, rev, ra_session, ctx, pool)); if (related) { err = svn_ra_get_lock(ra_session, &lock, "", pool); /* An old mod_dav_svn will always work; there's nothing wrong with doing a PROPFIND for a property named "DAV:supportedlock". But an old svnserve will error. */ if (err && err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED) { svn_error_clear(err); lock = NULL; } else if (err) return svn_error_trace(err); } else lock = NULL; /* Push the URL's dirent (and lock) at the callback.*/ SVN_ERR(build_info_from_dirent(&info, the_ent, lock, url, rev, repos_UUID, repos_root_URL, pool)); SVN_ERR(receiver(receiver_baton, base_name, info, pool)); /* Possibly recurse, using the original RA session. */ if (depth > svn_depth_empty && (the_ent->kind == svn_node_dir)) { apr_hash_t *locks; pre_1_2_recurse: if (peg_revision->kind == svn_opt_revision_head) { err = svn_ra_get_locks2(ra_session, &locks, "", depth, pool); /* Catch specific errors thrown by old mod_dav_svn or svnserve. */ if (err && (err->apr_err == SVN_ERR_RA_NOT_IMPLEMENTED || err->apr_err == SVN_ERR_UNSUPPORTED_FEATURE)) { svn_error_clear(err); locks = apr_hash_make(pool); /* use an empty hash */ } else if (err) return svn_error_trace(err); } else locks = apr_hash_make(pool); /* use an empty hash */ SVN_ERR(push_dir_info(ra_session, url, "", rev, repos_UUID, repos_root_URL, receiver, receiver_baton, depth, ctx, locks, pool)); } 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_revprop_set2(const char *propname, const svn_string_t *propval, const svn_string_t *original_propval, const char *URL, const svn_opt_revision_t *revision, svn_revnum_t *set_rev, svn_boolean_t force, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_ra_session_t *ra_session; svn_boolean_t be_atomic; if ((strcmp(propname, SVN_PROP_REVISION_AUTHOR) == 0) && propval && strchr(propval->data, '\n') != NULL && (! force)) return svn_error_create(SVN_ERR_CLIENT_REVISION_AUTHOR_CONTAINS_NEWLINE, NULL, _("Author name should not contain a newline;" " value will not be set unless forced")); if (propval && ! svn_prop_name_is_valid(propname)) return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, _("Bad property name: '%s'"), propname); /* Open an RA session for the URL. Note that we don't have a local directory, nor a place to put temp files. */ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, URL, NULL, NULL, FALSE, TRUE, ctx, pool)); /* Resolve the revision into something real, and return that to the caller as well. */ SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL, ra_session, revision, pool)); SVN_ERR(svn_ra_has_capability(ra_session, &be_atomic, SVN_RA_CAPABILITY_ATOMIC_REVPROPS, pool)); if (be_atomic) { /* Convert ORIGINAL_PROPVAL to an OLD_VALUE_P. */ const svn_string_t *const *old_value_p; const svn_string_t *unset = NULL; if (original_propval == NULL) old_value_p = NULL; else if (original_propval->data == NULL) old_value_p = &unset; else old_value_p = &original_propval; /* The actual RA call. */ SVN_ERR(svn_ra_change_rev_prop2(ra_session, *set_rev, propname, old_value_p, propval, pool)); } else { /* The actual RA call. */ SVN_ERR(check_and_set_revprop(set_rev, ra_session, propname, original_propval, propval, pool)); } if (ctx->notify_func2) { svn_wc_notify_t *notify = svn_wc_create_notify_url(URL, propval == NULL ? svn_wc_notify_revprop_deleted : svn_wc_notify_revprop_set, pool); notify->prop_name = propname; notify->revision = *set_rev; (*ctx->notify_func2)(ctx->notify_baton2, notify, pool); } return SVN_NO_ERROR; }
static svn_error_t * propset_on_url(const char *propname, const svn_string_t *propval, const char *target, svn_boolean_t skip_checks, svn_revnum_t base_revision_for_url, const apr_hash_t *revprop_table, svn_commit_callback2_t commit_callback, void *commit_baton, svn_client_ctx_t *ctx, apr_pool_t *pool) { enum svn_prop_kind prop_kind = svn_property_kind(NULL, propname); svn_ra_session_t *ra_session; svn_node_kind_t node_kind; const char *message; const svn_delta_editor_t *editor; void *edit_baton; apr_hash_t *commit_revprops; svn_error_t *err; if (prop_kind != svn_prop_regular_kind) return svn_error_createf (SVN_ERR_BAD_PROP_KIND, NULL, _("Property '%s' is not a regular property"), propname); /* Open an RA session for the URL. Note that we don't have a local directory, nor a place to put temp files. */ SVN_ERR(svn_client__open_ra_session_internal(&ra_session, NULL, target, NULL, NULL, FALSE, TRUE, ctx, pool)); SVN_ERR(svn_ra_check_path(ra_session, "", base_revision_for_url, &node_kind, pool)); if (node_kind == svn_node_none) return svn_error_createf (SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' does not exist in revision %ld"), target, base_revision_for_url); /* Setting an inappropriate property is not allowed (unless overridden by 'skip_checks', in some circumstances). Deleting an inappropriate property is allowed, however, since older clients allowed (and other clients possibly still allow) setting it in the first place. */ if (propval && svn_prop_is_svn_prop(propname)) { const svn_string_t *new_value; struct getter_baton gb; gb.ra_session = ra_session; gb.base_revision_for_url = base_revision_for_url; SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, propname, propval, target, node_kind, skip_checks, get_file_for_validation, &gb, pool)); propval = new_value; } /* Create a new commit item and add it to the array. */ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) { svn_client_commit_item3_t *item; const char *tmp_file; apr_array_header_t *commit_items = apr_array_make(pool, 1, sizeof(item)); item = svn_client_commit_item3_create(pool); item->url = target; item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS; APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items, ctx, pool)); if (! message) return SVN_NO_ERROR; } else message = ""; SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, message, ctx, pool)); /* Fetch RA commit editor. */ SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton, commit_revprops, commit_callback, commit_baton, NULL, TRUE, /* No lock tokens */ pool)); err = do_url_propset(propname, propval, node_kind, base_revision_for_url, editor, edit_baton, pool); if (err) { /* At least try to abort the edit (and fs txn) before throwing err. */ svn_error_clear(editor->abort_edit(edit_baton, pool)); return svn_error_trace(err); } /* Close the edit. */ return editor->close_edit(edit_baton, pool); }
svn_error_t * svn_client__derive_location(const char **url, svn_revnum_t *peg_revnum, const char *path_or_url, const svn_opt_revision_t *peg_revision, const svn_ra_session_t *ra_session, svn_wc_adm_access_t *adm_access, svn_client_ctx_t *ctx, apr_pool_t *pool) { /* If PATH_OR_URL is a local path (not a URL), we need to transform it into a URL. */ if (! svn_path_is_url(path_or_url)) { const svn_wc_entry_t *entry; if (adm_access) { SVN_ERR(svn_wc__entry_versioned(&entry, path_or_url, adm_access, FALSE, pool)); } else { svn_cancel_func_t cancel_func; void *cancel_baton; if (ctx) { cancel_func = ctx->cancel_func; cancel_baton = ctx->cancel_baton; } SVN_ERR(svn_wc_adm_probe_open3(&adm_access, NULL, path_or_url, FALSE, 0, cancel_func, cancel_baton, pool)); SVN_ERR(svn_wc__entry_versioned(&entry, path_or_url, adm_access, FALSE, pool)); SVN_ERR(svn_wc_adm_close(adm_access)); } SVN_ERR(svn_client__entry_location(url, peg_revnum, path_or_url, peg_revision->kind, entry, pool)); } else { *url = path_or_url; /* peg_revnum (if provided) will be set below. */ } /* If we haven't resolved for ourselves a numeric peg revision, do so. */ if (peg_revnum && !SVN_IS_VALID_REVNUM(*peg_revnum)) { /* Use sesspool to assure that if we opened an RA session, we close it. */ apr_pool_t *sesspool = NULL; svn_ra_session_t *session = (svn_ra_session_t *) ra_session; if (session == NULL) { sesspool = svn_pool_create(pool); SVN_ERR(svn_client__open_ra_session_internal(&session, *url, NULL, NULL, NULL, FALSE, TRUE, ctx, sesspool)); } SVN_ERR(svn_client__get_revision_number(peg_revnum, NULL, session, peg_revision, NULL, pool)); if (sesspool) svn_pool_destroy(sesspool); } 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; }
static svn_error_t * mkdir_urls(svn_commit_info_t **commit_info_p, const apr_array_header_t *urls, svn_boolean_t make_parents, const apr_hash_t *revprop_table, svn_client_ctx_t *ctx, apr_pool_t *pool) { svn_ra_session_t *ra_session = NULL; const svn_delta_editor_t *editor; void *edit_baton; void *commit_baton; const char *log_msg; apr_array_header_t *targets; apr_hash_t *targets_hash; apr_hash_t *commit_revprops; svn_error_t *err; const char *common; int i; /* Find any non-existent parent directories */ if (make_parents) { apr_array_header_t *all_urls = apr_array_make(pool, urls->nelts, sizeof(const char *)); const char *first_url = APR_ARRAY_IDX(urls, 0, const char *); apr_pool_t *iterpool = svn_pool_create(pool); SVN_ERR(svn_client__open_ra_session_internal(&ra_session, first_url, NULL, NULL, NULL, FALSE, TRUE, ctx, pool)); for (i = 0; i < urls->nelts; i++) { const char *url = APR_ARRAY_IDX(urls, i, const char *); svn_pool_clear(iterpool); SVN_ERR(add_url_parents(ra_session, url, all_urls, iterpool, pool)); } svn_pool_destroy(iterpool); urls = all_urls; } /* Condense our list of mkdir targets. */ SVN_ERR(svn_path_condense_targets(&common, &targets, urls, FALSE, pool)); SVN_ERR(svn_hash_from_cstring_keys(&targets_hash, targets, pool)); SVN_ERR(svn_hash_keys(&targets, targets_hash, pool)); if (! targets->nelts) { const char *bname; svn_path_split(common, &common, &bname, pool); APR_ARRAY_PUSH(targets, const char *) = bname; } else { svn_boolean_t resplit = FALSE; /* We can't "mkdir" the root of an editor drive, so if one of our targets is the empty string, we need to back everything up by a path component. */ for (i = 0; i < targets->nelts; i++) { const char *path = APR_ARRAY_IDX(targets, i, const char *); if (! *path) { resplit = TRUE; break; } } if (resplit) { const char *bname; svn_path_split(common, &common, &bname, pool); for (i = 0; i < targets->nelts; i++) { const char *path = APR_ARRAY_IDX(targets, i, const char *); path = svn_path_join(bname, path, pool); APR_ARRAY_IDX(targets, i, const char *) = path; } } } qsort(targets->elts, targets->nelts, targets->elt_size, svn_sort_compare_paths); /* Create new commit items and add them to the array. */ if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) { svn_client_commit_item3_t *item; const char *tmp_file; apr_array_header_t *commit_items = apr_array_make(pool, targets->nelts, sizeof(item)); for (i = 0; i < targets->nelts; i++) { const char *path = APR_ARRAY_IDX(targets, i, const char *); SVN_ERR(svn_client_commit_item_create ((const svn_client_commit_item3_t **) &item, pool)); item->url = svn_path_join(common, path, pool); item->state_flags = SVN_CLIENT_COMMIT_ITEM_ADD; APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; } SVN_ERR(svn_client__get_log_msg(&log_msg, &tmp_file, commit_items, ctx, pool)); if (! log_msg) return SVN_NO_ERROR; } else
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; }
/* 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; }