You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by hw...@apache.org on 2010/08/11 00:07:31 UTC

svn commit: r984234 [15/20] - in /subversion/branches/ignore-mergeinfo: ./ build/ build/ac-macros/ build/generator/ notes/ notes/api-errata/ notes/obliterate/ notes/obliterate/fspec-cc1/ notes/rename-tracking/ notes/svnpatch/ notes/tree-conflicts/ note...

Modified: subversion/branches/ignore-mergeinfo/subversion/libsvn_wc/wc_db.c
URL: http://svn.apache.org/viewvc/subversion/branches/ignore-mergeinfo/subversion/libsvn_wc/wc_db.c?rev=984234&r1=984233&r2=984234&view=diff
==============================================================================
--- subversion/branches/ignore-mergeinfo/subversion/libsvn_wc/wc_db.c (original)
+++ subversion/branches/ignore-mergeinfo/subversion/libsvn_wc/wc_db.c Tue Aug 10 22:07:24 2010
@@ -289,7 +289,7 @@ escape_sqlite_like(const char * const st
    Any other allocations are made in SCRATCH_POOL. */
 static svn_error_t *
 get_pristine_fname(const char **pristine_abspath,
-                   svn_wc__db_pdh_t *pdh,
+                   const char *wcroot_abspath,
                    const svn_checksum_t *sha1_checksum,
                    svn_boolean_t create_subdir,
                    apr_pool_t *result_pool,
@@ -303,7 +303,7 @@ get_pristine_fname(const char **pristine
 
   /* ### code is in transition. make sure we have the proper data.  */
   SVN_ERR_ASSERT(pristine_abspath != NULL);
-  SVN_ERR_ASSERT(pdh->wcroot != NULL);
+  SVN_ERR_ASSERT(svn_dirent_is_absolute(wcroot_abspath));
   SVN_ERR_ASSERT(sha1_checksum != NULL);
   SVN_ERR_ASSERT(sha1_checksum->kind == svn_checksum_sha1);
 
@@ -311,7 +311,7 @@ get_pristine_fname(const char **pristine
      ### to use join_many since we know "/" is the separator for
      ### internal canonical paths */
   base_dir_abspath = svn_dirent_join_many(scratch_pool,
-                                          pdh->wcroot->abspath,
+                                          wcroot_abspath,
                                           svn_wc_get_adm_dir(scratch_pool),
                                           PRISTINE_STORAGE_RELPATH,
                                           NULL);
@@ -472,7 +472,7 @@ scan_upwards_for_repos(apr_int64_t *repo
 
       /* 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_relpath, &current_basename,
+      svn_relpath_split(&current_relpath, &current_basename, current_relpath,
                         scratch_pool);
       relpath_suffix = svn_relpath_join(relpath_suffix, current_basename,
                                         scratch_pool);
@@ -522,22 +522,45 @@ navigate_to_parent(svn_wc__db_pdh_t **pa
                    svn_wc__db_t *db,
                    svn_wc__db_pdh_t *child_pdh,
                    svn_sqlite__mode_t smode,
+                   svn_boolean_t verify_parent_stub,
                    apr_pool_t *scratch_pool)
 {
   const char *parent_abspath;
   const char *local_relpath;
+  svn_sqlite__stmt_t *stmt;
+  svn_boolean_t got_row;
 
   if ((*parent_pdh = child_pdh->parent) != NULL
       && (*parent_pdh)->wcroot != NULL)
     return SVN_NO_ERROR;
 
+  /* Make sure we don't see the root as its own parent */
+  SVN_ERR_ASSERT(!svn_dirent_is_root(child_pdh->local_abspath,
+                                     strlen(child_pdh->local_abspath)));
+
   parent_abspath = svn_dirent_dirname(child_pdh->local_abspath, scratch_pool);
   SVN_ERR(svn_wc__db_pdh_parse_local_abspath(parent_pdh, &local_relpath, db,
                               parent_abspath, smode,
                               scratch_pool, scratch_pool));
   VERIFY_USABLE_PDH(*parent_pdh);
 
-  child_pdh->parent = *parent_pdh;
+  /* Check that the parent has an entry for the child */
+  SVN_ERR(svn_sqlite__get_statement(&stmt, (*parent_pdh)->wcroot->sdb,
+                                    STMT_SELECT_SUBDIR));
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", (*parent_pdh)->wcroot->wc_id,
+                            svn_dirent_basename(child_pdh->local_abspath,
+                                                NULL)));
+  SVN_ERR(svn_sqlite__step(&got_row, stmt));
+  SVN_ERR(svn_sqlite__reset(stmt));
+
+  if (!got_row && verify_parent_stub)
+    return svn_error_createf(SVN_ERR_WC_NOT_WORKING_COPY, NULL,
+                              _("'%s' does not have a parent."),
+                              svn_dirent_local_style(child_pdh->local_abspath,
+                                                     scratch_pool));
+
+  if (got_row)
+    child_pdh->parent = *parent_pdh;
 
   return SVN_NO_ERROR;
 }
@@ -668,9 +691,9 @@ insert_base_node(void *baton, svn_sqlite
 
           SVN_ERR(svn_sqlite__bindf(stmt, "issi",
                                     pibb->wc_id,
-                                    svn_dirent_join(pibb->local_relpath,
-                                                    name,
-                                                    scratch_pool),
+                                    svn_relpath_join(pibb->local_relpath,
+                                                     name,
+                                                     scratch_pool),
                                     pibb->local_relpath,
                                     (apr_int64_t)pibb->revision));
           SVN_ERR(svn_sqlite__insert(NULL, stmt));
@@ -694,6 +717,33 @@ blank_iwb(insert_working_baton_t *piwb)
      value, but... meh. We'll avoid them if ORIGINAL_REPOS_RELPATH==NULL.  */
 }
 
+static svn_error_t *
+insert_incomplete_working_children(svn_sqlite__db_t *sdb,
+                                   apr_int64_t wc_id,
+                                   const char *local_relpath,
+                                   const apr_array_header_t *children,
+                                   apr_pool_t *scratch_pool)
+{
+  svn_sqlite__stmt_t *stmt;
+  int i;
+
+  SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+                                    STMT_INSERT_WORKING_NODE_INCOMPLETE));
+
+  for (i = children->nelts; i--; )
+    {
+      const char *name = APR_ARRAY_IDX(children, i, const char *);
+
+      SVN_ERR(svn_sqlite__bindf(stmt, "iss",
+                                wc_id,
+                                svn_relpath_join(local_relpath, name,
+                                                 scratch_pool),
+                                local_relpath));
+      SVN_ERR(svn_sqlite__insert(NULL, stmt));
+    }
+
+  return SVN_NO_ERROR;
+}
 
 /* */
 static svn_error_t *
@@ -771,66 +821,120 @@ insert_working_node(void *baton,
   SVN_ERR(svn_sqlite__insert(NULL, stmt));
 
   if (piwb->kind == svn_wc__db_kind_dir && piwb->children)
-    {
-      int i;
+    SVN_ERR(insert_incomplete_working_children(sdb, piwb->wc_id,
+                                               piwb->local_relpath,
+                                               piwb->children,
+                                               scratch_pool));
 
-      SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
-                                        STMT_INSERT_WORKING_NODE_INCOMPLETE));
+  SVN_ERR(add_work_items(sdb, piwb->work_items, scratch_pool));
 
-      for (i = piwb->children->nelts; i--; )
-        {
-          const char *name = APR_ARRAY_IDX(piwb->children, i, const char *);
+  return SVN_NO_ERROR;
+}
 
-          SVN_ERR(svn_sqlite__bindf(stmt, "iss",
-                                    piwb->wc_id,
-                                    svn_relpath_join(piwb->local_relpath,
-                                                     name,
-                                                     scratch_pool),
-                                    piwb->local_relpath));
-          SVN_ERR(svn_sqlite__insert(NULL, stmt));
-        }
+
+/* Return the number of children under PARENT_RELPATH in the given WC_ID.
+   The table is implicitly defined by the STMT_IDX query.  */
+static svn_error_t *
+count_children(int *count,
+               int stmt_idx,
+               svn_sqlite__db_t *sdb,
+               apr_int64_t wc_id,
+               const char *parent_relpath)
+{
+  svn_sqlite__stmt_t *stmt;
+
+  SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, stmt_idx));
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, parent_relpath));
+  SVN_ERR(svn_sqlite__step_row(stmt));
+  *count = svn_sqlite__column_int(stmt, 0);
+  return svn_error_return(svn_sqlite__reset(stmt));
+}
+
+
+/* Each name is allocated in RESULT_POOL and stored into CHILDREN as a key
+   pointed to the same name.  */
+static svn_error_t *
+add_children_to_hash(apr_hash_t *children,
+                     int stmt_idx,
+                     svn_sqlite__db_t *sdb,
+                     apr_int64_t wc_id,
+                     const char *parent_relpath,
+                     apr_pool_t *result_pool)
+{
+  svn_sqlite__stmt_t *stmt;
+  svn_boolean_t have_row;
+
+  SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, stmt_idx));
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, parent_relpath));
+  SVN_ERR(svn_sqlite__step(&have_row, stmt));
+  while (have_row)
+    {
+      const char *child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+      const char *name = svn_relpath_basename(child_relpath, result_pool);
+
+      apr_hash_set(children, name, APR_HASH_KEY_STRING, name);
+
+      SVN_ERR(svn_sqlite__step(&have_row, stmt));
     }
 
-  SVN_ERR(add_work_items(sdb, piwb->work_items, scratch_pool));
+  return svn_sqlite__reset(stmt);
+}
+
+
+/* When children of PARENT_RELPATH are in both BASE_NODE and WORKING_NODE,
+   this function can be used to union those two sets, returning the set
+   in *CHILDREN (allocated in RESULT_POOL).  */
+static svn_error_t *
+union_children(const apr_array_header_t **children,
+               svn_sqlite__db_t *sdb,
+               apr_int64_t wc_id,
+               const char *parent_relpath,
+               apr_pool_t *result_pool,
+               apr_pool_t *scratch_pool)
+{
+  /* ### it would be nice to pre-size this hash table.  */
+  apr_hash_t *names = apr_hash_make(scratch_pool);
+  apr_array_header_t *names_array;
+
+  /* All of the names get allocated in RESULT_POOL.  */
+  SVN_ERR(add_children_to_hash(names, STMT_SELECT_BASE_NODE_CHILDREN,
+                               sdb, wc_id, parent_relpath, result_pool));
+  SVN_ERR(add_children_to_hash(names, STMT_SELECT_WORKING_NODE_CHILDREN,
+                               sdb, wc_id, parent_relpath, result_pool));
+
+  SVN_ERR(svn_hash_keys(&names_array, names, result_pool));
+  *children = names_array;
 
   return SVN_NO_ERROR;
 }
 
 
