/*
 * compat.c:  compatibility compliance logic
 *
 * ====================================================================
 *    Licensed to the Apache Software Foundation (ASF) under one
 *    or more contributor license agreements.  See the NOTICE file
 *    distributed with this work for additional information
 *    regarding copyright ownership.  The ASF licenses this file
 *    to you under the Apache License, Version 2.0 (the
 *    "License"); you may not use this file except in compliance
 *    with the License.  You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing,
 *    software distributed under the License is distributed on an
 *    "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
 *    KIND, either express or implied.  See the License for the
 *    specific language governing permissions and limitations
 *    under the License.
 * ====================================================================
 */

#include <apr_pools.h>

#include "svn_hash.h"
#include "svn_error.h"
#include "svn_pools.h"
#include "svn_sorts.h"
#include "svn_dirent_uri.h"
#include "svn_path.h"
#include "svn_ra.h"
#include "svn_io.h"
#include "svn_compat.h"
#include "svn_props.h"

#include "private/svn_fspath.h"
#include "ra_loader.h"
#include "svn_private_config.h"



/* This is just like svn_sort_compare_revisions, save that it sorts
   the revisions in *ascending* order. */
static int
compare_revisions(const void *a, const void *b)
{
  svn_revnum_t a_rev = *(const svn_revnum_t *)a;
  svn_revnum_t b_rev = *(const svn_revnum_t *)b;
  if (a_rev == b_rev)
    return 0;
  return a_rev < b_rev ? -1 : 1;
}

/* Given the CHANGED_PATHS and REVISION from an instance of a
   svn_log_message_receiver_t function, determine at which location
   PATH may be expected in the next log message, and set *PREV_PATH_P
   to that value.  KIND is the node kind of PATH.  Set *ACTION_P to a
   character describing the change that caused this revision (as
   listed in svn_log_changed_path_t) and set *COPYFROM_REV_P to the
   revision PATH was copied from, or SVN_INVALID_REVNUM if it was not
   copied.  ACTION_P and COPYFROM_REV_P may be NULL, in which case
   they are not used.  Perform all allocations in POOL.

   This is useful for tracking the various changes in location a
   particular resource has undergone when performing an RA->get_logs()
   operation on that resource.
*/
static svn_error_t *
prev_log_path(const char **prev_path_p,
              char *action_p,
              svn_revnum_t *copyfrom_rev_p,
              apr_hash_t *changed_paths,
              const char *path,
              svn_node_kind_t kind,
              svn_revnum_t revision,
              apr_pool_t *pool)
{
  svn_log_changed_path_t *change;
  const char *prev_path = NULL;

  /* It's impossible to find the predecessor path of a NULL path. */
  SVN_ERR_ASSERT(path);

  /* Initialize our return values for the action and copyfrom_rev in
     case we have an unhandled case later on. */
  if (action_p)
    *action_p = 'M';
  if (copyfrom_rev_p)
    *copyfrom_rev_p = SVN_INVALID_REVNUM;

  if (changed_paths)
    {
      /* See if PATH was explicitly changed in this revision. */
      change = svn_hash_gets(changed_paths, path);
      if (change)
        {
          /* If PATH was not newly added in this revision, then it may or may
             not have also been part of a moved subtree.  In this case, set a
             default previous path, but still look through the parents of this
             path for a possible copy event. */
          if (change->action != 'A' && change->action != 'R')
            {
              prev_path = path;
            }
          else
            {
              /* PATH is new in this revision.  This means it cannot have been
                 part of a copied subtree. */
              if (change->copyfrom_path)
                prev_path = apr_pstrdup(pool, change->copyfrom_path);
              else
                prev_path = NULL;

              *prev_path_p = prev_path;
              if (action_p)
                *action_p = change->action;
              if (copyfrom_rev_p)
                *copyfrom_rev_p = change->copyfrom_rev;
              return SVN_NO_ERROR;
            }
        }

      if (apr_hash_count(changed_paths))
        {
          /* The path was not explicitly changed in this revision.  The
             fact that we're hearing about this revision implies, then,
             that the path was a child of some copied directory.  We need
             to find that directory, and effectively "re-base" our path on
             that directory's copyfrom_path. */
          int i;
          apr_array_header_t *paths;

          /* Build a sorted list of the changed paths. */
          paths = svn_sort__hash(changed_paths,
                                 svn_sort_compare_items_as_paths, pool);

          /* Now, walk the list of paths backwards, looking a parent of
             our path that has copyfrom information. */
          for (i = paths->nelts; i > 0; i--)
            {
              svn_sort__item_t item = APR_ARRAY_IDX(paths,
                                                    i - 1, svn_sort__item_t);
              const char *ch_path = item.key;
              size_t len = strlen(ch_path);

              /* See if our path is the child of this change path.  If
                 not, keep looking.  */
              if (! ((strncmp(ch_path, path, len) == 0) && (path[len] == '/')))
                continue;

              /* Okay, our path *is* a child of this change path.  If
                 this change was copied, we just need to apply the
                 portion of our path that is relative to this change's
                 path, to the change's copyfrom path.  Otherwise, this
                 change isn't really interesting to us, and our search
                 continues. */
              change = item.value;
              if (change->copyfrom_path)
                {
                  if (action_p)
                    *action_p = change->action;
                  if (copyfrom_rev_p)
                    *copyfrom_rev_p = change->copyfrom_rev;
                  prev_path = svn_fspath__join(change->copyfrom_path,
                                               path + len + 1, pool);
                  break;
                }
            }
        }
    }

  /* If we didn't find what we expected to find, return an error.
     (Because directories bubble-up, we get a bunch of logs we might
     not want.  Be forgiving in that case.)  */
  if (! prev_path)
    {
      if (kind == svn_node_dir)
        prev_path = apr_pstrdup(pool, path);
      else
        return svn_error_createf(SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
                                 _("Missing changed-path information for "
                                   "'%s' in revision %ld"),
                                 svn_dirent_local_style(path, pool), revision);
    }

  *prev_path_p = prev_path;
  return SVN_NO_ERROR;
}


