prop_commands.c revision 299742
11558Srgrimes/*
21558Srgrimes * prop_commands.c:  Implementation of propset, propget, and proplist.
31558Srgrimes *
41558Srgrimes * ====================================================================
51558Srgrimes *    Licensed to the Apache Software Foundation (ASF) under one
61558Srgrimes *    or more contributor license agreements.  See the NOTICE file
71558Srgrimes *    distributed with this work for additional information
81558Srgrimes *    regarding copyright ownership.  The ASF licenses this file
91558Srgrimes *    to you under the Apache License, Version 2.0 (the
101558Srgrimes *    "License"); you may not use this file except in compliance
111558Srgrimes *    with the License.  You may obtain a copy of the License at
121558Srgrimes *
131558Srgrimes *      http://www.apache.org/licenses/LICENSE-2.0
141558Srgrimes *
151558Srgrimes *    Unless required by applicable law or agreed to in writing,
161558Srgrimes *    software distributed under the License is distributed on an
171558Srgrimes *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
181558Srgrimes *    KIND, either express or implied.  See the License for the
191558Srgrimes *    specific language governing permissions and limitations
201558Srgrimes *    under the License.
211558Srgrimes * ====================================================================
221558Srgrimes */
231558Srgrimes
241558Srgrimes/* ==================================================================== */
251558Srgrimes
261558Srgrimes
271558Srgrimes
281558Srgrimes/*** Includes. ***/
291558Srgrimes
30114589Sobrien#define APR_WANT_STRFUNC
311558Srgrimes#include <apr_want.h>
3238036Scharnier
331558Srgrimes#include "svn_error.h"
341558Srgrimes#include "svn_client.h"
351558Srgrimes#include "client.h"
361558Srgrimes#include "svn_dirent_uri.h"
371558Srgrimes#include "svn_path.h"
3841684Sbde#include "svn_pools.h"
39114589Sobrien#include "svn_props.h"
4038036Scharnier#include "svn_hash.h"
41114589Sobrien#include "svn_sorts.h"
42114589Sobrien
431558Srgrimes#include "svn_private_config.h"
441558Srgrimes#include "private/svn_wc_private.h"
451558Srgrimes#include "private/svn_ra_private.h"
461558Srgrimes#include "private/svn_client_private.h"
471558Srgrimes
481558Srgrimes
491558Srgrimes/*** Code. ***/
5038036Scharnier
511558Srgrimes/* Return an SVN_ERR_CLIENT_PROPERTY_NAME error if NAME is a wcprop,
52114763Sobrien   else return SVN_NO_ERROR. */
531558Srgrimesstatic svn_error_t *
541558Srgrimeserror_if_wcprop_name(const char *name)
551558Srgrimes{
561558Srgrimes  if (svn_property_kind2(name) == svn_prop_wc_kind)
571558Srgrimes    {
581558Srgrimes      return svn_error_createf
591558Srgrimes        (SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
601558Srgrimes         _("'%s' is a wcprop, thus not accessible to clients"),
611558Srgrimes         name);
621558Srgrimes    }
631558Srgrimes
641558Srgrimes  return SVN_NO_ERROR;
651558Srgrimes}
661558Srgrimes
671558Srgrimes
681558Srgrimesstruct getter_baton
691558Srgrimes{
70227081Sed  svn_ra_session_t *ra_session;
711558Srgrimes  svn_revnum_t base_revision_for_url;
721558Srgrimes};
7332399Salex
7432399Salex
7532399Salexstatic svn_error_t *
7632399Salexget_file_for_validation(const svn_string_t **mime_type,
7732399Salex                        svn_stream_t *stream,
7832399Salex                        void *baton,
7932399Salex                        apr_pool_t *pool)
8032399Salex{
8132399Salex  struct getter_baton *gb = baton;
8232399Salex  svn_ra_session_t *ra_session = gb->ra_session;
8332399Salex  apr_hash_t *props;
8432399Salex
851558Srgrimes  SVN_ERR(svn_ra_get_file(ra_session, "", gb->base_revision_for_url,
861558Srgrimes                          stream, NULL,
871558Srgrimes                          (mime_type ? &props : NULL),
881558Srgrimes                          pool));
891558Srgrimes
901558Srgrimes  if (mime_type)
9148078Sru    *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE);
9279749Sdd
9379749Sdd  return SVN_NO_ERROR;
941558Srgrimes}
95197560Sdelphij
96238968Sdes
97197560Sdelphijstatic
98197560Sdelphijsvn_error_t *
99197560Sdelphijdo_url_propset(const char *url,
100197560Sdelphij               const char *propname,
101197560Sdelphij               const svn_string_t *propval,
102197560Sdelphij               const svn_node_kind_t kind,
103197560Sdelphij               const svn_revnum_t base_revision_for_url,
1041558Srgrimes               const svn_delta_editor_t *editor,
105140796Sdelphij               void *edit_baton,
106140796Sdelphij               apr_pool_t *pool)
1071558Srgrimes{
108140796Sdelphij  void *root_baton;
1091558Srgrimes
11079749Sdd  SVN_ERR(editor->open_root(edit_baton, base_revision_for_url, pool,
1111558Srgrimes                            &root_baton));
1121558Srgrimes
1131558Srgrimes  if (kind == svn_node_file)
1141558Srgrimes    {
11526737Scharnier      void *file_baton;
11626737Scharnier      const char *uri_basename = svn_uri_basename(url, pool);
1171558Srgrimes
118216823Spjd      SVN_ERR(editor->open_file(uri_basename, root_baton,
1191558Srgrimes                                base_revision_for_url, pool, &file_baton));
1201558Srgrimes      SVN_ERR(editor->change_file_prop(file_baton, propname, propval, pool));
121216823Spjd      SVN_ERR(editor->close_file(file_baton, NULL, pool));
122216823Spjd    }
123216823Spjd  else
124216823Spjd    {
125216823Spjd      SVN_ERR(editor->change_dir_prop(root_baton, propname, propval, pool));
126229403Sed    }
127216823Spjd
128216823Spjd  return editor->close_directory(root_baton, pool);
129216823Spjd}
130216823Spjd
131216823Spjdstatic svn_error_t *
132216823Spjdpropset_on_url(const char *propname,
133216823Spjd               const svn_string_t *propval,
134216823Spjd               const char *target,
135216823Spjd               svn_boolean_t skip_checks,
136216823Spjd               svn_revnum_t base_revision_for_url,
137216823Spjd               const apr_hash_t *revprop_table,
138216823Spjd               svn_commit_callback2_t commit_callback,
139216823Spjd               void *commit_baton,
140216823Spjd               svn_client_ctx_t *ctx,
141216823Spjd               apr_pool_t *pool)
142216823Spjd{
14348078Sru  enum svn_prop_kind prop_kind = svn_property_kind2(propname);
1441558Srgrimes  svn_ra_session_t *ra_session;
1451558Srgrimes  svn_node_kind_t node_kind;
1461558Srgrimes  const char *message;
1471558Srgrimes  const svn_delta_editor_t *editor;
1481558Srgrimes  void *edit_baton;
1491558Srgrimes  apr_hash_t *commit_revprops;
1501558Srgrimes  svn_error_t *err;
1511558Srgrimes
1521558Srgrimes  if (prop_kind != svn_prop_regular_kind)
1531558Srgrimes    return svn_error_createf
1541558Srgrimes      (SVN_ERR_BAD_PROP_KIND, NULL,
1551558Srgrimes       _("Property '%s' is not a regular property"), propname);
1561558Srgrimes
15748078Sru  /* Open an RA session for the URL. Note that we don't have a local
15848078Sru     directory, nor a place to put temp files. */
15948078Sru  SVN_ERR(svn_client_open_ra_session2(&ra_session, target, NULL,
16041666Smsmith                                      ctx, pool, pool));
16141666Smsmith
16241666Smsmith  SVN_ERR(svn_ra_check_path(ra_session, "", base_revision_for_url,
1631558Srgrimes                            &node_kind, pool));
1641558Srgrimes  if (node_kind == svn_node_none)
1651558Srgrimes    return svn_error_createf
1661558Srgrimes      (SVN_ERR_FS_NOT_FOUND, NULL,
1671558Srgrimes       _("Path '%s' does not exist in revision %ld"),
16848078Sru       target, base_revision_for_url);
1691558Srgrimes
1701558Srgrimes  if (node_kind == svn_node_file)
1711558Srgrimes    {
1721558Srgrimes      /* We need to reparent our session one directory up, since editor
1731558Srgrimes         semantics require the root is a directory.
17448078Sru
1751558Srgrimes         ### How does this interact with authz? */
17648078Sru      const char *parent_url;
17748078Sru      parent_url = svn_uri_dirname(target, pool);
17848062Sjkoshy
17948078Sru      SVN_ERR(svn_ra_reparent(ra_session, parent_url, pool));
18048078Sru    }
18148062Sjkoshy
18248078Sru  /* Setting an inappropriate property is not allowed (unless
18348078Sru     overridden by 'skip_checks', in some circumstances).  Deleting an
18448078Sru     inappropriate property is allowed, however, since older clients
1851558Srgrimes     allowed (and other clients possibly still allow) setting it in
1861558Srgrimes     the first place. */
187216823Spjd  if (propval && svn_prop_is_svn_prop(propname))
1881558Srgrimes    {
1891558Srgrimes      const svn_string_t *new_value;
1901558Srgrimes      struct getter_baton gb;
1911558Srgrimes
1921558Srgrimes      gb.ra_session = ra_session;
1931558Srgrimes      gb.base_revision_for_url = base_revision_for_url;
1941558Srgrimes      SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, propname, propval,
19541684Sbde                                           target, node_kind, skip_checks,
1961558Srgrimes                                           get_file_for_validation, &gb, pool));
1971558Srgrimes      propval = new_value;
1981558Srgrimes    }
1991558Srgrimes
2001558Srgrimes  /* Create a new commit item and add it to the array. */
2011558Srgrimes  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
2021558Srgrimes    {
2031558Srgrimes      svn_client_commit_item3_t *item;
2041558Srgrimes      const char *tmp_file;
2051558Srgrimes      apr_array_header_t *commit_items = apr_array_make(pool, 1, sizeof(item));
2061558Srgrimes
2071558Srgrimes      item = svn_client_commit_item3_create(pool);
2081558Srgrimes      item->url = target;
2091558Srgrimes      item->kind = node_kind;
2101558Srgrimes      item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
2111558Srgrimes      APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
2121558Srgrimes      SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
2131558Srgrimes                                      ctx, pool));
2141558Srgrimes      if (! message)
2151558Srgrimes        return SVN_NO_ERROR;
2161558Srgrimes    }
2171558Srgrimes  else
2181558Srgrimes    message = "";
2191558Srgrimes
2201558Srgrimes  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
2211558Srgrimes                                           message, ctx, pool));
2221558Srgrimes
2231558Srgrimes  /* Fetch RA commit editor. */
2241558Srgrimes  SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
2251558Srgrimes                        svn_client__get_shim_callbacks(ctx->wc_ctx,
2261558Srgrimes                                                       NULL, pool)));
2271558Srgrimes  SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
2281558Srgrimes                                    commit_revprops,
2291558Srgrimes                                    commit_callback,
2301558Srgrimes                                    commit_baton,
2311558Srgrimes                                    NULL, TRUE, /* No lock tokens */
2321558Srgrimes                                    pool));
2331558Srgrimes
23426737Scharnier  err = do_url_propset(target, propname, propval, node_kind,
23526737Scharnier                       base_revision_for_url, editor, edit_baton, pool);
23626737Scharnier
23726737Scharnier  if (err)
2381558Srgrimes    {
23928613Sjoerg      /* At least try to abort the edit (and fs txn) before throwing err. */
2401558Srgrimes      svn_error_clear(editor->abort_edit(edit_baton, pool));
2411558Srgrimes      return svn_error_trace(err);
2421558Srgrimes    }
24338036Scharnier
2441558Srgrimes  if (ctx->notify_func2)
2451558Srgrimes    {
246197560Sdelphij      svn_wc_notify_t *notify;
247197560Sdelphij      notify = svn_wc_create_notify_url(target,
2481558Srgrimes                                        svn_wc_notify_commit_finalizing,
2491558Srgrimes                                        pool);
2501558Srgrimes      ctx->notify_func2(ctx->notify_baton2, notify, pool);
2511558Srgrimes    }
2521558Srgrimes  /* Close the edit. */
2531558Srgrimes  return editor->close_edit(edit_baton, pool);
2541558Srgrimes}
2551558Srgrimes
2561558Srgrimes/* Check that PROPNAME is a valid name for a versioned property.  Return an
2571558Srgrimes * error if it is not valid, specifically if it is:
2581558Srgrimes *   - the name of a standard Subversion rev-prop; or
2591558Srgrimes *   - in the namespace of WC-props; or
2601558Srgrimes *   - not a well-formed property name (except if PROPVAL is NULL: in other
2611558Srgrimes *     words we do allow deleting a prop with an ill-formed name).
2621558Srgrimes *
26348004Sru * Since Subversion controls the "svn:" property namespace, we don't honor
2641558Srgrimes * a 'skip_checks' flag here.  Checks for unusual property combinations such
2651558Srgrimes * as svn:eol-style with a non-text svn:mime-type might understandably be
2661558Srgrimes * skipped, but things such as using a property name reserved for revprops
2671558Srgrimes * on a local target are never allowed.
2681558Srgrimes */
26932399Salexstatic svn_error_t *
27079749Sddcheck_prop_name(const char *propname,
2711558Srgrimes                const svn_string_t *propval)
2721558Srgrimes{
2731558Srgrimes  if (svn_prop_is_known_svn_rev_prop(propname))
2741558Srgrimes    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
2751558Srgrimes                             _("Revision property '%s' not allowed "
2761558Srgrimes                               "in this context"), propname);
2771558Srgrimes
2781558Srgrimes  SVN_ERR(error_if_wcprop_name(propname));
2791558Srgrimes
2801558Srgrimes  if (propval && ! svn_prop_name_is_valid(propname))
2811558Srgrimes    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
2821558Srgrimes                             _("Bad property name: '%s'"), propname);
2831558Srgrimes
2841558Srgrimes  return SVN_NO_ERROR;
285238968Sdes}
2861558Srgrimes
2871558Srgrimessvn_error_t *
2881558Srgrimessvn_client_propset_local(const char *propname,
2891558Srgrimes                         const svn_string_t *propval,
29079749Sdd                         const apr_array_header_t *targets,
29132399Salex                         svn_depth_t depth,
29232399Salex                         svn_boolean_t skip_checks,
29332399Salex                         const apr_array_header_t *changelists,
29432399Salex                         svn_client_ctx_t *ctx,
295197560Sdelphij                         apr_pool_t *scratch_pool)
296140797Sdelphij{
2971558Srgrimes  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
2981558Srgrimes  svn_boolean_t targets_are_urls;
2991558Srgrimes  int i;
3001558Srgrimes
3011558Srgrimes  if (targets->nelts == 0)
3021558Srgrimes    return SVN_NO_ERROR;
3031558Srgrimes
3041558Srgrimes  /* Check for homogeneity among our targets. */
3051558Srgrimes  targets_are_urls = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *));
3061558Srgrimes  SVN_ERR(svn_client__assert_homogeneous_target_type(targets));
3071558Srgrimes
30832399Salex  if (targets_are_urls)
3091558Srgrimes    return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
3101558Srgrimes                            _("Targets must be working copy paths"));
3111558Srgrimes
3121558Srgrimes  SVN_ERR(check_prop_name(propname, propval));
3131558Srgrimes
3141558Srgrimes  for (i = 0; i < targets->nelts; i++)
3151558Srgrimes    {
3161558Srgrimes      svn_node_kind_t kind;
3171558Srgrimes      const char *target_abspath;
3181558Srgrimes      const char *target = APR_ARRAY_IDX(targets, i, const char *);
3191558Srgrimes
3201558Srgrimes      svn_pool_clear(iterpool);
3211558Srgrimes
3221558Srgrimes      /* Check for cancellation */
3231558Srgrimes      if (ctx->cancel_func)
3241558Srgrimes        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
3251558Srgrimes
3261558Srgrimes      SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, iterpool));
3271558Srgrimes
3281558Srgrimes      /* Call prop_set for deleted nodes to have special errors */
3291558Srgrimes      SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath,
3301558Srgrimes                                FALSE, FALSE, iterpool));
3311558Srgrimes
3321558Srgrimes      if (kind == svn_node_unknown || kind == svn_node_none)
3331558Srgrimes        {
33438036Scharnier          if (ctx->notify_func2)
3351558Srgrimes            {
3361558Srgrimes              svn_wc_notify_t *notify = svn_wc_create_notify(
3371558Srgrimes                                          target_abspath,
3381558Srgrimes                                          svn_wc_notify_path_nonexistent,
3391558Srgrimes                                          iterpool);
3401558Srgrimes
3411558Srgrimes              ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
3421558Srgrimes            }
3431558Srgrimes        }
3441558Srgrimes
345197560Sdelphij      SVN_WC__CALL_WITH_WRITE_LOCK(
346140797Sdelphij        svn_wc_prop_set4(ctx->wc_ctx, target_abspath, propname,
3471558Srgrimes                         propval, depth, skip_checks, changelists,
3481558Srgrimes                         ctx->cancel_func, ctx->cancel_baton,
3491558Srgrimes                         ctx->notify_func2, ctx->notify_baton2, iterpool),
3501558Srgrimes        ctx->wc_ctx, target_abspath, FALSE /* lock_anchor */, iterpool);
351197560Sdelphij    }
352238968Sdes  svn_pool_destroy(iterpool);
3531558Srgrimes
35432399Salex  return SVN_NO_ERROR;
3551558Srgrimes}
3561558Srgrimes
35741666Smsmithsvn_error_t *
35841666Smsmithsvn_client_propset_remote(const char *propname,
3591558Srgrimes                          const svn_string_t *propval,
3601558Srgrimes                          const char *url,
3611558Srgrimes                          svn_boolean_t skip_checks,
3621558Srgrimes                          svn_revnum_t base_revision_for_url,
3634844Sats                          const apr_hash_t *revprop_table,
3641558Srgrimes                          svn_commit_callback2_t commit_callback,
3651558Srgrimes                          void *commit_baton,
3661558Srgrimes                          svn_client_ctx_t *ctx,
3671558Srgrimes                          apr_pool_t *scratch_pool)
3681558Srgrimes{
3691558Srgrimes  if (!svn_path_is_url(url))
37041666Smsmith    return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
37141666Smsmith                            _("Targets must be URLs"));
37248078Sru
3731558Srgrimes  SVN_ERR(check_prop_name(propname, propval));
3741558Srgrimes
3751558Srgrimes  /* The rationale for requiring the base_revision_for_url
37648078Sru     argument is that without it, it's too easy to possibly
37748078Sru     overwrite someone else's change without noticing.  (See also
37848078Sru     tools/examples/svnput.c). */
37948078Sru  if (! SVN_IS_VALID_REVNUM(base_revision_for_url))
38048078Sru    return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
38148078Sru                            _("Setting property on non-local targets "
38248078Sru                              "needs a base revision"));
38348078Sru
38448078Sru  /* ### When you set svn:eol-style or svn:keywords on a wc file,
38548078Sru     ### Subversion sends a textdelta at commit time to properly
38648078Sru     ### normalize the file in the repository.  If we want to
38748078Sru     ### support editing these properties on URLs, then we should
38848078Sru     ### generate the same textdelta; for now, we won't support
38948078Sru     ### editing these properties on URLs.  (Admittedly, this
39048078Sru     ### means that all the machinery with get_file_for_validation
39148078Sru     ### is unused.)
39248078Sru   */
39348078Sru  if ((strcmp(propname, SVN_PROP_EOL_STYLE) == 0) ||
39448078Sru      (strcmp(propname, SVN_PROP_KEYWORDS) == 0))
39548078Sru    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
39648078Sru                             _("Setting property '%s' on non-local "
39748078Sru                               "targets is not supported"), propname);
39848078Sru
39948078Sru  SVN_ERR(propset_on_url(propname, propval, url, skip_checks,
40048078Sru                         base_revision_for_url, revprop_table,
40148078Sru                         commit_callback, commit_baton, ctx, scratch_pool));
40248078Sru
40348078Sru  return SVN_NO_ERROR;
4041558Srgrimes}
4051558Srgrimes
4061558Srgrimesstatic svn_error_t *
4071558Srgrimescheck_and_set_revprop(svn_revnum_t *set_rev,
4081558Srgrimes                      svn_ra_session_t *ra_session,
4091558Srgrimes                      const char *propname,
4101558Srgrimes                      const svn_string_t *original_propval,
411197560Sdelphij                      const svn_string_t *propval,
412140797Sdelphij                      apr_pool_t *pool)
4131558Srgrimes{
41479749Sdd  if (original_propval)
41579749Sdd    {
4161558Srgrimes      /* Ensure old value hasn't changed behind our back. */
41732328Salex      svn_string_t *current;
4181558Srgrimes      SVN_ERR(svn_ra_rev_prop(ra_session, *set_rev, propname, &current, pool));
41948062Sjkoshy
42048062Sjkoshy      if (original_propval->data && (! current))
4211558Srgrimes        {
4221558Srgrimes          return svn_error_createf(
42348062Sjkoshy                  SVN_ERR_RA_OUT_OF_DATE, NULL,
4241558Srgrimes                  _("revprop '%s' in r%ld is unexpectedly absent "
4251558Srgrimes                    "in repository (maybe someone else deleted it?)"),
4261558Srgrimes                  propname, *set_rev);
4271558Srgrimes        }
4281558Srgrimes      else if (original_propval->data
4291558Srgrimes               && (! svn_string_compare(original_propval, current)))
43048004Sru        {
43148004Sru          return svn_error_createf(
4321558Srgrimes                  SVN_ERR_RA_OUT_OF_DATE, NULL,
4331558Srgrimes                  _("revprop '%s' in r%ld has unexpected value "
4341558Srgrimes                    "in repository (maybe someone else changed it?)"),
4351558Srgrimes                  propname, *set_rev);
4361558Srgrimes        }
4371558Srgrimes      else if ((! original_propval->data) && current)
43848956Sbillf        {
4391558Srgrimes          return svn_error_createf(
4401558Srgrimes                  SVN_ERR_RA_OUT_OF_DATE, NULL,
4411558Srgrimes                  _("revprop '%s' in r%ld is unexpectedly present "
4421558Srgrimes                    "in repository (maybe someone else set it?)"),
4431558Srgrimes                  propname, *set_rev);
4441558Srgrimes        }
4451558Srgrimes    }
44648956Sbillf
4471558Srgrimes  SVN_ERR(svn_ra_change_rev_prop2(ra_session, *set_rev, propname,
4481558Srgrimes                                  NULL, propval, pool));
4491558Srgrimes
4501558Srgrimes  return SVN_NO_ERROR;
4511558Srgrimes}
4521558Srgrimes
45332328Salexsvn_error_t *
4541558Srgrimessvn_client_revprop_set2(const char *propname,
45532328Salex                        const svn_string_t *propval,
45632328Salex                        const svn_string_t *original_propval,
45732328Salex                        const char *URL,
45832328Salex                        const svn_opt_revision_t *revision,
45932328Salex                        svn_revnum_t *set_rev,
46032328Salex                        svn_boolean_t force,
46132328Salex                        svn_client_ctx_t *ctx,
46232329Salex                        apr_pool_t *pool)
46332328Salex{
4641558Srgrimes  svn_ra_session_t *ra_session;
4651558Srgrimes  svn_boolean_t be_atomic;
4661558Srgrimes
4671558Srgrimes  if ((strcmp(propname, SVN_PROP_REVISION_AUTHOR) == 0)
4681558Srgrimes      && propval
4691558Srgrimes      && strchr(propval->data, '\n') != NULL
4701558Srgrimes      && (! force))
4711558Srgrimes    return svn_error_create(SVN_ERR_CLIENT_REVISION_AUTHOR_CONTAINS_NEWLINE,
4721558Srgrimes                            NULL, _("Author name should not contain a newline;"
4731558Srgrimes                                    " value will not be set unless forced"));
4741558Srgrimes
4751558Srgrimes  if (propval && ! svn_prop_name_is_valid(propname))
4761558Srgrimes    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
4771558Srgrimes                             _("Bad property name: '%s'"), propname);
4781558Srgrimes
4791558Srgrimes  /* Open an RA session for the URL. */
4801558Srgrimes  SVN_ERR(svn_client_open_ra_session2(&ra_session, URL, NULL,
4811558Srgrimes                                      ctx, pool, pool));
4821558Srgrimes
4831558Srgrimes  /* Resolve the revision into something real, and return that to the
4841558Srgrimes     caller as well. */
48526737Scharnier  SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL,
48626737Scharnier                                          ra_session, revision, pool));
4871558Srgrimes
4881558Srgrimes  SVN_ERR(svn_ra_has_capability(ra_session, &be_atomic,
4891558Srgrimes                                SVN_RA_CAPABILITY_ATOMIC_REVPROPS, pool));
4901558Srgrimes  if (be_atomic)
4911558Srgrimes    {
4921558Srgrimes      /* Convert ORIGINAL_PROPVAL to an OLD_VALUE_P. */
4931558Srgrimes      const svn_string_t *const *old_value_p;
494197560Sdelphij      const svn_string_t *unset = NULL;
495197560Sdelphij
4961558Srgrimes      if (original_propval == NULL)
4971558Srgrimes        old_value_p = NULL;
4981558Srgrimes      else if (original_propval->data == NULL)
4991558Srgrimes        old_value_p = &unset;
5001558Srgrimes      else
5011558Srgrimes        old_value_p = &original_propval;
5021558Srgrimes
5031558Srgrimes      /* The actual RA call. */
5041558Srgrimes      SVN_ERR(svn_ra_change_rev_prop2(ra_session, *set_rev, propname,
5051558Srgrimes                                      old_value_p, propval, pool));
5061558Srgrimes    }
5071558Srgrimes  else
5081558Srgrimes    {
5091558Srgrimes      /* The actual RA call. */
5101558Srgrimes      SVN_ERR(check_and_set_revprop(set_rev, ra_session, propname,
5111558Srgrimes                                    original_propval, propval, pool));
5121558Srgrimes    }
5131558Srgrimes
5141558Srgrimes  if (ctx->notify_func2)
5151558Srgrimes    {
516197560Sdelphij      svn_wc_notify_t *notify = svn_wc_create_notify_url(URL,
517140797Sdelphij                                             propval == NULL
5181558Srgrimes                                               ? svn_wc_notify_revprop_deleted
51941684Sbde                                               : svn_wc_notify_revprop_set,
52041684Sbde                                             pool);
5211558Srgrimes      notify->prop_name = propname;
5221558Srgrimes      notify->revision = *set_rev;
5231558Srgrimes
524197560Sdelphij      ctx->notify_func2(ctx->notify_baton2, notify, pool);
525201180Sed    }
5261558Srgrimes
52738036Scharnier  return SVN_NO_ERROR;
5281558Srgrimes}
5291558Srgrimes
530197560Sdelphijsvn_error_t *
531140797Sdelphijsvn_client__remote_propget(apr_hash_t *props,
5321558Srgrimes                           apr_array_header_t **inherited_props,
53348078Sru                           const char *propname,
53448078Sru                           const char *target_prefix,
53548078Sru                           const char *target_relative,
536216823Spjd                           svn_node_kind_t kind,
537216823Spjd                           svn_revnum_t revnum,
5381558Srgrimes                           svn_ra_session_t *ra_session,
5391558Srgrimes                           svn_depth_t depth,
540                           apr_pool_t *result_pool,
541                           apr_pool_t *scratch_pool)
542{
543  apr_hash_t *dirents;
544  apr_hash_t *prop_hash = NULL;
545  const svn_string_t *val;
546  const char *target_full_url =
547    svn_path_url_add_component2(target_prefix, target_relative,
548                                scratch_pool);
549
550  if (kind == svn_node_dir)
551    {
552      SVN_ERR(svn_ra_get_dir2(ra_session,
553                              (depth >= svn_depth_files ? &dirents : NULL),
554                              NULL, &prop_hash, target_relative, revnum,
555                              SVN_DIRENT_KIND, scratch_pool));
556    }
557  else if (kind == svn_node_file)
558    {
559      SVN_ERR(svn_ra_get_file(ra_session, target_relative, revnum,
560                              NULL, NULL, &prop_hash, scratch_pool));
561    }
562  else if (kind == svn_node_none)
563    {
564      return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
565                               _("'%s' does not exist in revision %ld"),
566                               target_full_url, revnum);
567    }
568  else
569    {
570      return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
571                               _("Unknown node kind for '%s'"),
572                               target_full_url);
573    }
574
575  if (inherited_props)
576    {
577      const char *repos_root_url;
578      int i;
579      apr_array_header_t *final_iprops =
580        apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *));
581
582      /* We will filter out all but PROPNAME later, making a final copy
583         in RESULT_POOL, so pass SCRATCH_POOL for all pools. */
584      SVN_ERR(svn_ra_get_inherited_props(ra_session, inherited_props,
585                                         target_relative, revnum,
586                                         scratch_pool, scratch_pool));
587      SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
588                                     scratch_pool));
589      SVN_ERR(svn_client__iprop_relpaths_to_urls(*inherited_props,
590                                                 repos_root_url,
591                                                 scratch_pool,
592                                                 scratch_pool));
593
594      /* Make a copy of any inherited PROPNAME properties in RESULT_POOL. */
595      for (i = 0; i < (*inherited_props)->nelts; i++)
596        {
597          svn_prop_inherited_item_t *iprop =
598            APR_ARRAY_IDX((*inherited_props), i, svn_prop_inherited_item_t *);
599          svn_string_t *iprop_val = svn_hash_gets(iprop->prop_hash, propname);
600
601          if (iprop_val)
602            {
603              svn_prop_inherited_item_t *new_iprop =
604                apr_palloc(result_pool, sizeof(*new_iprop));
605              new_iprop->path_or_url =
606                apr_pstrdup(result_pool, iprop->path_or_url);
607              new_iprop->prop_hash = apr_hash_make(result_pool);
608              svn_hash_sets(new_iprop->prop_hash,
609                            apr_pstrdup(result_pool, propname),
610                            svn_string_dup(iprop_val, result_pool));
611              APR_ARRAY_PUSH(final_iprops, svn_prop_inherited_item_t *) =
612                new_iprop;
613            }
614        }
615      *inherited_props = final_iprops;
616    }
617
618  if (prop_hash
619      && (val = svn_hash_gets(prop_hash, propname)))
620    {
621      svn_hash_sets(props,
622                    apr_pstrdup(result_pool, target_full_url),
623                    svn_string_dup(val, result_pool));
624    }
625
626  if (depth >= svn_depth_files
627      && kind == svn_node_dir
628      && apr_hash_count(dirents) > 0)
629    {
630      apr_hash_index_t *hi;
631      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
632
633      for (hi = apr_hash_first(scratch_pool, dirents);
634           hi;
635           hi = apr_hash_next(hi))
636        {
637          const char *this_name = apr_hash_this_key(hi);
638          svn_dirent_t *this_ent = apr_hash_this_val(hi);
639          const char *new_target_relative;
640          svn_depth_t depth_below_here = depth;
641
642          svn_pool_clear(iterpool);
643
644          if (depth == svn_depth_files && this_ent->kind == svn_node_dir)
645            continue;
646
647          if (depth == svn_depth_files || depth == svn_depth_immediates)
648            depth_below_here = svn_depth_empty;
649
650          new_target_relative = svn_relpath_join(target_relative, this_name,
651                                                 iterpool);
652
653          SVN_ERR(svn_client__remote_propget(props, NULL,
654                                             propname,
655                                             target_prefix,
656                                             new_target_relative,
657                                             this_ent->kind,
658                                             revnum,
659                                             ra_session,
660                                             depth_below_here,
661                                             result_pool, iterpool));
662        }
663
664      svn_pool_destroy(iterpool);
665    }
666
667  return SVN_NO_ERROR;
668}
669
670/* Baton for recursive_propget_receiver(). */
671struct recursive_propget_receiver_baton
672{
673  apr_hash_t *props; /* Hash to collect props. */
674  apr_pool_t *pool; /* Pool to allocate additions to PROPS. */
675  svn_wc_context_t *wc_ctx;  /* Working copy context. */
676};
677
678/* An implementation of svn_wc__proplist_receiver_t. */
679static svn_error_t *
680recursive_propget_receiver(void *baton,
681                           const char *local_abspath,
682                           apr_hash_t *props,
683                           apr_pool_t *scratch_pool)
684{
685  struct recursive_propget_receiver_baton *b = baton;
686
687  if (apr_hash_count(props))
688    {
689      apr_hash_index_t *hi = apr_hash_first(scratch_pool, props);
690      svn_hash_sets(b->props, apr_pstrdup(b->pool, local_abspath),
691                    svn_string_dup(apr_hash_this_val(hi), b->pool));
692    }
693
694  return SVN_NO_ERROR;
695}
696
697/* Return the property value for any PROPNAME set on TARGET in *PROPS,
698   with WC paths of char * for keys and property values of
699   svn_string_t * for values.  Assumes that PROPS is non-NULL.  Additions
700   to *PROPS are allocated in RESULT_POOL, temporary allocations happen in
701   SCRATCH_POOL.
702
703   CHANGELISTS is an array of const char * changelist names, used as a
704   restrictive filter on items whose properties are set; that is,
705   don't set properties on any item unless it's a member of one of
706   those changelists.  If CHANGELISTS is empty (or altogether NULL),
707   no changelist filtering occurs.
708
709   Treat DEPTH as in svn_client_propget3().
710*/
711static svn_error_t *
712get_prop_from_wc(apr_hash_t **props,
713                 const char *propname,
714                 const char *target_abspath,
715                 svn_boolean_t pristine,
716                 svn_node_kind_t kind,
717                 svn_depth_t depth,
718                 const apr_array_header_t *changelists,
719                 svn_client_ctx_t *ctx,
720                 apr_pool_t *result_pool,
721                 apr_pool_t *scratch_pool)
722{
723  struct recursive_propget_receiver_baton rb;
724
725  /* Technically, svn_depth_unknown just means use whatever depth(s)
726     we find in the working copy.  But this is a walk over extant
727     working copy paths: if they're there at all, then by definition
728     the local depth reaches them, so let's just use svn_depth_infinity
729     to get there. */
730  if (depth == svn_depth_unknown)
731    depth = svn_depth_infinity;
732
733  if (!pristine && depth == svn_depth_infinity
734      && (!changelists || changelists->nelts == 0))
735    {
736      /* Handle this common svn:mergeinfo case more efficient than the target
737         list handling in the recursive retrieval. */
738      SVN_ERR(svn_wc__prop_retrieve_recursive(
739                            props, ctx->wc_ctx, target_abspath, propname,
740                            result_pool, scratch_pool));
741      return SVN_NO_ERROR;
742    }
743
744  *props = apr_hash_make(result_pool);
745  rb.props = *props;
746  rb.pool = result_pool;
747  rb.wc_ctx = ctx->wc_ctx;
748
749  SVN_ERR(svn_wc__prop_list_recursive(ctx->wc_ctx, target_abspath,
750                                      propname, depth, pristine,
751                                      changelists,
752                                      recursive_propget_receiver, &rb,
753                                      ctx->cancel_func, ctx->cancel_baton,
754                                      scratch_pool));
755
756  return SVN_NO_ERROR;
757}
758
759/* Note: this implementation is very similar to svn_client_proplist. */
760svn_error_t *
761svn_client_propget5(apr_hash_t **props,
762                    apr_array_header_t **inherited_props,
763                    const char *propname,
764                    const char *target,
765                    const svn_opt_revision_t *peg_revision,
766                    const svn_opt_revision_t *revision,
767                    svn_revnum_t *actual_revnum,
768                    svn_depth_t depth,
769                    const apr_array_header_t *changelists,
770                    svn_client_ctx_t *ctx,
771                    apr_pool_t *result_pool,
772                    apr_pool_t *scratch_pool)
773{
774  svn_revnum_t revnum;
775  svn_boolean_t local_explicit_props;
776  svn_boolean_t local_iprops;
777
778  SVN_ERR(error_if_wcprop_name(propname));
779  if (!svn_path_is_url(target))
780    SVN_ERR_ASSERT(svn_dirent_is_absolute(target));
781
782  peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
783                                                        target);
784  revision = svn_cl__rev_default_to_peg(revision, peg_revision);
785
786  local_explicit_props =
787    (! svn_path_is_url(target)
788     && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind)
789     && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind));
790
791  local_iprops =
792    (local_explicit_props
793     && (peg_revision->kind == svn_opt_revision_working
794         || peg_revision->kind == svn_opt_revision_unspecified )
795     && (revision->kind == svn_opt_revision_working
796         || revision->kind == svn_opt_revision_unspecified ));
797
798  if (local_explicit_props)
799    {
800      svn_node_kind_t kind;
801      svn_boolean_t pristine;
802      svn_error_t *err;
803
804      /* If FALSE, we want the working revision. */
805      pristine = (revision->kind == svn_opt_revision_committed
806                  || revision->kind == svn_opt_revision_base);
807
808      SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target,
809                                pristine, FALSE,
810                                scratch_pool));
811
812      if (kind == svn_node_unknown || kind == svn_node_none)
813        {
814          /* svn uses SVN_ERR_UNVERSIONED_RESOURCE as warning only
815             for this function. */
816          return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
817                                   _("'%s' is not under version control"),
818                                   svn_dirent_local_style(target,
819                                                          scratch_pool));
820        }
821
822      err = svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx,
823                                            target, NULL, revision,
824                                            scratch_pool);
825      if (err && err->apr_err == SVN_ERR_CLIENT_BAD_REVISION)
826        {
827          svn_error_clear(err);
828          revnum = SVN_INVALID_REVNUM;
829        }
830      else if (err)
831        return svn_error_trace(err);
832
833      if (inherited_props && local_iprops)
834        {
835          const char *repos_root_url;
836
837          SVN_ERR(svn_wc__get_iprops(inherited_props, ctx->wc_ctx,
838                                     target, propname,
839                                     result_pool, scratch_pool));
840          SVN_ERR(svn_client_get_repos_root(&repos_root_url, NULL,
841                                            target, ctx, scratch_pool,
842                                            scratch_pool));
843          SVN_ERR(svn_client__iprop_relpaths_to_urls(*inherited_props,
844                                                     repos_root_url,
845                                                     result_pool,
846                                                     scratch_pool));
847        }
848
849      SVN_ERR(get_prop_from_wc(props, propname, target,
850                               pristine, kind,
851                               depth, changelists, ctx, result_pool,
852                               scratch_pool));
853    }
854
855  if ((inherited_props && !local_iprops)
856      || !local_explicit_props)
857    {
858      svn_ra_session_t *ra_session;
859      svn_node_kind_t kind;
860      svn_opt_revision_t new_operative_rev;
861      svn_opt_revision_t new_peg_rev;
862
863      /* Peg or operative revisions may be WC specific for
864         TARGET's explicit props, but still require us to
865         contact the repository for the inherited properties. */
866      if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)
867          || SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
868        {
869          const char *repos_relpath;
870          const char *repos_root_url;
871          const char *local_abspath;
872
873          /* Avoid assertion on the next line when somebody accidentally asks for
874             a working copy revision on a URL */
875          if (svn_path_is_url(target))
876            return svn_error_create(SVN_ERR_CLIENT_VERSIONED_PATH_REQUIRED,
877                                    NULL, NULL);
878
879          SVN_ERR_ASSERT(svn_dirent_is_absolute(target));
880          local_abspath = target;
881
882          if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
883            {
884              SVN_ERR(svn_wc__node_get_origin(NULL, NULL,
885                                              &repos_relpath,
886                                              &repos_root_url,
887                                              NULL, NULL, NULL,
888                                              ctx->wc_ctx,
889                                              local_abspath,
890                                              FALSE, /* scan_deleted */
891                                              result_pool,
892                                              scratch_pool));
893              if (repos_relpath)
894                {
895                  target = svn_path_url_add_component2(repos_root_url,
896                                                       repos_relpath,
897                                                       scratch_pool);
898                  if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
899                    {
900                      svn_revnum_t resolved_peg_rev;
901
902                      SVN_ERR(svn_client__get_revision_number(
903                        &resolved_peg_rev, NULL, ctx->wc_ctx,
904                        local_abspath, NULL, peg_revision, scratch_pool));
905                      new_peg_rev.kind = svn_opt_revision_number;
906                      new_peg_rev.value.number = resolved_peg_rev;
907                      peg_revision = &new_peg_rev;
908                    }
909
910                  if (SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
911                    {
912                      svn_revnum_t resolved_operative_rev;
913
914                      SVN_ERR(svn_client__get_revision_number(
915                        &resolved_operative_rev, NULL, ctx->wc_ctx,
916                        local_abspath, NULL, revision, scratch_pool));
917                      new_operative_rev.kind = svn_opt_revision_number;
918                      new_operative_rev.value.number = resolved_operative_rev;
919                      revision = &new_operative_rev;
920                    }
921                }
922              else
923                {
924                  /* TARGET doesn't exist in the repository, so there are
925                     obviously not inherited props to be found there. */
926                  local_iprops = TRUE;
927                  *inherited_props = apr_array_make(
928                    result_pool, 0, sizeof(svn_prop_inherited_item_t *));
929                }
930            }
931        }
932
933      /* Do we still have anything to ask the repository about? */
934      if (!local_explicit_props || !local_iprops)
935        {
936          svn_client__pathrev_t *loc;
937
938          /* Get an RA plugin for this filesystem object. */
939          SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
940                                                    target, NULL,
941                                                    peg_revision,
942                                                    revision, ctx,
943                                                    scratch_pool));
944
945          SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind,
946                                    scratch_pool));
947
948          if (!local_explicit_props)
949            *props = apr_hash_make(result_pool);
950
951          SVN_ERR(svn_client__remote_propget(
952                                 !local_explicit_props ? *props : NULL,
953                                 !local_iprops ? inherited_props : NULL,
954                                 propname, loc->url, "",
955                                 kind, loc->rev, ra_session,
956                                 depth, result_pool, scratch_pool));
957          revnum = loc->rev;
958        }
959    }
960
961  if (actual_revnum)
962    *actual_revnum = revnum;
963  return SVN_NO_ERROR;
964}
965
966svn_error_t *
967svn_client_revprop_get(const char *propname,
968                       svn_string_t **propval,
969                       const char *URL,
970                       const svn_opt_revision_t *revision,
971                       svn_revnum_t *set_rev,
972                       svn_client_ctx_t *ctx,
973                       apr_pool_t *pool)
974{
975  svn_ra_session_t *ra_session;
976  apr_pool_t *subpool = svn_pool_create(pool);
977  svn_error_t *err;
978
979  /* Open an RA session for the URL. Note that we don't have a local
980     directory, nor a place to put temp files. */
981  SVN_ERR(svn_client_open_ra_session2(&ra_session, URL, NULL,
982                                      ctx, subpool, subpool));
983
984  /* Resolve the revision into something real, and return that to the
985     caller as well. */
986  SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL,
987                                          ra_session, revision, subpool));
988
989  /* The actual RA call. */
990  err = svn_ra_rev_prop(ra_session, *set_rev, propname, propval, pool);
991
992  /* Close RA session */
993  svn_pool_destroy(subpool);
994  return svn_error_trace(err);
995}
996
997
998/* Call RECEIVER for the given PATH and its PROP_HASH and/or
999 * INHERITED_PROPERTIES.
1000 *
1001 * If PROP_HASH is null or has zero count or INHERITED_PROPERTIES is null,
1002 * then do nothing.
1003 */
1004static svn_error_t*
1005call_receiver(const char *path,
1006              apr_hash_t *prop_hash,
1007              apr_array_header_t *inherited_properties,
1008              svn_proplist_receiver2_t receiver,
1009              void *receiver_baton,
1010              apr_pool_t *scratch_pool)
1011{
1012  if ((prop_hash && apr_hash_count(prop_hash))
1013      || inherited_properties)
1014    SVN_ERR(receiver(receiver_baton, path, prop_hash, inherited_properties,
1015                     scratch_pool));
1016
1017  return SVN_NO_ERROR;
1018}
1019
1020
1021/* Helper for the remote case of svn_client_proplist.
1022 *
1023 * If GET_EXPLICIT_PROPS is true, then call RECEIVER for paths at or under
1024 * "TARGET_PREFIX/TARGET_RELATIVE@REVNUM" (obtained using RA_SESSION) which
1025 * have regular properties.  If GET_TARGET_INHERITED_PROPS is true, then send
1026 * the target's inherited properties to the callback.
1027 *
1028 * The 'path' and keys for 'prop_hash' and 'inherited_prop' arguments to
1029 * RECEIVER are all URLs.
1030 *
1031 * RESULT_POOL is used to allocated the 'path', 'prop_hash', and
1032 * 'inherited_prop' arguments to RECEIVER.  SCRATCH_POOL is used for all
1033 * other (temporary) allocations.
1034 *
1035 * KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE".
1036 *
1037 * If the target is a directory, only fetch properties for the files
1038 * and directories at depth DEPTH.  DEPTH has not effect on inherited
1039 * properties.
1040 */
1041static svn_error_t *
1042remote_proplist(const char *target_prefix,
1043                const char *target_relative,
1044                svn_node_kind_t kind,
1045                svn_revnum_t revnum,
1046                svn_ra_session_t *ra_session,
1047                svn_boolean_t get_explicit_props,
1048                svn_boolean_t get_target_inherited_props,
1049                svn_depth_t depth,
1050                svn_proplist_receiver2_t receiver,
1051                void *receiver_baton,
1052                svn_cancel_func_t cancel_func,
1053                void *cancel_baton,
1054                apr_pool_t *scratch_pool)
1055{
1056  apr_hash_t *dirents;
1057  apr_hash_t *prop_hash = NULL;
1058  apr_hash_index_t *hi;
1059  const char *target_full_url =
1060    svn_path_url_add_component2(target_prefix, target_relative, scratch_pool);
1061  apr_array_header_t *inherited_props;
1062
1063  /* Note that we pass only the SCRATCH_POOL to svn_ra_get[dir*|file*] because
1064     we'll be filtering out non-regular properties from PROP_HASH before we
1065     return. */
1066  if (kind == svn_node_dir)
1067    {
1068      SVN_ERR(svn_ra_get_dir2(ra_session,
1069                              (depth > svn_depth_empty) ? &dirents : NULL,
1070                              NULL, &prop_hash, target_relative, revnum,
1071                              SVN_DIRENT_KIND, scratch_pool));
1072    }
1073  else if (kind == svn_node_file)
1074    {
1075      SVN_ERR(svn_ra_get_file(ra_session, target_relative, revnum,
1076                              NULL, NULL, &prop_hash, scratch_pool));
1077    }
1078  else
1079    {
1080      return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
1081                               _("Unknown node kind for '%s'"),
1082                               target_full_url);
1083    }
1084
1085  if (get_target_inherited_props)
1086    {
1087      const char *repos_root_url;
1088
1089      SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props,
1090                                         target_relative, revnum,
1091                                         scratch_pool, scratch_pool));
1092      SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
1093                                     scratch_pool));
1094      SVN_ERR(svn_client__iprop_relpaths_to_urls(inherited_props,
1095                                                 repos_root_url,
1096                                                 scratch_pool,
1097                                                 scratch_pool));
1098    }
1099  else
1100    {
1101      inherited_props = NULL;
1102    }
1103
1104  if (!get_explicit_props)
1105    prop_hash = NULL;
1106  else
1107    {
1108      /* Filter out non-regular properties, since the RA layer returns all
1109         kinds.  Copy regular properties keys/vals from the prop_hash
1110         allocated in SCRATCH_POOL to the "final" hash allocated in
1111         RESULT_POOL. */
1112      for (hi = apr_hash_first(scratch_pool, prop_hash);
1113           hi;
1114           hi = apr_hash_next(hi))
1115        {
1116          const char *name = apr_hash_this_key(hi);
1117          apr_ssize_t klen = apr_hash_this_key_len(hi);
1118          svn_prop_kind_t prop_kind;
1119
1120          prop_kind = svn_property_kind2(name);
1121
1122          if (prop_kind != svn_prop_regular_kind)
1123            {
1124              apr_hash_set(prop_hash, name, klen, NULL);
1125            }
1126        }
1127    }
1128
1129  SVN_ERR(call_receiver(target_full_url, prop_hash, inherited_props,
1130                        receiver, receiver_baton, scratch_pool));
1131
1132  if (depth > svn_depth_empty
1133      && get_explicit_props
1134      && (kind == svn_node_dir) && (apr_hash_count(dirents) > 0))
1135    {
1136      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1137
1138      for (hi = apr_hash_first(scratch_pool, dirents);
1139           hi;
1140           hi = apr_hash_next(hi))
1141        {
1142          const char *this_name = apr_hash_this_key(hi);
1143          svn_dirent_t *this_ent = apr_hash_this_val(hi);
1144          const char *new_target_relative;
1145
1146          if (cancel_func)
1147            SVN_ERR(cancel_func(cancel_baton));
1148
1149          svn_pool_clear(iterpool);
1150
1151          new_target_relative = svn_relpath_join(target_relative,
1152                                                 this_name, iterpool);
1153
1154          if (this_ent->kind == svn_node_file
1155              || depth > svn_depth_files)
1156            {
1157              svn_depth_t depth_below_here = depth;
1158
1159              if (depth == svn_depth_immediates)
1160                depth_below_here = svn_depth_empty;
1161
1162              SVN_ERR(remote_proplist(target_prefix,
1163                                      new_target_relative,
1164                                      this_ent->kind,
1165                                      revnum,
1166                                      ra_session,
1167                                      TRUE /* get_explicit_props */,
1168                                      FALSE /* get_target_inherited_props */,
1169                                      depth_below_here,
1170                                      receiver, receiver_baton,
1171                                      cancel_func, cancel_baton,
1172                                      iterpool));
1173            }
1174        }
1175
1176      svn_pool_destroy(iterpool);
1177    }
1178
1179  return SVN_NO_ERROR;
1180}
1181
1182
1183/* Baton for recursive_proplist_receiver(). */
1184struct recursive_proplist_receiver_baton
1185{
1186  svn_wc_context_t *wc_ctx;  /* Working copy context. */
1187  svn_proplist_receiver2_t wrapped_receiver;  /* Proplist receiver to call. */
1188  void *wrapped_receiver_baton;    /* Baton for the proplist receiver. */
1189  apr_array_header_t *iprops;
1190
1191  /* Anchor, anchor_abspath pair for converting to relative paths */
1192  const char *anchor;
1193  const char *anchor_abspath;
1194};
1195
1196/* An implementation of svn_wc__proplist_receiver_t. */
1197static svn_error_t *
1198recursive_proplist_receiver(void *baton,
1199                            const char *local_abspath,
1200                            apr_hash_t *props,
1201                            apr_pool_t *scratch_pool)
1202{
1203  struct recursive_proplist_receiver_baton *b = baton;
1204  const char *path;
1205  apr_array_header_t *iprops = NULL;
1206
1207  if (b->iprops
1208      && ! strcmp(local_abspath, b->anchor_abspath))
1209    {
1210      /* Report iprops with the properties for the anchor */
1211      iprops = b->iprops;
1212      b->iprops = NULL;
1213    }
1214  else if (b->iprops)
1215    {
1216      /* No report for the root?
1217         Report iprops anyway */
1218
1219      SVN_ERR(b->wrapped_receiver(b->wrapped_receiver_baton,
1220                                  b->anchor ? b->anchor : b->anchor_abspath,
1221                                  NULL /* prop_hash */,
1222                                  b->iprops,
1223                                  scratch_pool));
1224      b->iprops = NULL;
1225    }
1226
1227  /* Attempt to convert absolute paths to relative paths for
1228   * presentation purposes, if needed. */
1229  if (b->anchor && b->anchor_abspath)
1230    {
1231      path = svn_dirent_join(b->anchor,
1232                             svn_dirent_skip_ancestor(b->anchor_abspath,
1233                                                      local_abspath),
1234                             scratch_pool);
1235    }
1236  else
1237    path = local_abspath;
1238
1239  return svn_error_trace(b->wrapped_receiver(b->wrapped_receiver_baton,
1240                                             path, props, iprops,
1241                                             scratch_pool));
1242}
1243
1244/* Helper for svn_client_proplist4 when retrieving properties and/or
1245   inherited properties from the repository.  Except as noted below,
1246   all arguments are as per svn_client_proplist4.
1247
1248   GET_EXPLICIT_PROPS controls if explicit props are retrieved. */
1249static svn_error_t *
1250get_remote_props(const char *path_or_url,
1251                 const svn_opt_revision_t *peg_revision,
1252                 const svn_opt_revision_t *revision,
1253                 svn_depth_t depth,
1254                 svn_boolean_t get_explicit_props,
1255                 svn_boolean_t get_target_inherited_props,
1256                 svn_proplist_receiver2_t receiver,
1257                 void *receiver_baton,
1258                 svn_client_ctx_t *ctx,
1259                 apr_pool_t *scratch_pool)
1260{
1261  svn_ra_session_t *ra_session;
1262  svn_node_kind_t kind;
1263  svn_opt_revision_t new_operative_rev;
1264  svn_opt_revision_t new_peg_rev;
1265  svn_client__pathrev_t *loc;
1266
1267  /* Peg or operative revisions may be WC specific for
1268     PATH_OR_URL's explicit props, but still require us to
1269     contact the repository for the inherited properties. */
1270  if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)
1271      || SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
1272    {
1273      const char *repos_relpath;
1274      const char *repos_root_url;
1275      const char *local_abspath;
1276      svn_boolean_t is_copy;
1277
1278      /* Avoid assertion on the next line when somebody accidentally asks for
1279         a working copy revision on a URL */
1280      if (svn_path_is_url(path_or_url))
1281        return svn_error_create(SVN_ERR_CLIENT_VERSIONED_PATH_REQUIRED,
1282                                NULL, NULL);
1283
1284      SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
1285                                      scratch_pool));
1286
1287      if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
1288        {
1289          SVN_ERR(svn_wc__node_get_origin(&is_copy,
1290                                          NULL,
1291                                          &repos_relpath,
1292                                          &repos_root_url,
1293                                          NULL, NULL, NULL,
1294                                          ctx->wc_ctx,
1295                                          local_abspath,
1296                                          FALSE, /* scan_deleted */
1297                                          scratch_pool,
1298                                          scratch_pool));
1299          if (repos_relpath)
1300            {
1301              path_or_url =
1302                svn_path_url_add_component2(repos_root_url,
1303                                            repos_relpath,
1304                                            scratch_pool);
1305              if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
1306                {
1307                  svn_revnum_t resolved_peg_rev;
1308
1309                  SVN_ERR(svn_client__get_revision_number(&resolved_peg_rev,
1310                                                          NULL, ctx->wc_ctx,
1311                                                          local_abspath, NULL,
1312                                                          peg_revision,
1313                                                          scratch_pool));
1314                  new_peg_rev.kind = svn_opt_revision_number;
1315                  new_peg_rev.value.number = resolved_peg_rev;
1316                  peg_revision = &new_peg_rev;
1317                }
1318
1319              if (SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
1320                {
1321                  svn_revnum_t resolved_operative_rev;
1322
1323                  SVN_ERR(svn_client__get_revision_number(
1324                    &resolved_operative_rev,
1325                    NULL, ctx->wc_ctx,
1326                    local_abspath, NULL,
1327                    revision,
1328                    scratch_pool));
1329                  new_operative_rev.kind = svn_opt_revision_number;
1330                  new_operative_rev.value.number = resolved_operative_rev;
1331                  revision = &new_operative_rev;
1332                }
1333            }
1334          else
1335            {
1336                  /* PATH_OR_URL doesn't exist in the repository, so there are
1337                     obviously not inherited props to be found there. If we
1338                     aren't looking for explicit props then we're done. */
1339                  if (!get_explicit_props)
1340                    return SVN_NO_ERROR;
1341            }
1342        }
1343    }
1344
1345  /* Get an RA session for this URL. */
1346  SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
1347                                            path_or_url, NULL,
1348                                            peg_revision,
1349                                            revision, ctx,
1350                                            scratch_pool));
1351
1352  SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind,
1353                            scratch_pool));
1354
1355  SVN_ERR(remote_proplist(loc->url, "", kind, loc->rev, ra_session,
1356                          get_explicit_props,
1357                          get_target_inherited_props,
1358                          depth, receiver, receiver_baton,
1359                          ctx->cancel_func, ctx->cancel_baton,
1360                          scratch_pool));
1361  return SVN_NO_ERROR;
1362}
1363
1364/* Helper for svn_client_proplist4 when retrieving properties and
1365   possibly inherited properties from the WC.  All arguments are as
1366   per svn_client_proplist4. */
1367static svn_error_t *
1368get_local_props(const char *path_or_url,
1369                const svn_opt_revision_t *revision,
1370                svn_depth_t depth,
1371                const apr_array_header_t *changelists,
1372                svn_boolean_t get_target_inherited_props,
1373                svn_proplist_receiver2_t receiver,
1374                void *receiver_baton,
1375                svn_client_ctx_t *ctx,
1376                apr_pool_t *scratch_pool)
1377{
1378  svn_boolean_t pristine;
1379  svn_node_kind_t kind;
1380  apr_hash_t *changelist_hash = NULL;
1381  const char *local_abspath;
1382  apr_array_header_t *iprops = NULL;
1383
1384  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
1385                                  scratch_pool));
1386
1387  pristine = ((revision->kind == svn_opt_revision_committed)
1388              || (revision->kind == svn_opt_revision_base));
1389
1390  SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath,
1391                            pristine, FALSE, scratch_pool));
1392
1393  if (kind == svn_node_unknown || kind == svn_node_none)
1394    {
1395      /* svn uses SVN_ERR_UNVERSIONED_RESOURCE as warning only
1396         for this function. */
1397      return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
1398                               _("'%s' is not under version control"),
1399                               svn_dirent_local_style(local_abspath,
1400                                                      scratch_pool));
1401    }
1402
1403  if (get_target_inherited_props)
1404    {
1405      const char *repos_root_url;
1406
1407      SVN_ERR(svn_wc__get_iprops(&iprops, ctx->wc_ctx, local_abspath,
1408                                 NULL, scratch_pool, scratch_pool));
1409      SVN_ERR(svn_client_get_repos_root(&repos_root_url, NULL, local_abspath,
1410                                        ctx, scratch_pool, scratch_pool));
1411      SVN_ERR(svn_client__iprop_relpaths_to_urls(iprops, repos_root_url,
1412                                                 scratch_pool,
1413                                                 scratch_pool));
1414    }
1415
1416  if (changelists && changelists->nelts)
1417    SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
1418                                       changelists, scratch_pool));
1419
1420  /* Fetch, recursively or not. */
1421  if (kind == svn_node_dir)
1422    {
1423      struct recursive_proplist_receiver_baton rb;
1424
1425      rb.wc_ctx = ctx->wc_ctx;
1426      rb.wrapped_receiver = receiver;
1427      rb.wrapped_receiver_baton = receiver_baton;
1428      rb.iprops = iprops;
1429      rb.anchor_abspath = local_abspath;
1430
1431      if (strcmp(path_or_url, local_abspath) != 0)
1432        {
1433          rb.anchor = path_or_url;
1434        }
1435      else
1436        {
1437          rb.anchor = NULL;
1438        }
1439
1440      SVN_ERR(svn_wc__prop_list_recursive(ctx->wc_ctx, local_abspath, NULL,
1441                                          depth, pristine, changelists,
1442                                          recursive_proplist_receiver, &rb,
1443                                          ctx->cancel_func, ctx->cancel_baton,
1444                                          scratch_pool));
1445
1446      if (rb.iprops)
1447        {
1448          /* We didn't report for the root. Report iprops anyway */
1449          SVN_ERR(call_receiver(path_or_url, NULL /* props */, rb.iprops,
1450                                receiver, receiver_baton, scratch_pool));
1451        }
1452    }
1453  else if (svn_wc__changelist_match(ctx->wc_ctx, local_abspath,
1454                                    changelist_hash, scratch_pool))
1455    {
1456      apr_hash_t *props;
1457
1458        if (pristine)
1459          SVN_ERR(svn_wc_get_pristine_props(&props,
1460                                            ctx->wc_ctx, local_abspath,
1461                                            scratch_pool, scratch_pool));
1462        else
1463          {
1464            svn_error_t *err;
1465
1466            err = svn_wc_prop_list2(&props, ctx->wc_ctx, local_abspath,
1467                                    scratch_pool, scratch_pool);
1468
1469
1470            if (err)
1471              {
1472                if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1473                  return svn_error_trace(err);
1474                /* As svn_wc_prop_list2() doesn't return NULL for locally-deleted
1475                   let's do that here.  */
1476                svn_error_clear(err);
1477                props = apr_hash_make(scratch_pool);
1478              }
1479          }
1480
1481      SVN_ERR(call_receiver(path_or_url, props, iprops,
1482                            receiver, receiver_baton, scratch_pool));
1483
1484    }
1485  return SVN_NO_ERROR;
1486}
1487
1488svn_error_t *
1489svn_client_proplist4(const char *path_or_url,
1490                     const svn_opt_revision_t *peg_revision,
1491                     const svn_opt_revision_t *revision,
1492                     svn_depth_t depth,
1493                     const apr_array_header_t *changelists,
1494                     svn_boolean_t get_target_inherited_props,
1495                     svn_proplist_receiver2_t receiver,
1496                     void *receiver_baton,
1497                     svn_client_ctx_t *ctx,
1498                     apr_pool_t *scratch_pool)
1499{
1500  svn_boolean_t local_explicit_props;
1501  svn_boolean_t local_iprops;
1502
1503  peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
1504                                                        path_or_url);
1505  revision = svn_cl__rev_default_to_peg(revision, peg_revision);
1506
1507  if (depth == svn_depth_unknown)
1508    depth = svn_depth_empty;
1509
1510  /* Are explicit props available locally? */
1511  local_explicit_props =
1512    (! svn_path_is_url(path_or_url)
1513     && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind)
1514     && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind));
1515
1516  /* If we want iprops are they available locally? */
1517  local_iprops =
1518    (get_target_inherited_props /* We want iprops */
1519     && local_explicit_props /* No local explicit props means no local iprops. */
1520     && (peg_revision->kind == svn_opt_revision_working
1521         || peg_revision->kind == svn_opt_revision_unspecified )
1522     && (revision->kind == svn_opt_revision_working
1523         || revision->kind == svn_opt_revision_unspecified ));
1524
1525  if ((get_target_inherited_props && !local_iprops)
1526      || !local_explicit_props)
1527    {
1528      SVN_ERR(get_remote_props(path_or_url, peg_revision, revision, depth,
1529                               !local_explicit_props,
1530                               (get_target_inherited_props && !local_iprops),
1531                               receiver, receiver_baton, ctx, scratch_pool));
1532    }
1533
1534  if (local_explicit_props)
1535    {
1536      SVN_ERR(get_local_props(path_or_url, revision, depth, changelists,
1537                              local_iprops, receiver, receiver_baton, ctx,
1538                              scratch_pool));
1539    }
1540
1541  return SVN_NO_ERROR;
1542}
1543
1544svn_error_t *
1545svn_client_revprop_list(apr_hash_t **props,
1546                        const char *URL,
1547                        const svn_opt_revision_t *revision,
1548                        svn_revnum_t *set_rev,
1549                        svn_client_ctx_t *ctx,
1550                        apr_pool_t *pool)
1551{
1552  svn_ra_session_t *ra_session;
1553  apr_hash_t *proplist;
1554  apr_pool_t *subpool = svn_pool_create(pool);
1555  svn_error_t *err;
1556
1557  /* Open an RA session for the URL. Note that we don't have a local
1558     directory, nor a place to put temp files. */
1559  SVN_ERR(svn_client_open_ra_session2(&ra_session, URL, NULL,
1560                                      ctx, subpool, subpool));
1561
1562  /* Resolve the revision into something real, and return that to the
1563     caller as well. */
1564  SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL,
1565                                          ra_session, revision, subpool));
1566
1567  /* The actual RA call. */
1568  err = svn_ra_rev_proplist(ra_session, *set_rev, &proplist, pool);
1569
1570  *props = proplist;
1571  svn_pool_destroy(subpool); /* Close RA session */
1572  return svn_error_trace(err);
1573}
1574