-/* */
+/* Return all the children of PARENT_RELPATH from a single table, implicitly
+   defined by STMT_IDX. If the caller happens to know the count of children,
+   it should be passed as START_SIZE to pre-allocate space in the *CHILDREN
+   return value.
+
+   If the caller doesn't know the count, then it should pass a reasonable
+   idea of how many children may be present.  */
 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,
-                apr_pool_t *result_pool,
-                apr_pool_t *scratch_pool)
+single_table_children(const apr_array_header_t **children,
+                      int stmt_idx,
+                      int start_size,
+                      svn_sqlite__db_t *sdb,
+                      apr_int64_t wc_id,
+                      const char *parent_relpath,
+                      apr_pool_t *result_pool)
 {
-  svn_wc__db_pdh_t *pdh;
-  const char *local_relpath;
   svn_sqlite__stmt_t *stmt;
   apr_array_header_t *child_names;
   svn_boolean_t have_row;
 
-  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);
-
-  SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->wcroot->sdb,
-                                    base_only
-                                      ? STMT_SELECT_BASE_NODE_CHILDREN
-                                      : STMT_SELECT_WORKING_CHILDREN));
-  SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wcroot->wc_id, local_relpath));
+  SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, stmt_idx));
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, parent_relpath));
 
   /* ### should test the node to ensure it is a directory */
 
-  /* ### 10 is based on Subversion's average of 8.5 files per versioned
-     ### directory in its repository. maybe use a different value? or
-     ### count rows first?  */
-  child_names = apr_array_make(result_pool, 10, sizeof(const char *));
+  child_names = apr_array_make(result_pool, start_size, sizeof(const char *));
 
   SVN_ERR(svn_sqlite__step(&have_row, stmt));
   while (have_row)
@@ -849,6 +953,79 @@ gather_children(const apr_array_header_t
 }
 
 
+/* Return in *CHILDREN all of the children of the directory LOCAL_ABSPATH.
+   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.  */
+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,
+                apr_pool_t *result_pool,
+                apr_pool_t *scratch_pool)
+{
+  svn_wc__db_pdh_t *pdh;
+  const char *local_relpath;
+  int base_count;
+  int working_count;
+
+  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);
+
+  if (base_only)
+    {
+      /* 10 is based on Subversion's average of 8.7 files per versioned
+         directory in its repository.
+
+         ### note "files". should redo count with subdirs included */
+      return svn_error_return(single_table_children(
+                                children, STMT_SELECT_BASE_NODE_CHILDREN,
+                                10 /* start_size */,
+                                pdh->wcroot->sdb, pdh->wcroot->wc_id,
+                                local_relpath, result_pool));
+    }
+
+  SVN_ERR(count_children(&base_count, STMT_COUNT_BASE_NODE_CHILDREN,
+                         pdh->wcroot->sdb, pdh->wcroot->wc_id, local_relpath));
+  SVN_ERR(count_children(&working_count, STMT_COUNT_WORKING_NODE_CHILDREN,
+                         pdh->wcroot->sdb, pdh->wcroot->wc_id, local_relpath));
+
+  if (base_count == 0)
+    {
+      if (working_count == 0)
+        {
+          *children = apr_array_make(result_pool, 0, sizeof(const char *));
+          return SVN_NO_ERROR;
+        }
+
+      return svn_error_return(single_table_children(
+                                children, STMT_SELECT_WORKING_NODE_CHILDREN,
+                                working_count,
+                                pdh->wcroot->sdb, pdh->wcroot->wc_id,
+                                local_relpath, result_pool));
+    }
+  if (working_count == 0)
+    return svn_error_return(single_table_children(
+                              children, STMT_SELECT_BASE_NODE_CHILDREN,
+                              base_count,
+                              pdh->wcroot->sdb, pdh->wcroot->wc_id,
+                              local_relpath, result_pool));
+
+  /* ### it would be nice to pass BASE_COUNT and WORKING_COUNT, but there is
+     ### nothing union_children() can do with those.  */
+  return svn_error_return(union_children(children, 
+                                         pdh->wcroot->sdb, pdh->wcroot->wc_id,
+                                         local_relpath,
+                                         result_pool, scratch_pool));
+}
+
+
 /* */
 static void
 flush_entries(const svn_wc__db_pdh_t *pdh)
@@ -955,6 +1132,70 @@ which_trees_exist(svn_boolean_t *base_ex
 }
 
 
+/* Determine which trees' nodes exist for a given LOCAL_RELPATH in the
+   specified SDB.
+
+   Note: this is VERY similar to the above which_trees_exist() except that
+   we return a WC_ID and verify some additional constraints.  */
+static svn_error_t *
+prop_upgrade_trees(svn_boolean_t *base_exists,
+                   svn_boolean_t *working_exists,
+                   svn_wc__db_status_t *work_presence,
+                   apr_int64_t *wc_id,
+                   svn_sqlite__db_t *sdb,
+                   const char *local_relpath)
+{
+  svn_sqlite__stmt_t *stmt;
+  svn_boolean_t have_row;
+
+  *base_exists = FALSE;
+  *working_exists = FALSE;
+
+  SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_PLAN_PROP_UPGRADE));
+  SVN_ERR(svn_sqlite__bindf(stmt, "s", local_relpath));
+
+  SVN_ERR(svn_sqlite__step(&have_row, stmt));
+
+  /* During a property upgrade, there better be a row corresponding to
+     the provided LOCAL_RELPATH. We shouldn't even be here without a
+     query for available rows.  */
+  SVN_ERR_ASSERT(have_row);
+
+  /* Use the first column to detect which table this row came from.  */
+  if (svn_sqlite__column_int(stmt, 0))
+    {
+      *working_exists = TRUE;  /* value == 1  */
+      *work_presence = svn_sqlite__column_token(stmt, 1, presence_map);
+    }
+  else
+    {
+      *base_exists = TRUE;  /* value == 0  */
+    }
+
+  /* Return the WC_ID that was assigned.  */
+  *wc_id = svn_sqlite__column_int64(stmt, 2);
+
+  SVN_ERR(svn_sqlite__step(&have_row, stmt));
+  if (have_row)
+    {
+      /* If both rows, then both tables.  */
+      *base_exists = TRUE;
+      *working_exists = TRUE;
+
+      /* If the second row came from WORKING_NODE, then we should also
+         fetch the 'presence' column value.  */
+      if (svn_sqlite__column_int(stmt, 0))
+        *work_presence = svn_sqlite__column_token(stmt, 1, presence_map);
+
+      /* During an upgrade, there should be just one working copy, so both
+         rows should refer to the same value.  */
+      SVN_ERR_ASSERT(*wc_id == svn_sqlite__column_int64(stmt, 2));
+    }
+
+  return svn_error_return(svn_sqlite__reset(stmt));
+}
+
+
 /* */
 static svn_error_t *
 create_db(svn_sqlite__db_t **sdb,
@@ -1386,6 +1627,33 @@ svn_wc__db_base_add_absent_node(svn_wc__
                                        scratch_pool));
 
   flush_entries(pdh);
+
+  if (*local_relpath == '\0')
+    {
+      SVN_ERR(navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readwrite,
+                                 FALSE, scratch_pool));
+
+      VERIFY_USABLE_PDH(pdh);
+
+      SVN_ERR(create_repos_id(&repos_id, repos_root_url, repos_uuid,
+                          pdh->wcroot->sdb, scratch_pool));
+
+      blank_ibb(&ibb);
+      
+      ibb.status = status;
+      ibb.kind = svn_wc__db_kind_subdir;
+      ibb.wc_id = pdh->wcroot->wc_id;
+      ibb.local_relpath = svn_dirent_basename(local_abspath, scratch_pool);
+      ibb.repos_id = repos_id;
+      ibb.repos_relpath = repos_relpath;
+      ibb.revision = revision;
+
+      SVN_ERR(svn_sqlite__with_transaction(pdh->wcroot->sdb,
+                                           insert_base_node, &ibb,
+                                           scratch_pool));
+
+      flush_entries(pdh);
+    }
   return SVN_NO_ERROR;
 }
 
@@ -1473,6 +1741,32 @@ svn_wc__db_base_remove(svn_wc__db_t *db,
 
   flush_entries(pdh);
 
+#ifndef SINGLE_DB
+  if (*local_relpath == '\0')
+    {
+      svn_error_t *err;
+      err = navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readwrite,
+                               TRUE, scratch_pool);
+
+      if (!err)
+        {
+          SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->wcroot->sdb,
+                                            STMT_DELETE_BASE_NODE));
+          SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wcroot->wc_id,
+                                    svn_dirent_basename(local_abspath,
+                                                        scratch_pool)));
+
+          SVN_ERR(svn_sqlite__step_done(stmt));
+
+          flush_entries(pdh);
+        }
+      else if (err && err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
+        svn_error_clear(err);
+      else
+        SVN_ERR(err);
+    }
+#endif
+
   return SVN_NO_ERROR;
 }
 
@@ -1663,66 +1957,168 @@ svn_wc__db_base_get_info(svn_wc__db_stat
   return svn_error_compose_create(err, svn_sqlite__reset(stmt));
 }
 
-
 svn_error_t *
-svn_wc__db_base_get_prop(const svn_string_t **propval,
-                         svn_wc__db_t *db,
-                         const char *local_abspath,
-                         const char *propname,
-                         apr_pool_t *result_pool,
-                         apr_pool_t *scratch_pool)
+svn_wc__db_base_get_info_from_parent(svn_wc__db_status_t *status,
+                                     svn_wc__db_kind_t *kind,
+                                     svn_revnum_t *revision,
+                                     const char **repos_relpath,
+                                     const char **repos_root_url,
+                                     const char **repos_uuid,
+                                     svn_wc__db_t *db,
+                                     const char *local_abspath,
+                                     apr_pool_t *result_pool,
+                                     apr_pool_t *scratch_pool)
 {
-  apr_hash_t *props;
+  svn_wc__db_pdh_t *pdh;
+  const char *local_relpath;
+  svn_sqlite__stmt_t *stmt;
+  svn_boolean_t have_row;
+  svn_error_t *err = SVN_NO_ERROR;
+  const char *parent_abspath;
 
   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
-  SVN_ERR_ASSERT(propname != NULL);
-
-  /* Note: maybe one day, we'll have internal caches of this stuff, but
-     for now, we just grab all the props and pick out the requested prop. */
 
-  /* ### should: fetch into scratch_pool, then dup into result_pool.  */
-  SVN_ERR(svn_wc__db_base_get_props(&props, db, local_abspath,
-                                    result_pool, scratch_pool));
-
-  *propval = apr_hash_get(props, propname, APR_HASH_KEY_STRING);
-
-  return SVN_NO_ERROR;
-}
+  parent_abspath = svn_dirent_dirname(local_abspath, scratch_pool);
 
+  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &local_relpath, db,
+                              parent_abspath, svn_sqlite__mode_readonly,
+                              scratch_pool, scratch_pool));
+  VERIFY_USABLE_PDH(pdh);
 