/* Set *FS_PATH_P to the absolute filesystem path associated with the
   URL built from SESSION's URL and REL_PATH (which is relative to
   session's URL.  Use POOL for allocations. */
static svn_error_t *
get_fs_path(const char **fs_path_p,
            svn_ra_session_t *session,
            const char *rel_path,
            apr_pool_t *pool)
{
  const char *url, *fs_path;

  SVN_ERR(svn_ra_get_session_url(session, &url, pool));
  SVN_ERR(svn_ra_get_path_relative_to_root(session, &fs_path, url, pool));
  *fs_path_p = svn_fspath__canonicalize(svn_relpath_join(fs_path,
                                                         rel_path, pool),
                                        pool);
  return SVN_NO_ERROR;
}



/*** Fallback implementation of svn_ra_get_locations(). ***/


/* ### This is to support 1.0 servers. */
struct log_receiver_baton
{
  /* The kind of the path we're tracing. */
  svn_node_kind_t kind;

  /* The path at which we are trying to find our versioned resource in
     the log output. */
  const char *last_path;

  /* Input revisions and output hash; the whole point of this little game. */
  svn_revnum_t peg_revision;
  apr_array_header_t *location_revisions;
  const char *peg_path;
  apr_hash_t *locations;

  /* A pool from which to allocate stuff stored in this baton. */
  apr_pool_t *pool;
};


/* Implements svn_log_entry_receiver_t; helper for slow_get_locations.
   As input, takes log_receiver_baton (defined above) and attempts to
   "fill in" locations in the baton over the course of many
   iterations. */
