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