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