Merge the svnserve-logging branch, in its entirety, to trunk, using the
authorepg <epg@612f8ebc-c883-4be0-9ee0-a4e9ef946e3a>
Mon, 28 Apr 2008 22:52:24 +0000 (28 22:52 +0000)
committerepg <epg@612f8ebc-c883-4be0-9ee0-a4e9ef946e3a>
Mon, 28 Apr 2008 22:52:24 +0000 (28 22:52 +0000)
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

21 files changed:
build.conf
subversion/include/private/svn_log.h [new file with mode: 0644]
subversion/include/svn_ra_svn.h
subversion/libsvn_ra_svn/marshal.c
subversion/libsvn_ra_svn/ra_svn.h
subversion/libsvn_subr/log.c [new file with mode: 0644]
subversion/mod_dav_svn/deadprops.c
subversion/mod_dav_svn/lock.c
subversion/mod_dav_svn/reports/file-revs.c
subversion/mod_dav_svn/reports/log.c
subversion/mod_dav_svn/reports/mergeinfo.c
subversion/mod_dav_svn/reports/replay.c
subversion/mod_dav_svn/reports/update.c
subversion/mod_dav_svn/repos.c
subversion/mod_dav_svn/version.c
subversion/svnserve/log-escape.c [new file with mode: 0644]
subversion/svnserve/main.c
subversion/svnserve/serve.c
subversion/svnserve/server.h
tools/server-side/svn_server_log_parse.py [moved from tools/server-side/svn_dav_log_parse.py with 86% similarity]
tools/server-side/test_svn_server_log_parse.py [moved from tools/server-side/test_svn_dav_log_parse.py with 64% similarity]

index 287e3b6..2bccfc4 100644 (file)
@@ -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 (file)
index 0000000..65faba5
--- /dev/null
@@ -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 */
index f3f8025..ddac7ae 100644 (file)
@@ -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.
index 5d4a8b5..07b6f3e 100644 (file)
@@ -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,
index 56592c4..69937fd 100644 (file)
@@ -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 (file)
index 0000000..4ba1776
--- /dev/null
@@ -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/.
+ * ====================================================================
+ */
+
+
+
+\f
+#include <stdarg.h>
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <apr_strings.h>
+
+#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);
+}
index 4f367e0..fc205a0 100644 (file)
@@ -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,
index 581f00a..a5f9170 100644 (file)
@@ -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;
index 2ca4170..b902eaa 100644 (file)
@@ -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). */
index 9c1be95..19b805f 100644 (file)
@@ -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). */
index 238ac91..7ea2875 100644 (file)
@@ -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
index 4310849..81bf49a 100644 (file)
@@ -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)))
index cbcc7c8..b9c9b84 100644 (file)
@@ -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);
           }
       }
 
index c11114f..4a0c923 100644 (file)
@@ -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,
index cbc4d1e..0a2d155 100644 (file)
@@ -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 (file)
index 0000000..171ef6e
--- /dev/null
@@ -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 <apr.h>
+#include <apr_lib.h>
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+
+/* 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);
+}
index 03943f8..7786662 100644 (file)
@@ -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(&params.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
index 4621219..0bfd033 100644 (file)
 
 \f
 #include <limits.h> /* for UINT_MAX */
+#include <stdarg.h>
 
 #define APR_WANT_STRFUNC
 #include <apr_want.h>
 #include <apr_general.h>
+#include <apr_lib.h>
 #include <apr_strings.h>
 #include <apr_md5.h>
 
 #include "svn_mergeinfo.h"
 #include "svn_user.h"
 
+#include "private/svn_log.h"
 #include "private/svn_mergeinfo_private.h"
 
+#ifdef HAVE_UNISTD_H
+#include <unistd.h>   /* 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", &timestr));
+  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,
                                      &current_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
index 6da9743..93f6e61 100644 (file)
@@ -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 */
similarity index 86%
rename from tools/server-side/svn_dav_log_parse.py
rename to tools/server-side/svn_server_log_parse.py
index 2b2a3e3..8e3eaba 100644 (file)
@@ -94,11 +94,14 @@ pPATH = r'(/\S*)'
 pPATHS = r'\(([^)]*)\)'
 # r<N>
 pREVNUM = r'r(\d+)'
+# (<N> ...)
+pREVNUMS = r'\(((\d+\s*)*)\)'
 # r<N>:<M>
-pREVRANGE = r'r(\d+):(\d+)'
+pREVRANGE = r'r(-?\d+):(-?\d+)'
 # <PATH>@<N>
 pPATHREV = pPATH + r'@(\d+)'
 pWORD = r'(\S+)'
+pPROPERTY = pWORD
 # depth=<D>?
 pDEPTH = 'depth=' + pWORD
 
@@ -176,7 +179,7 @@ class Parser(object):
     example, "lock <PATH> 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):
-        # <REVPROP>
-        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)
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 1017e26..c634fda 100755 (executable)
 # 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)