You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@subversion.apache.org by st...@apache.org on 2016/04/29 20:38:56 UTC

svn commit: r1741682 [15/26] - in /subversion/branches/authzperf: ./ build/ build/ac-macros/ build/generator/ contrib/server-side/svncutter/ notes/ notes/api-errata/1.9/ notes/move-tracking/ subversion/ subversion/bindings/ctypes-python/ subversion/bin...

Modified: subversion/branches/authzperf/subversion/libsvn_repos/log.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_repos/log.c?rev=1741682&r1=1741681&r2=1741682&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_repos/log.c (original)
+++ subversion/branches/authzperf/subversion/libsvn_repos/log.c Fri Apr 29 18:38:53 2016
@@ -43,8 +43,21 @@
 #include "private/svn_mergeinfo_private.h"
 #include "private/svn_subr_private.h"
 #include "private/svn_sorts_private.h"
+#include "private/svn_string_private.h"
 
 
+/* This is a mere convenience struct such that we don't need to pass that
+   many parameters around individually. */
+typedef struct log_callbacks_t
+{
+  svn_repos_path_change_receiver_t path_change_receiver;
+  void *path_change_receiver_baton;
+  svn_repos_log_entry_receiver_t revision_receiver;
+  void *revision_receiver_baton;
+  svn_repos_authz_func_t authz_read_func;
+  void *authz_read_baton;
+} log_callbacks_t;
+
 
 svn_error_t *
 svn_repos_check_revision_access(svn_repos_revision_access_level_t *access_level,
@@ -56,11 +69,11 @@ svn_repos_check_revision_access(svn_repo
 {
   svn_fs_t *fs = svn_repos_fs(repos);
   svn_fs_root_t *rev_root;
-  apr_hash_t *changes;
-  apr_hash_index_t *hi;
+  svn_fs_path_change_iterator_t *iterator;
+  svn_fs_path_change3_t *change;
   svn_boolean_t found_readable = FALSE;
   svn_boolean_t found_unreadable = FALSE;
-  apr_pool_t *subpool;
+  apr_pool_t *iterpool;
 
   /* By default, we'll grant full read access to REVISION. */
   *access_level = svn_repos_revision_access_full;
@@ -71,25 +84,27 @@ svn_repos_check_revision_access(svn_repo
 
   /* Fetch the changes associated with REVISION. */
   SVN_ERR(svn_fs_revision_root(&rev_root, fs, revision, pool));
-  SVN_ERR(svn_fs_paths_changed2(&changes, rev_root, pool));
+  SVN_ERR(svn_fs_paths_changed3(&iterator, rev_root, pool, pool));
+  SVN_ERR(svn_fs_path_change_get(&change, iterator));
+
+  /* No changed paths?  We're done.
 
-  /* No changed paths?  We're done. */
-  if (apr_hash_count(changes) == 0)
+     Note that the check at "decision:" assumes that at least one
+     path has been processed.  So, this actually affects functionality. */
+  if (!change)
     return SVN_NO_ERROR;
 
   /* Otherwise, we have to check the readability of each changed
      path, or at least enough to answer the question asked. */
-  subpool = svn_pool_create(pool);
-  for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
+  iterpool = svn_pool_create(pool);
+  while (change)
     {
-      const char *key = apr_hash_this_key(hi);
-      svn_fs_path_change2_t *change = apr_hash_this_val(hi);
       svn_boolean_t readable;
 
-      svn_pool_clear(subpool);
+      svn_pool_clear(iterpool);
 
-      SVN_ERR(authz_read_func(&readable, rev_root, key,
-                              authz_read_baton, subpool));
+      SVN_ERR(authz_read_func(&readable, rev_root, change->path.data,
+                              authz_read_baton, iterpool));
       if (! readable)
         found_unreadable = TRUE;
       else
@@ -109,15 +124,16 @@ svn_repos_check_revision_access(svn_repo
             svn_revnum_t copyfrom_rev;
 
             SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
-                                       rev_root, key, subpool));
+                                       rev_root, change->path.data,
+                                       iterpool));
             if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
               {
                 svn_fs_root_t *copyfrom_root;
                 SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
-                                             copyfrom_rev, subpool));
+                                             copyfrom_rev, iterpool));
                 SVN_ERR(authz_read_func(&readable,
                                         copyfrom_root, copyfrom_path,
-                                        authz_read_baton, subpool));
+                                        authz_read_baton, iterpool));
                 if (! readable)
                   found_unreadable = TRUE;
 
@@ -134,10 +150,12 @@ svn_repos_check_revision_access(svn_repo
         default:
           break;
         }
+
+      SVN_ERR(svn_fs_path_change_get(&change, iterator));
     }
 
  decision:
-  svn_pool_destroy(subpool);
+  svn_pool_destroy(iterpool);
 
   /* Either every changed path was unreadable... */
   if (! found_readable)
@@ -152,181 +170,14 @@ svn_repos_check_revision_access(svn_repo
 }
 
 
-/* Core filter logic of detect_changed().  Convert FS API-level path change
- * CHANGE of PATH in FS at ROOT to higher level API *ITEM_P.
+/* Find all significant changes under ROOT and, if not NULL, report them
+ * to the CALLBACKS->PATH_CHANGE_RECEIVER.  "Significant" means that the
+ * text or properties of the node were changed, or that the node was added
+ * or deleted.
  *
- * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
- * AUTHZ_READ_BATON and FS) to check whether PATH and what parts of CHANGE
- * (copyfrom_path) is readable.  Update *FOUND_READABLE and
- * *FOUND_UNREADABLE accordingly.
- *
- * NOTE:  Much of this loop is going to look quite similar to
- *        svn_repos_check_revision_access(), but we have to do more things
- *        here, so we'll live with the duplication.
- */
-static svn_error_t *
-check_changed_path(svn_log_changed_path2_t **item_p,
-                   svn_boolean_t *found_readable,
-                   svn_boolean_t *found_unreadable,
-                   svn_fs_root_t *root,
-                   svn_fs_t *fs,
-                   const char *path,
-                   svn_fs_path_change2_t *change,
-                   svn_repos_authz_func_t authz_read_func,
-                   void *authz_read_baton,
-                   apr_pool_t *result_pool,
-                   apr_pool_t *scratch_pool)
-{
-  char action;
-  svn_log_changed_path2_t *item;
-
-  /* Skip path if unreadable. */
-  if (authz_read_func)
-    {
-      svn_boolean_t readable;
-      SVN_ERR(authz_read_func(&readable,
-                              root, path,
-                              authz_read_baton, scratch_pool));
-      if (! readable)
-        {
-          *found_unreadable = TRUE;
-          return SVN_NO_ERROR;
-        }
-    }
-
-  /* At least one changed-path was readable. */
-  *found_readable = TRUE;
-
-  switch (change->change_kind)
-    {
-    case svn_fs_path_change_reset:
-      return SVN_NO_ERROR;
-
-    case svn_fs_path_change_add:
-      action = 'A';
-      break;
-
-    case svn_fs_path_change_replace:
-      action = 'R';
-      break;
-
-    case svn_fs_path_change_delete:
-      action = 'D';
-      break;
-
-    case svn_fs_path_change_modify:
-    default:
-      action = 'M';
-      break;
-    }
-
-  item = svn_log_changed_path2_create(result_pool);
-  item->action = action;
-  item->node_kind = change->node_kind;
-  item->copyfrom_rev = SVN_INVALID_REVNUM;
-  item->text_modified = change->text_mod ? svn_tristate_true
-                                          : svn_tristate_false;
-  item->props_modified = change->prop_mod ? svn_tristate_true
-                                          : svn_tristate_false;
-
-  /* Pre-1.6 revision files don't store the change path kind, so fetch
-      it manually. */
-  if (item->node_kind == svn_node_unknown)
-    {
-      svn_fs_root_t *check_root = root;
-      const char *check_path = path;
-
-      /* Deleted items don't exist so check earlier revision.  We
-          know the parent must exist and could be a copy */
-      if (change->change_kind == svn_fs_path_change_delete)
-        {
-          svn_fs_history_t *history;
-          svn_revnum_t prev_rev;
-          const char *parent_path, *name;
-
-          svn_fspath__split(&parent_path, &name, path, scratch_pool);
-
-          SVN_ERR(svn_fs_node_history2(&history, root, parent_path,
-                                       scratch_pool, scratch_pool));
-
-          /* Two calls because the first call returns the original
-              revision as the deleted child means it is 'interesting' */
-          SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, scratch_pool,
-                                       scratch_pool));
-          SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, scratch_pool,
-                                       scratch_pool));
-
-          SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev, history,
-                                          scratch_pool));
-          SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev,
-                                       scratch_pool));
-          check_path = svn_fspath__join(parent_path, name, scratch_pool);
-        }
-
-      SVN_ERR(svn_fs_check_path(&item->node_kind, check_root, check_path,
-                                scratch_pool));
-    }
-
-
-  if ((action == 'A') || (action == 'R'))
-    {
-      const char *copyfrom_path = change->copyfrom_path;
-      svn_revnum_t copyfrom_rev = change->copyfrom_rev;
-
-      /* the following is a potentially expensive operation since on FSFS
-          we will follow the DAG from ROOT to PATH and that requires
-          actually reading the directories along the way. */
-      if (!change->copyfrom_known)
-        {
-          SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
-                                     root, path, scratch_pool));
-          copyfrom_path = apr_pstrdup(result_pool, copyfrom_path);
-        }
-
-      if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
-        {
-          svn_boolean_t readable = TRUE;
-
-          if (authz_read_func)
-            {
-              svn_fs_root_t *copyfrom_root;
-
-              SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
-                                            copyfrom_rev, scratch_pool));
-              SVN_ERR(authz_read_func(&readable,
-                                      copyfrom_root, copyfrom_path,
-                                      authz_read_baton, scratch_pool));
-              if (! readable)
-                *found_unreadable = TRUE;
-            }
-
-          if (readable)
-            {
-              item->copyfrom_path = copyfrom_path;
-              item->copyfrom_rev = copyfrom_rev;
-            }
-        }
-    }
-
-  *item_p = item;
-  return SVN_NO_ERROR;
-}
-
-/* Store as keys in CHANGED the paths of all node in ROOT that show a
- * significant change.  "Significant" means that the text or
- * properties of the node were changed, or that the node was added or
- * deleted.
- *
- * The CHANGED hash set and its keys and values are allocated in POOL;
- * keys are const char * paths and values are svn_log_changed_path_t.
- *
- * To prevent changes from being processed over and over again, the
- * changed paths for ROOT may be passed in PREFETCHED_CHANGES.  If the
- * latter is NULL, we will request the list inside this function.
- *
- * If optional AUTHZ_READ_FUNC is non-NULL, then use it (with
- * AUTHZ_READ_BATON and FS) to check whether each changed-path (and
- * copyfrom_path) is readable:
+ * If optional CALLBACKS->AUTHZ_READ_FUNC is non-NULL, then use it (with
+ * CALLBACKS->AUTHZ_READ_BATON and FS) to check whether each changed-path
+ * (and copyfrom_path) is readable:
  *
  *     - If absolutely every changed-path (and copyfrom_path) is
  *     readable, then return the full CHANGED hash, and set
@@ -344,41 +195,22 @@ check_changed_path(svn_log_changed_path2
  */
 static svn_error_t *
 detect_changed(svn_repos_revision_access_level_t *access_level,
-               apr_hash_t **changed,
                svn_fs_root_t *root,
                svn_fs_t *fs,
-               apr_hash_t *prefetched_changes,
-               svn_repos_authz_func_t authz_read_func,
-               void *authz_read_baton,
-               apr_pool_t *result_pool)
+               const log_callbacks_t *callbacks,
+               apr_pool_t *scratch_pool)
 {
-  apr_hash_t *changes;
-  apr_hash_index_t *hi;
+  svn_fs_path_change_iterator_t *iterator;
+  svn_fs_path_change3_t *change;
   apr_pool_t *iterpool;
   svn_boolean_t found_readable = FALSE;
   svn_boolean_t found_unreadable = FALSE;
 
-  /* If we create the CHANGES hash ourselves, we can reuse it as the
-   * result hash as it contains the exact same keys - but with _all_
-   * values being replaced by structs of a different type. */
-  if (prefetched_changes == NULL)
-    {
-      SVN_ERR(svn_fs_paths_changed2(&changes, root, result_pool));
+  /* Retrieve the first change in the list. */
+  SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, scratch_pool));
+  SVN_ERR(svn_fs_path_change_get(&change, iterator));
 
