Finish issue #2991: client should announce capabilities to server, and
server should pass them along to start-commit hook.  This would allow
a server to reject commits from clients that don't don't know how to
keep mergeinfo up to date, for example.

* subversion/libsvn_repos/repos.h
  (struct svn_repos_t): Add new capabilities field.
  (svn_repos__hooks_start_commit): Take new capabilities parameter.

* subversion/svnserve/serve.c: Include svn_repos_private.h.
  (find_repos): Take new capabilities parameter, use it to set a
    capabilities list in the new repos object.
  (serve): Pass the capabilities to find_repos.

* subversion/libsvn_repos/fs-wrap.c
  (svn_repos_fs_begin_txn_for_commit2): Pass capabilities along to 
    svn_repos__hooks_start_commit.

* subversion/libsvn_repos/hooks.c
  (svn_repos__hooks_start_commit): Take new capabilities parameter,
    convert the list to a string, and pass along to the hook program. 

* subversion/include/private/svn_repos_private.h: New file.
  (svn_repos__capabilities_as_list): New declaration.
  (svn_repos__set_capabilities): New declaration.

* subversion/libsvn_repos/repos.c: Include svn_ra.h, svn_repos_private.h.
  (svn_repos__capabilities_as_list): Define new function.
  (svn_repos__set_capabilities): Define new function.
  (create_hooks): Document the new capabilities list passed to the
    start-commit hook.

* subversion/mod_dav_svn/repos.c: Include svn_repos_private.h.
  (get_resource): Handle new SVN_RA_CAPABILITY_LOG_REVPROPS; store
    all capabilities in repos.

* subversion/libsvn_ra_local/split_url.c: Include svn_repos_private.h.
  (svn_ra_local__split_URL): Set capabilities in new repos object.

* subversion/libsvn_ra_serf/util.c
  (svn_ra_serf__setup_serf_req): Send new SVN_RA_CAPABILITY_LOG_REVPROPS.

* subversion/include/svn_dav.h
  (SVN_DAV_PROP_NS_DAV_SVN_LOG_REVPROPS): Move definition, fix formatting.

* subversion/libsvn_ra_serf/propfind_buckets.c
  (become_request): Send SVN_DAV_PROP_NS_DAV_SVN_LOG_REVPROPS.

* subversion/libsvn_ra_neon/util.c
  (svn_ra_neon__request_dispatch): Send SVN_DAV_PROP_NS_DAV_SVN_LOG_REVPROPS.

* subversion/libsvn_ra_svn/client.c
  (open_session): Count args correctly; send SVN_RA_SVN_CAP_LOG_REVPROPS.
  (ra_svn_has_capability): Rewrite for compactness.

* subversion/include/svn_ra.h
  (svn_ra_has_capability): Document error behavior.

* subversion/libsvn_ra_serf/serf.c
  (capabilities_headers_iterator_callback): Formatting fix for
    consistency with the rest of the capabilities code.

* subversion/include/svn_ra_svn.h
  (SVN_RA_SVN_CAP_LOG_REVPROPS): Fix comment formatting, for consistency.

* subversion/tests/cmdline/commit_tests.py
  (start_commit_detect_capabilities): New test.
  (test_list): Run it.


