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_desc2_t cmd_table[] =
217 {"author", subcommand_author, {0},
218 N_("usage: svnlook author REPOS_PATH\n\n"
219 "Print the author.\n"),
222 {"cat", subcommand_cat, {0},
223 N_("usage: svnlook cat REPOS_PATH FILE_PATH\n\n"
224 "Print the contents of a file. Leading '/' on FILE_PATH is optional.\n"),
227 {"changed", subcommand_changed, {0},
228 N_("usage: svnlook changed REPOS_PATH\n\n"
229 "Print the paths that were changed.\n"),
230 {'r', 't', svnlook__copy_info} },
232 {"date", subcommand_date, {0},
233 N_("usage: svnlook date REPOS_PATH\n\n"
234 "Print the datestamp.\n"),
237 {"diff", subcommand_diff, {0},
238 N_("usage: svnlook diff REPOS_PATH\n\n"
239 "Print GNU-style diffs of changed files and properties.\n"),
240 {'r', 't', svnlook__no_diff_deleted, svnlook__no_diff_added,
241 svnlook__diff_copy_from, svnlook__diff_cmd, 'x',
242 svnlook__ignore_properties, svnlook__properties_only} },
244 {"dirs-changed", subcommand_dirschanged, {0},
245 N_("usage: svnlook dirs-changed REPOS_PATH\n\n"
246 "Print the directories that were themselves changed (property edits)\n"
247 "or whose file children were changed.\n"),
250 {"filesize", subcommand_filesize, {0},
251 N_("usage: svnlook filesize REPOS_PATH PATH_IN_REPOS\n\n"
252 "Print the size (in bytes) of the file located at PATH_IN_REPOS as\n"
253 "it is represented in the repository.\n"),
256 {"help", subcommand_help, {"?", "h"},
257 N_("usage: svnlook help [SUBCOMMAND...]\n\n"
258 "Describe the usage of this program or its subcommands.\n"),
261 {"history", subcommand_history, {0},
262 N_("usage: svnlook history REPOS_PATH [PATH_IN_REPOS]\n\n"
263 "Print information about the history of a path in the repository (or\n"
264 "the root directory if no path is supplied).\n"),
265 {'r', svnlook__show_ids, 'l'} },
267 {"info", subcommand_info, {0},
268 N_("usage: svnlook info REPOS_PATH\n\n"
269 "Print the author, datestamp, log message size, and log message.\n"),
272 {"lock", subcommand_lock, {0},
273 N_("usage: svnlook lock REPOS_PATH PATH_IN_REPOS\n\n"
274 "If a lock exists on a path in the repository, describe it.\n"),
277 {"log", subcommand_log, {0},
278 N_("usage: svnlook log REPOS_PATH\n\n"
279 "Print the log message.\n"),
282 {"propget", subcommand_pget, {"pget", "pg"},
283 N_("usage: 1. svnlook propget REPOS_PATH PROPNAME PATH_IN_REPOS\n"
285 /* The line above is actually needed, so do NOT delete it! */
286 " 2. svnlook propget --revprop REPOS_PATH PROPNAME\n\n"
287 "Print the raw value of a property on a path in the repository.\n"
288 "With --revprop, print the raw value of a revision property.\n"),
289 {'r', 't', 'v', svnlook__revprop_opt, svnlook__show_inherited_props} },
291 {"proplist", subcommand_plist, {"plist", "pl"},
292 N_("usage: 1. svnlook proplist REPOS_PATH PATH_IN_REPOS\n"
294 /* The line above is actually needed, so do NOT delete it! */
295 " 2. svnlook proplist --revprop REPOS_PATH\n\n"
296 "List the properties of a path in the repository, or\n"
297 "with the --revprop option, revision properties.\n"
298 "With -v, show the property values too.\n"),
299 {'r', 't', 'v', svnlook__revprop_opt, svnlook__xml_opt,
300 svnlook__show_inherited_props} },
302 {"tree", subcommand_tree, {0},
303 N_("usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n\n"
304 "Print the tree, starting at PATH_IN_REPOS (if supplied, at the root\n"
305 "of the tree otherwise), optionally showing node revision ids.\n"),
306 {'r', 't', 'N', svnlook__show_ids, svnlook__full_paths, 'M'} },
308 {"uuid", subcommand_uuid, {0},
309 N_("usage: svnlook uuid REPOS_PATH\n\n"
310 "Print the repository's UUID.\n"),
313 {"youngest", subcommand_youngest, {0},
314 N_("usage: svnlook youngest REPOS_PATH\n\n"
315 "Print the youngest revision number.\n"),
316 {svnlook__no_newline} },
318 { NULL, NULL, {0}, NULL, {0} }
322 /* Baton for passing option/argument state to a subcommand function. */
323 struct svnlook_opt_state
325 const char *repos_path; /* 'arg0' is always the path to the repository. */
326 const char *arg1; /* Usually an fs path, a propname, or NULL. */
327 const char *arg2; /* Usually an fs path or NULL. */
330 svn_boolean_t version; /* --version */
331 svn_boolean_t show_ids; /* --show-ids */
332 apr_size_t limit; /* --limit */
333 svn_boolean_t help; /* --help */
334 svn_boolean_t no_diff_deleted; /* --no-diff-deleted */
335 svn_boolean_t no_diff_added; /* --no-diff-added */
336 svn_boolean_t diff_copy_from; /* --diff-copy-from */
337 svn_boolean_t verbose; /* --verbose */
338 svn_boolean_t revprop; /* --revprop */
339 svn_boolean_t full_paths; /* --full-paths */
340 svn_boolean_t copy_info; /* --copy-info */
341 svn_boolean_t non_recursive; /* --non-recursive */
342 svn_boolean_t xml; /* --xml */
343 const char *extensions; /* diff extension args (UTF-8!) */
344 svn_boolean_t quiet; /* --quiet */
345 svn_boolean_t ignore_properties; /* --ignore_properties */
346 svn_boolean_t properties_only; /* --properties-only */
347 const char *diff_cmd; /* --diff-cmd */
348 svn_boolean_t show_inherited_props; /* --show-inherited-props */
349 svn_boolean_t no_newline; /* --no-newline */
350 apr_uint64_t memory_cache_size; /* --memory-cache-size */
354 typedef struct svnlook_ctxt_t
358 svn_boolean_t is_revision;
359 svn_boolean_t show_ids;
361 svn_boolean_t no_diff_deleted;
362 svn_boolean_t no_diff_added;
363 svn_boolean_t diff_copy_from;
364 svn_boolean_t full_paths;
365 svn_boolean_t copy_info;
368 const char *txn_name /* UTF-8! */;
369 const apr_array_header_t *diff_options;
370 svn_boolean_t ignore_properties;
371 svn_boolean_t properties_only;
372 const char *diff_cmd;
377 /*** Helper functions. ***/
379 static svn_cancel_func_t check_cancel = NULL;
381 /* Version compatibility check */
383 check_lib_versions(void)
385 static const svn_version_checklist_t checklist[] =
387 { "svn_subr", svn_subr_version },
388 { "svn_repos", svn_repos_version },
389 { "svn_fs", svn_fs_version },
390 { "svn_delta", svn_delta_version },
391 { "svn_diff", svn_diff_version },
394 SVN_VERSION_DEFINE(my_version);
396 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
400 /* Get revision or transaction property PROP_NAME for the revision or
401 transaction specified in C, allocating in in POOL and placing it in
404 get_property(svn_string_t **prop_value,
406 const char *prop_name,
409 svn_string_t *raw_value;
411 /* Fetch transaction property... */
412 if (! c->is_revision)
413 SVN_ERR(svn_fs_txn_prop(&raw_value, c->txn, prop_name, pool));
415 /* ...or revision property -- it's your call. */
417 SVN_ERR(svn_fs_revision_prop2(&raw_value, c->fs, c->rev_id,
418 prop_name, TRUE, pool, pool));
420 *prop_value = raw_value;
427 get_root(svn_fs_root_t **root,
431 /* Open up the appropriate root (revision or transaction). */
434 /* If we didn't get a valid revision number, we'll look at the
435 youngest revision. */
436 if (! SVN_IS_VALID_REVNUM(c->rev_id))
437 SVN_ERR(svn_fs_youngest_rev(&(c->rev_id), c->fs, pool));
439 SVN_ERR(svn_fs_revision_root(root, c->fs, c->rev_id, pool));
443 SVN_ERR(svn_fs_txn_root(root, c->txn, pool));
451 /*** Tree Routines ***/
453 /* Generate a generic delta tree. */
455 generate_delta_tree(svn_repos_node_t **tree,
458 svn_revnum_t base_rev,
461 svn_fs_root_t *base_root;
462 const svn_delta_editor_t *editor;
464 apr_pool_t *edit_pool = svn_pool_create(pool);
465 svn_fs_t *fs = svn_repos_fs(repos);
467 /* Get the base root. */
468 SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, pool));
470 /* Request our editor. */
471 SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos,
472 base_root, root, pool, edit_pool));
474 /* Drive our editor. */
475 SVN_ERR(svn_repos_replay2(root, "", SVN_INVALID_REVNUM, TRUE,
476 editor, edit_baton, NULL, NULL, edit_pool));
478 /* Return the tree we just built. */
479 *tree = svn_repos_node_from_baton(edit_baton);
480 svn_pool_destroy(edit_pool);
486 /*** Tree Printing Routines ***/
488 /* Recursively print only directory nodes that either a) have property
489 mods, or b) contains files that have changed, or c) has added or deleted
490 children. NODE is the root node of the tree delta, so every node in it
491 is either changed or is a directory with a changed node somewhere in the
495 print_dirs_changed_tree(svn_repos_node_t *node,
496 const char *path /* UTF-8! */,
499 svn_repos_node_t *tmp_node;
500 svn_boolean_t print_me = FALSE;
501 const char *full_path;
502 apr_pool_t *iterpool;
504 SVN_ERR(check_cancel(NULL));
509 /* Not a directory? We're not interested. */
510 if (node->kind != svn_node_dir)
513 /* Got prop mods? Excellent. */
517 /* Fly through the list of children, checking for modified files. */
518 tmp_node = node->child;
519 while (tmp_node && (! print_me))
521 if ((tmp_node->kind == svn_node_file)
522 || (tmp_node->action == 'A')
523 || (tmp_node->action == 'D'))
527 tmp_node = tmp_node->sibling;
530 /* Print the node if it qualifies. */
533 SVN_ERR(svn_cmdline_printf(pool, "%s/\n", path));
536 /* Return here if the node has no children. */
537 tmp_node = node->child;
541 /* Recursively handle the node's children. */
542 iterpool = svn_pool_create(pool);
545 svn_pool_clear(iterpool);
546 full_path = svn_dirent_join(path, tmp_node->name, iterpool);
547 SVN_ERR(print_dirs_changed_tree(tmp_node, full_path, iterpool));
548 tmp_node = tmp_node->sibling;
550 svn_pool_destroy(iterpool);
556 /* Recursively print all nodes in the tree that have been modified
557 (do not include directories affected only by "bubble-up"). */
559 print_changed_tree(svn_repos_node_t *node,
560 const char *path /* UTF-8! */,
561 svn_boolean_t copy_info,
564 const char *full_path;
565 char status[4] = "_ ";
566 svn_boolean_t print_me = TRUE;
567 apr_pool_t *iterpool;
569 SVN_ERR(check_cancel(NULL));
574 /* Print the node. */
575 if (node->action == 'A')
578 if (copy_info && node->copyfrom_path)
581 else if (node->action == 'D')
583 else if (node->action == 'R')
585 if ((! node->text_mod) && (! node->prop_mod))
595 /* Print this node unless told to skip it. */
598 SVN_ERR(svn_cmdline_printf(pool, "%s %s%s\n",
601 node->kind == svn_node_dir ? "/" : ""));
602 if (copy_info && node->copyfrom_path)
603 /* Remove the leading slash from the copyfrom path for consistency
604 with the rest of the output. */
605 SVN_ERR(svn_cmdline_printf(pool, " (from %s%s:r%ld)\n",
606 (node->copyfrom_path[0] == '/'
607 ? node->copyfrom_path + 1
608 : node->copyfrom_path),
609 (node->kind == svn_node_dir ? "/" : ""),
610 node->copyfrom_rev));
613 /* Return here if the node has no children. */
618 /* Recursively handle the node's children. */
619 iterpool = svn_pool_create(pool);
622 svn_pool_clear(iterpool);
623 full_path = svn_dirent_join(path, node->name, iterpool);
624 SVN_ERR(print_changed_tree(node, full_path, copy_info, iterpool));
625 node = node->sibling;
627 svn_pool_destroy(iterpool);
634 dump_contents(svn_stream_t *stream,
636 const char *path /* UTF-8! */,
640 SVN_ERR(svn_stream_close(stream)); /* leave an empty file */
643 svn_stream_t *contents;
645 /* Grab the contents and copy them into the given stream. */
646 SVN_ERR(svn_fs_file_contents(&contents, root, path, pool));
647 SVN_ERR(svn_stream_copy3(contents, stream, NULL, NULL, pool));
654 /* Prepare temporary files *TMPFILE1 and *TMPFILE2 for diffing
655 PATH1@ROOT1 versus PATH2@ROOT2. If either ROOT1 or ROOT2 is NULL,
656 the temporary file for its path/root will be an empty one.
657 Otherwise, its temporary file will contain the contents of that
658 path/root in the repository.
660 An exception to this is when either path/root has an svn:mime-type
661 property set on it which indicates that the file contains
662 non-textual data -- in this case, the *IS_BINARY flag is set and no
663 temporary files are created.
665 TMPFILE1 and TMPFILE2 will be removed when RESULT_POOL is destroyed.
668 prepare_tmpfiles(const char **tmpfile1,
669 const char **tmpfile2,
670 svn_boolean_t *is_binary,
671 svn_fs_root_t *root1,
673 svn_fs_root_t *root2,
675 apr_pool_t *result_pool,
676 apr_pool_t *scratch_pool)
678 svn_string_t *mimetype;
679 svn_stream_t *stream;
681 /* Init the return values. */
686 assert(path1 && path2);
688 /* Check for binary mimetypes. If either file has a binary
689 mimetype, get outta here. */
692 SVN_ERR(svn_fs_node_prop(&mimetype, root1, path1,
693 SVN_PROP_MIME_TYPE, scratch_pool));
694 if (mimetype && svn_mime_type_is_binary(mimetype->data))
702 SVN_ERR(svn_fs_node_prop(&mimetype, root2, path2,
703 SVN_PROP_MIME_TYPE, scratch_pool));
704 if (mimetype && svn_mime_type_is_binary(mimetype->data))
711 /* Now, prepare the two temporary files, each of which will either
712 be empty, or will have real contents. */
713 SVN_ERR(svn_stream_open_unique(&stream, tmpfile1, NULL,
714 svn_io_file_del_on_pool_cleanup,
715 result_pool, scratch_pool));
716 SVN_ERR(dump_contents(stream, root1, path1, scratch_pool));
718 SVN_ERR(svn_stream_open_unique(&stream, tmpfile2, NULL,
719 svn_io_file_del_on_pool_cleanup,
720 result_pool, scratch_pool));
721 SVN_ERR(dump_contents(stream, root2, path2, scratch_pool));
727 /* Generate a diff label for PATH in ROOT, allocating in POOL.
728 ROOT may be NULL, in which case revision 0 is used. */
730 generate_label(const char **label,
737 const char *name = NULL;
738 svn_revnum_t rev = SVN_INVALID_REVNUM;
742 svn_fs_t *fs = svn_fs_root_fs(root);
743 if (svn_fs_is_revision_root(root))
745 rev = svn_fs_revision_root_revision(root);
746 SVN_ERR(svn_fs_revision_prop2(&date, fs, rev,
747 SVN_PROP_REVISION_DATE, TRUE,
753 name = svn_fs_txn_root_name(root, pool);
754 SVN_ERR(svn_fs_open_txn(&txn, fs, name, pool));
755 SVN_ERR(svn_fs_txn_prop(&date, txn, SVN_PROP_REVISION_DATE, pool));
765 datestr = apr_psprintf(pool, "%.10s %.8s UTC", date->data, date->data + 11);
770 *label = apr_psprintf(pool, "%s\t%s (txn %s)",
771 path, datestr, name);
773 *label = apr_psprintf(pool, "%s\t%s (rev %ld)",
779 /* Helper function to display differences in properties of a file */
781 display_prop_diffs(svn_stream_t *outstream,
782 const char *encoding,
783 const apr_array_header_t *propchanges,
784 apr_hash_t *original_props,
789 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
790 _("%sProperty changes on: %s%s"),
795 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
796 SVN_DIFF__UNDER_STRING APR_EOL_STR));
798 SVN_ERR(check_cancel(NULL));
800 SVN_ERR(svn_diff__display_prop_diffs(
801 outstream, encoding, propchanges, original_props,
802 FALSE /* pretty_print_mergeinfo */,
803 -1 /* context_size */,
804 check_cancel, NULL, pool));
810 /* Recursively print all nodes in the tree that have been modified
811 (do not include directories affected only by "bubble-up"). */
813 print_diff_tree(svn_stream_t *out_stream,
814 const char *encoding,
816 svn_fs_root_t *base_root,
817 svn_repos_node_t *node,
818 const char *path /* UTF-8! */,
819 const char *base_path /* UTF-8! */,
820 const svnlook_ctxt_t *c,
823 const char *orig_path = NULL, *new_path = NULL;
824 svn_boolean_t do_diff = FALSE;
825 svn_boolean_t orig_empty = FALSE;
826 svn_boolean_t is_copy = FALSE;
827 svn_boolean_t binary = FALSE;
828 svn_boolean_t diff_header_printed = FALSE;
829 apr_pool_t *iterpool;
830 svn_stringbuf_t *header;
832 SVN_ERR(check_cancel(NULL));
837 header = svn_stringbuf_create_empty(pool);
839 /* Print copyfrom history for the top node of a copied tree. */
840 if ((SVN_IS_VALID_REVNUM(node->copyfrom_rev))
841 && (node->copyfrom_path != NULL))
843 /* This is ... a copy. */
846 /* Propagate the new base. Copyfrom paths usually start with a
847 slash; we remove it for consistency with the target path.
848 ### Yes, it would be *much* better for something in the path
849 library to be taking care of this! */
850 if (node->copyfrom_path[0] == '/')
851 base_path = apr_pstrdup(pool, node->copyfrom_path + 1);
853 base_path = apr_pstrdup(pool, node->copyfrom_path);
855 svn_stringbuf_appendcstr
857 apr_psprintf(pool, _("Copied: %s (from rev %ld, %s)\n"),
858 path, node->copyfrom_rev, base_path));
860 SVN_ERR(svn_fs_revision_root(&base_root,
861 svn_fs_root_fs(base_root),
862 node->copyfrom_rev, pool));
865 /*** First, we'll just print file content diffs. ***/
866 if (node->kind == svn_node_file)
868 /* Here's the generalized way we do our diffs:
870 - First, we'll check for svn:mime-type properties on the old
871 and new files. If either has such a property, and it
872 represents a binary type, we won't actually be doing a real
875 - Second, dump the contents of the new version of the file
876 into the temporary directory.
878 - Then, dump the contents of the old version of the file into
879 the temporary directory.
881 - Next, we run 'diff', passing the repository paths as the
884 - Finally, we delete the temporary files. */
885 if (node->action == 'R' && node->text_mod)
888 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
889 base_root, base_path, root, path,
892 else if (c->diff_copy_from && node->action == 'A' && is_copy)
897 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
898 base_root, base_path, root, path,
902 else if (! c->no_diff_added && node->action == 'A')
906 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
907 NULL, base_path, root, path,
910 else if (! c->no_diff_deleted && node->action == 'D')
913 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
914 base_root, base_path, NULL, path,
918 /* The header for the copy case has already been created, and we don't
919 want a header here for files with only property modifications. */
921 && (node->action != 'R' || node->text_mod))
923 svn_stringbuf_appendcstr
924 (header, apr_psprintf(pool, "%s: %s\n",
925 ((node->action == 'A') ? _("Added") :
926 ((node->action == 'D') ? _("Deleted") :
927 ((node->action == 'R') ? _("Modified")
933 if (do_diff && (! c->properties_only))
935 svn_stringbuf_appendcstr(header, SVN_DIFF__EQUAL_STRING "\n");
939 svn_stringbuf_appendcstr(header, _("(Binary files differ)\n\n"));
940 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
941 "%s", header->data));
949 const char *outfilename;
950 const char *errfilename;
951 svn_stream_t *stream;
952 svn_stream_t *err_stream;
953 const char **diff_cmd_argv;
956 const char *orig_label;
957 const char *new_label;
959 diff_cmd_argv = NULL;
960 diff_cmd_argc = c->diff_options->nelts;
964 diff_cmd_argv = apr_palloc(pool,
965 diff_cmd_argc * sizeof(char *));
966 for (i = 0; i < diff_cmd_argc; i++)
967 SVN_ERR(svn_utf_cstring_to_utf8(&diff_cmd_argv[i],
968 APR_ARRAY_IDX(c->diff_options, i, const char *),
972 /* Print diff header. */
973 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
974 "%s", header->data));
977 SVN_ERR(generate_label(&orig_label, NULL, path, pool));
979 SVN_ERR(generate_label(&orig_label, base_root,
981 SVN_ERR(generate_label(&new_label, root, path, pool));
983 /* We deal in streams, but svn_io_run_diff2() deals in file
984 handles, so we may need to make temporary files and then
985 copy the contents to our stream. */
986 outfile = svn_stream__aprfile(out_stream);
990 SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
991 svn_io_file_del_on_pool_cleanup, pool, pool));
992 SVN_ERR(svn_stream_for_stderr(&err_stream, pool));
993 errfile = svn_stream__aprfile(err_stream);
997 SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
998 svn_io_file_del_on_pool_cleanup, pool, pool));
1000 SVN_ERR(svn_io_run_diff2(".",
1003 orig_label, new_label,
1004 orig_path, new_path,
1005 &exitcode, outfile, errfile,
1006 c->diff_cmd, pool));
1008 /* Now, open and copy our files to our output streams. */
1011 SVN_ERR(svn_io_file_close(outfile, pool));
1012 SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
1014 SVN_ERR(svn_stream_copy3(stream,
1015 svn_stream_disown(out_stream, pool),
1020 SVN_ERR(svn_io_file_close(errfile, pool));
1021 SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
1023 SVN_ERR(svn_stream_copy3(stream,
1024 svn_stream_disown(err_stream, pool),
1028 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1030 diff_header_printed = TRUE;
1035 svn_diff_file_options_t *opts = svn_diff_file_options_create(pool);
1037 if (c->diff_options)
1038 SVN_ERR(svn_diff_file_options_parse(opts, c->diff_options, pool));
1040 SVN_ERR(svn_diff_file_diff_2(&diff, orig_path,
1041 new_path, opts, pool));
1043 if (svn_diff_contains_diffs(diff))
1045 const char *orig_label, *new_label;
1047 /* Print diff header. */
1048 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1049 "%s", header->data));
1052 SVN_ERR(generate_label(&orig_label, NULL, path, pool));
1054 SVN_ERR(generate_label(&orig_label, base_root,
1056 SVN_ERR(generate_label(&new_label, root, path, pool));
1057 SVN_ERR(svn_diff_file_output_unified4(
1058 out_stream, diff, orig_path, new_path,
1059 orig_label, new_label,
1060 svn_cmdline_output_encoding(pool), NULL,
1061 opts->show_c_function, opts->context_size,
1062 check_cancel, NULL, pool));
1063 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1065 diff_header_printed = TRUE;
1067 else if (! node->prop_mod &&
1068 ((! c->no_diff_added && node->action == 'A') ||
1069 (! c->no_diff_deleted && node->action == 'D')))
1071 /* There was an empty file added or deleted in this revision.
1072 * We can't print a diff, but we can at least print
1073 * a diff header since we know what happened to this file. */
1074 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1075 "%s", header->data));
1081 /*** Now handle property diffs ***/
1082 if ((node->prop_mod) && (node->action != 'D') && (! c->ignore_properties))
1084 apr_hash_t *local_proptable;
1085 apr_hash_t *base_proptable;
1086 apr_array_header_t *propchanges, *props;
1088 SVN_ERR(svn_fs_node_proplist(&local_proptable, root, path, pool));
1089 if (c->diff_copy_from && node->action == 'A' && is_copy)
1090 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1092 else if (node->action == 'A')
1093 base_proptable = apr_hash_make(pool);
1094 else /* node->action == 'R' */
1095 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1097 SVN_ERR(svn_prop_diffs(&propchanges, local_proptable,
1098 base_proptable, pool));
1099 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, pool));
1100 if (props->nelts > 0)
1102 /* We print a diff header for the case when we only have property
1104 if (! diff_header_printed)
1106 const char *orig_label, *new_label;
1108 SVN_ERR(generate_label(&orig_label, base_root, base_path,
1110 SVN_ERR(generate_label(&new_label, root, path, pool));
1112 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1113 "Index: %s\n", path));
1114 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1115 SVN_DIFF__EQUAL_STRING "\n"));
1118 SVN_ERR(svn_diff__unidiff_write_header(
1119 out_stream, encoding, orig_label, new_label, pool));
1121 SVN_ERR(display_prop_diffs(out_stream, encoding,
1122 props, base_proptable, path, pool));
1126 /* Return here if the node has no children. */
1128 return SVN_NO_ERROR;
1130 /* Recursively handle the node's children. */
1131 iterpool = svn_pool_create(pool);
1132 for (node = node->child; node; node = node->sibling)
1134 svn_pool_clear(iterpool);
1136 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node,
1137 svn_dirent_join(path, node->name, iterpool),
1138 svn_dirent_join(base_path, node->name, iterpool),
1141 svn_pool_destroy(iterpool);
1143 return SVN_NO_ERROR;
1147 /* Print a repository directory, maybe recursively, possibly showing
1148 the node revision ids, and optionally using full paths.
1150 ROOT is the revision or transaction root used to build that tree.
1151 PATH and ID are the current path and node revision id being
1152 printed, and INDENTATION the number of spaces to prepent to that
1153 path's printed output. ID may be NULL if SHOW_IDS is FALSE (in
1154 which case, ids won't be printed at all). If RECURSE is TRUE,
1155 then print the tree recursively; otherwise, we'll stop after the
1156 first level (and use INDENTATION to keep track of how deep we are).
1158 Use POOL for all allocations. */
1159 static svn_error_t *
1160 print_tree(svn_fs_root_t *root,
1161 const char *path /* UTF-8! */,
1162 const svn_fs_id_t *id,
1163 svn_boolean_t is_dir,
1165 svn_boolean_t show_ids,
1166 svn_boolean_t full_paths,
1167 svn_boolean_t recurse,
1170 apr_pool_t *subpool;
1171 apr_hash_t *entries;
1174 SVN_ERR(check_cancel(NULL));
1176 /* Print the indentation. */
1180 for (i = 0; i < indentation; i++)
1181 SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
1184 /* ### The path format is inconsistent.. needs fix */
1187 else if (*path == '/')
1188 name = svn_fspath__basename(path, pool);
1190 name = svn_relpath_basename(path, NULL);
1192 if (svn_path_is_empty(name))
1193 name = "/"; /* basename of '/' is "" */
1195 /* Print the node. */
1196 SVN_ERR(svn_cmdline_printf(pool, "%s%s",
1198 is_dir && strcmp(name, "/") ? "/" : ""));
1202 svn_string_t *unparsed_id = NULL;
1204 unparsed_id = svn_fs_unparse_id(id, pool);
1205 SVN_ERR(svn_cmdline_printf(pool, " <%s>",
1210 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1212 /* Return here if PATH is not a directory. */
1214 return SVN_NO_ERROR;
1216 /* Recursively handle the node's children. */
1217 if (recurse || (indentation == 0))
1219 apr_array_header_t *sorted_entries;
1222 SVN_ERR(svn_fs_dir_entries(&entries, root, path, pool));
1223 subpool = svn_pool_create(pool);
1224 sorted_entries = svn_sort__hash(entries,
1225 svn_sort_compare_items_lexically, pool);
1226 for (i = 0; i < sorted_entries->nelts; i++)
1228 svn_sort__item_t item = APR_ARRAY_IDX(sorted_entries, i,
1230 svn_fs_dirent_t *entry = item.value;
1232 svn_pool_clear(subpool);
1233 SVN_ERR(print_tree(root,
1235 ? svn_fspath__join(path, entry->name, pool)
1236 : svn_relpath_join(path, entry->name, pool),
1237 entry->id, (entry->kind == svn_node_dir),
1238 indentation + 1, show_ids, full_paths,
1241 svn_pool_destroy(subpool);
1244 return SVN_NO_ERROR;
1248 /* Set *BASE_REV to the revision on which the target root specified in
1249 C is based, or to SVN_INVALID_REVNUM when C represents "revision
1250 0" (because that revision isn't based on another revision). */
1251 static svn_error_t *
1252 get_base_rev(svn_revnum_t *base_rev, svnlook_ctxt_t *c, apr_pool_t *pool)
1256 *base_rev = c->rev_id - 1;
1260 *base_rev = svn_fs_txn_base_revision(c->txn);
1262 if (! SVN_IS_VALID_REVNUM(*base_rev))
1263 return svn_error_createf
1264 (SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1265 _("Transaction '%s' is not based on a revision; how odd"),
1268 return SVN_NO_ERROR;
1273 /*** Subcommand handlers. ***/
1275 /* Print the revision's log message to stdout, followed by a newline. */
1276 static svn_error_t *
1277 do_log(svnlook_ctxt_t *c, svn_boolean_t print_size, apr_pool_t *pool)
1279 svn_string_t *prop_value;
1280 const char *prop_value_eol, *prop_value_native;
1281 svn_stream_t *stream;
1285 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_LOG, pool));
1286 if (! (prop_value && prop_value->data))
1288 SVN_ERR(svn_cmdline_printf(pool, "%s\n", print_size ? "0" : ""));
1289 return SVN_NO_ERROR;
1292 /* We immitate what svn_cmdline_printf does here, since we need the byte
1293 size of what we are going to print. */
1295 SVN_ERR(svn_subst_translate_cstring2(prop_value->data, &prop_value_eol,
1297 NULL, FALSE, pool));
1299 err = svn_cmdline_cstring_from_utf8(&prop_value_native, prop_value_eol,
1303 svn_error_clear(err);
1304 prop_value_native = svn_cmdline_cstring_from_utf8_fuzzy(prop_value_eol,
1308 len = strlen(prop_value_native);
1311 SVN_ERR(svn_cmdline_printf(pool, "%" APR_SIZE_T_FMT "\n", len));
1313 /* Use a stream to bypass all stdio translations. */
1314 SVN_ERR(svn_cmdline_fflush(stdout));
1315 SVN_ERR(svn_stream_for_stdout(&stream, pool));
1316 SVN_ERR(svn_stream_write(stream, prop_value_native, &len));
1317 SVN_ERR(svn_stream_close(stream));
1319 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1321 return SVN_NO_ERROR;
1325 /* Print the timestamp of the commit (in the revision case) or the
1326 empty string (in the transaction case) to stdout, followed by a
1328 static svn_error_t *
1329 do_date(svnlook_ctxt_t *c, apr_pool_t *pool)
1331 svn_string_t *prop_value;
1333 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_DATE, pool));
1334 if (prop_value && prop_value->data)
1336 /* Convert the date for humans. */
1338 const char *time_utf8;
1340 SVN_ERR(svn_time_from_cstring(&aprtime, prop_value->data, pool));
1342 time_utf8 = svn_time_to_human_cstring(aprtime, pool);
1344 SVN_ERR(svn_cmdline_printf(pool, "%s", time_utf8));
1347 SVN_ERR(svn_cmdline_printf(pool, "\n"));
1348 return SVN_NO_ERROR;
1352 /* Print the author of the commit to stdout, followed by a newline. */
1353 static svn_error_t *
1354 do_author(svnlook_ctxt_t *c, apr_pool_t *pool)
1356 svn_string_t *prop_value;
1358 SVN_ERR(get_property(&prop_value, c,
1359 SVN_PROP_REVISION_AUTHOR, pool));
1360 if (prop_value && prop_value->data)
1361 SVN_ERR(svn_cmdline_printf(pool, "%s", prop_value->data));
1363 SVN_ERR(svn_cmdline_printf(pool, "\n"));
1364 return SVN_NO_ERROR;
1368 /* Print a list of all directories in which files, or directory
1369 properties, have been modified. */
1370 static svn_error_t *
1371 do_dirs_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1373 svn_fs_root_t *root;
1374 svn_revnum_t base_rev_id;
1375 svn_repos_node_t *tree;
1377 SVN_ERR(get_root(&root, c, pool));
1378 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1379 if (base_rev_id == SVN_INVALID_REVNUM)
1380 return SVN_NO_ERROR;
1382 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1384 SVN_ERR(print_dirs_changed_tree(tree, "", pool));
1386 return SVN_NO_ERROR;
1390 /* Set *KIND to PATH's kind, if PATH exists.
1392 * If PATH does not exist, then error; the text of the error depends
1393 * on whether PATH looks like a URL or not.
1395 static svn_error_t *
1396 verify_path(svn_node_kind_t *kind,
1397 svn_fs_root_t *root,
1401 SVN_ERR(svn_fs_check_path(kind, root, path, pool));
1403 if (*kind == svn_node_none)
1405 if (svn_path_is_url(path)) /* check for a common mistake. */
1406 return svn_error_createf
1407 (SVN_ERR_FS_NOT_FOUND, NULL,
1408 _("'%s' is a URL, probably should be a path"), path);
1410 return svn_error_createf
1411 (SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' does not exist"), path);
1414 return SVN_NO_ERROR;
1418 /* Print the size (in bytes) of a file. */
1419 static svn_error_t *
1420 do_filesize(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
1422 svn_fs_root_t *root;
1423 svn_node_kind_t kind;
1424 svn_filesize_t length;
1426 SVN_ERR(get_root(&root, c, pool));
1427 SVN_ERR(verify_path(&kind, root, path, pool));
1429 if (kind != svn_node_file)
1430 return svn_error_createf
1431 (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
1435 SVN_ERR(svn_fs_file_length(&length, root, path, pool));
1436 return svn_cmdline_printf(pool, "%" SVN_FILESIZE_T_FMT "\n", length);
1439 /* Print the contents of the file at PATH in the repository.
1440 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
1441 SVN_ERR_FS_NOT_FILE if PATH exists but is not a file. */
1442 static svn_error_t *
1443 do_cat(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
1445 svn_fs_root_t *root;
1446 svn_node_kind_t kind;
1447 svn_stream_t *fstream, *stdout_stream;
1449 SVN_ERR(get_root(&root, c, pool));
1450 SVN_ERR(verify_path(&kind, root, path, pool));
1452 if (kind != svn_node_file)
1453 return svn_error_createf
1454 (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
1458 SVN_ERR(svn_fs_file_contents(&fstream, root, path, pool));
1459 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1461 return svn_stream_copy3(fstream, svn_stream_disown(stdout_stream, pool),
1462 check_cancel, NULL, pool);
1466 /* Print a list of all paths modified in a format compatible with `svn
1468 static svn_error_t *
1469 do_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1471 svn_fs_root_t *root;
1472 svn_revnum_t base_rev_id;
1473 svn_repos_node_t *tree;
1475 SVN_ERR(get_root(&root, c, pool));
1476 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1477 if (base_rev_id == SVN_INVALID_REVNUM)
1478 return SVN_NO_ERROR;
1480 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1482 SVN_ERR(print_changed_tree(tree, "", c->copy_info, pool));
1484 return SVN_NO_ERROR;
1488 /* Print some diff-y stuff in a TBD way. :-) */
1489 static svn_error_t *
1490 do_diff(svnlook_ctxt_t *c, apr_pool_t *pool)
1492 svn_fs_root_t *root, *base_root;
1493 svn_revnum_t base_rev_id;
1494 svn_repos_node_t *tree;
1496 SVN_ERR(get_root(&root, c, pool));
1497 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1498 if (base_rev_id == SVN_INVALID_REVNUM)
1499 return SVN_NO_ERROR;
1501 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1504 svn_stream_t *out_stream;
1505 const char *encoding = svn_cmdline_output_encoding(pool);
1507 SVN_ERR(svn_fs_revision_root(&base_root, c->fs, base_rev_id, pool));
1509 /* This fflush() might seem odd, but it was added to deal
1510 with this bug report:
1512 http://subversion.tigris.org/servlets/ReadMsg?\
1513 list=dev&msgNo=140782
1515 From: "Steve Hay" <SteveHay{_AT_}planit.com>
1516 To: <dev@subversion.tigris.org>
1517 Subject: svnlook diff output in wrong order when redirected
1518 Date: Fri, 4 Jul 2008 16:34:15 +0100
1519 Message-ID: <1B32FF956ABF414C9BCE5E487A1497E702014F62@\
1520 ukmail02.planit.group>
1522 Adding the fflush() fixed the bug (not everyone could
1523 reproduce it, but those who could confirmed the fix).
1524 Later in the thread, Daniel Shahaf speculated as to
1527 "Because svn_cmdline_printf() uses the standard
1528 'FILE *stdout' to write to stdout, while
1529 svn_stream_for_stdout() uses (through
1530 apr_file_open_stdout()) Windows API's to get a
1531 handle for stdout?" */
1532 SVN_ERR(svn_cmdline_fflush(stdout));
1533 SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1535 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, tree,
1538 return SVN_NO_ERROR;
1543 /* Callback baton for print_history() (and do_history()). */
1544 struct print_history_baton
1547 svn_boolean_t show_ids; /* whether to show node IDs */
1548 apr_size_t limit; /* max number of history items */
1549 apr_size_t count; /* number of history items processed */
1552 /* Implements svn_repos_history_func_t interface. Print the history
1553 that's reported through this callback, possibly finding and
1554 displaying node-rev-ids. */
1555 static svn_error_t *
1556 print_history(void *baton,
1558 svn_revnum_t revision,
1561 struct print_history_baton *phb = baton;
1563 SVN_ERR(check_cancel(NULL));
1567 const svn_fs_id_t *node_id;
1568 svn_fs_root_t *rev_root;
1569 svn_string_t *id_string;
1571 SVN_ERR(svn_fs_revision_root(&rev_root, phb->fs, revision, pool));
1572 SVN_ERR(svn_fs_node_id(&node_id, rev_root, path, pool));
1573 id_string = svn_fs_unparse_id(node_id, pool);
1574 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s <%s>\n",
1575 revision, path, id_string->data));
1579 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s\n", revision, path));
1585 if (phb->count >= phb->limit)
1586 /* Not L10N'd, since this error is suppressed by the caller. */
1587 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL,
1588 _("History item limit reached"));
1591 return SVN_NO_ERROR;
1595 /* Print a tabular display of history location points for PATH in
1596 revision C->rev_id. Optionally, SHOW_IDS. Use POOL for
1598 static svn_error_t *
1599 do_history(svnlook_ctxt_t *c,
1603 struct print_history_baton args;
1607 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH <ID>\n"
1608 "-------- ---------\n")));
1612 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH\n"
1613 "-------- ----\n")));
1616 /* Call our history crawler. We want the whole lifetime of the path
1617 (prior to the user-supplied revision, of course), across all
1620 args.show_ids = c->show_ids;
1621 args.limit = c->limit;
1623 SVN_ERR(svn_repos_history2(c->fs, path, print_history, &args,
1624 NULL, NULL, 0, c->rev_id, TRUE, pool));
1625 return SVN_NO_ERROR;
1629 /* Print the value of property PROPNAME on PATH in the repository.
1631 If VERBOSE, print their values too. If SHOW_INHERITED_PROPS, print
1632 PATH's inherited props too.
1634 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. If
1635 SHOW_INHERITED_PROPS is FALSE,then error with SVN_ERR_PROPERTY_NOT_FOUND
1636 if there is no such property on PATH. If SHOW_INHERITED_PROPS is TRUE,
1637 then error with SVN_ERR_PROPERTY_NOT_FOUND only if there is no such
1638 property on PATH nor inherited by path.
1640 If PATH is NULL, operate on a revision property. */
1641 static svn_error_t *
1642 do_pget(svnlook_ctxt_t *c,
1643 const char *propname,
1645 svn_boolean_t verbose,
1646 svn_boolean_t show_inherited_props,
1649 svn_fs_root_t *root;
1651 svn_node_kind_t kind;
1652 svn_stream_t *stdout_stream;
1654 apr_array_header_t *inherited_props = NULL;
1656 SVN_ERR(get_root(&root, c, pool));
1659 path = svn_fspath__canonicalize(path, pool);
1660 SVN_ERR(verify_path(&kind, root, path, pool));
1661 SVN_ERR(svn_fs_node_prop(&prop, root, path, propname, pool));
1663 if (show_inherited_props)
1665 SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
1666 path, propname, NULL,
1670 else /* --revprop */
1672 SVN_ERR(get_property(&prop, c, propname, pool));
1675 /* Did we find nothing? */
1677 && (!show_inherited_props || inherited_props->nelts == 0))
1679 const char *err_msg;
1682 /* We're operating on a revprop (e.g. c->is_revision). */
1683 if (SVN_IS_VALID_REVNUM(c->rev_id))
1684 err_msg = apr_psprintf(pool,
1685 _("Property '%s' not found on revision %ld"),
1686 propname, c->rev_id);
1688 err_msg = apr_psprintf(pool,
1689 _("Property '%s' not found on transaction %s"),
1690 propname, c->txn_name);
1694 if (SVN_IS_VALID_REVNUM(c->rev_id))
1696 if (show_inherited_props)
1697 err_msg = apr_psprintf(pool,
1698 _("Property '%s' not found on path '%s' "
1699 "or inherited from a parent "
1701 propname, path, c->rev_id);
1703 err_msg = apr_psprintf(pool,
1704 _("Property '%s' not found on path '%s' "
1706 propname, path, c->rev_id);
1710 if (show_inherited_props)
1711 err_msg = apr_psprintf(pool,
1712 _("Property '%s' not found on path '%s' "
1713 "or inherited from a parent "
1714 "in transaction %s"),
1715 propname, path, c->txn_name);
1717 err_msg = apr_psprintf(pool,
1718 _("Property '%s' not found on path '%s' "
1719 "in transaction %s"),
1720 propname, path, c->txn_name);
1723 return svn_error_create(SVN_ERR_PROPERTY_NOT_FOUND, NULL, err_msg);
1726 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1728 if (verbose || show_inherited_props)
1730 if (inherited_props)
1734 for (i = 0; i < inherited_props->nelts; i++)
1736 svn_prop_inherited_item_t *elt =
1737 APR_ARRAY_IDX(inherited_props, i,
1738 svn_prop_inherited_item_t *);
1742 SVN_ERR(svn_stream_printf(stdout_stream, pool,
1743 _("Inherited properties on '%s',\nfrom '%s':\n"),
1744 path, svn_fspath__canonicalize(elt->path_or_url,
1746 SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream,
1752 svn_string_t *propval =
1753 apr_hash_this_val(apr_hash_first(pool, elt->prop_hash));
1755 SVN_ERR(svn_stream_printf(
1756 stdout_stream, pool, "%s - ",
1757 svn_fspath__canonicalize(elt->path_or_url, pool)));
1759 SVN_ERR(svn_stream_write(stdout_stream, propval->data, &len));
1760 /* If we have more than one property to write, then add a newline*/
1761 if (inherited_props->nelts > 1 || prop)
1763 len = strlen(APR_EOL_STR);
1764 SVN_ERR(svn_stream_write(stdout_stream, APR_EOL_STR, &len));
1774 apr_hash_t *hash = apr_hash_make(pool);
1776 svn_hash_sets(hash, propname, prop);
1777 SVN_ERR(svn_stream_printf(stdout_stream, pool,
1778 _("Properties on '%s':\n"), path));
1779 SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, hash,
1784 SVN_ERR(svn_stream_printf(stdout_stream, pool, "%s - ", path));
1786 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1790 else /* Raw single prop output, i.e. non-verbose output with no
1793 /* Unlike the command line client, we don't translate the property
1794 value or print a trailing newline here. We just output the raw
1795 bytes of whatever's in the repository, as svnlook is more likely
1796 to be used for automated inspections. */
1798 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1801 return SVN_NO_ERROR;
1805 /* Print the property names of all properties on PATH in the repository.
1807 If VERBOSE, print their values too. If XML, print as XML rather than as
1808 plain text. If SHOW_INHERITED_PROPS, print PATH's inherited props too.
1810 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist.
1812 If PATH is NULL, operate on a revision properties. */
1813 static svn_error_t *
1814 do_plist(svnlook_ctxt_t *c,
1816 svn_boolean_t verbose,
1818 svn_boolean_t show_inherited_props,
1821 svn_fs_root_t *root;
1823 apr_hash_index_t *hi;
1824 svn_node_kind_t kind;
1825 svn_stringbuf_t *sb = NULL;
1826 svn_boolean_t revprop = FALSE;
1827 apr_array_header_t *inherited_props = NULL;
1831 /* PATH might be the root of the repsository and we accept both
1832 "" and "/". But to avoid the somewhat cryptic output like this:
1834 >svnlook pl repos-path ""
1839 We canonicalize PATH so that is has a leading slash. */
1840 path = svn_fspath__canonicalize(path, pool);
1842 SVN_ERR(get_root(&root, c, pool));
1843 SVN_ERR(verify_path(&kind, root, path, pool));
1844 SVN_ERR(svn_fs_node_proplist(&props, root, path, pool));
1846 if (show_inherited_props)
1847 SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
1848 path, NULL, NULL, NULL,
1851 else if (c->is_revision)
1853 SVN_ERR(svn_fs_revision_proplist2(&props, c->fs, c->rev_id, TRUE,
1859 SVN_ERR(svn_fs_txn_proplist(&props, c->txn, pool));
1865 /* <?xml version="1.0" encoding="UTF-8"?> */
1866 svn_xml_make_header2(&sb, "UTF-8", pool);
1868 /* "<properties>" */
1869 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "properties",
1873 if (inherited_props)
1877 for (i = 0; i < inherited_props->nelts; i++)
1879 svn_prop_inherited_item_t *elt =
1880 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1882 /* Canonicalize the inherited parent paths for consistency
1886 svn_xml_make_open_tag(
1887 &sb, pool, svn_xml_normal, "target", "path",
1888 svn_fspath__canonicalize(elt->path_or_url, pool),
1890 SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, elt->prop_hash,
1893 svn_xml_make_close_tag(&sb, pool, "target");
1897 SVN_ERR(svn_cmdline_printf(
1898 pool, _("Inherited properties on '%s',\nfrom '%s':\n"),
1899 path, svn_fspath__canonicalize(elt->path_or_url, pool)));
1900 SVN_ERR(svn_cmdline__print_prop_hash(NULL, elt->prop_hash,
1910 /* "<revprops ...>" */
1913 char *revstr = apr_psprintf(pool, "%ld", c->rev_id);
1915 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
1916 "rev", revstr, SVN_VA_NULL);
1920 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
1921 "txn", c->txn_name, SVN_VA_NULL);
1926 /* "<target ...>" */
1927 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
1928 "path", path, SVN_VA_NULL);
1932 if (!xml && path /* Not a --revprop */)
1933 SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), path));
1935 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
1937 const char *pname = apr_hash_this_key(hi);
1938 svn_string_t *propval = apr_hash_this_val(hi);
1940 SVN_ERR(check_cancel(NULL));
1942 /* Since we're already adding a trailing newline (and possible a
1943 colon and some spaces) anyway, just mimic the output of the
1944 command line client proplist. Compare to 'svnlook propget',
1945 which sends the raw bytes to stdout, untranslated. */
1946 /* We leave printf calls here, since we don't always know the encoding
1947 of the prop value. */
1948 if (svn_prop_needs_translation(pname))
1949 SVN_ERR(svn_subst_detranslate_string(&propval, propval, TRUE, pool));
1954 svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, pool);
1957 const char *pname_stdout;
1958 const char *indented_newval;
1960 SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname,
1962 printf(" %s\n", pname_stdout);
1963 /* Add an extra newline to the value before indenting, so that
1964 every line of output has the indentation whether the value
1965 already ended in a newline or not. */
1967 svn_cmdline__indent_string(apr_psprintf(pool, "%s\n",
1970 printf("%s", indented_newval);
1974 svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "property",
1975 "name", pname, SVN_VA_NULL);
1977 printf(" %s\n", pname);
1985 svn_xml_make_close_tag(&sb, pool, "revprops");
1990 svn_xml_make_close_tag(&sb, pool, "target");
1993 /* "</properties>" */
1994 svn_xml_make_close_tag(&sb, pool, "properties");
1997 if (fputs(sb->data, stdout) == EOF)
1999 if (apr_get_os_error()) /* is errno on POSIX */
2000 return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
2002 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
2006 return SVN_NO_ERROR;
2010 static svn_error_t *
2011 do_tree(svnlook_ctxt_t *c,
2013 svn_boolean_t show_ids,
2014 svn_boolean_t full_paths,
2015 svn_boolean_t recurse,
2018 svn_fs_root_t *root;
2019 const svn_fs_id_t *id;
2020 svn_boolean_t is_dir;
2022 SVN_ERR(get_root(&root, c, pool));
2023 SVN_ERR(svn_fs_node_id(&id, root, path, pool));
2024 SVN_ERR(svn_fs_is_dir(&is_dir, root, path, pool));
2025 SVN_ERR(print_tree(root, path, id, is_dir, 0, show_ids, full_paths,
2027 return SVN_NO_ERROR;
2031 /* Custom filesystem warning function. */
2033 warning_func(void *baton,
2038 svn_handle_error2(err, stderr, FALSE, "svnlook: ");
2042 /* Return an error if the number of arguments (excluding the repository
2043 * argument) is not NUM_ARGS. NUM_ARGS must be 0 or 1. The arguments
2044 * are assumed to be found in OPT_STATE->arg1 and OPT_STATE->arg2. */
2045 static svn_error_t *
2046 check_number_of_args(struct svnlook_opt_state *opt_state,
2049 if ((num_args == 0 && opt_state->arg1 != NULL)
2050 || (num_args == 1 && opt_state->arg2 != NULL))
2051 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2052 _("Too many arguments given"));
2053 if ((num_args == 1 && opt_state->arg1 == NULL))
2054 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2055 _("Missing repository path argument"));
2056 return SVN_NO_ERROR;
2060 /* Factory function for the context baton. */
2061 static svn_error_t *
2062 get_ctxt_baton(svnlook_ctxt_t **baton_p,
2063 struct svnlook_opt_state *opt_state,
2066 svnlook_ctxt_t *baton = apr_pcalloc(pool, sizeof(*baton));
2068 SVN_ERR(svn_repos_open3(&(baton->repos), opt_state->repos_path, NULL,
2070 baton->fs = svn_repos_fs(baton->repos);
2071 svn_fs_set_warning_func(baton->fs, warning_func, NULL);
2072 baton->show_ids = opt_state->show_ids;
2073 baton->limit = opt_state->limit;
2074 baton->no_diff_deleted = opt_state->no_diff_deleted;
2075 baton->no_diff_added = opt_state->no_diff_added;
2076 baton->diff_copy_from = opt_state->diff_copy_from;
2077 baton->full_paths = opt_state->full_paths;
2078 baton->copy_info = opt_state->copy_info;
2079 baton->is_revision = opt_state->txn == NULL;
2080 baton->rev_id = opt_state->rev;
2081 baton->txn_name = apr_pstrdup(pool, opt_state->txn);
2082 baton->diff_options = svn_cstring_split(opt_state->extensions
2083 ? opt_state->extensions : "",
2084 " \t\n\r", TRUE, pool);
2085 baton->ignore_properties = opt_state->ignore_properties;
2086 baton->properties_only = opt_state->properties_only;
2087 baton->diff_cmd = opt_state->diff_cmd;
2089 if (baton->txn_name)
2090 SVN_ERR(svn_fs_open_txn(&(baton->txn), baton->fs,
2091 baton->txn_name, pool));
2092 else if (baton->rev_id == SVN_INVALID_REVNUM)
2093 SVN_ERR(svn_fs_youngest_rev(&(baton->rev_id), baton->fs, pool));
2096 return SVN_NO_ERROR;
2101 /*** Subcommands. ***/
2103 /* This implements `svn_opt_subcommand_t'. */
2104 static svn_error_t *
2105 subcommand_author(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2107 struct svnlook_opt_state *opt_state = baton;
2110 SVN_ERR(check_number_of_args(opt_state, 0));
2112 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2113 SVN_ERR(do_author(c, pool));
2114 return SVN_NO_ERROR;
2117 /* This implements `svn_opt_subcommand_t'. */
2118 static svn_error_t *
2119 subcommand_cat(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2121 struct svnlook_opt_state *opt_state = baton;
2124 SVN_ERR(check_number_of_args(opt_state, 1));
2126 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2127 SVN_ERR(do_cat(c, opt_state->arg1, pool));
2128 return SVN_NO_ERROR;
2131 /* This implements `svn_opt_subcommand_t'. */
2132 static svn_error_t *
2133 subcommand_changed(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2135 struct svnlook_opt_state *opt_state = baton;
2138 SVN_ERR(check_number_of_args(opt_state, 0));
2140 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2141 SVN_ERR(do_changed(c, pool));
2142 return SVN_NO_ERROR;
2145 /* This implements `svn_opt_subcommand_t'. */
2146 static svn_error_t *
2147 subcommand_date(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2149 struct svnlook_opt_state *opt_state = baton;
2152 SVN_ERR(check_number_of_args(opt_state, 0));
2154 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2155 SVN_ERR(do_date(c, pool));
2156 return SVN_NO_ERROR;
2159 /* This implements `svn_opt_subcommand_t'. */
2160 static svn_error_t *
2161 subcommand_diff(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2163 struct svnlook_opt_state *opt_state = baton;
2166 SVN_ERR(check_number_of_args(opt_state, 0));
2168 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2169 SVN_ERR(do_diff(c, pool));
2170 return SVN_NO_ERROR;
2173 /* This implements `svn_opt_subcommand_t'. */
2174 static svn_error_t *
2175 subcommand_dirschanged(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2177 struct svnlook_opt_state *opt_state = baton;
2180 SVN_ERR(check_number_of_args(opt_state, 0));
2182 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2183 SVN_ERR(do_dirs_changed(c, pool));
2184 return SVN_NO_ERROR;
2187 /* This implements `svn_opt_subcommand_t'. */
2188 static svn_error_t *
2189 subcommand_filesize(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2191 struct svnlook_opt_state *opt_state = baton;
2194 SVN_ERR(check_number_of_args(opt_state, 1));
2196 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2197 SVN_ERR(do_filesize(c, opt_state->arg1, pool));
2198 return SVN_NO_ERROR;
2201 /* This implements `svn_opt_subcommand_t'. */
2202 static svn_error_t *
2203 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2205 struct svnlook_opt_state *opt_state = baton;
2206 const char *header =
2207 _("general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
2208 "Subversion repository inspection tool.\n"
2209 "Type 'svnlook help <subcommand>' for help on a specific subcommand.\n"
2210 "Type 'svnlook --version' to see the program version and FS modules.\n"
2211 "Note: any subcommand which takes the '--revision' and '--transaction'\n"
2212 " options will, if invoked without one of those options, act on\n"
2213 " the repository's youngest revision.\n"
2215 "Available subcommands:\n");
2217 const char *fs_desc_start
2218 = _("The following repository back-end (FS) modules are available:\n\n");
2220 svn_stringbuf_t *version_footer;
2222 version_footer = svn_stringbuf_create(fs_desc_start, pool);
2223 SVN_ERR(svn_fs_print_modules(version_footer, pool));
2225 SVN_ERR(svn_opt_print_help4(os, "svnlook",
2226 opt_state ? opt_state->version : FALSE,
2227 opt_state ? opt_state->quiet : FALSE,
2228 opt_state ? opt_state->verbose : FALSE,
2229 version_footer->data,
2230 header, cmd_table, options_table, NULL,
2233 return SVN_NO_ERROR;
2236 /* This implements `svn_opt_subcommand_t'. */
2237 static svn_error_t *
2238 subcommand_history(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2240 struct svnlook_opt_state *opt_state = baton;
2242 const char *path = (opt_state->arg1 ? opt_state->arg1 : "/");
2244 if (opt_state->arg2 != NULL)
2245 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2246 _("Too many arguments given"));
2248 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2249 SVN_ERR(do_history(c, path, pool));
2250 return SVN_NO_ERROR;
2254 /* This implements `svn_opt_subcommand_t'. */
2255 static svn_error_t *
2256 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2258 struct svnlook_opt_state *opt_state = baton;
2262 SVN_ERR(check_number_of_args(opt_state, 1));
2264 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2266 SVN_ERR(svn_fs_get_lock(&lock, c->fs, opt_state->arg1, pool));
2270 const char *cr_date, *exp_date = "";
2271 int comment_lines = 0;
2273 cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
2275 if (lock->expiration_date)
2276 exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
2279 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
2281 SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
2282 SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
2283 SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
2284 SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
2285 SVN_ERR(svn_cmdline_printf(pool,
2286 Q_("Comment (%i line):\n%s\n",
2287 "Comment (%i lines):\n%s\n",
2290 lock->comment ? lock->comment : ""));
2293 return SVN_NO_ERROR;
2297 /* This implements `svn_opt_subcommand_t'. */
2298 static svn_error_t *
2299 subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2301 struct svnlook_opt_state *opt_state = baton;
2304 SVN_ERR(check_number_of_args(opt_state, 0));
2306 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2307 SVN_ERR(do_author(c, pool));
2308 SVN_ERR(do_date(c, pool));
2309 SVN_ERR(do_log(c, TRUE, pool));
2310 return SVN_NO_ERROR;
2313 /* This implements `svn_opt_subcommand_t'. */
2314 static svn_error_t *
2315 subcommand_log(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2317 struct svnlook_opt_state *opt_state = baton;
2320 SVN_ERR(check_number_of_args(opt_state, 0));
2322 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2323 SVN_ERR(do_log(c, FALSE, pool));
2324 return SVN_NO_ERROR;
2327 /* This implements `svn_opt_subcommand_t'. */
2328 static svn_error_t *
2329 subcommand_pget(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2331 struct svnlook_opt_state *opt_state = baton;
2334 if (opt_state->arg1 == NULL)
2336 return svn_error_createf
2337 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2338 opt_state->revprop ? _("Missing propname argument") :
2339 _("Missing propname and repository path arguments"));
2341 else if (!opt_state->revprop && opt_state->arg2 == NULL)
2343 return svn_error_create
2344 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2345 _("Missing propname or repository path argument"));
2347 if ((opt_state->revprop && opt_state->arg2 != NULL)
2348 || os->ind < os->argc)
2349 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2350 _("Too many arguments given"));
2352 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2353 SVN_ERR(do_pget(c, opt_state->arg1,
2354 opt_state->revprop ? NULL : opt_state->arg2,
2355 opt_state->verbose, opt_state->show_inherited_props,
2357 return SVN_NO_ERROR;
2360 /* This implements `svn_opt_subcommand_t'. */
2361 static svn_error_t *
2362 subcommand_plist(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2364 struct svnlook_opt_state *opt_state = baton;
2367 SVN_ERR(check_number_of_args(opt_state, opt_state->revprop ? 0 : 1));
2369 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2370 SVN_ERR(do_plist(c, opt_state->revprop ? NULL : opt_state->arg1,
2371 opt_state->verbose, opt_state->xml,
2372 opt_state->show_inherited_props, pool));
2373 return SVN_NO_ERROR;
2376 /* This implements `svn_opt_subcommand_t'. */
2377 static svn_error_t *
2378 subcommand_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2380 struct svnlook_opt_state *opt_state = baton;
2383 if (opt_state->arg2 != NULL)
2384 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2385 _("Too many arguments given"));
2387 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2388 SVN_ERR(do_tree(c, opt_state->arg1 ? opt_state->arg1 : "",
2389 opt_state->show_ids, opt_state->full_paths,
2390 ! opt_state->non_recursive, pool));
2391 return SVN_NO_ERROR;
2394 /* This implements `svn_opt_subcommand_t'. */
2395 static svn_error_t *
2396 subcommand_youngest(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, 0));
2403 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2404 SVN_ERR(svn_cmdline_printf(pool, "%ld%s", c->rev_id,
2405 opt_state->no_newline ? "" : "\n"));
2406 return SVN_NO_ERROR;
2409 /* This implements `svn_opt_subcommand_t'. */
2410 static svn_error_t *
2411 subcommand_uuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2413 struct svnlook_opt_state *opt_state = baton;
2417 SVN_ERR(check_number_of_args(opt_state, 0));
2419 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2420 SVN_ERR(svn_fs_get_uuid(c->fs, &uuid, pool));
2421 SVN_ERR(svn_cmdline_printf(pool, "%s\n", uuid));
2422 return SVN_NO_ERROR;
2430 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
2431 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
2432 * return SVN_NO_ERROR.
2434 static svn_error_t *
2435 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
2438 apr_status_t apr_err;
2440 const svn_opt_subcommand_desc2_t *subcommand = NULL;
2441 struct svnlook_opt_state opt_state;
2444 apr_array_header_t *received_opts;
2447 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
2449 /* Check library versions */
2450 SVN_ERR(check_lib_versions());
2452 /* Initialize the FS library. */
2453 SVN_ERR(svn_fs_initialize(pool));
2457 SVN_ERR(subcommand_help(NULL, NULL, pool));
2458 *exit_code = EXIT_FAILURE;
2459 return SVN_NO_ERROR;
2462 /* Initialize opt_state. */
2463 memset(&opt_state, 0, sizeof(opt_state));
2464 opt_state.rev = SVN_INVALID_REVNUM;
2465 opt_state.memory_cache_size = svn_cache_config_get()->cache_size;
2467 /* Parse options. */
2468 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
2473 const char *opt_arg;
2475 /* Parse the next option. */
2476 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2477 if (APR_STATUS_IS_EOF(apr_err))
2481 SVN_ERR(subcommand_help(NULL, NULL, pool));
2482 *exit_code = EXIT_FAILURE;
2483 return SVN_NO_ERROR;
2486 /* Stash the option code in an array before parsing it. */
2487 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2493 char *digits_end = NULL;
2494 opt_state.rev = strtol(opt_arg, &digits_end, 10);
2495 if ((! SVN_IS_VALID_REVNUM(opt_state.rev))
2498 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2499 _("Invalid revision number supplied"));
2504 opt_state.txn = opt_arg;
2509 apr_uint64_t sz_val;
2510 SVN_ERR(svn_cstring_atoui64(&sz_val, opt_arg));
2512 opt_state.memory_cache_size = 0x100000 * sz_val;
2517 opt_state.non_recursive = TRUE;
2521 opt_state.verbose = TRUE;
2526 opt_state.help = TRUE;
2530 opt_state.quiet = TRUE;
2533 case svnlook__revprop_opt:
2534 opt_state.revprop = TRUE;
2537 case svnlook__xml_opt:
2538 opt_state.xml = TRUE;
2541 case svnlook__version:
2542 opt_state.version = TRUE;
2545 case svnlook__show_ids:
2546 opt_state.show_ids = TRUE;
2552 opt_state.limit = strtol(opt_arg, &end, 10);
2553 if (end == opt_arg || *end != '\0')
2555 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2556 _("Non-numeric limit argument given"));
2558 if (opt_state.limit <= 0)
2560 return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2561 _("Argument to --limit must be positive"));
2566 case svnlook__no_diff_deleted:
2567 opt_state.no_diff_deleted = TRUE;
2570 case svnlook__no_diff_added:
2571 opt_state.no_diff_added = TRUE;
2574 case svnlook__diff_copy_from:
2575 opt_state.diff_copy_from = TRUE;
2578 case svnlook__full_paths:
2579 opt_state.full_paths = TRUE;
2582 case svnlook__copy_info:
2583 opt_state.copy_info = TRUE;
2587 opt_state.extensions = opt_arg;
2590 case svnlook__ignore_properties:
2591 opt_state.ignore_properties = TRUE;
2594 case svnlook__properties_only:
2595 opt_state.properties_only = TRUE;
2598 case svnlook__diff_cmd:
2599 opt_state.diff_cmd = opt_arg;
2602 case svnlook__show_inherited_props:
2603 opt_state.show_inherited_props = TRUE;
2606 case svnlook__no_newline:
2607 opt_state.no_newline = TRUE;
2611 SVN_ERR(subcommand_help(NULL, NULL, pool));
2612 *exit_code = EXIT_FAILURE;
2613 return SVN_NO_ERROR;
2618 /* The --transaction and --revision options may not co-exist. */
2619 if ((opt_state.rev != SVN_INVALID_REVNUM) && opt_state.txn)
2620 return svn_error_create
2621 (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
2622 _("The '--transaction' (-t) and '--revision' (-r) arguments "
2623 "cannot co-exist"));
2625 /* The --show-inherited-props and --revprop options may not co-exist. */
2626 if (opt_state.show_inherited_props && opt_state.revprop)
2627 return svn_error_create
2628 (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
2629 _("Cannot use the '--show-inherited-props' option with the "
2630 "'--revprop' option"));
2632 /* If the user asked for help, then the rest of the arguments are
2633 the names of subcommands to get help on (if any), or else they're
2634 just typos/mistakes. Whatever the case, the subcommand to
2635 actually run is subcommand_help(). */
2637 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
2639 /* If we're not running the `help' subcommand, then look for a
2640 subcommand in the first argument. */
2641 if (subcommand == NULL)
2643 if (os->ind >= os->argc)
2645 if (opt_state.version)
2647 /* Use the "help" subcommand to handle the "--version" option. */
2648 static const svn_opt_subcommand_desc2_t pseudo_cmd =
2649 { "--version", subcommand_help, {0}, "",
2650 {svnlook__version, /* must accept its own option */
2654 subcommand = &pseudo_cmd;
2659 (svn_cmdline_fprintf(stderr, pool,
2660 _("Subcommand argument required\n")));
2661 SVN_ERR(subcommand_help(NULL, NULL, pool));
2662 *exit_code = EXIT_FAILURE;
2663 return SVN_NO_ERROR;
2668 const char *first_arg;
2670 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg, os->argv[os->ind++],
2672 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
2673 if (subcommand == NULL)
2676 svn_cmdline_fprintf(stderr, pool,
2677 _("Unknown subcommand: '%s'\n"),
2679 SVN_ERR(subcommand_help(NULL, NULL, pool));
2681 /* Be kind to people who try 'svnlook verify'. */
2682 if (strcmp(first_arg, "verify") == 0)
2685 svn_cmdline_fprintf(stderr, pool,
2686 _("Try 'svnadmin verify' instead.\n")));
2689 *exit_code = EXIT_FAILURE;
2690 return SVN_NO_ERROR;
2695 /* If there's a second argument, it's the repository. There may be
2696 more arguments following the repository; usually the next one is
2697 a path within the repository, or it's a propname and the one
2698 after that is the path. Since we don't know, we just call them
2699 arg1 and arg2, meaning the first and second arguments following
2701 if (subcommand->cmd_func != subcommand_help)
2703 const char *repos_path = NULL;
2704 const char *arg1 = NULL, *arg2 = NULL;
2706 /* Get the repository. */
2707 if (os->ind < os->argc)
2709 SVN_ERR(svn_utf_cstring_to_utf8(&repos_path,
2710 os->argv[os->ind++],
2712 repos_path = svn_dirent_internal_style(repos_path, pool);
2715 if (repos_path == NULL)
2718 (svn_cmdline_fprintf(stderr, pool,
2719 _("Repository argument required\n")));
2720 SVN_ERR(subcommand_help(NULL, NULL, pool));
2721 *exit_code = EXIT_FAILURE;
2722 return SVN_NO_ERROR;
2724 else if (svn_path_is_url(repos_path))
2727 (svn_cmdline_fprintf(stderr, pool,
2728 _("'%s' is a URL when it should be a path\n"),
2730 *exit_code = EXIT_FAILURE;
2731 return SVN_NO_ERROR;
2734 opt_state.repos_path = repos_path;
2736 /* Get next arg (arg1), if any. */
2737 if (os->ind < os->argc)
2739 SVN_ERR(svn_utf_cstring_to_utf8(&arg1, os->argv[os->ind++], pool));
2740 arg1 = svn_dirent_internal_style(arg1, pool);
2742 opt_state.arg1 = arg1;
2744 /* Get next arg (arg2), if any. */
2745 if (os->ind < os->argc)
2747 SVN_ERR(svn_utf_cstring_to_utf8(&arg2, os->argv[os->ind++], pool));
2748 arg2 = svn_dirent_internal_style(arg2, pool);
2750 opt_state.arg2 = arg2;
2753 /* Check that the subcommand wasn't passed any inappropriate options. */
2754 for (i = 0; i < received_opts->nelts; i++)
2756 opt_id = APR_ARRAY_IDX(received_opts, i, int);
2758 /* All commands implicitly accept --help, so just skip over this
2759 when we see it. Note that we don't want to include this option
2760 in their "accepted options" list because it would be awfully
2761 redundant to display it in every commands' help text. */
2762 if (opt_id == 'h' || opt_id == '?')
2765 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2768 const apr_getopt_option_t *badopt =
2769 svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
2771 svn_opt_format_option(&optstr, badopt, FALSE, pool);
2772 if (subcommand->name[0] == '-')
2773 SVN_ERR(subcommand_help(NULL, NULL, pool));
2776 (svn_cmdline_fprintf
2778 _("Subcommand '%s' doesn't accept option '%s'\n"
2779 "Type 'svnlook help %s' for usage.\n"),
2780 subcommand->name, optstr, subcommand->name));
2781 *exit_code = EXIT_FAILURE;
2782 return SVN_NO_ERROR;
2786 check_cancel = svn_cmdline__setup_cancellation_handler();
2788 /* Configure FSFS caches for maximum efficiency with svnadmin.
2789 * Also, apply the respective command line parameters, if given. */
2791 svn_cache_config_t settings = *svn_cache_config_get();
2793 settings.cache_size = opt_state.memory_cache_size;
2794 settings.single_threaded = TRUE;
2796 svn_cache_config_set(&settings);
2799 /* Run the subcommand. */
2800 err = (*subcommand->cmd_func)(os, &opt_state, pool);
2803 /* For argument-related problems, suggest using the 'help'
2805 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2806 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2808 err = svn_error_quick_wrap(err,
2809 _("Try 'svnlook help' for more info"));
2814 return SVN_NO_ERROR;
2818 main(int argc, const char *argv[])
2821 int exit_code = EXIT_SUCCESS;
2824 /* Initialize the app. */
2825 if (svn_cmdline_init("svnlook", stderr) != EXIT_SUCCESS)
2826 return EXIT_FAILURE;
2828 /* Create our top-level pool. Use a separate mutexless allocator,
2829 * given this application is single threaded.
2831 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2833 err = sub_main(&exit_code, argc, argv, pool);
2835 /* Flush stdout and report if it fails. It would be flushed on exit anyway
2836 but this makes sure that output is not silently lost if it fails. */
2837 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
2841 exit_code = EXIT_FAILURE;
2842 svn_cmdline_handle_exit_error(err, NULL, "svnlook: ");
2845 svn_pool_destroy(pool);
2847 svn_cmdline__cancellation_exit();