You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by br...@apache.org on 2015/01/16 15:01:37 UTC

svn commit: r1652409 [12/18] - in /subversion/branches/svn-auth-x509: ./ notes/ subversion/bindings/swig/ subversion/bindings/swig/include/ subversion/bindings/swig/perl/native/ subversion/bindings/swig/perl/native/t/ subversion/bindings/swig/python/li...

Modified: subversion/branches/svn-auth-x509/subversion/libsvn_fs_x/tree.c
URL: http://svn.apache.org/viewvc/subversion/branches/svn-auth-x509/subversion/libsvn_fs_x/tree.c?rev=1652409&r1=1652408&r2=1652409&view=diff
==============================================================================
--- subversion/branches/svn-auth-x509/subversion/libsvn_fs_x/tree.c (original)
+++ subversion/branches/svn-auth-x509/subversion/libsvn_fs_x/tree.c Fri Jan 16 14:01:35 2015
@@ -56,7 +56,7 @@
 #include "lock.h"
 #include "tree.h"
 #include "fs_x.h"
-#include "id.h"
+#include "fs_id.h"
 #include "temp_serializer.h"
 #include "cached_data.h"
 #include "transaction.h"
@@ -79,7 +79,7 @@
    them concurrently on disk!  (Why is the DAG node cache safer than
    the root DAG node?  When cloning transaction DAG nodes in and out
    of the cache, all of the possibly-mutable data from the
-   node_revision_t inside the dag_node_t is dropped.)  Additionally,
+   svn_fs_x__noderev_t inside the dag_node_t is dropped.)  Additionally,
    revisions are immutable enough that their DAG node cache can be
    kept in the FS object and shared among multiple revision root
    objects.
@@ -97,28 +97,32 @@ typedef struct fs_txn_root_data_t
 } fs_txn_root_data_t;
 
 /* Declared here to resolve the circular dependencies. */
-static svn_error_t * get_dag(dag_node_t **dag_node_p,
-                             svn_fs_root_t *root,
-                             const char *path,
-                             svn_boolean_t needs_lock_cache,
-                             apr_pool_t *pool);
-
-static svn_fs_root_t *make_revision_root(svn_fs_t *fs, svn_revnum_t rev,
-                                         dag_node_t *root_dir,
-                                         apr_pool_t *pool);
-
-static svn_error_t *make_txn_root(svn_fs_root_t **root_p,
-                                  svn_fs_t *fs,
-                                  svn_fs_x__txn_id_t txn_id,
-                                  svn_revnum_t base_rev,
-                                  apr_uint32_t flags,
-                                  apr_pool_t *pool);
-
-static svn_error_t *x_closest_copy(svn_fs_root_t **root_p,
-                                   const char **path_p,
-                                   svn_fs_root_t *root,
-                                   const char *path,
-                                   apr_pool_t *pool);
+static svn_error_t *
+get_dag(dag_node_t **dag_node_p,
+        svn_fs_root_t *root,
+        const char *path,
+        apr_pool_t *pool);
+
+static svn_fs_root_t *
+make_revision_root(svn_fs_t *fs,
+                   svn_revnum_t rev,
+                   dag_node_t *root_dir,
+                   apr_pool_t *pool);
+
+static svn_error_t *
+make_txn_root(svn_fs_root_t **root_p,
+              svn_fs_t *fs,
+              svn_fs_x__txn_id_t txn_id,
+              svn_revnum_t base_rev,
+              apr_uint32_t flags,
+              apr_pool_t *pool);
+
+static svn_error_t *
+x_closest_copy(svn_fs_root_t **root_p,
+               const char **path_p,
+               svn_fs_root_t *root,
+               const char *path,
+               apr_pool_t *pool);
 
 
 /*** Node Caching ***/
@@ -159,36 +163,12 @@ typedef struct cache_entry_t
  */
 enum { BUCKET_COUNT = 256 };
 
-/* Each pool that has received a DAG node, will hold at least on lock on
-   our cache to ensure that the node remains valid despite being allocated
-   in the cache's pool.  This is the structure to represent the lock.
- */
-typedef struct cache_lock_t
-{
-  /* pool holding the lock */
-  apr_pool_t *pool;
-
-  /* cache being locked */
-  fs_x_dag_cache_t *cache;
-
-  /* next lock. NULL at EOL */
-  struct cache_lock_t *next;
-
-  /* previous lock. NULL at list head. Only then this==cache->first_lock */
-  struct cache_lock_t *prev;
-} cache_lock_t;
-
 /* The actual cache structure.  All nodes will be allocated in POOL.
    When the number of INSERTIONS (i.e. objects created form that pool)
    exceeds a certain threshold, the pool will be cleared and the cache
    with it.
-
-   To ensure that nodes returned from this structure remain valid, the
-   cache will get locked for the lifetime of the _receiving_ pools (i.e.
-   those in which we would allocate the node if there was no cache.).
-   The cache will only be cleared FIRST_LOCK is 0.
  */
-struct fs_x_dag_cache_t
+struct svn_fs_x__dag_cache_t
 {
   /* fixed number of (possibly empty) cache entries */
   cache_entry_t buckets[BUCKET_COUNT];
@@ -203,109 +183,29 @@ struct fs_x_dag_cache_t
      Thus, remember the last hit location for optimistic lookup. */
   apr_size_t last_hit;
 
-  /* List of receiving pools that are still alive. */
-  cache_lock_t *first_lock;
+  /* Position of the last bucket hit that actually had a DAG node in it.
+     LAST_HIT may refer to a bucket that matches path@rev but has not
+     its NODE element set, yet.
+     This value is a mere hint for optimistic lookup and any value is
+     valid (as long as it is < BUCKET_COUNT). */
+  apr_size_t last_non_empty;
 };
 
-/* Cleanup function to be called when a receiving pool gets cleared.
-   Unlocks the cache once.
- */
-static apr_status_t
-unlock_cache(void *baton_void)
-{
-  cache_lock_t *lock = baton_void;
-
-  /* remove lock from chain. Update the head */
-  if (lock->next)
-    lock->next->prev = lock->prev;
-  if (lock->prev)
-    lock->prev->next = lock->next;
-  else
-    lock->cache->first_lock = lock->next;
-
-  return APR_SUCCESS;
-}
-
-/* Cleanup function to be called when the cache itself gets destroyed.
-   In that case, we must unregister all unlock requests.
- */
-static apr_status_t
-unregister_locks(void *baton_void)
-{
-  fs_x_dag_cache_t *cache = baton_void;
-  cache_lock_t *lock;
-
-  for (lock = cache->first_lock; lock; lock = lock->next)
-    apr_pool_cleanup_kill(lock->pool,
-                          lock,
-                          unlock_cache);
-
-  return APR_SUCCESS;
-}
-
-fs_x_dag_cache_t*
+svn_fs_x__dag_cache_t*
 svn_fs_x__create_dag_cache(apr_pool_t *pool)
 {
-  fs_x_dag_cache_t *result = apr_pcalloc(pool, sizeof(*result));
+  svn_fs_x__dag_cache_t *result = apr_pcalloc(pool, sizeof(*result));
   result->pool = svn_pool_create(pool);
 
-  apr_pool_cleanup_register(pool,
-                            result,
-                            unregister_locks,
-                            apr_pool_cleanup_null);
-
   return result;
 }
 
-/* Prevent the entries in CACHE from being destroyed, for as long as the
-   POOL lives.
- */
-static void
-lock_cache(fs_x_dag_cache_t* cache, apr_pool_t *pool)
-{
-  /* we only need to lock / unlock once per pool.  Since we will often ask
-     for multiple nodes with the same pool, we can reduce the overhead.
-     However, if e.g. pools are being used in an alternating pattern,
-     we may lock the cache more than once for the same pool (and register
-     just as many cleanup actions).
-   */
-  cache_lock_t *lock = cache->first_lock;
-
-  /* try to find an existing lock for POOL.
-     But limit the time spent on chasing pointers.  */
-  int limiter = 8;
-  while (lock && --limiter)
-    {
-      if (lock->pool == pool)
-        return;
-
-      lock = lock->next;
-    }
-
-  /* create a new lock and put it at the beginning of the lock chain */
-  lock = apr_palloc(pool, sizeof(*lock));
-  lock->cache = cache;
-  lock->pool = pool;
-  lock->next = cache->first_lock;
-  lock->prev = NULL;
-
-  if (cache->first_lock)
-    cache->first_lock->prev = lock;
-  cache->first_lock = lock;
-
-  /* instruct POOL to remove the look upon cleanup */
-  apr_pool_cleanup_register(pool,
-                            lock,
-                            unlock_cache,
-                            apr_pool_cleanup_null);
-}
-
 /* Clears the CACHE at regular intervals (destroying all cached nodes)
  */
 static void
