1/*
2 * propget-cmd.c -- Print properties and values of files/dirs
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#include "svn_hash.h"
31#include "svn_cmdline.h"
32#include "svn_pools.h"
33#include "svn_client.h"
34#include "svn_string.h"
35#include "svn_error_codes.h"
36#include "svn_error.h"
37#include "svn_utf.h"
38#include "svn_sorts.h"
39#include "svn_subst.h"
40#include "svn_dirent_uri.h"
41#include "svn_path.h"
42#include "svn_props.h"
43#include "svn_xml.h"
44#include "cl.h"
45
46#include "private/svn_cmdline_private.h"
47#include "private/svn_opt_private.h"
48#include "private/svn_sorts_private.h"
49#include "svn_private_config.h"
50
51
52/*** Code. ***/
53
54static svn_error_t *
55stream_write(svn_stream_t *out,
56             const char *data,
57             apr_size_t len)
58{
59  apr_size_t write_len = len;
60
61  /* We're gonna bail on an incomplete write here only because we know
62     that this stream is really stdout, which should never be blocking
63     on us. */
64  SVN_ERR(svn_stream_write(out, data, &write_len));
65  if (write_len != len)
66    return svn_error_create(SVN_ERR_STREAM_UNEXPECTED_EOF, NULL,
67                            _("Error writing to stream"));
68  return SVN_NO_ERROR;
69}
70
71
72static svn_error_t *
73print_properties_xml(const char *pname,
74                     apr_hash_t *props,
75                     apr_array_header_t *inherited_props,
76                     apr_pool_t *pool)
77{
78  apr_array_header_t *sorted_props;
79  int i;
80  apr_pool_t *iterpool = NULL;
81  svn_stringbuf_t *sb;
82
83  if (inherited_props && inherited_props->nelts)
84    {
85      iterpool = svn_pool_create(pool);
86
87      for (i = 0; i < inherited_props->nelts; i++)
88        {
89          const char *name_local;
90          svn_prop_inherited_item_t *iprop =
91           APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
92          svn_string_t *propval = apr_hash_this_val(
93            apr_hash_first(pool, iprop->prop_hash));
94
95          sb = NULL;
96          svn_pool_clear(iterpool);
97
98          if (svn_path_is_url(iprop->path_or_url))
99            name_local = iprop->path_or_url;
100          else
101            name_local = svn_dirent_local_style(iprop->path_or_url, iterpool);
102
103          svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target",
104                            "path", name_local, SVN_VA_NULL);
105
106          svn_cmdline__print_xml_prop(&sb, pname, propval, TRUE, iterpool);
107          svn_xml_make_close_tag(&sb, iterpool, "target");
108
109          SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
110        }
111    }
112
113  if (iterpool == NULL)
114    iterpool = svn_pool_create(iterpool);
115
116  sorted_props = svn_sort__hash(props, svn_sort_compare_items_as_paths, pool);
117  for (i = 0; i < sorted_props->nelts; i++)
118    {
119      svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
120      const char *filename = item.key;
121      svn_string_t *propval = item.value;
122
123      sb = NULL;
124      svn_pool_clear(iterpool);
125
126      svn_xml_make_open_tag(&sb, iterpool, svn_xml_normal, "target",
127                        "path", filename, SVN_VA_NULL);
128      svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, iterpool);
129      svn_xml_make_close_tag(&sb, iterpool, "target");
130
131      SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
132    }
133
134  if (iterpool)
135    svn_pool_destroy(iterpool);
136
137  return SVN_NO_ERROR;
138}
139
140/* Print the property PNAME_UTF with the value PROPVAL set on ABSPATH_OR_URL
141   to the stream OUT.
142
143   If INHERITED_PROPERTY is true then the property described is inherited,
144   otherwise it is explicit.
145
146   WC_PATH_PREFIX is the absolute path of the current working directory (and
147   is ignored if ABSPATH_OR_URL is a URL).
148
149   All other arguments are as per print_properties. */
150static svn_error_t *
151print_single_prop(svn_string_t *propval,
152                  const char *target_abspath_or_url,
153                  const char *abspath_or_URL,
154                  const char *wc_path_prefix,
155                  svn_stream_t *out,
156                  const char *pname_utf8,
157                  svn_boolean_t print_filenames,
158                  svn_boolean_t omit_newline,
159                  svn_boolean_t like_proplist,
160                  svn_boolean_t inherited_property,
161                  apr_pool_t *scratch_pool)
162{
163  if (print_filenames)
164    {
165      const char *header;
166
167      /* Print the file name. */
168
169      if (! svn_path_is_url(abspath_or_URL))
170        abspath_or_URL = svn_cl__local_style_skip_ancestor(wc_path_prefix,
171                                                           abspath_or_URL,
172                                                           scratch_pool);
173
174      /* In verbose mode, print exactly same as "proplist" does;
175       * otherwise, print a brief header. */
176      if (inherited_property)
177        {
178          if (like_proplist)
179            {
180              if (! svn_path_is_url(target_abspath_or_url))
181                target_abspath_or_url =
182                  svn_cl__local_style_skip_ancestor(wc_path_prefix,
183                                                    target_abspath_or_url,
184                                                    scratch_pool);
185              header = apr_psprintf(
186                scratch_pool,
187                _("Inherited properties on '%s',\nfrom '%s':\n"),
188                target_abspath_or_url, abspath_or_URL);
189            }
190          else
191            {
192              header = apr_psprintf(scratch_pool, "%s - ", abspath_or_URL);
193            }
194        }
195      else
196        header = apr_psprintf(scratch_pool, like_proplist
197                              ? _("Properties on '%s':\n")
198                              : "%s - ", abspath_or_URL);
199      SVN_ERR(svn_cmdline_cstring_from_utf8(&header, header, scratch_pool));
200      SVN_ERR(svn_subst_translate_cstring2(header, &header,
201                                           APR_EOL_STR,  /* 'native' eol */
202                                           FALSE, /* no repair */
203                                           NULL,  /* no keywords */
204                                           FALSE, /* no expansion */
205                                           scratch_pool));
206      SVN_ERR(stream_write(out, header, strlen(header)));
207    }
208
209  if (like_proplist)
210    {
211      /* Print the property name and value just as "proplist -v" does */
212      apr_hash_t *hash = apr_hash_make(scratch_pool);
213
214      svn_hash_sets(hash, pname_utf8, propval);
215      SVN_ERR(svn_cmdline__print_prop_hash(out, hash, FALSE, scratch_pool));
216    }
217  else
218    {
219      /* If this is a special Subversion property, it is stored as
220         UTF8, so convert to the native format. */
221      if (svn_prop_needs_translation(pname_utf8))
222        SVN_ERR(svn_subst_detranslate_string(&propval, propval,
223                                             TRUE, scratch_pool));
224
225      SVN_ERR(stream_write(out, propval->data, propval->len));
226
227      if (! omit_newline)
228        SVN_ERR(stream_write(out, APR_EOL_STR,
229                             strlen(APR_EOL_STR)));
230    }
231  return SVN_NO_ERROR;
232}
233
234/* Print the properties in PROPS and/or *INHERITED_PROPS to the stream OUT.
235   PROPS is a hash mapping (const char *) path to (svn_string_t) property
236   value.  INHERITED_PROPS is a depth-first ordered array of
237   svn_prop_inherited_item_t * structures.
238
239   TARGET_ABSPATH_OR_URL is the path which inherits INHERITED_PROPS.
240
241   PROPS may be an empty hash, but is never null.  INHERITED_PROPS may be
242   null.
243
244   If IS_URL is true, all paths in PROPS are URLs, else all paths are local
245   paths.
246
247   PNAME_UTF8 is the property name of all the properties.
248
249   If PRINT_FILENAMES is true, print the item's path before each property.
250
251   If OMIT_NEWLINE is true, don't add a newline at the end of each property.
252
253   If LIKE_PROPLIST is true, print everything in a more verbose format
254   like "svn proplist -v" does. */
255static svn_error_t *
256print_properties(svn_stream_t *out,
257                 const char *target_abspath_or_url,
258                 const char *pname_utf8,
259                 apr_hash_t *props,
260                 apr_array_header_t *inherited_props,
261                 svn_boolean_t print_filenames,
262                 svn_boolean_t omit_newline,
263                 svn_boolean_t like_proplist,
264                 apr_pool_t *pool)
265{
266  apr_array_header_t *sorted_props;
267  int i;
268  apr_pool_t *iterpool = svn_pool_create(pool);
269  const char *path_prefix;
270
271  SVN_ERR(svn_dirent_get_absolute(&path_prefix, "", pool));
272
273  if (inherited_props)
274    {
275      svn_pool_clear(iterpool);
276
277      for (i = 0; i < inherited_props->nelts; i++)
278        {
279          svn_prop_inherited_item_t *iprop =
280            APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
281          svn_string_t *propval = apr_hash_this_val(apr_hash_first(pool,
282                                                          iprop->prop_hash));
283          SVN_ERR(print_single_prop(propval, target_abspath_or_url,
284                                    iprop->path_or_url,
285                                    path_prefix, out, pname_utf8,
286                                    print_filenames, omit_newline,
287                                    like_proplist, TRUE, iterpool));
288        }
289    }
290
291  sorted_props = svn_sort__hash(props, svn_sort_compare_items_as_paths, pool);
292  for (i = 0; i < sorted_props->nelts; i++)
293    {
294      svn_sort__item_t item = APR_ARRAY_IDX(sorted_props, i, svn_sort__item_t);
295      const char *filename = item.key;
296      svn_string_t *propval = item.value;
297
298      svn_pool_clear(iterpool);
299
300      SVN_ERR(print_single_prop(propval, target_abspath_or_url, filename,
301                                path_prefix, out, pname_utf8, print_filenames,
302                                omit_newline, like_proplist, FALSE,
303                                iterpool));
304    }
305
306  svn_pool_destroy(iterpool);
307
308  return SVN_NO_ERROR;
309}
310
311
312/* This implements the `svn_opt_subcommand_t' interface. */
313svn_error_t *
314svn_cl__propget(apr_getopt_t *os,
315                void *baton,
316                apr_pool_t *pool)
317{
318  svn_cl__opt_state_t *opt_state = ((svn_cl__cmd_baton_t *) baton)->opt_state;
319  svn_client_ctx_t *ctx = ((svn_cl__cmd_baton_t *) baton)->ctx;
320  const char *pname, *pname_utf8;
321  apr_array_header_t *args, *targets;
322  svn_stream_t *out;
323  svn_boolean_t warned = FALSE;
324
325  if (opt_state->verbose && (opt_state->revprop || opt_state->no_newline
326                             || opt_state->xml))
327    return svn_error_create(SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
328                            _("--verbose cannot be used with --revprop or "
329                              "--no-newline or --xml"));
330
331  /* PNAME is first argument (and PNAME_UTF8 will be a UTF-8 version
332     thereof) */
333  SVN_ERR(svn_opt_parse_num_args(&args, os, 1, pool));
334  pname = APR_ARRAY_IDX(args, 0, const char *);
335  SVN_ERR(svn_utf_cstring_to_utf8(&pname_utf8, pname, pool));
336  if (! svn_prop_name_is_valid(pname_utf8))
337    return svn_error_createf(SVN_ERR_CLIENT_PROPERTY_NAME, NULL,
338                             _("'%s' is not a valid Subversion property name"),
339                             pname_utf8);
340
341  SVN_ERR(svn_cl__args_to_target_array_print_reserved(&targets, os,
342                                                      opt_state->targets,
343                                                      ctx, FALSE, pool));
344
345  /* Add "." if user passed 0 file arguments */
346  svn_opt_push_implicit_dot_target(targets, pool);
347
348  /* Open a stream to stdout. */
349  SVN_ERR(svn_stream_for_stdout(&out, pool));
350
351  if (opt_state->revprop)  /* operate on a revprop */
352    {
353      svn_revnum_t rev;
354      const char *URL;
355      svn_string_t *propval;
356
357      if (opt_state->show_inherited_props)
358        return svn_error_create(
359          SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
360          _("--show-inherited-props can't be used with --revprop"));
361
362      SVN_ERR(svn_cl__revprop_prepare(&opt_state->start_revision, targets,
363                                      &URL, ctx, pool));
364
365      /* Let libsvn_client do the real work. */
366      SVN_ERR(svn_client_revprop_get(pname_utf8, &propval,
367                                     URL, &(opt_state->start_revision),
368                                     &rev, ctx, pool));
369
370      if (propval == NULL)
371        {
372          return svn_error_createf(SVN_ERR_PROPERTY_NOT_FOUND, NULL,
373                                   _("Property '%s' not found on "
374                                     "revision %s"),
375                                   pname_utf8,
376                                   svn_opt__revision_to_string(
377                                     &opt_state->start_revision,
378                                     pool));
379        }
380      else
381        {
382          if (opt_state->xml)
383            {
384              svn_stringbuf_t *sb = NULL;
385              char *revstr = apr_psprintf(pool, "%ld", rev);
386
387              SVN_ERR(svn_cl__xml_print_header("properties", pool));
388
389              svn_xml_make_open_tag(&sb, pool, svn_xml_normal,
390                                    "revprops",
391                                    "rev", revstr, SVN_VA_NULL);
392
393              svn_cmdline__print_xml_prop(&sb, pname_utf8, propval, FALSE,
394                                          pool);
395
396              svn_xml_make_close_tag(&sb, pool, "revprops");
397
398              SVN_ERR(svn_cl__error_checked_fputs(sb->data, stdout));
399              SVN_ERR(svn_cl__xml_print_footer("properties", pool));
400            }
401          else
402            {
403              svn_string_t *printable_val = propval;
404
405              /* If this is a special Subversion property, it is stored as
406                 UTF8 and LF, so convert to the native locale and eol-style. */
407
408              if (svn_prop_needs_translation(pname_utf8))
409                SVN_ERR(svn_subst_detranslate_string(&printable_val, propval,
410                                                     TRUE, pool));
411
412              SVN_ERR(stream_write(out, printable_val->data,
413                                   printable_val->len));
414              if (! opt_state->no_newline)
415                SVN_ERR(stream_write(out, APR_EOL_STR, strlen(APR_EOL_STR)));
416            }
417        }
418    }
419  else  /* operate on a normal, versioned property (not a revprop) */
420    {
421      apr_pool_t *subpool = svn_pool_create(pool);
422      int i;
423
424      if (opt_state->xml)
425        SVN_ERR(svn_cl__xml_print_header("properties", subpool));
426
427      if (opt_state->depth == svn_depth_unknown)
428        opt_state->depth = svn_depth_empty;
429
430      /* No-newline mode only makes sense for a single target.  So make
431         sure we have only a single target, and that we're not being
432         asked to recurse on that target. */
433      if (opt_state->no_newline
434          && ((targets->nelts > 1) || (opt_state->depth != svn_depth_empty)
435              || (opt_state->show_inherited_props)))
436        return svn_error_create
437          (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
438           _("--no-newline is only available for single-target,"
439             " non-recursive propget operations"));
440
441      for (i = 0; i < targets->nelts; i++)
442        {
443          const char *target = APR_ARRAY_IDX(targets, i, const char *);
444          apr_hash_t *props;
445          svn_boolean_t print_filenames;
446          svn_boolean_t omit_newline;
447          svn_boolean_t like_proplist;
448          const char *truepath;
449          svn_opt_revision_t peg_revision;
450          apr_array_header_t *inherited_props;
451
452          svn_pool_clear(subpool);
453          SVN_ERR(svn_cl__check_cancel(ctx->cancel_baton));
454
455          /* Check for a peg revision. */
456          SVN_ERR(svn_opt_parse_path(&peg_revision, &truepath, target,
457                                     subpool));
458
459          if (!svn_path_is_url(truepath))
460            SVN_ERR(svn_dirent_get_absolute(&truepath, truepath, subpool));
461
462          SVN_ERR(svn_client_propget5(
463            &props,
464            opt_state->show_inherited_props ? &inherited_props : NULL,
465            pname_utf8, truepath,
466            &peg_revision,
467            &(opt_state->start_revision),
468            NULL, opt_state->depth,
469            opt_state->changelists, ctx, subpool,
470            subpool));
471
472          /* Any time there is more than one thing to print, or where
473             the path associated with a printed thing is not obvious,
474             we'll print filenames.  That is, unless we've been told
475             not to do so with the --no-newline option. */
476          print_filenames = ((opt_state->depth > svn_depth_empty
477                              || targets->nelts > 1
478                              || apr_hash_count(props) > 1
479                              || opt_state->verbose
480                              || opt_state->show_inherited_props)
481                             && (! opt_state->no_newline));
482          omit_newline = opt_state->no_newline;
483          like_proplist = opt_state->verbose && !opt_state->no_newline;
484
485          /* If there are no properties, and exactly one node was queried,
486             then warn. */
487          if (opt_state->depth == svn_depth_empty
488              && !opt_state->show_inherited_props
489              && apr_hash_count(props) == 0)
490            {
491              svn_error_t *err;
492              err = svn_error_createf(SVN_ERR_PROPERTY_NOT_FOUND, NULL,
493                                      _("Property '%s' not found on '%s'"),
494                                      pname_utf8, target);
495              svn_handle_warning2(stderr, err, "svn: ");
496              svn_error_clear(err);
497              warned = TRUE;
498            }
499
500          if (opt_state->xml)
501            SVN_ERR(print_properties_xml(
502              pname_utf8, props,
503              opt_state->show_inherited_props ? inherited_props : NULL,
504              subpool));
505          else
506            SVN_ERR(print_properties(
507              out, truepath, pname_utf8,
508              props,
509              opt_state->show_inherited_props ? inherited_props : NULL,
510              print_filenames,
511              omit_newline, like_proplist, subpool));
512        }
513
514      if (opt_state->xml)
515        SVN_ERR(svn_cl__xml_print_footer("properties", subpool));
516
517      svn_pool_destroy(subpool);
518    }
519
520  if (warned)
521    return svn_error_create(SVN_ERR_BASE, NULL, NULL);
522
523  return SVN_NO_ERROR;
524}
525