summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_client/prop_commands.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_client/prop_commands.c')
-rw-r--r--subversion/libsvn_client/prop_commands.c1559
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, &current, 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,
+ &copy_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,
+ &copy_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);
+}
OpenPOWER on IntegriCloud