git-svn-id: https://svn.apache.org/repos/asf/subversion/branches/issue-2991-dev@867687 13f79535-47bb-0310-9956-ffa450edef68
diff --git a/subversion/include/private/svn_repos_private.h b/subversion/include/private/svn_repos_private.h
new file mode 100644
index 0000000..342428a
--- /dev/null
+++ b/subversion/include/private/svn_repos_private.h
@@ -0,0 +1,46 @@
+/*
+ * svn_repos_private.h: Private declarations for repos functionality
+ * used internally by non-libsvn_repos* modules.
+ *
+ * ====================================================================
+ * Copyright (c) 2007 CollabNet.  All rights reserved.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution.  The terms
+ * are also available at http://subversion.tigris.org/license-1.html.
+ * If newer versions of this license are posted there, you may use a
+ * newer version instead, at your option.
+ *
+ * This software consists of voluntary contributions made by many
+ * individuals.  For exact contribution history, see the revision
+ * history and logs, available at http://subversion.tigris.org/.
+ * ====================================================================
+ */
+
+#ifndef SVN_REPOS_PRIVATE_H
+#define SVN_REPOS_PRIVATE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+/* Convert @a capabilities, a hash table mapping 'const char *' keys to
+ * "yes" or "no" values, to a list of all keys whose value is "yes".
+ * Return the list, allocated in @a pool, and use @a pool for all
+ * temporary allocation.
+ */
+apr_array_header_t *
+svn_repos__capabilities_as_list(apr_hash_t *capabilities, apr_pool_t *pool);
+
+/* Set the client-reported capabilities of @a repos to @a capabilities,
+ * which must be allocated in memory at least as long-lived as @a repos.
+ */
+void
+svn_repos__set_capabilities(svn_repos_t *repos,
+                            apr_array_header_t *capabilities);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_REPOS_PRIVATE_H */
diff --git a/subversion/include/svn_dav.h b/subversion/include/svn_dav.h
index 259b5ed..a1c60c4 100644
--- a/subversion/include/svn_dav.h
+++ b/subversion/include/svn_dav.h
@@ -156,13 +156,15 @@
    ### but we should re-use the SVN_DAV_PROP_NS_DAV, right?  We could
    ### change the name of SVN_DAV_PROP_NS_DAV_SVN_DEPTH, though. */
 #define SVN_DAV_PROP_NS_DAV_SVN_DEPTH SVN_DAV_PROP_NS_DAV "svn/depth"
-#define SVN_DAV_PROP_NS_DAV_SVN_LOG_REVPROPS SVN_DAV_PROP_NS_DAV "svn/log-revprops"
 
 /* Presence of this in the DAV header response to an OPTIONS request
    indicates that the server knows how to handle merge-tracking information.
    ### And see above about appropriateness of properties namespace. ### */
 #define SVN_DAV_PROP_NS_DAV_SVN_MERGEINFO SVN_DAV_PROP_NS_DAV "svn/merginfo"
 
+#define SVN_DAV_PROP_NS_DAV_SVN_LOG_REVPROPS SVN_DAV_PROP_NS_DAV \
+        "svn/log-revprops"
+
 /** @} */
 
 #ifdef __cplusplus
diff --git a/subversion/include/svn_ra.h b/subversion/include/svn_ra.h
index add3ab0..62346b7 100644
--- a/subversion/include/svn_ra.h
+++ b/subversion/include/svn_ra.h
@@ -1515,9 +1515,12 @@
 
 /**
  * Set @a *has to true if the server represented by @a session has
- * capability @a capability (one of the capabilities beginning with
+ * @a capability (one of the capabilities beginning with
  * @c "SVN_RA_CAPABILITY_"), else set @a *has to false.
  *
+ * If @a capability isn't recognized, throw @c SVN_ERR_RA_UNKNOWN_CAPABILITY,
+ * with the effect on @a *has undefined.
+ *
  * Use @a pool for all allocation.
  *
  * @since New in 1.5.
diff --git a/subversion/include/svn_ra_svn.h b/subversion/include/svn_ra_svn.h
index f5dc448..99bc7d0 100644
--- a/subversion/include/svn_ra_svn.h
+++ b/subversion/include/svn_ra_svn.h
@@ -48,7 +48,8 @@
 #define SVN_RA_SVN_CAP_MERGEINFO "mergeinfo"
 /* maps to SVN_RA_CAPABILITY_DEPTH: */
 #define SVN_RA_SVN_CAP_DEPTH "depth"
