2 * svnlook.c: Subversion server inspection tool main file.
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
27 #include <apr_general.h>
28 #include <apr_pools.h>
30 #include <apr_file_io.h>
31 #include <apr_signal.h>
33 #define APR_WANT_STDIO
34 #define APR_WANT_STRFUNC
38 #include "svn_cmdline.h"
39 #include "svn_types.h"
40 #include "svn_pools.h"
41 #include "svn_error.h"
42 #include "svn_error_codes.h"
43 #include "svn_dirent_uri.h"
45 #include "svn_repos.h"
49 #include "svn_subst.h"
50 #include "svn_sorts.h"
52 #include "svn_props.h"
54 #include "svn_version.h"
57 #include "private/svn_diff_private.h"
58 #include "private/svn_cmdline_private.h"
59 #include "private/svn_fspath.h"
60 #include "private/svn_io_private.h"
61 #include "private/svn_subr_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
109 * The entire list must be terminated with an entry of nulls.
111 static const apr_getopt_option_t options_table[] =
114 N_("show help on a subcommand")},
116 {"copy-info", svnlook__copy_info, 0,
117 N_("show details for copies")},
119 {"diff-copy-from", svnlook__diff_copy_from, 0,
120 N_("print differences against the copy source")},
122 {"full-paths", svnlook__full_paths, 0,
123 N_("show full paths instead of indenting them")},
126 N_("show help on a subcommand")},
129 N_("maximum number of history entries")},
131 {"no-diff-added", svnlook__no_diff_added, 0,
132 N_("do not print differences for added files")},
134 {"no-diff-deleted", svnlook__no_diff_deleted, 0,
135 N_("do not print differences for deleted files")},
137 {"diff-cmd", svnlook__diff_cmd, 1,
138 N_("use ARG as diff command")},
140 {"ignore-properties", svnlook__ignore_properties, 0,
141 N_("ignore properties during the operation")},
143 {"properties-only", svnlook__properties_only, 0,
144 N_("show only properties during the operation")},
146 {"non-recursive", 'N', 0,
147 N_("operate on single directory only")},
150 N_("specify revision number ARG")},
152 {"revprop", svnlook__revprop_opt, 0,
153 N_("operate on a revision property (use with -r or -t)")},
155 {"show-ids", svnlook__show_ids, 0,
156 N_("show node revision ids for each path")},
158 {"show-inherited-props", svnlook__show_inherited_props, 0,
159 N_("show path's inherited properties")},
161 {"transaction", 't', 1,
162 N_("specify transaction name ARG")},
167 {"version", svnlook__version, 0,
168 N_("show program version information")},
170 {"xml", svnlook__xml_opt, 0,
171 N_("output in XML")},
173 {"extensions", 'x', 1,
174 N_("Specify differencing options for external diff or\n"
176 "internal diff. Default: '-u'. Options are\n"
178 "separated by spaces. Internal diff takes:\n"
180 " -u, --unified: Show 3 lines of unified context\n"
182 " -b, --ignore-space-change: Ignore changes in\n"
184 " amount of white space\n"
186 " -w, --ignore-all-space: Ignore all white space\n"
188 " --ignore-eol-style: Ignore changes in EOL style\n"
190 " -p, --show-c-function: Show C function name")},
193 N_("no progress (only errors) to stderr")},
199 /* Array of available subcommands.
200 * The entire list must be terminated with an entry of nulls.
202 static const svn_opt_subcommand_desc2_t cmd_table[] =
204 {"author", subcommand_author, {0},
205 N_("usage: svnlook author REPOS_PATH\n\n"
206 "Print the author.\n"),
209 {"cat", subcommand_cat, {0},
210 N_("usage: svnlook cat REPOS_PATH FILE_PATH\n\n"
211 "Print the contents of a file. Leading '/' on FILE_PATH is optional.\n"),
214 {"changed", subcommand_changed, {0},
215 N_("usage: svnlook changed REPOS_PATH\n\n"
216 "Print the paths that were changed.\n"),
217 {'r', 't', svnlook__copy_info} },
219 {"date", subcommand_date, {0},
220 N_("usage: svnlook date REPOS_PATH\n\n"
221 "Print the datestamp.\n"),
224 {"diff", subcommand_diff, {0},
225 N_("usage: svnlook diff REPOS_PATH\n\n"
226 "Print GNU-style diffs of changed files and properties.\n"),
227 {'r', 't', svnlook__no_diff_deleted, svnlook__no_diff_added,
228 svnlook__diff_copy_from, svnlook__diff_cmd, 'x',
229 svnlook__ignore_properties, svnlook__properties_only} },
231 {"dirs-changed", subcommand_dirschanged, {0},
232 N_("usage: svnlook dirs-changed REPOS_PATH\n\n"
233 "Print the directories that were themselves changed (property edits)\n"
234 "or whose file children were changed.\n"),
237 {"filesize", subcommand_filesize, {0},
238 N_("usage: svnlook filesize REPOS_PATH PATH_IN_REPOS\n\n"
239 "Print the size (in bytes) of the file located at PATH_IN_REPOS as\n"
240 "it is represented in the repository.\n"),
243 {"help", subcommand_help, {"?", "h"},
244 N_("usage: svnlook help [SUBCOMMAND...]\n\n"
245 "Describe the usage of this program or its subcommands.\n"),
248 {"history", subcommand_history, {0},
249 N_("usage: svnlook history REPOS_PATH [PATH_IN_REPOS]\n\n"
250 "Print information about the history of a path in the repository (or\n"
251 "the root directory if no path is supplied).\n"),
252 {'r', svnlook__show_ids, 'l'} },
254 {"info", subcommand_info, {0},
255 N_("usage: svnlook info REPOS_PATH\n\n"
256 "Print the author, datestamp, log message size, and log message.\n"),
259 {"lock", subcommand_lock, {0},
260 N_("usage: svnlook lock REPOS_PATH PATH_IN_REPOS\n\n"
261 "If a lock exists on a path in the repository, describe it.\n"),
264 {"log", subcommand_log, {0},
265 N_("usage: svnlook log REPOS_PATH\n\n"
266 "Print the log message.\n"),
269 {"propget", subcommand_pget, {"pget", "pg"},
270 N_("usage: 1. svnlook propget REPOS_PATH PROPNAME PATH_IN_REPOS\n"
272 /* The line above is actually needed, so do NOT delete it! */
273 " 2. svnlook propget --revprop REPOS_PATH PROPNAME\n\n"
274 "Print the raw value of a property on a path in the repository.\n"
275 "With --revprop, print the raw value of a revision property.\n"),
276 {'r', 't', 'v', svnlook__revprop_opt, svnlook__show_inherited_props} },
278 {"proplist", subcommand_plist, {"plist", "pl"},
279 N_("usage: 1. svnlook proplist REPOS_PATH PATH_IN_REPOS\n"
281 /* The line above is actually needed, so do NOT delete it! */
282 " 2. svnlook proplist --revprop REPOS_PATH\n\n"
283 "List the properties of a path in the repository, or\n"
284 "with the --revprop option, revision properties.\n"
285 "With -v, show the property values too.\n"),
286 {'r', 't', 'v', svnlook__revprop_opt, svnlook__xml_opt,
287 svnlook__show_inherited_props} },
289 {"tree", subcommand_tree, {0},
290 N_("usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n\n"
291 "Print the tree, starting at PATH_IN_REPOS (if supplied, at the root\n"
292 "of the tree otherwise), optionally showing node revision ids.\n"),
293 {'r', 't', 'N', svnlook__show_ids, svnlook__full_paths} },
295 {"uuid", subcommand_uuid, {0},
296 N_("usage: svnlook uuid REPOS_PATH\n\n"
297 "Print the repository's UUID.\n"),
300 {"youngest", subcommand_youngest, {0},
301 N_("usage: svnlook youngest REPOS_PATH\n\n"
302 "Print the youngest revision number.\n"),
305 { NULL, NULL, {0}, NULL, {0} }
309 /* Baton for passing option/argument state to a subcommand function. */
310 struct svnlook_opt_state
312 const char *repos_path; /* 'arg0' is always the path to the repository. */
313 const char *arg1; /* Usually an fs path, a propname, or NULL. */
314 const char *arg2; /* Usually an fs path or NULL. */
317 svn_boolean_t version; /* --version */
318 svn_boolean_t show_ids; /* --show-ids */
319 apr_size_t limit; /* --limit */
320 svn_boolean_t help; /* --help */
321 svn_boolean_t no_diff_deleted; /* --no-diff-deleted */
322 svn_boolean_t no_diff_added; /* --no-diff-added */
323 svn_boolean_t diff_copy_from; /* --diff-copy-from */
324 svn_boolean_t verbose; /* --verbose */
325 svn_boolean_t revprop; /* --revprop */
326 svn_boolean_t full_paths; /* --full-paths */
327 svn_boolean_t copy_info; /* --copy-info */
328 svn_boolean_t non_recursive; /* --non-recursive */
329 svn_boolean_t xml; /* --xml */
330 const char *extensions; /* diff extension args (UTF-8!) */
331 svn_boolean_t quiet; /* --quiet */
332 svn_boolean_t ignore_properties; /* --ignore_properties */
333 svn_boolean_t properties_only; /* --properties-only */
334 const char *diff_cmd; /* --diff-cmd */
335 svn_boolean_t show_inherited_props; /* --show-inherited-props */
339 typedef struct svnlook_ctxt_t
343 svn_boolean_t is_revision;
344 svn_boolean_t show_ids;
346 svn_boolean_t no_diff_deleted;
347 svn_boolean_t no_diff_added;
348 svn_boolean_t diff_copy_from;
349 svn_boolean_t full_paths;
350 svn_boolean_t copy_info;
353 const char *txn_name /* UTF-8! */;
354 const apr_array_header_t *diff_options;
355 svn_boolean_t ignore_properties;
356 svn_boolean_t properties_only;
357 const char *diff_cmd;
361 /* A flag to see if we've been cancelled by the client or not. */
362 static volatile sig_atomic_t cancelled = FALSE;
365 /*** Helper functions. ***/
367 /* A signal handler to support cancellation. */
369 signal_handler(int signum)
371 apr_signal(signum, SIG_IGN);
375 /* Our cancellation callback. */
377 check_cancel(void *baton)
380 return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
386 /* Version compatibility check */
388 check_lib_versions(void)
390 static const svn_version_checklist_t checklist[] =
392 { "svn_subr", svn_subr_version },
393 { "svn_repos", svn_repos_version },
394 { "svn_fs", svn_fs_version },
395 { "svn_delta", svn_delta_version },
396 { "svn_diff", svn_diff_version },
399 SVN_VERSION_DEFINE(my_version);
401 return svn_ver_check_list2(&my_version, checklist, svn_ver_equal);
405 /* Get revision or transaction property PROP_NAME for the revision or
406 transaction specified in C, allocating in in POOL and placing it in
409 get_property(svn_string_t **prop_value,
411 const char *prop_name,
414 svn_string_t *raw_value;
416 /* Fetch transaction property... */
417 if (! c->is_revision)
418 SVN_ERR(svn_fs_txn_prop(&raw_value, c->txn, prop_name, pool));
420 /* ...or revision property -- it's your call. */
422 SVN_ERR(svn_fs_revision_prop(&raw_value, c->fs, c->rev_id,
425 *prop_value = raw_value;
432 get_root(svn_fs_root_t **root,
436 /* Open up the appropriate root (revision or transaction). */
439 /* If we didn't get a valid revision number, we'll look at the
440 youngest revision. */
441 if (! SVN_IS_VALID_REVNUM(c->rev_id))
442 SVN_ERR(svn_fs_youngest_rev(&(c->rev_id), c->fs, pool));
444 SVN_ERR(svn_fs_revision_root(root, c->fs, c->rev_id, pool));
448 SVN_ERR(svn_fs_txn_root(root, c->txn, pool));
456 /*** Tree Routines ***/
458 /* Generate a generic delta tree. */
460 generate_delta_tree(svn_repos_node_t **tree,
463 svn_revnum_t base_rev,
466 svn_fs_root_t *base_root;
467 const svn_delta_editor_t *editor;
469 apr_pool_t *edit_pool = svn_pool_create(pool);
470 svn_fs_t *fs = svn_repos_fs(repos);
472 /* Get the base root. */
473 SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, pool));
475 /* Request our editor. */
476 SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos,
477 base_root, root, pool, edit_pool));
479 /* Drive our editor. */
480 SVN_ERR(svn_repos_replay2(root, "", SVN_INVALID_REVNUM, TRUE,
481 editor, edit_baton, NULL, NULL, edit_pool));
483 /* Return the tree we just built. */
484 *tree = svn_repos_node_from_baton(edit_baton);
485 svn_pool_destroy(edit_pool);
491 /*** Tree Printing Routines ***/
493 /* Recursively print only directory nodes that either a) have property
494 mods, or b) contains files that have changed, or c) has added or deleted
495 children. NODE is the root node of the tree delta, so every node in it
496 is either changed or is a directory with a changed node somewhere in the
500 print_dirs_changed_tree(svn_repos_node_t *node,
501 const char *path /* UTF-8! */,
504 svn_repos_node_t *tmp_node;
505 svn_boolean_t print_me = FALSE;
506 const char *full_path;
507 apr_pool_t *iterpool;
509 SVN_ERR(check_cancel(NULL));
514 /* Not a directory? We're not interested. */
515 if (node->kind != svn_node_dir)
518 /* Got prop mods? Excellent. */
522 /* Fly through the list of children, checking for modified files. */
523 tmp_node = node->child;
524 while (tmp_node && (! print_me))
526 if ((tmp_node->kind == svn_node_file)
527 || (tmp_node->action == 'A')
528 || (tmp_node->action == 'D'))
532 tmp_node = tmp_node->sibling;
535 /* Print the node if it qualifies. */
538 SVN_ERR(svn_cmdline_printf(pool, "%s/\n", path));
541 /* Return here if the node has no children. */
542 tmp_node = node->child;
546 /* Recursively handle the node's children. */
547 iterpool = svn_pool_create(pool);
550 svn_pool_clear(iterpool);
551 full_path = svn_dirent_join(path, tmp_node->name, iterpool);
552 SVN_ERR(print_dirs_changed_tree(tmp_node, full_path, iterpool));
553 tmp_node = tmp_node->sibling;
555 svn_pool_destroy(iterpool);
561 /* Recursively print all nodes in the tree that have been modified
562 (do not include directories affected only by "bubble-up"). */
564 print_changed_tree(svn_repos_node_t *node,
565 const char *path /* UTF-8! */,
566 svn_boolean_t copy_info,
569 const char *full_path;
570 char status[4] = "_ ";
571 svn_boolean_t print_me = TRUE;
572 apr_pool_t *iterpool;
574 SVN_ERR(check_cancel(NULL));
579 /* Print the node. */
580 if (node->action == 'A')
583 if (copy_info && node->copyfrom_path)
586 else if (node->action == 'D')
588 else if (node->action == 'R')
590 if ((! node->text_mod) && (! node->prop_mod))
600 /* Print this node unless told to skip it. */
603 SVN_ERR(svn_cmdline_printf(pool, "%s %s%s\n",
606 node->kind == svn_node_dir ? "/" : ""));
607 if (copy_info && node->copyfrom_path)
608 /* Remove the leading slash from the copyfrom path for consistency
609 with the rest of the output. */
610 SVN_ERR(svn_cmdline_printf(pool, " (from %s%s:r%ld)\n",
611 (node->copyfrom_path[0] == '/'
612 ? node->copyfrom_path + 1
613 : node->copyfrom_path),
614 (node->kind == svn_node_dir ? "/" : ""),
615 node->copyfrom_rev));
618 /* Return here if the node has no children. */
623 /* Recursively handle the node's children. */
624 iterpool = svn_pool_create(pool);
627 svn_pool_clear(iterpool);
628 full_path = svn_dirent_join(path, node->name, iterpool);
629 SVN_ERR(print_changed_tree(node, full_path, copy_info, iterpool));
630 node = node->sibling;
632 svn_pool_destroy(iterpool);
639 dump_contents(svn_stream_t *stream,
641 const char *path /* UTF-8! */,
645 SVN_ERR(svn_stream_close(stream)); /* leave an empty file */
648 svn_stream_t *contents;
650 /* Grab the contents and copy them into the given stream. */
651 SVN_ERR(svn_fs_file_contents(&contents, root, path, pool));
652 SVN_ERR(svn_stream_copy3(contents, stream, NULL, NULL, pool));
659 /* Prepare temporary files *TMPFILE1 and *TMPFILE2 for diffing
660 PATH1@ROOT1 versus PATH2@ROOT2. If either ROOT1 or ROOT2 is NULL,
661 the temporary file for its path/root will be an empty one.
662 Otherwise, its temporary file will contain the contents of that
663 path/root in the repository.
665 An exception to this is when either path/root has an svn:mime-type
666 property set on it which indicates that the file contains
667 non-textual data -- in this case, the *IS_BINARY flag is set and no
668 temporary files are created.
670 Use POOL for all that allocation goodness. */
672 prepare_tmpfiles(const char **tmpfile1,
673 const char **tmpfile2,
674 svn_boolean_t *is_binary,
675 svn_fs_root_t *root1,
677 svn_fs_root_t *root2,
682 svn_string_t *mimetype;
683 svn_stream_t *stream;
685 /* Init the return values. */
690 assert(path1 && path2);
692 /* Check for binary mimetypes. If either file has a binary
693 mimetype, get outta here. */
696 SVN_ERR(svn_fs_node_prop(&mimetype, root1, path1,
697 SVN_PROP_MIME_TYPE, pool));
698 if (mimetype && svn_mime_type_is_binary(mimetype->data))
706 SVN_ERR(svn_fs_node_prop(&mimetype, root2, path2,
707 SVN_PROP_MIME_TYPE, pool));
708 if (mimetype && svn_mime_type_is_binary(mimetype->data))
715 /* Now, prepare the two temporary files, each of which will either
716 be empty, or will have real contents. */
717 SVN_ERR(svn_stream_open_unique(&stream, tmpfile1,
719 svn_io_file_del_none,
721 SVN_ERR(dump_contents(stream, root1, path1, pool));
723 SVN_ERR(svn_stream_open_unique(&stream, tmpfile2,
725 svn_io_file_del_none,
727 SVN_ERR(dump_contents(stream, root2, path2, pool));
733 /* Generate a diff label for PATH in ROOT, allocating in POOL.
734 ROOT may be NULL, in which case revision 0 is used. */
736 generate_label(const char **label,
743 const char *name = NULL;
744 svn_revnum_t rev = SVN_INVALID_REVNUM;
748 svn_fs_t *fs = svn_fs_root_fs(root);
749 if (svn_fs_is_revision_root(root))
751 rev = svn_fs_revision_root_revision(root);
752 SVN_ERR(svn_fs_revision_prop(&date, fs, rev,
753 SVN_PROP_REVISION_DATE, pool));
758 name = svn_fs_txn_root_name(root, pool);
759 SVN_ERR(svn_fs_open_txn(&txn, fs, name, pool));
760 SVN_ERR(svn_fs_txn_prop(&date, txn, SVN_PROP_REVISION_DATE, pool));
770 datestr = apr_psprintf(pool, "%.10s %.8s UTC", date->data, date->data + 11);
775 *label = apr_psprintf(pool, "%s\t%s (txn %s)",
776 path, datestr, name);
778 *label = apr_psprintf(pool, "%s\t%s (rev %ld)",
784 /* Helper function to display differences in properties of a file */
786 display_prop_diffs(svn_stream_t *outstream,
787 const char *encoding,
788 const apr_array_header_t *propchanges,
789 apr_hash_t *original_props,
794 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
795 _("%sProperty changes on: %s%s"),
800 SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
801 SVN_DIFF__UNDER_STRING APR_EOL_STR));
803 SVN_ERR(check_cancel(NULL));
805 SVN_ERR(svn_diff__display_prop_diffs(
806 outstream, encoding, propchanges, original_props,
807 FALSE /* pretty_print_mergeinfo */, pool));
813 /* Recursively print all nodes in the tree that have been modified
814 (do not include directories affected only by "bubble-up"). */
816 print_diff_tree(svn_stream_t *out_stream,
817 const char *encoding,
819 svn_fs_root_t *base_root,
820 svn_repos_node_t *node,
821 const char *path /* UTF-8! */,
822 const char *base_path /* UTF-8! */,
823 const svnlook_ctxt_t *c,
827 const char *orig_path = NULL, *new_path = NULL;
828 svn_boolean_t do_diff = FALSE;
829 svn_boolean_t orig_empty = FALSE;
830 svn_boolean_t is_copy = FALSE;
831 svn_boolean_t binary = FALSE;
832 svn_boolean_t diff_header_printed = FALSE;
834 svn_stringbuf_t *header;
836 SVN_ERR(check_cancel(NULL));
841 header = svn_stringbuf_create_empty(pool);
843 /* Print copyfrom history for the top node of a copied tree. */
844 if ((SVN_IS_VALID_REVNUM(node->copyfrom_rev))
845 && (node->copyfrom_path != NULL))
847 /* This is ... a copy. */
850 /* Propagate the new base. Copyfrom paths usually start with a
851 slash; we remove it for consistency with the target path.
852 ### Yes, it would be *much* better for something in the path
853 library to be taking care of this! */
854 if (node->copyfrom_path[0] == '/')
855 base_path = apr_pstrdup(pool, node->copyfrom_path + 1);
857 base_path = apr_pstrdup(pool, node->copyfrom_path);
859 svn_stringbuf_appendcstr
861 apr_psprintf(pool, _("Copied: %s (from rev %ld, %s)\n"),
862 path, node->copyfrom_rev, base_path));
864 SVN_ERR(svn_fs_revision_root(&base_root,
865 svn_fs_root_fs(base_root),
866 node->copyfrom_rev, pool));
869 /*** First, we'll just print file content diffs. ***/
870 if (node->kind == svn_node_file)
872 /* Here's the generalized way we do our diffs:
874 - First, we'll check for svn:mime-type properties on the old
875 and new files. If either has such a property, and it
876 represents a binary type, we won't actually be doing a real
879 - Second, dump the contents of the new version of the file
880 into the temporary directory.
882 - Then, dump the contents of the old version of the file into
883 the temporary directory.
885 - Next, we run 'diff', passing the repository paths as the
888 - Finally, we delete the temporary files. */
889 if (node->action == 'R' && node->text_mod)
892 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
893 base_root, base_path, root, path,
896 else if (c->diff_copy_from && node->action == 'A' && is_copy)
901 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
902 base_root, base_path, root, path,
906 else if (! c->no_diff_added && node->action == 'A')
910 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
911 NULL, base_path, root, path,
914 else if (! c->no_diff_deleted && node->action == 'D')
917 SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
918 base_root, base_path, NULL, path,
922 /* The header for the copy case has already been created, and we don't
923 want a header here for files with only property modifications. */
925 && (node->action != 'R' || node->text_mod))
927 svn_stringbuf_appendcstr
928 (header, apr_psprintf(pool, "%s: %s\n",
929 ((node->action == 'A') ? _("Added") :
930 ((node->action == 'D') ? _("Deleted") :
931 ((node->action == 'R') ? _("Modified")
937 if (do_diff && (! c->properties_only))
939 svn_stringbuf_appendcstr(header, SVN_DIFF__EQUAL_STRING "\n");
943 svn_stringbuf_appendcstr(header, _("(Binary files differ)\n\n"));
944 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
945 "%s", header->data));
953 const char *outfilename;
954 const char *errfilename;
955 svn_stream_t *stream;
956 svn_stream_t *err_stream;
957 const char **diff_cmd_argv;
960 const char *orig_label;
961 const char *new_label;
963 diff_cmd_argv = NULL;
964 diff_cmd_argc = c->diff_options->nelts;
968 diff_cmd_argv = apr_palloc(pool,
969 diff_cmd_argc * sizeof(char *));
970 for (i = 0; i < diff_cmd_argc; i++)
971 SVN_ERR(svn_utf_cstring_to_utf8(&diff_cmd_argv[i],
972 APR_ARRAY_IDX(c->diff_options, i, const char *),
976 /* Print diff header. */
977 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
978 "%s", header->data));
981 SVN_ERR(generate_label(&orig_label, NULL, path, pool));
983 SVN_ERR(generate_label(&orig_label, base_root,
985 SVN_ERR(generate_label(&new_label, root, path, pool));
987 /* We deal in streams, but svn_io_run_diff2() deals in file
988 handles, so we may need to make temporary files and then
989 copy the contents to our stream. */
990 outfile = svn_stream__aprfile(out_stream);
994 SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
995 svn_io_file_del_on_pool_cleanup, pool, pool));
996 SVN_ERR(svn_stream_for_stderr(&err_stream, pool));
997 errfile = svn_stream__aprfile(err_stream);
1001 SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
1002 svn_io_file_del_on_pool_cleanup, pool, pool));
1004 SVN_ERR(svn_io_run_diff2(".",
1007 orig_label, new_label,
1008 orig_path, new_path,
1009 &exitcode, outfile, errfile,
1010 c->diff_cmd, pool));
1012 /* Now, open and copy our files to our output streams. */
1015 SVN_ERR(svn_io_file_close(outfile, pool));
1016 SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
1018 SVN_ERR(svn_stream_copy3(stream,
1019 svn_stream_disown(out_stream, pool),
1024 SVN_ERR(svn_io_file_close(errfile, pool));
1025 SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
1027 SVN_ERR(svn_stream_copy3(stream,
1028 svn_stream_disown(err_stream, pool),
1032 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1034 diff_header_printed = TRUE;
1039 svn_diff_file_options_t *opts = svn_diff_file_options_create(pool);
1041 if (c->diff_options)
1042 SVN_ERR(svn_diff_file_options_parse(opts, c->diff_options, pool));
1044 SVN_ERR(svn_diff_file_diff_2(&diff, orig_path,
1045 new_path, opts, pool));
1047 if (svn_diff_contains_diffs(diff))
1049 const char *orig_label, *new_label;
1051 /* Print diff header. */
1052 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1053 "%s", header->data));
1056 SVN_ERR(generate_label(&orig_label, NULL, path, pool));
1058 SVN_ERR(generate_label(&orig_label, base_root,
1060 SVN_ERR(generate_label(&new_label, root, path, pool));
1061 SVN_ERR(svn_diff_file_output_unified3
1062 (out_stream, diff, orig_path, new_path,
1063 orig_label, new_label,
1064 svn_cmdline_output_encoding(pool), NULL,
1065 opts->show_c_function, pool));
1066 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1068 diff_header_printed = TRUE;
1070 else if (! node->prop_mod &&
1071 ((! c->no_diff_added && node->action == 'A') ||
1072 (! c->no_diff_deleted && node->action == 'D')))
1074 /* There was an empty file added or deleted in this revision.
1075 * We can't print a diff, but we can at least print
1076 * a diff header since we know what happened to this file. */
1077 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1078 "%s", header->data));
1084 /* Make sure we delete any temporary files. */
1086 SVN_ERR(svn_io_remove_file2(orig_path, FALSE, pool));
1088 SVN_ERR(svn_io_remove_file2(new_path, FALSE, pool));
1090 /*** Now handle property diffs ***/
1091 if ((node->prop_mod) && (node->action != 'D') && (! c->ignore_properties))
1093 apr_hash_t *local_proptable;
1094 apr_hash_t *base_proptable;
1095 apr_array_header_t *propchanges, *props;
1097 SVN_ERR(svn_fs_node_proplist(&local_proptable, root, path, pool));
1098 if (c->diff_copy_from && node->action == 'A' && is_copy)
1099 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1101 else if (node->action == 'A')
1102 base_proptable = apr_hash_make(pool);
1103 else /* node->action == 'R' */
1104 SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
1106 SVN_ERR(svn_prop_diffs(&propchanges, local_proptable,
1107 base_proptable, pool));
1108 SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, pool));
1109 if (props->nelts > 0)
1111 /* We print a diff header for the case when we only have property
1113 if (! diff_header_printed)
1115 const char *orig_label, *new_label;
1117 SVN_ERR(generate_label(&orig_label, base_root, base_path,
1119 SVN_ERR(generate_label(&new_label, root, path, pool));
1121 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1122 "Index: %s\n", path));
1123 SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
1124 SVN_DIFF__EQUAL_STRING "\n"));
1127 SVN_ERR(svn_diff__unidiff_write_header(
1128 out_stream, encoding, orig_label, new_label, pool));
1130 SVN_ERR(display_prop_diffs(out_stream, encoding,
1131 props, base_proptable, path, pool));
1135 /* Return here if the node has no children. */
1138 return SVN_NO_ERROR;
1140 /* Recursively handle the node's children. */
1141 subpool = svn_pool_create(pool);
1142 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node,
1143 svn_dirent_join(path, node->name, subpool),
1144 svn_dirent_join(base_path, node->name, subpool),
1145 c, tmpdir, subpool));
1146 while (node->sibling)
1148 svn_pool_clear(subpool);
1149 node = node->sibling;
1150 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node,
1151 svn_dirent_join(path, node->name, subpool),
1152 svn_dirent_join(base_path, node->name, subpool),
1153 c, tmpdir, subpool));
1155 svn_pool_destroy(subpool);
1157 return SVN_NO_ERROR;
1161 /* Print a repository directory, maybe recursively, possibly showing
1162 the node revision ids, and optionally using full paths.
1164 ROOT is the revision or transaction root used to build that tree.
1165 PATH and ID are the current path and node revision id being
1166 printed, and INDENTATION the number of spaces to prepent to that
1167 path's printed output. ID may be NULL if SHOW_IDS is FALSE (in
1168 which case, ids won't be printed at all). If RECURSE is TRUE,
1169 then print the tree recursively; otherwise, we'll stop after the
1170 first level (and use INDENTATION to keep track of how deep we are).
1172 Use POOL for all allocations. */
1173 static svn_error_t *
1174 print_tree(svn_fs_root_t *root,
1175 const char *path /* UTF-8! */,
1176 const svn_fs_id_t *id,
1177 svn_boolean_t is_dir,
1179 svn_boolean_t show_ids,
1180 svn_boolean_t full_paths,
1181 svn_boolean_t recurse,
1184 apr_pool_t *subpool;
1185 apr_hash_t *entries;
1188 SVN_ERR(check_cancel(NULL));
1190 /* Print the indentation. */
1194 for (i = 0; i < indentation; i++)
1195 SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
1198 /* ### The path format is inconsistent.. needs fix */
1201 else if (*path == '/')
1202 name = svn_fspath__basename(path, pool);
1204 name = svn_relpath_basename(path, NULL);
1206 if (svn_path_is_empty(name))
1207 name = "/"; /* basename of '/' is "" */
1209 /* Print the node. */
1210 SVN_ERR(svn_cmdline_printf(pool, "%s%s",
1212 is_dir && strcmp(name, "/") ? "/" : ""));
1216 svn_string_t *unparsed_id = NULL;
1218 unparsed_id = svn_fs_unparse_id(id, pool);
1219 SVN_ERR(svn_cmdline_printf(pool, " <%s>",
1224 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1226 /* Return here if PATH is not a directory. */
1228 return SVN_NO_ERROR;
1230 /* Recursively handle the node's children. */
1231 if (recurse || (indentation == 0))
1233 apr_array_header_t *sorted_entries;
1236 SVN_ERR(svn_fs_dir_entries(&entries, root, path, pool));
1237 subpool = svn_pool_create(pool);
1238 sorted_entries = svn_sort__hash(entries,
1239 svn_sort_compare_items_lexically, pool);
1240 for (i = 0; i < sorted_entries->nelts; i++)
1242 svn_sort__item_t item = APR_ARRAY_IDX(sorted_entries, i,
1244 svn_fs_dirent_t *entry = item.value;
1246 svn_pool_clear(subpool);
1247 SVN_ERR(print_tree(root,
1249 ? svn_fspath__join(path, entry->name, pool)
1250 : svn_relpath_join(path, entry->name, pool),
1251 entry->id, (entry->kind == svn_node_dir),
1252 indentation + 1, show_ids, full_paths,
1255 svn_pool_destroy(subpool);
1258 return SVN_NO_ERROR;
1262 /* Set *BASE_REV to the revision on which the target root specified in
1263 C is based, or to SVN_INVALID_REVNUM when C represents "revision
1264 0" (because that revision isn't based on another revision). */
1265 static svn_error_t *
1266 get_base_rev(svn_revnum_t *base_rev, svnlook_ctxt_t *c, apr_pool_t *pool)
1270 *base_rev = c->rev_id - 1;
1274 *base_rev = svn_fs_txn_base_revision(c->txn);
1276 if (! SVN_IS_VALID_REVNUM(*base_rev))
1277 return svn_error_createf
1278 (SVN_ERR_FS_NO_SUCH_REVISION, NULL,
1279 _("Transaction '%s' is not based on a revision; how odd"),
1282 return SVN_NO_ERROR;
1287 /*** Subcommand handlers. ***/
1289 /* Print the revision's log message to stdout, followed by a newline. */
1290 static svn_error_t *
1291 do_log(svnlook_ctxt_t *c, svn_boolean_t print_size, apr_pool_t *pool)
1293 svn_string_t *prop_value;
1294 const char *prop_value_eol, *prop_value_native;
1295 svn_stream_t *stream;
1299 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_LOG, pool));
1300 if (! (prop_value && prop_value->data))
1302 SVN_ERR(svn_cmdline_printf(pool, "%s\n", print_size ? "0" : ""));
1303 return SVN_NO_ERROR;
1306 /* We immitate what svn_cmdline_printf does here, since we need the byte
1307 size of what we are going to print. */
1309 SVN_ERR(svn_subst_translate_cstring2(prop_value->data, &prop_value_eol,
1311 NULL, FALSE, pool));
1313 err = svn_cmdline_cstring_from_utf8(&prop_value_native, prop_value_eol,
1317 svn_error_clear(err);
1318 prop_value_native = svn_cmdline_cstring_from_utf8_fuzzy(prop_value_eol,
1322 len = strlen(prop_value_native);
1325 SVN_ERR(svn_cmdline_printf(pool, "%" APR_SIZE_T_FMT "\n", len));
1327 /* Use a stream to bypass all stdio translations. */
1328 SVN_ERR(svn_cmdline_fflush(stdout));
1329 SVN_ERR(svn_stream_for_stdout(&stream, pool));
1330 SVN_ERR(svn_stream_write(stream, prop_value_native, &len));
1331 SVN_ERR(svn_stream_close(stream));
1333 SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
1335 return SVN_NO_ERROR;
1339 /* Print the timestamp of the commit (in the revision case) or the
1340 empty string (in the transaction case) to stdout, followed by a
1342 static svn_error_t *
1343 do_date(svnlook_ctxt_t *c, apr_pool_t *pool)
1345 svn_string_t *prop_value;
1347 SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_DATE, pool));
1348 if (prop_value && prop_value->data)
1350 /* Convert the date for humans. */
1352 const char *time_utf8;
1354 SVN_ERR(svn_time_from_cstring(&aprtime, prop_value->data, pool));
1356 time_utf8 = svn_time_to_human_cstring(aprtime, pool);
1358 SVN_ERR(svn_cmdline_printf(pool, "%s", time_utf8));
1361 SVN_ERR(svn_cmdline_printf(pool, "\n"));
1362 return SVN_NO_ERROR;
1366 /* Print the author of the commit to stdout, followed by a newline. */
1367 static svn_error_t *
1368 do_author(svnlook_ctxt_t *c, apr_pool_t *pool)
1370 svn_string_t *prop_value;
1372 SVN_ERR(get_property(&prop_value, c,
1373 SVN_PROP_REVISION_AUTHOR, pool));
1374 if (prop_value && prop_value->data)
1375 SVN_ERR(svn_cmdline_printf(pool, "%s", prop_value->data));
1377 SVN_ERR(svn_cmdline_printf(pool, "\n"));
1378 return SVN_NO_ERROR;
1382 /* Print a list of all directories in which files, or directory
1383 properties, have been modified. */
1384 static svn_error_t *
1385 do_dirs_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1387 svn_fs_root_t *root;
1388 svn_revnum_t base_rev_id;
1389 svn_repos_node_t *tree;
1391 SVN_ERR(get_root(&root, c, pool));
1392 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1393 if (base_rev_id == SVN_INVALID_REVNUM)
1394 return SVN_NO_ERROR;
1396 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1398 SVN_ERR(print_dirs_changed_tree(tree, "", pool));
1400 return SVN_NO_ERROR;
1404 /* Set *KIND to PATH's kind, if PATH exists.
1406 * If PATH does not exist, then error; the text of the error depends
1407 * on whether PATH looks like a URL or not.
1409 static svn_error_t *
1410 verify_path(svn_node_kind_t *kind,
1411 svn_fs_root_t *root,
1415 SVN_ERR(svn_fs_check_path(kind, root, path, pool));
1417 if (*kind == svn_node_none)
1419 if (svn_path_is_url(path)) /* check for a common mistake. */
1420 return svn_error_createf
1421 (SVN_ERR_FS_NOT_FOUND, NULL,
1422 _("'%s' is a URL, probably should be a path"), path);
1424 return svn_error_createf
1425 (SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' does not exist"), path);
1428 return SVN_NO_ERROR;
1432 /* Print the size (in bytes) of a file. */
1433 static svn_error_t *
1434 do_filesize(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
1436 svn_fs_root_t *root;
1437 svn_node_kind_t kind;
1438 svn_filesize_t length;
1440 SVN_ERR(get_root(&root, c, pool));
1441 SVN_ERR(verify_path(&kind, root, path, pool));
1443 if (kind != svn_node_file)
1444 return svn_error_createf
1445 (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
1449 SVN_ERR(svn_fs_file_length(&length, root, path, pool));
1450 return svn_cmdline_printf(pool, "%" SVN_FILESIZE_T_FMT "\n", length);
1453 /* Print the contents of the file at PATH in the repository.
1454 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
1455 SVN_ERR_FS_NOT_FILE if PATH exists but is not a file. */
1456 static svn_error_t *
1457 do_cat(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
1459 svn_fs_root_t *root;
1460 svn_node_kind_t kind;
1461 svn_stream_t *fstream, *stdout_stream;
1463 SVN_ERR(get_root(&root, c, pool));
1464 SVN_ERR(verify_path(&kind, root, path, pool));
1466 if (kind != svn_node_file)
1467 return svn_error_createf
1468 (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
1472 SVN_ERR(svn_fs_file_contents(&fstream, root, path, pool));
1473 SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
1475 return svn_stream_copy3(fstream, svn_stream_disown(stdout_stream, pool),
1476 check_cancel, NULL, pool);
1480 /* Print a list of all paths modified in a format compatible with `svn
1482 static svn_error_t *
1483 do_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
1485 svn_fs_root_t *root;
1486 svn_revnum_t base_rev_id;
1487 svn_repos_node_t *tree;
1489 SVN_ERR(get_root(&root, c, pool));
1490 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1491 if (base_rev_id == SVN_INVALID_REVNUM)
1492 return SVN_NO_ERROR;
1494 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1496 SVN_ERR(print_changed_tree(tree, "", c->copy_info, pool));
1498 return SVN_NO_ERROR;
1502 /* Print some diff-y stuff in a TBD way. :-) */
1503 static svn_error_t *
1504 do_diff(svnlook_ctxt_t *c, apr_pool_t *pool)
1506 svn_fs_root_t *root, *base_root;
1507 svn_revnum_t base_rev_id;
1508 svn_repos_node_t *tree;
1510 SVN_ERR(get_root(&root, c, pool));
1511 SVN_ERR(get_base_rev(&base_rev_id, c, pool));
1512 if (base_rev_id == SVN_INVALID_REVNUM)
1513 return SVN_NO_ERROR;
1515 SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
1519 svn_stream_t *out_stream;
1520 const char *encoding = svn_cmdline_output_encoding(pool);
1522 SVN_ERR(svn_fs_revision_root(&base_root, c->fs, base_rev_id, pool));
1523 SVN_ERR(svn_io_temp_dir(&tmpdir, pool));
1525 /* This fflush() might seem odd, but it was added to deal
1526 with this bug report:
1528 http://subversion.tigris.org/servlets/ReadMsg?\
1529 list=dev&msgNo=140782
1531 From: "Steve Hay" <SteveHay{_AT_}planit.com>
1532 To: <dev@subversion.tigris.org>
1533 Subject: svnlook diff output in wrong order when redirected
1534 Date: Fri, 4 Jul 2008 16:34:15 +0100
1535 Message-ID: <1B32FF956ABF414C9BCE5E487A1497E702014F62@\
1536 ukmail02.planit.group>
1538 Adding the fflush() fixed the bug (not everyone could
1539 reproduce it, but those who could confirmed the fix).
1540 Later in the thread, Daniel Shahaf speculated as to
1543 "Because svn_cmdline_printf() uses the standard
1544 'FILE *stdout' to write to stdout, while
1545 svn_stream_for_stdout() uses (through
1546 apr_file_open_stdout()) Windows API's to get a
1547 handle for stdout?" */
1548 SVN_ERR(svn_cmdline_fflush(stdout));
1549 SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
1551 SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, tree,
1552 "", "", c, tmpdir, pool));
1554 return SVN_NO_ERROR;
1559 /* Callback baton for print_history() (and do_history()). */
1560 struct print_history_baton
1563 svn_boolean_t show_ids; /* whether to show node IDs */
1564 apr_size_t limit; /* max number of history items */
1565 apr_size_t count; /* number of history items processed */
1568 /* Implements svn_repos_history_func_t interface. Print the history
1569 that's reported through this callback, possibly finding and
1570 displaying node-rev-ids. */
1571 static svn_error_t *
1572 print_history(void *baton,
1574 svn_revnum_t revision,
1577 struct print_history_baton *phb = baton;
1579 SVN_ERR(check_cancel(NULL));
1583 const svn_fs_id_t *node_id;
1584 svn_fs_root_t *rev_root;
1585 svn_string_t *id_string;
1587 SVN_ERR(svn_fs_revision_root(&rev_root, phb->fs, revision, pool));
1588 SVN_ERR(svn_fs_node_id(&node_id, rev_root, path, pool));
1589 id_string = svn_fs_unparse_id(node_id, pool);
1590 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s <%s>\n",
1591 revision, path, id_string->data));
1595 SVN_ERR(svn_cmdline_printf(pool, "%8ld %s\n", revision, path));
1601 if (phb->count >= phb->limit)
1602 /* Not L10N'd, since this error is supressed by the caller. */
1603 return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL,
1604 _("History item limit reached"));
1607 return SVN_NO_ERROR;
1611 /* Print a tabular display of history location points for PATH in
1612 revision C->rev_id. Optionally, SHOW_IDS. Use POOL for
1614 static svn_error_t *
1615 do_history(svnlook_ctxt_t *c,
1619 struct print_history_baton args;
1623 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH <ID>\n"
1624 "-------- ---------\n")));
1628 SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH\n"
1629 "-------- ----\n")));
1632 /* Call our history crawler. We want the whole lifetime of the path
1633 (prior to the user-supplied revision, of course), across all
1636 args.show_ids = c->show_ids;
1637 args.limit = c->limit;
1639 SVN_ERR(svn_repos_history2(c->fs, path, print_history, &args,
1640 NULL, NULL, 0, c->rev_id, TRUE, pool));
1641 return SVN_NO_ERROR;
1645 /* Print the value of property PROPNAME on PATH in the repository.
1647 If VERBOSE, print their values too. If SHOW_INHERITED_PROPS, print
1648 PATH's inherited props too.
1650 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. If
1651 SHOW_INHERITED_PROPS is FALSE,then error with SVN_ERR_PROPERTY_NOT_FOUND
1652 if there is no such property on PATH. If SHOW_INHERITED_PROPS is TRUE,
1653 then error with SVN_ERR_PROPERTY_NOT_FOUND only if there is no such
1654 property on PATH nor inherited by path.
1656 If PATH is NULL, operate on a revision property. */
1657 static svn_error_t *
1658 do_pget(svnlook_ctxt_t *c,
1659 const char *propname,
1661 svn_boolean_t verbose,
1662 svn_boolean_t show_inherited_props,
1665 svn_fs_root_t *root;
1667 svn_node_kind_t kind;
1668 svn_stream_t *stdout_stream;
1670 apr_array_header_t *inherited_props = NULL;
1672 SVN_ERR(get_root(&root, c, pool));
1675 path = svn_fspath__canonicalize(path, pool);
1676 SVN_ERR(verify_path(&kind, root, path, pool));
1677 SVN_ERR(svn_fs_node_prop(&prop, root, path, propname, pool));
1679 if (show_inherited_props)
1681 SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
1682 path, propname, NULL,
1686 else /* --revprop */
1688 SVN_ERR(get_property(&prop, c, propname, pool));
1691 /* Did we find nothing? */
1693 && (!show_inherited_props || inherited_props->nelts == 0))
1695 const char *err_msg;
1698 /* We're operating on a revprop (e.g. c->is_revision). */
1699 err_msg = apr_psprintf(pool,
1700 _("Property '%s' not found on revision %ld"),
1701 propname, c->rev_id);
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 svn__apr_hash_index_val(apr_hash_first(pool,
1767 SVN_ERR(svn_stream_printf(
1768 stdout_stream, pool, "%s - ",
1769 svn_fspath__canonicalize(elt->path_or_url, pool)));
1771 SVN_ERR(svn_stream_write(stdout_stream, propval->data, &len));
1772 /* If we have more than one property to write, then add a newline*/
1773 if (inherited_props->nelts > 1 || prop)
1775 len = strlen(APR_EOL_STR);
1776 SVN_ERR(svn_stream_write(stdout_stream, APR_EOL_STR, &len));
1786 apr_hash_t *hash = apr_hash_make(pool);
1788 svn_hash_sets(hash, propname, prop);
1789 SVN_ERR(svn_stream_printf(stdout_stream, pool,
1790 _("Properties on '%s':\n"), path));
1791 SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, hash,
1796 SVN_ERR(svn_stream_printf(stdout_stream, pool, "%s - ", path));
1798 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1802 else /* Raw single prop output, i.e. non-verbose output with no
1805 /* Unlike the command line client, we don't translate the property
1806 value or print a trailing newline here. We just output the raw
1807 bytes of whatever's in the repository, as svnlook is more likely
1808 to be used for automated inspections. */
1810 SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
1813 return SVN_NO_ERROR;
1817 /* Print the property names of all properties on PATH in the repository.
1819 If VERBOSE, print their values too. If XML, print as XML rather than as
1820 plain text. If SHOW_INHERITED_PROPS, print PATH's inherited props too.
1822 Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist.
1824 If PATH is NULL, operate on a revision properties. */
1825 static svn_error_t *
1826 do_plist(svnlook_ctxt_t *c,
1828 svn_boolean_t verbose,
1830 svn_boolean_t show_inherited_props,
1833 svn_fs_root_t *root;
1835 apr_hash_index_t *hi;
1836 svn_node_kind_t kind;
1837 svn_stringbuf_t *sb = NULL;
1838 svn_boolean_t revprop = FALSE;
1839 apr_array_header_t *inherited_props = NULL;
1843 /* PATH might be the root of the repsository and we accept both
1844 "" and "/". But to avoid the somewhat cryptic output like this:
1846 >svnlook pl repos-path ""
1851 We canonicalize PATH so that is has a leading slash. */
1852 path = svn_fspath__canonicalize(path, pool);
1854 SVN_ERR(get_root(&root, c, pool));
1855 SVN_ERR(verify_path(&kind, root, path, pool));
1856 SVN_ERR(svn_fs_node_proplist(&props, root, path, pool));
1858 if (show_inherited_props)
1859 SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
1860 path, NULL, NULL, NULL,
1863 else if (c->is_revision)
1865 SVN_ERR(svn_fs_revision_proplist(&props, c->fs, c->rev_id, pool));
1870 SVN_ERR(svn_fs_txn_proplist(&props, c->txn, pool));
1876 /* <?xml version="1.0" encoding="UTF-8"?> */
1877 svn_xml_make_header2(&sb, "UTF-8", pool);
1879 /* "<properties>" */
1880 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "properties", NULL);
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, NULL);
1930 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
1931 "txn", c->txn_name, NULL);
1936 /* "<target ...>" */
1937 svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
1938 "path", path, 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 = svn__apr_hash_index_key(hi);
1948 svn_string_t *propval = svn__apr_hash_index_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, 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");
2006 if (fputs(sb->data, stdout) == EOF)
2009 return svn_error_wrap_apr(errno, _("Write error"));
2011 return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
2015 return SVN_NO_ERROR;
2019 static svn_error_t *
2020 do_tree(svnlook_ctxt_t *c,
2022 svn_boolean_t show_ids,
2023 svn_boolean_t full_paths,
2024 svn_boolean_t recurse,
2027 svn_fs_root_t *root;
2028 const svn_fs_id_t *id;
2029 svn_boolean_t is_dir;
2031 SVN_ERR(get_root(&root, c, pool));
2032 SVN_ERR(svn_fs_node_id(&id, root, path, pool));
2033 SVN_ERR(svn_fs_is_dir(&is_dir, root, path, pool));
2034 SVN_ERR(print_tree(root, path, id, is_dir, 0, show_ids, full_paths,
2036 return SVN_NO_ERROR;
2040 /* Custom filesystem warning function. */
2042 warning_func(void *baton,
2047 svn_handle_error2(err, stderr, FALSE, "svnlook: ");
2051 /* Return an error if the number of arguments (excluding the repository
2052 * argument) is not NUM_ARGS. NUM_ARGS must be 0 or 1. The arguments
2053 * are assumed to be found in OPT_STATE->arg1 and OPT_STATE->arg2. */
2054 static svn_error_t *
2055 check_number_of_args(struct svnlook_opt_state *opt_state,
2058 if ((num_args == 0 && opt_state->arg1 != NULL)
2059 || (num_args == 1 && opt_state->arg2 != NULL))
2060 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2061 _("Too many arguments given"));
2062 if ((num_args == 1 && opt_state->arg1 == NULL))
2063 return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2064 _("Missing repository path argument"));
2065 return SVN_NO_ERROR;
2069 /* Factory function for the context baton. */
2070 static svn_error_t *
2071 get_ctxt_baton(svnlook_ctxt_t **baton_p,
2072 struct svnlook_opt_state *opt_state,
2075 svnlook_ctxt_t *baton = apr_pcalloc(pool, sizeof(*baton));
2077 SVN_ERR(svn_repos_open2(&(baton->repos), opt_state->repos_path, NULL,
2079 baton->fs = svn_repos_fs(baton->repos);
2080 svn_fs_set_warning_func(baton->fs, warning_func, NULL);
2081 baton->show_ids = opt_state->show_ids;
2082 baton->limit = opt_state->limit;
2083 baton->no_diff_deleted = opt_state->no_diff_deleted;
2084 baton->no_diff_added = opt_state->no_diff_added;
2085 baton->diff_copy_from = opt_state->diff_copy_from;
2086 baton->full_paths = opt_state->full_paths;
2087 baton->copy_info = opt_state->copy_info;
2088 baton->is_revision = opt_state->txn == NULL;
2089 baton->rev_id = opt_state->rev;
2090 baton->txn_name = apr_pstrdup(pool, opt_state->txn);
2091 baton->diff_options = svn_cstring_split(opt_state->extensions
2092 ? opt_state->extensions : "",
2093 " \t\n\r", TRUE, pool);
2094 baton->ignore_properties = opt_state->ignore_properties;
2095 baton->properties_only = opt_state->properties_only;
2096 baton->diff_cmd = opt_state->diff_cmd;
2098 if (baton->txn_name)
2099 SVN_ERR(svn_fs_open_txn(&(baton->txn), baton->fs,
2100 baton->txn_name, pool));
2101 else if (baton->rev_id == SVN_INVALID_REVNUM)
2102 SVN_ERR(svn_fs_youngest_rev(&(baton->rev_id), baton->fs, pool));
2105 return SVN_NO_ERROR;
2110 /*** Subcommands. ***/
2112 /* This implements `svn_opt_subcommand_t'. */
2113 static svn_error_t *
2114 subcommand_author(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2116 struct svnlook_opt_state *opt_state = baton;
2119 SVN_ERR(check_number_of_args(opt_state, 0));
2121 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2122 SVN_ERR(do_author(c, pool));
2123 return SVN_NO_ERROR;
2126 /* This implements `svn_opt_subcommand_t'. */
2127 static svn_error_t *
2128 subcommand_cat(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2130 struct svnlook_opt_state *opt_state = baton;
2133 SVN_ERR(check_number_of_args(opt_state, 1));
2135 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2136 SVN_ERR(do_cat(c, opt_state->arg1, pool));
2137 return SVN_NO_ERROR;
2140 /* This implements `svn_opt_subcommand_t'. */
2141 static svn_error_t *
2142 subcommand_changed(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2144 struct svnlook_opt_state *opt_state = baton;
2147 SVN_ERR(check_number_of_args(opt_state, 0));
2149 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2150 SVN_ERR(do_changed(c, pool));
2151 return SVN_NO_ERROR;
2154 /* This implements `svn_opt_subcommand_t'. */
2155 static svn_error_t *
2156 subcommand_date(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2158 struct svnlook_opt_state *opt_state = baton;
2161 SVN_ERR(check_number_of_args(opt_state, 0));
2163 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2164 SVN_ERR(do_date(c, pool));
2165 return SVN_NO_ERROR;
2168 /* This implements `svn_opt_subcommand_t'. */
2169 static svn_error_t *
2170 subcommand_diff(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2172 struct svnlook_opt_state *opt_state = baton;
2175 SVN_ERR(check_number_of_args(opt_state, 0));
2177 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2178 SVN_ERR(do_diff(c, pool));
2179 return SVN_NO_ERROR;
2182 /* This implements `svn_opt_subcommand_t'. */
2183 static svn_error_t *
2184 subcommand_dirschanged(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2186 struct svnlook_opt_state *opt_state = baton;
2189 SVN_ERR(check_number_of_args(opt_state, 0));
2191 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2192 SVN_ERR(do_dirs_changed(c, pool));
2193 return SVN_NO_ERROR;
2196 /* This implements `svn_opt_subcommand_t'. */
2197 static svn_error_t *
2198 subcommand_filesize(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2200 struct svnlook_opt_state *opt_state = baton;
2203 SVN_ERR(check_number_of_args(opt_state, 1));
2205 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2206 SVN_ERR(do_filesize(c, opt_state->arg1, pool));
2207 return SVN_NO_ERROR;
2210 /* This implements `svn_opt_subcommand_t'. */
2211 static svn_error_t *
2212 subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2214 struct svnlook_opt_state *opt_state = baton;
2215 const char *header =
2216 _("general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
2217 "Note: any subcommand which takes the '--revision' and '--transaction'\n"
2218 " options will, if invoked without one of those options, act on\n"
2219 " the repository's youngest revision.\n"
2220 "Type 'svnlook help <subcommand>' for help on a specific subcommand.\n"
2221 "Type 'svnlook --version' to see the program version and FS modules.\n"
2223 "Available subcommands:\n");
2225 const char *fs_desc_start
2226 = _("The following repository back-end (FS) modules are available:\n\n");
2228 svn_stringbuf_t *version_footer;
2230 version_footer = svn_stringbuf_create(fs_desc_start, pool);
2231 SVN_ERR(svn_fs_print_modules(version_footer, pool));
2233 SVN_ERR(svn_opt_print_help4(os, "svnlook",
2234 opt_state ? opt_state->version : FALSE,
2235 opt_state ? opt_state->quiet : FALSE,
2236 opt_state ? opt_state->verbose : FALSE,
2237 version_footer->data,
2238 header, cmd_table, options_table, NULL,
2241 return SVN_NO_ERROR;
2244 /* This implements `svn_opt_subcommand_t'. */
2245 static svn_error_t *
2246 subcommand_history(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2248 struct svnlook_opt_state *opt_state = baton;
2250 const char *path = (opt_state->arg1 ? opt_state->arg1 : "/");
2252 if (opt_state->arg2 != NULL)
2253 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2254 _("Too many arguments given"));
2256 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2257 SVN_ERR(do_history(c, path, pool));
2258 return SVN_NO_ERROR;
2262 /* This implements `svn_opt_subcommand_t'. */
2263 static svn_error_t *
2264 subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2266 struct svnlook_opt_state *opt_state = baton;
2270 SVN_ERR(check_number_of_args(opt_state, 1));
2272 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2274 SVN_ERR(svn_fs_get_lock(&lock, c->fs, opt_state->arg1, pool));
2278 const char *cr_date, *exp_date = "";
2279 int comment_lines = 0;
2281 cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
2283 if (lock->expiration_date)
2284 exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
2287 comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
2289 SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
2290 SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
2291 SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
2292 SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
2293 SVN_ERR(svn_cmdline_printf(pool,
2294 Q_("Comment (%i line):\n%s\n",
2295 "Comment (%i lines):\n%s\n",
2298 lock->comment ? lock->comment : ""));
2301 return SVN_NO_ERROR;
2305 /* This implements `svn_opt_subcommand_t'. */
2306 static svn_error_t *
2307 subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2309 struct svnlook_opt_state *opt_state = baton;
2312 SVN_ERR(check_number_of_args(opt_state, 0));
2314 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2315 SVN_ERR(do_author(c, pool));
2316 SVN_ERR(do_date(c, pool));
2317 SVN_ERR(do_log(c, TRUE, pool));
2318 return SVN_NO_ERROR;
2321 /* This implements `svn_opt_subcommand_t'. */
2322 static svn_error_t *
2323 subcommand_log(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2325 struct svnlook_opt_state *opt_state = baton;
2328 SVN_ERR(check_number_of_args(opt_state, 0));
2330 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2331 SVN_ERR(do_log(c, FALSE, pool));
2332 return SVN_NO_ERROR;
2335 /* This implements `svn_opt_subcommand_t'. */
2336 static svn_error_t *
2337 subcommand_pget(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2339 struct svnlook_opt_state *opt_state = baton;
2342 if (opt_state->arg1 == NULL)
2344 return svn_error_createf
2345 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2346 opt_state->revprop ? _("Missing propname argument") :
2347 _("Missing propname and repository path arguments"));
2349 else if (!opt_state->revprop && opt_state->arg2 == NULL)
2351 return svn_error_create
2352 (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
2353 _("Missing propname or repository path argument"));
2355 if ((opt_state->revprop && opt_state->arg2 != NULL)
2356 || os->ind < os->argc)
2357 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2358 _("Too many arguments given"));
2360 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2361 SVN_ERR(do_pget(c, opt_state->arg1,
2362 opt_state->revprop ? NULL : opt_state->arg2,
2363 opt_state->verbose, opt_state->show_inherited_props,
2365 return SVN_NO_ERROR;
2368 /* This implements `svn_opt_subcommand_t'. */
2369 static svn_error_t *
2370 subcommand_plist(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2372 struct svnlook_opt_state *opt_state = baton;
2375 SVN_ERR(check_number_of_args(opt_state, opt_state->revprop ? 0 : 1));
2377 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2378 SVN_ERR(do_plist(c, opt_state->revprop ? NULL : opt_state->arg1,
2379 opt_state->verbose, opt_state->xml,
2380 opt_state->show_inherited_props, pool));
2381 return SVN_NO_ERROR;
2384 /* This implements `svn_opt_subcommand_t'. */
2385 static svn_error_t *
2386 subcommand_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2388 struct svnlook_opt_state *opt_state = baton;
2391 if (opt_state->arg2 != NULL)
2392 return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2393 _("Too many arguments given"));
2395 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2396 SVN_ERR(do_tree(c, opt_state->arg1 ? opt_state->arg1 : "",
2397 opt_state->show_ids, opt_state->full_paths,
2398 ! opt_state->non_recursive, pool));
2399 return SVN_NO_ERROR;
2402 /* This implements `svn_opt_subcommand_t'. */
2403 static svn_error_t *
2404 subcommand_youngest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2406 struct svnlook_opt_state *opt_state = baton;
2409 SVN_ERR(check_number_of_args(opt_state, 0));
2411 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2412 SVN_ERR(svn_cmdline_printf(pool, "%ld\n", c->rev_id));
2413 return SVN_NO_ERROR;
2416 /* This implements `svn_opt_subcommand_t'. */
2417 static svn_error_t *
2418 subcommand_uuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
2420 struct svnlook_opt_state *opt_state = baton;
2424 SVN_ERR(check_number_of_args(opt_state, 0));
2426 SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
2427 SVN_ERR(svn_fs_get_uuid(c->fs, &uuid, pool));
2428 SVN_ERR(svn_cmdline_printf(pool, "%s\n", uuid));
2429 return SVN_NO_ERROR;
2437 main(int argc, const char *argv[])
2440 apr_status_t apr_err;
2443 const svn_opt_subcommand_desc2_t *subcommand = NULL;
2444 struct svnlook_opt_state opt_state;
2447 apr_array_header_t *received_opts;
2450 /* Initialize the app. */
2451 if (svn_cmdline_init("svnlook", stderr) != EXIT_SUCCESS)
2452 return EXIT_FAILURE;
2454 /* Create our top-level pool. Use a separate mutexless allocator,
2455 * given this application is single threaded.
2457 pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
2459 received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
2461 /* Check library versions */
2462 err = check_lib_versions();
2464 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2466 /* Initialize the FS library. */
2467 err = svn_fs_initialize(pool);
2469 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2473 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2474 svn_pool_destroy(pool);
2475 return EXIT_FAILURE;
2478 /* Initialize opt_state. */
2479 memset(&opt_state, 0, sizeof(opt_state));
2480 opt_state.rev = SVN_INVALID_REVNUM;
2482 /* Parse options. */
2483 err = svn_cmdline__getopt_init(&os, argc, argv, pool);
2485 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2490 const char *opt_arg;
2492 /* Parse the next option. */
2493 apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
2494 if (APR_STATUS_IS_EOF(apr_err))
2498 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2499 svn_pool_destroy(pool);
2500 return EXIT_FAILURE;
2503 /* Stash the option code in an array before parsing it. */
2504 APR_ARRAY_PUSH(received_opts, int) = opt_id;
2510 char *digits_end = NULL;
2511 opt_state.rev = strtol(opt_arg, &digits_end, 10);
2512 if ((! SVN_IS_VALID_REVNUM(opt_state.rev))
2515 SVN_INT_ERR(svn_error_create
2516 (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2517 _("Invalid revision number supplied")));
2522 opt_state.txn = opt_arg;
2526 opt_state.non_recursive = TRUE;
2530 opt_state.verbose = TRUE;
2535 opt_state.help = TRUE;
2539 opt_state.quiet = TRUE;
2542 case svnlook__revprop_opt:
2543 opt_state.revprop = TRUE;
2546 case svnlook__xml_opt:
2547 opt_state.xml = TRUE;
2550 case svnlook__version:
2551 opt_state.version = TRUE;
2554 case svnlook__show_ids:
2555 opt_state.show_ids = TRUE;
2561 opt_state.limit = strtol(opt_arg, &end, 10);
2562 if (end == opt_arg || *end != '\0')
2564 err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
2565 _("Non-numeric limit argument given"));
2566 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2568 if (opt_state.limit <= 0)
2570 err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
2571 _("Argument to --limit must be positive"));
2572 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2577 case svnlook__no_diff_deleted:
2578 opt_state.no_diff_deleted = TRUE;
2581 case svnlook__no_diff_added:
2582 opt_state.no_diff_added = TRUE;
2585 case svnlook__diff_copy_from:
2586 opt_state.diff_copy_from = TRUE;
2589 case svnlook__full_paths:
2590 opt_state.full_paths = TRUE;
2593 case svnlook__copy_info:
2594 opt_state.copy_info = TRUE;
2598 opt_state.extensions = opt_arg;
2601 case svnlook__ignore_properties:
2602 opt_state.ignore_properties = TRUE;
2605 case svnlook__properties_only:
2606 opt_state.properties_only = TRUE;
2609 case svnlook__diff_cmd:
2610 opt_state.diff_cmd = opt_arg;
2613 case svnlook__show_inherited_props:
2614 opt_state.show_inherited_props = TRUE;
2618 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2619 svn_pool_destroy(pool);
2620 return EXIT_FAILURE;
2625 /* The --transaction and --revision options may not co-exist. */
2626 if ((opt_state.rev != SVN_INVALID_REVNUM) && opt_state.txn)
2627 SVN_INT_ERR(svn_error_create
2628 (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
2629 _("The '--transaction' (-t) and '--revision' (-r) arguments "
2630 "cannot co-exist")));
2632 /* The --show-inherited-props and --revprop options may not co-exist. */
2633 if (opt_state.show_inherited_props && opt_state.revprop)
2634 SVN_INT_ERR(svn_error_create
2635 (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
2636 _("Cannot use the '--show-inherited-props' option with the "
2637 "'--revprop' option")));
2639 /* If the user asked for help, then the rest of the arguments are
2640 the names of subcommands to get help on (if any), or else they're
2641 just typos/mistakes. Whatever the case, the subcommand to
2642 actually run is subcommand_help(). */
2644 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
2646 /* If we're not running the `help' subcommand, then look for a
2647 subcommand in the first argument. */
2648 if (subcommand == NULL)
2650 if (os->ind >= os->argc)
2652 if (opt_state.version)
2654 /* Use the "help" subcommand to handle the "--version" option. */
2655 static const svn_opt_subcommand_desc2_t pseudo_cmd =
2656 { "--version", subcommand_help, {0}, "",
2657 {svnlook__version, /* must accept its own option */
2661 subcommand = &pseudo_cmd;
2666 (svn_cmdline_fprintf(stderr, pool,
2667 _("Subcommand argument required\n")));
2668 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2669 svn_pool_destroy(pool);
2670 return EXIT_FAILURE;
2675 const char *first_arg = os->argv[os->ind++];
2676 subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
2677 if (subcommand == NULL)
2679 const char *first_arg_utf8;
2680 err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
2683 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2685 svn_cmdline_fprintf(stderr, pool,
2686 _("Unknown subcommand: '%s'\n"),
2688 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2690 /* Be kind to people who try 'svnlook verify'. */
2691 if (strcmp(first_arg_utf8, "verify") == 0)
2694 svn_cmdline_fprintf(stderr, pool,
2695 _("Try 'svnadmin verify' instead.\n")));
2699 svn_pool_destroy(pool);
2700 return EXIT_FAILURE;
2705 /* If there's a second argument, it's the repository. There may be
2706 more arguments following the repository; usually the next one is
2707 a path within the repository, or it's a propname and the one
2708 after that is the path. Since we don't know, we just call them
2709 arg1 and arg2, meaning the first and second arguments following
2711 if (subcommand->cmd_func != subcommand_help)
2713 const char *repos_path = NULL;
2714 const char *arg1 = NULL, *arg2 = NULL;
2716 /* Get the repository. */
2717 if (os->ind < os->argc)
2719 SVN_INT_ERR(svn_utf_cstring_to_utf8(&repos_path,
2720 os->argv[os->ind++],
2722 repos_path = svn_dirent_internal_style(repos_path, pool);
2725 if (repos_path == NULL)
2728 (svn_cmdline_fprintf(stderr, pool,
2729 _("Repository argument required\n")));
2730 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2731 svn_pool_destroy(pool);
2732 return EXIT_FAILURE;
2734 else if (svn_path_is_url(repos_path))
2737 (svn_cmdline_fprintf(stderr, pool,
2738 _("'%s' is a URL when it should be a path\n"),
2740 svn_pool_destroy(pool);
2741 return EXIT_FAILURE;
2744 opt_state.repos_path = repos_path;
2746 /* Get next arg (arg1), if any. */
2747 if (os->ind < os->argc)
2749 SVN_INT_ERR(svn_utf_cstring_to_utf8
2750 (&arg1, os->argv[os->ind++], pool));
2751 arg1 = svn_dirent_internal_style(arg1, pool);
2753 opt_state.arg1 = arg1;
2755 /* Get next arg (arg2), if any. */
2756 if (os->ind < os->argc)
2758 SVN_INT_ERR(svn_utf_cstring_to_utf8
2759 (&arg2, os->argv[os->ind++], pool));
2760 arg2 = svn_dirent_internal_style(arg2, pool);
2762 opt_state.arg2 = arg2;
2765 /* Check that the subcommand wasn't passed any inappropriate options. */
2766 for (i = 0; i < received_opts->nelts; i++)
2768 opt_id = APR_ARRAY_IDX(received_opts, i, int);
2770 /* All commands implicitly accept --help, so just skip over this
2771 when we see it. Note that we don't want to include this option
2772 in their "accepted options" list because it would be awfully
2773 redundant to display it in every commands' help text. */
2774 if (opt_id == 'h' || opt_id == '?')
2777 if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
2780 const apr_getopt_option_t *badopt =
2781 svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
2783 svn_opt_format_option(&optstr, badopt, FALSE, pool);
2784 if (subcommand->name[0] == '-')
2785 SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
2788 (svn_cmdline_fprintf
2790 _("Subcommand '%s' doesn't accept option '%s'\n"
2791 "Type 'svnlook help %s' for usage.\n"),
2792 subcommand->name, optstr, subcommand->name));
2793 svn_pool_destroy(pool);
2794 return EXIT_FAILURE;
2798 /* Set up our cancellation support. */
2799 apr_signal(SIGINT, signal_handler);
2801 /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
2802 apr_signal(SIGBREAK, signal_handler);
2805 apr_signal(SIGHUP, signal_handler);
2808 apr_signal(SIGTERM, signal_handler);
2812 /* Disable SIGPIPE generation for the platforms that have it. */
2813 apr_signal(SIGPIPE, SIG_IGN);
2817 /* Disable SIGXFSZ generation for the platforms that have it, otherwise
2818 * working with large files when compiled against an APR that doesn't have
2819 * large file support will crash the program, which is uncool. */
2820 apr_signal(SIGXFSZ, SIG_IGN);
2823 /* Run the subcommand. */
2824 err = (*subcommand->cmd_func)(os, &opt_state, pool);
2827 /* For argument-related problems, suggest using the 'help'
2829 if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
2830 || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
2832 err = svn_error_quick_wrap(err,
2833 _("Try 'svnlook help' for more info"));
2835 return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
2839 svn_pool_destroy(pool);
2840 /* Ensure everything is printed on stdout, so the user sees any
2842 SVN_INT_ERR(svn_cmdline_fflush(stdout));
2843 return EXIT_SUCCESS;