You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by ph...@apache.org on 2014/02/25 21:18:18 UTC

svn commit: r1571812 [1/2] - in /subversion/branches/fsfs-lock-many/subversion: include/ libsvn_fs/ libsvn_fs_base/ libsvn_fs_fs/ libsvn_repos/ tests/libsvn_fs/ tests/libsvn_ra/

Author: philip
Date: Tue Feb 25 20:18:18 2014
New Revision: 1571812

URL: http://svn.apache.org/r1571812
Log:
On fsfs-lock-many branch: implement the multiple path functions
svn_fs_lock2 and svn_fs_unlock2 for FSFS along with the the
corresponding repos wrappers.  All the locking now goes via the
multiple path functions but only passing a single path at a time.
Add some low-level regression tests to exercise the code with
multiple paths.  This also fixes issue 4472, digest/lock written
in wrong order.

Notes:
 - BDB just loops calling the single path function at a low-level.
 - FSX is currently non-working but will eventually be a duplicate
   of FSFS.

* subversion/include/svn_fs.h
  (svn_fs_lock_target_t,
   svn_fs_lock_result_t,
   svn_fs_lock2,
   svn_fs_unlock2): New.

* subversion/include/svn_repos.h
  (svn_repos_fs_lock2,
   svn_repos_fs_unlock2): New.

* subversion/libsvn_fs/fs-loader.c
  (svn_fs_lock2): New.
  (svn_fs_lock): Call svn_fs_lock2.
  (svn_fs_unlock2): New.
  (svn_fs_unlock): Call svn_fs_unlock2.

* subversion/libsvn_fs/fs-loader.h
  (lock, unlock): Change parameters.

* subversion/libsvn_fs_fs/lock.h
  (svn_fs_fs__lock,
   svn_fs_fs__unlock): Change parameters.

* subversion/libsvn_fs_fs/lock.c
  (set_lock,
   delete_lock): Just act on one file.
  (add_to_digest,
   delete_from_digest): New.
  (struct lock_baton): Change members.
  (check_lock,
   struct lock_info_t): New.
  (lock_body): Handle multiple paths.
  (struct unlock_baton): Change members.
  (check_unlock): New.
  (unlock_body): Handle multiple paths.
  (unlock_single): New.
  (svn_fs_fs__lock,
   svn_fs_fs__unlock): Change parameters.
 
* subversion/libsvn_fs_base/lock.c
  (svn_fs_base__lock,
   svn_fs_base__unlock): Change parameters.

* subversion/libsvn_fs_base/lock.h
  (svn_fs_base__lock,
   svn_fs_base__unlock): Change parameters.

* subversion/libsvn_repos/fs-wrap.c
  (svn_repos_fs_lock2): New.
  (svn_repos_fs_lock): Call svn_repos_fs_lock2.
  (svn_repos_fs_unlock2): New.
  (svn_repos_fs_unlock): Call svn_repos_fs_unlock2.

* subversion/libsvn_repos/repos.c
  (SVN_REPOS__HOOK_POST_LOCK): Tweak comments in prototype file.
  (SVN_REPOS__HOOK_POST_UNLOCK): Tweak comments in prototype file.

* subversion/tests/libsvn_fs/locks-test.c
  (lock_multiple_paths): New test.
  (test_funcs): Add new test.

* subversion/tests/libsvn_ra/ra-test.c
  (make_and_open_local_repos): Create auth baton.
  (commit_tree): New helper.
  (struct lock_baton_t): New.
  (lock_cb): New helper.
  (lock_test): New test.
  (test_funcs): Add new test.

Modified:
    subversion/branches/fsfs-lock-many/subversion/include/svn_fs.h
    subversion/branches/fsfs-lock-many/subversion/include/svn_repos.h
    subversion/branches/fsfs-lock-many/subversion/libsvn_fs/fs-loader.c
    subversion/branches/fsfs-lock-many/subversion/libsvn_fs/fs-loader.h
    subversion/branches/fsfs-lock-many/subversion/libsvn_fs_base/lock.c
    subversion/branches/fsfs-lock-many/subversion/libsvn_fs_base/lock.h
    subversion/branches/fsfs-lock-many/subversion/libsvn_fs_fs/lock.c
    subversion/branches/fsfs-lock-many/subversion/libsvn_fs_fs/lock.h
    subversion/branches/fsfs-lock-many/subversion/libsvn_repos/fs-wrap.c
    subversion/branches/fsfs-lock-many/subversion/libsvn_repos/repos.c
    subversion/branches/fsfs-lock-many/subversion/tests/libsvn_fs/locks-test.c
    subversion/branches/fsfs-lock-many/subversion/tests/libsvn_ra/ra-test.c

