You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2010/10/30 16:20:18 UTC

svn commit: r1029078 [3/5] - in /subversion/branches/performance: ./ build/ac-macros/ subversion/bindings/javahl/native/ subversion/bindings/javahl/src/org/apache/subversion/javahl/ subversion/bindings/javahl/src/org/tigris/subversion/javahl/ subversio...

Modified: subversion/branches/performance/subversion/libsvn_wc/wc_db.c
URL: http://svn.apache.org/viewvc/subversion/branches/performance/subversion/libsvn_wc/wc_db.c?rev=1029078&r1=1029077&r2=1029078&view=diff
==============================================================================
--- subversion/branches/performance/subversion/libsvn_wc/wc_db.c (original)
+++ subversion/branches/performance/subversion/libsvn_wc/wc_db.c Sat Oct 30 14:20:17 2010
@@ -55,6 +55,9 @@
 #define NOT_IMPLEMENTED() SVN__NOT_IMPLEMENTED()
 
 
+/* #define TREE_CONFLICTS_ON_CHILDREN */
+
+
 /*
  * Some filename constants.
  */
@@ -101,6 +104,7 @@
  * wc_id  a WCROOT id associated with a node
  */
 
+#define INVALID_REPOS_ID ((apr_int64_t) -1)
 #define UNKNOWN_WC_ID ((apr_int64_t) -1)
 #define FORMAT_FROM_SDB (-1)
 
@@ -229,6 +233,8 @@ static svn_error_t *
 insert_incomplete_children(svn_sqlite__db_t *sdb,
                            apr_int64_t wc_id,
                            const char *local_relpath,
+                           apr_int64_t repos_id,
+                           const char *repos_relpath,
                            svn_revnum_t revision,
                            const apr_array_header_t *children,
                            apr_int64_t op_depth,
@@ -276,6 +282,29 @@ elide_copyfrom(svn_wc__db_pdh_t *pdh,
                const char *local_relpath,
                apr_pool_t *scratch_pool);
 
+static svn_error_t *
+scan_addition(svn_wc__db_status_t *status,
+              const char **op_root_relpath,
+              const char **repos_relpath,
+              apr_int64_t *repos_id,
+              const char **original_repos_relpath,
+              apr_int64_t *original_repos_id,
+              svn_revnum_t *original_revision,
+              svn_wc__db_pdh_t *pdh,
+              const char *local_relpath,
+              apr_pool_t *result_pool,
+              apr_pool_t *scratch_pool);
+
+static svn_error_t *
+scan_deletion(const char **base_del_relpath,
+              svn_boolean_t *base_replaced,
+              const char **moved_to_relpath,
+              const char **work_del_relpath,
+              svn_wc__db_pdh_t *pdh,
+              const char *local_relpath,
+              apr_pool_t *result_pool,
+              apr_pool_t *scratch_pool);
+
 
 /* Return the absolute path, in local path style, of LOCAL_RELPATH in WCROOT. */
 static const char *
@@ -371,16 +400,14 @@ escape_sqlite_like(const char * const st
  * operator, in order to match any path that is a child of LOCAL_RELPATH
  * (at any depth below LOCAL_RELPATH), *excluding* LOCAL_RELPATH itself.
  * LOCAL_RELPATH may be the empty string, in which case the result will
- * match any path *including* the empty path.
- *
- * ### Inconsistent on whether the match includes LOCAL_RELPATH itself.
+ * match any path except the empty path.
  *
  * Allocate the result either statically or in RESULT_POOL.  */
 static const char *construct_like_arg(const char *local_relpath,
                                       apr_pool_t *result_pool)
 {
   if (local_relpath[0] == '\0')
-    return "%";
+    return "_%";
 
   return apr_pstrcat(result_pool,
                      escape_sqlite_like(local_relpath, result_pool),
@@ -540,9 +567,8 @@ repos_location_from_columns(const char *
 }
 
 
-/* Scan from LOCAL_RELPATH upwards through parent nodes until we find a parent
-   that has values in the 'repos_id' and 'repos_relpath' columns.  Return
-   that information in REPOS_ID and REPOS_RELPATH (either may be NULL). */
+/* Set *REPOS_ID and *REPOS_RELPATH to the BASE node of LOCAL_RELPATH.
+ * Either of REPOS_ID and REPOS_RELPATH may be NULL if not wanted. */
 static svn_error_t *
 scan_upwards_for_repos(apr_int64_t *repos_id,
                        const char **repos_relpath,
@@ -551,92 +577,34 @@ scan_upwards_for_repos(apr_int64_t *repo
                        apr_pool_t *result_pool,
                        apr_pool_t *scratch_pool)
 {
-  const char *relpath_suffix = "";
-  const char *current_basename = svn_dirent_basename(local_relpath, NULL);
-  const char *current_relpath = local_relpath;
   svn_sqlite__stmt_t *stmt;
+  svn_boolean_t have_row;
 
   SVN_ERR_ASSERT(wcroot->sdb != NULL && wcroot->wc_id != UNKNOWN_WC_ID);
   SVN_ERR_ASSERT(repos_id != NULL || repos_relpath != NULL);
 
   SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb, STMT_SELECT_BASE_NODE));
-  while (TRUE)
-    {
-      svn_boolean_t have_row;
-
-      /* Get the current node's repository information.  */
-      SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, current_relpath));
-      SVN_ERR(svn_sqlite__step(&have_row, stmt));
-
-      if (!have_row)
-        {
-          svn_error_t *err;
-
-          /* If we moved upwards at least once, or we're looking at the
-             root directory of this WCROOT, then something is wrong.  */
-          if (*relpath_suffix != '\0' || *local_relpath == '\0')
-            {
-              err = svn_error_createf(
-                SVN_ERR_WC_CORRUPT, NULL,
-                _("Parent(s) of '%s' should have been present."),
-                path_for_error_message(wcroot, local_relpath, scratch_pool));
-            }
-          else
-            {
-              err = svn_error_createf(
-                SVN_ERR_WC_PATH_NOT_FOUND, NULL,
-                _("The node '%s' was not found."),
-                path_for_error_message(wcroot, local_relpath, scratch_pool));
-            }
-
-          return svn_error_compose_create(err, svn_sqlite__reset(stmt));
-        }
-
-      /* Did we find some non-NULL repository columns? */
-      if (!svn_sqlite__column_is_null(stmt, 0))
-        {
-          /* If one is non-NULL, then so should the other. */
-          SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 1));
-
-          if (repos_id)
-            *repos_id = svn_sqlite__column_int64(stmt, 0);
-
-          /* Given the node's relpath, append all the segments that
-             we stripped as we scanned upwards. */
-          if (repos_relpath)
-            *repos_relpath = svn_relpath_join(svn_sqlite__column_text(stmt, 1,
-                                                                      NULL),
-                                              relpath_suffix,
-                                              result_pool);
-          return svn_sqlite__reset(stmt);
-        }
-
-      SVN_ERR(svn_sqlite__reset(stmt));
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+  SVN_ERR(svn_sqlite__step(&have_row, stmt));
 
-      if (*current_relpath == '\0')
-        {
-          /* We scanned all the way up, and did not find the information.
-             Something is corrupt in the database. */
-          return svn_error_createf(
-            SVN_ERR_WC_CORRUPT, NULL,
-            _("Parent(s) of '%s' should have repository information."),
+  if (!have_row)
+    {
+      svn_error_t *err = svn_error_createf(
+            SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+            _("The node '%s' was not found."),
             path_for_error_message(wcroot, local_relpath, scratch_pool));
-        }
 
-      /* Strip a path segment off the end, and append it to the suffix
-         that we'll use when we finally find a base relpath.  */
-      svn_relpath_split(&current_relpath, &current_basename, current_relpath,
-                        scratch_pool);
-      relpath_suffix = svn_relpath_join(relpath_suffix, current_basename,
-                                        scratch_pool);
+      return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+    }
 
-      /* Loop to try the parent.  */
+  SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 0));
+  SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 1));
 
-      /* ### strictly speaking, moving to the parent could send us to a
-         ### different SDB, and (thus) we would need to fetch STMT again.
-         ### but we happen to know the parent is *always* in the same db,
-         ### and will have the repos info.  */
-    }
+  if (repos_id)
+    *repos_id = svn_sqlite__column_int64(stmt, 0);
+  if (repos_relpath)
+    *repos_relpath = svn_sqlite__column_text(stmt, 1, result_pool);
+  return svn_sqlite__reset(stmt);
 }
 
 
@@ -754,6 +722,9 @@ insert_base_node(void *baton, svn_sqlite
     (*pibb->local_relpath == '\0') ? NULL
     : svn_relpath_dirname(pibb->local_relpath, scratch_pool);
 
+  SVN_ERR_ASSERT(pibb->repos_id != INVALID_REPOS_ID);
+  SVN_ERR_ASSERT(pibb->repos_relpath != NULL);
+
   /* ### we can't handle this right now  */
   SVN_ERR_ASSERT(pibb->conflict == NULL);
 
@@ -795,6 +766,8 @@ insert_base_node(void *baton, svn_sqlite
   if (pibb->kind == svn_wc__db_kind_dir && pibb->children)
     SVN_ERR(insert_incomplete_children(sdb, pibb->wc_id,
                                        pibb->local_relpath,
+                                       pibb->repos_id,
+                                       pibb->repos_relpath,
                                        pibb->revision,
                                        pibb->children,
                                        0 /* BASE */,
@@ -818,11 +791,6 @@ blank_iwb(insert_working_baton_t *piwb)
 }
 
 
-struct relpath_op_depth_t {
-  const char *local_relpath;
-  apr_int64_t op_depth;
-};
-
 /* Copy the row specified by BATON->(wc_id,local_relpath) from BASE to
  * WORKING, changing its 'presence' and 'op_depth' to the values in BATON. */
 static svn_error_t *
@@ -832,11 +800,9 @@ copy_working_from_base(void *baton,
 {
   const insert_working_baton_t *piwb = baton;
   svn_sqlite__stmt_t *stmt;
-
+#ifdef SVN_WC__OP_DEPTH
   const char *like_arg;
-  svn_boolean_t have_row;
-  apr_array_header_t *nodes;
-  int i;
+#endif
 
   SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
                          STMT_INSERT_WORKING_NODE_FROM_BASE));
@@ -846,9 +812,10 @@ copy_working_from_base(void *baton,
                             presence_map, piwb->presence));
   SVN_ERR(svn_sqlite__insert(NULL, stmt));
 
-  /* Need to update the op_depth of all deleted children. A single
-     query can locate all the rows, but not update them, so we fall
-     back on one update per row.
+#ifdef SVN_WC__OP_DEPTH
+  /* Need to update the op_depth of all deleted child trees -- this
+     relies on the recursion having already deleted the trees so
+     that they are all at op_depth+1.
 
      ### Rewriting the op_depth means that the number of queries is
      ### O(depth^2).  Fix it by implementing svn_wc__db_op_delete so
@@ -857,37 +824,11 @@ copy_working_from_base(void *baton,
      ### only gets written once. */
   like_arg = construct_like_arg(piwb->local_relpath, scratch_pool);
   SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
-                                    STMT_SELECT_WORKING_OP_DEPTH_RECURSIVE));
-  SVN_ERR(svn_sqlite__bindf(stmt, "is", piwb->wc_id, like_arg));
-  SVN_ERR(svn_sqlite__step(&have_row, stmt));
-  nodes = apr_array_make(scratch_pool, 10, sizeof(struct relpath_op_depth_t *));
-  while (have_row)
-    {
-      int op_depth = svn_sqlite__column_int64(stmt, 1);
-      svn_wc__db_status_t status = svn_sqlite__column_token(stmt, 2,
-                                                            presence_map);
-      if (status != svn_wc__db_status_excluded)
-        {
-          struct relpath_op_depth_t *rod = apr_palloc(scratch_pool,
-                                                      sizeof(*rod));
-          rod->local_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool);
-          rod->op_depth = op_depth;
-          APR_ARRAY_PUSH(nodes, struct relpath_op_depth_t *) = rod;
-        }
-      SVN_ERR(svn_sqlite__step(&have_row, stmt));
-    }
-  SVN_ERR(svn_sqlite__reset(stmt));
-  for (i = 0; i < nodes->nelts; ++i)
-    {
-      struct relpath_op_depth_t *rod
-        = APR_ARRAY_IDX(nodes, i, struct relpath_op_depth_t *);
-
-      SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_UPDATE_OP_DEPTH));
-      SVN_ERR(svn_sqlite__bindf(stmt, "isii",
-                                piwb->wc_id, rod->local_relpath, rod->op_depth,
-                                piwb->op_depth));
-      SVN_ERR(svn_sqlite__update(NULL, stmt));
-    }
+                                    STMT_UPDATE_OP_DEPTH_RECURSIVE));
+  SVN_ERR(svn_sqlite__bindf(stmt, "isi",
+                            piwb->wc_id, like_arg, piwb->op_depth));
+  SVN_ERR(svn_sqlite__update(NULL, stmt));
+#endif
 
   return SVN_NO_ERROR;
 }
