<commit_before>/*
 * ra_plugin.c : the main RA module for local repository access
 *
 * ====================================================================
 *    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 "ra_local.h"
#include "svn_ra.h"
#include "svn_fs.h"
#include "svn_delta.h"
#include "svn_repos.h"
#include "svn_pools.h"
#include "svn_time.h"
#include "svn_props.h"
#include "svn_mergeinfo.h"
#include "svn_path.h"
#include "svn_version.h"

#include "svn_private_config.h"
#include "../libsvn_ra/ra_loader.h"
#include "private/svn_mergeinfo_private.h"
#include "private/svn_repos_private.h"
#include "private/svn_fspath.h"

#define APR_WANT_STRFUNC
#include <apr_want.h>

/*----------------------------------------------------------------*/


/*** Miscellaneous helper functions ***/


/* Pool cleanup handler: ensure that the access descriptor of the
   filesystem (svn_fs_t *) DATA is set to NULL. */
static apr_status_t
cleanup_access(void *data)
{
  svn_error_t *serr;
  svn_fs_t *fs = data;

  serr = svn_fs_set_access(fs, NULL);

  if (serr)
    {
      apr_status_t apr_err = serr->apr_err;
      svn_error_clear(serr);
      return apr_err;
    }

  return APR_SUCCESS;
}


/* Fetch a username for use with SESS, and store it in SESS->username. */
static svn_error_t *
get_username(svn_ra_session_t *session,
             apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  svn_auth_iterstate_t *iterstate;
  svn_fs_access_t *access_ctx;

  /* If we've already found the username don't ask for it again. */
  if (! sess->username)
    {
      /* Get a username somehow, so we have some svn:author property to
         attach to a commit. */
      if (sess->callbacks->auth_baton)
        {
          void *creds;
          svn_auth_cred_username_t *username_creds;
          SVN_ERR(svn_auth_first_credentials(&creds, &iterstate,
                                             SVN_AUTH_CRED_USERNAME,
                                             sess->uuid, /* realmstring */
                                             sess->callbacks->auth_baton,
                                             pool));

          /* No point in calling next_creds(), since that assumes that the
             first_creds() somehow failed to authenticate.  But there's no
             challenge going on, so we use whatever creds we get back on
             the first try. */
          username_creds = creds;
          if (username_creds && username_creds->username)
            {
              sess->username = apr_pstrdup(session->pool,
                                           username_creds->username);
              svn_error_clear(svn_auth_save_credentials(iterstate, pool));
            }
          else
            sess->username = "";
        }
      else
        sess->username = "";
    }

  /* If we have a real username, attach it to the filesystem so that it can
     be used to validate locks.  Even if there already is a user context
     associated, it may contain irrelevant lock tokens, so always create a new.
  */
  if (*sess->username)
    {
      SVN_ERR(svn_fs_create_access(&access_ctx, sess->username,
                                   session->pool));
      SVN_ERR(svn_fs_set_access(sess->fs, access_ctx));

      /* Make sure this context is disassociated when the pool gets
         destroyed. */
      apr_pool_cleanup_register(session->pool, sess->fs, cleanup_access,
                                apr_pool_cleanup_null);
    }

  return SVN_NO_ERROR;
}

/*----------------------------------------------------------------*/


/*** The reporter vtable needed by do_update() and friends ***/

typedef struct reporter_baton_t
{
  svn_ra_local__session_baton_t *sess;
  void *report_baton;

} reporter_baton_t;


static void *
make_reporter_baton(svn_ra_local__session_baton_t *sess,
                    void *report_baton,
                    apr_pool_t *pool)
{
  reporter_baton_t *rbaton = apr_palloc(pool, sizeof(*rbaton));
  rbaton->sess = sess;
  rbaton->report_baton = report_baton;
  return rbaton;
}


static svn_error_t *
reporter_set_path(void *reporter_baton,
                  const char *path,
                  svn_revnum_t revision,
                  svn_depth_t depth,
                  svn_boolean_t start_empty,
                  const char *lock_token,
                  apr_pool_t *pool)
{
  reporter_baton_t *rbaton = reporter_baton;
  return svn_repos_set_path3(rbaton->report_baton, path,
                             revision, depth, start_empty, lock_token, pool);
}


static svn_error_t *
reporter_delete_path(void *reporter_baton,
                     const char *path,
                     apr_pool_t *pool)
{
  reporter_baton_t *rbaton = reporter_baton;
  return svn_repos_delete_path(rbaton->report_baton, path, pool);
}


static svn_error_t *
reporter_link_path(void *reporter_baton,
                   const char *path,
                   const char *url,
                   svn_revnum_t revision,
                   svn_depth_t depth,
                   svn_boolean_t start_empty,
                   const char *lock_token,
                   apr_pool_t *pool)
{
  reporter_baton_t *rbaton = reporter_baton;
  const char *fs_path = NULL;
  const char *repos_url = rbaton->sess->repos_url;

  if (!svn_uri__is_ancestor(repos_url, url))
    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
                             _("'%s'\n"
                               "is not the same repository as\n"
                               "'%s'"), url, rbaton->sess->repos_url);

  /* Skip the repos_url, but keep the last '/' to create an fspath */
  fs_path = svn_uri_skip_ancestor(repos_url, url, pool);
  if (fs_path[0] == '\0')
    fs_path = "/";
  else
    fs_path = apr_pstrcat(pool, "/", fs_path, (char *)NULL);

  return svn_repos_link_path3(rbaton->report_baton, path, fs_path, revision,
                              depth, start_empty, lock_token, pool);
}


static svn_error_t *
reporter_finish_report(void *reporter_baton,
                       apr_pool_t *pool)
{
  reporter_baton_t *rbaton = reporter_baton;
  return svn_repos_finish_report(rbaton->report_baton, pool);
}


static svn_error_t *
reporter_abort_report(void *reporter_baton,
                      apr_pool_t *pool)
{
  reporter_baton_t *rbaton = reporter_baton;
  return svn_repos_abort_report(rbaton->report_baton, pool);
}


static const svn_ra_reporter3_t ra_local_reporter =
{
  reporter_set_path,
  reporter_delete_path,
  reporter_link_path,
  reporter_finish_report,
  reporter_abort_report
};


/* ...
 *
 * Wrap a cancellation editor using SESSION's cancellation function around
 * the supplied EDITOR.  ### Some callers (via svn_ra_do_update2() etc.)
 * don't appear to know that we do this, and are supplying an editor that
 * they have already wrapped with the same cancellation editor, so it ends
 * up double-wrapped.
 *
 * ... */