-svn_error_t *
-svn_wc__db_base_get_props(apr_hash_t **props,
-                          svn_wc__db_t *db,
-                          const char *local_abspath,
-                          apr_pool_t *result_pool,
-                          apr_pool_t *scratch_pool)
-{
-  svn_sqlite__stmt_t *stmt;
-  svn_boolean_t have_row;
-  svn_error_t *err;
+  local_relpath = svn_relpath_join(local_relpath,
+                                   svn_dirent_basename(local_abspath, NULL),
+                                   scratch_pool);
 
-  SVN_ERR(get_statement_for_path(&stmt, db, local_abspath,
-                                 STMT_SELECT_BASE_PROPS, scratch_pool));
+  SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->wcroot->sdb,
+                                    STMT_SELECT_BASE_NODE));
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wcroot->wc_id, local_relpath));
   SVN_ERR(svn_sqlite__step(&have_row, stmt));
-  if (!have_row)
-    {
-      err = svn_sqlite__reset(stmt);
-      return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, err,
-                               _("The node '%s' was not found."),
-                               svn_dirent_local_style(local_abspath,
-                                                      scratch_pool));
-    }
 
-  err = svn_sqlite__column_properties(props, stmt, 0, result_pool,
-                                      scratch_pool);
-  if (err == NULL && *props == NULL)
+  if (have_row)
     {
-      /* ### is this a DB constraint violation? the column "probably" should
-         ### never be null.  */
-      *props = apr_hash_make(result_pool);
-    }
+      svn_wc__db_kind_t node_kind = svn_sqlite__column_token(stmt, 3,
+                                                             kind_map);
 