Modified: subversion/branches/fsfs-lock-many/subversion/include/svn_fs.h
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-lock-many/subversion/include/svn_fs.h?rev=1571812&r1=1571811&r2=1571812&view=diff
==============================================================================
--- subversion/branches/fsfs-lock-many/subversion/include/svn_fs.h (original)
+++ subversion/branches/fsfs-lock-many/subversion/include/svn_fs.h Tue Feb 25 20:18:18 2014
@@ -2451,11 +2451,30 @@ svn_fs_set_uuid(svn_fs_t *fs,
  * expiration error (depending on the API).
  */
 
+/* The @a targets hash passed to svn_fs_lock2() has <tt>const char
+   *</tt> keys and <tt>svn_fs_lock_target_t *</tt> values. */
+typedef struct svn_fs_lock_target_t
+{
+  const char *token;
+  svn_revnum_t current_rev;
+
+} svn_fs_lock_target_t;
+
+/* The @a results hash returned by svn_fs_lock2() and svn_fs_unlock2()
+   has <tt>const char *</tt> keys and <tt>svn_fs_lock_result_t *</tt>
+   values. */
+typedef struct svn_fs_lock_result_t
+{
+  svn_lock_t *lock;
+  svn_error_t *err;
+
+} svn_fs_lock_result_t;
 
-/** Lock @a path in @a fs, and set @a *lock to a lock
- * representing the new lock, allocated in @a pool.
+/** Lock the paths in @a targets in @a fs, and set @a *results to the
+ * locks or errors representing each new lock, allocated in @a
+ * result_pool.
  *
- * @warning You may prefer to use svn_repos_fs_lock() instead,
+ * @warning You may prefer to use svn_repos_fs_lock2() instead,
  * which see.
  *
  * @a fs must have a username associated with it (see
@@ -2469,28 +2488,56 @@ svn_fs_set_uuid(svn_fs_t *fs,
  * generic DAV client; only mod_dav_svn's autoversioning feature needs
  * to use it.  If in doubt, pass 0.
  *
- * If path is already locked, then return #SVN_ERR_FS_PATH_ALREADY_LOCKED,
+ * The paths to be locked are passed as the <tt>const char *<tt> keys
+ * of the @a targets hash.  The hash values are
+ * <tt>svn_fs_lock_target_t *</tt> and provide the token and
+ * current_rev for each path.  The token is a lock token such as can
+ * be generated using svn_fs_generate_lock_token() (indicating that
+ * the caller wants to dictate the lock token used), or it is @c NULL
+ * (indicating that the caller wishes to have a new token generated by
+ * this function).  If the token is not @c NULL, and represents an
+ * existing lock, then the path must match the path associated with
+ * that existing lock.  If current_rev is a valid revnum, then do an
+ * out-of-dateness check.  If the revnum is less than the
+ * last-changed-revision of the path (or if the path doesn't exist in
+ * HEAD), return * #SVN_ERR_FS_OUT_OF_DATE.
+ *
+ * If a path is already locked, then return #SVN_ERR_FS_PATH_ALREADY_LOCKED,
  * unless @a steal_lock is TRUE, in which case "steal" the existing
  * lock, even if the FS access-context's username does not match the
- * current lock's owner: delete the existing lock on @a path, and
+ * current lock's owner: delete the existing lock on the path, and
  * create a new one.
  *
- * @a token is a lock token such as can be generated using
- * svn_fs_generate_lock_token() (indicating that the caller wants to
- * dictate the lock token used), or it is @c NULL (indicating that the
- * caller wishes to have a new token generated by this function).  If
- * @a token is not @c NULL, and represents an existing lock, then @a
- * path must match the path associated with that existing lock.
- *
  * If @a expiration_date is zero, then create a non-expiring lock.
  * Else, the lock will expire at @a expiration_date.
  *
- * If @a current_rev is a valid revnum, then do an out-of-dateness
- * check.  If the revnum is less than the last-changed-revision of @a
- * path (or if @a path doesn't exist in HEAD), return
- * #SVN_ERR_FS_OUT_OF_DATE.
+ * The results are returned in @a *results hash where the keys are
+ * <tt>const char *</tt> paths and the values are
+ * <tt>svn_fs_lock_result_t *</tt>.  The error associated with each
+ * path is returned as #svn_fs_lock_result_t->err.  The caller must
+ * ensure that all such errors are handled to avoid leaks.  The lock
+ * associated with each path is returned as #svn_fs_lock_result_t->lock,
+ * this will be @c NULL if no lock was created.
  *
  * @note At this time, only files can be locked.
+ *
+ * @since New in 1.9.
+ */
+svn_error_t *
+svn_fs_lock2(apr_hash_t **results,
+             svn_fs_t *fs,
+             apr_hash_t *targets,
+             const char *comment,
+             svn_boolean_t is_dav_comment,
+             apr_time_t expiration_date,
+             svn_boolean_t steal_lock,
+             apr_pool_t *result_pool,
+             apr_pool_t *scratch_pool);
+
+/* Similar to svn_fs_lock2() but locks only a single @a path and
+ * returns the lock in @a *lock, allocated in @a pool, or an error.
+ *
+ * @since New in 1.2.
  */
 svn_error_t *
 svn_fs_lock(svn_lock_t **lock,
@@ -2517,20 +2564,53 @@ svn_fs_generate_lock_token(const char **
                            apr_pool_t *pool);
 
 
-/** Remove the lock on @a path represented by @a token in @a fs.
+/** Remove the locks on the paths in @a targets in @a fs, and return
+ * the results in @a *results allocated in @a result_pool.
  *
- * If @a token doesn't point to a lock, return #SVN_ERR_FS_BAD_LOCK_TOKEN.
- * If @a token points to an expired lock, return #SVN_ERR_FS_LOCK_EXPIRED.
- * If @a fs has no username associated with it, return #SVN_ERR_FS_NO_USER
- * unless @a break_lock is specified.
+ * The paths to be unlocked are passed as <tt>const char *</tt> keys
+ * of the @a targets hash with <tt>svn_fs_lock_target_t *</tt> values.
+ * #svn_fs_lock_target_t->token provides the token to be unlocked for
+ * each path. If the the token doesn't point to a lock, return
+ * #SVN_ERR_FS_BAD_LOCK_TOKEN.  If the token points to an expired
+ * lock, return #SVN_ERR_FS_LOCK_EXPIRED.  If @a fs has no username
+ * associated with it, return #SVN_ERR_FS_NO_USER unless @a break_lock
+ * is specified.
  *
- * If @a token points to a lock, but the username of @a fs's access
+ * If the token points to a lock, but the username of @a fs's access
  * context doesn't match the lock's owner, return
  * #SVN_ERR_FS_LOCK_OWNER_MISMATCH.  If @a break_lock is TRUE, however, don't
  * return error;  allow the lock to be "broken" in any case.  In the latter
- * case, @a token shall be @c NULL.
+ * case, the token shall be @c NULL.
+ *
+ * The results are returned in @a *results hash where the keys are
+ * <tt>const char *</tt> paths and the values are
+ * <tt>svn_fs_lock_result_t *</tt>.  The error associated with each
+ * path is returned as #svn_fs_lock_result_t->err.  The caller must
+ * ensure that all such errors are handled to avoid leaks.
+ *
+ * @note #svn_fs_lock_target_t is used to allow @c NULL tokens to be
+ * passed (it is not possible to pass @c NULL as a hash value
+ * directly), #svn_fs_lock_target_t->current_rev is ignored.
+ *
+ * @note #svn_err_fs_lock_result_t is used to allow @c SVN_NO_ERROR to
+ * be returned (it is not possible to return @c SVN_NO_ERROR as a hash
+ * value directly), #svn_err_fs_lock_result_t->lock is always NULL.
  *
  * Use @a pool for temporary allocations.
+ *
+ * @since New in 1.9.
+ */
+svn_error_t *
+svn_fs_unlock2(apr_hash_t **results,
+               svn_fs_t *fs,
+               apr_hash_t *targets,
+               svn_boolean_t break_lock,
+               apr_pool_t *result_pool,
+               apr_pool_t *scratch_pool);
+
+/** Similar to svn_fs_unlock2() but only unlocks a single path.
+ *
+ * @since New in 1.2.
  */
 svn_error_t *
 svn_fs_unlock(svn_fs_t *fs,

Modified: subversion/branches/fsfs-lock-many/subversion/include/svn_repos.h
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-lock-many/subversion/include/svn_repos.h?rev=1571812&r1=1571811&r2=1571812&view=diff
==============================================================================
--- subversion/branches/fsfs-lock-many/subversion/include/svn_repos.h (original)
+++ subversion/branches/fsfs-lock-many/subversion/include/svn_repos.h Tue Feb 25 20:18:18 2014
@@ -2177,20 +2177,40 @@ svn_repos_fs_begin_txn_for_update(svn_fs
  * @{
  */
 
-/** Like svn_fs_lock(), but invoke the @a repos's pre- and
+/** Like svn_fs_lock2(), but invoke the @a repos's pre- and
  * post-lock hooks before and after the locking action.  Use @a pool
  * for any necessary allocations.
  *
- * If the pre-lock hook or svn_fs_lock() fails, throw the original
- * error to caller.  If an error occurs when running the post-lock
- * hook, return the original error wrapped with
+ * The pre-lock is run for every path in @a targets. Those entries in
+ * @a targets for which the pre-lock is successful are passed to
+ * svn_fs_lock2 and the post-lock is run for those that are
+ * successfully locked.
+ *
+ * @a results contains the result of running the pre-lock and
+ * svn_fs_lock2 if the pre-lock was successful.  If an error occurs
+ * when running the post-lock hook the error is returned wrapped with
  * SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED.  If the caller sees this
- * error, it knows that the lock succeeded anyway.
+ * error, it knows that the some locks succeeded.  In all cases the
+ * caller must handle all errors in @a results to avoid leaks.
  *
  * The pre-lock hook may cause a different token to be used for the
  * lock, instead of @a token; see the pre-lock-hook documentation for
  * more.
  *
+ * @since New in 1.9.
+ */
+svn_error_t *
+svn_repos_fs_lock2(apr_hash_t **results,
+                   svn_repos_t *repos,
+                   apr_hash_t *targets,
+                   const char *comment,
+                   svn_boolean_t is_dav_comment,
+                   apr_time_t expiration_date,
+                   svn_boolean_t steal_lock,
+                   apr_pool_t *scratch_pool);
+
+/** Similar to svn_repos_fs_lock2() but locks only a single path.
+ *
  * @since New in 1.2.
  */
 svn_error_t *
@@ -2219,6 +2239,13 @@ svn_repos_fs_lock(svn_lock_t **lock,
  * @since New in 1.2.
  */
 svn_error_t *
+svn_repos_fs_unlock2(apr_hash_t **results,
+                     svn_repos_t *repos,
+                     apr_hash_t *targets,
+                     svn_boolean_t break_lock,
+                     apr_pool_t *pool);
+
+svn_error_t *
 svn_repos_fs_unlock(svn_repos_t *repos,
                     const char *path,
                     const char *token,

Modified: subversion/branches/fsfs-lock-many/subversion/libsvn_fs/fs-loader.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-lock-many/subversion/libsvn_fs/fs-loader.c?rev=1571812&r1=1571811&r2=1571812&view=diff
==============================================================================
--- subversion/branches/fsfs-lock-many/subversion/libsvn_fs/fs-loader.c (original)
+++ subversion/branches/fsfs-lock-many/subversion/libsvn_fs/fs-loader.c Tue Feb 25 20:18:18 2014
@@ -1561,54 +1561,115 @@ svn_fs_set_uuid(svn_fs_t *fs, const char
 }
 
 svn_error_t *
-svn_fs_lock(svn_lock_t **lock, svn_fs_t *fs, const char *path,
-            const char *token, const char *comment,
-            svn_boolean_t is_dav_comment, apr_time_t expiration_date,
-            svn_revnum_t current_rev, svn_boolean_t steal_lock,
-            apr_pool_t *pool)
+svn_fs_lock2(apr_hash_t **results,
+             svn_fs_t *fs,
+             apr_hash_t *targets,
+             const char *comment,
+             svn_boolean_t is_dav_comment,
+             apr_time_t expiration_date,
+             svn_boolean_t steal_lock,
+             apr_pool_t *result_pool,
+             apr_pool_t *scratch_pool)
 {
+  apr_hash_index_t *hi;
+  apr_hash_t *ok_targets = apr_hash_make(scratch_pool), *ok_results;
+  svn_error_t *err;
+
+  *results = apr_hash_make(result_pool);
+
   /* Enforce that the comment be xml-escapable. */
   if (comment)
-    {
-      if (! svn_xml_is_xml_safe(comment, strlen(comment)))
-        return svn_error_create
-          (SVN_ERR_XML_UNESCAPABLE_DATA, NULL,
-           _("Lock comment contains illegal characters"));
-    }
+    if (! svn_xml_is_xml_safe(comment, strlen(comment)))
+      return svn_error_create(SVN_ERR_XML_UNESCAPABLE_DATA, NULL,
+                              _("Lock comment contains illegal characters"));
+
+  if (expiration_date < 0)
+    return svn_error_create(SVN_ERR_INCORRECT_PARAMS, NULL,
+              _("Negative expiration date passed to svn_fs_lock"));
 
   /* Enforce that the token be an XML-safe URI. */
-  if (token)
+  for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
     {
-      const char *c;
+      const svn_fs_lock_target_t *target = svn__apr_hash_index_val(hi);
+
+      err = SVN_NO_ERROR;
+      if (target->token)
+        {
+          const char *c;
+
+
+          if (strncmp(target->token, "opaquelocktoken:", 16))
+            err = svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+                                    _("Lock token URI '%s' has bad scheme; "
+                                      "expected '%s'"),
+                                    target->token, "opaquelocktoken");
+
+          if (!err)
+            for (c = target->token; *c && !err; c++)
+              if (! svn_ctype_isascii(*c) || svn_ctype_iscntrl(*c))
+                err = svn_error_createf(
+                        SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+                        _("Lock token '%s' is not ASCII or is a "
+                          "control character at byte %u"),
+                        target->token,
+                        (unsigned)(c - target->token));
+
+          /* strlen(token) == c - token. */
+          if (!err && !svn_xml_is_xml_safe(target->token, c - target->token))
+            err = svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+                                    _("Lock token URI '%s' is not XML-safe"),
+                                    target->token);
+        }
 
-      if (strncmp(token, "opaquelocktoken:", 16))
-        return svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
-                                 _("Lock token URI '%s' has bad scheme; "
-                                   "expected '%s'"),
-                                 token, "opaquelocktoken");
-
-      for (c = token; *c; c++)
-        if (! svn_ctype_isascii(*c) || svn_ctype_iscntrl(*c))
-          return svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
-                                   _("Lock token '%s' is not ASCII or is a "
-                                     "control character at byte %u"),
-                                   token, (unsigned)(c - token));
-
-      /* strlen(token) == c - token. */
-      if (! svn_xml_is_xml_safe(token, c - token))
-        return svn_error_createf(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
-                                 _("Lock token URI '%s' is not XML-safe"),
-                                 token);
+      if (err)
+        {
+          svn_fs_lock_result_t *result
+            = apr_palloc(result_pool, sizeof(svn_fs_lock_result_t));
+
+          result->err = err;
+          result->lock = NULL;
+          svn_hash_sets(*results, svn__apr_hash_index_key(hi), result);
+        }
+      else
+        svn_hash_sets(ok_targets, svn__apr_hash_index_key(hi), target);
     }
 
-  if (expiration_date < 0)
-        return svn_error_create
-          (SVN_ERR_INCORRECT_PARAMS, NULL,
-           _("Negative expiration date passed to svn_fs_lock"));
-
-  return svn_error_trace(fs->vtable->lock(lock, fs, path, token, comment,
-                                          is_dav_comment, expiration_date,
-                                          current_rev, steal_lock, pool));
+  err = fs->vtable->lock(&ok_results, fs, ok_targets, comment,
+                         is_dav_comment, expiration_date,
+                         steal_lock, result_pool, scratch_pool);
+
+  for (hi = apr_hash_first(scratch_pool, ok_results);
+       hi; hi = apr_hash_next(hi))
+    svn_hash_sets(*results, svn__apr_hash_index_key(hi),
+                  svn__apr_hash_index_val(hi));
+
+  return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_fs_lock(svn_lock_t **lock, svn_fs_t *fs, const char *path,
+            const char *token, const char *comment,
+            svn_boolean_t is_dav_comment, apr_time_t expiration_date,
+            svn_revnum_t current_rev, svn_boolean_t steal_lock,
+            apr_pool_t *pool)
+{
+  apr_hash_t *targets = apr_hash_make(pool), *results;
+  svn_fs_lock_target_t target; 
+  svn_fs_lock_result_t *result;
+
+  target.token = token;
+  target.current_rev = current_rev;
+  svn_hash_sets(targets, path, &target);
+
+  SVN_ERR(svn_fs_lock2(&results, fs, targets, comment, is_dav_comment,
+                       expiration_date, steal_lock, pool, pool));
+
+  SVN_ERR_ASSERT(apr_hash_count(results));
+  result = svn__apr_hash_index_val(apr_hash_first(pool, results));
+  if (result->lock)
+    *lock = result->lock;
+  
+  return result->err;
 }
 
 svn_error_t *
@@ -1618,11 +1679,34 @@ svn_fs_generate_lock_token(const char **
 }
 
 svn_error_t *
+svn_fs_unlock2(apr_hash_t **results,
+               svn_fs_t *fs,
+               apr_hash_t *targets,
+               svn_boolean_t break_lock,
+               apr_pool_t *result_pool,
+               apr_pool_t *scratch_pool)
+{
+  return svn_error_trace(fs->vtable->unlock(results, fs, targets, break_lock,
+                                            result_pool, scratch_pool));
+}
+
+svn_error_t *
 svn_fs_unlock(svn_fs_t *fs, const char *path, const char *token,
               svn_boolean_t break_lock, apr_pool_t *pool)
 {
-  return svn_error_trace(fs->vtable->unlock(fs, path, token, break_lock,
-                                            pool));
+  apr_hash_t *targets = apr_hash_make(pool), *results;
+  svn_fs_lock_result_t *result;
+
+  if (!token)
+    token = "";
+  svn_hash_sets(targets, path, token);
+
+  SVN_ERR(svn_fs_unlock2(&results, fs, targets, break_lock, pool, pool));
+
+  SVN_ERR_ASSERT(apr_hash_count(results));
+  result = svn__apr_hash_index_val(apr_hash_first(pool, results));
+
+  return result->err;
 }
 
 svn_error_t *

Modified: subversion/branches/fsfs-lock-many/subversion/libsvn_fs/fs-loader.h
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-lock-many/subversion/libsvn_fs/fs-loader.h?rev=1571812&r1=1571811&r2=1571812&view=diff
==============================================================================
--- subversion/branches/fsfs-lock-many/subversion/libsvn_fs/fs-loader.h (original)
+++ subversion/branches/fsfs-lock-many/subversion/libsvn_fs/fs-loader.h Tue Feb 25 20:18:18 2014
@@ -217,16 +217,18 @@ typedef struct fs_vtable_t
   svn_error_t *(*list_transactions)(apr_array_header_t **names_p,
                                     svn_fs_t *fs, apr_pool_t *pool);
   svn_error_t *(*deltify)(svn_fs_t *fs, svn_revnum_t rev, apr_pool_t *pool);
-  svn_error_t *(*lock)(svn_lock_t **lock, svn_fs_t *fs,
-                       const char *path, const char *token,
+  svn_error_t *(*lock)(apr_hash_t **results,
+                       svn_fs_t *fs,
+                       apr_hash_t *targets,
                        const char *comment, svn_boolean_t is_dav_comment,
-                       apr_time_t expiration_date,
-                       svn_revnum_t current_rev, svn_boolean_t steal_lock,
-                       apr_pool_t *pool);
+                       apr_time_t expiration_date, svn_boolean_t steal_lock,
+                       apr_pool_t *result_pool, apr_pool_t *scratch_pool);
   svn_error_t *(*generate_lock_token)(const char **token, svn_fs_t *fs,
                                       apr_pool_t *pool);
-  svn_error_t *(*unlock)(svn_fs_t *fs, const char *path, const char *token,
-                         svn_boolean_t break_lock, apr_pool_t *pool);
+  svn_error_t *(*unlock)(apr_hash_t ** results,
+                         svn_fs_t *fs, apr_hash_t *targets,
+                         svn_boolean_t break_lock,
+                         apr_pool_t *result_pool, apr_pool_t *scratch_pool);
   svn_error_t *(*get_lock)(svn_lock_t **lock, svn_fs_t *fs,
                            const char *path, apr_pool_t *pool);
   svn_error_t *(*get_locks)(svn_fs_t *fs, const char *path, svn_depth_t depth,

Modified: subversion/branches/fsfs-lock-many/subversion/libsvn_fs_base/lock.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-lock-many/subversion/libsvn_fs_base/lock.c?rev=1571812&r1=1571811&r2=1571812&view=diff
==============================================================================
--- subversion/branches/fsfs-lock-many/subversion/libsvn_fs_base/lock.c (original)
+++ subversion/branches/fsfs-lock-many/subversion/libsvn_fs_base/lock.c Tue Feb 25 20:18:18 2014
@@ -91,6 +91,8 @@ txn_body_lock(void *baton, trail_t *trai
   svn_lock_t *existing_lock;
   svn_lock_t *lock;
 
+  *args->lock_p = NULL;
+
   SVN_ERR(svn_fs_base__get_path_kind(&kind, args->path, trail, trail->pool));
 
   /* Until we implement directory locks someday, we only allow locks
@@ -215,31 +217,47 @@ txn_body_lock(void *baton, trail_t *trai
 
 
 svn_error_t *
-svn_fs_base__lock(svn_lock_t **lock,
+svn_fs_base__lock(apr_hash_t **results,
                   svn_fs_t *fs,
-                  const char *path,
-                  const char *token,
+                  apr_hash_t *targets,
                   const char *comment,
                   svn_boolean_t is_dav_comment,
                   apr_time_t expiration_date,
-                  svn_revnum_t current_rev,
                   svn_boolean_t steal_lock,
-                  apr_pool_t *pool)
+                  apr_pool_t *result_pool,
+                  apr_pool_t *scratch_pool)
 {
-  struct lock_args args;
+  apr_hash_index_t *hi;
+
+  *results = apr_hash_make(result_pool);
 
   SVN_ERR(svn_fs__check_fs(fs, TRUE));
 
-  args.lock_p = lock;
-  args.path = svn_fs__canonicalize_abspath(path, pool);
-  args.token = token;
-  args.comment = comment;
-  args.is_dav_comment = is_dav_comment;
-  args.steal_lock = steal_lock;
-  args.expiration_date = expiration_date;
-  args.current_rev = current_rev;
+  for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+    {
+      struct lock_args args;
+      const char *path = svn__apr_hash_index_key(hi);
+      const svn_fs_lock_target_t *target = svn__apr_hash_index_val(hi);
+      svn_lock_t *lock;
+      svn_fs_lock_result_t *result = apr_palloc(result_pool,
+                                                sizeof(svn_fs_lock_result_t));
+
+      args.lock_p = &lock;
+      args.path = svn_fs__canonicalize_abspath(path, result_pool);
+      args.token = target->token;
+      args.comment = comment;
+      args.is_dav_comment = is_dav_comment;
+      args.steal_lock = steal_lock;
+      args.expiration_date = expiration_date;
+      args.current_rev = target->current_rev;
+      
+      result->err = svn_fs_base__retry_txn(fs, txn_body_lock, &args, FALSE,
+                                           scratch_pool);
+      result->lock = lock;
+      svn_hash_sets(*results, args.path, result);
+    }
 
-  return svn_fs_base__retry_txn(fs, txn_body_lock, &args, FALSE, pool);
+  return SVN_NO_ERROR;
 }
 
 
@@ -307,20 +325,37 @@ txn_body_unlock(void *baton, trail_t *tr
 
 
 svn_error_t *
-svn_fs_base__unlock(svn_fs_t *fs,
-                    const char *path,
-                    const char *token,
+svn_fs_base__unlock(apr_hash_t **results,
+                    svn_fs_t *fs,
+                    apr_hash_t *targets,
                     svn_boolean_t break_lock,
-                    apr_pool_t *pool)
+                    apr_pool_t *result_pool,
+                    apr_pool_t *scratch_pool)
 {
-  struct unlock_args args;
+  apr_hash_index_t *hi;
+
+  *results = apr_hash_make(result_pool);
 
   SVN_ERR(svn_fs__check_fs(fs, TRUE));
 
-  args.path = svn_fs__canonicalize_abspath(path, pool);
-  args.token = token;
-  args.break_lock = break_lock;
-  return svn_fs_base__retry_txn(fs, txn_body_unlock, &args, TRUE, pool);
+  for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+    {
+      struct unlock_args args;
+      const char *path = svn__apr_hash_index_key(hi);
+      const char *token = svn__apr_hash_index_val(hi);
+      svn_fs_lock_result_t *result = apr_palloc(result_pool,
+                                                sizeof(svn_fs_lock_result_t));
+
+      args.path = svn_fs__canonicalize_abspath(path, result_pool);
+      args.token = token;
+      args.break_lock = break_lock;
+      result->err = svn_fs_base__retry_txn(fs, txn_body_unlock, &args, TRUE,
+                                           scratch_pool);
+      result->lock = NULL;
+      svn_hash_sets(*results, args.path, result);
+    }
+
+  return SVN_NO_ERROR;
 }
 
 

Modified: subversion/branches/fsfs-lock-many/subversion/libsvn_fs_base/lock.h
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-lock-many/subversion/libsvn_fs_base/lock.h?rev=1571812&r1=1571811&r2=1571812&view=diff
==============================================================================
--- subversion/branches/fsfs-lock-many/subversion/libsvn_fs_base/lock.h (original)
+++ subversion/branches/fsfs-lock-many/subversion/libsvn_fs_base/lock.h Tue Feb 25 20:18:18 2014
@@ -35,26 +35,26 @@ extern "C" {
 /* These functions implement part of the FS loader library's fs
    vtables.  See the public svn_fs.h for docstrings.*/
 
-svn_error_t *svn_fs_base__lock(svn_lock_t **lock,
+svn_error_t *svn_fs_base__lock(apr_hash_t **results,
                                svn_fs_t *fs,
-                               const char *path,
-                               const char *token,
+                               apr_hash_t *targets,
                                const char *comment,
                                svn_boolean_t is_dav_comment,
                                apr_time_t expiration_date,
-                               svn_revnum_t current_rev,
                                svn_boolean_t steal_lock,
-                               apr_pool_t *pool);
+                               apr_pool_t *result_pool,
+                               apr_pool_t *scratch_pool);
 
 svn_error_t *svn_fs_base__generate_lock_token(const char **token,
                                               svn_fs_t *fs,
                                               apr_pool_t *pool);
 
-svn_error_t *svn_fs_base__unlock(svn_fs_t *fs,
-                                 const char *path,
-                                 const char *token,
+svn_error_t *svn_fs_base__unlock(apr_hash_t **results,
+                                 svn_fs_t *fs,
+                                 apr_hash_t *targets,
                                  svn_boolean_t break_lock,
-                                 apr_pool_t *pool);
+                                 apr_pool_t *result_pool,
+                                 apr_pool_t *scratch_pool);
 
 svn_error_t *svn_fs_base__get_lock(svn_lock_t **lock,
                                    svn_fs_t *fs,

Modified: subversion/branches/fsfs-lock-many/subversion/libsvn_fs_fs/lock.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-lock-many/subversion/libsvn_fs_fs/lock.c?rev=1571812&r1=1571811&r2=1571812&view=diff
==============================================================================
--- subversion/branches/fsfs-lock-many/subversion/libsvn_fs_fs/lock.c (original)
+++ subversion/branches/fsfs-lock-many/subversion/libsvn_fs_fs/lock.c Tue Feb 25 20:18:18 2014
@@ -42,6 +42,7 @@
 
 #include "private/svn_fs_util.h"
 #include "private/svn_fspath.h"
+#include "private/svn_sorts_private.h"
 
 /* Names of hash keys used to store a lock for writing to disk. */
 #define PATH_KEY "path"
@@ -339,8 +340,6 @@ read_digest_file(apr_hash_t **children_p
 /* Write LOCK in FS to the actual OS filesystem.
 
    Use PERMS_REFERENCE for the permissions of any digest files.
-
-   Note: this takes an FS_PATH because it's called from the hotcopy logic.
  */
 static svn_error_t *
 set_lock(const char *fs_path,
@@ -348,129 +347,110 @@ set_lock(const char *fs_path,
          const char *perms_reference,
          apr_pool_t *pool)
 {
-  svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
-  const char *lock_digest_path = NULL;
-  apr_pool_t *subpool;
+  const char *digest_path;
+  apr_hash_t *children;
 
-  SVN_ERR_ASSERT(lock);
+  SVN_ERR(digest_path_from_path(&digest_path, fs_path, lock->path, pool));
 
-  /* Iterate in reverse, creating the lock for LOCK->path, and then
-     just adding entries for its parent, until we reach a parent
-     that's already listed in *its* parent. */
-  subpool = svn_pool_create(pool);
-  while (1729)
-    {
-      const char *digest_path, *digest_file;
-      apr_hash_t *this_children;
-      svn_lock_t *this_lock;
+  /* We could get away without reading the file as children should
+     always come back empty. */
+  SVN_ERR(read_digest_file(&children, NULL, fs_path, digest_path, pool));
 
-      svn_pool_clear(subpool);
+  SVN_ERR(write_digest_file(children, lock, fs_path, digest_path, 
+                            perms_reference, pool));
 
-      /* Calculate the DIGEST_PATH for the currently FS path, and then
-         get its DIGEST_FILE basename. */
-      SVN_ERR(digest_path_from_path(&digest_path, fs_path, this_path->data,
-                                    subpool));
-      digest_file = svn_dirent_basename(digest_path, subpool);
+  return SVN_NO_ERROR;
+}
 
-      SVN_ERR(read_digest_file(&this_children, &this_lock, fs_path,
-                               digest_path, subpool));
+static svn_error_t *
+delete_lock(const char *fs_path,
+            const char *path,
+            apr_pool_t *pool)
+{
+  const char *digest_path;
 
-      /* We're either writing a new lock (first time through only) or
-         a new entry (every time but the first). */
-      if (lock)
-        {
-          this_lock = lock;
-          lock = NULL;
-          lock_digest_path = apr_pstrdup(pool, digest_file);
-        }
-      else
-        {
-          /* If we already have an entry for this path, we're done. */
-          if (svn_hash_gets(this_children, lock_digest_path))
-            break;
-          svn_hash_sets(this_children, lock_digest_path, (void *)1);
-        }
-      SVN_ERR(write_digest_file(this_children, this_lock, fs_path,
-                                digest_path, perms_reference, subpool));
+  SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
 
-      /* Prep for next iteration, or bail if we're done. */
-      if (svn_fspath__is_root(this_path->data, this_path->len))
-        break;
-      svn_stringbuf_set(this_path,
-                        svn_fspath__dirname(this_path->data, subpool));
-    }
+  SVN_ERR(svn_io_remove_file2(digest_path, TRUE, pool));
 
-  svn_pool_destroy(subpool);
   return SVN_NO_ERROR;
 }
 
-/* Delete LOCK from FS in the actual OS filesystem. */
 static svn_error_t *
-delete_lock(svn_fs_t *fs,
-            svn_lock_t *lock,
-            apr_pool_t *pool)
+add_to_digest(const char *fs_path,
+              apr_array_header_t *paths,
+              const char *index_path,
+              const char *perms_reference,
+              apr_pool_t *pool)
 {
-  svn_stringbuf_t *this_path = svn_stringbuf_create(lock->path, pool);
-  const char *child_to_kill = NULL;
-  apr_pool_t *subpool;
+  const char *index_digest_path;
+  apr_hash_t *children;
+  svn_lock_t *lock;
+  int i, original_count;
 
-  SVN_ERR_ASSERT(lock);
+  SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
 
-  /* Iterate in reverse, deleting the lock for LOCK->path, and then
-     deleting its entry as it appears in each of its parents. */
-  subpool = svn_pool_create(pool);
-  while (1729)
+  SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
+
+  original_count = apr_hash_count(children);
+
+  for (i = 0; i < paths->nelts; ++i)
     {
+      const char *path = APR_ARRAY_IDX(paths, i, const char *);
       const char *digest_path, *digest_file;
-      apr_hash_t *this_children;
-      svn_lock_t *this_lock;
 
-      svn_pool_clear(subpool);
+      SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
+      digest_file = svn_dirent_basename(digest_path, NULL);
+      svn_hash_sets(children, digest_file, (void *)1);
+    }
 
-      /* Calculate the DIGEST_PATH for the currently FS path, and then
-         get its DIGEST_FILE basename. */
-      SVN_ERR(digest_path_from_path(&digest_path, fs->path, this_path->data,
-                                    subpool));
-      digest_file = svn_dirent_basename(digest_path, subpool);
+  if (apr_hash_count(children) != original_count)
+    SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path, 
+                              perms_reference, pool));
 
-      SVN_ERR(read_digest_file(&this_children, &this_lock, fs->path,
-                               digest_path, subpool));
+  return SVN_NO_ERROR;
+}
 
-      /* Delete the lock (first time through only). */
-      if (lock)
-        {
-          this_lock = NULL;
-          lock = NULL;
-          child_to_kill = apr_pstrdup(pool, digest_file);
-        }
+static svn_error_t *
+delete_from_digest(const char *fs_path,
+                   apr_array_header_t *paths,
+                   const char *index_path,
+                   const char *perms_reference,
+                   apr_pool_t *pool)
+{
+  const char *index_digest_path;
+  apr_hash_t *children;
+  svn_lock_t *lock;
+  int i;
 
-      if (child_to_kill)
-        svn_hash_sets(this_children, child_to_kill, NULL);
+  SVN_ERR(digest_path_from_path(&index_digest_path, fs_path, index_path, pool));
 
-      if (! (this_lock || apr_hash_count(this_children) != 0))
-        {
-          /* Special case:  no goodz, no file.  And remember to nix
-             the entry for it in its parent. */
-          SVN_ERR(svn_io_remove_file2(digest_path, FALSE, subpool));
-        }
-      else
-        {
-          const char *rev_0_path = svn_fs_fs__path_rev_absolute(fs, 0, pool);
-          SVN_ERR(write_digest_file(this_children, this_lock, fs->path,
-                                    digest_path, rev_0_path, subpool));
-        }
+  SVN_ERR(read_digest_file(&children, &lock, fs_path, index_digest_path, pool));
 
-      /* Prep for next iteration, or bail if we're done. */
-      if (svn_fspath__is_root(this_path->data, this_path->len))
-        break;
-      svn_stringbuf_set(this_path,
-                        svn_fspath__dirname(this_path->data, subpool));
+  for (i = 0; i < paths->nelts; ++i)
+    {
+      const char *path = APR_ARRAY_IDX(paths, i, const char *);
+      const char *digest_path, *digest_file;
+
+      SVN_ERR(digest_path_from_path(&digest_path, fs_path, path, pool));
+      digest_file = svn_dirent_basename(digest_path, NULL);
+      svn_hash_sets(children, digest_file, NULL);
     }
 
-  svn_pool_destroy(subpool);
+  if (apr_hash_count(children) || lock)
+    SVN_ERR(write_digest_file(children, lock, fs_path, index_digest_path, 
+                              perms_reference, pool));
+  else
+    SVN_ERR(svn_io_remove_file2(index_digest_path, TRUE, pool));
+
   return SVN_NO_ERROR;
 }
 
+static svn_error_t *
+unlock_single(svn_fs_t *fs,
+              svn_lock_t *lock,
+              apr_pool_t *pool);
+
 /* Set *LOCK_P to the lock for PATH in FS.  HAVE_WRITE_LOCK should be
    TRUE if the caller (or one of its callers) has taken out the
    repository-wide write lock, FALSE otherwise.  If MUST_EXIST is
@@ -505,7 +485,7 @@ get_lock(svn_lock_t **lock_p,
       /* Only remove the lock if we have the write lock.
          Read operations shouldn't change the filesystem. */
       if (have_write_lock)
-        SVN_ERR(delete_lock(fs, lock, pool));
+        SVN_ERR(unlock_single(fs, lock, pool));
       return SVN_FS__ERR_LOCK_EXPIRED(fs, lock->token);
     }
 
@@ -580,7 +560,7 @@ locks_walker(void *baton,
           /* Only remove the lock if we have the write lock.
              Read operations shouldn't change the filesystem. */
           if (have_write_lock)
-            SVN_ERR(delete_lock(wlb->fs, lock, pool));
+            SVN_ERR(unlock_single(wlb->fs, lock, pool));
         }
     }
 
@@ -737,68 +717,59 @@ svn_fs_fs__allow_locked_operation(const 
 
 /* Baton used for lock_body below. */
 struct lock_baton {
-  svn_lock_t **lock_p;
   svn_fs_t *fs;
-  const char *path;
-  const char *token;
+  apr_array_header_t *targets;
+  apr_array_header_t *infos;
   const char *comment;
   svn_boolean_t is_dav_comment;
   apr_time_t expiration_date;
-  svn_revnum_t current_rev;
   svn_boolean_t steal_lock;
-  apr_pool_t *pool;
+  apr_pool_t *result_pool;
 };
 
-
-/* This implements the svn_fs_fs__with_write_lock() 'body' callback
-   type, and assumes that the write lock is held.
-   BATON is a 'struct lock_baton *'. */
 static svn_error_t *
-lock_body(void *baton, apr_pool_t *pool)
+check_lock(svn_error_t **fs_err,
+           const char *path,
+           const svn_fs_lock_target_t *target,
+           struct lock_baton *lb,
+           svn_fs_root_t *root,
+           apr_pool_t *pool)
 {
-  struct lock_baton *lb = baton;
   svn_node_kind_t kind;
   svn_lock_t *existing_lock;
-  svn_lock_t *lock;
-  svn_fs_root_t *root;
-  svn_revnum_t youngest;
-  const char *rev_0_path;
 
-  /* Until we implement directory locks someday, we only allow locks
-     on files or non-existent paths. */
-  /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
-     library dependencies, which are not portable. */
-  SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
-  SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
-  SVN_ERR(svn_fs_fs__check_path(&kind, root, lb->path, pool));
+  *fs_err = SVN_NO_ERROR;
+
+  SVN_ERR(svn_fs_fs__check_path(&kind, root, path, pool));
   if (kind == svn_node_dir)
-    return SVN_FS__ERR_NOT_FILE(lb->fs, lb->path);
+    {
+      *fs_err = SVN_FS__ERR_NOT_FILE(lb->fs, path);
+      return SVN_NO_ERROR;
+    }
 
   /* While our locking implementation easily supports the locking of
      nonexistent paths, we deliberately choose not to allow such madness. */
   if (kind == svn_node_none)
     {
-      if (SVN_IS_VALID_REVNUM(lb->current_rev))
-        return svn_error_createf(
+      if (SVN_IS_VALID_REVNUM(target->current_rev))
+        *fs_err = svn_error_createf(
           SVN_ERR_FS_OUT_OF_DATE, NULL,
           _("Path '%s' doesn't exist in HEAD revision"),
-          lb->path);
+          path);
       else
-        return svn_error_createf(
+        *fs_err = svn_error_createf(
           SVN_ERR_FS_NOT_FOUND, NULL,
           _("Path '%s' doesn't exist in HEAD revision"),
-          lb->path);
-    }
+          path);
 
-  /* We need to have a username attached to the fs. */
-  if (!lb->fs->access_ctx || !lb->fs->access_ctx->username)
-    return SVN_FS__ERR_NO_USER(lb->fs);
+      return SVN_NO_ERROR;
+    }
 
   /* Is the caller attempting to lock an out-of-date working file? */
-  if (SVN_IS_VALID_REVNUM(lb->current_rev))
+  if (SVN_IS_VALID_REVNUM(target->current_rev))
     {
       svn_revnum_t created_rev;
-      SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, lb->path,
+      SVN_ERR(svn_fs_fs__node_created_rev(&created_rev, root, path,
                                           pool));
 
       /* SVN_INVALID_REVNUM means the path doesn't exist.  So
@@ -806,14 +777,22 @@ lock_body(void *baton, apr_pool_t *pool)
          working copy, but somebody else has deleted the thing
          from HEAD.  That counts as being 'out of date'. */
       if (! SVN_IS_VALID_REVNUM(created_rev))
-        return svn_error_createf
-          (SVN_ERR_FS_OUT_OF_DATE, NULL,
-           _("Path '%s' doesn't exist in HEAD revision"), lb->path);
-
-      if (lb->current_rev < created_rev)
-        return svn_error_createf
-          (SVN_ERR_FS_OUT_OF_DATE, NULL,
-           _("Lock failed: newer version of '%s' exists"), lb->path);
+        {
+          *fs_err = svn_error_createf
+            (SVN_ERR_FS_OUT_OF_DATE, NULL,
+             _("Path '%s' doesn't exist in HEAD revision"), path);
+
+          return SVN_NO_ERROR;
+        }
+
+      if (target->current_rev < created_rev)
+        {
+          *fs_err = svn_error_createf
+            (SVN_ERR_FS_OUT_OF_DATE, NULL,
+             _("Lock failed: newer version of '%s' exists"), path);
+
+          return SVN_NO_ERROR;
+        }
     }
 
   /* If the caller provided a TOKEN, we *really* need to see
@@ -832,117 +811,428 @@ lock_body(void *baton, apr_pool_t *pool)
      acceptable to ignore; it means that the path is now free and
      clear for locking, because the fsfs funcs just cleared out both
      of the tables for us.   */
-  SVN_ERR(get_lock_helper(lb->fs, &existing_lock, lb->path, TRUE, pool));
+  SVN_ERR(get_lock_helper(lb->fs, &existing_lock, path, TRUE, pool));
   if (existing_lock)
     {
       if (! lb->steal_lock)
         {
           /* Sorry, the path is already locked. */
-          return SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
-        }
-      else
-        {
-          /* STEAL_LOCK was passed, so fs_username is "stealing" the
-             lock from lock->owner.  Destroy the existing lock. */
-          SVN_ERR(delete_lock(lb->fs, existing_lock, pool));
+          *fs_err = SVN_FS__ERR_PATH_ALREADY_LOCKED(lb->fs, existing_lock);
+          return SVN_NO_ERROR;
         }
     }
 
-  /* Create our new lock, and add it to the tables.
-     Ensure that the lock is created in the correct pool. */
-  lock = svn_lock_create(lb->pool);
-  if (lb->token)
-    lock->token = apr_pstrdup(lb->pool, lb->token);
-  else
-    SVN_ERR(svn_fs_fs__generate_lock_token(&(lock->token), lb->fs,
-                                           lb->pool));
-  lock->path = apr_pstrdup(lb->pool, lb->path);
-  lock->owner = apr_pstrdup(lb->pool, lb->fs->access_ctx->username);
-  lock->comment = apr_pstrdup(lb->pool, lb->comment);
-  lock->is_dav_comment = lb->is_dav_comment;
-  lock->creation_date = apr_time_now();
-  lock->expiration_date = lb->expiration_date;
+  return SVN_NO_ERROR;
+}
+
+struct lock_info_t {
+  const char *path;
+  const char *component;
+  svn_lock_t *lock;
+  svn_error_t *fs_err;
+};
+
+/* This implements the svn_fs_fs__with_write_lock() 'body' callback
+   type, and assumes that the write lock is held.
+   BATON is a 'struct lock_baton *'. */
+static svn_error_t *
+lock_body(void *baton, apr_pool_t *pool)
+{
+  struct lock_baton *lb = baton;
+  svn_fs_root_t *root;
+  svn_revnum_t youngest;
+  const char *rev_0_path;
+  int i, outstanding = 0;
+  apr_pool_t *iterpool = svn_pool_create(pool);
+
+  lb->infos = apr_array_make(lb->result_pool, lb->targets->nelts,
+                             sizeof(struct lock_info_t));
+
+  /* Until we implement directory locks someday, we only allow locks
+     on files or non-existent paths. */
+  /* Use fs->vtable->foo instead of svn_fs_foo to avoid circular
+     library dependencies, which are not portable. */
+  SVN_ERR(lb->fs->vtable->youngest_rev(&youngest, lb->fs, pool));
+  SVN_ERR(lb->fs->vtable->revision_root(&root, lb->fs, youngest, pool));
+
+  for (i = 0; i < lb->targets->nelts; ++i)
+    {
+      const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
+                                                    svn_sort__item_t);
+      const svn_fs_lock_target_t *target = item->value;
+      struct lock_info_t info;
+
+      svn_pool_clear(iterpool);
+
+      info.path = item->key;
+      SVN_ERR(check_lock(&info.fs_err, info.path, target, lb, root, iterpool));
+      info.lock = NULL;
+      info.component = NULL;
+      APR_ARRAY_PUSH(lb->infos, struct lock_info_t) = info;
+      if (!info.fs_err)
+        ++outstanding;
+    }
 
   rev_0_path = svn_fs_fs__path_rev_absolute(lb->fs, 0, pool);
-  SVN_ERR(set_lock(lb->fs->path, lock, rev_0_path, pool));
-  *lb->lock_p = lock;
 
+  /* Given the paths:
+
+       /foo/bar/f
+       /foo/bar/g
+       /zig/x
+
+     we loop through repeatedly.  The first pass sees '/' on all paths
+     and writes the '/' index.  The second pass sees '/foo' twice and
+     writes that index followed by '/zig' and that index. The third
+     pass sees '/foo/bar' twice and writes that index, and then writes
+     the lock for '/zig/x'.  The fourth pass writes the locks for
+     '/foo/bar/f' and '/foo/bar/g'.
+
+     Writing indices before locks is correct: if interrupted it leaves
+     indices without locks rather than locks without indices.  An
+     index without a lock is consistent in that it always shows up as
+     unlocked in svn_fs_fs__allow_locked_operation.  A lock without an
+     index is inconsistent, svn_fs_fs__allow_locked_operation will
+     show locked on the file but unlocked on the parent. */
+
+    
+  while (outstanding)
+    {
+      const char *last_path = NULL;
+      apr_array_header_t *paths;
+
+      svn_pool_clear(iterpool);
+      paths = apr_array_make(iterpool, 1, sizeof(const char *));
+
+      for (i = 0; i < lb->infos->nelts; ++i)
+        {
+          struct lock_info_t *info = &APR_ARRAY_IDX(lb->infos, i,
+                                                    struct lock_info_t);
+          const svn_sort__item_t *item = &APR_ARRAY_IDX(lb->targets, i,
+                                                        svn_sort__item_t);
+          const svn_fs_lock_target_t *target = item->value;
+
+          if (!info->fs_err && !info->lock)
+            {
+              if (!info->component)
+                {
+                  info->component = info->path;
+                  APR_ARRAY_PUSH(paths, const char *) = info->path;
+                  last_path = "/";
+                }
+              else
+                {
+                  info->component = strchr(info->component + 1, '/');
+                  if (!info->component)
+                    {
+                      /* The component is a path to lock, this cannot
+                         match a previous path that need to be indexed. */
+                      if (paths->nelts)
+                        {
+                          SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
+                                                rev_0_path, iterpool));
+                          apr_array_clear(paths);
+                          last_path = NULL;
+                        }
+
+                      info->lock = svn_lock_create(lb->result_pool);
+                      if (target->token)
+                        info->lock->token = target->token;
+                      else
+                        SVN_ERR(svn_fs_fs__generate_lock_token(
+                                  &(info->lock->token), lb->fs,
+                                  lb->result_pool));
+                      info->lock->path = info->path;
+                      info->lock->owner = lb->fs->access_ctx->username;
+                      info->lock->comment = lb->comment;
+                      info->lock->is_dav_comment = lb->is_dav_comment;
+                      info->lock->creation_date = apr_time_now();
+                      info->lock->expiration_date = lb->expiration_date;
+
+                      info->fs_err = set_lock(lb->fs->path, info->lock,
+                                              rev_0_path, iterpool);
+                      --outstanding;
+                    }
+                  else
+                    {
+                      /* The component is a path to an index. */
+                      apr_size_t len = info->component - info->path;
+
+                      if (last_path
+                          && (strncmp(last_path, info->path, len)
+                              || strlen(last_path) != len))
+                        {
+                          /* No match to the previous paths to index. */
+                          SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
+                                                rev_0_path, iterpool));
+                          apr_array_clear(paths);
+                          last_path = NULL;
+                        }
+                      APR_ARRAY_PUSH(paths, const char *) = info->path;
+                      if (!last_path)
+                        last_path = apr_pstrndup(iterpool, info->path, len);
+                    }
+                }
+            }
+
+          if (last_path && i == lb->infos->nelts - 1)
+            SVN_ERR(add_to_digest(lb->fs->path, paths, last_path,
+                                  rev_0_path, iterpool));
+        }
+    }
+      
   return SVN_NO_ERROR;
 }
 
