Branko Čibej <br...@apache.org> writes:

> Still missing is a mechanism for the libsvn_wc (and possibly
> libsvn_client) to determine the capabilities of the working copy at
> runtime (this will be needed for deciding whether to use compressed
> pristines).

FWIW, I tried the idea of using LZ4 to compress the pristines and storing small
pristines as blobs in the `PRISTINE` table.  I was particularly interested in
how such change would affect the performance and what kind of obstacles
would have to be dealt with.

In the attachment you will find a more or less functional implementation of
this idea that might be useful to some extent.  The patch is a proof of
concept: it doesn't include the WC compatibility bits and most certainly
doesn't have everything necessary in place.  But in the meanwhile, I think
that is might give a good approximation of what can be expected from the
approach.

The patch applies to the `better-pristines` branch.

A couple of observations:

 - As expected, the combined size of the pristines is halved when the data
   itself is compressible, thus making the working copy 25% smaller.

 - A variety of the callers currently access the pristine contents by reading
   the corresponding files.  That doesn't work in case of compressed pristines
   or pristines stored as BLOBs.

   I think that ideally we would want to use streams as much as possible, and
   only spill the uncompressed pristine contents to temporary files when we
   need to pass them to external tools, etc.; and that temporary files need
   to be backed by a work queue to avoid leaving them in place in case of an
   application crash.

   The patch does that kind of plumbing to some extent, but that part of the
   work is not complete.  The starting point is around wc_db_pristine.c:
   svn_wc__db_pristine_get_path().

 - Using BLOBs to store the pristine contents didn't have a measurable impact
   on the speed of the WC operations such as checkout in my experiments on
   Windows.  These experiments were not comprehensive, and also I didn't run
   the tests on *nix.

 - There's also the deprecated svn_wc_get_pristine_copy_path() public API that
   would require plumbing to maintain compatibility; the patch performs it by
   spilling the pristine contents result into a temporary file whose lifetime
   is attached to the `result_pool`.

 (I probably won't be able to continue the work on this patch in the nearby
 future; posting this in case it might be useful.)


Thanks,
Evgeny Kotkov
Index: notes/wc-ng/pristine-store
===================================================================
--- notes/wc-ng/pristine-store  (revision 1844566)
+++ notes/wc-ng/pristine-store  (working copy)
@@ -1,6 +1,7 @@
 This spec defines how the Pristine Store works (part A) and how the WC
 uses it (part B).
 
+TODO: Update
 
 A. THE PRISTINE STORE
 =====================
Index: subversion/include/private/svn_io_private.h
===================================================================
--- subversion/include/private/svn_io_private.h (revision 1844566)
+++ subversion/include/private/svn_io_private.h (working copy)
@@ -119,14 +119,6 @@ svn_error_t *
 svn_stream__install_delete(svn_stream_t *install_stream,
                            apr_pool_t *scratch_pool);
 
-/* Optimized apr_file_stat / apr_file_info_get operating on a closed
-   install stream */
-svn_error_t *
-svn_stream__install_get_info(apr_finfo_t *finfo,
-                             svn_stream_t *install_stream,
-                             apr_int32_t wanted,
-                             apr_pool_t *scratch_pool);
-
 /* Internal version of svn_stream_from_aprfile2() supporting the
    additional TRUNCATE_ON_SEEK argument. */
 svn_stream_t *
Index: subversion/include/private/svn_subr_private.h
===================================================================
--- subversion/include/private/svn_subr_private.h       (revision 1844566)
+++ subversion/include/private/svn_subr_private.h       (working copy)
@@ -591,6 +591,12 @@ svn__decompress_lz4(const void *data, apr_size_t l
                     svn_stringbuf_t *out,
                     apr_size_t limit);
 