-auto_clear_dag_cache(fs_x_dag_cache_t* cache)
+auto_clear_dag_cache(svn_fs_x__dag_cache_t* cache)
 {
-  if (cache->first_lock == NULL && cache->insertions > BUCKET_COUNT)
+  if (cache->insertions > BUCKET_COUNT)
     {
       svn_pool_clear(cache->pool);
 
@@ -319,7 +219,7 @@ auto_clear_dag_cache(fs_x_dag_cache_t* c
    may then set it to the corresponding DAG node allocated in CACHE->POOL.
  */
 static cache_entry_t *
-cache_lookup( fs_x_dag_cache_t *cache
+cache_lookup( svn_fs_x__dag_cache_t *cache
             , svn_revnum_t revision
             , const char *path)
 {
@@ -338,6 +238,10 @@ cache_lookup( fs_x_dag_cache_t *cache
       && (result->path_len == path_len)
       && !memcmp(result->path, path, path_len))
     {
+      /* Remember the position of the last node we found in this cache. */
+      if (result->node)
+        cache->last_non_empty = cache->last_hit;
+
       return result;
     }
 
@@ -388,10 +292,38 @@ cache_lookup( fs_x_dag_cache_t *cache
 
       cache->insertions++;
     }
+  else if (result->node)
+    {
+      /* This bucket is valid & has a suitable DAG node in it.
+         Remember its location. */
+      cache->last_non_empty = bucket_index;
+    }
 
   return result;
 }
 
+/* Optimistic lookup using the last seen non-empty location in CACHE.
+   Return the node of that entry, if it is still in use and matches PATH.
+   Return NULL otherwise.  Since the caller usually already knows the path
+   length, provide it in PATH_LEN. */
+static dag_node_t *
+cache_lookup_last_path(svn_fs_x__dag_cache_t *cache,
+                       const char *path,
+                       apr_size_t path_len)
+{
+  cache_entry_t *result = &cache->buckets[cache->last_non_empty];
+  assert(strlen(path) == path_len);
+
+  if (   result->node
+      && (result->path_len == path_len)
+      && !memcmp(result->path, path, path_len))
+    {
+      return result->node;
+    }
+
+  return NULL;
+}
+
 /* 2nd level cache */
 
 /* Find and return the DAG node cache for ROOT and the key that
@@ -416,7 +348,7 @@ locate_cache(svn_cache__t **cache,
     }
   else
     {
-      fs_x_data_t *ffd = root->fs->fsap_data;
+      svn_fs_x__data_t *ffd = root->fs->fsap_data;
 
       if (cache)
         *cache = ffd->rev_node_cache;
@@ -428,15 +360,11 @@ locate_cache(svn_cache__t **cache,
 /* Return NODE for PATH from ROOT's node cache, or NULL if the node
    isn't cached; read it from the FS. *NODE remains valid until either
    POOL or the FS gets cleared or destroyed (whichever comes first).
-
-   Since locking can be expensive and POOL may be long-living, for
-   nodes that will not need to survive the next call to this function,
-   set NEEDS_LOCK_CACHE to FALSE. */
+ */
 static svn_error_t *
 dag_node_cache_get(dag_node_t **node_p,
                    svn_fs_root_t *root,
                    const char *path,
-                   svn_boolean_t needs_lock_cache,
                    apr_pool_t *pool)
 {
   svn_boolean_t found;
@@ -450,7 +378,7 @@ dag_node_cache_get(dag_node_t **node_p,
     {
       /* immutable DAG node. use the global caches for it */
 
-      fs_x_data_t *ffd = root->fs->fsap_data;
+      svn_fs_x__data_t *ffd = root->fs->fsap_data;
       cache_entry_t *bucket;
 
       auto_clear_dag_cache(ffd->dag_node_cache);
@@ -472,11 +400,6 @@ dag_node_cache_get(dag_node_t **node_p,
         {
           node = bucket->node;
         }
-
-      /* if we found a node, make sure it remains valid at least as long
-         as it would when allocated in POOL. */
-      if (node && needs_lock_cache)
-        lock_cache(ffd->dag_node_cache, pool);
     }
   else
     {
@@ -504,7 +427,7 @@ static svn_error_t *
 dag_node_cache_set(svn_fs_root_t *root,
                    const char *path,
                    dag_node_t *node,
-                   apr_pool_t *pool)
+                   apr_pool_t *scratch_pool)
 {
   svn_cache__t *cache;
   const char *key;
@@ -514,9 +437,9 @@ dag_node_cache_set(svn_fs_root_t *root,
   /* Do *not* attempt to dup and put the node into L1.
    * dup() is twice as expensive as an L2 lookup (which will set also L1).
    */
-  locate_cache(&cache, &key, root, path, pool);
+  locate_cache(&cache, &key, root, path, scratch_pool);
 
-  return svn_cache__set(cache, key, node, pool);
+  return svn_cache__set(cache, key, node, scratch_pool);
 }
 
 
@@ -551,7 +474,7 @@ find_descendants_in_cache(void *baton,
 static svn_error_t *
 dag_node_cache_invalidate(svn_fs_root_t *root,
                           const char *path,
-                          apr_pool_t *pool)
+                          apr_pool_t *scratch_pool)
 {
   struct fdic_baton b;
   svn_cache__t *cache;
@@ -559,7 +482,7 @@ dag_node_cache_invalidate(svn_fs_root_t
   int i;
 
   b.path = path;
-  b.pool = svn_pool_create(pool);
+  b.pool = svn_pool_create(scratch_pool);
   b.list = apr_array_make(b.pool, 1, sizeof(const char *));
 
   SVN_ERR_ASSERT(root->is_txn_root);
@@ -776,8 +699,8 @@ get_copy_inheritance(copy_id_inherit_t *
                      parent_path_t *child,
                      apr_pool_t *pool)
 {
-  const svn_fs_id_t *child_id, *parent_id, *copyroot_id;
-  const svn_fs_x__id_part_t *child_copy_id, *parent_copy_id;
+  svn_fs_x__id_t child_copy_id, parent_copy_id;
+  svn_boolean_t related;
   const char *id_path = NULL;
   svn_fs_root_t *copyroot_root;
   dag_node_t *copyroot_node;
@@ -787,13 +710,11 @@ get_copy_inheritance(copy_id_inherit_t *
   SVN_ERR_ASSERT(child && child->parent);
 
   /* Initialize some convenience variables. */
-  child_id = svn_fs_x__dag_get_id(child->node);
-  parent_id = svn_fs_x__dag_get_id(child->parent->node);
-  child_copy_id = svn_fs_x__id_copy_id(child_id);
-  parent_copy_id = svn_fs_x__id_copy_id(parent_id);
+  SVN_ERR(svn_fs_x__dag_get_copy_id(&child_copy_id, child->node));
+  SVN_ERR(svn_fs_x__dag_get_copy_id(&parent_copy_id, child->parent->node));
 
   /* If this child is already mutable, we have nothing to do. */
-  if (svn_fs_x__id_is_txn(child_id))
+  if (svn_fs_x__dag_check_mutable(child->node))
     {
       *inherit_p = copy_id_inherit_self;
       *copy_src_path = NULL;
@@ -807,14 +728,14 @@ get_copy_inheritance(copy_id_inherit_t *
 
   /* Special case: if the child's copy ID is '0', use the parent's
      copy ID. */
-  if (svn_fs_x__id_part_is_root(child_copy_id))
+  if (svn_fs_x__id_is_root(&child_copy_id))
     return SVN_NO_ERROR;
 
   /* Compare the copy IDs of the child and its parent.  If they are
      the same, then the child is already on the same branch as the
      parent, and should use the same mutability copy ID that the
      parent will use. */
-  if (svn_fs_x__id_part_eq(child_copy_id, parent_copy_id))
+  if (svn_fs_x__id_eq(&child_copy_id, &parent_copy_id))
     return SVN_NO_ERROR;
 
   /* If the child is on the same branch that the parent is on, the
@@ -825,12 +746,12 @@ get_copy_inheritance(copy_id_inherit_t *
      or if it is a branch point that we are accessing via its original
      copy destination path. */
   SVN_ERR(svn_fs_x__dag_get_copyroot(&copyroot_rev, &copyroot_path,
-                                      child->node));
+                                     child->node));
   SVN_ERR(svn_fs_x__revision_root(&copyroot_root, fs, copyroot_rev, pool));
-  SVN_ERR(get_dag(&copyroot_node, copyroot_root, copyroot_path, FALSE, pool));
-  copyroot_id = svn_fs_x__dag_get_id(copyroot_node);
+  SVN_ERR(get_dag(&copyroot_node, copyroot_root, copyroot_path, pool));
 
-  if (svn_fs_x__id_compare(copyroot_id, child_id) == svn_fs_node_unrelated)
+  SVN_ERR(svn_fs_x__dag_related_node(&related, copyroot_node, child->node));
+  if (!related)
     return SVN_NO_ERROR;
 
   /* Determine if we are looking at the child via its original path or
@@ -859,7 +780,8 @@ make_parent_path(dag_node_t *node,
                  apr_pool_t *pool)
 {
   parent_path_t *parent_path = apr_pcalloc(pool, sizeof(*parent_path));
-  parent_path->node = node;
+  if (node)
+    parent_path->node = svn_fs_x__dag_copy_into_pool(node, pool);
   parent_path->entry = entry;
   parent_path->parent = parent;
   parent_path->copy_inherit = copy_id_inherit_unknown;
@@ -891,6 +813,55 @@ typedef enum open_path_flags_t {
   open_path_allow_null = 8
 } open_path_flags_t;
 
+/* Try a short-cut for the open_path() function using the last node accessed.
+ * If that ROOT is that nodes's "created rev" and PATH of PATH_LEN chars is
+ * its "created path", return the node in *NODE_P.  Set it to NULL otherwise.
+ *
+ * This function is used to support ra_serf-style access patterns where we
+ * are first asked for path@rev and then for path@c_rev of the same node.
+ * The shortcut works by ignoring the "rev" part of the cache key and then
+ * checking whether we got lucky.  Lookup and verification are both quick
+ * plus there are many early outs for common types of mismatch.
+ */
+static svn_error_t *
+try_match_last_node(dag_node_t **node_p,
+                    svn_fs_root_t *root,
+                    const char *path,
+                    apr_size_t path_len,
+                    apr_pool_t *scratch_pool)
+{
+  svn_fs_x__data_t *ffd = root->fs->fsap_data;
+
+  /* Optimistic lookup: if the last node returned from the cache applied to
+     the same PATH, return it in NODE. */
+  dag_node_t *node
+    = cache_lookup_last_path(ffd->dag_node_cache, path, path_len);
+
+  /* Did we get a bucket with a committed node? */
+  if (node && !svn_fs_x__dag_check_mutable(node))
+    {
+      /* Get the path&rev pair at which this node was created.
+         This is repository location for which this node is _known_ to be
+         the right lookup result irrespective of how we found it. */
+      const char *created_path
+        = svn_fs_x__dag_get_created_path(node);
+      svn_revnum_t revision = svn_fs_x__dag_get_revision(node);
+
+      /* Is it an exact match? */
+      if (revision == root->rev && strcmp(created_path, path) == 0)
+        {
+          /* Cache it under its full path@rev access path. */
+          SVN_ERR(dag_node_cache_set(root, path, node, scratch_pool));
+
+          *node_p = node;
+          return SVN_NO_ERROR;
+        }
+    }
+
+  *node_p = NULL;
+  return SVN_NO_ERROR;
+}
+
 
 /* Open the node identified by PATH in ROOT, allocating in POOL.  Set
    *PARENT_PATH_P to a path from the node up to ROOT.  The resulting
@@ -938,20 +909,55 @@ open_path(parent_path_t **parent_path_p,
      at the respective position and replacing that with a '/' in the next
      iteration.  This is correct as we assert() PATH to be canonical. */
   svn_stringbuf_t *path_so_far = svn_stringbuf_create(path, pool);
+  apr_size_t path_len = path_so_far->len;
 
-  /* callers often traverse the tree in some path-based order.  That means
-     a sibling of PATH has been presently accessed.  Try to start the lookup
-     directly at the parent node, if the caller did not requested the full
-     parent chain. */
+  /* Callers often traverse the DAG in some path-based order or along the
+     history segments.  That allows us to try a few guesses about where to
+     find the next item.  This is only useful if the caller didn't request
+     the full parent chain. */
   assert(svn_fs__is_canonical_abspath(path));
   path_so_far->len = 0; /* "" */
   if (flags & open_path_node_only)
     {
-      const char *directory = svn_dirent_dirname(path, pool);
+      const char *directory;
+
+      /* First attempt: Assume that we access the DAG for the same path as
+         in the last lookup but for a different revision that happens to be
+         the last revision that touched the respective node.  This is a
+         common pattern when e.g. checking out over ra_serf.  Note that this
+         will only work for committed data as the revision info for nodes in
+         txns is bogus.
+
+         This shortcut is quick and will exit this function upon success.
+         So, try it first. */
+      if (!root->is_txn_root)
+        {
+          dag_node_t *node;
+          SVN_ERR(try_match_last_node(&node, root, path, path_len, iterpool));
+
+          /* Did the shortcut work? */
+          if (node)
+            {
+              /* Construct and return the result. */
+              svn_pool_destroy(iterpool);
+
+              parent_path = make_parent_path(node, 0, 0, pool);
+              parent_path->copy_inherit = copy_id_inherit_self;
+              *parent_path_p = parent_path;
+
+              return SVN_NO_ERROR;
+            }
+        }
+
+      /* Second attempt: Try starting the lookup immediately at the parent
+         node.  We will often have recently accessed either a sibling or
+         said parent DIRECTORY itself for the same revision. */
+      directory = svn_dirent_dirname(path, pool);
       if (directory[1] != 0) /* root nodes are covered anyway */
         {
-          SVN_ERR(dag_node_cache_get(&here, root, directory, TRUE, pool));
-          /* did the shortcut work? */
+          SVN_ERR(dag_node_cache_get(&here, root, directory, pool));
+
+          /* Did the shortcut work? */
           if (here)
             {
               apr_size_t dirname_len = strlen(directory);
@@ -995,15 +1001,12 @@ open_path(parent_path_t **parent_path_p,
       path_so_far->len += strlen(entry) + 1;
       path_so_far->data[path_so_far->len] = '\0';
 
-      if (*entry == '\0')
-        {
-          /* Given the behavior of svn_fs__next_entry_name(), this
-             happens when the path either starts or ends with a slash.
-             In either case, we stay put: the current directory stays
-             the same, and we add nothing to the parent path. */
-          child = here;
-        }
-      else
+      /* Given the behavior of svn_fs__next_entry_name(), ENTRY may be an
+         empty string when the path either starts or ends with a slash.
+         In either case, we stay put: the current directory stays the
+         same, and we add nothing to the parent path.  We only need to
+         process non-empty path segments. */
+      if (*entry != '\0')
         {
           copy_id_inherit_t inherit;
           const char *copy_path = NULL;
@@ -1016,7 +1019,7 @@ open_path(parent_path_t **parent_path_p,
              complete path. */
           if (next || !(flags & open_path_uncached))
             SVN_ERR(dag_node_cache_get(&cached_node, root, path_so_far->data,
-                                       TRUE, pool));
+                                       pool));
           if (cached_node)
             child = cached_node;
           else
@@ -1052,7 +1055,7 @@ open_path(parent_path_t **parent_path_p,
           if (flags & open_path_node_only)
             {
               /* Shortcut: the caller only wants the final DAG node. */
-              parent_path->node = child;
+              parent_path->node = svn_fs_x__dag_copy_into_pool(child, pool);
             }
           else
             {
@@ -1083,7 +1086,10 @@ open_path(parent_path_t **parent_path_p,
                   apr_psprintf(iterpool, _("Failure opening '%s'"), path));
 
       rest = next;
-      here = child;
+
+      /* The NODE in PARENT_PATH equals CHILD but lives in POOL, i.e.
+       * it will survive the cleanup of ITERPOOL.*/
+      here = parent_path->node;
     }
 
   svn_pool_destroy(iterpool);
@@ -1113,15 +1119,15 @@ make_path_mutable(svn_fs_root_t *root,
   /* Are we trying to clone the root, or somebody's child node?  */
   if (parent_path->parent)
     {
-      const svn_fs_id_t *parent_id, *child_id, *copyroot_id;
-      svn_fs_x__id_part_t copy_id = { SVN_INVALID_REVNUM, 0 };
-      svn_fs_x__id_part_t *copy_id_ptr = &copy_id;
+      svn_fs_x__id_t copy_id = { SVN_INVALID_REVNUM, 0 };
+      svn_fs_x__id_t *copy_id_ptr = &copy_id;
       copy_id_inherit_t inherit = parent_path->copy_inherit;
       const char *clone_path, *copyroot_path;
       svn_revnum_t copyroot_rev;
       svn_boolean_t is_parent_copyroot = FALSE;
       svn_fs_root_t *copyroot_root;
       dag_node_t *copyroot_node;
+      svn_boolean_t related;
 
       /* We're trying to clone somebody's child.  Make sure our parent
          is mutable.  */
@@ -1131,8 +1137,8 @@ make_path_mutable(svn_fs_root_t *root,
       switch (inherit)
         {
         case copy_id_inherit_parent:
-          parent_id = svn_fs_x__dag_get_id(parent_path->parent->node);
-          copy_id = *svn_fs_x__id_copy_id(parent_id);
+          SVN_ERR(svn_fs_x__dag_get_copy_id(&copy_id,
+                                            parent_path->parent->node));
           break;
 
         case copy_id_inherit_new:
@@ -1155,13 +1161,11 @@ make_path_mutable(svn_fs_root_t *root,
                                           parent_path->node));
       SVN_ERR(svn_fs_x__revision_root(&copyroot_root, root->fs,
                                       copyroot_rev, pool));
-      SVN_ERR(get_dag(&copyroot_node, copyroot_root, copyroot_path,
-                      FALSE, pool));
+      SVN_ERR(get_dag(&copyroot_node, copyroot_root, copyroot_path, pool));
 
-      child_id = svn_fs_x__dag_get_id(parent_path->node);
-      copyroot_id = svn_fs_x__dag_get_id(copyroot_node);
-      if (!svn_fs_x__id_part_eq(svn_fs_x__id_node_id(child_id),
-                                svn_fs_x__id_node_id(copyroot_id)))
+      SVN_ERR(svn_fs_x__dag_related_node(&related, copyroot_node,
+                                         parent_path->node));
+      if (!related)
         is_parent_copyroot = TRUE;
 
       /* Now make this node mutable.  */
@@ -1194,15 +1198,11 @@ make_path_mutable(svn_fs_root_t *root,
 /* Open the node identified by PATH in ROOT.  Set DAG_NODE_P to the
    node we find, allocated in POOL.  Return the error
    SVN_ERR_FS_NOT_FOUND if this node doesn't exist.
-
-   Since locking can be expensive and POOL may be long-living, for
-   nodes that will not need to survive the next call to this function,
-   set NEEDS_LOCK_CACHE to FALSE. */
+ */
 static svn_error_t *
 get_dag(dag_node_t **dag_node_p,
         svn_fs_root_t *root,
         const char *path,
-        svn_boolean_t needs_lock_cache,
         apr_pool_t *pool)
 {
   parent_path_t *parent_path;
@@ -1211,7 +1211,7 @@ get_dag(dag_node_t **dag_node_p,
   /* First we look for the DAG in our cache
      (if the path may be canonical). */
   if (*path == '/')
-    SVN_ERR(dag_node_cache_get(&node, root, path, needs_lock_cache, pool));
+    SVN_ERR(dag_node_cache_get(&node, root, path, pool));
 
   if (! node)
     {
@@ -1221,8 +1221,7 @@ get_dag(dag_node_t **dag_node_p,
        * performance benefit over previously checking path for being
        * canonical. */
       path = svn_fs__canonicalize_abspath(path, pool);
-      SVN_ERR(dag_node_cache_get(&node, root, path, needs_lock_cache,
-                                 pool));
+      SVN_ERR(dag_node_cache_get(&node, root, path, pool));
 
       if (! node)
         {
@@ -1237,7 +1236,7 @@ get_dag(dag_node_t **dag_node_p,
         }
     }
 
-  *dag_node_p = node;
+  *dag_node_p = svn_fs_x__dag_copy_into_pool(node, pool);
   return SVN_NO_ERROR;
 }
 
@@ -1252,12 +1251,13 @@ get_dag(dag_node_t **dag_node_p,
    MERGEINFO_MODs occurred.  If the change resulted from a copy,
    COPYFROM_REV and COPYFROM_PATH specify under which revision and path
    the node was copied from.  If this was not part of a copy, COPYFROM_REV
-   should be SVN_INVALID_REVNUM.  Do all this as part of POOL.  */
+   should be SVN_INVALID_REVNUM.  Use SCRATCH_POOL for temporary allocations.
+ */
 static svn_error_t *
 add_change(svn_fs_t *fs,
            svn_fs_x__txn_id_t txn_id,
            const char *path,
-           const svn_fs_id_t *noderev_id,
+           const svn_fs_x__id_t *noderev_id,
            svn_fs_path_change_kind_t change_kind,
            svn_boolean_t text_mod,
            svn_boolean_t prop_mod,
@@ -1265,14 +1265,15 @@ add_change(svn_fs_t *fs,
            svn_node_kind_t node_kind,
            svn_revnum_t copyfrom_rev,
            const char *copyfrom_path,
-           apr_pool_t *pool)
+           apr_pool_t *scratch_pool)
 {
   return svn_fs_x__add_change(fs, txn_id,
-                              svn_fs__canonicalize_abspath(path, pool),
+                              svn_fs__canonicalize_abspath(path,
+                                                           scratch_pool),
                               noderev_id, change_kind,
                               text_mod, prop_mod, mergeinfo_mod,
                               node_kind, copyfrom_rev, copyfrom_path,
-                              pool);
+                              scratch_pool);
 }
 
 
@@ -1281,12 +1282,14 @@ add_change(svn_fs_t *fs,
 
 /* Get the id of a node referenced by path PATH in ROOT.  Return the
    id in *ID_P allocated in POOL. */
-svn_error_t *
-svn_fs_x__node_id(const svn_fs_id_t **id_p,
-                  svn_fs_root_t *root,
-                  const char *path,
-                  apr_pool_t *pool)
+static svn_error_t *
+x_node_id(const svn_fs_id_t **id_p,
+          svn_fs_root_t *root,
+          const char *path,
+          apr_pool_t *pool)
 {
+  const svn_fs_x__id_t *noderev_id;
+
   if ((! root->is_txn_root)
       && (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0'))))
     {
@@ -1295,27 +1298,32 @@ svn_fs_x__node_id(const svn_fs_id_t **id
          svn_fs_root_t object, and never changes when it's a revision
          root, so we can just reach in and grab it directly. */
       dag_node_t *root_dir = root->fsap_data;
-      *id_p = svn_fs_x__id_copy(svn_fs_x__dag_get_id(root_dir), pool);
+      noderev_id = svn_fs_x__dag_get_id(root_dir);
     }
   else
     {
       dag_node_t *node;
 
-      SVN_ERR(get_dag(&node, root, path, FALSE, pool));
-      *id_p = svn_fs_x__id_copy(svn_fs_x__dag_get_id(node), pool);
+      SVN_ERR(get_dag(&node, root, path, pool));
+      noderev_id = svn_fs_x__dag_get_id(node);
     }
+
+  *id_p = svn_fs_x__id_create(svn_fs_x__id_create_context(root->fs, pool),
+                              noderev_id, pool);
+
   return SVN_NO_ERROR;
 }
 
 static svn_error_t *
 x_node_relation(svn_fs_node_relation_t *relation,
-                svn_fs_root_t *root_a, const char *path_a,
-                svn_fs_root_t *root_b, const char *path_b,
-                apr_pool_t *pool)
+                svn_fs_root_t *root_a,
+                const char *path_a,
+                svn_fs_root_t *root_b,
+                const char *path_b,
+                apr_pool_t *scratch_pool)
 {
   dag_node_t *node;
-  const svn_fs_id_t *id;
-  svn_fs_x__id_part_t noderev_id_a, noderev_id_b, node_id_a, node_id_b;
+  svn_fs_x__id_t noderev_id_a, noderev_id_b, node_id_a, node_id_b;
 
   /* Root paths are a common special case. */
   svn_boolean_t a_is_root_dir
@@ -1350,19 +1358,17 @@ x_node_relation(svn_fs_node_relation_t *
 
   /* We checked for all separations between ID spaces (repos, txn).
    * Now, we can simply test for the ID values themselves. */
-  SVN_ERR(get_dag(&node, root_a, path_a, FALSE, pool));
-  id = svn_fs_x__dag_get_id(node);
-  noderev_id_a = *svn_fs_x__id_noderev_id(id);
-  node_id_a = *svn_fs_x__id_node_id(id);
-
-  SVN_ERR(get_dag(&node, root_b, path_b, FALSE, pool));
-  id = svn_fs_x__dag_get_id(node);
-  noderev_id_b = *svn_fs_x__id_noderev_id(id);
-  node_id_b = *svn_fs_x__id_node_id(id);
+  SVN_ERR(get_dag(&node, root_a, path_a, scratch_pool));
+  noderev_id_a = *svn_fs_x__dag_get_id(node);
+  SVN_ERR(svn_fs_x__dag_get_node_id(&node_id_a, node));
+
+  SVN_ERR(get_dag(&node, root_b, path_b, scratch_pool));
+  noderev_id_b = *svn_fs_x__dag_get_id(node);
+  SVN_ERR(svn_fs_x__dag_get_node_id(&node_id_b, node));
 
-  if (svn_fs_x__id_part_eq(&noderev_id_a, &noderev_id_b))
+  if (svn_fs_x__id_eq(&noderev_id_a, &noderev_id_b))
     *relation = svn_fs_node_same;
-  else if (svn_fs_x__id_part_eq(&node_id_a, &node_id_b))
+  else if (svn_fs_x__id_eq(&node_id_a, &node_id_b))
     *relation = svn_fs_node_common_ancestor;
   else
     *relation = svn_fs_node_unrelated;
@@ -1374,12 +1380,14 @@ svn_error_t *
 svn_fs_x__node_created_rev(svn_revnum_t *revision,
                            svn_fs_root_t *root,
                            const char *path,
-                           apr_pool_t *pool)
+                           apr_pool_t *scratch_pool)
 {
   dag_node_t *node;
 
-  SVN_ERR(get_dag(&node, root, path, FALSE, pool));
-  return svn_fs_x__dag_get_revision(revision, node, pool);
+  SVN_ERR(get_dag(&node, root, path, scratch_pool));
+  *revision = svn_fs_x__dag_get_revision(node);
+
+  return SVN_NO_ERROR;
 }
 
 
@@ -1393,7 +1401,7 @@ x_node_created_path(const char **created
 {
   dag_node_t *node;
 
-  SVN_ERR(get_dag(&node, root, path, TRUE, pool));
+  SVN_ERR(get_dag(&node, root, path, pool));
   *created_path = svn_fs_x__dag_get_created_path(node);
 
   return SVN_NO_ERROR;
@@ -1401,21 +1409,19 @@ x_node_created_path(const char **created
 
 
 /* Set *KIND_P to the type of node located at PATH under ROOT.
-   Perform temporary allocations in POOL. */
+   Perform temporary allocations in SCRATCH_POOL. */
 static svn_error_t *
 node_kind(svn_node_kind_t *kind_p,
           svn_fs_root_t *root,
           const char *path,
-          apr_pool_t *pool)
+          apr_pool_t *scratch_pool)
 {
-  const svn_fs_id_t *node_id;
   dag_node_t *node;
 
   /* Get the node id. */
-  SVN_ERR(svn_fs_x__node_id(&node_id, root, path, pool));
+  SVN_ERR(get_dag(&node, root, path, scratch_pool));
 
   /* Use the node id to get the real kind. */
-  SVN_ERR(svn_fs_x__dag_get_node(&node, root->fs, node_id, pool));
   *kind_p = svn_fs_x__dag_node_kind(node);
 
   return SVN_NO_ERROR;
@@ -1424,14 +1430,14 @@ node_kind(svn_node_kind_t *kind_p,
 
 /* Set *KIND_P to the type of node present at PATH under ROOT.  If
    PATH does not exist under ROOT, set *KIND_P to svn_node_none.  Use
-   POOL for temporary allocation. */
+   SCRATCH_POOL for temporary allocation. */
 svn_error_t *
 svn_fs_x__check_path(svn_node_kind_t *kind_p,
                      svn_fs_root_t *root,
                      const char *path,
-                     apr_pool_t *pool)
+                     apr_pool_t *scratch_pool)
 {
-  svn_error_t *err = node_kind(kind_p, root, path, pool);
+  svn_error_t *err = node_kind(kind_p, root, path, scratch_pool);
   if (err &&
       ((err->apr_err == SVN_ERR_FS_NOT_FOUND)
        || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)))
@@ -1457,7 +1463,7 @@ x_node_prop(svn_string_t **value_p,
   dag_node_t *node;
   apr_hash_t *proplist;
 
-  SVN_ERR(get_dag(&node, root, path, FALSE, pool));
+  SVN_ERR(get_dag(&node, root, path,  pool));
   SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, node, pool));
   *value_p = NULL;
   if (proplist)
@@ -1480,7 +1486,7 @@ x_node_proplist(apr_hash_t **table_p,
   apr_hash_t *table;
   dag_node_t *node;
 
-  SVN_ERR(get_dag(&node, root, path, FALSE, pool));
+  SVN_ERR(get_dag(&node, root, path, pool));
   SVN_ERR(svn_fs_x__dag_get_proplist(&table, node, pool));
   *table_p = table ? table : apr_hash_make(pool);
 
@@ -1491,12 +1497,12 @@ x_node_proplist(apr_hash_t **table_p,
 static svn_error_t *
 increment_mergeinfo_up_tree(parent_path_t *pp,
                             apr_int64_t increment,
-                            apr_pool_t *pool)
+                            apr_pool_t *scratch_pool)
 {
   for (; pp; pp = pp->parent)
     SVN_ERR(svn_fs_x__dag_increment_mergeinfo_count(pp->node,
                                                     increment,
-                                                    pool));
+                                                    scratch_pool));
 
   return SVN_NO_ERROR;
 }
@@ -1505,34 +1511,35 @@ increment_mergeinfo_up_tree(parent_path_
    is PATH under ROOT, the property value to modify is NAME, and VALUE
    points to either a string value to set the new contents to, or NULL
    if the property should be deleted.  Perform temporary allocations
-   in POOL. */
+   in SCRATCH_POOL. */
 static svn_error_t *
 x_change_node_prop(svn_fs_root_t *root,
                    const char *path,
                    const char *name,
                    const svn_string_t *value,
-                   apr_pool_t *pool)
+                   apr_pool_t *scratch_pool)
 {
   parent_path_t *parent_path;
   apr_hash_t *proplist;
   svn_fs_x__txn_id_t txn_id;
   svn_boolean_t mergeinfo_mod = FALSE;
+  apr_pool_t *subpool = svn_pool_create(scratch_pool);
 
   if (! root->is_txn_root)
     return SVN_FS__NOT_TXN(root);
   txn_id = root_txn_id(root);
 
-  path = svn_fs__canonicalize_abspath(path, pool);
-  SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, pool));
+  path = svn_fs__canonicalize_abspath(path, subpool);
+  SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, subpool));
 
   /* Check (non-recursively) to see if path is locked; if so, check
      that we can use it. */
   if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
     SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE,
-                                             pool));
+                                             subpool));
 
-  SVN_ERR(make_path_mutable(root, parent_path, path, pool));
-  SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, parent_path->node, pool));
+  SVN_ERR(make_path_mutable(root, parent_path, path, subpool));
+  SVN_ERR(svn_fs_x__dag_get_proplist(&proplist, parent_path->node, subpool));
 
   /* If there's no proplist, but we're just deleting a property, exit now. */
   if ((! proplist) && (! value))
@@ -1540,7 +1547,7 @@ x_change_node_prop(svn_fs_root_t *root,
 
   /* Now, if there's no proplist, we know we need to make one. */
   if (! proplist)
-    proplist = apr_hash_make(pool);
+    proplist = apr_hash_make(subpool);
 
   if (strcmp(name, SVN_PROP_MERGEINFO) == 0)
     {
@@ -1555,9 +1562,9 @@ x_change_node_prop(svn_fs_root_t *root,
 
       if (increment != 0)
         {
-          SVN_ERR(increment_mergeinfo_up_tree(parent_path, increment, pool));
+          SVN_ERR(increment_mergeinfo_up_tree(parent_path, increment, subpool));
           SVN_ERR(svn_fs_x__dag_set_has_mergeinfo(parent_path->node,
-                                                  (value != NULL), pool));
+                                                  (value != NULL), subpool));
         }
 
       mergeinfo_mod = TRUE;
@@ -1568,14 +1575,17 @@ x_change_node_prop(svn_fs_root_t *root,
 
   /* Overwrite the node's proplist. */
   SVN_ERR(svn_fs_x__dag_set_proplist(parent_path->node, proplist,
-                                     pool));
+                                     subpool));
 
   /* Make a record of this modification in the changes table. */
-  return add_change(root->fs, txn_id, path,
-                    svn_fs_x__dag_get_id(parent_path->node),
-                    svn_fs_path_change_modify, FALSE, TRUE, mergeinfo_mod,
-                    svn_fs_x__dag_node_kind(parent_path->node),
-                    SVN_INVALID_REVNUM, NULL, pool);
+  SVN_ERR(add_change(root->fs, txn_id, path,
+                     svn_fs_x__dag_get_id(parent_path->node),
+                     svn_fs_path_change_modify, FALSE, TRUE, mergeinfo_mod,
+                     svn_fs_x__dag_node_kind(parent_path->node),
+                     SVN_INVALID_REVNUM, NULL, subpool));
+
+  svn_pool_destroy(subpool);
+  return SVN_NO_ERROR;
 }
 
 
@@ -1590,9 +1600,10 @@ x_props_changed(svn_boolean_t *changed_p
                 svn_fs_root_t *root2,
                 const char *path2,
                 svn_boolean_t strict,
-                apr_pool_t *pool)
+                apr_pool_t *scratch_pool)
 {
   dag_node_t *node1, *node2;
+  apr_pool_t *subpool = svn_pool_create(scratch_pool);
 
   /* Check that roots are in the same fs. */
   if (root1->fs != root2->fs)
@@ -1600,10 +1611,13 @@ x_props_changed(svn_boolean_t *changed_p
       (SVN_ERR_FS_GENERAL, NULL,
        _("Cannot compare property value between two different filesystems"));
 
-  SVN_ERR(get_dag(&node1, root1, path1, TRUE, pool));
-  SVN_ERR(get_dag(&node2, root2, path2, TRUE, pool));
-  return svn_fs_x__dag_things_different(changed_p, NULL, node1, node2,
-                                        strict, pool);
+  SVN_ERR(get_dag(&node1, root1, path1, subpool));
+  SVN_ERR(get_dag(&node2, root2, path2, subpool));
+  SVN_ERR(svn_fs_x__dag_things_different(changed_p, NULL, node1, node2,
+                                         strict, subpool));
+  svn_pool_destroy(subpool);
+
+  return SVN_NO_ERROR;
 }
 
 
@@ -1614,7 +1628,7 @@ x_props_changed(svn_boolean_t *changed_p
 static svn_error_t *
 get_root(dag_node_t **node, svn_fs_root_t *root, apr_pool_t *pool)
 {
-  return get_dag(node, root, "/", TRUE, pool);
+  return get_dag(node, root, "/", pool);
 }
 
 
@@ -1631,44 +1645,65 @@ conflict_err(svn_stringbuf_t *conflict_p
                            _("Conflict at '%s'"), path);
 }
 
-/* Compare the directory representations at nodes LHS and RHS and set
+/* Compare the directory representations at nodes LHS and RHS in FS and set
  * *CHANGED to TRUE, if at least one entry has been added or removed them.
- * Use POOL for temporary allocations.
+ * Use SCRATCH_POOL for temporary allocations.
  */
 static svn_error_t *
 compare_dir_structure(svn_boolean_t *changed,
+                      svn_fs_t *fs,
                       dag_node_t *lhs,
                       dag_node_t *rhs,
-                      apr_pool_t *pool)
+                      apr_pool_t *scratch_pool)
 {
   apr_array_header_t *lhs_entries;
   apr_array_header_t *rhs_entries;
   int i;
+  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
-  SVN_ERR(svn_fs_x__dag_dir_entries(&lhs_entries, lhs, pool));
-  SVN_ERR(svn_fs_x__dag_dir_entries(&rhs_entries, rhs, pool));
+  SVN_ERR(svn_fs_x__dag_dir_entries(&lhs_entries, lhs, scratch_pool));
+  SVN_ERR(svn_fs_x__dag_dir_entries(&rhs_entries, rhs, scratch_pool));
 
   /* Since directories are sorted by name, we can simply compare their
      entries one-by-one without binary lookup etc. */
   for (i = 0; i < lhs_entries->nelts; ++i)
     {
-      svn_fs_dirent_t *lhs_entry
-        = APR_ARRAY_IDX(lhs_entries, i, svn_fs_dirent_t *);
-      svn_fs_dirent_t *rhs_entry
-        = APR_ARRAY_IDX(rhs_entries, i, svn_fs_dirent_t *);
-
-      if (strcmp(lhs_entry->name, rhs_entry->name)
-          || !svn_fs_x__id_part_eq(svn_fs_x__id_node_id(lhs_entry->id),
-                                   svn_fs_x__id_node_id(rhs_entry->id))
-          || !svn_fs_x__id_part_eq(svn_fs_x__id_copy_id(lhs_entry->id),
-                                   svn_fs_x__id_copy_id(rhs_entry->id)))
+      svn_fs_x__dirent_t *lhs_entry
+        = APR_ARRAY_IDX(lhs_entries, i, svn_fs_x__dirent_t *);
+      svn_fs_x__dirent_t *rhs_entry
+        = APR_ARRAY_IDX(rhs_entries, i, svn_fs_x__dirent_t *);
+
+      if (strcmp(lhs_entry->name, rhs_entry->name) == 0)
         {
-          *changed = TRUE;
-          return SVN_NO_ERROR;
+          svn_boolean_t same_history;
+          dag_node_t *lhs_node, *rhs_node;
+
+          /* Unchanged entry? */
+          if (!svn_fs_x__id_eq(&lhs_entry->id, &rhs_entry->id))
+            continue;
+
+          /* We get here rarely. */
+          svn_pool_clear(iterpool);
+
+          /* Modified but not copied / replaced or anything? */
+          SVN_ERR(svn_fs_x__dag_get_node(&lhs_node, fs, &lhs_entry->id, 
+                                         iterpool));
+          SVN_ERR(svn_fs_x__dag_get_node(&rhs_node, fs, &rhs_entry->id, 
+                                         iterpool));
+          SVN_ERR(svn_fs_x__dag_same_line_of_history(&same_history,
+                                                     lhs_node, rhs_node));
+          if (same_history)
+            continue;
         }
+
+      /* This is a different entry. */
+      *changed = TRUE;
+      break;
     }
 
+  svn_pool_destroy(iterpool);
   *changed = FALSE;
+
   return SVN_NO_ERROR;
 }
 
@@ -1704,7 +1739,7 @@ merge(svn_stringbuf_t *conflict_p,
       apr_int64_t *mergeinfo_increment_out,
       apr_pool_t *pool)
 {
-  const svn_fs_id_t *source_id, *target_id, *ancestor_id;
+  const svn_fs_x__id_t *source_id, *target_id, *ancestor_id;
   apr_array_header_t *s_entries, *t_entries, *a_entries;
   int i, s_idx = -1, t_idx = -1;
   svn_fs_t *fs;
@@ -1831,20 +1866,20 @@ merge(svn_stringbuf_t *conflict_p,
      happening), the merge should fail.  See issue #2751.
   */
   {
-    node_revision_t *tgt_nr, *anc_nr, *src_nr;
+    svn_fs_x__noderev_t *tgt_nr, *anc_nr, *src_nr;
     svn_boolean_t same;
     apr_pool_t *scratch_pool;
 
     /* Get node revisions for our id's. */
     scratch_pool = svn_pool_create(pool);
-    SVN_ERR(svn_fs_x__get_node_revision(&tgt_nr, fs, target_id, pool,
-                                        scratch_pool));
+    SVN_ERR(svn_fs_x__get_node_revision(&tgt_nr, fs, target_id,
+                                        pool, scratch_pool));
     svn_pool_clear(scratch_pool);
-    SVN_ERR(svn_fs_x__get_node_revision(&anc_nr, fs, ancestor_id, pool,
-                                        scratch_pool));
+    SVN_ERR(svn_fs_x__get_node_revision(&anc_nr, fs, ancestor_id,
+                                        pool, scratch_pool));
     svn_pool_clear(scratch_pool);
-    SVN_ERR(svn_fs_x__get_node_revision(&src_nr, fs, source_id, pool,
-                                        scratch_pool));
+    SVN_ERR(svn_fs_x__get_node_revision(&src_nr, fs, source_id,
+                                        pool, scratch_pool));
     svn_pool_destroy(scratch_pool);
 
     /* Now compare the prop-keys of the skels.  Note that just because
@@ -1864,7 +1899,7 @@ merge(svn_stringbuf_t *conflict_p,
            to its entries, i.e. there were no additions or removals.
            Those could cause update problems to the working copy. */
         svn_boolean_t changed;
-        SVN_ERR(compare_dir_structure(&changed, source, ancestor, pool));
+        SVN_ERR(compare_dir_structure(&changed, fs, source, ancestor, pool));
 
         if (changed)
           return conflict_err(conflict_p, target_path);
@@ -1882,44 +1917,44 @@ merge(svn_stringbuf_t *conflict_p,
   iterpool = svn_pool_create(pool);
   for (i = 0; i < a_entries->nelts; ++i)
     {
-      svn_fs_dirent_t *s_entry, *t_entry, *a_entry;
+      svn_fs_x__dirent_t *s_entry, *t_entry, *a_entry;
       svn_pool_clear(iterpool);
 
-      a_entry = APR_ARRAY_IDX(a_entries, i, svn_fs_dirent_t *);
+      a_entry = APR_ARRAY_IDX(a_entries, i, svn_fs_x__dirent_t *);
       s_entry = svn_fs_x__find_dir_entry(s_entries, a_entry->name, &s_idx);
       t_entry = svn_fs_x__find_dir_entry(t_entries, a_entry->name, &t_idx);
 
       /* No changes were made to this entry while the transaction was
          in progress, so do nothing to the target. */
-      if (s_entry && svn_fs_x__id_eq(a_entry->id, s_entry->id))
+      if (s_entry && svn_fs_x__id_eq(&a_entry->id, &s_entry->id))
         continue;
 
       /* A change was made to this entry while the transaction was in
          process, but the transaction did not touch this entry. */
-      else if (t_entry && svn_fs_x__id_eq(a_entry->id, t_entry->id))
+      else if (t_entry && svn_fs_x__id_eq(&a_entry->id, &t_entry->id))
         {
           apr_int64_t mergeinfo_start;
           apr_int64_t mergeinfo_end;
 
           dag_node_t *t_ent_node;
-          SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs,
-                                         t_entry->id, iterpool));
+          SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id,
+                                         iterpool));
           SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_start,
-                                                      t_ent_node));
+                                                    t_ent_node));
           mergeinfo_increment -= mergeinfo_start;
 
           if (s_entry)
             {
               dag_node_t *s_ent_node;
-              SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs,
-                                             s_entry->id, iterpool));
+              SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
+                                             iterpool));
 
               SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_end,
                                                         s_ent_node));
               mergeinfo_increment += mergeinfo_end;
 
               SVN_ERR(svn_fs_x__dag_set_entry(target, a_entry->name,
-                                              s_entry->id,
+                                              &s_entry->id,
                                               s_entry->kind,
                                               txn_id,
                                               iterpool));
@@ -1939,6 +1974,7 @@ merge(svn_stringbuf_t *conflict_p,
           dag_node_t *s_ent_node, *t_ent_node, *a_ent_node;
           const char *new_tpath;
           apr_int64_t sub_mergeinfo_increment;
+          svn_boolean_t s_a_same, t_a_same;
 
           /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
              double delete; if one of them is null, that's a delete versus
@@ -1958,16 +1994,21 @@ merge(svn_stringbuf_t *conflict_p,
                                                  a_entry->name,
                                                  iterpool));
 
+          /* Fetch DAG nodes to efficiently access ID parts. */
+          SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
+                                         iterpool));
+          SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs, &t_entry->id,
+                                         iterpool));
+          SVN_ERR(svn_fs_x__dag_get_node(&a_ent_node, fs, &a_entry->id,
+                                         iterpool));
+
           /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
              modification of ANCESTOR-ENTRY, declare a conflict. */
-          if (!svn_fs_x__id_part_eq(svn_fs_x__id_node_id(s_entry->id),
-                                    svn_fs_x__id_node_id(a_entry->id))
-              || !svn_fs_x__id_part_eq(svn_fs_x__id_copy_id(s_entry->id),
-                                       svn_fs_x__id_copy_id(a_entry->id))
-              || !svn_fs_x__id_part_eq(svn_fs_x__id_node_id(t_entry->id),
-                                       svn_fs_x__id_node_id(a_entry->id))
-              || !svn_fs_x__id_part_eq(svn_fs_x__id_copy_id(t_entry->id),
-                                       svn_fs_x__id_copy_id(a_entry->id)))
+          SVN_ERR(svn_fs_x__dag_same_line_of_history(&s_a_same, s_ent_node,
+                                                     a_ent_node));
+          SVN_ERR(svn_fs_x__dag_same_line_of_history(&t_a_same, t_ent_node,
+                                                     a_ent_node));
+          if (!s_a_same || !t_a_same)
             return conflict_err(conflict_p,
                                 svn_fspath__join(target_path,
                                                  a_entry->name,
@@ -1976,12 +2017,6 @@ merge(svn_stringbuf_t *conflict_p,
           /* Direct modifications were made to the directory
              ANCESTOR-ENTRY in both SOURCE and TARGET.  Recursively
              merge these modifications. */
-          SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs,
-                                         s_entry->id, iterpool));
-          SVN_ERR(svn_fs_x__dag_get_node(&t_ent_node, fs,
-                                         t_entry->id, iterpool));
-          SVN_ERR(svn_fs_x__dag_get_node(&a_ent_node, fs,
-                                         a_entry->id, iterpool));
           new_tpath = svn_fspath__join(target_path, t_entry->name, iterpool);
           SVN_ERR(merge(conflict_p, new_tpath,
                         t_ent_node, s_ent_node, a_ent_node,
@@ -1995,13 +2030,13 @@ merge(svn_stringbuf_t *conflict_p,
   /* For each entry E in source but not in ancestor */
   for (i = 0; i < s_entries->nelts; ++i)
     {
-      svn_fs_dirent_t *a_entry, *s_entry, *t_entry;
+      svn_fs_x__dirent_t *a_entry, *s_entry, *t_entry;
       dag_node_t *s_ent_node;
       apr_int64_t mergeinfo_s;
 
       svn_pool_clear(iterpool);
 
-      s_entry = APR_ARRAY_IDX(s_entries, i, svn_fs_dirent_t *);
+      s_entry = APR_ARRAY_IDX(s_entries, i, svn_fs_x__dirent_t *);
       a_entry = svn_fs_x__find_dir_entry(a_entries, s_entry->name, &s_idx);
       t_entry = svn_fs_x__find_dir_entry(t_entries, s_entry->name, &t_idx);
 
@@ -2016,13 +2051,13 @@ merge(svn_stringbuf_t *conflict_p,
                                              t_entry->name,
                                              iterpool));
 
-      SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs,
-                                     s_entry->id, iterpool));
+      SVN_ERR(svn_fs_x__dag_get_node(&s_ent_node, fs, &s_entry->id,
+                                     iterpool));
       SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_s, s_ent_node));
       mergeinfo_increment += mergeinfo_s;
 
       SVN_ERR(svn_fs_x__dag_set_entry
-              (target, s_entry->name, s_entry->id, s_entry->kind,
+              (target, s_entry->name, &s_entry->id, s_entry->kind,
                txn_id, iterpool));
     }
   svn_pool_destroy(iterpool);
@@ -2054,22 +2089,23 @@ merge_changes(dag_node_t *ancestor_node,
               dag_node_t *source_node,
               svn_fs_txn_t *txn,
               svn_stringbuf_t *conflict,
-              apr_pool_t *pool)
+              apr_pool_t *scratch_pool)
 {
   dag_node_t *txn_root_node;
   svn_fs_t *fs = txn->fs;
   svn_fs_x__txn_id_t txn_id = svn_fs_x__txn_get_id(txn);
+  svn_boolean_t related;
   
-  SVN_ERR(svn_fs_x__dag_txn_root(&txn_root_node, fs, txn_id, pool));
+  SVN_ERR(svn_fs_x__dag_txn_root(&txn_root_node, fs, txn_id, scratch_pool));
 
   if (ancestor_node == NULL)
     {
       SVN_ERR(svn_fs_x__dag_txn_base_root(&ancestor_node, fs,
-                                          txn_id, pool));
+                                          txn_id, scratch_pool));
     }
 
-  if (svn_fs_x__id_eq(svn_fs_x__dag_get_id(ancestor_node),
-                      svn_fs_x__dag_get_id(txn_root_node)))
+  SVN_ERR(svn_fs_x__dag_related_node(&related, ancestor_node, txn_root_node));
+  if (!related)
     {
       /* If no changes have been made in TXN since its current base,
          then it can't conflict with any changes since that base.
@@ -2078,7 +2114,7 @@ merge_changes(dag_node_t *ancestor_node,
     }
   else
     SVN_ERR(merge(conflict, "/", txn_root_node,
-                  source_node, ancestor_node, txn_id, NULL, pool));
+                  source_node, ancestor_node, txn_id, NULL, scratch_pool));
 
   return SVN_NO_ERROR;
 }
@@ -2133,7 +2169,7 @@ svn_fs_x__commit_txn(const char **confli
   svn_error_t *err = SVN_NO_ERROR;
   svn_stringbuf_t *conflict = svn_stringbuf_create_empty(pool);
   svn_fs_t *fs = txn->fs;
-  fs_x_data_t *ffd = fs->fsap_data;
+  svn_fs_x__data_t *ffd = fs->fsap_data;
 
   /* Limit memory usage when the repository has a high commit rate and
      needs to run the following while loop multiple times.  The memory
@@ -2294,7 +2330,7 @@ x_merge(const char **conflict_p,
 svn_error_t *
 svn_fs_x__deltify(svn_fs_t *fs,
                   svn_revnum_t revision,
-                  apr_pool_t *pool)
+                  apr_pool_t *scratch_pool)
 {
   /* Deltify is a no-op for fs_x. */
 
@@ -2308,7 +2344,7 @@ svn_fs_x__deltify(svn_fs_t *fs,
 /* Set *TABLE_P to a newly allocated APR hash table containing the
    entries of the directory at PATH in ROOT.  The keys of the table
    are entry names, as byte strings, excluding the final null
-   character; the table's values are pointers to svn_fs_dirent_t
+   character; the table's values are pointers to svn_fs_svn_fs_x__dirent_t
    structures.  Allocate the table and its contents in POOL. */
 static svn_error_t *
 x_dir_entries(apr_hash_t **table_p,
@@ -2320,16 +2356,27 @@ x_dir_entries(apr_hash_t **table_p,
   apr_hash_t *hash = svn_hash__make(pool);
   apr_array_header_t *table;
   int i;
+  svn_fs_x__id_context_t *context = NULL;
 
   /* Get the entries for this path in the caller's pool. */
-  SVN_ERR(get_dag(&node, root, path, FALSE, pool));
+  SVN_ERR(get_dag(&node, root, path, pool));
   SVN_ERR(svn_fs_x__dag_dir_entries(&table, node, pool));
 
+  if (table->nelts)
+    context = svn_fs_x__id_create_context(root->fs, pool);
+
   /* Convert directory array to hash. */
   for (i = 0; i < table->nelts; ++i)
     {
-      svn_fs_dirent_t *entry = APR_ARRAY_IDX(table, i, svn_fs_dirent_t *);
-      svn_hash_sets(hash, entry->name, entry);
+      svn_fs_x__dirent_t *entry
+        = APR_ARRAY_IDX(table, i, svn_fs_x__dirent_t *);
+
+      svn_fs_dirent_t *api_dirent = apr_pcalloc(pool, sizeof(*api_dirent));
+      api_dirent->name = entry->name;
+      api_dirent->kind = entry->kind;
+      api_dirent->id = svn_fs_x__id_create(context, &entry->id, pool);
+
+      svn_hash_sets(hash, api_dirent->name, api_dirent);
     }
 
   *table_p = hash;
@@ -2350,26 +2397,27 @@ x_dir_optimal_order(apr_array_header_t *
 /* Create a new directory named PATH in ROOT.  The new directory has
    no entries, and no properties.  ROOT must be the root of a
    transaction, not a revision.  Do any necessary temporary allocation
-   in POOL.  */
+   in SCRATCH_POOL.  */
 static svn_error_t *
 x_make_dir(svn_fs_root_t *root,
            const char *path,
-           apr_pool_t *pool)
+           apr_pool_t *scratch_pool)
 {
   parent_path_t *parent_path;
   dag_node_t *sub_dir;
   svn_fs_x__txn_id_t txn_id = root_txn_id(root);
+  apr_pool_t *subpool = svn_pool_create(scratch_pool);
 
-  path = svn_fs__canonicalize_abspath(path, pool);
+  path = svn_fs__canonicalize_abspath(path, subpool);
   SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
-                    TRUE, pool));
+                    TRUE, subpool));
 
   /* Check (recursively) to see if some lock is 'reserving' a path at
      that location, or even some child-path; if so, check that we can
      use it. */
   if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
     SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE,
-                                             pool));
+                                             subpool));
 
   /* If there's already a sub-directory by that name, complain.  This
      also catches the case of trying to make a subdirectory named `/'.  */
@@ -2377,44 +2425,48 @@ x_make_dir(svn_fs_root_t *root,
     return SVN_FS__ALREADY_EXISTS(root, path);
 
   /* Create the subdirectory.  */
-  SVN_ERR(make_path_mutable(root, parent_path->parent, path, pool));
+  SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool));
   SVN_ERR(svn_fs_x__dag_make_dir(&sub_dir,
                                  parent_path->parent->node,
                                  parent_path_path(parent_path->parent,
-                                                  pool),
+                                                  subpool),
                                  parent_path->entry,
                                  txn_id,
-                                 pool));
+                                 subpool));
 
   /* Add this directory to the path cache. */
