]> CyberLeo.Net >> Repos - FreeBSD/releng/10.2.git/blob - contrib/subversion/subversion/libsvn_client/prop_commands.c
- Copy stable/10@285827 to releng/10.2 in preparation for 10.2-RC1
[FreeBSD/releng/10.2.git] / contrib / subversion / subversion / libsvn_client / prop_commands.c
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 \f
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 \f
49 /*** Code. ***/
50
51 /* Return an SVN_ERR_CLIENT_PROPERTY_NAME error if NAME is a wcprop,
52    else return SVN_NO_ERROR. */
53 static svn_error_t *
54 error_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
68 struct getter_baton
69 {
70   svn_ra_session_t *ra_session;
71   svn_revnum_t base_revision_for_url;
72 };
73
74
75 static svn_error_t *
76 get_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
97 static
98 svn_error_t *
99 do_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
131 static svn_error_t *
132 propset_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  */
260 static svn_error_t *
261 check_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
278 svn_error_t *
279 svn_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
348 svn_error_t *
349 svn_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
397 static svn_error_t *
398 check_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
444 svn_error_t *
445 svn_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  */
542 static svn_error_t *
543 remote_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(). */
687 struct 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. */
695 static svn_error_t *
696 recursive_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 */
727 static svn_error_t *
728 get_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. */
776 svn_error_t *
777 svn_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
987 svn_error_t *
988 svn_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  */
1025 static svn_error_t*
1026 call_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  */
1062 static svn_error_t *
1063 remote_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(). */
1205 struct 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. */
1218 static svn_error_t *
1219 recursive_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. */
1270 static svn_error_t *
1271 get_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. */
1392 static svn_error_t *
1393 get_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
1513 svn_error_t *
1514 svn_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
1569 svn_error_t *
1570 svn_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 }