-      /* If we are going to filter the results, we won't use the exact
-       * same keys but put them into a new hash. */
-      if (authz_read_func)
-        *changed = svn_hash__make(result_pool);
-      else
-        *changed = changes;
-    }
-  else
-    {
-      changes = prefetched_changes;
-      *changed = svn_hash__make(result_pool);
-    }
-
-  if (apr_hash_count(changes) == 0)
+  if (!change)
     {
       /* No paths changed in this revision?  Uh, sure, I guess the
          revision is readable, then.  */
@@ -386,53 +218,122 @@ detect_changed(svn_repos_revision_access
       return SVN_NO_ERROR;
     }
 
-  iterpool = svn_pool_create(result_pool);
-
-  /* Authz can be much faster when paths are being checked in tree order. */
-  if (authz_read_func && apr_hash_count(changes) > 1)
+  iterpool = svn_pool_create(scratch_pool);
+  while (change)
     {
-      apr_array_header_t *sorted;
-      int i;
-      apr_pool_t *scratch_pool = svn_pool_create(result_pool);
+      /* NOTE:  Much of this loop is going to look quite similar to
+         svn_repos_check_revision_access(), but we have to do more things
+         here, so we'll live with the duplication. */
+      const char *path = change->path.data;
+      svn_pool_clear(iterpool);
 
-      sorted = svn_sort__hash(changes, svn_sort_compare_items_lexically,
-                              scratch_pool);
-      for (i = 0; i < sorted->nelts; i++)
+      /* Skip path if unreadable. */
+      if (callbacks->authz_read_func)
         {
-          svn_sort__item_t item = APR_ARRAY_IDX(sorted, i, svn_sort__item_t);
-          const char *path = item.key;
-          svn_fs_path_change2_t *change = item.value;
+          svn_boolean_t readable;
+          SVN_ERR(callbacks->authz_read_func(&readable, root, path,
+                                             callbacks->authz_read_baton,
+                                             iterpool));
+          if (! readable)
+            {
+              found_unreadable = TRUE;
+              SVN_ERR(svn_fs_path_change_get(&change, iterator));
+              continue;
+            }
+        }
 
-          svn_log_changed_path2_t *changed_item = NULL;
+      /* At least one changed-path was readable. */
+      found_readable = TRUE;
 
-          svn_pool_clear(iterpool);
-          SVN_ERR(check_changed_path(&changed_item, &found_readable,
-                                     &found_unreadable, root, fs, path,
-                                     change, authz_read_func,
-                                     authz_read_baton, result_pool, iterpool));
+      /* Pre-1.6 revision files don't store the change path kind, so fetch
+         it manually. */
+      if (change->node_kind == svn_node_unknown)
+        {
+          svn_fs_root_t *check_root = root;
+          const char *check_path = path;
+
+          /* Deleted items don't exist so check earlier revision.  We
+             know the parent must exist and could be a copy */
+          if (change->change_kind == svn_fs_path_change_delete)
+            {
+              svn_fs_history_t *history;
+              svn_revnum_t prev_rev;
+              const char *parent_path, *name;
+
+              svn_fspath__split(&parent_path, &name, path, iterpool);
+
+              SVN_ERR(svn_fs_node_history2(&history, root, parent_path,
+                                           iterpool, iterpool));
+
+              /* Two calls because the first call returns the original
+                 revision as the deleted child means it is 'interesting' */
+              SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
+                                           iterpool));
+              SVN_ERR(svn_fs_history_prev2(&history, history, TRUE, iterpool,
+                                           iterpool));
+
+              SVN_ERR(svn_fs_history_location(&parent_path, &prev_rev,
+                                              history, iterpool));
+              SVN_ERR(svn_fs_revision_root(&check_root, fs, prev_rev,
+                                           iterpool));
+              check_path = svn_fspath__join(parent_path, name, iterpool);
+            }
 
-          if (changed_item)
-            svn_hash_sets(*changed, path, changed_item);
+          SVN_ERR(svn_fs_check_path(&change->node_kind, check_root, check_path,
+                                    iterpool));
         }
 
-      svn_pool_destroy(scratch_pool);
-    }
-  else
-    {
-      for (hi = apr_hash_first(result_pool, changes); hi; hi = apr_hash_next(hi))
+      if (   (change->change_kind == svn_fs_path_change_add)
+          || (change->change_kind == svn_fs_path_change_replace))
         {
-          const char *path = apr_hash_this_key(hi);
-          apr_ssize_t path_len = apr_hash_this_key_len(hi);
-          svn_fs_path_change2_t *change = apr_hash_this_val(hi);
-          svn_log_changed_path2_t *changed_item;
+          const char *copyfrom_path = change->copyfrom_path;
+          svn_revnum_t copyfrom_rev = change->copyfrom_rev;
 
-          svn_pool_clear(iterpool);
-          SVN_ERR(check_changed_path(&changed_item, &found_readable,
-                                     &found_unreadable, root, fs, path,
-                                     change, NULL, NULL, result_pool, iterpool));
+          /* the following is a potentially expensive operation since on FSFS
+             we will follow the DAG from ROOT to PATH and that requires
+             actually reading the directories along the way. */
+          if (!change->copyfrom_known)
+            {
+              SVN_ERR(svn_fs_copied_from(&copyfrom_rev, &copyfrom_path,
+                                        root, path, iterpool));
+              change->copyfrom_known = TRUE;
+            }
 
-          apr_hash_set(*changed, path, path_len, changed_item);
+          if (copyfrom_path && SVN_IS_VALID_REVNUM(copyfrom_rev))
+            {
+              svn_boolean_t readable = TRUE;
+
+              if (callbacks->authz_read_func)
+                {
+                  svn_fs_root_t *copyfrom_root;
+
+                  SVN_ERR(svn_fs_revision_root(&copyfrom_root, fs,
+                                               copyfrom_rev, iterpool));
+                  SVN_ERR(callbacks->authz_read_func(&readable,
+                                                     copyfrom_root,
+                                                     copyfrom_path,
+                                                     callbacks->authz_read_baton,
+                                                     iterpool));
+                  if (! readable)
+                    found_unreadable = TRUE;
+                }
+
+              if (readable)
+                {
+                  change->copyfrom_path = copyfrom_path;
+                  change->copyfrom_rev = copyfrom_rev;
+                }
+            }
         }
+
+      if (callbacks->path_change_receiver)
+        SVN_ERR(callbacks->path_change_receiver(
+                                     callbacks->path_change_receiver_baton,
+                                     change,
+                                     iterpool));
+
+      /* Next changed path. */
+      SVN_ERR(svn_fs_path_change_get(&change, iterator));
     }
 
   svn_pool_destroy(iterpool);