-  SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, pool),
-                             sub_dir, pool));
+  SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, subpool),
+                             sub_dir, subpool));
 
   /* Make a record of this modification in the changes table. */
-  return add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(sub_dir),
-                    svn_fs_path_change_add, FALSE, FALSE, FALSE,
-                    svn_node_dir, SVN_INVALID_REVNUM, NULL, pool);
+  SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(sub_dir),
+                     svn_fs_path_change_add, FALSE, FALSE, FALSE,
+                     svn_node_dir, SVN_INVALID_REVNUM, NULL, subpool));
+
+  svn_pool_destroy(subpool);
+  return SVN_NO_ERROR;
 }
 
 
 /* Delete the node at PATH under ROOT.  ROOT must be a transaction
-   root.  Perform temporary allocations in POOL. */
+   root.  Perform temporary allocations in SCRATCH_POOL. */
 static svn_error_t *
 x_delete_node(svn_fs_root_t *root,
               const char *path,
-              apr_pool_t *pool)
+              apr_pool_t *scratch_pool)
 {
   parent_path_t *parent_path;
   svn_fs_x__txn_id_t txn_id;
   apr_int64_t mergeinfo_count = 0;
   svn_node_kind_t kind;
+  apr_pool_t *subpool = svn_pool_create(scratch_pool);
 
   if (! root->is_txn_root)
     return SVN_FS__NOT_TXN(root);
 
   txn_id = root_txn_id(root);
-  path = svn_fs__canonicalize_abspath(path, pool);
-  SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, pool));
+  path = svn_fs__canonicalize_abspath(path, subpool);
+  SVN_ERR(open_path(&parent_path, root, path, 0, TRUE, subpool));
   kind = svn_fs_x__dag_node_kind(parent_path->node);
 
   /* We can't remove the root of the filesystem.  */