-#define SVN_RA_SVN_CAP_LOG_REVPROPS "log-revprops" /* SVN_RA_CAPABILITY_LOG_REVPROPS */
+/* maps to SVN_RA_CAPABILITY_LOG_REVPROPS */
+#define SVN_RA_SVN_CAP_LOG_REVPROPS "log-revprops"
 
 /** ra_svn passes @c svn_dirent_t fields over the wire as a list of
  * words, these are the values used to represent each field.
diff --git a/subversion/libsvn_ra_local/split_url.c b/subversion/libsvn_ra_local/split_url.c
index 066110b..f2df341 100644
--- a/subversion/libsvn_ra_local/split_url.c
+++ b/subversion/libsvn_ra_local/split_url.c
@@ -21,6 +21,7 @@
 #include <string.h>
 #include "svn_path.h"
 #include "svn_private_config.h"
+#include "private/svn_repos_private.h"
 
 
 svn_error_t *
@@ -133,6 +134,15 @@
       (SVN_ERR_RA_LOCAL_REPOS_OPEN_FAILED, err,
        _("Unable to open repository '%s'"), URL);
 
+  /* Assert capabilities directly, since client == server. */
+  {
+    apr_array_header_t *caps = apr_array_make(pool, 3, sizeof(const char *));
+    APR_ARRAY_PUSH(caps, const char *) = SVN_RA_CAPABILITY_DEPTH;
+    APR_ARRAY_PUSH(caps, const char *) = SVN_RA_CAPABILITY_MERGEINFO;
+    APR_ARRAY_PUSH(caps, const char *) = SVN_RA_CAPABILITY_LOG_REVPROPS;
+    svn_repos__set_capabilities(*repos, caps);
+  }
+
   /* What remains of URL after being hacked at in the previous step is
      REPOS_URL.  FS_PATH is what we've hacked off in the process.
      Note that path is not encoded and what we gave to svn_root_find_root_path
diff --git a/subversion/libsvn_ra_neon/util.c b/subversion/libsvn_ra_neon/util.c
index 6f93cb3..7ed2ead 100644
--- a/subversion/libsvn_ra_neon/util.c
+++ b/subversion/libsvn_ra_neon/util.c
@@ -1425,6 +1425,8 @@
                         SVN_DAV_PROP_NS_DAV_SVN_DEPTH);
   ne_add_request_header(req->ne_req, "X-SVN-Capabilities",
                         SVN_DAV_PROP_NS_DAV_SVN_MERGEINFO);
+  ne_add_request_header(req->ne_req, "X-SVN-Capabilities",
+                        SVN_DAV_PROP_NS_DAV_SVN_LOG_REVPROPS);
 
   /* add any extra headers passed in by caller. */
   if (extra_headers != NULL)
diff --git a/subversion/libsvn_ra_serf/propfind_buckets.c b/subversion/libsvn_ra_serf/propfind_buckets.c
index 3eb4607..0d7c0a6 100644
--- a/subversion/libsvn_ra_serf/propfind_buckets.c
+++ b/subversion/libsvn_ra_serf/propfind_buckets.c
@@ -159,6 +159,8 @@
                            SVN_DAV_PROP_NS_DAV_SVN_DEPTH);
   serf_bucket_headers_setn(hdrs_bkt, "X-SVN-Capabilities",
                            SVN_DAV_PROP_NS_DAV_SVN_MERGEINFO);
+  serf_bucket_headers_setn(hdrs_bkt, "X-SVN-Capabilities",
+                           SVN_DAV_PROP_NS_DAV_SVN_LOG_REVPROPS);
 
   if (ctx->label)
     {
diff --git a/subversion/libsvn_ra_serf/serf.c b/subversion/libsvn_ra_serf/serf.c
index f92185b..f4b2c61 100644
--- a/subversion/libsvn_ra_serf/serf.c
+++ b/subversion/libsvn_ra_serf/serf.c
@@ -791,8 +791,10 @@
         }
       if (svn_cstring_match_glob_list(SVN_DAV_PROP_NS_DAV_SVN_LOG_REVPROPS,
                                       vals))
-        apr_hash_set(crb->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
-                     APR_HASH_KEY_STRING, capability_yes);
+        {
+          apr_hash_set(crb->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
+                       APR_HASH_KEY_STRING, capability_yes);
+        }
     }
 
   return 0;
diff --git a/subversion/libsvn_ra_serf/util.c b/subversion/libsvn_ra_serf/util.c
index 2a9c36a..5c82385 100644
--- a/subversion/libsvn_ra_serf/util.c
+++ b/subversion/libsvn_ra_serf/util.c
@@ -281,6 +281,8 @@
                            SVN_DAV_PROP_NS_DAV_SVN_DEPTH);
   serf_bucket_headers_setn(hdrs_bkt, "X-SVN-Capabilities",
                            SVN_DAV_PROP_NS_DAV_SVN_MERGEINFO);
+  serf_bucket_headers_setn(hdrs_bkt, "X-SVN-Capabilities",
+                           SVN_DAV_PROP_NS_DAV_SVN_LOG_REVPROPS);
 
   if (conn->session->auth_protocol)
     conn->session->auth_protocol->setup_request_func(conn, hdrs_bkt);