@@ -895,13 +836,17 @@ copy_working_from_base(void *baton,
 
 /* Insert a row in NODES for each (const char *) child name in CHILDREN,
    whose parent directory is LOCAL_RELPATH, at op_depth=OP_DEPTH.  Set each
-   child's presence to 'incomplete', kind to 'unknown', and revision to
-   REVISION (which should match the parent's revision).  The child's
-   repos_id and repos_relpath will be inherited from the parent. */
+   child's presence to 'incomplete', kind to 'unknown', repos_id to REPOS_ID,
+   repos_path by appending the child name to REPOS_PATH, and revision to
+   REVISION (which should match the parent's revision).
+
+   If REPOS_ID is INVALID_REPOS_ID, set each child's repos_id to null. */
 static svn_error_t *
 insert_incomplete_children(svn_sqlite__db_t *sdb,
                            apr_int64_t wc_id,
                            const char *local_relpath,
+                           apr_int64_t repos_id,
+                           const char *repos_path,
                            svn_revnum_t revision,
                            const apr_array_header_t *children,
                            apr_int64_t op_depth,
@@ -910,6 +855,10 @@ insert_incomplete_children(svn_sqlite__d
   svn_sqlite__stmt_t *stmt;
   int i;
 
+  SVN_ERR_ASSERT(repos_path != NULL || op_depth > 0);
+  SVN_ERR_ASSERT((repos_id != INVALID_REPOS_ID)
+                 == (repos_path != NULL));
+
   SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE));
 
   for (i = children->nelts; i--; )
@@ -926,6 +875,14 @@ insert_incomplete_children(svn_sqlite__d
                                 "incomplete", /* 8, presence */
                                 "unknown"));  /* 10, kind */
 
+      if (repos_id != INVALID_REPOS_ID)
+        {
+          SVN_ERR(svn_sqlite__bind_int64(stmt, 5, repos_id));
+          SVN_ERR(svn_sqlite__bind_text(stmt, 6,
+                                        svn_relpath_join(repos_path, name,
+                                                         scratch_pool)));
+        }
+
       SVN_ERR(svn_sqlite__insert(NULL, stmt));
     }
 
