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