On 05.02.2011 21:55, Johan Corveleyn wrote:
> On Sat, Feb 5, 2011 at 9:37 PM, Johan Corveleyn <jcor...@gmail.com> wrote:
>> On Sat, Feb 5, 2011 at 7:14 PM, Stefan Sperling <s...@elego.de> wrote:
>>> On Sat, Feb 05, 2011 at 06:47:35PM +0100, Branko Čibej wrote:
>>>> I would not worry about existing clients -- simply mark the existing
>>>> APIs as deprecated, but keep them and do not attempt to improve their
>>>> performance.
>>> Neglecting performance of backwards compat code is an interesting idea.
>>> It allows us to focus on the new APIs first and foremost.
>>>
>>> The existing APIs will continue to work using the node walker and issue
>>> queries per node as they do now. We could consider optimising them later,
>>> before or after 1.7 release. The required changes are mostly mechanical.
>> I agree with most of what's been said here. I think it would be a pity
>> to use WC-NG in a way that provides far from optimal performance.
>>
>> FWIW, I just did a quick run of your per-directory proplist query vs.
>> the per-node version, on my Windows XP platform, to have another data
>> point. The performance improvement is significant, but not
>> earth-shattering (around 20%).
>>
>> Just tested with a freshly checked out working copy of svn trunk:
>>
>> 1) Per-node queries (r1066540). Looking at the third run, to make sure
>> everything is hot in cache:
>>
>> $ time svn proplist -R . >/dev/null
>>
>> real    0m1.532s
>> user    0m0.015s
>> sys     0m0.015s
>>
>>
>> 2) Per-dir queries (r1066541). Looking at the third run, to make sure
>> everything is hot in cache:
>>
>> $ time svn proplist -R . >/dev/null
>>
>> real    0m1.218s
>> user    0m0.015s
>> sys     0m0.031s
>>
>>
>> 3) For comparison, I also tested with SlikSVN 1.6.13. This is still
>> more than twice as fast:
>>
>> $ time svn proplist -R . >/dev/null
>>
>> real    0m0.578s
>> user    0m0.015s
>> sys     0m0.046s
> I should've added one more test, to put things in perspective :-),
> namely the test with r1039808 (the per-wc query version):
>
> $ time svn proplist -R . >/dev/null
>
> real    0m0.328s
> user    0m0.015s
> sys     0m0.031s
>
> (but, as you probably already know, this execution is not cancellable,
> which is pretty annoying :-), especially when I forget to redirect to
> /dev/null, but let it write on the console).
>
> Cheers,

Clearly the full-wc query is the best solution, and it's not really hard
to avoid the possible deadlocks associated with running callbacks from
within a transaction. And even the best current queries are doing way
too much in code instead of using the power of SQL to speed thing up.

As a proof of concept, I took the recursive proplist function, then
changed it it issue the callbacks outside sqlite transactions and made
the thing cancelable, by using a file-based temporary database to cache
the query results, and offloading most of the heavy lifting to sqlite.

Here's my resulton a fresh trunk working copy:

$ time svn proplist -R . >/dev/null

real    0m0.066s
user    0m0.047s
sys    0m0.014s

And here's current trunk without modifications, on the same working copy:
$ time svn proplist -R . >/dev/null

real    0m0.586s
user    0m0.549s
sys    0m0.029s


Just to put things in perspective, I ran both tests on a full checkout
of our tags directory:

With the patch:

$ time svn proplist -R . >/dev/null

real    0m4.926s
user    0m4.178s
sys    0m0.679s

Without the patch, I canceled the command after it had been running for
an hour, with CPU usage at more than 95%. So there's clearly quite a bit
of room for improving performance of the working copy.

Note that I didn't run the tests with this patch, so I'm not claiming it
to be bug-free. In fact there's one bug in the recursive proplist of the
WC root, where the root props show up twice -- there's an issue with the
filter in the query there. But it's proof-of-concept after all.

-- Brane