static svn_error_t *
make_reporter(svn_ra_session_t *session,
              const svn_ra_reporter3_t **reporter,
              void **report_baton,
              svn_revnum_t revision,
              const char *target,
              const char *other_url,
              svn_boolean_t text_deltas,
              svn_depth_t depth,
              svn_boolean_t send_copyfrom_args,
              svn_boolean_t ignore_ancestry,
              const svn_delta_editor_t *editor,
              void *edit_baton,
              apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  void *rbaton;
  const char *other_fs_path = NULL;

  /* Get the HEAD revision if one is not supplied. */
  if (! SVN_IS_VALID_REVNUM(revision))
    SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));

  /* If OTHER_URL was provided, validate it and convert it into a
     regular filesystem path. */
  if (other_url)
    {
      const char *other_relpath
        = svn_uri_skip_ancestor(sess->repos_url, other_url, pool);

      /* Sanity check:  the other_url better be in the same repository as
         the original session url! */
      if (! other_relpath)
        return svn_error_createf
          (SVN_ERR_RA_ILLEGAL_URL, NULL,
           _("'%s'\n"
             "is not the same repository as\n"
             "'%s'"), other_url, sess->repos_url);

      other_fs_path = apr_pstrcat(pool, "/", other_relpath, (char *)NULL);
    }

  /* Pass back our reporter */
  *reporter = &ra_local_reporter;

  SVN_ERR(get_username(session, pool));

  if (sess->callbacks)
    SVN_ERR(svn_delta_get_cancellation_editor(sess->callbacks->cancel_func,
                                              sess->callback_baton,
                                              editor,
                                              edit_baton,
                                              &editor,
                                              &edit_baton,
                                              pool));

  /* Build a reporter baton. */
  SVN_ERR(svn_repos_begin_report2(&rbaton,
                                  revision,
                                  sess->repos,
                                  sess->fs_path->data,
                                  target,
                                  other_fs_path,
                                  text_deltas,
                                  depth,
                                  ignore_ancestry,
                                  send_copyfrom_args,
                                  editor,
                                  edit_baton,
                                  NULL,
                                  NULL,
                                  pool));

  /* Wrap the report baton given us by the repos layer with our own
     reporter baton. */
  *report_baton = make_reporter_baton(sess, rbaton, pool);

  return SVN_NO_ERROR;
}


/*----------------------------------------------------------------*/


/*** Deltification stuff for get_commit_editor() ***/

struct deltify_etc_baton
{
  svn_fs_t *fs;                     /* the fs to deltify in */
  svn_repos_t *repos;               /* repos for unlocking */
  const char *fs_path;              /* fs-path part of split session URL */
  apr_hash_t *lock_tokens;          /* tokens to unlock, if any */
  apr_pool_t *pool;                 /* pool for scratch work */
  svn_commit_callback2_t callback;  /* the original callback */
  void *callback_baton;             /* the original callback's baton */
};

/* This implements 'svn_commit_callback_t'.  Its invokes the original
   (wrapped) callback, but also does deltification on the new revision and
   possibly unlocks committed paths.
   BATON is 'struct deltify_etc_baton *'. */
static svn_error_t *
deltify_etc(const svn_commit_info_t *commit_info,
            void *baton, apr_pool_t *pool)
{
  struct deltify_etc_baton *db = baton;
  svn_error_t *err1 = SVN_NO_ERROR;
  svn_error_t *err2;
  apr_hash_index_t *hi;
  apr_pool_t *iterpool;

  /* Invoke the original callback first, in case someone's waiting to
     know the revision number so they can go off and annotate an
     issue or something. */
  if (db->callback)
    err1 = db->callback(commit_info, db->callback_baton, pool);

  /* Maybe unlock the paths. */
  if (db->lock_tokens)
    {
      iterpool = svn_pool_create(db->pool);
      for (hi = apr_hash_first(db->pool, db->lock_tokens); hi;
           hi = apr_hash_next(hi))
        {
          const void *rel_path;
          void *val;
          const char *abs_path, *token;

          svn_pool_clear(iterpool);
          apr_hash_this(hi, &rel_path, NULL, &val);
          token = val;
          abs_path = svn_fspath__join(db->fs_path, rel_path, iterpool);
          /* We may get errors here if the lock was broken or stolen
             after the commit succeeded.  This is fine and should be
             ignored. */
          svn_error_clear(svn_repos_fs_unlock(db->repos, abs_path, token,
                                              FALSE, iterpool));
        }
      svn_pool_destroy(iterpool);
    }

  /* But, deltification shouldn't be stopped just because someone's
     random callback failed, so proceed unconditionally on to
     deltification. */
  err2 = svn_fs_deltify_revision(db->fs, commit_info->revision, db->pool);

  /* It's more interesting if the original callback failed, so let
     that one dominate. */
  if (err1)
    {
      svn_error_clear(err2);
      return err1;
    }

  return err2;
}

/*----------------------------------------------------------------*/


/*** The RA vtable routines ***/

#define RA_LOCAL_DESCRIPTION \
        N_("Module for accessing a repository on local disk.")

static const char *
svn_ra_local__get_description(void)
{
  return _(RA_LOCAL_DESCRIPTION);
}

static const char * const *
svn_ra_local__get_schemes(apr_pool_t *pool)
{
  static const char *schemes[] = { "file", NULL };

  return schemes;
}

/* Do nothing.
 *
 * Why is this acceptable?  FS warnings used to be used for only
 * two things: failures to close BDB repositories and failures to
 * interact with memcached in FSFS (new in 1.6).  In 1.5 and earlier,
 * we did not call svn_fs_set_warning_func in ra_local, which means
 * that any BDB-closing failure would have led to abort()s; the fact
 * that this hasn't led to huge hues and cries makes it seem likely
 * that this just doesn't happen that often, at least not through
 * ra_local.  And as far as memcached goes, it seems unlikely that
 * somebody is going to go through the trouble of setting up and
 * running memcached servers but then use ra_local access.  So we
 * ignore errors here, so that memcached can use the FS warnings API
 * without crashing ra_local.
 */
static void
ignore_warnings(void *baton,
                svn_error_t *err)
{
#ifdef SVN_DEBUG
  SVN_DBG(("Ignoring FS warning %d\n", err ? err->apr_err : 0));
#endif
  return;
}