-  return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+      if (kind)
+        {
+          if (node_kind == svn_wc__db_kind_subdir)
+            *kind = svn_wc__db_kind_dir;
+          else
+            *kind = node_kind;
+        }
+      if (status)
+        {
+          *status = svn_sqlite__column_token(stmt, 2, presence_map);
+
+          if (node_kind == svn_wc__db_kind_subdir
+              && *status == svn_wc__db_status_normal)
+            {
+              /* We're looking at the subdir record in the *parent* directory,
+                 which implies per-dir .svn subdirs. We should be looking
+                 at the subdir itself; therefore, it is missing or obstructed
+                 in some way. Inform the caller.  */
+              *status = svn_wc__db_status_obstructed;
+            }
+        }
+      if (revision)
+        {
+          *revision = svn_sqlite__column_revnum(stmt, 4);
+        }
+      if (repos_relpath)
+        {
+          *repos_relpath = svn_sqlite__column_text(stmt, 1, result_pool);
+        }
+      if (repos_root_url || repos_uuid)
+        {
+          /* Fetch repository information via REPOS_ID. */
+          if (svn_sqlite__column_is_null(stmt, 0))
+            {
+              if (repos_root_url)
+                *repos_root_url = NULL;
+              if (repos_uuid)
+                *repos_uuid = NULL;
+            }
+          else
+            {
+              err = fetch_repos_info(repos_root_url, repos_uuid,
+                                     pdh->wcroot->sdb,
+                                     svn_sqlite__column_int64(stmt, 0),
+                                     result_pool);
+            }
+        }
+    }
+  else
+    {
+      err = svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, NULL,
+                              _("The node '%s' was not found."),
+                              svn_dirent_local_style(local_abspath,
+                                                     scratch_pool));
+    }
+
+  /* Note: given the composition, no need to wrap for tracing.  */
+  return svn_error_compose_create(err, svn_sqlite__reset(stmt));
+}
+
+
+svn_error_t *
+svn_wc__db_base_get_prop(const svn_string_t **propval,
+                         svn_wc__db_t *db,
+                         const char *local_abspath,
+                         const char *propname,
+                         apr_pool_t *result_pool,
+                         apr_pool_t *scratch_pool)
+{
+  apr_hash_t *props;
+
+  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
+  SVN_ERR_ASSERT(propname != NULL);
+
+  /* Note: maybe one day, we'll have internal caches of this stuff, but
+     for now, we just grab all the props and pick out the requested prop. */
+
+  /* ### should: fetch into scratch_pool, then dup into result_pool.  */
+  SVN_ERR(svn_wc__db_base_get_props(&props, db, local_abspath,
+                                    result_pool, scratch_pool));
+
+  *propval = apr_hash_get(props, propname, APR_HASH_KEY_STRING);
+
+  return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_wc__db_base_get_props(apr_hash_t **props,
+                          svn_wc__db_t *db,
+                          const char *local_abspath,
+                          apr_pool_t *result_pool,
+                          apr_pool_t *scratch_pool)
+{
+  svn_sqlite__stmt_t *stmt;
+  svn_boolean_t have_row;
+  svn_error_t *err;
+
+  SVN_ERR(get_statement_for_path(&stmt, db, local_abspath,
+                                 STMT_SELECT_BASE_PROPS, scratch_pool));
+  SVN_ERR(svn_sqlite__step(&have_row, stmt));
+  if (!have_row)
+    {
+      err = svn_sqlite__reset(stmt);
+      return svn_error_createf(SVN_ERR_WC_PATH_NOT_FOUND, err,
+                               _("The node '%s' was not found."),
+                               svn_dirent_local_style(local_abspath,
+                                                      scratch_pool));
+    }
+
+  err = svn_sqlite__column_properties(props, stmt, 0, result_pool,
+                                      scratch_pool);
+  if (err == NULL && *props == NULL)
+    {
+      /* ### is this a DB constraint violation? the column "probably" should
+         ### never be null.  */
+      *props = apr_hash_make(result_pool);
+    }
+
+  return svn_error_compose_create(err, svn_sqlite__reset(stmt));
 }
 
 
@@ -1783,6 +2179,36 @@ svn_wc__db_base_get_dav_cache(apr_hash_t
 
 
 svn_error_t *
+svn_wc__db_base_clear_dav_cache_recursive(svn_wc__db_t *db,
+                                          const char *local_abspath,
+                                          apr_pool_t *scratch_pool)
+{
+  svn_wc__db_pdh_t *pdh;
+  const char *local_relpath;
+  const char *like_arg;
+  svn_sqlite__stmt_t *stmt;
+
+  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &local_relpath,
+                                             db, local_abspath,
+                                             svn_sqlite__mode_readwrite,
+                                             scratch_pool, scratch_pool));
+  VERIFY_USABLE_PDH(pdh);
+
+  if (local_relpath[0] == 0)
+    like_arg = "%";
+  else
+    like_arg = apr_pstrcat(scratch_pool,
+                           escape_sqlite_like(local_relpath, scratch_pool),
+                           "/%", NULL);
+
+  SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->wcroot->sdb,
+                                    STMT_CLEAR_BASE_RECURSIVE_DAV_CACHE));
+  SVN_ERR(svn_sqlite__bindf(stmt, "iss", pdh->wcroot->wc_id, local_relpath,
+                            like_arg));
+  return svn_error_return(svn_sqlite__step_done(stmt));
+}
+
+svn_error_t *
 svn_wc__db_pristine_get_path(const char **pristine_abspath,
                              svn_wc__db_t *db,
                              const char *wri_abspath,
@@ -1792,6 +2218,7 @@ svn_wc__db_pristine_get_path(const char 
 {
   svn_wc__db_pdh_t *pdh;
   const char *local_relpath;
+  svn_boolean_t present;
 
   SVN_ERR_ASSERT(pristine_abspath != NULL);
   SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
@@ -1810,15 +2237,34 @@ svn_wc__db_pristine_get_path(const char 
                                              scratch_pool, scratch_pool));
   VERIFY_USABLE_PDH(pdh);
 
-  /* ### should we look in the PRISTINE table for anything?  */
+  SVN_ERR(svn_wc__db_pristine_check(&present, db, wri_abspath, sha1_checksum,
+                                    svn_wc__db_checkmode_usable,
+                                    scratch_pool));
+  if (! present)
+    return svn_error_createf(SVN_ERR_WC_DB_ERROR, NULL,
+                             _("Pristine text not found"));
 
-  SVN_ERR(get_pristine_fname(pristine_abspath, pdh, sha1_checksum,
+  SVN_ERR(get_pristine_fname(pristine_abspath, pdh->wcroot->abspath,
+                             sha1_checksum,
                              FALSE /* create_subdir */,
-                             scratch_pool, scratch_pool));
+                             result_pool, scratch_pool));
 
   return SVN_NO_ERROR;
 }
 
+svn_error_t *
+svn_wc__db_pristine_get_future_path(const char **pristine_abspath,
+                                    const char *wcroot_abspath,
+                                    svn_checksum_t *sha1_checksum,
+                                    apr_pool_t *result_pool,
+                                    apr_pool_t *scratch_pool)
+{
+  SVN_ERR(get_pristine_fname(pristine_abspath, wcroot_abspath,
+                             sha1_checksum,
+                             FALSE /* create_subdir */,
+                             result_pool, scratch_pool));
+  return SVN_NO_ERROR;
+}
 
 svn_error_t *
 svn_wc__db_pristine_read(svn_stream_t **contents,
@@ -1850,7 +2296,8 @@ svn_wc__db_pristine_read(svn_stream_t **
 
   /* ### should we look in the PRISTINE table for anything?  */
 
-  SVN_ERR(get_pristine_fname(&pristine_abspath, pdh, sha1_checksum,
+  SVN_ERR(get_pristine_fname(&pristine_abspath, pdh->wcroot->abspath,
+                             sha1_checksum,
                              FALSE /* create_subdir */,
                              scratch_pool, scratch_pool));
   return svn_error_return(svn_stream_open_readonly(
@@ -1919,7 +2366,8 @@ svn_wc__db_pristine_install(svn_wc__db_t
                               scratch_pool, scratch_pool));
   VERIFY_USABLE_PDH(pdh);
 
-  SVN_ERR(get_pristine_fname(&pristine_abspath, pdh, sha1_checksum,
+  SVN_ERR(get_pristine_fname(&pristine_abspath, pdh->wcroot->abspath,
+                             sha1_checksum,
                              TRUE /* create_subdir */,
                              scratch_pool, scratch_pool));
 
@@ -2078,7 +2526,8 @@ svn_wc__db_pristine_remove(svn_wc__db_t 
       SVN_ERR(svn_sqlite__update(NULL, stmt));
 
       /* Remove the file */
-      SVN_ERR(get_pristine_fname(&pristine_abspath, pdh, sha1_checksum,
+      SVN_ERR(get_pristine_fname(&pristine_abspath, pdh->wcroot->abspath,
+                                 sha1_checksum,
                                  TRUE /* create_subdir */,
                                  scratch_pool, scratch_pool));
       SVN_ERR(svn_io_remove_file2(pristine_abspath, TRUE, scratch_pool));
@@ -2089,6 +2538,45 @@ svn_wc__db_pristine_remove(svn_wc__db_t 
 
 
 svn_error_t *
+svn_wc__db_pristine_cleanup(svn_wc__db_t *db,
+                            const char *wri_abspath,
+                            apr_pool_t *scratch_pool)
+{
+  svn_wc__db_pdh_t *pdh;
+  const char *local_relpath;
+  svn_sqlite__stmt_t *stmt;
+
+  SVN_ERR_ASSERT(svn_dirent_is_absolute(wri_abspath));
+
+  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &local_relpath, db,
+                              wri_abspath, svn_sqlite__mode_readonly,
+                              scratch_pool, scratch_pool));
+  VERIFY_USABLE_PDH(pdh);
+
+  /* Find the pristines in the DB */
+  SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->wcroot->sdb,
+                                    STMT_SELECT_PRISTINE_ROWS));
+  while (1)
+    {
+      svn_boolean_t have_row;
+      const svn_checksum_t *checksum;
+
+      SVN_ERR(svn_sqlite__step(&have_row, stmt));
+      if (! have_row)
+        break;
+
+      SVN_ERR(svn_sqlite__column_checksum(&checksum, stmt, 0,
+                                          scratch_pool));
+      SVN_ERR(svn_wc__db_pristine_remove(db, wri_abspath, checksum,
+                                         scratch_pool));
+    }
+  SVN_ERR(svn_sqlite__reset(stmt));
+
+  return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
 svn_wc__db_pristine_check(svn_boolean_t *present,
                           svn_wc__db_t *db,
                           const char *wri_abspath,
@@ -2127,7 +2615,8 @@ svn_wc__db_pristine_check(svn_boolean_t 
   SVN_ERR(svn_sqlite__reset(stmt));
 
   /* Check that the pristine text file exists. */
-  SVN_ERR(get_pristine_fname(&pristine_abspath, pdh, sha1_checksum,
+  SVN_ERR(get_pristine_fname(&pristine_abspath, pdh->wcroot->abspath,
+                             sha1_checksum,
                              FALSE /* create_subdir */,
                              scratch_pool, scratch_pool));
   SVN_ERR(svn_io_check_path(pristine_abspath, &kind_on_disk, scratch_pool));
@@ -2188,7 +2677,9 @@ temp_cross_db_copy(svn_wc__db_t *db,
                    const char *src_relpath,
                    svn_wc__db_pdh_t *dst_pdh,
                    const char *dst_relpath,
+                   svn_wc__db_status_t dst_status,
                    svn_wc__db_kind_t kind,
+                   const apr_array_header_t *children,
                    apr_int64_t copyfrom_id,
                    const char *copyfrom_relpath,
                    svn_revnum_t copyfrom_rev,
@@ -2202,37 +2693,41 @@ temp_cross_db_copy(svn_wc__db_t *db,
   apr_hash_t *props;
   svn_sqlite__stmt_t *stmt;
   svn_boolean_t have_row;
+  svn_depth_t depth;
 
-  SVN_ERR_ASSERT(kind == svn_wc__db_kind_file);
-
-  SVN_ERR(svn_wc__db_read_info(NULL, /* status */
-                               NULL, /* kind */
-                               NULL, /* revision */
-                               NULL, /* repos_relpath */
-                               NULL, /* repos_root_url */
-                               NULL, /* repos_uuid */
+  SVN_ERR_ASSERT(kind == svn_wc__db_kind_file
+                 || kind == svn_wc__db_kind_dir
+                 || kind == svn_wc__db_kind_subdir);
+
+  SVN_ERR(svn_wc__db_read_info(NULL /* status */,
+                               NULL /* kind */,
+                               NULL /* revision */,
+                               NULL /* repos_relpath */,
+                               NULL /* repos_root_url */,
+                               NULL /* repos_uuid */,
                                &changed_rev, &changed_date, &changed_author,
-                               NULL, /* last_mod_time */
-                               NULL, /* depth */
+                               NULL /* last_mod_time */,
+                               &depth,
                                &checksum,
-                               NULL, /* translated_size */
-                               NULL, /* target */
-                               NULL, /* changelist */
-                               NULL, /* original_repos_relpath */
-                               NULL, /* original_root_url */
-                               NULL, /* original_uuid */
-                               NULL, /* original_revision */
-                               NULL, /* text_mod */
-                               NULL, /* props_mod */
-                               NULL, /* base_shadowed */
-                               NULL, /* conflicted */
-                               NULL, /* lock */
+                               NULL /* translated_size */,
+                               NULL /* target */,
+                               NULL /* changelist */,
+                               NULL /* original_repos_relpath */,
+                               NULL /* original_root_url */,
+                               NULL /* original_uuid */,
+                               NULL /* original_revision */,
+                               NULL /* props_mod */,
+                               NULL /* have_base */,
+                               NULL /* have_work */,
+                               NULL /* conflicted */,
+                               NULL /* lock */,
                                db, src_abspath, scratch_pool, scratch_pool));
 
   SVN_ERR(svn_wc__get_pristine_props(&props, db, src_abspath,
                                      scratch_pool, scratch_pool));
 
-  iwb.presence = svn_wc__db_status_normal;
+  blank_iwb(&iwb);
+  iwb.presence = dst_status;
   iwb.kind = kind;
   iwb.wc_id = dst_pdh->wcroot->wc_id;
   iwb.local_relpath = dst_relpath;
@@ -2247,8 +2742,8 @@ temp_cross_db_copy(svn_wc__db_t *db,
   iwb.moved_here = FALSE;
 
   iwb.checksum = checksum;
-
-  iwb.work_items = NULL;
+  iwb.children = children;
+  iwb.depth = depth;
 
   SVN_ERR(insert_working_node(&iwb, dst_pdh->wcroot->sdb, scratch_pool));
 
@@ -2259,7 +2754,10 @@ temp_cross_db_copy(svn_wc__db_t *db,
   if (have_row)
     {
       /* const char *prop_reject = svn_sqlite__column_text(stmt, 0,
-                                                           scratch_pool); */
+                                                           scratch_pool);
+
+         ### STMT_INSERT_ACTUAL_NODE doesn't cover every column, it's
+         ### enough for some cases but will probably need to extended. */
       const char *changelist = svn_sqlite__column_text(stmt, 1, scratch_pool);
       const char *conflict_old = svn_sqlite__column_text(stmt, 2, scratch_pool);
       const char *conflict_new = svn_sqlite__column_text(stmt, 3, scratch_pool);
@@ -2268,6 +2766,8 @@ temp_cross_db_copy(svn_wc__db_t *db,
       const char *tree_conflict_data = svn_sqlite__column_text(stmt, 5,
                                                                scratch_pool);
       apr_size_t props_size;
+
+      /* No need to parse the properties when simply copying. */
       const char *properties = svn_sqlite__column_blob(stmt, 6, &props_size,
                                                        scratch_pool);
       SVN_ERR(svn_sqlite__reset(stmt));
@@ -2286,6 +2786,115 @@ temp_cross_db_copy(svn_wc__db_t *db,
   return SVN_NO_ERROR;
 }
 
+/* Set *COPYFROM_ID, *COPYFROM_RELPATH, *COPYFROM_REV to the values
+   appropriate for the copy. Also return *STATUS, *KIND and *HAVE_WORK
+   since they are available.  This is a helper for
+   svn_wc__db_op_copy. */
+static svn_error_t *
+get_info_for_copy(apr_int64_t *copyfrom_id,
+                  const char **copyfrom_relpath,
+                  svn_revnum_t *copyfrom_rev,
+                  svn_wc__db_status_t *status,
+                  svn_wc__db_kind_t *kind,
+                  svn_boolean_t *have_work,
+                  svn_wc__db_pdh_t *pdh,
+                  svn_wc__db_t *db,
+                  const char *local_abspath,
+                  apr_pool_t *result_pool,
+                  apr_pool_t *scratch_pool)
+{
+  const char *repos_relpath, *repos_root_url, *repos_uuid;
+  svn_revnum_t revision;
+
+  SVN_ERR(svn_wc__db_read_info(status, kind, &revision,
+                               &repos_relpath, &repos_root_url, &repos_uuid,
+                               NULL /* changed_rev */,
+                               NULL /* changed_date */,
+                               NULL /* changed_author */,
+                               NULL /* last_mod_time */,
+                               NULL /* depth */,
+                               NULL /* checksum */,
+                               NULL /* translated_size */,
+                               NULL /* target */,
+                               NULL /* changelist */,
+                               NULL /* original_repos_relpath */,
+                               NULL /* original_root_url */,
+                               NULL /* original_uuid */,
+                               NULL /* original_revision */,
+                               NULL /* props_mod */,
+                               NULL /* have_base */,
+                               have_work,
+                               NULL /* conflicted */,
+                               NULL /* lock */,
+                               db, local_abspath, result_pool, scratch_pool));
+
+  if (*status == svn_wc__db_status_excluded)
+    {
+      /* The parent cannot be excluded, so look at the parent and then
+         adjust the relpath */
+      const char *parent_abspath, *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_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, db, parent_abspath,
+                                scratch_pool, scratch_pool));
+      if (*copyfrom_relpath)
+        *copyfrom_relpath = svn_relpath_join(*copyfrom_relpath, base_name,
+                                             result_pool);
+    }
+  else if (*status != svn_wc__db_status_added)
+    {
+      *copyfrom_relpath = repos_relpath;
+      *copyfrom_rev = revision;
+      SVN_ERR(create_repos_id(copyfrom_id,
+                              repos_root_url, repos_uuid,
+                              pdh->wcroot->sdb, scratch_pool));
+    }
+  else
+    {
+      const char *op_root_abspath;
+      const char *original_repos_relpath, *original_root_url, *original_uuid;
+      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));
+
+      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),
+                               scratch_pool);
+          *copyfrom_rev = original_revision;
+          SVN_ERR(create_repos_id(copyfrom_id,
+                                  original_root_url, original_uuid,
+                                  pdh->wcroot->sdb, scratch_pool));
+        }
+      else
+        {
+          *copyfrom_relpath = NULL;
+          *copyfrom_rev = SVN_INVALID_REVNUM;
+          *copyfrom_id = 0;
+        }
+    }
+
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_wc__db_op_copy(svn_wc__db_t *db,
                    const char *src_abspath,
@@ -2294,12 +2903,13 @@ svn_wc__db_op_copy(svn_wc__db_t *db,
                    apr_pool_t *scratch_pool)
 {
   svn_wc__db_pdh_t *src_pdh, *dst_pdh;
-  const char *src_relpath, *dst_relpath;
-  const char *repos_relpath, *repos_root_url, *repos_uuid, *copyfrom_relpath;
-  svn_revnum_t revision, copyfrom_rev;
-  svn_wc__db_status_t status;
+  const char *src_relpath, *dst_relpath, *copyfrom_relpath;
+  svn_revnum_t copyfrom_rev;
+  svn_wc__db_status_t status, dst_status;
+  svn_boolean_t have_work;
   apr_int64_t copyfrom_id;
   svn_wc__db_kind_t kind;
+  const apr_array_header_t *children;
 
   SVN_ERR_ASSERT(svn_dirent_is_absolute(src_abspath));
   SVN_ERR_ASSERT(svn_dirent_is_absolute(dst_abspath));
@@ -2319,76 +2929,66 @@ svn_wc__db_op_copy(svn_wc__db_t *db,
                                              scratch_pool, scratch_pool));
   VERIFY_USABLE_PDH(dst_pdh);
 
-
-  SVN_ERR(svn_wc__db_read_info(&status, &kind, &revision,
-                               &repos_relpath, &repos_root_url, &repos_uuid,
-                               NULL, /* changed_rev */
-                               NULL, /* changed_date */
-                               NULL, /* changed_author */
-                               NULL, /* last_mod_time */
-                               NULL, /* depth */
-                               NULL, /* checksum */
-                               NULL, /* translated_size */
-                               NULL, /* target */
-                               NULL, /* changelist */
-                               NULL, /* original_repos_relpath */
-                               NULL, /* original_root_url */
-                               NULL, /* original_uuid */
-                               NULL, /* original_revision */
-                               NULL, /* text_mod */
-                               NULL, /* props_mod */
-                               NULL, /* base_shadowed */
-                               NULL, /* conflicted */
-                               NULL, /* lock */
-                               db, src_abspath, scratch_pool, scratch_pool));
-
-  SVN_ERR_ASSERT(kind == svn_wc__db_kind_file);
-
-  if (status != svn_wc__db_status_added)
-    {
-      copyfrom_relpath = repos_relpath;
-      copyfrom_rev = revision;
-      SVN_ERR(create_repos_id(&copyfrom_id,
-                              repos_root_url, repos_uuid,
-                              src_pdh->wcroot->sdb, scratch_pool));
+  SVN_ERR(get_info_for_copy(&copyfrom_id, &copyfrom_relpath, &copyfrom_rev,
+                            &status, &kind, &have_work,
+                            src_pdh, db, src_abspath,
+                            scratch_pool, scratch_pool));
+
+  SVN_ERR_ASSERT(kind == svn_wc__db_kind_file || kind == svn_wc__db_kind_dir);
+
+  /* ### New status, not finished, see notes/wc-ng/copying */
+  switch (status)
+    {
+    case svn_wc__db_status_normal:
+    case svn_wc__db_status_added:
+    case svn_wc__db_status_moved_here:
+    case svn_wc__db_status_copied:
+      dst_status = svn_wc__db_status_normal;
+      break;
+    case svn_wc__db_status_deleted:
+    case svn_wc__db_status_not_present:
+      dst_status = svn_wc__db_status_not_present;
+      break;
+    case svn_wc__db_status_excluded:
+      dst_status = svn_wc__db_status_excluded;
+      break;
+    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,
+                                                      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,
+                                                      scratch_pool));
     }
-  else
-    {
-      const char *op_root_abspath;
-      const char *original_repos_relpath, *original_root_url, *original_uuid;
-      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, src_abspath,
-                                       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,
-                                                        src_abspath),
-                               scratch_pool);
-          copyfrom_rev = original_revision;
-          SVN_ERR(create_repos_id(&copyfrom_id,
-                                  original_root_url, original_uuid,
-                                  src_pdh->wcroot->sdb, scratch_pool));
-        }
-      else
-        {
-          copyfrom_relpath = NULL;
-          copyfrom_rev = SVN_INVALID_REVNUM;
-        }
+  /* When copying a directory the destination may not exist, if so we
+     only copy the parent stub */
+  if (kind == svn_wc__db_kind_dir && !*src_relpath && *dst_relpath)
+    {
+      /* ### copy_tests.py 69 copies from the root of one wc to
+         ### another wc, that means the source doesn't have a
+         ### versioned parent and so there is no parent stub to
+         ### copy. We could generate a parent stub but it becomes
+         ### unnecessary when we centralise so for the moment we just
+         ### fail. */
+      SVN_ERR(navigate_to_parent(&src_pdh, db, src_pdh,
+                                 svn_sqlite__mode_readwrite, TRUE,
+                                 scratch_pool));
+      src_relpath = svn_dirent_basename(src_abspath, NULL);
+      kind = svn_wc__db_kind_subdir;
     }
 
-
+  /* Get the children for a directory if this is not the parent stub */
+  if (kind == svn_wc__db_kind_dir)
+    SVN_ERR(gather_children(&children, FALSE, db, src_abspath,
+                            scratch_pool, scratch_pool));
+  else
+    children = NULL;
 
   if (!strcmp(src_pdh->local_abspath, dst_pdh->local_abspath))
     {
@@ -2396,10 +2996,7 @@ svn_wc__db_op_copy(svn_wc__db_t *db,
       const char *dst_parent_relpath = svn_relpath_dirname(dst_relpath,
                                                            scratch_pool);
 
-      /* ### Need a better way to determine whether a WORKING_NODE exists */
-      if (status == svn_wc__db_status_added
-          || status == svn_wc__db_status_copied
-          || status == svn_wc__db_status_moved_here)
+      if (have_work)
         SVN_ERR(svn_sqlite__get_statement(&stmt, src_pdh->wcroot->sdb,
                                   STMT_INSERT_WORKING_NODE_COPY_FROM_WORKING));
       else
@@ -2409,7 +3006,7 @@ svn_wc__db_op_copy(svn_wc__db_t *db,
       SVN_ERR(svn_sqlite__bindf(stmt, "issst",
                                 src_pdh->wcroot->wc_id, src_relpath,
                                 dst_relpath, dst_parent_relpath,
-                                presence_map, svn_wc__db_status_normal));
+                                presence_map, dst_status));
 
       if (copyfrom_relpath)
         {
@@ -2426,11 +3023,19 @@ svn_wc__db_op_copy(svn_wc__db_t *db,
                                 src_pdh->wcroot->wc_id, src_relpath,
                                 dst_relpath, dst_parent_relpath));
       SVN_ERR(svn_sqlite__step_done(stmt));
+
+      if (kind == svn_wc__db_kind_dir)
+        SVN_ERR(insert_incomplete_working_children(dst_pdh->wcroot->sdb,
+                                                   dst_pdh->wcroot->wc_id,
+                                                   dst_relpath,
+                                                   children,
+                                                   scratch_pool));
     }
   else
     {
       SVN_ERR(temp_cross_db_copy(db, src_abspath, src_pdh, src_relpath,
-                                 dst_pdh, dst_relpath, kind,
+                                 dst_pdh, dst_relpath, dst_status,
+                                 kind, children,
                                  copyfrom_id, copyfrom_relpath, copyfrom_rev,
                                  scratch_pool));
     }
@@ -2512,29 +3117,24 @@ svn_wc__db_op_copy_dir(svn_wc__db_t *db,
   flush_entries(pdh);
 
   /* Add a parent stub.  */
-  {
-    svn_error_t *err;
+  if (*local_relpath == '\0')
+    {
+      SVN_ERR(navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readwrite,
+                                 FALSE, scratch_pool));
 
-    err = navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readwrite,
-                             scratch_pool);
-    if (err)
-      {
-        /* Prolly fell off the top of the wcroot. Just call it a day.  */
-        svn_error_clear(err);
-        return SVN_NO_ERROR;
-      }
+      VERIFY_USABLE_PDH(pdh);
 
-    blank_iwb(&iwb);
+      blank_iwb(&iwb);
 
-    iwb.presence = svn_wc__db_status_normal;
-    iwb.kind = svn_wc__db_kind_subdir;
-    iwb.wc_id = pdh->wcroot->wc_id;
-    iwb.local_relpath = svn_dirent_basename(local_abspath, scratch_pool);
-
-    /* No children or work items, so a txn is not needed.  */
-    SVN_ERR(insert_working_node(&iwb, pdh->wcroot->sdb, scratch_pool));
-    flush_entries(pdh);
-  }
+      iwb.presence = svn_wc__db_status_normal;
+      iwb.kind = svn_wc__db_kind_subdir;
+      iwb.wc_id = pdh->wcroot->wc_id;
+      iwb.local_relpath = svn_dirent_basename(local_abspath, scratch_pool);
+
+      /* No children or work items, so a txn is not needed.  */
+      SVN_ERR(insert_working_node(&iwb, pdh->wcroot->sdb, scratch_pool));
+      flush_entries(pdh);
+    }
 
   return SVN_NO_ERROR;
 }
@@ -2710,29 +3310,24 @@ svn_wc__db_op_add_directory(svn_wc__db_t
   flush_entries(pdh);
 
   /* Add a parent stub.  */
-  {
-    svn_error_t *err;
+  if (*local_relpath == '\0')
+    {
+      SVN_ERR(navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readwrite,
+                                 FALSE, scratch_pool));
 
-    err = navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readwrite,
-                             scratch_pool);
-    if (err)
-      {
-        /* Prolly fell off the top of the wcroot. Just call it a day.  */
-        svn_error_clear(err);
-        return SVN_NO_ERROR;
-      }
+      VERIFY_USABLE_PDH(pdh);
 
-    blank_iwb(&iwb);
+      blank_iwb(&iwb);
 
-    iwb.presence = svn_wc__db_status_normal;
-    iwb.kind = svn_wc__db_kind_subdir;
-    iwb.wc_id = pdh->wcroot->wc_id;
-    iwb.local_relpath = svn_dirent_basename(local_abspath, scratch_pool);
-
-    /* No children or work items, so a txn is not needed.  */
-    SVN_ERR(insert_working_node(&iwb, pdh->wcroot->sdb, scratch_pool));
-    flush_entries(pdh);
-  }
+      iwb.presence = svn_wc__db_status_normal;
+      iwb.kind = svn_wc__db_kind_subdir;
+      iwb.wc_id = pdh->wcroot->wc_id;
+      iwb.local_relpath = svn_dirent_basename(local_abspath, scratch_pool);
+
+      /* No children or work items, so a txn is not needed.  */
+      SVN_ERR(insert_working_node(&iwb, pdh->wcroot->sdb, scratch_pool));
+      flush_entries(pdh);
+    }
 
   return SVN_NO_ERROR;
 }
@@ -2816,8 +3411,8 @@ struct set_props_baton
 {
   apr_hash_t *props;
 
+  apr_int64_t wc_id;
   const char *local_relpath;
-  svn_wc__db_pdh_t *pdh;
 
   const svn_skel_t *conflict;
   const svn_skel_t *work_items;
@@ -2843,26 +3438,21 @@ set_props_txn(void *baton, svn_sqlite__d
   SVN_ERR(add_work_items(db, spb->work_items, scratch_pool));
 
   SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_UPDATE_ACTUAL_PROPS));
-  SVN_ERR(svn_sqlite__bindf(stmt, "is", spb->pdh->wcroot->wc_id,
-                            spb->local_relpath));
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", spb->wc_id, spb->local_relpath));
   SVN_ERR(svn_sqlite__bind_properties(stmt, 3, spb->props, scratch_pool));
   SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
 
   if (affected_rows == 1)
     return SVN_NO_ERROR; /* We are done */
 
-  /* We have to insert a row in actual */
+  /* We have to insert a row in ACTUAL */
 
   SVN_ERR(svn_sqlite__get_statement(&stmt, db, STMT_INSERT_ACTUAL_PROPS));
-
-  SVN_ERR(svn_sqlite__bindf(stmt, "is", spb->pdh->wcroot->wc_id,
-                            spb->local_relpath));
-
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", spb->wc_id, spb->local_relpath));
   if (*spb->local_relpath != '\0')
     SVN_ERR(svn_sqlite__bind_text(stmt, 3,
                                   svn_relpath_dirname(spb->local_relpath,
                                                       scratch_pool)));
-
   SVN_ERR(svn_sqlite__bind_properties(stmt, 4, spb->props, scratch_pool));
   return svn_error_return(svn_sqlite__step_done(stmt));
 }
@@ -2876,17 +3466,22 @@ svn_wc__db_op_set_props(svn_wc__db_t *db
                         apr_pool_t *scratch_pool)
 {
   struct set_props_baton spb;
+  svn_wc__db_pdh_t *pdh;
 
-  spb.props = props;
-  spb.conflict = conflict;
-  spb.work_items = work_items;
+  SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
 
-  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&spb.pdh, &spb.local_relpath, db,
+  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &spb.local_relpath, db,
                               local_abspath, svn_sqlite__mode_readwrite,
                               scratch_pool, scratch_pool));
+  VERIFY_USABLE_PDH(pdh);
+
+  spb.props = props;
+  spb.wc_id = pdh->wcroot->wc_id;
+  spb.conflict = conflict;
+  spb.work_items = work_items;
 
   return svn_error_return(
-            svn_sqlite__with_transaction(spb.pdh->wcroot->sdb,
+            svn_sqlite__with_transaction(pdh->wcroot->sdb,
                                          set_props_txn,
                                          &spb,
                                          scratch_pool));
@@ -3319,7 +3914,7 @@ svn_wc__db_op_read_tree_conflict(
   err = svn_wc__db_op_read_all_tree_conflicts(&tree_conflicts, db,
                                               parent_abspath,
                                               result_pool, scratch_pool);
-  if (err && err->apr_err == SVN_ERR_WC_NOT_WORKING_COPY)
+  if (err && SVN_WC__ERR_IS_NOT_CURRENT_WC(err))
     {
        /* We walked off the top of a working copy.  */
        svn_error_clear(err);
@@ -3360,23 +3955,6 @@ svn_wc__db_temp_op_remove_entry(svn_wc__
 
   flush_entries(pdh);
 
-  /* Check if we should remove it from the parent db instead */
-  /* (In theory, we should remove it from the parent db *as well*.  However,
-     we must be looking at a separate per-directory database, and deleting
-     the "this-dir" entry implies the caller is about to delete this whole
-     directory including the database from disk, so we don't bother deleting
-     the rows from here as well.) */
-  if (*local_relpath == '\0')
-    {
-      SVN_ERR(navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readwrite,
-                                 scratch_pool));
-      VERIFY_USABLE_PDH(pdh);
-
-      local_relpath = svn_dirent_basename(local_abspath, NULL);
-
-      flush_entries(pdh);
-    }
-
   sdb = pdh->wcroot->sdb;
   wc_id = pdh->wcroot->wc_id;
 
@@ -3391,7 +3969,37 @@ svn_wc__db_temp_op_remove_entry(svn_wc__
   SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_DELETE_ACTUAL_NODE));
   SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
 
-  return svn_error_return(svn_sqlite__step_done(stmt));
+  SVN_ERR(svn_sqlite__step_done(stmt));
+
+  /* Check if we should also remove it from the parent db */
+  if (*local_relpath == '\0')
+    {
+      SVN_ERR(navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readwrite,
+                                 TRUE, scratch_pool));
+      VERIFY_USABLE_PDH(pdh);
+
+      local_relpath = svn_dirent_basename(local_abspath, NULL);
+
+      flush_entries(pdh);
+
+      sdb = pdh->wcroot->sdb;
+      wc_id = pdh->wcroot->wc_id;
+
+      SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_DELETE_BASE_NODE));
+      SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+      SVN_ERR(svn_sqlite__step_done(stmt));
+
+      SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_DELETE_WORKING_NODE));
+      SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+      SVN_ERR(svn_sqlite__step_done(stmt));
+
+      SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_DELETE_ACTUAL_NODE));
+      SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+
+      SVN_ERR(svn_sqlite__step_done(stmt));
+    }
+
+  return SVN_NO_ERROR;
 }
 
 
