<commit_before>/*
 * editor.c:  Editor for modifying FS transactions
 *
 * ====================================================================
 *    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_types.h"
#include "svn_error.h"
#include "svn_pools.h"
#include "svn_editor.h"
#include "svn_fs.h"
#include "svn_props.h"

#include "svn_private_config.h"

#include "fs-loader.h"


struct edit_baton {
  /* The transaction associated with this editor.  */
  svn_fs_txn_t *txn;

  /* Has this editor been completed?  */
  svn_boolean_t completed;

  /* We sometimes need the cancellation beyond what svn_editor_t provides  */
  svn_cancel_func_t cancel_func;
  void *cancel_baton;

  /* The pool that the txn lives within. When we create a ROOT, it will
     be allocated within a subpool of this. The root will be closed in
     complete/abort and that subpool will be destroyed.

     This pool SHOULD NOT be used for any allocations.  */
  apr_pool_t *txn_pool;

  /* This is the root from the txn. Use get_root() to fetch/create this
     member as appropriate.  */
  svn_fs_root_t *root;
};

#define FSPATH(relpath, pool) apr_pstrcat(pool, "/", relpath, NULL)
#define UNUSED(x) ((void)(x))


static svn_error_t *
get_root(svn_fs_root_t **root,
         struct edit_baton *eb)
{
  if (eb->root == NULL)
    SVN_ERR(svn_fs_txn_root(&eb->root, eb->txn, eb->txn_pool));
  *root = eb->root;
  return SVN_NO_ERROR;
}


/* Apply each property in PROPS to the node at FSPATH in ROOT.  */
static svn_error_t *
add_new_props(svn_fs_root_t *root,
              const char *fspath,
              apr_hash_t *props,
              apr_pool_t *scratch_pool)
{
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
  apr_hash_index_t *hi;

  /* ### it would be nice to have svn_fs_set_node_props(). but since we
     ### don't... add each property to the node. this is a new node, so
     ### we don't need to worry about deleting props. just adding.  */

  for (hi = apr_hash_first(scratch_pool, props); hi;
       hi = apr_hash_next(hi))
    {
      const char *name = svn__apr_hash_index_key(hi);
      const svn_string_t *value = svn__apr_hash_index_val(hi);

      svn_pool_clear(iterpool);

      SVN_ERR(svn_fs_change_node_prop(root, fspath, name, value, iterpool));
    }

  svn_pool_destroy(iterpool);
  return SVN_NO_ERROR;
}


static svn_error_t *
alter_props(svn_fs_root_t *root,
            const char *fspath,
            apr_hash_t *props,
            apr_pool_t *scratch_pool)
{
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
  apr_hash_t *old_props;
  apr_array_header_t *propdiffs;
  int i;

  SVN_ERR(svn_fs_node_proplist(&old_props, root, fspath, scratch_pool));

  SVN_ERR(svn_prop_diffs(&propdiffs, props, old_props, scratch_pool));

  for (i = 0; i < propdiffs->nelts; ++i)
    {
      const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);

      svn_pool_clear(iterpool);

      /* Add, change, or delete properties.  */
      SVN_ERR(svn_fs_change_node_prop(root, fspath, prop->name, prop->value,
                                      iterpool));
    }

  svn_pool_destroy(iterpool);
  return SVN_NO_ERROR;
}


static svn_error_t *
set_text(svn_fs_root_t *root,
         const char *fspath,
         const svn_checksum_t *checksum,
         svn_stream_t *contents,
         svn_cancel_func_t cancel_func,
         void *cancel_baton,
         apr_pool_t *scratch_pool)
{
  svn_stream_t *fs_contents;

  /* ### We probably don't have an MD5 checksum, so no digest is available
     ### for svn_fs_apply_text() to validate. It would be nice to have an
     ### FS API that takes our CHECKSUM/CONTENTS pair (and PROPS!).  */
  SVN_ERR(svn_fs_apply_text(&fs_contents, root, fspath,
                            NULL /* result_checksum */,
                            scratch_pool));
  SVN_ERR(svn_stream_copy3(contents, fs_contents,
                           cancel_func, cancel_baton,
                           scratch_pool));

  return SVN_NO_ERROR;
}


