summaryrefslogtreecommitdiff
path: root/subversion/libsvn_delta
diff options
context:
space:
mode:
authorLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 14:29:52 +0100
committerLorry <lorry@roadtrain.codethink.co.uk>2012-08-22 14:29:52 +0100
commitf1bdf13786f0752c0846cf36f0d91e4fc6747929 (patch)
tree4223b2035bf2240d681a53822808b3c7f687b905 /subversion/libsvn_delta
downloadsubversion-tarball-f1bdf13786f0752c0846cf36f0d91e4fc6747929.tar.gz
Tarball conversion
Diffstat (limited to 'subversion/libsvn_delta')
-rw-r--r--subversion/libsvn_delta/cancel.c378
-rw-r--r--subversion/libsvn_delta/compat.c78
-rw-r--r--subversion/libsvn_delta/compose_delta.c837
-rw-r--r--subversion/libsvn_delta/debug_editor.c433
-rw-r--r--subversion/libsvn_delta/debug_editor.h49
-rw-r--r--subversion/libsvn_delta/default_editor.c161
-rw-r--r--subversion/libsvn_delta/delta.h90
-rw-r--r--subversion/libsvn_delta/depth_filter_editor.c485
-rw-r--r--subversion/libsvn_delta/editor.c446
-rw-r--r--subversion/libsvn_delta/path_driver.c292
-rw-r--r--subversion/libsvn_delta/svndiff.c976
-rw-r--r--subversion/libsvn_delta/text_delta.c916
-rw-r--r--subversion/libsvn_delta/version.c33
-rw-r--r--subversion/libsvn_delta/xdelta.c398
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);
+}