static svn_error_t *
svn_ra_local__open(svn_ra_session_t *session,
                   const char **corrected_url,
                   const char *repos_URL,
                   const svn_ra_callbacks2_t *callbacks,
                   void *callback_baton,
                   apr_hash_t *config,
                   apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess;
  const char *fs_path;

  /* We don't support redirections in ra-local. */
  if (corrected_url)
    *corrected_url = NULL;

  /* Allocate and stash the session_sess args we have already. */
  sess = apr_pcalloc(pool, sizeof(*sess));
  sess->callbacks = callbacks;
  sess->callback_baton = callback_baton;

  /* Look through the URL, figure out which part points to the
     repository, and which part is the path *within* the
     repository. */
  SVN_ERR_W(svn_ra_local__split_URL(&(sess->repos),
                                    &(sess->repos_url),
                                    &fs_path,
                                    repos_URL,
                                    session->pool),
            _("Unable to open an ra_local session to URL"));
  sess->fs_path = svn_stringbuf_create(fs_path, session->pool);

  /* Cache the filesystem object from the repos here for
     convenience. */
  sess->fs = svn_repos_fs(sess->repos);

  /* Ignore FS warnings. */
  svn_fs_set_warning_func(sess->fs, ignore_warnings, NULL);

  /* Cache the repository UUID as well */
  SVN_ERR(svn_fs_get_uuid(sess->fs, &sess->uuid, session->pool));

  /* Be sure username is NULL so we know to look it up / ask for it */
  sess->username = NULL;

  session->priv = sess;
  return SVN_NO_ERROR;
}

static svn_error_t *
svn_ra_local__reparent(svn_ra_session_t *session,
                       const char *url,
                       apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  const char *relpath = "";

  /* If the new URL isn't the same as our repository root URL, then
     let's ensure that it's some child of it. */
  if (strcmp(url, sess->repos_url) != 0)
    relpath = svn_uri__is_child(sess->repos_url, url, pool);
  if (! relpath)
    return svn_error_createf
      (SVN_ERR_RA_ILLEGAL_URL, NULL,
       _("URL '%s' is not a child of the session's repository root "
         "URL '%s'"), url, sess->repos_url);

  /* Update our FS_PATH sess member to point to our new
     relative-URL-turned-absolute-filesystem-path. */
  svn_stringbuf_set(sess->fs_path,
                    svn_fspath__canonicalize(relpath, pool));

  return SVN_NO_ERROR;
}

static svn_error_t *
svn_ra_local__get_session_url(svn_ra_session_t *session,
                              const char **url,
                              apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  *url = svn_path_url_add_component2(sess->repos_url,
                                     sess->fs_path->data + 1,
                                     pool);
  return SVN_NO_ERROR;
}

static svn_error_t *
svn_ra_local__get_latest_revnum(svn_ra_session_t *session,
                                svn_revnum_t *latest_revnum,
                                apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  return svn_fs_youngest_rev(latest_revnum, sess->fs, pool);
}

static svn_error_t *
svn_ra_local__get_file_revs(svn_ra_session_t *session,
                            const char *path,
                            svn_revnum_t start,
                            svn_revnum_t end,
                            svn_boolean_t include_merged_revisions,
                            svn_file_rev_handler_t handler,
                            void *handler_baton,
                            apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
  return svn_repos_get_file_revs2(sess->repos, abs_path, start, end,
                                  include_merged_revisions, NULL, NULL,
                                  handler, handler_baton, pool);
}

static svn_error_t *
svn_ra_local__get_dated_revision(svn_ra_session_t *session,
                                 svn_revnum_t *revision,
                                 apr_time_t tm,
                                 apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  return svn_repos_dated_revision(revision, sess->repos, tm, pool);
}


static svn_error_t *
svn_ra_local__change_rev_prop(svn_ra_session_t *session,
                              svn_revnum_t rev,
                              const char *name,
                              const svn_string_t *const *old_value_p,
                              const svn_string_t *value,
                              apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;

  SVN_ERR(get_username(session, pool));
  return svn_repos_fs_change_rev_prop4(sess->repos, rev, sess->username,
                                       name, old_value_p, value, TRUE, TRUE,
                                       NULL, NULL, pool);
}

static svn_error_t *
svn_ra_local__get_uuid(svn_ra_session_t *session,
                       const char **uuid,
                       apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  *uuid = sess->uuid;
  return SVN_NO_ERROR;
}

static svn_error_t *
svn_ra_local__get_repos_root(svn_ra_session_t *session,
                             const char **url,
                             apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  *url = sess->repos_url;
  return SVN_NO_ERROR;
}

static svn_error_t *
svn_ra_local__rev_proplist(svn_ra_session_t *session,
                           svn_revnum_t rev,
                           apr_hash_t **props,
                           apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  return svn_repos_fs_revision_proplist(props, sess->repos, rev,
                                        NULL, NULL, pool);
}

static svn_error_t *
svn_ra_local__rev_prop(svn_ra_session_t *session,
                       svn_revnum_t rev,
                       const char *name,
                       svn_string_t **value,
                       apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  return svn_repos_fs_revision_prop(value, sess->repos, rev, name,
                                    NULL, NULL, pool);
}

static svn_error_t *
svn_ra_local__get_commit_editor(svn_ra_session_t *session,
                                const svn_delta_editor_t **editor,
                                void **edit_baton,
                                apr_hash_t *revprop_table,
                                svn_commit_callback2_t callback,
                                void *callback_baton,
                                apr_hash_t *lock_tokens,
                                svn_boolean_t keep_locks,
                                apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  struct deltify_etc_baton *db = apr_palloc(pool, sizeof(*db));
  apr_hash_index_t *hi;
  svn_fs_access_t *fs_access;

  db->fs = sess->fs;
  db->repos = sess->repos;
  db->fs_path = sess->fs_path->data;
  if (! keep_locks)
    db->lock_tokens = lock_tokens;
  else
    db->lock_tokens = NULL;
  db->pool = pool;
  db->callback = callback;
  db->callback_baton = callback_baton;

  SVN_ERR(get_username(session, pool));

  /* If there are lock tokens to add, do so. */
  if (lock_tokens)
    {
      SVN_ERR(svn_fs_get_access(&fs_access, sess->fs));

      /* If there is no access context, the filesystem will scream if a
         lock is needed. */
      if (fs_access)
        {
          for (hi = apr_hash_first(pool, lock_tokens); hi;
               hi = apr_hash_next(hi))
            {
              void *val;
              const char *abs_path, *token;
              const void *key;

              apr_hash_this(hi, &key, NULL, &val);
              abs_path = svn_fspath__join(sess->fs_path->data, key, pool);
              token = val;
              SVN_ERR(svn_fs_access_add_lock_token2(fs_access,
                                                    abs_path, token));
            }
        }
    }

  /* Copy the revprops table so we can add the username. */
  revprop_table = apr_hash_copy(pool, revprop_table);
  apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR, APR_HASH_KEY_STRING,
               svn_string_create(sess->username, pool));

  /* Get the repos commit-editor */
  return svn_repos_get_commit_editor5
         (editor, edit_baton, sess->repos, NULL,
          svn_path_uri_decode(sess->repos_url, pool), sess->fs_path->data,
          revprop_table, deltify_etc, db, NULL, NULL, pool);
}