+svn_stream_t *
+svn_stream__lz4_compress(svn_stream_t *stream, apr_pool_t *pool);
+
+svn_stream_t *
+svn_stream__lz4_decompress(svn_stream_t *stream, apr_pool_t *pool);
+
 /** @} */
 
 /**
Index: subversion/libsvn_subr/compress_lz4.c
===================================================================
--- subversion/libsvn_subr/compress_lz4.c       (revision 1844566)
+++ subversion/libsvn_subr/compress_lz4.c       (working copy)
@@ -23,9 +23,14 @@
 
 #include <assert.h>
 
+#define APR_WANT_BYTEFUNC
+#include <apr_want.h>
+
+#include "private/svn_string_private.h"
 #include "private/svn_subr_private.h"
 
 #include "svn_private_config.h"
+#include "svn_pools.h"
 
 #ifdef SVN_INTERNAL_LZ4
 #include "lz4/lz4internal.h"
@@ -142,3 +147,322 @@ svn_lz4__runtime_version(void)
 {
   return LZ4_versionNumber();
 }
+
+typedef svn_error_t *
+(*stream_generator_next_substream_fn_t)(svn_stream_t **stream_p,
+                                        void *baton,
+                                        apr_pool_t *pool);
+
+typedef struct stream_generator_baton_t
+{
+  stream_generator_next_substream_fn_t next_fn;
+  void *next_fn_baton;
+  svn_stream_t *substream;
+  apr_pool_t *substream_pool;
+  svn_boolean_t hit_eof;
+} stream_generator_baton_t;
+
+static svn_error_t *
+read_helper(svn_stream_t *stream,
+            char *buffer,
+            apr_size_t *len,
+            svn_boolean_t *hit_eof)
+{
+  apr_size_t to_read = *len;
+  SVN_ERR(svn_stream_read_full(stream, buffer, len));
+  *hit_eof = *len < to_read;
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+stream_generator_read_full_fn(void *baton, char *buffer, apr_size_t *len)
+{
+  stream_generator_baton_t *b = baton;
+  apr_size_t remaining = *len;
+
+  *len = 0;
+  while (remaining && !b->hit_eof)
+    {
+      apr_size_t read_len;
+      svn_boolean_t hit_eof;
+
+      if (!b->substream)
+        {
+          svn_stream_t *substream;
+
+          SVN_ERR(b->next_fn(&substream, b->next_fn_baton, b->substream_pool));
+
+          if (substream)
+            b->substream = substream;
+          else
+            {
+              b->hit_eof = TRUE;
+              break;
+            }
+        }
+
+      read_len = remaining;
+      SVN_ERR(read_helper(b->substream, buffer, &read_len, &hit_eof));
+      buffer += read_len;
+      remaining -= read_len;
+      *len += read_len;
+
+      if (hit_eof)
+        {
+          SVN_ERR(svn_stream_close(b->substream));
+          b->substream = NULL;
+          svn_pool_clear(b->substream_pool);
+        }
+    }
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+stream_generator_close_fn(void *baton)
+{
+  stream_generator_baton_t *b = baton;
+
+  if (b->substream)
+    {
+      SVN_ERR(svn_stream_close(b->substream));
+      b->substream = NULL;
+    }
+
+  svn_pool_destroy(b->substream_pool);
+  return SVN_NO_ERROR;
+}
+
+static svn_stream_t *
+stream_generator_create(stream_generator_next_substream_fn_t next_fn,
+                        void *next_fn_baton,
+                        apr_pool_t *pool)
+{
+  stream_generator_baton_t *baton;
+  svn_stream_t *stream;
+
+  baton = apr_pcalloc(pool, sizeof(*baton));
+  baton->next_fn = next_fn;
+  baton->next_fn_baton = next_fn_baton;
+  baton->substream = NULL;
+  baton->substream_pool = svn_pool_create(pool);
+  baton->hit_eof = FALSE;
+
+  stream = svn_stream_create(baton, pool);
+  svn_stream_set_read2(stream, NULL, stream_generator_read_full_fn);
+  svn_stream_set_close(stream, stream_generator_close_fn);
+
+  return stream;
+}
+
+typedef struct lz4_compress_stream_baton_t
+{
+  svn_stream_t *stream;
+  svn_stringbuf_t *buf;
+  svn_stringbuf_t *tmpbuf;
+} lz4_compress_stream_baton_t;
+
+static const apr_size_t LZ4_STREAM_MAX_BLOCKLEN = 128 * 1024;
+
+static svn_error_t *
+write_helper(svn_stream_t *stream, const void *data, apr_size_t len)
+{
+  return svn_stream_write(stream, data, &len);
+}
+
+static svn_error_t *
+write_uint32(svn_stream_t *stream, apr_uint32_t val)
+{
+  apr_uint32_t nval = htonl(val);
+
+  SVN_ERR(write_helper(stream, &nval, sizeof(nval)));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+read_uint32(apr_uint32_t *val_p, svn_stream_t *stream)
+{
+  apr_uint32_t nval;
+  apr_size_t len = sizeof(nval);
+
+  SVN_ERR(svn_stream_read_full(stream, (char*)&nval, &len));
+  if (len < sizeof(nval))
+    return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL, NULL);
+
+  *val_p = ntohl(nval);
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+flush_output(lz4_compress_stream_baton_t *b)
+{
+  int uncompressed_len = (int)b->buf->len;
+  int compressed_len;
+
+  if (!uncompressed_len)
+    return SVN_NO_ERROR;
+
+  svn_stringbuf_ensure(b->tmpbuf, LZ4_compressBound(uncompressed_len));
+  compressed_len = LZ4_compress_default(b->buf->data,
+                                        b->tmpbuf->data,
+                                        uncompressed_len,
+                                        (int)b->tmpbuf->blocksize);
+  if (!compressed_len)
+    return svn_error_create(SVN_ERR_LZ4_COMPRESSION_FAILED, NULL, NULL);
+
+  if (compressed_len >= uncompressed_len)
+    {
+      SVN_ERR(write_uint32(b->stream, uncompressed_len));
+      SVN_ERR(write_uint32(b->stream, uncompressed_len));
+      SVN_ERR(write_helper(b->stream, b->buf->data, uncompressed_len));
+    }
+  else
+    {
+      SVN_ERR(write_uint32(b->stream, uncompressed_len));
+      SVN_ERR(write_uint32(b->stream, compressed_len));
+      SVN_ERR(write_helper(b->stream, b->tmpbuf->data, compressed_len));
+    }
+
+  b->buf->len = 0;
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+lz4_compress_stream_write_fn(void *baton, const char *data, apr_size_t *len)
+{
+  lz4_compress_stream_baton_t *b = baton;
+  apr_size_t remaining = *len;
+
+  while (remaining)
+    {
+      if (b->buf->len + remaining < LZ4_STREAM_MAX_BLOCKLEN)
+        {
+          svn_stringbuf_appendbytes(b->buf, data, remaining);
+          data += remaining;
+          remaining = 0;
+        }
+      else
+        {
+          apr_size_t to_copy = LZ4_STREAM_MAX_BLOCKLEN - b->buf->len;
+
+          svn_stringbuf_appendbytes(b->buf, data, to_copy);
+          data += to_copy;
+          remaining -= to_copy;
+          SVN_ERR(flush_output(b));
+        }
+    }
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+lz4_compress_stream_close_fn(void *baton)
+{
+  lz4_compress_stream_baton_t *b = baton;
+
+  SVN_ERR(flush_output(b));
+  SVN_ERR(write_uint32(b->stream, 0));
+  SVN_ERR(svn_stream_close(b->stream));
+
+  return SVN_NO_ERROR;
+}
+
+svn_stream_t *
+svn_stream__lz4_compress(svn_stream_t *stream, apr_pool_t *pool)
+{
+  svn_stream_t *result;
+  lz4_compress_stream_baton_t *baton;
+
+  baton = apr_pcalloc(pool, sizeof(*baton));
+  baton->stream = stream;
+  baton->buf = svn_stringbuf_create_empty(pool);
+  baton->tmpbuf = svn_stringbuf_create_empty(pool);
+
+  result = svn_stream_create(baton, pool);
+  svn_stream_set_write(result, lz4_compress_stream_write_fn);
+  svn_stream_set_close(result, lz4_compress_stream_close_fn);
+
+  return result;
+}
+
+static svn_error_t *
+ensure_eos(svn_stream_t *stream)
+{
+  char dummy;
+  apr_size_t len = sizeof(dummy);
+
+  SVN_ERR(svn_stream_read_full(stream, &dummy, &len));
+  if (len != 0)
+    return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL, NULL);
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+lz4_decompress_next_substream_fn(svn_stream_t **stream_p,
+                                 void *baton,
+                                 apr_pool_t *pool)
+{
+  svn_stream_t *stream = baton;
+  apr_uint32_t uncompressed_len;
+  apr_uint32_t compressed_len;
+  svn_stringbuf_t *buf;
+  apr_size_t len;
+
+  SVN_ERR(read_uint32(&uncompressed_len, stream));
+
+  if (uncompressed_len > LZ4_STREAM_MAX_BLOCKLEN)
+    return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL, NULL);
+
+  if (uncompressed_len == 0)
+    {
+      SVN_ERR(ensure_eos(stream));
+      SVN_ERR(svn_stream_close(stream));
+
+      *stream_p = NULL;
+      return SVN_NO_ERROR;
+    }
+
+  SVN_ERR(read_uint32(&compressed_len, stream));
+  buf = svn_stringbuf_create_ensure(compressed_len, pool);
+  len = compressed_len;
+  SVN_ERR(svn_stream_read_full(stream, buf->data, &len));
+  buf->len = len;
+  buf->data[buf->len] = 0;
+  if (len < compressed_len)
+    return svn_error_create(SVN_ERR_STREAM_MALFORMED_DATA, NULL, NULL);
+
+  if (uncompressed_len == compressed_len)
+    {
+      *stream_p = svn_stream_from_stringbuf(buf, pool);
+    }
+  else
+    {
+      svn_stringbuf_t *outbuf;
+      int rv;
+
+      outbuf = svn_stringbuf_create_ensure(uncompressed_len, pool);
+      rv = LZ4_decompress_safe(buf->data, outbuf->data,
+                               compressed_len, uncompressed_len);
+      if (rv < 0)
+        return svn_error_create(SVN_ERR_LZ4_DECOMPRESSION_FAILED, NULL, NULL);
+
+      if (rv != (int) uncompressed_len)
+        return svn_error_create(SVN_ERR_LZ4_DECOMPRESSION_FAILED, NULL,
+                                _("Size of uncompressed data "
+                                  "does not match stored original length"));
+      outbuf->len = uncompressed_len;
+      outbuf->data[outbuf->len] = 0;
+      *stream_p = svn_stream_from_stringbuf(outbuf, pool);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+svn_stream_t *
+svn_stream__lz4_decompress(svn_stream_t *stream, apr_pool_t *pool)
+{
+  return stream_generator_create(lz4_decompress_next_substream_fn, stream, 
pool);
+}
Index: subversion/libsvn_subr/stream.c
===================================================================
--- subversion/libsvn_subr/stream.c     (revision 1844566)
+++ subversion/libsvn_subr/stream.c     (working copy)
@@ -2399,23 +2399,6 @@ svn_stream__install_stream(svn_stream_t *install_s
 }
 
 svn_error_t *
-svn_stream__install_get_info(apr_finfo_t *finfo,
-                             svn_stream_t *install_stream,
-                             apr_int32_t wanted,
-                             apr_pool_t *scratch_pool)
-{
-  struct install_baton_t *ib = install_stream->baton;
-  apr_status_t status;
-
-  status = apr_file_info_get(finfo, wanted, ib->baton_apr.file);
-
-  if (status)
-    return svn_error_wrap_apr(status, NULL);
-
-  return SVN_NO_ERROR;
-}
-
-svn_error_t *
 svn_stream__install_delete(svn_stream_t *install_stream,
                            apr_pool_t *scratch_pool)
 {
Index: subversion/libsvn_wc/adm_files.c
===================================================================
--- subversion/libsvn_wc/adm_files.c    (revision 1844566)
+++ subversion/libsvn_wc/adm_files.c    (working copy)
@@ -210,8 +210,9 @@ svn_wc__text_base_path_to_read(const char **result
                              _("Node '%s' has no pristine text"),
                              svn_dirent_local_style(local_abspath,
                                                     scratch_pool));
-  SVN_ERR(svn_wc__db_pristine_get_path(result_abspath, db, local_abspath,
-                                       checksum,
+  SVN_ERR(svn_wc__db_pristine_get_path(result_abspath, db,
+                                       local_abspath, checksum,
+                                       svn_io_file_del_on_pool_cleanup,
                                        result_pool, scratch_pool));
   return SVN_NO_ERROR;
 }
Index: subversion/libsvn_wc/diff_editor.c
===================================================================
--- subversion/libsvn_wc/diff_editor.c  (revision 1844566)
+++ subversion/libsvn_wc/diff_editor.c  (working copy)
@@ -478,6 +478,7 @@ svn_wc__diff_base_working_diff(svn_wc__db_t *db,
 
   SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file,
                                        db, local_abspath, checksum,
+                                       svn_io_file_del_on_pool_cleanup,
                                        scratch_pool, scratch_pool));
 
   if (diff_pristine)
@@ -484,6 +485,7 @@ svn_wc__diff_base_working_diff(svn_wc__db_t *db,
     SVN_ERR(svn_wc__db_pristine_get_path(&local_file,
                                          db, local_abspath,
                                          working_checksum,
+                                         svn_io_file_del_on_pool_cleanup,
                                          scratch_pool, scratch_pool));
   else if (! (had_props || props_mod))
     local_file = local_abspath;
@@ -1019,8 +1021,10 @@ svn_wc__diff_local_only_file(svn_wc__db_t *db,
     right_props = svn_prop_hash_dup(pristine_props, scratch_pool);
 
   if (checksum)
-    SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, db, local_abspath,
-                                         checksum, scratch_pool, 
scratch_pool));
+    SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file, db,
+                                         local_abspath, checksum,
+                                         svn_io_file_del_on_pool_cleanup,
+                                         scratch_pool, scratch_pool));
   else
     pristine_file = NULL;
 
@@ -1417,6 +1421,7 @@ svn_wc__diff_base_only_file(svn_wc__db_t *db,
 
   SVN_ERR(svn_wc__db_pristine_get_path(&pristine_file,
                                        db, local_abspath, checksum,
+                                       svn_io_file_del_on_pool_cleanup,
                                        scratch_pool, scratch_pool));
 
   SVN_ERR(processor->file_deleted(relpath,
@@ -2218,6 +2223,7 @@ close_file(void *file_baton,
         SVN_ERR(svn_wc__db_pristine_get_path(&repos_file,
                                              eb->db, eb->anchor_abspath,
                                              fb->base_checksum,
+                                             svn_io_file_del_on_pool_cleanup,
                                              scratch_pool, scratch_pool));
       }
   }
@@ -2254,6 +2260,7 @@ close_file(void *file_baton,
           SVN_ERR(svn_wc__db_pristine_get_path(&localfile,
                                                eb->db, eb->anchor_abspath,
                                                checksum,
+                                               svn_io_file_del_on_pool_cleanup,
                                                scratch_pool, scratch_pool));
         }
       else
Index: subversion/libsvn_wc/update_editor.c
===================================================================
--- subversion/libsvn_wc/update_editor.c        (revision 1844566)
+++ subversion/libsvn_wc/update_editor.c        (working copy)
@@ -451,7 +451,8 @@ struct handler_baton
    ### file name to create later.  A better way may not be readily available.
  */
 static svn_error_t *