static svn_error_t *
log_receiver(void *baton,
             svn_log_entry_t *log_entry,
             apr_pool_t *pool)
{
  struct log_receiver_baton *lrb = baton;
  apr_pool_t *hash_pool = apr_hash_pool_get(lrb->locations);
  const char *current_path = lrb->last_path;
  const char *prev_path;

  /* No paths were changed in this revision.  Nothing to do. */
  if (! log_entry->changed_paths2)
    return SVN_NO_ERROR;

  /* If we've run off the end of the path's history, there's nothing
     to do.  (This should never happen with a properly functioning
     server, since we'd get no more log messages after the one where
     path was created.  But a malfunctioning server shouldn't cause us
     to trigger an assertion failure.) */
  if (! current_path)
    return SVN_NO_ERROR;

  /* If we haven't found our peg path yet, and we are now looking at a
     revision equal to or older than the peg revision, then our
     "current" path is our peg path. */
  if ((! lrb->peg_path) && (log_entry->revision <= lrb->peg_revision))
    lrb->peg_path = apr_pstrdup(lrb->pool, current_path);

  /* Determine the paths for any of the revisions for which we haven't
     gotten paths already. */
  while (lrb->location_revisions->nelts)
    {
      svn_revnum_t next = APR_ARRAY_IDX(lrb->location_revisions,
                                        lrb->location_revisions->nelts - 1,
                                        svn_revnum_t);
      if (log_entry->revision <= next)
        {
          apr_hash_set(lrb->locations,
                       apr_pmemdup(hash_pool, &next, sizeof(next)),
                       sizeof(next),
                       apr_pstrdup(hash_pool, current_path));
          apr_array_pop(lrb->location_revisions);
        }
      else
        break;
    }

  /* Figure out at which repository path our object of interest lived
     in the previous revision. */
  SVN_ERR(prev_log_path(&prev_path, NULL, NULL, log_entry->changed_paths2,
                        current_path, lrb->kind, log_entry->revision, pool));

  /* Squirrel away our "next place to look" path (suffer the strcmp
     hit to save on allocations). */
  if (! prev_path)
    lrb->last_path = NULL;
  else if (strcmp(prev_path, current_path) != 0)
    lrb->last_path = apr_pstrdup(lrb->pool, prev_path);

  return SVN_NO_ERROR;
}


svn_error_t *
svn_ra__locations_from_log(svn_ra_session_t *session,
                           apr_hash_t **locations_p,
                           const char *path,
                           svn_revnum_t peg_revision,
                           const apr_array_header_t *location_revisions,
                           apr_pool_t *pool)
{
  apr_hash_t *locations = apr_hash_make(pool);
  struct log_receiver_baton lrb = { 0 };
  apr_array_header_t *targets;
  svn_revnum_t youngest_requested, oldest_requested, youngest, oldest;
  svn_node_kind_t kind;
  const char *fs_path;

  /* Fetch the absolute FS path associated with PATH. */
  SVN_ERR(get_fs_path(&fs_path, session, path, pool));

  /* Sanity check: verify that the peg-object exists in repos. */
  SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool));
  if (kind == svn_node_none)
    return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
                             _("Path '%s' doesn't exist in revision %ld"),
                             fs_path, peg_revision);

  /* Easy out: no location revisions. */
  if (! location_revisions->nelts)
    {
      *locations_p = locations;
      return SVN_NO_ERROR;
    }

  /* Figure out the youngest and oldest revs (amongst the set of
     requested revisions + the peg revision) so we can avoid
     unnecessary log parsing. */
  qsort(location_revisions->elts, location_revisions->nelts,
        location_revisions->elt_size, compare_revisions);
  oldest_requested = APR_ARRAY_IDX(location_revisions, 0, svn_revnum_t);
  youngest_requested = APR_ARRAY_IDX(location_revisions,
                                     location_revisions->nelts - 1,
                                     svn_revnum_t);
  youngest = peg_revision;
  youngest = (oldest_requested > youngest) ? oldest_requested : youngest;
  youngest = (youngest_requested > youngest) ? youngest_requested : youngest;
  oldest = peg_revision;
  oldest = (oldest_requested < oldest) ? oldest_requested : oldest;
  oldest = (youngest_requested < oldest) ? youngest_requested : oldest;

  /* Populate most of our log receiver baton structure. */
  lrb.kind = kind;
  lrb.last_path = fs_path;
  lrb.location_revisions = apr_array_copy(pool, location_revisions);
  lrb.peg_revision = peg_revision;
  lrb.peg_path = NULL;
  lrb.locations = locations;
  lrb.pool = pool;

  /* Let the RA layer drive our log information handler, which will do
     the work of finding the actual locations for our resource.
     Notice that we always run on the youngest rev of the 3 inputs. */
  targets = apr_array_make(pool, 1, sizeof(const char *));
  APR_ARRAY_PUSH(targets, const char *) = path;
  SVN_ERR(svn_ra_get_log2(session, targets, youngest, oldest, 0,
                          TRUE, FALSE, FALSE,
                          apr_array_make(pool, 0, sizeof(const char *)),
                          log_receiver, &lrb, pool));

  /* If the received log information did not cover any of the
     requested revisions, use the last known path.  (This normally
     just means that FS_PATH was not modified between the requested
     revision and OLDEST.  If the file was created at some point after
     OLDEST, then lrb.last_path should be NULL.) */
  if (! lrb.peg_path)
    lrb.peg_path = lrb.last_path;
  if (lrb.last_path)
    {
      int i;
      for (i = 0; i < location_revisions->nelts; i++)
        {
          svn_revnum_t rev = APR_ARRAY_IDX(location_revisions, i,
                                           svn_revnum_t);
          if (! apr_hash_get(locations, &rev, sizeof(rev)))
            apr_hash_set(locations, apr_pmemdup(pool, &rev, sizeof(rev)),
                         sizeof(rev), apr_pstrdup(pool, lrb.last_path));
        }
    }

  /* Check that we got the peg path. */
  if (! lrb.peg_path)
    return svn_error_createf
      (APR_EGENERAL, NULL,
       _("Unable to find repository location for '%s' in revision %ld"),
       fs_path, peg_revision);

  /* Sanity check: make sure that our calculated peg path is the same
     as what we expected it to be. */
  if (strcmp(fs_path, lrb.peg_path) != 0)
    return svn_error_createf
      (SVN_ERR_CLIENT_UNRELATED_RESOURCES, NULL,
       _("'%s' in revision %ld is an unrelated object"),
       fs_path, youngest);

  *locations_p = locations;
  return SVN_NO_ERROR;
}




