Merge from trunk to in-memory-cache, perhaps for the last time.

git-svn-id: https://svn.apache.org/repos/asf/subversion/branches/in-memory-cache@871452 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/Makefile.in b/Makefile.in
index 8acbeab..e7eaec2 100644
--- a/Makefile.in
+++ b/Makefile.in
@@ -46,6 +46,7 @@
 SVN_SERF_LIBS = @SVN_SERF_LIBS@
 SVN_SASL_LIBS = @SVN_SASL_LIBS@
 SVN_ZLIB_LIBS = @SVN_ZLIB_LIBS@
+SVN_APR_MEMCACHE_LIBS = @SVN_APR_MEMCACHE_LIBS@
 
 LIBS = @LIBS@
 
@@ -108,7 +109,7 @@
 INCLUDES = -I$(top_srcdir)/subversion/include -I$(top_builddir)/subversion \
            @SVN_APR_INCLUDES@ @SVN_APRUTIL_INCLUDES@ @SVN_GNOME_KEYRING_INCLUDES@ \
            @SVN_KWALLET_INCLUDES@ @SVN_NEON_INCLUDES@ @SVN_SASL_INCLUDES@ \
-           @SVN_SERF_INCLUDES@ @SVN_ZLIB_INCLUDES@
+           @SVN_SERF_INCLUDES@ @SVN_ZLIB_INCLUDES@ @SVN_APR_MEMCACHE_INCLUDES@
 
 APACHE_INCLUDES = @APACHE_INCLUDES@
 APACHE_LIBEXECDIR = $(DESTDIR)@APACHE_LIBEXECDIR@
@@ -142,6 +143,9 @@
 SVN_ZLIB_PREFIX = @SVN_ZLIB_PREFIX@
 SVN_ZLIB_INCLUDES = @SVN_ZLIB_INCLUDES@
 
+SVN_APR_MEMCACHE_PREFIX = @SVN_APR_MEMCACHE_PREFIX@
+SVN_APR_MEMCACHE_INCLUDES = @SVN_APR_MEMCACHE_INCLUDES@
+
 MKDIR = @MKDIR@
 
 # The EXTRA_ parameters can be used to pass extra flags at 'make' time.
@@ -196,6 +200,7 @@
 INSTALL_KWALLET_LIB = $(INSTALL_LIB)
 INSTALL_NEON_LIB = $(INSTALL_LIB)
 INSTALL_SERF_LIB = $(INSTALL_LIB)
+INSTALL_APR_MEMCACHE_LIB = $(INSTALL_LIB)
 INSTALL_BIN = $(LIBTOOL) --mode=install $(INSTALL)
 INSTALL_CONTRIB = $(LIBTOOL) --mode=install $(INSTALL)
 INSTALL_TOOLS = $(LIBTOOL) --mode=install $(INSTALL)
@@ -395,7 +400,9 @@
 	  if test "$(PARALLEL)" != ""; then                                  \
 	    flags="--parallel $$flags";                                      \
 	  fi;                                                                \
-	  $(PYTHON) $(top_srcdir)/build/run_tests.py $$flags                 \
+	  $(PYTHON) $(top_srcdir)/build/run_tests.py			     \
+	            --config-file $(top_srcdir)/subversion/tests/tests.conf  \
+	            $$flags                 				     \
 		    '$(abs_srcdir)' '$(abs_builddir)' $(TESTS);              \
 	else                                                                 \
 	  echo "make check: Python 2.2 or greater is required,";             \
diff --git a/aclocal.m4 b/aclocal.m4
index d5485de..f902e54 100644
--- a/aclocal.m4
+++ b/aclocal.m4
@@ -20,6 +20,7 @@
 sinclude(build/ac-macros/swig.m4)
 sinclude(build/ac-macros/sasl.m4)
 sinclude(build/ac-macros/zlib.m4)
+sinclude(build/ac-macros/apr_memcache.m4)
 
 # Include the libtool macros
 sinclude(build/libtool.m4)
diff --git a/build.conf b/build.conf
index 3c8a2b5..e48d4ab 100644
--- a/build.conf
+++ b/build.conf
@@ -340,9 +340,9 @@
 type = lib
 install = fsmod-lib
 path = subversion/libsvn_subr
-libs = aprutil apriconv apr xml zlib
+libs = aprutil apriconv apr xml zlib apr_memcache
 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_log.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 svn_cache.h
 
 # Working copy management lib
 [libsvn_wc]
@@ -639,6 +639,14 @@
 # ----------------------------------------------------------------------------
 # Tests for libsvn_subr
 
+[cache-test]
+description = Test in-memory cache
+type = exe
+path = subversion/tests/libsvn_subr
+sources = cache-test.c
+install = test
+libs = libsvn_test libsvn_subr apr
+
 [compat-test]
 description = Test compatibility functions
 type = exe
@@ -870,6 +878,10 @@
 msvc-libs = ws2_32.lib
 msvc-static = yes
 
+[apr_memcache]
+type = lib
+external-lib = $(SVN_APR_MEMCACHE_LIBS)
+
 [serf]
 type = lib
 external-lib = $(SVN_SERF_LIBS)
@@ -904,7 +916,7 @@
        fs-test fs-base-test skel-test key-test strings-reps-test changes-test locks-test
        repos-test
        compat-test config-test hashdump-test mergeinfo-test opt-test path-test stream-test
-       string-test time-test utf-test target-test error-test
+       string-test time-test utf-test target-test error-test cache-test
        revision-test
        translate-test
        random-test
diff --git a/build/ac-macros/apr_memcache.m4 b/build/ac-macros/apr_memcache.m4
new file mode 100644
index 0000000..c99cf13
--- /dev/null
+++ b/build/ac-macros/apr_memcache.m4
@@ -0,0 +1,84 @@
+dnl
+dnl  SVN_LIB_APR_MEMCACHE
+dnl
+dnl  Check configure options and assign variables related to
+dnl  the apr_memcache client library.
+dnl  Sets svn_lib_apr_memcache to "yes" if memcache code is accessible
+dnl  either from the standalone apr_memcache library or from apr-util.
+dnl
+
+AC_DEFUN(SVN_LIB_APR_MEMCACHE,
+[
+  apr_memcache_found=no
+
+  AC_ARG_WITH(apr_memcache,AC_HELP_STRING([--with-apr_memcache=PREFIX],
+                                  [Standalone apr_memcache client library]),
+  [
+    if test "$withval" = "yes" ; then
+      AC_MSG_ERROR([--with-apr_memcache requires an argument.])
+    else
+      AC_MSG_NOTICE([looking for separate apr_memcache package])
+      apr_memcache_prefix=$withval
+      save_cppflags="$CPPFLAGS"
+      CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES $SVN_APRUTIL_INCLUDES -I$apr_memcache_prefix/include/apr_memcache-0"
+      AC_CHECK_HEADER(apr_memcache.h,[
+        save_ldflags="$LDFLAGS"
+        LDFLAGS="$LDFLAGS -L$apr_memcache_prefix/lib"
+        AC_CHECK_LIB(apr_memcache, apr_memcache_create,
+          [apr_memcache_found="standalone"])
+        LDFLAGS="$save_ldflags"])
+      CPPFLAGS="$save_cppflags"
+    fi
+  ], [
+    if test -d "$srcdir/apr_memcache"; then
+      apr_memcache_found=reconfig
+    else
+dnl   Try just looking in apr-util (>= 1.3 has it already).
+      AC_MSG_NOTICE([looking for apr_memcache as part of apr-util])
+      save_cppflags="$CPPFLAGS"
+      CPPFLAGS="$CPPFLAGS $SVN_APR_INCLUDES $SVN_APRUTIL_INCLUDES"
+      AC_CHECK_HEADER(apr_memcache.h,[
+        save_ldflags="$LDFLAGS"
+        LDFLAGS="$LDFLAGS $SVN_APRUTIL_EXPORT_LIBS"
+        AC_CHECK_LIB(aprutil-1, apr_memcache_create,
+          [apr_memcache_found="aprutil"])
+        LDFLAGS="$save_ldflags"])
+      CPPFLAGS="$save_cppflags"
+
+    fi
+   ])
+
+
+  if test $apr_memcache_found = "reconfig"; then
+    SVN_EXTERNAL_PROJECT([apr_memcache], [--with-apr=$apr_config --with-apr-util=$apu_config])
+    apr_memcache_prefix=$prefix
+    SVN_APR_MEMCACHE_PREFIX="$apr_memcache_prefix"
+    SVN_APR_MEMCACHE_INCLUDES="-I$srcdir/memcache"
+    SVN_APR_MEMCACHE_LIBS="$abs_builddir/memcache/libapr_memcache.la"
+    SVN_APR_MEMCACHE_EXPORT_LIBS="-L$apr_memcache_prefix/lib -lapr_memcache"
+  fi
+
+  if test $apr_memcache_found = "standalone"; then
+    SVN_APR_MEMCACHE_PREFIX="$apr_memcache_prefix"
+    SVN_APR_MEMCACHE_INCLUDES="-I$apr_memcache_prefix/include/apr_memcache-0"
+    SVN_APR_MEMCACHE_LIBS="$apr_memcache_prefix/lib/libapr_memcache.la"
+    SVN_APR_MEMCACHE_EXPORT_LIBS="-L$apr_memcache_prefix/lib -lapr_memcache"
+    svn_lib_apr_memcache=yes
+  elif test $apr_memcache_found = "aprutil"; then
+dnl We are already linking apr-util everywhere, so no special treatement needed.
+    SVN_APR_MEMCACHE_PREFIX=""
+    SVN_APR_MEMCACHE_INCLUDES=""
+    SVN_APR_MEMCACHE_LIBS=""
+    SVN_APR_MEMCACHE_EXPORT_LIBS=""
+    svn_lib_apr_memcache=yes
+  elif test $apr_memcache_found = "reconfig"; then
+    svn_lib_apr_memcache=yes
+  else
+    svn_lib_apr_memcache=no
+  fi
+
+  AC_SUBST(SVN_APR_MEMCACHE_PREFIX)
+  AC_SUBST(SVN_APR_MEMCACHE_INCLUDES)
+  AC_SUBST(SVN_APR_MEMCACHE_LIBS)
+  AC_SUBST(SVN_APR_MEMCACHE_EXPORT_LIBS)
+])
diff --git a/build/run_tests.py b/build/run_tests.py
index 9cee4c9..4bcbe8a 100755
--- a/build/run_tests.py
+++ b/build/run_tests.py
@@ -6,13 +6,13 @@
 '''usage: python run_tests.py [--url=<base-url>] [--fs-type=<fs-type>]
                     [--verbose] [--cleanup] [--enable-sasl] [--parallel]
                     [--http-library=<http-library>]
+                    [--config-file=<file>]
                     [--server-minor-version=<version>] <abs_srcdir> <abs_builddir>
                     <prog ...>
 
-The optional base-url, fs-type, http-library, server-minor-version,
-verbose, parallel, enable-sasl, and cleanup options, and the first two
-parameters are passed unchanged to the TestHarness constructor.  All
-other parameters are names of test programs.
+The optional flags and the first two parameters are passed unchanged
+to the TestHarness constructor.  All other parameters are names of
+test programs.
 '''
 
 import os, sys
