summaryrefslogtreecommitdiffstats
path: root/subversion/svnlook
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/svnlook')
-rw-r--r--subversion/svnlook/svnlook.147
-rw-r--r--subversion/svnlook/svnlook.c2830
2 files changed, 2877 insertions, 0 deletions
diff --git a/subversion/svnlook/svnlook.1 b/subversion/svnlook/svnlook.1
new file mode 100644
index 0000000..22ded4c
--- /dev/null
+++ b/subversion/svnlook/svnlook.1
@@ -0,0 +1,47 @@
+.\"
+.\"
+.\" Licensed to the Apache Software Foundation (ASF) under one
+.\" or more contributor license agreements. See the NOTICE file
+.\" distributed with this work for additional information
+.\" regarding copyright ownership. The ASF licenses this file
+.\" to you under the Apache License, Version 2.0 (the
+.\" "License"); you may not use this file except in compliance
+.\" with the License. You may obtain a copy of the License at
+.\"
+.\" http://www.apache.org/licenses/LICENSE-2.0
+.\"
+.\" Unless required by applicable law or agreed to in writing,
+.\" software distributed under the License is distributed on an
+.\" "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+.\" KIND, either express or implied. See the License for the
+.\" specific language governing permissions and limitations
+.\" under the License.
+.\"
+.\"
+.\" You can view this file with:
+.\" nroff -man [filename]
+.\"
+.TH svnlook 1
+.SH NAME
+svnlook \- Subversion repository examination tool
+.SH SYNOPSIS
+.TP
+\fBsvnlook\fP \fIcommand\fP \fI/path/to/repos\fP [\fIoptions\fP] [\fIargs\fP]
+.SH OVERVIEW
+Subversion is a version control system, which allows you to keep old
+versions of files and directories (usually source code), keep a log of
+who, when, and why changes occurred, etc., like CVS, RCS or SCCS.
+\fBSubversion\fP keeps a single copy of the master sources. This copy
+is called the source ``repository''; it contains all the information
+to permit extracting previous versions of those files at any time.
+
+For more information about the Subversion project, visit
+http://subversion.apache.org.
+
+Documentation for Subversion and its tools, including detailed usage
+explanations of the \fBsvn\fP, \fBsvnadmin\fP, \fBsvnserve\fP and
+\fBsvnlook\fP programs, historical background, philosophical
+approaches and reasonings, etc., can be found at
+http://svnbook.red-bean.com/.
+
+Run `svnlook help' to access the built-in tool documentation.
diff --git a/subversion/svnlook/svnlook.c b/subversion/svnlook/svnlook.c
new file mode 100644
index 0000000..e619450
--- /dev/null
+++ b/subversion/svnlook/svnlook.c
@@ -0,0 +1,2830 @@
+/*
+ * svnlook.c: Subversion server inspection tool main file.
+ *
+ * ====================================================================
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you under the Apache License, Version 2.0 (the
+ * "License"); you may not use this file except in compliance
+ * with the License. You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing,
+ * software distributed under the License is distributed on an
+ * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
+ * KIND, either express or implied. See the License for the
+ * specific language governing permissions and limitations
+ * under the License.
+ * ====================================================================
+ */
+
+#include <assert.h>
+#include <stdlib.h>
+
+#include <apr_general.h>
+#include <apr_pools.h>
+#include <apr_time.h>
+#include <apr_file_io.h>
+#include <apr_signal.h>
+
+#define APR_WANT_STDIO
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+#include "svn_hash.h"
+#include "svn_cmdline.h"
+#include "svn_types.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_error_codes.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_repos.h"
+#include "svn_fs.h"
+#include "svn_time.h"
+#include "svn_utf.h"
+#include "svn_subst.h"
+#include "svn_sorts.h"
+#include "svn_opt.h"
+#include "svn_props.h"
+#include "svn_diff.h"
+#include "svn_version.h"
+#include "svn_xml.h"
+
+#include "private/svn_diff_private.h"
+#include "private/svn_cmdline_private.h"
+#include "private/svn_fspath.h"
+
+#include "svn_private_config.h"
+
+
+/*** Some convenience macros and types. ***/
+
+
+/* Option handling. */
+
+static svn_opt_subcommand_t
+ subcommand_author,
+ subcommand_cat,
+ subcommand_changed,
+ subcommand_date,
+ subcommand_diff,
+ subcommand_dirschanged,
+ subcommand_filesize,
+ subcommand_help,
+ subcommand_history,
+ subcommand_info,
+ subcommand_lock,
+ subcommand_log,
+ subcommand_pget,
+ subcommand_plist,
+ subcommand_tree,
+ subcommand_uuid,
+ subcommand_youngest;
+
+/* Option codes and descriptions. */
+enum
+ {
+ svnlook__version = SVN_OPT_FIRST_LONGOPT_ID,
+ svnlook__show_ids,
+ svnlook__no_diff_deleted,
+ svnlook__no_diff_added,
+ svnlook__diff_copy_from,
+ svnlook__revprop_opt,
+ svnlook__full_paths,
+ svnlook__copy_info,
+ svnlook__xml_opt,
+ svnlook__ignore_properties,
+ svnlook__properties_only,
+ svnlook__diff_cmd,
+ svnlook__show_inherited_props
+ };
+
+/*
+ * The entire list must be terminated with an entry of nulls.
+ */
+static const apr_getopt_option_t options_table[] =
+{
+ {NULL, '?', 0,
+ N_("show help on a subcommand")},
+
+ {"copy-info", svnlook__copy_info, 0,
+ N_("show details for copies")},
+
+ {"diff-copy-from", svnlook__diff_copy_from, 0,
+ N_("print differences against the copy source")},
+
+ {"full-paths", svnlook__full_paths, 0,
+ N_("show full paths instead of indenting them")},
+
+ {"help", 'h', 0,
+ N_("show help on a subcommand")},
+
+ {"limit", 'l', 1,
+ N_("maximum number of history entries")},
+
+ {"no-diff-added", svnlook__no_diff_added, 0,
+ N_("do not print differences for added files")},
+
+ {"no-diff-deleted", svnlook__no_diff_deleted, 0,
+ N_("do not print differences for deleted files")},
+
+ {"diff-cmd", svnlook__diff_cmd, 1,
+ N_("use ARG as diff command")},
+
+ {"ignore-properties", svnlook__ignore_properties, 0,
+ N_("ignore properties during the operation")},
+
+ {"properties-only", svnlook__properties_only, 0,
+ N_("show only properties during the operation")},
+
+ {"non-recursive", 'N', 0,
+ N_("operate on single directory only")},
+
+ {"revision", 'r', 1,
+ N_("specify revision number ARG")},
+
+ {"revprop", svnlook__revprop_opt, 0,
+ N_("operate on a revision property (use with -r or -t)")},
+
+ {"show-ids", svnlook__show_ids, 0,
+ N_("show node revision ids for each path")},
+
+ {"show-inherited-props", svnlook__show_inherited_props, 0,
+ N_("show path's inherited properties")},
+
+ {"transaction", 't', 1,
+ N_("specify transaction name ARG")},
+
+ {"verbose", 'v', 0,
+ N_("be verbose")},
+
+ {"version", svnlook__version, 0,
+ N_("show program version information")},
+
+ {"xml", svnlook__xml_opt, 0,
+ N_("output in XML")},
+
+ {"extensions", 'x', 1,
+ N_("Specify differencing options for external diff or\n"
+ " "
+ "internal diff. Default: '-u'. Options are\n"
+ " "
+ "separated by spaces. Internal diff takes:\n"
+ " "
+ " -u, --unified: Show 3 lines of unified context\n"
+ " "
+ " -b, --ignore-space-change: Ignore changes in\n"
+ " "
+ " amount of white space\n"
+ " "
+ " -w, --ignore-all-space: Ignore all white space\n"
+ " "
+ " --ignore-eol-style: Ignore changes in EOL style\n"
+ " "
+ " -p, --show-c-function: Show C function name")},
+
+ {"quiet", 'q', 0,
+ N_("no progress (only errors) to stderr")},
+
+ {0, 0, 0, 0}
+};
+
+
+/* Array of available subcommands.
+ * The entire list must be terminated with an entry of nulls.
+ */
+static const svn_opt_subcommand_desc2_t cmd_table[] =
+{
+ {"author", subcommand_author, {0},
+ N_("usage: svnlook author REPOS_PATH\n\n"
+ "Print the author.\n"),
+ {'r', 't'} },
+
+ {"cat", subcommand_cat, {0},
+ N_("usage: svnlook cat REPOS_PATH FILE_PATH\n\n"
+ "Print the contents of a file. Leading '/' on FILE_PATH is optional.\n"),
+ {'r', 't'} },
+
+ {"changed", subcommand_changed, {0},
+ N_("usage: svnlook changed REPOS_PATH\n\n"
+ "Print the paths that were changed.\n"),
+ {'r', 't', svnlook__copy_info} },
+
+ {"date", subcommand_date, {0},
+ N_("usage: svnlook date REPOS_PATH\n\n"
+ "Print the datestamp.\n"),
+ {'r', 't'} },
+
+ {"diff", subcommand_diff, {0},
+ N_("usage: svnlook diff REPOS_PATH\n\n"
+ "Print GNU-style diffs of changed files and properties.\n"),
+ {'r', 't', svnlook__no_diff_deleted, svnlook__no_diff_added,
+ svnlook__diff_copy_from, svnlook__diff_cmd, 'x',
+ svnlook__ignore_properties, svnlook__properties_only} },
+
+ {"dirs-changed", subcommand_dirschanged, {0},
+ N_("usage: svnlook dirs-changed REPOS_PATH\n\n"
+ "Print the directories that were themselves changed (property edits)\n"
+ "or whose file children were changed.\n"),
+ {'r', 't'} },
+
+ {"filesize", subcommand_filesize, {0},
+ N_("usage: svnlook filesize REPOS_PATH PATH_IN_REPOS\n\n"
+ "Print the size (in bytes) of the file located at PATH_IN_REPOS as\n"
+ "it is represented in the repository.\n"),
+ {'r', 't'} },
+
+ {"help", subcommand_help, {"?", "h"},
+ N_("usage: svnlook help [SUBCOMMAND...]\n\n"
+ "Describe the usage of this program or its subcommands.\n"),
+ {0} },
+
+ {"history", subcommand_history, {0},
+ N_("usage: svnlook history REPOS_PATH [PATH_IN_REPOS]\n\n"
+ "Print information about the history of a path in the repository (or\n"
+ "the root directory if no path is supplied).\n"),
+ {'r', svnlook__show_ids, 'l'} },
+
+ {"info", subcommand_info, {0},
+ N_("usage: svnlook info REPOS_PATH\n\n"
+ "Print the author, datestamp, log message size, and log message.\n"),
+ {'r', 't'} },
+
+ {"lock", subcommand_lock, {0},
+ N_("usage: svnlook lock REPOS_PATH PATH_IN_REPOS\n\n"
+ "If a lock exists on a path in the repository, describe it.\n"),
+ {0} },
+
+ {"log", subcommand_log, {0},
+ N_("usage: svnlook log REPOS_PATH\n\n"
+ "Print the log message.\n"),
+ {'r', 't'} },
+
+ {"propget", subcommand_pget, {"pget", "pg"},
+ N_("usage: 1. svnlook propget REPOS_PATH PROPNAME PATH_IN_REPOS\n"
+ " "
+ /* The line above is actually needed, so do NOT delete it! */
+ " 2. svnlook propget --revprop REPOS_PATH PROPNAME\n\n"
+ "Print the raw value of a property on a path in the repository.\n"
+ "With --revprop, print the raw value of a revision property.\n"),
+ {'r', 't', 'v', svnlook__revprop_opt, svnlook__show_inherited_props} },
+
+ {"proplist", subcommand_plist, {"plist", "pl"},
+ N_("usage: 1. svnlook proplist REPOS_PATH PATH_IN_REPOS\n"
+ " "
+ /* The line above is actually needed, so do NOT delete it! */
+ " 2. svnlook proplist --revprop REPOS_PATH\n\n"
+ "List the properties of a path in the repository, or\n"
+ "with the --revprop option, revision properties.\n"
+ "With -v, show the property values too.\n"),
+ {'r', 't', 'v', svnlook__revprop_opt, svnlook__xml_opt,
+ svnlook__show_inherited_props} },
+
+ {"tree", subcommand_tree, {0},
+ N_("usage: svnlook tree REPOS_PATH [PATH_IN_REPOS]\n\n"
+ "Print the tree, starting at PATH_IN_REPOS (if supplied, at the root\n"
+ "of the tree otherwise), optionally showing node revision ids.\n"),
+ {'r', 't', 'N', svnlook__show_ids, svnlook__full_paths} },
+
+ {"uuid", subcommand_uuid, {0},
+ N_("usage: svnlook uuid REPOS_PATH\n\n"
+ "Print the repository's UUID.\n"),
+ {0} },
+
+ {"youngest", subcommand_youngest, {0},
+ N_("usage: svnlook youngest REPOS_PATH\n\n"
+ "Print the youngest revision number.\n"),
+ {0} },
+
+ { NULL, NULL, {0}, NULL, {0} }
+};
+
+
+/* Baton for passing option/argument state to a subcommand function. */
+struct svnlook_opt_state
+{
+ const char *repos_path; /* 'arg0' is always the path to the repository. */
+ const char *arg1; /* Usually an fs path, a propname, or NULL. */
+ const char *arg2; /* Usually an fs path or NULL. */
+ svn_revnum_t rev;
+ const char *txn;
+ svn_boolean_t version; /* --version */
+ svn_boolean_t show_ids; /* --show-ids */
+ apr_size_t limit; /* --limit */
+ svn_boolean_t help; /* --help */
+ svn_boolean_t no_diff_deleted; /* --no-diff-deleted */
+ svn_boolean_t no_diff_added; /* --no-diff-added */
+ svn_boolean_t diff_copy_from; /* --diff-copy-from */
+ svn_boolean_t verbose; /* --verbose */
+ svn_boolean_t revprop; /* --revprop */
+ svn_boolean_t full_paths; /* --full-paths */
+ svn_boolean_t copy_info; /* --copy-info */
+ svn_boolean_t non_recursive; /* --non-recursive */
+ svn_boolean_t xml; /* --xml */
+ const char *extensions; /* diff extension args (UTF-8!) */
+ svn_boolean_t quiet; /* --quiet */
+ svn_boolean_t ignore_properties; /* --ignore_properties */
+ svn_boolean_t properties_only; /* --properties-only */
+ const char *diff_cmd; /* --diff-cmd */
+ svn_boolean_t show_inherited_props; /* --show-inherited-props */
+};
+
+
+typedef struct svnlook_ctxt_t
+{
+ svn_repos_t *repos;
+ svn_fs_t *fs;
+ svn_boolean_t is_revision;
+ svn_boolean_t show_ids;
+ apr_size_t limit;
+ svn_boolean_t no_diff_deleted;
+ svn_boolean_t no_diff_added;
+ svn_boolean_t diff_copy_from;
+ svn_boolean_t full_paths;
+ svn_boolean_t copy_info;
+ svn_revnum_t rev_id;
+ svn_fs_txn_t *txn;
+ const char *txn_name /* UTF-8! */;
+ const apr_array_header_t *diff_options;
+ svn_boolean_t ignore_properties;
+ svn_boolean_t properties_only;
+ const char *diff_cmd;
+
+} svnlook_ctxt_t;
+
+/* A flag to see if we've been cancelled by the client or not. */
+static volatile sig_atomic_t cancelled = FALSE;
+
+
+/*** Helper functions. ***/
+
+/* A signal handler to support cancellation. */
+static void
+signal_handler(int signum)
+{
+ apr_signal(signum, SIG_IGN);
+ cancelled = TRUE;
+}
+
+/* Our cancellation callback. */
+static svn_error_t *
+check_cancel(void *baton)
+{
+ if (cancelled)
+ return svn_error_create(SVN_ERR_CANCELLED, NULL, _("Caught signal"));
+ else
+ return SVN_NO_ERROR;
+}
+
+
+/* Version compatibility check */
+static svn_error_t *
+check_lib_versions(void)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_repos", svn_repos_version },
+ { "svn_fs", svn_fs_version },
+ { "svn_delta", svn_delta_version },
+ { "svn_diff", svn_diff_version },
+ { NULL, NULL }
+ };
+ SVN_VERSION_DEFINE(my_version);
+
+ return svn_ver_check_list(&my_version, checklist);
+}
+
+
+/* Get revision or transaction property PROP_NAME for the revision or
+ transaction specified in C, allocating in in POOL and placing it in
+ *PROP_VALUE. */
+static svn_error_t *
+get_property(svn_string_t **prop_value,
+ svnlook_ctxt_t *c,
+ const char *prop_name,
+ apr_pool_t *pool)
+{
+ svn_string_t *raw_value;
+
+ /* Fetch transaction property... */
+ if (! c->is_revision)
+ SVN_ERR(svn_fs_txn_prop(&raw_value, c->txn, prop_name, pool));
+
+ /* ...or revision property -- it's your call. */
+ else
+ SVN_ERR(svn_fs_revision_prop(&raw_value, c->fs, c->rev_id,
+ prop_name, pool));
+
+ *prop_value = raw_value;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+get_root(svn_fs_root_t **root,
+ svnlook_ctxt_t *c,
+ apr_pool_t *pool)
+{
+ /* Open up the appropriate root (revision or transaction). */
+ if (c->is_revision)
+ {
+ /* If we didn't get a valid revision number, we'll look at the
+ youngest revision. */
+ if (! SVN_IS_VALID_REVNUM(c->rev_id))
+ SVN_ERR(svn_fs_youngest_rev(&(c->rev_id), c->fs, pool));
+
+ SVN_ERR(svn_fs_revision_root(root, c->fs, c->rev_id, pool));
+ }
+ else
+ {
+ SVN_ERR(svn_fs_txn_root(root, c->txn, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Tree Routines ***/
+
+/* Generate a generic delta tree. */
+static svn_error_t *
+generate_delta_tree(svn_repos_node_t **tree,
+ svn_repos_t *repos,
+ svn_fs_root_t *root,
+ svn_revnum_t base_rev,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *base_root;
+ const svn_delta_editor_t *editor;
+ void *edit_baton;
+ apr_pool_t *edit_pool = svn_pool_create(pool);
+ svn_fs_t *fs = svn_repos_fs(repos);
+
+ /* Get the base root. */
+ SVN_ERR(svn_fs_revision_root(&base_root, fs, base_rev, pool));
+
+ /* Request our editor. */
+ SVN_ERR(svn_repos_node_editor(&editor, &edit_baton, repos,
+ base_root, root, pool, edit_pool));
+
+ /* Drive our editor. */
+ SVN_ERR(svn_repos_replay2(root, "", SVN_INVALID_REVNUM, TRUE,
+ editor, edit_baton, NULL, NULL, edit_pool));
+
+ /* Return the tree we just built. */
+ *tree = svn_repos_node_from_baton(edit_baton);
+ svn_pool_destroy(edit_pool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Tree Printing Routines ***/
+
+/* Recursively print only directory nodes that either a) have property
+ mods, or b) contains files that have changed, or c) has added or deleted
+ children. NODE is the root node of the tree delta, so every node in it
+ is either changed or is a directory with a changed node somewhere in the
+ subtree below it.
+ */
+static svn_error_t *
+print_dirs_changed_tree(svn_repos_node_t *node,
+ const char *path /* UTF-8! */,
+ apr_pool_t *pool)
+{
+ svn_repos_node_t *tmp_node;
+ svn_boolean_t print_me = FALSE;
+ const char *full_path;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(check_cancel(NULL));
+
+ if (! node)
+ return SVN_NO_ERROR;
+
+ /* Not a directory? We're not interested. */
+ if (node->kind != svn_node_dir)
+ return SVN_NO_ERROR;
+
+ /* Got prop mods? Excellent. */
+ if (node->prop_mod)
+ print_me = TRUE;
+
+ /* Fly through the list of children, checking for modified files. */
+ tmp_node = node->child;
+ while (tmp_node && (! print_me))
+ {
+ if ((tmp_node->kind == svn_node_file)
+ || (tmp_node->action == 'A')
+ || (tmp_node->action == 'D'))
+ {
+ print_me = TRUE;
+ }
+ tmp_node = tmp_node->sibling;
+ }
+
+ /* Print the node if it qualifies. */
+ if (print_me)
+ {
+ SVN_ERR(svn_cmdline_printf(pool, "%s/\n", path));
+ }
+
+ /* Return here if the node has no children. */
+ tmp_node = node->child;
+ if (! tmp_node)
+ return SVN_NO_ERROR;
+
+ /* Recursively handle the node's children. */
+ iterpool = svn_pool_create(pool);
+ while (tmp_node)
+ {
+ svn_pool_clear(iterpool);
+ full_path = svn_dirent_join(path, tmp_node->name, iterpool);
+ SVN_ERR(print_dirs_changed_tree(tmp_node, full_path, iterpool));
+ tmp_node = tmp_node->sibling;
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Recursively print all nodes in the tree that have been modified
+ (do not include directories affected only by "bubble-up"). */
+static svn_error_t *
+print_changed_tree(svn_repos_node_t *node,
+ const char *path /* UTF-8! */,
+ svn_boolean_t copy_info,
+ apr_pool_t *pool)
+{
+ const char *full_path;
+ char status[4] = "_ ";
+ svn_boolean_t print_me = TRUE;
+ apr_pool_t *iterpool;
+
+ SVN_ERR(check_cancel(NULL));
+
+ if (! node)
+ return SVN_NO_ERROR;
+
+ /* Print the node. */
+ if (node->action == 'A')
+ {
+ status[0] = 'A';
+ if (copy_info && node->copyfrom_path)
+ status[2] = '+';
+ }
+ else if (node->action == 'D')
+ status[0] = 'D';
+ else if (node->action == 'R')
+ {
+ if ((! node->text_mod) && (! node->prop_mod))
+ print_me = FALSE;
+ if (node->text_mod)
+ status[0] = 'U';
+ if (node->prop_mod)
+ status[1] = 'U';
+ }
+ else
+ print_me = FALSE;
+
+ /* Print this node unless told to skip it. */
+ if (print_me)
+ {
+ SVN_ERR(svn_cmdline_printf(pool, "%s %s%s\n",
+ status,
+ path,
+ node->kind == svn_node_dir ? "/" : ""));
+ if (copy_info && node->copyfrom_path)
+ /* Remove the leading slash from the copyfrom path for consistency
+ with the rest of the output. */
+ SVN_ERR(svn_cmdline_printf(pool, " (from %s%s:r%ld)\n",
+ (node->copyfrom_path[0] == '/'
+ ? node->copyfrom_path + 1
+ : node->copyfrom_path),
+ (node->kind == svn_node_dir ? "/" : ""),
+ node->copyfrom_rev));
+ }
+
+ /* Return here if the node has no children. */
+ node = node->child;
+ if (! node)
+ return SVN_NO_ERROR;
+
+ /* Recursively handle the node's children. */
+ iterpool = svn_pool_create(pool);
+ while (node)
+ {
+ svn_pool_clear(iterpool);
+ full_path = svn_dirent_join(path, node->name, iterpool);
+ SVN_ERR(print_changed_tree(node, full_path, copy_info, iterpool));
+ node = node->sibling;
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+dump_contents(svn_stream_t *stream,
+ svn_fs_root_t *root,
+ const char *path /* UTF-8! */,
+ apr_pool_t *pool)
+{
+ if (root == NULL)
+ SVN_ERR(svn_stream_close(stream)); /* leave an empty file */
+ else
+ {
+ svn_stream_t *contents;
+
+ /* Grab the contents and copy them into the given stream. */
+ SVN_ERR(svn_fs_file_contents(&contents, root, path, pool));
+ SVN_ERR(svn_stream_copy3(contents, stream, NULL, NULL, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Prepare temporary files *TMPFILE1 and *TMPFILE2 for diffing
+ PATH1@ROOT1 versus PATH2@ROOT2. If either ROOT1 or ROOT2 is NULL,
+ the temporary file for its path/root will be an empty one.
+ Otherwise, its temporary file will contain the contents of that
+ path/root in the repository.
+
+ An exception to this is when either path/root has an svn:mime-type
+ property set on it which indicates that the file contains
+ non-textual data -- in this case, the *IS_BINARY flag is set and no
+ temporary files are created.
+
+ Use POOL for all that allocation goodness. */
+static svn_error_t *
+prepare_tmpfiles(const char **tmpfile1,
+ const char **tmpfile2,
+ svn_boolean_t *is_binary,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ const char *tmpdir,
+ apr_pool_t *pool)
+{
+ svn_string_t *mimetype;
+ svn_stream_t *stream;
+
+ /* Init the return values. */
+ *tmpfile1 = NULL;
+ *tmpfile2 = NULL;
+ *is_binary = FALSE;
+
+ assert(path1 && path2);
+
+ /* Check for binary mimetypes. If either file has a binary
+ mimetype, get outta here. */
+ if (root1)
+ {
+ SVN_ERR(svn_fs_node_prop(&mimetype, root1, path1,
+ SVN_PROP_MIME_TYPE, pool));
+ if (mimetype && svn_mime_type_is_binary(mimetype->data))
+ {
+ *is_binary = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+ if (root2)
+ {
+ SVN_ERR(svn_fs_node_prop(&mimetype, root2, path2,
+ SVN_PROP_MIME_TYPE, pool));
+ if (mimetype && svn_mime_type_is_binary(mimetype->data))
+ {
+ *is_binary = TRUE;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Now, prepare the two temporary files, each of which will either
+ be empty, or will have real contents. */
+ SVN_ERR(svn_stream_open_unique(&stream, tmpfile1,
+ tmpdir,
+ svn_io_file_del_none,
+ pool, pool));
+ SVN_ERR(dump_contents(stream, root1, path1, pool));
+
+ SVN_ERR(svn_stream_open_unique(&stream, tmpfile2,
+ tmpdir,
+ svn_io_file_del_none,
+ pool, pool));
+ SVN_ERR(dump_contents(stream, root2, path2, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Generate a diff label for PATH in ROOT, allocating in POOL.
+ ROOT may be NULL, in which case revision 0 is used. */
+static svn_error_t *
+generate_label(const char **label,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_string_t *date;
+ const char *datestr;
+ const char *name = NULL;
+ svn_revnum_t rev = SVN_INVALID_REVNUM;
+
+ if (root)
+ {
+ svn_fs_t *fs = svn_fs_root_fs(root);
+ if (svn_fs_is_revision_root(root))
+ {
+ rev = svn_fs_revision_root_revision(root);
+ SVN_ERR(svn_fs_revision_prop(&date, fs, rev,
+ SVN_PROP_REVISION_DATE, pool));
+ }
+ else
+ {
+ svn_fs_txn_t *txn;
+ name = svn_fs_txn_root_name(root, pool);
+ SVN_ERR(svn_fs_open_txn(&txn, fs, name, pool));
+ SVN_ERR(svn_fs_txn_prop(&date, txn, SVN_PROP_REVISION_DATE, pool));
+ }
+ }
+ else
+ {
+ rev = 0;
+ date = NULL;
+ }
+
+ if (date)
+ datestr = apr_psprintf(pool, "%.10s %.8s UTC", date->data, date->data + 11);
+ else
+ datestr = " ";
+
+ if (name)
+ *label = apr_psprintf(pool, "%s\t%s (txn %s)",
+ path, datestr, name);
+ else
+ *label = apr_psprintf(pool, "%s\t%s (rev %ld)",
+ path, datestr, rev);
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper function to display differences in properties of a file */
+static svn_error_t *
+display_prop_diffs(svn_stream_t *outstream,
+ const char *encoding,
+ const apr_array_header_t *propchanges,
+ apr_hash_t *original_props,
+ const char *path,
+ apr_pool_t *pool)
+{
+
+ SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
+ _("%sProperty changes on: %s%s"),
+ APR_EOL_STR,
+ path,
+ APR_EOL_STR));
+
+ SVN_ERR(svn_stream_printf_from_utf8(outstream, encoding, pool,
+ SVN_DIFF__UNDER_STRING APR_EOL_STR));
+
+ SVN_ERR(check_cancel(NULL));
+
+ SVN_ERR(svn_diff__display_prop_diffs(
+ outstream, encoding, propchanges, original_props,
+ FALSE /* pretty_print_mergeinfo */, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Recursively print all nodes in the tree that have been modified
+ (do not include directories affected only by "bubble-up"). */
+static svn_error_t *
+print_diff_tree(svn_stream_t *out_stream,
+ const char *encoding,
+ svn_fs_root_t *root,
+ svn_fs_root_t *base_root,
+ svn_repos_node_t *node,
+ const char *path /* UTF-8! */,
+ const char *base_path /* UTF-8! */,
+ const svnlook_ctxt_t *c,
+ const char *tmpdir,
+ apr_pool_t *pool)
+{
+ const char *orig_path = NULL, *new_path = NULL;
+ svn_boolean_t do_diff = FALSE;
+ svn_boolean_t orig_empty = FALSE;
+ svn_boolean_t is_copy = FALSE;
+ svn_boolean_t binary = FALSE;
+ svn_boolean_t diff_header_printed = FALSE;
+ apr_pool_t *subpool;
+ svn_stringbuf_t *header;
+
+ SVN_ERR(check_cancel(NULL));
+
+ if (! node)
+ return SVN_NO_ERROR;
+
+ header = svn_stringbuf_create_empty(pool);
+
+ /* Print copyfrom history for the top node of a copied tree. */
+ if ((SVN_IS_VALID_REVNUM(node->copyfrom_rev))
+ && (node->copyfrom_path != NULL))
+ {
+ /* This is ... a copy. */
+ is_copy = TRUE;
+
+ /* Propagate the new base. Copyfrom paths usually start with a
+ slash; we remove it for consistency with the target path.
+ ### Yes, it would be *much* better for something in the path
+ library to be taking care of this! */
+ if (node->copyfrom_path[0] == '/')
+ base_path = apr_pstrdup(pool, node->copyfrom_path + 1);
+ else
+ base_path = apr_pstrdup(pool, node->copyfrom_path);
+
+ svn_stringbuf_appendcstr
+ (header,
+ apr_psprintf(pool, _("Copied: %s (from rev %ld, %s)\n"),
+ path, node->copyfrom_rev, base_path));
+
+ SVN_ERR(svn_fs_revision_root(&base_root,
+ svn_fs_root_fs(base_root),
+ node->copyfrom_rev, pool));
+ }
+
+ /*** First, we'll just print file content diffs. ***/
+ if (node->kind == svn_node_file)
+ {
+ /* Here's the generalized way we do our diffs:
+
+ - First, we'll check for svn:mime-type properties on the old
+ and new files. If either has such a property, and it
+ represents a binary type, we won't actually be doing a real
+ diff.
+
+ - Second, dump the contents of the new version of the file
+ into the temporary directory.
+
+ - Then, dump the contents of the old version of the file into
+ the temporary directory.
+
+ - Next, we run 'diff', passing the repository paths as the
+ labels.
+
+ - Finally, we delete the temporary files. */
+ if (node->action == 'R' && node->text_mod)
+ {
+ do_diff = TRUE;
+ SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
+ base_root, base_path, root, path,
+ tmpdir, pool));
+ }
+ else if (c->diff_copy_from && node->action == 'A' && is_copy)
+ {
+ if (node->text_mod)
+ {
+ do_diff = TRUE;
+ SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
+ base_root, base_path, root, path,
+ tmpdir, pool));
+ }
+ }
+ else if (! c->no_diff_added && node->action == 'A')
+ {
+ do_diff = TRUE;
+ orig_empty = TRUE;
+ SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
+ NULL, base_path, root, path,
+ tmpdir, pool));
+ }
+ else if (! c->no_diff_deleted && node->action == 'D')
+ {
+ do_diff = TRUE;
+ SVN_ERR(prepare_tmpfiles(&orig_path, &new_path, &binary,
+ base_root, base_path, NULL, path,
+ tmpdir, pool));
+ }
+
+ /* The header for the copy case has already been created, and we don't
+ want a header here for files with only property modifications. */
+ if (header->len == 0
+ && (node->action != 'R' || node->text_mod))
+ {
+ svn_stringbuf_appendcstr
+ (header, apr_psprintf(pool, "%s: %s\n",
+ ((node->action == 'A') ? _("Added") :
+ ((node->action == 'D') ? _("Deleted") :
+ ((node->action == 'R') ? _("Modified")
+ : _("Index")))),
+ path));
+ }
+ }
+
+ if (do_diff && (! c->properties_only))
+ {
+ svn_stringbuf_appendcstr(header, SVN_DIFF__EQUAL_STRING "\n");
+
+ if (binary)
+ {
+ svn_stringbuf_appendcstr(header, _("(Binary files differ)\n\n"));
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ "%s", header->data));
+ }
+ else
+ {
+ if (c->diff_cmd)
+ {
+ apr_file_t *outfile;
+ apr_file_t *errfile;
+ const char *outfilename;
+ const char *errfilename;
+ svn_stream_t *stream;
+ svn_stream_t *err_stream;
+ const char **diff_cmd_argv;
+ int diff_cmd_argc;
+ int exitcode;
+ const char *orig_label;
+ const char *new_label;
+
+ diff_cmd_argv = NULL;
+ diff_cmd_argc = c->diff_options->nelts;
+ if (diff_cmd_argc)
+ {
+ int i;
+ diff_cmd_argv = apr_palloc(pool,
+ diff_cmd_argc * sizeof(char *));
+ for (i = 0; i < diff_cmd_argc; i++)
+ SVN_ERR(svn_utf_cstring_to_utf8(&diff_cmd_argv[i],
+ APR_ARRAY_IDX(c->diff_options, i, const char *),
+ pool));
+ }
+
+ /* Print diff header. */
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ "%s", header->data));
+
+ if (orig_empty)
+ SVN_ERR(generate_label(&orig_label, NULL, path, pool));
+ else
+ SVN_ERR(generate_label(&orig_label, base_root,
+ base_path, pool));
+ SVN_ERR(generate_label(&new_label, root, path, pool));
+
+ /* We deal in streams, but svn_io_run_diff2() deals in file
+ handles, unfortunately, so we need to make these temporary
+ files, and then copy the contents to our stream. */
+ SVN_ERR(svn_io_open_unique_file3(&outfile, &outfilename, NULL,
+ svn_io_file_del_on_pool_cleanup, pool, pool));
+ SVN_ERR(svn_io_open_unique_file3(&errfile, &errfilename, NULL,
+ svn_io_file_del_on_pool_cleanup, pool, pool));
+
+ SVN_ERR(svn_io_run_diff2(".",
+ diff_cmd_argv,
+ diff_cmd_argc,
+ orig_label, new_label,
+ orig_path, new_path,
+ &exitcode, outfile, errfile,
+ c->diff_cmd, pool));
+
+ SVN_ERR(svn_io_file_close(outfile, pool));
+ SVN_ERR(svn_io_file_close(errfile, pool));
+
+ /* Now, open and copy our files to our output streams. */
+ SVN_ERR(svn_stream_for_stderr(&err_stream, pool));
+ SVN_ERR(svn_stream_open_readonly(&stream, outfilename,
+ pool, pool));
+ SVN_ERR(svn_stream_copy3(stream,
+ svn_stream_disown(out_stream, pool),
+ NULL, NULL, pool));
+ SVN_ERR(svn_stream_open_readonly(&stream, errfilename,
+ pool, pool));
+ SVN_ERR(svn_stream_copy3(stream,
+ svn_stream_disown(err_stream, pool),
+ NULL, NULL, pool));
+
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ "\n"));
+ diff_header_printed = TRUE;
+ }
+ else
+ {
+ svn_diff_t *diff;
+ svn_diff_file_options_t *opts = svn_diff_file_options_create(pool);
+
+ if (c->diff_options)
+ SVN_ERR(svn_diff_file_options_parse(opts, c->diff_options, pool));
+
+ SVN_ERR(svn_diff_file_diff_2(&diff, orig_path,
+ new_path, opts, pool));
+
+ if (svn_diff_contains_diffs(diff))
+ {
+ const char *orig_label, *new_label;
+
+ /* Print diff header. */
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ "%s", header->data));
+
+ if (orig_empty)
+ SVN_ERR(generate_label(&orig_label, NULL, path, pool));
+ else
+ SVN_ERR(generate_label(&orig_label, base_root,
+ base_path, pool));
+ SVN_ERR(generate_label(&new_label, root, path, pool));
+ SVN_ERR(svn_diff_file_output_unified3
+ (out_stream, diff, orig_path, new_path,
+ orig_label, new_label,
+ svn_cmdline_output_encoding(pool), NULL,
+ opts->show_c_function, pool));
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ "\n"));
+ diff_header_printed = TRUE;
+ }
+ else if (! node->prop_mod &&
+ ((! c->no_diff_added && node->action == 'A') ||
+ (! c->no_diff_deleted && node->action == 'D')))
+ {
+ /* There was an empty file added or deleted in this revision.
+ * We can't print a diff, but we can at least print
+ * a diff header since we know what happened to this file. */
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ "%s", header->data));
+ }
+ }
+ }
+ }
+
+ /* Make sure we delete any temporary files. */
+ if (orig_path)
+ SVN_ERR(svn_io_remove_file2(orig_path, FALSE, pool));
+ if (new_path)
+ SVN_ERR(svn_io_remove_file2(new_path, FALSE, pool));
+
+ /*** Now handle property diffs ***/
+ if ((node->prop_mod) && (node->action != 'D') && (! c->ignore_properties))
+ {
+ apr_hash_t *local_proptable;
+ apr_hash_t *base_proptable;
+ apr_array_header_t *propchanges, *props;
+
+ SVN_ERR(svn_fs_node_proplist(&local_proptable, root, path, pool));
+ if (c->diff_copy_from && node->action == 'A' && is_copy)
+ SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
+ base_path, pool));
+ else if (node->action == 'A')
+ base_proptable = apr_hash_make(pool);
+ else /* node->action == 'R' */
+ SVN_ERR(svn_fs_node_proplist(&base_proptable, base_root,
+ base_path, pool));
+ SVN_ERR(svn_prop_diffs(&propchanges, local_proptable,
+ base_proptable, pool));
+ SVN_ERR(svn_categorize_props(propchanges, NULL, NULL, &props, pool));
+ if (props->nelts > 0)
+ {
+ /* We print a diff header for the case when we only have property
+ * mods. */
+ if (! diff_header_printed)
+ {
+ const char *orig_label, *new_label;
+
+ SVN_ERR(generate_label(&orig_label, base_root, base_path,
+ pool));
+ SVN_ERR(generate_label(&new_label, root, path, pool));
+
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ "Index: %s\n", path));
+ SVN_ERR(svn_stream_printf_from_utf8(out_stream, encoding, pool,
+ SVN_DIFF__EQUAL_STRING "\n"));
+ /* --- <label1>
+ * +++ <label2> */
+ SVN_ERR(svn_diff__unidiff_write_header(
+ out_stream, encoding, orig_label, new_label, pool));
+ }
+ SVN_ERR(display_prop_diffs(out_stream, encoding,
+ props, base_proptable, path, pool));
+ }
+ }
+
+ /* Return here if the node has no children. */
+ node = node->child;
+ if (! node)
+ return SVN_NO_ERROR;
+
+ /* Recursively handle the node's children. */
+ subpool = svn_pool_create(pool);
+ SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node,
+ svn_dirent_join(path, node->name, subpool),
+ svn_dirent_join(base_path, node->name, subpool),
+ c, tmpdir, subpool));
+ while (node->sibling)
+ {
+ svn_pool_clear(subpool);
+ node = node->sibling;
+ SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, node,
+ svn_dirent_join(path, node->name, subpool),
+ svn_dirent_join(base_path, node->name, subpool),
+ c, tmpdir, subpool));
+ }
+ svn_pool_destroy(subpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Print a repository directory, maybe recursively, possibly showing
+ the node revision ids, and optionally using full paths.
+
+ ROOT is the revision or transaction root used to build that tree.
+ PATH and ID are the current path and node revision id being
+ printed, and INDENTATION the number of spaces to prepent to that
+ path's printed output. ID may be NULL if SHOW_IDS is FALSE (in
+ which case, ids won't be printed at all). If RECURSE is TRUE,
+ then print the tree recursively; otherwise, we'll stop after the
+ first level (and use INDENTATION to keep track of how deep we are).
+
+ Use POOL for all allocations. */
+static svn_error_t *
+print_tree(svn_fs_root_t *root,
+ const char *path /* UTF-8! */,
+ const svn_fs_id_t *id,
+ svn_boolean_t is_dir,
+ int indentation,
+ svn_boolean_t show_ids,
+ svn_boolean_t full_paths,
+ svn_boolean_t recurse,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool;
+ apr_hash_t *entries;
+ const char* name;
+
+ SVN_ERR(check_cancel(NULL));
+
+ /* Print the indentation. */
+ if (!full_paths)
+ {
+ int i;
+ for (i = 0; i < indentation; i++)
+ SVN_ERR(svn_cmdline_fputs(" ", stdout, pool));
+ }
+
+ /* ### The path format is inconsistent.. needs fix */
+ if (full_paths)
+ name = path;
+ else if (*path == '/')
+ name = svn_fspath__basename(path, pool);
+ else
+ name = svn_relpath_basename(path, NULL);
+
+ if (svn_path_is_empty(name))
+ name = "/"; /* basename of '/' is "" */
+
+ /* Print the node. */
+ SVN_ERR(svn_cmdline_printf(pool, "%s%s",
+ name,
+ is_dir && strcmp(name, "/") ? "/" : ""));
+
+ if (show_ids)
+ {
+ svn_string_t *unparsed_id = NULL;
+ if (id)
+ unparsed_id = svn_fs_unparse_id(id, pool);
+ SVN_ERR(svn_cmdline_printf(pool, " <%s>",
+ unparsed_id
+ ? unparsed_id->data
+ : _("unknown")));
+ }
+ SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
+
+ /* Return here if PATH is not a directory. */
+ if (! is_dir)
+ return SVN_NO_ERROR;
+
+ /* Recursively handle the node's children. */
+ if (recurse || (indentation == 0))
+ {
+ apr_array_header_t *sorted_entries;
+ int i;
+
+ SVN_ERR(svn_fs_dir_entries(&entries, root, path, pool));
+ subpool = svn_pool_create(pool);
+ sorted_entries = svn_sort__hash(entries,
+ svn_sort_compare_items_lexically, pool);
+ for (i = 0; i < sorted_entries->nelts; i++)
+ {
+ svn_sort__item_t item = APR_ARRAY_IDX(sorted_entries, i,
+ svn_sort__item_t);
+ svn_fs_dirent_t *entry = item.value;
+
+ svn_pool_clear(subpool);
+ SVN_ERR(print_tree(root,
+ (*path == '/')
+ ? svn_fspath__join(path, entry->name, pool)
+ : svn_relpath_join(path, entry->name, pool),
+ entry->id, (entry->kind == svn_node_dir),
+ indentation + 1, show_ids, full_paths,
+ recurse, subpool));
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *BASE_REV to the revision on which the target root specified in
+ C is based, or to SVN_INVALID_REVNUM when C represents "revision
+ 0" (because that revision isn't based on another revision). */
+static svn_error_t *
+get_base_rev(svn_revnum_t *base_rev, svnlook_ctxt_t *c, apr_pool_t *pool)
+{
+ if (c->is_revision)
+ {
+ *base_rev = c->rev_id - 1;
+ }
+ else
+ {
+ *base_rev = svn_fs_txn_base_revision(c->txn);
+
+ if (! SVN_IS_VALID_REVNUM(*base_rev))
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_REVISION, NULL,
+ _("Transaction '%s' is not based on a revision; how odd"),
+ c->txn_name);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Subcommand handlers. ***/
+
+/* Print the revision's log message to stdout, followed by a newline. */
+static svn_error_t *
+do_log(svnlook_ctxt_t *c, svn_boolean_t print_size, apr_pool_t *pool)
+{
+ svn_string_t *prop_value;
+ const char *prop_value_eol, *prop_value_native;
+ svn_stream_t *stream;
+ svn_error_t *err;
+ apr_size_t len;
+
+ SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_LOG, pool));
+ if (! (prop_value && prop_value->data))
+ {
+ SVN_ERR(svn_cmdline_printf(pool, "%s\n", print_size ? "0" : ""));
+ return SVN_NO_ERROR;
+ }
+
+ /* We immitate what svn_cmdline_printf does here, since we need the byte
+ size of what we are going to print. */
+
+ SVN_ERR(svn_subst_translate_cstring2(prop_value->data, &prop_value_eol,
+ APR_EOL_STR, TRUE,
+ NULL, FALSE, pool));
+
+ err = svn_cmdline_cstring_from_utf8(&prop_value_native, prop_value_eol,
+ pool);
+ if (err)
+ {
+ svn_error_clear(err);
+ prop_value_native = svn_cmdline_cstring_from_utf8_fuzzy(prop_value_eol,
+ pool);
+ }
+
+ len = strlen(prop_value_native);
+
+ if (print_size)
+ SVN_ERR(svn_cmdline_printf(pool, "%" APR_SIZE_T_FMT "\n", len));
+
+ /* Use a stream to bypass all stdio translations. */
+ SVN_ERR(svn_cmdline_fflush(stdout));
+ SVN_ERR(svn_stream_for_stdout(&stream, pool));
+ SVN_ERR(svn_stream_write(stream, prop_value_native, &len));
+ SVN_ERR(svn_stream_close(stream));
+
+ SVN_ERR(svn_cmdline_fputs("\n", stdout, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Print the timestamp of the commit (in the revision case) or the
+ empty string (in the transaction case) to stdout, followed by a
+ newline. */
+static svn_error_t *
+do_date(svnlook_ctxt_t *c, apr_pool_t *pool)
+{
+ svn_string_t *prop_value;
+
+ SVN_ERR(get_property(&prop_value, c, SVN_PROP_REVISION_DATE, pool));
+ if (prop_value && prop_value->data)
+ {
+ /* Convert the date for humans. */
+ apr_time_t aprtime;
+ const char *time_utf8;
+
+ SVN_ERR(svn_time_from_cstring(&aprtime, prop_value->data, pool));
+
+ time_utf8 = svn_time_to_human_cstring(aprtime, pool);
+
+ SVN_ERR(svn_cmdline_printf(pool, "%s", time_utf8));
+ }
+
+ SVN_ERR(svn_cmdline_printf(pool, "\n"));
+ return SVN_NO_ERROR;
+}
+
+
+/* Print the author of the commit to stdout, followed by a newline. */
+static svn_error_t *
+do_author(svnlook_ctxt_t *c, apr_pool_t *pool)
+{
+ svn_string_t *prop_value;
+
+ SVN_ERR(get_property(&prop_value, c,
+ SVN_PROP_REVISION_AUTHOR, pool));
+ if (prop_value && prop_value->data)
+ SVN_ERR(svn_cmdline_printf(pool, "%s", prop_value->data));
+
+ SVN_ERR(svn_cmdline_printf(pool, "\n"));
+ return SVN_NO_ERROR;
+}
+
+
+/* Print a list of all directories in which files, or directory
+ properties, have been modified. */
+static svn_error_t *
+do_dirs_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_revnum_t base_rev_id;
+ svn_repos_node_t *tree;
+
+ SVN_ERR(get_root(&root, c, pool));
+ SVN_ERR(get_base_rev(&base_rev_id, c, pool));
+ if (base_rev_id == SVN_INVALID_REVNUM)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
+ if (tree)
+ SVN_ERR(print_dirs_changed_tree(tree, "", pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *KIND to PATH's kind, if PATH exists.
+ *
+ * If PATH does not exist, then error; the text of the error depends
+ * on whether PATH looks like a URL or not.
+ */
+static svn_error_t *
+verify_path(svn_node_kind_t *kind,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs_check_path(kind, root, path, pool));
+
+ if (*kind == svn_node_none)
+ {
+ if (svn_path_is_url(path)) /* check for a common mistake. */
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FOUND, NULL,
+ _("'%s' is a URL, probably should be a path"), path);
+ else
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FOUND, NULL, _("Path '%s' does not exist"), path);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Print the size (in bytes) of a file. */
+static svn_error_t *
+do_filesize(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_node_kind_t kind;
+ svn_filesize_t length;
+
+ SVN_ERR(get_root(&root, c, pool));
+ SVN_ERR(verify_path(&kind, root, path, pool));
+
+ if (kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
+
+ /* Else. */
+
+ SVN_ERR(svn_fs_file_length(&length, root, path, pool));
+ return svn_cmdline_printf(pool, "%" SVN_FILESIZE_T_FMT "\n", length);
+}
+
+/* Print the contents of the file at PATH in the repository.
+ Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist, or with
+ SVN_ERR_FS_NOT_FILE if PATH exists but is not a file. */
+static svn_error_t *
+do_cat(svnlook_ctxt_t *c, const char *path, apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_node_kind_t kind;
+ svn_stream_t *fstream, *stdout_stream;
+
+ SVN_ERR(get_root(&root, c, pool));
+ SVN_ERR(verify_path(&kind, root, path, pool));
+
+ if (kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL, _("Path '%s' is not a file"), path);
+
+ /* Else. */
+
+ SVN_ERR(svn_fs_file_contents(&fstream, root, path, pool));
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ return svn_stream_copy3(fstream, svn_stream_disown(stdout_stream, pool),
+ check_cancel, NULL, pool);
+}
+
+
+/* Print a list of all paths modified in a format compatible with `svn
+ update'. */
+static svn_error_t *
+do_changed(svnlook_ctxt_t *c, apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_revnum_t base_rev_id;
+ svn_repos_node_t *tree;
+
+ SVN_ERR(get_root(&root, c, pool));
+ SVN_ERR(get_base_rev(&base_rev_id, c, pool));
+ if (base_rev_id == SVN_INVALID_REVNUM)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
+ if (tree)
+ SVN_ERR(print_changed_tree(tree, "", c->copy_info, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Print some diff-y stuff in a TBD way. :-) */
+static svn_error_t *
+do_diff(svnlook_ctxt_t *c, apr_pool_t *pool)
+{
+ svn_fs_root_t *root, *base_root;
+ svn_revnum_t base_rev_id;
+ svn_repos_node_t *tree;
+
+ SVN_ERR(get_root(&root, c, pool));
+ SVN_ERR(get_base_rev(&base_rev_id, c, pool));
+ if (base_rev_id == SVN_INVALID_REVNUM)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(generate_delta_tree(&tree, c->repos, root, base_rev_id, pool));
+ if (tree)
+ {
+ const char *tmpdir;
+ svn_stream_t *out_stream;
+ const char *encoding = svn_cmdline_output_encoding(pool);
+
+ SVN_ERR(svn_fs_revision_root(&base_root, c->fs, base_rev_id, pool));
+ SVN_ERR(svn_io_temp_dir(&tmpdir, pool));
+
+ /* This fflush() might seem odd, but it was added to deal
+ with this bug report:
+
+ http://subversion.tigris.org/servlets/ReadMsg?\
+ list=dev&msgNo=140782
+
+ From: "Steve Hay" <SteveHay{_AT_}planit.com>
+ To: <dev@subversion.tigris.org>
+ Subject: svnlook diff output in wrong order when redirected
+ Date: Fri, 4 Jul 2008 16:34:15 +0100
+ Message-ID: <1B32FF956ABF414C9BCE5E487A1497E702014F62@\
+ ukmail02.planit.group>
+
+ Adding the fflush() fixed the bug (not everyone could
+ reproduce it, but those who could confirmed the fix).
+ Later in the thread, Daniel Shahaf speculated as to
+ why the fix works:
+
+ "Because svn_cmdline_printf() uses the standard
+ 'FILE *stdout' to write to stdout, while
+ svn_stream_for_stdout() uses (through
+ apr_file_open_stdout()) Windows API's to get a
+ handle for stdout?" */
+ SVN_ERR(svn_cmdline_fflush(stdout));
+ SVN_ERR(svn_stream_for_stdout(&out_stream, pool));
+
+ SVN_ERR(print_diff_tree(out_stream, encoding, root, base_root, tree,
+ "", "", c, tmpdir, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Callback baton for print_history() (and do_history()). */
+struct print_history_baton
+{
+ svn_fs_t *fs;
+ svn_boolean_t show_ids; /* whether to show node IDs */
+ apr_size_t limit; /* max number of history items */
+ apr_size_t count; /* number of history items processed */
+};
+
+/* Implements svn_repos_history_func_t interface. Print the history
+ that's reported through this callback, possibly finding and
+ displaying node-rev-ids. */
+static svn_error_t *
+print_history(void *baton,
+ const char *path,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ struct print_history_baton *phb = baton;
+
+ SVN_ERR(check_cancel(NULL));
+
+ if (phb->show_ids)
+ {
+ const svn_fs_id_t *node_id;
+ svn_fs_root_t *rev_root;
+ svn_string_t *id_string;
+
+ SVN_ERR(svn_fs_revision_root(&rev_root, phb->fs, revision, pool));
+ SVN_ERR(svn_fs_node_id(&node_id, rev_root, path, pool));
+ id_string = svn_fs_unparse_id(node_id, pool);
+ SVN_ERR(svn_cmdline_printf(pool, "%8ld %s <%s>\n",
+ revision, path, id_string->data));
+ }
+ else
+ {
+ SVN_ERR(svn_cmdline_printf(pool, "%8ld %s\n", revision, path));
+ }
+
+ if (phb->limit > 0)
+ {
+ phb->count++;
+ if (phb->count >= phb->limit)
+ /* Not L10N'd, since this error is supressed by the caller. */
+ return svn_error_create(SVN_ERR_CEASE_INVOCATION, NULL,
+ _("History item limit reached"));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Print a tabular display of history location points for PATH in
+ revision C->rev_id. Optionally, SHOW_IDS. Use POOL for
+ allocations. */
+static svn_error_t *
+do_history(svnlook_ctxt_t *c,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct print_history_baton args;
+
+ if (c->show_ids)
+ {
+ SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH <ID>\n"
+ "-------- ---------\n")));
+ }
+ else
+ {
+ SVN_ERR(svn_cmdline_printf(pool, _("REVISION PATH\n"
+ "-------- ----\n")));
+ }
+
+ /* Call our history crawler. We want the whole lifetime of the path
+ (prior to the user-supplied revision, of course), across all
+ copies. */
+ args.fs = c->fs;
+ args.show_ids = c->show_ids;
+ args.limit = c->limit;
+ args.count = 0;
+ SVN_ERR(svn_repos_history2(c->fs, path, print_history, &args,
+ NULL, NULL, 0, c->rev_id, TRUE, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Print the value of property PROPNAME on PATH in the repository.
+
+ If VERBOSE, print their values too. If SHOW_INHERITED_PROPS, print
+ PATH's inherited props too.
+
+ Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist. If
+ SHOW_INHERITED_PROPS is FALSE,then error with SVN_ERR_PROPERTY_NOT_FOUND
+ if there is no such property on PATH. If SHOW_INHERITED_PROPS is TRUE,
+ then error with SVN_ERR_PROPERTY_NOT_FOUND only if there is no such
+ property on PATH nor inherited by path.
+
+ If PATH is NULL, operate on a revision property. */
+static svn_error_t *
+do_pget(svnlook_ctxt_t *c,
+ const char *propname,
+ const char *path,
+ svn_boolean_t verbose,
+ svn_boolean_t show_inherited_props,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ svn_string_t *prop;
+ svn_node_kind_t kind;
+ svn_stream_t *stdout_stream;
+ apr_size_t len;
+ apr_array_header_t *inherited_props = NULL;
+
+ SVN_ERR(get_root(&root, c, pool));
+ if (path != NULL)
+ {
+ path = svn_fspath__canonicalize(path, pool);
+ SVN_ERR(verify_path(&kind, root, path, pool));
+ SVN_ERR(svn_fs_node_prop(&prop, root, path, propname, pool));
+
+ if (show_inherited_props)
+ {
+ SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
+ path, propname, NULL,
+ NULL, pool, pool));
+ }
+ }
+ else /* --revprop */
+ {
+ SVN_ERR(get_property(&prop, c, propname, pool));
+ }
+
+ /* Did we find nothing? */
+ if (prop == NULL
+ && (!show_inherited_props || inherited_props->nelts == 0))
+ {
+ const char *err_msg;
+ if (path == NULL)
+ {
+ /* We're operating on a revprop (e.g. c->is_revision). */
+ err_msg = apr_psprintf(pool,
+ _("Property '%s' not found on revision %ld"),
+ propname, c->rev_id);
+ }
+ else
+ {
+ if (SVN_IS_VALID_REVNUM(c->rev_id))
+ {
+ if (show_inherited_props)
+ err_msg = apr_psprintf(pool,
+ _("Property '%s' not found on path '%s' "
+ "or inherited from a parent "
+ "in revision %ld"),
+ propname, path, c->rev_id);
+ else
+ err_msg = apr_psprintf(pool,
+ _("Property '%s' not found on path '%s' "
+ "in revision %ld"),
+ propname, path, c->rev_id);
+ }
+ else
+ {
+ if (show_inherited_props)
+ err_msg = apr_psprintf(pool,
+ _("Property '%s' not found on path '%s' "
+ "or inherited from a parent "
+ "in transaction %s"),
+ propname, path, c->txn_name);
+ else
+ err_msg = apr_psprintf(pool,
+ _("Property '%s' not found on path '%s' "
+ "in transaction %s"),
+ propname, path, c->txn_name);
+ }
+ }
+ return svn_error_create(SVN_ERR_PROPERTY_NOT_FOUND, NULL, err_msg);
+ }
+
+ SVN_ERR(svn_stream_for_stdout(&stdout_stream, pool));
+
+ if (verbose || show_inherited_props)
+ {
+ if (inherited_props)
+ {
+ int i;
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *elt =
+ APR_ARRAY_IDX(inherited_props, i,
+ svn_prop_inherited_item_t *);
+
+ if (verbose)
+ {
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ _("Inherited properties on '%s',\nfrom '%s':\n"),
+ path, svn_fspath__canonicalize(elt->path_or_url,
+ pool)));
+ SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream,
+ elt->prop_hash,
+ !verbose, pool));
+ }
+ else
+ {
+ svn_string_t *propval =
+ svn__apr_hash_index_val(apr_hash_first(pool,
+ elt->prop_hash));
+
+ SVN_ERR(svn_stream_printf(
+ stdout_stream, pool, "%s - ",
+ svn_fspath__canonicalize(elt->path_or_url, pool)));
+ len = propval->len;
+ SVN_ERR(svn_stream_write(stdout_stream, propval->data, &len));
+ /* If we have more than one property to write, then add a newline*/
+ if (inherited_props->nelts > 1 || prop)
+ {
+ len = strlen(APR_EOL_STR);
+ SVN_ERR(svn_stream_write(stdout_stream, APR_EOL_STR, &len));
+ }
+ }
+ }
+ }
+
+ if (prop)
+ {
+ if (verbose)
+ {
+ apr_hash_t *hash = apr_hash_make(pool);
+
+ svn_hash_sets(hash, propname, prop);
+ SVN_ERR(svn_stream_printf(stdout_stream, pool,
+ _("Properties on '%s':\n"), path));
+ SVN_ERR(svn_cmdline__print_prop_hash(stdout_stream, hash,
+ FALSE, pool));
+ }
+ else
+ {
+ SVN_ERR(svn_stream_printf(stdout_stream, pool, "%s - ", path));
+ len = prop->len;
+ SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
+ }
+ }
+ }
+ else /* Raw single prop output, i.e. non-verbose output with no
+ inherited props. */
+ {
+ /* Unlike the command line client, we don't translate the property
+ value or print a trailing newline here. We just output the raw
+ bytes of whatever's in the repository, as svnlook is more likely
+ to be used for automated inspections. */
+ len = prop->len;
+ SVN_ERR(svn_stream_write(stdout_stream, prop->data, &len));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Print the property names of all properties on PATH in the repository.
+
+ If VERBOSE, print their values too. If XML, print as XML rather than as
+ plain text. If SHOW_INHERITED_PROPS, print PATH's inherited props too.
+
+ Error with SVN_ERR_FS_NOT_FOUND if PATH does not exist.
+
+ If PATH is NULL, operate on a revision properties. */
+static svn_error_t *
+do_plist(svnlook_ctxt_t *c,
+ const char *path,
+ svn_boolean_t verbose,
+ svn_boolean_t xml,
+ svn_boolean_t show_inherited_props,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ apr_hash_t *props;
+ apr_hash_index_t *hi;
+ svn_node_kind_t kind;
+ svn_stringbuf_t *sb = NULL;
+ svn_boolean_t revprop = FALSE;
+ apr_array_header_t *inherited_props = NULL;
+
+ if (path != NULL)
+ {
+ /* PATH might be the root of the repsository and we accept both
+ "" and "/". But to avoid the somewhat cryptic output like this:
+
+ >svnlook pl repos-path ""
+ Properties on '':
+ svn:auto-props
+ svn:global-ignores
+
+ We canonicalize PATH so that is has a leading slash. */
+ path = svn_fspath__canonicalize(path, pool);
+
+ SVN_ERR(get_root(&root, c, pool));
+ SVN_ERR(verify_path(&kind, root, path, pool));
+ SVN_ERR(svn_fs_node_proplist(&props, root, path, pool));
+
+ if (show_inherited_props)
+ SVN_ERR(svn_repos_fs_get_inherited_props(&inherited_props, root,
+ path, NULL, NULL, NULL,
+ pool, pool));
+ }
+ else if (c->is_revision)
+ {
+ SVN_ERR(svn_fs_revision_proplist(&props, c->fs, c->rev_id, pool));
+ revprop = TRUE;
+ }
+ else
+ {
+ SVN_ERR(svn_fs_txn_proplist(&props, c->txn, pool));
+ revprop = TRUE;
+ }
+
+ if (xml)
+ {
+ /* <?xml version="1.0" encoding="UTF-8"?> */
+ svn_xml_make_header2(&sb, "UTF-8", pool);
+
+ /* "<properties>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "properties", NULL);
+ }
+
+ if (inherited_props)
+ {
+ int i;
+
+ for (i = 0; i < inherited_props->nelts; i++)
+ {
+ svn_prop_inherited_item_t *elt =
+ APR_ARRAY_IDX(inherited_props, i, svn_prop_inherited_item_t *);
+
+ /* Canonicalize the inherited parent paths for consistency
+ with PATH. */
+ if (xml)
+ {
+ svn_xml_make_open_tag(
+ &sb, pool, svn_xml_normal, "target", "path",
+ svn_fspath__canonicalize(elt->path_or_url, pool),
+ NULL);
+ SVN_ERR(svn_cmdline__print_xml_prop_hash(&sb, elt->prop_hash,
+ !verbose, TRUE,
+ pool));
+ svn_xml_make_close_tag(&sb, pool, "target");
+ }
+ else
+ {
+ SVN_ERR(svn_cmdline_printf(
+ pool, _("Inherited properties on '%s',\nfrom '%s':\n"),
+ path, svn_fspath__canonicalize(elt->path_or_url, pool)));
+ SVN_ERR(svn_cmdline__print_prop_hash(NULL, elt->prop_hash,
+ !verbose, pool));
+ }
+ }
+ }
+
+ if (xml)
+ {
+ if (revprop)
+ {
+ /* "<revprops ...>" */
+ if (c->is_revision)
+ {
+ char *revstr = apr_psprintf(pool, "%ld", c->rev_id);
+
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
+ "rev", revstr, NULL);
+ }
+ else
+ {
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "revprops",
+ "txn", c->txn_name, NULL);
+ }
+ }
+ else
+ {
+ /* "<target ...>" */
+ svn_xml_make_open_tag(&sb, pool, svn_xml_normal, "target",
+ "path", path, NULL);
+ }
+ }
+
+ if (!xml && path /* Not a --revprop */)
+ SVN_ERR(svn_cmdline_printf(pool, _("Properties on '%s':\n"), path));
+
+ for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
+ {
+ const char *pname = svn__apr_hash_index_key(hi);
+ svn_string_t *propval = svn__apr_hash_index_val(hi);
+
+ SVN_ERR(check_cancel(NULL));
+
+ /* Since we're already adding a trailing newline (and possible a
+ colon and some spaces) anyway, just mimic the output of the
+ command line client proplist. Compare to 'svnlook propget',
+ which sends the raw bytes to stdout, untranslated. */
+ /* We leave printf calls here, since we don't always know the encoding
+ of the prop value. */
+ if (svn_prop_needs_translation(pname))
+ SVN_ERR(svn_subst_detranslate_string(&propval, propval, TRUE, pool));
+
+ if (verbose)
+ {
+ if (xml)
+ svn_cmdline__print_xml_prop(&sb, pname, propval, FALSE, pool);
+ else
+ {
+ const char *pname_stdout;
+ const char *indented_newval;
+
+ SVN_ERR(svn_cmdline_cstring_from_utf8(&pname_stdout, pname,
+ pool));
+ printf(" %s\n", pname_stdout);
+ /* Add an extra newline to the value before indenting, so that
+ every line of output has the indentation whether the value
+ already ended in a newline or not. */
+ indented_newval =
+ svn_cmdline__indent_string(apr_psprintf(pool, "%s\n",
+ propval->data),
+ " ", pool);
+ printf("%s", indented_newval);
+ }
+ }
+ else if (xml)
+ svn_xml_make_open_tag(&sb, pool, svn_xml_self_closing, "property",
+ "name", pname, NULL);
+ else
+ printf(" %s\n", pname);
+ }
+ if (xml)
+ {
+ errno = 0;
+ if (revprop)
+ {
+ /* "</revprops>" */
+ svn_xml_make_close_tag(&sb, pool, "revprops");
+ }
+ else
+ {
+ /* "</target>" */
+ svn_xml_make_close_tag(&sb, pool, "target");
+ }
+
+ /* "</properties>" */
+ svn_xml_make_close_tag(&sb, pool, "properties");
+
+ if (fputs(sb->data, stdout) == EOF)
+ {
+ if (errno)
+ return svn_error_wrap_apr(errno, _("Write error"));
+ else
+ return svn_error_create(SVN_ERR_IO_WRITE_ERROR, NULL, NULL);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+do_tree(svnlook_ctxt_t *c,
+ const char *path,
+ svn_boolean_t show_ids,
+ svn_boolean_t full_paths,
+ svn_boolean_t recurse,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ const svn_fs_id_t *id;
+ svn_boolean_t is_dir;
+
+ SVN_ERR(get_root(&root, c, pool));
+ SVN_ERR(svn_fs_node_id(&id, root, path, pool));
+ SVN_ERR(svn_fs_is_dir(&is_dir, root, path, pool));
+ SVN_ERR(print_tree(root, path, id, is_dir, 0, show_ids, full_paths,
+ recurse, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* Custom filesystem warning function. */
+static void
+warning_func(void *baton,
+ svn_error_t *err)
+{
+ if (! err)
+ return;
+ svn_handle_error2(err, stderr, FALSE, "svnlook: ");
+}
+
+
+/* Return an error if the number of arguments (excluding the repository
+ * argument) is not NUM_ARGS. NUM_ARGS must be 0 or 1. The arguments
+ * are assumed to be found in OPT_STATE->arg1 and OPT_STATE->arg2. */
+static svn_error_t *
+check_number_of_args(struct svnlook_opt_state *opt_state,
+ int num_args)
+{
+ if ((num_args == 0 && opt_state->arg1 != NULL)
+ || (num_args == 1 && opt_state->arg2 != NULL))
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments given"));
+ if ((num_args == 1 && opt_state->arg1 == NULL))
+ return svn_error_create(SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Missing repository path argument"));
+ return SVN_NO_ERROR;
+}
+
+
+/* Factory function for the context baton. */
+static svn_error_t *
+get_ctxt_baton(svnlook_ctxt_t **baton_p,
+ struct svnlook_opt_state *opt_state,
+ apr_pool_t *pool)
+{
+ svnlook_ctxt_t *baton = apr_pcalloc(pool, sizeof(*baton));
+
+ SVN_ERR(svn_repos_open2(&(baton->repos), opt_state->repos_path, NULL,
+ pool));
+ baton->fs = svn_repos_fs(baton->repos);
+ svn_fs_set_warning_func(baton->fs, warning_func, NULL);
+ baton->show_ids = opt_state->show_ids;
+ baton->limit = opt_state->limit;
+ baton->no_diff_deleted = opt_state->no_diff_deleted;
+ baton->no_diff_added = opt_state->no_diff_added;
+ baton->diff_copy_from = opt_state->diff_copy_from;
+ baton->full_paths = opt_state->full_paths;
+ baton->copy_info = opt_state->copy_info;
+ baton->is_revision = opt_state->txn == NULL;
+ baton->rev_id = opt_state->rev;
+ baton->txn_name = apr_pstrdup(pool, opt_state->txn);
+ baton->diff_options = svn_cstring_split(opt_state->extensions
+ ? opt_state->extensions : "",
+ " \t\n\r", TRUE, pool);
+ baton->ignore_properties = opt_state->ignore_properties;
+ baton->properties_only = opt_state->properties_only;
+ baton->diff_cmd = opt_state->diff_cmd;
+
+ if (baton->txn_name)
+ SVN_ERR(svn_fs_open_txn(&(baton->txn), baton->fs,
+ baton->txn_name, pool));
+ else if (baton->rev_id == SVN_INVALID_REVNUM)
+ SVN_ERR(svn_fs_youngest_rev(&(baton->rev_id), baton->fs, pool));
+
+ *baton_p = baton;
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Subcommands. ***/
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_author(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_author(c, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_cat(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 1));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_cat(c, opt_state->arg1, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_changed(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_changed(c, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_date(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_date(c, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_diff(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_diff(c, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_dirschanged(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_dirs_changed(c, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_filesize(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 1));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_filesize(c, opt_state->arg1, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_help(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ const char *header =
+ _("general usage: svnlook SUBCOMMAND REPOS_PATH [ARGS & OPTIONS ...]\n"
+ "Note: any subcommand which takes the '--revision' and '--transaction'\n"
+ " options will, if invoked without one of those options, act on\n"
+ " the repository's youngest revision.\n"
+ "Type 'svnlook help <subcommand>' for help on a specific subcommand.\n"
+ "Type 'svnlook --version' to see the program version and FS modules.\n"
+ "\n"
+ "Available subcommands:\n");
+
+ const char *fs_desc_start
+ = _("The following repository back-end (FS) modules are available:\n\n");
+
+ svn_stringbuf_t *version_footer;
+
+ version_footer = svn_stringbuf_create(fs_desc_start, pool);
+ SVN_ERR(svn_fs_print_modules(version_footer, pool));
+
+ SVN_ERR(svn_opt_print_help4(os, "svnlook",
+ opt_state ? opt_state->version : FALSE,
+ opt_state ? opt_state->quiet : FALSE,
+ opt_state ? opt_state->verbose : FALSE,
+ version_footer->data,
+ header, cmd_table, options_table, NULL,
+ NULL, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_history(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+ const char *path = (opt_state->arg1 ? opt_state->arg1 : "/");
+
+ if (opt_state->arg2 != NULL)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments given"));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_history(c, path, pool));
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_lock(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+ svn_lock_t *lock;
+
+ SVN_ERR(check_number_of_args(opt_state, 1));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+
+ SVN_ERR(svn_fs_get_lock(&lock, c->fs, opt_state->arg1, pool));
+
+ if (lock)
+ {
+ const char *cr_date, *exp_date = "";
+ int comment_lines = 0;
+
+ cr_date = svn_time_to_human_cstring(lock->creation_date, pool);
+
+ if (lock->expiration_date)
+ exp_date = svn_time_to_human_cstring(lock->expiration_date, pool);
+
+ if (lock->comment)
+ comment_lines = svn_cstring_count_newlines(lock->comment) + 1;
+
+ SVN_ERR(svn_cmdline_printf(pool, _("UUID Token: %s\n"), lock->token));
+ SVN_ERR(svn_cmdline_printf(pool, _("Owner: %s\n"), lock->owner));
+ SVN_ERR(svn_cmdline_printf(pool, _("Created: %s\n"), cr_date));
+ SVN_ERR(svn_cmdline_printf(pool, _("Expires: %s\n"), exp_date));
+ SVN_ERR(svn_cmdline_printf(pool,
+ Q_("Comment (%i line):\n%s\n",
+ "Comment (%i lines):\n%s\n",
+ comment_lines),
+ comment_lines,
+ lock->comment ? lock->comment : ""));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_info(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_author(c, pool));
+ SVN_ERR(do_date(c, pool));
+ SVN_ERR(do_log(c, TRUE, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_log(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_log(c, FALSE, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_pget(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ if (opt_state->arg1 == NULL)
+ {
+ return svn_error_createf
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ opt_state->revprop ? _("Missing propname argument") :
+ _("Missing propname and repository path arguments"));
+ }
+ else if (!opt_state->revprop && opt_state->arg2 == NULL)
+ {
+ return svn_error_create
+ (SVN_ERR_CL_INSUFFICIENT_ARGS, NULL,
+ _("Missing propname or repository path argument"));
+ }
+ if ((opt_state->revprop && opt_state->arg2 != NULL)
+ || os->ind < os->argc)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments given"));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_pget(c, opt_state->arg1,
+ opt_state->revprop ? NULL : opt_state->arg2,
+ opt_state->verbose, opt_state->show_inherited_props,
+ pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_plist(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, opt_state->revprop ? 0 : 1));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_plist(c, opt_state->revprop ? NULL : opt_state->arg1,
+ opt_state->verbose, opt_state->xml,
+ opt_state->show_inherited_props, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_tree(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ if (opt_state->arg2 != NULL)
+ return svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Too many arguments given"));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(do_tree(c, opt_state->arg1 ? opt_state->arg1 : "",
+ opt_state->show_ids, opt_state->full_paths,
+ ! opt_state->non_recursive, pool));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_youngest(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(svn_cmdline_printf(pool, "%ld\n", c->rev_id));
+ return SVN_NO_ERROR;
+}
+
+/* This implements `svn_opt_subcommand_t'. */
+static svn_error_t *
+subcommand_uuid(apr_getopt_t *os, void *baton, apr_pool_t *pool)
+{
+ struct svnlook_opt_state *opt_state = baton;
+ svnlook_ctxt_t *c;
+ const char *uuid;
+
+ SVN_ERR(check_number_of_args(opt_state, 0));
+
+ SVN_ERR(get_ctxt_baton(&c, opt_state, pool));
+ SVN_ERR(svn_fs_get_uuid(c->fs, &uuid, pool));
+ SVN_ERR(svn_cmdline_printf(pool, "%s\n", uuid));
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Main. ***/
+
+int
+main(int argc, const char *argv[])
+{
+ svn_error_t *err;
+ apr_status_t apr_err;
+ apr_pool_t *pool;
+
+ const svn_opt_subcommand_desc2_t *subcommand = NULL;
+ struct svnlook_opt_state opt_state;
+ apr_getopt_t *os;
+ int opt_id;
+ apr_array_header_t *received_opts;
+ int i;
+
+ /* Initialize the app. */
+ if (svn_cmdline_init("svnlook", stderr) != EXIT_SUCCESS)
+ return EXIT_FAILURE;
+
+ /* Create our top-level pool. Use a separate mutexless allocator,
+ * given this application is single threaded.
+ */
+ pool = apr_allocator_owner_get(svn_pool_create_allocator(FALSE));
+
+ received_opts = apr_array_make(pool, SVN_OPT_MAX_OPTIONS, sizeof(int));
+
+ /* Check library versions */
+ err = check_lib_versions();
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
+
+ /* Initialize the FS library. */
+ err = svn_fs_initialize(pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
+
+ if (argc <= 1)
+ {
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+
+ /* Initialize opt_state. */
+ memset(&opt_state, 0, sizeof(opt_state));
+ opt_state.rev = SVN_INVALID_REVNUM;
+
+ /* Parse options. */
+ err = svn_cmdline__getopt_init(&os, argc, argv, pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
+
+ os->interleave = 1;
+ while (1)
+ {
+ const char *opt_arg;
+
+ /* Parse the next option. */
+ apr_err = apr_getopt_long(os, options_table, &opt_id, &opt_arg);
+ if (APR_STATUS_IS_EOF(apr_err))
+ break;
+ else if (apr_err)
+ {
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+
+ /* Stash the option code in an array before parsing it. */
+ APR_ARRAY_PUSH(received_opts, int) = opt_id;
+
+ switch (opt_id)
+ {
+ case 'r':
+ {
+ char *digits_end = NULL;
+ opt_state.rev = strtol(opt_arg, &digits_end, 10);
+ if ((! SVN_IS_VALID_REVNUM(opt_state.rev))
+ || (! digits_end)
+ || *digits_end)
+ SVN_INT_ERR(svn_error_create
+ (SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Invalid revision number supplied")));
+ }
+ break;
+
+ case 't':
+ opt_state.txn = opt_arg;
+ break;
+
+ case 'N':
+ opt_state.non_recursive = TRUE;
+ break;
+
+ case 'v':
+ opt_state.verbose = TRUE;
+ break;
+
+ case 'h':
+ case '?':
+ opt_state.help = TRUE;
+ break;
+
+ case 'q':
+ opt_state.quiet = TRUE;
+ break;
+
+ case svnlook__revprop_opt:
+ opt_state.revprop = TRUE;
+ break;
+
+ case svnlook__xml_opt:
+ opt_state.xml = TRUE;
+ break;
+
+ case svnlook__version:
+ opt_state.version = TRUE;
+ break;
+
+ case svnlook__show_ids:
+ opt_state.show_ids = TRUE;
+ break;
+
+ case 'l':
+ {
+ char *end;
+ opt_state.limit = strtol(opt_arg, &end, 10);
+ if (end == opt_arg || *end != '\0')
+ {
+ err = svn_error_create(SVN_ERR_CL_ARG_PARSING_ERROR, NULL,
+ _("Non-numeric limit argument given"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
+ }
+ if (opt_state.limit <= 0)
+ {
+ err = svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+ _("Argument to --limit must be positive"));
+ return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
+ }
+ }
+ break;
+
+ case svnlook__no_diff_deleted:
+ opt_state.no_diff_deleted = TRUE;
+ break;
+
+ case svnlook__no_diff_added:
+ opt_state.no_diff_added = TRUE;
+ break;
+
+ case svnlook__diff_copy_from:
+ opt_state.diff_copy_from = TRUE;
+ break;
+
+ case svnlook__full_paths:
+ opt_state.full_paths = TRUE;
+ break;
+
+ case svnlook__copy_info:
+ opt_state.copy_info = TRUE;
+ break;
+
+ case 'x':
+ opt_state.extensions = opt_arg;
+ break;
+
+ case svnlook__ignore_properties:
+ opt_state.ignore_properties = TRUE;
+ break;
+
+ case svnlook__properties_only:
+ opt_state.properties_only = TRUE;
+ break;
+
+ case svnlook__diff_cmd:
+ opt_state.diff_cmd = opt_arg;
+ break;
+
+ case svnlook__show_inherited_props:
+ opt_state.show_inherited_props = TRUE;
+ break;
+
+ default:
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+
+ }
+ }
+
+ /* The --transaction and --revision options may not co-exist. */
+ if ((opt_state.rev != SVN_INVALID_REVNUM) && opt_state.txn)
+ SVN_INT_ERR(svn_error_create
+ (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("The '--transaction' (-t) and '--revision' (-r) arguments "
+ "cannot co-exist")));
+
+ /* The --show-inherited-props and --revprop options may not co-exist. */
+ if (opt_state.show_inherited_props && opt_state.revprop)
+ SVN_INT_ERR(svn_error_create
+ (SVN_ERR_CL_MUTUALLY_EXCLUSIVE_ARGS, NULL,
+ _("Cannot use the '--show-inherited-props' option with the "
+ "'--revprop' option")));
+
+ /* If the user asked for help, then the rest of the arguments are
+ the names of subcommands to get help on (if any), or else they're
+ just typos/mistakes. Whatever the case, the subcommand to
+ actually run is subcommand_help(). */
+ if (opt_state.help)
+ subcommand = svn_opt_get_canonical_subcommand2(cmd_table, "help");
+
+ /* If we're not running the `help' subcommand, then look for a
+ subcommand in the first argument. */
+ if (subcommand == NULL)
+ {
+ if (os->ind >= os->argc)
+ {
+ if (opt_state.version)
+ {
+ /* Use the "help" subcommand to handle the "--version" option. */
+ static const svn_opt_subcommand_desc2_t pseudo_cmd =
+ { "--version", subcommand_help, {0}, "",
+ {svnlook__version, /* must accept its own option */
+ 'q', 'v',
+ } };
+
+ subcommand = &pseudo_cmd;
+ }
+ else
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf(stderr, pool,
+ _("Subcommand argument required\n")));
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+ else
+ {
+ const char *first_arg = os->argv[os->ind++];
+ subcommand = svn_opt_get_canonical_subcommand2(cmd_table, first_arg);
+ if (subcommand == NULL)
+ {
+ const char *first_arg_utf8;
+ err = svn_utf_cstring_to_utf8(&first_arg_utf8, first_arg,
+ pool);
+ if (err)
+ return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
+ svn_error_clear(
+ svn_cmdline_fprintf(stderr, pool,
+ _("Unknown subcommand: '%s'\n"),
+ first_arg_utf8));
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+
+ /* Be kind to people who try 'svnlook verify'. */
+ if (strcmp(first_arg_utf8, "verify") == 0)
+ {
+ svn_error_clear(
+ svn_cmdline_fprintf(stderr, pool,
+ _("Try 'svnadmin verify' instead.\n")));
+ }
+
+
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+ }
+
+ /* If there's a second argument, it's the repository. There may be
+ more arguments following the repository; usually the next one is
+ a path within the repository, or it's a propname and the one
+ after that is the path. Since we don't know, we just call them
+ arg1 and arg2, meaning the first and second arguments following
+ the repository. */
+ if (subcommand->cmd_func != subcommand_help)
+ {
+ const char *repos_path = NULL;
+ const char *arg1 = NULL, *arg2 = NULL;
+
+ /* Get the repository. */
+ if (os->ind < os->argc)
+ {
+ SVN_INT_ERR(svn_utf_cstring_to_utf8(&repos_path,
+ os->argv[os->ind++],
+ pool));
+ repos_path = svn_dirent_internal_style(repos_path, pool);
+ }
+
+ if (repos_path == NULL)
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf(stderr, pool,
+ _("Repository argument required\n")));
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ else if (svn_path_is_url(repos_path))
+ {
+ svn_error_clear
+ (svn_cmdline_fprintf(stderr, pool,
+ _("'%s' is a URL when it should be a path\n"),
+ repos_path));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+
+ opt_state.repos_path = repos_path;
+
+ /* Get next arg (arg1), if any. */
+ if (os->ind < os->argc)
+ {
+ SVN_INT_ERR(svn_utf_cstring_to_utf8
+ (&arg1, os->argv[os->ind++], pool));
+ arg1 = svn_dirent_internal_style(arg1, pool);
+ }
+ opt_state.arg1 = arg1;
+
+ /* Get next arg (arg2), if any. */
+ if (os->ind < os->argc)
+ {
+ SVN_INT_ERR(svn_utf_cstring_to_utf8
+ (&arg2, os->argv[os->ind++], pool));
+ arg2 = svn_dirent_internal_style(arg2, pool);
+ }
+ opt_state.arg2 = arg2;
+ }
+
+ /* Check that the subcommand wasn't passed any inappropriate options. */
+ for (i = 0; i < received_opts->nelts; i++)
+ {
+ opt_id = APR_ARRAY_IDX(received_opts, i, int);
+
+ /* All commands implicitly accept --help, so just skip over this
+ when we see it. Note that we don't want to include this option
+ in their "accepted options" list because it would be awfully
+ redundant to display it in every commands' help text. */
+ if (opt_id == 'h' || opt_id == '?')
+ continue;
+
+ if (! svn_opt_subcommand_takes_option3(subcommand, opt_id, NULL))
+ {
+ const char *optstr;
+ const apr_getopt_option_t *badopt =
+ svn_opt_get_option_from_code2(opt_id, options_table, subcommand,
+ pool);
+ svn_opt_format_option(&optstr, badopt, FALSE, pool);
+ if (subcommand->name[0] == '-')
+ SVN_INT_ERR(subcommand_help(NULL, NULL, pool));
+ else
+ svn_error_clear
+ (svn_cmdline_fprintf
+ (stderr, pool,
+ _("Subcommand '%s' doesn't accept option '%s'\n"
+ "Type 'svnlook help %s' for usage.\n"),
+ subcommand->name, optstr, subcommand->name));
+ svn_pool_destroy(pool);
+ return EXIT_FAILURE;
+ }
+ }
+
+ /* Set up our cancellation support. */
+ apr_signal(SIGINT, signal_handler);
+#ifdef SIGBREAK
+ /* SIGBREAK is a Win32 specific signal generated by ctrl-break. */
+ apr_signal(SIGBREAK, signal_handler);
+#endif
+#ifdef SIGHUP
+ apr_signal(SIGHUP, signal_handler);
+#endif
+#ifdef SIGTERM
+ apr_signal(SIGTERM, signal_handler);
+#endif
+
+#ifdef SIGPIPE
+ /* Disable SIGPIPE generation for the platforms that have it. */
+ apr_signal(SIGPIPE, SIG_IGN);
+#endif
+
+#ifdef SIGXFSZ
+ /* Disable SIGXFSZ generation for the platforms that have it, otherwise
+ * working with large files when compiled against an APR that doesn't have
+ * large file support will crash the program, which is uncool. */
+ apr_signal(SIGXFSZ, SIG_IGN);
+#endif
+
+ /* Run the subcommand. */
+ err = (*subcommand->cmd_func)(os, &opt_state, pool);
+ if (err)
+ {
+ /* For argument-related problems, suggest using the 'help'
+ subcommand. */
+ if (err->apr_err == SVN_ERR_CL_INSUFFICIENT_ARGS
+ || err->apr_err == SVN_ERR_CL_ARG_PARSING_ERROR)
+ {
+ err = svn_error_quick_wrap(err,
+ _("Try 'svnlook help' for more info"));
+ }
+ return svn_cmdline_handle_exit_error(err, pool, "svnlook: ");
+ }
+ else
+ {
+ svn_pool_destroy(pool);
+ /* Ensure everything is printed on stdout, so the user sees any
+ print errors. */
+ SVN_INT_ERR(svn_cmdline_fflush(stdout));
+ return EXIT_SUCCESS;
+ }
+}
OpenPOWER on IntegriCloud