-/* Baton used for unlock_body below. */
 struct unlock_baton {
   svn_fs_t *fs;
-  const char *path;
-  const char *token;
+  apr_array_header_t *targets;
+  apr_array_header_t *infos;
+  svn_boolean_t skip_check;
   svn_boolean_t break_lock;
+  apr_pool_t *result_pool;
+};
+
+static svn_error_t *
+check_unlock(svn_error_t **fs_err,
+             const char *path,
+             const char *token,
+             struct unlock_baton *ub,
+             svn_fs_root_t *root,
+             apr_pool_t *pool)
+{
+  svn_lock_t *lock;
+
+  *fs_err = get_lock(&lock, ub->fs, path, TRUE, TRUE, pool);
+  if (!*fs_err && !ub->break_lock)
+    {
+      if (strcmp(token, lock->token) != 0)
+        *fs_err = SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, path);
+      else if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
+        *fs_err = SVN_FS__ERR_LOCK_OWNER_MISMATCH(ub->fs,
+                                                  ub->fs->access_ctx->username,
+                                                  lock->owner);
+    }
+
+  return SVN_NO_ERROR;
+}
+
+struct unlock_info_t {
+  const char *path;
+  const char *component;
+  svn_error_t *fs_err;
+  int components;
 };
 
-/* This implements the svn_fs_fs__with_write_lock() 'body' callback
-   type, and assumes that the write lock is held.
-   BATON is a 'struct unlock_baton *'. */
 static svn_error_t *
 unlock_body(void *baton, apr_pool_t *pool)
 {
   struct unlock_baton *ub = baton;
-  svn_lock_t *lock;
+  svn_fs_root_t *root;
+  svn_revnum_t youngest;
+  const char *rev_0_path;
+  int i, max_components = 0, outstanding = 0;
+  apr_pool_t *iterpool = svn_pool_create(pool);
+
+  ub->infos = apr_array_make(ub->result_pool, ub->targets->nelts,
+                             sizeof(struct unlock_info_t));
+
+  SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
+  SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
+
+  for (i = 0; i < ub->targets->nelts; ++i)
+    {
+      const svn_sort__item_t *item = &APR_ARRAY_IDX(ub->targets, i,
+                                                    svn_sort__item_t);
+      const char *token = item->value;
+      struct unlock_info_t info;
+
+      svn_pool_clear(iterpool);
+
+      info.path = item->key;
+      if (!ub->skip_check)
+        SVN_ERR(check_unlock(&info.fs_err, info.path, token, ub, root,
+                             iterpool));
+      if (!info.fs_err)
+        {
+          const char *s;
+
+          info.components = 1;
+          info.component = info.path;
+          while((s = strchr(info.component + 1, '/')))
+            {
+              info.component = s;
+              ++info.components;
+            }
+
+          if (info.components > max_components)
+            max_components = info.components;
+
+          ++outstanding;
+        }
+      APR_ARRAY_PUSH(ub->infos, struct unlock_info_t) = info;
+    }
 
-  /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
-  SVN_ERR(get_lock(&lock, ub->fs, ub->path, TRUE, TRUE, pool));
+  rev_0_path = svn_fs_fs__path_rev_absolute(ub->fs, 0, pool);
 
-  /* Unless breaking the lock, we do some checks. */
-  if (! ub->break_lock)
+  for (i = max_components; i >= 0; --i)
     {
-      /* Sanity check:  the incoming token should match lock->token. */
-      if (strcmp(ub->token, lock->token) != 0)
-        return SVN_FS__ERR_NO_SUCH_LOCK(ub->fs, lock->path);
+      const char *last_path = NULL;
+      apr_array_header_t *paths;
+      int j;
 
-      /* There better be a username attached to the fs. */
-      if (! (ub->fs->access_ctx && ub->fs->access_ctx->username))
-        return SVN_FS__ERR_NO_USER(ub->fs);
+      svn_pool_clear(iterpool);
+      paths = apr_array_make(pool, 1, sizeof(const char *));
 
-      /* And that username better be the same as the lock's owner. */
-      if (strcmp(ub->fs->access_ctx->username, lock->owner) != 0)
-        return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
-           ub->fs, ub->fs->access_ctx->username, lock->owner);
+      for (j = 0; j < ub->infos->nelts; ++j)
+        {
+          struct unlock_info_t *info = &APR_ARRAY_IDX(ub->infos, j,
+                                                      struct unlock_info_t);
+
+          if (!info->fs_err && info->path)
+            {
+
+              if (info->components == i)
+                {
+                  SVN_ERR(delete_lock(ub->fs->path, info->path, iterpool));
+                }
+              else if (info->components > i)
+                {
+                  apr_size_t len = info->component - info->path;
+
+                  if (last_path
+                      && (strncmp(last_path, info->path, len)
+                          || strlen(last_path) != len))
+                    {
+                      SVN_ERR(delete_from_digest(ub->fs->path, paths, last_path,
+                                                 rev_0_path, iterpool));
+                      apr_array_clear(paths);
+                      last_path = NULL;
+                    }
+                  APR_ARRAY_PUSH(paths, const char *) = info->path;
+                  if (!last_path)
+                    {
+                      if (info->component > info->path)
+                        last_path = apr_pstrndup(pool, info->path, len);
+                      else
+                        last_path = "/";
+                    }
+
+                  if (info->component > info->path)
+                    {
+                      --info->component;
+                      while(info->component[0] != '/')
+                        --info->component;
+                    }
+                }
+            }
+
+          if (last_path && j == ub->infos->nelts - 1)
+            SVN_ERR(delete_from_digest(ub->fs->path, paths, last_path,
+                                       rev_0_path, iterpool));
+        }
     }
 