@@ -942,13 +899,11 @@ insert_working_node(void *baton,
   const char *parent_relpath;
   svn_sqlite__stmt_t *stmt;
 
+  SVN_ERR_ASSERT(piwb->op_depth > 0);
+
   /* We cannot insert a WORKING_NODE row at the wcroot.  */
-  /* ### actually, with per-dir DB, we can... */
   SVN_ERR_ASSERT(*piwb->local_relpath != '\0');
-  if (*piwb->local_relpath == '\0')
-    parent_relpath = NULL;
-  else
-    parent_relpath = svn_relpath_dirname(piwb->local_relpath, scratch_pool);
+  parent_relpath = svn_relpath_dirname(piwb->local_relpath, scratch_pool);
 
   SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_INSERT_NODE));
   SVN_ERR(svn_sqlite__bindf(stmt, "isisnnntstrisn"
@@ -999,6 +954,8 @@ insert_working_node(void *baton,
   if (piwb->kind == svn_wc__db_kind_dir && piwb->children)
     SVN_ERR(insert_incomplete_children(sdb, piwb->wc_id,
                                        piwb->local_relpath,
+                                       INVALID_REPOS_ID /* inherit repos_id */,
+                                       NULL /* inherit repos_path */,
                                        piwb->original_revnum,
                                        piwb->children,
                                        piwb->op_depth,
@@ -1040,31 +997,21 @@ add_children_to_hash(apr_hash_t *childre
 }
 
 
-/* Return in *CHILDREN all of the children of the directory LOCAL_ABSPATH.
+/* Return in *CHILDREN all of the children of the directory LOCAL_RELPATH.
    If BASE_ONLY is true, then *only* the children from BASE_NODE are
    returned (those in WORKING_NODE are ignored). The result children are
-   allocated in RESULT_POOl.  */
+   allocated in RESULT_POOL.  */
 static svn_error_t *
 gather_children(const apr_array_header_t **children,
                 svn_boolean_t base_only,
-                svn_wc__db_t *db,
-                const char *local_abspath,
+                svn_wc__db_pdh_t *pdh,
+                const char *local_relpath,
                 apr_pool_t *result_pool,
                 apr_pool_t *scratch_pool)
 {
-  svn_wc__db_pdh_t *pdh;
-  const char *local_relpath;
   apr_hash_t *names_hash = apr_hash_make(scratch_pool);
   apr_array_header_t *names_array;
 
-  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
-
-  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &local_relpath, db,
-                                             local_abspath,
-                                             svn_sqlite__mode_readonly,
-                                             scratch_pool, scratch_pool));
-  VERIFY_USABLE_PDH(pdh);
-
   /* All of the names get allocated in RESULT_POOL.  For !base_only it
      appears to be faster to use the hash to remove duplicates than to
      use DISTINCT in the SQL query. */
@@ -1923,6 +1870,9 @@ svn_wc__db_base_get_info(svn_wc__db_stat
       err = repos_location_from_columns(repos_root_url, repos_uuid, revision,
                                         repos_relpath,
                                         pdh, stmt, 0, 4, 1, result_pool);
+      SVN_ERR_ASSERT(!repos_root_url || *repos_root_url);
+      SVN_ERR_ASSERT(!repos_uuid || *repos_uuid);
+      SVN_ERR_ASSERT(!repos_relpath || *repos_relpath);
       if (lock)
         {
           *lock = lock_from_columns(stmt, 14, 15, 16, 17, result_pool);
@@ -2071,8 +2021,19 @@ svn_wc__db_base_get_children(const apr_a
                              apr_pool_t *result_pool,
                              apr_pool_t *scratch_pool)
 {
+  svn_wc__db_pdh_t *pdh;
+  const char *local_relpath;
+
+  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &local_relpath, db,
+                                             local_abspath,
+                                             svn_sqlite__mode_readonly,
+                                             scratch_pool, scratch_pool));
+  VERIFY_USABLE_PDH(pdh);
+
   return gather_children(children, TRUE,
-                         db, local_abspath, result_pool, scratch_pool);
+                         pdh, local_relpath, result_pool, scratch_pool);
 }
 
 
@@ -2635,9 +2596,7 @@ svn_wc__db_pristine_repair(svn_wc__db_t 
 /* Helper for svn_wc__db_op_copy to handle copying from one db to
    another */
 static svn_error_t *
-cross_db_copy(svn_wc__db_t *db,
-              const char *src_abspath,
-              svn_wc__db_pdh_t *src_pdh,
+cross_db_copy(svn_wc__db_pdh_t *src_pdh,
               const char *src_relpath,
               svn_wc__db_pdh_t *dst_pdh,
               const char *dst_relpath,
@@ -2765,8 +2724,6 @@ get_info_for_copy(apr_int64_t *copyfrom_
                   svn_boolean_t *have_work,
                   svn_wc__db_pdh_t *pdh,
                   const char *local_relpath,
-                  svn_wc__db_t *db,
-                  const char *local_abspath,
                   apr_pool_t *result_pool,
                   apr_pool_t *scratch_pool)
 {
@@ -2799,18 +2756,16 @@ get_info_for_copy(apr_int64_t *copyfrom_
     {
       /* The parent cannot be excluded, so look at the parent and then
          adjust the relpath */
-      const char *parent_relpath, *parent_abspath, *base_name;
+      const char *parent_relpath, *base_name;
       svn_wc__db_status_t parent_status;
       svn_wc__db_kind_t parent_kind;
       svn_boolean_t parent_have_work;
 
       svn_dirent_split(&parent_relpath, &base_name, local_relpath,
                        scratch_pool);
-      svn_dirent_split(&parent_abspath, &base_name, local_abspath,
-                       scratch_pool);
       SVN_ERR(get_info_for_copy(copyfrom_id, copyfrom_relpath, copyfrom_rev,
                                 &parent_status, &parent_kind, &parent_have_work,
-                                pdh, parent_relpath, db, parent_abspath,
+                                pdh, parent_relpath,
                                 scratch_pool, scratch_pool));
       if (*copyfrom_relpath)
         *copyfrom_relpath = svn_relpath_join(*copyfrom_relpath, base_name,
@@ -2818,32 +2773,26 @@ get_info_for_copy(apr_int64_t *copyfrom_
     }
   else if (*status == svn_wc__db_status_added)
     {
-      const char *op_root_abspath;
-      const char *original_repos_relpath, *original_root_url, *original_uuid;
+      const char *op_root_relpath;
+      const char *original_repos_relpath;
       svn_revnum_t original_revision;
 
-      SVN_ERR(svn_wc__db_scan_addition(status, &op_root_abspath,
-                                       NULL /* repos_relpath */,
-                                       NULL /* repos_root_url */,
-                                       NULL /* repos_uuid */,
-                                       &original_repos_relpath,
-                                       &original_root_url, &original_uuid,
-                                       &original_revision,
-                                       db, local_abspath,
-                                       scratch_pool, scratch_pool));
+      SVN_ERR(scan_addition(status, &op_root_relpath,
+                            NULL, NULL, /* repos_* */
+                            &original_repos_relpath, copyfrom_id,
+                            &original_revision,
+                            pdh, local_relpath,
+                            scratch_pool, scratch_pool));
 
       if (*status == svn_wc__db_status_copied
           || *status == svn_wc__db_status_moved_here)
         {
           *copyfrom_relpath
             = svn_relpath_join(original_repos_relpath,
-                               svn_dirent_skip_ancestor(op_root_abspath,
-                                                        local_abspath),
+                               svn_dirent_skip_ancestor(op_root_relpath,
+                                                        local_relpath),
                                scratch_pool);
           *copyfrom_rev = original_revision;
-          SVN_ERR(fetch_repos_id(copyfrom_id,
-                                 original_root_url, original_uuid,
-                                 pdh->wcroot->sdb, scratch_pool));
         }
       else
         {
@@ -2854,54 +2803,45 @@ get_info_for_copy(apr_int64_t *copyfrom_
     }
   else if (*status == svn_wc__db_status_deleted)
     {
-      const char *work_del_abspath;
+      const char *work_del_relpath;
 
-      SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, NULL,
-                                       &work_del_abspath,
-                                       db, local_abspath,
-                                       scratch_pool, scratch_pool));
-      if (work_del_abspath)
-        {
-          const char *op_root_abspath;
-          const char *original_repos_relpath, *original_root_url;
-          const char *original_uuid;
+      SVN_ERR(scan_deletion(NULL, NULL, NULL, &work_del_relpath,
+                            pdh, local_relpath, scratch_pool, scratch_pool));
+      if (work_del_relpath)
+        {
+          const char *op_root_relpath;
+          const char *original_repos_relpath;
           svn_revnum_t original_revision;
-          const char *parent_del_abspath = svn_dirent_dirname(work_del_abspath,
+          const char *parent_del_relpath = svn_dirent_dirname(work_del_relpath,
                                                               scratch_pool);
 
           /* Similar to, but not the same as, the _scan_addition and
              _join above.  Can we use get_copyfrom here? */
-          SVN_ERR(svn_wc__db_scan_addition(NULL, &op_root_abspath,
-                                           NULL /* repos_relpath */,
-                                           NULL /* repos_root_url */,
-                                           NULL /* repos_uuid */,
-                                           &original_repos_relpath,
-                                           &original_root_url, &original_uuid,
-                                           &original_revision,
-                                           db, parent_del_abspath,
-                                           scratch_pool, scratch_pool));
+          SVN_ERR(scan_addition(NULL, &op_root_relpath,
+                                NULL, NULL, /* repos_* */
+                                &original_repos_relpath, copyfrom_id,
+                                &original_revision,
+                                pdh, parent_del_relpath,
+                                scratch_pool, scratch_pool));
           *copyfrom_relpath
             = svn_relpath_join(original_repos_relpath,
-                               svn_dirent_skip_ancestor(op_root_abspath,
-                                                        local_abspath),
+                               svn_dirent_skip_ancestor(op_root_relpath,
+                                                        local_relpath),
                                scratch_pool);
           *copyfrom_rev = original_revision;
-          SVN_ERR(fetch_repos_id(copyfrom_id,
-                                 original_root_url, original_uuid,
-                                 pdh->wcroot->sdb, scratch_pool));
         }
       else
         {
           *copyfrom_relpath = repos_relpath;
           *copyfrom_rev = revision;
           if (!repos_root_url || !repos_uuid)
-            SVN_ERR(svn_wc__db_scan_base_repos(NULL,
-                                               &repos_root_url, &repos_uuid,
-                                               db, local_abspath,
-                                               scratch_pool, scratch_pool));
-          SVN_ERR(fetch_repos_id(copyfrom_id,
-                                 repos_root_url, repos_uuid,
-                                 pdh->wcroot->sdb, scratch_pool));
+            SVN_ERR(scan_upwards_for_repos(copyfrom_id, NULL,
+                                           pdh->wcroot, local_relpath,
+                                           result_pool, scratch_pool));
+          else
+            SVN_ERR(fetch_repos_id(copyfrom_id,
+                                   repos_root_url, repos_uuid,
+                                   pdh->wcroot->sdb, scratch_pool));
         }
     }
   else
@@ -2916,15 +2856,17 @@ get_info_for_copy(apr_int64_t *copyfrom_
   return SVN_NO_ERROR;
 }
 
-svn_error_t *
-svn_wc__db_op_copy(svn_wc__db_t *db,
-                   const char *src_abspath,
-                   const char *dst_abspath,
-                   const svn_skel_t *work_items,
-                   apr_pool_t *scratch_pool)
+/* Like svn_wc__db_op_copy(), but with PDH+LOCAL_RELPATH instead of
+ * DB+LOCAL_ABSPATH. */
+static svn_error_t *
+db_op_copy(svn_wc__db_pdh_t *src_pdh,
+           const char *src_relpath,
+           svn_wc__db_pdh_t *dst_pdh,
+           const char *dst_relpath,
+           const svn_skel_t *work_items,
+           apr_pool_t *scratch_pool)
 {
-  svn_wc__db_pdh_t *src_pdh, *dst_pdh;
-  const char *src_relpath, *dst_relpath, *copyfrom_relpath;
+  const char *copyfrom_relpath;
   svn_revnum_t copyfrom_rev;
   svn_wc__db_status_t status, dst_status;
   svn_boolean_t have_work;
@@ -2933,28 +2875,9 @@ svn_wc__db_op_copy(svn_wc__db_t *db,
   const apr_array_header_t *children;
   apr_int64_t op_depth;
 
-  SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
-  SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
-
-  /* ### This should all happen in one transaction, but that can't
-     ### happen until we move to a centralised database. */
-
-  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&src_pdh, &src_relpath, db,
-                                             src_abspath,
-                                             svn_sqlite__mode_readwrite,
-                                             scratch_pool, scratch_pool));
-  VERIFY_USABLE_PDH(src_pdh);
-
-  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&dst_pdh, &dst_relpath, db,
-                                             dst_abspath,
-                                             svn_sqlite__mode_readwrite,
-                                             scratch_pool, scratch_pool));
-  VERIFY_USABLE_PDH(dst_pdh);
-
   SVN_ERR(get_info_for_copy(&copyfrom_id, &copyfrom_relpath, &copyfrom_rev,
                             &status, &kind, &have_work,
-                            src_pdh, src_relpath, db, src_abspath,
-                            scratch_pool, scratch_pool));
+                            src_pdh, src_relpath, scratch_pool, scratch_pool));
 
   SVN_ERR_ASSERT(kind == svn_wc__db_kind_file || kind == svn_wc__db_kind_dir);
 
@@ -2977,18 +2900,19 @@ svn_wc__db_op_copy(svn_wc__db_t *db,
     case svn_wc__db_status_absent:
       return svn_error_createf(SVN_ERR_AUTHZ_UNREADABLE, NULL,
                                _("Cannot copy '%s' excluded by server"),
-                               svn_dirent_local_style(src_abspath,
+                               path_for_error_message(src_pdh->wcroot,
+                                                      src_relpath,
                                                       scratch_pool));
-      break;
     default:
       return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS, NULL,
                                _("Cannot handle status of '%s'"),
-                               svn_dirent_local_style(src_abspath,
+                               path_for_error_message(src_pdh->wcroot,
+                                                      src_relpath,
                                                       scratch_pool));
     }
 
   if (kind == svn_wc__db_kind_dir)
-    SVN_ERR(gather_children(&children, FALSE, db, src_abspath,
+    SVN_ERR(gather_children(&children, FALSE, src_pdh, src_relpath,
                             scratch_pool, scratch_pool));
   else
     children = NULL;
@@ -3035,6 +2959,8 @@ svn_wc__db_op_copy(svn_wc__db_t *db,
         SVN_ERR(insert_incomplete_children(dst_pdh->wcroot->sdb,
                                            dst_pdh->wcroot->wc_id,
                                            dst_relpath,
+                                           INVALID_REPOS_ID /* inherit repos_id */,
+                                           NULL /* inherit repos_path */,
                                            copyfrom_rev,
                                            children,
                                            op_depth,
@@ -3042,7 +2968,7 @@ svn_wc__db_op_copy(svn_wc__db_t *db,
     }
   else
     {
-      SVN_ERR(cross_db_copy(db, src_abspath, src_pdh, src_relpath,
+      SVN_ERR(cross_db_copy(src_pdh, src_relpath,
                             dst_pdh, dst_relpath, dst_status,
                             kind, children,
                             copyfrom_id, copyfrom_relpath, copyfrom_rev,
@@ -3053,7 +2979,274 @@ svn_wc__db_op_copy(svn_wc__db_t *db,
   SVN_ERR(elide_copyfrom(dst_pdh, dst_relpath, scratch_pool));
 
   SVN_ERR(add_work_items(dst_pdh->wcroot->sdb, work_items, scratch_pool));
- 
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_copy(svn_wc__db_t *db,
+                   const char *src_abspath,
+                   const char *dst_abspath,
+                   const svn_skel_t *work_items,
+                   apr_pool_t *scratch_pool)
+{
+  svn_wc__db_pdh_t *src_pdh, *dst_pdh;
+  const char *src_relpath, *dst_relpath;
+
+  SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
+  SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+
+  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&src_pdh, &src_relpath, db,
+                                             src_abspath,
+                                             svn_sqlite__mode_readwrite,
+                                             scratch_pool, scratch_pool));
+  VERIFY_USABLE_PDH(src_pdh);
+
+  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&dst_pdh, &dst_relpath, db,
+                                             dst_abspath,
+                                             svn_sqlite__mode_readwrite,
+                                             scratch_pool, scratch_pool));
+  VERIFY_USABLE_PDH(dst_pdh);
+
+  /* ### This should all happen in one transaction. */
+  SVN_ERR(db_op_copy(src_pdh, src_relpath, dst_pdh, dst_relpath,
+                     work_items, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+svn_boolean_t
+svn_wc__db_same_db(svn_wc__db_t *db,
+                   const char *src_abspath,
+                   const char *dst_abspath,
+                   apr_pool_t *scratch_pool)
+{
+  svn_error_t *err;
+  svn_wc__db_pdh_t *src_pdh, *dst_pdh;
+  const char *src_relpath, *dst_relpath;
+
+  err = svn_wc__db_pdh_parse_local_abspath(&src_pdh, &src_relpath, db,
+                                           src_abspath,
+                                           svn_sqlite__mode_readonly,
+                                           scratch_pool, scratch_pool);
+  if (err)
+    {
+      src_pdh = NULL;
+      svn_error_clear(err);
+    }
+  err = svn_wc__db_pdh_parse_local_abspath(&dst_pdh, &dst_relpath, db,
+                                           dst_abspath,
+                                           svn_sqlite__mode_readonly,
+                                           scratch_pool, scratch_pool);
+  if (err)
+    {
+      dst_pdh = NULL;
+      svn_error_clear(err);
+    }
+
+  return (src_pdh && dst_pdh && src_pdh->wcroot == dst_pdh->wcroot);
+}
+
+/* Set *OP_DEPTH to the highest op depth of PDH:LOCAL_RELPATH. */
+static svn_error_t *
+op_depth_of(apr_int64_t *op_depth,
+            svn_wc__db_pdh_t *pdh,
+            const char *local_relpath)
+{
+  svn_sqlite__stmt_t *stmt;
+  svn_boolean_t have_row;
+
+  SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->wcroot->sdb,
+                                    STMT_SELECT_NODE_INFO));
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wcroot->wc_id, local_relpath));
+  SVN_ERR(svn_sqlite__step(&have_row, stmt));
+  SVN_ERR_ASSERT(have_row);
+  *op_depth = svn_sqlite__column_int64(stmt, 0);
+  SVN_ERR(svn_sqlite__reset(stmt));
+  return SVN_NO_ERROR;
+}
+
+/*
+ * Copy single NODES row from src_path@src_op_depth to dst_path@dst_depth.
+ * Copy all rows of descendent paths in == src_op_depth to == dst_depth.
+ * Copy all rows of descendent paths in > src_depth to > dst_depth,
+ * adjusting op_depth by (dst_depth - src_depth).
+ *
+ * ### TODO: Return a list of copied nodes. */
+static svn_error_t *
+copy_nodes_rows(svn_wc__db_pdh_t *src_pdh,
+                const char *src_relpath,
+                svn_wc__db_pdh_t *dst_pdh,
+                const char *dst_relpath,
+                apr_pool_t *scratch_pool)
+{
+  apr_int64_t src_op_depth;
+  apr_int64_t src_depth = relpath_depth(src_relpath);
+  apr_int64_t dst_depth = relpath_depth(dst_relpath);
+  svn_sqlite__stmt_t *stmt;
+  const char *dst_parent_relpath = svn_relpath_dirname(dst_relpath,
+                                                       scratch_pool);
+
+  SVN_ERR(op_depth_of(&src_op_depth, src_pdh, src_relpath));
+
+  /* If there are any absent (excluded by authz) base nodes then the
+     copy must fail.  It's not possible to commit such a copy. */
+  if (src_op_depth == 0)
+    {
+      svn_boolean_t have_row;
+      const char *local_relpath;
+
+      SVN_ERR(svn_sqlite__get_statement(&stmt, src_pdh->wcroot->sdb,
+                                        STMT_SELECT_ABSENT_NODES));
+      SVN_ERR(svn_sqlite__bindf(stmt, "iss",
+                                src_pdh->wcroot->wc_id,
+                                src_relpath,
+                                construct_like_arg(src_relpath, scratch_pool)));
+      SVN_ERR(svn_sqlite__step(&have_row, stmt));
+      if (have_row)
+        local_relpath = svn_sqlite__column_text(stmt, 0, scratch_pool);
+      SVN_ERR(svn_sqlite__reset(stmt));
+      if (have_row)
+        return svn_error_createf(SVN_ERR_AUTHZ_UNREADABLE, NULL,
+                                 _("Cannot copy '%s' excluded by server"),
+                                 path_for_error_message(src_pdh->wcroot,
+                                                        local_relpath,
+                                                        scratch_pool));
+    }
+
+  /* Root node */
+  SVN_ERR(svn_sqlite__get_statement(&stmt, src_pdh->wcroot->sdb,
+                                    STMT_COPY_NODES_ROW));
+  SVN_ERR(svn_sqlite__bindf(stmt, "isissi",
+                            src_pdh->wcroot->wc_id,
+                            src_relpath,
+                            src_op_depth,
+                            dst_relpath,
+                            dst_parent_relpath,
+                            dst_depth));
+  SVN_ERR(svn_sqlite__step_done(stmt));
+
+  /* Descendants at same depth */
+  SVN_ERR(svn_sqlite__get_statement(&stmt, src_pdh->wcroot->sdb,
+                                    STMT_COPY_NODES_AT_SAME_OP_DEPTH));
+  SVN_ERR(svn_sqlite__bindf(stmt, "isisii",
+                            src_pdh->wcroot->wc_id,
+                            construct_like_arg(src_relpath, scratch_pool),
+                            src_op_depth,
+                            dst_relpath,
+                            dst_depth,
+                            (apr_int64_t)(strlen(src_relpath) + 1)));
+  SVN_ERR(svn_sqlite__step_done(stmt));
+
+  /* Descendants at greater depths */
+  SVN_ERR(svn_sqlite__get_statement(&stmt, src_pdh->wcroot->sdb,
+                                    STMT_COPY_NODES_AT_GREATER_OP_DEPTH));
+  SVN_ERR(svn_sqlite__bindf(stmt, "isisii",
+                            src_pdh->wcroot->wc_id,
+                            construct_like_arg(src_relpath, scratch_pool),
+                            src_depth,
+                            dst_relpath,
+                            dst_depth,
+                            (apr_int64_t)(strlen(src_relpath) + 1)));
+  SVN_ERR(svn_sqlite__step_done(stmt));
+
+  return SVN_NO_ERROR;
+}
+
+/* */
+static svn_error_t *
+copy_actual_rows(svn_wc__db_pdh_t *src_pdh,
+                 const char *src_relpath,
+                 svn_wc__db_pdh_t *dst_pdh,
+                 const char *dst_relpath,
+                 apr_pool_t *scratch_pool)
+{
+  svn_sqlite__stmt_t *stmt;
+  const char *src_parent_relpath = svn_relpath_dirname(src_relpath,
+                                                       scratch_pool);
+  const char *dst_parent_relpath = svn_relpath_dirname(dst_relpath,
+                                                       scratch_pool);
+
+  /* ### Copying changelist is OK for a move but what about a copy? */
+  SVN_ERR(svn_sqlite__get_statement(&stmt, dst_pdh->wcroot->sdb,
+                                    STMT_COPY_ACTUAL_NODE_ROWS));
+  SVN_ERR(svn_sqlite__bindf(stmt, "issssii",
+                            src_pdh->wcroot->wc_id, src_relpath,
+                            construct_like_arg(src_relpath, scratch_pool),
+                            dst_relpath, dst_parent_relpath,
+                            strlen(src_relpath) + 1,
+                            strlen(src_parent_relpath) + 1));
+  SVN_ERR(svn_sqlite__step_done(stmt));
+
+  return SVN_NO_ERROR;
+}
+
+
+struct copy_tree_baton_t
+{
+  svn_wc__db_pdh_t *src_pdh, *dst_pdh;
+  const char *src_relpath, *dst_relpath;
+  const svn_skel_t *work_items;
+};
+
+/* */
+static svn_error_t *
+db_op_copy_tree(void *baton,
+                svn_sqlite__db_t *sdb,
+                apr_pool_t *scratch_pool)
+{
+  struct copy_tree_baton_t *b = baton;
+
+  SVN_ERR(copy_nodes_rows(b->src_pdh, b->src_relpath,
+                          b->dst_pdh, b->dst_relpath,
+                          scratch_pool));
+
+  SVN_ERR(copy_actual_rows(b->src_pdh, b->src_relpath,
+                           b->dst_pdh, b->dst_relpath,
+                           scratch_pool));
+
+  SVN_ERR(add_work_items(b->dst_pdh->wcroot->sdb, b->work_items, scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_wc__db_op_copy_tree(svn_wc__db_t *db,
+                        const char *src_abspath,
+                        const char *dst_abspath,
+                        const svn_skel_t *work_items,
+                        apr_pool_t *scratch_pool)
+{
+  struct copy_tree_baton_t b;
+  SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
+  SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
+
+  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&b.src_pdh, &b.src_relpath, db,
+                                             src_abspath,
+                                             svn_sqlite__mode_readwrite,
+                                             scratch_pool, scratch_pool));
+  VERIFY_USABLE_PDH(b.src_pdh);
+
+  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&b.dst_pdh, &b.dst_relpath, db,
+                                             dst_abspath,
+                                             svn_sqlite__mode_readwrite,
+                                             scratch_pool, scratch_pool));
+  VERIFY_USABLE_PDH(b.dst_pdh);
+
+  b.work_items = work_items;
+
+  SVN_ERR_ASSERT(b.src_pdh->wcroot == b.dst_pdh->wcroot);
+  /* ASSERT(presence == normal) */
+
+  SVN_ERR(svn_sqlite__with_transaction(b.dst_pdh->wcroot->sdb,
+                                       db_op_copy_tree, &b, scratch_pool));
+
+  /* ### Do we sometimes need to elide copy-from info and/or other fields on
+   * the destination root node? See elide_copyfrom(). */
+
+  /* ### Do we need to flush entries?
+   * SVN_ERR(flush_entries(db, b.dst_pdh, dst_abspath, scratch_pool)); */
+
   return SVN_NO_ERROR;
 }
 
@@ -3769,13 +3962,15 @@ svn_wc__db_op_mark_resolved(svn_wc__db_t
 struct set_tc_baton
 {
   const char *local_abspath;
-  apr_int64_t wc_id;
   const char *local_relpath;
+  apr_int64_t wc_id;
+  const char *parent_relpath;
   const char *parent_abspath;
   const svn_wc_conflict_description2_t *tree_conflict;
 };
 
 
+#ifndef TREE_CONFLICTS_ON_CHILDREN
 /* */
 static svn_error_t *
 set_tc_txn(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
@@ -3788,7 +3983,7 @@ set_tc_txn(void *baton, svn_sqlite__db_t
 
   /* Get the conflict information for the parent of LOCAL_ABSPATH. */
   SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_ACTUAL_NODE));
-  SVN_ERR(svn_sqlite__bindf(stmt, "is", stb->wc_id, stb->local_relpath));
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", stb->wc_id, stb->parent_relpath));
   SVN_ERR(svn_sqlite__step(&have_row, stmt));
 
   /* No ACTUAL node, no conflict info, no problem. */
@@ -3830,17 +4025,76 @@ set_tc_txn(void *baton, svn_sqlite__db_t
                                         STMT_INSERT_ACTUAL_TREE_CONFLICTS));
     }
 
-  SVN_ERR(svn_sqlite__bindf(stmt, "iss", stb->wc_id, stb->local_relpath,
+  SVN_ERR(svn_sqlite__bindf(stmt, "iss", stb->wc_id, stb->parent_relpath,
                             tree_conflict_data));
 
-  if (!have_row && stb->local_relpath[0])
+  if (!have_row && stb->parent_relpath[0])
     SVN_ERR(svn_sqlite__bind_text(stmt, 4,
-                                  svn_dirent_dirname(stb->local_relpath,
+                                  svn_dirent_dirname(stb->parent_relpath,
                                                      scratch_pool)));
 
   return svn_error_return(svn_sqlite__step_done(stmt));
 }
 
+#else
+
+/* */
+static svn_error_t *
+set_tc_txn2(void *baton, svn_sqlite__db_t *sdb, apr_pool_t *scratch_pool)
+{
+  struct set_tc_baton *stb = baton;
+  svn_sqlite__stmt_t *stmt;
+  svn_boolean_t have_row;
+  const char *tree_conflict_data;
+
+  /* Get existing conflict information for LOCAL_ABSPATH. */
+  SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_SELECT_ACTUAL_NODE));
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", stb->wc_id, stb->local_relpath));
+  SVN_ERR(svn_sqlite__step(&have_row, stmt));
+  SVN_ERR(svn_sqlite__reset(stmt));
+
+  if (stb->tree_conflict)
+    {
+      apr_hash_t *conflicts = apr_hash_make(scratch_pool);
+      apr_hash_set(conflicts, "", APR_HASH_KEY_STRING, stb->tree_conflict);
+      SVN_ERR(svn_wc__write_tree_conflicts(&tree_conflict_data, conflicts,
+                                           scratch_pool));
+    }
+  else
+    tree_conflict_data = NULL;
+
+  if (have_row)
+    {
+      /* There is an existing ACTUAL row, so just update it. */
+      SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+                                        STMT_UPDATE_ACTUAL_CONFLICT_DATA));
+    }
+  else
+    {
+      /* We need to insert an ACTUAL row with the tree conflict data. */
+      SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+                                        STMT_INSERT_ACTUAL_CONFLICT_DATA));
+    }
+
+  SVN_ERR(svn_sqlite__bindf(stmt, "iss", stb->wc_id, stb->local_relpath,
+                            tree_conflict_data));
+  if (!have_row)
+    SVN_ERR(svn_sqlite__bind_text(stmt, 4, stb->parent_relpath));
+
+  SVN_ERR(svn_sqlite__step_done(stmt));
+
+  /* Now, remove the actual node if it doesn't have any more useful
+     information.  We only need to do this if we've remove data ourselves. */
+  if (!tree_conflict_data)
+    {
+      SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_DELETE_ACTUAL_EMPTY));
+      SVN_ERR(svn_sqlite__bindf(stmt, "is", stb->wc_id, stb->local_relpath));
+      SVN_ERR(svn_sqlite__step_done(stmt));
+    }
+
+  return SVN_NO_ERROR;
+}
+#endif
 
 svn_error_t *
 svn_wc__db_op_set_tree_conflict(svn_wc__db_t *db,
@@ -3852,19 +4106,47 @@ svn_wc__db_op_set_tree_conflict(svn_wc__
   struct set_tc_baton stb;
 
   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+  stb.local_abspath = local_abspath;
   stb.parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
+  stb.tree_conflict = tree_conflict;
 
-  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &stb.local_relpath, db,
+#ifndef TREE_CONFLICTS_ON_CHILDREN
+  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &stb.parent_relpath, db,
                               stb.parent_abspath, svn_sqlite__mode_readwrite,
                               scratch_pool, scratch_pool));
   VERIFY_USABLE_PDH(pdh);
 
-  stb.local_abspath = local_abspath;
   stb.wc_id = pdh->wcroot->wc_id;
-  stb.tree_conflict = tree_conflict;
 
   SVN_ERR(svn_sqlite__with_transaction(pdh->wcroot->sdb, set_tc_txn, &stb,
                                        scratch_pool));
+#else
+
+
+  /* ### The above is for tree-conflicts storage in parents;
+     ### the following is for tree-conflicts in ACTUAL.conflict_data.
+     ###
+     ### They are obviously redundant, with the latter being the eventual
+     ### implementation. */
+
+  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &stb.local_relpath, db,
+                              local_abspath, svn_sqlite__mode_readwrite,
+                              scratch_pool, scratch_pool));
+  VERIFY_USABLE_PDH(pdh);
+
+  stb.wc_id = pdh->wcroot->wc_id;
+
+  /* ### does this work correctly? */
+  stb.parent_relpath = svn_relpath_dirname(stb.local_relpath, scratch_pool);
+
+  /* Should probably be in the same txn as above, but since we can't
+     guarantee that pdh->wcroot->sdb is the same for both, and since
+     the above implementation is going away, we'll fudge a bit here.
+
+     ### Or can we guarantee pdh->wcroot->sdb is the same, given single db? */
+  SVN_ERR(svn_sqlite__with_transaction(pdh->wcroot->sdb, set_tc_txn2, &stb,
+                                       scratch_pool));
+#endif
 
   /* There may be some entries, and the lock info is now out of date.  */
   SVN_ERR(flush_entries(db, pdh, local_abspath, scratch_pool));
@@ -3937,6 +4219,7 @@ read_all_tree_conflicts(apr_hash_t **tre
 {
   svn_sqlite__stmt_t *stmt;
   svn_boolean_t have_row;
+#ifndef TREE_CONFLICTS_ON_CHILDREN
   const char *tree_conflict_data;
   const char *local_abspath;
 
@@ -3969,6 +4252,44 @@ read_all_tree_conflicts(apr_hash_t **tre
   SVN_ERR(svn_wc__read_tree_conflicts(tree_conflicts, tree_conflict_data,
                                       local_abspath, result_pool));
 
+#else
+  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+  *tree_conflicts = apr_hash_make(result_pool);
+
+  /* Get the conflict information for children of LOCAL_ABSPATH. */
+  SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->wcroot->sdb,
+                               STMT_SELECT_ACTUAL_CHILDREN_TREE_CONFLICT));
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wcroot->wc_id, local_relpath));
+  SVN_ERR(svn_sqlite__step(&have_row, stmt));
+  while (have_row)
+    {
+      const char *child_basename;
+      const char *child_relpath;
+      const char *child_abspath;
+      const char *conflict_data;
+      apr_hash_t *conflict_hash;
+      
+      svn_pool_clear(iterpool);
+
+      child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+      child_basename = svn_relpath_basename(child_relpath, iterpool);
+      child_abspath = svn_dirent_join(pdh->wcroot->abspath, child_relpath,
+                                      iterpool);
+
+      conflict_data = svn_sqlite__column_text(stmt, 1, NULL);
+      SVN_ERR(svn_wc__read_tree_conflicts(&conflict_hash, conflict_data,
+                                          pdh->wcroot->abspath, result_pool));
+
+      *tree_conflicts = apr_hash_overlay(result_pool, conflict_hash,
+                                         *tree_conflicts);
+
+      SVN_ERR(svn_sqlite__step(&have_row, stmt));
+    }
+
+  svn_pool_destroy(iterpool);
+#endif
+
   return SVN_NO_ERROR;
 }
 
@@ -4168,13 +4489,40 @@ svn_wc__db_temp_op_set_dir_depth(svn_wc_
   /* ### We set depth on working and base to match entry behavior.
          Maybe these should be separated later? */
 
-  SVN_ERR(update_depth_values(db, local_abspath, pdh, local_relpath, depth,
-                              scratch_pool));
+  SVN_ERR(update_depth_values(db, local_abspath, pdh, local_relpath, depth,
+                              scratch_pool));
+
+  return SVN_NO_ERROR;
+}
+
+
+/* Delete child sub-trees of LOCAL_RELPATH that are presence=not-present
+   and at the same op_depth.
+
+   ### Do we need to handle incomplete here? */
+static svn_error_t *
+delete_not_present_children(svn_wc__db_pdh_t *pdh,
+                            const char *local_relpath,
+                            apr_pool_t *scratch_pool)
+{
+  svn_sqlite__stmt_t *stmt;
+#ifdef SVN_WC__OP_DEPTH
+  apr_int64_t op_depth = relpath_depth(local_relpath);
+#else
+  apr_int64_t op_depth = 2;  /* ### temporary op_depth */
+#endif
+
+  SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->wcroot->sdb,
+                                    STMT_DELETE_NOT_PRESENT_NODES_RECURSIVE));
+  SVN_ERR(svn_sqlite__bindf(stmt, "isi", pdh->wcroot->wc_id,
+                            construct_like_arg(local_relpath,
+                                               scratch_pool),
+                            op_depth));
+  SVN_ERR(svn_sqlite__step_done(stmt));
 
   return SVN_NO_ERROR;
 }
 
-
 /* Update the working node for LOCAL_ABSPATH setting presence=STATUS */
 static svn_error_t *
 db_working_update_presence(svn_wc__db_status_t status,
@@ -4198,11 +4546,19 @@ db_working_update_presence(svn_wc__db_st
                             presence_map, status));
   SVN_ERR(svn_sqlite__step_done(stmt));
 
+  /* Switching to base-deleted is undoing an add/copy.  If this was a
+     copy then any children of the copy will now be not-present and
+     should be removed.  By this stage an add will have no children. */
+  if (status == svn_wc__db_status_base_deleted)
+    SVN_ERR(delete_not_present_children(pdh, local_relpath, scratch_pool));
+
   return SVN_NO_ERROR;
 }
 
 