/* This implements svn_editor_cb_add_directory_t */
static svn_error_t *
add_directory_cb(void *baton,
                 const char *relpath,
                 const apr_array_header_t *children,
                 apr_hash_t *props,
                 svn_revnum_t replaces_rev,
                 apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *fspath = FSPATH(relpath, scratch_pool);
  svn_fs_root_t *root;

  /* Note: we ignore CHILDREN. We have no "incomplete" state to worry about,
     so we don't need to be aware of what children will be created.  */

  SVN_ERR(get_root(&root, eb));

  /* ### validate REPLACES_REV  */
  /* ### we probably have to delete first?  */

  SVN_ERR(svn_fs_make_dir(root, fspath, scratch_pool));
  SVN_ERR(add_new_props(root, fspath, props, scratch_pool));

  return SVN_NO_ERROR;
}


/* This implements svn_editor_cb_add_file_t */
static svn_error_t *
add_file_cb(void *baton,
            const char *relpath,
            const svn_checksum_t *checksum,
            svn_stream_t *contents,
            apr_hash_t *props,
            svn_revnum_t replaces_rev,
            apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *fspath = FSPATH(relpath, scratch_pool);
  svn_fs_root_t *root;

  SVN_ERR(get_root(&root, eb));

  /* ### validate REPLACES_REV  */
  /* ### we probably have to delete first?  */

  SVN_ERR(svn_fs_make_file(root, fspath, scratch_pool));

  SVN_ERR(set_text(root, fspath, checksum, contents,
                   eb->cancel_func, eb->cancel_baton, scratch_pool));
  SVN_ERR(add_new_props(root, fspath, props, scratch_pool));

  return SVN_NO_ERROR;
}


/* This implements svn_editor_cb_add_symlink_t */
static svn_error_t *
add_symlink_cb(void *baton,
               const char *relpath,
               const char *target,
               apr_hash_t *props,
               svn_revnum_t replaces_rev,
               apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *fspath = FSPATH(relpath, scratch_pool);
  svn_fs_root_t *root;
  svn_stream_t *fs_contents;

  SVN_ERR(get_root(&root, eb));

  /* ### validate REPLACES_REV  */
  /* ### we probably have to delete first?  */

  /* ### we probably need to construct a file with specific contents
     ### (until the FS grows some symlink APIs)  */
#if 0
  SVN_ERR(svn_fs_make_file(root, fspath, scratch_pool));
  SVN_ERR(svn_fs_apply_text(&fs_contents, root, fspath,
                            NULL /* result_checksum */,
                            scratch_pool));
  /* ### SVN_ERR(svn_stream_printf(fs_contents, ..., scratch_pool));  */
  apr_hash_set(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING,
               SVN_PROP_SPECIAL_VALUE);

  SVN_ERR(add_new_props(root, fspath, props, scratch_pool));
#endif
  UNUSED(fspath); UNUSED(fs_contents);

  SVN__NOT_IMPLEMENTED();
}


/* This implements svn_editor_cb_add_absent_t */
static svn_error_t *
add_absent_cb(void *baton,
              const char *relpath,
              svn_kind_t kind,
              svn_revnum_t replaces_rev,
              apr_pool_t *scratch_pool)
{
  /* This is a programming error. Code should not attempt to create these
     kinds of nodes within the FS.  */
  return svn_error_create(
           SVN_ERR_UNSUPPORTED_FEATURE, NULL,
           N_("The filesystem does not support 'absent' nodes"));
}


/* This implements svn_editor_cb_alter_directory_t */
static svn_error_t *
alter_directory_cb(void *baton,
                   const char *relpath,
                   svn_revnum_t revision,
                   apr_hash_t *props,
                   apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *fspath = FSPATH(relpath, scratch_pool);
  svn_fs_root_t *root;

  SVN_ERR(get_root(&root, eb));

  /* ### validate the revision  */

  SVN_ERR(alter_props(root, fspath, props, scratch_pool));

  return SVN_NO_ERROR;
}


