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