P.S.: By the way, checking out tags got the current trunk svn client
memory usage up to 2.5 gigs before it gave up and hung, and the WC was
borked, and I had to do the checkout in pieces, separately for each tag.
That's less than acceptable.

Index: subversion/libsvn_wc/props.c
===================================================================
--- subversion/libsvn_wc/props.c        (revision 1067790)
+++ subversion/libsvn_wc/props.c        (working copy)
@@ -1711,6 +1711,7 @@
   SVN_ERR(svn_wc__db_read_props_of_immediates(b->db, local_abspath,
                                               b->receiver_func,
                                               b->receiver_baton,
+                                              NULL, NULL,
                                               scratch_pool));
   return SVN_NO_ERROR;
 }
@@ -1725,47 +1726,42 @@
                             void *cancel_baton,
                             apr_pool_t *scratch_pool)
 {
-  struct read_dir_props_baton read_dir_baton;
+  apr_hash_t *props;
 
-  if (depth <= svn_depth_immediates)
-    {
-      apr_hash_t *props;
+  SVN_ERR(svn_wc__db_read_props(&props, wc_ctx->db, local_abspath,
+                                scratch_pool, scratch_pool));
+  if (receiver_func && props && apr_hash_count(props) > 0)
+    SVN_ERR((*receiver_func)(receiver_baton, local_abspath, props,
+                             scratch_pool));
+  if (depth == svn_depth_empty)
+    return SVN_NO_ERROR;
 
-      SVN_ERR(svn_wc__db_read_props(&props, wc_ctx->db, local_abspath,
-                                    scratch_pool, scratch_pool));
-      if (receiver_func && props && apr_hash_count(props) > 0)
-        SVN_ERR((*receiver_func)(receiver_baton, local_abspath, props,
-                                 scratch_pool));
-      if (depth == svn_depth_empty)
-        return SVN_NO_ERROR;
-    }
-
   if (depth == svn_depth_files)
     {
       SVN_ERR(svn_wc__db_read_props_of_files(wc_ctx->db, local_abspath,
                                              receiver_func, receiver_baton,
+                                             cancel_func, cancel_baton,
                                              scratch_pool));
-      return SVN_NO_ERROR;
     }
-
-  if (depth == svn_depth_immediates)
+  else if (depth == svn_depth_immediates)
     {
       SVN_ERR(svn_wc__db_read_props_of_immediates(wc_ctx->db, local_abspath,
-                                                  receiver_func,
-                                                  receiver_baton,
+                                                  receiver_func, 
receiver_baton,
+                                                  cancel_func, cancel_baton,
                                                   scratch_pool));
-      return SVN_NO_ERROR;
     }
+  else if (depth == svn_depth_infinity)
+    {
+      SVN_ERR(svn_wc__db_read_props_recursive(wc_ctx->db, local_abspath,
+                                              receiver_func, receiver_baton,
+                                              cancel_func, cancel_baton,
+                                              scratch_pool));
+    }
+  else
+    {
+      SVN_ERR_MALFUNCTION();
+    }
 
-  read_dir_baton.db = wc_ctx->db;
-  read_dir_baton.root_abspath = local_abspath;
-  read_dir_baton.receiver_func = receiver_func;
-  read_dir_baton.receiver_baton = receiver_baton;
-
-  SVN_ERR(svn_wc__internal_walk_children(wc_ctx->db, local_abspath, FALSE,
-                                         read_dir_props, &read_dir_baton,
-                                         depth, cancel_func, cancel_baton,
-                                         scratch_pool));
   return SVN_NO_ERROR;
 }
 
Index: subversion/libsvn_wc/wc-queries.sql
===================================================================
--- subversion/libsvn_wc/wc-queries.sql (revision 1067790)
+++ subversion/libsvn_wc/wc-queries.sql (working copy)
@@ -726,7 +726,69 @@
 UPDATE nodes SET checksum = ?4
 WHERE wc_id = ?1 AND local_relpath = ?2 AND op_depth = ?3;
 
