summaryrefslogtreecommitdiff
path: root/subversion/libsvn_delta/compat.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_delta/compat.c')
-rw-r--r--subversion/libsvn_delta/compat.c1942
1 files changed, 1939 insertions, 3 deletions
diff --git a/subversion/libsvn_delta/compat.c b/subversion/libsvn_delta/compat.c
index 7d12be9..470efa2 100644
--- a/subversion/libsvn_delta/compat.c
+++ b/subversion/libsvn_delta/compat.c
@@ -21,13 +21,23 @@
* ====================================================================
*/
-#include <apr_pools.h>
+#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;
@@ -67,7 +77,7 @@ svn_compat_wrap_file_rev_handler(svn_file_rev_handler_t *handler2,
void *handler_baton,
apr_pool_t *pool)
{
- struct file_rev_handler_wrapper_baton *fwb = apr_palloc(pool, sizeof(*fwb));
+ 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;
@@ -76,3 +86,1929 @@ svn_compat_wrap_file_rev_handler(svn_file_rev_handler_t *handler2,
*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;
+ change->kind = svn_node_unknown;
+
+ 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;
+}