Index: subversion/libsvn_subr/stream.c
==================================================================--- subversion/libsvn_subr/stream.c	(revision 38964)
+++ subversion/libsvn_subr/stream.c	(arbetskopia)
@@ -51,6 +51,7 @@
   svn_close_fn_t close_fn;
   svn_io_reset_fn_t reset_fn;
   svn_io_line_filter_cb_t line_filter_cb;
+  svn_io_line_transformer_cb_t line_transformer_cb;
 };


@@ -69,6 +70,7 @@
   stream->reset_fn = NULL;
   stream->close_fn = NULL;
   stream->line_filter_cb = NULL;
+  stream->line_transformer_cb = NULL;
   return stream;
 }

@@ -112,6 +114,14 @@
   stream->line_filter_cb = line_filter_cb;
 }

+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)
 {
@@ -207,6 +217,19 @@
   return SVN_NO_ERROR;
 }

+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;
+
+  scratch_pool = svn_pool_create(pool);
+  SVN_ERR(stream->line_transformer_cb(buf, line, pool, scratch_pool));
+  svn_pool_destroy(scratch_pool);
+
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_stream_readline(svn_stream_t *stream,
                     svn_stringbuf_t **stringbuf,
@@ -244,8 +267,12 @@
               *eof = TRUE;

               SVN_ERR(line_filter(stream, &filtered, str->data, iterpool));
+
               if (filtered)
                 *stringbuf = svn_stringbuf_create_ensure(0, pool);
+              else if (stream->line_transformer_cb)
+                SVN_ERR(line_transformer(stream, stringbuf, str->data,
+                                         iterpool));
               else
                 *stringbuf = svn_stringbuf_dup(str, pool);

@@ -268,6 +295,9 @@
   while (filtered);
   *stringbuf = svn_stringbuf_dup(str, pool);

+  if (stream->line_transformer_cb)
+    SVN_ERR(line_transformer(stream, stringbuf, str->data, iterpool));
+
   svn_pool_destroy(iterpool);
   return SVN_NO_ERROR;
 }
Index: subversion/tests/libsvn_subr/stream-test.c
==================================================================--- subversion/tests/libsvn_subr/stream-test.c	(revision 38964)
+++ subversion/tests/libsvn_subr/stream-test.c	(arbetskopia)
@@ -345,6 +345,105 @@
   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,
+                 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_ERR_ASSERT(strcmp(line->data, inv_lines[0]) == 0);
+
+  svn_stream_readline(stream, &line, "\n", &eof, pool);
+  SVN_ERR_ASSERT(strcmp(line->data, inv_lines[1]) == 0);
+
+  svn_stream_readline(stream, &line, "\n", &eof, pool);
+  SVN_ERR_ASSERT(strcmp(line->data, inv_lines[2]) == 0);
+
+  svn_stream_readline(stream, &line, "\n", &eof, pool);
+  SVN_ERR_ASSERT(strcmp(line->data, inv_lines[3]) == 0);
+
+  /* We should have reached eof and the stringbuf should be emtpy. */
+  svn_stream_readline(stream, &line, "\n", &eof, pool);
+  SVN_ERR_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_ERR_ASSERT(strcmp(line->data, inv_lines[1]) == 0);
+
+  svn_stream_readline(stream, &line, "\n", &eof, pool);
+  SVN_ERR_ASSERT(strcmp(line->data, inv_lines[2]) == 0);
+
+  /* The last line should also be filtered, and the resulting
+   * stringbuf should be empty. */
+  svn_stream_readline(stream, &line, "\n", &eof, pool);
+  SVN_ERR_ASSERT(eof && svn_stringbuf_isempty(line));
+
+  return SVN_NO_ERROR;
+
+}
+
+
+
 
 /* The test table.  */

@@ -359,5 +458,9 @@
                    "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_NULL
   };
Index: subversion/include/svn_io.h
==================================================================--- subversion/include/svn_io.h	(revision 38964)
+++ subversion/include/svn_io.h	(arbetskopia)
@@ -704,6 +704,21 @@
 typedef svn_error_t *(*svn_io_line_filter_cb_t)(svn_boolean_t *filtered,
                                                 const char *line,
                                                 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().
+ *
+ * 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.
+ *
+ * @see svn_stream_t, svn_io_line_filter_cb_t and svn_stream_readline().
+ *
+ * @since New in 1.7. */
+typedef svn_error_t *(*svn_io_line_transformer_cb_t)(svn_stringbuf_t **buf,
+                                                     const char *line,
+                                                     apr_pool_t *result_pool,
+                                                     apr_pool_t *scratch_pool);

 /** Create a generic stream.  @see svn_stream_t. */
 svn_stream_t *
@@ -742,6 +757,14 @@
 svn_stream_set_line_filter_callback(svn_stream_t *stream,
                                     svn_io_line_filter_cb_t line_filter_cb);

+/** 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 *
 svn_stream_empty(apr_pool_t *pool);
@@ -1030,6 +1053,13 @@
  * 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,

