[svn.haxx.se] · SVN Dev · SVN Users · SVN Org · TSVN Dev · TSVN Users · Subclipse Dev · Subclipse Users · this month's index

[PATCH] Reduce full file reads by svn_wc_transmit_text_deltas to 2 (from 5)

From: Erik Huelsmann <ehuels_at_gmail.com>
Date: 2005-12-18 23:11:50 CET

I wrote the patch below as an exercise to do further libsvn_wc
optimizations. Much of the machinery included can / will be reused in
later optimization proposals.

I did it now, because I think this would nicely fit into the
'libsvn_wc optimizing' 1.4 release.

The patch is quite big and therefore, I'm posting before committing.
It passes all tests on my system, but it changes one of the most used
code paths in svn, so this is just to be on the safe side.

I'd like to hear your comments!

bye,

Erik.

Log:
[[[
Improve file commit by reducing 5 -> 2 full file reads
in svn_wc_transmit_text_deltas().

Note:
  This patch consists of several changes which could be committed as
  separate commits. Because I wrote them before making this change
  public, they ended up in one patch. Also, this should make review
  easier.

* subversion/include/svn_subst.h
* subversion/libsvn_subr/subst.c
  (svn_subst_stream_translated_to_normal_form): New. Translating stream
   which translates to the internal normal form.
  (svn_subst_stream_from_special_file): New. Stream which translates
   special files to their normal form.

* subversion/libsvn_subr/subst.c
  (detranslate_special_file_to_stream): New. Abstracted
   from detranslate_special_file().
  (detranslate_special_file): Use detranslate_special_file_to_stream().
  (create_special_file_from_stringbuf): Abstracted from create_special_file().
  (create_special_file): Use create_specail_file_from_bufstring().
  (special_stream_baton, read_handler_special,
   write_handler_special, close_handler_special): New. Components to
   build a specialfile stream from.

* subversion/include/svn_wc.h
* subversion/libsvn_wc/translate.c
  (svn_wc_translated_stream): New. Returns a translating stream according
   to the properties of the versioned file it's associated with.

* subversion/libsvn_wc/translate.c
  (read_handler_unsupported,
   write_handler_unsupported): New. Used to enforce the function
   contract for svn_wc_translated_stream().

* subversion/libsvn_wc/adm_crawler.c
  (copying_stream_baton, read_handler_copy,
   close_handler_copy): New. Components to build a copying stream from.
  (copying_stream): New. Returns a stream which writes
   all data read to another stream.
  (svn_wc_transmit_text_deltas): Changed to use svn_wc_translated_stream,
   copying_stream and svn_stream_checksummed all stacked on top of each
   other. Before this change, there were max 5 full file reads
   (3 text base, 2 working copy). After this change that's reduced to
   2 full reads (once text base, once working copy).
]]]

Index: subversion/include/svn_subst.h
===================================================================
--- subversion/include/svn_subst.h (revision 17826)
+++ subversion/include/svn_subst.h (working copy)
@@ -250,6 +250,41 @@
                              svn_boolean_t expand,
                              apr_pool_t *pool);