@@ -2426,42 +2478,46 @@ x_delete_node(svn_fs_root_t *root,
      check that we can use the existing lock(s). */
   if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
     SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, TRUE, FALSE,
-                                             pool));
+                                             subpool));
 
   /* Make the parent directory mutable, and do the deletion.  */
-  SVN_ERR(make_path_mutable(root, parent_path->parent, path, pool));
+  SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool));
   SVN_ERR(svn_fs_x__dag_get_mergeinfo_count(&mergeinfo_count,
                                             parent_path->node));
   SVN_ERR(svn_fs_x__dag_delete(parent_path->parent->node,
                                parent_path->entry,
-                               txn_id, pool));
+                               txn_id, subpool));
 
   /* Remove this node and any children from the path cache. */
-  SVN_ERR(dag_node_cache_invalidate(root, parent_path_path(parent_path, pool),
-                                    pool));
+  SVN_ERR(dag_node_cache_invalidate(root, parent_path_path(parent_path,
+                                                           subpool),
+                                    subpool));
 
   /* Update mergeinfo counts for parents */
   if (mergeinfo_count > 0)
     SVN_ERR(increment_mergeinfo_up_tree(parent_path->parent,
                                         -mergeinfo_count,
-                                        pool));
+                                        subpool));
 
   /* Make a record of this modification in the changes table. */