/* This implements svn_editor_cb_alter_file_t */
static svn_error_t *
alter_file_cb(void *baton,
              const char *relpath,
              svn_revnum_t revision,
              apr_hash_t *props,
              const svn_checksum_t *checksum,
              svn_stream_t *contents,
              apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *fspath = FSPATH(relpath, scratch_pool);
  svn_fs_root_t *root;

  SVN_ERR(get_root(&root, eb));

  /* ### validate the revision  */

  SVN_ERR(set_text(root, fspath, checksum, contents,
                   eb->cancel_func, eb->cancel_baton, scratch_pool));
  SVN_ERR(alter_props(root, fspath, props, scratch_pool));

  return SVN_NO_ERROR;
}


/* This implements svn_editor_cb_alter_symlink_t */
static svn_error_t *
alter_symlink_cb(void *baton,
                 const char *relpath,
                 svn_revnum_t revision,
                 apr_hash_t *props,
                 const char *target,
                 apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;

  UNUSED(eb); SVN__NOT_IMPLEMENTED();
}


/* This implements svn_editor_cb_delete_t */
static svn_error_t *
delete_cb(void *baton,
          const char *relpath,
          svn_revnum_t revision,
          apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *fspath = FSPATH(relpath, scratch_pool);
  svn_fs_root_t *root;

  SVN_ERR(get_root(&root, eb));

  /* ### validate the revision  */

  SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));

  return SVN_NO_ERROR;
}


/* This implements svn_editor_cb_copy_t */
static svn_error_t *
copy_cb(void *baton,
        const char *src_relpath,
        svn_revnum_t src_revision,
        const char *dst_relpath,
        svn_revnum_t replaces_rev,
        apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *src_fspath = FSPATH(src_relpath, scratch_pool);
  const char *dst_fspath = FSPATH(dst_relpath, scratch_pool);
  svn_fs_root_t *root;
  svn_fs_root_t *src_root;

  SVN_ERR(get_root(&root, eb));

  /* ### validate the REPLACES_REV  */
  /* ### can we copy over the top of something, or delete first?  */

  SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision,
                               scratch_pool));
  SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool));
  svn_fs_close_root(src_root);

  return SVN_NO_ERROR;
}


/* This implements svn_editor_cb_move_t */
static svn_error_t *
move_cb(void *baton,
        const char *src_relpath,
        svn_revnum_t src_revision,
        const char *dst_relpath,
        svn_revnum_t replaces_rev,
        apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *src_fspath = FSPATH(src_relpath, scratch_pool);
  const char *dst_fspath = FSPATH(dst_relpath, scratch_pool);
  svn_fs_root_t *root;
  svn_fs_root_t *src_root;

  SVN_ERR(get_root(&root, eb));

  /* ### validate the SRC_REVISION  */
  /* ### validate the REPLACES_REV  */
  /* ### can we copy over the top of something, or delete first?  */

  /* ### would be nice to have svn_fs_move()  */

  /* Copy the src to the dst. */
  SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision,
                               scratch_pool));
  SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool));
  svn_fs_close_root(src_root);

  /* Notice: we're deleting the src repos path from the dst root. */<commit_msg>Add out-of-date checks to the FS commit editor.<commit_after>/*
 * editor.c:  Editor for modifying FS transactions
 *
 * ====================================================================
 *    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_types.h"
#include "svn_error.h"
#include "svn_pools.h"
#include "svn_editor.h"
#include "svn_fs.h"
#include "svn_props.h"

#include "svn_private_config.h"

#include "fs-loader.h"


struct edit_baton {
  /* The transaction associated with this editor.  */
  svn_fs_txn_t *txn;

  /* Has this editor been completed?  */
  svn_boolean_t completed;

  /* We sometimes need the cancellation beyond what svn_editor_t provides  */
  svn_cancel_func_t cancel_func;
  void *cancel_baton;

  /* The pool that the txn lives within. When we create a ROOT, it will
     be allocated within a subpool of this. The root will be closed in
     complete/abort and that subpool will be destroyed.

     This pool SHOULD NOT be used for any allocations.  */
  apr_pool_t *txn_pool;

  /* This is the root from the txn. Use get_root() to fetch/create this
     member as appropriate.  */
  svn_fs_root_t *root;
};

