diff options
Diffstat (limited to 'subversion/libsvn_delta/compat.c')
-rw-r--r-- | subversion/libsvn_delta/compat.c | 2010 |
1 files changed, 2010 insertions, 0 deletions
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; +} |