diff --git a/subversion/libsvn_ra_svn/client.c b/subversion/libsvn_ra_svn/client.c
index 74370c4..f6475df 100644
--- a/subversion/libsvn_ra_svn/client.c
+++ b/subversion/libsvn_ra_svn/client.c
@@ -568,12 +568,13 @@
   /* In protocol version 2, we send back our protocol version, our
    * capability list, and the URL, and subsequently there is an auth
    * request. */
-  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "n(www)c", (apr_uint64_t) 2,
+  SVN_ERR(svn_ra_svn_write_tuple(conn, pool, "n(wwwwww)c", (apr_uint64_t) 2,
                                  SVN_RA_SVN_CAP_EDIT_PIPELINE,
                                  SVN_RA_SVN_CAP_SVNDIFF1,
                                  SVN_RA_SVN_CAP_ABSENT_ENTRIES,
                                  SVN_RA_SVN_CAP_DEPTH,
                                  SVN_RA_SVN_CAP_MERGEINFO,
+                                 SVN_RA_SVN_CAP_LOG_REVPROPS,
                                  url));
   SVN_ERR(handle_auth_request(sess, pool));
 
@@ -2158,26 +2159,25 @@
 {
   svn_ra_svn__session_baton_t *sess = session->priv;
 
-  if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0)
+  *has = FALSE;
+
+  if (strcmp(capability, SVN_RA_CAPABILITY_DEPTH) == 0
+      && svn_ra_svn_has_capability(sess->conn,
+                                   SVN_RA_SVN_CAP_DEPTH))
     {
-      if (svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_DEPTH))
-        *has = TRUE;
-      else
-        *has = FALSE;
+      *has = TRUE;
     }
-  else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0)
+  else if (strcmp(capability, SVN_RA_CAPABILITY_MERGEINFO) == 0
+           && svn_ra_svn_has_capability(sess->conn,
+                                        SVN_RA_SVN_CAP_MERGEINFO))
     {
-      if (svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_MERGEINFO))
-        *has = TRUE;
-      else
-        *has = FALSE;
+      *has = TRUE;
     }
