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