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