diff options
Diffstat (limited to 'subversion/libsvn_delta/svndiff.c')
-rw-r--r-- | subversion/libsvn_delta/svndiff.c | 527 |
1 files changed, 350 insertions, 177 deletions
diff --git a/subversion/libsvn_delta/svndiff.c b/subversion/libsvn_delta/svndiff.c index 070c638a74d65..d95dde42b199a 100644 --- a/subversion/libsvn_delta/svndiff.c +++ b/subversion/libsvn_delta/svndiff.c @@ -36,6 +36,23 @@ #include "private/svn_string_private.h" #include "private/svn_dep_compat.h" +static const char SVNDIFF_V0[] = { 'S', 'V', 'N', 0 }; +static const char SVNDIFF_V1[] = { 'S', 'V', 'N', 1 }; +static const char SVNDIFF_V2[] = { 'S', 'V', 'N', 2 }; + +#define SVNDIFF_HEADER_SIZE (sizeof(SVNDIFF_V0)) + +static const char * +get_svndiff_header(int version) +{ + if (version == 2) + return SVNDIFF_V2; + else if (version == 1) + return SVNDIFF_V1; + else + return SVNDIFF_V0; +} + /* ----- Text delta to svndiff ----- */ /* We make one of these and get it passed back to us in calls to the @@ -46,7 +63,8 @@ struct encoder_baton { svn_boolean_t header_done; int version; int compression_level; - apr_pool_t *pool; + /* Pool for temporary allocations, will be cleared periodically. */ + apr_pool_t *scratch_pool; }; /* This is at least as big as the largest size for a single instruction. */ @@ -72,7 +90,7 @@ static svn_error_t * send_simple_insertion_window(svn_txdelta_window_t *window, struct encoder_baton *eb) { - unsigned char headers[4 + 5 * SVN__MAX_ENCODED_UINT_LEN + unsigned char headers[SVNDIFF_HEADER_SIZE + 5 * SVN__MAX_ENCODED_UINT_LEN + MAX_INSTRUCTION_LEN]; unsigned char ibuf[MAX_INSTRUCTION_LEN]; unsigned char *header_current; @@ -89,11 +107,8 @@ send_simple_insertion_window(svn_txdelta_window_t *window, if (!eb->header_done) { eb->header_done = TRUE; - headers[0] = 'S'; - headers[1] = 'V'; - headers[2] = 'N'; - headers[3] = (unsigned char)eb->version; - header_current = headers + 4; + memcpy(headers, get_svndiff_header(eb->version), SVNDIFF_HEADER_SIZE); + header_current = headers + SVNDIFF_HEADER_SIZE; } else { @@ -135,58 +150,28 @@ send_simple_insertion_window(svn_txdelta_window_t *window, return SVN_NO_ERROR; } +/* Encodes delta window WINDOW to svndiff-format. + The svndiff version is VERSION. COMPRESSION_LEVEL is the + compression level to use. + Returned values will be allocated in POOL or refer to *WINDOW + fields. */ static svn_error_t * -window_handler(svn_txdelta_window_t *window, void *baton) +encode_window(svn_stringbuf_t **instructions_p, + svn_stringbuf_t **header_p, + const svn_string_t **newdata_p, + svn_txdelta_window_t *window, + int version, + int compression_level, + apr_pool_t *pool) { - struct encoder_baton *eb = baton; - apr_pool_t *pool; svn_stringbuf_t *instructions; - svn_stringbuf_t *i1; svn_stringbuf_t *header; const svn_string_t *newdata; unsigned char ibuf[MAX_INSTRUCTION_LEN], *ip; const svn_txdelta_op_t *op; - apr_size_t len; - - /* use specialized code if there is no source */ - if (window && !window->src_ops && window->num_ops == 1 && !eb->version) - return svn_error_trace(send_simple_insertion_window(window, eb)); - - /* Make sure we write the header. */ - if (!eb->header_done) - { - char svnver[4] = {'S','V','N','\0'}; - len = 4; - svnver[3] = (char)eb->version; - SVN_ERR(svn_stream_write(eb->output, svnver, &len)); - eb->header_done = TRUE; - } - - if (window == NULL) - { - svn_stream_t *output = eb->output; - - /* We're done; clean up. - - We clean our pool first. Given that the output stream was passed - TO us, we'll assume it has a longer lifetime, and that it will not - be affected by our pool destruction. - - The contrary point of view (close the stream first): that could - tell our user that everything related to the output stream is done, - and a cleanup of the user pool should occur. However, that user - pool could include the subpool we created for our work (eb->pool), - which would then make our call to svn_pool_destroy() puke. - */ - svn_pool_destroy(eb->pool); - - return svn_stream_close(output); - } /* create the necessary data buffers */ - pool = svn_pool_create(eb->pool); instructions = svn_stringbuf_create_empty(pool); - i1 = svn_stringbuf_create_empty(pool); header = svn_stringbuf_create_empty(pool); /* Encode the instructions. */ @@ -213,21 +198,39 @@ window_handler(svn_txdelta_window_t *window, void *baton) append_encoded_int(header, window->sview_offset); append_encoded_int(header, window->sview_len); append_encoded_int(header, window->tview_len); - if (eb->version == 1) + if (version == 2) + { + svn_stringbuf_t *compressed_instructions; + compressed_instructions = svn_stringbuf_create_empty(pool); + SVN_ERR(svn__compress_lz4(instructions->data, instructions->len, + compressed_instructions)); + instructions = compressed_instructions; + } + else if (version == 1) { - SVN_ERR(svn__compress(instructions, i1, eb->compression_level)); - instructions = i1; + svn_stringbuf_t *compressed_instructions; + compressed_instructions = svn_stringbuf_create_empty(pool); + SVN_ERR(svn__compress_zlib(instructions->data, instructions->len, + compressed_instructions, compression_level)); + instructions = compressed_instructions; } append_encoded_int(header, instructions->len); - if (eb->version == 1) + + /* Encode the data. */ + if (version == 2) { svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool); - svn_stringbuf_t *original = svn_stringbuf_create_empty(pool); - original->data = (char *)window->new_data->data; /* won't be modified */ - original->len = window->new_data->len; - original->blocksize = window->new_data->len + 1; - SVN_ERR(svn__compress(original, compressed, eb->compression_level)); + SVN_ERR(svn__compress_lz4(window->new_data->data, window->new_data->len, + compressed)); + newdata = svn_stringbuf__morph_into_string(compressed); + } + else if (version == 1) + { + svn_stringbuf_t *compressed = svn_stringbuf_create_empty(pool); + + SVN_ERR(svn__compress_zlib(window->new_data->data, window->new_data->len, + compressed, compression_level)); newdata = svn_stringbuf__morph_into_string(compressed); } else @@ -235,6 +238,53 @@ window_handler(svn_txdelta_window_t *window, void *baton) append_encoded_int(header, newdata->len); + *instructions_p = instructions; + *header_p = header; + *newdata_p = newdata; + + return SVN_NO_ERROR; +} + +/* Note: When changing things here, check the related comment in + the svn_txdelta_to_svndiff_stream() function. */ +static svn_error_t * +window_handler(svn_txdelta_window_t *window, void *baton) +{ + struct encoder_baton *eb = baton; + apr_size_t len; + svn_stringbuf_t *instructions; + svn_stringbuf_t *header; + const svn_string_t *newdata; + + /* use specialized code if there is no source */ + if (window && !window->src_ops && window->num_ops == 1 && !eb->version) + return svn_error_trace(send_simple_insertion_window(window, eb)); + + /* Make sure we write the header. */ + if (!eb->header_done) + { + len = SVNDIFF_HEADER_SIZE; + SVN_ERR(svn_stream_write(eb->output, get_svndiff_header(eb->version), + &len)); + eb->header_done = TRUE; + } + + if (window == NULL) + { + /* We're done; clean up. */ + SVN_ERR(svn_stream_close(eb->output)); + + svn_pool_destroy(eb->scratch_pool); + + return SVN_NO_ERROR; + } + + svn_pool_clear(eb->scratch_pool); + + SVN_ERR(encode_window(&instructions, &header, &newdata, window, + eb->version, eb->compression_level, + eb->scratch_pool)); + /* Write out the window. */ len = header->len; SVN_ERR(svn_stream_write(eb->output, header->data, &len)); @@ -249,7 +299,6 @@ window_handler(svn_txdelta_window_t *window, void *baton) SVN_ERR(svn_stream_write(eb->output, newdata->data, &len)); } - svn_pool_destroy(pool); return SVN_NO_ERROR; } @@ -261,13 +310,12 @@ svn_txdelta_to_svndiff3(svn_txdelta_window_handler_t *handler, int compression_level, apr_pool_t *pool) { - apr_pool_t *subpool = svn_pool_create(pool); struct encoder_baton *eb; - eb = apr_palloc(subpool, sizeof(*eb)); + eb = apr_palloc(pool, sizeof(*eb)); eb->output = output; eb->header_done = FALSE; - eb->pool = subpool; + eb->scratch_pool = svn_pool_create(pool); eb->version = svndiff_version; eb->compression_level = compression_level; @@ -334,6 +382,17 @@ struct decode_baton /* svndiff version in use by delta. */ unsigned char version; + + /* Length of parsed delta window header. 0 if window is not parsed yet. */ + apr_size_t window_header_len; + + /* Five integer fields of parsed delta window header. Valid only if + WINDOW_HEADER_LEN > 0 */ + svn_filesize_t sview_offset; + apr_size_t sview_len; + apr_size_t tview_len; + apr_size_t inslen; + apr_size_t newlen; }; @@ -483,21 +542,6 @@ count_and_verify_instructions(int *ninst, return SVN_NO_ERROR; } -static svn_error_t * -zlib_decode(const unsigned char *in, apr_size_t inLen, svn_stringbuf_t *out, - apr_size_t limit) -{ - /* construct a fake string buffer as parameter to svn__decompress. - This is fine as that function never writes to it. */ - svn_stringbuf_t compressed; - compressed.pool = NULL; - compressed.data = (char *)in; - compressed.len = inLen; - compressed.blocksize = inLen + 1; - - return svn__decompress(&compressed, out, limit); -} - /* Given the five integer fields of a window header and a pointer to the remainder of the window contents, fill in a delta window structure *WINDOW. New allocations will be performed in POOL; @@ -513,7 +557,7 @@ decode_window(svn_txdelta_window_t *window, svn_filesize_t sview_offset, int ninst; apr_size_t npos; svn_txdelta_op_t *ops, *op; - svn_string_t *new_data = apr_palloc(pool, sizeof(*new_data)); + svn_string_t *new_data; window->sview_offset = sview_offset; window->sview_len = sview_len; @@ -521,33 +565,43 @@ decode_window(svn_txdelta_window_t *window, svn_filesize_t sview_offset, insend = data + inslen; - if (version == 1) + if (version == 2) { svn_stringbuf_t *instout = svn_stringbuf_create_empty(pool); svn_stringbuf_t *ndout = svn_stringbuf_create_empty(pool); - SVN_ERR(zlib_decode(insend, newlen, ndout, - SVN_DELTA_WINDOW_SIZE)); - SVN_ERR(zlib_decode(data, insend - data, instout, - MAX_INSTRUCTION_SECTION_LEN)); + SVN_ERR(svn__decompress_lz4(insend, newlen, ndout, + SVN_DELTA_WINDOW_SIZE)); + SVN_ERR(svn__decompress_lz4(data, insend - data, instout, + MAX_INSTRUCTION_SECTION_LEN)); newlen = ndout->len; data = (unsigned char *)instout->data; insend = (unsigned char *)instout->data + instout->len; - new_data->data = (const char *) ndout->data; - new_data->len = newlen; + new_data = svn_stringbuf__morph_into_string(ndout); + } + else if (version == 1) + { + svn_stringbuf_t *instout = svn_stringbuf_create_empty(pool); + svn_stringbuf_t *ndout = svn_stringbuf_create_empty(pool); + + SVN_ERR(svn__decompress_zlib(insend, newlen, ndout, + SVN_DELTA_WINDOW_SIZE)); + SVN_ERR(svn__decompress_zlib(data, insend - data, instout, + MAX_INSTRUCTION_SECTION_LEN)); + + newlen = ndout->len; + data = (unsigned char *)instout->data; + insend = (unsigned char *)instout->data + instout->len; + + new_data = svn_stringbuf__morph_into_string(ndout); } else { /* Copy the data because an svn_string_t must have the invariant data[len]=='\0'. */ - char *buf = apr_palloc(pool, newlen + 1); - - memcpy(buf, insend, newlen); - buf[newlen] = '\0'; - new_data->data = buf; - new_data->len = newlen; + new_data = svn_string_ncreate((const char*)insend, newlen, pool); } /* Count the instructions and make sure they are all valid. */ @@ -578,10 +632,6 @@ decode_window(svn_txdelta_window_t *window, svn_filesize_t sview_offset, return SVN_NO_ERROR; } -static const char SVNDIFF_V0[] = { 'S', 'V', 'N', 0 }; -static const char SVNDIFF_V1[] = { 'S', 'V', 'N', 1 }; -#define SVNDIFF_HEADER_SIZE (sizeof(SVNDIFF_V0)) - static svn_error_t * write_handler(void *baton, const char *buffer, @@ -589,8 +639,6 @@ write_handler(void *baton, { struct decode_baton *db = (struct decode_baton *) baton; const unsigned char *p, *end; - svn_filesize_t sview_offset; - apr_size_t sview_len, tview_len, inslen, newlen, remaining; apr_size_t buflen = *len; /* Chew up four bytes at the beginning for the header. */ @@ -603,6 +651,8 @@ write_handler(void *baton, db->version = 0; else if (memcmp(buffer, SVNDIFF_V1 + db->header_bytes, nheader) == 0) db->version = 1; + else if (memcmp(buffer, SVNDIFF_V2 + db->header_bytes, nheader) == 0) + db->version = 2; else return svn_error_create(SVN_ERR_SVNDIFF_INVALID_HEADER, NULL, _("Svndiff has invalid header")); @@ -628,92 +678,108 @@ write_handler(void *baton, while (1) { - apr_pool_t *newpool; svn_txdelta_window_t window; /* Read the header, if we have enough bytes for that. */ p = (const unsigned char *) db->buffer->data; end = (const unsigned char *) db->buffer->data + db->buffer->len; - p = decode_file_offset(&sview_offset, p, end); - if (p == NULL) - break; - - p = decode_size(&sview_len, p, end); - if (p == NULL) - break; - - p = decode_size(&tview_len, p, end); - if (p == NULL) - break; - - p = decode_size(&inslen, p, end); - if (p == NULL) - break; - - p = decode_size(&newlen, p, end); - if (p == NULL) - break; - - if (tview_len > SVN_DELTA_WINDOW_SIZE || - sview_len > SVN_DELTA_WINDOW_SIZE || - /* for svndiff1, newlen includes the original length */ - newlen > SVN_DELTA_WINDOW_SIZE + SVN__MAX_ENCODED_UINT_LEN || - inslen > MAX_INSTRUCTION_SECTION_LEN) - return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, - _("Svndiff contains a too-large window")); - - /* Check for integer overflow. */ - if (sview_offset < 0 || inslen + newlen < inslen - || sview_len + tview_len < sview_len - || (apr_size_t)sview_offset + sview_len < (apr_size_t)sview_offset) - return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, - _("Svndiff contains corrupt window header")); - - /* Check for source windows which slide backwards. */ - if (sview_len > 0 - && (sview_offset < db->last_sview_offset - || (sview_offset + sview_len - < db->last_sview_offset + db->last_sview_len))) - return svn_error_create - (SVN_ERR_SVNDIFF_BACKWARD_VIEW, NULL, - _("Svndiff has backwards-sliding source views")); + if (db->window_header_len == 0) + { + svn_filesize_t sview_offset; + apr_size_t sview_len, tview_len, inslen, newlen; + const unsigned char *hdr_start = p; + + p = decode_file_offset(&sview_offset, p, end); + if (p == NULL) + break; + + p = decode_size(&sview_len, p, end); + if (p == NULL) + break; + + p = decode_size(&tview_len, p, end); + if (p == NULL) + break; + + p = decode_size(&inslen, p, end); + if (p == NULL) + break; + + p = decode_size(&newlen, p, end); + if (p == NULL) + break; + + if (tview_len > SVN_DELTA_WINDOW_SIZE || + sview_len > SVN_DELTA_WINDOW_SIZE || + /* for svndiff1, newlen includes the original length */ + newlen > SVN_DELTA_WINDOW_SIZE + SVN__MAX_ENCODED_UINT_LEN || + inslen > MAX_INSTRUCTION_SECTION_LEN) + return svn_error_create( + SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, + _("Svndiff contains a too-large window")); + + /* Check for integer overflow. */ + if (sview_offset < 0 || inslen + newlen < inslen + || sview_len + tview_len < sview_len + || (apr_size_t)sview_offset + sview_len < (apr_size_t)sview_offset) + return svn_error_create( + SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, + _("Svndiff contains corrupt window header")); + + /* Check for source windows which slide backwards. */ + if (sview_len > 0 + && (sview_offset < db->last_sview_offset + || (sview_offset + sview_len + < db->last_sview_offset + db->last_sview_len))) + return svn_error_create( + SVN_ERR_SVNDIFF_BACKWARD_VIEW, NULL, + _("Svndiff has backwards-sliding source views")); + + /* Remember parsed window header. */ + db->window_header_len = p - hdr_start; + db->sview_offset = sview_offset; + db->sview_len = sview_len; + db->tview_len = tview_len; + db->inslen = inslen; + db->newlen = newlen; + } + else + { + /* Skip already parsed window header. */ + p += db->window_header_len; + } /* Wait for more data if we don't have enough bytes for the - whole window. */ - if ((apr_size_t) (end - p) < inslen + newlen) + whole window. */ + if ((apr_size_t) (end - p) < db->inslen + db->newlen) return SVN_NO_ERROR; /* Decode the window and send it off. */ - SVN_ERR(decode_window(&window, sview_offset, sview_len, tview_len, - inslen, newlen, p, db->subpool, - db->version)); + SVN_ERR(decode_window(&window, db->sview_offset, db->sview_len, + db->tview_len, db->inslen, db->newlen, p, + db->subpool, db->version)); SVN_ERR(db->consumer_func(&window, db->consumer_baton)); - /* Make a new subpool and buffer, saving aside the remaining - data in the old buffer. */ - newpool = svn_pool_create(db->pool); - p += inslen + newlen; - remaining = db->buffer->data + db->buffer->len - (const char *) p; - db->buffer = - svn_stringbuf_ncreate((const char *) p, remaining, newpool); + p += db->inslen + db->newlen; + + /* Remove processed data from the buffer. */ + svn_stringbuf_remove(db->buffer, 0, db->buffer->len - (end - p)); + + /* Reset window header length. */ + db->window_header_len = 0; /* Remember the offset and length of the source view for next time. */ - db->last_sview_offset = sview_offset; - db->last_sview_len = sview_len; - - /* We've copied stuff out of the old pool. Toss that pool and use - our new pool. - ### might be nice to avoid the copy and just use svn_pool_clear - ### to get rid of whatever the "other stuff" is. future project... - */ - svn_pool_destroy(db->subpool); - db->subpool = newpool; + db->last_sview_offset = db->sview_offset; + db->last_sview_len = db->sview_len; + + /* Clear subpool. */ + svn_pool_clear(db->subpool); } /* At this point we processed all integral windows and DB->BUFFER is empty or contains partially read window header. - Check that unprocessed data is not larger that theoretical maximum + Check that unprocessed data is not larger than theoretical maximum window header size. */ if (db->buffer->len > 5 * SVN__MAX_ENCODED_UINT_LEN) return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, @@ -757,23 +823,25 @@ svn_txdelta_parse_svndiff(svn_txdelta_window_handler_t handler, svn_boolean_t error_on_early_close, apr_pool_t *pool) { - apr_pool_t *subpool = svn_pool_create(pool); - struct decode_baton *db = apr_palloc(pool, sizeof(*db)); svn_stream_t *stream; - db->consumer_func = handler; - db->consumer_baton = handler_baton; - db->pool = subpool; - db->subpool = svn_pool_create(subpool); - db->buffer = svn_stringbuf_create_empty(db->subpool); - db->last_sview_offset = 0; - db->last_sview_len = 0; - db->header_bytes = 0; - db->error_on_early_close = error_on_early_close; - stream = svn_stream_create(db, pool); - if (handler != svn_delta_noop_window_handler) { + apr_pool_t *subpool = svn_pool_create(pool); + struct decode_baton *db = apr_palloc(pool, sizeof(*db)); + + db->consumer_func = handler; + db->consumer_baton = handler_baton; + db->pool = subpool; + db->subpool = svn_pool_create(subpool); + db->buffer = svn_stringbuf_create_empty(db->pool); + db->last_sview_offset = 0; + db->last_sview_len = 0; + db->header_bytes = 0; + db->error_on_early_close = error_on_early_close; + db->window_header_len = 0; + stream = svn_stream_create(db, pool); + svn_stream_set_write(stream, write_handler); svn_stream_set_close(stream, close_handler); } @@ -781,6 +849,7 @@ svn_txdelta_parse_svndiff(svn_txdelta_window_handler_t handler, { /* And else we just ignore everything as efficiently as we can. by only hooking a no-op handler */ + stream = svn_stream_create(NULL, pool); svn_stream_set_write(stream, noop_write_handler); } return stream; @@ -926,3 +995,107 @@ svn_txdelta__read_raw_window_len(apr_size_t *window_len, return SVN_NO_ERROR; } +typedef struct svndiff_stream_baton_t +{ + apr_pool_t *scratch_pool; + svn_txdelta_stream_t *txstream; + svn_txdelta_window_handler_t handler; + void *handler_baton; + svn_stringbuf_t *window_buffer; + apr_size_t read_pos; + svn_boolean_t hit_eof; +} svndiff_stream_baton_t; + +static svn_error_t * +svndiff_stream_write_fn(void *baton, const char *data, apr_size_t *len) +{ + svndiff_stream_baton_t *b = baton; + + /* The memory usage here is limited, as this buffer doesn't grow + beyond the (header size + max window size in svndiff format). + See the comment in svn_txdelta_to_svndiff_stream(). */ + svn_stringbuf_appendbytes(b->window_buffer, data, *len); + + return SVN_NO_ERROR; +} + +static svn_error_t * +svndiff_stream_read_fn(void *baton, char *buffer, apr_size_t *len) +{ + svndiff_stream_baton_t *b = baton; + apr_size_t left = *len; + apr_size_t read = 0; + + while (left) + { + apr_size_t chunk_size; + + if (b->read_pos == b->window_buffer->len && !b->hit_eof) + { + svn_txdelta_window_t *window; + + svn_pool_clear(b->scratch_pool); + svn_stringbuf_setempty(b->window_buffer); + SVN_ERR(svn_txdelta_next_window(&window, b->txstream, + b->scratch_pool)); + SVN_ERR(b->handler(window, b->handler_baton)); + b->read_pos = 0; + + if (!window) + b->hit_eof = TRUE; + } + + if (left > b->window_buffer->len - b->read_pos) + chunk_size = b->window_buffer->len - b->read_pos; + else + chunk_size = left; + + if (!chunk_size) + break; + + memcpy(buffer, b->window_buffer->data + b->read_pos, chunk_size); + b->read_pos += chunk_size; + buffer += chunk_size; + read += chunk_size; + left -= chunk_size; + } + + *len = read; + return SVN_NO_ERROR; +} + +svn_stream_t * +svn_txdelta_to_svndiff_stream(svn_txdelta_stream_t *txstream, + int svndiff_version, + int compression_level, + apr_pool_t *pool) +{ + svndiff_stream_baton_t *baton; + svn_stream_t *push_stream; + svn_stream_t *pull_stream; + + baton = apr_pcalloc(pool, sizeof(*baton)); + baton->scratch_pool = svn_pool_create(pool); + baton->txstream = txstream; + baton->window_buffer = svn_stringbuf_create_empty(pool); + baton->hit_eof = FALSE; + baton->read_pos = 0; + + push_stream = svn_stream_create(baton, pool); + svn_stream_set_write(push_stream, svndiff_stream_write_fn); + + /* We rely on the implementation detail of the svn_txdelta_to_svndiff3() + function, namely, on how the window_handler() function behaves. + As long as it writes one svndiff window at a time to the target + stream, the memory usage of this function (in other words, how + much data can be accumulated in the internal 'window_buffer') + is limited. */ + svn_txdelta_to_svndiff3(&baton->handler, &baton->handler_baton, + push_stream, svndiff_version, + compression_level, pool); + + pull_stream = svn_stream_create(baton, pool); + svn_stream_set_read2(pull_stream, NULL, svndiff_stream_read_fn); + + return pull_stream; +} |