/*** Fallback implementation of svn_ra_get_location_segments(). ***/

struct gls_log_receiver_baton {
  /* The kind of the path we're tracing. */
  svn_node_kind_t kind;

  /* Are we finished (and just listening to log entries because our
     caller won't shut up?). */
  svn_boolean_t done;

  /* The path at which we are trying to find our versioned resource in
     the log output. */
  const char *last_path;

  /* Input data. */
  svn_revnum_t start_rev;

  /* Output intermediate state and callback/baton. */
  svn_revnum_t range_end;
  svn_location_segment_receiver_t receiver;
  void *receiver_baton;

  /* A pool from which to allocate stuff stored in this baton. */
  apr_pool_t *pool;
};

/* Build a node location segment object from PATH, RANGE_START, and
   RANGE_END, and pass it off to RECEIVER/RECEIVER_BATON. */
static svn_error_t *
maybe_crop_and_send_segment(const char *path,
                            svn_revnum_t start_rev,
                            svn_revnum_t range_start,
                            svn_revnum_t range_end,
                            svn_location_segment_receiver_t receiver,
                            void *receiver_baton,
                            apr_pool_t *pool)
{
  svn_location_segment_t *segment = apr_pcalloc(pool, sizeof(*segment));
  segment->path = path ? ((*path == '/') ? path + 1 : path) : NULL;
  segment->range_start = range_start;
  segment->range_end = range_end;
  if (segment->range_start <= start_rev)
    {
      if (segment->range_end > start_rev)
        segment->range_end = start_rev;
      return receiver(segment, receiver_baton, pool);
    }
  return SVN_NO_ERROR;
}

static svn_error_t *
gls_log_receiver(void *baton,
                 svn_log_entry_t *log_entry,
                 apr_pool_t *pool)
{
  struct gls_log_receiver_baton *lrb = baton;
  const char *current_path = lrb->last_path;
  const char *prev_path;
  svn_revnum_t copyfrom_rev;

  /* If we're done, ignore this invocation. */
  if (lrb->done)
    return SVN_NO_ERROR;

  /* Figure out at which repository path our object of interest lived
     in the previous revision, and if its current location is the
     result of copy since then. */
  SVN_ERR(prev_log_path(&prev_path, NULL, &copyfrom_rev,
                        log_entry->changed_paths2, current_path,
                        lrb->kind, log_entry->revision, pool));

  /* If we've run off the end of the path's history, we need to report
     our final segment (and then, we're done). */
  if (! prev_path)
    {
      lrb->done = TRUE;
      return maybe_crop_and_send_segment(current_path, lrb->start_rev,
                                         log_entry->revision, lrb->range_end,
                                         lrb->receiver, lrb->receiver_baton,
                                         pool);
    }

  /* If there was a copy operation of interest... */
  if (SVN_IS_VALID_REVNUM(copyfrom_rev))
    {
      /* ...then report the segment between this revision and the
         last-reported revision. */
      SVN_ERR(maybe_crop_and_send_segment(current_path, lrb->start_rev,
                                          log_entry->revision, lrb->range_end,
                                          lrb->receiver, lrb->receiver_baton,
                                          pool));
      lrb->range_end = log_entry->revision - 1;

      /* And if there was a revision gap, we need to report that, too. */
      if (log_entry->revision - copyfrom_rev > 1)
        {
          SVN_ERR(maybe_crop_and_send_segment(NULL, lrb->start_rev,
                                              copyfrom_rev + 1, lrb->range_end,
                                              lrb->receiver,
                                              lrb->receiver_baton, pool));
          lrb->range_end = copyfrom_rev;
        }

      /* Update our state variables. */
      lrb->last_path = apr_pstrdup(lrb->pool, prev_path);
    }

  return SVN_NO_ERROR;
}


