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>
32 #define APR_WANT_STDIO
33 #define APR_WANT_STRFUNC
37 #include "svn_cmdline.h"
38 #include "svn_types.h"
39 #include "svn_pools.h"
40 #include "svn_error.h"
41 #include "svn_error_codes.h"
42 #include "svn_dirent_uri.h"
44 #include "svn_repos.h"
45 #include "svn_cache_config.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_cmdline_private.h"
58 #include "private/svn_diff_private.h"
59 #include "private/svn_fspath.h"
60 #include "private/svn_io_private.h"
61 #include "private/svn_sorts_private.h"
63 #include "svn_private_config.h"
66 /*** Some convenience macros and types. ***/
69 /* Option handling. */
71 static svn_opt_subcommand_t
77 subcommand_dirschanged,
90 /* Option codes and descriptions. */
93 svnlook__version = SVN_OPT_FIRST_LONGOPT_ID,
95 svnlook__no_diff_deleted,
96 svnlook__no_diff_added,
97 svnlook__diff_copy_from,
102 svnlook__ignore_properties,
103 svnlook__properties_only,
105 svnlook__show_inherited_props,
110 * The entire list must be terminated with an entry of nulls.
112 static const apr_getopt_option_t options_table[] =
115 N_("show help on a subcommand")},
117 {"copy-info", svnlook__copy_info, 0,
118 N_("show details for copies")},
120 {"diff-copy-from", svnlook__diff_copy_from, 0,
121 N_("print differences against the copy source")},
123 {"full-paths", svnlook__full_paths, 0,
124 N_("show full paths instead of indenting them")},
127 N_("show help on a subcommand")},
130 N_("maximum number of history entries")},
132 {"no-diff-added", svnlook__no_diff_added, 0,
133 N_("do not print differences for added files")},
135 {"no-diff-deleted", svnlook__no_diff_deleted, 0,
136 N_("do not print differences for deleted files")},
138 {"diff-cmd", svnlook__diff_cmd, 1,
139 N_("use ARG as diff command")},
141 {"ignore-properties", svnlook__ignore_properties, 0,
142 N_("ignore properties during the operation")},
144 {"properties-only", svnlook__properties_only, 0,
145 N_("show only properties during the operation")},
147 {"memory-cache-size", 'M', 1,
148 N_("size of the extra in-memory cache in MB used to\n"
150 "minimize redundant operations. Default: 16.\n"
152 "[used for FSFS repositories only]")},
154 {"no-newline", svnlook__no_newline, 0,
155 N_("do not output the trailing newline")},
157 {"non-recursive", 'N', 0,
158 N_("operate on single directory only")},
161 N_("specify revision number ARG")},
163 {"revprop", svnlook__revprop_opt, 0,
164 N_("operate on a revision property (use with -r or -t)")},
166 {"show-ids", svnlook__show_ids, 0,
167 N_("show node revision ids for each path")},
169 {"show-inherited-props", svnlook__show_inherited_props, 0,
170 N_("show path's inherited properties")},
172 {"transaction", 't', 1,
173 N_("specify transaction name ARG")},
178 {"version", svnlook__version, 0,
179 N_("show program version information")},
181 {"xml", svnlook__xml_opt, 0,
182 N_("output in XML")},
184 {"extensions", 'x', 1,
185 N_("Specify differencing options for external diff or\n"
187 "internal diff. Default: '-u'. Options are\n"
189 "separated by spaces. Internal diff takes:\n"
191 " -u, --unified: Show 3 lines of unified context\n"
193 " -b, --ignore-space-change: Ignore changes in\n"
195 " amount of white space\n"
197 " -w, --ignore-all-space: Ignore all white space\n"
199 " --ignore-eol-style: Ignore changes in EOL style\n"
201 " -U ARG, --context ARG: Show ARG lines of context\n"
203 " -p, --show-c-function: Show C function name")},
206 N_("no progress (only errors) to stderr")},
212 /* Array of available subcommands.
213 * The entire list must be terminated with an entry of nulls.
215 static const svn_opt_subcommand_desc3_t cmd_table[] =
217 {"author", subcommand_author, {0}, {N_(
218 "usage: svnlook author REPOS_PATH\n"
220 "Print the author.\n"
224 {"cat", subcommand_cat, {0}, {N_(
225 "usage: svnlook cat REPOS_PATH FILE_PATH\n"
227 "Print the contents of a file. Leading '/' on FILE_PATH is optional.\n"
231 {"changed", subcommand_changed, {0}, {N_(
232 "usage: svnlook changed REPOS_PATH\n"
234 "Print the paths that were changed.\n"
236 {'r', 't', svnlook__copy_info} },
238 {"date", subcommand_date, {0}, {N_(
239 "usage: svnlook date REPOS_PATH\n"
241 "Print the datestamp.\n"
245 {"diff", subcommand_diff, {0}, {N_(
246 "usage: svnlook diff REPOS_PATH\n"
248 "Print GNU-style diffs of changed files and properties.\n"
250 {'r', 't', svnlook__no_diff_deleted, svnlook__no_diff_added,
251 svnlook__diff_copy_from, svnlook__diff_cmd, 'x',
252 svnlook__ignore_properties, svnlook__properties_only} },
254 {"dirs-changed", subcommand_dirschanged, {0}, {N_(
255 "usage: svnlook dirs-changed REPOS_PATH\n"
257 "Print the directories that were themselves changed (property edits)\n"
258 "or whose file children were changed.\n"
262 {"filesize", subcommand_filesize, {0}, {N_(
263 "usage: svnlook filesize REPOS_PATH PATH_IN_REPOS\n"
265 "Print the size (in bytes) of the file located at PATH_IN_REPOS as\n"
266 "it is represented in the repository.\n"
270 {"help", subcommand_help, {"?", "h"}, {N_(
271 "usage: svnlook help [SUBCOMMAND...]\n"
273 "Describe the usage of this program or its subcommands.\n"
277 {"history", subcommand_history, {0}, {N_(
278 "usage: svnlook history REPOS_PATH [PATH_IN_REPOS]\n"
280 "Print information about the history of a path in the repository (or\n"
281 "the root directory if no path is supplied).\n"
283 {'r', svnlook__show_ids, 'l'} },
285 {"info", subcommand_info, {0}, {N_(
286 "usage: svnlook info REPOS_PATH\n"
288 "Print the author, datestamp, log message size, and log message.\n"
292 {"lock", subcommand_lock, {0}, {N_(
293 "usage: svnlook lock REPOS_PATH PATH_IN_REPOS\n"
295 "If a lock exists on a path in the repository, describe it.\n"
299 {"log", subcommand_log, {0}, {N_(
300 "usage: svnlook log REPOS_PATH\n"
302 "Print the log message.\n"
306 {"propget", subcommand_pget, {"pget", "pg"}, {N_(
307 "usage: 1. svnlook propget REPOS_PATH PROPNAME PATH_IN_REPOS\n"
309 /* The line above is actually needed, so do NOT delete it! */
310 " 2. svnlook propget --revprop REPOS_PATH PROPNAME\n"
312 "Print the raw value of a property on a path in the repository.\n"
313 "With --revprop, print the raw value of a revision property.\n"
315 {'r', 't', 'v', svnlook__revprop_opt, svnlook__show_inherited_props} },
317 {"proplist", subcommand_plist, {"plist", "pl"}, {N_(
318 "usage: 1. svnlook proplist REPOS_PATH PATH_IN_REPOS\n"
320 /* The line above is actually needed, so do NOT delete it! */
321 " 2. svnlook proplist --revprop REPOS_PATH\n"
323 "List the properties of a path in the repository, or\n"
324 "with the --revprop option, revision properties.\n"
325 "With -v, show the property values too.\n"
327 {'r', 't', 'v', svnlook__revprop_opt, svnlook__xml_opt,
328 svnlook__show_inherited_props} },
330 {"tree", subcommand_tree, {0}, {N_(
331 "usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n"
333 "Print the tree, starting at PATH_IN_REPOS (if supplied, at the root\n"
334 "of the tree otherwise), optionally showing node revision ids.\n"
336 {'r', 't', 'N', svnlook__show_ids, svnlook__full_paths, 'M'} },
338 {"uuid", subcommand_uuid, {0}, {N_(
339 "usage: svnlook uuid REPOS_PATH\n"
341 "Print the repository's UUID.\n"
345 {"youngest", subcommand_youngest, {0}, {N_(
346 "usage: svnlook youngest REPOS_PATH\n"
348 "Print the youngest revision number.\n"
350 {svnlook__no_newline} },
352 { NULL, NULL, {0}, {NULL}, {0} }
356 /* Baton for passing option/argument state to a subcommand function. */
357 struct svnlook_opt_state
359 const char *repos_path; /* 'arg0' is always the path to the repository. */
360 const char *arg1; /* Usually an fs path, a propname, or NULL. */
361 const char *arg2; /* Usually an fs path or NULL. */
364 svn_boolean_t version; /* --version */
365 svn_boolean_t show_ids; /* --show-ids */
366 apr_size_t limit; /* --limit */
367 svn_boolean_t help; /* --help */
368 svn_boolean_t no_diff_deleted; /* --no-diff-deleted */
369 svn_boolean_t no_diff_added; /* --no-diff-added */
370 svn_boolean_t diff_copy_from; /* --diff-copy-from */
371 svn_boolean_t verbose; /* --verbose */
372 svn_boolean_t revprop; /* --revprop */
373 svn_boolean_t full_paths; /* --full-paths */
374 svn_boolean_t copy_info; /* --copy-info */
375 svn_boolean_t non_recursive; /* --non-recursive */
376 svn_boolean_t xml; /* --xml */
377 const char *extensions; /* diff extension args (UTF-8!) */
378 svn_boolean_t quiet; /* --quiet */
379 svn_boolean_t ignore_properties; /* --ignore_properties */
380 svn_boolean_t properties_only; /* --properties-only */
381 const char *diff_cmd; /* --diff-cmd */
382 svn_boolean_t show_inherited_props; /* --show-inherited-props */
383 svn_boolean_t no_newline; /* --no-newline */
384 apr_uint64_t memory_cache_size; /* --memory-cache-size */
388 typedef struct svnlook_ctxt_t
392 svn_boolean_t is_revision;
393 svn_boolean_t show_ids;
395 svn_boolean_t no_diff_deleted;
396 svn_boolean_t no_diff_added;
397 svn_boolean_t diff_copy_from;
398 svn_boolean_t full_paths;
399 svn_boolean_t copy_info;
402 const char *txn_name /* UTF-8! */;
403 const apr_array_header_t *diff_options;
404 svn_boolean_t ignore_properties;
405 svn_boolean_t properties_only;
406 const char *diff_cmd;
411 /*** Helper functions. ***/
413 static svn_cancel_func_t check_cancel = NULL;
415 /* Version compatibility check */
417 check_lib_versions(void)
419 static const svn_version_checklist_t checklist[] =
421 { "svn_subr", svn_subr_version },
422 { "svn_repos", svn_repos_version },
423 { "svn_fs", svn_fs_version },
424 { "svn_delta", svn_delta_version },
425 { "svn_diff", svn_diff_version },
428 SVN_VERSION_DEFINE(my_version);
430 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
434 /* Get revision or transaction property PROP_NAME for the revision or
435 transaction specified in C, allocating in in POOL and placing it in
438 get_property(svn_string_t **prop_value,
440 const char *prop_name,
443 svn_string_t *raw_value;
445 /* Fetch transaction property... */
446 if (! c->is_revision)
447 SVN_ERR(svn_fs_txn_prop(&raw_value, c->txn, prop_name, pool));
449 /* ...or revision property -- it's your call. */
451 SVN_ERR(svn_fs_revision_prop2(&raw_value, c->fs, c->rev_id,
452 prop_name, TRUE, pool, pool));
454 *prop_value = raw_value;
461 get_root(svn_fs_root_t **root,
465 /* Open up the appropriate root (revision or transaction). */
468 /* If we didn't get a valid revision number, we'll look at the
469 youngest revision. */
470 if (! SVN_IS_VALID_REVNUM(c->rev_id))
471 SVN_ERR(svn_fs_youngest_rev(&(c->rev_id), c->fs, pool));
473 SVN_ERR(svn_fs_revision_root(root, c->fs, c->rev_id, pool));
477 SVN_ERR(svn_fs_txn_root(root, c->txn, pool));
485 /*** Tree Routines ***/
487 /* Generate a generic delta tree. */
489 generate_delta_tree(svn_repos_node_t **tree,
492 svn_revnum_t base_rev,
495 svn_fs_root_t *base_root;
496 const svn_delta_editor_t *editor;
498 apr_pool_t *edit_pool = svn_pool_create(pool);
499 svn_fs_t *fs = svn_repos_fs(repos);
501 /* Get the base root. */
502 SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, pool));
504 /* Request our editor. */
505 SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos,
506 base_root, root, pool, edit_pool));
508 /* Drive our editor. */
509 SVN_ERR(svn_repos_replay2(root, "", SVN_INVALID_REVNUM, TRUE,
510 editor, edit_baton, NULL, NULL, edit_pool));
512 /* Return the tree we just built. */
513 *tree = svn_repos_node_from_baton(edit_baton);
514 svn_pool_destroy(edit_pool);
520 /*** Tree Printing Routines ***/
522 /* Recursively print only directory nodes that either a) have property
523 mods, or b) contains files that have changed, or c) has added or deleted
524 children. NODE is the root node of the tree delta, so every node in it
525 is either changed or is a directory with a changed node somewhere in the
529 print_dirs_changed_tree(svn_repos_node_t *node,
530 const char *path /* UTF-8! */,
533 svn_repos_node_t *tmp_node;
534 svn_boolean_t print_me = FALSE;
535 const char *full_path;
536 apr_pool_t *iterpool;
538 SVN_ERR(check_cancel(NULL));
543 /* Not a directory? We're not interested. */
544 if (node->kind != svn_node_dir)
547 /* Got prop mods? Excellent. */
551 /* Fly through the list of children, checking for modified files. */
552 tmp_node = node->child;
553 while (tmp_node && (! print_me))
555 if ((tmp_node->kind == svn_node_file)
556 || (tmp_node->action == 'A')
557 || (tmp_node->action == 'D'))
561 tmp_node = tmp_node->sibling;
564 /* Print the node if it qualifies. */
567 SVN_ERR(svn_cmdline_printf(pool, "%s/\n", path));
570 /* Return here if the node has no children. */
571 tmp_node = node->child;
575 /* Recursively handle the node's children. */
576 iterpool = svn_pool_create(pool);
579 svn_pool_clear(iterpool);
580 full_path = svn_dirent_join(path, tmp_node->name, iterpool);
581 SVN_ERR(print_dirs_changed_tree(tmp_node, full_path, iterpool));
582 tmp_node = tmp_node->sibling;
584 svn_pool_destroy(iterpool);
590 /* Recursively print all nodes in the tree that have been modified
591 (do not include directories affected only by "bubble-up"). */
593 print_changed_tree(svn_repos_node_t *node,
594 const char *path /* UTF-8! */,
595 svn_boolean_t copy_info,
598 const char *full_path;
599 char status[4] = "_ ";
600 svn_boolean_t print_me = TRUE;
601 apr_pool_t *iterpool;
603 SVN_ERR(check_cancel(NULL));
608 /* Print the node. */
609 if (node->action == 'A')
612 if (copy_info && node->copyfrom_path)
615 else if (node->action == 'D')
617 else if (node->action == 'R')
619 if ((! node->text_mod) && (! node->prop_mod))
629 /* Print this node unless told to skip it. */
632 SVN_ERR(svn_cmdline_printf(pool, "%s %s%s\n",
635 node->kind == svn_node_dir ? "/" : ""));
636 if (copy_info && node->copyfrom_path)
637 /* Remove the leading slash from the copyfrom path for consistency
638 with the rest of the output. */
639 SVN_ERR(svn_cmdline_printf(pool, " (from %s%s:r%ld)\n",
640 (node->copyfrom_path[0] == '/'
641 ? node->copyfrom_path + 1
642 : node->copyfrom_path),
643 (node->kind == svn_node_dir ? "/" : ""),
644 node->copyfrom_rev));
647 /* Return here if the node has no children. */
652 /* Recursively handle the node's children. */
653 iterpool = svn_pool_create(pool);
656 svn_pool_clear(iterpool);
657 full_path = svn_dirent_join(path, node->name, iterpool);
658 SVN_ERR(print_changed_tree(node, full_path, copy_info, iterpool));
659 node = node->sibling;
661 svn_pool_destroy(iterpool);
668 dump_contents(svn_stream_t *stream,
670 const char *path /* UTF-8! */,
674 SVN_ERR(svn_stream_close(stream)); /* leave an empty file */
677 svn_stream_t *contents;
679 /* Grab the contents and copy them into the given stream. */
680 SVN_ERR(svn_fs_file_contents(&contents, root, path, pool));
681 SVN_ERR(svn_stream_copy3(contents, stream, NULL, NULL, pool));
688 /* Prepare temporary files *TMPFILE1 and *TMPFILE2 for diffing
689 PATH1@ROOT1 versus PATH2@ROOT2. If either ROOT1 or ROOT2 is NULL,
690 the temporary file for its path/root will be an empty one.
691 Otherwise, its temporary file will contain the contents of that
692 path/root in the repository.
694 An exception to this is when either path/root has an svn:mime-type
695 property set on it which indicates that the file contains
696 non-textual data -- in this case, the *IS_BINARY flag is set and no
697 temporary files are created.
699 TMPFILE1 and TMPFILE2 will be removed when RESULT_POOL is destroyed.
702 prepare_tmpfiles(const char **tmpfile1,
703 const char **tmpfile2,
704 svn_boolean_t *is_binary,
705 svn_fs_root_t *root1,
707 svn_fs_root_t *root2,
709 apr_pool_t *result_pool,
710 apr_pool_t *scratch_pool)
712 svn_string_t *mimetype;
713 svn_stream_t *stream;
715 /* Init the return values. */
720 assert(path1 && path2);
722 /* Check for binary mimetypes. If either file has a binary
723 mimetype, get outta here. */
726 SVN_ERR(svn_fs_node_prop(&mimetype, root1, path1,
727 SVN_PROP_MIME_TYPE, scratch_pool));
728 if (mimetype && svn_mime_type_is_binary(mimetype->data))
736 SVN_ERR(svn_fs_node_prop(&mimetype, root2, path2,
737 SVN_PROP_MIME_TYPE, scratch_pool));
738 if (mimetype && svn_mime_type_is_binary(mimetype->data))
745 /* Now, prepare the two temporary files, each of which will either
746 be empty, or will have real contents. */
747 SVN_ERR(svn_stream_open_unique(&stream, tmpfile1, NULL,
748 svn_io_file_del_on_pool_cleanup,
749 result_pool, scratch_pool));
750 SVN_ERR(dump_contents(stream, root1, path1, scratch_pool));
752 SVN_ERR(svn_stream_open_unique(&stream, tmpfile2, NULL,
753 svn_io_file_del_on_pool_cleanup,
754 result_pool, scratch_pool));
755 SVN_ERR(dump_contents(stream, root2, path2, scratch_pool));
761 /* Generate a diff label for PATH in ROOT, allocating in POOL.
762 ROOT may be NULL, in which case revision 0 is used. */
764 generate_label(const char **label,
771 const char *name = NULL;
772 svn_revnum_t rev = SVN_INVALID_REVNUM;
776 svn_fs_t *fs = svn_fs_root_fs(root);
777 if (svn_fs_is_revision_root(root))
779 rev = svn_fs_revision_root_revision(root);
780 SVN_ERR(svn_fs_revision_prop2(&date, fs, rev,
781 SVN_PROP_REVISION_DATE, TRUE,
787 name = svn_fs_txn_root_name(root, pool);
788 SVN_ERR(svn_fs_open_txn(&txn, fs, name, pool));
789 SVN_ERR(svn_fs_txn_prop(&date, txn, SVN_PROP_REVISION_DATE, pool));
799 datestr = apr_psprintf(pool, "%.10s %.8s UTC", date->data, date->data + 11);
804 *label = apr_psprintf(pool, "%s\t%s (txn %s)",
805 path, datestr, name);
807 *label = apr_psprintf(pool, "%s\t%s (rev %ld)",
813 /* Helper function to display differences in properties of a file */
815 display_prop_diffs(svn_stream_t *outstream,
816 const char *encoding,
817 const apr_array_header_t *propchanges,
818 apr_hash_t *original_props,
823 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
824 _("%sProperty changes on: %s%s"),
829 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
830 SVN_DIFF__UNDER_STRING APR_EOL_STR));
832 SVN_ERR(check_cancel(NULL));
834 SVN_ERR(svn_diff__display_prop_diffs(
835 outstream, encoding, propchanges, original_props,
836 FALSE /* pretty_print_mergeinfo */,
837 -1 /* context_size */,
838 check_cancel, NULL, pool));
844 /* Recursively print all nodes in the tree that have been modified
845 (do not include directories affected only by "bubble-up"). */
847 print_diff_tree(svn_stream_t *out_stream,
848 const char *encoding,
850 svn_fs_root_t *base_root,
851 svn_repos_node_t *node,
852 const char *path /* UTF-8! */,
853 const char *base_path /* UTF-8! */,
854 const svnlook_ctxt_t *c,
857 const char *orig_path = NULL, *new_path = NULL;
858 svn_boolean_t do_diff = FALSE;
859 svn_boolean_t orig_empty = FALSE;
860 svn_boolean_t is_copy = FALSE;
861 svn_boolean_t binary = FALSE;
862 svn_boolean_t diff_header_printed = FALSE;
863 apr_pool_t *iterpool;
864 svn_stringbuf_t *header;
866 SVN_ERR(check_cancel(NULL));
871 header = svn_stringbuf_create_empty(pool);
873 /* Print copyfrom history for the top node of a copied tree. */
874 if ((SVN_IS_VALID_REVNUM(node->copyfrom_rev))
875 && (node->copyfrom_path != NULL))
877 /* This is ... a copy. */
880 /* Propagate the new base. Copyfrom paths usually start with a
881 slash; we remove it for consistency with the target path.
882 ### Yes, it would be *much* better for something in the path
883 library to be taking care of this! */
884 if (node->copyfrom_path[0] == '/')
885 base_path = apr_pstrdup(pool, node->copyfrom_path + 1);
887 base_path = apr_pstrdup(pool, node->copyfrom_path);
889 svn_stringbuf_appendcstr
891 apr_psprintf(pool, _("Copied: %s (from rev %ld, %s)\n"),
892 path, node->copyfrom_rev, base_path));
894 SVN_ERR(svn_fs_revision_root(&base_root,
895 svn_fs_root_fs(base_root),
896 node->copyfrom_rev, pool));
899 /*** First, we'll just print file content diffs. ***/
900 if (node->kind == svn_node_file)
902 /* Here's the generalized way we do our diffs:
904 - First, we'll check for svn:mime-type properties on the old
905 and new files. If either has such a property, and it
906 represents a binary type, we won't actually be doing a real
909 - Second, dump the contents of the new version of the file
910 into the temporary directory.
912 - Then, dump the contents of the old version of the file into
913 the temporary directory.
915 - Next, we run 'diff', passing the repository paths as the
918 - Finally, we delete the temporary files. */
919 if (node->action == 'R' && node->text_mod)
922 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
923 base_root, base_path, root, path,
926 else if (c->diff_copy_from && node->action == 'A' && is_copy)
931 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
932 base_root, base_path, root, path,
936 else if (! c->no_diff_added && node->action == 'A')
940 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
941 NULL, base_path, root, path,
944 else if (! c->no_diff_deleted && node->action == 'D')
947 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
948 base_root, base_path, NULL, path,
952 /* The header for the copy case has already been created, and we don't
953 want a header here for files with only property modifications. */
955 && (node->action != 'R' || node->text_mod))
957 svn_stringbuf_appendcstr
958 (header, apr_psprintf(pool, "%s: %s\n",
959 ((node->action == 'A') ? _("Added") :
960 ((node->action == 'D') ? _("Deleted") :
961 ((node->action == 'R') ? _("Modified")
967 if (do_diff && (! c->properties_only))
969 svn_stringbuf_appendcstr(header, SVN_DIFF__EQUAL_STRING "\n");
973 svn_stringbuf_appendcstr(header, _("(Binary files differ)\n\n"));
974 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
975 "%s", header->data));
983 const char *outfilename;
984 const char *errfilename;
985 svn_stream_t *stream;
986 svn_stream_t *err_stream;
987 const char **diff_cmd_argv;
990 const char *orig_label;
991 const char *new_label;
993 diff_cmd_argv = NULL;
994 diff_cmd_argc = c->diff_options->nelts;
998 diff_cmd_argv = apr_palloc(pool,
999 diff_cmd_argc * sizeof(char *));
1000 for (i = 0; i < diff_cmd_argc; i++)
1001 SVN_ERR(svn_utf_cstring_to_utf8(&diff_cmd_argv[i],
1002 APR_ARRAY_IDX(c->diff_options, i, const char *),
1006 /* Print diff header. */
1007 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1008 "%s", header->data));
1011 SVN_ERR(generate_label(&orig_label, NULL, path, pool));
1013 SVN_ERR(generate_label(&orig_label, base_root,
1015 SVN_ERR(generate_label(&new_label, root, path, pool));
1017 /* We deal in streams, but svn_io_run_diff2() deals in file
1018 handles, so we may need to make temporary files and then
1019 copy the contents to our stream. */
1020 outfile = svn_stream__aprfile(out_stream);
1024 SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
1025 svn_io_file_del_on_pool_cleanup, pool, pool));
1026 SVN_ERR(svn_stream_for_stderr(&err_stream, pool));
1027 errfile = svn_stream__aprfile(err_stream);
1031 SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
1032 svn_io_file_del_on_pool_cleanup, pool, pool));
1034 SVN_ERR(svn_io_run_diff2(".",
1037 orig_label, new_label,
1038 orig_path, new_path,
1039 &exitcode, outfile, errfile,
1040 c->diff_cmd, pool));
1042 /* Now, open and copy our files to our output streams. */
1045 SVN_ERR(svn_io_file_close(outfile, pool));
1046 SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
1048 SVN_ERR(svn_stream_copy3(stream,
1049 svn_stream_disown(out_stream, pool),
1054 SVN_ERR(svn_io_file_close(errfile, pool));
1055 SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
1057 SVN_ERR(svn_stream_copy3(stream,
1058 svn_stream_disown(err_stream, pool),
1062 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1064 diff_header_printed = TRUE;
1069 svn_diff_file_options_t *opts = svn_diff_file_options_create(pool);
1071 if (c->diff_options)
1072 SVN_ERR(svn_diff_file_options_parse(opts, c->diff_options, pool));
1074 SVN_ERR(svn_diff_file_diff_2(&diff, orig_path,
1075 new_path, opts, pool));
1077 if (svn_diff_contains_diffs(diff))
1079 const char *orig_label, *new_label;
1081 /* Print diff header. */
1082 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1083 "%s", header->data));
1086 SVN_ERR(generate_label(&orig_label, NULL, path, pool));
1088 SVN_ERR(generate_label(&orig_label, base_root,
1090 SVN_ERR(generate_label(&new_label, root, path, pool));
1091 SVN_ERR(svn_diff_file_output_unified4(
1092 out_stream, diff, orig_path, new_path,
1093 orig_label, new_label,
1094 svn_cmdline_output_encoding(pool), NULL,
1095 opts->show_c_function, opts->context_size,
1096 check_cancel, NULL, pool));
1097 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1099 diff_header_printed = TRUE;
1101 else if (! node->prop_mod &&
1102 ((! c->no_diff_added && node->action == 'A') ||
1103 (! c->no_diff_deleted && node->action == 'D')))
1105 /* There was an empty file added or deleted in this revision.
1106 * We can't print a diff, but we can at least print
1107 * a diff header since we know what happened to this file. */
1108 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1109 "%s", header->data));
1115 /*** Now handle property diffs ***/
1116 if ((node->prop_mod) && (node->action != 'D') && (! c->ignore_properties))
1118 apr_hash_t *local_proptable;
1119 apr_hash_t *base_proptable;
1120 apr_array_header_t *propchanges, *props;
1122 SVN_ERR(svn_fs_node_proplist(&local_proptable, root, path, pool));
1123 if (c->diff_copy_from && node->action == 'A' && is_copy)
1124 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1126 else if (node->action == 'A')
1127 base_proptable = apr_hash_make(pool);
1128 else /* node->action == 'R' */
1129 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1131 SVN_ERR(svn_prop_diffs(&propchanges, local_proptable,
1132 base_proptable, pool));
1133 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, pool));
1134 if (props->nelts > 0)
1136 /* We print a diff header for the case when we only have property
1138 if (! diff_header_printed)
1140 const char *orig_label, *new_label;
1142 SVN_ERR(generate_label(&orig_label, base_root, base_path,
1144 SVN_ERR(generate_label(&new_label, root, path, pool));
1146 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1147 "Index: %s\n", path));
1148 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1149 SVN_DIFF__EQUAL_STRING "\n"));
1152 SVN_ERR(svn_diff__unidiff_write_header(
1153 out_stream, encoding, orig_label, new_label, pool));
1155 SVN_ERR(display_prop_diffs(out_stream, encoding,
1156 props, base_proptable, path, pool));
1160 /* Return here if the node has no children. */
1162 return SVN_NO_ERROR;
1164 /* Recursively handle the node's children. */
1165 iterpool = svn_pool_create(pool);
1166 for (node = node->child; node; node = node->sibling)
1168 svn_pool_clear(iterpool);
1170 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node,
1171 svn_dirent_join(path, node->name, iterpool),
1172 svn_dirent_join(base_path, node->name, iterpool),
1175 svn_pool_destroy(iterpool);
1177 return SVN_NO_ERROR;
1181 /* Print a repository directory, maybe recursively, possibly showing
1182 the node revision ids, and optionally using full paths.
1184 ROOT is the revision or transaction root used to build that tree.
1185 PATH and ID are the current path and node revision id being
1186 printed, and INDENTATION the number of spaces to prepent to that
1187 path's printed output. ID may be NULL if SHOW_IDS is FALSE (in
1188 which case, ids won't be printed at all). If RECURSE is TRUE,
1189 then print the tree recursively; otherwise, we'll stop after the
1190 first level (and use INDENTATION to keep track of how deep we are).
1192 Use POOL for all allocations. */
1193 static svn_error_t *
1194 print_tree(svn_fs_root_t *root,
1195 const char *path /* UTF-8! */,
1196 const svn_fs_id_t *id,
1197 svn_boolean_t is_dir,
1199 svn_boolean_t show_ids,
1200 svn_boolean_t full_paths,
1201 svn_boolean_t recurse,
1204 apr_pool_t *subpool;
1205 apr_hash_t *entries;
1208 SVN_ERR(check_cancel(NULL));
1210 /* Print the indentation. */
1214 for (i = 0; i < indentation; i++)
1215 SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
1218 /* ### The path format is inconsistent.. needs fix */
1221 else if (*path == '/')
1222 name = svn_fspath__basename(path, pool);
1224 name = svn_relpath_basename(path, NULL);
1226 if (svn_path_is_empty(name))
1227 name = "/"; /* basename of '/' is "" */
1229 /* Print the node. */
1230 SVN_ERR(svn_cmdline_printf(pool, "%s%s",
1232 is_dir && strcmp(name, "/") ? "/" : ""));
1236 svn_string_t *unparsed_id = NULL;
1238 unparsed_id = svn_fs_unparse_id(id, pool);
1239 SVN_ERR(svn_cmdline_printf(pool, " <%s>",
1244 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1246 /* Return here if PATH is not a directory. */
1248 return SVN_NO_ERROR;
1250 /* Recursively handle the node's children. */
1251 if (recurse || (indentation == 0))
1253 apr_array_header_t *sorted_entries;
1256 SVN_ERR(svn_fs_dir_entries(&entries, root, path, pool));
1257 subpool = svn_pool_create(pool);
1258 sorted_entries = svn_sort__hash(entries,
1259 svn_sort_compare_items_lexically, pool);
1260 for (i = 0; i < sorted_entries->nelts; i++)
1262 svn_sort__item_t item = APR_ARRAY_IDX(sorted_entries, i,
1264 svn_fs_dirent_t *entry = item.value;
1266 svn_pool_clear(subpool);
1267 SVN_ERR(print_tree(root,
1269 ? svn_fspath__join(path, entry->name, pool)
1270 : svn_relpath_join(path, entry->name, pool),
1271 entry->id, (entry->kind == svn_node_dir),
1272 indentation + 1, show_ids, full_paths,
1275 svn_pool_destroy(subpool);
1278 return SVN_NO_ERROR;
1282 /* Set *BASE_REV to the revision on which the target root specified in
1283 C is based, or to SVN_INVALID_REVNUM when C represents "revision
1284 0" (because that revision isn't based on another revision). */
1285 static svn_error_t *
1286 get_base_rev(svn_revnum_t *base_rev, svnlook_ctxt_t *c, apr_pool_t *pool)
1290 *base_rev = c->rev_id - 1;
1294 *base_rev = svn_fs_txn_base_revision(c->txn);
1296 if (! SVN_IS_VALID_REVNUM(*base_rev))
1297 return svn_error_createf
1298 (SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1299 _("Transaction '%s' is not based on a revision; how odd"),
1302 return SVN_NO_ERROR;
1307 /*** Subcommand handlers. ***/
1309 /* Print the revision's log message to stdout, followed by a newline. */
1310 static svn_error_t *
1311 do_log(svnlook_ctxt_t *c, svn_boolean_t print_size, apr_pool_t *pool)
1313 svn_string_t *prop_value;
1314 const char *prop_value_eol, *prop_value_native;
1315 svn_stream_t *stream;
1319 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_LOG, pool));
1320 if (! (prop_value && prop_value->data))
1322 SVN_ERR(svn_cmdline_printf(pool, "%s\n", print_size ? "0" : ""));
1323 return SVN_NO_ERROR;
1326 /* We immitate what svn_cmdline_printf does here, since we need the byte
1327 size of what we are going to print. */
1329 SVN_ERR(svn_subst_translate_cstring2(prop_value->data, &prop_value_eol,
1331 NULL, FALSE, pool));
1333 err = svn_cmdline_cstring_from_utf8(&prop_value_native, prop_value_eol,
1337 svn_error_clear(err);
1338 prop_value_native = svn_cmdline_cstring_from_utf8_fuzzy(prop_value_eol,
1342 len = strlen(prop_value_native);
1345 SVN_ERR(svn_cmdline_printf(pool, "%" APR_SIZE_T_FMT "\n", len));
1347 /* Use a stream to bypass all stdio translations. */
1348 SVN_ERR(svn_cmdline_fflush(stdout));
1349 SVN_ERR(svn_stream_for_stdout(&stream, pool));
1350 SVN_ERR(svn_stream_write(stream, prop_value_native, &len));
1351 SVN_ERR(svn_stream_close(stream));
1353 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1355 return SVN_NO_ERROR;
1359 /* Print the timestamp of the commit (in the revision case) or the
1360 empty string (in the transaction case) to stdout, followed by a
1362 static svn_error_t *
1363 do_date(svnlook_ctxt_t *c, apr_pool_t *pool)
1365 svn_string_t *prop_value;
1367 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_DATE, pool));
1368 if (prop_value && prop_value->data)
1370 /* Convert the date for humans. */
1372 const char *time_utf8;
1374 SVN_ERR(svn_time_from_cstring(&aprtime, prop_value->data, pool));
1376 time_utf8 = svn_time_to_human_cstring(aprtime, pool);
1378 SVN_ERR(svn_cmdline_printf(pool, "%s", time_utf8));
1381 SVN_ERR(svn_cmdline_printf(pool, "\n"));
1382 return SVN_NO_ERROR;
1386 /* Print the author of the commit to stdout, followed by a newline. */
1387 static svn_error_t *
1388 do_author(svnlook_ctxt_t *c, apr_pool_t *pool)
1390 svn_string_t *prop_value;
1392 SVN_ERR(get_property(&prop_value, c,
1393 SVN_PROP_REVISION_AUTHOR, pool));
1394 if (prop_value && prop_value->data)
1395 SVN_ERR(svn_cmdline_printf(pool, "%s", prop_value->data));
1397 SVN_ERR(svn_cmdline_printf(pool, "\n"));
1398 return SVN_NO_ERROR;
1402 /* Print a list of all directories in which files, or directory
1403 properties, have been modified. */
1404 static svn_error_t *
1405 do_dirs_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1407 svn_fs_root_t *root;
1408 svn_revnum_t base_rev_id;
1409 svn_repos_node_t *tree;
1411 SVN_ERR(get_root(&root, c, pool));
1412 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1413 if (base_rev_id == SVN_INVALID_REVNUM)
1414 return SVN_NO_ERROR;
1416 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1418 SVN_ERR(print_dirs_changed_tree(tree, "", pool));
1420 return SVN_NO_ERROR;
1424 /* Set *KIND to PATH's kind, if PATH exists.
1426 * If PATH does not exist, then error; the text of the error depends
1427 * on whether PATH looks like a URL or not.
1429 static svn_error_t *
1430 verify_path(svn_node_kind_t *kind,
1431 svn_fs_root_t *root,
1435 SVN_ERR(svn_fs_check_path(kind, root, path, pool));
1437 if (*kind == svn_node_none)
1439 if (svn_path_is_url(path)) /* check for a common mistake. */
1440 return svn_error_createf
1441 (SVN_ERR_FS_NOT_FOUND, NULL,
1442 _("'%s' is a URL, probably should be a path"), path);
1444 return svn_error_createf
1445 (SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' does not exist"), path);
1448 return SVN_NO_ERROR;
1452 /* Print the size (in bytes) of a file. */
1453 static svn_error_t *
1454 do_filesize(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
1456 svn_fs_root_t *root;
1457 svn_node_kind_t kind;
1458 svn_filesize_t length;
1460 SVN_ERR(get_root(&root, c, pool));
1461 SVN_ERR(verify_path(&kind, root, path, pool));
1463 if (kind != svn_node_file)
1464 return svn_error_createf
1465 (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
1469 SVN_ERR(svn_fs_file_length(&length, root, path, pool));
1470 return svn_cmdline_printf(pool, "%" SVN_FILESIZE_T_FMT "\n", length);
1473 /* Print the contents of the file at PATH in the repository.
1474 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
1475 SVN_ERR_FS_NOT_FILE if PATH exists but is not a file. */
1476 static svn_error_t *
1477 do_cat(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
1479 svn_fs_root_t *root;
1480 svn_node_kind_t kind;
1481 svn_stream_t *fstream, *stdout_stream;
1483 SVN_ERR(get_root(&root, c, pool));
1484 SVN_ERR(verify_path(&kind, root, path, pool));
1486 if (kind != svn_node_file)
1487 return svn_error_createf
1488 (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
1492 SVN_ERR(svn_fs_file_contents(&fstream, root, path, pool));
1493 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1495 return svn_stream_copy3(fstream, svn_stream_disown(stdout_stream, pool),
1496 check_cancel, NULL, pool);
1500 /* Print a list of all paths modified in a format compatible with `svn
1502 static svn_error_t *
1503 do_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1505 svn_fs_root_t *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));
1516 SVN_ERR(print_changed_tree(tree, "", c->copy_info, pool));
1518 return SVN_NO_ERROR;
1522 /* Print some diff-y stuff in a TBD way. :-) */
1523 static svn_error_t *
1524 do_diff(svnlook_ctxt_t *c, apr_pool_t *pool)
1526 svn_fs_root_t *root, *base_root;
1527 svn_revnum_t base_rev_id;
1528 svn_repos_node_t *tree;
1530 SVN_ERR(get_root(&root, c, pool));
1531 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1532 if (base_rev_id == SVN_INVALID_REVNUM)
1533 return SVN_NO_ERROR;
1535 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1538 svn_stream_t *out_stream;
1539 const char *encoding = svn_cmdline_output_encoding(pool);
1541 SVN_ERR(svn_fs_revision_root(&base_root, c->fs, base_rev_id, pool));
1543 /* This fflush() might seem odd, but it was added to deal
1544 with this bug report:
1546 http://subversion.tigris.org/servlets/ReadMsg?\
1547 list=dev&msgNo=140782
1549 From: "Steve Hay" <SteveHay{_AT_}planit.com>
1550 To: <dev@subversion.tigris.org>
1551 Subject: svnlook diff output in wrong order when redirected
1552 Date: Fri, 4 Jul 2008 16:34:15 +0100
1553 Message-ID: <1B32FF956ABF414C9BCE5E487A1497E702014F62@\
1554 ukmail02.planit.group>
1556 Adding the fflush() fixed the bug (not everyone could
1557 reproduce it, but those who could confirmed the fix).
1558 Later in the thread, Daniel Shahaf speculated as to
1561 "Because svn_cmdline_printf() uses the standard
1562 'FILE *stdout' to write to stdout, while
1563 svn_stream_for_stdout() uses (through
1564 apr_file_open_stdout()) Windows API's to get a
1565 handle for stdout?" */
1566 SVN_ERR(svn_cmdline_fflush(stdout));
1567 SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1569 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, tree,
1572 return SVN_NO_ERROR;
1577 /* Callback baton for print_history() (and do_history()). */
1578 struct print_history_baton
1581 svn_boolean_t show_ids; /* whether to show node IDs */
1582 apr_size_t limit; /* max number of history items */
1583 apr_size_t count; /* number of history items processed */
1586 /* Implements svn_repos_history_func_t interface. Print the history
1587 that's reported through this callback, possibly finding and
1588 displaying node-rev-ids. */
1589 static svn_error_t *
1590 print_history(void *baton,
1592 svn_revnum_t revision,
1595 struct print_history_baton *phb = baton;
1597 SVN_ERR(check_cancel(NULL));
1601 const svn_fs_id_t *node_id;
1602 svn_fs_root_t *rev_root;
1603 svn_string_t *id_string;
1605 SVN_ERR(svn_fs_revision_root(&rev_root, phb->fs, revision, pool));
1606 SVN_ERR(svn_fs_node_id(&node_id, rev_root, path, pool));
1607 id_string = svn_fs_unparse_id(node_id, pool);
1608 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s <%s>\n",
1609 revision, path, id_string->data));
1613 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s\n", revision, path));
1619 if (phb->count >= phb->limit)
1620 /* Not L10N'd, since this error is suppressed by the caller. */
1621 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL,
1622 _("History item limit reached"));
1625 return SVN_NO_ERROR;
1629 /* Print a tabular display of history location points for PATH in
1630 revision C->rev_id. Optionally, SHOW_IDS. Use POOL for
1632 static svn_error_t *
1633 do_history(svnlook_ctxt_t *c,
1637 struct print_history_baton args;
1641 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH <ID>\n"
1642 "-------- ---------\n")));
1646 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH\n"
1647 "-------- ----\n")));
1650 /* Call our history crawler. We want the whole lifetime of the path
1651 (prior to the user-supplied revision, of course), across all
1654 args.show_ids = c->show_ids;
1655 args.limit = c->limit;
1657 SVN_ERR(svn_repos_history2(c->fs, path, print_history, &args,
1658 NULL, NULL, 0, c->rev_id, TRUE, pool));
1659 return SVN_NO_ERROR;
1663 /* Print the value of property PROPNAME on PATH in the repository.
1665 If VERBOSE, print their values too. If SHOW_INHERITED_PROPS, print
1666 PATH's inherited props too.
1668 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. If
1669 SHOW_INHERITED_PROPS is FALSE,then error with SVN_ERR_PROPERTY_NOT_FOUND
1670 if there is no such property on PATH. If SHOW_INHERITED_PROPS is TRUE,
1671 then error with SVN_ERR_PROPERTY_NOT_FOUND only if there is no such
1672 property on PATH nor inherited by path.
1674 If PATH is NULL, operate on a revision property. */
1675 static svn_error_t *
1676 do_pget(svnlook_ctxt_t *c,
1677 const char *propname,
1679 svn_boolean_t verbose,
1680 svn_boolean_t show_inherited_props,
1683 svn_fs_root_t *root;
1685 svn_node_kind_t kind;
1686 svn_stream_t *stdout_stream;
1688 apr_array_header_t *inherited_props = NULL;
1690 SVN_ERR(get_root(&root, c, pool));
1693 path = svn_fspath__canonicalize(path, pool);
1694 SVN_ERR(verify_path(&kind, root, path, pool));
1695 SVN_ERR(svn_fs_node_prop(&prop, root, path, propname, pool));
1697 if (show_inherited_props)
1699 SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
1700 path, propname, NULL,
1704 else /* --revprop */
1706 SVN_ERR(get_property(&prop, c, propname, pool));
1709 /* Did we find nothing? */
1711 && (!show_inherited_props || inherited_props->nelts == 0))
1713 const char *err_msg;
1716 /* We're operating on a revprop (e.g. c->is_revision). */
1717 if (SVN_IS_VALID_REVNUM(c->rev_id))
1718 err_msg = apr_psprintf(pool,
1719 _("Property '%s' not found on revision %ld"),
1720 propname, c->rev_id);
1722 err_msg = apr_psprintf(pool,
1723 _("Property '%s' not found on transaction %s"),
1724 propname, c->txn_name);
1728 if (SVN_IS_VALID_REVNUM(c->rev_id))
1730 if (show_inherited_props)
1731 err_msg = apr_psprintf(pool,
1732 _("Property '%s' not found on path '%s' "
1733 "or inherited from a parent "
1735 propname, path, c->rev_id);
1737 err_msg = apr_psprintf(pool,
1738 _("Property '%s' not found on path '%s' "
1740 propname, path, c->rev_id);
1744 if (show_inherited_props)
1745 err_msg = apr_psprintf(pool,
1746 _("Property '%s' not found on path '%s' "
1747 "or inherited from a parent "
1748 "in transaction %s"),
1749 propname, path, c->txn_name);
1751 err_msg = apr_psprintf(pool,
1752 _("Property '%s' not found on path '%s' "
1753 "in transaction %s"),
1754 propname, path, c->txn_name);
1757 return svn_error_create(SVN_ERR_PROPERTY_NOT_FOUND, NULL, err_msg);
1760 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1762 if (verbose || show_inherited_props)
1764 if (inherited_props)
1768 for (i = 0; i < inherited_props->nelts; i++)
1770 svn_prop_inherited_item_t *elt =
1771 APR_ARRAY_IDX(inherited_props, i,
1772 svn_prop_inherited_item_t *);
1776 SVN_ERR(svn_stream_printf(stdout_stream, pool,
1777 _("Inherited properties on '%s',\nfrom '%s':\n"),
1778 path, svn_fspath__canonicalize(elt->path_or_url,
1780 SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream,
1786 svn_string_t *propval =
1787 apr_hash_this_val(apr_hash_first(pool, elt->prop_hash));
1789 SVN_ERR(svn_stream_printf(
1790 stdout_stream, pool, "%s - ",
1791 svn_fspath__canonicalize(elt->path_or_url, pool)));
1793 SVN_ERR(svn_stream_write(stdout_stream, propval->data, &len));
1794 /* If we have more than one property to write, then add a newline*/
1795 if (inherited_props->nelts > 1 || prop)
1797 len = strlen(APR_EOL_STR);
1798 SVN_ERR(svn_stream_write(stdout_stream, APR_EOL_STR, &len));
1808 apr_hash_t *hash = apr_hash_make(pool);
1810 svn_hash_sets(hash, propname, prop);
1811 SVN_ERR(svn_stream_printf(stdout_stream, pool,
1812 _("Properties on '%s':\n"), path));
1813 SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, hash,
1818 SVN_ERR(svn_stream_printf(stdout_stream, pool, "%s - ", path));
1820 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1824 else /* Raw single prop output, i.e. non-verbose output with no
1827 /* Unlike the command line client, we don't translate the property
1828 value or print a trailing newline here. We just output the raw
1829 bytes of whatever's in the repository, as svnlook is more likely
1830 to be used for automated inspections. */
1832 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1835 return SVN_NO_ERROR;
1839 /* Print the property names of all properties on PATH in the repository.
1841 If VERBOSE, print their values too. If XML, print as XML rather than as
1842 plain text. If SHOW_INHERITED_PROPS, print PATH's inherited props too.
1844 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist.
1846 If PATH is NULL, operate on a revision properties. */
1847 static svn_error_t *
1848 do_plist(svnlook_ctxt_t *c,
1850 svn_boolean_t verbose,
1852 svn_boolean_t show_inherited_props,
1855 svn_fs_root_t *root;
1857 apr_hash_index_t *hi;
1858 svn_node_kind_t kind;
1859 svn_stringbuf_t *sb = NULL;
1860 svn_boolean_t revprop = FALSE;
1861 apr_array_header_t *inherited_props = NULL;
1865 /* PATH might be the root of the repsository and we accept both
1866 "" and "/". But to avoid the somewhat cryptic output like this:
1868 >svnlook pl repos-path ""
1873 We canonicalize PATH so that is has a leading slash. */
1874 path = svn_fspath__canonicalize(path, pool);
1876 SVN_ERR(get_root(&root, c, pool));
1877 SVN_ERR(verify_path(&kind, root, path, pool));
1878 SVN_ERR(svn_fs_node_proplist(&props, root, path, pool));
1880 if (show_inherited_props)
1881 SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
1882 path, NULL, NULL, NULL,
1885 else if (c->is_revision)
1887 SVN_ERR(svn_fs_revision_proplist2(&props, c->fs, c->rev_id, TRUE,
1893 SVN_ERR(svn_fs_txn_proplist(&props, c->txn, pool));
1899 /* <?xml version="1.0" encoding="UTF-8"?> */
1900 svn_xml_make_header2(&sb, "UTF-8", pool);
1902 /* "<properties>" */
1903 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "properties",
1907 if (inherited_props)
1911 for (i = 0; i < inherited_props->nelts; i++)
1913 svn_prop_inherited_item_t *elt =
1914 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1916 /* Canonicalize the inherited parent paths for consistency
1920 svn_xml_make_open_tag(
1921 &sb, pool, svn_xml_normal, "target", "path",
1922 svn_fspath__canonicalize(elt->path_or_url, pool),
1924 SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, elt->prop_hash,
1927 svn_xml_make_close_tag(&sb, pool, "target");
1931 SVN_ERR(svn_cmdline_printf(
1932 pool, _("Inherited properties on '%s',\nfrom '%s':\n"),
1933 path, svn_fspath__canonicalize(elt->path_or_url, pool)));
1934 SVN_ERR(svn_cmdline__print_prop_hash(NULL, elt->prop_hash,
1944 /* "<revprops ...>" */
1947 char *revstr = apr_psprintf(pool, "%ld", c->rev_id);
1949 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
1950 "rev", revstr, SVN_VA_NULL);
1954 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
1955 "txn", c->txn_name, SVN_VA_NULL);
1960 /* "<target ...>" */
1961 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
1962 "path", path, SVN_VA_NULL);
1966 if (!xml && path /* Not a --revprop */)
1967 SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), path));
1969 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
1971 const char *pname = apr_hash_this_key(hi);
1972 svn_string_t *propval = apr_hash_this_val(hi);
1974 SVN_ERR(check_cancel(NULL));
1976 /* Since we're already adding a trailing newline (and possible a
1977 colon and some spaces) anyway, just mimic the output of the
1978 command line client proplist. Compare to 'svnlook propget',
1979 which sends the raw bytes to stdout, untranslated. */
1980 /* We leave printf calls here, since we don't always know the encoding
1981 of the prop value. */
1982 if (svn_prop_needs_translation(pname))
1983 SVN_ERR(svn_subst_detranslate_string(&propval, propval, TRUE, pool));
1988 svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, pool);
1991 const char *pname_stdout;
1992 const char *indented_newval;
1994 SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname,
1996 printf(" %s\n", pname_stdout);
1997 /* Add an extra newline to the value before indenting, so that
1998 every line of output has the indentation whether the value
1999 already ended in a newline or not. */
2001 svn_cmdline__indent_string(apr_psprintf(pool, "%s\n",
2004 printf("%s", indented_newval);
2008 svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "property",
2009 "name", pname, SVN_VA_NULL);
2011 printf(" %s\n", pname);
2019 svn_xml_make_close_tag(&sb, pool, "revprops");
2024 svn_xml_make_close_tag(&sb, pool, "target");
2027 /* "</properties>" */
2028 svn_xml_make_close_tag(&sb, pool, "properties");
2031 if (fputs(sb->data, stdout) == EOF)
2033 if (apr_get_os_error()) /* is errno on POSIX */
2034 return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
2036 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
2040 return SVN_NO_ERROR;
2044 static svn_error_t *
2045 do_tree(svnlook_ctxt_t *c,
2047 svn_boolean_t show_ids,
2048 svn_boolean_t full_paths,
2049 svn_boolean_t recurse,
2052 svn_fs_root_t *root;
2053 const svn_fs_id_t *id;
2054 svn_boolean_t is_dir;
2056 SVN_ERR(get_root(&root, c, pool));
2057 SVN_ERR(svn_fs_node_id(&id, root, path, pool));
2058 SVN_ERR(svn_fs_is_dir(&is_dir, root, path, pool));
2059 SVN_ERR(print_tree(root, path, id, is_dir, 0, show_ids, full_paths,
2061 return SVN_NO_ERROR;
2065 /* Custom filesystem warning function. */
2067 warning_func(void *baton,
2072 svn_handle_error2(err, stderr, FALSE, "svnlook: ");
2076 /* Return an error if the number of arguments (excluding the repository
2077 * argument) is not NUM_ARGS. NUM_ARGS must be 0 or 1. The arguments
2078 * are assumed to be found in OPT_STATE->arg1 and OPT_STATE->arg2. */
2079 static svn_error_t *
2080 check_number_of_args(struct svnlook_opt_state *opt_state,
2083 if ((num_args == 0 && opt_state->arg1 != NULL)
2084 || (num_args == 1 && opt_state->arg2 != NULL))
2085 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2086 _("Too many arguments given"));
2087 if ((num_args == 1 && opt_state->arg1 == NULL))
2088 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2089 _("Missing repository path argument"));
2090 return SVN_NO_ERROR;
2094 /* Factory function for the context baton. */
2095 static svn_error_t *
2096 get_ctxt_baton(svnlook_ctxt_t **baton_p,
2097 struct svnlook_opt_state *opt_state,
2100 svnlook_ctxt_t *baton = apr_pcalloc(pool, sizeof(*baton));
2102 SVN_ERR(svn_repos_open3(&(baton->repos), opt_state->repos_path, NULL,
2104 baton->fs = svn_repos_fs(baton->repos);
2105 svn_fs_set_warning_func(baton->fs, warning_func, NULL);
2106 baton->show_ids = opt_state->show_ids;
2107 baton->limit = opt_state->limit;
2108 baton->no_diff_deleted = opt_state->no_diff_deleted;
2109 baton->no_diff_added = opt_state->no_diff_added;
2110 baton->diff_copy_from = opt_state->diff_copy_from;
2111 baton->full_paths = opt_state->full_paths;
2112 baton->copy_info = opt_state->copy_info;
2113 baton->is_revision = opt_state->txn == NULL;
2114 baton->rev_id = opt_state->rev;
2115 baton->txn_name = apr_pstrdup(pool, opt_state->txn);
2116 baton->diff_options = svn_cstring_split(opt_state->extensions
2117 ? opt_state->extensions : "",
2118 " \t\n\r", TRUE, pool);
2119 baton->ignore_properties = opt_state->ignore_properties;
2120 baton->properties_only = opt_state->properties_only;
2121 baton->diff_cmd = opt_state->diff_cmd;
2123 if (baton->txn_name)
2124 SVN_ERR(svn_fs_open_txn(&(baton->txn), baton->fs,
2125 baton->txn_name, pool));
2126 else if (baton->rev_id == SVN_INVALID_REVNUM)
2127 SVN_ERR(svn_fs_youngest_rev(&(baton->rev_id), baton->fs, pool));
2130 return SVN_NO_ERROR;
2135 /*** Subcommands. ***/
2137 /* This implements `svn_opt_subcommand_t'. */
2138 static svn_error_t *
2139 subcommand_author(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2141 struct svnlook_opt_state *opt_state = baton;
2144 SVN_ERR(check_number_of_args(opt_state, 0));
2146 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2147 SVN_ERR(do_author(c, pool));
2148 return SVN_NO_ERROR;
2151 /* This implements `svn_opt_subcommand_t'. */
2152 static svn_error_t *
2153 subcommand_cat(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2155 struct svnlook_opt_state *opt_state = baton;
2158 SVN_ERR(check_number_of_args(opt_state, 1));
2160 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2161 SVN_ERR(do_cat(c, opt_state->arg1, pool));
2162 return SVN_NO_ERROR;
2165 /* This implements `svn_opt_subcommand_t'. */
2166 static svn_error_t *
2167 subcommand_changed(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2169 struct svnlook_opt_state *opt_state = baton;
2172 SVN_ERR(check_number_of_args(opt_state, 0));
2174 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2175 SVN_ERR(do_changed(c, pool));
2176 return SVN_NO_ERROR;
2179 /* This implements `svn_opt_subcommand_t'. */
2180 static svn_error_t *
2181 subcommand_date(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2183 struct svnlook_opt_state *opt_state = baton;
2186 SVN_ERR(check_number_of_args(opt_state, 0));
2188 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2189 SVN_ERR(do_date(c, pool));
2190 return SVN_NO_ERROR;
2193 /* This implements `svn_opt_subcommand_t'. */
2194 static svn_error_t *
2195 subcommand_diff(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2197 struct svnlook_opt_state *opt_state = baton;
2200 SVN_ERR(check_number_of_args(opt_state, 0));
2202 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2203 SVN_ERR(do_diff(c, pool));
2204 return SVN_NO_ERROR;
2207 /* This implements `svn_opt_subcommand_t'. */
2208 static svn_error_t *
2209 subcommand_dirschanged(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2211 struct svnlook_opt_state *opt_state = baton;
2214 SVN_ERR(check_number_of_args(opt_state, 0));
2216 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2217 SVN_ERR(do_dirs_changed(c, pool));
2218 return SVN_NO_ERROR;
2221 /* This implements `svn_opt_subcommand_t'. */
2222 static svn_error_t *
2223 subcommand_filesize(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2225 struct svnlook_opt_state *opt_state = baton;
2228 SVN_ERR(check_number_of_args(opt_state, 1));
2230 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2231 SVN_ERR(do_filesize(c, opt_state->arg1, pool));
2232 return SVN_NO_ERROR;
2235 /* This implements `svn_opt_subcommand_t'. */
2236 static svn_error_t *
2237 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2239 struct svnlook_opt_state *opt_state = baton;
2240 const char *header =
2241 _("general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
2242 "Subversion repository inspection tool.\n"
2243 "Type 'svnlook help <subcommand>' for help on a specific subcommand.\n"
2244 "Type 'svnlook --version' to see the program version and FS modules.\n"
2245 "Note: any subcommand which takes the '--revision' and '--transaction'\n"
2246 " options will, if invoked without one of those options, act on\n"
2247 " the repository's youngest revision.\n"
2249 "Available subcommands:\n");
2251 const char *fs_desc_start
2252 = _("The following repository back-end (FS) modules are available:\n\n");
2254 svn_stringbuf_t *version_footer;
2256 version_footer = svn_stringbuf_create(fs_desc_start, pool);
2257 SVN_ERR(svn_fs_print_modules(version_footer, pool));
2259 SVN_ERR(svn_opt_print_help5(os, "svnlook",
2260 opt_state ? opt_state->version : FALSE,
2261 opt_state ? opt_state->quiet : FALSE,
2262 opt_state ? opt_state->verbose : FALSE,
2263 version_footer->data,
2264 header, cmd_table, options_table, NULL,
2267 return SVN_NO_ERROR;
2270 /* This implements `svn_opt_subcommand_t'. */
2271 static svn_error_t *
2272 subcommand_history(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2274 struct svnlook_opt_state *opt_state = baton;
2276 const char *path = (opt_state->arg1 ? opt_state->arg1 : "/");
2278 if (opt_state->arg2 != NULL)
2279 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2280 _("Too many arguments given"));
2282 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2283 SVN_ERR(do_history(c, path, pool));
2284 return SVN_NO_ERROR;
2288 /* This implements `svn_opt_subcommand_t'. */
2289 static svn_error_t *
2290 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2292 struct svnlook_opt_state *opt_state = baton;
2296 SVN_ERR(check_number_of_args(opt_state, 1));
2298 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2300 SVN_ERR(svn_fs_get_lock(&lock, c->fs, opt_state->arg1, pool));
2304 const char *cr_date, *exp_date = "";
2305 int comment_lines = 0;
2307 cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
2309 if (lock->expiration_date)
2310 exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
2313 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
2315 SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
2316 SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
2317 SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
2318 SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
2319 SVN_ERR(svn_cmdline_printf(pool,
2320 Q_("Comment (%i line):\n%s\n",
2321 "Comment (%i lines):\n%s\n",
2324 lock->comment ? lock->comment : ""));
2327 return SVN_NO_ERROR;
2331 /* This implements `svn_opt_subcommand_t'. */
2332 static svn_error_t *
2333 subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2335 struct svnlook_opt_state *opt_state = baton;
2338 SVN_ERR(check_number_of_args(opt_state, 0));
2340 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2341 SVN_ERR(do_author(c, pool));
2342 SVN_ERR(do_date(c, pool));
2343 SVN_ERR(do_log(c, TRUE, pool));
2344 return SVN_NO_ERROR;
2347 /* This implements `svn_opt_subcommand_t'. */
2348 static svn_error_t *
2349 subcommand_log(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2351 struct svnlook_opt_state *opt_state = baton;
2354 SVN_ERR(check_number_of_args(opt_state, 0));
2356 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2357 SVN_ERR(do_log(c, FALSE, pool));
2358 return SVN_NO_ERROR;
2361 /* This implements `svn_opt_subcommand_t'. */
2362 static svn_error_t *
2363 subcommand_pget(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2365 struct svnlook_opt_state *opt_state = baton;
2368 if (opt_state->arg1 == NULL)
2370 return svn_error_createf
2371 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2372 opt_state->revprop ? _("Missing propname argument") :
2373 _("Missing propname and repository path arguments"));
2375 else if (!opt_state->revprop && opt_state->arg2 == NULL)
2377 return svn_error_create
2378 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2379 _("Missing propname or repository path argument"));
2381 if ((opt_state->revprop && opt_state->arg2 != NULL)
2382 || os->ind < os->argc)
2383 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2384 _("Too many arguments given"));
2386 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2387 SVN_ERR(do_pget(c, opt_state->arg1,
2388 opt_state->revprop ? NULL : opt_state->arg2,
2389 opt_state->verbose, opt_state->show_inherited_props,
2391 return SVN_NO_ERROR;
2394 /* This implements `svn_opt_subcommand_t'. */
2395 static svn_error_t *
2396 subcommand_plist(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2398 struct svnlook_opt_state *opt_state = baton;
2401 SVN_ERR(check_number_of_args(opt_state, opt_state->revprop ? 0 : 1));
2403 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2404 SVN_ERR(do_plist(c, opt_state->revprop ? NULL : opt_state->arg1,
2405 opt_state->verbose, opt_state->xml,
2406 opt_state->show_inherited_props, pool));
2407 return SVN_NO_ERROR;
2410 /* This implements `svn_opt_subcommand_t'. */
2411 static svn_error_t *
2412 subcommand_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2414 struct svnlook_opt_state *opt_state = baton;
2417 if (opt_state->arg2 != NULL)
2418 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2419 _("Too many arguments given"));
2421 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2422 SVN_ERR(do_tree(c, opt_state->arg1 ? opt_state->arg1 : "",
2423 opt_state->show_ids, opt_state->full_paths,
2424 ! opt_state->non_recursive, pool));
2425 return SVN_NO_ERROR;
2428 /* This implements `svn_opt_subcommand_t'. */
2429 static svn_error_t *
2430 subcommand_youngest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2432 struct svnlook_opt_state *opt_state = baton;
2435 SVN_ERR(check_number_of_args(opt_state, 0));
2437 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2438 SVN_ERR(svn_cmdline_printf(pool, "%ld%s", c->rev_id,
2439 opt_state->no_newline ? "" : "\n"));
2440 return SVN_NO_ERROR;
2443 /* This implements `svn_opt_subcommand_t'. */
2444 static svn_error_t *
2445 subcommand_uuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2447 struct svnlook_opt_state *opt_state = baton;
2451 SVN_ERR(check_number_of_args(opt_state, 0));
2453 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2454 SVN_ERR(svn_fs_get_uuid(c->fs, &uuid, pool));
2455 SVN_ERR(svn_cmdline_printf(pool, "%s\n", uuid));
2456 return SVN_NO_ERROR;
2464 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
2465 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
2466 * return SVN_NO_ERROR.
2468 static svn_error_t *
2469 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
2472 apr_status_t apr_err;
2474 const svn_opt_subcommand_desc3_t *subcommand = NULL;
2475 struct svnlook_opt_state opt_state;
2478 apr_array_header_t *received_opts;
2481 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
2483 /* Check library versions */
2484 SVN_ERR(check_lib_versions());
2486 /* Initialize the FS library. */
2487 SVN_ERR(svn_fs_initialize(pool));
2491 SVN_ERR(subcommand_help(NULL, NULL, pool));
2492 *exit_code = EXIT_FAILURE;
2493 return SVN_NO_ERROR;
2496 /* Initialize opt_state. */
2497 memset(&opt_state, 0, sizeof(opt_state));
2498 opt_state.rev = SVN_INVALID_REVNUM;
2499 opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
2501 /* Parse options. */
2502 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
2507 const char *opt_arg;
2509 /* Parse the next option. */
2510 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2511 if (APR_STATUS_IS_EOF(apr_err))
2515 SVN_ERR(subcommand_help(NULL, NULL, pool));
2516 *exit_code = EXIT_FAILURE;
2517 return SVN_NO_ERROR;
2520 /* Stash the option code in an array before parsing it. */
2521 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2527 char *digits_end = NULL;
2528 opt_state.rev = strtol(opt_arg, &digits_end, 10);
2529 if ((! SVN_IS_VALID_REVNUM(opt_state.rev))
2532 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2533 _("Invalid revision number supplied"));
2538 opt_state.txn = opt_arg;
2543 apr_uint64_t sz_val;
2544 SVN_ERR(svn_cstring_atoui64(&sz_val, opt_arg));
2546 opt_state.memory_cache_size = 0x100000 * sz_val;
2551 opt_state.non_recursive = TRUE;
2555 opt_state.verbose = TRUE;
2560 opt_state.help = TRUE;
2564 opt_state.quiet = TRUE;
2567 case svnlook__revprop_opt:
2568 opt_state.revprop = TRUE;
2571 case svnlook__xml_opt:
2572 opt_state.xml = TRUE;
2575 case svnlook__version:
2576 opt_state.version = TRUE;
2579 case svnlook__show_ids:
2580 opt_state.show_ids = TRUE;
2586 opt_state.limit = strtol(opt_arg, &end, 10);
2587 if (end == opt_arg || *end != '\0')
2589 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2590 _("Non-numeric limit argument given"));
2592 if (opt_state.limit <= 0)
2594 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2595 _("Argument to --limit must be positive"));
2600 case svnlook__no_diff_deleted:
2601 opt_state.no_diff_deleted = TRUE;
2604 case svnlook__no_diff_added:
2605 opt_state.no_diff_added = TRUE;
2608 case svnlook__diff_copy_from:
2609 opt_state.diff_copy_from = TRUE;
2612 case svnlook__full_paths:
2613 opt_state.full_paths = TRUE;
2616 case svnlook__copy_info:
2617 opt_state.copy_info = TRUE;
2621 opt_state.extensions = opt_arg;
2624 case svnlook__ignore_properties:
2625 opt_state.ignore_properties = TRUE;
2628 case svnlook__properties_only:
2629 opt_state.properties_only = TRUE;
2632 case svnlook__diff_cmd:
2633 opt_state.diff_cmd = opt_arg;
2636 case svnlook__show_inherited_props:
2637 opt_state.show_inherited_props = TRUE;
2640 case svnlook__no_newline:
2641 opt_state.no_newline = TRUE;
2645 SVN_ERR(subcommand_help(NULL, NULL, pool));
2646 *exit_code = EXIT_FAILURE;
2647 return SVN_NO_ERROR;
2652 /* The --transaction and --revision options may not co-exist. */
2653 if ((opt_state.rev != SVN_INVALID_REVNUM) && opt_state.txn)
2654 return svn_error_create
2655 (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
2656 _("The '--transaction' (-t) and '--revision' (-r) arguments "
2657 "cannot co-exist"));
2659 /* The --show-inherited-props and --revprop options may not co-exist. */
2660 if (opt_state.show_inherited_props && opt_state.revprop)
2661 return svn_error_create
2662 (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
2663 _("Cannot use the '--show-inherited-props' option with the "
2664 "'--revprop' option"));
2666 /* If the user asked for help, then the rest of the arguments are
2667 the names of subcommands to get help on (if any), or else they're
2668 just typos/mistakes. Whatever the case, the subcommand to
2669 actually run is subcommand_help(). */
2671 subcommand = svn_opt_get_canonical_subcommand3(cmd_table, "help");
2673 /* If we're not running the `help' subcommand, then look for a
2674 subcommand in the first argument. */
2675 if (subcommand == NULL)
2677 if (os->ind >= os->argc)
2679 if (opt_state.version)
2681 /* Use the "help" subcommand to handle the "--version" option. */
2682 static const svn_opt_subcommand_desc3_t pseudo_cmd =
2683 { "--version", subcommand_help, {0}, {""},
2684 {svnlook__version, /* must accept its own option */
2688 subcommand = &pseudo_cmd;
2693 (svn_cmdline_fprintf(stderr, pool,
2694 _("Subcommand argument required\n")));
2695 SVN_ERR(subcommand_help(NULL, NULL, pool));
2696 *exit_code = EXIT_FAILURE;
2697 return SVN_NO_ERROR;
2702 const char *first_arg;
2704 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
2706 subcommand = svn_opt_get_canonical_subcommand3(cmd_table, first_arg);
2707 if (subcommand == NULL)
2710 svn_cmdline_fprintf(stderr, pool,
2711 _("Unknown subcommand: '%s'\n"),
2713 SVN_ERR(subcommand_help(NULL, NULL, pool));
2715 /* Be kind to people who try 'svnlook verify'. */
2716 if (strcmp(first_arg, "verify") == 0)
2719 svn_cmdline_fprintf(stderr, pool,
2720 _("Try 'svnadmin verify' instead.\n")));
2723 *exit_code = EXIT_FAILURE;
2724 return SVN_NO_ERROR;
2729 /* If there's a second argument, it's the repository. There may be
2730 more arguments following the repository; usually the next one is
2731 a path within the repository, or it's a propname and the one
2732 after that is the path. Since we don't know, we just call them
2733 arg1 and arg2, meaning the first and second arguments following
2735 if (subcommand->cmd_func != subcommand_help)
2737 const char *repos_path = NULL;
2738 const char *arg1 = NULL, *arg2 = NULL;
2740 /* Get the repository. */
2741 if (os->ind < os->argc)
2743 SVN_ERR(svn_utf_cstring_to_utf8(&repos_path,
2744 os->argv[os->ind++],
2746 repos_path = svn_dirent_internal_style(repos_path, pool);
2749 if (repos_path == NULL)
2752 (svn_cmdline_fprintf(stderr, pool,
2753 _("Repository argument required\n")));
2754 SVN_ERR(subcommand_help(NULL, NULL, pool));
2755 *exit_code = EXIT_FAILURE;
2756 return SVN_NO_ERROR;
2758 else if (svn_path_is_url(repos_path))
2761 (svn_cmdline_fprintf(stderr, pool,
2762 _("'%s' is a URL when it should be a path\n"),
2764 *exit_code = EXIT_FAILURE;
2765 return SVN_NO_ERROR;
2768 opt_state.repos_path = repos_path;
2770 /* Get next arg (arg1), if any. */
2771 if (os->ind < os->argc)
2773 SVN_ERR(svn_utf_cstring_to_utf8(&arg1, os->argv[os->ind++], pool));
2774 arg1 = svn_dirent_internal_style(arg1, pool);
2776 opt_state.arg1 = arg1;
2778 /* Get next arg (arg2), if any. */
2779 if (os->ind < os->argc)
2781 SVN_ERR(svn_utf_cstring_to_utf8(&arg2, os->argv[os->ind++], pool));
2782 arg2 = svn_dirent_internal_style(arg2, pool);
2784 opt_state.arg2 = arg2;
2787 /* Check that the subcommand wasn't passed any inappropriate options. */
2788 for (i = 0; i < received_opts->nelts; i++)
2790 opt_id = APR_ARRAY_IDX(received_opts, i, int);
2792 /* All commands implicitly accept --help, so just skip over this
2793 when we see it. Note that we don't want to include this option
2794 in their "accepted options" list because it would be awfully
2795 redundant to display it in every commands' help text. */
2796 if (opt_id == 'h' || opt_id == '?')
2799 if (! svn_opt_subcommand_takes_option4(subcommand, opt_id, NULL))
2802 const apr_getopt_option_t *badopt =
2803 svn_opt_get_option_from_code3(opt_id, options_table, subcommand,
2805 svn_opt_format_option(&optstr, badopt, FALSE, pool);
2806 if (subcommand->name[0] == '-')
2807 SVN_ERR(subcommand_help(NULL, NULL, pool));
2810 (svn_cmdline_fprintf
2812 _("Subcommand '%s' doesn't accept option '%s'\n"
2813 "Type 'svnlook help %s' for usage.\n"),
2814 subcommand->name, optstr, subcommand->name));
2815 *exit_code = EXIT_FAILURE;
2816 return SVN_NO_ERROR;
2820 check_cancel = svn_cmdline__setup_cancellation_handler();
2822 /* Configure FSFS caches for maximum efficiency with svnadmin.
2823 * Also, apply the respective command line parameters, if given. */
2825 svn_cache_config_t settings = *svn_cache_config_get();
2827 settings.cache_size = opt_state.memory_cache_size;
2828 settings.single_threaded = TRUE;
2830 svn_cache_config_set(&settings);
2833 /* Run the subcommand. */
2834 err = (*subcommand->cmd_func)(os, &opt_state, pool);
2837 /* For argument-related problems, suggest using the 'help'
2839 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2840 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2842 err = svn_error_quick_wrap(err,
2843 _("Try 'svnlook help' for more info"));
2848 return SVN_NO_ERROR;
2852 main(int argc, const char *argv[])
2855 int exit_code = EXIT_SUCCESS;
2858 /* Initialize the app. */
2859 if (svn_cmdline_init("svnlook", stderr) != EXIT_SUCCESS)
2860 return EXIT_FAILURE;
2862 /* Create our top-level pool. Use a separate mutexless allocator,
2863 * given this application is single threaded.
2865 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2867 err = sub_main(&exit_code, argc, argv, pool);
2869 /* Flush stdout and report if it fails. It would be flushed on exit anyway
2870 but this makes sure that output is not silently lost if it fails. */
2871 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
2875 exit_code = EXIT_FAILURE;
2876 svn_cmdline_handle_exit_error(err, NULL, "svnlook: ");
2879 svn_pool_destroy(pool);
2881 svn_cmdline__cancellation_exit();