+/** Return a stream which performs eol translation and keyword
+ * expansion when read from or written to. The stream @a stream
+ * is used to read and write all data. Make sure you call
+ * svn_stream_close() on @a stream to make sure all data are flushed
+ * and cleaned up.
+ *
+ * Read and write operations perform the same transformation:
+ * all data is translated to normal form.
+ *
+ * @see svn_subst_translate_to_normal_form()
+ *
+ * @since New in 1.4
+ */
+svn_error_t *
+svn_subst_stream_translated_to_normal_form (svn_stream_t **stream,
+ svn_stream_t *source,
+ svn_subst_eol_style_t eol_style,
+ const char *eol_str,
+ svn_boolean_t always_repair_eols,
+ apr_hash_t *keywords,
+ apr_pool_t *pool);
+
+
+/** Returns a stream which translates the special file at @a path to
+ * the internal representation for special files when read from. When
+ * written to, it does the reverse: creating a special file when the
+ * stream is closed.
+ *
+ * @since New in 1.4.
+ */
+svn_error_t *
+svn_subst_stream_from_specialfile (svn_stream_t **stream,
+ const char *path,
+ apr_pool_t *pool);
+
 /** Similar to svn_subst_translate_stream3() except relies upon a
  * @c svn_subst_keywords_t struct instead of a hash for the keywords.
  *
@@ -280,7 +315,6 @@
                             const svn_subst_keywords_t *keywords,
                             svn_boolean_t expand);

-
 /**
  * Translates the file at path @a src into a file at path @a dst. The
  * parameters @a *eol_str, @a repair, @a *keywords and @a expand are
Index: subversion/include/svn_wc.h
===================================================================
--- subversion/include/svn_wc.h (revision 17826)
+++ subversion/include/svn_wc.h (working copy)
@@ -3262,6 +3262,33 @@
                                      svn_boolean_t force_repair,
                                      apr_pool_t *pool);

+
+/** Returns a @a stream allocated in @a pool with access to the given
+ * @a path taking the file properties from @a versioned_file using
+ * @a adm_access.
+ *
+ * When translation from normal form is requested
+ * (@c SVN_WC_TRANSLATE_FROM_NF is specified in @a flags), @a path
+ * is used as target path and stream read operations are not supported.
+ * Conversely, if translation to normal form is requested
+ * (@c SVN_WC_TRANSLATE_TO_NF is specified in @a flags), @a path is
+ * used as source path and stream write operations are not supported.
+ *
+ * The @a flags are the same constants as those used for
+ * svn_wc_translated_file().
+ *
+ *
+ *
+ * @since New in 1.4.
+ */
+svn_error_t *
+svn_wc_translated_stream (svn_stream_t **stream,
+ const char *path,
+ const char *versioned_file,
+ svn_wc_adm_access_t *adm_access,
+ apr_uint32_t flags,
+ apr_pool_t *pool);
+
 
 /* Text/Prop Deltas Using an Editor */

Index: subversion/libsvn_wc/adm_crawler.c
===================================================================
--- subversion/libsvn_wc/adm_crawler.c (revision 17826)
+++ subversion/libsvn_wc/adm_crawler.c (working copy)
@@ -694,6 +694,67 @@
                                   pool);
 }

+
+/*** Copying stream ***/
+
+/* A copying stream is a bit like the unix tee utility:
+ *
+ * It reads the SOURCE when asked for data and while returning it,
+ * also writes the same data to TARGET.
+ */
+struct copying_stream_baton
+{
+ /* Stream to read input from. */
+ svn_stream_t *source;
+
+ /* Stream to write all data read to. */
+ svn_stream_t *target;
+};
+
+
+static svn_error_t *
+read_handler_copy (void *baton, char *buffer, apr_size_t *len)
+{
+ struct copying_stream_baton *btn = baton;
+
+ SVN_ERR (svn_stream_read (btn->source, buffer, len));
+
+ return svn_stream_write (btn->target, buffer, len);
+}
+
+static svn_error_t *
+close_handler_copy (void *baton)
+{
+ struct copying_stream_baton *btn = baton;
+
+ SVN_ERR (svn_stream_close (btn->target));
+ return svn_stream_close (btn->source);
+}
+
+
+/* Return a stream - allocated in POOL - which reads its input
+ * from SOURCE and, while returning that to the caller, at the
+ * same time writes that to TARGET.
+ */
+static svn_stream_t *
+copying_stream (svn_stream_t *source,
+ svn_stream_t *target,
+ apr_pool_t *pool)
+{
+ struct copying_stream_baton *baton;
+ svn_stream_t *stream;
+
+ baton = apr_palloc (pool, sizeof (*baton));
+ baton->source = source;
+ baton->target = target;
+
+ stream = svn_stream_create (baton, pool);
+ svn_stream_set_read (stream, read_handler_copy);
+ svn_stream_set_close (stream, close_handler_copy);
+
+ return stream;
+}
+
 svn_error_t *
 svn_wc_transmit_text_deltas (const char *path,
                              svn_wc_adm_access_t *adm_access,
@@ -703,144 +764,152 @@
                              const char **tempfile,
                              apr_pool_t *pool)
 {
- const char *tmpf, *tmp_base;
+ const char *tmp_base;
   svn_txdelta_window_handler_t handler;
   void *wh_baton;
   svn_txdelta_stream_t *txdelta_stream;
- apr_file_t *localfile = NULL;
   apr_file_t *basefile = NULL;
+ apr_file_t *tempbasefile;
+ svn_stream_t *base_stream;
+ svn_stream_t *local_stream;
   const char *base_digest_hex = NULL;
- unsigned char digest[APR_MD5_DIGESTSIZE];
-
+ unsigned char *base_digest = NULL;
+ unsigned char *local_digest = NULL;
+ svn_error_t *err;
+ const svn_wc_entry_t *ent;
+
+ SVN_ERR (svn_wc_entry (&ent, path, adm_access, FALSE, pool));
+
   /* Make an untranslated copy of the working file in the
- administrative tmp area because a) we want this to work even if
- someone changes the working file while we're generating the
- txdelta, b) we need to detranslate eol and keywords anyway, and
- c) after the commit, we're going to copy the tmp file to become
- the new text base anyway. */
- SVN_ERR (svn_wc_translated_file2 (&tmpf, path, path,
- adm_access,
- SVN_WC_TRANSLATE_TO_NF,
- pool));
+ administrative tmp area because a) we need to detranslate eol
+ and keywords anyway, and b) after the commit, we're going to
+ copy the tmp file to become the new text base anyway. */