-/* Delete working and actual nodes for LOCAL_ABSPATH */
+/* Delete working and actual nodes for LOCAL_ABSPATH.  When called any
+   remain working child sub-trees should be presence=not-present and will
+   be deleted. */
 static svn_error_t *
 db_working_actual_remove(svn_wc__db_t *db,
                          const char *local_abspath,
@@ -4229,6 +4585,8 @@ db_working_actual_remove(svn_wc__db_t *d
   SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wcroot->wc_id, local_relpath));
   SVN_ERR(svn_sqlite__step_done(stmt));
 
+  SVN_ERR(delete_not_present_children(pdh, local_relpath, scratch_pool));
+
   SVN_ERR(flush_entries(db, pdh, local_abspath, scratch_pool));
 
   return SVN_NO_ERROR;
@@ -4281,31 +4639,29 @@ db_working_insert(svn_wc__db_status_t st
    a copy, to FALSE otherwise. */
 static svn_error_t*
 is_add_or_root_of_copy(svn_boolean_t *add_or_root_of_copy,
-                       svn_wc__db_t *db,
-                       const char *local_abspath,
+                       svn_wc__db_pdh_t *pdh,
+                       const char *local_relpath,
                        apr_pool_t *scratch_pool)
 {
   svn_wc__db_status_t status;
-  const char *op_root_abspath;
-  const char *original_repos_relpath, *original_repos_root;
-  const char *original_repos_uuid;
+  const char *op_root_relpath;
+  const char *original_repos_relpath;
+  apr_int64_t original_repos_id;
   svn_revnum_t original_revision;
 
-  SVN_ERR(svn_wc__db_scan_addition(&status, &op_root_abspath,
-                                   NULL, NULL, NULL,
-                                   &original_repos_relpath,
-                                   &original_repos_root,
-                                   &original_repos_uuid,
-                                   &original_revision,
-                                   db, local_abspath,
-                                   scratch_pool, scratch_pool));
+  SVN_ERR(scan_addition(&status, &op_root_relpath, NULL, NULL,
+                        &original_repos_relpath,
+                        &original_repos_id,
+                        &original_revision,
+                        pdh, local_relpath,
+                        scratch_pool, scratch_pool));
 
   SVN_ERR_ASSERT(status == svn_wc__db_status_added
                  || status == svn_wc__db_status_copied);
-  SVN_ERR_ASSERT(op_root_abspath != NULL);
+  SVN_ERR_ASSERT(op_root_relpath != NULL);
 
   *add_or_root_of_copy = (status == svn_wc__db_status_added
-                          || !strcmp(local_abspath, op_root_abspath));
+                          || !strcmp(local_relpath, op_root_relpath));
 
   if (*add_or_root_of_copy && status == svn_wc__db_status_copied)
     {
@@ -4314,24 +4670,22 @@ is_add_or_root_of_copy(svn_boolean_t *ad
              here because I just need to detect whether this is an
              instance of the merge bug, and that's easier than fixing
              scan_addition or merge. */
-      const char *parent_abspath;
+      const char *parent_relpath;
       const char *name;
       svn_wc__db_status_t parent_status;
-      const char *parent_original_repos_relpath, *parent_original_repos_root;
-      const char *parent_original_repos_uuid;
+      const char *parent_original_repos_relpath;
+      apr_int64_t parent_original_repos_id;
       svn_revnum_t parent_original_revision;
       svn_error_t *err;
 
-      svn_dirent_split(&parent_abspath, &name, local_abspath, scratch_pool);
+      svn_relpath_split(&parent_relpath, &name, local_relpath, scratch_pool);
 
-      err = svn_wc__db_scan_addition(&parent_status,
-                                     NULL, NULL, NULL, NULL,
-                                     &parent_original_repos_relpath,
-                                     &parent_original_repos_root,
-                                     &parent_original_repos_uuid,
-                                     &parent_original_revision,
-                                     db, parent_abspath,
-                                     scratch_pool, scratch_pool);
+      err = scan_addition(&parent_status, NULL, NULL, NULL,
+                          &parent_original_repos_relpath,
+                          &parent_original_repos_id,
+                          &parent_original_revision,
+                          pdh, parent_relpath,
+                          scratch_pool, scratch_pool);
       if (err)
         {
           if (err->apr_err != SVN_ERR_WC_PATH_NOT_FOUND)
@@ -4341,8 +4695,7 @@ is_add_or_root_of_copy(svn_boolean_t *ad
         }
       else if (parent_status == svn_wc__db_status_copied
                && original_revision == parent_original_revision
-               && !strcmp(original_repos_uuid, parent_original_repos_uuid)
-               && !strcmp(original_repos_root, parent_original_repos_root)
+               && original_repos_id == parent_original_repos_id
                && !strcmp(original_repos_relpath,
                           svn_dirent_join(parent_original_repos_relpath,
                                           name,
@@ -4362,39 +4715,24 @@ svn_wc__db_temp_op_delete(svn_wc__db_t *
                           const char *local_abspath,
                           apr_pool_t *scratch_pool)
 {
-  svn_error_t *err;
-  svn_boolean_t base_none, working_none, new_working_none;
+  svn_wc__db_pdh_t *pdh;
+  const char *local_relpath;
   svn_wc__db_status_t base_status, working_status, new_working_status;
-  svn_boolean_t have_work;
+  svn_boolean_t have_base, have_work, new_have_work;
 
-  err = svn_wc__db_base_get_info(&base_status,
-                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                                 NULL, NULL, NULL, NULL, NULL, NULL, NULL,
-                                 db, local_abspath,
-                                 scratch_pool, scratch_pool);
-  if (err && err->apr_err == SVN_ERR_WC_PATH_NOT_FOUND)
-    {
-      base_none = TRUE;
-      svn_error_clear(err);
-    }
-  else if (! err)
-    base_none = FALSE;
-  else
-    return svn_error_return(err);
+  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
 
-  /* ### should error on excluded, too. excluded nodes could be removed
-     ### from our metadata, but they cannot be scheduled for deletion.  */
-  if (!base_none && base_status == svn_wc__db_status_absent)
-    return SVN_NO_ERROR; /* ### should return an error.... WHICH ONE? */
+  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &local_relpath, db,
+                              local_abspath, svn_sqlite__mode_readonly,
+                              scratch_pool, scratch_pool));
+  VERIFY_USABLE_PDH(pdh);
 
-  /* No need to check for SVN_ERR_WC_PATH_NOT_FOUND. Something has to
-     be there for us to delete.  */
-  SVN_ERR(svn_wc__db_read_info(&working_status, NULL, NULL, NULL, NULL, NULL,
-                               NULL, NULL, NULL, NULL, NULL, NULL,
-                               NULL, NULL, NULL, NULL, NULL, NULL,
-                               NULL, NULL, NULL, &have_work, NULL, NULL,
-                               db, local_abspath,
-                               scratch_pool, scratch_pool));
+  SVN_ERR(read_info(&working_status, NULL, NULL, NULL, NULL, NULL,
+                    NULL, NULL, NULL, NULL, NULL, NULL,
+                    NULL, NULL, NULL, NULL, NULL, NULL,
+                    NULL, NULL, &have_base, &have_work, NULL, NULL,
+                    pdh, local_relpath,
+                    scratch_pool, scratch_pool));
   if (working_status == svn_wc__db_status_deleted)
     {
       /* The node is already deleted.  */
@@ -4402,9 +4740,18 @@ svn_wc__db_temp_op_delete(svn_wc__db_t *
       return SVN_NO_ERROR;
     }
 
-  working_none = !have_work;
+  if (have_base)
+    SVN_ERR(svn_wc__db_base_get_info(&base_status,
+                                     NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+                                     NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+                                     db, local_abspath,
+                                     scratch_pool, scratch_pool));
+
+  if (have_base && (base_status == svn_wc__db_status_absent
+                    || base_status == svn_wc__db_status_excluded))
+    return SVN_NO_ERROR; /* ### should return an error.... WHICH ONE? */
 
-  new_working_none = working_none;
+  new_have_work = have_work;
   new_working_status = working_status;
 
   if (working_status == svn_wc__db_status_normal
@@ -4413,9 +4760,9 @@ svn_wc__db_temp_op_delete(svn_wc__db_t *
       /* No structural changes (ie. no WORKING node). Mark the BASE node
          as deleted.  */
 
-      SVN_ERR_ASSERT(working_none);
+      SVN_ERR_ASSERT(!have_work);
 
-      new_working_none = FALSE;
+      new_have_work = TRUE;
       new_working_status = svn_wc__db_status_base_deleted;
     }
   /* ### remaining states: added, absent, excluded, incomplete
@@ -4423,31 +4770,31 @@ svn_wc__db_temp_op_delete(svn_wc__db_t *
      ### and this code may need to change further, but I'm not
      ### going to worry about it now
   */
-  else if (working_none)
+  else if (!have_work)
     {
       /* No structural changes  */
       if (base_status == svn_wc__db_status_normal
           || base_status == svn_wc__db_status_incomplete
           || base_status == svn_wc__db_status_excluded)
         {
-          new_working_none = FALSE;
+          new_have_work = TRUE;
           new_working_status = svn_wc__db_status_base_deleted;
         }
     }
-    /* ### BH: base_none is not a safe check, because a node can
+    /* ### BH: have_base is not a safe check, because a node can
        ### still be a child of an added node even though it replaces
        ### a base node. */
   else if (working_status == svn_wc__db_status_added
-           && (base_none || base_status == svn_wc__db_status_not_present))
+           && (!have_base || base_status == svn_wc__db_status_not_present))
     {
       /* ADD/COPY-HERE/MOVE-HERE. There is "no BASE node".  */
 
       svn_boolean_t add_or_root_of_copy;
 
       SVN_ERR(is_add_or_root_of_copy(&add_or_root_of_copy,
-                                     db, local_abspath, scratch_pool));
+                                     pdh, local_relpath, scratch_pool));
       if (add_or_root_of_copy)
-        new_working_none = TRUE;
+        new_have_work = FALSE;
       else
         new_working_status = svn_wc__db_status_not_present;
     }
@@ -4456,7 +4803,7 @@ svn_wc__db_temp_op_delete(svn_wc__db_t *
       /* DELETE + ADD  */
       svn_boolean_t add_or_root_of_copy;
       SVN_ERR(is_add_or_root_of_copy(&add_or_root_of_copy,
-                                     db, local_abspath, scratch_pool));
+                                     pdh, local_relpath, scratch_pool));
       if (add_or_root_of_copy)
         new_working_status = svn_wc__db_status_base_deleted;
       else
@@ -4466,12 +4813,12 @@ svn_wc__db_temp_op_delete(svn_wc__db_t *
     {
       svn_boolean_t add_or_root_of_copy;
       SVN_ERR(is_add_or_root_of_copy(&add_or_root_of_copy,
-                                     db, local_abspath, scratch_pool));
+                                     pdh, local_relpath, scratch_pool));
       if (add_or_root_of_copy)
-        new_working_none = TRUE;
+        new_have_work = FALSE;
     }
 
-  if (new_working_none && !working_none)
+  if (!new_have_work && have_work)
     {
       SVN_ERR(db_working_actual_remove(db, local_abspath, scratch_pool));
       /* ### Search the cached directories in db for directories below
@@ -4480,10 +4827,10 @@ svn_wc__db_temp_op_delete(svn_wc__db_t *
       SVN_ERR(svn_wc__db_temp_forget_directory(db, local_abspath,
                                                scratch_pool));
     }
-  else if (!new_working_none && working_none)
+  else if (new_have_work && !have_work)
     SVN_ERR(db_working_insert(new_working_status,
                               db, local_abspath, scratch_pool));
-  else if (!new_working_none && !working_none
+  else if (new_have_work && have_work
            && new_working_status != working_status)
     SVN_ERR(db_working_update_presence(new_working_status,
                                        db, local_abspath, scratch_pool));
@@ -4556,10 +4903,10 @@ read_info(svn_wc__db_status_t *status,
 
   if (have_info)
     {
-      int op_depth;
+      apr_int64_t op_depth;
       svn_wc__db_kind_t node_kind;
 
-      op_depth = svn_sqlite__column_int(stmt_info, 0);
+      op_depth = svn_sqlite__column_int64(stmt_info, 0);
       node_kind = svn_sqlite__column_token(stmt_info, 4, kind_map);
 
       if (status)
@@ -4740,10 +5087,15 @@ read_info(svn_wc__db_status_t *status,
           if (have_act)
             {
               *conflicted =
-                 svn_sqlite__column_text(stmt_act, 2, NULL) || /* old */
-                 svn_sqlite__column_text(stmt_act, 3, NULL) || /* new */
-                 svn_sqlite__column_text(stmt_act, 4, NULL) || /* working */
-                 svn_sqlite__column_text(stmt_act, 0, NULL); /* prop_reject */
+                 !svn_sqlite__column_is_null(stmt_act, 2) || /* old */
+                 !svn_sqlite__column_is_null(stmt_act, 3) || /* new */
+                 !svn_sqlite__column_is_null(stmt_act, 4) || /* working */
+                 !svn_sqlite__column_is_null(stmt_act, 0); /* prop_reject */
+
+#ifdef TREE_CONFLICTS_ON_CHILDREN
+              *conflicted = *conflicted ||
+                !svn_sqlite__column_is_null(stmt_act, 7); /* conflict_data */
+#endif
 
               /* At the end of this function we check for tree conflicts */
             }
@@ -4771,7 +5123,7 @@ read_info(svn_wc__db_status_t *status,
               if (err || !have_info)
                 break;
 
-              op_depth = svn_sqlite__column_int(stmt_info, 0);
+              op_depth = svn_sqlite__column_int64(stmt_info, 0);
             }
 
           if (have_base)
@@ -4802,6 +5154,7 @@ read_info(svn_wc__db_status_t *status,
 
   SVN_ERR(svn_error_compose_create(err, svn_sqlite__reset(stmt_info)));
 
+#ifndef TREE_CONFLICTS_ON_CHILDREN
   /* ### And finally, check for tree conflicts via parent.
          This reuses stmt_act and throws an error in Sqlite if
          we do it directly */
@@ -4814,6 +5167,7 @@ read_info(svn_wc__db_status_t *status,
 
       *conflicted = (cd != NULL);
     }
+#endif
 
   return SVN_NO_ERROR;
 }
@@ -4883,7 +5237,9 @@ svn_wc__db_read_children_info(apr_hash_t
   svn_boolean_t have_row;
   const char *repos_root_url = NULL;
   apr_int64_t last_repos_id;
+#ifndef TREE_CONFLICTS_ON_CHILDREN
   apr_hash_t *tree_conflicts;
+#endif
 
   SVN_ERR_ASSERT(svn_dirent_is_absolute(dir_abspath));
 
@@ -4905,7 +5261,7 @@ svn_wc__db_read_children_info(apr_hash_t
       const char *child_relpath = svn_sqlite__column_text(stmt, 19, NULL);
       const char *name = svn_relpath_basename(child_relpath, NULL);
       svn_error_t *err;
-      int *op_depth, row_op_depth;
+      apr_int64_t *op_depth, row_op_depth;
       svn_boolean_t new_child;
 
       child = apr_hash_get(*nodes, name, APR_HASH_KEY_STRING);
@@ -4913,12 +5269,11 @@ svn_wc__db_read_children_info(apr_hash_t
         new_child = FALSE;
       else
         {
-          child = apr_palloc(result_pool,
-                             sizeof(struct svn_wc__db_info_t) + sizeof(int));
+          child = apr_palloc(result_pool, sizeof(*child) + sizeof(*op_depth));
           new_child = TRUE;
         }
 
-      op_depth = (int *)(char*)(child + 1);
+      op_depth = (apr_int64_t *)(child + 1);
       row_op_depth = svn_sqlite__column_int(stmt, 0);
 
       if (new_child || *op_depth < row_op_depth)
@@ -5047,12 +5402,8 @@ svn_wc__db_read_children_info(apr_hash_t
       child = apr_hash_get(*nodes, name, APR_HASH_KEY_STRING);
       if (!child)
         {
-          err = svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
-                                  _("Corrupt data for '%s'"),
-                                  svn_dirent_local_style(child_relpath,
-                                                         scratch_pool));
-          SVN_ERR(svn_error_compose_create(err,
-                                           svn_sqlite__step(&have_row, stmt)));
+          child = apr_palloc(result_pool, sizeof(*child) + sizeof(int));
+          child->status = svn_wc__db_status_not_present;
         }
 
       child->changelist = svn_sqlite__column_text(stmt, 1, result_pool);
@@ -5075,10 +5426,15 @@ svn_wc__db_read_children_info(apr_hash_t
         }
 
 
-      child->conflicted = (svn_sqlite__column_text(stmt, 2, NULL)     /* old */
-                           || svn_sqlite__column_text(stmt, 3, NULL)  /* new */
-                           || svn_sqlite__column_text(stmt, 4, NULL)  /* work */
-                           || svn_sqlite__column_text(stmt, 0, NULL));/* prop */
+      child->conflicted = !svn_sqlite__column_is_null(stmt, 2) ||  /* old */
+                          !svn_sqlite__column_is_null(stmt, 3) ||  /* new */
+                          !svn_sqlite__column_is_null(stmt, 4) ||  /* work */
+                          !svn_sqlite__column_is_null(stmt, 0);    /* prop */
+
+#ifdef TREE_CONFLICTS_ON_CHILDREN
+      child->conflicted = child->conflicted ||
+                            !svn_sqlite__column_is_null(stmt, 8);  /* tree */
+#endif
 
       err = svn_sqlite__step(&have_row, stmt);
       if (err)
@@ -5087,9 +5443,10 @@ svn_wc__db_read_children_info(apr_hash_t
 
   SVN_ERR(svn_sqlite__reset(stmt));
 
+  *conflicts = apr_hash_make(result_pool);
+#ifndef TREE_CONFLICTS_ON_CHILDREN
   SVN_ERR(read_all_tree_conflicts(&tree_conflicts, pdh, dir_relpath,
                                   scratch_pool, scratch_pool));
-  *conflicts = apr_hash_make(result_pool);
   if (tree_conflicts)
     {
       apr_hash_index_t *hi;
@@ -5109,6 +5466,7 @@ svn_wc__db_read_children_info(apr_hash_t
                        APR_HASH_KEY_STRING, "");
         }
     }
+#endif
 
   return SVN_NO_ERROR;
 }
@@ -5289,8 +5647,19 @@ svn_wc__db_read_children(const apr_array
                          apr_pool_t *result_pool,
                          apr_pool_t *scratch_pool)
 {
+  svn_wc__db_pdh_t *pdh;
+  const char *local_relpath;
+
+  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+
+  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &local_relpath, db,
+                                             local_abspath,
+                                             svn_sqlite__mode_readonly,
+                                             scratch_pool, scratch_pool));
+  VERIFY_USABLE_PDH(pdh);
+
   return gather_children(children, FALSE,
-                         db, local_abspath, result_pool, scratch_pool);
+                         pdh, local_relpath, result_pool, scratch_pool);
 }
 
 struct relocate_baton
@@ -5551,13 +5920,10 @@ commit_node(void *baton, svn_sqlite__db_
         new_depth_str = svn_sqlite__column_text(stmt_base, 10, scratch_pool);
     }
 
-  /* Get the repository information. REPOS_RELPATH will indicate whether
-     we bind REPOS_ID/REPOS_RELPATH as null values in the database (in order
-     to inherit values from the parent node), or that we have actual data.
-     Note: only inherit if we're not at the root.  */
-  if (have_base && !svn_sqlite__column_is_null(stmt_base, 0))
+  /* Check that the repository information is not being changed.  */
+  if (have_base)
     {
-      /* If 'repos_id' is valid, then 'repos_relpath' should be, too.  */
+      SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt_base, 0));
       SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt_base, 1));
 
       /* A commit cannot change these values.  */
