You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2014/01/02 01:24:31 UTC

svn commit: r1554711 - in /subversion/trunk/subversion: include/private/ libsvn_fs_fs/ libsvn_subr/

Author: stefan2
Date: Thu Jan  2 00:24:30 2014
New Revision: 1554711

URL: http://svn.apache.org/r1554711
Log:
Within the FSFS lib, switch directory representation from APR hash
to sorted APR array.  Since all directories get written to and read
from the cache, it is simply cheaper to use a similar container
outside the cache as we do in the cache serialized format.

To minimize parsing overhead, we write the directory entries in sorted
order to disk and skip sorting them in the reading code when we discover
that they are already properly ordered.  This is not only fully backward
compatible but also solves the hash ordering problem when deltifying
directories.

* subversion/include/private/svn_subr_private.h
  (svn_hash__entry_t,
   svn_hash__read_entry): New private API factored out from svn_hash_read2.
                          This allows us to read hashes as well as arrays
                          with little extra code.

* subversion/libsvn_subr/hash.c
  (svn_hash__read_entry): Implement using code taken from ...
  (hash_read): ... this function which is now much simpler.

* subversion/libsvn_fs_fs/cached_data.h
  (svn_fs_fs__rep_contents_dir): Return an array instead of a hash.
  (svn_fs_fs__find_dir_entry): New utility function since we can't use
                               hash lookup anymore on directories.

* subversion/libsvn_fs_fs/cached_data.c
  (sorted,
   compare_dirents,
   compare_dirent_name): New utility functions for sorting directories
                         and looking entries up.
  (read_dir_entries): New function to fully read (one pass) directories
                      and entries the data directly into svn_fs_dirent_t.
  (parse_dir_entries): Superseded by the above.
  (get_dir_contents): Call the new reader function and return an array
                      instead of a hash.
  (svn_fs_fs__rep_contents_dir): Update and simplify as no second parser
                                 stage is needed anymore.
  (svn_fs_fs__find_dir_entry): Implement.
  (svn_fs_fs__rep_contents_dir_entry): Update dir entry lookup.

* subversion/libsvn_fs_fs/transaction.c
  (unparse_dir_entry): Write a whole dirent directly to STREAM instead of
                       producing some intermediate string.
  (unparse_dir_entries): Write a whole directory to STREAM (implicitly in
                         sorted, stable order)
  (svn_fs_fs__set_entry): Simplify adapting to the new unparse_* code.
  (collection_writer_t,
   write_hash_to_stream,
   write_directory_to_stream): Callback functions that allow all container
                               types (hash, array, possibly others) to be
                               written in a uniform fashon.
  (write_hash_baton,
   write_hash_handler,
   write_hash_rep,
   write_hash_delta_rep): Rename to ...
  (write_container_baton,
   write_container_handler,
   write_container_rep,
   write_container_delta_rep): ... these and update the rep writers to
                               use the new collection_writer_t callback.
  (write_final_rev): Update caller and simplify code as the directory
                     order is already stable now.

* subversion/libsvn_fs_fs/temp_serializer.h
  (svn_fs_fs__serialize_dir_entries,
   svn_fs_fs__deserialize_dir_entries): Update docstrings to reflect the
                                        new directory container type.

* subversion/libsvn_fs_fs/temp_serializer.c
  (hash_data_t): Rename to ...
  (dir_data_t): ... this without any other change since the in-cache
                representation format remains unchanged.
  (compare_dirent_id_names): Drop obsolete function since the array
                             is already sorted.
  (serialize_dir,
   deserialize_dir): Switch from de-/serializing hashes to processing
                     already correctly ordered arrays.
  (return_serialized_dir_context,
   svn_fs_fs__serialize_dir_entries,
   svn_fs_fs__deserialize_dir_entries,
   svn_fs_fs__extract_dir_entry): Purely syntactically adapt to renames
                                  and changed types.
  (slowly_replace_dir_entry): Updating a sorted array is harder than
                              updating a hash.  Explicitly code the
                              different use-cases.
  (svn_fs_fs__replace_dir_entry): Purely syntactically adapt to renames
                                  and changed types.

* subversion/libsvn_fs_fs/dag.h
  (svn_fs_fs__dag_dir_entries): Return an array instead of a hash.

* subversion/libsvn_fs_fs/dag.c
  (svn_fs_fs__dag_dir_entries): Update and simplify as iteration over
                                arrays is straight-forward.

* subversion/libsvn_fs_fs/tree.c
  (merge): Keep the basic principle but instead of dir hash lookups
           use quasi-linear array scans; see svn_sort__array_lookup.
           Don't modify any one the directory arrays as that would
           be O(n^2).
  (fs_dir_entries): Convert the internal dir representation into the
                    FS API level hash.
  (crawl_directory_dag_for_mergeinfo,
   verify_node): Iterate over directory arrays instead of hashes.

Modified:
    subversion/trunk/subversion/include/private/svn_subr_private.h
    subversion/trunk/subversion/libsvn_fs_fs/cached_data.c
    subversion/trunk/subversion/libsvn_fs_fs/cached_data.h
    subversion/trunk/subversion/libsvn_fs_fs/dag.c
    subversion/trunk/subversion/libsvn_fs_fs/dag.h
    subversion/trunk/subversion/libsvn_fs_fs/temp_serializer.c
    subversion/trunk/subversion/libsvn_fs_fs/temp_serializer.h
    subversion/trunk/subversion/libsvn_fs_fs/transaction.c
    subversion/trunk/subversion/libsvn_fs_fs/tree.c
    subversion/trunk/subversion/libsvn_subr/hash.c

Modified: subversion/trunk/subversion/include/private/svn_subr_private.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/include/private/svn_subr_private.h?rev=1554711&r1=1554710&r2=1554711&view=diff
==============================================================================
--- subversion/trunk/subversion/include/private/svn_subr_private.h (original)
+++ subversion/trunk/subversion/include/private/svn_subr_private.h Thu Jan  2 00:24:30 2014
@@ -356,6 +356,54 @@ svn_hash__make(apr_pool_t *pool);
 
 /** @} */
 
+/**
+ * @defgroup svn_hash_read Reading serialized hash tables
+ * @{
+ */
+
+/** Struct that represents a key value pair read from a serialized hash
+ * representation.  There are special cases that can also be represented:
+ * a #NULL @a key signifies the end of the hash, a #NULL @a val for non-
+ * NULL keys is only possible in incremental mode describes a deletion.
+ *
+ * @since New in 1.9.
+ */
+typedef struct svn_hash__entry_t
+{
+  /** 0-terminated Key.  #NULL if this contains no data at all because we
+   * encountered the end of the hash. */
+  char *key;
+
+  /** Length of @a key.  Must be 0 if @a key is #NULL. */
+  apr_size_t keylen;
+
+  /** 0-terminated value stored with the key.  If this is #NULL for a
+   * non-NULL @a key, then this means that the key shall be removed from
+   * the hash (only used in incremental mode).  Must be #NULL if @a key is
+   * #NULL. */
+  char *val;
+
+  /** Length of @a val.  Must be 0 if @a val is #NULL. */
+  apr_size_t vallen;
+} svn_hash__entry_t;
+
+/** Reads a single key-value pair from @a stream and returns it in the
+ * caller-provided @a *entry (members don't need to be pre-initialized).
+ * @a pool is used to allocate members of @a *entry and for tempoaries.
+ * 
+ * @see #svn_hash_read2 for more details.
+ *
+ * @since New in 1.9.
+ */
+svn_error_t *
+svn_hash__read_entry(svn_hash__entry_t *entry,
+                     svn_stream_t *stream,
+                     const char *terminator,
+                     svn_boolean_t incremental,
+                     apr_pool_t *pool);
+
+/** @} */
+
 /** @} */
 
 

Modified: subversion/trunk/subversion/libsvn_fs_fs/cached_data.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_fs/cached_data.c?rev=1554711&r1=1554710&r2=1554711&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_fs/cached_data.c (original)
+++ subversion/trunk/subversion/libsvn_fs_fs/cached_data.c Thu Jan  2 00:24:30 2014
@@ -26,7 +26,9 @@
 
 #include "svn_hash.h"
 #include "svn_ctype.h"
+#include "svn_sorts.h"
 #include "private/svn_io_private.h"
+#include "private/svn_subr_private.h"
 #include "private/svn_temp_serializer.h"
 
 #include "fs_fs.h"
@@ -1866,28 +1868,173 @@ svn_fs_fs__get_file_delta_stream(svn_txd
   return SVN_NO_ERROR;
 }
 
