subversion-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From julianf...@apache.org
Subject svn commit: r1634591 [2/4] - in /subversion/branches/move-tracking-2/subversion: include/private/svn_editor3.h libsvn_delta/compat3.c libsvn_delta/editor3.c libsvn_ra/ra_loader.c svnmover/svnmover.c
Date Mon, 27 Oct 2014 16:52:23 GMT
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=1634591&r1=1634590&r2=1634591&view=diff
==============================================================================
--- subversion/branches/move-tracking-2/subversion/libsvn_delta/compat3.c (original)
+++ subversion/branches/move-tracking-2/subversion/libsvn_delta/compat3.c Mon Oct 27 16:52:23 2014
@@ -33,6 +33,7 @@
 #include "svn_pools.h"
 
 #include "private/svn_delta_private.h"
+#include "private/svn_sorts_private.h"
 #include "../libsvn_delta/debug_editor.h"
 #include "svn_private_config.h"
 
@@ -1691,6 +1692,1415 @@ find_move(apr_hash_t *moves,
 
 /*
  * ========================================================================
+ * Element-Based Branching
+ * ========================================================================
+ */
+
+svn_branch_repos_t *
+svn_branch_repos_create(apr_pool_t *result_pool)
+{
+  svn_branch_repos_t *repos = apr_pcalloc(result_pool, sizeof(*repos));
+
+  repos->rev_roots = apr_array_make(result_pool, 1, sizeof(void *));
+  repos->pool = result_pool;
+  return repos;
+}
+
+svn_branch_revision_root_t *
+svn_branch_revision_root_create(svn_branch_repos_t *repos,
+                                svn_revnum_t rev,
+                                struct svn_branch_instance_t *root_branch,
+                                apr_pool_t *result_pool)
+{
+  svn_branch_revision_root_t *rev_root
+    = apr_pcalloc(result_pool, sizeof(*rev_root));
+
+  rev_root->repos = repos;
+  rev_root->rev = rev;
+  rev_root->root_branch = root_branch;
+  return rev_root;
+}
+
+svn_branch_family_t *
+svn_branch_family_create(svn_branch_repos_t *repos,
+                         int fid,
+                         int first_bid,
+                         int next_bid,
+                         int first_eid,
+                         int next_eid,
+                         apr_pool_t *result_pool)
+{
+  svn_branch_family_t *f = apr_pcalloc(result_pool, sizeof(*f));
+
+  f->fid = fid;
+  f->repos = repos;
+  f->branch_siblings = apr_array_make(result_pool, 1, sizeof(void *));
+  f->branch_instances = apr_array_make(result_pool, 1, sizeof(void *));
+  f->sub_families = apr_array_make(result_pool, 1, sizeof(void *));
+  f->first_bid = first_bid;
+  f->next_bid = next_bid;
+  f->first_eid = first_eid;
+  f->next_eid = next_eid;
+  f->pool = result_pool;
+  return f;
+}
+
+/* Assign a new element id in FAMILY.
+ */
+static int
+family_add_new_element(svn_branch_family_t *family)
+{
+  int eid = family->next_eid++;
+
+  return eid;
+}
+
+/* Create a new, empty family in OUTER_FAMILY.
+ */
+static svn_branch_family_t *
+family_add_new_subfamily(svn_branch_family_t *outer_family)
+{
+  svn_branch_repos_t *repos = outer_family->repos;
+  int fid = repos->next_fid++;
+  svn_branch_family_t *family
+    = svn_branch_family_create(repos, fid,
+                               fid * 10, fid * 10,
+                               fid * 100, fid * 100,
+                               outer_family->pool);
+
+  /* Register the family */
+  APR_ARRAY_PUSH(outer_family->sub_families, void *) = family;
+
+  return family;
+}
+
+/* Create a new branch sibling in FAMILY, with root element ROOT_EID.
+ */
+static svn_branch_sibling_t *
+family_add_new_branch_sibling(svn_branch_family_t *family,
+                              int root_eid)
+{
+  int bid = family->next_bid++;
+  svn_branch_sibling_t *branch_sibling
+    = svn_branch_sibling_create(family, bid, root_eid, family->pool);
+
+  /* The root EID must be an existing EID. */
+  SVN_ERR_ASSERT_NO_RETURN(root_eid >= family->first_eid
+                           /*&& root_eid < family->next_eid*/);
+  /* ROOT_RRPATH must not be within another branch of the family. */
+
+  /* Register the branch */
+  APR_ARRAY_PUSH(family->branch_siblings, void *) = branch_sibling;
+
+  return branch_sibling;
+}
+
+/* Find the existing family with id FID in FAMILY (recursively, including
+ * FAMILY itself). Assume FID is unique among all families.
+ *
+ * Return NULL if not found.
+ */
+static svn_branch_family_t *
+family_get_subfamily_by_id(svn_branch_family_t *family,
+                           int fid)
+{
+  int i;
+
+  SVN_ERR_ASSERT_NO_RETURN(fid >= 0 && fid < family->repos->next_fid);
+
+  if (family->fid == fid)
+    return family;
+
+  for (i = 0; i < family->sub_families->nelts; i++)
+    {
+      svn_branch_family_t *f
+        = APR_ARRAY_IDX(family->sub_families, i, svn_branch_family_t *);
+
+      f = family_get_subfamily_by_id(f, fid);
+      if (f)
+        return f;
+    }
+
+  return NULL;
+}
+
+svn_branch_sibling_t *
+svn_branch_sibling_create(svn_branch_family_t *family,
+                             int bid,
+                             int root_eid,
+                             apr_pool_t *result_pool)
+{
+  svn_branch_sibling_t *b = apr_pcalloc(result_pool, sizeof(*b));
+
+  SVN_ERR_ASSERT_NO_RETURN(bid >= family->first_bid
+                           && bid < family->next_bid);
+  SVN_ERR_ASSERT_NO_RETURN(root_eid >= family->first_eid
+                           && root_eid < family->next_eid);
+
+  b->family = family;
+  b->bid = bid;
+  b->root_eid = root_eid;
+  return b;
+}
+
+svn_branch_instance_t *
+svn_branch_instance_create(svn_branch_sibling_t *branch_sibling,
+                           svn_branch_revision_root_t *rev_root,
+                           const char *branch_root_rrpath,
+                           apr_pool_t *result_pool)
+{
+  svn_branch_instance_t *b = apr_pcalloc(result_pool, sizeof(*b));
+
+  b->sibling_defn = branch_sibling;
+  b->rev_root = rev_root;
+  b->e_map = apr_hash_make(result_pool);
+  b->branch_root_rrpath = branch_root_rrpath;
+  return b;
+}
+
+svn_branch_el_rev_content_t *
+svn_branch_el_rev_content_create(svn_editor3_nbid_t parent_eid,
+                                 const char *name,
+                                 const svn_editor3_node_content_t *node_content,
+                                 apr_pool_t *result_pool)
+{
+  svn_branch_el_rev_content_t *content
+     = apr_palloc(result_pool, sizeof(*content));
+
+  content->parent_eid = parent_eid;
+  content->name = apr_pstrdup(result_pool, name);
+  content->content = svn_editor3_node_content_dup(node_content, result_pool);
+  return content;
+}
+
+svn_boolean_t
+svn_branch_el_rev_content_equal(int eid,
+                                const svn_branch_el_rev_content_t *content_left,
+                                const svn_branch_el_rev_content_t *content_right,
+                                apr_pool_t *scratch_pool)
+{
+  if (!content_left && !content_right)
+    {
+      return TRUE;
+    }
+  else if (!content_left || !content_right)
+    {
+      return FALSE;
+    }
+
+  if (content_left->parent_eid != content_right->parent_eid)
+    {
+      return FALSE;
+    }
+  if (strcmp(content_left->name, content_right->name) != 0)
+    {
+      return FALSE;
+    }
+  if (! svn_editor3_node_content_equal(content_left->content,
+                                       content_right->content,
+                                       scratch_pool))
+    {
+      return FALSE;
+    }
+
+  return TRUE;
+}
+
+
+/*
+ * ========================================================================
+ * Branch mappings
+ * ========================================================================
+ */
+
+const char *
+svn_branch_get_root_rrpath(const svn_branch_instance_t *branch)
+{
+  const char *root_rrpath = branch->branch_root_rrpath;
+
+  SVN_ERR_ASSERT_NO_RETURN(root_rrpath);
+  return root_rrpath;
+}
+
+/* Validate that NODE is suitable for a mapping of BRANCH:EID.
+ * NODE->content may be null.
+ */
+static void
+branch_map_node_validate(const svn_branch_instance_t *branch,
+                         int eid,
+                         const svn_branch_el_rev_content_t *node)
+{
+  SVN_ERR_ASSERT_NO_RETURN(node);
+
+  /* Parent EID must be valid, or -1 iff EID is the branch root. */
+  SVN_ERR_ASSERT_NO_RETURN(
+    (eid == branch->sibling_defn->root_eid)
+    ? (node->parent_eid == -1)
+    : (node->parent_eid >= branch->sibling_defn->family->first_eid
+       && node->parent_eid < branch->sibling_defn->family->next_eid));
+
+  /* Node name must be given, and empty iff EID is the branch root. */
+  SVN_ERR_ASSERT_NO_RETURN(
+    node->name
+    && (eid == branch->sibling_defn->root_eid) == (*node->name == '\0'));
+
+  /* Content, if specified, must be in full or by reference. */
+  if (node->content)
+    SVN_ERR_ASSERT_NO_RETURN(node->content
+                             && ((SVN_IS_VALID_REVNUM(node->content->ref.rev)
+                                  && node->content->ref.relpath)
+                                 || (node->content->kind != svn_node_unknown
+                                     && node->content->kind != svn_node_none)));
+}
+
+/* In BRANCH, get element EID's node (parent, name, content).
+ *
+ * If element EID is not present, return null. Otherwise, the returned
+ * node's content may be null meaning it is unknown.
+ */
+static svn_branch_el_rev_content_t *
+branch_map_get(const svn_branch_instance_t *branch,
+               int eid)
+{
+  svn_branch_el_rev_content_t *node;
+
+  SVN_ERR_ASSERT_NO_RETURN(eid >= branch->sibling_defn->family->first_eid
+                           && eid < branch->sibling_defn->family->next_eid);
+
+  node = apr_hash_get(branch->e_map, &eid, sizeof(eid));
+
+  if (node)
+    branch_map_node_validate(branch, eid, node);
+  return node;
+}
+
+/* In BRANCH, set element EID's node (parent, name, content) to NODE.
+ *
+ * If NODE is null, delete element EID. Otherwise, NODE->content may be
+ * null meaning it is unknown.
+ *
+ * Assume NODE is already allocated with sufficient lifetime.
+ */
+static void
+branch_map_set(svn_branch_instance_t *branch,
+               int eid,
+               svn_branch_el_rev_content_t *node)
+{
+  apr_pool_t *map_pool = apr_hash_pool_get(branch->e_map);
+  int *eid_p = apr_pmemdup(map_pool, &eid, sizeof(eid));
+
+  SVN_ERR_ASSERT_NO_RETURN(eid >= branch->sibling_defn->family->first_eid
+                           && eid < branch->sibling_defn->family->next_eid);
+  if (node)
+    branch_map_node_validate(branch, eid, node);
+
+  apr_hash_set(branch->e_map, eid_p, sizeof(*eid_p), node);
+}
+
+/* In BRANCH, delete element EID.
+ */
+static void
+branch_map_delete(svn_branch_instance_t *branch,
+                  int eid)
+{
+  SVN_ERR_ASSERT_NO_RETURN(eid >= branch->sibling_defn->family->first_eid
+                           && eid < branch->sibling_defn->family->next_eid);
+
+  branch_map_set(branch, eid, NULL);
+}
+
+/* Set or change the EID:element mapping for EID in BRANCH.
+ *
+ * Duplicate NEW_NAME and NEW_CONTENT into the branch mapping's pool.
+ */
+static void
+branch_map_update(svn_branch_instance_t *branch,
+                  int eid,
+                  svn_editor3_nbid_t new_parent_eid,
+                  const char *new_name,
+                  const svn_editor3_node_content_t *new_content)
+{
+  apr_pool_t *map_pool = apr_hash_pool_get(branch->e_map);
+  svn_branch_el_rev_content_t *node
+    = svn_branch_el_rev_content_create(new_parent_eid, new_name, new_content,
+                                       map_pool);
+
+  /* EID must be a valid element id of the branch family */
+  SVN_ERR_ASSERT_NO_RETURN(eid >= branch->sibling_defn->family->first_eid
+                           && eid < branch->sibling_defn->family->next_eid);
+  /* NEW_CONTENT must be specified, either in full or by reference */
+  SVN_ERR_ASSERT_NO_RETURN(new_content);
+
+  /* We don't expect to be called more than once per eid. */
+  /*SVN_ERR_ASSERT_NO_RETURN(branch_map_get(branch, eid) == NULL); ### hmm, no! */
+
+  /* Insert the new version */
+  branch_map_set(branch, eid, node);
+}
+
+/* Set or change the EID:element mapping for EID in BRANCH to reflect a
+ * subbranch root node. This node has no content in this branch; the
+ * corresponding element of the subbranch will define its content.
+ *
+ * Duplicate NEW_NAME and NEW_CONTENT into the branch mapping's pool.
+ */
+static void
+branch_map_update_as_subbranch_root(svn_branch_instance_t *branch,
+                                    int eid,
+                                    svn_editor3_nbid_t new_parent_eid,
+                                    const char *new_name)
+{
+  apr_pool_t *map_pool = apr_hash_pool_get(branch->e_map);
+  svn_branch_el_rev_content_t *node
+    = svn_branch_el_rev_content_create(new_parent_eid, new_name, NULL /*content*/,
+                                       map_pool);
+
+  /* EID must be a valid element id of the branch family */
+  SVN_ERR_ASSERT_NO_RETURN(eid >= branch->sibling_defn->family->first_eid
+                           && eid < branch->sibling_defn->family->next_eid);
+  branch_map_node_validate(branch, eid, node);
+
+  /* We don't expect to be called more than once per eid. */
+  /*SVN_ERR_ASSERT_NO_RETURN(branch_map_get(branch, eid) == NULL); ### hmm, no! */
+
+  /* Insert the new version */
+  branch_map_set(branch, eid, node);
+}
+
+/* If the mapping has a complete path from the root to element EID,
+ * return this path, relative to the branch root.
+ *
+ * If the EID mapping does not currently have a complete path to EID,
+ * return NULL.
+ */
+static const char *
+branch_map_get_path_by_eid(const svn_branch_instance_t *branch,
+                           int eid,
+                           apr_pool_t *result_pool)
+{
+  const char *path = "";
+  svn_branch_el_rev_content_t *node;
+
+  SVN_ERR_ASSERT_NO_RETURN(eid >= branch->sibling_defn->family->first_eid
+                           && eid < branch->sibling_defn->family->next_eid);
+
+  for (; eid != branch->sibling_defn->root_eid; eid = node->parent_eid)
+    {
+      node = branch_map_get(branch, eid);
+      if (! node)
+        return NULL;
+      path = svn_relpath_join(node->name, path, result_pool);
+    }
+  SVN_ERR_ASSERT_NO_RETURN(eid == branch->sibling_defn->root_eid);
+  return path;
+}
+
+const char *
+svn_branch_get_rrpath_by_eid(const svn_branch_instance_t *branch,
+                             int eid,
+                             apr_pool_t *result_pool)
+{
+  const char *path = branch_map_get_path_by_eid(branch, eid, result_pool);
+  const char *rrpath = NULL;
+
+  if (path)
+    {
+      rrpath = svn_relpath_join(svn_branch_get_root_rrpath(branch),
+                                path, result_pool);
+    }
+  return rrpath;
+}
+
+/* Assuming the mapping has complete paths from the root to each element,
+ * get the EID for path PATH which is relative to the root of the branch.
+ */
+static int
+branch_map_get_eid_by_path(const svn_branch_instance_t *branch,
+                           const char *path,
+                           apr_pool_t *scratch_pool)
+{
+  apr_hash_index_t *hi;
+
+  /* ### This is a crude, linear search */
+  for (hi = apr_hash_first(scratch_pool, branch->e_map);
+       hi; hi = apr_hash_next(hi))
+    {
+      int eid = *(const int *)apr_hash_this_key(hi);
+      const char *this_path = branch_map_get_path_by_eid(branch, eid,
+                                                         scratch_pool);
+
+      if (strcmp(path, this_path) == 0)
+        {
+          return eid;
+        }
+    }
+
+  return -1;
+}
+
+/* Assuming the mapping has complete paths from the root to each element,
+ * get the EID for path PATH which is relative to the root of the branch.
+ */
+static int
+branch_map_get_eid_by_rrpath(svn_branch_instance_t *branch,
+                             const char *rrpath,
+                             apr_pool_t *scratch_pool)
+{
+  const char *path = svn_relpath_skip_ancestor(svn_branch_get_root_rrpath(branch),
+                                               rrpath);
+  int eid = -1;
+
+  if (path)
+    {
+      eid = branch_map_get_eid_by_path(branch, path, scratch_pool);
+    }
+  return eid;
+}
+
+/* Get an element's content (props, text, ...) in full or by reference.
+ */
+static svn_error_t *
+copy_content_from(svn_editor3_node_content_t **content_p,
+                  svn_branch_instance_t *from_branch,
+                  int from_eid,
+                  apr_pool_t *result_pool,
+                  apr_pool_t *scratch_pool)
+{
+  svn_branch_el_rev_content_t *old_el = branch_map_get(from_branch, from_eid);
+  svn_editor3_node_content_t *content = old_el->content;
+
+  /* If content is unknown, then presumably this is a committed rev and
+     so we can provide a reference to the committed content. */
+  if (! content)
+    {
+      svn_editor3_peg_path_t peg;
+
+      SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(from_branch->rev_root->rev));
+      peg.rev = from_branch->rev_root->rev;
+      peg.relpath = svn_branch_get_rrpath_by_eid(from_branch, from_eid,
+                                                 scratch_pool);
+      content = svn_editor3_node_content_create_ref(peg, result_pool);
+    }
+  *content_p = content;
+  return SVN_NO_ERROR;
+}
+
+/* In BRANCH, update the path mappings to delete the children of EID.
+ *
+ * EID MUST be an existing element in BRANCH. It may be the root element
+ * of BRANCH but not a subtree root.
+ */
+static svn_error_t *
+branch_map_delete_children(svn_branch_instance_t *branch,
+                           int eid,
+                           apr_pool_t *scratch_pool)
+{
+  apr_hash_index_t *hi;
+
+  for (hi = apr_hash_first(scratch_pool, branch->e_map);
+       hi; hi = apr_hash_next(hi))
+    {
+      int this_eid = *(const int *)apr_hash_this_key(hi);
+      svn_branch_el_rev_content_t *this_node = apr_hash_this_val(hi);
+
+      if (this_node->parent_eid == eid)
+        {
+          branch_map_delete(branch, this_eid);
+        }
+    }
+
+  return SVN_NO_ERROR;
+}
+/* In TO_BRANCH, assign new EIDs and path mappings to reflect the copying
+ * of all children of FROM_BRANCH:FROM_PARENT_EID to TO_BRANCH:TO_PARENT_EID,
+ * recursively, excluding the specified parent element itself.
+ *
+ * Assign a new EID in TO_BRANCH's family for each copied element.
+ *
+ * FROM_BRANCH and TO_BRANCH may be the same or different branch instances
+ * in the same or different branch families.
+ *
+ * FROM_PARENT_EID MUST be an existing element in FROM_BRANCH. It may be the
+ * root element of FROM_BRANCH, but not a subtree root.
+ *
+ * TO_PARENT_EID MUST be an existing path in TO_BRANCH. It may be the
+ * root element of TO_BRANCH, but not a subtree root.
+ */
+static svn_error_t *
+branch_map_copy_children(svn_branch_instance_t *from_branch,
+                         int from_parent_eid,
+                         svn_branch_instance_t *to_branch,
+                         int to_parent_eid,
+                         apr_pool_t *scratch_pool)
+{
+  apr_pool_t *map_pool = apr_hash_pool_get(to_branch->e_map);
+  apr_hash_index_t *hi;
+
+  /* The 'from' and 'to' nodes must exist. */
+  SVN_ERR_ASSERT(branch_map_get(from_branch, from_parent_eid));
+  SVN_ERR_ASSERT(branch_map_get(to_branch, to_parent_eid));
+
+  /* Process the immediate children of FROM_PARENT_EID. */
+  for (hi = apr_hash_first(scratch_pool, from_branch->e_map);
+       hi; hi = apr_hash_next(hi))
+    {
+      int this_from_eid = *(const int *)apr_hash_this_key(hi);
+      svn_branch_el_rev_content_t *from_node = apr_hash_this_val(hi);
+
+      if (from_node->parent_eid == from_parent_eid)
+        {
+          int new_eid = family_add_new_element(to_branch->sibling_defn->family);
+          svn_branch_el_rev_content_t *new_node
+            = svn_branch_el_rev_content_create(to_parent_eid, from_node->name,
+                                               from_node->content, map_pool);
+
+          branch_map_set(to_branch, new_eid, new_node);
+
+          /* Recurse. (We don't try to check whether it's a directory node,
+             as we might not have the node kind in the map.) */
+          SVN_ERR(branch_map_copy_children(from_branch, this_from_eid,
+                                           to_branch, new_eid,
+                                           scratch_pool));
+        }
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Update the mappings to reflect branching the subtree [### the what?!]
+ * at FROM_BRANCH:FROM_PARENT_EID to TO_BRANCH:TO_PARENT_EID.
+ *
+ * FROM_BRANCH and TO_BRANCH must be different branch instances in the
+ * same branch family.
+ *
+ * FROM_PATH MUST be an existing path in FROM_BRANCH, and may be the
+ * root path of FROM_BRANCH.
+ *
+ * TO_PATH MUST be a path in TO_BRANCH at which nothing currently exists
+ * if INCLUDE_SELF, or an existing path if not INCLUDE_SELF.
+ *
+ * If INCLUDE_SELF is true, include the element at FROM_PATH, otherwise
+ * only act on children (recursively) of FROM_PATH.
+ */
+static svn_error_t *
+branch_map_branch_children(svn_branch_instance_t *from_branch,
+                           int from_parent_eid,
+                           svn_branch_instance_t *to_branch,
+                           int to_parent_eid,
+                           apr_pool_t *scratch_pool)
+{
+  apr_hash_index_t *hi;
+
+  SVN_ERR_ASSERT(from_branch->sibling_defn->family->fid
+                 == to_branch->sibling_defn->family->fid);
+  SVN_ERR_ASSERT(from_branch->sibling_defn->bid != to_branch->sibling_defn->bid);
+
+  /* The 'from' and 'to' nodes must exist. */
+  SVN_ERR_ASSERT(branch_map_get(from_branch, from_parent_eid));
+  SVN_ERR_ASSERT(branch_map_get(to_branch, to_parent_eid));
+
+  /* Process the immediate children of FROM_PARENT_EID. */
+  for (hi = apr_hash_first(scratch_pool, from_branch->e_map);
+       hi; hi = apr_hash_next(hi))
+    {
+      int this_eid = *(const int *)apr_hash_this_key(hi);
+      svn_branch_el_rev_content_t *from_node = branch_map_get(from_branch,
+                                                              this_eid);
+
+      if (from_node->parent_eid == from_parent_eid)
+        {
+          svn_editor3_node_content_t *this_content;
+
+          SVN_ERR(copy_content_from(&this_content, from_branch, this_eid,
+                                    scratch_pool, scratch_pool));
+          from_node->content = this_content;
+          branch_map_set(to_branch, this_eid, from_node);
+
+          /* Recurse. (We don't try to check whether it's a directory node,
+             as we might not have the node kind in the map.) */
+          SVN_ERR(branch_map_branch_children(from_branch, this_eid,
+                                             to_branch, this_eid,
+                                             scratch_pool));
+        }
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Return an array of pointers to the branch instances that are immediate
+ * sub-branches of BRANCH at or below EID.
+ */
+static apr_array_header_t *
+branch_get_sub_branches(const svn_branch_instance_t *branch,
+                        int eid,
+                        apr_pool_t *result_pool,
+                        apr_pool_t *scratch_pool)
+{
+  const char *top_rrpath = svn_branch_get_rrpath_by_eid(branch, eid,
+                                                        scratch_pool);
+  apr_array_header_t *sub_branches = apr_array_make(result_pool, 0,
+                                                    sizeof(void *));
+  int i;
+
+  for (i = 0; i < branch->sibling_defn->family->sub_families->nelts; i++)
+    {
+      svn_branch_family_t *family
+        = APR_ARRAY_IDX(branch->sibling_defn->family->sub_families, i, void *);
+      int b;
+
+      for (b = 0; b < family->branch_instances->nelts; b++)
+        {
+          svn_branch_instance_t *sub_branch
+            = APR_ARRAY_IDX(family->branch_instances, b, void *);
+          const char *sub_branch_root_rrpath = svn_branch_get_root_rrpath(sub_branch);
+
+          if (svn_relpath_skip_ancestor(top_rrpath, sub_branch_root_rrpath))
+            {
+              APR_ARRAY_PUSH(sub_branches, void *) = sub_branch;
+            }
+        }
+    }
+  return sub_branches;
+}
+
+/* Return an array of pointers to the branch instances that are immediate
+ * sub-branches of BRANCH.
+ */
+static apr_array_header_t *
+branch_get_all_sub_branches(const svn_branch_instance_t *branch,
+                            apr_pool_t *result_pool,
+                            apr_pool_t *scratch_pool)
+{
+  return branch_get_sub_branches(branch, branch->sibling_defn->root_eid,
+                                 result_pool, scratch_pool);
+}
+
+/* Delete the branch instance BRANCH by removing the record of it from its
+ * family.
+ */
+static svn_error_t *
+branch_instance_delete(svn_branch_instance_t *branch,
+                       apr_pool_t *scratch_pool)
+{
+  svn_branch_family_t *family = branch->sibling_defn->family;
+  int i;
+
+  for (i = 0; i < family->branch_instances->nelts; i++)
+    {
+      svn_branch_instance_t *b
+        = APR_ARRAY_IDX(family->branch_instances, i, void *);
+
+      if (b == branch)
+        {
+          svn_sort__array_delete(family->branch_instances, i, 1);
+          break;
+        }
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/* Delete the branch instance object BRANCH and any nested branch instances,
+ * recursively.
+ */
+static svn_error_t *
+branch_instance_delete_r(svn_branch_instance_t *branch,
+                         apr_pool_t *scratch_pool)
+{
+  apr_array_header_t *subbranches
+    = branch_get_all_sub_branches(branch, scratch_pool, scratch_pool);
+  int i;
+
+  /* Delete nested branch instances, recursively */
+  for (i = 0; i < subbranches->nelts; i++)
+    {
+      svn_branch_instance_t *b = APR_ARRAY_IDX(subbranches, i, void *);
+
+      branch_instance_delete_r(b, scratch_pool);
+    }
+
+  /* Remove the record of this branch instance */
+  SVN_ERR(branch_instance_delete(branch, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+/* Create a new branch instance at OUTER_BRANCH:EID, of the branch class
+ * BRANCH_SIBLING, with no elements.
+ */
+static svn_branch_instance_t *
+branch_add_new_branch_instance(svn_branch_instance_t *outer_branch,
+                               int outer_eid,
+                               svn_branch_sibling_t *branch_sibling,
+                               apr_pool_t *scratch_pool)
+{
+  svn_branch_family_t *family = branch_sibling->family;
+
+  /* All this next bit is to get an RRPATH. Should ultimately go away. */
+  const char *outer_root_rrpath = svn_branch_get_root_rrpath(outer_branch);
+  const char *outer_eid_relpath
+    = branch_map_get_path_by_eid(outer_branch, outer_eid, scratch_pool);
+  const char *new_root_rrpath
+    = svn_relpath_join(outer_root_rrpath, outer_eid_relpath, scratch_pool);
+
+  svn_branch_instance_t *branch_instance
+    = svn_branch_instance_create(branch_sibling, outer_branch->rev_root,
+                                 new_root_rrpath, family->pool);
+
+  APR_ARRAY_PUSH(family->branch_instances, void *) = branch_instance;
+
+  return branch_instance;
+}
+
+
+/*
+ * ========================================================================
+ * Parsing and Serializing
+ * ========================================================================
+ */
+
+/* Create a new branch *NEW_BRANCH that belongs to FAMILY, initialized
+ * with info parsed from STREAM, allocated in RESULT_POOL.
+ */
+static svn_error_t *
+svn_branch_instance_parse(svn_branch_instance_t **new_branch,
+                          svn_branch_family_t *family,
+                          svn_branch_revision_root_t *rev_root,
+                          svn_stream_t *stream,
+                          apr_pool_t *result_pool,
+                          apr_pool_t *scratch_pool)
+{
+  svn_stringbuf_t *line;
+  svn_boolean_t eof;
+  int n;
+  int fid, bid, root_eid;
+  svn_branch_sibling_t *branch_sibling;
+  svn_branch_instance_t *branch_instance;
+  char branch_root_path[100];
+  const char *branch_root_rrpath;
+  int eid;
+
+  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+  SVN_ERR_ASSERT(! eof);
+  n = sscanf(line->data, "f%db%d: root-eid %d at %s\n",
+             &fid, &bid, &root_eid, branch_root_path);
+  SVN_ERR_ASSERT(n == 4);
+
+  SVN_ERR_ASSERT(fid == family->fid);
+  branch_root_rrpath = svn_path_internal_style(branch_root_path, scratch_pool);
+  branch_sibling = svn_branch_sibling_create(family, bid, root_eid,
+                                             result_pool);
+  branch_instance = svn_branch_instance_create(branch_sibling, rev_root,
+                                               branch_root_rrpath, result_pool);
+
+  for (eid = family->first_eid; eid < family->next_eid; eid++)
+    {
+      int this_fid, this_bid, this_eid, this_parent_eid;
+      char this_name[20], this_path[100];
+
+      SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+      SVN_ERR_ASSERT(! eof);
+      n = sscanf(line->data, "f%db%de%d: %d %20s %100s\n",
+                 &this_fid, &this_bid, &this_eid,
+                 &this_parent_eid, this_name, this_path);
+      SVN_ERR_ASSERT(n == 6);
+      if (strcmp(this_path, "(null)") != 0)
+        {
+          const char *name = svn_path_internal_style(this_name, scratch_pool);
+          const char *path = svn_path_internal_style(this_path, scratch_pool);
+          const char *rrpath = svn_relpath_join(branch_root_rrpath, path,
+                                                scratch_pool);
+          svn_editor3_peg_path_t peg;
+          svn_editor3_node_content_t *content;
+
+          /* Specify the content by reference */
+          peg.rev = rev_root->rev;
+          peg.relpath = rrpath;
+          content = svn_editor3_node_content_create_ref(peg, scratch_pool);
+
+          branch_map_update(branch_instance, this_eid,
+                            this_parent_eid, name, content);
+        }
+    }
+
+  *new_branch = branch_instance;
+  return SVN_NO_ERROR;
+}
+
+/* Create a new family *NEW_FAMILY as a sub-family of FAMILY, initialized
+ * with info parsed from STREAM, allocated in RESULT_POOL.
+ */
+static svn_error_t *
+svn_branch_family_parse(svn_branch_family_t **new_family,
+                        int *parent_fid,
+                        svn_branch_repos_t *repos,
+                        svn_stream_t *stream,
+                        apr_pool_t *result_pool,
+                        apr_pool_t *scratch_pool)
+{
+  svn_stringbuf_t *line;
+  svn_boolean_t eof;
+  int n;
+  int fid, first_bid, next_bid, first_eid, next_eid;
+
+  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+  SVN_ERR_ASSERT(!eof);
+  n = sscanf(line->data, "f%d: bids %d %d eids %d %d parent-fid %d\n",
+             &fid,
+             &first_bid, &next_bid, &first_eid, &next_eid,
+             parent_fid);
+  SVN_ERR_ASSERT(n == 6);
+
+  *new_family = svn_branch_family_create(repos, fid,
+                                         first_bid, next_bid,
+                                         first_eid, next_eid,
+                                         result_pool);
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch_revision_root_parse(svn_branch_revision_root_t **rev_root_p,
+                               int *next_fid_p,
+                               svn_branch_repos_t *repos,
+                               svn_stream_t *stream,
+                               apr_pool_t *result_pool,
+                               apr_pool_t *scratch_pool)
+{
+  svn_branch_revision_root_t *rev_root = NULL;
+  svn_revnum_t rev;
+  int root_fid;
+  svn_stringbuf_t *line;
+  svn_boolean_t eof;
+  int n, i;
+  svn_branch_family_t *root_family = NULL;
+
+  SVN_ERR(svn_stream_readline(stream, &line, "\n", &eof, scratch_pool));
+  SVN_ERR_ASSERT(! eof);
+  n = sscanf(line->data, "r%ld: fids %*d %d root-fid %d",
+             &rev, /* 0, */ next_fid_p, &root_fid);
+  SVN_ERR_ASSERT(n == 3);
+
+  /* parse the families */
+  for (i = 0; i < *next_fid_p; i++)
+    {
+      svn_branch_family_t *family;
+      int bid, parent_fid;
+
+      SVN_ERR(svn_branch_family_parse(&family, &parent_fid, repos, stream,
+                                      result_pool, scratch_pool));
+
+      if (family->fid == root_fid)
+        {
+          rev_root = svn_branch_revision_root_create(repos, rev,
+                                                     NULL /*root_branch*/,
+                                                     result_pool);
+          root_family = family;
+        }
+      else
+        {
+          svn_branch_family_t *parent_family
+            = family_get_subfamily_by_id(root_family, parent_fid);
+
+          SVN_ERR_ASSERT(parent_family);
+          APR_ARRAY_PUSH(parent_family->sub_families, void *) = family;
+        }
+
+      /* parse the branches */
+      for (bid = family->first_bid; bid < family->next_bid; ++bid)
+        {
+          svn_branch_instance_t *branch;
+
+          SVN_ERR(svn_branch_instance_parse(&branch, family, rev_root, stream,
+                                            family->pool, scratch_pool));
+          APR_ARRAY_PUSH(family->branch_instances, void *) = branch;
+          if (family->fid == root_fid)
+            {
+              branch->rev_root->root_branch = branch;
+            }
+        }
+    }
+
+  *rev_root_p = rev_root;
+  return SVN_NO_ERROR;
+}
+
+/* Write to STREAM a parseable representation of BRANCH.
+ */
+static svn_error_t *
+svn_branch_instance_serialize(svn_stream_t *stream,
+                              svn_branch_instance_t *branch,
+                              apr_pool_t *scratch_pool)
+{
+  svn_branch_family_t *family = branch->sibling_defn->family;
+  const char *branch_root_rrpath = svn_branch_get_root_rrpath(branch);
+  int eid;
+
+  SVN_ERR(svn_stream_printf(stream, scratch_pool,
+                            "f%db%d: root-eid %d at %s\n",
+                            family->fid, branch->sibling_defn->bid,
+                            branch->sibling_defn->root_eid,
+                            svn_path_local_style(branch_root_rrpath,
+                                                 scratch_pool)));
+  for (eid = family->first_eid; eid < family->next_eid; eid++)
+    {
+      svn_branch_el_rev_content_t *node = branch_map_get(branch, eid);
+      int parent_eid;
+      const char *name;
+      const char *path;
+
+      if (node)
+        {
+          path = branch_map_get_path_by_eid(branch, eid, scratch_pool);
+          SVN_ERR_ASSERT(path);
+          parent_eid = node->parent_eid;
+          name = svn_path_local_style(node->name, scratch_pool);
+          path = svn_path_local_style(path, scratch_pool);
+        }
+      else
+        {
+          /* ### TODO: instead, omit the line completely; but the
+                 parser currently can't handle that. */
+          parent_eid = -1;
+          name = "(null)";
+          path = "(null)";
+        }
+      SVN_ERR(svn_stream_printf(stream, scratch_pool,
+                                "f%db%de%d: %d %s %s\n",
+                                family->fid, branch->sibling_defn->bid, eid,
+                                parent_eid, name, path));
+    }
+  return SVN_NO_ERROR;
+}
+
+/* Write to STREAM a parseable representation of FAMILY whose parent
+ * family id is PARENT_FID. Recursively write all sub-families.
+ */
+static svn_error_t *
+svn_branch_family_serialize(svn_stream_t *stream,
+                            svn_branch_family_t *family,
+                            int parent_fid,
+                            apr_pool_t *scratch_pool)
+{
+  int i;
+
+  SVN_ERR(svn_stream_printf(stream, scratch_pool,
+                            "f%d: bids %d %d eids %d %d parent-fid %d\n",
+                            family->fid,
+                            family->first_bid, family->next_bid,
+                            family->first_eid, family->next_eid,
+                            parent_fid));
+
+  for (i = 0; i < family->branch_instances->nelts; i++)
+    {
+      svn_branch_instance_t *branch
+        = APR_ARRAY_IDX(family->branch_instances, i, void *);
+
+      SVN_ERR(svn_branch_instance_serialize(stream, branch, scratch_pool));
+    }
+
+  if (family->sub_families)
+    {
+      for (i = 0; i < family->sub_families->nelts; i++)
+        {
+          svn_branch_family_t *f
+            = APR_ARRAY_IDX(family->sub_families, i, void *);
+
+          SVN_ERR(svn_branch_family_serialize(stream, f, family->fid,
+                                              scratch_pool));
+        }
+    }
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch_revision_root_serialize(svn_stream_t *stream,
+                                   svn_branch_revision_root_t *rev_root,
+                                   int next_fid,
+                                   apr_pool_t *scratch_pool)
+{
+  SVN_ERR(svn_stream_printf(stream, scratch_pool,
+                            "r%ld: fids %d %d root-fid %d\n",
+                            rev_root->rev,
+                            0, next_fid,
+                            rev_root->root_branch->sibling_defn->family->fid));
+
+  SVN_ERR(svn_branch_family_serialize(
+            stream, rev_root->root_branch->sibling_defn->family,
+            0 /*parent_fid*/, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+
+/*
+ * ========================================================================
+ */
+
+static svn_error_t *
+svn_branch_el_rev_get(svn_branch_el_rev_content_t **node_p,
+                      svn_editor3_t *editor,
+                      svn_branch_instance_t *branch,
+                      int eid,
+                      apr_pool_t *result_pool,
+                      apr_pool_t *scratch_pool);
+
+/* Find the (deepest) branch of which the path RRPATH is either the root
+ * path or a normal, non-sub-branch path. An element need not exist at
+ * RRPATH.
+ *
+ * Set *BRANCH_P to the deepest branch within ROOT_BRANCH (recursively,
+ * including itself) that contains the path RRPATH.
+ *
+ * If EID_P is not null then set *EID_P to the element id of RRPATH in
+ * *BRANCH_P, or to -1 if no element exists at RRPATH in that branch.
+ *
+ * If RRPATH is not within any branch in ROOT_BRANCH, set *BRANCH_P to
+ * NULL and (if EID_P is not null) *EID_P to -1.
+ */
+static void
+svn_branch_find_nested_branch_element_by_rrpath(
+                                svn_branch_instance_t **branch_p,
+                                int *eid_p,
+                                svn_branch_instance_t *root_branch,
+                                const char *rrpath,
+                                apr_pool_t *scratch_pool)
+{
+  const char *branch_root_path = svn_branch_get_root_rrpath(root_branch);
+  apr_array_header_t *branch_instances;
+  int i;
+
+  if (! svn_relpath_skip_ancestor(branch_root_path, rrpath))
+    {
+      /* The path we're looking for is not (path-wise) in this branch. */
+      *branch_p = NULL;
+      if (eid_p)
+        *eid_p = -1;
+      return;
+    }
+
+  /* The path we're looking for is (path-wise) in this branch. See if it
+     is also in a sub-branch (recursively). */
+  branch_instances = branch_get_all_sub_branches(root_branch,
+                                                 scratch_pool, scratch_pool);
+  for (i = 0; i < branch_instances->nelts; i++)
+    {
+      svn_branch_instance_t *this_branch
+        = APR_ARRAY_IDX(branch_instances, i, void *);
+      svn_branch_instance_t *sub_branch;
+      int sub_branch_eid;
+
+      svn_branch_find_nested_branch_element_by_rrpath(&sub_branch, &sub_branch_eid,
+                                                    this_branch, rrpath,
+                                                    scratch_pool);
+      if (sub_branch)
+        {
+           *branch_p = sub_branch;
+           if (eid_p)
+             *eid_p = sub_branch_eid;
+           return;
+         }
+    }
+
+  *branch_p = root_branch;
+  if (eid_p)
+    *eid_p = branch_map_get_eid_by_rrpath(root_branch, rrpath, scratch_pool);
+}
+
+/* Find the deepest branch in REV_ROOT of which RRPATH is
+ * either the root element or a normal, non-sub-branch element.
+ * If EID_P is not null, set *EID_P to the EID of RRPATH in that branch.
+ *
+ * An element need not exist at RRPATH.
+ *
+ * The result will never be NULL.
+ */
+static svn_branch_instance_t *
+svn_branch_revision_root_find_branch_element_by_rrpath(
+                                int *eid_p,
+                                svn_branch_revision_root_t *rev_root,
+                                const char *rrpath,
+                                apr_pool_t *scratch_pool)
+{
+  svn_branch_instance_t *branch;
+
+  svn_branch_find_nested_branch_element_by_rrpath(&branch, eid_p,
+                                                  rev_root->root_branch, rrpath,
+                                                  scratch_pool);
+
+  /* Any path must at least be within the repository root branch */
+  SVN_ERR_ASSERT_NO_RETURN(branch);
+  return branch;
+}
+
+/* Adjust BRANCH and its subbranches (recursively),
+ * to reflect deletion of the subtree at EID.
+ *
+ * Element EID MUST be the location of a non-root element of BRANCH.
+ * If EID is the root of a subbranch and/or contains nested
+ * subbranches, also delete them.
+ */
+static svn_error_t *
+branch_delete_subtree_r(svn_branch_instance_t *branch,
+                        int eid,
+                        apr_pool_t *scratch_pool)
+{
+  apr_array_header_t *subbranches;
+  int i;
+
+  /* Delete any nested subbranches at or inside EID. */
+  subbranches = branch_get_sub_branches(branch, eid, scratch_pool, scratch_pool);
+  for (i = 0; i < subbranches->nelts; i++)
+    {
+      svn_branch_instance_t *subbranch
+        = APR_ARRAY_IDX(subbranches, i, void *);
+
+      /* Delete the whole subbranch (recursively) */
+      SVN_ERR(branch_instance_delete_r(subbranch, scratch_pool));
+    }
+
+  /* update the element mapping in this branch */
+  branch_map_delete(branch, eid /* ### , since_rev? */);
+  /* ### TODO: delete all elements under EID too. */
+
+  return SVN_NO_ERROR;
+}
+
+/* Adjust TO_OUTER_BRANCH and its subbranches (recursively),
+ * to reflect branching a subtree from FROM_BRANCH:FROM_EID to
+ * create a new subbranch of TO_OUTER_BRANCH at TO_OUTER_PARENT_EID:NEW_NAME.
+ *
+ * FROM_BRANCH must be an immediate child branch of OUTER_BRANCH.
+ *
+ * FROM_BRANCH:FROM_EID must be an existing element. It may be the
+ * root of FROM_BRANCH. It must not be the root of a subbranch of
+ * FROM_BRANCH.
+ *
+ * TO_OUTER_BRANCH:TO_OUTER_PARENT_EID must be an existing directory
+ * and NEW_NAME must be nonexistent in that directory.
+ */
+static svn_error_t *
+branch_branch_subtree_r(svn_branch_instance_t **new_branch_p,
+                        svn_branch_instance_t *from_branch,
+                        int from_eid,
+                        svn_branch_instance_t *to_outer_branch,
+                        svn_editor3_nbid_t to_outer_parent_eid,
+                        const char *new_name,
+                        apr_pool_t *scratch_pool)
+{
+  int to_outer_eid;
+  svn_branch_sibling_t *new_branch_def;
+  svn_branch_instance_t *new_branch;
+
+  /* FROM_BRANCH must be an immediate child branch of OUTER_BRANCH. */
+  /* SVN_ERR_ASSERT(from_branch->sibling_defn->family->parent_family->fid
+                 == to_outer_branch->sibling_defn->family->fid); */
+
+  /* SVN_ERR_ASSERT(...); */
+
+  /* assign new eid to root node (outer branch) */
+  to_outer_eid = family_add_new_element(to_outer_branch->sibling_defn->family);
+  branch_map_update_as_subbranch_root(to_outer_branch, to_outer_eid,
+                                      to_outer_parent_eid, new_name);
+
+  /* create new inner branch sibling & instance */
+  /* ### On sub-branches, should not add new branch sibling, only instance. */
+  new_branch_def
+    = family_add_new_branch_sibling(from_branch->sibling_defn->family,
+                                    from_eid);
+  new_branch = branch_add_new_branch_instance(to_outer_branch, to_outer_eid,
+                                              new_branch_def, scratch_pool);
+
+  /* Initialize the new (inner) branch root element */
+  {
+    svn_editor3_node_content_t *old_content;
+
+    SVN_ERR(copy_content_from(&old_content,
+                              from_branch, from_eid,
+                              scratch_pool, scratch_pool));
+    branch_map_update(new_branch, new_branch_def->root_eid,
+                      -1, "", old_content);
+  }
+
+  /* Populate the rest of the new branch mapping */
+  SVN_ERR(branch_map_branch_children(from_branch, from_eid,
+                                     new_branch, new_branch_def->root_eid,
+                                     scratch_pool));
+
+  /* branch any subbranches of FROM_BRANCH */
+#if 0 /* ### Later. */
+  apr_array_header_t *subbranches;
+  int i;
+  subbranches = branch_get_all_sub_branches(from_branch, scratch_pool, scratch_pool);
+  for (i = 0; i < subbranches->nelts; i++)
+    {
+      svn_branch_instance_t *subbranch
+        = APR_ARRAY_IDX(subbranches, i, void *);
+      const char *subbranch_root_path = svn_branch_get_root_rrpath(subbranch);
+      const char *subbranch_within_from_path
+        = svn_relpath_skip_ancestor(from_rrpath, subbranch_root_path);
+
+      if (subbranch_within_from_path)
+        {
+          const char *subbranch_root_to_path
+            = svn_relpath_join(to_rrpath, subbranch_within_from_path,
+                               scratch_pool);
+
+          /* branch this subbranch into NEW_BRANCH (recursing) */
+          SVN_ERR(branch_branch_subtree_r(NULL,
+                                          new_branch,
+                                          subbranch, subbranch_root_path,
+                                          subbranch_root_to_path,
+                                          scratch_pool));
+        }
+    }
+#endif
+
+  if (new_branch_p)
+    *new_branch_p = new_branch;
+  return SVN_NO_ERROR;
+}
+
+/* Adjust BRANCH and its subbranches (recursively),
+ * to reflect a copy of a subtree from FROM_PATH to TO_PATH.
+ *
+ * FROM_PATH must be an existing element of BRANCH. (It may be the root.)
+ * If FROM_PATH is the root of a subbranch and/or contains nested
+ * subbranches, also copy them (by branching).
+ *
+ * TO_PATH must be a non-existing path in an existing parent directory in
+ * BRANCH.
+ */
+static svn_error_t *
+branch_copy_subtree_r(svn_branch_instance_t *branch,
+                      svn_revnum_t src_revision,
+                      svn_editor3_nbid_t src_eid,
+                      svn_editor3_nbid_t new_parent_eid,
+                      const char *new_name,
+                      apr_pool_t *scratch_pool)
+{
+  /* assign new EIDs and update the mappings in this branch */
+  SVN_ERR(branch_map_copy_children(branch, src_eid,
+                                   branch, branch->sibling_defn->root_eid,
+                                   scratch_pool));
+
+#if 0
+  /* handle subbranches */
+  apr_array_header_t *subbranches;
+  int i;
+
+  subbranches = branch_get_all_sub_branches(branch, scratch_pool, scratch_pool);
+  for (i = 0; i < subbranches->nelts; i++)
+    {
+      svn_branch_instance_t *subbranch
+        = APR_ARRAY_IDX(subbranches, i, void *);
+      const char *subbranch_root_path = svn_branch_get_root_rrpath(subbranch);
+      const char *subbranch_within_from_path
+        = svn_relpath_skip_ancestor(from_path, subbranch_root_path);
+
+      if (subbranch_within_from_path)
+        {
+          const char *subbranch_root_to_path
+            = svn_relpath_join(to_path, subbranch_within_from_path,
+                               scratch_pool);
+
+          /* branch the whole subbranch (recursively) */
+          SVN_ERR(branch_branch_subtree_r(NULL,
+                                          branch,
+                                          subbranch, subbranch_root_path,
+                                          subbranch_root_to_path,
+                                          scratch_pool));
+        }
+    }
+#endif
+  return SVN_NO_ERROR;
+}
+
+/* Return the relative path to element EID within SUBTREE.
+ *
+ * Assumes the mapping is "complete" (has complete paths to SUBTREE and to EID).
+ */
+static const char *
+element_relpath_in_subtree(const svn_branch_el_rev_id_t *subtree,
+                           int eid,
+                           apr_pool_t *scratch_pool)
+{
+  const char *subtree_path = branch_map_get_path_by_eid(subtree->branch,
+                                                        subtree->eid,
+                                                        scratch_pool);
+  const char *element_path = branch_map_get_path_by_eid(subtree->branch,
+                                                        eid, scratch_pool);
+  const char *relpath = NULL;
+
+  SVN_ERR_ASSERT_NO_RETURN(subtree_path);
+
+  if (element_path)
+    relpath = svn_relpath_skip_ancestor(subtree_path, element_path);
+
+  return relpath;
+}
+
+svn_error_t *
+svn_branch_subtree_differences(apr_hash_t **diff_p,
+                               svn_editor3_t *editor,
+                               const svn_branch_el_rev_id_t *left,
+                               const svn_branch_el_rev_id_t *right,
+                               apr_pool_t *result_pool,
+                               apr_pool_t *scratch_pool)
+{
+  apr_hash_t *diff = apr_hash_make(result_pool);
+  int first_eid, next_eid;
+  int e;
+
+  /*SVN_DBG(("branch_element_differences(b%d r%ld, b%d r%ld, e%d)",
+           left->branch->sibling->bid, left->rev,
+           right->branch->sibling->bid, right->rev, right->eid));*/
+  SVN_ERR_ASSERT(left->branch->sibling_defn->family->fid
+                 == right->branch->sibling_defn->family->fid);
+
+  first_eid = left->branch->sibling_defn->family->first_eid;
+  next_eid = MAX(left->branch->sibling_defn->family->next_eid,
+                 right->branch->sibling_defn->family->next_eid);
+
+  for (e = first_eid; e < next_eid; e++)
+    {
+      svn_branch_el_rev_content_t *content_left = NULL;
+      svn_branch_el_rev_content_t *content_right = NULL;
+
+      if (e < left->branch->sibling_defn->family->next_eid
+          && element_relpath_in_subtree(left, e, scratch_pool))
+        {
+          SVN_ERR(svn_branch_el_rev_get(&content_left, editor,
+                                        left->branch, e,
+                                        result_pool, scratch_pool));
+        }
+      if (e < right->branch->sibling_defn->family->next_eid
+          && element_relpath_in_subtree(right, e, scratch_pool))
+        {
+          SVN_ERR(svn_branch_el_rev_get(&content_right, editor,
+                                        right->branch, e,
+                                        result_pool, scratch_pool));
+        }
+
+      if (! svn_branch_el_rev_content_equal(e, content_left, content_right, scratch_pool))
+        {
+          int *eid_stored = apr_pmemdup(result_pool, &e, sizeof(e));
+          svn_branch_el_rev_content_t **contents
+            = apr_palloc(result_pool, 2 * sizeof(void *));
+
+          contents[0] = content_left;
+          contents[1] = content_right;
+          apr_hash_set(diff, eid_stored, sizeof(*eid_stored), contents);
+        }
+    }
+
+  *diff_p = diff;
+  return SVN_NO_ERROR;
+}
+
+
+/*
+ * ========================================================================
  * Driving the Delta Editor
  * ========================================================================
  */
@@ -1725,10 +3135,23 @@ typedef struct ev3_from_delta_baton_t
   /* Moves recorded so far: from_relpath -> (char *)to_relpath. */
   apr_hash_t *moves;
 
+  /* The branch on which the per-element API is working */
+  /* ### Assumes most operations operate on a single branch. Not true for
+         operations such as "branch", "branchify", and those that recurse
+         into nested branches. For the time being, this just gives a
+         notion of a "main" branch for the editing. */
+  svn_branch_revision_root_t *edited_rev_root;
+  svn_branch_instance_t *edited_branch;
+
   apr_pool_t *edit_pool;
 } ev3_from_delta_baton_t;
 
-/* Get all the (Ev1) paths that have changes. */
+/* Get all the (Ev1) paths that have changes. Return only paths at or below
+ * BASE_RELPATH, and return them relative to BASE_RELPATH.
+ *
+ * ### Instead, we should probably avoid adding paths outside BASE_RELPATH
+ *     to CHANGES in the first place, and not allow them here.
+ */
 static const apr_array_header_t *
 get_unsorted_paths(apr_hash_t *changes,
                    const char *base_relpath,
@@ -1743,9 +3166,13 @@ get_unsorted_paths(apr_hash_t *changes,
   for (hi = apr_hash_first(scratch_pool, changes); hi; hi = apr_hash_next(hi))
     {
       const char *this_path = apr_hash_this_key(hi);
+      const char *this_relpath = svn_relpath_skip_ancestor(base_relpath,
+                                                           this_path);
 
-      APR_ARRAY_PUSH(paths, const char *)
-        = svn_relpath_skip_ancestor(base_relpath, this_path);
+      if (this_relpath)
+        {
+          APR_ARRAY_PUSH(paths, const char *) = this_relpath;
+        }
     }
 
   return paths;
@@ -1994,9 +3421,11 @@ apply_change(void **dir_baton,
       if (change->kind == svn_node_dir)
         SVN_ERR(eb->deditor->absent_directory(ev1_relpath, parent_baton,
                                               scratch_pool));
-      else
+      else if (change->kind == svn_node_file)
         SVN_ERR(eb->deditor->absent_file(ev1_relpath, parent_baton,
                                          scratch_pool));
+      else
+        SVN_ERR_MALFUNCTION();
 
       /* No further action possible for this node.  */
       return SVN_NO_ERROR;
@@ -2040,10 +3469,12 @@ apply_change(void **dir_baton,
         SVN_ERR(eb->deditor->add_directory(ev1_relpath, parent_baton,
                                            copyfrom_url, copyfrom_rev,
                                            result_pool, dir_baton));
-      else
+      else if (change->kind == svn_node_file)
         SVN_ERR(eb->deditor->add_file(ev1_relpath, parent_baton,
                                       copyfrom_url, copyfrom_rev,
                                       result_pool, &file_baton));
+      else
+        SVN_ERR_MALFUNCTION();
     }
   else /* RESTRUCTURE_NONE */
     {
@@ -2057,10 +3488,12 @@ apply_change(void **dir_baton,
         SVN_ERR(eb->deditor->open_directory(ev1_relpath, parent_baton,
                                             change->changing_rev,
                                             result_pool, dir_baton));
-      else
+      else if (change->kind == svn_node_file)
         SVN_ERR(eb->deditor->open_file(ev1_relpath, parent_baton,
                                        change->changing_rev,
                                        result_pool, &file_baton));
+      else
+        SVN_ERR_MALFUNCTION();
     }
 
   /* Apply any properties in CHANGE to the node.  */
@@ -2125,7 +3558,7 @@ 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. */
-  /* ### Seems unnecessarily clumsy. Eliminate the previous open_root call instead? */
+  /* ### Seems clumsy. Is there not a simpler way? */
   if (! svn_hash_gets(eb->changes, eb->base_relpath))
     {
       SVN_ERR(insert_change(&change, eb->changes, eb->base_relpath,
@@ -2248,147 +3681,723 @@ editor3_cp(void *baton,
                             "Ev3-to-Ev1 doesn't support copy-from-this-rev");
 #endif
 
-  /* Precondition: a child with this name in parent_loc must not exist,
-     as far as we know. This is checked by insert_change(). */
+  /* Precondition: a child with this name in parent_loc must not exist,
+     as far as we know. This is checked by insert_change(). */
+
+  /* copy subtree (from_loc originally) to (parent_loc, name in shadow txn) */
+  SVN_ERR(insert_change(&change, eb->changes, new_txnpath, RESTRUCTURE_ADD));
+  change->copyfrom_path = from_peg_loc.relpath;
+  change->copyfrom_rev = from_peg_loc.rev;
+  /* We need the source's kind to know whether to call add_directory()
+     or add_file() later on.  */
+  SVN_ERR(eb->fetch_func(&change->kind, NULL, NULL, NULL,
+                         eb->fetch_baton,
+                         from_peg_loc.relpath, from_peg_loc.rev,
+                         scratch_pool, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+/* An #svn_editor3_t method. */
+static svn_error_t *
+editor3_mv(void *baton,
+           svn_editor3_peg_path_t from_loc,
+           svn_editor3_txn_path_t new_parent_loc,
+           const char *new_name,
+           apr_pool_t *scratch_pool)
+{
+  ev3_from_delta_baton_t *eb = baton;
+  change_node_t *change;
+
+  /* look up old path and new parent path in shadow txn */
+  const char *from_txnpath
+    = e3_pegged_path_in_txn(eb, from_loc, scratch_pool);
+  const char *new_parent_txnpath
+    = e3_general_path_in_txn(eb, new_parent_loc, scratch_pool);
+  const char *new_txnpath
+    = svn_relpath_join(new_parent_txnpath, new_name, scratch_pool);
+
+  /* Precondition: a child with this name in parent_loc must not exist,
+     as far as we know. This is checked by insert_change(). */
+
+  /* copy subtree (from_loc originally) to (parent_loc, name in shadow txn) */
+  SVN_ERR(insert_change(&change, eb->changes, new_txnpath, RESTRUCTURE_ADD));
+  change->copyfrom_path = from_loc.relpath;
+  change->copyfrom_rev = from_loc.rev; /* ### or "copyfrom_rev = -1"? */
+  /* We need the source's kind to know whether to call add_directory()
+     or add_file() later on. (If the move source is one for which we have
+     already recorded a change -- an earlier move, I suppose -- then the
+     'kind' has already been recorded there and we could potentially
+     re-use it here. But we have no need yet to optimise that case.) */
+  SVN_ERR(eb->fetch_func(&change->kind, NULL, NULL, NULL, eb->fetch_baton,
+                         from_loc.relpath, from_loc.rev,
+                         scratch_pool, scratch_pool));
+
+  /* duplicate any child changes into the copy destination */
+  SVN_ERR(duplicate_child_changes(eb->changes, from_txnpath, new_txnpath,
+                                  scratch_pool));
+
+  /* delete subtree (from_loc in shadow txn) */
+  SVN_ERR(delete_subtree(eb->changes, from_txnpath, from_loc.rev));
+
+  /* Record the move. If we're moving something again that we already moved
+     before, just overwrite the previous entry. */
+  record_move(eb->moves, from_loc.relpath, new_txnpath);
+
+  return SVN_NO_ERROR;
+}
+
+#ifdef SVN_EDITOR3_WITH_RESURRECTION
+/* An #svn_editor3_t method. */
+static svn_error_t *
+editor3_res(void *baton,
+            svn_editor3_peg_path_t from_loc,
+            svn_editor3_txn_path_t parent_loc,
+            const char *new_name,
+            apr_pool_t *scratch_pool)
+{
+  /* ### */
+
+  return SVN_NO_ERROR;
+}
+#endif
+
+/* An #svn_editor3_t method. */
+static svn_error_t *
+editor3_rm(void *baton,
+           svn_editor3_txn_path_t loc,
+           apr_pool_t *scratch_pool)
+{
+  ev3_from_delta_baton_t *eb = baton;
+
+  /* look up old path in shadow txn */
+  const char *txnpath
+    = e3_general_path_in_txn(eb, loc, scratch_pool);
+
+  /* Precondition: txnpath points to a pre-existing node or a child of
+     a copy. This is checked by insert_change(). */
+
+  /* delete subtree (from_loc in shadow txn) */
+  /* 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 */
+  SVN_ERR(delete_subtree(eb->changes, txnpath,
+                         loc.relpath[0] ? SVN_INVALID_REVNUM : loc.peg.rev));
+
+  return SVN_NO_ERROR;
+}
+
+/* An #svn_editor3_t method. */
+static svn_error_t *
+editor3_put(void *baton,
+            svn_editor3_txn_path_t loc,
+            const svn_editor3_node_content_t *new_content,
+            apr_pool_t *scratch_pool)
+{
+  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
+    = e3_general_path_in_txn(eb, loc, scratch_pool);
+
+  /* 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 ...
+
+     ### For a copied path, and/or a change inside a copied subtree, should
+     we be using the copy-from rev instead? See comment in apply_change().
+   */
+  change->changing_rev = loc.peg.rev;
+  change->props = (new_content->props
+                   ? svn_prop_hash_dup(new_content->props, changes_pool)
+                   : NULL);
+
+  if (new_content->kind == svn_node_file)
+    {
+      /* Copy the provided text into the change record. */
+      change->contents_text = svn_stringbuf_dup(new_content->text,
+                                                changes_pool);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+/*
+ * ========================================================================
+ * Editor for Commit (independent per-node changes; node-id addressing)
+ * ========================================================================
+ */
+
+/* Get the content of BRANCH:EID, as fully resolved content (not as a
+ * reference). BRANCH:EID must not be a subbranch root.
+ *
+ * Set *NODE_P to the content, or to null if there is no such element.
+ * Use the editor's "fetch" callback if the content is not already in memory.
+ */
+static svn_error_t *
+svn_branch_el_rev_get(svn_branch_el_rev_content_t **node_p,
+                      svn_editor3_t *editor,
+                      svn_branch_instance_t *branch,
+                      int eid,
+                      apr_pool_t *result_pool,
+                      apr_pool_t *scratch_pool)
+{
+  ev3_from_delta_baton_t *eb = svn_editor3__get_baton(editor);
+  svn_branch_el_rev_content_t *node = branch_map_get(branch, eid);
+
+  /* Node content is null iff node is a subbranch root, but we shouldn't
+     be querying a subbranch root. */
+  SVN_ERR_ASSERT(!node || node->content);
+
+  /* If content is by reference, fetch full content. */
+  if (node && (node->content->ref.relpath))
+    {
+      svn_editor3_node_content_t *content
+        = apr_pcalloc(result_pool, sizeof(*content));
+
+      SVN_ERR(eb->fetch_func(&content->kind,
+                             &content->props,
+                             &content->text, NULL,
+                             eb->fetch_baton,
+                             node->content->ref.relpath, node->content->ref.rev,
+                             result_pool, scratch_pool));
+      node->content = content;
+    }
+
+  *node_p = node;
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_editor3_find_el_rev_by_path_rev(svn_branch_el_rev_id_t **el_rev_p,
+                                    svn_editor3_t *editor,
+                                    const char *rrpath,
+                                    svn_revnum_t revnum,
+                                    apr_pool_t *result_pool,
+                                    apr_pool_t *scratch_pool)
+{
+  ev3_from_delta_baton_t *eb = svn_editor3__get_baton(editor);
+  const svn_branch_repos_t *repos = eb->edited_rev_root->repos;
+  svn_branch_el_rev_id_t *el_rev = apr_palloc(result_pool, sizeof(*el_rev));
+  const svn_branch_revision_root_t *rev_root;
+
+  SVN_ERR_ASSERT(revnum >= 0 && revnum < repos->rev_roots->nelts);
+
+  rev_root = APR_ARRAY_IDX(repos->rev_roots, revnum, void *);
+  el_rev->rev = revnum;
+  svn_branch_find_nested_branch_element_by_rrpath(&el_rev->branch, &el_rev->eid,
+                                                  rev_root->root_branch, rrpath,
+                                                  scratch_pool);
+
+  /* Any path must at least be within the repository root branch */
+  SVN_ERR_ASSERT_NO_RETURN(el_rev->branch);
+  *el_rev_p = el_rev;
+  return SVN_NO_ERROR;
+}
+
+void
+svn_editor3_find_branch_element_by_rrpath(svn_branch_instance_t **branch_p,
+                                          int *eid_p,
+                                          svn_editor3_t *editor,
+                                          const char *rrpath,
+                                          apr_pool_t *scratch_pool)
+{
+  ev3_from_delta_baton_t *eb = svn_editor3__get_baton(editor);
+
+  svn_branch_find_nested_branch_element_by_rrpath(branch_p, eid_p,
+                                                  eb->edited_rev_root->root_branch,
+                                                  rrpath, scratch_pool);
+}
+
+svn_branch_family_t *
+svn_branch_get_family(svn_editor3_t *editor)
+{
+  ev3_from_delta_baton_t *eb = svn_editor3__get_baton(editor);
+  svn_branch_family_t *family = eb->edited_branch->sibling_defn->family;
+  return family;
+}
+
+svn_error_t *
+svn_branch_branch(svn_editor3_t *editor,
+                  svn_branch_instance_t *from_branch,
+                  int from_eid,
+                  svn_branch_instance_t *to_outer_branch,
+                  svn_editor3_nbid_t to_outer_parent_eid,
+                  const char *new_name,
+                  apr_pool_t *scratch_pool)
+{
+  if (! branch_map_get_path_by_eid(from_branch, from_eid, scratch_pool))
+    {
+      return svn_error_createf(SVN_ERR_BRANCHING, NULL,
+                               _("cannot branch from b%d e%d: "
+                                 "does not exist"),
+                               from_branch->sibling_defn->bid, from_eid);
+    }
+
+  SVN_ERR(branch_branch_subtree_r(NULL,
+                                  from_branch, from_eid,
+                                  to_outer_branch, to_outer_parent_eid,
+                                  new_name,
+                                  scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_branch_branchify(svn_editor3_t *editor,
+                     svn_editor3_nbid_t outer_eid,
+                     apr_pool_t *scratch_pool)
+{
+  ev3_from_delta_baton_t *eb = svn_editor3__get_baton(editor);
+  svn_branch_instance_t *outer_branch = eb->edited_branch;
+
+  /* ### TODO: First check the element is not already a branch root
+         and its subtree does not contain any branch roots. */
+
+  svn_branch_family_t *
+    new_family = family_add_new_subfamily(outer_branch->sibling_defn->family);
+  int new_root_eid = family_add_new_element(new_family);
+  svn_branch_sibling_t *
+    new_branch_def = family_add_new_branch_sibling(new_family, new_root_eid);
+  svn_branch_instance_t *
+    new_branch = branch_add_new_branch_instance(outer_branch, outer_eid,
+                                                new_branch_def, scratch_pool);
+
+  SVN_DBG(("branchify(b%d e%d at ^/%s): new f%d b%d e%d",
+           outer_branch->sibling_defn->bid, outer_eid,
+           svn_branch_get_root_rrpath(new_branch),
+           new_family->fid, new_branch_def->bid, new_branch_def->root_eid));
+
+  /* Initialize the root element */
+  {
+    svn_branch_el_rev_content_t *old_content = branch_map_get(outer_branch,
+                                                              outer_eid);
+
+    branch_map_update(new_branch, new_branch_def->root_eid,
+                      -1, "", old_content->content);
+  }
+
+  /* assign new EIDs and update the path mappings in this branch */
+  SVN_ERR(branch_map_copy_children(outer_branch, outer_eid,
+                                   new_branch, new_branch_def->root_eid,
+                                   scratch_pool));
+
+  /* remove old element mappings in outer branch */
+  SVN_ERR(branch_map_delete_children(outer_branch, outer_eid, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+/* An #svn_editor3_t method. */
+static svn_error_t *
+editor3_add(void *baton,
+            svn_editor3_nbid_t *eid_p,
+            svn_node_kind_t new_kind,
+            svn_editor3_nbid_t new_parent_eid,
+            const char *new_name,
+            const svn_editor3_node_content_t *new_content,
+            apr_pool_t *scratch_pool)
+{
+  ev3_from_delta_baton_t *eb = baton;
+  svn_branch_instance_t *branch = eb->edited_branch;
+  int eid;
+
+  eid = family_add_new_element(branch->sibling_defn->family);
+
+  SVN_DBG(("add(e%d): parent e%d, name '%s', kind %s",
+           /*branch->sibling->bid,*/ eid, new_parent_eid,
+           new_name, svn_node_kind_to_word(new_content->kind)));
+
+  branch_map_update(branch, eid, new_parent_eid, new_name, new_content);
+
+  *eid_p = eid;
+  return SVN_NO_ERROR;
+}
+
+/* An #svn_editor3_t method. */
+static svn_error_t *
+editor3_instantiate(void *baton,
+                    svn_editor3_nbid_t eid,
+                    svn_node_kind_t new_kind,
+                    svn_editor3_nbid_t new_parent_eid,
+                    const char *new_name,
+                    const svn_editor3_node_content_t *new_content,
+                    apr_pool_t *scratch_pool)
+{
+  ev3_from_delta_baton_t *eb = baton;
+  svn_branch_instance_t *branch = eb->edited_branch;
+
+  SVN_DBG(("add(e%d): parent e%d, name '%s', kind %s",
+           /*branch->sibling->bid,*/ eid, new_parent_eid,
+           new_name, svn_node_kind_to_word(new_content->kind)));
+
+  branch_map_update(branch, eid, new_parent_eid, new_name, new_content);
+  return SVN_NO_ERROR;
+}
 
-  /* copy subtree (from_loc originally) to (parent_loc, name in shadow txn) */
-  SVN_ERR(insert_change(&change, eb->changes, new_txnpath, RESTRUCTURE_ADD));
-  change->copyfrom_path = from_peg_loc.relpath;
-  change->copyfrom_rev = from_peg_loc.rev;
-  /* We need the source's kind to know whether to call add_directory()
-     or add_file() later on.  */
-  SVN_ERR(eb->fetch_func(&change->kind, NULL, NULL, NULL,
-                         eb->fetch_baton,
-                         from_peg_loc.relpath, from_peg_loc.rev,
-                         scratch_pool, scratch_pool));
+/* An #svn_editor3_t method. */
+static svn_error_t *
+editor3_copy_one(void *baton,
+                 svn_editor3_nbid_t eid,
+                 svn_revnum_t src_revision,
+                 svn_editor3_nbid_t src_eid,
+                 svn_editor3_nbid_t new_parent_eid,
+                 const char *new_name,
+                 const svn_editor3_node_content_t *new_content,
+                 apr_pool_t *scratch_pool)
+{
+  /* New content shall be the same as the source if NEW_CONTENT is null. */
+  /* ### if (! new_content)
+    {
+      new_content = branch_map_get(branch, eid)->content;
+    }
+   */
 
   return SVN_NO_ERROR;
 }
 
 /* An #svn_editor3_t method. */
 static svn_error_t *
-editor3_mv(void *baton,
-           svn_editor3_peg_path_t from_loc,
-           svn_editor3_txn_path_t new_parent_loc,
-           const char *new_name,
-           apr_pool_t *scratch_pool)
+editor3_copy_tree(void *baton,
+                  svn_revnum_t src_revision,
+                  svn_editor3_nbid_t src_eid,
+                  svn_editor3_nbid_t new_parent_eid,
+                  const char *new_name,
+                  apr_pool_t *scratch_pool)
 {
   ev3_from_delta_baton_t *eb = baton;
-  change_node_t *change;
+  svn_branch_instance_t *branch = eb->edited_branch;
 
-  /* look up old path and new parent path in shadow txn */
-  const char *from_txnpath
-    = e3_pegged_path_in_txn(eb, from_loc, scratch_pool);
-  const char *new_parent_txnpath
-    = e3_general_path_in_txn(eb, new_parent_loc, scratch_pool);
-  const char *new_txnpath
-    = svn_relpath_join(new_parent_txnpath, new_name, scratch_pool);
+  SVN_DBG(("copy_tree(e%d -> e%d/%s)",
+           /*branch->sibling_defn->bid,*/
+           src_eid, new_parent_eid, new_name));
+
+  SVN_ERR(branch_copy_subtree_r(branch,
+                                src_revision, src_eid,
+                                new_parent_eid, new_name,
+                                scratch_pool));
 
-  /* Precondition: a child with this name in parent_loc must not exist,
-     as far as we know. This is checked by insert_change(). */
+  return SVN_NO_ERROR;
+}
 
-  /* copy subtree (from_loc originally) to (parent_loc, name in shadow txn) */
-  SVN_ERR(insert_change(&change, eb->changes, new_txnpath, RESTRUCTURE_ADD));
-  change->copyfrom_path = from_loc.relpath;
-  change->copyfrom_rev = from_loc.rev; /* ### or "copyfrom_rev = -1"? */
-  /* We need the source's kind to know whether to call add_directory()
-     or add_file() later on. (If the move source is one for which we have
-     already recorded a change -- an earlier move, I suppose -- then the
-     'kind' has already been recorded there and we could potentially
-     re-use it here. But we have no need yet to optimise that case.) */
-  SVN_ERR(eb->fetch_func(&change->kind, NULL, NULL, NULL, eb->fetch_baton,
-                         from_loc.relpath, from_loc.rev,
-                         scratch_pool, scratch_pool));
+/* An #svn_editor3_t method. */
+static svn_error_t *
+editor3_delete_one(void *baton,
+                   svn_revnum_t since_rev,
+                   svn_editor3_nbid_t eid,
+                   apr_pool_t *scratch_pool)
+{
+  ev3_from_delta_baton_t *eb = baton;
+  svn_branch_instance_t *branch = eb->edited_branch;
 
-  /* duplicate any child changes into the copy destination */
-  SVN_ERR(duplicate_child_changes(eb->changes, from_txnpath, new_txnpath,
-                                  scratch_pool));
+  SVN_DBG(("delete_one(e%d)",
+           /*branch->sibling_defn->bid,*/ eid));
 
-  /* delete subtree (from_loc in shadow txn) */
-  SVN_ERR(delete_subtree(eb->changes, from_txnpath, from_loc.rev));
+  branch_map_delete(branch, eid /* ### , since_rev? */);
 
-  /* Record the move. If we're moving something again that we already moved
-     before, just overwrite the previous entry. */
-  record_move(eb->moves, from_loc.relpath, new_txnpath);
+  /* ### TODO: Delete nested branches. */
 
   return SVN_NO_ERROR;
 }
 
-#ifdef SVN_EDITOR3_WITH_RESURRECTION
 /* An #svn_editor3_t method. */
 static svn_error_t *
-editor3_res(void *baton,
-            svn_editor3_peg_path_t from_loc,
-            svn_editor3_txn_path_t parent_loc,
-            const char *new_name,
-            apr_pool_t *scratch_pool)
+editor3_delete_tree(void *baton,
+                    svn_revnum_t since_rev,
+                    svn_editor3_nbid_t eid,
+                    apr_pool_t *scratch_pool)
 {
-  /* ### */
+  ev3_from_delta_baton_t *eb = baton;
+  svn_branch_instance_t *branch = eb->edited_branch;
+
+  SVN_DBG(("delete_tree(e%d)",
+           /*branch->sibling_defn->bid,*/ eid));
+
+  SVN_ERR(branch_delete_subtree_r(branch, eid, scratch_pool));
 
   return SVN_NO_ERROR;
 }
-#endif
 
 /* An #svn_editor3_t method. */
 static svn_error_t *
-editor3_rm(void *baton,
-           svn_editor3_txn_path_t loc,
-           apr_pool_t *scratch_pool)
+editor3_alter(void *baton,
+              svn_revnum_t since_rev,
+              svn_editor3_nbid_t eid,
+              svn_editor3_nbid_t new_parent_eid,
+              const char *new_name,
+              const svn_editor3_node_content_t *new_content,
+              apr_pool_t *scratch_pool)
 {
   ev3_from_delta_baton_t *eb = baton;
+  svn_branch_instance_t *branch = eb->edited_branch;
 
-  /* look up old path in shadow txn */
-  const char *txnpath
-    = e3_general_path_in_txn(eb, loc, scratch_pool);
+  SVN_DBG(("alter(e%d): parent e%d, name '%s', kind %s",
+           /*branch->sibling_defn->bid,*/ eid,
+           new_parent_eid,
+           new_name ? new_name : "(same)",
+           new_content ? svn_node_kind_to_word(new_content->kind) : "(same)"));
 
-  /* Precondition: txnpath points to a pre-existing node or a child of
-     a copy. This is checked by insert_change(). */
+  /* New content shall be the same as the before if NEW_CONTENT is null. */
+  if (! new_content)
+    {
+      new_content = branch_map_get(branch, eid)->content;
+    }
 
-  /* delete subtree (from_loc in shadow txn) */
-  /* 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 */
-  SVN_ERR(delete_subtree(eb->changes, txnpath,
-                         loc.relpath[0] ? SVN_INVALID_REVNUM : loc.peg.rev));
+  branch_map_update(branch, eid, new_parent_eid, new_name, new_content);
 
   return SVN_NO_ERROR;
 }
 
-/* An #svn_editor3_t method. */
+/*  */
+typedef struct initial_final_t
+{
+  svn_branch_instance_t *branch;
+  int eid;
+  svn_revnum_t rev;
+  svn_branch_el_rev_content_t *node;
+} initial_final_t;
+
+/* Update *PATHS, a hash of (rrpath -> initial_final_t[2]), creating or
+ * filling in entries for all elements in BRANCH, at index [I] in the
+ * initial_final_t array.
+ */
+static void
+convert_branch_to_paths(apr_hash_t *paths,
+                        svn_branch_instance_t *branch,
+                        int i,
+                        apr_pool_t *result_pool,
+                        apr_pool_t *scratch_pool)
+{
+  apr_hash_index_t *hi;
+
+  for (hi = apr_hash_first(scratch_pool, branch->e_map);
+       hi; hi = apr_hash_next(hi))
+    {
+      int eid = *(const int *)apr_hash_this_key(hi);
+      svn_branch_el_rev_content_t *node = apr_hash_this_val(hi);
+      const char *relpath = branch_map_get_path_by_eid(branch, eid, result_pool);
+      const char *rrpath = svn_relpath_join(svn_branch_get_root_rrpath(branch),
+                                            relpath, result_pool);
+      initial_final_t *ba = svn_hash_gets(paths, rrpath);
+
+      if (! ba)
+        {
+          ba = apr_pcalloc(result_pool, 2 * sizeof(*ba));
+          ba[0].eid = -1;
+          ba[1].eid = -1;
+          ba[0].rev = SVN_INVALID_REVNUM;
+          ba[1].rev = SVN_INVALID_REVNUM;
+          svn_hash_sets(paths, rrpath, ba);
+        }
+
+      /* Fill in the details. If it's already been filled in, then let a
+         branch-root element override a sub-branch element of an outer
+         branch, because the branch-root element is the one that should
+         be specifying the element's content.
+       */
+      if (! ba[i].branch
+          || eid == branch->sibling_defn->root_eid)
+        {
+          ba[i].branch = branch;
+          ba[i].eid = eid;
+          ba[i].rev = SVN_INVALID_REVNUM; /* ### */
+          ba[i].node = node;
+          SVN_DBG(("branch-to-path[%d]: b%d e%d -> %s",
+                   i, branch->sibling_defn->bid, eid, rrpath));
+        }
+      else
+        {
+          SVN_DBG(("branch-to-path[%d]: b%d e%d -> <already present; not overwriting> (%s)",
+                   i, branch->sibling_defn->bid, eid, rrpath));
+        }
+    }
+}
+
+/* Update *PATHS_UNION, a hash of (rrpath -> initial_final_t[2]), creating or
+ * filling in entries for all elements in all branches at and under BRANCH,
+ * recursively, at index [I] in the initial_final_t array.
+ */
+static void
+convert_branch_to_paths_r(apr_hash_t *paths_union,
+                          svn_branch_instance_t *branch,
+                          int idx,
+                          apr_pool_t *result_pool,
+                          apr_pool_t *scratch_pool)
+{
+  apr_array_header_t *sub_branches;
+  int i;
+
+  SVN_DBG(("[%d] branch={b%de%d at '%s'}", idx,
+           branch->sibling_defn->bid, branch->sibling_defn->root_eid, branch->branch_root_rrpath));
+  convert_branch_to_paths(paths_union, branch, idx,
+                          result_pool, scratch_pool);
+
+  /* Rercurse into sub-branches */
+  sub_branches = branch_get_all_sub_branches(branch,
+                                             scratch_pool, scratch_pool);
+  for (i = 0; i < sub_branches->nelts; i++)
+    {
+      svn_branch_instance_t *b = APR_ARRAY_IDX(sub_branches, i, void *);
+
+      convert_branch_to_paths_r(paths_union, b, idx, result_pool, scratch_pool);
+    }
+}
+
+/*
+ * Drive svn_delta_editor_t (actions: add/copy/delete/modify) from
+ * a before-and-after element mapping.
+ */
 static svn_error_t *
-editor3_put(void *baton,
-            svn_editor3_txn_path_t loc,
-            const svn_editor3_node_content_t *new_content,
-            apr_pool_t *scratch_pool)
+drive_changes_branch(ev3_from_delta_baton_t *eb,
+                     apr_pool_t *scratch_pool)
 {
-  ev3_from_delta_baton_t *eb = baton;
-  change_node_t *change;
-  apr_pool_t *changes_pool = apr_hash_pool_get(eb->changes);
+  svn_branch_revision_root_t *base_rev_root;
+  apr_hash_t *paths_union;
+  apr_array_header_t *sorted_paths;
+  const apr_array_header_t *paths;
+  int i;
 
-  /* look up path in shadow txn */
-  const char *txnpath
-    = e3_general_path_in_txn(eb, loc, scratch_pool);
+  /* Find the initial branching state.
+     ### For now, assume single-rev state based on youngest known rev. */
+  {
+    int base_rev = eb->edited_rev_root->repos->rev_roots->nelts - 1;
+    base_rev_root = APR_ARRAY_IDX(eb->edited_rev_root->repos->rev_roots,
+                                  base_rev, void *);
+  }
 
-  /* 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 ...
+  /* Convert the element mappings to an svn_delta_editor_t traversal.
 
-     ### For a copied path, and/or a change inside a copied subtree, should
-     we be using the copy-from rev instead? See comment in apply_change().
+        1. find union of paths in initial and final states, across all branches.
+        2. traverse paths in depth-first order.
+        3. modify/delete/add/replace as needed at each path.
    */
-  change->changing_rev = loc.peg.rev;
-  change->props = (new_content->props
-                   ? svn_prop_hash_dup(new_content->props, changes_pool)
-                   : NULL);
+  paths_union = apr_hash_make(scratch_pool);
+  convert_branch_to_paths_r(paths_union, base_rev_root->root_branch, 0,
+                            scratch_pool, scratch_pool);
+  convert_branch_to_paths_r(paths_union, eb->edited_rev_root->root_branch, 1,
+                            scratch_pool, scratch_pool);
+
+  sorted_paths = svn_sort__hash(paths_union, svn_sort_compare_items_as_paths,
+                                scratch_pool);
+  for (i = 0; i < sorted_paths->nelts; i++)
+    {
+      svn_sort__item_t *item = &APR_ARRAY_IDX(sorted_paths, i, svn_sort__item_t);
+      const char *rrpath = item->key;
+      initial_final_t *ba = item->value, *initial = &ba[0], *final = &ba[1];
+
+      SVN_DBG(("%-4s -> %-4s  %s",
+               (initial->node && initial->node->content)
+                 ? svn_node_kind_to_word(initial->node->content->kind) : "---",
+               (final->node && final->node->content)
+                 ? svn_node_kind_to_word(final->node->content->kind) : "---",
+               rrpath));
+
+      /* If there's an initial node that isn't also going to be the final
+         node, it's being deleted or replaced: delete it. */
+      if (initial->node
+          && (! final->node || final->eid != initial->eid))
+        {
+          /* Issue an Ev1 delete unless this path is inside a path at which
+             we've already issued a delete. */
+          if (check_existence(eb->changes, rrpath) != svn_tristate_false)
+            {
+              SVN_DBG(("ev1:del(%s)", rrpath));
+              SVN_ERR(delete_subtree(eb->changes, rrpath, initial->rev));
+            }
+          else
+            SVN_DBG(("ev1:del(%s): parent is already deleted", rrpath));
+        }
 
-  if (new_content->kind == svn_node_file)
+      /* If there's a final node, it's being added or modified.
+         ### Or it's unchanged -- we should do nothing in that case. */
+      if (final->node)
+        {
+          svn_editor3_node_content_t *final_content = final->node->content;
+          change_node_t *change;
+
+          /* If the final node was also the initial node, it's being
+             modified, otherwise it's being added (perhaps a replacement). */
+          if (initial->node
+              /* && same branch family */
+              && (final->eid == initial->eid))
+            {
+              /* TODO: If no changes to make, then skip this path */
+              /*
+              if (svn_editor3_node_content_equal(initial_content, final_content,
+                                                 scratch_pool))
+                {
+                  SVN_DBG(("ev1:no-op(%s)", rrpath));
+                  continue;
+                }
+               */
+
+              SVN_DBG(("ev1:mod(%s)", rrpath));
+              SVN_ERR(insert_change(&change, eb->changes, rrpath,
+                                    RESTRUCTURE_NONE));
+              change->changing_rev = initial->rev;
+            }
+          else
+            {
+              SVN_DBG(("ev1:add(%s)", rrpath));
+              SVN_ERR(insert_change(&change, eb->changes, rrpath,
+                                    RESTRUCTURE_ADD));
+              /* ### TODO: copy from */
+            }
+
+          /* Ensure we have the full content of the final node. If we have
+             only a reference to the content, fetch it in full. */
+          SVN_ERR_ASSERT(final_content);
+          if (final_content->ref.relpath)
+            {
+              /* Get content by reference. */
+              SVN_ERR(eb->fetch_func(&final_content->kind,
+                                     &final_content->props,
+                                     &final_content->text, NULL,
+                                     eb->fetch_baton,
+                                     final_content->ref.relpath, final_content->ref.rev,
+                                     scratch_pool, scratch_pool));
+              SVN_ERR_ASSERT(final_content->kind == svn_node_dir
+                             || final_content->kind == svn_node_file);
+            }
+
+          /* Copy the required content into the change record. */
+          SVN_ERR_ASSERT(final_content->kind == svn_node_dir
+                         || final_content->kind == svn_node_file);
+          change->kind = final_content->kind;
+          change->props = final_content->props;
+          if (final_content->kind == svn_node_file)
+            {
+              change->contents_text = final_content->text;
+            }
+        }
+    }
+
+  /* If the driver has not explicitly opened the root directory via the
+     start_edit (aka open_root) callback, do so now. */
+  if (eb->ev1_root_dir_baton == NULL)
+    SVN_ERR(open_root_ev3(eb, SVN_INVALID_REVNUM));
+
+  /* Make the path driver visit the root dir of the edit. Otherwise, it
+     will attempt an open_root() instead, which we already did. */
+  if (! svn_hash_gets(eb->changes, eb->base_relpath))
     {
-      /* Copy the provided text into the change record. */
-      change->contents_text = svn_stringbuf_dup(new_content->text,
-                                                changes_pool);
+      change_node_t *change;
+
+      SVN_ERR(insert_change(&change, eb->changes, eb->base_relpath,
+                            RESTRUCTURE_NONE));
+      change->kind = svn_node_dir;
     }
 
+  /* Apply the appropriate Ev1 change to each Ev1-relative path. */
+  paths = get_unsorted_paths(eb->changes, eb->base_relpath, scratch_pool);
+  SVN_ERR(svn_delta_path_driver2(eb->deditor, eb->dedit_baton,
+                                 paths, TRUE /*sort*/,
+                                 apply_change, (void *)eb,
+                                 scratch_pool));
+
   return SVN_NO_ERROR;
 }
 
@@ -2401,7 +4410,10 @@ editor3_complete(void *baton,
   svn_error_t *err;
 
   /* Drive the tree we've created. */
-  err = drive_changes(eb, scratch_pool);
+  if (eb->edited_branch)
+    err = drive_changes_branch(eb, scratch_pool);
+  else
+    err = drive_changes(eb, scratch_pool);
   if (!err)
      {
        err = svn_error_compose_create(err, eb->deditor->close_edit(
@@ -2444,6 +4456,94 @@ editor3_abort(void *baton,
 }
 
 svn_error_t *
+svn_delta__ev3_from_delta_for_commit2(
+                        svn_editor3_t **editor_p,
+                        svn_editor3__shim_connector_t **shim_connector,
+                        const svn_delta_editor_t *deditor,
+                        void *dedit_baton,
+                        svn_branch_revision_root_t *branching_txn,
+                        const char *repos_root_url,
+                        const char *base_relpath,
+                        svn_editor3__shim_fetch_func_t fetch_func,
+                        void *fetch_baton,
+                        svn_cancel_func_t cancel_func,
+                        void *cancel_baton,
+                        apr_pool_t *result_pool,
+                        apr_pool_t *scratch_pool)
+{
+  static const svn_editor3_cb_funcs_t editor_funcs = {
+    NULL,
+    NULL,
+    NULL,
+#ifdef SVN_EDITOR3_WITH_RESURRECTION
+    NULL,
+#endif
+    NULL,
+    NULL,
+    editor3_add,
+    editor3_instantiate,
+    editor3_copy_one,
+    editor3_copy_tree,
+    editor3_delete_one,
+    editor3_alter,
+    editor3_complete,
+    editor3_abort
+  };
+  ev3_from_delta_baton_t *eb = apr_pcalloc(result_pool, sizeof(*eb));
+
+  eb->deditor = deditor;
+  eb->dedit_baton = dedit_baton;
+
+  eb->repos_root_url = apr_pstrdup(result_pool, repos_root_url);
+  eb->base_relpath = apr_pstrdup(result_pool, base_relpath);
+
+  eb->changes = apr_hash_make(result_pool);
+  eb->moves = apr_hash_make(result_pool);
+
+  eb->fetch_func = fetch_func;
+  eb->fetch_baton = fetch_baton;
+
+  eb->edit_pool = result_pool;
+
+  SVN_ERR(svn_editor3_create(editor_p, &editor_funcs, eb,
+                             cancel_func, cancel_baton,
+                             result_pool, scratch_pool));
+
+  /* Find what branch we are editing, based on BASE_RELPATH, and capture
+     its initial state.

[... 46 lines stripped ...]


Mime
View raw message