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