- /* If the translation didn't create a new file then we need an explicit
- copy, if it did create a new file we need to rename it. */
   tmp_base = svn_wc__text_base_path (path, TRUE, pool);
- if (tmpf == path)
- SVN_ERR (svn_io_copy_file (tmpf, tmp_base, FALSE, pool));
- else
- SVN_ERR (svn_io_file_rename (tmpf, tmp_base, pool));

- /* If we're not sending fulltext, we'll be sending diffs against the
- text-base. */
+ /* Alert the caller that we have created a temporary file that might
+ need to be cleaned up. */
+ if (tempfile)
+ *tempfile = tmp_base;
+
+ /* Translated input */
+ SVN_ERR (svn_wc_translated_stream (&local_stream, path, path,
+ adm_access, SVN_WC_TRANSLATE_TO_NF, pool));
+
+ /* Translation output: the new text base */
+ SVN_ERR (svn_io_file_open (&tempbasefile, tmp_base,
+ APR_WRITE | APR_CREATE, APR_OS_DEFAULT, pool));
+
+ local_stream
+ = copying_stream (local_stream,
+ svn_stream_from_aprfile (tempbasefile, pool), pool);
+
   if (! fulltext)
     {
- /* Before we set up an svndiff stream against the old text base,
- make sure the old text base still matches its checksum.
- Otherwise we could send corrupt data and never know it. */
- const svn_wc_entry_t *ent;
- SVN_ERR (svn_wc_entry (&ent, path, adm_access, FALSE, pool));
-
- /* For backwards compatibility, no checksum means assume a match. */
- if (ent->checksum)
+ if (! ent->checksum)
         {
+ /*### FIXME: The entries file should hold a checksum */
+ unsigned char digest[APR_MD5_DIGESTSIZE];
+
+ /* If there's no checksum in this entry, calculate one */
           const char *tb = svn_wc__text_base_path (path, FALSE, pool);
- unsigned char tb_digest[APR_MD5_DIGESTSIZE];

- SVN_ERR (svn_io_file_checksum (tb_digest, tb, pool));
- base_digest_hex = svn_md5_digest_to_cstring_display (tb_digest,
- pool);
-
- if (strcmp (base_digest_hex, ent->checksum) != 0)
- {
- /* Compatibility hack: working copies created before
- 13 Jan 2003 may have entry checksums stored in
- base64. See svn_io_file_checksum_base64()'s doc
- string for details. */
- const char *digest_base64
- = (svn_base64_from_md5 (tb_digest, pool))->data;
-
- if (strcmp (digest_base64, ent->checksum) != 0)
- {
- /* There is an entry checksum, but it does not match
- the actual text base checksum. Extreme badness.
- Of course, theoretically we could just switch to
- fulltext transmission here, and everything would
- work fine; after all, we're going to replace the
- text base with a new one in a moment anyway, and
- we'd fix the checksum then. But it's better to
- error out. People should know that their text
- bases are getting corrupted, so they can
- investigate. Other commands could be affected,
- too, such as `svn diff'. */
-
- /* Deliberately ignore error; the error about the
- checksum mismatch is more important to return.
- And wrapping the above error into the checksum
- error would be weird, as they're unrelated. */
- svn_error_clear (svn_io_remove_file (tmp_base, pool));
-
- if (tempfile)
- *tempfile = NULL;
-
- return svn_error_createf
- (SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL,
- _("Checksum mismatch for '%s'; "
- "expected '%s', actual: '%s'"),
- svn_path_local_style (tb, pool),
- ent->checksum, base_digest_hex);
- }
- }
+ SVN_ERR (svn_io_file_checksum (digest, tb, pool));
+ base_digest_hex = svn_md5_digest_to_cstring_display (digest, pool);
         }
+ else
+ base_digest_hex = ent->checksum;

       SVN_ERR (svn_wc__open_text_base (&basefile, path, APR_READ, pool));
     }

   /* Tell the editor that we're about to apply a textdelta to the
- file baton; the editor returns to us a window consumer routine
- and baton. */
+ file baton; the editor returns to us a window consumer and baton. */
   SVN_ERR (editor->apply_textdelta
- (file_baton,
- base_digest_hex, pool, &handler, &wh_baton));
+ (file_baton, base_digest_hex, pool, &handler, &wh_baton));