-  else if (strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0)
+  else if (strcmp(capability, SVN_RA_CAPABILITY_LOG_REVPROPS) == 0
+           && svn_ra_svn_has_capability(sess->conn,
+                                        SVN_RA_SVN_CAP_LOG_REVPROPS))
     {
-      if (svn_ra_svn_has_capability(sess->conn, SVN_RA_SVN_CAP_LOG_REVPROPS))
-        *has = TRUE;
-      else
-        *has = FALSE;
+      *has = TRUE;
     }
   else  /* Don't know any other capabilities, so error. */
     {
diff --git a/subversion/libsvn_repos/fs-wrap.c b/subversion/libsvn_repos/fs-wrap.c
index 769e6e8..25dadd1 100644
--- a/subversion/libsvn_repos/fs-wrap.c
+++ b/subversion/libsvn_repos/fs-wrap.c
@@ -77,7 +77,7 @@
 
   /* Run start-commit hooks. */
   SVN_ERR(svn_repos__hooks_start_commit(repos, author ? author->data : NULL,
-                                        pool));
+                                        repos->capabilities, pool));
 
   /* Begin the transaction, ask for the fs to do on-the-fly lock checks. */
   SVN_ERR(svn_fs_begin_txn2(txn_p, repos->fs, rev,
diff --git a/subversion/libsvn_repos/hooks.c b/subversion/libsvn_repos/hooks.c
index f0aafd3..2e5bea3 100644
--- a/subversion/libsvn_repos/hooks.c
+++ b/subversion/libsvn_repos/hooks.c
@@ -536,6 +536,7 @@
 svn_error_t *
 svn_repos__hooks_start_commit(svn_repos_t *repos,
                               const char *user,
+                              apr_array_header_t *capabilities,
                               apr_pool_t *pool)
 {
   const char *hook = svn_repos_start_commit_hook(repos, pool);
@@ -548,11 +549,15 @@
   else if (hook)
     {
       const char *args[4];
+      char *capabilities_string = svn_cstring_join(capabilities, ",", pool);
+      /* Get rid of that annoying final comma. */
+      capabilities_string[strlen(capabilities_string) - 1] = '\0';
 
       args[0] = hook;
       args[1] = svn_path_local_style(svn_repos_path(repos, pool), pool);
       args[2] = user ? user : "";
-      args[3] = NULL;
+      args[3] = capabilities_string;
+      args[4] = NULL;
 
       SVN_ERR(run_hook_cmd(SVN_REPOS__HOOK_START_COMMIT, hook, args, NULL,
                            pool));
diff --git a/subversion/libsvn_repos/repos.c b/subversion/libsvn_repos/repos.c
index a6abbad..0014263 100644
--- a/subversion/libsvn_repos/repos.c
+++ b/subversion/libsvn_repos/repos.c
@@ -26,8 +26,10 @@
 #include "svn_utf.h"
 #include "svn_time.h"
 #include "svn_fs.h"
+#include "svn_ra.h"  /* for SVN_RA_CAPABILITY_* */
 #include "svn_repos.h"
 #include "svn_private_config.h" /* for SVN_TEMPLATE_ROOT_DIR */
+#include "private/svn_repos_private.h"
 
 #include "repos.h"
 
@@ -305,9 +307,27 @@
 "#"                                                                          NL
 "#   [1] REPOS-PATH   (the path to this repository)"                         NL
 "#   [2] USER         (the authenticated user attempting to commit)"         NL
+"#   [3] CAPABILITIES (a comma-separated list of capabilities reported"      NL
+"#                     by the client; see note below)"                       NL
 "#"                                                                          NL
-"# The default working directory for the invocation is undefined, so"        NL
-"# the program should set one explicitly if it cares."                       NL
+"# Note: The CAPABILITIES parameter is new in Subversion 1.5, and 1.5"       NL
+"# clients will typically report at least the \""                            \
+   SVN_RA_CAPABILITY_MERGEINFO "\" capability."                              NL
+"# If there are other capabilities, then the list is comma-separated,"       NL
+"# e.g.: \""                                                                 \
+   SVN_RA_CAPABILITY_LOG_REVPROPS ","                                        \
+   SVN_RA_CAPABILITY_MERGEINFO ","                                           \
+   SVN_RA_CAPABILITY_DEPTH                                                   \
+"\" (the order is undefined)."                                               NL
+"#"                                                                          NL
+"# The list is self-reported by the client.  Therefore, you should not"      NL
+"# make security assumptions based on the capabilities list, nor should"     NL
+"# you assume that clients reliably report every capability they have."      NL
+"# However, stock Subversion 1.5 (and higher) clients will report"           NL
+"# at least the \"" SVN_RA_CAPABILITY_MERGEINFO "\" capability by default."  NL
+"#"                                                                          NL
+"# The working directory for this hook program's invocation is undefined,"   NL
+"# so the program should set one explicitly if it cares."                    NL
 "#"                                                                          NL
 "# If the hook program exits with success, the commit continues; but"        NL
 "# if it exits with failure (non-zero), the commit is stopped before"        NL
@@ -1680,3 +1700,30 @@
   *dirent = ent;
   return SVN_NO_ERROR;
 }
+
+apr_array_header_t *
+svn_repos__capabilities_as_list(apr_hash_t *capabilities, apr_pool_t *pool)
+{
+  apr_hash_index_t *hi;
+  apr_array_header_t *list = apr_array_make(pool, apr_hash_count(capabilities),
+                                            sizeof(char *));
+
+  for (hi = apr_hash_first(pool, capabilities); hi; hi = apr_hash_next(hi))
+    {
+      const void *key;
+      void *val;
+      apr_hash_this(hi, &key, NULL, &val);
+      if (strcmp((const char *) val, "yes") == 0)
+        APR_ARRAY_PUSH(list, const char *) = key;
+    }
+
+  return list;
+}
+
+void
+svn_repos__set_capabilities(svn_repos_t *repos,
+                            apr_array_header_t *capabilities)
+{
+  repos->capabilities = capabilities;
+}
+
diff --git a/subversion/libsvn_repos/repos.h b/subversion/libsvn_repos/repos.h
index a65fa0e..be62b00 100644
--- a/subversion/libsvn_repos/repos.h
+++ b/subversion/libsvn_repos/repos.h
@@ -117,6 +117,11 @@
 
   /* The FS backend in use within this repository. */
   const char *fs_type;
+
+  /* If non-null, a list of all the capabilities the client (on the
+     current connection) has self-reported.  Each element is a
+     'const char *', one of SVN_RA_CAPABILITY_*. */
+  apr_array_header_t *capabilities;
 };
 
 
@@ -125,10 +130,15 @@
 /* Run the start-commit hook for REPOS.  Use POOL for any temporary
    allocations.  If the hook fails, return SVN_ERR_REPOS_HOOK_FAILURE.
 
-   USER is the authenticated name of the user starting the commit.  */
+   USER is the authenticated name of the user starting the commit.  
+   CAPABILITIES is a list of 'const char *' capability names (using
+   SVN_RA_CAPABILITY_*) that the client has self-reported.  Note that
+   there is no guarantee the client is telling the truth: the hook
+   should not make security assumptions based on the capabilities. */
 svn_error_t *
 svn_repos__hooks_start_commit(svn_repos_t *repos,
                               const char *user,
+                              apr_array_header_t *capabilities,
                               apr_pool_t *pool);
 
 /* Run the pre-commit hook for REPOS.  Use POOL for any temporary
diff --git a/subversion/mod_dav_svn/repos.c b/subversion/mod_dav_svn/repos.c
index e2043ff..ad910c1 100644
--- a/subversion/mod_dav_svn/repos.c
+++ b/subversion/mod_dav_svn/repos.c
@@ -41,6 +41,7 @@
 #include "svn_props.h"
 #include "mod_dav_svn.h"
 #include "svn_ra.h"  /* for SVN_RA_CAPABILITY_* */
+#include "private/svn_repos_private.h"
 
 #include "dav_svn.h"
 
@@ -1629,6 +1630,8 @@
                      APR_HASH_KEY_STRING, capability_no);
         apr_hash_set(repos->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
                      APR_HASH_KEY_STRING, capability_no);
+        apr_hash_set(repos->capabilities, SVN_RA_CAPABILITY_LOG_REVPROPS,
+                     APR_HASH_KEY_STRING, capability_no);
 
         /* Then see what we can find. */
         val = apr_table_get(r->headers_in, "X-SVN-Capabilities");
@@ -1649,6 +1652,13 @@
                 apr_hash_set(repos->capabilities, SVN_RA_CAPABILITY_MERGEINFO,
                              APR_HASH_KEY_STRING, capability_yes);
               }
