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