#define FSPATH(relpath, pool) apr_pstrcat(pool, "/", relpath, NULL)
#define UNUSED(x) ((void)(x))


static svn_error_t *
get_root(svn_fs_root_t **root,
         struct edit_baton *eb)
{
  if (eb->root == NULL)
    SVN_ERR(svn_fs_txn_root(&eb->root, eb->txn, eb->txn_pool));
  *root = eb->root;
  return SVN_NO_ERROR;
}


/* Apply each property in PROPS to the node at FSPATH in ROOT.  */
static svn_error_t *
add_new_props(svn_fs_root_t *root,
              const char *fspath,
              apr_hash_t *props,
              apr_pool_t *scratch_pool)
{
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
  apr_hash_index_t *hi;

  /* ### it would be nice to have svn_fs_set_node_props(). but since we
     ### don't... add each property to the node. this is a new node, so
     ### we don't need to worry about deleting props. just adding.  */

  for (hi = apr_hash_first(scratch_pool, props); hi;
       hi = apr_hash_next(hi))
    {
      const char *name = svn__apr_hash_index_key(hi);
      const svn_string_t *value = svn__apr_hash_index_val(hi);

      svn_pool_clear(iterpool);

      SVN_ERR(svn_fs_change_node_prop(root, fspath, name, value, iterpool));
    }

  svn_pool_destroy(iterpool);
  return SVN_NO_ERROR;
}


static svn_error_t *
alter_props(svn_fs_root_t *root,
            const char *fspath,
            apr_hash_t *props,
            apr_pool_t *scratch_pool)
{
  apr_pool_t *iterpool = svn_pool_create(scratch_pool);
  apr_hash_t *old_props;
  apr_array_header_t *propdiffs;
  int i;

  SVN_ERR(svn_fs_node_proplist(&old_props, root, fspath, scratch_pool));

  SVN_ERR(svn_prop_diffs(&propdiffs, props, old_props, scratch_pool));

  for (i = 0; i < propdiffs->nelts; ++i)
    {
      const svn_prop_t *prop = &APR_ARRAY_IDX(propdiffs, i, svn_prop_t);

      svn_pool_clear(iterpool);

      /* Add, change, or delete properties.  */
      SVN_ERR(svn_fs_change_node_prop(root, fspath, prop->name, prop->value,
                                      iterpool));
    }

  svn_pool_destroy(iterpool);
  return SVN_NO_ERROR;
}


static svn_error_t *
set_text(svn_fs_root_t *root,
         const char *fspath,
         const svn_checksum_t *checksum,
         svn_stream_t *contents,
         svn_cancel_func_t cancel_func,
         void *cancel_baton,
         apr_pool_t *scratch_pool)
{
  svn_stream_t *fs_contents;

  /* ### We probably don't have an MD5 checksum, so no digest is available
     ### for svn_fs_apply_text() to validate. It would be nice to have an
     ### FS API that takes our CHECKSUM/CONTENTS pair (and PROPS!).  */
  SVN_ERR(svn_fs_apply_text(&fs_contents, root, fspath,
                            NULL /* result_checksum */,
                            scratch_pool));
  SVN_ERR(svn_stream_copy3(contents, fs_contents,
                           cancel_func, cancel_baton,
                           scratch_pool));

  return SVN_NO_ERROR;
}