svn_error_t *
svn_ra__location_segments_from_log(svn_ra_session_t *session,
                                   const char *path,
                                   svn_revnum_t peg_revision,
                                   svn_revnum_t start_rev,
                                   svn_revnum_t end_rev,
                                   svn_location_segment_receiver_t receiver,
                                   void *receiver_baton,
                                   apr_pool_t *pool)
{
  struct gls_log_receiver_baton lrb = { 0 };
  apr_array_header_t *targets;
  svn_node_kind_t kind;
  svn_revnum_t youngest_rev = SVN_INVALID_REVNUM;
  const char *fs_path;

  /* Fetch the absolute FS path associated with PATH. */
  SVN_ERR(get_fs_path(&fs_path, session, path, pool));

  /* If PEG_REVISION is invalid, it means HEAD.  If START_REV is
     invalid, it means HEAD.  If END_REV is SVN_INVALID_REVNUM, we'll
     use 0. */
  if (! SVN_IS_VALID_REVNUM(peg_revision))
    {
      SVN_ERR(svn_ra_get_latest_revnum(session, &youngest_rev, pool));
      peg_revision = youngest_rev;
    }
  if (! SVN_IS_VALID_REVNUM(start_rev))
    {
      if (SVN_IS_VALID_REVNUM(youngest_rev))
        start_rev = youngest_rev;
      else
        SVN_ERR(svn_ra_get_latest_revnum(session, &start_rev, pool));
    }
  if (! SVN_IS_VALID_REVNUM(end_rev))
    {
      end_rev = 0;
    }

  /* The API demands a certain ordering of our revision inputs. Enforce it. */
  SVN_ERR_ASSERT((peg_revision >= start_rev) && (start_rev >= end_rev));

  /* Sanity check: verify that the peg-object exists in repos. */
  SVN_ERR(svn_ra_check_path(session, path, peg_revision, &kind, pool));
  if (kind == svn_node_none)
    return svn_error_createf(SVN_ERR_FS_NOT_FOUND, NULL,
                             _("Path '%s' doesn't exist in revision %ld"),
                             fs_path, start_rev);

  /* Populate most of our log receiver baton structure. */
  lrb.kind = kind;
  lrb.last_path = fs_path;
  lrb.done = FALSE;
  lrb.start_rev = start_rev;
  lrb.range_end = start_rev;
  lrb.receiver = receiver;
  lrb.receiver_baton = receiver_baton;
  lrb.pool = pool;

  /* Let the RA layer drive our log information handler, which will do
     the work of finding the actual locations for our resource.
     Notice that we always run on the youngest rev of the 3 inputs. */
  targets = apr_array_make(pool, 1, sizeof(const char *));
  APR_ARRAY_PUSH(targets, const char *) = path;
  SVN_ERR(svn_ra_get_log2(session, targets, peg_revision, end_rev, 0,
                          TRUE, FALSE, FALSE,
                          apr_array_make(pool, 0, sizeof(const char *)),
                          gls_log_receiver, &lrb, pool));

  /* If we didn't finish, we need to do so with a final segment send. */
  if (! lrb.done)
    SVN_ERR(maybe_crop_and_send_segment(lrb.last_path, start_rev,
                                        end_rev, lrb.range_end,
                                        receiver, receiver_baton, pool));

  return SVN_NO_ERROR;
}



/*** Fallback implementation of svn_ra_get_file_revs(). ***/