@@ -3422,7 +4030,7 @@ svn_wc__db_temp_op_remove_working(svn_wc
   if (*local_relpath == '\0')
     {
       SVN_ERR(navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readwrite,
-                                 scratch_pool));
+                                 TRUE, scratch_pool));
       VERIFY_USABLE_PDH(pdh);
 
       local_relpath = svn_dirent_basename(local_abspath, NULL);
@@ -3505,10 +4113,10 @@ svn_wc__db_temp_op_set_dir_depth(svn_wc_
       svn_error_t *err;
 
       err = navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readwrite,
-                               scratch_pool);
+                               TRUE, scratch_pool);
       if (err)
         {
-          if (err->apr_err != SVN_ERR_WC_NOT_WORKING_COPY)
+          if (! SVN_WC__ERR_IS_NOT_CURRENT_WC(err))
             return svn_error_return(err);
 
           /* No parent to update */
@@ -3550,9 +4158,24 @@ db_working_update_presence(svn_wc__db_st
 
   flush_entries(pdh);
 
-  /* ### Parent stub?  I don't know; I'll punt for now as it passes
-         the regression tests as is and the problem will evaporate
-         when the db is centralised. */
+#ifndef SINGLE_DB
+  if (*local_relpath == '\0')
+    {
+      SVN_ERR(navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readwrite,
+                                 TRUE, scratch_pool));
+
+      VERIFY_USABLE_PDH(pdh);
+
+      SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->wcroot->sdb,
+                                        STMT_UPDATE_WORKING_PRESENCE));
+      SVN_ERR(svn_sqlite__bindf(stmt, "ist", pdh->wcroot->wc_id,
+                                svn_dirent_basename(local_abspath, NULL),
+                                presence_map, status));
+      SVN_ERR(svn_sqlite__step_done(stmt));
+
+      flush_entries(pdh);
+    }
+#endif
 
   return SVN_NO_ERROR;
 }
