diff options
Diffstat (limited to 'subversion/libsvn_client/prop_commands.c')
-rw-r--r-- | subversion/libsvn_client/prop_commands.c | 1559 |
1 files changed, 1559 insertions, 0 deletions
diff --git a/subversion/libsvn_client/prop_commands.c b/subversion/libsvn_client/prop_commands.c new file mode 100644 index 0000000..c3c1cfa --- /dev/null +++ b/subversion/libsvn_client/prop_commands.c @@ -0,0 +1,1559 @@ +/* + * prop_commands.c: Implementation of propset, propget, and proplist. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + + +/*** Includes. ***/ + +#define APR_WANT_STRFUNC +#include <apr_want.h> + +#include "svn_error.h" +#include "svn_client.h" +#include "client.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_props.h" +#include "svn_hash.h" +#include "svn_sorts.h" + +#include "svn_private_config.h" +#include "private/svn_wc_private.h" +#include "private/svn_ra_private.h" +#include "private/svn_client_private.h" + + +/*** Code. ***/ + +/* Return an SVN_ERR_CLIENT_PROPERTY_NAME error if NAME is a wcprop, + else return SVN_NO_ERROR. */ +static svn_error_t * +error_if_wcprop_name(const char *name) +{ + if (svn_property_kind2(name) == svn_prop_wc_kind) + { + return svn_error_createf + (SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("'%s' is a wcprop, thus not accessible to clients"), + name); + } + + return SVN_NO_ERROR; +} + + +struct getter_baton +{ + svn_ra_session_t *ra_session; + svn_revnum_t base_revision_for_url; +}; + + +static svn_error_t * +get_file_for_validation(const svn_string_t **mime_type, + svn_stream_t *stream, + void *baton, + apr_pool_t *pool) +{ + struct getter_baton *gb = baton; + svn_ra_session_t *ra_session = gb->ra_session; + apr_hash_t *props; + + SVN_ERR(svn_ra_get_file(ra_session, "", gb->base_revision_for_url, + stream, NULL, + (mime_type ? &props : NULL), + pool)); + + if (mime_type) + *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE); + + return SVN_NO_ERROR; +} + + +static +svn_error_t * +do_url_propset(const char *url, + const char *propname, + const svn_string_t *propval, + const svn_node_kind_t kind, + const svn_revnum_t base_revision_for_url, + const svn_delta_editor_t *editor, + void *edit_baton, + apr_pool_t *pool) +{ + void *root_baton; + + SVN_ERR(editor->open_root(edit_baton, base_revision_for_url, pool, + &root_baton)); + + if (kind == svn_node_file) + { + void *file_baton; + const char *uri_basename = svn_uri_basename(url, pool); + + SVN_ERR(editor->open_file(uri_basename, root_baton, + base_revision_for_url, pool, &file_baton)); + SVN_ERR(editor->change_file_prop(file_baton, propname, propval, pool)); + SVN_ERR(editor->close_file(file_baton, NULL, pool)); + } + else + { + SVN_ERR(editor->change_dir_prop(root_baton, propname, propval, pool)); + } + + return editor->close_directory(root_baton, pool); +} + +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_kind2(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_session2(&ra_session, target, NULL, + ctx, pool, 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); + + if (node_kind == svn_node_file) + { + /* We need to reparent our session one directory up, since editor + semantics require the root is a directory. + + ### How does this interact with authz? */ + const char *parent_url; + parent_url = svn_uri_dirname(target, pool); + + SVN_ERR(svn_ra_reparent(ra_session, parent_url, pool)); + } + + /* 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__register_editor_shim_callbacks(ra_session, + svn_client__get_shim_callbacks(ctx->wc_ctx, + NULL, pool))); + 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(target, 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); +} + +/* Check that PROPNAME is a valid name for a versioned property. Return an + * error if it is not valid, specifically if it is: + * - the name of a standard Subversion rev-prop; or + * - in the namespace of WC-props; or + * - not a well-formed property name (except if PROPVAL is NULL: in other + * words we do allow deleting a prop with an ill-formed name). + * + * Since Subversion controls the "svn:" property namespace, we don't honor + * a 'skip_checks' flag here. Checks for unusual property combinations such + * as svn:eol-style with a non-text svn:mime-type might understandably be + * skipped, but things such as using a property name reserved for revprops + * on a local target are never allowed. + */ +static svn_error_t * +check_prop_name(const char *propname, + const svn_string_t *propval) +{ + if (svn_prop_is_known_svn_rev_prop(propname)) + return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("Revision property '%s' not allowed " + "in this context"), propname); + + SVN_ERR(error_if_wcprop_name(propname)); + + if (propval && ! svn_prop_name_is_valid(propname)) + return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, + _("Bad property name: '%s'"), propname); + + 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; + 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)); + + /* Call prop_set for deleted nodes to have special errors */ + SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath, + FALSE, FALSE, iterpool)); + + if (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_WC__CALL_WITH_WRITE_LOCK( + svn_wc_prop_set4(ctx->wc_ctx, target_abspath, propname, + propval, depth, skip_checks, changelists, + ctx->cancel_func, ctx->cancel_baton, + ctx->notify_func2, ctx->notify_baton2, iterpool), + ctx->wc_ctx, target_abspath, FALSE /* lock_anchor */, iterpool); + } + svn_pool_destroy(iterpool); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client_propset_remote(const char *propname, + const svn_string_t *propval, + const char *url, + 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 *scratch_pool) +{ + if (!svn_path_is_url(url)) + return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, + _("Targets must be URLs")); + + SVN_ERR(check_prop_name(propname, propval)); + + /* The rationale for requiring the base_revision_for_url + argument is that without it, it's too easy to possibly + overwrite someone else's change without noticing. (See also + tools/examples/svnput.c). */ + if (! SVN_IS_VALID_REVNUM(base_revision_for_url)) + return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, + _("Setting property on non-local targets " + "needs a base revision")); + + /* ### When you set svn:eol-style or svn:keywords on a wc file, + ### Subversion sends a textdelta at commit time to properly + ### normalize the file in the repository. If we want to + ### support editing these properties on URLs, then we should + ### generate the same textdelta; for now, we won't support + ### editing these properties on URLs. (Admittedly, this + ### means that all the machinery with get_file_for_validation + ### is unused.) + */ + if ((strcmp(propname, SVN_PROP_EOL_STYLE) == 0) || + (strcmp(propname, SVN_PROP_KEYWORDS) == 0)) + return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, + _("Setting property '%s' on non-local " + "targets is not supported"), propname); + + SVN_ERR(propset_on_url(propname, propval, url, skip_checks, + base_revision_for_url, revprop_table, + commit_callback, commit_baton, ctx, scratch_pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +check_and_set_revprop(svn_revnum_t *set_rev, + svn_ra_session_t *ra_session, + const char *propname, + const svn_string_t *original_propval, + const svn_string_t *propval, + apr_pool_t *pool) +{ + if (original_propval) + { + /* Ensure old value hasn't changed behind our back. */ + svn_string_t *current; + SVN_ERR(svn_ra_rev_prop(ra_session, *set_rev, propname, ¤t, pool)); + + if (original_propval->data && (! current)) + { + return svn_error_createf( + SVN_ERR_RA_OUT_OF_DATE, NULL, + _("revprop '%s' in r%ld is unexpectedly absent " + "in repository (maybe someone else deleted it?)"), + propname, *set_rev); + } + else if (original_propval->data + && (! svn_string_compare(original_propval, current))) + { + return svn_error_createf( + SVN_ERR_RA_OUT_OF_DATE, NULL, + _("revprop '%s' in r%ld has unexpected value " + "in repository (maybe someone else changed it?)"), + propname, *set_rev); + } + else if ((! original_propval->data) && current) + { + return svn_error_createf( + SVN_ERR_RA_OUT_OF_DATE, NULL, + _("revprop '%s' in r%ld is unexpectedly present " + "in repository (maybe someone else set it?)"), + propname, *set_rev); + } + } + + SVN_ERR(svn_ra_change_rev_prop2(ra_session, *set_rev, propname, + NULL, propval, pool)); + + 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. */ + SVN_ERR(svn_client_open_ra_session2(&ra_session, URL, NULL, + ctx, pool, 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; +} + +/* Helper for the remote case of svn_client_propget. + * + * If PROPS is not null, then get the value of property PROPNAME in REVNUM, + using RA_LIB and SESSION. Store the value ('svn_string_t *') in PROPS, + under the path key "TARGET_PREFIX/TARGET_RELATIVE" ('const char *'). + * + * If INHERITED_PROPS is not null, then set *INHERITED_PROPS to a + * depth-first ordered array of svn_prop_inherited_item_t * structures + * representing the PROPNAME properties inherited by the target. If + * INHERITABLE_PROPS in not null and no inheritable properties are found, + * then set *INHERITED_PROPS to an empty array. + * + * Recurse according to DEPTH, similarly to svn_client_propget3(). + * + * KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE". + * Yes, caller passes this; it makes the recursion more efficient :-). + * + * Allocate PROPS and *INHERITED_PROPS in RESULT_POOL, but do all temporary + * work in SCRATCH_POOL. The two pools can be the same; recursive + * calls may use a different SCRATCH_POOL, however. + */ +static svn_error_t * +remote_propget(apr_hash_t *props, + apr_array_header_t **inherited_props, + const char *propname, + const char *target_prefix, + const char *target_relative, + svn_node_kind_t kind, + svn_revnum_t revnum, + svn_ra_session_t *ra_session, + svn_depth_t depth, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_hash_t *dirents; + apr_hash_t *prop_hash = NULL; + const svn_string_t *val; + const char *target_full_url = + svn_path_url_add_component2(target_prefix, target_relative, + scratch_pool); + + if (kind == svn_node_dir) + { + SVN_ERR(svn_ra_get_dir2(ra_session, + (depth >= svn_depth_files ? &dirents : NULL), + NULL, &prop_hash, target_relative, revnum, + SVN_DIRENT_KIND, scratch_pool)); + } + else if (kind == svn_node_file) + { + SVN_ERR(svn_ra_get_file(ra_session, target_relative, revnum, + NULL, NULL, &prop_hash, scratch_pool)); + } + else if (kind == svn_node_none) + { + return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL, + _("'%s' does not exist in revision %ld"), + target_full_url, revnum); + } + else + { + return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL, + _("Unknown node kind for '%s'"), + target_full_url); + } + + if (inherited_props) + { + const char *repos_root_url; + + /* We will filter out all but PROPNAME later, making a final copy + in RESULT_POOL, so pass SCRATCH_POOL for all pools. */ + SVN_ERR(svn_ra_get_inherited_props(ra_session, inherited_props, + target_relative, revnum, + scratch_pool, scratch_pool)); + SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, + scratch_pool)); + SVN_ERR(svn_client__iprop_relpaths_to_urls(*inherited_props, + repos_root_url, + scratch_pool, + scratch_pool)); + } + + /* Make a copy of any inherited PROPNAME properties in RESULT_POOL. */ + if (inherited_props) + { + int i; + apr_array_header_t *final_iprops = + apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *)); + + for (i = 0; i < (*inherited_props)->nelts; i++) + { + svn_prop_inherited_item_t *iprop = + APR_ARRAY_IDX((*inherited_props), i, svn_prop_inherited_item_t *); + svn_string_t *iprop_val = svn_hash_gets(iprop->prop_hash, propname); + + if (iprop_val) + { + svn_prop_inherited_item_t *new_iprop = + apr_palloc(result_pool, sizeof(*new_iprop)); + new_iprop->path_or_url = + apr_pstrdup(result_pool, iprop->path_or_url); + new_iprop->prop_hash = apr_hash_make(result_pool); + svn_hash_sets(new_iprop->prop_hash, + apr_pstrdup(result_pool, propname), + svn_string_dup(iprop_val, result_pool)); + APR_ARRAY_PUSH(final_iprops, svn_prop_inherited_item_t *) = + new_iprop; + } + } + *inherited_props = final_iprops; + } + + if (prop_hash + && (val = svn_hash_gets(prop_hash, propname))) + { + svn_hash_sets(props, + apr_pstrdup(result_pool, target_full_url), + svn_string_dup(val, result_pool)); + } + + if (depth >= svn_depth_files + && kind == svn_node_dir + && apr_hash_count(dirents) > 0) + { + apr_hash_index_t *hi; + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + for (hi = apr_hash_first(scratch_pool, dirents); + hi; + hi = apr_hash_next(hi)) + { + const char *this_name = svn__apr_hash_index_key(hi); + svn_dirent_t *this_ent = svn__apr_hash_index_val(hi); + const char *new_target_relative; + svn_depth_t depth_below_here = depth; + + svn_pool_clear(iterpool); + + if (depth == svn_depth_files && this_ent->kind == svn_node_dir) + continue; + + if (depth == svn_depth_files || depth == svn_depth_immediates) + depth_below_here = svn_depth_empty; + + new_target_relative = svn_relpath_join(target_relative, this_name, + iterpool); + + SVN_ERR(remote_propget(props, NULL, + propname, + target_prefix, + new_target_relative, + this_ent->kind, + revnum, + ra_session, + depth_below_here, + result_pool, iterpool)); + } + + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + +/* Baton for recursive_propget_receiver(). */ +struct recursive_propget_receiver_baton +{ + apr_hash_t *props; /* Hash to collect props. */ + apr_pool_t *pool; /* Pool to allocate additions to PROPS. */ + svn_wc_context_t *wc_ctx; /* Working copy context. */ +}; + +/* An implementation of svn_wc__proplist_receiver_t. */ +static svn_error_t * +recursive_propget_receiver(void *baton, + const char *local_abspath, + apr_hash_t *props, + apr_pool_t *scratch_pool) +{ + struct recursive_propget_receiver_baton *b = baton; + + if (apr_hash_count(props)) + { + apr_hash_index_t *hi = apr_hash_first(scratch_pool, props); + svn_hash_sets(b->props, apr_pstrdup(b->pool, local_abspath), + svn_string_dup(svn__apr_hash_index_val(hi), b->pool)); + } + + return SVN_NO_ERROR; +} + +/* Return the property value for any PROPNAME set on TARGET in *PROPS, + with WC paths of char * for keys and property values of + svn_string_t * for values. Assumes that PROPS is non-NULL. Additions + to *PROPS are allocated in RESULT_POOL, temporary allocations happen in + SCRATCH_POOL. + + CHANGELISTS is an array of const char * changelist names, used as a + restrictive filter on items whose properties are set; that is, + don't set properties on any item unless it's a member of one of + those changelists. If CHANGELISTS is empty (or altogether NULL), + no changelist filtering occurs. + + Treat DEPTH as in svn_client_propget3(). +*/ +static svn_error_t * +get_prop_from_wc(apr_hash_t **props, + const char *propname, + const char *target_abspath, + svn_boolean_t pristine, + svn_node_kind_t kind, + svn_depth_t depth, + const apr_array_header_t *changelists, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + struct recursive_propget_receiver_baton rb; + + /* Technically, svn_depth_unknown just means use whatever depth(s) + we find in the working copy. But this is a walk over extant + working copy paths: if they're there at all, then by definition + the local depth reaches them, so let's just use svn_depth_infinity + to get there. */ + if (depth == svn_depth_unknown) + depth = svn_depth_infinity; + + if (!pristine && depth == svn_depth_infinity + && (!changelists || changelists->nelts == 0)) + { + /* Handle this common svn:mergeinfo case more efficient than the target + list handling in the recursive retrieval. */ + SVN_ERR(svn_wc__prop_retrieve_recursive( + props, ctx->wc_ctx, target_abspath, propname, + result_pool, scratch_pool)); + return SVN_NO_ERROR; + } + + *props = apr_hash_make(result_pool); + rb.props = *props; + rb.pool = result_pool; + rb.wc_ctx = ctx->wc_ctx; + + SVN_ERR(svn_wc__prop_list_recursive(ctx->wc_ctx, target_abspath, + propname, depth, pristine, + changelists, + recursive_propget_receiver, &rb, + ctx->cancel_func, ctx->cancel_baton, + scratch_pool)); + + return SVN_NO_ERROR; +} + +/* Note: this implementation is very similar to svn_client_proplist. */ +svn_error_t * +svn_client_propget5(apr_hash_t **props, + apr_array_header_t **inherited_props, + const char *propname, + const char *target, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *revision, + svn_revnum_t *actual_revnum, + svn_depth_t depth, + const apr_array_header_t *changelists, + svn_client_ctx_t *ctx, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + svn_revnum_t revnum; + svn_boolean_t local_explicit_props; + svn_boolean_t local_iprops; + + SVN_ERR(error_if_wcprop_name(propname)); + if (!svn_path_is_url(target)) + SVN_ERR_ASSERT(svn_dirent_is_absolute(target)); + + peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision, + target); + revision = svn_cl__rev_default_to_peg(revision, peg_revision); + + local_explicit_props = + (! svn_path_is_url(target) + && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind) + && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind)); + + local_iprops = + (local_explicit_props + && (peg_revision->kind == svn_opt_revision_working + || peg_revision->kind == svn_opt_revision_unspecified ) + && (revision->kind == svn_opt_revision_working + || revision->kind == svn_opt_revision_unspecified )); + + if (local_explicit_props) + { + svn_node_kind_t kind; + svn_boolean_t pristine; + svn_error_t *err; + + /* If FALSE, we want the working revision. */ + pristine = (revision->kind == svn_opt_revision_committed + || revision->kind == svn_opt_revision_base); + + SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target, + pristine, FALSE, + scratch_pool)); + + if (kind == svn_node_unknown || kind == svn_node_none) + { + /* svn uses SVN_ERR_UNVERSIONED_RESOURCE as warning only + for this function. */ + return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL, + _("'%s' is not under version control"), + svn_dirent_local_style(target, + scratch_pool)); + } + + err = svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx, + target, NULL, revision, + scratch_pool); + if (err && err->apr_err == SVN_ERR_CLIENT_BAD_REVISION) + { + svn_error_clear(err); + revnum = SVN_INVALID_REVNUM; + } + else if (err) + return svn_error_trace(err); + + if (inherited_props && local_iprops) + { + const char *repos_root_url; + + SVN_ERR(svn_wc__get_iprops(inherited_props, ctx->wc_ctx, + target, propname, + result_pool, scratch_pool)); + SVN_ERR(svn_client_get_repos_root(&repos_root_url, NULL, + target, ctx, scratch_pool, + scratch_pool)); + SVN_ERR(svn_client__iprop_relpaths_to_urls(*inherited_props, + repos_root_url, + result_pool, + scratch_pool)); + } + + SVN_ERR(get_prop_from_wc(props, propname, target, + pristine, kind, + depth, changelists, ctx, result_pool, + scratch_pool)); + } + + if ((inherited_props && !local_iprops) + || !local_explicit_props) + { + svn_ra_session_t *ra_session; + svn_node_kind_t kind; + svn_opt_revision_t new_operative_rev; + svn_opt_revision_t new_peg_rev; + + /* Peg or operative revisions may be WC specific for + TARGET's explicit props, but still require us to + contact the repository for the inherited properties. */ + if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind) + || SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind)) + { + svn_revnum_t origin_rev; + const char *repos_relpath; + const char *repos_root_url; + const char *repos_uuid; + const char *local_abspath; + const char *copy_root_abspath; + svn_boolean_t is_copy; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, target, + scratch_pool)); + + if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)) + { + SVN_ERR(svn_wc__node_get_origin(&is_copy, + &origin_rev, + &repos_relpath, + &repos_root_url, + &repos_uuid, + ©_root_abspath, + ctx->wc_ctx, + local_abspath, + FALSE, /* scan_deleted */ + result_pool, + scratch_pool)); + if (repos_relpath) + { + target = svn_path_url_add_component2(repos_root_url, + repos_relpath, + scratch_pool); + if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)) + { + svn_revnum_t resolved_peg_rev; + + SVN_ERR(svn_client__get_revision_number( + &resolved_peg_rev, NULL, ctx->wc_ctx, + local_abspath, NULL, peg_revision, scratch_pool)); + new_peg_rev.kind = svn_opt_revision_number; + new_peg_rev.value.number = resolved_peg_rev; + peg_revision = &new_peg_rev; + } + + if (SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind)) + { + svn_revnum_t resolved_operative_rev; + + SVN_ERR(svn_client__get_revision_number( + &resolved_operative_rev, NULL, ctx->wc_ctx, + local_abspath, NULL, revision, scratch_pool)); + new_operative_rev.kind = svn_opt_revision_number; + new_operative_rev.value.number = resolved_operative_rev; + revision = &new_operative_rev; + } + } + else + { + /* TARGET doesn't exist in the repository, so there are + obviously not inherited props to be found there. */ + local_iprops = TRUE; + *inherited_props = apr_array_make( + result_pool, 0, sizeof(svn_prop_inherited_item_t *)); + } + } + } + + /* Do we still have anything to ask the repository about? */ + if (!local_explicit_props || !local_iprops) + { + svn_client__pathrev_t *loc; + + /* Get an RA plugin for this filesystem object. */ + SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc, + target, NULL, + peg_revision, + revision, ctx, + scratch_pool)); + + SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, + scratch_pool)); + + if (!local_explicit_props) + *props = apr_hash_make(result_pool); + + SVN_ERR(remote_propget(!local_explicit_props ? *props : NULL, + !local_iprops ? inherited_props : NULL, + propname, loc->url, "", + kind, loc->rev, ra_session, + depth, result_pool, scratch_pool)); + revnum = loc->rev; + } + } + + if (actual_revnum) + *actual_revnum = revnum; + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client_revprop_get(const char *propname, + svn_string_t **propval, + 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_pool_t *subpool = svn_pool_create(pool); + svn_error_t *err; + + /* 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_session2(&ra_session, URL, NULL, + ctx, subpool, subpool)); + + /* 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, subpool)); + + /* The actual RA call. */ + err = svn_ra_rev_prop(ra_session, *set_rev, propname, propval, pool); + + /* Close RA session */ + svn_pool_destroy(subpool); + return svn_error_trace(err); +} + + +/* Call RECEIVER for the given PATH and its PROP_HASH and/or + * INHERITED_PROPERTIES. + * + * If PROP_HASH is null or has zero count or INHERITED_PROPERTIES is null, + * then do nothing. + */ +static svn_error_t* +call_receiver(const char *path, + apr_hash_t *prop_hash, + apr_array_header_t *inherited_properties, + svn_proplist_receiver2_t receiver, + void *receiver_baton, + apr_pool_t *scratch_pool) +{ + if ((prop_hash && apr_hash_count(prop_hash)) + || inherited_properties) + SVN_ERR(receiver(receiver_baton, path, prop_hash, inherited_properties, + scratch_pool)); + + return SVN_NO_ERROR; +} + + +/* Helper for the remote case of svn_client_proplist. + * + * If GET_EXPLICIT_PROPS is true, then call RECEIVER for paths at or under + * "TARGET_PREFIX/TARGET_RELATIVE@REVNUM" (obtained using RA_SESSION) which + * have regular properties. If GET_TARGET_INHERITED_PROPS is true, then send + * the target's inherited properties to the callback. + * + * The 'path' and keys for 'prop_hash' and 'inherited_prop' arguments to + * RECEIVER are all URLs. + * + * RESULT_POOL is used to allocated the 'path', 'prop_hash', and + * 'inherited_prop' arguments to RECEIVER. SCRATCH_POOL is used for all + * other (temporary) allocations. + * + * KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE". + * + * If the target is a directory, only fetch properties for the files + * and directories at depth DEPTH. DEPTH has not effect on inherited + * properties. + */ +static svn_error_t * +remote_proplist(const char *target_prefix, + const char *target_relative, + svn_node_kind_t kind, + svn_revnum_t revnum, + svn_ra_session_t *ra_session, + svn_boolean_t get_explicit_props, + svn_boolean_t get_target_inherited_props, + svn_depth_t depth, + svn_proplist_receiver2_t receiver, + void *receiver_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *scratch_pool) +{ + apr_hash_t *dirents; + apr_hash_t *prop_hash = NULL; + apr_hash_index_t *hi; + const char *target_full_url = + svn_path_url_add_component2(target_prefix, target_relative, scratch_pool); + apr_array_header_t *inherited_props; + + /* Note that we pass only the SCRATCH_POOL to svn_ra_get[dir*|file*] because + we'll be filtering out non-regular properties from PROP_HASH before we + return. */ + if (kind == svn_node_dir) + { + SVN_ERR(svn_ra_get_dir2(ra_session, + (depth > svn_depth_empty) ? &dirents : NULL, + NULL, &prop_hash, target_relative, revnum, + SVN_DIRENT_KIND, scratch_pool)); + } + else if (kind == svn_node_file) + { + SVN_ERR(svn_ra_get_file(ra_session, target_relative, revnum, + NULL, NULL, &prop_hash, scratch_pool)); + } + else + { + return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL, + _("Unknown node kind for '%s'"), + target_full_url); + } + + if (get_target_inherited_props) + { + const char *repos_root_url; + + SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props, + target_relative, revnum, + scratch_pool, scratch_pool)); + SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url, + scratch_pool)); + SVN_ERR(svn_client__iprop_relpaths_to_urls(inherited_props, + repos_root_url, + scratch_pool, + scratch_pool)); + } + else + { + inherited_props = NULL; + } + + if (!get_explicit_props) + prop_hash = NULL; + else + { + /* Filter out non-regular properties, since the RA layer returns all + kinds. Copy regular properties keys/vals from the prop_hash + allocated in SCRATCH_POOL to the "final" hash allocated in + RESULT_POOL. */ + for (hi = apr_hash_first(scratch_pool, prop_hash); + hi; + hi = apr_hash_next(hi)) + { + const char *name = svn__apr_hash_index_key(hi); + apr_ssize_t klen = svn__apr_hash_index_klen(hi); + svn_prop_kind_t prop_kind; + + prop_kind = svn_property_kind2(name); + + if (prop_kind != svn_prop_regular_kind) + { + apr_hash_set(prop_hash, name, klen, NULL); + } + } + } + + SVN_ERR(call_receiver(target_full_url, prop_hash, inherited_props, + receiver, receiver_baton, scratch_pool)); + + if (depth > svn_depth_empty + && get_explicit_props + && (kind == svn_node_dir) && (apr_hash_count(dirents) > 0)) + { + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + + for (hi = apr_hash_first(scratch_pool, dirents); + hi; + hi = apr_hash_next(hi)) + { + const char *this_name = svn__apr_hash_index_key(hi); + svn_dirent_t *this_ent = svn__apr_hash_index_val(hi); + const char *new_target_relative; + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + + svn_pool_clear(iterpool); + + new_target_relative = svn_relpath_join(target_relative, + this_name, iterpool); + + if (this_ent->kind == svn_node_file + || depth > svn_depth_files) + { + svn_depth_t depth_below_here = depth; + + if (depth == svn_depth_immediates) + depth_below_here = svn_depth_empty; + + SVN_ERR(remote_proplist(target_prefix, + new_target_relative, + this_ent->kind, + revnum, + ra_session, + TRUE /* get_explicit_props */, + FALSE /* get_target_inherited_props */, + depth_below_here, + receiver, receiver_baton, + cancel_func, cancel_baton, + iterpool)); + } + } + + svn_pool_destroy(iterpool); + } + + return SVN_NO_ERROR; +} + + +/* Baton for recursive_proplist_receiver(). */ +struct recursive_proplist_receiver_baton +{ + svn_wc_context_t *wc_ctx; /* Working copy context. */ + svn_proplist_receiver2_t wrapped_receiver; /* Proplist receiver to call. */ + void *wrapped_receiver_baton; /* Baton for the proplist receiver. */ + + /* Anchor, anchor_abspath pair for converting to relative paths */ + const char *anchor; + const char *anchor_abspath; +}; + +/* An implementation of svn_wc__proplist_receiver_t. */ +static svn_error_t * +recursive_proplist_receiver(void *baton, + const char *local_abspath, + apr_hash_t *props, + apr_pool_t *scratch_pool) +{ + struct recursive_proplist_receiver_baton *b = baton; + const char *path; + + /* Attempt to convert absolute paths to relative paths for + * presentation purposes, if needed. */ + if (b->anchor && b->anchor_abspath) + { + path = svn_dirent_join(b->anchor, + svn_dirent_skip_ancestor(b->anchor_abspath, + local_abspath), + scratch_pool); + } + else + path = local_abspath; + + return svn_error_trace(b->wrapped_receiver(b->wrapped_receiver_baton, + path, props, NULL, + scratch_pool)); +} + +/* Helper for svn_client_proplist4 when retrieving properties and/or + inherited properties from the repository. Except as noted below, + all arguments are as per svn_client_proplist4. + + GET_EXPLICIT_PROPS controls if explicit props are retrieved. */ +static svn_error_t * +get_remote_props(const char *path_or_url, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *revision, + svn_depth_t depth, + svn_boolean_t get_explicit_props, + svn_boolean_t get_target_inherited_props, + svn_proplist_receiver2_t receiver, + void *receiver_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_ra_session_t *ra_session; + svn_node_kind_t kind; + svn_opt_revision_t new_operative_rev; + svn_opt_revision_t new_peg_rev; + svn_client__pathrev_t *loc; + + /* Peg or operative revisions may be WC specific for + PATH_OR_URL's explicit props, but still require us to + contact the repository for the inherited properties. */ + if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind) + || SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind)) + { + svn_revnum_t origin_rev; + const char *repos_relpath; + const char *repos_root_url; + const char *repos_uuid; + const char *local_abspath; + const char *copy_root_abspath; + svn_boolean_t is_copy; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url, + scratch_pool)); + + if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)) + { + SVN_ERR(svn_wc__node_get_origin(&is_copy, + &origin_rev, + &repos_relpath, + &repos_root_url, + &repos_uuid, + ©_root_abspath, + ctx->wc_ctx, + local_abspath, + FALSE, /* scan_deleted */ + scratch_pool, + scratch_pool)); + if (repos_relpath) + { + path_or_url = + svn_path_url_add_component2(repos_root_url, + repos_relpath, + scratch_pool); + if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)) + { + svn_revnum_t resolved_peg_rev; + + SVN_ERR(svn_client__get_revision_number(&resolved_peg_rev, + NULL, ctx->wc_ctx, + local_abspath, NULL, + peg_revision, + scratch_pool)); + new_peg_rev.kind = svn_opt_revision_number; + new_peg_rev.value.number = resolved_peg_rev; + peg_revision = &new_peg_rev; + } + + if (SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind)) + { + svn_revnum_t resolved_operative_rev; + + SVN_ERR(svn_client__get_revision_number( + &resolved_operative_rev, + NULL, ctx->wc_ctx, + local_abspath, NULL, + revision, + scratch_pool)); + new_operative_rev.kind = svn_opt_revision_number; + new_operative_rev.value.number = resolved_operative_rev; + revision = &new_operative_rev; + } + } + else + { + /* PATH_OR_URL doesn't exist in the repository, so there are + obviously not inherited props to be found there. If we + aren't looking for explicit props then we're done. */ + if (!get_explicit_props) + return SVN_NO_ERROR; + } + } + } + + /* Get an RA session for this URL. */ + SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc, + path_or_url, NULL, + peg_revision, + revision, ctx, + scratch_pool)); + + SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind, + scratch_pool)); + + SVN_ERR(remote_proplist(loc->url, "", kind, loc->rev, ra_session, + get_explicit_props, + get_target_inherited_props, + depth, receiver, receiver_baton, + ctx->cancel_func, ctx->cancel_baton, + scratch_pool)); + return SVN_NO_ERROR; +} + +/* Helper for svn_client_proplist4 when retrieving properties and + possibly inherited properties from the WC. All arguments are as + per svn_client_proplist4. */ +static svn_error_t * +get_local_props(const char *path_or_url, + const svn_opt_revision_t *revision, + svn_depth_t depth, + const apr_array_header_t *changelists, + svn_boolean_t get_target_inherited_props, + svn_proplist_receiver2_t receiver, + void *receiver_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_boolean_t pristine; + svn_node_kind_t kind; + apr_hash_t *changelist_hash = NULL; + const char *local_abspath; + + SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url, + scratch_pool)); + + pristine = ((revision->kind == svn_opt_revision_committed) + || (revision->kind == svn_opt_revision_base)); + + SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath, + pristine, FALSE, scratch_pool)); + + if (kind == svn_node_unknown || kind == svn_node_none) + { + /* svn uses SVN_ERR_UNVERSIONED_RESOURCE as warning only + for this function. */ + return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL, + _("'%s' is not under version control"), + svn_dirent_local_style(local_abspath, + scratch_pool)); + } + + if (get_target_inherited_props) + { + apr_array_header_t *iprops; + const char *repos_root_url; + + SVN_ERR(svn_wc__get_iprops(&iprops, ctx->wc_ctx, local_abspath, + NULL, scratch_pool, scratch_pool)); + SVN_ERR(svn_client_get_repos_root(&repos_root_url, NULL, local_abspath, + ctx, scratch_pool, scratch_pool)); + SVN_ERR(svn_client__iprop_relpaths_to_urls(iprops, repos_root_url, + scratch_pool, + scratch_pool)); + SVN_ERR(call_receiver(path_or_url, NULL, iprops, receiver, + receiver_baton, scratch_pool)); + } + + if (changelists && changelists->nelts) + SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash, + changelists, scratch_pool)); + + /* Fetch, recursively or not. */ + if (kind == svn_node_dir) + { + struct recursive_proplist_receiver_baton rb; + + rb.wc_ctx = ctx->wc_ctx; + rb.wrapped_receiver = receiver; + rb.wrapped_receiver_baton = receiver_baton; + + if (strcmp(path_or_url, local_abspath) != 0) + { + rb.anchor = path_or_url; + rb.anchor_abspath = local_abspath; + } + else + { + rb.anchor = NULL; + rb.anchor_abspath = NULL; + } + + SVN_ERR(svn_wc__prop_list_recursive(ctx->wc_ctx, local_abspath, NULL, + depth, pristine, changelists, + recursive_proplist_receiver, &rb, + ctx->cancel_func, ctx->cancel_baton, + scratch_pool)); + } + else if (svn_wc__changelist_match(ctx->wc_ctx, local_abspath, + changelist_hash, scratch_pool)) + { + apr_hash_t *props; + + if (pristine) + SVN_ERR(svn_wc_get_pristine_props(&props, + ctx->wc_ctx, local_abspath, + scratch_pool, scratch_pool)); + else + { + svn_error_t *err; + + err = svn_wc_prop_list2(&props, ctx->wc_ctx, local_abspath, + scratch_pool, scratch_pool); + + + if (err) + { + if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS) + return svn_error_trace(err); + /* As svn_wc_prop_list2() doesn't return NULL for locally-deleted + let's do that here. */ + svn_error_clear(err); + props = apr_hash_make(scratch_pool); + } + } + + SVN_ERR(call_receiver(path_or_url, props, NULL, + receiver, receiver_baton, scratch_pool)); + + } + return SVN_NO_ERROR; +} + +svn_error_t * +svn_client_proplist4(const char *path_or_url, + const svn_opt_revision_t *peg_revision, + const svn_opt_revision_t *revision, + svn_depth_t depth, + const apr_array_header_t *changelists, + svn_boolean_t get_target_inherited_props, + svn_proplist_receiver2_t receiver, + void *receiver_baton, + svn_client_ctx_t *ctx, + apr_pool_t *scratch_pool) +{ + svn_boolean_t local_explicit_props; + svn_boolean_t local_iprops; + + peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision, + path_or_url); + revision = svn_cl__rev_default_to_peg(revision, peg_revision); + + if (depth == svn_depth_unknown) + depth = svn_depth_empty; + + /* Are explicit props available locally? */ + local_explicit_props = + (! svn_path_is_url(path_or_url) + && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind) + && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind)); + + /* If we want iprops are they available locally? */ + local_iprops = + (get_target_inherited_props /* We want iprops */ + && local_explicit_props /* No local explicit props means no local iprops. */ + && (peg_revision->kind == svn_opt_revision_working + || peg_revision->kind == svn_opt_revision_unspecified ) + && (revision->kind == svn_opt_revision_working + || revision->kind == svn_opt_revision_unspecified )); + + if ((get_target_inherited_props && !local_iprops) + || !local_explicit_props) + { + SVN_ERR(get_remote_props(path_or_url, peg_revision, revision, depth, + !local_explicit_props, + (get_target_inherited_props && !local_iprops), + receiver, receiver_baton, ctx, scratch_pool)); + } + + if (local_explicit_props) + { + SVN_ERR(get_local_props(path_or_url, revision, depth, changelists, + local_iprops, receiver, receiver_baton, ctx, + scratch_pool)); + } + + return SVN_NO_ERROR; +} + +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; + apr_pool_t *subpool = svn_pool_create(pool); + svn_error_t *err; + + /* 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_session2(&ra_session, URL, NULL, + ctx, subpool, subpool)); + + /* 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, subpool)); + + /* The actual RA call. */ + err = svn_ra_rev_proplist(ra_session, *set_rev, &proplist, pool); + + *props = proplist; + svn_pool_destroy(subpool); /* Close RA session */ + return svn_error_trace(err); +} |