/* The metadata associated with a particular revision. */
struct rev
{
  svn_revnum_t revision; /* the revision number */
  const char *path;      /* the absolute repository path */
  apr_hash_t *props;     /* the revprops for this revision */
  struct rev *next;      /* the next revision */
};

/* File revs log message baton. */
struct fr_log_message_baton {
  const char *path;        /* The path to be processed */
  struct rev *eldest;      /* The eldest revision processed */
  char action;             /* The action associated with the eldest */
  svn_revnum_t copyrev;    /* The revision the eldest was copied from */
  apr_pool_t *pool;
};

/* Callback for log messages: implements svn_log_entry_receiver_t and
   accumulates revision metadata into a chronologically ordered list stored in
   the baton. */
static svn_error_t *
fr_log_message_receiver(void *baton,
                        svn_log_entry_t *log_entry,
                        apr_pool_t *pool)
{
  struct fr_log_message_baton *lmb = baton;
  struct rev *rev;

  rev = apr_palloc(lmb->pool, sizeof(*rev));
  rev->revision = log_entry->revision;
  rev->path = lmb->path;
  rev->next = lmb->eldest;
  lmb->eldest = rev;

  /* Duplicate log_entry revprops into rev->props */
  rev->props = svn_prop_hash_dup(log_entry->revprops, lmb->pool);

  return prev_log_path(&lmb->path, &lmb->action,
                       &lmb->copyrev, log_entry->changed_paths2,
                       lmb->path, svn_node_file, log_entry->revision,
                       lmb->pool);
}

svn_error_t *
svn_ra__file_revs_from_log(svn_ra_session_t *ra_session,
                           const char *path,
                           svn_revnum_t start,
                           svn_revnum_t end,
                           svn_file_rev_handler_t handler,
                           void *handler_baton,
                           apr_pool_t *pool)
{
  svn_node_kind_t kind;
  const char *repos_url, *session_url, *fs_path;
  apr_array_header_t *condensed_targets;
  struct fr_log_message_baton lmb;
  struct rev *rev;
  apr_hash_t *last_props;
  svn_stream_t *last_stream;
  apr_pool_t *currpool, *lastpool;

  /* Fetch the absolute FS path associated with PATH. */
  SVN_ERR(get_fs_path(&fs_path, ra_session, path, pool));

  /* Check to make sure we're dealing with a file. */
  SVN_ERR(svn_ra_check_path(ra_session, path, end, &kind, pool));
  if (kind == svn_node_dir)
    return svn_error_createf(SVN_ERR_FS_NOT_FILE, NULL,
                             _("'%s' is not a file"), fs_path);

  condensed_targets = apr_array_make(pool, 1, sizeof(const char *));
  APR_ARRAY_PUSH(condensed_targets, const char *) = path;

  lmb.path = fs_path;
  lmb.eldest = NULL;
  lmb.pool = pool;

  /* Accumulate revision metadata by walking the revisions
     backwards; this allows us to follow moves/copies
     correctly. */
  SVN_ERR(svn_ra_get_log2(ra_session,
                          condensed_targets,
                          end, start, 0, /* no limit */
                          TRUE, FALSE, FALSE,
                          NULL, fr_log_message_receiver, &lmb,
                          pool));

  /* Reparent the session while we go back through the history. */
  SVN_ERR(svn_ra_get_session_url(ra_session, &session_url, pool));
  SVN_ERR(svn_ra_get_repos_root2(ra_session, &repos_url, pool));
  SVN_ERR(svn_ra_reparent(ra_session, repos_url, pool));

  currpool = svn_pool_create(pool);
  lastpool = svn_pool_create(pool);

  /* We want the first txdelta to be against the empty file. */
  last_props = apr_hash_make(lastpool);
  last_stream = svn_stream_empty(lastpool);

  /* Walk the revision list in chronological order, downloading each fulltext,
     diffing it with its predecessor, and calling the file_revs handler for
     each one.  Use two iteration pools rather than one, because the diff
     routines need to look at a sliding window of revisions.  Two pools gives
     us a ring buffer of sorts. */
  for (rev = lmb.eldest; rev; rev = rev->next)
    {
      const char *temp_path;
      apr_pool_t *tmppool;
      apr_hash_t *props;
      apr_file_t *file;
      svn_stream_t *stream;
      apr_array_header_t *prop_diffs;
      svn_txdelta_stream_t *delta_stream;
      svn_txdelta_window_handler_t delta_handler = NULL;
      void *delta_baton = NULL;

      svn_pool_clear(currpool);

      /* Get the contents of the file from the repository, and put them in
         a temporary local file. */
      SVN_ERR(svn_stream_open_unique(&stream, &temp_path, NULL,
                                     svn_io_file_del_on_pool_cleanup,
                                     currpool, currpool));
      SVN_ERR(svn_ra_get_file(ra_session, rev->path + 1, rev->revision,
                              stream, NULL, &props, currpool));
      SVN_ERR(svn_stream_close(stream));

      /* Open up a stream to the local file. */
      SVN_ERR(svn_io_file_open(&file, temp_path, APR_READ, APR_OS_DEFAULT,
                               currpool));
      stream = svn_stream_from_aprfile2(file, FALSE, currpool);

      /* Calculate the property diff */
      SVN_ERR(svn_prop_diffs(&prop_diffs, props, last_props, lastpool));

      /* Call the file_rev handler */
      SVN_ERR(handler(handler_baton, rev->path, rev->revision, rev->props,
                      FALSE, /* merged revision */
                      &delta_handler, &delta_baton, prop_diffs, lastpool));

      /* Compute and send delta if client asked for it. */
      if (delta_handler)
        {
          /* Get the content delta. Don't calculate checksums as we don't
           * use them. */
          svn_txdelta2(&delta_stream, last_stream, stream, FALSE, lastpool);

          /* And send. */
          SVN_ERR(svn_txdelta_send_txstream(delta_stream, delta_handler,
                                            delta_baton, lastpool));
        }

      /* Switch the pools and data for the next iteration */
      tmppool = currpool;
      currpool = lastpool;
      lastpool = tmppool;

      SVN_ERR(svn_stream_close(last_stream));
      last_stream = stream;
      last_props = props;
    }

  SVN_ERR(svn_stream_close(last_stream));
  svn_pool_destroy(currpool);
  svn_pool_destroy(lastpool);

  /* Reparent the session back to the original URL. */
  return svn_ra_reparent(ra_session, session_url, pool);
}


