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