On Thu, Jul 08, 2010 at 09:52:51AM +0100, Julian Foad wrote:
> Hi Stefan.  I've just started looking at this.  Overall impression is it
> looks like you've done it very well - thank you!
> 
> Could you possibly get the readline_detect_eol() API changes out of this
> patch, and make them in a separate patch either before or after this?
> Or if that change belongs in this patch, please explain why in the log
> msg.

It's actually not needed. I think I added it trying to make the
parameter list of svn_io_readline_fn_t match the ones of
svn_stream_readline and svn_stream_readline_detect_eol as closely
as possible.

Looking at it again, adding another parameter to svn_io_readline_fn_t
which says whether the EOL should be detected is much cleaner.
This simplifies implementations, and allows callers of
svn_stream_readline_detect_eol to be lazy about initialising the
eol output parameter.

Updated diff (verified by running just the patch tests which exercise
stream_readline a lot) and updated log message below. Thanks!

[[[
Fix issue #3555, 'Remove the "line_filter" and "line_transformer" callbacks
from svn_stream_t'.

Make svn_stream_readline() a virtual method of svn_stream_t,
and provide custom implementations of readline methods in the diff
parsing code (which is the only place where we need this right now).

* subversion/include/svn_io.h
  (svn_io_line_filter_cb_t, svn_io_line_transformer_cb_t,
   svn_stream_set_line_filter_callback,
   svn_stream_set_line_transformer_callback): Remove.
  (svn_io_readline_fn_t, svn_stream_set_readline): Declare.
  (svn_stream_readline): Adjust docstring.
  (svn_stream_readline_detect_eol): Switch to dual-pool paradigm and
   document that the stream needs to support mark and seek.

* subversion/libsvn_diff/parse-diff.c
  (original_line_filter, modified_line_filter, remove_leading_char_transformer,
   reverse_diff_transformer): Remove.
  (hunk_text_stream_baton, read_handler_hunk_text, write_handler_hunk_text,
   close_handler_hunk_text, reset_handler_hunk_text, mark_handler_hunk_text,
   seek_handler_hunk_text, scan_eol, readline_handler_hunk_text,
   stream_hunk_text, stream_hunk_text_original, stream_hunk_text_modified):
    New. Implementation of a stream which provides a special readline
    method to read original/modified texts of hunks from a patch file stream.
  (reverse_diff_text_stream_baton, read_handler_reverse_diff_text,
   write_handler_reverse_diff_text, close_handler_reverse_diff_text,
   reset_handler_reverse_diff_text, mark_handler_reverse_diff_text,
   seek_handler_reverse_diff_text, readline_handler_reverse_diff_text,
   stream_reverse_diff_text): New. Implementation of a stream which
    provides a special readline method which reverses unidiff data read
    from the wrapped stream.
  (parse_next_hunk): Track svn_stream_readline_detect_eol() dual-pool change.
   Add a comment explaining why the patch file gets opened multiple
   times (drive-by fix because this confused me at first).
   Instead of installing line-filter/transformation callbacks on
   streams, wrap streams with appropriate wrapper streams.

* subversion/libsvn_subr/stream.c
  (struct svn_stream_t): Replace line_filter_cb and line_transformer_cb
   members with readline_fn member.
  (svn_stream_create): Track changes to svn_stream_t.
  (svn_stream_set_line_filter_callback,
   svn_stream_set_line_transformer_callback,
   line_filter, line_transformer): Remove.
  (scan_eol): Tweak argument list for use within a stream method.
   This function can no longer expect a stream, so pass a baton
   and a set of required stream methods instead.
  (stream_readline): Make this an svn_io_readline_fn_t implementation.
   Remove handling of line filters/transformers.
  (svn_stream_readline): Instead of calling the stream_readline() helper
   directly, call a custom readline implementation if one is set on the
   stream. If no custom implementation is provided, fall back to the
   stream_readline() helper function to preserve compatibility with 1.6.x.
  (svn_stream_readline_detect_eol): As previous, and ensure that
   the stream has mark/seek support as it is needed for EOL detection.
  (readline_handler_empty): Custom readline handler for the empty stream.
  (svn_stream_empty, svn_stream_disown, svn_stream_from_aprfile2,
   svn_stream_from_aprfile_range_readonly, svn_stream_compressed,
   svn_stream_checksummed2, svn_stream_checksummed, svn_stream_from_stringbuf,
   svn_stream_from_string): Set a readline method.

* subversion/libsvn_client/patch.c
  (read_line, match_hunk, reject_hunk, apply_hunk): Pass two pools to
   svn_stream_readline_detect_eol().

* subversion/tests/libsvn_subr/stream-test.c
  (line_filter, test_stream_line_filter, line_transformer,
   test_stream_line_transformer,
   test_stream_line_filter_and_transformer): Remove these tests.
  (test_funcs): Remove removed tests.
]]]