- /* Alert the caller that we have created a temporary file that might
- need to be cleaned up. */
- if (tempfile)
- *tempfile = tmp_base;
+ /* Create a text-delta stream object that pulls
+ data out of the two files. */
+ base_stream = svn_stream_from_aprfile (basefile, pool);
+ if (! fulltext)
+ base_stream
+ = svn_stream_checksummed (base_stream, &base_digest, NULL, pool);
+ local_stream
+ = svn_stream_checksummed (local_stream, &local_digest, NULL, pool);

- /* Open a filehandle for tmp text-base. */
- SVN_ERR_W (svn_io_file_open (&localfile, tmp_base,
- APR_READ, APR_OS_DEFAULT, pool),
- _("Error opening local file"));
+ svn_txdelta (&txdelta_stream, base_stream, local_stream, pool);

- /* Create a text-delta stream object that pulls data out of the two
- files. */
- svn_txdelta (&txdelta_stream,
- svn_stream_from_aprfile (basefile, pool),
- svn_stream_from_aprfile (localfile, pool),
- pool);
-
   /* Pull windows from the delta stream and feed to the consumer. */
- SVN_ERR (svn_txdelta_send_txstream (txdelta_stream, handler,
- wh_baton, pool));
-
+ err = svn_txdelta_send_txstream (txdelta_stream, handler, wh_baton, pool);
+
+ /* Close the two streams to force writing the digest,
+ if we already have an error, ignore this one. */
+ if (err)
+ {
+ svn_error_clear (svn_stream_close (base_stream));
+ svn_error_clear (svn_stream_close (local_stream));
+ }
+ else
+ {
+ SVN_ERR (svn_stream_close (base_stream));
+ SVN_ERR (svn_stream_close (local_stream));
+ }
+
+ /* If we have an error, it may be caused by a corrupt text base.
+ Check the checksum and discard `err' if they don't match. */
+ if (! fulltext && ent->checksum && base_digest)
+ {
+ /*### FIXME: The entries file should hold a checksum,
+ meaning the above condition should not include ent->checksum */
+
+ base_digest_hex = svn_md5_digest_to_cstring_display (base_digest, pool);
+
+ if (strcmp (base_digest_hex, ent->checksum) != 0)
+ {
+ /* The entry checksum does not match the actual text
+ base checksum. Extreme badness. Of course,
+ theoretically we could just switch to
+ fulltext transmission here, and everything would
+ work fine; after all, we're going to replace the
+ text base with a new one in a moment anyway, and
+ we'd fix the checksum then. But it's better to
+ error out. People should know that their text
+ bases are getting corrupted, so they can
+ investigate. Other commands could be affected,
+ too, such as `svn diff'. */
+
+ /* Deliberately ignore errors; the error about the
+ checksum mismatch is more important to return. */
+ svn_error_clear (err);
+ svn_error_clear (svn_io_remove_file (tmp_base, pool));
+
+ if (tempfile)
+ *tempfile = NULL;
+
+ return svn_error_createf
+ (SVN_ERR_WC_CORRUPT_TEXT_BASE, NULL,
+ _("Checksum mismatch for '%s'; "
+ "expected '%s', actual: '%s'"),
+ svn_path_local_style (svn_wc__text_base_path (path, FALSE, pool),
+ pool),
+ ent->checksum, base_digest_hex);
+ }
+ else
+ SVN_ERR (err);
+ }
+ else
+ SVN_ERR (err);
+
   /* Close the two files */