-  /* Remove lock and lock token files. */
-  return delete_lock(ub->fs, lock, pool);
+  return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+unlock_single(svn_fs_t *fs,
+              svn_lock_t *lock,
+              apr_pool_t *pool)
+{
+  struct unlock_baton ub;
+  svn_sort__item_t item;
+  apr_array_header_t *targets = apr_array_make(pool, 1,
+                                               sizeof(svn_sort__item_t));
+  item.key = lock->path;
+  item.klen = strlen(item.key);
+  item.value = (char*)lock->token;
+  APR_ARRAY_PUSH(targets, svn_sort__item_t) = item;
+
+  ub.fs = fs;
+  ub.targets = targets;
+  ub.skip_check = TRUE;
+  ub.result_pool = pool;
+
+  SVN_ERR(unlock_body(&ub, pool));
+
+  return SVN_NO_ERROR;
 }
 
 
 /*** Public API implementations ***/
 
 svn_error_t *
-svn_fs_fs__lock(svn_lock_t **lock_p,
+svn_fs_fs__lock(apr_hash_t **results,
                 svn_fs_t *fs,
-                const char *path,
-                const char *token,
+                apr_hash_t *targets,
                 const char *comment,
                 svn_boolean_t is_dav_comment,
                 apr_time_t expiration_date,
-                svn_revnum_t current_rev,
                 svn_boolean_t steal_lock,
-                apr_pool_t *pool)
+                apr_pool_t *result_pool,
+                apr_pool_t *scratch_pool)
 {
   struct lock_baton lb;
+  apr_array_header_t *sorted_targets;
+  apr_hash_t *cannonical_targets = apr_hash_make(scratch_pool);
+  apr_hash_index_t *hi;
+  svn_error_t *err;
+  int i;
+
+  *results = apr_hash_make(result_pool);
 
   SVN_ERR(svn_fs__check_fs(fs, TRUE));
-  path = svn_fs__canonicalize_abspath(path, pool);
 
-  lb.lock_p = lock_p;
+  /* We need to have a username attached to the fs. */
+  if (!fs->access_ctx || !fs->access_ctx->username)
+    return SVN_FS__ERR_NO_USER(fs);
+
+  /* The FS locking API allows both cannonical and non-cannonical
+     paths which means that the same cannonical path could be
+     represented more than once in the TARGETS hash.  We just keep
+     one, choosing one with a token if possible. */
+  for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+    {
+      const char *path = svn__apr_hash_index_key(hi);
+      const svn_fs_lock_target_t *target = svn__apr_hash_index_val(hi);
+      const svn_fs_lock_target_t *other;
+
+      path = svn_fspath__canonicalize(path, result_pool);
+      other = svn_hash_gets(cannonical_targets, path);
+
+      if (!other || (!other->token && target->token))
+        svn_hash_sets(cannonical_targets, path, target);
+    }
+
+  sorted_targets = svn_sort__hash(cannonical_targets,
+                                  svn_sort_compare_items_as_paths,
+                                  scratch_pool);
+
   lb.fs = fs;
-  lb.path = path;
-  lb.token = token;
+  lb.targets = sorted_targets;
   lb.comment = comment;
   lb.is_dav_comment = is_dav_comment;
   lb.expiration_date = expiration_date;
-  lb.current_rev = current_rev;
   lb.steal_lock = steal_lock;
-  lb.pool = pool;
+  lb.result_pool = result_pool;
+
+  err = svn_fs_fs__with_write_lock(fs, lock_body, &lb, scratch_pool);
+  for (i = 0; i < lb.infos->nelts; ++i)
+    {
+      struct lock_info_t *info = &APR_ARRAY_IDX(lb.infos, i,
+                                                struct lock_info_t);
+      svn_fs_lock_result_t *result
+        = apr_palloc(result_pool, sizeof(svn_fs_lock_result_t));
+
+      result->lock = info->lock;
+      result->err = info->fs_err;
+
+      svn_hash_sets(*results, info->path, result);
+    }
 
-  return svn_fs_fs__with_write_lock(fs, lock_body, &lb, pool);
+  return err;
 }
 
 