/* The caller wants to modify REVISION of FSPATH. Is that allowed?  */
static svn_error_t *
can_modify(svn_fs_root_t *txn_root,
           const char *fspath,
           svn_revnum_t revision,
           apr_pool_t *scratch_pool)
{
  svn_revnum_t created_rev;

  /* Out-of-dateness check:  compare the created-rev of the noden
     in the txn against the created-rev of FSPATH.  */
  SVN_ERR(svn_fs_node_created_rev(&created_rev, txn_root, fspath,
                                  scratch_pool));

  /* If CREATED_REV is invalid, that means it's already mutable in the
     txn, which means it has already passed this out-of-dateness check.
     (Usually, this happens when looking at a parent directory of an
     already-modified node)  */
  if (SVN_IS_VALID_REVNUM(created_rev) && revision != created_rev)
    {
      if (revision < created_rev)
        {
          /* We asked to change a node that is *older* than what we found
             in the transaction. The client is out of date.  */
          return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
                                   _("'%s' is out of date; try updating"),
                                   fspath);
        }

      /* revision > created_rev  */
      {
        /* We asked to change a node that is *newer* than what we found
           in the transaction. Given that the transaction was based off
           of 'youngest', then either:
           - the caller asked to modify a future node
           - the caller has committed more revisions since this txn
           was constructed, and is asking to modify a node in one
           of those new revisions.
           In either case, the node may not have changed in those new
           revisions; use the node's ID to determine this case.  */
        const svn_fs_id_t *txn_noderev_id;
        svn_fs_root_t *rev_root;
        const svn_fs_id_t *new_noderev_id;

        /* The ID of the node that we would be modifying in the txn  */
        SVN_ERR(svn_fs_node_id(&txn_noderev_id, txn_root, fspath,
                               scratch_pool));

        /* Get the ID from the future/new revision.  */
        SVN_ERR(svn_fs_revision_root(&rev_root, svn_fs_root_fs(txn_root),
                                     revision, scratch_pool));
        SVN_ERR(svn_fs_node_id(&new_noderev_id, rev_root, fspath,
                               scratch_pool));
        svn_fs_close_root(rev_root);

        /* Has the target node changed in the future?  */
        if (svn_fs_compare_ids(txn_noderev_id, new_noderev_id) != 0)
          {
            /* Restarting the commit will base the txn on the future/new
               revision, allowing the modification at REVISION.  */
            /* ### use a custom error code  */
            return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
                                     _("'%s' has been modified since the "
                                       "commit began (restart the commit)"),
                                     fspath);
          }
      }
    }

  return SVN_NO_ERROR;
}


/* This implements svn_editor_cb_add_directory_t */
static svn_error_t *
add_directory_cb(void *baton,
                 const char *relpath,
                 const apr_array_header_t *children,
                 apr_hash_t *props,
                 svn_revnum_t replaces_rev,
                 apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *fspath = FSPATH(relpath, scratch_pool);
  svn_fs_root_t *root;

  /* Note: we ignore CHILDREN. We have no "incomplete" state to worry about,
     so we don't need to be aware of what children will be created.  */

  SVN_ERR(get_root(&root, eb));

  if (SVN_IS_VALID_REVNUM(replaces_rev))
    {
      SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
      SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
    }
  /* else better not be there!  */

  SVN_ERR(svn_fs_make_dir(root, fspath, scratch_pool));
  SVN_ERR(add_new_props(root, fspath, props, scratch_pool));

  return SVN_NO_ERROR;
}


/* This implements svn_editor_cb_add_file_t */
static svn_error_t *
add_file_cb(void *baton,
            const char *relpath,
            const svn_checksum_t *checksum,
            svn_stream_t *contents,
            apr_hash_t *props,
            svn_revnum_t replaces_rev,
            apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *fspath = FSPATH(relpath, scratch_pool);
  svn_fs_root_t *root;

  SVN_ERR(get_root(&root, eb));

  if (SVN_IS_VALID_REVNUM(replaces_rev))
    {
      SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
      SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
    }
  /* else better not be there!  */

  SVN_ERR(svn_fs_make_file(root, fspath, scratch_pool));

  SVN_ERR(set_text(root, fspath, checksum, contents,
                   eb->cancel_func, eb->cancel_baton, scratch_pool));
  SVN_ERR(add_new_props(root, fspath, props, scratch_pool));

  return SVN_NO_ERROR;
}