+            if (svn_cstring_match_glob_list
+                (SVN_DAV_PROP_NS_DAV_SVN_LOG_REVPROPS, vals))
+              {
+                apr_hash_set(repos->capabilities,
+                             SVN_RA_CAPABILITY_LOG_REVPROPS,
+                             APR_HASH_KEY_STRING, capability_yes);
+              }
           }
       }
   }
@@ -1675,6 +1685,12 @@
       /* Cache the open repos for the next request on this connection */
       apr_pool_userdata_set(repos->repos, repos_key,
                             NULL, r->connection->pool);
+
+      /* Store the capabilities of the current connection, making sure
+         to use the same pool repos->repos itself was created in. */
+      svn_repos__set_capabilities
+        (repos->repos, svn_repos__capabilities_as_list(repos->capabilities,
+                                                       r->connection->pool));
     }
 
   /* cache the filesystem object */
diff --git a/subversion/svnserve/serve.c b/subversion/svnserve/serve.c
index df2143e..36fcbcb 100644
--- a/subversion/svnserve/serve.c
+++ b/subversion/svnserve/serve.c
@@ -42,6 +42,7 @@
 #include "svn_props.h"
 #include "svn_mergeinfo.h"
 #include "svn_user.h"
+#include "private/svn_repos_private.h"
 
 #include "server.h"
 
