Continuing issue #749.  Make locks associate together in groups
instead of in parent-child hierarchies.  Parent-child is still used
when closing.  Directory locking is now separate from committable
harvesting.

* subversion/include/svn_wc.h
  (svn_wc_adm_open, svn_wc_adm_retrieve, svn_wc_adm_close): Document
  new behavior.

* subversion/libsvn_wc/wc.h
  (struct svn_wc_adm_access_t): Remove parent member. Rename children
  member to set.

* subversion/libsvn_wc/lock.c
  (svn_wc__adm_access_child): Deleted.
  (svn_wc__adm_access_alloc): Remove parent parameter and change all
  callers.
  (svn_wc_adm_open): Add the associated access baton to the hash after
  creating the hash. Use temporary hash for opening children.
  (svn_wc_adm_retrieve): Don't call svn_wc__adm_access_child.
  (svn_wc__do_adm_close): Copy children to array before closing them.

* subversion/libsvn_client/client.h
  (svn_client__harvest_committables, svn_client__get_copy_committables):
  Remove locked_dirs parameter.

* subversion/libsvn_client/commit_util.c
  (lock_dir): Deleted.
  (harvest_committables): Revert 2554 for this file. Also remove locked_dirs
  parameter, and remove all directory locking.
  (svn_client__harvest_committables, svn_client__get_copy_committables):
  Remove locked_dirs parameter and directory locking.

* subversion/libsvn_client/commit.c
  (unlock_dirs): Deleted.
  (svn_client_commit): Remove locked_dirs.  Open an access baton.
  Close the baton intead of calling unlock_dirs.

* subversion/libsvn_client/copy.c
  (unlock_dirs): Deleted.
  (wc_to_repos_copy): Remove locked_dirs.  Open an access baton.
  Close the baton intead of calling unlock_dirs.
  


git-svn-id: https://svn.apache.org/repos/asf/subversion/branches/issue-749-dev@842699 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/subversion/include/svn_wc.h b/subversion/include/svn_wc.h
index b58df44..3dab4a9 100644
--- a/subversion/include/svn_wc.h
+++ b/subversion/include/svn_wc.h
@@ -53,66 +53,67 @@
 /*** Locking/Opening/Closing ***/
 
 /* Baton for access to working copy administrative area. One day all such
-   access will require a baton, we're not there yet. */
+   access will require a baton, we're not there yet.
+
+   Access batons can be grouped into sets, by passing an existing open
+   baton when opening a new baton.  Given one baton in a set, other batons
+   may be retrieved.  This allows an entire hierarchy to be locked, and
+   then the set of batons can be passed around by passing a single baton.
+ */
 typedef struct svn_wc_adm_access_t svn_wc_adm_access_t;
 
 /* Return an access baton in ADM_ACCESS for the working copy administrative
    area associated with the directory PATH.  If WRITE_LOCK is set the baton
    will include a write lock, otherwise the baton can only be used for read
-   access.
+   access.  If PATH refers to a directory that is already locked then the
+   error SVN_ERR_WC_LOCKED will be returned.
 
-   If PARENT_ACCESS is an open access baton PATH must refer to a
-   subdirectory of the PARENT_ACCESS directory, and then ADM_ACCESS will be
-   a child of PARENT_ACCESS.  svn_wc_adm_close can be used to close
-   ADM_ACCESS. If ADM_ACCESS has not been closed when PARENT_ACCESS is
-   closed, it wil be closed automatically at that stage.
-
-   If PARENT_ACCESS is an open access baton, and PATH refers to a
-   subdirectory that has already been opened as a child of PARENT_ACCESS
-   then the error SVN_ERR_WC_LOCKED will be returned.
-
-   PARENT_ACCESS can be NULL, in which case ADM_ACCESS is not a child of
-   any other baton and must be explicitly closed.
+   If ASSOCIATED is an open access baton then ADM_ACCESS will be added to
+   the set containing ASSOCIATED.  ASSOCIATED can be NULL, in which case
+   ADM_ACCESS is the start of a new set.
 
    If TREE_LOCK is TRUE then the working copy directory hierarchy under
-   PATH will be locked. At each level batons for the subdirectories will be
-   children of the parent directory's baton.  This is an all-or-nothing
-   option, if it is not possible to lock the entire tree then an error will
-   be returned, there will be no locks and ADM_ACCESS will not be valid.
+   PATH will be locked.  All the access batons will become part of the set
+   containing ADM_ACCESS.  This is an all-or-nothing option, if it is not
+   possible to lock the entire tree then an error will be returned and
+   ADM_ACCESS will be invalid.
 
    POOL will be used to allocate memory for the baton and any subsequently
    cached items.  If ADM_ACCESS has not been closed when the pool is
-   cleared, it will be closed automatically at the point but any physical
-   lock will remain in the working copy.  */
+   cleared, it will be closed automatically at that point, and removed from
+   its set.  A baton closed in this way will not remove physical locks from
+   the working copy.  */
 svn_error_t *svn_wc_adm_open (svn_wc_adm_access_t **adm_access,
-                              svn_wc_adm_access_t *parent_access,
+                              svn_wc_adm_access_t *associated,
                               const char *path,
                               svn_boolean_t write_lock,
                               svn_boolean_t tree_lock,
                               apr_pool_t *pool);
 
