summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_delta
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_delta')
-rw-r--r--subversion/libsvn_delta/cancel.c378
-rw-r--r--subversion/libsvn_delta/compat.c2010
-rw-r--r--subversion/libsvn_delta/compose_delta.c837
-rw-r--r--subversion/libsvn_delta/debug_editor.c437
-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.h96
-rw-r--r--subversion/libsvn_delta/deprecated.c48
-rw-r--r--subversion/libsvn_delta/depth_filter_editor.c485
-rw-r--r--subversion/libsvn_delta/editor.c956
-rw-r--r--subversion/libsvn_delta/path_driver.c298
-rw-r--r--subversion/libsvn_delta/svndiff.c1103
-rw-r--r--subversion/libsvn_delta/text_delta.c1041
-rw-r--r--subversion/libsvn_delta/version.c33
-rw-r--r--subversion/libsvn_delta/xdelta.c514
15 files changed, 8446 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..8d315d1
--- /dev/null
+++ b/subversion/libsvn_delta/compat.c
@@ -0,0 +1,2010 @@
+/*
+ * 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 <stddef.h>
+
+#include "svn_types.h"
+#include "svn_error.h"
+#include "svn_delta.h"
+#include "svn_sorts.h"
+#include "svn_dirent_uri.h"
+#include "svn_path.h"
+#include "svn_hash.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+
+#include "svn_private_config.h"
+
+#include "private/svn_delta_private.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_pcalloc(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;
+}
+
+
+/* The following code maps the calls to a traditional delta editor to an
+ * Editorv2 editor. It does this by keeping track of a lot of state, and
+ * then communicating that state to Ev2 upon closure of the file or dir (or
+ * edit). Note that Ev2 calls add_symlink() and alter_symlink() are not
+ * present in the delta editor paradigm, so we never call them.
+ *
+ * The general idea here is that we have to see *all* the actions on a node's
+ * parent before we can process that node, which means we need to buffer a
+ * large amount of information in the dir batons, and then process it in the
+ * close_directory() handler.
+ *
+ * There are a few ways we alter the callback stream. One is when unlocking
+ * paths. To tell a client a path should be unlocked, the server sends a
+ * prop-del for the SVN_PROP_ENTRY_LOCK_TOKEN property. This causes problems,
+ * since the client doesn't have this property in the first place, but the
+ * deletion has side effects (unlike deleting a non-existent regular property
+ * would). To solve this, we introduce *another* function into the API, not
+ * a part of the Ev2 callbacks, but a companion which is used to register
+ * the unlock of a path. See ev2_change_file_prop() for implemenation
+ * details.
+ */
+
+struct ev2_edit_baton
+{
+ svn_editor_t *editor;
+
+ apr_hash_t *changes; /* REPOS_RELPATH -> struct change_node */
+
+ apr_array_header_t *path_order;
+ int paths_processed;
+
+ /* For calculating relpaths from Ev1 copyfrom urls. */
+ const char *repos_root;
+ const char *base_relpath;
+
+ apr_pool_t *edit_pool;
+ struct svn_delta__extra_baton *exb;
+ svn_boolean_t closed;
+
+ svn_boolean_t *found_abs_paths; /* Did we strip an incoming '/' from the
+ paths? */
+
+ svn_delta_fetch_props_func_t fetch_props_func;
+ void *fetch_props_baton;
+
+ svn_delta_fetch_base_func_t fetch_base_func;
+ void *fetch_base_baton;
+
+ svn_delta__unlock_func_t do_unlock;
+ void *unlock_baton;
+};
+
+struct ev2_dir_baton
+{
+ struct ev2_edit_baton *eb;
+ const char *path;
+ svn_revnum_t base_revision;
+
+ const char *copyfrom_relpath;
+ svn_revnum_t copyfrom_rev;
+};
+
+struct ev2_file_baton
+{
+ struct ev2_edit_baton *eb;
+ const char *path;
+ svn_revnum_t base_revision;
+ const char *delta_base;
+};
+
+enum restructure_action_t
+{
+ RESTRUCTURE_NONE = 0,
+ RESTRUCTURE_ADD, /* add the node, maybe replacing. maybe copy */
+ RESTRUCTURE_ADD_ABSENT, /* add an absent node, possibly replacing */
+ RESTRUCTURE_DELETE /* delete this node */
+};
+
+/* Records everything about how this node is to be changed. */
+struct change_node
+{
+ /* what kind of (tree) restructure is occurring at this node? */
+ enum restructure_action_t action;
+
+ svn_node_kind_t kind; /* the NEW kind of this node */
+
+ /* We need two revisions: one to specify the revision we are altering,
+ and a second to specify the revision to delete/replace. These are
+ mutually exclusive, but they need to be separate to ensure we don't
+ confuse the operation on this node. For example, we may delete a
+ node and replace it we use DELETING for REPLACES_REV, and ignore
+ the value placed into CHANGING when properties were set/changed
+ on the new node. Or we simply change a node (setting CHANGING),
+ and DELETING remains SVN_INVALID_REVNUM, indicating we are not
+ attempting to replace a node. */
+ svn_revnum_t changing;
+ svn_revnum_t deleting;
+
+ apr_hash_t *props; /* new/final set of props to apply */
+
+ const char *contents_abspath; /* file containing new fulltext */
+ svn_checksum_t *checksum; /* checksum of new fulltext */
+
+ /* If COPYFROM_PATH is not NULL, then copy PATH@REV to this node.
+ RESTRUCTURE must be RESTRUCTURE_ADD. */
+ const char *copyfrom_path;
+ svn_revnum_t copyfrom_rev;
+
+ /* Record whether an incoming propchange unlocked this node. */
+ svn_boolean_t unlock;
+};
+
+
+static struct change_node *
+locate_change(struct ev2_edit_baton *eb,
+ const char *relpath)
+{
+ struct change_node *change = svn_hash_gets(eb->changes, relpath);
+
+ if (change != NULL)
+ return change;
+
+ /* Shift RELPATH into the proper pool, and record the observed order. */
+ relpath = apr_pstrdup(eb->edit_pool, relpath);
+ APR_ARRAY_PUSH(eb->path_order, const char *) = relpath;
+
+ /* Return an empty change. Callers will tweak as needed. */
+ change = apr_pcalloc(eb->edit_pool, sizeof(*change));
+ change->changing = SVN_INVALID_REVNUM;
+ change->deleting = SVN_INVALID_REVNUM;
+
+ svn_hash_sets(eb->changes, relpath, change);
+
+ return change;
+}
+
+
+static svn_error_t *
+apply_propedit(struct ev2_edit_baton *eb,
+ const char *relpath,
+ svn_node_kind_t kind,
+ svn_revnum_t base_revision,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct change_node *change = locate_change(eb, relpath);
+
+ SVN_ERR_ASSERT(change->kind == svn_node_unknown || change->kind == kind);
+ change->kind = kind;
+
+ /* We're now changing the node. Record the revision. */
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->changing)
+ || change->changing == base_revision);
+ change->changing = base_revision;
+
+ if (change->props == NULL)
+ {
+ /* Fetch the original set of properties. We'll apply edits to create
+ the new/target set of properties.
+
+ If this is a copied/moved now, then the original properties come
+ from there. If the node has been added, it starts with empty props.
+ Otherwise, we get the properties from BASE. */
+
+ if (change->copyfrom_path)
+ SVN_ERR(eb->fetch_props_func(&change->props,
+ eb->fetch_props_baton,
+ change->copyfrom_path,
+ change->copyfrom_rev,
+ eb->edit_pool, scratch_pool));
+ else if (change->action == RESTRUCTURE_ADD)
+ change->props = apr_hash_make(eb->edit_pool);
+ else
+ SVN_ERR(eb->fetch_props_func(&change->props,
+ eb->fetch_props_baton,
+ relpath, base_revision,
+ eb->edit_pool, scratch_pool));
+ }
+
+ if (value == NULL)
+ svn_hash_sets(change->props, name, NULL);
+ else
+ svn_hash_sets(change->props,
+ apr_pstrdup(eb->edit_pool, name),
+ svn_string_dup(value, eb->edit_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Find all the paths which are immediate children of PATH and return their
+ basenames in a list. */
+static apr_array_header_t *
+get_children(struct ev2_edit_baton *eb,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *children = apr_array_make(pool, 1, sizeof(const char *));
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, eb->changes); hi; hi = apr_hash_next(hi))
+ {
+ const char *repos_relpath = svn__apr_hash_index_key(hi);
+ const char *child;
+
+ /* Find potential children. */
+ child = svn_relpath_skip_ancestor(path, repos_relpath);
+ if (!child || !*child)
+ continue;
+
+ /* If we have a path separator, it's a deep child, so just ignore it.
+ ### Is there an API we should be using for this? */
+ if (strchr(child, '/') != NULL)
+ continue;
+
+ APR_ARRAY_PUSH(children, const char *) = child;
+ }
+
+ return children;
+}
+
+
+static svn_error_t *
+process_actions(struct ev2_edit_baton *eb,
+ const char *repos_relpath,
+ const struct change_node *change,
+ apr_pool_t *scratch_pool)
+{
+ apr_hash_t *props = NULL;
+ svn_stream_t *contents = NULL;
+ svn_checksum_t *checksum = NULL;
+ svn_node_kind_t kind = svn_node_unknown;
+
+ SVN_ERR_ASSERT(change != NULL);
+
+ if (change->unlock)
+ SVN_ERR(eb->do_unlock(eb->unlock_baton, repos_relpath, scratch_pool));
+
+ if (change->action == RESTRUCTURE_DELETE)
+ {
+ /* If the action was left as RESTRUCTURE_DELETE, then a
+ replacement is not occurring. Just do the delete and bail. */
+ SVN_ERR(svn_editor_delete(eb->editor, repos_relpath,
+ change->deleting));
+
+ /* No further work possible on this node. */
+ return SVN_NO_ERROR;
+ }
+ if (change->action == RESTRUCTURE_ADD_ABSENT)
+ {
+ SVN_ERR(svn_editor_add_absent(eb->editor, repos_relpath,
+ change->kind, change->deleting));
+
+ /* No further work possible on this node. */
+ return SVN_NO_ERROR;
+ }
+
+ if (change->contents_abspath != NULL)
+ {
+ /* We can only set text on files. */
+ /* ### validate we aren't overwriting KIND? */
+ kind = svn_node_file;
+
+ /* ### the checksum might be in CHANGE->CHECKSUM */
+ SVN_ERR(svn_io_file_checksum2(&checksum, change->contents_abspath,
+ svn_checksum_sha1, scratch_pool));
+ SVN_ERR(svn_stream_open_readonly(&contents, change->contents_abspath,
+ scratch_pool, scratch_pool));
+ }
+
+ if (change->props != NULL)
+ {
+ /* ### validate we aren't overwriting KIND? */
+ kind = change->kind;
+ props = change->props;
+ }
+
+ if (change->action == RESTRUCTURE_ADD)
+ {
+ /* An add might be a replace. Grab the revnum we're replacing. */
+ svn_revnum_t replaces_rev = change->deleting;
+
+ kind = change->kind;
+
+ if (change->copyfrom_path != NULL)
+ {
+ SVN_ERR(svn_editor_copy(eb->editor, change->copyfrom_path,
+ change->copyfrom_rev,
+ repos_relpath, replaces_rev));
+ /* Fall through to possibly make changes post-copy. */
+ }
+ else
+ {
+ /* If no properties were defined, then use an empty set. */
+ if (props == NULL)
+ props = apr_hash_make(scratch_pool);
+
+ if (kind == svn_node_dir)
+ {
+ const apr_array_header_t *children;
+
+ children = get_children(eb, repos_relpath, scratch_pool);
+ SVN_ERR(svn_editor_add_directory(eb->editor, repos_relpath,
+ children, props,
+ replaces_rev));
+ }
+ else
+ {
+ /* If this file was added, but apply_txdelta() was not
+ called (ie. no CONTENTS_ABSPATH), then we're adding
+ an empty file. */
+ if (change->contents_abspath == NULL)
+ {
+ contents = svn_stream_empty(scratch_pool);
+ checksum = svn_checksum_empty_checksum(svn_checksum_sha1,
+ scratch_pool);
+ }
+
+ SVN_ERR(svn_editor_add_file(eb->editor, repos_relpath,
+ checksum, contents, props,
+ replaces_rev));
+ }
+
+ /* No further work possible on this node. */
+ return SVN_NO_ERROR;
+ }
+ }
+
+#if 0
+ /* There *should* be work for this node. But it seems that isn't true
+ in some cases. Future investigation... */
+ SVN_ERR_ASSERT(props || contents);
+#endif
+ if (props || contents)
+ {
+ /* Changes to properties or content should have indicated the revision
+ it was intending to change.
+
+ Oop. Not true. The node may be locally-added. */
+#if 0
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(change->changing));
+#endif
+
+ /* ### we need to gather up the target set of children */
+
+ if (kind == svn_node_dir)
+ SVN_ERR(svn_editor_alter_directory(eb->editor, repos_relpath,
+ change->changing, NULL, props));
+ else
+ SVN_ERR(svn_editor_alter_file(eb->editor, repos_relpath,
+ change->changing, props,
+ checksum, contents));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+run_ev2_actions(struct ev2_edit_baton *eb,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool;
+
+ iterpool = svn_pool_create(scratch_pool);
+
+ /* Possibly pick up where we left off. Ocassionally, we do some of these
+ as part of close_edit() and then some more as part of abort_edit() */
+ for (; eb->paths_processed < eb->path_order->nelts; ++eb->paths_processed)
+ {
+ const char *repos_relpath = APR_ARRAY_IDX(eb->path_order,
+ eb->paths_processed,
+ const char *);
+ const struct change_node *change = svn_hash_gets(eb->changes,
+ repos_relpath);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(process_actions(eb, repos_relpath, change, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static const char *
+map_to_repos_relpath(struct ev2_edit_baton *eb,
+ const char *path_or_url,
+ apr_pool_t *result_pool)
+{
+ if (svn_path_is_url(path_or_url))
+ {
+ return svn_uri_skip_ancestor(eb->repos_root, path_or_url, result_pool);
+ }
+ else
+ {
+ return svn_relpath_join(eb->base_relpath,
+ path_or_url[0] == '/'
+ ? path_or_url + 1 : path_or_url,
+ result_pool);
+ }
+}
+
+
+static svn_error_t *
+ev2_set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_edit_baton *eb = edit_baton;
+
+ if (eb->exb->target_revision)
+ SVN_ERR(eb->exb->target_revision(eb->exb->baton, target_revision,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **root_baton)
+{
+ struct ev2_dir_baton *db = apr_pcalloc(result_pool, sizeof(*db));
+ struct ev2_edit_baton *eb = edit_baton;
+
+ db->eb = eb;
+ db->path = apr_pstrdup(eb->edit_pool, eb->base_relpath);
+ db->base_revision = base_revision;
+
+ *root_baton = db;
+
+ if (eb->exb->start_edit)
+ SVN_ERR(eb->exb->start_edit(eb->exb->baton, base_revision));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_dir_baton *pb = parent_baton;
+ svn_revnum_t base_revision;
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ struct change_node *change = locate_change(pb->eb, relpath);
+
+ if (SVN_IS_VALID_REVNUM(revision))
+ base_revision = revision;
+ else
+ base_revision = pb->base_revision;
+
+ SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE);
+ change->action = RESTRUCTURE_DELETE;
+
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->deleting)
+ || change->deleting == base_revision);
+ change->deleting = base_revision;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_add_directory(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *result_pool,
+ void **child_baton)
+{
+ /* ### fix this? */
+ apr_pool_t *scratch_pool = result_pool;
+ struct ev2_dir_baton *pb = parent_baton;
+ struct ev2_dir_baton *cb = apr_pcalloc(result_pool, sizeof(*cb));
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ struct change_node *change = locate_change(pb->eb, relpath);
+
+ /* ### assert that RESTRUCTURE is NONE or DELETE? */
+ change->action = RESTRUCTURE_ADD;
+ change->kind = svn_node_dir;
+
+ cb->eb = pb->eb;
+ cb->path = apr_pstrdup(result_pool, relpath);
+ cb->base_revision = pb->base_revision;
+ *child_baton = cb;
+
+ if (!copyfrom_path)
+ {
+ if (pb->copyfrom_relpath)
+ {
+ const char *name = svn_relpath_basename(relpath, scratch_pool);
+ cb->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name,
+ result_pool);
+ cb->copyfrom_rev = pb->copyfrom_rev;
+ }
+ }
+ else
+ {
+ /* A copy */
+
+ change->copyfrom_path = map_to_repos_relpath(pb->eb, copyfrom_path,
+ pb->eb->edit_pool);
+ change->copyfrom_rev = copyfrom_revision;
+
+ cb->copyfrom_relpath = change->copyfrom_path;
+ cb->copyfrom_rev = change->copyfrom_rev;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **child_baton)
+{
+ /* ### fix this? */
+ apr_pool_t *scratch_pool = result_pool;
+ struct ev2_dir_baton *pb = parent_baton;
+ struct ev2_dir_baton *db = apr_pcalloc(result_pool, sizeof(*db));
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+
+ db->eb = pb->eb;
+ db->path = apr_pstrdup(result_pool, relpath);
+ db->base_revision = base_revision;
+
+ if (pb->copyfrom_relpath)
+ {
+ /* We are inside a copy. */
+ const char *name = svn_relpath_basename(relpath, scratch_pool);
+
+ db->copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath, name,
+ result_pool);
+ db->copyfrom_rev = pb->copyfrom_rev;
+ }
+
+ *child_baton = db;
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_dir_baton *db = dir_baton;
+
+ SVN_ERR(apply_propedit(db->eb, db->path, svn_node_dir, db->base_revision,
+ name, value, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_close_directory(void *dir_baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_absent_directory(const char *path,
+ void *parent_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_dir_baton *pb = parent_baton;
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ struct change_node *change = locate_change(pb->eb, relpath);
+
+ /* ### assert that RESTRUCTURE is NONE or DELETE? */
+ change->action = RESTRUCTURE_ADD_ABSENT;
+ change->kind = svn_node_dir;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_add_file(const char *path,
+ void *parent_baton,
+ const char *copyfrom_path,
+ svn_revnum_t copyfrom_revision,
+ apr_pool_t *result_pool,
+ void **file_baton)
+{
+ /* ### fix this? */
+ apr_pool_t *scratch_pool = result_pool;
+ struct ev2_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
+ struct ev2_dir_baton *pb = parent_baton;
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ struct change_node *change = locate_change(pb->eb, relpath);
+
+ /* ### assert that RESTRUCTURE is NONE or DELETE? */
+ change->action = RESTRUCTURE_ADD;
+ change->kind = svn_node_file;
+
+ fb->eb = pb->eb;
+ fb->path = apr_pstrdup(result_pool, relpath);
+ fb->base_revision = pb->base_revision;
+ *file_baton = fb;
+
+ if (!copyfrom_path)
+ {
+ /* Don't bother fetching the base, as in an add we don't have a base. */
+ fb->delta_base = NULL;
+ }
+ else
+ {
+ /* A copy */
+
+ change->copyfrom_path = map_to_repos_relpath(fb->eb, copyfrom_path,
+ fb->eb->edit_pool);
+ change->copyfrom_rev = copyfrom_revision;
+
+ SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base,
+ fb->eb->fetch_base_baton,
+ change->copyfrom_path,
+ change->copyfrom_rev,
+ result_pool, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **file_baton)
+{
+ /* ### fix this? */
+ apr_pool_t *scratch_pool = result_pool;
+ struct ev2_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
+ struct ev2_dir_baton *pb = parent_baton;
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+
+ fb->eb = pb->eb;
+ fb->path = apr_pstrdup(result_pool, relpath);
+ fb->base_revision = base_revision;
+
+ if (pb->copyfrom_relpath)
+ {
+ /* We're in a copied directory, so the delta base is going to be
+ based up on the copy source. */
+ const char *name = svn_relpath_basename(relpath, scratch_pool);
+ const char *copyfrom_relpath = svn_relpath_join(pb->copyfrom_relpath,
+ name,
+ scratch_pool);
+
+ SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base,
+ fb->eb->fetch_base_baton,
+ copyfrom_relpath, pb->copyfrom_rev,
+ result_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_ERR(fb->eb->fetch_base_func(&fb->delta_base,
+ fb->eb->fetch_base_baton,
+ relpath, base_revision,
+ result_pool, scratch_pool));
+ }
+
+ *file_baton = fb;
+ return SVN_NO_ERROR;
+}
+
+struct handler_baton
+{
+ svn_txdelta_window_handler_t apply_handler;
+ void *apply_baton;
+
+ svn_stream_t *source;
+
+ apr_pool_t *pool;
+};
+
+static svn_error_t *
+window_handler(svn_txdelta_window_t *window, void *baton)
+{
+ struct handler_baton *hb = baton;
+ svn_error_t *err;
+
+ err = hb->apply_handler(window, hb->apply_baton);
+ if (window != NULL && !err)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_stream_close(hb->source));
+
+ svn_pool_destroy(hb->pool);
+
+ return svn_error_trace(err);
+}
+
+
+static svn_error_t *
+ev2_apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *result_pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct ev2_file_baton *fb = file_baton;
+ apr_pool_t *handler_pool = svn_pool_create(fb->eb->edit_pool);
+ struct handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb));
+ struct change_node *change;
+ svn_stream_t *target;
+ /* ### fix this. for now, we know this has a "short" lifetime. */
+ apr_pool_t *scratch_pool = handler_pool;
+
+ change = locate_change(fb->eb, fb->path);
+ SVN_ERR_ASSERT(change->contents_abspath == NULL);
+ SVN_ERR_ASSERT(!SVN_IS_VALID_REVNUM(change->changing)
+ || change->changing == fb->base_revision);
+ change->changing = fb->base_revision;
+
+ if (! fb->delta_base)
+ hb->source = svn_stream_empty(handler_pool);
+ else
+ SVN_ERR(svn_stream_open_readonly(&hb->source, fb->delta_base, handler_pool,
+ scratch_pool));
+
+ SVN_ERR(svn_stream_open_unique(&target, &change->contents_abspath, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ fb->eb->edit_pool, scratch_pool));
+
+ svn_txdelta_apply(hb->source, target,
+ NULL, NULL,
+ handler_pool,
+ &hb->apply_handler, &hb->apply_baton);
+
+ hb->pool = handler_pool;
+
+ *handler_baton = hb;
+ *handler = window_handler;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_file_baton *fb = file_baton;
+
+ if (!strcmp(name, SVN_PROP_ENTRY_LOCK_TOKEN) && value == NULL)
+ {
+ /* We special case the lock token propery deletion, which is the
+ server's way of telling the client to unlock the path. */
+
+ /* ### this duplicates much of apply_propedit(). fix in future. */
+ const char *relpath = map_to_repos_relpath(fb->eb, fb->path,
+ scratch_pool);
+ struct change_node *change = locate_change(fb->eb, relpath);
+
+ change->unlock = TRUE;
+ }
+
+ SVN_ERR(apply_propedit(fb->eb, fb->path, svn_node_file, fb->base_revision,
+ name, value, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_absent_file(const char *path,
+ void *parent_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_dir_baton *pb = parent_baton;
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ struct change_node *change = locate_change(pb->eb, relpath);
+
+ /* ### assert that RESTRUCTURE is NONE or DELETE? */
+ change->action = RESTRUCTURE_ADD_ABSENT;
+ change->kind = svn_node_file;
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+ev2_close_edit(void *edit_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_edit_baton *eb = edit_baton;
+
+ SVN_ERR(run_ev2_actions(edit_baton, scratch_pool));
+ eb->closed = TRUE;
+ return svn_error_trace(svn_editor_complete(eb->editor));
+}
+
+static svn_error_t *
+ev2_abort_edit(void *edit_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev2_edit_baton *eb = edit_baton;
+
+ SVN_ERR(run_ev2_actions(edit_baton, scratch_pool));
+ if (!eb->closed)
+ return svn_error_trace(svn_editor_abort(eb->editor));
+ else
+ return SVN_NO_ERROR;
+}
+
+/* Return a svn_delta_editor_t * in DEDITOR, with an accompanying baton in
+ * DEDITOR_BATON, which will drive EDITOR. These will both be
+ * allocated in RESULT_POOL, which may become large and long-lived;
+ * SCRATCH_POOL is used for temporary allocations.
+ *
+ * The other parameters are as follows:
+ * - UNLOCK_FUNC / UNLOCK_BATON: A callback / baton which will be called
+ * when an unlocking action is received.
+ * - FOUND_ABS_PATHS: A pointer to a boolean flag which will be set if
+ * this shim determines that it is receiving absolute paths.
+ * - FETCH_PROPS_FUNC / FETCH_PROPS_BATON: A callback / baton pair which
+ * will be used by the shim handlers if they need to determine the
+ * existing properties on a path.
+ * - FETCH_BASE_FUNC / FETCH_BASE_BATON: A callback / baton pair which will
+ * be used by the shims handlers if they need to determine the base
+ * text of a path. It should only be invoked for files.
+ * - EXB: An 'extra baton' which is used to communicate between the shims.
+ * Its callbacks should be invoked at the appropriate time by this
+ * shim.
+ */
+svn_error_t *
+svn_delta__delta_from_editor(const svn_delta_editor_t **deditor,
+ void **dedit_baton,
+ svn_editor_t *editor,
+ svn_delta__unlock_func_t unlock_func,
+ void *unlock_baton,
+ svn_boolean_t *found_abs_paths,
+ const char *repos_root,
+ const char *base_relpath,
+ svn_delta_fetch_props_func_t fetch_props_func,
+ void *fetch_props_baton,
+ svn_delta_fetch_base_func_t fetch_base_func,
+ void *fetch_base_baton,
+ struct svn_delta__extra_baton *exb,
+ apr_pool_t *pool)
+{
+ /* Static 'cause we don't want it to be on the stack. */
+ static svn_delta_editor_t delta_editor = {
+ ev2_set_target_revision,
+ ev2_open_root,
+ ev2_delete_entry,
+ ev2_add_directory,
+ ev2_open_directory,
+ ev2_change_dir_prop,
+ ev2_close_directory,
+ ev2_absent_directory,
+ ev2_add_file,
+ ev2_open_file,
+ ev2_apply_textdelta,
+ ev2_change_file_prop,
+ ev2_close_file,
+ ev2_absent_file,
+ ev2_close_edit,
+ ev2_abort_edit
+ };
+ struct ev2_edit_baton *eb = apr_pcalloc(pool, sizeof(*eb));
+
+ if (!base_relpath)
+ base_relpath = "";
+ else if (base_relpath[0] == '/')
+ base_relpath += 1;
+
+ eb->editor = editor;
+ eb->changes = apr_hash_make(pool);
+ eb->path_order = apr_array_make(pool, 1, sizeof(const char *));
+ eb->edit_pool = pool;
+ eb->found_abs_paths = found_abs_paths;
+ *eb->found_abs_paths = FALSE;
+ eb->exb = exb;
+ eb->repos_root = apr_pstrdup(pool, repos_root);
+ eb->base_relpath = apr_pstrdup(pool, base_relpath);
+
+ eb->fetch_props_func = fetch_props_func;
+ eb->fetch_props_baton = fetch_props_baton;
+
+ eb->fetch_base_func = fetch_base_func;
+ eb->fetch_base_baton = fetch_base_baton;
+
+ eb->do_unlock = unlock_func;
+ eb->unlock_baton = unlock_baton;
+
+ *dedit_baton = eb;
+ *deditor = &delta_editor;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* ### note the similarity to struct change_node. these structures will
+ ### be combined in the future. */
+struct operation {
+ /* ### leave these two here for now. still used. */
+ svn_revnum_t base_revision;
+ void *baton;
+};
+
+struct editor_baton
+{
+ const svn_delta_editor_t *deditor;
+ void *dedit_baton;
+
+ svn_delta_fetch_kind_func_t fetch_kind_func;
+ void *fetch_kind_baton;
+
+ svn_delta_fetch_props_func_t fetch_props_func;
+ void *fetch_props_baton;
+
+ struct operation root;
+ svn_boolean_t *make_abs_paths;
+ const char *repos_root;
+ const char *base_relpath;
+
+ /* REPOS_RELPATH -> struct change_node * */
+ apr_hash_t *changes;
+
+ apr_pool_t *edit_pool;
+};
+
+
+/* Insert a new change for RELPATH, or return an existing one. */
+static struct change_node *
+insert_change(const char *relpath,
+ apr_hash_t *changes)
+{
+ apr_pool_t *result_pool;
+ struct change_node *change;
+
+ change = svn_hash_gets(changes, relpath);
+ if (change != NULL)
+ return change;
+
+ result_pool = apr_hash_pool_get(changes);
+
+ /* Return an empty change. Callers will tweak as needed. */
+ change = apr_pcalloc(result_pool, sizeof(*change));
+ change->changing = SVN_INVALID_REVNUM;
+ change->deleting = SVN_INVALID_REVNUM;
+
+ svn_hash_sets(changes, apr_pstrdup(result_pool, relpath), change);
+
+ return change;
+}
+
+
+/* This implements svn_editor_cb_add_directory_t */
+static svn_error_t *
+add_directory_cb(void *baton,
+ const char *relpath,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ struct change_node *change = insert_change(relpath, eb->changes);
+
+ change->action = RESTRUCTURE_ADD;
+ change->kind = svn_node_dir;
+ change->deleting = replaces_rev;
+ change->props = svn_prop_hash_dup(props, eb->edit_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_add_file_t */
+static svn_error_t *
+add_file_cb(void *baton,
+ const char *relpath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ const char *tmp_filename;
+ svn_stream_t *tmp_stream;
+ svn_checksum_t *md5_checksum;
+ struct change_node *change = insert_change(relpath, eb->changes);
+
+ /* We may need to re-checksum these contents */
+ if (!(checksum && checksum->kind == svn_checksum_md5))
+ contents = svn_stream_checksummed2(contents, &md5_checksum, NULL,
+ svn_checksum_md5, TRUE, scratch_pool);
+ else
+ md5_checksum = (svn_checksum_t *)checksum;
+
+ /* Spool the contents to a tempfile, and provide that to the driver. */
+ SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ eb->edit_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(contents, tmp_stream, NULL, NULL, scratch_pool));
+
+ change->action = RESTRUCTURE_ADD;
+ change->kind = svn_node_file;
+ change->deleting = replaces_rev;
+ change->props = svn_prop_hash_dup(props, eb->edit_pool);
+ change->contents_abspath = tmp_filename;
+ change->checksum = svn_checksum_dup(md5_checksum, eb->edit_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_add_symlink_t */
+static svn_error_t *
+add_symlink_cb(void *baton,
+ const char *relpath,
+ const char *target,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+#if 0
+ struct editor_baton *eb = baton;
+ struct change_node *change = insert_change(relpath, eb->changes);
+
+ change->action = RESTRUCTURE_ADD;
+ change->kind = svn_node_symlink;
+ change->deleting = replaces_rev;
+ change->props = svn_prop_hash_dup(props, eb->edit_pool);
+ /* ### target */
+#endif
+
+ SVN__NOT_IMPLEMENTED();
+}
+
+/* This implements svn_editor_cb_add_absent_t */
+static svn_error_t *
+add_absent_cb(void *baton,
+ const char *relpath,
+ svn_node_kind_t kind,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ struct change_node *change = insert_change(relpath, eb->changes);
+
+ change->action = RESTRUCTURE_ADD_ABSENT;
+ change->kind = kind;
+ change->deleting = replaces_rev;
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_alter_directory_t */
+static svn_error_t *
+alter_directory_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ const apr_array_header_t *children,
+ apr_hash_t *props,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ struct change_node *change = insert_change(relpath, eb->changes);
+
+ /* ### should we verify the kind is truly a directory? */
+
+ /* ### do we need to do anything with CHILDREN? */
+
+ /* Note: this node may already have information in CHANGE as a result
+ of an earlier copy/move operation. */
+ change->kind = svn_node_dir;
+ change->changing = revision;
+ change->props = svn_prop_hash_dup(props, eb->edit_pool);
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_alter_file_t */
+static svn_error_t *
+alter_file_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ const char *tmp_filename;
+ svn_stream_t *tmp_stream;
+ svn_checksum_t *md5_checksum;
+ struct change_node *change = insert_change(relpath, eb->changes);
+
+ /* ### should we verify the kind is truly a file? */
+
+ if (contents)
+ {
+ /* We may need to re-checksum these contents */
+ if (!(checksum && checksum->kind == svn_checksum_md5))
+ contents = svn_stream_checksummed2(contents, &md5_checksum, NULL,
+ svn_checksum_md5, TRUE,
+ scratch_pool);
+ else
+ md5_checksum = (svn_checksum_t *)checksum;
+
+ /* Spool the contents to a tempfile, and provide that to the driver. */
+ SVN_ERR(svn_stream_open_unique(&tmp_stream, &tmp_filename, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ eb->edit_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(contents, tmp_stream, NULL, NULL,
+ scratch_pool));
+ }
+
+ /* Note: this node may already have information in CHANGE as a result
+ of an earlier copy/move operation. */
+
+ change->kind = svn_node_file;
+ change->changing = revision;
+ if (props != NULL)
+ change->props = svn_prop_hash_dup(props, eb->edit_pool);
+ if (contents != NULL)
+ {
+ change->contents_abspath = tmp_filename;
+ change->checksum = svn_checksum_dup(md5_checksum, eb->edit_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_alter_symlink_t */
+static svn_error_t *
+alter_symlink_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const char *target,
+ apr_pool_t *scratch_pool)
+{
+ /* ### should we verify the kind is truly a symlink? */
+
+ /* ### do something */
+
+ SVN__NOT_IMPLEMENTED();
+}
+
+/* This implements svn_editor_cb_delete_t */
+static svn_error_t *
+delete_cb(void *baton,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ struct change_node *change = insert_change(relpath, eb->changes);
+
+ change->action = RESTRUCTURE_DELETE;
+ /* change->kind = svn_node_unknown; */
+ change->deleting = revision;
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_copy_t */
+static svn_error_t *
+copy_cb(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ struct change_node *change = insert_change(dst_relpath, eb->changes);
+
+ change->action = RESTRUCTURE_ADD;
+ /* change->kind = svn_node_unknown; */
+ change->deleting = replaces_rev;
+ change->copyfrom_path = apr_pstrdup(eb->edit_pool, src_relpath);
+ change->copyfrom_rev = src_revision;
+
+ /* We need the source's kind to know whether to call add_directory()
+ or add_file() later on. */
+ SVN_ERR(eb->fetch_kind_func(&change->kind, eb->fetch_kind_baton,
+ change->copyfrom_path,
+ change->copyfrom_rev,
+ scratch_pool));
+
+ /* Note: this node may later have alter_*() called on it. */
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_move_t */
+static svn_error_t *
+move_cb(void *baton,
+ const char *src_relpath,
+ svn_revnum_t src_revision,
+ const char *dst_relpath,
+ svn_revnum_t replaces_rev,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ struct change_node *change;
+
+ /* Remap a move into a DELETE + COPY. */
+
+ change = insert_change(src_relpath, eb->changes);
+ change->action = RESTRUCTURE_DELETE;
+ /* change->kind = svn_node_unknown; */
+ change->deleting = src_revision;
+
+ change = insert_change(dst_relpath, eb->changes);
+ change->action = RESTRUCTURE_ADD;
+ /* change->kind = svn_node_unknown; */
+ change->deleting = replaces_rev;
+ change->copyfrom_path = apr_pstrdup(eb->edit_pool, src_relpath);
+ change->copyfrom_rev = src_revision;
+
+ /* We need the source's kind to know whether to call add_directory()
+ or add_file() later on. */
+ SVN_ERR(eb->fetch_kind_func(&change->kind, eb->fetch_kind_baton,
+ change->copyfrom_path,
+ change->copyfrom_rev,
+ scratch_pool));
+
+ /* Note: this node may later have alter_*() called on it. */
+
+ return SVN_NO_ERROR;
+}
+
+/* This implements svn_editor_cb_rotate_t */
+static svn_error_t *
+rotate_cb(void *baton,
+ const apr_array_header_t *relpaths,
+ const apr_array_header_t *revisions,
+ apr_pool_t *scratch_pool)
+{
+ SVN__NOT_IMPLEMENTED();
+}
+
+
+static int
+count_components(const char *relpath)
+{
+ int count = 1;
+ const char *slash = strchr(relpath, '/');
+
+ while (slash != NULL)
+ {
+ ++count;
+ slash = strchr(slash + 1, '/');
+ }
+ return count;
+}
+
+
+static int
+sort_deletes_first(const svn_sort__item_t *item1,
+ const svn_sort__item_t *item2)
+{
+ const char *relpath1 = item1->key;
+ const char *relpath2 = item2->key;
+ const struct change_node *change1 = item1->value;
+ const struct change_node *change2 = item2->value;
+ const char *slash1;
+ const char *slash2;
+ ptrdiff_t len1;
+ ptrdiff_t len2;
+
+ /* Force the root to always sort first. Otherwise, it may look like a
+ sibling of its children (no slashes), and could get sorted *after*
+ any children that get deleted. */
+ if (*relpath1 == '\0')
+ return -1;
+ if (*relpath2 == '\0')
+ return 1;
+
+ /* Are these two items siblings? The 'if' statement tests if they are
+ siblings in the root directory, or that slashes were found in both
+ paths, that the length of the paths to those slashes match, and that
+ the path contents up to those slashes also match. */
+ slash1 = strrchr(relpath1, '/');
+ slash2 = strrchr(relpath2, '/');
+ if ((slash1 == NULL && slash2 == NULL)
+ || (slash1 != NULL
+ && slash2 != NULL
+ && (len1 = slash1 - relpath1) == (len2 = slash2 - relpath2)
+ && memcmp(relpath1, relpath2, len1) == 0))
+ {
+ if (change1->action == RESTRUCTURE_DELETE)
+ {
+ if (change2->action == RESTRUCTURE_DELETE)
+ {
+ /* If both items are being deleted, then we don't care about
+ the order. State they are equal. */
+ return 0;
+ }
+
+ /* ITEM1 is being deleted. Sort it before the surviving item. */
+ return -1;
+ }
+ if (change2->action == RESTRUCTURE_DELETE)
+ /* ITEM2 is being deleted. Sort it before the surviving item. */
+ return 1;
+
+ /* Normally, we don't care about the ordering of two siblings. However,
+ if these siblings are directories, then we need to provide an
+ ordering so that the quicksort algorithm will further sort them
+ relative to the maybe-directory's children.
+
+ Without this additional ordering, we could see that A/B/E and A/B/F
+ are equal. And then A/B/E/child is sorted before A/B/F. But since
+ E and F are "equal", A/B/E could arrive *after* A/B/F and after the
+ A/B/E/child node. */
+
+ /* FALLTHROUGH */
+ }
+
+ /* Paths-to-be-deleted with fewer components always sort earlier.
+
+ For example, gamma will sort before E/alpha.
+
+ Without this test, E/alpha lexicographically sorts before gamma,
+ but gamma sorts before E when gamma is to be deleted. This kind of
+ ordering would place E/alpha before E. Not good.
+
+ With this test, gamma sorts before E/alpha. E and E/alpha are then
+ sorted by svn_path_compare_paths() (which places E before E/alpha). */
+ if (change1->action == RESTRUCTURE_DELETE
+ || change2->action == RESTRUCTURE_DELETE)
+ {
+ int count1 = count_components(relpath1);
+ int count2 = count_components(relpath2);
+
+ if (count1 < count2 && change1->action == RESTRUCTURE_DELETE)
+ return -1;
+ if (count1 > count2 && change2->action == RESTRUCTURE_DELETE)
+ return 1;
+ }
+
+ /* Use svn_path_compare_paths() to get correct depth-based ordering. */
+ return svn_path_compare_paths(relpath1, relpath2);
+}
+
+
+static const apr_array_header_t *
+get_sorted_paths(apr_hash_t *changes,
+ const char *base_relpath,
+ apr_pool_t *scratch_pool)
+{
+ const apr_array_header_t *items;
+ apr_array_header_t *paths;
+ int i;
+
+ /* Construct a sorted array of svn_sort__item_t structs. Within a given
+ directory, nodes that are to be deleted will appear first. */
+ items = svn_sort__hash(changes, sort_deletes_first, scratch_pool);
+
+ /* Build a new array with just the paths, trimmed to relative paths for
+ the Ev1 drive. */
+ paths = apr_array_make(scratch_pool, items->nelts, sizeof(const char *));
+ for (i = items->nelts; i--; )
+ {
+ const svn_sort__item_t *item;
+
+ item = &APR_ARRAY_IDX(items, i, const svn_sort__item_t);
+ APR_ARRAY_IDX(paths, i, const char *)
+ = svn_relpath_skip_ancestor(base_relpath, item->key);
+ }
+
+ /* We didn't use PUSH, so set the proper number of elements. */
+ paths->nelts = items->nelts;
+
+ return paths;
+}
+
+
+static svn_error_t *
+drive_ev1_props(const struct editor_baton *eb,
+ const char *repos_relpath,
+ const struct change_node *change,
+ void *node_baton,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ apr_hash_t *old_props;
+ apr_array_header_t *propdiffs;
+ int i;
+
+ /* If there are no properties to install, then just exit. */
+ if (change->props == NULL)
+ return SVN_NO_ERROR;
+
+ if (change->copyfrom_path)
+ {
+ /* The pristine properties are from the copy/move source. */
+ SVN_ERR(eb->fetch_props_func(&old_props, eb->fetch_props_baton,
+ change->copyfrom_path,
+ change->copyfrom_rev,
+ scratch_pool, iterpool));
+ }
+ else if (change->action == RESTRUCTURE_ADD)
+ {
+ /* Locally-added nodes have no pristine properties.
+
+ Note: we can use iterpool; this hash only needs to survive to
+ the propdiffs call, and there are no contents to preserve. */
+ old_props = apr_hash_make(iterpool);
+ }
+ else
+ {
+ /* Fetch the pristine properties for whatever we're editing. */
+ SVN_ERR(eb->fetch_props_func(&old_props, eb->fetch_props_baton,
+ repos_relpath, change->changing,
+ scratch_pool, iterpool));
+ }
+
+ SVN_ERR(svn_prop_diffs(&propdiffs, change->props, old_props, scratch_pool));
+
+ for (i = 0; i < propdiffs->nelts; i++)
+ {
+ /* Note: the array returned by svn_prop_diffs() is an array of
+ actual structures, not pointers to them. */
+ const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);
+
+ svn_pool_clear(iterpool);
+
+ if (change->kind == svn_node_dir)
+ SVN_ERR(eb->deditor->change_dir_prop(node_baton,
+ prop->name, prop->value,
+ iterpool));
+ else
+ SVN_ERR(eb->deditor->change_file_prop(node_baton,
+ prop->name, prop->value,
+ iterpool));
+ }
+
+ /* Handle the funky unlock protocol. Note: only possibly on files. */
+ if (change->unlock)
+ {
+ SVN_ERR_ASSERT(change->kind == svn_node_file);
+ SVN_ERR(eb->deditor->change_file_prop(node_baton,
+ SVN_PROP_ENTRY_LOCK_TOKEN, NULL,
+ iterpool));
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Conforms to svn_delta_path_driver_cb_func_t */
+static svn_error_t *
+apply_change(void **dir_baton,
+ void *parent_baton,
+ void *callback_baton,
+ const char *ev1_relpath,
+ apr_pool_t *result_pool)
+{
+ /* ### fix this? */
+ apr_pool_t *scratch_pool = result_pool;
+ const struct editor_baton *eb = callback_baton;
+ const struct change_node *change;
+ const char *relpath;
+ void *file_baton = NULL;
+
+ /* Typically, we are not creating new directory batons. */
+ *dir_baton = NULL;
+
+ relpath = svn_relpath_join(eb->base_relpath, ev1_relpath, scratch_pool);
+ change = svn_hash_gets(eb->changes, relpath);
+
+ /* The callback should only be called for paths in CHANGES. */
+ SVN_ERR_ASSERT(change != NULL);
+
+ /* Are we editing the root of the tree? */
+ if (parent_baton == NULL)
+ {
+ /* The root was opened in start_edit_func() */
+ *dir_baton = eb->root.baton;
+
+ /* Only property edits are allowed on the root. */
+ SVN_ERR_ASSERT(change->action == RESTRUCTURE_NONE);
+ SVN_ERR(drive_ev1_props(eb, relpath, change, *dir_baton, scratch_pool));
+
+ /* No further action possible for the root. */
+ return SVN_NO_ERROR;
+ }
+
+ if (change->action == RESTRUCTURE_DELETE)
+ {
+ SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting,
+ parent_baton, scratch_pool));
+
+ /* No futher action possible for this node. */
+ return SVN_NO_ERROR;
+ }
+
+ /* If we're not deleting this node, then we should know its kind. */
+ SVN_ERR_ASSERT(change->kind != svn_node_unknown);
+
+ if (change->action == RESTRUCTURE_ADD_ABSENT)
+ {
+ if (change->kind == svn_node_dir)
+ SVN_ERR(eb->deditor->absent_directory(ev1_relpath, parent_baton,
+ scratch_pool));
+ else
+ SVN_ERR(eb->deditor->absent_file(ev1_relpath, parent_baton,
+ scratch_pool));
+
+ /* No further action possible for this node. */
+ return SVN_NO_ERROR;
+ }
+ /* RESTRUCTURE_NONE or RESTRUCTURE_ADD */
+
+ if (change->action == RESTRUCTURE_ADD)
+ {
+ const char *copyfrom_url = NULL;
+ svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
+
+ /* Do we have an old node to delete first? */
+ if (SVN_IS_VALID_REVNUM(change->deleting))
+ SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting,
+ parent_baton, scratch_pool));
+
+ /* Are we copying the node from somewhere? */
+ if (change->copyfrom_path)
+ {
+ if (eb->repos_root)
+ copyfrom_url = svn_path_url_add_component2(eb->repos_root,
+ change->copyfrom_path,
+ scratch_pool);
+ else
+ copyfrom_url = change->copyfrom_path;
+
+ /* Make this an FS path by prepending "/" */
+ if (copyfrom_url[0] != '/')
+ copyfrom_url = apr_pstrcat(scratch_pool, "/", copyfrom_url, NULL);
+
+ copyfrom_rev = change->copyfrom_rev;
+ }
+
+ if (change->kind == svn_node_dir)
+ SVN_ERR(eb->deditor->add_directory(ev1_relpath, parent_baton,
+ copyfrom_url, copyfrom_rev,
+ result_pool, dir_baton));
+ else
+ SVN_ERR(eb->deditor->add_file(ev1_relpath, parent_baton,
+ copyfrom_url, copyfrom_rev,
+ result_pool, &file_baton));
+ }
+ else
+ {
+ if (change->kind == svn_node_dir)
+ SVN_ERR(eb->deditor->open_directory(ev1_relpath, parent_baton,
+ change->changing,
+ result_pool, dir_baton));
+ else
+ SVN_ERR(eb->deditor->open_file(ev1_relpath, parent_baton,
+ change->changing,
+ result_pool, &file_baton));
+ }
+
+ /* Apply any properties in CHANGE to the node. */
+ if (change->kind == svn_node_dir)
+ SVN_ERR(drive_ev1_props(eb, relpath, change, *dir_baton, scratch_pool));
+ else
+ SVN_ERR(drive_ev1_props(eb, relpath, change, file_baton, scratch_pool));
+
+ if (change->contents_abspath)
+ {
+ svn_txdelta_window_handler_t handler;
+ void *handler_baton;
+ svn_stream_t *contents;
+
+ /* ### would be nice to have a BASE_CHECKSUM, but hey: this is the
+ ### shim code... */
+ SVN_ERR(eb->deditor->apply_textdelta(file_baton, NULL, scratch_pool,
+ &handler, &handler_baton));
+ SVN_ERR(svn_stream_open_readonly(&contents, change->contents_abspath,
+ scratch_pool, scratch_pool));
+ /* ### it would be nice to send a true txdelta here, but whatever. */
+ SVN_ERR(svn_txdelta_send_stream(contents, handler, handler_baton,
+ NULL, scratch_pool));
+ SVN_ERR(svn_stream_close(contents));
+ }
+
+ if (file_baton)
+ {
+ const char *digest = svn_checksum_to_cstring(change->checksum,
+ scratch_pool);
+
+ SVN_ERR(eb->deditor->close_file(file_baton, digest, scratch_pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+drive_changes(const struct editor_baton *eb,
+ apr_pool_t *scratch_pool)
+{
+ struct change_node *change;
+ const apr_array_header_t *paths;
+
+ /* If we never opened a root baton, then the caller aborted the editor
+ before it even began. There is nothing to do. Bail. */
+ if (eb->root.baton == NULL)
+ return SVN_NO_ERROR;
+
+ /* We need to make the path driver believe we want to make changes to
+ the root. Otherwise, it will attempt an open_root(), which we already
+ did in start_edit_func(). We can forge up a change record, if one
+ does not already exist. */
+ change = insert_change(eb->base_relpath, eb->changes);
+ change->kind = svn_node_dir;
+ /* No property changes (tho they might exist from a real change). */
+
+ /* Get a sorted list of Ev1-relative paths. */
+ paths = get_sorted_paths(eb->changes, eb->base_relpath, scratch_pool);
+ SVN_ERR(svn_delta_path_driver2(eb->deditor, eb->dedit_baton, paths,
+ FALSE, apply_change, (void *)eb,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements svn_editor_cb_complete_t */
+static svn_error_t *
+complete_cb(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ svn_error_t *err;
+
+ /* Drive the tree we've created. */
+ err = drive_changes(eb, scratch_pool);
+ if (!err)
+ {
+ err = svn_error_compose_create(err, eb->deditor->close_edit(
+ eb->dedit_baton,
+ scratch_pool));
+ }
+
+ if (err)
+ svn_error_clear(eb->deditor->abort_edit(eb->dedit_baton, scratch_pool));
+
+ return svn_error_trace(err);
+}
+
+/* This implements svn_editor_cb_abort_t */
+static svn_error_t *
+abort_cb(void *baton,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+ svn_error_t *err;
+ svn_error_t *err2;
+
+ /* We still need to drive anything we collected in the editor to this
+ point. */
+
+ /* Drive the tree we've created. */
+ err = drive_changes(eb, scratch_pool);
+
+ err2 = eb->deditor->abort_edit(eb->dedit_baton, scratch_pool);
+
+ if (err2)
+ {
+ if (err)
+ svn_error_clear(err2);
+ else
+ err = err2;
+ }
+
+ return svn_error_trace(err);
+}
+
+static svn_error_t *
+start_edit_func(void *baton,
+ svn_revnum_t base_revision)
+{
+ struct editor_baton *eb = baton;
+
+ eb->root.base_revision = base_revision;
+
+ /* For some Ev1 editors (such as the repos commit editor), the root must
+ be open before can invoke any callbacks. The open_root() call sets up
+ stuff (eg. open an FS txn) which will be needed. */
+ SVN_ERR(eb->deditor->open_root(eb->dedit_baton, eb->root.base_revision,
+ eb->edit_pool, &eb->root.baton));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+target_revision_func(void *baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+
+ SVN_ERR(eb->deditor->set_target_revision(eb->dedit_baton, target_revision,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+do_unlock(void *baton,
+ const char *path,
+ apr_pool_t *scratch_pool)
+{
+ struct editor_baton *eb = baton;
+
+ {
+ /* PATH is REPOS_RELPATH */
+ struct change_node *change = insert_change(path, eb->changes);
+
+ /* We will need to propagate a deletion of SVN_PROP_ENTRY_LOCK_TOKEN */
+ change->unlock = TRUE;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Return an svn_editor_t * in EDITOR_P which will drive
+ * DEDITOR/DEDIT_BATON. EDITOR_P is allocated in RESULT_POOL, which may
+ * become large and long-lived; SCRATCH_POOL is used for temporary
+ * allocations.
+ *
+ * The other parameters are as follows:
+ * - EXB: An 'extra_baton' used for passing information between the coupled
+ * shims. This includes actions like 'start edit' and 'set target'.
+ * As this shim receives these actions, it provides the extra baton
+ * to its caller.
+ * - UNLOCK_FUNC / UNLOCK_BATON: A callback / baton pair which a caller
+ * can use to notify this shim that a path should be unlocked (in the
+ * 'svn lock' sense). As this shim receives this action, it provides
+ * this callback / baton to its caller.
+ * - SEND_ABS_PATHS: A pointer which will be set prior to this edit (but
+ * not necessarily at the invocation of editor_from_delta()),and
+ * which indicates whether incoming paths should be expected to
+ * be absolute or relative.
+ * - CANCEL_FUNC / CANCEL_BATON: The usual; folded into the produced editor.
+ * - FETCH_KIND_FUNC / FETCH_KIND_BATON: A callback / baton pair which will
+ * be used by the shim handlers if they need to determine the kind of
+ * a path.
+ * - FETCH_PROPS_FUNC / FETCH_PROPS_BATON: A callback / baton pair which
+ * will be used by the shim handlers if they need to determine the
+ * existing properties on a path.
+ */
+svn_error_t *
+svn_delta__editor_from_delta(svn_editor_t **editor_p,
+ struct svn_delta__extra_baton **exb,
+ svn_delta__unlock_func_t *unlock_func,
+ void **unlock_baton,
+ const svn_delta_editor_t *deditor,
+ void *dedit_baton,
+ svn_boolean_t *send_abs_paths,
+ const char *repos_root,
+ const char *base_relpath,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ svn_delta_fetch_kind_func_t fetch_kind_func,
+ void *fetch_kind_baton,
+ svn_delta_fetch_props_func_t fetch_props_func,
+ void *fetch_props_baton,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_editor_t *editor;
+ static const svn_editor_cb_many_t editor_cbs = {
+ add_directory_cb,
+ add_file_cb,
+ add_symlink_cb,
+ add_absent_cb,
+ alter_directory_cb,
+ alter_file_cb,
+ alter_symlink_cb,
+ delete_cb,
+ copy_cb,
+ move_cb,
+ rotate_cb,
+ complete_cb,
+ abort_cb
+ };
+ struct editor_baton *eb = apr_pcalloc(result_pool, sizeof(*eb));
+ struct svn_delta__extra_baton *extra_baton = apr_pcalloc(result_pool,
+ sizeof(*extra_baton));
+
+ if (!base_relpath)
+ base_relpath = "";
+ else if (base_relpath[0] == '/')
+ base_relpath += 1;
+
+ eb->deditor = deditor;
+ eb->dedit_baton = dedit_baton;
+ eb->edit_pool = result_pool;
+ eb->repos_root = apr_pstrdup(result_pool, repos_root);
+ eb->base_relpath = apr_pstrdup(result_pool, base_relpath);
+
+ eb->changes = apr_hash_make(result_pool);
+
+ eb->fetch_kind_func = fetch_kind_func;
+ eb->fetch_kind_baton = fetch_kind_baton;
+ eb->fetch_props_func = fetch_props_func;
+ eb->fetch_props_baton = fetch_props_baton;
+
+ eb->root.base_revision = SVN_INVALID_REVNUM;
+
+ eb->make_abs_paths = send_abs_paths;
+
+ SVN_ERR(svn_editor_create(&editor, eb, cancel_func, cancel_baton,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_editor_setcb_many(editor, &editor_cbs, scratch_pool));
+
+ *editor_p = editor;
+
+ *unlock_func = do_unlock;
+ *unlock_baton = eb;
+
+ extra_baton->start_edit = start_edit_func;
+ extra_baton->target_revision = target_revision_func;
+ extra_baton->baton = eb;
+
+ *exb = extra_baton;
+
+ return SVN_NO_ERROR;
+}
+
+svn_delta_shim_callbacks_t *
+svn_delta_shim_callbacks_default(apr_pool_t *result_pool)
+{
+ svn_delta_shim_callbacks_t *shim_callbacks = apr_pcalloc(result_pool,
+ sizeof(*shim_callbacks));
+ return shim_callbacks;
+}
+
+/* To enable editor shims throughout Subversion, ENABLE_EV2_SHIMS should be
+ * defined. This can be done manually, or by providing `--enable-ev2-shims'
+ * to `configure'. */
+
+svn_error_t *
+svn_editor__insert_shims(const svn_delta_editor_t **deditor_out,
+ void **dedit_baton_out,
+ const svn_delta_editor_t *deditor_in,
+ void *dedit_baton_in,
+ const char *repos_root,
+ const char *base_relpath,
+ svn_delta_shim_callbacks_t *shim_callbacks,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+#ifndef ENABLE_EV2_SHIMS
+ /* Shims disabled, just copy the editor and baton directly. */
+ *deditor_out = deditor_in;
+ *dedit_baton_out = dedit_baton_in;
+#else
+ /* Use our shim APIs to create an intermediate svn_editor_t, and then
+ wrap that again back into a svn_delta_editor_t. This introduces
+ a lot of overhead. */
+ svn_editor_t *editor;
+
+ /* The "extra baton" is a set of functions and a baton which allows the
+ shims to communicate additional events to each other.
+ svn_delta__editor_from_delta() returns a pointer to this baton, which
+ svn_delta__delta_from_editor() should then store. */
+ struct svn_delta__extra_baton *exb;
+
+ /* The reason this is a pointer is that we don't know the appropriate
+ value until we start receiving paths. So process_actions() sets the
+ flag, which drive_tree() later consumes. */
+ svn_boolean_t *found_abs_paths = apr_palloc(result_pool,
+ sizeof(*found_abs_paths));
+
+ svn_delta__unlock_func_t unlock_func;
+ void *unlock_baton;
+
+ SVN_ERR_ASSERT(shim_callbacks->fetch_kind_func != NULL);
+ SVN_ERR_ASSERT(shim_callbacks->fetch_props_func != NULL);
+ SVN_ERR_ASSERT(shim_callbacks->fetch_base_func != NULL);
+
+ SVN_ERR(svn_delta__editor_from_delta(&editor, &exb,
+ &unlock_func, &unlock_baton,
+ deditor_in, dedit_baton_in,
+ found_abs_paths, repos_root, base_relpath,
+ NULL, NULL,
+ shim_callbacks->fetch_kind_func,
+ shim_callbacks->fetch_baton,
+ shim_callbacks->fetch_props_func,
+ shim_callbacks->fetch_baton,
+ result_pool, scratch_pool));
+ SVN_ERR(svn_delta__delta_from_editor(deditor_out, dedit_baton_out, editor,
+ unlock_func, unlock_baton,
+ found_abs_paths,
+ repos_root, base_relpath,
+ shim_callbacks->fetch_props_func,
+ shim_callbacks->fetch_baton,
+ shim_callbacks->fetch_base_func,
+ shim_callbacks->fetch_baton,
+ exb, result_pool));
+
+#endif
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_delta/compose_delta.c b/subversion/libsvn_delta/compose_delta.c
new file mode 100644
index 0000000..7b96438
--- /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_empty(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..7c2cdec
--- /dev/null
+++ b/subversion/libsvn_delta/debug_editor.c
@@ -0,0 +1,437 @@
+/*
+ * 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 "svn_io.h"
+
+#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;
+
+ /* This is DBG_FLAG from ../libsvn_subr/debug.c */
+ SVN_ERR(svn_stream_puts(eb->out, "DBG:"));
+ for (i = 0; i < eb->indent_level; ++i)
+ SVN_ERR(svn_stream_puts(eb->out, " "));
+
+ 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..95fe4f7
--- /dev/null
+++ b/subversion/libsvn_delta/delta.h
@@ -0,0 +1,96 @@
+/*
+ * 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);
+
+/* Remove / truncate the last delta ops spanning the last MAX_LEN bytes
+ from the delta window being built via BUILD_BATON starting. Return the
+ number of bytes that were actually removed. */
+apr_size_t
+svn_txdelta__remove_copy(svn_txdelta__ops_baton_t *build_baton,
+ apr_size_t max_len);
+
+/* 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/deprecated.c b/subversion/libsvn_delta/deprecated.c
new file mode 100644
index 0000000..4171244
--- /dev/null
+++ b/subversion/libsvn_delta/deprecated.c
@@ -0,0 +1,48 @@
+/*
+ * deprecated.c: holding file for all deprecated APIs.
+ * "we can't lose 'em, but we can shun 'em!"
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+/* We define this here to remove any further warnings about the usage of
+ deprecated functions in this file. */
+#define SVN_DEPRECATED
+
+#include "svn_delta.h"
+#include "svn_sorts.h"
+
+
+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 *scratch_pool)
+{
+ /* REVISION is dropped on the floor. */
+
+ return svn_error_trace(svn_delta_path_driver2(editor, edit_baton, paths,
+ TRUE,
+ callback_func, callback_baton,
+ scratch_pool));
+}
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..1dc94b2
--- /dev/null
+++ b/subversion/libsvn_delta/editor.c
@@ -0,0 +1,956 @@
+/*
+ * 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 "svn_dirent_uri.h"
+
+#include "private/svn_editor.h"
+
+#ifdef SVN_DEBUG
+/* This enables runtime checks of the editor API constraints. This may
+ introduce additional memory and runtime overhead, and should not be used
+ in production builds.
+
+ ### Remove before release?
+
+ ### Disabled for now. If I call svn_editor_alter_directory(A) then
+ svn_editor_add_file(A/f) the latter fails on SHOULD_ALLOW_ADD.
+ If I modify svn_editor_alter_directory to MARK_ALLOW_ADD(child)
+ then if I call svn_editor_alter_directory(A) followed by
+ svn_editor_alter_directory(A/B/C) the latter fails on
+ VERIFY_PARENT_MAY_EXIST. */
+#if 0
+#define ENABLE_ORDERING_CHECK
+#endif
+#endif
+
+
+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;
+
+#ifdef ENABLE_ORDERING_CHECK
+ svn_boolean_t within_callback;
+
+ apr_hash_t *pending_incomplete_children;
+ apr_hash_t *completed_nodes;
+ svn_boolean_t finished;
+
+ apr_pool_t *state_pool;
+#endif
+};
+
+
+#ifdef ENABLE_ORDERING_CHECK
+
+#define START_CALLBACK(editor) \
+ do { \
+ svn_editor_t *editor__tmp_e = (editor); \
+ SVN_ERR_ASSERT(!editor__tmp_e->within_callback); \
+ editor__tmp_e->within_callback = TRUE; \
+ } while (0)
+#define END_CALLBACK(editor) ((editor)->within_callback = FALSE)
+
+/* Marker to indicate no further changes are allowed on this node. */
+static const int marker_done = 0;
+#define MARKER_DONE (&marker_done)
+
+/* Marker indicating that add_* may be called for this path, or that it
+ can be the destination of a copy or move. For copy/move, the path
+ will switch to MARKER_ALLOW_ALTER, to enable further tweaks. */
+static const int marker_allow_add = 0;
+#define MARKER_ALLOW_ADD (&marker_allow_add)
+
+/* Marker indicating that alter_* may be called for this path. */
+static const int marker_allow_alter = 0;
+#define MARKER_ALLOW_ALTER (&marker_allow_alter)
+
+/* Just like MARKER_DONE, but also indicates that the node was created
+ via add_directory(). This allows us to verify that the CHILDREN param
+ was comprehensive. */
+static const int marker_added_dir = 0;
+#define MARKER_ADDED_DIR (&marker_added_dir)
+
+#define MARK_FINISHED(editor) ((editor)->finished = TRUE)
+#define SHOULD_NOT_BE_FINISHED(editor) SVN_ERR_ASSERT(!(editor)->finished)
+
+#define CLEAR_INCOMPLETE(editor, relpath) \
+ svn_hash_sets((editor)->pending_incomplete_children, relpath, NULL);
+
+#define MARK_RELPATH(editor, relpath, value) \
+ svn_hash_sets((editor)->completed_nodes, \
+ apr_pstrdup((editor)->state_pool, relpath), value)
+
+#define MARK_COMPLETED(editor, relpath) \
+ MARK_RELPATH(editor, relpath, MARKER_DONE)
+#define SHOULD_NOT_BE_COMPLETED(editor, relpath) \
+ SVN_ERR_ASSERT(svn_hash_gets((editor)->completed_nodes, relpath) == NULL)
+
+#define MARK_ALLOW_ADD(editor, relpath) \
+ MARK_RELPATH(editor, relpath, MARKER_ALLOW_ADD)
+#define SHOULD_ALLOW_ADD(editor, relpath) \
+ SVN_ERR_ASSERT(allow_either(editor, relpath, MARKER_ALLOW_ADD, NULL))
+
+#define MARK_ALLOW_ALTER(editor, relpath) \
+ MARK_RELPATH(editor, relpath, MARKER_ALLOW_ALTER)
+#define SHOULD_ALLOW_ALTER(editor, relpath) \
+ SVN_ERR_ASSERT(allow_either(editor, relpath, MARKER_ALLOW_ALTER, NULL))
+
+#define MARK_ADDED_DIR(editor, relpath) \
+ MARK_RELPATH(editor, relpath, MARKER_ADDED_DIR)
+#define CHECK_UNKNOWN_CHILD(editor, relpath) \
+ SVN_ERR_ASSERT(check_unknown_child(editor, relpath))
+
+/* When a child is changed in some way, mark the parent directory as needing
+ to be "stable" (no future structural changes). IOW, only allow "alter" on
+ the parent. Prevents parent-add/delete/move after any child operation. */
+#define MARK_PARENT_STABLE(editor, relpath) \
+ mark_parent_stable(editor, relpath)
+
+/* If the parent is MARKER_ALLOW_ADD, then it has been moved-away, and we
+ know it does not exist. All other cases: it might exist. */
+#define VERIFY_PARENT_MAY_EXIST(editor, relpath) \
+ SVN_ERR_ASSERT(svn_hash_gets((editor)->completed_nodes, \
+ svn_relpath_dirname(relpath, \
+ (editor)->scratch_pool)) \
+ != MARKER_ALLOW_ADD)
+
+/* If the parent is MARKER_ADDED_DIR, then we should not be deleting
+ children(*). If the parent is MARKER_ALLOW_ADD, then it has been
+ moved-away, so children cannot exist. That leaves MARKER_DONE,
+ MARKER_ALLOW_ALTER, and NULL as possible values. Just assert that
+ we didn't get either of the bad ones.
+
+ (*) if the child as added via add_*(), then it would have been marked
+ as completed and delete/move-away already test against completed nodes.
+ This test is to beware of trying to delete "children" that are not
+ actually (and can't possibly be) present. */
+#define CHILD_DELETIONS_ALLOWED(editor, relpath) \
+ SVN_ERR_ASSERT(!allow_either(editor, \
+ svn_relpath_dirname(relpath, \
+ (editor)->scratch_pool), \
+ MARKER_ADDED_DIR, MARKER_ALLOW_ADD))
+
+static svn_boolean_t
+allow_either(const svn_editor_t *editor,
+ const char *relpath,
+ const void *marker1,
+ const void *marker2)
+{
+ void *value = svn_hash_gets(editor->completed_nodes, relpath);
+ return value == marker1 || value == marker2;
+}
+
+static svn_boolean_t
+check_unknown_child(const svn_editor_t *editor,
+ const char *relpath)
+{
+ const char *parent;
+
+ /* If we already know about the new child, then exit early. */
+ if (svn_hash_gets(editor->pending_incomplete_children, relpath) != NULL)
+ return TRUE;
+
+ parent = svn_relpath_dirname(relpath, editor->scratch_pool);
+
+ /* Was this parent created via svn_editor_add_directory() ? */
+ if (svn_hash_gets(editor->completed_nodes, parent)
+ == MARKER_ADDED_DIR)
+ {
+ /* Whoops. This child should have been listed in that add call,
+ and placed into ->pending_incomplete_children. */
+ return FALSE;
+ }
+
+ /* The parent was not added in this drive. */
+ return TRUE;
+}
+
+static void
+mark_parent_stable(const svn_editor_t *editor,
+ const char *relpath)
+{
+ const char *parent = svn_relpath_dirname(relpath, editor->scratch_pool);
+ const void *marker = svn_hash_gets(editor->completed_nodes, parent);
+
+ /* If RELPATH has already been marked (to disallow adds, or that it
+ has been fully-completed), then do nothing. */
+ if (marker == MARKER_ALLOW_ALTER
+ || marker == MARKER_DONE
+ || marker == MARKER_ADDED_DIR)
+ return;
+
+ /* If the marker is MARKER_ALLOW_ADD, then that means the parent was
+ moved away. There is no way to work on a child. That should have
+ been tested before we got here by VERIFY_PARENT_MAY_EXIST(). */
+ SVN_ERR_ASSERT_NO_RETURN(marker != MARKER_ALLOW_ADD);
+
+ /* MARKER is NULL. Upgrade it to MARKER_ALLOW_ALTER. */
+ MARK_RELPATH(editor, parent, MARKER_ALLOW_ALTER);
+}
+
+#else
+
+/* Be wary with the definition of these macros so that we don't
+ end up with "statement with no effect" warnings. Obviously, this
+ depends upon particular usage, which is easy to verify. */
+
+#define START_CALLBACK(editor) /* empty */
+#define END_CALLBACK(editor) /* empty */
+
+#define MARK_FINISHED(editor) /* empty */
+#define SHOULD_NOT_BE_FINISHED(editor) /* empty */
+
+#define CLEAR_INCOMPLETE(editor, relpath) /* empty */
+
+#define MARK_COMPLETED(editor, relpath) /* empty */
+#define SHOULD_NOT_BE_COMPLETED(editor, relpath) /* empty */
+
+#define MARK_ALLOW_ADD(editor, relpath) /* empty */
+#define SHOULD_ALLOW_ADD(editor, relpath) /* empty */
+
+#define MARK_ALLOW_ALTER(editor, relpath) /* empty */
+#define SHOULD_ALLOW_ALTER(editor, relpath) /* empty */
+
+#define MARK_ADDED_DIR(editor, relpath) /* empty */
+#define CHECK_UNKNOWN_CHILD(editor, relpath) /* empty */
+
+#define MARK_PARENT_STABLE(editor, relpath) /* empty */
+#define VERIFY_PARENT_MAY_EXIST(editor, relpath) /* empty */
+#define CHILD_DELETIONS_ALLOWED(editor, relpath) /* empty */
+
+#endif /* ENABLE_ORDERING_CHECK */
+
+
+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);
+
+#ifdef ENABLE_ORDERING_CHECK
+ (*editor)->pending_incomplete_children = apr_hash_make(result_pool);
+ (*editor)->completed_nodes = apr_hash_make(result_pool);
+ (*editor)->finished = FALSE;
+ (*editor)->state_pool = result_pool;
+#endif
+
+ return SVN_NO_ERROR;
+}
+
+
+void *
+svn_editor_get_baton(const svn_editor_t *editor)
+{
+ return editor->baton;
+}
+
+
+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_alter_directory(svn_editor_t *editor,
+ svn_editor_cb_alter_directory_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_alter_directory = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_alter_file(svn_editor_t *editor,
+ svn_editor_cb_alter_file_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_alter_file = callback;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_editor_setcb_alter_symlink(svn_editor_t *editor,
+ svn_editor_cb_alter_symlink_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_alter_symlink = 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_rotate(svn_editor_t *editor,
+ svn_editor_cb_rotate_t callback,
+ apr_pool_t *scratch_pool)
+{
+ editor->funcs.cb_rotate = 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_alter_directory);
+ COPY_CALLBACK(cb_alter_file);
+ COPY_CALLBACK(cb_alter_symlink);
+ COPY_CALLBACK(cb_delete);
+ COPY_CALLBACK(cb_copy);
+ COPY_CALLBACK(cb_move);
+ COPY_CALLBACK(cb_rotate);
+ COPY_CALLBACK(cb_complete);
+ COPY_CALLBACK(cb_abort);
+
+#undef COPY_CALLBACK
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+check_cancel(svn_editor_t *editor)
+{
+ svn_error_t *err = NULL;
+
+ if (editor->cancel_func)
+ {
+ START_CALLBACK(editor);
+ err = editor->cancel_func(editor->cancel_baton);
+ END_CALLBACK(editor);
+ }
+
+ return svn_error_trace(err);
+}
+
+
+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_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SVN_ERR_ASSERT(children != NULL);
+ SVN_ERR_ASSERT(props != NULL);
+ /* ### validate children are just basenames? */
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ADD(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+ CHECK_UNKNOWN_CHILD(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_add_directory)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_add_directory(editor->baton, relpath, children,
+ props, replaces_rev,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_ADDED_DIR(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+ CLEAR_INCOMPLETE(editor, relpath);
+
+#ifdef ENABLE_ORDERING_CHECK
+ {
+ int i;
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *child_basename = APR_ARRAY_IDX(children, i, const char *);
+ const char *child = svn_relpath_join(relpath, child_basename,
+ editor->state_pool);
+
+ svn_hash_sets(editor->pending_incomplete_children, child, "");
+ }
+ }
+#endif
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_add_file(svn_editor_t *editor,
+ const char *relpath,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents,
+ apr_hash_t *props,
+ svn_revnum_t replaces_rev)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SVN_ERR_ASSERT(checksum != NULL
+ && checksum->kind == SVN_EDITOR_CHECKSUM_KIND);
+ SVN_ERR_ASSERT(contents != NULL);
+ SVN_ERR_ASSERT(props != NULL);
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ADD(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+ CHECK_UNKNOWN_CHILD(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_add_file)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_add_file(editor->baton, relpath,
+ checksum, contents, props,
+ replaces_rev, editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_COMPLETED(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+ CLEAR_INCOMPLETE(editor, relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(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_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SVN_ERR_ASSERT(props != NULL);
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ADD(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+ CHECK_UNKNOWN_CHILD(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_add_symlink)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_add_symlink(editor->baton, relpath, target, props,
+ replaces_rev, editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_COMPLETED(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+ CLEAR_INCOMPLETE(editor, relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(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_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ADD(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+ CHECK_UNKNOWN_CHILD(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_add_absent)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_add_absent(editor->baton, relpath, kind,
+ replaces_rev, editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_COMPLETED(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+ CLEAR_INCOMPLETE(editor, relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_alter_directory(svn_editor_t *editor,
+ const char *relpath,
+ svn_revnum_t revision,
+ const apr_array_header_t *children,
+ apr_hash_t *props)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SVN_ERR_ASSERT(children != NULL || props != NULL);
+ /* ### validate children are just basenames? */
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ALTER(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_alter_directory)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_alter_directory(editor->baton,
+ relpath, revision,
+ children, props,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_COMPLETED(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+
+#ifdef ENABLE_ORDERING_CHECK
+ /* ### this is not entirely correct. we probably need to adjust the
+ ### check_unknown_child() function for this scenario. */
+#if 0
+ {
+ int i;
+ for (i = 0; i < children->nelts; i++)
+ {
+ const char *child_basename = APR_ARRAY_IDX(children, i, const char *);
+ const char *child = svn_relpath_join(relpath, child_basename,
+ editor->state_pool);
+
+ apr_hash_set(editor->pending_incomplete_children, child,
+ APR_HASH_KEY_STRING, "");
+ /* Perhaps MARK_ALLOW_ADD(editor, child); ? */
+ }
+ }
+#endif
+#endif
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_alter_file(svn_editor_t *editor,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const svn_checksum_t *checksum,
+ svn_stream_t *contents)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SVN_ERR_ASSERT((checksum != NULL && contents != NULL)
+ || (checksum == NULL && contents == NULL));
+ SVN_ERR_ASSERT(props != NULL || checksum != NULL);
+ if (checksum)
+ SVN_ERR_ASSERT(checksum->kind == SVN_EDITOR_CHECKSUM_KIND);
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ALTER(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_alter_file)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_alter_file(editor->baton,
+ relpath, revision, props,
+ checksum, contents,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_COMPLETED(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_alter_symlink(svn_editor_t *editor,
+ const char *relpath,
+ svn_revnum_t revision,
+ apr_hash_t *props,
+ const char *target)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SVN_ERR_ASSERT(props != NULL || target != NULL);
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ALTER(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_alter_symlink)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_alter_symlink(editor->baton,
+ relpath, revision, props,
+ target,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_COMPLETED(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_delete(svn_editor_t *editor,
+ const char *relpath,
+ svn_revnum_t revision)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_NOT_BE_COMPLETED(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+ CHILD_DELETIONS_ALLOWED(editor, relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_delete)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_delete(editor->baton, relpath, revision,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_COMPLETED(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(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_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath));
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(dst_relpath));
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_ALLOW_ADD(editor, dst_relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, src_relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, dst_relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_copy)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_copy(editor->baton, src_relpath, src_revision,
+ dst_relpath, replaces_rev,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_ALLOW_ALTER(editor, dst_relpath);
+ MARK_PARENT_STABLE(editor, dst_relpath);
+ CLEAR_INCOMPLETE(editor, dst_relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(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_NO_ERROR;
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(src_relpath));
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(dst_relpath));
+ SHOULD_NOT_BE_FINISHED(editor);
+ SHOULD_NOT_BE_COMPLETED(editor, src_relpath);
+ SHOULD_ALLOW_ADD(editor, dst_relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, src_relpath);
+ CHILD_DELETIONS_ALLOWED(editor, src_relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, dst_relpath);
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_move)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_move(editor->baton, src_relpath, src_revision,
+ dst_relpath, replaces_rev,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_ALLOW_ADD(editor, src_relpath);
+ MARK_PARENT_STABLE(editor, src_relpath);
+ MARK_ALLOW_ALTER(editor, dst_relpath);
+ MARK_PARENT_STABLE(editor, dst_relpath);
+ CLEAR_INCOMPLETE(editor, dst_relpath);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_rotate(svn_editor_t *editor,
+ const apr_array_header_t *relpaths,
+ const apr_array_header_t *revisions)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SHOULD_NOT_BE_FINISHED(editor);
+#ifdef ENABLE_ORDERING_CHECK
+ {
+ int i;
+ for (i = 0; i < relpaths->nelts; i++)
+ {
+ const char *relpath = APR_ARRAY_IDX(relpaths, i, const char *);
+
+ SVN_ERR_ASSERT(svn_relpath_is_canonical(relpath));
+ SHOULD_NOT_BE_COMPLETED(editor, relpath);
+ VERIFY_PARENT_MAY_EXIST(editor, relpath);
+ CHILD_DELETIONS_ALLOWED(editor, relpath);
+ }
+ }
+#endif
+
+ SVN_ERR(check_cancel(editor));
+
+ if (editor->funcs.cb_rotate)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_rotate(editor->baton, relpaths, revisions,
+ editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+#ifdef ENABLE_ORDERING_CHECK
+ {
+ int i;
+ for (i = 0; i < relpaths->nelts; i++)
+ {
+ const char *relpath = APR_ARRAY_IDX(relpaths, i, const char *);
+ MARK_ALLOW_ALTER(editor, relpath);
+ MARK_PARENT_STABLE(editor, relpath);
+ }
+ }
+#endif
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_complete(svn_editor_t *editor)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SHOULD_NOT_BE_FINISHED(editor);
+#ifdef ENABLE_ORDERING_CHECK
+ SVN_ERR_ASSERT(apr_hash_count(editor->pending_incomplete_children) == 0);
+#endif
+
+ if (editor->funcs.cb_complete)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_complete(editor->baton, editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_FINISHED(editor);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_editor_abort(svn_editor_t *editor)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ SHOULD_NOT_BE_FINISHED(editor);
+
+ if (editor->funcs.cb_abort)
+ {
+ START_CALLBACK(editor);
+ err = editor->funcs.cb_abort(editor->baton, editor->scratch_pool);
+ END_CALLBACK(editor);
+ }
+
+ MARK_FINISHED(editor);
+
+ svn_pool_clear(editor->scratch_pool);
+ return svn_error_trace(err);
+}
diff --git a/subversion/libsvn_delta/path_driver.c b/subversion/libsvn_delta/path_driver.c
new file mode 100644
index 0000000..62e703a
--- /dev/null
+++ b/subversion/libsvn_delta/path_driver.c
@@ -0,0 +1,298 @@
+/*
+ * 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 argument, 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,
+ 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, SVN_INVALID_REVNUM, 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_driver2(const svn_delta_editor_t *editor,
+ void *edit_baton,
+ const apr_array_header_t *paths,
+ svn_boolean_t sort_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);
+
+ /* sort paths if necessary */
+ if (sort_paths && paths->nelts > 1)
+ {
+ apr_array_header_t *sorted = apr_array_copy(subpool, paths);
+ qsort(sorted->elts, sorted->nelts, sorted->elt_size,
+ svn_sort_compare_paths);
+ paths = sorted;
+ }
+
+ item = apr_pcalloc(subpool, sizeof(*item));
+
+ /* 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, SVN_INVALID_REVNUM, 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, 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..f4b9dc6
--- /dev/null
+++ b/subversion/libsvn_delta/svndiff.c
@@ -0,0 +1,1103 @@
+/*
+ * 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>
+
+#include "private/svn_error_private.h"
+#include "private/svn_delta_private.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 and the COMPRESSION_LEVEL
+ is not SVN_DELTA_COMPRESSION_LEVEL_NONE, zlib compress it and places 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;
+
+ svn_stringbuf_setempty(out);
+ 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
+ {
+ int zerr;
+
+ svn_stringbuf_ensure(out, svnCompressBound(len) + intlen);
+ endlen = out->blocksize;
+
+ zerr = compress2((unsigned char *)out->data + intlen, &endlen,
+ (const unsigned char *)data, len,
+ compression_level);
+ if (zerr != Z_OK)
+ return svn_error_trace(svn_error__wrap_zlib(
+ zerr, "compress2",
+ _("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;
+ out->data[out->len] = 0;
+ }
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+send_simple_insertion_window(svn_txdelta_window_t *window,
+ struct encoder_baton *eb)
+{
+ unsigned char headers[4 + 5 * MAX_ENCODED_INT_LEN + MAX_INSTRUCTION_LEN];
+ unsigned char ibuf[MAX_INSTRUCTION_LEN];
+ unsigned char *header_current;
+ apr_size_t header_len;
+ apr_size_t ip_len, i;
+ apr_size_t len = window->new_data->len;
+
+ /* there is only one target copy op. It must span the whole window */
+ assert(window->ops[0].action_code == svn_txdelta_new);
+ assert(window->ops[0].length == window->tview_len);
+ assert(window->ops[0].offset == 0);
+
+ /* write stream header if necessary */
+ if (!eb->header_done)
+ {
+ eb->header_done = TRUE;
+ headers[0] = 'S';
+ headers[1] = 'V';
+ headers[2] = 'N';
+ headers[3] = (unsigned char)eb->version;
+ header_current = headers + 4;
+ }
+ else
+ {
+ header_current = headers;
+ }
+
+ /* Encode the action code and length. */
+ if (window->tview_len >> 6 == 0)
+ {
+ ibuf[0] = (unsigned char)(window->tview_len + (0x2 << 6));
+ ip_len = 1;
+ }
+ else
+ {
+ ibuf[0] = (0x2 << 6);
+ ip_len = encode_int(ibuf + 1, window->tview_len) - ibuf;
+ }
+
+ /* encode the window header. Please note that the source window may
+ * have content despite not being used for deltification. */
+ header_current = encode_int(header_current, window->sview_offset);
+ header_current = encode_int(header_current, window->sview_len);
+ header_current = encode_int(header_current, window->tview_len);
+ header_current[0] = (unsigned char)ip_len; /* 1 instruction */
+ header_current = encode_int(&header_current[1], len);
+
+ /* append instructions (1 to a handful of bytes) */
+ for (i = 0; i < ip_len; ++i)
+ header_current[i] = ibuf[i];
+
+ header_len = header_current - headers + ip_len;
+
+ /* Write out the window. */
+ SVN_ERR(svn_stream_write(eb->output, (const char *)headers, &header_len));
+ if (len)
+ SVN_ERR(svn_stream_write(eb->output, window->new_data->data, &len));
+
+ 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_stringbuf_t *instructions;
+ svn_stringbuf_t *i1;
+ svn_stringbuf_t *header;
+ const svn_string_t *newdata;
+ unsigned char ibuf[MAX_INSTRUCTION_LEN], *ip;
+ const svn_txdelta_op_t *op;
+ apr_size_t len;
+
+ /* use specialized code if there is no source */
+ if (window && !window->src_ops && window->num_ops == 1 && !eb->version)
+ return svn_error_trace(send_simple_insertion_window(window, eb));
+
+ /* Make sure we write the header. */
+ if (!eb->header_done)
+ {
+ char svnver[4] = {'S','V','N','\0'};
+ len = 4;
+ svnver[3] = (char)eb->version;
+ SVN_ERR(svn_stream_write(eb->output, svnver, &len));
+ eb->header_done = TRUE;
+ }
+
+ if (window == NULL)
+ {
+ svn_stream_t *output = eb->output;
+
+ /* We're done; clean up.
+
+ We clean our pool first. Given that the output stream was passed
+ TO us, we'll assume it has a longer lifetime, and that it will not
+ be affected by our pool destruction.
+
+ The contrary point of view (close the stream first): that could
+ tell our user that everything related to the output stream is done,
+ and a cleanup of the user pool should occur. However, that user
+ pool could include the subpool we created for our work (eb->pool),
+ which would then make our call to svn_pool_destroy() puke.
+ */
+ svn_pool_destroy(eb->pool);
+
+ return svn_stream_close(output);
+ }
+
+ /* create the necessary data buffers */
+ pool = svn_pool_create(eb->pool);
+ instructions = svn_stringbuf_create_empty(pool);
+ i1 = svn_stringbuf_create_empty(pool);
+ header = svn_stringbuf_create_empty(pool);
+
+ /* Encode the instructions. */
+ 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++ |= (unsigned char)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_empty(pool);
+ svn_string_t *tempstr = svn_string_create_empty(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 of length INLEN 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.
+ In that case, we will simply return pointer into IN as data pointer for
+ OUT, COPYLESS_ALLOWED has been set. The, the caller is expected not to
+ modify the contents of OUT.
+ An error is returned if the decoded length exceeds the given LIMIT.
+ */
+static svn_error_t *
+zlib_decode(const unsigned char *in, apr_size_t inLen, svn_stringbuf_t *out,
+ apr_size_t limit)
+{
+ apr_size_t len;
+ const unsigned char *oldplace = in;
+
+ /* First thing in the string is the original length. */
+ in = decode_size(&len, in, in + inLen);
+ if (in == 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. */
+ inLen -= (in - oldplace);
+ if (inLen == len)
+ {
+ svn_stringbuf_ensure(out, len);
+ memcpy(out->data, in, len);
+ out->data[len] = 0;
+ out->len = len;
+
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ unsigned long zlen = len;
+ int zerr;
+
+ svn_stringbuf_ensure(out, len);
+ zerr = uncompress((unsigned char *)out->data, &zlen, in, inLen);
+ if (zerr != Z_OK)
+ return svn_error_trace(svn_error__wrap_zlib(
+ zerr, "uncompress",
+ _("Decompression of svndiff data failed")));
+
+ /* Zlib should not produce something that has a different size than the
+ original length we stored. */
+ if (zlen != len)
+ return svn_error_create(SVN_ERR_SVNDIFF_INVALID_COMPRESSED_DATA,
+ NULL,
+ _("Size of uncompressed data "
+ "does not match stored original length"));
+ out->data[zlen] = 0;
+ out->len = zlen;
+ }
+ 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 *instout = svn_stringbuf_create_empty(pool);
+ svn_stringbuf_t *ndout = svn_stringbuf_create_empty(pool);
+
+ /* these may in fact simply return references to insend */
+
+ SVN_ERR(zlib_decode(insend, newlen, ndout,
+ SVN_DELTA_WINDOW_SIZE));
+ SVN_ERR(zlib_decode(data, insend - data, instout,
+ MAX_INSTRUCTION_SECTION_LEN));
+
+ newlen = ndout->len;
+ data = (unsigned char *)instout->data;
+ insend = (unsigned char *)instout->data + instout->len;
+
+ new_data->data = (const char *) ndout->data;
+ new_data->len = newlen;
+ }
+ 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
+ || (apr_size_t)sview_offset + sview_len < (apr_size_t)sview_offset)
+ return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
+ _("Svndiff contains corrupt window header"));
+
+ /* Check for source windows which slide backwards. */
+ if (sview_len > 0
+ && (sview_offset < db->last_sview_offset
+ || (sview_offset + sview_len
+ < db->last_sview_offset + db->last_sview_len)))
+ return svn_error_create
+ (SVN_ERR_SVNDIFF_BACKWARD_VIEW, NULL,
+ _("Svndiff has backwards-sliding source views"));
+
+ /* 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 */
+}
+
+/* Minimal svn_stream_t write handler, doing nothing */
+static svn_error_t *
+noop_write_handler(void *baton,
+ const char *buffer,
+ apr_size_t *len)
+{
+ return SVN_NO_ERROR;
+}
+
+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_empty(db->subpool);
+ db->last_sview_offset = 0;
+ db->last_sview_len = 0;
+ db->header_bytes = 0;
+ db->error_on_early_close = error_on_early_close;
+ stream = svn_stream_create(db, pool);
+
+ if (handler != svn_delta_noop_window_handler)
+ {
+ svn_stream_set_write(stream, write_handler);
+ svn_stream_set_close(stream, close_handler);
+ }
+ else
+ {
+ /* And else we just ignore everything as efficiently as we can.
+ by only hooking a no-op handler */
+ svn_stream_set_write(stream, noop_write_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
+ || (apr_size_t)*sview_offset + *sview_len < (apr_size_t)*sview_offset)
+ return svn_error_create(SVN_ERR_SVNDIFF_CORRUPT_WINDOW, NULL,
+ _("Svndiff contains corrupt window header"));
+
+ 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);
+}
+
+
+svn_error_t *
+svn__compress(svn_string_t *in,
+ svn_stringbuf_t *out,
+ int compression_level)
+{
+ return zlib_encode(in->data, in->len, out, compression_level);
+}
+
+svn_error_t *
+svn__decompress(svn_string_t *in,
+ svn_stringbuf_t *out,
+ apr_size_t limit)
+{
+ return zlib_decode((const unsigned char*)in->data, in->len, out, limit);
+}
diff --git a/subversion/libsvn_delta/text_delta.c b/subversion/libsvn_delta/text_delta.c
new file mode 100644
index 0000000..be2c434
--- /dev/null
+++ b/subversion/libsvn_delta/text_delta.c
@@ -0,0 +1,1041 @@
+/*
+ * 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; /* If not NULL, the 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_empty(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;
+}
+
+apr_size_t
+svn_txdelta__remove_copy(svn_txdelta__ops_baton_t *build_baton,
+ apr_size_t max_len)
+{
+ svn_txdelta_op_t *op;
+ apr_size_t len = 0;
+
+ /* remove ops back to front */
+ while (build_baton->num_ops > 0)
+ {
+ op = &build_baton->ops[build_baton->num_ops-1];
+
+ /* we can't modify svn_txdelta_target ops -> stop there */
+ if (op->action_code == svn_txdelta_target)
+ break;
+
+ /* handle the case that we cannot remove the op entirely */
+ if (op->length + len > max_len)
+ {
+ /* truncate only insertions. Copies don't benefit
+ from being truncated. */
+ if (op->action_code == svn_txdelta_new)
+ {
+ build_baton->new_data->len -= max_len - len;
+ op->length -= max_len - len;
+ len = max_len;
+ }
+
+ break;
+ }
+
+ /* drop the op entirely */
+ if (op->action_code == svn_txdelta_new)
+ build_baton->new_data->len -= op->length;
+
+ len += op->length;
+ --build_baton->num_ops;
+ }
+
+ return len;
+}
+
+
+
+/* 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;
+
+ /* If checksumming has not been activated, there will be no digest. */
+ if (b->context == NULL)
+ 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_txdelta2(svn_txdelta_stream_t **stream,
+ svn_stream_t *source,
+ svn_stream_t *target,
+ svn_boolean_t calculate_checksum,
+ 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 = calculate_checksum
+ ? svn_checksum_ctx_create(svn_checksum_md5, pool)
+ : NULL;
+ b->result_pool = pool;
+
+ *stream = svn_txdelta_stream_create(b, txdelta_next_window,
+ txdelta_md5_digest, pool);
+}
+
+void
+svn_txdelta(svn_txdelta_stream_t **stream,
+ svn_stream_t *source,
+ svn_stream_t *target,
+ apr_pool_t *pool)
+{
+ svn_txdelta2(stream, source, target, TRUE, 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(sbuf);
+ 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 ( (apr_size_t)ab->sbuf_offset + ab->sbuf_len
+ > (apr_size_t)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_window_t delta_window = { 0 };
+ svn_txdelta_op_t delta_op;
+ svn_string_t window_data;
+ char read_buf[SVN__STREAM_CHUNK_SIZE + 1];
+ svn_checksum_ctx_t *md5_checksum_ctx;
+
+ if (digest)
+ md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+
+ while (1)
+ {
+ apr_size_t read_len = SVN__STREAM_CHUNK_SIZE;
+
+ SVN_ERR(svn_stream_read(stream, read_buf, &read_len));
+ if (read_len == 0)
+ break;
+
+ window_data.data = read_buf;
+ window_data.len = read_len;
+
+ delta_op.action_code = svn_txdelta_new;
+ delta_op.offset = 0;
+ delta_op.length = read_len;
+
+ delta_window.tview_len = read_len;
+ delta_window.num_ops = 1;
+ delta_window.ops = &delta_op;
+ delta_window.new_data = &window_data;
+
+ SVN_ERR(handler(&delta_window, handler_baton));
+
+ if (digest)
+ SVN_ERR(svn_checksum_update(md5_checksum_ctx, read_buf, read_len));
+
+ if (read_len < SVN__STREAM_CHUNK_SIZE)
+ break;
+ }
+ SVN_ERR(handler(NULL, handler_baton));
+
+ if (digest)
+ {
+ svn_checksum_t *md5_checksum;
+
+ SVN_ERR(svn_checksum_final(&md5_checksum, md5_checksum_ctx, pool));
+ memcpy(digest, md5_checksum->digest, APR_MD5_DIGESTSIZE);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+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;
+}
+
+svn_error_t *
+svn_txdelta_send_contents(const unsigned char *contents,
+ apr_size_t len,
+ svn_txdelta_window_handler_t handler,
+ void *handler_baton,
+ apr_pool_t *pool)
+{
+ svn_string_t new_data;
+ svn_txdelta_op_t op = { svn_txdelta_new, 0, 0 };
+ svn_txdelta_window_t window = { 0, 0, 0, 1, 0 };
+ window.ops = &op;
+ window.new_data = &new_data;
+
+ /* send CONTENT as a series of max-sized windows */
+ while (len > 0)
+ {
+ /* stuff next chunk into the window */
+ window.tview_len = len < SVN_DELTA_WINDOW_SIZE
+ ? len
+ : SVN_DELTA_WINDOW_SIZE;
+ op.length = window.tview_len;
+ new_data.len = window.tview_len;
+ new_data.data = (const char*)contents;
+
+ /* update remaining */
+ contents += window.tview_len;
+ len -= window.tview_len;
+
+ /* shove it at the handler */
+ SVN_ERR((*handler)(&window, handler_baton));
+ }
+
+ /* indicate end of stream */
+ SVN_ERR((*handler)(NULL, handler_baton));
+
+ 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..2075a51
--- /dev/null
+++ b/subversion/libsvn_delta/xdelta.c
@@ -0,0 +1,514 @@
+/*
+ * 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_hash.h"
+#include "svn_delta.h"
+#include "delta.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
+
+/* "no" / "invalid" / "unused" value for positions within the delta windows
+ */
+#define NO_POSITION ((apr_uint32_t)-1)
+
+/* 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 pseudo-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;
+
+/* Even in 64 bit systems, store only 32 bit offsets in our hash table
+ (our delta window size much much smaller then 4GB).
+ That reduces the hash table size by 50% from 32to 16KB
+ and makes it easier to fit into the CPU's L1 cache. */
+ apr_uint32_t pos; /* NO_POSITION -> block is not used */
+};
+
+/* A hash table, using open addressing, of the blocks of the source. */
+struct blocks
+{
+ /* The largest valid index of slots.
+ This value has an upper bound proportionate to the text delta
+ window size, so unless we dramatically increase the window size,
+ it's safe to make this a 32-bit value. In any case, it has to be
+ hte same width as the block position index, (struct
+ block).pos. */
+ apr_uint32_t max;
+ /* Source buffer that the positions in SLOTS refer to. */
+ const char* data;
+ /* The vector of blocks. A pos value of NO_POSITION 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_uint32_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_uint32_t pos)
+{
+ apr_uint32_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 != NO_POSITION; 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 NO_POSITION. */
+static apr_uint32_t
+find_block(const struct blocks *blocks,
+ apr_uint32_t adlersum,
+ const char* data)
+{
+ apr_uint32_t h = hash_func(adlersum) & blocks->max;
+
+ for (; blocks->slots[h].pos != NO_POSITION; 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 NO_POSITION;
+}
+
+/* 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 nblocks;
+ apr_size_t wnslots = 1;
+ apr_uint32_t nslots;
+ apr_uint32_t i;
+
+ /* Be pessimistic about the block count. */
+ nblocks = datalen / MATCH_BLOCKSIZE + 1;
+ /* Find nearest larger power of two. */
+ while (wnslots <= nblocks)
+ wnslots *= 2;
+ /* Double the number of slots to avoid a too high load. */
+ wnslots *= 2;
+ /* Narrow the number of slots to 32 bits, which is the size of the
+ block position index in the hash table.
+ Sanity check: On 64-bit platforms, apr_size_t is likely to be
+ larger than apr_uint32_t. Make sure that the number of slots
+ actually fits into blocks->max. It's safe to use a hard assert
+ here, because the largest possible value for nslots is
+ proportional to the text delta window size and is therefore much
+ smaller than the range of an apr_uint32_t. If we ever happen to
+ increase the window size too much, this assertion will get
+ triggered by the test suite. */
+ nslots = (apr_uint32_t) wnslots;
+ SVN_ERR_ASSERT_NO_RETURN(wnslots == nslots);
+ 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 = NO_POSITION;
+ }
+
+ /* 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;
+}
+
+/* Return the number of bytes before A and B that don't differ. If no
+ * difference can be found in the first MAX_LEN characters, MAX_LEN will
+ * be returned. Please note that A-MAX_LEN and B-MAX_LEN must both be
+ * valid addresses.
+ */
+static apr_size_t
+reverse_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); pos <= max_len; pos += sizeof(apr_size_t))
+ if (*(const apr_size_t*)(a - pos) != *(const apr_size_t*)(b - pos))
+ break;
+
+ pos -= sizeof(apr_size_t);
+
+#endif
+
+ /* If we find a mismatch at -pos, pos-1 characters matched.
+ */
+ while (++pos <= max_len)
+ if (a[0-pos] != b[0-pos])
+ return pos - 1;
+
+ /* No mismatch found -> at least MAX_LEN matching chars.
+ */
+ return max_len;
+}
+
+
+/* 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 corresponding
+ 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 == NO_POSITION)
+ 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;
+}
+
+/* Utility for compute_delta() that compares the range B[START,BSIZE) with
+ * the range of similar size before A[ASIZE]. Create corresponding copy and
+ * insert operations.
+ *
+ * BUILD_BATON and POOL will be passed through from compute_delta().
+ */
+static void
+store_delta_trailer(svn_txdelta__ops_baton_t *build_baton,
+ const char *a,
+ apr_size_t asize,
+ const char *b,
+ apr_size_t bsize,
+ apr_size_t start,
+ apr_pool_t *pool)
+{
+ apr_size_t end_match;
+ apr_size_t max_len = asize > (bsize - start) ? bsize - start : asize;
+ if (max_len == 0)
+ return;
+
+ end_match = reverse_match_length(a + asize, b + bsize, max_len);
+ if (end_match <= 4)
+ end_match = 0;
+
+ if (bsize - start > end_match)
+ svn_txdelta__insert_op(build_baton, svn_txdelta_new,
+ start, bsize - start - end_match, b + start, pool);
+ if (end_match)
+ svn_txdelta__insert_op(build_baton, svn_txdelta_source,
+ asize - end_match, end_match, NULL, pool);
+}
+
+
+/* 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_size_t asize,
+ const char *b,
+ apr_size_t bsize,
+ apr_pool_t *pool)
+{
+ struct blocks blocks;
+ apr_uint32_t rolling;
+ apr_size_t lo = 0, pending_insert_start = 0;
+
+ /* Optimization: directly compare window starts. If more than 4
+ * bytes match, we can immediately create a matching windows.
+ * Shorter sequences result in a net data increase. */
+ lo = match_length(a, b, asize > bsize ? bsize : asize);
+ if ((lo > 4) || (lo == bsize))
+ {
+ svn_txdelta__insert_op(build_baton, svn_txdelta_source,
+ 0, lo, NULL, pool);
+ pending_insert_start = lo;
+ }
+ else
+ lo = 0;
+
+ /* If the size of the target is smaller than the match blocksize, just
+ insert the entire target. */
+ if ((bsize - lo < MATCH_BLOCKSIZE) || (asize < MATCH_BLOCKSIZE))
+ {
+ store_delta_trailer(build_baton, a, asize, b, bsize, lo, pool);
+ return;
+ }
+
+ /* Initialize the matches table. */
+ init_blocks_table(a, asize, &blocks, pool);
+
+ /* Initialize our rolling checksum. */
+ rolling = init_adler32(b + lo);
+ 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);
+ else
+ {
+ /* the match borders on the previous op. Maybe, we found a
+ * match that is better than / overlapping the previous one. */
+ apr_size_t len = reverse_match_length(a + apos, b + lo, apos < lo ? apos : lo);
+ if (len > 0)
+ {
+ len = svn_txdelta__remove_copy(build_baton, len);
+ apos -= len;
+ matchlen += len;
+ lo -= len;
+ }
+ }
+
+ /* 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. */
+ store_delta_trailer(build_baton, a, asize, b, bsize, 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);
+}
OpenPOWER on IntegriCloud