-get_empty_tmp_file(const char **tmp_filename,
+get_empty_tmp_file(const char **tmp_filename_p,
+                   svn_skel_t **cleanup_work_item_p,
                    svn_wc__db_t *db,
                    const char *wri_abspath,
                    apr_pool_t *result_pool,
@@ -458,13 +459,26 @@ static svn_error_t *
                    apr_pool_t *scratch_pool)
 {
   const char *temp_dir_abspath;
+  const char *tmp_filename;
+  svn_skel_t *work_item;
+  svn_error_t *err;
 
   SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&temp_dir_abspath, db, wri_abspath,
                                          scratch_pool, scratch_pool));
-  SVN_ERR(svn_io_open_unique_file3(NULL, tmp_filename, temp_dir_abspath,
+  SVN_ERR(svn_io_open_unique_file3(NULL, &tmp_filename, temp_dir_abspath,
                                    svn_io_file_del_none,
                                    scratch_pool, scratch_pool));
+  err = svn_wc__wq_build_file_remove(&work_item, db, wri_abspath,
+                                     tmp_filename,
+                                     result_pool, scratch_pool);
+  if (err)
+    {
+      return svn_error_compose_create(err,
+               svn_io_remove_file2(tmp_filename, TRUE, scratch_pool));
+    }
 
+  *tmp_filename_p = tmp_filename;
+  *cleanup_work_item_p = work_item;
   return SVN_NO_ERROR;
 }
 
@@ -3873,18 +3887,15 @@ svn_wc__perform_file_merge(svn_skel_t **work_items
      the textual changes into the working file. */
   const char *oldrev_str, *newrev_str, *mine_str;
   const char *merge_left;
-  svn_boolean_t delete_left = FALSE;
   const char *path_ext = "";
   const char *new_pristine_abspath;
   enum svn_wc_merge_outcome_t merge_outcome = svn_wc_merge_unchanged;
   svn_skel_t *work_item;
+  svn_skel_t *cleanup_queue = NULL;
+  svn_error_t *err;
 
   *work_items = NULL;
 
-  SVN_ERR(svn_wc__db_pristine_get_path(&new_pristine_abspath,
-                                       db, wri_abspath, new_checksum,
-                                       scratch_pool, scratch_pool));
-
   /* If we have any file extensions we're supposed to
      preserve in generated conflict file names, then find
      this path's extension.  But then, if it isn't one of
@@ -3917,45 +3928,57 @@ svn_wc__perform_file_merge(svn_skel_t **work_items
 
   if (! original_checksum)
     {
-      SVN_ERR(get_empty_tmp_file(&merge_left, db, wri_abspath,
+      SVN_ERR(get_empty_tmp_file(&merge_left, &work_item,
+                                 db, wri_abspath,
                                  result_pool, scratch_pool));
-      delete_left = TRUE;
     }
   else
-    SVN_ERR(svn_wc__db_pristine_get_path(&merge_left, db, wri_abspath,
-                                         original_checksum,
-                                         result_pool, scratch_pool));
+    {
+      SVN_ERR(svn_wc__get_pristine_file_with_wq(&merge_left, &work_item,
+                                                db, wri_abspath,
+                                                original_checksum,
+                                                result_pool, scratch_pool));
+    }
+  cleanup_queue = svn_wc__wq_merge(cleanup_queue, work_item, result_pool);
 
+  err = svn_wc__get_pristine_file_with_wq(&new_pristine_abspath, &work_item,
+                                          db, wri_abspath, new_checksum,
+                                          result_pool, scratch_pool);
+  if (err)
+    {
+      return svn_error_compose_create(err,
+               svn_wc__db_wq_add(db, wri_abspath, cleanup_queue, 
scratch_pool));
+    }
+  cleanup_queue = svn_wc__wq_merge(cleanup_queue, work_item, result_pool);
+
   /* Merge the changes from the old textbase to the new
      textbase into the file we're updating.
      Remember that this function wants full paths! */
-  SVN_ERR(svn_wc__internal_merge(&work_item,
-                                 conflict_skel,
-                                 &merge_outcome,
-                                 db,
-                                 merge_left,
-                                 new_pristine_abspath,
-                                 local_abspath,
-                                 wri_abspath,
-                                 oldrev_str, newrev_str, mine_str,
-                                 old_actual_props,
-                                 FALSE /* dry_run */,
-                                 diff3_cmd, NULL, propchanges,
-                                 cancel_func, cancel_baton,
-                                 result_pool, scratch_pool));
+  err = svn_wc__internal_merge(&work_item,
+                               conflict_skel,
+                               &merge_outcome,
+                               db,
+                               merge_left,
+                               new_pristine_abspath,
+                               local_abspath,
+                               wri_abspath,
+                               oldrev_str, newrev_str, mine_str,
+                               old_actual_props,
+                               FALSE /* dry_run */,
+                               diff3_cmd, NULL, propchanges,
+                               cancel_func, cancel_baton,
+                               result_pool, scratch_pool);
+  if (err)
+    {
+      return svn_error_compose_create(err,
+               svn_wc__db_wq_add(db, wri_abspath, cleanup_queue, 
scratch_pool));
+    }
 
   *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
+  /* Queue the cleanup after the merge work item. */
+  *work_items = svn_wc__wq_merge(*work_items, cleanup_queue, result_pool);
   *found_conflict = (merge_outcome == svn_wc_merge_conflict);
 
-  /* If we created a temporary left merge file, get rid of it. */
-  if (delete_left)
-    {
-      SVN_ERR(svn_wc__wq_build_file_remove(&work_item, db, wri_abspath,
-                                           merge_left,
-                                           result_pool, scratch_pool));
-      *work_items = svn_wc__wq_merge(*work_items, work_item, result_pool);
-    }
-
   return SVN_NO_ERROR;
 }
 
Index: subversion/libsvn_wc/util.c
===================================================================
--- subversion/libsvn_wc/util.c (revision 1844566)
+++ subversion/libsvn_wc/util.c (working copy)
@@ -37,6 +37,7 @@
 
 #include "wc.h"   /* just for prototypes of things in this .c file */
 #include "entries.h"
+#include "workqueue.h"
 #include "private/svn_wc_private.h"
 
 #include "svn_private_config.h"
@@ -505,8 +506,42 @@ svn_wc__fetch_base_func(const char **filename,
       return SVN_NO_ERROR;
     }
 
-  SVN_ERR(svn_wc__db_pristine_get_path(filename, sfb->db, local_abspath,
-                                       checksum, scratch_pool, scratch_pool));
+  SVN_ERR(svn_wc__db_pristine_get_path(filename, sfb->db,
+                                       local_abspath, checksum,
+                                       svn_io_file_del_on_pool_cleanup,
+                                       result_pool, scratch_pool));
 
   return SVN_NO_ERROR;
 }
