Exemple #1
0
svn_error_t *
svn_repos_fs_commit_txn(const char **conflict_p,
                        svn_repos_t *repos,
                        svn_revnum_t *new_rev,
                        svn_fs_txn_t *txn,
                        apr_pool_t *pool)
{
  svn_error_t *err;
  const char *txn_name;

  /* Run pre-commit hooks. */
  SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
  SVN_ERR(svn_repos__hooks_pre_commit(repos, txn_name, pool));

  /* Commit. */
  SVN_ERR(svn_fs_commit_txn(conflict_p, new_rev, txn, pool));

  /* Run post-commit hooks.   Notice that we're wrapping the error
     with a -specific- errorcode, so that our caller knows not to try
     and abort the transaction. */
  if ((err = svn_repos__hooks_post_commit(repos, *new_rev, pool)))
    return svn_error_create
      (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
       _("Commit succeeded, but post-commit hook failed"));

  return SVN_NO_ERROR;
}
Exemple #2
0
svn_error_t *
svn_repos_fs_begin_txn_for_commit2(svn_fs_txn_t **txn_p,
                                   svn_repos_t *repos,
                                   svn_revnum_t rev,
                                   apr_hash_t *revprop_table,
                                   apr_pool_t *pool)
{
    apr_array_header_t *revprops;
    const char *txn_name;
    svn_string_t *author = svn_hash_gets(revprop_table, SVN_PROP_REVISION_AUTHOR);
    apr_hash_t *hooks_env;

    /* Parse the hooks-env file (if any). */
    SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
                                       pool, pool));

    /* Begin the transaction, ask for the fs to do on-the-fly lock checks.
       We fetch its name, too, so the start-commit hook can use it.  */
    SVN_ERR(svn_fs_begin_txn2(txn_p, repos->fs, rev,
                              SVN_FS_TXN_CHECK_LOCKS, pool));
    SVN_ERR(svn_fs_txn_name(&txn_name, *txn_p, pool));

    /* We pass the revision properties to the filesystem by adding them
       as properties on the txn.  Later, when we commit the txn, these
       properties will be copied into the newly created revision. */
    revprops = svn_prop_hash_to_array(revprop_table, pool);
    SVN_ERR(svn_repos_fs_change_txn_props(*txn_p, revprops, pool));

    /* Run start-commit hooks. */
    SVN_ERR(svn_repos__hooks_start_commit(repos, hooks_env,
                                          author ? author->data : NULL,
                                          repos->client_capabilities, txn_name,
                                          pool));
    return SVN_NO_ERROR;
}
Exemple #3
0
svn_error_t *
svn_repos_fs_commit_txn(const char **conflict_p,
                        svn_repos_t *repos,
                        svn_revnum_t *new_rev,
                        svn_fs_txn_t *txn,
                        apr_pool_t *pool)
{
  svn_error_t *err, *err2;
  const char *txn_name;

  *new_rev = SVN_INVALID_REVNUM;

  /* Run pre-commit hooks. */
  SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
  SVN_ERR(svn_repos__hooks_pre_commit(repos, txn_name, pool));

  /* Commit. */
  err = svn_fs_commit_txn(conflict_p, new_rev, txn, pool);
  if (! SVN_IS_VALID_REVNUM(*new_rev))
    return err;

  /* Run post-commit hooks. */
  if ((err2 = svn_repos__hooks_post_commit(repos, *new_rev, pool)))
    {
      err2 = svn_error_create
               (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err2,
                _("Commit succeeded, but post-commit hook failed"));
    }

  return svn_error_compose_create(err, err2);
}
Exemple #4
0
svn_error_t *
svn_repos_fs_commit_txn(const char **conflict_p,
                        svn_repos_t *repos,
                        svn_revnum_t *new_rev,
                        svn_fs_txn_t *txn,
                        apr_pool_t *pool)
{
    svn_error_t *err, *err2;
    const char *txn_name;
    apr_hash_t *props;
    apr_pool_t *iterpool;
    apr_hash_index_t *hi;
    apr_hash_t *hooks_env;

    *new_rev = SVN_INVALID_REVNUM;

    /* Parse the hooks-env file (if any). */
    SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, repos->hooks_env_path,
                                       pool, pool));

    /* Run pre-commit hooks. */
    SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
    SVN_ERR(svn_repos__hooks_pre_commit(repos, hooks_env, txn_name, pool));

    /* Remove any ephemeral transaction properties. */
    SVN_ERR(svn_fs_txn_proplist(&props, txn, pool));
    iterpool = svn_pool_create(pool);
    for (hi = apr_hash_first(pool, props); hi; hi = apr_hash_next(hi))
    {
        const void *key;
        apr_hash_this(hi, &key, NULL, NULL);

        svn_pool_clear(iterpool);

        if (strncmp(key, SVN_PROP_TXN_PREFIX,
                    (sizeof(SVN_PROP_TXN_PREFIX) - 1)) == 0)
        {
            SVN_ERR(svn_fs_change_txn_prop(txn, key, NULL, iterpool));
        }
    }
    svn_pool_destroy(iterpool);

    /* Commit. */
    err = svn_fs_commit_txn(conflict_p, new_rev, txn, pool);
    if (! SVN_IS_VALID_REVNUM(*new_rev))
        return err;

    /* Run post-commit hooks. */
    if ((err2 = svn_repos__hooks_post_commit(repos, hooks_env,
                *new_rev, txn_name, pool)))
    {
        err2 = svn_error_create
               (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err2,
                _("Commit succeeded, but post-commit hook failed"));
    }

    return svn_error_compose_create(err, err2);
}
Exemple #5
0
static svn_error_t *
open_root(void *edit_baton,
          svn_revnum_t base_revision,
          apr_pool_t *pool,
          void **root_baton)
{
  struct dir_baton *dirb;
  struct edit_baton *eb = edit_baton;
  svn_revnum_t youngest;

  /* Ignore BASE_REVISION.  We always build our transaction against
     HEAD.  However, we will keep it in our dir baton for out of
     dateness checks.  */
  SVN_ERR(svn_fs_youngest_rev(&youngest, eb->fs, eb->pool));

  if (base_revision > youngest)
    return svn_error_createf(SVN_ERR_FS_NO_SUCH_REVISION, NULL,
                             _("No such revision %ld (HEAD is %ld)"),
                             base_revision, youngest);

  /* Unless we've been instructed to use a specific transaction, we'll
     make our own. */
  if (eb->txn_owner)
    {
      SVN_ERR(svn_repos_fs_begin_txn_for_commit2(&(eb->txn),
                                                 eb->repos,
                                                 youngest,
                                                 eb->revprop_table,
                                                 eb->pool));
    }
  else /* Even if we aren't the owner of the transaction, we might
          have been instructed to set some properties. */
    {
      apr_array_header_t *props = svn_prop_hash_to_array(eb->revprop_table,
                                                         pool);
      SVN_ERR(svn_repos_fs_change_txn_props(eb->txn, props, pool));
    }
  SVN_ERR(svn_fs_txn_name(&(eb->txn_name), eb->txn, eb->pool));
  SVN_ERR(svn_fs_txn_root(&(eb->txn_root), eb->txn, eb->pool));

  /* Create a root dir baton.  The `base_path' field is an -absolute-
     path in the filesystem, upon which all further editor paths are
     based. */
  dirb = apr_pcalloc(pool, sizeof(*dirb));
  dirb->edit_baton = edit_baton;
  dirb->parent = NULL;
  dirb->pool = pool;
  dirb->was_copied = FALSE;
  dirb->path = apr_pstrdup(pool, eb->base_path);
  dirb->base_rev = base_revision;

  *root_baton = dirb;
  return SVN_NO_ERROR;
}
Exemple #6
0
dav_error *
dav_svn__create_txn(dav_svn_repos *repos,
                    const char **ptxn_name,
                    apr_hash_t *revprops,
                    apr_pool_t *pool)
{
  svn_revnum_t rev;
  svn_fs_txn_t *txn;
  svn_error_t *serr;

  if (! revprops)
    {
      revprops = apr_hash_make(pool);
    }

  if (repos->username)
    {
      svn_hash_sets(revprops,
                    SVN_PROP_REVISION_AUTHOR,
                    svn_string_create(repos->username, pool));
    }

  serr = dav_svn__get_youngest_rev(&rev, repos, pool);
  if (serr != NULL)
    {
      return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                  "could not determine youngest revision",
                                  repos->pool);
    }

  serr = svn_repos_fs_begin_txn_for_commit2(&txn, repos->repos, rev,
                                            revprops, repos->pool);
  if (serr != NULL)
    {
      return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                  "could not begin a transaction",
                                  repos->pool);
    }

  serr = svn_fs_txn_name(ptxn_name, txn, pool);
  if (serr != NULL)
    {
      return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                  "could not fetch transaction name",
                                  repos->pool);
    }

  return NULL;
}
Exemple #7
0
svn_error_t *
svn_fs__editor_create(svn_editor_t **editor,
                      const char **txn_name,
                      svn_fs_t *fs,
                      apr_uint32_t flags,
                      svn_cancel_func_t cancel_func,
                      void *cancel_baton,
                      apr_pool_t *result_pool,
                      apr_pool_t *scratch_pool)
{
    svn_revnum_t revision;
    svn_fs_txn_t *txn;

    SVN_ERR(svn_fs_youngest_rev(&revision, fs, scratch_pool));
    SVN_ERR(svn_fs_begin_txn2(&txn, fs, revision, flags, result_pool));
    SVN_ERR(svn_fs_txn_name(txn_name, txn, result_pool));
    return svn_error_trace(make_editor(editor, txn,
                                       cancel_func, cancel_baton,
                                       result_pool, scratch_pool));
}
Exemple #8
0
dav_error *
dav_svn__create_activity(const dav_svn_repos *repos,
                         const char **ptxn_name,
                         apr_pool_t *pool)
{
  svn_revnum_t rev;
  svn_fs_txn_t *txn;
  svn_error_t *serr;

  serr = svn_fs_youngest_rev(&rev, repos->fs, pool);
  if (serr != NULL)
    {
      return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                  "could not determine youngest revision",
                                  repos->pool);
    }

  serr = svn_repos_fs_begin_txn_for_commit(&txn, repos->repos, rev,
                                           repos->username, NULL,
                                           repos->pool);
  if (serr != NULL)
    {
      return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                  "could not begin a transaction",
                                  repos->pool);
    }

  serr = svn_fs_txn_name(ptxn_name, txn, pool);
  if (serr != NULL)
    {
      return dav_svn__convert_err(serr, HTTP_INTERNAL_SERVER_ERROR,
                                  "could not fetch transaction name",
                                  repos->pool);
    }

  return NULL;
}
Exemple #9
0
static svn_error_t *
close_revision(void *baton)
{
  struct revision_baton *rb = baton;
  struct parse_baton *pb = rb->pb;
  const char *conflict_msg = NULL;
  svn_revnum_t committed_rev;
  svn_error_t *err;
  const char *txn_name = NULL;
  apr_hash_t *hooks_env;

  /* If we're skipping this revision we're done here. */
  if (rb->skipped)
    return SVN_NO_ERROR;

  if (rb->rev == 0)
    {
      /* Special case: set revision 0 properties when loading into an
         'empty' filesystem. */
      svn_revnum_t youngest_rev;

      SVN_ERR(svn_fs_youngest_rev(&youngest_rev, pb->fs, rb->pool));

      if (youngest_rev == 0)
        {
          apr_hash_t *orig_props;
          apr_hash_t *new_props;
          apr_array_header_t *diff;
          int i;

          SVN_ERR(svn_fs_revision_proplist(&orig_props, pb->fs, 0, rb->pool));
          new_props = svn_prop_array_to_hash(rb->revprops, rb->pool);
          SVN_ERR(svn_prop_diffs(&diff, new_props, orig_props, rb->pool));

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

              SVN_ERR(change_rev_prop(pb->repos, 0, prop->name, prop->value,
                                      pb->validate_props, rb->pool));
          }
        }

      return SVN_NO_ERROR;
    }

  /* If the dumpstream doesn't have an 'svn:date' property and we
     aren't ignoring the dates in the dumpstream altogether, remove
     any 'svn:date' revision property that was set by FS layer when
     the TXN was created.  */
  if (! (pb->ignore_dates || rb->datestamp))
    {
      svn_prop_t *prop = &APR_ARRAY_PUSH(rb->revprops, svn_prop_t);
      prop->name = SVN_PROP_REVISION_DATE;
      prop->value = NULL;
    }

  /* Apply revision property changes. */
  if (rb->pb->validate_props)
    SVN_ERR(svn_repos_fs_change_txn_props(rb->txn, rb->revprops, rb->pool));
  else
    SVN_ERR(svn_fs_change_txn_props(rb->txn, rb->revprops, rb->pool));

  /* Get the txn name and hooks environment if they will be needed. */
  if (pb->use_pre_commit_hook || pb->use_post_commit_hook)
    {
      SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, pb->repos->hooks_env_path,
                                         rb->pool, rb->pool));

      err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool);
      if (err)
        {
          svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
          return svn_error_trace(err);
        }
    }

  /* Run the pre-commit hook, if so commanded. */
  if (pb->use_pre_commit_hook)
    {
      err = svn_repos__hooks_pre_commit(pb->repos, hooks_env,
                                        txn_name, rb->pool);
      if (err)
        {
          svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
          return svn_error_trace(err);
        }
    }

  /* Commit. */
  err = svn_fs_commit_txn(&conflict_msg, &committed_rev, rb->txn, rb->pool);
  if (SVN_IS_VALID_REVNUM(committed_rev))
    {
      if (err)
        {
          /* ### Log any error, but better yet is to rev
             ### close_revision()'s API to allow both committed_rev and err
             ### to be returned, see #3768. */
          svn_error_clear(err);
        }
    }
  else
    {
      svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
      if (conflict_msg)
        return svn_error_quick_wrap(err, conflict_msg);
      else
        return svn_error_trace(err);
    }

  /* Run post-commit hook, if so commanded.  */
  if (pb->use_post_commit_hook)
    {
      if ((err = svn_repos__hooks_post_commit(pb->repos, hooks_env,
                                              committed_rev, txn_name,
                                              rb->pool)))
        return svn_error_create
          (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
           _("Commit succeeded, but post-commit hook failed"));
    }

  /* After a successful commit, must record the dump-rev -> in-repos-rev
     mapping, so that copyfrom instructions in the dump file can look up the
     correct repository revision to copy from. */
  set_revision_mapping(pb->rev_map, rb->rev, committed_rev);

  /* If the incoming dump stream has non-contiguous revisions (e.g. from
     using svndumpfilter --drop-empty-revs without --renumber-revs) then
     we must account for the missing gaps in PB->REV_MAP.  Otherwise we
     might not be able to map all mergeinfo source revisions to the correct
     revisions in the target repos. */
  if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
      && (rb->rev != pb->last_rev_mapped + 1))
    {
      svn_revnum_t i;

      for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
        {
          set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
        }
    }

  /* Update our "last revision mapped". */
  pb->last_rev_mapped = rb->rev;

  /* Deltify the predecessors of paths changed in this revision. */
  SVN_ERR(svn_fs_deltify_revision(pb->fs, committed_rev, rb->pool));

  if (pb->notify_func)
    {
      /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
      svn_repos_notify_t *notify = svn_repos_notify_create(
                                        svn_repos_notify_load_txn_committed,
                                        pb->notify_pool);

      notify->new_revision = committed_rev;
      notify->old_revision = ((committed_rev == rb->rev)
                                    ? SVN_INVALID_REVNUM
                                    : rb->rev);
      pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
      svn_pool_clear(pb->notify_pool);
    }

  return SVN_NO_ERROR;
}
Exemple #10
0
static svn_error_t *
close_revision(void *baton)
{
  struct revision_baton *rb = baton;
  struct parse_baton *pb = rb->pb;
  const char *conflict_msg = NULL;
  svn_revnum_t committed_rev;
  svn_error_t *err;
  const char *txn_name = NULL;
  apr_hash_t *hooks_env;

  /* If we're skipping this revision or it has an invalid revision
     number, we're done here. */
  if (rb->skipped || (rb->rev <= 0))
    return SVN_NO_ERROR;

  /* Get the txn name and hooks environment if they will be needed. */
  if (pb->use_pre_commit_hook || pb->use_post_commit_hook)
    {
      SVN_ERR(svn_repos__parse_hooks_env(&hooks_env, pb->repos->hooks_env_path,
                                         rb->pool, rb->pool));

      err = svn_fs_txn_name(&txn_name, rb->txn, rb->pool);
      if (err)
        {
          svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
          return svn_error_trace(err);
        }
    }

  /* Run the pre-commit hook, if so commanded. */
  if (pb->use_pre_commit_hook)
    {
      err = svn_repos__hooks_pre_commit(pb->repos, hooks_env,
                                        txn_name, rb->pool);
      if (err)
        {
          svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
          return svn_error_trace(err);
        }
    }

  /* Commit. */
  err = svn_fs_commit_txn(&conflict_msg, &committed_rev, rb->txn, rb->pool);
  if (SVN_IS_VALID_REVNUM(committed_rev))
    {
      if (err)
        {
          /* ### Log any error, but better yet is to rev
             ### close_revision()'s API to allow both committed_rev and err
             ### to be returned, see #3768. */
          svn_error_clear(err);
        }
    }
  else
    {
      svn_error_clear(svn_fs_abort_txn(rb->txn, rb->pool));
      if (conflict_msg)
        return svn_error_quick_wrap(err, conflict_msg);
      else
        return svn_error_trace(err);
    }

  /* Run post-commit hook, if so commanded.  */
  if (pb->use_post_commit_hook)
    {
      if ((err = svn_repos__hooks_post_commit(pb->repos, hooks_env,
                                              committed_rev, txn_name,
                                              rb->pool)))
        return svn_error_create
          (SVN_ERR_REPOS_POST_COMMIT_HOOK_FAILED, err,
           _("Commit succeeded, but post-commit hook failed"));
    }

  /* After a successful commit, must record the dump-rev -> in-repos-rev
     mapping, so that copyfrom instructions in the dump file can look up the
     correct repository revision to copy from. */
  set_revision_mapping(pb->rev_map, rb->rev, committed_rev);

  /* If the incoming dump stream has non-contiguous revisions (e.g. from
     using svndumpfilter --drop-empty-revs without --renumber-revs) then
     we must account for the missing gaps in PB->REV_MAP.  Otherwise we
     might not be able to map all mergeinfo source revisions to the correct
     revisions in the target repos. */
  if ((pb->last_rev_mapped != SVN_INVALID_REVNUM)
      && (rb->rev != pb->last_rev_mapped + 1))
    {
      svn_revnum_t i;

      for (i = pb->last_rev_mapped + 1; i < rb->rev; i++)
        {
          set_revision_mapping(pb->rev_map, i, pb->last_rev_mapped);
        }
    }

  /* Update our "last revision mapped". */
  pb->last_rev_mapped = rb->rev;

  /* Deltify the predecessors of paths changed in this revision. */
  SVN_ERR(svn_fs_deltify_revision(pb->fs, committed_rev, rb->pool));

  /* Grrr, svn_fs_commit_txn rewrites the datestamp property to the
     current clock-time.  We don't want that, we want to preserve
     history exactly.  Good thing revision props aren't versioned!
     Note that if rb->datestamp is NULL, that's fine -- if the dump
     data doesn't carry a datestamp, we want to preserve that fact in
     the load. */
  SVN_ERR(change_rev_prop(pb->repos, committed_rev, SVN_PROP_REVISION_DATE,
                          rb->datestamp, pb->validate_props, rb->pool));

  if (pb->notify_func)
    {
      /* ### TODO: Use proper scratch pool instead of pb->notify_pool */
      svn_repos_notify_t *notify = svn_repos_notify_create(
                                        svn_repos_notify_load_txn_committed,
                                        pb->notify_pool);

      notify->new_revision = committed_rev;
      notify->old_revision = ((committed_rev == rb->rev)
                                    ? SVN_INVALID_REVNUM
                                    : rb->rev);
      pb->notify_func(pb->notify_baton, notify, pb->notify_pool);
      svn_pool_clear(pb->notify_pool);
    }

  return SVN_NO_ERROR;
}
Exemple #11
0
static svn_error_t *
changes_fetch_ordering(const char **msg,
                       svn_boolean_t msg_only,
                       svn_test_opts_t *opts,
                       apr_pool_t *pool)
{
  svn_fs_t *fs;
  svn_revnum_t youngest_rev = 0;
  const char *txn_name;
  svn_fs_txn_t *txn;
  svn_fs_root_t *txn_root, *rev_root;
  struct changes_args args;
  apr_pool_t *subpool = svn_pool_create(pool);
  apr_hash_index_t *hi;

  *msg = "verify ordered-ness of fetched compressed changes";

  if (msg_only)
    return SVN_NO_ERROR;

  /* Create a new fs and repos */
  SVN_ERR(svn_test__create_fs
          (&fs, "test-repo-changes-fetch-ordering",
           "bdb", pool));

  /*** REVISION 1: Make some files and dirs. ***/
  SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
  SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
  {
    static svn_test__txn_script_command_t script_entries[] = {
      { 'a', "dir1",        0 },
      { 'a', "file1",       "This is the file 'file1'.\n" },
      { 'a', "dir1/file2",  "This is the file 'file2'.\n" },
      { 'a', "dir1/file3",  "This is the file 'file3'.\n" },
      { 'a', "dir1/file4",  "This is the file 'file4'.\n" },
    };
    SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 5, subpool));
  }
  SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
  svn_pool_clear(subpool);

  /*** REVISION 2: Delete and add some stuff, non-depth-first. ***/
  SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
  /* Don't use subpool, txn_name is used after subpool is cleared */
  SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
  SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
  {
    static svn_test__txn_script_command_t script_entries[] = {
      { 'd', "file1",       "This is the file 'file1'.\n" },
      { 'd', "dir1/file2",  "This is the file 'file2'.\n" },
      { 'd', "dir1/file3",  "This is the file 'file3'.\n" },
      { 'a', "dir1/file5",  "This is the file 'file4'.\n" },
      { 'a', "dir1/dir2",   0 },
      { 'd', "dir1",        0 },
      { 'a', "dir3",        0 },
    };
    SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 7, subpool));
  }
  SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
  svn_pool_clear(subpool);

  /*** TEST:  We should have only three changes, the deletion of 'file1'
       the deletion of 'dir1', and the addition of 'dir3'. ***/
  args.fs = fs;
  args.key = txn_name;
  SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_changes_fetch, &args,
                                 subpool));
  if ((! args.changes) || (apr_hash_count(args.changes) != 3))
    return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
                            "expected changes");
  for (hi = apr_hash_first(subpool, args.changes);
       hi; hi = apr_hash_next(hi))
    {
      const void *key;
      void *val;
      svn_fs_path_change_t *change;

      /* KEY will be the path, VAL the change. */
      apr_hash_this(hi, &key, NULL, &val);
      change = val;

      if ((change->change_kind == svn_fs_path_change_add)
          && (strcmp(key, "/dir3") == 0))
        ;
      else if ((change->change_kind == svn_fs_path_change_delete)
               && ((strcmp(key, "/dir1") == 0)
                   || (strcmp(key, "/file1") == 0)))
        ;
      else
        return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
                                "got wrong changes");
    }

  /*** REVISION 3: Do the same stuff as in revision 1. ***/
  SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
  SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
  {
    static svn_test__txn_script_command_t script_entries[] = {
      { 'a', "dir1",        0 },
      { 'a', "file1",       "This is the file 'file1'.\n" },
      { 'a', "dir1/file2",  "This is the file 'file2'.\n" },
      { 'a', "dir1/file3",  "This is the file 'file3'.\n" },
      { 'a', "dir1/file4",  "This is the file 'file4'.\n" },
    };
    SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 5, subpool));
  }
  SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
  svn_pool_clear(subpool);

  /*** REVISION 4: Do the same stuff as in revision 2, but use a copy
       overwrite of the top directory (instead of a delete) to test
       that the 'replace' change type works, too.  (And add 'dir4'
       instead of 'dir3', since 'dir3' still exists).  ***/
  SVN_ERR(svn_fs_begin_txn(&txn, fs, youngest_rev, subpool));
  /* Don't use subpool, txn_name is used after subpool is cleared */
  SVN_ERR(svn_fs_txn_name(&txn_name, txn, pool));
  SVN_ERR(svn_fs_txn_root(&txn_root, txn, subpool));
  SVN_ERR(svn_fs_revision_root(&rev_root, fs, 1, subpool));
  {
    static svn_test__txn_script_command_t script_entries[] = {
      { 'd', "file1",       "This is the file 'file1'.\n" },
      { 'd', "dir1/file2",  "This is the file 'file2'.\n" },
      { 'd', "dir1/file3",  "This is the file 'file3'.\n" },
      { 'a', "dir1/file5",  "This is the file 'file4'.\n" },
      { 'a', "dir1/dir2",   0 },
    };
    SVN_ERR(svn_test__txn_script_exec(txn_root, script_entries, 5, subpool));
    SVN_ERR(svn_fs_copy(rev_root, "dir1", txn_root, "dir1", subpool));
    SVN_ERR(svn_fs_make_dir(txn_root, "dir4", subpool));
  }
  SVN_ERR(svn_fs_commit_txn(NULL, &youngest_rev, txn, subpool));
  svn_pool_clear(subpool);

  /*** TEST:  We should have only three changes, the deletion of 'file1'
       the replacement of 'dir1', and the addition of 'dir4'. ***/
  args.fs = fs;
  args.key = txn_name;
  SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_changes_fetch, &args,
                                 subpool));
  if ((! args.changes) || (apr_hash_count(args.changes) != 3))
    return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
                            "expected changes");
  for (hi = apr_hash_first(subpool, args.changes);
       hi; hi = apr_hash_next(hi))
    {
      const void *key;
      void *val;
      svn_fs_path_change_t *change;

      /* KEY will be the path, VAL the change. */
      apr_hash_this(hi, &key, NULL, &val);
      change = val;

      if ((change->change_kind == svn_fs_path_change_add)
          && (strcmp(key, "/dir4") == 0))
        ;
      else if ((change->change_kind == svn_fs_path_change_replace)
               && (strcmp(key, "/dir1") == 0))
        ;
      else if ((change->change_kind == svn_fs_path_change_delete)
               && (strcmp(key, "/file1") == 0))
        ;
      else
        return svn_error_create(SVN_ERR_TEST_FAILED, NULL,
                                "got wrong changes");
    }

  return SVN_NO_ERROR;
}