- SVN_ERR (svn_io_file_close (localfile, pool));
-
+ /* SVN_ERR (svn_io_file_close (localfile, pool)); */
+ SVN_ERR (svn_io_file_close (tempbasefile, pool));
   if (basefile)
     SVN_ERR (svn_wc__close_text_base (basefile, path, 0, pool));

- /* ### This is a pity. tmp_base was created with svn_io_copy_file()
- above, which uses apr_file_copy(), which probably called
- apr_file_transfer_contents(), which ran over every byte of the
- file and therefore could have computed a checksum effortlessly.
- But we're not about to change the interface of apr_file_copy(),
- so we'll have to run over the bytes again... */
- SVN_ERR (svn_io_file_checksum (digest, tmp_base, pool));

   /* Close the file baton, and get outta here. */
   return editor->close_file
- (file_baton, svn_md5_digest_to_cstring (digest, pool), pool);
+ (file_baton, svn_md5_digest_to_cstring (local_digest, pool), pool);
 }

Index: subversion/libsvn_wc/translate.c
===================================================================
--- subversion/libsvn_wc/translate.c (revision 17826)
+++ subversion/libsvn_wc/translate.c (working copy)
@@ -41,7 +41,72 @@

 #include "svn_private_config.h"

+
+
+static svn_error_t *
+read_handler_unsupported (void *baton, char *buffer, apr_size_t *len)
+{
+ abort();
+}
+
+static svn_error_t *
+write_handler_unsupported (void *baton, const char *buffer, apr_size_t *len)
+{
+ abort();
+}
+
 svn_error_t *
+svn_wc_translated_stream (svn_stream_t **stream,
+ const char *path,
+ const char *versioned_file,
+ svn_wc_adm_access_t *adm_access,
+ apr_uint32_t flags,
+ apr_pool_t *pool)
+{
+ svn_subst_eol_style_t style;
+ const char *eol;
+ apr_hash_t *keywords;
+ svn_boolean_t special;
+ svn_boolean_t to_nf = flags & SVN_WC_TRANSLATE_TO_NF;
+
+ SVN_ERR (svn_wc__get_eol_style (&style, &eol, versioned_file,
+ adm_access, pool));
+ SVN_ERR (svn_wc__get_keywords (&keywords, versioned_file,
+ adm_access, NULL, pool));
+ SVN_ERR (svn_wc__get_special (&special, versioned_file, adm_access, pool));
+
+ if (special)
+ SVN_ERR (svn_subst_stream_from_specialfile (stream, path, pool));
+ else
+ {
+ apr_file_t *file;
+ svn_boolean_t repair_forced = flags & SVN_WC_TRANSLATE_FORCE_EOL_REPAIR;
+
+ SVN_ERR (svn_io_file_open (&file, path,
+ to_nf ? (APR_READ | APR_BUFFERED)
+ : (APR_CREATE | APR_WRITE | APR_BUFFERED),
+ APR_OS_DEFAULT, pool));
+
+ *stream = svn_io_stream_from_aprfile2 (file, pool);
+ if (to_nf)
+ SVN_ERR (svn_subst_stream_translated_to_normal_form
+ (stream, *stream, style, eol, repair_forced, keywords, pool));
+ else
+ *stream = svn_subst_stream_translated
+ (*stream, eol, repair_forced, keywords, TRUE, pool);
+ }
+
+ /* Enfore our contract, because a specialfile stream won't */
+ if (to_nf)
+ svn_stream_set_write (*stream, write_handler_unsupported);
+ else
+ svn_stream_set_read (*stream, read_handler_unsupported);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
 svn_wc_translated_file2 (const char **xlated_path,
                          const char *src,
                          const char *versioned_file,
Index: subversion/libsvn_subr/subst.c
===================================================================
--- subversion/libsvn_subr/subst.c (revision 17826)
+++ subversion/libsvn_subr/subst.c (working copy)
@@ -139,6 +139,28 @@
                                         pool);
 }