/*** Fallback implementation of svn_ra_get_deleted_rev(). ***/

/* svn_ra_get_log2() receiver_baton for svn_ra__get_deleted_rev_from_log(). */
typedef struct log_path_del_rev_t
{
  /* Absolute repository path. */
  const char *path;

  /* Revision PATH was first deleted or replaced. */
  svn_revnum_t revision_deleted;
} log_path_del_rev_t;

/* A svn_log_entry_receiver_t callback for finding the revision
   ((log_path_del_rev_t *)BATON)->PATH was first deleted or replaced.
   Stores that revision in ((log_path_del_rev_t *)BATON)->REVISION_DELETED.
 */
static svn_error_t *
log_path_del_receiver(void *baton,
                      svn_log_entry_t *log_entry,
                      apr_pool_t *pool)
{
  log_path_del_rev_t *b = baton;
  apr_hash_index_t *hi;

  /* No paths were changed in this revision.  Nothing to do. */
  if (! log_entry->changed_paths2)
    return SVN_NO_ERROR;

  for (hi = apr_hash_first(pool, log_entry->changed_paths2);
       hi != NULL;
       hi = apr_hash_next(hi))
    {
      void *val;
      char *path;
      svn_log_changed_path_t *log_item;

      apr_hash_this(hi, (void *) &path, NULL, &val);
      log_item = val;
      if (svn_path_compare_paths(b->path, path) == 0
          && (log_item->action == 'D' || log_item->action == 'R'))
        {
          /* Found the first deletion or replacement, we are done. */
          b->revision_deleted = log_entry->revision;
          break;
        }
    }
  return SVN_NO_ERROR;
}