+/* Return TRUE when all svn_fs_dirent_t* in ENTRIES are already sorted
+   by their respective name. */
+static svn_boolean_t
+sorted(apr_array_header_t *entries)
+{
+  int i;
+
+  const svn_fs_dirent_t * const *dirents = (const void *)entries->elts;
+  for (i = 0; i < entries->nelts-1; ++i)
+    if (strcmp(dirents[i]->name, dirents[i+1]->name) > 0)
+      return FALSE;
+
+  return TRUE;
+}
+
+/* Compare the names of the two dirents given in **A and **B. */
+static int
+compare_dirents(const void *a, const void *b)
+{
+  const svn_fs_dirent_t *lhs = *((const svn_fs_dirent_t * const *) a);
+  const svn_fs_dirent_t *rhs = *((const svn_fs_dirent_t * const *) b);
+
+  return strcmp(lhs->name, rhs->name);
+}
+
+/* Compare the name of the dirents given in **A with the C string in *B. */
+static int
+compare_dirent_name(const void *a, const void *b)
+{
+  const svn_fs_dirent_t *lhs = *((const svn_fs_dirent_t * const *) a);
+  const char *rhs = b;
+
+  return strcmp(lhs->name, rhs);
+}
+
+/* Into ENTRIES, read all directories entries from the key-value text in
+ * STREAM.  If INCREMENTAL is TRUE, read until the end of the STREAM and
+ * update the data.  ID is provided for nicer error messages.
+ */
+static svn_error_t *
+read_dir_entries(apr_array_header_t *entries,
+                 svn_stream_t *stream,
+                 svn_boolean_t incremental,
+                 const svn_fs_id_t *id,
+                 apr_pool_t *result_pool,
+                 apr_pool_t *scratch_pool)
+{
+  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+  apr_hash_t *hash = incremental ? svn_hash__make(scratch_pool) : NULL;
+  const char *terminator = SVN_HASH_TERMINATOR;
+
+  /* Read until the terminator (non-incremental) or the end of STREAM
+     (incremental mode).  In the latter mode, we use a temporary HASH
+     to make updating and removing entries cheaper. */
+  while (1)
+    {
+      svn_hash__entry_t entry;
+      svn_fs_dirent_t *dirent;
+      char *str;
+
+      svn_pool_clear(iterpool);
+      SVN_ERR(svn_hash__read_entry(&entry, stream, terminator,
+                                   incremental, iterpool));
+
+      /* End of directory? */
+      if (entry.key == NULL)
+        {
+          /* In incremental mode, we skip the terminator and read the
+             increments following it until the end of the stream. */
+          if (incremental && terminator)
+            terminator = NULL;
+          else
+            break;
+        }
+
+      /* Deleted entry? */
+      if (entry.val == NULL)
+        {
+          /* We must be in incremental mode */
+          assert(hash);
+          apr_hash_set(hash, entry.key, entry.keylen, NULL);
+          continue;
+        }
+
+      /* Add a new directory entry. */
+      dirent = apr_pcalloc(result_pool, sizeof(*dirent));
+      dirent->name = apr_pstrmemdup(result_pool, entry.key, entry.keylen);
+
+      str = svn_cstring_tokenize(" ", &entry.val);
+      if (str == NULL)
+        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                           _("Directory entry corrupt in '%s'"),
+                           svn_fs_fs__id_unparse(id, scratch_pool)->data);
+
+      if (strcmp(str, SVN_FS_FS__KIND_FILE) == 0)
+        {
+          dirent->kind = svn_node_file;
+        }
+      else if (strcmp(str, SVN_FS_FS__KIND_DIR) == 0)
+        {
+          dirent->kind = svn_node_dir;
+        }
+      else
+        {
+          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                           _("Directory entry corrupt in '%s'"),
+                           svn_fs_fs__id_unparse(id, scratch_pool)->data);
+        }
+
+      str = svn_cstring_tokenize(" ", &entry.val);
+      if (str == NULL)
+        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                           _("Directory entry corrupt in '%s'"),
+                           svn_fs_fs__id_unparse(id, scratch_pool)->data);
+
+      dirent->id = svn_fs_fs__id_parse(str, strlen(str), result_pool);
+
+      /* In incremental mode, update the hash; otherwise, write to the
+       * final array. */
+      if (incremental)
+        apr_hash_set(hash, entry.key, entry.keylen, dirent);
+      else
+        APR_ARRAY_PUSH(entries, svn_fs_dirent_t *) = dirent;
+    }
+
+  /* Convert container to a sorted array. */
+  if (incremental)
+    {
+      apr_hash_index_t *hi;
+      for (hi = apr_hash_first(iterpool, hash); hi; hi = apr_hash_next(hi))
+        APR_ARRAY_PUSH(entries, svn_fs_dirent_t *)
+          = svn__apr_hash_index_val(hi);
+    }
+
+  if (!sorted(entries))
+    qsort(entries->elts, entries->nelts, entries->elt_size, compare_dirents);
+
+  svn_pool_destroy(iterpool);
+
+  return SVN_NO_ERROR;
+}
+
 /* Fetch the contents of a directory into ENTRIES.  Values are stored
    as filename to string mappings; further conversion is necessary to
    convert them into svn_fs_dirent_t values. */
 static svn_error_t *
-get_dir_contents(apr_hash_t *entries,
+get_dir_contents(apr_array_header_t **entries,
                  svn_fs_t *fs,
                  node_revision_t *noderev,
-                 apr_pool_t *pool)
+                 apr_pool_t *result_pool,
+                 apr_pool_t *scratch_pool)
 {
   svn_stream_t *contents;
 
+  *entries = apr_array_make(result_pool, 16, sizeof(svn_fs_dirent_t *));
   if (noderev->data_rep && svn_fs_fs__id_txn_used(&noderev->data_rep->txn_id))
     {
       const char *filename
-        = svn_fs_fs__path_txn_node_children(fs, noderev->id, pool);
+        = svn_fs_fs__path_txn_node_children(fs, noderev->id, scratch_pool);
 
       /* The representation is mutable.  Read the old directory
          contents from the mutable children file, followed by the
          changes we've made in this transaction. */
-      SVN_ERR(svn_stream_open_readonly(&contents, filename, pool, pool));
-      SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
-      SVN_ERR(svn_hash_read_incremental(entries, contents, NULL, pool));
+      SVN_ERR(svn_stream_open_readonly(&contents, filename, scratch_pool,
+                                       scratch_pool));
+      SVN_ERR(read_dir_entries(*entries, contents, TRUE,  noderev->id,
+                               result_pool, scratch_pool));
       SVN_ERR(svn_stream_close(contents));
     }
   else if (noderev->data_rep)
@@ -1896,7 +2043,7 @@ get_dir_contents(apr_hash_t *entries,
        * Also undeltify content before parsing it. Otherwise, we could only
        * parse it byte-by-byte.
        */
-      apr_pool_t *text_pool = svn_pool_create(pool);
+      apr_pool_t *text_pool = svn_pool_create(scratch_pool);
       apr_size_t len = noderev->data_rep->expanded_size
                      ? (apr_size_t)noderev->data_rep->expanded_size
                      : (apr_size_t)noderev->data_rep->size;
@@ -1910,7 +2057,8 @@ get_dir_contents(apr_hash_t *entries,
 
       /* de-serialize hash */
       contents = svn_stream_from_stringbuf(text, text_pool);
-      SVN_ERR(svn_hash_read2(entries, contents, SVN_HASH_TERMINATOR, pool));
+      SVN_ERR(read_dir_entries(*entries, contents, FALSE,  noderev->id,
+                               result_pool, scratch_pool));
 
       svn_pool_destroy(text_pool);
     }
@@ -1919,66 +2067,6 @@ get_dir_contents(apr_hash_t *entries,
 }
 
 
-/* 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.  Use ID to generate more helpful error messages.
-   Perform allocations in POOL. */
-static svn_error_t *
-parse_dir_entries(apr_hash_t **entries_p,
-                  apr_hash_t *str_entries,
-                  const svn_fs_id_t *id,
-                  apr_pool_t *pool)
-{
-  apr_hash_index_t *hi;
-
-  *entries_p = apr_hash_make(pool);
-
-  /* Translate the string dir entries into real entries. */
-  for (hi = apr_hash_first(pool, str_entries); hi; hi = apr_hash_next(hi))
-    {
-      const char *name = svn__apr_hash_index_key(hi);
-      svn_string_t *str_val = svn__apr_hash_index_val(hi);
-      char *str, *last_str;
-      svn_fs_dirent_t *dirent = apr_pcalloc(pool, sizeof(*dirent));
-
-      last_str = apr_pstrdup(pool, str_val->data);
-      dirent->name = apr_pstrdup(pool, name);
-
-      str = svn_cstring_tokenize(" ", &last_str);
-      if (str == NULL)
-        return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                                 _("Directory entry corrupt in '%s'"),
-                                 svn_fs_fs__id_unparse(id, pool)->data);
-
-      if (strcmp(str, SVN_FS_FS__KIND_FILE) == 0)
-        {
-          dirent->kind = svn_node_file;
-        }
-      else if (strcmp(str, SVN_FS_FS__KIND_DIR) == 0)
-        {
-          dirent->kind = svn_node_dir;
-        }
-      else
-        {
-          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                                   _("Directory entry corrupt in '%s'"),
-                                   svn_fs_fs__id_unparse(id, pool)->data);
-        }
-
-      str = svn_cstring_tokenize(" ", &last_str);
-      if (str == NULL)
-          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                                   _("Directory entry corrupt in '%s'"),
-                                   svn_fs_fs__id_unparse(id, pool)->data);
-
-      dirent->id = svn_fs_fs__id_parse(str, strlen(str), pool);
-
-      svn_hash_sets(*entries_p, dirent->name, dirent);
-    }
-
-  return SVN_NO_ERROR;
-}
-
 /* Return the cache object in FS responsible to storing the directory the
  * NODEREV plus the corresponding *KEY.  If no cache exists, return NULL.
  * PAIR_KEY must point to some key struct, which does not need to be
@@ -2019,7 +2107,7 @@ locate_dir_cache(svn_fs_t *fs,
 }
 
 svn_error_t *
-svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
+svn_fs_fs__rep_contents_dir(apr_array_header_t **entries_p,
                             svn_fs_t *fs,
                             node_revision_t *noderev,
                             apr_pool_t *result_pool,
@@ -2027,7 +2115,6 @@ svn_fs_fs__rep_contents_dir(apr_hash_t *
 {
   pair_cache_key_t pair_key = { 0 };
   const void *key;
-  apr_hash_t *unparsed_entries, *parsed_entries;
 
   /* find the cache we may use */
   svn_cache__t *cache = locate_dir_cache(fs, &key, &pair_key, noderev,
@@ -2042,20 +2129,27 @@ svn_fs_fs__rep_contents_dir(apr_hash_t *
         return SVN_NO_ERROR;
     }
 
-  /* Read in the directory hash. */
-  unparsed_entries = apr_hash_make(scratch_pool);
-  SVN_ERR(get_dir_contents(unparsed_entries, fs, noderev, scratch_pool));
-  SVN_ERR(parse_dir_entries(&parsed_entries, unparsed_entries,
-                            noderev->id, result_pool));
+  /* Read in the directory contents. */
+  SVN_ERR(get_dir_contents(entries_p, fs, noderev, result_pool,
+                           scratch_pool));
 
   /* Update the cache, if we are to use one. */
   if (cache)
-    SVN_ERR(svn_cache__set(cache, key, parsed_entries, scratch_pool));
+    SVN_ERR(svn_cache__set(cache, key, *entries_p, scratch_pool));
 
-  *entries_p = parsed_entries;
   return SVN_NO_ERROR;
 }
 