+svn_error_t *
+svn_subst_stream_translated_to_normal_form (svn_stream_t **stream,
+ svn_stream_t *source,
+ svn_subst_eol_style_t eol_style,
+ const char *eol_str,
+ svn_boolean_t always_repair_eols,
+ apr_hash_t *keywords,
+ apr_pool_t *pool)
+{
+ if (eol_style == svn_subst_eol_style_native)
+ eol_str = SVN_SUBST__DEFAULT_EOL_STR;
+ else if (! (eol_style == svn_subst_eol_style_fixed
+ || eol_style == svn_subst_eol_style_none))
+ return svn_error_create (SVN_ERR_IO_UNKNOWN_EOL, NULL, NULL);
+
+ *stream = svn_subst_stream_translated (source, eol_str,
+ always_repair_eols,
+ keywords, FALSE, pool);
+
+ return SVN_NO_ERROR;
+}
+
 
 /* Helper function for svn_subst_build_keywords */

@@ -1130,10 +1152,7 @@
   svn_pool_clear (b->iterpool);

   b->written = TRUE;
- SVN_ERR (translate_chunk (b->stream, b->out_baton, buffer, *len,
- b->iterpool));
-
- return SVN_NO_ERROR;
+ return translate_chunk (b->stream, b->out_baton, buffer, *len, b->iterpool);
 }

 static svn_error_t *
@@ -1340,99 +1359,98 @@
 }

-/* Given a special file at SRC, generate a textual representation of
- it in a normal file at DST. Perform all allocations in POOL. */
+/* Set SRC_STREAM to a stream from which the internal representation
+ * for the special file at SRC can be read.
+ *
+ * The stream returned will be allocated in POOL.
+ */
 static svn_error_t *