-/* Return an access baton in ADM_ACCESS associated with PATH. PATH must be
-   a subdirectory of the PARENT_ACCESS directory, and must have been
-   previously opened with PARENT_ACCESS as a parent.
 
-   POOL is used only for local processing it is not used for the batons.
+/* Return an access baton in ADM_ACCESS associated with PATH.  PATH must be
+   a directory that is locked and part of the set containing the ASSOCIATED
+   access baton.
 
-   ### I can't think of a good name for this function. */
+   POOL is used only for local processing it is not used for the batons. */
 svn_error_t *svn_wc_adm_retrieve (svn_wc_adm_access_t **adm_access,
-                                  svn_wc_adm_access_t *parent_access,
+                                  svn_wc_adm_access_t *associated,
                                   const char *path,
                                   apr_pool_t *pool);
 
-/* Give up the access baton ADM_ACCESS, and its lock if any. It will also
-   close any child batons, for which this is a parent baton, that have not
-   yet been explicitly closed.  Any physical locks will be removed from the
-   working copy. */
+
+/* Give up the access baton ADM_ACCESS, and its lock if any. This will
+   recursively close any batons in the same set that are subdirectories of
+   ADM_ACCESS.  Any physical locks will be removed from the working
+   copy. */
 svn_error_t *svn_wc_adm_close (svn_wc_adm_access_t *adm_access);
 
+
 /* Ensure ADM_ACCESS has a write lock, and that it is still valid. Returns
    SVN_ERR_WC_NOT_LOCKED if this is not the case. */
 svn_error_t *svn_wc_adm_write_check (svn_wc_adm_access_t *adm_access);
 