/* This implements svn_editor_cb_add_symlink_t */
static svn_error_t *
add_symlink_cb(void *baton,
               const char *relpath,
               const char *target,
               apr_hash_t *props,
               svn_revnum_t replaces_rev,
               apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *fspath = FSPATH(relpath, scratch_pool);
  svn_fs_root_t *root;

  SVN_ERR(get_root(&root, eb));

  if (SVN_IS_VALID_REVNUM(replaces_rev))
    {
      SVN_ERR(can_modify(root, fspath, replaces_rev, scratch_pool));
      SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));
    }
  /* else better not be there!  */

  /* ### we probably need to construct a file with specific contents
     ### (until the FS grows some symlink APIs)  */
#if 0
  SVN_ERR(svn_fs_make_file(root, fspath, scratch_pool));
  SVN_ERR(svn_fs_apply_text(&fs_contents, root, fspath,
                            NULL /* result_checksum */,
                            scratch_pool));
  /* ### SVN_ERR(svn_stream_printf(fs_contents, ..., scratch_pool));  */
  apr_hash_set(props, SVN_PROP_SPECIAL, APR_HASH_KEY_STRING,
               SVN_PROP_SPECIAL_VALUE);

  SVN_ERR(add_new_props(root, fspath, props, scratch_pool));
#endif

  SVN__NOT_IMPLEMENTED();
}


/* This implements svn_editor_cb_add_absent_t */
static svn_error_t *
add_absent_cb(void *baton,
              const char *relpath,
              svn_kind_t kind,
              svn_revnum_t replaces_rev,
              apr_pool_t *scratch_pool)
{
  /* This is a programming error. Code should not attempt to create these
     kinds of nodes within the FS.  */
  /* ### use a custom error code  */
  return svn_error_create(
           SVN_ERR_UNSUPPORTED_FEATURE, NULL,
           N_("The filesystem does not support 'absent' nodes"));
}


/* This implements svn_editor_cb_alter_directory_t */
static svn_error_t *
alter_directory_cb(void *baton,
                   const char *relpath,
                   svn_revnum_t revision,
                   apr_hash_t *props,
                   apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *fspath = FSPATH(relpath, scratch_pool);
  svn_fs_root_t *root;

  if (!SVN_IS_VALID_REVNUM(revision))
    /* ### use a custom error code?  */
    return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
                             N_("Revision for modification of '%s' "
                                "is required"),
                             fspath);

  SVN_ERR(get_root(&root, eb));
  SVN_ERR(can_modify(root, fspath, revision, scratch_pool));

  SVN_ERR(alter_props(root, fspath, props, scratch_pool));

  return SVN_NO_ERROR;
}


/* This implements svn_editor_cb_alter_file_t */
static svn_error_t *
alter_file_cb(void *baton,
              const char *relpath,
              svn_revnum_t revision,
              apr_hash_t *props,
              const svn_checksum_t *checksum,
              svn_stream_t *contents,
              apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *fspath = FSPATH(relpath, scratch_pool);
  svn_fs_root_t *root;

  if (!SVN_IS_VALID_REVNUM(revision))
    /* ### use a custom error code?  */
    return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
                             N_("Revision for modification of '%s' "
                                "is required"),
                             fspath);

  SVN_ERR(get_root(&root, eb));
  SVN_ERR(can_modify(root, fspath, revision, scratch_pool));

  SVN_ERR(set_text(root, fspath, checksum, contents,
                   eb->cancel_func, eb->cancel_baton, scratch_pool));
  SVN_ERR(alter_props(root, fspath, props, scratch_pool));

  return SVN_NO_ERROR;
}


/* This implements svn_editor_cb_alter_symlink_t */
static svn_error_t *
alter_symlink_cb(void *baton,
                 const char *relpath,
                 svn_revnum_t revision,
                 apr_hash_t *props,
                 const char *target,
                 apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;

  UNUSED(eb); SVN__NOT_IMPLEMENTED();
}