@@ -5693,11 +6059,9 @@ commit_node(void *baton, svn_sqlite__db_
 
 
 /* Set *REPOS_ID and *REPOS_RELPATH to the BASE repository location of
- * (PDH, LOCAL_RELPATH), scanning upwards through parents if no BASE row
- * exists for this node or if it inherits the info.
- *
- * Similar to scan_upwards_for_repos() except that the node need not exist
- * in BASE. */
+ * (PDH, LOCAL_RELPATH), directly if its BASE row exists or implied from
+ * its parent's BASE row if not. In the latter case, error if the parent
+ * BASE row does not exist. */
 static svn_error_t *
 determine_repos_info(apr_int64_t *repos_id,
                      const char **repos_relpath,
@@ -5719,9 +6083,9 @@ determine_repos_info(apr_int64_t *repos_
   SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wcroot->wc_id, local_relpath));
   SVN_ERR(svn_sqlite__step(&have_row, stmt));
 
-  if (have_row && !svn_sqlite__column_is_null(stmt, 0))
+  if (have_row)
     {
-      /* If one is non-NULL, then so should the other. */
+      SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 0));
       SVN_ERR_ASSERT(!svn_sqlite__column_is_null(stmt, 1));
 
       *repos_id = svn_sqlite__column_int64(stmt, 0);