-  return add_change(root->fs, txn_id, path,
-                    svn_fs_x__dag_get_id(parent_path->node),
-                    svn_fs_path_change_delete, FALSE, FALSE, FALSE, kind,
-                    SVN_INVALID_REVNUM, NULL, pool);
+  SVN_ERR(add_change(root->fs, txn_id, path,
+                     svn_fs_x__dag_get_id(parent_path->node),
+                     svn_fs_path_change_delete, FALSE, FALSE, FALSE, kind,
+                     SVN_INVALID_REVNUM, NULL, subpool));
+
+  svn_pool_destroy(subpool);
+  return SVN_NO_ERROR;
 }
 
 
 /* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE.
-   Use POOL for temporary allocation only.
+   Use SCRATCH_POOL for temporary allocation only.
    Note: this code is duplicated between libsvn_fs_x and libsvn_fs_base. */
 static svn_error_t *
 x_same_p(svn_boolean_t *same_p,
          svn_fs_t *fs1,
          svn_fs_t *fs2,
-         apr_pool_t *pool)
+         apr_pool_t *scratch_pool)
 {
   *same_p = ! strcmp(fs1->uuid, fs2->uuid);
   return SVN_NO_ERROR;
@@ -2469,14 +2525,14 @@ x_same_p(svn_boolean_t *same_p,
 
 /* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under
    TO_ROOT.  If PRESERVE_HISTORY is set, then the copy is recorded in
-   the copies table.  Perform temporary allocations in POOL. */
+   the copies table.  Perform temporary allocations in SCRATCH_POOL. */
 static svn_error_t *
 copy_helper(svn_fs_root_t *from_root,
             const char *from_path,
             svn_fs_root_t *to_root,
             const char *to_path,
             svn_boolean_t preserve_history,
-            apr_pool_t *pool)
+            apr_pool_t *scratch_pool)
 {
   dag_node_t *from_node;
   parent_path_t *to_parent_path;
@@ -2485,7 +2541,7 @@ copy_helper(svn_fs_root_t *from_root,
 
   /* Use an error check, not an assert, because even the caller cannot
      guarantee that a filesystem's UUID has not changed "on the fly". */
-  SVN_ERR(x_same_p(&same_p, from_root->fs, to_root->fs, pool));
+  SVN_ERR(x_same_p(&same_p, from_root->fs, to_root->fs, scratch_pool));
   if (! same_p)
     return svn_error_createf
       (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
@@ -2504,19 +2560,19 @@ copy_helper(svn_fs_root_t *from_root,
        _("Copy immutable tree not supported"));
 
   /* Get the NODE for FROM_PATH in FROM_ROOT.*/
-  SVN_ERR(get_dag(&from_node, from_root, from_path, TRUE, pool));
+  SVN_ERR(get_dag(&from_node, from_root, from_path, scratch_pool));
 
   /* Build up the parent path from TO_PATH in TO_ROOT.  If the last
      component does not exist, it's not that big a deal.  We'll just
      make one there. */
   SVN_ERR(open_path(&to_parent_path, to_root, to_path,
-                    open_path_last_optional, TRUE, pool));
+                    open_path_last_optional, TRUE, scratch_pool));
 
   /* Check to see if path (or any child thereof) is locked; if so,
      check that we can use the existing lock(s). */
   if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
     SVN_ERR(svn_fs_x__allow_locked_operation(to_path, to_root->fs,
-                                             TRUE, FALSE, pool));
+                                             TRUE, FALSE, scratch_pool));
 
   /* If the destination node already exists as the same node as the
      source (in other words, this operation would result in nothing
@@ -2553,10 +2609,10 @@ copy_helper(svn_fs_root_t *from_root,
 
       /* Make sure the target node's parents are mutable.  */
       SVN_ERR(make_path_mutable(to_root, to_parent_path->parent,
-                                to_path, pool));
+                                to_path, scratch_pool));
 
       /* Canonicalize the copyfrom path. */