-detranslate_special_file (const char *src,
- const char *dst,
- apr_pool_t *pool)
+detranslate_special_file_to_stream (svn_stream_t **src_stream,
+ const char *src,
+ apr_pool_t *pool)
 {
- const char *dst_tmp;
+ apr_finfo_t finfo;
+ apr_file_t *s;
   svn_string_t *buf;
- apr_file_t *s, *d;
- svn_stream_t *src_stream, *dst_stream;
- apr_finfo_t finfo;
-
+
   /* First determine what type of special file we are
      detranslating. */
   SVN_ERR (svn_io_stat (&finfo, src, APR_FINFO_MIN | APR_FINFO_LINK, pool));

- /* Open a temporary destination that we will eventually atomically
- rename into place. */
- SVN_ERR (svn_io_open_unique_file2 (&d, &dst_tmp, dst,
- ".tmp", svn_io_file_del_none, pool));
-
- dst_stream = svn_stream_from_aprfile (d, pool);
-
   switch (finfo.filetype) {
   case APR_REG:
     /* Nothing special to do here, just copy the original file's
        contents. */
     SVN_ERR (svn_io_file_open (&s, src, APR_READ | APR_BUFFERED,
                                APR_OS_DEFAULT, pool));
- src_stream = svn_stream_from_aprfile (s, pool);
+ *src_stream = svn_stream_from_aprfile (s, pool);

- SVN_ERR (svn_stream_copy (src_stream, dst_stream, pool));
     break;
   case APR_LNK:
     /* Determine the destination of the link. */
+
+ *src_stream = svn_stream_from_stringbuf (svn_stringbuf_create ("", pool),
+ pool);
     SVN_ERR (svn_io_read_link (&buf, src, pool));

- SVN_ERR (svn_stream_printf (dst_stream, pool, "link %s",
+ SVN_ERR (svn_stream_printf (*src_stream, pool, "link %s",
                                 buf->data));
     break;
   default:
     abort ();
   }

+ return SVN_NO_ERROR;
+}
+
+/* Given a special file at SRC, generate a textual representation of
+ it in a normal file at DST. Perform all allocations in POOL. */
+static svn_error_t *
+detranslate_special_file (const char *src,
+ const char *dst,
+ apr_pool_t *pool)
+{
+ const char *dst_tmp;
+ apr_file_t *d;
+ svn_stream_t *src_stream, *dst_stream;
+
+
+ /* Open a temporary destination that we will eventually atomically
+ rename into place. */
+ SVN_ERR (svn_io_open_unique_file2 (&d, &dst_tmp, dst,
+ ".tmp", svn_io_file_del_none, pool));
+
+ dst_stream = svn_stream_from_aprfile (d, pool);
+
+ SVN_ERR (detranslate_special_file_to_stream (&src_stream, src, pool));
+ SVN_ERR (svn_stream_copy (src_stream, dst_stream, pool));
+
   SVN_ERR (svn_io_file_close (d, pool));

   /* Do the atomic rename from our temporary location. */
   SVN_ERR (svn_io_file_rename (dst_tmp, dst, pool));
-
+
   return SVN_NO_ERROR;
 }

-
-/* Given a file containing a repository representation of a special
- file in SRC, create the appropriate special file at location DST.
- Perform all allocations in POOL. */
+/* Creates a special file DST from the internal representation given
+ * in SRC.
+ *
+ * All temporary allocations will be done in POOL.
+ */
 static svn_error_t *
-create_special_file (const char *src,
- const char *dst,
- apr_pool_t *pool)
+create_special_file_from_stringbuf (svn_stringbuf_t *src,
+ const char *dst,
+ apr_pool_t *pool)
 {
- svn_stringbuf_t *contents;
+ svn_error_t *err;
   char *identifier, *remainder;
- const char *dst_tmp, *src_tmp = NULL;
- svn_error_t *err;
- svn_node_kind_t kind;
- svn_boolean_t is_special;
+ const char *dst_tmp;

- /* Check to see if we are being asked to create a special file from
- a special file. If so, do a temporary detranslation and work
- from there. */
- SVN_ERR (svn_io_check_special_path (src, &kind, &is_special, pool));
-
- if (is_special)
- {
- SVN_ERR (svn_io_open_unique_file2 (NULL, &src_tmp, dst, ".tmp",
- svn_io_file_del_none, pool));
- SVN_ERR (detranslate_special_file (src, src_tmp, pool));
- src = src_tmp;
- }
-
- /* Read in the detranslated file. */
- SVN_ERR (svn_stringbuf_from_file (&contents, src, pool));
-
- /* If there was just a temporary detranslation, remove it now. */
- if (src_tmp)
- SVN_ERR (svn_io_remove_file (src_tmp, pool));
-
   /* Separate off the identifier. The first space character delimits
      the identifier, after which any remaining characters are specific
      to the actual special device being created. */
- identifier = contents->data;
+ identifier = src->data;
   for (remainder = identifier; *remainder; remainder++)
     {
       if (*remainder == ' ')
@@ -1442,7 +1460,7 @@
           break;
         }
     }
-
+
   if (! strcmp (identifier, SVN_SUBST__SPECIAL_LINK_STR))
     {
       /* For symlinks, the type specific data is just a filesystem
@@ -1468,16 +1486,48 @@
           /* Fall back to just copying the text-base. */
           SVN_ERR (svn_io_open_unique_file2 (NULL, &dst_tmp, dst, ".tmp",
                                              svn_io_file_del_none, pool));
- SVN_ERR (svn_io_copy_file (src, dst_tmp, TRUE, pool));
+ SVN_ERR (svn_io_file_create (dst_tmp, src->data, pool));
         }
       else
         return err;
     }

   /* Do the atomic rename from our temporary location. */
- SVN_ERR (svn_io_file_rename (dst_tmp, dst, pool));
+ return svn_io_file_rename (dst_tmp, dst, pool);
+}