@@ -3590,7 +4213,7 @@ db_working_actual_remove(svn_wc__db_t *d
     {
       /* ### Delete parent stub. Remove when db is centralised. */
       SVN_ERR(navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readwrite,
-                                 scratch_pool));
+                                 TRUE, scratch_pool));
       local_relpath = svn_dirent_basename(local_abspath, NULL);
       VERIFY_USABLE_PDH(pdh);
 
@@ -3636,7 +4259,7 @@ db_working_insert(svn_wc__db_status_t st
     {
       /* ### Insert parent stub. Remove when db is centralised. */
       SVN_ERR(navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readwrite,
-                                 scratch_pool));
+                                 TRUE, scratch_pool));
       local_relpath = svn_dirent_basename(local_abspath, NULL);
       VERIFY_USABLE_PDH(pdh);
 
@@ -3701,7 +4324,7 @@ is_add_or_root_of_copy(svn_boolean_t *ad
       svn_revnum_t parent_original_revision;
       svn_error_t *err;
 
-      svn_dirent_split(local_abspath, &parent_abspath, &name, scratch_pool);
+      svn_dirent_split(&parent_abspath, &name, local_abspath, scratch_pool);
 
       err = svn_wc__db_scan_addition(&parent_status,
                                      NULL, NULL, NULL, NULL,
@@ -3744,7 +4367,7 @@ svn_wc__db_temp_op_delete(svn_wc__db_t *
   svn_error_t *err;
   svn_boolean_t base_none, working_none, new_working_none;
   svn_wc__db_status_t base_status, working_status, new_working_status;
-  svn_boolean_t base_shadowed;
+  svn_boolean_t have_work;
 
   err = svn_wc__db_base_get_info(&base_status,
                                  NULL, NULL, NULL, NULL, NULL, NULL, NULL,
@@ -3771,7 +4394,7 @@ svn_wc__db_temp_op_delete(svn_wc__db_t *
   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, &base_shadowed, NULL, NULL,
+                               NULL, NULL, NULL, &have_work, NULL, NULL,
                                db, local_abspath,
                                scratch_pool, scratch_pool));
   if (working_status == svn_wc__db_status_deleted
@@ -3782,10 +4405,7 @@ svn_wc__db_temp_op_delete(svn_wc__db_t *
       return SVN_NO_ERROR;
     }
 
-  /* We must have a WORKING node if there is no BASE node (gotta have
-     something!). If there IS a BASE node, then we have a WORKING node
-     if BASE_SHADOWED is TRUE.  */
-  working_none = !(base_none || base_shadowed);
+  working_none = !have_work;
 
   new_working_none = working_none;
   new_working_status = working_status;
@@ -3842,6 +4462,9 @@ svn_wc__db_temp_op_delete(svn_wc__db_t *
           new_working_status = svn_wc__db_status_base_deleted;
         }
     }
+    /* ### BH: base_none 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))
     {
@@ -3918,9 +4541,9 @@ svn_wc__db_read_info(svn_wc__db_status_t
                      const char **original_root_url,
                      const char **original_uuid,
                      svn_revnum_t *original_revision,
-                     svn_boolean_t *text_mod,
                      svn_boolean_t *props_mod,
-                     svn_boolean_t *base_shadowed,
+                     svn_boolean_t *have_base,
+                     svn_boolean_t *have_work,
                      svn_boolean_t *conflicted,
                      svn_wc__db_lock_t **lock,
                      svn_wc__db_t *db,
@@ -3933,8 +4556,8 @@ svn_wc__db_read_info(svn_wc__db_status_t
   svn_sqlite__stmt_t *stmt_base;
   svn_sqlite__stmt_t *stmt_work;
   svn_sqlite__stmt_t *stmt_act;
-  svn_boolean_t have_base;
-  svn_boolean_t have_work;
+  svn_boolean_t local_have_base;
+  svn_boolean_t local_have_work;
   svn_boolean_t have_act;
   svn_error_t *err = NULL;
 
@@ -3945,18 +4568,23 @@ svn_wc__db_read_info(svn_wc__db_status_t
                               scratch_pool, scratch_pool));
   VERIFY_USABLE_PDH(pdh);
 
+  if (!have_base)
+    have_base = &local_have_base;
+  if (!have_work)
+    have_work = &local_have_work;
+    
   SVN_ERR(svn_sqlite__get_statement(&stmt_base, pdh->wcroot->sdb,
                                     lock ? STMT_SELECT_BASE_NODE_WITH_LOCK
                                          : STMT_SELECT_BASE_NODE));
   SVN_ERR(svn_sqlite__bindf(stmt_base, "is",
                             pdh->wcroot->wc_id, local_relpath));
-  SVN_ERR(svn_sqlite__step(&have_base, stmt_base));
+  SVN_ERR(svn_sqlite__step(have_base, stmt_base));
 
   SVN_ERR(svn_sqlite__get_statement(&stmt_work, pdh->wcroot->sdb,
                                     STMT_SELECT_WORKING_NODE));
   SVN_ERR(svn_sqlite__bindf(stmt_work, "is",
                             pdh->wcroot->wc_id, local_relpath));
-  SVN_ERR(svn_sqlite__step(&have_work, stmt_work));
+  SVN_ERR(svn_sqlite__step(have_work, stmt_work));
 
   SVN_ERR(svn_sqlite__get_statement(&stmt_act, pdh->wcroot->sdb,
                                     STMT_SELECT_ACTUAL_NODE));
@@ -3964,18 +4592,18 @@ svn_wc__db_read_info(svn_wc__db_status_t
                             pdh->wcroot->wc_id, local_relpath));
   SVN_ERR(svn_sqlite__step(&have_act, stmt_act));
 
-  if (have_base || have_work)
+  if (*have_base || *have_work)
     {
       svn_wc__db_kind_t node_kind;
 
-      if (have_work)
+      if (*have_work)
         node_kind = svn_sqlite__column_token(stmt_work, 1, kind_map);
       else
         node_kind = svn_sqlite__column_token(stmt_base, 3, kind_map);
 
       if (status)
         {
-          if (have_base)
+          if (*have_base)
             {
               *status = svn_sqlite__column_token(stmt_base, 2, presence_map);
 
@@ -3987,7 +4615,7 @@ svn_wc__db_read_info(svn_wc__db_status_t
               SVN_ERR_ASSERT((*status != svn_wc__db_status_absent
                               && *status != svn_wc__db_status_excluded
                               /* && *status != svn_wc__db_status_incomplete */)
-                             || !have_work);
+                             || !*have_work);
 
               if (node_kind == svn_wc__db_kind_subdir
                   && *status == svn_wc__db_status_normal)