+/* ------------------------------------------------------------------------- */
+/* PROOF OF CONCEPT: Complex queries for callback walks, using file-backed
+ *                   temporary database. */
 
+-- STMT_ATTACH_TEMPORARY_DATABASE
+ATTACH DATABASE '' AS temp_query_cache;
+
+-- STMT_DETACH_TEMPORARY_DATABASE
+DETACH DATABASE temp_query_cache;
+
+-- STMT_CLEAR_NODE_PROPS_CACHE
+DROP TABLE IF EXISTS temp_query_cache.node_props_cache;
+
+-- STMT_QUERY_NODE_PROPS_OF_CHILDREN
+CREATE TABLE temp_query_cache.node_props_cache AS
+  SELECT N.local_relpath, N.properties, N.kind, ?1 AS wc_id
+    FROM nodes AS N
+    JOIN (
+      SELECT local_relpath, MAX(op_depth) AS op_depth FROM nodes
+      WHERE wc_id = ?1 AND parent_relpath = ?2
+      GROUP BY local_relpath
+    ) AS V
+      ON N.local_relpath = V.local_relpath
+        AND N.op_depth = V.op_depth
+  WHERE N.wc_id = ?1
+    AND (N.presence = 'normal' OR N.presence = 'incomplete');
+CREATE UNIQUE INDEX temp_query_cache.node_props_cache_unique
+  ON node_props_cache (local_relpath);
+
+-- STMT_QUERY_NODE_PROPS_RECURSIVE
+CREATE TABLE temp_query_cache.node_props_cache AS
+  SELECT N.local_relpath, N.properties, '' AS kind, ?1 AS wc_id
+    FROM nodes AS N
+    JOIN (
+      SELECT local_relpath, MAX(op_depth) AS op_depth FROM nodes
+      WHERE wc_id = ?1
+        AND local_relpath LIKE
+          CASE ?2 WHEN '' THEN '%' ELSE ?2 || '/%' END
+      GROUP BY local_relpath
+    ) AS V
+      ON N.local_relpath = V.local_relpath
+        AND N.op_depth = V.op_depth
+  WHERE N.wc_id = ?1
+    AND (N.presence = 'normal' OR N.presence = 'incomplete');
+CREATE UNIQUE INDEX temp_query_cache.node_props_cache_unique
+  ON node_props_cache (local_relpath);
+
+-- STMT_REPLACE_ACTUAL_PROPS_IN_CACHE
+INSERT OR REPLACE INTO temp_query_cache.node_props_cache
+  (local_relpath, properties)
+  SELECT N.local_relpath, N.properties
+    FROM actual_node AS N JOIN temp_query_cache.node_props_cache AS C
+      ON N.local_relpath = C.local_relpath
+        AND  N.wc_id = C.wc_id;
+/*COMMIT TRANSACTION;*/
+
+-- STMT_SELECT_RELEVANT_PROPS_FROM_CACHE
+SELECT local_relpath, properties, kind
+  FROM temp_query_cache.node_props_cache
+WHERE properties IS NOT NULL
+ORDER BY local_relpath;
+
+
 /* ------------------------------------------------------------------------- */
 
 /* Grab all the statements related to the schema.  */
Index: subversion/libsvn_wc/wc_db.c
===================================================================
--- subversion/libsvn_wc/wc_db.c        (revision 1067790)
+++ subversion/libsvn_wc/wc_db.c        (working copy)
@@ -5056,187 +5056,150 @@
   return SVN_NO_ERROR;
 }
 
-/* Parse a node's PROP_DATA (which is PROP_DATA_LEN bytes long)
- * into a hash table keyed by property names and containing property values.
- *
- * If parsing succeeds, and the set of properties is not empty,
- * add the hash table to PROPS_PER_CHILD, keyed by the absolute path
- * of the node CHILD_RELPATH within the working copy at WCROOT_ABSPATH.
- *
- * If the set of properties is empty, and PROPS_PER_CHILD already contains
- * an entry for the node, clear the entry. This facilitates overriding
- * properties retrieved from the NODES table with empty sets of properties
- * stored in the ACTUAL_NODE table. */
+/** POC **/
+
 static svn_error_t *
