prop_commands.c revision 262250
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          SVN_ERR(svn_dirent_get_absolute(&local_abspath, target,
894                                          scratch_pool));
895
896          if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
897            {
898              SVN_ERR(svn_wc__node_get_origin(&is_copy,
899                                              &origin_rev,
900                                              &repos_relpath,
901                                              &repos_root_url,
902                                              &repos_uuid,
903                                              &copy_root_abspath,
904                                              ctx->wc_ctx,
905                                              local_abspath,
906                                              FALSE, /* scan_deleted */
907                                              result_pool,
908                                              scratch_pool));
909              if (repos_relpath)
910                {
911                  target = svn_path_url_add_component2(repos_root_url,
912                                                       repos_relpath,
913                                                       scratch_pool);
914                  if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
915                    {
916                      svn_revnum_t resolved_peg_rev;
917
918                      SVN_ERR(svn_client__get_revision_number(
919                        &resolved_peg_rev, NULL, ctx->wc_ctx,
920                        local_abspath, NULL, peg_revision, scratch_pool));
921                      new_peg_rev.kind = svn_opt_revision_number;
922                      new_peg_rev.value.number = resolved_peg_rev;
923                      peg_revision = &new_peg_rev;
924                    }
925
926                  if (SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
927                    {
928                      svn_revnum_t resolved_operative_rev;
929
930                      SVN_ERR(svn_client__get_revision_number(
931                        &resolved_operative_rev, NULL, ctx->wc_ctx,
932                        local_abspath, NULL, revision, scratch_pool));
933                      new_operative_rev.kind = svn_opt_revision_number;
934                      new_operative_rev.value.number = resolved_operative_rev;
935                      revision = &new_operative_rev;
936                    }
937                }
938              else
939                {
940                  /* TARGET doesn't exist in the repository, so there are
941                     obviously not inherited props to be found there. */
942                  local_iprops = TRUE;
943                  *inherited_props = apr_array_make(
944                    result_pool, 0, sizeof(svn_prop_inherited_item_t *));
945                }
946            }
947        }
948
949      /* Do we still have anything to ask the repository about? */
950      if (!local_explicit_props || !local_iprops)
951        {
952          svn_client__pathrev_t *loc;
953
954          /* Get an RA plugin for this filesystem object. */
955          SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
956                                                    target, NULL,
957                                                    peg_revision,
958                                                    revision, ctx,
959                                                    scratch_pool));
960
961          SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind,
962                                    scratch_pool));
963
964          if (!local_explicit_props)
965            *props = apr_hash_make(result_pool);
966
967          SVN_ERR(remote_propget(!local_explicit_props ? *props : NULL,
968                                 !local_iprops ? inherited_props : NULL,
969                                 propname, loc->url, "",
970                                 kind, loc->rev, ra_session,
971                                 depth, result_pool, scratch_pool));
972          revnum = loc->rev;
973        }
974    }
975
976  if (actual_revnum)
977    *actual_revnum = revnum;
978  return SVN_NO_ERROR;
979}
980
981svn_error_t *
982svn_client_revprop_get(const char *propname,
983                       svn_string_t **propval,
984                       const char *URL,
985                       const svn_opt_revision_t *revision,
986                       svn_revnum_t *set_rev,
987                       svn_client_ctx_t *ctx,
988                       apr_pool_t *pool)
989{
990  svn_ra_session_t *ra_session;
991  apr_pool_t *subpool = svn_pool_create(pool);
992  svn_error_t *err;
993
994  /* Open an RA session for the URL. Note that we don't have a local
995     directory, nor a place to put temp files. */
996  SVN_ERR(svn_client_open_ra_session2(&ra_session, URL, NULL,
997                                      ctx, subpool, subpool));
998
999  /* Resolve the revision into something real, and return that to the
1000     caller as well. */
1001  SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL,
1002                                          ra_session, revision, subpool));
1003
1004  /* The actual RA call. */
1005  err = svn_ra_rev_prop(ra_session, *set_rev, propname, propval, pool);
1006
1007  /* Close RA session */
1008  svn_pool_destroy(subpool);
1009  return svn_error_trace(err);
1010}
1011
1012
1013/* Call RECEIVER for the given PATH and its PROP_HASH and/or
1014 * INHERITED_PROPERTIES.
1015 *
1016 * If PROP_HASH is null or has zero count or INHERITED_PROPERTIES is null,
1017 * then do nothing.
1018 */
1019static svn_error_t*
1020call_receiver(const char *path,
1021              apr_hash_t *prop_hash,
1022              apr_array_header_t *inherited_properties,
1023              svn_proplist_receiver2_t receiver,
1024              void *receiver_baton,
1025              apr_pool_t *scratch_pool)
1026{
1027  if ((prop_hash && apr_hash_count(prop_hash))
1028      || inherited_properties)
1029    SVN_ERR(receiver(receiver_baton, path, prop_hash, inherited_properties,
1030                     scratch_pool));
1031
1032  return SVN_NO_ERROR;
1033}
1034
1035
1036/* Helper for the remote case of svn_client_proplist.
1037 *
1038 * If GET_EXPLICIT_PROPS is true, then call RECEIVER for paths at or under
1039 * "TARGET_PREFIX/TARGET_RELATIVE@REVNUM" (obtained using RA_SESSION) which
1040 * have regular properties.  If GET_TARGET_INHERITED_PROPS is true, then send
1041 * the target's inherited properties to the callback.
1042 *
1043 * The 'path' and keys for 'prop_hash' and 'inherited_prop' arguments to
1044 * RECEIVER are all URLs.
1045 *
1046 * RESULT_POOL is used to allocated the 'path', 'prop_hash', and
1047 * 'inherited_prop' arguments to RECEIVER.  SCRATCH_POOL is used for all
1048 * other (temporary) allocations.
1049 *
1050 * KIND is the kind of the node at "TARGET_PREFIX/TARGET_RELATIVE".
1051 *
1052 * If the target is a directory, only fetch properties for the files
1053 * and directories at depth DEPTH.  DEPTH has not effect on inherited
1054 * properties.
1055 */
1056static svn_error_t *
1057remote_proplist(const char *target_prefix,
1058                const char *target_relative,
1059                svn_node_kind_t kind,
1060                svn_revnum_t revnum,
1061                svn_ra_session_t *ra_session,
1062                svn_boolean_t get_explicit_props,
1063                svn_boolean_t get_target_inherited_props,
1064                svn_depth_t depth,
1065                svn_proplist_receiver2_t receiver,
1066                void *receiver_baton,
1067                svn_cancel_func_t cancel_func,
1068                void *cancel_baton,
1069                apr_pool_t *scratch_pool)
1070{
1071  apr_hash_t *dirents;
1072  apr_hash_t *prop_hash = NULL;
1073  apr_hash_index_t *hi;
1074  const char *target_full_url =
1075    svn_path_url_add_component2(target_prefix, target_relative, scratch_pool);
1076  apr_array_header_t *inherited_props;
1077
1078  /* Note that we pass only the SCRATCH_POOL to svn_ra_get[dir*|file*] because
1079     we'll be filtering out non-regular properties from PROP_HASH before we
1080     return. */
1081  if (kind == svn_node_dir)
1082    {
1083      SVN_ERR(svn_ra_get_dir2(ra_session,
1084                              (depth > svn_depth_empty) ? &dirents : NULL,
1085                              NULL, &prop_hash, target_relative, revnum,
1086                              SVN_DIRENT_KIND, scratch_pool));
1087    }
1088  else if (kind == svn_node_file)
1089    {
1090      SVN_ERR(svn_ra_get_file(ra_session, target_relative, revnum,
1091                              NULL, NULL, &prop_hash, scratch_pool));
1092    }
1093  else
1094    {
1095      return svn_error_createf(SVN_ERR_NODE_UNKNOWN_KIND, NULL,
1096                               _("Unknown node kind for '%s'"),
1097                               target_full_url);
1098    }
1099
1100  if (get_target_inherited_props)
1101    {
1102      const char *repos_root_url;
1103
1104      SVN_ERR(svn_ra_get_inherited_props(ra_session, &inherited_props,
1105                                         target_relative, revnum,
1106                                         scratch_pool, scratch_pool));
1107      SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_root_url,
1108                                     scratch_pool));
1109      SVN_ERR(svn_client__iprop_relpaths_to_urls(inherited_props,
1110                                                 repos_root_url,
1111                                                 scratch_pool,
1112                                                 scratch_pool));
1113    }
1114  else
1115    {
1116      inherited_props = NULL;
1117    }
1118
1119  if (!get_explicit_props)
1120    prop_hash = NULL;
1121  else
1122    {
1123      /* Filter out non-regular properties, since the RA layer returns all
1124         kinds.  Copy regular properties keys/vals from the prop_hash
1125         allocated in SCRATCH_POOL to the "final" hash allocated in
1126         RESULT_POOL. */
1127      for (hi = apr_hash_first(scratch_pool, prop_hash);
1128           hi;
1129           hi = apr_hash_next(hi))
1130        {
1131          const char *name = svn__apr_hash_index_key(hi);
1132          apr_ssize_t klen = svn__apr_hash_index_klen(hi);
1133          svn_prop_kind_t prop_kind;
1134
1135          prop_kind = svn_property_kind2(name);
1136
1137          if (prop_kind != svn_prop_regular_kind)
1138            {
1139              apr_hash_set(prop_hash, name, klen, NULL);
1140            }
1141        }
1142    }
1143
1144  SVN_ERR(call_receiver(target_full_url, prop_hash, inherited_props,
1145                        receiver, receiver_baton, scratch_pool));
1146
1147  if (depth > svn_depth_empty
1148      && get_explicit_props
1149      && (kind == svn_node_dir) && (apr_hash_count(dirents) > 0))
1150    {
1151      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
1152
1153      for (hi = apr_hash_first(scratch_pool, dirents);
1154           hi;
1155           hi = apr_hash_next(hi))
1156        {
1157          const char *this_name = svn__apr_hash_index_key(hi);
1158          svn_dirent_t *this_ent = svn__apr_hash_index_val(hi);
1159          const char *new_target_relative;
1160
1161          if (cancel_func)
1162            SVN_ERR(cancel_func(cancel_baton));
1163
1164          svn_pool_clear(iterpool);
1165
1166          new_target_relative = svn_relpath_join(target_relative,
1167                                                 this_name, iterpool);
1168
1169          if (this_ent->kind == svn_node_file
1170              || depth > svn_depth_files)
1171            {
1172              svn_depth_t depth_below_here = depth;
1173
1174              if (depth == svn_depth_immediates)
1175                depth_below_here = svn_depth_empty;
1176
1177              SVN_ERR(remote_proplist(target_prefix,
1178                                      new_target_relative,
1179                                      this_ent->kind,
1180                                      revnum,
1181                                      ra_session,
1182                                      TRUE /* get_explicit_props */,
1183                                      FALSE /* get_target_inherited_props */,
1184                                      depth_below_here,
1185                                      receiver, receiver_baton,
1186                                      cancel_func, cancel_baton,
1187                                      iterpool));
1188            }
1189        }
1190
1191      svn_pool_destroy(iterpool);
1192    }
1193
1194  return SVN_NO_ERROR;
1195}
1196
1197
1198/* Baton for recursive_proplist_receiver(). */
1199struct recursive_proplist_receiver_baton
1200{
1201  svn_wc_context_t *wc_ctx;  /* Working copy context. */
1202  svn_proplist_receiver2_t wrapped_receiver;  /* Proplist receiver to call. */
1203  void *wrapped_receiver_baton;    /* Baton for the proplist receiver. */
1204  apr_array_header_t *iprops;
1205
1206  /* Anchor, anchor_abspath pair for converting to relative paths */
1207  const char *anchor;
1208  const char *anchor_abspath;
1209};
1210
1211/* An implementation of svn_wc__proplist_receiver_t. */
1212static svn_error_t *
1213recursive_proplist_receiver(void *baton,
1214                            const char *local_abspath,
1215                            apr_hash_t *props,
1216                            apr_pool_t *scratch_pool)
1217{
1218  struct recursive_proplist_receiver_baton *b = baton;
1219  const char *path;
1220  apr_array_header_t *iprops = NULL;
1221
1222  if (b->iprops
1223      && ! strcmp(local_abspath, b->anchor_abspath))
1224    {
1225      /* Report iprops with the properties for the anchor */
1226      iprops = b->iprops;
1227      b->iprops = NULL;
1228    }
1229  else if (b->iprops)
1230    {
1231      /* No report for the root?
1232         Report iprops anyway */
1233
1234      SVN_ERR(b->wrapped_receiver(b->wrapped_receiver_baton,
1235                                  b->anchor ? b->anchor : local_abspath,
1236                                  NULL /* prop_hash */,
1237                                  b->iprops,
1238                                  scratch_pool));
1239      b->iprops = NULL;
1240    }
1241
1242  /* Attempt to convert absolute paths to relative paths for
1243   * presentation purposes, if needed. */
1244  if (b->anchor && b->anchor_abspath)
1245    {
1246      path = svn_dirent_join(b->anchor,
1247                             svn_dirent_skip_ancestor(b->anchor_abspath,
1248                                                      local_abspath),
1249                             scratch_pool);
1250    }
1251  else
1252    path = local_abspath;
1253
1254  return svn_error_trace(b->wrapped_receiver(b->wrapped_receiver_baton,
1255                                             path, props, iprops,
1256                                             scratch_pool));
1257}
1258
1259/* Helper for svn_client_proplist4 when retrieving properties and/or
1260   inherited properties from the repository.  Except as noted below,
1261   all arguments are as per svn_client_proplist4.
1262
1263   GET_EXPLICIT_PROPS controls if explicit props are retrieved. */
1264static svn_error_t *
1265get_remote_props(const char *path_or_url,
1266                 const svn_opt_revision_t *peg_revision,
1267                 const svn_opt_revision_t *revision,
1268                 svn_depth_t depth,
1269                 svn_boolean_t get_explicit_props,
1270                 svn_boolean_t get_target_inherited_props,
1271                 svn_proplist_receiver2_t receiver,
1272                 void *receiver_baton,
1273                 svn_client_ctx_t *ctx,
1274                 apr_pool_t *scratch_pool)
1275{
1276  svn_ra_session_t *ra_session;
1277  svn_node_kind_t kind;
1278  svn_opt_revision_t new_operative_rev;
1279  svn_opt_revision_t new_peg_rev;
1280  svn_client__pathrev_t *loc;
1281
1282  /* Peg or operative revisions may be WC specific for
1283     PATH_OR_URL's explicit props, but still require us to
1284     contact the repository for the inherited properties. */
1285  if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)
1286      || SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
1287    {
1288      svn_revnum_t origin_rev;
1289      const char *repos_relpath;
1290      const char *repos_root_url;
1291      const char *repos_uuid;
1292      const char *local_abspath;
1293      const char *copy_root_abspath;
1294      svn_boolean_t is_copy;
1295
1296      SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
1297                                      scratch_pool));
1298
1299      if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
1300        {
1301          SVN_ERR(svn_wc__node_get_origin(&is_copy,
1302                                          &origin_rev,
1303                                          &repos_relpath,
1304                                          &repos_root_url,
1305                                          &repos_uuid,
1306                                          &copy_root_abspath,
1307                                          ctx->wc_ctx,
1308                                          local_abspath,
1309                                          FALSE, /* scan_deleted */
1310                                          scratch_pool,
1311                                          scratch_pool));
1312          if (repos_relpath)
1313            {
1314              path_or_url =
1315                svn_path_url_add_component2(repos_root_url,
1316                                            repos_relpath,
1317                                            scratch_pool);
1318              if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
1319                {
1320                  svn_revnum_t resolved_peg_rev;
1321
1322                  SVN_ERR(svn_client__get_revision_number(&resolved_peg_rev,
1323                                                          NULL, ctx->wc_ctx,
1324                                                          local_abspath, NULL,
1325                                                          peg_revision,
1326                                                          scratch_pool));
1327                  new_peg_rev.kind = svn_opt_revision_number;
1328                  new_peg_rev.value.number = resolved_peg_rev;
1329                  peg_revision = &new_peg_rev;
1330                }
1331
1332              if (SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
1333                {
1334                  svn_revnum_t resolved_operative_rev;
1335
1336                  SVN_ERR(svn_client__get_revision_number(
1337                    &resolved_operative_rev,
1338                    NULL, ctx->wc_ctx,
1339                    local_abspath, NULL,
1340                    revision,
1341                    scratch_pool));
1342                  new_operative_rev.kind = svn_opt_revision_number;
1343                  new_operative_rev.value.number = resolved_operative_rev;
1344                  revision = &new_operative_rev;
1345                }
1346            }
1347          else
1348            {
1349                  /* PATH_OR_URL doesn't exist in the repository, so there are
1350                     obviously not inherited props to be found there. If we
1351                     aren't looking for explicit props then we're done. */
1352                  if (!get_explicit_props)
1353                    return SVN_NO_ERROR;
1354            }
1355        }
1356    }
1357
1358  /* Get an RA session for this URL. */
1359  SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
1360                                            path_or_url, NULL,
1361                                            peg_revision,
1362                                            revision, ctx,
1363                                            scratch_pool));
1364
1365  SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind,
1366                            scratch_pool));
1367
1368  SVN_ERR(remote_proplist(loc->url, "", kind, loc->rev, ra_session,
1369                          get_explicit_props,
1370                          get_target_inherited_props,
1371                          depth, receiver, receiver_baton,
1372                          ctx->cancel_func, ctx->cancel_baton,
1373                          scratch_pool));
1374  return SVN_NO_ERROR;
1375}
1376
1377/* Helper for svn_client_proplist4 when retrieving properties and
1378   possibly inherited properties from the WC.  All arguments are as
1379   per svn_client_proplist4. */
1380static svn_error_t *
1381get_local_props(const char *path_or_url,
1382                const svn_opt_revision_t *revision,
1383                svn_depth_t depth,
1384                const apr_array_header_t *changelists,
1385                svn_boolean_t get_target_inherited_props,
1386                svn_proplist_receiver2_t receiver,
1387                void *receiver_baton,
1388                svn_client_ctx_t *ctx,
1389                apr_pool_t *scratch_pool)
1390{
1391  svn_boolean_t pristine;
1392  svn_node_kind_t kind;
1393  apr_hash_t *changelist_hash = NULL;
1394  const char *local_abspath;
1395  apr_array_header_t *iprops = NULL;
1396
1397  SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
1398                                  scratch_pool));
1399
1400  pristine = ((revision->kind == svn_opt_revision_committed)
1401              || (revision->kind == svn_opt_revision_base));
1402
1403  SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath,
1404                            pristine, FALSE, scratch_pool));
1405
1406  if (kind == svn_node_unknown || kind == svn_node_none)
1407    {
1408      /* svn uses SVN_ERR_UNVERSIONED_RESOURCE as warning only
1409         for this function. */
1410      return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
1411                               _("'%s' is not under version control"),
1412                               svn_dirent_local_style(local_abspath,
1413                                                      scratch_pool));
1414    }
1415
1416  if (get_target_inherited_props)
1417    {
1418      const char *repos_root_url;
1419
1420      SVN_ERR(svn_wc__get_iprops(&iprops, ctx->wc_ctx, local_abspath,
1421                                 NULL, scratch_pool, scratch_pool));
1422      SVN_ERR(svn_client_get_repos_root(&repos_root_url, NULL, local_abspath,
1423                                        ctx, scratch_pool, scratch_pool));
1424      SVN_ERR(svn_client__iprop_relpaths_to_urls(iprops, repos_root_url,
1425                                                 scratch_pool,
1426                                                 scratch_pool));
1427    }
1428
1429  if (changelists && changelists->nelts)
1430    SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
1431                                       changelists, scratch_pool));
1432
1433  /* Fetch, recursively or not. */
1434  if (kind == svn_node_dir)
1435    {
1436      struct recursive_proplist_receiver_baton rb;
1437
1438      rb.wc_ctx = ctx->wc_ctx;
1439      rb.wrapped_receiver = receiver;
1440      rb.wrapped_receiver_baton = receiver_baton;
1441      rb.iprops = iprops;
1442      rb.anchor_abspath = local_abspath;
1443
1444      if (strcmp(path_or_url, local_abspath) != 0)
1445        {
1446          rb.anchor = path_or_url;
1447        }
1448      else
1449        {
1450          rb.anchor = NULL;
1451        }
1452
1453      SVN_ERR(svn_wc__prop_list_recursive(ctx->wc_ctx, local_abspath, NULL,
1454                                          depth, pristine, changelists,
1455                                          recursive_proplist_receiver, &rb,
1456                                          ctx->cancel_func, ctx->cancel_baton,
1457                                          scratch_pool));
1458
1459      if (rb.iprops)
1460        {
1461          /* We didn't report for the root. Report iprops anyway */
1462          SVN_ERR(call_receiver(path_or_url, NULL /* props */, rb.iprops,
1463                                receiver, receiver_baton, scratch_pool));
1464        }
1465    }
1466  else if (svn_wc__changelist_match(ctx->wc_ctx, local_abspath,
1467                                    changelist_hash, scratch_pool))
1468    {
1469      apr_hash_t *props;
1470
1471        if (pristine)
1472          SVN_ERR(svn_wc_get_pristine_props(&props,
1473                                            ctx->wc_ctx, local_abspath,
1474                                            scratch_pool, scratch_pool));
1475        else
1476          {
1477            svn_error_t *err;
1478
1479            err = svn_wc_prop_list2(&props, ctx->wc_ctx, local_abspath,
1480                                    scratch_pool, scratch_pool);
1481
1482
1483            if (err)
1484              {
1485                if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1486                  return svn_error_trace(err);
1487                /* As svn_wc_prop_list2() doesn't return NULL for locally-deleted
1488                   let's do that here.  */
1489                svn_error_clear(err);
1490                props = apr_hash_make(scratch_pool);
1491              }
1492          }
1493
1494      SVN_ERR(call_receiver(path_or_url, props, iprops,
1495                            receiver, receiver_baton, scratch_pool));
1496
1497    }
1498  return SVN_NO_ERROR;
1499}
1500
1501svn_error_t *
1502svn_client_proplist4(const char *path_or_url,
1503                     const svn_opt_revision_t *peg_revision,
1504                     const svn_opt_revision_t *revision,
1505                     svn_depth_t depth,
1506                     const apr_array_header_t *changelists,
1507                     svn_boolean_t get_target_inherited_props,
1508                     svn_proplist_receiver2_t receiver,
1509                     void *receiver_baton,
1510                     svn_client_ctx_t *ctx,
1511                     apr_pool_t *scratch_pool)
1512{
1513  svn_boolean_t local_explicit_props;
1514  svn_boolean_t local_iprops;
1515
1516  peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
1517                                                        path_or_url);
1518  revision = svn_cl__rev_default_to_peg(revision, peg_revision);
1519
1520  if (depth == svn_depth_unknown)
1521    depth = svn_depth_empty;
1522
1523  /* Are explicit props available locally? */
1524  local_explicit_props =
1525    (! svn_path_is_url(path_or_url)
1526     && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind)
1527     && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind));
1528
1529  /* If we want iprops are they available locally? */
1530  local_iprops =
1531    (get_target_inherited_props /* We want iprops */
1532     && local_explicit_props /* No local explicit props means no local iprops. */
1533     && (peg_revision->kind == svn_opt_revision_working
1534         || peg_revision->kind == svn_opt_revision_unspecified )
1535     && (revision->kind == svn_opt_revision_working
1536         || revision->kind == svn_opt_revision_unspecified ));
1537
1538  if ((get_target_inherited_props && !local_iprops)
1539      || !local_explicit_props)
1540    {
1541      SVN_ERR(get_remote_props(path_or_url, peg_revision, revision, depth,
1542                               !local_explicit_props,
1543                               (get_target_inherited_props && !local_iprops),
1544                               receiver, receiver_baton, ctx, scratch_pool));
1545    }
1546
1547  if (local_explicit_props)
1548    {
1549      SVN_ERR(get_local_props(path_or_url, revision, depth, changelists,
1550                              local_iprops, receiver, receiver_baton, ctx,
1551                              scratch_pool));
1552    }
1553
1554  return SVN_NO_ERROR;
1555}
1556
1557svn_error_t *
1558svn_client_revprop_list(apr_hash_t **props,
1559                        const char *URL,
1560                        const svn_opt_revision_t *revision,
1561                        svn_revnum_t *set_rev,
1562                        svn_client_ctx_t *ctx,
1563                        apr_pool_t *pool)
1564{
1565  svn_ra_session_t *ra_session;
1566  apr_hash_t *proplist;
1567  apr_pool_t *subpool = svn_pool_create(pool);
1568  svn_error_t *err;
1569
1570  /* Open an RA session for the URL. Note that we don't have a local
1571     directory, nor a place to put temp files. */
1572  SVN_ERR(svn_client_open_ra_session2(&ra_session, URL, NULL,
1573                                      ctx, subpool, subpool));
1574
1575  /* Resolve the revision into something real, and return that to the
1576     caller as well. */
1577  SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL,
1578                                          ra_session, revision, subpool));
1579
1580  /* The actual RA call. */
1581  err = svn_ra_rev_proplist(ra_session, *set_rev, &proplist, pool);
1582
1583  *props = proplist;
1584  svn_pool_destroy(subpool); /* Close RA session */
1585  return svn_error_trace(err);
1586}
1587