- return SVN_NO_ERROR;
+/* Given a file containing a repository representation of a special
+ file in SRC, create the appropriate special file at location DST.
+ Perform all allocations in POOL. */
+static svn_error_t *
+create_special_file (const char *src,
+ const char *dst,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *contents;
+ svn_node_kind_t kind;
+ svn_boolean_t is_special;
+ svn_stream_t *source;
+
+ /* Check to see if we are being asked to create a special file from
+ a special file. If so, do a temporary detranslation and work
+ from there. */
+ SVN_ERR (svn_io_check_special_path (src, &kind, &is_special, pool));
+
+ if (is_special)
+ {
+ svn_boolean_t eof;
+
+ SVN_ERR (detranslate_special_file_to_stream (&source, src, pool));
+ /* The special file normal form doesn't have line endings,
+ * so, read all of the file into the stringbuf */
+ SVN_ERR (svn_stream_readline (source, &contents, "\n", &eof, pool));
+ }
+ else
+ /* Read in the detranslated file. */
+ SVN_ERR (svn_stringbuf_from_file (&contents, src, pool));
+
+ return create_special_file_from_stringbuf (contents, dst, pool);
 }

@@ -1573,9 +1623,94 @@
   return SVN_NO_ERROR;
 }

+
+/*** Special file stream support */

+struct special_stream_baton
+{
+ svn_stream_t *read_stream;
+ svn_stringbuf_t *write_content;
+ svn_stream_t *write_stream;
+ const char *path;
+ apr_pool_t *pool;
+};

+
+static svn_error_t *
+read_handler_special (void *baton, char *buffer, apr_size_t *len)
+{
+ struct special_stream_baton *btn = baton;
+
+ if (btn->read_stream)
+ /* We actually found a file to read from */
+ return svn_stream_read (btn->read_stream, buffer, len);
+ else
+ return svn_error_createf (APR_ENOENT, NULL,
+ "Can't read special file: File '%s' not found",
+ svn_path_local_style (btn->path, btn->pool));
+}
+
+static svn_error_t *
+write_handler_special (void *baton, const char *buffer, apr_size_t *len)
+{
+ struct special_stream_baton *btn = baton;
+
+ return svn_stream_write (btn->write_stream, buffer, len);
+}
+
+
+static svn_error_t *
+close_handler_special (void *baton)
+{
+ struct special_stream_baton *btn = baton;
+
+ if (btn->write_content->len)
+ {
+ /* yeay! we received data and need to create a special file! */
+
+ SVN_ERR (create_special_file_from_stringbuf (btn->write_content,
+ btn->path,
+ btn->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
 svn_error_t *
+svn_subst_stream_from_specialfile (svn_stream_t **stream,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct special_stream_baton *baton = apr_palloc (pool, sizeof(*baton));
+ svn_error_t *err;
+
+ baton->pool = pool;
+ baton->path = apr_pstrdup (pool, path);
+
+ err = detranslate_special_file_to_stream (&baton->read_stream, path, pool);
+
+ if (err && APR_STATUS_IS_ENOENT (err->apr_err))
+ {
+ svn_error_clear (err);
+ baton->read_stream = NULL;
+ }
+
+ baton->write_content = svn_stringbuf_create ("", pool);
+ baton->write_stream = svn_stream_from_stringbuf (baton->write_content, pool);
+
+ *stream = svn_stream_create (baton, pool);
+ svn_stream_set_read (*stream, read_handler_special);
+ svn_stream_set_write (*stream, write_handler_special);
+ svn_stream_set_close (*stream, close_handler_special);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** String translation */
+svn_error_t *
 svn_subst_translate_string (svn_string_t **new_value,
                             const svn_string_t *value,
                             const char *encoding,
Received on Sun Dec 18 23:12:56 2005

This is an archived mail posted to the Subversion Dev mailing list.