You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by ju...@apache.org on 2012/03/06 18:50:31 UTC

svn commit: r1297604 [5/12] - in /subversion/branches/reintegrate-keep-alive: ./ build/ build/ac-macros/ build/generator/ build/generator/templates/ build/win32/ notes/ notes/api-errata/1.7/ subversion/bindings/javahl/native/ subversion/bindings/javahl...

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/fs_fs.c
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/fs_fs.c?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/fs_fs.c (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/fs_fs.c Tue Mar  6 17:50:23 2012
@@ -81,7 +81,7 @@
 /* Begin deltification after a node history exceeded this this limit.
    Useful values are 4 to 64 with 16 being a good compromise between
    computational overhead and pository size savings.
-   Should be a power of 2.  
+   Should be a power of 2.
    Values < 2 will result in standard skip-delta behavior. */
 #define SVN_FS_FS_MAX_LINEAR_DELTIFICATION 16
 
@@ -225,6 +225,12 @@ path_lock(svn_fs_t *fs, apr_pool_t *pool
 }
 
 static const char *
+path_revprop_generation(svn_fs_t *fs, apr_pool_t *pool)
+{
+  return svn_dirent_join(fs->path, PATH_REVPROP_GEN, pool);
+}
+
+static const char *
 path_rev_packed(svn_fs_t *fs, svn_revnum_t rev, const char *kind,
                 apr_pool_t *pool)
 {
@@ -800,7 +806,7 @@ get_writable_proto_rev_body(svn_fs_t *fs
       err = svn_error_compose_create(
               err,
               unlock_proto_rev_list_locked(fs, txn_id, *lockcookie, pool));
-      
+
       *lockcookie = NULL;
     }
 
@@ -870,25 +876,39 @@ get_file_offset(apr_off_t *offset_p, apr
 }
 
 
-/* Check that BUF, a nul-terminated buffer of text from format file PATH,
+/* Check that BUF, a nul-terminated buffer of text from file PATH,
    contains only digits at OFFSET and beyond, raising an error if not.
+   TITLE contains a user-visible description of the file, usually the
+   short file name.
 
    Uses POOL for temporary allocation. */
 static svn_error_t *
-check_format_file_buffer_numeric(const char *buf, apr_off_t offset,
-                                 const char *path, apr_pool_t *pool)
+check_file_buffer_numeric(const char *buf, apr_off_t offset,
+                          const char *path, const char *title,
+                          apr_pool_t *pool)
 {
   const char *p;
 
   for (p = buf + offset; *p; p++)
     if (!svn_ctype_isdigit(*p))
       return svn_error_createf(SVN_ERR_BAD_VERSION_FILE_FORMAT, NULL,
-        _("Format file '%s' contains unexpected non-digit '%c' within '%s'"),
-        svn_dirent_local_style(path, pool), *p, buf);
+        _("%s file '%s' contains unexpected non-digit '%c' within '%s'"),
+        title, svn_dirent_local_style(path, pool), *p, buf);
 
   return SVN_NO_ERROR;
 }
 
+/* Check that BUF, a nul-terminated buffer of text from format file PATH,
+   contains only digits at OFFSET and beyond, raising an error if not.
+
+   Uses POOL for temporary allocation. */
+static svn_error_t *
+check_format_file_buffer_numeric(const char *buf, apr_off_t offset,
+                                 const char *path, apr_pool_t *pool)
+{
+  return check_file_buffer_numeric(buf, offset, path, "Format", pool);
+}
+
 /* Read the format number and maximum number of files per directory
    from PATH and return them in *PFORMAT and *MAX_FILES_PER_DIR
    respectively.
@@ -1548,6 +1568,16 @@ ensure_revision_exists(svn_fs_t *fs,
                            _("No such revision %ld"), rev);
 }
 
+svn_error_t *
+svn_fs_fs__revision_exists(svn_revnum_t rev,
+                           svn_fs_t *fs,
+                           apr_pool_t *pool)
+{
+  /* Different order of parameters. */
+  SVN_ERR(ensure_revision_exists(fs, rev, pool));
+  return SVN_NO_ERROR;
+}
+
 /* Open the correct revision file for REV.  If the filesystem FS has
    been packed, *FILE will be set to the packed file; otherwise, set *FILE
    to the revision file for REV.  Return SVN_ERR_FS_NO_SUCH_REVISION if the
@@ -2677,6 +2707,105 @@ svn_fs_fs__rev_get_root(svn_fs_id_t **ro
   return SVN_NO_ERROR;
 }
 
+/* Reads the revprop_gen file and writes the content into the
+   REVPROP_GENERATION member of FS.  Use pool for allocations. */
+static svn_error_t *
+read_revprop_generation(svn_fs_t *fs,
+                        apr_pool_t *pool)
+{
+  fs_fs_data_t *ffd = fs->fsap_data;
+  
+  const char *path = path_revprop_generation(fs, pool);
+  svn_error_t *err;
+  char buf[80];
+  int i;
+
+  /* Read the raw data from the file, if it exists. If it does
+     not, set the generation to "1" and return.
+     We don't want to have this function fail.  So, patiently
+     retry a couple of times the case the OS denied us access. */
+  apr_pool_t *iterpool = svn_pool_create(pool);
+  for (i = 0; i < RECOVERABLE_RETRY_COUNT; ++i)
+    {
+      apr_file_t *file;
+      apr_size_t len = sizeof(buf);
+      
+      svn_pool_clear(iterpool);
+
+      err = svn_io_file_open(&file, path, APR_READ | APR_BUFFERED,
+                            APR_OS_DEFAULT, iterpool);
+      if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+        {
+          /* No-one changed a revprop -> we are still at gen 1. */
+          ffd->revprop_generation = 1;
+          svn_error_clear(err);
+          return SVN_NO_ERROR;
+        }
+      svn_error_clear(err);
+
+      RETRY_RECOVERABLE(err, file,
+                        svn_io_read_length_line(file,
+                                                buf,
+                                                &len,
+                                                iterpool));
+      IGNORE_RECOVERABLE(err, svn_io_file_close(file,
+                                                iterpool));
+
+      break;
+    }
+  SVN_ERR(err);
+
+  svn_pool_destroy(iterpool);
+
+  /* Check that the first line contains only digits. */
+  SVN_ERR(check_file_buffer_numeric(buf, 0, path, 
+                                    "Revprop generations", pool));
+  SVN_ERR(svn_cstring_atoi64(&ffd->revprop_generation, buf));
+
+  /* Graceful behavior in case someone put a "0" in the file. */
+  if (ffd->revprop_generation <= 0)
+    ffd->revprop_generation = 1;
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+check_revprop_generation(svn_fs_t *fs,
+                         apr_pool_t *pool)
+{
+  fs_fs_data_t *ffd = fs->fsap_data;
+  
+  return ffd->revprop_generation == 0
+    ? read_revprop_generation(fs, pool)
+    : SVN_NO_ERROR;
+}
+
+static svn_error_t *
+increment_revprop_generation(svn_fs_t *fs,
+                             apr_pool_t *pool)
+{
+  fs_fs_data_t *ffd = fs->fsap_data;
+  
+  const char *path = path_revprop_generation(fs, pool);
+  const char *tmp_filename;
+  svn_string_t *generation;
+
+  SVN_ERR(read_revprop_generation(fs, pool));
+
+  /* Increment the key and add a trailing \n to the string so the
+     txn-current file has a newline in it. */
+  ++ffd->revprop_generation;
+  generation = svn_string_createf(pool, "%" APR_INT64_T_FMT "\n",
+                                  ffd->revprop_generation);
+
+  SVN_ERR(svn_io_write_unique(&tmp_filename,
+                              svn_dirent_dirname(path, pool),
+                              generation->data, generation->len,
+                              svn_io_file_del_none, pool));
+  return move_into_place(tmp_filename, path, 
+                         svn_fs_fs__path_current(fs, pool), pool);
+}
+
 /* Set the revision property list of revision REV in filesystem FS to
    PROPLIST.  Use POOL for temporary allocations. */
 static svn_error_t *
@@ -2693,6 +2822,10 @@ set_revision_proplist(svn_fs_t *fs,
       const char *tmp_path;
       const char *perms_reference;
       svn_stream_t *stream;
+      svn_node_kind_t kind;
+
+      /* test whether revprops already exist for this revision */
+      SVN_ERR(svn_io_check_path(final_path, &kind, pool));
 
       /* ### do we have a directory sitting around already? we really shouldn't
          ### have to get the dirname here. */
@@ -2709,6 +2842,12 @@ set_revision_proplist(svn_fs_t *fs,
       SVN_ERR(svn_fs_fs__path_rev_absolute(&perms_reference, fs, rev, pool));
       SVN_ERR(move_into_place(tmp_path, final_path, perms_reference, pool));
 
+      /* Invalidate all cached revprops for this FS and for all other
+         users that haven't read any revprops, YET.  Since writing revprops
+         implies a write lock, there can be no races. */
+      if (kind != svn_node_none)
+        SVN_ERR(increment_revprop_generation(fs, pool));
+
       return SVN_NO_ERROR;
     }
 
@@ -2722,8 +2861,22 @@ revision_proplist(apr_hash_t **proplist_
                   apr_pool_t *pool)
 {
   apr_hash_t *proplist;
+  fs_fs_data_t *ffd = fs->fsap_data;
+  const char *key;
 
   SVN_ERR(ensure_revision_exists(fs, rev, pool));
+  SVN_ERR(check_revprop_generation(fs, pool));
+
+  /* Try cache lookup first. */
+  key = svn_fs_fs__combine_two_numbers(rev, ffd->revprop_generation, pool);
+  if (ffd->revprop_cache)
+    {
+      svn_boolean_t is_cached;
+      SVN_ERR(svn_cache__get((void **) proplist_p, &is_cached,
+                              ffd->revprop_cache, key, pool));
+      if (is_cached)
+        return SVN_NO_ERROR;
+    }
 
   /* if (1); null condition for easier merging to revprop-packing */
     {
@@ -2779,6 +2932,10 @@ revision_proplist(apr_hash_t **proplist_
       svn_pool_destroy(iterpool);
     }
 
+  /* Cache the result, if caching has been activated. */
+  if (ffd->revprop_cache)
+    SVN_ERR(svn_cache__set(ffd->revprop_cache, key, proplist, pool));
+
   *proplist_p = proplist;
 
   return SVN_NO_ERROR;
@@ -2802,6 +2959,8 @@ struct rep_state
   apr_file_t *file;
                     /* The txdelta window cache to use or NULL. */
   svn_cache__t *window_cache;
+                    /* Caches un-deltified windows. May be NULL. */
+  svn_cache__t *combined_cache;
   apr_off_t start;  /* The starting offset for the raw
                        svndiff/plaintext data minus header. */
   apr_off_t off;    /* The current offset into the file. */
@@ -2825,6 +2984,7 @@ create_rep_state_body(struct rep_state *
 
   SVN_ERR(open_and_seek_representation(&rs->file, fs, rep, pool));
   rs->window_cache = ffd->txdelta_window_cache;
+  rs->combined_cache = ffd->combined_window_cache;
 
   SVN_ERR(read_rep_line(&ra, rs->file, pool));
   SVN_ERR(get_file_offset(&rs->start, rs->file, pool));
@@ -2876,7 +3036,7 @@ create_rep_state(struct rep_state **rep_
          ### going to jump straight to this comment anyway! */
       return svn_error_createf(SVN_ERR_FS_CORRUPT, err,
                                "Corrupt representation '%s'",
-                               rep 
+                               rep
                                ? representation_string(rep, ffd->format, TRUE,
                                                        TRUE, pool)
                                : "(null)");
@@ -2885,57 +3045,14 @@ create_rep_state(struct rep_state **rep_
   return svn_error_trace(err);
 }
 
-/* Build an array of rep_state structures in *LIST giving the delta
-   reps from first_rep to a plain-text or self-compressed rep.  Set
-   *SRC_STATE to the plain-text rep we find at the end of the chain,
-   or to NULL if the final delta representation is self-compressed.
-   The representation to start from is designated by filesystem FS, id
-   ID, and representation REP. */
-static svn_error_t *
-build_rep_list(apr_array_header_t **list,
-               struct rep_state **src_state,
-               svn_fs_t *fs,
-               representation_t *first_rep,
-               apr_pool_t *pool)
-{
-  representation_t rep;
-  struct rep_state *rs;
-  struct rep_args *rep_args;
-
-  *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
-  rep = *first_rep;
-
-  while (1)
-    {
-      SVN_ERR(create_rep_state(&rs, &rep_args, &rep, fs, pool));
-      if (rep_args->is_delta == FALSE)
-        {
-          /* This is a plaintext, so just return the current rep_state. */
-          *src_state = rs;
-          return SVN_NO_ERROR;
-        }
-
-      /* Push this rep onto the list.  If it's self-compressed, we're done. */
-      APR_ARRAY_PUSH(*list, struct rep_state *) = rs;
-      if (rep_args->is_delta_vs_empty)
-        {
-          *src_state = NULL;
-          return SVN_NO_ERROR;
-        }
-
-      rep.revision = rep_args->base_revision;
-      rep.offset = rep_args->base_offset;
-      rep.size = rep_args->base_length;
-      rep.txn_id = NULL;
-    }
-}
-
-
 struct rep_read_baton
 {
   /* The FS from which we're reading. */
   svn_fs_t *fs;
 
+  /* If not NULL, this is the base for the first delta window in rs_list */
+  svn_stringbuf_t *base_window;
+
   /* The state of all prior delta representations. */
   apr_array_header_t *rs_list;
 
@@ -2980,51 +3097,9 @@ struct rep_read_baton
   apr_pool_t *filehandle_pool;
 };
 
-/* Create a rep_read_baton structure for node revision NODEREV 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;
-
-  b = apr_pcalloc(pool, sizeof(*b));
-  b->fs = fs;
-  b->chunk_index = 0;
-  b->buf = NULL;
-  b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
-  b->checksum_finalized = FALSE;
-  b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
-  b->len = rep->expanded_size ? rep->expanded_size : rep->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_ensure
-                            ((apr_size_t)b->len,
-                             b->filehandle_pool);
-  else
-    b->current_fulltext = NULL;
-
-  SVN_ERR(build_rep_list(&b->rs_list, &b->src_state, fs, rep,
-                         b->filehandle_pool));
-
-  /* Save our output baton. */
-  *rb_p = b;
-
-  return SVN_NO_ERROR;
-}
-
 /* Combine the name of the rev file in RS with the given OFFSET to form
- * a cache lookup key. Allocations will be made from POOL. */
+ * a cache lookup key.  Allocations will be made from POOL.  May return
+ * NULL if the key cannot be constructed. */
 static const char*
 get_window_key(struct rep_state *rs, apr_off_t offset, apr_pool_t *pool)
 {
@@ -3038,7 +3113,7 @@ get_window_key(struct rep_state *rs, apr
    * comparison _will_ find them.
    */
   if (apr_file_name_get(&name, rs->file))
-    return "";
+    return NULL;
 
   /* Handle packed files as well by scanning backwards until we find the
    * revision or pack number. */
@@ -3105,35 +3180,194 @@ get_cached_window(svn_txdelta_window_t *
           rs->chunk_index++;
           rs->off = cached_window->end_offset;
 
-          /* manipulate the rev file as if we just read from it */
-          SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
-        }
-    }
+          /* manipulate the rev file as if we just read from it */
+          SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off, pool));
+        }
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Store the WINDOW read at OFFSET for the rep state RS in the current
+ * FSFS session's cache. This will be a no-op if no cache has been given.
+ * Temporary allocations will be made from SCRATCH_POOL. */
+static svn_error_t *
+set_cached_window(svn_txdelta_window_t *window,
+                  struct rep_state *rs,
+                  apr_off_t offset,
+                  apr_pool_t *scratch_pool)
+{
+  if (rs->window_cache)
+    {
+      /* store the window and the first offset _past_ it */
+      svn_fs_fs__txdelta_cached_window_t cached_window = { window, rs->off };
+
+      /* but key it with the start offset because that is the known state
+       * when we will look it up */
+      return svn_cache__set(rs->window_cache,
+                            get_window_key(rs, offset, scratch_pool),
+                            &cached_window,
+                            scratch_pool);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Read the WINDOW_P for the rep state RS from the current FSFS session's
+ * cache. This will be a no-op and IS_CACHED will be set to FALSE if no
+ * cache has been given. If a cache is available IS_CACHED will inform
+ * the caller about the success of the lookup. Allocations (of the window
+ * in particualar) will be made from POOL.
+ */
+static svn_error_t *
+get_cached_combined_window(svn_stringbuf_t **window_p,
+                           struct rep_state *rs,
+                           svn_boolean_t *is_cached,
+                           apr_pool_t *pool)
+{
+  if (! rs->combined_cache)
+    {
+      /* txdelta window has not been enabled */
+      *is_cached = FALSE;
+    }
+  else
+    {
+      /* ask the cache for the desired txdelta window */
+      return svn_cache__get((void **)window_p,
+                            is_cached,
+                            rs->combined_cache,
+                            get_window_key(rs, rs->start, pool),
+                            pool);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Store the WINDOW read at OFFSET for the rep state RS in the current
+ * FSFS session's cache. This will be a no-op if no cache has been given.
+ * Temporary allocations will be made from SCRATCH_POOL. */
+static svn_error_t *
+set_cached_combined_window(svn_stringbuf_t *window,
+                           struct rep_state *rs,
+                           apr_off_t offset,
+                           apr_pool_t *scratch_pool)
+{
+  if (rs->combined_cache)
+    {
+      /* but key it with the start offset because that is the known state
+       * when we will look it up */
+      return svn_cache__set(rs->combined_cache,
+                            get_window_key(rs, offset, scratch_pool),
+                            window,
+                            scratch_pool);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Build an array of rep_state structures in *LIST giving the delta
+   reps from first_rep to a plain-text or self-compressed rep.  Set
+   *SRC_STATE to the plain-text rep we find at the end of the chain,
+   or to NULL if the final delta representation is self-compressed.
+   The representation to start from is designated by filesystem FS, id
+   ID, and representation REP.
+   Also, set *WINDOW_P to the base window content for *LIST, if it
+   could be found in cache. Otherwise, *LIST will contain the base
+   representation for the whole delta chain.  */
+static svn_error_t *
+build_rep_list(apr_array_header_t **list,
+               svn_stringbuf_t **window_p,
+               struct rep_state **src_state,
+               svn_fs_t *fs,
+               representation_t *first_rep,
+               apr_pool_t *pool)
+{
+  representation_t rep;
+  struct rep_state *rs;
+  struct rep_args *rep_args;
+  svn_boolean_t is_cached = FALSE;
+
+  *list = apr_array_make(pool, 1, sizeof(struct rep_state *));
+  rep = *first_rep;
+
+  while (1)
+    {
+      SVN_ERR(create_rep_state(&rs, &rep_args, &rep, fs, pool));
+      SVN_ERR(get_cached_combined_window(window_p, rs, &is_cached, pool));
+      if (is_cached)
+        {
+          /* We already have a reconstructed window in our cache.
+             Write a pseudo rep_state with the full length. */
+          rs->off = rs->start;
+          rs->end = rs->start + (*window_p)->len;
+          *src_state = rs;
+          return SVN_NO_ERROR;
+        }
+
+      if (rep_args->is_delta == FALSE)
+        {
+          /* This is a plaintext, so just return the current rep_state. */
+          *src_state = rs;
+          return SVN_NO_ERROR;
+        }
+
+      /* Push this rep onto the list.  If it's self-compressed, we're done. */
+      APR_ARRAY_PUSH(*list, struct rep_state *) = rs;
+      if (rep_args->is_delta_vs_empty)
+        {
+          *src_state = NULL;
+          return SVN_NO_ERROR;
+        }
+
+      rep.revision = rep_args->base_revision;
+      rep.offset = rep_args->base_offset;
+      rep.size = rep_args->base_length;
+      rep.txn_id = NULL;
+    }
+}
+
+
+/* Create a rep_read_baton structure for node revision NODEREV 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;
+
+  b = apr_pcalloc(pool, sizeof(*b));
+  b->fs = fs;
+  b->base_window = NULL;
+  b->chunk_index = 0;
+  b->buf = NULL;
+  b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+  b->checksum_finalized = FALSE;
+  b->md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
+  b->len = rep->expanded_size ? rep->expanded_size : rep->size;
+  b->off = 0;
+  b->fulltext_cache_key = fulltext_cache_key;
+  b->pool = svn_pool_create(pool);
+  b->filehandle_pool = svn_pool_create(pool);
 
-  return SVN_NO_ERROR;
-}
+  if (fulltext_cache_key)
+    b->current_fulltext = svn_stringbuf_create_ensure
+                            ((apr_size_t)b->len,
+                             b->filehandle_pool);
+  else
+    b->current_fulltext = NULL;
 
-/* Store the WINDOW read at OFFSET for the rep state RS in the current
- * FSFS session's cache. This will be a no-op if no cache has been given.
- * Temporary allocations will be made from SCRATCH_POOL. */
-static svn_error_t *
-set_cached_window(svn_txdelta_window_t *window,
-                  struct rep_state *rs,
-                  apr_off_t offset,
-                  apr_pool_t *scratch_pool)
-{
-  if (rs->window_cache)
-    {
-      /* store the window and the first offset _past_ it */
-      svn_fs_fs__txdelta_cached_window_t cached_window = { window, rs->off };
+  SVN_ERR(build_rep_list(&b->rs_list, &b->base_window,
+                         &b->src_state, fs, rep,
+                         b->filehandle_pool));
 
-      /* but key it with the start offset because that is the known state
-       * when we will look it up */
-      return svn_cache__set(rs->window_cache,
-                            get_window_key(rs, offset, scratch_pool),
-                            &cached_window,
-                            scratch_pool);
-    }
+  /* Save our output baton. */
+  *rb_p = b;
 
   return SVN_NO_ERROR;
 }
@@ -3185,45 +3419,73 @@ read_window(svn_txdelta_window_t **nwin,
   return set_cached_window(*nwin, rs, old_offset, pool);
 }
 
-/* Get one delta window that is a result of combining all but the last deltas
-   from the current desired representation identified in *RB, to its
-   final base representation.  Store the window in *RESULT. */
+/* Get the undeltified window that is a result of combining all deltas
+   from the current desired representation identified in *RB with its
+   base representation.  Store the window in *RESULT. */
 static svn_error_t *
-get_combined_window(svn_txdelta_window_t **result,
+get_combined_window(svn_stringbuf_t **result,
                     struct rep_read_baton *rb)
 {
-  apr_pool_t *pool, *new_pool;
+  apr_pool_t *pool, *new_pool, *window_pool;
   int i;
-  svn_txdelta_window_t *window, *nwin;
+  svn_txdelta_window_t *window;
+  apr_array_header_t *windows;
+  svn_stringbuf_t *source, *buf = rb->base_window;
   struct rep_state *rs;
 
-  SVN_ERR_ASSERT(rb->rs_list->nelts >= 2);
-
-  pool = svn_pool_create(rb->pool);
-
-  /* Read the next window from the original rep. */
-  rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
-  SVN_ERR(read_window(&window, rb->chunk_index, rs, pool));
-
-  /* Combine in the windows from the other delta reps, if needed. */
-  for (i = 1; i < rb->rs_list->nelts - 1; i++)
+  /* Read all windows that we need to combine. This is fine because
+     the size of each window is relatively small (100kB) and skip-
+     delta limits the number of deltas in a chain to well under 100.
+     Stop early if one of them does not depend on its predecessors. */
+  window_pool = svn_pool_create(rb->pool);
+  windows = apr_array_make(window_pool, 0, sizeof(svn_txdelta_window_t *));
+  for (i = 0; i < rb->rs_list->nelts; ++i)
     {
+      rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
+      SVN_ERR(read_window(&window, rb->chunk_index, rs, window_pool));
+
+      APR_ARRAY_PUSH(windows, svn_txdelta_window_t *) = window;
       if (window->src_ops == 0)
-        break;
+        {
+          ++i;
+          break;
+        }
+    }
 
+  /* Combine in the windows from the other delta reps. */
+  pool = svn_pool_create(rb->pool);
+  for (--i; i >= 0; --i)
+    {
       rs = APR_ARRAY_IDX(rb->rs_list, i, struct rep_state *);
+      window = APR_ARRAY_IDX(windows, i, svn_txdelta_window_t *);
 
-      SVN_ERR(read_window(&nwin, rb->chunk_index, rs, pool));
-
-      /* Combine this window with the current one.  Cycle pools so that we
-         only need to hold three windows at a time. */
+      /* Combine this window with the current one. */
+      source = buf;
       new_pool = svn_pool_create(rb->pool);
-      window = svn_txdelta_compose_windows(nwin, window, new_pool);
+      buf = svn_stringbuf_create_ensure(window->tview_len, new_pool);
+      buf->len = window->tview_len;
+
+      svn_txdelta_apply_instructions(window, source ? source->data : NULL,
+                                     buf->data, &buf->len);
+      if (buf->len != window->tview_len)
+        return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
+                                _("svndiff window length is "
+                                  "corrupt"));
+
+      /* Cache windows only if the whole rep content could be read as a
+         single chunk.  Only then will no other chunk need a deeper RS
+         list than the cached chunk. */
+      if ((rb->chunk_index == 0) && (rs->off == rs->end))
+        SVN_ERR(set_cached_combined_window(buf, rs, rs->start, new_pool));
+
+      /* Cycle pools so that we only need to hold three windows at a time. */
       svn_pool_destroy(pool);
       pool = new_pool;
     }
 
-  *result = window;
+  svn_pool_destroy(window_pool);
+
+  *result = buf;
   return SVN_NO_ERROR;
 }
 
@@ -3256,10 +3518,9 @@ get_contents(struct rep_read_baton *rb,
              char *buf,
              apr_size_t *len)
 {
-  apr_size_t copy_len, remaining = *len, tlen;
-  char *sbuf, *tbuf, *cur = buf;
+  apr_size_t copy_len, remaining = *len, offset;
+  char *cur = buf;
   struct rep_state *rs;
-  svn_txdelta_window_t *cwindow, *lwindow;
 
   /* Special case for when there are no delta reps, only a plain
      text. */
@@ -3267,10 +3528,28 @@ get_contents(struct rep_read_baton *rb,
     {
       copy_len = remaining;
       rs = rb->src_state;
-      if (((apr_off_t) copy_len) > rs->end - rs->off)
-        copy_len = (apr_size_t) (rs->end - rs->off);
-      SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL,
-                                     NULL, rb->pool));
+
+      if (rb->base_window != NULL)
+        {
+          /* We got the desired rep directly from the cache.
+             This is where we need the pseudo rep_state created
+             by build_rep_list(). */
+          offset = rs->off - rs->start;
+          if (copy_len + offset > rb->base_window->len)
+            copy_len = offset < rb->base_window->len
+                     ? rb->base_window->len - offset
+                     : 0;
+
+          memcpy (cur, rb->base_window->data + offset, copy_len);
+        }
+      else
+        {
+          if (((apr_off_t) copy_len) > rs->end - rs->off)
+            copy_len = (apr_size_t) (rs->end - rs->off);
+          SVN_ERR(svn_io_file_read_full2(rs->file, cur, copy_len, NULL,
+                                         NULL, rb->pool));
+        }
+
       rs->off += copy_len;
       *len = copy_len;
       return SVN_NO_ERROR;
@@ -3302,90 +3581,18 @@ get_contents(struct rep_read_baton *rb,
         }
       else
         {
+          svn_stringbuf_t *sbuf = NULL;
 
           rs = APR_ARRAY_IDX(rb->rs_list, 0, struct rep_state *);
           if (rs->off == rs->end)
             break;
 
           /* Get more buffered data by evaluating a chunk. */
-          if (rb->rs_list->nelts > 1)
-            SVN_ERR(get_combined_window(&cwindow, rb));
-          else
-            cwindow = NULL;
-          if (!cwindow || cwindow->src_ops > 0)
-            {
-              rs = APR_ARRAY_IDX(rb->rs_list, rb->rs_list->nelts - 1,
-                                 struct rep_state *);
-              /* Read window from last representation in list. */
-              /* We apply this window directly instead of combining it
-                 with the others.  We do this because vdelta used to
-                 be used for deltas against the empty stream, which
-                 will trigger quadratic behaviour in the delta
-                 combiner.  It's still likely that we'll find such
-                 deltas in an old repository; it may be worth
-                 considering whether or not this special case is still
-                 needed in the future, though. */
-              SVN_ERR(read_window(&lwindow, rb->chunk_index, rs, rb->pool));
-
-              if (lwindow->src_ops > 0)
-                {
-                  if (! rb->src_state)
-                    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                                            _("svndiff data requested "
-                                              "non-existent source"));
-                  rs = rb->src_state;
-                  sbuf = apr_palloc(rb->pool, lwindow->sview_len);
-                  if (! ((rs->start + lwindow->sview_offset) < rs->end))
-                    return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                                            _("svndiff requested position "
-                                              "beyond end of stream"));
-                  if ((rs->start + lwindow->sview_offset) != rs->off)
-                    {
-                      rs->off = rs->start + lwindow->sview_offset;
-                      SVN_ERR(svn_io_file_seek(rs->file, APR_SET, &rs->off,
-                                               rb->pool));
-                    }
-                  SVN_ERR(svn_io_file_read_full2(rs->file, sbuf,
-                                                 lwindow->sview_len,
-                                                 NULL, NULL, rb->pool));
-                  rs->off += lwindow->sview_len;
-                }
-              else
-                sbuf = NULL;
-
-              /* Apply lwindow to source. */
-              tlen = lwindow->tview_len;
-              tbuf = apr_palloc(rb->pool, tlen);
-              svn_txdelta_apply_instructions(lwindow, sbuf, tbuf,
-                                             &tlen);
-              if (tlen != lwindow->tview_len)
-                return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                                        _("svndiff window length is "
-                                          "corrupt"));
-              sbuf = tbuf;
-            }
-          else
-            sbuf = NULL;
+          SVN_ERR(get_combined_window(&sbuf, rb));
 
           rb->chunk_index++;
-
-          if (cwindow)
-            {
-              rb->buf_len = cwindow->tview_len;
-              rb->buf = apr_palloc(rb->pool, rb->buf_len);
-              svn_txdelta_apply_instructions(cwindow, sbuf, rb->buf,
-                                             &rb->buf_len);
-              if (rb->buf_len != cwindow->tview_len)
-                return svn_error_create(SVN_ERR_FS_CORRUPT, NULL,
-                                        _("svndiff window length is "
-                                          "corrupt"));
-            }
-          else
-            {
-              rb->buf_len = lwindow->tview_len;
-              rb->buf = sbuf;
-            }
-
+          rb->buf_len = sbuf->len;
+          rb->buf = sbuf->data;
           rb->buf_pos = 0;
         }
     }
@@ -3469,27 +3676,28 @@ read_representation(svn_stream_t **conte
   else
     {
       fs_fs_data_t *ffd = fs->fsap_data;
-      const char *fulltext_key = NULL;
+      const char *fulltext_cache_key = NULL;
       svn_filesize_t len = rep->expanded_size ? rep->expanded_size : rep->size;
       struct rep_read_baton *rb;
 
       if (ffd->fulltext_cache && SVN_IS_VALID_REVNUM(rep->revision)
           && fulltext_size_is_cachable(ffd, len))
         {
-          svn_string_t *fulltext;
+          svn_stringbuf_t *fulltext;
           svn_boolean_t is_cached;
-          fulltext_key = apr_psprintf(pool, "%ld/%" APR_OFF_T_FMT,
+          fulltext_cache_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));
+                                 ffd->fulltext_cache, fulltext_cache_key,
+                                 pool));
           if (is_cached)
             {
-              *contents_p = svn_stream_from_string(fulltext, pool);
+              *contents_p = svn_stream_from_stringbuf(fulltext, pool);
               return SVN_NO_ERROR;
             }
         }
 
-      SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_key, pool));
+      SVN_ERR(rep_read_get_baton(&rb, fs, rep, fulltext_cache_key, pool));
 
       *contents_p = svn_stream_create(rb, pool);
       svn_stream_set_read(*contents_p, rep_read_contents);
@@ -5218,7 +5426,7 @@ choose_delta_base(representation_t **rep
   count = noderev->predecessor_count;
   count = count & (count - 1);
 
-  /* We use skip delta for limiting the number of delta operations 
+  /* We use skip delta for limiting the number of delta operations
      along very long node histories.  Close to HEAD however, we create
      a linear history to minimize delta size.  */
   walk = noderev->predecessor_count - count;
@@ -5327,6 +5535,80 @@ rep_write_get_baton(struct rep_write_bat
   return SVN_NO_ERROR;
 }
 
+/* For the hash REP->SHA1, try to find an already existing representation
+   in FS and return it in *OUT_REP.  If no such representation exists or
+   if rep sharing has been disabled for FS, NULL will be returned.  Since
+   there may be new duplicate representations within the same uncommitted
+   revision, those can be passed in REPS_HASH (maps a sha1 digest onto
+   representation_t*), otherwise pass in NULL for REPS_HASH.
+   POOL will be used for allocations. The lifetime of the returned rep is
+   limited by both, POOL and REP lifetime.
+ */
+static svn_error_t *
+get_shared_rep(representation_t **old_rep,
+               svn_fs_t *fs,
+               representation_t *rep,
+               apr_hash_t *reps_hash,
+               apr_pool_t *pool)
+{
+  svn_error_t *err;
+  fs_fs_data_t *ffd = fs->fsap_data;
+
+  /* Return NULL, if rep sharing has been disabled. */
+  *old_rep = NULL;
+  if (!ffd->rep_sharing_allowed)
+    return SVN_NO_ERROR;
+
+  /* Check and see if we already have a representation somewhere that's
+     identical to the one we just wrote out.  Start with the hash lookup
+     because it is cheepest. */
+  if (reps_hash)
+    *old_rep = apr_hash_get(reps_hash,
+                            rep->sha1_checksum->digest,
+                            APR_SHA1_DIGESTSIZE);
+
+  /* If we haven't found anything yet, try harder and consult our DB. */
+  if (*old_rep == NULL)
+    {
+      err = svn_fs_fs__get_rep_reference(old_rep, fs, rep->sha1_checksum,
+                                         pool);
+      /* ### Other error codes that we shouldn't mask out? */
+      if (err == SVN_NO_ERROR
+          || err->apr_err == SVN_ERR_FS_CORRUPT
+          || SVN_ERROR_IN_CATEGORY(err->apr_err,
+                                   SVN_ERR_MALFUNC_CATEGORY_START))
+        {
+          /* Fatal error; don't mask it.
+
+             In particular, this block is triggered when the rep-cache refers
+             to revisions in the future.  We signal that as a corruption situation
+             since, once those revisions are less than youngest (because of more
+             commits), the rep-cache would be invalid.
+           */
+          SVN_ERR(err);
+        }
+      else
+        {
+          /* Something's wrong with the rep-sharing index.  We can continue
+             without rep-sharing, but warn.
+           */
+          (fs->warning)(fs->warning_baton, err);
+          svn_error_clear(err);
+          *old_rep = NULL;
+        }
+    }
+
+  /* Add information that is missing in the cached data. */
+  if (*old_rep)
+    {
+      /* Use the old rep for this content. */
+      (*old_rep)->md5_checksum = rep->md5_checksum;
+      (*old_rep)->uniquifier = rep->uniquifier;
+    }
+
+  return SVN_NO_ERROR;
+}
+
 /* Close handler for the representation write stream.  BATON is a
    rep_write_baton.  Writes out a new node-rev that correctly
    references the representation we just finished writing. */
@@ -5334,7 +5616,6 @@ static svn_error_t *
 rep_write_contents_close(void *baton)
 {
   struct rep_write_baton *b = baton;
-  fs_fs_data_t *ffd = b->fs->fsap_data;
   const char *unique_suffix;
   representation_t *rep;
   representation_t *old_rep;
@@ -5368,38 +5649,7 @@ rep_write_contents_close(void *baton)
 
   /* Check and see if we already have a representation somewhere that's
      identical to the one we just wrote out. */
-  if (ffd->rep_sharing_allowed)
-    {
-      svn_error_t *err;
-      err = svn_fs_fs__get_rep_reference(&old_rep, b->fs, rep->sha1_checksum,
-                                         b->parent_pool);
-      /* ### Other error codes that we shouldn't mask out? */
-      if (err == SVN_NO_ERROR
-          || err->apr_err == SVN_ERR_FS_CORRUPT
-          || SVN_ERROR_IN_CATEGORY(err->apr_err,
-                                   SVN_ERR_MALFUNC_CATEGORY_START))
-        {
-          /* Fatal error; don't mask it.
-
-             In particular, this block is triggered when the rep-cache refers
-             to revisions in the future.  We signal that as a corruption situation
-             since, once those revisions are less than youngest (because of more
-             commits), the rep-cache would be invalid.
-           */
-          SVN_ERR(err);
-        }
-      else
-        {
-          /* Something's wrong with the rep-sharing index.  We can continue
-             without rep-sharing, but warn.
-           */
-          (b->fs->warning)(b->fs->warning_baton, err);
-          svn_error_clear(err);
-          old_rep = NULL;
-        }
-    }
-  else
-    old_rep = NULL;
+  SVN_ERR(get_shared_rep(&old_rep, b->fs, rep, NULL, b->parent_pool));
 
   if (old_rep)
     {
@@ -5407,8 +5657,6 @@ rep_write_contents_close(void *baton)
       SVN_ERR(svn_io_file_trunc(b->file, b->rep_offset, b->pool));
 
       /* Use the old rep for this content. */
-      old_rep->md5_checksum = rep->md5_checksum;
-      old_rep->uniquifier = rep->uniquifier;
       b->noderev->data_rep = old_rep;
     }
   else
@@ -5575,7 +5823,8 @@ struct write_hash_baton
 
   apr_size_t size;
 
-  svn_checksum_ctx_t *checksum_ctx;
+  svn_checksum_ctx_t *md5_ctx;
+  svn_checksum_ctx_t *sha1_ctx;
 };
 
 /* The handler for the write_hash_rep stream.  BATON is a
@@ -5588,7 +5837,8 @@ write_hash_handler(void *baton,
 {
   struct write_hash_baton *whb = baton;
 
-  SVN_ERR(svn_checksum_update(whb->checksum_ctx, data, *len));
+  SVN_ERR(svn_checksum_update(whb->md5_ctx, data, *len));
+  SVN_ERR(svn_checksum_update(whb->sha1_ctx, data, *len));
 
   SVN_ERR(svn_stream_write(whb->stream, data, len));
   whb->size += *len;
@@ -5597,23 +5847,32 @@ write_hash_handler(void *baton,
 }
 
 /* Write out the hash HASH as a text representation to file FILE.  In
-   the process, record the total size of the dump in *SIZE, and the
-   md5 digest in CHECKSUM.  Perform temporary allocations in POOL. */
+   the process, record position, the total size of the dump and MD5 as
+   well as SHA1 in REP.   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.  Perform temporary allocations in POOL. */
 static svn_error_t *
-write_hash_rep(svn_filesize_t *size,
-               svn_checksum_t **checksum,
+write_hash_rep(representation_t *rep,
                apr_file_t *file,
                apr_hash_t *hash,
+               svn_fs_t *fs,
+               apr_hash_t *reps_hash,
                apr_pool_t *pool)
 {
   svn_stream_t *stream;
   struct write_hash_baton *whb;
+  representation_t *old_rep;
+
+  SVN_ERR(get_file_offset(&rep->offset, file, pool));
 
   whb = apr_pcalloc(pool, sizeof(*whb));
 
   whb->stream = svn_stream_from_aprfile2(file, TRUE, pool);
   whb->size = 0;
-  whb->checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+  whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+  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);
@@ -5623,15 +5882,41 @@ write_hash_rep(svn_filesize_t *size,
   SVN_ERR(svn_hash_write2(hash, stream, SVN_HASH_TERMINATOR, pool));
 
   /* Store the results. */
-  SVN_ERR(svn_checksum_final(checksum, whb->checksum_ctx, pool));
-  *size = whb->size;
+  SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
+  SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
+
+  /* Check and see if we already have a representation somewhere that's
+     identical to the one we just wrote out. */
+  SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
+
+  if (old_rep)
+    {
+      /* We need to erase from the protorev the data we just wrote. */
+      SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
+
+      /* Use the old rep for this content. */
+      memcpy(rep, old_rep, sizeof (*rep));
+    }
+  else
+    {
+      /* Write out our cosmetic end marker. */
+      SVN_ERR(svn_stream_printf(whb->stream, pool, "ENDREP\n"));
+
+      /* update the representation */
+      rep->size = whb->size;
+      rep->expanded_size = 0;
+    }
 
-  return svn_stream_printf(whb->stream, pool, "ENDREP\n");
+  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.  Perform temporary allocations in POOL. */
+   and the md5 digest in REP.  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.  Perform temporary allocations in POOL. */
 #ifdef SVN_FS_FS_DELTIFY_DIRECTORIES
 static svn_error_t *
 write_hash_delta_rep(representation_t *rep,
@@ -5639,19 +5924,21 @@ write_hash_delta_rep(representation_t *r
                      apr_hash_t *hash,
                      svn_fs_t *fs,
                      node_revision_t *noderev,
+                     apr_hash_t *reps_hash,
                      apr_pool_t *pool)
 {
   svn_txdelta_window_handler_t diff_wh;
   void *diff_whb;
-  
+
   svn_stream_t *file_stream;
   svn_stream_t *stream;
   representation_t *base_rep;
+  representation_t *old_rep;
   svn_stream_t *source;
   const char *header;
 
-  apr_off_t rep_end = 0; 
-  apr_off_t delta_start = 0; 
+  apr_off_t rep_end = 0;
+  apr_off_t delta_start = 0;
 
   struct write_hash_baton *whb;
   fs_fs_data_t *ffd = fs->fsap_data;
@@ -5680,7 +5967,7 @@ write_hash_delta_rep(representation_t *r
 
   SVN_ERR(get_file_offset(&delta_start, file, pool));
   file_stream = svn_stream_from_aprfile2(file, TRUE, pool);
-  
+
   /* Prepare to write the svndiff data. */
   svn_txdelta_to_svndiff3(&diff_wh,
                           &diff_whb,
@@ -5692,7 +5979,8 @@ write_hash_delta_rep(representation_t *r
   whb = apr_pcalloc(pool, sizeof(*whb));
   whb->stream = svn_txdelta_target_push(diff_wh, diff_whb, source, pool);
   whb->size = 0;
-  whb->checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+  whb->md5_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+  whb->sha1_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
 
   /* serialize the hash */
   stream = svn_stream_create(whb, pool);
@@ -5702,15 +5990,31 @@ write_hash_delta_rep(representation_t *r
   SVN_ERR(svn_stream_close(whb->stream));
 
   /* Store the results. */
-  SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->checksum_ctx, pool));
+  SVN_ERR(svn_checksum_final(&rep->md5_checksum, whb->md5_ctx, pool));
+  SVN_ERR(svn_checksum_final(&rep->sha1_checksum, whb->sha1_ctx, pool));
+
+  /* Check and see if we already have a representation somewhere that's
+     identical to the one we just wrote out. */
+  SVN_ERR(get_shared_rep(&old_rep, fs, rep, reps_hash, pool));
+
+  if (old_rep)
+    {
+      /* We need to erase from the protorev the data we just wrote. */
+      SVN_ERR(svn_io_file_trunc(file, rep->offset, pool));
+
+      /* Use the old rep for this content. */
+      memcpy(rep, old_rep, sizeof (*rep));
+    }
+  else
+    {
+      /* Write out our cosmetic end marker. */
+      SVN_ERR(get_file_offset(&rep_end, file, pool));
+      SVN_ERR(svn_stream_printf(file_stream, pool, "ENDREP\n"));
 
-  /* Write out our cosmetic end marker. */
-  SVN_ERR(get_file_offset(&rep_end, file, pool));
-  SVN_ERR(svn_stream_printf(file_stream, pool, "ENDREP\n"));
-
-  /* update the representation */
-  rep->expanded_size = whb->size;
-  rep->size = rep_end - delta_start;
+      /* update the representation */
+      rep->expanded_size = whb->size;
+      rep->size = rep_end - delta_start;
+    }
 
   return SVN_NO_ERROR;
 }
@@ -5719,6 +6023,8 @@ write_hash_delta_rep(representation_t *r
 /* Sanity check ROOT_NODEREV, a candidate for being the root node-revision
    of (not yet committed) revision REV in FS.  Use POOL for temporary
    allocations.
+
+   If you change this function, consider updating svn_fs_fs__verify() too.
  */
 static svn_error_t *
 validate_root_noderev(svn_fs_t *fs,
@@ -5765,8 +6071,11 @@ validate_root_noderev(svn_fs_t *fs,
       return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
                                _("predecessor count for "
                                  "the root node-revision is wrong: "
-                                 "found %d, committing r%ld"),
-                                 root_noderev->predecessor_count, rev);
+                                 "found (%d+%ld != %d), committing r%ld"),
+                                 head_predecessor_count,
+                                 rev - head_revnum, /* This is equal to 1. */
+                                 root_noderev->predecessor_count,
+                                 rev);
     }
 
   return SVN_NO_ERROR;
@@ -5789,6 +6098,10 @@ validate_root_noderev(svn_fs_t *fs,
    If REPS_TO_CACHE is not NULL, append to it a copy (allocated in
    REPS_POOL) of each data rep that is new in this revision.
 
+   If REPS_HASH is not NULL, append copies (allocated in REPS_POOL)
+   of the representations of each property rep that is new in this
+   revision.
+
    AT_ROOT is true if the node revision being written is the root
    node-revision.  It is only controls additional sanity checking
    logic.
@@ -5804,6 +6117,7 @@ write_final_rev(const svn_fs_id_t **new_
                 const char *start_copy_id,
                 apr_off_t initial_offset,
                 apr_array_header_t *reps_to_cache,
+                apr_hash_t *reps_hash,
                 apr_pool_t *reps_pool,
                 svn_boolean_t at_root,
                 apr_pool_t *pool)
@@ -5842,7 +6156,7 @@ write_final_rev(const svn_fs_id_t **new_
           svn_pool_clear(subpool);
           SVN_ERR(write_final_rev(&new_id, file, rev, fs, dirent->id,
                                   start_node_id, start_copy_id, initial_offset,
-                                  reps_to_cache, reps_pool, FALSE,
+                                  reps_to_cache, reps_hash, reps_pool, FALSE,
                                   subpool));
           if (new_id && (svn_fs_fs__id_rev(new_id) == rev))
             dirent->id = svn_fs_fs__id_copy(new_id, pool);
@@ -5859,13 +6173,11 @@ write_final_rev(const svn_fs_id_t **new_
 
 #ifdef SVN_FS_FS_DELTIFY_DIRECTORIES
           SVN_ERR(write_hash_delta_rep(noderev->data_rep, file,
-                                       str_entries, fs, noderev, pool));
+                                       str_entries, fs, noderev, NULL,
+                                       pool));
 #else
-          SVN_ERR(get_file_offset(&noderev->data_rep->offset, file, pool));
-          SVN_ERR(write_hash_rep(&noderev->data_rep->size,
-                                 &noderev->data_rep->md5_checksum, file,
-                                 str_entries, pool));
-          noderev->data_rep->expanded_size = noderev->data_rep->size;
+          SVN_ERR(write_hash_rep(noderev->data_rep, file, str_entries,
+                                 fs, NULL, pool));
 #endif
         }
     }
@@ -5896,17 +6208,17 @@ write_final_rev(const svn_fs_id_t **new_
       apr_hash_t *proplist;
       SVN_ERR(svn_fs_fs__get_proplist(&proplist, fs, noderev, pool));
 
+      noderev->prop_rep->txn_id = NULL;
+      noderev->prop_rep->revision = rev;
+
 #ifdef SVN_FS_FS_DELTIFY_PROPS
       SVN_ERR(write_hash_delta_rep(noderev->prop_rep, file,
-                                   proplist, fs, noderev, pool));
+                                   proplist, fs, noderev, reps_hash,
+                                   pool));
 #else
-      SVN_ERR(get_file_offset(&noderev->prop_rep->offset, file, pool));
-      SVN_ERR(write_hash_rep(&noderev->prop_rep->size,
-                             &noderev->prop_rep->md5_checksum, file,
-                             proplist, pool));
+      SVN_ERR(write_hash_rep(noderev->prop_rep, file, proplist,
+                             fs, reps_hash, pool));
 #endif
-      noderev->prop_rep->txn_id = NULL;
-      noderev->prop_rep->revision = rev;
     }
 
 
@@ -5949,6 +6261,41 @@ write_final_rev(const svn_fs_id_t **new_
 
   noderev->id = new_id;
 
+  if (ffd->rep_sharing_allowed)
+    {
+      /* Save the data representation's hash in the rep cache. */
+      if (   noderev->data_rep && noderev->kind == svn_node_file
+          && noderev->data_rep->revision == rev)
+        {
+          SVN_ERR_ASSERT(reps_to_cache && reps_pool);
+          APR_ARRAY_PUSH(reps_to_cache, representation_t *)
+            = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
+        }
+
+      if (noderev->prop_rep && noderev->prop_rep->revision == rev)
+        {
+          /* Add new property reps to hash and on-disk cache. */
+          representation_t *copy
+            = svn_fs_fs__rep_copy(noderev->prop_rep, reps_pool);
+
+          SVN_ERR_ASSERT(reps_to_cache && reps_pool);
+          APR_ARRAY_PUSH(reps_to_cache, representation_t *) = copy;
+
+          apr_hash_set(reps_hash,
+                        copy->sha1_checksum->digest,
+                        APR_SHA1_DIGESTSIZE,
+                        copy);
+        }
+    }
+
+  /* don't serialize SHA1 for dirs to disk (waste of space) */
+  if (noderev->data_rep && noderev->kind == svn_node_dir)
+    noderev->data_rep->sha1_checksum = NULL;
+
+  /* don't serialize SHA1 for props to disk (waste of space) */
+  if (noderev->prop_rep)
+    noderev->prop_rep->sha1_checksum = NULL;
+
   /* Write out our new node-revision. */
   if (at_root)
     SVN_ERR(validate_root_noderev(fs, noderev, rev, pool));
@@ -5957,16 +6304,6 @@ write_final_rev(const svn_fs_id_t **new_
                                    svn_fs_fs__fs_supports_mergeinfo(fs),
                                    pool));
 
-  /* Save the data representation's hash in the rep cache. */
-  if (ffd->rep_sharing_allowed
-        && noderev->data_rep && noderev->kind == svn_node_file
-        && noderev->data_rep->revision == rev)
-    {
-      SVN_ERR_ASSERT(reps_to_cache && reps_pool);
-      APR_ARRAY_PUSH(reps_to_cache, representation_t *)
-        = svn_fs_fs__rep_copy(noderev->data_rep, reps_pool);
-    }
-
   /* Return our ID that references the revision file. */
   *new_id_p = noderev->id;
 
@@ -6174,6 +6511,7 @@ struct commit_baton {
   svn_fs_t *fs;
   svn_fs_txn_t *txn;
   apr_array_header_t *reps_to_cache;
+  apr_hash_t *reps_hash;
   apr_pool_t *reps_pool;
 };
 
@@ -6231,8 +6569,8 @@ commit_body(void *baton, apr_pool_t *poo
   root_id = svn_fs_fs__id_txn_create("0", "0", cb->txn->id, pool);
   SVN_ERR(write_final_rev(&new_root_id, proto_file, new_rev, cb->fs, root_id,
                           start_node_id, start_copy_id, initial_offset,
-                          cb->reps_to_cache, cb->reps_pool, TRUE,
-                          pool));
+                          cb->reps_to_cache, cb->reps_hash, cb->reps_pool,
+                          TRUE, pool));
 
   /* Write the changed-path information. */
   SVN_ERR(write_final_changed_path_info(&changed_path_offset, proto_file,
@@ -6406,11 +6744,13 @@ svn_fs_fs__commit(svn_revnum_t *new_rev_
   if (ffd->rep_sharing_allowed)
     {
       cb.reps_to_cache = apr_array_make(pool, 5, sizeof(representation_t *));
+      cb.reps_hash = apr_hash_make(pool);
       cb.reps_pool = pool;
     }
   else
     {
       cb.reps_to_cache = NULL;
+      cb.reps_hash = NULL;
       cb.reps_pool = NULL;
     }
 
@@ -6569,6 +6909,9 @@ svn_fs_fs__create(svn_fs_t *fs,
                                  "", pool));
     }
 
+  /* Create the revprop generation tracking file. */
+  SVN_ERR(increment_revprop_generation(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));
@@ -7701,7 +8044,7 @@ svn_fs_fs__pack(svn_fs_t *fs,
 
 /** Verifying. **/
 
-/* Body of svn_fs_fs__verify().
+/* Used by svn_fs_fs__verify().
    Implements svn_fs_fs__walk_rep_reference().walker.  */
 static svn_error_t *
 verify_walker(representation_t *rep,
@@ -7722,23 +8065,93 @@ svn_error_t *
 svn_fs_fs__verify(svn_fs_t *fs,
                   svn_cancel_func_t cancel_func,
                   void *cancel_baton,
+                  svn_revnum_t start,
+                  svn_revnum_t end,
                   apr_pool_t *pool)
 {
   fs_fs_data_t *ffd = fs->fsap_data;
   svn_boolean_t exists;
+  svn_revnum_t youngest = ffd->youngest_rev_cache; /* cache is current */
+  apr_pool_t *iterpool = svn_pool_create(pool);
 
   if (ffd->format < SVN_FS_FS__MIN_REP_SHARING_FORMAT)
     return SVN_NO_ERROR;
 
-  /* Do not attempt to walk the rep-cache database if its file does not exists,
-     since doing so would create it --- which may confuse the administrator. */
+  /* Input validation. */
+  if (! SVN_IS_VALID_REVNUM(start))
+    start = 0;
+  if (! SVN_IS_VALID_REVNUM(end))
+    end = youngest;
+  SVN_ERR(ensure_revision_exists(fs, start, iterpool));
+  SVN_ERR(ensure_revision_exists(fs, end, iterpool));
+
+  /* rep-cache verification. */
   SVN_ERR(svn_fs_fs__exists_rep_cache(&exists, fs, pool));
   if (exists)
-    /* Don't take any lock. */
+    /* Do not attempt to walk the rep-cache database if its file does not exist,
+       since doing so would create it --- which may confuse the administrator.
+       Don't take any lock. */
     SVN_ERR(svn_fs_fs__walk_rep_reference(fs, verify_walker, NULL,
                                           cancel_func, cancel_baton,
+                                          start, end,
                                           pool));
 
+  /* Issue #4129: bogus pred-counts on the root node-rev. */
+  {
+    svn_revnum_t i;
+    int predecessor_predecessor_count;
+
+    /* Compute PREDECESSOR_PREDECESSOR_COUNT. */
+    if (start == 0)
+      /* The value that passes the if() at the end of the loop. */
+      predecessor_predecessor_count = -1;
+    else
+      {
+        svn_fs_id_t *root_id;
+        node_revision_t *root_noderev;
+        SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, start-1, iterpool));
+        SVN_ERR(svn_fs_fs__get_node_revision(&root_noderev, fs, root_id,
+                                             iterpool));
+        predecessor_predecessor_count = root_noderev->predecessor_count;
+      }
+
+    for (i = start; i <= end; i++)
+      {
+        /* ### Caching.
+
+           svn_fs_fs__rev_get_root() consults caches, which in verify we
+           don't want.  But we can't easily bypass that, as
+           svn_fs_revision_root()+svn_fs_node_id()'s implementation uses
+           svn_fs_fs__rev_get_root() too.
+
+           ### A future revision will make fs_verify() disable caches when it
+           ### opens ffd.
+         */
+        svn_fs_id_t *root_id;
+        node_revision_t *root_noderev;
+
+        if ((i % 128) == 0) /* uneducated guess */
+          svn_pool_clear(iterpool);
+
+        /* Fetch ROOT_NODEREV. */
+        SVN_ERR(svn_fs_fs__rev_get_root(&root_id, fs, i, iterpool));
+        SVN_ERR(svn_fs_fs__get_node_revision(&root_noderev, fs, root_id,
+                                             iterpool));
+
+        /* Check correctness. (Compare validate_root_noderev().) */
+        if (1+predecessor_predecessor_count != root_noderev->predecessor_count)
+          return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+                                   _("predecessor count for "
+                                     "the root node-revision is wrong: "
+                                     "r%ld has %d, but r%ld has %d"),
+                                   i, root_noderev->predecessor_count,
+                                   i-1, predecessor_predecessor_count);
+
+        predecessor_predecessor_count = root_noderev->predecessor_count;
+      }
+  }
+
+  svn_pool_destroy(iterpool);
   return SVN_NO_ERROR;
 }
 
@@ -7902,7 +8315,7 @@ hotcopy_io_copy_dir_recursively(const ch
           else if (this_entry.filetype == APR_DIR) /* recurse */
             {
               const char *src_target;
-              
+
               /* Prevent infinite recursion by filtering off our
                  newly created destination path. */
               if (strcmp(src, dst_parent) == 0
@@ -8107,7 +8520,7 @@ hotcopy_remove_rev_files(svn_fs_t *dst_f
   for (rev = start_rev; rev < end_rev; rev++)
     {
       const char *rev_path;
-      
+
       svn_pool_clear(iterpool);
 
       /* If necessary, update paths for shard. */
@@ -8549,6 +8962,7 @@ hotcopy_create_empty_dest(svn_fs_t *src_
 {
   fs_fs_data_t *src_ffd = src_fs->fsap_data;
   fs_fs_data_t *dst_ffd = dst_fs->fsap_data;
+  svn_node_kind_t kind;
 
   dst_fs->path = apr_pstrdup(pool, dst_path);
 
@@ -8612,6 +9026,14 @@ hotcopy_create_empty_dest(svn_fs_t *src_
                                  "", pool));
     }
 
+  /* Copy the revprop generation file if it exists in SRC_FS. */
+  SVN_ERR(svn_io_check_path(path_revprop_generation(src_fs, pool),
+                            &kind, pool));
+  if (kind == svn_node_file)
+    SVN_ERR(svn_io_copy_file(path_revprop_generation(src_fs, pool),
+                             path_revprop_generation(dst_fs, pool),
+                             TRUE, pool));
+    
   dst_ffd->youngest_rev_cache = 0;
   return SVN_NO_ERROR;
 }

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/fs_fs.h
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/fs_fs.h?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/fs_fs.h (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/fs_fs.h Tue Mar  6 17:50:23 2012
@@ -42,6 +42,8 @@ svn_error_t *svn_fs_fs__upgrade(svn_fs_t
 svn_error_t *svn_fs_fs__verify(svn_fs_t *fs,
                                svn_cancel_func_t cancel_func,
                                void *cancel_baton,
+                               svn_revnum_t start,
+                               svn_revnum_t end,
                                apr_pool_t *pool);
 
 /* Copy the fsfs filesystem SRC_FS at SRC_PATH into a new copy DST_FS at
@@ -106,6 +108,12 @@ svn_error_t *svn_fs_fs__youngest_rev(svn
                                      svn_fs_t *fs,
                                      apr_pool_t *pool);
 
+/* Return an error iff REV does not exist in FS. */
+svn_error_t *
+svn_fs_fs__revision_exists(svn_revnum_t rev,
+                           svn_fs_t *fs,
+                           apr_pool_t *pool);
+
 /* Set *ROOT_ID to the node-id for the root of revision REV in
    filesystem FS.  Do any allocations in POOL. */
 svn_error_t *svn_fs_fs__rev_get_root(svn_fs_id_t **root_id,

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/rep-cache-db.sql
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/rep-cache-db.sql?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/rep-cache-db.sql (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/rep-cache-db.sql Tue Mar  6 17:50:23 2012
@@ -33,6 +33,11 @@ CREATE TABLE rep_cache (
   expanded_size INTEGER NOT NULL
   );
 
+/* There isn't an implicit index on a TEXT column, so create an explicit one. */
+CREATE INDEX I_HASH on REP_CACHE (hash);
+
+/* The index didn't exist until 1.7.5; therefore, some USER_VERSION=1
+   rep-cache.db files out there DO NOT contain an I_HASH index. */
 PRAGMA USER_VERSION = 1;
 
 
@@ -47,9 +52,15 @@ INSERT OR FAIL INTO rep_cache (hash, rev
 VALUES (?1, ?2, ?3, ?4, ?5)
 
 
--- STMT_GET_ALL_REPS
+-- STMT_GET_REPS_FOR_RANGE
 SELECT hash, revision, offset, size, expanded_size
 FROM rep_cache
+WHERE revision >= ?1 AND revision <= ?2
+
+
+-- STMT_GET_MAX_REV
+SELECT MAX(revision)
+FROM rep_cache
 
 
 -- STMT_DEL_REPS_YOUNGER_THAN_REV

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/rep-cache.c
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/rep-cache.c?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/rep-cache.c (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/rep-cache.c Tue Mar  6 17:50:23 2012
@@ -56,24 +56,9 @@ rep_has_been_born(representation_t *rep,
                   svn_fs_t *fs,
                   apr_pool_t *pool)
 {
-  fs_fs_data_t *ffd = fs->fsap_data;
-  svn_revnum_t youngest;
-
   SVN_ERR_ASSERT(rep);
 
-  youngest = ffd->youngest_rev_cache;
-  if (youngest < rep->revision)
-  {
-    /* Stale cache. */
-    SVN_ERR(svn_fs_fs__youngest_rev(&youngest, fs, pool));
-
-    /* Fresh cache. */
-    if (youngest < rep->revision)
-      return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
-                               _("Youngest revision is r%ld, but "
-                                 "rep-cache contains r%ld"),
-                               youngest, rep->revision);
-  }
+  SVN_ERR(svn_fs_fs__revision_exists(rep->revision, fs, pool));
 
   return SVN_NO_ERROR;
 }
@@ -141,11 +126,13 @@ svn_error_t *
 svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
                               svn_error_t *(*walker)(representation_t *,
                                                      void *,
-                                                     svn_fs_t *, 
+                                                     svn_fs_t *,
                                                      apr_pool_t *),
                               void *walker_baton,
                               svn_cancel_func_t cancel_func,
                               void *cancel_baton,
+                              svn_revnum_t start,
+                              svn_revnum_t end,
                               apr_pool_t *pool)
 {
   fs_fs_data_t *ffd = fs->fsap_data;
@@ -161,9 +148,25 @@ svn_fs_fs__walk_rep_reference(svn_fs_t *
   if (! ffd->rep_cache_db)
     SVN_ERR(svn_fs_fs__open_rep_cache(fs, pool));
 
+  /* Check global invariants. */
+  if (start == 0)
+    {
+      svn_sqlite__stmt_t *stmt2;
+      svn_revnum_t max;
+
+      SVN_ERR(svn_sqlite__get_statement(&stmt2, ffd->rep_cache_db,
+                                        STMT_GET_MAX_REV));
+      SVN_ERR(svn_sqlite__step(&have_row, stmt2));
+      max = svn_sqlite__column_revnum(stmt2, 0);
+      SVN_ERR(svn_fs_fs__revision_exists(max, fs, iterpool));
+      SVN_ERR(svn_sqlite__reset(stmt2));
+    }
+
   /* Get the statement. (There are no arguments to bind.) */
   SVN_ERR(svn_sqlite__get_statement(&stmt, ffd->rep_cache_db,
-                                    STMT_GET_ALL_REPS));
+                                    STMT_GET_REPS_FOR_RANGE));
+  SVN_ERR(svn_sqlite__bindf(stmt, "rr",
+                            start, end));
 
   /* Walk the cache entries. */
   SVN_ERR(svn_sqlite__step(&have_row, stmt));
@@ -171,7 +174,7 @@ svn_fs_fs__walk_rep_reference(svn_fs_t *
     {
       representation_t *rep;
       const char *sha1_digest;
-      
+
       /* Clear ITERPOOL occasionally. */
       if (iterations++ % 16 == 0)
         svn_pool_clear(iterpool);
@@ -191,10 +194,6 @@ svn_fs_fs__walk_rep_reference(svn_fs_t *
       rep->size = svn_sqlite__column_int64(stmt, 3);
       rep->expanded_size = svn_sqlite__column_int64(stmt, 4);
 
-      /* Sanity check. */
-      if (rep)
-        SVN_ERR(rep_has_been_born(rep, fs, iterpool));
-
       /* Walk. */
       SVN_ERR(walker(rep, walker_baton, fs, iterpool));
 

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/rep-cache.h
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/rep-cache.h?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/rep-cache.h (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/rep-cache.h Tue Mar  6 17:50:23 2012
@@ -50,11 +50,13 @@ svn_error_t *
 svn_fs_fs__walk_rep_reference(svn_fs_t *fs,
                               svn_error_t *(*walker)(representation_t *rep,
                                                      void *walker_baton,
-                                                     svn_fs_t *fs, 
+                                                     svn_fs_t *fs,
                                                      apr_pool_t *scratch_pool),
                               void *walker_baton,
                               svn_cancel_func_t cancel_func,
                               void *cancel_baton,
+                              svn_revnum_t start,
+                              svn_revnum_t end,
                               apr_pool_t *pool);
 
 /* Return the representation REP in FS which has fulltext CHECKSUM.

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/structure
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/structure?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/structure (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/structure Tue Mar  6 17:50:23 2012
@@ -266,7 +266,7 @@ Within a revision:
   "r<rev>/<offset>" txn-id fields.
 
   In Format 3 and above, this uniqueness is done by changing a temporary
-  id of "_<base36>" to "<rev>-<base36>".  Note that this means that the
+  id of "_<base36>" to "<base36>-<rev>".  Note that this means that the
   originating revision of a line of history or a copy can be determined
   by looking at the node ID.
 

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/temp_serializer.c
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/temp_serializer.c?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/temp_serializer.c (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/temp_serializer.c Tue Mar  6 17:50:23 2012
@@ -136,7 +136,7 @@ serialize_svn_string(svn_temp_serializer
    * Thus, we cannot use svn_temp_serializer__add_string. */
   svn_temp_serializer__push(context,
                             (const void * const *)&string->data,
-                            string->len);
+                            string->len + 1);
 
   /* back to the caller's nesting level */
   svn_temp_serializer__pop(context);
@@ -579,6 +579,142 @@ svn_fs_fs__deserialize_manifest(void **o
   return SVN_NO_ERROR;
 }
 
+/* Auxilliary structure representing the content of a properties hash.
+   This structure is much easier to (de-)serialize than an apr_hash.
+ */
+typedef struct properties_data_t
+{
+  /* number of entries in the hash */
+  apr_size_t count;
+
+  /* reference to the keys */
+  const char **keys;
+
+  /* reference to the values */
+  const svn_string_t **values;
+} properties_data_t;
+
+/* Serialize COUNT C-style strings from *STRINGS into CONTEXT. */
+static void
+serialize_cstring_array(svn_temp_serializer__context_t *context,
+                        const char ***strings,
+                        apr_size_t count)
+{
+  apr_size_t i;
+  const char **entries = *strings;
+  
+  /* serialize COUNT entries pointers (the array) */
+  svn_temp_serializer__push(context,
+                            (const void * const *)strings,
+                            count * sizeof(const char*));
+
+  /* serialize array elements */
+  for (i = 0; i < count; ++i)
+    svn_temp_serializer__add_string(context, &entries[i]);
+
+  svn_temp_serializer__pop(context);
+}
+
+/* Serialize COUNT svn_string_t* items from *STRINGS into CONTEXT. */
+static void
+serialize_svn_string_array(svn_temp_serializer__context_t *context,
+                           const svn_string_t ***strings,
+                           apr_size_t count)
+{
+  apr_size_t i;
+  const svn_string_t **entries = *strings;
+  
+  /* serialize COUNT entries pointers (the array) */
+  svn_temp_serializer__push(context,
+                            (const void * const *)strings,
+                            count * sizeof(const char*));
+
+  /* serialize array elements */
+  for (i = 0; i < count; ++i)
+    serialize_svn_string(context, &entries[i]);
+
+  svn_temp_serializer__pop(context);
+}
+
+svn_error_t *
+svn_fs_fs__serialize_properties(char **data,
+                                apr_size_t *data_len,
+                                void *in,
+                                apr_pool_t *pool)
+{
+  apr_hash_t *hash = in;
+  properties_data_t properties;
+  svn_temp_serializer__context_t *context;
+  apr_hash_index_t *hi;
+  svn_stringbuf_t *serialized;
+  apr_size_t i;
+
+  /* create our auxilliary data structure */
+  properties.count = apr_hash_count(hash);
+  properties.keys = apr_palloc(pool, sizeof(const char*) * (properties.count + 1));
+  properties.values = apr_palloc(pool, sizeof(const char*) * properties.count);
+  
+  /* populate it with the hash entries */
+  for (hi = apr_hash_first(pool, hash), i=0; hi; hi = apr_hash_next(hi), ++i)
+    {
+      properties.keys[i] = svn__apr_hash_index_key(hi);
+      properties.values[i] = svn__apr_hash_index_val(hi);
+    }
+  
+  /* serialize it */
+  context = svn_temp_serializer__init(&properties,
+                                      sizeof(properties),
+                                      properties.count * 100,
+                                      pool);
+
+  properties.keys[i] = "";
+  serialize_cstring_array(context, &properties.keys, properties.count + 1);
+  serialize_svn_string_array(context, &properties.values, properties.count);
+
+  /* return the serialized result */
+  serialized = svn_temp_serializer__get(context);
+
+  *data = serialized->data;
+  *data_len = serialized->len;
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_fs__deserialize_properties(void **out,
+                                  char *data,
+                                  apr_size_t data_len,
+                                  apr_pool_t *pool)
+{
+  apr_hash_t *hash = apr_hash_make(pool);
+  properties_data_t *properties = (properties_data_t *)data;
+  size_t i;
+
+  /* de-serialize our auxilliary data structure */
+  svn_temp_deserializer__resolve(properties, (void**)&properties->keys);
+  svn_temp_deserializer__resolve(properties, (void**)&properties->values);
+  
+  /* de-serialize each entry and put it into the hash */
+  for (i = 0; i < properties->count; ++i)
+    {
+      apr_size_t len = properties->keys[i+1] - properties->keys[i] - 1;
+      svn_temp_deserializer__resolve(properties->keys, 
+                                     (void**)&properties->keys[i]);
+      
+      deserialize_svn_string(properties->values, 
+                             (svn_string_t **)&properties->values[i]);
+      
+      apr_hash_set(hash, 
+                   properties->keys[i], len, 
+                   properties->values[i]);
+    }
+
+  /* done */
+  *out = hash;
+
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_fs_fs__serialize_id(char **data,
                         apr_size_t *data_len,

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/temp_serializer.h
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/temp_serializer.h?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/temp_serializer.h (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/temp_serializer.h Tue Mar  6 17:50:23 2012
@@ -113,6 +113,26 @@ svn_fs_fs__deserialize_manifest(void **o
                                 apr_pool_t *pool);
 
 /**
+ * Implements #svn_cache__serialize_func_t for a properties hash
+ * (@a in is an #apr_hash_t of svn_string_t elements, keyed by const char*).
+ */
+svn_error_t *
+svn_fs_fs__serialize_properties(char **data,
+                                apr_size_t *data_len,
+                                void *in,
+                                apr_pool_t *pool);
+
+/**
+ * Implements #svn_cache__deserialize_func_t for a properties hash
+ * (@a *out is an #apr_hash_t of svn_string_t elements, keyed by const char*).
+ */
+svn_error_t *
+svn_fs_fs__deserialize_properties(void **out,
+                                  char *data,
+                                  apr_size_t data_len,
+                                  apr_pool_t *pool);
+
+/**
  * Implements #svn_cache__serialize_func_t for #svn_fs_id_t
  */
 svn_error_t *

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/tree.c
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/tree.c?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/tree.c (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_fs_fs/tree.c Tue Mar  6 17:50:23 2012
@@ -115,7 +115,7 @@ typedef struct fs_rev_root_data_t
 typedef struct fs_txn_root_data_t
 {
   /* Cache of txn DAG nodes (without their nested noderevs, because
-   * it's mutable). */
+   * it's mutable). Same keys/values as ffd->rev_node_cache. */
   svn_cache__t *txn_node_cache;
 } fs_txn_root_data_t;
 
@@ -2807,8 +2807,10 @@ find_youngest_copyroot(svn_revnum_t *rev
                        parent_path_t *parent_path,
                        apr_pool_t *pool)
 {
-  svn_revnum_t rev_mine, rev_parent = -1;
-  const char *path_mine, *path_parent;
+  svn_revnum_t rev_mine;
+  svn_revnum_t rev_parent = SVN_INVALID_REVNUM;
+  const char *path_mine;
+  const char *path_parent = NULL;
 
   /* First find our parent's youngest copyroot. */
   if (parent_path->parent)

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra/ra_loader.c
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra/ra_loader.c?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra/ra_loader.c (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra/ra_loader.c Tue Mar  6 17:50:23 2012
@@ -1304,6 +1304,15 @@ svn_ra_print_ra_libraries(svn_stringbuf_
 }
 
 
+svn_error_t *
+svn_ra__register_editor_shim_callbacks(svn_ra_session_t *session,
+                                       svn_delta_shim_callbacks_t *callbacks)
+{
+  SVN_ERR(session->vtable->register_editor_shim_callbacks(session, callbacks));
+  return SVN_NO_ERROR;
+}
+
+
 /* Return the library version number. */
 const svn_version_t *
 svn_ra_version(void)

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra/ra_loader.h
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra/ra_loader.h?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra/ra_loader.h (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra/ra_loader.h Tue Mar  6 17:50:23 2012
@@ -293,6 +293,8 @@ typedef struct svn_ra__vtable_t {
                                   svn_revnum_t end_revision,
                                   svn_revnum_t *revision_deleted,
                                   apr_pool_t *pool);
+  svn_error_t *(*register_editor_shim_callbacks)(svn_ra_session_t *session,
+                                    svn_delta_shim_callbacks_t *callbacks);
 
 } svn_ra__vtable_t;
 

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_local/ra_plugin.c
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_local/ra_plugin.c?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_local/ra_plugin.c (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_local/ra_plugin.c Tue Mar  6 17:50:23 2012
@@ -153,7 +153,7 @@ cache_init(void *baton, apr_pool_t *pool
       SVN_ERR(svn_error_quick_wrap(svn_cstring_atoui64(&memory_cache_size,
                                                        memory_cache_size_str),
                                    _("memory-cache-size invalid")));
-      settings.cache_size = 1024 * 1024 * memory_cache_size; 
+      settings.cache_size = 1024 * 1024 * memory_cache_size;
       svn_cache_config_set(&settings);
     }
 
@@ -1505,6 +1505,15 @@ svn_ra_local__get_deleted_rev(svn_ra_ses
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+svn_ra_local__register_editor_shim_callbacks(svn_ra_session_t *session,
+                                    svn_delta_shim_callbacks_t *callbacks)
+{
+  /* This is currenly a no-op, since we don't provide our own editor, just
+     use the one the libsvn_repos hands back to us. */
+  return SVN_NO_ERROR;
+}
+
 /*----------------------------------------------------------------*/
 
 static const svn_version_t *
@@ -1551,7 +1560,8 @@ static const svn_ra__vtable_t ra_local_v
   svn_ra_local__replay,
   svn_ra_local__has_capability,
   svn_ra_local__replay_range,
-  svn_ra_local__get_deleted_rev
+  svn_ra_local__get_deleted_rev,
+  svn_ra_local__register_editor_shim_callbacks
 };
 
 

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/commit.c
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/commit.c?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/commit.c (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/commit.c Tue Mar  6 17:50:23 2012
@@ -1570,8 +1570,6 @@ svn_error_t * svn_ra_neon__get_commit_ed
   svn_delta_editor_t *commit_editor;
   commit_ctx_t *cc;
   apr_hash_index_t *hi;
-  svn_delta_shim_callbacks_t *shim_callbacks =
-                                svn_delta_shim_callbacks_default(pool);
 
   /* Build the main commit editor's baton. */
   cc = apr_pcalloc(pool, sizeof(*cc));
@@ -1622,7 +1620,7 @@ svn_error_t * svn_ra_neon__get_commit_ed
   *edit_baton = cc;
 
   SVN_ERR(svn_editor__insert_shims(editor, edit_baton, *editor, *edit_baton,
-                                   shim_callbacks, pool, pool));
+                                   ras->shim_callbacks, pool, pool));
 
   return SVN_NO_ERROR;
 }

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/log.c
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/log.c?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/log.c (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/log.c Tue Mar  6 17:50:23 2012
@@ -166,7 +166,7 @@ log_start_element(int *elem, void *baton
       lb->want_cdata = lb->cdata;
       svn_stringbuf_setempty(lb->cdata);
       lb->cdata_encoding = NULL;
-          
+
       /* Some tags might contain encoded CDATA. */
       if ((elm->id == ELEM_comment) ||
           (elm->id == ELEM_creator_displayname) ||
@@ -280,7 +280,7 @@ maybe_decode_log_cdata(const svn_string_
   return SVN_NO_ERROR;
 }
 
-                       
+
 
 /*
  * This implements the `svn_ra_neon__xml_endelm_cb' prototype.

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/merge.c
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/merge.c?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/merge.c (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/merge.c Tue Mar  6 17:50:23 2012
@@ -576,29 +576,22 @@ svn_error_t * svn_ra_neon__assemble_lock
                                                    apr_hash_t *lock_tokens,
                                                    apr_pool_t *pool)
 {
-  apr_hash_index_t *hi;
-  apr_size_t buf_size;
-  const char *closing_tag = "</S:lock-token-list>";
-  apr_size_t closing_tag_size = strlen(closing_tag);
-  apr_pool_t *tmppool = svn_pool_create(pool);
-  apr_hash_t *xml_locks = apr_hash_make(tmppool);
-  svn_stringbuf_t *lockbuf = svn_stringbuf_create
-    ("<S:lock-token-list xmlns:S=\"" SVN_XML_NAMESPACE "\">" DEBUG_CR, pool);
-
-  buf_size = lockbuf->len;
-
+#define SVN_LOCK_TOKEN_LIST \
+          "<S:lock-token-list xmlns:S=\"" SVN_XML_NAMESPACE "\">" DEBUG_CR
 #define SVN_LOCK "<S:lock>" DEBUG_CR
-#define SVN_LOCK_LEN sizeof(SVN_LOCK)-1
 #define SVN_LOCK_CLOSE "</S:lock>" DEBUG_CR
-#define SVN_LOCK_CLOSE_LEN sizeof(SVN_LOCK_CLOSE)-1
 #define SVN_LOCK_PATH "<S:lock-path>"
-#define SVN_LOCK_PATH_LEN sizeof(SVN_LOCK_PATH)-1
 #define SVN_LOCK_PATH_CLOSE "</S:lock-path>" DEBUG_CR
-#define SVN_LOCK_PATH_CLOSE_LEN sizeof(SVN_LOCK_CLOSE)-1
 #define SVN_LOCK_TOKEN "<S:lock-token>"
-#define SVN_LOCK_TOKEN_LEN sizeof(SVN_LOCK_TOKEN)-1
 #define SVN_LOCK_TOKEN_CLOSE "</S:lock-token>" DEBUG_CR
-#define SVN_LOCK_TOKEN_CLOSE_LEN sizeof(SVN_LOCK_TOKEN_CLOSE)-1
+#define SVN_LOCK_TOKEN_LIST_CLOSE "</S:lock-token-list>"
+#define SVN_LEN(str) (sizeof(str) - 1)
+
+  apr_hash_index_t *hi;
+  apr_pool_t *tmppool = svn_pool_create(pool);
+  apr_hash_t *xml_locks = apr_hash_make(tmppool);
+  svn_stringbuf_t *lockbuf = svn_stringbuf_create(SVN_LOCK_TOKEN_LIST, pool);
+  apr_size_t buf_size = lockbuf->len;
 
   /* First, figure out how much string data we're talking about,
      and allocate a stringbuf big enough to hold it all... we *never*
@@ -622,17 +615,17 @@ svn_error_t * svn_ra_neon__assemble_lock
       apr_hash_set(xml_locks, lock_path_xml->data, lock_path_xml->len, val);
 
       /* Now, on with the stringbuf calculations. */
-      buf_size += SVN_LOCK_LEN;
-      buf_size += SVN_LOCK_PATH_LEN;
+      buf_size += SVN_LEN(SVN_LOCK);
+      buf_size += SVN_LEN(SVN_LOCK_PATH);
       buf_size += lock_path_xml->len;
-      buf_size += SVN_LOCK_PATH_CLOSE_LEN;
-      buf_size += SVN_LOCK_TOKEN_LEN;
+      buf_size += SVN_LEN(SVN_LOCK_PATH_CLOSE);
+      buf_size += SVN_LEN(SVN_LOCK_TOKEN);
       buf_size += strlen(val);
-      buf_size += SVN_LOCK_TOKEN_CLOSE_LEN;
-      buf_size += SVN_LOCK_CLOSE_LEN;
+      buf_size += SVN_LEN(SVN_LOCK_TOKEN_CLOSE);
+      buf_size += SVN_LEN(SVN_LOCK_CLOSE);
     }
 
-  buf_size += closing_tag_size;
+  buf_size += SVN_LEN(SVN_LOCK_TOKEN_LIST_CLOSE);
 
   svn_stringbuf_ensure(lockbuf, buf_size + 1);
 
@@ -659,25 +652,25 @@ svn_error_t * svn_ra_neon__assemble_lock
       svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_CLOSE);
     }
 
-  svn_stringbuf_appendcstr(lockbuf, closing_tag);
+  svn_stringbuf_appendcstr(lockbuf, SVN_LOCK_TOKEN_LIST_CLOSE);
+
+  /* Check our size calculation was correct */
+  SVN_ERR_ASSERT(lockbuf->len == buf_size);
 
+  *body = lockbuf;
+
+  svn_pool_destroy(tmppool);
+  return SVN_NO_ERROR;
+
+#undef SVN_LOCK_TOKEN_LIST
 #undef SVN_LOCK
-#undef SVN_LOCK_LEN
 #undef SVN_LOCK_CLOSE
-#undef SVN_LOCK_CLOSE_LEN
 #undef SVN_LOCK_PATH
-#undef SVN_LOCK_PATH_LEN
 #undef SVN_LOCK_PATH_CLOSE
-#undef SVN_LOCK_PATH_CLOSE_LEN
 #undef SVN_LOCK_TOKEN
-#undef SVN_LOCK_TOKEN_LEN
 #undef SVN_LOCK_TOKEN_CLOSE
-#undef SVN_LOCK_TOKEN_CLOSE_LEN
-
-  *body = lockbuf;
-
-  svn_pool_destroy(tmppool);
-  return SVN_NO_ERROR;
+#undef SVN_LOCK_TOKEN_LIST_CLOSE
+#undef SVN_LEN
 }
 
 

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/ra_neon.h
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/ra_neon.h?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/ra_neon.h (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/ra_neon.h Tue Mar  6 17:50:23 2012
@@ -131,6 +131,9 @@ typedef struct svn_ra_neon__session_t {
      deadprop-count property.*/
   svn_tristate_t supports_deadprop_count;
 
+  /* Ev2 shim callbacks. */
+  svn_delta_shim_callbacks_t *shim_callbacks;
+
   /*** HTTP v2 protocol stuff. ***
    *
    * We assume that if mod_dav_svn sends one of the special v2 OPTIONs
@@ -1181,6 +1184,10 @@ svn_ra_neon__get_deadprop_count_support(
                                         const char *final_url,
                                         apr_pool_t *pool);
 
+svn_error_t *
+svn_ra_neon__register_editor_shim_callbacks(svn_ra_session_t *session,
+                                    svn_delta_shim_callbacks_t *callbacks);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */

Modified: subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/session.c
URL: http://svn.apache.org/viewvc/subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/session.c?rev=1297604&r1=1297603&r2=1297604&view=diff
==============================================================================
--- subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/session.c (original)
+++ subversion/branches/reintegrate-keep-alive/subversion/libsvn_ra_neon/session.c Tue Mar  6 17:50:23 2012
@@ -1229,7 +1229,8 @@ static const svn_ra__vtable_t neon_vtabl
   svn_ra_neon__replay,
   svn_ra_neon__has_capability,
   svn_ra_neon__replay_range,
-  svn_ra_neon__get_deleted_rev
+  svn_ra_neon__get_deleted_rev,
+  svn_ra_neon__register_editor_shim_callbacks
 };
 
 svn_error_t *