static svn_error_t *
svn_ra_local__get_mergeinfo(svn_ra_session_t *session,
                            svn_mergeinfo_catalog_t *catalog,
                            const apr_array_header_t *paths,
                            svn_revnum_t revision,
                            svn_mergeinfo_inheritance_t inherit,
                            svn_boolean_t validate_inherited_mergeinfo,
                            svn_boolean_t include_descendants,
                            apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  svn_mergeinfo_catalog_t tmp_catalog;
  int i;
  apr_array_header_t *abs_paths =
    apr_array_make(pool, 0, sizeof(const char *));

  for (i = 0; i < paths->nelts; i++)
    {
      const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
      APR_ARRAY_PUSH(abs_paths, const char *) =
        svn_fspath__join(sess->fs_path->data, relative_path, pool);
    }

  SVN_ERR(svn_repos_fs_get_mergeinfo2(&tmp_catalog, sess->repos, abs_paths,
                                      revision, inherit,
                                      validate_inherited_mergeinfo,
                                      include_descendants,
                                      NULL, NULL, pool));
  if (apr_hash_count(tmp_catalog) > 0)
    SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(catalog,
                                                      tmp_catalog,
                                                      sess->fs_path->data,
                                                      pool));
  else
    *catalog = NULL;

  return SVN_NO_ERROR;
}


static svn_error_t *
svn_ra_local__do_update(svn_ra_session_t *session,
                        const svn_ra_reporter3_t **reporter,
                        void **report_baton,
                        svn_revnum_t update_revision,
                        const char *update_target,
                        svn_depth_t depth,
                        svn_boolean_t send_copyfrom_args,
                        const svn_delta_editor_t *update_editor,
                        void *update_baton,
                        apr_pool_t *pool)
{
  return make_reporter(session,
                       reporter,
                       report_baton,
                       update_revision,
                       update_target,
                       NULL,
                       TRUE,
                       depth,
                       send_copyfrom_args,
                       FALSE,
                       update_editor,
                       update_baton,
                       pool);
}


static svn_error_t *
svn_ra_local__do_switch(svn_ra_session_t *session,
                        const svn_ra_reporter3_t **reporter,
                        void **report_baton,
                        svn_revnum_t update_revision,
                        const char *update_target,
                        svn_depth_t depth,
                        const char *switch_url,
                        const svn_delta_editor_t *update_editor,
                        void *update_baton,
                        apr_pool_t *pool)
{
  return make_reporter(session,
                       reporter,
                       report_baton,
                       update_revision,
                       update_target,
                       switch_url,
                       TRUE,
                       depth,
                       FALSE,   /* ### TODO(sussman): take new arg */
                       TRUE,
                       update_editor,
                       update_baton,
                       pool);
}


static svn_error_t *
svn_ra_local__do_status(svn_ra_session_t *session,
                        const svn_ra_reporter3_t **reporter,
                        void **report_baton,
                        const char *status_target,
                        svn_revnum_t revision,
                        svn_depth_t depth,
                        const svn_delta_editor_t *status_editor,
                        void *status_baton,
                        apr_pool_t *pool)
{
  return make_reporter(session,
                       reporter,
                       report_baton,
                       revision,
                       status_target,
                       NULL,
                       FALSE,
                       depth,
                       FALSE,
                       FALSE,
                       status_editor,
                       status_baton,
                       pool);
}


static svn_error_t *
svn_ra_local__do_diff(svn_ra_session_t *session,
                      const svn_ra_reporter3_t **reporter,
                      void **report_baton,
                      svn_revnum_t update_revision,
                      const char *update_target,
                      svn_depth_t depth,
                      svn_boolean_t ignore_ancestry,
                      svn_boolean_t text_deltas,
                      const char *switch_url,
                      const svn_delta_editor_t *update_editor,
                      void *update_baton,
                      apr_pool_t *pool)
{
  return make_reporter(session,
                       reporter,
                       report_baton,
                       update_revision,
                       update_target,
                       switch_url,
                       text_deltas,
                       depth,
                       FALSE,
                       ignore_ancestry,
                       update_editor,
                       update_baton,
                       pool);
}


struct log_baton
{
  svn_ra_local__session_baton_t *sess;
  svn_log_entry_receiver_t real_cb;
  void *real_baton;
};

static svn_error_t *
log_receiver_wrapper(void *baton,
                     svn_log_entry_t *log_entry,
                     apr_pool_t *pool)
{
  struct log_baton *b = baton;
  svn_ra_local__session_baton_t *sess = b->sess;

  if (sess->callbacks->cancel_func)
    SVN_ERR((sess->callbacks->cancel_func)(sess->callback_baton));

  /* For consistency with the other RA layers, replace an empty
     changed-paths hash with a NULL one. */
  if ((log_entry->changed_paths2)
      && (apr_hash_count(log_entry->changed_paths2) == 0))
    {
      log_entry->changed_paths = NULL;
      log_entry->changed_paths2 = NULL;
    }

  return b->real_cb(b->real_baton, log_entry, pool);
}


