summaryrefslogtreecommitdiff
path: root/subversion/libsvn_fs_base/revs-txns.c
diff options
context:
space:
mode:
Diffstat (limited to 'subversion/libsvn_fs_base/revs-txns.c')
-rw-r--r--subversion/libsvn_fs_base/revs-txns.c1067
1 files changed, 1067 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_base/revs-txns.c b/subversion/libsvn_fs_base/revs-txns.c
new file mode 100644
index 0000000000000..d21884311a834
--- /dev/null
+++ b/subversion/libsvn_fs_base/revs-txns.c
@@ -0,0 +1,1067 @@
+/* revs-txns.c : operations on revision and 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 <string.h>
+
+#include <apr_tables.h>
+#include <apr_pools.h>
+
+#include "svn_pools.h"
+#include "svn_time.h"
+#include "svn_fs.h"
+#include "svn_props.h"
+#include "svn_hash.h"
+#include "svn_io.h"
+
+#include "fs.h"
+#include "dag.h"
+#include "err.h"
+#include "trail.h"
+#include "tree.h"
+#include "revs-txns.h"
+#include "key-gen.h"
+#include "id.h"
+#include "bdb/rev-table.h"
+#include "bdb/txn-table.h"
+#include "bdb/copies-table.h"
+#include "bdb/changes-table.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+#include "private/svn_fs_util.h"
+
+
+/*** Helpers ***/
+
+/* Set *txn_p to a transaction object allocated in POOL for the
+ transaction in FS whose id is TXN_ID. If EXPECT_DEAD is set, this
+ transaction must be a dead one, else an error is returned. If
+ EXPECT_DEAD is not set, the transaction must *not* be a dead one,
+ else an error is returned. */
+static svn_error_t *
+get_txn(transaction_t **txn_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ svn_boolean_t expect_dead,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+ SVN_ERR(svn_fs_bdb__get_txn(&txn, fs, txn_id, trail, pool));
+ if (expect_dead && (txn->kind != transaction_kind_dead))
+ return svn_error_createf(SVN_ERR_FS_TRANSACTION_NOT_DEAD, 0,
+ _("Transaction is not dead: '%s'"), txn_id);
+ if ((! expect_dead) && (txn->kind == transaction_kind_dead))
+ return svn_error_createf(SVN_ERR_FS_TRANSACTION_DEAD, 0,
+ _("Transaction is dead: '%s'"), txn_id);
+ *txn_p = txn;
+ return SVN_NO_ERROR;
+}
+
+
+/* This is only for symmetry with the get_txn() helper. */
+#define put_txn svn_fs_bdb__put_txn
+
+
+
+/*** Revisions ***/
+
+/* Return the committed transaction record *TXN_P and its ID *TXN_ID
+ (as long as those parameters aren't NULL) for the revision REV in
+ FS as part of TRAIL. */
+static svn_error_t *
+get_rev_txn(transaction_t **txn_p,
+ const char **txn_id,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ revision_t *revision;
+ transaction_t *txn;
+
+ SVN_ERR(svn_fs_bdb__get_rev(&revision, fs, rev, trail, pool));
+ if (revision->txn_id == NULL)
+ return svn_fs_base__err_corrupt_fs_revision(fs, rev);
+
+ SVN_ERR(get_txn(&txn, fs, revision->txn_id, FALSE, trail, pool));
+ if (txn->revision != rev)
+ return svn_fs_base__err_corrupt_txn(fs, revision->txn_id);
+
+ if (txn_p)
+ *txn_p = txn;
+ if (txn_id)
+ *txn_id = revision->txn_id;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__rev_get_root(const svn_fs_id_t **root_id_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+
+ SVN_ERR(get_rev_txn(&txn, NULL, fs, rev, trail, pool));
+ if (txn->root_id == NULL)
+ return svn_fs_base__err_corrupt_fs_revision(fs, rev);
+
+ *root_id_p = txn->root_id;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__rev_get_txn_id(const char **txn_id_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ revision_t *revision;
+
+ SVN_ERR(svn_fs_bdb__get_rev(&revision, fs, rev, trail, pool));
+ if (revision->txn_id == NULL)
+ return svn_fs_base__err_corrupt_fs_revision(fs, rev);
+
+ *txn_id_p = revision->txn_id;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+txn_body_youngest_rev(void *baton, trail_t *trail)
+{
+ return svn_fs_bdb__youngest_rev(baton, trail->fs, trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__youngest_rev(svn_revnum_t *youngest_p,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ svn_revnum_t youngest;
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_youngest_rev, &youngest,
+ TRUE, pool));
+ *youngest_p = youngest;
+ return SVN_NO_ERROR;
+}
+
+
+struct revision_proplist_args {
+ apr_hash_t **table_p;
+ svn_revnum_t rev;
+};
+
+
+static svn_error_t *
+txn_body_revision_proplist(void *baton, trail_t *trail)
+{
+ struct revision_proplist_args *args = baton;
+ transaction_t *txn;
+
+ SVN_ERR(get_rev_txn(&txn, NULL, trail->fs, args->rev, trail, trail->pool));
+ *(args->table_p) = txn->proplist;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__revision_proplist(apr_hash_t **table_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ struct revision_proplist_args args;
+ apr_hash_t *table;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.table_p = &table;
+ args.rev = rev;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_proplist, &args,
+ FALSE, pool));
+
+ *table_p = table ? table : apr_hash_make(pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__revision_prop(svn_string_t **value_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *propname,
+ apr_pool_t *pool)
+{
+ struct revision_proplist_args args;
+ apr_hash_t *table;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ /* Get the proplist. */
+ args.table_p = &table;
+ args.rev = rev;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_proplist, &args,
+ FALSE, pool));
+
+ /* And then the prop from that list (if there was a list). */
+ *value_p = svn_hash_gets(table, propname);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__set_rev_prop(svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+ const char *txn_id;
+
+ SVN_ERR(get_rev_txn(&txn, &txn_id, fs, rev, trail, pool));
+
+ /* If there's no proplist, but we're just deleting a property, exit now. */
+ if ((! txn->proplist) && (! value))
+ return SVN_NO_ERROR;
+
+ /* Now, if there's no proplist, we know we need to make one. */
+ if (! txn->proplist)
+ txn->proplist = apr_hash_make(pool);
+
+ /* Set the property. */
+ if (old_value_p)
+ {
+ const svn_string_t *wanted_value = *old_value_p;
+ const svn_string_t *present_value = svn_hash_gets(txn->proplist, name);
+ if ((!wanted_value != !present_value)
+ || (wanted_value && present_value
+ && !svn_string_compare(wanted_value, present_value)))
+ {
+ /* What we expected isn't what we found. */
+ return svn_error_createf(SVN_ERR_FS_PROP_BASEVALUE_MISMATCH, NULL,
+ _("revprop '%s' has unexpected value in "
+ "filesystem"),
+ name);
+ }
+ /* Fall through. */
+ }
+ svn_hash_sets(txn->proplist, name, value);
+
+ /* Overwrite the revision. */
+ return put_txn(fs, txn, txn_id, trail, pool);
+}
+
+
+struct change_rev_prop_args {
+ svn_revnum_t rev;
+ const char *name;
+ const svn_string_t *const *old_value_p;
+ const svn_string_t *value;
+};
+
+
+static svn_error_t *
+txn_body_change_rev_prop(void *baton, trail_t *trail)
+{
+ struct change_rev_prop_args *args = baton;
+
+ return svn_fs_base__set_rev_prop(trail->fs, args->rev,
+ args->name, args->old_value_p, args->value,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__change_rev_prop(svn_fs_t *fs,
+ svn_revnum_t rev,
+ const char *name,
+ const svn_string_t *const *old_value_p,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct change_rev_prop_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.rev = rev;
+ args.name = name;
+ args.old_value_p = old_value_p;
+ args.value = value;
+ return svn_fs_base__retry_txn(fs, txn_body_change_rev_prop, &args,
+ TRUE, pool);
+}
+
+
+
+/*** Transactions ***/
+
+svn_error_t *
+svn_fs_base__txn_make_committed(svn_fs_t *fs,
+ const char *txn_name,
+ svn_revnum_t revision,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+
+ SVN_ERR_ASSERT(SVN_IS_VALID_REVNUM(revision));
+
+ /* Make sure the TXN is not committed already. */
+ SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
+ if (txn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(fs, txn_name);
+
+ /* Convert TXN to a committed transaction. */
+ txn->base_id = NULL;
+ txn->revision = revision;
+ txn->kind = transaction_kind_committed;
+ return put_txn(fs, txn, txn_name, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__txn_get_revision(svn_revnum_t *revision,
+ svn_fs_t *fs,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+ SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
+ *revision = txn->revision;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__get_txn_ids(const svn_fs_id_t **root_id_p,
+ const svn_fs_id_t **base_root_id_p,
+ svn_fs_t *fs,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+
+ SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
+ if (txn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(fs, txn_name);
+
+ *root_id_p = txn->root_id;
+ *base_root_id_p = txn->base_id;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__set_txn_root(svn_fs_t *fs,
+ const char *txn_name,
+ const svn_fs_id_t *new_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+
+ SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
+ if (txn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(fs, txn_name);
+
+ if (! svn_fs_base__id_eq(txn->root_id, new_id))
+ {
+ txn->root_id = new_id;
+ SVN_ERR(put_txn(fs, txn, txn_name, trail, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__set_txn_base(svn_fs_t *fs,
+ const char *txn_name,
+ const svn_fs_id_t *new_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+
+ SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
+ if (txn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(fs, txn_name);
+
+ if (! svn_fs_base__id_eq(txn->base_id, new_id))
+ {
+ txn->base_id = new_id;
+ SVN_ERR(put_txn(fs, txn, txn_name, trail, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__add_txn_copy(svn_fs_t *fs,
+ const char *txn_name,
+ const char *copy_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+
+ /* Get the transaction and ensure its mutability. */
+ SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
+ if (txn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(fs, txn_name);
+
+ /* Allocate a new array if this transaction has no copies. */
+ if (! txn->copies)
+ txn->copies = apr_array_make(pool, 1, sizeof(copy_id));
+
+ /* Add COPY_ID to the array. */
+ APR_ARRAY_PUSH(txn->copies, const char *) = copy_id;
+
+ /* Finally, write out the transaction. */
+ return put_txn(fs, txn, txn_name, trail, pool);
+}
+
+
+
+/* Generic transaction operations. */
+
+struct txn_proplist_args {
+ apr_hash_t **table_p;
+ const char *id;
+};
+
+
+static svn_error_t *
+txn_body_txn_proplist(void *baton, trail_t *trail)
+{
+ transaction_t *txn;
+ struct txn_proplist_args *args = baton;
+
+ SVN_ERR(get_txn(&txn, trail->fs, args->id, FALSE, trail, trail->pool));
+ if (txn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(trail->fs, args->id);
+
+ *(args->table_p) = txn->proplist;
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_fs_base__txn_proplist_in_trail(apr_hash_t **table_p,
+ const char *txn_id,
+ trail_t *trail)
+{
+ struct txn_proplist_args args;
+ apr_hash_t *table;
+
+ args.table_p = &table;
+ args.id = txn_id;
+ SVN_ERR(txn_body_txn_proplist(&args, trail));
+
+ *table_p = table ? table : apr_hash_make(trail->pool);
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_fs_base__txn_proplist(apr_hash_t **table_p,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ struct txn_proplist_args args;
+ apr_hash_t *table;
+ svn_fs_t *fs = txn->fs;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.table_p = &table;
+ args.id = txn->id;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_proplist, &args,
+ FALSE, pool));
+
+ *table_p = table ? table : apr_hash_make(pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__txn_prop(svn_string_t **value_p,
+ svn_fs_txn_t *txn,
+ const char *propname,
+ apr_pool_t *pool)
+{
+ struct txn_proplist_args args;
+ apr_hash_t *table;
+ svn_fs_t *fs = txn->fs;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ /* Get the proplist. */
+ args.table_p = &table;
+ args.id = txn->id;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_proplist, &args,
+ FALSE, pool));
+
+ /* And then the prop from that list (if there was a list). */
+ *value_p = svn_hash_gets(table, propname);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+struct change_txn_prop_args {
+ svn_fs_t *fs;
+ const char *id;
+ const char *name;
+ const svn_string_t *value;
+};
+
+
+svn_error_t *
+svn_fs_base__set_txn_prop(svn_fs_t *fs,
+ const char *txn_name,
+ const char *name,
+ const svn_string_t *value,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ transaction_t *txn;
+
+ SVN_ERR(get_txn(&txn, fs, txn_name, FALSE, trail, pool));
+ if (txn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(fs, txn_name);
+
+ /* If there's no proplist, but we're just deleting a property, exit now. */
+ if ((! txn->proplist) && (! value))
+ return SVN_NO_ERROR;
+
+ /* Now, if there's no proplist, we know we need to make one. */
+ if (! txn->proplist)
+ txn->proplist = apr_hash_make(pool);
+
+ /* Set the property. */
+ svn_hash_sets(txn->proplist, name, value);
+
+ /* Now overwrite the transaction. */
+ return put_txn(fs, txn, txn_name, trail, pool);
+}
+
+
+static svn_error_t *
+txn_body_change_txn_prop(void *baton, trail_t *trail)
+{
+ struct change_txn_prop_args *args = baton;
+ return svn_fs_base__set_txn_prop(trail->fs, args->id, args->name,
+ args->value, trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__change_txn_prop(svn_fs_txn_t *txn,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct change_txn_prop_args args;
+ svn_fs_t *fs = txn->fs;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.id = txn->id;
+ args.name = name;
+ args.value = value;
+ return svn_fs_base__retry_txn(fs, txn_body_change_txn_prop, &args,
+ TRUE, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__change_txn_props(svn_fs_txn_t *txn,
+ const apr_array_header_t *props,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool = svn_pool_create(pool);
+ int i;
+
+ for (i = 0; i < props->nelts; i++)
+ {
+ svn_prop_t *prop = &APR_ARRAY_IDX(props, i, svn_prop_t);
+
+ svn_pool_clear(iterpool);
+
+ SVN_ERR(svn_fs_base__change_txn_prop(txn, prop->name,
+ prop->value, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Creating a transaction */
+
+static txn_vtable_t txn_vtable = {
+ svn_fs_base__commit_txn,
+ svn_fs_base__abort_txn,
+ svn_fs_base__txn_prop,
+ svn_fs_base__txn_proplist,
+ svn_fs_base__change_txn_prop,
+ svn_fs_base__txn_root,
+ svn_fs_base__change_txn_props
+};
+
+
+/* Allocate and return a new transaction object in POOL for FS whose
+ transaction ID is ID. ID is not copied. */
+static svn_fs_txn_t *
+make_txn(svn_fs_t *fs,
+ const char *id,
+ svn_revnum_t base_rev,
+ apr_pool_t *pool)
+{
+ svn_fs_txn_t *txn = apr_pcalloc(pool, sizeof(*txn));
+
+ txn->fs = fs;
+ txn->id = id;
+ txn->base_rev = base_rev;
+ txn->vtable = &txn_vtable;
+ txn->fsap_data = NULL;
+
+ return txn;
+}
+
+
+struct begin_txn_args
+{
+ svn_fs_txn_t **txn_p;
+ svn_revnum_t base_rev;
+ apr_uint32_t flags;
+};
+
+
+static svn_error_t *
+txn_body_begin_txn(void *baton, trail_t *trail)
+{
+ struct begin_txn_args *args = baton;
+ const svn_fs_id_t *root_id;
+ const char *txn_id;
+
+ SVN_ERR(svn_fs_base__rev_get_root(&root_id, trail->fs, args->base_rev,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_bdb__create_txn(&txn_id, trail->fs, root_id,
+ trail, trail->pool));
+
+ if (args->flags & SVN_FS_TXN_CHECK_OOD)
+ {
+ struct change_txn_prop_args cpargs;
+ cpargs.fs = trail->fs;
+ cpargs.id = txn_id;
+ cpargs.name = SVN_FS__PROP_TXN_CHECK_OOD;
+ cpargs.value = svn_string_create("true", trail->pool);
+
+ SVN_ERR(txn_body_change_txn_prop(&cpargs, trail));
+ }
+
+ if (args->flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ struct change_txn_prop_args cpargs;
+ cpargs.fs = trail->fs;
+ cpargs.id = txn_id;
+ cpargs.name = SVN_FS__PROP_TXN_CHECK_LOCKS;
+ cpargs.value = svn_string_create("true", trail->pool);
+
+ SVN_ERR(txn_body_change_txn_prop(&cpargs, trail));
+ }
+
+ *args->txn_p = make_txn(trail->fs, txn_id, args->base_rev, trail->pool);
+ return SVN_NO_ERROR;
+}
+
+/* Note: it is acceptable for this function to call back into
+ public FS API interfaces because it does not itself use trails. */
+svn_error_t *
+svn_fs_base__begin_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_uint32_t flags,
+ apr_pool_t *pool)
+{
+ svn_fs_txn_t *txn;
+ struct begin_txn_args args;
+ svn_string_t date;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.txn_p = &txn;
+ args.base_rev = rev;
+ args.flags = flags;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_begin_txn, &args, FALSE, pool));
+
+ *txn_p = txn;
+
+ /* Put a datestamp on the newly created txn, so we always know
+ exactly how old it is. (This will help sysadmins identify
+ long-abandoned txns that may need to be manually removed.) When
+ a txn is promoted to a revision, this property will be
+ automatically overwritten with a revision datestamp. */
+ date.data = svn_time_to_cstring(apr_time_now(), pool);
+ date.len = strlen(date.data);
+ return svn_fs_base__change_txn_prop(txn, SVN_PROP_REVISION_DATE,
+ &date, pool);
+}
+
+
+struct open_txn_args
+{
+ svn_fs_txn_t **txn_p;
+ const char *name;
+};
+
+
+static svn_error_t *
+txn_body_open_txn(void *baton, trail_t *trail)
+{
+ struct open_txn_args *args = baton;
+ transaction_t *fstxn;
+ svn_revnum_t base_rev = SVN_INVALID_REVNUM;
+ const char *txn_id;
+
+ SVN_ERR(get_txn(&fstxn, trail->fs, args->name, FALSE, trail, trail->pool));
+ if (fstxn->kind != transaction_kind_committed)
+ {
+ txn_id = svn_fs_base__id_txn_id(fstxn->base_id);
+ SVN_ERR(svn_fs_base__txn_get_revision(&base_rev, trail->fs, txn_id,
+ trail, trail->pool));
+ }
+
+ *args->txn_p = make_txn(trail->fs, args->name, base_rev, trail->pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__open_txn(svn_fs_txn_t **txn_p,
+ svn_fs_t *fs,
+ const char *name,
+ apr_pool_t *pool)
+{
+ svn_fs_txn_t *txn;
+ struct open_txn_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.txn_p = &txn;
+ args.name = name;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_open_txn, &args, FALSE, pool));
+
+ *txn_p = txn;
+ return SVN_NO_ERROR;
+}
+
+
+struct cleanup_txn_args
+{
+ transaction_t **txn_p;
+ const char *name;
+};
+
+
+static svn_error_t *
+txn_body_cleanup_txn(void *baton, trail_t *trail)
+{
+ struct cleanup_txn_args *args = baton;
+ return get_txn(args->txn_p, trail->fs, args->name, TRUE,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+txn_body_cleanup_txn_copy(void *baton, trail_t *trail)
+{
+ const char *copy_id = *(const char **)baton;
+ svn_error_t *err = svn_fs_bdb__delete_copy(trail->fs, copy_id, trail,
+ trail->pool);
+
+ /* Copy doesn't exist? No sweat. */
+ if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_COPY))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+}
+
+
+static svn_error_t *
+txn_body_cleanup_txn_changes(void *baton, trail_t *trail)
+{
+ const char *key = *(const char **)baton;
+
+ return svn_fs_bdb__changes_delete(trail->fs, key, trail, trail->pool);
+}
+
+
+struct get_dirents_args
+{
+ apr_hash_t **dirents;
+ const svn_fs_id_t *id;
+ const char *txn_id;
+};
+
+
+static svn_error_t *
+txn_body_get_dirents(void *baton, trail_t *trail)
+{
+ struct get_dirents_args *args = baton;
+ dag_node_t *node;
+
+ /* Get the node. */
+ SVN_ERR(svn_fs_base__dag_get_node(&node, trail->fs, args->id,
+ trail, trail->pool));
+
+ /* If immutable, do nothing and return. */
+ if (! svn_fs_base__dag_check_mutable(node, args->txn_id))
+ return SVN_NO_ERROR;
+
+ /* If a directory, do nothing and return. */
+ *(args->dirents) = NULL;
+ if (svn_fs_base__dag_node_kind(node) != svn_node_dir)
+ return SVN_NO_ERROR;
+
+ /* Else it's mutable. Get its dirents. */
+ return svn_fs_base__dag_dir_entries(args->dirents, node,
+ trail, trail->pool);
+}
+
+
+struct remove_node_args
+{
+ const svn_fs_id_t *id;
+ const char *txn_id;
+};
+
+
+static svn_error_t *
+txn_body_remove_node(void *baton, trail_t *trail)
+{
+ struct remove_node_args *args = baton;
+ return svn_fs_base__dag_remove_node(trail->fs, args->id, args->txn_id,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+delete_txn_tree(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ struct get_dirents_args dirent_args;
+ struct remove_node_args rm_args;
+ apr_hash_t *dirents = NULL;
+ apr_hash_index_t *hi;
+ svn_error_t *err;
+
+ /* If this sucker isn't mutable, there's nothing to do. */
+ if (svn_fs_base__key_compare(svn_fs_base__id_txn_id(id), txn_id) != 0)
+ return SVN_NO_ERROR;
+
+ /* See if the thing has dirents that need to be recursed upon. If
+ you can't find the thing itself, don't sweat it. We probably
+ already cleaned it up. */
+ dirent_args.dirents = &dirents;
+ dirent_args.id = id;
+ dirent_args.txn_id = txn_id;
+ err = svn_fs_base__retry_txn(fs, txn_body_get_dirents, &dirent_args,
+ FALSE, pool);
+ if (err && (err->apr_err == SVN_ERR_FS_ID_NOT_FOUND))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ /* If there are dirents upon which to recurse ... recurse. */
+ if (dirents)
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Loop over hash entries */
+ for (hi = apr_hash_first(pool, dirents); hi; hi = apr_hash_next(hi))
+ {
+ void *val;
+ svn_fs_dirent_t *dirent;
+
+ svn_pool_clear(subpool);
+ apr_hash_this(hi, NULL, NULL, &val);
+ dirent = val;
+ SVN_ERR(delete_txn_tree(fs, dirent->id, txn_id, subpool));
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ /* Remove the node. */
+ rm_args.id = id;
+ rm_args.txn_id = txn_id;
+ return svn_fs_base__retry_txn(fs, txn_body_remove_node, &rm_args,
+ TRUE, pool);
+}
+
+
+static svn_error_t *
+txn_body_delete_txn(void *baton, trail_t *trail)
+{
+ const char *txn_id = *(const char **)baton;
+
+ return svn_fs_bdb__delete_txn(trail->fs, txn_id, trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__purge_txn(svn_fs_t *fs,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ struct cleanup_txn_args args;
+ transaction_t *txn;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ /* Open the transaction, expecting it to be dead. */
+ args.txn_p = &txn;
+ args.name = txn_id;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_cleanup_txn, &args,
+ FALSE, pool));
+
+ /* Delete the mutable portion of the tree hanging from the
+ transaction (which should gracefully recover if we've already
+ done this). */
+ SVN_ERR(delete_txn_tree(fs, txn->root_id, txn_id, pool));
+
+ /* Kill the transaction's changes (which should gracefully recover
+ if...). */
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_cleanup_txn_changes,
+ &txn_id, TRUE, pool));
+
+ /* Kill the transaction's copies (which should gracefully...). */
+ if (txn->copies)
+ {
+ int i;
+
+ for (i = 0; i < txn->copies->nelts; i++)
+ {
+ SVN_ERR(svn_fs_base__retry_txn
+ (fs, txn_body_cleanup_txn_copy,
+ &APR_ARRAY_IDX(txn->copies, i, const char *),
+ TRUE, pool));
+ }
+ }
+
+ /* Kill the transaction itself (which ... just kidding -- this has
+ no graceful failure mode). */
+ return svn_fs_base__retry_txn(fs, txn_body_delete_txn, &txn_id,
+ TRUE, pool);
+}
+
+
+static svn_error_t *
+txn_body_abort_txn(void *baton, trail_t *trail)
+{
+ svn_fs_txn_t *txn = baton;
+ transaction_t *fstxn;
+
+ /* Get the transaction by its id, set it to "dead", and store the
+ transaction. */
+ SVN_ERR(get_txn(&fstxn, txn->fs, txn->id, FALSE, trail, trail->pool));
+ if (fstxn->kind != transaction_kind_normal)
+ return svn_fs_base__err_txn_not_mutable(txn->fs, txn->id);
+
+ fstxn->kind = transaction_kind_dead;
+ return put_txn(txn->fs, fstxn, txn->id, trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__abort_txn(svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ SVN_ERR(svn_fs__check_fs(txn->fs, TRUE));
+
+ /* Set the transaction to "dead". */
+ SVN_ERR(svn_fs_base__retry_txn(txn->fs, txn_body_abort_txn, txn,
+ TRUE, pool));
+
+ /* Now, purge it. */
+ SVN_ERR_W(svn_fs_base__purge_txn(txn->fs, txn->id, pool),
+ _("Transaction aborted, but cleanup failed"));
+
+ return SVN_NO_ERROR;
+}
+
+
+struct list_transactions_args
+{
+ apr_array_header_t **names_p;
+ apr_pool_t *pool;
+};
+
+static svn_error_t *
+txn_body_list_transactions(void* baton, trail_t *trail)
+{
+ struct list_transactions_args *args = baton;
+ return svn_fs_bdb__get_txn_list(args->names_p, trail->fs,
+ trail, args->pool);
+}
+
+svn_error_t *
+svn_fs_base__list_transactions(apr_array_header_t **names_p,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *names;
+ struct list_transactions_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.names_p = &names;
+ args.pool = pool;
+ SVN_ERR(svn_fs_base__retry(fs, txn_body_list_transactions, &args,
+ FALSE, pool));
+
+ *names_p = names;
+ return SVN_NO_ERROR;
+}