-maybe_add_child_props(apr_hash_t *props_per_child,
-                      const char *prop_data,
-                      apr_size_t prop_data_len,
-                      const char *child_relpath,
-                      const char *wcroot_abspath,
-                      apr_pool_t *result_pool,
-                      apr_pool_t *scratch_pool)
+attach_cache_db(svn_wc__db_pdh_t *pdh)
 {
-  const char *child_abspath;
-  apr_hash_t *props;
-  svn_skel_t *prop_skel;
+  return svn_error_return
+    (svn_sqlite__exec_statements(pdh->wcroot->sdb,
+                                 STMT_ATTACH_TEMPORARY_DATABASE));
+}
 
-  prop_skel = svn_skel__parse(prop_data, prop_data_len, scratch_pool);
-  if (svn_skel__list_length(prop_skel) == 0)
-    return SVN_NO_ERROR;
+static svn_error_t *
+detach_cache_db(svn_wc__db_pdh_t *pdh)
+{
+  return svn_error_return
+    (svn_sqlite__exec_statements(pdh->wcroot->sdb,
+                                 STMT_DETACH_TEMPORARY_DATABASE));
+}
 
-  child_abspath = svn_dirent_join(wcroot_abspath, child_relpath, result_pool);
-  SVN_ERR(svn_skel__parse_proplist(&props, prop_skel, result_pool));
-  if (apr_hash_count(props))
-    apr_hash_set(props_per_child, child_abspath, APR_HASH_KEY_STRING, props);
-  else
-    apr_hash_set(props_per_child, child_abspath, APR_HASH_KEY_STRING, NULL);
+typedef struct cache_props_of_children_baton_t {
+  int stmt_idx;
+  apr_int64_t wc_id;
+  const char *local_relpath;
+} cache_props_of_children_baton_t;
 
+static svn_error_t *
+cache_props_of_children(void *cb_baton,
+                        svn_sqlite__db_t *db,
+                        apr_pool_t *scratch_pool)
+{
+  cache_props_of_children_baton_t *baton = cb_baton;
+  svn_sqlite__stmt_t *stmt;
+
+  SVN_ERR(svn_sqlite__get_statement(&stmt, db, baton->stmt_idx));
+  SVN_ERR(svn_sqlite__bindf(stmt, "is", baton->wc_id, baton->local_relpath));
+  SVN_ERR(svn_sqlite__step_done(stmt));
+  SVN_ERR(svn_sqlite__exec_statements(db, STMT_REPLACE_ACTUAL_PROPS_IN_CACHE));
+
   return SVN_NO_ERROR;
 }
 
 /* Call RECEIVER_FUNC, passing RECEIVER_BATON, an absolute path, and
  * a hash table mapping <tt>char *</tt> names onto svn_string_t *
- * values for any properties of immediate child nodes of LOCAL_ABSPATH.
+ * values for any properties of immediate or recursive child nodes of
+ * LOCAL_ABSPATH, the actual query being determined by STMT_IDX.
  * If FILES_ONLY is true, only report properties for file child nodes.
+ * Check for cancellation between calls of RECEIVER_FUNC.
  */