@@ -962,25 +1252,67 @@ svn_fs_fs__generate_lock_token(const cha
   return SVN_NO_ERROR;
 }
 
-
 svn_error_t *
-svn_fs_fs__unlock(svn_fs_t *fs,
-                  const char *path,
-                  const char *token,
+svn_fs_fs__unlock(apr_hash_t **results,
+                  svn_fs_t *fs,
+                  apr_hash_t *targets,
                   svn_boolean_t break_lock,
-                  apr_pool_t *pool)
+                  apr_pool_t *result_pool,
+                  apr_pool_t *scratch_pool)
 {
   struct unlock_baton ub;
+  apr_array_header_t *sorted_targets;
+  apr_hash_t *cannonical_targets = apr_hash_make(scratch_pool);
+  apr_hash_index_t *hi;
+  svn_error_t *err;
+  int i;
+
+  *results = apr_hash_make(result_pool);
 
   SVN_ERR(svn_fs__check_fs(fs, TRUE));
-  path = svn_fs__canonicalize_abspath(path, pool);
+
+  /* We need to have a username attached to the fs. */
+  if (!fs->access_ctx || !fs->access_ctx->username)
+    return SVN_FS__ERR_NO_USER(fs);
+
+  for (hi = apr_hash_first(scratch_pool, targets); hi; hi = apr_hash_next(hi))
+    {
+      const char *path = svn__apr_hash_index_key(hi);
+      const char *token = svn__apr_hash_index_val(hi);
+      const char *other;
+
+      path = svn_fspath__canonicalize(path, result_pool);
+      other = svn_hash_gets(cannonical_targets, path);
+
+      if (!other)
+        svn_hash_sets(cannonical_targets, path, token);
+    }
+
+  sorted_targets = svn_sort__hash(cannonical_targets,
+                                  svn_sort_compare_items_as_paths,
+                                  scratch_pool);
 
   ub.fs = fs;
-  ub.path = path;
-  ub.token = token;
+  ub.targets = sorted_targets;
+  ub.skip_check = FALSE;
   ub.break_lock = break_lock;
+  ub.result_pool = result_pool;
+
+  err = svn_fs_fs__with_write_lock(fs, unlock_body, &ub, scratch_pool);
+  for (i = 0; i < ub.infos->nelts; ++i)
+    {
+      struct unlock_info_t *info = &APR_ARRAY_IDX(ub.infos, i,
+                                                  struct unlock_info_t);
+      svn_fs_lock_result_t *result
+        = apr_palloc(result_pool, sizeof(svn_fs_lock_result_t));
+
+      result->lock = NULL;
+      result->err = info->fs_err;
+
+      svn_hash_sets(*results, info->path, result);
+    }
 
-  return svn_fs_fs__with_write_lock(fs, unlock_body, &ub, pool);
+  return err;
 }
 
 