+
+svn_error_t *
+svn_wc__get_pristine_file_with_wq(const char **pristine_abspath_p,
+                                  svn_skel_t **cleanup_work_item_p,
+                                  svn_wc__db_t *db,
+                                  const char *wri_abspath,
+                                  const svn_checksum_t *sha1_checksum,
+                                  apr_pool_t *result_pool,
+                                  apr_pool_t *scratch_pool)
+{
+  svn_error_t *err;
+  const char *pristine_abspath;
+  svn_skel_t *work_item;
+
+  SVN_ERR(svn_wc__db_pristine_get_path(&pristine_abspath, db,
+                                       wri_abspath, sha1_checksum,
+                                       svn_io_file_del_none,
+                                       result_pool, scratch_pool));
+  err = svn_wc__wq_build_file_remove(&work_item, db, wri_abspath,
+                                     pristine_abspath,
+                                     result_pool, scratch_pool);
+  if (err)
+    {
+      return svn_error_compose_create(err,
+               svn_io_remove_file2(pristine_abspath, TRUE, scratch_pool));
+    }
+
+  *pristine_abspath_p = pristine_abspath;
+  *cleanup_work_item_p = work_item;
+  return SVN_NO_ERROR;
+}
+
Index: subversion/libsvn_wc/wc-metadata.sql
===================================================================
--- subversion/libsvn_wc/wc-metadata.sql        (revision 1844566)
+++ subversion/libsvn_wc/wc-metadata.sql        (working copy)
@@ -80,7 +80,7 @@ CREATE UNIQUE INDEX I_LOCAL_ABSPATH ON WCROOT (loc
    derived from the 'checksum' column.  Each pristine text is referenced by
    any number of rows in the NODES and ACTUAL_NODE tables.
 
-   In future, the pristine text file may be compressed.
+   TODO: COMPATIBILITY: Add a separate statement for older WC formats.
  */
 CREATE TABLE PRISTINE (
   /* The SHA-1 checksum of the pristine text. This is a unique key. The
@@ -93,8 +93,7 @@ CREATE TABLE PRISTINE (
      and the pristine text is stored verbatim in the file. */
   compression  INTEGER,
 
-  /* The size in bytes of the file in which the pristine text is stored.
-     Used to verify the pristine file is "proper". */
+  /* The size of uncompressed pristine content. */
   size  INTEGER NOT NULL,
 
   /* The number of rows in the NODES table that have a 'checksum' column
@@ -104,9 +103,13 @@ CREATE TABLE PRISTINE (
 
   /* Alternative MD5 checksum used for communicating with older
      repositories. Not strictly guaranteed to be unique among table rows. */
-  md5_checksum  TEXT NOT NULL
+  md5_checksum  TEXT NOT NULL,
+
+  data  BLOB
   );
 
+/* TODO: While here, perhaps add `WITHOUT ROWID` for the new schema table? */
+
 CREATE INDEX I_PRISTINE_MD5 ON PRISTINE (md5_checksum);
 
 /* ------------------------------------------------------------------------- */
Index: subversion/libsvn_wc/wc-queries.sql
===================================================================
--- subversion/libsvn_wc/wc-queries.sql (revision 1844566)
+++ subversion/libsvn_wc/wc-queries.sql (working copy)
@@ -884,12 +884,15 @@ SELECT id, work FROM work_queue ORDER BY id LIMIT
 DELETE FROM work_queue WHERE id = ?1
 
 -- STMT_INSERT_OR_IGNORE_PRISTINE
-INSERT OR IGNORE INTO pristine (checksum, md5_checksum, size, refcount)
-VALUES (?1, ?2, ?3, 0)
+/* TODO: COMPATIBILITY: Add a separate statement for older WC formats. */
+INSERT OR IGNORE INTO pristine (
+  checksum, md5_checksum, size, refcount, compression, data )
+VALUES (?1, ?2, ?3, 0, ?4, null)
 
 -- STMT_INSERT_PRISTINE
-INSERT INTO pristine (checksum, md5_checksum, size, refcount)
-VALUES (?1, ?2, ?3, 0)
+/* TODO: COMPATIBILITY: Add a separate statement for older WC formats. */
+INSERT INTO pristine (checksum, md5_checksum, size, refcount, compression, 
data)
+VALUES (?1, ?2, ?3, 0, ?4, ?5)
 
 -- STMT_SELECT_PRISTINE
 SELECT md5_checksum
@@ -896,8 +899,9 @@ SELECT md5_checksum
 FROM pristine
 WHERE checksum = ?1
 
--- STMT_SELECT_PRISTINE_SIZE
-SELECT size
+-- STMT_SELECT_PRISTINE_INFO
+/* TODO: COMPATIBILITY: Add a separate statement for older WC formats. */
+SELECT size, compression, data
 FROM pristine
 WHERE checksum = ?1 LIMIT 1
 
@@ -916,8 +920,9 @@ DELETE FROM pristine
 WHERE checksum = ?1 AND refcount = 0
 
 -- STMT_SELECT_COPY_PRISTINES
+/* TODO: COMPATIBILITY: Add a separate statement for older WC formats. */
 /* For the root itself */
-SELECT n.checksum, md5_checksum, size
+SELECT n.checksum, md5_checksum, size, compression, data
 FROM nodes_current n
 LEFT JOIN pristine p ON n.checksum = p.checksum
 WHERE wc_id = ?1
@@ -925,7 +930,7 @@ WHERE wc_id = ?1
   AND n.checksum IS NOT NULL
 UNION ALL
 /* And all descendants */
-SELECT n.checksum, md5_checksum, size
+SELECT n.checksum, md5_checksum, size, compression, data
 FROM nodes n
 LEFT JOIN pristine p ON n.checksum = p.checksum
 WHERE wc_id = ?1
Index: subversion/libsvn_wc/wc.h
===================================================================
--- subversion/libsvn_wc/wc.h   (revision 1844566)
+++ subversion/libsvn_wc/wc.h   (working copy)
@@ -760,6 +760,15 @@ svn_wc__node_has_local_mods(svn_boolean_t *modifie
                             void *cancel_baton,
                             apr_pool_t *scratch_pool);
 
+svn_error_t *
+svn_wc__get_pristine_file_with_wq(const char **pristine_abspath_p,
+                                  svn_skel_t **cleanup_work_item_p,
+                                  svn_wc__db_t *db,
+                                  const char *wri_abspath,
+                                  const svn_checksum_t *sha1_checksum,
+                                  apr_pool_t *result_pool,
+                                  apr_pool_t *scratch_pool);
+
 #ifdef __cplusplus
 }
 #endif /* __cplusplus */
