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