You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by ju...@apache.org on 2014/07/29 15:35:28 UTC
svn commit: r1614346 - in /subversion/branches/move-tracking-2/subversion:
include/private/svn_delta_private.h libsvn_delta/compat3.c
Author: julianfoad
Date: Tue Jul 29 13:35:28 2014
New Revision: 1614346
URL: http://svn.apache.org/r1614346
Log:
On the 'moves-tracking-2' branch: Add an Ev3-to-Ev1 commit editor shim,
completing the commit editor shims.
This is not fully working -- a few tests still fail when this pair of shims
is inserted in line with the repository-layer commit editor.
* subversion/include/private/svn_delta_private.h
(svn_delta__shim_connector_t): New.
(svn_delta__ev3_from_delta_for_commit): Return a 'shim connector' object
instead of just one of the possible shim connector callbacks.
(svn_delta__delta_from_ev3_for_commit): New.
(svn_editor3__insert_shims): New.
* subversion/libsvn_delta/compat3.c
Many changes to implement the above, including some debugging of the
existing Ev3-to-Ev1 shim code.
Modified:
subversion/branches/move-tracking-2/subversion/include/private/svn_delta_private.h
subversion/branches/move-tracking-2/subversion/libsvn_delta/compat3.c
Modified: subversion/branches/move-tracking-2/subversion/include/private/svn_delta_private.h
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/include/private/svn_delta_private.h?rev=1614346&r1=1614345&r2=1614346&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/include/private/svn_delta_private.h (original)
+++ subversion/branches/move-tracking-2/subversion/include/private/svn_delta_private.h Tue Jul 29 13:35:28 2014
@@ -102,6 +102,10 @@ svn_delta__delta_from_editor(const svn_d
struct svn_delta__extra_baton *exb,
apr_pool_t *pool);
+/* An object for communicating out-of-band details between an Ev1-to-Ev3
+ * shim and an Ev3-to-Ev1 shim. */
+typedef struct svn_delta__shim_connector_t svn_delta__shim_connector_t;
+
/* Return an Ev3 editor in *EDITOR_P which will drive the Ev1 delta
* editor DEDITOR/DEDIT_BATON.
*
@@ -120,20 +124,15 @@ svn_delta__delta_from_editor(const svn_d
* This editor implements the "incremental changes" variant of the Ev3
* commit editor interface.
*
+ * SHIM_CONNECTOR can be used to enable a more exact round-trip conversion
+ * from an Ev1 drive to Ev3 and back to Ev1. The caller should pass the
+ * returned *SHIM_CONNECTOR value to svn_delta__delta_from_ev3_for_commit().
+ * SHIM_CONNECTOR may be null if not wanted.
+ *
* REPOS_ROOT_URL is the repository root URL, and BASE_RELPATH is the
* relative path within the repository of the root directory of the edit.
* (An Ev1 edit must be rooted at a directory, not at a file.)
*
- * OPEN_ROOT_FUNC can be used to enable a more exact round-trip conversion
- * from an Ev1 drive to Ev3 and back to Ev1. If OPEN_ROOT_FUNC is not
- * null, set *OPEN_ROOT_FUNC to a callback that the Ev3 driver may call to
- * provide the "base revision" of the root directory, even if it is not
- * going to modify that directory. (If it does modify it, then it will
- * pass in the appropriate base revision at that time.) If OPEN_ROOT_FUNC
- * is null or the driver does not call the callback, then the Ev1
- * open_root() method will be called with SVN_INVALID_REVNUM as the base
- * revision parameter.
- *
* FETCH_KIND_FUNC / FETCH_KIND_BATON: A callback by which the shim may
* determine the kind of a path. This is called for a copy source or move
* source node, passing the Ev3 relpath and the specific copy-from
@@ -154,7 +153,7 @@ svn_delta__delta_from_editor(const svn_d
svn_error_t *
svn_delta__ev3_from_delta_for_commit(
svn_editor3_t **editor_p,
- svn_delta__start_edit_func_t *open_root_func,
+ svn_delta__shim_connector_t **shim_connector,
const svn_delta_editor_t *deditor,
void *dedit_baton,
const char *repos_root,
@@ -168,6 +167,65 @@ svn_delta__ev3_from_delta_for_commit(
apr_pool_t *result_pool,
apr_pool_t *scratch_pool);
+/* Return a delta editor in DEDITOR/DEDITOR_BATON which will drive EDITOR.
+ *
+ * REPOS_ROOT_URL is the repository root URL, and BASE_RELPATH is the
+ * relative path within the repository of the root directory of the edit.
+ * (An Ev1 edit must be rooted at a directory, not at a file.)
+ *
+ * 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.
+ *
+ * SHIM_CONNECTOR can be used to enable a more exact round-trip conversion
+ * from an Ev1 drive to Ev3 and back to Ev1. It must live for the lifetime
+ * of the edit. It may be null if not wanted.
+ *
+ * Allocate the new editor in RESULT_POOL, which may become large and must
+ * live for the lifetime of the edit. Use SCRATCH_POOL for temporary
+ * allocations.
+ */
+svn_error_t *
+svn_delta__delta_from_ev3_for_commit(
+ const svn_delta_editor_t **deditor,
+ void **dedit_baton,
+ svn_editor3_t *editor,
+ 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,
+ const svn_delta__shim_connector_t *shim_connector,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
+/* Return in NEW_DEDITOR/NEW_DETIT_BATON a delta editor that wraps
+ * OLD_DEDITOR/OLD_DEDIT_BATON, inserting a pair of shims that convert
+ * Ev1 to Ev3 and back to Ev1.
+ *
+ * REPOS_ROOT_URL is the repository root URL, and BASE_RELPATH is the
+ * relative path within the repository of the root directory of the edit.
+ *
+ * SHIM_CB provides callbacks that the shims may use to fetch details of
+ * the base state when needed.
+ */
+svn_error_t *
+svn_editor3__insert_shims(
+ const svn_delta_editor_t **new_deditor,
+ void **new_dedit_baton,
+ const svn_delta_editor_t *old_deditor,
+ void *old_dedit_baton,
+ const char *repos_root,
+ const char *base_relpath,
+ const svn_delta_shim_callbacks_t *shim_cb,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool);
+
/** Read the txdelta window header from @a stream and return the total
length of the unparsed window data in @a *window_len. */
svn_error_t *
Modified: subversion/branches/move-tracking-2/subversion/libsvn_delta/compat3.c
URL: http://svn.apache.org/viewvc/subversion/branches/move-tracking-2/subversion/libsvn_delta/compat3.c?rev=1614346&r1=1614345&r2=1614346&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/libsvn_delta/compat3.c (original)
+++ subversion/branches/move-tracking-2/subversion/libsvn_delta/compat3.c Tue Jul 29 13:35:28 2014
@@ -33,47 +33,128 @@
#include "svn_pools.h"
#include "private/svn_delta_private.h"
+#include "../libsvn_delta/debug_editor.h"
#include "svn_private_config.h"
/* Verify EXPR is true; raise an error if not. */
#define VERIFY(expr) SVN_ERR_ASSERT(expr);
+
+/*
+ * ========================================================================
+ * Configuration Options
+ * ========================================================================
+ */
+
/* Features that are not wanted for this commit editor shim but may be
* wanted in a similar but different shim such as for an update editor. */
/* #define SHIM_WITH_ADD_ABSENT */
/* #define SHIM_WITH_UNLOCK */
+/* The Ev2 shim ran the accumulated actions during abort... But why?
+ * If we don't, then aborting and re-opening a commit txn doesn't find
+ * all the previous changes, so tests/libsvn_repos/repos-test 12 fails.
+ */
+#define SHIM_WITH_ACTIONS_DURING_ABORT
+
+/* Whether to support switching from relative to absolute paths in the
+ * Ev1 methods. */
+/* #define SHIM_WITH_ABS_PATHS */
+
/*
- * ===================================================================
- * Commit Editor converter to join a v3 driver to a v1 consumer
- * ===================================================================
- *
- * This editor buffers all the changes before driving the Ev1 at the end,
- * since it needs to do a single depth-first traversal of the path space
- * and this cannot be started until all moves are known.
+ * ========================================================================
+ * Shim Connector
+ * ========================================================================
*
- * Moves are converted to copy-and-delete, with the copy being from
- * the source peg rev. (### Should it request copy-from revision "-1"?)
+ * The shim connector enables a more exact round-trip conversion from an
+ * Ev1 drive to Ev3 and back to Ev1.
*/
+struct svn_delta__shim_connector_t
+{
+ /* Set to true if and when an Ev1 receiving shim receives an absolute
+ * path (prefixed with '/') from the delta edit, and causes the Ev1
+ * sending shim to send absolute paths.
+ * ### NOT IMPLEMENTED
+ */
+#ifdef SHIM_WITH_ABS_PATHS
+ svn_boolean_t *ev1_absolute_paths;
+#endif
-/* TODO
- *
- * ### For changes inside a copied subtree, the calls to the "open dir"
- * and "open file" Ev1 methods may be passing the wrong revision
- * number: see comment in apply_change().
- *
- * ### Have we got our rel-paths in order? Ev1, Ev3 and callbacks may
- * all expect different paths. 'repos_relpath' or relative to
- * eb->base_relpath? Leading slash (unimplemented 'send_abs_paths'
- * feature), etc.
- *
- * ### May be tidier for OPEN_ROOT_FUNC callback (see open_root_ev3())
- * not to actually open the root in advance, but instead just to
- * remember the base revision that the driver wants us to specify
- * when we do open it.
- */
+ /* The Ev1 set_target_revision and start_edit methods, respectively, will
+ * call the TARGET_REVISION_FUNC and START_EDIT_FUNC callbacks, if non-null.
+ * Otherwise, default calls will be used.
+ *
+ * (Possibly more useful for update editors than for commit editors?) */
+ svn_delta__target_revision_func_t target_revision_func;
+
+ /* If not null, a callback that the Ev3 driver may call to
+ * provide the "base revision" of the root directory, even if it is not
+ * going to modify that directory. (If it does modify it, then it will
+ * pass in the appropriate base revision at that time.) If null
+ * or if the driver does not call it, then the Ev1
+ * open_root() method will be called with SVN_INVALID_REVNUM as the base
+ * revision parameter.
+ */
+ svn_delta__start_edit_func_t start_edit_func;
+
+#ifdef SHIM_WITH_UNLOCK
+ /* A callback which will be called when an unlocking action is received.
+ (For update editors?) */
+ svn_delta__unlock_func_t unlock_func;
+#endif
+
+ void *baton;
+};
+
+svn_error_t *
+svn_editor3__insert_shims(
+ const svn_delta_editor_t **new_deditor,
+ void **new_dedit_baton,
+ const svn_delta_editor_t *old_deditor,
+ void *old_dedit_baton,
+ const char *repos_root,
+ const char *base_relpath,
+ const svn_delta_shim_callbacks_t *shim_cb,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_editor3_t *editor3;
+ svn_delta__shim_connector_t *shim_connector;
+
+#ifdef SVN_DEBUG
+ SVN_ERR(svn_delta__get_debug_editor(&old_deditor, &old_dedit_baton,
+ old_deditor, old_dedit_baton,
+ "[OUT] ", result_pool));
+#endif
+ SVN_ERR(svn_delta__ev3_from_delta_for_commit(
+ &editor3,
+ &shim_connector,
+ old_deditor, old_dedit_baton,
+ repos_root, base_relpath,
+ shim_cb->fetch_kind_func, shim_cb->fetch_baton,
+ shim_cb->fetch_props_func, shim_cb->fetch_baton,
+ NULL, NULL /*cancel*/,
+ result_pool, scratch_pool));
+#ifdef SVN_DEBUG
+ SVN_ERR(svn_editor3__get_debug_editor(&editor3, editor3, result_pool));
+#endif
+ SVN_ERR(svn_delta__delta_from_ev3_for_commit(
+ new_deditor, new_dedit_baton,
+ editor3,
+ repos_root, base_relpath,
+ shim_cb->fetch_props_func, shim_cb->fetch_baton,
+ shim_cb->fetch_base_func, shim_cb->fetch_baton,
+ shim_connector,
+ result_pool, scratch_pool));
+#ifdef SVN_DEBUG
+ SVN_ERR(svn_delta__get_debug_editor(new_deditor, new_dedit_baton,
+ *new_deditor, *new_dedit_baton,
+ "[IN] ", result_pool));
+#endif
+ return SVN_NO_ERROR;
+}
/*
@@ -105,256 +186,1305 @@ typedef struct change_node_t
/* We may need to specify the revision we are altering or the revision
to delete or replace. These are mutually exclusive, but are separate
- for clarity.
+ for clarity. */
+ /* CHANGING_REV is the base revision of the change if ACTION is 'none',
+ else is SVN_INVALID_REVNUM. (If ACTION is 'add' and COPYFROM_PATH
+ is non-null, then COPYFROM_REV serves the equivalent purpose for the
+ copied node.) */
+ svn_revnum_t changing_rev;
+ /* If ACTION is 'delete' or if ACTION is 'add' and it is a replacement,
+ DELETING is TRUE and DELETING_REV is the revision to delete. */
+ svn_boolean_t deleting;
+ svn_revnum_t deleting_rev;
+
+ /* new/final set of props to apply; null => no *change*, not no props */
+ apr_hash_t *props;
+
+ /* new fulltext; null => no change */
+ svn_boolean_t contents_changed;
+ const char *contents_abspath;
+ const svn_checksum_t *text_checksum_md5; /* 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;
+
+#ifdef SHIM_WITH_UNLOCK
+ /* Record whether an incoming propchange unlocked this node. */
+ svn_boolean_t unlock;
+#endif
+} change_node_t;
+
+/* Check whether RELPATH is known to exist, known to not exist, or unknown. */
+static svn_tristate_t
+check_existence(apr_hash_t *changes,
+ const char *relpath)
+{
+ apr_pool_t *changes_pool = apr_hash_pool_get(changes);
+ apr_pool_t *scratch_pool = changes_pool;
+ change_node_t *change = svn_hash_gets(changes, relpath);
+ svn_tristate_t exists = svn_tristate_unknown;
+
+ if (change && change->action != RESTRUCTURE_DELETE)
+ exists = svn_tristate_true;
+ else if (change && change->action == RESTRUCTURE_DELETE)
+ exists = svn_tristate_false;
+ else
+ {
+ const char *parent_path = relpath;
+
+ /* Find the nearest parent change. If that's a delete or a simple
+ (non-recursive) add, this path cannot exist, else we don't know. */
+ while ((parent_path = svn_relpath_dirname(parent_path, scratch_pool)),
+ *parent_path)
+ {
+ change = svn_hash_gets(changes, parent_path);
+ if (change)
+ {
+ if ((change->action == RESTRUCTURE_ADD && !change->copyfrom_path)
+ || change->action == RESTRUCTURE_DELETE)
+ exists = svn_tristate_false;
+ break;
+ }
+ }
+ }
+
+ return exists;
+}
+
+/* Insert a new Ev1-style change for RELPATH, or return an existing one.
+ *
+ * Verify Ev3 rules. Primary differences from Ev1 rules are ...
+ *
+ * If ACTION is 'delete', elide any previous explicit deletes inside
+ * that subtree. (Other changes inside that subtree are not allowed.) We
+ * do not store multiple change records per path even with nested moves
+ * -- the most complex change is delete + copy which all fits in one
+ * record with action='add'.
+ */
+static svn_error_t *
+insert_change(change_node_t **change_p, apr_hash_t *changes,
+ const char *relpath,
+ enum restructure_action_t action)
+{
+ apr_pool_t *changes_pool = apr_hash_pool_get(changes);
+ apr_pool_t *scratch_pool = changes_pool;
+ change_node_t *change = svn_hash_gets(changes, relpath);
+
+ /* Check whether this op is allowed. */
+ switch (action)
+ {
+ case RESTRUCTURE_NONE:
+ if (change)
+ {
+ /* A no-restructure change is allowed after add, but not allowed
+ * (in Ev3) after another no-restructure change, nor a delete. */
+ VERIFY(change->action == RESTRUCTURE_ADD);
+ }
+ break;
+
+ case RESTRUCTURE_ADD:
+ if (change)
+ {
+ /* Add or copy is allowed after delete (and replaces the delete),
+ * but not allowed after an add or a no-restructure change. */
+ VERIFY(change->action == RESTRUCTURE_DELETE);
+ change->action = action;
+ }
+ break;
+
+#ifdef SHIM_WITH_ADD_ABSENT
+ case RESTRUCTURE_ADD_ABSENT:
+ /* ### */
+ break;
+#endif
+
+ case RESTRUCTURE_DELETE:
+ /* In Ev3 (incremental), delete is allowed after add (only if that
+ * was a move and this is a move), but not after delete or modify.
+ * Delete is not allowed if any child paths have been modified or
+ * created, but is allowed when child paths have been moved away or
+ * deleted. */
+ if (change)
+ {
+ VERIFY(change->action == RESTRUCTURE_ADD && change->copyfrom_path);
+ }
+ /* Validate that all child ops are deletes, and elide them. */
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, changes);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *this_relpath = svn__apr_hash_index_key(hi);
+ change_node_t *this_change = svn__apr_hash_index_val(hi);
+ const char *r = svn_relpath_skip_ancestor(relpath, this_relpath);
+
+ if (r && r[0])
+ {
+ VERIFY(this_change->action == RESTRUCTURE_DELETE);
+ svn_hash_sets(changes, this_relpath, NULL);
+ }
+ }
+ }
+ break;
+ }
+
+ if (change)
+ {
+ if (action != RESTRUCTURE_NONE)
+ {
+ change->action = action;
+ }
+ }
+ else
+ {
+ /* Create a new change. Callers will set the other fields as needed. */
+ change = apr_pcalloc(changes_pool, sizeof(*change));
+ change->action = action;
+ change->changing_rev = SVN_INVALID_REVNUM;
+
+ svn_hash_sets(changes, apr_pstrdup(changes_pool, relpath), change);
+ }
+
+ *change_p = change;
+ return SVN_NO_ERROR;
+}
+
+/* Insert a new change for RELPATH, or return an existing one.
+ *
+ * Verify Ev1 ordering.
+ */
+static svn_error_t *
+insert_change_ev1_rules(change_node_t **change_p, apr_hash_t *changes,
+ const char *relpath,
+ enum restructure_action_t action,
+ svn_node_kind_t kind)
+{
+ apr_pool_t *changes_pool = apr_hash_pool_get(changes);
+ apr_pool_t *scratch_pool = changes_pool;
+ change_node_t *change = svn_hash_gets(changes, relpath);
+ svn_tristate_t exists = check_existence(changes, relpath);
+
+ /* Check whether this op is allowed. */
+ switch (action)
+ {
+ case RESTRUCTURE_NONE:
+ VERIFY(kind == svn_node_dir || kind == svn_node_file);
+ VERIFY(exists != svn_tristate_false);
+ if (change)
+ {
+ VERIFY(change->kind == kind);
+ }
+ break;
+
+ case RESTRUCTURE_ADD:
+ VERIFY(kind == svn_node_dir || kind == svn_node_file);
+ if (change)
+ {
+ /* Add or copy is allowed after delete (and replaces the delete),
+ * but not allowed after an add or a no-restructure change. */
+ VERIFY(change->action == RESTRUCTURE_DELETE);
+ }
+ else
+ {
+ const char *parent_path = svn_relpath_dirname(relpath, scratch_pool);
+
+ /* Disallow if *parent* path is known to be non-existent
+ * (deleted (root or child), or child of a non-copy add). */
+ VERIFY(check_existence(changes, parent_path) != svn_tristate_false);
+ }
+ break;
+
+#ifdef SHIM_WITH_ADD_ABSENT
+ case RESTRUCTURE_ADD_ABSENT:
+ VERIFY(kind == svn_node_dir || kind == svn_node_file);
+ /* ### */
+ break;
+#endif
+
+ case RESTRUCTURE_DELETE:
+ VERIFY(kind == svn_node_none);
+ /* Delete is allowed only on a currently existing path. */
+ VERIFY(exists != svn_tristate_false);
+ /* Remove change records for any child paths inside this delete. */
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(scratch_pool, changes);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *this_relpath = svn__apr_hash_index_key(hi);
+ const char *r = svn_relpath_skip_ancestor(relpath, this_relpath);
+
+ if (r && r[0])
+ {
+ svn_hash_sets(changes, this_relpath, NULL);
+ }
+ }
+ }
+
+ break;
+ }
+
+ if (change)
+ {
+ if (action != RESTRUCTURE_NONE)
+ {
+ change->action = action;
+ }
+ change->kind = kind;
+ }
+ else
+ {
+ /* Create a new change. Callers will set the other fields as needed. */
+ change = apr_pcalloc(changes_pool, sizeof(*change));
+ change->action = action;
+ change->kind = kind;
+ change->changing_rev = SVN_INVALID_REVNUM;
+ change->deleting_rev = SVN_INVALID_REVNUM;
+
+ svn_hash_sets(changes, apr_pstrdup(changes_pool, relpath), change);
+ }
+
+ *change_p = change;
+ return SVN_NO_ERROR;
+}
+
+/* Duplicate any child changes from the subtree under (but excluding)
+ * FROM_PATH into the subtree under (but excluding) NEW_PATH. */
+static svn_error_t *
+duplicate_child_changes(apr_hash_t *changes,
+ const char *from_path,
+ const char *new_path,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *changes_pool = apr_hash_pool_get(changes);
+ apr_hash_index_t *hi;
+
+ /* for each change ... */
+ for (hi = apr_hash_first(scratch_pool, changes);
+ hi; hi = apr_hash_next(hi))
+ {
+ const char *this_path = svn__apr_hash_index_key(hi);
+ change_node_t *this_change = svn__apr_hash_index_val(hi);
+ const char *r = svn_relpath_skip_ancestor(from_path, this_path);
+
+ /* ... at a child path strictly below FROM_PATH ... */
+ if (r && r[0])
+ {
+ const char *new_child_path
+ = svn_relpath_join(new_path, r, changes_pool);
+
+ /* ... duplicate that change as a child of NEW_PATH */
+ svn_hash_sets(changes, new_child_path, this_change);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/*
+ * ===================================================================
+ * Commit Editor converter to join a v1 driver to a v3 consumer
+ * ===================================================================
+ *
+ * The following code maps the calls to a traditional delta editor to an
+ * Editor v3.
+ *
+ * It does not create 'move' operations, neither heuristically nor using
+ * out-of-band cues. In fact, the code structure is likely to be
+ * unsuitable for processing moves.
+ *
+ * ### This was designed (in its Ev2 form) for both commit and update
+ * editors, but Ev3 is currently only designed as a commit editor.
+ * Therefore 'update' functionality probably doesn't work, including:
+ * - create 'absent' node (currently just omits the 'put', which
+ * Ev3 currently defines will create an empty node)
+ *
+ * ### Need to review all revisions passed to pathrev()/txn_path()
+ * constructors: are they really the right peg revs?
+ *
+ * TODO: Fetch the base (kind, props, text) of an opened file or dir
+ * right away when it's opened. Delaying the fetch, as we do for the
+ * sake of 'optimization', adds complexity & is probably a poor choice.
+ */
+
+/* Construct a peg-path-rev */
+static svn_editor3_peg_path_t
+pathrev(const char *repos_relpath, svn_revnum_t revision)
+{
+ svn_editor3_peg_path_t p;
+
+ p.rev = revision;
+ p.relpath = repos_relpath;
+ return p;
+}
+
+/* Construct a txn-path-rev */
+static svn_editor3_txn_path_t
+txn_path(const char *repos_relpath, svn_revnum_t revision,
+ const char *created_relpath)
+{
+ svn_editor3_txn_path_t p;
+
+ p.peg.rev = revision;
+ p.peg.relpath = repos_relpath;
+ p.relpath = created_relpath;
+ return p;
+}
+
+struct ev3_edit_baton
+{
+ svn_editor3_t *editor;
+
+ apr_hash_t *changes; /* REPOS_RELPATH -> struct change_node */
+
+ /* (const char *) relpaths in path visiting order. */
+ apr_array_header_t *path_order;
+#ifdef SHIM_WITH_ACTIONS_DURING_ABORT
+ int paths_processed;
+#endif
+
+ /* For calculating relpaths from Ev1 copyfrom urls. */
+ const char *repos_root;
+ const char *base_relpath;
+
+ const svn_delta__shim_connector_t *shim_connector;
+
+ 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_boolean_t closed;
+
+ apr_pool_t *edit_pool;
+};
+
+struct ev3_dir_baton
+{
+ struct ev3_edit_baton *eb;
+ const char *path;
+ svn_revnum_t base_revision;
+
+ /* The copyfrom is set for each dir inside a copy, not just the copy root. */
+ const char *copyfrom_relpath;
+ svn_revnum_t copyfrom_rev;
+};
+
+struct ev3_file_baton
+{
+ struct ev3_edit_baton *eb;
+ const char *path;
+ svn_revnum_t base_revision;
+ const char *delta_base;
+};
+
+/* Return the 'txn_path' that addresses the node that is currently at
+ * RELPATH according to the info in CHANGES.
+ *
+ * One way to describe it:
+ *
+ * If relpath is a created path:
+ * find_txn_path(its parent)
+ * add (its basename) to the created-path part
+ * elif relpath is an already-existing path:
+ * return txn_path(relpath, base rev, "")
+ * else: # it's deleted
+ * return None
+ *
+ * Another way:
+ *
+ * p := first path component that is add/copy, starting from root
+ * d := dirname(p)
+ * return txn_path(d, changing_rev(d), remainder-relpath)
+ */
+static svn_editor3_txn_path_t
+find_txn_path(apr_hash_t *changes,
+ const char *relpath,
+ apr_pool_t *result_pool)
+{
+ const char *existing_path = "", *remainder_path = relpath;
+ svn_revnum_t existing_revnum = SVN_INVALID_REVNUM;
+ int i;
+ svn_editor3_txn_path_t loc;
+
+ /* The root path was necessarily existing. For each further path component,
+ if it was existing, add it to the 'existing path', else stop there. */
+ for (i = 1; *remainder_path; i++)
+ {
+ const char *new_prefix_path = svn_relpath_limit(relpath, i, result_pool);
+ change_node_t *change = svn_hash_gets(changes, new_prefix_path);
+
+ if (change && change->action == RESTRUCTURE_ADD)
+ break;
+
+ existing_path = new_prefix_path;
+ remainder_path = svn_relpath_skip_ancestor(existing_path, relpath);
+ if (change && change->action == RESTRUCTURE_NONE)
+ /* ### This is all well and good when there is a RESTRUCTURE_NONE
+ change recorded for this dir, but for an unchanged parent
+ dir we don't know what the base revision was ... unless we
+ record every 'opened' parent dir. Should we do that? */
+ existing_revnum = change->changing_rev;
+ }
+ loc = txn_path(existing_path, existing_revnum, remainder_path);
+ return loc;
+}
+
+/* Drive the Ev3 EB->editor to make the Ev1-style edits described by
+ * CHANGE for the path REPOS_RELPATH.
+ *
+ * Note: We do not support converting copy-and-delete to send an Ev3 move.
+ * This per-path model of processing is not well suited to doing so.
+ */
+static svn_error_t *
+process_actions(struct ev3_edit_baton *eb,
+ const char *repos_relpath,
+ const change_node_t *change,
+ apr_pool_t *scratch_pool)
+{
+ svn_editor3_txn_path_t change_loc
+ = find_txn_path(eb->changes, repos_relpath, scratch_pool);
+ const char *repos_relpath_dirname = svn_relpath_dirname(repos_relpath, scratch_pool);
+ const char *repos_relpath_basename = svn_relpath_basename(repos_relpath, scratch_pool);
+
+ SVN_ERR_ASSERT(change != NULL);
+
+#ifdef SHIM_WITH_UNLOCK
+ if (eb->shim_connector && change->unlock)
+ SVN_ERR(eb->shim_connector->unlock_func(
+ eb->shim_connector->baton, repos_relpath, scratch_pool));
+#endif
+
+ /* Process any delete, no matter whether it will be replaced. */
+ if (change->deleting)
+ {
+ SVN_ERR(svn_editor3_rm(eb->editor,
+ txn_path(repos_relpath, change->deleting_rev, "")));
+ }
+
+#ifdef SHIM_WITH_ADD_ABSENT
+ if (change->action == RESTRUCTURE_ADD_ABSENT)
+ {
+ SVN_ERR(svn_editor3_mk(eb->editor, change->kind,
+ parent_loc, repos_relpath_basename));
+
+ /* No further work possible on this path. */
+ return SVN_NO_ERROR;
+ }
+#endif
+
+ if (change->action == RESTRUCTURE_ADD)
+ {
+ svn_editor3_txn_path_t parent_loc
+ = find_txn_path(eb->changes, repos_relpath_dirname, scratch_pool);
+
+ if (change->copyfrom_path != NULL)
+ {
+ SVN_ERR(svn_editor3_cp(eb->editor,
+#ifdef SVN_EDITOR3_WITH_COPY_FROM_THIS_REV
+ txn_path(change->copyfrom_path, change->copyfrom_rev, ""),
+#else
+ pathrev(change->copyfrom_path, change->copyfrom_rev),
+#endif
+ parent_loc, repos_relpath_basename));
+ /* Fall through to possibly make changes post-copy. */
+ }
+ else
+ {
+ SVN_ERR(svn_editor3_mk(eb->editor, change->kind,
+ parent_loc, repos_relpath_basename));
+ /* Fall through to make changes post-add. */
+ }
+ }
+
+#if 0
+ /* ### There *should* be work for this node. But it seems that isn't true
+ in some cases. Future investigation... */
+ VERIFY(change->props || change->contents_changed);
+#endif
+ if (change->props || change->contents_changed)
+ {
+ svn_editor3_node_content_t *new_content;
+
+ if (change->kind == svn_node_file)
+ {
+ svn_stream_t *contents;
+ svn_checksum_t *checksum;
+
+ if (change->contents_abspath)
+ {
+ /* ### The checksum might be in CHANGE->text_checksum. Should we
+ assume it's there? Should we use it, validate it, or ignore it? */
+ /*if (change->text_checksum)
+ checksum = change->text_checksum;
+ else*/
+ SVN_ERR(svn_io_file_checksum2(&checksum, change->contents_abspath,
+ SVN_EDITOR3_CHECKSUM_KIND, scratch_pool));
+ SVN_ERR(svn_stream_open_readonly(&contents, change->contents_abspath,
+ scratch_pool, scratch_pool));
+ }
+ else
+ {
+ SVN_DBG(("file '%s', no content, act=%d, cp=%s@%ld",
+ repos_relpath, change->action, change->copyfrom_path, change->copyfrom_rev));
+ /*### not: VERIFY(change->action == RESTRUCTURE_ADD && ! change->copyfrom_path);*/
+
+ /* If this file was added, but apply_txdelta() was not called (i.e.
+ CONTENTS_CHANGED is FALSE), we're adding an empty file. */
+ contents = svn_stream_empty(scratch_pool);
+ checksum = svn_checksum_empty_checksum(SVN_EDITOR3_CHECKSUM_KIND,
+ scratch_pool);
+ }
+
+ new_content = svn_editor3_node_content_create_file(
+ pathrev(NULL, -1),
+ change->props, checksum, contents, scratch_pool);
+ }
+ else if (change->kind == svn_node_dir)
+ {
+ new_content = svn_editor3_node_content_create_dir(
+ pathrev(NULL, -1), change->props, scratch_pool);
+ }
+ else
+ SVN_ERR_MALFUNCTION();
+ SVN_ERR(svn_editor3_put(eb->editor, change_loc, new_content));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+run_actions(struct ev3_edit_baton *eb,
+ apr_pool_t *scratch_pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+#ifdef SHIM_WITH_ACTIONS_DURING_ABORT
+ /* Possibly pick up where we left off. Occasionally, we do some of these
+ as part of close_edit() and then some more as part of abort_edit() */
+ for (i = eb->paths_processed; i < eb->path_order->nelts; ++i, ++eb->paths_processed)
+#else
+ for (i = 0; i < eb->path_order->nelts; ++i)
+#endif
+ {
+ const char *repos_relpath
+ = APR_ARRAY_IDX(eb->path_order, i, const char *);
+ const change_node_t *change = svn_hash_gets(eb->changes, repos_relpath);
+
+ /* Process the change for each path only once, no
+ matter how many times the Ev1 driver visited it. When we've
+ processed a path successfully, remove it from the queue. */
+ if (change)
+ {
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(process_actions(eb, repos_relpath, change, iterpool));
+
+ /* Remove the action, as we've now processed it. */
+ svn_hash_sets(eb->changes, repos_relpath, NULL);
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+/* Return the repository-relative path for a given Ev1 input path (that is,
+ * a relpath-within-edit or a URL). */
+static const char *
+map_to_repos_relpath(struct ev3_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);
+ }
+}
+
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_set_target_revision(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *scratch_pool)
+{
+ struct ev3_edit_baton *eb = edit_baton;
+
+ if (eb->shim_connector && eb->shim_connector->target_revision_func)
+ SVN_ERR(eb->shim_connector->target_revision_func(
+ eb->shim_connector->baton, target_revision, scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_open_root(void *edit_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **root_baton)
+{
+ struct ev3_dir_baton *db = apr_pcalloc(result_pool, sizeof(*db));
+ struct ev3_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->shim_connector && eb->shim_connector->start_edit_func)
+ SVN_ERR(eb->shim_connector->start_edit_func(
+ eb->shim_connector->baton, base_revision));
+
+ return SVN_NO_ERROR;
+}
+
+/* Record a property change in the (existing or new) change record for
+ * the node at RELPATH of kind KIND. Change property NAME to value VALUE,
+ * or delete the property if VALUE is NULL.
+ *
+ * Fetch and store the base properties for this node, using the callback
+ * EB->fetch_props_func/baton, if we have not yet done so. Then apply the
+ * edit to those base properties or to the set of properties resulting
+ * from the previous edit.
+ *
+ * ### BASE_REVISION is ... the base revision of the node that is
+ * currently at RELPATH ... ? But in the case of a copied node,
+ * it gets put into (change->changing_rev) rather than copyfrom_rev.
+ */
+static svn_error_t *
+apply_propedit(struct ev3_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)
+{
+ change_node_t *change;
+
+ SVN_ERR(insert_change_ev1_rules(&change, eb->changes, relpath,
+ RESTRUCTURE_NONE, kind));
+
+ /* Record the observed order. */
+ APR_ARRAY_PUSH(eb->path_order, const char *)
+ = apr_pstrdup(eb->edit_pool, relpath);
+
+ /* We're now changing the node. Record the revision. */
+ VERIFY(!SVN_IS_VALID_REVNUM(change->changing_rev)
+ || change->changing_rev == base_revision);
+ change->changing_rev = base_revision;
+
+ /* Fetch the original set of properties, if we haven't done so yet. */
+ if (change->props == NULL)
+ {
+ /* If this is a copied/moved node, 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;
+}
+
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_delete_entry(const char *path,
+ svn_revnum_t revision,
+ void *parent_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev3_dir_baton *pb = parent_baton;
+ svn_revnum_t base_revision;
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ change_node_t *change;
+
+ SVN_ERR(insert_change_ev1_rules(&change, pb->eb->changes, relpath,
+ RESTRUCTURE_DELETE, svn_node_none));
+
+ /* Record the observed order. */
+ APR_ARRAY_PUSH(pb->eb->path_order, const char *)
+ = apr_pstrdup(pb->eb->edit_pool, relpath);
+
+ if (SVN_IS_VALID_REVNUM(revision))
+ base_revision = revision;
+ else
+ base_revision = pb->base_revision;
+
+ /* ### Should these checks be in insert_change()? */
+ VERIFY(!change->deleting || change->deleting_rev == base_revision);
+ change->deleting = TRUE;
+ change->deleting_rev = base_revision;
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_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)
+{
+ apr_pool_t *scratch_pool = result_pool;
+ struct ev3_dir_baton *pb = parent_baton;
+ struct ev3_dir_baton *cb = apr_pcalloc(result_pool, sizeof(*cb));
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ change_node_t *change;
+
+ SVN_ERR(insert_change_ev1_rules(&change, pb->eb->changes, relpath,
+ RESTRUCTURE_ADD, svn_node_dir));
+
+ /* Record the observed order. */
+ APR_ARRAY_PUSH(pb->eb->path_order, const char *)
+ = apr_pstrdup(pb->eb->edit_pool, relpath);
+
+ 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;
+}
+
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_open_directory(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **child_baton)
+{
+ apr_pool_t *scratch_pool = result_pool;
+ struct ev3_dir_baton *pb = parent_baton;
+ struct ev3_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;
+}
+
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_change_dir_prop(void *dir_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct ev3_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;
+}
+
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_close_directory(void *dir_baton,
+ apr_pool_t *scratch_pool)
+{
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_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)
+{
+ apr_pool_t *scratch_pool = result_pool;
+ struct ev3_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
+ struct ev3_dir_baton *pb = parent_baton;
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ change_node_t *change;
+
+ SVN_ERR(insert_change_ev1_rules(&change, pb->eb->changes, relpath,
+ RESTRUCTURE_ADD, svn_node_file));
+
+ /* Record the observed order. */
+ APR_ARRAY_PUSH(pb->eb->path_order, const char *)
+ = apr_pstrdup(pb->eb->edit_pool, relpath);
+
+ fb->eb = pb->eb;
+ fb->path = apr_pstrdup(result_pool, relpath);
+ fb->base_revision = pb->base_revision;
+ *file_baton = fb;
+
+ /* Fetch the base text as a file FB->delta_base, if it's a copy */
+ if (copyfrom_path)
+ {
+ 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));
+ }
+ else
+ {
+ /* It's a plain add -- we don't have a base. */
+ fb->delta_base = NULL;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_open_file(const char *path,
+ void *parent_baton,
+ svn_revnum_t base_revision,
+ apr_pool_t *result_pool,
+ void **file_baton)
+{
+ apr_pool_t *scratch_pool = result_pool;
+ struct ev3_file_baton *fb = apr_pcalloc(result_pool, sizeof(*fb));
+ struct ev3_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 delta is based on 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 ev3_handler_baton
+{
+ svn_txdelta_window_handler_t apply_handler;
+ void *apply_baton;
+
+ svn_stream_t *source;
+
+ apr_pool_t *pool;
+};
+
+static svn_error_t *
+ev3_window_handler(svn_txdelta_window_t *window, void *baton)
+{
+ struct ev3_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);
+}
+
+/* Lazy-open handler for getting a read-only stream of the delta base. */
+static svn_error_t *
+ev3_open_delta_base(svn_stream_t **stream, void *file_baton,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ struct ev3_file_baton *fb = file_baton;
+
+ return svn_stream_open_readonly(stream, fb->delta_base,
+ result_pool, scratch_pool);
+}
+
+/* Lazy-open handler for opening a stream for the delta result. */
+static svn_error_t *
+ev3_open_delta_target(svn_stream_t **stream, void *baton,
+ apr_pool_t *result_pool, apr_pool_t *scratch_pool)
+{
+ const char **delta_target = baton;
+ return svn_stream_open_unique(stream, delta_target, NULL,
+ svn_io_file_del_on_pool_cleanup,
+ result_pool, scratch_pool);
+}
+
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_apply_textdelta(void *file_baton,
+ const char *base_checksum,
+ apr_pool_t *result_pool,
+ svn_txdelta_window_handler_t *handler,
+ void **handler_baton)
+{
+ struct ev3_file_baton *fb = file_baton;
+ apr_pool_t *handler_pool = svn_pool_create(fb->eb->edit_pool);
+ struct ev3_handler_baton *hb = apr_pcalloc(handler_pool, sizeof(*hb));
+ change_node_t *change;
+ svn_stream_t *target;
+
+ SVN_ERR(insert_change_ev1_rules(&change, fb->eb->changes, fb->path,
+ RESTRUCTURE_NONE, svn_node_file));
+
+ /* Record the observed order. */
+ APR_ARRAY_PUSH(fb->eb->path_order, const char *)
+ = apr_pstrdup(fb->eb->edit_pool, fb->path);
+
+ /* The content for this path must be changed only once. (Not explicitly
+ mandated by svn_delta_editor, but we'll assume it is mandatory.) */
+ VERIFY(!change->contents_changed);
+ change->contents_changed = TRUE;
+
+ /* This can come after property changes or no changes or an add. */
+ VERIFY(!SVN_IS_VALID_REVNUM(change->changing_rev)
+ || change->changing_rev == fb->base_revision);
+ change->changing_rev = fb->base_revision;
+
+ if (! fb->delta_base)
+ hb->source = svn_stream_empty(handler_pool);
+ else
+ hb->source = svn_stream_lazyopen_create(ev3_open_delta_base, fb,
+ FALSE, handler_pool);
- If ACTION is 'delete', DELETING_REV is the revision to delete and
- CHANGING_REV is SVN_INVALID_REVNUM as any replacement is either a
- plain add (no base rev) or a copy (with any changes being based on
- COPYFROM_REV). If ACTION is 'none' then CHANGING_REV is the base
- revision of the change and DELETING_REV is SVN_INVALID_REVNUM.
- If ACTION is 'add', both are SVN_INVALID_REVNUM. */
- svn_revnum_t changing_rev;
- svn_revnum_t deleting_rev;
+ target = svn_stream_lazyopen_create(ev3_open_delta_target,
+ &change->contents_abspath,
+ FALSE, fb->eb->edit_pool);
+
+ svn_txdelta_apply(hb->source, target,
+ NULL, NULL,
+ handler_pool,
+ &hb->apply_handler, &hb->apply_baton);
- /* new/final set of props to apply; null => no *change*, not no props */
- apr_hash_t *props;
+ hb->pool = handler_pool;
- svn_stream_t *text_stream; /* new fulltext; null => no change */
- const svn_checksum_t *text_checksum; /* checksum of new fulltext */
+ *handler_baton = hb;
+ *handler = ev3_window_handler;
+ return SVN_NO_ERROR;
+}
- /* 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;
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_change_file_prop(void *file_baton,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *scratch_pool)
+{
+ struct ev3_file_baton *fb = file_baton;
#ifdef SHIM_WITH_UNLOCK
- /* Record whether an incoming propchange unlocked this node. */
- svn_boolean_t unlock;
+ 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);
+ change_node_t *change = locate_change(fb->eb, relpath);
+
+ change->unlock = TRUE;
+ }
#endif
-} change_node_t;
-/* Find the change in CHANGES whose path is the longest out of TARGET_PATH
- * and its ancestors.
- *
- * Always return a non-null change: if no change is found, return a dummy
- * no-tree-change with no other information.
- */
-static change_node_t *
-find_nearest_change(apr_hash_t *changes,
- const char *target_path)
-{
- apr_pool_t *pool = apr_hash_pool_get(changes);
- const char *nearest_change_path = "";
- change_node_t *nearest_change;
- apr_hash_index_t *hi;
+ SVN_ERR(apply_propedit(fb->eb, fb->path, svn_node_file, fb->base_revision,
+ name, value, scratch_pool));
- /* find the change with the longest matching prefix of TARGET_PATH */
- for (hi = apr_hash_first(pool, changes);
- hi; hi = apr_hash_next(hi))
- {
- const char *this_relpath = svn__apr_hash_index_key(hi);
- const char *r = svn_relpath_skip_ancestor(this_relpath, target_path);
+ return SVN_NO_ERROR;
+}
- if (r && strlen(this_relpath) > strlen(nearest_change_path))
- {
- nearest_change_path = apr_pstrdup(pool, this_relpath);
- /* stop looking for a longer match if this is an exact match */
- if (r[0] == '\0')
- break;
- }
- }
- nearest_change = svn_hash_gets(changes, nearest_change_path);
- if (! nearest_change)
- {
- /* Create a new, dummy change. */
- nearest_change = apr_pcalloc(pool, sizeof(*nearest_change));
- nearest_change->action = RESTRUCTURE_NONE;
- nearest_change->changing_rev = SVN_INVALID_REVNUM;
- nearest_change->deleting_rev = SVN_INVALID_REVNUM;
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_close_file(void *file_baton,
+ const char *text_checksum,
+ apr_pool_t *scratch_pool)
+{
+ struct ev3_file_baton *fb = file_baton;
+ change_node_t *change = svn_hash_gets(fb->eb->changes, fb->path);
+
+ /* If this file is being modified, or copied, and apply_txdelta()
+ was not called (i.e. CONTENTS_CHANGED is FALSE), then there is
+ no change to the content. We must fetch the original content
+ in order to tell Ev3 not to change it.
+ (### Or we could retract the changing of this file entirely
+ if there were no prop changes either.)
+
+ The only exception is for a new, empty file, where we leave
+ CONTENTS_CHANGED false for now (and CONTENTS_ABSPATH null), and
+ generate an empty stream for it later. */
+ if (! change->contents_changed
+ && (change->action == RESTRUCTURE_NONE || change->copyfrom_path))
+ {
+ change->contents_changed = TRUE;
+ change->contents_abspath = apr_pstrdup(fb->eb->edit_pool, fb->delta_base);
+ SVN_DBG(("close_file: unchanged => use base file '%s'",
+ change->contents_abspath));
}
- return nearest_change;
+
+ return SVN_NO_ERROR;
}
-/* Check whether RELPATH is known to exist, known to not exist, or unknown. */
-static svn_tristate_t
-check_existence(apr_hash_t *changes,
- const char *relpath)
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_absent_directory(const char *path,
+ void *parent_baton,
+ apr_pool_t *scratch_pool)
{
- apr_pool_t *changes_pool = apr_hash_pool_get(changes);
- apr_pool_t *scratch_pool = changes_pool;
- change_node_t *change = svn_hash_gets(changes, relpath);
- svn_tristate_t exists = svn_tristate_unknown;
+#ifdef SHIM_WITH_ADD_ABSENT
+ struct ev3_dir_baton *pb = parent_baton;
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ change_node_t *change;
- if (change && change->action != RESTRUCTURE_DELETE)
- exists = svn_tristate_true;
- else if (change && change->action == RESTRUCTURE_DELETE)
- exists = svn_tristate_false;
- else
- {
- const char *parent_path = svn_relpath_dirname(relpath, scratch_pool);
+ SVN_ERR(insert_change_ev1_rules(&change, eb->changes, relpath,
+ RESTRUCTURE_ADD_ABSENT, svn_node_dir));
- change = find_nearest_change(changes, parent_path);
- if (change->action == RESTRUCTURE_DELETE)
- exists = svn_tristate_false;
- else if (change->action == RESTRUCTURE_ADD && !change->copyfrom_path)
- exists = svn_tristate_false;
- }
+ /* Record the observed order. */
+ APR_ARRAY_PUSH(pb->eb->path_order, const char *)
+ = apr_pstrdup(pb->eb->edit_pool, path);
+#endif
- return exists;
+ return SVN_NO_ERROR;
}
-/* Insert a new change for RELPATH, or return an existing one.
- *
- * Check that this change makes sense on top of previous changes; return
- * an error if not.
- *
- * If the new op is a delete, then any previous explicit deletes inside
- * that subtree are elided. (Other changes inside that subtree are not
- * allowed.) The result of this we do not need to store multiple
- * restructure ops per path even with nested moves.
- */
+/* An svn_delta_editor_t method. */
static svn_error_t *
-insert_change(change_node_t **change_p, apr_hash_t *changes,
- const char *relpath,
- enum restructure_action_t action)
+ev3_absent_file(const char *path,
+ void *parent_baton,
+ apr_pool_t *scratch_pool)
{
- apr_pool_t *changes_pool = apr_hash_pool_get(changes);
- apr_pool_t *scratch_pool = changes_pool;
- change_node_t *change = svn_hash_gets(changes, relpath);
- const char *parent_path = svn_relpath_dirname(relpath, scratch_pool);
- svn_tristate_t exists = check_existence(changes, relpath);
-
- /* Check whether this op is allowed. */
- switch (action)
- {
- case RESTRUCTURE_NONE:
- if (change)
- {
- /* A no-restructure change is allowed after add, but not allowed
- * (in Ev3) after another no-restructure change, nor a delete. */
- VERIFY(change->action == RESTRUCTURE_ADD);
- }
- else
- {
- /* Disallow if path is known to be non-existent. */
- VERIFY(exists != svn_tristate_false);
- }
- break;
+#ifdef SHIM_WITH_ADD_ABSENT
+ struct ev3_dir_baton *pb = parent_baton;
+ const char *relpath = map_to_repos_relpath(pb->eb, path, scratch_pool);
+ change_node_t *change;
- case RESTRUCTURE_ADD:
- if (change)
- {
- /* Add or copy is allowed after delete (and replaces the delete),
- * but not allowed after an add or a no-restructure change. */
- VERIFY(change->action == RESTRUCTURE_DELETE);
- change->action = action;
- }
- else
- {
- /* Disallow if *parent* path is known to be non-existent
- * (deleted (root or child), or child of a non-copy add). */
- VERIFY(check_existence(changes, parent_path) != svn_tristate_false);
- }
- break;
+ SVN_ERR(insert_change_ev1_rules(&change, eb->changes, relpath,
+ RESTRUCTURE_ADD_ABSENT, svn_node_file));
-#ifdef SHIM_WITH_ADD_ABSENT
- case RESTRUCTURE_ADD_ABSENT:
- /* ### */
- break;
+ /* Record the observed order. */
+ APR_ARRAY_PUSH(pb->eb->path_order, const char *)
+ = apr_pstrdup(pb->eb->edit_pool, path);
#endif
- case RESTRUCTURE_DELETE:
- /* Delete is allowed on a copy-child, and also after add (if that
- * was a move and this is a move), but not after delete. Delete
- * is not allowed if there are non-delete changes to child paths;
- * if there are such changes it elides them.
- */
- if (change)
- {
- VERIFY(change->action != RESTRUCTURE_DELETE);
- }
- else
- {
- /* Disallow if path is known to be non-existent. */
- VERIFY(exists != svn_tristate_false);
- }
- /* Validate that all child ops are deletes, and elide them. */
- {
- apr_hash_index_t *hi;
-
- for (hi = apr_hash_first(scratch_pool, changes);
- hi; hi = apr_hash_next(hi))
- {
- const char *this_relpath = svn__apr_hash_index_key(hi);
- change_node_t *this_change = svn__apr_hash_index_val(hi);
- const char *r = svn_relpath_skip_ancestor(relpath, this_relpath);
+ return SVN_NO_ERROR;
+}
- if (r && r[0])
- {
- VERIFY(this_change->action == RESTRUCTURE_DELETE);
- svn_hash_sets(changes, this_relpath, NULL);
- }
- }
- }
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_close_edit(void *edit_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev3_edit_baton *eb = edit_baton;
- break;
- }
+ SVN_ERR(run_actions(edit_baton, scratch_pool));
+ eb->closed = TRUE;
+ SVN_ERR(svn_editor3_complete(eb->editor));
+ return SVN_NO_ERROR;
+}
- if (change)
- {
- if (action != RESTRUCTURE_NONE)
- {
- change->action = action;
- }
- }
- else
- {
- /* Create a new change. Callers will set the other fields as needed. */
- change = apr_pcalloc(changes_pool, sizeof(*change));
- change->action = action;
- change->changing_rev = SVN_INVALID_REVNUM;
- change->deleting_rev = SVN_INVALID_REVNUM;
+/* An svn_delta_editor_t method. */
+static svn_error_t *
+ev3_abort_edit(void *edit_baton,
+ apr_pool_t *scratch_pool)
+{
+ struct ev3_edit_baton *eb = edit_baton;
- svn_hash_sets(changes, apr_pstrdup(changes_pool, relpath), change);
- }
+#ifdef SHIM_WITH_ACTIONS_DURING_ABORT
+ SVN_ERR(run_actions(edit_baton, scratch_pool));
+#endif
- *change_p = change;
+ if (!eb->closed)
+ SVN_ERR(svn_editor3_abort(eb->editor));
return SVN_NO_ERROR;
}
-/* Duplicate any child changes from the subtree under (but excluding)
- * FROM_PATH into the subtree under (but excluding) NEW_PATH. */
-static svn_error_t *
-duplicate_child_changes(apr_hash_t *changes,
- const char *from_path,
- const char *new_path,
+svn_error_t *
+svn_delta__delta_from_ev3_for_commit(
+ const svn_delta_editor_t **deditor,
+ void **dedit_baton,
+ svn_editor3_t *editor,
+ 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,
+ const svn_delta__shim_connector_t *shim_connector,
+ apr_pool_t *result_pool,
apr_pool_t *scratch_pool)
{
- apr_pool_t *changes_pool = apr_hash_pool_get(changes);
- apr_hash_index_t *hi;
+ static const svn_delta_editor_t delta_editor = {
+ ev3_set_target_revision,
+ ev3_open_root,
+ ev3_delete_entry,
+ ev3_add_directory,
+ ev3_open_directory,
+ ev3_change_dir_prop,
+ ev3_close_directory,
+ ev3_absent_directory,
+ ev3_add_file,
+ ev3_open_file,
+ ev3_apply_textdelta,
+ ev3_change_file_prop,
+ ev3_close_file,
+ ev3_absent_file,
+ ev3_close_edit,
+ ev3_abort_edit
+ };
+ struct ev3_edit_baton *eb = apr_pcalloc(result_pool, sizeof(*eb));
- /* for each change ... */
- for (hi = apr_hash_first(scratch_pool, changes);
- hi; hi = apr_hash_next(hi))
+ if (!base_relpath)
+ base_relpath = "";
+ else if (base_relpath[0] == '/')
+ base_relpath += 1;
+
+ eb->editor = editor;
+ eb->changes = apr_hash_make(result_pool);
+ eb->path_order = apr_array_make(result_pool, 1, sizeof(const char *));
+ eb->edit_pool = result_pool;
+ eb->shim_connector = shim_connector;
+ if (shim_connector)
{
- const char *this_path = svn__apr_hash_index_key(hi);
- change_node_t *this_change = svn__apr_hash_index_val(hi);
- const char *r = svn_relpath_skip_ancestor(from_path, this_path);
+#ifdef SHIM_WITH_ABS_PATHS
+ *eb->shim_connector->ev1_absolute_paths = FALSE;
+#endif
+ }
+ eb->repos_root = apr_pstrdup(result_pool, repos_root);
+ eb->base_relpath = apr_pstrdup(result_pool, base_relpath);
- /* ... at a child path strictly below FROM_PATH ... */
- if (r && r[0])
- {
- const char *new_child_path
- = svn_relpath_join(new_path, r, changes_pool);
+ eb->fetch_props_func = fetch_props_func;
+ eb->fetch_props_baton = fetch_props_baton;
- /* ... duplicate that change as a child of NEW_PATH */
- svn_hash_sets(changes, new_child_path, this_change);
- }
- }
+ eb->fetch_base_func = fetch_base_func;
+ eb->fetch_base_baton = fetch_base_baton;
+
+ *dedit_baton = eb;
+ *deditor = &delta_editor;
return SVN_NO_ERROR;
}
+
+/*
+ * ===================================================================
+ * Commit Editor converter to join a v3 driver to a v1 consumer
+ * ===================================================================
+ *
+ * This editor buffers all the changes before driving the Ev1 at the end,
+ * since it needs to do a single depth-first traversal of the path space
+ * and this cannot be started until all moves are known.
+ *
+ * Moves are converted to copy-and-delete, with the copy being from
+ * the source peg rev. (### Should it request copy-from revision "-1"?)
+ */
+
+/* TODO
+ *
+ * ### For changes inside a copied subtree, the calls to the "open dir"
+ * and "open file" Ev1 methods may be passing the wrong revision
+ * number: see comment in apply_change().
+ *
+ * ### Have we got our rel-paths in order? Ev1, Ev3 and callbacks may
+ * all expect different paths. 'repos_relpath' or relative to
+ * eb->base_relpath? Leading slash (unimplemented 'send_abs_paths'
+ * feature), etc.
+ *
+ * ### May be tidier for OPEN_ROOT_FUNC callback (see open_root_ev3())
+ * not to actually open the root in advance, but instead just to
+ * remember the base revision that the driver wants us to specify
+ * when we do open it.
+ */
+
+
/* Record a move of a subtree from INITIAL_RELPATH to CURRENT_RELPATH.
*/
static void
@@ -424,7 +1554,9 @@ typedef struct ev3_from_delta_baton_t
/* The Ev1 root directory baton if we have opened the root, else null. */
void *ev1_root_dir_baton;
- /*svn_boolean_t *make_abs_paths;*/
+#ifdef SHIM_WITH_ABS_PATHS
+ const svn_boolean_t *make_abs_paths;
+#endif
/* Repository root URL
### Some code allows this to be null -- but is that valid? */
@@ -466,6 +1598,20 @@ get_unsorted_paths(apr_hash_t *changes,
/* */
static svn_error_t *
+set_target_revision_ev3(void *edit_baton,
+ svn_revnum_t target_revision,
+ apr_pool_t *scratch_pool)
+{
+ ev3_from_delta_baton_t *eb = edit_baton;
+
+ SVN_ERR(eb->deditor->set_target_revision(eb->dedit_baton, target_revision,
+ scratch_pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
open_root_ev3(void *baton,
svn_revnum_t base_revision)
{
@@ -476,18 +1622,15 @@ open_root_ev3(void *baton,
return SVN_NO_ERROR;
}
-/* Return the path of the nearest restructure operation in CHANGES that
- * encloses (or is equal to) RELPATH. Return NULL if the nearest
- * restructure operation is not a copy.
- *
- * Ignore changes whose restructure action is 'none'.
+/* If RELPATH is a child of a copy, return the path of the copy root,
+ * else return NULL.
*/
static const char *
find_enclosing_copy(apr_hash_t *changes,
const char *relpath,
apr_pool_t *result_pool)
{
- while (relpath)
+ while (*relpath)
{
const change_node_t *change = svn_hash_gets(changes, relpath);
@@ -645,7 +1788,7 @@ apply_change(void **dir_baton,
const char *ev1_relpath,
apr_pool_t *result_pool)
{
- apr_pool_t *scratch_pool = result_pool; /* ### fix this? */
+ apr_pool_t *scratch_pool = result_pool;
const ev3_from_delta_baton_t *eb = callback_baton;
const char *relpath = svn_relpath_join(eb->base_relpath, ev1_relpath,
scratch_pool);
@@ -712,7 +1855,7 @@ apply_change(void **dir_baton,
svn_revnum_t copyfrom_rev = SVN_INVALID_REVNUM;
/* Do we have an old node to delete first? If so, delete it. */
- if (SVN_IS_VALID_REVNUM(change->deleting_rev))
+ if (change->deleting)
SVN_ERR(eb->deditor->delete_entry(ev1_relpath, change->deleting_rev,
parent_baton, scratch_pool));
@@ -773,25 +1916,30 @@ apply_change(void **dir_baton,
SVN_ERR(drive_ev1_props(relpath, change, base_props,
eb->deditor, file_baton, scratch_pool));
- if (change->text_stream)
+ /* Send the text content delta, if new text content is provided. */
+ if (change->contents_abspath)
{
+ svn_stream_t *read_stream;
svn_txdelta_window_handler_t handler;
void *handler_baton;
+ SVN_ERR(svn_stream_open_readonly(&read_stream, change->contents_abspath,
+ scratch_pool, scratch_pool));
/* ### 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));
/* ### it would be nice to send a true txdelta here, but whatever. */
- SVN_ERR(svn_txdelta_send_stream(change->text_stream,
- handler, handler_baton,
+ SVN_ERR(svn_txdelta_send_stream(read_stream, handler, handler_baton,
NULL, scratch_pool));
- SVN_ERR(svn_stream_close(change->text_stream));
+ SVN_ERR(svn_stream_close(read_stream));
+ SVN_ERR(svn_io_remove_file2(change->contents_abspath,
+ FALSE /*ignore_enoent*/, scratch_pool));
}
if (file_baton)
{
- const char *digest = svn_checksum_to_cstring(change->text_checksum,
+ const char *digest = svn_checksum_to_cstring(change->text_checksum_md5,
scratch_pool);
SVN_ERR(eb->deditor->close_file(file_baton, digest, scratch_pool));
@@ -827,9 +1975,13 @@ drive_changes(ev3_from_delta_baton_t *eb
/* Make the path driver visit the root dir of the edit. Otherwise, it
will attempt an open_root() instead, which we already did. */
- SVN_ERR(insert_change(&change, eb->changes, eb->base_relpath,
- RESTRUCTURE_NONE));
- change->kind = svn_node_dir;
+ /* ### Seems unnecessarily clumsy. Eliminate the previous open_root call instead? */
+ if (! svn_hash_gets(eb->changes, eb->base_relpath))
+ {
+ SVN_ERR(insert_change(&change, eb->changes, eb->base_relpath,
+ RESTRUCTURE_NONE));
+ change->kind = svn_node_dir;
+ }
/* No property changes (tho they might exist from a real change). */
/* Get a list of Ev1-relative paths (unsorted). */
@@ -1002,6 +2154,7 @@ editor3_mv(void *baton,
/* delete subtree (from_loc in shadow txn) */
SVN_ERR(insert_change(&change, eb->changes, from_txnpath, RESTRUCTURE_DELETE));
+ change->deleting = TRUE;
change->deleting_rev = from_loc.rev;
/* Record the move. If we're moving something again that we already moved
@@ -1046,6 +2199,7 @@ editor3_rm(void *baton,
SVN_ERR(insert_change(&change, eb->changes, txnpath, RESTRUCTURE_DELETE));
/* if we're deleting a pre-existing node (as opposed to a child of a
copy that we made), give its rev num for out-of-date checking */
+ change->deleting = TRUE;
change->deleting_rev = (loc.relpath[0] ? SVN_INVALID_REVNUM : loc.peg.rev);
return SVN_NO_ERROR;
@@ -1060,6 +2214,7 @@ editor3_put(void *baton,
{
ev3_from_delta_baton_t *eb = baton;
change_node_t *change;
+ apr_pool_t *changes_pool = apr_hash_pool_get(eb->changes);
/* look up path in shadow txn */
const char *txnpath
@@ -1067,6 +2222,7 @@ editor3_put(void *baton,
/* look up the 'change' record; this may be a new or an existing record */
SVN_ERR(insert_change(&change, eb->changes, txnpath, RESTRUCTURE_NONE));
+ change->kind = new_content->kind;
/* The revision number that this change is based on is the peg rev for
a simple change. For a plain add it is unused. For a copy ...
@@ -1074,9 +2230,39 @@ editor3_put(void *baton,
we be using the copy-from rev instead? See comment in apply_change().
*/
change->changing_rev = loc.peg.rev;
- change->props = new_content->props;
- change->text_stream = new_content->stream;
- change->text_checksum = new_content->checksum;
+ change->props = (new_content->props
+ ? svn_prop_hash_dup(new_content->props, changes_pool)
+ : NULL);
+
+ if (new_content->kind == svn_node_file)
+ {
+ svn_stream_t *read_stream = new_content->stream;
+ svn_stream_t *write_stream;
+ const char *write_abspath;
+ svn_checksum_t *md5_checksum;
+
+ /* Re-checksum the text contents while reading, if necessary */
+ if (new_content->checksum
+ && new_content->checksum->kind == svn_checksum_md5)
+ {
+ md5_checksum = (svn_checksum_t *)new_content->checksum;
+ }
+ else
+ {
+ read_stream = svn_stream_checksummed2(read_stream, &md5_checksum,
+ NULL, svn_checksum_md5, TRUE,
+ scratch_pool);
+ }
+
+ /* Copy the content from the provided stream into a temporary file. */
+ SVN_ERR(svn_stream_open_unique(&write_stream, &write_abspath,
+ NULL, svn_io_file_del_none,
+ scratch_pool, scratch_pool));
+ SVN_ERR(svn_stream_copy3(read_stream, write_stream,
+ NULL, NULL, scratch_pool));
+ change->contents_abspath = apr_pstrdup(changes_pool, write_abspath);
+ change->text_checksum_md5 = svn_checksum_dup(md5_checksum, changes_pool);
+ }
return SVN_NO_ERROR;
}
@@ -1113,10 +2299,11 @@ editor3_abort(void *baton,
svn_error_t *err;
svn_error_t *err2;
- /* ### A comment in the Ev2-Ev1 converter said: "We still need to drive
- anything we collected in the editor to this point." But why? Let's
- try not doing so. */
- err = NULL; /*= editor3_drive_changes(eb, scratch_pool);*/
+#ifdef SHIM_WITH_ACTIONS_DURING_ABORT
+ err = drive_changes(eb, scratch_pool);
+#else
+ err = NULL;
+#endif
err2 = eb->deditor->abort_edit(eb->dedit_baton, scratch_pool);
@@ -1131,24 +2318,12 @@ editor3_abort(void *baton,
return err;
}
-/*
- * ### SEND_ABS_PATHS is not implemented. When/where is it needed?
- * SEND_ABS_PATHS: A pointer to a flag which must be set prior to driving
- * this edit, but not necessarily at the invocation of this function,
- * that indicates whether Ev1 paths should be sent as absolute paths (with
- * a leading slash) or relative paths.
- *
- * # The Ev2 function on which this is based also had:
- * - returned a 'target_revision' callback (for update editors)
- * - returned an 'unlock_func' callback (for update editors)
- */
svn_error_t *
svn_delta__ev3_from_delta_for_commit(
svn_editor3_t **editor_p,
- svn_delta__start_edit_func_t *open_root_func,
+ svn_delta__shim_connector_t **shim_connector,
const svn_delta_editor_t *deditor,
void *dedit_baton,
- /*svn_boolean_t *send_abs_paths,*/
const char *repos_root_url,
const char *base_relpath,
svn_delta_fetch_kind_func_t fetch_kind_func,
@@ -1178,7 +2353,6 @@ svn_delta__ev3_from_delta_for_commit(
eb->deditor = deditor;
eb->dedit_baton = dedit_baton;
- /*eb->make_abs_paths = send_abs_paths;*/
eb->repos_root_url = apr_pstrdup(result_pool, repos_root_url);
eb->base_relpath = apr_pstrdup(result_pool, base_relpath);
@@ -1196,13 +2370,20 @@ svn_delta__ev3_from_delta_for_commit(
cancel_func, cancel_baton,
result_pool, scratch_pool));
- if (open_root_func)
- *open_root_func = open_root_ev3;
-
+ if (shim_connector)
+ {
+ *shim_connector = apr_palloc(result_pool, sizeof(**shim_connector));
+#ifdef SHIM_WITH_ABS_PATHS
+ (*shim_connector)->ev1_absolute_paths = apr_palloc(result_pool, sizeof(svn_boolean_t));
+ eb->make_abs_paths = (*shim_connector)->ev1_absolute_paths;
+#endif
+ (*shim_connector)->target_revision_func = set_target_revision_ev3;
+ (*shim_connector)->start_edit_func = open_root_ev3;
#ifdef SHIM_WITH_UNLOCK
- *unlock_func = do_unlock;
- *unlock_baton = eb;
+ (*shim_connector)->unlock_func = do_unlock;
#endif
+ (*shim_connector)->baton = eb;
+ }
return SVN_NO_ERROR;
}