Index: subversion/libsvn_subr/stream.c
==================================================================--- subversion/libsvn_subr/stream.c	(revision 38953)
+++ 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,22 @@
   return SVN_NO_ERROR;
 }

+static svn_error_t *
+line_transformer(svn_stream_t *stream, svn_boolean_t *filtered,
+            const char **new_line, const char *line,
+            apr_pool_t *pool)
+{
+  if (! stream->line_transformer_cb)
+    {
+      *new_line = line;
+      *filtered = FALSE;
+      return SVN_NO_ERROR;
+    }
+
+  SVN_ERR(stream->line_transformer_cb(filtered, new_line, line, pool));
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_stream_readline(svn_stream_t *stream,
                     svn_stringbuf_t **stringbuf,
@@ -217,6 +243,7 @@
   svn_stringbuf_t *str;
   apr_pool_t *iterpool;
   svn_boolean_t filtered;
+  const char *temp;

   iterpool = svn_pool_create(pool);
   do
@@ -249,6 +276,11 @@
               else
                 *stringbuf = svn_stringbuf_dup(str, pool);

+              SVN_ERR(line_transformer(stream, &filtered, &temp, str->data,
+                                  iterpool));
+              if (filtered)
+                *stringbuf = svn_stringbuf_create(temp, pool);
+
               return SVN_NO_ERROR;
             }

@@ -266,8 +298,14 @@
       SVN_ERR(line_filter(stream, &filtered, str->data, iterpool));
     }
   while (filtered);
-  *stringbuf = svn_stringbuf_dup(str, pool);

+  SVN_ERR(line_transformer(stream, &filtered, &temp, str->data,
+                      iterpool));
+  if (filtered)
+    *stringbuf = svn_stringbuf_create(temp, pool);
+  else
+    *stringbuf = svn_stringbuf_dup(str, pool);
+
   svn_pool_destroy(iterpool);
   return SVN_NO_ERROR;
 }
Index: subversion/tests/libsvn_subr/stream-test.c
==================================================================--- subversion/tests/libsvn_subr/stream-test.c	(revision 38953)
+++ subversion/tests/libsvn_subr/stream-test.c	(arbetskopia)
@@ -345,6 +345,60 @@
   return SVN_NO_ERROR;
 }

+/* An implementation of svn_io_line_transformer_cb_t */
+static svn_error_t *
+line_transformer(svn_boolean_t *filtered, const char **new_line, const char *line,
+            apr_pool_t *pool)
+{
+  if (line[0] == '!')
+    {
+      *new_line = line + 1;
+      *filtered = TRUE;
+    }
+  else
+    {
+      *new_line = line;
+      *filtered = FALSE;
+    }
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+test_stream_line_transformer(apr_pool_t *pool)
+{
+  static const char *lines[4] = {"Not filtered", "!Filtered",
+                                 "", "!"};
+  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, lines[0]) == 0);
+
+  /* Line[1] should be filtered. */
+  svn_stream_readline(stream, &line, "\n", &eof, pool);
+  SVN_ERR_ASSERT(strcmp(line->data, lines[1] + 1) == 0);
+
+  svn_stream_readline(stream, &line, "\n", &eof, pool);
+  SVN_ERR_ASSERT(strcmp(line->data, lines[2]) == 0);
+
+  /* Line[3] should be filtered. */
+  svn_stream_readline(stream, &line, "\n", &eof, pool);
+  SVN_ERR_ASSERT(strcmp(line->data, lines[3] + 1) == 0);
+
+  return SVN_NO_ERROR;
+}
+
+
+
 
 /* The test table.  */

@@ -359,5 +413,7 @@
                    "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_NULL
   };
Index: subversion/include/svn_io.h
==================================================================--- subversion/include/svn_io.h	(revision 38953)
+++ subversion/include/svn_io.h	(arbetskopia)
@@ -705,6 +705,16 @@
                                                 const char *line,
                                                 apr_pool_t *scratch_pool);

+/** Character-filtering callback function for a generic
+ * stream.
+ * @see svn_stream_t and svn_stream_readline().
+ *
+ * @since New in 1.7. */
+typedef svn_error_t *(*svn_io_line_transformer_cb_t)(svn_boolean_t *filtered,
+                                                     const char **new_line,
+                                                     const char *line,
+                                                     apr_pool_t *pool);
+
 /** Create a generic stream.  @see svn_stream_t. */
 svn_stream_t *
 svn_stream_create(void *baton,
@@ -742,6 +752,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 +1048,10 @@
  * 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.
  */
 svn_error_t *
 svn_stream_readline(svn_stream_t *stream,

