diff options
author | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-08-22 14:29:52 +0100 |
---|---|---|
committer | Lorry <lorry@roadtrain.codethink.co.uk> | 2012-08-22 14:29:52 +0100 |
commit | f1bdf13786f0752c0846cf36f0d91e4fc6747929 (patch) | |
tree | 4223b2035bf2240d681a53822808b3c7f687b905 /subversion/libsvn_delta | |
download | subversion-tarball-f1bdf13786f0752c0846cf36f0d91e4fc6747929.tar.gz |
Tarball conversion
Diffstat (limited to 'subversion/libsvn_delta')
-rw-r--r-- | subversion/libsvn_delta/cancel.c | 378 | ||||
-rw-r--r-- | subversion/libsvn_delta/compat.c | 78 | ||||
-rw-r--r-- | subversion/libsvn_delta/compose_delta.c | 837 | ||||
-rw-r--r-- | subversion/libsvn_delta/debug_editor.c | 433 | ||||
-rw-r--r-- | subversion/libsvn_delta/debug_editor.h | 49 | ||||
-rw-r--r-- | subversion/libsvn_delta/default_editor.c | 161 | ||||
-rw-r--r-- | subversion/libsvn_delta/delta.h | 90 | ||||
-rw-r--r-- | subversion/libsvn_delta/depth_filter_editor.c | 485 | ||||
-rw-r--r-- | subversion/libsvn_delta/editor.c | 446 | ||||
-rw-r--r-- | subversion/libsvn_delta/path_driver.c | 292 | ||||
-rw-r--r-- | subversion/libsvn_delta/svndiff.c | 976 | ||||
-rw-r--r-- | subversion/libsvn_delta/text_delta.c | 916 | ||||
-rw-r--r-- | subversion/libsvn_delta/version.c | 33 | ||||
-rw-r--r-- | subversion/libsvn_delta/xdelta.c | 398 |
14 files changed, 5572 insertions, 0 deletions
diff --git a/subversion/libsvn_delta/cancel.c b/subversion/libsvn_delta/cancel.c new file mode 100644 index 0000000..89995a8 --- /dev/null +++ b/subversion/libsvn_delta/cancel.c @@ -0,0 +1,378 @@ +/* + * cancel.c: Routines to support cancellation of running subversion functions. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "svn_delta.h" + +struct edit_baton +{ + const svn_delta_editor_t *wrapped_editor; + void *wrapped_edit_baton; + + svn_cancel_func_t cancel_func; + void *cancel_baton; +}; + +struct dir_baton +{ + void *edit_baton; + void *wrapped_dir_baton; +}; + +struct file_baton +{ + void *edit_baton; + void *wrapped_file_baton; +}; + +static svn_error_t * +set_target_revision(void *edit_baton, + svn_revnum_t target_revision, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton, + target_revision, + pool); +} + +static svn_error_t * +open_root(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **root_baton) +{ + struct edit_baton *eb = edit_baton; + struct dir_baton *dir_baton = apr_palloc(pool, sizeof(*dir_baton)); + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton, + base_revision, + pool, + &dir_baton->wrapped_dir_baton)); + + dir_baton->edit_baton = edit_baton; + + *root_baton = dir_baton; + + return SVN_NO_ERROR; +} + +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t base_revision, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + return eb->wrapped_editor->delete_entry(path, + base_revision, + pb->wrapped_dir_baton, + pool); +} + +static svn_error_t * +add_directory(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct dir_baton *b = apr_palloc(pool, sizeof(*b)); + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + SVN_ERR(eb->wrapped_editor->add_directory(path, + pb->wrapped_dir_baton, + copyfrom_path, + copyfrom_revision, + pool, + &b->wrapped_dir_baton)); + + b->edit_baton = eb; + *child_baton = b; + + return SVN_NO_ERROR; +} + +static svn_error_t * +open_directory(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct dir_baton *db = apr_palloc(pool, sizeof(*db)); + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + SVN_ERR(eb->wrapped_editor->open_directory(path, + pb->wrapped_dir_baton, + base_revision, + pool, + &db->wrapped_dir_baton)); + + db->edit_baton = eb; + *child_baton = db; + + return SVN_NO_ERROR; +} + +static svn_error_t * +add_file(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct file_baton *fb = apr_palloc(pool, sizeof(*fb)); + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + SVN_ERR(eb->wrapped_editor->add_file(path, + pb->wrapped_dir_baton, + copyfrom_path, + copyfrom_revision, + pool, + &fb->wrapped_file_baton)); + + fb->edit_baton = eb; + *file_baton = fb; + + return SVN_NO_ERROR; +} + +static svn_error_t * +open_file(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct file_baton *fb = apr_palloc(pool, sizeof(*fb)); + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + SVN_ERR(eb->wrapped_editor->open_file(path, + pb->wrapped_dir_baton, + base_revision, + pool, + &fb->wrapped_file_baton)); + + fb->edit_baton = eb; + *file_baton = fb; + + return SVN_NO_ERROR; +} + +static svn_error_t * +apply_textdelta(void *file_baton, + const char *base_checksum, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct file_baton *fb = file_baton; + struct edit_baton *eb = fb->edit_baton; + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + return eb->wrapped_editor->apply_textdelta(fb->wrapped_file_baton, + base_checksum, + pool, + handler, + handler_baton); +} + +static svn_error_t * +close_file(void *file_baton, + const char *text_checksum, + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + struct edit_baton *eb = fb->edit_baton; + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + return eb->wrapped_editor->close_file(fb->wrapped_file_baton, + text_checksum, pool); +} + +static svn_error_t * +absent_file(const char *path, + void *file_baton, + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + struct edit_baton *eb = fb->edit_baton; + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + return eb->wrapped_editor->absent_file(path, fb->wrapped_file_baton, + pool); +} + +static svn_error_t * +close_directory(void *dir_baton, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + struct edit_baton *eb = db->edit_baton; + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + return eb->wrapped_editor->close_directory(db->wrapped_dir_baton, pool); +} + +static svn_error_t * +absent_directory(const char *path, + void *dir_baton, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + struct edit_baton *eb = db->edit_baton; + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + return eb->wrapped_editor->absent_directory(path, db->wrapped_dir_baton, + pool); +} + +static svn_error_t * +change_file_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + struct edit_baton *eb = fb->edit_baton; + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + return eb->wrapped_editor->change_file_prop(fb->wrapped_file_baton, + name, value, pool); +} + +static svn_error_t * +change_dir_prop(void *dir_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + struct edit_baton *eb = db->edit_baton; + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + return eb->wrapped_editor->change_dir_prop(db->wrapped_dir_baton, + name, + value, + pool); +} + +static svn_error_t * +close_edit(void *edit_baton, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool); +} + +static svn_error_t * +abort_edit(void *edit_baton, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + + SVN_ERR(eb->cancel_func(eb->cancel_baton)); + + return eb->wrapped_editor->abort_edit(eb->wrapped_edit_baton, pool); +} + +svn_error_t * +svn_delta_get_cancellation_editor(svn_cancel_func_t cancel_func, + void *cancel_baton, + const svn_delta_editor_t *wrapped_editor, + void *wrapped_edit_baton, + const svn_delta_editor_t **editor, + void **edit_baton, + apr_pool_t *pool) +{ + if (cancel_func) + { + svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool); + struct edit_baton *eb = apr_palloc(pool, sizeof(*eb)); + + tree_editor->set_target_revision = set_target_revision; + tree_editor->open_root = open_root; + tree_editor->delete_entry = delete_entry; + tree_editor->add_directory = add_directory; + tree_editor->open_directory = open_directory; + tree_editor->change_dir_prop = change_dir_prop; + tree_editor->close_directory = close_directory; + tree_editor->absent_directory = absent_directory; + tree_editor->add_file = add_file; + tree_editor->open_file = open_file; + tree_editor->apply_textdelta = apply_textdelta; + tree_editor->change_file_prop = change_file_prop; + tree_editor->close_file = close_file; + tree_editor->absent_file = absent_file; + tree_editor->close_edit = close_edit; + tree_editor->abort_edit = abort_edit; + + eb->wrapped_editor = wrapped_editor; + eb->wrapped_edit_baton = wrapped_edit_baton; + eb->cancel_func = cancel_func; + eb->cancel_baton = cancel_baton; + + *editor = tree_editor; + *edit_baton = eb; + } + else + { + *editor = wrapped_editor; + *edit_baton = wrapped_edit_baton; + } + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_delta/compat.c b/subversion/libsvn_delta/compat.c new file mode 100644 index 0000000..7d12be9 --- /dev/null +++ b/subversion/libsvn_delta/compat.c @@ -0,0 +1,78 @@ +/* + * compat.c : Wrappers and callbacks for compatibility. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <apr_pools.h> + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_delta.h" + + +struct file_rev_handler_wrapper_baton { + void *baton; + svn_file_rev_handler_old_t handler; +}; + +/* This implements svn_file_rev_handler_t. */ +static svn_error_t * +file_rev_handler_wrapper(void *baton, + const char *path, + svn_revnum_t rev, + apr_hash_t *rev_props, + svn_boolean_t result_of_merge, + svn_txdelta_window_handler_t *delta_handler, + void **delta_baton, + apr_array_header_t *prop_diffs, + apr_pool_t *pool) +{ + struct file_rev_handler_wrapper_baton *fwb = baton; + + if (fwb->handler) + return fwb->handler(fwb->baton, + path, + rev, + rev_props, + delta_handler, + delta_baton, + prop_diffs, + pool); + + return SVN_NO_ERROR; +} + +void +svn_compat_wrap_file_rev_handler(svn_file_rev_handler_t *handler2, + void **handler2_baton, + svn_file_rev_handler_old_t handler, + void *handler_baton, + apr_pool_t *pool) +{ + struct file_rev_handler_wrapper_baton *fwb = apr_palloc(pool, sizeof(*fwb)); + + /* Set the user provided old format callback in the baton. */ + fwb->baton = handler_baton; + fwb->handler = handler; + + *handler2_baton = fwb; + *handler2 = file_rev_handler_wrapper; +} diff --git a/subversion/libsvn_delta/compose_delta.c b/subversion/libsvn_delta/compose_delta.c new file mode 100644 index 0000000..382701d --- /dev/null +++ b/subversion/libsvn_delta/compose_delta.c @@ -0,0 +1,837 @@ +/* + * compose_delta.c: Delta window composition. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#include <assert.h> + +#include <apr_general.h> /* For APR_INLINE */ + +#include "svn_delta.h" +#include "svn_pools.h" +#include "delta.h" + +/* Define a MIN macro if this platform doesn't already have one. */ +#ifndef MIN +#define MIN(a, b) ((a) < (b) ? (a) : (b)) +#endif + + +/* ==================================================================== */ +/* Support for efficient small-block allocation from pools. */ + +/* The following structs will be allocated and freed often: */ + +/* A node in the range index tree. */ +typedef struct range_index_node_t range_index_node_t; +struct range_index_node_t +{ + /* 'offset' and 'limit' define the range in the source window. */ + apr_size_t offset; + apr_size_t limit; + + /* 'target_offset' is where that range is represented in the target. */ + apr_size_t target_offset; + + /* 'left' and 'right' link the node into a splay tree. */ + range_index_node_t *left, *right; + + /* 'prev' and 'next' link it into an ordered, doubly-linked list. */ + range_index_node_t *prev, *next; +}; + +/* A node in a list of ranges for source and target op copies. */ +enum range_kind + { + range_from_source, + range_from_target + }; + +typedef struct range_list_node_t range_list_node_t; +struct range_list_node_t +{ + /* Where does the range come from? + 'offset' and 'limit' always refer to the "virtual" source data + for the second delta window. For a target range, the actual + offset to use for generating the target op is 'target_offset'; + that field isn't used by source ranges. */ + enum range_kind kind; + + /* 'offset' and 'limit' define the range. */ + apr_size_t offset; + apr_size_t limit; + + /* 'target_offset' is the start of the range in the target. */ + apr_size_t target_offset; + + /* 'prev' and 'next' link the node into an ordered, doubly-linked list. */ + range_list_node_t *prev, *next; +}; + + +/* This is what will be allocated: */ +typedef union alloc_block_t alloc_block_t; +union alloc_block_t +{ + range_index_node_t index_node; + range_list_node_t list_node; + + /* Links free blocks into a freelist. */ + alloc_block_t *next_free; +}; + + +/* Allocate a block. */ +static APR_INLINE void * +alloc_block(apr_pool_t *pool, alloc_block_t **free_list) +{ + alloc_block_t *block; + if (*free_list == NULL) + block = apr_palloc(pool, sizeof(*block)); + else + { + block = *free_list; + *free_list = block->next_free; + } + return block; +} + +/* Return the block back to the free list. */ +static APR_INLINE void +free_block(void *ptr, alloc_block_t **free_list) +{ + /* Wrapper functions take care of type safety. */ + alloc_block_t *const block = ptr; + block->next_free = *free_list; + *free_list = block; +} + + + +/* ==================================================================== */ +/* Mapping offsets in the target streem to txdelta ops. */ + +typedef struct offset_index_t +{ + int length; + apr_size_t *offs; +} offset_index_t; + +/* Create an index mapping target stream offsets to delta ops in + WINDOW. Allocate from POOL. */ + +static offset_index_t * +create_offset_index(const svn_txdelta_window_t *window, apr_pool_t *pool) +{ + offset_index_t *ndx = apr_palloc(pool, sizeof(*ndx)); + apr_size_t offset = 0; + int i; + + ndx->length = window->num_ops; + ndx->offs = apr_palloc(pool, (ndx->length + 1) * sizeof(*ndx->offs)); + + for (i = 0; i < ndx->length; ++i) + { + ndx->offs[i] = offset; + offset += window->ops[i].length; + } + ndx->offs[ndx->length] = offset; + + return ndx; +} + +/* Find the index of the delta op thet defines that data at OFFSET in + NDX. HINT is an arbitrary positin within NDX and doesn't even need + to be valid. To effectively speed up the search, use the last result + as hint because most lookups come as a sequence of decreasing values + for OFFSET and they concentrate on the lower end of the array. */ + +static apr_size_t +search_offset_index(const offset_index_t *ndx, + apr_size_t offset, + apr_size_t hint) +{ + apr_size_t lo, hi, op; + + assert(offset < ndx->offs[ndx->length]); + + lo = 0; + hi = ndx->length; + + /* If we got a valid hint, use it to reduce the range to cover. + Note that this will only be useful if either the hint is a + hit (i.e. equals the desired result) or narrows the range + length by a factor larger than 2. */ + + if (hint < hi) + { + if (offset < ndx->offs[hint]) + hi = hint; + else if (offset < ndx->offs[hint+1]) + return hint; + else + lo = hint+1; + } + + /* ordinary binary search */ + + for (op = (lo + hi)/2; lo != hi; op = (lo + hi)/2) + { + if (offset < ndx->offs[op]) + hi = op; + else + lo = ++op; + } + + --lo; + assert(ndx->offs[lo] <= offset && offset < ndx->offs[lo + 1]); + return lo; +} + + + +/* ==================================================================== */ +/* Mapping ranges in the source stream to ranges in the composed delta. */ + +/* The range index tree. */ +typedef struct range_index_t +{ + range_index_node_t *tree; + alloc_block_t *free_list; + apr_pool_t *pool; +} range_index_t; + +/* Create a range index tree. Allocate from POOL. */ +static range_index_t * +create_range_index(apr_pool_t *pool) +{ + range_index_t *ndx = apr_palloc(pool, sizeof(*ndx)); + ndx->tree = NULL; + ndx->pool = pool; + ndx->free_list = NULL; + return ndx; +} + +/* Allocate a node for the range index tree. */ +static range_index_node_t * +alloc_range_index_node(range_index_t *ndx, + apr_size_t offset, + apr_size_t limit, + apr_size_t target_offset) +{ + range_index_node_t *const node = alloc_block(ndx->pool, &ndx->free_list); + node->offset = offset; + node->limit = limit; + node->target_offset = target_offset; + node->left = node->right = NULL; + node->prev = node->next = NULL; + return node; +} + +/* Free a node from the range index tree. */ +static void +free_range_index_node(range_index_t *ndx, range_index_node_t *node) +{ + if (node->next) + node->next->prev = node->prev; + if (node->prev) + node->prev->next = node->next; + free_block(node, &ndx->free_list); +} + + +/* Splay the index tree, using OFFSET as the key. */ + +static void +splay_range_index(apr_size_t offset, range_index_t *ndx) +{ + range_index_node_t *tree = ndx->tree; + range_index_node_t scratch_node; + range_index_node_t *left, *right; + + if (tree == NULL) + return; + + scratch_node.left = scratch_node.right = NULL; + left = right = &scratch_node; + + for (;;) + { + if (offset < tree->offset) + { + if (tree->left != NULL + && offset < tree->left->offset) + { + /* Right rotation */ + range_index_node_t *const node = tree->left; + tree->left = node->right; + node->right = tree; + tree = node; + } + if (tree->left == NULL) + break; + + /* Remember the right subtree */ + right->left = tree; + right = tree; + tree = tree->left; + } + else if (offset > tree->offset) + { + if (tree->right != NULL + && offset > tree->right->offset) + { + /* Left rotation */ + range_index_node_t *const node = tree->right; + tree->right = node->left; + node->left = tree; + tree = node; + } + if (tree->right == NULL) + break; + + /* Remember the left subtree */ + left->right = tree; + left = tree; + tree = tree->right; + } + else + break; + } + + /* Link in the left and right subtrees */ + left->right = tree->left; + right->left = tree->right; + tree->left = scratch_node.right; + tree->right = scratch_node.left; + + /* The basic top-down splay is finished, but we may still need to + turn the tree around. What we want is to put the node with the + largest offset where node->offset <= offset at the top of the + tree, so that we can insert the new data (or search for existing + ranges) to the right of the root. This makes cleaning up the + tree after an insert much simpler, and -- incidentally -- makes + the whole range index magic work. */ + if (offset < tree->offset && tree->left != NULL) + { + if (tree->left->right == NULL) + { + /* A single right rotation is enough. */ + range_index_node_t *const node = tree->left; + tree->left = node->right; /* Which is always NULL. */ + node->right = tree; + tree = node; + } + else + { + /* Slide down to the rightmost node in the left subtree. */ + range_index_node_t **nodep = &tree->left; + while ((*nodep)->right != NULL) + nodep = &(*nodep)->right; + + /* Now move this node to root in one giant promotion. */ + right = tree; + left = tree->left; + tree = *nodep; + *nodep = tree->left; + right->left = tree->right; /* Which is always NULL, too. */ + tree->left = left; + tree->right = right; + } + } + + /* Sanity check ... */ + assert((offset >= tree->offset) + || ((tree->left == NULL) + && (tree->prev == NULL))); + ndx->tree = tree; +} + + +/* Remove all ranges from NDX that fall into the root's range. To + keep the range index as small as possible, we must also remove + nodes that don't fall into the new range, but have become redundant + because the new range overlaps the beginning of the next range. + Like this: + + new-range: |-----------------| + range-1: |-----------------| + range-2: |--------------------| + + Before new-range was inserted, range-1 and range-2 were both + necessary. Now the union of new-range and range-2 completely covers + range-1, which has become redundant now. + + FIXME: But, of course, there's a catch. range-1 must still remain + in the tree if we want to optimize the number of target copy ops in + the case were a copy falls within range-1, but starts before + range-2 and ends after new-range. */ + +static void +delete_subtree(range_index_t *ndx, range_index_node_t *node) +{ + if (node != NULL) + { + delete_subtree(ndx, node->left); + delete_subtree(ndx, node->right); + free_range_index_node(ndx, node); + } +} + +static void +clean_tree(range_index_t *ndx, apr_size_t limit) +{ + apr_size_t top_offset = limit + 1; + range_index_node_t **nodep = &ndx->tree->right; + while (*nodep != NULL) + { + range_index_node_t *const node = *nodep; + apr_size_t const offset = + (node->right != NULL && node->right->offset < top_offset + ? node->right->offset + : top_offset); + + if (node->limit <= limit + || (node->offset < limit && offset < limit)) + { + *nodep = node->right; + node->right = NULL; + delete_subtree(ndx, node); + } + else + { + top_offset = node->offset; + nodep = &node->left; + } + } +} + + +/* Add a range [OFFSET, LIMIT) into NDX. If NDX already contains a + range that encloses [OFFSET, LIMIT), do nothing. Otherwise, remove + all ranges from NDX that are superseded by the new range. + NOTE: The range index must be splayed to OFFSET! */ + +static void +insert_range(apr_size_t offset, apr_size_t limit, apr_size_t target_offset, + range_index_t *ndx) +{ + range_index_node_t *node = NULL; + + if (ndx->tree == NULL) + { + node = alloc_range_index_node(ndx, offset, limit, target_offset); + ndx->tree = node; + } + else + { + if (offset == ndx->tree->offset + && limit > ndx->tree->limit) + { + ndx->tree->limit = limit; + ndx->tree->target_offset = target_offset; + clean_tree(ndx, limit); + } + else if (offset > ndx->tree->offset + && limit > ndx->tree->limit) + { + /* We have to make the same sort of checks as clean_tree() + does for superseded ranges. Have to merge these someday. */ + + const svn_boolean_t insert_range_p = + (!ndx->tree->next + || ndx->tree->limit < ndx->tree->next->offset + || limit > ndx->tree->next->limit); + + if (insert_range_p) + { + /* Again, we have to check if the new node and the one + to the left of the root override root's range. */ + if (ndx->tree->prev && ndx->tree->prev->limit > offset) + { + /* Replace the data in the splayed node. */ + ndx->tree->offset = offset; + ndx->tree->limit = limit; + ndx->tree->target_offset = target_offset; + } + else + { + /* Insert the range to the right of the splayed node. */ + node = alloc_range_index_node(ndx, offset, limit, + target_offset); + if ((node->next = ndx->tree->next) != NULL) + node->next->prev = node; + ndx->tree->next = node; + node->prev = ndx->tree; + + node->right = ndx->tree->right; + ndx->tree->right = NULL; + node->left = ndx->tree; + ndx->tree = node; + } + clean_tree(ndx, limit); + } + else + /* Ignore the range */; + } + else if (offset < ndx->tree->offset) + { + assert(ndx->tree->left == NULL); + + /* Insert the range left of the splayed node */ + node = alloc_range_index_node(ndx, offset, limit, target_offset); + node->left = node->prev = NULL; + node->right = node->next = ndx->tree; + ndx->tree = node->next->prev = node; + clean_tree(ndx, limit); + } + else + /* Ignore the range */; + } +} + + + +/* ==================================================================== */ +/* Juggling with lists of ranges. */ + +/* Allocate a node and add it to the range list. LIST is the head of + the range list, TAIL is the last node in the list. NDX holds the + freelist; OFFSET, LIMIT and KIND are node data. */ +static range_list_node_t * +alloc_range_list(range_list_node_t **list, + range_list_node_t **tail, + range_index_t *ndx, + enum range_kind kind, + apr_size_t offset, + apr_size_t limit, + apr_size_t target_offset) +{ + range_list_node_t *const node = alloc_block(ndx->pool, &ndx->free_list); + node->kind = kind; + node->offset = offset; + node->limit = limit; + node->target_offset = target_offset; + if (*list == NULL) + { + node->prev = node->next = NULL; + *list = *tail = node; + } + else + { + node->prev = *tail; + node->next = NULL; + (*tail)->next = node; + *tail = node; + } + return *list; +} + +/* Free a range list. LIST is the head of the list, NDX holds the freelist. */ +static void +free_range_list(range_list_node_t *list, range_index_t *ndx) +{ + while (list) + { + range_list_node_t *const node = list; + list = node->next; + free_block(node, &ndx->free_list); + } +} + + +/* Based on the data in NDX, build a list of ranges that cover + [OFFSET, LIMIT) in the "virtual" source data. + NOTE: The range index must be splayed to OFFSET! */ + +static range_list_node_t * +build_range_list(apr_size_t offset, apr_size_t limit, range_index_t *ndx) +{ + range_list_node_t *range_list = NULL; + range_list_node_t *last_range = NULL; + range_index_node_t *node = ndx->tree; + + while (offset < limit) + { + if (node == NULL) + return alloc_range_list(&range_list, &last_range, ndx, + range_from_source, + offset, limit, 0); + + if (offset < node->offset) + { + if (limit <= node->offset) + return alloc_range_list(&range_list, &last_range, ndx, + range_from_source, + offset, limit, 0); + else + { + alloc_range_list(&range_list, &last_range, ndx, + range_from_source, + offset, node->offset, 0); + offset = node->offset; + } + } + else + { + /* TODO: (Potential optimization) Investigate if it would + make sense to forbid short range_from_target lengths + (this comment originally said "shorter than, say, + VD_KEY_SIZE (see vdelta.c)", but Subversion no longer + uses vdelta). */ + + if (offset >= node->limit) + node = node->next; + else + { + const apr_size_t target_offset = + offset - node->offset + node->target_offset; + + if (limit <= node->limit) + return alloc_range_list(&range_list, &last_range, ndx, + range_from_target, + offset, limit, target_offset); + else + { + alloc_range_list(&range_list, &last_range, ndx, + range_from_target, + offset, node->limit, target_offset); + offset = node->limit; + node = node->next; + } + } + } + } + + /* A range's offset isn't smaller than its limit? Impossible! */ + SVN_ERR_MALFUNCTION_NO_RETURN(); +} + + +/* Copy the instructions from WINDOW that define the range [OFFSET, + LIMIT) in WINDOW's target stream to TARGET_OFFSET in the window + represented by BUILD_BATON. HINT is a position in the instructions + array that helps finding the position for OFFSET. A safe default + is 0. Use NDX to find the instructions in WINDOW. Allocate space + in BUILD_BATON from POOL. */ + +static void +copy_source_ops(apr_size_t offset, apr_size_t limit, + apr_size_t target_offset, + apr_size_t hint, + svn_txdelta__ops_baton_t *build_baton, + const svn_txdelta_window_t *window, + const offset_index_t *ndx, + apr_pool_t *pool) +{ + apr_size_t op_ndx = search_offset_index(ndx, offset, hint); + for (;; ++op_ndx) + { + const svn_txdelta_op_t *const op = &window->ops[op_ndx]; + const apr_size_t *const off = &ndx->offs[op_ndx]; + apr_size_t fix_offset; + apr_size_t fix_limit; + + if (off[0] >= limit) + break; + + fix_offset = (offset > off[0] ? offset - off[0] : 0); + fix_limit = (off[1] > limit ? off[1] - limit : 0); + + /* It would be extremely weird if the fixed-up op had zero length. */ + assert(fix_offset + fix_limit < op->length); + + if (op->action_code != svn_txdelta_target) + { + /* Delta ops that don't depend on the virtual target can be + copied to the composite unchanged. */ + const char *const new_data = (op->action_code == svn_txdelta_new + ? (window->new_data->data + + op->offset + fix_offset) + : NULL); + + svn_txdelta__insert_op(build_baton, op->action_code, + op->offset + fix_offset, + op->length - fix_offset - fix_limit, + new_data, pool); + } + else + { + /* The source of a target copy must start before the current + offset in the (virtual) target stream. */ + assert(op->offset < off[0]); + + if (op->offset + op->length - fix_limit <= off[0]) + { + /* The recursion _must_ end, otherwise the delta has + circular references, and that is not possible. */ + copy_source_ops(op->offset + fix_offset, + op->offset + op->length - fix_limit, + target_offset, + op_ndx, + build_baton, window, ndx, pool); + } + else + { + /* This is an overlapping target copy. + The idea here is to transpose the pattern, then generate + another overlapping copy. */ + const apr_size_t ptn_length = off[0] - op->offset; + const apr_size_t ptn_overlap = fix_offset % ptn_length; + apr_size_t fix_off = fix_offset; + apr_size_t tgt_off = target_offset; + assert(ptn_length > ptn_overlap); + + /* ### FIXME: ptn_overlap is unsigned, so the if() condition + below is always true! Either it should be '> 0', or the + code block should be unconditional. See also r842362. */ + if (ptn_overlap >= 0) + { + /* Issue second subrange in the pattern. */ + const apr_size_t length = + MIN(op->length - fix_off - fix_limit, + ptn_length - ptn_overlap); + copy_source_ops(op->offset + ptn_overlap, + op->offset + ptn_overlap + length, + tgt_off, + op_ndx, + build_baton, window, ndx, pool); + fix_off += length; + tgt_off += length; + } + + assert(fix_off + fix_limit <= op->length); + if (ptn_overlap > 0 + && fix_off + fix_limit < op->length) + { + /* Issue the first subrange in the pattern. */ + const apr_size_t length = + MIN(op->length - fix_off - fix_limit, ptn_overlap); + copy_source_ops(op->offset, + op->offset + length, + tgt_off, + op_ndx, + build_baton, window, ndx, pool); + fix_off += length; + tgt_off += length; + } + + assert(fix_off + fix_limit <= op->length); + if (fix_off + fix_limit < op->length) + { + /* Now multiply the pattern */ + svn_txdelta__insert_op(build_baton, svn_txdelta_target, + tgt_off - ptn_length, + op->length - fix_off - fix_limit, + NULL, pool); + } + } + } + + /* Adjust the target offset for the next op in the list. */ + target_offset += op->length - fix_offset - fix_limit; + } +} + + + +/* ==================================================================== */ +/* Bringing it all together. */ + + +svn_txdelta_window_t * +svn_txdelta_compose_windows(const svn_txdelta_window_t *window_A, + const svn_txdelta_window_t *window_B, + apr_pool_t *pool) +{ + svn_txdelta__ops_baton_t build_baton = { 0 }; + svn_txdelta_window_t *composite; + apr_pool_t *subpool = svn_pool_create(pool); + offset_index_t *offset_index = create_offset_index(window_A, subpool); + range_index_t *range_index = create_range_index(subpool); + apr_size_t target_offset = 0; + int i; + + /* Read the description of the delta composition algorithm in + notes/fs-improvements.txt before going any further. + You have been warned. */ + build_baton.new_data = svn_stringbuf_create("", pool); + for (i = 0; i < window_B->num_ops; ++i) + { + const svn_txdelta_op_t *const op = &window_B->ops[i]; + if (op->action_code != svn_txdelta_source) + { + /* Delta ops that don't depend on the source can be copied + to the composite unchanged. */ + const char *const new_data = + (op->action_code == svn_txdelta_new + ? window_B->new_data->data + op->offset + : NULL); + svn_txdelta__insert_op(&build_baton, op->action_code, + op->offset, op->length, + new_data, pool); + } + else + { + /* NOTE: Remember that `offset' and `limit' refer to + positions in window_B's _source_ stream, which is the + same as window_A's _target_ stream! */ + const apr_size_t offset = op->offset; + const apr_size_t limit = op->offset + op->length; + range_list_node_t *range_list, *range; + apr_size_t tgt_off = target_offset; + + splay_range_index(offset, range_index); + range_list = build_range_list(offset, limit, range_index); + + for (range = range_list; range; range = range->next) + { + if (range->kind == range_from_target) + svn_txdelta__insert_op(&build_baton, svn_txdelta_target, + range->target_offset, + range->limit - range->offset, + NULL, pool); + else + copy_source_ops(range->offset, range->limit, tgt_off, 0, + &build_baton, window_A, offset_index, + pool); + + tgt_off += range->limit - range->offset; + } + assert(tgt_off == target_offset + op->length); + + free_range_list(range_list, range_index); + insert_range(offset, limit, target_offset, range_index); + } + + /* Remember the new offset in the would-be target stream. */ + target_offset += op->length; + } + + svn_pool_destroy(subpool); + + composite = svn_txdelta__make_window(&build_baton, pool); + composite->sview_offset = window_A->sview_offset; + composite->sview_len = window_A->sview_len; + composite->tview_len = window_B->tview_len; + return composite; +} diff --git a/subversion/libsvn_delta/debug_editor.c b/subversion/libsvn_delta/debug_editor.c new file mode 100644 index 0000000..a25b1b4 --- /dev/null +++ b/subversion/libsvn_delta/debug_editor.c @@ -0,0 +1,433 @@ +/* + * debug_editor.c : An editor that writes the operations it does to stderr. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "debug_editor.h" + +struct edit_baton +{ + const svn_delta_editor_t *wrapped_editor; + void *wrapped_edit_baton; + + int indent_level; + + svn_stream_t *out; +}; + +struct dir_baton +{ + void *edit_baton; + void *wrapped_dir_baton; +}; + +struct file_baton +{ + void *edit_baton; + void *wrapped_file_baton; +}; + +static svn_error_t * +write_indent(struct edit_baton *eb, apr_pool_t *pool) +{ + int i; + + for (i = 0; i < eb->indent_level; ++i) + SVN_ERR(svn_stream_printf(eb->out, pool, " ")); + + return SVN_NO_ERROR; +} + +static svn_error_t * +set_target_revision(void *edit_baton, + svn_revnum_t target_revision, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, "set_target_revision : %ld\n", + target_revision)); + + return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton, + target_revision, + pool); +} + +static svn_error_t * +open_root(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **root_baton) +{ + struct edit_baton *eb = edit_baton; + struct dir_baton *dir_baton = apr_palloc(pool, sizeof(*dir_baton)); + + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, "open_root : %ld\n", + base_revision)); + eb->indent_level++; + + SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton, + base_revision, + pool, + &dir_baton->wrapped_dir_baton)); + + dir_baton->edit_baton = edit_baton; + + *root_baton = dir_baton; + + return SVN_NO_ERROR; +} + +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t base_revision, + void *parent_baton, + apr_pool_t *pool) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, "delete_entry : %s:%ld\n", + path, base_revision)); + + return eb->wrapped_editor->delete_entry(path, + base_revision, + pb->wrapped_dir_baton, + pool); +} + +static svn_error_t * +add_directory(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct dir_baton *b = apr_palloc(pool, sizeof(*b)); + + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, + "add_directory : '%s' [from '%s':%ld]\n", + path, copyfrom_path, copyfrom_revision)); + eb->indent_level++; + + SVN_ERR(eb->wrapped_editor->add_directory(path, + pb->wrapped_dir_baton, + copyfrom_path, + copyfrom_revision, + pool, + &b->wrapped_dir_baton)); + + b->edit_baton = eb; + *child_baton = b; + + return SVN_NO_ERROR; +} + +static svn_error_t * +open_directory(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct dir_baton *db = apr_palloc(pool, sizeof(*db)); + + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, "open_directory : '%s':%ld\n", + path, base_revision)); + eb->indent_level++; + + SVN_ERR(eb->wrapped_editor->open_directory(path, + pb->wrapped_dir_baton, + base_revision, + pool, + &db->wrapped_dir_baton)); + + db->edit_baton = eb; + *child_baton = db; + + return SVN_NO_ERROR; +} + +static svn_error_t * +add_file(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct file_baton *fb = apr_palloc(pool, sizeof(*fb)); + + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, + "add_file : '%s' [from '%s':%ld]\n", + path, copyfrom_path, copyfrom_revision)); + + eb->indent_level++; + + SVN_ERR(eb->wrapped_editor->add_file(path, + pb->wrapped_dir_baton, + copyfrom_path, + copyfrom_revision, + pool, + &fb->wrapped_file_baton)); + + fb->edit_baton = eb; + *file_baton = fb; + + return SVN_NO_ERROR; +} + +static svn_error_t * +open_file(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **file_baton) +{ + struct dir_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct file_baton *fb = apr_palloc(pool, sizeof(*fb)); + + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, "open_file : '%s':%ld\n", + path, base_revision)); + + eb->indent_level++; + + SVN_ERR(eb->wrapped_editor->open_file(path, + pb->wrapped_dir_baton, + base_revision, + pool, + &fb->wrapped_file_baton)); + + fb->edit_baton = eb; + *file_baton = fb; + + return SVN_NO_ERROR; +} + +static svn_error_t * +apply_textdelta(void *file_baton, + const char *base_checksum, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct file_baton *fb = file_baton; + struct edit_baton *eb = fb->edit_baton; + + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, "apply_textdelta : %s\n", + base_checksum)); + + SVN_ERR(eb->wrapped_editor->apply_textdelta(fb->wrapped_file_baton, + base_checksum, + pool, + handler, + handler_baton)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +close_file(void *file_baton, + const char *text_checksum, + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + struct edit_baton *eb = fb->edit_baton; + + eb->indent_level--; + + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, "close_file : %s\n", + text_checksum)); + + SVN_ERR(eb->wrapped_editor->close_file(fb->wrapped_file_baton, + text_checksum, pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +absent_file(const char *path, + void *file_baton, + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + struct edit_baton *eb = fb->edit_baton; + + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, "absent_file : %s\n", path)); + + SVN_ERR(eb->wrapped_editor->absent_file(path, fb->wrapped_file_baton, + pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +close_directory(void *dir_baton, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + struct edit_baton *eb = db->edit_baton; + + eb->indent_level--; + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, "close_directory\n")); + + SVN_ERR(eb->wrapped_editor->close_directory(db->wrapped_dir_baton, + pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +absent_directory(const char *path, + void *dir_baton, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + struct edit_baton *eb = db->edit_baton; + + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, "absent_directory : %s\n", + path)); + + SVN_ERR(eb->wrapped_editor->absent_directory(path, db->wrapped_dir_baton, + pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +change_file_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct file_baton *fb = file_baton; + struct edit_baton *eb = fb->edit_baton; + + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, "change_file_prop : %s\n", + name)); + + SVN_ERR(eb->wrapped_editor->change_file_prop(fb->wrapped_file_baton, + name, + value, + pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +change_dir_prop(void *dir_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct dir_baton *db = dir_baton; + struct edit_baton *eb = db->edit_baton; + + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, "change_dir_prop : %s\n", name)); + + SVN_ERR(eb->wrapped_editor->change_dir_prop(db->wrapped_dir_baton, + name, + value, + pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +close_edit(void *edit_baton, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + + SVN_ERR(write_indent(eb, pool)); + SVN_ERR(svn_stream_printf(eb->out, pool, "close_edit\n")); + + SVN_ERR(eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool)); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_delta__get_debug_editor(const svn_delta_editor_t **editor, + void **edit_baton, + const svn_delta_editor_t *wrapped_editor, + void *wrapped_edit_baton, + apr_pool_t *pool) +{ + svn_delta_editor_t *tree_editor = svn_delta_default_editor(pool); + struct edit_baton *eb = apr_palloc(pool, sizeof(*eb)); + apr_file_t *errfp; + svn_stream_t *out; + + apr_status_t apr_err = apr_file_open_stderr(&errfp, pool); + if (apr_err) + return svn_error_wrap_apr(apr_err, "Problem opening stderr"); + + out = svn_stream_from_aprfile2(errfp, TRUE, pool); + + tree_editor->set_target_revision = set_target_revision; + tree_editor->open_root = open_root; + tree_editor->delete_entry = delete_entry; + tree_editor->add_directory = add_directory; + tree_editor->open_directory = open_directory; + tree_editor->change_dir_prop = change_dir_prop; + tree_editor->close_directory = close_directory; + tree_editor->absent_directory = absent_directory; + tree_editor->add_file = add_file; + tree_editor->open_file = open_file; + tree_editor->apply_textdelta = apply_textdelta; + tree_editor->change_file_prop = change_file_prop; + tree_editor->close_file = close_file; + tree_editor->absent_file = absent_file; + tree_editor->close_edit = close_edit; + + eb->wrapped_editor = wrapped_editor; + eb->wrapped_edit_baton = wrapped_edit_baton; + eb->out = out; + eb->indent_level = 0; + + *editor = tree_editor; + *edit_baton = eb; + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_delta/debug_editor.h b/subversion/libsvn_delta/debug_editor.h new file mode 100644 index 0000000..2b031af --- /dev/null +++ b/subversion/libsvn_delta/debug_editor.h @@ -0,0 +1,49 @@ +/* + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#ifndef SVN_DEBUG_EDITOR_H +#define SVN_DEBUG_EDITOR_H + +#include "svn_delta.h" + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + +/* Return a debug editor that wraps @a wrapped_editor. + * + * The debug editor simply prints an indication of what callbacks are being + * called to @c stderr, and is only intended for use in debugging subversion + * editors. + */ +svn_error_t * +svn_delta__get_debug_editor(const svn_delta_editor_t **editor, + void **edit_baton, + const svn_delta_editor_t *wrapped_editor, + void *wrapped_baton, + apr_pool_t *pool); + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_DEBUG_EDITOR_H */ diff --git a/subversion/libsvn_delta/default_editor.c b/subversion/libsvn_delta/default_editor.c new file mode 100644 index 0000000..2f1c973 --- /dev/null +++ b/subversion/libsvn_delta/default_editor.c @@ -0,0 +1,161 @@ +/* + * default_editor.c -- provide a basic svn_delta_editor_t + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#include <apr_pools.h> +#include <apr_strings.h> + +#include "svn_types.h" +#include "svn_delta.h" + + +static svn_error_t * +set_target_revision(void *edit_baton, + svn_revnum_t target_revision, + apr_pool_t *pool) +{ + return SVN_NO_ERROR; +} +static svn_error_t * +add_item(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *pool, + void **baton) +{ + *baton = NULL; + return SVN_NO_ERROR; +} + + +static svn_error_t * +single_baton_func(void *baton, + apr_pool_t *pool) +{ + return SVN_NO_ERROR; +} + + +static svn_error_t * +absent_xxx_func(const char *path, + void *baton, + apr_pool_t *pool) +{ + return SVN_NO_ERROR; +} + + +static svn_error_t * +open_root(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *dir_pool, + void **root_baton) +{ + *root_baton = NULL; + return SVN_NO_ERROR; +} + +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t revision, + void *parent_baton, + apr_pool_t *pool) +{ + return SVN_NO_ERROR; +} + +static svn_error_t * +open_item(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **baton) +{ + *baton = NULL; + return SVN_NO_ERROR; +} + +static svn_error_t * +change_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + return SVN_NO_ERROR; +} + +svn_error_t *svn_delta_noop_window_handler(svn_txdelta_window_t *window, + void *baton) +{ + return SVN_NO_ERROR; +} + +static svn_error_t * +apply_textdelta(void *file_baton, + const char *base_checksum, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + *handler = svn_delta_noop_window_handler; + *handler_baton = NULL; + return SVN_NO_ERROR; +} + + +static svn_error_t * +close_file(void *file_baton, + const char *text_checksum, + apr_pool_t *pool) +{ + return SVN_NO_ERROR; +} + + + +static const svn_delta_editor_t default_editor = +{ + set_target_revision, + open_root, + delete_entry, + add_item, + open_item, + change_prop, + single_baton_func, + absent_xxx_func, + add_item, + open_item, + apply_textdelta, + change_prop, + close_file, + absent_xxx_func, + single_baton_func, + single_baton_func +}; + +svn_delta_editor_t * +svn_delta_default_editor(apr_pool_t *pool) +{ + return apr_pmemdup(pool, &default_editor, sizeof(default_editor)); +} diff --git a/subversion/libsvn_delta/delta.h b/subversion/libsvn_delta/delta.h new file mode 100644 index 0000000..6ca1d97 --- /dev/null +++ b/subversion/libsvn_delta/delta.h @@ -0,0 +1,90 @@ +/* + * delta.h: private delta library things + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +/* ==================================================================== */ + + +#include <apr_pools.h> +#include <apr_hash.h> + +#include "svn_delta.h" + +#ifndef SVN_LIBSVN_DELTA_H +#define SVN_LIBSVN_DELTA_H + + +#ifdef __cplusplus +extern "C" { +#endif /* __cplusplus */ + + +/* Private interface for text deltas. */ + +/* The standard size of one svndiff window. */ + +#define SVN_DELTA_WINDOW_SIZE 102400 + + +/* Context/baton for building an operation sequence. */ + +typedef struct svn_txdelta__ops_baton_t { + int num_ops; /* current number of ops */ + int src_ops; /* current number of source copy ops */ + int ops_size; /* number of ops allocated */ + svn_txdelta_op_t *ops; /* the operations */ + + svn_stringbuf_t *new_data; /* any new data used by the operations */ +} svn_txdelta__ops_baton_t; + + +/* Insert a delta op into the delta window being built via BUILD_BATON. If + OPCODE is svn_delta_new, bytes from NEW_DATA are copied into the window + data and OFFSET is ignored. Otherwise NEW_DATA is ignored. All + allocations are performed in POOL. */ +void svn_txdelta__insert_op(svn_txdelta__ops_baton_t *build_baton, + enum svn_delta_action opcode, + apr_size_t offset, + apr_size_t length, + const char *new_data, + apr_pool_t *pool); + + +/* Allocate a delta window from POOL. */ +svn_txdelta_window_t * +svn_txdelta__make_window(const svn_txdelta__ops_baton_t *build_baton, + apr_pool_t *pool); + + +/* Create xdelta window data. Allocate temporary data from POOL. */ +void svn_txdelta__xdelta(svn_txdelta__ops_baton_t *build_baton, + const char *start, + apr_size_t source_len, + apr_size_t target_len, + apr_pool_t *pool); + + +#ifdef __cplusplus +} +#endif /* __cplusplus */ + +#endif /* SVN_LIBSVN_DELTA_H */ diff --git a/subversion/libsvn_delta/depth_filter_editor.c b/subversion/libsvn_delta/depth_filter_editor.c new file mode 100644 index 0000000..b161979 --- /dev/null +++ b/subversion/libsvn_delta/depth_filter_editor.c @@ -0,0 +1,485 @@ +/* + * depth_filter_editor.c -- provide a svn_delta_editor_t which wraps + * another editor and provides depth-based filtering + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include "svn_delta.h" + + +/*** Batons, and the Toys That Create Them ***/ + +struct edit_baton +{ + /* The editor/baton we're wrapping. */ + const svn_delta_editor_t *wrapped_editor; + void *wrapped_edit_baton; + + /* The depth to which we are limiting the drive of the wrapped + editor/baton. */ + svn_depth_t requested_depth; + + /* Does the wrapped editor/baton have an explicit target (in the + anchor/target sense of the word)? */ + svn_boolean_t has_target; +}; + +struct node_baton +{ + /* TRUE iff this node was filtered out -- that is, not allowed to + pass through to the wrapped editor -- by virtue of not appearing + at a depth in the tree that was "inside" the requested depth. Of + course, any children of this node will be deeper still, and so + will also be filtered out for the same reason. */ + svn_boolean_t filtered; + + /* Pointer to the edit_baton. */ + void *edit_baton; + + /* The real node baton we're wrapping. May be a directory or file + baton; we don't care. */ + void *wrapped_baton; + + /* The calculated depth (in terms of counted, stacked, integral + deepnesses) of this node. If the node is a directory, this value + is 1 greater than the value of the same on its parent directory; + if a file, it is equal to its parent directory's depth value. */ + int dir_depth; +}; + +/* Allocate and return a new node_baton structure, populated via the + the input to this helper function. */ +static struct node_baton * +make_node_baton(void *edit_baton, + svn_boolean_t filtered, + int dir_depth, + apr_pool_t *pool) +{ + struct node_baton *b = apr_palloc(pool, sizeof(*b)); + b->edit_baton = edit_baton; + b->wrapped_baton = NULL; + b->filtered = filtered; + b->dir_depth = dir_depth; + return b; +} + +/* Return TRUE iff changes to immediate children of the directory + identified by PB, when those children are of node kind KIND, are + allowed by the requested depth which this editor is trying to + preserve. EB is the edit baton. */ +static svn_boolean_t +okay_to_edit(struct edit_baton *eb, + struct node_baton *pb, + svn_node_kind_t kind) +{ + int effective_depth; + + /* If we've already filter out the parent directory, we necessarily + are filtering out its children, too. */ + if (pb->filtered) + return FALSE; + + /* Calculate the effective depth of the parent directory. + + NOTE: "Depth" in this sense is not the same as the Subversion + magic depth keywords. Here, we're talking about a literal, + integral, stacked depth of directories. + + The root of the edit is generally depth=1, subdirectories thereof + depth=2, and so on. But if we have an edit target -- which means + that the real target of the edit operation isn't the root + directory, but is instead some immediate child thereof -- we have + to adjust our calculated effected depth such that the target + itself is depth=1 (as are its siblings, which we trust aren't + present in the edit at all), immediate subdirectories thereof are + depth=2, and so on. + */ + effective_depth = pb->dir_depth - (eb->has_target ? 1 : 0); + switch (eb->requested_depth) + { + case svn_depth_empty: + return (effective_depth <= 0); + case svn_depth_files: + return ((effective_depth <= 0) + || (kind == svn_node_file && effective_depth == 1)); + case svn_depth_immediates: + return (effective_depth <= 1); + case svn_depth_unknown: + case svn_depth_exclude: + case svn_depth_infinity: + /* Shouldn't reach; see svn_delta_depth_filter_editor() */ + default: + SVN_ERR_MALFUNCTION_NO_RETURN(); + } +} + + +/*** Editor Functions ***/ + +static svn_error_t * +set_target_revision(void *edit_baton, + svn_revnum_t target_revision, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + + /* Nothing depth-y to filter here. */ + return eb->wrapped_editor->set_target_revision(eb->wrapped_edit_baton, + target_revision, pool); +} + +static svn_error_t * +open_root(void *edit_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **root_baton) +{ + struct edit_baton *eb = edit_baton; + struct node_baton *b; + + /* The root node always gets through cleanly. */ + b = make_node_baton(edit_baton, FALSE, 1, pool); + SVN_ERR(eb->wrapped_editor->open_root(eb->wrapped_edit_baton, base_revision, + pool, &b->wrapped_baton)); + + *root_baton = b; + return SVN_NO_ERROR; +} + +static svn_error_t * +delete_entry(const char *path, + svn_revnum_t base_revision, + void *parent_baton, + apr_pool_t *pool) +{ + struct node_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + + /* ### FIXME: We don't know the type of the entry, which ordinarily + doesn't matter, but is a key (*the* key, in fact) distinction + between depth "files" and depths "immediates". If the server is + telling us to delete a subdirectory and our requested depth was + "immediates", that's fine; if our requested depth was "files", + though, this deletion shouldn't survive filtering. For now, + we'll claim to our helper function that the to-be-deleted thing + is a file because that's the conservative route to take. */ + if (okay_to_edit(eb, pb, svn_node_file)) + SVN_ERR(eb->wrapped_editor->delete_entry(path, base_revision, + pb->wrapped_baton, pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +add_directory(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct node_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct node_baton *b = NULL; + + /* Check for sufficient depth. */ + if (okay_to_edit(eb, pb, svn_node_dir)) + { + b = make_node_baton(eb, FALSE, pb->dir_depth + 1, pool); + SVN_ERR(eb->wrapped_editor->add_directory(path, pb->wrapped_baton, + copyfrom_path, + copyfrom_revision, + pool, &b->wrapped_baton)); + } + else + { + b = make_node_baton(eb, TRUE, pb->dir_depth + 1, pool); + } + + *child_baton = b; + return SVN_NO_ERROR; +} + +static svn_error_t * +open_directory(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct node_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct node_baton *b; + + /* Check for sufficient depth. */ + if (okay_to_edit(eb, pb, svn_node_dir)) + { + b = make_node_baton(eb, FALSE, pb->dir_depth + 1, pool); + SVN_ERR(eb->wrapped_editor->open_directory(path, pb->wrapped_baton, + base_revision, pool, + &b->wrapped_baton)); + } + else + { + b = make_node_baton(eb, TRUE, pb->dir_depth + 1, pool); + } + + *child_baton = b; + return SVN_NO_ERROR; +} + +static svn_error_t * +add_file(const char *path, + void *parent_baton, + const char *copyfrom_path, + svn_revnum_t copyfrom_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct node_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct node_baton *b = NULL; + + /* Check for sufficient depth. */ + if (okay_to_edit(eb, pb, svn_node_file)) + { + b = make_node_baton(eb, FALSE, pb->dir_depth, pool); + SVN_ERR(eb->wrapped_editor->add_file(path, pb->wrapped_baton, + copyfrom_path, copyfrom_revision, + pool, &b->wrapped_baton)); + } + else + { + b = make_node_baton(eb, TRUE, pb->dir_depth, pool); + } + + *child_baton = b; + return SVN_NO_ERROR; +} + +static svn_error_t * +open_file(const char *path, + void *parent_baton, + svn_revnum_t base_revision, + apr_pool_t *pool, + void **child_baton) +{ + struct node_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + struct node_baton *b; + + /* Check for sufficient depth. */ + if (okay_to_edit(eb, pb, svn_node_file)) + { + b = make_node_baton(eb, FALSE, pb->dir_depth, pool); + SVN_ERR(eb->wrapped_editor->open_file(path, pb->wrapped_baton, + base_revision, pool, + &b->wrapped_baton)); + } + else + { + b = make_node_baton(eb, TRUE, pb->dir_depth, pool); + } + + *child_baton = b; + return SVN_NO_ERROR; +} + +static svn_error_t * +apply_textdelta(void *file_baton, + const char *base_checksum, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + struct node_baton *fb = file_baton; + struct edit_baton *eb = fb->edit_baton; + + /* For filtered files, we just consume the textdelta. */ + if (fb->filtered) + { + *handler = svn_delta_noop_window_handler; + *handler_baton = NULL; + } + else + { + SVN_ERR(eb->wrapped_editor->apply_textdelta(fb->wrapped_baton, + base_checksum, pool, + handler, handler_baton)); + } + return SVN_NO_ERROR; +} + +static svn_error_t * +close_file(void *file_baton, + const char *text_checksum, + apr_pool_t *pool) +{ + struct node_baton *fb = file_baton; + struct edit_baton *eb = fb->edit_baton; + + /* Don't close filtered files. */ + if (! fb->filtered) + SVN_ERR(eb->wrapped_editor->close_file(fb->wrapped_baton, + text_checksum, pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +absent_file(const char *path, + void *parent_baton, + apr_pool_t *pool) +{ + struct node_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + + /* Don't report absent items in filtered directories. */ + if (! pb->filtered) + SVN_ERR(eb->wrapped_editor->absent_file(path, pb->wrapped_baton, pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +close_directory(void *dir_baton, + apr_pool_t *pool) +{ + struct node_baton *db = dir_baton; + struct edit_baton *eb = db->edit_baton; + + /* Don't close filtered directories. */ + if (! db->filtered) + SVN_ERR(eb->wrapped_editor->close_directory(db->wrapped_baton, pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +absent_directory(const char *path, + void *parent_baton, + apr_pool_t *pool) +{ + struct node_baton *pb = parent_baton; + struct edit_baton *eb = pb->edit_baton; + + /* Don't report absent items in filtered directories. */ + if (! pb->filtered) + SVN_ERR(eb->wrapped_editor->absent_directory(path, pb->wrapped_baton, + pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +change_file_prop(void *file_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct node_baton *fb = file_baton; + struct edit_baton *eb = fb->edit_baton; + + /* No propchanges on filtered files. */ + if (! fb->filtered) + SVN_ERR(eb->wrapped_editor->change_file_prop(fb->wrapped_baton, + name, value, pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +change_dir_prop(void *dir_baton, + const char *name, + const svn_string_t *value, + apr_pool_t *pool) +{ + struct node_baton *db = dir_baton; + struct edit_baton *eb = db->edit_baton; + + /* No propchanges on filtered nodes. */ + if (! db->filtered) + SVN_ERR(eb->wrapped_editor->change_dir_prop(db->wrapped_baton, + name, value, pool)); + + return SVN_NO_ERROR; +} + +static svn_error_t * +close_edit(void *edit_baton, + apr_pool_t *pool) +{ + struct edit_baton *eb = edit_baton; + return eb->wrapped_editor->close_edit(eb->wrapped_edit_baton, pool); +} + +svn_error_t * +svn_delta_depth_filter_editor(const svn_delta_editor_t **editor, + void **edit_baton, + const svn_delta_editor_t *wrapped_editor, + void *wrapped_edit_baton, + svn_depth_t requested_depth, + svn_boolean_t has_target, + apr_pool_t *pool) +{ + svn_delta_editor_t *depth_filter_editor; + struct edit_baton *eb; + + /* Easy out: if the caller wants infinite depth, there's nothing to + filter, so just return the editor we were supposed to wrap. And + if they've asked for an unknown depth, we can't possibly know + what that means, so why bother? */ + if ((requested_depth == svn_depth_unknown) + || (requested_depth == svn_depth_infinity)) + { + *editor = wrapped_editor; + *edit_baton = wrapped_edit_baton; + return SVN_NO_ERROR; + } + + depth_filter_editor = svn_delta_default_editor(pool); + depth_filter_editor->set_target_revision = set_target_revision; + depth_filter_editor->open_root = open_root; + depth_filter_editor->delete_entry = delete_entry; + depth_filter_editor->add_directory = add_directory; + depth_filter_editor->open_directory = open_directory; + depth_filter_editor->change_dir_prop = change_dir_prop; + depth_filter_editor->close_directory = close_directory; + depth_filter_editor->absent_directory = absent_directory; + depth_filter_editor->add_file = add_file; + depth_filter_editor->open_file = open_file; + depth_filter_editor->apply_textdelta = apply_textdelta; + depth_filter_editor->change_file_prop = change_file_prop; + depth_filter_editor->close_file = close_file; + depth_filter_editor->absent_file = absent_file; + depth_filter_editor->close_edit = close_edit; + + eb = apr_palloc(pool, sizeof(*eb)); + eb->wrapped_editor = wrapped_editor; + eb->wrapped_edit_baton = wrapped_edit_baton; + eb->has_target = has_target; + eb->requested_depth = requested_depth; + + *editor = depth_filter_editor; + *edit_baton = eb; + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_delta/editor.c b/subversion/libsvn_delta/editor.c new file mode 100644 index 0000000..b4bef5e --- /dev/null +++ b/subversion/libsvn_delta/editor.c @@ -0,0 +1,446 @@ +/* + * editor.c : editing trees of versioned resources + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + +#include <apr_pools.h> + +#include "svn_types.h" +#include "svn_error.h" +#include "svn_pools.h" + +#include "private/svn_editor.h" + + +struct svn_editor_t +{ + void *baton; + + /* Standard cancellation function. Called before each callback. */ + svn_cancel_func_t cancel_func; + void *cancel_baton; + + /* Our callback functions match that of the set-many structure, so + just use that. */ + svn_editor_cb_many_t funcs; + + /* This pool is used as the scratch_pool for all callbacks. */ + apr_pool_t *scratch_pool; +}; + + +svn_error_t * +svn_editor_create(svn_editor_t **editor, + void *editor_baton, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + *editor = apr_pcalloc(result_pool, sizeof(**editor)); + + (*editor)->baton = editor_baton; + (*editor)->cancel_func = cancel_func; + (*editor)->cancel_baton = cancel_baton; + (*editor)->scratch_pool = svn_pool_create(result_pool); + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_editor_setcb_add_directory(svn_editor_t *editor, + svn_editor_cb_add_directory_t callback, + apr_pool_t *scratch_pool) +{ + editor->funcs.cb_add_directory = callback; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_editor_setcb_add_file(svn_editor_t *editor, + svn_editor_cb_add_file_t callback, + apr_pool_t *scratch_pool) +{ + editor->funcs.cb_add_file = callback; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_editor_setcb_add_symlink(svn_editor_t *editor, + svn_editor_cb_add_symlink_t callback, + apr_pool_t *scratch_pool) +{ + editor->funcs.cb_add_symlink = callback; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_editor_setcb_add_absent(svn_editor_t *editor, + svn_editor_cb_add_absent_t callback, + apr_pool_t *scratch_pool) +{ + editor->funcs.cb_add_absent = callback; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_editor_setcb_set_props(svn_editor_t *editor, + svn_editor_cb_set_props_t callback, + apr_pool_t *scratch_pool) +{ + editor->funcs.cb_set_props = callback; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_editor_setcb_set_text(svn_editor_t *editor, + svn_editor_cb_set_text_t callback, + apr_pool_t *scratch_pool) +{ + editor->funcs.cb_set_text = callback; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_editor_setcb_set_target(svn_editor_t *editor, + svn_editor_cb_set_target_t callback, + apr_pool_t *scratch_pool) +{ + editor->funcs.cb_set_target = callback; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_editor_setcb_delete(svn_editor_t *editor, + svn_editor_cb_delete_t callback, + apr_pool_t *scratch_pool) +{ + editor->funcs.cb_delete = callback; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_editor_setcb_copy(svn_editor_t *editor, + svn_editor_cb_copy_t callback, + apr_pool_t *scratch_pool) +{ + editor->funcs.cb_copy = callback; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_editor_setcb_move(svn_editor_t *editor, + svn_editor_cb_move_t callback, + apr_pool_t *scratch_pool) +{ + editor->funcs.cb_move = callback; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_editor_setcb_complete(svn_editor_t *editor, + svn_editor_cb_complete_t callback, + apr_pool_t *scratch_pool) +{ + editor->funcs.cb_complete = callback; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_editor_setcb_abort(svn_editor_t *editor, + svn_editor_cb_abort_t callback, + apr_pool_t *scratch_pool) +{ + editor->funcs.cb_abort = callback; + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_editor_setcb_many(svn_editor_t *editor, + const svn_editor_cb_many_t *many, + apr_pool_t *scratch_pool) +{ +#define COPY_CALLBACK(NAME) if (many->NAME) editor->funcs.NAME = many->NAME + + COPY_CALLBACK(cb_add_directory); + COPY_CALLBACK(cb_add_file); + COPY_CALLBACK(cb_add_symlink); + COPY_CALLBACK(cb_add_absent); + COPY_CALLBACK(cb_set_props); + COPY_CALLBACK(cb_set_text); + COPY_CALLBACK(cb_set_target); + COPY_CALLBACK(cb_delete); + COPY_CALLBACK(cb_copy); + COPY_CALLBACK(cb_move); + COPY_CALLBACK(cb_complete); + COPY_CALLBACK(cb_abort); + +#undef COPY_CALLBACK + + return SVN_NO_ERROR; +} + + +svn_error_t * +svn_editor_add_directory(svn_editor_t *editor, + const char *relpath, + const apr_array_header_t *children, + apr_hash_t *props, + svn_revnum_t replaces_rev) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(editor->funcs.cb_add_directory != NULL); + + if (editor->cancel_func) + SVN_ERR(editor->cancel_func(editor->cancel_baton)); + + err = editor->funcs.cb_add_directory(editor->baton, relpath, children, + props, replaces_rev, + editor->scratch_pool); + svn_pool_clear(editor->scratch_pool); + return err; +} + + +svn_error_t * +svn_editor_add_file(svn_editor_t *editor, + const char *relpath, + apr_hash_t *props, + svn_revnum_t replaces_rev) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(editor->funcs.cb_add_file != NULL); + + if (editor->cancel_func) + SVN_ERR(editor->cancel_func(editor->cancel_baton)); + + err = editor->funcs.cb_add_file(editor->baton, relpath, props, + replaces_rev, editor->scratch_pool); + svn_pool_clear(editor->scratch_pool); + return err; +} + + +svn_error_t * +svn_editor_add_symlink(svn_editor_t *editor, + const char *relpath, + const char *target, + apr_hash_t *props, + svn_revnum_t replaces_rev) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(editor->funcs.cb_add_symlink != NULL); + + if (editor->cancel_func) + SVN_ERR(editor->cancel_func(editor->cancel_baton)); + + err = editor->funcs.cb_add_symlink(editor->baton, relpath, target, props, + replaces_rev, editor->scratch_pool); + svn_pool_clear(editor->scratch_pool); + return err; +} + + +svn_error_t * +svn_editor_add_absent(svn_editor_t *editor, + const char *relpath, + svn_node_kind_t kind, + svn_revnum_t replaces_rev) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(editor->funcs.cb_add_absent != NULL); + + if (editor->cancel_func) + SVN_ERR(editor->cancel_func(editor->cancel_baton)); + + err = editor->funcs.cb_add_absent(editor->baton, relpath, kind, + replaces_rev, editor->scratch_pool); + svn_pool_clear(editor->scratch_pool); + return err; +} + + +svn_error_t * +svn_editor_set_props(svn_editor_t *editor, + const char *relpath, + svn_revnum_t revision, + apr_hash_t *props, + svn_boolean_t complete) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(editor->funcs.cb_set_props != NULL); + + if (editor->cancel_func) + SVN_ERR(editor->cancel_func(editor->cancel_baton)); + + err = editor->funcs.cb_set_props(editor->baton, relpath, revision, props, + complete, editor->scratch_pool); + svn_pool_clear(editor->scratch_pool); + return err; +} + + +svn_error_t * +svn_editor_set_text(svn_editor_t *editor, + const char *relpath, + svn_revnum_t revision, + const svn_checksum_t *checksum, + svn_stream_t *contents) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(editor->funcs.cb_set_text != NULL); + + if (editor->cancel_func) + SVN_ERR(editor->cancel_func(editor->cancel_baton)); + + err = editor->funcs.cb_set_text(editor->baton, relpath, revision, + checksum, contents, editor->scratch_pool); + svn_pool_clear(editor->scratch_pool); + return err; +} + + +svn_error_t * +svn_editor_set_target(svn_editor_t *editor, + const char *relpath, + svn_revnum_t revision, + const char *target) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(editor->funcs.cb_set_target != NULL); + + if (editor->cancel_func) + SVN_ERR(editor->cancel_func(editor->cancel_baton)); + + err = editor->funcs.cb_set_target(editor->baton, relpath, revision, + target, editor->scratch_pool); + svn_pool_clear(editor->scratch_pool); + return err; +} + + +svn_error_t * +svn_editor_delete(svn_editor_t *editor, + const char *relpath, + svn_revnum_t revision) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(editor->funcs.cb_delete != NULL); + + if (editor->cancel_func) + SVN_ERR(editor->cancel_func(editor->cancel_baton)); + + err = editor->funcs.cb_delete(editor->baton, relpath, revision, + editor->scratch_pool); + svn_pool_clear(editor->scratch_pool); + return err; +} + + +svn_error_t * +svn_editor_copy(svn_editor_t *editor, + const char *src_relpath, + svn_revnum_t src_revision, + const char *dst_relpath, + svn_revnum_t replaces_rev) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(editor->funcs.cb_copy != NULL); + + if (editor->cancel_func) + SVN_ERR(editor->cancel_func(editor->cancel_baton)); + + err = editor->funcs.cb_copy(editor->baton, src_relpath, src_revision, + dst_relpath, replaces_rev, + editor->scratch_pool); + svn_pool_clear(editor->scratch_pool); + return err; +} + + +svn_error_t * +svn_editor_move(svn_editor_t *editor, + const char *src_relpath, + svn_revnum_t src_revision, + const char *dst_relpath, + svn_revnum_t replaces_rev) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(editor->funcs.cb_move != NULL); + + if (editor->cancel_func) + SVN_ERR(editor->cancel_func(editor->cancel_baton)); + + err = editor->funcs.cb_move(editor->baton, src_relpath, src_revision, + dst_relpath, replaces_rev, + editor->scratch_pool); + svn_pool_clear(editor->scratch_pool); + return err; +} + + +svn_error_t * +svn_editor_complete(svn_editor_t *editor) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(editor->funcs.cb_complete != NULL); + + err = editor->funcs.cb_complete(editor->baton, editor->scratch_pool); + svn_pool_clear(editor->scratch_pool); + return err; +} + + +svn_error_t * +svn_editor_abort(svn_editor_t *editor) +{ + svn_error_t *err; + + SVN_ERR_ASSERT(editor->funcs.cb_abort != NULL); + + err = editor->funcs.cb_abort(editor->baton, editor->scratch_pool); + svn_pool_clear(editor->scratch_pool); + return err; +} diff --git a/subversion/libsvn_delta/path_driver.c b/subversion/libsvn_delta/path_driver.c new file mode 100644 index 0000000..13fb571 --- /dev/null +++ b/subversion/libsvn_delta/path_driver.c @@ -0,0 +1,292 @@ +/* + * path_driver.c -- drive an editor across a set of paths + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#include <apr_pools.h> +#include <apr_strings.h> + +#include "svn_types.h" +#include "svn_delta.h" +#include "svn_pools.h" +#include "svn_dirent_uri.h" +#include "svn_path.h" +#include "svn_sorts.h" +#include "private/svn_fspath.h" + + +/*** Helper functions. ***/ + +typedef struct dir_stack_t +{ + void *dir_baton; /* the dir baton. */ + apr_pool_t *pool; /* the pool associated with the dir baton. */ + +} dir_stack_t; + + +/* Call EDITOR's open_directory() function with the PATH and REVISION + * arguments, and then add the resulting dir baton to the dir baton + * stack. + */ +static svn_error_t * +open_dir(apr_array_header_t *db_stack, + const svn_delta_editor_t *editor, + const char *path, + svn_revnum_t revision, + apr_pool_t *pool) +{ + void *parent_db, *db; + dir_stack_t *item; + apr_pool_t *subpool; + + /* Assert that we are in a stable state. */ + SVN_ERR_ASSERT(db_stack && db_stack->nelts); + + /* Get the parent dir baton. */ + item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, void *); + parent_db = item->dir_baton; + + /* Call the EDITOR's open_directory function to get a new directory + baton. */ + subpool = svn_pool_create(pool); + SVN_ERR(editor->open_directory(path, parent_db, revision, subpool, &db)); + + /* Now add the dir baton to the stack. */ + item = apr_pcalloc(subpool, sizeof(*item)); + item->dir_baton = db; + item->pool = subpool; + APR_ARRAY_PUSH(db_stack, dir_stack_t *) = item; + + return SVN_NO_ERROR; +} + + +/* Pop a directory from the dir baton stack and update the stack + * pointer. + * + * This function calls the EDITOR's close_directory() function. + */ +static svn_error_t * +pop_stack(apr_array_header_t *db_stack, + const svn_delta_editor_t *editor) +{ + dir_stack_t *item; + + /* Assert that we are in a stable state. */ + SVN_ERR_ASSERT(db_stack && db_stack->nelts); + + /* Close the most recent directory pushed to the stack. */ + item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, dir_stack_t *); + (void) apr_array_pop(db_stack); + SVN_ERR(editor->close_directory(item->dir_baton, item->pool)); + svn_pool_destroy(item->pool); + + return SVN_NO_ERROR; +} + + +/* Count the number of path components in PATH. */ +static int +count_components(const char *path) +{ + int count = 1; + const char *instance = path; + + if ((strlen(path) == 1) && (path[0] == '/')) + return 0; + + do + { + instance++; + instance = strchr(instance, '/'); + if (instance) + count++; + } + while (instance); + + return count; +} + + + +/*** Public interfaces ***/ +svn_error_t * +svn_delta_path_driver(const svn_delta_editor_t *editor, + void *edit_baton, + svn_revnum_t revision, + const apr_array_header_t *paths, + svn_delta_path_driver_cb_func_t callback_func, + void *callback_baton, + apr_pool_t *pool) +{ + apr_array_header_t *db_stack = apr_array_make(pool, 4, sizeof(void *)); + const char *last_path = NULL; + int i = 0; + void *parent_db = NULL, *db = NULL; + const char *path; + apr_pool_t *subpool, *iterpool; + dir_stack_t *item; + + /* Do nothing if there are no paths. */ + if (! paths->nelts) + return SVN_NO_ERROR; + + subpool = svn_pool_create(pool); + iterpool = svn_pool_create(pool); + item = apr_pcalloc(subpool, sizeof(*item)); + + /* Sort the paths in a depth-first directory-ish order. */ + qsort(paths->elts, paths->nelts, paths->elt_size, svn_sort_compare_paths); + + /* If the root of the edit is also a target path, we want to call + the callback function to let the user open the root directory and + do what needs to be done. Otherwise, we'll do the open_root() + ourselves. */ + path = APR_ARRAY_IDX(paths, 0, const char *); + if (svn_path_is_empty(path)) + { + SVN_ERR(callback_func(&db, NULL, callback_baton, path, subpool)); + last_path = path; + i++; + } + else + { + SVN_ERR(editor->open_root(edit_baton, revision, subpool, &db)); + } + item->pool = subpool; + item->dir_baton = db; + APR_ARRAY_PUSH(db_stack, void *) = item; + + /* Now, loop over the commit items, traversing the URL tree and + driving the editor. */ + for (; i < paths->nelts; i++) + { + const char *pdir, *bname; + const char *common = ""; + size_t common_len; + + /* Clear the iteration pool. */ + svn_pool_clear(iterpool); + + /* Get the next path. */ + path = APR_ARRAY_IDX(paths, i, const char *); + + /*** Step A - Find the common ancestor of the last path and the + current one. For the first iteration, this is just the + empty string. ***/ + if (i > 0) + common = (last_path[0] == '/') + ? svn_fspath__get_longest_ancestor(last_path, path, iterpool) + : svn_relpath_get_longest_ancestor(last_path, path, iterpool); + common_len = strlen(common); + + /*** Step B - Close any directories between the last path and + the new common ancestor, if any need to be closed. + Sometimes there is nothing to do here (like, for the first + iteration, or when the last path was an ancestor of the + current one). ***/ + if ((i > 0) && (strlen(last_path) > common_len)) + { + const char *rel = last_path + (common_len ? (common_len + 1) : 0); + int count = count_components(rel); + while (count--) + { + SVN_ERR(pop_stack(db_stack, editor)); + } + } + + /*** Step C - Open any directories between the common ancestor + and the parent of the current path. ***/ + if (*path == '/') + svn_fspath__split(&pdir, &bname, path, iterpool); + else + svn_relpath_split(&pdir, &bname, path, iterpool); + if (strlen(pdir) > common_len) + { + const char *piece = pdir + common_len + 1; + + while (1) + { + const char *rel = pdir; + + /* Find the first separator. */ + piece = strchr(piece, '/'); + + /* Calculate REL as the portion of PDIR up to (but not + including) the location to which PIECE is pointing. */ + if (piece) + rel = apr_pstrmemdup(iterpool, pdir, piece - pdir); + + /* Open the subdirectory. */ + SVN_ERR(open_dir(db_stack, editor, rel, revision, pool)); + + /* If we found a '/', advance our PIECE pointer to + character just after that '/'. Otherwise, we're + done. */ + if (piece) + piece++; + else + break; + } + } + + /*** Step D - Tell our caller to handle the current path. ***/ + item = APR_ARRAY_IDX(db_stack, db_stack->nelts - 1, void *); + parent_db = item->dir_baton; + subpool = svn_pool_create(pool); + SVN_ERR(callback_func(&db, parent_db, callback_baton, path, subpool)); + if (db) + { + item = apr_pcalloc(subpool, sizeof(*item)); + item->dir_baton = db; + item->pool = subpool; + APR_ARRAY_PUSH(db_stack, void *) = item; + } + else + { + svn_pool_destroy(subpool); + } + + /*** Step E - Save our state for the next iteration. If our + caller opened or added PATH as a directory, that becomes + our LAST_PATH. Otherwise, we use PATH's parent + directory. ***/ + + /* NOTE: The variable LAST_PATH needs to outlive the loop. */ + if (db) + last_path = path; /* lives in a pool outside our control. */ + else + last_path = apr_pstrdup(pool, pdir); /* duping into POOL. */ + } + + /* Destroy the iteration subpool. */ + svn_pool_destroy(iterpool); + + /* Close down any remaining open directory batons. */ + while (db_stack->nelts) + { + SVN_ERR(pop_stack(db_stack, editor)); + } + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_delta/svndiff.c b/subversion/libsvn_delta/svndiff.c new file mode 100644 index 0000000..0beb1b2 --- /dev/null +++ b/subversion/libsvn_delta/svndiff.c @@ -0,0 +1,976 @@ +/* + * svndiff.c -- Encoding and decoding svndiff-format deltas. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#include <assert.h> +#include <string.h> +#include "svn_delta.h" +#include "svn_io.h" +#include "delta.h" +#include "svn_pools.h" +#include "svn_private_config.h" +#include <zlib.h> + +/* The zlib compressBound function was not exported until 1.2.0. */ +#if ZLIB_VERNUM >= 0x1200 +#define svnCompressBound(LEN) compressBound(LEN) +#else +#define svnCompressBound(LEN) ((LEN) + ((LEN) >> 12) + ((LEN) >> 14) + 11) +#endif + +/* For svndiff1, address/instruction/new data under this size will not + be compressed using zlib as a secondary compressor. */ +#define MIN_COMPRESS_SIZE 512 + +/* ----- Text delta to svndiff ----- */ + +/* We make one of these and get it passed back to us in calls to the + window handler. We only use it to record the write function and + baton passed to svn_txdelta_to_svndiff3(). */ +struct encoder_baton { + svn_stream_t *output; + svn_boolean_t header_done; + int version; + int compression_level; + apr_pool_t *pool; +}; + +/* This is at least as big as the largest size of an integer that + encode_int can generate; it is sufficient for creating buffers for + it to write into. This assumes that integers are at most 64 bits, + and so 10 bytes (with 7 bits of information each) are sufficient to + represent them. */ +#define MAX_ENCODED_INT_LEN 10 +/* This is at least as big as the largest size for a single instruction. */ +#define MAX_INSTRUCTION_LEN (2*MAX_ENCODED_INT_LEN+1) +/* This is at least as big as the largest possible instructions + section: in theory, the instructions could be SVN_DELTA_WINDOW_SIZE + 1-byte copy-from-source instructions (though this is very unlikely). */ +#define MAX_INSTRUCTION_SECTION_LEN (SVN_DELTA_WINDOW_SIZE*MAX_INSTRUCTION_LEN) + +/* Encode VAL into the buffer P using the variable-length svndiff + integer format. Return the incremented value of P after the + encoded bytes have been written. P must point to a buffer of size + at least MAX_ENCODED_INT_LEN. + + This encoding uses the high bit of each byte as a continuation bit + and the other seven bits as data bits. High-order data bits are + encoded first, followed by lower-order bits, so the value can be + reconstructed by concatenating the data bits from left to right and + interpreting the result as a binary number. Examples (brackets + denote byte boundaries, spaces are for clarity only): + + 1 encodes as [0 0000001] + 33 encodes as [0 0100001] + 129 encodes as [1 0000001] [0 0000001] + 2000 encodes as [1 0001111] [0 1010000] +*/ +static unsigned char * +encode_int(unsigned char *p, svn_filesize_t val) +{ + int n; + svn_filesize_t v; + unsigned char cont; + + SVN_ERR_ASSERT_NO_RETURN(val >= 0); + + /* Figure out how many bytes we'll need. */ + v = val >> 7; + n = 1; + while (v > 0) + { + v = v >> 7; + n++; + } + + SVN_ERR_ASSERT_NO_RETURN(n <= MAX_ENCODED_INT_LEN); + + /* Encode the remaining bytes; n is always the number of bytes + coming after the one we're encoding. */ + while (--n >= 0) + { + cont = ((n > 0) ? 0x1 : 0x0) << 7; + *p++ = (unsigned char)(((val >> (n * 7)) & 0x7f) | cont); + } + + return p; +} + + +/* Append an encoded integer to a string. */ +static void +append_encoded_int(svn_stringbuf_t *header, svn_filesize_t val) +{ + unsigned char buf[MAX_ENCODED_INT_LEN], *p; + + p = encode_int(buf, val); + svn_stringbuf_appendbytes(header, (const char *)buf, p - buf); +} + +/* If IN is a string that is >= MIN_COMPRESS_SIZE, zlib compress it and + place the result in OUT, with an integer prepended specifying the + original size. If IN is < MIN_COMPRESS_SIZE, or if the compressed + version of IN was no smaller than the original IN, OUT will be a copy + of IN with the size prepended as an integer. */ +static svn_error_t * +zlib_encode(const char *data, + apr_size_t len, + svn_stringbuf_t *out, + int compression_level) +{ + unsigned long endlen; + apr_size_t intlen; + + append_encoded_int(out, len); + intlen = out->len; + + /* Compression initialization overhead is considered to large for + short buffers. Also, if we don't actually want to compress data, + ZLIB will produce an output no shorter than the input. Hence, + the DATA would directly appended to OUT, so we can do that directly + without calling ZLIB before. */ + if ( (len < MIN_COMPRESS_SIZE) + || (compression_level == SVN_DELTA_COMPRESSION_LEVEL_NONE)) + { + svn_stringbuf_appendbytes(out, data, len); + } + else + { + svn_stringbuf_ensure(out, svnCompressBound(len) + intlen); + endlen = out->blocksize; + + if (compress2((unsigned char *)out->data + intlen, &endlen, + (const unsigned char *)data, len, + compression_level) != Z_OK) + return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA, + NULL, + _("Compression of svndiff data failed")); + + /* Compression didn't help :(, just append the original text */ + if (endlen >= len) + { + svn_stringbuf_appendbytes(out, data, len); + return SVN_NO_ERROR; + } + out->len = endlen + intlen; + } + return SVN_NO_ERROR; +} + +static svn_error_t * +window_handler(svn_txdelta_window_t *window, void *baton) +{ + struct encoder_baton *eb = baton; + apr_pool_t *pool = svn_pool_create(eb->pool); + svn_stringbuf_t *instructions = svn_stringbuf_create("", pool); + svn_stringbuf_t *i1 = svn_stringbuf_create("", pool); + svn_stringbuf_t *header = svn_stringbuf_create("", pool); + const svn_string_t *newdata; + unsigned char ibuf[MAX_INSTRUCTION_LEN], *ip; + const svn_txdelta_op_t *op; + apr_size_t len; + + /* Make sure we write the header. */ + if (eb->header_done == FALSE) + { + 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); + } + + /* Encode the instructions. */ + for (op = window->ops; op < window->ops + window->num_ops; op++) + { + /* Encode the action code and length. */ + ip = ibuf; + switch (op->action_code) + { + case svn_txdelta_source: *ip = 0; break; + case svn_txdelta_target: *ip = (0x1 << 6); break; + case svn_txdelta_new: *ip = (0x2 << 6); break; + } + if (op->length >> 6 == 0) + *ip++ |= op->length; + else + ip = encode_int(ip + 1, op->length); + if (op->action_code != svn_txdelta_new) + ip = encode_int(ip, op->offset); + svn_stringbuf_appendbytes(instructions, (const char *)ibuf, ip - ibuf); + } + + /* Encode the header. */ + 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) + { + SVN_ERR(zlib_encode(instructions->data, instructions->len, + i1, eb->compression_level)); + instructions = i1; + } + append_encoded_int(header, instructions->len); + if (eb->version == 1) + { + svn_stringbuf_t *temp = svn_stringbuf_create("", pool); + svn_string_t *tempstr = svn_string_create("", pool); + SVN_ERR(zlib_encode(window->new_data->data, window->new_data->len, + temp, eb->compression_level)); + tempstr->data = temp->data; + tempstr->len = temp->len; + newdata = tempstr; + } + else + newdata = window->new_data; + + append_encoded_int(header, newdata->len); + + /* Write out the window. */ + len = header->len; + SVN_ERR(svn_stream_write(eb->output, header->data, &len)); + if (instructions->len > 0) + { + len = instructions->len; + SVN_ERR(svn_stream_write(eb->output, instructions->data, &len)); + } + if (newdata->len > 0) + { + len = newdata->len; + SVN_ERR(svn_stream_write(eb->output, newdata->data, &len)); + } + + svn_pool_destroy(pool); + return SVN_NO_ERROR; +} + +void +svn_txdelta_to_svndiff3(svn_txdelta_window_handler_t *handler, + void **handler_baton, + svn_stream_t *output, + int svndiff_version, + 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->output = output; + eb->header_done = FALSE; + eb->pool = subpool; + eb->version = svndiff_version; + eb->compression_level = compression_level; + + *handler = window_handler; + *handler_baton = eb; +} + +void +svn_txdelta_to_svndiff2(svn_txdelta_window_handler_t *handler, + void **handler_baton, + svn_stream_t *output, + int svndiff_version, + apr_pool_t *pool) +{ + svn_txdelta_to_svndiff3(handler, handler_baton, output, svndiff_version, + SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); +} + +void +svn_txdelta_to_svndiff(svn_stream_t *output, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + svn_txdelta_to_svndiff3(handler, handler_baton, output, 0, + SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool); +} + + +/* ----- svndiff to text delta ----- */ + +/* An svndiff parser object. */ +struct decode_baton +{ + /* Once the svndiff parser has enough data buffered to create a + "window", it passes this window to the caller's consumer routine. */ + svn_txdelta_window_handler_t consumer_func; + void *consumer_baton; + + /* Pool to create subpools from; each developing window will be a + subpool. */ + apr_pool_t *pool; + + /* The current subpool which contains our current window-buffer. */ + apr_pool_t *subpool; + + /* The actual svndiff data buffer, living within subpool. */ + svn_stringbuf_t *buffer; + + /* The offset and size of the last source view, so that we can check + to make sure the next one isn't sliding backwards. */ + svn_filesize_t last_sview_offset; + apr_size_t last_sview_len; + + /* We have to discard four bytes at the beginning for the header. + This field keeps track of how many of those bytes we have read. */ + apr_size_t header_bytes; + + /* Do we want an error to occur when we close the stream that + indicates we didn't send the whole svndiff data? If you plan to + not transmit the whole svndiff data stream, you will want this to + be FALSE. */ + svn_boolean_t error_on_early_close; + + /* svndiff version in use by delta. */ + unsigned char version; +}; + + +/* Decode an svndiff-encoded integer into *VAL and return a pointer to + the byte after the integer. The bytes to be decoded live in the + range [P..END-1]. If these bytes do not contain a whole encoded + integer, return NULL; in this case *VAL is undefined. + + See the comment for encode_int() earlier in this file for more detail on + the encoding format. */ +static const unsigned char * +decode_file_offset(svn_filesize_t *val, + const unsigned char *p, + const unsigned char *end) +{ + svn_filesize_t temp = 0; + + if (p + MAX_ENCODED_INT_LEN < end) + end = p + MAX_ENCODED_INT_LEN; + /* Decode bytes until we're done. */ + while (p < end) + { + /* Don't use svn_filesize_t here, because this might be 64 bits + * on 32 bit targets. Optimizing compilers may or may not be + * able to reduce that to the effective code below. */ + unsigned int c = *p++; + + temp = (temp << 7) | (c & 0x7f); + if (c < 0x80) + { + *val = temp; + return p; + } + } + + return NULL; +} + + +/* Same as above, only decode into a size variable. */ +static const unsigned char * +decode_size(apr_size_t *val, + const unsigned char *p, + const unsigned char *end) +{ + apr_size_t temp = 0; + + if (p + MAX_ENCODED_INT_LEN < end) + end = p + MAX_ENCODED_INT_LEN; + /* Decode bytes until we're done. */ + while (p < end) + { + apr_size_t c = *p++; + + temp = (temp << 7) | (c & 0x7f); + if (c < 0x80) + { + *val = temp; + return p; + } + } + + return NULL; +} + +/* Decode the possibly-zlib compressed string that is in IN, into OUT. + We expect an integer is prepended to IN that specifies the original + size, and that if encoded size == original size, that the remaining + data is not compressed. */ +static svn_error_t * +zlib_decode(svn_stringbuf_t *in, svn_stringbuf_t *out, apr_size_t limit) +{ + apr_size_t len; + char *oldplace = in->data; + + /* First thing in the string is the original length. */ + in->data = (char *)decode_size(&len, (unsigned char *)in->data, + (unsigned char *)in->data+in->len); + if (in->data == NULL) + return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA, NULL, + _("Decompression of svndiff data failed: no size")); + if (len > limit) + return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA, NULL, + _("Decompression of svndiff data failed: " + "size too large")); + /* We need to subtract the size of the encoded original length off the + * still remaining input length. */ + in->len -= (in->data - oldplace); + if (in->len == len) + { + svn_stringbuf_appendstr(out, in); + return SVN_NO_ERROR; + } + else + { + unsigned long zliblen; + + svn_stringbuf_ensure(out, len); + + zliblen = len; + if (uncompress ((unsigned char *)out->data, &zliblen, + (const unsigned char *)in->data, in->len) != Z_OK) + return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA, + NULL, + _("Decompression of svndiff data failed")); + + /* Zlib should not produce something that has a different size than the + original length we stored. */ + if (zliblen != len) + return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA, + NULL, + _("Size of uncompressed data " + "does not match stored original length")); + out->len = zliblen; + } + return SVN_NO_ERROR; +} + +/* Decode an instruction into OP, returning a pointer to the text + after the instruction. Note that if the action code is + svn_txdelta_new, the offset field of *OP will not be set. */ +static const unsigned char * +decode_instruction(svn_txdelta_op_t *op, + const unsigned char *p, + const unsigned char *end) +{ + apr_size_t c; + apr_size_t action; + + if (p == end) + return NULL; + + /* We need this more than once */ + c = *p++; + + /* Decode the instruction selector. */ + action = (c >> 6) & 0x3; + if (action >= 0x3) + return NULL; + + /* This relies on enum svn_delta_action values to match and never to be + redefined. */ + op->action_code = (enum svn_delta_action)(action); + + /* Decode the length and offset. */ + op->length = c & 0x3f; + if (op->length == 0) + { + p = decode_size(&op->length, p, end); + if (p == NULL) + return NULL; + } + if (action != svn_txdelta_new) + { + p = decode_size(&op->offset, p, end); + if (p == NULL) + return NULL; + } + + return p; +} + +/* Count the instructions in the range [P..END-1] and make sure they + are valid for the given window lengths. Return an error if the + instructions are invalid; otherwise set *NINST to the number of + instructions. */ +static svn_error_t * +count_and_verify_instructions(int *ninst, + const unsigned char *p, + const unsigned char *end, + apr_size_t sview_len, + apr_size_t tview_len, + apr_size_t new_len) +{ + int n = 0; + svn_txdelta_op_t op; + apr_size_t tpos = 0, npos = 0; + + while (p < end) + { + p = decode_instruction(&op, p, end); + + /* Detect any malformed operations from the instruction stream. */ + if (p == NULL) + return svn_error_createf + (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, + _("Invalid diff stream: insn %d cannot be decoded"), n); + else if (op.length == 0) + return svn_error_createf + (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, + _("Invalid diff stream: insn %d has length zero"), n); + else if (op.length > tview_len - tpos) + return svn_error_createf + (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, + _("Invalid diff stream: insn %d overflows the target view"), n); + + switch (op.action_code) + { + case svn_txdelta_source: + if (op.length > sview_len - op.offset || + op.offset > sview_len) + return svn_error_createf + (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, + _("Invalid diff stream: " + "[src] insn %d overflows the source view"), n); + break; + case svn_txdelta_target: + if (op.offset >= tpos) + return svn_error_createf + (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, + _("Invalid diff stream: " + "[tgt] insn %d starts beyond the target view position"), n); + break; + case svn_txdelta_new: + if (op.length > new_len - npos) + return svn_error_createf + (SVN_ERR_SVNDIFF_INVALID_OPS, NULL, + _("Invalid diff stream: " + "[new] insn %d overflows the new data section"), n); + npos += op.length; + break; + } + tpos += op.length; + n++; + } + if (tpos != tview_len) + return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL, + _("Delta does not fill the target window")); + if (npos != new_len) + return svn_error_create(SVN_ERR_SVNDIFF_INVALID_OPS, NULL, + _("Delta does not contain enough new data")); + + *ninst = n; + return SVN_NO_ERROR; +} + +/* 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; + the new_data field of *WINDOW will refer directly to memory pointed + to by DATA. */ +static svn_error_t * +decode_window(svn_txdelta_window_t *window, svn_filesize_t sview_offset, + apr_size_t sview_len, apr_size_t tview_len, apr_size_t inslen, + apr_size_t newlen, const unsigned char *data, apr_pool_t *pool, + unsigned int version) +{ + const unsigned char *insend; + int ninst; + apr_size_t npos; + svn_txdelta_op_t *ops, *op; + svn_string_t *new_data = apr_palloc(pool, sizeof(*new_data)); + + window->sview_offset = sview_offset; + window->sview_len = sview_len; + window->tview_len = tview_len; + + insend = data + inslen; + + if (version == 1) + { + svn_stringbuf_t *instin, *ndin; + svn_stringbuf_t *instout, *ndout; + + instin = svn_stringbuf_ncreate((const char *)data, insend - data, pool); + instout = svn_stringbuf_create("", pool); + SVN_ERR(zlib_decode(instin, instout, MAX_INSTRUCTION_SECTION_LEN)); + + ndin = svn_stringbuf_ncreate((const char *)insend, newlen, pool); + ndout = svn_stringbuf_create("", pool); + SVN_ERR(zlib_decode(ndin, ndout, SVN_DELTA_WINDOW_SIZE)); + + 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; + } + else + { + new_data->data = (const char *) insend; + new_data->len = newlen; + } + + /* Count the instructions and make sure they are all valid. */ + SVN_ERR(count_and_verify_instructions(&ninst, data, insend, + sview_len, tview_len, newlen)); + + /* Allocate a buffer for the instructions and decode them. */ + ops = apr_palloc(pool, ninst * sizeof(*ops)); + npos = 0; + window->src_ops = 0; + for (op = ops; op < ops + ninst; op++) + { + data = decode_instruction(op, data, insend); + if (op->action_code == svn_txdelta_source) + ++window->src_ops; + else if (op->action_code == svn_txdelta_new) + { + op->offset = npos; + npos += op->length; + } + } + SVN_ERR_ASSERT(data == insend); + + window->ops = ops; + window->num_ops = ninst; + window->new_data = new_data; + + return SVN_NO_ERROR; +} + +static svn_error_t * +write_handler(void *baton, + const char *buffer, + apr_size_t *len) +{ + 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. */ + if (db->header_bytes < 4) + { + apr_size_t nheader = 4 - db->header_bytes; + if (nheader > buflen) + nheader = buflen; + if (memcmp(buffer, "SVN\0" + db->header_bytes, nheader) == 0) + db->version = 0; + else if (memcmp(buffer, "SVN\1" + db->header_bytes, nheader) == 0) + db->version = 1; + else + return svn_error_create(SVN_ERR_SVNDIFF_INVALID_HEADER, NULL, + _("Svndiff has invalid header")); + buflen -= nheader; + buffer += nheader; + db->header_bytes += nheader; + } + + /* Concatenate the old with the new. */ + svn_stringbuf_appendbytes(db->buffer, buffer, buflen); + + /* We have a buffer of svndiff data that might be good for: + + a) an integral number of windows' worth of data - this is a + trivial case. Make windows from our data and ship them off. + + b) a non-integral number of windows' worth of data - we shall + consume the integral portion of the window data, and then + somewhere in the following loop the decoding of the svndiff + data will run out of stuff to decode, and will simply return + SVN_NO_ERROR, anxiously awaiting more data. + */ + + 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) + return SVN_NO_ERROR; + + p = decode_size(&sview_len, p, end); + if (p == NULL) + return SVN_NO_ERROR; + + p = decode_size(&tview_len, p, end); + if (p == NULL) + return SVN_NO_ERROR; + + p = decode_size(&inslen, p, end); + if (p == NULL) + return SVN_NO_ERROR; + + p = decode_size(&newlen, p, end); + if (p == NULL) + return SVN_NO_ERROR; + + 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 + MAX_ENCODED_INT_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 + || sview_offset + sview_len < 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")); + + /* Wait for more data if we don't have enough bytes for the + whole window. */ + if ((apr_size_t) (end - p) < inslen + 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(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); + + /* 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; + } + + /* NOTREACHED */ +} + + +static svn_error_t * +close_handler(void *baton) +{ + struct decode_baton *db = (struct decode_baton *) baton; + svn_error_t *err; + + /* Make sure that we're at a plausible end of stream, returning an + error if we are expected to do so. */ + if ((db->error_on_early_close) + && (db->header_bytes < 4 || db->buffer->len != 0)) + return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL, + _("Unexpected end of svndiff input")); + + /* Tell the window consumer that we're done, and clean up. */ + err = db->consumer_func(NULL, db->consumer_baton); + svn_pool_destroy(db->pool); + return err; +} + + +svn_stream_t * +svn_txdelta_parse_svndiff(svn_txdelta_window_handler_t handler, + void *handler_baton, + 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("", 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); + svn_stream_set_write(stream, write_handler); + svn_stream_set_close(stream, close_handler); + return stream; +} + + +/* Routines for reading one svndiff window at a time. */ + +/* Read one byte from STREAM into *BYTE. */ +static svn_error_t * +read_one_byte(unsigned char *byte, svn_stream_t *stream) +{ + char c; + apr_size_t len = 1; + + SVN_ERR(svn_stream_read(stream, &c, &len)); + if (len == 0) + return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL, + _("Unexpected end of svndiff input")); + *byte = (unsigned char) c; + return SVN_NO_ERROR; +} + +/* Read and decode one integer from STREAM into *SIZE. */ +static svn_error_t * +read_one_size(apr_size_t *size, svn_stream_t *stream) +{ + unsigned char c; + + *size = 0; + while (1) + { + SVN_ERR(read_one_byte(&c, stream)); + *size = (*size << 7) | (c & 0x7f); + if (!(c & 0x80)) + break; + } + return SVN_NO_ERROR; +} + +/* Read a window header from STREAM and check it for integer overflow. */ +static svn_error_t * +read_window_header(svn_stream_t *stream, svn_filesize_t *sview_offset, + apr_size_t *sview_len, apr_size_t *tview_len, + apr_size_t *inslen, apr_size_t *newlen) +{ + unsigned char c; + + /* Read the source view offset by hand, since it's not an apr_size_t. */ + *sview_offset = 0; + while (1) + { + SVN_ERR(read_one_byte(&c, stream)); + *sview_offset = (*sview_offset << 7) | (c & 0x7f); + if (!(c & 0x80)) + break; + } + + /* Read the four size fields. */ + SVN_ERR(read_one_size(sview_len, stream)); + SVN_ERR(read_one_size(tview_len, stream)); + SVN_ERR(read_one_size(inslen, stream)); + SVN_ERR(read_one_size(newlen, stream)); + + 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 + MAX_ENCODED_INT_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 + || *sview_offset + *sview_len < *sview_offset) + return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL, + _("Svndiff contains corrupt window header")); + + return SVN_NO_ERROR; +} + +svn_error_t * +svn_txdelta_read_svndiff_window(svn_txdelta_window_t **window, + svn_stream_t *stream, + int svndiff_version, + apr_pool_t *pool) +{ + svn_filesize_t sview_offset; + apr_size_t sview_len, tview_len, inslen, newlen, len; + unsigned char *buf; + + SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len, + &inslen, &newlen)); + len = inslen + newlen; + buf = apr_palloc(pool, len); + SVN_ERR(svn_stream_read(stream, (char*)buf, &len)); + if (len < inslen + newlen) + return svn_error_create(SVN_ERR_SVNDIFF_UNEXPECTED_END, NULL, + _("Unexpected end of svndiff input")); + *window = apr_palloc(pool, sizeof(**window)); + return decode_window(*window, sview_offset, sview_len, tview_len, inslen, + newlen, buf, pool, svndiff_version); +} + + +svn_error_t * +svn_txdelta_skip_svndiff_window(apr_file_t *file, + int svndiff_version, + apr_pool_t *pool) +{ + svn_stream_t *stream = svn_stream_from_aprfile2(file, TRUE, pool); + svn_filesize_t sview_offset; + apr_size_t sview_len, tview_len, inslen, newlen; + apr_off_t offset; + + SVN_ERR(read_window_header(stream, &sview_offset, &sview_len, &tview_len, + &inslen, &newlen)); + + offset = inslen + newlen; + return svn_io_file_seek(file, APR_CUR, &offset, pool); +} diff --git a/subversion/libsvn_delta/text_delta.c b/subversion/libsvn_delta/text_delta.c new file mode 100644 index 0000000..ba35ba6 --- /dev/null +++ b/subversion/libsvn_delta/text_delta.c @@ -0,0 +1,916 @@ +/* + * text-delta.c -- Internal text delta representation + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#include <assert.h> +#include <string.h> + +#include <apr_general.h> /* for APR_INLINE */ +#include <apr_md5.h> /* for, um...MD5 stuff */ + +#include "svn_delta.h" +#include "svn_io.h" +#include "svn_pools.h" +#include "svn_checksum.h" + +#include "delta.h" + + +/* Text delta stream descriptor. */ + +struct svn_txdelta_stream_t { + /* Copied from parameters to svn_txdelta_stream_create. */ + void *baton; + svn_txdelta_next_window_fn_t next_window; + svn_txdelta_md5_digest_fn_t md5_digest; +}; + +/* Delta stream baton. */ +struct txdelta_baton { + /* These are copied from parameters passed to svn_txdelta. */ + svn_stream_t *source; + svn_stream_t *target; + + /* Private data */ + svn_boolean_t more_source; /* FALSE if source stream hit EOF. */ + svn_boolean_t more; /* TRUE if there are more data in the pool. */ + svn_filesize_t pos; /* Offset of next read in source file. */ + char *buf; /* Buffer for input data. */ + + svn_checksum_ctx_t *context; /* Context for computing the checksum. */ + svn_checksum_t *checksum; /* If non-NULL, the checksum of TARGET. */ + + apr_pool_t *result_pool; /* For results (e.g. checksum) */ +}; + + +/* Target-push stream descriptor. */ + +struct tpush_baton { + /* These are copied from parameters passed to svn_txdelta_target_push. */ + svn_stream_t *source; + svn_txdelta_window_handler_t wh; + void *whb; + apr_pool_t *pool; + + /* Private data */ + char *buf; + svn_filesize_t source_offset; + apr_size_t source_len; + svn_boolean_t source_done; + apr_size_t target_len; +}; + + +/* Text delta applicator. */ + +struct apply_baton { + /* These are copied from parameters passed to svn_txdelta_apply. */ + svn_stream_t *source; + svn_stream_t *target; + + /* Private data. Between calls, SBUF contains the data from the + * last window's source view, as specified by SBUF_OFFSET and + * SBUF_LEN. The contents of TBUF are not interesting between + * calls. */ + apr_pool_t *pool; /* Pool to allocate data from */ + char *sbuf; /* Source buffer */ + apr_size_t sbuf_size; /* Allocated source buffer space */ + svn_filesize_t sbuf_offset; /* Offset of SBUF data in source stream */ + apr_size_t sbuf_len; /* Length of SBUF data */ + char *tbuf; /* Target buffer */ + apr_size_t tbuf_size; /* Allocated target buffer space */ + + apr_md5_ctx_t md5_context; /* Leads to result_digest below. */ + unsigned char *result_digest; /* MD5 digest of resultant fulltext; + must point to at least APR_MD5_DIGESTSIZE + bytes of storage. */ + + const char *error_info; /* Optional extra info for error returns. */ +}; + + + +svn_txdelta_window_t * +svn_txdelta__make_window(const svn_txdelta__ops_baton_t *build_baton, + apr_pool_t *pool) +{ + svn_txdelta_window_t *window; + svn_string_t *new_data = apr_palloc(pool, sizeof(*new_data)); + + window = apr_palloc(pool, sizeof(*window)); + window->sview_offset = 0; + window->sview_len = 0; + window->tview_len = 0; + + window->num_ops = build_baton->num_ops; + window->src_ops = build_baton->src_ops; + window->ops = build_baton->ops; + + /* just copy the fields over, rather than alloc/copying into a whole new + svn_string_t structure. */ + /* ### would be much nicer if window->new_data were not a ptr... */ + new_data->data = build_baton->new_data->data; + new_data->len = build_baton->new_data->len; + window->new_data = new_data; + + return window; +} + + +/* Compute and return a delta window using the xdelta algorithm on + DATA, which contains SOURCE_LEN bytes of source data and TARGET_LEN + bytes of target data. SOURCE_OFFSET gives the offset of the source + data, and is simply copied into the window's sview_offset field. */ +static svn_txdelta_window_t * +compute_window(const char *data, apr_size_t source_len, apr_size_t target_len, + svn_filesize_t source_offset, apr_pool_t *pool) +{ + svn_txdelta__ops_baton_t build_baton = { 0 }; + svn_txdelta_window_t *window; + + /* Compute the delta operations. */ + build_baton.new_data = svn_stringbuf_create("", pool); + + if (source_len == 0) + svn_txdelta__insert_op(&build_baton, svn_txdelta_new, 0, target_len, data, + pool); + else + svn_txdelta__xdelta(&build_baton, data, source_len, target_len, pool); + + /* Create and return the delta window. */ + window = svn_txdelta__make_window(&build_baton, pool); + window->sview_offset = source_offset; + window->sview_len = source_len; + window->tview_len = target_len; + return window; +} + + + +svn_txdelta_window_t * +svn_txdelta_window_dup(const svn_txdelta_window_t *window, + apr_pool_t *pool) +{ + svn_txdelta__ops_baton_t build_baton = { 0 }; + svn_txdelta_window_t *new_window; + const apr_size_t ops_size = (window->num_ops * sizeof(*build_baton.ops)); + + build_baton.num_ops = window->num_ops; + build_baton.src_ops = window->src_ops; + build_baton.ops_size = window->num_ops; + build_baton.ops = apr_palloc(pool, ops_size); + memcpy(build_baton.ops, window->ops, ops_size); + build_baton.new_data = + svn_stringbuf_create_from_string(window->new_data, pool); + + new_window = svn_txdelta__make_window(&build_baton, pool); + new_window->sview_offset = window->sview_offset; + new_window->sview_len = window->sview_len; + new_window->tview_len = window->tview_len; + return new_window; +} + +/* This is a private interlibrary compatibility wrapper. */ +svn_txdelta_window_t * +svn_txdelta__copy_window(const svn_txdelta_window_t *window, + apr_pool_t *pool); +svn_txdelta_window_t * +svn_txdelta__copy_window(const svn_txdelta_window_t *window, + apr_pool_t *pool) +{ + return svn_txdelta_window_dup(window, pool); +} + + +/* Insert a delta op into a delta window. */ + +void +svn_txdelta__insert_op(svn_txdelta__ops_baton_t *build_baton, + enum svn_delta_action opcode, + apr_size_t offset, + apr_size_t length, + const char *new_data, + apr_pool_t *pool) +{ + svn_txdelta_op_t *op; + + /* Check if this op can be merged with the previous op. The delta + combiner sometimes generates such ops, and this is the obvious + place to make the check. */ + if (build_baton->num_ops > 0) + { + op = &build_baton->ops[build_baton->num_ops - 1]; + if (op->action_code == opcode + && (opcode == svn_txdelta_new + || op->offset + op->length == offset)) + { + op->length += length; + if (opcode == svn_txdelta_new) + svn_stringbuf_appendbytes(build_baton->new_data, + new_data, length); + return; + } + } + + /* Create space for the new op. */ + if (build_baton->num_ops == build_baton->ops_size) + { + svn_txdelta_op_t *const old_ops = build_baton->ops; + int const new_ops_size = (build_baton->ops_size == 0 + ? 16 : 2 * build_baton->ops_size); + build_baton->ops = + apr_palloc(pool, new_ops_size * sizeof(*build_baton->ops)); + + /* Copy any existing ops into the new array */ + if (old_ops) + memcpy(build_baton->ops, old_ops, + build_baton->ops_size * sizeof(*build_baton->ops)); + build_baton->ops_size = new_ops_size; + } + + /* Insert the op. svn_delta_source and svn_delta_target are + just inserted. For svn_delta_new, the new data must be + copied into the window. */ + op = &build_baton->ops[build_baton->num_ops]; + switch (opcode) + { + case svn_txdelta_source: + ++build_baton->src_ops; + /*** FALLTHRU ***/ + case svn_txdelta_target: + op->action_code = opcode; + op->offset = offset; + op->length = length; + break; + case svn_txdelta_new: + op->action_code = opcode; + op->offset = build_baton->new_data->len; + op->length = length; + svn_stringbuf_appendbytes(build_baton->new_data, new_data, length); + break; + default: + assert(!"unknown delta op."); + } + + ++build_baton->num_ops; +} + + + +/* Generic delta stream functions. */ + +svn_txdelta_stream_t * +svn_txdelta_stream_create(void *baton, + svn_txdelta_next_window_fn_t next_window, + svn_txdelta_md5_digest_fn_t md5_digest, + apr_pool_t *pool) +{ + svn_txdelta_stream_t *stream = apr_palloc(pool, sizeof(*stream)); + + stream->baton = baton; + stream->next_window = next_window; + stream->md5_digest = md5_digest; + + return stream; +} + +svn_error_t * +svn_txdelta_next_window(svn_txdelta_window_t **window, + svn_txdelta_stream_t *stream, + apr_pool_t *pool) +{ + return stream->next_window(window, stream->baton, pool); +} + +const unsigned char * +svn_txdelta_md5_digest(svn_txdelta_stream_t *stream) +{ + return stream->md5_digest(stream->baton); +} + + + +static svn_error_t * +txdelta_next_window(svn_txdelta_window_t **window, + void *baton, + apr_pool_t *pool) +{ + struct txdelta_baton *b = baton; + apr_size_t source_len = SVN_DELTA_WINDOW_SIZE; + apr_size_t target_len = SVN_DELTA_WINDOW_SIZE; + + /* Read the source stream. */ + if (b->more_source) + { + SVN_ERR(svn_stream_read(b->source, b->buf, &source_len)); + b->more_source = (source_len == SVN_DELTA_WINDOW_SIZE); + } + else + source_len = 0; + + /* Read the target stream. */ + SVN_ERR(svn_stream_read(b->target, b->buf + source_len, &target_len)); + b->pos += source_len; + + if (target_len == 0) + { + /* No target data? We're done; return the final window. */ + if (b->context != NULL) + SVN_ERR(svn_checksum_final(&b->checksum, b->context, b->result_pool)); + + *window = NULL; + b->more = FALSE; + return SVN_NO_ERROR; + } + else if (b->context != NULL) + SVN_ERR(svn_checksum_update(b->context, b->buf + source_len, target_len)); + + *window = compute_window(b->buf, source_len, target_len, + b->pos - source_len, pool); + + /* That's it. */ + return SVN_NO_ERROR; +} + + +static const unsigned char * +txdelta_md5_digest(void *baton) +{ + struct txdelta_baton *b = baton; + /* If there are more windows for this stream, the digest has not yet + been calculated. */ + if (b->more) + return NULL; + + /* The checksum should be there. */ + return b->checksum->digest; +} + + +svn_error_t * +svn_txdelta_run(svn_stream_t *source, + svn_stream_t *target, + svn_txdelta_window_handler_t handler, + void *handler_baton, + svn_checksum_kind_t checksum_kind, + svn_checksum_t **checksum, + svn_cancel_func_t cancel_func, + void *cancel_baton, + apr_pool_t *result_pool, + apr_pool_t *scratch_pool) +{ + apr_pool_t *iterpool = svn_pool_create(scratch_pool); + struct txdelta_baton tb = { 0 }; + svn_txdelta_window_t *window; + + tb.source = source; + tb.target = target; + tb.more_source = TRUE; + tb.more = TRUE; + tb.pos = 0; + tb.buf = apr_palloc(scratch_pool, 2 * SVN_DELTA_WINDOW_SIZE); + tb.result_pool = result_pool; + + if (checksum != NULL) + tb.context = svn_checksum_ctx_create(checksum_kind, scratch_pool); + + do + { + /* free the window (if any) */ + svn_pool_clear(iterpool); + + /* read in a single delta window */ + SVN_ERR(txdelta_next_window(&window, &tb, iterpool)); + + /* shove it at the handler */ + SVN_ERR((*handler)(window, handler_baton)); + + if (cancel_func) + SVN_ERR(cancel_func(cancel_baton)); + } + while (window != NULL); + + svn_pool_destroy(iterpool); + + if (checksum != NULL) + *checksum = tb.checksum; /* should be there! */ + + return SVN_NO_ERROR; +} + + +void +svn_txdelta(svn_txdelta_stream_t **stream, + svn_stream_t *source, + svn_stream_t *target, + apr_pool_t *pool) +{ + struct txdelta_baton *b = apr_pcalloc(pool, sizeof(*b)); + + b->source = source; + b->target = target; + b->more_source = TRUE; + b->more = TRUE; + b->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE); + b->context = svn_checksum_ctx_create(svn_checksum_md5, pool); + b->result_pool = pool; + + *stream = svn_txdelta_stream_create(b, txdelta_next_window, + txdelta_md5_digest, pool); +} + + + +/* Functions for implementing a "target push" delta. */ + +/* This is the write handler for a target-push delta stream. It reads + * source data, buffers target data, and fires off delta windows when + * the target data buffer is full. */ +static svn_error_t * +tpush_write_handler(void *baton, const char *data, apr_size_t *len) +{ + struct tpush_baton *tb = baton; + apr_size_t chunk_len, data_len = *len; + apr_pool_t *pool = svn_pool_create(tb->pool); + svn_txdelta_window_t *window; + + while (data_len > 0) + { + svn_pool_clear(pool); + + /* Make sure we're all full up on source data, if possible. */ + if (tb->source_len == 0 && !tb->source_done) + { + tb->source_len = SVN_DELTA_WINDOW_SIZE; + SVN_ERR(svn_stream_read(tb->source, tb->buf, &tb->source_len)); + if (tb->source_len < SVN_DELTA_WINDOW_SIZE) + tb->source_done = TRUE; + } + + /* Copy in the target data, up to SVN_DELTA_WINDOW_SIZE. */ + chunk_len = SVN_DELTA_WINDOW_SIZE - tb->target_len; + if (chunk_len > data_len) + chunk_len = data_len; + memcpy(tb->buf + tb->source_len + tb->target_len, data, chunk_len); + data += chunk_len; + data_len -= chunk_len; + tb->target_len += chunk_len; + + /* If we're full of target data, compute and fire off a window. */ + if (tb->target_len == SVN_DELTA_WINDOW_SIZE) + { + window = compute_window(tb->buf, tb->source_len, tb->target_len, + tb->source_offset, pool); + SVN_ERR(tb->wh(window, tb->whb)); + tb->source_offset += tb->source_len; + tb->source_len = 0; + tb->target_len = 0; + } + } + + svn_pool_destroy(pool); + return SVN_NO_ERROR; +} + + +/* This is the close handler for a target-push delta stream. It sends + * a final window if there is any buffered target data, and then sends + * a NULL window signifying the end of the window stream. */ +static svn_error_t * +tpush_close_handler(void *baton) +{ + struct tpush_baton *tb = baton; + svn_txdelta_window_t *window; + + /* Send a final window if we have any residual target data. */ + if (tb->target_len > 0) + { + window = compute_window(tb->buf, tb->source_len, tb->target_len, + tb->source_offset, tb->pool); + SVN_ERR(tb->wh(window, tb->whb)); + } + + /* Send a final NULL window signifying the end. */ + return tb->wh(NULL, tb->whb); +} + + +svn_stream_t * +svn_txdelta_target_push(svn_txdelta_window_handler_t handler, + void *handler_baton, svn_stream_t *source, + apr_pool_t *pool) +{ + struct tpush_baton *tb; + svn_stream_t *stream; + + /* Initialize baton. */ + tb = apr_palloc(pool, sizeof(*tb)); + tb->source = source; + tb->wh = handler; + tb->whb = handler_baton; + tb->pool = pool; + tb->buf = apr_palloc(pool, 2 * SVN_DELTA_WINDOW_SIZE); + tb->source_offset = 0; + tb->source_len = 0; + tb->source_done = FALSE; + tb->target_len = 0; + + /* Create and return writable stream. */ + stream = svn_stream_create(tb, pool); + svn_stream_set_write(stream, tpush_write_handler); + svn_stream_set_close(stream, tpush_close_handler); + return stream; +} + + + +/* Functions for applying deltas. */ + +/* Ensure that BUF has enough space for VIEW_LEN bytes. */ +static APR_INLINE svn_error_t * +size_buffer(char **buf, apr_size_t *buf_size, + apr_size_t view_len, apr_pool_t *pool) +{ + if (view_len > *buf_size) + { + *buf_size *= 2; + if (*buf_size < view_len) + *buf_size = view_len; + SVN_ERR_ASSERT(APR_ALIGN_DEFAULT(*buf_size) >= *buf_size); + *buf = apr_palloc(pool, *buf_size); + } + + return SVN_NO_ERROR; +} + +/* Copy LEN bytes from SOURCE to TARGET, optimizing for the case where LEN + * is often very small. Return a pointer to the first byte after the copied + * target range, unlike standard memcpy(), as a potential further + * optimization for the caller. + * + * memcpy() is hard to tune for a wide range of buffer lengths. Therefore, + * it is often tuned for high throughput on large buffers and relatively + * low latency for mid-sized buffers (tens of bytes). However, the overhead + * for very small buffers (<10 bytes) is still high. Even passing the + * parameters, for instance, may take as long as copying 3 bytes. + * + * Because short copy sequences seem to be a common case, at least in + * "format 2" FSFS repositories, we copy them directly. Larger buffer sizes + * aren't hurt measurably by the exta 'if' clause. */ +static APR_INLINE char * +fast_memcpy(char *target, const char *source, apr_size_t len) +{ + if (len > 7) + { + memcpy(target, source, len); + target += len; + } + else + { + /* memcpy is not exactly fast for small block sizes. + * Since they are common, let's run optimized code for them. */ + const char *end = source + len; + for (; source != end; source++) + *(target++) = *source; + } + + return target; +} + +/* Copy LEN bytes from SOURCE to TARGET. Unlike memmove() or memcpy(), + * create repeating patterns if the source and target ranges overlap. + * Return a pointer to the first byte after the copied target range. */ +static APR_INLINE char * +patterning_copy(char *target, const char *source, apr_size_t len) +{ + const char *end = source + len; + + /* On many machines, we can do "chunky" copies. */ + +#if SVN_UNALIGNED_ACCESS_IS_OK + + if (end + sizeof(apr_uint32_t) <= target) + { + /* Source and target are at least 4 bytes apart, so we can copy in + * 4-byte chunks. */ + for (; source + sizeof(apr_uint32_t) <= end; + source += sizeof(apr_uint32_t), + target += sizeof(apr_uint32_t)) + *(apr_uint32_t *)(target) = *(apr_uint32_t *)(source); + } + +#endif + + /* fall through to byte-wise copy (either for the below-chunk-size tail + * or the whole copy) */ + for (; source != end; source++) + *(target++) = *source; + + return target; +} + +void +svn_txdelta_apply_instructions(svn_txdelta_window_t *window, + const char *sbuf, char *tbuf, + apr_size_t *tlen) +{ + const svn_txdelta_op_t *op; + apr_size_t tpos = 0; + + for (op = window->ops; op < window->ops + window->num_ops; op++) + { + const apr_size_t buf_len = (op->length < *tlen - tpos + ? op->length : *tlen - tpos); + + /* Check some invariants common to all instructions. */ + assert(tpos + op->length <= window->tview_len); + + switch (op->action_code) + { + case svn_txdelta_source: + /* Copy from source area. */ + assert(op->offset + op->length <= window->sview_len); + fast_memcpy(tbuf + tpos, sbuf + op->offset, buf_len); + break; + + case svn_txdelta_target: + /* Copy from target area. We can't use memcpy() or the like + * since we need a specific semantics for overlapping copies: + * they must result in repeating patterns. + * Note that most copies won't have overlapping source and + * target ranges (they are just a result of self-compressed + * data) but a small percentage will. */ + assert(op->offset < tpos); + patterning_copy(tbuf + tpos, tbuf + op->offset, buf_len); + break; + + case svn_txdelta_new: + /* Copy from window new area. */ + assert(op->offset + op->length <= window->new_data->len); + fast_memcpy(tbuf + tpos, + window->new_data->data + op->offset, + buf_len); + break; + + default: + assert(!"Invalid delta instruction code"); + } + + tpos += op->length; + if (tpos >= *tlen) + return; /* The buffer is full. */ + } + + /* Check that we produced the right amount of data. */ + assert(tpos == window->tview_len); + *tlen = tpos; +} + +/* This is a private interlibrary compatibility wrapper. */ +void +svn_txdelta__apply_instructions(svn_txdelta_window_t *window, + const char *sbuf, char *tbuf, + apr_size_t *tlen); +void +svn_txdelta__apply_instructions(svn_txdelta_window_t *window, + const char *sbuf, char *tbuf, + apr_size_t *tlen) +{ + svn_txdelta_apply_instructions(window, sbuf, tbuf, tlen); +} + + +/* Apply WINDOW to the streams given by APPL. */ +static svn_error_t * +apply_window(svn_txdelta_window_t *window, void *baton) +{ + struct apply_baton *ab = (struct apply_baton *) baton; + apr_size_t len; + svn_error_t *err; + + if (window == NULL) + { + /* We're done; just clean up. */ + if (ab->result_digest) + apr_md5_final(ab->result_digest, &(ab->md5_context)); + + err = svn_stream_close(ab->target); + svn_pool_destroy(ab->pool); + + return err; + } + + /* Make sure the source view didn't slide backwards. */ + SVN_ERR_ASSERT(window->sview_len == 0 + || (window->sview_offset >= ab->sbuf_offset + && (window->sview_offset + window->sview_len + >= ab->sbuf_offset + ab->sbuf_len))); + + /* Make sure there's enough room in the target buffer. */ + SVN_ERR(size_buffer(&ab->tbuf, &ab->tbuf_size, window->tview_len, ab->pool)); + + /* Prepare the source buffer for reading from the input stream. */ + if (window->sview_offset != ab->sbuf_offset + || window->sview_len > ab->sbuf_size) + { + char *old_sbuf = ab->sbuf; + + /* Make sure there's enough room. */ + SVN_ERR(size_buffer(&ab->sbuf, &ab->sbuf_size, window->sview_len, + ab->pool)); + + /* If the existing view overlaps with the new view, copy the + * overlap to the beginning of the new buffer. */ + if (ab->sbuf_offset + ab->sbuf_len > window->sview_offset) + { + apr_size_t start = + (apr_size_t)(window->sview_offset - ab->sbuf_offset); + memmove(ab->sbuf, old_sbuf + start, ab->sbuf_len - start); + ab->sbuf_len -= start; + } + else + ab->sbuf_len = 0; + ab->sbuf_offset = window->sview_offset; + } + + /* Read the remainder of the source view into the buffer. */ + if (ab->sbuf_len < window->sview_len) + { + len = window->sview_len - ab->sbuf_len; + err = svn_stream_read(ab->source, ab->sbuf + ab->sbuf_len, &len); + if (err == SVN_NO_ERROR && len != window->sview_len - ab->sbuf_len) + err = svn_error_create(SVN_ERR_INCOMPLETE_DATA, NULL, + "Delta source ended unexpectedly"); + if (err != SVN_NO_ERROR) + return err; + ab->sbuf_len = window->sview_len; + } + + /* Apply the window instructions to the source view to generate + the target view. */ + len = window->tview_len; + svn_txdelta_apply_instructions(window, ab->sbuf, ab->tbuf, &len); + SVN_ERR_ASSERT(len == window->tview_len); + + /* Write out the output. */ + + /* ### We've also considered just adding two (optionally null) + arguments to svn_stream_create(): read_checksum and + write_checksum. Then instead of every caller updating an md5 + context when it calls svn_stream_write() or svn_stream_read(), + streams would do it automatically, and verify the checksum in + svn_stream_closed(). But this might be overkill for issue #689; + so for now we just update the context here. */ + if (ab->result_digest) + apr_md5_update(&(ab->md5_context), ab->tbuf, len); + + return svn_stream_write(ab->target, ab->tbuf, &len); +} + + +void +svn_txdelta_apply(svn_stream_t *source, + svn_stream_t *target, + unsigned char *result_digest, + const char *error_info, + apr_pool_t *pool, + svn_txdelta_window_handler_t *handler, + void **handler_baton) +{ + apr_pool_t *subpool = svn_pool_create(pool); + struct apply_baton *ab; + + ab = apr_palloc(subpool, sizeof(*ab)); + ab->source = source; + ab->target = target; + ab->pool = subpool; + ab->sbuf = NULL; + ab->sbuf_size = 0; + ab->sbuf_offset = 0; + ab->sbuf_len = 0; + ab->tbuf = NULL; + ab->tbuf_size = 0; + ab->result_digest = result_digest; + + if (result_digest) + apr_md5_init(&(ab->md5_context)); + + if (error_info) + ab->error_info = apr_pstrdup(subpool, error_info); + else + ab->error_info = NULL; + + *handler = apply_window; + *handler_baton = ab; +} + + + +/* Convenience routines */ + +svn_error_t * +svn_txdelta_send_string(const svn_string_t *string, + svn_txdelta_window_handler_t handler, + void *handler_baton, + apr_pool_t *pool) +{ + svn_txdelta_window_t window = { 0 }; + svn_txdelta_op_t op; + + /* Build a single `new' op */ + op.action_code = svn_txdelta_new; + op.offset = 0; + op.length = string->len; + + /* Build a single window containing a ptr to the string. */ + window.tview_len = string->len; + window.num_ops = 1; + window.ops = &op; + window.new_data = string; + + /* Push the one window at the handler. */ + SVN_ERR((*handler)(&window, handler_baton)); + + /* Push a NULL at the handler, because we're done. */ + return (*handler)(NULL, handler_baton); +} + +svn_error_t *svn_txdelta_send_stream(svn_stream_t *stream, + svn_txdelta_window_handler_t handler, + void *handler_baton, + unsigned char *digest, + apr_pool_t *pool) +{ + svn_txdelta_stream_t *txstream; + svn_error_t *err; + + /* ### this is a hack. we should simply read from the stream, construct + ### some windows, and pass those to the handler. there isn't any reason + ### to crank up a full "diff" algorithm just to copy a stream. + ### + ### will fix RSN. */ + + /* Create a delta stream which converts an *empty* bytestream into the + target bytestream. */ + svn_txdelta(&txstream, svn_stream_empty(pool), stream, pool); + err = svn_txdelta_send_txstream(txstream, handler, handler_baton, pool); + + if (digest && (! err)) + { + const unsigned char *result_md5; + result_md5 = svn_txdelta_md5_digest(txstream); + /* Since err is null, result_md5 "cannot" be null. */ + memcpy(digest, result_md5, APR_MD5_DIGESTSIZE); + } + + return err; +} + +svn_error_t *svn_txdelta_send_txstream(svn_txdelta_stream_t *txstream, + svn_txdelta_window_handler_t handler, + void *handler_baton, + apr_pool_t *pool) +{ + svn_txdelta_window_t *window; + + /* create a pool just for the windows */ + apr_pool_t *wpool = svn_pool_create(pool); + + do + { + /* free the window (if any) */ + svn_pool_clear(wpool); + + /* read in a single delta window */ + SVN_ERR(svn_txdelta_next_window(&window, txstream, wpool)); + + /* shove it at the handler */ + SVN_ERR((*handler)(window, handler_baton)); + } + while (window != NULL); + + svn_pool_destroy(wpool); + + return SVN_NO_ERROR; +} diff --git a/subversion/libsvn_delta/version.c b/subversion/libsvn_delta/version.c new file mode 100644 index 0000000..ffbfb0f --- /dev/null +++ b/subversion/libsvn_delta/version.c @@ -0,0 +1,33 @@ +/* + * version.c: library version number + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + + +#include "svn_version.h" +#include "svn_delta.h" + +const svn_version_t * +svn_delta_version(void) +{ + SVN_VERSION_BODY; +} diff --git a/subversion/libsvn_delta/xdelta.c b/subversion/libsvn_delta/xdelta.c new file mode 100644 index 0000000..ea810e9 --- /dev/null +++ b/subversion/libsvn_delta/xdelta.c @@ -0,0 +1,398 @@ +/* + * xdelta.c: xdelta generator. + * + * ==================================================================== + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + * ==================================================================== + */ + + +#include <assert.h> + +#include <apr_general.h> /* for APR_INLINE */ +#include <apr_hash.h> + +#include "svn_delta.h" +#include "delta.h" + +#include "private/svn_adler32.h" + +/* This is pseudo-adler32. It is adler32 without the prime modulus. + The idea is borrowed from monotone, and is a translation of the C++ + code. Graydon Hoare, the author of the original code, gave his + explicit permission to use it under these terms at 8:02pm on + Friday, February 11th, 2005. */ + +/* Size of the blocks we compute checksums for. This was chosen out of + thin air. Monotone used 64, xdelta1 used 64, rsync uses 128. + However, later optimizations assume it to be 256 or less. + */ +#define MATCH_BLOCKSIZE 64 + +/* Feed C_IN into the adler32 checksum and remove C_OUT at the same time. + * This function may (and will) only be called for characters that are + * MATCH_BLOCKSIZE positions apart. + * + * Please note that the lower 16 bits cannot overflow in neither direction. + * Therefore, we don't need to split the value into separate values for + * sum(char) and sum(sum(char)). + */ +static APR_INLINE apr_uint32_t +adler32_replace(apr_uint32_t adler32, const char c_out, const char c_in) +{ + adler32 -= (MATCH_BLOCKSIZE * 0x10000u * ((unsigned char) c_out)); + + adler32 -= (unsigned char)c_out; + adler32 += (unsigned char)c_in; + + return adler32 + adler32 * 0x10000; +} + +/* Calculate an peudo-adler32 checksum for MATCH_BLOCKSIZE bytes starting + at DATA. Return the checksum value. */ + +static APR_INLINE apr_uint32_t +init_adler32(const char *data) +{ + const unsigned char *input = (const unsigned char *)data; + const unsigned char *last = input + MATCH_BLOCKSIZE; + + apr_uint32_t s1 = 0; + apr_uint32_t s2 = 0; + + for (; input < last; input += 8) + { + s1 += input[0]; s2 += s1; + s1 += input[1]; s2 += s1; + s1 += input[2]; s2 += s1; + s1 += input[3]; s2 += s1; + s1 += input[4]; s2 += s1; + s1 += input[5]; s2 += s1; + s1 += input[6]; s2 += s1; + s1 += input[7]; s2 += s1; + } + + return s2 * 0x10000 + s1; +} + +/* Information for a block of the delta source. The length of the + block is the smaller of MATCH_BLOCKSIZE and the difference between + the size of the source data and the position of this block. */ +struct block +{ + apr_uint32_t adlersum; + apr_size_t pos; +}; + +/* A hash table, using open addressing, of the blocks of the source. */ +struct blocks +{ + /* The largest valid index of slots. */ + apr_size_t max; + /* Source buffer that the positions in SLOTS refer to. */ + const char* data; + /* The vector of blocks. A pos value of (apr_size_t)-1 represents an unused + slot. */ + struct block *slots; +}; + + +/* Return a hash value calculated from the adler32 SUM, suitable for use with + our hash table. */ +static apr_size_t hash_func(apr_uint32_t sum) +{ + /* Since the adl32 checksum have a bad distribution for the 11th to 16th + bits when used for our small block size, we add some bits from the + other half of the checksum. */ + return sum ^ (sum >> 12); +} + +/* Insert a block with the checksum ADLERSUM at position POS in the source + data into the table BLOCKS. Ignore true duplicates, i.e. blocks with + actually the same content. */ +static void +add_block(struct blocks *blocks, apr_uint32_t adlersum, apr_size_t pos) +{ + apr_size_t h = hash_func(adlersum) & blocks->max; + + /* This will terminate, since we know that we will not fill the table. */ + for (; blocks->slots[h].pos != (apr_size_t)-1; h = (h + 1) & blocks->max) + if (blocks->slots[h].adlersum == adlersum) + if (memcmp(blocks->data + blocks->slots[h].pos, blocks->data + pos, + MATCH_BLOCKSIZE) == 0) + return; + + blocks->slots[h].adlersum = adlersum; + blocks->slots[h].pos = pos; +} + +/* Find a block in BLOCKS with the checksum ADLERSUM and matching the content + at DATA, returning its position in the source data. If there is no such + block, return (apr_size_t)-1. */ +static apr_size_t +find_block(const struct blocks *blocks, + apr_uint32_t adlersum, + const char* data) +{ + apr_size_t h = hash_func(adlersum) & blocks->max; + + for (; blocks->slots[h].pos != (apr_size_t)-1; h = (h + 1) & blocks->max) + if (blocks->slots[h].adlersum == adlersum) + if (memcmp(blocks->data + blocks->slots[h].pos, data, + MATCH_BLOCKSIZE) == 0) + return blocks->slots[h].pos; + + return (apr_size_t)-1; +} + +/* Initialize the matches table from DATA of size DATALEN. This goes + through every block of MATCH_BLOCKSIZE bytes in the source and + checksums it, inserting the result into the BLOCKS table. */ +static void +init_blocks_table(const char *data, + apr_size_t datalen, + struct blocks *blocks, + apr_pool_t *pool) +{ + apr_size_t i; + apr_size_t nblocks; + apr_size_t nslots = 1; + + /* Be pesimistic about the block count. */ + nblocks = datalen / MATCH_BLOCKSIZE + 1; + /* Find nearest larger power of two. */ + while (nslots <= nblocks) + nslots *= 2; + /* Double the number of slots to avoid a too high load. */ + nslots *= 2; + blocks->max = nslots - 1; + blocks->data = data; + blocks->slots = apr_palloc(pool, nslots * sizeof(*(blocks->slots))); + for (i = 0; i < nslots; ++i) + { + /* Avoid using an indeterminate value in the lookup. */ + blocks->slots[i].adlersum = 0; + blocks->slots[i].pos = (apr_size_t)-1; + } + + /* If there is an odd block at the end of the buffer, we will + not use that shorter block for deltification (only indirectly + as an extension of some previous block). */ + for (i = 0; i + MATCH_BLOCKSIZE <= datalen; i += MATCH_BLOCKSIZE) + add_block(blocks, init_adler32(data + i), i); +} + +/* Return the lowest position at which A and B differ. If no difference + * can be found in the first MAX_LEN characters, MAX_LEN will be returned. + */ +static apr_size_t +match_length(const char *a, const char *b, apr_size_t max_len) +{ + apr_size_t pos = 0; + +#if SVN_UNALIGNED_ACCESS_IS_OK + + /* Chunky processing is so much faster ... + * + * We can't make this work on architectures that require aligned access + * because A and B will probably have different alignment. So, skipping + * the first few chars until alignment is reached is not an option. + */ + for (; pos + sizeof(apr_size_t) <= max_len; pos += sizeof(apr_size_t)) + if (*(const apr_size_t*)(a + pos) != *(const apr_size_t*)(b + pos)) + break; + +#endif + + for (; pos < max_len; ++pos) + if (a[pos] != b[pos]) + break; + + return pos; +} + +/* Try to find a match for the target data B in BLOCKS, and then + extend the match as long as data in A and B at the match position + continues to match. We set the position in A we ended up in (in + case we extended it backwards) in APOSP and update the correspnding + position within B given in BPOSP. PENDING_INSERT_START sets the + lower limit to BPOSP. + Return number of matching bytes starting at ASOP. Return 0 if + no match has been found. + */ +static apr_size_t +find_match(const struct blocks *blocks, + const apr_uint32_t rolling, + const char *a, + apr_size_t asize, + const char *b, + apr_size_t bsize, + apr_size_t *bposp, + apr_size_t *aposp, + apr_size_t pending_insert_start) +{ + apr_size_t apos, bpos = *bposp; + apr_size_t delta, max_delta; + + apos = find_block(blocks, rolling, b + bpos); + + /* See if we have a match. */ + if (apos == (apr_size_t)-1) + return 0; + + /* Extend the match forward as far as possible */ + max_delta = asize - apos - MATCH_BLOCKSIZE < bsize - bpos - MATCH_BLOCKSIZE + ? asize - apos - MATCH_BLOCKSIZE + : bsize - bpos - MATCH_BLOCKSIZE; + delta = match_length(a + apos + MATCH_BLOCKSIZE, + b + bpos + MATCH_BLOCKSIZE, + max_delta); + + /* See if we can extend backwards (max MATCH_BLOCKSIZE-1 steps because A's + content has been sampled only every MATCH_BLOCKSIZE positions). */ + while (apos > 0 && bpos > pending_insert_start && a[apos-1] == b[bpos-1]) + { + --apos; + --bpos; + ++delta; + } + + *aposp = apos; + *bposp = bpos; + + return MATCH_BLOCKSIZE + delta; +} + + +/* Compute a delta from A to B using xdelta. + + The basic xdelta algorithm is as follows: + + 1. Go through the source data, checksumming every MATCH_BLOCKSIZE + block of bytes using adler32, and inserting the checksum into a + match table with the position of the match. + 2. Go through the target byte by byte, seeing if that byte starts a + match that we have in the match table. + 2a. If so, try to extend the match as far as possible both + forwards and backwards, and then insert a source copy + operation into the delta ops builder for the match. + 2b. If not, insert the byte as new data using an insert delta op. + + Our implementation doesn't immediately insert "insert" operations, + it waits until we have another copy, or we are done. The reasoning + is twofold: + + 1. Otherwise, we would just be building a ton of 1 byte insert + operations + 2. So that we can extend a source match backwards into a pending + insert operation, and possibly remove the need for the insert + entirely. This can happen due to stream alignment. +*/ +static void +compute_delta(svn_txdelta__ops_baton_t *build_baton, + const char *a, + apr_uint32_t asize, + const char *b, + apr_uint32_t bsize, + apr_pool_t *pool) +{ + struct blocks blocks; + apr_uint32_t rolling; + apr_size_t lo = 0, pending_insert_start = 0; + + /* If the size of the target is smaller than the match blocksize, just + insert the entire target. */ + if (bsize < MATCH_BLOCKSIZE) + { + svn_txdelta__insert_op(build_baton, svn_txdelta_new, + 0, bsize, b, pool); + return; + } + + /* Initialize the matches table. */ + init_blocks_table(a, asize, &blocks, pool); + + /* Initialize our rolling checksum. */ + rolling = init_adler32(b); + while (lo < bsize) + { + apr_size_t matchlen = 0; + apr_size_t apos; + + if (lo + MATCH_BLOCKSIZE <= bsize) + matchlen = find_match(&blocks, rolling, a, asize, b, bsize, + &lo, &apos, pending_insert_start); + + /* If we didn't find a real match, insert the byte at the target + position into the pending insert. */ + if (matchlen == 0) + { + /* move block one position forward. Short blocks at the end of + the buffer cannot be used as the beginning of a new match */ + if (lo + MATCH_BLOCKSIZE < bsize) + rolling = adler32_replace(rolling, b[lo], b[lo+MATCH_BLOCKSIZE]); + + lo++; + } + else + { + /* store the sequence of B that is between the matches */ + if (lo - pending_insert_start > 0) + svn_txdelta__insert_op(build_baton, svn_txdelta_new, + 0, lo - pending_insert_start, + b + pending_insert_start, pool); + + /* Reset the pending insert start to immediately after the + match. */ + lo += matchlen; + pending_insert_start = lo; + svn_txdelta__insert_op(build_baton, svn_txdelta_source, + apos, matchlen, NULL, pool); + + /* Calculate the Adler32 sum for the first block behind the match. + * Ignore short buffers at the end of B. + */ + if (lo + MATCH_BLOCKSIZE <= bsize) + rolling = init_adler32(b + lo); + } + } + + /* If we still have an insert pending at the end, throw it in. */ + if (lo - pending_insert_start > 0) + { + svn_txdelta__insert_op(build_baton, svn_txdelta_new, + 0, lo - pending_insert_start, + b + pending_insert_start, pool); + } +} + +void +svn_txdelta__xdelta(svn_txdelta__ops_baton_t *build_baton, + const char *data, + apr_size_t source_len, + apr_size_t target_len, + apr_pool_t *pool) +{ + /* We should never be asked to compute something when the source_len is 0; + we just use a single insert op there (and rely on zlib for + compression). */ + assert(source_len != 0); + compute_delta(build_baton, data, source_len, + data + source_len, target_len, + pool); +} |