+
 static svn_error_t *
 read_props_of_children(svn_wc__db_t *db,
+                       int stmt_idx,
                        const char *local_abspath,
                        svn_boolean_t files_only,
                        svn_wc__proplist_receiver_t receiver_func,
                        void *receiver_baton,
+                       svn_cancel_func_t cancel_func,
+                       void *cancel_baton,
                        apr_pool_t *scratch_pool)
 {
   svn_wc__db_pdh_t *pdh;
-  const char *local_relpath;
-  const char *prev_child_relpath;
+  cache_props_of_children_baton_t txn_baton;
   svn_sqlite__stmt_t *stmt;
   svn_boolean_t have_row;
-  apr_hash_t *props_per_child;
-  apr_hash_t *files;
-  apr_hash_t *not_present;
-  apr_hash_index_t *hi;
   apr_pool_t *iterpool;
 
   SVN_ERR_ASSERT(svn_dirent_is_absolute(local_abspath));
   SVN_ERR_ASSERT(receiver_func);
 
-  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &local_relpath, db,
-                                             local_abspath,
+  SVN_ERR(svn_wc__db_pdh_parse_local_abspath(&pdh, &txn_baton.local_relpath,
+                                             db, local_abspath,
                                              svn_sqlite__mode_readwrite,
                                              scratch_pool, scratch_pool));
   VERIFY_USABLE_PDH(pdh);
 
-  props_per_child = apr_hash_make(scratch_pool);
-  not_present = apr_hash_make(scratch_pool);
-  if (files_only)
-    files = apr_hash_make(scratch_pool);
-  else
-    files = NULL;
+  SVN_ERR(attach_cache_db(pdh));
+  SVN_ERR(svn_sqlite__exec_statements(pdh->wcroot->sdb,
+                                      STMT_CLEAR_NODE_PROPS_CACHE));
 
+  txn_baton.stmt_idx = stmt_idx;
+  txn_baton.wc_id = pdh->wcroot->wc_id;
+  SVN_ERR(svn_sqlite__with_transaction(pdh->wcroot->sdb,
+                                       cache_props_of_children,
+                                       &txn_baton,
+                                       scratch_pool));
+
+  iterpool = svn_pool_create(scratch_pool);
+
   SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->wcroot->sdb,
-                                    STMT_SELECT_NODE_PROPS_OF_CHILDREN));
-  SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wcroot->wc_id, local_relpath));
+                                    STMT_SELECT_RELEVANT_PROPS_FROM_CACHE));
   SVN_ERR(svn_sqlite__step(&have_row, stmt));
-  prev_child_relpath = NULL;
   while (have_row)
     {
-      svn_wc__db_status_t child_presence;
-      const char *child_relpath;
       const char *prop_data;
       apr_size_t len;
 
-      child_relpath = svn_sqlite__column_text(stmt, 2, scratch_pool);
-
-      if (prev_child_relpath && strcmp(child_relpath, prev_child_relpath) == 0)
+      if (files_only)
         {
-          /* Same child, but lower op_depth -- skip this row. */
-          SVN_ERR(svn_sqlite__step(&have_row, stmt));
-          continue;
-        }
-      prev_child_relpath = child_relpath;
+          svn_wc__db_kind_t child_kind;
 
-      child_presence = svn_sqlite__column_token(stmt, 1, presence_map);
-      if (child_presence != svn_wc__db_status_normal)
-        {
-          apr_hash_set(not_present, child_relpath, APR_HASH_KEY_STRING, "");
-          SVN_ERR(svn_sqlite__step(&have_row, stmt));
-          continue;
-        }
-
-      prop_data = svn_sqlite__column_blob(stmt, 0, &len, NULL);
-      if (prop_data)
-        {
-          if (files_only)
+          child_kind = svn_sqlite__column_token(stmt, 2, kind_map);
+          if (child_kind != svn_wc__db_kind_file &&
+              child_kind != svn_wc__db_kind_symlink)
             {
-              svn_wc__db_kind_t child_kind;
-
-              child_kind = svn_sqlite__column_token(stmt, 3, kind_map);
-              if (child_kind != svn_wc__db_kind_file &&
-                  child_kind != svn_wc__db_kind_symlink)
-                {
-                  SVN_ERR(svn_sqlite__step(&have_row, stmt));
-                  continue;
-                }
-              apr_hash_set(files, child_relpath, APR_HASH_KEY_STRING, NULL);
+              SVN_ERR(svn_sqlite__step(&have_row, stmt));
+              continue;
             }
-
-          SVN_ERR(maybe_add_child_props(props_per_child, prop_data, len,
-                                        child_relpath, pdh->wcroot->abspath,
-                                        scratch_pool, scratch_pool));
         }
 
-      SVN_ERR(svn_sqlite__step(&have_row, stmt));
-    }
+      svn_pool_clear(iterpool);
 