Index: subversion/libsvn_wc/wc_db.h
===================================================================
--- subversion/libsvn_wc/wc_db.h        (revision 1844566)
+++ subversion/libsvn_wc/wc_db.h        (working copy)
@@ -915,6 +915,7 @@ svn_wc__db_pristine_get_path(const char **pristine
                              svn_wc__db_t *db,
                              const char *wri_abspath,
                              const svn_checksum_t *checksum,
+                             svn_io_file_del_t delete_when,
                              apr_pool_t *result_pool,
                              apr_pool_t *scratch_pool);
 
@@ -965,10 +966,10 @@ typedef struct svn_wc__db_install_data_t
    Allocate the new stream, path and checksums in RESULT_POOL.
  */
 svn_error_t *
-svn_wc__db_pristine_prepare_install(svn_stream_t **stream,
-                                    svn_wc__db_install_data_t **install_data,
-                                    svn_checksum_t **sha1_checksum,
-                                    svn_checksum_t **md5_checksum,
+svn_wc__db_pristine_prepare_install(svn_stream_t **stream_p,
+                                    svn_wc__db_install_data_t **install_data_p,
+                                    svn_checksum_t **sha1_checksum_p,
+                                    svn_checksum_t **md5_checksum_p,
                                     svn_wc__db_t *db,
                                     const char *wri_abspath,
                                     apr_pool_t *result_pool,
Index: subversion/libsvn_wc/wc_db_pristine.c
===================================================================
--- subversion/libsvn_wc/wc_db_pristine.c       (revision 1844566)
+++ subversion/libsvn_wc/wc_db_pristine.c       (working copy)
@@ -30,6 +30,7 @@
 #include "svn_dirent_uri.h"
 
 #include "private/svn_io_private.h"
+#include "private/svn_subr_private.h"
 
 #include "wc.h"
 #include "wc_db.h"
@@ -98,6 +99,7 @@ svn_wc__db_pristine_get_path(const char **pristine
                              svn_wc__db_t *db,
                              const char *wri_abspath,
                              const svn_checksum_t *sha1_checksum,
+                             svn_io_file_del_t delete_when,
                              apr_pool_t *result_pool,
                              apr_pool_t *scratch_pool)
 {
@@ -104,10 +106,17 @@ svn_wc__db_pristine_get_path(const char **pristine
   svn_wc__db_wcroot_t *wcroot;
   const char *local_relpath;
   svn_boolean_t present;
+  svn_stream_t *contents;
+  const char *tmpdir;
+  svn_stream_t *tmpstream;
+  svn_error_t *err;
 
   SVN_ERR_ASSERT(pristine_abspath != NULL);
   SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
   SVN_ERR_ASSERT(sha1_checksum != NULL);
+  SVN_ERR_ASSERT(delete_when == svn_io_file_del_none ||
+                 delete_when == svn_io_file_del_on_pool_cleanup);
+
   /* ### Transitional: accept MD-5 and look up the SHA-1.  Return an error
    * if the pristine text is not in the store. */
   if (sha1_checksum->kind != svn_checksum_sha1)
@@ -130,11 +139,22 @@ svn_wc__db_pristine_get_path(const char **pristine
                              svn_checksum_to_cstring_display(sha1_checksum,
                                                              scratch_pool));
 
-  SVN_ERR(get_pristine_fname(pristine_abspath, wcroot->abspath,
-                             sha1_checksum,
-                             result_pool, scratch_pool));
+  /* TODO: COMPATIBILITY: Not required for older WC formats: */
+  SVN_ERR(svn_wc__db_temp_wcroot_tempdir(&tmpdir, db, wri_abspath,
+                                         scratch_pool, scratch_pool));
+  SVN_ERR(svn_wc__db_pristine_read(&contents, NULL, db, wri_abspath,
+                                   sha1_checksum, scratch_pool,
+                                   scratch_pool));
+  SVN_ERR(svn_stream_open_unique(&tmpstream, pristine_abspath, tmpdir,
+                                 delete_when, result_pool, scratch_pool));
+  err = svn_stream_copy3(contents, tmpstream, NULL, NULL, scratch_pool);
+  if (err && delete_when == svn_io_file_del_none)
+    {
+      err = svn_error_compose_create(err,
+              svn_io_remove_file2(*pristine_abspath, TRUE, scratch_pool));
+    }
 
-  return SVN_NO_ERROR;
+  return svn_error_trace(err);
 }
 
 svn_error_t *
@@ -150,6 +170,31 @@ svn_wc__db_pristine_get_future_path(const char **p
   return SVN_NO_ERROR;
 }
 
+/* Matches the allowed values of the PRISTINE.compression field. */
+typedef enum compression_type_t
+{
+  compression_type_none = 0,
+  compression_type_lz4 = 1
+} compression_type_t;
+
+static svn_error_t *
+parse_compression_type(compression_type_t *type_p, int value)
+{
+  switch (value)
+    {
+    case compression_type_none:
+      break;
+    case compression_type_lz4:
+      /* TODO: Check if this value is allowed by the WC format. */
+      break;
+    default:
+      return svn_error_create(SVN_ERR_INVALID_INPUT, NULL, NULL);
+    }
+
+  *type_p = (compression_type_t) value;
+  return SVN_NO_ERROR;
+}
+
 /* Set *CONTENTS to a readable stream from which the pristine text
  * identified by SHA1_CHECKSUM and PRISTINE_ABSPATH can be read from the
  * pristine store of WCROOT.  If SIZE is not null, set *SIZE to the size
@@ -176,11 +221,11 @@ pristine_read_txn(svn_stream_t **contents,
 {
   svn_sqlite__stmt_t *stmt;
   svn_boolean_t have_row;
+  compression_type_t compression_type;
 
-  /* Check that this pristine text is present in the store.  (The presence
-   * of the file is not sufficient.) */
+  /* Check that this pristine text is present in the store. */
   SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
-                                    STMT_SELECT_PRISTINE_SIZE));
+                                    STMT_SELECT_PRISTINE_INFO));
   SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
   SVN_ERR(svn_sqlite__step(&have_row, stmt));
 
@@ -187,7 +232,6 @@ pristine_read_txn(svn_stream_t **contents,
   if (size)
     *size = svn_sqlite__column_int64(stmt, 0);
 
-  SVN_ERR(svn_sqlite__reset(stmt));
   if (! have_row)
     {
       return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
@@ -196,21 +240,50 @@ pristine_read_txn(svn_stream_t **contents,
                                  sha1_checksum, scratch_pool));
     }
 
