prop_commands.c revision 266731
1/*
2 * prop_commands.c:  Implementation of propset, propget, and proplist.
3 *
4 * ====================================================================
5 *    Licensed to the Apache Software Foundation (ASF) under one
6 *    or more contributor license agreements.  See the NOTICE file
7 *    distributed with this work for additional information
8 *    regarding copyright ownership.  The ASF licenses this file
9 *    to you under the Apache License, Version 2.0 (the
10 *    "License"); you may not use this file except in compliance
11 *    with the License.  You may obtain a copy of the License at
12 *
13 *      http://www.apache.org/licenses/LICENSE-2.0
14 *
15 *    Unless required by applicable law or agreed to in writing,
16 *    software distributed under the License is distributed on an
17 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 *    KIND, either express or implied.  See the License for the
19 *    specific language governing permissions and limitations
20 *    under the License.
21 * ====================================================================
22 */
23
24/* ==================================================================== */
25
26
27
28/*** Includes. ***/
29
30#define APR_WANT_STRFUNC
31#include <apr_want.h>
32
33#include "svn_error.h"
34#include "svn_client.h"
35#include "client.h"
36#include "svn_dirent_uri.h"
37#include "svn_path.h"
38#include "svn_pools.h"
39#include "svn_props.h"
40#include "svn_hash.h"
41#include "svn_sorts.h"
42
43#include "svn_private_config.h"
44#include "private/svn_wc_private.h"
45#include "private/svn_ra_private.h"
46#include "private/svn_client_private.h"
47
48
49/*** Code. ***/
50
51/* Return an SVN_ERR_CLIENT_PROPERTY_NAME error if NAME is a wcprop,
52   else return SVN_NO_ERROR. */
53static svn_error_t *
54error_if_wcprop_name(const char *name)
55{
56  if (svn_property_kind2(name) == svn_prop_wc_kind)
57    {
58      return svn_error_createf
59        (SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
60         _("'%s' is a wcprop, thus not accessible to clients"),
61         name);
62    }
63
64  return SVN_NO_ERROR;
65}
66
67
68struct getter_baton
69{
70  svn_ra_session_t *ra_session;
71  svn_revnum_t base_revision_for_url;
72};
73
74
75static svn_error_t *
76get_file_for_validation(const svn_string_t **mime_type,
77                        svn_stream_t *stream,
78                        void *baton,
79                        apr_pool_t *pool)
80{
81  struct getter_baton *gb = baton;
82  svn_ra_session_t *ra_session = gb->ra_session;
83  apr_hash_t *props;
84
85  SVN_ERR(svn_ra_get_file(ra_session, "", gb->base_revision_for_url,
86                          stream, NULL,
87                          (mime_type ? &props : NULL),
88                          pool));
89
90  if (mime_type)
91    *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE);
92
93  return SVN_NO_ERROR;
94}
95
96
97static
98svn_error_t *
99do_url_propset(const char *url,
100               const char *propname,
101               const svn_string_t *propval,
102               const svn_node_kind_t kind,
103               const svn_revnum_t base_revision_for_url,
104               const svn_delta_editor_t *editor,
105               void *edit_baton,
106               apr_pool_t *pool)
107{
108  void *root_baton;
109
110  SVN_ERR(editor->open_root(edit_baton, base_revision_for_url, pool,
111                            &root_baton));
112
113  if (kind == svn_node_file)
114    {
115      void *file_baton;
116      const char *uri_basename = svn_uri_basename(url, pool);
117
118      SVN_ERR(editor->open_file(uri_basename, root_baton,
119                                base_revision_for_url, pool, &file_baton));
120      SVN_ERR(editor->change_file_prop(file_baton, propname, propval, pool));
121      SVN_ERR(editor->close_file(file_baton, NULL, pool));
122    }
123  else
124    {
125      SVN_ERR(editor->change_dir_prop(root_baton, propname, propval, pool));
126    }
127
128  return editor->close_directory(root_baton, pool);
129}
130
131static svn_error_t *
132propset_on_url(const char *propname,
133               const svn_string_t *propval,
134               const char *target,
135               svn_boolean_t skip_checks,
136               svn_revnum_t base_revision_for_url,
137               const apr_hash_t *revprop_table,
138               svn_commit_callback2_t commit_callback,
139               void *commit_baton,
140               svn_client_ctx_t *ctx,
141               apr_pool_t *pool)
142{
143  enum svn_prop_kind prop_kind = svn_property_kind2(propname);
144  svn_ra_session_t *ra_session;
145  svn_node_kind_t node_kind;
146  const char *message;
147  const svn_delta_editor_t *editor;
148  void *edit_baton;
149  apr_hash_t *commit_revprops;
150  svn_error_t *err;
151
152  if (prop_kind != svn_prop_regular_kind)
153    return svn_error_createf
154      (SVN_ERR_BAD_PROP_KIND, NULL,
155       _("Property '%s' is not a regular property"), propname);
156
157  /* Open an RA session for the URL. Note that we don't have a local
158     directory, nor a place to put temp files. */
159  SVN_ERR(svn_client_open_ra_session2(&ra_session, target, NULL,
160                                      ctx, pool, pool));
161
162  SVN_ERR(svn_ra_check_path(ra_session, "", base_revision_for_url,
163                            &node_kind, pool));
164  if (node_kind == svn_node_none)
165    return svn_error_createf
166      (SVN_ERR_FS_NOT_FOUND, NULL,
167       _("Path '%s' does not exist in revision %ld"),
168       target, base_revision_for_url);
169
170  if (node_kind == svn_node_file)
171    {
172      /* We need to reparent our session one directory up, since editor
173         semantics require the root is a directory.
174
175         ### How does this interact with authz? */
176      const char *parent_url;
177      parent_url = svn_uri_dirname(target, pool);
178
179      SVN_ERR(svn_ra_reparent(ra_session, parent_url, pool));
180    }
181
182  /* Setting an inappropriate property is not allowed (unless
183     overridden by 'skip_checks', in some circumstances).  Deleting an
184     inappropriate property is allowed, however, since older clients
185     allowed (and other clients possibly still allow) setting it in
186     the first place. */
187  if (propval && svn_prop_is_svn_prop(propname))
188    {
189      const svn_string_t *new_value;
190      struct getter_baton gb;
191
192      gb.ra_session = ra_session;
193      gb.base_revision_for_url = base_revision_for_url;
194      SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, propname, propval,
195                                           target, node_kind, skip_checks,
196                                           get_file_for_validation, &gb, pool));
197      propval = new_value;
198    }
199
200  /* Create a new commit item and add it to the array. */
201  if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx))
202    {
203      svn_client_commit_item3_t *item;
204      const char *tmp_file;
205      apr_array_header_t *commit_items = apr_array_make(pool, 1, sizeof(item));
206
207      item = svn_client_commit_item3_create(pool);
208      item->url = target;
209      item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS;
210      APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item;
211      SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items,
212                                      ctx, pool));
213      if (! message)
214        return SVN_NO_ERROR;
215    }
216  else
217    message = "";
218
219  SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table,
220                                           message, ctx, pool));
221
222  /* Fetch RA commit editor. */
223  SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session,
224                        svn_client__get_shim_callbacks(ctx->wc_ctx,
225                                                       NULL, pool)));
226  SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton,
227                                    commit_revprops,
228                                    commit_callback,
229                                    commit_baton,
230                                    NULL, TRUE, /* No lock tokens */
231                                    pool));
232
233  err = do_url_propset(target, propname, propval, node_kind,
234                       base_revision_for_url, editor, edit_baton, pool);
235
236  if (err)
237    {
238      /* At least try to abort the edit (and fs txn) before throwing err. */
239      svn_error_clear(editor->abort_edit(edit_baton, pool));
240      return svn_error_trace(err);
241    }
242
243  /* Close the edit. */
244  return editor->close_edit(edit_baton, pool);
245}
246
247/* Check that PROPNAME is a valid name for a versioned property.  Return an
248 * error if it is not valid, specifically if it is:
249 *   - the name of a standard Subversion rev-prop; or
250 *   - in the namespace of WC-props; or
251 *   - not a well-formed property name (except if PROPVAL is NULL: in other
252 *     words we do allow deleting a prop with an ill-formed name).
253 *
254 * Since Subversion controls the "svn:" property namespace, we don't honor
255 * a 'skip_checks' flag here.  Checks for unusual property combinations such
256 * as svn:eol-style with a non-text svn:mime-type might understandably be
257 * skipped, but things such as using a property name reserved for revprops
258 * on a local target are never allowed.
259 */
260static svn_error_t *
261check_prop_name(const char *propname,
262                const svn_string_t *propval)
263{
264  if (svn_prop_is_known_svn_rev_prop(propname))
265    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
266                             _("Revision property '%s' not allowed "
267                               "in this context"), propname);
268
269  SVN_ERR(error_if_wcprop_name(propname));
270
271  if (propval && ! svn_prop_name_is_valid(propname))
272    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
273                             _("Bad property name: '%s'"), propname);
274
275  return SVN_NO_ERROR;
276}
277
278svn_error_t *
279svn_client_propset_local(const char *propname,
280                         const svn_string_t *propval,
281                         const apr_array_header_t *targets,
282                         svn_depth_t depth,
283                         svn_boolean_t skip_checks,
284                         const apr_array_header_t *changelists,
285                         svn_client_ctx_t *ctx,
286                         apr_pool_t *scratch_pool)
287{
288  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
289  svn_boolean_t targets_are_urls;
290  int i;
291
292  if (targets->nelts == 0)
293    return SVN_NO_ERROR;
294
295  /* Check for homogeneity among our targets. */
296  targets_are_urls = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *));
297  SVN_ERR(svn_client__assert_homogeneous_target_type(targets));
298
299  if (targets_are_urls)
300    return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
301                            _("Targets must be working copy paths"));
302
303  SVN_ERR(check_prop_name(propname, propval));
304
305  for (i = 0; i < targets->nelts; i++)
306    {
307      svn_node_kind_t kind;
308      const char *target_abspath;
309      const char *target = APR_ARRAY_IDX(targets, i, const char *);
310
311      svn_pool_clear(iterpool);
312
313      /* Check for cancellation */
314      if (ctx->cancel_func)
315        SVN_ERR(ctx->cancel_func(ctx->cancel_baton));
316
317      SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, iterpool));
318
319      /* Call prop_set for deleted nodes to have special errors */
320      SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath,
321                                FALSE, FALSE, iterpool));
322
323      if (kind == svn_node_unknown || kind == svn_node_none)
324        {
325          if (ctx->notify_func2)
326            {
327              svn_wc_notify_t *notify = svn_wc_create_notify(
328                                          target_abspath,
329                                          svn_wc_notify_path_nonexistent,
330                                          iterpool);
331
332              ctx->notify_func2(ctx->notify_baton2, notify, iterpool);
333            }
334        }
335
336      SVN_WC__CALL_WITH_WRITE_LOCK(
337        svn_wc_prop_set4(ctx->wc_ctx, target_abspath, propname,
338                         propval, depth, skip_checks, changelists,
339                         ctx->cancel_func, ctx->cancel_baton,
340                         ctx->notify_func2, ctx->notify_baton2, iterpool),
341        ctx->wc_ctx, target_abspath, FALSE /* lock_anchor */, iterpool);
342    }
343  svn_pool_destroy(iterpool);
344
345  return SVN_NO_ERROR;
346}
347
348svn_error_t *
349svn_client_propset_remote(const char *propname,
350                          const svn_string_t *propval,
351                          const char *url,
352                          svn_boolean_t skip_checks,
353                          svn_revnum_t base_revision_for_url,
354                          const apr_hash_t *revprop_table,
355                          svn_commit_callback2_t commit_callback,
356                          void *commit_baton,
357                          svn_client_ctx_t *ctx,
358                          apr_pool_t *scratch_pool)
359{
360  if (!svn_path_is_url(url))
361    return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL,
362                            _("Targets must be URLs"));
363
364  SVN_ERR(check_prop_name(propname, propval));
365
366  /* The rationale for requiring the base_revision_for_url
367     argument is that without it, it's too easy to possibly
368     overwrite someone else's change without noticing.  (See also
369     tools/examples/svnput.c). */
370  if (! SVN_IS_VALID_REVNUM(base_revision_for_url))
371    return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
372                            _("Setting property on non-local targets "
373                              "needs a base revision"));
374
375  /* ### When you set svn:eol-style or svn:keywords on a wc file,
376     ### Subversion sends a textdelta at commit time to properly
377     ### normalize the file in the repository.  If we want to
378     ### support editing these properties on URLs, then we should
379     ### generate the same textdelta; for now, we won't support
380     ### editing these properties on URLs.  (Admittedly, this
381     ### means that all the machinery with get_file_for_validation
382     ### is unused.)
383   */
384  if ((strcmp(propname, SVN_PROP_EOL_STYLE) == 0) ||
385      (strcmp(propname, SVN_PROP_KEYWORDS) == 0))
386    return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
387                             _("Setting property '%s' on non-local "
388                               "targets is not supported"), propname);
389
390  SVN_ERR(propset_on_url(propname, propval, url, skip_checks,
391                         base_revision_for_url, revprop_table,
392                         commit_callback, commit_baton, ctx, scratch_pool));
393
394  return SVN_NO_ERROR;
395}
396
397static svn_error_t *
398check_and_set_revprop(svn_revnum_t *set_rev,
399                      svn_ra_session_t *ra_session,
400                      const char *propname,
401                      const svn_string_t *original_propval,
402                      const svn_string_t *propval,
403                      apr_pool_t *pool)
404{
405  if (original_propval)
406    {
407      /* Ensure old value hasn't changed behind our back. */
408      svn_string_t *current;
409      SVN_ERR(svn_ra_rev_prop(ra_session, *set_rev, propname, &current, pool));
410
411      if (original_propval->data && (! current))
412        {
413          return svn_error_createf(
414                  SVN_ERR_RA_OUT_OF_DATE, NULL,
415                  _("revprop '%s' in r%ld is unexpectedly absent "
416                    "in repository (maybe someone else deleted it?)"),
417                  propname, *set_rev);
418        }
419      else if (original_propval->data
420               && (! svn_string_compare(original_propval, current)))
421        {
422          return svn_error_createf(
423                  SVN_ERR_RA_OUT_OF_DATE, NULL,
424                  _("revprop '%s' in r%ld has unexpected value "
425                    "in repository (maybe someone else changed it?)"),
426                  propname, *set_rev);
427        }
428      else if ((! original_propval->data) && current)
429        {
430          return svn_error_createf(
431                  SVN_ERR_RA_OUT_OF_DATE, NULL,
432                  _("revprop '%s' in r%ld is unexpectedly present "
433                    "in repository (maybe someone else set it?)"),
434                  propname, *set_rev);
435        }
436    }
437
438  SVN_ERR(svn_ra_change_rev_prop2(ra_session, *set_rev, propname,
439                                  NULL, propval, pool));
440
441  return SVN_NO_ERROR;
442}
443
444svn_error_t *
445svn_client_revprop_set2(const char *propname,
446                        const svn_string_t *propval,
447                        const svn_string_t *original_propval,
448                        const char *URL,
449                        const svn_opt_revision_t *revision,
450                        svn_revnum_t *set_rev,
451                        svn_boolean_t force,
452                        svn_client_ctx_t *ctx,
453                        apr_pool_t *pool)
454{
455  svn_ra_session_t *ra_session;
456  svn_boolean_t be_atomic;
457
458  if ((strcmp(propname, SVN_PROP_REVISION_AUTHOR) == 0)
459      && propval
460      && strchr(propval->data, '\n') != NULL
461      && (! force))
462    return svn_error_create(SVN_ERR_CLIENT_REVISION_AUTHOR_CONTAINS_NEWLINE,
463                            NULL, _("Author name should not contain a newline;"
464                                    " value will not be set unless forced"));
465
466  if (propval && ! svn_prop_name_is_valid(propname))
467    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
468                             _("Bad property name: '%s'"), propname);
469
470  /* Open an RA session for the URL. */
471  SVN_ERR(svn_client_open_ra_session2(&ra_session, URL, NULL,
472                                      ctx, pool, pool));
473
474  /* Resolve the revision into something real, and return that to the
475     caller as well. */
476  SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL,
477                                          ra_session, revision, pool));
478
479  SVN_ERR(svn_ra_has_capability(ra_session, &be_atomic,
480                                SVN_RA_CAPABILITY_ATOMIC_REVPROPS, pool));
481  if (be_atomic)
482    {
483      /* Convert ORIGINAL_PROPVAL to an OLD_VALUE_P. */
484      const svn_string_t *const *old_value_p;
485      const svn_string_t *unset = NULL;
486
487      if (original_propval == NULL)
488      	old_value_p = NULL;
489      else if (original_propval->data == NULL)
490      	old_value_p = &unset;
491      else
492      	old_value_p = &original_propval;
493
494      /* The actual RA call. */
495      SVN_ERR(svn_ra_change_rev_prop2(ra_session, *set_rev, propname,
496                                      old_value_p, propval, pool));
497    }
498  else
499    {
500      /* The actual RA call. */
501      SVN_ERR(check_and_set_revprop(set_rev, ra_session, propname,
502                                    original_propval, propval, pool));
503    }
504
505  if (ctx->notify_func2)
506    {
507      svn_wc_notify_t *notify = svn_wc_create_notify_url(URL,
508                                             propval == NULL
509                                               ? svn_wc_notify_revprop_deleted
510                                               : svn_wc_notify_revprop_set,
511                                             pool);
512      notify->prop_name = propname;
513      notify->revision = *set_rev;
514
515      (*ctx->notify_func2)(ctx->notify_baton2, notify, pool);
516    }
517
518  return SVN_NO_ERROR;
519}
520
521/* Helper for the remote case of svn_client_propget.
522 *
523 * If PROPS is not null, then get the value of property PROPNAME in REVNUM,
524   using RA_LIB and SESSION.  Store the value ('svn_string_t *') in PROPS,
525   under the path key "TARGET_PREFIX/TARGET_RELATIVE" ('const char *').
526 *
527 * If INHERITED_PROPS is not null, then set *INHERITED_PROPS to a
528 * depth-first ordered array of svn_prop_inherited_item_t * structures
529 * representing the PROPNAME properties inherited by the target.  If
530 * INHERITABLE_PROPS in not null and no inheritable properties are found,
531 * then set *INHERITED_PROPS to an empty array.
532 *
533 * Recurse according to DEPTH, similarly to svn_client_propget3().
534 *
535 * KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE".
536 * Yes, caller passes this; it makes the recursion more efficient :-).
537 *
538 * Allocate PROPS and *INHERITED_PROPS in RESULT_POOL, but do all temporary
539 * work in SCRATCH_POOL.  The two pools can be the same; recursive
540 * calls may use a different SCRATCH_POOL, however.
541 */
542static svn_error_t *
543remote_propget(apr_hash_t *props,
544               apr_array_header_t **inherited_props,
545               const char *propname,
546               const char *target_prefix,
547               const char *target_relative,
548               svn_node_kind_t kind,
549               svn_revnum_t revnum,
550               svn_ra_session_t *ra_session,
551               svn_depth_t depth,
552               apr_pool_t *result_pool,
553               apr_pool_t *scratch_pool)
554{
555  apr_hash_t *dirents;
556  apr_hash_t *prop_hash = NULL;
557  const svn_string_t *val;
558  const char *target_full_url =
559    svn_path_url_add_component2(target_prefix, target_relative,
560                                scratch_pool);
561
562  if (kind == svn_node_dir)
563    {
564      SVN_ERR(svn_ra_get_dir2(ra_session,
565                              (depth >= svn_depth_files ? &dirents : NULL),
566                              NULL, &prop_hash, target_relative, revnum,
567                              SVN_DIRENT_KIND, scratch_pool));
568    }
569  else if (kind == svn_node_file)
570    {
571      SVN_ERR(svn_ra_get_file(ra_session, target_relative, revnum,
572                              NULL, NULL, &prop_hash, scratch_pool));
573    }
574  else if (kind == svn_node_none)
575    {
576      return svn_error_createf(SVN_ERR_ENTRY_NOT_FOUND, NULL,
577                               _("'%s' does not exist in revision %ld"),
578                               target_full_url, revnum);
579    }
580  else
581    {
582      return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
583                               _("Unknown node kind for '%s'"),
584                               target_full_url);
585    }
586
587  if (inherited_props)
588    {
589      const char *repos_root_url;
590
591      /* We will filter out all but PROPNAME later, making a final copy
592         in RESULT_POOL, so pass SCRATCH_POOL for all pools. */
593      SVN_ERR(svn_ra_get_inherited_props(ra_session, inherited_props,
594                                         target_relative, revnum,
595                                         scratch_pool, scratch_pool));
596      SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
597                                     scratch_pool));
598      SVN_ERR(svn_client__iprop_relpaths_to_urls(*inherited_props,
599                                                 repos_root_url,
600                                                 scratch_pool,
601                                                 scratch_pool));
602    }
603
604  /* Make a copy of any inherited PROPNAME properties in RESULT_POOL. */
605  if (inherited_props)
606    {
607      int i;
608      apr_array_header_t *final_iprops =
609        apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *));
610
611      for (i = 0; i < (*inherited_props)->nelts; i++)
612        {
613          svn_prop_inherited_item_t *iprop =
614            APR_ARRAY_IDX((*inherited_props), i, svn_prop_inherited_item_t *);
615          svn_string_t *iprop_val = svn_hash_gets(iprop->prop_hash, propname);
616
617          if (iprop_val)
618            {
619              svn_prop_inherited_item_t *new_iprop =
620                apr_palloc(result_pool, sizeof(*new_iprop));
621              new_iprop->path_or_url =
622                apr_pstrdup(result_pool, iprop->path_or_url);
623              new_iprop->prop_hash = apr_hash_make(result_pool);
624              svn_hash_sets(new_iprop->prop_hash,
625                            apr_pstrdup(result_pool, propname),
626                            svn_string_dup(iprop_val, result_pool));
627              APR_ARRAY_PUSH(final_iprops, svn_prop_inherited_item_t *) =
628                new_iprop;
629            }
630        }
631      *inherited_props = final_iprops;
632    }
633
634  if (prop_hash
635      && (val = svn_hash_gets(prop_hash, propname)))
636    {
637      svn_hash_sets(props,
638                    apr_pstrdup(result_pool, target_full_url),
639                    svn_string_dup(val, result_pool));
640    }
641
642  if (depth >= svn_depth_files
643      && kind == svn_node_dir
644      && apr_hash_count(dirents) > 0)
645    {
646      apr_hash_index_t *hi;
647      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
648
649      for (hi = apr_hash_first(scratch_pool, dirents);
650           hi;
651           hi = apr_hash_next(hi))
652        {
653          const char *this_name = svn__apr_hash_index_key(hi);
654          svn_dirent_t *this_ent = svn__apr_hash_index_val(hi);
655          const char *new_target_relative;
656          svn_depth_t depth_below_here = depth;
657
658          svn_pool_clear(iterpool);
659
660          if (depth == svn_depth_files && this_ent->kind == svn_node_dir)
661            continue;
662
663          if (depth == svn_depth_files || depth == svn_depth_immediates)
664            depth_below_here = svn_depth_empty;
665
666          new_target_relative = svn_relpath_join(target_relative, this_name,
667                                                 iterpool);
668
669          SVN_ERR(remote_propget(props, NULL,
670                                 propname,
671                                 target_prefix,
672                                 new_target_relative,
673                                 this_ent->kind,
674                                 revnum,
675                                 ra_session,
676                                 depth_below_here,
677                                 result_pool, iterpool));
678        }
679
680      svn_pool_destroy(iterpool);
681    }
682
683  return SVN_NO_ERROR;
684}
685
686/* Baton for recursive_propget_receiver(). */
687struct recursive_propget_receiver_baton
688{
689  apr_hash_t *props; /* Hash to collect props. */
690  apr_pool_t *pool; /* Pool to allocate additions to PROPS. */
691  svn_wc_context_t *wc_ctx;  /* Working copy context. */
692};
693
694/* An implementation of svn_wc__proplist_receiver_t. */
695static svn_error_t *
696recursive_propget_receiver(void *baton,
697                           const char *local_abspath,
698                           apr_hash_t *props,
699                           apr_pool_t *scratch_pool)
700{
701  struct recursive_propget_receiver_baton *b = baton;
702
703  if (apr_hash_count(props))
704    {
705      apr_hash_index_t *hi = apr_hash_first(scratch_pool, props);
706      svn_hash_sets(b->props, apr_pstrdup(b->pool, local_abspath),
707                    svn_string_dup(svn__apr_hash_index_val(hi), b->pool));
708    }
709
710  return SVN_NO_ERROR;
711}
712
713/* Return the property value for any PROPNAME set on TARGET in *PROPS,
714   with WC paths of char * for keys and property values of
715   svn_string_t * for values.  Assumes that PROPS is non-NULL.  Additions
716   to *PROPS are allocated in RESULT_POOL, temporary allocations happen in
717   SCRATCH_POOL.
718
719   CHANGELISTS is an array of const char * changelist names, used as a
720   restrictive filter on items whose properties are set; that is,
721   don't set properties on any item unless it's a member of one of
722   those changelists.  If CHANGELISTS is empty (or altogether NULL),
723   no changelist filtering occurs.
724
725   Treat DEPTH as in svn_client_propget3().
726*/
727static svn_error_t *
728get_prop_from_wc(apr_hash_t **props,
729                 const char *propname,
730                 const char *target_abspath,
731                 svn_boolean_t pristine,
732                 svn_node_kind_t kind,
733                 svn_depth_t depth,
734                 const apr_array_header_t *changelists,
735                 svn_client_ctx_t *ctx,
736                 apr_pool_t *result_pool,
737                 apr_pool_t *scratch_pool)
738{
739  struct recursive_propget_receiver_baton rb;
740
741  /* Technically, svn_depth_unknown just means use whatever depth(s)
742     we find in the working copy.  But this is a walk over extant
743     working copy paths: if they're there at all, then by definition
744     the local depth reaches them, so let's just use svn_depth_infinity
745     to get there. */
746  if (depth == svn_depth_unknown)
747    depth = svn_depth_infinity;
748
749  if (!pristine && depth == svn_depth_infinity
750      && (!changelists || changelists->nelts == 0))
751    {
752      /* Handle this common svn:mergeinfo case more efficient than the target
753         list handling in the recursive retrieval. */
754      SVN_ERR(svn_wc__prop_retrieve_recursive(
755                            props, ctx->wc_ctx, target_abspath, propname,
756                            result_pool, scratch_pool));
757      return SVN_NO_ERROR;
758    }
759
760  *props = apr_hash_make(result_pool);
761  rb.props = *props;
762  rb.pool = result_pool;
763  rb.wc_ctx = ctx->wc_ctx;
764
765  SVN_ERR(svn_wc__prop_list_recursive(ctx->wc_ctx, target_abspath,
766                                      propname, depth, pristine,
767                                      changelists,
768                                      recursive_propget_receiver, &rb,
769                                      ctx->cancel_func, ctx->cancel_baton,
770                                      scratch_pool));
771
772  return SVN_NO_ERROR;
773}
774
775/* Note: this implementation is very similar to svn_client_proplist. */
776svn_error_t *
777svn_client_propget5(apr_hash_t **props,
778                    apr_array_header_t **inherited_props,
779                    const char *propname,
780                    const char *target,
781                    const svn_opt_revision_t *peg_revision,
782                    const svn_opt_revision_t *revision,
783                    svn_revnum_t *actual_revnum,
784                    svn_depth_t depth,
785                    const apr_array_header_t *changelists,
786                    svn_client_ctx_t *ctx,
787                    apr_pool_t *result_pool,
788                    apr_pool_t *scratch_pool)
789{
790  svn_revnum_t revnum;
791  svn_boolean_t local_explicit_props;
792  svn_boolean_t local_iprops;
793
794  SVN_ERR(error_if_wcprop_name(propname));
795  if (!svn_path_is_url(target))
796    SVN_ERR_ASSERT(svn_dirent_is_absolute(target));
797
798  peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
799                                                        target);
800  revision = svn_cl__rev_default_to_peg(revision, peg_revision);
801
802  local_explicit_props =
803    (! svn_path_is_url(target)
804     && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind)
805     && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind));
806
807  local_iprops =
808    (local_explicit_props
809     && (peg_revision->kind == svn_opt_revision_working
810         || peg_revision->kind == svn_opt_revision_unspecified )
811     && (revision->kind == svn_opt_revision_working
812         || revision->kind == svn_opt_revision_unspecified ));
813
814  if (local_explicit_props)
815    {
816      svn_node_kind_t kind;
817      svn_boolean_t pristine;
818      svn_error_t *err;
819
820      /* If FALSE, we want the working revision. */
821      pristine = (revision->kind == svn_opt_revision_committed
822                  || revision->kind == svn_opt_revision_base);
823
824      SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target,
825                                pristine, FALSE,
826                                scratch_pool));
827
828      if (kind == svn_node_unknown || kind == svn_node_none)
829        {
830          /* svn uses SVN_ERR_UNVERSIONED_RESOURCE as warning only
831             for this function. */
832          return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
833                                   _("'%s' is not under version control"),
834                                   svn_dirent_local_style(target,
835                                                          scratch_pool));
836        }
837
838      err = svn_client__get_revision_number(&revnum, NULL, ctx->wc_ctx,
839                                            target, NULL, revision,
840                                            scratch_pool);
841      if (err && err->apr_err == SVN_ERR_CLIENT_BAD_REVISION)
842        {
843          svn_error_clear(err);
844          revnum = SVN_INVALID_REVNUM;
845        }
846      else if (err)
847        return svn_error_trace(err);
848
849      if (inherited_props && local_iprops)
850        {
851          const char *repos_root_url;
852
853          SVN_ERR(svn_wc__get_iprops(inherited_props, ctx->wc_ctx,
854                                     target, propname,
855                                     result_pool, scratch_pool));
856          SVN_ERR(svn_client_get_repos_root(&repos_root_url, NULL,
857                                            target, ctx, scratch_pool,
858                                            scratch_pool));
859          SVN_ERR(svn_client__iprop_relpaths_to_urls(*inherited_props,
860                                                     repos_root_url,
861                                                     result_pool,
862                                                     scratch_pool));
863        }
864
865      SVN_ERR(get_prop_from_wc(props, propname, target,
866                               pristine, kind,
867                               depth, changelists, ctx, result_pool,
868                               scratch_pool));
869    }
870
871  if ((inherited_props && !local_iprops)
872      || !local_explicit_props)
873    {
874      svn_ra_session_t *ra_session;
875      svn_node_kind_t kind;
876      svn_opt_revision_t new_operative_rev;
877      svn_opt_revision_t new_peg_rev;
878
879      /* Peg or operative revisions may be WC specific for
880         TARGET's explicit props, but still require us to
881         contact the repository for the inherited properties. */
882      if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)
883          || SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
884        {
885          svn_revnum_t origin_rev;
886          const char *repos_relpath;
887          const char *repos_root_url;
888          const char *repos_uuid;
889          const char *local_abspath;
890          const char *copy_root_abspath;
891          svn_boolean_t is_copy;
892
893          /* Avoid assertion on the next line when somebody accidentally asks for
894             a working copy revision on a URL */
895          if (svn_path_is_url(target))
896            return svn_error_create(SVN_ERR_CLIENT_VERSIONED_PATH_REQUIRED,
897                                    NULL, NULL);
898
899          SVN_ERR_ASSERT(svn_dirent_is_absolute(target));
900          local_abspath = target;
901
902          if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
903            {
904              SVN_ERR(svn_wc__node_get_origin(&is_copy,
905                                              &origin_rev,
906                                              &repos_relpath,
907                                              &repos_root_url,
908                                              &repos_uuid,
909                                              &copy_root_abspath,
910                                              ctx->wc_ctx,
911                                              local_abspath,
912                                              FALSE, /* scan_deleted */
913                                              result_pool,
914                                              scratch_pool));
915              if (repos_relpath)
916                {
917                  target = svn_path_url_add_component2(repos_root_url,
918                                                       repos_relpath,
919                                                       scratch_pool);
920                  if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
921                    {
922                      svn_revnum_t resolved_peg_rev;
923
924                      SVN_ERR(svn_client__get_revision_number(
925                        &resolved_peg_rev, NULL, ctx->wc_ctx,
926                        local_abspath, NULL, peg_revision, scratch_pool));
927                      new_peg_rev.kind = svn_opt_revision_number;
928                      new_peg_rev.value.number = resolved_peg_rev;
929                      peg_revision = &new_peg_rev;
930                    }
931
932                  if (SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
933                    {
934                      svn_revnum_t resolved_operative_rev;
935
936                      SVN_ERR(svn_client__get_revision_number(
937                        &resolved_operative_rev, NULL, ctx->wc_ctx,
938                        local_abspath, NULL, revision, scratch_pool));
939                      new_operative_rev.kind = svn_opt_revision_number;
940                      new_operative_rev.value.number = resolved_operative_rev;
941                      revision = &new_operative_rev;
942                    }
943                }
944              else
945                {
946                  /* TARGET doesn't exist in the repository, so there are
947                     obviously not inherited props to be found there. */
948                  local_iprops = TRUE;
949                  *inherited_props = apr_array_make(
950                    result_pool, 0, sizeof(svn_prop_inherited_item_t *));
951                }
952            }
953        }
954
955      /* Do we still have anything to ask the repository about? */
956      if (!local_explicit_props || !local_iprops)
957        {
958          svn_client__pathrev_t *loc;
959
960          /* Get an RA plugin for this filesystem object. */
961          SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
962                                                    target, NULL,
963                                                    peg_revision,
964                                                    revision, ctx,
965                                                    scratch_pool));
966
967          SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind,
968                                    scratch_pool));
969
970          if (!local_explicit_props)
971            *props = apr_hash_make(result_pool);
972
973          SVN_ERR(remote_propget(!local_explicit_props ? *props : NULL,
974                                 !local_iprops ? inherited_props : NULL,
975                                 propname, loc->url, "",
976                                 kind, loc->rev, ra_session,
977                                 depth, result_pool, scratch_pool));
978          revnum = loc->rev;
979        }
980    }
981
982  if (actual_revnum)
983    *actual_revnum = revnum;
984  return SVN_NO_ERROR;
985}
986
987svn_error_t *
988svn_client_revprop_get(const char *propname,
989                       svn_string_t **propval,
990                       const char *URL,
991                       const svn_opt_revision_t *revision,
992                       svn_revnum_t *set_rev,
993                       svn_client_ctx_t *ctx,
994                       apr_pool_t *pool)
995{
996  svn_ra_session_t *ra_session;
997  apr_pool_t *subpool = svn_pool_create(pool);
998  svn_error_t *err;
999
1000  /* Open an RA session for the URL. Note that we don't have a local
1001     directory, nor a place to put temp files. */
1002  SVN_ERR(svn_client_open_ra_session2(&ra_session, URL, NULL,
1003                                      ctx, subpool, subpool));
1004
1005  /* Resolve the revision into something real, and return that to the
1006     caller as well. */
1007  SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL,
1008                                          ra_session, revision, subpool));
1009
1010  /* The actual RA call. */
1011  err = svn_ra_rev_prop(ra_session, *set_rev, propname, propval, pool);
1012
1013  /* Close RA session */
1014  svn_pool_destroy(subpool);
1015  return svn_error_trace(err);
1016}
1017
1018
1019/* Call RECEIVER for the given PATH and its PROP_HASH and/or
1020 * INHERITED_PROPERTIES.
1021 *
1022 * If PROP_HASH is null or has zero count or INHERITED_PROPERTIES is null,
1023 * then do nothing.
1024 */
1025static svn_error_t*
1026call_receiver(const char *path,
1027              apr_hash_t *prop_hash,
1028              apr_array_header_t *inherited_properties,
1029              svn_proplist_receiver2_t receiver,
1030              void *receiver_baton,
1031              apr_pool_t *scratch_pool)
1032{
1033  if ((prop_hash && apr_hash_count(prop_hash))
1034      || inherited_properties)
1035    SVN_ERR(receiver(receiver_baton, path, prop_hash, inherited_properties,
1036                     scratch_pool));
1037
1038  return SVN_NO_ERROR;
1039}
1040
1041
1042/* Helper for the remote case of svn_client_proplist.
1043 *
1044 * If GET_EXPLICIT_PROPS is true, then call RECEIVER for paths at or under
1045 * "TARGET_PREFIX/TARGET_RELATIVE@REVNUM" (obtained using RA_SESSION) which
1046 * have regular properties.  If GET_TARGET_INHERITED_PROPS is true, then send
1047 * the target's inherited properties to the callback.
1048 *
1049 * The 'path' and keys for 'prop_hash' and 'inherited_prop' arguments to
1050 * RECEIVER are all URLs.
1051 *
1052 * RESULT_POOL is used to allocated the 'path', 'prop_hash', and
1053 * 'inherited_prop' arguments to RECEIVER.  SCRATCH_POOL is used for all
1054 * other (temporary) allocations.
1055 *
1056 * KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE".
1057 *
1058 * If the target is a directory, only fetch properties for the files
1059 * and directories at depth DEPTH.  DEPTH has not effect on inherited
1060 * properties.
1061 */
1062static svn_error_t *
1063remote_proplist(const char *target_prefix,
1064                const char *target_relative,
1065                svn_node_kind_t kind,
1066                svn_revnum_t revnum,
1067                svn_ra_session_t *ra_session,
1068                svn_boolean_t get_explicit_props,
1069                svn_boolean_t get_target_inherited_props,
1070                svn_depth_t depth,
1071                svn_proplist_receiver2_t receiver,
1072                void *receiver_baton,
1073                svn_cancel_func_t cancel_func,
1074                void *cancel_baton,
1075                apr_pool_t *scratch_pool)
1076{
1077  apr_hash_t *dirents;
1078  apr_hash_t *prop_hash = NULL;
1079  apr_hash_index_t *hi;
1080  const char *target_full_url =
1081    svn_path_url_add_component2(target_prefix, target_relative, scratch_pool);
1082  apr_array_header_t *inherited_props;
1083
1084  /* Note that we pass only the SCRATCH_POOL to svn_ra_get[dir*|file*] because
1085     we'll be filtering out non-regular properties from PROP_HASH before we
1086     return. */
1087  if (kind == svn_node_dir)
1088    {
1089      SVN_ERR(svn_ra_get_dir2(ra_session,
1090                              (depth > svn_depth_empty) ? &dirents : NULL,
1091                              NULL, &prop_hash, target_relative, revnum,
1092                              SVN_DIRENT_KIND, scratch_pool));
1093    }
1094  else if (kind == svn_node_file)
1095    {
1096      SVN_ERR(svn_ra_get_file(ra_session, target_relative, revnum,
1097                              NULL, NULL, &prop_hash, scratch_pool));
1098    }
1099  else
1100    {
1101      return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
1102                               _("Unknown node kind for '%s'"),
1103                               target_full_url);
1104    }
1105
1106  if (get_target_inherited_props)
1107    {
1108      const char *repos_root_url;
1109
1110      SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props,
1111                                         target_relative, revnum,
1112                                         scratch_pool, scratch_pool));
1113      SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
1114                                     scratch_pool));
1115      SVN_ERR(svn_client__iprop_relpaths_to_urls(inherited_props,
1116                                                 repos_root_url,
1117                                                 scratch_pool,
1118                                                 scratch_pool));
1119    }
1120  else
1121    {
1122      inherited_props = NULL;
1123    }
1124
1125  if (!get_explicit_props)
1126    prop_hash = NULL;
1127  else
1128    {
1129      /* Filter out non-regular properties, since the RA layer returns all
1130         kinds.  Copy regular properties keys/vals from the prop_hash
1131         allocated in SCRATCH_POOL to the "final" hash allocated in
1132         RESULT_POOL. */
1133      for (hi = apr_hash_first(scratch_pool, prop_hash);
1134           hi;
1135           hi = apr_hash_next(hi))
1136        {
1137          const char *name = svn__apr_hash_index_key(hi);
1138          apr_ssize_t klen = svn__apr_hash_index_klen(hi);
1139          svn_prop_kind_t prop_kind;
1140
1141          prop_kind = svn_property_kind2(name);
1142
1143          if (prop_kind != svn_prop_regular_kind)
1144            {
1145              apr_hash_set(prop_hash, name, klen, NULL);
1146            }
1147        }
1148    }
1149
1150  SVN_ERR(call_receiver(target_full_url, prop_hash, inherited_props,
1151                        receiver, receiver_baton, scratch_pool));
1152
1153  if (depth > svn_depth_empty
1154      && get_explicit_props
1155      && (kind == svn_node_dir) && (apr_hash_count(dirents) > 0))
1156    {
1157      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1158
1159      for (hi = apr_hash_first(scratch_pool, dirents);
1160           hi;
1161           hi = apr_hash_next(hi))
1162        {
1163          const char *this_name = svn__apr_hash_index_key(hi);
1164          svn_dirent_t *this_ent = svn__apr_hash_index_val(hi);
1165          const char *new_target_relative;
1166
1167          if (cancel_func)
1168            SVN_ERR(cancel_func(cancel_baton));
1169
1170          svn_pool_clear(iterpool);
1171
1172          new_target_relative = svn_relpath_join(target_relative,
1173                                                 this_name, iterpool);
1174
1175          if (this_ent->kind == svn_node_file
1176              || depth > svn_depth_files)
1177            {
1178              svn_depth_t depth_below_here = depth;
1179
1180              if (depth == svn_depth_immediates)
1181                depth_below_here = svn_depth_empty;
1182
1183              SVN_ERR(remote_proplist(target_prefix,
1184                                      new_target_relative,
1185                                      this_ent->kind,
1186                                      revnum,
1187                                      ra_session,
1188                                      TRUE /* get_explicit_props */,
1189                                      FALSE /* get_target_inherited_props */,
1190                                      depth_below_here,
1191                                      receiver, receiver_baton,
1192                                      cancel_func, cancel_baton,
1193                                      iterpool));
1194            }
1195        }
1196
1197      svn_pool_destroy(iterpool);
1198    }
1199
1200  return SVN_NO_ERROR;
1201}
1202
1203
1204/* Baton for recursive_proplist_receiver(). */
1205struct recursive_proplist_receiver_baton
1206{
1207  svn_wc_context_t *wc_ctx;  /* Working copy context. */
1208  svn_proplist_receiver2_t wrapped_receiver;  /* Proplist receiver to call. */
1209  void *wrapped_receiver_baton;    /* Baton for the proplist receiver. */
1210  apr_array_header_t *iprops;
1211
1212  /* Anchor, anchor_abspath pair for converting to relative paths */
1213  const char *anchor;
1214  const char *anchor_abspath;
1215};
1216
1217/* An implementation of svn_wc__proplist_receiver_t. */
1218static svn_error_t *
1219recursive_proplist_receiver(void *baton,
1220                            const char *local_abspath,
1221                            apr_hash_t *props,
1222                            apr_pool_t *scratch_pool)
1223{
1224  struct recursive_proplist_receiver_baton *b = baton;
1225  const char *path;
1226  apr_array_header_t *iprops = NULL;
1227
1228  if (b->iprops
1229      && ! strcmp(local_abspath, b->anchor_abspath))
1230    {
1231      /* Report iprops with the properties for the anchor */
1232      iprops = b->iprops;
1233      b->iprops = NULL;
1234    }
1235  else if (b->iprops)
1236    {
1237      /* No report for the root?
1238         Report iprops anyway */
1239
1240      SVN_ERR(b->wrapped_receiver(b->wrapped_receiver_baton,
1241                                  b->anchor ? b->anchor : b->anchor_abspath,
1242                                  NULL /* prop_hash */,
1243                                  b->iprops,
1244                                  scratch_pool));
1245      b->iprops = NULL;
1246    }
1247
1248  /* Attempt to convert absolute paths to relative paths for
1249   * presentation purposes, if needed. */
1250  if (b->anchor && b->anchor_abspath)
1251    {
1252      path = svn_dirent_join(b->anchor,
1253                             svn_dirent_skip_ancestor(b->anchor_abspath,
1254                                                      local_abspath),
1255                             scratch_pool);
1256    }
1257  else
1258    path = local_abspath;
1259
1260  return svn_error_trace(b->wrapped_receiver(b->wrapped_receiver_baton,
1261                                             path, props, iprops,
1262                                             scratch_pool));
1263}
1264
1265/* Helper for svn_client_proplist4 when retrieving properties and/or
1266   inherited properties from the repository.  Except as noted below,
1267   all arguments are as per svn_client_proplist4.
1268
1269   GET_EXPLICIT_PROPS controls if explicit props are retrieved. */
1270static svn_error_t *
1271get_remote_props(const char *path_or_url,
1272                 const svn_opt_revision_t *peg_revision,
1273                 const svn_opt_revision_t *revision,
1274                 svn_depth_t depth,
1275                 svn_boolean_t get_explicit_props,
1276                 svn_boolean_t get_target_inherited_props,
1277                 svn_proplist_receiver2_t receiver,
1278                 void *receiver_baton,
1279                 svn_client_ctx_t *ctx,
1280                 apr_pool_t *scratch_pool)
1281{
1282  svn_ra_session_t *ra_session;
1283  svn_node_kind_t kind;
1284  svn_opt_revision_t new_operative_rev;
1285  svn_opt_revision_t new_peg_rev;
1286  svn_client__pathrev_t *loc;
1287
1288  /* Peg or operative revisions may be WC specific for
1289     PATH_OR_URL's explicit props, but still require us to
1290     contact the repository for the inherited properties. */
1291  if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)
1292      || SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
1293    {
1294      svn_revnum_t origin_rev;
1295      const char *repos_relpath;
1296      const char *repos_root_url;
1297      const char *repos_uuid;
1298      const char *local_abspath;
1299      const char *copy_root_abspath;
1300      svn_boolean_t is_copy;
1301
1302      /* Avoid assertion on the next line when somebody accidentally asks for
1303         a working copy revision on a URL */
1304      if (svn_path_is_url(path_or_url))
1305        return svn_error_create(SVN_ERR_CLIENT_VERSIONED_PATH_REQUIRED,
1306                                NULL, NULL);
1307
1308      SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
1309                                      scratch_pool));
1310
1311      if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
1312        {
1313          SVN_ERR(svn_wc__node_get_origin(&is_copy,
1314                                          &origin_rev,
1315                                          &repos_relpath,
1316                                          &repos_root_url,
1317                                          &repos_uuid,
1318                                          &copy_root_abspath,
1319                                          ctx->wc_ctx,
1320                                          local_abspath,
1321                                          FALSE, /* scan_deleted */
1322                                          scratch_pool,
1323                                          scratch_pool));
1324          if (repos_relpath)
1325            {
1326              path_or_url =
1327                svn_path_url_add_component2(repos_root_url,
1328                                            repos_relpath,
1329                                            scratch_pool);
1330              if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
1331                {
1332                  svn_revnum_t resolved_peg_rev;
1333
1334                  SVN_ERR(svn_client__get_revision_number(&resolved_peg_rev,
1335                                                          NULL, ctx->wc_ctx,
1336                                                          local_abspath, NULL,
1337                                                          peg_revision,
1338                                                          scratch_pool));
1339                  new_peg_rev.kind = svn_opt_revision_number;
1340                  new_peg_rev.value.number = resolved_peg_rev;
1341                  peg_revision = &new_peg_rev;
1342                }
1343
1344              if (SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
1345                {
1346                  svn_revnum_t resolved_operative_rev;
1347
1348                  SVN_ERR(svn_client__get_revision_number(
1349                    &resolved_operative_rev,
1350                    NULL, ctx->wc_ctx,
1351                    local_abspath, NULL,
1352                    revision,
1353                    scratch_pool));
1354                  new_operative_rev.kind = svn_opt_revision_number;
1355                  new_operative_rev.value.number = resolved_operative_rev;
1356                  revision = &new_operative_rev;
1357                }
1358            }
1359          else
1360            {
1361                  /* PATH_OR_URL doesn't exist in the repository, so there are
1362                     obviously not inherited props to be found there. If we
1363                     aren't looking for explicit props then we're done. */
1364                  if (!get_explicit_props)
1365                    return SVN_NO_ERROR;
1366            }
1367        }
1368    }
1369
1370  /* Get an RA session for this URL. */
1371  SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
1372                                            path_or_url, NULL,
1373                                            peg_revision,
1374                                            revision, ctx,
1375                                            scratch_pool));
1376
1377  SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind,
1378                            scratch_pool));
1379
1380  SVN_ERR(remote_proplist(loc->url, "", kind, loc->rev, ra_session,
1381                          get_explicit_props,
1382                          get_target_inherited_props,
1383                          depth, receiver, receiver_baton,
1384                          ctx->cancel_func, ctx->cancel_baton,
1385                          scratch_pool));
1386  return SVN_NO_ERROR;
1387}
1388
1389/* Helper for svn_client_proplist4 when retrieving properties and
1390   possibly inherited properties from the WC.  All arguments are as
1391   per svn_client_proplist4. */
1392static svn_error_t *
1393get_local_props(const char *path_or_url,
1394                const svn_opt_revision_t *revision,
1395                svn_depth_t depth,
1396                const apr_array_header_t *changelists,
1397                svn_boolean_t get_target_inherited_props,
1398                svn_proplist_receiver2_t receiver,
1399                void *receiver_baton,
1400                svn_client_ctx_t *ctx,
1401                apr_pool_t *scratch_pool)
1402{
1403  svn_boolean_t pristine;
1404  svn_node_kind_t kind;
1405  apr_hash_t *changelist_hash = NULL;
1406  const char *local_abspath;
1407  apr_array_header_t *iprops = NULL;
1408
1409  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
1410                                  scratch_pool));
1411
1412  pristine = ((revision->kind == svn_opt_revision_committed)
1413              || (revision->kind == svn_opt_revision_base));
1414
1415  SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath,
1416                            pristine, FALSE, scratch_pool));
1417
1418  if (kind == svn_node_unknown || kind == svn_node_none)
1419    {
1420      /* svn uses SVN_ERR_UNVERSIONED_RESOURCE as warning only
1421         for this function. */
1422      return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
1423                               _("'%s' is not under version control"),
1424                               svn_dirent_local_style(local_abspath,
1425                                                      scratch_pool));
1426    }
1427
1428  if (get_target_inherited_props)
1429    {
1430      const char *repos_root_url;
1431
1432      SVN_ERR(svn_wc__get_iprops(&iprops, ctx->wc_ctx, local_abspath,
1433                                 NULL, scratch_pool, scratch_pool));
1434      SVN_ERR(svn_client_get_repos_root(&repos_root_url, NULL, local_abspath,
1435                                        ctx, scratch_pool, scratch_pool));
1436      SVN_ERR(svn_client__iprop_relpaths_to_urls(iprops, repos_root_url,
1437                                                 scratch_pool,
1438                                                 scratch_pool));
1439    }
1440
1441  if (changelists && changelists->nelts)
1442    SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
1443                                       changelists, scratch_pool));
1444
1445  /* Fetch, recursively or not. */
1446  if (kind == svn_node_dir)
1447    {
1448      struct recursive_proplist_receiver_baton rb;
1449
1450      rb.wc_ctx = ctx->wc_ctx;
1451      rb.wrapped_receiver = receiver;
1452      rb.wrapped_receiver_baton = receiver_baton;
1453      rb.iprops = iprops;
1454      rb.anchor_abspath = local_abspath;
1455
1456      if (strcmp(path_or_url, local_abspath) != 0)
1457        {
1458          rb.anchor = path_or_url;
1459        }
1460      else
1461        {
1462          rb.anchor = NULL;
1463        }
1464
1465      SVN_ERR(svn_wc__prop_list_recursive(ctx->wc_ctx, local_abspath, NULL,
1466                                          depth, pristine, changelists,
1467                                          recursive_proplist_receiver, &rb,
1468                                          ctx->cancel_func, ctx->cancel_baton,
1469                                          scratch_pool));
1470
1471      if (rb.iprops)
1472        {
1473          /* We didn't report for the root. Report iprops anyway */
1474          SVN_ERR(call_receiver(path_or_url, NULL /* props */, rb.iprops,
1475                                receiver, receiver_baton, scratch_pool));
1476        }
1477    }
1478  else if (svn_wc__changelist_match(ctx->wc_ctx, local_abspath,
1479                                    changelist_hash, scratch_pool))
1480    {
1481      apr_hash_t *props;
1482
1483        if (pristine)
1484          SVN_ERR(svn_wc_get_pristine_props(&props,
1485                                            ctx->wc_ctx, local_abspath,
1486                                            scratch_pool, scratch_pool));
1487        else
1488          {
1489            svn_error_t *err;
1490
1491            err = svn_wc_prop_list2(&props, ctx->wc_ctx, local_abspath,
1492                                    scratch_pool, scratch_pool);
1493
1494
1495            if (err)
1496              {
1497                if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1498                  return svn_error_trace(err);
1499                /* As svn_wc_prop_list2() doesn't return NULL for locally-deleted
1500                   let's do that here.  */
1501                svn_error_clear(err);
1502                props = apr_hash_make(scratch_pool);
1503              }
1504          }
1505
1506      SVN_ERR(call_receiver(path_or_url, props, iprops,
1507                            receiver, receiver_baton, scratch_pool));
1508
1509    }
1510  return SVN_NO_ERROR;
1511}
1512
1513svn_error_t *
1514svn_client_proplist4(const char *path_or_url,
1515                     const svn_opt_revision_t *peg_revision,
1516                     const svn_opt_revision_t *revision,
1517                     svn_depth_t depth,
1518                     const apr_array_header_t *changelists,
1519                     svn_boolean_t get_target_inherited_props,
1520                     svn_proplist_receiver2_t receiver,
1521                     void *receiver_baton,
1522                     svn_client_ctx_t *ctx,
1523                     apr_pool_t *scratch_pool)
1524{
1525  svn_boolean_t local_explicit_props;
1526  svn_boolean_t local_iprops;
1527
1528  peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
1529                                                        path_or_url);
1530  revision = svn_cl__rev_default_to_peg(revision, peg_revision);
1531
1532  if (depth == svn_depth_unknown)
1533    depth = svn_depth_empty;
1534
1535  /* Are explicit props available locally? */
1536  local_explicit_props =
1537    (! svn_path_is_url(path_or_url)
1538     && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind)
1539     && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind));
1540
1541  /* If we want iprops are they available locally? */
1542  local_iprops =
1543    (get_target_inherited_props /* We want iprops */
1544     && local_explicit_props /* No local explicit props means no local iprops. */
1545     && (peg_revision->kind == svn_opt_revision_working
1546         || peg_revision->kind == svn_opt_revision_unspecified )
1547     && (revision->kind == svn_opt_revision_working
1548         || revision->kind == svn_opt_revision_unspecified ));
1549
1550  if ((get_target_inherited_props && !local_iprops)
1551      || !local_explicit_props)
1552    {
1553      SVN_ERR(get_remote_props(path_or_url, peg_revision, revision, depth,
1554                               !local_explicit_props,
1555                               (get_target_inherited_props && !local_iprops),
1556                               receiver, receiver_baton, ctx, scratch_pool));
1557    }
1558
1559  if (local_explicit_props)
1560    {
1561      SVN_ERR(get_local_props(path_or_url, revision, depth, changelists,
1562                              local_iprops, receiver, receiver_baton, ctx,
1563                              scratch_pool));
1564    }
1565
1566  return SVN_NO_ERROR;
1567}
1568
1569svn_error_t *
1570svn_client_revprop_list(apr_hash_t **props,
1571                        const char *URL,
1572                        const svn_opt_revision_t *revision,
1573                        svn_revnum_t *set_rev,
1574                        svn_client_ctx_t *ctx,
1575                        apr_pool_t *pool)
1576{
1577  svn_ra_session_t *ra_session;
1578  apr_hash_t *proplist;
1579  apr_pool_t *subpool = svn_pool_create(pool);
1580  svn_error_t *err;
1581
1582  /* Open an RA session for the URL. Note that we don't have a local
1583     directory, nor a place to put temp files. */
1584  SVN_ERR(svn_client_open_ra_session2(&ra_session, URL, NULL,
1585                                      ctx, subpool, subpool));
1586
1587  /* Resolve the revision into something real, and return that to the
1588     caller as well. */
1589  SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL,
1590                                          ra_session, revision, subpool));
1591
1592  /* The actual RA call. */
1593  err = svn_ra_rev_proplist(ra_session, *set_rev, &proplist, pool);
1594
1595  *props = proplist;
1596  svn_pool_destroy(subpool); /* Close RA session */
1597  return svn_error_trace(err);
1598}
1599