2 * text-delta.c -- Internal text delta representation
4 * ====================================================================
5 * Licensed to the Apache Software Foundation (ASF) under one
6 * or more contributor license agreements. See the NOTICE file
7 * distributed with this work for additional information
8 * regarding copyright ownership. The ASF licenses this file
9 * to you under the Apache License, Version 2.0 (the
10 * "License"); you may not use this file except in compliance
11 * with the License. You may obtain a copy of the License at
13 * http://www.apache.org/licenses/LICENSE-2.0
15 * Unless required by applicable law or agreed to in writing,
16 * software distributed under the License is distributed on an
17 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
18 * KIND, either express or implied. See the License for the
19 * specific language governing permissions and limitations
21 * ====================================================================
28 #include <apr_general.h> /* for APR_INLINE */
29 #include <apr_md5.h> /* for, um...MD5 stuff */
31 #include "svn_delta.h"
33 #include "svn_pools.h"
34 #include "svn_checksum.h"
39 /* Text delta stream descriptor. */
41 struct svn_txdelta_stream_t {
42 /* Copied from parameters to svn_txdelta_stream_create. */
44 svn_txdelta_next_window_fn_t next_window;
45 svn_txdelta_md5_digest_fn_t md5_digest;
48 /* Delta stream baton. */
49 struct txdelta_baton {
50 /* These are copied from parameters passed to svn_txdelta. */
55 svn_boolean_t more_source; /* FALSE if source stream hit EOF. */
56 svn_boolean_t more; /* TRUE if there are more data in the pool. */
57 svn_filesize_t pos; /* Offset of next read in source file. */
58 char *buf; /* Buffer for input data. */
60 svn_checksum_ctx_t *context; /* If not NULL, the context for computing
62 svn_checksum_t *checksum; /* If non-NULL, the checksum of TARGET. */
64 apr_pool_t *result_pool; /* For results (e.g. checksum) */
68 /* Target-push stream descriptor. */
71 /* These are copied from parameters passed to svn_txdelta_target_push. */
73 svn_txdelta_window_handler_t wh;
79 svn_filesize_t source_offset;
80 apr_size_t source_len;
81 svn_boolean_t source_done;
82 apr_size_t target_len;
86 /* Text delta applicator. */
89 /* These are copied from parameters passed to svn_txdelta_apply. */
93 /* Private data. Between calls, SBUF contains the data from the
94 * last window's source view, as specified by SBUF_OFFSET and
95 * SBUF_LEN. The contents of TBUF are not interesting between
97 apr_pool_t *pool; /* Pool to allocate data from */
98 char *sbuf; /* Source buffer */
99 apr_size_t sbuf_size; /* Allocated source buffer space */
100 svn_filesize_t sbuf_offset; /* Offset of SBUF data in source stream */
101 apr_size_t sbuf_len; /* Length of SBUF data */
102 char *tbuf; /* Target buffer */
103 apr_size_t tbuf_size; /* Allocated target buffer space */
105 svn_checksum_ctx_t *md5_context; /* Leads to result_digest below. */
106 unsigned char *result_digest; /* MD5 digest of resultant fulltext;
107 must point to at least APR_MD5_DIGESTSIZE
110 const char *error_info; /* Optional extra info for error returns. */
115 svn_txdelta_window_t *
116 svn_txdelta__make_window(const svn_txdelta__ops_baton_t *build_baton,
119 svn_txdelta_window_t *window;
120 svn_string_t *new_data = apr_palloc(pool, sizeof(*new_data));
122 window = apr_palloc(pool, sizeof(*window));
123 window->sview_offset = 0;
124 window->sview_len = 0;
125 window->tview_len = 0;
127 window->num_ops = build_baton->num_ops;
128 window->src_ops = build_baton->src_ops;
129 window->ops = build_baton->ops;
131 /* just copy the fields over, rather than alloc/copying into a whole new
132 svn_string_t structure. */
133 /* ### would be much nicer if window->new_data were not a ptr... */
134 new_data->data = build_baton->new_data->data;
135 new_data->len = build_baton->new_data->len;
136 window->new_data = new_data;
142 /* Compute and return a delta window using the xdelta algorithm on
143 DATA, which contains SOURCE_LEN bytes of source data and TARGET_LEN
144 bytes of target data. SOURCE_OFFSET gives the offset of the source
145 data, and is simply copied into the window's sview_offset field. */
146 static svn_txdelta_window_t *
147 compute_window(const char *data, apr_size_t source_len, apr_size_t target_len,
148 svn_filesize_t source_offset, apr_pool_t *pool)
150 svn_txdelta__ops_baton_t build_baton = { 0 };
151 svn_txdelta_window_t *window;
153 /* Compute the delta operations. */
154 build_baton.new_data = svn_stringbuf_create_empty(pool);
157 svn_txdelta__insert_op(&build_baton, svn_txdelta_new, 0, target_len, data,
160 svn_txdelta__xdelta(&build_baton, data, source_len, target_len, pool);
162 /* Create and return the delta window. */
163 window = svn_txdelta__make_window(&build_baton, pool);
164 window->sview_offset = source_offset;
165 window->sview_len = source_len;
166 window->tview_len = target_len;
172 svn_txdelta_window_t *
173 svn_txdelta_window_dup(const svn_txdelta_window_t *window,
176 svn_txdelta__ops_baton_t build_baton = { 0 };
177 svn_txdelta_window_t *new_window;
178 const apr_size_t ops_size = (window->num_ops * sizeof(*build_baton.ops));
180 build_baton.num_ops = window->num_ops;
181 build_baton.src_ops = window->src_ops;
182 build_baton.ops_size = window->num_ops;
183 build_baton.ops = apr_pmemdup(pool, window->ops, ops_size);
184 build_baton.new_data =
185 svn_stringbuf_create_from_string(window->new_data, pool);
187 new_window = svn_txdelta__make_window(&build_baton, pool);
188 new_window->sview_offset = window->sview_offset;
189 new_window->sview_len = window->sview_len;
190 new_window->tview_len = window->tview_len;
194 /* This is a private interlibrary compatibility wrapper. */
195 svn_txdelta_window_t *
196 svn_txdelta__copy_window(const svn_txdelta_window_t *window,
198 svn_txdelta_window_t *
199 svn_txdelta__copy_window(const svn_txdelta_window_t *window,
202 return svn_txdelta_window_dup(window, pool);
206 /* Insert a delta op into a delta window. */
209 svn_txdelta__insert_op(svn_txdelta__ops_baton_t *build_baton,
210 enum svn_delta_action opcode,
213 const char *new_data,
216 svn_txdelta_op_t *op;
218 /* Check if this op can be merged with the previous op. The delta
219 combiner sometimes generates such ops, and this is the obvious
220 place to make the check. */
221 if (build_baton->num_ops > 0)
223 op = &build_baton->ops[build_baton->num_ops - 1];
224 if (op->action_code == opcode
225 && (opcode == svn_txdelta_new
226 || op->offset + op->length == offset))
228 op->length += length;
229 if (opcode == svn_txdelta_new)
230 svn_stringbuf_appendbytes(build_baton->new_data,
236 /* Create space for the new op. */
237 if (build_baton->num_ops == build_baton->ops_size)
239 svn_txdelta_op_t *const old_ops = build_baton->ops;
240 int const new_ops_size = (build_baton->ops_size == 0
241 ? 16 : 2 * build_baton->ops_size);
243 apr_palloc(pool, new_ops_size * sizeof(*build_baton->ops));
245 /* Copy any existing ops into the new array */
247 memcpy(build_baton->ops, old_ops,
248 build_baton->ops_size * sizeof(*build_baton->ops));
249 build_baton->ops_size = new_ops_size;
252 /* Insert the op. svn_delta_source and svn_delta_target are
253 just inserted. For svn_delta_new, the new data must be
254 copied into the window. */
255 op = &build_baton->ops[build_baton->num_ops];
258 case svn_txdelta_source:
259 ++build_baton->src_ops;
261 case svn_txdelta_target:
262 op->action_code = opcode;
266 case svn_txdelta_new:
267 op->action_code = opcode;
268 op->offset = build_baton->new_data->len;
270 svn_stringbuf_appendbytes(build_baton->new_data, new_data, length);
273 assert(!"unknown delta op.");
276 ++build_baton->num_ops;
280 svn_txdelta__remove_copy(svn_txdelta__ops_baton_t *build_baton,
283 svn_txdelta_op_t *op;
286 /* remove ops back to front */
287 while (build_baton->num_ops > 0)
289 op = &build_baton->ops[build_baton->num_ops-1];
291 /* we can't modify svn_txdelta_target ops -> stop there */
292 if (op->action_code == svn_txdelta_target)
295 /* handle the case that we cannot remove the op entirely */
296 if (op->length + len > max_len)
298 /* truncate only insertions. Copies don't benefit
299 from being truncated. */
300 if (op->action_code == svn_txdelta_new)
302 build_baton->new_data->len -= max_len - len;
303 op->length -= max_len - len;
310 /* drop the op entirely */
311 if (op->action_code == svn_txdelta_new)
312 build_baton->new_data->len -= op->length;
315 --build_baton->num_ops;
323 /* Generic delta stream functions. */
325 svn_txdelta_stream_t *
326 svn_txdelta_stream_create(void *baton,
327 svn_txdelta_next_window_fn_t next_window,
328 svn_txdelta_md5_digest_fn_t md5_digest,
331 svn_txdelta_stream_t *stream = apr_palloc(pool, sizeof(*stream));
333 stream->baton = baton;
334 stream->next_window = next_window;
335 stream->md5_digest = md5_digest;
341 svn_txdelta_next_window(svn_txdelta_window_t **window,
342 svn_txdelta_stream_t *stream,
345 return stream->next_window(window, stream->baton, pool);
348 const unsigned char *
349 svn_txdelta_md5_digest(svn_txdelta_stream_t *stream)
351 return stream->md5_digest(stream->baton);
357 txdelta_next_window(svn_txdelta_window_t **window,
361 struct txdelta_baton *b = baton;
362 apr_size_t source_len = SVN_DELTA_WINDOW_SIZE;
363 apr_size_t target_len = SVN_DELTA_WINDOW_SIZE;
365 /* Read the source stream. */
368 SVN_ERR(svn_stream_read_full(b->source, b->buf, &source_len));
369 b->more_source = (source_len == SVN_DELTA_WINDOW_SIZE);
374 /* Read the target stream. */
375 SVN_ERR(svn_stream_read_full(b->target, b->buf + source_len, &target_len));
376 b->pos += source_len;
380 /* No target data? We're done; return the final window. */
381 if (b->context != NULL)
382 SVN_ERR(svn_checksum_final(&b->checksum, b->context, b->result_pool));
388 else if (b->context != NULL)
389 SVN_ERR(svn_checksum_update(b->context, b->buf + source_len, target_len));
391 *window = compute_window(b->buf, source_len, target_len,
392 b->pos - source_len, pool);
399 static const unsigned char *
400 txdelta_md5_digest(void *baton)
402 struct txdelta_baton *b = baton;
403 /* If there are more windows for this stream, the digest has not yet
408 /* If checksumming has not been activated, there will be no digest. */
409 if (b->context == NULL)
412 /* The checksum should be there. */
413 return b->checksum->digest;
418 svn_txdelta_run(svn_stream_t *source,
419 svn_stream_t *target,
420 svn_txdelta_window_handler_t handler,
422 svn_checksum_kind_t checksum_kind,
423 svn_checksum_t **checksum,
424 svn_cancel_func_t cancel_func,
426 apr_pool_t *result_pool,
427 apr_pool_t *scratch_pool)
429 apr_pool_t *iterpool = svn_pool_create(scratch_pool);
430 struct txdelta_baton tb = { 0 };
431 svn_txdelta_window_t *window;
435 tb.more_source = TRUE;
438 tb.buf = apr_palloc(scratch_pool, 2 * SVN_DELTA_WINDOW_SIZE);
439 tb.result_pool = result_pool;
441 if (checksum != NULL)
442 tb.context = svn_checksum_ctx_create(checksum_kind, scratch_pool);
446 /* free the window (if any) */
447 svn_pool_clear(iterpool);
449 /* read in a single delta window */
450 SVN_ERR(txdelta_next_window(&window, &tb, iterpool));
452 /* shove it at the handler */
453 SVN_ERR((*handler)(window, handler_baton));
456 SVN_ERR(cancel_func(cancel_baton));
458 while (window != NULL);
460 svn_pool_destroy(iterpool);
462 if (checksum != NULL)
463 *checksum = tb.checksum; /* should be there! */
470 svn_txdelta2(svn_txdelta_stream_t **stream,
471 svn_stream_t *source,
472 svn_stream_t *target,
473 svn_boolean_t calculate_checksum,
476 struct txdelta_baton *b = apr_pcalloc(pool, sizeof(*b));
480 b->more_source = TRUE;
482 b->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE);
483 b->context = calculate_checksum
484 ? svn_checksum_ctx_create(svn_checksum_md5, pool)
486 b->result_pool = pool;
488 *stream = svn_txdelta_stream_create(b, txdelta_next_window,
489 txdelta_md5_digest, pool);
493 svn_txdelta(svn_txdelta_stream_t **stream,
494 svn_stream_t *source,
495 svn_stream_t *target,
498 svn_txdelta2(stream, source, target, TRUE, pool);
503 /* Functions for implementing a "target push" delta. */
505 /* This is the write handler for a target-push delta stream. It reads
506 * source data, buffers target data, and fires off delta windows when
507 * the target data buffer is full. */
509 tpush_write_handler(void *baton, const char *data, apr_size_t *len)
511 struct tpush_baton *tb = baton;
512 apr_size_t chunk_len, data_len = *len;
513 apr_pool_t *pool = svn_pool_create(tb->pool);
514 svn_txdelta_window_t *window;
518 svn_pool_clear(pool);
520 /* Make sure we're all full up on source data, if possible. */
521 if (tb->source_len == 0 && !tb->source_done)
523 tb->source_len = SVN_DELTA_WINDOW_SIZE;
524 SVN_ERR(svn_stream_read_full(tb->source, tb->buf, &tb->source_len));
525 if (tb->source_len < SVN_DELTA_WINDOW_SIZE)
526 tb->source_done = TRUE;
529 /* Copy in the target data, up to SVN_DELTA_WINDOW_SIZE. */
530 chunk_len = SVN_DELTA_WINDOW_SIZE - tb->target_len;
531 if (chunk_len > data_len)
532 chunk_len = data_len;
533 memcpy(tb->buf + tb->source_len + tb->target_len, data, chunk_len);
535 data_len -= chunk_len;
536 tb->target_len += chunk_len;
538 /* If we're full of target data, compute and fire off a window. */
539 if (tb->target_len == SVN_DELTA_WINDOW_SIZE)
541 window = compute_window(tb->buf, tb->source_len, tb->target_len,
542 tb->source_offset, pool);
543 SVN_ERR(tb->wh(window, tb->whb));
544 tb->source_offset += tb->source_len;
550 svn_pool_destroy(pool);
555 /* This is the close handler for a target-push delta stream. It sends
556 * a final window if there is any buffered target data, and then sends
557 * a NULL window signifying the end of the window stream. */
559 tpush_close_handler(void *baton)
561 struct tpush_baton *tb = baton;
562 svn_txdelta_window_t *window;
564 /* Send a final window if we have any residual target data. */
565 if (tb->target_len > 0)
567 window = compute_window(tb->buf, tb->source_len, tb->target_len,
568 tb->source_offset, tb->pool);
569 SVN_ERR(tb->wh(window, tb->whb));
572 /* Send a final NULL window signifying the end. */
573 return tb->wh(NULL, tb->whb);
578 svn_txdelta_target_push(svn_txdelta_window_handler_t handler,
579 void *handler_baton, svn_stream_t *source,
582 struct tpush_baton *tb;
583 svn_stream_t *stream;
585 /* Initialize baton. */
586 tb = apr_palloc(pool, sizeof(*tb));
589 tb->whb = handler_baton;
591 tb->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE);
592 tb->source_offset = 0;
594 tb->source_done = FALSE;
597 /* Create and return writable stream. */
598 stream = svn_stream_create(tb, pool);
599 svn_stream_set_write(stream, tpush_write_handler);
600 svn_stream_set_close(stream, tpush_close_handler);
606 /* Functions for applying deltas. */
608 /* Ensure that BUF has enough space for VIEW_LEN bytes. */
609 static APR_INLINE svn_error_t *
610 size_buffer(char **buf, apr_size_t *buf_size,
611 apr_size_t view_len, apr_pool_t *pool)
613 if (view_len > *buf_size)
616 if (*buf_size < view_len)
617 *buf_size = view_len;
618 SVN_ERR_ASSERT(APR_ALIGN_DEFAULT(*buf_size) >= *buf_size);
619 *buf = apr_palloc(pool, *buf_size);
625 /* Copy LEN bytes from SOURCE to TARGET. Unlike memmove() or memcpy(),
626 * create repeating patterns if the source and target ranges overlap.
627 * Return a pointer to the first byte after the copied target range. */
628 static APR_INLINE char *
629 patterning_copy(char *target, const char *source, apr_size_t len)
631 /* If the source and target overlap, repeat the overlapping pattern
632 in the target buffer. Always copy from the source buffer because
633 presumably it will be in the L1 cache after the first iteration
634 and doing this should avoid pipeline stalls due to write/read
636 const apr_size_t overlap = target - source;
637 while (len > overlap)
639 memcpy(target, source, overlap);
644 /* Copy any remaining source pattern. */
647 memcpy(target, source, len);
655 svn_txdelta_apply_instructions(svn_txdelta_window_t *window,
656 const char *sbuf, char *tbuf,
659 const svn_txdelta_op_t *op;
662 /* Nothing to do for empty buffers.
663 * This check allows for NULL TBUF in that case. */
667 for (op = window->ops; op < window->ops + window->num_ops; op++)
669 const apr_size_t buf_len = (op->length < *tlen - tpos
670 ? op->length : *tlen - tpos);
672 /* Check some invariants common to all instructions. */
673 assert(tpos + op->length <= window->tview_len);
675 switch (op->action_code)
677 case svn_txdelta_source:
678 /* Copy from source area. */
680 assert(op->offset + op->length <= window->sview_len);
681 memcpy(tbuf + tpos, sbuf + op->offset, buf_len);
684 case svn_txdelta_target:
685 /* Copy from target area. We can't use memcpy() or the like
686 * since we need a specific semantics for overlapping copies:
687 * they must result in repeating patterns.
688 * Note that most copies won't have overlapping source and
689 * target ranges (they are just a result of self-compressed
690 * data) but a small percentage will. */
691 assert(op->offset < tpos);
692 patterning_copy(tbuf + tpos, tbuf + op->offset, buf_len);
695 case svn_txdelta_new:
696 /* Copy from window new area. */
697 assert(op->offset + op->length <= window->new_data->len);
699 window->new_data->data + op->offset,
704 assert(!"Invalid delta instruction code");
709 return; /* The buffer is full. */
712 /* Check that we produced the right amount of data. */
713 assert(tpos == window->tview_len);
717 /* Apply WINDOW to the streams given by APPL. */
719 apply_window(svn_txdelta_window_t *window, void *baton)
721 struct apply_baton *ab = (struct apply_baton *) baton;
726 svn_error_t *err = SVN_NO_ERROR;
728 /* We're done; just clean up. */
729 if (ab->result_digest)
731 svn_checksum_t *md5_checksum;
733 err = svn_checksum_final(&md5_checksum, ab->md5_context, ab->pool);
735 memcpy(ab->result_digest, md5_checksum->digest,
736 svn_checksum_size(md5_checksum));
739 err = svn_error_compose_create(err, svn_stream_close(ab->target));
740 svn_pool_destroy(ab->pool);
745 /* Make sure the source view didn't slide backwards. */
746 SVN_ERR_ASSERT(window->sview_len == 0
747 || (window->sview_offset >= ab->sbuf_offset
748 && (window->sview_offset + window->sview_len
749 >= ab->sbuf_offset + ab->sbuf_len)));
751 /* Make sure there's enough room in the target buffer. */
752 SVN_ERR(size_buffer(&ab->tbuf, &ab->tbuf_size, window->tview_len, ab->pool));
754 /* Prepare the source buffer for reading from the input stream. */
755 if (window->sview_offset != ab->sbuf_offset
756 || window->sview_len > ab->sbuf_size)
758 char *old_sbuf = ab->sbuf;
760 /* Make sure there's enough room. */
761 SVN_ERR(size_buffer(&ab->sbuf, &ab->sbuf_size, window->sview_len,
764 /* If the existing view overlaps with the new view, copy the
765 * overlap to the beginning of the new buffer. */
766 if ( (apr_size_t)ab->sbuf_offset + ab->sbuf_len
767 > (apr_size_t)window->sview_offset)
770 (apr_size_t)(window->sview_offset - ab->sbuf_offset);
771 memmove(ab->sbuf, old_sbuf + start, ab->sbuf_len - start);
772 ab->sbuf_len -= start;
776 ab->sbuf_offset = window->sview_offset;
779 /* Read the remainder of the source view into the buffer. */
780 if (ab->sbuf_len < window->sview_len)
782 len = window->sview_len - ab->sbuf_len;
783 SVN_ERR(svn_stream_read_full(ab->source, ab->sbuf + ab->sbuf_len, &len));
784 if (len != window->sview_len - ab->sbuf_len)
785 return svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL,
786 "Delta source ended unexpectedly");
787 ab->sbuf_len = window->sview_len;
790 /* Apply the window instructions to the source view to generate
792 len = window->tview_len;
793 svn_txdelta_apply_instructions(window, ab->sbuf, ab->tbuf, &len);
794 SVN_ERR_ASSERT(len == window->tview_len);
796 /* Write out the output. */
798 /* Just update the context here. */
799 if (ab->result_digest)
800 SVN_ERR(svn_checksum_update(ab->md5_context, ab->tbuf, len));
802 return svn_stream_write(ab->target, ab->tbuf, &len);
807 svn_txdelta_apply(svn_stream_t *source,
808 svn_stream_t *target,
809 unsigned char *result_digest,
810 const char *error_info,
812 svn_txdelta_window_handler_t *handler,
813 void **handler_baton)
815 apr_pool_t *subpool = svn_pool_create(pool);
816 struct apply_baton *ab;
818 ab = apr_palloc(subpool, sizeof(*ab));
828 ab->result_digest = result_digest;
831 ab->md5_context = svn_checksum_ctx_create(svn_checksum_md5, subpool);
834 ab->error_info = apr_pstrdup(subpool, error_info);
836 ab->error_info = NULL;
838 *handler = apply_window;
844 /* Convenience routines */
847 svn_txdelta_send_string(const svn_string_t *string,
848 svn_txdelta_window_handler_t handler,
852 svn_txdelta_window_t window = { 0 };
855 /* Build a single `new' op */
856 op.action_code = svn_txdelta_new;
858 op.length = string->len;
860 /* Build a single window containing a ptr to the string. */
861 window.tview_len = string->len;
864 window.new_data = string;
866 /* Push the one window at the handler. */
867 SVN_ERR((*handler)(&window, handler_baton));
869 /* Push a NULL at the handler, because we're done. */
870 return (*handler)(NULL, handler_baton);
873 svn_error_t *svn_txdelta_send_stream(svn_stream_t *stream,
874 svn_txdelta_window_handler_t handler,
876 unsigned char *digest,
879 svn_txdelta_window_t delta_window = { 0 };
880 svn_txdelta_op_t delta_op;
881 svn_string_t window_data;
882 char read_buf[SVN__STREAM_CHUNK_SIZE + 1];
883 svn_checksum_ctx_t *md5_checksum_ctx;
886 md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
890 apr_size_t read_len = SVN__STREAM_CHUNK_SIZE;
892 SVN_ERR(svn_stream_read_full(stream, read_buf, &read_len));
896 window_data.data = read_buf;
897 window_data.len = read_len;
899 delta_op.action_code = svn_txdelta_new;
901 delta_op.length = read_len;
903 delta_window.tview_len = read_len;
904 delta_window.num_ops = 1;
905 delta_window.ops = &delta_op;
906 delta_window.new_data = &window_data;
908 SVN_ERR(handler(&delta_window, handler_baton));
911 SVN_ERR(svn_checksum_update(md5_checksum_ctx, read_buf, read_len));
913 if (read_len < SVN__STREAM_CHUNK_SIZE)
916 SVN_ERR(handler(NULL, handler_baton));
920 svn_checksum_t *md5_checksum;
922 SVN_ERR(svn_checksum_final(&md5_checksum, md5_checksum_ctx, pool));
923 memcpy(digest, md5_checksum->digest, APR_MD5_DIGESTSIZE);
929 svn_error_t *svn_txdelta_send_txstream(svn_txdelta_stream_t *txstream,
930 svn_txdelta_window_handler_t handler,
934 svn_txdelta_window_t *window;
936 /* create a pool just for the windows */
937 apr_pool_t *wpool = svn_pool_create(pool);
941 /* free the window (if any) */
942 svn_pool_clear(wpool);
944 /* read in a single delta window */
945 SVN_ERR(svn_txdelta_next_window(&window, txstream, wpool));
947 /* shove it at the handler */
948 SVN_ERR((*handler)(window, handler_baton));
950 while (window != NULL);
952 svn_pool_destroy(wpool);
958 svn_txdelta_send_contents(const unsigned char *contents,
960 svn_txdelta_window_handler_t handler,
964 svn_string_t new_data;
965 svn_txdelta_op_t op = { svn_txdelta_new, 0, 0 };
966 svn_txdelta_window_t window = { 0, 0, 0, 1, 0 };
968 window.new_data = &new_data;
970 /* send CONTENT as a series of max-sized windows */
973 /* stuff next chunk into the window */
974 window.tview_len = len < SVN_DELTA_WINDOW_SIZE
976 : SVN_DELTA_WINDOW_SIZE;
977 op.length = window.tview_len;
978 new_data.len = window.tview_len;
979 new_data.data = (const char*)contents;
981 /* update remaining */
982 contents += window.tview_len;
983 len -= window.tview_len;
985 /* shove it at the handler */
986 SVN_ERR((*handler)(&window, handler_baton));
989 /* indicate end of stream */
990 SVN_ERR((*handler)(NULL, handler_baton));