@@ -30,8 +30,8 @@
   def __init__(self, abs_srcdir, abs_builddir, logfile,
                base_url=None, fs_type=None, http_library=None,
                server_minor_version=None, verbose=None,
-               cleanup=None, enable_sasl=None, parallel=None, list_tests=None,
-               svn_bin=None):
+               cleanup=None, enable_sasl=None, parallel=None, config_file=None,
+               list_tests=None, svn_bin=None):
     '''Construct a TestHarness instance.
 
     ABS_SRCDIR and ABS_BUILDDIR are the source and build directories.
@@ -53,6 +53,9 @@
     self.cleanup = cleanup
     self.enable_sasl = enable_sasl
     self.parallel = parallel
+    self.config_file = None
+    if config_file is not None:
+      self.config_file = os.path.abspath(config_file)
     self.list_tests = list_tests
     self.svn_bin = svn_bin
     self.log = None
@@ -114,10 +117,14 @@
         cmdline.append('--enable-sasl')
       if self.parallel is not None:
         cmdline.append('--parallel')
+      if self.config_file is not None:
+        cmdline.append(quote('--config-file=' + self.config_file))
     elif os.access(prog, os.X_OK):
       progname = './' + progbase
       cmdline = [quote(progname),
                  quote('--srcdir=' + os.path.join(self.srcdir, progdir))]
+      if self.config_file is not None:
+        cmdline.append(quote('--config-file=' + self.config_file))
     else:
       print 'Don\'t know what to do about ' + progbase
       sys.exit(1)
@@ -190,7 +197,7 @@
     opts, args = my_getopt(sys.argv[1:], 'u:f:vc',
                            ['url=', 'fs-type=', 'verbose', 'cleanup',
                             'http-library=', 'server-minor-version=',
-                            'enable-sasl', 'parallel'])
+                            'enable-sasl', 'parallel', 'config-file='])
   except getopt.GetoptError:
     args = []
 
@@ -199,8 +206,8 @@
     sys.exit(2)
 
   base_url, fs_type, verbose, cleanup, enable_sasl, http_library, \
-    server_minor_version, parallel = \
-            None, None, None, None, None, None, None, None
+    server_minor_version, parallel, config_file = \
+            None, None, None, None, None, None, None, None, None
   for opt, val in opts:
     if opt in ['-u', '--url']:
       base_url = val
@@ -218,13 +225,15 @@
       enable_sasl = 1
     elif opt in ['--parallel']:
       parallel = 1
+    elif opt in ['--config-file']:
+      config_file = val
     else:
       raise getopt.GetoptError
 
   th = TestHarness(args[0], args[1],
                    os.path.abspath('tests.log'),
                    base_url, fs_type, http_library, server_minor_version,
-                   verbose, cleanup, enable_sasl, parallel)
+                   verbose, cleanup, enable_sasl, parallel, config_file)
 
   failed = th.run(args[2:])
   if failed:
diff --git a/configure.ac b/configure.ac
index 30b817f..bb1cc02 100644
--- a/configure.ac
+++ b/configure.ac
@@ -89,6 +89,14 @@
 dnl Search for serf as an alternative to neon
 SVN_LIB_SERF
 
+dnl Search for apr_memcache (only affects fs_fs)
+SVN_LIB_APR_MEMCACHE
+
+if test "$svn_lib_apr_memcache" = "yes"; then
+  AC_DEFINE(SVN_HAVE_MEMCACHE, 1,
+            [Defined if apr_memcache (standalone or in apr-util) is present])
+fi
+
 dnl Set up a number of directories ---------------------
 
 dnl Create SVN_BINDIR for proper substitution
diff --git a/gen-make.py b/gen-make.py
index 6fe722c..66a5d8b 100755
--- a/gen-make.py
+++ b/gen-make.py
@@ -178,6 +178,9 @@
   print "  --vsnet-version=VER"
   print "           generate for VS.NET version VER (2002, 2003, 2005 or 2008)"
   print "           [only valid in combination with '-t vcproj']"
+  print
+  print "  --with-apr_memcache=DIR"
+  print "           the apr_memcache sources are in DIR"
   sys.exit(0)
 
 
@@ -214,6 +217,7 @@
                             'with-junit=',
                             'with-swig=',
                             'with-sasl=',
+                            'with-apr_memcache=',
                             'enable-pool-debug',
                             'enable-purify',
                             'enable-quantify',
diff --git a/subversion/bindings/swig/python/tests/mergeinfo.py b/subversion/bindings/swig/python/tests/mergeinfo.py
index d59d4ed..ecd9a10 100644
--- a/subversion/bindings/swig/python/tests/mergeinfo.py
+++ b/subversion/bindings/swig/python/tests/mergeinfo.py
@@ -39,7 +39,7 @@
     self.tearDown()
     self.repos = repos.svn_repos_create(REPOS_PATH, '', '', None, None)
     repos.svn_repos_load_fs2(self.repos, dumpfile, StringIO(),
-                             repos.svn_repos_load_uuid_default, '',
+                             repos.svn_repos_load_uuid_ignore, '',
                              0, 0, None)
     self.fs = repos.fs(self.repos)
     self.rev = fs.youngest_rev(self.fs)
diff --git a/subversion/bindings/swig/python/tests/trac/versioncontrol/tests/svn_fs.py b/subversion/bindings/swig/python/tests/trac/versioncontrol/tests/svn_fs.py
index 30b4e88..877876b 100644
--- a/subversion/bindings/swig/python/tests/trac/versioncontrol/tests/svn_fs.py
+++ b/subversion/bindings/swig/python/tests/trac/versioncontrol/tests/svn_fs.py
@@ -54,7 +54,7 @@
 
         r = repos.svn_repos_create(REPOS_PATH, '', '', None, None)
         repos.svn_repos_load_fs2(r, dumpfile, StringIO(),
-                                repos.svn_repos_load_uuid_default, '',
+                                repos.svn_repos_load_uuid_ignore, '',
                                 0, 0, None)
 
     def tearDown(self):
diff --git a/subversion/include/svn_base64.h b/subversion/include/svn_base64.h
index 679addc..ceb5e24 100644
--- a/subversion/include/svn_base64.h
+++ b/subversion/include/svn_base64.h
@@ -55,7 +55,22 @@
 /** Encode an @c svn_stringbuf_t into base64.
  *
  * A simple interface for encoding base64 data assuming we have all of
- * it present at once.  The returned string will be allocated from @c pool.
+ * it present at once.  If @a break_lines is true, newlines will be
+ * inserted periodically; otherwise the string will only consist of
+ * base64 encoding characters.  The returned string will be allocated
+ * from @c pool.
+ *
+ * @since New in 1.6.
+ */
+const svn_string_t *svn_base64_encode_string2(const svn_string_t *str,
+                                              svn_boolean_t break_lines,
+                                              apr_pool_t *pool);
+
+/**
+ * Same as svn_base64_encode_string2, but with @a break_lines always
+ * TRUE.
+ *
+ * @deprecated Provided for backward compatibility with the 1.5 API.
  */
 const svn_string_t *svn_base64_encode_string(const svn_string_t *str,
                                              apr_pool_t *pool);
@@ -63,7 +78,9 @@
 /** Decode an @c svn_stringbuf_t from base64.
  *
  * A simple interface for decoding base64 data assuming we have all of
- * it present at once.  The returned string will be allocated from @c pool.
+ * it present at once.  The returned string will be allocated from @c
+ * pool.
+ *
  */
 const svn_string_t *svn_base64_decode_string(const svn_string_t *str,
                                              apr_pool_t *pool);
diff --git a/subversion/include/svn_cache.h b/subversion/include/svn_cache.h
new file mode 100644
index 0000000..df4b77c
--- /dev/null
+++ b/subversion/include/svn_cache.h
@@ -0,0 +1,277 @@
+/**
+ * @copyright
+ * ====================================================================
+ * 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/.
+ * ====================================================================
+ * @endcopyright
+ *
+ * @file svn_cache.h
+ * @brief In-memory cache implementation.
+ */
+
+
+#ifndef SVN_CACHE_H
+#define SVN_CACHE_H
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_iter.h"
+#include "svn_config.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/**
+ * @defgroup svn_cache_support In-memory caching
+ * @{
+ */
+
+/**
+ * A function type for copying an object @a in into a different pool @pool
+ *  and returning the result in @a *out.
+ *
+ * @since New in 1.6.
+*/
+typedef svn_error_t *(svn_cache_dup_func_t)(void **out,
+                                            void *in,
+                                            apr_pool_t *pool);
+
+/**
+ * A function type for deserializing an object @a *out from the string
+ * @a data of length @a data_len in the pool @pool.
+ *
+ * @since New in 1.6.
+*/
+typedef svn_error_t *(svn_cache_deserialize_func_t)(void **out,
+                                                    const char *data,
+                                                    apr_size_t data_len,
+                                                    apr_pool_t *pool);
+
+/**
+ * A function type for serializing an object @a in into bytes.  The
+ * function should allocate the serialized value in @a pool, set @a
+ * *data to the serialized value, and set *data_len to its length.
+ *
+ * @since New in 1.6.
+*/
+typedef svn_error_t *(svn_cache_serialize_func_t)(char **data,
+                                                  apr_size_t *data_len,
+                                                  void *in,
+                                                  apr_pool_t *pool);
+
+/**
+ * A function type for transforming or ignoring errors.  @a pool may
+ * be used for temporary allocations.
+ *
+ * @since New in 1.6.
+ */
+typedef svn_error_t *(svn_cache_error_handler_t)(svn_error_t *err,
+                                                 void *baton,
+                                                 apr_pool_t *pool);
+
+/**
+ * A wrapper around apr_memcache_t, provided essentially so that the
+ * Subversion public API doesn't depend on whether or not you have
+ * access to the APR memcache libraries.
+ *
+ * @since New in 1.6.
+ */
+typedef struct svn_memcache_t svn_memcache_t;
+
+/**
+ * Opaque type for an in-memory cache.
+ *
+ * @since New in 1.6.
+ */
+typedef struct svn_cache_t svn_cache_t;
+
+/**
+ * Creates a new cache in @a *cache_p.  This cache will use @a pool
+ * for all of its storage needs.  The elements in the cache will be
+ * indexed by keys of length @a klen, which may be APR_HASH_KEY_STRING
+ * if they are strings.  Cached values will be copied in and out of
+ * the cache using @a dup_func.
+ *
+ * The cache stores up to @a pages * @a items_per_page items at a
+ * time.  The exact cache invalidation strategy is not defined here,
+ * but in general, a lower value for @a items_per_page means more
+ * memory overhead for the same number of items, but a higher value
+ * for @a items_per_page means more items are cleared at once.  Both
+ * @a pages and @a items_per_page must be positive (though they both
+ * may certainly be 1).
+ *
+ * If @a thread_safe is true, and APR is compiled with threads, all
+ * accesses to the cache will be protected with a mutex.
+ *
+ * Note that NULL is a legitimate value for cache entries (and @a dup_func
+ * will not be called on it).
+ *
+ * It is not safe for @a dup_func to interact with the cache itself.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_cache_create_inprocess(svn_cache_t **cache_p,
+                           svn_cache_dup_func_t *dup_func,
+                           apr_ssize_t klen,
+                           apr_int64_t pages,
+                           apr_int64_t items_per_page,
+                           svn_boolean_t thread_safe,
+                           apr_pool_t *pool);
+/**
+ * Creates a new cache in @a *cache_p, communicating to a memcached
+ * process via @a memcache.  The elements in the cache will be indexed
+ * by keys of length @a klen, which may be APR_HASH_KEY_STRING if they
+ * are strings.  Values will be serialized for memcached using @a
+ * serialize_func and deserialized using @a deserialize_func.  Because
+ * the same memcached server may cache many different kinds of values,
+ * @a prefix should be specified to differentiate this cache from
+ * other caches.  @a *cache_p will be allocated in @a pool.
+ *
+ * If @a deserialize_func is NULL, then the data is returned as an
+ * svn_string_t; if @a serialize_func is NULL, then the data is
+ * assumed to be an svn_stringbuf_t.
+ *
+ * These caches are always thread safe.
+ *
+ * These caches do not support svn_cache_iter.
+ *
+ * If Subversion was not built with apr_memcache support, always
+ * raises SVN_ERR_NO_APR_MEMCACHE.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_cache_create_memcache(svn_cache_t **cache_p,
+                          svn_memcache_t *memcache,
+                          svn_cache_serialize_func_t *serialize_func,
+                          svn_cache_deserialize_func_t *deserialize_func,
+                          apr_ssize_t klen,
+                          const char *prefix,
+                          apr_pool_t *pool);
+
+/**
+ * Given @a config, returns an APR memcached interface in @a
+ * *memcache_p allocated in @a pool if @a config contains entries in
+ * the SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS section describing
+ * memcached servers; otherwise, sets @a *memcache_p to NULL.
+ *
+ * If Subversion was not built with apr_memcache_support, then raises
+ * SVN_ERR_NO_APR_MEMCACHE if and only if @a config is configured to
+ * use memcache.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_cache_make_memcache_from_config(svn_memcache_t **memcache_p,
+                                    svn_config_t *config,
+                                    apr_pool_t *pool);
+
+/**
+ * Sets @a handler to be @a cache's error handling routine.  If any
+ * error is returned from a call to svn_cache_get or svn_cache_set, @a
+ * handler will be called with @a baton and the error, and the
+ * original function will return whatever error @a handler returns
+ * instead (possibly SVN_NO_ERROR); @a handler will receive the pool
+ * passed to the svn_cache_* function.  @a pool is used for temporary
+ * allocations.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_cache_set_error_handler(svn_cache_t *cache,
+                            svn_cache_error_handler_t *handler,
+                            void *baton,
+                            apr_pool_t *pool);
+
+
+#define SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "memcached-servers"
+
+/**
+ * Fetches a value indexed by @a key from @a cache into @a *value,
+ * setting @a *found to TRUE iff it is in the cache and FALSE if it is
+ * not found.  The value is copied into @a pool using the copy
+ * function provided to the cache's constructor.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_cache_get(void **value,
+              svn_boolean_t *found,
+              svn_cache_t *cache,
+              const void *key,
+              apr_pool_t *pool);
+
+/**
+ * Stores the value @value under the key @a key in @a cache.  @a pool
+ * is used only for temporary allocations.  The cache makes copies of
+ * @a key and @a value if necessary (that is, @a key and @a value may
+ * have shorter lifetimes than the cache).
+ *
+ * If there is already a value for @a key, this will replace it.  Bear
+ * in mind that in some circumstances this may leak memory (that is,
+ * the cache's copy of the previous value may not be immediately
+ * cleared); it is only guaranteed to not leak for caches created with
+ * @a items_per_page equal to 1.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_cache_set(svn_cache_t *cache,
+              const void *key,
+              void *value,
+              apr_pool_t *pool);
+
+/**
+ * Iterates over the elements currently in @a cache, calling @a func
+ * for each one until there are no more elements or @a func returns an
+ * error.  Uses @a pool for temporary allocations.
+ *
+ * If @a completed is not NULL, then on return - if @a func returns no
+ * errors - @a *completed will be set to @c TRUE.
+ *
+ * If @a func returns an error other than @c SVN_ERR_ITER_BREAK, that
+ * error is returned.  When @a func returns @c SVN_ERR_ITER_BREAK,
+ * iteration is interrupted, but no error is returned and @a
+ * *completed is set to @c FALSE.  (The error handler set by
+ * svn_cache_set_error_handler is not used for svn_cache_iter.)
+ *
+ * It is not legal to perform any other cache operations on @a cache
+ * inside @a func.
+ *
+ * svn_cache_iter is not supported by all cache implementations; see
+ * the svn_cache_create_* function for details.
+ *
+ * @since New in 1.6.
+ */
+svn_error_t *
+svn_cache_iter(svn_boolean_t *completed,
+               svn_cache_t *cache,
+               svn_iter_apr_hash_cb_t func,
+               void *baton,
+               apr_pool_t *pool);
+/** @} */
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_CACHE_H */
diff --git a/subversion/include/svn_error_codes.h b/subversion/include/svn_error_codes.h
index 42f830e..b386ac6 100644
--- a/subversion/include/svn_error_codes.h
+++ b/subversion/include/svn_error_codes.h
@@ -171,6 +171,10 @@
              SVN_ERR_BAD_CATEGORY_START + 4,
              "Bogus mime-type")
 
+  SVN_ERRDEF(SVN_ERR_BAD_SERVER_SPECIFICATION,
+             SVN_ERR_BAD_CATEGORY_START + 5,
+             "Bogus server specification")
+
   /** @since New in 1.5.
    *
    * Note that there was an unused slot sitting here at
@@ -1153,6 +1157,16 @@
              SVN_ERR_MISC_CATEGORY_START + 26,
              "Inquiry about unknown capability")
 
+  /** @since New in 1.6. */
+  SVN_ERRDEF(SVN_ERR_TEST_SKIPPED,
+             SVN_ERR_MISC_CATEGORY_START + 27,
+             "Test skipped")
+
+  /** @since New in 1.6. */
+  SVN_ERRDEF(SVN_ERR_NO_APR_MEMCACHE,
+             SVN_ERR_MISC_CATEGORY_START + 28,
+             "apr memcache library not available")
+
   /* command-line client errors */
 
   SVN_ERRDEF(SVN_ERR_CL_ARG_PARSING_ERROR,
diff --git a/subversion/include/svn_io.h b/subversion/include/svn_io.h
index 57f28d7..d7f5b91 100644
--- a/subversion/include/svn_io.h
+++ b/subversion/include/svn_io.h
@@ -628,6 +628,12 @@
 svn_stream_t *svn_stream_from_stringbuf(svn_stringbuf_t *str,
                                         apr_pool_t *pool);
 
+/** Return a generic read-only stream connected to string @a str.
+ *  Allocate the stream in @a pool.
+ */
+svn_stream_t *svn_stream_from_string(svn_string_t *str,
+                                     apr_pool_t *pool);
+
 /** Return a stream that decompresses all data read and compresses all
  * data written. The stream @a stream is used to read and write all
  * compressed data. All compression data structures are allocated on
diff --git a/subversion/include/svn_path.h b/subversion/include/svn_path.h
index d456c2f..09b5cea 100644
--- a/subversion/include/svn_path.h
+++ b/subversion/include/svn_path.h
@@ -32,6 +32,7 @@
  *    - @c svn_path_canonicalize()
  *    - @c svn_path_is_canonical()
  *    - @c svn_path_internal_style()
+ *    - @c svn_path_uri_encode()
  *
  * For the most part, we mean what most anyone would mean when talking
  * about canonical paths, but to be on the safe side, you must run
@@ -459,7 +460,9 @@
 /** Return @c TRUE iff @a path is URI-safe, @c FALSE otherwise. */
 svn_boolean_t svn_path_is_uri_safe(const char *path);
 
-/** Return a URI-encoded copy of @a path, allocated in @a pool. */
+/** Return a URI-encoded copy of @a path, allocated in @a pool.  (@a
+    path can be an arbitrary UTF-8 string and does not have to be a
+    canonical path.) */
 const char *svn_path_uri_encode(const char *path, apr_pool_t *pool);
 
 /** Return a URI-decoded copy of @a path, allocated in @a pool. */
diff --git a/subversion/libsvn_fs_fs/caching.c b/subversion/libsvn_fs_fs/caching.c
new file mode 100644
index 0000000..ca6fcd6
--- /dev/null
+++ b/subversion/libsvn_fs_fs/caching.c
@@ -0,0 +1,241 @@
+/* caching.c : in-memory caching
+ *
+ * ====================================================================
+ * 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 "fs.h"
+#include "fs_fs.h"
+#include "id.h"
+#include "dag.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_config.h"
+
+#include "svn_private_config.h"
+
+/*** Dup/serialize/deserialize functions. ***/
+
+
+/** Caching SVN_FS_ID_T values. **/
+static svn_cache_dup_func_t dup_id;
+static svn_error_t *
+dup_id(void **out,
+       void *in,
+       apr_pool_t *pool)
+{
+  svn_fs_id_t *id = in;
+  *out = svn_fs_fs__id_copy(id, pool);
+  return SVN_NO_ERROR;
+}
+
+static svn_cache_serialize_func_t serialize_id;
+static svn_error_t *
+serialize_id(char **data,
+             apr_size_t *data_len,
+             void *in,
+             apr_pool_t *pool)
+{
+  svn_fs_id_t *id = in;
+  svn_string_t *id_str = svn_fs_fs__id_unparse(id, pool);
+  *data = (char *) id_str->data;
+  *data_len = id_str->len;
+
+  return SVN_NO_ERROR;
+}
+
+
+static svn_cache_deserialize_func_t deserialize_id;
+static svn_error_t *
+deserialize_id(void **out,
+               const char *data,
+               apr_size_t data_len,
+               apr_pool_t *pool)
+{
+  svn_fs_id_t *id = svn_fs_fs__id_parse(data, data_len, pool);
+  if (id == NULL)
+    {
+      return svn_error_create(SVN_ERR_FS_NOT_ID, NULL,
+                              _("Bad ID in cache"));
+    }
+
+  *out = id;
+  return SVN_NO_ERROR;
+}
+
+
+/** Caching directory listings. **/
+static svn_cache_dup_func_t dup_dir_listing;
+static svn_error_t *
+dup_dir_listing(void **out,
+                void *in,
+                apr_pool_t *pool)
+{
+  apr_hash_t *new_entries = apr_hash_make(pool), *entries = in;
+  apr_hash_index_t *hi;
+
+  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+    {
+      void *val;
+      svn_fs_dirent_t *dirent, *new_dirent;
+
+      apr_hash_this(hi, NULL, NULL, &val);
+      dirent = val;
+      new_dirent = apr_palloc(pool, sizeof(*new_dirent));
+      new_dirent->name = apr_pstrdup(pool, dirent->name);
+      new_dirent->kind = dirent->kind;
+      new_dirent->id = svn_fs_fs__id_copy(dirent->id, pool);
+      apr_hash_set(new_entries, new_dirent->name, APR_HASH_KEY_STRING,
+                   new_dirent);
+    }
+
+  *out = new_entries;
+  return SVN_NO_ERROR;
+}
+
+/* Return a memcache in *MEMCACHE_P for FS if it's configured to use
+   memcached, or NULL otherwise.  Also, sets *FAIL_STOP to a boolean
+   indicating whether cache errors should be returned to the caller or
+   just passed to the FS warning handler.  Use FS->pool for allocating
+   the memcache, and POOL for temporary allocations. */
+svn_error_t *
+read_config(svn_memcache_t **memcache_p,
+            svn_boolean_t *fail_stop,
+            svn_fs_t *fs,
+            apr_pool_t *pool)
+{
+  svn_config_t *config;
+
+  SVN_ERR(svn_fs_fs__get_config(&config, fs, pool));
+  SVN_ERR(svn_cache_make_memcache_from_config(memcache_p, config,
+                                              fs->pool));
+  SVN_ERR(svn_config_get_bool(config, fail_stop,
+                              CONFIG_SECTION_CACHES, CONFIG_OPTION_FAIL_STOP,
+                              FALSE));
+
+  return SVN_NO_ERROR;
+
+}
+
+
+static svn_cache_error_handler_t warn_on_cache_errors;
+static svn_error_t *
+warn_on_cache_errors(svn_error_t *err,
+                     void *baton,
+                     apr_pool_t *pool)
+{
+  svn_fs_t *fs = baton;
+  (fs->warning)(fs->warning_baton, err);
+  svn_error_clear(err);
+  return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__initialize_caches(svn_fs_t *fs,
+                             apr_pool_t *pool)
+{
+  fs_fs_data_t *ffd = fs->fsap_data;
+  const char *prefix = apr_pstrcat(pool,
+                                   "fsfs:", ffd->uuid,
+                                   "/", fs->path, ":",
+                                   NULL);
+  svn_memcache_t *memcache;
+  svn_boolean_t no_handler;
+
+  SVN_ERR(read_config(&memcache, &no_handler, fs, pool));
+
+  /* Make the cache for revision roots.  For the vast majority of
+   * commands, this is only going to contain a few entries (svnadmin
+   * dump/verify is an exception here), so to reduce overhead let's
+   * try to keep it to just one page.  I estimate each entry has about
+   * 72 bytes of overhead (svn_revnum_t key, svn_fs_id_t +
+   * id_private_t + 3 strings for value, and the cache_entry); the
+   * default pool size is 8192, so about a hundred should fit
+   * comfortably. */
+  if (memcache)
+    SVN_ERR(svn_cache_create_memcache(&(ffd->rev_root_id_cache),
+                                      memcache,
+                                      serialize_id,
+                                      deserialize_id,
+                                      sizeof(svn_revnum_t),
+                                      apr_pstrcat(pool, prefix, "RRI",
+                                                  NULL),
+                                      fs->pool));
+  else
+    SVN_ERR(svn_cache_create_inprocess(&(ffd->rev_root_id_cache),
+                                       dup_id, sizeof(svn_revnum_t),
+                                       1, 100, FALSE, fs->pool));
+  if (! no_handler)
+    SVN_ERR(svn_cache_set_error_handler(ffd->rev_root_id_cache,
+                                        warn_on_cache_errors, fs, pool));
+
+
+  /* Rough estimate: revision DAG nodes have size around 320 bytes, so
+   * let's put 16 on a page. */
+  if (memcache)
+    SVN_ERR(svn_cache_create_memcache(&(ffd->rev_node_cache),
+                                      memcache,
+                                      svn_fs_fs__dag_serialize,
+                                      svn_fs_fs__dag_deserialize,
+                                      APR_HASH_KEY_STRING,
+                                      apr_pstrcat(pool, prefix, "DAG",
+                                                  NULL),
+                                      fs->pool));
+  else
+    SVN_ERR(svn_cache_create_inprocess(&(ffd->rev_node_cache),
+                                       svn_fs_fs__dag_dup_for_cache,
+                                       APR_HASH_KEY_STRING,
+                                       1024, 16, FALSE, fs->pool));
+  if (! no_handler)
+    SVN_ERR(svn_cache_set_error_handler(ffd->rev_node_cache,
+                                        warn_on_cache_errors, fs, pool));
+
+
+  /* Very rough estimate: 1K per directory. */
+  if (memcache)
+    SVN_ERR(svn_cache_create_memcache(&(ffd->dir_cache),
+                                      memcache,
+                                      svn_fs_fs__dir_entries_serialize,
+                                      svn_fs_fs__dir_entries_deserialize,
+                                      APR_HASH_KEY_STRING,
+                                      apr_pstrcat(pool, prefix, "DIR",
+                                                  NULL),
+                                      fs->pool));
+  else
+    SVN_ERR(svn_cache_create_inprocess(&(ffd->dir_cache),
+                                       dup_dir_listing, APR_HASH_KEY_STRING,
+                                       1024, 8, FALSE, fs->pool));
+
+  if (! no_handler)
+    SVN_ERR(svn_cache_set_error_handler(ffd->dir_cache,
+                                        warn_on_cache_errors, fs, pool));
+
+  if (memcache)
+    SVN_ERR(svn_cache_create_memcache(&(ffd->fulltext_cache),
+                                      memcache,
+                                      NULL, NULL, /* Values are svn_string_t */
+                                      APR_HASH_KEY_STRING,
+                                      apr_pstrcat(pool, prefix, "TEXT",
+                                                  NULL),
+                                      fs->pool));
+  else
+    ffd->fulltext_cache = NULL;
+
+  if (! no_handler)
+    SVN_ERR(svn_cache_set_error_handler(ffd->fulltext_cache,
+                                        warn_on_cache_errors, fs, pool));
+
+  return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_fs/dag.c b/subversion/libsvn_fs_fs/dag.c
index 5e17e46..fa60a90 100644
--- a/subversion/libsvn_fs_fs/dag.c
+++ b/subversion/libsvn_fs_fs/dag.c
@@ -98,6 +98,12 @@
   return node->fs;
 }
 
+void
+svn_fs_fs__dag_set_fs(dag_node_t *node, svn_fs_t *fs)
+{
+  node->fs = fs;
+}
+
 
 /* Dup NODEREV and all associated data into POOL.
    Leaves the id and is_fresh_txn_root fields as zero bytes. */
@@ -1073,6 +1079,137 @@
   return new_node;
 }
 
+svn_error_t *
+svn_fs_fs__dag_dup_for_cache(void **out,
+                             void *in,
+                             apr_pool_t *pool)
+{
+  dag_node_t *in_node = in, *out_node;
+  out_node = svn_fs_fs__dag_dup(in_node, pool);
+  out_node->fs = NULL;
+  *out = out_node;
+  return SVN_NO_ERROR;
+}
+
+/* The cache serialization format is:
+ *
+ * - For mutable nodes: the character 'M', then 'F' for files or 'D'
+ *   for directories, then the ID, then '\n', then the created path.
+ *
+ * - For immutable nodes: the character 'I' followed by the noderev
+ *   hash dump (the other fields can be reconstructed from this).  (We
+ *   assume that, once constructed, immutable nodes always contain
+ *   their noderev.)
+ */
+
+svn_error_t *
+svn_fs_fs__dag_serialize(char **data,
+                         apr_size_t *data_len,
+                         void *in,
+                         apr_pool_t *pool)
+{
+  dag_node_t *node = in;
+  svn_stringbuf_t *buf = svn_stringbuf_create("", pool);
+
+  if (svn_fs_fs__dag_check_mutable(node))
+    {
+      svn_stringbuf_appendcstr(buf, "M");
+      svn_stringbuf_appendcstr(buf, (node->kind == svn_node_file ? "F" : "D"));
+      svn_stringbuf_appendcstr(buf, svn_fs_fs__id_unparse(node->id,
+                                                          pool)->data);
+      svn_stringbuf_appendcstr(buf, "\n");
+      svn_stringbuf_appendcstr(buf, node->created_path);
+    }
+  else
+    {
+      svn_stringbuf_appendcstr(buf, "I");
+      SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_stringbuf(buf, pool),
+                                       node->node_revision, TRUE, pool));
+    }
+
+  *data = buf->data;
+  *data_len = buf->len;
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__dag_deserialize(void **out,
+                           const char *data,
+                           apr_size_t data_len,
+                           apr_pool_t *pool)
+{
+  dag_node_t *node = apr_pcalloc(pool, sizeof(*node));
+
+  if (data_len == 0)
+    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                            _("Empty noderev in cache"));
+
+  if (*data == 'M')
+    {
+      const char *newline;
+      int id_len;
+
+      data++; data_len--;
+      if (data_len == 0)
+        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                                _("Kindless noderev in cache"));
+      if (*data == 'F')
+        node->kind = svn_node_file;
+      else if (*data == 'D')
+        node->kind = svn_node_dir;
+      else
+        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                                 _("Unknown kind for noderev in cache: '%c'"),
+                                 *data);
+
+      data++; data_len--;
+      newline = memchr(data, '\n', data_len);
+      if (!newline)
+        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                                _("Unterminated ID in cache"));
+      id_len = newline - 1 - data;
+      node->id = svn_fs_fs__id_parse(data, id_len, pool);
+      if (! node->id)
+        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                                 _("Bogus ID '%s' in cache"),
+                                 apr_pstrndup(pool, data, id_len));
+
+      data += id_len; data_len -= id_len;
+      data++; data_len--;
+      if (data_len == 0)
+        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                                _("No created path"));
+      node->created_path = apr_pstrndup(pool, data, data_len);
+    }
+  else if (*data == 'I')
+    {
+      node_revision_t *noderev;
+      apr_pool_t *subpool = svn_pool_create(pool);
+      svn_stream_t *stream =
+        svn_stream_from_stringbuf(svn_stringbuf_ncreate(data + 1,
+                                                        data_len - 1,
+                                                        subpool),
+                                  subpool);
+      SVN_ERR(svn_fs_fs__read_noderev(&noderev, stream, pool));
+      node->kind = noderev->kind;
+      node->id = svn_fs_fs__id_copy(noderev->id, pool);
+      node->created_path = apr_pstrdup(pool, noderev->created_path);
+
+      if (noderev->is_fresh_txn_root)
+        node->fresh_root_predecessor_id = noderev->predecessor_id;
+
+      node->node_revision = noderev;
+
+      svn_pool_destroy(subpool);
+    }
+  else
+    return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                             _("Unknown node type in cache: '%c'"), *data);
+
+  *out = node;
+
+  return SVN_NO_ERROR;
+}
 
 svn_error_t *
 svn_fs_fs__dag_open(dag_node_t **child_p,
diff --git a/subversion/libsvn_fs_fs/dag.h b/subversion/libsvn_fs_fs/dag.h
index 7a2c4a4..79741c3 100644
--- a/subversion/libsvn_fs_fs/dag.h
+++ b/subversion/libsvn_fs_fs/dag.h
@@ -20,8 +20,7 @@
 
 #include "svn_fs.h"
 #include "svn_delta.h"
-
-#include "fs.h"
+#include "svn_cache.h"
 
 #ifdef __cplusplus
 extern "C" {
@@ -55,6 +54,7 @@
 
 /* Generic DAG node stuff.  */
 
+typedef struct dag_node_t dag_node_t;
 
 /* Fill *NODE with a dag_node_t representing node revision ID in FS,
    allocating in POOL.  */
@@ -72,10 +72,23 @@
 dag_node_t *svn_fs_fs__dag_dup(dag_node_t *node,
                                apr_pool_t *pool);
 
+/* Like svn_fs_fs__dag_dup, but implementing the svn_cache_dup_func_t
+   prototype, and NULLing the FS field. */
+svn_cache_dup_func_t svn_fs_fs__dag_dup_for_cache;
+
+/* Serialize a DAG node. */
+svn_cache_serialize_func_t svn_fs_fs__dag_serialize;
+
+/* Deserialize a DAG node. */
+svn_cache_deserialize_func_t svn_fs_fs__dag_deserialize;
 
 /* Return the filesystem containing NODE.  */
 svn_fs_t *svn_fs_fs__dag_get_fs(dag_node_t *node);
 
+/* Changes the filesystem containing NODE to FS.  (Used when pulling
+   nodes out of a shared cache, say.) */
+void svn_fs_fs__dag_set_fs(dag_node_t *node, svn_fs_t *fs);
+
 
 /* Set *REV to NODE's revision number, allocating in POOL.  If NODE
    has never been committed as part of a revision, set *REV to
diff --git a/subversion/libsvn_fs_fs/fs.c b/subversion/libsvn_fs_fs/fs.c
index d3cf428..afef0e7 100644
--- a/subversion/libsvn_fs_fs/fs.c
+++ b/subversion/libsvn_fs_fs/fs.c
@@ -33,6 +33,7 @@
 #include "fs_fs.h"
 #include "tree.h"
 #include "lock.h"
+#include "id.h"
 #include "svn_private_config.h"
 #include "private/svn_fs_util.h"
 
@@ -158,19 +159,13 @@
 /* Creating a new filesystem. */
 
 /* Set up vtable and fsap_data fields in FS. */
-static void
+static svn_error_t *
 initialize_fs_struct(svn_fs_t *fs)
 {
   fs_fs_data_t *ffd = apr_pcalloc(fs->pool, sizeof(*ffd));
   fs->vtable = &fs_vtable;
   fs->fsap_data = ffd;
-
-  ffd->rev_root_id_cache_pool = svn_pool_create(fs->pool);
-  ffd->rev_root_id_cache = apr_hash_make(ffd->rev_root_id_cache_pool);
-
-  ffd->rev_node_cache = apr_hash_make(fs->pool);
-  ffd->rev_node_list.prev = &ffd->rev_node_list;
-  ffd->rev_node_list.next = &ffd->rev_node_list;
+  return SVN_NO_ERROR;
 }
 
 /* This implements the fs_library_vtable_t.create() API.  Create a new
@@ -183,9 +178,11 @@
 {
   SVN_ERR(svn_fs__check_fs(fs, FALSE));
 
-  initialize_fs_struct(fs);
+  SVN_ERR(initialize_fs_struct(fs));
 
   SVN_ERR(svn_fs_fs__create(fs, path, pool));
+
+  SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
   return fs_serialized_init(fs, common_pool, pool);
 }
 
@@ -201,9 +198,11 @@
 fs_open(svn_fs_t *fs, const char *path, apr_pool_t *pool,
         apr_pool_t *common_pool)
 {
-  initialize_fs_struct(fs);
+  SVN_ERR(initialize_fs_struct(fs));
 
   SVN_ERR(svn_fs_fs__open(fs, path, pool));
+
+  SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
   return fs_serialized_init(fs, common_pool, pool);
 }
 
@@ -242,8 +241,9 @@
            apr_pool_t *common_pool)
 {
   SVN_ERR(svn_fs__check_fs(fs, FALSE));
-  initialize_fs_struct(fs);
+  SVN_ERR(initialize_fs_struct(fs));
   SVN_ERR(svn_fs_fs__open(fs, path, pool));
+  SVN_ERR(svn_fs_fs__initialize_caches(fs, pool));
   SVN_ERR(fs_serialized_init(fs, common_pool, pool));
   return svn_fs_fs__upgrade(fs, pool);
 }
diff --git a/subversion/libsvn_fs_fs/fs.h b/subversion/libsvn_fs_fs/fs.h
index 421be41..adaab5c 100644
--- a/subversion/libsvn_fs_fs/fs.h
+++ b/subversion/libsvn_fs_fs/fs.h
@@ -25,6 +25,8 @@
 #include <apr_network_io.h>
 
 #include "svn_fs.h"
+#include "svn_cache.h"
+#include "svn_config.h"
 #include "private/svn_fs_private.h"
 
 #ifdef __cplusplus
@@ -50,6 +52,8 @@
 #define PATH_TXN_CURRENT      "txn-current"      /* File with next txn key */
 #define PATH_TXN_CURRENT_LOCK "txn-current-lock" /* Lock for txn-current */
 #define PATH_LOCKS_DIR        "locks"            /* Directory of locks */
+/* If you change this, look at tests/svn_test_fs.c(maybe_install_fsfs_conf) */
+#define PATH_CONFIG           "fsfs.conf"        /* Configuration */
 
 /* Names of special files and file extensions for transactions */
 #define PATH_CHANGES       "changes"       /* Records changes made so far */
@@ -65,6 +69,10 @@
 #define PATH_REV           "rev"           /* Proto rev file */
 #define PATH_REV_LOCK      "rev-lock"      /* Proto rev (write) lock file */
 
+/* Names of sections and options in fsfs.conf. */
+#define CONFIG_SECTION_CACHES     "caches"
+#define CONFIG_OPTION_FAIL_STOP       "fail-stop"
+
 /* The format number of this filesystem.
    This is independent of the repository format number, and
    independent of any other FS back ends. */
@@ -91,15 +99,6 @@
    noderev fields. */
 #define SVN_FS_FS__MIN_MERGEINFO_FORMAT 3
 
-/* Maximum number of directories to cache dirents for.
-   This *must* be a power of 2 for DIR_CACHE_ENTRIES_MASK
-   to work.  */
-#define NUM_DIR_CACHE_ENTRIES 128
-#define DIR_CACHE_ENTRIES_MASK(x) ((x) & (NUM_DIR_CACHE_ENTRIES - 1))
-
-/* Maximum number of revroot ids to cache dirents for at a time. */
-#define NUM_RRI_CACHE_ENTRIES 4096
-
 /* Private FSFS-specific data shared between all svn_txn_t objects that
    relate to a particular transaction in a filesystem (as identified
    by transaction id and filesystem UUID).  Objects of this type are
@@ -164,32 +163,9 @@
   apr_pool_t *common_pool;
 } fs_fs_shared_data_t;
 
-typedef struct dag_node_t dag_node_t;
-
-/* Structure for DAG-node cache.  Cache items are arranged in a
-   circular LRU list with a dummy entry, and also indexed with a hash
-   table.  Transaction nodes are cached within the individual txn
-   roots; revision nodes are cached together within the FS object. */
-typedef struct dag_node_cache_t
-{
-  const char *key;                /* Lookup key for cached node: path
-                                     for txns; rev catenated with path
-                                     for revs */
-  dag_node_t *node;               /* Cached node */
-  struct dag_node_cache_t *prev;  /* Next node in LRU list */
-  struct dag_node_cache_t *next;  /* Previous node in LRU list */
-  apr_pool_t *pool;               /* Pool in which node is allocated */
-} dag_node_cache_t;
-
-
 /* Private (non-shared) FSFS-specific data for each svn_fs_t object. */
 typedef struct
 {
-  /* A cache of the last directory opened within the filesystem. */
-  svn_fs_id_t *dir_cache_id[NUM_DIR_CACHE_ENTRIES];
-  apr_hash_t *dir_cache[NUM_DIR_CACHE_ENTRIES];
-  apr_pool_t *dir_cache_pool[NUM_DIR_CACHE_ENTRIES];
-
   /* The format number of this FS. */
   int format;
   /* The maximum number of files to store per directory (for sharded
@@ -202,23 +178,27 @@
   /* The revision that was youngest, last time we checked. */
   svn_revnum_t youngest_rev_cache;
 
-  /* Caches of immutable data.
-     
-     Both of these could be moved to fs_fs_shared_data_t to make them
-     last longer; on the other hand, this would require adding mutexes
-     for threaded builds.
-  */
+  /* The fsfs.conf file, parsed.  Allocated in FS->pool. */
+  svn_config_t *config;
 
-  /* A cache of revision root IDs, allocated in this subpool.  (IDs
-     are so small that one pool per ID would be overkill;
-     unfortunately, this means the only way we expire cache entries is
-     by wiping the whole cache.) */
-  apr_hash_t *rev_root_id_cache;
-  apr_pool_t *rev_root_id_cache_pool;
+  /* Caches of immutable data.  (Note that if these are created with
+     svn_cache_create_memcache, the data can be shared between
+     multiple svn_fs_t's for the same filesystem.) */
+
+  /* A cache of revision root IDs, mapping from (svn_revnum_t *) to
+     (svn_fs_id_t *).  (Not threadsafe.) */
+  svn_cache_t *rev_root_id_cache;
 
   /* DAG node cache for immutable nodes */
-  dag_node_cache_t rev_node_list;
-  apr_hash_t *rev_node_cache;
+  svn_cache_t *rev_node_cache;
+
+  /* A cache of the contents of immutable directories; maps from
+     unparsed FS ID to ###x. */
+  svn_cache_t *dir_cache;
+
+  /* Fulltext cache; currently only used with memcached.  Maps from
+     rep key to svn_string_t. */
+  svn_cache_t *fulltext_cache;
 
   /* Data shared between all svn_fs_t objects for a given filesystem. */
   fs_fs_shared_data_t *shared;
diff --git a/subversion/libsvn_fs_fs/fs_fs.c b/subversion/libsvn_fs_fs/fs_fs.c
index b59ef06..8b42699 100644
--- a/subversion/libsvn_fs_fs/fs_fs.c
+++ b/subversion/libsvn_fs_fs/fs_fs.c
@@ -39,6 +39,7 @@
 #include "svn_sorts.h"
 #include "svn_time.h"
 #include "svn_mergeinfo.h"
+#include "svn_config.h"
 
 #include "fs.h"
 #include "err.h"
@@ -982,6 +983,58 @@
   return ffd->format >= SVN_FS_FS__MIN_MERGEINFO_FORMAT;
 }
 
+svn_error_t *
+svn_fs_fs__get_config(svn_config_t **config,
+                      svn_fs_t *fs,
+                      apr_pool_t *pool)
+{
+  fs_fs_data_t *ffd = fs->fsap_data;
+
+  if (! ffd->config)
+    SVN_ERR(svn_config_read(&(ffd->config),
+                            svn_path_join(fs->path, PATH_CONFIG, pool),
+                            FALSE,
+                            fs->pool));
+
+  *config = ffd->config;
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_config(svn_fs_t *fs,
+             apr_pool_t *pool)
+{
+#define NL APR_EOL_STR
+  static const char * const fsfs_conf_contents =
+"### This file controls the configuration of the FSFS filesystem."           NL
+""                                                                           NL
+"[" SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS "]"                          NL
+"### These options name memcached servers used to cache internal FSFS"       NL
+"### data.  See http://www.danga.com/memcached/ for more information on"     NL
+"### memcached.  To use memcached with FSFS, run one or more memcached"      NL
+"### servers, and specify each of them as an option like so:"                NL
+"# first-server = 127.0.0.1:11211"                                           NL
+"# remote-memcached = mymemcached.corp.example.com:11212"                    NL
+"### The option name is ignored; the value is of the form HOST:PORT."        NL
+"### memcached servers can be shared between multiple repositories;"         NL
+"### however, if you do this, you *must* ensure that repositories have"      NL
+"### distinct UUIDs and paths, or else cached data from one repository"      NL
+"### might be used by another accidentally.  Note also that memcached has"   NL
+"### no authentication for reads or writes, so you must sure that your"      NL
+"### memcached servers are only accessible by trusted users."                NL
+""                                                                           NL
+"[" CONFIG_SECTION_CACHES "]"                                                NL
+"### When a cache-related error occurs, normally Subversion ignores it"      NL
+"### and continues, logging an error if the server is appropriately"         NL
+"### configured (and ignoring it with file:// access).  To make"             NL
+"### Subversion never ignore cache errors, uncomment this line."             NL
+"# " CONFIG_OPTION_FAIL_STOP " = true"                                       NL
+;
+#undef NL
+  return svn_io_file_create(svn_path_join(fs->path, PATH_CONFIG, pool),
+                            fsfs_conf_contents, pool);
+}
+
 static svn_error_t *
 get_youngest(svn_revnum_t *youngest_p, const char *fs_path, apr_pool_t *pool);
 
@@ -1364,39 +1417,31 @@
   return SVN_NO_ERROR;
 }
 
-/* HEADER_CPATH lines need to be long enough to hold FSFS_MAX_PATH_LEN
- * bytes plus the stuff around them. */
-#define MAX_HEADERS_STR_LEN FSFS_MAX_PATH_LEN + sizeof(HEADER_CPATH ": \n") - 1
-
 /* Given a revision file FILE that has been pre-positioned at the
    beginning of a Node-Rev header block, read in that header block and
    store it in the apr_hash_t HEADERS.  All allocations will be from
    POOL. */
 static svn_error_t * read_header_block(apr_hash_t **headers,
-                                       apr_file_t *file,
+                                       svn_stream_t *stream,
                                        apr_pool_t *pool)
 {
   *headers = apr_hash_make(pool);
 
   while (1)
     {
-      char header_str[MAX_HEADERS_STR_LEN];
+      svn_stringbuf_t *header_str;
       const char *name, *value;
-      apr_size_t i = 0, header_len;
-      apr_size_t limit;
-      char *local_name, *local_value;
+      apr_size_t i = 0;
+      svn_boolean_t eof;
 
-      limit = sizeof(header_str);
-      SVN_ERR(svn_io_read_length_line(file, header_str, &limit, pool));
+      SVN_ERR(svn_stream_readline(stream, &header_str, "\n", &eof, pool));
 
-      if (strlen(header_str) == 0)
+      if (eof || header_str->len == 0)
         break; /* end of header block */
 
-      header_len = strlen(header_str);
-
-      while (header_str[i] != ':')
+      while (header_str->data[i] != ':')
         {
-          if (header_str[i] == '\0')
+          if (header_str->data[i] == '\0')
             return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
                                     _("Found malformed header in "
                                       "revision file"));
@@ -1404,23 +1449,22 @@
         }
 
       /* Create a 'name' string and point to it. */
-      header_str[i] = '\0';
-      name = header_str;
+      header_str->data[i] = '\0';
+      name = header_str->data;
 
       /* Skip over the NULL byte and the space following it. */
       i += 2;
 
-      if (i > header_len)
+      if (i > header_str->len)
         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
                                 _("Found malformed header in "
                                   "revision file"));
 
-      value = header_str + i;
+      value = header_str->data + i;
 
-      local_name = apr_pstrdup(pool, name);
-      local_value = apr_pstrdup(pool, value);
-
-      apr_hash_set(*headers, local_name, APR_HASH_KEY_STRING, local_value);
+      /* header_str is safely in our pool, so we can use bits of it as
+         key and value. */
+      apr_hash_set(*headers, name, APR_HASH_KEY_STRING, value);
     }
 
   return SVN_NO_ERROR;
@@ -1622,9 +1666,6 @@
                        apr_pool_t *pool)
 {
   apr_file_t *revision_file;
-  apr_hash_t *headers;
-  node_revision_t *noderev;
-  char *value;
   svn_error_t *err;
 
   if (svn_fs_fs__id_txn_id(id))
@@ -1653,7 +1694,22 @@
       return err;
     }
 
-  SVN_ERR(read_header_block(&headers, revision_file, pool) );
+  return svn_fs_fs__read_noderev(noderev_p,
+                                 svn_stream_from_aprfile2(revision_file, FALSE,
+                                                          pool),
+                                 pool);
+}
+
+svn_error_t *
+svn_fs_fs__read_noderev(node_revision_t **noderev_p,
+                        svn_stream_t *stream,
+                        apr_pool_t *pool)
+{
+  apr_hash_t *headers;
+  node_revision_t *noderev;
+  char *value;
+
+  SVN_ERR(read_header_block(&headers, stream, pool));
 
   noderev = apr_pcalloc(pool, sizeof(*noderev));
 
@@ -1663,7 +1719,7 @@
       return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
                               _("Missing id field in node-rev"));
 
-  SVN_ERR(svn_io_file_close(revision_file, pool));
+  SVN_ERR(svn_stream_close(stream));
 
   noderev->id = svn_fs_fs__id_parse(value, strlen(value), pool);
 
@@ -1687,7 +1743,7 @@
   if (value)
     {
       SVN_ERR(read_rep_offsets(&noderev->prop_rep, value,
-                               svn_fs_fs__id_txn_id(id), TRUE, pool));
+                               svn_fs_fs__id_txn_id(noderev->id), TRUE, pool));
     }
 
   /* Get the data location. */
@@ -1695,7 +1751,7 @@
   if (value)
     {
       SVN_ERR(read_rep_offsets(&noderev->data_rep, value,
-                               svn_fs_fs__id_txn_id(id),
+                               svn_fs_fs__id_txn_id(noderev->id),
                                (noderev->kind == svn_node_dir), pool));
     }
 
@@ -1819,19 +1875,13 @@
                                                           pool));
 }
 
-/* Write the node-revision NODEREV into the file FILE.  Only write
-   mergeinfo-related metadata if INCLUDE_MERGEINFO is true.  Temporary
-   allocations are from POOL. */
-static svn_error_t *
-write_noderev_txn(apr_file_t *file,
-                  node_revision_t *noderev,
-                  svn_boolean_t include_mergeinfo,
-                  apr_pool_t *pool)
+
+svn_error_t *
+svn_fs_fs__write_noderev(svn_stream_t *outfile,
+                         node_revision_t *noderev,
+                         svn_boolean_t include_mergeinfo,
+                         apr_pool_t *pool)
 {
-  svn_stream_t *outfile;
-
-  outfile = svn_stream_from_aprfile(file, pool);
-
   SVN_ERR(svn_stream_printf(outfile, pool, HEADER_ID ": %s\n",
                             svn_fs_fs__id_unparse(noderev->id,
                                                   pool)->data));
@@ -1915,9 +1965,10 @@
                            APR_WRITE | APR_CREATE | APR_TRUNCATE
                            | APR_BUFFERED, APR_OS_DEFAULT, pool));
 
-  SVN_ERR(write_noderev_txn(noderev_file, noderev,
-                            svn_fs_fs__fs_supports_mergeinfo(fs),
-                            pool));
+  SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile(noderev_file, pool),
+                                   noderev,
+                                   svn_fs_fs__fs_supports_mergeinfo(fs),
+                                   pool));
 
   SVN_ERR(svn_io_file_close(noderev_file, pool));
 
@@ -2013,7 +2064,8 @@
 
   SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
 
-  SVN_ERR(read_header_block(&headers, rev_file, pool));
+  SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile(rev_file, pool),
+                            pool));
 
   node_id_str = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING);
 
@@ -2120,21 +2172,14 @@
   apr_off_t root_offset;
   svn_fs_id_t *root_id;
   svn_error_t *err;
-  const char *rev_str = apr_psprintf(pool, "%ld", rev);
-  svn_fs_id_t *cached_id;
+  svn_boolean_t is_cached;
 
   SVN_ERR(ensure_revision_exists(fs, rev, pool));
 
-  /* Calculate an index into the revroot id cache */
-  cached_id = apr_hash_get(ffd->rev_root_id_cache,
-                           rev_str,
-                           APR_HASH_KEY_STRING);
-
-  if (cached_id)
-    {
-      *root_id_p = svn_fs_fs__id_copy(cached_id, pool);
-      return SVN_NO_ERROR;
-    }
+  SVN_ERR(svn_cache_get((void **) root_id_p, &is_cached, ffd->rev_root_id_cache,
+                        &rev, pool));
+  if (is_cached)
+    return SVN_NO_ERROR;
 
   err = svn_io_file_open(&revision_file, svn_fs_fs__path_rev(fs, rev, pool),
                          APR_READ | APR_BUFFERED, APR_OS_DEFAULT, pool);
@@ -2154,21 +2199,7 @@
 
   SVN_ERR(svn_io_file_close(revision_file, pool));
 
-  /* Make sure our cache size doesn't grow without bounds. */
-  if (apr_hash_count(ffd->rev_root_id_cache) >= NUM_RRI_CACHE_ENTRIES)
-    {
-      /* In order to only use one pool for the whole cache, we need to
-       * completely wipe it to expire entries! */
-      svn_pool_clear(ffd->rev_root_id_cache_pool);
-      ffd->rev_root_id_cache = apr_hash_make(ffd->rev_root_id_cache_pool);
-    }
-
-  /* Cache the answer, copying both the key and value into the cache's
-     pool. */
-  apr_hash_set(ffd->rev_root_id_cache,
-               apr_pstrdup(ffd->rev_root_id_cache_pool, rev_str),
-               APR_HASH_KEY_STRING,
-               svn_fs_fs__id_copy(root_id, ffd->rev_root_id_cache_pool));
+  SVN_ERR(svn_cache_set(ffd->rev_root_id_cache, &rev, root_id, pool));
 
   *root_id_p = root_id;
 
@@ -2419,6 +2450,12 @@
   svn_filesize_t len;
   svn_filesize_t off;
 
+  /* The key for the fulltext cache for this rep, if there is a
+     fulltext cache. */
+  const char *fulltext_cache_key;
+  /* The text we've been reading, if we're going to cache it. */
+  svn_stringbuf_t *current_fulltext;
+
   /* Used for temporary allocations during the read. */
   apr_pool_t *pool;
 
@@ -2428,12 +2465,15 @@
 };
 
 /* Create a rep_read_baton structure for node revision NODEREV in
-   filesystem FS and store it in *RB_P.  Perform all allocations in
+   filesystem FS and store it in *RB_P.  If FULLTEXT_CACHE_KEY is not
+   NULL, it is the rep's key in the fulltext cache, and a stringbuf
+   must be allocated to store the text.  Perform all allocations in
    POOL.  If rep is mutable, it must be for file contents. */
 static svn_error_t *
 rep_read_get_baton(struct rep_read_baton **rb_p,
                    svn_fs_t *fs,
                    representation_t *rep,
+                   const char *fulltext_cache_key,
                    apr_pool_t *pool)
 {
   struct rep_read_baton *b;
@@ -2447,9 +2487,15 @@
   memcpy(b->checksum, rep->checksum, sizeof(b->checksum));
   b->len = rep->expanded_size;
   b->off = 0;
+  b->fulltext_cache_key = fulltext_cache_key;
   b->pool = svn_pool_create(pool);
   b->filehandle_pool = svn_pool_create(pool);
 
+  if (fulltext_cache_key)
+    b->current_fulltext = svn_stringbuf_create("", b->filehandle_pool);
+  else
+    b->current_fulltext = NULL;
+
   SVN_ERR(build_rep_list(&b->rs_list, &b->src_state, fs, rep,
                          b->filehandle_pool));
 
@@ -2707,6 +2753,9 @@
   /* Get the next block of data. */
   SVN_ERR(get_contents(rb, buf, len));
 
+  if (rb->current_fulltext)
+    svn_stringbuf_appendbytes(rb->current_fulltext, buf, *len);
+
   /* Perform checksumming.  We want to check the checksum as soon as
      the last byte of data is read, in case the caller never performs
      a short read, but we don't want to finalize the MD5 context
@@ -2731,9 +2780,31 @@
                svn_md5_digest_to_cstring_display(checksum, rb->pool));
         }
     }
+
+  if (rb->off == rb->len && rb->current_fulltext)
+    {
+      fs_fs_data_t *ffd = rb->fs->fsap_data;
+      SVN_ERR(svn_cache_set(ffd->fulltext_cache, rb->fulltext_cache_key,
+                            rb->current_fulltext, rb->pool));
+      rb->current_fulltext = NULL;
+    }
+
   return SVN_NO_ERROR;
 }
 
+
+/* Returns whether or not the expanded fulltext of the file is
+ * cacheable based on its size SIZE.  Specifically, if it will fit
+ * into a memcached value.  The memcached cutoff seems to be a bit
+ * (header length?) under a megabyte; we round down a little to be
+ * safe.
+ */
+static svn_boolean_t
+fulltext_size_is_cacheable(svn_filesize_t size)
+{
+  return size < 1000000;
+}
+
 /* Return a stream in *CONTENTS_P that will read the contents of a
    representation stored at the location given by REP.  Appropriate
    for any kind of immutable representation, but only for file
@@ -2749,15 +2820,34 @@
                     representation_t *rep,
                     apr_pool_t *pool)
 {
-  struct rep_read_baton *rb;
-
   if (! rep)
     {
       *contents_p = svn_stream_empty(pool);
     }
   else
     {
-      SVN_ERR(rep_read_get_baton(&rb, fs, rep, pool));
+      fs_fs_data_t *ffd = fs->fsap_data;
+      const char *fulltext_key = NULL;
+      struct rep_read_baton *rb;
+
+      if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
+          && fulltext_size_is_cacheable(rep->expanded_size))
+        {
+          svn_string_t *fulltext;
+          svn_boolean_t is_cached;
+          fulltext_key = apr_psprintf(pool, "%ld/%" APR_OFF_T_FMT,
+                                      rep->revision, rep->offset);
+          SVN_ERR(svn_cache_get((void **) &fulltext, &is_cached,
+                                ffd->fulltext_cache, fulltext_key, pool));
+          if (is_cached)
+            {
+              *contents_p = svn_stream_from_string(fulltext, pool);
+              return SVN_NO_ERROR;
+            }
+        }
+
+      SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_key, pool));
+
       *contents_p = svn_stream_create(rb, pool);
       svn_stream_set_read(*contents_p, rep_read_contents);
       svn_stream_set_close(*contents_p, rep_read_contents_close);
@@ -2896,76 +2986,95 @@
   return SVN_NO_ERROR;
 }
 
-/* Return a copy of the directory hash ENTRIES in POOL. */
-static apr_hash_t *
-copy_dir_entries(apr_hash_t *entries,
-                 apr_pool_t *pool)
+
+static const char *
+unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
+                  apr_pool_t *pool)
 {
-  apr_hash_t *new_entries = apr_hash_make(pool);
+  return apr_psprintf(pool, "%s %s",
+                      (kind == svn_node_file) ? KIND_FILE : KIND_DIR,
+                      svn_fs_fs__id_unparse(id, pool)->data);
+}
+
+/* Given a hash ENTRIES of dirent structions, return a hash in
+   *STR_ENTRIES_P, that has svn_string_t as the values in the format
+   specified by the fs_fs directory contents file.  Perform
+   allocations in POOL. */
+static svn_error_t *
+unparse_dir_entries(apr_hash_t **str_entries_p,
+                    apr_hash_t *entries,
+                    apr_pool_t *pool)
+{
   apr_hash_index_t *hi;
 
+  *str_entries_p = apr_hash_make(pool);
+
   for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
     {
+      const void *key;
+      apr_ssize_t klen;
       void *val;
-      svn_fs_dirent_t *dirent, *new_dirent;
+      svn_fs_dirent_t *dirent;
+      const char *new_val;
 
-      apr_hash_this(hi, NULL, NULL, &val);
+      apr_hash_this(hi, &key, &klen, &val);
       dirent = val;
-      new_dirent = apr_palloc(pool, sizeof(*new_dirent));
-      new_dirent->name = apr_pstrdup(pool, dirent->name);
-      new_dirent->kind = dirent->kind;
-      new_dirent->id = svn_fs_fs__id_copy(dirent->id, pool);
-      apr_hash_set(new_entries, new_dirent->name, APR_HASH_KEY_STRING,
-                   new_dirent);
+      new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
+      apr_hash_set(*str_entries_p, key, klen,
+                   svn_string_create(new_val, pool));
     }
-  return new_entries;
+
+  return SVN_NO_ERROR;
 }
 
 
 svn_error_t *
-svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
-                            svn_fs_t *fs,
-                            node_revision_t *noderev,
-                            apr_pool_t *pool)
+svn_fs_fs__dir_entries_serialize(char **data,
+                                 apr_size_t *data_len,
+                                 void *in,
+                                 apr_pool_t *pool)
 {
-  fs_fs_data_t *ffd = fs->fsap_data;
-  apr_hash_t *unparsed_entries, *parsed_entries;
+  apr_hash_t *entries = in;
+  svn_stringbuf_t *buf = svn_stringbuf_create("", pool);
+  svn_stream_t *stream = svn_stream_from_stringbuf(buf, pool);
+
+  SVN_ERR(unparse_dir_entries(&entries, entries, pool));
+  SVN_ERR(svn_hash_write2(entries, stream, SVN_HASH_TERMINATOR, pool));
+
+  *data = buf->data;
+  *data_len = buf->len;
+
+  return SVN_NO_ERROR;
+}
+
+
+/* Given a hash STR_ENTRIES with values as svn_string_t as specified
+   in an FSFS directory contents listing, return a hash of dirents in
+   *ENTRIES_P.  Perform allocations in POOL. */
+static svn_error_t *
+parse_dir_entries(apr_hash_t **entries_p,
+                  apr_hash_t *str_entries,
+                  apr_pool_t *pool)
+{
   apr_hash_index_t *hi;
-  unsigned int hid;
 
-  /* Calculate an index into the dir entries cache.  This should be
-     completely ignored if this is a mutable noderev. */
-  hid = DIR_CACHE_ENTRIES_MASK(svn_fs_fs__id_rev(noderev->id));
-
-  /* If we have this directory cached, return it. */
-  if (! svn_fs_fs__id_txn_id(noderev->id) &&
-      ffd->dir_cache_id[hid] && svn_fs_fs__id_eq(ffd->dir_cache_id[hid],
-                                                 noderev->id))
-    {
-      *entries_p = copy_dir_entries(ffd->dir_cache[hid], pool);
-      return SVN_NO_ERROR;
-    }
-
-  /* Read in the directory hash. */
-  unparsed_entries = apr_hash_make(pool);
-  SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
-
-  parsed_entries = apr_hash_make(pool);
+  *entries_p = apr_hash_make(pool);
 
   /* Translate the string dir entries into real entries. */
-  for (hi = apr_hash_first(pool, unparsed_entries); hi; hi = apr_hash_next(hi))
+  for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi))
     {
       const void *key;
       void *val;
-      char *str_val;
+      svn_string_t *str_val;
       char *str, *last_str;
       svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
 
       apr_hash_this(hi, &key, NULL, &val);
-      str_val = apr_pstrdup(pool, *((char **)val));
+      str_val = val;
+      str = apr_pstrdup(pool, str_val->data);
       dirent->name = apr_pstrdup(pool, key);
 
-      str = apr_strtok(str_val, " ", &last_str);
+      str = apr_strtok(str, " ", &last_str);
       if (str == NULL)
         return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
                                 _("Directory entry corrupt"));
@@ -2991,26 +3100,61 @@
 
       dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
 
-      apr_hash_set(parsed_entries, dirent->name, APR_HASH_KEY_STRING, dirent);
+      apr_hash_set(*entries_p, dirent->name, APR_HASH_KEY_STRING, dirent);
     }
 
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__dir_entries_deserialize(void **out,
+                                   const char *data,
+                                   apr_size_t data_len,
+                                   apr_pool_t *pool)
+{
+  apr_hash_t *entries = apr_hash_make(pool);
+  svn_stringbuf_t *buf = svn_stringbuf_ncreate(data, data_len, pool);
+  svn_stream_t *stream = svn_stream_from_stringbuf(buf, pool);
+
+  SVN_ERR(svn_hash_read2(entries, stream, SVN_HASH_TERMINATOR, pool));
+  SVN_ERR(parse_dir_entries(&entries, entries, pool));
+
+  *out = entries;
+  return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
+                            svn_fs_t *fs,
+                            node_revision_t *noderev,
+                            apr_pool_t *pool)
+{
+  fs_fs_data_t *ffd = fs->fsap_data;
+  const char *unparsed_id;
+  apr_hash_t *unparsed_entries, *parsed_entries;
+
+  /* Are we looking for an immutable directory?  We could try the
+   * cache. */
+  if (! svn_fs_fs__id_txn_id(noderev->id))
+    {
+      svn_boolean_t found;
+
+      unparsed_id = svn_fs_fs__id_unparse(noderev->id, pool)->data;
+      SVN_ERR(svn_cache_get((void **) entries_p, &found, ffd->dir_cache,
+                            unparsed_id, pool));
+      if (found)
+        return SVN_NO_ERROR;
+    }
+
+  /* Read in the directory hash. */
+  unparsed_entries = apr_hash_make(pool);
+  SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, pool));
+  SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries, pool));
+
   /* If this is an immutable directory, let's cache the contents. */
   if (! svn_fs_fs__id_txn_id(noderev->id))
-    {
-      /* Start by NULLing the ID field, so that we never leave the
-         cache in an illegal state. */
-      ffd->dir_cache_id[hid] = NULL;
-
-      if (ffd->dir_cache_pool[hid])
-        svn_pool_clear(ffd->dir_cache_pool[hid]);
-      else
-        ffd->dir_cache_pool[hid] = svn_pool_create(fs->pool);
-
-      ffd->dir_cache[hid] = copy_dir_entries(parsed_entries,
-                                             ffd->dir_cache_pool[hid]);
-      ffd->dir_cache_id[hid] = svn_fs_fs__id_copy(noderev->id,
-                                                  ffd->dir_cache_pool[hid]);
-    }
+    SVN_ERR(svn_cache_set(ffd->dir_cache, unparsed_id, parsed_entries, pool));
 
   *entries_p = parsed_entries;
   return SVN_NO_ERROR;
@@ -4076,15 +4220,8 @@
 svn_fs_fs__abort_txn(svn_fs_txn_t *txn,
                      apr_pool_t *pool)
 {
-  fs_fs_data_t *ffd;
-
   SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
 
-  /* Clean out the directory cache. */
-  ffd = txn->fs->fsap_data;
-  memset(&ffd->dir_cache_id, 0,
-         sizeof(svn_fs_id_t *) * NUM_DIR_CACHE_ENTRIES);
-
   /* Now, purge the transaction. */
   SVN_ERR_W(svn_fs_fs__purge_txn(txn->fs, txn->id, pool),
             _("Transaction cleanup failed"));
@@ -4093,47 +4230,6 @@
 }
 
 
-static const char *
-unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
-                  apr_pool_t *pool)
-{
-  return apr_psprintf(pool, "%s %s",
-                      (kind == svn_node_file) ? KIND_FILE : KIND_DIR,
-                      svn_fs_fs__id_unparse(id, pool)->data);
-}
-
-/* Given a hash ENTRIES of dirent structions, return a hash in
-   *STR_ENTRIES_P, that has svn_string_t as the values in the format
-   specified by the fs_fs directory contents file.  Perform
-   allocations in POOL. */
-static svn_error_t *
-unparse_dir_entries(apr_hash_t **str_entries_p,
-                    apr_hash_t *entries,
-                    apr_pool_t *pool)
-{
-  apr_hash_index_t *hi;
-
-  *str_entries_p = apr_hash_make(pool);
-
-  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
-    {
-      const void *key;
-      apr_ssize_t klen;
-      void *val;
-      svn_fs_dirent_t *dirent;
-      const char *new_val;
-
-      apr_hash_this(hi, &key, &klen, &val);
-      dirent = val;
-      new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
-      apr_hash_set(*str_entries_p, key, klen,
-                   svn_string_create(new_val, pool));
-    }
-
-  return SVN_NO_ERROR;
-}
-
-
 svn_error_t *
 svn_fs_fs__set_entry(svn_fs_t *fs,
                      const char *txn_id,
@@ -4868,9 +4964,10 @@
   noderev->id = new_id;
 
   /* Write out our new node-revision. */
-  SVN_ERR(write_noderev_txn(file, noderev,
-                            svn_fs_fs__fs_supports_mergeinfo(fs),
-                            pool));
+  SVN_ERR(svn_fs_fs__write_noderev(svn_stream_from_aprfile(file, pool),
+                                   noderev,
+                                   svn_fs_fs__fs_supports_mergeinfo(fs),
+                                   pool));
 
   /* Return our ID that references the revision file. */
   *new_id_p = noderev->id;
@@ -5462,6 +5559,8 @@
                                  "", pool));
     }
 
+  SVN_ERR(write_config(fs, pool));
+
   /* This filesystem is ready.  Stamp it with a format number. */
   SVN_ERR(write_format(path_format(fs, pool),
                        ffd->format, ffd->max_files_per_dir, FALSE, pool));
@@ -5582,7 +5681,8 @@
   apr_pool_t *iterpool;
 
   SVN_ERR(svn_io_file_seek(rev_file, APR_SET, &offset, pool));
-  SVN_ERR(read_header_block(&headers, rev_file, pool));
+  SVN_ERR(read_header_block(&headers, svn_stream_from_aprfile(rev_file, pool),
+                            pool));
 
   /* We're going to populate a skeletal noderev - just the id and data_rep. */
   value = apr_hash_get(headers, HEADER_ID, APR_HASH_KEY_STRING);
diff --git a/subversion/libsvn_fs_fs/fs_fs.h b/subversion/libsvn_fs_fs/fs_fs.h
index 1f0f17c..1b37e86 100644
--- a/subversion/libsvn_fs_fs/fs_fs.h
+++ b/subversion/libsvn_fs_fs/fs_fs.h
@@ -60,6 +60,23 @@
                                           svn_boolean_t fresh_txn_root,
                                           apr_pool_t *pool);
 
+/* Write the node-revision NODEREV into the stream OUTFILE.  Only write
+   mergeinfo-related metadata if INCLUDE_MERGEINFO is true.  Temporary
+   allocations are from POOL. */
+svn_error_t *
+svn_fs_fs__write_noderev(svn_stream_t *outfile,
+                         node_revision_t *noderev,
+                         svn_boolean_t include_mergeinfo,
+                         apr_pool_t *pool);
+
+/* Reads the node-revision *NODEREV from the stream STREAM.  Temporary
+   allocations are from POOL. */
+svn_error_t *
+svn_fs_fs__read_noderev(node_revision_t **noderev,
+                        svn_stream_t *stream,
+                        apr_pool_t *pool);
+
+
 /* Set *YOUNGEST to the youngest revision in filesystem FS.  Do any
    temporary allocation in POOL. */
 svn_error_t *svn_fs_fs__youngest_rev(svn_revnum_t *youngest,
@@ -73,6 +90,12 @@
                                      svn_revnum_t rev,
                                      apr_pool_t *pool);
 
+/* Serialize a directory contents hash. */
+svn_cache_serialize_func_t svn_fs_fs__dir_entries_serialize;
+
+/* Deserialize a directory contents hash. */
+svn_cache_deserialize_func_t svn_fs_fs__dir_entries_deserialize;
+
 /* Set *ENTRIES to an apr_hash_t of dirent structs that contain the
    directory entries of node-revision NODEREV in filesystem FS.  The
    returned table (and its keys and values) is allocated in POOL,
@@ -180,6 +203,14 @@
 /* Return whether or not the given FS supports mergeinfo metadata. */
 svn_boolean_t svn_fs_fs__fs_supports_mergeinfo(svn_fs_t *fs);
 
+/* Sets *CONFIG to the parsed version of FS's fsfs.conf, allocated
+   in FS->pool.  POOL is used for temporary allocations. */
+svn_error_t *
+svn_fs_fs__get_config(svn_config_t **config,
+                      svn_fs_t *fs,
+                      apr_pool_t *pool);
+
+
 /* Store a transaction record in *TXN_P for the transaction identified
    by TXN_ID in filesystem FS.  Allocate everything from POOL. */
 svn_error_t *svn_fs_fs__get_txn(transaction_t **txn_p,
@@ -469,4 +500,10 @@
                            apr_pool_t *pool);
 
 
+/* Sets up the svn_cache_t structures in FS.  POOL is used for
+   temporary allocations. */
+svn_error_t *
+svn_fs_fs__initialize_caches(svn_fs_t *fs, apr_pool_t *pool);
+
+
 #endif
diff --git a/subversion/libsvn_fs_fs/structure b/subversion/libsvn_fs_fs/structure
index 3ff4b8f..daef181 100644
--- a/subversion/libsvn_fs_fs/structure
+++ b/subversion/libsvn_fs_fs/structure
@@ -54,6 +54,7 @@
   txn-current-lock    Empty file, locked to serialise 'txn-current'
   uuid                File containing the UUID of the repository
   format              File containing the format number of this filesystem
+  fsfs.conf           Configuration file
 
 Files in the revprops directory are in the hash dump format used by
 svn_hash_write.
@@ -86,6 +87,10 @@
 performs on this file is "get and increment"; the "txn-current-lock"
 file is locked during this operation.
 
+"fsfs.conf" is a configuration file in the standard Subversion/Python
+config format.  It is automatically generated when you create a new
+repository; read the generated file for details on what it controls.
+
 Filesystem formats
 ------------------
 
diff --git a/subversion/libsvn_fs_fs/tree.c b/subversion/libsvn_fs_fs/tree.c
index bb15649..f9524a3 100644
--- a/subversion/libsvn_fs_fs/tree.c
+++ b/subversion/libsvn_fs_fs/tree.c
@@ -76,10 +76,6 @@
    to the database (which also generates more log-files).  */
 #define WRITE_BUFFER_SIZE          512000
 
-/* The maximum number of cache items to maintain in the node cache. */
-#define TXN_NODE_CACHE_MAX_KEYS        32
-#define REV_NODE_CACHE_MAX_KEYS        128
-
 
 
 /* The root structures.
@@ -111,9 +107,9 @@
 
 typedef struct
 {
-  /* Dummy entry for circular LRU cache, and associated hash table. */
-  dag_node_cache_t txn_node_list;
-  apr_hash_t *txn_node_cache;
+  /* Cache of txn DAG nodes (without their nested noderevs, because
+   * it's mutable). */
+  svn_cache_t *txn_node_cache;
 } fs_txn_root_data_t;
 
 /* Declared here to resolve the circular dependencies. */
@@ -124,16 +120,18 @@
                                          dag_node_t *root_dir,
                                          apr_pool_t *pool);
 
-static svn_fs_root_t *make_txn_root(svn_fs_t *fs, const char *txn,
-                                    svn_revnum_t base_rev, apr_uint32_t flags,
-                                    apr_pool_t *pool);
+static svn_error_t *make_txn_root(svn_fs_root_t **root_p,
+                                  svn_fs_t *fs, const char *txn,
+                                  svn_revnum_t base_rev, apr_uint32_t flags,
+                                  apr_pool_t *pool);
 
 
 /*** Node Caching ***/
 
+/* Find and return the DAG node cache for ROOT and the key that
+   should be used for PATH. */
 static void
-locate_cache(dag_node_cache_t **node_list,
-             apr_hash_t **node_cache,
+locate_cache(svn_cache_t **cache,
              const char **key,
              svn_fs_root_t *root,
              const char *path,
@@ -142,148 +140,130 @@
   if (root->is_txn_root)
     {
       fs_txn_root_data_t *frd = root->fsap_data;
-      *node_list = &frd->txn_node_list;
-      *node_cache = frd->txn_node_cache;
-      *key = path;
+      if (cache) *cache = frd->txn_node_cache;
+      if (key && path) *key = path;
     }
   else
     {
       fs_fs_data_t *ffd = root->fs->fsap_data;
-      *node_list = &ffd->rev_node_list;
-      *node_cache = ffd->rev_node_cache;
-      *key = apr_psprintf(pool, "%ld%s",
-                          root->rev, path);
+      if (cache) *cache = ffd->rev_node_cache;
+      if (key && path) *key = apr_psprintf(pool, "%ld%s",
+                                           root->rev, path);
     }
 }
 
 /* Return NODE for PATH from ROOT's node cache, or NULL if the node
-   isn't cached. */
-static dag_node_t *
-dag_node_cache_get(svn_fs_root_t *root,
+   isn't cached; the node is copied into POOL. */
+static svn_error_t *
+dag_node_cache_get(dag_node_t **node_p,
+                   svn_fs_root_t *root,
                    const char *path,
                    apr_pool_t *pool)
 {
-  dag_node_cache_t *item, *node_list;
-  apr_hash_t *node_cache;
+  svn_boolean_t found;
+  dag_node_t *node;
+  svn_cache_t *cache;
   const char *key;
 
   /* Assert valid input. */
   assert(*path == '/');
 
-  locate_cache(&node_list, &node_cache, &key,
-               root, path, pool);
+  locate_cache(&cache, &key, root, path, pool);
 
-  /* Look in the cache for our desired item. */
-  item = apr_hash_get(node_cache, key, APR_HASH_KEY_STRING);
-  if (item && item->node)
+  SVN_ERR(svn_cache_get((void **) &node, &found, cache, key, pool));
+  if (found && node)
     {
-      /* Move this cache item to the front of the LRU list. */
-      item->prev->next = item->next;
-      item->next->prev = item->prev;
-      item->prev = node_list;
-      item->next = node_list->next;
-      item->prev->next = item;
-      item->next->prev = item;
-
-      /* Return the cached node. */
-      return svn_fs_fs__dag_dup(item->node, pool);
+      /* Patch up the FS, since this might have come from an old FS
+       * object. */
+      svn_fs_fs__dag_set_fs(node, root->fs);
+      *node_p = node;
     }
-
-  return NULL;
+  else
+    *node_p = NULL;
+  return SVN_NO_ERROR;
 }
 
 
 /* Add the NODE for PATH to ROOT's node cache. */
-static void
+static svn_error_t *
 dag_node_cache_set(svn_fs_root_t *root,
                    const char *path,
                    dag_node_t *node,
-                   apr_pool_t *temp_pool)
+                   apr_pool_t *pool)
 {
-  dag_node_cache_t *item, *node_list;
-  apr_hash_t *node_cache;
+  svn_cache_t *cache;
   const char *key;
-  apr_pool_t *pool;
-  int max_keys = root->is_txn_root
-      ? TXN_NODE_CACHE_MAX_KEYS : REV_NODE_CACHE_MAX_KEYS;
-
-  /* The pool passed to this function can *only* be used for
-     short-term calculations, not for the actual cache value!
-
-     To ensure that our cache values live as long as the svn_fs_root_t
-     in which they are ultimately stored, and to allow us to free()
-     them individually without harming the rest, they are each
-     allocated from a subpool of ROOT's pool.  We'll keep one subpool
-     around for each cache slot -- as we start expiring stuff
-     to make room for more entries, we'll re-use the expired thing's
-     pool. */
 
   /* Assert valid input and state. */
   assert(*path == '/');
 
-  locate_cache(&node_list, &node_cache, &key,
-               root, path, temp_pool);
+  locate_cache(&cache, &key, root, path, pool);
 
-  /* If we have an existing entry for this path, reuse it. */
-  item = apr_hash_get(node_cache, key, APR_HASH_KEY_STRING);
-
-  /* Otherwise, if the cache is full, reuse the tail of the LRU list. */
-  if (!item && apr_hash_count(node_cache) == max_keys)
-    item = node_list->prev;
-
-  if (item)
-    {
-      /* Remove the existing item from the cache and reuse its pool. */
-      item->prev->next = item->next;
-      item->next->prev = item->prev;
-      apr_hash_set(node_cache, item->key, APR_HASH_KEY_STRING, NULL);
-      pool = item->pool;
-      svn_pool_clear(pool);
-    }
-  else
-    {
-      /* Allocate a new pool. */
-      apr_pool_t *parent_pool = root->is_txn_root ? root->pool : root->fs->pool;
-      pool = svn_pool_create(parent_pool);
-    }
-
-  /* Create and fill in the cache item. */
-  item = apr_palloc(pool, sizeof(*item));
-  item->key = apr_pstrdup(pool, key);
-  item->node = svn_fs_fs__dag_dup(node, pool);
-  item->pool = pool;
-
-  /* Link it into the head of the LRU list and hash table. */
-  item->prev = node_list;
-  item->next = node_list->next;
-  item->prev->next = item;
-  item->next->prev = item;
-  apr_hash_set(node_cache, item->key, APR_HASH_KEY_STRING, item);
+  return svn_cache_set(cache, key, node, pool);
 }
 
 
-/* Invalidate cache entries for PATH and any of its children. */
-static void
-dag_node_cache_invalidate(svn_fs_root_t *root,
-                          const char *path)
+/* Baton for find_descendents_in_cache. */
+struct fdic_baton {
+  const char *path;
+  apr_array_header_t *list;
+  apr_pool_t *pool;
+};
+
+/* If the given item is a descendent of BATON->PATH, push
+ * it onto BATON->LIST (copying into BATON->POOL).  Implements
+ * the svn_iter_apr_hash_cb_t prototype. */
+static svn_error_t *
+find_descendents_in_cache(void *baton,
+                          const void *key,
+                          apr_ssize_t klen,
+                          void *val,
+                          apr_pool_t *pool)
 {
-  fs_txn_root_data_t *frd;
-  apr_size_t len = strlen(path);
-  const char *key;
-  dag_node_cache_t *item;
+  struct fdic_baton *b = baton;
+  const char *item_path = key;
+
+  if (svn_path_is_ancestor(b->path, item_path))
+    APR_ARRAY_PUSH(b->list, const char *) = apr_pstrdup(b->pool, item_path);
+
+  return SVN_NO_ERROR;
+}
+
+/* Invalidate cache entries for PATH and any of its children.  This
+   should *only* be called on a transaction root! */
+static svn_error_t *
+dag_node_cache_invalidate(svn_fs_root_t *root,
+                          const char *path,
+                          apr_pool_t *pool)
+{
+  struct fdic_baton b;
+  svn_cache_t *cache;
+  apr_pool_t *iterpool;
+  int i;
+
+  b.path = path;
+  b.pool = svn_pool_create(pool);
+  b.list = apr_array_make(b.pool, 1, sizeof(const char *));
 
   assert(root->is_txn_root);
+  locate_cache(&cache, NULL, root, NULL, b.pool);
 
-  frd = root->fsap_data;
 
-  for (item = frd->txn_node_list.next;
-       item != &frd->txn_node_list;
-       item = item->next)
+  SVN_ERR(svn_cache_iter(NULL, cache, find_descendents_in_cache,
+                         &b, b.pool));
+
+  iterpool = svn_pool_create(b.pool);
+
+  for (i = 0; i < b.list->nelts; i++)
     {
-      key = item->key;
-      if (strncmp(key, path, len) == 0 && (key[len] == '/' || !key[len]))
-        item->node = NULL;
+      const char *descendent = APR_ARRAY_IDX(b.list, i, const char *);
+      svn_pool_clear(iterpool);
+      SVN_ERR(svn_cache_set(cache, descendent, NULL, iterpool));
     }
+
+  svn_pool_destroy(iterpool);
+  svn_pool_destroy(b.pool);
+  return SVN_NO_ERROR;
 }
 
 
@@ -295,7 +275,6 @@
                     svn_fs_txn_t *txn,
                     apr_pool_t *pool)
 {
-  svn_fs_root_t *root;
   apr_uint32_t flags = 0;
   apr_hash_t *txnprops;
 
@@ -312,11 +291,7 @@
         flags |= SVN_FS_TXN_CHECK_LOCKS;
     }
 
-  root = make_txn_root(txn->fs, txn->id, txn->base_rev, flags, pool);
-
-  *root_p = root;
-
-  return SVN_NO_ERROR;
+  return make_txn_root(root_p, txn->fs, txn->id, txn->base_rev, flags, pool);
 }
 
 
@@ -661,7 +636,7 @@
           /* If we found a directory entry, follow it.  First, we
              check our node cache, and, failing that, we hit the DAG
              layer. */
-          cached_node = dag_node_cache_get(root, path_so_far, pool);
+          SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far, pool));
           if (cached_node)
             child = cached_node;
           else
@@ -706,7 +681,7 @@
 
           /* Cache the node we found (if it wasn't already cached). */
           if (! cached_node)
-            dag_node_cache_set(root, path_so_far, child, pool);
+            SVN_ERR(dag_node_cache_set(root, path_so_far, child, pool));
         }
 
       /* Are we finished traversing the path?  */
@@ -808,8 +783,8 @@
                                          pool));
 
       /* Update the path cache. */
-      dag_node_cache_set(root, parent_path_path(parent_path, pool), clone,
-                         pool);
+      SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, pool),
+                                 clone, pool));
     }
   else
     {
@@ -834,13 +809,13 @@
         apr_pool_t *pool)
 {
   parent_path_t *parent_path;
-  dag_node_t *node = NULL;
+  dag_node_t *node;
 
   /* Canonicalize the input PATH. */
   path = svn_fs__canonicalize_abspath(path, pool);
 
-  /* If ROOT is a revision root, we'll look for the DAG in our cache. */
-  node = dag_node_cache_get(root, path, pool);
+  /* First we look for the DAG in our cache. */
+  SVN_ERR(dag_node_cache_get(&node, root, path, pool));
   if (! node)
     {
       /* Call open_path with no flags, as we want this to return an error
@@ -1907,7 +1882,8 @@
                                   pool));
 
   /* Add this directory to the path cache. */
-  dag_node_cache_set(root, parent_path_path(parent_path, pool), sub_dir, pool);
+  SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, pool), 
+                             sub_dir, pool));
 
   /* Make a record of this modification in the changes table. */
   SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_fs__dag_get_id(sub_dir),
@@ -1956,7 +1932,8 @@
                                 txn_id, pool));
 
   /* Remove this node and any children from the path cache. */
-  dag_node_cache_invalidate(root, parent_path_path(parent_path, pool));
+  SVN_ERR(dag_node_cache_invalidate(root, parent_path_path(parent_path, pool),
+                                    pool));
 
   /* Update mergeinfo counts for parents */
   if (svn_fs_fs__fs_supports_mergeinfo(root->fs) && mergeinfo_count > 0)
@@ -2095,8 +2072,9 @@
                                   txn_id, pool));
 
       if (kind == svn_fs_path_change_replace)
-        dag_node_cache_invalidate(to_root, parent_path_path(to_parent_path,
-                                                            pool));
+        SVN_ERR(dag_node_cache_invalidate(to_root,
+                                          parent_path_path(to_parent_path,
+                                                           pool), pool));
 
       if (svn_fs_fs__fs_supports_mergeinfo(to_root->fs)
           && mergeinfo_start != mergeinfo_end)
@@ -2256,7 +2234,8 @@
                                    pool));
 
   /* Add this file to the path cache. */
-  dag_node_cache_set(root, parent_path_path(parent_path, pool), child, pool);
+  SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, pool), child,
+                             pool));
 
   /* Make a record of this modification in the changes table. */
   SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_fs__dag_get_id(child),
@@ -3786,8 +3765,9 @@
 /* Construct a root object referring to the root of the transaction
    named TXN and based on revision BASE_REV in FS, with FLAGS to
    describe transaction's behavior.  Create the new root in POOL.  */
-static svn_fs_root_t *
-make_txn_root(svn_fs_t *fs,
+static svn_error_t *
+make_txn_root(svn_fs_root_t **root_p,
+              svn_fs_t *fs,
               const char *txn,
               svn_revnum_t base_rev,
               apr_uint32_t flags,
@@ -3801,11 +3781,18 @@
   root->txn_flags = flags;
   root->rev = base_rev;
 
-  frd->txn_node_cache = apr_hash_make(root->pool);
-  frd->txn_node_list.prev = &frd->txn_node_list;
-  frd->txn_node_list.next = &frd->txn_node_list;
+  /* Because this cache actually tries to invalidate elements, keep
+     the number of elements per page down.
+
+     Note that since dag_node_cache_invalidate uses svn_cache_iter,
+     this *cannot* be a memcache-based cache.  */
+  SVN_ERR(svn_cache_create_inprocess(&(frd->txn_node_cache),
+                                     svn_fs_fs__dag_dup_for_cache,
+                                     APR_HASH_KEY_STRING,
+                                     32, 20, FALSE, root->pool));
 
   root->fsap_data = frd;
 
-  return root;
+  *root_p = root;
+  return SVN_NO_ERROR;
 }
diff --git a/subversion/libsvn_ra_local/ra_plugin.c b/subversion/libsvn_ra_local/ra_plugin.c
index 6d855f4..d2786f8 100644
--- a/subversion/libsvn_ra_local/ra_plugin.c
+++ b/subversion/libsvn_ra_local/ra_plugin.c
@@ -408,6 +408,28 @@
   return schemes;
 }
 
+/* Do nothing.
+ *
+ * Why is this acceptable?  As of now, FS warnings are used for only
+ * two things: failures to close BDB repositories and failures to
+ * interact with memcached in FSFS (new in 1.6).  In 1.5 and earlier,
+ * we did not call svn_fs_set_warning_func in ra_local, which means
+ * that any BDB-closing failure would have led to abort()s; the fact
+ * that this hasn't led to huge hues and cries makes it seem likely
+ * that this just doesn't happen that often, at least not through
+ * ra_local.  And as far as memcached goes, it seems unlikely that
+ * somebody is going to go through the trouble of setting up and
+ * running memcached servers but then use ra_local access.  So we
+ * ignore errors here, so that memcached can use the FS warnings API
+ * without crashing ra_local.
+ */
+static void
+ignore_warnings(void *baton,
+                svn_error_t *err)
+{
+  return;
+}
+
 static svn_error_t *
 svn_ra_local__open(svn_ra_session_t *session,
                    const char *repos_URL,
@@ -439,6 +461,9 @@
      convenience. */
   sess->fs = svn_repos_fs(sess->repos);
 
+  /* Ignore FS warnings. */
+  svn_fs_set_warning_func(sess->fs, ignore_warnings, NULL);
+
   /* Cache the repository UUID as well */
   SVN_ERR(svn_fs_get_uuid(sess->fs, &sess->uuid, session->pool));
 
diff --git a/subversion/libsvn_subr/cache-inprocess.c b/subversion/libsvn_subr/cache-inprocess.c
new file mode 100644
index 0000000..cef78cd
--- /dev/null
+++ b/subversion/libsvn_subr/cache-inprocess.c
@@ -0,0 +1,461 @@
+/*
+ * cache-inprocess.c: in-memory caching for Subversion
+ *
+ * ====================================================================
+ * 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 <assert.h>
+
+#include <apr_thread_mutex.h>
+
+#include "svn_pools.h"
+
+#include "svn_private_config.h"
+
+#include "cache.h"
+
+/* The (internal) cache object. */
+typedef struct {
+  /* Maps from a key (of size CACHE->KLEN) to a struct cache_entry. */
+  apr_hash_t *hash;
+  apr_ssize_t klen;
+
+  /* Used to copy values in and out of the cache. */
+  svn_cache_dup_func_t *dup_func;
+
+  /* The number of pages we're allowed to allocate before having to
+   * try to reuse one. */
+  apr_int64_t unallocated_pages;
+  /* Number of cache entries stored on each page.  Must be at least 1. */
+  apr_int64_t items_per_page;
+
+  /* A dummy cache_page serving as the head of a circular doubly
+   * linked list of cache_pages.  SENTINEL->NEXT is the most recently
+   * used page, and SENTINEL->PREV is the least recently used page.
+   * All pages in this list are "full"; the page currently being
+   * filled (PARTIAL_PAGE) is not in the list. */
+  struct cache_page *sentinel;
+
+  /* A page currently being filled with entries, or NULL if there's no
+   * partially-filled page.  This page is not in SENTINEL's list. */
+  struct cache_page *partial_page;
+  /* If PARTIAL_PAGE is not null, this is the number of entries
+   * currently on PARTIAL_PAGE. */
+  apr_int64_t partial_page_number_filled;
+
+  /* The pool that the svn_cache_t itself, HASH, and all pages are
+   * allocated in; subpools of this pool are used for the cache_entry
+   * structs, as well as the dup'd values and hash keys.
+   */
+  apr_pool_t *cache_pool;
+
+#if APR_HAS_THREADS
+  /* A lock for intra-process synchronization to the cache, or NULL if
+   * the cache's creator doesn't feel the cache needs to be
+   * thread-safe. */
+  apr_thread_mutex_t *mutex;
+#endif
+} inprocess_cache_t;
+
+/* A cache page; all items on the page are allocated from the same
+ * pool. */
+struct cache_page {
+  /* Pointers for the LRU list anchored at the cache's SENTINEL.
+   * (NULL for the PARTIAL_PAGE.) */
+  struct cache_page *prev;
+  struct cache_page *next;
+
+  /* The pool in which cache_entry structs, hash keys, and dup'd
+   * values are allocated. */
+  apr_pool_t *page_pool;
+
+  /* A singly linked list of the entries on this page; used to remove
+   * them from the cache's HASH before reusing the page. */
+  struct cache_entry *first_entry;
+};
+
+/* An cache entry. */
+struct cache_entry {
+  const void *key;
+  void *value;
+
+  /* The page it's on (needed so that the LRU list can be
+   * maintained). */
+  struct cache_page *page;
+
+  /* Next entry on the page. */
+  struct cache_entry *next_entry;
+};
+
+
+/* Removes PAGE from the doubly-linked list it is in (leaving its PREV
+ * and NEXT fields undefined). */
+static void
+remove_page_from_list(struct cache_page *page)
+{
+  page->prev->next = page->next;
+  page->next->prev = page->prev;
+}
+
+/* Inserts PAGE after CACHE's sentinel. */
+static void
+insert_page(inprocess_cache_t *cache,
+            struct cache_page *page)
+{
+  struct cache_page *pred = cache->sentinel;
+
+  page->prev = pred;
+  page->next = pred->next;
+  page->prev->next = page;
+  page->next->prev = page;
+}
+
+/* If PAGE is in the circularly linked list (eg, its NEXT isn't NULL),
+ * move it to the front of the list. */
+static void
+move_page_to_front(inprocess_cache_t *cache,
+                   struct cache_page *page)
+{
+  assert(page != cache->sentinel);
+
+  if (! page->next)
+    return;
+
+  remove_page_from_list(page);
+  insert_page(cache, page);
+}
+
+/* Uses CACHE->dup_func to copy VALUE into *VALUE_P inside POOL, or
+   just sets *VALUE_P to NULL if VALUE is NULL. */
+static svn_error_t *
+duplicate_value(void **value_p,
+                inprocess_cache_t *cache,
+                void *value,
+                apr_pool_t *pool)
+{
+  if (value)
+    SVN_ERR((cache->dup_func)(value_p, value, pool));
+  else
+    *value_p = NULL;
+  return SVN_NO_ERROR;
+}
+
+/* Return a copy of KEY inside POOL, using CACHE->KLEN to figure out
+ * how. */
+static const void *
+duplicate_key(inprocess_cache_t *cache,
+              const void *key,
+              apr_pool_t *pool)
+{
+  if (cache->klen == APR_HASH_KEY_STRING)
+    return apr_pstrdup(pool, key);
+  else
+    return apr_pmemdup(pool, key, cache->klen);
+}
+
+/* If applicable, locks CACHE's mutex. */
+static svn_error_t *
+lock_cache(inprocess_cache_t *cache)
+{
+#if APR_HAS_THREADS
+  apr_status_t status;
+  if (! cache->mutex)
+    return SVN_NO_ERROR;
+
+  status = apr_thread_mutex_lock(cache->mutex);
+  if (status)
+    return svn_error_wrap_apr(status, _("Can't lock cache mutex"));
+#endif
+
+  return SVN_NO_ERROR;
+}
+
+/* If applicable, unlocks CACHE's mutex, then returns ERR. */
+static svn_error_t *
+unlock_cache(inprocess_cache_t *cache,
+             svn_error_t *err)
+{
+#if APR_HAS_THREADS
+  apr_status_t status;
+  if (! cache->mutex)
+    return err;
+
+  status = apr_thread_mutex_unlock(cache->mutex);
+  if (status && !err)
+    return svn_error_wrap_apr(status, _("Can't unlock cache mutex"));
+#endif
+
+  return err;
+}
+
+svn_error_t *
+inprocess_cache_get(void **value_p,
+                    svn_boolean_t *found,
+                    void *cache_void,
+                    const void *key,
+                    apr_pool_t *pool)
+{
+  inprocess_cache_t *cache = cache_void;
+  struct cache_entry *entry;
+  svn_error_t *err;
+
+  SVN_ERR(lock_cache(cache));
+
+  entry = apr_hash_get(cache->hash, key, cache->klen);
+  if (! entry)
+    {
+      *found = FALSE;
+      return unlock_cache(cache, SVN_NO_ERROR);
+    }
+
+  move_page_to_front(cache, entry->page);
+
+  *found = TRUE;
+  err = duplicate_value(value_p, cache, entry->value, pool);
+  return unlock_cache(cache, err);
+}
+
+/* Removes PAGE from the LRU list, removes all of its entries from
+ * CACHE's hash, clears its pool, and sets its entry pointer to NULL.
+ * Finally, puts it in the "partial page" slot in the cache and sets
+ * partial_page_number_filled to 0.  Must be called on a page actually
+ * in the list. */
+static void
+erase_page(inprocess_cache_t *cache,
+           struct cache_page *page)
+{
+  struct cache_entry *e;
+
+  remove_page_from_list(page);
+
+  for (e = page->first_entry;
+       e;
+       e = e->next_entry)
+    {
+      apr_hash_set(cache->hash, e->key, cache->klen, NULL);
+    }
+
+  svn_pool_clear(page->page_pool);
+
+  page->first_entry = NULL;
+  page->prev = NULL;
+  page->next = NULL;
+
+  cache->partial_page = page;
+  cache->partial_page_number_filled = 0;
+}
+
+
+svn_error_t *
+inprocess_cache_set(void *cache_void,
+                     const void *key,
+                     void *value,
+                     apr_pool_t *pool)
+{
+  inprocess_cache_t *cache = cache_void;
+  struct cache_entry *existing_entry;
+  svn_error_t *err = SVN_NO_ERROR;
+
+  SVN_ERR(lock_cache(cache));
+
+  existing_entry = apr_hash_get(cache->hash, key, cache->klen);
+
+  /* Is it already here, but we can do the one-item-per-page
+   * optimization? */
+  if (existing_entry && cache->items_per_page == 1)
+    {
+      /* Special case!  ENTRY is the *only* entry on this page, so
+       * why not wipe it (so as not to leak the previous value).
+       */
+      struct cache_page *page = existing_entry->page;
+
+      /* This can't be the partial page: items_per_page == 1
+       * *never* has a partial page (except for in the temporary state
+       * that we're about to fake). */
+      assert(page->next != NULL);
+      assert(cache->partial_page == NULL);
+
+      erase_page(cache, page);
+      existing_entry = NULL;
+    }
+
+  /* Is it already here, and we just have to leak the old value? */
+  if (existing_entry)
+    {
+      struct cache_page *page = existing_entry->page;
+
+      move_page_to_front(cache, page);
+      err = duplicate_value(&(existing_entry->value), cache, 
+                            value, page->page_pool);
+      goto cleanup;
+    }
+
+  /* Do we not have a partial page to put it on, but we are allowed to
+   * allocate more? */
+  if (cache->partial_page == NULL && cache->unallocated_pages > 0)
+    {
+      cache->partial_page = apr_pcalloc(cache->cache_pool,
+                                        sizeof(*(cache->partial_page)));
+      cache->partial_page->page_pool = svn_pool_create(cache->cache_pool);
+      cache->partial_page_number_filled = 0;
+      (cache->unallocated_pages)--;
+    }
+
+  /* Do we really not have a partial page to put it on, even after the
+   * one-item-per-page optimization and checking the unallocated page
+   * count? */
+  if (cache->partial_page == NULL)
+    {
+      struct cache_page *oldest_page = cache->sentinel->prev;
+
+      assert(oldest_page != cache->sentinel);
+
+      /* Erase the page and put it in cache->partial_page. */
+      erase_page(cache, oldest_page);
+    }
+
+  assert(cache->partial_page != NULL);
+
+  {
+    struct cache_page *page = cache->partial_page;
+    struct cache_entry *new_entry = apr_pcalloc(page->page_pool,
+                                                sizeof(*new_entry));
+
+    /* Copy the key and value into the page's pool.  */
+    new_entry->key = duplicate_key(cache, key, page->page_pool);
+    err = duplicate_value(&(new_entry->value), cache, value,
+                          page->page_pool);
+    if (err)
+      goto cleanup;
+
+    /* Add the entry to the page's list. */
+    new_entry->page = page;
+    new_entry->next_entry = page->first_entry;
+    page->first_entry = new_entry;
+
+    /* Add the entry to the hash, using the *entry's* copy of the
+     * key. */
+    apr_hash_set(cache->hash, new_entry->key, cache->klen, new_entry);
+
+    /* We've added something else to the partial page. */
+    (cache->partial_page_number_filled)++;
+
+    /* Is it full? */
+    if (cache->partial_page_number_filled >= cache->items_per_page)
+      {
+        insert_page(cache, page);
+        cache->partial_page = NULL;
+      }
+  }
+
+ cleanup:
+  return unlock_cache(cache, err);
+}
+
+/* Baton type for svn_cache_iter. */
+struct cache_iter_baton {
+  svn_iter_apr_hash_cb_t user_cb;
+  void *user_baton;
+};
+
+/* Call the user's callback with the actual value, not the
+   cache_entry.  Implements the svn_iter_apr_hash_cb_t
+   prototype. */
+static svn_error_t *
+iter_cb(void *baton,
+        const void *key,
+        apr_ssize_t klen,
+        void *val,
+        apr_pool_t *pool)
+{
+  struct cache_iter_baton *b = baton;
+  struct cache_entry *entry = val;
+  return (b->user_cb)(b->user_baton, key, klen, entry->value, pool);
+}
+
+svn_error_t *
+inprocess_cache_iter(svn_boolean_t *completed,
+                     void *cache_void,
+                     svn_iter_apr_hash_cb_t user_cb,
+                     void *user_baton,
+                     apr_pool_t *pool)
+{
+  inprocess_cache_t *cache = cache_void;
+  struct cache_iter_baton b;
+  b.user_cb = user_cb;
+  b.user_baton = user_baton;
+
+  SVN_ERR(lock_cache(cache));
+  return unlock_cache(cache,
+                      svn_iter_apr_hash(completed, cache->hash, iter_cb, &b,
+                                        pool));
+
+}
+
+static svn_cache__vtable_t inprocess_cache_vtable = {
+  inprocess_cache_get,
+  inprocess_cache_set,
+  inprocess_cache_iter
+};
+
+svn_error_t *
+svn_cache_create_inprocess(svn_cache_t **cache_p,
+                           svn_cache_dup_func_t *dup_func,
+                           apr_ssize_t klen,
+                           apr_int64_t pages,
+                           apr_int64_t items_per_page,
+                           svn_boolean_t thread_safe,
+                           apr_pool_t *pool)
+{
+  svn_cache_t *wrapper = apr_pcalloc(pool, sizeof(*wrapper));
+  inprocess_cache_t *cache = apr_pcalloc(pool, sizeof(*cache));
+
+  cache->hash = apr_hash_make(pool);
+  cache->klen = klen;
+
+  cache->dup_func = dup_func;
+
+  assert(pages >= 1);
+  cache->unallocated_pages = pages;
+  assert(items_per_page >= 1);
+  cache->items_per_page = items_per_page;
+
+  cache->sentinel = apr_pcalloc(pool, sizeof(*(cache->sentinel)));
+  cache->sentinel->prev = cache->sentinel;
+  cache->sentinel->next = cache->sentinel;
+  /* The sentinel doesn't need a pool.  (We're happy to crash if we
+   * accidentally try to treat it like a real page.) */
+
+#if APR_HAS_THREADS
+  if (thread_safe)
+    {
+      apr_status_t status = apr_thread_mutex_create(&(cache->mutex),
+                                                    APR_THREAD_MUTEX_DEFAULT,
+                                                    pool);
+      if (status)
+        return svn_error_wrap_apr(status,
+                                  _("Can't create cache mutex"));
+    }
+#endif
+
+  cache->cache_pool = pool;
+
+  wrapper->vtable = &inprocess_cache_vtable;
+  wrapper->cache_internal = cache;
+  wrapper->error_handler = wrapper->error_baton = NULL;
+
+  *cache_p = wrapper;
+  return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_subr/cache-memcache.c b/subversion/libsvn_subr/cache-memcache.c
new file mode 100644
index 0000000..bf0af81
--- /dev/null
+++ b/subversion/libsvn_subr/cache-memcache.c
@@ -0,0 +1,417 @@
+/*
+ * cache-memcache.c: memcached caching for Subversion
+ *
+ * ====================================================================
+ * 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 <assert.h>
+
+#include "svn_cache.h"
+#include "svn_pools.h"
+#include "svn_base64.h"
+#include "svn_md5.h"
+#include "svn_path.h"
+
+#include "svn_private_config.h"
+
+#include "cache.h"
+
+#if SVN_HAVE_MEMCACHE
+
+#include <apr_memcache.h>
+
+/* A note on thread safety:
+
+   The apr_memcache_t object does its own mutex handling, and nothing
+   else in memcache_t is ever modified, so this implementation should
+   be fully thread-safe.
+*/
+
+/* The (internal) cache object. */
+typedef struct {
+  /* The memcached server set we're using. */
+  apr_memcache_t *memcache;
+
+  /* A prefix used to differentiate our data from any other data in
+   * the memcached (URI-encoded). */
+  const char *prefix;
+
+  /* The size of the key: either a fixed number of bytes or
+   * APR_HASH_KEY_STRING. */
+  apr_ssize_t klen;
+
+
+  /* Used to marshal values in and out of the cache. */
+  svn_cache_serialize_func_t *serialize_func;
+  svn_cache_deserialize_func_t *deserialize_func;
+} memcache_t;
+
+/* The wrapper around apr_memcache_t. */
+struct svn_memcache_t {
+  apr_memcache_t *c;
+};
+
+
+/* The memcached protocol says the maximum key length is 250.  Let's
+   just say 249, to be safe. */
+#define MAX_MEMCACHED_KEY_LEN 249
+#define MEMCACHED_KEY_UNHASHED_LEN (MAX_MEMCACHED_KEY_LEN - \
+                                    2 * APR_MD5_DIGESTSIZE)
+
+
+/* Returns a memcache key for the given key KEY for CACHE, allocated
+   in POOL. */
+const char *
+build_key(memcache_t *cache,
+          const void *raw_key,
+          apr_pool_t *pool)
+{
+  const char *encoded_suffix;
+  const char *long_key;
+  apr_size_t long_key_len;
+
+  if (cache->klen == APR_HASH_KEY_STRING)
+    encoded_suffix = svn_path_uri_encode(raw_key, pool);
+  else
+    {
+      const svn_string_t *raw = svn_string_ncreate(raw_key, cache->klen, pool);
+      const svn_string_t *encoded = svn_base64_encode_string2(raw, FALSE,
+                                                              pool);
+      encoded_suffix = encoded->data;
+    }
+
+  long_key = apr_pstrcat(pool, "SVN:", cache->prefix, ":", encoded_suffix,
+                         NULL);
+  long_key_len = strlen(long_key);
+
+  /* We don't want to have a key that's too big.  If it was going to
+     be too big, we MD5 the entire string, then replace the last bit
+     with the checksum.  Note that APR_MD5_DIGESTSIZE is for the pure
+     binary digest; we have to double that when we convert to hex.
+
+     Every key we use will either be at most
+     MEMCACHED_KEY_UNHASHED_LEN bytes long, or be exactly
+     MAX_MEMCACHED_KEY_LEN bytes long. */
+  if (long_key_len > MEMCACHED_KEY_UNHASHED_LEN)
+    {
+      unsigned char digest[APR_MD5_DIGESTSIZE];
+      apr_md5(digest, long_key, long_key_len);
+
+      long_key = apr_pstrcat(pool,
+                             apr_pstrmemdup(pool, long_key,
+                                            MEMCACHED_KEY_UNHASHED_LEN),
+                             svn_md5_digest_to_cstring_display(digest, pool),
+                             NULL);
+    }
+
+  return long_key;
+}
+
+
+svn_error_t *
+memcache_get(void **value_p,
+             svn_boolean_t *found,
+             void *cache_void,
+             const void *key,
+             apr_pool_t *pool)
+{
+  memcache_t *cache = cache_void;
+  apr_status_t apr_err;
+  char *data;
+  const char *mc_key;
+  apr_size_t data_len;
+  apr_pool_t *subpool = svn_pool_create(pool);
+
+  mc_key = build_key(cache, key, subpool);
+
+  apr_err = apr_memcache_getp(cache->memcache,
+                              (cache->deserialize_func ? subpool : pool),
+                              mc_key,
+                              &data,
+                              &data_len,
+                              NULL /* ignore flags */);
+  if (apr_err == APR_NOTFOUND)
+    {
+      *found = FALSE;
+      svn_pool_destroy(subpool);
+      return SVN_NO_ERROR;
+    }
+  else if (apr_err != APR_SUCCESS || !data)
+    return svn_error_wrap_apr(apr_err,
+                              _("Unknown memcached error while reading"));
+
+  /* We found it! */
+  if (cache->deserialize_func)
+    {
+      SVN_ERR((cache->deserialize_func)(value_p, data, data_len, pool));
+    }
+  else
+    {
+      svn_string_t *value = apr_pcalloc(pool, sizeof(*value));
+      value->data = data;
+      value->len = data_len;
+      *value_p = value;
+    }
+  *found = TRUE;
+
+  svn_pool_destroy(subpool);
+  return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+memcache_set(void *cache_void,
+             const void *key,
+             void *value,
+             apr_pool_t *pool)
+{
+  memcache_t *cache = cache_void;
+  apr_pool_t *subpool = svn_pool_create(pool);
+  char *data;
+  const char *mc_key = build_key(cache, key, subpool);
+  apr_size_t data_len;
+  apr_status_t apr_err;
+
+  if (cache->serialize_func)
+    {
+      SVN_ERR((cache->serialize_func)(&data, &data_len, value, subpool));
+    }
+  else
+    {
+      svn_stringbuf_t *value_str = value;
+      data = value_str->data;
+      data_len = value_str->len;
+    }
+
+  apr_err = apr_memcache_set(cache->memcache, mc_key, data, data_len, 0, 0);
+
+  /* ### Maybe write failures should be ignored (but logged)? */
+  if (apr_err != APR_SUCCESS)
+    return svn_error_wrap_apr(apr_err,
+                              _("Unknown memcached error while writing"));
+
+  svn_pool_destroy(subpool);
+  return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+memcache_iter(svn_boolean_t *completed,
+              void *cache_void,
+              svn_iter_apr_hash_cb_t user_cb,
+              void *user_baton,
+              apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+                          _("Can't iterate a memcached cache."));
+}
+
+static svn_cache__vtable_t memcache_vtable = {
+  memcache_get,
+  memcache_set,
+  memcache_iter
+};
+
+svn_error_t *
+svn_cache_create_memcache(svn_cache_t **cache_p,
+                          svn_memcache_t *memcache,
+                          svn_cache_serialize_func_t *serialize_func,
+                          svn_cache_deserialize_func_t *deserialize_func,
+                          apr_ssize_t klen,
+                          const char *prefix,
+                          apr_pool_t *pool)
+{
+  svn_cache_t *wrapper = apr_pcalloc(pool, sizeof(*wrapper));
+  memcache_t *cache = apr_pcalloc(pool, sizeof(*cache));
+
+  cache->serialize_func = serialize_func;
+  cache->deserialize_func = deserialize_func;
+  cache->klen = klen;
+  cache->prefix = svn_path_uri_encode(prefix, pool);
+  cache->memcache = memcache->c;
+
+  wrapper->vtable = &memcache_vtable;
+  wrapper->cache_internal = cache;
+  wrapper->error_handler = wrapper->error_baton = NULL;
+
+  *cache_p = wrapper;
+  return SVN_NO_ERROR;
+}
+
+
+/*** Creating apr_memcache_t from svn_config_t. ***/
+
+/* Baton for add_memcache_server. */
+struct ams_baton {
+  apr_memcache_t *memcache;
+  apr_pool_t *memcache_pool;
+  svn_error_t *err;
+};
+
+/* Implements svn_config_enumerator2_t. */
+static svn_boolean_t
+add_memcache_server(const char *name,
+                    const char *value,
+                    void *baton,
+                    apr_pool_t *pool)
+{
+  struct ams_baton *b = baton;
+  char *host, *scope;
+  apr_port_t port;
+  apr_status_t apr_err;
+  apr_memcache_server_t *server;
+
+  apr_err = apr_parse_addr_port(&host, &scope, &port,
+                                value, pool);
+  if (apr_err != APR_SUCCESS)
+    {
+      b->err = svn_error_wrap_apr(apr_err,
+                                  _("Error parsing memcache server '%s'"),
+                                  name);
+      return FALSE;
+    }
+
+  if (scope)
+    {
+      b->err = svn_error_createf(SVN_ERR_BAD_SERVER_SPECIFICATION, NULL,
+                                  _("Scope not allowed in memcache server "
+                                    "'%s'"),
+                                  name);
+      return FALSE;
+    }
+  if (!host || !port)
+    {
+      b->err = svn_error_createf(SVN_ERR_BAD_SERVER_SPECIFICATION, NULL,
+                                  _("Must specify host and port for memcache "
+                                    "server '%s'"),
+                                  name);
+      return FALSE;
+    }
+
+  /* Note: the four numbers here are only relevant when an
+     apr_memcache_t is being shared by multiple threads. */
+  apr_err = apr_memcache_server_create(b->memcache_pool,
+                                       host,
+                                       port,
+                                       0,  /* min connections */
+                                       5,  /* soft max connections */
+                                       10, /* hard max connections */
+                                       50, /* connection time to live (secs) */
+                                       &server);
+  if (apr_err != APR_SUCCESS)
+    {
+      b->err = svn_error_wrap_apr(apr_err,
+                                  _("Unknown error creating memcache server"));
+      return FALSE;
+    }
+
+  apr_err = apr_memcache_add_server(b->memcache, server);
+  if (apr_err != APR_SUCCESS)
+    {
+      b->err = svn_error_wrap_apr(apr_err,
+                                  _("Unknown error adding server to memcache"));
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+#else /* ! SVN_HAVE_MEMCACHE */
+
+/* Stubs for no apr memcache library. */
+
+struct svn_memcache_t {
+  void *unused; /* Let's not have a size-zero struct. */
+};
+
+svn_error_t *
+svn_cache_create_memcache(svn_cache_t **cache_p,
+                          svn_memcache_t *memcache,
+                          svn_cache_serialize_func_t *serialize_func,
+                          svn_cache_deserialize_func_t *deserialize_func,
+                          apr_ssize_t klen,
+                          const char *prefix,
+                          apr_pool_t *pool)
+{
+  return svn_error_create(SVN_ERR_NO_APR_MEMCACHE, NULL, NULL);
+}
+
+#endif /* SVN_HAVE_MEMCACHE */
+
+/* Implements svn_config_enumerator2_t.  Just used for the
+   entry-counting return value of svn_config_enumerate2. */
+static svn_boolean_t
+nop_enumerator(const char *name,
+               const char *value,
+               void *baton,
+               apr_pool_t *pool)
+{
+  return TRUE;
+}
+
+svn_error_t *
+svn_cache_make_memcache_from_config(svn_memcache_t **memcache_p,
+                                    svn_config_t *config,
+                                    apr_pool_t *pool)
+{
+  apr_uint16_t server_count;
+  apr_pool_t *subpool = svn_pool_create(pool);
+
+  server_count =
+    svn_config_enumerate2(config,
+                          SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS,
+                          nop_enumerator, NULL, subpool);
+
+  if (server_count == 0)
+    {
+      *memcache_p = NULL;
+      svn_pool_destroy(subpool);
+      return SVN_NO_ERROR;
+    }
+
+#if SVN_HAVE_MEMCACHE
+  {
+    struct ams_baton b;
+    svn_memcache_t *memcache = apr_pcalloc(pool, sizeof(*memcache));
+    apr_status_t apr_err = apr_memcache_create(pool,
+                                               server_count,
+                                               0, /* flags */
+                                               &(memcache->c));
+    if (apr_err != APR_SUCCESS)
+      return svn_error_wrap_apr(apr_err,
+                                _("Unknown error creating apr_memcache_t"));
+
+    b.memcache = memcache->c;
+    b.memcache_pool = pool;
+    b.err = SVN_NO_ERROR;
+    svn_config_enumerate2(config,
+                          SVN_CACHE_CONFIG_CATEGORY_MEMCACHED_SERVERS,
+                          add_memcache_server, &b,
+                          subpool);
+
+    if (b.err)
+      return b.err;
+
+    *memcache_p = memcache;
+
+    svn_pool_destroy(subpool);
+    return SVN_NO_ERROR;
+  }
+#else /* ! SVN_HAVE_MEMCACHE */
+  {
+    return svn_error_create(SVN_ERR_NO_APR_MEMCACHE, NULL, NULL);
+  }
+#endif /* SVN_HAVE_MEMCACHE */
+}
diff --git a/subversion/libsvn_subr/cache.c b/subversion/libsvn_subr/cache.c
new file mode 100644
index 0000000..3aed9e1
--- /dev/null
+++ b/subversion/libsvn_subr/cache.c
@@ -0,0 +1,93 @@
+/*
+ * cache.c: cache interface for Subversion
+ *
+ * ====================================================================
+ * 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 "cache.h"
+
+svn_error_t *
+svn_cache_set_error_handler(svn_cache_t *cache,
+                            svn_cache_error_handler_t *handler,
+                            void *baton,
+                            apr_pool_t *pool)
+{
+  cache->error_handler = handler;
+  cache->error_baton = baton;
+  return SVN_NO_ERROR;
+}
+
+
+/* Give the error handler callback a chance to replace or ignore the
+   error. */
+static svn_error_t *
+handle_error(svn_cache_t *cache,
+             svn_error_t *err,
+             apr_pool_t *pool)
+{
+  if (err && cache->error_handler)
+    err = (cache->error_handler)(err, cache->error_baton, pool);
+  return err;
+}
+
+
+svn_error_t *
+svn_cache_get(void **value_p,
+              svn_boolean_t *found,
+              svn_cache_t *cache,
+              const void *key,
+              apr_pool_t *pool)
+{
+  /* In case any errors happen and are quelched, make sure we start
+     out with FOUND set to false. */
+  *found = FALSE;
+  return handle_error(cache,
+                      (cache->vtable->get)(value_p,
+                                           found,
+                                           cache->cache_internal,
+                                           key,
+                                           pool),
+                      pool);
+}
+
+svn_error_t *
+svn_cache_set(svn_cache_t *cache,
+              const void *key,
+              void *value,
+              apr_pool_t *pool)
+{
+  return handle_error(cache,
+                      (cache->vtable->set)(cache->cache_internal,
+                                           key,
+                                           value,
+                                           pool),
+                      pool);
+}
+
+
+svn_error_t *
+svn_cache_iter(svn_boolean_t *completed,
+               svn_cache_t *cache,
+               svn_iter_apr_hash_cb_t user_cb,
+               void *user_baton,
+               apr_pool_t *pool)
+{
+  return (cache->vtable->iter)(completed,
+                               cache->cache_internal,
+                               user_cb,
+                               user_baton,
+                               pool);
+}
+
diff --git a/subversion/libsvn_subr/cache.h b/subversion/libsvn_subr/cache.h
new file mode 100644
index 0000000..b39b236
--- /dev/null
+++ b/subversion/libsvn_subr/cache.h
@@ -0,0 +1,59 @@
+/*
+ * cache.h: cache vtable interface
+ *
+ * ====================================================================
+ * 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_LIBSVN_SUBR_CACHE_H
+#define SVN_LIBSVN_SUBR_CACHE_H
+
+#include "svn_cache.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+typedef struct {
+  svn_error_t *(*get)(void **value,
+                      svn_boolean_t *found,
+                      void *cache_implementation,
+                      const void *key,
+                      apr_pool_t *pool);
+
+  svn_error_t *(*set)(void *cache_implementation,
+                      const void *key,
+                      void *value,
+                      apr_pool_t *pool);
+
+  svn_error_t *(*iter)(svn_boolean_t *completed,
+                       void *cache_implementation,
+                       svn_iter_apr_hash_cb_t func,
+                       void *baton,
+                       apr_pool_t *pool);
+} svn_cache__vtable_t;
+
+struct svn_cache_t {
+  const svn_cache__vtable_t *vtable;
+  svn_cache_error_handler_t *error_handler;
+  void *error_baton;
+  void *cache_internal;
+};
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_SUBR_CACHE_H */
diff --git a/subversion/libsvn_subr/stream.c b/subversion/libsvn_subr/stream.c
index 532c41e..5680b7a 100644
--- a/subversion/libsvn_subr/stream.c
+++ b/subversion/libsvn_subr/stream.c
@@ -820,13 +820,59 @@
 
 
 /* Miscellaneous stream functions. */
-struct string_stream_baton
+struct stringbuf_stream_baton
 {
   svn_stringbuf_t *str;
   apr_size_t amt_read;
 };
 
 static svn_error_t *
+read_handler_stringbuf(void *baton, char *buffer, apr_size_t *len)
+{
+  struct stringbuf_stream_baton *btn = baton;
+  apr_size_t left_to_read = btn->str->len - btn->amt_read;
+
+  *len = (*len > left_to_read) ? left_to_read : *len;
+  memcpy(buffer, btn->str->data + btn->amt_read, *len);
+  btn->amt_read += *len;
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+write_handler_stringbuf(void *baton, const char *data, apr_size_t *len)
+{
+  struct stringbuf_stream_baton *btn = baton;
+
+  svn_stringbuf_appendbytes(btn->str, data, *len);
+  return SVN_NO_ERROR;
+}
+
+svn_stream_t *
+svn_stream_from_stringbuf(svn_stringbuf_t *str,
+                          apr_pool_t *pool)
+{
+  svn_stream_t *stream;
+  struct stringbuf_stream_baton *baton;
+
+  if (! str)
+    return svn_stream_empty(pool);
+
+  baton = apr_palloc(pool, sizeof(*baton));
+  baton->str = str;
+  baton->amt_read = 0;
+  stream = svn_stream_create(baton, pool);
+  svn_stream_set_read(stream, read_handler_stringbuf);
+  svn_stream_set_write(stream, write_handler_stringbuf);
+  return stream;
+}
+
+struct string_stream_baton
+{
+  svn_string_t *str;
+  apr_size_t amt_read;
+};
+
+static svn_error_t *
 read_handler_string(void *baton, char *buffer, apr_size_t *len)
 {
   struct string_stream_baton *btn = baton;
@@ -838,18 +884,9 @@
   return SVN_NO_ERROR;
 }
 
-static svn_error_t *
-write_handler_string(void *baton, const char *data, apr_size_t *len)
-{
-  struct string_stream_baton *btn = baton;
-
-  svn_stringbuf_appendbytes(btn->str, data, *len);
-  return SVN_NO_ERROR;
-}
-
 svn_stream_t *
-svn_stream_from_stringbuf(svn_stringbuf_t *str,
-                          apr_pool_t *pool)
+svn_stream_from_string(svn_string_t *str,
+                       apr_pool_t *pool)
 {
   svn_stream_t *stream;
   struct string_stream_baton *baton;
@@ -862,7 +899,6 @@
   baton->amt_read = 0;
   stream = svn_stream_create(baton, pool);
   svn_stream_set_read(stream, read_handler_string);
-  svn_stream_set_write(stream, write_handler_string);
   return stream;
 }
 
diff --git a/subversion/libsvn_subr/svn_base64.c b/subversion/libsvn_subr/svn_base64.c
index 3886221..45d49b1 100644
--- a/subversion/libsvn_subr/svn_base64.c
+++ b/subversion/libsvn_subr/svn_base64.c
@@ -66,10 +66,11 @@
    data from call to call, and *LINELEN carries the length of the
    current output line.  Make INBUF have room for three characters and
    initialize *INBUFLEN and *LINELEN to 0.  Output will be appended to
-   STR.  */
+   STR.  Include newlines every so often if BREAK_LINES is true. */
 static void
 encode_bytes(svn_stringbuf_t *str, const char *data, apr_size_t len,
-             unsigned char *inbuf, int *inbuflen, int *linelen)
+             unsigned char *inbuf, int *inbuflen, int *linelen,
+             svn_boolean_t break_lines)
 {
   char group[4];
   const char *p = data, *end = data + len;
@@ -83,7 +84,7 @@
       svn_stringbuf_appendbytes(str, group, 4);
       *inbuflen = 0;
       *linelen += 4;
-      if (*linelen == BASE64_LINELEN)
+      if (break_lines && *linelen == BASE64_LINELEN)
         {
           svn_stringbuf_appendcstr(str, "\n");
           *linelen = 0;
@@ -96,11 +97,12 @@
 }
 
 
-/* Encode leftover data, if any, and possibly a final newline,
-   appending to STR.  LEN must be in the range 0..2.  */
+/* Encode leftover data, if any, and possibly a final newline (if
+   there has been any data and BREAK_LINES is set), appending to STR.
+   LEN must be in the range 0..2.  */
 static void
 encode_partial_group(svn_stringbuf_t *str, const unsigned char *extra,
-                     int len, int linelen)
+                     int len, int linelen, svn_boolean_t break_lines)
 {
   unsigned char ingroup[3];
   char outgroup[4];
@@ -114,7 +116,7 @@
       svn_stringbuf_appendbytes(str, outgroup, 4);
       linelen += 4;
     }
-  if (linelen > 0)
+  if (break_lines && linelen > 0)
     svn_stringbuf_appendcstr(str, "\n");
 }
 
@@ -130,7 +132,7 @@
   svn_error_t *err = SVN_NO_ERROR;
 
   /* Encode this block of data and write it out.  */
-  encode_bytes(encoded, data, *len, eb->buf, &eb->buflen, &eb->linelen);
+  encode_bytes(encoded, data, *len, eb->buf, &eb->buflen, &eb->linelen, TRUE);
   enclen = encoded->len;
   if (enclen != 0)
     err = svn_stream_write(eb->output, encoded->data, &enclen);
@@ -149,7 +151,7 @@
   svn_error_t *err = SVN_NO_ERROR;
 
   /* Encode a partial group at the end if necessary, and write it out.  */
-  encode_partial_group(encoded, eb->buf, eb->buflen, eb->linelen);
+  encode_partial_group(encoded, eb->buf, eb->buflen, eb->linelen, TRUE);
   enclen = encoded->len;
   if (enclen != 0)
     err = svn_stream_write(eb->output, encoded->data, &enclen);
@@ -181,20 +183,30 @@
 
 
 const svn_string_t *
-svn_base64_encode_string(const svn_string_t *str, apr_pool_t *pool)
+svn_base64_encode_string2(const svn_string_t *str,
+                          svn_boolean_t break_lines,
+                          apr_pool_t *pool)
 {
   svn_stringbuf_t *encoded = svn_stringbuf_create("", pool);
   svn_string_t *retval = apr_pcalloc(pool, sizeof(*retval));
   unsigned char ingroup[3];
   int ingrouplen = 0, linelen = 0;
 
-  encode_bytes(encoded, str->data, str->len, ingroup, &ingrouplen, &linelen);
-  encode_partial_group(encoded, ingroup, ingrouplen, linelen);
+  encode_bytes(encoded, str->data, str->len, ingroup, &ingrouplen, &linelen,
+               break_lines);
+  encode_partial_group(encoded, ingroup, ingrouplen, linelen,
+                       break_lines);
   retval->data = encoded->data;
   retval->len = encoded->len;
   return retval;
 }
 
+const svn_string_t *
+svn_base64_encode_string(const svn_string_t *str, apr_pool_t *pool)
+{
+  return svn_base64_encode_string2(str, TRUE, pool);
+}
+
 
 
 /* Base64-encoded input --> binary output */
@@ -373,8 +385,8 @@
    * does an implicit unsigned char * cast.
    */
   encode_bytes(md5str, (char*)digest, APR_MD5_DIGESTSIZE, ingroup,
-               &ingrouplen, &linelen);
-  encode_partial_group(md5str, ingroup, ingrouplen, linelen);
+               &ingrouplen, &linelen, TRUE);
+  encode_partial_group(md5str, ingroup, ingrouplen, linelen, TRUE);
 
   /* Our base64-encoding routines append a final newline if any data
      was created at all, so let's hack that off. */
diff --git a/subversion/svnserve/serve.c b/subversion/svnserve/serve.c
index d812326..69dc6ad 100644
--- a/subversion/svnserve/serve.c
+++ b/subversion/svnserve/serve.c
@@ -2889,6 +2889,7 @@
     return error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL,
                                  "No access allowed to this repository",
                                  b, conn, pool);
+
   return SVN_NO_ERROR;
 }
 
diff --git a/subversion/tests/cmdline/svndumpfilter_tests.py b/subversion/tests/cmdline/svndumpfilter_tests.py
index 3893355..0d394fa 100755
--- a/subversion/tests/cmdline/svndumpfilter_tests.py
+++ b/subversion/tests/cmdline/svndumpfilter_tests.py
@@ -72,7 +72,8 @@
                                           "--skip-missing-merge-sources",
                                           "--drop-empty-revs",
                                           "--renumber-revs", "--quiet")
-  load_and_verify_dumpstream(sbox, [], [], None, filtered_out)
+  load_and_verify_dumpstream(sbox, [], [], None, filtered_out,
+                             "--ignore-uuid")
 
   # Verify the svn:mergeinfo properties
   svntest.actions.run_and_verify_svn(None,
@@ -91,7 +92,8 @@
                                           "--skip-missing-merge-sources",
                                           "--drop-empty-revs",
                                           "--renumber-revs", "--quiet")
-  load_and_verify_dumpstream(sbox, [], [], None, filtered_out)
+  load_and_verify_dumpstream(sbox, [], [], None, filtered_out,
+                             "--ignore-uuid")
 
   # Verify the svn:mergeinfo properties
   svntest.actions.run_and_verify_svn(None,
diff --git a/subversion/tests/cmdline/svntest/main.py b/subversion/tests/cmdline/svntest/main.py
index da28863..17e845f 100644
--- a/subversion/tests/cmdline/svntest/main.py
+++ b/subversion/tests/cmdline/svntest/main.py
@@ -153,6 +153,9 @@
 # ('neon', 'serf')
 http_library = None
 
+# Configuration file (copied into FSFS fsfs.conf).
+config_file = None
+
 # Global variable indicating what the minor version of the server
 # tested against is (4 for 1.4.x, for example).
 server_minor_version = 5
@@ -304,6 +307,11 @@
 
   return os.path.join(repo_dir, "conf", "svnserve.conf")
 
+def get_fsfs_conf_file_path(repo_dir):
+  "Return the path of the fsfs.conf file in REPO_DIR."
+
+  return os.path.join(repo_dir, "db", "fsfs.conf")
+
 # Run any binary, logging the command line and return code
 def run_command(command, error_expected, binary_mode=0, *varargs):
   """Run COMMAND with VARARGS; return exit code as int; stdout, stderr
@@ -625,6 +633,10 @@
     file_append(get_svnserve_conf_file_path(path), "password-db = passwd\n")
     file_append(os.path.join(path, "conf", "passwd"),
                 "[users]\njrandom = rayjandom\njconstant = rayjandom\n");
+
+  if config_file is not None and (fs_type is None or fs_type == 'fsfs'):
+    shutil.copy(config_file, get_fsfs_conf_file_path(path))
+
   # make the repos world-writeable, for mod_dav_svn's sake.
   chmod_tree(path, 0666, 0666)
 
@@ -1241,6 +1253,7 @@
         "                 useful during test development!"
   print " --server-minor-version  Set the minor version for the server.\n" \
         "                 Supports version 4 or 5."
+  print " --config-file   Configuration file for tests."
   print " --help          This information"
 
 
@@ -1270,6 +1283,7 @@
   global svnversion_binary
   global command_line_parsed
   global http_library
+  global config_file
   global server_minor_version
 
   testnums = []
@@ -1279,13 +1293,14 @@
   parallel = 0
   svn_bin = None
   use_jsvn = False
+  config_file = None
 
   try:
     opts, args = my_getopt(sys.argv[1:], 'vqhpc',
                            ['url=', 'fs-type=', 'verbose', 'quiet', 'cleanup',
                             'list', 'enable-sasl', 'help', 'parallel',
-                            'bin=', 'http-library=', 'server-minor-version=', 
-                            'use-jsvn', 'development'])
+                            'bin=', 'http-library=', 'server-minor-version=',
+                            'use-jsvn', 'development', 'config-file='])
   except getopt.GetoptError, e:
     print "ERROR: %s\n" % e
     usage()
@@ -1355,6 +1370,9 @@
     elif opt == '--development':
       setup_development_mode()
 
+    elif opt == '--config-file':
+      config_file = val
+
   if test_area_url[-1:] == '/': # Normalize url to have no trailing slash
     test_area_url = test_area_url[:-1]
 
diff --git a/subversion/tests/libsvn_fs/fs-test.c b/subversion/tests/libsvn_fs/fs-test.c
index 23aa5a2..8859830 100644
--- a/subversion/tests/libsvn_fs/fs-test.c
+++ b/subversion/tests/libsvn_fs/fs-test.c
@@ -133,7 +133,7 @@
     return SVN_NO_ERROR;
 
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-trivial-txn",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Begin a new transaction that is based on revision 0.  */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
@@ -189,7 +189,7 @@
     return SVN_NO_ERROR;
 
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-reopen-trivial-txn",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Begin a new transaction that is based on revision 0.  */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
@@ -228,7 +228,7 @@
     return SVN_NO_ERROR;
 
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-create-file-txn",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Begin a new transaction that is based on revision 0.  */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
@@ -262,7 +262,7 @@
     return SVN_NO_ERROR;
 
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-verify-txn-list",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Begin a new transaction, get its name (in the top pool), close it.  */
   subpool = svn_pool_create(pool);
@@ -390,7 +390,7 @@
     return SVN_NO_ERROR;
 
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-txn-names-are-not-reused",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   subpool = svn_pool_create(pool);
 
@@ -431,7 +431,7 @@
 
   wstring = svn_stringbuf_create("Wicki wild, wicki wicki wild.", pool);
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-read-and-write-file",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -473,7 +473,7 @@
     return SVN_NO_ERROR;
 
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-create-mini-tree-txn",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Begin a new transaction that is based on revision 0.  */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
@@ -512,7 +512,7 @@
 
   /* Prepare a txn to receive the greek tree. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-create-greek-tree-txn",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -577,7 +577,7 @@
     return SVN_NO_ERROR;
 
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-list-dir",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -659,7 +659,7 @@
 
   /* Open the fs */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-rev-props",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Set some properties on the revision. */
   for (i = 0; i < 4; i++)
@@ -762,7 +762,7 @@
 
   /* Open the fs */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-txn-props",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
 
   /* Set some properties on the revision. */
@@ -915,7 +915,7 @@
 
   /* Open the fs and transaction */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-node-props",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -1071,7 +1071,7 @@
     return SVN_NO_ERROR;
 
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-youngest-rev",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Get youngest revision of brand spankin' new filesystem. */
   SVN_ERR(svn_fs_youngest_rev(&youngest_rev, fs, pool));
@@ -1122,7 +1122,7 @@
 
   /* Prepare a filesystem. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-basic-commit",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Save the current youngest revision. */
   SVN_ERR(svn_fs_youngest_rev(&before_rev, fs, pool));
@@ -1181,7 +1181,7 @@
 
   /* Prepare a filesystem. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-validate-tree-entries",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* In a txn, create the greek tree. */
   subpool = svn_pool_create(pool);
@@ -1314,7 +1314,7 @@
 
   /* Prepare a filesystem. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-merging-commit",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Initialize our revision number stuffs. */
   for (i = 0;
@@ -2095,7 +2095,7 @@
 
   /* Prepare a filesystem. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-copy-test",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* In first txn, create and commit the greek tree. */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
@@ -2398,7 +2398,7 @@
 
   /* Prepare a txn to receive the greek tree. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-del-from-dir",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -2554,7 +2554,7 @@
 
   /* Prepare a txn to receive the greek tree. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-del-tree",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -2910,7 +2910,7 @@
 
   /* Prepare a filesystem. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-commit-date",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   before_commit = apr_time_now();
 
@@ -2966,7 +2966,7 @@
 
   /* Prepare a filesystem. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-check-old-revisions",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Commit a greek tree. */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
@@ -3332,7 +3332,7 @@
 
   /* Create a filesystem and repository. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-check-all-revisions",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /***********************************************************************/
   /* REVISION 0 */
@@ -3650,7 +3650,7 @@
 
 static svn_error_t *
 file_integrity_helper(apr_size_t filesize, apr_uint32_t *seed,
-                      const char *fs_type, const char *fs_name,
+                      svn_test_opts_t *opts, const char *fs_name,
                       apr_pool_t *pool)
 {
   svn_fs_t *fs;
@@ -3667,7 +3667,7 @@
   svn_revnum_t j;
 
   /* Create a filesystem and repository. */
-  SVN_ERR(svn_test__create_fs(&fs, fs_name, fs_type, pool));
+  SVN_ERR(svn_test__create_fs(&fs, fs_name, opts, pool));
 
   /* Set up our file contents string buffer. */
   content_buffer = apr_palloc(pool, filesize);
@@ -3799,7 +3799,7 @@
 
   /* Being no larger than the standard delta window size affects
      deltification internally, so test that. */
-  return file_integrity_helper(SVN_DELTA_WINDOW_SIZE, &seed, opts->fs_type,
+  return file_integrity_helper(SVN_DELTA_WINDOW_SIZE, &seed, opts,
                                "test-repo-medium-file-integrity", pool);
 }
 
@@ -3820,7 +3820,7 @@
 
   /* Being larger than the standard delta window size affects
      deltification internally, so test that. */
-  return file_integrity_helper(SVN_DELTA_WINDOW_SIZE + 1, &seed, opts->fs_type,
+  return file_integrity_helper(SVN_DELTA_WINDOW_SIZE + 1, &seed, opts,
                                "test-repo-large-file-integrity", pool);
 }
 
@@ -3845,7 +3845,7 @@
 
   /* Create a filesystem and repository. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-check-root-revision",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Create and commit the greek tree. */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
@@ -3971,7 +3971,7 @@
 
   /* Create a filesystem and repository. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-node-created-rev",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Created the greek tree in revision 1. */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
@@ -4086,7 +4086,7 @@
 
   /* Create a filesystem and repository. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-check-related",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /*** Step I: Build up some state in our repository through a series
        of commits */
@@ -4296,7 +4296,7 @@
 
   /* Create a filesystem and repository. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-branch-test",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /*** Revision 1:  Create the greek tree in revision.  ***/
   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
@@ -4382,7 +4382,7 @@
   apr_md5(expected_digest, str->data, str->len);
 
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-verify-checksum",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
   SVN_ERR(svn_fs_make_file(txn_root, "fact", pool));
@@ -4474,7 +4474,7 @@
 
   /* Prepare a filesystem. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-closest-copy",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* In first txn, create and commit the greek tree. */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, spool));
@@ -4575,7 +4575,7 @@
 
   /* Prepare a filesystem. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-root-revisions",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* In first txn, create and commit the greek tree. */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, spool));
@@ -4651,7 +4651,7 @@
 
   /* Prepare a filesystem. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-unordered-txn-dirprops",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Create and commit the greek tree. */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
@@ -4727,7 +4727,7 @@
 
   /* Prepare a filesystem. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-set-uuid",
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Set the repository UUID to something fixed. */
   SVN_ERR(svn_fs_set_uuid(fs, fixed_uuid, pool));
@@ -4778,7 +4778,7 @@
 
   /* Create the repository. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-node-origin-rev", 
-                              opts->fs_type, pool));
+                              opts, pool));
 
   /* Revision 1: Create the Greek tree.  */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
diff --git a/subversion/tests/libsvn_fs/locks-test.c b/subversion/tests/libsvn_fs/locks-test.c
index 5965224..7e0007c 100644
--- a/subversion/tests/libsvn_fs/locks-test.c
+++ b/subversion/tests/libsvn_fs/locks-test.c
@@ -112,7 +112,7 @@
 
   /* Prepare a filesystem and a new txn. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-only",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -158,7 +158,7 @@
 
   /* Prepare a filesystem and a new txn. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-lookup-lock-by-path",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -208,7 +208,7 @@
 
   /* Prepare a filesystem and a new txn. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-attach-lock",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -267,7 +267,7 @@
 
   /* Prepare a filesystem and a new txn. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-get-locks",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -402,7 +402,7 @@
 
   /* Prepare a filesystem and a new txn. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-basic-lock",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -460,7 +460,7 @@
 
   /* Prepare a filesystem and a new txn. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-credentials",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -557,7 +557,7 @@
 
   /* Prepare a filesystem and a new txn. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-final-lock-check",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -621,7 +621,7 @@
 
   /* Prepare a filesystem and a new txn. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-dir-propchange",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -678,7 +678,7 @@
 
   /* Prepare a filesystem and a new txn. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-name-reservation",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -737,7 +737,7 @@
 
   /* Prepare a filesystem and a new txn. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-directory-locks-kinda",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -815,7 +815,7 @@
 
   /* Prepare a filesystem and a new txn. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-expiration",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -906,7 +906,7 @@
 
   /* Prepare a filesystem and a new txn. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-steal-refresh",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -993,7 +993,7 @@
 
   /* Prepare a filesystem and a new txn. */
   SVN_ERR(svn_test__create_fs(&fs, "test-repo-lock-out-of-date",
-                              opts->fs_type, pool));
+                              opts, pool));
   SVN_ERR(svn_fs_begin_txn2(&txn, fs, 0, SVN_FS_TXN_CHECK_LOCKS, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
diff --git a/subversion/tests/libsvn_fs_base/changes-test.c b/subversion/tests/libsvn_fs_base/changes-test.c
index 5d34ddf..e77c437 100644
--- a/subversion/tests/libsvn_fs_base/changes-test.c
+++ b/subversion/tests/libsvn_fs_base/changes-test.c
@@ -173,8 +173,8 @@
     return SVN_NO_ERROR;
 
   /* Create a new fs and repos */
-  SVN_ERR(svn_test__create_fs(&fs, "test-repo-changes-add",
-                              "bdb", pool));
+  SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-changes-add",
+                                  pool));
 
   /* Add the standard slew of changes. */
   SVN_ERR(add_standard_changes(fs, pool));
@@ -201,8 +201,8 @@
     return SVN_NO_ERROR;
 
   /* Create a new fs and repos */
-  SVN_ERR(svn_test__create_fs(&fs, "test-repo-changes-fetch",
-                              "bdb", pool));
+  SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-changes-fetch",
+                                  pool));
 
   /* First, verify that we can request changes for an arbitrary key
      without error. */
@@ -309,8 +309,8 @@
     return SVN_NO_ERROR;
 
   /* Create a new fs and repos */
-  SVN_ERR(svn_test__create_fs(&fs, "test-repo-changes-delete",
-                              "bdb", pool));
+  SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-changes-delete",
+                                  pool));
 
   /* Add the standard slew of changes. */
   SVN_ERR(add_standard_changes(fs, pool));
@@ -505,8 +505,8 @@
     return SVN_NO_ERROR;
 
   /* Create a new fs and repos */
-  SVN_ERR(svn_test__create_fs(&fs, "test-repo-changes-fetch",
-                              "bdb", pool));
+  SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-changes-fetch",
+                                  pool));
 
   /* First, verify that we can request changes for an arbitrary key
      without error. */
@@ -575,9 +575,9 @@
     return SVN_NO_ERROR;
 
   /* Create a new fs and repos */
-  SVN_ERR(svn_test__create_fs
+  SVN_ERR(svn_test__create_bdb_fs
           (&fs, "test-repo-changes-fetch-ordering",
-           "bdb", pool));
+           pool));
 
   /*** REVISION 1: Make some files and dirs. ***/
   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
diff --git a/subversion/tests/libsvn_fs_base/fs-base-test.c b/subversion/tests/libsvn_fs_base/fs-base-test.c
index 5a362a0..340466f 100644
--- a/subversion/tests/libsvn_fs_base/fs-base-test.c
+++ b/subversion/tests/libsvn_fs_base/fs-base-test.c
@@ -58,8 +58,8 @@
     return SVN_NO_ERROR;
 
   /* Create and close a repository. */
-  SVN_ERR(svn_test__create_fs(&fs, "test-repo-create-berkeley",
-                              "bdb", pool));
+  SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-create-berkeley",
+                                  pool));
 
   return SVN_NO_ERROR;
 }
@@ -88,8 +88,8 @@
     return SVN_NO_ERROR;
 
   /* Create and close a repository (using fs). */
-  SVN_ERR(svn_test__create_fs(&fs, "test-repo-open-berkeley",
-                              "bdb", pool));
+  SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-open-berkeley",
+                                  pool));
 
   /* Create a different fs object, and use it to re-open the
      repository again.  */
@@ -278,8 +278,8 @@
     return SVN_NO_ERROR;
 
   /* Prepare two txns to receive the Greek tree. */
-  SVN_ERR(svn_test__create_fs(&fs, "test-repo-abort-txn",
-                              "bdb", pool));
+  SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-abort-txn",
+                                  pool));
   SVN_ERR(svn_fs_begin_txn(&txn1, fs, 0, pool));
   SVN_ERR(svn_fs_begin_txn(&txn2, fs, 0, pool));
   SVN_ERR(svn_fs_txn_root(&txn1_root, txn1, pool));
@@ -517,8 +517,8 @@
     return SVN_NO_ERROR;
 
   /* Prepare a txn to receive the greek tree. */
-  SVN_ERR(svn_test__create_fs(&fs, "test-repo-del-from-dir",
-                              "bdb", pool));
+  SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-del-from-dir",
+                                  pool));
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -713,8 +713,8 @@
    */
 
   /* Prepare a txn to receive the greek tree. */
-  SVN_ERR(svn_test__create_fs(&fs, "test-repo-del-tree",
-                              "bdb", pool));
+  SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-del-tree",
+                                  pool));
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, pool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
 
@@ -1200,8 +1200,8 @@
     return SVN_NO_ERROR;
 
   /* Create a filesystem and repository. */
-  SVN_ERR(svn_test__create_fs(&fs, "test-repo-create-within-copy",
-                              "bdb", pool));
+  SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-create-within-copy",
+                                  pool));
 
   /*** Revision 1:  Create the greek tree in revision.  ***/
   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, spool));
@@ -1329,8 +1329,8 @@
     return SVN_NO_ERROR;
 
   /* Create a filesystem and repository. */
-  SVN_ERR(svn_test__create_fs(&fs, "test-repo-skip-deltas",
-                              "bdb", pool));
+  SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-skip-deltas",
+                                  pool));
 
   /* Create the file. */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
@@ -1408,8 +1408,8 @@
     return SVN_NO_ERROR;
 
   /* Create a filesystem and repository. */
-  SVN_ERR(svn_test__create_fs(&fs, "test-repo-redundant-copy",
-                              "bdb", pool));
+  SVN_ERR(svn_test__create_bdb_fs(&fs, "test-repo-redundant-copy",
+                                  pool));
 
   /* Create the greek tree in revision 1. */
   SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, pool));
diff --git a/subversion/tests/libsvn_fs_base/strings-reps-test.c b/subversion/tests/libsvn_fs_base/strings-reps-test.c
index 7dc521a..cedd22c 100644
--- a/subversion/tests/libsvn_fs_base/strings-reps-test.c
+++ b/subversion/tests/libsvn_fs_base/strings-reps-test.c
@@ -103,9 +103,9 @@
     return SVN_NO_ERROR;
 
   /* Create a new fs and repos */
-  SVN_ERR(svn_test__create_fs
+  SVN_ERR(svn_test__create_bdb_fs
           (&fs, "test-repo-write-new-rep",
-           "bdb", pool));
+           pool));
 
   /* Set up transaction baton */
   args.fs = fs;
@@ -142,9 +142,9 @@
     return SVN_NO_ERROR;
 
   /* Create a new fs and repos */
-  SVN_ERR(svn_test__create_fs
+  SVN_ERR(svn_test__create_bdb_fs
           (&fs, "test-repo-write-rep",
-           "bdb", pool));
+           pool));
 
   /* Set up transaction baton */
   new_args.fs = fs;
@@ -220,9 +220,9 @@
     return SVN_NO_ERROR;
 
   /* Create a new fs and repos */
-  SVN_ERR(svn_test__create_fs
+  SVN_ERR(svn_test__create_bdb_fs
           (&fs, "test-repo-read-rep",
-           "bdb", pool));
+           pool));
 
   /* Set up transaction baton */
   new_args.fs = fs;
@@ -305,9 +305,9 @@
     return SVN_NO_ERROR;
 
   /* Create a new fs and repos */
-  SVN_ERR(svn_test__create_fs
+  SVN_ERR(svn_test__create_bdb_fs
           (&fs, "test-repo-delete-rep",
-           "bdb", pool));
+           pool));
 
   /* Set up transaction baton */
   new_args.fs = fs;
@@ -540,9 +540,9 @@
     return SVN_NO_ERROR;
 
   /* Create a new fs and repos */
-  SVN_ERR(svn_test__create_fs
+  SVN_ERR(svn_test__create_bdb_fs
           (&fs, "test-repo-test-strings",
-           "bdb", pool));
+           pool));
 
   /* The plan (after each step below, verify the size and contents of
      the string):
@@ -645,9 +645,9 @@
     return SVN_NO_ERROR;
 
   /* Create a new fs and repos */
-  SVN_ERR(svn_test__create_fs
+  SVN_ERR(svn_test__create_bdb_fs
           (&fs, "test-repo-test-strings",
-           "bdb", pool));
+           pool));
 
   args.fs = fs;
   args.key = NULL;
@@ -675,9 +675,9 @@
     return SVN_NO_ERROR;
 
   /* Create a new fs and repos */
-  SVN_ERR(svn_test__create_fs
+  SVN_ERR(svn_test__create_bdb_fs
           (&fs, "test-repo-abort-string",
-           "bdb", pool));
+           pool));
 
   /* The plan:
 
@@ -743,9 +743,9 @@
     return SVN_NO_ERROR;
 
   /* Create a new fs and repos */
-  SVN_ERR(svn_test__create_fs
+  SVN_ERR(svn_test__create_bdb_fs
           (&fs, "test-repo-copy-string",
-           "bdb", pool));
+           pool));
 
   /*  Write a new string (string1). */
   args.fs = fs;
diff --git a/subversion/tests/libsvn_ra_local/ra-local-test.c b/subversion/tests/libsvn_ra_local/ra-local-test.c
index 5714582..ecff146 100644
--- a/subversion/tests/libsvn_ra_local/ra-local-test.c
+++ b/subversion/tests/libsvn_ra_local/ra-local-test.c
@@ -85,7 +85,7 @@
 static svn_error_t *
 make_and_open_local_repos(svn_ra_session_t **session,
                           const char *repos_name,
-                          const char *fs_type,
+                          svn_test_opts_t *opts,
                           apr_pool_t *pool)
 {
   svn_repos_t *repos;
@@ -94,7 +94,7 @@
 
   SVN_ERR(svn_ra_create_callbacks(&cbtable, pool));
 
-  SVN_ERR(svn_test__create_repos(&repos, repos_name, fs_type, pool));
+  SVN_ERR(svn_test__create_repos(&repos, repos_name, opts, pool));
   SVN_ERR(svn_ra_initialize(pool));
 
   SVN_ERR(current_directory_url(&url, repos_name, pool));
@@ -130,7 +130,7 @@
     return SVN_NO_ERROR;
 
   SVN_ERR(make_and_open_local_repos(&session,
-                                    "test-repo-open", opts->fs_type, pool));
+                                    "test-repo-open", opts, pool));
 
   return SVN_NO_ERROR;
 }
@@ -152,7 +152,7 @@
     return SVN_NO_ERROR;
 
   SVN_ERR(make_and_open_local_repos(&session,
-                                    "test-repo-getrev", opts->fs_type,
+                                    "test-repo-getrev", opts,
                                     pool));
 
   /* Get the youngest revision and make sure it's 0. */
@@ -294,14 +294,14 @@
 static svn_error_t *
 check_split_url(const char *repos_path,
                 const char *in_repos_path,
-                const char *fs_type,
+                svn_test_opts_t *opts,
                 apr_pool_t *pool)
 {
   svn_repos_t *repos;
   const char *url, *root_url, *repos_part, *in_repos_part;
 
   /* Create a filesystem and repository */
-  SVN_ERR(svn_test__create_repos(&repos, repos_path, fs_type, pool));
+  SVN_ERR(svn_test__create_repos(&repos, repos_path, opts, pool));
 
   SVN_ERR(current_directory_url(&root_url, repos_path, pool));
   if (in_repos_path)
@@ -347,15 +347,15 @@
      in-repository path begins.  */
   SVN_ERR(check_split_url("test-repo-split-fs1",
                           "/trunk/foobar/quux.c",
-                          opts->fs_type,
+                          opts,
                           pool));
   SVN_ERR(check_split_url("test-repo-split-fs2",
                           "/alpha/beta/gamma/delta/epsilon/zeta/eta/theta",
-                          opts->fs_type,
+                          opts,
                           pool));
   SVN_ERR(check_split_url("test-repo-split-fs3",
                           NULL,
-                          opts->fs_type,
+                          opts,
                           pool));
 
   return SVN_NO_ERROR;
diff --git a/subversion/tests/libsvn_repos/repos-test.c b/subversion/tests/libsvn_repos/repos-test.c
index 6bbd7e3..d715959 100644
--- a/subversion/tests/libsvn_repos/repos-test.c
+++ b/subversion/tests/libsvn_repos/repos-test.c
@@ -73,7 +73,7 @@
 
   /* Create a filesystem and repository. */
   SVN_ERR(svn_test__create_repos(&repos, "test-repo-dir-deltas",
-                                 opts->fs_type, pool));
+                                 opts, pool));
   fs = svn_repos_fs(repos);
   expected_trees[revision_count].num_entries = 0;
   expected_trees[revision_count++].entries = 0;
@@ -380,7 +380,7 @@
 
   /* Create a filesystem and repository. */
   SVN_ERR(svn_test__create_repos(&repos, "test-repo-del-under-copy",
-                                 opts->fs_type, pool));
+                                 opts, pool));
   fs = svn_repos_fs(repos);
 
   /* Prepare a txn to receive the greek tree. */
@@ -520,7 +520,7 @@
 
   /* Create a filesystem and repository. */
   SVN_ERR(svn_test__create_repos(&repos, "test-repo-revisions-changed",
-                                 opts->fs_type, pool));
+                                 opts, pool));
   fs = svn_repos_fs(repos);
 
   /*** Testing Algorithm ***
@@ -779,7 +779,7 @@
 
   /* Create the repository with a Greek tree. */
   SVN_ERR(svn_test__create_repos(&repos, "test-repo-node-locations",
-                                 opts->fs_type, pool));
+                                 opts, pool));
   fs = svn_repos_fs(repos);
   SVN_ERR(svn_fs_begin_txn(&txn, fs, 0, subpool));
   SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
@@ -832,7 +832,7 @@
 
   /* Create the repository. */
   SVN_ERR(svn_test__create_repos(&repos, "test-repo-node-locations2",
-                                 opts->fs_type, pool));
+                                 opts, pool));
   fs = svn_repos_fs(repos);
 
   /* Revision 1:  Add a directory /foo  */
@@ -1045,7 +1045,7 @@
 
   /* Create a filesystem and repository. */
   SVN_ERR(svn_test__create_repos(&repos, "test-repo-rmlocks",
-                                 opts->fs_type, pool));
+                                 opts, pool));
   fs = svn_repos_fs(repos);
 
   /* Prepare a txn to receive the greek tree. */
@@ -1419,7 +1419,7 @@
 
   /* Create a filesystem and repository. */
   SVN_ERR(svn_test__create_repos(&repos, "test-repo-commit-authz",
-                                 opts->fs_type, subpool));
+                                 opts, subpool));
   fs = svn_repos_fs(repos);
 
   /* Prepare a txn to receive the greek tree. */
@@ -1637,7 +1637,7 @@
 
   /* Create a filesystem and repository. */
   SVN_ERR(svn_test__create_repos(&repos, "test-repo-commit-continue",
-                                 opts->fs_type, subpool));
+                                 opts, subpool));
   fs = svn_repos_fs(repos);
 
   /* Prepare a txn to receive the greek tree. */
@@ -1818,7 +1818,7 @@
 
   /* Create the repository. */
   SVN_ERR(svn_test__create_repos(&repos, "test-repo-node-location-segments",
-                                 opts->fs_type, pool));
+                                 opts, pool));
   fs = svn_repos_fs(repos);
 
   /* Revision 1: Create the Greek tree.  */
@@ -2011,7 +2011,7 @@
     return SVN_NO_ERROR;
 
   SVN_ERR(svn_test__create_repos(&repos, "test-repo-reporter-depth-exclude",
-                                 opts->fs_type, pool));
+                                 opts, pool));
   fs = svn_repos_fs(repos);
 
   /* Prepare a txn to receive the greek tree. */
diff --git a/subversion/tests/libsvn_subr/cache-test.c b/subversion/tests/libsvn_subr/cache-test.c
new file mode 100644
index 0000000..4828ace
--- /dev/null
+++ b/subversion/tests/libsvn_subr/cache-test.c
@@ -0,0 +1,274 @@
+/*
+ * cache-test.c -- test the in-memory cache
+ *
+ * ====================================================================
+ * Copyright (c) 2006 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 <stdio.h>
+#include <string.h>
+#include <apr_general.h>
+#include <apr_lib.h>
+#include <apr_time.h>
+
+#include "svn_cache.h"
+#include "svn_pools.h"
+
+#include "svn_private_config.h"
+
+#include "../svn_test.h"
+
+static svn_cache_dup_func_t dup_revnum;
+static svn_error_t *
+dup_revnum(void **out,
+           void *in,
+           apr_pool_t *pool)
+{
+  svn_revnum_t *in_rn = in, *duped = apr_palloc(pool, sizeof(*duped));
+
+  *duped = *in_rn;
+
+  *out = duped;
+
+  return SVN_NO_ERROR;
+}
+
+static svn_cache_serialize_func_t serialize_revnum;
+static svn_error_t *
+serialize_revnum(char **data,
+                 apr_size_t *data_len,
+                 void *in,
+                 apr_pool_t *pool)
+{
+  *data_len = sizeof(svn_revnum_t);
+  *data = apr_pmemdup(pool, in, *data_len);
+
+  return SVN_NO_ERROR;
+}
+
+
+static svn_cache_deserialize_func_t deserialize_revnum;
+static svn_error_t *
+deserialize_revnum(void **out,
+                   const char *data,
+                   apr_size_t data_len,
+                   apr_pool_t *pool)
+{
+  svn_revnum_t *in_rev, *out_rev;
+  if (data_len != sizeof(*in_rev))
+    return svn_error_create(SVN_ERR_REVNUM_PARSE_FAILURE, NULL,
+                            _("Bad size for revision number in cache"));
+  in_rev = (svn_revnum_t *) data;
+  out_rev = apr_palloc(pool, sizeof(*out_rev));
+  *out_rev = *in_rev;
+  *out = out_rev;
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+basic_cache_test(svn_cache_t *cache,
+                 svn_boolean_t size_is_one,
+                 apr_pool_t *pool)
+{
+  svn_boolean_t found;
+  svn_revnum_t twenty = 20, thirty = 30, *answer;
+  apr_pool_t *subpool;
+
+  /* We use a subpool for all calls in this test and aggressively
+   * clear it, to try to find any bugs where the cached values aren't
+   * actually saved away in the cache's pools. */
+  subpool = svn_pool_create(pool);
+
+  SVN_ERR(svn_cache_get((void **) &answer, &found, cache, "twenty", subpool));
+  if (found)
+    return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
+                            "cache found an entry that wasn't there");
+  svn_pool_clear(subpool);
+
+  SVN_ERR(svn_cache_set(cache, "twenty", &twenty, subpool));
+  svn_pool_clear(subpool);
+
+  SVN_ERR(svn_cache_get((void **) &answer, &found, cache, "twenty", subpool));
+  if (! found)
+    return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
+                            "cache failed to find entry for 'twenty'");
+  if (*answer != 20)
+    return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
+                             "expected 20 but found '%ld'", *answer);
+  svn_pool_clear(subpool);
+
+  SVN_ERR(svn_cache_set(cache, "thirty", &thirty, subpool));
+  svn_pool_clear(subpool);
+
+  SVN_ERR(svn_cache_get((void **) &answer, &found, cache, "thirty", subpool));
+  if (! found)
+    return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
+                            "cache failed to find entry for 'thirty'");
+  if (*answer != 30)
+    return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
+                             "expected 30 but found '%ld'", *answer);
+
+  if (size_is_one)
+    {
+      SVN_ERR(svn_cache_get((void **) &answer, &found, cache, "twenty", subpool));
+      if (found)
+        return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
+                                "cache found entry for 'twenty' that should have "
+                                "expired");
+    }
+  svn_pool_destroy(subpool);
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_inprocess_cache_basic(const char **msg,
+                           svn_boolean_t msg_only,
+                           svn_test_opts_t *opts,
+                           apr_pool_t *pool)
+{
+  svn_cache_t *cache;
+
+  *msg = "basic inprocess svn_cache test";
+
+  if (msg_only)
+    return SVN_NO_ERROR;
+
+  /* Create a cache with just one entry. */
+  SVN_ERR(svn_cache_create_inprocess(&cache,
+                                     dup_revnum,
+                                     APR_HASH_KEY_STRING,
+                                     1,
+                                     1,
+                                     TRUE,
+                                     pool));
+
+  return basic_cache_test(cache, TRUE, pool);
+}
+
+static svn_error_t *
+test_memcache_basic(const char **msg,
+                    svn_boolean_t msg_only,
+                    svn_test_opts_t *opts,
+                    apr_pool_t *pool)
+{
+  svn_cache_t *cache;
+  svn_config_t *config;
+  svn_memcache_t *memcache = NULL;
+  const char *prefix = apr_psprintf(pool,
+                                    "test_memcache_basic-%" APR_TIME_T_FMT,
+                                    apr_time_now());
+
+  *msg = "basic memcache svn_cache test";
+
+  if (msg_only)
+    return SVN_NO_ERROR;
+
+  if (opts->config_file)
+    {
+      SVN_ERR(svn_config_read(&config, opts->config_file, TRUE, pool));
+      SVN_ERR(svn_cache_make_memcache_from_config(&memcache, config, pool));
+    }
+
+  if (! memcache)
+    return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
+                            "not configured to use memcached");
+
+
+  /* Create a memcache-based cache. */
+  SVN_ERR(svn_cache_create_memcache(&cache,
+                                    memcache,
+                                    serialize_revnum,
+                                    deserialize_revnum,
+                                    APR_HASH_KEY_STRING,
+                                    prefix,
+                                    pool));
+
+  return basic_cache_test(cache, FALSE, pool);
+}
+
+
+
+static svn_error_t *
+test_memcache_long_key(const char **msg,
+                       svn_boolean_t msg_only,
+                       svn_test_opts_t *opts,
+                       apr_pool_t *pool)
+{
+  svn_cache_t *cache;
+  svn_config_t *config;
+  svn_memcache_t *memcache = NULL;
+  svn_revnum_t fifty = 50, *answer;
+  svn_boolean_t found = FALSE;
+  const char *prefix = apr_psprintf(pool,
+                                    "test_memcache_long_key-%" APR_TIME_T_FMT,
+                                    apr_time_now());
+  static const char *long_key =
+    "0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 50 */
+    "0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 100 */
+    "0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 150 */
+    "0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 200 */
+    "0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 250 */
+    "0123456789" "0123456789" "0123456789" "0123456789" "0123456789" /* 300 */
+    ;
+
+  *msg = "memcache svn_cache with very long keys";
+
+  if (msg_only)
+    return SVN_NO_ERROR;
+
+  if (opts->config_file)
+    {
+      SVN_ERR(svn_config_read(&config, opts->config_file, TRUE, pool));
+      SVN_ERR(svn_cache_make_memcache_from_config(&memcache, config, pool));
+    }
+
+  if (! memcache)
+    return svn_error_create(SVN_ERR_TEST_SKIPPED, NULL,
+                            "not configured to use memcached");
+
+
+  /* Create a memcache-based cache. */
+  SVN_ERR(svn_cache_create_memcache(&cache,
+                                    memcache,
+                                    serialize_revnum,
+                                    deserialize_revnum,
+                                    APR_HASH_KEY_STRING,
+                                    prefix,
+                                    pool));
+
+  SVN_ERR(svn_cache_set(cache, long_key, &fifty, pool));
+  SVN_ERR(svn_cache_get((void **) &answer, &found, cache, long_key, pool));
+
+  if (! found)
+    return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
+                            "cache failed to find entry for 'fifty'");
+  if (*answer != 50)
+    return svn_error_createf(SVN_ERR_TEST_FAILED, NULL,
+                             "expected 50 but found '%ld'", *answer);
+
+  return SVN_NO_ERROR;
+}
+
+
+/* The test table.  */
+
+struct svn_test_descriptor_t test_funcs[] =
+  {
+    SVN_TEST_NULL,
+    SVN_TEST_PASS(test_inprocess_cache_basic),
+    SVN_TEST_PASS(test_memcache_basic),
+    SVN_TEST_PASS(test_memcache_long_key),
+    SVN_TEST_NULL
+  };
diff --git a/subversion/tests/svn_test.h b/subversion/tests/svn_test.h
index 2860e80..a7756bc 100644
--- a/subversion/tests/svn_test.h
+++ b/subversion/tests/svn_test.h
@@ -37,6 +37,8 @@
 {
   /* Description of the fs backend that should be used for testing. */
   const char *fs_type;
+  /* Config file. */
+  const char *config_file;
   /* Add future "arguments" here. */
 } svn_test_opts_t;
 
diff --git a/subversion/tests/svn_test_fs.c b/subversion/tests/svn_test_fs.c
index c2daad3..3b29017 100644
--- a/subversion/tests/svn_test_fs.c
+++ b/subversion/tests/svn_test_fs.c
@@ -73,11 +73,11 @@
 }
 
 
-svn_error_t *
-svn_test__create_fs(svn_fs_t **fs_p,
-                    const char *name,
-                    const char *fs_type,
-                    apr_pool_t *pool)
+static svn_error_t *
+create_fs(svn_fs_t **fs_p,
+          const char *name,
+          const char *fs_type,
+          apr_pool_t *pool)
 {
   apr_finfo_t finfo;
   apr_hash_t *fs_config = make_fs_config(fs_type, pool);
@@ -110,15 +110,63 @@
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+maybe_install_fsfs_conf(svn_fs_t *fs,
+                        svn_test_opts_t *opts,
+                        svn_boolean_t *must_reopen,
+                        apr_pool_t *pool)
+{
+  *must_reopen = FALSE;
+  if (strcmp(opts->fs_type, "fsfs") != 0 || ! opts->config_file)
+    return SVN_NO_ERROR;
+
+  *must_reopen = TRUE;
+  return svn_io_copy_file(opts->config_file,
+                          svn_path_join(svn_fs_path(fs, pool), "fsfs.conf", pool),
+                          FALSE,
+                          pool);
+}
+
+
+svn_error_t *
+svn_test__create_bdb_fs(svn_fs_t **fs_p,
+                        const char *name,
+                        apr_pool_t *pool)
+{
+  return create_fs(fs_p, name, "bdb", pool);
+}
+
+svn_error_t *
+svn_test__create_fs(svn_fs_t **fs_p,
+                    const char *name,
+                    svn_test_opts_t *opts,
+                    apr_pool_t *pool)
+{
+  svn_boolean_t must_reopen;
+
+  SVN_ERR(create_fs(fs_p, name, opts->fs_type, pool));
+
+  SVN_ERR(maybe_install_fsfs_conf(*fs_p, opts, &must_reopen, pool));
+  if (must_reopen)
+    {
+      SVN_ERR(svn_fs_open(fs_p, name, NULL, pool));
+      svn_fs_set_warning_func(*fs_p, fs_warning_handler, NULL);
+    }
+
+  return SVN_NO_ERROR;
+}
+
 
 svn_error_t *
 svn_test__create_repos(svn_repos_t **repos_p,
                        const char *name,
-                       const char *fs_type,
+                       svn_test_opts_t *opts,
                        apr_pool_t *pool)
 {
   apr_finfo_t finfo;
-  apr_hash_t *fs_config = make_fs_config(fs_type, pool);
+  svn_repos_t *repos;
+  svn_boolean_t must_reopen;
+  apr_hash_t *fs_config = make_fs_config(opts->fs_type, pool);
 
   /* If there's already a repository named NAME, delete it.  Doing
      things this way means that repositories stick around after a
@@ -134,12 +182,21 @@
                                  "there is already a file named '%s'", name);
     }
 
-  SVN_ERR(svn_repos_create(repos_p, name, NULL, NULL, NULL,
+  SVN_ERR(svn_repos_create(&repos, name, NULL, NULL, NULL,
                            fs_config, pool));
 
   /* Register this repo for cleanup. */
   svn_test_add_dir_cleanup(name);
 
+  SVN_ERR(maybe_install_fsfs_conf(svn_repos_fs(repos), opts, &must_reopen,
+                                  pool));
+  if (must_reopen)
+    {
+      SVN_ERR(svn_repos_open(&repos, name, pool));
+      svn_fs_set_warning_func(svn_repos_fs(repos), fs_warning_handler, NULL);
+    }
+
+  *repos_p = repos;
   return SVN_NO_ERROR;
 }
 
diff --git a/subversion/tests/svn_test_fs.h b/subversion/tests/svn_test_fs.h
index c50a482..11198c6 100644
--- a/subversion/tests/svn_test_fs.h
+++ b/subversion/tests/svn_test_fs.h
@@ -41,23 +41,29 @@
 svn_test__fs_new(svn_fs_t **fs_p, apr_pool_t *pool);
 
 
-/* Create a filesystem of FS_TYPE in a subdir NAME and return a new FS
-   object which points to it.  FS_TYPE should be either "bdb" or
-   "fsfs".  Filesystem tests that are backend-specific should use
-   svn_test__create_fs instead of this. */
+/* Creates a filesystem which is always of type "bdb" in a subdir NAME
+   and return a new FS object which points to it. */
+svn_error_t *
+svn_test__create_bdb_fs(svn_fs_t **fs_p,
+                        const char *name,
+                        apr_pool_t *pool);
+
+
+/* Create a filesystem based on OPTS in a subdir NAME and return a new
+   FS object which points to it.  */
 svn_error_t *
 svn_test__create_fs(svn_fs_t **fs_p,
                     const char *name,
-                    const char *fs_type,
+                    svn_test_opts_t *opts,
                     apr_pool_t *pool);
 
 
-/* Create a repository with a filesystem of FS_TYPE in a subdir NAME
+/* Create a repository with a filesystem based on OPTS in a subdir NAME
    and return a new REPOS object which points to it.  */
 svn_error_t *
 svn_test__create_repos(svn_repos_t **repos_p,
                        const char *name,
-                       const char *fs_type,
+                       svn_test_opts_t *opts,
                        apr_pool_t *pool);
 
 
diff --git a/subversion/tests/svn_test_main.c b/subversion/tests/svn_test_main.c
index 943f857..7b5020c 100644
--- a/subversion/tests/svn_test_main.c
+++ b/subversion/tests/svn_test_main.c
@@ -57,13 +57,16 @@
   fstype_opt,
   list_opt,
   verbose_opt,
-  quiet_opt
+  quiet_opt,
+  config_opt
 };
 
 static const apr_getopt_option_t cl_options[] =
 {
   {"cleanup",       cleanup_opt, 0,
                     N_("remove test directories after success")},
+  {"config-file",   config_opt, 1,
+                    N_("specify test config file ARG")},
   {"fs-type",       fstype_opt, 1,
                     N_("specify a filesystem backend type ARG")},
   {"list",          list_opt, 0,
@@ -185,6 +188,13 @@
   /* Do test */
   err = func(&msg, msg_only || skip, opts, pool);
 
+  if (err && err->apr_err == SVN_ERR_TEST_SKIPPED)
+    {
+      svn_error_clear(err);
+      err = SVN_NO_ERROR;
+      skip = TRUE;
+    }
+
   /* Failure means unexpected results -- FAIL or XPASS. */
   test_failed = ((err != SVN_NO_ERROR) != (xfail != 0));
 
@@ -306,6 +316,9 @@
         case cleanup_opt:
           cleanup_mode = 1;
           break;
+        case config_opt:
+          opts.config_file = apr_pstrdup(pool, opt_arg);
+          break;
         case fstype_opt:
           opts.fs_type = apr_pstrdup(pool, opt_arg);
           break;
diff --git a/subversion/tests/tests.conf b/subversion/tests/tests.conf
new file mode 100644
index 0000000..1b3d643
--- /dev/null
+++ b/subversion/tests/tests.conf
@@ -0,0 +1,19 @@
+### This config file configures some aspects of the Subversion test
+### suite.  Pass --config-file FILENAME to test programs if running
+### them manually; "make check" passes this file in automatically.
+
+### Currently, it is used for two purposes: it is used to configure
+### memcached for direct svn_cache/memcached tests in
+### libsvn_subr/cache-test; and it is copied into new FSFS
+### repositories as fsfs.conf (to configure their use of memcached as
+### well).
+
+[memcached-servers]
+### Run memcached servers and enter lines like the following (the key
+### is ignored):
+# key = 127.0.0.1:11211
+
+[caches]
+### In the test suite, we should make FSFS cache failures into actual
+### test failures:
+fail-stop = true