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/03/13 19:47:18 UTC

svn commit: r1577280 [2/3] - in /subversion/trunk: ./ notes/ subversion/include/ subversion/include/private/ subversion/libsvn_client/ subversion/libsvn_fs/ subversion/libsvn_fs_base/ subversion/libsvn_fs_fs/ subversion/libsvn_fs_x/ subversion/libsvn_r...

Modified: subversion/trunk/subversion/libsvn_fs_x/lock.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_x/lock.c?rev=1577280&r1=1577279&r2=1577280&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_x/lock.c (original)
+++ subversion/trunk/subversion/libsvn_fs_x/lock.c Thu Mar 13 18:47:17 2014
@@ -20,7 +20,6 @@
  * ====================================================================
  */
 
-
 #include "svn_pools.h"
 #include "svn_error.h"
 #include "svn_dirent_uri.h"
@@ -42,6 +41,7 @@
 
 #include "private/svn_fs_util.h"
 #include "private/svn_fspath.h"
+#include "private/svn_sorts_private.h"
 #include "svn_private_config.h"
 
 /* Names of hash keys used to store a lock for writing to disk. */
@@ -253,24 +253,23 @@ read_digest_file(apr_hash_t **children_p
   apr_hash_t *hash;
   svn_stream_t *stream;
   const char *val;
+  svn_node_kind_t kind;
 
   if (lock_p)
     *lock_p = NULL;
   if (children_p)
     *children_p = apr_hash_make(pool);
 
-  err = svn_stream_open_readonly(&stream, digest_path, pool, pool);
-  if (err && APR_STATUS_IS_ENOENT(err->apr_err))
-    {
-      svn_error_clear(err);
-      return SVN_NO_ERROR;
-    }
-  SVN_ERR(err);
+  SVN_ERR(svn_io_check_path(digest_path, &kind, pool));
+  if (kind == svn_node_none)
+    return SVN_NO_ERROR;
 
   /* If our caller doesn't care about anything but the presence of the
      file... whatever. */
-  if (! (lock_p || children_p))
-    return svn_stream_close(stream);
+  if (kind == svn_node_file && !lock_p && !children_p)
+    return SVN_NO_ERROR;
+
+  SVN_ERR(svn_stream_open_readonly(&stream, digest_path, pool, pool));
 
   hash = apr_hash_make(pool);
   if ((err = svn_hash_read2(hash, stream, SVN_HASH_TERMINATOR, pool)))
@@ -341,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,
@@ -350,130 +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_x__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
@@ -508,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);
     }
 
@@ -583,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));
         }
     }
 
@@ -740,82 +717,82 @@ svn_fs_x__allow_locked_operation(const c
 
 /* 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_x__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_x__check_path(&kind, root, lb->path, pool));
+  *fs_err = SVN_NO_ERROR;
+
+  SVN_ERR(svn_fs_x__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_x__node_created_rev(&created_rev, root, lb->path, pool));
+      SVN_ERR(svn_fs_x__node_created_rev(&created_rev, root, path,
+                                         pool));
 
       /* SVN_INVALID_REVNUM means the path doesn't exist.  So
          apparently somebody is trying to lock something in their
          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
@@ -834,116 +811,429 @@ 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 fsx 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_x__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_x__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_x__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_x__allow_locked_operation.  A lock without an
+     index is inconsistent, svn_fs_x__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_x__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_x__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));
 
-  /* 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));
+  SVN_ERR(ub->fs->vtable->youngest_rev(&youngest, ub->fs, pool));
+  SVN_ERR(ub->fs->vtable->revision_root(&root, ub->fs, youngest, pool));
 
-  /* Unless breaking the lock, we do some checks. */
-  if (! ub->break_lock)
+  for (i = 0; i < ub->targets->nelts; ++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 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;
 
-      /* 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);
 
-      /* 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);
+      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;
     }
 
-  /* Remove lock and lock token files. */
-  return delete_lock(ub->fs, lock, pool);
+  rev_0_path = svn_fs_x__path_rev_absolute(ub->fs, 0, pool);
+
+  for (i = max_components; i >= 0; --i)
+    {
+      const char *last_path = NULL;
+      apr_array_header_t *paths;
+      int j;
+
+      svn_pool_clear(iterpool);
+      paths = apr_array_make(pool, 1, sizeof(const char *));
+
+      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
+                      && strcmp(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));
+        }
+    }
+
+  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_x__lock(svn_lock_t **lock_p,
+svn_fs_x__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_x__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_x__with_write_lock(fs, lock_body, &lb, pool);
+  return err;
 }
 
 
@@ -963,25 +1253,67 @@ svn_fs_x__generate_lock_token(const char
   return SVN_NO_ERROR;
 }
 
-
 svn_error_t *
-svn_fs_x__unlock(svn_fs_t *fs,
-                 const char *path,
-                 const char *token,
+svn_fs_x__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_x__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_x__with_write_lock(fs, unlock_body, &ub, pool);
+  return err;
 }
 
 