Modified: subversion/branches/fsfs-lock-many/subversion/libsvn_fs_fs/lock.h
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-lock-many/subversion/libsvn_fs_fs/lock.h?rev=1571812&r1=1571811&r2=1571812&view=diff
==============================================================================
--- subversion/branches/fsfs-lock-many/subversion/libsvn_fs_fs/lock.h (original)
+++ subversion/branches/fsfs-lock-many/subversion/libsvn_fs_fs/lock.h Tue Feb 25 20:18:18 2014
@@ -32,26 +32,26 @@ extern "C" {
 /* These functions implement some of the calls in the FS loader
    library's fs vtables. */
 
-svn_error_t *svn_fs_fs__lock(svn_lock_t **lock,
+svn_error_t *svn_fs_fs__lock(apr_hash_t **results,
                              svn_fs_t *fs,
-                             const char *path,
-                             const char *token,
+                             apr_hash_t *targets,
                              const char *comment,
                              svn_boolean_t is_dav_comment,
                              apr_time_t expiration_date,
-                             svn_revnum_t current_rev,
                              svn_boolean_t steal_lock,
-                             apr_pool_t *pool);
+                             apr_pool_t *result_pool,
+                             apr_pool_t *scratch_pool);
 
 svn_error_t *svn_fs_fs__generate_lock_token(const char **token,
                                             svn_fs_t *fs,
                                             apr_pool_t *pool);
 
-svn_error_t *svn_fs_fs__unlock(svn_fs_t *fs,
-                               const char *path,
-                               const char *token,
+svn_error_t *svn_fs_fs__unlock(apr_hash_t **results,
+                               svn_fs_t *fs,
+                               apr_hash_t *targets,
                                svn_boolean_t break_lock,
-                               apr_pool_t *pool);
+                               apr_pool_t *result_pool,
+                               apr_pool_t *scratch_pool);
 
 svn_error_t *svn_fs_fs__get_lock(svn_lock_t **lock,
                                  svn_fs_t *fs,

Modified: subversion/branches/fsfs-lock-many/subversion/libsvn_repos/fs-wrap.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-lock-many/subversion/libsvn_repos/fs-wrap.c?rev=1571812&r1=1571811&r2=1571812&view=diff
==============================================================================
--- subversion/branches/fsfs-lock-many/subversion/libsvn_repos/fs-wrap.c (original)
+++ subversion/branches/fsfs-lock-many/subversion/libsvn_repos/fs-wrap.c Tue Feb 25 20:18:18 2014
@@ -500,114 +500,254 @@ svn_repos_fs_revision_proplist(apr_hash_
 }
 
 svn_error_t *
-svn_repos_fs_lock(svn_lock_t **lock,
-                  svn_repos_t *repos,
-                  const char *path,
-                  const char *token,
-                  const char *comment,
-                  svn_boolean_t is_dav_comment,
-                  apr_time_t expiration_date,
-                  svn_revnum_t current_rev,
-                  svn_boolean_t steal_lock,
-                  apr_pool_t *pool)
+svn_repos_fs_lock2(apr_hash_t **results,
+                   svn_repos_t *repos,
+                   apr_hash_t *targets,
+                   const char *comment,
+                   svn_boolean_t is_dav_comment,
+                   apr_time_t expiration_date,
+                   svn_boolean_t steal_lock,
+                   apr_pool_t *pool)
 {
   svn_error_t *err;
   svn_fs_access_t *access_ctx = NULL;
   const char *username = NULL;
-  const char *new_token;
-  apr_array_header_t *paths;
   apr_hash_t *hooks_env;
+  apr_hash_t *pre_targets = apr_hash_make(pool);
+  apr_hash_index_t *hi;
+  apr_array_header_t *paths;
+  apr_hash_t *pre_results;
+
+  *results = apr_hash_make(pool);
 
   /* Parse the hooks-env file (if any). */
   SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
                                      pool, pool));
 
-  /* Setup an array of paths in anticipation of the ra layers handling
-     multiple locks in one request (1.3 most likely).  This is only
-     used by svn_repos__hooks_post_lock. */
-  paths = apr_array_make(pool, 1, sizeof(const char *));
-  APR_ARRAY_PUSH(paths, const char *) = path;
-
   SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
   if (access_ctx)
     SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
 
   if (! username)
-    return svn_error_createf
+    return svn_error_create
       (SVN_ERR_FS_NO_USER, NULL,
-       "Cannot lock path '%s', no authenticated username available.", path);
+       "Cannot lock path, no authenticated username available.");
 
   /* Run pre-lock hook.  This could throw error, preventing
-     svn_fs_lock() from happening. */
-  SVN_ERR(svn_repos__hooks_pre_lock(repos, hooks_env, &new_token, path,
-                                    username, comment, steal_lock, pool));
-  if (*new_token)
-    token = new_token;
-
-  /* Lock. */
-  SVN_ERR(svn_fs_lock(lock, repos->fs, path, token, comment, is_dav_comment,
-                      expiration_date, current_rev, steal_lock, pool));
-
-  /* Run post-lock hook. */
-  if ((err = svn_repos__hooks_post_lock(repos, hooks_env,
-                                        paths, username, pool)))
-    return svn_error_create
-      (SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED, err,
-       "Lock succeeded, but post-lock hook failed");
+     svn_fs_lock2() from happening for that path. */
+  for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
+    {
+      const char *new_token;
+      svn_fs_lock_target_t *target;
+      const char *path = svn__apr_hash_index_key(hi);
+
+      err = svn_repos__hooks_pre_lock(repos, hooks_env, &new_token, path,
+                                      username, comment, steal_lock, pool);
+      if (err)
+        {
+          svn_fs_lock_result_t *result
+            = apr_palloc(pool, sizeof(svn_fs_lock_result_t));
+          result->lock = NULL;
+          result->err = err;
+          svn_hash_sets(*results, path, result);
+          continue;
+        }
 
-  return SVN_NO_ERROR;
+      target = svn__apr_hash_index_val(hi);
+      if (*new_token)
+        target->token = new_token;
+      svn_hash_sets(pre_targets, path, target);
+    }
+
+  err = svn_fs_lock2(&pre_results, repos->fs, pre_targets, comment,
+                     is_dav_comment, expiration_date, steal_lock,
+                     pool, pool);
+
+  /* Combine results so the caller can handle all the errors. */
+  for (hi = apr_hash_first(pool, pre_results); hi; hi = apr_hash_next(hi))
+    svn_hash_sets(*results, svn__apr_hash_index_key(hi),
+                  svn__apr_hash_index_val(hi));
+
+  /* If there are locks and an error should we return or run the post-lock? */
+  if (err)
+    return svn_error_trace(err);
+
+  /* Extract paths that were successfully locked for the post-lock. */
+  paths = apr_array_make(pool, apr_hash_count(pre_results),
+                         sizeof(const char *));
+  for (hi = apr_hash_first(pool, pre_results); hi; hi = apr_hash_next(hi))
+    {
+      const char *path = svn__apr_hash_index_key(hi);
+      svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+
+      if (result->lock)
+        APR_ARRAY_PUSH(paths, const char *) = path;
+    }
+
+  err = svn_repos__hooks_post_lock(repos, hooks_env, paths, username, pool);
+  if (err)
+    err = svn_error_create(SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED, err,
+                           "Locking succeeded, but post-lock hook failed");
+
+  return err;
+}
+
+svn_error_t *
+svn_repos_fs_lock(svn_lock_t **lock,
+                  svn_repos_t *repos,
+                  const char *path,
+                  const char *token,
+                  const char *comment,
+                  svn_boolean_t is_dav_comment,
+                  apr_time_t expiration_date,
+                  svn_revnum_t current_rev,
+                  svn_boolean_t steal_lock,
+                  apr_pool_t *pool)
+{
+  apr_hash_t *targets = apr_hash_make(pool), *results;
+  svn_fs_lock_target_t target; 
+  svn_error_t *err;
+
+  target.token = token;
+  target.current_rev = current_rev;
+  svn_hash_sets(targets, path, &target);
+
+  err = svn_repos_fs_lock2(&results, repos, targets, comment, is_dav_comment,
+                           expiration_date, steal_lock,
+                           pool);
+
+  if (apr_hash_count(results))
+    {
+      const svn_fs_lock_result_t *result
+        = svn__apr_hash_index_val(apr_hash_first(pool, results));
+
+      if (result->lock)
+        *lock = result->lock;
+
+      if (err && result->err)
+        svn_error_compose(err, result->err);
+      else if (!err)
+        err = result->err;
+    }
+
+  return err;
 }
 
 
 svn_error_t *
