1/*
2 * svnlook.c: Subversion server inspection tool main file.
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#include <assert.h>
25#include <stdlib.h>
26
27#include <apr_general.h>
28#include <apr_pools.h>
29#include <apr_time.h>
30#include <apr_file_io.h>
31
32#define APR_WANT_STDIO
33#define APR_WANT_STRFUNC
34#include <apr_want.h>
35
36#include "svn_hash.h"
37#include "svn_cmdline.h"
38#include "svn_types.h"
39#include "svn_pools.h"
40#include "svn_error.h"
41#include "svn_error_codes.h"
42#include "svn_dirent_uri.h"
43#include "svn_path.h"
44#include "svn_repos.h"
45#include "svn_cache_config.h"
46#include "svn_fs.h"
47#include "svn_time.h"
48#include "svn_utf.h"
49#include "svn_subst.h"
50#include "svn_sorts.h"
51#include "svn_opt.h"
52#include "svn_props.h"
53#include "svn_diff.h"
54#include "svn_version.h"
55#include "svn_xml.h"
56
57#include "private/svn_cmdline_private.h"
58#include "private/svn_diff_private.h"
59#include "private/svn_fspath.h"
60#include "private/svn_io_private.h"
61#include "private/svn_sorts_private.h"
62
63#include "svn_private_config.h"
64
65
66/*** Some convenience macros and types. ***/
67
68
69/* Option handling. */
70
71static svn_opt_subcommand_t
72  subcommand_author,
73  subcommand_cat,
74  subcommand_changed,
75  subcommand_date,
76  subcommand_diff,
77  subcommand_dirschanged,
78  subcommand_filesize,
79  subcommand_help,
80  subcommand_history,
81  subcommand_info,
82  subcommand_lock,
83  subcommand_log,
84  subcommand_pget,
85  subcommand_plist,
86  subcommand_tree,
87  subcommand_uuid,
88  subcommand_youngest;
89
90/* Option codes and descriptions. */
91enum
92  {
93    svnlook__version = SVN_OPT_FIRST_LONGOPT_ID,
94    svnlook__show_ids,
95    svnlook__no_diff_deleted,
96    svnlook__no_diff_added,
97    svnlook__diff_copy_from,
98    svnlook__revprop_opt,
99    svnlook__full_paths,
100    svnlook__copy_info,
101    svnlook__xml_opt,
102    svnlook__ignore_properties,
103    svnlook__properties_only,
104    svnlook__diff_cmd,
105    svnlook__show_inherited_props,
106    svnlook__no_newline
107  };
108
109/*
110 * The entire list must be terminated with an entry of nulls.
111 */
112static const apr_getopt_option_t options_table[] =
113{
114  {NULL,                '?', 0,
115   N_("show help on a subcommand")},
116
117  {"copy-info",         svnlook__copy_info, 0,
118   N_("show details for copies")},
119
120  {"diff-copy-from",    svnlook__diff_copy_from, 0,
121   N_("print differences against the copy source")},
122
123  {"full-paths",        svnlook__full_paths, 0,
124   N_("show full paths instead of indenting them")},
125
126  {"help",              'h', 0,
127   N_("show help on a subcommand")},
128
129  {"limit",             'l', 1,
130   N_("maximum number of history entries")},
131
132  {"no-diff-added",     svnlook__no_diff_added, 0,
133   N_("do not print differences for added files")},
134
135  {"no-diff-deleted",   svnlook__no_diff_deleted, 0,
136   N_("do not print differences for deleted files")},
137
138  {"diff-cmd",          svnlook__diff_cmd, 1,
139   N_("use ARG as diff command")},
140
141  {"ignore-properties",   svnlook__ignore_properties, 0,
142   N_("ignore properties during the operation")},
143
144  {"properties-only",   svnlook__properties_only, 0,
145   N_("show only properties during the operation")},
146
147  {"memory-cache-size", 'M', 1,
148   N_("size of the extra in-memory cache in MB used to\n"
149      "                             "
150      "minimize redundant operations. Default: 16.\n"
151      "                             "
152      "[used for FSFS repositories only]")},
153
154  {"no-newline",        svnlook__no_newline, 0,
155   N_("do not output the trailing newline")},
156
157  {"non-recursive",     'N', 0,
158   N_("operate on single directory only")},
159
160  {"revision",          'r', 1,
161   N_("specify revision number ARG")},
162
163  {"revprop",           svnlook__revprop_opt, 0,
164   N_("operate on a revision property (use with -r or -t)")},
165
166  {"show-ids",          svnlook__show_ids, 0,
167   N_("show node revision ids for each path")},
168
169  {"show-inherited-props", svnlook__show_inherited_props, 0,
170   N_("show path's inherited properties")},
171
172  {"transaction",       't', 1,
173   N_("specify transaction name ARG")},
174
175  {"verbose",           'v', 0,
176   N_("be verbose")},
177
178  {"version",           svnlook__version, 0,
179   N_("show program version information")},
180
181  {"xml",               svnlook__xml_opt, 0,
182   N_("output in XML")},
183
184  {"extensions",        'x', 1,
185   N_("Specify differencing options for external diff or\n"
186      "                             "
187      "internal diff. Default: '-u'. Options are\n"
188      "                             "
189      "separated by spaces. Internal diff takes:\n"
190      "                             "
191      "  -u, --unified: Show 3 lines of unified context\n"
192      "                             "
193      "  -b, --ignore-space-change: Ignore changes in\n"
194      "                             "
195      "    amount of white space\n"
196      "                             "
197      "  -w, --ignore-all-space: Ignore all white space\n"
198      "                             "
199      "  --ignore-eol-style: Ignore changes in EOL style\n"
200      "                             "
201      "  -U ARG, --context ARG: Show ARG lines of context\n"
202      "                             "
203      "  -p, --show-c-function: Show C function name")},
204
205  {"quiet",             'q', 0,
206   N_("no progress (only errors) to stderr")},
207
208  {0,                   0, 0, 0}
209};
210
211
212/* Array of available subcommands.
213 * The entire list must be terminated with an entry of nulls.
214 */
215static const svn_opt_subcommand_desc3_t cmd_table[] =
216{
217  {"author", subcommand_author, {0}, {N_(
218      "usage: svnlook author REPOS_PATH\n"
219      "\n"), N_(
220      "Print the author.\n"
221   )},
222   {'r', 't'} },
223
224  {"cat", subcommand_cat, {0}, {N_(
225      "usage: svnlook cat REPOS_PATH FILE_PATH\n"
226      "\n"), N_(
227      "Print the contents of a file.  Leading '/' on FILE_PATH is optional.\n"
228   )},
229   {'r', 't'} },
230
231  {"changed", subcommand_changed, {0}, {N_(
232      "usage: svnlook changed REPOS_PATH\n"
233      "\n"), N_(
234      "Print the paths that were changed.\n"
235   )},
236   {'r', 't', svnlook__copy_info} },
237
238  {"date", subcommand_date, {0}, {N_(
239      "usage: svnlook date REPOS_PATH\n"
240      "\n"), N_(
241      "Print the datestamp.\n"
242   )},
243   {'r', 't'} },
244
245  {"diff", subcommand_diff, {0}, {N_(
246      "usage: svnlook diff REPOS_PATH\n"
247      "\n"), N_(
248      "Print GNU-style diffs of changed files and properties.\n"
249   )},
250   {'r', 't', svnlook__no_diff_deleted, svnlook__no_diff_added,
251    svnlook__diff_copy_from, svnlook__diff_cmd, 'x',
252    svnlook__ignore_properties, svnlook__properties_only} },
253
254  {"dirs-changed", subcommand_dirschanged, {0}, {N_(
255      "usage: svnlook dirs-changed REPOS_PATH\n"
256      "\n"), N_(
257      "Print the directories that were themselves changed (property edits)\n"
258      "or whose file children were changed.\n"
259   )},
260   {'r', 't'} },
261
262  {"filesize", subcommand_filesize, {0}, {N_(
263      "usage: svnlook filesize REPOS_PATH PATH_IN_REPOS\n"
264      "\n"), N_(
265      "Print the size (in bytes) of the file located at PATH_IN_REPOS as\n"
266      "it is represented in the repository.\n"
267   )},
268   {'r', 't'} },
269
270  {"help", subcommand_help, {"?", "h"}, {N_(
271      "usage: svnlook help [SUBCOMMAND...]\n"
272      "\n"), N_(
273      "Describe the usage of this program or its subcommands.\n"
274   )},
275   {0} },
276
277  {"history", subcommand_history, {0}, {N_(
278      "usage: svnlook history REPOS_PATH [PATH_IN_REPOS]\n"
279      "\n"), N_(
280      "Print information about the history of a path in the repository (or\n"
281      "the root directory if no path is supplied).\n"
282   )},
283   {'r', svnlook__show_ids, 'l'} },
284
285  {"info", subcommand_info, {0}, {N_(
286      "usage: svnlook info REPOS_PATH\n"
287      "\n"), N_(
288      "Print the author, datestamp, log message size, and log message.\n"
289   )},
290   {'r', 't'} },
291
292  {"lock", subcommand_lock, {0}, {N_(
293      "usage: svnlook lock REPOS_PATH PATH_IN_REPOS\n"
294      "\n"), N_(
295      "If a lock exists on a path in the repository, describe it.\n"
296   )},
297   {0} },
298
299  {"log", subcommand_log, {0}, {N_(
300      "usage: svnlook log REPOS_PATH\n"
301      "\n"), N_(
302      "Print the log message.\n"
303   )},
304   {'r', 't'} },
305
306  {"propget", subcommand_pget, {"pget", "pg"}, {N_(
307      "usage: 1. svnlook propget REPOS_PATH PROPNAME PATH_IN_REPOS\n"
308      "                    "
309      /* The line above is actually needed, so do NOT delete it! */
310      "       2. svnlook propget --revprop REPOS_PATH PROPNAME\n"
311      "\n"), N_(
312      "Print the raw value of a property on a path in the repository.\n"
313      "With --revprop, print the raw value of a revision property.\n"
314   )},
315   {'r', 't', 'v', svnlook__revprop_opt, svnlook__show_inherited_props} },
316
317  {"proplist", subcommand_plist, {"plist", "pl"}, {N_(
318      "usage: 1. svnlook proplist REPOS_PATH PATH_IN_REPOS\n"
319      "                      "
320      /* The line above is actually needed, so do NOT delete it! */
321      "       2. svnlook proplist --revprop REPOS_PATH\n"
322      "\n"), N_(
323      "List the properties of a path in the repository, or\n"
324      "with the --revprop option, revision properties.\n"
325      "With -v, show the property values too.\n"
326   )},
327   {'r', 't', 'v', svnlook__revprop_opt, svnlook__xml_opt,
328    svnlook__show_inherited_props} },
329
330  {"tree", subcommand_tree, {0}, {N_(
331      "usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n"
332      "\n"), N_(
333      "Print the tree, starting at PATH_IN_REPOS (if supplied, at the root\n"
334      "of the tree otherwise), optionally showing node revision ids.\n"
335   )},
336   {'r', 't', 'N', svnlook__show_ids, svnlook__full_paths, 'M'} },
337
338  {"uuid", subcommand_uuid, {0}, {N_(
339      "usage: svnlook uuid REPOS_PATH\n"
340      "\n"), N_(
341      "Print the repository's UUID.\n"
342   )},
343   {0} },
344
345  {"youngest", subcommand_youngest, {0}, {N_(
346      "usage: svnlook youngest REPOS_PATH\n"
347      "\n"), N_(
348      "Print the youngest revision number.\n"
349   )},
350   {svnlook__no_newline} },
351
352  { NULL, NULL, {0}, {NULL}, {0} }
353};
354
355
356/* Baton for passing option/argument state to a subcommand function. */
357struct svnlook_opt_state
358{
359  const char *repos_path;  /* 'arg0' is always the path to the repository. */
360  const char *arg1;        /* Usually an fs path, a propname, or NULL. */
361  const char *arg2;        /* Usually an fs path or NULL. */
362  svn_revnum_t rev;
363  const char *txn;
364  svn_boolean_t version;          /* --version */
365  svn_boolean_t show_ids;         /* --show-ids */
366  apr_size_t limit;               /* --limit */
367  svn_boolean_t help;             /* --help */
368  svn_boolean_t no_diff_deleted;  /* --no-diff-deleted */
369  svn_boolean_t no_diff_added;    /* --no-diff-added */
370  svn_boolean_t diff_copy_from;   /* --diff-copy-from */
371  svn_boolean_t verbose;          /* --verbose */
372  svn_boolean_t revprop;          /* --revprop */
373  svn_boolean_t full_paths;       /* --full-paths */
374  svn_boolean_t copy_info;        /* --copy-info */
375  svn_boolean_t non_recursive;    /* --non-recursive */
376  svn_boolean_t xml;              /* --xml */
377  const char *extensions;         /* diff extension args (UTF-8!) */
378  svn_boolean_t quiet;            /* --quiet */
379  svn_boolean_t ignore_properties;  /* --ignore_properties */
380  svn_boolean_t properties_only;    /* --properties-only */
381  const char *diff_cmd;           /* --diff-cmd */
382  svn_boolean_t show_inherited_props; /*  --show-inherited-props */
383  svn_boolean_t no_newline;       /* --no-newline */
384  apr_uint64_t memory_cache_size; /* --memory-cache-size */
385};
386
387
388typedef struct svnlook_ctxt_t
389{
390  svn_repos_t *repos;
391  svn_fs_t *fs;
392  svn_boolean_t is_revision;
393  svn_boolean_t show_ids;
394  apr_size_t limit;
395  svn_boolean_t no_diff_deleted;
396  svn_boolean_t no_diff_added;
397  svn_boolean_t diff_copy_from;
398  svn_boolean_t full_paths;
399  svn_boolean_t copy_info;
400  svn_revnum_t rev_id;
401  svn_fs_txn_t *txn;
402  const char *txn_name /* UTF-8! */;
403  const apr_array_header_t *diff_options;
404  svn_boolean_t ignore_properties;
405  svn_boolean_t properties_only;
406  const char *diff_cmd;
407
408} svnlook_ctxt_t;
409
410
411/*** Helper functions. ***/
412
413static svn_cancel_func_t check_cancel = NULL;
414
415/* Version compatibility check */
416static svn_error_t *
417check_lib_versions(void)
418{
419  static const svn_version_checklist_t checklist[] =
420    {
421      { "svn_subr",  svn_subr_version },
422      { "svn_repos", svn_repos_version },
423      { "svn_fs",    svn_fs_version },
424      { "svn_delta", svn_delta_version },
425      { "svn_diff",  svn_diff_version },
426      { NULL, NULL }
427    };
428  SVN_VERSION_DEFINE(my_version);
429
430  return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
431}
432
433
434/* Get revision or transaction property PROP_NAME for the revision or
435   transaction specified in C, allocating in in POOL and placing it in
436   *PROP_VALUE. */
437static svn_error_t *
438get_property(svn_string_t **prop_value,
439             svnlook_ctxt_t *c,
440             const char *prop_name,
441             apr_pool_t *pool)
442{
443  svn_string_t *raw_value;
444
445  /* Fetch transaction property... */
446  if (! c->is_revision)
447    SVN_ERR(svn_fs_txn_prop(&raw_value, c->txn, prop_name, pool));
448
449  /* ...or revision property -- it's your call. */
450  else
451    SVN_ERR(svn_fs_revision_prop2(&raw_value, c->fs, c->rev_id,
452                                  prop_name, TRUE, pool, pool));
453
454  *prop_value = raw_value;
455
456  return SVN_NO_ERROR;
457}
458
459
460static svn_error_t *
461get_root(svn_fs_root_t **root,
462         svnlook_ctxt_t *c,
463         apr_pool_t *pool)
464{
465  /* Open up the appropriate root (revision or transaction). */
466  if (c->is_revision)
467    {
468      /* If we didn't get a valid revision number, we'll look at the
469         youngest revision. */
470      if (! SVN_IS_VALID_REVNUM(c->rev_id))
471        SVN_ERR(svn_fs_youngest_rev(&(c->rev_id), c->fs, pool));
472
473      SVN_ERR(svn_fs_revision_root(root, c->fs, c->rev_id, pool));
474    }
475  else
476    {
477      SVN_ERR(svn_fs_txn_root(root, c->txn, pool));
478    }
479
480  return SVN_NO_ERROR;
481}
482
483
484
485/*** Tree Routines ***/
486
487/* Generate a generic delta tree. */
488static svn_error_t *
489generate_delta_tree(svn_repos_node_t **tree,
490                    svn_repos_t *repos,
491                    svn_fs_root_t *root,
492                    svn_revnum_t base_rev,
493                    apr_pool_t *pool)
494{
495  svn_fs_root_t *base_root;
496  const svn_delta_editor_t *editor;
497  void *edit_baton;
498  apr_pool_t *edit_pool = svn_pool_create(pool);
499  svn_fs_t *fs = svn_repos_fs(repos);
500
501  /* Get the base root. */
502  SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, pool));
503
504  /* Request our editor. */
505  SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos,
506                                base_root, root, pool, edit_pool));
507
508  /* Drive our editor. */
509  SVN_ERR(svn_repos_replay2(root, "", SVN_INVALID_REVNUM, TRUE,
510                            editor, edit_baton, NULL, NULL, edit_pool));
511
512  /* Return the tree we just built. */
513  *tree = svn_repos_node_from_baton(edit_baton);
514  svn_pool_destroy(edit_pool);
515  return SVN_NO_ERROR;
516}
517
518
519
520/*** Tree Printing Routines ***/
521
522/* Recursively print only directory nodes that either a) have property
523   mods, or b) contains files that have changed, or c) has added or deleted
524   children.  NODE is the root node of the tree delta, so every node in it
525   is either changed or is a directory with a changed node somewhere in the
526   subtree below it.
527 */
528static svn_error_t *
529print_dirs_changed_tree(svn_repos_node_t *node,
530                        const char *path /* UTF-8! */,
531                        apr_pool_t *pool)
532{
533  svn_repos_node_t *tmp_node;
534  svn_boolean_t print_me = FALSE;
535  const char *full_path;
536  apr_pool_t *iterpool;
537
538  SVN_ERR(check_cancel(NULL));
539
540  if (! node)
541    return SVN_NO_ERROR;
542
543  /* Not a directory?  We're not interested. */
544  if (node->kind != svn_node_dir)
545    return SVN_NO_ERROR;
546
547  /* Got prop mods?  Excellent. */
548  if (node->prop_mod)
549    print_me = TRUE;
550
551  /* Fly through the list of children, checking for modified files. */
552  tmp_node = node->child;
553  while (tmp_node && (! print_me))
554    {
555      if ((tmp_node->kind == svn_node_file)
556           || (tmp_node->action == 'A')
557           || (tmp_node->action == 'D'))
558        {
559          print_me = TRUE;
560        }
561      tmp_node = tmp_node->sibling;
562    }
563
564  /* Print the node if it qualifies. */
565  if (print_me)
566    {
567      SVN_ERR(svn_cmdline_printf(pool, "%s/\n", path));
568    }
569
570  /* Return here if the node has no children. */
571  tmp_node = node->child;
572  if (! tmp_node)
573    return SVN_NO_ERROR;
574
575  /* Recursively handle the node's children. */
576  iterpool = svn_pool_create(pool);
577  while (tmp_node)
578    {
579      svn_pool_clear(iterpool);
580      full_path = svn_dirent_join(path, tmp_node->name, iterpool);
581      SVN_ERR(print_dirs_changed_tree(tmp_node, full_path, iterpool));
582      tmp_node = tmp_node->sibling;
583    }
584  svn_pool_destroy(iterpool);
585
586  return SVN_NO_ERROR;
587}
588
589
590/* Recursively print all nodes in the tree that have been modified
591   (do not include directories affected only by "bubble-up"). */
592static svn_error_t *
593print_changed_tree(svn_repos_node_t *node,
594                   const char *path /* UTF-8! */,
595                   svn_boolean_t copy_info,
596                   apr_pool_t *pool)
597{
598  const char *full_path;
599  char status[4] = "_  ";
600  svn_boolean_t print_me = TRUE;
601  apr_pool_t *iterpool;
602
603  SVN_ERR(check_cancel(NULL));
604
605  if (! node)
606    return SVN_NO_ERROR;
607
608  /* Print the node. */
609  if (node->action == 'A')
610    {
611      status[0] = 'A';
612      if (copy_info && node->copyfrom_path)
613        status[2] = '+';
614    }
615  else if (node->action == 'D')
616    status[0] = 'D';
617  else if (node->action == 'R')
618    {
619      if ((! node->text_mod) && (! node->prop_mod))
620        print_me = FALSE;
621      if (node->text_mod)
622        status[0] = 'U';
623      if (node->prop_mod)
624        status[1] = 'U';
625    }
626  else
627    print_me = FALSE;
628
629  /* Print this node unless told to skip it. */
630  if (print_me)
631    {
632      SVN_ERR(svn_cmdline_printf(pool, "%s %s%s\n",
633                                 status,
634                                 path,
635                                 node->kind == svn_node_dir ? "/" : ""));
636      if (copy_info && node->copyfrom_path)
637        /* Remove the leading slash from the copyfrom path for consistency
638           with the rest of the output. */
639        SVN_ERR(svn_cmdline_printf(pool, "    (from %s%s:r%ld)\n",
640                                   (node->copyfrom_path[0] == '/'
641                                    ? node->copyfrom_path + 1
642                                    : node->copyfrom_path),
643                                   (node->kind == svn_node_dir ? "/" : ""),
644                                   node->copyfrom_rev));
645    }
646
647  /* Return here if the node has no children. */
648  node = node->child;
649  if (! node)
650    return SVN_NO_ERROR;
651
652  /* Recursively handle the node's children. */
653  iterpool = svn_pool_create(pool);
654  while (node)
655    {
656      svn_pool_clear(iterpool);
657      full_path = svn_dirent_join(path, node->name, iterpool);
658      SVN_ERR(print_changed_tree(node, full_path, copy_info, iterpool));
659      node = node->sibling;
660    }
661  svn_pool_destroy(iterpool);
662
663  return SVN_NO_ERROR;
664}
665
666
667static svn_error_t *
668dump_contents(svn_stream_t *stream,
669              svn_fs_root_t *root,
670              const char *path /* UTF-8! */,
671              apr_pool_t *pool)
672{
673  if (root == NULL)
674    SVN_ERR(svn_stream_close(stream));  /* leave an empty file */
675  else
676    {
677      svn_stream_t *contents;
678
679      /* Grab the contents and copy them into the given stream. */
680      SVN_ERR(svn_fs_file_contents(&contents, root, path, pool));
681      SVN_ERR(svn_stream_copy3(contents, stream, NULL, NULL, pool));
682    }
683
684  return SVN_NO_ERROR;
685}
686
687
688/* Prepare temporary files *TMPFILE1 and *TMPFILE2 for diffing
689   PATH1@ROOT1 versus PATH2@ROOT2.  If either ROOT1 or ROOT2 is NULL,
690   the temporary file for its path/root will be an empty one.
691   Otherwise, its temporary file will contain the contents of that
692   path/root in the repository.
693
694   An exception to this is when either path/root has an svn:mime-type
695   property set on it which indicates that the file contains
696   non-textual data -- in this case, the *IS_BINARY flag is set and no
697   temporary files are created.
698
699   TMPFILE1 and TMPFILE2 will be removed when RESULT_POOL is destroyed.
700 */
701static svn_error_t *
702prepare_tmpfiles(const char **tmpfile1,
703                 const char **tmpfile2,
704                 svn_boolean_t *is_binary,
705                 svn_fs_root_t *root1,
706                 const char *path1,
707                 svn_fs_root_t *root2,
708                 const char *path2,
709                 apr_pool_t *result_pool,
710                 apr_pool_t *scratch_pool)
711{
712  svn_string_t *mimetype;
713  svn_stream_t *stream;
714
715  /* Init the return values. */
716  *tmpfile1 = NULL;
717  *tmpfile2 = NULL;
718  *is_binary = FALSE;
719
720  assert(path1 && path2);
721
722  /* Check for binary mimetypes.  If either file has a binary
723     mimetype, get outta here.  */
724  if (root1)
725    {
726      SVN_ERR(svn_fs_node_prop(&mimetype, root1, path1,
727                               SVN_PROP_MIME_TYPE, scratch_pool));
728      if (mimetype && svn_mime_type_is_binary(mimetype->data))
729        {
730          *is_binary = TRUE;
731          return SVN_NO_ERROR;
732        }
733    }
734  if (root2)
735    {
736      SVN_ERR(svn_fs_node_prop(&mimetype, root2, path2,
737                               SVN_PROP_MIME_TYPE, scratch_pool));
738      if (mimetype && svn_mime_type_is_binary(mimetype->data))
739        {
740          *is_binary = TRUE;
741          return SVN_NO_ERROR;
742        }
743    }
744
745  /* Now, prepare the two temporary files, each of which will either
746     be empty, or will have real contents.  */
747  SVN_ERR(svn_stream_open_unique(&stream, tmpfile1, NULL,
748                                 svn_io_file_del_on_pool_cleanup,
749                                 result_pool, scratch_pool));
750  SVN_ERR(dump_contents(stream, root1, path1, scratch_pool));
751
752  SVN_ERR(svn_stream_open_unique(&stream, tmpfile2, NULL,
753                                 svn_io_file_del_on_pool_cleanup,
754                                 result_pool, scratch_pool));
755  SVN_ERR(dump_contents(stream, root2, path2, scratch_pool));
756
757  return SVN_NO_ERROR;
758}
759
760
761/* Generate a diff label for PATH in ROOT, allocating in POOL.
762   ROOT may be NULL, in which case revision 0 is used. */
763static svn_error_t *
764generate_label(const char **label,
765               svn_fs_root_t *root,
766               const char *path,
767               apr_pool_t *pool)
768{
769  svn_string_t *date;
770  const char *datestr;
771  const char *name = NULL;
772  svn_revnum_t rev = SVN_INVALID_REVNUM;
773
774  if (root)
775    {
776      svn_fs_t *fs = svn_fs_root_fs(root);
777      if (svn_fs_is_revision_root(root))
778        {
779          rev = svn_fs_revision_root_revision(root);
780          SVN_ERR(svn_fs_revision_prop2(&date, fs, rev,
781                                        SVN_PROP_REVISION_DATE, TRUE,
782                                        pool, pool));
783        }
784      else
785        {
786          svn_fs_txn_t *txn;
787          name = svn_fs_txn_root_name(root, pool);
788          SVN_ERR(svn_fs_open_txn(&txn, fs, name, pool));
789          SVN_ERR(svn_fs_txn_prop(&date, txn, SVN_PROP_REVISION_DATE, pool));
790        }
791    }
792  else
793    {
794      rev = 0;
795      date = NULL;
796    }
797
798  if (date)
799    datestr = apr_psprintf(pool, "%.10s %.8s UTC", date->data, date->data + 11);
800  else
801    datestr = "                       ";
802
803  if (name)
804    *label = apr_psprintf(pool, "%s\t%s (txn %s)",
805                          path, datestr, name);
806  else
807    *label = apr_psprintf(pool, "%s\t%s (rev %ld)",
808                          path, datestr, rev);
809  return SVN_NO_ERROR;
810}
811
812
813/* Helper function to display differences in properties of a file */
814static svn_error_t *
815display_prop_diffs(svn_stream_t *outstream,
816                   const char *encoding,
817                   const apr_array_header_t *propchanges,
818                   apr_hash_t *original_props,
819                   const char *path,
820                   apr_pool_t *pool)
821{
822
823  SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
824                                      _("%sProperty changes on: %s%s"),
825                                      APR_EOL_STR,
826                                      path,
827                                      APR_EOL_STR));
828
829  SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
830                                      SVN_DIFF__UNDER_STRING APR_EOL_STR));
831
832  SVN_ERR(check_cancel(NULL));
833
834  SVN_ERR(svn_diff__display_prop_diffs(
835            outstream, encoding, propchanges, original_props,
836            FALSE /* pretty_print_mergeinfo */,
837            -1 /* context_size */,
838            check_cancel, NULL, pool));
839
840  return SVN_NO_ERROR;
841}
842
843
844/* Recursively print all nodes in the tree that have been modified
845   (do not include directories affected only by "bubble-up"). */
846static svn_error_t *
847print_diff_tree(svn_stream_t *out_stream,
848                const char *encoding,
849                svn_fs_root_t *root,
850                svn_fs_root_t *base_root,
851                svn_repos_node_t *node,
852                const char *path /* UTF-8! */,
853                const char *base_path /* UTF-8! */,
854                const svnlook_ctxt_t *c,
855                apr_pool_t *pool)
856{
857  const char *orig_path = NULL, *new_path = NULL;
858  svn_boolean_t do_diff = FALSE;
859  svn_boolean_t orig_empty = FALSE;
860  svn_boolean_t is_copy = FALSE;
861  svn_boolean_t binary = FALSE;
862  svn_boolean_t diff_header_printed = FALSE;
863  apr_pool_t *iterpool;
864  svn_stringbuf_t *header;
865
866  SVN_ERR(check_cancel(NULL));
867
868  if (! node)
869    return SVN_NO_ERROR;
870
871  header = svn_stringbuf_create_empty(pool);
872
873  /* Print copyfrom history for the top node of a copied tree. */
874  if ((SVN_IS_VALID_REVNUM(node->copyfrom_rev))
875      && (node->copyfrom_path != NULL))
876    {
877      /* This is ... a copy. */
878      is_copy = TRUE;
879
880      /* Propagate the new base.  Copyfrom paths usually start with a
881         slash; we remove it for consistency with the target path.
882         ### Yes, it would be *much* better for something in the path
883             library to be taking care of this! */
884      if (node->copyfrom_path[0] == '/')
885        base_path = apr_pstrdup(pool, node->copyfrom_path + 1);
886      else
887        base_path = apr_pstrdup(pool, node->copyfrom_path);
888
889      svn_stringbuf_appendcstr
890        (header,
891         apr_psprintf(pool, _("Copied: %s (from rev %ld, %s)\n"),
892                      path, node->copyfrom_rev, base_path));
893
894      SVN_ERR(svn_fs_revision_root(&base_root,
895                                   svn_fs_root_fs(base_root),
896                                   node->copyfrom_rev, pool));
897    }
898
899  /*** First, we'll just print file content diffs. ***/
900  if (node->kind == svn_node_file)
901    {
902      /* Here's the generalized way we do our diffs:
903
904         - First, we'll check for svn:mime-type properties on the old
905           and new files.  If either has such a property, and it
906           represents a binary type, we won't actually be doing a real
907           diff.
908
909         - Second, dump the contents of the new version of the file
910           into the temporary directory.
911
912         - Then, dump the contents of the old version of the file into
913           the temporary directory.
914
915         - Next, we run 'diff', passing the repository paths as the
916           labels.
917
918         - Finally, we delete the temporary files.  */
919      if (node->action == 'R' && node->text_mod)
920        {
921          do_diff = TRUE;
922          SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
923                                   base_root, base_path, root, path,
924                                   pool, pool));
925        }
926      else if (c->diff_copy_from && node->action == 'A' && is_copy)
927        {
928          if (node->text_mod)
929            {
930              do_diff = TRUE;
931              SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
932                                       base_root, base_path, root, path,
933                                       pool, pool));
934            }
935        }
936      else if (! c->no_diff_added && node->action == 'A')
937        {
938          do_diff = TRUE;
939          orig_empty = TRUE;
940          SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
941                                   NULL, base_path, root, path,
942                                   pool, pool));
943        }
944      else if (! c->no_diff_deleted && node->action == 'D')
945        {
946          do_diff = TRUE;
947          SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
948                                   base_root, base_path, NULL, path,
949                                   pool, pool));
950        }
951
952      /* The header for the copy case has already been created, and we don't
953         want a header here for files with only property modifications. */
954      if (header->len == 0
955          && (node->action != 'R' || node->text_mod))
956        {
957          svn_stringbuf_appendcstr
958            (header, apr_psprintf(pool, "%s: %s\n",
959                                  ((node->action == 'A') ? _("Added") :
960                                   ((node->action == 'D') ? _("Deleted") :
961                                    ((node->action == 'R') ? _("Modified")
962                                     : _("Index")))),
963                                  path));
964        }
965    }
966
967  if (do_diff && (! c->properties_only))
968    {
969      svn_stringbuf_appendcstr(header, SVN_DIFF__EQUAL_STRING "\n");
970
971      if (binary)
972        {
973          svn_stringbuf_appendcstr(header, _("(Binary files differ)\n\n"));
974          SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
975                                              "%s", header->data));
976        }
977      else
978        {
979          if (c->diff_cmd)
980            {
981              apr_file_t *outfile;
982              apr_file_t *errfile;
983              const char *outfilename;
984              const char *errfilename;
985              svn_stream_t *stream;
986              svn_stream_t *err_stream;
987              const char **diff_cmd_argv;
988              int diff_cmd_argc;
989              int exitcode;
990              const char *orig_label;
991              const char *new_label;
992
993              diff_cmd_argv = NULL;
994              diff_cmd_argc = c->diff_options->nelts;
995              if (diff_cmd_argc)
996                {
997                  int i;
998                  diff_cmd_argv = apr_palloc(pool,
999                                             diff_cmd_argc * sizeof(char *));
1000                  for (i = 0; i < diff_cmd_argc; i++)
1001                    SVN_ERR(svn_utf_cstring_to_utf8(&diff_cmd_argv[i],
1002                              APR_ARRAY_IDX(c->diff_options, i, const char *),
1003                              pool));
1004                }
1005
1006              /* Print diff header. */
1007              SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1008                                                  "%s", header->data));
1009
1010              if (orig_empty)
1011                SVN_ERR(generate_label(&orig_label, NULL, path, pool));
1012              else
1013                SVN_ERR(generate_label(&orig_label, base_root,
1014                                       base_path, pool));
1015              SVN_ERR(generate_label(&new_label, root, path, pool));
1016
1017              /* We deal in streams, but svn_io_run_diff2() deals in file
1018                 handles, so we may need to make temporary files and then
1019                 copy the contents to our stream. */
1020              outfile = svn_stream__aprfile(out_stream);
1021              if (outfile)
1022                outfilename = NULL;
1023              else
1024                SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
1025                          svn_io_file_del_on_pool_cleanup, pool, pool));
1026              SVN_ERR(svn_stream_for_stderr(&err_stream, pool));
1027              errfile = svn_stream__aprfile(err_stream);
1028              if (errfile)
1029                errfilename = NULL;
1030              else
1031                SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
1032                          svn_io_file_del_on_pool_cleanup, pool, pool));
1033
1034              SVN_ERR(svn_io_run_diff2(".",
1035                                       diff_cmd_argv,
1036                                       diff_cmd_argc,
1037                                       orig_label, new_label,
1038                                       orig_path, new_path,
1039                                       &exitcode, outfile, errfile,
1040                                       c->diff_cmd, pool));
1041
1042              /* Now, open and copy our files to our output streams. */
1043              if (outfilename)
1044                {
1045                  SVN_ERR(svn_io_file_close(outfile, pool));
1046                  SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
1047                                                   pool, pool));
1048                  SVN_ERR(svn_stream_copy3(stream,
1049                                           svn_stream_disown(out_stream, pool),
1050                                           NULL, NULL, pool));
1051                }
1052              if (errfilename)
1053                {
1054                  SVN_ERR(svn_io_file_close(errfile, pool));
1055                  SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
1056                                                   pool, pool));
1057                  SVN_ERR(svn_stream_copy3(stream,
1058                                           svn_stream_disown(err_stream, pool),
1059                                           NULL, NULL, pool));
1060                }
1061
1062              SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1063                                                  "\n"));
1064              diff_header_printed = TRUE;
1065            }
1066          else
1067            {
1068              svn_diff_t *diff;
1069              svn_diff_file_options_t *opts = svn_diff_file_options_create(pool);
1070
1071              if (c->diff_options)
1072                SVN_ERR(svn_diff_file_options_parse(opts, c->diff_options, pool));
1073
1074              SVN_ERR(svn_diff_file_diff_2(&diff, orig_path,
1075                                           new_path, opts, pool));
1076
1077              if (svn_diff_contains_diffs(diff))
1078                {
1079                  const char *orig_label, *new_label;
1080
1081                  /* Print diff header. */
1082                  SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1083                                                      "%s", header->data));
1084
1085                  if (orig_empty)
1086                    SVN_ERR(generate_label(&orig_label, NULL, path, pool));
1087                  else
1088                    SVN_ERR(generate_label(&orig_label, base_root,
1089                                           base_path, pool));
1090                  SVN_ERR(generate_label(&new_label, root, path, pool));
1091                  SVN_ERR(svn_diff_file_output_unified4(
1092                           out_stream, diff, orig_path, new_path,
1093                           orig_label, new_label,
1094                           svn_cmdline_output_encoding(pool), NULL,
1095                           opts->show_c_function, opts->context_size,
1096                           check_cancel, NULL, pool));
1097                  SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1098                                                      "\n"));
1099                  diff_header_printed = TRUE;
1100                }
1101              else if (! node->prop_mod &&
1102                      ((! c->no_diff_added && node->action == 'A') ||
1103                       (! c->no_diff_deleted && node->action == 'D')))
1104                {
1105                  /* There was an empty file added or deleted in this revision.
1106                   * We can't print a diff, but we can at least print
1107                   * a diff header since we know what happened to this file. */
1108                  SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1109                                                      "%s", header->data));
1110                }
1111            }
1112        }
1113    }
1114
1115  /*** Now handle property diffs ***/
1116  if ((node->prop_mod) && (node->action != 'D') && (! c->ignore_properties))
1117    {
1118      apr_hash_t *local_proptable;
1119      apr_hash_t *base_proptable;
1120      apr_array_header_t *propchanges, *props;
1121
1122      SVN_ERR(svn_fs_node_proplist(&local_proptable, root, path, pool));
1123      if (c->diff_copy_from && node->action == 'A' && is_copy)
1124        SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1125                                     base_path, pool));
1126      else if (node->action == 'A')
1127        base_proptable = apr_hash_make(pool);
1128      else  /* node->action == 'R' */
1129        SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1130                                     base_path, pool));
1131      SVN_ERR(svn_prop_diffs(&propchanges, local_proptable,
1132                             base_proptable, pool));
1133      SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, pool));
1134      if (props->nelts > 0)
1135        {
1136          /* We print a diff header for the case when we only have property
1137           * mods. */
1138          if (! diff_header_printed)
1139            {
1140              const char *orig_label, *new_label;
1141
1142              SVN_ERR(generate_label(&orig_label, base_root, base_path,
1143                                     pool));
1144              SVN_ERR(generate_label(&new_label, root, path, pool));
1145
1146              SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1147                                                  "Index: %s\n", path));
1148              SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1149                                                  SVN_DIFF__EQUAL_STRING "\n"));
1150              /* --- <label1>
1151               * +++ <label2> */
1152              SVN_ERR(svn_diff__unidiff_write_header(
1153                        out_stream, encoding, orig_label, new_label, pool));
1154            }
1155          SVN_ERR(display_prop_diffs(out_stream, encoding,
1156                                     props, base_proptable, path, pool));
1157        }
1158    }
1159
1160  /* Return here if the node has no children. */
1161  if (! node->child)
1162    return SVN_NO_ERROR;
1163
1164  /* Recursively handle the node's children. */
1165  iterpool = svn_pool_create(pool);
1166  for (node = node->child; node; node = node->sibling)
1167    {
1168      svn_pool_clear(iterpool);
1169
1170      SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node,
1171                              svn_dirent_join(path, node->name, iterpool),
1172                              svn_dirent_join(base_path, node->name, iterpool),
1173                              c, iterpool));
1174    }
1175  svn_pool_destroy(iterpool);
1176
1177  return SVN_NO_ERROR;
1178}
1179
1180
1181/* Print a repository directory, maybe recursively, possibly showing
1182   the node revision ids, and optionally using full paths.
1183
1184   ROOT is the revision or transaction root used to build that tree.
1185   PATH and ID are the current path and node revision id being
1186   printed, and INDENTATION the number of spaces to prepent to that
1187   path's printed output.  ID may be NULL if SHOW_IDS is FALSE (in
1188   which case, ids won't be printed at all).  If RECURSE is TRUE,
1189   then print the tree recursively; otherwise, we'll stop after the
1190   first level (and use INDENTATION to keep track of how deep we are).
1191
1192   Use POOL for all allocations.  */
1193static svn_error_t *
1194print_tree(svn_fs_root_t *root,
1195           const char *path /* UTF-8! */,
1196           const svn_fs_id_t *id,
1197           svn_boolean_t is_dir,
1198           int indentation,
1199           svn_boolean_t show_ids,
1200           svn_boolean_t full_paths,
1201           svn_boolean_t recurse,
1202           apr_pool_t *pool)
1203{
1204  apr_pool_t *subpool;
1205  apr_hash_t *entries;
1206  const char* name;
1207
1208  SVN_ERR(check_cancel(NULL));
1209
1210  /* Print the indentation. */
1211  if (!full_paths)
1212    {
1213      int i;
1214      for (i = 0; i < indentation; i++)
1215        SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
1216    }
1217
1218  /* ### The path format is inconsistent.. needs fix */
1219  if (full_paths)
1220    name = path;
1221  else if (*path == '/')
1222    name = svn_fspath__basename(path, pool);
1223  else
1224    name = svn_relpath_basename(path, NULL);
1225
1226  if (svn_path_is_empty(name))
1227    name = "/"; /* basename of '/' is "" */
1228
1229  /* Print the node. */
1230  SVN_ERR(svn_cmdline_printf(pool, "%s%s",
1231                             name,
1232                             is_dir && strcmp(name, "/") ? "/" : ""));
1233
1234  if (show_ids)
1235    {
1236      svn_string_t *unparsed_id = NULL;
1237      if (id)
1238        unparsed_id = svn_fs_unparse_id(id, pool);
1239      SVN_ERR(svn_cmdline_printf(pool, " <%s>",
1240                                 unparsed_id
1241                                 ? unparsed_id->data
1242                                 : _("unknown")));
1243    }
1244  SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1245
1246  /* Return here if PATH is not a directory. */
1247  if (! is_dir)
1248    return SVN_NO_ERROR;
1249
1250  /* Recursively handle the node's children. */
1251  if (recurse || (indentation == 0))
1252    {
1253      apr_array_header_t *sorted_entries;
1254      int i;
1255
1256      SVN_ERR(svn_fs_dir_entries(&entries, root, path, pool));
1257      subpool = svn_pool_create(pool);
1258      sorted_entries = svn_sort__hash(entries,
1259                                      svn_sort_compare_items_lexically, pool);
1260      for (i = 0; i < sorted_entries->nelts; i++)
1261        {
1262          svn_sort__item_t item = APR_ARRAY_IDX(sorted_entries, i,
1263                                                svn_sort__item_t);
1264          svn_fs_dirent_t *entry = item.value;
1265
1266          svn_pool_clear(subpool);
1267          SVN_ERR(print_tree(root,
1268                             (*path == '/')
1269                                 ? svn_fspath__join(path, entry->name, pool)
1270                                 : svn_relpath_join(path, entry->name, pool),
1271                             entry->id, (entry->kind == svn_node_dir),
1272                             indentation + 1, show_ids, full_paths,
1273                             recurse, subpool));
1274        }
1275      svn_pool_destroy(subpool);
1276    }
1277
1278  return SVN_NO_ERROR;
1279}
1280
1281
1282/* Set *BASE_REV to the revision on which the target root specified in
1283   C is based, or to SVN_INVALID_REVNUM when C represents "revision
1284   0" (because that revision isn't based on another revision). */
1285static svn_error_t *
1286get_base_rev(svn_revnum_t *base_rev, svnlook_ctxt_t *c, apr_pool_t *pool)
1287{
1288  if (c->is_revision)
1289    {
1290      *base_rev = c->rev_id - 1;
1291    }
1292  else
1293    {
1294      *base_rev = svn_fs_txn_base_revision(c->txn);
1295
1296      if (! SVN_IS_VALID_REVNUM(*base_rev))
1297        return svn_error_createf
1298          (SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1299           _("Transaction '%s' is not based on a revision; how odd"),
1300           c->txn_name);
1301    }
1302  return SVN_NO_ERROR;
1303}
1304
1305
1306
1307/*** Subcommand handlers. ***/
1308
1309/* Print the revision's log message to stdout, followed by a newline. */
1310static svn_error_t *
1311do_log(svnlook_ctxt_t *c, svn_boolean_t print_size, apr_pool_t *pool)
1312{
1313  svn_string_t *prop_value;
1314  const char *prop_value_eol, *prop_value_native;
1315  svn_stream_t *stream;
1316  svn_error_t *err;
1317  apr_size_t len;
1318
1319  SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_LOG, pool));
1320  if (! (prop_value && prop_value->data))
1321    {
1322      SVN_ERR(svn_cmdline_printf(pool, "%s\n", print_size ? "0" : ""));
1323      return SVN_NO_ERROR;
1324    }
1325
1326  /* We immitate what svn_cmdline_printf does here, since we need the byte
1327     size of what we are going to print. */
1328
1329  SVN_ERR(svn_subst_translate_cstring2(prop_value->data, &prop_value_eol,
1330                                       APR_EOL_STR, TRUE,
1331                                       NULL, FALSE, pool));
1332
1333  err = svn_cmdline_cstring_from_utf8(&prop_value_native, prop_value_eol,
1334                                      pool);
1335  if (err)
1336    {
1337      svn_error_clear(err);
1338      prop_value_native = svn_cmdline_cstring_from_utf8_fuzzy(prop_value_eol,
1339                                                              pool);
1340    }
1341
1342  len = strlen(prop_value_native);
1343
1344  if (print_size)
1345    SVN_ERR(svn_cmdline_printf(pool, "%" APR_SIZE_T_FMT "\n", len));
1346
1347  /* Use a stream to bypass all stdio translations. */
1348  SVN_ERR(svn_cmdline_fflush(stdout));
1349  SVN_ERR(svn_stream_for_stdout(&stream, pool));
1350  SVN_ERR(svn_stream_write(stream, prop_value_native, &len));
1351  SVN_ERR(svn_stream_close(stream));
1352
1353  SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1354
1355  return SVN_NO_ERROR;
1356}
1357
1358
1359/* Print the timestamp of the commit (in the revision case) or the
1360   empty string (in the transaction case) to stdout, followed by a
1361   newline. */
1362static svn_error_t *
1363do_date(svnlook_ctxt_t *c, apr_pool_t *pool)
1364{
1365  svn_string_t *prop_value;
1366
1367  SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_DATE, pool));
1368  if (prop_value && prop_value->data)
1369    {
1370      /* Convert the date for humans. */
1371      apr_time_t aprtime;
1372      const char *time_utf8;
1373
1374      SVN_ERR(svn_time_from_cstring(&aprtime, prop_value->data, pool));
1375
1376      time_utf8 = svn_time_to_human_cstring(aprtime, pool);
1377
1378      SVN_ERR(svn_cmdline_printf(pool, "%s", time_utf8));
1379    }
1380
1381  SVN_ERR(svn_cmdline_printf(pool, "\n"));
1382  return SVN_NO_ERROR;
1383}
1384
1385
1386/* Print the author of the commit to stdout, followed by a newline. */
1387static svn_error_t *
1388do_author(svnlook_ctxt_t *c, apr_pool_t *pool)
1389{
1390  svn_string_t *prop_value;
1391
1392  SVN_ERR(get_property(&prop_value, c,
1393                       SVN_PROP_REVISION_AUTHOR, pool));
1394  if (prop_value && prop_value->data)
1395    SVN_ERR(svn_cmdline_printf(pool, "%s", prop_value->data));
1396
1397  SVN_ERR(svn_cmdline_printf(pool, "\n"));
1398  return SVN_NO_ERROR;
1399}
1400
1401
1402/* Print a list of all directories in which files, or directory
1403   properties, have been modified. */
1404static svn_error_t *
1405do_dirs_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1406{
1407  svn_fs_root_t *root;
1408  svn_revnum_t base_rev_id;
1409  svn_repos_node_t *tree;
1410
1411  SVN_ERR(get_root(&root, c, pool));
1412  SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1413  if (base_rev_id == SVN_INVALID_REVNUM)
1414    return SVN_NO_ERROR;
1415
1416  SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1417  if (tree)
1418    SVN_ERR(print_dirs_changed_tree(tree, "", pool));
1419
1420  return SVN_NO_ERROR;
1421}
1422
1423
1424/* Set *KIND to PATH's kind, if PATH exists.
1425 *
1426 * If PATH does not exist, then error; the text of the error depends
1427 * on whether PATH looks like a URL or not.
1428 */
1429static svn_error_t *
1430verify_path(svn_node_kind_t *kind,
1431            svn_fs_root_t *root,
1432            const char *path,
1433            apr_pool_t *pool)
1434{
1435  SVN_ERR(svn_fs_check_path(kind, root, path, pool));
1436
1437  if (*kind == svn_node_none)
1438    {
1439      if (svn_path_is_url(path))  /* check for a common mistake. */
1440        return svn_error_createf
1441          (SVN_ERR_FS_NOT_FOUND, NULL,
1442           _("'%s' is a URL, probably should be a path"), path);
1443      else
1444        return svn_error_createf
1445          (SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' does not exist"), path);
1446    }
1447
1448  return SVN_NO_ERROR;
1449}
1450
1451
1452/* Print the size (in bytes) of a file. */
1453static svn_error_t *
1454do_filesize(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
1455{
1456  svn_fs_root_t *root;
1457  svn_node_kind_t kind;
1458  svn_filesize_t length;
1459
1460  SVN_ERR(get_root(&root, c, pool));
1461  SVN_ERR(verify_path(&kind, root, path, pool));
1462
1463  if (kind != svn_node_file)
1464    return svn_error_createf
1465      (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
1466
1467  /* Else. */
1468
1469  SVN_ERR(svn_fs_file_length(&length, root, path, pool));
1470  return svn_cmdline_printf(pool, "%" SVN_FILESIZE_T_FMT "\n", length);
1471}
1472
1473/* Print the contents of the file at PATH in the repository.
1474   Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
1475   SVN_ERR_FS_NOT_FILE if PATH exists but is not a file. */
1476static svn_error_t *
1477do_cat(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
1478{
1479  svn_fs_root_t *root;
1480  svn_node_kind_t kind;
1481  svn_stream_t *fstream, *stdout_stream;
1482
1483  SVN_ERR(get_root(&root, c, pool));
1484  SVN_ERR(verify_path(&kind, root, path, pool));
1485
1486  if (kind != svn_node_file)
1487    return svn_error_createf
1488      (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
1489
1490  /* Else. */
1491
1492  SVN_ERR(svn_fs_file_contents(&fstream, root, path, pool));
1493  SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1494
1495  return svn_stream_copy3(fstream, svn_stream_disown(stdout_stream, pool),
1496                          check_cancel, NULL, pool);
1497}
1498
1499
1500/* Print a list of all paths modified in a format compatible with `svn
1501   update'. */
1502static svn_error_t *
1503do_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1504{
1505  svn_fs_root_t *root;
1506  svn_revnum_t base_rev_id;
1507  svn_repos_node_t *tree;
1508
1509  SVN_ERR(get_root(&root, c, pool));
1510  SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1511  if (base_rev_id == SVN_INVALID_REVNUM)
1512    return SVN_NO_ERROR;
1513
1514  SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1515  if (tree)
1516    SVN_ERR(print_changed_tree(tree, "", c->copy_info, pool));
1517
1518  return SVN_NO_ERROR;
1519}
1520
1521
1522/* Print some diff-y stuff in a TBD way. :-) */
1523static svn_error_t *
1524do_diff(svnlook_ctxt_t *c, apr_pool_t *pool)
1525{
1526  svn_fs_root_t *root, *base_root;
1527  svn_revnum_t base_rev_id;
1528  svn_repos_node_t *tree;
1529
1530  SVN_ERR(get_root(&root, c, pool));
1531  SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1532  if (base_rev_id == SVN_INVALID_REVNUM)
1533    return SVN_NO_ERROR;
1534
1535  SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1536  if (tree)
1537    {
1538      svn_stream_t *out_stream;
1539      const char *encoding = svn_cmdline_output_encoding(pool);
1540
1541      SVN_ERR(svn_fs_revision_root(&base_root, c->fs, base_rev_id, pool));
1542
1543      /* This fflush() might seem odd, but it was added to deal
1544         with this bug report:
1545
1546         http://subversion.tigris.org/servlets/ReadMsg?\
1547         list=dev&msgNo=140782
1548
1549         From: "Steve Hay" <SteveHay{_AT_}planit.com>
1550         To: <dev@subversion.tigris.org>
1551         Subject: svnlook diff output in wrong order when redirected
1552         Date: Fri, 4 Jul 2008 16:34:15 +0100
1553         Message-ID: <1B32FF956ABF414C9BCE5E487A1497E702014F62@\
1554                     ukmail02.planit.group>
1555
1556         Adding the fflush() fixed the bug (not everyone could
1557         reproduce it, but those who could confirmed the fix).
1558         Later in the thread, Daniel Shahaf speculated as to
1559         why the fix works:
1560
1561         "Because svn_cmdline_printf() uses the standard
1562         'FILE *stdout' to write to stdout, while
1563         svn_stream_for_stdout() uses (through
1564         apr_file_open_stdout()) Windows API's to get a
1565         handle for stdout?" */
1566      SVN_ERR(svn_cmdline_fflush(stdout));
1567      SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1568
1569      SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, tree,
1570                              "", "", c, pool));
1571    }
1572  return SVN_NO_ERROR;
1573}
1574
1575
1576
1577/* Callback baton for print_history() (and do_history()). */
1578struct print_history_baton
1579{
1580  svn_fs_t *fs;
1581  svn_boolean_t show_ids;    /* whether to show node IDs */
1582  apr_size_t limit;          /* max number of history items */
1583  apr_size_t count;          /* number of history items processed */
1584};
1585
1586/* Implements svn_repos_history_func_t interface.  Print the history
1587   that's reported through this callback, possibly finding and
1588   displaying node-rev-ids. */
1589static svn_error_t *
1590print_history(void *baton,
1591              const char *path,
1592              svn_revnum_t revision,
1593              apr_pool_t *pool)
1594{
1595  struct print_history_baton *phb = baton;
1596
1597  SVN_ERR(check_cancel(NULL));
1598
1599  if (phb->show_ids)
1600    {
1601      const svn_fs_id_t *node_id;
1602      svn_fs_root_t *rev_root;
1603      svn_string_t *id_string;
1604
1605      SVN_ERR(svn_fs_revision_root(&rev_root, phb->fs, revision, pool));
1606      SVN_ERR(svn_fs_node_id(&node_id, rev_root, path, pool));
1607      id_string = svn_fs_unparse_id(node_id, pool);
1608      SVN_ERR(svn_cmdline_printf(pool, "%8ld   %s <%s>\n",
1609                                 revision, path, id_string->data));
1610    }
1611  else
1612    {
1613      SVN_ERR(svn_cmdline_printf(pool, "%8ld   %s\n", revision, path));
1614    }
1615
1616  if (phb->limit > 0)
1617    {
1618      phb->count++;
1619      if (phb->count >= phb->limit)
1620        /* Not L10N'd, since this error is suppressed by the caller. */
1621        return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL,
1622                                _("History item limit reached"));
1623    }
1624
1625  return SVN_NO_ERROR;
1626}
1627
1628
1629/* Print a tabular display of history location points for PATH in
1630   revision C->rev_id.  Optionally, SHOW_IDS.  Use POOL for
1631   allocations. */
1632static svn_error_t *
1633do_history(svnlook_ctxt_t *c,
1634           const char *path,
1635           apr_pool_t *pool)
1636{
1637  struct print_history_baton args;
1638
1639  if (c->show_ids)
1640    {
1641      SVN_ERR(svn_cmdline_printf(pool, _("REVISION   PATH <ID>\n"
1642                                         "--------   ---------\n")));
1643    }
1644  else
1645    {
1646      SVN_ERR(svn_cmdline_printf(pool, _("REVISION   PATH\n"
1647                                         "--------   ----\n")));
1648    }
1649
1650  /* Call our history crawler.  We want the whole lifetime of the path
1651     (prior to the user-supplied revision, of course), across all
1652     copies. */
1653  args.fs = c->fs;
1654  args.show_ids = c->show_ids;
1655  args.limit = c->limit;
1656  args.count = 0;
1657  SVN_ERR(svn_repos_history2(c->fs, path, print_history, &args,
1658                             NULL, NULL, 0, c->rev_id, TRUE, pool));
1659  return SVN_NO_ERROR;
1660}
1661
1662
1663/* Print the value of property PROPNAME on PATH in the repository.
1664
1665   If VERBOSE, print their values too.  If SHOW_INHERITED_PROPS, print
1666   PATH's inherited props too.
1667
1668   Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. If
1669   SHOW_INHERITED_PROPS is FALSE,then error with SVN_ERR_PROPERTY_NOT_FOUND
1670   if there is no such property on PATH.  If SHOW_INHERITED_PROPS is TRUE,
1671   then error with SVN_ERR_PROPERTY_NOT_FOUND only if there is no such
1672   property on PATH nor inherited by path.
1673
1674   If PATH is NULL, operate on a revision property. */
1675static svn_error_t *
1676do_pget(svnlook_ctxt_t *c,
1677        const char *propname,
1678        const char *path,
1679        svn_boolean_t verbose,
1680        svn_boolean_t show_inherited_props,
1681        apr_pool_t *pool)
1682{
1683  svn_fs_root_t *root;
1684  svn_string_t *prop;
1685  svn_node_kind_t kind;
1686  svn_stream_t *stdout_stream;
1687  apr_size_t len;
1688  apr_array_header_t *inherited_props = NULL;
1689
1690  SVN_ERR(get_root(&root, c, pool));
1691  if (path != NULL)
1692    {
1693      path = svn_fspath__canonicalize(path, pool);
1694      SVN_ERR(verify_path(&kind, root, path, pool));
1695      SVN_ERR(svn_fs_node_prop(&prop, root, path, propname, pool));
1696
1697      if (show_inherited_props)
1698        {
1699          SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
1700                                                   path, propname, NULL,
1701                                                   NULL, pool, pool));
1702        }
1703    }
1704  else /* --revprop */
1705    {
1706      SVN_ERR(get_property(&prop, c, propname, pool));
1707    }
1708
1709  /* Did we find nothing? */
1710  if (prop == NULL
1711      && (!show_inherited_props || inherited_props->nelts == 0))
1712    {
1713       const char *err_msg;
1714       if (path == NULL)
1715         {
1716           /* We're operating on a revprop (e.g. c->is_revision). */
1717           if (SVN_IS_VALID_REVNUM(c->rev_id))
1718             err_msg = apr_psprintf(pool,
1719                                    _("Property '%s' not found on revision %ld"),
1720                                    propname, c->rev_id);
1721           else
1722             err_msg = apr_psprintf(pool,
1723                                    _("Property '%s' not found on transaction %s"),
1724                                    propname, c->txn_name);
1725         }
1726       else
1727         {
1728           if (SVN_IS_VALID_REVNUM(c->rev_id))
1729             {
1730               if (show_inherited_props)
1731                 err_msg = apr_psprintf(pool,
1732                                        _("Property '%s' not found on path '%s' "
1733                                          "or inherited from a parent "
1734                                          "in revision %ld"),
1735                                        propname, path, c->rev_id);
1736               else
1737                 err_msg = apr_psprintf(pool,
1738                                        _("Property '%s' not found on path '%s' "
1739                                          "in revision %ld"),
1740                                        propname, path, c->rev_id);
1741             }
1742           else
1743             {
1744               if (show_inherited_props)
1745                 err_msg = apr_psprintf(pool,
1746                                        _("Property '%s' not found on path '%s' "
1747                                          "or inherited from a parent "
1748                                          "in transaction %s"),
1749                                        propname, path, c->txn_name);
1750               else
1751                 err_msg = apr_psprintf(pool,
1752                                        _("Property '%s' not found on path '%s' "
1753                                          "in transaction %s"),
1754                                        propname, path, c->txn_name);
1755             }
1756         }
1757       return svn_error_create(SVN_ERR_PROPERTY_NOT_FOUND, NULL, err_msg);
1758    }
1759
1760  SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1761
1762  if (verbose || show_inherited_props)
1763    {
1764      if (inherited_props)
1765        {
1766          int i;
1767
1768          for (i = 0; i < inherited_props->nelts; i++)
1769            {
1770              svn_prop_inherited_item_t *elt =
1771                APR_ARRAY_IDX(inherited_props, i,
1772                              svn_prop_inherited_item_t *);
1773
1774              if (verbose)
1775                {
1776                  SVN_ERR(svn_stream_printf(stdout_stream, pool,
1777                          _("Inherited properties on '%s',\nfrom '%s':\n"),
1778                          path, svn_fspath__canonicalize(elt->path_or_url,
1779                                                         pool)));
1780                  SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream,
1781                                                       elt->prop_hash,
1782                                                       !verbose, pool));
1783                }
1784              else
1785                {
1786                  svn_string_t *propval =
1787                    apr_hash_this_val(apr_hash_first(pool, elt->prop_hash));
1788
1789                  SVN_ERR(svn_stream_printf(
1790                    stdout_stream, pool, "%s - ",
1791                    svn_fspath__canonicalize(elt->path_or_url, pool)));
1792                  len = propval->len;
1793                  SVN_ERR(svn_stream_write(stdout_stream, propval->data, &len));
1794                  /* If we have more than one property to write, then add a newline*/
1795                  if (inherited_props->nelts > 1 || prop)
1796                    {
1797                      len = strlen(APR_EOL_STR);
1798                      SVN_ERR(svn_stream_write(stdout_stream, APR_EOL_STR, &len));
1799                    }
1800                }
1801            }
1802        }
1803
1804      if (prop)
1805        {
1806          if (verbose)
1807            {
1808              apr_hash_t *hash = apr_hash_make(pool);
1809
1810              svn_hash_sets(hash, propname, prop);
1811              SVN_ERR(svn_stream_printf(stdout_stream, pool,
1812                      _("Properties on '%s':\n"), path));
1813              SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, hash,
1814                                                   FALSE, pool));
1815            }
1816          else
1817            {
1818              SVN_ERR(svn_stream_printf(stdout_stream, pool, "%s - ", path));
1819              len = prop->len;
1820              SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1821            }
1822        }
1823    }
1824  else /* Raw single prop output, i.e. non-verbose output with no
1825          inherited props. */
1826    {
1827      /* Unlike the command line client, we don't translate the property
1828         value or print a trailing newline here.  We just output the raw
1829         bytes of whatever's in the repository, as svnlook is more likely
1830         to be used for automated inspections. */
1831      len = prop->len;
1832      SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1833    }
1834
1835  return SVN_NO_ERROR;
1836}
1837
1838
1839/* Print the property names of all properties on PATH in the repository.
1840
1841   If VERBOSE, print their values too.  If XML, print as XML rather than as
1842   plain text.  If SHOW_INHERITED_PROPS, print PATH's inherited props too.
1843
1844   Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist.
1845
1846   If PATH is NULL, operate on a revision properties. */
1847static svn_error_t *
1848do_plist(svnlook_ctxt_t *c,
1849         const char *path,
1850         svn_boolean_t verbose,
1851         svn_boolean_t xml,
1852         svn_boolean_t show_inherited_props,
1853         apr_pool_t *pool)
1854{
1855  svn_fs_root_t *root;
1856  apr_hash_t *props;
1857  apr_hash_index_t *hi;
1858  svn_node_kind_t kind;
1859  svn_stringbuf_t *sb = NULL;
1860  svn_boolean_t revprop = FALSE;
1861  apr_array_header_t *inherited_props = NULL;
1862
1863  if (path != NULL)
1864    {
1865      /* PATH might be the root of the repsository and we accept both
1866         "" and "/".  But to avoid the somewhat cryptic output like this:
1867
1868           >svnlook pl repos-path ""
1869           Properties on '':
1870             svn:auto-props
1871             svn:global-ignores
1872
1873         We canonicalize PATH so that is has a leading slash. */
1874      path = svn_fspath__canonicalize(path, pool);
1875
1876      SVN_ERR(get_root(&root, c, pool));
1877      SVN_ERR(verify_path(&kind, root, path, pool));
1878      SVN_ERR(svn_fs_node_proplist(&props, root, path, pool));
1879
1880      if (show_inherited_props)
1881        SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
1882                                                 path, NULL, NULL, NULL,
1883                                                 pool, pool));
1884    }
1885  else if (c->is_revision)
1886    {
1887      SVN_ERR(svn_fs_revision_proplist2(&props, c->fs, c->rev_id, TRUE,
1888                                        pool, pool));
1889      revprop = TRUE;
1890    }
1891  else
1892    {
1893      SVN_ERR(svn_fs_txn_proplist(&props, c->txn, pool));
1894      revprop = TRUE;
1895    }
1896
1897  if (xml)
1898    {
1899      /* <?xml version="1.0" encoding="UTF-8"?> */
1900      svn_xml_make_header2(&sb, "UTF-8", pool);
1901
1902      /* "<properties>" */
1903      svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "properties",
1904                            SVN_VA_NULL);
1905    }
1906
1907  if (inherited_props)
1908    {
1909      int i;
1910
1911      for (i = 0; i < inherited_props->nelts; i++)
1912        {
1913          svn_prop_inherited_item_t *elt =
1914            APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1915
1916          /* Canonicalize the inherited parent paths for consistency
1917             with PATH. */
1918          if (xml)
1919            {
1920              svn_xml_make_open_tag(
1921                &sb, pool, svn_xml_normal, "target", "path",
1922                svn_fspath__canonicalize(elt->path_or_url, pool),
1923                SVN_VA_NULL);
1924              SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, elt->prop_hash,
1925                                                       !verbose, TRUE,
1926                                                       pool));
1927              svn_xml_make_close_tag(&sb, pool, "target");
1928            }
1929          else
1930            {
1931              SVN_ERR(svn_cmdline_printf(
1932                pool, _("Inherited properties on '%s',\nfrom '%s':\n"),
1933                path, svn_fspath__canonicalize(elt->path_or_url, pool)));
1934               SVN_ERR(svn_cmdline__print_prop_hash(NULL, elt->prop_hash,
1935                                                    !verbose, pool));
1936            }
1937        }
1938    }
1939
1940  if (xml)
1941    {
1942      if (revprop)
1943        {
1944          /* "<revprops ...>" */
1945          if (c->is_revision)
1946            {
1947              char *revstr = apr_psprintf(pool, "%ld", c->rev_id);
1948
1949              svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
1950                                    "rev", revstr, SVN_VA_NULL);
1951            }
1952          else
1953            {
1954              svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
1955                                    "txn", c->txn_name, SVN_VA_NULL);
1956            }
1957        }
1958      else
1959        {
1960          /* "<target ...>" */
1961          svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
1962                                "path", path, SVN_VA_NULL);
1963        }
1964    }
1965
1966  if (!xml && path /* Not a --revprop */)
1967    SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), path));
1968
1969  for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
1970    {
1971      const char *pname = apr_hash_this_key(hi);
1972      svn_string_t *propval = apr_hash_this_val(hi);
1973
1974      SVN_ERR(check_cancel(NULL));
1975
1976      /* Since we're already adding a trailing newline (and possible a
1977         colon and some spaces) anyway, just mimic the output of the
1978         command line client proplist.   Compare to 'svnlook propget',
1979         which sends the raw bytes to stdout, untranslated. */
1980      /* We leave printf calls here, since we don't always know the encoding
1981         of the prop value. */
1982      if (svn_prop_needs_translation(pname))
1983        SVN_ERR(svn_subst_detranslate_string(&propval, propval, TRUE, pool));
1984
1985      if (verbose)
1986        {
1987          if (xml)
1988            svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, pool);
1989          else
1990            {
1991              const char *pname_stdout;
1992              const char *indented_newval;
1993
1994              SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname,
1995                                                    pool));
1996              printf("  %s\n", pname_stdout);
1997              /* Add an extra newline to the value before indenting, so that
1998                 every line of output has the indentation whether the value
1999                 already ended in a newline or not. */
2000              indented_newval =
2001                svn_cmdline__indent_string(apr_psprintf(pool, "%s\n",
2002                                                        propval->data),
2003                                           "    ", pool);
2004              printf("%s", indented_newval);
2005            }
2006        }
2007      else if (xml)
2008        svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "property",
2009                              "name", pname, SVN_VA_NULL);
2010      else
2011        printf("  %s\n", pname);
2012    }
2013  if (xml)
2014    {
2015      errno = 0;
2016      if (revprop)
2017        {
2018          /* "</revprops>" */
2019          svn_xml_make_close_tag(&sb, pool, "revprops");
2020        }
2021      else
2022        {
2023          /* "</target>" */
2024          svn_xml_make_close_tag(&sb, pool, "target");
2025        }
2026
2027      /* "</properties>" */
2028      svn_xml_make_close_tag(&sb, pool, "properties");
2029
2030      errno = 0;
2031      if (fputs(sb->data, stdout) == EOF)
2032        {
2033          if (apr_get_os_error()) /* is errno on POSIX */
2034            return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
2035          else
2036            return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
2037        }
2038    }
2039
2040  return SVN_NO_ERROR;
2041}
2042
2043
2044static svn_error_t *
2045do_tree(svnlook_ctxt_t *c,
2046        const char *path,
2047        svn_boolean_t show_ids,
2048        svn_boolean_t full_paths,
2049        svn_boolean_t recurse,
2050        apr_pool_t *pool)
2051{
2052  svn_fs_root_t *root;
2053  const svn_fs_id_t *id;
2054  svn_boolean_t is_dir;
2055
2056  SVN_ERR(get_root(&root, c, pool));
2057  SVN_ERR(svn_fs_node_id(&id, root, path, pool));
2058  SVN_ERR(svn_fs_is_dir(&is_dir, root, path, pool));
2059  SVN_ERR(print_tree(root, path, id, is_dir, 0, show_ids, full_paths,
2060                     recurse, pool));
2061  return SVN_NO_ERROR;
2062}
2063
2064
2065/* Custom filesystem warning function. */
2066static void
2067warning_func(void *baton,
2068             svn_error_t *err)
2069{
2070  if (! err)
2071    return;
2072  svn_handle_error2(err, stderr, FALSE, "svnlook: ");
2073}
2074
2075
2076/* Return an error if the number of arguments (excluding the repository
2077 * argument) is not NUM_ARGS.  NUM_ARGS must be 0 or 1.  The arguments
2078 * are assumed to be found in OPT_STATE->arg1 and OPT_STATE->arg2. */
2079static svn_error_t *
2080check_number_of_args(struct svnlook_opt_state *opt_state,
2081                     int num_args)
2082{
2083  if ((num_args == 0 && opt_state->arg1 != NULL)
2084      || (num_args == 1 && opt_state->arg2 != NULL))
2085    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2086                            _("Too many arguments given"));
2087  if ((num_args == 1 && opt_state->arg1 == NULL))
2088    return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2089                            _("Missing repository path argument"));
2090  return SVN_NO_ERROR;
2091}
2092
2093
2094/* Factory function for the context baton. */
2095static svn_error_t *
2096get_ctxt_baton(svnlook_ctxt_t **baton_p,
2097               struct svnlook_opt_state *opt_state,
2098               apr_pool_t *pool)
2099{
2100  svnlook_ctxt_t *baton = apr_pcalloc(pool, sizeof(*baton));
2101
2102  SVN_ERR(svn_repos_open3(&(baton->repos), opt_state->repos_path, NULL,
2103                          pool, pool));
2104  baton->fs = svn_repos_fs(baton->repos);
2105  svn_fs_set_warning_func(baton->fs, warning_func, NULL);
2106  baton->show_ids = opt_state->show_ids;
2107  baton->limit = opt_state->limit;
2108  baton->no_diff_deleted = opt_state->no_diff_deleted;
2109  baton->no_diff_added = opt_state->no_diff_added;
2110  baton->diff_copy_from = opt_state->diff_copy_from;
2111  baton->full_paths = opt_state->full_paths;
2112  baton->copy_info = opt_state->copy_info;
2113  baton->is_revision = opt_state->txn == NULL;
2114  baton->rev_id = opt_state->rev;
2115  baton->txn_name = apr_pstrdup(pool, opt_state->txn);
2116  baton->diff_options = svn_cstring_split(opt_state->extensions
2117                                          ? opt_state->extensions : "",
2118                                          " \t\n\r", TRUE, pool);
2119  baton->ignore_properties = opt_state->ignore_properties;
2120  baton->properties_only = opt_state->properties_only;
2121  baton->diff_cmd = opt_state->diff_cmd;
2122
2123  if (baton->txn_name)
2124    SVN_ERR(svn_fs_open_txn(&(baton->txn), baton->fs,
2125                            baton->txn_name, pool));
2126  else if (baton->rev_id == SVN_INVALID_REVNUM)
2127    SVN_ERR(svn_fs_youngest_rev(&(baton->rev_id), baton->fs, pool));
2128
2129  *baton_p = baton;
2130  return SVN_NO_ERROR;
2131}
2132
2133
2134
2135/*** Subcommands. ***/
2136
2137/* This implements `svn_opt_subcommand_t'. */
2138static svn_error_t *
2139subcommand_author(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2140{
2141  struct svnlook_opt_state *opt_state = baton;
2142  svnlook_ctxt_t *c;
2143
2144  SVN_ERR(check_number_of_args(opt_state, 0));
2145
2146  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2147  SVN_ERR(do_author(c, pool));
2148  return SVN_NO_ERROR;
2149}
2150
2151/* This implements `svn_opt_subcommand_t'. */
2152static svn_error_t *
2153subcommand_cat(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2154{
2155  struct svnlook_opt_state *opt_state = baton;
2156  svnlook_ctxt_t *c;
2157
2158  SVN_ERR(check_number_of_args(opt_state, 1));
2159
2160  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2161  SVN_ERR(do_cat(c, opt_state->arg1, pool));
2162  return SVN_NO_ERROR;
2163}
2164
2165/* This implements `svn_opt_subcommand_t'. */
2166static svn_error_t *
2167subcommand_changed(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2168{
2169  struct svnlook_opt_state *opt_state = baton;
2170  svnlook_ctxt_t *c;
2171
2172  SVN_ERR(check_number_of_args(opt_state, 0));
2173
2174  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2175  SVN_ERR(do_changed(c, pool));
2176  return SVN_NO_ERROR;
2177}
2178
2179/* This implements `svn_opt_subcommand_t'. */
2180static svn_error_t *
2181subcommand_date(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2182{
2183  struct svnlook_opt_state *opt_state = baton;
2184  svnlook_ctxt_t *c;
2185
2186  SVN_ERR(check_number_of_args(opt_state, 0));
2187
2188  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2189  SVN_ERR(do_date(c, pool));
2190  return SVN_NO_ERROR;
2191}
2192
2193/* This implements `svn_opt_subcommand_t'. */
2194static svn_error_t *
2195subcommand_diff(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2196{
2197  struct svnlook_opt_state *opt_state = baton;
2198  svnlook_ctxt_t *c;
2199
2200  SVN_ERR(check_number_of_args(opt_state, 0));
2201
2202  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2203  SVN_ERR(do_diff(c, pool));
2204  return SVN_NO_ERROR;
2205}
2206
2207/* This implements `svn_opt_subcommand_t'. */
2208static svn_error_t *
2209subcommand_dirschanged(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2210{
2211  struct svnlook_opt_state *opt_state = baton;
2212  svnlook_ctxt_t *c;
2213
2214  SVN_ERR(check_number_of_args(opt_state, 0));
2215
2216  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2217  SVN_ERR(do_dirs_changed(c, pool));
2218  return SVN_NO_ERROR;
2219}
2220
2221/* This implements `svn_opt_subcommand_t'. */
2222static svn_error_t *
2223subcommand_filesize(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2224{
2225  struct svnlook_opt_state *opt_state = baton;
2226  svnlook_ctxt_t *c;
2227
2228  SVN_ERR(check_number_of_args(opt_state, 1));
2229
2230  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2231  SVN_ERR(do_filesize(c, opt_state->arg1, pool));
2232  return SVN_NO_ERROR;
2233}
2234
2235/* This implements `svn_opt_subcommand_t'. */
2236static svn_error_t *
2237subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2238{
2239  struct svnlook_opt_state *opt_state = baton;
2240  const char *header =
2241    _("general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
2242      "Subversion repository inspection tool.\n"
2243      "Type 'svnlook help <subcommand>' for help on a specific subcommand.\n"
2244      "Type 'svnlook --version' to see the program version and FS modules.\n"
2245      "Note: any subcommand which takes the '--revision' and '--transaction'\n"
2246      "      options will, if invoked without one of those options, act on\n"
2247      "      the repository's youngest revision.\n"
2248      "\n"
2249      "Available subcommands:\n");
2250
2251  const char *fs_desc_start
2252    = _("The following repository back-end (FS) modules are available:\n\n");
2253
2254  svn_stringbuf_t *version_footer;
2255
2256  version_footer = svn_stringbuf_create(fs_desc_start, pool);
2257  SVN_ERR(svn_fs_print_modules(version_footer, pool));
2258
2259  SVN_ERR(svn_opt_print_help5(os, "svnlook",
2260                              opt_state ? opt_state->version : FALSE,
2261                              opt_state ? opt_state->quiet : FALSE,
2262                              opt_state ? opt_state->verbose : FALSE,
2263                              version_footer->data,
2264                              header, cmd_table, options_table, NULL,
2265                              NULL, pool));
2266
2267  return SVN_NO_ERROR;
2268}
2269
2270/* This implements `svn_opt_subcommand_t'. */
2271static svn_error_t *
2272subcommand_history(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2273{
2274  struct svnlook_opt_state *opt_state = baton;
2275  svnlook_ctxt_t *c;
2276  const char *path = (opt_state->arg1 ? opt_state->arg1 : "/");
2277
2278  if (opt_state->arg2 != NULL)
2279    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2280                            _("Too many arguments given"));
2281
2282  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2283  SVN_ERR(do_history(c, path, pool));
2284  return SVN_NO_ERROR;
2285}
2286
2287
2288/* This implements `svn_opt_subcommand_t'. */
2289static svn_error_t *
2290subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2291{
2292  struct svnlook_opt_state *opt_state = baton;
2293  svnlook_ctxt_t *c;
2294  svn_lock_t *lock;
2295
2296  SVN_ERR(check_number_of_args(opt_state, 1));
2297
2298  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2299
2300  SVN_ERR(svn_fs_get_lock(&lock, c->fs, opt_state->arg1, pool));
2301
2302  if (lock)
2303    {
2304      const char *cr_date, *exp_date = "";
2305      int comment_lines = 0;
2306
2307      cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
2308
2309      if (lock->expiration_date)
2310        exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
2311
2312      if (lock->comment)
2313        comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
2314
2315      SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
2316      SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
2317      SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
2318      SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
2319      SVN_ERR(svn_cmdline_printf(pool,
2320                                 Q_("Comment (%i line):\n%s\n",
2321                                    "Comment (%i lines):\n%s\n",
2322                                    comment_lines),
2323                                 comment_lines,
2324                                 lock->comment ? lock->comment : ""));
2325    }
2326
2327  return SVN_NO_ERROR;
2328}
2329
2330
2331/* This implements `svn_opt_subcommand_t'. */
2332static svn_error_t *
2333subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2334{
2335  struct svnlook_opt_state *opt_state = baton;
2336  svnlook_ctxt_t *c;
2337
2338  SVN_ERR(check_number_of_args(opt_state, 0));
2339
2340  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2341  SVN_ERR(do_author(c, pool));
2342  SVN_ERR(do_date(c, pool));
2343  SVN_ERR(do_log(c, TRUE, pool));
2344  return SVN_NO_ERROR;
2345}
2346
2347/* This implements `svn_opt_subcommand_t'. */
2348static svn_error_t *
2349subcommand_log(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2350{
2351  struct svnlook_opt_state *opt_state = baton;
2352  svnlook_ctxt_t *c;
2353
2354  SVN_ERR(check_number_of_args(opt_state, 0));
2355
2356  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2357  SVN_ERR(do_log(c, FALSE, pool));
2358  return SVN_NO_ERROR;
2359}
2360
2361/* This implements `svn_opt_subcommand_t'. */
2362static svn_error_t *
2363subcommand_pget(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2364{
2365  struct svnlook_opt_state *opt_state = baton;
2366  svnlook_ctxt_t *c;
2367
2368  if (opt_state->arg1 == NULL)
2369    {
2370      return svn_error_createf
2371        (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2372         opt_state->revprop ?  _("Missing propname argument") :
2373         _("Missing propname and repository path arguments"));
2374    }
2375  else if (!opt_state->revprop && opt_state->arg2 == NULL)
2376    {
2377      return svn_error_create
2378        (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2379         _("Missing propname or repository path argument"));
2380    }
2381  if ((opt_state->revprop && opt_state->arg2 != NULL)
2382      || os->ind < os->argc)
2383    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2384                            _("Too many arguments given"));
2385
2386  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2387  SVN_ERR(do_pget(c, opt_state->arg1,
2388                  opt_state->revprop ? NULL : opt_state->arg2,
2389                  opt_state->verbose, opt_state->show_inherited_props,
2390                  pool));
2391  return SVN_NO_ERROR;
2392}
2393
2394/* This implements `svn_opt_subcommand_t'. */
2395static svn_error_t *
2396subcommand_plist(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2397{
2398  struct svnlook_opt_state *opt_state = baton;
2399  svnlook_ctxt_t *c;
2400
2401  SVN_ERR(check_number_of_args(opt_state, opt_state->revprop ? 0 : 1));
2402
2403  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2404  SVN_ERR(do_plist(c, opt_state->revprop ? NULL : opt_state->arg1,
2405                   opt_state->verbose, opt_state->xml,
2406                   opt_state->show_inherited_props, pool));
2407  return SVN_NO_ERROR;
2408}
2409
2410/* This implements `svn_opt_subcommand_t'. */
2411static svn_error_t *
2412subcommand_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2413{
2414  struct svnlook_opt_state *opt_state = baton;
2415  svnlook_ctxt_t *c;
2416
2417  if (opt_state->arg2 != NULL)
2418    return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2419                            _("Too many arguments given"));
2420
2421  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2422  SVN_ERR(do_tree(c, opt_state->arg1 ? opt_state->arg1 : "",
2423                  opt_state->show_ids, opt_state->full_paths,
2424                  ! opt_state->non_recursive, pool));
2425  return SVN_NO_ERROR;
2426}
2427
2428/* This implements `svn_opt_subcommand_t'. */
2429static svn_error_t *
2430subcommand_youngest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2431{
2432  struct svnlook_opt_state *opt_state = baton;
2433  svnlook_ctxt_t *c;
2434
2435  SVN_ERR(check_number_of_args(opt_state, 0));
2436
2437  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2438  SVN_ERR(svn_cmdline_printf(pool, "%ld%s", c->rev_id,
2439                             opt_state->no_newline ? "" : "\n"));
2440  return SVN_NO_ERROR;
2441}
2442
2443/* This implements `svn_opt_subcommand_t'. */
2444static svn_error_t *
2445subcommand_uuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2446{
2447  struct svnlook_opt_state *opt_state = baton;
2448  svnlook_ctxt_t *c;
2449  const char *uuid;
2450
2451  SVN_ERR(check_number_of_args(opt_state, 0));
2452
2453  SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2454  SVN_ERR(svn_fs_get_uuid(c->fs, &uuid, pool));
2455  SVN_ERR(svn_cmdline_printf(pool, "%s\n", uuid));
2456  return SVN_NO_ERROR;
2457}
2458
2459
2460
2461/*** Main. ***/
2462
2463/*
2464 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
2465 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
2466 * return SVN_NO_ERROR.
2467 */
2468static svn_error_t *
2469sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
2470{
2471  svn_error_t *err;
2472  apr_status_t apr_err;
2473
2474  const svn_opt_subcommand_desc3_t *subcommand = NULL;
2475  struct svnlook_opt_state opt_state;
2476  apr_getopt_t *os;
2477  int opt_id;
2478  apr_array_header_t *received_opts;
2479  int i;
2480
2481  received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
2482
2483  /* Check library versions */
2484  SVN_ERR(check_lib_versions());
2485
2486  /* Initialize the FS library. */
2487  SVN_ERR(svn_fs_initialize(pool));
2488
2489  if (argc <= 1)
2490    {
2491      SVN_ERR(subcommand_help(NULL, NULL, pool));
2492      *exit_code = EXIT_FAILURE;
2493      return SVN_NO_ERROR;
2494    }
2495
2496  /* Initialize opt_state. */
2497  memset(&opt_state, 0, sizeof(opt_state));
2498  opt_state.rev = SVN_INVALID_REVNUM;
2499  opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
2500
2501  /* Parse options. */
2502  SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
2503
2504  os->interleave = 1;
2505  while (1)
2506    {
2507      const char *opt_arg;
2508
2509      /* Parse the next option. */
2510      apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2511      if (APR_STATUS_IS_EOF(apr_err))
2512        break;
2513      else if (apr_err)
2514        {
2515          SVN_ERR(subcommand_help(NULL, NULL, pool));
2516          *exit_code = EXIT_FAILURE;
2517          return SVN_NO_ERROR;
2518        }
2519
2520      /* Stash the option code in an array before parsing it. */
2521      APR_ARRAY_PUSH(received_opts, int) = opt_id;
2522
2523      switch (opt_id)
2524        {
2525        case 'r':
2526          {
2527            char *digits_end = NULL;
2528            opt_state.rev = strtol(opt_arg, &digits_end, 10);
2529            if ((! SVN_IS_VALID_REVNUM(opt_state.rev))
2530                || (! digits_end)
2531                || *digits_end)
2532              return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2533                                      _("Invalid revision number supplied"));
2534          }
2535          break;
2536
2537        case 't':
2538          opt_state.txn = opt_arg;
2539          break;
2540
2541        case 'M':
2542          {
2543            apr_uint64_t sz_val;
2544            SVN_ERR(svn_cstring_atoui64(&sz_val, opt_arg));
2545
2546            opt_state.memory_cache_size = 0x100000 * sz_val;
2547          }
2548          break;
2549
2550        case 'N':
2551          opt_state.non_recursive = TRUE;
2552          break;
2553
2554        case 'v':
2555          opt_state.verbose = TRUE;
2556          break;
2557
2558        case 'h':
2559        case '?':
2560          opt_state.help = TRUE;
2561          break;
2562
2563        case 'q':
2564          opt_state.quiet = TRUE;
2565          break;
2566
2567        case svnlook__revprop_opt:
2568          opt_state.revprop = TRUE;
2569          break;
2570
2571        case svnlook__xml_opt:
2572          opt_state.xml = TRUE;
2573          break;
2574
2575        case svnlook__version:
2576          opt_state.version = TRUE;
2577          break;
2578
2579        case svnlook__show_ids:
2580          opt_state.show_ids = TRUE;
2581          break;
2582
2583        case 'l':
2584          {
2585            char *end;
2586            opt_state.limit = strtol(opt_arg, &end, 10);
2587            if (end == opt_arg || *end != '\0')
2588              {
2589                return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2590                                        _("Non-numeric limit argument given"));
2591              }
2592            if (opt_state.limit <= 0)
2593              {
2594                return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2595                                    _("Argument to --limit must be positive"));
2596              }
2597          }
2598          break;
2599
2600        case svnlook__no_diff_deleted:
2601          opt_state.no_diff_deleted = TRUE;
2602          break;
2603
2604        case svnlook__no_diff_added:
2605          opt_state.no_diff_added = TRUE;
2606          break;
2607
2608        case svnlook__diff_copy_from:
2609          opt_state.diff_copy_from = TRUE;
2610          break;
2611
2612        case svnlook__full_paths:
2613          opt_state.full_paths = TRUE;
2614          break;
2615
2616        case svnlook__copy_info:
2617          opt_state.copy_info = TRUE;
2618          break;
2619
2620        case 'x':
2621          opt_state.extensions = opt_arg;
2622          break;
2623
2624        case svnlook__ignore_properties:
2625          opt_state.ignore_properties = TRUE;
2626          break;
2627
2628        case svnlook__properties_only:
2629          opt_state.properties_only = TRUE;
2630          break;
2631
2632        case svnlook__diff_cmd:
2633          opt_state.diff_cmd = opt_arg;
2634          break;
2635
2636        case svnlook__show_inherited_props:
2637          opt_state.show_inherited_props = TRUE;
2638          break;
2639
2640        case svnlook__no_newline:
2641          opt_state.no_newline = TRUE;
2642          break;
2643
2644        default:
2645          SVN_ERR(subcommand_help(NULL, NULL, pool));
2646          *exit_code = EXIT_FAILURE;
2647          return SVN_NO_ERROR;
2648
2649        }
2650    }
2651
2652  /* The --transaction and --revision options may not co-exist. */
2653  if ((opt_state.rev != SVN_INVALID_REVNUM) && opt_state.txn)
2654    return svn_error_create
2655                (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
2656                 _("The '--transaction' (-t) and '--revision' (-r) arguments "
2657                   "cannot co-exist"));
2658
2659  /* The --show-inherited-props and --revprop options may not co-exist. */
2660  if (opt_state.show_inherited_props && opt_state.revprop)
2661    return svn_error_create
2662                (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
2663                 _("Cannot use the '--show-inherited-props' option with the "
2664                   "'--revprop' option"));
2665
2666  /* If the user asked for help, then the rest of the arguments are
2667     the names of subcommands to get help on (if any), or else they're
2668     just typos/mistakes.  Whatever the case, the subcommand to
2669     actually run is subcommand_help(). */
2670  if (opt_state.help)
2671    subcommand = svn_opt_get_canonical_subcommand3(cmd_table, "help");
2672
2673  /* If we're not running the `help' subcommand, then look for a
2674     subcommand in the first argument. */
2675  if (subcommand == NULL)
2676    {
2677      if (os->ind >= os->argc)
2678        {
2679          if (opt_state.version)
2680            {
2681              /* Use the "help" subcommand to handle the "--version" option. */
2682              static const svn_opt_subcommand_desc3_t pseudo_cmd =
2683                { "--version", subcommand_help, {0}, {""},
2684                  {svnlook__version,  /* must accept its own option */
2685                   'q', 'v',
2686                  } };
2687
2688              subcommand = &pseudo_cmd;
2689            }
2690          else
2691            {
2692              svn_error_clear
2693                (svn_cmdline_fprintf(stderr, pool,
2694                                     _("Subcommand argument required\n")));
2695              SVN_ERR(subcommand_help(NULL, NULL, pool));
2696              *exit_code = EXIT_FAILURE;
2697              return SVN_NO_ERROR;
2698            }
2699        }
2700      else
2701        {
2702          const char *first_arg;
2703
2704          SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
2705                                          pool));
2706          subcommand = svn_opt_get_canonical_subcommand3(cmd_table, first_arg);
2707          if (subcommand == NULL)
2708            {
2709              svn_error_clear(
2710                svn_cmdline_fprintf(stderr, pool,
2711                                    _("Unknown subcommand: '%s'\n"),
2712                                    first_arg));
2713              SVN_ERR(subcommand_help(NULL, NULL, pool));
2714
2715              /* Be kind to people who try 'svnlook verify'. */
2716              if (strcmp(first_arg, "verify") == 0)
2717                {
2718                  svn_error_clear(
2719                    svn_cmdline_fprintf(stderr, pool,
2720                                        _("Try 'svnadmin verify' instead.\n")));
2721                }
2722
2723              *exit_code = EXIT_FAILURE;
2724              return SVN_NO_ERROR;
2725            }
2726        }
2727    }
2728
2729  /* If there's a second argument, it's the repository.  There may be
2730     more arguments following the repository; usually the next one is
2731     a path within the repository, or it's a propname and the one
2732     after that is the path.  Since we don't know, we just call them
2733     arg1 and arg2, meaning the first and second arguments following
2734     the repository. */
2735  if (subcommand->cmd_func != subcommand_help)
2736    {
2737      const char *repos_path = NULL;
2738      const char *arg1 = NULL, *arg2 = NULL;
2739
2740      /* Get the repository. */
2741      if (os->ind < os->argc)
2742        {
2743          SVN_ERR(svn_utf_cstring_to_utf8(&repos_path,
2744                                          os->argv[os->ind++],
2745                                          pool));
2746          repos_path = svn_dirent_internal_style(repos_path, pool);
2747        }
2748
2749      if (repos_path == NULL)
2750        {
2751          svn_error_clear
2752            (svn_cmdline_fprintf(stderr, pool,
2753                                 _("Repository argument required\n")));
2754          SVN_ERR(subcommand_help(NULL, NULL, pool));
2755          *exit_code = EXIT_FAILURE;
2756          return SVN_NO_ERROR;
2757        }
2758      else if (svn_path_is_url(repos_path))
2759        {
2760          svn_error_clear
2761            (svn_cmdline_fprintf(stderr, pool,
2762                                 _("'%s' is a URL when it should be a path\n"),
2763                                 repos_path));
2764          *exit_code = EXIT_FAILURE;
2765          return SVN_NO_ERROR;
2766        }
2767
2768      opt_state.repos_path = repos_path;
2769
2770      /* Get next arg (arg1), if any. */
2771      if (os->ind < os->argc)
2772        {
2773          SVN_ERR(svn_utf_cstring_to_utf8(&arg1, os->argv[os->ind++], pool));
2774          arg1 = svn_dirent_internal_style(arg1, pool);
2775        }
2776      opt_state.arg1 = arg1;
2777
2778      /* Get next arg (arg2), if any. */
2779      if (os->ind < os->argc)
2780        {
2781          SVN_ERR(svn_utf_cstring_to_utf8(&arg2, os->argv[os->ind++], pool));
2782          arg2 = svn_dirent_internal_style(arg2, pool);
2783        }
2784      opt_state.arg2 = arg2;
2785    }
2786
2787  /* Check that the subcommand wasn't passed any inappropriate options. */
2788  for (i = 0; i < received_opts->nelts; i++)
2789    {
2790      opt_id = APR_ARRAY_IDX(received_opts, i, int);
2791
2792      /* All commands implicitly accept --help, so just skip over this
2793         when we see it. Note that we don't want to include this option
2794         in their "accepted options" list because it would be awfully
2795         redundant to display it in every commands' help text. */
2796      if (opt_id == 'h' || opt_id == '?')
2797        continue;
2798
2799      if (! svn_opt_subcommand_takes_option4(subcommand, opt_id, NULL))
2800        {
2801          const char *optstr;
2802          const apr_getopt_option_t *badopt =
2803            svn_opt_get_option_from_code3(opt_id, options_table, subcommand,
2804                                          pool);
2805          svn_opt_format_option(&optstr, badopt, FALSE, pool);
2806          if (subcommand->name[0] == '-')
2807            SVN_ERR(subcommand_help(NULL, NULL, pool));
2808          else
2809            svn_error_clear
2810              (svn_cmdline_fprintf
2811               (stderr, pool,
2812                _("Subcommand '%s' doesn't accept option '%s'\n"
2813                  "Type 'svnlook help %s' for usage.\n"),
2814                subcommand->name, optstr, subcommand->name));
2815          *exit_code = EXIT_FAILURE;
2816          return SVN_NO_ERROR;
2817        }
2818    }
2819
2820  check_cancel = svn_cmdline__setup_cancellation_handler();
2821
2822  /* Configure FSFS caches for maximum efficiency with svnadmin.
2823   * Also, apply the respective command line parameters, if given. */
2824  {
2825    svn_cache_config_t settings = *svn_cache_config_get();
2826
2827    settings.cache_size = opt_state.memory_cache_size;
2828    settings.single_threaded = TRUE;
2829
2830    svn_cache_config_set(&settings);
2831  }
2832
2833  /* Run the subcommand. */
2834  err = (*subcommand->cmd_func)(os, &opt_state, pool);
2835  if (err)
2836    {
2837      /* For argument-related problems, suggest using the 'help'
2838         subcommand. */
2839      if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2840          || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2841        {
2842          err = svn_error_quick_wrap(err,
2843                                     _("Try 'svnlook help' for more info"));
2844        }
2845      return err;
2846    }
2847
2848  return SVN_NO_ERROR;
2849}
2850
2851int
2852main(int argc, const char *argv[])
2853{
2854  apr_pool_t *pool;
2855  int exit_code = EXIT_SUCCESS;
2856  svn_error_t *err;
2857
2858  /* Initialize the app. */
2859  if (svn_cmdline_init("svnlook", stderr) != EXIT_SUCCESS)
2860    return EXIT_FAILURE;
2861
2862  /* Create our top-level pool.  Use a separate mutexless allocator,
2863   * given this application is single threaded.
2864   */
2865  pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2866
2867  err = sub_main(&exit_code, argc, argv, pool);
2868
2869  /* Flush stdout and report if it fails. It would be flushed on exit anyway
2870     but this makes sure that output is not silently lost if it fails. */
2871  err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
2872
2873  if (err)
2874    {
2875      exit_code = EXIT_FAILURE;
2876      svn_cmdline_handle_exit_error(err, NULL, "svnlook: ");
2877    }
2878
2879  svn_pool_destroy(pool);
2880
2881  svn_cmdline__cancellation_exit();
2882
2883  return exit_code;
2884}
2885