-  SVN_ERR(svn_sqlite__reset(stmt));
+      /* See if someone wants to cancel this operation. */
+      if (cancel_func)
+        SVN_ERR(cancel_func(cancel_baton));
 
-  SVN_ERR(svn_sqlite__get_statement(&stmt, pdh->wcroot->sdb,
-                                    STMT_SELECT_ACTUAL_PROPS_OF_CHILDREN));
-  SVN_ERR(svn_sqlite__bindf(stmt, "is", pdh->wcroot->wc_id, local_relpath));
-  SVN_ERR(svn_sqlite__step(&have_row, stmt));
-  while (have_row)
-    {
-      const char *child_relpath;
-      const char *prop_data;
-      apr_size_t len;
-
-      prop_data = svn_sqlite__column_blob(stmt, 0, &len, NULL);
+      prop_data = svn_sqlite__column_blob(stmt, 1, &len, NULL);
       if (prop_data)
         {
-          child_relpath = svn_sqlite__column_text(stmt, 1, scratch_pool);
+          svn_skel_t *prop_skel;
 
-          if (apr_hash_get(not_present, child_relpath, APR_HASH_KEY_STRING) ||
-              (files_only &&
-               apr_hash_get(files, child_relpath, APR_HASH_KEY_STRING) == 
NULL))
+          prop_skel = svn_skel__parse(prop_data, len, iterpool);
+          if (svn_skel__list_length(prop_skel) != 0)
             {
-                SVN_ERR(svn_sqlite__step(&have_row, stmt));
-                continue;
+              const char *child_relpath;
+              const char *child_abspath;
+              apr_hash_t *props;
+
+              child_relpath = svn_sqlite__column_text(stmt, 0, NULL);
+              child_abspath = svn_dirent_join(pdh->wcroot->abspath,
+                                              child_relpath, iterpool);
+              SVN_ERR(svn_skel__parse_proplist(&props, prop_skel, iterpool));
+              if (receiver_func && apr_hash_count(props) > 0)
+                {
+                  SVN_ERR((*receiver_func)(receiver_baton,
+                                           child_abspath, props,
+                                           iterpool));
+                }
             }
-          SVN_ERR(maybe_add_child_props(props_per_child, prop_data, len,
-                                        child_relpath, pdh->wcroot->abspath,
-                                        scratch_pool, scratch_pool));
         }
 
       SVN_ERR(svn_sqlite__step(&have_row, stmt));
     }
 
   SVN_ERR(svn_sqlite__reset(stmt));
-
-  iterpool = svn_pool_create(scratch_pool);
-  for (hi = apr_hash_first(scratch_pool, props_per_child);
-       hi;
-       hi = apr_hash_next(hi))
-    {
-      const char *child_abspath = svn__apr_hash_index_key(hi);
-      apr_hash_t *child_props = svn__apr_hash_index_val(hi);
-
-      svn_pool_clear(iterpool);
-
-      if (child_props)
-        SVN_ERR((*receiver_func)(receiver_baton, child_abspath, child_props,
-                                 iterpool));
-    }
+  SVN_ERR(detach_cache_db(pdh));
   svn_pool_destroy(iterpool);
 
   return SVN_NO_ERROR;
@@ -5247,11 +5210,17 @@
                                const char *local_abspath,
                                svn_wc__proplist_receiver_t receiver_func,
                                void *receiver_baton,
+                               svn_cancel_func_t cancel_func,
+                               void *cancel_baton,
                                apr_pool_t *scratch_pool)
 {
-  return svn_error_return(read_props_of_children(db, local_abspath, TRUE,
-                                                 receiver_func, receiver_baton,
-                                                 scratch_pool));
+  SVN_ERR(read_props_of_children(db,
+                                 STMT_QUERY_NODE_PROPS_OF_CHILDREN,
+                                 local_abspath, TRUE,
+                                 receiver_func, receiver_baton,
+                                 cancel_func, cancel_baton,
+                                 scratch_pool));
+  return SVN_NO_ERROR;
 }
 
 svn_error_t *