@@ -5790,19 +6154,12 @@ svn_wc__db_global_commit(svn_wc__db_t *d
   cb.no_unlock = no_unlock;
   cb.work_items = work_items;
 
-  /* If we are adding a directory (no BASE_NODE), then we need to get
-     repository information from an ancestor node (start scanning from the
-     parent node since "this node" does not have a BASE). We cannot simply
-     inherit that information (across SDB boundaries).
-
-     If we're adding a file, then leaving the fields as null (in order to
-     inherit) would be possible.
+  /* If we are adding a file or directory, then we need to get
+     repository information from the parent node since "this node" does
+     not have a BASE).
 
      For existing nodes, we should retain the (potentially-switched)
-     repository information.
-
-     ### this always returns values. we should switch to null if/when
-     ### possible.  */
+     repository information.  */
   SVN_ERR(determine_repos_info(&cb.repos_id, &cb.repos_relpath,
                                pdh, local_relpath,
                                scratch_pool, scratch_pool));
@@ -6105,59 +6462,45 @@ svn_wc__db_scan_base_repos(const char **
 }
 
 
-svn_error_t *
-svn_wc__db_scan_addition(svn_wc__db_status_t *status,
-                         const char **op_root_abspath,
-                         const char **repos_relpath,
-                         const char **repos_root_url,
-                         const char **repos_uuid,
-                         const char **original_repos_relpath,
-                         const char **original_root_url,
-                         const char **original_uuid,
-                         svn_revnum_t *original_revision,
-                         svn_wc__db_t *db,
-                         const char *local_abspath,
-                         apr_pool_t *result_pool,
-                         apr_pool_t *scratch_pool)
+/* Like svn_wc__db_scan_addition(), but with PDH+LOCAL_RELPATH instead of
+ * DB+LOCAL_ABSPATH.
+ *
+ * The output value of *ORIGINAL_REPOS_ID will be INVALID_REPOS_ID if there
+ * is no 'copy-from' repository. */
+static svn_error_t *
+scan_addition(svn_wc__db_status_t *status,
+              const char **op_root_relpath,
+              const char **repos_relpath,
+              apr_int64_t *repos_id,
+              const char **original_repos_relpath,
+              apr_int64_t *original_repos_id,
+              svn_revnum_t *original_revision,
+              svn_wc__db_pdh_t *pdh,
+              const char *local_relpath,
+              apr_pool_t *result_pool,
+              apr_pool_t *scratch_pool)
 {
-  const char *current_abspath = local_abspath;
-  const char *current_relpath;
-  const char *child_abspath = NULL;
+  const char *current_relpath = local_relpath;
+  const char *child_relpath = NULL;
   const char *build_relpath = "";
-  svn_wc__db_pdh_t *pdh;
-  svn_wc__db_wcroot_t *wcroot;
+  svn_wc__db_wcroot_t *wcroot = pdh->wcroot;
   svn_boolean_t found_info = FALSE;
 
-  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
-
-  /* Initialize all the OUT parameters. Generally, we'll only be filling
+  /* Initialize most of the OUT parameters. Generally, we'll only be filling
      in a subset of these, so it is easier to init all up front. Note that
      the STATUS parameter will be initialized once we read the status of
      the specified node.  */
-  if (op_root_abspath)
-    *op_root_abspath = NULL;
-  if (repos_relpath)
-    *repos_relpath = NULL;
-  if (repos_root_url)
-    *repos_root_url = NULL;
-  if (repos_uuid)
-    *repos_uuid = NULL;
+  if (op_root_relpath)
+    *op_root_relpath = NULL;
   if (original_repos_relpath)
     *original_repos_relpath = NULL;
-  if (original_root_url)
-    *original_root_url = NULL;
-  if (original_uuid)
-    *original_uuid = NULL;
+  if (original_repos_id)
+    *original_repos_id = INVALID_REPOS_ID;
   if (original_revision)
     *original_revision = SVN_INVALID_REVNUM;
 
-  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &current_relpath, db,
-                              current_abspath, svn_sqlite__mode_readonly,
-                              scratch_pool, scratch_pool));
-  VERIFY_USABLE_PDH(pdh);
-
-  wcroot = pdh->wcroot;
 