@@ -4001,7 +4629,7 @@ svn_wc__db_read_info(svn_wc__db_status_t
                 }
             }
 
-          if (have_work)
+          if (*have_work)
             {
               svn_wc__db_status_t work_status;
 
@@ -4010,12 +4638,17 @@ svn_wc__db_read_info(svn_wc__db_status_t
               SVN_ERR_ASSERT(work_status == svn_wc__db_status_normal
                              || work_status == svn_wc__db_status_not_present
                              || work_status == svn_wc__db_status_base_deleted
-                             || work_status == svn_wc__db_status_incomplete);
+                             || work_status == svn_wc__db_status_incomplete
+                             || work_status == svn_wc__db_status_excluded);
 
               if (work_status == svn_wc__db_status_incomplete)
                 {
                   *status = svn_wc__db_status_incomplete;
                 }
+              else if (work_status == svn_wc__db_status_excluded)
+                {
+                  *status = svn_wc__db_status_excluded;
+                }
               else if (work_status == svn_wc__db_status_not_present
                        || work_status == svn_wc__db_status_base_deleted)
                 {
@@ -4058,14 +4691,14 @@ svn_wc__db_read_info(svn_wc__db_status_t
         }
       if (revision)
         {
-          if (have_work)
+          if (*have_work)
             *revision = SVN_INVALID_REVNUM;
           else
             *revision = svn_sqlite__column_revnum(stmt_base, 4);
         }
       if (repos_relpath)
         {
-          if (have_work)
+          if (*have_work)
             {
               /* Our path is implied by our parent somewhere up the tree.
                  With the NULL value and status, the caller will know to
@@ -4082,7 +4715,7 @@ svn_wc__db_read_info(svn_wc__db_status_t
              WORKING_NODE (and have been added), then the repository
              we're being added to will be dependent upon a parent. The
              caller can scan upwards to locate the repository.  */
-          if (have_work || svn_sqlite__column_is_null(stmt_base, 0))
+          if (*have_work || svn_sqlite__column_is_null(stmt_base, 0))
             {
               if (repos_root_url)
                 *repos_root_url = NULL;
@@ -4100,21 +4733,21 @@ svn_wc__db_read_info(svn_wc__db_status_t
         }
       if (changed_rev)
         {
-          if (have_work)
+          if (*have_work)
             *changed_rev = svn_sqlite__column_revnum(stmt_work, 4);
           else
             *changed_rev = svn_sqlite__column_revnum(stmt_base, 7);
         }
       if (changed_date)
         {
-          if (have_work)
+          if (*have_work)
             *changed_date = svn_sqlite__column_int64(stmt_work, 5);
           else
             *changed_date = svn_sqlite__column_int64(stmt_base, 8);
         }
       if (changed_author)
         {
-          if (have_work)
+          if (*have_work)
             *changed_author = svn_sqlite__column_text(stmt_work, 6,
                                                       result_pool);
           else
@@ -4123,7 +4756,7 @@ svn_wc__db_read_info(svn_wc__db_status_t
         }
       if (last_mod_time)
         {
-          if (have_work)
+          if (*have_work)
             *last_mod_time = svn_sqlite__column_int64(stmt_work, 14);
           else
             *last_mod_time = svn_sqlite__column_int64(stmt_base, 12);
@@ -4139,7 +4772,7 @@ svn_wc__db_read_info(svn_wc__db_status_t
             {
               const char *depth_str;
 
-              if (have_work)
+              if (*have_work)
                 depth_str = svn_sqlite__column_text(stmt_work, 7, NULL);
               else
                 depth_str = svn_sqlite__column_text(stmt_base, 10, NULL);
@@ -4159,7 +4792,7 @@ svn_wc__db_read_info(svn_wc__db_status_t
           else
             {
               svn_error_t *err2;
-              if (have_work)
+              if (*have_work)
                 err2 = svn_sqlite__column_checksum(checksum, stmt_work, 2,
                                                    result_pool);
               else
@@ -4178,7 +4811,7 @@ svn_wc__db_read_info(svn_wc__db_status_t
         }
       if (translated_size)
         {
-          if (have_work)
+          if (*have_work)
             *translated_size = get_translated_size(stmt_work, 3);
           else
             *translated_size = get_translated_size(stmt_base, 6);
@@ -4187,7 +4820,7 @@ svn_wc__db_read_info(svn_wc__db_status_t
         {
           if (node_kind != svn_wc__db_kind_symlink)
             *target = NULL;
-          else if (have_work)
+          else if (*have_work)
             *target = svn_sqlite__column_text(stmt_work, 8, result_pool);
           else
             *target = svn_sqlite__column_text(stmt_base, 11, result_pool);
@@ -4201,13 +4834,13 @@ svn_wc__db_read_info(svn_wc__db_status_t
         }
       if (original_repos_relpath)
         {
-          if (have_work)
+          if (*have_work)
             *original_repos_relpath = svn_sqlite__column_text(stmt_work, 10,
                                                               result_pool);
           else
             *original_repos_relpath = NULL;
         }
-      if (!have_work || svn_sqlite__column_is_null(stmt_work, 9))
+      if (!*have_work || svn_sqlite__column_is_null(stmt_work, 9))
         {
           if (original_root_url)
             *original_root_url = NULL;
@@ -4226,23 +4859,14 @@ svn_wc__db_read_info(svn_wc__db_status_t
         }
       if (original_revision)
         {
-          if (have_work)
+          if (*have_work)
             *original_revision = svn_sqlite__column_revnum(stmt_work, 11);
           else
             *original_revision = SVN_INVALID_REVNUM;
         }
-      if (text_mod)
-        {
-          /* ### fix this */
-          *text_mod = FALSE;
-        }
       if (props_mod)
         {
-          *props_mod = have_act && !svn_sqlite__column_is_null(stmt_act, 7);
-        }
-      if (base_shadowed)
-        {
-          *base_shadowed = have_base && have_work;
+          *props_mod = have_act && !svn_sqlite__column_is_null(stmt_act, 6);
         }
       if (conflicted)
         {
@@ -4547,8 +5171,9 @@ svn_wc__db_global_relocate(svn_wc__db_t 
                            apr_pool_t *scratch_pool)
 {
   svn_wc__db_pdh_t *pdh;
+  svn_wc__db_status_t status;
   struct relocate_baton rb;
-  svn_sqlite__stmt_t *stmt;
+  const char *old_repos_root_url, *stored_local_dir_abspath;
 
   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_dir_abspath));
   /* ### assert that we were passed a directory?  */
@@ -4558,32 +5183,99 @@ svn_wc__db_global_relocate(svn_wc__db_t 
                               scratch_pool, scratch_pool));
   VERIFY_USABLE_PDH(pdh);
 
-  /* Get the existing repos_id of the base node, since we'll need it to
-     update a potential lock. */
-  /* ### is it faster to fetch fewer columns? */
-  SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->wcroot->sdb,
-                                    STMT_SELECT_BASE_NODE));
-  SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wcroot->wc_id,
-                            rb.local_relpath));
-  SVN_ERR(svn_sqlite__step(&rb.have_base_node, stmt));
-  if (rb.have_base_node)
-    {
-      rb.old_repos_id = svn_sqlite__column_int64(stmt, 0);
-      rb.repos_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool);
-      SVN_ERR(svn_sqlite__reset(stmt));
+  SVN_ERR(svn_wc__db_read_info(&status,
+                               NULL, NULL,
+                               &rb.repos_relpath, &old_repos_root_url,
+                               &rb.repos_uuid,
+                               NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+                               NULL, NULL, NULL, NULL, NULL, NULL,
+                               &rb.have_base_node,
+                               NULL, NULL, NULL,
+                               db, local_dir_abspath,
+                               scratch_pool, scratch_pool));
 
-      SVN_ERR(fetch_repos_info(NULL, &rb.repos_uuid, pdh->wcroot->sdb,
-                               rb.old_repos_id, scratch_pool));
+  if (status == svn_wc__db_status_excluded)
+    {
+      /* The parent cannot be excluded, so look at the parent and then
+         adjust the relpath */
+      const char *parent_abspath = svn_dirent_dirname(local_dir_abspath,
+                                                      scratch_pool);
+      SVN_ERR(svn_wc__db_read_info(&status,
+                                   NULL, NULL,
+                                   &rb.repos_relpath, &old_repos_root_url,
+                                   &rb.repos_uuid,
+                                   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+                                   NULL, NULL, NULL, NULL, NULL, NULL, NULL,
+                                   NULL, NULL, NULL, NULL,
+                                   db, parent_abspath,
+                                   scratch_pool, scratch_pool));
+      stored_local_dir_abspath = local_dir_abspath;
+      local_dir_abspath = parent_abspath;
     }
   else
+    stored_local_dir_abspath = NULL;
+
+  if (!rb.repos_relpath || !old_repos_root_url || !rb.repos_uuid)
     {
-      SVN_ERR(svn_sqlite__reset(stmt));
-      SVN_ERR(svn_wc__db_scan_addition(NULL, NULL, NULL, NULL, &rb.repos_uuid,
-                                       NULL, NULL, NULL, NULL,
-                                       db, local_dir_abspath, scratch_pool,
-                                       scratch_pool));
+      /* Do we need to support relocating something that is
+         added/deleted/excluded without relocating the parent?  If not
+         then perhaps relpath, root_url and uuid should be passed down
+         to the children so that they don't have to scan? */
+
+      if (status == svn_wc__db_status_deleted)
+        {
+          const char *work_del_abspath;
+          SVN_ERR(svn_wc__db_scan_deletion(NULL, NULL, NULL,
+                                           &work_del_abspath,
+                                           db, local_dir_abspath,
+                                           scratch_pool, scratch_pool));
+          if (work_del_abspath)
+            {
+              /* Deleted within a copy/move */
+              SVN_ERR_ASSERT(!stored_local_dir_abspath);
+              stored_local_dir_abspath = local_dir_abspath;
+
+              /* The parent of the delete is added. */
+              status = svn_wc__db_status_added;
+              local_dir_abspath = svn_dirent_dirname(work_del_abspath,
+                                                     scratch_pool);
+            }
+        }
+
+      if (status == svn_wc__db_status_added
+          || status == svn_wc__db_status_obstructed_add)
+        {
+          SVN_ERR(svn_wc__db_scan_addition(NULL, NULL,
+                                           &rb.repos_relpath,
+                                           &old_repos_root_url, &rb.repos_uuid,
+                                           NULL, NULL, NULL, NULL,
+                                           db, local_dir_abspath,
+                                           scratch_pool, scratch_pool));
+        }
+      else
+        SVN_ERR(svn_wc__db_scan_base_repos(&rb.repos_relpath,
+                                           &old_repos_root_url, &rb.repos_uuid,
+                                           db, local_dir_abspath,
+                                           scratch_pool, scratch_pool));
+    }
+
+  SVN_ERR_ASSERT(rb.repos_relpath && old_repos_root_url && rb.repos_uuid);
+
+  if (stored_local_dir_abspath)
+    {
+      /* Adjust to get value suitable for local_dir_abspath */
+      const char *part = svn_dirent_is_child(local_dir_abspath,
+                                             stored_local_dir_abspath,
+                                             scratch_pool);
+      rb.repos_relpath = svn_relpath_join(rb.repos_relpath, part,
+                                          scratch_pool);
+      local_dir_abspath = stored_local_dir_abspath;
     }
 
+
+  SVN_ERR(create_repos_id(&rb.old_repos_id, old_repos_root_url, rb.repos_uuid,
+                          pdh->wcroot->sdb, scratch_pool));
+
   rb.wc_id = pdh->wcroot->wc_id;
   rb.repos_root_url = repos_root_url;
 
@@ -4644,6 +5336,7 @@ struct commit_baton {
   const apr_array_header_t *new_children;
   apr_hash_t *new_dav_cache;
   svn_boolean_t keep_changelist;
+  svn_boolean_t no_unlock;
 
   apr_int64_t repos_id;
   const char *repos_relpath;
@@ -4847,6 +5540,16 @@ commit_node(void *baton, svn_sqlite__db_
       /* ### process the children  */
     }
 
+  if (!cb->no_unlock)
+    {
+      svn_sqlite__stmt_t *lock_stmt;
+
+      SVN_ERR(svn_sqlite__get_statement(&lock_stmt, sdb, STMT_DELETE_LOCK));
+      SVN_ERR(svn_sqlite__bindf(lock_stmt, "is", cb->repos_id,
+                                cb->repos_relpath));
+      SVN_ERR(svn_sqlite__step_done(lock_stmt));
+    }
+
   /* Install any work items into the queue, as part of this transaction.  */
   SVN_ERR(add_work_items(sdb, cb->work_items, scratch_pool));
 
@@ -4901,7 +5604,7 @@ determine_repos_info(apr_int64_t *repos_
          which means one PDH up, and stick to local_relpath == "".  */
       SVN_ERR(navigate_to_parent(&pdh, db, pdh,
                                  svn_sqlite__mode_readonly,
-                                 scratch_pool));
+                                 TRUE, scratch_pool));
       local_relpath = "";
     }
   else
@@ -4933,6 +5636,7 @@ svn_wc__db_global_commit(svn_wc__db_t *d
                          const apr_array_header_t *new_children,
                          apr_hash_t *new_dav_cache,
                          svn_boolean_t keep_changelist,
+                         svn_boolean_t no_unlock,
                          const svn_skel_t *work_items,
                          apr_pool_t *scratch_pool)
 {
@@ -4959,6 +5663,7 @@ svn_wc__db_global_commit(svn_wc__db_t *d
   cb.new_children = new_children;
   cb.new_dav_cache = new_dav_cache;
   cb.keep_changelist = keep_changelist;
+  cb.no_unlock = no_unlock;
   cb.work_items = work_items;
 
   /* If we are adding a directory (no BASE_NODE), then we need to get
@@ -5456,7 +6161,7 @@ svn_wc__db_scan_addition(svn_wc__db_stat
         {
           /* The current node is a directory, so move to the parent dir.  */
           SVN_ERR(navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readonly,
-                                     scratch_pool));
+                                     TRUE, scratch_pool));
         }
       current_abspath = pdh->local_abspath;
       current_relpath = svn_wc__db_pdh_compute_relpath(pdh, NULL);
@@ -5688,7 +6393,7 @@ svn_wc__db_scan_deletion(const char **ba
         {
           /* The current node is a directory, so move to the parent dir.  */
           SVN_ERR(navigate_to_parent(&pdh, db, pdh, svn_sqlite__mode_readonly,
-                                     scratch_pool));
+                                     TRUE, scratch_pool));
         }
       current_abspath = pdh->local_abspath;
       current_relpath = svn_wc__db_pdh_compute_relpath(pdh, NULL);
@@ -5762,7 +6467,12 @@ svn_wc__db_upgrade_apply_props(svn_sqlit
                                int original_format,
                                apr_pool_t *scratch_pool)
 {
-  NOT_IMPLEMENTED();
+  svn_boolean_t have_base;
+  svn_boolean_t have_work;
+  svn_wc__db_status_t work_presence;
+  apr_int64_t wc_id;
+  svn_sqlite__stmt_t *stmt;
+  int affected_rows;
 
   /* ### working_props: use set_props_txn.
      ### if working_props == NULL, then skip. what if they equal the
@@ -5772,9 +6482,10 @@ svn_wc__db_upgrade_apply_props(svn_sqlit
      ###
      ### revert only goes into BASE. (and WORKING better be there!)
 
-     Prior to 1.4.0, REVERT_PROPS did not exist. If a file was deleted,
-     then a copy (with props) could not replace the deletion. An addition
-     *could* be performed, but that would never bring its own props.
+     Prior to 1.4.0 (ORIGINAL_FORMAT < 8), REVERT_PROPS did not exist. If a
+     file was deleted, then a copy (potentially with props) was disallowed
+     and could not replace the deletion. An addition *could* be performed,
+     but that would never bring its own props.
 
      1.4.0 through 1.4.5 created the concept of REVERT_PROPS, but had a
      bug in svn_wc_add_repos_file2() whereby a copy-with-props did NOT
@@ -5789,6 +6500,80 @@ svn_wc__db_upgrade_apply_props(svn_sqlit
      We will use ORIGINAL_FORMAT and SVN_WC__NO_REVERT_FILES to determine
      the handling of our inputs, relative to the state of this node.
   */
+
+  /* Collect information about this node.  */
+  SVN_ERR(prop_upgrade_trees(&have_base, &have_work, &work_presence, &wc_id,
+                             sdb, local_relpath));
+
+  /* Detect the buggy scenario described above. We cannot upgrade this
+     working copy if we have no idea where BASE_PROPS should go.  */
+  if (original_format > SVN_WC__NO_REVERT_FILES
+      && revert_props == NULL
+      && have_work
+      && work_presence == svn_wc__db_status_normal)
+    {
+      /* There should be REVERT_PROPS, so it appears that we just ran into
+         the described bug. Sigh.  */
+      return svn_error_createf(SVN_ERR_WC_CORRUPT, NULL,
+                               _("The properties of '%s' are in an "
+                                 "indeterminate state and cannot be "
+                                 "upgraded. See issue #2530."),
+                               local_relpath);
+    }
+
+  if (have_base)
+    {
+      apr_hash_t *props = revert_props ? revert_props : base_props;
+
+      SVN_ERR(svn_sqlite__get_statement(&stmt, sdb, STMT_UPDATE_BASE_PROPS));
+      SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+      SVN_ERR(svn_sqlite__bind_properties(stmt, 3, props, scratch_pool));
+      SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+      /* ### should we provide a nicer error message?  */
+      SVN_ERR_ASSERT(affected_rows == 1);
+    }
+
+  if (have_work)
+    {
+      /* WORKING_NODE has very limited 'presence' values.  */
+      SVN_ERR_ASSERT(work_presence == svn_wc__db_status_normal
+                     || work_presence == svn_wc__db_status_not_present
+                     || work_presence == svn_wc__db_status_base_deleted
+                     || work_presence == svn_wc__db_status_incomplete);
+
+      /* Do we have a replaced node? It has properties: an empty set for
+         adds, and a non-empty set for copies/moves.  */
+      if (work_presence == svn_wc__db_status_normal
+          && original_format > SVN_WC__NO_REVERT_FILES)
+        {
+          SVN_ERR(svn_sqlite__get_statement(&stmt, sdb,
+                                            STMT_UPDATE_WORKING_PROPS));
+          SVN_ERR(svn_sqlite__bindf(stmt, "is", wc_id, local_relpath));
+          SVN_ERR(svn_sqlite__bind_properties(stmt, 3, revert_props,
+                                              scratch_pool));
+          SVN_ERR(svn_sqlite__update(&affected_rows, stmt));
+
+          /* ### should we provide a nicer error message?  */
+          SVN_ERR_ASSERT(affected_rows == 1);
+        }
+      /* else other states should have no properties.  */
+      /* ### should we insert empty props for <= SVN_WC__NO_REVERT_FILES?  */
+    }
+
+  /* If there are WORKING_PROPS, then they always go into ACTUAL_NODE.  */
+  if (working_props != NULL)
+    {
+      struct set_props_baton spb = { 0 };
+
+      spb.props = working_props;

[... 680 lines stripped ...]