-      from_canonpath = svn_fs__canonicalize_abspath(from_path, pool);
+      from_canonpath = svn_fs__canonicalize_abspath(from_path, scratch_pool);
 
       SVN_ERR(svn_fs_x__dag_copy(to_parent_path->parent->node,
                                  to_parent_path->entry,
@@ -2564,24 +2620,25 @@ copy_helper(svn_fs_root_t *from_root,
                                  preserve_history,
                                  from_root->rev,
                                  from_canonpath,
-                                 txn_id, pool));
+                                 txn_id, scratch_pool));
 
       if (kind != svn_fs_path_change_add)
         SVN_ERR(dag_node_cache_invalidate(to_root,
                                           parent_path_path(to_parent_path,
-                                                           pool), pool));
+                                                           scratch_pool),
+                                          scratch_pool));
 
       if (mergeinfo_start != mergeinfo_end)
         SVN_ERR(increment_mergeinfo_up_tree(to_parent_path->parent,
                                             mergeinfo_end - mergeinfo_start,
-                                            pool));
+                                            scratch_pool));
 
       /* Make a record of this modification in the changes table. */
-      SVN_ERR(get_dag(&new_node, to_root, to_path, TRUE, pool));
+      SVN_ERR(get_dag(&new_node, to_root, to_path, scratch_pool));
       SVN_ERR(add_change(to_root->fs, txn_id, to_path,
                          svn_fs_x__dag_get_id(new_node), kind, FALSE,
                          FALSE, FALSE, svn_fs_x__dag_node_kind(from_node),
-                         from_root->rev, from_canonpath, pool));
+                         from_root->rev, from_canonpath, scratch_pool));
     }
   else
     {
@@ -2606,39 +2663,50 @@ copy_helper(svn_fs_root_t *from_root,
 
 /* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
    If FROM_PATH is a directory, copy it recursively.  Temporary
-   allocations are from POOL.*/
+   allocations are from SCRATCH_POOL.*/
 static svn_error_t *
 x_copy(svn_fs_root_t *from_root,
        const char *from_path,
        svn_fs_root_t *to_root,
        const char *to_path,
-       apr_pool_t *pool)
+       apr_pool_t *scratch_pool)
 {
-  return svn_error_trace(copy_helper(from_root,
-                                     svn_fs__canonicalize_abspath(from_path,
-                                                                  pool),
-                                     to_root,
-                                     svn_fs__canonicalize_abspath(to_path,
-                                                                  pool),
-                                     TRUE, pool));
+  apr_pool_t *subpool = svn_pool_create(scratch_pool);
+
+  SVN_ERR(copy_helper(from_root,
+                      svn_fs__canonicalize_abspath(from_path, subpool),
+                      to_root, 
+                      svn_fs__canonicalize_abspath(to_path, subpool),
+                      TRUE, subpool));
+
+  svn_pool_destroy(subpool);
+
+  return SVN_NO_ERROR;
 }
 
 
 /* Create a copy of FROM_PATH in FROM_ROOT named TO_PATH in TO_ROOT.
    If FROM_PATH is a directory, copy it recursively.  No history is
-   preserved.  Temporary allocations are from POOL. */
+   preserved.  Temporary allocations are from SCRATCH_POOL. */
 static svn_error_t *
 x_revision_link(svn_fs_root_t *from_root,
                 svn_fs_root_t *to_root,
                 const char *path,
-                apr_pool_t *pool)
+                apr_pool_t *scratch_pool)
 {
+  apr_pool_t *subpool;
+
   if (! to_root->is_txn_root)
     return SVN_FS__NOT_TXN(to_root);
 
-  path = svn_fs__canonicalize_abspath(path, pool);
-  return svn_error_trace(copy_helper(from_root, path, to_root, path,
-                                     FALSE, pool));
+  subpool = svn_pool_create(scratch_pool);
+
+  path = svn_fs__canonicalize_abspath(path, subpool);
+  SVN_ERR(copy_helper(from_root, path, to_root, path, FALSE, subpool));
+
+  svn_pool_destroy(subpool);
+
+  return SVN_NO_ERROR;
 }
 
 
