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