The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
/*
 * 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;

          /* Avoid assertion on the next line when somebody accidentally asks for
             a working copy revision on a URL */
          if (svn_path_is_url(target))
            return svn_error_create(SVN_ERR_CLIENT_VERSIONED_PATH_REQUIRED,
                                    NULL, NULL);

          SVN_ERR_ASSERT(svn_dirent_is_absolute(target));
          local_abspath = target;

          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. */
  apr_array_header_t *iprops;

  /* 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;
  apr_array_header_t *iprops = NULL;

  if (b->iprops
      && ! strcmp(local_abspath, b->anchor_abspath))
    {
      /* Report iprops with the properties for the anchor */
      iprops = b->iprops;
      b->iprops = NULL;
    }
  else if (b->iprops)
    {
      /* No report for the root?
         Report iprops anyway */

      SVN_ERR(b->wrapped_receiver(b->wrapped_receiver_baton,
                                  b->anchor ? b->anchor : b->anchor_abspath,
                                  NULL /* prop_hash */,
                                  b->iprops,
                                  scratch_pool));
      b->iprops = NULL;
    }

  /* 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, iprops,
                                             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;

      /* Avoid assertion on the next line when somebody accidentally asks for
         a working copy revision on a URL */
      if (svn_path_is_url(path_or_url))
        return svn_error_create(SVN_ERR_CLIENT_VERSIONED_PATH_REQUIRED,
                                NULL, NULL);

      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;
  apr_array_header_t *iprops = NULL;

  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)
    {
      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));
    }

  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;
      rb.iprops = iprops;
      rb.anchor_abspath = local_abspath;

      if (strcmp(path_or_url, local_abspath) != 0)
        {
          rb.anchor = path_or_url;
        }
      else
        {
          rb.anchor = 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));

      if (rb.iprops)
        {
          /* We didn't report for the root. Report iprops anyway */
          SVN_ERR(call_receiver(path_or_url, NULL /* props */, rb.iprops,
                                receiver, receiver_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, iprops,
                            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);
}