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