static svn_error_t *
svn_ra_local__get_log(svn_ra_session_t *session,
                      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,<commit_msg>* subversion/libsvn_ra_local/ra_plugin.c   (log_receiver_wrapper): Comment tweak only.<commit_after>/*
 * ra_plugin.c : the main RA module for local repository access
 *
 * ====================================================================
 *    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 "ra_local.h"
#include "svn_ra.h"
#include "svn_fs.h"
#include "svn_delta.h"
#include "svn_repos.h"
#include "svn_pools.h"
#include "svn_time.h"
#include "svn_props.h"
#include "svn_mergeinfo.h"
#include "svn_path.h"
#include "svn_version.h"

#include "svn_private_config.h"
#include "../libsvn_ra/ra_loader.h"
#include "private/svn_mergeinfo_private.h"
#include "private/svn_repos_private.h"
#include "private/svn_fspath.h"

#define APR_WANT_STRFUNC
#include <apr_want.h>

/*----------------------------------------------------------------*/


/*** Miscellaneous helper functions ***/


/* Pool cleanup handler: ensure that the access descriptor of the
   filesystem (svn_fs_t *) DATA is set to NULL. */
static apr_status_t
cleanup_access(void *data)
{
  svn_error_t *serr;
  svn_fs_t *fs = data;

  serr = svn_fs_set_access(fs, NULL);

  if (serr)
    {
      apr_status_t apr_err = serr->apr_err;
      svn_error_clear(serr);
      return apr_err;
    }

  return APR_SUCCESS;
}


/* Fetch a username for use with SESS, and store it in SESS->username. */
static svn_error_t *
get_username(svn_ra_session_t *session,
             apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  svn_auth_iterstate_t *iterstate;
  svn_fs_access_t *access_ctx;

  /* If we've already found the username don't ask for it again. */
  if (! sess->username)
    {
      /* Get a username somehow, so we have some svn:author property to
         attach to a commit. */
      if (sess->callbacks->auth_baton)
        {
          void *creds;
          svn_auth_cred_username_t *username_creds;
          SVN_ERR(svn_auth_first_credentials(&creds, &iterstate,
                                             SVN_AUTH_CRED_USERNAME,
                                             sess->uuid, /* realmstring */
                                             sess->callbacks->auth_baton,
                                             pool));

          /* No point in calling next_creds(), since that assumes that the
             first_creds() somehow failed to authenticate.  But there's no
             challenge going on, so we use whatever creds we get back on
             the first try. */
          username_creds = creds;
          if (username_creds && username_creds->username)
            {
              sess->username = apr_pstrdup(session->pool,
                                           username_creds->username);
              svn_error_clear(svn_auth_save_credentials(iterstate, pool));
            }
          else
            sess->username = "";
        }
      else
        sess->username = "";
    }

  /* If we have a real username, attach it to the filesystem so that it can
     be used to validate locks.  Even if there already is a user context
     associated, it may contain irrelevant lock tokens, so always create a new.
  */
  if (*sess->username)
    {
      SVN_ERR(svn_fs_create_access(&access_ctx, sess->username,
                                   session->pool));
      SVN_ERR(svn_fs_set_access(sess->fs, access_ctx));

      /* Make sure this context is disassociated when the pool gets
         destroyed. */
      apr_pool_cleanup_register(session->pool, sess->fs, cleanup_access,
                                apr_pool_cleanup_null);
    }

  return SVN_NO_ERROR;
}

/*----------------------------------------------------------------*/


/*** The reporter vtable needed by do_update() and friends ***/

typedef struct reporter_baton_t
{
  svn_ra_local__session_baton_t *sess;
  void *report_baton;

} reporter_baton_t;


static void *
make_reporter_baton(svn_ra_local__session_baton_t *sess,
                    void *report_baton,
                    apr_pool_t *pool)
{
  reporter_baton_t *rbaton = apr_palloc(pool, sizeof(*rbaton));
  rbaton->sess = sess;
  rbaton->report_baton = report_baton;
  return rbaton;
}


static svn_error_t *
reporter_set_path(void *reporter_baton,
                  const char *path,
                  svn_revnum_t revision,
                  svn_depth_t depth,
                  svn_boolean_t start_empty,
                  const char *lock_token,
                  apr_pool_t *pool)
{
  reporter_baton_t *rbaton = reporter_baton;
  return svn_repos_set_path3(rbaton->report_baton, path,
                             revision, depth, start_empty, lock_token, pool);
}


static svn_error_t *
reporter_delete_path(void *reporter_baton,
                     const char *path,
                     apr_pool_t *pool)
{
  reporter_baton_t *rbaton = reporter_baton;
  return svn_repos_delete_path(rbaton->report_baton, path, pool);
}


static svn_error_t *
reporter_link_path(void *reporter_baton,
                   const char *path,
                   const char *url,
                   svn_revnum_t revision,
                   svn_depth_t depth,
                   svn_boolean_t start_empty,
                   const char *lock_token,
                   apr_pool_t *pool)
{
  reporter_baton_t *rbaton = reporter_baton;
  const char *fs_path = NULL;
  const char *repos_url = rbaton->sess->repos_url;

  if (!svn_uri__is_ancestor(repos_url, url))
    return svn_error_createf(SVN_ERR_RA_ILLEGAL_URL, NULL,
                             _("'%s'\n"
                               "is not the same repository as\n"
                               "'%s'"), url, rbaton->sess->repos_url);

  /* Skip the repos_url, but keep the last '/' to create an fspath */
  fs_path = svn_uri_skip_ancestor(repos_url, url, pool);
  if (fs_path[0] == '\0')
    fs_path = "/";
  else
    fs_path = apr_pstrcat(pool, "/", fs_path, (char *)NULL);

  return svn_repos_link_path3(rbaton->report_baton, path, fs_path, revision,
                              depth, start_empty, lock_token, pool);
}


static svn_error_t *
reporter_finish_report(void *reporter_baton,
                       apr_pool_t *pool)
{
  reporter_baton_t *rbaton = reporter_baton;
  return svn_repos_finish_report(rbaton->report_baton, pool);
}


static svn_error_t *
reporter_abort_report(void *reporter_baton,
                      apr_pool_t *pool)
{
  reporter_baton_t *rbaton = reporter_baton;
  return svn_repos_abort_report(rbaton->report_baton, pool);
}


static const svn_ra_reporter3_t ra_local_reporter =
{
  reporter_set_path,
  reporter_delete_path,
  reporter_link_path,
  reporter_finish_report,
  reporter_abort_report
};


/* ...
 *
 * Wrap a cancellation editor using SESSION's cancellation function around
 * the supplied EDITOR.  ### Some callers (via svn_ra_do_update2() etc.)
 * don't appear to know that we do this, and are supplying an editor that
 * they have already wrapped with the same cancellation editor, so it ends
 * up double-wrapped.
 *
 * ... */
static svn_error_t *
make_reporter(svn_ra_session_t *session,
              const svn_ra_reporter3_t **reporter,
              void **report_baton,
              svn_revnum_t revision,
              const char *target,
              const char *other_url,
              svn_boolean_t text_deltas,
              svn_depth_t depth,
              svn_boolean_t send_copyfrom_args,
              svn_boolean_t ignore_ancestry,
              const svn_delta_editor_t *editor,
              void *edit_baton,
              apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  void *rbaton;
  const char *other_fs_path = NULL;

  /* Get the HEAD revision if one is not supplied. */
  if (! SVN_IS_VALID_REVNUM(revision))
    SVN_ERR(svn_fs_youngest_rev(&revision, sess->fs, pool));

  /* If OTHER_URL was provided, validate it and convert it into a
     regular filesystem path. */
  if (other_url)
    {
      const char *other_relpath
        = svn_uri_skip_ancestor(sess->repos_url, other_url, pool);

      /* Sanity check:  the other_url better be in the same repository as
         the original session url! */
      if (! other_relpath)
        return svn_error_createf
          (SVN_ERR_RA_ILLEGAL_URL, NULL,
           _("'%s'\n"
             "is not the same repository as\n"
             "'%s'"), other_url, sess->repos_url);

      other_fs_path = apr_pstrcat(pool, "/", other_relpath, (char *)NULL);
    }

  /* Pass back our reporter */
  *reporter = &ra_local_reporter;

  SVN_ERR(get_username(session, pool));

  if (sess->callbacks)
    SVN_ERR(svn_delta_get_cancellation_editor(sess->callbacks->cancel_func,
                                              sess->callback_baton,
                                              editor,
                                              edit_baton,
                                              &editor,
                                              &edit_baton,
                                              pool));

  /* Build a reporter baton. */
  SVN_ERR(svn_repos_begin_report2(&rbaton,
                                  revision,
                                  sess->repos,
                                  sess->fs_path->data,
                                  target,
                                  other_fs_path,
                                  text_deltas,
                                  depth,
                                  ignore_ancestry,
                                  send_copyfrom_args,
                                  editor,
                                  edit_baton,
                                  NULL,
                                  NULL,
                                  pool));

  /* Wrap the report baton given us by the repos layer with our own
     reporter baton. */
  *report_baton = make_reporter_baton(sess, rbaton, pool);

  return SVN_NO_ERROR;
}


/*----------------------------------------------------------------*/


/*** Deltification stuff for get_commit_editor() ***/

struct deltify_etc_baton
{
  svn_fs_t *fs;                     /* the fs to deltify in */
  svn_repos_t *repos;               /* repos for unlocking */
  const char *fs_path;              /* fs-path part of split session URL */
  apr_hash_t *lock_tokens;          /* tokens to unlock, if any */
  apr_pool_t *pool;                 /* pool for scratch work */
  svn_commit_callback2_t callback;  /* the original callback */
  void *callback_baton;             /* the original callback's baton */
};

/* This implements 'svn_commit_callback_t'.  Its invokes the original
   (wrapped) callback, but also does deltification on the new revision and
   possibly unlocks committed paths.
   BATON is 'struct deltify_etc_baton *'. */
static svn_error_t *
deltify_etc(const svn_commit_info_t *commit_info,
            void *baton, apr_pool_t *pool)
{
  struct deltify_etc_baton *db = baton;
  svn_error_t *err1 = SVN_NO_ERROR;
  svn_error_t *err2;
  apr_hash_index_t *hi;
  apr_pool_t *iterpool;

  /* Invoke the original callback first, in case someone's waiting to
     know the revision number so they can go off and annotate an
     issue or something. */
  if (db->callback)
    err1 = db->callback(commit_info, db->callback_baton, pool);

  /* Maybe unlock the paths. */
  if (db->lock_tokens)
    {
      iterpool = svn_pool_create(db->pool);
      for (hi = apr_hash_first(db->pool, db->lock_tokens); hi;
           hi = apr_hash_next(hi))
        {
          const void *rel_path;
          void *val;
          const char *abs_path, *token;

          svn_pool_clear(iterpool);
          apr_hash_this(hi, &rel_path, NULL, &val);
          token = val;
          abs_path = svn_fspath__join(db->fs_path, rel_path, iterpool);
          /* We may get errors here if the lock was broken or stolen
             after the commit succeeded.  This is fine and should be
             ignored. */
          svn_error_clear(svn_repos_fs_unlock(db->repos, abs_path, token,
                                              FALSE, iterpool));
        }
      svn_pool_destroy(iterpool);
    }

  /* But, deltification shouldn't be stopped just because someone's
     random callback failed, so proceed unconditionally on to
     deltification. */
  err2 = svn_fs_deltify_revision(db->fs, commit_info->revision, db->pool);

  /* It's more interesting if the original callback failed, so let
     that one dominate. */
  if (err1)
    {
      svn_error_clear(err2);
      return err1;
    }

  return err2;
}

/*----------------------------------------------------------------*/


/*** The RA vtable routines ***/

#define RA_LOCAL_DESCRIPTION \
        N_("Module for accessing a repository on local disk.")

static const char *
svn_ra_local__get_description(void)
{
  return _(RA_LOCAL_DESCRIPTION);
}

static const char * const *
svn_ra_local__get_schemes(apr_pool_t *pool)
{
  static const char *schemes[] = { "file", NULL };

  return schemes;
}

/* Do nothing.
 *
 * Why is this acceptable?  FS warnings used to be used for only
 * two things: failures to close BDB repositories and failures to
 * interact with memcached in FSFS (new in 1.6).  In 1.5 and earlier,
 * we did not call svn_fs_set_warning_func in ra_local, which means
 * that any BDB-closing failure would have led to abort()s; the fact
 * that this hasn't led to huge hues and cries makes it seem likely
 * that this just doesn't happen that often, at least not through
 * ra_local.  And as far as memcached goes, it seems unlikely that
 * somebody is going to go through the trouble of setting up and
 * running memcached servers but then use ra_local access.  So we
 * ignore errors here, so that memcached can use the FS warnings API
 * without crashing ra_local.
 */
static void
ignore_warnings(void *baton,
                svn_error_t *err)
{
#ifdef SVN_DEBUG
  SVN_DBG(("Ignoring FS warning %d\n", err ? err->apr_err : 0));
#endif
  return;
}

static svn_error_t *
svn_ra_local__open(svn_ra_session_t *session,
                   const char **corrected_url,
                   const char *repos_URL,
                   const svn_ra_callbacks2_t *callbacks,
                   void *callback_baton,
                   apr_hash_t *config,
                   apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess;
  const char *fs_path;

  /* We don't support redirections in ra-local. */
  if (corrected_url)
    *corrected_url = NULL;

  /* Allocate and stash the session_sess args we have already. */
  sess = apr_pcalloc(pool, sizeof(*sess));
  sess->callbacks = callbacks;
  sess->callback_baton = callback_baton;

  /* Look through the URL, figure out which part points to the
     repository, and which part is the path *within* the
     repository. */
  SVN_ERR_W(svn_ra_local__split_URL(&(sess->repos),
                                    &(sess->repos_url),
                                    &fs_path,
                                    repos_URL,
                                    session->pool),
            _("Unable to open an ra_local session to URL"));
  sess->fs_path = svn_stringbuf_create(fs_path, session->pool);

  /* Cache the filesystem object from the repos here for
     convenience. */
  sess->fs = svn_repos_fs(sess->repos);

  /* Ignore FS warnings. */
  svn_fs_set_warning_func(sess->fs, ignore_warnings, NULL);

  /* Cache the repository UUID as well */
  SVN_ERR(svn_fs_get_uuid(sess->fs, &sess->uuid, session->pool));

  /* Be sure username is NULL so we know to look it up / ask for it */
  sess->username = NULL;

  session->priv = sess;
  return SVN_NO_ERROR;
}

static svn_error_t *
svn_ra_local__reparent(svn_ra_session_t *session,
                       const char *url,
                       apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  const char *relpath = "";

  /* If the new URL isn't the same as our repository root URL, then
     let's ensure that it's some child of it. */
  if (strcmp(url, sess->repos_url) != 0)
    relpath = svn_uri__is_child(sess->repos_url, url, pool);
  if (! relpath)
    return svn_error_createf
      (SVN_ERR_RA_ILLEGAL_URL, NULL,
       _("URL '%s' is not a child of the session's repository root "
         "URL '%s'"), url, sess->repos_url);

  /* Update our FS_PATH sess member to point to our new
     relative-URL-turned-absolute-filesystem-path. */
  svn_stringbuf_set(sess->fs_path,
                    svn_fspath__canonicalize(relpath, pool));

  return SVN_NO_ERROR;
}

static svn_error_t *
svn_ra_local__get_session_url(svn_ra_session_t *session,
                              const char **url,
                              apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  *url = svn_path_url_add_component2(sess->repos_url,
                                     sess->fs_path->data + 1,
                                     pool);
  return SVN_NO_ERROR;
}

static svn_error_t *
svn_ra_local__get_latest_revnum(svn_ra_session_t *session,
                                svn_revnum_t *latest_revnum,
                                apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  return svn_fs_youngest_rev(latest_revnum, sess->fs, pool);
}

static svn_error_t *
svn_ra_local__get_file_revs(svn_ra_session_t *session,
                            const char *path,
                            svn_revnum_t start,
                            svn_revnum_t end,
                            svn_boolean_t include_merged_revisions,
                            svn_file_rev_handler_t handler,
                            void *handler_baton,
                            apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  const char *abs_path = svn_fspath__join(sess->fs_path->data, path, pool);
  return svn_repos_get_file_revs2(sess->repos, abs_path, start, end,
                                  include_merged_revisions, NULL, NULL,
                                  handler, handler_baton, pool);
}

static svn_error_t *
svn_ra_local__get_dated_revision(svn_ra_session_t *session,
                                 svn_revnum_t *revision,
                                 apr_time_t tm,
                                 apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  return svn_repos_dated_revision(revision, sess->repos, tm, pool);
}


static svn_error_t *
svn_ra_local__change_rev_prop(svn_ra_session_t *session,
                              svn_revnum_t rev,
                              const char *name,
                              const svn_string_t *const *old_value_p,
                              const svn_string_t *value,
                              apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;

  SVN_ERR(get_username(session, pool));
  return svn_repos_fs_change_rev_prop4(sess->repos, rev, sess->username,
                                       name, old_value_p, value, TRUE, TRUE,
                                       NULL, NULL, pool);
}

static svn_error_t *
svn_ra_local__get_uuid(svn_ra_session_t *session,
                       const char **uuid,
                       apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  *uuid = sess->uuid;
  return SVN_NO_ERROR;
}

static svn_error_t *
svn_ra_local__get_repos_root(svn_ra_session_t *session,
                             const char **url,
                             apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  *url = sess->repos_url;
  return SVN_NO_ERROR;
}

static svn_error_t *
svn_ra_local__rev_proplist(svn_ra_session_t *session,
                           svn_revnum_t rev,
                           apr_hash_t **props,
                           apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  return svn_repos_fs_revision_proplist(props, sess->repos, rev,
                                        NULL, NULL, pool);
}

static svn_error_t *
svn_ra_local__rev_prop(svn_ra_session_t *session,
                       svn_revnum_t rev,
                       const char *name,
                       svn_string_t **value,
                       apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  return svn_repos_fs_revision_prop(value, sess->repos, rev, name,
                                    NULL, NULL, pool);
}

static svn_error_t *
svn_ra_local__get_commit_editor(svn_ra_session_t *session,
                                const svn_delta_editor_t **editor,
                                void **edit_baton,
                                apr_hash_t *revprop_table,
                                svn_commit_callback2_t callback,
                                void *callback_baton,
                                apr_hash_t *lock_tokens,
                                svn_boolean_t keep_locks,
                                apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  struct deltify_etc_baton *db = apr_palloc(pool, sizeof(*db));
  apr_hash_index_t *hi;
  svn_fs_access_t *fs_access;

  db->fs = sess->fs;
  db->repos = sess->repos;
  db->fs_path = sess->fs_path->data;
  if (! keep_locks)
    db->lock_tokens = lock_tokens;
  else
    db->lock_tokens = NULL;
  db->pool = pool;
  db->callback = callback;
  db->callback_baton = callback_baton;

  SVN_ERR(get_username(session, pool));

  /* If there are lock tokens to add, do so. */
  if (lock_tokens)
    {
      SVN_ERR(svn_fs_get_access(&fs_access, sess->fs));

      /* If there is no access context, the filesystem will scream if a
         lock is needed. */
      if (fs_access)
        {
          for (hi = apr_hash_first(pool, lock_tokens); hi;
               hi = apr_hash_next(hi))
            {
              void *val;
              const char *abs_path, *token;
              const void *key;

              apr_hash_this(hi, &key, NULL, &val);
              abs_path = svn_fspath__join(sess->fs_path->data, key, pool);
              token = val;
              SVN_ERR(svn_fs_access_add_lock_token2(fs_access,
                                                    abs_path, token));
            }
        }
    }

  /* Copy the revprops table so we can add the username. */
  revprop_table = apr_hash_copy(pool, revprop_table);
  apr_hash_set(revprop_table, SVN_PROP_REVISION_AUTHOR, APR_HASH_KEY_STRING,
               svn_string_create(sess->username, pool));

  /* Get the repos commit-editor */
  return svn_repos_get_commit_editor5
         (editor, edit_baton, sess->repos, NULL,
          svn_path_uri_decode(sess->repos_url, pool), sess->fs_path->data,
          revprop_table, deltify_etc, db, NULL, NULL, pool);
}


static svn_error_t *
svn_ra_local__get_mergeinfo(svn_ra_session_t *session,
                            svn_mergeinfo_catalog_t *catalog,
                            const apr_array_header_t *paths,
                            svn_revnum_t revision,
                            svn_mergeinfo_inheritance_t inherit,
                            svn_boolean_t validate_inherited_mergeinfo,
                            svn_boolean_t include_descendants,
                            apr_pool_t *pool)
{
  svn_ra_local__session_baton_t *sess = session->priv;
  svn_mergeinfo_catalog_t tmp_catalog;
  int i;
  apr_array_header_t *abs_paths =
    apr_array_make(pool, 0, sizeof(const char *));

  for (i = 0; i < paths->nelts; i++)
    {
      const char *relative_path = APR_ARRAY_IDX(paths, i, const char *);
      APR_ARRAY_PUSH(abs_paths, const char *) =
        svn_fspath__join(sess->fs_path->data, relative_path, pool);
    }

  SVN_ERR(svn_repos_fs_get_mergeinfo2(&tmp_catalog, sess->repos, abs_paths,
                                      revision, inherit,
                                      validate_inherited_mergeinfo,
                                      include_descendants,
                                      NULL, NULL, pool));
  if (apr_hash_count(tmp_catalog) > 0)
    SVN_ERR(svn_mergeinfo__remove_prefix_from_catalog(catalog,
                                                      tmp_catalog,
                                                      sess->fs_path->data,
                                                      pool));
  else
    *catalog = NULL;

  return SVN_NO_ERROR;
}


static svn_error_t *
svn_ra_local__do_update(svn_ra_session_t *session,
                        const svn_ra_reporter3_t **reporter,
                        void **report_baton,
                        svn_revnum_t update_revision,
                        const char *update_target,
                        svn_depth_t depth,
                        svn_boolean_t send_copyfrom_args,
                        const svn_delta_editor_t *update_editor,
                        void *update_baton,
                        apr_pool_t *pool)
{
  return make_reporter(session,
                       reporter,
                       report_baton,
                       update_revision,
                       update_target,
                       NULL,
                       TRUE,
                       depth,
                       send_copyfrom_args,
                       FALSE,
                       update_editor,
                       update_baton,
                       pool);
}


static svn_error_t *
svn_ra_local__do_switch(svn_ra_session_t *session,
                        const svn_ra_reporter3_t **reporter,
                        void **report_baton,
                        svn_revnum_t update_revision,
                        const char *update_target,
                        svn_depth_t depth,
                        const char *switch_url,
                        const svn_delta_editor_t *update_editor,
                        void *update_baton,
                        apr_pool_t *pool)
{
  return make_reporter(session,
                       reporter,
                       report_baton,
                       update_revision,
                       update_target,
                       switch_url,
                       TRUE,
                       depth,
                       FALSE,   /* ### TODO(sussman): take new arg */
                       TRUE,
                       update_editor,
                       update_baton,
                       pool);
}


static svn_error_t *
svn_ra_local__do_status(svn_ra_session_t *session,
                        const svn_ra_reporter3_t **reporter,
                        void **report_baton,
                        const char *status_target,
                        svn_revnum_t revision,
                        svn_depth_t depth,
                        const svn_delta_editor_t *status_editor,
                        void *status_baton,
                        apr_pool_t *pool)
{
  return make_reporter(session,
                       reporter,
                       report_baton,
                       revision,
                       status_target,
                       NULL,
                       FALSE,
                       depth,
                       FALSE,
                       FALSE,
                       status_editor,
                       status_baton,
                       pool);
}


static svn_error_t *
svn_ra_local__do_diff(svn_ra_session_t *session,
                      const svn_ra_reporter3_t **reporter,
                      void **report_baton,
                      svn_revnum_t update_revision,
                      const char *update_target,
                      svn_depth_t depth,
                      svn_boolean_t ignore_ancestry,
                      svn_boolean_t text_deltas,
                      const char *switch_url,
                      const svn_delta_editor_t *update_editor,
                      void *update_baton,
                      apr_pool_t *pool)
{
  return make_reporter(session,
                       reporter,
                       report_baton,
                       update_revision,
                       update_target,
                       switch_url,
                       text_deltas,
                       depth,
                       FALSE,
                       ignore_ancestry,
                       update_editor,
                       update_baton,
                       pool);
}


struct log_baton
{
  svn_ra_local__session_baton_t *sess;
  svn_log_entry_receiver_t real_cb;
  void *real_baton;
};

static svn_error_t *
log_receiver_wrapper(void *baton,
                     svn_log_entry_t *log_entry,
                     apr_pool_t *pool)
{
  struct log_baton *b = baton;
  svn_ra_local__session_baton_t *sess = b->sess;

  if (sess->callbacks->cancel_func)
    SVN_ERR((sess->callbacks->cancel_func)(sess->callback_baton));

  /* For consistency with the other RA layers, replace an empty
     changed-paths hash with a NULL one.

     ### Should this be done by svn_ra_get_log2() instead, then? */
  if ((log_entry->changed_paths2)
      && (apr_hash_count(log_entry->changed_paths2) == 0))
    {
      log_entry->changed_paths = NULL;
      log_entry->changed_paths2 = NULL;
    }

  return b->real_cb(b->real_baton, log_entry, pool);
}


static svn_error_t *
svn_ra_local__get_log(svn_ra_session_t *session,
                      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,
{'fraction_non_alphanumeric': 0.08917438436610682, 'fraction_numerical': 0.001562731325360662, 'mean_word_length': 1.7284233203177595, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 285, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 134, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '916968', 'n_tokens_mistral': 18807, 'n_tokens_neox': 18106, 'n_words': 4174}