]> CyberLeo.Net >> Repos - FreeBSD/releng/10.0.git/blob - contrib/subversion/subversion/libsvn_client/prop_commands.c
- Copy stable/10 (r259064) to releng/10.0 as part of the
[FreeBSD/releng/10.0.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           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
981 svn_error_t *
982 svn_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  */
1019 static svn_error_t*
1020 call_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  */
1056 static svn_error_t *
1057 remote_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(). */
1199 struct 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
1205   /* Anchor, anchor_abspath pair for converting to relative paths */
1206   const char *anchor;
1207   const char *anchor_abspath;
1208 };
1209
1210 /* An implementation of svn_wc__proplist_receiver_t. */
1211 static svn_error_t *
1212 recursive_proplist_receiver(void *baton,
1213                             const char *local_abspath,
1214                             apr_hash_t *props,
1215                             apr_pool_t *scratch_pool)
1216 {
1217   struct recursive_proplist_receiver_baton *b = baton;
1218   const char *path;
1219
1220   /* Attempt to convert absolute paths to relative paths for
1221    * presentation purposes, if needed. */
1222   if (b->anchor && b->anchor_abspath)
1223     {
1224       path = svn_dirent_join(b->anchor,
1225                              svn_dirent_skip_ancestor(b->anchor_abspath,
1226                                                       local_abspath),
1227                              scratch_pool);
1228     }
1229   else
1230     path = local_abspath;
1231
1232   return svn_error_trace(b->wrapped_receiver(b->wrapped_receiver_baton,
1233                                              path, props, NULL,
1234                                              scratch_pool));
1235 }
1236
1237 /* Helper for svn_client_proplist4 when retrieving properties and/or
1238    inherited properties from the repository.  Except as noted below,
1239    all arguments are as per svn_client_proplist4.
1240
1241    GET_EXPLICIT_PROPS controls if explicit props are retrieved. */
1242 static svn_error_t *
1243 get_remote_props(const char *path_or_url,
1244                  const svn_opt_revision_t *peg_revision,
1245                  const svn_opt_revision_t *revision,
1246                  svn_depth_t depth,
1247                  svn_boolean_t get_explicit_props,
1248                  svn_boolean_t get_target_inherited_props,
1249                  svn_proplist_receiver2_t receiver,
1250                  void *receiver_baton,
1251                  svn_client_ctx_t *ctx,
1252                  apr_pool_t *scratch_pool)
1253 {
1254   svn_ra_session_t *ra_session;
1255   svn_node_kind_t kind;
1256   svn_opt_revision_t new_operative_rev;
1257   svn_opt_revision_t new_peg_rev;
1258   svn_client__pathrev_t *loc;
1259
1260   /* Peg or operative revisions may be WC specific for
1261      PATH_OR_URL's explicit props, but still require us to
1262      contact the repository for the inherited properties. */
1263   if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind)
1264       || SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
1265     {
1266       svn_revnum_t origin_rev;
1267       const char *repos_relpath;
1268       const char *repos_root_url;
1269       const char *repos_uuid;
1270       const char *local_abspath;
1271       const char *copy_root_abspath;
1272       svn_boolean_t is_copy;
1273
1274       SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
1275                                       scratch_pool));
1276
1277       if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
1278         {
1279           SVN_ERR(svn_wc__node_get_origin(&is_copy,
1280                                           &origin_rev,
1281                                           &repos_relpath,
1282                                           &repos_root_url,
1283                                           &repos_uuid,
1284                                           &copy_root_abspath,
1285                                           ctx->wc_ctx,
1286                                           local_abspath,
1287                                           FALSE, /* scan_deleted */
1288                                           scratch_pool,
1289                                           scratch_pool));
1290           if (repos_relpath)
1291             {
1292               path_or_url =
1293                 svn_path_url_add_component2(repos_root_url,
1294                                             repos_relpath,
1295                                             scratch_pool);
1296               if (SVN_CLIENT__REVKIND_NEEDS_WC(peg_revision->kind))
1297                 {
1298                   svn_revnum_t resolved_peg_rev;
1299
1300                   SVN_ERR(svn_client__get_revision_number(&resolved_peg_rev,
1301                                                           NULL, ctx->wc_ctx,
1302                                                           local_abspath, NULL,
1303                                                           peg_revision,
1304                                                           scratch_pool));
1305                   new_peg_rev.kind = svn_opt_revision_number;
1306                   new_peg_rev.value.number = resolved_peg_rev;
1307                   peg_revision = &new_peg_rev;
1308                 }
1309
1310               if (SVN_CLIENT__REVKIND_NEEDS_WC(revision->kind))
1311                 {
1312                   svn_revnum_t resolved_operative_rev;
1313
1314                   SVN_ERR(svn_client__get_revision_number(
1315                     &resolved_operative_rev,
1316                     NULL, ctx->wc_ctx,
1317                     local_abspath, NULL,
1318                     revision,
1319                     scratch_pool));
1320                   new_operative_rev.kind = svn_opt_revision_number;
1321                   new_operative_rev.value.number = resolved_operative_rev;
1322                   revision = &new_operative_rev;
1323                 }
1324             }
1325           else
1326             {
1327                   /* PATH_OR_URL doesn't exist in the repository, so there are
1328                      obviously not inherited props to be found there. If we
1329                      aren't looking for explicit props then we're done. */
1330                   if (!get_explicit_props)
1331                     return SVN_NO_ERROR;
1332             }
1333         }
1334     }
1335
1336   /* Get an RA session for this URL. */
1337   SVN_ERR(svn_client__ra_session_from_path2(&ra_session, &loc,
1338                                             path_or_url, NULL,
1339                                             peg_revision,
1340                                             revision, ctx,
1341                                             scratch_pool));
1342
1343   SVN_ERR(svn_ra_check_path(ra_session, "", loc->rev, &kind,
1344                             scratch_pool));
1345
1346   SVN_ERR(remote_proplist(loc->url, "", kind, loc->rev, ra_session,
1347                           get_explicit_props,
1348                           get_target_inherited_props,
1349                           depth, receiver, receiver_baton,
1350                           ctx->cancel_func, ctx->cancel_baton,
1351                           scratch_pool));
1352   return SVN_NO_ERROR;
1353 }
1354
1355 /* Helper for svn_client_proplist4 when retrieving properties and
1356    possibly inherited properties from the WC.  All arguments are as
1357    per svn_client_proplist4. */
1358 static svn_error_t *
1359 get_local_props(const char *path_or_url,
1360                 const svn_opt_revision_t *revision,
1361                 svn_depth_t depth,
1362                 const apr_array_header_t *changelists,
1363                 svn_boolean_t get_target_inherited_props,
1364                 svn_proplist_receiver2_t receiver,
1365                 void *receiver_baton,
1366                 svn_client_ctx_t *ctx,
1367                 apr_pool_t *scratch_pool)
1368 {
1369   svn_boolean_t pristine;
1370   svn_node_kind_t kind;
1371   apr_hash_t *changelist_hash = NULL;
1372   const char *local_abspath;
1373
1374   SVN_ERR(svn_dirent_get_absolute(&local_abspath, path_or_url,
1375                                   scratch_pool));
1376
1377   pristine = ((revision->kind == svn_opt_revision_committed)
1378               || (revision->kind == svn_opt_revision_base));
1379
1380   SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, local_abspath,
1381                             pristine, FALSE, scratch_pool));
1382
1383   if (kind == svn_node_unknown || kind == svn_node_none)
1384     {
1385       /* svn uses SVN_ERR_UNVERSIONED_RESOURCE as warning only
1386          for this function. */
1387       return svn_error_createf(SVN_ERR_UNVERSIONED_RESOURCE, NULL,
1388                                _("'%s' is not under version control"),
1389                                svn_dirent_local_style(local_abspath,
1390                                                       scratch_pool));
1391     }
1392
1393   if (get_target_inherited_props)
1394     {
1395       apr_array_header_t *iprops;
1396       const char *repos_root_url;
1397
1398       SVN_ERR(svn_wc__get_iprops(&iprops, ctx->wc_ctx, local_abspath,
1399                                  NULL, scratch_pool, scratch_pool));
1400       SVN_ERR(svn_client_get_repos_root(&repos_root_url, NULL, local_abspath,
1401                                         ctx, scratch_pool, scratch_pool));
1402       SVN_ERR(svn_client__iprop_relpaths_to_urls(iprops, repos_root_url,
1403                                                  scratch_pool,
1404                                                  scratch_pool));
1405       SVN_ERR(call_receiver(path_or_url, NULL, iprops, receiver,
1406                             receiver_baton, scratch_pool));
1407     }
1408
1409   if (changelists && changelists->nelts)
1410     SVN_ERR(svn_hash_from_cstring_keys(&changelist_hash,
1411                                        changelists, scratch_pool));
1412
1413   /* Fetch, recursively or not. */
1414   if (kind == svn_node_dir)
1415     {
1416       struct recursive_proplist_receiver_baton rb;
1417
1418       rb.wc_ctx = ctx->wc_ctx;
1419       rb.wrapped_receiver = receiver;
1420       rb.wrapped_receiver_baton = receiver_baton;
1421
1422       if (strcmp(path_or_url, local_abspath) != 0)
1423         {
1424           rb.anchor = path_or_url;
1425           rb.anchor_abspath = local_abspath;
1426         }
1427       else
1428         {
1429           rb.anchor = NULL;
1430           rb.anchor_abspath = NULL;
1431         }
1432
1433       SVN_ERR(svn_wc__prop_list_recursive(ctx->wc_ctx, local_abspath, NULL,
1434                                           depth, pristine, changelists,
1435                                           recursive_proplist_receiver, &rb,
1436                                           ctx->cancel_func, ctx->cancel_baton,
1437                                           scratch_pool));
1438     }
1439   else if (svn_wc__changelist_match(ctx->wc_ctx, local_abspath,
1440                                     changelist_hash, scratch_pool))
1441     {
1442       apr_hash_t *props;
1443
1444         if (pristine)
1445           SVN_ERR(svn_wc_get_pristine_props(&props,
1446                                             ctx->wc_ctx, local_abspath,
1447                                             scratch_pool, scratch_pool));
1448         else
1449           {
1450             svn_error_t *err;
1451
1452             err = svn_wc_prop_list2(&props, ctx->wc_ctx, local_abspath,
1453                                     scratch_pool, scratch_pool);
1454
1455
1456             if (err)
1457               {
1458                 if (err->apr_err != SVN_ERR_WC_PATH_UNEXPECTED_STATUS)
1459                   return svn_error_trace(err);
1460                 /* As svn_wc_prop_list2() doesn't return NULL for locally-deleted
1461                    let's do that here.  */
1462                 svn_error_clear(err);
1463                 props = apr_hash_make(scratch_pool);
1464               }
1465           }
1466
1467       SVN_ERR(call_receiver(path_or_url, props, NULL,
1468                             receiver, receiver_baton, scratch_pool));
1469
1470     }
1471   return SVN_NO_ERROR;
1472 }
1473
1474 svn_error_t *
1475 svn_client_proplist4(const char *path_or_url,
1476                      const svn_opt_revision_t *peg_revision,
1477                      const svn_opt_revision_t *revision,
1478                      svn_depth_t depth,
1479                      const apr_array_header_t *changelists,
1480                      svn_boolean_t get_target_inherited_props,
1481                      svn_proplist_receiver2_t receiver,
1482                      void *receiver_baton,
1483                      svn_client_ctx_t *ctx,
1484                      apr_pool_t *scratch_pool)
1485 {
1486   svn_boolean_t local_explicit_props;
1487   svn_boolean_t local_iprops;
1488
1489   peg_revision = svn_cl__rev_default_to_head_or_working(peg_revision,
1490                                                         path_or_url);
1491   revision = svn_cl__rev_default_to_peg(revision, peg_revision);
1492
1493   if (depth == svn_depth_unknown)
1494     depth = svn_depth_empty;
1495
1496   /* Are explicit props available locally? */
1497   local_explicit_props =
1498     (! svn_path_is_url(path_or_url)
1499      && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(peg_revision->kind)
1500      && SVN_CLIENT__REVKIND_IS_LOCAL_TO_WC(revision->kind));
1501
1502   /* If we want iprops are they available locally? */
1503   local_iprops =
1504     (get_target_inherited_props /* We want iprops */
1505      && local_explicit_props /* No local explicit props means no local iprops. */
1506      && (peg_revision->kind == svn_opt_revision_working
1507          || peg_revision->kind == svn_opt_revision_unspecified )
1508      && (revision->kind == svn_opt_revision_working
1509          || revision->kind == svn_opt_revision_unspecified ));
1510
1511   if ((get_target_inherited_props && !local_iprops)
1512       || !local_explicit_props)
1513     {
1514       SVN_ERR(get_remote_props(path_or_url, peg_revision, revision, depth,
1515                                !local_explicit_props,
1516                                (get_target_inherited_props && !local_iprops),
1517                                receiver, receiver_baton, ctx, scratch_pool));
1518     }
1519
1520   if (local_explicit_props)
1521     {
1522       SVN_ERR(get_local_props(path_or_url, revision, depth, changelists,
1523                               local_iprops, receiver, receiver_baton, ctx,
1524                               scratch_pool));
1525     }
1526
1527   return SVN_NO_ERROR;
1528 }
1529
1530 svn_error_t *
1531 svn_client_revprop_list(apr_hash_t **props,
1532                         const char *URL,
1533                         const svn_opt_revision_t *revision,
1534                         svn_revnum_t *set_rev,
1535                         svn_client_ctx_t *ctx,
1536                         apr_pool_t *pool)
1537 {
1538   svn_ra_session_t *ra_session;
1539   apr_hash_t *proplist;
1540   apr_pool_t *subpool = svn_pool_create(pool);
1541   svn_error_t *err;
1542
1543   /* Open an RA session for the URL. Note that we don't have a local
1544      directory, nor a place to put temp files. */
1545   SVN_ERR(svn_client_open_ra_session2(&ra_session, URL, NULL,
1546                                       ctx, subpool, subpool));
1547
1548   /* Resolve the revision into something real, and return that to the
1549      caller as well. */
1550   SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL,
1551                                           ra_session, revision, subpool));
1552
1553   /* The actual RA call. */
1554   err = svn_ra_rev_proplist(ra_session, *set_rev, &proplist, pool);
1555
1556   *props = proplist;
1557   svn_pool_destroy(subpool); /* Close RA session */
1558   return svn_error_trace(err);
1559 }