+svn_fs_dirent_t *
+svn_fs_fs__find_dir_entry(apr_array_header_t *entries,
+                          const char *name,
+                          int *hint)
+{
+  svn_fs_dirent_t **result
+    = svn_sort__array_lookup(entries, name, hint, compare_dirent_name);
+  return result ? *result : NULL;
+}
+
 svn_error_t *
 svn_fs_fs__rep_contents_dir_entry(svn_fs_dirent_t **dirent,
                                   svn_fs_t *fs,
@@ -2086,7 +2180,7 @@ svn_fs_fs__rep_contents_dir_entry(svn_fs
   /* fetch data from disk if we did not find it in the cache */
   if (! found)
     {
-      apr_hash_t *entries;
+      apr_array_header_t *entries;
       svn_fs_dirent_t *entry;
       svn_fs_dirent_t *entry_copy = NULL;
 
@@ -2096,8 +2190,8 @@ svn_fs_fs__rep_contents_dir_entry(svn_fs
                                           scratch_pool, scratch_pool));
 
       /* find desired entry and return a copy in POOL, if found */
-      entry = svn_hash_gets(entries, name);
-      if (entry != NULL)
+      entry = svn_fs_fs__find_dir_entry(entries, name, NULL);
+      if (entry)
         {
           entry_copy = apr_palloc(result_pool, sizeof(*entry_copy));
           entry_copy->name = apr_pstrdup(result_pool, entry->name);

Modified: subversion/trunk/subversion/libsvn_fs_fs/cached_data.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_fs/cached_data.h?rev=1554711&r1=1554710&r2=1554711&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_fs/cached_data.h (original)
+++ subversion/trunk/subversion/libsvn_fs_fs/cached_data.h Thu Jan  2 00:24:30 2014
@@ -98,17 +98,26 @@ svn_fs_fs__get_file_delta_stream(svn_txd
                                  node_revision_t *target,
                                  apr_pool_t *pool);
 
-/* 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 RESULT_POOL;
-   SCRATCH_POOL used for temporary allocations. */
+/* Set *ENTRIES to an apr_array_header_t of dirent structs that contain
+   the directory entries of node-revision NODEREV in filesystem FS.  The
+   returned table is allocated in RESULT_POOL and entries are sorted
+   lexicographically.  SCRATCH_POOL is used for temporary allocations. */
 svn_error_t *
-svn_fs_fs__rep_contents_dir(apr_hash_t **entries_p,
+svn_fs_fs__rep_contents_dir(apr_array_header_t **entries_p,
                             svn_fs_t *fs,
                             node_revision_t *noderev,
                             apr_pool_t *result_pool,
                             apr_pool_t *scratch_pool);
 
+/* Return the directory entry from ENTRIES that matches NAME.  If no such
+   entry exists, return NULL.  If HINT is not NULL, set *HINT to the array
+   index of the entry returned.  Successive calls in a linear scan scenario
+   will be faster called with the same HINT variable. */
+svn_fs_dirent_t *
+svn_fs_fs__find_dir_entry(apr_array_header_t *entries,
+                          const char *name,
+                          int *hint);
+
 /* Set *DIRENT to the entry identified by NAME in the directory given
    by NODEREV in filesystem FS.  If no such entry exits, *DIRENT will
    be NULL. The returned object is allocated in RESULT_POOL; SCRATCH_POOL

Modified: subversion/trunk/subversion/libsvn_fs_fs/dag.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_fs/dag.c?rev=1554711&r1=1554710&r2=1554711&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_fs/dag.c (original)
+++ subversion/trunk/subversion/libsvn_fs_fs/dag.c Thu Jan  2 00:24:30 2014
@@ -415,7 +415,7 @@ make_entry(dag_node_t **child_p,
 
 
 svn_error_t *
-svn_fs_fs__dag_dir_entries(apr_hash_t **entries,
+svn_fs_fs__dag_dir_entries(apr_array_header_t **entries,
                            dag_node_t *node,
                            apr_pool_t *pool)
 {
@@ -857,23 +857,16 @@ svn_fs_fs__dag_delete_if_mutable(svn_fs_
   /* Else it's mutable.  Recurse on directories... */
   if (node->kind == svn_node_dir)
     {
-      apr_hash_t *entries;
-      apr_hash_index_t *hi;
+      apr_array_header_t *entries;
+      int i;
 
-      /* Loop over hash entries */
+      /* Loop over directory entries */
       SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, node, pool));
       if (entries)
-        {
-          for (hi = apr_hash_first(pool, entries);
-               hi;
-               hi = apr_hash_next(hi))
-            {
-              svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
-
-              SVN_ERR(svn_fs_fs__dag_delete_if_mutable(fs, dirent->id,
-                                                       pool));
-            }
-        }
+        for (i = 0; i < entries->nelts; ++i)
+          SVN_ERR(svn_fs_fs__dag_delete_if_mutable(fs,
+                        APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *)->id,
+                        pool));
     }
 
   /* ... then delete the node itself, after deleting any mutable

Modified: subversion/trunk/subversion/libsvn_fs_fs/dag.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_fs/dag.h?rev=1554711&r1=1554710&r2=1554711&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_fs/dag.h (original)
+++ subversion/trunk/subversion/libsvn_fs_fs/dag.h Thu Jan  2 00:24:30 2014
@@ -264,11 +264,10 @@ svn_fs_fs__dag_open(dag_node_t **child_p
                     apr_pool_t *scratch_pool);
 
 
-/* Set *ENTRIES_P to a hash table of NODE's entries.  The keys of the
-   table are entry names, and the values are svn_fs_dirent_t's.  The
-   returned table (and its keys and values) is allocated in POOL,
-   which is also used for temporary allocations. */
-svn_error_t *svn_fs_fs__dag_dir_entries(apr_hash_t **entries_p,
+/* Set *ENTRIES_P to an array of NODE's entries, sorted by entry names,
+   and the values are svn_fs_dirent_t's.  The returned table (and elements)
+   is allocated in POOL, which is also used for temporary allocations. */
+svn_error_t *svn_fs_fs__dag_dir_entries(apr_array_header_t **entries_p,
                                         dag_node_t *node,
                                         apr_pool_t *pool);
 

Modified: subversion/trunk/subversion/libsvn_fs_fs/temp_serializer.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_fs/temp_serializer.c?rev=1554711&r1=1554710&r2=1554711&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_fs/temp_serializer.c (original)
+++ subversion/trunk/subversion/libsvn_fs_fs/temp_serializer.c Thu Jan  2 00:24:30 2014
@@ -25,16 +25,17 @@
 #include "svn_private_config.h"
 #include "svn_pools.h"
 #include "svn_hash.h"
-
-#include "id.h"
+#include "svn_sorts.h"
 #include "svn_fs.h"
 
 #include "private/svn_fs_util.h"
 #include "private/svn_temp_serializer.h"
 #include "private/svn_subr_private.h"
 
+#include "id.h"
 #include "temp_serializer.h"
 #include "low_level.h"
+#include "cached_data.h"
 
 /* Utility to encode a signed NUMBER into a variable-length sequence of
  * 8-bit chars in KEY_BUFFER and return the last writen position.
@@ -145,8 +146,8 @@ serialize_representation(svn_temp_serial
                                 sizeof(*rep));
 }
 
-/* auxilliary structure representing the content of a directory hash */
-typedef struct hash_data_t
+/* auxilliary structure representing the content of a directory array */
+typedef struct dir_data_t
 {
   /* number of entries in the directory */
   apr_size_t count;
@@ -169,14 +170,7 @@ typedef struct hash_data_t
   /* size of the serialized entries and don't be too wasteful
    * (needed since the entries are no longer in sequence) */
   apr_uint32_t *lengths;
-} hash_data_t;
-
-static int
-compare_dirent_id_names(const void *lhs, const void *rhs)
-{
-  return strcmp((*(const svn_fs_dirent_t *const *)lhs)->name,
-                (*(const svn_fs_dirent_t *const *)rhs)->name);
-}
+} dir_data_t;
 
 /* Utility function to serialize the *ENTRY_P into a the given
  * serialization CONTEXT. Return the serialized size of the
@@ -207,91 +201,85 @@ serialize_dir_entry(svn_temp_serializer_
  * context to be returned. Allocation will be made form POOL.
  */
 static svn_temp_serializer__context_t *
-serialize_dir(apr_hash_t *entries, apr_pool_t *pool)
+serialize_dir(apr_array_header_t *entries, apr_pool_t *pool)
 {
-  hash_data_t hash_data;
-  apr_hash_index_t *hi;
+  dir_data_t dir_data;
   apr_size_t i = 0;
   svn_temp_serializer__context_t *context;
 
   /* calculate sizes */
-  apr_size_t count = apr_hash_count(entries);
+  apr_size_t count = entries->nelts;
   apr_size_t over_provision = 2 + count / 4;
   apr_size_t entries_len = (count + over_provision) * sizeof(svn_fs_dirent_t*);
   apr_size_t lengths_len = (count + over_provision) * sizeof(apr_uint32_t);
 
   /* copy the hash entries to an auxilliary struct of known layout */
-  hash_data.count = count;
-  hash_data.over_provision = over_provision;
-  hash_data.operations = 0;
-  hash_data.entries = apr_palloc(pool, entries_len);
-  hash_data.lengths = apr_palloc(pool, lengths_len);
-
-  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi), ++i)
-    hash_data.entries[i] = svn__apr_hash_index_val(hi);
-
-  /* sort entry index by ID name */
-  qsort(hash_data.entries,
-        count,
-        sizeof(*hash_data.entries),
-        compare_dirent_id_names);
+  dir_data.count = count;
+  dir_data.over_provision = over_provision;
+  dir_data.operations = 0;
+  dir_data.entries = apr_palloc(pool, entries_len);
+  dir_data.lengths = apr_palloc(pool, lengths_len);
+
+  for (i = 0; i < count; ++i)
+    dir_data.entries[i] = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
 
   /* Serialize that aux. structure into a new one. Also, provide a good
    * estimate for the size of the buffer that we will need. */