-svn_repos_fs_unlock(svn_repos_t *repos,
-                    const char *path,
-                    const char *token,
-                    svn_boolean_t break_lock,
-                    apr_pool_t *pool)
+svn_repos_fs_unlock2(apr_hash_t **results,
+                     svn_repos_t *repos,
+                     apr_hash_t *targets,
+                     svn_boolean_t break_lock,
+                     apr_pool_t *pool)
 {
   svn_error_t *err;
   svn_fs_access_t *access_ctx = NULL;
   const char *username = NULL;
-  apr_array_header_t *paths;
   apr_hash_t *hooks_env;
+  apr_hash_t *pre_targets = apr_hash_make(pool);
+  apr_hash_index_t *hi;
+  apr_array_header_t *paths;
+  apr_hash_t *pre_results;
+
+  * results = apr_hash_make(pool);
 
   /* Parse the hooks-env file (if any). */
   SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
                                      pool, pool));
 
-  /* Setup an array of paths in anticipation of the ra layers handling
-     multiple locks in one request (1.3 most likely).  This is only
-     used by svn_repos__hooks_post_lock. */
-  paths = apr_array_make(pool, 1, sizeof(const char *));
-  APR_ARRAY_PUSH(paths, const char *) = path;
-
   SVN_ERR(svn_fs_get_access(&access_ctx, repos->fs));
   if (access_ctx)
     SVN_ERR(svn_fs_access_get_username(&username, access_ctx));
 
   if (! break_lock && ! username)