Index: subversion/include/svn_io.h
===================================================================
--- subversion/include/svn_io.h (revision 961349)
+++ subversion/include/svn_io.h (working copy)
@@ -780,40 +780,39 @@ typedef svn_error_t *(*svn_io_mark_fn_t)(void *bat
 typedef svn_error_t *(*svn_io_seek_fn_t)(void *baton,
                                          svn_stream_mark_t *mark);
 
-/** Line-filtering callback function for a generic stream.
- * @a baton is the stream's baton.
- * @see svn_stream_t, svn_stream_set_baton() and svn_stream_readline().
+/** Line-reading handler function for a generic stream.
  *
- * @since New in 1.7.
- */
-typedef svn_error_t *(*svn_io_line_filter_cb_t)(svn_boolean_t *filtered,
-                                                const char *line,
-                                                void *baton,
-                                                apr_pool_t *scratch_pool);
-
-/** A callback function, invoked by svn_stream_readline(), which can perform
- * arbitary transformations on the line before it is passed back to the caller
- * of svn_stream_readline().
+ * Allocate @a *stringbuf in @a result_pool, and read into it one line
+ * from the stream. @a baton is the stream's baton.
+ * If @a detect_eol is @c FALSE, @a *eol is used as the expected line
+ * terminator. If @a detect_eol is @c TRUE, the line-terminator is
+ * detected automatically and stored in @a *eol if @a eol is not NULL.
+ * If EOF is reached and the stream does not end with a newline character,
+ * and @a eol is not NULL, @a *eol is set to NULL.
  *
- * Returns a transformed stringbuf in @a buf, allocated in @a result_pool.
- * This callback gets invoked on lines which were not filtered by the
- * line-filtering callback function.
+ * @a mark_fn and @a seek_fn are required to be non-NULL if the end-of-line
+ * indicator is to be detected automatically. Else, they may be NULL.
  *
- * Implementations should always at least return an empty stringbuf.
- * It is a fatal error if an implementation returns @a *buf as NULL.
+ * The line-terminator is read from the stream, but is not added to
+ * the end of the stringbuf.  Instead, the stringbuf ends with a usual '\\0'.
  *
- * @a baton is the stream's baton.
+ * If @a stream runs out of bytes before encountering a line-terminator,
+ * then set @a *eof to @c TRUE, otherwise set @a *eof to @c FALSE.
  *
- * @see svn_stream_t, svn_stream_set_baton(), svn_io_line_filter_cb_t and
- * svn_stream_readline().
+ * Temporary allocations will be performed in @a scratch_pool.
  *
- * @since New in 1.7.
- */
-typedef svn_error_t *(*svn_io_line_transformer_cb_t)(svn_stringbuf_t **buf,
-                                                     const char *line,
-                                                     void *baton,
-                                                     apr_pool_t *result_pool,
-                                                     apr_pool_t *scratch_pool);
+ * @see svn_stream_readline and svn_stream_readline_detect_eol.
+ * @since New in 1.7. */
+typedef svn_error_t *(*svn_io_readline_fn_t)(void *baton,
+                                             svn_stringbuf_t **stringbuf,
+                                             const char **eol,
+                                             svn_boolean_t *eof,
+                                             svn_boolean_t detect_eol,
+                                             svn_read_fn_t read_fn,
+                                             svn_io_mark_fn_t mark_fn,
+                                             svn_io_seek_fn_t seek_fn,
+                                             apr_pool_t *result_pool,
+                                             apr_pool_t *scratch_pool);
 
 /** Create a generic stream.  @see svn_stream_t. */
 svn_stream_t *
@@ -864,23 +863,11 @@ void
 svn_stream_set_seek(svn_stream_t *stream,
                     svn_io_seek_fn_t seek_fn);
 
-/** Set @a stream's line-filtering callback function to @a line_filter_cb
- *
- * @since New in 1.7.
- */
+/** Set @a stream's readline function to @a readline_fn */
 void
-svn_stream_set_line_filter_callback(svn_stream_t *stream,
-                                    svn_io_line_filter_cb_t line_filter_cb);
+svn_stream_set_readline(svn_stream_t *stream,
+                        svn_io_readline_fn_t readline_fn);
 
-/** Set @a streams's line-transforming callback function to
- * @a line_transformer_cb.
- *
- * @since New in 1.7.
- */
-void
-svn_stream_set_line_transformer_callback(
-  svn_stream_t *stream,
-  svn_io_line_transformer_cb_t line_transformer_cb);
 
 /** Create a stream that is empty for reading and infinite for writing. */
 svn_stream_t *
@@ -1203,19 +1190,6 @@ svn_stream_printf_from_utf8(svn_stream_t *stream,
  *
  * If @a stream runs out of bytes before encountering a line-terminator,
  * then set @a *eof to @c TRUE, otherwise set @a *eof to FALSE.
- *
- * If a line-filter callback function was set on the stream using
- * svn_stream_set_line_filter_callback(), lines will only be returned
- * if they pass the filtering decision of the callback function.
- * If end-of-file is encountered while reading the line and the line
- * is filtered, @a *stringbuf will be empty.
- *
- * If a line-transformer callback function was set on the stream using
- * svn_stream_set_line_transformer_callback(), lines will be returned
- * transformed, in a way determined by the callback.
- *
- * Note that the line-transformer callback gets called after the line-filter
- * callback, not before.
  */
 svn_error_t *
 svn_stream_readline(svn_stream_t *stream,
@@ -1230,6 +1204,11 @@ svn_stream_readline(svn_stream_t *stream,
  * is returned in @a *eol.  If EOF is reached and the stream does not
  * end with a newline character, @a *eol will be NULL.
  *
+ * The @a stream is required to support mark and seek. @see svn_stream_mark.
+ *
+ * The @a *stringbuf will be allocated in @a result_pool.
+ * @a scratch_pool is used for temporary allocations.
+ *
  * @since New in 1.7.
  */
 svn_error_t *
@@ -1237,7 +1216,8 @@ svn_stream_readline_detect_eol(svn_stream_t *strea
                                svn_stringbuf_t **stringbuf,
                                const char **eol,
                                svn_boolean_t *eof,
-                               apr_pool_t *pool);
+                               apr_pool_t *result_pool,
+                               apr_pool_t *scratch_pool);
 
 /**
  * Read the contents of the readable stream @a from and write them to the
Index: subversion/libsvn_diff/parse-diff.c
===================================================================
--- subversion/libsvn_diff/parse-diff.c (revision 961349)
+++ subversion/libsvn_diff/parse-diff.c (working copy)
@@ -33,6 +33,8 @@
 #include "svn_dirent_uri.h"
 #include "svn_diff.h"
 
+#include "private/svn_eol_private.h"
+
 /* Helper macro for readability */
 #define starts_with(str, start)  \
   (strncmp((str), (start), strlen(start)) == 0)
@@ -184,89 +186,405 @@ parse_hunk_header(const char *header, svn_hunk_t *
   return TRUE;
 }
 
-/* A stream line-filter which allows only original text from a hunk,
- * and filters special lines (which start with a backslash). */
+/* Users of the diff parsing API are provided with various streams
+ * to read lines from a hunk. These streams return:
+ *
+ *  - the original hunk text (all lines starting with '-' or ' ')
+ *  - the modified hunk text (all lines starting with '+' or ' ')
+ *  - the plain unidiff text (possibly reversed)
+ *
+ * To achieve this, we wrap the patch file stream with custom streams,
+ * which override the wrapped stream's readline method. */
+
+/* Baton for a stream that reads hunk texts. */
+struct hunk_text_stream_baton {
+  /* The leading unidiff symbol of lines which should be filtered.
+   * Can be '+' or '-', depending on whether we're providing the original
+   * or modified version of the hunk. */
+  char verboten;
+
+  svn_stream_t *wrapped_stream;
+};
+
+/* An implementation of svn_read_fn_t. */
 static svn_error_t *
-original_line_filter(svn_boolean_t *filtered, const char *line, void *baton,
-                     apr_pool_t *scratch_pool)
+read_handler_hunk_text(void *baton, char *buffer, apr_size_t *len)
 {
-  *filtered = (line[0] == '+' || line[0] == '\\');
-  return SVN_NO_ERROR;
+  struct hunk_text_stream_baton *b = baton;
+  return svn_stream_read(b->wrapped_stream, buffer, len);
 }
 
-/* A stream line-filter which allows only modified text from a hunk,
- * and filters special lines (which start with a backslash). */
+/* An implementation of svn_write_fn_t. */
 static svn_error_t *
-modified_line_filter(svn_boolean_t *filtered, const char *line, void *baton,
-                     apr_pool_t *scratch_pool)
+write_handler_hunk_text(void *baton, const char *buffer, apr_size_t *len)
 {
-  *filtered = (line[0] == '-' || line[0] == '\\');
-  return SVN_NO_ERROR;
+  struct hunk_text_stream_baton *b = baton;
+  return svn_stream_write(b->wrapped_stream, buffer, len);
 }
 
-/** line-transformer callback to shave leading diff symbols. */
+/* An implementation of svn_close_fn_t. */
 static svn_error_t *
-remove_leading_char_transformer(svn_stringbuf_t **buf,
-                                const char *line,
-                                void *baton,
-                                apr_pool_t *result_pool,
-                                apr_pool_t *scratch_pool)
+close_handler_hunk_text(void *baton)
 {
-  if (line[0] == '+' || line[0] == '-' || line[0] == ' ')
-    *buf = svn_stringbuf_create(line + 1, result_pool);
-  else
-    *buf = svn_stringbuf_create(line, result_pool);
+  struct hunk_text_stream_baton *b = baton;
+  return svn_stream_close(b->wrapped_stream);
+}
 
+/* An implementation of svn_io_reset_fn_t. */
+static svn_error_t *
+reset_handler_hunk_text(void *baton)
+{
+  struct hunk_text_stream_baton *b = baton;
+  return svn_stream_reset(b->wrapped_stream);
+}
+
+/* An implementation of svn_io_mark_fn_t. */
+static svn_error_t *
+mark_handler_hunk_text(void *baton, svn_stream_mark_t **mark, apr_pool_t *pool)
+{
+  struct hunk_text_stream_baton *b = baton;
+  return svn_stream_mark(b->wrapped_stream, mark, pool);
+}
+
+/* An implementation of svn_io_seek_fn_t. */
+static svn_error_t *
+seek_handler_hunk_text(void *baton, svn_stream_mark_t *mark)
+{
+  struct hunk_text_stream_baton *b = baton;
+  return svn_stream_seek(b->wrapped_stream, mark);
+}
+
+/* Invoke the READ_FN function of a stream to scan for an end-of-line
+ * indicator, and return it in *EOL.
+ * Set *EOL to NULL if the stream runs out before an end-of-line indicator
+ * is found. */
+static svn_error_t *
+scan_eol(void *baton,
+         svn_read_fn_t read_fn,
+         svn_io_mark_fn_t mark_fn,
+         svn_io_seek_fn_t seek_fn,
+         const char **eol,
+         apr_pool_t *pool)
+{
+  const char *eol_str;
+  svn_stream_mark_t *mark;
+
+  SVN_ERR(mark_fn(baton, &mark, pool));
+
+  eol_str = NULL;
+  while (! eol_str)
+    {
+      char buf[512];
+      apr_size_t len;
+
+      len = sizeof(buf);
+      SVN_ERR(read_fn(baton, buf, &len));
+      if (len == 0)
+          break; /* EOF */
+      eol_str = svn_eol__detect_eol(buf, buf + len);
+    }
+
+  SVN_ERR(seek_fn(baton, mark));
+
+  *eol = eol_str;
+
   return SVN_NO_ERROR;
 }
 
-/** line-transformer callback to reverse a diff text. */
+/* An implementation of svn_io_readline_fn_t. */
 static svn_error_t *
-reverse_diff_transformer(svn_stringbuf_t **buf,
-                         const char *line,
-                         void *baton,
-                         apr_pool_t *result_pool,
-                         apr_pool_t *scratch_pool)
+readline_handler_hunk_text(void *baton,
+                           svn_stringbuf_t **stringbuf,
+                           const char **eol,
+                           svn_boolean_t *eof,
+                           svn_boolean_t detect_eol,
+                           svn_read_fn_t read_fn,
+                           svn_io_mark_fn_t mark_fn,
+                           svn_io_seek_fn_t seek_fn,
+                           apr_pool_t *result_pool,
+                           apr_pool_t *scratch_pool)
 {
-  svn_hunk_t hunk;
+  svn_stringbuf_t *str;
+  apr_pool_t *iterpool;
+  svn_boolean_t filtered;
+  const char *eol_str;
+  struct hunk_text_stream_baton *b = baton;
 
-  /* ### Pass the already parsed hunk via the baton?
-   * ### Maybe we should really make svn_stream_readline() a proper stream
-   * ### method and override it instead of adding special callbacks? */
-  if (parse_hunk_header(line, &hunk, "@@", FALSE, scratch_pool))
+  *eof = FALSE;
+
+  iterpool = svn_pool_create(scratch_pool);
+  do
     {
-      *buf = svn_stringbuf_createf(result_pool,
-                                   "@@ -%lu,%lu +%lu,%lu @@",
-                                   hunk.modified_start,
-                                   hunk.modified_length,
-                                   hunk.original_start,
-                                   hunk.original_length);
+      apr_size_t numbytes;
+      const char *match;
+      char c;
+
+      svn_pool_clear(iterpool);
+
+      /* Since we're reading one character at a time, let's at least
+         optimize for the 90% case.  90% of the time, we can avoid the
+         stringbuf ever having to realloc() itself if we start it out at
+         80 chars.  */
+      str = svn_stringbuf_create_ensure(80, iterpool);
+
+      if (detect_eol)
+        {
+          SVN_ERR(scan_eol(baton, read_fn, mark_fn, seek_fn,
+                           &eol_str, iterpool));
+          if (eol)
+            *eol = eol_str;
+          if (eol_str == NULL)
+            {
+              /* No newline until EOF, EOL_STR can be anything. */
+              eol_str = APR_EOL_STR;
+            }
+        }
+      else
+        eol_str = *eol;
+
+      /* Read into STR up to and including the next EOL sequence. */
+      match = eol_str;
+      numbytes = 1;
+      while (*match)
+        {
+          SVN_ERR(read_fn(baton, &c, &numbytes));
+          if (numbytes != 1)
+            {
+              /* a 'short' read means the stream has run out. */
+              *eof = TRUE;
+              /* We know we don't have a whole EOL sequence, but ensure we
+               * don't chop off any partial EOL sequence that we may have. */
+              match = eol_str;
+              /* Process this short (or empty) line just like any other
+               * except with *EOF set. */
+              break;
+            }
+
+          if (c == *match)
+            match++;
+          else
+            match = eol_str;
+
+          svn_stringbuf_appendbytes(str, &c, 1);
+        }
+
+      svn_stringbuf_chop(str, match - eol_str);
+      filtered = (str->data[0] == b->verboten || str->data[0] == '\\');
     }
-  else if (parse_hunk_header(line, &hunk, "##", FALSE, scratch_pool))
+  while (filtered && ! *eof);
+  /* Not destroying the iterpool just yet since we still need STR
+   * which is allocated in it. */
+
+  if (filtered)
     {
-      *buf = svn_stringbuf_createf(result_pool,
-                                   "## -%lu,%lu +%lu,%lu ##",
-                                   hunk.modified_start,
-                                   hunk.modified_length,
-                                   hunk.original_start,
-                                   hunk.original_length);
+      /* EOF, return an empty string. */
+      *stringbuf = svn_stringbuf_create_ensure(0, result_pool);
     }
-  else if (line[0] == '+')
+  else if (str->data[0] == '+' || str->data[0] == '-' || str->data[0] == ' ')
     {
-      *buf = svn_stringbuf_create(line, result_pool);
-      (*buf)->data[0] = '-';
+      /* Shave off leading unidiff symbols. */
+      *stringbuf = svn_stringbuf_create(str->data + 1, result_pool);
     }
-  else if (line[0] == '-')
+  else
     {
-      *buf = svn_stringbuf_create(line, result_pool);
-      (*buf)->data[0] = '+';
+      /* Return the line as-is. */
+      *stringbuf = svn_stringbuf_dup(str, result_pool);
     }
+
+  /* Done. RIP iterpool. */
+  svn_pool_destroy(iterpool);
+
+  return SVN_NO_ERROR;
+}
+
+/* Return a stream for reading hunk text from WRAPPED_STREAM.
+ * VERBOTEN is the leading character of lines which should be
+ * filtered by this stream's readline method ('+' or '-').
+ * Allocate the new stream in RESULT_POOL. */
+static svn_stream_t *
+stream_hunk_text(svn_stream_t *wrapped_stream,
+                 char verboten,
+                 apr_pool_t *result_pool)
+{
+  svn_stream_t *stream;
+  struct hunk_text_stream_baton *baton;
+
+  baton = apr_palloc(result_pool, sizeof(*baton));
+  baton->wrapped_stream = wrapped_stream;
+  baton->verboten = verboten;
+
+  stream = svn_stream_create(baton, result_pool);
+
+  svn_stream_set_read(stream, read_handler_hunk_text);
+  svn_stream_set_write(stream, write_handler_hunk_text);
+  svn_stream_set_close(stream, close_handler_hunk_text);
+  svn_stream_set_reset(stream, reset_handler_hunk_text);
+  svn_stream_set_mark(stream, mark_handler_hunk_text);
+  svn_stream_set_seek(stream, seek_handler_hunk_text);
+  svn_stream_set_readline(stream, readline_handler_hunk_text);
+
+  return stream;
+}
+
+/* Return a stream to read the original text of a hunk from
+ * WRAPPED_STREAM, and allocated in RESULT_POOL.*/
+static svn_stream_t *
+stream_hunk_text_original(svn_stream_t *wrapped_stream,
+                          apr_pool_t *result_pool)
+{
+  return stream_hunk_text(wrapped_stream, '+', result_pool);
+}
+
+/* Return a stream to read the modified text of a hunk from
+ * WRAPPED_STREAM, and allocated in RESULT_POOL.*/
+static svn_stream_t *
+stream_hunk_text_modified(svn_stream_t *wrapped_stream,
+                          apr_pool_t *result_pool)
+{
+  return stream_hunk_text(wrapped_stream, '-', result_pool);
+}
+
+
+/* Baton for a stream that reads unidiff text reversed. */
+struct reverse_diff_text_stream_baton {
+  svn_stream_t *wrapped_stream;
+}; 
+
+/* An implementation of svn_read_fn_t. */
+static svn_error_t *
+read_handler_reverse_diff_text(void *baton, char *buffer, apr_size_t *len)
+{
+  struct reverse_diff_text_stream_baton *b = baton;
+  return svn_stream_read(b->wrapped_stream, buffer, len);
+}
+
+/* An implementation of svn_write_fn_t. */
+static svn_error_t *
+write_handler_reverse_diff_text(void *baton, const char *buffer,
+                                apr_size_t *len)
+{
+  struct reverse_diff_text_stream_baton *b = baton;
+  return svn_stream_write(b->wrapped_stream, buffer, len);
+}
+
+/* An implementation of svn_close_fn_t. */
+static svn_error_t *
+close_handler_reverse_diff_text(void *baton)
+{
+  struct reverse_diff_text_stream_baton *b = baton;
+  return svn_stream_close(b->wrapped_stream);
+}
+
+/* An implementation of svn_io_reset_fn_t. */
+static svn_error_t *
+reset_handler_reverse_diff_text(void *baton)
+{
+  struct reverse_diff_text_stream_baton *b = baton;
+  return svn_stream_reset(b->wrapped_stream);
+}
+
+/* An implementation of svn_io_mark_fn_t. */
+static svn_error_t *
+mark_handler_reverse_diff_text(void *baton, svn_stream_mark_t **mark,
+                               apr_pool_t *pool)
+{
+  struct reverse_diff_text_stream_baton *b = baton;
+  return svn_stream_mark(b->wrapped_stream, mark, pool);
+}
+
+/* An implementation of svn_io_seek_fn_t. */
+static svn_error_t *
+seek_handler_reverse_diff_text(void *baton, svn_stream_mark_t *mark)
+{
+  struct hunk_text_stream_baton *b = baton;
+  return svn_stream_seek(b->wrapped_stream, mark);
+}
+
+/* An implementation of svn_io_readline_fn_t. */
+static svn_error_t *
+readline_handler_reverse_diff_text(void *baton,
+                                   svn_stringbuf_t **stringbuf,
+                                   const char **eol,
+                                   svn_boolean_t *eof,
+                                   svn_boolean_t detect_eol,
+                                   svn_read_fn_t read_fn,
+                                   svn_io_mark_fn_t mark_fn,
+                                   svn_io_seek_fn_t seek_fn,
+                                   apr_pool_t *result_pool,
+                                   apr_pool_t *scratch_pool)
+{
+  svn_hunk_t hunk;
+  svn_stringbuf_t *line;
+  struct reverse_diff_text_stream_baton *b = baton;
+
+  /* Read the line and perform necessary transformations to
+   * produce a reversed diff. */
+  if (detect_eol)
+    SVN_ERR(svn_stream_readline_detect_eol(b->wrapped_stream,
+                                           &line, eol, eof,
+                                           result_pool, scratch_pool));
   else
-    *buf = svn_stringbuf_create(line, result_pool);
+    SVN_ERR(svn_stream_readline(b->wrapped_stream, &line, *eol, eof,
+                                result_pool));
 
+  if (parse_hunk_header(line->data, &hunk, "@@", FALSE, scratch_pool))
+    {
+      /* Line is a hunk header, reverse it. */
+      *stringbuf = svn_stringbuf_createf(result_pool,
+                                         "@@ -%lu,%lu +%lu,%lu @@",
+                                         hunk.modified_start,
+                                         hunk.modified_length,
+                                         hunk.original_start,
+                                         hunk.original_length);
+    }
+  else if (parse_hunk_header(line->data, &hunk, "##", FALSE, scratch_pool))
+    {
+      /* Line is a hunk header, reverse it. */
+      *stringbuf = svn_stringbuf_createf(result_pool,
+                                         "## -%lu,%lu +%lu,%lu ##",
+                                         hunk.modified_start,
+                                         hunk.modified_length,
+                                         hunk.original_start,
+                                         hunk.original_length);
+    }
+  else
+    {
+      if (line->data[0] == '+')
+        line->data[0] = '-';
+      else if (line->data[0] == '-')
+        line->data[0] = '+';
+
+      *stringbuf = line;
+    }
+
   return SVN_NO_ERROR;
 }
 
+/* Return a stream for reading diff text from WRAPPED_STREAM.
+ * The unidiff will appear reversed when read via the stream's readline method.
+ * Allocate the new stream in RESULT_POOL. */
+static svn_stream_t *
+stream_reverse_diff_text(svn_stream_t *wrapped_stream,
+                         apr_pool_t *result_pool)
+{
+  svn_stream_t *stream;
+  struct reverse_diff_text_stream_baton *baton;
+
+  baton = apr_palloc(result_pool, sizeof(*baton));
+  baton->wrapped_stream = wrapped_stream;
+
+  stream = svn_stream_create(baton, result_pool);
+  svn_stream_set_read(stream, read_handler_reverse_diff_text);
+  svn_stream_set_write(stream, write_handler_reverse_diff_text);
+  svn_stream_set_close(stream, close_handler_reverse_diff_text);
+  svn_stream_set_reset(stream, reset_handler_reverse_diff_text);
+  svn_stream_set_mark(stream, mark_handler_reverse_diff_text);
+  svn_stream_set_seek(stream, seek_handler_reverse_diff_text);
+  svn_stream_set_readline(stream, readline_handler_reverse_diff_text);
+
+  return stream;
+}
+
 /* Parse PROP_NAME from HEADER as the part after the INDICATOR line. */
 static svn_error_t *
 parse_prop_name(const char **prop_name, const char *header, 
@@ -349,7 +667,7 @@ parse_next_hunk(svn_hunk_t **hunk,
       /* Remember the current line's offset, and read the line. */
       last_line = pos;
       SVN_ERR(svn_stream_readline_detect_eol(stream, &line, NULL, &eof,
-                                             iterpool));
+                                             iterpool, iterpool));
 
       if (! eof)
         {
@@ -502,38 +820,41 @@ parse_next_hunk(svn_hunk_t **hunk,
       apr_file_t *f;
       apr_int32_t flags = APR_READ | APR_BUFFERED;
 
+      /* For each of the streams created below, we re-open the patch file.
+       * Each stream needs its own file descriptor in order to have
+       * independent seek behaviour. */
+
       /* Create a stream which returns the hunk text itself. */
       SVN_ERR(svn_io_file_open(&f, patch->path, flags, APR_OS_DEFAULT,
                                result_pool));
       diff_text = svn_stream_from_aprfile_range_readonly(f, FALSE,
                                                          start, end,
                                                          result_pool);
+      if (reverse)
+        diff_text = stream_reverse_diff_text(diff_text, result_pool);
 
       /* Create a stream which returns the original hunk text. */
       SVN_ERR(svn_io_file_open(&f, patch->path, flags, APR_OS_DEFAULT,
                                result_pool));
-      original_text = svn_stream_from_aprfile_range_readonly(f, FALSE,
-                                                             start, end,
-                                                             result_pool);
-      svn_stream_set_line_filter_callback(original_text, original_line_filter);
-      svn_stream_set_line_transformer_callback(original_text,
-                                               
remove_leading_char_transformer);
+      original_text = stream_hunk_text_original(
+                        svn_stream_from_aprfile_range_readonly(f, FALSE,
+                                                               start, end,
+                                                               result_pool),
+                        result_pool);
 
       /* Create a stream which returns the modified hunk text. */
       SVN_ERR(svn_io_file_open(&f, patch->path, flags, APR_OS_DEFAULT,
                                result_pool));
-      modified_text = svn_stream_from_aprfile_range_readonly(f, FALSE,
-                                                             start, end,
-                                                             result_pool);
-      svn_stream_set_line_filter_callback(modified_text, modified_line_filter);
-      svn_stream_set_line_transformer_callback(modified_text,
-                                               
remove_leading_char_transformer);
+      modified_text = stream_hunk_text_modified(
+                        svn_stream_from_aprfile_range_readonly(f, FALSE,
+                                                               start, end,
+                                                               result_pool),
+                        result_pool);
+
       /* Set the hunk's texts. */
       (*hunk)->diff_text = diff_text;
       if (reverse)
         {
-          svn_stream_set_line_transformer_callback(diff_text,
-                                                   reverse_diff_transformer);
           (*hunk)->original_text = modified_text;
           (*hunk)->modified_text = original_text;
         }
@@ -918,7 +1239,7 @@ svn_diff_parse_next_patch(svn_patch_t **patch,
       /* Remember the current line's offset, and read the line. */
       last_line = pos;
       SVN_ERR(svn_stream_readline_detect_eol(stream, &line, NULL, &eof,
-                                             iterpool));
+                                             iterpool, iterpool));
 
       if (! eof)
         {
Index: subversion/libsvn_subr/stream.c
===================================================================
--- subversion/libsvn_subr/stream.c     (revision 961349)
+++ subversion/libsvn_subr/stream.c     (working copy)
@@ -53,8 +53,7 @@ struct svn_stream_t {
   svn_io_reset_fn_t reset_fn;
   svn_io_mark_fn_t mark_fn;
   svn_io_seek_fn_t seek_fn;
-  svn_io_line_filter_cb_t line_filter_cb;
-  svn_io_line_transformer_cb_t line_transformer_cb;
+  svn_io_readline_fn_t readline_fn;
 };
 
 
@@ -73,8 +72,7 @@ svn_stream_create(void *baton, apr_pool_t *pool)
   stream->reset_fn = NULL;
   stream->mark_fn = NULL;
   stream->seek_fn = NULL;
-  stream->line_filter_cb = NULL;
-  stream->line_transformer_cb = NULL;
+  stream->readline_fn = NULL;
   return stream;
 }
 
@@ -124,20 +122,11 @@ svn_stream_set_seek(svn_stream_t *stream, svn_io_s
 }
 
 void
-svn_stream_set_line_filter_callback(svn_stream_t *stream,
-                                    svn_io_line_filter_cb_t line_filter_cb)
+svn_stream_set_readline(svn_stream_t *stream, svn_io_readline_fn_t readline_fn)
 {
-  stream->line_filter_cb = line_filter_cb;
+  stream->readline_fn = readline_fn;
 }
 
-void
-svn_stream_set_line_transformer_callback(
-  svn_stream_t *stream,
-  svn_io_line_transformer_cb_t line_transformer_cb)
-{
-  stream->line_transformer_cb = line_transformer_cb;
-}
-
 svn_error_t *
 svn_stream_read(svn_stream_t *stream, char *buffer, apr_size_t *len)
 {
@@ -233,55 +222,23 @@ svn_stream_printf_from_utf8(svn_stream_t *stream,
   return svn_stream_write(stream, translated, &len);
 }
 
-/* If a line filter callback was set on STREAM, invoke it on LINE,
- * and indicate in *FILTERED whether the line should be filtered.
- * If no line filter callback was set on STREAM, just set *FILTERED to FALSE.
- */
-static svn_error_t *
-line_filter(svn_stream_t *stream, svn_boolean_t *filtered, const char *line,
-            apr_pool_t *pool)
-{
-  apr_pool_t *scratch_pool;
-
-  if (! stream->line_filter_cb)
-    {
-      *filtered = FALSE;
-      return SVN_NO_ERROR;
-    }
-
-  scratch_pool = svn_pool_create(pool);
-  SVN_ERR(stream->line_filter_cb(filtered, line, stream->baton, scratch_pool));
-  svn_pool_destroy(scratch_pool);
-  return SVN_NO_ERROR;
-}
-
-/* Run the line transformer callback of STREAM with LINE as input,
- * and expect the transformation result to be returned in BUF,
- * allocated in POOL. */
-static svn_error_t *
-line_transformer(svn_stream_t *stream, svn_stringbuf_t **buf,
-                 const char *line, apr_pool_t *pool, apr_pool_t *scratch_pool)
-{
-  *buf = NULL;  /* so we can assert that the callback has set it non-null */
-  SVN_ERR(stream->line_transformer_cb(buf, line, stream->baton,
-                                      pool, scratch_pool));
-
-  /* Die if the line transformer didn't provide any output. */
-  SVN_ERR_ASSERT(*buf);
-
-  return SVN_NO_ERROR;
-}
-
-/* Scan STREAM for an end-of-line indicatior, and return it in *EOL.
+/* Invoke the READ_FN function of a stream to scan  for an end-of-line
+ * indicatior, and return it in *EOL.
+ * Use MARK_FN and SEEK_FN to seek in the stream.
  * Set *EOL to NULL if the stream runs out before an end-of-line indicator
  * is found. */
 static svn_error_t *
-scan_eol(const char **eol, svn_stream_t *stream, apr_pool_t *pool)
+scan_eol(void *baton,
+         svn_read_fn_t read_fn,
+         svn_io_mark_fn_t mark_fn,
+         svn_io_seek_fn_t seek_fn,
+         const char **eol,
+         apr_pool_t *pool)
 {
   const char *eol_str;
   svn_stream_mark_t *mark;
 
-  SVN_ERR(svn_stream_mark(stream, &mark, pool));
+  SVN_ERR(mark_fn(baton, &mark, pool));
 
   eol_str = NULL;
   while (! eol_str)
@@ -290,113 +247,82 @@ static svn_error_t *
       apr_size_t len;
 
       len = sizeof(buf);
-      SVN_ERR(svn_stream_read(stream, buf, &len));
+      SVN_ERR(read_fn(baton, buf, &len));
       if (len == 0)
           break; /* EOF */
       eol_str = svn_eol__detect_eol(buf, buf + len);
     }
 
-  SVN_ERR(svn_stream_seek(stream, mark));
+  SVN_ERR(seek_fn(baton, mark));
 
   *eol = eol_str;
 
   return SVN_NO_ERROR;
 }
 
-/* Guts of svn_stream_readline() and svn_stream_readline_detect_eol().
- * Returns the line read from STREAM in *STRINGBUF, and indicates
- * end-of-file in *EOF.  If DETECT_EOL is TRUE, the end-of-line indicator
- * is detected automatically and returned in *EOL.
- * If DETECT_EOL is FALSE, *EOL must point to the desired end-of-line
- * indicator.  STRINGBUF is allocated in POOL. */
+/* An implementation of svn_io_readline_fn_t */
 static svn_error_t *
-stream_readline(svn_stringbuf_t **stringbuf,
-                svn_boolean_t *eof,
+stream_readline(void *baton,
+                svn_stringbuf_t **stringbuf,
                 const char **eol,
-                svn_stream_t *stream,
+                svn_boolean_t *eof,
                 svn_boolean_t detect_eol,
-                apr_pool_t *pool)
+                svn_read_fn_t read_fn,
+                svn_io_mark_fn_t mark_fn,
+                svn_io_seek_fn_t seek_fn,
+                apr_pool_t *result_pool,
+                apr_pool_t *scratch_pool)
 {
-  svn_stringbuf_t *str;
-  apr_pool_t *iterpool;
-  svn_boolean_t filtered;
+  apr_size_t numbytes;
+  const char *match;
+  char c;
   const char *eol_str;
+  /* Since we're reading one character at a time, let's at least
+     optimize for the 90% case.  90% of the time, we can avoid the
+     stringbuf ever having to realloc() itself if we start it out at
+     80 chars.  */
+  svn_stringbuf_t *str = svn_stringbuf_create_ensure(80, scratch_pool);
 
-  *eof = FALSE;
-
-  iterpool = svn_pool_create(pool);
-  do
+  if (detect_eol)
     {
-      apr_size_t numbytes;
-      const char *match;
-      char c;
-
-      svn_pool_clear(iterpool);
-
-      /* Since we're reading one character at a time, let's at least
-         optimize for the 90% case.  90% of the time, we can avoid the
-         stringbuf ever having to realloc() itself if we start it out at
-         80 chars.  */
-      str = svn_stringbuf_create_ensure(80, iterpool);
-
-      if (detect_eol)
+      SVN_ERR(scan_eol(baton, read_fn, mark_fn, seek_fn,
+                       &eol_str, scratch_pool));
+      if (eol)
+        *eol = eol_str;
+      if (eol_str == NULL)
         {
-          SVN_ERR(scan_eol(&eol_str, stream, iterpool));
-          if (eol)
-            *eol = eol_str;
-          if (! eol_str)
-            {
-              /* No newline until EOF, EOL_STR can be anything. */
-              eol_str = APR_EOL_STR;
-            }
+          /* No newline until EOF, EOL_STR can be anything. */
+          eol_str = APR_EOL_STR;
         }
-      else
-        eol_str = *eol;
+    }
+  else
+    eol_str = *eol;
 
-      /* Read into STR up to and including the next EOL sequence. */
-      match = eol_str;
+  /* Read into STR up to and including the next EOL sequence. */
+  match = eol_str;
+  while (*match)
+    {
       numbytes = 1;
-      while (*match)
+      SVN_ERR(read_fn(baton, &c, &numbytes));
+      if (numbytes != 1)
         {
-          SVN_ERR(svn_stream_read(stream, &c, &numbytes));
-          if (numbytes != 1)
-            {
-              /* a 'short' read means the stream has run out. */
-              *eof = TRUE;
-              /* We know we don't have a whole EOL sequence, but ensure we
-               * don't chop off any partial EOL sequence that we may have. */
-              match = eol_str;
-              /* Process this short (or empty) line just like any other
-               * except with *EOF set. */
-              break;
-            }
-
-          if (c == *match)
-            match++;
-          else
-            match = eol_str;
-
-          svn_stringbuf_appendbytes(str, &c, 1);
+          /* a 'short' read means the stream has run out. */
+          *eof = TRUE;
+          *stringbuf = svn_stringbuf_dup(str, result_pool);
+          return SVN_NO_ERROR;
         }
 
-      svn_stringbuf_chop(str, match - eol_str);
+      if (c == *match)
+        match++;
+      else
+        match = eol_str;
 
-      SVN_ERR(line_filter(stream, &filtered, str->data, iterpool));
+      svn_stringbuf_appendbytes(str, &c, 1);
     }
-  while (filtered && ! *eof);
-  /* Not destroying the iterpool just yet since we still need STR
-   * which is allocated in it. */
 
-  if (filtered)
-    *stringbuf = svn_stringbuf_create_ensure(0, pool);
-  else if (stream->line_transformer_cb)
-    SVN_ERR(line_transformer(stream, stringbuf, str->data, pool, iterpool));
-  else
-    *stringbuf = svn_stringbuf_dup(str, pool);
-
-  /* Done. RIP iterpool. */
-  svn_pool_destroy(iterpool);
-
+  *eof = FALSE;
+  svn_stringbuf_chop(str, match - eol_str);
+  *stringbuf = svn_stringbuf_dup(str, result_pool);
   return SVN_NO_ERROR;
 }
 
@@ -407,8 +333,23 @@ svn_stream_readline(svn_stream_t *stream,
                     svn_boolean_t *eof,
                     apr_pool_t *pool)
 {
-  return svn_error_return(stream_readline(stringbuf, eof, &eol, stream,
-                                          FALSE, pool));
+  svn_io_readline_fn_t readline_fn;
+
+  /* Provide a default readline implementation if the stream
+   * hasn't overridden it. This is needed for backwards compat
+   * to 1.6.x and earlier. */
+  if (stream->readline_fn)
+    readline_fn = stream->readline_fn;
+  else
+    readline_fn = stream_readline;
+
+  return svn_error_return(readline_fn(stream->baton,
+                                      stringbuf, &eol, eof,
+                                      FALSE,
+                                      stream->read_fn,
+                                      stream->mark_fn,
+                                      stream->seek_fn,
+                                      pool, pool));
 }
 
 svn_error_t *
@@ -416,13 +357,36 @@ svn_stream_readline_detect_eol(svn_stream_t *strea
                                svn_stringbuf_t **stringbuf,
                                const char **eol,
                                svn_boolean_t *eof,
-                               apr_pool_t *pool)
+                               apr_pool_t *result_pool,
+                               apr_pool_t *scratch_pool)
 {
-  return svn_error_return(stream_readline(stringbuf, eof, eol, stream,
-                                          TRUE, pool));
-}
+  svn_io_readline_fn_t readline_fn;
 
+  /* Provide a default readline implementation if the stream
+   * hasn't overridden it. This is not needed for backwards compat
+   * to 1.6.x and earlier (this function is new in 1.7), but it
+   * is nice anyway because it saves us from adding dummy readline
+   * methods to custom streams sprinkled throughout the code. */
+  if (stream->readline_fn)
+    readline_fn = stream->readline_fn;
+  else
+    readline_fn = stream_readline;
 
+  /* EOL-detection requires mark/seek support. */
+  if (stream->mark_fn == NULL)
+    return svn_error_create(SVN_ERR_STREAM_SEEK_NOT_SUPPORTED, NULL, NULL);
+  if (stream->seek_fn == NULL)
+    return svn_error_create(SVN_ERR_STREAM_SEEK_NOT_SUPPORTED, NULL, NULL);
+
+  return svn_error_return(readline_fn(stream->baton,
+                                      stringbuf, eol, eof,
+                                      TRUE,
+                                      stream->read_fn,
+                                      stream->mark_fn,
+                                      stream->seek_fn,
+                                      result_pool, scratch_pool));
+}
+
 svn_error_t *svn_stream_copy3(svn_stream_t *from, svn_stream_t *to,
                               svn_cancel_func_t cancel_func,
                               void *cancel_baton,
@@ -536,7 +500,27 @@ seek_handler_empty(void *baton, svn_stream_mark_t
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+readline_handler_empty(void *baton,
+                       svn_read_fn_t read_fn,
+                       svn_io_mark_fn_t mark_fn,
+                       svn_io_seek_fn_t seek_fn,
+                       svn_stringbuf_t **stringbuf,
+                       const char **eol,
+                       svn_boolean_t *eof,
+                       apr_pool_t *result_pool,
+                       apr_pool_t *scratch_pool)
+{
+  *stringbuf = svn_stringbuf_create_ensure(0, result_pool);
 
+  if (eol && *eol == NULL)
+    *eol = APR_EOL_STR;
+
+  *eof = TRUE;
+
+  return SVN_NO_ERROR;
+}
+
 svn_stream_t *
 svn_stream_empty(apr_pool_t *pool)
 {
@@ -548,6 +532,8 @@ svn_stream_empty(apr_pool_t *pool)
   svn_stream_set_reset(stream, reset_handler_empty);
   svn_stream_set_mark(stream, mark_handler_empty);
   svn_stream_set_seek(stream, seek_handler_empty);
+  svn_stream_set_readline(stream, readline_handler_empty);
+
   return stream;
 }
 
@@ -652,6 +638,7 @@ svn_stream_disown(svn_stream_t *stream, apr_pool_t
   svn_stream_set_reset(s, reset_handler_disown);
   svn_stream_set_mark(s, mark_handler_disown);
   svn_stream_set_seek(s, seek_handler_disown);
+  svn_stream_set_readline(s, stream_readline);
 
   return s;
 }
@@ -820,6 +807,7 @@ svn_stream_from_aprfile2(apr_file_t *file,
   svn_stream_set_reset(stream, reset_handler_apr);
   svn_stream_set_mark(stream, mark_handler_apr);
   svn_stream_set_seek(stream, seek_handler_apr);
+  svn_stream_set_readline(stream, stream_readline);
 
   if (! disown)
     svn_stream_set_close(stream, close_handler_apr);
@@ -898,6 +886,7 @@ svn_stream_from_aprfile_range_readonly(apr_file_t
   svn_stream_set_reset(stream, reset_handler_apr);
   svn_stream_set_mark(stream, mark_handler_apr);
   svn_stream_set_seek(stream, seek_handler_apr);
+  svn_stream_set_readline(stream, stream_readline);
 
   if (! disown)
     svn_stream_set_close(stream, close_handler_apr);
@@ -1187,6 +1176,7 @@ svn_stream_compressed(svn_stream_t *stream, apr_po
   svn_stream_set_read(zstream, read_handler_gz);
   svn_stream_set_write(zstream, write_handler_gz);
   svn_stream_set_close(zstream, close_handler_gz);
+  svn_stream_set_readline(zstream, stream_readline);
 
   return zstream;
 }
@@ -1302,6 +1292,8 @@ svn_stream_checksummed2(svn_stream_t *stream,
   svn_stream_set_read(s, read_handler_checksum);
   svn_stream_set_write(s, write_handler_checksum);
   svn_stream_set_close(s, close_handler_checksum);
+  svn_stream_set_readline(s, stream_readline);
+
   return s;
 }
 
@@ -1384,6 +1376,8 @@ svn_stream_checksummed(svn_stream_t *stream,
   svn_stream_set_read(s, read_handler_md5);
   svn_stream_set_write(s, write_handler_md5);
   svn_stream_set_close(s, close_handler_md5);
+  svn_stream_set_readline(s, stream_readline);
+
   return s;
 }
 
@@ -1475,6 +1469,8 @@ svn_stream_from_stringbuf(svn_stringbuf_t *str,
   svn_stream_set_reset(stream, reset_handler_stringbuf);
   svn_stream_set_mark(stream, mark_handler_stringbuf);
   svn_stream_set_seek(stream, seek_handler_stringbuf);
+  svn_stream_set_readline(stream, stream_readline);
+
   return stream;
 }
 
@@ -1511,6 +1507,8 @@ svn_stream_from_string(const svn_string_t *str,
   baton->amt_read = 0;
   stream = svn_stream_create(baton, pool);
   svn_stream_set_read(stream, read_handler_string);
+  svn_stream_set_readline(stream, stream_readline);
+
   return stream;
 }
 
Index: subversion/libsvn_client/patch.c
===================================================================
--- subversion/libsvn_client/patch.c    (revision 961349)
+++ subversion/libsvn_client/patch.c    (working copy)
@@ -532,7 +532,7 @@ read_line(patch_target_t *target,
 
   SVN_ERR(svn_stream_readline_detect_eol(target->stream, &line_raw,
                                          &eol_str, &target->eof,
-                                         scratch_pool));
+                                         scratch_pool, scratch_pool));
   if (target->eol_style == svn_subst_eol_style_none)
     target->eol_str = eol_str;
 
@@ -632,7 +632,8 @@ match_hunk(svn_boolean_t *matched, patch_target_t
 
       SVN_ERR(svn_stream_readline_detect_eol(hunk->original_text,
                                              &hunk_line, NULL,
-                                             &hunk_eof, iterpool));
+                                             &hunk_eof,
+                                             iterpool, iterpool));
       /* Contract keywords, if any, before matching. */
       SVN_ERR(svn_subst_translate_cstring2(hunk_line->data,
                                            &hunk_line_translated,
@@ -676,7 +677,8 @@ match_hunk(svn_boolean_t *matched, patch_target_t
       /* If the target has no newline at end-of-file, we get an EOF
        * indication for the target earlier than we do get it for the hunk. */
       SVN_ERR(svn_stream_readline_detect_eol(hunk->original_text, &hunk_line,
-                                             NULL, &hunk_eof, iterpool));
+                                             NULL, &hunk_eof,
+                                             iterpool, iterpool));
       if (hunk_line->len == 0 && hunk_eof)
         *matched = lines_matched;
       else
@@ -928,7 +930,8 @@ reject_hunk(patch_target_t *target, hunk_info_t *h
       svn_pool_clear(iterpool);
 
       SVN_ERR(svn_stream_readline_detect_eol(hi->hunk->diff_text, &hunk_line,
-                                             &eol_str, &eof, iterpool));
+                                             &eol_str, &eof, iterpool,
+                                             iterpool));
       if (! eof)
         {
           if (hunk_line->len >= 1)
@@ -994,7 +997,7 @@ apply_hunk(patch_target_t *target, hunk_info_t *hi
 
       SVN_ERR(svn_stream_readline_detect_eol(hi->hunk->modified_text,
                                              &hunk_line, &eol_str,
-                                             &eof, iterpool));
+                                             &eof, iterpool, iterpool));
       lines_read++;
       if (! eof && lines_read > hi->fuzz &&
           lines_read <= hi->hunk->modified_length - hi->fuzz)
Index: subversion/tests/libsvn_subr/stream-test.c
===================================================================
--- subversion/tests/libsvn_subr/stream-test.c  (revision 961349)
+++ subversion/tests/libsvn_subr/stream-test.c  (working copy)
@@ -307,143 +307,7 @@ test_stream_range(apr_pool_t *pool)
     return SVN_NO_ERROR;
 }
 
-/* An implementation of svn_io_line_filter_cb_t */
 static svn_error_t *
-line_filter(svn_boolean_t *filtered, const char *line, void *baton,
-            apr_pool_t *scratch_pool)
-{
-  *filtered = strchr(line, '!') != NULL;
-  return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-test_stream_line_filter(apr_pool_t *pool)
-{
-  static const char *lines[4] = {"Not filtered.", "Filtered!",
-                                 "Not filtered either.", "End of the lines!"};
-  svn_string_t *string;
-  svn_stream_t *stream;
-  svn_stringbuf_t *line;
-  svn_boolean_t eof;
-
-  string = svn_string_createf(pool, "%s\n%s\n%s\n%s", lines[0], lines[1],
-                              lines[2], lines[3]);
-  stream = svn_stream_from_string(string, pool);
-
-  svn_stream_set_line_filter_callback(stream, line_filter);
-
-  svn_stream_readline(stream, &line, "\n", &eof, pool);
-  SVN_TEST_STRING_ASSERT(line->data, lines[0]);
-  /* line[1] should be filtered */
-  svn_stream_readline(stream, &line, "\n", &eof, pool);
-  SVN_TEST_STRING_ASSERT(line->data, lines[2]);
-
-  /* The last line should also be filtered, and the resulting
-   * stringbuf should be empty. */
-  svn_stream_readline(stream, &line, "\n", &eof, pool);
-  SVN_TEST_ASSERT(eof && svn_stringbuf_isempty(line));
-
-  return SVN_NO_ERROR;
-}
-
-/* An implementation of svn_io_line_transformer_cb_t */
-static svn_error_t *
-line_transformer(svn_stringbuf_t **buf, const char *line, void *baton,
-                 apr_pool_t *result_pool, apr_pool_t *scratch_pool)
-{
-  int i, len = strlen(line);
-  char *temp = apr_palloc(scratch_pool, len + 1 );
-
-  for (i = 0; i < len; i++)
-    {
-      temp[i] = line[len - 1 - i];
-    }
-
-  temp[len] = '\0';
-
-  *buf = svn_stringbuf_create(temp, result_pool);
-
-  return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-test_stream_line_transformer(apr_pool_t *pool)
-{
-  static const char *lines[4] = {"gamma", "",
-                                 "iota", "!"};
-
-  static const char *inv_lines[4] = {"ammag", "",
-                                 "atoi", "!"};
-  svn_string_t *string;
-  svn_stream_t *stream;
-  svn_stringbuf_t *line;
-  svn_boolean_t eof;
-
-  string = svn_string_createf(pool, "%s\n%s\n%s\n%s", lines[0], lines[1],
-                              lines[2], lines[3]);
-
-  stream = svn_stream_from_string(string, pool);
-
-  svn_stream_set_line_transformer_callback(stream, line_transformer);
-
-  svn_stream_readline(stream, &line, "\n", &eof, pool);
-  SVN_TEST_STRING_ASSERT(line->data, inv_lines[0]);
-
-  svn_stream_readline(stream, &line, "\n", &eof, pool);
-  SVN_TEST_STRING_ASSERT(line->data, inv_lines[1]);
-
-  svn_stream_readline(stream, &line, "\n", &eof, pool);
-  SVN_TEST_STRING_ASSERT(line->data, inv_lines[2]);
-
-  svn_stream_readline(stream, &line, "\n", &eof, pool);
-  SVN_TEST_STRING_ASSERT(line->data, inv_lines[3]);
-
-  /* We should have reached eof and the stringbuf should be emtpy. */
-  svn_stream_readline(stream, &line, "\n", &eof, pool);
-  SVN_TEST_ASSERT(eof && svn_stringbuf_isempty(line));
-
-  return SVN_NO_ERROR;
-}
-
-static svn_error_t *
-test_stream_line_filter_and_transformer(apr_pool_t *pool)
-{
-  static const char *lines[4] = {"!gamma", "",
-                                 "iota", "!"};
-
-  static const char *inv_lines[4] = {"ammag", "",
-                                 "atoi", "!"};
-  svn_string_t *string;
-  svn_stream_t *stream;
-  svn_stringbuf_t *line;
-  svn_boolean_t eof;
-
-  string = svn_string_createf(pool, "%s\n%s\n%s\n%s", lines[0], lines[1],
-                              lines[2], lines[3]);
-
-  stream = svn_stream_from_string(string, pool);
-
-  svn_stream_set_line_filter_callback(stream, line_filter);
-
-  svn_stream_set_line_transformer_callback(stream, line_transformer);
-
-  /* Line one should be filtered. */
-  svn_stream_readline(stream, &line, "\n", &eof, pool);
-  SVN_TEST_STRING_ASSERT(line->data, inv_lines[1]);
-
-  svn_stream_readline(stream, &line, "\n", &eof, pool);
-  SVN_TEST_STRING_ASSERT(line->data, inv_lines[2]);
-
-  /* The last line should also be filtered, and the resulting
-   * stringbuf should be empty. */
-  svn_stream_readline(stream, &line, "\n", &eof, pool);
-  SVN_TEST_ASSERT(eof && svn_stringbuf_isempty(line));
-
-  return SVN_NO_ERROR;
-
-}
-
-static svn_error_t *
 test_stream_tee(apr_pool_t *pool)
 {
   svn_stringbuf_t *test_bytes = generate_test_bytes(100, pool);
@@ -647,12 +511,6 @@ struct svn_test_descriptor_t test_funcs[] =
                    "test compressed streams"),
     SVN_TEST_PASS2(test_stream_range,
                    "test streams reading from range of file"),
-    SVN_TEST_PASS2(test_stream_line_filter,
-                   "test stream line filtering"),
-    SVN_TEST_PASS2(test_stream_line_transformer,
-                   "test stream line transforming"),
-    SVN_TEST_PASS2(test_stream_line_filter_and_transformer,
-                   "test stream line filtering and transforming"),
     SVN_TEST_PASS2(test_stream_tee,
                    "test 'tee' streams"),
     SVN_TEST_PASS2(test_stream_seek_file,

Reply via email to