Modified: subversion/trunk/subversion/libsvn_fs_x/lock.h
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_fs_x/lock.h?rev=1577280&r1=1577279&r2=1577280&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_fs_x/lock.h (original)
+++ subversion/trunk/subversion/libsvn_fs_x/lock.h Thu Mar 13 18:47:17 2014
@@ -32,27 +32,27 @@ extern "C" {
 /* These functions implement some of the calls in the FS loader
    library's fs vtables. */
 
-svn_error_t *svn_fs_x__lock(svn_lock_t **lock,
+svn_error_t *svn_fs_x__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_x__generate_lock_token(const char **token,
                                            svn_fs_t *fs,
                                            apr_pool_t *pool);
 
-svn_error_t *svn_fs_x__unlock(svn_fs_t *fs,
-                              const char *path,
-                              const char *token,
+svn_error_t *svn_fs_x__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_x__get_lock(svn_lock_t **lock,
                                 svn_fs_t *fs,
                                 const char *path,

Modified: subversion/trunk/subversion/libsvn_ra_local/ra_plugin.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_ra_local/ra_plugin.c?rev=1577280&r1=1577279&r2=1577280&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_ra_local/ra_plugin.c (original)
+++ subversion/trunk/subversion/libsvn_ra_local/ra_plugin.c Thu Mar 13 18:47:17 2014
@@ -409,28 +409,35 @@ deltify_etc(const svn_commit_info_t *com
   /* Maybe unlock the paths. */
   if (deb->lock_tokens)
     {
-      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+      apr_pool_t *subpool = svn_pool_create(scratch_pool);
+      apr_hash_t *targets = apr_hash_make(subpool);
+      apr_hash_t *results;
       apr_hash_index_t *hi;
 
-      for (hi = apr_hash_first(scratch_pool, deb->lock_tokens); hi;
+      for (hi = apr_hash_first(subpool, deb->lock_tokens); hi;
            hi = apr_hash_next(hi))
         {
           const void *relpath = svn__apr_hash_index_key(hi);
           const char *token = svn__apr_hash_index_val(hi);
           const char *fspath;
 
-          svn_pool_clear(iterpool);
+          fspath = svn_fspath__join(deb->fspath_base, relpath, subpool);
+          svn_hash_sets(targets, fspath, token);
+        }
 
-          fspath = svn_fspath__join(deb->fspath_base, relpath, iterpool);
+      /* We may get errors here if the lock was broken or stolen
+         after the commit succeeded.  This is fine and should be
+         ignored. */
+      svn_error_clear(svn_repos_fs_unlock2(&results, deb->repos, targets,
+                                           FALSE, subpool, subpool));
 
-          /* We may get errors here if the lock was broken or stolen
-             after the commit succeeded.  This is fine and should be
-             ignored. */
-          svn_error_clear(svn_repos_fs_unlock(deb->repos, fspath, token,
-                                              FALSE, iterpool));
+      for (hi = apr_hash_first(subpool, results); hi; hi = apr_hash_next(hi))
+        {
+          svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+          svn_error_clear(result->err);
         }
 
-      svn_pool_destroy(iterpool);
+      svn_pool_destroy(subpool);
     }
 
   /* But, deltification shouldn't be stopped just because someone's
@@ -1374,7 +1381,6 @@ svn_ra_local__get_location_segments(svn_
                                           NULL, NULL, pool);
 }
 
-
 static svn_error_t *
 svn_ra_local__lock(svn_ra_session_t *session,
                    apr_hash_t *path_revs,
@@ -1385,51 +1391,53 @@ svn_ra_local__lock(svn_ra_session_t *ses
                    apr_pool_t *pool)
 {
   svn_ra_local__session_baton_t *sess = session->priv;
-  apr_hash_index_t *hi;
   apr_pool_t *iterpool = svn_pool_create(pool);
+  apr_hash_t *targets = apr_hash_make(pool), *results;
+  apr_hash_index_t *hi;
+  svn_error_t *err, *err_cb = SVN_NO_ERROR;
 
   /* A username is absolutely required to lock a path. */
   SVN_ERR(get_username(session, pool));
 
   for (hi = apr_hash_first(pool, path_revs); hi; hi = apr_hash_next(hi))
     {
-      svn_lock_t *lock;
-      const void *key;
-      const char *path;
-      void *val;
-      svn_revnum_t *revnum;
-      const char *abs_path;
-      svn_error_t *err, *callback_err = NULL;
+      const char *abs_path = svn_fspath__join(sess->fs_path->data,
+                                              svn__apr_hash_index_key(hi),
+                                              pool);
+      svn_fs_lock_target_t *target = apr_palloc(pool,
+                                                sizeof(svn_fs_lock_target_t));
+      target->token = NULL;
+      target->current_rev = *(svn_revnum_t *)svn__apr_hash_index_val(hi);
 
-      svn_pool_clear(iterpool);
-      apr_hash_this(hi, &key, NULL, &val);
-      path = key;
-      revnum = val;
-
-      abs_path = svn_fspath__join(sess->fs_path->data, path, iterpool);
-
-      /* This wrapper will call pre- and post-lock hooks. */
-      err = svn_repos_fs_lock(&lock, sess->repos, abs_path, NULL, comment,
-                              FALSE /* not DAV comment */,
-                              0 /* no expiration */, *revnum, force,
-                              iterpool);
+      svn_hash_sets(targets, abs_path, target);
+    }
 
-      if (err && !SVN_ERR_IS_LOCK_ERROR(err))
-        return err;
+  err = svn_repos_fs_lock2(&results, sess->repos, targets, comment,
+                           FALSE /* not DAV comment */,
+                           0 /* no expiration */, force,
+                           pool, iterpool);
 
-      if (lock_func)
-        callback_err = lock_func(lock_baton, path, TRUE, err ? NULL : lock,
-                                 err, iterpool);
+  /* Make sure we handle all locking errors in results hash. */
+  for (hi = apr_hash_first(pool, 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);
 
-      svn_error_clear(err);
+      svn_pool_clear(iterpool);
 
-      if (callback_err)
-        return callback_err;
+      path = svn_fspath__skip_ancestor(sess->fs_path->data, path);
+      if (!err_cb)
+        err_cb = lock_func(lock_baton, path, TRUE, result->lock, result->err,
+                           iterpool);
+      svn_error_clear(result->err);
     }
 
   svn_pool_destroy(iterpool);
-
-  return SVN_NO_ERROR;
+  if (err && err_cb)
+    svn_error_compose(err, err_cb);
+  else if (!err)
+    err = err_cb;
+  return svn_error_trace(err);
 }
 
 
@@ -1442,51 +1450,48 @@ svn_ra_local__unlock(svn_ra_session_t *s
                      apr_pool_t *pool)
 {
   svn_ra_local__session_baton_t *sess = session->priv;
-  apr_hash_index_t *hi;
   apr_pool_t *iterpool = svn_pool_create(pool);
+  apr_hash_t *targets = apr_hash_make(pool), *results;
+  apr_hash_index_t *hi;
+  svn_error_t *err, *err_cb = SVN_NO_ERROR;
 
   /* A username is absolutely required to unlock a path. */
   SVN_ERR(get_username(session, pool));
 
   for (hi = apr_hash_first(pool, path_tokens); hi; hi = apr_hash_next(hi))
     {
-      const void *key;
-      const char *path;
-      void *val;
-      const char *abs_path, *token;
-      svn_error_t *err, *callback_err = NULL;
+      const char *abs_path = svn_fspath__join(sess->fs_path->data,
+                                              svn__apr_hash_index_key(hi),
+                                              pool);
+      const char *token = svn__apr_hash_index_val(hi);
 
-      svn_pool_clear(iterpool);
-      apr_hash_this(hi, &key, NULL, &val);
-      path = key;
-      /* Since we can't store NULL values in a hash, we turn "" to
-         NULL here. */
-      if (strcmp(val, "") != 0)
-        token = val;
-      else
-        token = NULL;
-
-      abs_path = svn_fspath__join(sess->fs_path->data, path, iterpool);
-
-      /* This wrapper will call pre- and post-unlock hooks. */
-      err = svn_repos_fs_unlock(sess->repos, abs_path, token, force,
-                                iterpool);
-
-      if (err && !SVN_ERR_IS_UNLOCK_ERROR(err))
-        return err;
+      svn_hash_sets(targets, abs_path, token);
+    }
 
-      if (lock_func)
-        callback_err = lock_func(lock_baton, path, FALSE, NULL, err, iterpool);
+  err = svn_repos_fs_unlock2(&results, sess->repos, targets, force,
+                             pool, iterpool);
 
-      svn_error_clear(err);
+  /* Make sure we handle all locking errors in results hash. */
+  for (hi = apr_hash_first(pool, 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);
+
+      svn_pool_clear(iterpool);
 
-      if (callback_err)
-        return callback_err;
+      path = svn_fspath__skip_ancestor(sess->fs_path->data, path);
+      if (lock_func && !err_cb)
+        err_cb = lock_func(lock_baton, path, FALSE, NULL, result->err,
+                           iterpool);
+      svn_error_clear(result->err);
     }
 
   svn_pool_destroy(iterpool);
-
-  return SVN_NO_ERROR;
+  if (err && err_cb)
+    svn_error_compose(err, err_cb);
+  else if (!err)
+    err = err_cb;
+  return svn_error_trace(err);
 }
 
 

Modified: subversion/trunk/subversion/libsvn_repos/fs-wrap.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_repos/fs-wrap.c?rev=1577280&r1=1577279&r2=1577280&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_repos/fs-wrap.c (original)
+++ subversion/trunk/subversion/libsvn_repos/fs-wrap.c Thu Mar 13 18:47:17 2014
@@ -497,114 +497,266 @@ 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 *result_pool,
+                   apr_pool_t *scratch_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(scratch_pool);
+  apr_hash_index_t *hi;
+  apr_array_header_t *paths;
+  apr_hash_t *pre_results;
+  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+  *results = apr_hash_make(result_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;
+                                     scratch_pool, scratch_pool));
 
   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(scratch_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);
 
-  return SVN_NO_ERROR;
+      svn_pool_clear(iterpool);
+
+      err = svn_repos__hooks_pre_lock(repos, hooks_env, &new_token, path,
+                                      username, comment, steal_lock, iterpool);
+      if (err)
+        {
+          svn_fs_lock_result_t *result
+            = apr_palloc(result_pool, sizeof(svn_fs_lock_result_t));
+          result->lock = NULL;
+          result->err = err;
+          svn_hash_sets(*results, path, result);
+          continue;
+        }
+
+      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,
+                     result_pool, iterpool);
+
+  /* Combine results so the caller can handle all the errors. */
+  for (hi = apr_hash_first(iterpool, 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(iterpool, apr_hash_count(pre_results),
+                         sizeof(const char *));
+  for (hi = apr_hash_first(iterpool, 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, iterpool);
+  if (err)
+    err = svn_error_create(SVN_ERR_REPOS_POST_LOCK_HOOK_FAILED, err,
+                           "Locking succeeded, but post-lock hook failed");
+
+  svn_pool_destroy(iterpool);
+
+  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, 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 *result_pool,
+                     apr_pool_t *scratch_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(scratch_pool);
+  apr_hash_index_t *hi;
+  apr_array_header_t *paths;
+  apr_hash_t *pre_results;
+  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+  *results = apr_hash_make(result_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;
+                                     scratch_pool, scratch_pool));
 
   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(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);
+
+      svn_pool_clear(iterpool);
+
+      err = svn_repos__hooks_pre_unlock(repos, hooks_env, path, username, token,
+                                        break_lock, iterpool);
+      if (err)
+        {
+          svn_fs_lock_result_t *result
+            = apr_palloc(result_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,
+                       result_pool, iterpool);
 
-  /* Unlock. */
-  SVN_ERR(svn_fs_unlock(repos->fs, path, token, break_lock, pool));
+  /* Combine results for all paths. */
+  for (hi = apr_hash_first(iterpool, 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(iterpool, apr_hash_count(pre_results),
+                         sizeof(const char *));
+  for (hi = apr_hash_first(iterpool, 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"));
+                                          username, iterpool)))
+    err = svn_error_create(SVN_ERR_REPOS_POST_UNLOCK_HOOK_FAILED, err,
+                           _("Unlock succeeded, but post-unlock hook failed"));
 
-  return SVN_NO_ERROR;
+  svn_pool_destroy(iterpool);
+
+  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, 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/trunk/subversion/libsvn_repos/repos.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_repos/repos.c?rev=1577280&r1=1577279&r2=1577280&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_repos/repos.c (original)
+++ subversion/trunk/subversion/libsvn_repos/repos.c Thu Mar 13 18:47:17 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/trunk/subversion/libsvn_subr/log.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/libsvn_subr/log.c?rev=1577280&r1=1577279&r2=1577280&view=diff
==============================================================================
--- subversion/trunk/subversion/libsvn_subr/log.c (original)
+++ subversion/trunk/subversion/libsvn_subr/log.c Thu Mar 13 18:47:17 2014
@@ -36,6 +36,7 @@
 #include "svn_path.h"
 #include "svn_pools.h"
 #include "svn_string.h"
+#include "svn_hash.h"
 
 #include "private/svn_log.h"
 
@@ -306,18 +307,18 @@ svn_log__get_file_revs(const char *path,
 }
 
 const char *
-svn_log__lock(const apr_array_header_t *paths,
+svn_log__lock(apr_hash_t *targets,
               svn_boolean_t steal, apr_pool_t *pool)
 {
-  int i;
+  apr_hash_index_t *hi;
   apr_pool_t *iterpool = svn_pool_create(pool);
   svn_stringbuf_t *space_separated_paths = svn_stringbuf_create_empty(pool);
 
-  for (i = 0; i < paths->nelts; i++)
+  for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
     {
-      const char *path = APR_ARRAY_IDX(paths, i, const char *);
+      const char *path = svn__apr_hash_index_key(hi);
       svn_pool_clear(iterpool);
-      if (i != 0)
+      if (space_separated_paths->len)
         svn_stringbuf_appendcstr(space_separated_paths, " ");
       svn_stringbuf_appendcstr(space_separated_paths,
                                svn_path_uri_encode(path, iterpool));
@@ -329,18 +330,18 @@ svn_log__lock(const apr_array_header_t *
 }
 
 const char *
-svn_log__unlock(const apr_array_header_t *paths,
+svn_log__unlock(apr_hash_t *targets,
                 svn_boolean_t break_lock, apr_pool_t *pool)
 {
-  int i;
+  apr_hash_index_t *hi;
   apr_pool_t *iterpool = svn_pool_create(pool);
   svn_stringbuf_t *space_separated_paths = svn_stringbuf_create_empty(pool);
 
-  for (i = 0; i < paths->nelts; i++)
+  for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
     {
-      const char *path = APR_ARRAY_IDX(paths, i, const char *);
+      const char *path = svn__apr_hash_index_key(hi);
       svn_pool_clear(iterpool);
-      if (i != 0)
+      if (space_separated_paths->len)
         svn_stringbuf_appendcstr(space_separated_paths, " ");
       svn_stringbuf_appendcstr(space_separated_paths,
                                svn_path_uri_encode(path, iterpool));
@@ -355,18 +356,18 @@ const char *
 svn_log__lock_one_path(const char *path, svn_boolean_t steal,
                        apr_pool_t *pool)
 {
-    apr_array_header_t *paths = apr_array_make(pool, 1, sizeof(path));
-    APR_ARRAY_PUSH(paths, const char *) = path;
-    return svn_log__lock(paths, steal, pool);
+  apr_hash_t *paths = apr_hash_make(pool);
+  svn_hash_sets(paths, path, path);
+  return svn_log__lock(paths, steal, pool);
 }
 
 const char *
 svn_log__unlock_one_path(const char *path, svn_boolean_t break_lock,
                          apr_pool_t *pool)
 {
-    apr_array_header_t *paths = apr_array_make(pool, 1, sizeof(path));
-    APR_ARRAY_PUSH(paths, const char *) = path;
-    return svn_log__unlock(paths, break_lock, pool);
+  apr_hash_t *paths = apr_hash_make(pool);
+  svn_hash_sets(paths, path, path);
+  return svn_log__unlock(paths, break_lock, pool);
 }
 
 const char *

Modified: subversion/trunk/subversion/mod_dav_svn/version.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/mod_dav_svn/version.c?rev=1577280&r1=1577279&r2=1577280&view=diff
==============================================================================
--- subversion/trunk/subversion/mod_dav_svn/version.c (original)
+++ subversion/trunk/subversion/mod_dav_svn/version.c Thu Mar 13 18:47:17 2014
@@ -1364,27 +1364,33 @@ release_locks(apr_hash_t *locks,
               apr_pool_t *pool)
 {
   apr_hash_index_t *hi;
-  const void *key;
-  void *val;
   apr_pool_t *subpool = svn_pool_create(pool);
+  apr_hash_t *targets = apr_hash_make(subpool);
+  apr_hash_t *results;
   svn_error_t *err;
 
-  for (hi = apr_hash_first(pool, locks); hi; hi = apr_hash_next(hi))
+  for (hi = apr_hash_first(subpool, locks); hi; hi = apr_hash_next(hi))
     {
-      svn_pool_clear(subpool);
-      apr_hash_this(hi, &key, NULL, &val);
+      const char *path = svn__apr_hash_index_key(hi);
+      const char *token = svn__apr_hash_index_val(hi);
 
-      /* The lock may be stolen or broken sometime between
-         svn_fs_commit_txn() and this post-commit cleanup.  So ignore
-         any errors from this command; just free as many locks as we can. */
-      err = svn_repos_fs_unlock(repos, key, val, FALSE, subpool);
+      svn_hash_sets(targets, path, token);
+    }
 
-      if (err) /* If we got an error, just log it and move along. */
-          ap_log_rerror(APLOG_MARK, APLOG_ERR, err->apr_err, r,
-                        "%s", err->message);
+  err = svn_repos_fs_unlock2(&results, repos, targets, FALSE, subpool, subpool);
 
-      svn_error_clear(err);
+  for (hi = apr_hash_first(subpool, results); hi; hi = apr_hash_next(hi))
+    {
+      svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+      if (result->err)
+        ap_log_rerror(APLOG_MARK, APLOG_ERR, result->err->apr_err, r,
+                      "%s", result->err->message);
+      svn_error_clear(result->err);
     }
+  if (err) /* If we got an error, just log it and move along. */
+    ap_log_rerror(APLOG_MARK, APLOG_ERR, err->apr_err, r,
+                  "%s", err->message);
+  svn_error_clear(err);
 
   svn_pool_destroy(subpool);
 

Modified: subversion/trunk/subversion/svnserve/serve.c
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/svnserve/serve.c?rev=1577280&r1=1577279&r2=1577280&view=diff
==============================================================================
--- subversion/trunk/subversion/svnserve/serve.c (original)
+++ subversion/trunk/subversion/svnserve/serve.c Thu Mar 13 18:47:17 2014
@@ -1353,37 +1353,45 @@ static svn_error_t *unlock_paths(const a
                                  apr_pool_t *pool)
 {
   int i;
-  apr_pool_t *iterpool;
-
-  iterpool = svn_pool_create(pool);
+  apr_pool_t *subpool = svn_pool_create(pool);
+  apr_hash_t *targets = apr_hash_make(subpool);
+  apr_hash_t *results;
+  apr_hash_index_t *hi;
+  svn_error_t *err;
 
   for (i = 0; i < lock_tokens->nelts; ++i)
     {
       svn_ra_svn_item_t *item, *path_item, *token_item;
       const char *path, *token, *full_path;
-      svn_error_t *err;
-      svn_pool_clear(iterpool);
 
       item = &APR_ARRAY_IDX(lock_tokens, i, svn_ra_svn_item_t);
       path_item = &APR_ARRAY_IDX(item->u.list, 0, svn_ra_svn_item_t);
       token_item = &APR_ARRAY_IDX(item->u.list, 1, svn_ra_svn_item_t);
 
       path = path_item->u.string->data;
+      full_path = svn_fspath__join(sb->repository->fs_path->data,
+                                   svn_relpath_canonicalize(path, subpool),
+                                   subpool);
       token = token_item->u.string->data;
+      svn_hash_sets(targets, full_path, token);
+    }
 
-      full_path = svn_fspath__join(sb->repository->fs_path->data,
-                                   svn_relpath_canonicalize(path, iterpool),
-                                   iterpool);
 
-      /* The lock may have become defunct after the commit, so ignore such
-         errors. */
-      err = svn_repos_fs_unlock(sb->repository->repos, full_path, token,
-                                FALSE, iterpool);
-      log_error(err, sb);
-      svn_error_clear(err);
+  /* The lock may have become defunct after the commit, so ignore such
+     errors. */
+  err = svn_repos_fs_unlock2(&results, sb->repository->repos, targets,
+                             FALSE, subpool, subpool);
+  for (hi = apr_hash_first(subpool, results); hi; hi = apr_hash_next(hi))
+    {
+      svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+
+      log_error(result->err, sb);
+      svn_error_clear(result->err);
     }
+  log_error(err, sb);
+  svn_error_clear(err);
 
-  svn_pool_destroy(iterpool);
+  svn_pool_destroy(subpool);
 
   return SVN_NO_ERROR;
 }
@@ -2693,12 +2701,11 @@ static svn_error_t *lock_many(svn_ra_svn
   svn_boolean_t steal_lock;
   int i;
   apr_pool_t *subpool;
-  const char *path;
-  const char *full_path;
-  svn_revnum_t current_rev;
-  apr_array_header_t *log_paths;
-  svn_lock_t *l;
-  svn_error_t *err = SVN_NO_ERROR, *write_err;
+  svn_error_t *err, *write_err = SVN_NO_ERROR;
+  apr_hash_t *targets = apr_hash_make(pool);
+  apr_hash_t *authz_results = apr_hash_make(pool);
+  apr_hash_t *results;
+  apr_hash_index_t *hi;
 
   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "(?c)bl", &comment, &steal_lock,
                                   &path_revs));
@@ -2712,11 +2719,14 @@ static svn_error_t *lock_many(svn_ra_svn
   SVN_ERR(must_have_access(conn, pool, b, svn_authz_write, NULL, TRUE));
 
   /* Loop through the lock requests. */
-  log_paths = apr_array_make(pool, path_revs->nelts, sizeof(full_path));
   for (i = 0; i < path_revs->nelts; ++i)
     {
+      const char *path, *full_path;
+      svn_revnum_t current_rev;
       svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
                                                svn_ra_svn_item_t);
+      svn_fs_lock_target_t *target
+        = apr_palloc(pool, sizeof(svn_fs_lock_target_t));
 
       svn_pool_clear(subpool);
 
@@ -2724,56 +2734,106 @@ static svn_error_t *lock_many(svn_ra_svn
         return svn_error_create(SVN_ERR_RA_SVN_MALFORMED_DATA, NULL,
                                 "Lock requests should be list of lists");
 
-      SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, pool, "c(?r)", &path,
+      SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?r)", &path,
                                       &current_rev));
 
-      /* Allocate the full_path out of pool so it will survive for use
-       * by operational logging, after this loop. */
       full_path = svn_fspath__join(b->repository->fs_path->data,
                                    svn_relpath_canonicalize(path, subpool),
                                    pool);
-      APR_ARRAY_PUSH(log_paths, const char *) = full_path;
+      target->token = NULL;
+      target->current_rev = current_rev;
 
-      if (! lookup_access(pool, b, svn_authz_write, full_path, TRUE))
-        {
-          err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED, NULL, NULL,
-                                     b);
-          break;
-        }
+      /* We could check for duplicate paths and reject the request? */
+      svn_hash_sets(targets, full_path, target);
+    }
+
+  SVN_ERR(log_command(b, conn, subpool, "%s",
+                      svn_log__lock(targets, steal_lock, subpool)));
 
-      err = svn_repos_fs_lock(&l, b->repository->repos, full_path,
-                              NULL, comment, FALSE,
-                              0, /* No expiration time. */
-                              current_rev,
-                              steal_lock, subpool);
+  /* From here on we need to make sure any errors in authz_results, or
+     results, are cleared before returning from this function. */
+  for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
+    {
+      const char *full_path = svn__apr_hash_index_key(hi);
 
-      if (err)
+      svn_pool_clear(subpool);
+
+      if (! lookup_access(subpool, b, svn_authz_write, full_path, TRUE))
         {
-          if (SVN_ERR_IS_LOCK_ERROR(err))
-            {
-              write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
-              svn_error_clear(err);
-              err = NULL;
-              SVN_ERR(write_err);
-            }
-          else
-            break;
+          svn_fs_lock_result_t *result
+            = apr_palloc(pool, sizeof(svn_fs_lock_result_t));
+
+          result->lock = NULL;
+          result->err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
+                                             NULL, NULL, b);
+          svn_hash_sets(authz_results, full_path, result);
+          svn_hash_sets(targets, full_path, NULL);
         }
+    }
+
+  err = svn_repos_fs_lock2(&results, b->repository->repos, targets,
+                           comment, FALSE,
+                           0, /* No expiration time. */
+                           steal_lock, pool, subpool);
+
+  /* The client expects results in the same order as paths were supplied. */
+  for (i = 0; i < path_revs->nelts; ++i)
+    {
+      const char *path, *full_path;
+      svn_revnum_t current_rev;
+      svn_ra_svn_item_t *item = &APR_ARRAY_IDX(path_revs, i,
+                                               svn_ra_svn_item_t);
+      svn_fs_lock_result_t *result;
+
+      svn_pool_clear(subpool);
+
+      write_err = svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?r)", &path,
+                                          &current_rev);
+      if (write_err)
+        break;
+      
+      full_path = svn_fspath__join(b->repository->fs_path->data,
+                                   svn_relpath_canonicalize(path, subpool),
+                                   subpool);
+
+      result = svn_hash_gets(results, full_path);
+      if (!result)
+        result = svn_hash_gets(authz_results, full_path);
+      if (!result)
+        /* No result?  Should we return some sort of placeholder error? */
+        break;
+
+      if (result->err)
+        write_err = svn_ra_svn__write_cmd_failure(conn, subpool,
+                                                  result->err);
       else
         {
-          SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w!", "success"));
-          SVN_ERR(write_lock(conn, subpool, l));
-          SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "!"));
+          write_err = svn_ra_svn__write_tuple(conn, subpool,
+                                              "w!", "success");
+          if (!write_err)
+            write_err = write_lock(conn, subpool, result->lock);
+          if (!write_err)
+            write_err = svn_ra_svn__write_tuple(conn, subpool, "!");
         }
+      if (write_err)
+        break;
     }
 
-  svn_pool_destroy(subpool);
+  for (hi = apr_hash_first(subpool, authz_results); hi; hi = apr_hash_next(hi))
+    {
+      svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+      svn_error_clear(result->err);
+    }
+  for (hi = apr_hash_first(subpool, results); hi; hi = apr_hash_next(hi))
+    {
+      svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+      svn_error_clear(result->err);
+    }
 
-  SVN_ERR(log_command(b, conn, pool, "%s",
-                      svn_log__lock(log_paths, steal_lock, pool)));
+  svn_pool_destroy(subpool);
 
-  /* NOTE: err might contain a fatal locking error from the loop above. */
-  write_err = svn_ra_svn__write_word(conn, pool, "done");
+  if (!write_err)
+    write_err = svn_ra_svn__write_word(conn, pool, "done");
   if (!write_err)
     SVN_CMD_ERR(err);
   svn_error_clear(err);
@@ -2818,11 +2878,11 @@ static svn_error_t *unlock_many(svn_ra_s
   apr_array_header_t *unlock_tokens;
   int i;
   apr_pool_t *subpool;
-  const char *path;
-  const char *full_path;
-  apr_array_header_t *log_paths;
-  const char *token;
   svn_error_t *err = SVN_NO_ERROR, *write_err;
+  apr_hash_t *targets = apr_hash_make(pool);
+  apr_hash_t *authz_results = apr_hash_make(pool);
+  apr_hash_t *results;
+  apr_hash_index_t *hi;
 
   SVN_ERR(svn_ra_svn__parse_tuple(params, pool, "bl", &break_lock,
                                   &unlock_tokens));
@@ -2833,11 +2893,11 @@ static svn_error_t *unlock_many(svn_ra_s
   subpool = svn_pool_create(pool);
 
   /* Loop through the unlock requests. */
-  log_paths = apr_array_make(pool, unlock_tokens->nelts, sizeof(full_path));
   for (i = 0; i < unlock_tokens->nelts; i++)
     {
       svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
                                                svn_ra_svn_item_t);
+      const char *path, *full_path, *token;
 
       svn_pool_clear(subpool);
 
@@ -2847,50 +2907,97 @@ static svn_error_t *unlock_many(svn_ra_s
 
       SVN_ERR(svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?c)", &path,
                                       &token));
+      if (!token)
+        token = "";
 
-      /* Allocate the full_path out of pool so it will survive for use
-       * by operational logging, after this loop. */
       full_path = svn_fspath__join(b->repository->fs_path->data,
                                    svn_relpath_canonicalize(path, subpool),
                                    pool);
-      APR_ARRAY_PUSH(log_paths, const char *) = full_path;
+      svn_hash_sets(targets, full_path, token);
+    }
+
+  SVN_ERR(log_command(b, conn, subpool, "%s",
+                      svn_log__unlock(targets, break_lock, subpool)));
+
+  /* From here on we need to make sure any errors in authz_results, or
+     results, are cleared before returning from this function. */
+  for (hi = apr_hash_first(pool, targets); hi; hi = apr_hash_next(hi))
+    {
+      const char *full_path = svn__apr_hash_index_key(hi);
+
+      svn_pool_clear(subpool);
 
       if (! lookup_access(subpool, b, svn_authz_write, full_path,
                           ! break_lock))
-        return svn_error_create(SVN_ERR_RA_SVN_CMD_ERR,
-                                error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
-                                                     NULL, NULL, b),
-                                NULL);
-
-      err = svn_repos_fs_unlock(b->repository->repos, full_path, token,
-                                break_lock, subpool);
-      if (err)
         {
-          if (SVN_ERR_IS_UNLOCK_ERROR(err))
-            {
-              write_err = svn_ra_svn__write_cmd_failure(conn, pool, err);
-              svn_error_clear(err);
-              err = NULL;
-              SVN_ERR(write_err);
-            }
-          else
-            break;
+          svn_fs_lock_result_t *result
+            = apr_palloc(pool, sizeof(svn_fs_lock_result_t));
+
+          result->lock = NULL;
+          result->err = error_create_and_log(SVN_ERR_RA_NOT_AUTHORIZED,
+                                             NULL, NULL, b);
+          svn_hash_sets(authz_results, full_path, result);
+          svn_hash_sets(targets, full_path, NULL);
         }
+    }
+
+  err = svn_repos_fs_unlock2(&results, b->repository->repos, targets,
+                             break_lock, pool, subpool);
+
+  /* Return results in the same order as the paths were supplied. */
+  for (i = 0; i < unlock_tokens->nelts; ++i)
+    {
+      const char *path, *token, *full_path;
+      svn_ra_svn_item_t *item = &APR_ARRAY_IDX(unlock_tokens, i,
+                                               svn_ra_svn_item_t);
+      svn_fs_lock_result_t *result;
+
+      svn_pool_clear(subpool);
+
+      write_err = svn_ra_svn__parse_tuple(item->u.list, subpool, "c(?c)", &path,
+                                          &token);
+      if (write_err)
+        break;
+
+      full_path = svn_fspath__join(b->repository->fs_path->data,
+                                   svn_relpath_canonicalize(path, subpool),
+                                   pool);
+
+      result = svn_hash_gets(results, full_path);
+      if (!result)
+        result = svn_hash_gets(authz_results, full_path);
+      if (!result)
+        /* No result?  Should we return some sort of placeholder error? */
+        break;
+
+      if (result->err)
+        write_err = svn_ra_svn__write_cmd_failure(conn, pool, result->err);
       else
-        SVN_ERR(svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success",
-                                        path));
+        write_err = svn_ra_svn__write_tuple(conn, subpool, "w(c)", "success",
+                                            path);
+      if (write_err)
+        break;
     }
 
-  svn_pool_destroy(subpool);
+  for (hi = apr_hash_first(subpool, authz_results); hi; hi = apr_hash_next(hi))
+    {
+      svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+      svn_error_clear(result->err);
+    }
+  for (hi = apr_hash_first(subpool, results); hi; hi = apr_hash_next(hi))
+    {
+      svn_fs_lock_result_t *result = svn__apr_hash_index_val(hi);
+      svn_error_clear(result->err);
+    }
 
-  SVN_ERR(log_command(b, conn, pool, "%s",
-                      svn_log__unlock(log_paths, break_lock, pool)));
+  svn_pool_destroy(subpool);
 
-  /* NOTE: err might contain a fatal unlocking error from the loop above. */
-  write_err = svn_ra_svn__write_word(conn, pool, "done");
+  if (!write_err)
+    write_err = svn_ra_svn__write_word(conn, pool, "done");
   if (! write_err)
     SVN_CMD_ERR(err);
   svn_error_clear(err);
+  SVN_ERR(write_err);
   SVN_ERR(svn_ra_svn__write_cmd_response(conn, pool, ""));
 
   return SVN_NO_ERROR;

Modified: subversion/trunk/subversion/tests/cmdline/authz_tests.py
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/authz_tests.py?rev=1577280&r1=1577279&r2=1577280&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/authz_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/authz_tests.py Thu Mar 13 18:47:17 2014
@@ -752,8 +752,10 @@ def authz_locking(sbox):
 
   if sbox.repo_url.startswith('http'):
     expected_err = ".*svn: E175013: .*[Ff]orbidden.*"
+    expected_status = 1
   else:
-    expected_err = ".*svn: E170001: Authorization failed.*"
+    expected_err = ".*svn: warning: W170001: Authorization failed.*"
+    expected_status = 0
 
   root_url = sbox.repo_url
   wc_dir = sbox.wc_dir
@@ -763,18 +765,18 @@ def authz_locking(sbox):
   mu_path = os.path.join(wc_dir, 'A', 'mu')
 
   # lock a file url, target is readonly: should fail
-  svntest.actions.run_and_verify_svn(None,
-                                     None, expected_err,
-                                     'lock',
-                                     '-m', 'lock msg',
-                                     iota_url)
+  svntest.actions.run_and_verify_svn2(None,
+                                      None, expected_err, expected_status,
+                                      'lock',
+                                      '-m', 'lock msg',
+                                      iota_url)
 
   # lock a file path, target is readonly: should fail
-  svntest.actions.run_and_verify_svn(None,
-                                     None, expected_err,
-                                     'lock',
-                                     '-m', 'lock msg',
-                                     iota_path)
+  svntest.actions.run_and_verify_svn2(None,
+                                      None, expected_err, expected_status,
+                                      'lock',
+                                      '-m', 'lock msg',
+                                      iota_path)
 
   # Test for issue 2700: we have write access in folder /A, but not in root.
   # Get a lock on /A/mu and try to commit it.

Modified: subversion/trunk/subversion/tests/cmdline/lock_tests.py
URL: http://svn.apache.org/viewvc/subversion/trunk/subversion/tests/cmdline/lock_tests.py?rev=1577280&r1=1577279&r2=1577280&view=diff
==============================================================================
--- subversion/trunk/subversion/tests/cmdline/lock_tests.py (original)
+++ subversion/trunk/subversion/tests/cmdline/lock_tests.py Thu Mar 13 18:47:17 2014
@@ -1683,7 +1683,7 @@ def block_unlock_if_pre_unlock_hook_fail
   # Make sure the unlock operation fails as pre-unlock hook blocks it.
   expected_unlock_fail_err_re = ".*error text"
   svntest.actions.run_and_verify_svn2(None, None, expected_unlock_fail_err_re,
-                                      1, 'unlock', pi_path)
+                                      0, 'unlock', pi_path)
   svntest.actions.run_and_verify_status(wc_dir, expected_status)
 
 #----------------------------------------------------------------------
@@ -1932,32 +1932,62 @@ def lock_hook_messages(sbox):
   svntest.actions.create_failing_hook(repo_dir, "pre-lock", error_msg)
   svntest.actions.create_failing_hook(repo_dir, "pre-unlock", error_msg)
 
-  _, _, actual_stderr = svntest.actions.run_and_verify_svn(
-                                     None, [], svntest.verify.AnyOutput,
+  _, _, actual_stderr = svntest.actions.run_and_verify_svn2(
+                                     None, [], svntest.verify.AnyOutput, 0,
                                      'lock', mu_url)
   if len(actual_stderr) > 2:
     actual_stderr = actual_stderr[-2:]
   expected_err = [
-    'svn: E165001: ' + svntest.actions.hook_failure_message('pre-lock'),
+    'svn: warning: W165001: ' + svntest.actions.hook_failure_message('pre-lock'),
     error_msg + "\n",
   ]
   svntest.verify.compare_and_display_lines(None, 'STDERR',
                                            expected_err, actual_stderr)
 
 
-  _, _, actual_stderr = svntest.actions.run_and_verify_svn(
-                                     None, [], svntest.verify.AnyOutput,
+  _, _, actual_stderr = svntest.actions.run_and_verify_svn2(
+                                     None, [], svntest.verify.AnyOutput, 0,
                                      'unlock', iota_url)
   if len(actual_stderr) > 2:
     actual_stderr = actual_stderr[-2:]
   expected_err = [
-    'svn: E165001: ' + svntest.actions.hook_failure_message('pre-unlock'),
+    'svn: warning: W165001: ' + svntest.actions.hook_failure_message('pre-unlock'),
     error_msg + "\n",
   ]
   svntest.verify.compare_and_display_lines(None, 'STDERR',
                                            expected_err, actual_stderr)
 
 
+@XFail(svntest.main.is_ra_type_dav)
+def failing_post_hooks(sbox):
+  "locking with failing post-lock and post-unlock"
+
+  sbox.build()
+  wc_dir = sbox.wc_dir
+  repo_dir = sbox.repo_dir
+
+  svntest.actions.create_failing_hook(repo_dir, "post-lock", "error text")
+  svntest.actions.create_failing_hook(repo_dir, "post-unlock", "error text")
+
+  pi_path = sbox.ospath('A/D/G/pi')
+  expected_status = svntest.actions.get_virginal_state(wc_dir, 1)
+  expected_status.tweak('A/D/G/pi', writelocked='K')
+  expected_fail_err_re = ".*error text"
+  
+  # Failing post-lock doesn't stop lock being created.
+  svntest.actions.run_and_verify_svn2(None, "'pi' locked by user",
+                                      expected_fail_err_re, 1,
+                                      'lock', '-m', '', pi_path)
+  svntest.actions.run_and_verify_status(wc_dir, expected_status)
+
+  expected_status.tweak('A/D/G/pi', writelocked=None)
+
+  # Failing post-unlock doesn't stop lock being removed.
+  svntest.actions.run_and_verify_svn2(None, "'pi' unlocked",
+                                      expected_fail_err_re, 1,
+                                      'unlock', pi_path)
+  svntest.actions.run_and_verify_status(wc_dir, expected_status)
+
 ########################################################################
 # Run the tests
 
@@ -2013,6 +2043,7 @@ test_list = [ None,
               drop_locks_on_parent_deletion,
               copy_with_lock,
               lock_hook_messages,
+              failing_post_hooks,
             ]
 
 if __name__ == '__main__':