2 * svnlook.c: Subversion server inspection tool main file.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
27 #include <apr_general.h>
28 #include <apr_pools.h>
30 #include <apr_file_io.h>
31 #include <apr_signal.h>
33 #define APR_WANT_STDIO
34 #define APR_WANT_STRFUNC
38 #include "svn_cmdline.h"
39 #include "svn_types.h"
40 #include "svn_pools.h"
41 #include "svn_error.h"
42 #include "svn_error_codes.h"
43 #include "svn_dirent_uri.h"
45 #include "svn_repos.h"
49 #include "svn_subst.h"
50 #include "svn_sorts.h"
52 #include "svn_props.h"
54 #include "svn_version.h"
57 #include "private/svn_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 {"no-newline", svnlook__no_newline, 0,
148 N_("do not output the trailing newline")},
150 {"non-recursive", 'N', 0,
151 N_("operate on single directory only")},
154 N_("specify revision number ARG")},
156 {"revprop", svnlook__revprop_opt, 0,
157 N_("operate on a revision property (use with -r or -t)")},
159 {"show-ids", svnlook__show_ids, 0,
160 N_("show node revision ids for each path")},
162 {"show-inherited-props", svnlook__show_inherited_props, 0,
163 N_("show path's inherited properties")},
165 {"transaction", 't', 1,
166 N_("specify transaction name ARG")},
171 {"version", svnlook__version, 0,
172 N_("show program version information")},
174 {"xml", svnlook__xml_opt, 0,
175 N_("output in XML")},
177 {"extensions", 'x', 1,
178 N_("Specify differencing options for external diff or\n"
180 "internal diff. Default: '-u'. Options are\n"
182 "separated by spaces. Internal diff takes:\n"
184 " -u, --unified: Show 3 lines of unified context\n"
186 " -b, --ignore-space-change: Ignore changes in\n"
188 " amount of white space\n"
190 " -w, --ignore-all-space: Ignore all white space\n"
192 " --ignore-eol-style: Ignore changes in EOL style\n"
194 " -U ARG, --context ARG: Show ARG lines of context\n"
196 " -p, --show-c-function: Show C function name")},
199 N_("no progress (only errors) to stderr")},
205 /* Array of available subcommands.
206 * The entire list must be terminated with an entry of nulls.
208 static const svn_opt_subcommand_desc2_t cmd_table[] =
210 {"author", subcommand_author, {0},
211 N_("usage: svnlook author REPOS_PATH\n\n"
212 "Print the author.\n"),
215 {"cat", subcommand_cat, {0},
216 N_("usage: svnlook cat REPOS_PATH FILE_PATH\n\n"
217 "Print the contents of a file. Leading '/' on FILE_PATH is optional.\n"),
220 {"changed", subcommand_changed, {0},
221 N_("usage: svnlook changed REPOS_PATH\n\n"
222 "Print the paths that were changed.\n"),
223 {'r', 't', svnlook__copy_info} },
225 {"date", subcommand_date, {0},
226 N_("usage: svnlook date REPOS_PATH\n\n"
227 "Print the datestamp.\n"),
230 {"diff", subcommand_diff, {0},
231 N_("usage: svnlook diff REPOS_PATH\n\n"
232 "Print GNU-style diffs of changed files and properties.\n"),
233 {'r', 't', svnlook__no_diff_deleted, svnlook__no_diff_added,
234 svnlook__diff_copy_from, svnlook__diff_cmd, 'x',
235 svnlook__ignore_properties, svnlook__properties_only} },
237 {"dirs-changed", subcommand_dirschanged, {0},
238 N_("usage: svnlook dirs-changed REPOS_PATH\n\n"
239 "Print the directories that were themselves changed (property edits)\n"
240 "or whose file children were changed.\n"),
243 {"filesize", subcommand_filesize, {0},
244 N_("usage: svnlook filesize REPOS_PATH PATH_IN_REPOS\n\n"
245 "Print the size (in bytes) of the file located at PATH_IN_REPOS as\n"
246 "it is represented in the repository.\n"),
249 {"help", subcommand_help, {"?", "h"},
250 N_("usage: svnlook help [SUBCOMMAND...]\n\n"
251 "Describe the usage of this program or its subcommands.\n"),
254 {"history", subcommand_history, {0},
255 N_("usage: svnlook history REPOS_PATH [PATH_IN_REPOS]\n\n"
256 "Print information about the history of a path in the repository (or\n"
257 "the root directory if no path is supplied).\n"),
258 {'r', svnlook__show_ids, 'l'} },
260 {"info", subcommand_info, {0},
261 N_("usage: svnlook info REPOS_PATH\n\n"
262 "Print the author, datestamp, log message size, and log message.\n"),
265 {"lock", subcommand_lock, {0},
266 N_("usage: svnlook lock REPOS_PATH PATH_IN_REPOS\n\n"
267 "If a lock exists on a path in the repository, describe it.\n"),
270 {"log", subcommand_log, {0},
271 N_("usage: svnlook log REPOS_PATH\n\n"
272 "Print the log message.\n"),
275 {"propget", subcommand_pget, {"pget", "pg"},
276 N_("usage: 1. svnlook propget REPOS_PATH PROPNAME PATH_IN_REPOS\n"
278 /* The line above is actually needed, so do NOT delete it! */
279 " 2. svnlook propget --revprop REPOS_PATH PROPNAME\n\n"
280 "Print the raw value of a property on a path in the repository.\n"
281 "With --revprop, print the raw value of a revision property.\n"),
282 {'r', 't', 'v', svnlook__revprop_opt, svnlook__show_inherited_props} },
284 {"proplist", subcommand_plist, {"plist", "pl"},
285 N_("usage: 1. svnlook proplist REPOS_PATH PATH_IN_REPOS\n"
287 /* The line above is actually needed, so do NOT delete it! */
288 " 2. svnlook proplist --revprop REPOS_PATH\n\n"
289 "List the properties of a path in the repository, or\n"
290 "with the --revprop option, revision properties.\n"
291 "With -v, show the property values too.\n"),
292 {'r', 't', 'v', svnlook__revprop_opt, svnlook__xml_opt,
293 svnlook__show_inherited_props} },
295 {"tree", subcommand_tree, {0},
296 N_("usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n\n"
297 "Print the tree, starting at PATH_IN_REPOS (if supplied, at the root\n"
298 "of the tree otherwise), optionally showing node revision ids.\n"),
299 {'r', 't', 'N', svnlook__show_ids, svnlook__full_paths} },
301 {"uuid", subcommand_uuid, {0},
302 N_("usage: svnlook uuid REPOS_PATH\n\n"
303 "Print the repository's UUID.\n"),
306 {"youngest", subcommand_youngest, {0},
307 N_("usage: svnlook youngest REPOS_PATH\n\n"
308 "Print the youngest revision number.\n"),
309 {svnlook__no_newline} },
311 { NULL, NULL, {0}, NULL, {0} }
315 /* Baton for passing option/argument state to a subcommand function. */
316 struct svnlook_opt_state
318 const char *repos_path; /* 'arg0' is always the path to the repository. */
319 const char *arg1; /* Usually an fs path, a propname, or NULL. */
320 const char *arg2; /* Usually an fs path or NULL. */
323 svn_boolean_t version; /* --version */
324 svn_boolean_t show_ids; /* --show-ids */
325 apr_size_t limit; /* --limit */
326 svn_boolean_t help; /* --help */
327 svn_boolean_t no_diff_deleted; /* --no-diff-deleted */
328 svn_boolean_t no_diff_added; /* --no-diff-added */
329 svn_boolean_t diff_copy_from; /* --diff-copy-from */
330 svn_boolean_t verbose; /* --verbose */
331 svn_boolean_t revprop; /* --revprop */
332 svn_boolean_t full_paths; /* --full-paths */
333 svn_boolean_t copy_info; /* --copy-info */
334 svn_boolean_t non_recursive; /* --non-recursive */
335 svn_boolean_t xml; /* --xml */
336 const char *extensions; /* diff extension args (UTF-8!) */
337 svn_boolean_t quiet; /* --quiet */
338 svn_boolean_t ignore_properties; /* --ignore_properties */
339 svn_boolean_t properties_only; /* --properties-only */
340 const char *diff_cmd; /* --diff-cmd */
341 svn_boolean_t show_inherited_props; /* --show-inherited-props */
342 svn_boolean_t no_newline; /* --no-newline */
346 typedef struct svnlook_ctxt_t
350 svn_boolean_t is_revision;
351 svn_boolean_t show_ids;
353 svn_boolean_t no_diff_deleted;
354 svn_boolean_t no_diff_added;
355 svn_boolean_t diff_copy_from;
356 svn_boolean_t full_paths;
357 svn_boolean_t copy_info;
360 const char *txn_name /* UTF-8! */;
361 const apr_array_header_t *diff_options;
362 svn_boolean_t ignore_properties;
363 svn_boolean_t properties_only;
364 const char *diff_cmd;
368 /* A flag to see if we've been cancelled by the client or not. */
369 static volatile sig_atomic_t cancelled = FALSE;
372 /*** Helper functions. ***/
374 /* A signal handler to support cancellation. */
376 signal_handler(int signum)
378 apr_signal(signum, SIG_IGN);
382 /* Our cancellation callback. */
384 check_cancel(void *baton)
387 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
393 /* Version compatibility check */
395 check_lib_versions(void)
397 static const svn_version_checklist_t checklist[] =
399 { "svn_subr", svn_subr_version },
400 { "svn_repos", svn_repos_version },
401 { "svn_fs", svn_fs_version },
402 { "svn_delta", svn_delta_version },
403 { "svn_diff", svn_diff_version },
406 SVN_VERSION_DEFINE(my_version);
408 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
412 /* Get revision or transaction property PROP_NAME for the revision or
413 transaction specified in C, allocating in in POOL and placing it in
416 get_property(svn_string_t **prop_value,
418 const char *prop_name,
421 svn_string_t *raw_value;
423 /* Fetch transaction property... */
424 if (! c->is_revision)
425 SVN_ERR(svn_fs_txn_prop(&raw_value, c->txn, prop_name, pool));
427 /* ...or revision property -- it's your call. */
429 SVN_ERR(svn_fs_revision_prop(&raw_value, c->fs, c->rev_id,
432 *prop_value = raw_value;
439 get_root(svn_fs_root_t **root,
443 /* Open up the appropriate root (revision or transaction). */
446 /* If we didn't get a valid revision number, we'll look at the
447 youngest revision. */
448 if (! SVN_IS_VALID_REVNUM(c->rev_id))
449 SVN_ERR(svn_fs_youngest_rev(&(c->rev_id), c->fs, pool));
451 SVN_ERR(svn_fs_revision_root(root, c->fs, c->rev_id, pool));
455 SVN_ERR(svn_fs_txn_root(root, c->txn, pool));
463 /*** Tree Routines ***/
465 /* Generate a generic delta tree. */
467 generate_delta_tree(svn_repos_node_t **tree,
470 svn_revnum_t base_rev,
473 svn_fs_root_t *base_root;
474 const svn_delta_editor_t *editor;
476 apr_pool_t *edit_pool = svn_pool_create(pool);
477 svn_fs_t *fs = svn_repos_fs(repos);
479 /* Get the base root. */
480 SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, pool));
482 /* Request our editor. */
483 SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos,
484 base_root, root, pool, edit_pool));
486 /* Drive our editor. */
487 SVN_ERR(svn_repos_replay2(root, "", SVN_INVALID_REVNUM, TRUE,
488 editor, edit_baton, NULL, NULL, edit_pool));
490 /* Return the tree we just built. */
491 *tree = svn_repos_node_from_baton(edit_baton);
492 svn_pool_destroy(edit_pool);
498 /*** Tree Printing Routines ***/
500 /* Recursively print only directory nodes that either a) have property
501 mods, or b) contains files that have changed, or c) has added or deleted
502 children. NODE is the root node of the tree delta, so every node in it
503 is either changed or is a directory with a changed node somewhere in the
507 print_dirs_changed_tree(svn_repos_node_t *node,
508 const char *path /* UTF-8! */,
511 svn_repos_node_t *tmp_node;
512 svn_boolean_t print_me = FALSE;
513 const char *full_path;
514 apr_pool_t *iterpool;
516 SVN_ERR(check_cancel(NULL));
521 /* Not a directory? We're not interested. */
522 if (node->kind != svn_node_dir)
525 /* Got prop mods? Excellent. */
529 /* Fly through the list of children, checking for modified files. */
530 tmp_node = node->child;
531 while (tmp_node && (! print_me))
533 if ((tmp_node->kind == svn_node_file)
534 || (tmp_node->action == 'A')
535 || (tmp_node->action == 'D'))
539 tmp_node = tmp_node->sibling;
542 /* Print the node if it qualifies. */
545 SVN_ERR(svn_cmdline_printf(pool, "%s/\n", path));
548 /* Return here if the node has no children. */
549 tmp_node = node->child;
553 /* Recursively handle the node's children. */
554 iterpool = svn_pool_create(pool);
557 svn_pool_clear(iterpool);
558 full_path = svn_dirent_join(path, tmp_node->name, iterpool);
559 SVN_ERR(print_dirs_changed_tree(tmp_node, full_path, iterpool));
560 tmp_node = tmp_node->sibling;
562 svn_pool_destroy(iterpool);
568 /* Recursively print all nodes in the tree that have been modified
569 (do not include directories affected only by "bubble-up"). */
571 print_changed_tree(svn_repos_node_t *node,
572 const char *path /* UTF-8! */,
573 svn_boolean_t copy_info,
576 const char *full_path;
577 char status[4] = "_ ";
578 svn_boolean_t print_me = TRUE;
579 apr_pool_t *iterpool;
581 SVN_ERR(check_cancel(NULL));
586 /* Print the node. */
587 if (node->action == 'A')
590 if (copy_info && node->copyfrom_path)
593 else if (node->action == 'D')
595 else if (node->action == 'R')
597 if ((! node->text_mod) && (! node->prop_mod))
607 /* Print this node unless told to skip it. */
610 SVN_ERR(svn_cmdline_printf(pool, "%s %s%s\n",
613 node->kind == svn_node_dir ? "/" : ""));
614 if (copy_info && node->copyfrom_path)
615 /* Remove the leading slash from the copyfrom path for consistency
616 with the rest of the output. */
617 SVN_ERR(svn_cmdline_printf(pool, " (from %s%s:r%ld)\n",
618 (node->copyfrom_path[0] == '/'
619 ? node->copyfrom_path + 1
620 : node->copyfrom_path),
621 (node->kind == svn_node_dir ? "/" : ""),
622 node->copyfrom_rev));
625 /* Return here if the node has no children. */
630 /* Recursively handle the node's children. */
631 iterpool = svn_pool_create(pool);
634 svn_pool_clear(iterpool);
635 full_path = svn_dirent_join(path, node->name, iterpool);
636 SVN_ERR(print_changed_tree(node, full_path, copy_info, iterpool));
637 node = node->sibling;
639 svn_pool_destroy(iterpool);
646 dump_contents(svn_stream_t *stream,
648 const char *path /* UTF-8! */,
652 SVN_ERR(svn_stream_close(stream)); /* leave an empty file */
655 svn_stream_t *contents;
657 /* Grab the contents and copy them into the given stream. */
658 SVN_ERR(svn_fs_file_contents(&contents, root, path, pool));
659 SVN_ERR(svn_stream_copy3(contents, stream, NULL, NULL, pool));
666 /* Prepare temporary files *TMPFILE1 and *TMPFILE2 for diffing
667 PATH1@ROOT1 versus PATH2@ROOT2. If either ROOT1 or ROOT2 is NULL,
668 the temporary file for its path/root will be an empty one.
669 Otherwise, its temporary file will contain the contents of that
670 path/root in the repository.
672 An exception to this is when either path/root has an svn:mime-type
673 property set on it which indicates that the file contains
674 non-textual data -- in this case, the *IS_BINARY flag is set and no
675 temporary files are created.
677 TMPFILE1 and TMPFILE2 will be removed when RESULT_POOL is destroyed.
680 prepare_tmpfiles(const char **tmpfile1,
681 const char **tmpfile2,
682 svn_boolean_t *is_binary,
683 svn_fs_root_t *root1,
685 svn_fs_root_t *root2,
687 apr_pool_t *result_pool,
688 apr_pool_t *scratch_pool)
690 svn_string_t *mimetype;
691 svn_stream_t *stream;
693 /* Init the return values. */
698 assert(path1 && path2);
700 /* Check for binary mimetypes. If either file has a binary
701 mimetype, get outta here. */
704 SVN_ERR(svn_fs_node_prop(&mimetype, root1, path1,
705 SVN_PROP_MIME_TYPE, scratch_pool));
706 if (mimetype && svn_mime_type_is_binary(mimetype->data))
714 SVN_ERR(svn_fs_node_prop(&mimetype, root2, path2,
715 SVN_PROP_MIME_TYPE, scratch_pool));
716 if (mimetype && svn_mime_type_is_binary(mimetype->data))
723 /* Now, prepare the two temporary files, each of which will either
724 be empty, or will have real contents. */
725 SVN_ERR(svn_stream_open_unique(&stream, tmpfile1, NULL,
726 svn_io_file_del_on_pool_cleanup,
727 result_pool, scratch_pool));
728 SVN_ERR(dump_contents(stream, root1, path1, scratch_pool));
730 SVN_ERR(svn_stream_open_unique(&stream, tmpfile2, NULL,
731 svn_io_file_del_on_pool_cleanup,
732 result_pool, scratch_pool));
733 SVN_ERR(dump_contents(stream, root2, path2, scratch_pool));
739 /* Generate a diff label for PATH in ROOT, allocating in POOL.
740 ROOT may be NULL, in which case revision 0 is used. */
742 generate_label(const char **label,
749 const char *name = NULL;
750 svn_revnum_t rev = SVN_INVALID_REVNUM;
754 svn_fs_t *fs = svn_fs_root_fs(root);
755 if (svn_fs_is_revision_root(root))
757 rev = svn_fs_revision_root_revision(root);
758 SVN_ERR(svn_fs_revision_prop(&date, fs, rev,
759 SVN_PROP_REVISION_DATE, pool));
764 name = svn_fs_txn_root_name(root, pool);
765 SVN_ERR(svn_fs_open_txn(&txn, fs, name, pool));
766 SVN_ERR(svn_fs_txn_prop(&date, txn, SVN_PROP_REVISION_DATE, pool));
776 datestr = apr_psprintf(pool, "%.10s %.8s UTC", date->data, date->data + 11);
781 *label = apr_psprintf(pool, "%s\t%s (txn %s)",
782 path, datestr, name);
784 *label = apr_psprintf(pool, "%s\t%s (rev %ld)",
790 /* Helper function to display differences in properties of a file */
792 display_prop_diffs(svn_stream_t *outstream,
793 const char *encoding,
794 const apr_array_header_t *propchanges,
795 apr_hash_t *original_props,
800 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
801 _("%sProperty changes on: %s%s"),
806 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
807 SVN_DIFF__UNDER_STRING APR_EOL_STR));
809 SVN_ERR(check_cancel(NULL));
811 SVN_ERR(svn_diff__display_prop_diffs(
812 outstream, encoding, propchanges, original_props,
813 FALSE /* pretty_print_mergeinfo */,
814 -1 /* context_size */,
815 check_cancel, NULL, pool));
821 /* Recursively print all nodes in the tree that have been modified
822 (do not include directories affected only by "bubble-up"). */
824 print_diff_tree(svn_stream_t *out_stream,
825 const char *encoding,
827 svn_fs_root_t *base_root,
828 svn_repos_node_t *node,
829 const char *path /* UTF-8! */,
830 const char *base_path /* UTF-8! */,
831 const svnlook_ctxt_t *c,
834 const char *orig_path = NULL, *new_path = NULL;
835 svn_boolean_t do_diff = FALSE;
836 svn_boolean_t orig_empty = FALSE;
837 svn_boolean_t is_copy = FALSE;
838 svn_boolean_t binary = FALSE;
839 svn_boolean_t diff_header_printed = FALSE;
840 apr_pool_t *iterpool;
841 svn_stringbuf_t *header;
843 SVN_ERR(check_cancel(NULL));
848 header = svn_stringbuf_create_empty(pool);
850 /* Print copyfrom history for the top node of a copied tree. */
851 if ((SVN_IS_VALID_REVNUM(node->copyfrom_rev))
852 && (node->copyfrom_path != NULL))
854 /* This is ... a copy. */
857 /* Propagate the new base. Copyfrom paths usually start with a
858 slash; we remove it for consistency with the target path.
859 ### Yes, it would be *much* better for something in the path
860 library to be taking care of this! */
861 if (node->copyfrom_path[0] == '/')
862 base_path = apr_pstrdup(pool, node->copyfrom_path + 1);
864 base_path = apr_pstrdup(pool, node->copyfrom_path);
866 svn_stringbuf_appendcstr
868 apr_psprintf(pool, _("Copied: %s (from rev %ld, %s)\n"),
869 path, node->copyfrom_rev, base_path));
871 SVN_ERR(svn_fs_revision_root(&base_root,
872 svn_fs_root_fs(base_root),
873 node->copyfrom_rev, pool));
876 /*** First, we'll just print file content diffs. ***/
877 if (node->kind == svn_node_file)
879 /* Here's the generalized way we do our diffs:
881 - First, we'll check for svn:mime-type properties on the old
882 and new files. If either has such a property, and it
883 represents a binary type, we won't actually be doing a real
886 - Second, dump the contents of the new version of the file
887 into the temporary directory.
889 - Then, dump the contents of the old version of the file into
890 the temporary directory.
892 - Next, we run 'diff', passing the repository paths as the
895 - Finally, we delete the temporary files. */
896 if (node->action == 'R' && node->text_mod)
899 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
900 base_root, base_path, root, path,
903 else if (c->diff_copy_from && node->action == 'A' && is_copy)
908 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
909 base_root, base_path, root, path,
913 else if (! c->no_diff_added && node->action == 'A')
917 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
918 NULL, base_path, root, path,
921 else if (! c->no_diff_deleted && node->action == 'D')
924 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
925 base_root, base_path, NULL, path,
929 /* The header for the copy case has already been created, and we don't
930 want a header here for files with only property modifications. */
932 && (node->action != 'R' || node->text_mod))
934 svn_stringbuf_appendcstr
935 (header, apr_psprintf(pool, "%s: %s\n",
936 ((node->action == 'A') ? _("Added") :
937 ((node->action == 'D') ? _("Deleted") :
938 ((node->action == 'R') ? _("Modified")
944 if (do_diff && (! c->properties_only))
946 svn_stringbuf_appendcstr(header, SVN_DIFF__EQUAL_STRING "\n");
950 svn_stringbuf_appendcstr(header, _("(Binary files differ)\n\n"));
951 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
952 "%s", header->data));
960 const char *outfilename;
961 const char *errfilename;
962 svn_stream_t *stream;
963 svn_stream_t *err_stream;
964 const char **diff_cmd_argv;
967 const char *orig_label;
968 const char *new_label;
970 diff_cmd_argv = NULL;
971 diff_cmd_argc = c->diff_options->nelts;
975 diff_cmd_argv = apr_palloc(pool,
976 diff_cmd_argc * sizeof(char *));
977 for (i = 0; i < diff_cmd_argc; i++)
978 SVN_ERR(svn_utf_cstring_to_utf8(&diff_cmd_argv[i],
979 APR_ARRAY_IDX(c->diff_options, i, const char *),
983 /* Print diff header. */
984 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
985 "%s", header->data));
988 SVN_ERR(generate_label(&orig_label, NULL, path, pool));
990 SVN_ERR(generate_label(&orig_label, base_root,
992 SVN_ERR(generate_label(&new_label, root, path, pool));
994 /* We deal in streams, but svn_io_run_diff2() deals in file
995 handles, so we may need to make temporary files and then
996 copy the contents to our stream. */
997 outfile = svn_stream__aprfile(out_stream);
1001 SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
1002 svn_io_file_del_on_pool_cleanup, pool, pool));
1003 SVN_ERR(svn_stream_for_stderr(&err_stream, pool));
1004 errfile = svn_stream__aprfile(err_stream);
1008 SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
1009 svn_io_file_del_on_pool_cleanup, pool, pool));
1011 SVN_ERR(svn_io_run_diff2(".",
1014 orig_label, new_label,
1015 orig_path, new_path,
1016 &exitcode, outfile, errfile,
1017 c->diff_cmd, pool));
1019 /* Now, open and copy our files to our output streams. */
1022 SVN_ERR(svn_io_file_close(outfile, pool));
1023 SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
1025 SVN_ERR(svn_stream_copy3(stream,
1026 svn_stream_disown(out_stream, pool),
1031 SVN_ERR(svn_io_file_close(errfile, pool));
1032 SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
1034 SVN_ERR(svn_stream_copy3(stream,
1035 svn_stream_disown(err_stream, pool),
1039 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1041 diff_header_printed = TRUE;
1046 svn_diff_file_options_t *opts = svn_diff_file_options_create(pool);
1048 if (c->diff_options)
1049 SVN_ERR(svn_diff_file_options_parse(opts, c->diff_options, pool));
1051 SVN_ERR(svn_diff_file_diff_2(&diff, orig_path,
1052 new_path, opts, pool));
1054 if (svn_diff_contains_diffs(diff))
1056 const char *orig_label, *new_label;
1058 /* Print diff header. */
1059 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1060 "%s", header->data));
1063 SVN_ERR(generate_label(&orig_label, NULL, path, pool));
1065 SVN_ERR(generate_label(&orig_label, base_root,
1067 SVN_ERR(generate_label(&new_label, root, path, pool));
1068 SVN_ERR(svn_diff_file_output_unified4(
1069 out_stream, diff, orig_path, new_path,
1070 orig_label, new_label,
1071 svn_cmdline_output_encoding(pool), NULL,
1072 opts->show_c_function, opts->context_size,
1073 check_cancel, NULL, pool));
1074 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1076 diff_header_printed = TRUE;
1078 else if (! node->prop_mod &&
1079 ((! c->no_diff_added && node->action == 'A') ||
1080 (! c->no_diff_deleted && node->action == 'D')))
1082 /* There was an empty file added or deleted in this revision.
1083 * We can't print a diff, but we can at least print
1084 * a diff header since we know what happened to this file. */
1085 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1086 "%s", header->data));
1092 /*** Now handle property diffs ***/
1093 if ((node->prop_mod) && (node->action != 'D') && (! c->ignore_properties))
1095 apr_hash_t *local_proptable;
1096 apr_hash_t *base_proptable;
1097 apr_array_header_t *propchanges, *props;
1099 SVN_ERR(svn_fs_node_proplist(&local_proptable, root, path, pool));
1100 if (c->diff_copy_from && node->action == 'A' && is_copy)
1101 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1103 else if (node->action == 'A')
1104 base_proptable = apr_hash_make(pool);
1105 else /* node->action == 'R' */
1106 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1108 SVN_ERR(svn_prop_diffs(&propchanges, local_proptable,
1109 base_proptable, pool));
1110 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, pool));
1111 if (props->nelts > 0)
1113 /* We print a diff header for the case when we only have property
1115 if (! diff_header_printed)
1117 const char *orig_label, *new_label;
1119 SVN_ERR(generate_label(&orig_label, base_root, base_path,
1121 SVN_ERR(generate_label(&new_label, root, path, pool));
1123 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1124 "Index: %s\n", path));
1125 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1126 SVN_DIFF__EQUAL_STRING "\n"));
1129 SVN_ERR(svn_diff__unidiff_write_header(
1130 out_stream, encoding, orig_label, new_label, pool));
1132 SVN_ERR(display_prop_diffs(out_stream, encoding,
1133 props, base_proptable, path, pool));
1137 /* Return here if the node has no children. */
1139 return SVN_NO_ERROR;
1141 /* Recursively handle the node's children. */
1142 iterpool = svn_pool_create(pool);
1143 for (node = node->child; node; node = node->sibling)
1145 svn_pool_clear(iterpool);
1147 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node,
1148 svn_dirent_join(path, node->name, iterpool),
1149 svn_dirent_join(base_path, node->name, iterpool),
1152 svn_pool_destroy(iterpool);
1154 return SVN_NO_ERROR;
1158 /* Print a repository directory, maybe recursively, possibly showing
1159 the node revision ids, and optionally using full paths.
1161 ROOT is the revision or transaction root used to build that tree.
1162 PATH and ID are the current path and node revision id being
1163 printed, and INDENTATION the number of spaces to prepent to that
1164 path's printed output. ID may be NULL if SHOW_IDS is FALSE (in
1165 which case, ids won't be printed at all). If RECURSE is TRUE,
1166 then print the tree recursively; otherwise, we'll stop after the
1167 first level (and use INDENTATION to keep track of how deep we are).
1169 Use POOL for all allocations. */
1170 static svn_error_t *
1171 print_tree(svn_fs_root_t *root,
1172 const char *path /* UTF-8! */,
1173 const svn_fs_id_t *id,
1174 svn_boolean_t is_dir,
1176 svn_boolean_t show_ids,
1177 svn_boolean_t full_paths,
1178 svn_boolean_t recurse,
1181 apr_pool_t *subpool;
1182 apr_hash_t *entries;
1185 SVN_ERR(check_cancel(NULL));
1187 /* Print the indentation. */
1191 for (i = 0; i < indentation; i++)
1192 SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
1195 /* ### The path format is inconsistent.. needs fix */
1198 else if (*path == '/')
1199 name = svn_fspath__basename(path, pool);
1201 name = svn_relpath_basename(path, NULL);
1203 if (svn_path_is_empty(name))
1204 name = "/"; /* basename of '/' is "" */
1206 /* Print the node. */
1207 SVN_ERR(svn_cmdline_printf(pool, "%s%s",
1209 is_dir && strcmp(name, "/") ? "/" : ""));
1213 svn_string_t *unparsed_id = NULL;
1215 unparsed_id = svn_fs_unparse_id(id, pool);
1216 SVN_ERR(svn_cmdline_printf(pool, " <%s>",
1221 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1223 /* Return here if PATH is not a directory. */
1225 return SVN_NO_ERROR;
1227 /* Recursively handle the node's children. */
1228 if (recurse || (indentation == 0))
1230 apr_array_header_t *sorted_entries;
1233 SVN_ERR(svn_fs_dir_entries(&entries, root, path, pool));
1234 subpool = svn_pool_create(pool);
1235 sorted_entries = svn_sort__hash(entries,
1236 svn_sort_compare_items_lexically, pool);
1237 for (i = 0; i < sorted_entries->nelts; i++)
1239 svn_sort__item_t item = APR_ARRAY_IDX(sorted_entries, i,
1241 svn_fs_dirent_t *entry = item.value;
1243 svn_pool_clear(subpool);
1244 SVN_ERR(print_tree(root,
1246 ? svn_fspath__join(path, entry->name, pool)
1247 : svn_relpath_join(path, entry->name, pool),
1248 entry->id, (entry->kind == svn_node_dir),
1249 indentation + 1, show_ids, full_paths,
1252 svn_pool_destroy(subpool);
1255 return SVN_NO_ERROR;
1259 /* Set *BASE_REV to the revision on which the target root specified in
1260 C is based, or to SVN_INVALID_REVNUM when C represents "revision
1261 0" (because that revision isn't based on another revision). */
1262 static svn_error_t *
1263 get_base_rev(svn_revnum_t *base_rev, svnlook_ctxt_t *c, apr_pool_t *pool)
1267 *base_rev = c->rev_id - 1;
1271 *base_rev = svn_fs_txn_base_revision(c->txn);
1273 if (! SVN_IS_VALID_REVNUM(*base_rev))
1274 return svn_error_createf
1275 (SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1276 _("Transaction '%s' is not based on a revision; how odd"),
1279 return SVN_NO_ERROR;
1284 /*** Subcommand handlers. ***/
1286 /* Print the revision's log message to stdout, followed by a newline. */
1287 static svn_error_t *
1288 do_log(svnlook_ctxt_t *c, svn_boolean_t print_size, apr_pool_t *pool)
1290 svn_string_t *prop_value;
1291 const char *prop_value_eol, *prop_value_native;
1292 svn_stream_t *stream;
1296 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_LOG, pool));
1297 if (! (prop_value && prop_value->data))
1299 SVN_ERR(svn_cmdline_printf(pool, "%s\n", print_size ? "0" : ""));
1300 return SVN_NO_ERROR;
1303 /* We immitate what svn_cmdline_printf does here, since we need the byte
1304 size of what we are going to print. */
1306 SVN_ERR(svn_subst_translate_cstring2(prop_value->data, &prop_value_eol,
1308 NULL, FALSE, pool));
1310 err = svn_cmdline_cstring_from_utf8(&prop_value_native, prop_value_eol,
1314 svn_error_clear(err);
1315 prop_value_native = svn_cmdline_cstring_from_utf8_fuzzy(prop_value_eol,
1319 len = strlen(prop_value_native);
1322 SVN_ERR(svn_cmdline_printf(pool, "%" APR_SIZE_T_FMT "\n", len));
1324 /* Use a stream to bypass all stdio translations. */
1325 SVN_ERR(svn_cmdline_fflush(stdout));
1326 SVN_ERR(svn_stream_for_stdout(&stream, pool));
1327 SVN_ERR(svn_stream_write(stream, prop_value_native, &len));
1328 SVN_ERR(svn_stream_close(stream));
1330 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1332 return SVN_NO_ERROR;
1336 /* Print the timestamp of the commit (in the revision case) or the
1337 empty string (in the transaction case) to stdout, followed by a
1339 static svn_error_t *
1340 do_date(svnlook_ctxt_t *c, apr_pool_t *pool)
1342 svn_string_t *prop_value;
1344 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_DATE, pool));
1345 if (prop_value && prop_value->data)
1347 /* Convert the date for humans. */
1349 const char *time_utf8;
1351 SVN_ERR(svn_time_from_cstring(&aprtime, prop_value->data, pool));
1353 time_utf8 = svn_time_to_human_cstring(aprtime, pool);
1355 SVN_ERR(svn_cmdline_printf(pool, "%s", time_utf8));
1358 SVN_ERR(svn_cmdline_printf(pool, "\n"));
1359 return SVN_NO_ERROR;
1363 /* Print the author of the commit to stdout, followed by a newline. */
1364 static svn_error_t *
1365 do_author(svnlook_ctxt_t *c, apr_pool_t *pool)
1367 svn_string_t *prop_value;
1369 SVN_ERR(get_property(&prop_value, c,
1370 SVN_PROP_REVISION_AUTHOR, pool));
1371 if (prop_value && prop_value->data)
1372 SVN_ERR(svn_cmdline_printf(pool, "%s", prop_value->data));
1374 SVN_ERR(svn_cmdline_printf(pool, "\n"));
1375 return SVN_NO_ERROR;
1379 /* Print a list of all directories in which files, or directory
1380 properties, have been modified. */
1381 static svn_error_t *
1382 do_dirs_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1384 svn_fs_root_t *root;
1385 svn_revnum_t base_rev_id;
1386 svn_repos_node_t *tree;
1388 SVN_ERR(get_root(&root, c, pool));
1389 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1390 if (base_rev_id == SVN_INVALID_REVNUM)
1391 return SVN_NO_ERROR;
1393 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1395 SVN_ERR(print_dirs_changed_tree(tree, "", pool));
1397 return SVN_NO_ERROR;
1401 /* Set *KIND to PATH's kind, if PATH exists.
1403 * If PATH does not exist, then error; the text of the error depends
1404 * on whether PATH looks like a URL or not.
1406 static svn_error_t *
1407 verify_path(svn_node_kind_t *kind,
1408 svn_fs_root_t *root,
1412 SVN_ERR(svn_fs_check_path(kind, root, path, pool));
1414 if (*kind == svn_node_none)
1416 if (svn_path_is_url(path)) /* check for a common mistake. */
1417 return svn_error_createf
1418 (SVN_ERR_FS_NOT_FOUND, NULL,
1419 _("'%s' is a URL, probably should be a path"), path);
1421 return svn_error_createf
1422 (SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' does not exist"), path);
1425 return SVN_NO_ERROR;
1429 /* Print the size (in bytes) of a file. */
1430 static svn_error_t *
1431 do_filesize(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
1433 svn_fs_root_t *root;
1434 svn_node_kind_t kind;
1435 svn_filesize_t length;
1437 SVN_ERR(get_root(&root, c, pool));
1438 SVN_ERR(verify_path(&kind, root, path, pool));
1440 if (kind != svn_node_file)
1441 return svn_error_createf
1442 (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
1446 SVN_ERR(svn_fs_file_length(&length, root, path, pool));
1447 return svn_cmdline_printf(pool, "%" SVN_FILESIZE_T_FMT "\n", length);
1450 /* Print the contents of the file at PATH in the repository.
1451 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
1452 SVN_ERR_FS_NOT_FILE if PATH exists but is not a file. */
1453 static svn_error_t *
1454 do_cat(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_stream_t *fstream, *stdout_stream;
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_contents(&fstream, root, path, pool));
1470 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1472 return svn_stream_copy3(fstream, svn_stream_disown(stdout_stream, pool),
1473 check_cancel, NULL, pool);
1477 /* Print a list of all paths modified in a format compatible with `svn
1479 static svn_error_t *
1480 do_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1482 svn_fs_root_t *root;
1483 svn_revnum_t base_rev_id;
1484 svn_repos_node_t *tree;
1486 SVN_ERR(get_root(&root, c, pool));
1487 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1488 if (base_rev_id == SVN_INVALID_REVNUM)
1489 return SVN_NO_ERROR;
1491 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1493 SVN_ERR(print_changed_tree(tree, "", c->copy_info, pool));
1495 return SVN_NO_ERROR;
1499 /* Print some diff-y stuff in a TBD way. :-) */
1500 static svn_error_t *
1501 do_diff(svnlook_ctxt_t *c, apr_pool_t *pool)
1503 svn_fs_root_t *root, *base_root;
1504 svn_revnum_t base_rev_id;
1505 svn_repos_node_t *tree;
1507 SVN_ERR(get_root(&root, c, pool));
1508 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1509 if (base_rev_id == SVN_INVALID_REVNUM)
1510 return SVN_NO_ERROR;
1512 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1515 svn_stream_t *out_stream;
1516 const char *encoding = svn_cmdline_output_encoding(pool);
1518 SVN_ERR(svn_fs_revision_root(&base_root, c->fs, base_rev_id, pool));
1520 /* This fflush() might seem odd, but it was added to deal
1521 with this bug report:
1523 http://subversion.tigris.org/servlets/ReadMsg?\
1524 list=dev&msgNo=140782
1526 From: "Steve Hay" <SteveHay{_AT_}planit.com>
1527 To: <dev@subversion.tigris.org>
1528 Subject: svnlook diff output in wrong order when redirected
1529 Date: Fri, 4 Jul 2008 16:34:15 +0100
1530 Message-ID: <1B32FF956ABF414C9BCE5E487A1497E702014F62@\
1531 ukmail02.planit.group>
1533 Adding the fflush() fixed the bug (not everyone could
1534 reproduce it, but those who could confirmed the fix).
1535 Later in the thread, Daniel Shahaf speculated as to
1538 "Because svn_cmdline_printf() uses the standard
1539 'FILE *stdout' to write to stdout, while
1540 svn_stream_for_stdout() uses (through
1541 apr_file_open_stdout()) Windows API's to get a
1542 handle for stdout?" */
1543 SVN_ERR(svn_cmdline_fflush(stdout));
1544 SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1546 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, tree,
1549 return SVN_NO_ERROR;
1554 /* Callback baton for print_history() (and do_history()). */
1555 struct print_history_baton
1558 svn_boolean_t show_ids; /* whether to show node IDs */
1559 apr_size_t limit; /* max number of history items */
1560 apr_size_t count; /* number of history items processed */
1563 /* Implements svn_repos_history_func_t interface. Print the history
1564 that's reported through this callback, possibly finding and
1565 displaying node-rev-ids. */
1566 static svn_error_t *
1567 print_history(void *baton,
1569 svn_revnum_t revision,
1572 struct print_history_baton *phb = baton;
1574 SVN_ERR(check_cancel(NULL));
1578 const svn_fs_id_t *node_id;
1579 svn_fs_root_t *rev_root;
1580 svn_string_t *id_string;
1582 SVN_ERR(svn_fs_revision_root(&rev_root, phb->fs, revision, pool));
1583 SVN_ERR(svn_fs_node_id(&node_id, rev_root, path, pool));
1584 id_string = svn_fs_unparse_id(node_id, pool);
1585 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s <%s>\n",
1586 revision, path, id_string->data));
1590 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s\n", revision, path));
1596 if (phb->count >= phb->limit)
1597 /* Not L10N'd, since this error is suppressed by the caller. */
1598 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL,
1599 _("History item limit reached"));
1602 return SVN_NO_ERROR;
1606 /* Print a tabular display of history location points for PATH in
1607 revision C->rev_id. Optionally, SHOW_IDS. Use POOL for
1609 static svn_error_t *
1610 do_history(svnlook_ctxt_t *c,
1614 struct print_history_baton args;
1618 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH <ID>\n"
1619 "-------- ---------\n")));
1623 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH\n"
1624 "-------- ----\n")));
1627 /* Call our history crawler. We want the whole lifetime of the path
1628 (prior to the user-supplied revision, of course), across all
1631 args.show_ids = c->show_ids;
1632 args.limit = c->limit;
1634 SVN_ERR(svn_repos_history2(c->fs, path, print_history, &args,
1635 NULL, NULL, 0, c->rev_id, TRUE, pool));
1636 return SVN_NO_ERROR;
1640 /* Print the value of property PROPNAME on PATH in the repository.
1642 If VERBOSE, print their values too. If SHOW_INHERITED_PROPS, print
1643 PATH's inherited props too.
1645 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. If
1646 SHOW_INHERITED_PROPS is FALSE,then error with SVN_ERR_PROPERTY_NOT_FOUND
1647 if there is no such property on PATH. If SHOW_INHERITED_PROPS is TRUE,
1648 then error with SVN_ERR_PROPERTY_NOT_FOUND only if there is no such
1649 property on PATH nor inherited by path.
1651 If PATH is NULL, operate on a revision property. */
1652 static svn_error_t *
1653 do_pget(svnlook_ctxt_t *c,
1654 const char *propname,
1656 svn_boolean_t verbose,
1657 svn_boolean_t show_inherited_props,
1660 svn_fs_root_t *root;
1662 svn_node_kind_t kind;
1663 svn_stream_t *stdout_stream;
1665 apr_array_header_t *inherited_props = NULL;
1667 SVN_ERR(get_root(&root, c, pool));
1670 path = svn_fspath__canonicalize(path, pool);
1671 SVN_ERR(verify_path(&kind, root, path, pool));
1672 SVN_ERR(svn_fs_node_prop(&prop, root, path, propname, pool));
1674 if (show_inherited_props)
1676 SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
1677 path, propname, NULL,
1681 else /* --revprop */
1683 SVN_ERR(get_property(&prop, c, propname, pool));
1686 /* Did we find nothing? */
1688 && (!show_inherited_props || inherited_props->nelts == 0))
1690 const char *err_msg;
1693 /* We're operating on a revprop (e.g. c->is_revision). */
1694 if (SVN_IS_VALID_REVNUM(c->rev_id))
1695 err_msg = apr_psprintf(pool,
1696 _("Property '%s' not found on revision %ld"),
1697 propname, c->rev_id);
1699 err_msg = apr_psprintf(pool,
1700 _("Property '%s' not found on transaction %s"),
1701 propname, c->txn_name);
1705 if (SVN_IS_VALID_REVNUM(c->rev_id))
1707 if (show_inherited_props)
1708 err_msg = apr_psprintf(pool,
1709 _("Property '%s' not found on path '%s' "
1710 "or inherited from a parent "
1712 propname, path, c->rev_id);
1714 err_msg = apr_psprintf(pool,
1715 _("Property '%s' not found on path '%s' "
1717 propname, path, c->rev_id);
1721 if (show_inherited_props)
1722 err_msg = apr_psprintf(pool,
1723 _("Property '%s' not found on path '%s' "
1724 "or inherited from a parent "
1725 "in transaction %s"),
1726 propname, path, c->txn_name);
1728 err_msg = apr_psprintf(pool,
1729 _("Property '%s' not found on path '%s' "
1730 "in transaction %s"),
1731 propname, path, c->txn_name);
1734 return svn_error_create(SVN_ERR_PROPERTY_NOT_FOUND, NULL, err_msg);
1737 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1739 if (verbose || show_inherited_props)
1741 if (inherited_props)
1745 for (i = 0; i < inherited_props->nelts; i++)
1747 svn_prop_inherited_item_t *elt =
1748 APR_ARRAY_IDX(inherited_props, i,
1749 svn_prop_inherited_item_t *);
1753 SVN_ERR(svn_stream_printf(stdout_stream, pool,
1754 _("Inherited properties on '%s',\nfrom '%s':\n"),
1755 path, svn_fspath__canonicalize(elt->path_or_url,
1757 SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream,
1763 svn_string_t *propval =
1764 apr_hash_this_val(apr_hash_first(pool, elt->prop_hash));
1766 SVN_ERR(svn_stream_printf(
1767 stdout_stream, pool, "%s - ",
1768 svn_fspath__canonicalize(elt->path_or_url, pool)));
1770 SVN_ERR(svn_stream_write(stdout_stream, propval->data, &len));
1771 /* If we have more than one property to write, then add a newline*/
1772 if (inherited_props->nelts > 1 || prop)
1774 len = strlen(APR_EOL_STR);
1775 SVN_ERR(svn_stream_write(stdout_stream, APR_EOL_STR, &len));
1785 apr_hash_t *hash = apr_hash_make(pool);
1787 svn_hash_sets(hash, propname, prop);
1788 SVN_ERR(svn_stream_printf(stdout_stream, pool,
1789 _("Properties on '%s':\n"), path));
1790 SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, hash,
1795 SVN_ERR(svn_stream_printf(stdout_stream, pool, "%s - ", path));
1797 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1801 else /* Raw single prop output, i.e. non-verbose output with no
1804 /* Unlike the command line client, we don't translate the property
1805 value or print a trailing newline here. We just output the raw
1806 bytes of whatever's in the repository, as svnlook is more likely
1807 to be used for automated inspections. */
1809 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1812 return SVN_NO_ERROR;
1816 /* Print the property names of all properties on PATH in the repository.
1818 If VERBOSE, print their values too. If XML, print as XML rather than as
1819 plain text. If SHOW_INHERITED_PROPS, print PATH's inherited props too.
1821 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist.
1823 If PATH is NULL, operate on a revision properties. */
1824 static svn_error_t *
1825 do_plist(svnlook_ctxt_t *c,
1827 svn_boolean_t verbose,
1829 svn_boolean_t show_inherited_props,
1832 svn_fs_root_t *root;
1834 apr_hash_index_t *hi;
1835 svn_node_kind_t kind;
1836 svn_stringbuf_t *sb = NULL;
1837 svn_boolean_t revprop = FALSE;
1838 apr_array_header_t *inherited_props = NULL;
1842 /* PATH might be the root of the repsository and we accept both
1843 "" and "/". But to avoid the somewhat cryptic output like this:
1845 >svnlook pl repos-path ""
1850 We canonicalize PATH so that is has a leading slash. */
1851 path = svn_fspath__canonicalize(path, pool);
1853 SVN_ERR(get_root(&root, c, pool));
1854 SVN_ERR(verify_path(&kind, root, path, pool));
1855 SVN_ERR(svn_fs_node_proplist(&props, root, path, pool));
1857 if (show_inherited_props)
1858 SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
1859 path, NULL, NULL, NULL,
1862 else if (c->is_revision)
1864 SVN_ERR(svn_fs_revision_proplist(&props, c->fs, c->rev_id, pool));
1869 SVN_ERR(svn_fs_txn_proplist(&props, c->txn, pool));
1875 /* <?xml version="1.0" encoding="UTF-8"?> */
1876 svn_xml_make_header2(&sb, "UTF-8", pool);
1878 /* "<properties>" */
1879 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "properties",
1883 if (inherited_props)
1887 for (i = 0; i < inherited_props->nelts; i++)
1889 svn_prop_inherited_item_t *elt =
1890 APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
1892 /* Canonicalize the inherited parent paths for consistency
1896 svn_xml_make_open_tag(
1897 &sb, pool, svn_xml_normal, "target", "path",
1898 svn_fspath__canonicalize(elt->path_or_url, pool),
1900 SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, elt->prop_hash,
1903 svn_xml_make_close_tag(&sb, pool, "target");
1907 SVN_ERR(svn_cmdline_printf(
1908 pool, _("Inherited properties on '%s',\nfrom '%s':\n"),
1909 path, svn_fspath__canonicalize(elt->path_or_url, pool)));
1910 SVN_ERR(svn_cmdline__print_prop_hash(NULL, elt->prop_hash,
1920 /* "<revprops ...>" */
1923 char *revstr = apr_psprintf(pool, "%ld", c->rev_id);
1925 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
1926 "rev", revstr, SVN_VA_NULL);
1930 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
1931 "txn", c->txn_name, SVN_VA_NULL);
1936 /* "<target ...>" */
1937 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
1938 "path", path, SVN_VA_NULL);
1942 if (!xml && path /* Not a --revprop */)
1943 SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), path));
1945 for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
1947 const char *pname = apr_hash_this_key(hi);
1948 svn_string_t *propval = apr_hash_this_val(hi);
1950 SVN_ERR(check_cancel(NULL));
1952 /* Since we're already adding a trailing newline (and possible a
1953 colon and some spaces) anyway, just mimic the output of the
1954 command line client proplist. Compare to 'svnlook propget',
1955 which sends the raw bytes to stdout, untranslated. */
1956 /* We leave printf calls here, since we don't always know the encoding
1957 of the prop value. */
1958 if (svn_prop_needs_translation(pname))
1959 SVN_ERR(svn_subst_detranslate_string(&propval, propval, TRUE, pool));
1964 svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, pool);
1967 const char *pname_stdout;
1968 const char *indented_newval;
1970 SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname,
1972 printf(" %s\n", pname_stdout);
1973 /* Add an extra newline to the value before indenting, so that
1974 every line of output has the indentation whether the value
1975 already ended in a newline or not. */
1977 svn_cmdline__indent_string(apr_psprintf(pool, "%s\n",
1980 printf("%s", indented_newval);
1984 svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "property",
1985 "name", pname, SVN_VA_NULL);
1987 printf(" %s\n", pname);
1995 svn_xml_make_close_tag(&sb, pool, "revprops");
2000 svn_xml_make_close_tag(&sb, pool, "target");
2003 /* "</properties>" */
2004 svn_xml_make_close_tag(&sb, pool, "properties");
2007 if (fputs(sb->data, stdout) == EOF)
2009 if (apr_get_os_error()) /* is errno on POSIX */
2010 return svn_error_wrap_apr(apr_get_os_error(), _("Write error"));
2012 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
2016 return SVN_NO_ERROR;
2020 static svn_error_t *
2021 do_tree(svnlook_ctxt_t *c,
2023 svn_boolean_t show_ids,
2024 svn_boolean_t full_paths,
2025 svn_boolean_t recurse,
2028 svn_fs_root_t *root;
2029 const svn_fs_id_t *id;
2030 svn_boolean_t is_dir;
2032 SVN_ERR(get_root(&root, c, pool));
2033 SVN_ERR(svn_fs_node_id(&id, root, path, pool));
2034 SVN_ERR(svn_fs_is_dir(&is_dir, root, path, pool));
2035 SVN_ERR(print_tree(root, path, id, is_dir, 0, show_ids, full_paths,
2037 return SVN_NO_ERROR;
2041 /* Custom filesystem warning function. */
2043 warning_func(void *baton,
2048 svn_handle_error2(err, stderr, FALSE, "svnlook: ");
2052 /* Return an error if the number of arguments (excluding the repository
2053 * argument) is not NUM_ARGS. NUM_ARGS must be 0 or 1. The arguments
2054 * are assumed to be found in OPT_STATE->arg1 and OPT_STATE->arg2. */
2055 static svn_error_t *
2056 check_number_of_args(struct svnlook_opt_state *opt_state,
2059 if ((num_args == 0 && opt_state->arg1 != NULL)
2060 || (num_args == 1 && opt_state->arg2 != NULL))
2061 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2062 _("Too many arguments given"));
2063 if ((num_args == 1 && opt_state->arg1 == NULL))
2064 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2065 _("Missing repository path argument"));
2066 return SVN_NO_ERROR;
2070 /* Factory function for the context baton. */
2071 static svn_error_t *
2072 get_ctxt_baton(svnlook_ctxt_t **baton_p,
2073 struct svnlook_opt_state *opt_state,
2076 svnlook_ctxt_t *baton = apr_pcalloc(pool, sizeof(*baton));
2078 SVN_ERR(svn_repos_open3(&(baton->repos), opt_state->repos_path, NULL,
2080 baton->fs = svn_repos_fs(baton->repos);
2081 svn_fs_set_warning_func(baton->fs, warning_func, NULL);
2082 baton->show_ids = opt_state->show_ids;
2083 baton->limit = opt_state->limit;
2084 baton->no_diff_deleted = opt_state->no_diff_deleted;
2085 baton->no_diff_added = opt_state->no_diff_added;
2086 baton->diff_copy_from = opt_state->diff_copy_from;
2087 baton->full_paths = opt_state->full_paths;
2088 baton->copy_info = opt_state->copy_info;
2089 baton->is_revision = opt_state->txn == NULL;
2090 baton->rev_id = opt_state->rev;
2091 baton->txn_name = apr_pstrdup(pool, opt_state->txn);
2092 baton->diff_options = svn_cstring_split(opt_state->extensions
2093 ? opt_state->extensions : "",
2094 " \t\n\r", TRUE, pool);
2095 baton->ignore_properties = opt_state->ignore_properties;
2096 baton->properties_only = opt_state->properties_only;
2097 baton->diff_cmd = opt_state->diff_cmd;
2099 if (baton->txn_name)
2100 SVN_ERR(svn_fs_open_txn(&(baton->txn), baton->fs,
2101 baton->txn_name, pool));
2102 else if (baton->rev_id == SVN_INVALID_REVNUM)
2103 SVN_ERR(svn_fs_youngest_rev(&(baton->rev_id), baton->fs, pool));
2106 return SVN_NO_ERROR;
2111 /*** Subcommands. ***/
2113 /* This implements `svn_opt_subcommand_t'. */
2114 static svn_error_t *
2115 subcommand_author(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2117 struct svnlook_opt_state *opt_state = baton;
2120 SVN_ERR(check_number_of_args(opt_state, 0));
2122 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2123 SVN_ERR(do_author(c, pool));
2124 return SVN_NO_ERROR;
2127 /* This implements `svn_opt_subcommand_t'. */
2128 static svn_error_t *
2129 subcommand_cat(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2131 struct svnlook_opt_state *opt_state = baton;
2134 SVN_ERR(check_number_of_args(opt_state, 1));
2136 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2137 SVN_ERR(do_cat(c, opt_state->arg1, pool));
2138 return SVN_NO_ERROR;
2141 /* This implements `svn_opt_subcommand_t'. */
2142 static svn_error_t *
2143 subcommand_changed(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2145 struct svnlook_opt_state *opt_state = baton;
2148 SVN_ERR(check_number_of_args(opt_state, 0));
2150 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2151 SVN_ERR(do_changed(c, pool));
2152 return SVN_NO_ERROR;
2155 /* This implements `svn_opt_subcommand_t'. */
2156 static svn_error_t *
2157 subcommand_date(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2159 struct svnlook_opt_state *opt_state = baton;
2162 SVN_ERR(check_number_of_args(opt_state, 0));
2164 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2165 SVN_ERR(do_date(c, pool));
2166 return SVN_NO_ERROR;
2169 /* This implements `svn_opt_subcommand_t'. */
2170 static svn_error_t *
2171 subcommand_diff(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2173 struct svnlook_opt_state *opt_state = baton;
2176 SVN_ERR(check_number_of_args(opt_state, 0));
2178 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2179 SVN_ERR(do_diff(c, pool));
2180 return SVN_NO_ERROR;
2183 /* This implements `svn_opt_subcommand_t'. */
2184 static svn_error_t *
2185 subcommand_dirschanged(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2187 struct svnlook_opt_state *opt_state = baton;
2190 SVN_ERR(check_number_of_args(opt_state, 0));
2192 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2193 SVN_ERR(do_dirs_changed(c, pool));
2194 return SVN_NO_ERROR;
2197 /* This implements `svn_opt_subcommand_t'. */
2198 static svn_error_t *
2199 subcommand_filesize(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2201 struct svnlook_opt_state *opt_state = baton;
2204 SVN_ERR(check_number_of_args(opt_state, 1));
2206 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2207 SVN_ERR(do_filesize(c, opt_state->arg1, pool));
2208 return SVN_NO_ERROR;
2211 /* This implements `svn_opt_subcommand_t'. */
2212 static svn_error_t *
2213 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2215 struct svnlook_opt_state *opt_state = baton;
2216 const char *header =
2217 _("general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
2218 "Subversion repository inspection tool.\n"
2219 "Type 'svnlook help <subcommand>' for help on a specific subcommand.\n"
2220 "Type 'svnlook --version' to see the program version and FS modules.\n"
2221 "Note: any subcommand which takes the '--revision' and '--transaction'\n"
2222 " options will, if invoked without one of those options, act on\n"
2223 " the repository's youngest revision.\n"
2225 "Available subcommands:\n");
2227 const char *fs_desc_start
2228 = _("The following repository back-end (FS) modules are available:\n\n");
2230 svn_stringbuf_t *version_footer;
2232 version_footer = svn_stringbuf_create(fs_desc_start, pool);
2233 SVN_ERR(svn_fs_print_modules(version_footer, pool));
2235 SVN_ERR(svn_opt_print_help4(os, "svnlook",
2236 opt_state ? opt_state->version : FALSE,
2237 opt_state ? opt_state->quiet : FALSE,
2238 opt_state ? opt_state->verbose : FALSE,
2239 version_footer->data,
2240 header, cmd_table, options_table, NULL,
2243 return SVN_NO_ERROR;
2246 /* This implements `svn_opt_subcommand_t'. */
2247 static svn_error_t *
2248 subcommand_history(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2250 struct svnlook_opt_state *opt_state = baton;
2252 const char *path = (opt_state->arg1 ? opt_state->arg1 : "/");
2254 if (opt_state->arg2 != NULL)
2255 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2256 _("Too many arguments given"));
2258 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2259 SVN_ERR(do_history(c, path, pool));
2260 return SVN_NO_ERROR;
2264 /* This implements `svn_opt_subcommand_t'. */
2265 static svn_error_t *
2266 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2268 struct svnlook_opt_state *opt_state = baton;
2272 SVN_ERR(check_number_of_args(opt_state, 1));
2274 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2276 SVN_ERR(svn_fs_get_lock(&lock, c->fs, opt_state->arg1, pool));
2280 const char *cr_date, *exp_date = "";
2281 int comment_lines = 0;
2283 cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
2285 if (lock->expiration_date)
2286 exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
2289 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
2291 SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
2292 SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
2293 SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
2294 SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
2295 SVN_ERR(svn_cmdline_printf(pool,
2296 Q_("Comment (%i line):\n%s\n",
2297 "Comment (%i lines):\n%s\n",
2300 lock->comment ? lock->comment : ""));
2303 return SVN_NO_ERROR;
2307 /* This implements `svn_opt_subcommand_t'. */
2308 static svn_error_t *
2309 subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2311 struct svnlook_opt_state *opt_state = baton;
2314 SVN_ERR(check_number_of_args(opt_state, 0));
2316 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2317 SVN_ERR(do_author(c, pool));
2318 SVN_ERR(do_date(c, pool));
2319 SVN_ERR(do_log(c, TRUE, pool));
2320 return SVN_NO_ERROR;
2323 /* This implements `svn_opt_subcommand_t'. */
2324 static svn_error_t *
2325 subcommand_log(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2327 struct svnlook_opt_state *opt_state = baton;
2330 SVN_ERR(check_number_of_args(opt_state, 0));
2332 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2333 SVN_ERR(do_log(c, FALSE, pool));
2334 return SVN_NO_ERROR;
2337 /* This implements `svn_opt_subcommand_t'. */
2338 static svn_error_t *
2339 subcommand_pget(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2341 struct svnlook_opt_state *opt_state = baton;
2344 if (opt_state->arg1 == NULL)
2346 return svn_error_createf
2347 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2348 opt_state->revprop ? _("Missing propname argument") :
2349 _("Missing propname and repository path arguments"));
2351 else if (!opt_state->revprop && opt_state->arg2 == NULL)
2353 return svn_error_create
2354 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2355 _("Missing propname or repository path argument"));
2357 if ((opt_state->revprop && opt_state->arg2 != NULL)
2358 || os->ind < os->argc)
2359 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2360 _("Too many arguments given"));
2362 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2363 SVN_ERR(do_pget(c, opt_state->arg1,
2364 opt_state->revprop ? NULL : opt_state->arg2,
2365 opt_state->verbose, opt_state->show_inherited_props,
2367 return SVN_NO_ERROR;
2370 /* This implements `svn_opt_subcommand_t'. */
2371 static svn_error_t *
2372 subcommand_plist(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2374 struct svnlook_opt_state *opt_state = baton;
2377 SVN_ERR(check_number_of_args(opt_state, opt_state->revprop ? 0 : 1));
2379 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2380 SVN_ERR(do_plist(c, opt_state->revprop ? NULL : opt_state->arg1,
2381 opt_state->verbose, opt_state->xml,
2382 opt_state->show_inherited_props, pool));
2383 return SVN_NO_ERROR;
2386 /* This implements `svn_opt_subcommand_t'. */
2387 static svn_error_t *
2388 subcommand_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2390 struct svnlook_opt_state *opt_state = baton;
2393 if (opt_state->arg2 != NULL)
2394 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2395 _("Too many arguments given"));
2397 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2398 SVN_ERR(do_tree(c, opt_state->arg1 ? opt_state->arg1 : "",
2399 opt_state->show_ids, opt_state->full_paths,
2400 ! opt_state->non_recursive, pool));
2401 return SVN_NO_ERROR;
2404 /* This implements `svn_opt_subcommand_t'. */
2405 static svn_error_t *
2406 subcommand_youngest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2408 struct svnlook_opt_state *opt_state = baton;
2411 SVN_ERR(check_number_of_args(opt_state, 0));
2413 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2414 SVN_ERR(svn_cmdline_printf(pool, "%ld%s", c->rev_id,
2415 opt_state->no_newline ? "" : "\n"));
2416 return SVN_NO_ERROR;
2419 /* This implements `svn_opt_subcommand_t'. */
2420 static svn_error_t *
2421 subcommand_uuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2423 struct svnlook_opt_state *opt_state = baton;
2427 SVN_ERR(check_number_of_args(opt_state, 0));
2429 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2430 SVN_ERR(svn_fs_get_uuid(c->fs, &uuid, pool));
2431 SVN_ERR(svn_cmdline_printf(pool, "%s\n", uuid));
2432 return SVN_NO_ERROR;
2440 * On success, leave *EXIT_CODE untouched and return SVN_NO_ERROR. On error,
2441 * either return an error to be displayed, or set *EXIT_CODE to non-zero and
2442 * return SVN_NO_ERROR.
2444 static svn_error_t *
2445 sub_main(int *exit_code, int argc, const char *argv[], apr_pool_t *pool)
2448 apr_status_t apr_err;
2450 const svn_opt_subcommand_desc2_t *subcommand = NULL;
2451 struct svnlook_opt_state opt_state;
2454 apr_array_header_t *received_opts;
2457 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
2459 /* Check library versions */
2460 SVN_ERR(check_lib_versions());
2462 /* Initialize the FS library. */
2463 SVN_ERR(svn_fs_initialize(pool));
2467 SVN_ERR(subcommand_help(NULL, NULL, pool));
2468 *exit_code = EXIT_FAILURE;
2469 return SVN_NO_ERROR;
2472 /* Initialize opt_state. */
2473 memset(&opt_state, 0, sizeof(opt_state));
2474 opt_state.rev = SVN_INVALID_REVNUM;
2476 /* Parse options. */
2477 SVN_ERR(svn_cmdline__getopt_init(&os, argc, argv, pool));
2482 const char *opt_arg;
2484 /* Parse the next option. */
2485 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2486 if (APR_STATUS_IS_EOF(apr_err))
2490 SVN_ERR(subcommand_help(NULL, NULL, pool));
2491 *exit_code = EXIT_FAILURE;
2492 return SVN_NO_ERROR;
2495 /* Stash the option code in an array before parsing it. */
2496 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2502 char *digits_end = NULL;
2503 opt_state.rev = strtol(opt_arg, &digits_end, 10);
2504 if ((! SVN_IS_VALID_REVNUM(opt_state.rev))
2507 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2508 _("Invalid revision number supplied"));
2513 opt_state.txn = opt_arg;
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 = os->argv[os->ind++];
2669 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
2670 if (subcommand == NULL)
2672 const char *first_arg_utf8;
2673 SVN_ERR(svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
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_utf8, "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 /* Set up our cancellation support. */
2787 apr_signal(SIGINT, signal_handler);
2789 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
2790 apr_signal(SIGBREAK, signal_handler);
2793 apr_signal(SIGHUP, signal_handler);
2796 apr_signal(SIGTERM, signal_handler);
2800 /* Disable SIGPIPE generation for the platforms that have it. */
2801 apr_signal(SIGPIPE, SIG_IGN);
2805 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
2806 * working with large files when compiled against an APR that doesn't have
2807 * large file support will crash the program, which is uncool. */
2808 apr_signal(SIGXFSZ, SIG_IGN);
2811 /* Run the subcommand. */
2812 err = (*subcommand->cmd_func)(os, &opt_state, pool);
2815 /* For argument-related problems, suggest using the 'help'
2817 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2818 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2820 err = svn_error_quick_wrap(err,
2821 _("Try 'svnlook help' for more info"));
2826 return SVN_NO_ERROR;
2830 main(int argc, const char *argv[])
2833 int exit_code = EXIT_SUCCESS;
2836 /* Initialize the app. */
2837 if (svn_cmdline_init("svnlook", stderr) != EXIT_SUCCESS)
2838 return EXIT_FAILURE;
2840 /* Create our top-level pool. Use a separate mutexless allocator,
2841 * given this application is single threaded.
2843 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2845 err = sub_main(&exit_code, argc, argv, pool);
2847 /* Flush stdout and report if it fails. It would be flushed on exit anyway
2848 but this makes sure that output is not silently lost if it fails. */
2849 err = svn_error_compose_create(err, svn_cmdline_fflush(stdout));
2853 exit_code = EXIT_FAILURE;
2854 svn_cmdline_handle_exit_error(err, NULL, "svnlook: ");
2857 svn_pool_destroy(pool);