-  context = svn_temp_serializer__init(&hash_data,
-                                      sizeof(hash_data),
+  context = svn_temp_serializer__init(&dir_data,
+                                      sizeof(dir_data),
                                       50 + count * 200 + entries_len,
                                       pool);
 
   /* serialize entries references */
   svn_temp_serializer__push(context,
-                            (const void * const *)&hash_data.entries,
+                            (const void * const *)&dir_data.entries,
                             entries_len);
 
   /* serialize the individual entries and their sub-structures */
   for (i = 0; i < count; ++i)
     serialize_dir_entry(context,
-                        &hash_data.entries[i],
-                        &hash_data.lengths[i]);
+                        &dir_data.entries[i],
+                        &dir_data.lengths[i]);
 
   svn_temp_serializer__pop(context);
 
   /* serialize entries references */
   svn_temp_serializer__push(context,
-                            (const void * const *)&hash_data.lengths,
+                            (const void * const *)&dir_data.lengths,
                             lengths_len);
 
   return context;
 }
 
-/* Utility function to reconstruct a dir entries hash from serialized data
- * in BUFFER and HASH_DATA. Allocation will be made form POOL.
+/* Utility function to reconstruct a dir entries array from serialized data
+ * in BUFFER and DIR_DATA. Allocation will be made form POOL.
  */
