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