/* This implements svn_editor_cb_delete_t */
static svn_error_t *
delete_cb(void *baton,
          const char *relpath,
          svn_revnum_t revision,
          apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *fspath = FSPATH(relpath, scratch_pool);
  svn_fs_root_t *root;

  if (!SVN_IS_VALID_REVNUM(revision))
    /* ### use a custom error code?  */
    return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
                             N_("Revision for deletion of '%s' "
                                "is required"),
                             fspath);

  SVN_ERR(get_root(&root, eb));
  SVN_ERR(can_modify(root, fspath, revision, scratch_pool));

  SVN_ERR(svn_fs_delete(root, fspath, scratch_pool));

  return SVN_NO_ERROR;
}


/* This implements svn_editor_cb_copy_t */
static svn_error_t *
copy_cb(void *baton,
        const char *src_relpath,
        svn_revnum_t src_revision,
        const char *dst_relpath,
        svn_revnum_t replaces_rev,
        apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *src_fspath = FSPATH(src_relpath, scratch_pool);
  const char *dst_fspath = FSPATH(dst_relpath, scratch_pool);
  svn_fs_root_t *root;
  svn_fs_root_t *src_root;

  if (!SVN_IS_VALID_REVNUM(src_revision))
    /* ### use a custom error code?  */
    return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
                             N_("Source revision for copy of '%s' "
                                "is required"),
                             src_fspath);

  SVN_ERR(get_root(&root, eb));

  /* Check if we can we replace the maybe-specified destination (revision).  */
  if (SVN_IS_VALID_REVNUM(replaces_rev))
    {
      SVN_ERR(can_modify(root, dst_fspath, replaces_rev, scratch_pool));
      SVN_ERR(svn_fs_delete(root, dst_fspath, scratch_pool));
    }

  SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision,
                               scratch_pool));
  SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool));
  svn_fs_close_root(src_root);

  return SVN_NO_ERROR;
}


/* This implements svn_editor_cb_move_t */
static svn_error_t *
move_cb(void *baton,
        const char *src_relpath,
        svn_revnum_t src_revision,
        const char *dst_relpath,
        svn_revnum_t replaces_rev,
        apr_pool_t *scratch_pool)
{
  struct edit_baton *eb = baton;
  const char *src_fspath = FSPATH(src_relpath, scratch_pool);
  const char *dst_fspath = FSPATH(dst_relpath, scratch_pool);
  svn_fs_root_t *root;
  svn_fs_root_t *src_root;

  if (!SVN_IS_VALID_REVNUM(src_revision))
    /* ### use a custom error code?  */
    return svn_error_createf(SVN_ERR_INCORRECT_PARAMS, NULL,
                             N_("Source revision for move of '%s' "
                                "is required"),
                             src_fspath);

  SVN_ERR(get_root(&root, eb));

  /* Check if we delete the specified source (revision), and can we replace
     the maybe-specified destination (revision).  */
  SVN_ERR(can_modify(root, src_fspath, src_revision, scratch_pool));
  if (SVN_IS_VALID_REVNUM(replaces_rev))
    {
      SVN_ERR(can_modify(root, dst_fspath, replaces_rev, scratch_pool));
      SVN_ERR(svn_fs_delete(root, dst_fspath, scratch_pool));
    }

  /* ### would be nice to have svn_fs_move()  */

  /* Copy the src to the dst. */
  SVN_ERR(svn_fs_revision_root(&src_root, svn_fs_root_fs(root), src_revision,
                               scratch_pool));
  SVN_ERR(svn_fs_copy(src_root, src_fspath, root, dst_fspath, scratch_pool));
  svn_fs_close_root(src_root);

  /* Notice: we're deleting the src repos path from the dst root. */
{'fraction_non_alphanumeric': 0.10011695906432748, 'fraction_numerical': 0.0005680868838763576, 'mean_word_length': 2.7402824646919135, 'pattern_counts': {'":': 0, '<': 8, '<?xml version=': 0, '>': 30, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 14, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5499851', 'n_tokens_mistral': 10540, 'n_tokens_neox': 10367, 'n_words': 2473}