+#ifndef SVN_WC__OP_DEPTH
   while (TRUE)
     {
       svn_sqlite__stmt_t *stmt;
@@ -6175,20 +6518,21 @@ svn_wc__db_scan_addition(svn_wc__db_stat
           /* Reset statement before (optionally) returning */
           SVN_ERR(svn_sqlite__reset(stmt));
 
-          if (current_abspath == local_abspath)
+          if (current_relpath == local_relpath)
             /* ### maybe we should return a usage error instead?  */
             return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
                                      _("The node '%s' was not found."),
-                                     svn_dirent_local_style(local_abspath,
+                                     path_for_error_message(wcroot,
+                                                            local_relpath,
                                                             scratch_pool));
 
           /* We just fell off the top of the WORKING tree. If we haven't
              found the operation root, then the child node that we just
              left was that root.  */
-          if (op_root_abspath && *op_root_abspath == NULL)
+          if (op_root_relpath && *op_root_relpath == NULL)
             {
-              SVN_ERR_ASSERT(child_abspath != NULL);
-              *op_root_abspath = apr_pstrdup(result_pool, child_abspath);
+              SVN_ERR_ASSERT(child_relpath != NULL);
+              *op_root_relpath = apr_pstrdup(result_pool, child_relpath);
             }
 
           /* This node was added/copied/moved and has an implicit location
@@ -6200,14 +6544,15 @@ svn_wc__db_scan_addition(svn_wc__db_stat
       presence = svn_sqlite__column_token(stmt, 1, presence_map);
 
       /* Record information from the starting node.  */
-      if (current_abspath == local_abspath)
+      if (current_relpath == local_relpath)
         {
           /* The starting node should exist normally.  */
           if (presence != svn_wc__db_status_normal)
             return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
                                      svn_sqlite__reset(stmt),
                                      _("Expected node '%s' to be added."),
-                                     svn_dirent_local_style(local_abspath,
+                                     path_for_error_message(wcroot,
+                                                            local_relpath,
                                                             scratch_pool));
 
           /* Provide the default status; we'll override as appropriate. */
@@ -6228,24 +6573,20 @@ svn_wc__db_scan_addition(svn_wc__db_stat
               else
                 *status = svn_wc__db_status_copied;
             }
-          if (op_root_abspath)
-            *op_root_abspath = apr_pstrdup(result_pool, current_abspath);
+          if (op_root_relpath)
+            *op_root_relpath = apr_pstrdup(result_pool, current_relpath);
           if (original_repos_relpath)
             *original_repos_relpath = svn_sqlite__column_text(stmt, 11,
                                                               result_pool);
-          if (original_root_url || original_uuid)
-            SVN_ERR(fetch_repos_info(original_root_url, original_uuid,
-                                     wcroot->sdb,
-                                     svn_sqlite__column_int64(stmt, 10),
-                                     result_pool));
+          if (original_repos_id)
+            *original_repos_id = svn_sqlite__column_int64(stmt, 10);
           if (original_revision)
             *original_revision = svn_sqlite__column_revnum(stmt, 12);
 
           /* We may have to keep tracking upwards for REPOS_* values.
              If they're not needed, then just return.  */
           if (repos_relpath == NULL
-              && repos_root_url == NULL
-              && repos_uuid == NULL)
+              && repos_id == NULL)
             return svn_error_return(svn_sqlite__reset(stmt));
 
           /* We've found the info we needed. Scan for the top of the
@@ -6260,7 +6601,7 @@ svn_wc__db_scan_addition(svn_wc__db_stat
          traverse up the tree.  */
       if (repos_relpath)
         {
-          build_relpath = svn_relpath_join(svn_dirent_basename(current_abspath,
+          build_relpath = svn_relpath_join(svn_dirent_basename(current_relpath,
                                                                NULL),
                                            build_relpath,
                                            scratch_pool);
@@ -6268,33 +6609,187 @@ svn_wc__db_scan_addition(svn_wc__db_stat
 
       /* Move to the parent node. Remember the abspath to this node, since
          it could be the root of an add/delete.  */
-      child_abspath = current_abspath;
+      child_relpath = current_relpath;
 
       /* The wcroot can't have a restructuring operation; make sure we don't
          loop on invalid data */
       SVN_ERR_ASSERT(current_relpath[0] != '\0');
 
       current_relpath = svn_relpath_dirname(current_relpath, scratch_pool);
-      current_abspath = svn_dirent_dirname(current_abspath, scratch_pool);
     }
+#else  /* ifdef SVN_WC__OP_DEPTH */
+  {
+    svn_sqlite__stmt_t *stmt;
+    svn_boolean_t have_row;
+    svn_wc__db_status_t presence;
+    apr_int64_t op_depth;
+    const char *repos_prefix_path = "";
+    int i;
+
+    /* ### is it faster to fetch fewer columns? */
+    SVN_ERR(svn_sqlite__get_statement(&stmt, wcroot->sdb,
+                                      STMT_SELECT_WORKING_NODE));
+    SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, local_relpath));
+    SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+    if (!have_row)
+      {
+        /* Reset statement before returning */
+        SVN_ERR(svn_sqlite__reset(stmt));
+
+        /* ### maybe we should return a usage error instead?  */
+        return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+                                 _("The node '%s' was not found."),
+                                 path_for_error_message(wcroot,
+                                                        local_relpath,
+                                                        scratch_pool));
+      }
+
+    presence = svn_sqlite__column_token(stmt, 1, presence_map);
+
+    /* The starting node should exist normally.  */
+    if (presence != svn_wc__db_status_normal)
+      /* reset the statement as part of the error generation process */
+      return svn_error_createf(SVN_ERR_WC_PATH_UNEXPECTED_STATUS,
+                               svn_sqlite__reset(stmt),
+                               _("Expected node '%s' to be added."),
+                               path_for_error_message(wcroot,
+                                                      local_relpath,
+                                                      scratch_pool));
+
+    if (original_revision)
+      *original_revision = svn_sqlite__column_revnum(stmt, 12);
+
+    /* Provide the default status; we'll override as appropriate. */
+    if (status)
+      *status = svn_wc__db_status_added;
+
+
+    /* Calculate the op root local path components */
+    op_depth = svn_sqlite__column_int64(stmt, 0);
+    current_relpath = local_relpath;
+
+    for (i = relpath_depth(local_relpath); i > op_depth; --i)
+      {
+        /* Calculate the path of the operation root */
+        repos_prefix_path =
+          svn_relpath_join(svn_dirent_basename(current_relpath, NULL),
+                           repos_prefix_path,
+                           scratch_pool);
+        current_relpath = svn_relpath_dirname(current_relpath, scratch_pool);
+      }
+
+    if (op_root_relpath)
+      *op_root_relpath = apr_pstrdup(result_pool, current_relpath);
+
+    if (original_repos_relpath
+        || original_repos_id
+        || status)
+      {
+        if (local_relpath != current_relpath)
+          /* requery to get the add/copy root */
+          {
+            SVN_ERR(svn_sqlite__reset(stmt));
+
+            SVN_ERR(svn_sqlite__bindf(stmt, "is",
+                                      wcroot->wc_id, current_relpath));
+            SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+            if (!have_row)
+              {
+                /* Reset statement before returning */
+                SVN_ERR(svn_sqlite__reset(stmt));
+
+                /* ### maybe we should return a usage error instead?  */
+                return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+                                         _("The node '%s' was not found."),
+                                         path_for_error_message(wcroot,
+                                                                current_relpath,
+                                                                scratch_pool));
+              }
+          }
+
+        /* current_relpath / current_abspath
+           as well as the record in stmt contain the data of the op_root */
+        if (original_repos_relpath)
+          *original_repos_relpath = svn_sqlite__column_text(stmt, 11,
+                                                            result_pool);
+
+        if (!svn_sqlite__column_is_null(stmt, 10)
+            && (status
+                || original_repos_id))
+          /* If column 10 (original_repos_id) is NULL,
+             this is a plain add, not a copy or a move */
+          {
+            if (original_repos_id)
+              *original_repos_id = svn_sqlite__column_int64(stmt, 10);
+
+            if (status)
+              {
+                if (svn_sqlite__column_boolean(stmt, 13 /* moved_here */))
+                  *status = svn_wc__db_status_moved_here;
+                else
+                  *status = svn_wc__db_status_copied;
+              }
+          }
+      }
+
+
+    /* ### This loop here is to skip up to the first node which is a BASE node,
+       because scan_upwards_for_repos() doesn't accomodate the scenario that
+       we're looking at here; we found the true op_root, which may be inside
+       further changed trees. */
+    while (TRUE)
+      {
+
+        SVN_ERR(svn_sqlite__reset(stmt));
+
+        /* Pointing at op_depth, look at the parent */
+        repos_prefix_path =
+          svn_relpath_join(svn_dirent_basename(current_relpath, NULL),
+                           repos_prefix_path,
+                           scratch_pool);
+        current_relpath = svn_relpath_dirname(current_relpath, scratch_pool);
+
+
+        SVN_ERR(svn_sqlite__bindf(stmt, "is", wcroot->wc_id, current_relpath));
+        SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+        if (! have_row)
+          break;
+
+        op_depth = svn_sqlite__column_int64(stmt, 0);
+
+        /* Skip to op_depth */
+        for (i = relpath_depth(current_relpath); i > op_depth; i--)
+          {
+            /* Calculate the path of the operation root */
+            repos_prefix_path =
+              svn_relpath_join(svn_dirent_basename(current_relpath, NULL),
+                               repos_prefix_path,
+                               scratch_pool);
+            current_relpath =
+              svn_relpath_dirname(current_relpath, scratch_pool);
+          }
+      }
+
+    SVN_ERR(svn_sqlite__reset(stmt));
 
+    build_relpath = repos_prefix_path;
+  }
+#endif
   /* If we're here, then we have an added/copied/moved (start) node, and
      CURRENT_ABSPATH now points to a BASE node. Figure out the repository
      information for the current node, and use that to compute the start
      node's repository information.  */
-  if (repos_relpath || repos_root_url || repos_uuid)
+  if (repos_relpath || repos_id)
     {
-      apr_int64_t repos_id;
       const char *base_relpath;
 
-      SVN_ERR(scan_upwards_for_repos(&repos_id, &base_relpath,
+      SVN_ERR(scan_upwards_for_repos(repos_id, &base_relpath,
                                      pdh->wcroot, current_relpath,
                                      scratch_pool, scratch_pool));
 
-      if (repos_root_url || repos_uuid)
-        SVN_ERR(fetch_repos_info(repos_root_url, repos_uuid, pdh->wcroot->sdb,
-                                 repos_id, result_pool));
-
       if (repos_relpath)
         *repos_relpath = svn_relpath_join(base_relpath, build_relpath,
                                           result_pool);
@@ -6305,47 +6800,99 @@ svn_wc__db_scan_addition(svn_wc__db_stat
 
 
 svn_error_t *
-svn_wc__db_scan_deletion(const char **base_del_abspath,
-                         svn_boolean_t *base_replaced,
-                         const char **moved_to_abspath,
-                         const char **work_del_abspath,
+svn_wc__db_scan_addition(svn_wc__db_status_t *status,
+                         const char **op_root_abspath,
+                         const char **repos_relpath,
+                         const char **repos_root_url,
+                         const char **repos_uuid,
+                         const char **original_repos_relpath,
+                         const char **original_root_url,
+                         const char **original_uuid,
+                         svn_revnum_t *original_revision,
                          svn_wc__db_t *db,
                          const char *local_abspath,
                          apr_pool_t *result_pool,
                          apr_pool_t *scratch_pool)
 {
-  const char *current_abspath = local_abspath;

[... 417 lines stripped ...]