-  /* Open the file as a readable stream.  It will remain readable even when
-   * deleted from disk; APR guarantees that on Windows as well as Unix.
-   *
-   * We also don't enable APR_BUFFERED on this file to maximize throughput
-   * e.g. for fulltext comparison.  As we use SVN__STREAM_CHUNK_SIZE buffers
-   * where needed in streams, there is no point in having another layer of
-   * buffers. */
+  SVN_ERR(parse_compression_type(&compression_type,
+                                 svn_sqlite__column_int(stmt, 1)));
+
   if (contents)
     {
-      apr_file_t *file;
-      SVN_ERR(svn_io_file_open(&file, pristine_abspath, APR_READ,
-                               APR_OS_DEFAULT, result_pool));
-      *contents = svn_stream_from_aprfile2(file, FALSE, result_pool);
+      if (svn_sqlite__column_is_null(stmt, 2))
+        {
+          apr_file_t *file;
+          /* Open the file as a readable stream.  It will remain readable even 
when
+           * deleted from disk; APR guarantees that on Windows as well as Unix.
+           *
+           * We also don't enable APR_BUFFERED on this file to maximize 
throughput
+           * e.g. for fulltext comparison.  As we use SVN__STREAM_CHUNK_SIZE 
buffers
+           * where needed in streams, there is no point in having another 
layer of
+           * buffers. */
+          SVN_ERR(svn_io_file_open(&file, pristine_abspath, APR_READ,
+                                   APR_OS_DEFAULT, result_pool));
+          *contents = svn_stream_from_aprfile2(file, FALSE, result_pool);
+        }
+      else
+        {
+          apr_size_t data_len;
+          const void *data;
+          svn_string_t *str;
+
+          data = svn_sqlite__column_blob(stmt, 2, &data_len, NULL);
+          str = svn_string_ncreate(data, data_len, result_pool);
+          *contents = svn_stream_from_string(str, result_pool);
+        }
+
+      switch (compression_type)
+        {
+        case compression_type_none:
+          break;
+        case compression_type_lz4:
+          *contents = svn_stream__lz4_decompress(*contents, result_pool);
+          break;
+        default:
+          SVN_ERR_MALFUNCTION();
+        }
     }
 
+  SVN_ERR(svn_sqlite__reset(stmt));
+
   return SVN_NO_ERROR;
 }
 
@@ -268,6 +341,19 @@ pristine_get_tempdir(svn_wc__db_wcroot_t *wcroot,
                               PRISTINE_TEMPDIR_RELPATH, SVN_VA_NULL);
 }
 
+struct svn_wc__db_install_data_t
+{
+  svn_wc__db_wcroot_t *wcroot;
+  svn_stream_t *install_stream;
+  svn_stringbuf_t *buf;
+  svn_stream_t *inner_stream;
+  compression_type_t compression_type;
+  svn_filesize_t uncompressed_size;
+  const char *temp_dir_abspath;
+  int blob_size_limit;
+  apr_pool_t *pool;
+};
+
 /* Install the pristine text described by BATON into the pristine store of
  * SDB.  If it is already stored then just delete the new file
  * BATON->tempfile_abspath.
@@ -279,8 +365,7 @@ pristine_get_tempdir(svn_wc__db_wcroot_t *wcroot,
  */
 static svn_error_t *
 pristine_install_txn(svn_sqlite__db_t *sdb,
-                     /* The path to the source file that is to be moved into 
place. */
-                     svn_stream_t *install_stream,
+                     svn_wc__db_install_data_t *install_data,
                      /* The target path for the file (within the pristine 
store). */
                      const char *pristine_abspath,
                      /* The pristine text's SHA-1 checksum. */
@@ -301,7 +386,7 @@ pristine_install_txn(svn_sqlite__db_t *sdb,
 
   if (have_row)
     {
-#ifdef SVN_DEBUG
+#if 0
       /* Consistency checks.  Verify both files exist and match.
        * ### We could check much more. */
       {
@@ -325,47 +410,129 @@ pristine_install_txn(svn_sqlite__db_t *sdb,
 #endif
 
       /* Remove the temp file: it's already there */
-      SVN_ERR(svn_stream__install_delete(install_stream, scratch_pool));
+      if (install_data->install_stream)
+        SVN_ERR(svn_stream__install_delete(install_data->install_stream, 
scratch_pool));
       return SVN_NO_ERROR;
     }
 
   /* Move the file to its target location.  (If it is already there, it is
    * an orphan file and it doesn't matter if we overwrite it.) */
-  {
-    apr_finfo_t finfo;
-    SVN_ERR(svn_stream__install_get_info(&finfo, install_stream,
-                                         APR_FINFO_SIZE, scratch_pool));
-    SVN_ERR(svn_stream__install_stream(install_stream, pristine_abspath,
-                                       TRUE, scratch_pool));
+  if (install_data->install_stream)
+    SVN_ERR(svn_stream__install_stream(install_data->install_stream,
+                                       pristine_abspath, TRUE,
+                                       scratch_pool));
 
-    SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_PRISTINE));
-    SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
-    SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, scratch_pool));
-    SVN_ERR(svn_sqlite__bind_int64(stmt, 3, finfo.size));
-    SVN_ERR(svn_sqlite__insert(NULL, stmt));
+  SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_PRISTINE));
+  SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, sha1_checksum, scratch_pool));
+  SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, scratch_pool));
+  SVN_ERR(svn_sqlite__bind_int64(stmt, 3, install_data->uncompressed_size));
+  SVN_ERR(svn_sqlite__bind_int(stmt, 4, install_data->compression_type));
 
