From 65e76db586deadf73f262da9412cfb4b76b24fad Mon Sep 17 00:00:00 2001 From: epg Date: Mon, 28 Apr 2008 22:52:24 +0000 Subject: [PATCH] Merge the svnserve-logging branch, in its entirety, to trunk, using the following command: svn merge http://svn.collab.net/repos/svn/trunk@30530 \ http://svn.collab.net/repos/svn/branches/svnserve-logging@30700 (svn merge --reintegrate does not handle renames on the branch) See the branch for the details, but here is a summary: - Add very simple --log-file functionality to svnserve, with no support for log rotation beyond simply killing the listener, rotating, and restarting. - Move the logic for constructing the operational log line from all the places in mod_dav_svn where an operation is logged to the new svn_log__ functions in libsvn_subr. - Use svn_log__ functions to log operations in svnserve. - Copy escape_errorlog_item from Apache 2.2.4 to new log-escape.c file, for escaping whatever random crap may be in logged error messages (e.g. newlines). - Rename tools/server-side/svn_dav_log_parse.py and test_svn_dav_log_parse.py to tools/server-side/svn_server_log_parse.py and test_svn_server_log_parse.py and add support for svnserve format. git-svn-id: http://svn.collab.net/repos/svn/trunk@30825 612f8ebc-c883-4be0-9ee0-a4e9ef946e3a --- build.conf | 2 +- subversion/include/private/svn_log.h | 244 +++++++++++++ subversion/include/svn_ra_svn.h | 4 + subversion/libsvn_ra_svn/marshal.c | 18 +- subversion/libsvn_ra_svn/ra_svn.h | 1 + subversion/libsvn_subr/log.c | 377 +++++++++++++++++++++ subversion/mod_dav_svn/deadprops.c | 31 +- subversion/mod_dav_svn/lock.c | 17 +- subversion/mod_dav_svn/reports/file-revs.c | 10 +- subversion/mod_dav_svn/reports/log.c | 54 +-- subversion/mod_dav_svn/reports/mergeinfo.c | 21 +- subversion/mod_dav_svn/reports/replay.c | 15 +- subversion/mod_dav_svn/reports/update.c | 58 +--- subversion/mod_dav_svn/repos.c | 10 +- subversion/mod_dav_svn/version.c | 5 +- subversion/svnserve/log-escape.c | 136 ++++++++ subversion/svnserve/main.c | 17 + subversion/svnserve/serve.c | 237 ++++++++++++- subversion/svnserve/server.h | 13 +- ...vn_dav_log_parse.py => svn_server_log_parse.py} | 68 +++- ...v_log_parse.py => test_svn_server_log_parse.py} | 247 ++++++++++---- 21 files changed, 1349 insertions(+), 236 deletions(-) create mode 100644 subversion/include/private/svn_log.h create mode 100644 subversion/libsvn_subr/log.c create mode 100644 subversion/svnserve/log-escape.c rename tools/server-side/{svn_dav_log_parse.py => svn_server_log_parse.py} (86%) rename tools/server-side/{test_svn_dav_log_parse.py => test_svn_server_log_parse.py} (64%) diff --git a/build.conf b/build.conf index 287e3b6f0..2bccfc44f 100644 --- a/build.conf +++ b/build.conf @@ -319,7 +319,7 @@ install = fsmod-lib path = subversion/libsvn_subr libs = aprutil apriconv apr xml zlib msvc-libs = advapi32.lib shfolder.lib ole32.lib -msvc-export = svn_auth.h svn_base64.h svn_cmdline.h svn_compat.h svn_config.h svn_ctype.h svn_dso.h svn_error.h svn_hash.h svn_io.h svn_md5.h svn_nls.h svn_opt.h svn_mergeinfo.h svn_path.h svn_pools.h svn_props.h svn_quoprint.h svn_sorts.h svn_string.h svn_subst.h svn_time.h svn_types.h svn_user.h svn_utf.h svn_version.h svn_xml.h private\svn_atomic.h private\svn_mergeinfo_private.h svn_iter.h private\svn_opt_private.h +msvc-export = svn_auth.h svn_base64.h svn_cmdline.h svn_compat.h svn_config.h svn_ctype.h svn_dso.h svn_error.h svn_hash.h svn_io.h svn_md5.h svn_nls.h svn_opt.h svn_mergeinfo.h svn_path.h svn_pools.h svn_props.h svn_quoprint.h svn_sorts.h svn_string.h svn_subst.h svn_time.h svn_types.h svn_user.h svn_utf.h svn_version.h svn_xml.h private\svn_atomic.h private\svn_log.h private\svn_mergeinfo_private.h svn_iter.h private\svn_opt_private.h # Low-level grab bag of utilities [libsvn_fs_util] diff --git a/subversion/include/private/svn_log.h b/subversion/include/private/svn_log.h new file mode 100644 index 000000000..65faba5ed --- /dev/null +++ b/subversion/include/private/svn_log.h @@ -0,0 +1,244 @@ +/* + * svn_log.h: Functions for assembling entries for server-side logs. + * See also tools/server-side/svn_server_log_parse.py . + * ==================================================================== + * Copyright (c) 2008 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + +#ifndef SVN_LOG_H +#define SVN_LOG_H + +#include "svn_types.h" +#include "svn_mergeinfo.h" +#include "svn_pools.h" +#include "svn_string.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/** + * Return a log string for a reparent action. + * + * @since New in 1.6. + */ +const char * +svn_log__reparent(const char *path, apr_pool_t *pool); + +/** + * Return a log string for a change-rev-prop action. + * + * @since New in 1.6. + */ +const char * +svn_log__change_rev_prop(svn_revnum_t rev, const char *name, apr_pool_t *pool); + +/** + * Return a log string for a rev-proplist action. + * + * @since New in 1.6. + */ +const char * +svn_log__rev_proplist(svn_revnum_t rev, apr_pool_t *pool); + +/** + * Return a log string for a rev-prop action. + * + * @since New in 1.6. + */ +const char * +svn_log__rev_prop(svn_revnum_t rev, const char *name, apr_pool_t *pool); + +/** + * Return a log string for a commit action. + * + * @since New in 1.6. + */ +const char * +svn_log__commit(svn_revnum_t rev, apr_pool_t *pool); + +/** + * Return a log string for a get-file action. + * + * @since New in 1.6. + */ +const char * +svn_log__get_file(const char *path, svn_revnum_t rev, + svn_boolean_t want_contents, svn_boolean_t want_props, + apr_pool_t *pool); + +/** + * Return a log string for a get-dir action. + * + * @since New in 1.6. + */ +const char * +svn_log__get_dir(const char *path, svn_revnum_t rev, + svn_boolean_t want_contents, svn_boolean_t want_props, + apr_uint64_t dirent_fields, + apr_pool_t *pool); + +/** + * Return a log string for a get-mergeinfo action. + * + * @since New in 1.6. + */ +const char * +svn_log__get_mergeinfo(const apr_array_header_t *paths, + svn_mergeinfo_inheritance_t inherit, + svn_boolean_t include_descendants, + apr_pool_t *pool); + +/** + * Return a log string for a checkout action. + * + * @since New in 1.6. + */ +const char * +svn_log__checkout(const char *path, svn_revnum_t rev, svn_depth_t depth, + apr_pool_t *pool); + +/** + * Return a log string for a update action. + * + * @since New in 1.6. + */ +const char * +svn_log__update(const char *path, svn_revnum_t rev, svn_depth_t depth, + svn_boolean_t send_copyfrom_args, + apr_pool_t *pool); + +/** + * Return a log string for a switch action. + * + * @since New in 1.6. + */ +const char * +svn_log__switch(const char *path, const char *dst_path, svn_revnum_t revnum, + svn_depth_t depth, apr_pool_t *pool); + +/** + * Return a log string for a status action. + * + * @since New in 1.6. + */ +const char * +svn_log__status(const char *path, svn_revnum_t rev, svn_depth_t depth, + apr_pool_t *pool); + +/** + * Return a log string for a diff action. + * + * @since New in 1.6. + */ +const char * +svn_log__diff(const char *path, svn_revnum_t from_revnum, + const char *dst_path, svn_revnum_t revnum, + svn_depth_t depth, svn_boolean_t ignore_ancestry, + apr_pool_t *pool); + +/** + * Return a log string for a log action. + * + * @since New in 1.6. + */ +const char * +svn_log__log(const apr_array_header_t *paths, + svn_revnum_t start, svn_revnum_t end, + int limit, svn_boolean_t discover_changed_paths, + svn_boolean_t strict_node_history, + svn_boolean_t include_merged_revisions, + const apr_array_header_t *revprops, apr_pool_t *pool); + +/** + * Return a log string for a get-locations action. + * + * @since New in 1.6. + */ +const char * +svn_log__get_locations(const char *path, svn_revnum_t peg_revision, + const apr_array_header_t *location_revisions, + apr_pool_t *pool); + +/** + * Return a log string for a get-location-segments action. + * + * @since New in 1.6. + */ +const char * +svn_log__get_location_segments(const char *path, svn_revnum_t peg_revision, + svn_revnum_t start, svn_revnum_t end, + apr_pool_t *pool); + +/** + * Return a log string for a get-file-revs action. + * + * @since New in 1.6. + */ +const char * +svn_log__get_file_revs(const char *path, svn_revnum_t start, svn_revnum_t end, + svn_boolean_t include_merged_revisions, + apr_pool_t *pool); + +/** + * Return a log string for a lock action. + * + * @since New in 1.6. + */ +const char * +svn_log__lock(const apr_array_header_t *paths, svn_boolean_t steal, + apr_pool_t *pool); + +/** + * Return a log string for an unlock action. + * + * @since New in 1.6. + */ +const char * +svn_log__unlock(const apr_array_header_t *paths, svn_boolean_t break_lock, + apr_pool_t *pool); + +/** + * Return a log string for a lock action on only one path; this is + * just a convenience wrapper around svn_log__lock(). + * + * @since New in 1.6. + */ +const char * +svn_log__lock_one_path(const char *path, svn_boolean_t steal, + apr_pool_t *pool); + +/** + * Return a log string for a unlock action on only one path; this is + * just a convenience wrapper around svn_log__unlock(). + * + * @since New in 1.6. + */ +const char * +svn_log__unlock_one_path(const char *path, svn_boolean_t break_lock, + apr_pool_t *pool); + +/** + * Return a log string for a replay action. + * + * @since New in 1.6. + */ +const char * +svn_log__replay(const char *path, svn_revnum_t rev, apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LOG_H */ diff --git a/subversion/include/svn_ra_svn.h b/subversion/include/svn_ra_svn.h index f3f80251d..ddac7ae1b 100644 --- a/subversion/include/svn_ra_svn.h +++ b/subversion/include/svn_ra_svn.h @@ -173,6 +173,10 @@ svn_error_t *svn_ra_svn_set_capabilities(svn_ra_svn_conn_t *conn, svn_boolean_t svn_ra_svn_has_capability(svn_ra_svn_conn_t *conn, const char *capability); +/** Returns the remote address of the connection as a string, if known, + * or NULL if inapplicable. */ +const char *svn_ra_svn_conn_remote_host(svn_ra_svn_conn_t *conn); + /** Write a number over the net. * * Writes will be buffered until the next read or flush. diff --git a/subversion/libsvn_ra_svn/marshal.c b/subversion/libsvn_ra_svn/marshal.c index 5d4a8b534..07b6f3e40 100644 --- a/subversion/libsvn_ra_svn/marshal.c +++ b/subversion/libsvn_ra_svn/marshal.c @@ -62,9 +62,18 @@ svn_ra_svn_conn_t *svn_ra_svn_create_conn(apr_socket_t *sock, conn->pool = pool; if (sock != NULL) - conn->stream = svn_ra_svn__stream_from_sock(sock, pool); + { + apr_sockaddr_t *sa; + conn->stream = svn_ra_svn__stream_from_sock(sock, pool); + if (!(apr_socket_addr_get(&sa, APR_REMOTE, sock) == APR_SUCCESS + && apr_sockaddr_ip_get(&conn->remote_ip, sa) == APR_SUCCESS)) + conn->remote_ip = NULL; + } else - conn->stream = svn_ra_svn__stream_from_files(in_file, out_file, pool); + { + conn->stream = svn_ra_svn__stream_from_files(in_file, out_file, pool); + conn->remote_ip = NULL; + } return conn; } @@ -95,6 +104,11 @@ svn_boolean_t svn_ra_svn_has_capability(svn_ra_svn_conn_t *conn, APR_HASH_KEY_STRING) != NULL); } +const char *svn_ra_svn_conn_remote_host(svn_ra_svn_conn_t *conn) +{ + return conn->remote_ip; +} + void svn_ra_svn__set_block_handler(svn_ra_svn_conn_t *conn, ra_svn_block_handler_t handler, diff --git a/subversion/libsvn_ra_svn/ra_svn.h b/subversion/libsvn_ra_svn/ra_svn.h index 56592c410..69937fde1 100644 --- a/subversion/libsvn_ra_svn/ra_svn.h +++ b/subversion/libsvn_ra_svn/ra_svn.h @@ -80,6 +80,7 @@ struct svn_ra_svn_conn_st { ra_svn_block_handler_t block_handler; void *block_baton; apr_hash_t *capabilities; + char *remote_ip; apr_pool_t *pool; }; diff --git a/subversion/libsvn_subr/log.c b/subversion/libsvn_subr/log.c new file mode 100644 index 000000000..4ba17761a --- /dev/null +++ b/subversion/libsvn_subr/log.c @@ -0,0 +1,377 @@ +/* + * serve.c : Functions for serving the Subversion protocol + * + * ==================================================================== + * Copyright (c) 2008 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + + + + +#include + +#define APR_WANT_STRFUNC +#include +#include + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_mergeinfo.h" +#include "svn_path.h" +#include "svn_pools.h" +#include "svn_string.h" + +#include "private/svn_log.h" + + +static const char * +log_depth(svn_depth_t depth, apr_pool_t *pool) +{ + if (depth == svn_depth_unknown) + return ""; + return apr_pstrcat(pool, " depth=", svn_depth_to_word(depth), NULL); +} + +static const char * +log_include_merged_revisions(svn_boolean_t include_merged_revisions) +{ + if (include_merged_revisions) + return " include-merged-revisions"; + return ""; +} + + +const char * +svn_log__reparent(const char *path, apr_pool_t *pool) +{ + return apr_psprintf(pool, "reparent %s", svn_path_uri_encode(path, pool)); + +} + +const char * +svn_log__change_rev_prop(svn_revnum_t rev, const char *name, apr_pool_t *pool) +{ + return apr_psprintf(pool, "change-rev-prop r%ld %s", rev, + svn_path_uri_encode(name, pool)); +} + +const char * +svn_log__rev_proplist(svn_revnum_t rev, apr_pool_t *pool) +{ + return apr_psprintf(pool, "rev-proplist r%ld", rev); +} + +const char * +svn_log__rev_prop(svn_revnum_t rev, const char *name, apr_pool_t *pool) +{ + return apr_psprintf(pool, "rev-prop r%ld %s", rev, + svn_path_uri_encode(name, pool)); +} + +const char * +svn_log__commit(svn_revnum_t rev, apr_pool_t *pool) +{ + return apr_psprintf(pool, "commit r%ld", rev); +} + +const char * +svn_log__get_file(const char *path, svn_revnum_t rev, + svn_boolean_t want_contents, svn_boolean_t want_props, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "get-file %s r%ld%s%s", + svn_path_uri_encode(path, pool), rev, + want_contents ? " text" : "", + want_props ? " props" : ""); +} + +const char * +svn_log__get_dir(const char *path, svn_revnum_t rev, + svn_boolean_t want_contents, svn_boolean_t want_props, + apr_uint64_t dirent_fields, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "get-dir %s r%ld%s%s", + svn_path_uri_encode(path, pool), rev, + want_contents ? " text" : "", + want_props ? " props" : ""); +} + +const char * +svn_log__get_mergeinfo(const apr_array_header_t *paths, + svn_mergeinfo_inheritance_t inherit, + svn_boolean_t include_descendants, + apr_pool_t *pool) +{ + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + svn_stringbuf_t *space_separated_paths = svn_stringbuf_create("", pool); + + for (i = 0; i < paths->nelts; i++) + { + const char *path = APR_ARRAY_IDX(paths, i, const char *); + svn_pool_clear(iterpool); + if (i != 0) + svn_stringbuf_appendcstr(space_separated_paths, " "); + svn_stringbuf_appendcstr(space_separated_paths, + svn_path_uri_encode(path, iterpool)); + } + svn_pool_destroy(iterpool); + + return apr_psprintf(pool, "get-mergeinfo (%s) %s%s", + space_separated_paths->data, + svn_inheritance_to_word(inherit), + include_descendants ? " include-descendants" : ""); +} + +const char * +svn_log__checkout(const char *path, svn_revnum_t rev, svn_depth_t depth, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "checkout-or-export %s r%ld%s", + svn_path_uri_encode(path, pool), rev, + log_depth(depth, pool)); +} + +const char * +svn_log__update(const char *path, svn_revnum_t rev, svn_depth_t depth, + svn_boolean_t send_copyfrom_args, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "update %s r%ld%s%s", + svn_path_uri_encode(path, pool), rev, + log_depth(depth, pool), + (send_copyfrom_args + ? " send-copyfrom-args" + : "")); +} + +const char * +svn_log__switch(const char *path, const char *dst_path, svn_revnum_t revnum, + svn_depth_t depth, apr_pool_t *pool) +{ + return apr_psprintf(pool, "switch %s %s@%ld%s", + svn_path_uri_encode(path, pool), + svn_path_uri_encode(dst_path, pool), revnum, + log_depth(depth, pool)); +} + +const char * +svn_log__status(const char *path, svn_revnum_t rev, svn_depth_t depth, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "status %s r%ld%s", + svn_path_uri_encode(path, pool), rev, + log_depth(depth, pool)); +} + +const char * +svn_log__diff(const char *path, svn_revnum_t from_revnum, + const char *dst_path, svn_revnum_t revnum, + svn_depth_t depth, svn_boolean_t ignore_ancestry, + apr_pool_t *pool) +{ + const char *log_ignore_ancestry = (ignore_ancestry + ? " ignore-ancestry" + : ""); + if (strcmp(path, dst_path) == 0) + return apr_psprintf(pool, "diff %s r%ld:%ld%s%s", + svn_path_uri_encode(path, pool), from_revnum, revnum, + log_depth(depth, pool), log_ignore_ancestry); + return apr_psprintf(pool, "diff %s@%ld %s@%ld%s%s", + svn_path_uri_encode(path, pool), from_revnum, + svn_path_uri_encode(dst_path, pool), revnum, + log_depth(depth, pool), log_ignore_ancestry); +} + +const char * +svn_log__log(const apr_array_header_t *paths, + svn_revnum_t start, svn_revnum_t end, + int limit, svn_boolean_t discover_changed_paths, + svn_boolean_t strict_node_history, + svn_boolean_t include_merged_revisions, + const apr_array_header_t *revprops, apr_pool_t *pool) +{ + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + svn_stringbuf_t *space_separated_paths = svn_stringbuf_create("", pool); + svn_stringbuf_t *options = svn_stringbuf_create("", pool); + + for (i = 0; i < paths->nelts; i++) + { + const char *path = APR_ARRAY_IDX(paths, i, const char *); + svn_pool_clear(iterpool); + if (i != 0) + svn_stringbuf_appendcstr(space_separated_paths, " "); + svn_stringbuf_appendcstr(space_separated_paths, + svn_path_uri_encode(path, iterpool)); + } + + if (limit) + { + const char *tmp = apr_psprintf(pool, " limit=%d", limit); + svn_stringbuf_appendcstr(options, tmp); + } + if (discover_changed_paths) + svn_stringbuf_appendcstr(options, " discover-changed-paths"); + if (strict_node_history) + svn_stringbuf_appendcstr(options, " strict"); + if (include_merged_revisions) + svn_stringbuf_appendcstr(options, + log_include_merged_revisions(include_merged_revisions)); + if (revprops == NULL) + svn_stringbuf_appendcstr(options, " revprops=all"); + else if (revprops->nelts > 0) + { + svn_stringbuf_appendcstr(options, " revprops=("); + for (i = 0; i < revprops->nelts; i++) + { + const char *name = APR_ARRAY_IDX(revprops, i, const char *); + svn_pool_clear(iterpool); + if (i != 0) + svn_stringbuf_appendcstr(options, " "); + svn_stringbuf_appendcstr(options, svn_path_uri_encode(name, + iterpool)); + } + svn_stringbuf_appendcstr(options, ")"); + } + svn_pool_destroy(iterpool); + return apr_psprintf(pool, "log (%s) r%ld:%ld%s", + space_separated_paths->data, start, end, + options->data); +} + +const char * +svn_log__get_locations(const char *path, svn_revnum_t peg_revision, + const apr_array_header_t *location_revisions, + apr_pool_t *pool) +{ + const svn_revnum_t *revision_ptr, *revision_ptr_start, *revision_ptr_end; + apr_pool_t *iterpool = svn_pool_create(pool); + svn_stringbuf_t *space_separated_revnums = svn_stringbuf_create("", pool); + + revision_ptr_start = (const svn_revnum_t *)location_revisions->elts; + revision_ptr = revision_ptr_start; + revision_ptr_end = revision_ptr + location_revisions->nelts; + while (revision_ptr < revision_ptr_end) + { + svn_pool_clear(iterpool); + if (revision_ptr != revision_ptr_start) + svn_stringbuf_appendcstr(space_separated_revnums, " "); + svn_stringbuf_appendcstr(space_separated_revnums, + apr_psprintf(iterpool, "%ld", *revision_ptr)); + ++revision_ptr; + } + svn_pool_destroy(iterpool); + + return apr_psprintf(pool, "get-locations %s@%ld (%s)", + svn_path_uri_encode(path, pool), + peg_revision, space_separated_revnums->data); +} + +const char * +svn_log__get_location_segments(const char *path, svn_revnum_t peg_revision, + svn_revnum_t start, svn_revnum_t end, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "get-location-segments %s@%ld r%ld:%ld", + svn_path_uri_encode(path, pool), + peg_revision, start, end); +} + +const char * +svn_log__get_file_revs(const char *path, svn_revnum_t start, svn_revnum_t end, + svn_boolean_t include_merged_revisions, + apr_pool_t *pool) +{ + return apr_psprintf(pool, "get-file-revs %s r%ld:%ld%s", + svn_path_uri_encode(path, pool), start, end, + log_include_merged_revisions(include_merged_revisions)); +} + +const char * +svn_log__lock(const apr_array_header_t *paths, + svn_boolean_t steal, apr_pool_t *pool) +{ + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + svn_stringbuf_t *space_separated_paths = svn_stringbuf_create("", pool); + + for (i = 0; i < paths->nelts; i++) + { + const char *path = APR_ARRAY_IDX(paths, i, const char *); + svn_pool_clear(iterpool); + if (i != 0) + svn_stringbuf_appendcstr(space_separated_paths, " "); + svn_stringbuf_appendcstr(space_separated_paths, + svn_path_uri_encode(path, iterpool)); + } + svn_pool_destroy(iterpool); + + return apr_psprintf(pool, "lock (%s)%s", space_separated_paths->data, + steal ? " steal" : ""); +} + +const char * +svn_log__unlock(const apr_array_header_t *paths, + svn_boolean_t break_lock, apr_pool_t *pool) +{ + int i; + apr_pool_t *iterpool = svn_pool_create(pool); + svn_stringbuf_t *space_separated_paths = svn_stringbuf_create("", pool); + + for (i = 0; i < paths->nelts; i++) + { + const char *path = APR_ARRAY_IDX(paths, i, const char *); + svn_pool_clear(iterpool); + if (i != 0) + svn_stringbuf_appendcstr(space_separated_paths, " "); + svn_stringbuf_appendcstr(space_separated_paths, + svn_path_uri_encode(path, iterpool)); + } + svn_pool_destroy(iterpool); + + return apr_psprintf(pool, "unlock (%s)%s", space_separated_paths->data, + break_lock ? " break" : ""); +} + +const char * +svn_log__lock_one_path(const char *path, svn_boolean_t steal, + apr_pool_t *pool) +{ + apr_array_header_t *paths = apr_array_make(pool, 1, sizeof(path)); + APR_ARRAY_PUSH(paths, const char *) = path; + return svn_log__lock(paths, steal, pool); +} + +const char * +svn_log__unlock_one_path(const char *path, svn_boolean_t break_lock, + apr_pool_t *pool) +{ + apr_array_header_t *paths = apr_array_make(pool, 1, sizeof(path)); + APR_ARRAY_PUSH(paths, const char *) = path; + return svn_log__unlock(paths, break_lock, pool); +} + +const char * +svn_log__replay(const char *path, svn_revnum_t rev, apr_pool_t *pool) +{ + const char *log_path; + + if (path && path[0] != '\0') + log_path = svn_path_uri_encode(path, pool); + else + log_path = "/"; + return apr_psprintf(pool, "replay %s r%ld", log_path, rev); +} diff --git a/subversion/mod_dav_svn/deadprops.c b/subversion/mod_dav_svn/deadprops.c index 4f367e055..fc205a0f7 100644 --- a/subversion/mod_dav_svn/deadprops.c +++ b/subversion/mod_dav_svn/deadprops.c @@ -26,6 +26,7 @@ #include "svn_dav.h" #include "svn_base64.h" #include "svn_props.h" +#include "private/svn_log.h" #include "dav_svn.h" @@ -171,11 +172,10 @@ save_value(dav_db *db, const dav_prop_name *name, const svn_string_t *value) /* Tell the logging subsystem about the revprop change. */ dav_svn__operational_log(db->resource->info, - apr_psprintf(db->resource->pool, - "change-rev-prop r%ld %s", - db->resource->info->root.rev, - svn_path_uri_encode(propname, - db->resource->pool))); + svn_log__change_rev_prop( + db->resource->info->root.rev, + propname, + db->resource->pool)); } else serr = svn_repos_fs_change_node_prop(db->resource->info->root.root, @@ -507,7 +507,7 @@ static dav_error * db_first_name(dav_db *db, dav_prop_name *pname) { /* for operational logging */ - char *action = NULL; + const char *action = NULL; /* if we don't have a copy of the properties, then get one */ if (db->props == NULL) @@ -523,8 +523,8 @@ db_first_name(dav_db *db, dav_prop_name *pname) db->p); else { - action = apr_psprintf(db->resource->pool, "rev-proplist r%ld", - db->resource->info->root.rev); + action = svn_log__rev_proplist(db->resource->info->root.rev, + db->resource->pool); serr = svn_repos_fs_revision_proplist (&db->props, db->resource->info->repos->repos, @@ -547,11 +547,16 @@ db_first_name(dav_db *db, dav_prop_name *pname) db->p); if (! serr) - action = apr_psprintf(db->resource->pool, "get-%s %s r%ld props", - (kind == svn_node_dir ? "dir" : "file"), - svn_path_uri_encode(db->resource->info->repos_path, - db->resource->pool), - db->resource->info->root.rev); + { + if (kind == svn_node_dir) + action = svn_log__get_dir(db->resource->info->repos_path, + db->resource->info->root.rev, + FALSE, TRUE, 0, db->resource->pool); + else + action = svn_log__get_file(db->resource->info->repos_path, + db->resource->info->root.rev, + FALSE, TRUE, db->resource->pool); + } } if (serr != NULL) return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR, diff --git a/subversion/mod_dav_svn/lock.c b/subversion/mod_dav_svn/lock.c index 581f00a9a..a5f917021 100644 --- a/subversion/mod_dav_svn/lock.c +++ b/subversion/mod_dav_svn/lock.c @@ -28,6 +28,7 @@ #include "svn_dav.h" #include "svn_time.h" #include "svn_pools.h" +#include "private/svn_log.h" #include "dav_svn.h" @@ -759,11 +760,8 @@ append_locks(dav_lockdb *lockdb, /* Log the locking as a 'high-level' action. */ dav_svn__operational_log(resource->info, - apr_psprintf(resource->info->r->pool, - "lock (%s)%s", - svn_path_uri_encode(slock->path, - resource->info->r->pool), - info->lock_steal ? " steal" : "")); + svn_log__lock_one_path(slock->path, info->lock_steal, + resource->info->r->pool)); return 0; } @@ -847,11 +845,10 @@ remove_lock(dav_lockdb *lockdb, /* Log the unlocking as a 'high-level' action. */ dav_svn__operational_log(resource->info, - apr_psprintf(resource->info->r->pool, - "unlock (%s)%s", - svn_path_uri_encode(resource->info->repos_path, - resource->info->r->pool), - info->lock_break ? " break" : "")); + svn_log__unlock_one_path( + resource->info->repos_path, + info->lock_break, + resource->info->r->pool)); } return 0; diff --git a/subversion/mod_dav_svn/reports/file-revs.c b/subversion/mod_dav_svn/reports/file-revs.c index 2ca41704a..b902eaa5a 100644 --- a/subversion/mod_dav_svn/reports/file-revs.c +++ b/subversion/mod_dav_svn/reports/file-revs.c @@ -25,6 +25,7 @@ #include "svn_base64.h" #include "svn_props.h" #include "svn_dav.h" +#include "private/svn_log.h" #include "../dav_svn.h" @@ -324,12 +325,9 @@ dav_svn__file_revs_report(const dav_resource *resource, /* We've detected a 'high level' svn action to log. */ dav_svn__operational_log(resource->info, - apr_psprintf(resource->pool, - "get-file-revs %s r%ld:%ld%s", - svn_path_uri_encode(path, resource->pool), - start, end, - (include_merged_revisions - ? " include-merged-revisions" : ""))); + svn_log__get_file_revs(path, start, end, + include_merged_revisions, + resource->pool)); /* Flush the contents of the brigade (returning an error only if we don't already have one). */ diff --git a/subversion/mod_dav_svn/reports/log.c b/subversion/mod_dav_svn/reports/log.c index 9c1be9551..19b805f62 100644 --- a/subversion/mod_dav_svn/reports/log.c +++ b/subversion/mod_dav_svn/reports/log.c @@ -29,6 +29,7 @@ #include "svn_path.h" #include "svn_dav.h" #include "svn_pools.h" +#include "private/svn_log.h" #include "../dav_svn.h" @@ -272,10 +273,6 @@ dav_svn__log_report(const dav_resource *resource, sizeof(const char *)); apr_array_header_t *paths = apr_array_make(resource->pool, 1, sizeof(const char *)); - svn_stringbuf_t *space_separated_paths = - svn_stringbuf_create("", resource->pool); - svn_stringbuf_t *space_separated_revprops = - svn_stringbuf_create("", resource->pool); /* Sanity check. */ ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE); @@ -330,11 +327,6 @@ dav_svn__log_report(const dav_resource *resource, && strcmp(name, SVN_PROP_REVISION_DATE) != 0 && strcmp(name, SVN_PROP_REVISION_LOG) != 0) lrb.requested_custom_revprops = TRUE; - - /* Gather a formatted list of revprops for operational logging. */ - if (space_separated_revprops->len > 1) - svn_stringbuf_appendcstr(space_separated_revprops, " "); - svn_stringbuf_appendcstr(space_separated_revprops, name); } seen_revprop_element = TRUE; } @@ -346,14 +338,6 @@ dav_svn__log_report(const dav_resource *resource, target = svn_path_join(resource->info->repos_path, rel_path, resource->pool); APR_ARRAY_PUSH(paths, const char *) = target; - - /* Gather a formatted list of paths to include in our - operational logging. */ - if (space_separated_paths->len > 1) - svn_stringbuf_appendcstr(space_separated_paths, " "); - svn_stringbuf_appendcstr(space_separated_paths, - svn_path_uri_encode(target, - resource->pool)); } /* else unknown element; skip it */ } @@ -424,36 +408,12 @@ dav_svn__log_report(const dav_resource *resource, cleanup: - { - /* We've detected a 'high level' svn action to log. */ - svn_stringbuf_t *options = svn_stringbuf_create("", resource->pool); - const char *action; - - if (limit) - { - char *tmp = apr_psprintf(resource->pool, " limit=%d", limit); - svn_stringbuf_appendcstr(options, tmp); - } - if (discover_changed_paths) - svn_stringbuf_appendcstr(options, " discover-changed-paths"); - if (strict_node_history) - svn_stringbuf_appendcstr(options, " strict"); - if (include_merged_revisions) - svn_stringbuf_appendcstr(options, " include-merged-revisions"); - if (revprops == NULL) - svn_stringbuf_appendcstr(options, " revprops=all"); - else if (revprops->nelts > 0) - { - svn_stringbuf_appendcstr(options, " revprops=("); - svn_stringbuf_appendstr(options, space_separated_revprops); - svn_stringbuf_appendcstr(options, ")"); - } - - action = apr_psprintf(resource->pool, "log (%s) r%ld:%ld%s", - space_separated_paths->data, start, end, - options->data); - dav_svn__operational_log(resource->info, action); - } + dav_svn__operational_log(resource->info, + svn_log__log(paths, start, end, limit, + discover_changed_paths, + strict_node_history, + include_merged_revisions, revprops, + resource->pool)); /* Flush the contents of the brigade (returning an error only if we don't already have one). */ diff --git a/subversion/mod_dav_svn/reports/mergeinfo.c b/subversion/mod_dav_svn/reports/mergeinfo.c index 238ac9118..7ea287516 100644 --- a/subversion/mod_dav_svn/reports/mergeinfo.c +++ b/subversion/mod_dav_svn/reports/mergeinfo.c @@ -31,6 +31,7 @@ #include "svn_path.h" #include "svn_dav.h" #include "private/svn_dav_protocol.h" +#include "private/svn_log.h" #include "private/svn_mergeinfo_private.h" #include "../dav_svn.h" @@ -49,7 +50,6 @@ dav_svn__get_mergeinfo_report(const dav_resource *resource, svn_boolean_t include_descendants = FALSE; dav_svn__authz_read_baton arb; const dav_svn_repos *repos = resource->info->repos; - const char *action; int ns; apr_bucket_brigade *bb; apr_hash_index_t *hi; @@ -61,9 +61,6 @@ dav_svn__get_mergeinfo_report(const dav_resource *resource, svn_mergeinfo_inheritance_t inherit = svn_mergeinfo_explicit; apr_array_header_t *paths = apr_array_make(resource->pool, 0, sizeof(const char *)); - /* for high-level logging */ - svn_stringbuf_t *space_separated_paths = - svn_stringbuf_create("", resource->pool); /* Sanity check. */ ns = dav_svn__find_ns(doc->namespaces, SVN_XML_NAMESPACE); @@ -99,13 +96,6 @@ dav_svn__get_mergeinfo_report(const dav_resource *resource, target = svn_path_join(resource->info->repos_path, rel_path, resource->pool); (*((const char **)(apr_array_push(paths)))) = target; - /* Gather a formatted list of paths to include in our - operational logging. */ - if (space_separated_paths->len > 1) - svn_stringbuf_appendcstr(space_separated_paths, " "); - svn_stringbuf_appendcstr(space_separated_paths, - svn_path_uri_encode(target, - resource->pool)); } else if (strcmp(child->name, SVN_DAV__INCLUDE_DESCENDANTS) == 0) { @@ -220,11 +210,10 @@ dav_svn__get_mergeinfo_report(const dav_resource *resource, cleanup: /* We've detected a 'high level' svn action to log. */ - action = apr_psprintf(resource->pool, "get-mergeinfo (%s) %s%s", - space_separated_paths->data, - svn_inheritance_to_word(inherit), - include_descendants ? " include-descendants" : ""); - dav_svn__operational_log(resource->info, action); + dav_svn__operational_log(resource->info, + svn_log__get_mergeinfo(paths, inherit, + include_descendants, + resource->pool)); /* We don't flush the brigade unless there's something in it to flush; that way, if we jumped to 'cleanup' before sending diff --git a/subversion/mod_dav_svn/reports/replay.c b/subversion/mod_dav_svn/reports/replay.c index 4310849ff..81bf49a3c 100644 --- a/subversion/mod_dav_svn/reports/replay.c +++ b/subversion/mod_dav_svn/reports/replay.c @@ -34,6 +34,7 @@ #include "svn_path.h" #include "svn_dav.h" #include "svn_props.h" +#include "private/svn_log.h" #include "../dav_svn.h" @@ -485,17 +486,9 @@ dav_svn__replay_report(const dav_resource *resource, "Problem closing editor drive", resource->pool); - { - const char *action, *log_base_dir; - - if (base_dir && base_dir[0] != '\0') - log_base_dir = svn_path_uri_encode(base_dir, resource->info->r->pool); - else - log_base_dir = "/"; - action = apr_psprintf(resource->info->r->pool, "replay %s r%ld", - log_base_dir, rev); - dav_svn__operational_log(resource->info, action); - } + dav_svn__operational_log(resource->info, + svn_log__replay(base_dir, rev, + resource->info->r->pool)); /* Flush the brigade. */ if ((apr_err = ap_fflush(output, bb))) diff --git a/subversion/mod_dav_svn/reports/update.c b/subversion/mod_dav_svn/reports/update.c index cbcc7c85b..b9c9b84ea 100644 --- a/subversion/mod_dav_svn/reports/update.c +++ b/subversion/mod_dav_svn/reports/update.c @@ -34,6 +34,7 @@ #include "svn_path.h" #include "svn_dav.h" #include "svn_props.h" +#include "private/svn_log.h" #include "../dav_svn.h" @@ -914,6 +915,7 @@ dav_svn__update_report(const dav_resource *resource, svn_revnum_t revnum = SVN_INVALID_REVNUM; svn_revnum_t from_revnum = SVN_INVALID_REVNUM; int ns; + /* entry_counter and entry_is_empty are for operational logging. */ int entry_counter = 0; svn_boolean_t entry_is_empty = FALSE; svn_error_t *serr; @@ -1335,32 +1337,12 @@ dav_svn__update_report(const dav_resource *resource, { /* diff/merge don't ask for inline text-deltas. */ if (uc.send_all) - action = apr_psprintf(resource->pool, - "switch %s %s@%ld%s", - svn_path_uri_encode(spath, resource->pool), - svn_path_uri_encode(dst_path, resource->pool), - revnum, log_depth); + action = svn_log__switch(spath, dst_path, revnum, + requested_depth, resource->pool); else - { - if (strcmp(spath, dst_path) == 0) - action = apr_psprintf(resource->pool, - "diff %s r%ld:%ld%s%s", - svn_path_uri_encode(spath, resource->pool), - from_revnum, - revnum, log_depth, - ignore_ancestry ? " ignore-ancestry" : ""); - else - action = apr_psprintf(resource->pool, - "diff %s@%ld %s@%ld%s%s", - svn_path_uri_encode(spath, resource->pool), - from_revnum, - svn_path_uri_encode(dst_path, - resource->pool), - revnum, log_depth, - (ignore_ancestry - ? " ignore-ancestry" - : "")); - } + action = svn_log__diff(spath, from_revnum, dst_path, revnum, + requested_depth, ignore_ancestry, + resource->pool); } /* Otherwise, it must be checkout, export, update, or status -u. */ @@ -1369,29 +1351,17 @@ dav_svn__update_report(const dav_resource *resource, /* svn_client_checkout() creates a single root directory, then reports it (and it alone) to the server as being empty. */ if (entry_counter == 1 && entry_is_empty) - action = apr_psprintf(resource->pool, - "checkout-or-export %s r%ld%s", - svn_path_uri_encode(spath, resource->pool), - revnum, - log_depth); + action = svn_log__checkout(spath, revnum, requested_depth, + resource->pool); else { if (text_deltas) - action = apr_psprintf(resource->pool, - "update %s r%ld%s%s", - svn_path_uri_encode(spath, - resource->pool), - revnum, - log_depth, - (send_copyfrom_args - ? " send-copyfrom-args" : "")); + action = svn_log__update(spath, revnum, requested_depth, + send_copyfrom_args, + resource->pool); else - action = apr_psprintf(resource->pool, - "status %s r%ld%s", - svn_path_uri_encode(spath, - resource->pool), - revnum, - log_depth); + action = svn_log__status(spath, revnum, requested_depth, + resource->pool); } } diff --git a/subversion/mod_dav_svn/repos.c b/subversion/mod_dav_svn/repos.c index c11114f2a..4a0c92309 100644 --- a/subversion/mod_dav_svn/repos.c +++ b/subversion/mod_dav_svn/repos.c @@ -41,6 +41,7 @@ #include "svn_props.h" #include "mod_dav_svn.h" #include "svn_ra.h" /* for SVN_RA_CAPABILITY_* */ +#include "private/svn_log.h" #include "dav_svn.h" @@ -3467,11 +3468,10 @@ do_walk(walker_ctx_t *ctx, int depth) header and distinguish an svn client ('svn ls') from a generic DAV client. */ dav_svn__operational_log(&ctx->info, - apr_psprintf(params->pool, - "get-dir %s r%ld text", - svn_path_uri_encode(ctx->info.repos_path, - params->pool), - ctx->info.root.rev)); + svn_log__get_dir(ctx->info.repos_path, + ctx->info.root.rev, + TRUE, FALSE, SVN_DIRENT_ALL, + params->pool)); /* fetch this collection's children */ serr = svn_fs_dir_entries(&children, ctx->info.root.root, diff --git a/subversion/mod_dav_svn/version.c b/subversion/mod_dav_svn/version.c index cbc4d1e33..0a2d155d4 100644 --- a/subversion/mod_dav_svn/version.c +++ b/subversion/mod_dav_svn/version.c @@ -33,6 +33,7 @@ #include "svn_dav.h" #include "svn_base64.h" #include "private/svn_dav_protocol.h" +#include "private/svn_log.h" #include "dav_svn.h" @@ -1340,9 +1341,7 @@ merge(dav_resource *target, /* We've detected a 'high level' svn action to log. */ dav_svn__operational_log(target->info, - apr_psprintf(target->info->r->pool, - "commit r%ld", - new_rev)); + svn_log__commit(new_rev, target->info->r->pool)); /* Since the commit was successful, the txn ID is no longer valid. Store an empty txn ID in the activity database so that when the diff --git a/subversion/svnserve/log-escape.c b/subversion/svnserve/log-escape.c new file mode 100644 index 000000000..171ef6e85 --- /dev/null +++ b/subversion/svnserve/log-escape.c @@ -0,0 +1,136 @@ +/* + * log-escape.c : Functions for escaping log items + * copied from Apache httpd + * + * ==================================================================== + * 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. + * + * ==================================================================== + * + * Copyright (c) 2008 CollabNet. All rights reserved. + * + * This software is licensed as described in the file COPYING, which + * you should have received as part of this distribution. The terms + * are also available at http://subversion.tigris.org/license-1.html. + * If newer versions of this license are posted there, you may use a + * newer version instead, at your option. + * + * This software consists of voluntary contributions made by many + * individuals. For exact contribution history, see the revision + * history and logs, available at http://subversion.tigris.org/. + * ==================================================================== + */ + + +#include +#include +#define APR_WANT_STRFUNC +#include + +/* copied from httpd-2.2.4/server/util.c */ +/* c2x takes an unsigned, and expects the caller has guaranteed that + * 0 <= what < 256... which usually means that you have to cast to + * unsigned char first, because (unsigned)(char)(x) first goes through + * signed extension to an int before the unsigned cast. + * + * The reason for this assumption is to assist gcc code generation -- + * the unsigned char -> unsigned extension is already done earlier in + * both uses of this code, so there's no need to waste time doing it + * again. + */ +static const char c2x_table[] = "0123456789abcdef"; + +/* copied from httpd-2.2.4/server/util.c */ +static APR_INLINE unsigned char *c2x(unsigned what, unsigned char prefix, + unsigned char *where) +{ +#if APR_CHARSET_EBCDIC + what = apr_xlate_conv_byte(ap_hdrs_to_ascii, (unsigned char)what); +#endif /*APR_CHARSET_EBCDIC*/ + *where++ = prefix; + *where++ = c2x_table[what >> 4]; + *where++ = c2x_table[what & 0xf]; + return where; +} + +/* copied from httpd-2.2.4/server/util.c */ +apr_size_t escape_errorlog_item(char *dest, const char *source, + apr_size_t buflen) +{ + unsigned char *d, *ep; + const unsigned char *s; + + if (!source || !buflen) { /* be safe */ + return 0; + } + + d = (unsigned char *)dest; + s = (const unsigned char *)source; + ep = d + buflen - 1; + + for (; d < ep && *s; ++s) { + + /* httpd-2.2.4/server/util.c has this: + if (TEST_CHAR(*s, T_ESCAPE_LOGITEM)) { + which does this same check with a fast lookup table. Well, + mostly the same; we don't escape quotes, as that does. + */ + if (*s && (!apr_isprint(*s) || *s == '\\' || apr_iscntrl(*s))) { + *d++ = '\\'; + if (d >= ep) { + --d; + break; + } + + switch(*s) { + case '\b': + *d++ = 'b'; + break; + case '\n': + *d++ = 'n'; + break; + case '\r': + *d++ = 'r'; + break; + case '\t': + *d++ = 't'; + break; + case '\v': + *d++ = 'v'; + break; + case '\\': + *d++ = *s; + break; + case '"': /* no need for this in error log */ + d[-1] = *s; + break; + default: + if (d >= ep - 2) { + ep = --d; /* break the for loop as well */ + break; + } + c2x(*s, 'x', d); + d += 3; + } + } + else { + *d++ = *s; + } + } + *d = '\0'; + + return (d - (unsigned char *)dest); +} diff --git a/subversion/svnserve/main.c b/subversion/svnserve/main.c index 03943f813..77866622b 100644 --- a/subversion/svnserve/main.c +++ b/subversion/svnserve/main.c @@ -136,6 +136,7 @@ void winservice_notify_stop(void) #define SVNSERVE_OPT_PID_FILE 261 #define SVNSERVE_OPT_SERVICE 262 #define SVNSERVE_OPT_CONFIG_FILE 263 +#define SVNSERVE_OPT_LOG_FILE 264 static const apr_getopt_option_t svnserve__options[] = { @@ -182,6 +183,8 @@ static const apr_getopt_option_t svnserve__options[] = N_("run in foreground (useful for debugging)\n" " " "[mode: daemon]")}, + {"log-file", SVNSERVE_OPT_LOG_FILE, 1, + N_("svnserve log file")}, {"pid-file", SVNSERVE_OPT_PID_FILE, 1, #ifdef WIN32 N_("write server process ID to file ARG\n" @@ -374,6 +377,7 @@ int main(int argc, const char *argv[]) int mode_opt_count = 0; const char *config_filename = NULL; const char *pid_filename = NULL; + const char *log_filename = NULL; svn_node_kind_t kind; /* Initialize the app. */ @@ -408,6 +412,7 @@ int main(int argc, const char *argv[]) params.cfg = NULL; params.pwdb = NULL; params.authzdb = NULL; + params.log_file = NULL; while (1) { @@ -527,6 +532,13 @@ int main(int argc, const char *argv[]) pool)); break; + case SVNSERVE_OPT_LOG_FILE: + SVN_INT_ERR(svn_utf_cstring_to_utf8(&log_filename, arg, pool)); + log_filename = svn_path_internal_style(log_filename, pool); + SVN_INT_ERR(svn_path_get_absolute(&log_filename, log_filename, + pool)); + break; + } } if (os->ind != argc) @@ -553,6 +565,11 @@ int main(int argc, const char *argv[]) svn_path_dirname(config_filename, pool), pool)); + if (log_filename) + SVN_INT_ERR(svn_io_file_open(¶ms.log_file, log_filename, + APR_WRITE | APR_CREATE | APR_APPEND, + APR_OS_DEFAULT, pool)); + if (params.tunnel_user && run_mode != run_mode_tunnel) { svn_error_clear diff --git a/subversion/svnserve/serve.c b/subversion/svnserve/serve.c index 462121943..0bfd033b7 100644 --- a/subversion/svnserve/serve.c +++ b/subversion/svnserve/serve.c @@ -20,10 +20,12 @@ #include /* for UINT_MAX */ +#include #define APR_WANT_STRFUNC #include #include +#include #include #include @@ -44,8 +46,13 @@ #include "svn_mergeinfo.h" #include "svn_user.h" +#include "private/svn_log.h" #include "private/svn_mergeinfo_private.h" +#ifdef HAVE_UNISTD_H +#include /* For getpid() */ +#endif + #include "server.h" typedef struct { @@ -61,6 +68,11 @@ typedef struct { const char *repos_url; /* Decoded repository URL. */ void *report_baton; svn_error_t *err; + /* so update() can distinguish checkout from update in logging */ + int entry_counter; + svn_boolean_t only_empty_entries; + /* for diff() logging */ + svn_revnum_t *from_rev; } report_driver_baton_t; typedef struct { @@ -74,6 +86,12 @@ typedef struct { apr_pool_t *pool; /* Pool provided in the handler call. */ } file_revs_baton_t; +typedef struct { + server_baton_t *server; + svn_ra_svn_conn_t *conn; + apr_pool_t *pool; +} fs_warning_baton_t; + svn_error_t *load_configs(svn_config_t **cfg, svn_config_t **pwdb, svn_authz_t **authzdb, @@ -148,6 +166,84 @@ static svn_error_t *get_fs_path(const char *repos_url, const char *url, return SVN_NO_ERROR; } +static void +log_fs_warning(void *baton, svn_error_t *err) +{ + fs_warning_baton_t *b = baton; + server_baton_t *server = b->server; + svn_ra_svn_conn_t *conn = b->conn; + const char *timestr, *remote_host, *user, *continuation; + char errbuf[256]; + /* 8192 from MAX_STRING_LEN in from httpd-2.2.4/include/httpd.h */ + char errstr[8192]; + + if (server->log_file == NULL) + return; + + svn_pool_clear(b->pool); + timestr = svn_time_to_cstring(apr_time_now(), b->pool); + remote_host = svn_ra_svn_conn_remote_host(conn); + remote_host = (remote_host ? remote_host : "-"); + user = (server->user ? server->user : "-"); + + continuation = ""; + while (err != NULL) + { + const char *message = svn_err_best_message(err, errbuf, sizeof(errbuf)); + /* based on httpd-2.2.4/server/log.c:log_error_core */ + apr_size_t len = apr_snprintf(errstr, sizeof(errstr), + "%" APR_PID_T_FMT + " %s %s %s %s ERR%s %d ", + getpid(), timestr, remote_host, user, + server->repos_name, continuation, + err->apr_err); + + len += escape_errorlog_item(errstr + len, message, + sizeof(errstr) - len); + /* Truncate for the terminator (as apr_snprintf does) */ + if (len > sizeof(errstr) - sizeof(APR_EOL_STR)) { + len = sizeof(errstr) - sizeof(APR_EOL_STR); + } + strcpy(errstr + len, APR_EOL_STR); + len += strlen(APR_EOL_STR); + svn_error_clear(svn_io_file_write(server->log_file, errstr, &len, + b->pool)); + + continuation = "-"; + err = err->child; + } +} + +static svn_error_t *svnserve_log(server_baton_t *b, + svn_ra_svn_conn_t *conn, + apr_pool_t *pool, + const char *fmt, ...) +{ + const char *remote_host, *timestr, *log, *line; + va_list ap; + apr_size_t nbytes; + + if (b->log_file == NULL) + return SVN_NO_ERROR; + + remote_host = svn_ra_svn_conn_remote_host(conn); + timestr = svn_time_to_cstring(apr_time_now(), pool); + + va_start(ap, fmt); + log = apr_pvsprintf(pool, fmt, ap); + va_end(ap); + + line = apr_psprintf(pool, "%" APR_PID_T_FMT + " %s %s %s %s %s" APR_EOL_STR, + getpid(), timestr, + (remote_host ? remote_host : "-"), + (b->user ? b->user : "-"), b->repos_name, log); + nbytes = strlen(line); + return svn_io_file_write(b->log_file, line, &nbytes, pool); +} + +#define SLOG(...) SVN_ERR(svnserve_log(baton, conn, pool, __VA_ARGS__)) + /* --- AUTHENTICATION AND AUTHORIZATION FUNCTIONS --- */ /* Set *ALLOWED to TRUE if PATH is accessible in the REQUIRED mode to @@ -532,9 +628,14 @@ static svn_error_t *set_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, if (depth_word) depth = svn_depth_from_word(depth_word); path = svn_path_canonicalize(path, pool); + if (b->from_rev && strcmp(path, "") == 0) + *b->from_rev = rev; if (!b->err) b->err = svn_repos_set_path3(b->report_baton, path, rev, depth, start_empty, lock_token, pool); + b->entry_counter++; + if (!start_empty) + b->only_empty_entries = FALSE; return SVN_NO_ERROR; } @@ -574,6 +675,7 @@ static svn_error_t *link_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, if (!b->err) b->err = svn_repos_link_path3(b->report_baton, path, fs_path, rev, depth, start_empty, lock_token, pool); + b->entry_counter++; return SVN_NO_ERROR; } @@ -611,8 +713,19 @@ static const svn_ra_svn_cmd_entry_t report_commands[] = { /* Accept a report from the client, drive the network editor with the * result, and then write an empty command response. If there is a * non-protocol failure, accept_report will abort the edit and return - * a command error to be reported by handle_commands(). */ -static svn_error_t *accept_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool, + * a command error to be reported by handle_commands(). + * + * If only_empty_entry is not NULL and the report contains only one + * item, and that item is empty, set *only_empty_entry to TRUE, else + * set it to FALSE. + * + * If from_rev is not NULL, set *from_rev to the revision number from + * the set-path on ""; if somehow set-path "" never happens, set + * *from_rev to SVN_INVALID_REVNUM. + */ +static svn_error_t *accept_report(svn_boolean_t *only_empty_entry, + svn_revnum_t *from_rev, + svn_ra_svn_conn_t *conn, apr_pool_t *pool, server_baton_t *b, svn_revnum_t rev, const char *target, const char *tgt_path, svn_boolean_t text_deltas, @@ -640,6 +753,11 @@ static svn_error_t *accept_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool, rb.repos_url = svn_path_uri_decode(b->repos_url, pool); rb.report_baton = report_baton; rb.err = NULL; + rb.entry_counter = 0; + rb.only_empty_entries = TRUE; + rb.from_rev = from_rev; + if (from_rev) + *from_rev = SVN_INVALID_REVNUM; err = svn_ra_svn_handle_commands(conn, pool, report_commands, &rb); if (err) { @@ -653,6 +771,10 @@ static svn_error_t *accept_report(svn_ra_svn_conn_t *conn, apr_pool_t *pool, SVN_CMD_ERR(rb.err); } SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); + + if (only_empty_entry) + *only_empty_entry = rb.entry_counter == 1 && rb.only_empty_entries; + return SVN_NO_ERROR; } @@ -759,6 +881,7 @@ static svn_error_t *reparent(svn_ra_svn_conn_t *conn, apr_pool_t *pool, SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool), url, &fs_path)); + SLOG("%s", svn_log__reparent(fs_path, pool)); svn_stringbuf_set(b->fs_path, fs_path); SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "")); return SVN_NO_ERROR; @@ -770,6 +893,8 @@ static svn_error_t *get_latest_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool, server_baton_t *b = baton; svn_revnum_t rev; + SLOG("get-latest-rev"); + SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); SVN_ERR(svn_ra_svn_write_cmd_response(conn, pool, "r", rev)); @@ -785,6 +910,8 @@ static svn_error_t *get_dated_rev(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *timestr; SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "c", ×tr)); + SLOG("get-dated-rev %s", timestr); + SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_time_from_cstring(&tm, timestr, pool)); SVN_CMD_ERR(svn_repos_dated_revision(&rev, b->repos, tm, pool)); @@ -803,7 +930,9 @@ static svn_error_t *change_rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, /* Because the revprop value was at one time mandatory, the usual optional element pattern "(?s)" isn't used. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc?s", &rev, &name, &value)); + SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, FALSE)); + SLOG("%s", svn_log__change_rev_prop(rev, name, pool)); SVN_CMD_ERR(svn_repos_fs_change_rev_prop3(b->repos, rev, b->user, name, value, TRUE, TRUE, authz_check_access_cb_func(b), b, @@ -820,6 +949,8 @@ static svn_error_t *rev_proplist(svn_ra_svn_conn_t *conn, apr_pool_t *pool, apr_hash_t *props; SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "r", &rev)); + SLOG("%s", svn_log__rev_proplist(rev, pool)); + SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_repos_fs_revision_proplist(&props, b->repos, rev, authz_check_access_cb_func(b), b, @@ -839,6 +970,8 @@ static svn_error_t *rev_prop(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_string_t *value; SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "rc", &rev, &name)); + SLOG("%s", svn_log__rev_prop(rev, name, pool)); + SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_repos_fs_revision_prop(&value, b->repos, rev, name, authz_check_access_cb_func(b), b, @@ -1042,6 +1175,7 @@ static svn_error_t *commit(svn_ra_svn_conn_t *conn, apr_pool_t *pool, SVN_ERR(svn_ra_svn_drive_editor(conn, pool, editor, edit_baton, &aborted)); if (!aborted) { + SLOG("%s", svn_log__commit(new_rev, pool)); SVN_ERR(trivial_auth_request(conn, pool, b)); /* In tunnel mode, deltify before answering the client, because @@ -1094,6 +1228,7 @@ static svn_error_t *get_file(svn_ra_svn_conn_t *conn, apr_pool_t *pool, if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); + SLOG("%s", svn_log__get_file(full_path, rev, want_contents, want_props, pool)); /* Fetch the properties and a stream for the contents. */ SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool)); @@ -1209,6 +1344,8 @@ static svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool, if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); + SLOG("%s", svn_log__get_dir(full_path, rev, want_contents, want_props, + dirent_fields, pool)); /* Fetch the root of the appropriate revision. */ SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool)); @@ -1309,7 +1446,6 @@ static svn_error_t *get_dir(svn_ra_svn_conn_t *conn, apr_pool_t *pool, return SVN_NO_ERROR; } - static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool, apr_array_header_t *params, void *baton) { @@ -1322,6 +1458,7 @@ static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool, /* Default to unknown. Old clients won't send depth, but we'll handle that by converting recurse if necessary. */ svn_depth_t depth = svn_depth_unknown; + svn_boolean_t is_checkout; /* Parse the arguments. */ SVN_ERR(svn_ra_svn_parse_tuple(params, pool, "(?r)cb?wB", &rev, &target, @@ -1343,8 +1480,16 @@ static svn_error_t *update(svn_ra_svn_conn_t *conn, apr_pool_t *pool, if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); - return accept_report(conn, pool, b, rev, target, NULL, TRUE, - depth, send_copyfrom_args, FALSE); + SVN_ERR(accept_report(&is_checkout, NULL, + conn, pool, b, rev, target, NULL, TRUE, + depth, send_copyfrom_args, FALSE)); + if (is_checkout) + SLOG("%s", svn_log__checkout(full_path, rev, depth, pool)); + else + SLOG("%s", svn_log__update(full_path, rev, depth, send_copyfrom_args, + pool)); + + return SVN_NO_ERROR; } static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, @@ -1373,11 +1518,18 @@ static svn_error_t *switch_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, SVN_ERR(trivial_auth_request(conn, pool, b)); if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); + SVN_CMD_ERR(get_fs_path(svn_path_uri_decode(b->repos_url, pool), svn_path_uri_decode(switch_url, pool), &switch_path)); - return accept_report(conn, pool, b, rev, target, switch_path, TRUE, + { + const char *full_path = svn_path_join(b->fs_path->data, target, pool); + SLOG("%s", svn_log__switch(full_path, switch_path, rev, depth, pool)); + } + + return accept_report(NULL, NULL, + conn, pool, b, rev, target, switch_path, TRUE, depth, FALSE /* TODO(sussman): no copyfrom args for now */, TRUE); @@ -1408,7 +1560,12 @@ static svn_error_t *status(svn_ra_svn_conn_t *conn, apr_pool_t *pool, if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); - return accept_report(conn, pool, b, rev, target, NULL, FALSE, + { + const char *full_path = svn_path_join(b->fs_path->data, target, pool); + SLOG("%s", svn_log__status(full_path, rev, depth, pool)); + } + + return accept_report(NULL, NULL, conn, pool, b, rev, target, NULL, FALSE, depth, FALSE, FALSE); } @@ -1456,8 +1613,16 @@ static svn_error_t *diff(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_path_uri_decode(versus_url, pool), &versus_path)); - return accept_report(conn, pool, b, rev, target, versus_path, - text_deltas, depth, FALSE, ignore_ancestry); + { + const char *full_path = svn_path_join(b->fs_path->data, target, pool); + svn_revnum_t from_rev; + SVN_ERR(accept_report(NULL, &from_rev, + conn, pool, b, rev, target, versus_path, + text_deltas, depth, FALSE, ignore_ancestry)); + SLOG("%s", svn_log__diff(full_path, from_rev, versus_path, rev, depth, + ignore_ancestry, pool)); + } + return SVN_NO_ERROR; } /* Regardless of whether a client's capabilities indicate an @@ -1500,6 +1665,8 @@ static svn_error_t *get_mergeinfo(svn_ra_svn_conn_t *conn, apr_pool_t *pool, pool); APR_ARRAY_PUSH(canonical_paths, const char *) = full_path; } + SLOG("%s", svn_log__get_mergeinfo(canonical_paths, inherit, include_descendants, + pool)); SVN_ERR(trivial_auth_request(conn, pool, b)); SVN_CMD_ERR(svn_repos_fs_get_mergeinfo(&mergeinfo, b->repos, @@ -1674,6 +1841,10 @@ static svn_error_t *log_cmd(svn_ra_svn_conn_t *conn, apr_pool_t *pool, } SVN_ERR(trivial_auth_request(conn, pool, b)); + SLOG("%s", svn_log__log(full_paths, start_rev, end_rev, limit, + changed_paths, strict_node, include_merged_revisions, + revprops, pool)); + /* Get logs. (Can't report errors back to the client at this point.) */ lb.fs_path = b->fs_path->data; lb.conn = conn; @@ -1714,6 +1885,7 @@ static svn_error_t *check_path(svn_ra_svn_conn_t *conn, apr_pool_t *pool, if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); + SLOG("check-path %s@%d", svn_path_uri_encode(full_path, pool), rev); SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool)); SVN_CMD_ERR(svn_fs_check_path(&kind, root, full_path, pool)); @@ -1740,6 +1912,8 @@ static svn_error_t *stat(svn_ra_svn_conn_t *conn, apr_pool_t *pool, if (!SVN_IS_VALID_REVNUM(rev)) SVN_CMD_ERR(svn_fs_youngest_rev(&rev, b->fs, pool)); + SLOG("stat %s@%d", svn_path_uri_encode(full_path, pool), rev); + SVN_CMD_ERR(svn_fs_revision_root(&root, b->fs, rev, pool)); SVN_CMD_ERR(svn_repos_stat(&dirent, root, full_path, pool)); @@ -1802,6 +1976,8 @@ static svn_error_t *get_locations(svn_ra_svn_conn_t *conn, apr_pool_t *pool, APR_ARRAY_PUSH(location_revisions, svn_revnum_t) = revision; } SVN_ERR(trivial_auth_request(conn, pool, b)); + SLOG("%s", svn_log__get_locations(abs_path, peg_revision, location_revisions, + pool)); /* All the parameters are fine - let's perform the query against the * repository. */ @@ -1887,6 +2063,8 @@ static svn_error_t *get_location_segments(svn_ra_svn_conn_t *conn, "be younger than peg revision"); SVN_ERR(trivial_auth_request(conn, pool, b)); + SLOG("%s", svn_log__get_location_segments(abs_path, peg_revision, start_rev, + end_rev, pool)); /* All the parameters are fine - let's perform the query against the * repository. */ @@ -2000,6 +2178,9 @@ static svn_error_t *get_file_revs(svn_ra_svn_conn_t *conn, apr_pool_t *pool, else include_merged_revisions = (svn_boolean_t) include_merged_revs_param; + SLOG("%s", svn_log__get_file_revs(full_path, start_rev, end_rev, + include_merged_revisions, pool)); + frb.conn = conn; frb.pool = NULL; @@ -2037,6 +2218,7 @@ static svn_error_t *lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, full_path, TRUE)); + SLOG("%s", svn_log__lock_one_path(full_path, steal_lock, pool)); SVN_CMD_ERR(svn_repos_fs_lock(&l, b->repos, full_path, NULL, comment, 0, 0, /* No expiration time. */ @@ -2061,6 +2243,7 @@ static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, const char *path; const char *full_path; svn_revnum_t current_rev; + apr_array_header_t *log_paths; svn_lock_t *l; svn_error_t *err = SVN_NO_ERROR, *write_err; @@ -2076,6 +2259,7 @@ static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE)); /* Loop through the lock requests. */ + log_paths = apr_array_make(pool, path_revs->nelts, sizeof(full_path)); for (i = 0; i < path_revs->nelts; ++i) { svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i, @@ -2090,9 +2274,12 @@ static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, pool, "c(?r)", &path, ¤t_rev)); + /* Allocate the full_path out of pool so it will survive for use + * by operational logging, after this loop. */ full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path, subpool), - subpool); + pool); + APR_ARRAY_PUSH(log_paths, const char *) = full_path; if (! lookup_access(pool, b, svn_authz_write, full_path, TRUE)) { @@ -2129,6 +2316,8 @@ static svn_error_t *lock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_pool_destroy(subpool); + SLOG("%s", svn_log__lock(log_paths, steal_lock, pool)); + /* NOTE: err might contain a fatal locking error from the loop above. */ write_err = svn_ra_svn_write_word(conn, pool, "done"); if (!write_err) @@ -2156,6 +2345,7 @@ static svn_error_t *unlock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, /* Username required unless break_lock was specified. */ SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, full_path, ! break_lock)); + SLOG("%s", svn_log__unlock_one_path(full_path, break_lock, pool)); SVN_CMD_ERR(svn_repos_fs_unlock(b->repos, full_path, token, break_lock, pool)); @@ -2175,6 +2365,7 @@ static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, apr_pool_t *subpool; const char *path; const char *full_path; + apr_array_header_t *log_paths; const char *token; svn_error_t *err = SVN_NO_ERROR, *write_err; @@ -2187,6 +2378,7 @@ static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, subpool = svn_pool_create(pool); /* Loop through the unlock requests. */ + log_paths = apr_array_make(pool, unlock_tokens->nelts, sizeof(full_path)); for (i = 0; i < unlock_tokens->nelts; i++) { svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i, @@ -2201,9 +2393,12 @@ static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, SVN_ERR(svn_ra_svn_parse_tuple(item->u.list, subpool, "c(?c)", &path, &token)); + /* Allocate the full_path out of pool so it will survive for use + * by operational logging, after this loop. */ full_path = svn_path_join(b->fs_path->data, svn_path_canonicalize(path, subpool), - subpool); + pool); + APR_ARRAY_PUSH(log_paths, const char *) = full_path; if (! lookup_access(subpool, b, svn_authz_write, full_path, ! break_lock)) @@ -2232,6 +2427,8 @@ static svn_error_t *unlock_many(svn_ra_svn_conn_t *conn, apr_pool_t *pool, svn_pool_destroy(subpool); + SLOG("%s", svn_log__unlock(log_paths, break_lock, pool)); + /* NOTE: err might contain a fatal unlocking error from the loop above. */ write_err = svn_ra_svn_write_word(conn, pool, "done"); if (! write_err) @@ -2258,6 +2455,7 @@ static svn_error_t *get_lock(svn_ra_svn_conn_t *conn, apr_pool_t *pool, SVN_ERR(must_have_access(conn, pool, b, svn_authz_read, full_path, FALSE)); + SLOG("get-lock %s", svn_path_uri_encode(full_path, pool)); SVN_CMD_ERR(svn_fs_get_lock(&l, b->fs, full_path, pool)); @@ -2288,6 +2486,7 @@ static svn_error_t *get_locks(svn_ra_svn_conn_t *conn, apr_pool_t *pool, SVN_ERR(trivial_auth_request(conn, pool, b)); + SLOG("get-locks %s", svn_path_uri_encode(full_path, pool)); SVN_CMD_ERR(svn_repos_fs_get_locks(&locks, b->repos, full_path, authz_check_access_cb_func(b), b, pool)); @@ -2315,6 +2514,10 @@ static svn_error_t *replay_one_revision(svn_ra_svn_conn_t *conn, svn_fs_root_t *root; svn_error_t *err; + SVN_ERR(svnserve_log(b, conn, pool, + svn_log__replay(b->fs_path->data, low_water_mark, + pool))); + svn_ra_svn_get_editor(&editor, &edit_baton, conn, pool, NULL, NULL); err = svn_fs_revision_root(&root, b->fs, rev, pool); @@ -2530,6 +2733,11 @@ static svn_error_t *find_repos(const char *url, const char *root, svn_path_component_count(b->fs_path->data)); b->repos_url = url_buf->data; b->authz_repos_name = svn_path_is_child(root, repos_root, pool); + if (b->authz_repos_name == NULL) + b->repos_name = svn_path_basename(repos_root, pool); + else + b->repos_name = b->authz_repos_name; + b->repos_name = svn_path_uri_encode(b->repos_name, pool); /* If the svnserve configuration files have not been loaded then load them from the repository. */ @@ -2584,6 +2792,7 @@ svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params, const char *uuid, *client_url; apr_array_header_t *caplist, *cap_words; server_baton_t b; + fs_warning_baton_t warn_baton; b.tunnel = params->tunnel; b.tunnel_user = get_tunnel_user(params, pool); @@ -2593,6 +2802,7 @@ svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params, b.pwdb = params->pwdb; b.authzdb = params->authzdb; b.realm = NULL; + b.log_file = params->log_file; b.pool = pool; b.use_sasl = FALSE; @@ -2666,6 +2876,11 @@ svn_error_t *serve(svn_ra_svn_conn_t *conn, serve_params_t *params, return svn_ra_svn_flush(conn, pool); } + warn_baton.server = &b; + warn_baton.conn = conn; + warn_baton.pool = svn_pool_create(pool); + svn_fs_set_warning_func(b.fs, log_fs_warning, &warn_baton); + SVN_ERR(svn_fs_get_uuid(b.fs, &uuid, pool)); /* We can't claim mergeinfo capability until we know whether the diff --git a/subversion/svnserve/server.h b/subversion/svnserve/server.h index 6da97437c..93f6e610b 100644 --- a/subversion/svnserve/server.h +++ b/subversion/svnserve/server.h @@ -31,11 +31,12 @@ extern "C" { typedef struct server_baton_t { svn_repos_t *repos; + const char *repos_name; /* URI-encoded name of repository (not for authz) */ svn_fs_t *fs; /* For convenience; same as svn_repos_fs(repos) */ svn_config_t *cfg; /* Parsed repository svnserve.conf */ svn_config_t *pwdb; /* Parsed password database */ svn_authz_t *authzdb; /* Parsed authz rules */ - const char *authz_repos_name; /* The name of the repository */ + const char *authz_repos_name; /* The name of the repository for authz */ const char *realm; /* Authentication realm */ const char *repos_url; /* URL to base of repository */ svn_stringbuf_t *fs_path;/* Decoded base in-repos path (w/ leading slash) */ @@ -45,6 +46,7 @@ typedef struct server_baton_t { svn_boolean_t read_only; /* Disallow write access (global flag) */ svn_boolean_t use_sasl; /* Use Cyrus SASL for authentication; always false if SVN_HAVE_SASL not defined */ + apr_file_t *log_file; /* Log filehandle. */ apr_pool_t *pool; } server_baton_t; @@ -89,6 +91,9 @@ typedef struct serve_params_t { command line, or it was specified and it did not refer to a authorization database. */ svn_authz_t *authzdb; + + /* A filehandle open for writing logs to; possibly NULL. */ + apr_file_t *log_file; } serve_params_t; /* Serve the connection CONN according to the parameters PARAMS. */ @@ -119,6 +124,12 @@ svn_error_t *cyrus_auth_request(svn_ra_svn_conn_t *conn, enum access_type required, svn_boolean_t needs_username); +/* Escape SOURCE into DEST where SOURCE is null-terminated and DEST is + size BUFLEN DEST will be null-terminated. Returns number of bytes + written, including terminating null byte. */ +apr_size_t escape_errorlog_item(char *dest, const char *source, + apr_size_t buflen); + #ifdef __cplusplus } #endif /* __cplusplus */ diff --git a/tools/server-side/svn_dav_log_parse.py b/tools/server-side/svn_server_log_parse.py similarity index 86% rename from tools/server-side/svn_dav_log_parse.py rename to tools/server-side/svn_server_log_parse.py index 2b2a3e355..8e3eabae8 100644 --- a/tools/server-side/svn_dav_log_parse.py +++ b/tools/server-side/svn_server_log_parse.py @@ -94,11 +94,14 @@ pPATH = r'(/\S*)' pPATHS = r'\(([^)]*)\)' # r pREVNUM = r'r(\d+)' +# ( ...) +pREVNUMS = r'\(((\d+\s*)*)\)' # r: -pREVRANGE = r'r(\d+):(\d+)' +pREVRANGE = r'r(-?\d+):(-?\d+)' # @ pPATHREV = pPATH + r'@(\d+)' pWORD = r'(\S+)' +pPROPERTY = pWORD # depth=? pDEPTH = 'depth=' + pWORD @@ -176,7 +179,7 @@ class Parser(object): example, "lock steal?" => def handle_lock(self, path, steal) where steal will be True if "steal" was present. - See the end of test_svn_dav_log_parse.py for a complete example. + See the end of test_svn_server_log_parse.py for a complete example. """ def parse(self, line): """Parse line and call appropriate handle_ method. @@ -204,6 +207,20 @@ class Parser(object): self.handle_commit(int(m.group(1))) return line[m.end():] + def _parse_reparent(self, line): + m = _match(line, pPATH) + self.handle_reparent(m.group(1)) + return line[m.end():] + + def _parse_get_latest_rev(self, line): + self.handle_get_latest_rev() + return line + + def _parse_get_dated_rev(self, line): + m = _match(line, pWORD) + self.handle_get_dated_rev(m.group(1)) + return line[m.end():] + def _parse_get_dir(self, line): m = _match(line, pPATH, pREVNUM, ['text', 'props']) self.handle_get_dir(m.group(1), int(m.group(2)), @@ -225,8 +242,6 @@ class Parser(object): return line[m.end():] def _parse_change_rev_prop(self, line): - # - pPROPERTY = pWORD m = _match(line, pREVNUM, pPROPERTY) self.handle_change_rev_prop(int(m.group(1)), m.group(2)) return line[m.end():] @@ -236,13 +251,42 @@ class Parser(object): self.handle_rev_proplist(int(m.group(1))) return line[m.end():] + def _parse_rev_prop(self, line): + m = _match(line, pREVNUM, pPROPERTY) + self.handle_rev_prop(int(m.group(1)), m.group(2)) + return line[m.end():] + def _parse_unlock(self, line): m = _match(line, pPATHS, ['break']) paths = m.group(1).split() self.handle_unlock(paths, m.group(2) is not None) return line[m.end():] - # reports + def _parse_get_lock(self, line): + m = _match(line, pPATH) + self.handle_get_lock(m.group(1)) + return line[m.end():] + + def _parse_get_locks(self, line): + m = _match(line, pPATH) + self.handle_get_locks(m.group(1)) + return line[m.end():] + + def _parse_get_locations(self, line): + m = _match(line, pPATH, pREVNUMS) + path = m.group(1) + revnums = [int(x) for x in m.group(2).split()] + self.handle_get_locations(path, revnums) + return line[m.end():] + + def _parse_get_location_segments(self, line): + m = _match(line, pPATHREV, pREVRANGE) + path = m.group(1) + peg = int(m.group(2)) + left = int(m.group(3)) + right = int(m.group(4)) + self.handle_get_location_segments(path, peg, left, right) + return line[m.end():] def _parse_get_file_revs(self, line): m = _match(line, pPATH, pREVRANGE, ['include-merged-revisions']) @@ -294,6 +338,20 @@ class Parser(object): strict, include_merged_revisions, revprops) return line[m.end():] + def _parse_check_path(self, line): + m = _match(line, pPATHREV) + path = m.group(1) + revnum = int(m.group(2)) + self.handle_check_path(path, revnum) + return line[m.end():] + + def _parse_stat(self, line): + m = _match(line, pPATHREV) + path = m.group(1) + revnum = int(m.group(2)) + self.handle_stat(path, revnum) + return line[m.end():] + def _parse_replay(self, line): m = _match(line, pPATH, pREVNUM) path = m.group(1) diff --git a/tools/server-side/test_svn_dav_log_parse.py b/tools/server-side/test_svn_server_log_parse.py similarity index 64% rename from tools/server-side/test_svn_dav_log_parse.py rename to tools/server-side/test_svn_server_log_parse.py index 1017e267c..c634fda8c 100755 --- a/tools/server-side/test_svn_dav_log_parse.py +++ b/tools/server-side/test_svn_server_log_parse.py @@ -18,19 +18,20 @@ # Run with a path to a davautocheck ops log to test that it can parse that. import os +import re import sys import tempfile import unittest import svn.core -import svn_dav_log_parse +import svn_server_log_parse class TestCase(unittest.TestCase): def setUp(self): # Define a class to stuff everything passed to any handle_ # method into self.result. - class cls(svn_dav_log_parse.Parser): + class cls(svn_server_log_parse.Parser): def __getattr__(cls_self, attr): if attr.startswith('handle_'): return lambda *a: setattr(self, 'result', a) @@ -42,9 +43,26 @@ class TestCase(unittest.TestCase): self.parse(line) self.assertEqual(self.result, (line,)) + def test_reparent(self): + self.assertRaises(svn_server_log_parse.Error, self.parse, 'reparent') + self.assertEqual(self.parse('reparent /'), '') + self.assertEqual(self.result, ('/',)) + + def test_get_latest_rev(self): + self.assertEqual(self.parse('get-latest-rev'), '') + self.assertEqual(self.result, ()) + self.assertEqual(self.parse('get-latest-rev r3'), 'r3') + self.assertEqual(self.result, ()) + + def test_get_dated_rev(self): + self.assertRaises(svn_server_log_parse.Error, self.parse, + 'get-dated-rev') + self.assertEqual(self.parse('get-dated-rev 2008-04-15T20:41:24.000000Z'), '') + self.assertEqual(self.result, ('2008-04-15T20:41:24.000000Z',)) + def test_commit(self): - self.assertRaises(svn_dav_log_parse.Error, self.parse, 'commit') - self.assertRaises(svn_dav_log_parse.Error, self.parse, 'commit 3') + self.assertRaises(svn_server_log_parse.Error, self.parse, 'commit') + self.assertRaises(svn_server_log_parse.Error, self.parse, 'commit 3') self.assertEqual(self.parse('commit r3'), '') self.assertEqual(self.result, (3,)) self.assertEqual(self.parse('commit r3 leftover'), ' leftover') @@ -57,15 +75,15 @@ class TestCase(unittest.TestCase): self.get_dir_or_file('get-file') def get_dir_or_file(self, c): - self.assertRaises(svn_dav_log_parse.Error, self.parse, c) - self.assertRaises(svn_dav_log_parse.Error, self.parse, c + ' foo') - self.assertRaises(svn_dav_log_parse.Error, self.parse, c + ' foo 3') + self.assertRaises(svn_server_log_parse.Error, self.parse, c) + self.assertRaises(svn_server_log_parse.Error, self.parse, c + ' foo') + self.assertRaises(svn_server_log_parse.Error, self.parse, c + ' foo 3') self.assertEqual(self.parse(c + ' /a/b/c r3 ...'), ' ...') self.assertEqual(self.result, ('/a/b/c', 3, False, False)) self.assertEqual(self.parse(c + ' / r3'), '') self.assertEqual(self.result, ('/', 3, False, False)) # path must be absolute - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, c + ' a/b/c r3') self.assertEqual(self.parse(c + ' /k r27 text'), '') self.assertEqual(self.result, ('/k', 27, True, False)) @@ -78,7 +96,7 @@ class TestCase(unittest.TestCase): self.assertEqual(self.result, ('/k', 27, False, True)) def test_lock(self): - self.assertRaises(svn_dav_log_parse.Error, self.parse, 'lock') + self.assertRaises(svn_server_log_parse.Error, self.parse, 'lock') self.parse('lock (/foo)') self.assertEqual(self.result, (['/foo'], False)) self.assertEqual(self.parse('lock (/foo) steal ...'), ' ...') @@ -86,40 +104,75 @@ class TestCase(unittest.TestCase): self.assertEqual(self.parse('lock (/foo) stear'), ' stear') def test_change_rev_prop(self): - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'change-rev-prop r3') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'change-rev-prop r svn:log') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'change-rev-prop rX svn:log') self.assertEqual(self.parse('change-rev-prop r3 svn:log ...'), ' ...') self.assertEqual(self.result, (3, 'svn:log')) def test_rev_proplist(self): - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'rev-proplist') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'rev-proplist r') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'rev-proplist rX') self.assertEqual(self.parse('rev-proplist r3 ...'), ' ...') self.assertEqual(self.result, (3,)) + def test_rev_prop(self): + self.assertRaises(svn_server_log_parse.Error, self.parse, 'rev-prop') + self.assertRaises(svn_server_log_parse.Error, self.parse, 'rev-prop r') + self.assertRaises(svn_server_log_parse.Error, self.parse, 'rev-prop rX') + self.assertEqual(self.parse('rev-prop r3 foo ...'), ' ...') + self.assertEqual(self.result, (3, 'foo')) + def test_unlock(self): - self.assertRaises(svn_dav_log_parse.Error, self.parse, 'unlock') + self.assertRaises(svn_server_log_parse.Error, self.parse, 'unlock') self.parse('unlock (/foo)') self.assertEqual(self.result, (['/foo'], False)) self.assertEqual(self.parse('unlock (/foo) break ...'), ' ...') self.assertEqual(self.result, (['/foo'], True)) self.assertEqual(self.parse('unlock (/foo) bear'), ' bear') + def test_get_lock(self): + self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-lock') + self.parse('get-lock /foo') + self.assertEqual(self.result, ('/foo',)) + + def test_get_locks(self): + self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-locks') + self.parse('get-locks /foo') + self.assertEqual(self.result, ('/foo',)) + + def test_get_locations(self): + self.assertRaises(svn_server_log_parse.Error, self.parse, + 'get-locations') + self.assertRaises(svn_server_log_parse.Error, + self.parse, 'get-locations /foo 3') + self.assertEqual(self.parse('get-locations /foo (3 4) ...'), ' ...') + self.assertEqual(self.result, ('/foo', [3, 4])) + self.assertEqual(self.parse('get-locations /foo (3)'), '') + self.assertEqual(self.result, ('/foo', [3])) + + def test_get_location_segments(self): + self.assertRaises(svn_server_log_parse.Error, self.parse, + 'get-location-segments') + self.assertRaises(svn_server_log_parse.Error, + self.parse, 'get-location-segments /foo 3') + self.assertEqual(self.parse('get-location-segments /foo@2 r3:4'), '') + self.assertEqual(self.result, ('/foo', 2, 3, 4)) + def test_get_file_revs(self): - self.assertRaises(svn_dav_log_parse.Error, self.parse, 'get-file-revs') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-file-revs') + self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-file-revs /foo 3') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-file-revs /foo 3:a') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-file-revs /foo r3:a') self.assertEqual(self.parse('get-file-revs /foo r3:4 ...'), ' ...') self.assertEqual(self.result, ('/foo', 3, 4, False)) @@ -128,17 +181,17 @@ class TestCase(unittest.TestCase): self.assertEqual(self.result, ('/foo', 3, 4, True)) def test_get_mergeinfo(self): - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-mergeinfo') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-mergeinfo /foo') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-mergeinfo (/foo') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-mergeinfo (/foo /bar') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'get-mergeinfo (/foo)') - self.assertRaises(svn_dav_log_parse.BadMergeinfoInheritanceError, + self.assertRaises(svn_server_log_parse.BadMergeinfoInheritanceError, self.parse, 'get-mergeinfo (/foo) bork') self.assertEqual(self.parse('get-mergeinfo (/foo) explicit'), '') self.assertEqual(self.result, (['/foo'], @@ -151,10 +204,10 @@ class TestCase(unittest.TestCase): svn.core.svn_mergeinfo_inherited, False)) def test_log(self): - self.assertRaises(svn_dav_log_parse.Error, self.parse, 'log') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'log') + self.assertRaises(svn_server_log_parse.Error, self.parse, 'log /foo') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'log (/foo)') self.assertEqual(self.parse('log (/foo) r3:4' ' include-merged-revisions'), '') @@ -172,34 +225,44 @@ class TestCase(unittest.TestCase): self.assertEqual(self.result, (['/foo'], 8, 1, 3, False, False, False, [])) + def test_check_path(self): + self.assertRaises(svn_server_log_parse.Error, self.parse, 'check-path') + self.assertEqual(self.parse('check-path /foo@9'), '') + self.assertEqual(self.result, ('/foo', 9)) + + def test_stat(self): + self.assertRaises(svn_server_log_parse.Error, self.parse, 'stat') + self.assertEqual(self.parse('stat /foo@9'), '') + self.assertEqual(self.result, ('/foo', 9)) + def test_replay(self): - self.assertRaises(svn_dav_log_parse.Error, self.parse, 'replay') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'replay') + self.assertRaises(svn_server_log_parse.Error, self.parse, 'replay /foo') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'replay (/foo) r9') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'replay (/foo) r9:10') self.assertEqual(self.parse('replay /foo r9'), '') self.assertEqual(self.result, ('/foo', 9)) def test_checkout_or_export(self): - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'checkout-or-export') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'checkout-or-export /foo') self.assertEqual(self.parse('checkout-or-export /foo r9'), '') self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_unknown)) - self.assertRaises(svn_dav_log_parse.BadDepthError, self.parse, + self.assertRaises(svn_server_log_parse.BadDepthError, self.parse, 'checkout-or-export /foo r9 depth=INVALID-DEPTH') - self.assertRaises(svn_dav_log_parse.BadDepthError, self.parse, + self.assertRaises(svn_server_log_parse.BadDepthError, self.parse, 'checkout-or-export /foo r9 depth=bork') self.assertEqual(self.parse('checkout-or-export /foo r9 depth=files .'), ' .') self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_files)) def test_diff_1path(self): - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'diff') self.assertEqual(self.parse('diff /foo r9:10'), '') self.assertEqual(self.result, ('/foo', 9, 10, @@ -226,15 +289,15 @@ class TestCase(unittest.TestCase): svn.core.svn_depth_files, True)) def test_status(self): - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'status') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'status /foo') self.assertEqual(self.parse('status /foo r9'), '') self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_unknown)) - self.assertRaises(svn_dav_log_parse.BadDepthError, self.parse, + self.assertRaises(svn_server_log_parse.BadDepthError, self.parse, 'status /foo r9 depth=INVALID-DEPTH') - self.assertRaises(svn_dav_log_parse.BadDepthError, self.parse, + self.assertRaises(svn_server_log_parse.BadDepthError, self.parse, 'status /foo r9 depth=bork') self.assertEqual(self.parse('status /foo r9 depth=files .'), ' .') @@ -250,16 +313,16 @@ class TestCase(unittest.TestCase): svn.core.svn_depth_files)) def test_update(self): - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'update') - self.assertRaises(svn_dav_log_parse.Error, + self.assertRaises(svn_server_log_parse.Error, self.parse, 'update /foo') self.assertEqual(self.parse('update /foo r9'), '') self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_unknown, False)) - self.assertRaises(svn_dav_log_parse.BadDepthError, self.parse, + self.assertRaises(svn_server_log_parse.BadDepthError, self.parse, 'update /foo r9 depth=INVALID-DEPTH') - self.assertRaises(svn_dav_log_parse.BadDepthError, self.parse, + self.assertRaises(svn_server_log_parse.BadDepthError, self.parse, 'update /foo r9 depth=bork') self.assertEqual(self.parse('update /foo r9 depth=files .'), ' .') self.assertEqual(self.result, ('/foo', 9, svn.core.svn_depth_files, @@ -279,11 +342,21 @@ if __name__ == '__main__': # Use the argument as the path to a log file to test against. # Define a class to reconstruct the SVN-ACTION string. - class Test(svn_dav_log_parse.Parser): + class Test(svn_server_log_parse.Parser): def handle_unknown(self, line): - sys.stderr.write('unknown log line at %d\n' % (self.linenum,)) + sys.stderr.write('unknown log line at %d:\n%s\n' % (self.linenum, + line)) sys.exit(2) + def handle_reparent(self, path): + self.action = 'reparent ' + path + + def handle_get_latest_rev(self): + self.action = 'get-latest-rev' + + def handle_get_dated_rev(self, date): + self.action = 'get-dated-rev ' + date + def handle_commit(self, revision): self.action = 'commit r%d' % (revision,) @@ -309,6 +382,9 @@ if __name__ == '__main__': def handle_change_rev_prop(self, revision, revprop): self.action = 'change-rev-prop r%d %s' % (revision, revprop) + def handle_rev_prop(self, revision, revprop): + self.action = 'rev-prop r%d %s' % (revision, revprop) + def handle_rev_proplist(self, revision): self.action = 'rev-proplist r%d' % (revision,) @@ -317,7 +393,19 @@ if __name__ == '__main__': if break_lock: self.action += ' break' - # reports + def handle_get_lock(self, path): + self.action = 'get-lock ' + path + + def handle_get_locks(self, path): + self.action = 'get-locks ' + path + + def handle_get_locations(self, path, revisions): + self.action = ('get-locations %s (%s)' + % (path, ' '.join([str(x) for x in revisions]))) + + def handle_get_location_segments(self, path, peg, left, right): + self.action = 'get-location-segments %s@%d r%d:%d' % (path, peg, + left, right) def handle_get_file_revs(self, path, left, right, include_merged_revisions): self.action = 'get-file-revs %s r%d:%d' % (path, left, right) @@ -348,11 +436,15 @@ if __name__ == '__main__': elif len(revprops) > 0: self.action += ' revprops=(%s)' % (' '.join(revprops),) + def handle_check_path(self, path, revision): + self.action = 'check-path %s@%d' % (path, revision) + + def handle_stat(self, path, revision): + self.action = 'stat %s@%d' % (path, revision) + def handle_replay(self, path, revision): self.action = 'replay %s r%d' % (path, revision) - # the update report - def maybe_depth(self, depth): if depth != svn.core.svn_depth_unknown: self.action += ' depth=%s' % ( @@ -398,24 +490,57 @@ if __name__ == '__main__': fp = open(tmp, 'w') parser = Test() parser.linenum = 0 - for line in open(sys.argv[1]): - parser.linenum += 1 - # Find the SVN-ACTION string from the CustomLog format - # davautocheck.sh uses. If that changes, this will need - # to as well. Currently it's - # %t %u %{SVN-REPOS-NAME}e %{SVN-ACTION}e + log_file = sys.argv[1] + log_type = None + for line in open(log_file): + if log_type is None: + # Figure out which log type we have. + if re.match(r'\d+ \d\d\d\d-', line): + log_type = 'svnserve' + elif re.match(r'\[\d\d/', line): + log_type = 'mod_dav_svn' + else: + sys.stderr.write("unknown log format in '%s'" + % (log_file,)) + sys.exit(3) + sys.stderr.write('parsing %s log...\n' % (log_type,)) + sys.stderr.flush() + words = line.split() - leading = ' '.join(words[:4]) - action = ' '.join(words[4:]) + if log_type == 'svnserve': + # Skip over PID, date, client address, username, and repos. + if words[5].startswith('ERR'): + # Skip error lines. + fp.write(line) + continue + leading = ' '.join(words[:5]) + action = ' '.join(words[5:]) + else: + # Find the SVN-ACTION string from the CustomLog format + # davautocheck.sh uses. If that changes, this will need + # to as well. Currently it's + # %t %u %{SVN-REPOS-NAME}e %{SVN-ACTION}e + leading = ' '.join(words[:4]) + action = ' '.join(words[4:]) + # Parse the action and write the reconstructed action to # the temporary file. Ignore the returned trailing text, # as we have none in the davautocheck ops log. - parser.parse(action) + parser.linenum += 1 + try: + parser.parse(action) + except svn_server_log_parse.Error: + sys.stderr.write('error at line %d: %s\n' + % (parser.linenum, action)) + raise fp.write(leading + ' ' + parser.action + '\n') fp.close() # Check differences between original and reconstructed files # (should be identical). - sys.exit(os.spawnlp(os.P_WAIT, 'diff', 'diff', '-u', sys.argv[1], tmp)) + result = os.spawnlp(os.P_WAIT, 'diff', 'diff', '-u', log_file, tmp) + if result == 0: + sys.stderr.write('OK\n') + sys.exit(result) finally: try: os.unlink(tmp) -- 2.11.4.GIT