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