@@ -2404,9 +2405,14 @@
 
 /* Look for the repository given by URL, using ROOT as the virtual
  * repository root.  If we find one, fill in the repos, fs, cfg,
- * repos_url, and fs_path fields of B. */
+ * repos_url, and fs_path fields of B, and set the capabilities of
+ * B->repos to CAPABILITIES (which must be at least as long-lived
+ * as POOL).
+ */
 static svn_error_t *find_repos(const char *url, const char *root,
-                               server_baton_t *b, apr_pool_t *pool)
+                               server_baton_t *b,
+                               apr_array_header_t *capabilities,
+                               apr_pool_t *pool)
 {
   const char *path, *full_path, *repos_root;
   svn_stringbuf_t *url_buf;
@@ -2442,6 +2448,7 @@
 
   /* Open the repository and fill in b with the resulting information. */
   SVN_ERR(svn_repos_open(&b->repos, repos_root, pool));
+  svn_repos__set_capabilities(b->repos, capabilities);
   b->fs = svn_repos_fs(b->repos);
   b->fs_path = svn_stringbuf_create(full_path + strlen(repos_root),
                                     pool);
@@ -2506,7 +2513,7 @@
   svn_error_t *err, *io_err;
   apr_uint64_t ver;
   const char *uuid, *client_url;
-  apr_array_header_t *caplist;
+  apr_array_header_t *caplist, *cap_words;
   server_baton_t b;
 
   b.tunnel = params->tunnel;
@@ -2547,7 +2554,26 @@
   if (! svn_ra_svn_has_capability(conn, SVN_RA_SVN_CAP_EDIT_PIPELINE))
     return SVN_NO_ERROR;
 
-  err = find_repos(client_url, params->root, &b, pool);
+  /* find_repos needs the capabilities as a list of words (eventually
+     they get handed to the start-commit hook).  While we could add a
+     new interface to re-retrieve them from conn and convert the
+     result to a list, it's simpler to just convert caplist by hand
+     here, since we already have it and turning 'svn_ra_svn_item_t's
+     into 'const char *'s is pretty easy. */
+  {
+    int i;
+    svn_ra_svn_item_t *item;
+
+    cap_words = apr_array_make(pool, caplist->nelts, sizeof(const char *));
+    for (i = 0; i < caplist->nelts; i++)
+      {
+        item = &APR_ARRAY_IDX(caplist, i, svn_ra_svn_item_t);
+        /* ra_svn_set_capabilities() already type-checked for us */
+        APR_ARRAY_PUSH(cap_words, const char *) = item->u.word;
+      }
+  }
+
+  err = find_repos(client_url, params->root, &b, cap_words, pool);
   if (!err)
     {
       SVN_ERR(auth_request(conn, pool, &b, READ_ACCESS, FALSE));
diff --git a/subversion/tests/cmdline/commit_tests.py b/subversion/tests/cmdline/commit_tests.py
index 3a3973c..4717d08 100755
--- a/subversion/tests/cmdline/commit_tests.py
+++ b/subversion/tests/cmdline/commit_tests.py
@@ -2502,6 +2502,42 @@
                                      'commit', '-m', 'log message',
                                      wc_backup)
 
+def start_commit_detect_capabilities(sbox):
+  "start-commit hook sees client capabilities"  # Issue #2991
+  sbox.build()
+  wc_dir = sbox.wc_dir
+  repos_dir = sbox.repo_dir
+
+  # Create a start-commit hook that detects the "mergeinfo" capability.
+  hook_text = "import sys\n"                                 + \
+              "fp = open(sys.argv[1] + '/hooks.log', 'w')\n" + \
+              "caps = sys.argv[3].split(',')\n"              + \
+              "if 'mergeinfo' in caps:\n"                    + \
+              "  fp.write('yes')\n"                          + \
+              "else:\n"                                      + \
+              "  fp.write('no')\n"                           + \
+              "fp.close()\n"
+
+  start_commit_hook = svntest.main.get_start_commit_hook_path(repos_dir)
+  svntest.main.create_python_hook_script(start_commit_hook, hook_text)
+
+  # Commit something.
+  iota_path = os.path.join(wc_dir, "iota")
+  svntest.main.file_append(iota_path, "More stuff in iota")
+  svntest.actions.run_and_verify_svn(None, [], [], 'ci', '--quiet',
+                                     '-m', 'log msg', wc_dir)
+
+  # Check that "mergeinfo" was detected.
+  log_path = os.path.join(repos_dir, "hooks.log")
+  if os.path.exists(log_path):
+    data = open(log_path).read()
+    os.unlink(log_path)
+  else:
+    raise svntest.actions.SVNUnexpectedOutput("'%s' not found") % log_path
+  if data != 'yes':
+    raise svntest.Failure
+
+
 ########################################################################
 # Run the tests
 
@@ -2563,6 +2599,7 @@
               changelist_near_conflict,
               no_such_changelist,
               commit_out_of_date_file,
+              start_commit_detect_capabilities,
              ]
 
 if __name__ == '__main__':