Remove no-longer-used svn_*_get_mergeinfo_for_tree APIs.
[svn.git] / subversion / svnlook / main.c
blob5c4fc94576d6d5ef660781fdcc7166625f6b838d
1 /*
2 * main.c: Subversion server inspection tool.
4 * ====================================================================
5 * Copyright (c) 2000-2006 CollabNet. All rights reserved.
7 * This software is licensed as described in the file COPYING, which
8 * you should have received as part of this distribution. The terms
9 * are also available at http://subversion.tigris.org/license-1.html.
10 * If newer versions of this license are posted there, you may use a
11 * newer version instead, at your option.
13 * This software consists of voluntary contributions made by many
14 * individuals. For exact contribution history, see the revision
15 * history and logs, available at http://subversion.tigris.org/.
16 * ====================================================================
19 #include <assert.h>
20 #include <stdlib.h>
22 #include <apr_general.h>
23 #include <apr_pools.h>
24 #include <apr_time.h>
25 #include <apr_file_io.h>
26 #include <apr_signal.h>
28 #define APR_WANT_STDIO
29 #define APR_WANT_STRFUNC
30 #include <apr_want.h>
32 #include "svn_cmdline.h"
33 #include "svn_types.h"
34 #include "svn_pools.h"
35 #include "svn_error.h"
36 #include "svn_error_codes.h"
37 #include "svn_path.h"
38 #include "svn_repos.h"
39 #include "svn_fs.h"
40 #include "svn_time.h"
41 #include "svn_utf.h"
42 #include "svn_subst.h"
43 #include "svn_opt.h"
44 #include "svn_props.h"
45 #include "svn_diff.h"
47 #include "svn_private_config.h"
50 /*** Some convenience macros and types. ***/
53 /* Option handling. */
55 static svn_opt_subcommand_t
56 subcommand_author,
57 subcommand_cat,
58 subcommand_changed,
59 subcommand_date,
60 subcommand_diff,
61 subcommand_dirschanged,
62 subcommand_help,
63 subcommand_history,
64 subcommand_info,
65 subcommand_lock,
66 subcommand_log,
67 subcommand_pget,
68 subcommand_plist,
69 subcommand_tree,
70 subcommand_uuid,
71 subcommand_youngest;
73 /* Option codes and descriptions. */
74 enum
76 svnlook__version = SVN_OPT_FIRST_LONGOPT_ID,
77 svnlook__show_ids,
78 svnlook__no_diff_deleted,
79 svnlook__no_diff_added,
80 svnlook__diff_copy_from,
81 svnlook__revprop_opt,
82 svnlook__full_paths,
83 svnlook__copy_info
87 * The entire list must be terminated with an entry of nulls.
89 static const apr_getopt_option_t options_table[] =
91 {NULL, '?', 0,
92 N_("show help on a subcommand")},
94 {"copy-info", svnlook__copy_info, 0,
95 N_("show details for copies")},
97 {"diff-copy-from", svnlook__diff_copy_from, 0,
98 N_("print differences against the copy source")},
100 {"full-paths", svnlook__full_paths, 0,
101 N_("show full paths instead of indenting them")},
103 {"help", 'h', 0,
104 N_("show help on a subcommand")},
106 {"limit", 'l', 1,
107 N_("maximum number of history entries")},
109 {"no-diff-added", svnlook__no_diff_added, 0,
110 N_("do not print differences for added files")},
112 {"no-diff-deleted", svnlook__no_diff_deleted, 0,
113 N_("do not print differences for deleted files")},
115 {"non-recursive", 'N', 0,
116 N_("operate on single directory only")},
118 {"revision", 'r', 1,
119 N_("specify revision number ARG")},
121 {"revprop", svnlook__revprop_opt, 0,
122 N_("operate on a revision property (use with -r or -t)")},
124 {"show-ids", svnlook__show_ids, 0,
125 N_("show node revision ids for each path")},
127 {"transaction", 't', 1,
128 N_("specify transaction name ARG")},
130 {"verbose", 'v', 0,
131 N_("be verbose")},
133 {"version", svnlook__version, 0,
134 N_("show program version information")},
136 #ifndef AS400
137 {"extensions", 'x', 1,
138 N_("Default: '-u'. When Subversion is invoking an\n"
140 " external diff program, ARG is simply passed along\n"
142 " to the program. But when Subversion is using its\n"
144 " default internal diff implementation, or when\n"
146 " Subversion is displaying blame annotations, ARG\n"
148 " could be any of the following:\n"
150 " -u (--unified):\n"
152 " Output 3 lines of unified context.\n"
154 " -b (--ignore-space-change):\n"
156 " Ignore changes in the amount of white space.\n"
158 " -w (--ignore-all-space):\n"
160 " Ignore all white space.\n"
162 " --ignore-eol-style:\n"
164 " Ignore changes in EOL style")},
165 #endif
167 {0, 0, 0, 0}
171 /* Array of available subcommands.
172 * The entire list must be terminated with an entry of nulls.
174 static const svn_opt_subcommand_desc_t cmd_table[] =
176 {"author", subcommand_author, {0},
177 N_("usage: svnlook author REPOS_PATH\n\n"
178 "Print the author.\n"),
179 {'r', 't'} },
181 {"cat", subcommand_cat, {0},
182 N_("usage: svnlook cat REPOS_PATH FILE_PATH\n\n"
183 "Print the contents of a file. Leading '/' on FILE_PATH is optional.\n"),
184 {'r', 't'} },
186 {"changed", subcommand_changed, {0},
187 N_("usage: svnlook changed REPOS_PATH\n\n"
188 "Print the paths that were changed.\n"),
189 {'r', 't', svnlook__copy_info} },
191 {"date", subcommand_date, {0},
192 N_("usage: svnlook date REPOS_PATH\n\n"
193 "Print the datestamp.\n"),
194 {'r', 't'} },
196 {"diff", subcommand_diff, {0},
197 N_("usage: svnlook diff REPOS_PATH\n\n"
198 "Print GNU-style diffs of changed files and properties.\n"),
199 {'r', 't', svnlook__no_diff_deleted, svnlook__no_diff_added,
200 svnlook__diff_copy_from, 'x'} },
202 {"dirs-changed", subcommand_dirschanged, {0},
203 N_("usage: svnlook dirs-changed REPOS_PATH\n\n"
204 "Print the directories that were themselves changed (property edits)\n"
205 "or whose file children were changed.\n"),
206 {'r', 't'} },
208 {"help", subcommand_help, {"?", "h"},
209 N_("usage: svnlook help [SUBCOMMAND...]\n\n"
210 "Describe the usage of this program or its subcommands.\n"),
211 {0} },
213 {"history", subcommand_history, {0},
214 N_("usage: svnlook history REPOS_PATH [PATH_IN_REPOS]\n\n"
215 "Print information about the history of a path in the repository (or\n"
216 "the root directory if no path is supplied).\n"),
217 {'r', svnlook__show_ids, 'l'} },
219 {"info", subcommand_info, {0},
220 N_("usage: svnlook info REPOS_PATH\n\n"
221 "Print the author, datestamp, log message size, and log message.\n"),
222 {'r', 't'} },
224 {"lock", subcommand_lock, {0},
225 N_("usage: svnlook lock REPOS_PATH PATH_IN_REPOS\n\n"
226 "If a lock exists on a path in the repository, describe it.\n"),
227 {0} },
229 {"log", subcommand_log, {0},
230 N_("usage: svnlook log REPOS_PATH\n\n"
231 "Print the log message.\n"),
232 {'r', 't'} },
234 {"propget", subcommand_pget, {"pget", "pg"},
235 N_("usage: svnlook propget REPOS_PATH PROPNAME [PATH_IN_REPOS]\n\n"
236 "Print the raw value of a property on a path in the repository.\n"
237 "With --revprop, prints the raw value of a revision property.\n"),
238 {'r', 't', svnlook__revprop_opt} },
240 {"proplist", subcommand_plist, {"plist", "pl"},
241 N_("usage: svnlook proplist REPOS_PATH [PATH_IN_REPOS]\n\n"
242 "List the properties of a path in the repository, or\n"
243 "with the --revprop option, revision properties.\n"
244 "With -v, show the property values too.\n"),
245 {'r', 't', 'v', svnlook__revprop_opt} },
247 {"tree", subcommand_tree, {0},
248 N_("usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n\n"
249 "Print the tree, starting at PATH_IN_REPOS (if supplied, at the root\n"
250 "of the tree otherwise), optionally showing node revision ids.\n"),
251 {'r', 't', 'N', svnlook__show_ids, svnlook__full_paths} },
253 {"uuid", subcommand_uuid, {0},
254 N_("usage: svnlook uuid REPOS_PATH\n\n"
255 "Print the repository's UUID.\n"),
256 {0} },
258 {"youngest", subcommand_youngest, {0},
259 N_("usage: svnlook youngest REPOS_PATH\n\n"
260 "Print the youngest revision number.\n"),
261 {0} },
263 { NULL, NULL, {0}, NULL, {0} }
267 /* Baton for passing option/argument state to a subcommand function. */
268 struct svnlook_opt_state
270 const char *repos_path; /* 'arg0' is always the path to the repository. */
271 const char *arg1; /* Usually an fs path, a propname, or NULL. */
272 const char *arg2; /* Usually an fs path or NULL. */
273 svn_revnum_t rev;
274 const char *txn;
275 svn_boolean_t version; /* --version */
276 svn_boolean_t show_ids; /* --show-ids */
277 apr_size_t limit; /* --limit */
278 svn_boolean_t help; /* --help */
279 svn_boolean_t no_diff_deleted; /* --no-diff-deleted */
280 svn_boolean_t no_diff_added; /* --no-diff-added */
281 svn_boolean_t diff_copy_from; /* --diff-copy-from */
282 svn_boolean_t verbose; /* --verbose */
283 svn_boolean_t revprop; /* --revprop */
284 svn_boolean_t full_paths; /* --full-paths */
285 svn_boolean_t copy_info; /* --copy-info */
286 svn_boolean_t non_recursive; /* --non-recursive */
287 const char *extensions; /* diff extension args (UTF-8!) */
291 typedef struct svnlook_ctxt_t
293 svn_repos_t *repos;
294 svn_fs_t *fs;
295 svn_boolean_t is_revision;
296 svn_boolean_t show_ids;
297 apr_size_t limit;
298 svn_boolean_t no_diff_deleted;
299 svn_boolean_t no_diff_added;
300 svn_boolean_t diff_copy_from;
301 svn_boolean_t full_paths;
302 svn_boolean_t copy_info;
303 svn_revnum_t rev_id;
304 svn_fs_txn_t *txn;
305 const char *txn_name /* UTF-8! */;
306 const apr_array_header_t *diff_options;
308 } svnlook_ctxt_t;
310 /* A flag to see if we've been cancelled by the client or not. */
311 static volatile sig_atomic_t cancelled = FALSE;
314 /*** Helper functions. ***/
316 /* A signal handler to support cancellation. */
317 static void
318 signal_handler(int signum)
320 apr_signal(signum, SIG_IGN);
321 cancelled = TRUE;
324 /* Our cancellation callback. */
325 static svn_error_t *
326 check_cancel(void *baton)
328 if (cancelled)
329 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
330 else
331 return SVN_NO_ERROR;
335 /* Version compatibility check */
336 static svn_error_t *
337 check_lib_versions(void)
339 static const svn_version_checklist_t checklist[] =
341 { "svn_subr", svn_subr_version },
342 { "svn_repos", svn_repos_version },
343 { "svn_fs", svn_fs_version },
344 { "svn_delta", svn_delta_version },
345 { "svn_diff", svn_diff_version },
346 { NULL, NULL }
349 SVN_VERSION_DEFINE(my_version);
350 return svn_ver_check_list(&my_version, checklist);
354 /* Get revision or transaction property PROP_NAME for the revision or
355 transaction specified in C, allocating in in POOL and placing it in
356 *PROP_VALUE. */
357 static svn_error_t *
358 get_property(svn_string_t **prop_value,
359 svnlook_ctxt_t *c,
360 const char *prop_name,
361 apr_pool_t *pool)
363 svn_string_t *raw_value;
365 /* Fetch transaction property... */
366 if (! c->is_revision)
367 SVN_ERR(svn_fs_txn_prop(&raw_value, c->txn, prop_name, pool));
369 /* ...or revision property -- it's your call. */
370 else
371 SVN_ERR(svn_fs_revision_prop(&raw_value, c->fs, c->rev_id,
372 prop_name, pool));
374 *prop_value = raw_value;
376 return SVN_NO_ERROR;
380 static svn_error_t *
381 get_root(svn_fs_root_t **root,
382 svnlook_ctxt_t *c,
383 apr_pool_t *pool)
385 /* Open up the appropriate root (revision or transaction). */
386 if (c->is_revision)
388 /* If we didn't get a valid revision number, we'll look at the
389 youngest revision. */
390 if (! SVN_IS_VALID_REVNUM(c->rev_id))
391 SVN_ERR(svn_fs_youngest_rev(&(c->rev_id), c->fs, pool));
393 SVN_ERR(svn_fs_revision_root(root, c->fs, c->rev_id, pool));
395 else
397 SVN_ERR(svn_fs_txn_root(root, c->txn, pool));
400 return SVN_NO_ERROR;
405 /*** Tree Routines ***/
407 /* Generate a generic delta tree. */
408 static svn_error_t *
409 generate_delta_tree(svn_repos_node_t **tree,
410 svn_repos_t *repos,
411 svn_fs_root_t *root,
412 svn_revnum_t base_rev,
413 svn_boolean_t use_copy_history,
414 apr_pool_t *pool)
416 svn_fs_root_t *base_root;
417 const svn_delta_editor_t *editor;
418 void *edit_baton;
419 apr_pool_t *edit_pool = svn_pool_create(pool);
420 svn_fs_t *fs = svn_repos_fs(repos);
422 /* Get the base root. */
423 SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, pool));
425 /* Request our editor. */
426 SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos,
427 base_root, root, pool, edit_pool));
429 /* Drive our editor. */
430 SVN_ERR(svn_repos_replay2(root, "", SVN_INVALID_REVNUM, FALSE,
431 editor, edit_baton, NULL, NULL, edit_pool));
433 /* Return the tree we just built. */
434 *tree = svn_repos_node_from_baton(edit_baton);
435 svn_pool_destroy(edit_pool);
436 return SVN_NO_ERROR;
441 /*** Tree Printing Routines ***/
443 /* Recursively print only directory nodes that either a) have property
444 mods, or b) contains files that have changed. */
445 static svn_error_t *
446 print_dirs_changed_tree(svn_repos_node_t *node,
447 const char *path /* UTF-8! */,
448 apr_pool_t *pool)
450 svn_repos_node_t *tmp_node;
451 int print_me = 0;
452 const char *full_path;
453 apr_pool_t *subpool;
455 SVN_ERR(check_cancel(NULL));
457 if (! node)
458 return SVN_NO_ERROR;
460 /* Not a directory? We're not interested. */
461 if (node->kind != svn_node_dir)
462 return SVN_NO_ERROR;
464 /* Got prop mods? Excellent. */
465 if (node->prop_mod)
466 print_me = 1;
468 if (! print_me)
470 /* Fly through the list of children, checking for modified files. */
471 tmp_node = node->child;
472 if (tmp_node)
474 if ((tmp_node->kind == svn_node_file)
475 || (tmp_node->text_mod)
476 || (tmp_node->action == 'A')
477 || (tmp_node->action == 'D'))
479 print_me = 1;
481 while (tmp_node->sibling && (! print_me ))
483 tmp_node = tmp_node->sibling;
484 if ((tmp_node->kind == svn_node_file)
485 || (tmp_node->text_mod)
486 || (tmp_node->action == 'A')
487 || (tmp_node->action == 'D'))
489 print_me = 1;
495 /* Print the node if it qualifies. */
496 if (print_me)
498 SVN_ERR(svn_cmdline_printf(pool, "%s/\n", path));
501 /* Return here if the node has no children. */
502 tmp_node = node->child;
503 if (! tmp_node)
504 return SVN_NO_ERROR;
506 /* Recursively handle the node's children. */
507 subpool = svn_pool_create(pool);
508 full_path = svn_path_join(path, tmp_node->name, subpool);
509 SVN_ERR(print_dirs_changed_tree(tmp_node, full_path, subpool));
510 while (tmp_node->sibling)
512 svn_pool_clear(subpool);
513 tmp_node = tmp_node->sibling;
514 full_path = svn_path_join(path, tmp_node->name, subpool);
515 SVN_ERR(print_dirs_changed_tree(tmp_node, full_path, subpool));
517 svn_pool_destroy(subpool);
519 return SVN_NO_ERROR;
523 /* Recursively print all nodes in the tree that have been modified
524 (do not include directories affected only by "bubble-up"). */
525 static svn_error_t *
526 print_changed_tree(svn_repos_node_t *node,
527 const char *path /* UTF-8! */,
528 svn_boolean_t copy_info,
529 apr_pool_t *pool)
531 const char *full_path;
532 char status[4] = "_ ";
533 int print_me = 1;
534 apr_pool_t *subpool;
536 SVN_ERR(check_cancel(NULL));
538 if (! node)
539 return SVN_NO_ERROR;
541 /* Print the node. */
542 if (node->action == 'A')
544 status[0] = 'A';
545 if (copy_info && node->copyfrom_path)
546 status[2] = '+';
548 else if (node->action == 'D')
549 status[0] = 'D';
550 else if (node->action == 'R')
552 if ((! node->text_mod) && (! node->prop_mod))
553 print_me = 0;
554 if (node->text_mod)
555 status[0] = 'U';
556 if (node->prop_mod)
557 status[1] = 'U';
559 else
560 print_me = 0;
562 /* Print this node unless told to skip it. */
563 if (print_me)
565 SVN_ERR(svn_cmdline_printf(pool, "%s %s%s\n",
566 status,
567 path,
568 node->kind == svn_node_dir ? "/" : ""));
569 if (copy_info && node->copyfrom_path)
570 /* Remove the leading slash from the copyfrom path for consistency
571 with the rest of the output. */
572 SVN_ERR(svn_cmdline_printf(pool, " (from %s%s:r%ld)\n",
573 (node->copyfrom_path[0] == '/'
574 ? node->copyfrom_path + 1
575 : node->copyfrom_path),
576 (node->kind == svn_node_dir ? "/" : ""),
577 node->copyfrom_rev));
580 /* Return here if the node has no children. */
581 node = node->child;
582 if (! node)
583 return SVN_NO_ERROR;
585 /* Recursively handle the node's children. */
586 subpool = svn_pool_create(pool);
587 full_path = svn_path_join(path, node->name, subpool);
588 SVN_ERR(print_changed_tree(node, full_path, copy_info, subpool));
589 while (node->sibling)
591 svn_pool_clear(subpool);
592 node = node->sibling;
593 full_path = svn_path_join(path, node->name, subpool);
594 SVN_ERR(print_changed_tree(node, full_path, copy_info, subpool));
596 svn_pool_destroy(subpool);
598 return SVN_NO_ERROR;
602 static svn_error_t *
603 dump_contents(apr_file_t *fh,
604 svn_fs_root_t *root,
605 const char *path /* UTF-8! */,
606 apr_pool_t *pool)
608 svn_stream_t *contents, *file_stream;
610 /* Grab the contents and copy them into fh. */
611 SVN_ERR(svn_fs_file_contents(&contents, root, path, pool));
612 file_stream = svn_stream_from_aprfile(fh, pool);
613 SVN_ERR(svn_stream_copy(contents, file_stream, pool));
614 return SVN_NO_ERROR;
618 /* Prepare temporary files *TMPFILE1 and *TMPFILE2 for diffing
619 PATH1@ROOT1 versus PATH2@ROOT2. If either ROOT1 or ROOT2 is NULL,
620 the temporary file for its path/root will be an empty one.
621 Otherwise, its temporary file will contain the contents of that
622 path/root in the repository.
624 An exception to this is when either path/root has an svn:mime-type
625 property set on it which indicates that the file contains
626 non-textual data -- in this case, the *IS_BINARY flag is set and no
627 temporary files are created.
629 Use POOL for all that allocation goodness. */
630 static svn_error_t *
631 prepare_tmpfiles(const char **tmpfile1,
632 const char **tmpfile2,
633 svn_boolean_t *is_binary,
634 svn_fs_root_t *root1,
635 const char *path1,
636 svn_fs_root_t *root2,
637 const char *path2,
638 const char *tmpdir,
639 apr_pool_t *pool)
641 svn_string_t *mimetype;
642 apr_file_t *fh;
644 /* Init the return values. */
645 *tmpfile1 = NULL;
646 *tmpfile2 = NULL;
647 *is_binary = FALSE;
649 assert(path1 && path2);
651 /* Check for binary mimetypes. If either file has a binary
652 mimetype, get outta here. */
653 if (root1)
655 SVN_ERR(svn_fs_node_prop(&mimetype, root1, path1,
656 SVN_PROP_MIME_TYPE, pool));
657 if (mimetype && svn_mime_type_is_binary(mimetype->data))
659 *is_binary = TRUE;
660 return SVN_NO_ERROR;
663 if (root2)
665 SVN_ERR(svn_fs_node_prop(&mimetype, root2, path2,
666 SVN_PROP_MIME_TYPE, pool));
667 if (mimetype && svn_mime_type_is_binary(mimetype->data))
669 *is_binary = TRUE;
670 return SVN_NO_ERROR;
674 /* Now, prepare the two temporary files, each of which will either
675 be empty, or will have real contents. */
676 SVN_ERR(svn_io_open_unique_file2(&fh, tmpfile2,
677 apr_psprintf(pool, "%s/diff", tmpdir),
678 ".tmp", svn_io_file_del_none, pool));
679 if (root2)
680 SVN_ERR(dump_contents(fh, root2, path2, pool));
681 apr_file_close(fh);
683 /* The second file is constructed from the first one's path. */
684 SVN_ERR(svn_io_open_unique_file2(&fh, tmpfile1, *tmpfile2,
685 ".tmp", svn_io_file_del_none, pool));
686 if (root1)
687 SVN_ERR(dump_contents(fh, root1, path1, pool));
688 apr_file_close(fh);
690 return SVN_NO_ERROR;
694 /* Generate a diff label for PATH in ROOT, allocating in POOL.
695 ROOT may be NULL, in which case revision 0 is used. */
696 static svn_error_t *
697 generate_label(const char **label,
698 svn_fs_root_t *root,
699 const char *path,
700 apr_pool_t *pool)
702 svn_string_t *date;
703 const char *datestr;
704 const char *name = NULL;
705 svn_revnum_t rev = SVN_INVALID_REVNUM;
707 if (root)
709 svn_fs_t *fs = svn_fs_root_fs(root);
710 if (svn_fs_is_revision_root(root))
712 rev = svn_fs_revision_root_revision(root);
713 SVN_ERR(svn_fs_revision_prop(&date, fs, rev,
714 SVN_PROP_REVISION_DATE, pool));
716 else
718 svn_fs_txn_t *txn;
719 name = svn_fs_txn_root_name(root, pool);
720 SVN_ERR(svn_fs_open_txn(&txn, fs, name, pool));
721 SVN_ERR(svn_fs_txn_prop(&date, txn, SVN_PROP_REVISION_DATE, pool));
724 else
726 rev = 0;
727 date = NULL;
730 if (date)
731 datestr = apr_psprintf(pool, "%.10s %.8s UTC", date->data, date->data + 11);
732 else
733 datestr = " ";
735 if (name)
736 *label = apr_psprintf(pool, "%s\t%s (txn %s)",
737 path, datestr, name);
738 else
739 *label = apr_psprintf(pool, "%s\t%s (rev %ld)",
740 path, datestr, rev);
741 return SVN_NO_ERROR;
746 * Constant diff output separator strings
748 static const char equal_string[] =
749 "===================================================================";
750 static const char under_string[] =
751 "___________________________________________________________________";
754 /* Helper function to display differences in properties of a file */
755 static svn_error_t *
756 display_prop_diffs(const apr_array_header_t *prop_diffs,
757 apr_hash_t *orig_props,
758 const char *path,
759 apr_pool_t *pool)
761 int i;
763 SVN_ERR(svn_cmdline_printf(pool, "\nProperty changes on: %s\n%s\n",
764 path, under_string));
766 for (i = 0; i < prop_diffs->nelts; i++)
768 const char *header_fmt;
769 const svn_string_t *orig_value;
770 const svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
772 SVN_ERR(check_cancel(NULL));
774 if (orig_props)
775 orig_value = apr_hash_get(orig_props, pc->name, APR_HASH_KEY_STRING);
776 else
777 orig_value = NULL;
779 if (! orig_value)
780 header_fmt = _("Added: %s\n");
781 else if (! pc->value)
782 header_fmt = _("Deleted: %s\n");
783 else
784 header_fmt = _("Modified: %s\n");
785 SVN_ERR(svn_cmdline_printf(pool, header_fmt, pc->name));
787 /* For now, we have a rather simple heuristic: if this is an
788 "svn:" property, then assume the value is UTF-8 and must
789 therefore be converted before printing. Otherwise, just
790 print whatever's there and hope for the best.
791 ### We don't use svn_cmdline_printf here, since we don't know if the
792 values are UTF-8. */
794 svn_boolean_t val_to_utf8 = svn_prop_is_svn_prop(pc->name);
795 const char *printable_val;
797 if (orig_value != NULL)
799 if (val_to_utf8)
800 SVN_ERR(svn_cmdline_cstring_from_utf8(&printable_val,
801 orig_value->data, pool));
802 else
803 printable_val = orig_value->data;
804 printf(" - %s\n", printable_val);
807 if (pc->value != NULL)
809 if (val_to_utf8)
810 SVN_ERR(svn_cmdline_cstring_from_utf8
811 (&printable_val, pc->value->data, pool));
812 else
813 printable_val = pc->value->data;
814 printf(" + %s\n", printable_val);
819 SVN_ERR(svn_cmdline_printf(pool, "\n"));
820 return svn_cmdline_fflush(stdout);
825 /* Recursively print all nodes in the tree that have been modified
826 (do not include directories affected only by "bubble-up"). */
827 static svn_error_t *
828 print_diff_tree(svn_fs_root_t *root,
829 svn_fs_root_t *base_root,
830 svn_repos_node_t *node,
831 const char *path /* UTF-8! */,
832 const char *base_path /* UTF-8! */,
833 const svnlook_ctxt_t *c,
834 const char *tmpdir,
835 apr_pool_t *pool)
837 const char *orig_path = NULL, *new_path = NULL;
838 svn_boolean_t do_diff = FALSE;
839 svn_boolean_t orig_empty = FALSE;
840 svn_boolean_t is_copy = FALSE;
841 svn_boolean_t binary = FALSE;
842 apr_pool_t *subpool;
843 svn_stringbuf_t *header;
845 SVN_ERR(check_cancel(NULL));
847 if (! node)
848 return SVN_NO_ERROR;
850 header = svn_stringbuf_create("", pool);
852 /* Print copyfrom history for the top node of a copied tree. */
853 if ((SVN_IS_VALID_REVNUM(node->copyfrom_rev))
854 && (node->copyfrom_path != NULL))
856 /* This is ... a copy. */
857 is_copy = TRUE;
859 /* Propagate the new base. Copyfrom paths usually start with a
860 slash; we remove it for consistency with the target path.
861 ### Yes, it would be *much* better for something in the path
862 library to be taking care of this! */
863 if (node->copyfrom_path[0] == '/')
864 base_path = apr_pstrdup(pool, node->copyfrom_path + 1);
865 else
866 base_path = apr_pstrdup(pool, node->copyfrom_path);
868 svn_stringbuf_appendcstr
869 (header,
870 apr_psprintf(pool, _("Copied: %s (from rev %ld, %s)\n"),
871 path, node->copyfrom_rev, base_path));
873 SVN_ERR(svn_fs_revision_root(&base_root,
874 svn_fs_root_fs(base_root),
875 node->copyfrom_rev, pool));
878 /*** First, we'll just print file content diffs. ***/
879 if (node->kind == svn_node_file)
881 /* Here's the generalized way we do our diffs:
883 - First, we'll check for svn:mime-type properties on the old
884 and new files. If either has such a property, and it
885 represents a binary type, we won't actually be doing a real
886 diff.
888 - Second, dump the contents of the new version of the file
889 into the temporary directory.
891 - Then, dump the contents of the old version of the file into
892 the temporary directory.
894 - Next, we run 'diff', passing the repository paths as the
895 labels.
897 - Finally, we delete the temporary files. */
898 if (node->action == 'R' && node->text_mod)
900 do_diff = TRUE;
901 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
902 base_root, base_path, root, path,
903 tmpdir, pool));
905 else if (c->diff_copy_from && node->action == 'A' && is_copy)
907 if (node->text_mod)
909 do_diff = TRUE;
910 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
911 base_root, base_path, root, path,
912 tmpdir, pool));
915 else if (! c->no_diff_added && node->action == 'A')
917 do_diff = TRUE;
918 orig_empty = TRUE;
919 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
920 NULL, base_path, root, path,
921 tmpdir, pool));
923 else if (! c->no_diff_deleted && node->action == 'D')
925 do_diff = TRUE;
926 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
927 base_root, base_path, NULL, path,
928 tmpdir, pool));
931 /* The header for the copy case has already been created, and we don't
932 want a header here for files with only property modifications. */
933 if (header->len == 0
934 && (node->action != 'R' || node->text_mod))
936 svn_stringbuf_appendcstr
937 (header, apr_psprintf(pool, "%s: %s\n",
938 ((node->action == 'A') ? _("Added") :
939 ((node->action == 'D') ? _("Deleted") :
940 ((node->action == 'R') ? _("Modified")
941 : _("Index")))),
942 path));
946 if (do_diff)
948 svn_stringbuf_appendcstr(header, equal_string);
949 svn_stringbuf_appendcstr(header, "\n");
951 if (binary)
952 svn_stringbuf_appendcstr(header, _("(Binary files differ)\n\n"));
953 else
955 svn_diff_t *diff;
956 svn_diff_file_options_t *opts = svn_diff_file_options_create(pool);
958 if (c->diff_options)
959 SVN_ERR(svn_diff_file_options_parse(opts, c->diff_options, pool));
961 SVN_ERR(svn_diff_file_diff_2(&diff, orig_path,
962 new_path, opts, pool));
964 if (svn_diff_contains_diffs(diff))
966 svn_stream_t *ostream;
967 const char *orig_label, *new_label;
969 /* Print diff header. */
970 SVN_ERR(svn_cmdline_printf(pool, header->data));
971 SVN_ERR(svn_stream_for_stdout(&ostream, pool));
973 if (orig_empty)
974 SVN_ERR(generate_label(&orig_label, NULL, path, pool));
975 else
976 SVN_ERR(generate_label(&orig_label, base_root,
977 base_path, pool));
978 SVN_ERR(generate_label(&new_label, root, path, pool));
979 SVN_ERR(svn_diff_file_output_unified2
980 (ostream, diff, orig_path, new_path,
981 orig_label, new_label,
982 svn_cmdline_output_encoding(pool), pool));
983 SVN_ERR(svn_stream_close(ostream));
984 SVN_ERR(svn_cmdline_printf(pool, "\n"));
987 SVN_ERR(svn_cmdline_fflush(stdout));
990 /* Make sure we delete any temporary files. */
991 if (orig_path)
992 SVN_ERR(svn_io_remove_file(orig_path, pool));
993 if (new_path)
994 SVN_ERR(svn_io_remove_file(new_path, pool));
996 /*** Now handle property diffs ***/
997 if ((node->prop_mod) && (node->action != 'D'))
999 apr_hash_t *local_proptable;
1000 apr_hash_t *base_proptable;
1001 apr_array_header_t *propchanges, *props;
1003 SVN_ERR(svn_fs_node_proplist(&local_proptable, root, path, pool));
1004 if (node->action == 'A')
1005 base_proptable = apr_hash_make(pool);
1006 else
1007 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1008 base_path, pool));
1009 SVN_ERR(svn_prop_diffs(&propchanges, local_proptable,
1010 base_proptable, pool));
1011 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, pool));
1012 if (props->nelts > 0)
1013 SVN_ERR(display_prop_diffs(props, base_proptable, path, pool));
1016 /* Return here if the node has no children. */
1017 node = node->child;
1018 if (! node)
1019 return SVN_NO_ERROR;
1021 /* Recursively handle the node's children. */
1022 subpool = svn_pool_create(pool);
1023 SVN_ERR(print_diff_tree(root, base_root, node,
1024 svn_path_join(path, node->name, subpool),
1025 svn_path_join(base_path, node->name, subpool),
1026 c, tmpdir, subpool));
1027 while (node->sibling)
1029 svn_pool_clear(subpool);
1030 node = node->sibling;
1031 SVN_ERR(print_diff_tree(root, base_root, node,
1032 svn_path_join(path, node->name, subpool),
1033 svn_path_join(base_path, node->name, subpool),
1034 c, tmpdir, subpool));
1036 svn_pool_destroy(subpool);
1038 return SVN_NO_ERROR;
1042 /* Print a repository directory, maybe recursively, possibly showing
1043 the node revision ids, and optionally using full paths.
1045 ROOT is the revision or transaction root used to build that tree.
1046 PATH and ID are the current path and node revision id being
1047 printed, and INDENTATION the number of spaces to prepent to that
1048 path's printed output. ID may be NULL if SHOW_IDS is FALSE (in
1049 which case, ids won't be printed at all). If RECURSE is TRUE,
1050 then print the tree recursively; otherwise, we'll stop after the
1051 first level (and use INDENTATION to keep track of how deep we are).
1053 Use POOL for all allocations. */
1054 static svn_error_t *
1055 print_tree(svn_fs_root_t *root,
1056 const char *path /* UTF-8! */,
1057 const svn_fs_id_t *id,
1058 svn_boolean_t is_dir,
1059 int indentation,
1060 svn_boolean_t show_ids,
1061 svn_boolean_t full_paths,
1062 svn_boolean_t recurse,
1063 apr_pool_t *pool)
1065 apr_pool_t *subpool;
1066 int i;
1067 apr_hash_t *entries;
1068 apr_hash_index_t *hi;
1070 SVN_ERR(check_cancel(NULL));
1072 /* Print the indentation. */
1073 if (!full_paths)
1074 for (i = 0; i < indentation; i++)
1075 SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
1077 /* Print the node. */
1078 SVN_ERR(svn_cmdline_printf(pool, "%s%s",
1079 full_paths ? path : svn_path_basename(path,
1080 pool),
1081 is_dir && strcmp(path, "/") ? "/" : ""));
1083 if (show_ids)
1085 svn_string_t *unparsed_id = NULL;
1086 if (id)
1087 unparsed_id = svn_fs_unparse_id(id, pool);
1088 SVN_ERR(svn_cmdline_printf(pool, " <%s>",
1089 unparsed_id
1090 ? unparsed_id->data
1091 : _("unknown")));
1093 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1095 /* Return here if PATH is not a directory. */
1096 if (! is_dir)
1097 return SVN_NO_ERROR;
1099 /* Recursively handle the node's children. */
1100 if (recurse || (indentation == 0))
1102 SVN_ERR(svn_fs_dir_entries(&entries, root, path, pool));
1103 subpool = svn_pool_create(pool);
1104 for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
1106 void *val;
1107 svn_fs_dirent_t *entry;
1109 svn_pool_clear(subpool);
1110 apr_hash_this(hi, NULL, NULL, &val);
1111 entry = val;
1112 SVN_ERR(print_tree(root, svn_path_join(path, entry->name, pool),
1113 entry->id, (entry->kind == svn_node_dir),
1114 indentation + 1, show_ids, full_paths,
1115 recurse, subpool));
1117 svn_pool_destroy(subpool);
1120 return SVN_NO_ERROR;
1125 /*** Subcommand handlers. ***/
1127 /* Print the revision's log message to stdout, followed by a newline. */
1128 static svn_error_t *
1129 do_log(svnlook_ctxt_t *c, svn_boolean_t print_size, apr_pool_t *pool)
1131 svn_string_t *prop_value;
1132 const char *prop_value_eol, *prop_value_native;
1133 svn_stream_t *stream;
1134 svn_error_t *err;
1135 apr_size_t len;
1137 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_LOG, pool));
1138 if (! (prop_value && prop_value->data))
1140 SVN_ERR(svn_cmdline_printf(pool, "%s\n", print_size ? "0" : ""));
1141 return SVN_NO_ERROR;
1144 /* We immitate what svn_cmdline_printf does here, since we need the byte
1145 size of what we are going to print. */
1147 SVN_ERR(svn_subst_translate_cstring2(prop_value->data, &prop_value_eol,
1148 APR_EOL_STR, TRUE,
1149 NULL, FALSE, pool));
1151 err = svn_cmdline_cstring_from_utf8(&prop_value_native, prop_value_eol,
1152 pool);
1153 if (err)
1155 svn_error_clear(err);
1156 prop_value_native = svn_cmdline_cstring_from_utf8_fuzzy(prop_value_eol,
1157 pool);
1160 len = strlen(prop_value_native);
1162 if (print_size)
1163 SVN_ERR(svn_cmdline_printf(pool, "%" APR_SIZE_T_FMT "\n", len));
1165 /* Use a stream to bypass all stdio translations. */
1166 SVN_ERR(svn_cmdline_fflush(stdout));
1167 SVN_ERR(svn_stream_for_stdout(&stream, pool));
1168 SVN_ERR(svn_stream_write(stream, prop_value_native, &len));
1169 SVN_ERR(svn_stream_close(stream));
1171 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1173 return SVN_NO_ERROR;
1177 /* Print the timestamp of the commit (in the revision case) or the
1178 empty string (in the transaction case) to stdout, followed by a
1179 newline. */
1180 static svn_error_t *
1181 do_date(svnlook_ctxt_t *c, apr_pool_t *pool)
1183 svn_string_t *prop_value;
1185 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_DATE, pool));
1186 if (prop_value && prop_value->data)
1188 /* Convert the date for humans. */
1189 apr_time_t aprtime;
1190 const char *time_utf8;
1192 SVN_ERR(svn_time_from_cstring(&aprtime, prop_value->data, pool));
1194 time_utf8 = svn_time_to_human_cstring(aprtime, pool);
1196 SVN_ERR(svn_cmdline_printf(pool, "%s", time_utf8));
1199 SVN_ERR(svn_cmdline_printf(pool, "\n"));
1200 return SVN_NO_ERROR;
1204 /* Print the author of the commit to stdout, followed by a newline. */
1205 static svn_error_t *
1206 do_author(svnlook_ctxt_t *c, apr_pool_t *pool)
1208 svn_string_t *prop_value;
1210 SVN_ERR(get_property(&prop_value, c,
1211 SVN_PROP_REVISION_AUTHOR, pool));
1212 if (prop_value && prop_value->data)
1213 SVN_ERR(svn_cmdline_printf(pool, "%s", prop_value->data));
1215 SVN_ERR(svn_cmdline_printf(pool, "\n"));
1216 return SVN_NO_ERROR;
1220 /* Print a list of all directories in which files, or directory
1221 properties, have been modified. */
1222 static svn_error_t *
1223 do_dirs_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1225 svn_fs_root_t *root;
1226 svn_revnum_t base_rev_id;
1227 svn_repos_node_t *tree;
1229 SVN_ERR(get_root(&root, c, pool));
1230 if (c->is_revision)
1231 base_rev_id = c->rev_id - 1;
1232 else
1233 base_rev_id = svn_fs_txn_base_revision(c->txn);
1235 if (! SVN_IS_VALID_REVNUM(base_rev_id))
1236 return svn_error_createf
1237 (SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1238 _("Transaction '%s' is not based on a revision; how odd"),
1239 c->txn_name);
1241 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id,
1242 TRUE, pool));
1243 if (tree)
1244 SVN_ERR(print_dirs_changed_tree(tree, "", pool));
1246 return SVN_NO_ERROR;
1250 /* Set *KIND to PATH's kind, if PATH exists.
1252 * If PATH does not exist, then error; the text of the error depends
1253 * on whether PATH looks like a URL or not.
1255 static svn_error_t *
1256 verify_path(svn_node_kind_t *kind,
1257 svn_fs_root_t *root,
1258 const char *path,
1259 apr_pool_t *pool)
1261 SVN_ERR(svn_fs_check_path(kind, root, path, pool));
1263 if (*kind == svn_node_none)
1265 if (svn_path_is_url(path)) /* check for a common mistake. */
1266 return svn_error_createf
1267 (SVN_ERR_FS_NOT_FOUND, NULL,
1268 _("'%s' is a URL, probably should be a path"), path);
1269 else
1270 return svn_error_createf
1271 (SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' does not exist"), path);
1274 return SVN_NO_ERROR;
1278 /* Print the contents of the file at PATH in the repository.
1279 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
1280 SVN_ERR_FS_NOT_FILE if PATH exists but is not a file. */
1281 static svn_error_t *
1282 do_cat(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
1284 svn_fs_root_t *root;
1285 svn_node_kind_t kind;
1286 svn_stream_t *fstream, *stdout_stream;
1287 char *buf = apr_palloc(pool, SVN__STREAM_CHUNK_SIZE);
1288 apr_size_t len = SVN__STREAM_CHUNK_SIZE;
1290 SVN_ERR(get_root(&root, c, pool));
1291 SVN_ERR(verify_path(&kind, root, path, pool));
1293 if (kind != svn_node_file)
1294 return svn_error_createf
1295 (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
1297 /* Else. */
1299 SVN_ERR(svn_fs_file_contents(&fstream, root, path, pool));
1300 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1303 SVN_ERR(check_cancel(NULL));
1304 SVN_ERR(svn_stream_read(fstream, buf, &len));
1305 SVN_ERR(svn_stream_write(stdout_stream, buf, &len));
1307 while (len == SVN__STREAM_CHUNK_SIZE);
1309 return SVN_NO_ERROR;
1313 /* Print a list of all paths modified in a format compatible with `svn
1314 update'. */
1315 static svn_error_t *
1316 do_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1318 svn_fs_root_t *root;
1319 svn_revnum_t base_rev_id;
1320 svn_repos_node_t *tree;
1322 SVN_ERR(get_root(&root, c, pool));
1323 if (c->is_revision)
1324 base_rev_id = c->rev_id - 1;
1325 else
1326 base_rev_id = svn_fs_txn_base_revision(c->txn);
1328 if (! SVN_IS_VALID_REVNUM(base_rev_id))
1329 return svn_error_createf
1330 (SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1331 _("Transaction '%s' is not based on a revision; how odd"),
1332 c->txn_name);
1334 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id,
1335 TRUE, pool));
1336 if (tree)
1337 SVN_ERR(print_changed_tree(tree, "", c->copy_info, pool));
1339 return SVN_NO_ERROR;
1343 /* Print some diff-y stuff in a TBD way. :-) */
1344 static svn_error_t *
1345 do_diff(svnlook_ctxt_t *c, apr_pool_t *pool)
1347 svn_fs_root_t *root, *base_root;
1348 svn_revnum_t base_rev_id;
1349 svn_repos_node_t *tree;
1351 SVN_ERR(get_root(&root, c, pool));
1352 if (c->is_revision)
1353 base_rev_id = c->rev_id - 1;
1354 else
1355 base_rev_id = svn_fs_txn_base_revision(c->txn);
1357 if (! SVN_IS_VALID_REVNUM(base_rev_id))
1358 return svn_error_createf
1359 (SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1360 _("Transaction '%s' is not based on a revision; how odd"),
1361 c->txn_name);
1363 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id,
1364 TRUE, pool));
1365 if (tree)
1367 const char *tmpdir;
1369 SVN_ERR(svn_fs_revision_root(&base_root, c->fs, base_rev_id, pool));
1370 SVN_ERR(svn_io_temp_dir(&tmpdir, pool));
1372 SVN_ERR(print_diff_tree(root, base_root, tree, "", "",
1373 c, tmpdir, pool));
1375 return SVN_NO_ERROR;
1380 /* Callback baton for print_history() (and do_history()). */
1381 struct print_history_baton
1383 svn_fs_t *fs;
1384 svn_boolean_t show_ids; /* whether to show node IDs */
1385 apr_size_t limit; /* max number of history items */
1386 apr_size_t count; /* number of history items processed */
1389 /* Implements svn_repos_history_func_t interface. Print the history
1390 that's reported through this callback, possibly finding and
1391 displaying node-rev-ids. */
1392 static svn_error_t *
1393 print_history(void *baton,
1394 const char *path,
1395 svn_revnum_t revision,
1396 apr_pool_t *pool)
1398 struct print_history_baton *phb = baton;
1400 SVN_ERR(check_cancel(NULL));
1402 if (phb->show_ids)
1404 const svn_fs_id_t *node_id;
1405 svn_fs_root_t *rev_root;
1406 svn_string_t *id_string;
1408 SVN_ERR(svn_fs_revision_root(&rev_root, phb->fs, revision, pool));
1409 SVN_ERR(svn_fs_node_id(&node_id, rev_root, path, pool));
1410 id_string = svn_fs_unparse_id(node_id, pool);
1411 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s <%s>\n",
1412 revision, path, id_string->data));
1414 else
1416 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s\n", revision, path));
1419 if (phb->limit > 0)
1421 phb->count++;
1422 if (phb->count >= phb->limit)
1423 /* Not L10N'd, since this error is supressed by the caller. */
1424 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL,
1425 "History item limit reached");
1428 return SVN_NO_ERROR;
1432 /* Print a tabular display of history location points for PATH in
1433 revision C->rev_id. Optionally, SHOW_IDS. Use POOL for
1434 allocations. */
1435 static svn_error_t *
1436 do_history(svnlook_ctxt_t *c,
1437 const char *path,
1438 apr_pool_t *pool)
1440 struct print_history_baton args;
1442 if (c->show_ids)
1444 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH <ID>\n"
1445 "-------- ---------\n")));
1447 else
1449 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH\n"
1450 "-------- ----\n")));
1453 /* Call our history crawler. We want the whole lifetime of the path
1454 (prior to the user-supplied revision, of course), across all
1455 copies. */
1456 args.fs = c->fs;
1457 args.show_ids = c->show_ids;
1458 args.limit = c->limit;
1459 args.count = 0;
1460 SVN_ERR(svn_repos_history2(c->fs, path, print_history, &args,
1461 NULL, NULL, 0, c->rev_id, TRUE, pool));
1462 return SVN_NO_ERROR;
1466 /* Print the value of property PROPNAME on PATH in the repository.
1467 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
1468 SVN_ERR_PROPERTY_NOT_FOUND if no such property on PATH.
1469 If PATH is NULL, operate on a revision property. */
1470 static svn_error_t *
1471 do_pget(svnlook_ctxt_t *c,
1472 const char *propname,
1473 const char *path,
1474 apr_pool_t *pool)
1476 svn_fs_root_t *root;
1477 svn_string_t *prop;
1478 svn_node_kind_t kind;
1479 svn_stream_t *stdout_stream;
1480 apr_size_t len;
1482 SVN_ERR(get_root(&root, c, pool));
1483 if (path != NULL)
1485 SVN_ERR(verify_path(&kind, root, path, pool));
1486 SVN_ERR(svn_fs_node_prop(&prop, root, path, propname, pool));
1488 else
1489 SVN_ERR(get_property(&prop, c, propname, pool));
1491 if (prop == NULL)
1493 const char *err_msg;
1494 if (path == NULL)
1496 /* We're operating on a revprop (e.g. c->is_revision). */
1497 err_msg = apr_psprintf(pool,
1498 _("Property '%s' not found on revision %ld"),
1499 propname, c->rev_id);
1501 else
1503 if (SVN_IS_VALID_REVNUM(c->rev_id))
1504 err_msg = apr_psprintf(pool,
1505 _("Property '%s' not found on path '%s' "
1506 "in revision %ld"),
1507 propname, path, c->rev_id);
1508 else
1509 err_msg = apr_psprintf(pool,
1510 _("Property '%s' not found on path '%s' "
1511 "in transaction %s"),
1512 propname, path, c->txn_name);
1514 return svn_error_create(SVN_ERR_PROPERTY_NOT_FOUND, NULL, err_msg);
1517 /* Else. */
1519 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1521 /* Unlike the command line client, we don't translate the property
1522 value or print a trailing newline here. We just output the raw
1523 bytes of whatever's in the repository, as svnlook is more likely
1524 to be used for automated inspections. */
1525 len = prop->len;
1526 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1528 return SVN_NO_ERROR;
1532 /* Print the property names of all properties on PATH in the repository.
1533 If VERBOSE, print their values too.
1534 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
1535 SVN_ERR_PROPERTY_NOT_FOUND if no such property on PATH.
1536 If PATH is NULL, operate on a revision properties. */
1537 static svn_error_t *
1538 do_plist(svnlook_ctxt_t *c,
1539 const char *path,
1540 svn_boolean_t verbose,
1541 apr_pool_t *pool)
1543 svn_stream_t *stdout_stream;
1544 svn_fs_root_t *root;
1545 apr_hash_t *props;
1546 apr_hash_index_t *hi;
1547 svn_node_kind_t kind;
1549 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1550 if (path != NULL)
1552 SVN_ERR(get_root(&root, c, pool));
1553 SVN_ERR(verify_path(&kind, root, path, pool));
1554 SVN_ERR(svn_fs_node_proplist(&props, root, path, pool));
1556 else
1557 SVN_ERR(svn_fs_revision_proplist(&props, c->fs, c->rev_id, pool));
1559 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
1561 const void *key;
1562 void *val;
1563 const char *pname;
1564 svn_string_t *propval;
1566 SVN_ERR(check_cancel(NULL));
1568 apr_hash_this(hi, &key, NULL, &val);
1569 pname = key;
1570 propval = val;
1572 /* Since we're already adding a trailing newline (and possible a
1573 colon and some spaces) anyway, just mimic the output of the
1574 command line client proplist. Compare to 'svnlook propget',
1575 which sends the raw bytes to stdout, untranslated. */
1576 /* We leave printf calls here, since we don't always know the encoding
1577 of the prop value. */
1578 if (svn_prop_needs_translation(pname))
1579 SVN_ERR(svn_subst_detranslate_string(&propval, propval, TRUE, pool));
1581 if (verbose)
1583 const char *pname_stdout;
1584 SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname, pool));
1585 printf(" %s : %s\n", pname_stdout, propval->data);
1587 else
1588 printf(" %s\n", pname);
1591 return SVN_NO_ERROR;
1595 static svn_error_t *
1596 do_tree(svnlook_ctxt_t *c,
1597 const char *path,
1598 svn_boolean_t show_ids,
1599 svn_boolean_t full_paths,
1600 svn_boolean_t recurse,
1601 apr_pool_t *pool)
1603 svn_fs_root_t *root;
1604 const svn_fs_id_t *id;
1605 svn_boolean_t is_dir;
1607 SVN_ERR(get_root(&root, c, pool));
1608 SVN_ERR(svn_fs_node_id(&id, root, path, pool));
1609 SVN_ERR(svn_fs_is_dir(&is_dir, root, path, pool));
1610 SVN_ERR(print_tree(root, path, id, is_dir, 0, show_ids, full_paths,
1611 recurse, pool));
1612 return SVN_NO_ERROR;
1616 /* Custom filesystem warning function. */
1617 static void
1618 warning_func(void *baton,
1619 svn_error_t *err)
1621 if (! err)
1622 return;
1623 svn_handle_error2(err, stderr, FALSE, "svnlook: ");
1627 /* Factory function for the context baton. */
1628 static svn_error_t *
1629 get_ctxt_baton(svnlook_ctxt_t **baton_p,
1630 struct svnlook_opt_state *opt_state,
1631 apr_pool_t *pool)
1633 svnlook_ctxt_t *baton = apr_pcalloc(pool, sizeof(*baton));
1635 SVN_ERR(svn_repos_open(&(baton->repos), opt_state->repos_path, pool));
1636 baton->fs = svn_repos_fs(baton->repos);
1637 svn_fs_set_warning_func(baton->fs, warning_func, NULL);
1638 baton->show_ids = opt_state->show_ids;
1639 baton->limit = opt_state->limit;
1640 baton->no_diff_deleted = opt_state->no_diff_deleted;
1641 baton->no_diff_added = opt_state->no_diff_added;
1642 baton->diff_copy_from = opt_state->diff_copy_from;
1643 baton->full_paths = opt_state->full_paths;
1644 baton->copy_info = opt_state->copy_info;
1645 baton->is_revision = opt_state->txn ? FALSE : TRUE;
1646 baton->rev_id = opt_state->rev;
1647 baton->txn_name = apr_pstrdup(pool, opt_state->txn);
1648 baton->diff_options = svn_cstring_split(opt_state->extensions
1649 ? opt_state->extensions : "",
1650 " \t\n\r", TRUE, pool);
1652 if (baton->txn_name)
1653 SVN_ERR(svn_fs_open_txn(&(baton->txn), baton->fs,
1654 baton->txn_name, pool));
1655 else if (baton->rev_id == SVN_INVALID_REVNUM)
1656 SVN_ERR(svn_fs_youngest_rev(&(baton->rev_id), baton->fs, pool));
1658 *baton_p = baton;
1659 return SVN_NO_ERROR;
1664 /*** Subcommands. ***/
1666 /* This implements `svn_opt_subcommand_t'. */
1667 static svn_error_t *
1668 subcommand_author(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1670 struct svnlook_opt_state *opt_state = baton;
1671 svnlook_ctxt_t *c;
1673 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1674 SVN_ERR(do_author(c, pool));
1675 return SVN_NO_ERROR;
1678 /* This implements `svn_opt_subcommand_t'. */
1679 static svn_error_t *
1680 subcommand_cat(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1682 struct svnlook_opt_state *opt_state = baton;
1683 svnlook_ctxt_t *c;
1685 if (opt_state->arg1 == NULL)
1686 return svn_error_createf
1687 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
1688 _("Missing repository path argument"));
1690 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1691 SVN_ERR(do_cat(c, opt_state->arg1, pool));
1692 return SVN_NO_ERROR;
1695 /* This implements `svn_opt_subcommand_t'. */
1696 static svn_error_t *
1697 subcommand_changed(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1699 struct svnlook_opt_state *opt_state = baton;
1700 svnlook_ctxt_t *c;
1702 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1703 SVN_ERR(do_changed(c, pool));
1704 return SVN_NO_ERROR;
1707 /* This implements `svn_opt_subcommand_t'. */
1708 static svn_error_t *
1709 subcommand_date(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1711 struct svnlook_opt_state *opt_state = baton;
1712 svnlook_ctxt_t *c;
1714 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1715 SVN_ERR(do_date(c, pool));
1716 return SVN_NO_ERROR;
1719 /* This implements `svn_opt_subcommand_t'. */
1720 static svn_error_t *
1721 subcommand_diff(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1723 struct svnlook_opt_state *opt_state = baton;
1724 svnlook_ctxt_t *c;
1726 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1727 SVN_ERR(do_diff(c, pool));
1728 return SVN_NO_ERROR;
1731 /* This implements `svn_opt_subcommand_t'. */
1732 static svn_error_t *
1733 subcommand_dirschanged(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1735 struct svnlook_opt_state *opt_state = baton;
1736 svnlook_ctxt_t *c;
1738 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1739 SVN_ERR(do_dirs_changed(c, pool));
1740 return SVN_NO_ERROR;
1743 /* This implements `svn_opt_subcommand_t'. */
1744 static svn_error_t *
1745 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1747 struct svnlook_opt_state *opt_state = baton;
1748 const char *header =
1749 _("general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
1750 "Note: any subcommand which takes the '--revision' and '--transaction'\n"
1751 " options will, if invoked without one of those options, act on\n"
1752 " the repository's youngest revision.\n"
1753 "Type 'svnlook help <subcommand>' for help on a specific subcommand.\n"
1754 "Type 'svnlook --version' to see the program version and FS modules.\n"
1755 "\n"
1756 "Available subcommands:\n");
1758 const char *fs_desc_start
1759 = _("The following repository back-end (FS) modules are available:\n\n");
1761 svn_stringbuf_t *version_footer;
1763 version_footer = svn_stringbuf_create(fs_desc_start, pool);
1764 SVN_ERR(svn_fs_print_modules(version_footer, pool));
1766 SVN_ERR(svn_opt_print_help(os, "svnlook",
1767 opt_state ? opt_state->version : FALSE,
1768 FALSE, version_footer->data,
1769 header, cmd_table, options_table, NULL,
1770 pool));
1772 return SVN_NO_ERROR;
1775 /* This implements `svn_opt_subcommand_t'. */
1776 static svn_error_t *
1777 subcommand_history(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1779 struct svnlook_opt_state *opt_state = baton;
1780 svnlook_ctxt_t *c;
1781 const char *path = "/";
1783 if (opt_state->arg1)
1784 path = opt_state->arg1;
1786 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1787 SVN_ERR(do_history(c, path, pool));
1788 return SVN_NO_ERROR;
1792 /* This implements `svn_opt_subcommand_t'. */
1793 static svn_error_t *
1794 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1796 struct svnlook_opt_state *opt_state = baton;
1797 svnlook_ctxt_t *c;
1798 const char *path;
1799 svn_lock_t *lock;
1801 if (opt_state->arg1)
1802 path = opt_state->arg1;
1803 else
1804 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
1805 _("Missing path argument"));
1807 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1809 SVN_ERR(svn_fs_get_lock(&lock, c->fs, path, pool));
1811 if (lock)
1813 const char *cr_date, *exp_date = "";
1814 int comment_lines = 0;
1816 cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
1818 if (lock->expiration_date)
1819 exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
1821 if (lock->comment)
1822 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
1824 SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
1825 SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
1826 SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
1827 SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
1828 SVN_ERR(svn_cmdline_printf(pool,
1829 (comment_lines != 1)
1830 ? _("Comment (%i lines):\n%s\n")
1831 : _("Comment (%i line):\n%s\n"),
1832 comment_lines,
1833 lock->comment ? lock->comment : ""));
1836 return SVN_NO_ERROR;
1840 /* This implements `svn_opt_subcommand_t'. */
1841 static svn_error_t *
1842 subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1844 struct svnlook_opt_state *opt_state = baton;
1845 svnlook_ctxt_t *c;
1847 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1848 SVN_ERR(do_author(c, pool));
1849 SVN_ERR(do_date(c, pool));
1850 SVN_ERR(do_log(c, TRUE, pool));
1851 return SVN_NO_ERROR;
1854 /* This implements `svn_opt_subcommand_t'. */
1855 static svn_error_t *
1856 subcommand_log(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1858 struct svnlook_opt_state *opt_state = baton;
1859 svnlook_ctxt_t *c;
1861 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1862 SVN_ERR(do_log(c, FALSE, pool));
1863 return SVN_NO_ERROR;
1866 /* This implements `svn_opt_subcommand_t'. */
1867 static svn_error_t *
1868 subcommand_pget(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1870 struct svnlook_opt_state *opt_state = baton;
1871 svnlook_ctxt_t *c;
1873 if (opt_state->arg1 == NULL)
1875 return svn_error_createf
1876 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
1877 opt_state->revprop ? _("Missing propname argument") :
1878 _("Missing propname and repository path arguments"));
1880 else if (!opt_state->revprop && opt_state->arg2 == NULL)
1882 return svn_error_create
1883 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
1884 _("Missing propname or repository path argument"));
1887 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1888 SVN_ERR(do_pget(c, opt_state->arg1,
1889 opt_state->revprop ? NULL : opt_state->arg2, pool));
1890 return SVN_NO_ERROR;
1893 /* This implements `svn_opt_subcommand_t'. */
1894 static svn_error_t *
1895 subcommand_plist(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1897 struct svnlook_opt_state *opt_state = baton;
1898 svnlook_ctxt_t *c;
1900 if (!opt_state->revprop && opt_state->arg1 == NULL)
1901 return svn_error_create
1902 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
1903 _("Missing repository path argument"));
1905 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1906 SVN_ERR(do_plist(c, opt_state->revprop ? NULL : opt_state->arg1,
1907 opt_state->verbose, pool));
1908 return SVN_NO_ERROR;
1911 /* This implements `svn_opt_subcommand_t'. */
1912 static svn_error_t *
1913 subcommand_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1915 struct svnlook_opt_state *opt_state = baton;
1916 svnlook_ctxt_t *c;
1918 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1919 SVN_ERR(do_tree(c, opt_state->arg1 ? opt_state->arg1 : "",
1920 opt_state->show_ids, opt_state->full_paths,
1921 opt_state->non_recursive ? FALSE : TRUE, pool));
1922 return SVN_NO_ERROR;
1925 /* This implements `svn_opt_subcommand_t'. */
1926 static svn_error_t *
1927 subcommand_youngest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1929 struct svnlook_opt_state *opt_state = baton;
1930 svnlook_ctxt_t *c;
1932 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1933 SVN_ERR(svn_cmdline_printf(pool, "%ld\n", c->rev_id));
1934 return SVN_NO_ERROR;
1937 /* This implements `svn_opt_subcommand_t'. */
1938 static svn_error_t *
1939 subcommand_uuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
1941 struct svnlook_opt_state *opt_state = baton;
1942 svnlook_ctxt_t *c;
1943 const char *uuid;
1945 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
1946 SVN_ERR(svn_fs_get_uuid(c->fs, &uuid, pool));
1947 SVN_ERR(svn_cmdline_printf(pool, "%s\n", uuid));
1948 return SVN_NO_ERROR;
1953 /*** Main. ***/
1956 main(int argc, const char *argv[])
1958 svn_error_t *err;
1959 apr_status_t apr_err;
1960 apr_allocator_t *allocator;
1961 apr_pool_t *pool;
1963 const svn_opt_subcommand_desc_t *subcommand = NULL;
1964 struct svnlook_opt_state opt_state;
1965 apr_getopt_t *os;
1966 int opt_id;
1967 apr_array_header_t *received_opts;
1968 int i;
1970 /* Initialize the app. */
1971 if (svn_cmdline_init("svnlook", stderr) != EXIT_SUCCESS)
1972 return EXIT_FAILURE;
1974 /* Create our top-level pool. Use a seperate mutexless allocator,
1975 * given this application is single threaded.
1977 if (apr_allocator_create(&allocator))
1978 return EXIT_FAILURE;
1980 apr_allocator_max_free_set(allocator, SVN_ALLOCATOR_RECOMMENDED_MAX_FREE);
1982 pool = svn_pool_create_ex(NULL, allocator);
1983 apr_allocator_owner_set(allocator, pool);
1985 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
1987 /* Check library versions */
1988 err = check_lib_versions();
1989 if (err)
1990 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
1992 /* Initialize the FS library. */
1993 err = svn_fs_initialize(pool);
1994 if (err)
1995 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
1997 if (argc <= 1)
1999 subcommand_help(NULL, NULL, pool);
2000 svn_pool_destroy(pool);
2001 return EXIT_FAILURE;
2004 /* Initialize opt_state. */
2005 memset(&opt_state, 0, sizeof(opt_state));
2006 opt_state.rev = SVN_INVALID_REVNUM;
2008 /* Parse options. */
2009 err = svn_cmdline__getopt_init(&os, argc, argv, pool);
2010 if (err)
2011 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2013 os->interleave = 1;
2014 while (1)
2016 const char *opt_arg;
2018 /* Parse the next option. */
2019 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2020 if (APR_STATUS_IS_EOF(apr_err))
2021 break;
2022 else if (apr_err)
2024 subcommand_help(NULL, NULL, pool);
2025 svn_pool_destroy(pool);
2026 return EXIT_FAILURE;
2029 /* Stash the option code in an array before parsing it. */
2030 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2032 switch (opt_id)
2034 case 'r':
2036 char *digits_end = NULL;
2037 opt_state.rev = strtol(opt_arg, &digits_end, 10);
2038 if ((! SVN_IS_VALID_REVNUM(opt_state.rev))
2039 || (! digits_end)
2040 || *digits_end)
2041 SVN_INT_ERR(svn_error_create
2042 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2043 _("Invalid revision number supplied")));
2045 break;
2047 case 't':
2048 opt_state.txn = opt_arg;
2049 break;
2051 case 'N':
2052 opt_state.non_recursive = TRUE;
2053 break;
2055 case 'v':
2056 opt_state.verbose = TRUE;
2057 break;
2059 case 'h':
2060 case '?':
2061 opt_state.help = TRUE;
2062 break;
2064 case svnlook__revprop_opt:
2065 opt_state.revprop = TRUE;
2066 break;
2068 case svnlook__version:
2069 opt_state.version = TRUE;
2070 break;
2072 case svnlook__show_ids:
2073 opt_state.show_ids = TRUE;
2074 break;
2076 case 'l':
2078 char *end;
2079 opt_state.limit = strtol(opt_arg, &end, 10);
2080 if (end == opt_arg || *end != '\0')
2082 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2083 _("Non-numeric limit argument given"));
2084 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2086 if (opt_state.limit <= 0)
2088 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2089 _("Argument to --limit must be positive"));
2090 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2093 break;
2095 case svnlook__no_diff_deleted:
2096 opt_state.no_diff_deleted = TRUE;
2097 break;
2099 case svnlook__no_diff_added:
2100 opt_state.no_diff_added = TRUE;
2101 break;
2103 case svnlook__diff_copy_from:
2104 opt_state.diff_copy_from = TRUE;
2105 break;
2107 case svnlook__full_paths:
2108 opt_state.full_paths = TRUE;
2109 break;
2111 case svnlook__copy_info:
2112 opt_state.copy_info = TRUE;
2113 break;
2115 case 'x':
2116 opt_state.extensions = opt_arg;
2117 break;
2119 default:
2120 subcommand_help(NULL, NULL, pool);
2121 svn_pool_destroy(pool);
2122 return EXIT_FAILURE;
2127 /* The --transaction and --revision options may not co-exist. */
2128 if ((opt_state.rev != SVN_INVALID_REVNUM) && opt_state.txn)
2129 SVN_INT_ERR(svn_error_create
2130 (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
2131 _("The '--transaction' (-t) and '--revision' (-r) arguments "
2132 "can not co-exist")));
2134 /* If the user asked for help, then the rest of the arguments are
2135 the names of subcommands to get help on (if any), or else they're
2136 just typos/mistakes. Whatever the case, the subcommand to
2137 actually run is subcommand_help(). */
2138 if (opt_state.help)
2139 subcommand = svn_opt_get_canonical_subcommand(cmd_table, "help");
2141 /* If we're not running the `help' subcommand, then look for a
2142 subcommand in the first argument. */
2143 if (subcommand == NULL)
2145 if (os->ind >= os->argc)
2147 if (opt_state.version)
2149 /* Use the "help" subcommand to handle the "--version" option. */
2150 static const svn_opt_subcommand_desc_t pseudo_cmd =
2151 { "--version", subcommand_help, {0}, "",
2152 {svnlook__version, /* must accept its own option */
2153 } };
2155 subcommand = &pseudo_cmd;
2157 else
2159 svn_error_clear
2160 (svn_cmdline_fprintf(stderr, pool,
2161 _("Subcommand argument required\n")));
2162 subcommand_help(NULL, NULL, pool);
2163 svn_pool_destroy(pool);
2164 return EXIT_FAILURE;
2167 else
2169 const char *first_arg = os->argv[os->ind++];
2170 subcommand = svn_opt_get_canonical_subcommand(cmd_table, first_arg);
2171 if (subcommand == NULL)
2173 const char *first_arg_utf8;
2174 err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
2175 pool);
2176 if (err)
2177 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2178 svn_error_clear
2179 (svn_cmdline_fprintf(stderr, pool,
2180 _("Unknown command: '%s'\n"),
2181 first_arg_utf8));
2182 subcommand_help(NULL, NULL, pool);
2183 svn_pool_destroy(pool);
2184 return EXIT_FAILURE;
2189 /* If there's a second argument, it's the repository. There may be
2190 more arguments following the repository; usually the next one is
2191 a path within the repository, or it's a propname and the one
2192 after that is the path. Since we don't know, we just call them
2193 arg1 and arg2, meaning the first and second arguments following
2194 the repository. */
2195 if (subcommand->cmd_func != subcommand_help)
2197 const char *repos_path = NULL;
2198 const char *arg1 = NULL, *arg2 = NULL;
2200 /* Get the repository. */
2201 if (os->ind < os->argc)
2203 SVN_INT_ERR(svn_utf_cstring_to_utf8(&repos_path,
2204 os->argv[os->ind++],
2205 pool));
2206 repos_path = svn_path_internal_style(repos_path, pool);
2209 if (repos_path == NULL)
2211 svn_error_clear
2212 (svn_cmdline_fprintf(stderr, pool,
2213 _("Repository argument required\n")));
2214 subcommand_help(NULL, NULL, pool);
2215 svn_pool_destroy(pool);
2216 return EXIT_FAILURE;
2218 else if (svn_path_is_url(repos_path))
2220 svn_error_clear
2221 (svn_cmdline_fprintf(stderr, pool,
2222 _("'%s' is a URL when it should be a path\n"),
2223 repos_path));
2224 svn_pool_destroy(pool);
2225 return EXIT_FAILURE;
2228 opt_state.repos_path = repos_path;
2230 /* Get next arg (arg1), if any. */
2231 if (os->ind < os->argc)
2233 SVN_INT_ERR(svn_utf_cstring_to_utf8
2234 (&arg1, os->argv[os->ind++], pool));
2235 arg1 = svn_path_internal_style(arg1, pool);
2237 opt_state.arg1 = arg1;
2239 /* Get next arg (arg2), if any. */
2240 if (os->ind < os->argc)
2242 SVN_INT_ERR(svn_utf_cstring_to_utf8
2243 (&arg2, os->argv[os->ind++], pool));
2244 arg2 = svn_path_internal_style(arg2, pool);
2246 opt_state.arg2 = arg2;
2249 /* Check that the subcommand wasn't passed any inappropriate options. */
2250 for (i = 0; i < received_opts->nelts; i++)
2252 opt_id = APR_ARRAY_IDX(received_opts, i, int);
2254 /* All commands implicitly accept --help, so just skip over this
2255 when we see it. Note that we don't want to include this option
2256 in their "accepted options" list because it would be awfully
2257 redundant to display it in every commands' help text. */
2258 if (opt_id == 'h' || opt_id == '?')
2259 continue;
2261 if (! svn_opt_subcommand_takes_option(subcommand, opt_id))
2263 const char *optstr;
2264 const apr_getopt_option_t *badopt =
2265 svn_opt_get_option_from_code(opt_id, options_table);
2266 svn_opt_format_option(&optstr, badopt, FALSE, pool);
2267 if (subcommand->name[0] == '-')
2268 subcommand_help(NULL, NULL, pool);
2269 else
2270 svn_error_clear
2271 (svn_cmdline_fprintf
2272 (stderr, pool,
2273 _("Subcommand '%s' doesn't accept option '%s'\n"
2274 "Type 'svnlook help %s' for usage.\n"),
2275 subcommand->name, optstr, subcommand->name));
2276 svn_pool_destroy(pool);
2277 return EXIT_FAILURE;
2281 /* Set up our cancellation support. */
2282 apr_signal(SIGINT, signal_handler);
2283 #ifdef SIGBREAK
2284 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
2285 apr_signal(SIGBREAK, signal_handler);
2286 #endif
2287 #ifdef SIGHUP
2288 apr_signal(SIGHUP, signal_handler);
2289 #endif
2290 #ifdef SIGTERM
2291 apr_signal(SIGTERM, signal_handler);
2292 #endif
2294 #ifdef SIGPIPE
2295 /* Disable SIGPIPE generation for the platforms that have it. */
2296 apr_signal(SIGPIPE, SIG_IGN);
2297 #endif
2299 #ifdef SIGXFSZ
2300 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
2301 * working with large files when compiled against an APR that doesn't have
2302 * large file support will crash the program, which is uncool. */
2303 apr_signal(SIGXFSZ, SIG_IGN);
2304 #endif
2306 /* Run the subcommand. */
2307 err = (*subcommand->cmd_func)(os, &opt_state, pool);
2308 if (err)
2310 /* For argument-related problems, suggest using the 'help'
2311 subcommand. */
2312 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2313 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2315 err = svn_error_quick_wrap(err,
2316 _("Try 'svnlook help' for more info"));
2318 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2320 else
2322 svn_pool_destroy(pool);
2323 /* Ensure everything is printed on stdout, so the user sees any
2324 print errors. */
2325 SVN_INT_ERR(svn_cmdline_fflush(stdout));
2326 return EXIT_SUCCESS;