@@ -659,23 +560,21 @@ next_history_rev(const apr_array_header_
 
 /* Set *DELETED_MERGEINFO_CATALOG and *ADDED_MERGEINFO_CATALOG to
    catalogs describing how mergeinfo values on paths (which are the
-   keys of those catalogs) were changed in REV.  If *PREFETCHED_CHANGES
-   already contains the changed paths for REV, use that.  Otherwise,
-   request that data and return it in *PREFETCHED_CHANGES. */
+   keys of those catalogs) were changed in REV. */
 /* ### TODO: This would make a *great*, useful public function,
    ### svn_repos_fs_mergeinfo_changed()!  -- cmpilato  */
 static svn_error_t *
 fs_mergeinfo_changed(svn_mergeinfo_catalog_t *deleted_mergeinfo_catalog,
                      svn_mergeinfo_catalog_t *added_mergeinfo_catalog,
-                     apr_hash_t **prefetched_changes,
                      svn_fs_t *fs,
                      svn_revnum_t rev,
                      apr_pool_t *result_pool,
                      apr_pool_t *scratch_pool)
 {
   svn_fs_root_t *root;
-  apr_pool_t *iterpool;
-  apr_hash_index_t *hi;
+  apr_pool_t *iterpool, *iterator_pool;
+  svn_fs_path_change_iterator_t *iterator;
+  svn_fs_path_change3_t *change;
   svn_boolean_t any_mergeinfo = FALSE;
   svn_boolean_t any_copy = FALSE;
 
@@ -687,56 +586,69 @@ fs_mergeinfo_changed(svn_mergeinfo_catal
   if (rev == 0)
     return SVN_NO_ERROR;
 
+  /* FS iterators are potentially heavy objects.
+   * Hold them in a separate pool to clean them up asap. */
+  iterator_pool = svn_pool_create(scratch_pool);
+
   /* We're going to use the changed-paths information for REV to
      narrow down our search. */
   SVN_ERR(svn_fs_revision_root(&root, fs, rev, scratch_pool));
-  if (*prefetched_changes == NULL)
-    SVN_ERR(svn_fs_paths_changed2(prefetched_changes, root, scratch_pool));
+  SVN_ERR(svn_fs_paths_changed3(&iterator, root, iterator_pool,
+                                iterator_pool));
+  SVN_ERR(svn_fs_path_change_get(&change, iterator));
 
   /* Look for copies and (potential) mergeinfo changes.
-     We will use both flags to take shortcuts further down the road. */
-  for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
-       hi;
-       hi = apr_hash_next(hi))
-    {
-      svn_fs_path_change2_t *change = apr_hash_this_val(hi);
+     We will use both flags to take shortcuts further down the road.
 
+     The critical information here is whether there are any copies
+     because that greatly influences the costs for log processing.
+     So, it is faster to iterate over the changes twice - in the worst
+     case b/c most times there is no m/i at all and we exit out early
+     without any overhead. 
+   */
+  while (change && (!any_mergeinfo || !any_copy))
+    {
       /* If there was a prop change and we are not positive that _no_
          mergeinfo change happened, we must assume that it might have. */
       if (change->mergeinfo_mod != svn_tristate_false && change->prop_mod)
         any_mergeinfo = TRUE;
 
-      switch (change->change_kind)
-        {
-        case svn_fs_path_change_add:
-        case svn_fs_path_change_replace:
-          any_copy = TRUE;
-          break;
+      if (   (change->change_kind == svn_fs_path_change_add)
+          || (change->change_kind == svn_fs_path_change_replace))
+        any_copy = TRUE;
 
-        default:
-          break;
-        }
+      SVN_ERR(svn_fs_path_change_get(&change, iterator));
     }
 
   /* No potential mergeinfo changes?  We're done. */
   if (! any_mergeinfo)
-    return SVN_NO_ERROR;
+    {
+      svn_pool_destroy(iterator_pool);
+      return SVN_NO_ERROR;
+    }
+
+  /* There is or may be some m/i change. Look closely now. */
+  svn_pool_clear(iterator_pool);
+  SVN_ERR(svn_fs_paths_changed3(&iterator, root, iterator_pool,
+                                iterator_pool));
 
   /* Loop over changes, looking for anything that might carry an
      svn:mergeinfo change and is one of our paths of interest, or a
      child or [grand]parent directory thereof. */
   iterpool = svn_pool_create(scratch_pool);
-  for (hi = apr_hash_first(scratch_pool, *prefetched_changes);
-       hi;
-       hi = apr_hash_next(hi))
+  while (TRUE)
     {
       const char *changed_path;
-      svn_fs_path_change2_t *change = apr_hash_this_val(hi);
       const char *base_path = NULL;
       svn_revnum_t base_rev = SVN_INVALID_REVNUM;
       svn_fs_root_t *base_root = NULL;
       svn_string_t *prev_mergeinfo_value = NULL, *mergeinfo_value;
 
+      /* Next change. */
+      SVN_ERR(svn_fs_path_change_get(&change, iterator));
+      if (!change)
+        break;
+
       /* Cheap pre-checks that don't require memory allocation etc. */
 
       /* No mergeinfo change? -> nothing to do here. */
@@ -748,7 +660,7 @@ fs_mergeinfo_changed(svn_mergeinfo_catal
         continue;
 
       /* Begin actual processing */
-      changed_path = apr_hash_this_key(hi);
+      changed_path = change->path.data;
       svn_pool_clear(iterpool);
 
       switch (change->change_kind)
@@ -906,20 +818,18 @@ fs_mergeinfo_changed(svn_mergeinfo_catal
     }
 
   svn_pool_destroy(iterpool);
+  svn_pool_destroy(iterator_pool);
+
   return SVN_NO_ERROR;
 }
 
 
 /* Determine what (if any) mergeinfo for PATHS was modified in
    revision REV, returning the differences for added mergeinfo in
-   *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO.
-   If *PREFETCHED_CHANGES already contains the changed paths for
-   REV, use that.  Otherwise, request that data and return it in
-   *PREFETCHED_CHANGES. */
+   *ADDED_MERGEINFO and deleted mergeinfo in *DELETED_MERGEINFO. */
 static svn_error_t *
 get_combined_mergeinfo_changes(svn_mergeinfo_t *added_mergeinfo,
                                svn_mergeinfo_t *deleted_mergeinfo,
-                               apr_hash_t **prefetched_changes,
                                svn_fs_t *fs,
                                const apr_array_header_t *paths,
                                svn_revnum_t rev,
@@ -948,7 +858,6 @@ get_combined_mergeinfo_changes(svn_merge
   /* Fetch the mergeinfo changes for REV. */
   err = fs_mergeinfo_changed(&deleted_mergeinfo_catalog,
                              &added_mergeinfo_catalog,
-                             prefetched_changes,
                              fs, rev,
                              scratch_pool, scratch_pool);
   if (err)
@@ -1137,38 +1046,31 @@ get_combined_mergeinfo_changes(svn_merge
 
 /* Fill LOG_ENTRY with history information in FS at REV. */
 static svn_error_t *
-fill_log_entry(svn_log_entry_t *log_entry,
+fill_log_entry(svn_repos_log_entry_t *log_entry,
                svn_revnum_t rev,
                svn_fs_t *fs,
-               apr_hash_t *prefetched_changes,
-               svn_boolean_t discover_changed_paths,
                const apr_array_header_t *revprops,
-               svn_repos_authz_func_t authz_read_func,
-               void *authz_read_baton,
+               const log_callbacks_t *callbacks,
                apr_pool_t *pool)
 {
-  apr_hash_t *r_props, *changed_paths = NULL;
+  apr_hash_t *r_props;
   svn_boolean_t get_revprops = TRUE, censor_revprops = FALSE;
   svn_boolean_t want_revprops = !revprops || revprops->nelts;
 
   /* Discover changed paths if the user requested them
      or if we need to check that they are readable. */
   if ((rev > 0)
-      && (authz_read_func || discover_changed_paths))
+      && (callbacks->authz_read_func || callbacks->path_change_receiver))
     {
       svn_fs_root_t *newroot;
       svn_repos_revision_access_level_t access_level;
 
       SVN_ERR(svn_fs_revision_root(&newroot, fs, rev, pool));
-      SVN_ERR(detect_changed(&access_level, &changed_paths,
-                             newroot, fs, prefetched_changes,
-                             authz_read_func, authz_read_baton,
-                             pool));
+      SVN_ERR(detect_changed(&access_level, newroot, fs, callbacks, pool));
 
       if (access_level == svn_repos_revision_access_none)
         {
           /* All changed-paths are unreadable, so clear all fields. */
-          changed_paths = NULL;
           get_revprops = FALSE;
         }
       else if (access_level == svn_repos_revision_access_partial)
@@ -1178,17 +1080,13 @@ fill_log_entry(svn_log_entry_t *log_entr
              missing from the hash.) */
           censor_revprops = TRUE;
         }
-
-      /* It may be the case that an authz func was passed in, but
-         the user still doesn't want to see any changed-paths. */
-      if (! discover_changed_paths)
-        changed_paths = NULL;
     }
 
   if (get_revprops && want_revprops)
     {
       /* User is allowed to see at least some revprops. */
-      SVN_ERR(svn_fs_revision_proplist(&r_props, fs, rev, pool));
+      SVN_ERR(svn_fs_revision_proplist2(&r_props, fs, rev, FALSE, pool,
+                                        pool));
       if (revprops == NULL)
         {
           /* Requested all revprops... */
@@ -1216,9 +1114,9 @@ fill_log_entry(svn_log_entry_t *log_entr
              we want static initialization here and must therefore emulate
              strlen(x) by sizeof(x)-1. */
           static const svn_string_t svn_prop_revision_author
-            = {SVN_PROP_REVISION_AUTHOR, sizeof(SVN_PROP_REVISION_AUTHOR)-1};
+            = SVN__STATIC_STRING(SVN_PROP_REVISION_AUTHOR);
           static const svn_string_t svn_prop_revision_date
-            = {SVN_PROP_REVISION_DATE, sizeof(SVN_PROP_REVISION_DATE)-1};
+            = SVN__STATIC_STRING(SVN_PROP_REVISION_DATE);
 
           /* often only the standard revprops got requested and delivered.
              In that case, we can simply pass the hash on. */
@@ -1258,20 +1156,83 @@ fill_log_entry(svn_log_entry_t *log_entr
         }
     }
 
-  log_entry->changed_paths = changed_paths;
-  log_entry->changed_paths2 = changed_paths;
   log_entry->revision = rev;
 
   return SVN_NO_ERROR;
 }
 
-/* Send a log message for REV to RECEIVER with its RECEIVER_BATON.
+/* Baton type to be used with the interesting_merge callback. */
+typedef struct interesting_merge_baton_t
+{
+  /* What we are looking for. */
+  svn_revnum_t rev;
+  svn_mergeinfo_t log_target_history_as_mergeinfo;
+
+  /* Set to TRUE if we found it. */
+  svn_boolean_t found_rev_of_interest;
+
+  /* We need to invoke this user-provided callback if not NULL. */
+  svn_repos_path_change_receiver_t inner;
+  void *inner_baton;
+} interesting_merge_baton_t;
+
+/* Implements svn_repos_path_change_receiver_t. 
+ * *BATON is a interesting_merge_baton_t.
+ *
+ * If BATON->REV a merged revision that is not already part of
+ * BATON->LOG_TARGET_HISTORY_AS_MERGEINFO, set BATON->FOUND_REV_OF_INTEREST.
+ */
+static svn_error_t *
+interesting_merge(void *baton,
+                  svn_fs_path_change3_t *change,
+                  apr_pool_t *scratch_pool)
+{
+  interesting_merge_baton_t *b = baton;
+  apr_hash_index_t *hi;
+
+  if (b->inner)
+    SVN_ERR(b->inner(b->inner_baton, change, scratch_pool));
+
+  if (b->found_rev_of_interest)
+    return SVN_NO_ERROR;
+
+  /* Look at each path on the log target's mergeinfo. */
+  for (hi = apr_hash_first(scratch_pool, b->log_target_history_as_mergeinfo);
+       hi;
+       hi = apr_hash_next(hi))
+    {
+      const char *mergeinfo_path = apr_hash_this_key(hi);
+      svn_rangelist_t *rangelist = apr_hash_this_val(hi);
+
+      /* Check whether CHANGED_PATH at revision REV is a child of
+          a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
+      if (svn_fspath__skip_ancestor(mergeinfo_path, change->path.data))
+        {
+          int i;
+
+          for (i = 0; i < rangelist->nelts; i++)
+            {
+              svn_merge_range_t *range
+                = APR_ARRAY_IDX(rangelist, i, svn_merge_range_t *);
+              if (b->rev > range->start && b->rev <= range->end)
+               return SVN_NO_ERROR;
+            }
+        }
+    }
+
+  b->found_rev_of_interest = TRUE;
+
+  return SVN_NO_ERROR;
+}
+
+/* Send a log message for REV to the CALLBACKS.
 
    FS is used with REV to fetch the interesting history information,
    such as changed paths, revprops, etc.
 
-   The detect_changed function is used if either AUTHZ_READ_FUNC is
-   not NULL, or if DISCOVER_CHANGED_PATHS is TRUE.  See it for details.
+   The detect_changed function is used if either CALLBACKS->AUTHZ_READ_FUNC
+   is not NULL, or if CALLBACKS->PATH_CHANGE_RECEIVER is not NULL.
+   See it for details.
 
    If DESCENDING_ORDER is true, send child messages in descending order.
 
@@ -1293,107 +1254,51 @@ fill_log_entry(svn_log_entry_t *log_entr
 static svn_error_t *
 send_log(svn_revnum_t rev,
          svn_fs_t *fs,
-         apr_hash_t *prefetched_changes,
          svn_mergeinfo_t log_target_history_as_mergeinfo,
          svn_bit_array__t *nested_merges,
-         svn_boolean_t discover_changed_paths,
          svn_boolean_t subtractive_merge,
          svn_boolean_t handling_merged_revision,
          const apr_array_header_t *revprops,
          svn_boolean_t has_children,
-         svn_log_entry_receiver_t receiver,
-         void *receiver_baton,
-         svn_repos_authz_func_t authz_read_func,
-         void *authz_read_baton,
+         const log_callbacks_t *callbacks,
          apr_pool_t *pool)
 {
-  svn_log_entry_t *log_entry;
-  /* Assume we want to send the log for REV. */
-  svn_boolean_t found_rev_of_interest = TRUE;
-
-  log_entry = svn_log_entry_create(pool);
-  SVN_ERR(fill_log_entry(log_entry, rev, fs, prefetched_changes,
-                         discover_changed_paths || handling_merged_revision,
-                         revprops, authz_read_func, authz_read_baton, pool));
-  log_entry->has_children = has_children;
-  log_entry->subtractive_merge = subtractive_merge;
+  svn_repos_log_entry_t log_entry = { 0 };
+  log_callbacks_t my_callbacks = *callbacks;
+
+  interesting_merge_baton_t baton;
 
   /* Is REV a merged revision that is already part of
      LOG_TARGET_HISTORY_AS_MERGEINFO?  If so then there is no
-     need to send it, since it already was (or will be) sent. */
+     need to send it, since it already was (or will be) sent.
+
+     Use our callback to snoop through the changes. */
   if (handling_merged_revision
-      && log_entry->changed_paths2
       && log_target_history_as_mergeinfo
       && apr_hash_count(log_target_history_as_mergeinfo))
     {
-      apr_hash_index_t *hi;
-      apr_pool_t *iterpool = svn_pool_create(pool);
-
-      /* REV was merged in, but it might already be part of the log target's
-         natural history, so change our starting assumption. */
-      found_rev_of_interest = FALSE;
-
-      /* Look at each changed path in REV. */
-      for (hi = apr_hash_first(pool, log_entry->changed_paths2);
-           hi;
-           hi = apr_hash_next(hi))
-        {
-          svn_boolean_t path_is_in_history = FALSE;
-          const char *changed_path = apr_hash_this_key(hi);
-          apr_hash_index_t *hi2;
-
-          /* Look at each path on the log target's mergeinfo. */
-          for (hi2 = apr_hash_first(iterpool,
-                                    log_target_history_as_mergeinfo);
-               hi2;
-               hi2 = apr_hash_next(hi2))
-            {
-              const char *mergeinfo_path = apr_hash_this_key(hi2);
-              svn_rangelist_t *rangelist = apr_hash_this_val(hi2);
-
-              /* Check whether CHANGED_PATH at revision REV is a child of
-                 a (path, revision) tuple in LOG_TARGET_HISTORY_AS_MERGEINFO. */
-              if (svn_fspath__skip_ancestor(mergeinfo_path, changed_path))
-                {
-                  int i;
-
-                  for (i = 0; i < rangelist->nelts; i++)
-                    {
-                      svn_merge_range_t *range =
-                        APR_ARRAY_IDX(rangelist, i,
-                                      svn_merge_range_t *);
-                      if (rev > range->start && rev <= range->end)
-                        {
-                          path_is_in_history = TRUE;
-                          break;
-                        }
-                    }
-                }
-              if (path_is_in_history)
-                break;
-            }
-          svn_pool_clear(iterpool);
-
-          if (!path_is_in_history)
-            {
-              /* If even one path in LOG_ENTRY->CHANGED_PATHS2 is not part of
-                 LOG_TARGET_HISTORY_AS_MERGEINFO, then we want to send the
-                 log for REV. */
-              found_rev_of_interest = TRUE;
-              break;
-            }
-        }
-      svn_pool_destroy(iterpool);
+      baton.found_rev_of_interest = FALSE;
+      baton.rev = rev;
+      baton.log_target_history_as_mergeinfo = log_target_history_as_mergeinfo;
+      baton.inner = callbacks->path_change_receiver;
+      baton.inner_baton = callbacks->path_change_receiver_baton;
+
+      my_callbacks.path_change_receiver = interesting_merge;
+      my_callbacks.path_change_receiver_baton = &baton;
+      callbacks = &my_callbacks;
+    }
+  else
+    {
+      baton.found_rev_of_interest = TRUE;
     }
 
-  /* If we only got changed paths the sake of detecting redundant merged
-     revisions, then be sure we don't send that info to the receiver. */
-  if (!discover_changed_paths && handling_merged_revision)
-    log_entry->changed_paths = log_entry->changed_paths2 = NULL;
+  SVN_ERR(fill_log_entry(&log_entry, rev, fs, revprops, callbacks, pool));
+  log_entry.has_children = has_children;
+  log_entry.subtractive_merge = subtractive_merge;
 
   /* Send the entry to the receiver, unless it is a redundant merged
      revision. */
-  if (found_rev_of_interest)
+  if (baton.found_rev_of_interest)
     {
       apr_pool_t *scratch_pool;
 
@@ -1417,7 +1322,8 @@ send_log(svn_revnum_t rev,
       /* Pass a scratch pool to ensure no temporary state stored
          by the receiver callback persists. */
       scratch_pool = svn_pool_create(pool);
-      SVN_ERR(receiver(receiver_baton, log_entry, scratch_pool));
+      SVN_ERR(callbacks->revision_receiver(callbacks->revision_receiver_baton,
+                                           &log_entry, scratch_pool));
       svn_pool_destroy(scratch_pool);
     }
 
@@ -1766,7 +1672,6 @@ do_logs(svn_fs_t *fs,
         svn_revnum_t hist_start,
         svn_revnum_t hist_end,
         int limit,
-        svn_boolean_t discover_changed_paths,
         svn_boolean_t strict_node_history,
         svn_boolean_t include_merged_revisions,
         svn_boolean_t handling_merged_revisions,
@@ -1774,10 +1679,7 @@ do_logs(svn_fs_t *fs,
         svn_boolean_t ignore_missing_locations,
         const apr_array_header_t *revprops,
         svn_boolean_t descending_order,
-        svn_log_entry_receiver_t receiver,
-        void *receiver_baton,
-        svn_repos_authz_func_t authz_read_func,
-        void *authz_read_baton,
+        log_callbacks_t *callbacks,
         apr_pool_t *pool);
 
 /* Comparator function for handle_merged_revisions().  Sorts path_list_range
@@ -1819,17 +1721,13 @@ handle_merged_revisions(svn_revnum_t rev
                         svn_mergeinfo_t processed,
                         svn_mergeinfo_t added_mergeinfo,
                         svn_mergeinfo_t deleted_mergeinfo,
-                        svn_boolean_t discover_changed_paths,
                         svn_boolean_t strict_node_history,
                         const apr_array_header_t *revprops,
-                        svn_log_entry_receiver_t receiver,
-                        void *receiver_baton,
-                        svn_repos_authz_func_t authz_read_func,
-                        void *authz_read_baton,
+                        log_callbacks_t *callbacks,
                         apr_pool_t *pool)
 {
   apr_array_header_t *combined_list = NULL;
-  svn_log_entry_t *empty_log_entry;
+  svn_repos_log_entry_t empty_log_entry = { 0 };
   apr_pool_t *iterpool;
   int i;
 
@@ -1860,17 +1758,16 @@ handle_merged_revisions(svn_revnum_t rev
       SVN_ERR(do_logs(fs, pl_range->paths, log_target_history_as_mergeinfo,
                       processed, nested_merges,
                       pl_range->range.start, pl_range->range.end, 0,
-                      discover_changed_paths, strict_node_history,
+                      strict_node_history,
                       TRUE, pl_range->reverse_merge, TRUE, TRUE,
-                      revprops, TRUE, receiver, receiver_baton,
-                      authz_read_func, authz_read_baton, iterpool));
+                      revprops, TRUE, callbacks, iterpool));
     }
   svn_pool_destroy(iterpool);
 
   /* Send the empty revision.  */
-  empty_log_entry = svn_log_entry_create(pool);
-  empty_log_entry->revision = SVN_INVALID_REVNUM;
-  return (*receiver)(receiver_baton, empty_log_entry, pool);
+  empty_log_entry.revision = SVN_INVALID_REVNUM;
+  return (callbacks->revision_receiver)(callbacks->revision_receiver_baton,
+                                        &empty_log_entry, pool);
 }
 
 /* This is used by do_logs to differentiate between forward and
@@ -1987,10 +1884,10 @@ store_search(svn_mergeinfo_t processed,
   return SVN_NO_ERROR;
 }
 
-/* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke
-   RECEIVER with RECEIVER_BATON on them.  If DESCENDING_ORDER is TRUE, send
-   the logs back as we find them, else buffer the logs and send them back
-   in youngest->oldest order.
+/* Find logs for PATHS from HIST_START to HIST_END in FS, and invoke the
+   CALLBACKS on them.  If DESCENDING_ORDER is TRUE, send the logs back as
+   we find them, else buffer the logs and send them back in youngest->oldest
+   order.
 
    If IGNORE_MISSING_LOCATIONS is set, don't treat requests for bogus
    repository locations as fatal -- just ignore them.
@@ -2026,7 +1923,6 @@ do_logs(svn_fs_t *fs,
         svn_revnum_t hist_start,
         svn_revnum_t hist_end,
         int limit,
-        svn_boolean_t discover_changed_paths,
         svn_boolean_t strict_node_history,
         svn_boolean_t include_merged_revisions,
         svn_boolean_t subtractive_merge,
@@ -2034,10 +1930,7 @@ do_logs(svn_fs_t *fs,
         svn_boolean_t ignore_missing_locations,
         const apr_array_header_t *revprops,
         svn_boolean_t descending_order,
-        svn_log_entry_receiver_t receiver,
-        void *receiver_baton,
-        svn_repos_authz_func_t authz_read_func,
-        void *authz_read_baton,
+        log_callbacks_t *callbacks,
         apr_pool_t *pool)
 {
   apr_pool_t *iterpool, *iterpool2;
@@ -2070,7 +1963,8 @@ do_logs(svn_fs_t *fs,
      revisions contain real changes to at least one of our paths.  */
   SVN_ERR(get_path_histories(&histories, fs, paths, hist_start, hist_end,
                              strict_node_history, ignore_missing_locations,
-                             authz_read_func, authz_read_baton, pool));
+                             callbacks->authz_read_func,
+                             callbacks->authz_read_baton, pool));
 
   /* Loop through all the revisions in the range and add any
      where a path was changed to the array, or if they wanted
@@ -2094,9 +1988,10 @@ do_logs(svn_fs_t *fs,
 
           /* Check history for this path in current rev. */
           SVN_ERR(check_history(&changed, info, fs, current,
-                                strict_node_history, authz_read_func,
-                                authz_read_baton, hist_start, pool,
-                                iterpool2));
+                                strict_node_history,
+                                callbacks->authz_read_func,
+                                callbacks->authz_read_baton,
+                                hist_start, pool, iterpool2));
           if (! info->done)
             any_histories_left = TRUE;
         }
@@ -2109,7 +2004,6 @@ do_logs(svn_fs_t *fs,
           svn_mergeinfo_t added_mergeinfo = NULL;
           svn_mergeinfo_t deleted_mergeinfo = NULL;
           svn_boolean_t has_children = FALSE;
-          apr_hash_t *changes = NULL;
 
           /* If we're including merged revisions, we need to calculate
              the mergeinfo deltas committed in this revision to our
@@ -2130,7 +2024,6 @@ do_logs(svn_fs_t *fs,
                 }
               SVN_ERR(get_combined_mergeinfo_changes(&added_mergeinfo,
                                                      &deleted_mergeinfo,
-                                                     &changes,
                                                      fs, cur_paths,
                                                      current,
                                                      iterpool, iterpool));
@@ -2143,13 +2036,10 @@ do_logs(svn_fs_t *fs,
              in anyway). */
           if (descending_order)
             {
-              SVN_ERR(send_log(current, fs, changes,
+              SVN_ERR(send_log(current, fs,
                                log_target_history_as_mergeinfo, nested_merges,
-                               discover_changed_paths,
                                subtractive_merge, handling_merged_revisions,
-                               revprops, has_children,
-                               receiver, receiver_baton,
-                               authz_read_func, authz_read_baton, iterpool));
+                               revprops, has_children, callbacks, iterpool));
 
               if (has_children) /* Implies include_merged_revisions == TRUE */
                 {
@@ -2168,12 +2058,9 @@ do_logs(svn_fs_t *fs,
                     log_target_history_as_mergeinfo, nested_merges,
                     processed,
                     added_mergeinfo, deleted_mergeinfo,
-                    discover_changed_paths,
                     strict_node_history,
                     revprops,
-                    receiver, receiver_baton,
-                    authz_read_func,
-                    authz_read_baton,
+                    callbacks,
                     iterpool));
                 }
               if (limit && ++send_count >= limit)
@@ -2192,7 +2079,7 @@ do_logs(svn_fs_t *fs,
               if (added_mergeinfo || deleted_mergeinfo)
                 {
                   svn_revnum_t *cur_rev =
-                    apr_pmemdup(pool, &current, sizeof(cur_rev));
+                    apr_pmemdup(pool, &current, sizeof(*cur_rev));
                   struct added_deleted_mergeinfo *add_and_del_mergeinfo =
                     apr_palloc(pool, sizeof(*add_and_del_mergeinfo));
 
@@ -2248,13 +2135,10 @@ do_logs(svn_fs_t *fs,
                               || apr_hash_count(deleted_mergeinfo) > 0);
             }
 
-          SVN_ERR(send_log(current, fs, NULL,
+          SVN_ERR(send_log(current, fs,
                            log_target_history_as_mergeinfo, nested_merges,
-                           discover_changed_paths, subtractive_merge,
-                           handling_merged_revisions,
-                           revprops, has_children,
-                           receiver, receiver_baton, authz_read_func,
-                           authz_read_baton, iterpool));
+                           subtractive_merge, handling_merged_revisions,
+                           revprops, has_children, callbacks, iterpool));
           if (has_children)
             {
               if (!nested_merges)
@@ -2269,12 +2153,8 @@ do_logs(svn_fs_t *fs,
                                               processed,
                                               added_mergeinfo,
                                               deleted_mergeinfo,
-                                              discover_changed_paths,
                                               strict_node_history,
-                                              revprops,
-                                              receiver, receiver_baton,
-                                              authz_read_func,
-                                              authz_read_baton,
+                                              revprops, callbacks,
                                               iterpool));
             }
           if (limit && i + 1 >= limit)
@@ -2373,41 +2253,56 @@ get_paths_history_as_mergeinfo(svn_merge
 }
 
 svn_error_t *
-svn_repos_get_logs4(svn_repos_t *repos,
+svn_repos_get_logs5(svn_repos_t *repos,
                     const apr_array_header_t *paths,
                     svn_revnum_t start,
                     svn_revnum_t end,
                     int limit,
-                    svn_boolean_t discover_changed_paths,
                     svn_boolean_t strict_node_history,
                     svn_boolean_t include_merged_revisions,
                     const apr_array_header_t *revprops,
                     svn_repos_authz_func_t authz_read_func,
                     void *authz_read_baton,
-                    svn_log_entry_receiver_t receiver,
-                    void *receiver_baton,
-                    apr_pool_t *pool)
+                    svn_repos_path_change_receiver_t path_change_receiver,
+                    void *path_change_receiver_baton,
+                    svn_repos_log_entry_receiver_t revision_receiver,
+                    void *revision_receiver_baton,
+                    apr_pool_t *scratch_pool)
 {
   svn_revnum_t head = SVN_INVALID_REVNUM;
   svn_fs_t *fs = repos->fs;
   svn_boolean_t descending_order;
   svn_mergeinfo_t paths_history_mergeinfo = NULL;
+  log_callbacks_t callbacks;
+
+  callbacks.path_change_receiver = path_change_receiver;
+  callbacks.path_change_receiver_baton = path_change_receiver_baton;
+  callbacks.revision_receiver = revision_receiver;
+  callbacks.revision_receiver_baton = revision_receiver_baton;
+  callbacks.authz_read_func = authz_read_func;
+  callbacks.authz_read_baton = authz_read_baton;
 
   if (revprops)
     {
       int i;
       apr_array_header_t *new_revprops
-        = apr_array_make(pool, revprops->nelts, sizeof(svn_string_t *));
+        = apr_array_make(scratch_pool, revprops->nelts,
+                         sizeof(svn_string_t *));
 
       for (i = 0; i < revprops->nelts; ++i)
         APR_ARRAY_PUSH(new_revprops, svn_string_t *)
-          = svn_string_create(APR_ARRAY_IDX(revprops, i, const char *), pool);
+          = svn_string_create(APR_ARRAY_IDX(revprops, i, const char *),
+                              scratch_pool);
 
       revprops = new_revprops;
     }
 
+  /* Make sure we catch up on the latest revprop changes.  This is the only
+   * time we will refresh the revprop data in this query. */
+  SVN_ERR(svn_fs_refresh_revision_props(fs, scratch_pool));
+
   /* Setup log range. */
-  SVN_ERR(svn_fs_youngest_rev(&head, fs, pool));
+  SVN_ERR(svn_fs_youngest_rev(&head, fs, scratch_pool));
 
   if (! SVN_IS_VALID_REVNUM(start))
     start = head;
@@ -2436,7 +2331,7 @@ svn_repos_get_logs4(svn_repos_t *repos,
     }
 
   if (! paths)
-    paths = apr_array_make(pool, 0, sizeof(const char *));
+    paths = apr_array_make(scratch_pool, 0, sizeof(const char *));
 
   /* If we're not including merged revisions, and we were given no
      paths or a single empty (or "/") path, then we can bypass a bunch
@@ -2451,7 +2346,7 @@ svn_repos_get_logs4(svn_repos_t *repos,
     {
       apr_uint64_t send_count = 0;
       int i;
-      apr_pool_t *iterpool = svn_pool_create(pool);
+      apr_pool_t *iterpool = svn_pool_create(scratch_pool);
 
       /* If we are provided an authz callback function, use it to
          verify that the user has read access to the root path in the
@@ -2467,9 +2362,10 @@ svn_repos_get_logs4(svn_repos_t *repos,
           svn_fs_root_t *rev_root;
 
           SVN_ERR(svn_fs_revision_root(&rev_root, fs,
-                                       descending_order ? end : start, pool));
+                                       descending_order ? end : start,
+                                       scratch_pool));
           SVN_ERR(authz_read_func(&readable, rev_root, "",
-                                  authz_read_baton, pool));
+                                  authz_read_baton, scratch_pool));
           if (! readable)
             return svn_error_create(SVN_ERR_AUTHZ_UNREADABLE, NULL, NULL);
         }
@@ -2487,10 +2383,9 @@ svn_repos_get_logs4(svn_repos_t *repos,
             rev = end - i;
           else
             rev = start + i;
-          SVN_ERR(send_log(rev, fs, NULL, NULL, NULL,
-                           discover_changed_paths, FALSE,
-                           FALSE, revprops, FALSE, receiver, receiver_baton,
-                           authz_read_func, authz_read_baton, iterpool));
+          SVN_ERR(send_log(rev, fs, NULL, NULL,
+                           FALSE, FALSE, revprops, FALSE,
+                           &callbacks, iterpool));
         }
       svn_pool_destroy(iterpool);
 
@@ -2504,19 +2399,158 @@ svn_repos_get_logs4(svn_repos_t *repos,
      http://subversion.tigris.org/issues/show_bug.cgi?id=3650#desc5 */
   if (include_merged_revisions)
     {
-      apr_pool_t *subpool = svn_pool_create(pool);
+      apr_pool_t *subpool = svn_pool_create(scratch_pool);
 
       SVN_ERR(get_paths_history_as_mergeinfo(&paths_history_mergeinfo,
                                              repos, paths, start, end,
                                              authz_read_func,
                                              authz_read_baton,
-                                             pool, subpool));
+                                             scratch_pool, subpool));
       svn_pool_destroy(subpool);
     }
 
-  return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL, start, end,
-                 limit, discover_changed_paths, strict_node_history,
+  return do_logs(repos->fs, paths, paths_history_mergeinfo, NULL, NULL,
+                 start, end, limit, strict_node_history,
                  include_merged_revisions, FALSE, FALSE, FALSE,
-                 revprops, descending_order, receiver, receiver_baton,
-                 authz_read_func, authz_read_baton, pool);
+                 revprops, descending_order, &callbacks, scratch_pool);
+}
+
+/* Baton type to be used with both log4 compatibility callbacks.
+ * For each revision, we collect the CHANGES and then pass them
+ * on to INNER. */
+typedef struct log_entry_receiver_baton_t
+{
+  /* Pool to use to allocate CHANGES and its entries.
+   * Gets cleared after each revision. */
+  apr_pool_t *changes_pool;
+
+  /* Path changes reported so far for the current revision.
+   * Will be NULL before the first item gets added and will be reset
+   * to NULL after the INNER callback has returned. */
+  apr_hash_t *changes;
+
+  /* User-provided callback to send the log entry to. */
+  svn_log_entry_receiver_t inner;
+  void *inner_baton;
+} log_entry_receiver_baton_t;
+
+/* Return the action character (see svn_log_changed_path2_t) for KIND.
+ * Returns 0 for invalid KINDs. */
+static char
+path_change_kind_to_char(svn_fs_path_change_kind_t kind)
+{
+  const char symbol[] = "MADR";
+
+  if (kind < svn_fs_path_change_modify || kind > svn_fs_path_change_replace)
+    return 0;
+
+  return symbol[kind];
+}
+
+/* Implement svn_repos_path_change_receiver_t.
+ * Convert CHANGE and add it to the CHANGES list in *BATON. */
+static svn_error_t *
+log4_path_change_receiver(void *baton,
+                          svn_repos_path_change_t *change,
+                          apr_pool_t *scratch_pool)
+{
+  log_entry_receiver_baton_t *b = baton;
+  svn_log_changed_path2_t *change_copy;
+  const char *path = apr_pstrmemdup(b->changes_pool, change->path.data,
+                                    change->path.len);
+
+  /* Create a deep copy of the temporary CHANGE struct. */
+  change_copy = svn_log_changed_path2_create(b->changes_pool);
+  change_copy->action = path_change_kind_to_char(change->change_kind);
+
+  if (change->copyfrom_path)
+    change_copy->copyfrom_path = apr_pstrdup(b->changes_pool,
+                                             change->copyfrom_path);
+
+  change_copy->copyfrom_rev = change->copyfrom_rev;
+  change_copy->node_kind = change->node_kind;
+  change_copy->text_modified = change->text_mod ? svn_tristate_true
+                                                : svn_tristate_false;
+  change_copy->props_modified = change->prop_mod ? svn_tristate_true
+                                                 : svn_tristate_false;
+
+  /* Auto-create the CHANGES container (happens for each first change
+   * in any revison. */
+  if (b->changes == NULL)
+    b->changes = svn_hash__make(b->changes_pool);
+
+  /* Add change to per-revision collection. */
+  apr_hash_set(b->changes, path, change->path.len, change_copy);
+
+  return SVN_NO_ERROR;
+}
+
+/* Implement svn_log_entry_receiver_t.
+ * Combine the data gathered in BATON for this revision and send it
+ * to the user-provided log4-compatible callback. */
+static svn_error_t *
+log4_entry_receiver(void *baton,
+                    svn_repos_log_entry_t *log_entry,
+                    apr_pool_t *scratch_pool)
+{
+  log_entry_receiver_baton_t *b = baton;
+  svn_log_entry_t *entry = svn_log_entry_create(scratch_pool);
+
+  /* Complete the ENTRY. */
+  entry->changed_paths = b->changes;
+  entry->revision = log_entry->revision;
+  entry->revprops = log_entry->revprops;
+  entry->has_children = log_entry->has_children;
+  entry->changed_paths2 = b->changes;
+  entry->non_inheritable = log_entry->non_inheritable;
+  entry->subtractive_merge = log_entry->subtractive_merge;
+
+  /* Invoke the log4-compatible callback. */
+  SVN_ERR(b->inner(b->inner_baton, entry, scratch_pool));
+
+  /* Release per-revision data. */
+  svn_pool_clear(b->changes_pool);
+  b->changes = NULL;
+
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_repos_get_logs4(svn_repos_t *repos,
+                    const apr_array_header_t *paths,
+                    svn_revnum_t start,
+                    svn_revnum_t end,
+                    int limit,
+                    svn_boolean_t discover_changed_paths,
+                    svn_boolean_t strict_node_history,
+                    svn_boolean_t include_merged_revisions,
+                    const apr_array_header_t *revprops,
+                    svn_repos_authz_func_t authz_read_func,
+                    void *authz_read_baton,
+                    svn_log_entry_receiver_t receiver,
+                    void *receiver_baton,
+                    apr_pool_t *pool)
+{
+  apr_pool_t *changes_pool = svn_pool_create(pool);
+
+  log_entry_receiver_baton_t baton;
+  baton.changes_pool = changes_pool;
+  baton.changes = NULL;
+  baton.inner = receiver;
+  baton.inner_baton = receiver_baton;
+
+  SVN_ERR(svn_repos_get_logs5(repos, paths, start, end, limit,
+                              strict_node_history,
+                              include_merged_revisions,
+                              revprops,
+                              authz_read_func, authz_read_baton,
+                              discover_changed_paths
+                                ? log4_path_change_receiver
+                                : NULL,
+                              &baton,
+                              log4_entry_receiver, &baton,
+                              pool));
+
+  svn_pool_destroy(changes_pool);
+  return SVN_NO_ERROR;
 }

Modified: subversion/branches/authzperf/subversion/libsvn_repos/replay.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_repos/replay.c?rev=1741682&r1=1741681&r2=1741682&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_repos/replay.c (original)
+++ subversion/branches/authzperf/subversion/libsvn_repos/replay.c Fri Apr 29 18:38:53 2016
@@ -198,7 +198,7 @@ add_subdir(svn_fs_root_t *source_root,
 
   for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
     {
-      svn_fs_path_change2_t *change;
+      svn_fs_path_change3_t *change;
       svn_boolean_t readable = TRUE;
       svn_fs_dirent_t *dent = apr_hash_this_val(hi);
       const char *copyfrom_path = NULL;
@@ -412,7 +412,7 @@ fill_copyfrom(svn_fs_root_t **copyfrom_r
               svn_revnum_t *copyfrom_rev,
               svn_boolean_t *src_readable,
               svn_fs_root_t *root,
-              svn_fs_path_change2_t *change,
+              svn_fs_path_change3_t *change,
               svn_repos_authz_func_t authz_read_func,
               void *authz_read_baton,
               const char *path,
@@ -463,7 +463,7 @@ path_driver_cb_func(void **dir_baton,
   const svn_delta_editor_t *editor = cb->editor;
   void *edit_baton = cb->edit_baton;
   svn_fs_root_t *root = cb->root;
-  svn_fs_path_change2_t *change;
+  svn_fs_path_change3_t *change;
   svn_boolean_t do_add = FALSE, do_delete = FALSE;
   void *file_baton = NULL;
   svn_revnum_t copyfrom_rev;
@@ -843,6 +843,80 @@ fetch_props_func(apr_hash_t **props,
 
 
 
+/* Retrieve the path changes under ROOT, filter them with AUTHZ_READ_FUNC
+   and AUTHZ_READ_BATON and return those that intersect with BASE_RELPATH.
+
+   The svn_fs_path_change3_t* will be returned in *CHANGED_PATHS, keyed by
+   their path.  The paths themselves are additionally returned in *PATHS.
+
+   Allocate the returned data in RESULT_POOL and use SCRATCH_POOL for
+   temporary allocations.
+ */
+static svn_error_t *
+get_relevant_changes(apr_hash_t **changed_paths,
+                     apr_array_header_t **paths,
+                     svn_fs_root_t *root,
+                     const char *base_relpath,
+                     svn_repos_authz_func_t authz_read_func,
+                     void *authz_read_baton,
+                     apr_pool_t *result_pool,
+                     apr_pool_t *scratch_pool)
+{
+  svn_fs_path_change_iterator_t *iterator;
+  svn_fs_path_change3_t *change;
+  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+
+  /* Fetch the paths changed under ROOT. */
+  SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, scratch_pool));
+  SVN_ERR(svn_fs_path_change_get(&change, iterator));
+
+  /* Make an array from the keys of our CHANGED_PATHS hash, and copy
+     the values into a new hash whose keys have no leading slashes. */
+  *paths = apr_array_make(result_pool, 16, sizeof(const char *));
+  *changed_paths = apr_hash_make(result_pool);
+  while (change)
+    {
+      const char *path = change->path.data;
+      apr_ssize_t keylen = change->path.len;
+      svn_boolean_t allowed = TRUE;
+
+      svn_pool_clear(iterpool);
+      if (authz_read_func)
+        SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
+                                iterpool));
+
+      if (allowed)
+        {
+          if (path[0] == '/')
+            {
+              path++;
+              keylen--;
+            }
+
+          /* If the base_path doesn't match the top directory of this path
+             we don't want anything to do with it... 
+             ...unless this was a change to one of the parent directories of
+             base_path. */
+          if (   svn_relpath_skip_ancestor(base_relpath, path)
+              || svn_relpath_skip_ancestor(path, base_relpath))
+            {
+              change = svn_fs_path_change3_dup(change, result_pool);
+              path = change->path.data;
+              if (path[0] == '/')
+                path++;
+
+              APR_ARRAY_PUSH(*paths, const char *) = path;
+              apr_hash_set(*changed_paths, path, keylen, change);
+            }
+        }
+
+      SVN_ERR(svn_fs_path_change_get(&change, iterator));
+    }
+
+  svn_pool_destroy(iterpool);
+  return SVN_NO_ERROR;
+}
+
 svn_error_t *
 svn_repos_replay2(svn_fs_root_t *root,
                   const char *base_path,
@@ -855,9 +929,7 @@ svn_repos_replay2(svn_fs_root_t *root,
                   apr_pool_t *pool)
 {
 #ifndef USE_EV2_IMPL
-  apr_hash_t *fs_changes;
   apr_hash_t *changed_paths;
-  apr_hash_index_t *hi;
   apr_array_header_t *paths;
   struct path_driver_cb_baton cb_baton;
 
@@ -869,54 +941,15 @@ svn_repos_replay2(svn_fs_root_t *root,
       return SVN_NO_ERROR;
     }
 
-  /* Fetch the paths changed under ROOT. */
-  SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, pool));
-
   if (! base_path)
     base_path = "";
   else if (base_path[0] == '/')
     ++base_path;
 
-  /* Make an array from the keys of our CHANGED_PATHS hash, and copy
-     the values into a new hash whose keys have no leading slashes. */
-  paths = apr_array_make(pool, apr_hash_count(fs_changes),
-                         sizeof(const char *));
-  changed_paths = apr_hash_make(pool);
-  for (hi = apr_hash_first(pool, fs_changes); hi; hi = apr_hash_next(hi))
-    {
-      const char *path = apr_hash_this_key(hi);
-      apr_ssize_t keylen = apr_hash_this_key_len(hi);
-      svn_fs_path_change2_t *change = apr_hash_this_val(hi);
-      svn_boolean_t allowed = TRUE;
-
-      if (authz_read_func)
-        SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
-                                pool));
-
-      if (allowed)
-        {
-          if (path[0] == '/')
-            {
-              path++;
-              keylen--;
-            }
-
-          /* If the base_path doesn't match the top directory of this path
-             we don't want anything to do with it... */
-          if (svn_relpath_skip_ancestor(base_path, path) != NULL)
-            {
-              APR_ARRAY_PUSH(paths, const char *) = path;
-              apr_hash_set(changed_paths, path, keylen, change);
-            }
-          /* ...unless this was a change to one of the parent directories of
-             base_path. */
-          else if (svn_relpath_skip_ancestor(path, base_path) != NULL)
-            {
-              APR_ARRAY_PUSH(paths, const char *) = path;
-              apr_hash_set(changed_paths, path, keylen, change);
-            }
-        }
-    }
+  /* Fetch the paths changed under ROOT. */
+  SVN_ERR(get_relevant_changes(&changed_paths, &paths, root, base_path,
+                               authz_read_func, authz_read_baton,
+                               pool, pool));
 
   /* If we were not given a low water mark, assume that everything is there,
      all the way back to revision 0. */
@@ -1062,7 +1095,7 @@ add_subdir_ev2(svn_fs_root_t *source_roo
 
   for (hi = apr_hash_first(scratch_pool, dirents); hi; hi = apr_hash_next(hi))
     {
-      svn_fs_path_change2_t *change;
+      svn_fs_path_change3_t *change;
       svn_boolean_t readable = TRUE;
       svn_fs_dirent_t *dent = apr_hash_this_val(hi);
       const char *copyfrom_path = NULL;
@@ -1190,7 +1223,7 @@ replay_node(svn_fs_root_t *root,
             apr_pool_t *result_pool,
             apr_pool_t *scratch_pool)
 {
-  svn_fs_path_change2_t *change;
+  svn_fs_path_change3_t *change;
   svn_boolean_t do_add = FALSE;
   svn_boolean_t do_delete = FALSE;
   svn_revnum_t copyfrom_rev;
@@ -1485,9 +1518,7 @@ svn_repos__replay_ev2(svn_fs_root_t *roo
                       void *authz_read_baton,
                       apr_pool_t *scratch_pool)
 {
-  apr_hash_t *fs_changes;
   apr_hash_t *changed_paths;
-  apr_hash_index_t *hi;
   apr_array_header_t *paths;
   apr_array_header_t *copies;
   apr_pool_t *iterpool;
@@ -1505,49 +1536,10 @@ svn_repos__replay_ev2(svn_fs_root_t *roo
     }
 
   /* Fetch the paths changed under ROOT. */
-  SVN_ERR(svn_fs_paths_changed2(&fs_changes, root, scratch_pool));
-
-  /* Make an array from the keys of our CHANGED_PATHS hash, and copy
-     the values into a new hash whose keys have no leading slashes. */
-  paths = apr_array_make(scratch_pool, apr_hash_count(fs_changes),
-                         sizeof(const char *));
-  changed_paths = apr_hash_make(scratch_pool);
-  for (hi = apr_hash_first(scratch_pool, fs_changes); hi;
-        hi = apr_hash_next(hi))
-    {
-      const char *path = apr_hash_this_key(hi);
-      apr_ssize_t keylen = apr_hash_this_key_len(hi);
-      svn_fs_path_change2_t *change = apr_hash_this_val(hi);
-      svn_boolean_t allowed = TRUE;
-
-      if (authz_read_func)
-        SVN_ERR(authz_read_func(&allowed, root, path, authz_read_baton,
-                                scratch_pool));
-
-      if (allowed)
-        {
-          if (path[0] == '/')
-            {
-              path++;
-              keylen--;
-            }
-
-          /* If the base_path doesn't match the top directory of this path
-             we don't want anything to do with it... */
-          if (svn_relpath_skip_ancestor(base_repos_relpath, path) != NULL)
-            {
-              APR_ARRAY_PUSH(paths, const char *) = path;
-              apr_hash_set(changed_paths, path, keylen, change);
-            }
-          /* ...unless this was a change to one of the parent directories of
-             base_path. */
-          else if (svn_relpath_skip_ancestor(path, base_repos_relpath) != NULL)
-            {
-              APR_ARRAY_PUSH(paths, const char *) = path;
-              apr_hash_set(changed_paths, path, keylen, change);
-            }
-        }
-    }
+  SVN_ERR(get_relevant_changes(&changed_paths, &paths, root,
+                               base_repos_relpath,
+                               authz_read_func, authz_read_baton,
+                               scratch_pool, scratch_pool));
 
   /* If we were not given a low water mark, assume that everything is there,
      all the way back to revision 0. */

Modified: subversion/branches/authzperf/subversion/libsvn_repos/reporter.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_repos/reporter.c?rev=1741682&r1=1741681&r2=1741682&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_repos/reporter.c (original)
+++ subversion/branches/authzperf/subversion/libsvn_repos/reporter.c Fri Apr 29 18:38:53 2016
@@ -481,10 +481,12 @@ get_revision_info(report_baton_t *b,
     {
       /* Info is not available, yet.
          Get all revprops. */
-      SVN_ERR(svn_fs_revision_proplist(&r_props,
-                                       b->repos->fs,
-                                       rev,
-                                       scratch_pool));
+      SVN_ERR(svn_fs_revision_proplist2(&r_props,
+                                        b->repos->fs,
+                                        rev,
+                                        FALSE,
+                                        scratch_pool,
+                                        scratch_pool));
 
       /* Extract the committed-date. */
       cdate = svn_hash_gets(r_props, SVN_PROP_REVISION_DATE);
@@ -520,19 +522,13 @@ delta_proplists(report_baton_t *b, svn_r
 {
   svn_fs_root_t *s_root;
   apr_hash_t *s_props = NULL, *t_props;
-  apr_array_header_t *prop_diffs;
-  int i;
   svn_revnum_t crev;
-  revision_info_t *revision_info;
-  svn_boolean_t changed;
-  const svn_prop_t *pc;
-  svn_lock_t *lock;
-  apr_hash_index_t *hi;
 
   /* Fetch the created-rev and send entry props. */
   SVN_ERR(svn_fs_node_created_rev(&crev, b->t_root, t_path, pool));
   if (SVN_IS_VALID_REVNUM(crev))
     {
+      revision_info_t *revision_info;
       /* convert committed-rev to  string */
       char buf[SVN_INT64_BUFFER_SIZE];
       svn_string_t cr_str;
@@ -563,6 +559,7 @@ delta_proplists(report_baton_t *b, svn_r
   /* Update lock properties. */
   if (lock_token)
     {
+      svn_lock_t *lock;
       SVN_ERR(svn_fs_get_lock(&lock, b->repos->fs, t_path, pool));
 
       /* Delete a defunct lock. */
@@ -573,6 +570,7 @@ delta_proplists(report_baton_t *b, svn_r
 
   if (s_path)
     {
+      svn_boolean_t changed;
       SVN_ERR(get_source_root(b, &s_root, s_rev));
 
       /* Is this deltification worth our time? */
@@ -590,16 +588,20 @@ delta_proplists(report_baton_t *b, svn_r
 
   if (s_props && apr_hash_count(s_props))
     {
+      apr_array_header_t *prop_diffs;
+      int i;
+
       /* Now transmit the differences. */
       SVN_ERR(svn_prop_diffs(&prop_diffs, t_props, s_props, pool));
       for (i = 0; i < prop_diffs->nelts; i++)
         {
-          pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
+          const svn_prop_t *pc = &APR_ARRAY_IDX(prop_diffs, i, svn_prop_t);
           SVN_ERR(change_fn(b, object, pc->name, pc->value, pool));
         }
     }
   else if (apr_hash_count(t_props))
     {
+      apr_hash_index_t *hi;
       /* So source, i.e. all new.  Transmit all target props. */
       for (hi = apr_hash_first(pool, t_props); hi; hi = apr_hash_next(hi))
         {
@@ -669,7 +671,6 @@ delta_files(report_baton_t *b, void *fil
             const char *s_path, const char *t_path, const char *lock_token,
             apr_pool_t *pool)
 {
-  svn_boolean_t changed;
   svn_fs_root_t *s_root = NULL;
   svn_txdelta_stream_t *dstream = NULL;
   svn_checksum_t *s_checksum;
@@ -683,14 +684,15 @@ delta_files(report_baton_t *b, void *fil
 
   if (s_path)
     {
+      svn_boolean_t changed;
       SVN_ERR(get_source_root(b, &s_root, s_rev));
 
       /* We're not interested in the theoretical difference between "has
          contents which have not changed with respect to" and "has the same
          actual contents as" when sending text-deltas.  If we know the
          delta is an empty one, we avoiding sending it in either case. */
-      SVN_ERR(svn_repos__compare_files(&changed, b->t_root, t_path,
-                                       s_root, s_path, pool));
+      SVN_ERR(svn_fs_contents_different(&changed, b->t_root, t_path,
+                                        s_root, s_path, pool));
 
       if (!changed)
         return SVN_NO_ERROR;
@@ -1548,6 +1550,7 @@ svn_repos_finish_report(void *baton, apr
 {
   report_baton_t *b = baton;
 
+  SVN_ERR(svn_fs_refresh_revision_props(svn_repos_fs(b->repos), pool));
   return svn_error_trace(finish_report(b, pool));
 }
 

Modified: subversion/branches/authzperf/subversion/libsvn_repos/repos.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_repos/repos.c?rev=1741682&r1=1741681&r2=1741682&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_repos/repos.c (original)
+++ subversion/branches/authzperf/subversion/libsvn_repos/repos.c Fri Apr 29 18:38:53 2016
@@ -848,11 +848,16 @@ create_conf(svn_repos_t *repos, apr_pool
 "### no path-based access control is done."                                  NL
 "### Uncomment the line below to use the default authorization file."        NL
 "# authz-db = " SVN_REPOS__CONF_AUTHZ                                        NL
-"### The groups-db option controls the location of the groups file."         NL
-"### Unless you specify a path starting with a /, the file's location is"    NL
-"### relative to the directory containing this file.  The specified path"    NL
-"### may be a repository relative URL (^/) or an absolute file:// URL to a"  NL
-"### text file in a Subversion repository."                                  NL
+"### The groups-db option controls the location of the file with the"        NL
+"### group definitions and allows maintaining groups separately from the"    NL
+"### authorization rules.  The groups-db file is of the same format as the"  NL
+"### authz-db file and should contain a single [groups] section with the"    NL
+"### group definitions.  If the option is enabled, the authz-db file cannot" NL
+"### contain a [groups] section.  Unless you specify a path starting with"   NL
+"### a /, the file's location is relative to the directory containing this"  NL
+"### file.  The specified path may be a repository relative URL (^/) or an"  NL
+"### absolute file:// URL to a text file in a Subversion repository."        NL
+"### This option is not being used by default."                              NL
 "# groups-db = " SVN_REPOS__CONF_GROUPS                                      NL
 "### This option specifies the authentication realm of the repository."      NL
 "### If two repositories have the same authentication realm, they should"    NL
@@ -1175,8 +1180,8 @@ svn_repos_create(svn_repos_t **repos_p,
   SVN_ERR(lock_repos(repos, FALSE, FALSE, scratch_pool));
 
   /* Create an environment for the filesystem. */
-  if ((err = svn_fs_create(&repos->fs, repos->db_path, fs_config,
-                           result_pool)))
+  if ((err = svn_fs_create2(&repos->fs, repos->db_path, fs_config,
+                            result_pool, scratch_pool)))
     {
       /* If there was an error making the filesytem, e.g. unknown/supported
        * filesystem type.  Clean up after ourselves.  Yes this is safe because

Modified: subversion/branches/authzperf/subversion/libsvn_repos/repos.h
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_repos/repos.h?rev=1741682&r1=1741681&r2=1741682&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_repos/repos.h (original)
+++ subversion/branches/authzperf/subversion/libsvn_repos/repos.h Fri Apr 29 18:38:53 2016
@@ -388,17 +388,6 @@ svn_repos__authz_read(svn_authz_t **auth
 
 /*** Utility Functions ***/
 
-/* Set *CHANGED_P to TRUE if ROOT1/PATH1 and ROOT2/PATH2 have
-   different contents, FALSE if they have the same contents.
-   Use POOL for temporary allocation. */
-svn_error_t *
-svn_repos__compare_files(svn_boolean_t *changed_p,
-                         svn_fs_root_t *root1,
-                         const char *path1,
-                         svn_fs_root_t *root2,
-                         const char *path2,
-                         apr_pool_t *pool);
-
 /* Set *PREV_PATH and *PREV_REV to the path and revision which
    represent the location at which PATH in FS was located immediately
    prior to REVISION iff there was a copy operation (to PATH or one of

Modified: subversion/branches/authzperf/subversion/libsvn_repos/rev_hunt.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_repos/rev_hunt.c?rev=1741682&r1=1741681&r2=1741682&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_repos/rev_hunt.c (original)
+++ subversion/branches/authzperf/subversion/libsvn_repos/rev_hunt.c Fri Apr 29 18:38:53 2016
@@ -65,8 +65,8 @@ get_time(apr_time_t *tm,
 {
   svn_string_t *date_str;
 
-  SVN_ERR(svn_fs_revision_prop(&date_str, fs, rev, SVN_PROP_REVISION_DATE,
-                               pool));
+  SVN_ERR(svn_fs_revision_prop2(&date_str, fs, rev, SVN_PROP_REVISION_DATE,
+                                FALSE, pool, pool));
   if (! date_str)
     return svn_error_createf
       (SVN_ERR_FS_GENERAL, NULL,
@@ -88,6 +88,7 @@ svn_repos_dated_revision(svn_revnum_t *r
 
   /* Initialize top and bottom values of binary search. */
   SVN_ERR(svn_fs_youngest_rev(&rev_latest, fs, pool));
+  SVN_ERR(svn_fs_refresh_revision_props(fs, pool));
   rev_bot = 0;
   rev_top = rev_latest;
 
@@ -170,7 +171,8 @@ svn_repos_get_committed_info(svn_revnum_
   SVN_ERR(svn_fs_node_created_rev(committed_rev, root, path, pool));
 
   /* Get the revision properties of this revision. */
-  SVN_ERR(svn_fs_revision_proplist(&revprops, fs, *committed_rev, pool));
+  SVN_ERR(svn_fs_revision_proplist2(&revprops, fs, *committed_rev, TRUE,
+                                    pool, pool));
 
   /* Extract date and author from these revprops. */
   committed_date_s = svn_hash_gets(revprops, SVN_PROP_REVISION_DATE);
@@ -1010,26 +1012,39 @@ get_merged_mergeinfo(apr_hash_t **merged
   apr_hash_t *curr_mergeinfo, *prev_mergeinfo, *deleted, *changed;
   svn_error_t *err;
   svn_fs_root_t *root, *prev_root;
-  apr_hash_t *changed_paths;
-  const char *path = old_path_rev->path;
+  const char *start_path = old_path_rev->path;
+  const char *path = NULL;
+
+  svn_fs_path_change_iterator_t *iterator;
+  svn_fs_path_change3_t *change;
 
   /* Getting/parsing/diffing svn:mergeinfo is expensive, so only do it
      if there is a property change. */
   SVN_ERR(svn_fs_revision_root(&root, repos->fs, old_path_rev->revnum,
                                scratch_pool));
-  SVN_ERR(svn_fs_paths_changed2(&changed_paths, root, scratch_pool));
-  while (1)
-    {
-      svn_fs_path_change2_t *changed_path = svn_hash_gets(changed_paths, path);
-      if (changed_path && changed_path->prop_mod
-          && changed_path->mergeinfo_mod != svn_tristate_false)
-        break;
-      if (svn_fspath__is_root(path, strlen(path)))
+  SVN_ERR(svn_fs_paths_changed3(&iterator, root, scratch_pool, scratch_pool));
+  SVN_ERR(svn_fs_path_change_get(&change, iterator));
+
+  /* Find the changed PATH closest to START_PATH which may have a mergeinfo
+   * change. */
+  while (change)
+    {
+      if (   change->prop_mod
+          && change->mergeinfo_mod != svn_tristate_false
+          && svn_fspath__skip_ancestor(change->path.data, start_path))
         {
-          *merged_mergeinfo = NULL;
-          return SVN_NO_ERROR;
+          if (!path || svn_fspath__skip_ancestor(path, change->path.data))
+            path = apr_pstrmemdup(scratch_pool, change->path.data,
+                                  change->path.len);
         }
-      path = svn_fspath__dirname(path, scratch_pool);
+
+      SVN_ERR(svn_fs_path_change_get(&change, iterator));
+    }
+
+  if (path == NULL)
+    {
+      *merged_mergeinfo = NULL;
+      return SVN_NO_ERROR;
     }
 
   /* First, find the mergeinfo difference for old_path_rev->revnum, and
@@ -1354,22 +1369,48 @@ send_path_revision(struct path_revision
   void *delta_baton = NULL;
   apr_pool_t *tmp_pool;  /* For swapping */
   svn_boolean_t contents_changed;
+  svn_boolean_t props_changed;
 
   svn_pool_clear(sb->iterpool);
 
   /* Get the revision properties. */
-  SVN_ERR(svn_fs_revision_proplist(&rev_props, repos->fs,
-                                   path_rev->revnum, sb->iterpool));
+  SVN_ERR(svn_fs_revision_proplist2(&rev_props, repos->fs,
+                                    path_rev->revnum, FALSE,
+                                    sb->iterpool, sb->iterpool));
 
   /* Open the revision root. */
   SVN_ERR(svn_fs_revision_root(&root, repos->fs, path_rev->revnum,
                                sb->iterpool));
 
-  /* Get the file's properties for this revision and compute the diffs. */
-  SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path,
+  /* Check if the props *may* have changed. */
+  if (sb->last_root)
+    {
+      /* We don't use svn_fs_props_different() because it's more
+       * expensive. */
+      SVN_ERR(svn_fs_props_changed(&props_changed,
+                                   sb->last_root, sb->last_path,
+                                   root, path_rev->path, sb->iterpool));
+    }
+  else
+    {
+      props_changed = TRUE;
+    }
+
+  /* Calculate actual difference between last and current properties. */
+  if (props_changed)
+    {
+      /* Get the file's properties for this revision and compute the diffs. */
+      SVN_ERR(svn_fs_node_proplist(&props, root, path_rev->path,
                                    sb->iterpool));
-  SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
-                         sb->iterpool));
+      SVN_ERR(svn_prop_diffs(&prop_diffs, props, sb->last_props,
+                             sb->iterpool));
+    }
+  else
+    {
+      /* Properties didn't change: copy  LAST_PROPS to current POOL. */
+      props = svn_prop_hash_dup(sb->last_props, sb->iterpool);
+      prop_diffs = apr_array_make(sb->iterpool, 0, sizeof(svn_prop_t));
+    }
 
   /* Check if the contents *may* have changed. */
   if (! sb->last_root)
@@ -1594,6 +1635,10 @@ svn_repos_get_file_revs2(svn_repos_t *re
         end = youngest_rev;
     }
 
+  /* Make sure we catch up on the latest revprop changes.  This is the only
+   * time we will refresh the revprop data in this query. */
+  SVN_ERR(svn_fs_refresh_revision_props(repos->fs, scratch_pool));
+
   if (end < start)
     {
       if (include_merged_revisions)

Modified: subversion/branches/authzperf/subversion/libsvn_subr/atomic.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_subr/atomic.c?rev=1741682&r1=1741681&r2=1741682&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_subr/atomic.c (original)
+++ subversion/branches/authzperf/subversion/libsvn_subr/atomic.c Fri Apr 29 18:38:53 2016
@@ -22,8 +22,11 @@
 
 #include <assert.h>
 #include <apr_time.h>
-#include "private/svn_atomic.h"
 
+#include "svn_pools.h"
+
+#include "private/svn_atomic.h"
+#include "private/svn_mutex.h"
 
 /* Magic values for atomic initialization */
 #define SVN_ATOMIC_UNINITIALIZED 0
@@ -174,3 +177,42 @@ svn_atomic__init_once_no_error(volatile
   else
     return init_baton.errstr;
 }
+
+/* The process-global counter that we use to produce process-wide unique
+ * values.  Since APR has no 64 bit atomics, all access to this will be
+ * serialized through COUNTER_MUTEX. */
+static apr_uint64_t uniqiue_counter = 0;
+
+/* The corresponding mutex and initialization state. */
+static volatile svn_atomic_t counter_status = SVN_ATOMIC_UNINITIALIZED;
+static svn_mutex__t *counter_mutex = NULL;
+
+/* svn_atomic__err_init_func_t implementation that initializes COUNTER_MUTEX.
+ * Note that neither argument will be used and should be NULL. */
+static svn_error_t *
+init_unique_counter(void *null_baton,
+                    apr_pool_t *null_pool)
+{
+  /* COUNTER_MUTEX is global, so it needs to live in a global pool.
+   * APR also makes those thread-safe by default. */
+  SVN_ERR(svn_mutex__init(&counter_mutex, TRUE, svn_pool_create(NULL)));
+  return SVN_NO_ERROR;
+}
+
+/* Read and increment UNIQIUE_COUNTER. Return the new value in *VALUE.
+ * Call this function only while having acquired the COUNTER_MUTEX. */
+static svn_error_t *
+read_unique_counter(apr_uint64_t *value)
+{
+  *value = ++uniqiue_counter;
+  return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_atomic__unique_counter(apr_uint64_t *value)
+{
+  SVN_ERR(svn_atomic__init_once(&counter_status, init_unique_counter, NULL,
+                                NULL));
+  SVN_MUTEX__WITH_LOCK(counter_mutex, read_unique_counter(value));
+  return SVN_NO_ERROR;
+}

Modified: subversion/branches/authzperf/subversion/libsvn_subr/base64.c
URL: http://svn.apache.org/viewvc/subversion/branches/authzperf/subversion/libsvn_subr/base64.c?rev=1741682&r1=1741681&r2=1741682&view=diff
==============================================================================
--- subversion/branches/authzperf/subversion/libsvn_subr/base64.c (original)
+++ subversion/branches/authzperf/subversion/libsvn_subr/base64.c Fri Apr 29 18:38:53 2016
@@ -58,6 +58,7 @@ struct encode_baton {
   unsigned char buf[3];         /* Bytes waiting to be encoded */
   size_t buflen;                /* Number of bytes waiting */
   size_t linelen;               /* Bytes output so far on this line */
+  svn_boolean_t break_lines;
   apr_pool_t *scratch_pool;
 };
 
@@ -140,7 +141,7 @@ encode_bytes(svn_stringbuf_t *str, const
   svn_stringbuf_ensure(str, str->len + buflen);
 
   /* Keep encoding three-byte groups until we run out.  */
-  while (*inbuflen + (end - p) >= 3)
+  while ((end - p) >= (3 - *inbuflen))
     {
       /* May we encode BYTES_PER_LINE bytes without caring about
          line breaks, data in the temporary INBUF or running out
@@ -214,7 +215,8 @@ encode_data(void *baton, const char *dat
   svn_error_t *err = SVN_NO_ERROR;
 
   /* Encode this block of data and write it out.  */
-  encode_bytes(encoded, data, *len, eb->buf, &eb->buflen, &eb->linelen, TRUE);
+  encode_bytes(encoded, data, *len, eb->buf, &eb->buflen, &eb->linelen,
+               eb->break_lines);
   enclen = encoded->len;
   if (enclen != 0)
     err = svn_stream_write(eb->output, encoded->data, &enclen);
@@ -233,7 +235,8 @@ finish_encoding_data(void *baton)
   svn_error_t *err = SVN_NO_ERROR;
 
   /* Encode a partial group at the end if necessary, and write it out.  */
-  encode_partial_group(encoded, eb->buf, eb->buflen, eb->linelen, TRUE);
+  encode_partial_group(encoded, eb->buf, eb->buflen, eb->linelen,
+                       eb->break_lines);
   enclen = encoded->len;
   if (enclen != 0)
     err = svn_stream_write(eb->output, encoded->data, &enclen);
@@ -247,7 +250,9 @@ finish_encoding_data(void *baton)
 
 
 svn_stream_t *
-svn_base64_encode(svn_stream_t *output, apr_pool_t *pool)
+svn_base64_encode2(svn_stream_t *output,
+                   svn_boolean_t break_lines,
+                   apr_pool_t *pool)
 {
   struct encode_baton *eb = apr_palloc(pool, sizeof(*eb));
   svn_stream_t *stream;
@@ -255,6 +260,7 @@ svn_base64_encode(svn_stream_t *output,
   eb->output = output;
   eb->buflen = 0;
   eb->linelen = 0;
+  eb->break_lines = break_lines;
   eb->scratch_pool = svn_pool_create(pool);
   stream = svn_stream_create(eb, pool);
   svn_stream_set_write(stream, encode_data);
@@ -424,7 +430,7 @@ decode_bytes(svn_stringbuf_t *str, const
       /* If no data is left in temporary INBUF and there is at least
          one line-sized chunk left to decode, we may use the optimized
          code path. */
-      if ((*inbuflen == 0) && (p + BASE64_LINELEN <= end))
+      if ((*inbuflen == 0) && (end - p >= BASE64_LINELEN))
         if (decode_line(str, &p))
           continue;