@@ -2656,7 +2724,7 @@ x_copied_from(svn_revnum_t *rev_p,
 
   /* There is no cached entry, look it up the old-fashioned
       way. */
-  SVN_ERR(get_dag(&node, root, path, TRUE, pool));
+  SVN_ERR(get_dag(&node, root, path, pool));
   SVN_ERR(svn_fs_x__dag_get_copyfrom_rev(rev_p, node));
   SVN_ERR(svn_fs_x__dag_get_copyfrom_path(path_p, node));
 
@@ -2668,19 +2736,20 @@ x_copied_from(svn_revnum_t *rev_p,
 /* Files.  */
 
 /* Create the empty file PATH under ROOT.  Temporary allocations are
-   in POOL. */
+   in SCRATCH_POOL. */
 static svn_error_t *
 x_make_file(svn_fs_root_t *root,
             const char *path,
-            apr_pool_t *pool)
+            apr_pool_t *scratch_pool)
 {
   parent_path_t *parent_path;
   dag_node_t *child;
   svn_fs_x__txn_id_t txn_id = root_txn_id(root);
+  apr_pool_t *subpool = svn_pool_create(scratch_pool);
 
-  path = svn_fs__canonicalize_abspath(path, pool);
+  path = svn_fs__canonicalize_abspath(path, subpool);
   SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
-                    TRUE, pool));
+                    TRUE, subpool));
 
   /* If there's already a file by that name, complain.
      This also catches the case of trying to make a file named `/'.  */
@@ -2691,44 +2760,47 @@ x_make_file(svn_fs_root_t *root,
      that we can use it. */
   if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
     SVN_ERR(svn_fs_x__allow_locked_operation(path, root->fs, FALSE, FALSE,
-                                             pool));
+                                             subpool));
 
   /* Create the file.  */
-  SVN_ERR(make_path_mutable(root, parent_path->parent, path, pool));
+  SVN_ERR(make_path_mutable(root, parent_path->parent, path, subpool));
   SVN_ERR(svn_fs_x__dag_make_file(&child,
                                   parent_path->parent->node,
                                   parent_path_path(parent_path->parent,
-                                                   pool),
+                                                   subpool),
                                   parent_path->entry,
                                   txn_id,
-                                  pool));
+                                  subpool));
 
   /* Add this file to the path cache. */
-  SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, pool), child,
-                             pool));
+  SVN_ERR(dag_node_cache_set(root, parent_path_path(parent_path, subpool),
+                             child, subpool));
 
   /* Make a record of this modification in the changes table. */
-  return add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(child),
-                    svn_fs_path_change_add, TRUE, FALSE, FALSE,
-                    svn_node_file, SVN_INVALID_REVNUM, NULL, pool);
+  SVN_ERR(add_change(root->fs, txn_id, path, svn_fs_x__dag_get_id(child),
+                     svn_fs_path_change_add, TRUE, FALSE, FALSE,
+                     svn_node_file, SVN_INVALID_REVNUM, NULL, subpool));
+
+  svn_pool_destroy(subpool);
+  return SVN_NO_ERROR;
 }
 
 
 /* Set *LENGTH_P to the size of the file PATH under ROOT.  Temporary
-   allocations are in POOL. */
+   allocations are in SCRATCH_POOL. */
 static svn_error_t *
 x_file_length(svn_filesize_t *length_p,
               svn_fs_root_t *root,
               const char *path,
-              apr_pool_t *pool)
+              apr_pool_t *scratch_pool)
 {
   dag_node_t *file;
 
   /* First create a dag_node_t from the root/path pair. */
-  SVN_ERR(get_dag(&file, root, path, FALSE, pool));
+  SVN_ERR(get_dag(&file, root, path, scratch_pool));
 
   /* Now fetch its length */
-  return svn_fs_x__dag_file_length(length_p, file, pool);
+  return svn_fs_x__dag_file_length(length_p, file);
 }
 
 
@@ -2744,7 +2816,7 @@ x_file_checksum(svn_checksum_t **checksu
 {
   dag_node_t *file;
 
-  SVN_ERR(get_dag(&file, root, path, FALSE, pool));
+  SVN_ERR(get_dag(&file, root, path, pool));
   return svn_fs_x__dag_file_checksum(checksum, file, kind, pool);
 }
 
@@ -2763,7 +2835,7 @@ x_file_contents(svn_stream_t **contents,
   svn_stream_t *file_stream;
 
   /* First create a dag_node_t from the root/path pair. */
-  SVN_ERR(get_dag(&node, root, path, FALSE, pool));
+  SVN_ERR(get_dag(&node, root, path, pool));
 
   /* Then create a readable stream from the dag_node_t. */
   SVN_ERR(svn_fs_x__dag_get_contents(&file_stream, node, pool));
@@ -2786,7 +2858,7 @@ x_try_process_file_contents(svn_boolean_
                             apr_pool_t *pool)
 {
   dag_node_t *node;
-  SVN_ERR(get_dag(&node, root, path, FALSE, pool));
+  SVN_ERR(get_dag(&node, root, path, pool));
 
   return svn_fs_x__dag_try_process_file_contents(success, node,
                                                  processor, baton, pool);
@@ -2853,7 +2925,8 @@ window_consumer(svn_txdelta_window_t *wi
 /* Helper function for fs_apply_textdelta.  BATON is of type
    txdelta_baton_t. */
 static svn_error_t *
-apply_textdelta(void *baton, apr_pool_t *pool)
+apply_textdelta(void *baton,
+                apr_pool_t *scratch_pool)
 {
   txdelta_baton_t *tb = (txdelta_baton_t *) baton;
   parent_path_t *parent_path;
@@ -2861,17 +2934,17 @@ apply_textdelta(void *baton, apr_pool_t
 
   /* Call open_path with no flags, as we want this to return an error
      if the node for which we are searching doesn't exist. */
-  SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, pool));
+  SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, scratch_pool));
 
   /* Check (non-recursively) to see if path is locked; if so, check
      that we can use it. */
   if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
     SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs,
-                                             FALSE, FALSE, pool));
+                                             FALSE, FALSE, scratch_pool));
 
   /* Now, make sure this path is mutable. */
-  SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, pool));
-  tb->node = parent_path->node;
+  SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, scratch_pool));
+  tb->node = svn_fs_x__dag_dup(parent_path->node, tb->pool);
 
   if (tb->base_checksum)
     {
@@ -2880,9 +2953,11 @@ apply_textdelta(void *baton, apr_pool_t
       /* Until we finalize the node, its data_key points to the old
          contents, in other words, the base text. */
       SVN_ERR(svn_fs_x__dag_file_checksum(&checksum, tb->node,
-                                          tb->base_checksum->kind, pool));
+                                          tb->base_checksum->kind,
+                                          scratch_pool));
       if (!svn_checksum_match(tb->base_checksum, checksum))
-        return svn_checksum_mismatch_err(tb->base_checksum, checksum, pool,
+        return svn_checksum_mismatch_err(tb->base_checksum, checksum,
+                                         scratch_pool,
                                          _("Base checksum mismatch on '%s'"),
                                          tb->path);
     }
@@ -2910,7 +2985,7 @@ apply_textdelta(void *baton, apr_pool_t
   return add_change(tb->root->fs, txn_id, tb->path,
                     svn_fs_x__dag_get_id(tb->node),
                     svn_fs_path_change_modify, TRUE, FALSE, FALSE,
-                    svn_node_file, SVN_INVALID_REVNUM, NULL, pool);
+                    svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool);
 }
 
 
@@ -2926,6 +3001,7 @@ x_apply_textdelta(svn_txdelta_window_han
                   svn_checksum_t *result_checksum,
                   apr_pool_t *pool)
 {
+  apr_pool_t *subpool = svn_pool_create(pool);
   txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
 
   tb->root = root;
@@ -2934,10 +3010,12 @@ x_apply_textdelta(svn_txdelta_window_han
   tb->base_checksum = svn_checksum_dup(base_checksum, pool);
   tb->result_checksum = svn_checksum_dup(result_checksum, pool);
 
-  SVN_ERR(apply_textdelta(tb, pool));
+  SVN_ERR(apply_textdelta(tb, subpool));
 
   *contents_p = window_consumer;
   *contents_baton_p = tb;
+
+  svn_pool_destroy(subpool);
   return SVN_NO_ERROR;
 }
 
@@ -3012,7 +3090,8 @@ text_stream_closer(void *baton)
 /* Helper function for fs_apply_text.  BATON is of type
    text_baton_t. */
 static svn_error_t *
-apply_text(void *baton, apr_pool_t *pool)
+apply_text(void *baton,
+           apr_pool_t *scratch_pool)
 {
   struct text_baton_t *tb = baton;
   parent_path_t *parent_path;
@@ -3020,21 +3099,21 @@ apply_text(void *baton, apr_pool_t *pool
 
   /* Call open_path with no flags, as we want this to return an error
      if the node for which we are searching doesn't exist. */
-  SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, pool));
+  SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, TRUE, scratch_pool));
 
   /* Check (non-recursively) to see if path is locked; if so, check
      that we can use it. */
   if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
     SVN_ERR(svn_fs_x__allow_locked_operation(tb->path, tb->root->fs,
-                                             FALSE, FALSE, pool));
+                                             FALSE, FALSE, scratch_pool));
 
   /* Now, make sure this path is mutable. */