@@ -5259,13 +5228,36 @@
                                     const char *local_abspath,
                                     svn_wc__proplist_receiver_t receiver_func,
                                     void *receiver_baton,
+                                    svn_cancel_func_t cancel_func,
+                                    void *cancel_baton,
                                     apr_pool_t *scratch_pool)
 {
-  return svn_error_return(read_props_of_children(db, local_abspath, FALSE,
-                                                 receiver_func, receiver_baton,
-                                                 scratch_pool));
+  SVN_ERR(read_props_of_children(db,
+                                 STMT_QUERY_NODE_PROPS_OF_CHILDREN,
+                                 local_abspath, FALSE,
+                                 receiver_func, receiver_baton,
+                                 cancel_func, cancel_baton,
+                                 scratch_pool));
+  return SVN_NO_ERROR;
 }
 
+svn_error_t *
+svn_wc__db_read_props_recursive(svn_wc__db_t *db,
+                                const char *local_abspath,
+                                svn_wc__proplist_receiver_t receiver_func,
+                                void *receiver_baton,
+                                svn_cancel_func_t cancel_func,
+                                void *cancel_baton,
+                                apr_pool_t *scratch_pool)
+{
+  SVN_ERR(read_props_of_children(db,
+                                 STMT_QUERY_NODE_PROPS_RECURSIVE,
+                                 local_abspath, FALSE,
+                                 receiver_func, receiver_baton,
+                                 cancel_func, cancel_baton,
+                                 scratch_pool));
+  return SVN_NO_ERROR;
+}
 
 static svn_error_t *
 db_read_pristine_props(apr_hash_t **props,
Index: subversion/libsvn_wc/wc_db.h
===================================================================
--- subversion/libsvn_wc/wc_db.h        (revision 1067790)
+++ subversion/libsvn_wc/wc_db.h        (working copy)
@@ -1580,9 +1580,10 @@
 svn_error_t *
 svn_wc__db_read_props_of_files(svn_wc__db_t *db,
                                const char *local_abspath,
-                               svn_wc__proplist_receiver_t
-                                 receiver_func,
+                               svn_wc__proplist_receiver_t receiver_func,
                                void *receiver_baton,
+                               svn_cancel_func_t cancel_func,
+                               void *cancel_baton,
                                apr_pool_t *scratch_pool);
 
 /* Call RECEIVER_FUNC, passing RECEIVER_BATON, an absolute path, and
@@ -1592,11 +1593,25 @@
 svn_error_t *
 svn_wc__db_read_props_of_immediates(svn_wc__db_t *db,
                                     const char *local_abspath,
-                                    svn_wc__proplist_receiver_t
-                                      receiver_func,
+                                    svn_wc__proplist_receiver_t receiver_func,
                                     void *receiver_baton,
+                                    svn_cancel_func_t cancel_func,
+                                    void *cancel_baton,
                                     apr_pool_t *scratch_pool);
 
+/* Call RECEIVER_FUNC, passing RECEIVER_BATON, an absolute path, and
+ * a hash table mapping <tt>char *</tt> names onto svn_string_t *
+ * values for any properties of all (recursive) child nodes of LOCAL_ABSPATH.
+ */
+svn_error_t *
+svn_wc__db_read_props_recursive(svn_wc__db_t *db,
+                                const char *local_abspath,
+                                svn_wc__proplist_receiver_t receiver_func,
+                                void *receiver_baton,
+                                svn_cancel_func_t cancel_func,
+                                  void *cancel_baton,
+                                apr_pool_t *scratch_pool);
+
 /* Set *PROPS to the properties of the node LOCAL_ABSPATH in the WORKING
    tree (looking through to the BASE tree as required).
 

Reply via email to