+
 /* Set *LOCKED to non-zero if PATH is locked, else set it to zero. */
 svn_error_t *svn_wc_locked (svn_boolean_t *locked, 
                             const char *path,
diff --git a/subversion/libsvn_client/client.h b/subversion/libsvn_client/client.h
index c7a1eb5..b93c5f9 100644
--- a/subversion/libsvn_client/client.h
+++ b/subversion/libsvn_client/client.h
@@ -329,7 +329,6 @@
    found in TARGETS will not be crawled for modifications.  */
 svn_error_t *
 svn_client__harvest_committables (apr_hash_t **committables,
-                                  apr_hash_t **locked_dirs,
                                   const char *parent_dir,
                                   apr_array_header_t *targets,
                                   svn_boolean_t nonrecursive,
@@ -344,7 +343,6 @@
    to a new repository URL (NEW_URL). */
 svn_error_t *
 svn_client__get_copy_committables (apr_hash_t **committables,
-                                   apr_hash_t **locked_dirs,
                                    const char *new_url,
                                    const char *target,
                                    apr_pool_t *pool);
diff --git a/subversion/libsvn_client/commit.c b/subversion/libsvn_client/commit.c
index 87a7bc8..5f35078 100644
--- a/subversion/libsvn_client/commit.c
+++ b/subversion/libsvn_client/commit.c
@@ -605,32 +605,6 @@
 
 
 static svn_error_t *
-unlock_dirs (apr_hash_t *locked_dirs,
-             apr_pool_t *pool)
-{
-  apr_hash_index_t *hi;
-
-  /* Split if there's nothing to be done. */
-  if (! locked_dirs)
-    return SVN_NO_ERROR;
-
-  /* Clean up any locks. */
-  for (hi = apr_hash_first (pool, locked_dirs); hi; hi = apr_hash_next (hi))
-    {
-      const void *key;
-      void *val;
-      svn_wc_adm_access_t *adm_access;
-
-      apr_hash_this (hi, &key, NULL, &val);
-      adm_access = val;
-      SVN_ERR (svn_wc_adm_close (adm_access));
-    }
-
-  return SVN_NO_ERROR;
-}  
-
-
-static svn_error_t *
 remove_tmpfiles (apr_hash_t *tempfiles,
                  apr_pool_t *pool)
 {
@@ -747,7 +721,8 @@
   const char *base_dir;
   const char *base_url;
   apr_array_header_t *rel_targets;
-  apr_hash_t *committables, *locked_dirs, *tempfiles = NULL;
+  apr_hash_t *committables, *tempfiles = NULL;
+  svn_wc_adm_access_t *base_dir_access;
   apr_array_header_t *commit_items;
   apr_status_t apr_err = 0;
   apr_file_t *xml_hnd = NULL;
@@ -755,6 +730,7 @@
   svn_error_t *bump_err = NULL, *cleanup_err = NULL;
   svn_boolean_t use_xml = (xml_dst && xml_dst[0]) ? TRUE : FALSE;
   svn_boolean_t commit_in_progress = FALSE;
+  svn_wc_entry_t *base_entry;
   const char *display_dir = ".";
   int notify_path_offset;
   int i;
@@ -762,6 +738,21 @@
   /* Condense the target list. */
   SVN_ERR (svn_path_condense_targets (&base_dir, &rel_targets, targets, pool));
 
+  /* Eeek!  To get an access baton we need a directory name.  If the user
+     explicitly commits a deleted file that has been physically removed
+     from the working copy (as is normal for deleted files), then base_dir
+     will be the file name not a directory name.  To identify this case we
+     need to get the entry for base_dir before we get the access baton.  I
+     wonder where this logic should live? */
+  SVN_ERR (svn_wc_entry (&base_entry, base_dir, TRUE, pool));
+  if (base_entry->kind == svn_node_dir)
+    SVN_ERR (svn_wc_adm_open (&base_dir_access, NULL, base_dir, TRUE, TRUE,
+                              pool));
+  else
+    SVN_ERR (svn_wc_adm_open (&base_dir_access, NULL,
+                              svn_path_remove_component_nts (base_dir, pool),
+                              TRUE, TRUE, pool));
+
   /* If we calculated only a base_dir and no relative targets, this
      must mean that we are being asked to commit a single directory.
      In order to do this properly, we need to anchor our commit up one
@@ -790,7 +781,6 @@
 
   /* Crawl the working copy for commit items. */
   if ((cmt_err = svn_client__harvest_committables (&committables, 
-                                                   &locked_dirs,
                                                    base_dir,
                                                    rel_targets, 
                                                    nonrecursive,
@@ -917,13 +907,9 @@
             adm_access_path = item->path;
           else
             svn_path_split_nts (item->path, &adm_access_path, NULL, pool);
-          adm_access = apr_hash_get (locked_dirs, adm_access_path,
-                                     APR_HASH_KEY_STRING);
-          if (! adm_access)
-            /* This should *never* happen */
-            return svn_error_createf(SVN_ERR_GENERAL, 0, NULL, pool,
-                                     "BUG: no lock for %s",
-                                     item->path);
+          if ((bump_err = svn_wc_adm_retrieve (&adm_access, base_dir_access,
+                                               adm_access_path, pool)))
+            goto cleanup;
           
           if ((item->state_flags & SVN_CLIENT_COMMIT_ITEM_ADD) 
               && (item->kind == svn_node_dir)
@@ -948,10 +934,6 @@
         svn_pool_destroy (subpool);
     }
 
-  /* Unlock the locked directories. */
-  if (! ((unlock_err = unlock_dirs (locked_dirs, pool))))
-    locked_dirs = NULL;
-
   /* If we were committing into XML, close the xml file. */      
   if (use_xml)
     {
@@ -980,9 +962,8 @@
   if (commit_in_progress)
     editor->abort_edit (edit_baton); /* ignore return value */
 
-  /* Unlock any remaining locked dirs. */
-  if (locked_dirs)
-    unlock_err = unlock_dirs (locked_dirs, pool);
+  /* ### Under what conditions should we remove the locks? */
+  unlock_err = svn_wc_adm_close (base_dir_access);
 
   /* Remove any outstanding temporary text-base files. */
   cleanup_err = remove_tmpfiles (tempfiles, pool);
diff --git a/subversion/libsvn_client/commit_util.c b/subversion/libsvn_client/commit_util.c
index 05fbb73..f9c6a7d 100644
--- a/subversion/libsvn_client/commit_util.c
+++ b/subversion/libsvn_client/commit_util.c
@@ -47,35 +47,6 @@
 /*** Harvesting Commit Candidates ***/
 
 
-/* If DIR isn't already in the LOCKED_DIRS hash, attempt to lock it.
-   If the lock is successful, add DIR to the LOCKED_DIRS hash.  Use
-   the hash's pool for adding new items, and POOL for any other
-   allocations. */
-static svn_error_t *
-lock_dir (apr_hash_t *locked_dirs,
-          const char *dir,
-          apr_pool_t *pool)
-{
-  apr_pool_t *hash_pool = apr_hash_pool_get (locked_dirs);
-
-  if (! apr_hash_get (locked_dirs, dir, APR_HASH_KEY_STRING))
-    {
-      /* Short lived, don't use hash pool */
-      const char *parent_dir = svn_path_remove_component_nts (dir, pool);
-      svn_wc_adm_access_t *adm_access, *parent_access;
-
-      /* Get parent, NULL is OK */
-      parent_access = apr_hash_get (locked_dirs, parent_dir,
-                                    APR_HASH_KEY_STRING);
-      SVN_ERR (svn_wc_adm_open (&adm_access, parent_access, dir, TRUE, FALSE,
-                                hash_pool));
-      apr_hash_set (locked_dirs, apr_pstrdup (hash_pool, dir), 
-                    APR_HASH_KEY_STRING, adm_access);
-    }
-  return SVN_NO_ERROR;
-}
-
-
 /* Add a new commit candidate (described by all parameters except
    `COMMITTABLES') to the COMMITABLES hash. */
 static void
@@ -137,7 +108,6 @@
    added with history as URL.  */
 static svn_error_t *
 harvest_committables (apr_hash_t *committables,
-                      apr_hash_t *locked_dirs,
                       const char *path,
                       const char *url,
                       const char *copyfrom_url,
@@ -146,7 +116,6 @@
                       svn_boolean_t adds_only,
                       svn_boolean_t copy_mode,
                       svn_boolean_t nonrecursive,
-                      svn_boolean_t lock_subdirs,
                       apr_pool_t *pool)
 {
   apr_pool_t *subpool = svn_pool_create (pool);  /* ### why? */
@@ -299,37 +268,25 @@
   /* Now, if this is something to commit, add it to our list. */
   if (state_flags)
     {
-      /* If the commit item is a directory, lock it, else lock its parent. */
-      if (entry->kind == svn_node_dir)
-        /* ### Need to lock parent here as well? We will need to modify the
-           ### parents entry file if this is not the root */
-        SVN_ERR (lock_dir (locked_dirs, path, pool));
-      else
-        SVN_ERR (lock_dir (locked_dirs, p_path, pool));
-
       /* Finally, add the committable item. */
       add_committable (committables, path, entry->kind, url,
                        cf_url ? entry->copyfrom_rev : entry->revision, 
                        cf_url, state_flags);
-
-      /* We need to lock all directories to enable post-commit
-         processing of the working copy */
-      lock_subdirs = TRUE;
     }
 
-  if (entries) 
+  /* For directories, recursively handle each of their entries (except
+     when the directory is being deleted, unless the deletion is part
+     of a replacement ... how confusing). */
+  if ((entries) 
+      && ((! (state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE))
+          || (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD)))
     {
       apr_hash_index_t *hi;
       svn_wc_entry_t *this_entry;
       apr_pool_t *loop_pool = svn_pool_create (subpool);
 
       /* Loop over all other entries in this directory, skipping the
-         "this dir" entry.
-
-         ### May need to do this twice, if we need to do sub-directories,
-         ### before files. Subdirectories may require us to lock the
-         ### parent, files only lock this dir, and doing them in the wrong
-         ### order may break the parent-child lock relationship. */
+         "this dir" entry. */
       for (hi = apr_hash_first (subpool, entries);
            hi;
            hi = apr_hash_next (hi))
@@ -372,16 +329,6 @@
           if (this_cf_url)
             this_cf_url = svn_path_join (this_cf_url, name, loop_pool);
 
-          if (lock_subdirs && this_entry->kind == svn_node_dir)
-            SVN_ERR (lock_dir (locked_dirs, full_path, pool));
-
-          /* For directories, recursively handle each of their entries
-             (except when the directory is being deleted, unless the
-             deletion is part of a replacement ... how confusing). */
-          if ((state_flags & SVN_CLIENT_COMMIT_ITEM_DELETE)
-              && ! (state_flags & SVN_CLIENT_COMMIT_ITEM_ADD))
-            continue;
-
           /* We'll use the entry's URL if it has one and if we aren't
              in copy_mode, else, we'll just extend the parent's URL
              with the entry's basename.  */
@@ -393,7 +340,7 @@
 
           /* Recurse. */
           SVN_ERR (harvest_committables 
-                   (committables, locked_dirs, full_path,
+                   (committables, full_path,
                     used_url ? used_url : this_entry->url,
                     this_cf_url,
                     (svn_wc_entry_t *)val, 
@@ -401,7 +348,6 @@
                     adds_only,
                     copy_mode,
                     FALSE,
-                    lock_subdirs,
                     subpool));
 
           svn_pool_clear (loop_pool);
@@ -416,7 +362,6 @@
 
 svn_error_t *
 svn_client__harvest_committables (apr_hash_t **committables,
-                                  apr_hash_t **locked_dirs,
                                   const char *parent_dir,
                                   apr_array_header_t *targets,
                                   svn_boolean_t nonrecursive,
@@ -427,9 +372,6 @@
   /* Create the COMMITTABLES hash. */
   *committables = apr_hash_make (pool);
 
-  /* Create the LOCKED_DIRS hash. */
-  *locked_dirs = apr_hash_make (pool);
-
   /* ### Would be nice to use an iteration pool here, but need to
      first look into lifetime issues w/ anything passed to
      harvest_committables() and possibly stored by it. */ 
@@ -516,9 +458,9 @@
            target);
 
       /* Handle our TARGET. */
-      SVN_ERR (harvest_committables (*committables, *locked_dirs, target, 
+      SVN_ERR (harvest_committables (*committables, target, 
                                      url, NULL, entry, NULL, FALSE, FALSE, 
-                                     nonrecursive, FALSE, pool));
+                                     nonrecursive, pool));
 
       i++;
     }
@@ -530,7 +472,6 @@
 
 svn_error_t *
 svn_client__get_copy_committables (apr_hash_t **committables,
-                                   apr_hash_t **locked_dirs,
                                    const char *new_url,
                                    const char *target,
                                    apr_pool_t *pool)
@@ -540,9 +481,6 @@
   /* Create the COMMITTABLES hash. */
   *committables = apr_hash_make (pool);
 
-  /* Create the LOCKED_DIRS hash. */
-  *locked_dirs = apr_hash_make (pool);
-
   /* Read the entry for TARGET. */
   SVN_ERR (svn_wc_entry (&entry, target, FALSE, pool));
   if (! entry)
@@ -550,9 +488,9 @@
       (SVN_ERR_ENTRY_NOT_FOUND, 0, NULL, pool, target);
       
   /* Handle our TARGET. */
-  SVN_ERR (harvest_committables (*committables, *locked_dirs, target, 
+  SVN_ERR (harvest_committables (*committables, target, 
                                  new_url, entry->url, entry, NULL,
-                                 FALSE, TRUE, FALSE, FALSE, pool));
+                                 FALSE, TRUE, FALSE, pool));
 
   return SVN_NO_ERROR;
 }
diff --git a/subversion/libsvn_client/copy.c b/subversion/libsvn_client/copy.c
index 437a0fb..dc4eaeb 100644
--- a/subversion/libsvn_client/copy.c
+++ b/subversion/libsvn_client/copy.c
@@ -362,32 +362,6 @@
 
 
 static svn_error_t *
-unlock_dirs (apr_hash_t *locked_dirs,
-             apr_pool_t *pool)
-{
-  apr_hash_index_t *hi;
-
-  /* Split if there's nothing to be done. */
-  if (! locked_dirs)
-    return SVN_NO_ERROR;
-
-  /* Clean up any locks. */
-  for (hi = apr_hash_first (pool, locked_dirs); hi; hi = apr_hash_next (hi))
-    {
-      const void *key;
-      void *val;
-      svn_wc_adm_access_t *adm_access;
-
-      apr_hash_this (hi, &key, NULL, &val);
-      adm_access = val;
-      SVN_ERR (svn_wc_adm_close (adm_access));
-    }
-
-  return SVN_NO_ERROR;
-}  
-
-
-static svn_error_t *
 remove_tmpfiles (apr_hash_t *tempfiles,
                  apr_pool_t *pool)
 {
@@ -489,7 +463,8 @@
   svn_revnum_t committed_rev = SVN_INVALID_REVNUM;
   const char *committed_date = NULL;
   const char *committed_author = NULL;
-  apr_hash_t *committables, *locked_dirs, *tempfiles = NULL;
+  apr_hash_t *committables, *tempfiles = NULL;
+  svn_wc_adm_access_t *adm_access;
   apr_array_header_t *commit_items;
   svn_error_t *cmt_err = NULL, *unlock_err = NULL, *cleanup_err = NULL;
   svn_boolean_t commit_in_progress = FALSE;
@@ -504,6 +479,9 @@
   if (svn_path_is_empty_nts (parent))
     parent = ".";
 
+  /* ### Do we need locks for a wc->repos copy? */
+  SVN_ERR (svn_wc_adm_open (&adm_access, NULL, parent, TRUE, TRUE, pool));
+
   /* Split the DST_URL into an anchor and target. */
   svn_path_split_nts (dst_url, &anchor, &target, pool);
 
@@ -551,7 +529,6 @@
 
   /* Crawl the working copy for commit items. */
   if ((cmt_err = svn_client__get_copy_committables (&committables, 
-                                                    &locked_dirs,
                                                     base_url,
                                                     base_path,
                                                     pool)))
@@ -608,9 +585,8 @@
   if (session)
     ra_lib->close (session);
 
-  /* Unlock any remaining locked dirs. */
-  if (locked_dirs)
-    unlock_err = unlock_dirs (locked_dirs, pool);
+  /* ### Under what conditions should we remove the locks? */
+  unlock_err = svn_wc_adm_close (adm_access);
 
   /* Remove any outstanding temporary text-base files. */
   if (tempfiles)
diff --git a/subversion/libsvn_wc/lock.c b/subversion/libsvn_wc/lock.c
index 668e389..960d479 100644
--- a/subversion/libsvn_wc/lock.c
+++ b/subversion/libsvn_wc/lock.c
@@ -93,14 +93,12 @@
 
 static svn_wc_adm_access_t *
 svn_wc__adm_access_alloc (enum svn_wc__adm_access_type type,
-                          svn_wc_adm_access_t *parent,
                           const char *path,
                           apr_pool_t *pool)
 {
   svn_wc_adm_access_t *lock = apr_palloc (pool, sizeof (*lock));
   lock->type = type;
-  lock->parent = parent;
-  lock->children = NULL;
+  lock->set = NULL;
   lock->lock_exists = FALSE;
   /* ### Some places lock with a path that is not canonical, we need
      ### cannonical paths for reliable parent-child determination */
@@ -120,8 +118,7 @@
 {
   svn_error_t *err;
   svn_wc_adm_access_t *lock
-    = svn_wc__adm_access_alloc (svn_wc__adm_access_write_lock, NULL, path,
-                                pool);
+    = svn_wc__adm_access_alloc (svn_wc__adm_access_write_lock, path, pool);
 
   err = svn_wc__lock (lock, 0, pool);
   if (err)
@@ -137,33 +134,9 @@
   return SVN_NO_ERROR;
 }
 
-static svn_error_t *
-svn_wc__adm_access_child (svn_wc_adm_access_t **adm_access,
-                          svn_wc_adm_access_t *parent_access,
-                          const char *path,
-                          apr_pool_t *pool)
-{
-
-  /* PATH must be a child. We only need the result, not the allocated
-     name. */
-  const char *name = svn_path_is_child (parent_access->path, path, pool);
-  if (!name)
-    return svn_error_createf (SVN_ERR_WC_INVALID_LOCK, 0, NULL, pool,
-                              "lock path is not a child (%s)",
-                              path);
-
-  if (parent_access->children)
-    *adm_access = apr_hash_get (parent_access->children, path,
-                                APR_HASH_KEY_STRING);
-  else
-    *adm_access = NULL;
-
-  return SVN_NO_ERROR;
-}
-
 svn_error_t *
 svn_wc_adm_open (svn_wc_adm_access_t **adm_access,
-                 svn_wc_adm_access_t *parent_access,
+                 svn_wc_adm_access_t *associated,
                  const char *path,
                  svn_boolean_t write_lock,
                  svn_boolean_t tree_lock,
@@ -171,12 +144,14 @@
 {
   svn_wc_adm_access_t *lock;
 
-  if (parent_access)
+  if (associated && associated->set)
     {
-      SVN_ERR (svn_wc__adm_access_child (&lock, parent_access, path,
-                                         pool));
+      lock = apr_hash_get (associated->set, path, APR_HASH_KEY_STRING);
       if (lock)
-        /* Already locked */
+        /* Already locked.  The reason we don't return the existing baton
+           here is that the user is supposed to know whether a directory is
+           locked: if it's not locked call svn_wc_adm_open, if it is locked
+           call svn_wc_adm_retrieve.  */
         return svn_error_createf (SVN_ERR_WC_LOCKED, 0, NULL, pool,
                                   "directory already locked (%s)",
                                   path);
@@ -185,8 +160,8 @@
   /* Need to create a new lock */
   if (write_lock)
     {
-      lock = svn_wc__adm_access_alloc (svn_wc__adm_access_write_lock,
-                                       parent_access, path, pool);
+      lock = svn_wc__adm_access_alloc (svn_wc__adm_access_write_lock, path,
+                                       pool);
       SVN_ERR (svn_wc__lock (lock, 0, pool));
       lock->lock_exists = TRUE;
     }
@@ -201,36 +176,36 @@
                                   "lock path is not a directory (%s)",
                                   path);
 
-      lock = svn_wc__adm_access_alloc (svn_wc__adm_access_unlocked,
-                                       parent_access, path, pool);
+      lock = svn_wc__adm_access_alloc (svn_wc__adm_access_unlocked, path, pool);
     }
 
-  lock->parent = parent_access;
-  if (lock->parent)
+  if (associated)
     {
-      if (! lock->parent->children)
-        lock->parent->children = apr_hash_make (lock->parent->pool);
+      if (! associated->set)
+        {
+          associated->set = apr_hash_make (associated->pool);
+          apr_hash_set (associated->set, associated->path, APR_HASH_KEY_STRING,
+                        associated);
+        }
 
-#if 0
-      /* ### Need to think about this. Creating an empty APR hash allocates
-         ### several hundred bytes. Since the hash key is the complete
-         ### path, children for different parents cannot clash. Can we save
-         ### memory by reusing the parent's hash? */
-      lock->children = lock->parent->children;
-#endif
-
-      apr_hash_set (lock->parent->children, lock->path, APR_HASH_KEY_STRING,
-                    lock);
+      lock->set = associated->set;
+      apr_hash_set (lock->set, lock->path, APR_HASH_KEY_STRING, lock);
     }
 
   if (tree_lock)
     {
-      /* ### Could use this code to initialise the cache, in which case the
-         ### subpool should be removed. */
+      /* ### Use this code to initialise the cache? */
       apr_hash_t *entries;
       apr_hash_index_t *hi;
       apr_pool_t *subpool = svn_pool_create (pool);
+
       SVN_ERR (svn_wc_entries_read (&entries, path, FALSE, subpool));
+
+      /* Use a temporary hash until all children have been opened. */
+      if (associated)
+        lock->set = apr_hash_make (subpool);
+
+      /* Open the tree */
       for (hi = apr_hash_first (subpool, entries); hi; hi = apr_hash_next (hi))
         {
           void *val;
@@ -246,16 +221,39 @@
             continue;
           entry_path = svn_path_join (lock->path, entry->name, subpool);
 
-          /* Use the main lock's pool here, it needs to persist */
+          /* Don't use the subpool pool here, the lock needs to persist */
           svn_err = svn_wc_adm_open (&entry_access, lock, entry_path,
                                      write_lock, tree_lock, lock->pool);
           if (svn_err)
             {
-              /* Give up any locks we have acquired. */
+              /* This closes all the children in temporary hash as well */
               svn_wc_adm_close (lock);
+              svn_pool_destroy (subpool);
               return svn_err;
             }
         }
+
+      /* Switch from temporary hash to permanent hash */
+      if (associated)
+        {
+          for (hi = apr_hash_first (subpool, lock->set);
+               hi;
+               hi = apr_hash_next (hi))
+            {
+              const void *key;
+              void *val;
+              const char *entry_path;
+              svn_wc_adm_access_t *entry_access;
+
+              apr_hash_this (hi, &key, NULL, &val);
+              entry_path = key;
+              entry_access = val;
+              apr_hash_set (associated->set, entry_path, APR_HASH_KEY_STRING,
+                            entry_access);
+              entry_access->set = associated->set;
+            }
+          lock->set = associated->set;
+        }
       svn_pool_destroy (subpool);
     }
 
@@ -265,11 +263,16 @@
 
 svn_error_t *
 svn_wc_adm_retrieve (svn_wc_adm_access_t **adm_access,
-                     svn_wc_adm_access_t *parent_access,
+                     svn_wc_adm_access_t *associated,
                      const char *path,
                      apr_pool_t *pool)
 {
-  SVN_ERR (svn_wc__adm_access_child (adm_access, parent_access, path, pool));
+  if (associated->set)
+    *adm_access = apr_hash_get (associated->set, path, APR_HASH_KEY_STRING);
+  else if (! strcmp (associated->path, path))
+    *adm_access = associated;
+  else
+    *adm_access = NULL;
   if (! *adm_access)
     return svn_error_createf (SVN_ERR_WC_NOT_LOCKED, 0, NULL, pool,
                               "directory not locked (%s)",
@@ -287,17 +290,39 @@
                          svn_wc__adm_access_pool_cleanup);
 
   /* Close children */
-  if (adm_access->children)
+  if (adm_access->set)
     {
-      for (hi = apr_hash_first (adm_access->pool, adm_access->children);
+      /* The documentation says that modifying a hash while iterating over
+         it is allowed but unpredictable!  So, first loop to identify and
+         copy children, second loop to close them. */
+      int i;
+      apr_array_header_t *children = apr_array_make (adm_access->pool, 1,
+                                                     sizeof (adm_access));
+
+      for (hi = apr_hash_first (adm_access->pool, adm_access->set);
            hi;
            hi = apr_hash_next (hi))
         {
           void *val;
-          svn_wc_adm_access_t *child_access;
+          svn_wc_adm_access_t *associated;
+          const char *name;
           apr_hash_this (hi, NULL, NULL, &val);
-          child_access = val;
-          SVN_ERR (svn_wc__do_adm_close (child_access, preserve_lock));
+          associated = val;
+          name = svn_path_is_child (adm_access->path, associated->path,
+                                    adm_access->pool);
+          if (name)
+            {
+              *(svn_wc_adm_access_t**)apr_array_push (children) = associated;
+              /* Deleting current element is allowed and predictable */
+              apr_hash_set (adm_access->set, associated->path,
+                            APR_HASH_KEY_STRING, NULL);
+            }
+        }
+      for (i = 0; i < children->nelts; ++i)
+        {
+          svn_wc_adm_access_t *child = APR_ARRAY_IDX(children, i,
+                                                     svn_wc_adm_access_t*);
+          SVN_ERR (svn_wc__do_adm_close (child, preserve_lock));
         }
     }
 
@@ -313,10 +338,9 @@
       adm_access->type = svn_wc__adm_access_closed;
     }
 
-  /* Detach from parent */
-  if (adm_access->parent)
-    apr_hash_set (adm_access->parent->children, adm_access->path,
-                  APR_HASH_KEY_STRING, NULL);
+  /* Detach from set */
+  if (adm_access->set)
+    apr_hash_set (adm_access->set, adm_access->path, APR_HASH_KEY_STRING, NULL);
 
   return SVN_NO_ERROR;
 }
diff --git a/subversion/libsvn_wc/wc.h b/subversion/libsvn_wc/wc.h
index c087a74..9f7300f 100644
--- a/subversion/libsvn_wc/wc.h
+++ b/subversion/libsvn_wc/wc.h
@@ -188,12 +188,9 @@
    apr_hash_t *entries;
 #endif
 
-   /* PARENT access baton, may be NULL. */
-   svn_wc_adm_access_t *parent;
-
-   /* CHILDREN is a hash of svn_wc_adm_access_t* keyed on char*
-      representing the path to sub-directories that are also locked. */
-   apr_hash_t *children;
+   /* SET is a hash of svn_wc_adm_access_t* keyed on char* representing the
+      path to other directories that are also locked. */
+   apr_hash_t *set;
 
    /* POOL is used to allocate cached items, they need to persist for the
       lifetime of this access baton */