diff options
author | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
---|---|---|
committer | peter <peter@FreeBSD.org> | 2013-06-18 02:07:41 +0000 |
commit | d25dac7fcc6acc838b71bbda8916fd9665c709ab (patch) | |
tree | 135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_delta | |
download | FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz |
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_delta')
-rw-r--r-- | subversion/libsvn_delta/cancel.c | 378 | ||||
-rw-r--r-- | subversion/libsvn_delta/compat.c | 2010 | ||||
-rw-r--r-- | subversion/libsvn_delta/compose_delta.c | 837 | ||||
-rw-r--r-- | subversion/libsvn_delta/debug_editor.c | 437 | ||||
-rw-r--r-- | subversion/libsvn_delta/debug_editor.h | 49 | ||||
-rw-r--r-- | subversion/libsvn_delta/default_editor.c | 161 | ||||
-rw-r--r-- | subversion/libsvn_delta/delta.h | 96 | ||||
-rw-r--r-- | subversion/libsvn_delta/deprecated.c | 48 | ||||
-rw-r--r-- | subversion/libsvn_delta/depth_filter_editor.c | 485 | ||||
-rw-r--r-- | subversion/libsvn_delta/editor.c | 956 | ||||
-rw-r--r-- | subversion/libsvn_delta/path_driver.c | 298 | ||||
-rw-r--r-- | subversion/libsvn_delta/svndiff.c | 1103 | ||||
-rw-r--r-- | subversion/libsvn_delta/text_delta.c | 1041 | ||||
-rw-r--r-- | subversion/libsvn_delta/version.c | 33 | ||||
-rw-r--r-- | subversion/libsvn_delta/xdelta.c | 514 |
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); +} |