svn_error_t *
svn_ra__get_deleted_rev_from_log(svn_ra_session_t *session,
                                 const char *rel_deleted_path,
                                 svn_revnum_t peg_revision,
                                 svn_revnum_t end_revision,
                                 svn_revnum_t *revision_deleted,
                                 apr_pool_t *pool)
{
  const char *fs_path;
  log_path_del_rev_t log_path_deleted_baton;

  /* Fetch the absolute FS path associated with PATH. */
  SVN_ERR(get_fs_path(&fs_path, session, rel_deleted_path, pool));

  if (!SVN_IS_VALID_REVNUM(peg_revision))
    return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
                             _("Invalid peg revision %ld"), peg_revision);
  if (!SVN_IS_VALID_REVNUM(end_revision))
    return svn_error_createf(SVN_ERR_CLIENT_BAD_REVISION, NULL,
                             _("Invalid end revision %ld"), end_revision);
  if (end_revision <= peg_revision)
    return svn_error_create(SVN_ERR_CLIENT_BAD_REVISION, NULL,
                            _("Peg revision must precede end revision"));

  log_path_deleted_baton.path = fs_path;
  log_path_deleted_baton.revision_deleted = SVN_INVALID_REVNUM;

  /* Examine the logs of SESSION's URL to find when DELETED_PATH was first
     deleted or replaced. */
  SVN_ERR(svn_ra_get_log2(session, NULL, peg_revision, end_revision, 0,
                          TRUE, TRUE, FALSE,
                          apr_array_make(pool, 0, sizeof(char *)),
                          log_path_del_receiver, &log_path_deleted_baton,
                          pool));
  *revision_deleted = log_path_deleted_baton.revision_deleted;
  return SVN_NO_ERROR;
}


svn_error_t *
svn_ra__get_inherited_props_walk(svn_ra_session_t *session,
                                 const char *path,
                                 svn_revnum_t revision,
                                 apr_array_header_t **inherited_props,
                                 apr_pool_t *result_pool,
                                 apr_pool_t *scratch_pool)
{
  const char *repos_root_url;
  const char *session_url;
  const char *parent_url;
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);

  *inherited_props =
    apr_array_make(result_pool, 1, sizeof(svn_prop_inherited_item_t *));

  /* Walk to the root of the repository getting inherited
     props for PATH. */
  SVN_ERR(svn_ra_get_repos_root2(session, &repos_root_url, scratch_pool));
  SVN_ERR(svn_ra_get_session_url(session, &session_url, scratch_pool));
  parent_url = session_url;

  while (strcmp(repos_root_url, parent_url))
    {
      apr_hash_index_t *hi;
      apr_hash_t *parent_props;
      apr_hash_t *final_hash = apr_hash_make(result_pool);
      svn_error_t *err;

      svn_pool_clear(iterpool);
      parent_url = svn_uri_dirname(parent_url, scratch_pool);
      SVN_ERR(svn_ra_reparent(session, parent_url, iterpool));
      err = session->vtable->get_dir(session, NULL, NULL,
                                     &parent_props, "",
                                     revision, SVN_DIRENT_ALL,
                                     iterpool);

      /* If the user doesn't have read access to a parent path then
         skip, but allow them to inherit from further up. */
      if (err)
        {
          if ((err->apr_err == SVN_ERR_RA_NOT_AUTHORIZED)
              || (err->apr_err == SVN_ERR_RA_DAV_FORBIDDEN))
            {
              svn_error_clear(err);
              continue;
            }
          else
            {
              return svn_error_trace(err);
            }
        }

      for (hi = apr_hash_first(scratch_pool, parent_props);
           hi;
           hi = apr_hash_next(hi))
        {
          const char *name = svn__apr_hash_index_key(hi);
          apr_ssize_t klen = svn__apr_hash_index_klen(hi);
          svn_string_t *value = svn__apr_hash_index_val(hi);

          if (svn_property_kind2(name) == svn_prop_regular_kind)
            {
              name = apr_pstrdup(result_pool, name);
              value = svn_string_dup(value, result_pool);
              apr_hash_set(final_hash, name, klen, value);
            }
        }

      if (apr_hash_count(final_hash))
        {
          svn_prop_inherited_item_t *new_iprop =
            apr_palloc(result_pool, sizeof(*new_iprop));
          new_iprop->path_or_url = svn_uri_skip_ancestor(repos_root_url,
                                                         parent_url,
                                                         result_pool);
          new_iprop->prop_hash = final_hash;
          svn_sort__array_insert(&new_iprop, *inherited_props, 0);
        }
    }

  /* Reparent session back to original URL. */
  SVN_ERR(svn_ra_reparent(session, session_url, scratch_pool));

  svn_pool_destroy(iterpool);
  return SVN_NO_ERROR;
}
