2 * svnlook.c: Subversion server inspection tool main file.
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
13 * http://www.apache.org/licenses/LICENSE-2.0
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
21 * ====================================================================
27 #include <apr_general.h>
28 #include <apr_pools.h>
30 #include <apr_file_io.h>
31 #include <apr_signal.h>
33 #define APR_WANT_STDIO
34 #define APR_WANT_STRFUNC
38 #include "svn_cmdline.h"
39 #include "svn_types.h"
40 #include "svn_pools.h"
41 #include "svn_error.h"
42 #include "svn_error_codes.h"
43 #include "svn_dirent_uri.h"
45 #include "svn_repos.h"
49 #include "svn_subst.h"
50 #include "svn_sorts.h"
52 #include "svn_props.h"
54 #include "svn_version.h"
57 #include "private/svn_diff_private.h"
58 #include "private/svn_cmdline_private.h"
59 #include "private/svn_fspath.h"
60 #include "private/svn_io_private.h"
62 #include "svn_private_config.h"
65 /*** Some convenience macros and types. ***/
68 /* Option handling. */
70 static svn_opt_subcommand_t
76 subcommand_dirschanged,
89 /* Option codes and descriptions. */
92 svnlook__version = SVN_OPT_FIRST_LONGOPT_ID,
94 svnlook__no_diff_deleted,
95 svnlook__no_diff_added,
96 svnlook__diff_copy_from,
101 svnlook__ignore_properties,
102 svnlook__properties_only,
104 svnlook__show_inherited_props
108 * The entire list must be terminated with an entry of nulls.
110 static const apr_getopt_option_t options_table[] =
113 N_("show help on a subcommand")},
115 {"copy-info", svnlook__copy_info, 0,
116 N_("show details for copies")},
118 {"diff-copy-from", svnlook__diff_copy_from, 0,
119 N_("print differences against the copy source")},
121 {"full-paths", svnlook__full_paths, 0,
122 N_("show full paths instead of indenting them")},
125 N_("show help on a subcommand")},
128 N_("maximum number of history entries")},
130 {"no-diff-added", svnlook__no_diff_added, 0,
131 N_("do not print differences for added files")},
133 {"no-diff-deleted", svnlook__no_diff_deleted, 0,
134 N_("do not print differences for deleted files")},
136 {"diff-cmd", svnlook__diff_cmd, 1,
137 N_("use ARG as diff command")},
139 {"ignore-properties", svnlook__ignore_properties, 0,
140 N_("ignore properties during the operation")},
142 {"properties-only", svnlook__properties_only, 0,
143 N_("show only properties during the operation")},
145 {"non-recursive", 'N', 0,
146 N_("operate on single directory only")},
149 N_("specify revision number ARG")},
151 {"revprop", svnlook__revprop_opt, 0,
152 N_("operate on a revision property (use with -r or -t)")},
154 {"show-ids", svnlook__show_ids, 0,
155 N_("show node revision ids for each path")},
157 {"show-inherited-props", svnlook__show_inherited_props, 0,
158 N_("show path's inherited properties")},
160 {"transaction", 't', 1,
161 N_("specify transaction name ARG")},
166 {"version", svnlook__version, 0,
167 N_("show program version information")},
169 {"xml", svnlook__xml_opt, 0,
170 N_("output in XML")},
172 {"extensions", 'x', 1,
173 N_("Specify differencing options for external diff or\n"
175 "internal diff. Default: '-u'. Options are\n"
177 "separated by spaces. Internal diff takes:\n"
179 " -u, --unified: Show 3 lines of unified context\n"
181 " -b, --ignore-space-change: Ignore changes in\n"
183 " amount of white space\n"
185 " -w, --ignore-all-space: Ignore all white space\n"
187 " --ignore-eol-style: Ignore changes in EOL style\n"
189 " -p, --show-c-function: Show C function name")},
192 N_("no progress (only errors) to stderr")},
198 /* Array of available subcommands.
199 * The entire list must be terminated with an entry of nulls.
201 static const svn_opt_subcommand_desc2_t cmd_table[] =
203 {"author", subcommand_author, {0},
204 N_("usage: svnlook author REPOS_PATH\n\n"
205 "Print the author.\n"),
208 {"cat", subcommand_cat, {0},
209 N_("usage: svnlook cat REPOS_PATH FILE_PATH\n\n"
210 "Print the contents of a file. Leading '/' on FILE_PATH is optional.\n"),
213 {"changed", subcommand_changed, {0},
214 N_("usage: svnlook changed REPOS_PATH\n\n"
215 "Print the paths that were changed.\n"),
216 {'r', 't', svnlook__copy_info} },
218 {"date", subcommand_date, {0},
219 N_("usage: svnlook date REPOS_PATH\n\n"
220 "Print the datestamp.\n"),
223 {"diff", subcommand_diff, {0},
224 N_("usage: svnlook diff REPOS_PATH\n\n"
225 "Print GNU-style diffs of changed files and properties.\n"),
226 {'r', 't', svnlook__no_diff_deleted, svnlook__no_diff_added,
227 svnlook__diff_copy_from, svnlook__diff_cmd, 'x',
228 svnlook__ignore_properties, svnlook__properties_only} },
230 {"dirs-changed", subcommand_dirschanged, {0},
231 N_("usage: svnlook dirs-changed REPOS_PATH\n\n"
232 "Print the directories that were themselves changed (property edits)\n"
233 "or whose file children were changed.\n"),
236 {"filesize", subcommand_filesize, {0},
237 N_("usage: svnlook filesize REPOS_PATH PATH_IN_REPOS\n\n"
238 "Print the size (in bytes) of the file located at PATH_IN_REPOS as\n"
239 "it is represented in the repository.\n"),
242 {"help", subcommand_help, {"?", "h"},
243 N_("usage: svnlook help [SUBCOMMAND...]\n\n"
244 "Describe the usage of this program or its subcommands.\n"),
247 {"history", subcommand_history, {0},
248 N_("usage: svnlook history REPOS_PATH [PATH_IN_REPOS]\n\n"
249 "Print information about the history of a path in the repository (or\n"
250 "the root directory if no path is supplied).\n"),
251 {'r', svnlook__show_ids, 'l'} },
253 {"info", subcommand_info, {0},
254 N_("usage: svnlook info REPOS_PATH\n\n"
255 "Print the author, datestamp, log message size, and log message.\n"),
258 {"lock", subcommand_lock, {0},
259 N_("usage: svnlook lock REPOS_PATH PATH_IN_REPOS\n\n"
260 "If a lock exists on a path in the repository, describe it.\n"),
263 {"log", subcommand_log, {0},
264 N_("usage: svnlook log REPOS_PATH\n\n"
265 "Print the log message.\n"),
268 {"propget", subcommand_pget, {"pget", "pg"},
269 N_("usage: 1. svnlook propget REPOS_PATH PROPNAME PATH_IN_REPOS\n"
271 /* The line above is actually needed, so do NOT delete it! */
272 " 2. svnlook propget --revprop REPOS_PATH PROPNAME\n\n"
273 "Print the raw value of a property on a path in the repository.\n"
274 "With --revprop, print the raw value of a revision property.\n"),
275 {'r', 't', 'v', svnlook__revprop_opt, svnlook__show_inherited_props} },
277 {"proplist", subcommand_plist, {"plist", "pl"},
278 N_("usage: 1. svnlook proplist REPOS_PATH PATH_IN_REPOS\n"
280 /* The line above is actually needed, so do NOT delete it! */
281 " 2. svnlook proplist --revprop REPOS_PATH\n\n"
282 "List the properties of a path in the repository, or\n"
283 "with the --revprop option, revision properties.\n"
284 "With -v, show the property values too.\n"),
285 {'r', 't', 'v', svnlook__revprop_opt, svnlook__xml_opt,
286 svnlook__show_inherited_props} },
288 {"tree", subcommand_tree, {0},
289 N_("usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n\n"
290 "Print the tree, starting at PATH_IN_REPOS (if supplied, at the root\n"
291 "of the tree otherwise), optionally showing node revision ids.\n"),
292 {'r', 't', 'N', svnlook__show_ids, svnlook__full_paths} },
294 {"uuid", subcommand_uuid, {0},
295 N_("usage: svnlook uuid REPOS_PATH\n\n"
296 "Print the repository's UUID.\n"),
299 {"youngest", subcommand_youngest, {0},
300 N_("usage: svnlook youngest REPOS_PATH\n\n"
301 "Print the youngest revision number.\n"),
304 { NULL, NULL, {0}, NULL, {0} }
308 /* Baton for passing option/argument state to a subcommand function. */
309 struct svnlook_opt_state
311 const char *repos_path; /* 'arg0' is always the path to the repository. */
312 const char *arg1; /* Usually an fs path, a propname, or NULL. */
313 const char *arg2; /* Usually an fs path or NULL. */
316 svn_boolean_t version; /* --version */
317 svn_boolean_t show_ids; /* --show-ids */
318 apr_size_t limit; /* --limit */
319 svn_boolean_t help; /* --help */
320 svn_boolean_t no_diff_deleted; /* --no-diff-deleted */
321 svn_boolean_t no_diff_added; /* --no-diff-added */
322 svn_boolean_t diff_copy_from; /* --diff-copy-from */
323 svn_boolean_t verbose; /* --verbose */
324 svn_boolean_t revprop; /* --revprop */
325 svn_boolean_t full_paths; /* --full-paths */
326 svn_boolean_t copy_info; /* --copy-info */
327 svn_boolean_t non_recursive; /* --non-recursive */
328 svn_boolean_t xml; /* --xml */
329 const char *extensions; /* diff extension args (UTF-8!) */
330 svn_boolean_t quiet; /* --quiet */
331 svn_boolean_t ignore_properties; /* --ignore_properties */
332 svn_boolean_t properties_only; /* --properties-only */
333 const char *diff_cmd; /* --diff-cmd */
334 svn_boolean_t show_inherited_props; /* --show-inherited-props */
338 typedef struct svnlook_ctxt_t
342 svn_boolean_t is_revision;
343 svn_boolean_t show_ids;
345 svn_boolean_t no_diff_deleted;
346 svn_boolean_t no_diff_added;
347 svn_boolean_t diff_copy_from;
348 svn_boolean_t full_paths;
349 svn_boolean_t copy_info;
352 const char *txn_name /* UTF-8! */;
353 const apr_array_header_t *diff_options;
354 svn_boolean_t ignore_properties;
355 svn_boolean_t properties_only;
356 const char *diff_cmd;
360 /* A flag to see if we've been cancelled by the client or not. */
361 static volatile sig_atomic_t cancelled = FALSE;
364 /*** Helper functions. ***/
366 /* A signal handler to support cancellation. */
368 signal_handler(int signum)
370 apr_signal(signum, SIG_IGN);
374 /* Our cancellation callback. */
376 check_cancel(void *baton)
379 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
385 /* Version compatibility check */
387 check_lib_versions(void)
389 static const svn_version_checklist_t checklist[] =
391 { "svn_subr", svn_subr_version },
392 { "svn_repos", svn_repos_version },
393 { "svn_fs", svn_fs_version },
394 { "svn_delta", svn_delta_version },
395 { "svn_diff", svn_diff_version },
398 SVN_VERSION_DEFINE(my_version);
400 return svn_ver_check_list(&my_version, checklist);
404 /* Get revision or transaction property PROP_NAME for the revision or
405 transaction specified in C, allocating in in POOL and placing it in
408 get_property(svn_string_t **prop_value,
410 const char *prop_name,
413 svn_string_t *raw_value;
415 /* Fetch transaction property... */
416 if (! c->is_revision)
417 SVN_ERR(svn_fs_txn_prop(&raw_value, c->txn, prop_name, pool));
419 /* ...or revision property -- it's your call. */
421 SVN_ERR(svn_fs_revision_prop(&raw_value, c->fs, c->rev_id,
424 *prop_value = raw_value;
431 get_root(svn_fs_root_t **root,
435 /* Open up the appropriate root (revision or transaction). */
438 /* If we didn't get a valid revision number, we'll look at the
439 youngest revision. */
440 if (! SVN_IS_VALID_REVNUM(c->rev_id))
441 SVN_ERR(svn_fs_youngest_rev(&(c->rev_id), c->fs, pool));
443 SVN_ERR(svn_fs_revision_root(root, c->fs, c->rev_id, pool));
447 SVN_ERR(svn_fs_txn_root(root, c->txn, pool));
455 /*** Tree Routines ***/
457 /* Generate a generic delta tree. */
459 generate_delta_tree(svn_repos_node_t **tree,
462 svn_revnum_t base_rev,
465 svn_fs_root_t *base_root;
466 const svn_delta_editor_t *editor;
468 apr_pool_t *edit_pool = svn_pool_create(pool);
469 svn_fs_t *fs = svn_repos_fs(repos);
471 /* Get the base root. */
472 SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, pool));
474 /* Request our editor. */
475 SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos,
476 base_root, root, pool, edit_pool));
478 /* Drive our editor. */
479 SVN_ERR(svn_repos_replay2(root, "", SVN_INVALID_REVNUM, TRUE,
480 editor, edit_baton, NULL, NULL, edit_pool));
482 /* Return the tree we just built. */
483 *tree = svn_repos_node_from_baton(edit_baton);
484 svn_pool_destroy(edit_pool);
490 /*** Tree Printing Routines ***/
492 /* Recursively print only directory nodes that either a) have property
493 mods, or b) contains files that have changed, or c) has added or deleted
494 children. NODE is the root node of the tree delta, so every node in it
495 is either changed or is a directory with a changed node somewhere in the
499 print_dirs_changed_tree(svn_repos_node_t *node,
500 const char *path /* UTF-8! */,
503 svn_repos_node_t *tmp_node;
504 svn_boolean_t print_me = FALSE;
505 const char *full_path;
506 apr_pool_t *iterpool;
508 SVN_ERR(check_cancel(NULL));
513 /* Not a directory? We're not interested. */
514 if (node->kind != svn_node_dir)
517 /* Got prop mods? Excellent. */
521 /* Fly through the list of children, checking for modified files. */
522 tmp_node = node->child;
523 while (tmp_node && (! print_me))
525 if ((tmp_node->kind == svn_node_file)
526 || (tmp_node->action == 'A')
527 || (tmp_node->action == 'D'))
531 tmp_node = tmp_node->sibling;
534 /* Print the node if it qualifies. */
537 SVN_ERR(svn_cmdline_printf(pool, "%s/\n", path));
540 /* Return here if the node has no children. */
541 tmp_node = node->child;
545 /* Recursively handle the node's children. */
546 iterpool = svn_pool_create(pool);
549 svn_pool_clear(iterpool);
550 full_path = svn_dirent_join(path, tmp_node->name, iterpool);
551 SVN_ERR(print_dirs_changed_tree(tmp_node, full_path, iterpool));
552 tmp_node = tmp_node->sibling;
554 svn_pool_destroy(iterpool);
560 /* Recursively print all nodes in the tree that have been modified
561 (do not include directories affected only by "bubble-up"). */
563 print_changed_tree(svn_repos_node_t *node,
564 const char *path /* UTF-8! */,
565 svn_boolean_t copy_info,
568 const char *full_path;
569 char status[4] = "_ ";
570 svn_boolean_t print_me = TRUE;
571 apr_pool_t *iterpool;
573 SVN_ERR(check_cancel(NULL));
578 /* Print the node. */
579 if (node->action == 'A')
582 if (copy_info && node->copyfrom_path)
585 else if (node->action == 'D')
587 else if (node->action == 'R')
589 if ((! node->text_mod) && (! node->prop_mod))
599 /* Print this node unless told to skip it. */
602 SVN_ERR(svn_cmdline_printf(pool, "%s %s%s\n",
605 node->kind == svn_node_dir ? "/" : ""));
606 if (copy_info && node->copyfrom_path)
607 /* Remove the leading slash from the copyfrom path for consistency
608 with the rest of the output. */
609 SVN_ERR(svn_cmdline_printf(pool, " (from %s%s:r%ld)\n",
610 (node->copyfrom_path[0] == '/'
611 ? node->copyfrom_path + 1
612 : node->copyfrom_path),
613 (node->kind == svn_node_dir ? "/" : ""),
614 node->copyfrom_rev));
617 /* Return here if the node has no children. */
622 /* Recursively handle the node's children. */
623 iterpool = svn_pool_create(pool);
626 svn_pool_clear(iterpool);
627 full_path = svn_dirent_join(path, node->name, iterpool);
628 SVN_ERR(print_changed_tree(node, full_path, copy_info, iterpool));
629 node = node->sibling;
631 svn_pool_destroy(iterpool);
638 dump_contents(svn_stream_t *stream,
640 const char *path /* UTF-8! */,
644 SVN_ERR(svn_stream_close(stream)); /* leave an empty file */
647 svn_stream_t *contents;
649 /* Grab the contents and copy them into the given stream. */
650 SVN_ERR(svn_fs_file_contents(&contents, root, path, pool));
651 SVN_ERR(svn_stream_copy3(contents, stream, NULL, NULL, pool));
658 /* Prepare temporary files *TMPFILE1 and *TMPFILE2 for diffing
659 PATH1@ROOT1 versus PATH2@ROOT2. If either ROOT1 or ROOT2 is NULL,
660 the temporary file for its path/root will be an empty one.
661 Otherwise, its temporary file will contain the contents of that
662 path/root in the repository.
664 An exception to this is when either path/root has an svn:mime-type
665 property set on it which indicates that the file contains
666 non-textual data -- in this case, the *IS_BINARY flag is set and no
667 temporary files are created.
669 Use POOL for all that allocation goodness. */
671 prepare_tmpfiles(const char **tmpfile1,
672 const char **tmpfile2,
673 svn_boolean_t *is_binary,
674 svn_fs_root_t *root1,
676 svn_fs_root_t *root2,
681 svn_string_t *mimetype;
682 svn_stream_t *stream;
684 /* Init the return values. */
689 assert(path1 && path2);
691 /* Check for binary mimetypes. If either file has a binary
692 mimetype, get outta here. */
695 SVN_ERR(svn_fs_node_prop(&mimetype, root1, path1,
696 SVN_PROP_MIME_TYPE, pool));
697 if (mimetype && svn_mime_type_is_binary(mimetype->data))
705 SVN_ERR(svn_fs_node_prop(&mimetype, root2, path2,
706 SVN_PROP_MIME_TYPE, pool));
707 if (mimetype && svn_mime_type_is_binary(mimetype->data))
714 /* Now, prepare the two temporary files, each of which will either
715 be empty, or will have real contents. */
716 SVN_ERR(svn_stream_open_unique(&stream, tmpfile1,
718 svn_io_file_del_none,
720 SVN_ERR(dump_contents(stream, root1, path1, pool));
722 SVN_ERR(svn_stream_open_unique(&stream, tmpfile2,
724 svn_io_file_del_none,
726 SVN_ERR(dump_contents(stream, root2, path2, pool));
732 /* Generate a diff label for PATH in ROOT, allocating in POOL.
733 ROOT may be NULL, in which case revision 0 is used. */
735 generate_label(const char **label,
742 const char *name = NULL;
743 svn_revnum_t rev = SVN_INVALID_REVNUM;
747 svn_fs_t *fs = svn_fs_root_fs(root);
748 if (svn_fs_is_revision_root(root))
750 rev = svn_fs_revision_root_revision(root);
751 SVN_ERR(svn_fs_revision_prop(&date, fs, rev,
752 SVN_PROP_REVISION_DATE, pool));
757 name = svn_fs_txn_root_name(root, pool);
758 SVN_ERR(svn_fs_open_txn(&txn, fs, name, pool));
759 SVN_ERR(svn_fs_txn_prop(&date, txn, SVN_PROP_REVISION_DATE, pool));
769 datestr = apr_psprintf(pool, "%.10s %.8s UTC", date->data, date->data + 11);
774 *label = apr_psprintf(pool, "%s\t%s (txn %s)",
775 path, datestr, name);
777 *label = apr_psprintf(pool, "%s\t%s (rev %ld)",
783 /* Helper function to display differences in properties of a file */
785 display_prop_diffs(svn_stream_t *outstream,
786 const char *encoding,
787 const apr_array_header_t *propchanges,
788 apr_hash_t *original_props,
793 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
794 _("%sProperty changes on: %s%s"),
799 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
800 SVN_DIFF__UNDER_STRING APR_EOL_STR));
802 SVN_ERR(check_cancel(NULL));
804 SVN_ERR(svn_diff__display_prop_diffs(
805 outstream, encoding, propchanges, original_props,
806 FALSE /* pretty_print_mergeinfo */, pool));
812 /* Recursively print all nodes in the tree that have been modified
813 (do not include directories affected only by "bubble-up"). */
815 print_diff_tree(svn_stream_t *out_stream,
816 const char *encoding,
818 svn_fs_root_t *base_root,
819 svn_repos_node_t *node,
820 const char *path /* UTF-8! */,
821 const char *base_path /* UTF-8! */,
822 const svnlook_ctxt_t *c,
826 const char *orig_path = NULL, *new_path = NULL;
827 svn_boolean_t do_diff = FALSE;
828 svn_boolean_t orig_empty = FALSE;
829 svn_boolean_t is_copy = FALSE;
830 svn_boolean_t binary = FALSE;
831 svn_boolean_t diff_header_printed = FALSE;
833 svn_stringbuf_t *header;
835 SVN_ERR(check_cancel(NULL));
840 header = svn_stringbuf_create_empty(pool);
842 /* Print copyfrom history for the top node of a copied tree. */
843 if ((SVN_IS_VALID_REVNUM(node->copyfrom_rev))
844 && (node->copyfrom_path != NULL))
846 /* This is ... a copy. */
849 /* Propagate the new base. Copyfrom paths usually start with a
850 slash; we remove it for consistency with the target path.
851 ### Yes, it would be *much* better for something in the path
852 library to be taking care of this! */
853 if (node->copyfrom_path[0] == '/')
854 base_path = apr_pstrdup(pool, node->copyfrom_path + 1);
856 base_path = apr_pstrdup(pool, node->copyfrom_path);
858 svn_stringbuf_appendcstr
860 apr_psprintf(pool, _("Copied: %s (from rev %ld, %s)\n"),
861 path, node->copyfrom_rev, base_path));
863 SVN_ERR(svn_fs_revision_root(&base_root,
864 svn_fs_root_fs(base_root),
865 node->copyfrom_rev, pool));
868 /*** First, we'll just print file content diffs. ***/
869 if (node->kind == svn_node_file)
871 /* Here's the generalized way we do our diffs:
873 - First, we'll check for svn:mime-type properties on the old
874 and new files. If either has such a property, and it
875 represents a binary type, we won't actually be doing a real
878 - Second, dump the contents of the new version of the file
879 into the temporary directory.
881 - Then, dump the contents of the old version of the file into
882 the temporary directory.
884 - Next, we run 'diff', passing the repository paths as the
887 - Finally, we delete the temporary files. */
888 if (node->action == 'R' && node->text_mod)
891 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
892 base_root, base_path, root, path,
895 else if (c->diff_copy_from && node->action == 'A' && is_copy)
900 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
901 base_root, base_path, root, path,
905 else if (! c->no_diff_added && node->action == 'A')
909 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
910 NULL, base_path, root, path,
913 else if (! c->no_diff_deleted && node->action == 'D')
916 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
917 base_root, base_path, NULL, path,
921 /* The header for the copy case has already been created, and we don't
922 want a header here for files with only property modifications. */
924 && (node->action != 'R' || node->text_mod))
926 svn_stringbuf_appendcstr
927 (header, apr_psprintf(pool, "%s: %s\n",
928 ((node->action == 'A') ? _("Added") :
929 ((node->action == 'D') ? _("Deleted") :
930 ((node->action == 'R') ? _("Modified")
936 if (do_diff && (! c->properties_only))
938 svn_stringbuf_appendcstr(header, SVN_DIFF__EQUAL_STRING "\n");
942 svn_stringbuf_appendcstr(header, _("(Binary files differ)\n\n"));
943 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
944 "%s", header->data));
952 const char *outfilename;
953 const char *errfilename;
954 svn_stream_t *stream;
955 svn_stream_t *err_stream;
956 const char **diff_cmd_argv;
959 const char *orig_label;
960 const char *new_label;
962 diff_cmd_argv = NULL;
963 diff_cmd_argc = c->diff_options->nelts;
967 diff_cmd_argv = apr_palloc(pool,
968 diff_cmd_argc * sizeof(char *));
969 for (i = 0; i < diff_cmd_argc; i++)
970 SVN_ERR(svn_utf_cstring_to_utf8(&diff_cmd_argv[i],
971 APR_ARRAY_IDX(c->diff_options, i, const char *),
975 /* Print diff header. */
976 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
977 "%s", header->data));
980 SVN_ERR(generate_label(&orig_label, NULL, path, pool));
982 SVN_ERR(generate_label(&orig_label, base_root,
984 SVN_ERR(generate_label(&new_label, root, path, pool));
986 /* We deal in streams, but svn_io_run_diff2() deals in file
987 handles, so we may need to make temporary files and then
988 copy the contents to our stream. */
989 outfile = svn_stream__aprfile(out_stream);
993 SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
994 svn_io_file_del_on_pool_cleanup, pool, pool));
995 SVN_ERR(svn_stream_for_stderr(&err_stream, pool));
996 errfile = svn_stream__aprfile(err_stream);
1000 SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
1001 svn_io_file_del_on_pool_cleanup, pool, pool));
1003 SVN_ERR(svn_io_run_diff2(".",
1006 orig_label, new_label,
1007 orig_path, new_path,
1008 &exitcode, outfile, errfile,
1009 c->diff_cmd, pool));
1011 /* Now, open and copy our files to our output streams. */
1014 SVN_ERR(svn_io_file_close(outfile, pool));
1015 SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
1017 SVN_ERR(svn_stream_copy3(stream,
1018 svn_stream_disown(out_stream, pool),
1023 SVN_ERR(svn_io_file_close(errfile, pool));
1024 SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
1026 SVN_ERR(svn_stream_copy3(stream,
1027 svn_stream_disown(err_stream, pool),
1031 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1033 diff_header_printed = TRUE;
1038 svn_diff_file_options_t *opts = svn_diff_file_options_create(pool);
1040 if (c->diff_options)
1041 SVN_ERR(svn_diff_file_options_parse(opts, c->diff_options, pool));
1043 SVN_ERR(svn_diff_file_diff_2(&diff, orig_path,
1044 new_path, opts, pool));
1046 if (svn_diff_contains_diffs(diff))
1048 const char *orig_label, *new_label;
1050 /* Print diff header. */
1051 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1052 "%s", header->data));
1055 SVN_ERR(generate_label(&orig_label, NULL, path, pool));
1057 SVN_ERR(generate_label(&orig_label, base_root,
1059 SVN_ERR(generate_label(&new_label, root, path, pool));
1060 SVN_ERR(svn_diff_file_output_unified3
1061 (out_stream, diff, orig_path, new_path,
1062 orig_label, new_label,
1063 svn_cmdline_output_encoding(pool), NULL,
1064 opts->show_c_function, pool));
1065 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1067 diff_header_printed = TRUE;
1069 else if (! node->prop_mod &&
1070 ((! c->no_diff_added && node->action == 'A') ||
1071 (! c->no_diff_deleted && node->action == 'D')))
1073 /* There was an empty file added or deleted in this revision.
1074 * We can't print a diff, but we can at least print
1075 * a diff header since we know what happened to this file. */
1076 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1077 "%s", header->data));
1083 /* Make sure we delete any temporary files. */
1085 SVN_ERR(svn_io_remove_file2(orig_path, FALSE, pool));
1087 SVN_ERR(svn_io_remove_file2(new_path, FALSE, pool));
1089 /*** Now handle property diffs ***/
1090 if ((node->prop_mod) && (node->action != 'D') && (! c->ignore_properties))
1092 apr_hash_t *local_proptable;
1093 apr_hash_t *base_proptable;
1094 apr_array_header_t *propchanges, *props;
1096 SVN_ERR(svn_fs_node_proplist(&local_proptable, root, path, pool));
1097 if (c->diff_copy_from && node->action == 'A' && is_copy)
1098 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1100 else if (node->action == 'A')
1101 base_proptable = apr_hash_make(pool);
1102 else /* node->action == 'R' */
1103 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1105 SVN_ERR(svn_prop_diffs(&propchanges, local_proptable,
1106 base_proptable, pool));
1107 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, pool));
1108 if (props->nelts > 0)
1110 /* We print a diff header for the case when we only have property
1112 if (! diff_header_printed)
1114 const char *orig_label, *new_label;
1116 SVN_ERR(generate_label(&orig_label, base_root, base_path,
1118 SVN_ERR(generate_label(&new_label, root, path, pool));
1120 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1121 "Index: %s\n", path));
1122 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1123 SVN_DIFF__EQUAL_STRING "\n"));
1126 SVN_ERR(svn_diff__unidiff_write_header(
1127 out_stream, encoding, orig_label, new_label, pool));
1129 SVN_ERR(display_prop_diffs(out_stream, encoding,
1130 props, base_proptable, path, pool));
1134 /* Return here if the node has no children. */
1137 return SVN_NO_ERROR;
1139 /* Recursively handle the node's children. */
1140 subpool = svn_pool_create(pool);
1141 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node,
1142 svn_dirent_join(path, node->name, subpool),
1143 svn_dirent_join(base_path, node->name, subpool),
1144 c, tmpdir, subpool));
1145 while (node->sibling)
1147 svn_pool_clear(subpool);
1148 node = node->sibling;
1149 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node,
1150 svn_dirent_join(path, node->name, subpool),
1151 svn_dirent_join(base_path, node->name, subpool),
1152 c, tmpdir, subpool));
1154 svn_pool_destroy(subpool);
1156 return SVN_NO_ERROR;
1160 /* Print a repository directory, maybe recursively, possibly showing
1161 the node revision ids, and optionally using full paths.
1163 ROOT is the revision or transaction root used to build that tree.
1164 PATH and ID are the current path and node revision id being
1165 printed, and INDENTATION the number of spaces to prepent to that
1166 path's printed output. ID may be NULL if SHOW_IDS is FALSE (in
1167 which case, ids won't be printed at all). If RECURSE is TRUE,
1168 then print the tree recursively; otherwise, we'll stop after the
1169 first level (and use INDENTATION to keep track of how deep we are).
1171 Use POOL for all allocations. */
1172 static svn_error_t *
1173 print_tree(svn_fs_root_t *root,
1174 const char *path /* UTF-8! */,
1175 const svn_fs_id_t *id,
1176 svn_boolean_t is_dir,
1178 svn_boolean_t show_ids,
1179 svn_boolean_t full_paths,
1180 svn_boolean_t recurse,
1183 apr_pool_t *subpool;
1184 apr_hash_t *entries;
1187 SVN_ERR(check_cancel(NULL));
1189 /* Print the indentation. */
1193 for (i = 0; i < indentation; i++)
1194 SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
1197 /* ### The path format is inconsistent.. needs fix */
1200 else if (*path == '/')
1201 name = svn_fspath__basename(path, pool);
1203 name = svn_relpath_basename(path, NULL);
1205 if (svn_path_is_empty(name))
1206 name = "/"; /* basename of '/' is "" */
1208 /* Print the node. */
1209 SVN_ERR(svn_cmdline_printf(pool, "%s%s",
1211 is_dir && strcmp(name, "/") ? "/" : ""));
1215 svn_string_t *unparsed_id = NULL;
1217 unparsed_id = svn_fs_unparse_id(id, pool);
1218 SVN_ERR(svn_cmdline_printf(pool, " <%s>",
1223 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1225 /* Return here if PATH is not a directory. */
1227 return SVN_NO_ERROR;
1229 /* Recursively handle the node's children. */
1230 if (recurse || (indentation == 0))
1232 apr_array_header_t *sorted_entries;
1235 SVN_ERR(svn_fs_dir_entries(&entries, root, path, pool));
1236 subpool = svn_pool_create(pool);
1237 sorted_entries = svn_sort__hash(entries,
1238 svn_sort_compare_items_lexically, pool);
1239 for (i = 0; i < sorted_entries->nelts; i++)
1241 svn_sort__item_t item = APR_ARRAY_IDX(sorted_entries, i,
1243 svn_fs_dirent_t *entry = item.value;
1245 svn_pool_clear(subpool);
1246 SVN_ERR(print_tree(root,
1248 ? svn_fspath__join(path, entry->name, pool)
1249 : svn_relpath_join(path, entry->name, pool),
1250 entry->id, (entry->kind == svn_node_dir),
1251 indentation + 1, show_ids, full_paths,
1254 svn_pool_destroy(subpool);
1257 return SVN_NO_ERROR;
1261 /* Set *BASE_REV to the revision on which the target root specified in
1262 C is based, or to SVN_INVALID_REVNUM when C represents "revision
1263 0" (because that revision isn't based on another revision). */
1264 static svn_error_t *
1265 get_base_rev(svn_revnum_t *base_rev, svnlook_ctxt_t *c, apr_pool_t *pool)
1269 *base_rev = c->rev_id - 1;
1273 *base_rev = svn_fs_txn_base_revision(c->txn);
1275 if (! SVN_IS_VALID_REVNUM(*base_rev))
1276 return svn_error_createf
1277 (SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1278 _("Transaction '%s' is not based on a revision; how odd"),
1281 return SVN_NO_ERROR;
1286 /*** Subcommand handlers. ***/
1288 /* Print the revision's log message to stdout, followed by a newline. */
1289 static svn_error_t *
1290 do_log(svnlook_ctxt_t *c, svn_boolean_t print_size, apr_pool_t *pool)
1292 svn_string_t *prop_value;
1293 const char *prop_value_eol, *prop_value_native;
1294 svn_stream_t *stream;
1298 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_LOG, pool));
1299 if (! (prop_value && prop_value->data))
1301 SVN_ERR(svn_cmdline_printf(pool, "%s\n", print_size ? "0" : ""));
1302 return SVN_NO_ERROR;
1305 /* We immitate what svn_cmdline_printf does here, since we need the byte
1306 size of what we are going to print. */
1308 SVN_ERR(svn_subst_translate_cstring2(prop_value->data, &prop_value_eol,
1310 NULL, FALSE, pool));
1312 err = svn_cmdline_cstring_from_utf8(&prop_value_native, prop_value_eol,
1316 svn_error_clear(err);
1317 prop_value_native = svn_cmdline_cstring_from_utf8_fuzzy(prop_value_eol,
1321 len = strlen(prop_value_native);
1324 SVN_ERR(svn_cmdline_printf(pool, "%" APR_SIZE_T_FMT "\n", len));
1326 /* Use a stream to bypass all stdio translations. */
1327 SVN_ERR(svn_cmdline_fflush(stdout));
1328 SVN_ERR(svn_stream_for_stdout(&stream, pool));
1329 SVN_ERR(svn_stream_write(stream, prop_value_native, &len));
1330 SVN_ERR(svn_stream_close(stream));
1332 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1334 return SVN_NO_ERROR;
1338 /* Print the timestamp of the commit (in the revision case) or the
1339 empty string (in the transaction case) to stdout, followed by a
1341 static svn_error_t *
1342 do_date(svnlook_ctxt_t *c, apr_pool_t *pool)
1344 svn_string_t *prop_value;
1346 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_DATE, pool));
1347 if (prop_value && prop_value->data)
1349 /* Convert the date for humans. */
1351 const char *time_utf8;
1353 SVN_ERR(svn_time_from_cstring(&aprtime, prop_value->data, pool));
1355 time_utf8 = svn_time_to_human_cstring(aprtime, pool);
1357 SVN_ERR(svn_cmdline_printf(pool, "%s", time_utf8));
1360 SVN_ERR(svn_cmdline_printf(pool, "\n"));
1361 return SVN_NO_ERROR;
1365 /* Print the author of the commit to stdout, followed by a newline. */
1366 static svn_error_t *
1367 do_author(svnlook_ctxt_t *c, apr_pool_t *pool)
1369 svn_string_t *prop_value;
1371 SVN_ERR(get_property(&prop_value, c,
1372 SVN_PROP_REVISION_AUTHOR, pool));
1373 if (prop_value && prop_value->data)
1374 SVN_ERR(svn_cmdline_printf(pool, "%s", prop_value->data));
1376 SVN_ERR(svn_cmdline_printf(pool, "\n"));
1377 return SVN_NO_ERROR;
1381 /* Print a list of all directories in which files, or directory
1382 properties, have been modified. */
1383 static svn_error_t *
1384 do_dirs_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1386 svn_fs_root_t *root;
1387 svn_revnum_t base_rev_id;
1388 svn_repos_node_t *tree;
1390 SVN_ERR(get_root(&root, c, pool));
1391 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1392 if (base_rev_id == SVN_INVALID_REVNUM)
1393 return SVN_NO_ERROR;
1395 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1397 SVN_ERR(print_dirs_changed_tree(tree, "", pool));
1399 return SVN_NO_ERROR;
1403 /* Set *KIND to PATH's kind, if PATH exists.
1405 * If PATH does not exist, then error; the text of the error depends
1406 * on whether PATH looks like a URL or not.
1408 static svn_error_t *
1409 verify_path(svn_node_kind_t *kind,
1410 svn_fs_root_t *root,
1414 SVN_ERR(svn_fs_check_path(kind, root, path, pool));
1416 if (*kind == svn_node_none)
1418 if (svn_path_is_url(path)) /* check for a common mistake. */
1419 return svn_error_createf
1420 (SVN_ERR_FS_NOT_FOUND, NULL,
1421 _("'%s' is a URL, probably should be a path"), path);
1423 return svn_error_createf
1424 (SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' does not exist"), path);
1427 return SVN_NO_ERROR;
1431 /* Print the size (in bytes) of a file. */
1432 static svn_error_t *
1433 do_filesize(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
1435 svn_fs_root_t *root;
1436 svn_node_kind_t kind;
1437 svn_filesize_t length;
1439 SVN_ERR(get_root(&root, c, pool));
1440 SVN_ERR(verify_path(&kind, root, path, pool));
1442 if (kind != svn_node_file)
1443 return svn_error_createf
1444 (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
1448 SVN_ERR(svn_fs_file_length(&length, root, path, pool));
1449 return svn_cmdline_printf(pool, "%" SVN_FILESIZE_T_FMT "\n", length);
1452 /* Print the contents of the file at PATH in the repository.
1453 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
1454 SVN_ERR_FS_NOT_FILE if PATH exists but is not a file. */
1455 static svn_error_t *
1456 do_cat(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
1458 svn_fs_root_t *root;
1459 svn_node_kind_t kind;
1460 svn_stream_t *fstream, *stdout_stream;
1462 SVN_ERR(get_root(&root, c, pool));
1463 SVN_ERR(verify_path(&kind, root, path, pool));
1465 if (kind != svn_node_file)
1466 return svn_error_createf
1467 (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
1471 SVN_ERR(svn_fs_file_contents(&fstream, root, path, pool));
1472 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1474 return svn_stream_copy3(fstream, svn_stream_disown(stdout_stream, pool),
1475 check_cancel, NULL, pool);
1479 /* Print a list of all paths modified in a format compatible with `svn
1481 static svn_error_t *
1482 do_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1484 svn_fs_root_t *root;
1485 svn_revnum_t base_rev_id;
1486 svn_repos_node_t *tree;
1488 SVN_ERR(get_root(&root, c, pool));
1489 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1490 if (base_rev_id == SVN_INVALID_REVNUM)
1491 return SVN_NO_ERROR;
1493 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1495 SVN_ERR(print_changed_tree(tree, "", c->copy_info, pool));
1497 return SVN_NO_ERROR;
1501 /* Print some diff-y stuff in a TBD way. :-) */
1502 static svn_error_t *
1503 do_diff(svnlook_ctxt_t *c, apr_pool_t *pool)
1505 svn_fs_root_t *root, *base_root;
1506 svn_revnum_t base_rev_id;
1507 svn_repos_node_t *tree;
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;
1514 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1518 svn_stream_t *out_stream;
1519 const char *encoding = svn_cmdline_output_encoding(pool);
1521 SVN_ERR(svn_fs_revision_root(&base_root, c->fs, base_rev_id, pool));
1522 SVN_ERR(svn_io_temp_dir(&tmpdir, pool));
1524 /* This fflush() might seem odd, but it was added to deal
1525 with this bug report:
1527 http://subversion.tigris.org/servlets/ReadMsg?\
1528 list=dev&msgNo=140782
1530 From: "Steve Hay" <SteveHay{_AT_}planit.com>
1531 To: <dev@subversion.tigris.org>
1532 Subject: svnlook diff output in wrong order when redirected
1533 Date: Fri, 4 Jul 2008 16:34:15 +0100
1534 Message-ID: <1B32FF956ABF414C9BCE5E487A1497E702014F62@\
1535 ukmail02.planit.group>
1537 Adding the fflush() fixed the bug (not everyone could
1538 reproduce it, but those who could confirmed the fix).
1539 Later in the thread, Daniel Shahaf speculated as to
1542 "Because svn_cmdline_printf() uses the standard
1543 'FILE *stdout' to write to stdout, while
1544 svn_stream_for_stdout() uses (through
1545 apr_file_open_stdout()) Windows API's to get a
1546 handle for stdout?" */
1547 SVN_ERR(svn_cmdline_fflush(stdout));
1548 SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1550 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, tree,
1551 "", "", c, tmpdir, pool));
1553 return SVN_NO_ERROR;
1558 /* Callback baton for print_history() (and do_history()). */
1559 struct print_history_baton
1562 svn_boolean_t show_ids; /* whether to show node IDs */
1563 apr_size_t limit; /* max number of history items */
1564 apr_size_t count; /* number of history items processed */
1567 /* Implements svn_repos_history_func_t interface. Print the history
1568 that's reported through this callback, possibly finding and
1569 displaying node-rev-ids. */
1570 static svn_error_t *
1571 print_history(void *baton,
1573 svn_revnum_t revision,
1576 struct print_history_baton *phb = baton;
1578 SVN_ERR(check_cancel(NULL));
1582 const svn_fs_id_t *node_id;
1583 svn_fs_root_t *rev_root;
1584 svn_string_t *id_string;
1586 SVN_ERR(svn_fs_revision_root(&rev_root, phb->fs, revision, pool));
1587 SVN_ERR(svn_fs_node_id(&node_id, rev_root, path, pool));
1588 id_string = svn_fs_unparse_id(node_id, pool);
1589 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s <%s>\n",
1590 revision, path, id_string->data));
1594 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s\n", revision, path));
1600 if (phb->count >= phb->limit)
1601 /* Not L10N'd, since this error is supressed by the caller. */
1602 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL,
1603 _("History item limit reached"));
1606 return SVN_NO_ERROR;
1610 /* Print a tabular display of history location points for PATH in
1611 revision C->rev_id. Optionally, SHOW_IDS. Use POOL for
1613 static svn_error_t *
1614 do_history(svnlook_ctxt_t *c,
1618 struct print_history_baton args;
1622 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH <ID>\n"
1623 "-------- ---------\n")));
1627 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH\n"
1628 "-------- ----\n")));
1631 /* Call our history crawler. We want the whole lifetime of the path
1632 (prior to the user-supplied revision, of course), across all
1635 args.show_ids = c->show_ids;
1636 args.limit = c->limit;
1638 SVN_ERR(svn_repos_history2(c->fs, path, print_history, &args,
1639 NULL, NULL, 0, c->rev_id, TRUE, pool));
1640 return SVN_NO_ERROR;
1644 /* Print the value of property PROPNAME on PATH in the repository.
1646 If VERBOSE, print their values too. If SHOW_INHERITED_PROPS, print
1647 PATH's inherited props too.
1649 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. If
1650 SHOW_INHERITED_PROPS is FALSE,then error with SVN_ERR_PROPERTY_NOT_FOUND
1651 if there is no such property on PATH. If SHOW_INHERITED_PROPS is TRUE,
1652 then error with SVN_ERR_PROPERTY_NOT_FOUND only if there is no such
1653 property on PATH nor inherited by path.
1655 If PATH is NULL, operate on a revision property. */
1656 static svn_error_t *
1657 do_pget(svnlook_ctxt_t *c,
1658 const char *propname,
1660 svn_boolean_t verbose,
1661 svn_boolean_t show_inherited_props,
1664 svn_fs_root_t *root;
1666 svn_node_kind_t kind;
1667 svn_stream_t *stdout_stream;
1669 apr_array_header_t *inherited_props = NULL;
1671 SVN_ERR(get_root(&root, c, pool));
1674 path = svn_fspath__canonicalize(path, pool);
1675 SVN_ERR(verify_path(&kind, root, path, pool));
1676 SVN_ERR(svn_fs_node_prop(&prop, root, path, propname, pool));
1678 if (show_inherited_props)
1680 SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
1681 path, propname, NULL,
1685 else /* --revprop */
1687 SVN_ERR(get_property(&prop, c, propname, pool));
1690 /* Did we find nothing? */
1692 && (!show_inherited_props || inherited_props->nelts == 0))
1694 const char *err_msg;
1697 /* We're operating on a revprop (e.g. c->is_revision). */
1698 err_msg = apr_psprintf(pool,
1699 _("Property '%s' not found on revision %ld"),
1700 propname, c->rev_id);
1704 if (SVN_IS_VALID_REVNUM(c->rev_id))
1706 if (show_inherited_props)
1707 err_msg = apr_psprintf(pool,
1708 _("Property '%s' not found on path '%s' "
1709 "or inherited from a parent "
1711 propname, path, c->rev_id);
1713 err_msg = apr_psprintf(pool,
1714 _("Property '%s' not found on path '%s' "
1716 propname, path, c->rev_id);
1720 if (show_inherited_props)
1721 err_msg = apr_psprintf(pool,
1722 _("Property '%s' not found on path '%s' "
1723 "or inherited from a parent "
1724 "in transaction %s"),
1725 propname, path, c->txn_name);
1727 err_msg = apr_psprintf(pool,
1728 _("Property '%s' not found on path '%s' "
1729 "in transaction %s"),
1730 propname, path, c->txn_name);
1733 return svn_error_create(SVN_ERR_PROPERTY_NOT_FOUND, NULL, err_msg);
1736 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1738 if (verbose || show_inherited_props)
1740 if (inherited_props)
1744 for (i = 0; i < inherited_props->nelts; i++)
1746 svn_prop_inherited_item_t *elt =
1747 APR_ARRAY_IDX(inherited_props, i,
1748 svn_prop_inherited_item_t *);
1752 SVN_ERR(svn_stream_printf(stdout_stream, pool,
1753 _("Inherited properties on '%s',\nfrom '%s':\n"),
1754 path, svn_fspath__canonicalize(elt->path_or_url,
1756 SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream,
1762 svn_string_t *propval =
1763 svn__apr_hash_index_val(apr_hash_first(pool,
1766 SVN_ERR(svn_stream_printf(
1767 stdout_stream, pool, "%s - ",
1768 svn_fspath__canonicalize(elt->path_or_url, pool)));
1770 SVN_ERR(svn_stream_write(stdout_stream, propval->data, &len));
1771 /* If we have more than one property to write, then add a newline*/
1772 if (inherited_props->nelts > 1 || prop)
1774 len = strlen(APR_EOL_STR);
1775 SVN_ERR(svn_stream_write(stdout_stream, APR_EOL_STR, &len));
1785 apr_hash_t *hash = apr_hash_make(pool);
1787 svn_hash_sets(hash, propname, prop);
1788 SVN_ERR(svn_stream_printf(stdout_stream, pool,
1789 _("Properties on '%s':\n"), path));
1790 SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, hash,
1795 SVN_ERR(svn_stream_printf(stdout_stream, pool, "%s - ", path));
1797 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1801 else /* Raw single prop output, i.e. non-verbose output with no
1804 /* Unlike the command line client, we don't translate the property
1805 value or print a trailing newline here. We just output the raw
1806 bytes of whatever's in the repository, as svnlook is more likely
1807 to be used for automated inspections. */
1809 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1812 return SVN_NO_ERROR;
1816 /* Print the property names of all properties on PATH in the repository.
1818 If VERBOSE, print their values too. If XML, print as XML rather than as
1819 plain text. If SHOW_INHERITED_PROPS, print PATH's inherited props too.
1821 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist.
1823 If PATH is NULL, operate on a revision properties. */
1824 static svn_error_t *
1825 do_plist(svnlook_ctxt_t *c,
1827 svn_boolean_t verbose,
1829 svn_boolean_t show_inherited_props,
1832 svn_fs_root_t *root;
1834 apr_hash_index_t *hi;
1835 svn_node_kind_t kind;
1836 svn_stringbuf_t *sb = NULL;
1837 svn_boolean_t revprop = FALSE;
1838 apr_array_header_t *inherited_props = NULL;
1842 /* PATH might be the root of the repsository and we accept both
1843 "" and "/". But to avoid the somewhat cryptic output like this:
1845 >svnlook pl repos-path ""
1850 We canonicalize PATH so that is has a leading slash. */
1851 path = svn_fspath__canonicalize(path, pool);
1853 SVN_ERR(get_root(&root, c, pool));
1854 SVN_ERR(verify_path(&kind, root, path, pool));
1855 SVN_ERR(svn_fs_node_proplist(&props, root, path, pool));
1857 if (show_inherited_props)
1858 SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
1859 path, NULL, NULL, NULL,
1862 else if (c->is_revision)
1864 SVN_ERR(svn_fs_revision_proplist(&props, c->fs, c->rev_id, pool));
1869 SVN_ERR(svn_fs_txn_proplist(&props, c->txn, pool));
1875 /* <?xml version="1.0" encoding="UTF-8"?> */
1876 svn_xml_make_header2(&sb, "UTF-8", pool);
1878 /* "<properties>" */
1879 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "properties", NULL);
1882 if (inherited_props)
1886 for (i = 0; i < inherited_props->nelts; i++)
1888 svn_prop_inherited_item_t *elt =
1889 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1891 /* Canonicalize the inherited parent paths for consistency
1895 svn_xml_make_open_tag(
1896 &sb, pool, svn_xml_normal, "target", "path",
1897 svn_fspath__canonicalize(elt->path_or_url, pool),
1899 SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, elt->prop_hash,
1902 svn_xml_make_close_tag(&sb, pool, "target");
1906 SVN_ERR(svn_cmdline_printf(
1907 pool, _("Inherited properties on '%s',\nfrom '%s':\n"),
1908 path, svn_fspath__canonicalize(elt->path_or_url, pool)));
1909 SVN_ERR(svn_cmdline__print_prop_hash(NULL, elt->prop_hash,
1919 /* "<revprops ...>" */
1922 char *revstr = apr_psprintf(pool, "%ld", c->rev_id);
1924 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
1925 "rev", revstr, NULL);
1929 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
1930 "txn", c->txn_name, NULL);
1935 /* "<target ...>" */
1936 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
1937 "path", path, NULL);
1941 if (!xml && path /* Not a --revprop */)
1942 SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), path));
1944 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
1946 const char *pname = svn__apr_hash_index_key(hi);
1947 svn_string_t *propval = svn__apr_hash_index_val(hi);
1949 SVN_ERR(check_cancel(NULL));
1951 /* Since we're already adding a trailing newline (and possible a
1952 colon and some spaces) anyway, just mimic the output of the
1953 command line client proplist. Compare to 'svnlook propget',
1954 which sends the raw bytes to stdout, untranslated. */
1955 /* We leave printf calls here, since we don't always know the encoding
1956 of the prop value. */
1957 if (svn_prop_needs_translation(pname))
1958 SVN_ERR(svn_subst_detranslate_string(&propval, propval, TRUE, pool));
1963 svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, pool);
1966 const char *pname_stdout;
1967 const char *indented_newval;
1969 SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname,
1971 printf(" %s\n", pname_stdout);
1972 /* Add an extra newline to the value before indenting, so that
1973 every line of output has the indentation whether the value
1974 already ended in a newline or not. */
1976 svn_cmdline__indent_string(apr_psprintf(pool, "%s\n",
1979 printf("%s", indented_newval);
1983 svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "property",
1984 "name", pname, NULL);
1986 printf(" %s\n", pname);
1994 svn_xml_make_close_tag(&sb, pool, "revprops");
1999 svn_xml_make_close_tag(&sb, pool, "target");
2002 /* "</properties>" */
2003 svn_xml_make_close_tag(&sb, pool, "properties");
2005 if (fputs(sb->data, stdout) == EOF)
2008 return svn_error_wrap_apr(errno, _("Write error"));
2010 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
2014 return SVN_NO_ERROR;
2018 static svn_error_t *
2019 do_tree(svnlook_ctxt_t *c,
2021 svn_boolean_t show_ids,
2022 svn_boolean_t full_paths,
2023 svn_boolean_t recurse,
2026 svn_fs_root_t *root;
2027 const svn_fs_id_t *id;
2028 svn_boolean_t is_dir;
2030 SVN_ERR(get_root(&root, c, pool));
2031 SVN_ERR(svn_fs_node_id(&id, root, path, pool));
2032 SVN_ERR(svn_fs_is_dir(&is_dir, root, path, pool));
2033 SVN_ERR(print_tree(root, path, id, is_dir, 0, show_ids, full_paths,
2035 return SVN_NO_ERROR;
2039 /* Custom filesystem warning function. */
2041 warning_func(void *baton,
2046 svn_handle_error2(err, stderr, FALSE, "svnlook: ");
2050 /* Return an error if the number of arguments (excluding the repository
2051 * argument) is not NUM_ARGS. NUM_ARGS must be 0 or 1. The arguments
2052 * are assumed to be found in OPT_STATE->arg1 and OPT_STATE->arg2. */
2053 static svn_error_t *
2054 check_number_of_args(struct svnlook_opt_state *opt_state,
2057 if ((num_args == 0 && opt_state->arg1 != NULL)
2058 || (num_args == 1 && opt_state->arg2 != NULL))
2059 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2060 _("Too many arguments given"));
2061 if ((num_args == 1 && opt_state->arg1 == NULL))
2062 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2063 _("Missing repository path argument"));
2064 return SVN_NO_ERROR;
2068 /* Factory function for the context baton. */
2069 static svn_error_t *
2070 get_ctxt_baton(svnlook_ctxt_t **baton_p,
2071 struct svnlook_opt_state *opt_state,
2074 svnlook_ctxt_t *baton = apr_pcalloc(pool, sizeof(*baton));
2076 SVN_ERR(svn_repos_open2(&(baton->repos), opt_state->repos_path, NULL,
2078 baton->fs = svn_repos_fs(baton->repos);
2079 svn_fs_set_warning_func(baton->fs, warning_func, NULL);
2080 baton->show_ids = opt_state->show_ids;
2081 baton->limit = opt_state->limit;
2082 baton->no_diff_deleted = opt_state->no_diff_deleted;
2083 baton->no_diff_added = opt_state->no_diff_added;
2084 baton->diff_copy_from = opt_state->diff_copy_from;
2085 baton->full_paths = opt_state->full_paths;
2086 baton->copy_info = opt_state->copy_info;
2087 baton->is_revision = opt_state->txn == NULL;
2088 baton->rev_id = opt_state->rev;
2089 baton->txn_name = apr_pstrdup(pool, opt_state->txn);
2090 baton->diff_options = svn_cstring_split(opt_state->extensions
2091 ? opt_state->extensions : "",
2092 " \t\n\r", TRUE, pool);
2093 baton->ignore_properties = opt_state->ignore_properties;
2094 baton->properties_only = opt_state->properties_only;
2095 baton->diff_cmd = opt_state->diff_cmd;
2097 if (baton->txn_name)
2098 SVN_ERR(svn_fs_open_txn(&(baton->txn), baton->fs,
2099 baton->txn_name, pool));
2100 else if (baton->rev_id == SVN_INVALID_REVNUM)
2101 SVN_ERR(svn_fs_youngest_rev(&(baton->rev_id), baton->fs, pool));
2104 return SVN_NO_ERROR;
2109 /*** Subcommands. ***/
2111 /* This implements `svn_opt_subcommand_t'. */
2112 static svn_error_t *
2113 subcommand_author(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2115 struct svnlook_opt_state *opt_state = baton;
2118 SVN_ERR(check_number_of_args(opt_state, 0));
2120 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2121 SVN_ERR(do_author(c, pool));
2122 return SVN_NO_ERROR;
2125 /* This implements `svn_opt_subcommand_t'. */
2126 static svn_error_t *
2127 subcommand_cat(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2129 struct svnlook_opt_state *opt_state = baton;
2132 SVN_ERR(check_number_of_args(opt_state, 1));
2134 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2135 SVN_ERR(do_cat(c, opt_state->arg1, pool));
2136 return SVN_NO_ERROR;
2139 /* This implements `svn_opt_subcommand_t'. */
2140 static svn_error_t *
2141 subcommand_changed(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2143 struct svnlook_opt_state *opt_state = baton;
2146 SVN_ERR(check_number_of_args(opt_state, 0));
2148 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2149 SVN_ERR(do_changed(c, pool));
2150 return SVN_NO_ERROR;
2153 /* This implements `svn_opt_subcommand_t'. */
2154 static svn_error_t *
2155 subcommand_date(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2157 struct svnlook_opt_state *opt_state = baton;
2160 SVN_ERR(check_number_of_args(opt_state, 0));
2162 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2163 SVN_ERR(do_date(c, pool));
2164 return SVN_NO_ERROR;
2167 /* This implements `svn_opt_subcommand_t'. */
2168 static svn_error_t *
2169 subcommand_diff(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2171 struct svnlook_opt_state *opt_state = baton;
2174 SVN_ERR(check_number_of_args(opt_state, 0));
2176 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2177 SVN_ERR(do_diff(c, pool));
2178 return SVN_NO_ERROR;
2181 /* This implements `svn_opt_subcommand_t'. */
2182 static svn_error_t *
2183 subcommand_dirschanged(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2185 struct svnlook_opt_state *opt_state = baton;
2188 SVN_ERR(check_number_of_args(opt_state, 0));
2190 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2191 SVN_ERR(do_dirs_changed(c, pool));
2192 return SVN_NO_ERROR;
2195 /* This implements `svn_opt_subcommand_t'. */
2196 static svn_error_t *
2197 subcommand_filesize(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2199 struct svnlook_opt_state *opt_state = baton;
2202 SVN_ERR(check_number_of_args(opt_state, 1));
2204 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2205 SVN_ERR(do_filesize(c, opt_state->arg1, pool));
2206 return SVN_NO_ERROR;
2209 /* This implements `svn_opt_subcommand_t'. */
2210 static svn_error_t *
2211 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2213 struct svnlook_opt_state *opt_state = baton;
2214 const char *header =
2215 _("general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
2216 "Note: any subcommand which takes the '--revision' and '--transaction'\n"
2217 " options will, if invoked without one of those options, act on\n"
2218 " the repository's youngest revision.\n"
2219 "Type 'svnlook help <subcommand>' for help on a specific subcommand.\n"
2220 "Type 'svnlook --version' to see the program version and FS modules.\n"
2222 "Available subcommands:\n");
2224 const char *fs_desc_start
2225 = _("The following repository back-end (FS) modules are available:\n\n");
2227 svn_stringbuf_t *version_footer;
2229 version_footer = svn_stringbuf_create(fs_desc_start, pool);
2230 SVN_ERR(svn_fs_print_modules(version_footer, pool));
2232 SVN_ERR(svn_opt_print_help4(os, "svnlook",
2233 opt_state ? opt_state->version : FALSE,
2234 opt_state ? opt_state->quiet : FALSE,
2235 opt_state ? opt_state->verbose : FALSE,
2236 version_footer->data,
2237 header, cmd_table, options_table, NULL,
2240 return SVN_NO_ERROR;
2243 /* This implements `svn_opt_subcommand_t'. */
2244 static svn_error_t *
2245 subcommand_history(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2247 struct svnlook_opt_state *opt_state = baton;
2249 const char *path = (opt_state->arg1 ? opt_state->arg1 : "/");
2251 if (opt_state->arg2 != NULL)
2252 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2253 _("Too many arguments given"));
2255 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2256 SVN_ERR(do_history(c, path, pool));
2257 return SVN_NO_ERROR;
2261 /* This implements `svn_opt_subcommand_t'. */
2262 static svn_error_t *
2263 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2265 struct svnlook_opt_state *opt_state = baton;
2269 SVN_ERR(check_number_of_args(opt_state, 1));
2271 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2273 SVN_ERR(svn_fs_get_lock(&lock, c->fs, opt_state->arg1, pool));
2277 const char *cr_date, *exp_date = "";
2278 int comment_lines = 0;
2280 cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
2282 if (lock->expiration_date)
2283 exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
2286 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
2288 SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
2289 SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
2290 SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
2291 SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
2292 SVN_ERR(svn_cmdline_printf(pool,
2293 Q_("Comment (%i line):\n%s\n",
2294 "Comment (%i lines):\n%s\n",
2297 lock->comment ? lock->comment : ""));
2300 return SVN_NO_ERROR;
2304 /* This implements `svn_opt_subcommand_t'. */
2305 static svn_error_t *
2306 subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2308 struct svnlook_opt_state *opt_state = baton;
2311 SVN_ERR(check_number_of_args(opt_state, 0));
2313 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2314 SVN_ERR(do_author(c, pool));
2315 SVN_ERR(do_date(c, pool));
2316 SVN_ERR(do_log(c, TRUE, pool));
2317 return SVN_NO_ERROR;
2320 /* This implements `svn_opt_subcommand_t'. */
2321 static svn_error_t *
2322 subcommand_log(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2324 struct svnlook_opt_state *opt_state = baton;
2327 SVN_ERR(check_number_of_args(opt_state, 0));
2329 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2330 SVN_ERR(do_log(c, FALSE, pool));
2331 return SVN_NO_ERROR;
2334 /* This implements `svn_opt_subcommand_t'. */
2335 static svn_error_t *
2336 subcommand_pget(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2338 struct svnlook_opt_state *opt_state = baton;
2341 if (opt_state->arg1 == NULL)
2343 return svn_error_createf
2344 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2345 opt_state->revprop ? _("Missing propname argument") :
2346 _("Missing propname and repository path arguments"));
2348 else if (!opt_state->revprop && opt_state->arg2 == NULL)
2350 return svn_error_create
2351 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2352 _("Missing propname or repository path argument"));
2354 if ((opt_state->revprop && opt_state->arg2 != NULL)
2355 || os->ind < os->argc)
2356 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2357 _("Too many arguments given"));
2359 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2360 SVN_ERR(do_pget(c, opt_state->arg1,
2361 opt_state->revprop ? NULL : opt_state->arg2,
2362 opt_state->verbose, opt_state->show_inherited_props,
2364 return SVN_NO_ERROR;
2367 /* This implements `svn_opt_subcommand_t'. */
2368 static svn_error_t *
2369 subcommand_plist(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2371 struct svnlook_opt_state *opt_state = baton;
2374 SVN_ERR(check_number_of_args(opt_state, opt_state->revprop ? 0 : 1));
2376 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2377 SVN_ERR(do_plist(c, opt_state->revprop ? NULL : opt_state->arg1,
2378 opt_state->verbose, opt_state->xml,
2379 opt_state->show_inherited_props, pool));
2380 return SVN_NO_ERROR;
2383 /* This implements `svn_opt_subcommand_t'. */
2384 static svn_error_t *
2385 subcommand_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2387 struct svnlook_opt_state *opt_state = baton;
2390 if (opt_state->arg2 != NULL)
2391 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2392 _("Too many arguments given"));
2394 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2395 SVN_ERR(do_tree(c, opt_state->arg1 ? opt_state->arg1 : "",
2396 opt_state->show_ids, opt_state->full_paths,
2397 ! opt_state->non_recursive, pool));
2398 return SVN_NO_ERROR;
2401 /* This implements `svn_opt_subcommand_t'. */
2402 static svn_error_t *
2403 subcommand_youngest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2405 struct svnlook_opt_state *opt_state = baton;
2408 SVN_ERR(check_number_of_args(opt_state, 0));
2410 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2411 SVN_ERR(svn_cmdline_printf(pool, "%ld\n", c->rev_id));
2412 return SVN_NO_ERROR;
2415 /* This implements `svn_opt_subcommand_t'. */
2416 static svn_error_t *
2417 subcommand_uuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2419 struct svnlook_opt_state *opt_state = baton;
2423 SVN_ERR(check_number_of_args(opt_state, 0));
2425 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2426 SVN_ERR(svn_fs_get_uuid(c->fs, &uuid, pool));
2427 SVN_ERR(svn_cmdline_printf(pool, "%s\n", uuid));
2428 return SVN_NO_ERROR;
2436 main(int argc, const char *argv[])
2439 apr_status_t apr_err;
2442 const svn_opt_subcommand_desc2_t *subcommand = NULL;
2443 struct svnlook_opt_state opt_state;
2446 apr_array_header_t *received_opts;
2449 /* Initialize the app. */
2450 if (svn_cmdline_init("svnlook", stderr) != EXIT_SUCCESS)
2451 return EXIT_FAILURE;
2453 /* Create our top-level pool. Use a separate mutexless allocator,
2454 * given this application is single threaded.
2456 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2458 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
2460 /* Check library versions */
2461 err = check_lib_versions();
2463 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2465 /* Initialize the FS library. */
2466 err = svn_fs_initialize(pool);
2468 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2472 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2473 svn_pool_destroy(pool);
2474 return EXIT_FAILURE;
2477 /* Initialize opt_state. */
2478 memset(&opt_state, 0, sizeof(opt_state));
2479 opt_state.rev = SVN_INVALID_REVNUM;
2481 /* Parse options. */
2482 err = svn_cmdline__getopt_init(&os, argc, argv, pool);
2484 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2489 const char *opt_arg;
2491 /* Parse the next option. */
2492 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2493 if (APR_STATUS_IS_EOF(apr_err))
2497 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2498 svn_pool_destroy(pool);
2499 return EXIT_FAILURE;
2502 /* Stash the option code in an array before parsing it. */
2503 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2509 char *digits_end = NULL;
2510 opt_state.rev = strtol(opt_arg, &digits_end, 10);
2511 if ((! SVN_IS_VALID_REVNUM(opt_state.rev))
2514 SVN_INT_ERR(svn_error_create
2515 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2516 _("Invalid revision number supplied")));
2521 opt_state.txn = opt_arg;
2525 opt_state.non_recursive = TRUE;
2529 opt_state.verbose = TRUE;
2534 opt_state.help = TRUE;
2538 opt_state.quiet = TRUE;
2541 case svnlook__revprop_opt:
2542 opt_state.revprop = TRUE;
2545 case svnlook__xml_opt:
2546 opt_state.xml = TRUE;
2549 case svnlook__version:
2550 opt_state.version = TRUE;
2553 case svnlook__show_ids:
2554 opt_state.show_ids = TRUE;
2560 opt_state.limit = strtol(opt_arg, &end, 10);
2561 if (end == opt_arg || *end != '\0')
2563 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2564 _("Non-numeric limit argument given"));
2565 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2567 if (opt_state.limit <= 0)
2569 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2570 _("Argument to --limit must be positive"));
2571 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2576 case svnlook__no_diff_deleted:
2577 opt_state.no_diff_deleted = TRUE;
2580 case svnlook__no_diff_added:
2581 opt_state.no_diff_added = TRUE;
2584 case svnlook__diff_copy_from:
2585 opt_state.diff_copy_from = TRUE;
2588 case svnlook__full_paths:
2589 opt_state.full_paths = TRUE;
2592 case svnlook__copy_info:
2593 opt_state.copy_info = TRUE;
2597 opt_state.extensions = opt_arg;
2600 case svnlook__ignore_properties:
2601 opt_state.ignore_properties = TRUE;
2604 case svnlook__properties_only:
2605 opt_state.properties_only = TRUE;
2608 case svnlook__diff_cmd:
2609 opt_state.diff_cmd = opt_arg;
2612 case svnlook__show_inherited_props:
2613 opt_state.show_inherited_props = TRUE;
2617 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2618 svn_pool_destroy(pool);
2619 return EXIT_FAILURE;
2624 /* The --transaction and --revision options may not co-exist. */
2625 if ((opt_state.rev != SVN_INVALID_REVNUM) && opt_state.txn)
2626 SVN_INT_ERR(svn_error_create
2627 (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
2628 _("The '--transaction' (-t) and '--revision' (-r) arguments "
2629 "cannot co-exist")));
2631 /* The --show-inherited-props and --revprop options may not co-exist. */
2632 if (opt_state.show_inherited_props && opt_state.revprop)
2633 SVN_INT_ERR(svn_error_create
2634 (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
2635 _("Cannot use the '--show-inherited-props' option with the "
2636 "'--revprop' option")));
2638 /* If the user asked for help, then the rest of the arguments are
2639 the names of subcommands to get help on (if any), or else they're
2640 just typos/mistakes. Whatever the case, the subcommand to
2641 actually run is subcommand_help(). */
2643 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
2645 /* If we're not running the `help' subcommand, then look for a
2646 subcommand in the first argument. */
2647 if (subcommand == NULL)
2649 if (os->ind >= os->argc)
2651 if (opt_state.version)
2653 /* Use the "help" subcommand to handle the "--version" option. */
2654 static const svn_opt_subcommand_desc2_t pseudo_cmd =
2655 { "--version", subcommand_help, {0}, "",
2656 {svnlook__version, /* must accept its own option */
2660 subcommand = &pseudo_cmd;
2665 (svn_cmdline_fprintf(stderr, pool,
2666 _("Subcommand argument required\n")));
2667 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2668 svn_pool_destroy(pool);
2669 return EXIT_FAILURE;
2674 const char *first_arg = os->argv[os->ind++];
2675 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
2676 if (subcommand == NULL)
2678 const char *first_arg_utf8;
2679 err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
2682 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2684 svn_cmdline_fprintf(stderr, pool,
2685 _("Unknown subcommand: '%s'\n"),
2687 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2689 /* Be kind to people who try 'svnlook verify'. */
2690 if (strcmp(first_arg_utf8, "verify") == 0)
2693 svn_cmdline_fprintf(stderr, pool,
2694 _("Try 'svnadmin verify' instead.\n")));
2698 svn_pool_destroy(pool);
2699 return EXIT_FAILURE;
2704 /* If there's a second argument, it's the repository. There may be
2705 more arguments following the repository; usually the next one is
2706 a path within the repository, or it's a propname and the one
2707 after that is the path. Since we don't know, we just call them
2708 arg1 and arg2, meaning the first and second arguments following
2710 if (subcommand->cmd_func != subcommand_help)
2712 const char *repos_path = NULL;
2713 const char *arg1 = NULL, *arg2 = NULL;
2715 /* Get the repository. */
2716 if (os->ind < os->argc)
2718 SVN_INT_ERR(svn_utf_cstring_to_utf8(&repos_path,
2719 os->argv[os->ind++],
2721 repos_path = svn_dirent_internal_style(repos_path, pool);
2724 if (repos_path == NULL)
2727 (svn_cmdline_fprintf(stderr, pool,
2728 _("Repository argument required\n")));
2729 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2730 svn_pool_destroy(pool);
2731 return EXIT_FAILURE;
2733 else if (svn_path_is_url(repos_path))
2736 (svn_cmdline_fprintf(stderr, pool,
2737 _("'%s' is a URL when it should be a path\n"),
2739 svn_pool_destroy(pool);
2740 return EXIT_FAILURE;
2743 opt_state.repos_path = repos_path;
2745 /* Get next arg (arg1), if any. */
2746 if (os->ind < os->argc)
2748 SVN_INT_ERR(svn_utf_cstring_to_utf8
2749 (&arg1, os->argv[os->ind++], pool));
2750 arg1 = svn_dirent_internal_style(arg1, pool);
2752 opt_state.arg1 = arg1;
2754 /* Get next arg (arg2), if any. */
2755 if (os->ind < os->argc)
2757 SVN_INT_ERR(svn_utf_cstring_to_utf8
2758 (&arg2, os->argv[os->ind++], pool));
2759 arg2 = svn_dirent_internal_style(arg2, pool);
2761 opt_state.arg2 = arg2;
2764 /* Check that the subcommand wasn't passed any inappropriate options. */
2765 for (i = 0; i < received_opts->nelts; i++)
2767 opt_id = APR_ARRAY_IDX(received_opts, i, int);
2769 /* All commands implicitly accept --help, so just skip over this
2770 when we see it. Note that we don't want to include this option
2771 in their "accepted options" list because it would be awfully
2772 redundant to display it in every commands' help text. */
2773 if (opt_id == 'h' || opt_id == '?')
2776 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2779 const apr_getopt_option_t *badopt =
2780 svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
2782 svn_opt_format_option(&optstr, badopt, FALSE, pool);
2783 if (subcommand->name[0] == '-')
2784 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2787 (svn_cmdline_fprintf
2789 _("Subcommand '%s' doesn't accept option '%s'\n"
2790 "Type 'svnlook help %s' for usage.\n"),
2791 subcommand->name, optstr, subcommand->name));
2792 svn_pool_destroy(pool);
2793 return EXIT_FAILURE;
2797 /* Set up our cancellation support. */
2798 apr_signal(SIGINT, signal_handler);
2800 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
2801 apr_signal(SIGBREAK, signal_handler);
2804 apr_signal(SIGHUP, signal_handler);
2807 apr_signal(SIGTERM, signal_handler);
2811 /* Disable SIGPIPE generation for the platforms that have it. */
2812 apr_signal(SIGPIPE, SIG_IGN);
2816 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
2817 * working with large files when compiled against an APR that doesn't have
2818 * large file support will crash the program, which is uncool. */
2819 apr_signal(SIGXFSZ, SIG_IGN);
2822 /* Run the subcommand. */
2823 err = (*subcommand->cmd_func)(os, &opt_state, pool);
2826 /* For argument-related problems, suggest using the 'help'
2828 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2829 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2831 err = svn_error_quick_wrap(err,
2832 _("Try 'svnlook help' for more info"));
2834 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2838 svn_pool_destroy(pool);
2839 /* Ensure everything is printed on stdout, so the user sees any
2841 SVN_INT_ERR(svn_cmdline_fflush(stdout));
2842 return EXIT_SUCCESS;