-    return svn_error_createf
+    return svn_error_create
       (SVN_ERR_FS_NO_USER, NULL,
-       _("Cannot unlock path '%s', no authenticated username available"),
-       path);
+       _("Cannot unlock, no authenticated username available"));
 
   /* Run pre-unlock hook.  This could throw error, preventing
-     svn_fs_unlock() from happening. */
-  SVN_ERR(svn_repos__hooks_pre_unlock(repos, hooks_env, path, username, token,
-                                      break_lock, pool));
+     svn_fs_unlock2() from happening for that path. */
+  for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
+    {
+      const char *path = svn__apr_hash_index_key(hi);
+      const char *token = svn__apr_hash_index_val(hi);
 
-  /* Unlock. */
-  SVN_ERR(svn_fs_unlock(repos->fs, path, token, break_lock, pool));
+      err = svn_repos__hooks_pre_unlock(repos, hooks_env, path, username, token,
+                                        break_lock, pool);
+      if (err)
+        {
+          svn_fs_lock_result_t *result
+            = apr_palloc(pool, sizeof(svn_fs_lock_result_t));
+          result->lock = NULL;
+          result->err = err;
+          svn_hash_sets(*results, path, result);
+          continue;
+        }
+
+      svn_hash_sets(pre_targets, path, token);
+    }
+
+  err = svn_fs_unlock2(&pre_results, repos->fs, pre_targets, break_lock,
+                       pool, pool);
+
+  /* Combine results for all paths. */
+  for (hi = apr_hash_first(pool, pre_results); hi; hi = apr_hash_next(hi))
+    svn_hash_sets(*results, svn__apr_hash_index_key(hi),
+                  svn__apr_hash_index_val(hi));
+
+  if (err)
+    return svn_error_trace(err);
+
+  /* Extract paths that were successfully unlocked for the post-unlock. */
+  paths = apr_array_make(pool, apr_hash_count(pre_results),
+                         sizeof(const char *));
+  for (hi = apr_hash_first(pool, pre_results); hi; hi = apr_hash_next(hi))
+    {
+      const char *path = svn__apr_hash_index_key(hi);
+      svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+
+      if (result->lock)
+        APR_ARRAY_PUSH(paths, const char *) = path;
+    }
+  
 
-  /* Run post-unlock hook. */
   if ((err = svn_repos__hooks_post_unlock(repos, hooks_env, paths,
                                           username, pool)))
-    return svn_error_create
-      (SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED, err,
-       _("Unlock succeeded, but post-unlock hook failed"));
+    err = svn_error_create(SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED, err,
+                           _("Unlock succeeded, but post-unlock hook failed"));
 
-  return SVN_NO_ERROR;
+  return err;
+}
+
+svn_error_t *
+svn_repos_fs_unlock(svn_repos_t *repos,
+                    const char *path,
+                    const char *token,
+                    svn_boolean_t break_lock,
+                    apr_pool_t *pool)
+{
+  apr_hash_t *targets = apr_hash_make(pool), *results;
+  svn_error_t *err;
+
+  if (!token)
+    token = "";
+
+  svn_hash_sets(targets, path, token);
+
+  err = svn_repos_fs_unlock2(&results, repos, targets, break_lock, pool);
+
+  if (apr_hash_count(results))
+    {
+      const svn_fs_lock_result_t *result
+        = svn__apr_hash_index_val(apr_hash_first(pool, results));
+
+      if (err && result->err)
+        svn_error_compose(err, result->err);
+      else if (!err)
+        err = result->err;
+    }
+
+  return err;
 }
 
 

Modified: subversion/branches/fsfs-lock-many/subversion/libsvn_repos/repos.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-lock-many/subversion/libsvn_repos/repos.c?rev=1571812&r1=1571811&r2=1571812&view=diff
==============================================================================
--- subversion/branches/fsfs-lock-many/subversion/libsvn_repos/repos.c (original)
+++ subversion/branches/fsfs-lock-many/subversion/libsvn_repos/repos.c Tue Feb 25 20:18:18 2014
@@ -807,18 +807,16 @@ PREWRITTEN_HOOKS_TEXT
 "#   [1] REPOS-PATH   (the path to this repository)"                         NL
 "#   [2] USER         (the user who created the lock)"                       NL
 "#"                                                                          NL
-"# The paths that were just locked are passed to the hook via STDIN (as"     NL
-"# of Subversion 1.2, only one path is passed per invocation, but the"       NL
-"# plan is to pass all locked paths at once, so the hook program"            NL
-"# should be written accordingly)."                                          NL
+"# The paths that were just locked are passed to the hook via STDIN."        NL
 "#"                                                                          NL
 "# The default working directory for the invocation is undefined, so"        NL
 "# the program should set one explicitly if it cares."                       NL
 "#"                                                                          NL
-"# Because the lock has already been created and cannot be undone,"          NL
+"# Because the locks have already been created and cannot be undone,"        NL
 "# the exit code of the hook program is ignored.  The hook program"          NL
-"# can use the 'svnlook' utility to help it examine the"                     NL
-"# newly-created lock."                                                      NL
+"# can use the 'svnlook' utility to examine the paths in the repository"     NL
+"# but since the hook is invoked asyncronously the newly-created locks"      NL
+"# may no longer be present."                                                NL
 "#"                                                                          NL
 "# On a Unix system, the normal procedure is to have '"SCRIPT_NAME"'"        NL
 "# invoke other programs to do the real work, though it may do the"          NL
@@ -870,10 +868,7 @@ PREWRITTEN_HOOKS_TEXT
 "#   [1] REPOS-PATH   (the path to this repository)"                         NL
 "#   [2] USER         (the user who destroyed the lock)"                     NL
 "#"                                                                          NL
-"# The paths that were just unlocked are passed to the hook via STDIN"       NL
-"# (as of Subversion 1.2, only one path is passed per invocation, but"       NL
-"# the plan is to pass all unlocked paths at once, so the hook program"      NL
-"# should be written accordingly)."                                          NL
+"# The paths that were just unlocked are passed to the hook via STDIN."      NL
 "#"                                                                          NL
 "# The default working directory for the invocation is undefined, so"        NL
 "# the program should set one explicitly if it cares."                       NL

Modified: subversion/branches/fsfs-lock-many/subversion/tests/libsvn_fs/locks-test.c
URL: http://svn.apache.org/viewvc/subversion/branches/fsfs-lock-many/subversion/tests/libsvn_fs/locks-test.c?rev=1571812&r1=1571811&r2=1571812&view=diff
==============================================================================
--- subversion/branches/fsfs-lock-many/subversion/tests/libsvn_fs/locks-test.c (original)
+++ subversion/branches/fsfs-lock-many/subversion/tests/libsvn_fs/locks-test.c Tue Feb 25 20:18:18 2014
@@ -28,6 +28,7 @@
 
 #include "svn_error.h"
 #include "svn_fs.h"
+#include "svn_hash.h"
 
 #include "../svn_test_fs.h"
 
@@ -787,6 +788,94 @@ lock_out_of_date(const svn_test_opts_t *
 }
 
 
+static svn_error_t *
+lock_multiple_paths(const svn_test_opts_t *opts,
+                    apr_pool_t *pool)
+{
+  svn_fs_t *fs;
+  svn_fs_txn_t *txn;
+  svn_fs_root_t *root, *txn_root;
+  const char *conflict;
+  svn_revnum_t newrev;
+  svn_fs_access_t *access;
+  svn_fs_lock_target_t target;
+  apr_hash_t *paths, *results;
+  svn_fs_lock_result_t *result;
+  svn_lock_t *lock;
+
+  SVN_ERR(create_greek_fs(&fs, &newrev, "test-lock-multiple-paths",
+                          opts, pool));
+  SVN_ERR(svn_fs_create_access(&access, "bubba", pool));
+  SVN_ERR(svn_fs_set_access(fs, access));
+  SVN_ERR(svn_fs_revision_root(&root, fs, newrev, pool));
+  SVN_ERR(svn_fs_begin_txn2(&txn, fs, newrev, SVN_FS_TXN_CHECK_LOCKS, pool));
+  SVN_ERR(svn_fs_txn_root(&txn_root, txn, pool));
+  SVN_ERR(svn_fs_make_dir(txn_root, "/A/BB", pool));
+  SVN_ERR(svn_fs_make_dir(txn_root, "/A/BBB", pool));
+  SVN_ERR(svn_fs_copy(root, "/A/mu", txn_root, "/A/BB/mu", pool));
+  SVN_ERR(svn_fs_copy(root, "/A/mu", txn_root, "/A/BBB/mu", pool));
+  SVN_ERR(svn_fs_commit_txn(&conflict, &newrev, txn, pool));
+
+  paths = apr_hash_make(pool);
+  target.token = NULL;
+  target.current_rev = newrev;
+  svn_hash_sets(paths, "/A/B/E/alpha", &target);
+  svn_hash_sets(paths, "/A/B/E/beta", &target);
+  svn_hash_sets(paths, "/A/B/E/zulu", &target);
+  svn_hash_sets(paths, "/A/BB/mu", &target);
+  svn_hash_sets(paths, "/A/BBB/mu", &target);
+  svn_hash_sets(paths, "/A/D/G/pi", &target);
+  svn_hash_sets(paths, "/A/D/G/rho", &target);
+  svn_hash_sets(paths, "/A/mu", &target);
+  svn_hash_sets(paths, "/X/zulu", &target);
+
+#define EXPECT_LOCK(path)                                  \
+  result = svn_hash_gets(results, (path));                 \
+  SVN_TEST_ASSERT(result && result->lock && !result->err); \
+  SVN_ERR(svn_fs_get_lock(&lock, fs, (path), pool));       \
+  SVN_TEST_ASSERT(lock)
+
+#define EXPECT_ERROR(path)                                 \
+  result = svn_hash_gets(results, (path));                 \
+  SVN_TEST_ASSERT(result && !result->lock && result->err); \
+  svn_error_clear(result->err);                            \
+  SVN_ERR(svn_fs_get_lock(&lock, fs, (path), pool));       \
+  SVN_TEST_ASSERT(!lock)
+
+  SVN_ERR(svn_fs_lock2(&results, fs, paths, "comment", 0, 0, 0, pool, pool));
+
+  EXPECT_LOCK("/A/B/E/alpha");
+  EXPECT_LOCK("/A/B/E/beta");
+  EXPECT_ERROR("/A/B/E/zulu");
+  EXPECT_LOCK("/A/BB/mu");
+  EXPECT_LOCK("/A/BBB/mu");
+  EXPECT_LOCK("/A/D/G/pi");
+  EXPECT_LOCK("/A/D/G/rho");
+  EXPECT_LOCK("/A/mu");
+  EXPECT_ERROR("/X/zulu");
+
+#define EXPECT_NO_ERROR(path)              \
+  result = svn_hash_gets(results, (path)); \
+  SVN_TEST_ASSERT(result && !result->err)
+
+  SVN_ERR(svn_fs_unlock2(&results, fs, paths, TRUE, pool, pool));
+
+  EXPECT_NO_ERROR("/A/B/E/alpha");
+  EXPECT_NO_ERROR("/A/B/E/beta");
+  EXPECT_ERROR("/A/B/E/zulu");
+  EXPECT_NO_ERROR("/A/BB/mu");
+  EXPECT_NO_ERROR("/A/BBB/mu");
+  EXPECT_NO_ERROR("/A/D/G/pi");
+  EXPECT_NO_ERROR("/A/D/G/rho");
+  EXPECT_NO_ERROR("/A/mu");
+  EXPECT_ERROR("/X/zulu");
+
+  return SVN_NO_ERROR;
+
+#undef EXPECT_LOCK
+#undef EXPECT_ERROR
+#undef EXPECT_NO_ERROR
+}
 
 /* ------------------------------------------------------------------------ */
 
@@ -819,5 +908,7 @@ struct svn_test_descriptor_t test_funcs[
                        "breaking, stealing, refreshing a lock"),
     SVN_TEST_OPTS_PASS(lock_out_of_date,
                        "check out-of-dateness before locking"),
+    SVN_TEST_OPTS_PASS(lock_multiple_paths,
+                       "lock multiple paths"),
     SVN_TEST_NULL
   };