1251881Speter/* 2251881Speter * propget-cmd.c -- Print properties and values of files/dirs 3251881Speter * 4251881Speter * ==================================================================== 5251881Speter * Licensed to the Apache Software Foundation (ASF) under one 6251881Speter * or more contributor license agreements. See the NOTICE file 7251881Speter * distributed with this work for additional information 8251881Speter * regarding copyright ownership. The ASF licenses this file 9251881Speter * to you under the Apache License, Version 2.0 (the 10251881Speter * "License"); you may not use this file except in compliance 11251881Speter * with the License. You may obtain a copy of the License at 12251881Speter * 13251881Speter * http://www.apache.org/licenses/LICENSE-2.0 14251881Speter * 15251881Speter * Unless required by applicable law or agreed to in writing, 16251881Speter * software distributed under the License is distributed on an 17251881Speter * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY 18251881Speter * KIND, either express or implied. See the License for the 19251881Speter * specific language governing permissions and limitations 20251881Speter * under the License. 21251881Speter * ==================================================================== 22251881Speter */ 23251881Speter 24251881Speter/* ==================================================================== */ 25251881Speter 26251881Speter 27251881Speter 28251881Speter/*** Includes. ***/ 29251881Speter 30251881Speter#include "svn_hash.h" 31251881Speter#include "svn_cmdline.h" 32251881Speter#include "svn_pools.h" 33251881Speter#include "svn_client.h" 34251881Speter#include "svn_string.h" 35251881Speter#include "svn_error_codes.h" 36251881Speter#include "svn_error.h" 37251881Speter#include "svn_utf.h" 38251881Speter#include "svn_sorts.h" 39251881Speter#include "svn_subst.h" 40251881Speter#include "svn_dirent_uri.h" 41251881Speter#include "svn_path.h" 42251881Speter#include "svn_props.h" 43251881Speter#include "svn_xml.h" 44251881Speter#include "cl.h" 45251881Speter 46251881Speter#include "private/svn_cmdline_private.h" 47289180Speter#include "private/svn_opt_private.h" 48289180Speter#include "private/svn_sorts_private.h" 49251881Speter#include "svn_private_config.h" 50251881Speter 51251881Speter 52251881Speter/*** Code. ***/ 53251881Speter 54251881Speterstatic svn_error_t * 55251881Speterstream_write(svn_stream_t *out, 56251881Speter const char *data, 57251881Speter apr_size_t len) 58251881Speter{ 59251881Speter apr_size_t write_len = len; 60251881Speter 61251881Speter /* We're gonna bail on an incomplete write here only because we know 62251881Speter that this stream is really stdout, which should never be blocking 63251881Speter on us. */ 64251881Speter SVN_ERR(svn_stream_write(out, data, &write_len)); 65251881Speter if (write_len != len) 66251881Speter return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL, 67251881Speter _("Error writing to stream")); 68251881Speter return SVN_NO_ERROR; 69251881Speter} 70251881Speter 71251881Speter 72251881Speterstatic svn_error_t * 73251881Speterprint_properties_xml(const char *pname, 74251881Speter apr_hash_t *props, 75251881Speter apr_array_header_t *inherited_props, 76251881Speter apr_pool_t *pool) 77251881Speter{ 78251881Speter apr_array_header_t *sorted_props; 79251881Speter int i; 80251881Speter apr_pool_t *iterpool = NULL; 81251881Speter svn_stringbuf_t *sb; 82251881Speter 83251881Speter if (inherited_props && inherited_props->nelts) 84251881Speter { 85251881Speter iterpool = svn_pool_create(pool); 86251881Speter 87251881Speter for (i = 0; i < inherited_props->nelts; i++) 88251881Speter { 89251881Speter const char *name_local; 90251881Speter svn_prop_inherited_item_t *iprop = 91251881Speter APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); 92289180Speter svn_string_t *propval = apr_hash_this_val( 93251881Speter apr_hash_first(pool, iprop->prop_hash)); 94251881Speter 95251881Speter sb = NULL; 96251881Speter svn_pool_clear(iterpool); 97251881Speter 98251881Speter if (svn_path_is_url(iprop->path_or_url)) 99251881Speter name_local = iprop->path_or_url; 100251881Speter else 101251881Speter name_local = svn_dirent_local_style(iprop->path_or_url, iterpool); 102251881Speter 103251881Speter svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target", 104289180Speter "path", name_local, SVN_VA_NULL); 105251881Speter 106251881Speter svn_cmdline__print_xml_prop(&sb, pname, propval, TRUE, iterpool); 107251881Speter svn_xml_make_close_tag(&sb, iterpool, "target"); 108251881Speter 109251881Speter SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); 110251881Speter } 111251881Speter } 112251881Speter 113251881Speter if (iterpool == NULL) 114251881Speter iterpool = svn_pool_create(iterpool); 115251881Speter 116251881Speter sorted_props = svn_sort__hash(props, svn_sort_compare_items_as_paths, pool); 117251881Speter for (i = 0; i < sorted_props->nelts; i++) 118251881Speter { 119251881Speter svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t); 120251881Speter const char *filename = item.key; 121251881Speter svn_string_t *propval = item.value; 122251881Speter 123251881Speter sb = NULL; 124251881Speter svn_pool_clear(iterpool); 125251881Speter 126251881Speter svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target", 127289180Speter "path", filename, SVN_VA_NULL); 128251881Speter svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, iterpool); 129251881Speter svn_xml_make_close_tag(&sb, iterpool, "target"); 130251881Speter 131251881Speter SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); 132251881Speter } 133251881Speter 134251881Speter if (iterpool) 135251881Speter svn_pool_destroy(iterpool); 136251881Speter 137251881Speter return SVN_NO_ERROR; 138251881Speter} 139251881Speter 140362181Sdim/* Print the property PNAME with the value PROPVAL set on ABSPATH_OR_URL 141251881Speter to the stream OUT. 142251881Speter 143251881Speter If INHERITED_PROPERTY is true then the property described is inherited, 144251881Speter otherwise it is explicit. 145251881Speter 146251881Speter WC_PATH_PREFIX is the absolute path of the current working directory (and 147251881Speter is ignored if ABSPATH_OR_URL is a URL). 148251881Speter 149251881Speter All other arguments are as per print_properties. */ 150251881Speterstatic svn_error_t * 151251881Speterprint_single_prop(svn_string_t *propval, 152251881Speter const char *target_abspath_or_url, 153251881Speter const char *abspath_or_URL, 154251881Speter const char *wc_path_prefix, 155251881Speter svn_stream_t *out, 156362181Sdim const char *pname, 157251881Speter svn_boolean_t print_filenames, 158251881Speter svn_boolean_t omit_newline, 159251881Speter svn_boolean_t like_proplist, 160251881Speter svn_boolean_t inherited_property, 161251881Speter apr_pool_t *scratch_pool) 162251881Speter{ 163251881Speter if (print_filenames) 164251881Speter { 165251881Speter const char *header; 166251881Speter 167251881Speter /* Print the file name. */ 168251881Speter 169251881Speter if (! svn_path_is_url(abspath_or_URL)) 170251881Speter abspath_or_URL = svn_cl__local_style_skip_ancestor(wc_path_prefix, 171251881Speter abspath_or_URL, 172251881Speter scratch_pool); 173251881Speter 174251881Speter /* In verbose mode, print exactly same as "proplist" does; 175251881Speter * otherwise, print a brief header. */ 176251881Speter if (inherited_property) 177251881Speter { 178251881Speter if (like_proplist) 179251881Speter { 180251881Speter if (! svn_path_is_url(target_abspath_or_url)) 181251881Speter target_abspath_or_url = 182251881Speter svn_cl__local_style_skip_ancestor(wc_path_prefix, 183251881Speter target_abspath_or_url, 184251881Speter scratch_pool); 185251881Speter header = apr_psprintf( 186251881Speter scratch_pool, 187251881Speter _("Inherited properties on '%s',\nfrom '%s':\n"), 188251881Speter target_abspath_or_url, abspath_or_URL); 189251881Speter } 190251881Speter else 191251881Speter { 192251881Speter header = apr_psprintf(scratch_pool, "%s - ", abspath_or_URL); 193251881Speter } 194251881Speter } 195251881Speter else 196251881Speter header = apr_psprintf(scratch_pool, like_proplist 197251881Speter ? _("Properties on '%s':\n") 198251881Speter : "%s - ", abspath_or_URL); 199251881Speter SVN_ERR(svn_cmdline_cstring_from_utf8(&header, header, scratch_pool)); 200251881Speter SVN_ERR(svn_subst_translate_cstring2(header, &header, 201251881Speter APR_EOL_STR, /* 'native' eol */ 202251881Speter FALSE, /* no repair */ 203251881Speter NULL, /* no keywords */ 204251881Speter FALSE, /* no expansion */ 205251881Speter scratch_pool)); 206251881Speter SVN_ERR(stream_write(out, header, strlen(header))); 207251881Speter } 208251881Speter 209251881Speter if (like_proplist) 210251881Speter { 211251881Speter /* Print the property name and value just as "proplist -v" does */ 212251881Speter apr_hash_t *hash = apr_hash_make(scratch_pool); 213251881Speter 214362181Sdim svn_hash_sets(hash, pname, propval); 215251881Speter SVN_ERR(svn_cmdline__print_prop_hash(out, hash, FALSE, scratch_pool)); 216251881Speter } 217251881Speter else 218251881Speter { 219251881Speter /* If this is a special Subversion property, it is stored as 220251881Speter UTF8, so convert to the native format. */ 221362181Sdim if (svn_prop_needs_translation(pname)) 222251881Speter SVN_ERR(svn_subst_detranslate_string(&propval, propval, 223251881Speter TRUE, scratch_pool)); 224251881Speter 225251881Speter SVN_ERR(stream_write(out, propval->data, propval->len)); 226251881Speter 227251881Speter if (! omit_newline) 228251881Speter SVN_ERR(stream_write(out, APR_EOL_STR, 229251881Speter strlen(APR_EOL_STR))); 230251881Speter } 231251881Speter return SVN_NO_ERROR; 232251881Speter} 233251881Speter 234251881Speter/* Print the properties in PROPS and/or *INHERITED_PROPS to the stream OUT. 235251881Speter PROPS is a hash mapping (const char *) path to (svn_string_t) property 236251881Speter value. INHERITED_PROPS is a depth-first ordered array of 237251881Speter svn_prop_inherited_item_t * structures. 238251881Speter 239251881Speter TARGET_ABSPATH_OR_URL is the path which inherits INHERITED_PROPS. 240251881Speter 241251881Speter PROPS may be an empty hash, but is never null. INHERITED_PROPS may be 242251881Speter null. 243251881Speter 244251881Speter If IS_URL is true, all paths in PROPS are URLs, else all paths are local 245251881Speter paths. 246251881Speter 247362181Sdim PNAME is the property name of all the properties. 248251881Speter 249251881Speter If PRINT_FILENAMES is true, print the item's path before each property. 250251881Speter 251251881Speter If OMIT_NEWLINE is true, don't add a newline at the end of each property. 252251881Speter 253251881Speter If LIKE_PROPLIST is true, print everything in a more verbose format 254251881Speter like "svn proplist -v" does. */ 255251881Speterstatic svn_error_t * 256251881Speterprint_properties(svn_stream_t *out, 257251881Speter const char *target_abspath_or_url, 258362181Sdim const char *pname, 259251881Speter apr_hash_t *props, 260251881Speter apr_array_header_t *inherited_props, 261251881Speter svn_boolean_t print_filenames, 262251881Speter svn_boolean_t omit_newline, 263251881Speter svn_boolean_t like_proplist, 264251881Speter apr_pool_t *pool) 265251881Speter{ 266251881Speter apr_array_header_t *sorted_props; 267251881Speter int i; 268251881Speter apr_pool_t *iterpool = svn_pool_create(pool); 269251881Speter const char *path_prefix; 270251881Speter 271251881Speter SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", pool)); 272251881Speter 273251881Speter if (inherited_props) 274251881Speter { 275251881Speter svn_pool_clear(iterpool); 276251881Speter 277251881Speter for (i = 0; i < inherited_props->nelts; i++) 278251881Speter { 279251881Speter svn_prop_inherited_item_t *iprop = 280251881Speter APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *); 281289180Speter svn_string_t *propval = apr_hash_this_val(apr_hash_first(pool, 282251881Speter iprop->prop_hash)); 283251881Speter SVN_ERR(print_single_prop(propval, target_abspath_or_url, 284251881Speter iprop->path_or_url, 285362181Sdim path_prefix, out, pname, 286251881Speter print_filenames, omit_newline, 287251881Speter like_proplist, TRUE, iterpool)); 288251881Speter } 289251881Speter } 290251881Speter 291251881Speter sorted_props = svn_sort__hash(props, svn_sort_compare_items_as_paths, pool); 292251881Speter for (i = 0; i < sorted_props->nelts; i++) 293251881Speter { 294251881Speter svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t); 295251881Speter const char *filename = item.key; 296251881Speter svn_string_t *propval = item.value; 297251881Speter 298251881Speter svn_pool_clear(iterpool); 299251881Speter 300251881Speter SVN_ERR(print_single_prop(propval, target_abspath_or_url, filename, 301362181Sdim path_prefix, out, pname, print_filenames, 302251881Speter omit_newline, like_proplist, FALSE, 303251881Speter iterpool)); 304251881Speter } 305251881Speter 306251881Speter svn_pool_destroy(iterpool); 307251881Speter 308251881Speter return SVN_NO_ERROR; 309251881Speter} 310251881Speter 311251881Speter 312251881Speter/* This implements the `svn_opt_subcommand_t' interface. */ 313251881Spetersvn_error_t * 314251881Spetersvn_cl__propget(apr_getopt_t *os, 315251881Speter void *baton, 316251881Speter apr_pool_t *pool) 317251881Speter{ 318251881Speter svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state; 319251881Speter svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx; 320362181Sdim const char *pname; 321251881Speter apr_array_header_t *args, *targets; 322251881Speter svn_stream_t *out; 323289180Speter svn_boolean_t warned = FALSE; 324251881Speter 325289180Speter if (opt_state->verbose && (opt_state->revprop || opt_state->no_newline 326251881Speter || opt_state->xml)) 327251881Speter return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL, 328251881Speter _("--verbose cannot be used with --revprop or " 329289180Speter "--no-newline or --xml")); 330251881Speter 331362181Sdim /* PNAME is first argument */ 332251881Speter SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool)); 333251881Speter pname = APR_ARRAY_IDX(args, 0, const char *); 334362181Sdim SVN_ERR(svn_utf_cstring_to_utf8(&pname, pname, pool)); 335362181Sdim if (! svn_prop_name_is_valid(pname)) 336251881Speter return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL, 337251881Speter _("'%s' is not a valid Subversion property name"), 338362181Sdim pname); 339251881Speter 340251881Speter SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os, 341251881Speter opt_state->targets, 342251881Speter ctx, FALSE, pool)); 343251881Speter 344251881Speter /* Add "." if user passed 0 file arguments */ 345251881Speter svn_opt_push_implicit_dot_target(targets, pool); 346251881Speter 347251881Speter /* Open a stream to stdout. */ 348251881Speter SVN_ERR(svn_stream_for_stdout(&out, pool)); 349251881Speter 350251881Speter if (opt_state->revprop) /* operate on a revprop */ 351251881Speter { 352251881Speter svn_revnum_t rev; 353251881Speter const char *URL; 354251881Speter svn_string_t *propval; 355251881Speter 356251881Speter if (opt_state->show_inherited_props) 357251881Speter return svn_error_create( 358251881Speter SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 359251881Speter _("--show-inherited-props can't be used with --revprop")); 360251881Speter 361251881Speter SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets, 362251881Speter &URL, ctx, pool)); 363251881Speter 364251881Speter /* Let libsvn_client do the real work. */ 365362181Sdim SVN_ERR(svn_client_revprop_get(pname, &propval, 366251881Speter URL, &(opt_state->start_revision), 367251881Speter &rev, ctx, pool)); 368251881Speter 369289180Speter if (propval == NULL) 370251881Speter { 371289180Speter return svn_error_createf(SVN_ERR_PROPERTY_NOT_FOUND, NULL, 372289180Speter _("Property '%s' not found on " 373289180Speter "revision %s"), 374362181Sdim pname, 375289180Speter svn_opt__revision_to_string( 376289180Speter &opt_state->start_revision, 377289180Speter pool)); 378289180Speter } 379289180Speter else 380289180Speter { 381251881Speter if (opt_state->xml) 382251881Speter { 383251881Speter svn_stringbuf_t *sb = NULL; 384251881Speter char *revstr = apr_psprintf(pool, "%ld", rev); 385251881Speter 386251881Speter SVN_ERR(svn_cl__xml_print_header("properties", pool)); 387251881Speter 388251881Speter svn_xml_make_open_tag(&sb, pool, svn_xml_normal, 389251881Speter "revprops", 390289180Speter "rev", revstr, SVN_VA_NULL); 391251881Speter 392362181Sdim svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, 393251881Speter pool); 394251881Speter 395251881Speter svn_xml_make_close_tag(&sb, pool, "revprops"); 396251881Speter 397251881Speter SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout)); 398251881Speter SVN_ERR(svn_cl__xml_print_footer("properties", pool)); 399251881Speter } 400251881Speter else 401251881Speter { 402251881Speter svn_string_t *printable_val = propval; 403251881Speter 404251881Speter /* If this is a special Subversion property, it is stored as 405251881Speter UTF8 and LF, so convert to the native locale and eol-style. */ 406251881Speter 407362181Sdim if (svn_prop_needs_translation(pname)) 408251881Speter SVN_ERR(svn_subst_detranslate_string(&printable_val, propval, 409251881Speter TRUE, pool)); 410251881Speter 411251881Speter SVN_ERR(stream_write(out, printable_val->data, 412251881Speter printable_val->len)); 413289180Speter if (! opt_state->no_newline) 414251881Speter SVN_ERR(stream_write(out, APR_EOL_STR, strlen(APR_EOL_STR))); 415251881Speter } 416251881Speter } 417251881Speter } 418251881Speter else /* operate on a normal, versioned property (not a revprop) */ 419251881Speter { 420251881Speter apr_pool_t *subpool = svn_pool_create(pool); 421251881Speter int i; 422251881Speter 423251881Speter if (opt_state->xml) 424251881Speter SVN_ERR(svn_cl__xml_print_header("properties", subpool)); 425251881Speter 426251881Speter if (opt_state->depth == svn_depth_unknown) 427251881Speter opt_state->depth = svn_depth_empty; 428251881Speter 429289180Speter /* No-newline mode only makes sense for a single target. So make 430251881Speter sure we have only a single target, and that we're not being 431251881Speter asked to recurse on that target. */ 432289180Speter if (opt_state->no_newline 433289180Speter && ((targets->nelts > 1) || (opt_state->depth != svn_depth_empty) 434289180Speter || (opt_state->show_inherited_props))) 435251881Speter return svn_error_create 436251881Speter (SVN_ERR_CL_ARG_PARSING_ERROR, NULL, 437289180Speter _("--no-newline is only available for single-target," 438289180Speter " non-recursive propget operations")); 439251881Speter 440251881Speter for (i = 0; i < targets->nelts; i++) 441251881Speter { 442251881Speter const char *target = APR_ARRAY_IDX(targets, i, const char *); 443251881Speter apr_hash_t *props; 444251881Speter svn_boolean_t print_filenames; 445251881Speter svn_boolean_t omit_newline; 446251881Speter svn_boolean_t like_proplist; 447251881Speter const char *truepath; 448251881Speter svn_opt_revision_t peg_revision; 449251881Speter apr_array_header_t *inherited_props; 450251881Speter 451251881Speter svn_pool_clear(subpool); 452251881Speter SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton)); 453251881Speter 454251881Speter /* Check for a peg revision. */ 455251881Speter SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target, 456251881Speter subpool)); 457251881Speter 458251881Speter if (!svn_path_is_url(truepath)) 459251881Speter SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool)); 460251881Speter 461251881Speter SVN_ERR(svn_client_propget5( 462251881Speter &props, 463251881Speter opt_state->show_inherited_props ? &inherited_props : NULL, 464362181Sdim pname, truepath, 465251881Speter &peg_revision, 466251881Speter &(opt_state->start_revision), 467251881Speter NULL, opt_state->depth, 468251881Speter opt_state->changelists, ctx, subpool, 469251881Speter subpool)); 470251881Speter 471251881Speter /* Any time there is more than one thing to print, or where 472251881Speter the path associated with a printed thing is not obvious, 473251881Speter we'll print filenames. That is, unless we've been told 474289180Speter not to do so with the --no-newline option. */ 475251881Speter print_filenames = ((opt_state->depth > svn_depth_empty 476251881Speter || targets->nelts > 1 477251881Speter || apr_hash_count(props) > 1 478251881Speter || opt_state->verbose 479251881Speter || opt_state->show_inherited_props) 480289180Speter && (! opt_state->no_newline)); 481289180Speter omit_newline = opt_state->no_newline; 482289180Speter like_proplist = opt_state->verbose && !opt_state->no_newline; 483251881Speter 484289180Speter /* If there are no properties, and exactly one node was queried, 485289180Speter then warn. */ 486289180Speter if (opt_state->depth == svn_depth_empty 487289180Speter && !opt_state->show_inherited_props 488289180Speter && apr_hash_count(props) == 0) 489289180Speter { 490289180Speter svn_error_t *err; 491289180Speter err = svn_error_createf(SVN_ERR_PROPERTY_NOT_FOUND, NULL, 492289180Speter _("Property '%s' not found on '%s'"), 493362181Sdim pname, target); 494289180Speter svn_handle_warning2(stderr, err, "svn: "); 495289180Speter svn_error_clear(err); 496289180Speter warned = TRUE; 497289180Speter } 498289180Speter 499251881Speter if (opt_state->xml) 500251881Speter SVN_ERR(print_properties_xml( 501362181Sdim pname, props, 502251881Speter opt_state->show_inherited_props ? inherited_props : NULL, 503251881Speter subpool)); 504251881Speter else 505251881Speter SVN_ERR(print_properties( 506362181Sdim out, truepath, pname, 507251881Speter props, 508251881Speter opt_state->show_inherited_props ? inherited_props : NULL, 509251881Speter print_filenames, 510251881Speter omit_newline, like_proplist, subpool)); 511251881Speter } 512251881Speter 513251881Speter if (opt_state->xml) 514251881Speter SVN_ERR(svn_cl__xml_print_footer("properties", subpool)); 515251881Speter 516251881Speter svn_pool_destroy(subpool); 517251881Speter } 518251881Speter 519289180Speter if (warned) 520289180Speter return svn_error_create(SVN_ERR_BASE, NULL, NULL); 521289180Speter 522251881Speter return SVN_NO_ERROR; 523251881Speter} 524