+  if (!install_data->install_stream)
+    SVN_ERR(svn_sqlite__bind_blob(stmt, 5, install_data->buf->data, 
install_data->buf->len));
+
+  SVN_ERR(svn_sqlite__insert(NULL, stmt));
+
+  if (install_data->install_stream)
     SVN_ERR(svn_io_set_file_read_only(pristine_abspath, FALSE, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+inner_stream_write_fn(void *baton, const char *data, apr_size_t *len)
+{
+  svn_wc__db_install_data_t *install_data = baton;
+
+  if (!install_data->install_stream &&
+      install_data->buf->len + *len > install_data->blob_size_limit)
+  {
+    apr_size_t wlen;
+
+    SVN_ERR_W(svn_stream__create_for_install(&install_data->install_stream,
+                                             install_data->temp_dir_abspath,
+                                             install_data->pool,
+                                             /* TODO */ install_data->pool),
+              _("Unable to create pristine install stream"));
+    wlen = install_data->buf->len;
+    SVN_ERR(svn_stream_write(install_data->install_stream,
+                             install_data->buf->data, &wlen));
+    install_data->buf = NULL;
   }
 
+  if (install_data->install_stream)
+    SVN_ERR(svn_stream_write(install_data->install_stream, data, len));
+  else
+    svn_stringbuf_appendbytes(install_data->buf, data, *len);
+
   return SVN_NO_ERROR;
 }
 
-struct svn_wc__db_install_data_t
+static svn_error_t *
+inner_stream_seek_fn(void *baton, const svn_stream_mark_t *mark)
 {
-  svn_wc__db_wcroot_t *wcroot;
-  svn_stream_t *inner_stream;
-};
+  /* TODO: Implement this. */
+  return svn_error_create(APR_ENOTIMPL, NULL, NULL);
+}
 
+static svn_error_t *
+inner_stream_close_fn(void *baton)
+{
+  svn_wc__db_install_data_t *install_data = baton;
+
+  if (install_data->install_stream)
+    SVN_ERR(svn_stream_close(install_data->install_stream));
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+install_stream_write_fn(void *baton, const char *data, apr_size_t *len)
+{
+  svn_wc__db_install_data_t *install_data = baton;
+
+  SVN_ERR(svn_stream_write(install_data->inner_stream, data, len));
+  install_data->uncompressed_size += *len;
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+install_stream_seek_fn(void *baton, const svn_stream_mark_t *mark)
+{
+  svn_wc__db_install_data_t *install_data = baton;
+
+  if (!mark)
+    return svn_error_create(SVN_ERR_STREAM_SEEK_NOT_SUPPORTED, NULL, NULL);
+
+  SVN_ERR(svn_stream_reset(install_data->inner_stream));
+  install_data->uncompressed_size = 0;
+
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+install_stream_close_fn(void *baton)
+{
+  svn_wc__db_install_data_t *install_data = baton;
+
+  SVN_ERR(svn_stream_close(install_data->inner_stream));
+
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
-svn_wc__db_pristine_prepare_install(svn_stream_t **stream,
-                                    svn_wc__db_install_data_t **install_data,
-                                    svn_checksum_t **sha1_checksum,
-                                    svn_checksum_t **md5_checksum,
+svn_wc__db_pristine_prepare_install(svn_stream_t **stream_p,
+                                    svn_wc__db_install_data_t **install_data_p,
+                                    svn_checksum_t **sha1_checksum_p,
+                                    svn_checksum_t **md5_checksum_p,
                                     svn_wc__db_t *db,
                                     const char *wri_abspath,
                                     apr_pool_t *result_pool,
                                     apr_pool_t *scratch_pool)
 {
+  svn_stream_t *stream;
+  svn_wc__db_install_data_t *install_data;
   svn_wc__db_wcroot_t *wcroot;
   const char *local_relpath;
   const char *temp_dir_abspath;
@@ -378,23 +545,49 @@ svn_error_t *
 
   temp_dir_abspath = pristine_get_tempdir(wcroot, scratch_pool, scratch_pool);
 
-  *install_data = apr_pcalloc(result_pool, sizeof(**install_data));
-  (*install_data)->wcroot = wcroot;
+  install_data = apr_pcalloc(result_pool, sizeof(*install_data));
+  install_data->wcroot = wcroot;
+  /* TODO: Determine the compression type based on the WC format, etc. */
+  install_data->compression_type = compression_type_lz4;
+  install_data->uncompressed_size = 0;
+  install_data->install_stream = NULL;
+  install_data->temp_dir_abspath = apr_pstrdup(result_pool, temp_dir_abspath);
+  install_data->pool = result_pool;
+  install_data->blob_size_limit = 16 * 1024;
+  install_data->buf = 
svn_stringbuf_create_ensure(install_data->blob_size_limit,
+                                                  result_pool);
 
-  SVN_ERR_W(svn_stream__create_for_install(stream,
-                                           temp_dir_abspath,
-                                           result_pool, scratch_pool),
-            _("Unable to create pristine install stream"));
+  install_data->inner_stream = svn_stream_create(install_data, result_pool);
+  svn_stream_set_write(install_data->inner_stream, inner_stream_write_fn);
+  svn_stream_set_seek(install_data->inner_stream, inner_stream_seek_fn);
+  svn_stream_set_close(install_data->inner_stream, inner_stream_close_fn);
 
-  (*install_data)->inner_stream = *stream;
+  switch (install_data->compression_type)
+    {
+    case compression_type_none:
+      break;
+    case compression_type_lz4:
+      install_data->inner_stream =
+        svn_stream__lz4_compress(install_data->inner_stream, result_pool);
+      break;
+    default:
+      SVN_ERR_MALFUNCTION();
+    }
 
-  if (md5_checksum)
-    *stream = svn_stream_checksummed2(*stream, NULL, md5_checksum,
-                                      svn_checksum_md5, FALSE, result_pool);
-  if (sha1_checksum)
-    *stream = svn_stream_checksummed2(*stream, NULL, sha1_checksum,
-                                      svn_checksum_sha1, FALSE, result_pool);
+  stream = svn_stream_create(install_data, result_pool);
+  svn_stream_set_write(stream, install_stream_write_fn);
+  svn_stream_set_seek(stream, install_stream_seek_fn);
+  svn_stream_set_close(stream, install_stream_close_fn);
 
+  if (md5_checksum_p)
+    stream = svn_stream_checksummed2(stream, NULL, md5_checksum_p,
+                                     svn_checksum_md5, FALSE, result_pool);
+  if (sha1_checksum_p)
+    stream = svn_stream_checksummed2(stream, NULL, sha1_checksum_p,
+                                     svn_checksum_sha1, FALSE, result_pool);
+
+  *stream_p = stream;
+  *install_data_p = install_data;
   return SVN_NO_ERROR;
 }
 
@@ -420,7 +613,7 @@ svn_wc__db_pristine_install(svn_wc__db_install_dat
    * at the disk, to ensure no concurrent pristine install/delete txn. */
   SVN_SQLITE__WITH_IMMEDIATE_TXN(
     pristine_install_txn(wcroot->sdb,
-                         install_data->inner_stream, pristine_abspath,
+                         install_data, pristine_abspath,
                          sha1_checksum, md5_checksum,
                          scratch_pool),
     wcroot->sdb);
@@ -432,7 +625,10 @@ svn_error_t *
 svn_wc__db_pristine_install_abort(svn_wc__db_install_data_t *install_data,
                                   apr_pool_t *scratch_pool)
 {
-  return svn_error_trace(svn_stream__install_delete(install_data->inner_stream,
+  if (!install_data->install_stream)
+    return SVN_NO_ERROR;
+
+  return 
svn_error_trace(svn_stream__install_delete(install_data->install_stream,
                                                     scratch_pool));
 }
 
@@ -521,6 +717,7 @@ maybe_transfer_one_pristine(svn_wc__db_wcroot_t *s
                             const svn_checksum_t *checksum,
                             const svn_checksum_t *md5_checksum,
                             apr_int64_t size,
+                            compression_type_t compression,
                             svn_cancel_func_t cancel_func,
                             void *cancel_baton,
                             apr_pool_t *scratch_pool)
@@ -539,6 +736,7 @@ maybe_transfer_one_pristine(svn_wc__db_wcroot_t *s
   SVN_ERR(svn_sqlite__bind_checksum(stmt, 1, checksum, scratch_pool));
   SVN_ERR(svn_sqlite__bind_checksum(stmt, 2, md5_checksum, scratch_pool));
   SVN_ERR(svn_sqlite__bind_int64(stmt, 3, size));
+  SVN_ERR(svn_sqlite__bind_int(stmt, 4, compression));
 
   SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
 
@@ -624,6 +822,7 @@ pristine_transfer_txn(svn_wc__db_wcroot_t *src_wcr
       const svn_checksum_t *md5_checksum;
       apr_int64_t size;
       svn_error_t *err;
+      compression_type_t compression;
 
       svn_pool_clear(iterpool);
 
@@ -630,9 +829,13 @@ pristine_transfer_txn(svn_wc__db_wcroot_t *src_wcr
       SVN_ERR(svn_sqlite__column_checksum(&checksum, stmt, 0, iterpool));
       SVN_ERR(svn_sqlite__column_checksum(&md5_checksum, stmt, 1, iterpool));
       size = svn_sqlite__column_int64(stmt, 2);
+      SVN_ERR(parse_compression_type(&compression,
+                                     svn_sqlite__column_int(stmt, 3)));
 
+      /* TODO: Update for intra-SQLite pristines, doesn't work yet. */
       err = maybe_transfer_one_pristine(src_wcroot, dst_wcroot,
                                         checksum, md5_checksum, size,
+                                        compression,
                                         cancel_func, cancel_baton,
                                         iterpool);
 
@@ -908,6 +1111,8 @@ svn_wc__db_pristine_check(svn_boolean_t *present,
                               wri_abspath, scratch_pool, scratch_pool));
   VERIFY_USABLE_WCROOT(wcroot);
 
+  /* TODO: COMPATIBILITY: Restore this optimization for older WC formats: */
+#if 0
   /* A filestat is much cheaper than a sqlite transaction especially on NFS,
      so first check if there is a pristine file and then if we are allowed
      to use it. */
@@ -941,6 +1146,7 @@ svn_wc__db_pristine_check(svn_boolean_t *present,
         return SVN_NO_ERROR;
       }
   }
+#endif
 
   /* Check that there is an entry in the PRISTINE table. */
   SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_PRISTINE));
Index: subversion/libsvn_wc/wc_db_update_move.c
===================================================================
--- subversion/libsvn_wc/wc_db_update_move.c    (revision 1844566)
+++ subversion/libsvn_wc/wc_db_update_move.c    (working copy)
@@ -1317,8 +1317,6 @@ tc_editor_alter_file(node_move_baton_t *nmb,
   const char *local_abspath = svn_dirent_join(b->wcroot->abspath,
                                               dst_relpath,
                                               scratch_pool);
-  const char *old_pristine_abspath;
-  const char *new_pristine_abspath;
   svn_skel_t *conflict_skel = NULL;
   apr_hash_t *actual_props;
   apr_array_header_t *propchanges;
@@ -1382,6 +1380,10 @@ tc_editor_alter_file(node_move_baton_t *nmb,
         }
       else
         {
+          const char *old_pristine_abspath;
+          const char *new_pristine_abspath;
+          svn_skel_t *cleanup_queue = NULL;
+          svn_error_t *err;
           /*
            * Run a 3-way merge to update the file, using the pre-update
            * pristine text as the merge base, the post-update pristine
@@ -1388,30 +1390,50 @@ tc_editor_alter_file(node_move_baton_t *nmb,
            * text as the merge-left version, and the current content of the
            * moved-here working file as the merge-right version.
            */
-          SVN_ERR(svn_wc__db_pristine_get_path(&old_pristine_abspath,
-                                               b->db, b->wcroot->abspath,
-                                               old_version.checksum,
-                                               scratch_pool, scratch_pool));
-          SVN_ERR(svn_wc__db_pristine_get_path(&new_pristine_abspath,
-                                               b->db, b->wcroot->abspath,
-                                               new_version.checksum,
-                                               scratch_pool, scratch_pool));
-          SVN_ERR(svn_wc__internal_merge(&work_item, &conflict_skel,
-                                         &merge_outcome, b->db,
-                                         old_pristine_abspath,
-                                         new_pristine_abspath,
-                                         local_abspath,
-                                         local_abspath,
-                                         NULL, NULL, NULL, /* diff labels */
-                                         actual_props,
-                                         FALSE, /* dry-run */
-                                         NULL, /* diff3-cmd */
-                                         NULL, /* merge options */
-                                         propchanges,
-                                         b->cancel_func, b->cancel_baton,
-                                         scratch_pool, scratch_pool));
+          SVN_ERR(svn_wc__get_pristine_file_with_wq(&old_pristine_abspath,
+                                                    &work_item, b->db,
+                                                    b->wcroot->abspath,
+                                                    old_version.checksum,
+                                                    scratch_pool, 
scratch_pool));
+          cleanup_queue = svn_wc__wq_merge(cleanup_queue, work_item, 
scratch_pool);
 
+          err = svn_wc__get_pristine_file_with_wq(&new_pristine_abspath,
+                                                  &work_item, b->db,
+                                                  b->wcroot->abspath,
+                                                  new_version.checksum,
+                                                  scratch_pool, scratch_pool);
+          if (err)
+            {
+              return svn_error_compose_create(err,
+                       svn_wc__db_wq_add(b->db, b->wcroot->abspath, 
cleanup_queue,
+                                         scratch_pool));
+            }
+          cleanup_queue = svn_wc__wq_merge(cleanup_queue, work_item, 
scratch_pool);
+
+          err = svn_wc__internal_merge(&work_item, &conflict_skel,
+                                       &merge_outcome, b->db,
+                                       old_pristine_abspath,
+                                       new_pristine_abspath,
+                                       local_abspath,
+                                       local_abspath,
+                                       NULL, NULL, NULL, /* diff labels */
+                                       actual_props,
+                                       FALSE, /* dry-run */
+                                       NULL, /* diff3-cmd */
+                                       NULL, /* merge options */
+                                       propchanges,
+                                       b->cancel_func, b->cancel_baton,
+                                       scratch_pool, scratch_pool);
+          if (err)
+            {
+              return svn_error_compose_create(err,
+                       svn_wc__db_wq_add(b->db, b->wcroot->abspath, 
cleanup_queue,
+                                         scratch_pool));
+            }
+
           work_items = svn_wc__wq_merge(work_items, work_item, scratch_pool);
+          /* Queue the cleanup after the merge work item. */
+          work_items = svn_wc__wq_merge(work_items, cleanup_queue, 
scratch_pool);
 
           if (merge_outcome == svn_wc_merge_conflict)
             content_state = svn_wc_notify_state_conflicted;
@@ -1610,6 +1632,8 @@ tc_editor_update_incoming_moved_file(node_move_bat
           SVN_ERR(svn_wc__db_pristine_get_path(&old_pristine_abspath,
                                                b->db, b->wcroot->abspath,
                                                src_checksum,
+                                               /* TODO: 
svn_wc__get_pristine_file_with_wq() */
+                                               svn_io_file_del_on_pool_cleanup,
                                                scratch_pool, scratch_pool));
           src_abspath = svn_dirent_join(b->wcroot->abspath, src_relpath,
                                         scratch_pool);
@@ -3168,17 +3192,19 @@ tc_editor_update_add_merge_files(added_node_baton_
        * and the locally added content of the working file as the merge-right
        * version.
        */
+
+      /* Create a property diff which shows all props as added. */
+      SVN_ERR(svn_prop_diffs(&propchanges, working_props,
+                             apr_hash_make(scratch_pool), scratch_pool));
+
       SVN_ERR(svn_io_open_unique_file3(NULL, &empty_file_abspath, NULL,
                                        svn_io_file_del_on_pool_cleanup,
                                        scratch_pool, scratch_pool));
       SVN_ERR(svn_wc__db_pristine_get_path(&pristine_abspath, b->db,
                                            b->wcroot->abspath, base_checksum,
+                                           /* TODO: 
svn_wc__get_pristine_file_with_wq() */
+                                           svn_io_file_del_on_pool_cleanup,
                                            scratch_pool, scratch_pool));
-
-      /* Create a property diff which shows all props as added. */
-      SVN_ERR(svn_prop_diffs(&propchanges, working_props,
-                             apr_hash_make(scratch_pool), scratch_pool));
-
       SVN_ERR(svn_wc__internal_merge(&work_item, &conflict_skel,
                                      &merge_outcome, b->db,
                                      empty_file_abspath,
Index: subversion/libsvn_wc/workqueue.c
===================================================================
--- subversion/libsvn_wc/workqueue.c    (revision 1844566)
+++ subversion/libsvn_wc/workqueue.c    (working copy)
@@ -499,6 +499,8 @@ run_file_install(work_item_baton_t *wqb,
       SVN_ERR(svn_wc__db_from_relpath(&source_abspath, db, wri_abspath,
                                       local_relpath,
                                       scratch_pool, scratch_pool));
+      SVN_ERR(svn_stream_open_readonly(&src_stream, source_abspath,
+                                       scratch_pool, scratch_pool));
     }
   else if (! checksum)
     {
@@ -518,15 +520,13 @@ run_file_install(work_item_baton_t *wqb,
     }
   else
     {
-      SVN_ERR(svn_wc__db_pristine_get_future_path(&source_abspath,
-                                                  wcroot_abspath,
-                                                  checksum,
-                                                  scratch_pool, scratch_pool));
+      /* TODO: Potential regression here: this now requires an additional
+       * transaction, whereas previously we did not have to consult the DB.
+       * This may be optimized by storing the additional data in the workitem. 
*/
+      SVN_ERR(svn_wc__db_pristine_read(&src_stream, NULL, db, local_abspath,
+                                       checksum, scratch_pool, scratch_pool));
     }
 
-  SVN_ERR(svn_stream_open_readonly(&src_stream, source_abspath,
-                                   scratch_pool, scratch_pool));
-
   /* Fetch all the translation bits.  */
   SVN_ERR(svn_wc__get_translate_info(&style, &eol,
                                      &keywords,
Index: subversion/svn/svn.c
===================================================================
--- subversion/svn/svn.c        (revision 1844566)
+++ subversion/svn/svn.c        (working copy)
@@ -2233,8 +2233,8 @@ parse_compatible_version(svn_cl__opt_state_t* opt_
      release, and svn_client_supported_wc_version() should return such
      a value. */
   const svn_version_t *supported = svn_client_supported_wc_version();
-  const svn_version_t *current = svn_client_version();
-  const svn_version_t latest = {current->major, current->minor, 0, NULL};
+  /* Plumbing the non-constant aggregate initialization here: */
+  const svn_version_t latest = {SVN_VER_MAJOR, SVN_VER_MINOR, 0, NULL};
 
   /* Double check that the oldest supported version is sane. */
   SVN_ERR_ASSERT(supported->patch == 0);

Reply via email to