prop_commands.c revision 299742
11558Srgrimes/* 21558Srgrimes * prop_commands.c: Implementation of propset, propget, and proplist. 31558Srgrimes * 41558Srgrimes * ==================================================================== 51558Srgrimes * Licensed to the Apache Software Foundation (ASF) under one 61558Srgrimes * or more contributor license agreements. See the NOTICE file 71558Srgrimes * distributed with this work for additional information 81558Srgrimes * regarding copyright ownership. The ASF licenses this file 91558Srgrimes * to you under the Apache License, Version 2.0 (the 101558Srgrimes * "License"); you may not use this file except in compliance 111558Srgrimes * with the License. You may obtain a copy of the License at 121558Srgrimes * 131558Srgrimes * http://www.apache.org/licenses/LICENSE-2.0 141558Srgrimes * 151558Srgrimes * Unless required by applicable law or agreed to in writing, 161558Srgrimes * software distributed under the License is distributed on an 171558Srgrimes * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 181558Srgrimes * KIND, either express or implied. See the License for the 191558Srgrimes * specific language governing permissions and limitations 201558Srgrimes * under the License. 211558Srgrimes * ==================================================================== 221558Srgrimes */ 231558Srgrimes 241558Srgrimes/* ==================================================================== */ 251558Srgrimes 261558Srgrimes 271558Srgrimes 281558Srgrimes/*** Includes. ***/ 291558Srgrimes 30114589Sobrien#define APR_WANT_STRFUNC 311558Srgrimes#include <apr_want.h> 3238036Scharnier 331558Srgrimes#include "svn_error.h" 341558Srgrimes#include "svn_client.h" 351558Srgrimes#include "client.h" 361558Srgrimes#include "svn_dirent_uri.h" 371558Srgrimes#include "svn_path.h" 3841684Sbde#include "svn_pools.h" 39114589Sobrien#include "svn_props.h" 4038036Scharnier#include "svn_hash.h" 41114589Sobrien#include "svn_sorts.h" 42114589Sobrien 431558Srgrimes#include "svn_private_config.h" 441558Srgrimes#include "private/svn_wc_private.h" 451558Srgrimes#include "private/svn_ra_private.h" 461558Srgrimes#include "private/svn_client_private.h" 471558Srgrimes 481558Srgrimes 491558Srgrimes/*** Code. ***/ 5038036Scharnier 511558Srgrimes/* Return an SVN_ERR_CLIENT_PROPERTY_NAME error if NAME is a wcprop, 52114763Sobrien else return SVN_NO_ERROR. */ 531558Srgrimesstatic svn_error_t * 541558Srgrimeserror_if_wcprop_name(const char *name) 551558Srgrimes{ 561558Srgrimes if (svn_property_kind2(name) == svn_prop_wc_kind) 571558Srgrimes { 581558Srgrimes return svn_error_createf 591558Srgrimes (SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 601558Srgrimes _("'%s' is a wcprop, thus not accessible to clients"), 611558Srgrimes name); 621558Srgrimes } 631558Srgrimes 641558Srgrimes return SVN_NO_ERROR; 651558Srgrimes} 661558Srgrimes 671558Srgrimes 681558Srgrimesstruct getter_baton 691558Srgrimes{ 70227081Sed svn_ra_session_t *ra_session; 711558Srgrimes svn_revnum_t base_revision_for_url; 721558Srgrimes}; 7332399Salex 7432399Salex 7532399Salexstatic svn_error_t * 7632399Salexget_file_for_validation(const svn_string_t **mime_type, 7732399Salex svn_stream_t *stream, 7832399Salex void *baton, 7932399Salex apr_pool_t *pool) 8032399Salex{ 8132399Salex struct getter_baton *gb = baton; 8232399Salex svn_ra_session_t *ra_session = gb->ra_session; 8332399Salex apr_hash_t *props; 8432399Salex 851558Srgrimes SVN_ERR(svn_ra_get_file(ra_session, "", gb->base_revision_for_url, 861558Srgrimes stream, NULL, 871558Srgrimes (mime_type ? &props : NULL), 881558Srgrimes pool)); 891558Srgrimes 901558Srgrimes if (mime_type) 9148078Sru *mime_type = svn_hash_gets(props, SVN_PROP_MIME_TYPE); 9279749Sdd 9379749Sdd return SVN_NO_ERROR; 941558Srgrimes} 95197560Sdelphij 96238968Sdes 97197560Sdelphijstatic 98197560Sdelphijsvn_error_t * 99197560Sdelphijdo_url_propset(const char *url, 100197560Sdelphij const char *propname, 101197560Sdelphij const svn_string_t *propval, 102197560Sdelphij const svn_node_kind_t kind, 103197560Sdelphij const svn_revnum_t base_revision_for_url, 1041558Srgrimes const svn_delta_editor_t *editor, 105140796Sdelphij void *edit_baton, 106140796Sdelphij apr_pool_t *pool) 1071558Srgrimes{ 108140796Sdelphij void *root_baton; 1091558Srgrimes 11079749Sdd SVN_ERR(editor->open_root(edit_baton, base_revision_for_url, pool, 1111558Srgrimes &root_baton)); 1121558Srgrimes 1131558Srgrimes if (kind == svn_node_file) 1141558Srgrimes { 11526737Scharnier void *file_baton; 11626737Scharnier const char *uri_basename = svn_uri_basename(url, pool); 1171558Srgrimes 118216823Spjd SVN_ERR(editor->open_file(uri_basename, root_baton, 1191558Srgrimes base_revision_for_url, pool, &file_baton)); 1201558Srgrimes SVN_ERR(editor->change_file_prop(file_baton, propname, propval, pool)); 121216823Spjd SVN_ERR(editor->close_file(file_baton, NULL, pool)); 122216823Spjd } 123216823Spjd else 124216823Spjd { 125216823Spjd SVN_ERR(editor->change_dir_prop(root_baton, propname, propval, pool)); 126229403Sed } 127216823Spjd 128216823Spjd return editor->close_directory(root_baton, pool); 129216823Spjd} 130216823Spjd 131216823Spjdstatic svn_error_t * 132216823Spjdpropset_on_url(const char *propname, 133216823Spjd const svn_string_t *propval, 134216823Spjd const char *target, 135216823Spjd svn_boolean_t skip_checks, 136216823Spjd svn_revnum_t base_revision_for_url, 137216823Spjd const apr_hash_t *revprop_table, 138216823Spjd svn_commit_callback2_t commit_callback, 139216823Spjd void *commit_baton, 140216823Spjd svn_client_ctx_t *ctx, 141216823Spjd apr_pool_t *pool) 142216823Spjd{ 14348078Sru enum svn_prop_kind prop_kind = svn_property_kind2(propname); 1441558Srgrimes svn_ra_session_t *ra_session; 1451558Srgrimes svn_node_kind_t node_kind; 1461558Srgrimes const char *message; 1471558Srgrimes const svn_delta_editor_t *editor; 1481558Srgrimes void *edit_baton; 1491558Srgrimes apr_hash_t *commit_revprops; 1501558Srgrimes svn_error_t *err; 1511558Srgrimes 1521558Srgrimes if (prop_kind != svn_prop_regular_kind) 1531558Srgrimes return svn_error_createf 1541558Srgrimes (SVN_ERR_BAD_PROP_KIND, NULL, 1551558Srgrimes _("Property '%s' is not a regular property"), propname); 1561558Srgrimes 15748078Sru /* Open an RA session for the URL. Note that we don't have a local 15848078Sru directory, nor a place to put temp files. */ 15948078Sru SVN_ERR(svn_client_open_ra_session2(&ra_session, target, NULL, 16041666Smsmith ctx, pool, pool)); 16141666Smsmith 16241666Smsmith SVN_ERR(svn_ra_check_path(ra_session, "", base_revision_for_url, 1631558Srgrimes &node_kind, pool)); 1641558Srgrimes if (node_kind == svn_node_none) 1651558Srgrimes return svn_error_createf 1661558Srgrimes (SVN_ERR_FS_NOT_FOUND, NULL, 1671558Srgrimes _("Path '%s' does not exist in revision %ld"), 16848078Sru target, base_revision_for_url); 1691558Srgrimes 1701558Srgrimes if (node_kind == svn_node_file) 1711558Srgrimes { 1721558Srgrimes /* We need to reparent our session one directory up, since editor 1731558Srgrimes semantics require the root is a directory. 17448078Sru 1751558Srgrimes ### How does this interact with authz? */ 17648078Sru const char *parent_url; 17748078Sru parent_url = svn_uri_dirname(target, pool); 17848062Sjkoshy 17948078Sru SVN_ERR(svn_ra_reparent(ra_session, parent_url, pool)); 18048078Sru } 18148062Sjkoshy 18248078Sru /* Setting an inappropriate property is not allowed (unless 18348078Sru overridden by 'skip_checks', in some circumstances). Deleting an 18448078Sru inappropriate property is allowed, however, since older clients 1851558Srgrimes allowed (and other clients possibly still allow) setting it in 1861558Srgrimes the first place. */ 187216823Spjd if (propval && svn_prop_is_svn_prop(propname)) 1881558Srgrimes { 1891558Srgrimes const svn_string_t *new_value; 1901558Srgrimes struct getter_baton gb; 1911558Srgrimes 1921558Srgrimes gb.ra_session = ra_session; 1931558Srgrimes gb.base_revision_for_url = base_revision_for_url; 1941558Srgrimes SVN_ERR(svn_wc_canonicalize_svn_prop(&new_value, propname, propval, 19541684Sbde target, node_kind, skip_checks, 1961558Srgrimes get_file_for_validation, &gb, pool)); 1971558Srgrimes propval = new_value; 1981558Srgrimes } 1991558Srgrimes 2001558Srgrimes /* Create a new commit item and add it to the array. */ 2011558Srgrimes if (SVN_CLIENT__HAS_LOG_MSG_FUNC(ctx)) 2021558Srgrimes { 2031558Srgrimes svn_client_commit_item3_t *item; 2041558Srgrimes const char *tmp_file; 2051558Srgrimes apr_array_header_t *commit_items = apr_array_make(pool, 1, sizeof(item)); 2061558Srgrimes 2071558Srgrimes item = svn_client_commit_item3_create(pool); 2081558Srgrimes item->url = target; 2091558Srgrimes item->kind = node_kind; 2101558Srgrimes item->state_flags = SVN_CLIENT_COMMIT_ITEM_PROP_MODS; 2111558Srgrimes APR_ARRAY_PUSH(commit_items, svn_client_commit_item3_t *) = item; 2121558Srgrimes SVN_ERR(svn_client__get_log_msg(&message, &tmp_file, commit_items, 2131558Srgrimes ctx, pool)); 2141558Srgrimes if (! message) 2151558Srgrimes return SVN_NO_ERROR; 2161558Srgrimes } 2171558Srgrimes else 2181558Srgrimes message = ""; 2191558Srgrimes 2201558Srgrimes SVN_ERR(svn_client__ensure_revprop_table(&commit_revprops, revprop_table, 2211558Srgrimes message, ctx, pool)); 2221558Srgrimes 2231558Srgrimes /* Fetch RA commit editor. */ 2241558Srgrimes SVN_ERR(svn_ra__register_editor_shim_callbacks(ra_session, 2251558Srgrimes svn_client__get_shim_callbacks(ctx->wc_ctx, 2261558Srgrimes NULL, pool))); 2271558Srgrimes SVN_ERR(svn_ra_get_commit_editor3(ra_session, &editor, &edit_baton, 2281558Srgrimes commit_revprops, 2291558Srgrimes commit_callback, 2301558Srgrimes commit_baton, 2311558Srgrimes NULL, TRUE, /* No lock tokens */ 2321558Srgrimes pool)); 2331558Srgrimes 23426737Scharnier err = do_url_propset(target, propname, propval, node_kind, 23526737Scharnier base_revision_for_url, editor, edit_baton, pool); 23626737Scharnier 23726737Scharnier if (err) 2381558Srgrimes { 23928613Sjoerg /* At least try to abort the edit (and fs txn) before throwing err. */ 2401558Srgrimes svn_error_clear(editor->abort_edit(edit_baton, pool)); 2411558Srgrimes return svn_error_trace(err); 2421558Srgrimes } 24338036Scharnier 2441558Srgrimes if (ctx->notify_func2) 2451558Srgrimes { 246197560Sdelphij svn_wc_notify_t *notify; 247197560Sdelphij notify = svn_wc_create_notify_url(target, 2481558Srgrimes svn_wc_notify_commit_finalizing, 2491558Srgrimes pool); 2501558Srgrimes ctx->notify_func2(ctx->notify_baton2, notify, pool); 2511558Srgrimes } 2521558Srgrimes /* Close the edit. */ 2531558Srgrimes return editor->close_edit(edit_baton, pool); 2541558Srgrimes} 2551558Srgrimes 2561558Srgrimes/* Check that PROPNAME is a valid name for a versioned property. Return an 2571558Srgrimes * error if it is not valid, specifically if it is: 2581558Srgrimes * - the name of a standard Subversion rev-prop; or 2591558Srgrimes * - in the namespace of WC-props; or 2601558Srgrimes * - not a well-formed property name (except if PROPVAL is NULL: in other 2611558Srgrimes * words we do allow deleting a prop with an ill-formed name). 2621558Srgrimes * 26348004Sru * Since Subversion controls the "svn:" property namespace, we don't honor 2641558Srgrimes * a 'skip_checks' flag here. Checks for unusual property combinations such 2651558Srgrimes * as svn:eol-style with a non-text svn:mime-type might understandably be 2661558Srgrimes * skipped, but things such as using a property name reserved for revprops 2671558Srgrimes * on a local target are never allowed. 2681558Srgrimes */ 26932399Salexstatic svn_error_t * 27079749Sddcheck_prop_name(const char *propname, 2711558Srgrimes const svn_string_t *propval) 2721558Srgrimes{ 2731558Srgrimes if (svn_prop_is_known_svn_rev_prop(propname)) 2741558Srgrimes return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 2751558Srgrimes _("Revision property '%s' not allowed " 2761558Srgrimes "in this context"), propname); 2771558Srgrimes 2781558Srgrimes SVN_ERR(error_if_wcprop_name(propname)); 2791558Srgrimes 2801558Srgrimes if (propval && ! svn_prop_name_is_valid(propname)) 2811558Srgrimes return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 2821558Srgrimes _("Bad property name: '%s'"), propname); 2831558Srgrimes 2841558Srgrimes return SVN_NO_ERROR; 285238968Sdes} 2861558Srgrimes 2871558Srgrimessvn_error_t * 2881558Srgrimessvn_client_propset_local(const char *propname, 2891558Srgrimes const svn_string_t *propval, 29079749Sdd const apr_array_header_t *targets, 29132399Salex svn_depth_t depth, 29232399Salex svn_boolean_t skip_checks, 29332399Salex const apr_array_header_t *changelists, 29432399Salex svn_client_ctx_t *ctx, 295197560Sdelphij apr_pool_t *scratch_pool) 296140797Sdelphij{ 2971558Srgrimes apr_pool_t *iterpool = svn_pool_create(scratch_pool); 2981558Srgrimes svn_boolean_t targets_are_urls; 2991558Srgrimes int i; 3001558Srgrimes 3011558Srgrimes if (targets->nelts == 0) 3021558Srgrimes return SVN_NO_ERROR; 3031558Srgrimes 3041558Srgrimes /* Check for homogeneity among our targets. */ 3051558Srgrimes targets_are_urls = svn_path_is_url(APR_ARRAY_IDX(targets, 0, const char *)); 3061558Srgrimes SVN_ERR(svn_client__assert_homogeneous_target_type(targets)); 3071558Srgrimes 30832399Salex if (targets_are_urls) 3091558Srgrimes return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, 3101558Srgrimes _("Targets must be working copy paths")); 3111558Srgrimes 3121558Srgrimes SVN_ERR(check_prop_name(propname, propval)); 3131558Srgrimes 3141558Srgrimes for (i = 0; i < targets->nelts; i++) 3151558Srgrimes { 3161558Srgrimes svn_node_kind_t kind; 3171558Srgrimes const char *target_abspath; 3181558Srgrimes const char *target = APR_ARRAY_IDX(targets, i, const char *); 3191558Srgrimes 3201558Srgrimes svn_pool_clear(iterpool); 3211558Srgrimes 3221558Srgrimes /* Check for cancellation */ 3231558Srgrimes if (ctx->cancel_func) 3241558Srgrimes SVN_ERR(ctx->cancel_func(ctx->cancel_baton)); 3251558Srgrimes 3261558Srgrimes SVN_ERR(svn_dirent_get_absolute(&target_abspath, target, iterpool)); 3271558Srgrimes 3281558Srgrimes /* Call prop_set for deleted nodes to have special errors */ 3291558Srgrimes SVN_ERR(svn_wc_read_kind2(&kind, ctx->wc_ctx, target_abspath, 3301558Srgrimes FALSE, FALSE, iterpool)); 3311558Srgrimes 3321558Srgrimes if (kind == svn_node_unknown || kind == svn_node_none) 3331558Srgrimes { 33438036Scharnier if (ctx->notify_func2) 3351558Srgrimes { 3361558Srgrimes svn_wc_notify_t *notify = svn_wc_create_notify( 3371558Srgrimes target_abspath, 3381558Srgrimes svn_wc_notify_path_nonexistent, 3391558Srgrimes iterpool); 3401558Srgrimes 3411558Srgrimes ctx->notify_func2(ctx->notify_baton2, notify, iterpool); 3421558Srgrimes } 3431558Srgrimes } 3441558Srgrimes 345197560Sdelphij SVN_WC__CALL_WITH_WRITE_LOCK( 346140797Sdelphij svn_wc_prop_set4(ctx->wc_ctx, target_abspath, propname, 3471558Srgrimes propval, depth, skip_checks, changelists, 3481558Srgrimes ctx->cancel_func, ctx->cancel_baton, 3491558Srgrimes ctx->notify_func2, ctx->notify_baton2, iterpool), 3501558Srgrimes ctx->wc_ctx, target_abspath, FALSE /* lock_anchor */, iterpool); 351197560Sdelphij } 352238968Sdes svn_pool_destroy(iterpool); 3531558Srgrimes 35432399Salex return SVN_NO_ERROR; 3551558Srgrimes} 3561558Srgrimes 35741666Smsmithsvn_error_t * 35841666Smsmithsvn_client_propset_remote(const char *propname, 3591558Srgrimes const svn_string_t *propval, 3601558Srgrimes const char *url, 3611558Srgrimes svn_boolean_t skip_checks, 3621558Srgrimes svn_revnum_t base_revision_for_url, 3634844Sats const apr_hash_t *revprop_table, 3641558Srgrimes svn_commit_callback2_t commit_callback, 3651558Srgrimes void *commit_baton, 3661558Srgrimes svn_client_ctx_t *ctx, 3671558Srgrimes apr_pool_t *scratch_pool) 3681558Srgrimes{ 3691558Srgrimes if (!svn_path_is_url(url)) 37041666Smsmith return svn_error_create(SVN_ERR_ILLEGAL_TARGET, NULL, 37141666Smsmith _("Targets must be URLs")); 37248078Sru 3731558Srgrimes SVN_ERR(check_prop_name(propname, propval)); 3741558Srgrimes 3751558Srgrimes /* The rationale for requiring the base_revision_for_url 37648078Sru argument is that without it, it's too easy to possibly 37748078Sru overwrite someone else's change without noticing. (See also 37848078Sru tools/examples/svnput.c). */ 37948078Sru if (! SVN_IS_VALID_REVNUM(base_revision_for_url)) 38048078Sru return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL, 38148078Sru _("Setting property on non-local targets " 38248078Sru "needs a base revision")); 38348078Sru 38448078Sru /* ### When you set svn:eol-style or svn:keywords on a wc file, 38548078Sru ### Subversion sends a textdelta at commit time to properly 38648078Sru ### normalize the file in the repository. If we want to 38748078Sru ### support editing these properties on URLs, then we should 38848078Sru ### generate the same textdelta; for now, we won't support 38948078Sru ### editing these properties on URLs. (Admittedly, this 39048078Sru ### means that all the machinery with get_file_for_validation 39148078Sru ### is unused.) 39248078Sru */ 39348078Sru if ((strcmp(propname, SVN_PROP_EOL_STYLE) == 0) || 39448078Sru (strcmp(propname, SVN_PROP_KEYWORDS) == 0)) 39548078Sru return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL, 39648078Sru _("Setting property '%s' on non-local " 39748078Sru "targets is not supported"), propname); 39848078Sru 39948078Sru SVN_ERR(propset_on_url(propname, propval, url, skip_checks, 40048078Sru base_revision_for_url, revprop_table, 40148078Sru commit_callback, commit_baton, ctx, scratch_pool)); 40248078Sru 40348078Sru return SVN_NO_ERROR; 4041558Srgrimes} 4051558Srgrimes 4061558Srgrimesstatic svn_error_t * 4071558Srgrimescheck_and_set_revprop(svn_revnum_t *set_rev, 4081558Srgrimes svn_ra_session_t *ra_session, 4091558Srgrimes const char *propname, 4101558Srgrimes const svn_string_t *original_propval, 411197560Sdelphij const svn_string_t *propval, 412140797Sdelphij apr_pool_t *pool) 4131558Srgrimes{ 41479749Sdd if (original_propval) 41579749Sdd { 4161558Srgrimes /* Ensure old value hasn't changed behind our back. */ 41732328Salex svn_string_t *current; 4181558Srgrimes SVN_ERR(svn_ra_rev_prop(ra_session, *set_rev, propname, ¤t, pool)); 41948062Sjkoshy 42048062Sjkoshy if (original_propval->data && (! current)) 4211558Srgrimes { 4221558Srgrimes return svn_error_createf( 42348062Sjkoshy SVN_ERR_RA_OUT_OF_DATE, NULL, 4241558Srgrimes _("revprop '%s' in r%ld is unexpectedly absent " 4251558Srgrimes "in repository (maybe someone else deleted it?)"), 4261558Srgrimes propname, *set_rev); 4271558Srgrimes } 4281558Srgrimes else if (original_propval->data 4291558Srgrimes && (! svn_string_compare(original_propval, current))) 43048004Sru { 43148004Sru return svn_error_createf( 4321558Srgrimes SVN_ERR_RA_OUT_OF_DATE, NULL, 4331558Srgrimes _("revprop '%s' in r%ld has unexpected value " 4341558Srgrimes "in repository (maybe someone else changed it?)"), 4351558Srgrimes propname, *set_rev); 4361558Srgrimes } 4371558Srgrimes else if ((! original_propval->data) && current) 43848956Sbillf { 4391558Srgrimes return svn_error_createf( 4401558Srgrimes SVN_ERR_RA_OUT_OF_DATE, NULL, 4411558Srgrimes _("revprop '%s' in r%ld is unexpectedly present " 4421558Srgrimes "in repository (maybe someone else set it?)"), 4431558Srgrimes propname, *set_rev); 4441558Srgrimes } 4451558Srgrimes } 44648956Sbillf 4471558Srgrimes SVN_ERR(svn_ra_change_rev_prop2(ra_session, *set_rev, propname, 4481558Srgrimes NULL, propval, pool)); 4491558Srgrimes 4501558Srgrimes return SVN_NO_ERROR; 4511558Srgrimes} 4521558Srgrimes 45332328Salexsvn_error_t * 4541558Srgrimessvn_client_revprop_set2(const char *propname, 45532328Salex const svn_string_t *propval, 45632328Salex const svn_string_t *original_propval, 45732328Salex const char *URL, 45832328Salex const svn_opt_revision_t *revision, 45932328Salex svn_revnum_t *set_rev, 46032328Salex svn_boolean_t force, 46132328Salex svn_client_ctx_t *ctx, 46232329Salex apr_pool_t *pool) 46332328Salex{ 4641558Srgrimes svn_ra_session_t *ra_session; 4651558Srgrimes svn_boolean_t be_atomic; 4661558Srgrimes 4671558Srgrimes if ((strcmp(propname, SVN_PROP_REVISION_AUTHOR) == 0) 4681558Srgrimes && propval 4691558Srgrimes && strchr(propval->data, '\n') != NULL 4701558Srgrimes && (! force)) 4711558Srgrimes return svn_error_create(SVN_ERR_CLIENT_REVISION_AUTHOR_CONTAINS_NEWLINE, 4721558Srgrimes NULL, _("Author name should not contain a newline;" 4731558Srgrimes " value will not be set unless forced")); 4741558Srgrimes 4751558Srgrimes if (propval && ! svn_prop_name_is_valid(propname)) 4761558Srgrimes return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 4771558Srgrimes _("Bad property name: '%s'"), propname); 4781558Srgrimes 4791558Srgrimes /* Open an RA session for the URL. */ 4801558Srgrimes SVN_ERR(svn_client_open_ra_session2(&ra_session, URL, NULL, 4811558Srgrimes ctx, pool, pool)); 4821558Srgrimes 4831558Srgrimes /* Resolve the revision into something real, and return that to the 4841558Srgrimes caller as well. */ 48526737Scharnier SVN_ERR(svn_client__get_revision_number(set_rev, NULL, ctx->wc_ctx, NULL, 48626737Scharnier ra_session, revision, pool)); 4871558Srgrimes 4881558Srgrimes SVN_ERR(svn_ra_has_capability(ra_session, &be_atomic, 4891558Srgrimes SVN_RA_CAPABILITY_ATOMIC_REVPROPS, pool)); 4901558Srgrimes if (be_atomic) 4911558Srgrimes { 4921558Srgrimes /* Convert ORIGINAL_PROPVAL to an OLD_VALUE_P. */ 4931558Srgrimes const svn_string_t *const *old_value_p; 494197560Sdelphij const svn_string_t *unset = NULL; 495197560Sdelphij 4961558Srgrimes if (original_propval == NULL) 4971558Srgrimes old_value_p = NULL; 4981558Srgrimes else if (original_propval->data == NULL) 4991558Srgrimes old_value_p = &unset; 5001558Srgrimes else 5011558Srgrimes old_value_p = &original_propval; 5021558Srgrimes 5031558Srgrimes /* The actual RA call. */ 5041558Srgrimes SVN_ERR(svn_ra_change_rev_prop2(ra_session, *set_rev, propname, 5051558Srgrimes old_value_p, propval, pool)); 5061558Srgrimes } 5071558Srgrimes else 5081558Srgrimes { 5091558Srgrimes /* The actual RA call. */ 5101558Srgrimes SVN_ERR(check_and_set_revprop(set_rev, ra_session, propname, 5111558Srgrimes original_propval, propval, pool)); 5121558Srgrimes } 5131558Srgrimes 5141558Srgrimes if (ctx->notify_func2) 5151558Srgrimes { 516197560Sdelphij svn_wc_notify_t *notify = svn_wc_create_notify_url(URL, 517140797Sdelphij propval == NULL 5181558Srgrimes ? svn_wc_notify_revprop_deleted 51941684Sbde : svn_wc_notify_revprop_set, 52041684Sbde pool); 5211558Srgrimes notify->prop_name = propname; 5221558Srgrimes notify->revision = *set_rev; 5231558Srgrimes 524197560Sdelphij ctx->notify_func2(ctx->notify_baton2, notify, pool); 525201180Sed } 5261558Srgrimes 52738036Scharnier return SVN_NO_ERROR; 5281558Srgrimes} 5291558Srgrimes 530197560Sdelphijsvn_error_t * 531140797Sdelphijsvn_client__remote_propget(apr_hash_t *props, 5321558Srgrimes apr_array_header_t **inherited_props, 53348078Sru const char *propname, 53448078Sru const char *target_prefix, 53548078Sru const char *target_relative, 536216823Spjd svn_node_kind_t kind, 537216823Spjd svn_revnum_t revnum, 5381558Srgrimes svn_ra_session_t *ra_session, 5391558Srgrimes 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