-static apr_hash_t *
-deserialize_dir(void *buffer, hash_data_t *hash_data, apr_pool_t *pool)
+static apr_array_header_t *
+deserialize_dir(void *buffer, dir_data_t *dir_data, apr_pool_t *pool)
 {
-  apr_hash_t *result = svn_hash__make(pool);
+  apr_array_header_t *result
+    = apr_array_make(pool, dir_data->count, sizeof(svn_fs_dirent_t *));
   apr_size_t i;
   apr_size_t count;
   svn_fs_dirent_t *entry;
   svn_fs_dirent_t **entries;
 
   /* resolve the reference to the entries array */
-  svn_temp_deserializer__resolve(buffer, (void **)&hash_data->entries);
-  entries = hash_data->entries;
+  svn_temp_deserializer__resolve(buffer, (void **)&dir_data->entries);
+  entries = dir_data->entries;
 
   /* fixup the references within each entry and add it to the hash */
-  for (i = 0, count = hash_data->count; i < count; ++i)
+  for (i = 0, count = dir_data->count; i < count; ++i)
     {
       svn_temp_deserializer__resolve(entries, (void **)&entries[i]);
-      entry = hash_data->entries[i];
+      entry = dir_data->entries[i];
 
       /* pointer fixup */
       svn_temp_deserializer__resolve(entry, (void **)&entry->name);
       svn_fs_fs__id_deserialize(entry, (svn_fs_id_t **)&entry->id);
 
       /* add the entry to the hash */
-      svn_hash_sets(result, entry->name, entry);
+      APR_ARRAY_PUSH(result, svn_fs_dirent_t *) = entry;
     }
 
   /* return the now complete hash */
@@ -715,7 +703,7 @@ return_serialized_dir_context(svn_temp_s
 
   *data = serialized->data;
   *data_len = serialized->blocksize;
-  ((hash_data_t *)serialized->data)->len = serialized->len;
+  ((dir_data_t *)serialized->data)->len = serialized->len;
 
   return SVN_NO_ERROR;
 }
@@ -726,7 +714,7 @@ svn_fs_fs__serialize_dir_entries(void **
                                  void *in,
                                  apr_pool_t *pool)
 {
-  apr_hash_t *dir = in;
+  apr_array_header_t *dir = in;
 
   /* serialize the dir content into a new serialization context
    * and return the serialized data */
@@ -742,10 +730,10 @@ svn_fs_fs__deserialize_dir_entries(void 
                                    apr_pool_t *pool)
 {
   /* Copy the _full_ buffer as it also contains the sub-structures. */
-  hash_data_t *hash_data = (hash_data_t *)data;
+  dir_data_t *dir_data = (dir_data_t *)data;
 
   /* reconstruct the hash from the serialized data */
-  *out = deserialize_dir(hash_data, hash_data, pool);
+  *out = deserialize_dir(dir_data, dir_data, pool);
 
   return SVN_NO_ERROR;
 }
@@ -818,22 +806,22 @@ svn_fs_fs__extract_dir_entry(void **out,
                              void *baton,
                              apr_pool_t *pool)
 {
-  const hash_data_t *hash_data = data;
+  const dir_data_t *dir_data = data;
   const char* name = baton;
   svn_boolean_t found;
 
   /* resolve the reference to the entries array */
   const svn_fs_dirent_t * const *entries =
-    svn_temp_deserializer__ptr(data, (const void *const *)&hash_data->entries);
+    svn_temp_deserializer__ptr(data, (const void *const *)&dir_data->entries);
 
   /* resolve the reference to the lengths array */
   const apr_uint32_t *lengths =
-    svn_temp_deserializer__ptr(data, (const void *const *)&hash_data->lengths);
+    svn_temp_deserializer__ptr(data, (const void *const *)&dir_data->lengths);
 
   /* binary search for the desired entry by name */
   apr_size_t pos = find_entry((svn_fs_dirent_t **)entries,
                               name,
-                              hash_data->count,
+                              dir_data->count,
                               &found);
 
   /* de-serialize that entry or return NULL, if no match has been found */
@@ -872,14 +860,33 @@ slowly_replace_dir_entry(void **data,
                          apr_pool_t *pool)
 {
   replace_baton_t *replace_baton = (replace_baton_t *)baton;
-  hash_data_t *hash_data = (hash_data_t *)*data;
-  apr_hash_t *dir;
+  dir_data_t *dir_data = (dir_data_t *)*data;
+  apr_array_header_t *dir;
+  int idx = -1;
+  svn_fs_dirent_t *entry;
 
   SVN_ERR(svn_fs_fs__deserialize_dir_entries((void **)&dir,
                                              *data,
-                                             hash_data->len,
+                                             dir_data->len,
                                              pool));
-  svn_hash_sets(dir, replace_baton->name, replace_baton->new_entry);
+
+  entry = svn_fs_fs__find_dir_entry(dir, replace_baton->name, &idx);
+
+  /* Replacement or removal? */
+  if (replace_baton->new_entry)
+    {
+      /* Replace ENTRY with / insert the NEW_ENTRY */
+      if (entry)
+        APR_ARRAY_IDX(dir, idx, svn_fs_dirent_t *) = replace_baton->new_entry;
+      else
+        svn_sort__array_insert(dir, &replace_baton->new_entry, idx);
+    }
+  else
+    {
+      /* Remove the old ENTRY. */
+      if (entry)
+        svn_sort__array_delete(dir, idx, 1);
+    }
 
   return svn_fs_fs__serialize_dir_entries(data, data_len, dir, pool);
 }
@@ -891,7 +898,7 @@ svn_fs_fs__replace_dir_entry(void **data
                              apr_pool_t *pool)
 {
   replace_baton_t *replace_baton = (replace_baton_t *)baton;
-  hash_data_t *hash_data = (hash_data_t *)*data;
+  dir_data_t *dir_data = (dir_data_t *)*data;
   svn_boolean_t found;
   svn_fs_dirent_t **entries;
   apr_uint32_t *lengths;
@@ -901,23 +908,23 @@ svn_fs_fs__replace_dir_entry(void **data
   svn_temp_serializer__context_t *context;
 
   /* after quite a number of operations, let's re-pack everything.
-   * This is to limit the number of vasted space as we cannot overwrite
+   * This is to limit the number of wasted space as we cannot overwrite
    * existing data but must always append. */
-  if (hash_data->operations > 2 + hash_data->count / 4)
+  if (dir_data->operations > 2 + dir_data->count / 4)
     return slowly_replace_dir_entry(data, data_len, baton, pool);
 
   /* resolve the reference to the entries array */
   entries = (svn_fs_dirent_t **)
-    svn_temp_deserializer__ptr((const char *)hash_data,
-                               (const void *const *)&hash_data->entries);
+    svn_temp_deserializer__ptr((const char *)dir_data,
+                               (const void *const *)&dir_data->entries);
 
   /* resolve the reference to the lengths array */
   lengths = (apr_uint32_t *)
-    svn_temp_deserializer__ptr((const char *)hash_data,
-                               (const void *const *)&hash_data->lengths);
+    svn_temp_deserializer__ptr((const char *)dir_data,
+                               (const void *const *)&dir_data->lengths);
 
   /* binary search for the desired entry by name */
-  pos = find_entry(entries, replace_baton->name, hash_data->count, &found);
+  pos = find_entry(entries, replace_baton->name, dir_data->count, &found);
 
   /* handle entry removal (if found at all) */
   if (replace_baton->new_entry == NULL)
@@ -927,14 +934,14 @@ svn_fs_fs__replace_dir_entry(void **data
           /* remove reference to the entry from the index */
           memmove(&entries[pos],
                   &entries[pos + 1],
-                  sizeof(entries[pos]) * (hash_data->count - pos));
+                  sizeof(entries[pos]) * (dir_data->count - pos));
           memmove(&lengths[pos],
                   &lengths[pos + 1],
-                  sizeof(lengths[pos]) * (hash_data->count - pos));
+                  sizeof(lengths[pos]) * (dir_data->count - pos));
 
-          hash_data->count--;
-          hash_data->over_provision++;
-          hash_data->operations++;
+          dir_data->count--;
+          dir_data->over_provision++;
+          dir_data->operations++;
         }
 
       return SVN_NO_ERROR;
@@ -946,27 +953,27 @@ svn_fs_fs__replace_dir_entry(void **data
       /* fallback to slow operation if there is no place left to insert an
        * new entry to index. That will automatically give add some spare
        * entries ("overprovision"). */
-      if (hash_data->over_provision == 0)
+      if (dir_data->over_provision == 0)
         return slowly_replace_dir_entry(data, data_len, baton, pool);
 
       /* make entries[index] available for pointing to the new entry */
       memmove(&entries[pos + 1],
               &entries[pos],
-              sizeof(entries[pos]) * (hash_data->count - pos));
+              sizeof(entries[pos]) * (dir_data->count - pos));
       memmove(&lengths[pos + 1],
               &lengths[pos],
-              sizeof(lengths[pos]) * (hash_data->count - pos));
+              sizeof(lengths[pos]) * (dir_data->count - pos));
 
-      hash_data->count++;
-      hash_data->over_provision--;
-      hash_data->operations++;
+      dir_data->count++;
+      dir_data->over_provision--;
+      dir_data->operations++;
     }
 
   /* de-serialize the new entry */
   entries[pos] = replace_baton->new_entry;
-  context = svn_temp_serializer__init_append(hash_data,
+  context = svn_temp_serializer__init_append(dir_data,
                                              entries,
-                                             hash_data->len,
+                                             dir_data->len,
                                              *data_len,
                                              pool);
   serialize_dir_entry(context, &entries[pos], &length);
@@ -980,10 +987,10 @@ svn_fs_fs__replace_dir_entry(void **data
    * pointer may no longer point to the entry in that buffer. Therefore,
    * re-map it again and store the length value after that. */
 
-  hash_data = (hash_data_t *)*data;
+  dir_data = (dir_data_t *)*data;
   lengths = (apr_uint32_t *)
-    svn_temp_deserializer__ptr((const char *)hash_data,
-                               (const void *const *)&hash_data->lengths);
+    svn_temp_deserializer__ptr((const char *)dir_data,
+                               (const void *const *)&dir_data->lengths);
   lengths[pos] = length;
 
   return SVN_NO_ERROR;

Modified: subversion/trunk/subversion/libsvn_fs_fs/temp_serializer.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_fs/temp_serializer.h?rev=1554711&r1=1554710&r2=1554711&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_fs/temp_serializer.h (original)
+++ subversion/trunk/subversion/libsvn_fs_fs/temp_serializer.h Thu Jan  2 00:24:30 2014
@@ -159,7 +159,7 @@ svn_fs_fs__deserialize_node_revision(voi
                                      apr_pool_t *pool);
 
 /**
- * Implements #svn_cache__serialize_func_t for a directory contents hash
+ * Implements #svn_cache__serialize_func_t for a directory contents array
  */
 svn_error_t *
 svn_fs_fs__serialize_dir_entries(void **data,
@@ -168,7 +168,7 @@ svn_fs_fs__serialize_dir_entries(void **
                                  apr_pool_t *pool);
 
 /**
- * Implements #svn_cache__deserialize_func_t for a directory contents hash
+ * Implements #svn_cache__deserialize_func_t for a directory contents array
  */
 svn_error_t *
 svn_fs_fs__deserialize_dir_entries(void **out,

Modified: subversion/trunk/subversion/libsvn_fs_fs/transaction.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_fs/transaction.c?rev=1554711&r1=1554710&r2=1554711&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_fs/transaction.c (original)
+++ subversion/trunk/subversion/libsvn_fs_fs/transaction.c Thu Jan  2 00:24:30 2014
@@ -563,49 +563,45 @@ store_sha1_rep_mapping(svn_fs_t *fs,
   return SVN_NO_ERROR;
 }
 
-static const char *
-unparse_dir_entry(svn_node_kind_t kind, const svn_fs_id_t *id,
+static svn_error_t *
+unparse_dir_entry(svn_fs_dirent_t *dirent,
+                  svn_stream_t *stream,
                   apr_pool_t *pool)
 {
-  return apr_psprintf(pool, "%s %s",
-                      (kind == svn_node_file) ? SVN_FS_FS__KIND_FILE
-                                              : SVN_FS_FS__KIND_DIR,
-                      svn_fs_fs__id_unparse(id, pool)->data);
+  const char *val
+    = apr_psprintf(pool, "%s %s",
+                   (dirent->kind == svn_node_file) ? SVN_FS_FS__KIND_FILE
+                                                   : SVN_FS_FS__KIND_DIR,
+                   svn_fs_fs__id_unparse(dirent->id, pool)->data);
+
+  SVN_ERR(svn_stream_printf(stream, pool, "K %" APR_SIZE_T_FMT "\n%s\n"
+                            "V %" APR_SIZE_T_FMT "\n%s\n",
+                            strlen(dirent->name), dirent->name,
+                            strlen(val), val));
+  return SVN_NO_ERROR;
 }
 
-/* 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. */
+/* Write the directory given as array of dirent structs in ENTRIES to STREAM.
+   Perform temporary allocations in POOL. */
 static svn_error_t *
-unparse_dir_entries(apr_hash_t **str_entries_p,
-                    apr_hash_t *entries,
+unparse_dir_entries(apr_array_header_t *entries,
+                    svn_stream_t *stream,
                     apr_pool_t *pool)
 {
-  apr_hash_index_t *hi;
-
-  /* For now, we use a our own hash function to ensure that we get a
-   * (largely) stable order when serializing the data.  It also gives
-   * us some performance improvement.
-   *
-   * ### TODO ###
-   * Use some sorted or other fixed order data container.
-   */
-  *str_entries_p = svn_hash__make(pool);
-
-  for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+  apr_pool_t *iterpool = svn_pool_create(pool);
+  int i;
+  for (i = 0; i < entries->nelts; ++i)
     {
-      const void *key;
-      apr_ssize_t klen;
-      svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
-      const char *new_val;
-
-      apr_hash_this(hi, &key, &klen, NULL);
-      new_val = unparse_dir_entry(dirent->kind, dirent->id, pool);
-      apr_hash_set(*str_entries_p, key, klen,
-                   svn_string_create(new_val, pool));
+      svn_fs_dirent_t *dirent;
+
+      svn_pool_clear(iterpool);
+      dirent = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
+      SVN_ERR(unparse_dir_entry(dirent, stream, iterpool));
     }
 
+  SVN_ERR(svn_stream_printf(stream, pool, "%s\n", SVN_HASH_TERMINATOR));
+
+  svn_pool_destroy(iterpool);
   return SVN_NO_ERROR;
 }
 
@@ -1487,18 +1483,17 @@ svn_fs_fs__set_entry(svn_fs_t *fs,
 
   if (!rep || !is_txn_rep(rep))
     {
-      apr_hash_t *entries;
+      apr_array_header_t *entries;
 
       /* Before we can modify the directory, we need to dump its old
          contents into a mutable representation file. */
       SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, parent_noderev,
                                           subpool, subpool));
-      SVN_ERR(unparse_dir_entries(&entries, entries, subpool));
       SVN_ERR(svn_io_file_open(&file, filename,
                                APR_WRITE | APR_CREATE | APR_BUFFERED,
                                APR_OS_DEFAULT, pool));
       out = svn_stream_from_aprfile2(file, TRUE, pool);
-      SVN_ERR(svn_hash_write2(entries, out, SVN_HASH_TERMINATOR, subpool));
+      SVN_ERR(unparse_dir_entries(entries, out, subpool));
 
       svn_pool_clear(subpool);
 
@@ -1548,12 +1543,12 @@ svn_fs_fs__set_entry(svn_fs_t *fs,
   /* Append an incremental hash entry for the entry change. */
   if (id)
     {
-      const char *val = unparse_dir_entry(kind, id, subpool);
+      svn_fs_dirent_t entry;
+      entry.name = name;
+      entry.id = id;
+      entry.kind = kind;
 
-      SVN_ERR(svn_stream_printf(out, subpool, "K %" APR_SIZE_T_FMT "\n%s\n"
-                                "V %" APR_SIZE_T_FMT "\n%s\n",
-                                strlen(name), name,
-                                strlen(val), val));
+      SVN_ERR(unparse_dir_entry(&entry, out, subpool));
     }
   else
     {
@@ -2423,8 +2418,8 @@ get_next_revision_ids(apr_uint64_t *node
   return SVN_NO_ERROR;
 }
 
-/* This baton is used by the stream created for write_hash_rep. */
-struct write_hash_baton
+/* This baton is used by the stream created for write_container_rep. */
+struct write_container_baton
 {
   svn_stream_t *stream;
 
@@ -2434,15 +2429,15 @@ struct write_hash_baton
   svn_checksum_ctx_t *sha1_ctx;
 };
 
-/* The handler for the write_hash_rep stream.  BATON is a
-   write_hash_baton, DATA has the data to write and *LEN is the number
+/* The handler for the write_container_rep stream.  BATON is a
+   write_container_baton, DATA has the data to write and *LEN is the number
    of bytes to write. */
 static svn_error_t *
-write_hash_handler(void *baton,
-                   const char *data,
-                   apr_size_t *len)
+write_container_handler(void *baton,
+                        const char *data,
+                        apr_size_t *len)
 {
-  struct write_hash_baton *whb = baton;
+  struct write_container_baton *whb = baton;
 
   SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
   SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
@@ -2453,9 +2448,39 @@ write_hash_handler(void *baton,
   return SVN_NO_ERROR;
 }
 
-/* Write out the hash HASH as a text representation to file FILE.  In
-   the process, record position, the total size of the dump and MD5 as
-   well as SHA1 in REP.   Add the representation of type ITEM_TYPE to
+/* Callback function type.  Write the data provided by BATON into STREAM. */
+typedef svn_error_t *
+(* collection_writer_t)(svn_stream_t *stream, void *baton, apr_pool_t *pool);
+
+/* Implement collection_writer_t writing the C string->svn_string_t hash
+   given as BATON. */
+static svn_error_t *
+write_hash_to_stream(svn_stream_t *stream,
+                     void *baton,
+                     apr_pool_t *pool)
+{
+  apr_hash_t *hash = baton;
+  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
+
+  return SVN_NO_ERROR;
+}
+
+/* Implement collection_writer_t writing the svn_fs_dirent_t* array given
+   as BATON. */
+static svn_error_t *
+write_directory_to_stream(svn_stream_t *stream,
+                          void *baton,
+                          apr_pool_t *pool)
+{
+  apr_array_header_t *dir = baton;
+  SVN_ERR(unparse_dir_entries(dir, stream, pool));
+
+  return SVN_NO_ERROR;
+}
+
+/* Write out the COLLECTION as a text representation to file FILE using
+   WRITER.  In the process, record position, the total size of the dump and
+   MD5 as well as SHA1 in REP.   Add the representation of type ITEM_TYPE to
    the indexes if necessary.  If rep sharing has been enabled and REPS_HASH
    is not NULL, it will be used in addition to the on-disk cache to find
    earlier reps with the same content.  When such existing reps can be
@@ -2464,17 +2489,18 @@ write_hash_handler(void *baton,
    to determine whether to write to the proto-index files.
    Perform temporary allocations in POOL. */
 static svn_error_t *
-write_hash_rep(representation_t *rep,
-               apr_file_t *file,
-               apr_hash_t *hash,
-               svn_fs_t *fs,
-               apr_hash_t *reps_hash,
-               int item_type,
-               svn_revnum_t final_revision,
-               apr_pool_t *pool)
+write_container_rep(representation_t *rep,
+                    apr_file_t *file,
+                    void *collection,
+                    collection_writer_t writer,
+                    svn_fs_t *fs,
+                    apr_hash_t *reps_hash,
+                    int item_type,
+                    svn_revnum_t final_revision,
+                    apr_pool_t *pool)
 {
   svn_stream_t *stream;
-  struct write_hash_baton *whb;
+  struct write_container_baton *whb;
   svn_checksum_ctx_t *fnv1a_checksum_ctx;
   representation_t *old_rep;
   apr_off_t offset = 0;
@@ -2491,11 +2517,11 @@ write_hash_rep(representation_t *rep,
   whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
 
   stream = svn_stream_create(whb, pool);
-  svn_stream_set_write(stream, write_hash_handler);
+  svn_stream_set_write(stream, write_container_handler);
 
   SVN_ERR(svn_stream_puts(whb->stream, "PLAIN\n"));
 
-  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
+  SVN_ERR(writer(stream, collection, pool));
 
   /* Store the results. */
   SVN_ERR(digests_final(rep, whb->md5_ctx, whb->sha1_ctx, pool));
@@ -2543,28 +2569,31 @@ write_hash_rep(representation_t *rep,
   return SVN_NO_ERROR;
 }
 
-/* Write out the hash HASH pertaining to the NODEREV in FS as a deltified
-   text representation to file FILE.  In the process, record the total size
-   and the md5 digest in REP and add the representation of type ITEM_TYPE
-   to the indexes if necessary.  If rep sharing has been enabled and REPS_HASH
-   is not NULL, it will be used in addition to the on-disk cache to find
-   earlier reps with the same content.  When such existing reps can be found,
-   we will truncate the one just written from the file and return the existing
-   rep.  If ITEM_TYPE is IS_PROPS equals SVN_FS_FS__ITEM_TYPE_*_PROPS, assume
+/* Write out the COLLECTION pertaining to the NODEREV in FS as a deltified
+   text representation to file FILE using WRITER.  In the process, record the
+   total size and the md5 digest in REP and add the representation of type
+   ITEM_TYPE to the indexes if necessary.  If rep sharing has been enabled and
+   REPS_HASH is not NULL, it will be used in addition to the on-disk cache to
+   find earlier reps with the same content.  When such existing reps can be
+   found, we will truncate the one just written from the file and return the
+   existing rep.
+
+   If ITEM_TYPE is IS_PROPS equals SVN_FS_FS__ITEM_TYPE_*_PROPS, assume
    that we want to a props representation as the base for our delta.
    If FINAL_REVISION is not SVN_INVALID_REVNUM, use it to determine whether
    to write to the proto-index files.  Perform temporary allocations in POOL.
  */
 static svn_error_t *
-write_hash_delta_rep(representation_t *rep,
-                     apr_file_t *file,
-                     apr_hash_t *hash,
-                     svn_fs_t *fs,
-                     node_revision_t *noderev,
-                     apr_hash_t *reps_hash,
-                     int item_type,
-                     svn_revnum_t final_revision,
-                     apr_pool_t *pool)
+write_container_delta_rep(representation_t *rep,
+                          apr_file_t *file,
+                          void *collection,
+                          collection_writer_t writer,
+                          svn_fs_t *fs,
+                          node_revision_t *noderev,
+                          apr_hash_t *reps_hash,
+                          int item_type,
+                          svn_revnum_t final_revision,
+                          apr_pool_t *pool)
 {
   svn_txdelta_window_handler_t diff_wh;
   void *diff_whb;
@@ -2581,7 +2610,7 @@ write_hash_delta_rep(representation_t *r
   apr_off_t delta_start = 0;
   apr_off_t offset = 0;
 
-  struct write_hash_baton *whb;
+  struct write_container_baton *whb;
   fs_fs_data_t *ffd = fs->fsap_data;
   int diff_version = ffd->format >= SVN_FS_FS__MIN_SVNDIFF1_FORMAT ? 1 : 0;
   svn_boolean_t is_props = (item_type == SVN_FS_FS__ITEM_TYPE_FILE_PROPS)
@@ -2628,9 +2657,9 @@ write_hash_delta_rep(representation_t *r
 
   /* serialize the hash */
   stream = svn_stream_create(whb, pool);
-  svn_stream_set_write(stream, write_hash_handler);
+  svn_stream_set_write(stream, write_container_handler);
 
-  SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
+  SVN_ERR(writer(stream, collection, pool));
   SVN_ERR(svn_stream_close(whb->stream));
 
   /* Store the results. */
@@ -2826,8 +2855,7 @@ write_final_rev(const svn_fs_id_t **new_
   if (noderev->kind == svn_node_dir)
     {
       apr_pool_t *subpool;
-      apr_hash_t *entries, *str_entries;
-      apr_array_header_t *sorted_entries;
+      apr_array_header_t *entries;
       int i;
 
       /* This is a directory.  Write out all the children first. */
@@ -2835,16 +2863,10 @@ write_final_rev(const svn_fs_id_t **new_
 
       SVN_ERR(svn_fs_fs__rep_contents_dir(&entries, fs, noderev, pool,
                                           subpool));
-      /* For the sake of the repository administrator sort the entries
-         so that the final file is deterministic and repeatable,
-         however the rest of the FSFS code doesn't require any
-         particular order here. */
-      sorted_entries = svn_sort__hash(entries, svn_sort_compare_items_lexically,
-                                      pool);
-      for (i = 0; i < sorted_entries->nelts; ++i)
+      for (i = 0; i < entries->nelts; ++i)
         {
-          svn_fs_dirent_t *dirent = APR_ARRAY_IDX(sorted_entries, i,
-                                                  svn_sort__item_t).value;
+          svn_fs_dirent_t *dirent
+            = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
 
           svn_pool_clear(subpool);
           SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
@@ -2859,19 +2881,19 @@ write_final_rev(const svn_fs_id_t **new_
       if (noderev->data_rep && is_txn_rep(noderev->data_rep))
         {
           /* Write out the contents of this directory as a text rep. */
-          SVN_ERR(unparse_dir_entries(&str_entries, entries, pool));
-
           noderev->data_rep->revision = rev;
-
           if (ffd->deltify_directories)
-            SVN_ERR(write_hash_delta_rep(noderev->data_rep, file,
-                                         str_entries, fs, noderev, NULL,
-                                         SVN_FS_FS__ITEM_TYPE_DIR_REP,
-                                         rev, pool));
+            SVN_ERR(write_container_delta_rep(noderev->data_rep, file,
+                                              entries,
+                                              write_directory_to_stream,
+                                              fs, noderev, NULL,
+                                              SVN_FS_FS__ITEM_TYPE_DIR_REP,
+                                              rev, pool));
           else
-            SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries,
-                                   fs, NULL, SVN_FS_FS__ITEM_TYPE_DIR_REP,
-                                   rev, pool));
+            SVN_ERR(write_container_rep(noderev->data_rep, file, entries,
+                                        write_directory_to_stream, fs, NULL,
+                                        SVN_FS_FS__ITEM_TYPE_DIR_REP, rev,
+                                        pool));
 
           reset_txn_in_rep(noderev->data_rep);
         }
@@ -2912,12 +2934,13 @@ write_final_rev(const svn_fs_id_t **new_
       noderev->prop_rep->revision = rev;
 
       if (ffd->deltify_properties)
-        SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file,
-                                     proplist, fs, noderev, reps_hash,
-                                     item_type, rev, pool));
+        SVN_ERR(write_container_delta_rep(noderev->prop_rep, file, proplist,
+                                          write_hash_to_stream, fs, noderev,
+                                          reps_hash, item_type, rev, pool));
       else
-        SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist,
-                               fs, reps_hash, item_type, rev, pool));
+        SVN_ERR(write_container_rep(noderev->prop_rep, file, proplist,
+                                    write_hash_to_stream, fs, reps_hash,
+                                    item_type, rev, pool));
 
       reset_txn_in_rep(noderev->prop_rep);
     }

Modified: subversion/trunk/subversion/libsvn_fs_fs/tree.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_fs/tree.c?rev=1554711&r1=1554710&r2=1554711&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_fs/tree.c (original)
+++ subversion/trunk/subversion/libsvn_fs_fs/tree.c Thu Jan  2 00:24:30 2014
@@ -49,6 +49,7 @@
 #include "svn_mergeinfo.h"
 #include "svn_fs.h"
 #include "svn_props.h"
+#include "svn_sorts.h"
 
 #include "fs.h"
 #include "cached_data.h"
@@ -1624,8 +1625,8 @@ merge(svn_stringbuf_t *conflict_p,
       apr_pool_t *pool)
 {
   const svn_fs_id_t *source_id, *target_id, *ancestor_id;
-  apr_hash_t *s_entries, *t_entries, *a_entries;
-  apr_hash_index_t *hi;
+  apr_array_header_t *s_entries, *t_entries, *a_entries;
+  int i, s_idx = -1, t_idx = -1;
   svn_fs_t *fs;
   apr_pool_t *iterpool;
   apr_int64_t mergeinfo_increment = 0;
@@ -1781,27 +1782,19 @@ merge(svn_stringbuf_t *conflict_p,
 
   /* for each entry E in a_entries... */
   iterpool = svn_pool_create(pool);
-  for (hi = apr_hash_first(pool, a_entries);
-       hi;
-       hi = apr_hash_next(hi))
+  for (i = 0; i < a_entries->nelts; ++i)
     {
       svn_fs_dirent_t *s_entry, *t_entry, *a_entry;
-      const char *name;
-      apr_ssize_t klen;
-
       svn_pool_clear(iterpool);
 
-      name = svn__apr_hash_index_key(hi);
-      klen = svn__apr_hash_index_klen(hi);
-      a_entry = svn__apr_hash_index_val(hi);
-
-      s_entry = apr_hash_get(s_entries, name, klen);
-      t_entry = apr_hash_get(t_entries, name, klen);
+      a_entry = APR_ARRAY_IDX(a_entries, i, svn_fs_dirent_t *);
+      s_entry = svn_fs_fs__find_dir_entry(s_entries, a_entry->name, &s_idx);
+      t_entry = svn_fs_fs__find_dir_entry(t_entries, a_entry->name, &t_idx);
 
       /* No changes were made to this entry while the transaction was
          in progress, so do nothing to the target. */
       if (s_entry && svn_fs_fs__id_eq(a_entry->id, s_entry->id))
-        goto end;
+        continue;
 
       /* A change was made to this entry while the transaction was in
          process, but the transaction did not touch this entry. */
@@ -1832,7 +1825,7 @@ merge(svn_stringbuf_t *conflict_p,
                   mergeinfo_increment += mergeinfo_end;
                 }
 
-              SVN_ERR(svn_fs_fs__dag_set_entry(target, name,
+              SVN_ERR(svn_fs_fs__dag_set_entry(target, a_entry->name,
                                                s_entry->id,
                                                s_entry->kind,
                                                txn_id,
@@ -1840,7 +1833,8 @@ merge(svn_stringbuf_t *conflict_p,
             }
           else
             {
-              SVN_ERR(svn_fs_fs__dag_delete(target, name, txn_id, iterpool));
+              SVN_ERR(svn_fs_fs__dag_delete(target, a_entry->name, txn_id,
+                                            iterpool));
             }
         }
 
@@ -1904,29 +1898,23 @@ merge(svn_stringbuf_t *conflict_p,
           if (fs_supports_mergeinfo)
             mergeinfo_increment += sub_mergeinfo_increment;
         }
-
-      /* We've taken care of any possible implications E could have.
-         Remove it from source_entries, so it's easy later to loop
-         over all the source entries that didn't exist in
-         ancestor_entries. */
-    end:
-      apr_hash_set(s_entries, name, klen, NULL);
     }
 
   /* For each entry E in source but not in ancestor */
-  for (hi = apr_hash_first(pool, s_entries);
-       hi;
-       hi = apr_hash_next(hi))
-    {
-      svn_fs_dirent_t *s_entry, *t_entry;
-      const char *name = svn__apr_hash_index_key(hi);
-      apr_ssize_t klen = svn__apr_hash_index_klen(hi);
+  for (i = 0; i < s_entries->nelts; ++i)
+    {
+      svn_fs_dirent_t *a_entry, *s_entry, *t_entry;
       dag_node_t *s_ent_node;
 
       svn_pool_clear(iterpool);
 
-      s_entry = svn__apr_hash_index_val(hi);
-      t_entry = apr_hash_get(t_entries, name, klen);
+      s_entry = APR_ARRAY_IDX(s_entries, i, svn_fs_dirent_t *);
+      a_entry = svn_fs_fs__find_dir_entry(a_entries, s_entry->name, &s_idx);
+      t_entry = svn_fs_fs__find_dir_entry(t_entries, s_entry->name, &t_idx);
+
+      /* Process only entries in source that are NOT in ancestor. */
+      if (a_entry)
+        continue;
 
       /* If NAME exists in TARGET, declare a conflict. */
       if (t_entry)
@@ -2235,10 +2223,23 @@ fs_dir_entries(apr_hash_t **table_p,
                apr_pool_t *pool)
 {
   dag_node_t *node;
+  apr_hash_t *hash = svn_hash__make(pool);
+  apr_array_header_t *table;
+  int i;
 
   /* Get the entries for this path in the caller's pool. */
   SVN_ERR(get_dag(&node, root, path, FALSE, pool));
-  return svn_fs_fs__dag_dir_entries(table_p, node, pool);
+  SVN_ERR(svn_fs_fs__dag_dir_entries(&table, node, pool));
+
+  /* Convert directory array to hash. */
+  for (i = 0; i < table->nelts; ++i)
+    {
+      svn_fs_dirent_t *entry = APR_ARRAY_IDX(table, i, svn_fs_dirent_t *);
+      svn_hash_sets(hash, entry->name, entry);
+    }
+
+  *table_p = hash;
+  return SVN_NO_ERROR;
 }
 
 static svn_error_t *
@@ -3904,18 +3905,14 @@ crawl_directory_dag_for_mergeinfo(svn_fs
                                   apr_pool_t *result_pool,
                                   apr_pool_t *scratch_pool)
 {
-  apr_hash_t *entries;
-  apr_hash_index_t *hi;
+  apr_array_header_t *entries;
+  int i;
   apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
-  SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, dir_dag,
-                                     scratch_pool));
-
-  for (hi = apr_hash_first(scratch_pool, entries);
-       hi;
-       hi = apr_hash_next(hi))
+  SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, dir_dag, scratch_pool));
+  for (i = 0; i < entries->nelts; ++i)
     {
-      svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
+      svn_fs_dirent_t *dirent = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
       const char *kid_path;
       dag_node_t *kid_dag;
       svn_boolean_t has_mergeinfo, go_down;
@@ -4462,18 +4459,17 @@ verify_node(dag_node_t *node,
     }
   if (kind == svn_node_dir)
     {
-      apr_hash_t *entries;
-      apr_hash_index_t *hi;
+      apr_array_header_t *entries;
+      int i;
       apr_int64_t children_mergeinfo = 0;
 
       SVN_ERR(svn_fs_fs__dag_dir_entries(&entries, node, pool));
 
       /* Compute CHILDREN_MERGEINFO. */
-      for (hi = apr_hash_first(pool, entries);
-           hi;
-           hi = apr_hash_next(hi))
+      for (i = 0; i < entries->nelts; ++i)
         {
-          svn_fs_dirent_t *dirent = svn__apr_hash_index_val(hi);
+          svn_fs_dirent_t *dirent
+            = APR_ARRAY_IDX(entries, i, svn_fs_dirent_t *);
           dag_node_t *child;
           apr_int64_t child_mergeinfo;
 

Modified: subversion/trunk/subversion/libsvn_subr/hash.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_subr/hash.c?rev=1554711&r1=1554710&r2=1554711&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_subr/hash.c (original)
+++ subversion/trunk/subversion/libsvn_subr/hash.c Thu Jan  2 00:24:30 2014
@@ -88,37 +88,67 @@
 /*** Dumping and loading hash files. */
 
 /* Implements svn_hash_read2 and svn_hash_read_incremental. */
-static svn_error_t *
-hash_read(apr_hash_t *hash, svn_stream_t *stream, const char *terminator,
-          svn_boolean_t incremental, apr_pool_t *pool)
+svn_error_t *
+svn_hash__read_entry(svn_hash__entry_t *entry,
+                     svn_stream_t *stream,
+                     const char *terminator,
+                     svn_boolean_t incremental,
+                     apr_pool_t *pool)
 {
   svn_stringbuf_t *buf;
   svn_boolean_t eof;
-  apr_size_t len, keylen, vallen;
-  char c, *keybuf, *valbuf;
-  apr_pool_t *iterpool = svn_pool_create(pool);
-
-  while (1)
-    {
-      svn_error_t *err;
-      apr_uint64_t ui64;
+  apr_size_t len;
+  char c;
 
-      svn_pool_clear(iterpool);
+  svn_error_t *err;
+  apr_uint64_t ui64;
 
-      /* Read a key length line.  Might be END, though. */
-      SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool));
+  /* Read a key length line.  Might be END, though. */
+  SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, pool));
 
-      /* Check for the end of the hash. */
-      if ((!terminator && eof && buf->len == 0)
-          || (terminator && (strcmp(buf->data, terminator) == 0)))
-        break;
+  /* Check for the end of the hash. */
+  if ((!terminator && eof && buf->len == 0)
+      || (terminator && (strcmp(buf->data, terminator) == 0)))
+  {
+    entry->key = NULL;
+    entry->keylen = 0;
+    entry->val = NULL;
+    entry->vallen = 0;
+
+    return SVN_NO_ERROR;
+  }
+
+  /* Check for unexpected end of stream */
+  if (eof)
+    return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+                            _("Serialized hash missing terminator"));
 
-      /* Check for unexpected end of stream */
-      if (eof)
+  if ((buf->len >= 3) && (buf->data[0] == 'K') && (buf->data[1] == ' '))
+    {
+      /* Get the length of the key */
+      err = svn_cstring_strtoui64(&ui64, buf->data + 2,
+                                  0, APR_SIZE_MAX, 10);
+      if (err)
+        return svn_error_create(SVN_ERR_MALFORMED_FILE, err,
+                                _("Serialized hash malformed"));
+      entry->keylen = (apr_size_t)ui64;
+
+      /* Now read that much into a buffer. */
+      entry->key = apr_palloc(pool, entry->keylen + 1);
+      SVN_ERR(svn_stream_read(stream, entry->key, &entry->keylen));
+      entry->key[entry->keylen] = '\0';
+
+      /* Suck up extra newline after key data */
+      len = 1;
+      SVN_ERR(svn_stream_read(stream, &c, &len));
+      if (c != '\n')
         return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
-                                _("Serialized hash missing terminator"));
+                                _("Serialized hash malformed"));
+
+      /* Read a val length line */
+      SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, pool));
 
-      if ((buf->len >= 3) && (buf->data[0] == 'K') && (buf->data[1] == ' '))
+      if ((buf->data[0] == 'V') && (buf->data[1] == ' '))
         {
           /* Get the length of the key */
           err = svn_cstring_strtoui64(&ui64, buf->data + 2,
@@ -126,81 +156,88 @@ hash_read(apr_hash_t *hash, svn_stream_t
           if (err)
             return svn_error_create(SVN_ERR_MALFORMED_FILE, err,
                                     _("Serialized hash malformed"));
-          keylen = (apr_size_t)ui64;
+          entry->vallen = (apr_size_t)ui64;
 
-          /* Now read that much into a buffer. */
-          keybuf = apr_palloc(pool, keylen + 1);
-          SVN_ERR(svn_stream_read(stream, keybuf, &keylen));
-          keybuf[keylen] = '\0';
+          entry->val = apr_palloc(pool, entry->vallen + 1);
+          SVN_ERR(svn_stream_read(stream, entry->val, &entry->vallen));
+          entry->val[entry->vallen] = '\0';
 
-          /* Suck up extra newline after key data */
+          /* Suck up extra newline after val data */
           len = 1;
           SVN_ERR(svn_stream_read(stream, &c, &len));
           if (c != '\n')
             return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
                                     _("Serialized hash malformed"));
+        }
+      else
+        return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+                                _("Serialized hash malformed"));
+    }
+  else if (incremental && (buf->len >= 3)
+           && (buf->data[0] == 'D') && (buf->data[1] == ' '))
+    {
+      /* Get the length of the key */
+      err = svn_cstring_strtoui64(&ui64, buf->data + 2,
+                                  0, APR_SIZE_MAX, 10);
+      if (err)
+        return svn_error_create(SVN_ERR_MALFORMED_FILE, err,
+                                _("Serialized hash malformed"));
+      entry->keylen = (apr_size_t)ui64;
+
+      /* Now read that much into a buffer. */
+      entry->key = apr_palloc(pool, entry->keylen + 1);
+      SVN_ERR(svn_stream_read(stream, entry->key, &entry->keylen));
+      entry->key[entry->keylen] = '\0';
+
+      /* Suck up extra newline after key data */
+      len = 1;
+      SVN_ERR(svn_stream_read(stream, &c, &len));
+      if (c != '\n')
+        return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+                                _("Serialized hash malformed"));
 
-          /* Read a val length line */
-          SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool));
+      /* Remove this hash entry. */
+      entry->vallen = 0;
+      entry->val = NULL;
+    }
+  else
+    {
+      return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
+                              _("Serialized hash malformed"));
+    }
 
-          if ((buf->data[0] == 'V') && (buf->data[1] == ' '))
-            {
-              err = svn_cstring_strtoui64(&ui64, buf->data + 2,
-                                          0, APR_SIZE_MAX, 10);
-              if (err)
-                return svn_error_create(SVN_ERR_MALFORMED_FILE, err,
-                                        _("Serialized hash malformed"));
-              vallen = (apr_size_t)ui64;
-
-              valbuf = apr_palloc(iterpool, vallen + 1);
-              SVN_ERR(svn_stream_read(stream, valbuf, &vallen));
-              valbuf[vallen] = '\0';
+  return SVN_NO_ERROR;
+}
 
-              /* Suck up extra newline after val data */
-              len = 1;
-              SVN_ERR(svn_stream_read(stream, &c, &len));
-              if (c != '\n')
-                return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
-                                        _("Serialized hash malformed"));
+static svn_error_t *
+hash_read(apr_hash_t *hash, svn_stream_t *stream, const char *terminator,
+          svn_boolean_t incremental, apr_pool_t *pool)
+{
+  apr_pool_t *iterpool = svn_pool_create(pool);
 
-              /* Add a new hash entry. */
-              apr_hash_set(hash, keybuf, keylen,
-                           svn_string_ncreate(valbuf, vallen, pool));
-            }
-          else
-            return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
-                                    _("Serialized hash malformed"));
-        }
-      else if (incremental && (buf->len >= 3)
-               && (buf->data[0] == 'D') && (buf->data[1] == ' '))
-        {
-          /* Get the length of the key */
-          err = svn_cstring_strtoui64(&ui64, buf->data + 2,
-                                      0, APR_SIZE_MAX, 10);
-          if (err)
-            return svn_error_create(SVN_ERR_MALFORMED_FILE, err,
-                                    _("Serialized hash malformed"));
-          keylen = (apr_size_t)ui64;
+  while (1)
+    {
+      svn_hash__entry_t entry;
 
-          /* Now read that much into a buffer. */
-          keybuf = apr_palloc(iterpool, keylen + 1);
-          SVN_ERR(svn_stream_read(stream, keybuf, &keylen));
-          keybuf[keylen] = '\0';
+      svn_pool_clear(iterpool);
+      SVN_ERR(svn_hash__read_entry(&entry, stream, terminator,
+                                   incremental, iterpool));
 
-          /* Suck up extra newline after key data */
-          len = 1;
-          SVN_ERR(svn_stream_read(stream, &c, &len));
-          if (c != '\n')
-            return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
-                                    _("Serialized hash malformed"));
+      /* end of hash? */
+      if (entry.key == NULL)
+        break;
 
-          /* Remove this hash entry. */
-          apr_hash_set(hash, keybuf, keylen, NULL);
+      if (entry.val)
+        {
+          /* Add a new hash entry. */
+          apr_hash_set(hash, apr_pstrmemdup(pool, entry.key, entry.keylen),
+                       entry.keylen,
+                       svn_string_ncreate(entry.val, entry.vallen, pool));
         }
       else
         {
-          return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL,
-                                  _("Serialized hash malformed"));
+          /* Remove this hash entry. */
+          apr_hash_set(hash, entry.key, entry.keylen, NULL);
         }
     }