-  SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, pool));
-  tb->node = parent_path->node;
+  SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path, scratch_pool));
+  tb->node = svn_fs_x__dag_dup(parent_path->node, tb->pool);
 
   /* Make a writable stream for replacing the file's text. */
   SVN_ERR(svn_fs_x__dag_get_edit_stream(&(tb->file_stream), tb->node,
-                                         tb->pool));
+                                        tb->pool));
 
   /* Create a 'returnable' stream which writes to the file_stream. */
   tb->stream = svn_stream_create(tb, tb->pool);
@@ -3045,7 +3124,7 @@ apply_text(void *baton, apr_pool_t *pool
   return add_change(tb->root->fs, txn_id, tb->path,
                     svn_fs_x__dag_get_id(tb->node),
                     svn_fs_path_change_modify, TRUE, FALSE, FALSE,
-                    svn_node_file, SVN_INVALID_REVNUM, NULL, pool);
+                    svn_node_file, SVN_INVALID_REVNUM, NULL, scratch_pool);
 }
 
 
@@ -3059,6 +3138,7 @@ x_apply_text(svn_stream_t **contents_p,
              svn_checksum_t *result_checksum,
              apr_pool_t *pool)
 {
+  apr_pool_t *subpool = svn_pool_create(pool);
   struct text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
 
   tb->root = root;
@@ -3066,9 +3146,11 @@ x_apply_text(svn_stream_t **contents_p,
   tb->pool = pool;
   tb->result_checksum = svn_checksum_dup(result_checksum, pool);
 
-  SVN_ERR(apply_text(tb, pool));
+  SVN_ERR(apply_text(tb, subpool));
 
   *contents_p = tb->stream;
+
+  svn_pool_destroy(subpool);
   return SVN_NO_ERROR;
 }
 
@@ -3085,9 +3167,10 @@ x_contents_changed(svn_boolean_t *change
                    svn_fs_root_t *root2,
                    const char *path2,
                    svn_boolean_t strict,
-                   apr_pool_t *pool)
+                   apr_pool_t *scratch_pool)
 {
   dag_node_t *node1, *node2;
+  apr_pool_t *subpool = svn_pool_create(scratch_pool);
 
   /* Check that roots are in the same fs. */
   if (root1->fs != root2->fs)
@@ -3099,21 +3182,24 @@ x_contents_changed(svn_boolean_t *change
   {
     svn_node_kind_t kind;
 
-    SVN_ERR(svn_fs_x__check_path(&kind, root1, path1, pool));
+    SVN_ERR(svn_fs_x__check_path(&kind, root1, path1, subpool));
     if (kind != svn_node_file)
       return svn_error_createf
         (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1);
 
-    SVN_ERR(svn_fs_x__check_path(&kind, root2, path2, pool));
+    SVN_ERR(svn_fs_x__check_path(&kind, root2, path2, subpool));
     if (kind != svn_node_file)
       return svn_error_createf
         (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
   }
 
-  SVN_ERR(get_dag(&node1, root1, path1, TRUE, pool));
-  SVN_ERR(get_dag(&node2, root2, path2, TRUE, pool));
-  return svn_fs_x__dag_things_different(NULL, changed_p, node1, node2,
-                                        strict, pool);
+  SVN_ERR(get_dag(&node1, root1, path1, subpool));
+  SVN_ERR(get_dag(&node2, root2, path2, subpool));
+  SVN_ERR(svn_fs_x__dag_things_different(NULL, changed_p, node1, node2,
+                                         strict, subpool));
+
+  svn_pool_destroy(subpool);
+  return SVN_NO_ERROR;
 }
 
 
@@ -3129,22 +3215,55 @@ x_get_file_delta_stream(svn_txdelta_stre
                         apr_pool_t *pool)
 {
   dag_node_t *source_node, *target_node;
+  apr_pool_t *subpool = svn_pool_create(pool);
 
   if (source_root && source_path)
-    SVN_ERR(get_dag(&source_node, source_root, source_path, TRUE, pool));
+    SVN_ERR(get_dag(&source_node, source_root, source_path, pool));
   else
     source_node = NULL;
-  SVN_ERR(get_dag(&target_node, target_root, target_path, TRUE, pool));
+  SVN_ERR(get_dag(&target_node, target_root, target_path, pool));
 
   /* Create a delta stream that turns the source into the target.  */
-  return svn_fs_x__dag_get_file_delta_stream(stream_p, source_node,
-                                             target_node, pool);
+  SVN_ERR(svn_fs_x__dag_get_file_delta_stream(stream_p, source_node,
+                                              target_node, pool));
+
+  svn_pool_destroy(subpool);
+  return SVN_NO_ERROR;
 }
 
 
 
 /* Finding Changes */
 
+/* Copy CHANGE into a FS API object allocated in RESULT_POOL and return
+   it in *RESULT_P.  Pass CONTEXT to the ID API object being created. */
+static svn_error_t *
+construct_fs_path_change(svn_fs_path_change2_t **result_p,
+                         svn_fs_x__id_context_t *context,
+                         svn_fs_x__change_t *change,
+                         apr_pool_t *result_pool)
+{
+  const svn_fs_id_t *id
+    = svn_fs_x__id_create(context, &change->noderev_id, result_pool);
+  svn_fs_path_change2_t *result
+    = svn_fs__path_change_create_internal(id, change->change_kind,
+                                          result_pool);
+
+  result->text_mod = change->text_mod;
+  result->prop_mod = change->prop_mod;
+  result->node_kind = change->node_kind;
+
+  result->copyfrom_known = change->copyfrom_known;
+  result->copyfrom_rev = change->copyfrom_rev;
+  result->copyfrom_path = change->copyfrom_path;
+
+  result->mergeinfo_mod = change->mergeinfo_mod;
+
+  *result_p = result;
+
+  return SVN_NO_ERROR;
+}
+
 /* Set *CHANGED_PATHS_P to a newly allocated hash containing
    descriptions of the paths changed under ROOT.  The hash is keyed
    with const char * paths and has svn_fs_path_change2_t * values.  Use
@@ -3154,12 +3273,50 @@ x_paths_changed(apr_hash_t **changed_pat
                 svn_fs_root_t *root,
                 apr_pool_t *pool)
 {
+  apr_hash_t *changed_paths;
+  svn_fs_path_change2_t *path_change;
+  svn_fs_x__id_context_t *context
+    = svn_fs_x__id_create_context(root->fs, pool);
+
   if (root->is_txn_root)
-    return svn_fs_x__txn_changes_fetch(changed_paths_p, root->fs,
-                                       root_txn_id(root), pool);
+    {
+      apr_hash_index_t *hi;
+      SVN_ERR(svn_fs_x__txn_changes_fetch(&changed_paths, root->fs,
+                                          root_txn_id(root), pool));
+      for (hi = apr_hash_first(pool, changed_paths);
+           hi;
+           hi = apr_hash_next(hi))
+        {
+          svn_fs_x__change_t *change = apr_hash_this_val(hi);
+          SVN_ERR(construct_fs_path_change(&path_change, context, change,
+                                           pool));
+          apr_hash_set(changed_paths,
+                       apr_hash_this_key(hi), apr_hash_this_key_len(hi),
+                       path_change);
+        }
+    }
   else
-    return svn_fs_x__paths_changed(changed_paths_p, root->fs, root->rev,
-                                   pool);
+    {
+      apr_array_header_t *changes;
+      int i;
+
+      SVN_ERR(svn_fs_x__get_changes(&changes, root->fs, root->rev, pool));
+
+      changed_paths = svn_hash__make(pool);
+      for (i = 0; i < changes->nelts; ++i)
+        {
+          svn_fs_x__change_t *change = APR_ARRAY_IDX(changes, i,
+                                                     svn_fs_x__change_t *);
+          SVN_ERR(construct_fs_path_change(&path_change, context, change,
+                                           pool));
+          apr_hash_set(changed_paths, change->path.data, change->path.len,
+                       path_change);
+        }
+    }
+
+  *changed_paths_p = changed_paths;
+
+  return SVN_NO_ERROR;
 }
 
 
@@ -3220,14 +3377,12 @@ x_node_history(svn_fs_history_t **histor
 }
 
 /* Find the youngest copyroot for path PARENT_PATH or its parents in
-   filesystem FS, and store the copyroot in *REV_P and *PATH_P.
-   Perform all allocations in POOL. */
+   filesystem FS, and store the copyroot in *REV_P and *PATH_P. */
 static svn_error_t *
 find_youngest_copyroot(svn_revnum_t *rev_p,
                        const char **path_p,
                        svn_fs_t *fs,
-                       parent_path_t *parent_path,
-                       apr_pool_t *pool)
+                       parent_path_t *parent_path)
 {
   svn_revnum_t rev_mine;
   svn_revnum_t rev_parent = SVN_INVALID_REVNUM;
@@ -3237,7 +3392,7 @@ find_youngest_copyroot(svn_revnum_t *rev
   /* First find our parent's youngest copyroot. */
   if (parent_path->parent)
     SVN_ERR(find_youngest_copyroot(&rev_parent, &path_parent, fs,
-                                   parent_path->parent, pool));
+                                   parent_path->parent));
 
   /* Find our copyroot. */
   SVN_ERR(svn_fs_x__dag_get_copyroot(&rev_mine, &path_mine,
@@ -3261,12 +3416,12 @@ find_youngest_copyroot(svn_revnum_t *rev
 }
 
 
-static
-svn_error_t *x_closest_copy(svn_fs_root_t **root_p,
-                            const char **path_p,
-                            svn_fs_root_t *root,
-                            const char *path,
-                            apr_pool_t *pool)
+static svn_error_t *
+x_closest_copy(svn_fs_root_t **root_p,
+               const char **path_p,
+               svn_fs_root_t *root,
+               const char *path,
+               apr_pool_t *pool)
 {
   svn_fs_t *fs = root->fs;
   parent_path_t *parent_path, *copy_dst_parent_path;
@@ -3274,35 +3429,48 @@ svn_error_t *x_closest_copy(svn_fs_root_
   const char *copy_dst_path;
   svn_fs_root_t *copy_dst_root;
   dag_node_t *copy_dst_node;
+  svn_boolean_t related;
+  apr_pool_t *subpool = svn_pool_create(pool);
 
   /* Initialize return values. */
   *root_p = NULL;
   *path_p = NULL;
 
-  path = svn_fs__canonicalize_abspath(path, pool);
-  SVN_ERR(open_path(&parent_path, root, path, 0, FALSE, pool));
+  path = svn_fs__canonicalize_abspath(path, subpool);
+  SVN_ERR(open_path(&parent_path, root, path, 0, FALSE, subpool));
 
   /* Find the youngest copyroot in the path of this node-rev, which
      will indicate the target of the innermost copy affecting the
      node-rev. */
   SVN_ERR(find_youngest_copyroot(&copy_dst_rev, &copy_dst_path,
-                                 fs, parent_path, pool));
+                                 fs, parent_path));
   if (copy_dst_rev == 0)  /* There are no copies affecting this node-rev. */
-    return SVN_NO_ERROR;
+    {
+      svn_pool_destroy(subpool);
+      return SVN_NO_ERROR;
+    }
 
   /* It is possible that this node was created from scratch at some
      revision between COPY_DST_REV and REV.  Make sure that PATH
      exists as of COPY_DST_REV and is related to this node-rev. */
   SVN_ERR(svn_fs_x__revision_root(&copy_dst_root, fs, copy_dst_rev, pool));

[... 343 lines stripped ...]