summaryrefslogtreecommitdiffstats
path: root/subversion/libsvn_fs_base
diff options
context:
space:
mode:
authorpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
committerpeter <peter@FreeBSD.org>2013-06-18 02:07:41 +0000
commitd25dac7fcc6acc838b71bbda8916fd9665c709ab (patch)
tree135691142dc0e75a5e5d97b5074d03436435b8e0 /subversion/libsvn_fs_base
downloadFreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.zip
FreeBSD-src-d25dac7fcc6acc838b71bbda8916fd9665c709ab.tar.gz
Import trimmed svn-1.8.0-rc3
Diffstat (limited to 'subversion/libsvn_fs_base')
-rw-r--r--subversion/libsvn_fs_base/bdb/bdb-err.c106
-rw-r--r--subversion/libsvn_fs_base/bdb/bdb-err.h115
-rw-r--r--subversion/libsvn_fs_base/bdb/bdb_compat.c34
-rw-r--r--subversion/libsvn_fs_base/bdb/bdb_compat.h135
-rw-r--r--subversion/libsvn_fs_base/bdb/changes-table.c457
-rw-r--r--subversion/libsvn_fs_base/bdb/changes-table.h94
-rw-r--r--subversion/libsvn_fs_base/bdb/checksum-reps-table.c208
-rw-r--r--subversion/libsvn_fs_base/bdb/checksum-reps-table.h89
-rw-r--r--subversion/libsvn_fs_base/bdb/copies-table.c210
-rw-r--r--subversion/libsvn_fs_base/bdb/copies-table.h93
-rw-r--r--subversion/libsvn_fs_base/bdb/dbt.c170
-rw-r--r--subversion/libsvn_fs_base/bdb/dbt.h120
-rw-r--r--subversion/libsvn_fs_base/bdb/env.c719
-rw-r--r--subversion/libsvn_fs_base/bdb/env.h159
-rw-r--r--subversion/libsvn_fs_base/bdb/lock-tokens-table.c157
-rw-r--r--subversion/libsvn_fs_base/bdb/lock-tokens-table.h96
-rw-r--r--subversion/libsvn_fs_base/bdb/locks-table.c328
-rw-r--r--subversion/libsvn_fs_base/bdb/locks-table.h110
-rw-r--r--subversion/libsvn_fs_base/bdb/miscellaneous-table.c135
-rw-r--r--subversion/libsvn_fs_base/bdb/miscellaneous-table.h71
-rw-r--r--subversion/libsvn_fs_base/bdb/node-origins-table.c145
-rw-r--r--subversion/libsvn_fs_base/bdb/node-origins-table.h76
-rw-r--r--subversion/libsvn_fs_base/bdb/nodes-table.c259
-rw-r--r--subversion/libsvn_fs_base/bdb/nodes-table.h121
-rw-r--r--subversion/libsvn_fs_base/bdb/reps-table.c204
-rw-r--r--subversion/libsvn_fs_base/bdb/reps-table.h94
-rw-r--r--subversion/libsvn_fs_base/bdb/rev-table.c221
-rw-r--r--subversion/libsvn_fs_base/bdb/rev-table.h85
-rw-r--r--subversion/libsvn_fs_base/bdb/strings-table.c541
-rw-r--r--subversion/libsvn_fs_base/bdb/strings-table.h143
-rw-r--r--subversion/libsvn_fs_base/bdb/txn-table.c325
-rw-r--r--subversion/libsvn_fs_base/bdb/txn-table.h100
-rw-r--r--subversion/libsvn_fs_base/bdb/uuids-table.c149
-rw-r--r--subversion/libsvn_fs_base/bdb/uuids-table.h69
-rw-r--r--subversion/libsvn_fs_base/dag.c1758
-rw-r--r--subversion/libsvn_fs_base/dag.h587
-rw-r--r--subversion/libsvn_fs_base/err.c177
-rw-r--r--subversion/libsvn_fs_base/err.h98
-rw-r--r--subversion/libsvn_fs_base/fs.c1436
-rw-r--r--subversion/libsvn_fs_base/fs.h357
-rw-r--r--subversion/libsvn_fs_base/id.c208
-rw-r--r--subversion/libsvn_fs_base/id.h81
-rw-r--r--subversion/libsvn_fs_base/key-gen.c131
-rw-r--r--subversion/libsvn_fs_base/key-gen.h100
-rw-r--r--subversion/libsvn_fs_base/lock.c594
-rw-r--r--subversion/libsvn_fs_base/lock.h120
-rw-r--r--subversion/libsvn_fs_base/node-rev.c126
-rw-r--r--subversion/libsvn_fs_base/node-rev.h101
-rw-r--r--subversion/libsvn_fs_base/notes/TODO137
-rw-r--r--subversion/libsvn_fs_base/notes/fs-history270
-rw-r--r--subversion/libsvn_fs_base/notes/structure1086
-rw-r--r--subversion/libsvn_fs_base/reps-strings.c1617
-rw-r--r--subversion/libsvn_fs_base/reps-strings.h176
-rw-r--r--subversion/libsvn_fs_base/revs-txns.c1067
-rw-r--r--subversion/libsvn_fs_base/revs-txns.h231
-rw-r--r--subversion/libsvn_fs_base/trail.c292
-rw-r--r--subversion/libsvn_fs_base/trail.h239
-rw-r--r--subversion/libsvn_fs_base/tree.c5451
-rw-r--r--subversion/libsvn_fs_base/tree.h99
-rw-r--r--subversion/libsvn_fs_base/util/fs_skels.c1515
-rw-r--r--subversion/libsvn_fs_base/util/fs_skels.h177
-rw-r--r--subversion/libsvn_fs_base/uuid.c116
-rw-r--r--subversion/libsvn_fs_base/uuid.h49
63 files changed, 24534 insertions, 0 deletions
diff --git a/subversion/libsvn_fs_base/bdb/bdb-err.c b/subversion/libsvn_fs_base/bdb/bdb-err.c
new file mode 100644
index 0000000..3d51711
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/bdb-err.c
@@ -0,0 +1,106 @@
+/*
+ * err.c : implementation of fs-private error functions
+ *
+ * ====================================================================
+ * 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 <stdlib.h>
+#include <stdarg.h>
+
+#include <apr_strings.h>
+
+#include "svn_fs.h"
+#include "../fs.h"
+#include "../err.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+
+/* Return a distinguished error for any db error code we want to detect
+ * programatically; otherwise return a generic error.
+ */
+static int
+bdb_err_to_apr_err(int db_err)
+{
+ if (db_err == DB_LOCK_DEADLOCK)
+ return SVN_ERR_FS_BERKELEY_DB_DEADLOCK;
+ else
+ return SVN_ERR_FS_BERKELEY_DB;
+}
+
+
+svn_error_t *
+svn_fs_bdb__dberr(bdb_env_baton_t *bdb_baton, int db_err)
+{
+ svn_error_t *child_errors;
+
+ child_errors = bdb_baton->error_info->pending_errors;
+ bdb_baton->error_info->pending_errors = NULL;
+
+ return svn_error_create(bdb_err_to_apr_err(db_err), child_errors,
+ db_strerror(db_err));
+}
+
+
+svn_error_t *
+svn_fs_bdb__dberrf(bdb_env_baton_t *bdb_baton,
+ int db_err, const char *fmt, ...)
+{
+ va_list ap;
+ char *msg;
+ svn_error_t *err;
+ svn_error_t *child_errors;
+
+ child_errors = bdb_baton->error_info->pending_errors;
+ bdb_baton->error_info->pending_errors = NULL;
+
+ err = svn_error_create(bdb_err_to_apr_err(db_err), child_errors, NULL);
+
+ va_start(ap, fmt);
+ msg = apr_pvsprintf(err->pool, fmt, ap);
+ va_end(ap);
+ err->message = apr_psprintf(err->pool, "%s%s", msg, db_strerror(db_err));
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__wrap_db(svn_fs_t *fs, const char *operation, int db_err)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ if (! db_err)
+ {
+ svn_error_clear(bfd->bdb->error_info->pending_errors);
+ bfd->bdb->error_info->pending_errors = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ bfd = fs->fsap_data;
+ return svn_fs_bdb__dberrf
+ (bfd->bdb, db_err,
+ _("Berkeley DB error for filesystem '%s' while %s:\n"),
+ fs->path ? fs->path : "(none)", _(operation));
+}
diff --git a/subversion/libsvn_fs_base/bdb/bdb-err.h b/subversion/libsvn_fs_base/bdb/bdb-err.h
new file mode 100644
index 0000000..200afe9
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/bdb-err.h
@@ -0,0 +1,115 @@
+/*
+ * err.h : interface to routines for returning Berkeley DB errors
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+
+
+#ifndef SVN_LIBSVN_FS_BDB_ERR_H
+#define SVN_LIBSVN_FS_BDB_ERR_H
+
+#include <apr_pools.h>
+
+#include "svn_error.h"
+#include "svn_fs.h"
+
+#include "env.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Return an svn_error_t object that reports a Berkeley DB error.
+ DB_ERR is the error value returned by the Berkeley DB routine.
+ Wrap and consume pending errors in BDB. */
+svn_error_t *svn_fs_bdb__dberr(bdb_env_baton_t *bdb_baton, int db_err);
+
+
+/* Allocate an error object for a Berkeley DB error, with a formatted message.
+ Wrap and consume pending errors in BDB.
+
+ DB_ERR is the Berkeley DB error code.
+ FMT is a printf-style format string, describing how to format any
+ subsequent arguments.
+
+ The svn_error_t object returned has a message consisting of:
+ - the text specified by FMT and the subsequent arguments, and
+ - the Berkeley DB error message for the error code DB_ERR.
+
+ There is no separator between the two messages; if you want one,
+ you should include it in FMT. */
+svn_error_t *svn_fs_bdb__dberrf(bdb_env_baton_t *bdb_baton, int db_err,
+ const char *fmt, ...)
+ __attribute__((format(printf, 3, 4)));
+
+
+/* Clear errors associated with BDB. */
+void svn_fs_bdb__clear_err(bdb_env_t *bdb);
+
+
+/* Check the return status from the Berkeley DB operation. If the
+ operation succeeded, return zero. Otherwise, construct an
+ appropriate Subversion error object describing what went wrong.
+ - FS is the Subversion filesystem we're operating on.
+ - OPERATION is a gerund clause describing what we were trying to do.
+ - BDB_ERR is the return status from the Berkeley DB function. */
+svn_error_t *svn_fs_bdb__wrap_db(svn_fs_t *fs,
+ const char *operation,
+ int db_err);
+
+
+/* A terse wrapper for svn_fs_bdb__wrap_db. */
+#define BDB_WRAP(fs, op, err) (svn_fs_bdb__wrap_db((fs), (op), (err)))
+
+/* If EXPR returns a non-zero value, pass that value to
+ svn_fs_bdb__dberr and return that function's value. This is like
+ SVN_ERR, but is used by functions that return a Subversion error
+ and call other functions that return a Berkeley DB error code. */
+#define SVN_BDB_ERR(bdb, expr) \
+ do { \
+ int db_err__temp = (expr); \
+ if (db_err__temp) \
+ return svn_fs_bdb__dberr((bdb), db_err__temp); \
+ svn_error_clear((bdb)->error_info->pending_errors); \
+ (bdb)->error_info->pending_errors = NULL; \
+ } while (0)
+
+
+/* If EXPR returns a non-zero value, return it. This is like SVN_ERR,
+ but for functions that return a Berkeley DB error code. */
+#define BDB_ERR(expr) \
+ do { \
+ int db_err__temp = (expr); \
+ if (db_err__temp) \
+ return db_err__temp; \
+ } while (0)
+
+
+/* Verify that FS refers to an open database; return an appropriate
+ error if this is not the case. */
+svn_error_t *svn_fs_bdb__check_fs(svn_fs_t *fs);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_BDB_ERR_H */
diff --git a/subversion/libsvn_fs_base/bdb/bdb_compat.c b/subversion/libsvn_fs_base/bdb/bdb_compat.c
new file mode 100644
index 0000000..5596eee
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/bdb_compat.c
@@ -0,0 +1,34 @@
+/* bdb_compat.c --- Compatibility wrapper for different BDB versions.
+ *
+ * ====================================================================
+ * 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 "bdb_compat.h"
+
+int
+svn_fs_bdb__check_version(void)
+{
+ int major, minor;
+
+ db_version(&major, &minor, NULL);
+ if (major != DB_VERSION_MAJOR || minor != DB_VERSION_MINOR)
+ return DB_OLD_VERSION;
+ return 0;
+}
diff --git a/subversion/libsvn_fs_base/bdb/bdb_compat.h b/subversion/libsvn_fs_base/bdb/bdb_compat.h
new file mode 100644
index 0000000..bea62de
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/bdb_compat.h
@@ -0,0 +1,135 @@
+/* svn_bdb_compat.h --- Compatibility wrapper for different BDB versions.
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_BDB_COMPAT_H
+#define SVN_LIBSVN_FS_BDB_COMPAT_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Symbols and constants */
+
+/* BDB 4.1 introduced the DB_AUTO_COMMIT flag. Older versions can just
+ use 0 instead. */
+#ifdef DB_AUTO_COMMIT
+#define SVN_BDB_AUTO_COMMIT (DB_AUTO_COMMIT)
+#else
+#define SVN_BDB_AUTO_COMMIT (0)
+#endif
+
+/* DB_INCOMPLETE is obsolete in BDB 4.1. */
+#ifdef DB_INCOMPLETE
+#define SVN_BDB_HAS_DB_INCOMPLETE 1
+#else
+#define SVN_BDB_HAS_DB_INCOMPLETE 0
+#endif
+
+/* In BDB 4.3, "buffer too small" errors come back with
+ DB_BUFFER_SMALL (instead of ENOMEM, which is now fatal). */
+#ifdef DB_BUFFER_SMALL
+#define SVN_BDB_DB_BUFFER_SMALL DB_BUFFER_SMALL
+#else
+#define SVN_BDB_DB_BUFFER_SMALL ENOMEM
+#endif
+
+/* BDB 4.4 introdiced the DB_REGISTER flag for DBEnv::open that allows
+ for automatic recovery of the databases after a program crash. */
+#ifdef DB_REGISTER
+#define SVN_BDB_AUTO_RECOVER (DB_REGISTER | DB_RECOVER)
+#else
+#define SVN_BDB_AUTO_RECOVER (0)
+#endif
+
+
+/* Explicit BDB version check. */
+#define SVN_BDB_VERSION_AT_LEAST(major,minor) \
+ (DB_VERSION_MAJOR > (major) \
+ || (DB_VERSION_MAJOR == (major) && DB_VERSION_MINOR >= (minor)))
+
+
+/* Parameter lists */
+
+/* In BDB 4.1, DB->open takes a transaction parameter. We'll ignore it
+ when building with 4.0. */
+#if SVN_BDB_VERSION_AT_LEAST(4,1)
+#define SVN_BDB_OPEN_PARAMS(env,txn) (env), (txn)
+#else
+#define SVN_BDB_OPEN_PARAMS(env,txn) (env)
+#endif
+
+/* In BDB 4.3, the error gatherer function grew a new DBENV parameter,
+ and the MSG parameter's type changed. */
+#if SVN_BDB_VERSION_AT_LEAST(4,3)
+/* Prevents most compilers from whining about unused parameters. */
+#define SVN_BDB_ERROR_GATHERER_IGNORE(varname) ((void)(varname))
+#else
+#define bdb_error_gatherer(param1, param2, param3) \
+ bdb_error_gatherer(param2, char *msg)
+#define SVN_BDB_ERROR_GATHERER_IGNORE(varname) ((void)0)
+#endif
+
+/* In BDB 4.3 and later, the file names in DB_ENV->open and DB->open
+ are assumed to be encoded in UTF-8 on Windows. */
+#if defined(WIN32) && SVN_BDB_VERSION_AT_LEAST(4,3)
+#define SVN_BDB_PATH_UTF8 (1)
+#else
+#define SVN_BDB_PATH_UTF8 (0)
+#endif
+
+/* In BDB 4.6, the cursor routines were renamed, and the old names
+ deprecated. */
+#if SVN_BDB_VERSION_AT_LEAST(4,6)
+#define svn_bdb_dbc_close(c) ((c)->close(c))
+#define svn_bdb_dbc_count(c,r,f) ((c)->count(c,r,f))
+#define svn_bdb_dbc_del(c,f) ((c)->del(c,f))
+#define svn_bdb_dbc_dup(c,p,f) ((c)->dup(c,p,f))
+#define svn_bdb_dbc_get(c,k,d,f) ((c)->get(c,k,d,f))
+#define svn_bdb_dbc_pget(c,k,p,d,f) ((c)->pget(c,k,p,d,f))
+#define svn_bdb_dbc_put(c,k,d,f) ((c)->put(c,k,d,f))
+#else
+#define svn_bdb_dbc_close(c) ((c)->c_close(c))
+#define svn_bdb_dbc_count(c,r,f) ((c)->c_count(c,r,f))
+#define svn_bdb_dbc_del(c,f) ((c)->c_del(c,f))
+#define svn_bdb_dbc_dup(c,p,f) ((c)->c_dup(c,p,f))
+#define svn_bdb_dbc_get(c,k,d,f) ((c)->c_get(c,k,d,f))
+#define svn_bdb_dbc_pget(c,k,p,d,f) ((c)->c_pget(c,k,p,d,f))
+#define svn_bdb_dbc_put(c,k,d,f) ((c)->c_put(c,k,d,f))
+#endif
+
+/* Before calling db_create, we must check that the version of the BDB
+ libraries we're linking with is the same as the one we compiled
+ against, because the DB->open call is not binary compatible between
+ BDB 4.0 and 4.1. This function returns DB_OLD_VERSION if the
+ compile-time and run-time versions of BDB don't match. */
+int svn_fs_bdb__check_version(void);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_BDB_COMPAT_H */
diff --git a/subversion/libsvn_fs_base/bdb/changes-table.c b/subversion/libsvn_fs_base/bdb/changes-table.c
new file mode 100644
index 0000000..80ff468
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/changes-table.c
@@ -0,0 +1,457 @@
+/* changes-table.c : operations on the `changes' table
+ *
+ * ====================================================================
+ * 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 "bdb_compat.h"
+
+#include <apr_hash.h>
+#include <apr_tables.h>
+
+#include "svn_hash.h"
+#include "svn_fs.h"
+#include "svn_pools.h"
+#include "svn_path.h"
+#include "../fs.h"
+#include "../err.h"
+#include "../trail.h"
+#include "../id.h"
+#include "../util/fs_skels.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "dbt.h"
+#include "changes-table.h"
+
+#include "private/svn_fs_util.h"
+#include "private/svn_fspath.h"
+#include "svn_private_config.h"
+
+
+/*** Creating and opening the changes table. ***/
+
+int
+svn_fs_bdb__open_changes_table(DB **changes_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *changes;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&changes, env, 0));
+
+ /* Enable duplicate keys. This allows us to store the changes
+ one-per-row. Note: this must occur before ->open(). */
+ BDB_ERR(changes->set_flags(changes, DB_DUP));
+
+ BDB_ERR((changes->open)(SVN_BDB_OPEN_PARAMS(changes, NULL),
+ "changes", 0, DB_BTREE,
+ open_flags, 0666));
+
+ *changes_p = changes;
+ return 0;
+}
+
+
+
+/*** Storing and retrieving changes. ***/
+
+svn_error_t *
+svn_fs_bdb__changes_add(svn_fs_t *fs,
+ const char *key,
+ change_t *change,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, value;
+ svn_skel_t *skel;
+
+ /* Convert native type to skel. */
+ SVN_ERR(svn_fs_base__unparse_change_skel(&skel, change, pool));
+
+ /* Store a new record into the database. */
+ svn_fs_base__str_to_dbt(&query, key);
+ svn_fs_base__skel_to_dbt(&value, skel, pool);
+ svn_fs_base__trail_debug(trail, "changes", "put");
+ return BDB_WRAP(fs, N_("creating change"),
+ bfd->changes->put(bfd->changes, trail->db_txn,
+ &query, &value, 0));
+}
+
+
+svn_error_t *
+svn_fs_bdb__changes_delete(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ int db_err;
+ DBT query;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ svn_fs_base__trail_debug(trail, "changes", "del");
+ db_err = bfd->changes->del(bfd->changes, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, key), 0);
+
+ /* If there're no changes for KEY, that is acceptable. Any other
+ error should be propagated to the caller, though. */
+ if ((db_err) && (db_err != DB_NOTFOUND))
+ {
+ SVN_ERR(BDB_WRAP(fs, N_("deleting changes"), db_err));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Merge the internal-use-only CHANGE into a hash of public-FS
+ svn_fs_path_change2_t CHANGES, collapsing multiple changes into a
+ single succinct change per path. */
+static svn_error_t *
+fold_change(apr_hash_t *changes,
+ const change_t *change)
+{
+ apr_pool_t *pool = apr_hash_pool_get(changes);
+ svn_fs_path_change2_t *old_change, *new_change;
+ const char *path;
+
+ if ((old_change = svn_hash_gets(changes, change->path)))
+ {
+ /* This path already exists in the hash, so we have to merge
+ this change into the already existing one. */
+
+ /* Since the path already exists in the hash, we don't have to
+ dup the allocation for the path itself. */
+ path = change->path;
+
+ /* Sanity check: only allow NULL node revision ID in the
+ `reset' case. */
+ if ((! change->noderev_id) && (change->kind != svn_fs_path_change_reset))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Missing required node revision ID"));
+
+ /* Sanity check: we should be talking about the same node
+ revision ID as our last change except where the last change
+ was a deletion. */
+ if (change->noderev_id
+ && (! svn_fs_base__id_eq(old_change->node_rev_id,
+ change->noderev_id))
+ && (old_change->change_kind != svn_fs_path_change_delete))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid change ordering: new node revision ID without delete"));
+
+ /* Sanity check: an add, replacement, or reset must be the first
+ thing to follow a deletion. */
+ if ((old_change->change_kind == svn_fs_path_change_delete)
+ && (! ((change->kind == svn_fs_path_change_replace)
+ || (change->kind == svn_fs_path_change_reset)
+ || (change->kind == svn_fs_path_change_add))))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid change ordering: non-add change on deleted path"));
+
+ /* Sanity check: an add can't follow anything except
+ a delete or reset. */
+ if ((change->kind == svn_fs_path_change_add)
+ && (old_change->change_kind != svn_fs_path_change_delete)
+ && (old_change->change_kind != svn_fs_path_change_reset))
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Invalid change ordering: add change on preexisting path"));
+
+ /* Now, merge that change in. */
+ switch (change->kind)
+ {
+ case svn_fs_path_change_reset:
+ /* A reset here will simply remove the path change from the
+ hash. */
+ old_change = NULL;
+ break;
+
+ case svn_fs_path_change_delete:
+ if (old_change->change_kind == svn_fs_path_change_add)
+ {
+ /* If the path was introduced in this transaction via an
+ add, and we are deleting it, just remove the path
+ altogether. */
+ old_change = NULL;
+ }
+ else
+ {
+ /* A deletion overrules all previous changes. */
+ old_change->change_kind = svn_fs_path_change_delete;
+ old_change->text_mod = change->text_mod;
+ old_change->prop_mod = change->prop_mod;
+ }
+ break;
+
+ case svn_fs_path_change_add:
+ case svn_fs_path_change_replace:
+ /* An add at this point must be following a previous delete,
+ so treat it just like a replace. */
+ old_change->change_kind = svn_fs_path_change_replace;
+ old_change->node_rev_id = svn_fs_base__id_copy(change->noderev_id,
+ pool);
+ old_change->text_mod = change->text_mod;
+ old_change->prop_mod = change->prop_mod;
+ break;
+
+ case svn_fs_path_change_modify:
+ default:
+ if (change->text_mod)
+ old_change->text_mod = TRUE;
+ if (change->prop_mod)
+ old_change->prop_mod = TRUE;
+ break;
+ }
+
+ /* Point our new_change to our (possibly modified) old_change. */
+ new_change = old_change;
+ }
+ else
+ {
+ /* This change is new to the hash, so make a new public change
+ structure from the internal one (in the hash's pool), and dup
+ the path into the hash's pool, too. */
+ new_change = svn_fs__path_change_create_internal(
+ svn_fs_base__id_copy(change->noderev_id, pool),
+ change->kind,
+ pool);
+ new_change->text_mod = change->text_mod;
+ new_change->prop_mod = change->prop_mod;
+ new_change->node_kind = svn_node_unknown;
+ new_change->copyfrom_known = FALSE;
+ path = apr_pstrdup(pool, change->path);
+ }
+
+ /* Add (or update) this path. */
+ svn_hash_sets(changes, path, new_change);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_bdb__changes_fetch(apr_hash_t **changes_p,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBC *cursor;
+ DBT query, result;
+ int db_err = 0, db_c_err = 0;
+ svn_error_t *err = SVN_NO_ERROR;
+ apr_hash_t *changes = apr_hash_make(pool);
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Get a cursor on the first record matching KEY, and then loop over
+ the records, adding them to the return array. */
+ svn_fs_base__trail_debug(trail, "changes", "cursor");
+ SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading changes"),
+ bfd->changes->cursor(bfd->changes, trail->db_txn,
+ &cursor, 0)));
+
+ /* Advance the cursor to the key that we're looking for. */
+ svn_fs_base__str_to_dbt(&query, key);
+ svn_fs_base__result_dbt(&result);
+ db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
+ if (! db_err)
+ svn_fs_base__track_dbt(&result, pool);
+
+ while (! db_err)
+ {
+ change_t *change;
+ svn_skel_t *result_skel;
+
+ /* Clear the per-iteration subpool. */
+ svn_pool_clear(subpool);
+
+ /* RESULT now contains a change record associated with KEY. We
+ need to parse that skel into an change_t structure ... */
+ result_skel = svn_skel__parse(result.data, result.size, subpool);
+ if (! result_skel)
+ {
+ err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Error reading changes for key '%s'"),
+ key);
+ goto cleanup;
+ }
+ err = svn_fs_base__parse_change_skel(&change, result_skel, subpool);
+ if (err)
+ goto cleanup;
+
+ /* ... and merge it with our return hash. */
+ err = fold_change(changes, change);
+ if (err)
+ goto cleanup;
+
+ /* Now, if our change was a deletion or replacement, we have to
+ blow away any changes thus far on paths that are (or, were)
+ children of this path.
+ ### i won't bother with another iteration pool here -- at
+ most we talking about a few extra dups of paths into what
+ is already a temporary subpool.
+ */
+ if ((change->kind == svn_fs_path_change_delete)
+ || (change->kind == svn_fs_path_change_replace))
+ {
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(subpool, changes);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ /* KEY is the path. */
+ const void *hashkey;
+ apr_ssize_t klen;
+ const char *child_relpath;
+
+ apr_hash_this(hi, &hashkey, &klen, NULL);
+
+ /* If we come across our own path, ignore it.
+ If we come across a child of our path, remove it. */
+ child_relpath = svn_fspath__skip_ancestor(change->path, hashkey);
+ if (child_relpath && *child_relpath)
+ apr_hash_set(changes, hashkey, klen, NULL);
+ }
+ }
+
+ /* Advance the cursor to the next record with this same KEY, and
+ fetch that record. */
+ svn_fs_base__result_dbt(&result);
+ db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
+ if (! db_err)
+ svn_fs_base__track_dbt(&result, pool);
+ }
+
+ /* Destroy the per-iteration subpool. */
+ svn_pool_destroy(subpool);
+
+ /* If there are no (more) change records for this KEY, we're
+ finished. Just return the (possibly empty) array. Any other
+ error, however, needs to get handled appropriately. */
+ if (db_err && (db_err != DB_NOTFOUND))
+ err = BDB_WRAP(fs, N_("fetching changes"), db_err);
+
+ cleanup:
+ /* Close the cursor. */
+ db_c_err = svn_bdb_dbc_close(cursor);
+
+ /* If we had an error prior to closing the cursor, return the error. */
+ if (err)
+ return svn_error_trace(err);
+
+ /* If our only error thus far was when we closed the cursor, return
+ that error. */
+ if (db_c_err)
+ SVN_ERR(BDB_WRAP(fs, N_("closing changes cursor"), db_c_err));
+
+ /* Finally, set our return variable and get outta here. */
+ *changes_p = changes;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_bdb__changes_fetch_raw(apr_array_header_t **changes_p,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBC *cursor;
+ DBT query, result;
+ int db_err = 0, db_c_err = 0;
+ svn_error_t *err = SVN_NO_ERROR;
+ change_t *change;
+ apr_array_header_t *changes = apr_array_make(pool, 4, sizeof(change));
+
+ /* Get a cursor on the first record matching KEY, and then loop over
+ the records, adding them to the return array. */
+ svn_fs_base__trail_debug(trail, "changes", "cursor");
+ SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading changes"),
+ bfd->changes->cursor(bfd->changes, trail->db_txn,
+ &cursor, 0)));
+
+ /* Advance the cursor to the key that we're looking for. */
+ svn_fs_base__str_to_dbt(&query, key);
+ svn_fs_base__result_dbt(&result);
+ db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
+ if (! db_err)
+ svn_fs_base__track_dbt(&result, pool);
+
+ while (! db_err)
+ {
+ svn_skel_t *result_skel;
+
+ /* RESULT now contains a change record associated with KEY. We
+ need to parse that skel into an change_t structure ... */
+ result_skel = svn_skel__parse(result.data, result.size, pool);
+ if (! result_skel)
+ {
+ err = svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Error reading changes for key '%s'"),
+ key);
+ goto cleanup;
+ }
+ err = svn_fs_base__parse_change_skel(&change, result_skel, pool);
+ if (err)
+ goto cleanup;
+
+ /* ... and add it to our return array. */
+ APR_ARRAY_PUSH(changes, change_t *) = change;
+
+ /* Advance the cursor to the next record with this same KEY, and
+ fetch that record. */
+ svn_fs_base__result_dbt(&result);
+ db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
+ if (! db_err)
+ svn_fs_base__track_dbt(&result, pool);
+ }
+
+ /* If there are no (more) change records for this KEY, we're
+ finished. Just return the (possibly empty) array. Any other
+ error, however, needs to get handled appropriately. */
+ if (db_err && (db_err != DB_NOTFOUND))
+ err = BDB_WRAP(fs, N_("fetching changes"), db_err);
+
+ cleanup:
+ /* Close the cursor. */
+ db_c_err = svn_bdb_dbc_close(cursor);
+
+ /* If we had an error prior to closing the cursor, return the error. */
+ if (err)
+ return svn_error_trace(err);
+
+ /* If our only error thus far was when we closed the cursor, return
+ that error. */
+ if (db_c_err)
+ SVN_ERR(BDB_WRAP(fs, N_("closing changes cursor"), db_c_err));
+
+ /* Finally, set our return variable and get outta here. */
+ *changes_p = changes;
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_base/bdb/changes-table.h b/subversion/libsvn_fs_base/bdb/changes-table.h
new file mode 100644
index 0000000..c9df636
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/changes-table.h
@@ -0,0 +1,94 @@
+/* changes-table.h : internal interface to `changes' table
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_CHANGES_TABLE_H
+#define SVN_LIBSVN_FS_CHANGES_TABLE_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_io.h"
+#include "svn_fs.h"
+#include "../fs.h"
+#include "../trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `changes' table in ENV. If CREATE is non-zero, create one
+ if it doesn't exist. Set *CHANGES_P to the new table. Return a
+ Berkeley DB error code. */
+int svn_fs_bdb__open_changes_table(DB **changes_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+/* Add CHANGE as a record to the `changes' table in FS as part of
+ TRAIL, keyed on KEY.
+
+ CHANGE->path is expected to be a canonicalized filesystem path (see
+ svn_fs__canonicalize_abspath).
+
+ Note that because the `changes' table uses duplicate keys, this
+ function will not overwrite prior additions that have the KEY
+ key, but simply adds this new record alongside previous ones. */
+svn_error_t *svn_fs_bdb__changes_add(svn_fs_t *fs,
+ const char *key,
+ change_t *change,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Remove all changes associated with KEY from the `changes' table in
+ FS, as part of TRAIL. */
+svn_error_t *svn_fs_bdb__changes_delete(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Return a hash *CHANGES_P, keyed on const char * paths, and
+ containing svn_fs_path_change2_t * values representing summarized
+ changed records associated with KEY in FS, as part of TRAIL.
+ Allocate the array and its items in POOL. */
+svn_error_t *svn_fs_bdb__changes_fetch(apr_hash_t **changes_p,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Return an array *CHANGES_P of change_t * items representing
+ all the change records associated with KEY in FS, as part of TRAIL.
+ Allocate the array and its items in POOL. */
+svn_error_t *svn_fs_bdb__changes_fetch_raw(apr_array_header_t **changes_p,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_CHANGES_TABLE_H */
diff --git a/subversion/libsvn_fs_base/bdb/checksum-reps-table.c b/subversion/libsvn_fs_base/bdb/checksum-reps-table.c
new file mode 100644
index 0000000..f4a34c3
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/checksum-reps-table.c
@@ -0,0 +1,208 @@
+/* checksum-reps-table.c : operations on the `checksum-reps' table
+ *
+ * ====================================================================
+ * 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_strings.h>
+
+#include "bdb_compat.h"
+#include "../fs.h"
+#include "../err.h"
+#include "../key-gen.h"
+#include "dbt.h"
+#include "../trail.h"
+#include "bdb-err.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "checksum-reps-table.h"
+
+#include "svn_private_config.h"
+
+
+int svn_fs_bdb__open_checksum_reps_table(DB **checksum_reps_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *checksum_reps;
+ int error;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&checksum_reps, env, 0));
+ error = (checksum_reps->open)(SVN_BDB_OPEN_PARAMS(checksum_reps, NULL),
+ "checksum-reps", 0, DB_BTREE,
+ open_flags, 0666);
+
+ /* Create the checksum-reps table if it doesn't exist. */
+ if (error == ENOENT && (! create))
+ {
+ BDB_ERR(checksum_reps->close(checksum_reps, 0));
+ return svn_fs_bdb__open_checksum_reps_table(checksum_reps_p, env, TRUE);
+ }
+
+ /* Create the initial `next-key' table entry. */
+ if (create)
+ {
+ DBT key, value;
+ BDB_ERR(checksum_reps->put(checksum_reps, 0,
+ svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&value, "0"), 0));
+ }
+
+ BDB_ERR(error);
+
+ *checksum_reps_p = checksum_reps;
+ return 0;
+}
+
+svn_error_t *svn_fs_bdb__get_checksum_rep(const char **rep_key,
+ svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+
+ /* We only allow SHA1 checksums in this table. */
+ if (checksum->kind != svn_checksum_sha1)
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
+ _("Only SHA1 checksums can be used as keys in the "
+ "checksum-reps table.\n"));
+
+ svn_fs_base__trail_debug(trail, "checksum-reps", "get");
+ db_err = bfd->checksum_reps->get(bfd->checksum_reps, trail->db_txn,
+ svn_fs_base__checksum_to_dbt(&key, checksum),
+ svn_fs_base__result_dbt(&value), 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_no_such_checksum_rep(fs, checksum);
+
+ *rep_key = apr_pstrmemdup(pool, value.data, value.size);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_fs_bdb__set_checksum_rep(svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+
+ /* We only allow SHA1 checksums in this table. */
+ if (checksum->kind != svn_checksum_sha1)
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
+ _("Only SHA1 checksums can be used as keys in the "
+ "checksum-reps table.\n"));
+
+ /* Create a key from our CHECKSUM. */
+ svn_fs_base__checksum_to_dbt(&key, checksum);
+
+ /* Check to see if we already have a mapping for CHECKSUM. If so,
+ and the value is the same one we were about to write, that's
+ cool -- just do nothing. If, however, the value is *different*,
+ that's a red flag! */
+ svn_fs_base__trail_debug(trail, "checksum-reps", "get");
+ db_err = bfd->checksum_reps->get(bfd->checksum_reps, trail->db_txn,
+ &key, svn_fs_base__result_dbt(&value), 0);
+ svn_fs_base__track_dbt(&value, pool);
+ if (db_err != DB_NOTFOUND)
+ {
+ const char *sum_str = svn_checksum_to_cstring_display(checksum, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_ALREADY_EXISTS, NULL,
+ _("Representation key for checksum '%s' exists in filesystem '%s'."),
+ sum_str, fs->path);
+ }
+
+ /* Create a value from our REP_KEY, and add this record to the table. */
+ svn_fs_base__str_to_dbt(&value, rep_key);
+ svn_fs_base__trail_debug(trail, "checksum-reps", "put");
+ SVN_ERR(BDB_WRAP(fs, N_("storing checksum-reps record"),
+ bfd->checksum_reps->put(bfd->checksum_reps, trail->db_txn,
+ &key, &value, 0)));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_fs_bdb__delete_checksum_rep(svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key;
+
+ /* We only allow SHA1 checksums in this table. */
+ if (checksum->kind != svn_checksum_sha1)
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL,
+ _("Only SHA1 checksums can be used as keys in the "
+ "checksum-reps table.\n"));
+
+ svn_fs_base__checksum_to_dbt(&key, checksum);
+ svn_fs_base__trail_debug(trail, "checksum-reps", "del");
+ SVN_ERR(BDB_WRAP(fs, N_("deleting entry from 'checksum-reps' table"),
+ bfd->checksum_reps->del(bfd->checksum_reps,
+ trail->db_txn, &key, 0)));
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_fs_bdb__reserve_rep_reuse_id(const char **id_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, result;
+ apr_size_t len;
+ char next_key[MAX_KEY_SIZE];
+ int db_err;
+
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY);
+
+ /* Get the current value associated with the `next-key' key in the
+ `checksum-reps' table. */
+ svn_fs_base__trail_debug(trail, "checksum-reps", "get");
+ SVN_ERR(BDB_WRAP(fs, N_("allocating new representation reuse ID "
+ "(getting 'next-key')"),
+ bfd->checksum_reps->get(bfd->checksum_reps, trail->db_txn,
+ &query,
+ svn_fs_base__result_dbt(&result),
+ 0)));
+ svn_fs_base__track_dbt(&result, pool);
+
+ /* Set our return value. */
+ *id_p = apr_pstrmemdup(pool, result.data, result.size);
+
+ /* Bump to future key. */
+ len = result.size;
+ svn_fs_base__next_key(result.data, &len, next_key);
+ svn_fs_base__trail_debug(trail, "checksum_reps", "put");
+ db_err = bfd->checksum_reps->put(bfd->checksum_reps, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query,
+ NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&result, next_key),
+ 0);
+
+ return BDB_WRAP(fs, N_("bumping next representation reuse ID"), db_err);
+}
diff --git a/subversion/libsvn_fs_base/bdb/checksum-reps-table.h b/subversion/libsvn_fs_base/bdb/checksum-reps-table.h
new file mode 100644
index 0000000..ccdcd48
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/checksum-reps-table.h
@@ -0,0 +1,89 @@
+/* checksum-reps-table.h : internal interface to ops on `checksum-reps' table
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_CHECKSUM_REPS_TABLE_H
+#define SVN_LIBSVN_FS_CHECKSUM_REPS_TABLE_H
+
+#include "svn_fs.h"
+#include "svn_error.h"
+#include "svn_checksum.h"
+#include "../trail.h"
+#include "../fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `checksum-reps' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *CHECKSUM_REPS_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_checksum_reps_table(DB **checksum_reps_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+/* Set *REP_KEY to the representation key stored as the value of key
+ CHECKSUM in the `checksum-reps' table. Do this as part of TRAIL.
+ Use POOL for allocations.
+
+ If no such node revision ID is stored for CHECKSUM, return
+ SVN_ERR_FS_NO_SUCH_CHECKSUM_REP. */
+svn_error_t *svn_fs_bdb__get_checksum_rep(const char **rep_key,
+ svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Store in the `checksum-reps' table a mapping of CHECKSUM to
+ representation key REP_KEY in FS. Do this as part of TRAIL. Use
+ POOL for temporary allocations.
+
+ WARNING: NEVER store a record that maps a checksum to a mutable
+ representation. Ever. Under pain of dismemberment and death. */
+svn_error_t *svn_fs_bdb__set_checksum_rep(svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Delete from the `checksum-reps' table the mapping of CHECKSUM to a
+ representation key in FS. Do this as part of TRAIL. Use POOL for
+ temporary allocations. */
+svn_error_t *svn_fs_bdb__delete_checksum_rep(svn_fs_t *fs,
+ svn_checksum_t *checksum,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Reserve a unique reuse ID in the `checksum-reps' table in FS for a
+ new instance of a re-used representation as part of TRAIL. Return
+ the slot's id in *REUSE_ID_P, allocated in POOL. */
+svn_error_t *svn_fs_bdb__reserve_rep_reuse_id(const char **reuse_id_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_CHECKSUM_REPS_TABLE_H */
diff --git a/subversion/libsvn_fs_base/bdb/copies-table.c b/subversion/libsvn_fs_base/bdb/copies-table.c
new file mode 100644
index 0000000..7bf6ca8
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/copies-table.c
@@ -0,0 +1,210 @@
+/* copies-table.c : operations on the `copies' table
+ *
+ * ====================================================================
+ * 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 "bdb_compat.h"
+
+#include "private/svn_skel.h"
+
+#include "../fs.h"
+#include "../err.h"
+#include "../key-gen.h"
+#include "dbt.h"
+#include "../util/fs_skels.h"
+#include "../trail.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "copies-table.h"
+#include "rev-table.h"
+
+#include "svn_private_config.h"
+
+
+int
+svn_fs_bdb__open_copies_table(DB **copies_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *copies;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&copies, env, 0));
+ BDB_ERR((copies->open)(SVN_BDB_OPEN_PARAMS(copies, NULL),
+ "copies", 0, DB_BTREE,
+ open_flags, 0666));
+
+ /* Create the initial `next-key' table entry. */
+ if (create)
+ {
+ DBT key, value;
+ BDB_ERR(copies->put(copies, 0,
+ svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&value, "0"), 0));
+ }
+
+ *copies_p = copies;
+ return 0;
+}
+
+
+/* Store COPY as a copy named COPY_ID in FS as part of TRAIL. */
+/* ### only has one caller; might not need to be abstracted */
+static svn_error_t *
+put_copy(svn_fs_t *fs,
+ const copy_t *copy,
+ const char *copy_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ svn_skel_t *copy_skel;
+ DBT key, value;
+
+ /* Convert native type to skel. */
+ SVN_ERR(svn_fs_base__unparse_copy_skel(&copy_skel, copy, pool));
+
+ /* Only in the context of this function do we know that the DB call
+ will not attempt to modify COPY_ID, so the cast belongs here. */
+ svn_fs_base__str_to_dbt(&key, copy_id);
+ svn_fs_base__skel_to_dbt(&value, copy_skel, pool);
+ svn_fs_base__trail_debug(trail, "copies", "put");
+ return BDB_WRAP(fs, N_("storing copy record"),
+ bfd->copies->put(bfd->copies, trail->db_txn,
+ &key, &value, 0));
+}
+
+
+svn_error_t *
+svn_fs_bdb__reserve_copy_id(const char **id_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, result;
+ apr_size_t len;
+ char next_key[MAX_KEY_SIZE];
+ int db_err;
+
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY);
+
+ /* Get the current value associated with the `next-key' key in the
+ copies table. */
+ svn_fs_base__trail_debug(trail, "copies", "get");
+ SVN_ERR(BDB_WRAP(fs, N_("allocating new copy ID (getting 'next-key')"),
+ bfd->copies->get(bfd->copies, trail->db_txn, &query,
+ svn_fs_base__result_dbt(&result),
+ 0)));
+ svn_fs_base__track_dbt(&result, pool);
+
+ /* Set our return value. */
+ *id_p = apr_pstrmemdup(pool, result.data, result.size);
+
+ /* Bump to future key. */
+ len = result.size;
+ svn_fs_base__next_key(result.data, &len, next_key);
+ svn_fs_base__trail_debug(trail, "copies", "put");
+ db_err = bfd->copies->put(bfd->copies, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&result, next_key),
+ 0);
+
+ return BDB_WRAP(fs, N_("bumping next copy key"), db_err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__create_copy(svn_fs_t *fs,
+ const char *copy_id,
+ const char *src_path,
+ const char *src_txn_id,
+ const svn_fs_id_t *dst_noderev_id,
+ copy_kind_t kind,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ copy_t copy;
+ copy.kind = kind;
+ copy.src_path = src_path;
+ copy.src_txn_id = src_txn_id;
+ copy.dst_noderev_id = dst_noderev_id;
+ return put_copy(fs, &copy, copy_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_bdb__delete_copy(svn_fs_t *fs,
+ const char *copy_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key;
+ int db_err;
+
+ svn_fs_base__str_to_dbt(&key, copy_id);
+ svn_fs_base__trail_debug(trail, "copies", "del");
+ db_err = bfd->copies->del(bfd->copies, trail->db_txn, &key, 0);
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_no_such_copy(fs, copy_id);
+ return BDB_WRAP(fs, N_("deleting entry from 'copies' table"), db_err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__get_copy(copy_t **copy_p,
+ svn_fs_t *fs,
+ const char *copy_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+ svn_skel_t *skel;
+ copy_t *copy;
+
+ /* Only in the context of this function do we know that the DB call
+ will not attempt to modify copy_id, so the cast belongs here. */
+ svn_fs_base__trail_debug(trail, "copies", "get");
+ db_err = bfd->copies->get(bfd->copies, trail->db_txn,
+ svn_fs_base__str_to_dbt(&key, copy_id),
+ svn_fs_base__result_dbt(&value),
+ 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_no_such_copy(fs, copy_id);
+ SVN_ERR(BDB_WRAP(fs, N_("reading copy"), db_err));
+
+ /* Unparse COPY skel */
+ skel = svn_skel__parse(value.data, value.size, pool);
+ if (! skel)
+ return svn_fs_base__err_corrupt_copy(fs, copy_id);
+
+ /* Convert skel to native type. */
+ SVN_ERR(svn_fs_base__parse_copy_skel(&copy, skel, pool));
+ *copy_p = copy;
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_base/bdb/copies-table.h b/subversion/libsvn_fs_base/bdb/copies-table.h
new file mode 100644
index 0000000..08ab139
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/copies-table.h
@@ -0,0 +1,93 @@
+/* copies-table.h : internal interface to ops on `copies' table
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_COPIES_TABLE_H
+#define SVN_LIBSVN_FS_COPIES_TABLE_H
+
+#include "svn_fs.h"
+#include "../fs.h"
+#include "../trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `copies' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *COPIES_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_copies_table(DB **copies_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+/* Reserve a slot in the `copies' table in FS for a new copy operation
+ as part of TRAIL. Return the slot's id in *COPY_ID_P, allocated in
+ POOL. */
+svn_error_t *svn_fs_bdb__reserve_copy_id(const char **copy_id_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Create a new copy with id COPY_ID in FS as part of TRAIL.
+ SRC_PATH/SRC_TXN_ID are the path/transaction ID (respectively) of
+ the copy source, and DST_NODEREV_ID is the node revision id of the
+ copy destination. KIND describes the type of copy operation.
+
+ SRC_PATH is expected to be a canonicalized filesystem path (see
+ svn_fs__canonicalize_abspath).
+
+ COPY_ID should generally come from a call to
+ svn_fs_bdb__reserve_copy_id(). */
+svn_error_t *svn_fs_bdb__create_copy(svn_fs_t *fs,
+ const char *copy_id,
+ const char *src_path,
+ const char *src_txn_id,
+ const svn_fs_id_t *dst_noderev_id,
+ copy_kind_t kind,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Remove the copy whose name is COPY_ID from the `copies' table of
+ FS, as part of TRAIL. If there is no such copy,
+ SVN_ERR_FS_NO_SUCH_COPY is the error returned. */
+svn_error_t *svn_fs_bdb__delete_copy(svn_fs_t *fs,
+ const char *copy_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Retrieve the copy *COPY_P named COPY_ID from the `copies' table of
+ FS, as part of TRAIL. Perform all allocations in POOL. If
+ there is no such copy, SVN_ERR_FS_NO_SUCH_COPY is the error
+ returned. */
+svn_error_t *svn_fs_bdb__get_copy(copy_t **copy_p,
+ svn_fs_t *fs,
+ const char *copy_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_COPIES_TABLE_H */
diff --git a/subversion/libsvn_fs_base/bdb/dbt.c b/subversion/libsvn_fs_base/bdb/dbt.c
new file mode 100644
index 0000000..a18ba47
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/dbt.c
@@ -0,0 +1,170 @@
+/* dbt.c --- DBT-frobbing functions
+ *
+ * ====================================================================
+ * 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 <stdlib.h>
+#include <string.h>
+#include <apr_pools.h>
+#include <apr_md5.h>
+#include <apr_sha1.h>
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "../id.h"
+#include "dbt.h"
+
+
+DBT *
+svn_fs_base__clear_dbt(DBT *dbt)
+{
+ memset(dbt, 0, sizeof(*dbt));
+
+ return dbt;
+}
+
+
+DBT *svn_fs_base__nodata_dbt(DBT *dbt)
+{
+ svn_fs_base__clear_dbt(dbt);
+
+ /* A `nodata' dbt is one which retrieves zero bytes from offset zero,
+ and stores them in a zero-byte buffer in user-allocated memory. */
+ dbt->flags |= (DB_DBT_USERMEM | DB_DBT_PARTIAL);
+ dbt->doff = dbt->dlen = 0;
+
+ return dbt;
+}
+
+
+DBT *
+svn_fs_base__set_dbt(DBT *dbt, const void *data, apr_size_t size)
+{
+ svn_fs_base__clear_dbt(dbt);
+
+ dbt->data = (void *) data;
+ dbt->size = (u_int32_t) size;
+
+ return dbt;
+}
+
+
+DBT *
+svn_fs_base__result_dbt(DBT *dbt)
+{
+ svn_fs_base__clear_dbt(dbt);
+ dbt->flags |= DB_DBT_MALLOC;
+
+ return dbt;
+}
+
+
+/* An APR pool cleanup function that simply applies `free' to its
+ argument. */
+static apr_status_t
+apr_free_cleanup(void *arg)
+{
+ free(arg);
+
+ return 0;
+}
+
+
+DBT *
+svn_fs_base__track_dbt(DBT *dbt, apr_pool_t *pool)
+{
+ if (dbt->data)
+ apr_pool_cleanup_register(pool, dbt->data, apr_free_cleanup,
+ apr_pool_cleanup_null);
+
+ return dbt;
+}
+
+
+DBT *
+svn_fs_base__recno_dbt(DBT *dbt, db_recno_t *recno)
+{
+ svn_fs_base__set_dbt(dbt, recno, sizeof(*recno));
+ dbt->ulen = dbt->size;
+ dbt->flags |= DB_DBT_USERMEM;
+
+ return dbt;
+}
+
+
+int
+svn_fs_base__compare_dbt(const DBT *a, const DBT *b)
+{
+ int common_size = a->size > b->size ? b->size : a->size;
+ int cmp = memcmp(a->data, b->data, common_size);
+
+ if (cmp)
+ return cmp;
+ else
+ return a->size - b->size;
+}
+
+
+
+/* Building DBT's from interesting things. */
+
+
+/* Set DBT to the unparsed form of ID; allocate memory from POOL.
+ Return DBT. */
+DBT *
+svn_fs_base__id_to_dbt(DBT *dbt,
+ const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ svn_string_t *unparsed_id = svn_fs_base__id_unparse(id, pool);
+ svn_fs_base__set_dbt(dbt, unparsed_id->data, unparsed_id->len);
+ return dbt;
+}
+
+
+/* Set DBT to the unparsed form of SKEL; allocate memory from POOL. */
+DBT *
+svn_fs_base__skel_to_dbt(DBT *dbt,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ svn_stringbuf_t *unparsed_skel = svn_skel__unparse(skel, pool);
+ svn_fs_base__set_dbt(dbt, unparsed_skel->data, unparsed_skel->len);
+ return dbt;
+}
+
+
+/* Set DBT to the text of the null-terminated string STR. DBT will
+ refer to STR's storage. Return DBT. */
+DBT *
+svn_fs_base__str_to_dbt(DBT *dbt, const char *str)
+{
+ svn_fs_base__set_dbt(dbt, str, strlen(str));
+ return dbt;
+}
+
+DBT *
+svn_fs_base__checksum_to_dbt(DBT *dbt, svn_checksum_t *checksum)
+{
+ svn_fs_base__set_dbt(dbt, checksum->digest, svn_checksum_size(checksum));
+
+ return dbt;
+}
diff --git a/subversion/libsvn_fs_base/bdb/dbt.h b/subversion/libsvn_fs_base/bdb/dbt.h
new file mode 100644
index 0000000..db93d77
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/dbt.h
@@ -0,0 +1,120 @@
+/* dbt.h --- interface to DBT-frobbing functions
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_DBT_H
+#define SVN_LIBSVN_FS_DBT_H
+
+#include <apr_pools.h>
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_fs.h"
+#include "private/svn_skel.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Set all fields of DBT to zero. Return DBT. */
+DBT *svn_fs_base__clear_dbt(DBT *dbt);
+
+
+/* Set DBT to retrieve no data. This is useful when you're just
+ probing the table to see if an entry exists, or to find a key, but
+ don't care what the value is. Return DBT. */
+DBT *svn_fs_base__nodata_dbt(DBT *dbt);
+
+
+/* Set DBT to refer to the SIZE bytes at DATA. Return DBT. */
+DBT *svn_fs_base__set_dbt(DBT *dbt, const void *data, apr_size_t size);
+
+
+/* Prepare DBT to hold data returned from Berkeley DB. Return DBT.
+
+ Clear all its fields to zero, but set the DB_DBT_MALLOC flag,
+ requesting that Berkeley DB place the returned data in a freshly
+ malloc'd block. If the database operation succeeds, the caller
+ then owns the data block, and is responsible for making sure it
+ gets freed.
+
+ You can use this with svn_fs_base__track_dbt:
+
+ svn_fs_base__result_dbt (&foo);
+ ... some Berkeley DB operation that puts data in foo ...
+ svn_fs_base__track_dbt (&foo, pool);
+
+ This arrangement is:
+ - thread-safe --- the returned data is allocated via malloc, and
+ won't be overwritten if some other thread performs an operation
+ on the same table. See the explanation of ``Retrieved key/data
+ permanence'' in the section of the Berkeley DB manual on the DBT
+ type.
+ - pool-friendly --- the data returned by Berkeley DB is now guaranteed
+ to be freed when POOL is cleared. */
+DBT *svn_fs_base__result_dbt(DBT *dbt);
+
+/* Arrange for POOL to `track' DBT's data: when POOL is cleared,
+ DBT->data will be freed, using `free'. If DBT->data is zero,
+ do nothing.
+
+ This is meant for use with svn_fs_base__result_dbt; see the explanation
+ there. */
+DBT *svn_fs_base__track_dbt(DBT *dbt, apr_pool_t *pool);
+
+
+/* Prepare DBT for use as a key into a RECNO table. This call makes
+ DBT refer to the db_recno_t pointed to by RECNO as its buffer; the
+ record number you assign to *RECNO will be the table key. */
+DBT *svn_fs_base__recno_dbt(DBT *dbt, db_recno_t *recno);
+
+
+/* Compare two DBT values in byte-by-byte lexicographic order. */
+int svn_fs_base__compare_dbt(const DBT *a, const DBT *b);
+
+
+/* Set DBT to the unparsed form of ID; allocate memory from POOL.
+ Return DBT. */
+DBT *svn_fs_base__id_to_dbt(DBT *dbt, const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+
+/* Set DBT to the unparsed form of SKEL; allocate memory from POOL.
+ Return DBT. */
+DBT *svn_fs_base__skel_to_dbt(DBT *dbt, svn_skel_t *skel, apr_pool_t *pool);
+
+
+/* Set DBT to the text of the null-terminated string STR. DBT will
+ refer to STR's storage. Return DBT. */
+DBT *svn_fs_base__str_to_dbt(DBT *dbt, const char *str);
+
+
+/* Set DBT to the bytes contained by CHECKSUM. DBT will refer to CHECKSUM's
+ storage. Return DBT.*/
+DBT *svn_fs_base__checksum_to_dbt(DBT* dbt, svn_checksum_t *checksum);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_DBT_H */
diff --git a/subversion/libsvn_fs_base/bdb/env.c b/subversion/libsvn_fs_base/bdb/env.c
new file mode 100644
index 0000000..557c9dc
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/env.c
@@ -0,0 +1,719 @@
+/* env.h : managing the BDB environment
+ *
+ * ====================================================================
+ * 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 <assert.h>
+
+#include <apr.h>
+#if APR_HAS_THREADS
+#include <apr_thread_proc.h>
+#include <apr_time.h>
+#endif
+
+#include <apr_strings.h>
+#include <apr_hash.h>
+
+#include "svn_hash.h"
+#include "svn_path.h"
+#include "svn_pools.h"
+#include "svn_utf.h"
+#include "private/svn_atomic.h"
+#include "private/svn_mutex.h"
+
+#include "bdb-err.h"
+#include "bdb_compat.h"
+
+#include "env.h"
+
+/* A note about the BDB environment descriptor cache.
+
+ With the advent of DB_REGISTER in BDB-4.4, a process may only open
+ an environment handle once. This means that we must maintain a
+ cache of open environment handles, with reference counts. We
+ allocate each environment descriptor (a bdb_env_t) from its own
+ pool. The cache itself (and the cache pool) are shared between
+ threads, so all direct or indirect access to the pool is serialized
+ with a global mutex.
+
+ Because several threads can now use the same DB_ENV handle, we must
+ use the DB_THREAD flag when opening the environments, otherwise the
+ env handles (and all of libsvn_fs_base) won't be thread-safe.
+
+ If we use DB_THREAD, however, all of the code that reads data from
+ the database without a cursor must use either DB_DBT_MALLOC,
+ DB_DBT_REALLOC, or DB_DBT_USERMEM, as described in the BDB
+ documentation.
+
+ (Oh, yes -- using DB_THREAD might not work on some systems. But
+ then, it's quite probable that threading is seriously broken on
+ those systems anyway, so we'll rely on APR_HAS_THREADS.)
+*/
+
+
+/* The cache key for a Berkeley DB environment descriptor. This is a
+ combination of the device ID and INODE number of the Berkeley DB
+ config file.
+
+ XXX FIXME: Although the dev+inode combination is supposed do be
+ unique, apparently that's not always the case with some remote
+ filesystems. We /should/ be safe using this as a unique hash key,
+ because the database must be on a local filesystem. We can hope,
+ anyway. */
+typedef struct bdb_env_key_t
+{
+ apr_dev_t device;
+ apr_ino_t inode;
+} bdb_env_key_t;
+
+/* The cached Berkeley DB environment descriptor. */
+struct bdb_env_t
+{
+ /**************************************************************************/
+ /* Error Reporting */
+
+ /* A (char *) casted pointer to this structure is passed to BDB's
+ set_errpfx(), which treats it as a NUL-terminated character
+ string to prefix all BDB error messages. However, svn also
+ registers bdb_error_gatherer() as an error handler with
+ set_errcall() which turns off BDB's default printing of errors to
+ stderr and anytime thereafter when BDB reports an error and
+ before the BDB function returns, it calls bdb_error_gatherer()
+ and passes the same error prefix (char *) pointer given to
+ set_errpfx(). The bdb_error_gatherer() callback casts the
+ (char *) it back to a (bdb_env_t *).
+
+ To avoid problems should BDB ever try to interpret our baton as a
+ string, the first field in the structure is a char
+ errpfx_string[]. Initializers of this structure must strcpy the
+ value of BDB_ERRPFX_STRING into this array. */
+ char errpfx_string[sizeof(BDB_ERRPFX_STRING)];
+
+ /* Extended error information. */
+#if APR_HAS_THREADS
+ apr_threadkey_t *error_info; /* Points to a bdb_error_info_t. */
+#else
+ bdb_error_info_t error_info;
+#endif
+
+ /**************************************************************************/
+ /* BDB Environment Cache */
+
+ /* The Berkeley DB environment. */
+ DB_ENV *env;
+
+ /* The flags with which this environment was opened. Reopening the
+ environment with a different set of flags is not allowed. Trying
+ to change the state of the DB_PRIVATE flag is an especially bad
+ idea, so svn_fs_bdb__open() forbids any flag changes. */
+ u_int32_t flags;
+
+ /* The home path of this environment; a canonical SVN path encoded in
+ UTF-8 and allocated from this decriptor's pool. */
+ const char *path;
+
+ /* The home path of this environment, in the form expected by BDB. */
+ const char *path_bdb;
+
+ /* The reference count for this environment handle; this is
+ essentially the difference between the number of calls to
+ svn_fs_bdb__open and svn_fs_bdb__close. */
+ unsigned refcount;
+
+ /* If this flag is TRUE, someone has detected that the environment
+ descriptor is in a panicked state and should be removed from the
+ cache.
+
+ Note 1: Once this flag is set, it must not be cleared again.
+
+ Note 2: Unlike other fields in this structure, this field is not
+ protected by the cache mutex on threaded platforms, and
+ should only be accesses via the svn_atomic functions. */
+ volatile svn_atomic_t panic;
+
+ /* The key for the environment descriptor cache. */
+ bdb_env_key_t key;
+
+ /* The handle of the open DB_CONFIG file.
+
+ We keep the DB_CONFIG file open in this process as long as the
+ environment handle itself is open. On Windows, this guarantees
+ that the cache key remains unique; here's what the Windows SDK
+ docs have to say about the file index (interpreted as the INODE
+ number by APR):
+
+ "This value is useful only while the file is open by at least
+ one process. If no processes have it open, the index may
+ change the next time the file is opened."
+
+ Now, we certainly don't want a unique key to change while it's
+ being used, do we... */
+ apr_file_t *dbconfig_file;
+
+ /* The pool associated with this environment descriptor.
+
+ Because the descriptor has a life of its own, the structure and
+ any data associated with it are allocated from their own global
+ pool. */
+ apr_pool_t *pool;
+
+};
+
+
+#if APR_HAS_THREADS
+/* Get the thread-specific error info from a bdb_env_t. */
+static bdb_error_info_t *
+get_error_info(const bdb_env_t *bdb)
+{
+ void *priv;
+ apr_threadkey_private_get(&priv, bdb->error_info);
+ if (!priv)
+ {
+ priv = calloc(1, sizeof(bdb_error_info_t));
+ apr_threadkey_private_set(priv, bdb->error_info);
+ }
+ return priv;
+}
+#else
+#define get_error_info(bdb) (&(bdb)->error_info)
+#endif /* APR_HAS_THREADS */
+
+
+/* Convert a BDB error to a Subversion error. */
+static svn_error_t *
+convert_bdb_error(bdb_env_t *bdb, int db_err)
+{
+ if (db_err)
+ {
+ bdb_env_baton_t bdb_baton;
+ bdb_baton.env = bdb->env;
+ bdb_baton.bdb = bdb;
+ bdb_baton.error_info = get_error_info(bdb);
+ SVN_BDB_ERR(&bdb_baton, db_err);
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/* Allocating an appropriate Berkeley DB environment object. */
+
+/* BDB error callback. See bdb_error_info_t in env.h for more info.
+ Note: bdb_error_gatherer is a macro with BDB < 4.3, so be careful how
+ you use it! */
+static void
+bdb_error_gatherer(const DB_ENV *dbenv, const char *baton, const char *msg)
+{
+ /* See the documentation at bdb_env_t's definition why the
+ (bdb_env_t *) cast is safe and why it is done. */
+ bdb_error_info_t *error_info = get_error_info((const bdb_env_t *) baton);
+ svn_error_t *new_err;
+
+ SVN_BDB_ERROR_GATHERER_IGNORE(dbenv);
+
+ new_err = svn_error_createf(APR_SUCCESS, NULL, "bdb: %s", msg);
+ if (error_info->pending_errors)
+ svn_error_compose(error_info->pending_errors, new_err);
+ else
+ error_info->pending_errors = new_err;
+
+ if (error_info->user_callback)
+ error_info->user_callback(NULL, (char *)msg); /* ### I hate this cast... */
+}
+
+
+/* Pool cleanup for the cached environment descriptor. */
+static apr_status_t
+cleanup_env(void *data)
+{
+ bdb_env_t *bdb = data;
+ bdb->pool = NULL;
+ bdb->dbconfig_file = NULL; /* will be closed during pool destruction */
+#if APR_HAS_THREADS
+ apr_threadkey_private_delete(bdb->error_info);
+#endif /* APR_HAS_THREADS */
+
+ /* If there are no references to this descriptor, free its memory here,
+ so that we don't leak it if create_env returns an error.
+ See bdb_close, which takes care of freeing this memory if the
+ environment is still open when the cache is destroyed. */
+ if (!bdb->refcount)
+ free(data);
+
+ return APR_SUCCESS;
+}
+
+#if APR_HAS_THREADS
+/* This cleanup is the fall back plan. If the thread exits and the
+ environment hasn't been closed it's responsible for cleanup of the
+ thread local error info variable, which would otherwise be leaked.
+ Normally it will not be called, because svn_fs_bdb__close will
+ set the thread's error info to NULL after cleaning it up. */
+static void
+cleanup_error_info(void *baton)
+{
+ bdb_error_info_t *error_info = baton;
+
+ if (error_info)
+ svn_error_clear(error_info->pending_errors);
+
+ free(error_info);
+}
+#endif /* APR_HAS_THREADS */
+
+/* Create a Berkeley DB environment. */
+static svn_error_t *
+create_env(bdb_env_t **bdbp, const char *path, apr_pool_t *pool)
+{
+ int db_err;
+ bdb_env_t *bdb;
+ const char *path_bdb;
+ char *tmp_path, *tmp_path_bdb;
+ apr_size_t path_size, path_bdb_size;
+
+#if SVN_BDB_PATH_UTF8
+ path_bdb = svn_dirent_local_style(path, pool);
+#else
+ SVN_ERR(svn_utf_cstring_from_utf8(&path_bdb,
+ svn_dirent_local_style(path, pool),
+ pool));
+#endif
+
+ /* Allocate the whole structure, including strings, from the heap,
+ because it must survive the cache pool cleanup. */
+ path_size = strlen(path) + 1;
+ path_bdb_size = strlen(path_bdb) + 1;
+ /* Using calloc() to ensure the padding bytes in bdb->key (which is used as
+ * a hash key) are zeroed. */
+ bdb = calloc(1, sizeof(*bdb) + path_size + path_bdb_size);
+
+ /* We must initialize this now, as our callers may assume their bdb
+ pointer is valid when checking for errors. */
+ apr_pool_cleanup_register(pool, bdb, cleanup_env, apr_pool_cleanup_null);
+ apr_cpystrn(bdb->errpfx_string, BDB_ERRPFX_STRING,
+ sizeof(bdb->errpfx_string));
+ bdb->path = tmp_path = (char*)(bdb + 1);
+ bdb->path_bdb = tmp_path_bdb = tmp_path + path_size;
+ apr_cpystrn(tmp_path, path, path_size);
+ apr_cpystrn(tmp_path_bdb, path_bdb, path_bdb_size);
+ bdb->pool = pool;
+ *bdbp = bdb;
+
+#if APR_HAS_THREADS
+ {
+ apr_status_t apr_err = apr_threadkey_private_create(&bdb->error_info,
+ cleanup_error_info,
+ pool);
+ if (apr_err)
+ return svn_error_create(apr_err, NULL,
+ "Can't allocate thread-specific storage"
+ " for the Berkeley DB environment descriptor");
+ }
+#endif /* APR_HAS_THREADS */
+
+ db_err = db_env_create(&(bdb->env), 0);
+ if (!db_err)
+ {
+ /* See the documentation at bdb_env_t's definition why the
+ (char *) cast is safe and why it is done. */
+ bdb->env->set_errpfx(bdb->env, (char *) bdb);
+
+ /* bdb_error_gatherer is in parens to stop macro expansion. */
+ bdb->env->set_errcall(bdb->env, (bdb_error_gatherer));
+
+ /* Needed on Windows in case Subversion and Berkeley DB are using
+ different C runtime libraries */
+ db_err = bdb->env->set_alloc(bdb->env, malloc, realloc, free);
+
+ /* If we detect a deadlock, select a transaction to abort at
+ random from those participating in the deadlock. */
+ if (!db_err)
+ db_err = bdb->env->set_lk_detect(bdb->env, DB_LOCK_RANDOM);
+ }
+ return convert_bdb_error(bdb, db_err);
+}
+
+
+
+/* The environment descriptor cache. */
+
+/* The global pool used for this cache. */
+static apr_pool_t *bdb_cache_pool = NULL;
+
+/* The cache. The items are bdb_env_t structures. */
+static apr_hash_t *bdb_cache = NULL;
+
+/* The mutex that protects bdb_cache. */
+static svn_mutex__t *bdb_cache_lock = NULL;
+
+/* Cleanup callback to NULL out the cache, so we don't try to use it after
+ the pool has been cleared during global shutdown. */
+static apr_status_t
+clear_cache(void *data)
+{
+ bdb_cache = NULL;
+ bdb_cache_lock = NULL;
+ return APR_SUCCESS;
+}
+
+static volatile svn_atomic_t bdb_cache_state = 0;
+
+static svn_error_t *
+bdb_init_cb(void *baton, apr_pool_t *pool)
+{
+ bdb_cache_pool = svn_pool_create(pool);
+ bdb_cache = apr_hash_make(bdb_cache_pool);
+
+ SVN_ERR(svn_mutex__init(&bdb_cache_lock, TRUE, bdb_cache_pool));
+ apr_pool_cleanup_register(bdb_cache_pool, NULL, clear_cache,
+ apr_pool_cleanup_null);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_bdb__init(apr_pool_t* pool)
+{
+ return svn_atomic__init_once(&bdb_cache_state, bdb_init_cb, NULL, pool);
+}
+
+/* Construct a cache key for the BDB environment at PATH in *KEYP.
+ if DBCONFIG_FILE is not NULL, return the opened file handle.
+ Allocate from POOL. */
+static svn_error_t *
+bdb_cache_key(bdb_env_key_t *keyp, apr_file_t **dbconfig_file,
+ const char *path, apr_pool_t *pool)
+{
+ const char *dbcfg_file_name = svn_dirent_join(path, BDB_CONFIG_FILE, pool);
+ apr_file_t *dbcfg_file;
+ apr_status_t apr_err;
+ apr_finfo_t finfo;
+
+ SVN_ERR(svn_io_file_open(&dbcfg_file, dbcfg_file_name,
+ APR_READ, APR_OS_DEFAULT, pool));
+
+ apr_err = apr_file_info_get(&finfo, APR_FINFO_DEV | APR_FINFO_INODE,
+ dbcfg_file);
+ if (apr_err)
+ return svn_error_wrap_apr
+ (apr_err, "Can't create BDB environment cache key");
+
+ /* Make sure that any padding in the key is always cleared, so that
+ the key's hash deterministic. */
+ memset(keyp, 0, sizeof *keyp);
+ keyp->device = finfo.device;
+ keyp->inode = finfo.inode;
+
+ if (dbconfig_file)
+ *dbconfig_file = dbcfg_file;
+ else
+ apr_file_close(dbcfg_file);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Find a BDB environment in the cache.
+ Return the environment's panic state in *PANICP.
+
+ Note: You MUST acquire the cache mutex before calling this function.
+*/
+static bdb_env_t *
+bdb_cache_get(const bdb_env_key_t *keyp, svn_boolean_t *panicp)
+{
+ bdb_env_t *bdb = apr_hash_get(bdb_cache, keyp, sizeof *keyp);
+ if (bdb && bdb->env)
+ {
+ *panicp = !!svn_atomic_read(&bdb->panic);
+#if SVN_BDB_VERSION_AT_LEAST(4,2)
+ if (!*panicp)
+ {
+ u_int32_t flags;
+ if (bdb->env->get_flags(bdb->env, &flags)
+ || (flags & DB_PANIC_ENVIRONMENT))
+ {
+ /* Something is wrong with the environment. */
+ svn_atomic_set(&bdb->panic, TRUE);
+ *panicp = TRUE;
+ bdb = NULL;
+ }
+ }
+#endif /* at least bdb-4.2 */
+ }
+ else
+ {
+ *panicp = FALSE;
+ }
+ return bdb;
+}
+
+
+
+/* Close and destroy a BDB environment descriptor. */
+static svn_error_t *
+bdb_close(bdb_env_t *bdb)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ /* This bit is delcate; we must propagate the error from
+ DB_ENV->close to the caller, and always destroy the pool. */
+ int db_err = bdb->env->close(bdb->env, 0);
+
+ /* If automatic database recovery is enabled, ignore DB_RUNRECOVERY
+ errors, since they're dealt with eventually by BDB itself. */
+ if (db_err && (!SVN_BDB_AUTO_RECOVER || db_err != DB_RUNRECOVERY))
+ err = convert_bdb_error(bdb, db_err);
+
+ /* Free the environment descriptor. The pool cleanup will do this unless
+ the cache has already been destroyed. */
+ if (bdb->pool)
+ svn_pool_destroy(bdb->pool);
+ else
+ free(bdb);
+ return svn_error_trace(err);
+}
+
+
+static svn_error_t *
+svn_fs_bdb__close_internal(bdb_env_t *bdb)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+
+ if (--bdb->refcount != 0)
+ {
+ /* If the environment is panicked and automatic recovery is not
+ enabled, return an appropriate error. */
+#if !SVN_BDB_AUTO_RECOVER
+ if (svn_atomic_read(&bdb->panic))
+ err = svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL,
+ db_strerror(DB_RUNRECOVERY));
+#endif
+ }
+ else
+ {
+ /* If the bdb cache has been set to NULL that means we are
+ shutting down, and the pool that holds the bdb cache has
+ already been destroyed, so accessing it here would be a Bad
+ Thing (tm) */
+ if (bdb_cache)
+ apr_hash_set(bdb_cache, &bdb->key, sizeof bdb->key, NULL);
+ err = bdb_close(bdb);
+ }
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_fs_bdb__close(bdb_env_baton_t *bdb_baton)
+{
+ bdb_env_t *bdb = bdb_baton->bdb;
+
+ SVN_ERR_ASSERT(bdb_baton->env == bdb_baton->bdb->env);
+ SVN_ERR_ASSERT(bdb_baton->error_info->refcount > 0);
+
+ /* Neutralize bdb_baton's pool cleanup to prevent double-close. See
+ cleanup_env_baton(). */
+ bdb_baton->bdb = NULL;
+
+ /* Note that we only bother with this cleanup if the pool is non-NULL, to
+ guard against potential races between this and the cleanup_env cleanup
+ callback. It's not clear if that can actually happen, but better safe
+ than sorry. */
+ if (0 == --bdb_baton->error_info->refcount && bdb->pool)
+ {
+ svn_error_clear(bdb_baton->error_info->pending_errors);
+#if APR_HAS_THREADS
+ free(bdb_baton->error_info);
+ apr_threadkey_private_set(NULL, bdb->error_info);
+#endif
+ }
+
+ /* This may run during final pool cleanup when the lock is NULL. */
+ SVN_MUTEX__WITH_LOCK(bdb_cache_lock, svn_fs_bdb__close_internal(bdb));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Open and initialize a BDB environment. */
+static svn_error_t *
+bdb_open(bdb_env_t *bdb, u_int32_t flags, int mode)
+{
+#if APR_HAS_THREADS
+ flags |= DB_THREAD;
+#endif
+ SVN_ERR(convert_bdb_error
+ (bdb, (bdb->env->open)(bdb->env, bdb->path_bdb, flags, mode)));
+
+#if SVN_BDB_AUTO_COMMIT
+ /* Assert the BDB_AUTO_COMMIT flag on the opened environment. This
+ will force all operations on the environment (and handles that
+ are opened within the environment) to be transactional. */
+
+ SVN_ERR(convert_bdb_error
+ (bdb, bdb->env->set_flags(bdb->env, SVN_BDB_AUTO_COMMIT, 1)));
+#endif
+
+ return bdb_cache_key(&bdb->key, &bdb->dbconfig_file,
+ bdb->path, bdb->pool);
+}
+
+
+/* Pool cleanup for the environment baton. */
+static apr_status_t
+cleanup_env_baton(void *data)
+{
+ bdb_env_baton_t *bdb_baton = data;
+
+ if (bdb_baton->bdb)
+ svn_error_clear(svn_fs_bdb__close(bdb_baton));
+
+ return APR_SUCCESS;
+}
+
+
+static svn_error_t *
+svn_fs_bdb__open_internal(bdb_env_baton_t **bdb_batonp,
+ const char *path,
+ u_int32_t flags, int mode,
+ apr_pool_t *pool)
+{
+ bdb_env_key_t key;
+ bdb_env_t *bdb;
+ svn_boolean_t panic;
+
+ /* We can safely discard the open DB_CONFIG file handle. If the
+ environment descriptor is in the cache, the key's immutability is
+ guaranteed. If it's not, we don't care if the key changes,
+ between here and the actual insertion of the newly-created
+ environment into the cache, because no other thread can touch the
+ cache in the meantime. */
+ SVN_ERR(bdb_cache_key(&key, NULL, path, pool));
+
+ bdb = bdb_cache_get(&key, &panic);
+ if (panic)
+ return svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL,
+ db_strerror(DB_RUNRECOVERY));
+
+ /* Make sure that the environment's open flags haven't changed. */
+ if (bdb && bdb->flags != flags)
+ {
+ /* Handle changes to the DB_PRIVATE flag specially */
+ if ((flags ^ bdb->flags) & DB_PRIVATE)
+ {
+ if (flags & DB_PRIVATE)
+ return svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL,
+ "Reopening a public Berkeley DB"
+ " environment with private attributes");
+ else
+ return svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL,
+ "Reopening a private Berkeley DB"
+ " environment with public attributes");
+ }
+
+ /* Otherwise return a generic "flags-mismatch" error. */
+ return svn_error_create(SVN_ERR_FS_BERKELEY_DB, NULL,
+ "Reopening a Berkeley DB environment"
+ " with different attributes");
+ }
+
+ if (!bdb)
+ {
+ svn_error_t *err;
+
+ SVN_ERR(create_env(&bdb, path, svn_pool_create(bdb_cache_pool)));
+ err = bdb_open(bdb, flags, mode);
+ if (err)
+ {
+ /* Clean up, and we can't do anything about returned errors. */
+ svn_error_clear(bdb_close(bdb));
+ return svn_error_trace(err);
+ }
+
+ apr_hash_set(bdb_cache, &bdb->key, sizeof bdb->key, bdb);
+ bdb->flags = flags;
+ bdb->refcount = 1;
+ }
+ else
+ {
+ ++bdb->refcount;
+ }
+
+ *bdb_batonp = apr_palloc(pool, sizeof **bdb_batonp);
+ (*bdb_batonp)->env = bdb->env;
+ (*bdb_batonp)->bdb = bdb;
+ (*bdb_batonp)->error_info = get_error_info(bdb);
+ ++(*bdb_batonp)->error_info->refcount;
+ apr_pool_cleanup_register(pool, *bdb_batonp, cleanup_env_baton,
+ apr_pool_cleanup_null);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *
+svn_fs_bdb__open(bdb_env_baton_t **bdb_batonp, const char *path,
+ u_int32_t flags, int mode,
+ apr_pool_t *pool)
+{
+ SVN_MUTEX__WITH_LOCK(bdb_cache_lock,
+ svn_fs_bdb__open_internal(bdb_batonp,
+ path,
+ flags,
+ mode,
+ pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_boolean_t
+svn_fs_bdb__get_panic(bdb_env_baton_t *bdb_baton)
+{
+ /* An invalid baton is equivalent to a panicked environment; in both
+ cases, database cleanups should be skipped. */
+ if (!bdb_baton->bdb)
+ return TRUE;
+
+ assert(bdb_baton->env == bdb_baton->bdb->env);
+ return !!svn_atomic_read(&bdb_baton->bdb->panic);
+}
+
+void
+svn_fs_bdb__set_panic(bdb_env_baton_t *bdb_baton)
+{
+ if (!bdb_baton->bdb)
+ return;
+
+ assert(bdb_baton->env == bdb_baton->bdb->env);
+ svn_atomic_set(&bdb_baton->bdb->panic, TRUE);
+}
+
+
+/* This function doesn't actually open the environment, so it doesn't
+ have to look in the cache. Callers are supposed to own an
+ exclusive lock on the filesystem anyway. */
+svn_error_t *
+svn_fs_bdb__remove(const char *path, apr_pool_t *pool)
+{
+ bdb_env_t *bdb;
+
+ SVN_ERR(create_env(&bdb, path, pool));
+ return convert_bdb_error
+ (bdb, bdb->env->remove(bdb->env, bdb->path_bdb, DB_FORCE));
+}
diff --git a/subversion/libsvn_fs_base/bdb/env.h b/subversion/libsvn_fs_base/bdb/env.h
new file mode 100644
index 0000000..a8cce4e
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/env.h
@@ -0,0 +1,159 @@
+/* env.h : managing the BDB environment
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_BDB_ENV_H
+#define SVN_LIBSVN_FS_BDB_ENV_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include <apr_pools.h>
+#include <apr_file_io.h>
+
+#include "bdb_compat.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* The name of the Berkeley DB config file. */
+#define BDB_CONFIG_FILE "DB_CONFIG"
+
+/* Prefix string for BDB errors. */
+#define BDB_ERRPFX_STRING "svn (bdb): "
+
+
+/* Opaque descriptor of an open BDB environment. */
+typedef struct bdb_env_t bdb_env_t;
+
+
+/* Thread-specific error info related to the bdb_env_t. */
+typedef struct bdb_error_info_t
+{
+ /* We hold the extended info here until the Berkeley DB function returns.
+ It usually returns an error code, triggering the collection and
+ wrapping of the additional errors stored here.
+
+ Note: In some circumstances BDB will call the error function and not
+ go on to return an error code, so the caller must always check whether
+ pending_errors is non-NULL to avoid leaking errors. This behaviour
+ has been seen when running recovery on a repository upgraded to 4.3
+ that still has old 4.2 log files present, a typical error string is
+ "Skipping log file db/log.0000000002: historic log version 8" */
+ svn_error_t *pending_errors;
+
+ /* We permitted clients of our library to install a Berkeley BDB errcall.
+ Since we now use the errcall ourselves, we must store and invoke a user
+ errcall, to maintain our API guarantees. */
+ void (*user_callback)(const char *errpfx, char *msg);
+
+ /* The reference count. It counts the number of bdb_env_baton_t
+ instances that refer to this object. */
+ unsigned refcount;
+
+} bdb_error_info_t;
+
+
+/* The Berkeley DB environment baton. */
+typedef struct bdb_env_baton_t
+{
+ /* The Berkeley DB environment. This pointer must be identical to
+ the one in the bdb_env_t. */
+ DB_ENV *env;
+
+ /* The (opaque) cached environment descriptor. */
+ bdb_env_t *bdb;
+
+ /* The error info related to this baton. */
+ bdb_error_info_t *error_info;
+} bdb_env_baton_t;
+
+
+
+/* Flag combination for opening a shared BDB environment. */
+#define SVN_BDB_STANDARD_ENV_FLAGS (DB_CREATE \
+ | DB_INIT_LOCK \
+ | DB_INIT_LOG \
+ | DB_INIT_MPOOL \
+ | DB_INIT_TXN \
+ | SVN_BDB_AUTO_RECOVER)
+
+/* Flag combination for opening a private BDB environment. */
+#define SVN_BDB_PRIVATE_ENV_FLAGS (DB_CREATE \
+ | DB_INIT_LOG \
+ | DB_INIT_MPOOL \
+ | DB_INIT_TXN \
+ | DB_PRIVATE)
+
+
+/* Iniitalize the BDB back-end's private stuff. */
+svn_error_t *svn_fs_bdb__init(apr_pool_t* pool);
+
+
+/* Allocate the Berkeley DB descriptor BDB and open the environment.
+ *
+ * Allocate *BDBP from POOL and open (*BDBP)->env in PATH, using FLAGS
+ * and MODE. If applicable, set the BDB_AUTO_COMMIT flag for this
+ * environment.
+ *
+ * Use POOL for temporary allocation.
+ *
+ * Note: This function may return a bdb_env_baton_t object that refers
+ * to a previously opened environment. If FLAGS contains
+ * DB_PRIVATE and the environment is already open, the function
+ * will fail (this isn't a problem in practice, because a caller
+ * should obtain an exclusive lock on the repository before
+ * opening the environment).
+ */
+
+svn_error_t *svn_fs_bdb__open(bdb_env_baton_t **bdb_batonp,
+ const char *path,
+ u_int32_t flags, int mode,
+ apr_pool_t *pool);
+
+/* Close the Berkeley DB descriptor BDB.
+ *
+ * Note: This function might not actually close the environment if it
+ * has been opened more than once.
+ */
+svn_error_t *svn_fs_bdb__close(bdb_env_baton_t *bdb_baton);
+
+
+/* Get the panic state of the open BDB environment. */
+svn_boolean_t svn_fs_bdb__get_panic(bdb_env_baton_t *bdb_baton);
+
+/* Set the panic flag on the open BDB environment. */
+void svn_fs_bdb__set_panic(bdb_env_baton_t *bdb_baton);
+
+
+/* Remove the Berkeley DB environment at PATH.
+ *
+ * Use POOL for temporary allocation.
+ */
+svn_error_t *svn_fs_bdb__remove(const char *path, apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_BDB_ENV_H */
diff --git a/subversion/libsvn_fs_base/bdb/lock-tokens-table.c b/subversion/libsvn_fs_base/bdb/lock-tokens-table.c
new file mode 100644
index 0000000..e70ef17
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/lock-tokens-table.c
@@ -0,0 +1,157 @@
+/* lock-tokens-table.c : operations on the `lock-tokens' table
+ *
+ * ====================================================================
+ * 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 <assert.h>
+
+#include "bdb_compat.h"
+
+#include "svn_pools.h"
+#include "private/svn_skel.h"
+
+#include "dbt.h"
+#include "../err.h"
+#include "../fs.h"
+#include "../util/fs_skels.h"
+#include "../trail.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "lock-tokens-table.h"
+#include "locks-table.h"
+
+#include "private/svn_fs_util.h"
+
+
+int
+svn_fs_bdb__open_lock_tokens_table(DB **lock_tokens_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *lock_tokens;
+ int error;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&lock_tokens, env, 0));
+ error = (lock_tokens->open)(SVN_BDB_OPEN_PARAMS(lock_tokens, NULL),
+ "lock-tokens", 0, DB_BTREE,
+ open_flags, 0666);
+
+ /* Create the table if it doesn't yet exist. This is a form of
+ automagical repository upgrading. */
+ if (error == ENOENT && (! create))
+ {
+ BDB_ERR(lock_tokens->close(lock_tokens, 0));
+ return svn_fs_bdb__open_lock_tokens_table(lock_tokens_p, env, TRUE);
+ }
+ BDB_ERR(error);
+
+ *lock_tokens_p = lock_tokens;
+ return 0;
+}
+
+
+svn_error_t *
+svn_fs_bdb__lock_token_add(svn_fs_t *fs,
+ const char *path,
+ const char *lock_token,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+
+ svn_fs_base__str_to_dbt(&key, path);
+ svn_fs_base__str_to_dbt(&value, lock_token);
+ svn_fs_base__trail_debug(trail, "lock-tokens", "add");
+ return BDB_WRAP(fs, N_("storing lock token record"),
+ bfd->lock_tokens->put(bfd->lock_tokens, trail->db_txn,
+ &key, &value, 0));
+}
+
+
+svn_error_t *
+svn_fs_bdb__lock_token_delete(svn_fs_t *fs,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key;
+ int db_err;
+
+ svn_fs_base__str_to_dbt(&key, path);
+ svn_fs_base__trail_debug(trail, "lock-tokens", "del");
+ db_err = bfd->lock_tokens->del(bfd->lock_tokens, trail->db_txn, &key, 0);
+ if (db_err == DB_NOTFOUND)
+ return SVN_FS__ERR_NO_SUCH_LOCK(fs, path);
+ return BDB_WRAP(fs, N_("deleting entry from 'lock-tokens' table"), db_err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__lock_token_get(const char **lock_token_p,
+ svn_fs_t *fs,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ svn_error_t *err;
+ svn_lock_t *lock;
+ const char *lock_token;
+ int db_err;
+
+ svn_fs_base__trail_debug(trail, "lock-tokens", "get");
+ db_err = bfd->lock_tokens->get(bfd->lock_tokens, trail->db_txn,
+ svn_fs_base__str_to_dbt(&key, path),
+ svn_fs_base__result_dbt(&value),
+ 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ if (db_err == DB_NOTFOUND)
+ return SVN_FS__ERR_NO_SUCH_LOCK(fs, path);
+ SVN_ERR(BDB_WRAP(fs, N_("reading lock token"), db_err));
+
+ lock_token = apr_pstrmemdup(pool, value.data, value.size);
+
+ /* Make sure the token still points to an existing, non-expired
+ lock, by doing a lookup in the `locks' table. */
+ err = svn_fs_bdb__lock_get(&lock, fs, lock_token, trail, pool);
+ if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
+ {
+ /* If `locks' doesn't have the lock, then we should lose it too. */
+ svn_error_t *delete_err;
+ delete_err = svn_fs_bdb__lock_token_delete(fs, path, trail, pool);
+ if (delete_err)
+ svn_error_compose(err, delete_err);
+ return svn_error_trace(err);
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ *lock_token_p = lock_token;
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_base/bdb/lock-tokens-table.h b/subversion/libsvn_fs_base/bdb/lock-tokens-table.h
new file mode 100644
index 0000000..de958ce
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/lock-tokens-table.h
@@ -0,0 +1,96 @@
+/* lock-tokens-table.h : internal interface to ops on `lock-tokens' table
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_LOCK_TOKENS_TABLE_H
+#define SVN_LIBSVN_FS_LOCK_TOKENS_TABLE_H
+
+#include "svn_fs.h"
+#include "svn_error.h"
+#include "../trail.h"
+#include "../fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `lock-tokens' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *LOCK_TOKENS_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_lock_tokens_table(DB **locks_tokens_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+/* Add a lock-token to the `lock-tokens' table in FS, as part of TRAIL.
+ Use PATH as the key and LOCK_TOKEN as the value.
+
+ Warning: if PATH already exists as a key, then its value will be
+ overwritten. */
+svn_error_t *
+svn_fs_bdb__lock_token_add(svn_fs_t *fs,
+ const char *path,
+ const char *lock_token,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Remove the lock-token whose key is PATH from the `lock-tokens'
+ table of FS, as part of TRAIL.
+
+ If PATH doesn't exist as a key, return SVN_ERR_FS_NO_SUCH_LOCK.
+*/
+svn_error_t *
+svn_fs_bdb__lock_token_delete(svn_fs_t *fs,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Retrieve the lock-token *LOCK_TOKEN_P pointed to by PATH from the
+ `lock-tokens' table of FS, as part of TRAIL. Perform all
+ allocations in POOL.
+
+ If PATH doesn't exist as a key, return SVN_ERR_FS_NO_SUCH_LOCK.
+
+ If PATH points to a token which points to an expired lock, return
+ SVN_ERR_FS_LOCK_EXPIRED. (After this, both the token and lock are
+ gone from their respective tables.)
+
+ If PATH points to a token which points to a non-existent lock,
+ return SVN_ERR_FS_BAD_LOCK_TOKEN. (After this, the token is also
+ removed from the `lock-tokens' table.)
+ */
+svn_error_t *
+svn_fs_bdb__lock_token_get(const char **lock_token_p,
+ svn_fs_t *fs,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_LOCK_TOKENS_TABLE_H */
diff --git a/subversion/libsvn_fs_base/bdb/locks-table.c b/subversion/libsvn_fs_base/bdb/locks-table.c
new file mode 100644
index 0000000..a22663f
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/locks-table.c
@@ -0,0 +1,328 @@
+/* locks-table.c : operations on the `locks' table
+ *
+ * ====================================================================
+ * 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 <assert.h>
+
+#include "bdb_compat.h"
+
+#include "svn_pools.h"
+#include "svn_path.h"
+#include "private/svn_skel.h"
+
+#include "dbt.h"
+#include "../err.h"
+#include "../fs.h"
+#include "../util/fs_skels.h"
+#include "../trail.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "locks-table.h"
+#include "lock-tokens-table.h"
+
+#include "private/svn_fs_util.h"
+#include "private/svn_fspath.h"
+
+
+int
+svn_fs_bdb__open_locks_table(DB **locks_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *locks;
+ int error;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&locks, env, 0));
+ error = (locks->open)(SVN_BDB_OPEN_PARAMS(locks, NULL),
+ "locks", 0, DB_BTREE,
+ open_flags, 0666);
+
+ /* Create the table if it doesn't yet exist. This is a form of
+ automagical repository upgrading. */
+ if (error == ENOENT && (! create))
+ {
+ BDB_ERR(locks->close(locks, 0));
+ return svn_fs_bdb__open_locks_table(locks_p, env, TRUE);
+ }
+ BDB_ERR(error);
+
+ *locks_p = locks;
+ return 0;
+}
+
+
+
+svn_error_t *
+svn_fs_bdb__lock_add(svn_fs_t *fs,
+ const char *lock_token,
+ svn_lock_t *lock,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ svn_skel_t *lock_skel;
+ DBT key, value;
+
+ /* Convert native type to skel. */
+ SVN_ERR(svn_fs_base__unparse_lock_skel(&lock_skel, lock, pool));
+
+ svn_fs_base__str_to_dbt(&key, lock_token);
+ svn_fs_base__skel_to_dbt(&value, lock_skel, pool);
+ svn_fs_base__trail_debug(trail, "lock", "add");
+ return BDB_WRAP(fs, N_("storing lock record"),
+ bfd->locks->put(bfd->locks, trail->db_txn,
+ &key, &value, 0));
+}
+
+
+
+svn_error_t *
+svn_fs_bdb__lock_delete(svn_fs_t *fs,
+ const char *lock_token,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key;
+ int db_err;
+
+ svn_fs_base__str_to_dbt(&key, lock_token);
+ svn_fs_base__trail_debug(trail, "locks", "del");
+ db_err = bfd->locks->del(bfd->locks, trail->db_txn, &key, 0);
+
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_bad_lock_token(fs, lock_token);
+ return BDB_WRAP(fs, N_("deleting lock from 'locks' table"), db_err);
+}
+
+
+
+svn_error_t *
+svn_fs_bdb__lock_get(svn_lock_t **lock_p,
+ svn_fs_t *fs,
+ const char *lock_token,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+ svn_skel_t *skel;
+ svn_lock_t *lock;
+
+ svn_fs_base__trail_debug(trail, "lock", "get");
+ db_err = bfd->locks->get(bfd->locks, trail->db_txn,
+ svn_fs_base__str_to_dbt(&key, lock_token),
+ svn_fs_base__result_dbt(&value),
+ 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_bad_lock_token(fs, lock_token);
+ SVN_ERR(BDB_WRAP(fs, N_("reading lock"), db_err));
+
+ /* Parse TRANSACTION skel */
+ skel = svn_skel__parse(value.data, value.size, pool);
+ if (! skel)
+ return svn_fs_base__err_corrupt_lock(fs, lock_token);
+
+ /* Convert skel to native type. */
+ SVN_ERR(svn_fs_base__parse_lock_skel(&lock, skel, pool));
+
+ /* Possibly auto-expire the lock. */
+ if (lock->expiration_date && (apr_time_now() > lock->expiration_date))
+ {
+ SVN_ERR(svn_fs_bdb__lock_delete(fs, lock_token, trail, pool));
+ return SVN_FS__ERR_LOCK_EXPIRED(fs, lock_token);
+ }
+
+ *lock_p = lock;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+get_lock(svn_lock_t **lock_p,
+ svn_fs_t *fs,
+ const char *path,
+ const char *lock_token,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ *lock_p = NULL;
+
+ /* Make sure the token points to an existing, non-expired lock, by
+ doing a lookup in the `locks' table. Use 'pool'. */
+ err = svn_fs_bdb__lock_get(lock_p, fs, lock_token, trail, pool);
+ if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
+ {
+ svn_error_clear(err);
+
+ /* If `locks' doesn't have the lock, then we should lose it
+ from `lock-tokens' table as well, then skip to the next
+ matching path-key. */
+ err = svn_fs_bdb__lock_token_delete(fs, path, trail, pool);
+ }
+ return svn_error_trace(err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__locks_get(svn_fs_t *fs,
+ const char *path,
+ svn_depth_t depth,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBC *cursor;
+ DBT key, value;
+ int db_err, db_c_err;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ const char *lock_token;
+ svn_lock_t *lock;
+ svn_error_t *err;
+ const char *lookup_path = path;
+ apr_size_t lookup_len;
+
+ /* First, try to lookup PATH itself. */
+ err = svn_fs_bdb__lock_token_get(&lock_token, fs, path, trail, pool);
+ if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)
+ || (err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)))
+ {
+ svn_error_clear(err);
+ }
+ else if (err)
+ {
+ return svn_error_trace(err);
+ }
+ else
+ {
+ SVN_ERR(get_lock(&lock, fs, path, lock_token, trail, pool));
+ if (lock && get_locks_func)
+ {
+ SVN_ERR(get_locks_func(get_locks_baton, lock, pool));
+
+ /* Found a lock so PATH is a file and we can ignore depth */
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* If we're only looking at PATH itself (depth = empty), stop here. */
+ if (depth == svn_depth_empty)
+ return SVN_NO_ERROR;
+
+ /* Now go hunt for possible children of PATH. */
+
+ svn_fs_base__trail_debug(trail, "lock-tokens", "cursor");
+ db_err = bfd->lock_tokens->cursor(bfd->lock_tokens, trail->db_txn,
+ &cursor, 0);
+ SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading lock tokens"),
+ db_err));
+
+ /* Since the key is going to be returned as well as the value make
+ sure BDB malloc's the returned key. */
+ svn_fs_base__str_to_dbt(&key, lookup_path);
+ key.flags |= DB_DBT_MALLOC;
+
+ /* Get the first matching key that is either equal or greater than
+ the one passed in, by passing in the DB_RANGE_SET flag. */
+ db_err = svn_bdb_dbc_get(cursor, &key, svn_fs_base__result_dbt(&value),
+ DB_SET_RANGE);
+
+ if (!svn_fspath__is_root(path, strlen(path)))
+ lookup_path = apr_pstrcat(pool, path, "/", (char *)NULL);
+ lookup_len = strlen(lookup_path);
+
+ /* As long as the prefix of the returned KEY matches LOOKUP_PATH we
+ know it is either LOOKUP_PATH or a decendant thereof. */
+ while ((! db_err)
+ && lookup_len < key.size
+ && strncmp(lookup_path, key.data, lookup_len) == 0)
+ {
+ const char *child_path;
+
+ svn_pool_clear(subpool);
+
+ svn_fs_base__track_dbt(&key, subpool);
+ svn_fs_base__track_dbt(&value, subpool);
+
+ /* Create a usable path and token in temporary memory. */
+ child_path = apr_pstrmemdup(subpool, key.data, key.size);
+ lock_token = apr_pstrmemdup(subpool, value.data, value.size);
+
+ if ((depth == svn_depth_files) || (depth == svn_depth_immediates))
+ {
+ /* On the assumption that we only store locks for files,
+ depth=files and depth=immediates should boil down to the
+ same set of results. So just see if CHILD_PATH is an
+ immediate child of PATH. If not, we don't care about
+ this item. */
+ const char *rel_path = svn_fspath__skip_ancestor(path, child_path);
+ if (!rel_path || (svn_path_component_count(rel_path) != 1))
+ goto loop_it;
+ }
+
+ /* Get the lock for CHILD_PATH. */
+ err = get_lock(&lock, fs, child_path, lock_token, trail, subpool);
+ if (err)
+ {
+ svn_bdb_dbc_close(cursor);
+ return svn_error_trace(err);
+ }
+
+ /* Lock is verified, hand it off to our callback. */
+ if (lock && get_locks_func)
+ {
+ err = get_locks_func(get_locks_baton, lock, subpool);
+ if (err)
+ {
+ svn_bdb_dbc_close(cursor);
+ return svn_error_trace(err);
+ }
+ }
+
+ loop_it:
+ svn_fs_base__result_dbt(&key);
+ svn_fs_base__result_dbt(&value);
+ db_err = svn_bdb_dbc_get(cursor, &key, &value, DB_NEXT);
+ }
+
+ svn_pool_destroy(subpool);
+ db_c_err = svn_bdb_dbc_close(cursor);
+
+ if (db_err && (db_err != DB_NOTFOUND))
+ SVN_ERR(BDB_WRAP(fs, N_("fetching lock tokens"), db_err));
+ if (db_c_err)
+ SVN_ERR(BDB_WRAP(fs, N_("fetching lock tokens (closing cursor)"),
+ db_c_err));
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/subversion/libsvn_fs_base/bdb/locks-table.h b/subversion/libsvn_fs_base/bdb/locks-table.h
new file mode 100644
index 0000000..e0d087c
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/locks-table.h
@@ -0,0 +1,110 @@
+/* locks-table.h : internal interface to ops on `locks' table
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_LOCKS_TABLE_H
+#define SVN_LIBSVN_FS_LOCKS_TABLE_H
+
+#include "svn_fs.h"
+#include "svn_error.h"
+#include "../trail.h"
+#include "../fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `locks' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *LOCKS_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_locks_table(DB **locks_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+/* Add a lock to the `locks' table in FS, as part of TRAIL.
+
+ Use LOCK_TOKEN as the key, presumably a string form of an apr_uuid_t.
+ Convert LOCK into a skel and store it as the value.
+
+ Warning: if LOCK_TOKEN already exists as a key, then its value
+ will be overwritten. */
+svn_error_t *svn_fs_bdb__lock_add(svn_fs_t *fs,
+ const char *lock_token,
+ svn_lock_t *lock,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Remove the lock whose key is LOCK_TOKEN from the `locks' table of
+ FS, as part of TRAIL.
+
+ Return SVN_ERR_FS_BAD_LOCK_TOKEN if LOCK_TOKEN does not exist as a
+ table key. */
+svn_error_t *svn_fs_bdb__lock_delete(svn_fs_t *fs,
+ const char *lock_token,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Retrieve the lock *LOCK_P pointed to by LOCK_TOKEN from the `locks'
+ table of FS, as part of TRAIL. Perform all allocations in POOL.
+
+ Return SVN_ERR_FS_BAD_LOCK_TOKEN if LOCK_TOKEN does not exist as a
+ table key.
+
+ Before returning LOCK_P, check its expiration date. If expired,
+ remove the row from the `locks' table and return SVN_ERR_FS_LOCK_EXPIRED.
+ */
+svn_error_t *svn_fs_bdb__lock_get(svn_lock_t **lock_p,
+ svn_fs_t *fs,
+ const char *lock_token,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Retrieve locks representing all locks that exist at or below PATH
+ in FS. Pass each lock to GET_LOCKS_FUNC callback along with
+ GET_LOCKS_BATON.
+
+ Use DEPTH to filter the reported locks to only those within the
+ requested depth of PATH.
+
+ This function promises to auto-expire any locks encountered while
+ building the hash. That means that the caller can trust that each
+ returned lock hasn't yet expired.
+*/
+svn_error_t *svn_fs_bdb__locks_get(svn_fs_t *fs,
+ const char *path,
+ svn_depth_t depth,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_LOCKS_TABLE_H */
diff --git a/subversion/libsvn_fs_base/bdb/miscellaneous-table.c b/subversion/libsvn_fs_base/bdb/miscellaneous-table.c
new file mode 100644
index 0000000..21a05ca
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/miscellaneous-table.c
@@ -0,0 +1,135 @@
+/* miscellaneous-table.c : operations on the `miscellaneous' table
+ *
+ * ====================================================================
+ * 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 <assert.h>
+#include "bdb_compat.h"
+
+#include "svn_pools.h"
+#include "dbt.h"
+#include "../err.h"
+#include "../fs.h"
+#include "../trail.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "miscellaneous-table.h"
+
+#include "private/svn_fs_util.h"
+
+
+int
+svn_fs_bdb__open_miscellaneous_table(DB **miscellaneous_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *miscellaneous;
+ int error;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&miscellaneous, env, 0));
+ error = (miscellaneous->open)(SVN_BDB_OPEN_PARAMS(miscellaneous, NULL),
+ "miscellaneous", 0, DB_BTREE,
+ open_flags, 0666);
+
+ /* Create the table if it doesn't yet exist. This is a form of
+ automagical repository upgrading. */
+ if (error == ENOENT && (! create))
+ {
+ BDB_ERR(miscellaneous->close(miscellaneous, 0));
+ return svn_fs_bdb__open_miscellaneous_table(miscellaneous_p, env, TRUE);
+ }
+ BDB_ERR(error);
+
+ /* If we're creating the table from scratch (not upgrading), record the
+ upgrade rev as 0. */
+ if (create)
+ {
+ DBT key, value;
+
+ BDB_ERR(miscellaneous->put
+ (miscellaneous, 0,
+ svn_fs_base__str_to_dbt
+ (&key, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE),
+ svn_fs_base__str_to_dbt(&value, "0"), 0));
+ }
+
+ *miscellaneous_p = miscellaneous;
+ return 0;
+}
+
+
+svn_error_t *
+svn_fs_bdb__miscellaneous_set(svn_fs_t *fs,
+ const char *key_str,
+ const char *val,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+
+ svn_fs_base__str_to_dbt(&key, key_str);
+ if (val == NULL)
+ {
+ svn_fs_base__trail_debug(trail, "miscellaneous", "del");
+ return BDB_WRAP(fs, N_("deleting record from 'miscellaneous' table"),
+ bfd->miscellaneous->del(bfd->miscellaneous,
+ trail->db_txn, &key, 0));
+ }
+ else
+ {
+ svn_fs_base__str_to_dbt(&value, val);
+ svn_fs_base__trail_debug(trail, "miscellaneous", "add");
+ return BDB_WRAP(fs, N_("storing miscellaneous record"),
+ bfd->miscellaneous->put(bfd->miscellaneous,
+ trail->db_txn,
+ &key, &value, 0));
+ }
+}
+
+
+svn_error_t *
+svn_fs_bdb__miscellaneous_get(const char **val,
+ svn_fs_t *fs,
+ const char *key_str,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+
+ *val = NULL;
+ svn_fs_base__trail_debug(trail, "miscellaneous", "get");
+ db_err = bfd->miscellaneous->get(bfd->miscellaneous, trail->db_txn,
+ svn_fs_base__str_to_dbt(&key, key_str),
+ svn_fs_base__result_dbt(&value), 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ if (db_err != DB_NOTFOUND)
+ {
+ SVN_ERR(BDB_WRAP(fs, N_("fetching miscellaneous record"), db_err));
+ *val = apr_pstrmemdup(pool, value.data, value.size);
+ }
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_base/bdb/miscellaneous-table.h b/subversion/libsvn_fs_base/bdb/miscellaneous-table.h
new file mode 100644
index 0000000..f972d02
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/miscellaneous-table.h
@@ -0,0 +1,71 @@
+/* miscellaneous-table.h : internal interface to ops on `miscellaneous' table
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_MISCELLANEOUS_TABLE_H
+#define SVN_LIBSVN_FS_MISCELLANEOUS_TABLE_H
+
+#include "svn_fs.h"
+#include "svn_error.h"
+#include "../trail.h"
+#include "../fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `miscellaneous' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *MISCELLANEOUS_P to the new table.
+ Return a Berkeley DB error code. */
+int
+svn_fs_bdb__open_miscellaneous_table(DB **miscellaneous_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+/* Add data to the `miscellaneous' table in FS, as part of TRAIL.
+
+ KEY and VAL should be NULL-terminated strings. If VAL is NULL,
+ the key is removed from the table. */
+svn_error_t *
+svn_fs_bdb__miscellaneous_set(svn_fs_t *fs,
+ const char *key,
+ const char *val,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *VAL to the value of data cooresponding to KEY in the
+ `miscellaneous' table of FS, or to NULL if that key isn't found. */
+svn_error_t *
+svn_fs_bdb__miscellaneous_get(const char **val,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_MISCELLANEOUS_TABLE_H */
diff --git a/subversion/libsvn_fs_base/bdb/node-origins-table.c b/subversion/libsvn_fs_base/bdb/node-origins-table.c
new file mode 100644
index 0000000..48dc43b
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/node-origins-table.c
@@ -0,0 +1,145 @@
+/* node-origins-table.c : operations on the `node-origins' table
+ *
+ * ====================================================================
+ * 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 "bdb_compat.h"
+#include "../fs.h"
+#include "../err.h"
+#include "../id.h"
+#include "dbt.h"
+#include "../trail.h"
+#include "bdb-err.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "node-origins-table.h"
+
+#include "svn_private_config.h"
+
+
+int svn_fs_bdb__open_node_origins_table(DB **node_origins_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *node_origins;
+ int error;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&node_origins, env, 0));
+ error = (node_origins->open)(SVN_BDB_OPEN_PARAMS(node_origins, NULL),
+ "node-origins", 0, DB_BTREE,
+ open_flags, 0666);
+
+ /* Create the node-origins table if it doesn't exist. */
+ if (error == ENOENT && (! create))
+ {
+ BDB_ERR(node_origins->close(node_origins, 0));
+ return svn_fs_bdb__open_node_origins_table(node_origins_p, env, TRUE);
+ }
+
+ BDB_ERR(error);
+
+ *node_origins_p = node_origins;
+ return 0;
+}
+
+svn_error_t *svn_fs_bdb__get_node_origin(const svn_fs_id_t **origin_id,
+ svn_fs_t *fs,
+ const char *node_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+
+ svn_fs_base__trail_debug(trail, "node-origins", "get");
+ db_err = bfd->node_origins->get(bfd->node_origins, trail->db_txn,
+ svn_fs_base__str_to_dbt(&key, node_id),
+ svn_fs_base__result_dbt(&value), 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_no_such_node_origin(fs, node_id);
+
+ *origin_id = svn_fs_base__id_parse(value.data, value.size, pool);
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_fs_bdb__set_node_origin(svn_fs_t *fs,
+ const char *node_id,
+ const svn_fs_id_t *origin_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+
+ /* Create a key from our NODE_ID. */
+ svn_fs_base__str_to_dbt(&key, node_id);
+
+ /* Check to see if we already have a mapping for NODE_ID. If so,
+ and the value is the same one we were about to write. That's
+ cool -- just do nothing. If, however, the value is *different*,
+ that's a red flag! */
+ svn_fs_base__trail_debug(trail, "node-origins", "get");
+ db_err = bfd->node_origins->get(bfd->node_origins, trail->db_txn,
+ &key, svn_fs_base__result_dbt(&value), 0);
+ svn_fs_base__track_dbt(&value, pool);
+ if (db_err != DB_NOTFOUND)
+ {
+ const svn_string_t *origin_id_str =
+ svn_fs_base__id_unparse(origin_id, pool);
+ const svn_string_t *old_origin_id_str =
+ svn_string_ncreate(value.data, value.size, pool);
+
+ if (! svn_string_compare(origin_id_str, old_origin_id_str))
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Node origin for '%s' exists in filesystem '%s' with a different "
+ "value (%s) than what we were about to store (%s)"),
+ node_id, fs->path, old_origin_id_str->data, origin_id_str->data);
+ else
+ return SVN_NO_ERROR;
+ }
+
+ /* Create a value from our ORIGIN_ID, and add this record to the table. */
+ svn_fs_base__id_to_dbt(&value, origin_id, pool);
+ svn_fs_base__trail_debug(trail, "node-origins", "put");
+ return BDB_WRAP(fs, N_("storing node-origins record"),
+ bfd->node_origins->put(bfd->node_origins, trail->db_txn,
+ &key, &value, 0));
+}
+
+svn_error_t *svn_fs_bdb__delete_node_origin(svn_fs_t *fs,
+ const char *node_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key;
+
+ svn_fs_base__str_to_dbt(&key, node_id);
+ svn_fs_base__trail_debug(trail, "node-origins", "del");
+ return BDB_WRAP(fs, N_("deleting entry from 'node-origins' table"),
+ bfd->node_origins->del(bfd->node_origins,
+ trail->db_txn, &key, 0));
+}
diff --git a/subversion/libsvn_fs_base/bdb/node-origins-table.h b/subversion/libsvn_fs_base/bdb/node-origins-table.h
new file mode 100644
index 0000000..44587ca
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/node-origins-table.h
@@ -0,0 +1,76 @@
+/* node-origins-table.h : internal interface to ops on `node-origins' table
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_NODE_ORIGINS_TABLE_H
+#define SVN_LIBSVN_FS_NODE_ORIGINS_TABLE_H
+
+#include "svn_fs.h"
+#include "svn_error.h"
+#include "../trail.h"
+#include "../fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `node-origins' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *NODE_ORIGINS_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_node_origins_table(DB **node_origins_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+/* Set *ORIGIN_ID to the node revision ID from which the history of
+ all nodes in FS whose node ID is NODE_ID springs, as determined by
+ a look in the `node-origins' table. Do this as part of TRAIL. Use
+ POOL for allocations.
+
+ If no such node revision ID is stored for NODE_ID, return
+ SVN_ERR_FS_NO_SUCH_NODE_ORIGIN. */
+svn_error_t *svn_fs_bdb__get_node_origin(const svn_fs_id_t **origin_id,
+ svn_fs_t *fs,
+ const char *node_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Store in the `node-origins' table a mapping of NODE_ID to original
+ node revision ID ORIGIN_ID for FS. Do this as part of TRAIL. Use
+ POOL for temporary allocations. */
+svn_error_t *svn_fs_bdb__set_node_origin(svn_fs_t *fs,
+ const char *node_id,
+ const svn_fs_id_t *origin_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Delete from the `node-origins' table the record for NODE_ID in FS.
+ Do this as part of TRAIL. Use POOL for temporary allocations. */
+svn_error_t *svn_fs_bdb__delete_node_origin(svn_fs_t *fs,
+ const char *node_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_NODE_ORIGINS_TABLE_H */
diff --git a/subversion/libsvn_fs_base/bdb/nodes-table.c b/subversion/libsvn_fs_base/bdb/nodes-table.c
new file mode 100644
index 0000000..d0f475f
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/nodes-table.c
@@ -0,0 +1,259 @@
+/* nodes-table.c : working with the `nodes' table
+ *
+ * ====================================================================
+ * 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 <assert.h>
+
+#include "bdb_compat.h"
+
+#include "svn_fs.h"
+#include "private/svn_skel.h"
+
+#include "../fs.h"
+#include "../err.h"
+#include "dbt.h"
+#include "../util/fs_skels.h"
+#include "../trail.h"
+#include "../key-gen.h"
+#include "../id.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "nodes-table.h"
+
+#include "svn_private_config.h"
+
+
+
+/* Opening/creating the `nodes' table. */
+
+
+int
+svn_fs_bdb__open_nodes_table(DB **nodes_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *nodes;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&nodes, env, 0));
+ BDB_ERR((nodes->open)(SVN_BDB_OPEN_PARAMS(nodes, NULL),
+ "nodes", 0, DB_BTREE,
+ open_flags, 0666));
+
+ /* Create the `next-key' table entry (use '1' because '0' is
+ reserved for the root directory to use). */
+ if (create)
+ {
+ DBT key, value;
+
+ BDB_ERR(nodes->put(nodes, 0,
+ svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&value, "1"), 0));
+ }
+
+ *nodes_p = nodes;
+ return 0;
+}
+
+
+
+/* Choosing node revision ID's. */
+
+svn_error_t *
+svn_fs_bdb__new_node_id(svn_fs_id_t **id_p,
+ svn_fs_t *fs,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, result;
+ apr_size_t len;
+ char next_key[MAX_KEY_SIZE];
+ int db_err;
+ const char *next_node_id;
+
+ SVN_ERR_ASSERT(txn_id);
+
+ /* Get the current value associated with the `next-key' key in the table. */
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY);
+ svn_fs_base__trail_debug(trail, "nodes", "get");
+ SVN_ERR(BDB_WRAP(fs, N_("allocating new node ID (getting 'next-key')"),
+ bfd->nodes->get(bfd->nodes, trail->db_txn,
+ &query,
+ svn_fs_base__result_dbt(&result),
+ 0)));
+ svn_fs_base__track_dbt(&result, pool);
+
+ /* Squirrel away our next node id value. */
+ next_node_id = apr_pstrmemdup(pool, result.data, result.size);
+
+ /* Bump to future key. */
+ len = result.size;
+ svn_fs_base__next_key(result.data, &len, next_key);
+ svn_fs_base__trail_debug(trail, "nodes", "put");
+ db_err = bfd->nodes->put(bfd->nodes, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&result, next_key),
+ 0);
+ SVN_ERR(BDB_WRAP(fs, N_("bumping next node ID key"), db_err));
+
+ /* Create and return the new node id. */
+ *id_p = svn_fs_base__id_create(next_node_id, copy_id, txn_id, pool);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_bdb__new_successor_id(svn_fs_id_t **successor_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *new_id;
+ svn_error_t *err;
+
+ SVN_ERR_ASSERT(txn_id);
+
+ /* Create and return the new successor ID. */
+ new_id = svn_fs_base__id_create(svn_fs_base__id_node_id(id),
+ copy_id ? copy_id
+ : svn_fs_base__id_copy_id(id),
+ txn_id, pool);
+
+ /* Now, make sure this NEW_ID doesn't already exist in FS. */
+ err = svn_fs_bdb__get_node_revision(NULL, fs, new_id, trail, trail->pool);
+ if ((! err) || (err->apr_err != SVN_ERR_FS_ID_NOT_FOUND))
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(id, pool);
+ svn_string_t *new_id_str = svn_fs_base__id_unparse(new_id, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_ALREADY_EXISTS, err,
+ _("Successor id '%s' (for '%s') already exists in filesystem '%s'"),
+ new_id_str->data, id_str->data, fs->path);
+ }
+
+ /* err is SVN_ERR_FS_ID_NOT_FOUND, meaning the ID is available. But
+ we don't want this error. */
+ svn_error_clear(err);
+
+ /* Return the new node revision ID. */
+ *successor_p = new_id;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Removing node revisions. */
+svn_error_t *
+svn_fs_bdb__delete_nodes_entry(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key;
+
+ svn_fs_base__trail_debug(trail, "nodes", "del");
+ return BDB_WRAP(fs, N_("deleting entry from 'nodes' table"),
+ bfd->nodes->del(bfd->nodes,
+ trail->db_txn,
+ svn_fs_base__id_to_dbt(&key, id, pool),
+ 0));
+}
+
+
+
+
+/* Storing and retrieving NODE-REVISIONs. */
+
+
+svn_error_t *
+svn_fs_bdb__get_node_revision(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ node_revision_t *noderev;
+ svn_skel_t *skel;
+ int db_err;
+ DBT key, value;
+
+ svn_fs_base__trail_debug(trail, "nodes", "get");
+ db_err = bfd->nodes->get(bfd->nodes, trail->db_txn,
+ svn_fs_base__id_to_dbt(&key, id, pool),
+ svn_fs_base__result_dbt(&value),
+ 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ /* If there's no such node, return an appropriately specific error. */
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_dangling_id(fs, id);
+
+ /* Handle any other error conditions. */
+ SVN_ERR(BDB_WRAP(fs, N_("reading node revision"), db_err));
+
+ /* If our caller doesn't really care about the return value here,
+ just return successfully. */
+ if (! noderev_p)
+ return SVN_NO_ERROR;
+
+ /* Parse and the NODE-REVISION skel. */
+ skel = svn_skel__parse(value.data, value.size, pool);
+
+ /* Convert to a native FS type. */
+ SVN_ERR(svn_fs_base__parse_node_revision_skel(&noderev, skel, pool));
+ *noderev_p = noderev;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_bdb__put_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ node_revision_t *noderev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DB_TXN *db_txn = trail->db_txn;
+ DBT key, value;
+ svn_skel_t *skel;
+
+ /* Convert from native type into skel */
+ SVN_ERR(svn_fs_base__unparse_node_revision_skel(&skel, noderev,
+ bfd->format, pool));
+ svn_fs_base__trail_debug(trail, "nodes", "put");
+ return BDB_WRAP(fs, N_("storing node revision"),
+ bfd->nodes->put(bfd->nodes, db_txn,
+ svn_fs_base__id_to_dbt(&key, id, pool),
+ svn_fs_base__skel_to_dbt(&value, skel,
+ pool),
+ 0));
+}
diff --git a/subversion/libsvn_fs_base/bdb/nodes-table.h b/subversion/libsvn_fs_base/bdb/nodes-table.h
new file mode 100644
index 0000000..c1687ab
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/nodes-table.h
@@ -0,0 +1,121 @@
+/* nodes-table.h : interface to `nodes' table
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_NODES_TABLE_H
+#define SVN_LIBSVN_FS_NODES_TABLE_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_fs.h"
+#include "../trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Creating and opening the `nodes' table. */
+
+
+/* Open a `nodes' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *NODES_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_nodes_table(DB **nodes_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+/* Check FS's `nodes' table to find an unused node number, and set
+ *ID_P to the ID of the first revision of an entirely new node in
+ FS, with copy_id COPY_ID, created in transaction TXN_ID, as part
+ of TRAIL. Allocate the new ID, and do all temporary allocation,
+ in POOL. */
+svn_error_t *svn_fs_bdb__new_node_id(svn_fs_id_t **id_p,
+ svn_fs_t *fs,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Delete node revision ID from FS's `nodes' table, as part of TRAIL.
+ WARNING: This does not check that the node revision is mutable!
+ Callers should do that check themselves.
+
+ todo: Jim and Karl are both not sure whether it would be better for
+ this to check mutability or not. On the one hand, having the
+ lowest level do that check would seem intuitively good. On the
+ other hand, we'll need a way to delete even immutable nodes someday
+ -- for example, someone accidentally commits NDA-protected data to
+ a public repository and wants to remove it. Thoughts? */
+svn_error_t *svn_fs_bdb__delete_nodes_entry(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *SUCCESSOR_P to the ID of an immediate successor to node
+ revision ID in FS that does not exist yet, as part of TRAIL.
+ Allocate *SUCCESSOR_P in POOL.
+
+ Use the current Subversion transaction name TXN_ID, and optionally
+ a copy id COPY_ID, in the determination of the new node revision
+ ID. */
+svn_error_t *svn_fs_bdb__new_successor_id(svn_fs_id_t **successor_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *NODEREV_P to the node-revision for the node ID in FS, as
+ part of TRAIL. Do any allocations in POOL. Allow NODEREV_P
+ to be NULL, in which case it is not used, and this function acts as
+ an existence check for ID in FS. */
+svn_error_t *svn_fs_bdb__get_node_revision(node_revision_t **noderev_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Store NODEREV as the node-revision for the node whose id
+ is ID in FS, as part of TRAIL. Do any necessary temporary
+ allocation in POOL.
+
+ After this call, the node table manager assumes that NODE's
+ contents will change frequently. */
+svn_error_t *svn_fs_bdb__put_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ node_revision_t *noderev,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_NODES_TABLE_H */
diff --git a/subversion/libsvn_fs_base/bdb/reps-table.c b/subversion/libsvn_fs_base/bdb/reps-table.c
new file mode 100644
index 0000000..1c8ea6d
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/reps-table.c
@@ -0,0 +1,204 @@
+/* reps-table.c : operations on the `representations' table
+ *
+ * ====================================================================
+ * 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 "bdb_compat.h"
+#include "svn_fs.h"
+#include "../fs.h"
+#include "../util/fs_skels.h"
+#include "../err.h"
+#include "dbt.h"
+#include "../trail.h"
+#include "../key-gen.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "reps-table.h"
+#include "strings-table.h"
+
+
+#include "svn_private_config.h"
+
+
+/*** Creating and opening the representations table. ***/
+
+int
+svn_fs_bdb__open_reps_table(DB **reps_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *reps;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&reps, env, 0));
+ BDB_ERR((reps->open)(SVN_BDB_OPEN_PARAMS(reps, NULL),
+ "representations", 0, DB_BTREE,
+ open_flags, 0666));
+
+ /* Create the `next-key' table entry. */
+ if (create)
+ {
+ DBT key, value;
+
+ BDB_ERR(reps->put
+ (reps, 0,
+ svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&value, "0"), 0));
+ }
+
+ *reps_p = reps;
+ return 0;
+}
+
+
+
+/*** Storing and retrieving reps. ***/
+
+svn_error_t *
+svn_fs_bdb__read_rep(representation_t **rep_p,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ svn_skel_t *skel;
+ int db_err;
+ DBT query, result;
+
+ svn_fs_base__trail_debug(trail, "representations", "get");
+ db_err = bfd->representations->get(bfd->representations,
+ trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, key),
+ svn_fs_base__result_dbt(&result), 0);
+ svn_fs_base__track_dbt(&result, pool);
+
+ /* If there's no such node, return an appropriately specific error. */
+ if (db_err == DB_NOTFOUND)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_REPRESENTATION, 0,
+ _("No such representation '%s'"), key);
+
+ /* Handle any other error conditions. */
+ SVN_ERR(BDB_WRAP(fs, N_("reading representation"), db_err));
+
+ /* Parse the REPRESENTATION skel. */
+ skel = svn_skel__parse(result.data, result.size, pool);
+
+ /* Convert to a native type. */
+ return svn_fs_base__parse_representation_skel(rep_p, skel, pool);
+}
+
+
+svn_error_t *
+svn_fs_bdb__write_rep(svn_fs_t *fs,
+ const char *key,
+ const representation_t *rep,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, result;
+ svn_skel_t *skel;
+
+ /* Convert from native type to skel. */
+ SVN_ERR(svn_fs_base__unparse_representation_skel(&skel, rep,
+ bfd->format, pool));
+
+ /* Now write the record. */
+ svn_fs_base__trail_debug(trail, "representations", "put");
+ return BDB_WRAP(fs, N_("storing representation"),
+ bfd->representations->put
+ (bfd->representations, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, key),
+ svn_fs_base__skel_to_dbt(&result, skel, pool),
+ 0));
+}
+
+
+svn_error_t *
+svn_fs_bdb__write_new_rep(const char **key,
+ svn_fs_t *fs,
+ const representation_t *rep,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, result;
+ int db_err;
+ apr_size_t len;
+ char next_key[MAX_KEY_SIZE];
+
+ /* ### todo: see issue #409 for why bumping the key as part of this
+ trail is problematic. */
+
+ /* Get the current value associated with `next-key'. */
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY);
+ svn_fs_base__trail_debug(trail, "representations", "get");
+ SVN_ERR(BDB_WRAP(fs, N_("allocating new representation (getting next-key)"),
+ bfd->representations->get
+ (bfd->representations, trail->db_txn, &query,
+ svn_fs_base__result_dbt(&result), 0)));
+
+ svn_fs_base__track_dbt(&result, pool);
+
+ /* Store the new rep. */
+ *key = apr_pstrmemdup(pool, result.data, result.size);
+ SVN_ERR(svn_fs_bdb__write_rep(fs, *key, rep, trail, pool));
+
+ /* Bump to future key. */
+ len = result.size;
+ svn_fs_base__next_key(result.data, &len, next_key);
+ svn_fs_base__trail_debug(trail, "representations", "put");
+ db_err = bfd->representations->put
+ (bfd->representations, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&result, next_key),
+ 0);
+
+ return BDB_WRAP(fs, N_("bumping next representation key"), db_err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__delete_rep(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ int db_err;
+ DBT query;
+
+ svn_fs_base__trail_debug(trail, "representations", "del");
+ db_err = bfd->representations->del
+ (bfd->representations, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, key), 0);
+
+ /* If there's no such node, return an appropriately specific error. */
+ if (db_err == DB_NOTFOUND)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_REPRESENTATION, 0,
+ _("No such representation '%s'"), key);
+
+ /* Handle any other error conditions. */
+ return BDB_WRAP(fs, N_("deleting representation"), db_err);
+}
diff --git a/subversion/libsvn_fs_base/bdb/reps-table.h b/subversion/libsvn_fs_base/bdb/reps-table.h
new file mode 100644
index 0000000..b5cd27d
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/reps-table.h
@@ -0,0 +1,94 @@
+/* reps-table.h : internal interface to `representations' table
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_REPS_TABLE_H
+#define SVN_LIBSVN_FS_REPS_TABLE_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_io.h"
+#include "svn_fs.h"
+#include "../fs.h"
+#include "../trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** Creating the `representations' table. ***/
+
+/* Open a `representations' table in ENV. If CREATE is non-zero,
+ create one if it doesn't exist. Set *REPS_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_reps_table(DB **reps_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+
+/*** Storing and retrieving reps. ***/
+
+/* Set *REP_P to point to the representation for the key KEY in
+ FS, as part of TRAIL. Perform all allocations in POOL.
+
+ If KEY is not a representation in FS, the error
+ SVN_ERR_FS_NO_SUCH_REPRESENTATION is returned. */
+svn_error_t *svn_fs_bdb__read_rep(representation_t **rep_p,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Store REP as the representation for KEY in FS, as part of
+ TRAIL. Do any necessary temporary allocation in POOL. */
+svn_error_t *svn_fs_bdb__write_rep(svn_fs_t *fs,
+ const char *key,
+ const representation_t *rep,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Store REP as a new representation in FS, and the new rep's key in
+ *KEY, as part of trail. The new key is allocated in POOL. */
+svn_error_t *svn_fs_bdb__write_new_rep(const char **key,
+ svn_fs_t *fs,
+ const representation_t *rep,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Delete representation KEY from FS, as part of TRAIL.
+ WARNING: This does not ensure that no one references this
+ representation! Callers should ensure that themselves. */
+svn_error_t *svn_fs_bdb__delete_rep(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_REPS_TABLE_H */
diff --git a/subversion/libsvn_fs_base/bdb/rev-table.c b/subversion/libsvn_fs_base/bdb/rev-table.c
new file mode 100644
index 0000000..b752249
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/rev-table.c
@@ -0,0 +1,221 @@
+ /* rev-table.c : working with the `revisions' table
+ *
+ * ====================================================================
+ * 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 "bdb_compat.h"
+
+#include "svn_fs.h"
+#include "private/svn_skel.h"
+
+#include "../fs.h"
+#include "../err.h"
+#include "../util/fs_skels.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "dbt.h"
+#include "rev-table.h"
+
+#include "svn_private_config.h"
+#include "private/svn_fs_util.h"
+
+
+/* Opening/creating the `revisions' table. */
+
+int svn_fs_bdb__open_revisions_table(DB **revisions_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *revisions;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&revisions, env, 0));
+ BDB_ERR((revisions->open)(SVN_BDB_OPEN_PARAMS(revisions, NULL),
+ "revisions", 0, DB_RECNO,
+ open_flags, 0666));
+
+ *revisions_p = revisions;
+ return 0;
+}
+
+
+
+/* Storing and retrieving filesystem revisions. */
+
+
+svn_error_t *
+svn_fs_bdb__get_rev(revision_t **revision_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ int db_err;
+ DBT key, value;
+ svn_skel_t *skel;
+ revision_t *revision;
+
+ /* Turn the revision number into a Berkeley DB record number.
+ Revisions are numbered starting with zero; Berkeley DB record
+ numbers begin with one. */
+ db_recno_t recno = (db_recno_t) rev + 1;
+
+ svn_fs_base__trail_debug(trail, "revisions", "get");
+ db_err = bfd->revisions->get(bfd->revisions, trail->db_txn,
+ svn_fs_base__set_dbt(&key, &recno,
+ sizeof(recno)),
+ svn_fs_base__result_dbt(&value),
+ 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ /* If there's no such revision, return an appropriately specific error. */
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_dangling_rev(fs, rev);
+
+ /* Handle any other error conditions. */
+ SVN_ERR(BDB_WRAP(fs, N_("reading filesystem revision"), db_err));
+
+ /* Parse REVISION skel. */
+ skel = svn_skel__parse(value.data, value.size, pool);
+ if (! skel)
+ return svn_fs_base__err_corrupt_fs_revision(fs, rev);
+
+ /* Convert skel to native type. */
+ SVN_ERR(svn_fs_base__parse_revision_skel(&revision, skel, pool));
+
+ *revision_p = revision;
+ return SVN_NO_ERROR;
+}
+
+
+/* Write REVISION to FS as part of TRAIL. If *REV is a valid revision
+ number, write this revision as one that corresponds to *REV, else
+ write a new revision and return its newly created revision number
+ in *REV. */
+svn_error_t *
+svn_fs_bdb__put_rev(svn_revnum_t *rev,
+ svn_fs_t *fs,
+ const revision_t *revision,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ int db_err;
+ db_recno_t recno = 0;
+ svn_skel_t *skel;
+ DBT key, value;
+
+ /* Convert native type to skel. */
+ SVN_ERR(svn_fs_base__unparse_revision_skel(&skel, revision, pool));
+
+ if (SVN_IS_VALID_REVNUM(*rev))
+ {
+ DBT query, result;
+
+ /* Update the filesystem revision with the new skel. */
+ recno = (db_recno_t) *rev + 1;
+ svn_fs_base__trail_debug(trail, "revisions", "put");
+ db_err = bfd->revisions->put
+ (bfd->revisions, trail->db_txn,
+ svn_fs_base__set_dbt(&query, &recno, sizeof(recno)),
+ svn_fs_base__skel_to_dbt(&result, skel, pool), 0);
+ return BDB_WRAP(fs, N_("updating filesystem revision"), db_err);
+ }
+
+ svn_fs_base__trail_debug(trail, "revisions", "put");
+ db_err = bfd->revisions->put(bfd->revisions, trail->db_txn,
+ svn_fs_base__recno_dbt(&key, &recno),
+ svn_fs_base__skel_to_dbt(&value, skel, pool),
+ DB_APPEND);
+ SVN_ERR(BDB_WRAP(fs, N_("storing filesystem revision"), db_err));
+
+ /* Turn the record number into a Subversion revision number.
+ Revisions are numbered starting with zero; Berkeley DB record
+ numbers begin with one. */
+ *rev = recno - 1;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Getting the youngest revision. */
+
+
+svn_error_t *
+svn_fs_bdb__youngest_rev(svn_revnum_t *youngest_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ int db_err;
+ DBC *cursor = 0;
+ DBT key, value;
+ db_recno_t recno;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ /* Create a database cursor. */
+ svn_fs_base__trail_debug(trail, "revisions", "cursor");
+ SVN_ERR(BDB_WRAP(fs, N_("getting youngest revision (creating cursor)"),
+ bfd->revisions->cursor(bfd->revisions, trail->db_txn,
+ &cursor, 0)));
+
+ /* Find the last entry in the `revisions' table. */
+ db_err = svn_bdb_dbc_get(cursor,
+ svn_fs_base__recno_dbt(&key, &recno),
+ svn_fs_base__nodata_dbt(&value),
+ DB_LAST);
+
+ if (db_err)
+ {
+ /* Free the cursor. Ignore any error value --- the error above
+ is more interesting. */
+ svn_bdb_dbc_close(cursor);
+
+ if (db_err == DB_NOTFOUND)
+ /* The revision 0 should always be present, at least. */
+ return
+ svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ "Corrupt DB: revision 0 missing from 'revisions' table, in "
+ "filesystem '%s'", fs->path);
+
+ SVN_ERR(BDB_WRAP(fs, N_("getting youngest revision (finding last entry)"),
+ db_err));
+ }
+
+ /* You can't commit a transaction with open cursors, because:
+ 1) key/value pairs don't get deleted until the cursors referring
+ to them are closed, so closing a cursor can fail for various
+ reasons, and txn_commit shouldn't fail that way, and
+ 2) using a cursor after committing its transaction can cause
+ undetectable database corruption. */
+ SVN_ERR(BDB_WRAP(fs, N_("getting youngest revision (closing cursor)"),
+ svn_bdb_dbc_close(cursor)));
+
+ /* Turn the record number into a Subversion revision number.
+ Revisions are numbered starting with zero; Berkeley DB record
+ numbers begin with one. */
+ *youngest_p = recno - 1;
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_base/bdb/rev-table.h b/subversion/libsvn_fs_base/bdb/rev-table.h
new file mode 100644
index 0000000..47209a8
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/rev-table.h
@@ -0,0 +1,85 @@
+/* rev-table.h : internal interface to revision table operations
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_REV_TABLE_H
+#define SVN_LIBSVN_FS_REV_TABLE_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_fs.h"
+
+#include "../fs.h"
+#include "../trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Creating and opening the `revisions' table. */
+
+/* Open a `revisions' table in ENV. If CREATE is non-zero, create one
+ if it doesn't exist. Set *REVS_P to the new table. Return a
+ Berkeley DB error code. */
+int svn_fs_bdb__open_revisions_table(DB **revisions_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+
+/* Storing and retrieving filesystem revisions. */
+
+
+/* Set *REVISION_P to point to the revision structure for the
+ filesystem revision REV in FS, as part of TRAIL. Perform all
+ allocations in POOL. */
+svn_error_t *svn_fs_bdb__get_rev(revision_t **revision_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Store REVISION in FS as revision *REV as part of TRAIL. If *REV is
+ an invalid revision number, create a brand new revision and return
+ its revision number as *REV to the caller. Do any necessary
+ temporary allocation in POOL. */
+svn_error_t *svn_fs_bdb__put_rev(svn_revnum_t *rev,
+ svn_fs_t *fs,
+ const revision_t *revision,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *YOUNGEST_P to the youngest revision in filesystem FS,
+ as part of TRAIL. Use POOL for all temporary allocation. */
+svn_error_t *svn_fs_bdb__youngest_rev(svn_revnum_t *youngest_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_REV_TABLE_H */
diff --git a/subversion/libsvn_fs_base/bdb/strings-table.c b/subversion/libsvn_fs_base/bdb/strings-table.c
new file mode 100644
index 0000000..f5348e7
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/strings-table.c
@@ -0,0 +1,541 @@
+/* strings-table.c : operations on the `strings' table
+ *
+ * ====================================================================
+ * 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 "bdb_compat.h"
+#include "svn_fs.h"
+#include "svn_pools.h"
+#include "../fs.h"
+#include "../err.h"
+#include "dbt.h"
+#include "../trail.h"
+#include "../key-gen.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "strings-table.h"
+
+#include "svn_private_config.h"
+
+
+/*** Creating and opening the strings table. ***/
+
+int
+svn_fs_bdb__open_strings_table(DB **strings_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *strings;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&strings, env, 0));
+
+ /* Enable duplicate keys. This allows the data to be spread out across
+ multiple records. Note: this must occur before ->open(). */
+ BDB_ERR(strings->set_flags(strings, DB_DUP));
+
+ BDB_ERR((strings->open)(SVN_BDB_OPEN_PARAMS(strings, NULL),
+ "strings", 0, DB_BTREE,
+ open_flags, 0666));
+
+ if (create)
+ {
+ DBT key, value;
+
+ /* Create the `next-key' table entry. */
+ BDB_ERR(strings->put
+ (strings, 0,
+ svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&value, "0"), 0));
+ }
+
+ *strings_p = strings;
+ return 0;
+}
+
+
+
+/*** Storing and retrieving strings. ***/
+
+/* Allocate *CURSOR and advance it to first row in the set of rows
+ whose key is defined by QUERY. Set *LENGTH to the size of that
+ first row. */
+static svn_error_t *
+locate_key(apr_size_t *length,
+ DBC **cursor,
+ DBT *query,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ int db_err;
+ DBT result;
+
+ svn_fs_base__trail_debug(trail, "strings", "cursor");
+ SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
+ bfd->strings->cursor(bfd->strings, trail->db_txn,
+ cursor, 0)));
+
+ /* Set up the DBT for reading the length of the record. */
+ svn_fs_base__clear_dbt(&result);
+ result.ulen = 0;
+ result.flags |= DB_DBT_USERMEM;
+
+ /* Advance the cursor to the key that we're looking for. */
+ db_err = svn_bdb_dbc_get(*cursor, query, &result, DB_SET);
+
+ /* We don't need to svn_fs_base__track_dbt() the result, because nothing
+ was allocated in it. */
+
+ /* If there's no such node, return an appropriately specific error. */
+ if (db_err == DB_NOTFOUND)
+ {
+ svn_bdb_dbc_close(*cursor);
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_STRING, 0,
+ "No such string '%s'", (const char *)query->data);
+ }
+ if (db_err)
+ {
+ DBT rerun;
+
+ if (db_err != SVN_BDB_DB_BUFFER_SMALL)
+ {
+ svn_bdb_dbc_close(*cursor);
+ return BDB_WRAP(fs, N_("moving cursor"), db_err);
+ }
+
+ /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a
+ zero length buf), so we need to re-run the operation to make
+ it happen. */
+ svn_fs_base__clear_dbt(&rerun);
+ rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL;
+ db_err = svn_bdb_dbc_get(*cursor, query, &rerun, DB_SET);
+ if (db_err)
+ {
+ svn_bdb_dbc_close(*cursor);
+ return BDB_WRAP(fs, N_("rerunning cursor move"), db_err);
+ }
+ }
+
+ /* ### this cast might not be safe? */
+ *length = (apr_size_t) result.size;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Advance CURSOR by a single row in the set of rows whose keys match
+ CURSOR's current location. Set *LENGTH to the size of that next
+ row. If any error occurs, CURSOR will be destroyed. */
+static int
+get_next_length(apr_size_t *length, DBC *cursor, DBT *query)
+{
+ DBT result;
+ int db_err;
+
+ /* Set up the DBT for reading the length of the record. */
+ svn_fs_base__clear_dbt(&result);
+ result.ulen = 0;
+ result.flags |= DB_DBT_USERMEM;
+
+ /* Note: this may change the QUERY DBT, but that's okay: we're going
+ to be sticking with the same key anyways. */
+ db_err = svn_bdb_dbc_get(cursor, query, &result, DB_NEXT_DUP);
+
+ /* Note that we exit on DB_NOTFOUND. The caller uses that to end a loop. */
+ if (db_err)
+ {
+ DBT rerun;
+
+ if (db_err != SVN_BDB_DB_BUFFER_SMALL)
+ {
+ svn_bdb_dbc_close(cursor);
+ return db_err;
+ }
+
+ /* We got an SVN_BDB_DB_BUFFER_SMALL (typical since we have a
+ zero length buf), so we need to re-run the operation to make
+ it happen. */
+ svn_fs_base__clear_dbt(&rerun);
+ rerun.flags |= DB_DBT_USERMEM | DB_DBT_PARTIAL;
+ db_err = svn_bdb_dbc_get(cursor, query, &rerun, DB_NEXT_DUP);
+ if (db_err)
+ svn_bdb_dbc_close(cursor);
+ }
+
+ /* ### this cast might not be safe? */
+ *length = (apr_size_t) result.size;
+ return db_err;
+}
+
+
+svn_error_t *
+svn_fs_bdb__string_read(svn_fs_t *fs,
+ const char *key,
+ char *buf,
+ svn_filesize_t offset,
+ apr_size_t *len,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ int db_err;
+ DBT query, result;
+ DBC *cursor;
+ apr_size_t length, bytes_read = 0;
+
+ svn_fs_base__str_to_dbt(&query, key);
+
+ SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool));
+
+ /* Seek through the records for this key, trying to find the record that
+ includes OFFSET. Note that we don't require reading from more than
+ one record since we're allowed to return partial reads. */
+ while (length <= offset)
+ {
+ offset -= length;
+
+ /* Remember, if any error happens, our cursor has been closed
+ for us. */
+ db_err = get_next_length(&length, cursor, &query);
+
+ /* No more records? They tried to read past the end. */
+ if (db_err == DB_NOTFOUND)
+ {
+ *len = 0;
+ return SVN_NO_ERROR;
+ }
+ if (db_err)
+ return BDB_WRAP(fs, N_("reading string"), db_err);
+ }
+
+ /* The current record contains OFFSET. Fetch the contents now. Note that
+ OFFSET has been moved to be relative to this record. The length could
+ quite easily extend past this record, so we use DB_DBT_PARTIAL and
+ read successive records until we've filled the request. */
+ while (1)
+ {
+ svn_fs_base__clear_dbt(&result);
+ result.data = buf + bytes_read;
+ result.ulen = *len - bytes_read;
+ result.doff = (u_int32_t)offset;
+ result.dlen = *len - bytes_read;
+ result.flags |= (DB_DBT_USERMEM | DB_DBT_PARTIAL);
+ db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_CURRENT);
+ if (db_err)
+ {
+ svn_bdb_dbc_close(cursor);
+ return BDB_WRAP(fs, N_("reading string"), db_err);
+ }
+
+ bytes_read += result.size;
+ if (bytes_read == *len)
+ {
+ /* Done with the cursor. */
+ SVN_ERR(BDB_WRAP(fs, N_("closing string-reading cursor"),
+ svn_bdb_dbc_close(cursor)));
+ break;
+ }
+
+ /* Remember, if any error happens, our cursor has been closed
+ for us. */
+ db_err = get_next_length(&length, cursor, &query);
+ if (db_err == DB_NOTFOUND)
+ break;
+ if (db_err)
+ return BDB_WRAP(fs, N_("reading string"), db_err);
+
+ /* We'll be reading from the beginning of the next record */
+ offset = 0;
+ }
+
+ *len = bytes_read;
+ return SVN_NO_ERROR;
+}
+
+
+/* Get the current 'next-key' value and bump the record. */
+static svn_error_t *
+get_key_and_bump(svn_fs_t *fs,
+ const char **key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBC *cursor;
+ char next_key[MAX_KEY_SIZE];
+ apr_size_t key_len;
+ int db_err;
+ DBT query;
+ DBT result;
+
+ /* ### todo: see issue #409 for why bumping the key as part of this
+ trail is problematic. */
+
+ /* Open a cursor and move it to the 'next-key' value. We can then fetch
+ the contents and use the cursor to overwrite those contents. Since
+ this database allows duplicates, we can't do an arbitrary 'put' to
+ write the new value -- that would append, not overwrite. */
+
+ svn_fs_base__trail_debug(trail, "strings", "cursor");
+ SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
+ bfd->strings->cursor(bfd->strings, trail->db_txn,
+ &cursor, 0)));
+
+ /* Advance the cursor to 'next-key' and read it. */
+
+ db_err = svn_bdb_dbc_get(cursor,
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY),
+ svn_fs_base__result_dbt(&result),
+ DB_SET);
+ if (db_err)
+ {
+ svn_bdb_dbc_close(cursor);
+ return BDB_WRAP(fs, N_("getting next-key value"), db_err);
+ }
+
+ svn_fs_base__track_dbt(&result, pool);
+ *key = apr_pstrmemdup(pool, result.data, result.size);
+
+ /* Bump to future key. */
+ key_len = result.size;
+ svn_fs_base__next_key(result.data, &key_len, next_key);
+
+ /* Shove the new key back into the database, at the cursor position. */
+ db_err = svn_bdb_dbc_put(cursor, &query,
+ svn_fs_base__str_to_dbt(&result, next_key),
+ DB_CURRENT);
+ if (db_err)
+ {
+ svn_bdb_dbc_close(cursor); /* ignore the error, the original is
+ more important. */
+ return BDB_WRAP(fs, N_("bumping next string key"), db_err);
+ }
+
+ return BDB_WRAP(fs, N_("closing string-reading cursor"),
+ svn_bdb_dbc_close(cursor));
+}
+
+svn_error_t *
+svn_fs_bdb__string_append(svn_fs_t *fs,
+ const char **key,
+ apr_size_t len,
+ const char *buf,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, result;
+
+ /* If the passed-in key is NULL, we graciously generate a new string
+ using the value of the `next-key' record in the strings table. */
+ if (*key == NULL)
+ {
+ SVN_ERR(get_key_and_bump(fs, key, trail, pool));
+ }
+
+ /* Store a new record into the database. */
+ svn_fs_base__trail_debug(trail, "strings", "put");
+ return BDB_WRAP(fs, N_("appending string"),
+ bfd->strings->put
+ (bfd->strings, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, *key),
+ svn_fs_base__set_dbt(&result, buf, len),
+ 0));
+}
+
+
+svn_error_t *
+svn_fs_bdb__string_clear(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ int db_err;
+ DBT query, result;
+
+ svn_fs_base__str_to_dbt(&query, key);
+
+ /* Torch the prior contents */
+ svn_fs_base__trail_debug(trail, "strings", "del");
+ db_err = bfd->strings->del(bfd->strings, trail->db_txn, &query, 0);
+
+ /* If there's no such node, return an appropriately specific error. */
+ if (db_err == DB_NOTFOUND)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_STRING, 0,
+ "No such string '%s'", key);
+
+ /* Handle any other error conditions. */
+ SVN_ERR(BDB_WRAP(fs, N_("clearing string"), db_err));
+
+ /* Shove empty data back in for this key. */
+ svn_fs_base__clear_dbt(&result);
+ result.data = 0;
+ result.size = 0;
+ result.flags |= DB_DBT_USERMEM;
+
+ svn_fs_base__trail_debug(trail, "strings", "put");
+ return BDB_WRAP(fs, N_("storing empty contents"),
+ bfd->strings->put(bfd->strings, trail->db_txn,
+ &query, &result, 0));
+}
+
+
+svn_error_t *
+svn_fs_bdb__string_size(svn_filesize_t *size,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ int db_err;
+ DBT query;
+ DBC *cursor;
+ apr_size_t length;
+ svn_filesize_t total;
+
+ svn_fs_base__str_to_dbt(&query, key);
+
+ SVN_ERR(locate_key(&length, &cursor, &query, fs, trail, pool));
+
+ total = length;
+ while (1)
+ {
+ /* Remember, if any error happens, our cursor has been closed
+ for us. */
+ db_err = get_next_length(&length, cursor, &query);
+
+ /* No more records? Then return the total length. */
+ if (db_err == DB_NOTFOUND)
+ {
+ *size = total;
+ return SVN_NO_ERROR;
+ }
+ if (db_err)
+ return BDB_WRAP(fs, N_("fetching string length"), db_err);
+
+ total += length;
+ }
+
+ /* NOTREACHED */
+}
+
+
+svn_error_t *
+svn_fs_bdb__string_delete(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ int db_err;
+ DBT query;
+
+ svn_fs_base__trail_debug(trail, "strings", "del");
+ db_err = bfd->strings->del(bfd->strings, trail->db_txn,
+ svn_fs_base__str_to_dbt(&query, key), 0);
+
+ /* If there's no such node, return an appropriately specific error. */
+ if (db_err == DB_NOTFOUND)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_STRING, 0,
+ "No such string '%s'", key);
+
+ /* Handle any other error conditions. */
+ return BDB_WRAP(fs, N_("deleting string"), db_err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__string_copy(svn_fs_t *fs,
+ const char **new_key,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query;
+ DBT result;
+ DBT copykey;
+ DBC *cursor;
+ int db_err;
+
+ /* Copy off the old key in case the caller is sharing storage
+ between the old and new keys. */
+ const char *old_key = apr_pstrdup(pool, key);
+
+ SVN_ERR(get_key_and_bump(fs, new_key, trail, pool));
+
+ svn_fs_base__trail_debug(trail, "strings", "cursor");
+ SVN_ERR(BDB_WRAP(fs, N_("creating cursor for reading a string"),
+ bfd->strings->cursor(bfd->strings, trail->db_txn,
+ &cursor, 0)));
+
+ svn_fs_base__str_to_dbt(&query, old_key);
+ svn_fs_base__str_to_dbt(&copykey, *new_key);
+
+ svn_fs_base__clear_dbt(&result);
+
+ /* Move to the first record and fetch its data (under BDB's mem mgmt). */
+ db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_SET);
+ if (db_err)
+ {
+ svn_bdb_dbc_close(cursor);
+ return BDB_WRAP(fs, N_("getting next-key value"), db_err);
+ }
+
+ while (1)
+ {
+ /* ### can we pass a BDB-provided buffer to another BDB function?
+ ### they are supposed to have a duration up to certain points
+ ### of calling back into BDB, but I'm not sure what the exact
+ ### rules are. it is definitely nicer to use BDB buffers here
+ ### to simplify things and reduce copies, but... hrm.
+ */
+
+ /* Write the data to the database */
+ svn_fs_base__trail_debug(trail, "strings", "put");
+ db_err = bfd->strings->put(bfd->strings, trail->db_txn,
+ &copykey, &result, 0);
+ if (db_err)
+ {
+ svn_bdb_dbc_close(cursor);
+ return BDB_WRAP(fs, N_("writing copied data"), db_err);
+ }
+
+ /* Read the next chunk. Terminate loop if we're done. */
+ svn_fs_base__clear_dbt(&result);
+ db_err = svn_bdb_dbc_get(cursor, &query, &result, DB_NEXT_DUP);
+ if (db_err == DB_NOTFOUND)
+ break;
+ if (db_err)
+ {
+ svn_bdb_dbc_close(cursor);
+ return BDB_WRAP(fs, N_("fetching string data for a copy"), db_err);
+ }
+ }
+
+ return BDB_WRAP(fs, N_("closing string-reading cursor"),
+ svn_bdb_dbc_close(cursor));
+}
diff --git a/subversion/libsvn_fs_base/bdb/strings-table.h b/subversion/libsvn_fs_base/bdb/strings-table.h
new file mode 100644
index 0000000..443cb72
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/strings-table.h
@@ -0,0 +1,143 @@
+/* strings-table.h : internal interface to `strings' table
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_STRINGS_TABLE_H
+#define SVN_LIBSVN_FS_STRINGS_TABLE_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_io.h"
+#include "svn_fs.h"
+#include "../trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* This interface provides raw access to the `strings' table. It does
+ not deal with deltification, undeltification, or skels. It just
+ reads and writes strings of bytes. */
+
+
+/* Open a `strings' table in ENV. If CREATE is non-zero, create
+ * one if it doesn't exist. Set *STRINGS_P to the new table.
+ * Return a Berkeley DB error code.
+ */
+int svn_fs_bdb__open_strings_table(DB **strings_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+/* Read *LEN bytes into BUF from OFFSET in string KEY in FS, as part
+ * of TRAIL.
+ *
+ * On return, *LEN is set to the number of bytes read. If this value
+ * is less than the number requested, the end of the string has been
+ * reached (no error is returned on end-of-string).
+ *
+ * If OFFSET is past the end of the string, then *LEN will be set to
+ * zero. Callers which are advancing OFFSET as they read portions of
+ * the string can terminate their loop when *LEN is returned as zero
+ * (which will occur when OFFSET == length(the string)).
+ *
+ * If string KEY does not exist, the error SVN_ERR_FS_NO_SUCH_STRING
+ * is returned.
+ */
+svn_error_t *svn_fs_bdb__string_read(svn_fs_t *fs,
+ const char *key,
+ char *buf,
+ svn_filesize_t offset,
+ apr_size_t *len,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *SIZE to the size in bytes of string KEY in FS, as part of
+ * TRAIL.
+ *
+ * If string KEY does not exist, return SVN_ERR_FS_NO_SUCH_STRING.
+ */
+svn_error_t *svn_fs_bdb__string_size(svn_filesize_t *size,
+ svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Append LEN bytes from BUF to string *KEY in FS, as part of TRAIL.
+ *
+ * If *KEY is null, then create a new string and store the new key in
+ * *KEY (allocating it in POOL), and write LEN bytes from BUF
+ * as the initial contents of the string.
+ *
+ * If *KEY is not null but there is no string named *KEY, return
+ * SVN_ERR_FS_NO_SUCH_STRING.
+ *
+ * Note: to overwrite the old contents of a string, call
+ * svn_fs_bdb__string_clear() and then svn_fs_bdb__string_append(). */
+svn_error_t *svn_fs_bdb__string_append(svn_fs_t *fs,
+ const char **key,
+ apr_size_t len,
+ const char *buf,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Make string KEY in FS zero length, as part of TRAIL.
+ * If the string does not exist, return SVN_ERR_FS_NO_SUCH_STRING.
+ */
+svn_error_t *svn_fs_bdb__string_clear(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Delete string KEY from FS, as part of TRAIL.
+ *
+ * If string KEY does not exist, return SVN_ERR_FS_NO_SUCH_STRING.
+ *
+ * WARNING: Deleting a string renders unusable any representations
+ * that refer to it. Be careful.
+ */
+svn_error_t *svn_fs_bdb__string_delete(svn_fs_t *fs,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Copy the contents of the string referred to by KEY in FS into a new
+ * record, returning the new record's key in *NEW_KEY. All
+ * allocations (including *NEW_KEY) occur in POOL. */
+svn_error_t *svn_fs_bdb__string_copy(svn_fs_t *fs,
+ const char **new_key,
+ const char *key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_STRINGS_TABLE_H */
diff --git a/subversion/libsvn_fs_base/bdb/txn-table.c b/subversion/libsvn_fs_base/bdb/txn-table.c
new file mode 100644
index 0000000..54a0e28
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/txn-table.c
@@ -0,0 +1,325 @@
+/* txn-table.c : operations on the `transactions' table
+ *
+ * ====================================================================
+ * 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 <assert.h>
+
+#include "bdb_compat.h"
+
+#include "svn_pools.h"
+#include "private/svn_skel.h"
+
+#include "dbt.h"
+#include "../err.h"
+#include "../fs.h"
+#include "../key-gen.h"
+#include "../util/fs_skels.h"
+#include "../trail.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "txn-table.h"
+
+#include "svn_private_config.h"
+
+
+static svn_boolean_t
+is_committed(transaction_t *txn)
+{
+ return (txn->kind == transaction_kind_committed);
+}
+
+
+int
+svn_fs_bdb__open_transactions_table(DB **transactions_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *txns;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&txns, env, 0));
+ BDB_ERR((txns->open)(SVN_BDB_OPEN_PARAMS(txns, NULL),
+ "transactions", 0, DB_BTREE,
+ open_flags, 0666));
+
+ /* Create the `next-key' table entry. */
+ if (create)
+ {
+ DBT key, value;
+
+ BDB_ERR(txns->put(txns, 0,
+ svn_fs_base__str_to_dbt(&key, NEXT_KEY_KEY),
+ svn_fs_base__str_to_dbt(&value, "0"), 0));
+ }
+
+ *transactions_p = txns;
+ return 0;
+}
+
+
+svn_error_t *
+svn_fs_bdb__put_txn(svn_fs_t *fs,
+ const transaction_t *txn,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ svn_skel_t *txn_skel;
+ DBT key, value;
+
+ /* Convert native type to skel. */
+ SVN_ERR(svn_fs_base__unparse_transaction_skel(&txn_skel, txn, pool));
+
+ /* Only in the context of this function do we know that the DB call
+ will not attempt to modify txn_name, so the cast belongs here. */
+ svn_fs_base__str_to_dbt(&key, txn_name);
+ svn_fs_base__skel_to_dbt(&value, txn_skel, pool);
+ svn_fs_base__trail_debug(trail, "transactions", "put");
+ return BDB_WRAP(fs, N_("storing transaction record"),
+ bfd->transactions->put(bfd->transactions, trail->db_txn,
+ &key, &value, 0));
+}
+
+
+/* Allocate a Subversion transaction ID in FS, as part of TRAIL. Set
+ *ID_P to the new transaction ID, allocated in POOL. */
+static svn_error_t *
+allocate_txn_id(const char **id_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT query, result;
+ apr_size_t len;
+ char next_key[MAX_KEY_SIZE];
+ int db_err;
+
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY);
+
+ /* Get the current value associated with the `next-key' key in the table. */
+ svn_fs_base__trail_debug(trail, "transactions", "get");
+ SVN_ERR(BDB_WRAP(fs, N_("allocating new transaction ID (getting 'next-key')"),
+ bfd->transactions->get(bfd->transactions, trail->db_txn,
+ &query,
+ svn_fs_base__result_dbt(&result),
+ 0)));
+ svn_fs_base__track_dbt(&result, pool);
+
+ /* Set our return value. */
+ *id_p = apr_pstrmemdup(pool, result.data, result.size);
+
+ /* Bump to future key. */
+ len = result.size;
+ svn_fs_base__next_key(result.data, &len, next_key);
+ svn_fs_base__str_to_dbt(&query, NEXT_KEY_KEY);
+ svn_fs_base__str_to_dbt(&result, next_key);
+ svn_fs_base__trail_debug(trail, "transactions", "put");
+ db_err = bfd->transactions->put(bfd->transactions, trail->db_txn,
+ &query, &result, 0);
+
+ return BDB_WRAP(fs, N_("bumping next transaction key"), db_err);
+}
+
+
+svn_error_t *
+svn_fs_bdb__create_txn(const char **txn_name_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *root_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const char *txn_name;
+ transaction_t txn;
+
+ SVN_ERR(allocate_txn_id(&txn_name, fs, trail, pool));
+ txn.kind = transaction_kind_normal;
+ txn.root_id = root_id;
+ txn.base_id = root_id;
+ txn.proplist = NULL;
+ txn.copies = NULL;
+ txn.revision = SVN_INVALID_REVNUM;
+ SVN_ERR(svn_fs_bdb__put_txn(fs, &txn, txn_name, trail, pool));
+
+ *txn_name_p = txn_name;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_bdb__delete_txn(svn_fs_t *fs,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key;
+ transaction_t *txn;
+
+ /* Make sure TXN is dead. */
+ SVN_ERR(svn_fs_bdb__get_txn(&txn, fs, txn_name, trail, pool));
+ if (is_committed(txn))
+ return svn_fs_base__err_txn_not_mutable(fs, txn_name);
+
+ /* Delete the transaction from the `transactions' table. */
+ svn_fs_base__str_to_dbt(&key, txn_name);
+ svn_fs_base__trail_debug(trail, "transactions", "del");
+ return BDB_WRAP(fs, N_("deleting entry from 'transactions' table"),
+ bfd->transactions->del(bfd->transactions,
+ trail->db_txn, &key, 0));
+}
+
+
+svn_error_t *
+svn_fs_bdb__get_txn(transaction_t **txn_p,
+ svn_fs_t *fs,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DBT key, value;
+ int db_err;
+ svn_skel_t *skel;
+ transaction_t *transaction;
+
+ /* Only in the context of this function do we know that the DB call
+ will not attempt to modify txn_name, so the cast belongs here. */
+ svn_fs_base__trail_debug(trail, "transactions", "get");
+ db_err = bfd->transactions->get(bfd->transactions, trail->db_txn,
+ svn_fs_base__str_to_dbt(&key, txn_name),
+ svn_fs_base__result_dbt(&value),
+ 0);
+ svn_fs_base__track_dbt(&value, pool);
+
+ if (db_err == DB_NOTFOUND)
+ return svn_fs_base__err_no_such_txn(fs, txn_name);
+ SVN_ERR(BDB_WRAP(fs, N_("reading transaction"), db_err));
+
+ /* Parse TRANSACTION skel */
+ skel = svn_skel__parse(value.data, value.size, pool);
+ if (! skel)
+ return svn_fs_base__err_corrupt_txn(fs, txn_name);
+
+ /* Convert skel to native type. */
+ SVN_ERR(svn_fs_base__parse_transaction_skel(&transaction, skel, pool));
+ *txn_p = transaction;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_bdb__get_txn_list(apr_array_header_t **names_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ apr_size_t const next_key_key_len = strlen(NEXT_KEY_KEY);
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_array_header_t *names;
+ DBC *cursor;
+ DBT key, value;
+ int db_err, db_c_err;
+
+ /* Allocate the initial names array */
+ names = apr_array_make(pool, 4, sizeof(const char *));
+
+ /* Create a database cursor to list the transaction names. */
+ svn_fs_base__trail_debug(trail, "transactions", "cursor");
+ SVN_ERR(BDB_WRAP(fs, N_("reading transaction list (opening cursor)"),
+ bfd->transactions->cursor(bfd->transactions,
+ trail->db_txn, &cursor, 0)));
+
+ /* Build a null-terminated array of keys in the transactions table. */
+ for (db_err = svn_bdb_dbc_get(cursor,
+ svn_fs_base__result_dbt(&key),
+ svn_fs_base__result_dbt(&value),
+ DB_FIRST);
+ db_err == 0;
+ db_err = svn_bdb_dbc_get(cursor,
+ svn_fs_base__result_dbt(&key),
+ svn_fs_base__result_dbt(&value),
+ DB_NEXT))
+ {
+ transaction_t *txn;
+ svn_skel_t *txn_skel;
+ svn_error_t *err;
+
+ /* Clear the per-iteration subpool */
+ svn_pool_clear(subpool);
+
+ /* Track the memory alloc'd for fetching the key and value here
+ so that when the containing pool is cleared, this memory is
+ freed. */
+ svn_fs_base__track_dbt(&key, subpool);
+ svn_fs_base__track_dbt(&value, subpool);
+
+ /* Ignore the "next-key" key. */
+ if (key.size == next_key_key_len
+ && 0 == memcmp(key.data, NEXT_KEY_KEY, next_key_key_len))
+ continue;
+
+ /* Parse TRANSACTION skel */
+ txn_skel = svn_skel__parse(value.data, value.size, subpool);
+ if (! txn_skel)
+ {
+ svn_bdb_dbc_close(cursor);
+ return svn_fs_base__err_corrupt_txn
+ (fs, apr_pstrmemdup(pool, key.data, key.size));
+ }
+
+ /* Convert skel to native type. */
+ if ((err = svn_fs_base__parse_transaction_skel(&txn, txn_skel,
+ subpool)))
+ {
+ svn_bdb_dbc_close(cursor);
+ return svn_error_trace(err);
+ }
+
+ /* If this is an immutable "committed" transaction, ignore it. */
+ if (is_committed(txn))
+ continue;
+
+ /* Add the transaction name to the NAMES array, duping it into POOL. */
+ APR_ARRAY_PUSH(names, const char *) = apr_pstrmemdup(pool, key.data,
+ key.size);
+ }
+
+ /* Check for errors, but close the cursor first. */
+ db_c_err = svn_bdb_dbc_close(cursor);
+ if (db_err != DB_NOTFOUND)
+ {
+ SVN_ERR(BDB_WRAP(fs, N_("reading transaction list (listing keys)"),
+ db_err));
+ }
+ SVN_ERR(BDB_WRAP(fs, N_("reading transaction list (closing cursor)"),
+ db_c_err));
+
+ /* Destroy the per-iteration subpool */
+ svn_pool_destroy(subpool);
+
+ *names_p = names;
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_base/bdb/txn-table.h b/subversion/libsvn_fs_base/bdb/txn-table.h
new file mode 100644
index 0000000..ff0cc9c
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/txn-table.h
@@ -0,0 +1,100 @@
+/* txn-table.h : internal interface to ops on `transactions' table
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_TXN_TABLE_H
+#define SVN_LIBSVN_FS_TXN_TABLE_H
+
+#include "svn_fs.h"
+#include "svn_error.h"
+#include "../trail.h"
+#include "../fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `transactions' table in ENV. If CREATE is non-zero, create
+ one if it doesn't exist. Set *TRANSACTIONS_P to the new table.
+ Return a Berkeley DB error code. */
+int svn_fs_bdb__open_transactions_table(DB **transactions_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+
+/* Create a new transaction in FS as part of TRAIL, with an initial
+ root and base root ID of ROOT_ID. Set *TXN_NAME_P to the name of the
+ new transaction, allocated in POOL. */
+svn_error_t *svn_fs_bdb__create_txn(const char **txn_name_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *root_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Remove the transaction whose name is TXN_NAME from the `transactions'
+ table of FS, as part of TRAIL.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed. */
+svn_error_t *svn_fs_bdb__delete_txn(svn_fs_t *fs,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Retrieve the transaction *TXN_P for the Subversion transaction
+ named TXN_NAME from the `transactions' table of FS, as part of
+ TRAIL. Perform all allocations in POOL.
+
+ If there is no such transaction, SVN_ERR_FS_NO_SUCH_TRANSACTION is
+ the error returned. */
+svn_error_t *svn_fs_bdb__get_txn(transaction_t **txn_p,
+ svn_fs_t *fs,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Store the Subversion transaction TXN in FS with an ID of TXN_NAME as
+ part of TRAIL. */
+svn_error_t *svn_fs_bdb__put_txn(svn_fs_t *fs,
+ const transaction_t *txn,
+ const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *NAMES_P to an array of const char * IDs (unfinished
+ transactions in FS) as part of TRAIL. Allocate the array and the
+ names in POOL, and use POOL for any temporary allocations. */
+svn_error_t *svn_fs_bdb__get_txn_list(apr_array_header_t **names_p,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_TXN_TABLE_H */
diff --git a/subversion/libsvn_fs_base/bdb/uuids-table.c b/subversion/libsvn_fs_base/bdb/uuids-table.c
new file mode 100644
index 0000000..0481894
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/uuids-table.c
@@ -0,0 +1,149 @@
+/* uuids-table.c : operations on the `uuids' table
+ *
+ * ====================================================================
+ * 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_uuid.h>
+
+#include "bdb_compat.h"
+#include "svn_fs.h"
+#include "../fs.h"
+#include "../err.h"
+#include "dbt.h"
+#include "../trail.h"
+#include "../../libsvn_fs/fs-loader.h"
+#include "bdb-err.h"
+#include "uuids-table.h"
+
+#include "svn_private_config.h"
+
+
+/*** Creating and opening the uuids table.
+ When the table is created, the repository's uuid is
+ generated and stored as record #1. ***/
+
+int
+svn_fs_bdb__open_uuids_table(DB **uuids_p,
+ DB_ENV *env,
+ svn_boolean_t create)
+{
+ const u_int32_t open_flags = (create ? (DB_CREATE | DB_EXCL) : 0);
+ DB *uuids;
+ int error;
+
+ BDB_ERR(svn_fs_bdb__check_version());
+ BDB_ERR(db_create(&uuids, env, 0));
+ BDB_ERR(uuids->set_re_len(uuids, APR_UUID_FORMATTED_LENGTH));
+
+ error = (uuids->open)(SVN_BDB_OPEN_PARAMS(uuids, NULL),
+ "uuids", 0, DB_RECNO,
+ open_flags, 0666);
+
+ /* This is a temporary compatibility check; it creates the
+ UUIDs table if one does not already exist. */
+ if (error == ENOENT && (! create))
+ {
+ BDB_ERR(uuids->close(uuids, 0));
+ return svn_fs_bdb__open_uuids_table(uuids_p, env, TRUE);
+ }
+
+ BDB_ERR(error);
+
+ if (create)
+ {
+ char buffer[APR_UUID_FORMATTED_LENGTH + 1];
+ DBT key, value;
+ apr_uuid_t uuid;
+ int recno = 0;
+
+ svn_fs_base__clear_dbt(&key);
+ key.data = &recno;
+ key.size = sizeof(recno);
+ key.ulen = key.size;
+ key.flags |= DB_DBT_USERMEM;
+
+ svn_fs_base__clear_dbt(&value);
+ value.data = buffer;
+ value.size = sizeof(buffer) - 1;
+
+ apr_uuid_get(&uuid);
+ apr_uuid_format(buffer, &uuid);
+
+ BDB_ERR(uuids->put(uuids, 0, &key, &value, DB_APPEND));
+ }
+
+ *uuids_p = uuids;
+ return 0;
+}
+
+svn_error_t *svn_fs_bdb__get_uuid(svn_fs_t *fs,
+ int idx,
+ const char **uuid,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ char buffer[APR_UUID_FORMATTED_LENGTH + 1];
+ DB *uuids = bfd->uuids;
+ DBT key;
+ DBT value;
+
+ svn_fs_base__clear_dbt(&key);
+ key.data = &idx;
+ key.size = sizeof(idx);
+
+ svn_fs_base__clear_dbt(&value);
+ value.data = buffer;
+ value.size = sizeof(buffer) - 1;
+ value.ulen = value.size;
+ value.flags |= DB_DBT_USERMEM;
+
+ svn_fs_base__trail_debug(trail, "uuids", "get");
+ SVN_ERR(BDB_WRAP(fs, N_("get repository uuid"),
+ uuids->get(uuids, trail->db_txn, &key, &value, 0)));
+
+ *uuid = apr_pstrmemdup(pool, value.data, value.size);
+
+ return SVN_NO_ERROR;
+}
+
+svn_error_t *svn_fs_bdb__set_uuid(svn_fs_t *fs,
+ int idx,
+ const char *uuid,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DB *uuids = bfd->uuids;
+ DBT key;
+ DBT value;
+
+ svn_fs_base__clear_dbt(&key);
+ key.data = &idx;
+ key.size = sizeof(idx);
+
+ svn_fs_base__clear_dbt(&value);
+ value.size = (u_int32_t) strlen(uuid);
+ value.data = apr_pstrmemdup(pool, uuid, value.size + 1);
+
+ svn_fs_base__trail_debug(trail, "uuids", "put");
+ return BDB_WRAP(fs, N_("set repository uuid"),
+ uuids->put(uuids, trail->db_txn, &key, &value, 0));
+}
diff --git a/subversion/libsvn_fs_base/bdb/uuids-table.h b/subversion/libsvn_fs_base/bdb/uuids-table.h
new file mode 100644
index 0000000..f6d38df
--- /dev/null
+++ b/subversion/libsvn_fs_base/bdb/uuids-table.h
@@ -0,0 +1,69 @@
+/* uuids-table.h : internal interface to `uuids' table
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_UUIDS_TABLE_H
+#define SVN_LIBSVN_FS_UUIDS_TABLE_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_io.h"
+#include "svn_fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* Open a `uuids' table in @a env.
+ *
+ * Open a `uuids' table in @a env. If @a create is non-zero, create
+ * one if it doesn't exist. Set @a *uuids_p to the new table.
+ * Return a Berkeley DB error code.
+ */
+int svn_fs_bdb__open_uuids_table(DB **uuids_p,
+ DB_ENV *env,
+ svn_boolean_t create);
+
+/* Get the UUID at index @a idx in the uuids table within @a fs,
+ * storing the result in @a *uuid.
+ */
+svn_error_t *svn_fs_bdb__get_uuid(svn_fs_t *fs,
+ int idx,
+ const char **uuid,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Set the UUID at index @a idx in the uuids table within @a fs
+ * to @a uuid.
+ */
+svn_error_t *svn_fs_bdb__set_uuid(svn_fs_t *fs,
+ int idx,
+ const char *uuid,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_UUIDS_TABLE_H */
diff --git a/subversion/libsvn_fs_base/dag.c b/subversion/libsvn_fs_base/dag.c
new file mode 100644
index 0000000..510ccbb
--- /dev/null
+++ b/subversion/libsvn_fs_base/dag.c
@@ -0,0 +1,1758 @@
+/* dag.c : DAG-like interface filesystem, private to libsvn_fs
+ *
+ * ====================================================================
+ * 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 "svn_path.h"
+#include "svn_time.h"
+#include "svn_error.h"
+#include "svn_fs.h"
+#include "svn_hash.h"
+#include "svn_props.h"
+#include "svn_pools.h"
+
+#include "dag.h"
+#include "err.h"
+#include "fs.h"
+#include "key-gen.h"
+#include "node-rev.h"
+#include "trail.h"
+#include "reps-strings.h"
+#include "revs-txns.h"
+#include "id.h"
+
+#include "util/fs_skels.h"
+
+#include "bdb/txn-table.h"
+#include "bdb/rev-table.h"
+#include "bdb/nodes-table.h"
+#include "bdb/copies-table.h"
+#include "bdb/reps-table.h"
+#include "bdb/strings-table.h"
+#include "bdb/checksum-reps-table.h"
+#include "bdb/changes-table.h"
+#include "bdb/node-origins-table.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_fs_util.h"
+#include "private/svn_fspath.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "svn_private_config.h"
+
+
+/* Initializing a filesystem. */
+
+struct dag_node_t
+{
+ /*** NOTE: Keeping in-memory representations of disk data that can
+ be changed by other accessors is a nasty business. Such
+ representations are basically a cache with some pretty complex
+ invalidation rules. For example, the "node revision"
+ associated with a DAG node ID can look completely different to
+ a process that has modified that information as part of a
+ Berkeley DB transaction than it does to some other process.
+ That said, there are some aspects of a "node revision" which
+ never change, like its 'id' or 'kind'. Our best bet is to
+ limit ourselves to exposing outside of this interface only
+ those immutable aspects of a DAG node representation. ***/
+
+ /* The filesystem this dag node came from. */
+ svn_fs_t *fs;
+
+ /* The node revision ID for this dag node. */
+ svn_fs_id_t *id;
+
+ /* The node's type (file, dir, etc.) */
+ svn_node_kind_t kind;
+
+ /* the path at which this node was created. */
+ const char *created_path;
+};
+
+
+
+/* Trivial helper/accessor functions. */
+svn_node_kind_t svn_fs_base__dag_node_kind(dag_node_t *node)
+{
+ return node->kind;
+}
+
+
+const svn_fs_id_t *
+svn_fs_base__dag_get_id(dag_node_t *node)
+{
+ return node->id;
+}
+
+
+const char *
+svn_fs_base__dag_get_created_path(dag_node_t *node)
+{
+ return node->created_path;
+}
+
+
+svn_fs_t *
+svn_fs_base__dag_get_fs(dag_node_t *node)
+{
+ return node->fs;
+}
+
+
+svn_boolean_t svn_fs_base__dag_check_mutable(dag_node_t *node,
+ const char *txn_id)
+{
+ return (strcmp(svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)),
+ txn_id) == 0);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_node(dag_node_t **node,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *new_node;
+ node_revision_t *noderev;
+
+ /* Construct the node. */
+ new_node = apr_pcalloc(pool, sizeof(*new_node));
+ new_node->fs = fs;
+ new_node->id = svn_fs_base__id_copy(id, pool);
+
+ /* Grab the contents so we can cache some of the immutable parts of it. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool));
+
+ /* Initialize the KIND and CREATED_PATH attributes */
+ new_node->kind = noderev->kind;
+ new_node->created_path = noderev->created_path;
+
+ /* Return a fresh new node */
+ *node = new_node;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_revision(svn_revnum_t *rev,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ /* Use the txn ID from the NODE's id to look up the transaction and
+ get its revision number. */
+ return svn_fs_base__txn_get_revision
+ (rev, svn_fs_base__dag_get_fs(node),
+ svn_fs_base__id_txn_id(svn_fs_base__dag_get_id(node)), trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_predecessor_id(const svn_fs_id_t **id_p,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
+ trail, pool));
+ *id_p = noderev->predecessor_id;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_predecessor_count(int *count,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
+ trail, pool));
+ *count = noderev->predecessor_count;
+ return SVN_NO_ERROR;
+}
+
+
+/* Trail body for svn_fs_base__dag_init_fs. */
+static svn_error_t *
+txn_body_dag_init_fs(void *baton,
+ trail_t *trail)
+{
+ node_revision_t noderev;
+ revision_t revision;
+ svn_revnum_t rev = SVN_INVALID_REVNUM;
+ svn_fs_t *fs = trail->fs;
+ svn_string_t date;
+ const char *txn_id;
+ const char *copy_id;
+ svn_fs_id_t *root_id = svn_fs_base__id_create("0", "0", "0", trail->pool);
+
+ /* Create empty root directory with node revision 0.0.0. */
+ memset(&noderev, 0, sizeof(noderev));
+ noderev.kind = svn_node_dir;
+ noderev.created_path = "/";
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, root_id, &noderev,
+ trail, trail->pool));
+
+ /* Create a new transaction (better have an id of "0") */
+ SVN_ERR(svn_fs_bdb__create_txn(&txn_id, fs, root_id, trail, trail->pool));
+ if (strcmp(txn_id, "0"))
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: initial transaction id not '0' in filesystem '%s'"),
+ fs->path);
+
+ /* Create a default copy (better have an id of "0") */
+ SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, trail->pool));
+ if (strcmp(copy_id, "0"))
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: initial copy id not '0' in filesystem '%s'"), fs->path);
+ SVN_ERR(svn_fs_bdb__create_copy(fs, copy_id, NULL, NULL, root_id,
+ copy_kind_real, trail, trail->pool));
+
+ /* Link it into filesystem revision 0. */
+ revision.txn_id = txn_id;
+ SVN_ERR(svn_fs_bdb__put_rev(&rev, fs, &revision, trail, trail->pool));
+ if (rev != 0)
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: initial revision number "
+ "is not '0' in filesystem '%s'"), fs->path);
+
+ /* Promote our transaction to a "committed" transaction. */
+ SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, rev,
+ trail, trail->pool));
+
+ /* Set a date on revision 0. */
+ date.data = svn_time_to_cstring(apr_time_now(), trail->pool);
+ date.len = strlen(date.data);
+ return svn_fs_base__set_rev_prop(fs, 0, SVN_PROP_REVISION_DATE, NULL, &date,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_init_fs(svn_fs_t *fs)
+{
+ return svn_fs_base__retry_txn(fs, txn_body_dag_init_fs, NULL,
+ TRUE, fs->pool);
+}
+
+
+
+/*** Directory node functions ***/
+
+/* Some of these are helpers for functions outside this section. */
+
+/* Given directory NODEREV in FS, set *ENTRIES_P to its entries list
+ hash, as part of TRAIL, or to NULL if NODEREV has no entries. The
+ entries list will be allocated in POOL, and the entries in that
+ list will not have interesting value in their 'kind' fields. If
+ NODEREV is not a directory, return the error SVN_ERR_FS_NOT_DIRECTORY. */
+static svn_error_t *
+get_dir_entries(apr_hash_t **entries_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ apr_hash_t *entries = NULL;
+ apr_hash_index_t *hi;
+ svn_string_t entries_raw;
+ svn_skel_t *entries_skel;
+
+ /* Error if this is not a directory. */
+ if (noderev->kind != svn_node_dir)
+ return svn_error_create
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Attempted to get entries of a non-directory node"));
+
+ /* If there's a DATA-KEY, there might be entries to fetch. */
+ if (noderev->data_key)
+ {
+ /* Now we have a rep, follow through to get the entries. */
+ SVN_ERR(svn_fs_base__rep_contents(&entries_raw, fs, noderev->data_key,
+ trail, pool));
+ entries_skel = svn_skel__parse(entries_raw.data, entries_raw.len, pool);
+
+ /* Were there entries? Make a hash from them. */
+ if (entries_skel)
+ SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel,
+ pool));
+ }
+
+ /* No hash? No problem. */
+ *entries_p = NULL;
+ if (! entries)
+ return SVN_NO_ERROR;
+
+ /* Else, convert the hash from a name->id mapping to a name->dirent one. */
+ *entries_p = apr_hash_make(pool);
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_ssize_t klen;
+ void *val;
+ svn_fs_dirent_t *dirent = apr_palloc(pool, sizeof(*dirent));
+
+ /* KEY will be the entry name in ancestor, VAL the id. */
+ apr_hash_this(hi, &key, &klen, &val);
+ dirent->name = key;
+ dirent->id = val;
+ dirent->kind = svn_node_unknown;
+ apr_hash_set(*entries_p, key, klen, dirent);
+ }
+
+ /* Return our findings. */
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *ID_P to the node-id for entry NAME in PARENT, as part of
+ TRAIL. If no such entry, set *ID_P to NULL but do not error. The
+ entry is allocated in POOL or in the same pool as PARENT;
+ the caller should copy if it cares. */
+static svn_error_t *
+dir_entry_id_from_node(const svn_fs_id_t **id_p,
+ dag_node_t *parent,
+ const char *name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ apr_hash_t *entries;
+ svn_fs_dirent_t *dirent;
+
+ SVN_ERR(svn_fs_base__dag_dir_entries(&entries, parent, trail, pool));
+ if (entries)
+ dirent = svn_hash_gets(entries, name);
+ else
+ dirent = NULL;
+
+ *id_p = dirent ? dirent->id : NULL;
+ return SVN_NO_ERROR;
+}
+
+
+/* Add or set in PARENT a directory entry NAME pointing to ID.
+ Allocations are done in TRAIL.
+
+ Assumptions:
+ - PARENT is a mutable directory.
+ - ID does not refer to an ancestor of parent
+ - NAME is a single path component
+*/
+static svn_error_t *
+set_entry(dag_node_t *parent,
+ const char *name,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *parent_noderev;
+ const char *rep_key, *mutable_rep_key;
+ apr_hash_t *entries = NULL;
+ svn_stream_t *wstream;
+ apr_size_t len;
+ svn_string_t raw_entries;
+ svn_stringbuf_t *raw_entries_buf;
+ svn_skel_t *entries_skel;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(parent);
+
+ /* Get the parent's node-revision. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id,
+ trail, pool));
+ rep_key = parent_noderev->data_key;
+ SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
+ fs, txn_id, trail, pool));
+
+ /* If the parent node already pointed at a mutable representation,
+ we don't need to do anything. But if it didn't, either because
+ the parent didn't refer to any rep yet or because it referred to
+ an immutable one, we must make the parent refer to the mutable
+ rep we just created. */
+ if (! svn_fs_base__same_keys(rep_key, mutable_rep_key))
+ {
+ parent_noderev->data_key = mutable_rep_key;
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev,
+ trail, pool));
+ }
+
+ /* If the new representation inherited nothing, start a new entries
+ list for it. Else, go read its existing entries list. */
+ if (rep_key)
+ {
+ SVN_ERR(svn_fs_base__rep_contents(&raw_entries, fs, rep_key,
+ trail, pool));
+ entries_skel = svn_skel__parse(raw_entries.data, raw_entries.len, pool);
+ if (entries_skel)
+ SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel,
+ pool));
+ }
+
+ /* If we still have no ENTRIES hash, make one here. */
+ if (! entries)
+ entries = apr_hash_make(pool);
+
+ /* Now, add our new entry to the entries list. */
+ svn_hash_sets(entries, name, id);
+
+ /* Finally, replace the old entries list with the new one. */
+ SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries,
+ pool));
+ raw_entries_buf = svn_skel__unparse(entries_skel, pool);
+ SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs,
+ mutable_rep_key, txn_id,
+ TRUE, trail, pool));
+ len = raw_entries_buf->len;
+ SVN_ERR(svn_stream_write(wstream, raw_entries_buf->data, &len));
+ return svn_stream_close(wstream);
+}
+
+
+/* Make a new entry named NAME in PARENT, as part of TRAIL. If IS_DIR
+ is true, then the node revision the new entry points to will be a
+ directory, else it will be a file. The new node will be allocated
+ in POOL. PARENT must be mutable, and must not have an entry
+ named NAME. */
+static svn_error_t *
+make_entry(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ svn_boolean_t is_dir,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *new_node_id;
+ node_revision_t new_noderev;
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ _("Attempted to create a node with an illegal name '%s'"), name);
+
+ /* Make sure that parent is a directory */
+ if (parent->kind != svn_node_dir)
+ return svn_error_create
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Attempted to create entry in non-directory parent"));
+
+ /* Check that the parent is mutable. */
+ if (! svn_fs_base__dag_check_mutable(parent, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to clone child of non-mutable node"));
+
+ /* Check that parent does not already have an entry named NAME. */
+ SVN_ERR(dir_entry_id_from_node(&new_node_id, parent, name, trail, pool));
+ if (new_node_id)
+ return svn_error_createf
+ (SVN_ERR_FS_ALREADY_EXISTS, NULL,
+ _("Attempted to create entry that already exists"));
+
+ /* Create the new node's NODE-REVISION */
+ memset(&new_noderev, 0, sizeof(new_noderev));
+ new_noderev.kind = is_dir ? svn_node_dir : svn_node_file;
+ new_noderev.created_path = svn_fspath__join(parent_path, name, pool);
+ SVN_ERR(svn_fs_base__create_node
+ (&new_node_id, svn_fs_base__dag_get_fs(parent), &new_noderev,
+ svn_fs_base__id_copy_id(svn_fs_base__dag_get_id(parent)),
+ txn_id, trail, pool));
+
+ /* Create a new dag_node_t for our new node */
+ SVN_ERR(svn_fs_base__dag_get_node(child_p,
+ svn_fs_base__dag_get_fs(parent),
+ new_node_id, trail, pool));
+
+ /* We can safely call set_entry because we already know that
+ PARENT is mutable, and we just created CHILD, so we know it has
+ no ancestors (therefore, PARENT cannot be an ancestor of CHILD) */
+ return set_entry(parent, name, svn_fs_base__dag_get_id(*child_p),
+ txn_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_dir_entries(apr_hash_t **entries,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
+ trail, pool));
+ return get_dir_entries(entries, node->fs, noderev, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_set_entry(dag_node_t *node,
+ const char *entry_name,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ /* Check it's a directory. */
+ if (node->kind != svn_node_dir)
+ return svn_error_create
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Attempted to set entry in non-directory node"));
+
+ /* Check it's mutable. */
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ return svn_error_create
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to set entry in immutable node"));
+
+ return set_entry(node, entry_name, id, txn_id, trail, pool);
+}
+
+
+
+/*** Proplists. ***/
+
+svn_error_t *
+svn_fs_base__dag_get_proplist(apr_hash_t **proplist_p,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ apr_hash_t *proplist = NULL;
+ svn_string_t raw_proplist;
+ svn_skel_t *proplist_skel;
+
+ /* Go get a fresh NODE-REVISION for this node. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, node->fs, node->id,
+ trail, pool));
+
+ /* Get property key (returning early if there isn't one) . */
+ if (! noderev->prop_key)
+ {
+ *proplist_p = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Get the string associated with the property rep, parsing it as a
+ skel, and then attempt to parse *that* into a property hash. */
+ SVN_ERR(svn_fs_base__rep_contents(&raw_proplist,
+ svn_fs_base__dag_get_fs(node),
+ noderev->prop_key, trail, pool));
+ proplist_skel = svn_skel__parse(raw_proplist.data, raw_proplist.len, pool);
+ if (proplist_skel)
+ SVN_ERR(svn_skel__parse_proplist(&proplist, proplist_skel, pool));
+
+ *proplist_p = proplist;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_set_proplist(dag_node_t *node,
+ const apr_hash_t *proplist,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ const char *rep_key, *mutable_rep_key;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
+ svn_stream_t *wstream;
+ apr_size_t len;
+ svn_skel_t *proplist_skel;
+ svn_stringbuf_t *raw_proplist_buf;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* Sanity check: this node better be mutable! */
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ {
+ svn_string_t *idstr = svn_fs_base__id_unparse(node->id, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Can't set proplist on *immutable* node-revision %s"),
+ idstr->data);
+ }
+
+ /* Go get a fresh NODE-REVISION for this node. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, node->id,
+ trail, pool));
+ rep_key = noderev->prop_key;
+
+ /* Flatten the proplist into a string. */
+ SVN_ERR(svn_skel__unparse_proplist(&proplist_skel, proplist, pool));
+ raw_proplist_buf = svn_skel__unparse(proplist_skel, pool);
+
+ /* If this repository supports representation sharing, and the
+ resulting property list is exactly the same as another string in
+ the database, just use the previously existing string and get
+ outta here. */
+ if (bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
+ {
+ svn_error_t *err;
+ const char *dup_rep_key;
+ svn_checksum_t *checksum;
+
+ SVN_ERR(svn_checksum(&checksum, svn_checksum_sha1, raw_proplist_buf->data,
+ raw_proplist_buf->len, pool));
+
+ err = svn_fs_bdb__get_checksum_rep(&dup_rep_key, fs, checksum,
+ trail, pool);
+ if (! err)
+ {
+ if (noderev->prop_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key,
+ txn_id, trail, pool));
+ noderev->prop_key = dup_rep_key;
+ return svn_fs_bdb__put_node_revision(fs, node->id, noderev,
+ trail, pool);
+ }
+ else if (err)
+ {
+ if (err->apr_err != SVN_ERR_FS_NO_SUCH_CHECKSUM_REP)
+ return svn_error_trace(err);
+
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ }
+
+ /* Get a mutable version of this rep (updating the node revision if
+ this isn't a NOOP) */
+ SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
+ fs, txn_id, trail, pool));
+ if (! svn_fs_base__same_keys(mutable_rep_key, rep_key))
+ {
+ noderev->prop_key = mutable_rep_key;
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, node->id, noderev,
+ trail, pool));
+ }
+
+ /* Replace the old property list with the new one. */
+ SVN_ERR(svn_fs_base__rep_contents_write_stream(&wstream, fs,
+ mutable_rep_key, txn_id,
+ TRUE, trail, pool));
+ len = raw_proplist_buf->len;
+ SVN_ERR(svn_stream_write(wstream, raw_proplist_buf->data, &len));
+ SVN_ERR(svn_stream_close(wstream));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Roots. ***/
+
+svn_error_t *
+svn_fs_base__dag_revision_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *root_id;
+
+ SVN_ERR(svn_fs_base__rev_get_root(&root_id, fs, rev, trail, pool));
+ return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_txn_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *root_id, *ignored;
+
+ SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &ignored, fs, txn_id,
+ trail, pool));
+ return svn_fs_base__dag_get_node(node_p, fs, root_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_txn_base_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *base_root_id, *ignored;
+
+ SVN_ERR(svn_fs_base__get_txn_ids(&ignored, &base_root_id, fs, txn_id,
+ trail, pool));
+ return svn_fs_base__dag_get_node(node_p, fs, base_root_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_clone_child(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *cur_entry; /* parent's current entry named NAME */
+ const svn_fs_id_t *new_node_id; /* node id we'll put into NEW_NODE */
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(parent);
+
+ /* First check that the parent is mutable. */
+ if (! svn_fs_base__dag_check_mutable(parent, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to clone child of non-mutable node"));
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ _("Attempted to make a child clone with an illegal name '%s'"), name);
+
+ /* Find the node named NAME in PARENT's entries list if it exists. */
+ SVN_ERR(svn_fs_base__dag_open(&cur_entry, parent, name, trail, pool));
+
+ /* Check for mutability in the node we found. If it's mutable, we
+ don't need to clone it. */
+ if (svn_fs_base__dag_check_mutable(cur_entry, txn_id))
+ {
+ /* This has already been cloned */
+ new_node_id = cur_entry->id;
+ }
+ else
+ {
+ node_revision_t *noderev;
+
+ /* Go get a fresh NODE-REVISION for current child node. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, cur_entry->id,
+ trail, pool));
+
+ /* Do the clone thingy here. */
+ noderev->predecessor_id = cur_entry->id;
+ if (noderev->predecessor_count != -1)
+ noderev->predecessor_count++;
+ noderev->created_path = svn_fspath__join(parent_path, name, pool);
+ SVN_ERR(svn_fs_base__create_successor(&new_node_id, fs, cur_entry->id,
+ noderev, copy_id, txn_id,
+ trail, pool));
+
+ /* Replace the ID in the parent's ENTRY list with the ID which
+ refers to the mutable clone of this child. */
+ SVN_ERR(set_entry(parent, name, new_node_id, txn_id, trail, pool));
+ }
+
+ /* Initialize the youngster. */
+ return svn_fs_base__dag_get_node(child_p, fs, new_node_id, trail, pool);
+}
+
+
+
+svn_error_t *
+svn_fs_base__dag_clone_root(dag_node_t **root_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *base_root_id, *root_id;
+ node_revision_t *noderev;
+
+ /* Get the node ID's of the root directories of the transaction and
+ its base revision. */
+ SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &base_root_id, fs, txn_id,
+ trail, pool));
+
+ /* Oh, give me a clone...
+ (If they're the same, we haven't cloned the transaction's root
+ directory yet.) */
+ if (svn_fs_base__id_eq(root_id, base_root_id))
+ {
+ const char *base_copy_id = svn_fs_base__id_copy_id(base_root_id);
+
+ /* Of my own flesh and bone...
+ (Get the NODE-REVISION for the base node, and then write
+ it back out as the clone.) */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, base_root_id,
+ trail, pool));
+
+ /* With its Y-chromosome changed to X...
+ (Store it with an updated predecessor count.) */
+ /* ### TODO: Does it even makes sense to have a different copy id for
+ the root node? That is, does this function need a copy_id
+ passed in? */
+ noderev->predecessor_id = svn_fs_base__id_copy(base_root_id, pool);
+ if (noderev->predecessor_count != -1)
+ noderev->predecessor_count++;
+ SVN_ERR(svn_fs_base__create_successor(&root_id, fs, base_root_id,
+ noderev, base_copy_id,
+ txn_id, trail, pool));
+
+ /* ... And when it is grown
+ * Then my own little clone
+ * Will be of the opposite sex!
+ */
+ SVN_ERR(svn_fs_base__set_txn_root(fs, txn_id, root_id, trail, pool));
+ }
+
+ /*
+ * (Sung to the tune of "Home, Home on the Range", with thanks to
+ * Randall Garrett and Isaac Asimov.)
+ */
+
+ /* One way or another, root_id now identifies a cloned root node. */
+ return svn_fs_base__dag_get_node(root_p, fs, root_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_delete(dag_node_t *parent,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *parent_noderev;
+ const char *rep_key, *mutable_rep_key;
+ apr_hash_t *entries = NULL;
+ svn_skel_t *entries_skel;
+ svn_fs_t *fs = parent->fs;
+ svn_string_t str;
+ svn_fs_id_t *id = NULL;
+ dag_node_t *node;
+
+ /* Make sure parent is a directory. */
+ if (parent->kind != svn_node_dir)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_DIRECTORY, NULL,
+ _("Attempted to delete entry '%s' from *non*-directory node"), name);
+
+ /* Make sure parent is mutable. */
+ if (! svn_fs_base__dag_check_mutable(parent, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to delete entry '%s' from immutable directory node"),
+ name);
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ _("Attempted to delete a node with an illegal name '%s'"), name);
+
+ /* Get a fresh NODE-REVISION for the parent node. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&parent_noderev, fs, parent->id,
+ trail, pool));
+
+ /* Get the key for the parent's entries list (data) representation. */
+ rep_key = parent_noderev->data_key;
+
+ /* No REP_KEY means no representation, and no representation means
+ no data, and no data means no entries...there's nothing here to
+ delete! */
+ if (! rep_key)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
+ _("Delete failed: directory has no entry '%s'"), name);
+
+ /* Ensure we have a key to a mutable representation of the entries
+ list. We'll have to update the NODE-REVISION if it points to an
+ immutable version. */
+ SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, rep_key,
+ fs, txn_id, trail, pool));
+ if (! svn_fs_base__same_keys(mutable_rep_key, rep_key))
+ {
+ parent_noderev->data_key = mutable_rep_key;
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, parent->id, parent_noderev,
+ trail, pool));
+ }
+
+ /* Read the representation, then use it to get the string that holds
+ the entries list. Parse that list into a skel, and parse *that*
+ into a hash. */
+
+ SVN_ERR(svn_fs_base__rep_contents(&str, fs, rep_key, trail, pool));
+ entries_skel = svn_skel__parse(str.data, str.len, pool);
+ if (entries_skel)
+ SVN_ERR(svn_fs_base__parse_entries_skel(&entries, entries_skel, pool));
+
+ /* Find NAME in the ENTRIES skel. */
+ if (entries)
+ id = svn_hash_gets(entries, name);
+
+ /* If we never found ID in ENTRIES (perhaps because there are no
+ ENTRIES, perhaps because ID just isn't in the existing ENTRIES
+ ... it doesn't matter), return an error. */
+ if (! id)
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_ENTRY, NULL,
+ _("Delete failed: directory has no entry '%s'"), name);
+
+ /* Use the ID of this ENTRY to get the entry's node. */
+ SVN_ERR(svn_fs_base__dag_get_node(&node, svn_fs_base__dag_get_fs(parent),
+ id, trail, pool));
+
+ /* If mutable, remove it and any mutable children from db. */
+ SVN_ERR(svn_fs_base__dag_delete_if_mutable(parent->fs, id, txn_id,
+ trail, pool));
+
+ /* Remove this entry from its parent's entries list. */
+ svn_hash_sets(entries, name, NULL);
+
+ /* Replace the old entries list with the new one. */
+ {
+ svn_stream_t *ws;
+ svn_stringbuf_t *unparsed_entries;
+ apr_size_t len;
+
+ SVN_ERR(svn_fs_base__unparse_entries_skel(&entries_skel, entries, pool));
+ unparsed_entries = svn_skel__unparse(entries_skel, pool);
+ SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
+ txn_id, TRUE, trail,
+ pool));
+ len = unparsed_entries->len;
+ SVN_ERR(svn_stream_write(ws, unparsed_entries->data, &len));
+ SVN_ERR(svn_stream_close(ws));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_remove_node(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+ node_revision_t *noderev;
+
+ /* Fetch the node. */
+ SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool));
+
+ /* If immutable, do nothing and return immediately. */
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted removal of immutable node"));
+
+ /* Get a fresh node-revision. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, id, trail, pool));
+
+ /* Delete any mutable property representation. */
+ if (noderev->prop_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->prop_key,
+ txn_id, trail, pool));
+
+ /* Delete any mutable data representation. */
+ if (noderev->data_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->data_key,
+ txn_id, trail, pool));
+
+ /* Delete any mutable edit representation (files only). */
+ if (noderev->edit_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
+ txn_id, trail, pool));
+
+ /* Delete the node revision itself. */
+ return svn_fs_base__delete_node_revision(fs, id,
+ noderev->predecessor_id == NULL,
+ trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_delete_if_mutable(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *node;
+
+ /* Get the node. */
+ SVN_ERR(svn_fs_base__dag_get_node(&node, fs, id, trail, pool));
+
+ /* If immutable, do nothing and return immediately. */
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ return SVN_NO_ERROR;
+
+ /* Else it's mutable. Recurse on directories... */
+ if (node->kind == svn_node_dir)
+ {
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+
+ /* Loop over hash entries */
+ SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, pool));
+ if (entries)
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ void *val;
+ svn_fs_dirent_t *dirent;
+
+ apr_hash_this(hi, NULL, NULL, &val);
+ dirent = val;
+ SVN_ERR(svn_fs_base__dag_delete_if_mutable(fs, dirent->id,
+ txn_id, trail,
+ subpool));
+ }
+ }
+ }
+
+ /* ... then delete the node itself, any mutable representations and
+ strings it points to, and possibly its node-origins record. */
+ return svn_fs_base__dag_remove_node(fs, id, txn_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_make_file(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ /* Call our little helper function */
+ return make_entry(child_p, parent, parent_path, name, FALSE,
+ txn_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_make_dir(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ /* Call our little helper function */
+ return make_entry(child_p, parent, parent_path, name, TRUE,
+ txn_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_contents(svn_stream_t **contents,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ /* Make sure our node is a file. */
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ _("Attempted to get textual contents of a *non*-file node"));
+
+ /* Go get a fresh node-revision for FILE. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
+ trail, pool));
+
+ /* Our job is to _return_ a stream on the file's contents, so the
+ stream has to be trail-independent. Here, we pass NULL to tell
+ the stream that we're not providing it a trail that lives across
+ reads. This means the stream will do each read in a one-off,
+ temporary trail. */
+ return svn_fs_base__rep_contents_read_stream(contents, file->fs,
+ noderev->data_key,
+ FALSE, trail, pool);
+
+ /* Note that we're not registering any `close' func, because there's
+ nothing to cleanup outside of our trail. When the trail is
+ freed, the stream/baton will be too. */
+}
+
+
+svn_error_t *
+svn_fs_base__dag_file_length(svn_filesize_t *length,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ /* Make sure our node is a file. */
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ _("Attempted to get length of a *non*-file node"));
+
+ /* Go get a fresh node-revision for FILE, and . */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
+ trail, pool));
+ if (noderev->data_key)
+ SVN_ERR(svn_fs_base__rep_contents_size(length, file->fs,
+ noderev->data_key, trail, pool));
+ else
+ *length = 0;
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_file_checksum(svn_checksum_t **checksum,
+ svn_checksum_kind_t checksum_kind,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ _("Attempted to get checksum of a *non*-file node"));
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, file->fs, file->id,
+ trail, pool));
+ if (! noderev->data_key)
+ {
+ *checksum = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ if (checksum_kind == svn_checksum_md5)
+ return svn_fs_base__rep_contents_checksums(checksum, NULL, file->fs,
+ noderev->data_key,
+ trail, pool);
+ else if (checksum_kind == svn_checksum_sha1)
+ return svn_fs_base__rep_contents_checksums(NULL, checksum, file->fs,
+ noderev->data_key,
+ trail, pool);
+ else
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_get_edit_stream(svn_stream_t **contents,
+ dag_node_t *file,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = file->fs; /* just for nicer indentation */
+ node_revision_t *noderev;
+ const char *mutable_rep_key;
+ svn_stream_t *ws;
+
+ /* Make sure our node is a file. */
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ _("Attempted to set textual contents of a *non*-file node"));
+
+ /* Make sure our node is mutable. */
+ if (! svn_fs_base__dag_check_mutable(file, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to set textual contents of an immutable node"));
+
+ /* Get the node revision. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
+ trail, pool));
+
+ /* If this node already has an EDIT-DATA-KEY, destroy the data
+ associated with that key. */
+ if (noderev->edit_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, noderev->edit_key,
+ txn_id, trail, pool));
+
+ /* Now, let's ensure that we have a new EDIT-DATA-KEY available for
+ use. */
+ SVN_ERR(svn_fs_base__get_mutable_rep(&mutable_rep_key, NULL, fs,
+ txn_id, trail, pool));
+
+ /* We made a new rep, so update the node revision. */
+ noderev->edit_key = mutable_rep_key;
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev,
+ trail, pool));
+
+ /* Return a writable stream with which to set new contents. */
+ SVN_ERR(svn_fs_base__rep_contents_write_stream(&ws, fs, mutable_rep_key,
+ txn_id, FALSE, trail,
+ pool));
+ *contents = ws;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_fs_base__dag_finalize_edits(dag_node_t *file,
+ const svn_checksum_t *checksum,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = file->fs; /* just for nicer indentation */
+ node_revision_t *noderev;
+ const char *old_data_key, *new_data_key, *useless_data_key = NULL;
+ const char *data_key_uniquifier = NULL;
+ svn_checksum_t *md5_checksum, *sha1_checksum;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* Make sure our node is a file. */
+ if (file->kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FILE, NULL,
+ _("Attempted to set textual contents of a *non*-file node"));
+
+ /* Make sure our node is mutable. */
+ if (! svn_fs_base__dag_check_mutable(file, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted to set textual contents of an immutable node"));
+
+ /* Get the node revision. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, file->id,
+ trail, pool));
+
+ /* If this node has no EDIT-DATA-KEY, this is a no-op. */
+ if (! noderev->edit_key)
+ return SVN_NO_ERROR;
+
+ /* Get our representation's checksums. */
+ SVN_ERR(svn_fs_base__rep_contents_checksums(&md5_checksum, &sha1_checksum,
+ fs, noderev->edit_key,
+ trail, pool));
+
+ /* If our caller provided a checksum of the right kind to compare, do so. */
+ if (checksum)
+ {
+ svn_checksum_t *test_checksum;
+
+ if (checksum->kind == svn_checksum_md5)
+ test_checksum = md5_checksum;
+ else if (checksum->kind == svn_checksum_sha1)
+ test_checksum = sha1_checksum;
+ else
+ return svn_error_create(SVN_ERR_BAD_CHECKSUM_KIND, NULL, NULL);
+
+ if (! svn_checksum_match(checksum, test_checksum))
+ return svn_checksum_mismatch_err(checksum, test_checksum, pool,
+ _("Checksum mismatch on representation '%s'"),
+ noderev->edit_key);
+ }
+
+ /* Now, we want to delete the old representation and replace it with
+ the new. Of course, we don't actually delete anything until
+ everything is being properly referred to by the node-revision
+ skel.
+
+ Now, if the result of all this editing is that we've created a
+ representation that describes content already represented
+ immutably in our database, we don't even need to keep these edits.
+ We can simply point our data_key at that pre-existing
+ representation and throw away our work! In this situation,
+ though, we'll need a unique ID to help other code distinguish
+ between "the contents weren't touched" and "the contents were
+ touched but still look the same" (to state it oversimply). */
+ old_data_key = noderev->data_key;
+ if (sha1_checksum && bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
+ {
+ svn_error_t *err = svn_fs_bdb__get_checksum_rep(&new_data_key, fs,
+ sha1_checksum,
+ trail, pool);
+ if (! err)
+ {
+ useless_data_key = noderev->edit_key;
+ err = svn_fs_bdb__reserve_rep_reuse_id(&data_key_uniquifier,
+ trail->fs, trail, pool);
+ }
+ else if (err && (err->apr_err == SVN_ERR_FS_NO_SUCH_CHECKSUM_REP))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ new_data_key = noderev->edit_key;
+ }
+ SVN_ERR(err);
+ }
+ else
+ {
+ new_data_key = noderev->edit_key;
+ }
+
+ noderev->data_key = new_data_key;
+ noderev->data_key_uniquifier = data_key_uniquifier;
+ noderev->edit_key = NULL;
+
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, file->id, noderev, trail, pool));
+
+ /* Only *now* can we safely destroy the old representation (if it
+ even existed in the first place). */
+ if (old_data_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, old_data_key, txn_id,
+ trail, pool));
+
+ /* If we've got a discardable rep (probably because we ended up
+ re-using a preexisting one), throw out the discardable rep. */
+ if (useless_data_key)
+ SVN_ERR(svn_fs_base__delete_rep_if_mutable(fs, useless_data_key,
+ txn_id, trail, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+dag_node_t *
+svn_fs_base__dag_dup(dag_node_t *node,
+ apr_pool_t *pool)
+{
+ /* Allocate our new node. */
+ dag_node_t *new_node = apr_pcalloc(pool, sizeof(*new_node));
+
+ new_node->fs = node->fs;
+ new_node->id = svn_fs_base__id_copy(node->id, pool);
+ new_node->kind = node->kind;
+ new_node->created_path = apr_pstrdup(pool, node->created_path);
+ return new_node;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_open(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *node_id;
+
+ /* Ensure that NAME exists in PARENT's entry list. */
+ SVN_ERR(dir_entry_id_from_node(&node_id, parent, name, trail, pool));
+ if (! node_id)
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Attempted to open non-existent child node '%s'"), name);
+
+ /* Make sure that NAME is a single path component. */
+ if (! svn_path_is_single_path_component(name))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_SINGLE_PATH_COMPONENT, NULL,
+ _("Attempted to open node with an illegal name '%s'"), name);
+
+ /* Now get the node that was requested. */
+ return svn_fs_base__dag_get_node(child_p, svn_fs_base__dag_get_fs(parent),
+ node_id, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_copy(dag_node_t *to_node,
+ const char *entry,
+ dag_node_t *from_node,
+ svn_boolean_t preserve_history,
+ svn_revnum_t from_rev,
+ const char *from_path,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *id;
+
+ if (preserve_history)
+ {
+ node_revision_t *noderev;
+ const char *copy_id;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(from_node);
+ const svn_fs_id_t *src_id = svn_fs_base__dag_get_id(from_node);
+ const char *from_txn_id = NULL;
+
+ /* Make a copy of the original node revision. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, from_node->id,
+ trail, pool));
+
+ /* Reserve a copy ID for this new copy. */
+ SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, pool));
+
+ /* Create a successor with its predecessor pointing at the copy
+ source. */
+ noderev->predecessor_id = svn_fs_base__id_copy(src_id, pool);
+ if (noderev->predecessor_count != -1)
+ noderev->predecessor_count++;
+ noderev->created_path = svn_fspath__join
+ (svn_fs_base__dag_get_created_path(to_node), entry, pool);
+ SVN_ERR(svn_fs_base__create_successor(&id, fs, src_id, noderev,
+ copy_id, txn_id, trail, pool));
+
+ /* Translate FROM_REV into a transaction ID. */
+ SVN_ERR(svn_fs_base__rev_get_txn_id(&from_txn_id, fs, from_rev,
+ trail, pool));
+
+ /* Now that we've done the copy, we need to add the information
+ about the copy to the `copies' table, using the COPY_ID we
+ reserved above. */
+ SVN_ERR(svn_fs_bdb__create_copy
+ (fs, copy_id,
+ svn_fs__canonicalize_abspath(from_path, pool),
+ from_txn_id, id, copy_kind_real, trail, pool));
+
+ /* Finally, add the COPY_ID to the transaction's list of copies
+ so that, if this transaction is aborted, the `copies' table
+ entry we added above will be cleaned up. */
+ SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id, trail, pool));
+ }
+ else /* don't preserve history */
+ {
+ id = svn_fs_base__dag_get_id(from_node);
+ }
+
+ /* Set the entry in to_node to the new id. */
+ return svn_fs_base__dag_set_entry(to_node, entry, id, txn_id,
+ trail, pool);
+}
+
+
+
+/*** Deltification ***/
+
+/* Maybe change the representation identified by TARGET_REP_KEY to be
+ a delta against the representation identified by SOURCE_REP_KEY.
+ Some reasons why we wouldn't include:
+
+ - TARGET_REP_KEY and SOURCE_REP_KEY are the same key.
+
+ - TARGET_REP_KEY's representation isn't mutable in TXN_ID (if
+ TXN_ID is non-NULL).
+
+ - The delta provides less space savings that a fulltext (this is
+ a detail handled by lower logic layers, not this function).
+
+ Do this work in TRAIL, using POOL for necessary allocations.
+*/
+static svn_error_t *
+maybe_deltify_mutable_rep(const char *target_rep_key,
+ const char *source_rep_key,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ if (! (target_rep_key && source_rep_key
+ && (strcmp(target_rep_key, source_rep_key) != 0)))
+ return SVN_NO_ERROR;
+
+ if (txn_id)
+ {
+ representation_t *target_rep;
+ SVN_ERR(svn_fs_bdb__read_rep(&target_rep, trail->fs, target_rep_key,
+ trail, pool));
+ if (strcmp(target_rep->txn_id, txn_id) != 0)
+ return SVN_NO_ERROR;
+ }
+
+ return svn_fs_base__rep_deltify(trail->fs, target_rep_key, source_rep_key,
+ trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__dag_deltify(dag_node_t *target,
+ dag_node_t *source,
+ svn_boolean_t props_only,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *source_nr, *target_nr;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(target);
+
+ /* Get node revisions for the two nodes. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&target_nr, fs, target->id,
+ trail, pool));
+ SVN_ERR(svn_fs_bdb__get_node_revision(&source_nr, fs, source->id,
+ trail, pool));
+
+ /* If TARGET and SOURCE both have properties, and are not sharing a
+ property key, deltify TARGET's properties. */
+ SVN_ERR(maybe_deltify_mutable_rep(target_nr->prop_key, source_nr->prop_key,
+ txn_id, trail, pool));
+
+ /* If we are not only attending to properties, and if TARGET and
+ SOURCE both have data, and are not sharing a data key, deltify
+ TARGET's data. */
+ if (! props_only)
+ SVN_ERR(maybe_deltify_mutable_rep(target_nr->data_key, source_nr->data_key,
+ txn_id, trail, pool));
+
+ return SVN_NO_ERROR;
+}
+
+/* Maybe store a `checksum-reps' index record for the representation whose
+ key is REP. (If there's already a rep for this checksum, we don't
+ bother overwriting it.) */
+static svn_error_t *
+maybe_store_checksum_rep(const char *rep,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = SVN_NO_ERROR;
+ svn_fs_t *fs = trail->fs;
+ svn_checksum_t *sha1_checksum;
+
+ /* We want the SHA1 checksum, if any. */
+ SVN_ERR(svn_fs_base__rep_contents_checksums(NULL, &sha1_checksum,
+ fs, rep, trail, pool));
+ if (sha1_checksum)
+ {
+ err = svn_fs_bdb__set_checksum_rep(fs, sha1_checksum, rep, trail, pool);
+ if (err && (err->apr_err == SVN_ERR_FS_ALREADY_EXISTS))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ }
+ return svn_error_trace(err);
+}
+
+svn_error_t *
+svn_fs_base__dag_index_checksums(dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *node_rev;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, trail->fs, node->id,
+ trail, pool));
+ if ((node_rev->kind == svn_node_file) && node_rev->data_key)
+ SVN_ERR(maybe_store_checksum_rep(node_rev->data_key, trail, pool));
+ if (node_rev->prop_key)
+ SVN_ERR(maybe_store_checksum_rep(node_rev->prop_key, trail, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Committing ***/
+
+svn_error_t *
+svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev,
+ svn_fs_txn_t *txn,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ revision_t revision;
+ svn_string_t date;
+ apr_hash_t *txnprops;
+ svn_fs_t *fs = txn->fs;
+ const char *txn_id = txn->id;
+
+ /* Remove any temporary transaction properties initially created by
+ begin_txn(). */
+ SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, txn_id, trail));
+
+ /* Add new revision entry to `revisions' table. */
+ revision.txn_id = txn_id;
+ *new_rev = SVN_INVALID_REVNUM;
+ SVN_ERR(svn_fs_bdb__put_rev(new_rev, fs, &revision, trail, pool));
+
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
+ SVN_ERR(svn_fs_base__set_txn_prop
+ (fs, txn_id, SVN_FS__PROP_TXN_CHECK_OOD, NULL, trail, pool));
+
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
+ SVN_ERR(svn_fs_base__set_txn_prop
+ (fs, txn_id, SVN_FS__PROP_TXN_CHECK_LOCKS, NULL, trail, pool));
+
+ /* Promote the unfinished transaction to a committed one. */
+ SVN_ERR(svn_fs_base__txn_make_committed(fs, txn_id, *new_rev,
+ trail, pool));
+
+ /* Set a date on the commit. We wait until now to fetch the date,
+ so it's definitely newer than any previous revision's date. */
+ date.data = svn_time_to_cstring(apr_time_now(), pool);
+ date.len = strlen(date.data);
+ return svn_fs_base__set_rev_prop(fs, *new_rev, SVN_PROP_REVISION_DATE,
+ NULL, &date, trail, pool);
+}
+
+
+/*** Comparison. ***/
+
+svn_error_t *
+svn_fs_base__things_different(svn_boolean_t *props_changed,
+ svn_boolean_t *contents_changed,
+ dag_node_t *node1,
+ dag_node_t *node2,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev1, *noderev2;
+
+ /* If we have no place to store our results, don't bother doing
+ anything. */
+ if (! props_changed && ! contents_changed)
+ return SVN_NO_ERROR;
+
+ /* The node revision skels for these two nodes. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev1, node1->fs, node1->id,
+ trail, pool));
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev2, node2->fs, node2->id,
+ trail, pool));
+
+ /* Compare property keys. */
+ if (props_changed != NULL)
+ *props_changed = (! svn_fs_base__same_keys(noderev1->prop_key,
+ noderev2->prop_key));
+
+ /* Compare contents keys and their (optional) uniquifiers. */
+ if (contents_changed != NULL)
+ *contents_changed =
+ (! (svn_fs_base__same_keys(noderev1->data_key,
+ noderev2->data_key)
+ /* Technically, these uniquifiers aren't used and "keys",
+ but keys are base-36 stringified numbers, so we'll take
+ this liberty. */
+ && (svn_fs_base__same_keys(noderev1->data_key_uniquifier,
+ noderev2->data_key_uniquifier))));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Mergeinfo tracking stuff ***/
+
+svn_error_t *
+svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo,
+ apr_int64_t *count,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *node_rev;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
+ const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
+ if (has_mergeinfo)
+ *has_mergeinfo = node_rev->has_mergeinfo;
+ if (count)
+ *count = node_rev->mergeinfo_count;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node,
+ svn_boolean_t has_mergeinfo,
+ svn_boolean_t *had_mergeinfo,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *node_rev;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
+ const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
+
+ SVN_ERR(svn_fs_base__test_required_feature_format
+ (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
+
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted merge tracking info change on "
+ "immutable node"));
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
+ *had_mergeinfo = node_rev->has_mergeinfo;
+
+ /* Are we changing the node? */
+ if ((! has_mergeinfo) != (! *had_mergeinfo))
+ {
+ /* Note the new has-mergeinfo state. */
+ node_rev->has_mergeinfo = has_mergeinfo;
+
+ /* Increment or decrement the mergeinfo count as necessary. */
+ if (has_mergeinfo)
+ node_rev->mergeinfo_count++;
+ else
+ node_rev->mergeinfo_count--;
+
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node,
+ apr_int64_t count_delta,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *node_rev;
+ svn_fs_t *fs = svn_fs_base__dag_get_fs(node);
+ const svn_fs_id_t *id = svn_fs_base__dag_get_id(node);
+
+ SVN_ERR(svn_fs_base__test_required_feature_format
+ (trail->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
+
+ if (! svn_fs_base__dag_check_mutable(node, txn_id))
+ return svn_error_createf(SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Attempted mergeinfo count change on "
+ "immutable node"));
+
+ if (count_delta == 0)
+ return SVN_NO_ERROR;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&node_rev, fs, id, trail, pool));
+ node_rev->mergeinfo_count = node_rev->mergeinfo_count + count_delta;
+ if ((node_rev->mergeinfo_count < 0)
+ || ((node->kind == svn_node_file) && (node_rev->mergeinfo_count > 1)))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ apr_psprintf(pool,
+ _("Invalid value (%%%s) for node "
+ "revision mergeinfo count"),
+ APR_INT64_T_FMT),
+ node_rev->mergeinfo_count);
+
+ return svn_fs_bdb__put_node_revision(fs, id, node_rev, trail, pool);
+}
diff --git a/subversion/libsvn_fs_base/dag.h b/subversion/libsvn_fs_base/dag.h
new file mode 100644
index 0000000..4c50c84
--- /dev/null
+++ b/subversion/libsvn_fs_base/dag.h
@@ -0,0 +1,587 @@
+/* dag.h : DAG-like interface filesystem, private to libsvn_fs
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_DAG_H
+#define SVN_LIBSVN_FS_DAG_H
+
+#include "svn_fs.h"
+
+#include "trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* The interface in this file provides all the essential filesystem
+ operations, but exposes the filesystem's DAG structure. This makes
+ it simpler to implement than the public interface, since a client
+ of this interface has to understand and cope with shared structure
+ directly as it appears in the database. However, it's still a
+ self-consistent set of invariants to maintain, making it
+ (hopefully) a useful interface boundary.
+
+ In other words:
+
+ - The dag_node_t interface exposes the internal DAG structure of
+ the filesystem, while the svn_fs.h interface does any cloning
+ necessary to make the filesystem look like a tree.
+
+ - The dag_node_t interface exposes the existence of copy nodes,
+ whereas the svn_fs.h handles them transparently.
+
+ - dag_node_t's must be explicitly cloned, whereas the svn_fs.h
+ operations make clones implicitly.
+
+ - Callers of the dag_node_t interface use Berkeley DB transactions
+ to ensure consistency between operations, while callers of the
+ svn_fs.h interface use Subversion transactions. */
+
+
+/* Initializing a filesystem. */
+
+
+/* Given a filesystem FS, which contains all the necessary tables,
+ create the initial revision 0, and the initial root directory. */
+svn_error_t *svn_fs_base__dag_init_fs(svn_fs_t *fs);
+
+
+
+/* Generic DAG node stuff. */
+
+typedef struct dag_node_t dag_node_t;
+
+
+/* Fill *NODE with a dag_node_t representing node revision ID in FS,
+ allocating in POOL. */
+svn_error_t *svn_fs_base__dag_get_node(dag_node_t **node,
+ svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Return a new dag_node_t object referring to the same node as NODE,
+ allocated in POOL. */
+dag_node_t *svn_fs_base__dag_dup(dag_node_t *node,
+ apr_pool_t *pool);
+
+
+/* Return the filesystem containing NODE. */
+svn_fs_t *svn_fs_base__dag_get_fs(dag_node_t *node);
+
+
+/* Set *REV to NODE's revision number, as part of TRAIL. If NODE has
+ never been committed as part of a revision, set *REV to
+ SVN_INVALID_REVNUM. */
+svn_error_t *svn_fs_base__dag_get_revision(svn_revnum_t *rev,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Return the node revision ID of NODE. The value returned is shared
+ with NODE, and will be deallocated when NODE is. */
+const svn_fs_id_t *svn_fs_base__dag_get_id(dag_node_t *node);
+
+
+/* Return the created path of NODE. The value returned is shared
+ with NODE, and will be deallocated when NODE is. */
+const char *svn_fs_base__dag_get_created_path(dag_node_t *node);
+
+
+/* Set *ID_P to the node revision ID of NODE's immediate predecessor,
+ or NULL if NODE has no predecessor, as part of TRAIL. The returned
+ ID will be allocated in POOL. */
+svn_error_t *svn_fs_base__dag_get_predecessor_id(const svn_fs_id_t **id_p,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *COUNT to the number of predecessors NODE has (recursively), or
+ -1 if not known, as part of TRAIL. */
+svn_error_t *svn_fs_base__dag_get_predecessor_count(int *count,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Return non-zero IFF NODE is currently mutable under Subversion
+ transaction TXN_ID. */
+svn_boolean_t svn_fs_base__dag_check_mutable(dag_node_t *node,
+ const char *txn_id);
+
+/* Return the node kind of NODE. */
+svn_node_kind_t svn_fs_base__dag_node_kind(dag_node_t *node);
+
+/* Set *PROPLIST_P to a PROPLIST hash representing the entire property
+ list of NODE, as part of TRAIL. The hash has const char * names
+ (the property names) and svn_string_t * values (the property values).
+
+ If properties do not exist on NODE, *PROPLIST_P will be set to NULL.
+
+ The returned property list is allocated in POOL. */
+svn_error_t *svn_fs_base__dag_get_proplist(apr_hash_t **proplist_p,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Set the property list of NODE to PROPLIST, as part of TRAIL. The
+ node being changed must be mutable. TXN_ID is the Subversion
+ transaction under which this occurs. */
+svn_error_t *svn_fs_base__dag_set_proplist(dag_node_t *node,
+ const apr_hash_t *proplist,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+/* Mergeinfo tracking stuff. */
+
+/* If HAS_MERGEINFO is not null, set *HAS_MERGEINFO to TRUE iff NODE
+ records that its property list contains merge tracking information.
+
+ If COUNT is not null, set *COUNT to the number of nodes --
+ including NODE itself -- in the subtree rooted at NODE which claim
+ to carry merge tracking information.
+
+ Do this as part of TRAIL, and use POOL for necessary allocations.
+
+ NOTE: No validation against NODE's actual property list is
+ performed. */
+svn_error_t *svn_fs_base__dag_get_mergeinfo_stats(svn_boolean_t *has_mergeinfo,
+ apr_int64_t *count,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* If HAS_MERGEINFO is set, record on NODE that its property list
+ carries merge tracking information. Otherwise, record on NODE its
+ property list does *not* carry merge tracking information. NODE
+ must be mutable under TXN_ID (the Subversion transaction under
+ which this operation occurs). Set *HAD_MERGEINFO to the previous
+ state of this record.
+
+ Update the mergeinfo count on NODE as necessary.
+
+ Do all of this as part of TRAIL, and use POOL for necessary
+ allocations.
+
+ NOTE: No validation against NODE's actual property list is
+ performed. */
+svn_error_t *svn_fs_base__dag_set_has_mergeinfo(dag_node_t *node,
+ svn_boolean_t has_mergeinfo,
+ svn_boolean_t *had_mergeinfo,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Record on NODE a change of COUNT_DELTA nodes -- including NODE
+ itself -- in the subtree rooted at NODE claim to carry merge
+ tracking information. That is, add COUNT_DELTA to NODE's current
+ mergeinfo count (regardless of whether COUNT_DELTA is a positive or
+ negative integer).
+
+ NODE must be mutable under TXN_ID (the Subversion transaction under
+ which this operation occurs). Do this as part of TRAIL, and use
+ POOL for necessary allocations.
+
+ NOTE: No validation of these claims is performed. */
+svn_error_t *svn_fs_base__dag_adjust_mergeinfo_count(dag_node_t *node,
+ apr_int64_t count_delta,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Revision and transaction roots. */
+
+
+/* Open the root of revision REV of filesystem FS, as part of TRAIL.
+ Set *NODE_P to the new node. Allocate the node in POOL. */
+svn_error_t *svn_fs_base__dag_revision_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *NODE_P to the root of transaction TXN_ID in FS, as part
+ of TRAIL. Allocate the node in POOL.
+
+ Note that the root node of TXN_ID is not necessarily mutable. If no
+ changes have been made in the transaction, then it may share its
+ root directory with its base revision. To get a mutable root node
+ for a transaction, call svn_fs_base__dag_clone_root. */
+svn_error_t *svn_fs_base__dag_txn_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *NODE_P to the base root of transaction TXN_ID in FS, as part
+ of TRAIL. Allocate the node in POOL. */
+svn_error_t *svn_fs_base__dag_txn_base_root(dag_node_t **node_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Clone the root directory of TXN_ID in FS, and update the
+ `transactions' table entry to point to it, unless this has been
+ done already. In either case, set *ROOT_P to a reference to the
+ root directory clone. Do all this as part of TRAIL, and allocate
+ *ROOT_P in POOL. */
+svn_error_t *svn_fs_base__dag_clone_root(dag_node_t **root_p,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Commit the transaction TXN->id in TXN->FS, as part of TRAIL. Store the
+ new revision number in *NEW_REV. This entails:
+ - marking the tree of mutable nodes at TXN->id's root as immutable,
+ and marking all their contents as stable
+ - creating a new revision, with TXN->id's root as its root directory
+ - promoting TXN->id to a "committed" transaction.
+
+ Beware! This does not make sure that TXN->id is based on the very
+ latest revision in TXN->FS. If the caller doesn't take care of this,
+ you may lose people's work!
+
+ Do any necessary temporary allocation in a subpool of POOL.
+ Consume temporary space at most proportional to the maximum depth
+ of SVN_TXN's tree of mutable nodes. */
+svn_error_t *svn_fs_base__dag_commit_txn(svn_revnum_t *new_rev,
+ svn_fs_txn_t *txn,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Directories. */
+
+
+/* Open the node named NAME in the directory PARENT, as part of TRAIL.
+ Set *CHILD_P to the new node, allocated in POOL. NAME must be a
+ single path component; it cannot be a slash-separated directory
+ path. */
+svn_error_t *svn_fs_base__dag_open(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *name,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *ENTRIES_P to a hash table of NODE's entries, as part of TRAIL,
+ or NULL if NODE has no entries. The keys of the table are entry
+ names, and the values are svn_fs_dirent_t's.
+
+ The returned table is allocated in POOL.
+
+ NOTE: the 'kind' field of the svn_fs_dirent_t's is set to
+ svn_node_unknown by this function -- callers that need in
+ interesting value in these slots should fill them in using a new
+ TRAIL, since the list of entries can be arbitrarily large. */
+svn_error_t *svn_fs_base__dag_dir_entries(apr_hash_t **entries_p,
+ dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set ENTRY_NAME in NODE to point to ID, as part of TRAIL. NODE must
+ be a mutable directory. ID can refer to a mutable or immutable
+ node. If ENTRY_NAME does not exist, it will be created. TXN_ID is
+ the Subversion transaction under which this occurs.*/
+svn_error_t *svn_fs_base__dag_set_entry(dag_node_t *node,
+ const char *entry_name,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Make a new mutable clone of the node named NAME in PARENT, and
+ adjust PARENT's directory entry to point to it, as part of TRAIL,
+ unless NAME in PARENT already refers to a mutable node. In either
+ case, set *CHILD_P to a reference to the new node, allocated in
+ POOL. PARENT must be mutable. NAME must be a single path
+ component; it cannot be a slash-separated directory path.
+ PARENT_PATH must be the canonicalized absolute path of the parent
+ directory.
+
+ COPY_ID, if non-NULL, is a key into the `copies' table, and
+ indicates that this new node is being created as the result of a
+ copy operation, and specifically which operation that was.
+
+ PATH is the canonicalized absolute path at which this node is being
+ created.
+
+ TXN_ID is the Subversion transaction under which this occurs. */
+svn_error_t *svn_fs_base__dag_clone_child(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Delete the directory entry named NAME from PARENT, as part of
+ TRAIL. PARENT must be mutable. NAME must be a single path
+ component; it cannot be a slash-separated directory path. If the
+ entry being deleted points to a mutable node revision, also remove
+ that node revision and (if it is a directory) all mutable node
+ revisions reachable from it. Also delete the node-origins record
+ for each deleted node revision that had no predecessor.
+
+ TXN_ID is the Subversion transaction under which this occurs.
+
+ If return SVN_ERR_FS_NO_SUCH_ENTRY, then there is no entry NAME in
+ PARENT. */
+svn_error_t *svn_fs_base__dag_delete(dag_node_t *parent,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Delete the node revision assigned to node ID from FS's `nodes'
+ table, as part of TRAIL. Also delete any mutable representations
+ and strings associated with that node revision. Also delete the
+ node-origins record for this node revision's node id, if this node
+ revision had no predecessor.
+
+ ID may refer to a file or directory, which must be mutable. TXN_ID
+ is the Subversion transaction under which this occurs.
+
+ NOTE: If ID represents a directory, and that directory has mutable
+ children, you risk orphaning those children by leaving them
+ dangling, disconnected from all DAG trees. It is assumed that
+ callers of this interface know what in the world they are doing. */
+svn_error_t *svn_fs_base__dag_remove_node(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Delete all mutable node revisions reachable from node ID, including
+ ID itself, from FS's `nodes' table, as part of TRAIL. Also delete
+ any mutable representations and strings associated with that node
+ revision. Also delete the node-origins record for each deleted
+ node revision that had no predecessor.
+
+ ID may refer to a file or directory, which may be mutable or
+ immutable. TXN_ID is the Subversion transaction under which this
+ occurs. */
+svn_error_t *svn_fs_base__dag_delete_if_mutable(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Create a new mutable directory named NAME in PARENT, as part of
+ TRAIL. Set *CHILD_P to a reference to the new node, allocated in
+ POOL. The new directory has no contents, and no properties.
+ PARENT must be mutable. NAME must be a single path component; it
+ cannot be a slash-separated directory path. PARENT_PATH must be
+ the canonicalized absolute path of the parent directory. PARENT
+ must not currently have an entry named NAME. Do any temporary
+ allocation in POOL. TXN_ID is the Subversion transaction
+ under which this occurs. */
+svn_error_t *svn_fs_base__dag_make_dir(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+/* Files. */
+
+
+/* Set *CONTENTS to a readable generic stream which yields the
+ contents of FILE, as part of TRAIL. Allocate the stream in POOL.
+ If FILE is not a file, return SVN_ERR_FS_NOT_FILE. */
+svn_error_t *svn_fs_base__dag_get_contents(svn_stream_t **contents,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Return a generic writable stream in *CONTENTS with which to set the
+ contents of FILE as part of TRAIL. Allocate the stream in POOL.
+ TXN_ID is the Subversion transaction under which this occurs. Any
+ previous edits on the file will be deleted, and a new edit stream
+ will be constructed. */
+svn_error_t *svn_fs_base__dag_get_edit_stream(svn_stream_t **contents,
+ dag_node_t *file,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Signify the completion of edits to FILE made using the stream
+ returned by svn_fs_base__dag_get_edit_stream, as part of TRAIL. TXN_ID
+ is the Subversion transaction under which this occurs.
+
+ If CHECKSUM is non-null, it must match the checksum for FILE's
+ contents (note: this is not recalculated, the recorded checksum is
+ used), else the error SVN_ERR_CHECKSUM_MISMATCH is returned.
+
+ This operation is a no-op if no edits are present. */
+svn_error_t *svn_fs_base__dag_finalize_edits(dag_node_t *file,
+ const svn_checksum_t *checksum,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *LENGTH to the length of the contents of FILE, as part of TRAIL. */
+svn_error_t *svn_fs_base__dag_file_length(svn_filesize_t *length,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Put the checksum of type CHECKSUM_KIND recorded for FILE into
+ * CHECKSUM, as part of TRAIL.
+ *
+ * If no stored checksum of the requested kind is available, do not
+ * calculate the checksum, just put NULL into CHECKSUM.
+ */
+svn_error_t *svn_fs_base__dag_file_checksum(svn_checksum_t **checksum,
+ svn_checksum_kind_t checksum_kind,
+ dag_node_t *file,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Create a new mutable file named NAME in PARENT, as part of TRAIL.
+ Set *CHILD_P to a reference to the new node, allocated in
+ POOL. The new file's contents are the empty string, and it
+ has no properties. PARENT must be mutable. NAME must be a single
+ path component; it cannot be a slash-separated directory path.
+ PARENT_PATH must be the canonicalized absolute path of the parent
+ directory. TXN_ID is the Subversion transaction under which this
+ occurs. */
+svn_error_t *svn_fs_base__dag_make_file(dag_node_t **child_p,
+ dag_node_t *parent,
+ const char *parent_path,
+ const char *name,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+/* Copies */
+
+/* Make ENTRY in TO_NODE be a copy of FROM_NODE, as part of TRAIL.
+ TO_NODE must be mutable. TXN_ID is the Subversion transaction
+ under which this occurs.
+
+ If PRESERVE_HISTORY is true, the new node will record that it was
+ copied from FROM_PATH in FROM_REV; therefore, FROM_NODE should be
+ the node found at FROM_PATH in FROM_REV, although this is not
+ checked.
+
+ If PRESERVE_HISTORY is false, FROM_PATH and FROM_REV are ignored. */
+svn_error_t *svn_fs_base__dag_copy(dag_node_t *to_node,
+ const char *entry,
+ dag_node_t *from_node,
+ svn_boolean_t preserve_history,
+ svn_revnum_t from_rev,
+ const char *from_path,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+/* Deltification */
+
+/* Change TARGET's representation to be a delta against SOURCE, as
+ part of TRAIL. If TARGET or SOURCE does not exist, do nothing and
+ return success. If PROPS_ONLY is non-zero, only the node property
+ portion of TARGET will be deltified.
+
+ If TXN_ID is non-NULL, it is the transaction ID in which TARGET's
+ representation(s) must have been created (otherwise deltification
+ is silently not attempted).
+
+ WARNING WARNING WARNING: Do *NOT* call this with a mutable SOURCE
+ node. Things will go *very* sour if you deltify TARGET against a
+ node that might just disappear from the filesystem in the (near)
+ future. */
+svn_error_t *svn_fs_base__dag_deltify(dag_node_t *target,
+ dag_node_t *source,
+ svn_boolean_t props_only,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Index NODE's backing data representations by their checksum. Do
+ this as part of TRAIL. Use POOL for allocations. */
+svn_error_t *svn_fs_base__dag_index_checksums(dag_node_t *node,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Comparison */
+
+/* Find out what is the same between two nodes.
+
+ If PROPS_CHANGED is non-null, set *PROPS_CHANGED to 1 if the two
+ nodes have different property lists, or to 0 if same.
+
+ If CONTENTS_CHANGED is non-null, set *CONTENTS_CHANGED to 1 if the
+ two nodes have different contents, or to 0 if same. For files,
+ file contents are compared; for directories, the entries lists are
+ compared. If one is a file and the other is a directory, the one's
+ contents will be compared to the other's entries list. (Not
+ terribly useful, I suppose, but that's the caller's business.)
+
+ ### todo: This function only compares rep keys at the moment. This
+ may leave us with a slight chance of a false positive, though I
+ don't really see how that would happen in practice. Nevertheless,
+ it should probably be fixed. */
+svn_error_t *svn_fs_base__things_different(svn_boolean_t *props_changed,
+ svn_boolean_t *contents_changed,
+ dag_node_t *node1,
+ dag_node_t *node2,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_DAG_H */
diff --git a/subversion/libsvn_fs_base/err.c b/subversion/libsvn_fs_base/err.c
new file mode 100644
index 0000000..c1e691d
--- /dev/null
+++ b/subversion/libsvn_fs_base/err.c
@@ -0,0 +1,177 @@
+/*
+ * err.c : implementation of fs-private error functions
+ *
+ * ====================================================================
+ * 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 <stdlib.h>
+#include <stdarg.h>
+
+#include "svn_private_config.h"
+#include "svn_fs.h"
+#include "err.h"
+#include "id.h"
+
+#include "../libsvn_fs/fs-loader.h"
+
+
+
+/* Building common error objects. */
+
+
+svn_error_t *
+svn_fs_base__err_corrupt_fs_revision(svn_fs_t *fs, svn_revnum_t rev)
+{
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt filesystem revision %ld in filesystem '%s'"),
+ rev, fs->path);
+}
+
+
+svn_error_t *
+svn_fs_base__err_dangling_id(svn_fs_t *fs, const svn_fs_id_t *id)
+{
+ svn_string_t *id_str = svn_fs_base__id_unparse(id, fs->pool);
+ return svn_error_createf
+ (SVN_ERR_FS_ID_NOT_FOUND, 0,
+ _("Reference to non-existent node '%s' in filesystem '%s'"),
+ id_str->data, fs->path);
+}
+
+
+svn_error_t *
+svn_fs_base__err_dangling_rev(svn_fs_t *fs, svn_revnum_t rev)
+{
+ /* Log the UUID as this error may be reported to the client. */
+ return svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_REVISION, 0,
+ _("No such revision %ld in filesystem '%s'"),
+ rev, fs->uuid);
+}
+
+
+svn_error_t *
+svn_fs_base__err_corrupt_txn(svn_fs_t *fs,
+ const char *txn)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt entry in 'transactions' table for '%s'"
+ " in filesystem '%s'"), txn, fs->path);
+}
+
+
+svn_error_t *
+svn_fs_base__err_corrupt_copy(svn_fs_t *fs, const char *copy_id)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt entry in 'copies' table for '%s' in filesystem '%s'"),
+ copy_id, fs->path);
+}
+
+
+svn_error_t *
+svn_fs_base__err_no_such_txn(svn_fs_t *fs, const char *txn)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_TRANSACTION, 0,
+ _("No transaction named '%s' in filesystem '%s'"),
+ txn, fs->path);
+}
+
+
+svn_error_t *
+svn_fs_base__err_txn_not_mutable(svn_fs_t *fs, const char *txn)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_TRANSACTION_NOT_MUTABLE, 0,
+ _("Cannot modify transaction named '%s' in filesystem '%s'"),
+ txn, fs->path);
+}
+
+
+svn_error_t *
+svn_fs_base__err_no_such_copy(svn_fs_t *fs, const char *copy_id)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_COPY, 0,
+ _("No copy with id '%s' in filesystem '%s'"), copy_id, fs->path);
+}
+
+
+svn_error_t *
+svn_fs_base__err_bad_lock_token(svn_fs_t *fs, const char *lock_token)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_BAD_LOCK_TOKEN, 0,
+ _("Token '%s' does not point to any existing lock in filesystem '%s'"),
+ lock_token, fs->path);
+}
+
+svn_error_t *
+svn_fs_base__err_no_lock_token(svn_fs_t *fs, const char *path)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_NO_LOCK_TOKEN, 0,
+ _("No token given for path '%s' in filesystem '%s'"), path, fs->path);
+}
+
+svn_error_t *
+svn_fs_base__err_corrupt_lock(svn_fs_t *fs, const char *lock_token)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt lock in 'locks' table for '%s' in filesystem '%s'"),
+ lock_token, fs->path);
+}
+
+svn_error_t *
+svn_fs_base__err_no_such_node_origin(svn_fs_t *fs, const char *node_id)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_NODE_ORIGIN, 0,
+ _("No record in 'node-origins' table for node id '%s' in "
+ "filesystem '%s'"), node_id, fs->path);
+}
+
+svn_error_t *
+svn_fs_base__err_no_such_checksum_rep(svn_fs_t *fs, svn_checksum_t *checksum)
+{
+ return
+ svn_error_createf
+ (SVN_ERR_FS_NO_SUCH_CHECKSUM_REP, 0,
+ _("No record in 'checksum-reps' table for checksum '%s' in "
+ "filesystem '%s'"), svn_checksum_to_cstring_display(checksum,
+ fs->pool),
+ fs->path);
+}
diff --git a/subversion/libsvn_fs_base/err.h b/subversion/libsvn_fs_base/err.h
new file mode 100644
index 0000000..5c03d9f
--- /dev/null
+++ b/subversion/libsvn_fs_base/err.h
@@ -0,0 +1,98 @@
+/*
+ * err.h : interface to routines for returning Berkeley DB errors
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+
+
+#ifndef SVN_LIBSVN_FS_ERR_H
+#define SVN_LIBSVN_FS_ERR_H
+
+#include <apr_pools.h>
+
+#include "svn_error.h"
+#include "svn_fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* Building common error objects. */
+
+
+/* SVN_ERR_FS_CORRUPT: the REVISION skel of revision REV in FS is corrupt. */
+svn_error_t *svn_fs_base__err_corrupt_fs_revision(svn_fs_t *fs,
+ svn_revnum_t rev);
+
+/* SVN_ERR_FS_ID_NOT_FOUND: something in FS refers to node revision
+ ID, but that node revision doesn't exist. */
+svn_error_t *svn_fs_base__err_dangling_id(svn_fs_t *fs,
+ const svn_fs_id_t *id);
+
+/* SVN_ERR_FS_CORRUPT: something in FS refers to filesystem revision REV,
+ but that filesystem revision doesn't exist. */
+svn_error_t *svn_fs_base__err_dangling_rev(svn_fs_t *fs, svn_revnum_t rev);
+
+/* SVN_ERR_FS_CORRUPT: the entry for TXN in the `transactions' table
+ is corrupt. */
+svn_error_t *svn_fs_base__err_corrupt_txn(svn_fs_t *fs, const char *txn);
+
+/* SVN_ERR_FS_CORRUPT: the entry for COPY_ID in the `copies' table
+ is corrupt. */
+svn_error_t *svn_fs_base__err_corrupt_copy(svn_fs_t *fs, const char *copy_id);
+
+/* SVN_ERR_FS_NO_SUCH_TRANSACTION: there is no transaction named TXN in FS. */
+svn_error_t *svn_fs_base__err_no_such_txn(svn_fs_t *fs, const char *txn);
+
+/* SVN_ERR_FS_TRANSACTION_NOT_MUTABLE: trying to change the
+ unchangeable transaction named TXN in FS. */
+svn_error_t *svn_fs_base__err_txn_not_mutable(svn_fs_t *fs, const char *txn);
+
+/* SVN_ERR_FS_NO_SUCH_COPY: there is no copy with id COPY_ID in FS. */
+svn_error_t *svn_fs_base__err_no_such_copy(svn_fs_t *fs, const char *copy_id);
+
+/* SVN_ERR_FS_BAD_LOCK_TOKEN: LOCK_TOKEN does not refer to a lock in FS. */
+svn_error_t *svn_fs_base__err_bad_lock_token(svn_fs_t *fs,
+ const char *lock_token);
+
+/* SVN_ERR_FS_NO_LOCK_TOKEN: no lock token given for PATH in FS. */
+svn_error_t *svn_fs_base__err_no_lock_token(svn_fs_t *fs, const char *path);
+
+/* SVN_ERR_FS_CORRUPT: a lock in `locks' table is corrupt. */
+svn_error_t *svn_fs_base__err_corrupt_lock(svn_fs_t *fs,
+ const char *lock_token);
+
+/* SVN_ERR_FS_NO_SUCH_NODE_ORIGIN: no recorded node origin for NODE_ID
+ in FS. */
+svn_error_t *svn_fs_base__err_no_such_node_origin(svn_fs_t *fs,
+ const char *node_id);
+
+/* SVN_ERR_FS_NO_SUCH_CHECKSUM_REP: no recorded rep key for CHECKSUM in FS. */
+svn_error_t *svn_fs_base__err_no_such_checksum_rep(svn_fs_t *fs,
+ svn_checksum_t *checksum);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_ERR_H */
diff --git a/subversion/libsvn_fs_base/fs.c b/subversion/libsvn_fs_base/fs.c
new file mode 100644
index 0000000..bee921b
--- /dev/null
+++ b/subversion/libsvn_fs_base/fs.c
@@ -0,0 +1,1436 @@
+/* fs.c --- creating, opening and closing filesystems
+ *
+ * ====================================================================
+ * 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 <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#include <apr_general.h>
+#include <apr_pools.h>
+#include <apr_file_io.h>
+
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_fs.h"
+#include "svn_path.h"
+#include "svn_utf.h"
+#include "svn_delta.h"
+#include "svn_version.h"
+#include "fs.h"
+#include "err.h"
+#include "dag.h"
+#include "revs-txns.h"
+#include "uuid.h"
+#include "tree.h"
+#include "id.h"
+#include "lock.h"
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "bdb/bdb-err.h"
+#include "bdb/bdb_compat.h"
+#include "bdb/env.h"
+#include "bdb/nodes-table.h"
+#include "bdb/rev-table.h"
+#include "bdb/txn-table.h"
+#include "bdb/copies-table.h"
+#include "bdb/changes-table.h"
+#include "bdb/reps-table.h"
+#include "bdb/strings-table.h"
+#include "bdb/uuids-table.h"
+#include "bdb/locks-table.h"
+#include "bdb/lock-tokens-table.h"
+#include "bdb/node-origins-table.h"
+#include "bdb/miscellaneous-table.h"
+#include "bdb/checksum-reps-table.h"
+
+#include "../libsvn_fs/fs-loader.h"
+#include "private/svn_fs_util.h"
+
+
+/* Checking for return values, and reporting errors. */
+
+/* Check that we're using the right Berkeley DB version. */
+/* FIXME: This check should be abstracted into the DB back-end layer. */
+static svn_error_t *
+check_bdb_version(void)
+{
+ int major, minor, patch;
+
+ db_version(&major, &minor, &patch);
+
+ /* First, check that we're using a reasonably correct of Berkeley DB. */
+ if ((major < SVN_FS_WANT_DB_MAJOR)
+ || (major == SVN_FS_WANT_DB_MAJOR && minor < SVN_FS_WANT_DB_MINOR)
+ || (major == SVN_FS_WANT_DB_MAJOR && minor == SVN_FS_WANT_DB_MINOR
+ && patch < SVN_FS_WANT_DB_PATCH))
+ return svn_error_createf(SVN_ERR_FS_GENERAL, 0,
+ _("Bad database version: got %d.%d.%d,"
+ " should be at least %d.%d.%d"),
+ major, minor, patch,
+ SVN_FS_WANT_DB_MAJOR,
+ SVN_FS_WANT_DB_MINOR,
+ SVN_FS_WANT_DB_PATCH);
+
+ /* Now, check that the version we're running against is the same as
+ the one we compiled with. */
+ if (major != DB_VERSION_MAJOR || minor != DB_VERSION_MINOR)
+ return svn_error_createf(SVN_ERR_FS_GENERAL, 0,
+ _("Bad database version:"
+ " compiled with %d.%d.%d,"
+ " running against %d.%d.%d"),
+ DB_VERSION_MAJOR,
+ DB_VERSION_MINOR,
+ DB_VERSION_PATCH,
+ major, minor, patch);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Cleanup functions. */
+
+/* Close a database in the filesystem FS.
+ DB_PTR is a pointer to the DB pointer in *FS to close.
+ NAME is the name of the database, for use in error messages. */
+static svn_error_t *
+cleanup_fs_db(svn_fs_t *fs, DB **db_ptr, const char *name)
+{
+ /* If the BDB environment is panicked, don't do anything, since
+ attempting to close the database will fail anyway. */
+ base_fs_data_t *bfd = fs->fsap_data;
+ if (*db_ptr && !svn_fs_bdb__get_panic(bfd->bdb))
+ {
+ DB *db = *db_ptr;
+ char *msg = apr_psprintf(fs->pool, "closing '%s' database", name);
+ int db_err;
+
+ *db_ptr = 0;
+ db_err = db->close(db, 0);
+ if (db_err == DB_RUNRECOVERY)
+ {
+ /* We can ignore DB_RUNRECOVERY errors from DB->close, but
+ must set the panic flag in the environment baton. The
+ error will be propagated appropriately from
+ svn_fs_bdb__close. */
+ svn_fs_bdb__set_panic(bfd->bdb);
+ db_err = 0;
+ }
+
+#if SVN_BDB_HAS_DB_INCOMPLETE
+ /* We can ignore DB_INCOMPLETE on db->close and db->sync; it
+ * just means someone else was using the db at the same time
+ * we were. See the Berkeley documentation at:
+ * http://www.sleepycat.com/docs/ref/program/errorret.html#DB_INCOMPLETE
+ * http://www.sleepycat.com/docs/api_c/db_close.html
+ */
+ if (db_err == DB_INCOMPLETE)
+ db_err = 0;
+#endif /* SVN_BDB_HAS_DB_INCOMPLETE */
+
+ SVN_ERR(BDB_WRAP(fs, msg, db_err));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Close whatever Berkeley DB resources are allocated to FS. */
+static svn_error_t *
+cleanup_fs(svn_fs_t *fs)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ bdb_env_baton_t *bdb = (bfd ? bfd->bdb : NULL);
+
+ if (!bdb)
+ return SVN_NO_ERROR;
+
+ /* Close the databases. */
+ SVN_ERR(cleanup_fs_db(fs, &bfd->nodes, "nodes"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->revisions, "revisions"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->transactions, "transactions"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->copies, "copies"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->changes, "changes"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->representations, "representations"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->strings, "strings"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->uuids, "uuids"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->locks, "locks"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->lock_tokens, "lock-tokens"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->node_origins, "node-origins"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->checksum_reps, "checksum-reps"));
+ SVN_ERR(cleanup_fs_db(fs, &bfd->miscellaneous, "miscellaneous"));
+
+ /* Finally, close the environment. */
+ bfd->bdb = 0;
+ {
+ svn_error_t *err = svn_fs_bdb__close(bdb);
+ if (err)
+ return svn_error_createf
+ (err->apr_err, err,
+ _("Berkeley DB error for filesystem '%s'"
+ " while closing environment:\n"),
+ fs->path);
+ }
+ return SVN_NO_ERROR;
+}
+
+#if 0 /* Set to 1 for instrumenting. */
+static void print_fs_stats(svn_fs_t *fs)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ DB_TXN_STAT *t;
+ DB_LOCK_STAT *l;
+ int db_err;
+
+ /* Print transaction statistics for this DB env. */
+ if ((db_err = bfd->bdb->env->txn_stat(bfd->bdb->env, &t, 0)) != 0)
+ fprintf(stderr, "Error running bfd->bdb->env->txn_stat(): %s",
+ db_strerror(db_err));
+ else
+ {
+ printf("*** DB transaction stats, right before closing env:\n");
+ printf(" Number of transactions currently active: %d\n",
+ t->st_nactive);
+ printf(" Max number of active transactions at any one time: %d\n",
+ t->st_maxnactive);
+ printf(" Number of transactions that have begun: %d\n",
+ t->st_nbegins);
+ printf(" Number of transactions that have aborted: %d\n",
+ t->st_naborts);
+ printf(" Number of transactions that have committed: %d\n",
+ t->st_ncommits);
+ printf(" Number of times a thread was forced to wait: %d\n",
+ t->st_region_wait);
+ printf(" Number of times a thread didn't need to wait: %d\n",
+ t->st_region_nowait);
+ printf("*** End DB transaction stats.\n\n");
+ }
+
+ /* Print transaction statistics for this DB env. */
+ if ((db_err = bfd->bdb->env->lock_stat(bfd->bdb->env, &l, 0)) != 0)
+ fprintf(stderr, "Error running bfd->bdb->env->lock_stat(): %s",
+ db_strerror(db_err));
+ else
+ {
+ printf("*** DB lock stats, right before closing env:\n");
+ printf(" The number of current locks: %d\n",
+ l->st_nlocks);
+ printf(" Max number of locks at any one time: %d\n",
+ l->st_maxnlocks);
+ printf(" Number of current lockers: %d\n",
+ l->st_nlockers);
+ printf(" Max number of lockers at any one time: %d\n",
+ l->st_maxnlockers);
+ printf(" Number of current objects: %d\n",
+ l->st_nobjects);
+ printf(" Max number of objects at any one time: %d\n",
+ l->st_maxnobjects);
+ printf(" Total number of locks requested: %d\n",
+ l->st_nrequests);
+ printf(" Total number of locks released: %d\n",
+ l->st_nreleases);
+ printf(" Total number of lock reqs failed because "
+ "DB_LOCK_NOWAIT was set: %d\n", l->st_nnowaits);
+ printf(" Total number of locks not immediately available "
+ "due to conflicts: %d\n", l->st_nconflicts);
+ printf(" Number of deadlocks detected: %d\n", l->st_ndeadlocks);
+ printf(" Number of times a thread waited before "
+ "obtaining the region lock: %d\n", l->st_region_wait);
+ printf(" Number of times a thread didn't have to wait: %d\n",
+ l->st_region_nowait);
+ printf("*** End DB lock stats.\n\n");
+ }
+
+}
+#else
+# define print_fs_stats(fs)
+#endif /* 0/1 */
+
+/* An APR pool cleanup function for a filesystem. DATA must be a
+ pointer to the filesystem to clean up.
+
+ When the filesystem object's pool is freed, we want the resources
+ held by Berkeley DB to go away, just like everything else. So we
+ register this cleanup function with the filesystem's pool, and let
+ it take care of closing the databases, the environment, and any
+ other DB objects we might be using. APR calls this function before
+ actually freeing the pool's memory.
+
+ It's a pity that we can't return an svn_error_t object from an APR
+ cleanup function. For now, we return the rather generic
+ SVN_ERR_FS_CLEANUP, and pass the real svn_error_t to the registered
+ warning callback. */
+
+static apr_status_t
+cleanup_fs_apr(void *data)
+{
+ svn_fs_t *fs = data;
+ svn_error_t *err;
+
+ print_fs_stats(fs);
+
+ err = cleanup_fs(fs);
+ if (! err)
+ return APR_SUCCESS;
+
+ /* Darn. An error during cleanup. Call the warning handler to
+ try and do something "right" with this error. Note that
+ the default will simply abort(). */
+ (*fs->warning)(fs->warning_baton, err);
+
+ svn_error_clear(err);
+
+ return SVN_ERR_FS_CLEANUP;
+}
+
+
+static svn_error_t *
+base_bdb_set_errcall(svn_fs_t *fs,
+ void (*db_errcall_fcn)(const char *errpfx, char *msg))
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+ bfd->bdb->error_info->user_callback = db_errcall_fcn;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Write the DB_CONFIG file. */
+static svn_error_t *
+bdb_write_config(svn_fs_t *fs)
+{
+ const char *dbconfig_file_name =
+ svn_dirent_join(fs->path, BDB_CONFIG_FILE, fs->pool);
+ apr_file_t *dbconfig_file = NULL;
+ int i;
+
+ static const char dbconfig_contents[] =
+ "# This is the configuration file for the Berkeley DB environment\n"
+ "# used by your Subversion repository.\n"
+ "# You must run 'svnadmin recover' whenever you modify this file,\n"
+ "# for your changes to take effect.\n"
+ "\n"
+ "### Lock subsystem\n"
+ "#\n"
+ "# Make sure you read the documentation at:\n"
+ "#\n"
+ "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/lock_max.html\n"
+ "#\n"
+ "# before tweaking these values.\n"
+ "#\n"
+ "set_lk_max_locks 2000\n"
+ "set_lk_max_lockers 2000\n"
+ "set_lk_max_objects 2000\n"
+ "\n"
+ "### Log file subsystem\n"
+ "#\n"
+ "# Make sure you read the documentation at:\n"
+ "#\n"
+ "# http://docs.oracle.com/cd/E17076_02/html/api_reference/C/envset_lg_bsize.html\n"
+ "# http://docs.oracle.com/cd/E17076_02/html/api_reference/C/envset_lg_max.html\n"
+ "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_limits.html\n"
+ "#\n"
+ "# Increase the size of the in-memory log buffer from the default\n"
+ "# of 32 Kbytes to 256 Kbytes. Decrease the log file size from\n"
+ "# 10 Mbytes to 1 Mbyte. This will help reduce the amount of disk\n"
+ "# space required for hot backups. The size of the log file must be\n"
+ "# at least four times the size of the in-memory log buffer.\n"
+ "#\n"
+ "# Note: Decreasing the in-memory buffer size below 256 Kbytes will hurt\n"
+ "# hurt commit performance. For details, see:\n"
+ "#\n"
+ "# http://svn.haxx.se/dev/archive-2002-02/0141.shtml\n"
+ "#\n"
+ "set_lg_bsize 262144\n"
+ "set_lg_max 1048576\n"
+ "#\n"
+ "# If you see \"log region out of memory\" errors, bump lg_regionmax.\n"
+ "# For more information, see:\n"
+ "#\n"
+ "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n"
+ "# http://svn.haxx.se/users/archive-2004-10/1000.shtml\n"
+ "#\n"
+ "set_lg_regionmax 131072\n"
+ "#\n"
+ /* ### Configure this with "svnadmin create --bdb-cache-size" */
+ "# The default cache size in BDB is only 256k. As explained in\n"
+ "# http://svn.haxx.se/dev/archive-2004-12/0368.shtml, this is too\n"
+ "# small for most applications. Bump this number if \"db_stat -m\"\n"
+ "# shows too many cache misses.\n"
+ "#\n"
+ "set_cachesize 0 1048576 1\n";
+
+ /* Run-time configurable options.
+ Each option set consists of a minimum required BDB version, a
+ config hash key, a header, an inactive form and an active
+ form. We always write the header; then, depending on the
+ run-time configuration and the BDB version we're compiling
+ against, we write either the active or inactive form of the
+ value. */
+ static const struct
+ {
+ int bdb_major;
+ int bdb_minor;
+ const char *config_key;
+ const char *header;
+ const char *inactive;
+ const char *active;
+ } dbconfig_options[] = {
+ /* Controlled by "svnadmin create --bdb-txn-nosync" */
+ { 4, 0, SVN_FS_CONFIG_BDB_TXN_NOSYNC,
+ /* header */
+ "#\n"
+ "# Disable fsync of log files on transaction commit. Read the\n"
+ "# documentation about DB_TXN_NOSYNC at:\n"
+ "#\n"
+ "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n"
+ "#\n"
+ "# [requires Berkeley DB 4.0]\n"
+ "#\n",
+ /* inactive */
+ "#set_flags DB_TXN_NOSYNC\n",
+ /* active */
+ "set_flags DB_TXN_NOSYNC\n" },
+ /* Controlled by "svnadmin create --bdb-log-keep" */
+ { 4, 2, SVN_FS_CONFIG_BDB_LOG_AUTOREMOVE,
+ /* header */
+ "#\n"
+ "# Enable automatic removal of unused transaction log files.\n"
+ "# Read the documentation about DB_LOG_AUTOREMOVE at:\n"
+ "#\n"
+ "# http://docs.oracle.com/cd/E17076_02/html/programmer_reference/log_config.html\n"
+ "#\n"
+ "# [requires Berkeley DB 4.2]\n"
+ "#\n",
+ /* inactive */
+ "#set_flags DB_LOG_AUTOREMOVE\n",
+ /* active */
+ "set_flags DB_LOG_AUTOREMOVE\n" },
+ };
+ static const int dbconfig_options_length =
+ sizeof(dbconfig_options)/sizeof(*dbconfig_options);
+
+
+ SVN_ERR(svn_io_file_open(&dbconfig_file, dbconfig_file_name,
+ APR_WRITE | APR_CREATE, APR_OS_DEFAULT,
+ fs->pool));
+
+ SVN_ERR(svn_io_file_write_full(dbconfig_file, dbconfig_contents,
+ sizeof(dbconfig_contents) - 1, NULL,
+ fs->pool));
+
+ /* Write the variable DB_CONFIG flags. */
+ for (i = 0; i < dbconfig_options_length; ++i)
+ {
+ void *value = NULL;
+ const char *choice;
+
+ if (fs->config)
+ {
+ value = svn_hash_gets(fs->config, dbconfig_options[i].config_key);
+ }
+
+ SVN_ERR(svn_io_file_write_full(dbconfig_file,
+ dbconfig_options[i].header,
+ strlen(dbconfig_options[i].header),
+ NULL, fs->pool));
+
+ if (((DB_VERSION_MAJOR == dbconfig_options[i].bdb_major
+ && DB_VERSION_MINOR >= dbconfig_options[i].bdb_minor)
+ || DB_VERSION_MAJOR > dbconfig_options[i].bdb_major)
+ && value != NULL && strcmp(value, "0") != 0)
+ choice = dbconfig_options[i].active;
+ else
+ choice = dbconfig_options[i].inactive;
+
+ SVN_ERR(svn_io_file_write_full(dbconfig_file, choice, strlen(choice),
+ NULL, fs->pool));
+ }
+
+ return svn_io_file_close(dbconfig_file, fs->pool);
+}
+
+static svn_error_t *
+base_bdb_verify_root(svn_fs_root_t *root,
+ apr_pool_t *scratch_pool)
+{
+ /* Verifying is currently a no op for BDB. */
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+base_bdb_freeze(svn_fs_t *fs,
+ svn_fs_freeze_func_t freeze_func,
+ void *freeze_baton,
+ apr_pool_t *pool)
+{
+ SVN__NOT_IMPLEMENTED();
+}
+
+
+/* Creating a new filesystem */
+
+static fs_vtable_t fs_vtable = {
+ svn_fs_base__youngest_rev,
+ svn_fs_base__revision_prop,
+ svn_fs_base__revision_proplist,
+ svn_fs_base__change_rev_prop,
+ svn_fs_base__set_uuid,
+ svn_fs_base__revision_root,
+ svn_fs_base__begin_txn,
+ svn_fs_base__open_txn,
+ svn_fs_base__purge_txn,
+ svn_fs_base__list_transactions,
+ svn_fs_base__deltify,
+ svn_fs_base__lock,
+ svn_fs_base__generate_lock_token,
+ svn_fs_base__unlock,
+ svn_fs_base__get_lock,
+ svn_fs_base__get_locks,
+ base_bdb_verify_root,
+ base_bdb_freeze,
+ base_bdb_set_errcall,
+};
+
+/* Where the format number is stored. */
+#define FORMAT_FILE "format"
+
+/* Depending on CREATE, create or open the environment and databases
+ for filesystem FS in PATH. Use POOL for temporary allocations. */
+static svn_error_t *
+open_databases(svn_fs_t *fs,
+ svn_boolean_t create,
+ int format,
+ const char *path,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd;
+
+ SVN_ERR(svn_fs__check_fs(fs, FALSE));
+
+ bfd = apr_pcalloc(fs->pool, sizeof(*bfd));
+ fs->vtable = &fs_vtable;
+ fs->fsap_data = bfd;
+
+ /* Initialize the fs's path. */
+ fs->path = apr_pstrdup(fs->pool, path);
+
+ if (create)
+ SVN_ERR(bdb_write_config(fs));
+
+ /* Create the Berkeley DB environment. */
+ {
+ svn_error_t *err = svn_fs_bdb__open(&(bfd->bdb), path,
+ SVN_BDB_STANDARD_ENV_FLAGS,
+ 0666, fs->pool);
+ if (err)
+ {
+ if (create)
+ return svn_error_createf
+ (err->apr_err, err,
+ _("Berkeley DB error for filesystem '%s'"
+ " while creating environment:\n"),
+ fs->path);
+ else
+ return svn_error_createf
+ (err->apr_err, err,
+ _("Berkeley DB error for filesystem '%s'"
+ " while opening environment:\n"),
+ fs->path);
+ }
+ }
+
+ /* We must register the FS cleanup function *after* opening the
+ environment, so that it's run before the environment baton
+ cleanup. */
+ apr_pool_cleanup_register(fs->pool, fs, cleanup_fs_apr,
+ apr_pool_cleanup_null);
+
+
+ /* Create the databases in the environment. */
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'nodes' table")
+ : N_("opening 'nodes' table")),
+ svn_fs_bdb__open_nodes_table(&bfd->nodes,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'revisions' table")
+ : N_("opening 'revisions' table")),
+ svn_fs_bdb__open_revisions_table(&bfd->revisions,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'transactions' table")
+ : N_("opening 'transactions' table")),
+ svn_fs_bdb__open_transactions_table(&bfd->transactions,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'copies' table")
+ : N_("opening 'copies' table")),
+ svn_fs_bdb__open_copies_table(&bfd->copies,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'changes' table")
+ : N_("opening 'changes' table")),
+ svn_fs_bdb__open_changes_table(&bfd->changes,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'representations' table")
+ : N_("opening 'representations' table")),
+ svn_fs_bdb__open_reps_table(&bfd->representations,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'strings' table")
+ : N_("opening 'strings' table")),
+ svn_fs_bdb__open_strings_table(&bfd->strings,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'uuids' table")
+ : N_("opening 'uuids' table")),
+ svn_fs_bdb__open_uuids_table(&bfd->uuids,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'locks' table")
+ : N_("opening 'locks' table")),
+ svn_fs_bdb__open_locks_table(&bfd->locks,
+ bfd->bdb->env,
+ create)));
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'lock-tokens' table")
+ : N_("opening 'lock-tokens' table")),
+ svn_fs_bdb__open_lock_tokens_table(&bfd->lock_tokens,
+ bfd->bdb->env,
+ create)));
+
+ if (format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT)
+ {
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'node-origins' table")
+ : N_("opening 'node-origins' table")),
+ svn_fs_bdb__open_node_origins_table(&bfd->node_origins,
+ bfd->bdb->env,
+ create)));
+ }
+
+ if (format >= SVN_FS_BASE__MIN_MISCELLANY_FORMAT)
+ {
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'miscellaneous' table")
+ : N_("opening 'miscellaneous' table")),
+ svn_fs_bdb__open_miscellaneous_table(&bfd->miscellaneous,
+ bfd->bdb->env,
+ create)));
+ }
+
+ if (format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
+ {
+ SVN_ERR(BDB_WRAP(fs, (create
+ ? N_("creating 'checksum-reps' table")
+ : N_("opening 'checksum-reps' table")),
+ svn_fs_bdb__open_checksum_reps_table(&bfd->checksum_reps,
+ bfd->bdb->env,
+ create)));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Called by functions that initialize an svn_fs_t struct, after that
+ initialization is done, to populate svn_fs_t->uuid. */
+static svn_error_t *
+populate_opened_fs(svn_fs_t *fs, apr_pool_t *scratch_pool)
+{
+ SVN_ERR(svn_fs_base__populate_uuid(fs, scratch_pool));
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+base_create(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ int format = SVN_FS_BASE__FORMAT_NUMBER;
+ svn_error_t *svn_err;
+
+ /* See if compatibility with older versions was explicitly requested. */
+ if (fs->config)
+ {
+ if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_4_COMPATIBLE))
+ format = 1;
+ else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_5_COMPATIBLE))
+ format = 2;
+ else if (svn_hash_gets(fs->config, SVN_FS_CONFIG_PRE_1_6_COMPATIBLE))
+ format = 3;
+ }
+
+ /* Create the environment and databases. */
+ svn_err = open_databases(fs, TRUE, format, path, pool);
+ if (svn_err) goto error;
+
+ /* Initialize the DAG subsystem. */
+ svn_err = svn_fs_base__dag_init_fs(fs);
+ if (svn_err) goto error;
+
+ /* This filesystem is ready. Stamp it with a format number. */
+ svn_err = svn_io_write_version_file(
+ svn_dirent_join(fs->path, FORMAT_FILE, pool), format, pool);
+ if (svn_err) goto error;
+
+ ((base_fs_data_t *) fs->fsap_data)->format = format;
+
+ SVN_ERR(populate_opened_fs(fs, pool));
+ return SVN_NO_ERROR;;
+
+error:
+ svn_error_clear(cleanup_fs(fs));
+ return svn_err;
+}
+
+
+/* Gaining access to an existing Berkeley DB-based filesystem. */
+
+svn_error_t *
+svn_fs_base__test_required_feature_format(svn_fs_t *fs,
+ const char *feature,
+ int requires)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ if (bfd->format < requires)
+ return svn_error_createf
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("The '%s' feature requires version %d of the filesystem schema; "
+ "filesystem '%s' uses only version %d"),
+ feature, requires, fs->path, bfd->format);
+ return SVN_NO_ERROR;
+}
+
+/* Return the error SVN_ERR_FS_UNSUPPORTED_FORMAT if FS's format
+ number is not the same as the format number supported by this
+ Subversion. */
+static svn_error_t *
+check_format(int format)
+{
+ /* We currently support any format less than the compiled format number
+ simultaneously. */
+ if (format <= SVN_FS_BASE__FORMAT_NUMBER)
+ return SVN_NO_ERROR;
+
+ return svn_error_createf(
+ SVN_ERR_FS_UNSUPPORTED_FORMAT, NULL,
+ _("Expected FS format '%d'; found format '%d'"),
+ SVN_FS_BASE__FORMAT_NUMBER, format);
+}
+
+static svn_error_t *
+base_open(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ int format;
+ svn_error_t *svn_err;
+ svn_boolean_t write_format_file = FALSE;
+
+ /* Read the FS format number. */
+ svn_err = svn_io_read_version_file(&format,
+ svn_dirent_join(path, FORMAT_FILE, pool),
+ pool);
+ if (svn_err && APR_STATUS_IS_ENOENT(svn_err->apr_err))
+ {
+ /* Pre-1.2 filesystems did not have a format file (you could say
+ they were format "0"), so they get upgraded on the fly.
+ However, we stopped "upgrading on the fly" in 1.5, so older
+ filesystems should only be bumped to 1.3, which is format "1". */
+ svn_error_clear(svn_err);
+ svn_err = SVN_NO_ERROR;
+ format = 1;
+ write_format_file = TRUE;
+ }
+ else if (svn_err)
+ goto error;
+
+ /* Create the environment and databases. */
+ svn_err = open_databases(fs, FALSE, format, path, pool);
+ if (svn_err) goto error;
+
+ ((base_fs_data_t *) fs->fsap_data)->format = format;
+ SVN_ERR(check_format(format));
+
+ /* If we lack a format file, write one. */
+ if (write_format_file)
+ {
+ svn_err = svn_io_write_version_file(svn_dirent_join(path, FORMAT_FILE,
+ pool),
+ format, pool);
+ if (svn_err) goto error;
+ }
+
+ SVN_ERR(populate_opened_fs(fs, pool));
+ return SVN_NO_ERROR;
+
+ error:
+ svn_error_clear(cleanup_fs(fs));
+ return svn_err;
+}
+
+
+/* Running recovery on a Berkeley DB-based filesystem. */
+
+
+/* Recover a database at PATH. Perform catastrophic recovery if FATAL
+ is TRUE. Use POOL for temporary allocation. */
+static svn_error_t *
+bdb_recover(const char *path, svn_boolean_t fatal, apr_pool_t *pool)
+{
+ bdb_env_baton_t *bdb;
+
+ /* Here's the comment copied from db_recover.c:
+
+ Initialize the environment -- we don't actually do anything
+ else, that all that's needed to run recovery.
+
+ Note that we specify a private environment, as we're about to
+ create a region, and we don't want to leave it around. If we
+ leave the region around, the application that should create it
+ will simply join it instead, and will then be running with
+ incorrectly sized (and probably terribly small) caches. */
+
+ /* Note that since we're using a private environment, we shoudl
+ /not/ initialize locking. We want the environment files to go
+ away. */
+
+ SVN_ERR(svn_fs_bdb__open(&bdb, path,
+ ((fatal ? DB_RECOVER_FATAL : DB_RECOVER)
+ | SVN_BDB_PRIVATE_ENV_FLAGS),
+ 0666, pool));
+ return svn_fs_bdb__close(bdb);
+}
+
+static svn_error_t *
+base_open_for_recovery(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ /* Just stash the path in the fs pointer - it's all we really need. */
+ fs->path = apr_pstrdup(fs->pool, path);
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+base_upgrade(svn_fs_t *fs, const char *path, apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ const char *version_file_path;
+ int old_format_number;
+ svn_error_t *err;
+
+ version_file_path = svn_dirent_join(path, FORMAT_FILE, pool);
+
+ /* Read the old number so we've got it on hand later on. */
+ err = svn_io_read_version_file(&old_format_number, version_file_path, pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err))
+ {
+ /* Pre-1.2 filesystems do not have a 'format' file. */
+ old_format_number = 0;
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ /* Bump the format file's stored version number. */
+ SVN_ERR(svn_io_write_version_file(version_file_path,
+ SVN_FS_BASE__FORMAT_NUMBER, pool));
+
+ /* Check and see if we need to record the "bump" revision. */
+ if (old_format_number < SVN_FS_BASE__MIN_FORWARD_DELTAS_FORMAT)
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+ svn_revnum_t youngest_rev;
+ const char *value;
+
+ /* Open the filesystem in a subpool (so we can control its
+ closure) and do our fiddling.
+
+ NOTE: By using base_open() here instead of open_databases(),
+ we will end up re-reading the format file that we just wrote.
+ But it's better to use the existing encapsulation of "opening
+ the filesystem" rather than duplicating (or worse, partially
+ duplicating) that logic here. */
+ SVN_ERR(base_open(fs, path, subpool, common_pool));
+
+ /* Fetch the youngest rev, and record it */
+ SVN_ERR(svn_fs_base__youngest_rev(&youngest_rev, fs, subpool));
+ value = apr_psprintf(subpool, "%ld", youngest_rev);
+ SVN_ERR(svn_fs_base__miscellaneous_set
+ (fs, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE,
+ value, subpool));
+ svn_pool_destroy(subpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+base_verify(svn_fs_t *fs, const char *path,
+ svn_revnum_t start,
+ svn_revnum_t end,
+ svn_fs_progress_notify_func_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ /* Verifying is currently a no op for BDB. */
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+base_bdb_recover(svn_fs_t *fs,
+ svn_cancel_func_t cancel_func, void *cancel_baton,
+ apr_pool_t *pool)
+{
+ /* The fs pointer is a fake created in base_open_for_recovery above.
+ We only care about the path. */
+ return bdb_recover(fs->path, FALSE, pool);
+}
+
+static svn_error_t *
+base_bdb_pack(svn_fs_t *fs,
+ const char *path,
+ svn_fs_pack_notify_t notify_func,
+ void *notify_baton,
+ svn_cancel_func_t cancel,
+ void *cancel_baton,
+ apr_pool_t *pool,
+ apr_pool_t *common_pool)
+{
+ /* Packing is currently a no op for BDB. */
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Running the 'archive' command on a Berkeley DB-based filesystem. */
+
+
+static svn_error_t *
+base_bdb_logfiles(apr_array_header_t **logfiles,
+ const char *path,
+ svn_boolean_t only_unused,
+ apr_pool_t *pool)
+{
+ bdb_env_baton_t *bdb;
+ char **filelist;
+ char **filename;
+ u_int32_t flags = only_unused ? 0 : DB_ARCH_LOG;
+
+ *logfiles = apr_array_make(pool, 4, sizeof(const char *));
+
+ SVN_ERR(svn_fs_bdb__open(&bdb, path,
+ SVN_BDB_STANDARD_ENV_FLAGS,
+ 0666, pool));
+ SVN_BDB_ERR(bdb, bdb->env->log_archive(bdb->env, &filelist, flags));
+
+ if (filelist == NULL)
+ return svn_fs_bdb__close(bdb);
+
+ for (filename = filelist; *filename != NULL; ++filename)
+ {
+ APR_ARRAY_PUSH(*logfiles, const char *) = apr_pstrdup(pool, *filename);
+ }
+
+ free(filelist);
+
+ return svn_fs_bdb__close(bdb);
+}
+
+
+
+/* Copying a live Berkeley DB-base filesystem. */
+
+/**
+ * Delete all unused log files from DBD enviroment at @a live_path that exist
+ * in @a backup_path.
+ */
+static svn_error_t *
+svn_fs_base__clean_logs(const char *live_path,
+ const char *backup_path,
+ apr_pool_t *pool)
+{
+ apr_array_header_t *logfiles;
+
+ SVN_ERR(base_bdb_logfiles(&logfiles,
+ live_path,
+ TRUE, /* Only unused logs */
+ pool));
+
+ { /* Process unused logs from live area */
+ int idx;
+ apr_pool_t *sub_pool = svn_pool_create(pool);
+
+ /* Process log files. */
+ for (idx = 0; idx < logfiles->nelts; idx++)
+ {
+ const char *log_file = APR_ARRAY_IDX(logfiles, idx, const char *);
+ const char *live_log_path;
+ const char *backup_log_path;
+
+ svn_pool_clear(sub_pool);
+ live_log_path = svn_dirent_join(live_path, log_file, sub_pool);
+ backup_log_path = svn_dirent_join(backup_path, log_file, sub_pool);
+
+ { /* Compare files. No point in using MD5 and wasting CPU cycles as we
+ got full copies of both logs */
+
+ svn_boolean_t files_match = FALSE;
+ svn_node_kind_t kind;
+
+ /* Check to see if there is a corresponding log file in the backup
+ directory */
+ SVN_ERR(svn_io_check_path(backup_log_path, &kind, pool));
+
+ /* If the copy of the log exists, compare them */
+ if (kind == svn_node_file)
+ SVN_ERR(svn_io_files_contents_same_p(&files_match,
+ live_log_path,
+ backup_log_path,
+ sub_pool));
+
+ /* If log files do not match, go to the next log file. */
+ if (!files_match)
+ continue;
+ }
+
+ SVN_ERR(svn_io_remove_file2(live_log_path, FALSE, sub_pool));
+ }
+
+ svn_pool_destroy(sub_pool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* DB_ENV->get_flags() and DB->get_pagesize() don't exist prior to
+ Berkeley DB 4.2. */
+#if SVN_BDB_VERSION_AT_LEAST(4, 2)
+
+/* Open the BDB environment at PATH and compare its configuration
+ flags with FLAGS. If every flag in FLAGS is set in the
+ environment, then set *MATCH to true. Else set *MATCH to false. */
+static svn_error_t *
+check_env_flags(svn_boolean_t *match,
+ u_int32_t flags,
+ const char *path,
+ apr_pool_t *pool)
+{
+ bdb_env_baton_t *bdb;
+#if SVN_BDB_VERSION_AT_LEAST(4, 7)
+ int flag_state;
+#else
+ u_int32_t envflags;
+#endif
+
+ SVN_ERR(svn_fs_bdb__open(&bdb, path,
+ SVN_BDB_STANDARD_ENV_FLAGS,
+ 0666, pool));
+#if SVN_BDB_VERSION_AT_LEAST(4, 7)
+ SVN_BDB_ERR(bdb, bdb->env->log_get_config(bdb->env, flags, &flag_state));
+#else
+ SVN_BDB_ERR(bdb, bdb->env->get_flags(bdb->env, &envflags));
+#endif
+
+ SVN_ERR(svn_fs_bdb__close(bdb));
+
+#if SVN_BDB_VERSION_AT_LEAST(4, 7)
+ if (flag_state == 0)
+#else
+ if (flags & envflags)
+#endif
+ *match = TRUE;
+ else
+ *match = FALSE;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *PAGESIZE to the size of pages used to hold items in the
+ database environment located at PATH.
+*/
+static svn_error_t *
+get_db_pagesize(u_int32_t *pagesize,
+ const char *path,
+ apr_pool_t *pool)
+{
+ bdb_env_baton_t *bdb;
+ DB *nodes_table;
+
+ SVN_ERR(svn_fs_bdb__open(&bdb, path,
+ SVN_BDB_STANDARD_ENV_FLAGS,
+ 0666, pool));
+
+ /* ### We're only asking for the pagesize on the 'nodes' table.
+ Is this enough? We never call DB->set_pagesize() on any of
+ our tables, so presumably BDB is using the same default
+ pagesize for all our databases, right? */
+ SVN_BDB_ERR(bdb, svn_fs_bdb__open_nodes_table(&nodes_table, bdb->env,
+ FALSE));
+ SVN_BDB_ERR(bdb, nodes_table->get_pagesize(nodes_table, pagesize));
+ SVN_BDB_ERR(bdb, nodes_table->close(nodes_table, 0));
+
+ return svn_fs_bdb__close(bdb);
+}
+#endif /* SVN_BDB_VERSION_AT_LEAST(4, 2) */
+
+
+/* Copy FILENAME from SRC_DIR to DST_DIR in byte increments of size
+ CHUNKSIZE. The read/write buffer of size CHUNKSIZE will be
+ allocated in POOL. If ALLOW_MISSING is set, we won't make a fuss
+ if FILENAME isn't found in SRC_DIR; otherwise, we will. */
+static svn_error_t *
+copy_db_file_safely(const char *src_dir,
+ const char *dst_dir,
+ const char *filename,
+ u_int32_t chunksize,
+ svn_boolean_t allow_missing,
+ apr_pool_t *pool)
+{
+ apr_file_t *s = NULL, *d = NULL; /* init to null important for APR */
+ const char *file_src_path = svn_dirent_join(src_dir, filename, pool);
+ const char *file_dst_path = svn_dirent_join(dst_dir, filename, pool);
+ svn_error_t *err;
+ char *buf;
+
+ /* Open source file. If it's missing and that's allowed, there's
+ nothing more to do here. */
+ err = svn_io_file_open(&s, file_src_path,
+ (APR_READ | APR_LARGEFILE),
+ APR_OS_DEFAULT, pool);
+ if (err && APR_STATUS_IS_ENOENT(err->apr_err) && allow_missing)
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+
+ /* Open destination file. */
+ SVN_ERR(svn_io_file_open(&d, file_dst_path, (APR_WRITE | APR_CREATE |
+ APR_LARGEFILE),
+ APR_OS_DEFAULT, pool));
+
+ /* Allocate our read/write buffer. */
+ buf = apr_palloc(pool, chunksize);
+
+ /* Copy bytes till the cows come home. */
+ while (1)
+ {
+ apr_size_t bytes_this_time = chunksize;
+ svn_error_t *read_err, *write_err;
+
+ /* Read 'em. */
+ if ((read_err = svn_io_file_read(s, buf, &bytes_this_time, pool)))
+ {
+ if (APR_STATUS_IS_EOF(read_err->apr_err))
+ svn_error_clear(read_err);
+ else
+ {
+ svn_error_clear(svn_io_file_close(s, pool));
+ svn_error_clear(svn_io_file_close(d, pool));
+ return read_err;
+ }
+ }
+
+ /* Write 'em. */
+ if ((write_err = svn_io_file_write_full(d, buf, bytes_this_time, NULL,
+ pool)))
+ {
+ svn_error_clear(svn_io_file_close(s, pool));
+ svn_error_clear(svn_io_file_close(d, pool));
+ return write_err;
+ }
+
+ /* read_err is either NULL, or a dangling pointer - but it is only a
+ dangling pointer if it used to be an EOF error. */
+ if (read_err)
+ {
+ SVN_ERR(svn_io_file_close(s, pool));
+ SVN_ERR(svn_io_file_close(d, pool));
+ break; /* got EOF on read, all files closed, all done. */
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+
+
+static svn_error_t *
+base_hotcopy(svn_fs_t *src_fs,
+ svn_fs_t *dst_fs,
+ const char *src_path,
+ const char *dest_path,
+ svn_boolean_t clean_logs,
+ svn_boolean_t incremental,
+ svn_cancel_func_t cancel_func,
+ void *cancel_baton,
+ apr_pool_t *pool)
+{
+ svn_error_t *err;
+ u_int32_t pagesize;
+ svn_boolean_t log_autoremove = FALSE;
+ int format;
+
+ if (incremental)
+ return svn_error_createf(SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("BDB repositories do not support incremental "
+ "hotcopy"));
+
+ /* Check the FS format number to be certain that we know how to
+ hotcopy this FS. Pre-1.2 filesystems did not have a format file (you
+ could say they were format "0"), so we will error here. This is not
+ optimal, but since this has been the case since 1.2.0, and no one has
+ complained, it apparently isn't much of a concern. (We did not check
+ the 'format' file in 1.2.x, but we did blindly try to copy 'locks',
+ which would have errored just the same.) */
+ SVN_ERR(svn_io_read_version_file(
+ &format, svn_dirent_join(src_path, FORMAT_FILE, pool), pool));
+ SVN_ERR(check_format(format));
+
+ /* If using Berkeley DB 4.2 or later, note whether the DB_LOG_AUTO_REMOVE
+ feature is on. If it is, we have a potential race condition:
+ another process might delete a logfile while we're in the middle
+ of copying all the logfiles. (This is not a huge deal; at worst,
+ the hotcopy fails with a file-not-found error.) */
+#if SVN_BDB_VERSION_AT_LEAST(4, 2)
+ err = check_env_flags(&log_autoremove,
+#if SVN_BDB_VERSION_AT_LEAST(4, 7)
+ DB_LOG_AUTO_REMOVE,
+ /* DB_LOG_AUTO_REMOVE was named DB_LOG_AUTOREMOVE before Berkeley DB 4.7. */
+#else
+ DB_LOG_AUTOREMOVE,
+#endif
+ src_path, pool);
+#endif
+ SVN_ERR(err);
+
+ /* Copy the DB_CONFIG file. */
+ SVN_ERR(svn_io_dir_file_copy(src_path, dest_path, "DB_CONFIG", pool));
+
+ /* In order to copy the database files safely and atomically, we
+ must copy them in chunks which are multiples of the page-size
+ used by BDB. See sleepycat docs for details, or svn issue #1818. */
+#if SVN_BDB_VERSION_AT_LEAST(4, 2)
+ SVN_ERR(get_db_pagesize(&pagesize, src_path, pool));
+ if (pagesize < SVN__STREAM_CHUNK_SIZE)
+ {
+ /* use the largest multiple of BDB pagesize we can. */
+ int multiple = SVN__STREAM_CHUNK_SIZE / pagesize;
+ pagesize *= multiple;
+ }
+#else
+ /* default to 128K chunks, which should be safe.
+ BDB almost certainly uses a power-of-2 pagesize. */
+ pagesize = (4096 * 32);
+#endif
+
+ /* Copy the databases. */
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "nodes", pagesize, FALSE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "transactions", pagesize, FALSE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "revisions", pagesize, FALSE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "copies", pagesize, FALSE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "changes", pagesize, FALSE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "representations", pagesize, FALSE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "strings", pagesize, FALSE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "uuids", pagesize, TRUE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "locks", pagesize, TRUE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "lock-tokens", pagesize, TRUE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "node-origins", pagesize, TRUE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "checksum-reps", pagesize, TRUE, pool));
+ SVN_ERR(copy_db_file_safely(src_path, dest_path,
+ "miscellaneous", pagesize, TRUE, pool));
+
+ {
+ apr_array_header_t *logfiles;
+ int idx;
+ apr_pool_t *subpool;
+
+ SVN_ERR(base_bdb_logfiles(&logfiles,
+ src_path,
+ FALSE, /* All logs */
+ pool));
+
+ /* Process log files. */
+ subpool = svn_pool_create(pool);
+ for (idx = 0; idx < logfiles->nelts; idx++)
+ {
+ svn_pool_clear(subpool);
+ err = svn_io_dir_file_copy(src_path, dest_path,
+ APR_ARRAY_IDX(logfiles, idx,
+ const char *),
+ subpool);
+ if (err)
+ {
+ if (log_autoremove)
+ return
+ svn_error_quick_wrap
+ (err,
+ _("Error copying logfile; the DB_LOG_AUTOREMOVE feature\n"
+ "may be interfering with the hotcopy algorithm. If\n"
+ "the problem persists, try deactivating this feature\n"
+ "in DB_CONFIG"));
+ else
+ return svn_error_trace(err);
+ }
+ }
+ svn_pool_destroy(subpool);
+ }
+
+ /* Since this is a copy we will have exclusive access to the repository. */
+ err = bdb_recover(dest_path, TRUE, pool);
+ if (err)
+ {
+ if (log_autoremove)
+ return
+ svn_error_quick_wrap
+ (err,
+ _("Error running catastrophic recovery on hotcopy; the\n"
+ "DB_LOG_AUTOREMOVE feature may be interfering with the\n"
+ "hotcopy algorithm. If the problem persists, try deactivating\n"
+ "this feature in DB_CONFIG"));
+ else
+ return svn_error_trace(err);
+ }
+
+ /* Only now that the hotcopied filesystem is complete,
+ stamp it with a format file. */
+ SVN_ERR(svn_io_write_version_file(
+ svn_dirent_join(dest_path, FORMAT_FILE, pool), format, pool));
+
+ if (clean_logs)
+ SVN_ERR(svn_fs_base__clean_logs(src_path, dest_path, pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Deleting a Berkeley DB-based filesystem. */
+
+
+static svn_error_t *
+base_delete_fs(const char *path,
+ apr_pool_t *pool)
+{
+ /* First, use the Berkeley DB library function to remove any shared
+ memory segments. */
+ SVN_ERR(svn_fs_bdb__remove(path, pool));
+
+ /* Remove the environment directory. */
+ return svn_io_remove_dir2(path, FALSE, NULL, NULL, pool);
+}
+
+static const svn_version_t *
+base_version(void)
+{
+ SVN_VERSION_BODY;
+}
+
+static const char *
+base_get_description(void)
+{
+ return _("Module for working with a Berkeley DB repository.");
+}
+
+static svn_error_t *
+base_set_svn_fs_open(svn_fs_t *fs,
+ svn_error_t *(*svn_fs_open_)(svn_fs_t **,
+ const char *,
+ apr_hash_t *,
+ apr_pool_t *))
+{
+ return SVN_NO_ERROR;
+}
+
+
+/* Base FS library vtable, used by the FS loader library. */
+static fs_library_vtable_t library_vtable = {
+ base_version,
+ base_create,
+ base_open,
+ base_open_for_recovery,
+ base_upgrade,
+ base_verify,
+ base_delete_fs,
+ base_hotcopy,
+ base_get_description,
+ base_bdb_recover,
+ base_bdb_pack,
+ base_bdb_logfiles,
+ svn_fs_base__id_parse,
+ base_set_svn_fs_open
+};
+
+svn_error_t *
+svn_fs_base__init(const svn_version_t *loader_version,
+ fs_library_vtable_t **vtable, apr_pool_t* common_pool)
+{
+ static const svn_version_checklist_t checklist[] =
+ {
+ { "svn_subr", svn_subr_version },
+ { "svn_delta", svn_delta_version },
+ { NULL, NULL }
+ };
+
+ /* Simplified version check to make sure we can safely use the
+ VTABLE parameter. The FS loader does a more exhaustive check. */
+ if (loader_version->major != SVN_VER_MAJOR)
+ return svn_error_createf(SVN_ERR_VERSION_MISMATCH, NULL,
+ _("Unsupported FS loader version (%d) for bdb"),
+ loader_version->major);
+ SVN_ERR(svn_ver_check_list(base_version(), checklist));
+ SVN_ERR(check_bdb_version());
+ SVN_ERR(svn_fs_bdb__init(common_pool));
+
+ *vtable = &library_vtable;
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_base/fs.h b/subversion/libsvn_fs_base/fs.h
new file mode 100644
index 0000000..017c898
--- /dev/null
+++ b/subversion/libsvn_fs_base/fs.h
@@ -0,0 +1,357 @@
+/* fs.h : interface to Subversion filesystem, private to libsvn_fs
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_BASE_H
+#define SVN_LIBSVN_FS_BASE_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+#include "svn_fs.h"
+
+#include "bdb/env.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** Filesystem schema versions ***/
+
+/* The format number of this filesystem. This is independent of the
+ repository format number, and independent of any other FS back
+ ends. See the SVN_FS_BASE__MIN_*_FORMAT defines to get a sense of
+ what changes and features were added in which versions of this
+ back-end's format. */
+#define SVN_FS_BASE__FORMAT_NUMBER 4
+
+/* Minimum format number that supports representation sharing. This
+ also brings in the support for storing SHA1 checksums. */
+#define SVN_FS_BASE__MIN_REP_SHARING_FORMAT 4
+
+/* Minimum format number that supports the 'miscellaneous' table */
+#define SVN_FS_BASE__MIN_MISCELLANY_FORMAT 4
+
+/* Minimum format number that supports forward deltas */
+#define SVN_FS_BASE__MIN_FORWARD_DELTAS_FORMAT 4
+
+/* Minimum format number that supports node-origins tracking */
+#define SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT 3
+
+/* Minimum format number that supports mergeinfo */
+#define SVN_FS_BASE__MIN_MERGEINFO_FORMAT 3
+
+/* Minimum format number that supports svndiff version 1. */
+#define SVN_FS_BASE__MIN_SVNDIFF1_FORMAT 2
+
+/* Return SVN_ERR_UNSUPPORTED_FEATURE if the version of filesystem FS does
+ not indicate support for FEATURE (which REQUIRES a newer version). */
+svn_error_t *
+svn_fs_base__test_required_feature_format(svn_fs_t *fs,
+ const char *feature,
+ int requires);
+
+
+
+/*** Miscellany keys. ***/
+
+/* Revision at which the repo started using forward deltas. */
+#define SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE "forward-delta-rev"
+
+
+
+/*** The filesystem structure. ***/
+
+typedef struct base_fs_data_t
+{
+ /* A Berkeley DB environment for all the filesystem's databases.
+ This establishes the scope of the filesystem's transactions. */
+ bdb_env_baton_t *bdb;
+
+ /* The filesystem's various tables. See `structure' for details. */
+ DB *changes;
+ DB *copies;
+ DB *nodes;
+ DB *representations;
+ DB *revisions;
+ DB *strings;
+ DB *transactions;
+ DB *uuids;
+ DB *locks;
+ DB *lock_tokens;
+ DB *node_origins;
+ DB *miscellaneous;
+ DB *checksum_reps;
+
+ /* A boolean for tracking when we have a live Berkeley DB
+ transaction trail alive. */
+ svn_boolean_t in_txn_trail;
+
+ /* The format number of this FS. */
+ int format;
+
+} base_fs_data_t;
+
+
+/*** Filesystem Revision ***/
+typedef struct revision_t
+{
+ /* id of the transaction that was committed to create this
+ revision. */
+ const char *txn_id;
+
+} revision_t;
+
+
+/*** Transaction Kind ***/
+typedef enum transaction_kind_t
+{
+ transaction_kind_normal = 1, /* normal, uncommitted */
+ transaction_kind_committed, /* committed */
+ transaction_kind_dead /* uncommitted and dead */
+
+} transaction_kind_t;
+
+
+/*** Filesystem Transaction ***/
+typedef struct transaction_t
+{
+ /* kind of transaction. */
+ transaction_kind_t kind;
+
+ /* revision which this transaction was committed to create, or an
+ invalid revision number if this transaction was never committed. */
+ svn_revnum_t revision;
+
+ /* property list (const char * name, svn_string_t * value).
+ may be NULL if there are no properties. */
+ apr_hash_t *proplist;
+
+ /* node revision id of the root node. */
+ const svn_fs_id_t *root_id;
+
+ /* node revision id of the node which is the root of the revision
+ upon which this txn is base. (unfinished only) */
+ const svn_fs_id_t *base_id;
+
+ /* copies list (const char * copy_ids), or NULL if there have been
+ no copies in this transaction. */
+ apr_array_header_t *copies;
+
+} transaction_t;
+
+
+/*** Node-Revision ***/
+typedef struct node_revision_t
+{
+ /* node kind */
+ svn_node_kind_t kind;
+
+ /* predecessor node revision id, or NULL if there is no predecessor
+ for this node revision */
+ const svn_fs_id_t *predecessor_id;
+
+ /* number of predecessors this node revision has (recursively), or
+ -1 if not known (for backward compatibility). */
+ int predecessor_count;
+
+ /* representation key for this node's properties. may be NULL if
+ there are no properties. */
+ const char *prop_key;
+
+ /* representation key for this node's text data (files) or entries
+ list (dirs). may be NULL if there are no contents. */
+ const char *data_key;
+
+ /* data representation instance identifier. Sounds fancy, but is
+ really just a way to distinguish between "I use the same rep key
+ as another node because we share ancestry and haven't had our
+ text touched at all" and "I use the same rep key as another node
+ only because one or both of us decided to pick up a shared
+ representation after-the-fact." May be NULL (if this node
+ revision isn't using a shared rep, or isn't the original
+ "assignee" of a shared rep). */
+ const char *data_key_uniquifier;
+
+ /* representation key for this node's text-data-in-progess (files
+ only). NULL if no edits are currently in-progress. This field
+ is always NULL for kinds other than "file". */
+ const char *edit_key;
+
+ /* path at which this node first came into existence. */
+ const char *created_path;
+
+ /* does this node revision have the mergeinfo tracking property set
+ on it? (only valid for FS schema 3 and newer) */
+ svn_boolean_t has_mergeinfo;
+
+ /* number of children of this node which have the mergeinfo tracking
+ property set (0 for files; valid only for FS schema 3 and newer). */
+ apr_int64_t mergeinfo_count;
+
+} node_revision_t;
+
+
+/*** Representation Kind ***/
+typedef enum rep_kind_t
+{
+ rep_kind_fulltext = 1, /* fulltext */
+ rep_kind_delta /* delta */
+
+} rep_kind_t;
+
+
+/*** "Delta" Offset/Window Chunk ***/
+typedef struct rep_delta_chunk_t
+{
+ /* diff format version number ### at this point, "svndiff" is the
+ only format used. */
+ apr_byte_t version;
+
+ /* starting offset of the data represented by this chunk */
+ svn_filesize_t offset;
+
+ /* string-key to which this representation points. */
+ const char *string_key;
+
+ /* size of the fulltext data represented by this delta window. */
+ apr_size_t size;
+
+ /* representation-key to use when needed source data for
+ undeltification. */
+ const char *rep_key;
+
+ /* apr_off_t rep_offset; ### not implemented */
+
+} rep_delta_chunk_t;
+
+
+/*** Representation ***/
+typedef struct representation_t
+{
+ /* representation kind */
+ rep_kind_t kind;
+
+ /* transaction ID under which representation was created (used as a
+ mutability flag when compared with a current editing
+ transaction). */
+ const char *txn_id;
+
+ /* Checksums for the contents produced by this representation.
+ These checksum is for the contents the rep shows to consumers,
+ regardless of how the rep stores the data under the hood. It is
+ independent of the storage (fulltext, delta, whatever).
+
+ If this is NULL, then for compatibility behave as though
+ this checksum matches the expected checksum. */
+ svn_checksum_t *md5_checksum;
+ svn_checksum_t *sha1_checksum;
+
+ /* kind-specific stuff */
+ union
+ {
+ /* fulltext stuff */
+ struct
+ {
+ /* string-key which holds the fulltext data */
+ const char *string_key;
+
+ } fulltext;
+
+ /* delta stuff */
+ struct
+ {
+ /* an array of rep_delta_chunk_t * chunks of delta
+ information */
+ apr_array_header_t *chunks;
+
+ } delta;
+ } contents;
+} representation_t;
+
+
+/*** Copy Kind ***/
+typedef enum copy_kind_t
+{
+ copy_kind_real = 1, /* real copy */
+ copy_kind_soft /* soft copy */
+
+} copy_kind_t;
+
+
+/*** Copy ***/
+typedef struct copy_t
+{
+ /* What kind of copy occurred. */
+ copy_kind_t kind;
+
+ /* Path of copy source. */
+ const char *src_path;
+
+ /* Transaction id of copy source. */
+ const char *src_txn_id;
+
+ /* Node-revision of copy destination. */
+ const svn_fs_id_t *dst_noderev_id;
+
+} copy_t;
+
+
+/*** Change ***/
+typedef struct change_t
+{
+ /* Path of the change. */
+ const char *path;
+
+ /* Node revision ID of the change. */
+ const svn_fs_id_t *noderev_id;
+
+ /* The kind of change. */
+ svn_fs_path_change_kind_t kind;
+
+ /* Text or property mods? */
+ svn_boolean_t text_mod;
+ svn_boolean_t prop_mod;
+
+} change_t;
+
+
+/*** Lock node ***/
+typedef struct lock_node_t
+{
+ /* entries list, maps (const char *) name --> (const char *) lock-node-id */
+ apr_hash_t *entries;
+
+ /* optional lock-token, might be NULL. */
+ const char *lock_token;
+
+} lock_node_t;
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_BASE_H */
diff --git a/subversion/libsvn_fs_base/id.c b/subversion/libsvn_fs_base/id.c
new file mode 100644
index 0000000..c063d02
--- /dev/null
+++ b/subversion/libsvn_fs_base/id.c
@@ -0,0 +1,208 @@
+/* id.c : operations on node-revision IDs
+ *
+ * ====================================================================
+ * 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 <stdlib.h>
+
+#include "id.h"
+#include "../libsvn_fs/fs-loader.h"
+
+
+
+typedef struct id_private_t {
+ const char *node_id;
+ const char *copy_id;
+ const char *txn_id;
+} id_private_t;
+
+
+/* Accessing ID Pieces. */
+
+const char *
+svn_fs_base__id_node_id(const svn_fs_id_t *id)
+{
+ id_private_t *pvt = id->fsap_data;
+
+ return pvt->node_id;
+}
+
+
+const char *
+svn_fs_base__id_copy_id(const svn_fs_id_t *id)
+{
+ id_private_t *pvt = id->fsap_data;
+
+ return pvt->copy_id;
+}
+
+
+const char *
+svn_fs_base__id_txn_id(const svn_fs_id_t *id)
+{
+ id_private_t *pvt = id->fsap_data;
+
+ return pvt->txn_id;
+}
+
+
+svn_string_t *
+svn_fs_base__id_unparse(const svn_fs_id_t *id,
+ apr_pool_t *pool)
+{
+ id_private_t *pvt = id->fsap_data;
+
+ return svn_string_createf(pool, "%s.%s.%s",
+ pvt->node_id, pvt->copy_id, pvt->txn_id);
+}
+
+
+/*** Comparing node IDs ***/
+
+svn_boolean_t
+svn_fs_base__id_eq(const svn_fs_id_t *a,
+ const svn_fs_id_t *b)
+{
+ id_private_t *pvta = a->fsap_data, *pvtb = b->fsap_data;
+
+ if (a == b)
+ return TRUE;
+ if (strcmp(pvta->node_id, pvtb->node_id) != 0)
+ return FALSE;
+ if (strcmp(pvta->copy_id, pvtb->copy_id) != 0)
+ return FALSE;
+ if (strcmp(pvta->txn_id, pvtb->txn_id) != 0)
+ return FALSE;
+ return TRUE;
+}
+
+
+svn_boolean_t
+svn_fs_base__id_check_related(const svn_fs_id_t *a,
+ const svn_fs_id_t *b)
+{
+ id_private_t *pvta = a->fsap_data, *pvtb = b->fsap_data;
+
+ if (a == b)
+ return TRUE;
+
+ return (strcmp(pvta->node_id, pvtb->node_id) == 0);
+}
+
+
+int
+svn_fs_base__id_compare(const svn_fs_id_t *a,
+ const svn_fs_id_t *b)
+{
+ if (svn_fs_base__id_eq(a, b))
+ return 0;
+ return (svn_fs_base__id_check_related(a, b) ? 1 : -1);
+}
+
+
+
+/* Creating ID's. */
+
+static id_vtable_t id_vtable = {
+ svn_fs_base__id_unparse,
+ svn_fs_base__id_compare
+};
+
+
+svn_fs_id_t *
+svn_fs_base__id_create(const char *node_id,
+ const char *copy_id,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *id = apr_palloc(pool, sizeof(*id));
+ id_private_t *pvt = apr_palloc(pool, sizeof(*pvt));
+
+ pvt->node_id = apr_pstrdup(pool, node_id);
+ pvt->copy_id = apr_pstrdup(pool, copy_id);
+ pvt->txn_id = apr_pstrdup(pool, txn_id);
+ id->vtable = &id_vtable;
+ id->fsap_data = pvt;
+ return id;
+}
+
+
+svn_fs_id_t *
+svn_fs_base__id_copy(const svn_fs_id_t *id, apr_pool_t *pool)
+{
+ svn_fs_id_t *new_id = apr_palloc(pool, sizeof(*new_id));
+ id_private_t *new_pvt = apr_palloc(pool, sizeof(*new_pvt));
+ id_private_t *pvt = id->fsap_data;
+
+ new_pvt->node_id = apr_pstrdup(pool, pvt->node_id);
+ new_pvt->copy_id = apr_pstrdup(pool, pvt->copy_id);
+ new_pvt->txn_id = apr_pstrdup(pool, pvt->txn_id);
+ new_id->vtable = &id_vtable;
+ new_id->fsap_data = new_pvt;
+ return new_id;
+}
+
+
+svn_fs_id_t *
+svn_fs_base__id_parse(const char *data,
+ apr_size_t len,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *id;
+ id_private_t *pvt;
+ char *data_copy, *str;
+
+ /* Dup the ID data into POOL. Our returned ID will have references
+ into this memory. */
+ data_copy = apr_pstrmemdup(pool, data, len);
+
+ /* Alloc a new svn_fs_id_t structure. */
+ id = apr_palloc(pool, sizeof(*id));
+ pvt = apr_palloc(pool, sizeof(*pvt));
+ id->vtable = &id_vtable;
+ id->fsap_data = pvt;
+
+ /* Now, we basically just need to "split" this data on `.'
+ characters. We will use svn_cstring_tokenize, which will put
+ terminators where each of the '.'s used to be. Then our new
+ id field will reference string locations inside our duplicate
+ string.*/
+
+ /* Node Id */
+ str = svn_cstring_tokenize(".", &data_copy);
+ if (str == NULL)
+ return NULL;
+ pvt->node_id = str;
+
+ /* Copy Id */
+ str = svn_cstring_tokenize(".", &data_copy);
+ if (str == NULL)
+ return NULL;
+ pvt->copy_id = str;
+
+ /* Txn Id */
+ str = svn_cstring_tokenize(".", &data_copy);
+ if (str == NULL)
+ return NULL;
+ pvt->txn_id = str;
+
+ return id;
+}
diff --git a/subversion/libsvn_fs_base/id.h b/subversion/libsvn_fs_base/id.h
new file mode 100644
index 0000000..4cdb45c
--- /dev/null
+++ b/subversion/libsvn_fs_base/id.h
@@ -0,0 +1,81 @@
+/* id.h : interface to node ID functions, private to libsvn_fs_base
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_BASE_ID_H
+#define SVN_LIBSVN_FS_BASE_ID_H
+
+#include "svn_fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** ID accessor functions. ***/
+
+/* Get the "node id" portion of ID. */
+const char *svn_fs_base__id_node_id(const svn_fs_id_t *id);
+
+/* Get the "copy id" portion of ID. */
+const char *svn_fs_base__id_copy_id(const svn_fs_id_t *id);
+
+/* Get the "txn id" portion of ID. */
+const char *svn_fs_base__id_txn_id(const svn_fs_id_t *id);
+
+/* Convert ID into string form, allocated in POOL. */
+svn_string_t *svn_fs_base__id_unparse(const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+/* Return true if A and B are equal. */
+svn_boolean_t svn_fs_base__id_eq(const svn_fs_id_t *a,
+ const svn_fs_id_t *b);
+
+/* Return true if A and B are related. */
+svn_boolean_t svn_fs_base__id_check_related(const svn_fs_id_t *a,
+ const svn_fs_id_t *b);
+
+/* Return 0 if A and B are equal, 1 if they are related, -1 otherwise. */
+int svn_fs_base__id_compare(const svn_fs_id_t *a,
+ const svn_fs_id_t *b);
+
+/* Create an ID based on NODE_ID, COPY_ID, and TXN_ID, allocated in
+ POOL. */
+svn_fs_id_t *svn_fs_base__id_create(const char *node_id,
+ const char *copy_id,
+ const char *txn_id,
+ apr_pool_t *pool);
+
+/* Return a copy of ID, allocated from POOL. */
+svn_fs_id_t *svn_fs_base__id_copy(const svn_fs_id_t *id,
+ apr_pool_t *pool);
+
+/* Return an ID resulting from parsing the string DATA (with length
+ LEN), or NULL if DATA is an invalid ID string. */
+svn_fs_id_t *svn_fs_base__id_parse(const char *data,
+ apr_size_t len,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_ID_H */
diff --git a/subversion/libsvn_fs_base/key-gen.c b/subversion/libsvn_fs_base/key-gen.c
new file mode 100644
index 0000000..411207d
--- /dev/null
+++ b/subversion/libsvn_fs_base/key-gen.c
@@ -0,0 +1,131 @@
+/* key-gen.c --- manufacturing sequential keys for some db tables
+ *
+ * ====================================================================
+ * 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 <assert.h>
+#include <string.h>
+
+#define APR_WANT_STRFUNC
+#include <apr_want.h>
+#include <stdlib.h>
+#include <apr.h>
+
+#include "key-gen.h"
+
+
+
+/*** Keys for reps and strings. ***/
+
+
+void
+svn_fs_base__next_key(const char *this, apr_size_t *len, char *next)
+{
+ apr_size_t olen = *len; /* remember the first length */
+ int i = olen - 1; /* initial index; we work backwards */
+ char c; /* current char */
+ svn_boolean_t carry = TRUE; /* boolean: do we have a carry or not?
+ We start with a carry, because we're
+ incrementing the number, after all. */
+
+ /* Leading zeros are not allowed, except for the string "0". */
+ if ((*len > 1) && (this[0] == '0'))
+ {
+ *len = 0;
+ return;
+ }
+
+ for (i = (olen - 1); i >= 0; i--)
+ {
+ c = this[i];
+
+ /* Validate as we go. */
+ if (! (((c >= '0') && (c <= '9')) || ((c >= 'a') && (c <= 'z'))))
+ {
+ *len = 0;
+ return;
+ }
+
+ if (carry)
+ {
+ if (c == 'z')
+ next[i] = '0';
+ else
+ {
+ carry = FALSE;
+
+ if (c == '9')
+ next[i] = 'a';
+ else
+ next[i] = c + 1;
+ }
+ }
+ else
+ next[i] = c;
+ }
+
+ /* The new length is OLEN, plus 1 if there's a carry out of the
+ leftmost digit. */
+ *len = olen + (carry ? 1 : 0);
+
+ /* Ensure that we haven't overrun the (ludicrous) bound on key length.
+ Note that MAX_KEY_SIZE is a bound on the size *including*
+ the trailing null byte. */
+ assert(*len < MAX_KEY_SIZE);
+
+ /* Now we know it's safe to add the null terminator. */
+ next[*len] = '\0';
+
+ /* Handle any leftover carry. */
+ if (carry)
+ {
+ memmove(next+1, next, olen);
+ next[0] = '1';
+ }
+}
+
+
+int
+svn_fs_base__key_compare(const char *a, const char *b)
+{
+ int a_len = strlen(a);
+ int b_len = strlen(b);
+ int cmp;
+
+ if (a_len > b_len)
+ return 1;
+ if (b_len > a_len)
+ return -1;
+ cmp = strcmp(a, b);
+ return (cmp ? (cmp / abs(cmp)) : 0);
+}
+
+
+svn_boolean_t
+svn_fs_base__same_keys(const char *a, const char *b)
+{
+ if (! (a || b))
+ return TRUE;
+ if (a && (! b))
+ return FALSE;
+ if ((! a) && b)
+ return FALSE;
+ return (strcmp(a, b) == 0);
+}
diff --git a/subversion/libsvn_fs_base/key-gen.h b/subversion/libsvn_fs_base/key-gen.h
new file mode 100644
index 0000000..e1cd1aa
--- /dev/null
+++ b/subversion/libsvn_fs_base/key-gen.h
@@ -0,0 +1,100 @@
+/* key-gen.c --- manufacturing sequential keys for some db tables
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_KEY_GEN_H
+#define SVN_LIBSVN_FS_KEY_GEN_H
+
+#include <apr.h>
+
+#include "svn_types.h"
+#include "private/svn_skel.h" /* ### for svn_fs_base__{get,put}size() */
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* The alphanumeric keys passed in and out of svn_fs_base__next_key
+ are guaranteed never to be longer than this many bytes,
+ *including* the trailing null byte. It is therefore safe
+ to declare a key as "char key[MAX_KEY_SIZE]".
+
+ Note that this limit will be a problem if the number of
+ keys in a table ever exceeds
+
+ 18217977168218728251394687124089371267338971528174
+ 76066745969754933395997209053270030282678007662838
+ 67331479599455916367452421574456059646801054954062
+ 15017704234999886990788594743994796171248406730973
+ 80736524850563115569208508785942830080999927310762
+ 50733948404739350551934565743979678824151197232629
+ 947748581376,
+
+ but that's a risk we'll live with for now. */
+#define MAX_KEY_SIZE 200
+
+/* In many of the databases, the value at this key is the key to use
+ when storing a new record. */
+#define NEXT_KEY_KEY "next-key"
+
+
+/* Generate the next key after a given alphanumeric key.
+ *
+ * The first *LEN bytes of THIS are an ascii representation of a
+ * number in base 36: digits 0-9 have their usual values, and a-z have
+ * values 10-35.
+ *
+ * The new key is stored in NEXT, null-terminated. NEXT must be at
+ * least *LEN + 2 bytes long -- one extra byte to hold a possible
+ * overflow column, and one for null termination. On return, *LEN
+ * will be set to the length of the new key, not counting the null
+ * terminator. In other words, the outgoing *LEN will be either equal
+ * to the incoming, or to the incoming + 1.
+ *
+ * If THIS contains anything other than digits and lower-case
+ * alphabetic characters, or if it starts with `0' but is not the
+ * string "0", then *LEN is set to zero and the effect on NEXT
+ * is undefined.
+ */
+void svn_fs_base__next_key(const char *this, apr_size_t *len, char *next);
+
+
+/* Compare two strings A and B as base-36 alphanumeric keys.
+ *
+ * Return -1, 0, or 1 if A is less than, equal to, or greater than B,
+ * respectively.
+ */
+int svn_fs_base__key_compare(const char *a, const char *b);
+
+/* Compare two strings A and B as base-36 alphanumber keys.
+ *
+ * Return TRUE iff both keys are NULL or both keys have the same
+ * contents.
+ */
+svn_boolean_t svn_fs_base__same_keys(const char *a, const char *b);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_KEY_GEN_H */
diff --git a/subversion/libsvn_fs_base/lock.c b/subversion/libsvn_fs_base/lock.c
new file mode 100644
index 0000000..79f72cc
--- /dev/null
+++ b/subversion/libsvn_fs_base/lock.c
@@ -0,0 +1,594 @@
+/* lock.c : functions for manipulating filesystem locks.
+ *
+ * ====================================================================
+ * 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 "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_fs.h"
+#include "svn_private_config.h"
+
+#include <apr_uuid.h>
+
+#include "lock.h"
+#include "tree.h"
+#include "err.h"
+#include "bdb/locks-table.h"
+#include "bdb/lock-tokens-table.h"
+#include "util/fs_skels.h"
+#include "../libsvn_fs/fs-loader.h"
+#include "private/svn_fs_util.h"
+#include "private/svn_subr_private.h"
+#include "private/svn_dep_compat.h"
+
+
+/* Add LOCK and its associated LOCK_TOKEN (associated with PATH) as
+ part of TRAIL. */
+static svn_error_t *
+add_lock_and_token(svn_lock_t *lock,
+ const char *lock_token,
+ const char *path,
+ trail_t *trail)
+{
+ SVN_ERR(svn_fs_bdb__lock_add(trail->fs, lock_token, lock,
+ trail, trail->pool));
+ return svn_fs_bdb__lock_token_add(trail->fs, path, lock_token,
+ trail, trail->pool);
+}
+
+
+/* Delete LOCK_TOKEN and its corresponding lock (associated with PATH,
+ whose KIND is supplied), as part of TRAIL. */
+static svn_error_t *
+delete_lock_and_token(const char *lock_token,
+ const char *path,
+ trail_t *trail)
+{
+ SVN_ERR(svn_fs_bdb__lock_delete(trail->fs, lock_token,
+ trail, trail->pool));
+ return svn_fs_bdb__lock_token_delete(trail->fs, path,
+ trail, trail->pool);
+}
+
+
+struct lock_args
+{
+ svn_lock_t **lock_p;
+ const char *path;
+ const char *token;
+ const char *comment;
+ svn_boolean_t is_dav_comment;
+ svn_boolean_t steal_lock;
+ apr_time_t expiration_date;
+ svn_revnum_t current_rev;
+};
+
+
+static svn_error_t *
+txn_body_lock(void *baton, trail_t *trail)
+{
+ struct lock_args *args = baton;
+ svn_node_kind_t kind = svn_node_file;
+ svn_lock_t *existing_lock;
+ svn_lock_t *lock;
+
+ SVN_ERR(svn_fs_base__get_path_kind(&kind, args->path, trail, trail->pool));
+
+ /* Until we implement directory locks someday, we only allow locks
+ on files or non-existent paths. */
+ if (kind == svn_node_dir)
+ return SVN_FS__ERR_NOT_FILE(trail->fs, args->path);
+
+ /* While our locking implementation easily supports the locking of
+ nonexistent paths, we deliberately choose not to allow such madness. */
+ if (kind == svn_node_none)
+ {
+ if (SVN_IS_VALID_REVNUM(args->current_rev))
+ return svn_error_createf(
+ SVN_ERR_FS_OUT_OF_DATE, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"),
+ args->path);
+ else
+ return svn_error_createf(
+ SVN_ERR_FS_NOT_FOUND, NULL,
+ _("Path '%s' doesn't exist in HEAD revision"),
+ args->path);
+ }
+
+ /* There better be a username attached to the fs. */
+ if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(trail->fs);
+
+ /* Is the caller attempting to lock an out-of-date working file? */
+ if (SVN_IS_VALID_REVNUM(args->current_rev))
+ {
+ svn_revnum_t created_rev;
+ SVN_ERR(svn_fs_base__get_path_created_rev(&created_rev, args->path,
+ trail, trail->pool));
+
+ /* SVN_INVALID_REVNUM means the path doesn't exist. So
+ apparently somebody is trying to lock something in their
+ working copy, but somebody else has deleted the thing
+ from HEAD. That counts as being 'out of date'. */
+ if (! SVN_IS_VALID_REVNUM(created_rev))
+ return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
+ "Path '%s' doesn't exist in HEAD revision",
+ args->path);
+
+ if (args->current_rev < created_rev)
+ return svn_error_createf(SVN_ERR_FS_OUT_OF_DATE, NULL,
+ "Lock failed: newer version of '%s' exists",
+ args->path);
+ }
+
+ /* If the caller provided a TOKEN, we *really* need to see
+ if a lock already exists with that token, and if so, verify that
+ the lock's path matches PATH. Otherwise we run the risk of
+ breaking the 1-to-1 mapping of lock tokens to locked paths. */
+ if (args->token)
+ {
+ svn_lock_t *lock_from_token;
+ svn_error_t *err = svn_fs_bdb__lock_get(&lock_from_token, trail->fs,
+ args->token, trail,
+ trail->pool);
+ if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
+ {
+ svn_error_clear(err);
+ }
+ else
+ {
+ SVN_ERR(err);
+ if (strcmp(lock_from_token->path, args->path) != 0)
+ return svn_error_create(SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+ "Lock failed: token refers to existing "
+ "lock with non-matching path.");
+ }
+ }
+
+ /* Is the path already locked?
+
+ Note that this next function call will automatically ignore any
+ errors about {the path not existing as a key, the path's token
+ not existing as a key, the lock just having been expired}. And
+ that's totally fine. Any of these three errors are perfectly
+ acceptable to ignore; it means that the path is now free and
+ clear for locking, because the bdb funcs just cleared out both
+ of the tables for us. */
+ SVN_ERR(svn_fs_base__get_lock_helper(&existing_lock, args->path,
+ trail, trail->pool));
+ if (existing_lock)
+ {
+ if (! args->steal_lock)
+ {
+ /* Sorry, the path is already locked. */
+ return SVN_FS__ERR_PATH_ALREADY_LOCKED(trail->fs,
+ existing_lock);
+ }
+ else
+ {
+ /* ARGS->steal_lock is set, so fs_username is "stealing" the
+ lock from lock->owner. Destroy the existing lock. */
+ SVN_ERR(delete_lock_and_token(existing_lock->token,
+ existing_lock->path, trail));
+ }
+ }
+
+ /* Create a new lock, and add it to the tables. */
+ lock = svn_lock_create(trail->pool);
+ if (args->token)
+ lock->token = apr_pstrdup(trail->pool, args->token);
+ else
+ SVN_ERR(svn_fs_base__generate_lock_token(&(lock->token), trail->fs,
+ trail->pool));
+ lock->path = apr_pstrdup(trail->pool, args->path);
+ lock->owner = apr_pstrdup(trail->pool, trail->fs->access_ctx->username);
+ lock->comment = apr_pstrdup(trail->pool, args->comment);
+ lock->is_dav_comment = args->is_dav_comment;
+ lock->creation_date = apr_time_now();
+ lock->expiration_date = args->expiration_date;
+ SVN_ERR(add_lock_and_token(lock, lock->token, args->path, trail));
+ *(args->lock_p) = lock;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+svn_error_t *
+svn_fs_base__lock(svn_lock_t **lock,
+ svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_revnum_t current_rev,
+ svn_boolean_t steal_lock,
+ apr_pool_t *pool)
+{
+ struct lock_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.lock_p = lock;
+ args.path = svn_fs__canonicalize_abspath(path, pool);
+ args.token = token;
+ args.comment = comment;
+ args.is_dav_comment = is_dav_comment;
+ args.steal_lock = steal_lock;
+ args.expiration_date = expiration_date;
+ args.current_rev = current_rev;
+
+ return svn_fs_base__retry_txn(fs, txn_body_lock, &args, FALSE, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__generate_lock_token(const char **token,
+ svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ /* Notice that 'fs' is currently unused. But perhaps someday, we'll
+ want to use the fs UUID + some incremented number? For now, we
+ generate a URI that matches the DAV RFC. We could change this to
+ some other URI scheme someday, if we wish. */
+ *token = apr_pstrcat(pool, "opaquelocktoken:",
+ svn_uuid_generate(pool), (char *)NULL);
+ return SVN_NO_ERROR;
+}
+
+
+struct unlock_args
+{
+ const char *path;
+ const char *token;
+ svn_boolean_t break_lock;
+};
+
+
+static svn_error_t *
+txn_body_unlock(void *baton, trail_t *trail)
+{
+ struct unlock_args *args = baton;
+ const char *lock_token;
+ svn_lock_t *lock;
+
+ /* This could return SVN_ERR_FS_BAD_LOCK_TOKEN or SVN_ERR_FS_LOCK_EXPIRED. */
+ SVN_ERR(svn_fs_bdb__lock_token_get(&lock_token, trail->fs, args->path,
+ trail, trail->pool));
+
+ /* If not breaking the lock, we need to do some more checking. */
+ if (!args->break_lock)
+ {
+ /* Sanity check: The lock token must exist, and must match. */
+ if (args->token == NULL)
+ return svn_fs_base__err_no_lock_token(trail->fs, args->path);
+ else if (strcmp(lock_token, args->token) != 0)
+ return SVN_FS__ERR_NO_SUCH_LOCK(trail->fs, args->path);
+
+ SVN_ERR(svn_fs_bdb__lock_get(&lock, trail->fs, lock_token,
+ trail, trail->pool));
+
+ /* There better be a username attached to the fs. */
+ if (!trail->fs->access_ctx || !trail->fs->access_ctx->username)
+ return SVN_FS__ERR_NO_USER(trail->fs);
+
+ /* And that username better be the same as the lock's owner. */
+ if (strcmp(trail->fs->access_ctx->username, lock->owner) != 0)
+ return SVN_FS__ERR_LOCK_OWNER_MISMATCH(
+ trail->fs,
+ trail->fs->access_ctx->username,
+ lock->owner);
+ }
+
+ /* Remove a row from each of the locking tables. */
+ return delete_lock_and_token(lock_token, args->path, trail);
+}
+
+
+svn_error_t *
+svn_fs_base__unlock(svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ svn_boolean_t break_lock,
+ apr_pool_t *pool)
+{
+ struct unlock_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.path = svn_fs__canonicalize_abspath(path, pool);
+ args.token = token;
+ args.break_lock = break_lock;
+ return svn_fs_base__retry_txn(fs, txn_body_unlock, &args, TRUE, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__get_lock_helper(svn_lock_t **lock_p,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const char *lock_token;
+ svn_error_t *err;
+
+ err = svn_fs_bdb__lock_token_get(&lock_token, trail->fs, path,
+ trail, pool);
+
+ /* We've deliberately decided that this function doesn't tell the
+ caller *why* the lock is unavailable. */
+ if (err && ((err->apr_err == SVN_ERR_FS_NO_SUCH_LOCK)
+ || (err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
+ {
+ svn_error_clear(err);
+ *lock_p = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ /* Same situation here. */
+ err = svn_fs_bdb__lock_get(lock_p, trail->fs, lock_token, trail, pool);
+ if (err && ((err->apr_err == SVN_ERR_FS_LOCK_EXPIRED)
+ || (err->apr_err == SVN_ERR_FS_BAD_LOCK_TOKEN)))
+ {
+ svn_error_clear(err);
+ *lock_p = NULL;
+ return SVN_NO_ERROR;
+ }
+ else
+ SVN_ERR(err);
+
+ return svn_error_trace(err);
+}
+
+
+struct lock_token_get_args
+{
+ svn_lock_t **lock_p;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_get_lock(void *baton, trail_t *trail)
+{
+ struct lock_token_get_args *args = baton;
+ return svn_fs_base__get_lock_helper(args->lock_p, args->path,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__get_lock(svn_lock_t **lock,
+ svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct lock_token_get_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.path = svn_fs__canonicalize_abspath(path, pool);
+ args.lock_p = lock;
+ return svn_fs_base__retry_txn(fs, txn_body_get_lock, &args, FALSE, pool);
+}
+
+/* Implements `svn_fs_get_locks_callback_t', spooling lock information
+ to a stream as the filesystem provides it. BATON is an 'svn_stream_t *'
+ object pointing to the stream. We'll write the spool stream with a
+ format like so:
+
+ SKEL1_LEN "\n" SKEL1 "\n" SKEL2_LEN "\n" SKEL2 "\n" ...
+
+ where each skel is a lock skel (the same format we use to store
+ locks in the `locks' table). */
+static svn_error_t *
+spool_locks_info(void *baton,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ svn_skel_t *lock_skel;
+ svn_stream_t *stream = baton;
+ const char *skel_len;
+ svn_stringbuf_t *skel_buf;
+ apr_size_t len;
+
+ SVN_ERR(svn_fs_base__unparse_lock_skel(&lock_skel, lock, pool));
+ skel_buf = svn_skel__unparse(lock_skel, pool);
+ skel_len = apr_psprintf(pool, "%" APR_SIZE_T_FMT "\n", skel_buf->len);
+ len = strlen(skel_len);
+ SVN_ERR(svn_stream_write(stream, skel_len, &len));
+ len = skel_buf->len;
+ SVN_ERR(svn_stream_write(stream, skel_buf->data, &len));
+ len = 1;
+ return svn_stream_write(stream, "\n", &len);
+}
+
+
+struct locks_get_args
+{
+ const char *path;
+ svn_depth_t depth;
+ svn_stream_t *stream;
+};
+
+
+static svn_error_t *
+txn_body_get_locks(void *baton, trail_t *trail)
+{
+ struct locks_get_args *args = baton;
+ return svn_fs_bdb__locks_get(trail->fs, args->path, args->depth,
+ spool_locks_info, args->stream,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__get_locks(svn_fs_t *fs,
+ const char *path,
+ svn_depth_t depth,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ apr_pool_t *pool)
+{
+ struct locks_get_args args;
+ svn_stream_t *stream;
+ svn_stringbuf_t *buf;
+ svn_boolean_t eof;
+ apr_pool_t *iterpool = svn_pool_create(pool);
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.path = svn_fs__canonicalize_abspath(path, pool);
+ args.depth = depth;
+ /* Enough for 100+ locks if the comments are small. */
+ args.stream = svn_stream__from_spillbuf(4 * 1024 /* blocksize */,
+ 64 * 1024 /* maxsize */,
+ pool);
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_locks, &args, FALSE, pool));
+
+ /* Read the stream calling GET_LOCKS_FUNC(). */
+ stream = args.stream;
+
+ while (1)
+ {
+ apr_size_t len, skel_len;
+ char c, *skel_buf;
+ svn_skel_t *lock_skel;
+ svn_lock_t *lock;
+ apr_uint64_t ui64;
+ svn_error_t *err;
+
+ svn_pool_clear(iterpool);
+
+ /* Read a skel length line and parse it for the skel's length. */
+ SVN_ERR(svn_stream_readline(stream, &buf, "\n", &eof, iterpool));
+ if (eof)
+ break;
+ err = svn_cstring_strtoui64(&ui64, buf->data, 0, APR_SIZE_MAX, 10);
+ if (err)
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, err, NULL);
+ skel_len = (apr_size_t)ui64;
+
+ /* Now read that much into a buffer. */
+ skel_buf = apr_palloc(pool, skel_len + 1);
+ SVN_ERR(svn_stream_read(stream, skel_buf, &skel_len));
+ skel_buf[skel_len] = '\0';
+
+ /* Read the extra newline that follows the skel. */
+ len = 1;
+ SVN_ERR(svn_stream_read(stream, &c, &len));
+ if (c != '\n')
+ return svn_error_create(SVN_ERR_MALFORMED_FILE, NULL, NULL);
+
+ /* Parse the skel into a lock, and notify the caller. */
+ lock_skel = svn_skel__parse(skel_buf, skel_len, iterpool);
+ SVN_ERR(svn_fs_base__parse_lock_skel(&lock, lock_skel, iterpool));
+ SVN_ERR(get_locks_func(get_locks_baton, lock, iterpool));
+ }
+
+ SVN_ERR(svn_stream_close(stream));
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Utility function: verify that a lock can be used.
+
+ If no username is attached to the FS, return SVN_ERR_FS_NO_USER.
+
+ If the FS username doesn't match LOCK's owner, return
+ SVN_ERR_FS_LOCK_OWNER_MISMATCH.
+
+ If FS hasn't been supplied with a matching lock-token for LOCK,
+ return SVN_ERR_FS_BAD_LOCK_TOKEN.
+
+ Otherwise return SVN_NO_ERROR.
+ */
+static svn_error_t *
+verify_lock(svn_fs_t *fs,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ if ((! fs->access_ctx) || (! fs->access_ctx->username))
+ return svn_error_createf
+ (SVN_ERR_FS_NO_USER, NULL,
+ _("Cannot verify lock on path '%s'; no username available"),
+ lock->path);
+
+ else if (strcmp(fs->access_ctx->username, lock->owner) != 0)
+ return svn_error_createf
+ (SVN_ERR_FS_LOCK_OWNER_MISMATCH, NULL,
+ _("User '%s' does not own lock on path '%s' (currently locked by '%s')"),
+ fs->access_ctx->username, lock->path, lock->owner);
+
+ else if (svn_hash_gets(fs->access_ctx->lock_tokens, lock->token) == NULL)
+ return svn_error_createf
+ (SVN_ERR_FS_BAD_LOCK_TOKEN, NULL,
+ _("Cannot verify lock on path '%s'; no matching lock-token available"),
+ lock->path);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* This implements the svn_fs_get_locks_callback_t interface, where
+ BATON is just an svn_fs_t object. */
+static svn_error_t *
+get_locks_callback(void *baton,
+ svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ return verify_lock(baton, lock, pool);
+}
+
+
+/* The main routine for lock enforcement, used throughout libsvn_fs_base. */
+svn_error_t *
+svn_fs_base__allow_locked_operation(const char *path,
+ svn_boolean_t recurse,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ if (recurse)
+ {
+ /* Discover all locks at or below the path. */
+ SVN_ERR(svn_fs_bdb__locks_get(trail->fs, path, svn_depth_infinity,
+ get_locks_callback,
+ trail->fs, trail, pool));
+ }
+ else
+ {
+ svn_lock_t *lock;
+
+ /* Discover any lock attached to the path. */
+ SVN_ERR(svn_fs_base__get_lock_helper(&lock, path, trail, pool));
+ if (lock)
+ SVN_ERR(verify_lock(trail->fs, lock, pool));
+ }
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_base/lock.h b/subversion/libsvn_fs_base/lock.h
new file mode 100644
index 0000000..603e78c
--- /dev/null
+++ b/subversion/libsvn_fs_base/lock.h
@@ -0,0 +1,120 @@
+/* lock.h : internal interface to lock functions
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_LOCK_H
+#define SVN_LIBSVN_FS_LOCK_H
+
+#include "trail.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* These functions implement part of the FS loader library's fs
+ vtables. See the public svn_fs.h for docstrings.*/
+
+svn_error_t *svn_fs_base__lock(svn_lock_t **lock,
+ svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ const char *comment,
+ svn_boolean_t is_dav_comment,
+ apr_time_t expiration_date,
+ svn_revnum_t current_rev,
+ svn_boolean_t steal_lock,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__generate_lock_token(const char **token,
+ svn_fs_t *fs,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__unlock(svn_fs_t *fs,
+ const char *path,
+ const char *token,
+ svn_boolean_t break_lock,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__get_lock(svn_lock_t **lock,
+ svn_fs_t *fs,
+ const char *path,
+ apr_pool_t *pool);
+
+svn_error_t *
+svn_fs_base__get_locks(svn_fs_t *fs,
+ const char *path,
+ svn_depth_t depth,
+ svn_fs_get_locks_callback_t get_locks_func,
+ void *get_locks_baton,
+ apr_pool_t *pool);
+
+
+
+/* These next functions are 'helpers' for internal fs use:
+ if a fs function's txn_body needs to enforce existing locks, it
+ should use these routines:
+*/
+
+
+/* Implements main logic of 'svn_fs_get_lock' (or in this
+ case, svn_fs_base__get_lock() above.) See svn_fs.h. */
+svn_error_t *
+svn_fs_base__get_lock_helper(svn_lock_t **lock_p,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Examine PATH for existing locks, and check whether they can be
+ used. Do all work in the context of TRAIL, using POOL for
+ temporary allocations.
+
+ If no locks are present, return SVN_NO_ERROR.
+
+ If PATH is locked (or contains locks "below" it, when RECURSE is
+ set), then verify that:
+
+ 1. a username has been supplied to TRAIL->fs's access-context,
+ else return SVN_ERR_FS_NO_USER.
+
+ 2. for every lock discovered, the current username in the access
+ context of TRAIL->fs matches the "owner" of the lock, else
+ return SVN_ERR_FS_LOCK_OWNER_MISMATCH.
+
+ 3. for every lock discovered, a matching lock token has been
+ passed into TRAIL->fs's access-context, else return
+ SVN_ERR_FS_BAD_LOCK_TOKEN.
+
+ If all three conditions are met, return SVN_NO_ERROR.
+*/
+svn_error_t *svn_fs_base__allow_locked_operation(const char *path,
+ svn_boolean_t recurse,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_LOCK_H */
diff --git a/subversion/libsvn_fs_base/node-rev.c b/subversion/libsvn_fs_base/node-rev.c
new file mode 100644
index 0000000..949a24b
--- /dev/null
+++ b/subversion/libsvn_fs_base/node-rev.c
@@ -0,0 +1,126 @@
+/* node-rev.c --- storing and retrieving NODE-REVISION skels
+ *
+ * ====================================================================
+ * 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>
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_fs.h"
+#include "fs.h"
+#include "err.h"
+#include "node-rev.h"
+#include "reps-strings.h"
+#include "id.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "bdb/nodes-table.h"
+#include "bdb/node-origins-table.h"
+
+
+/* Creating completely new nodes. */
+
+
+svn_error_t *
+svn_fs_base__create_node(const svn_fs_id_t **id_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *id;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* Find an unused ID for the node. */
+ SVN_ERR(svn_fs_bdb__new_node_id(&id, fs, copy_id, txn_id, trail, pool));
+
+ /* Store its NODE-REVISION skel. */
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, id, noderev, trail, pool));
+
+ /* Add a record in the node origins index table if our format
+ supports it. */
+ if (bfd->format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT)
+ {
+ SVN_ERR(svn_fs_bdb__set_node_origin(fs, svn_fs_base__id_node_id(id),
+ id, trail, pool));
+ }
+
+ *id_p = id;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Creating new revisions of existing nodes. */
+
+svn_error_t *
+svn_fs_base__create_successor(const svn_fs_id_t **new_id_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *old_id,
+ node_revision_t *new_noderev,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_id_t *new_id;
+
+ /* Choose an ID for the new node, and store it in the database. */
+ SVN_ERR(svn_fs_bdb__new_successor_id(&new_id, fs, old_id, copy_id,
+ txn_id, trail, pool));
+
+ /* Store the new skel under that ID. */
+ SVN_ERR(svn_fs_bdb__put_node_revision(fs, new_id, new_noderev,
+ trail, pool));
+
+ *new_id_p = new_id;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Deleting a node revision. */
+
+svn_error_t *
+svn_fs_base__delete_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ svn_boolean_t origin_also,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* ### todo: here, we should adjust other nodes to compensate for
+ the missing node. */
+
+ /* Delete the node origin record, too, if asked to do so and our
+ format supports it. */
+ if (origin_also && (bfd->format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT))
+ {
+ SVN_ERR(svn_fs_bdb__delete_node_origin(fs, svn_fs_base__id_node_id(id),
+ trail, pool));
+ }
+
+ return svn_fs_bdb__delete_nodes_entry(fs, id, trail, pool);
+}
diff --git a/subversion/libsvn_fs_base/node-rev.h b/subversion/libsvn_fs_base/node-rev.h
new file mode 100644
index 0000000..206be20
--- /dev/null
+++ b/subversion/libsvn_fs_base/node-rev.h
@@ -0,0 +1,101 @@
+/* node-rev.h : interface to node revision retrieval and storage
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_NODE_REV_H
+#define SVN_LIBSVN_FS_NODE_REV_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_fs.h"
+#include "trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** Functions. ***/
+
+/* Create an entirely new, mutable node in the filesystem FS, whose
+ NODE-REVISION is NODEREV, as part of TRAIL. Set *ID_P to the new
+ node revision's ID. Use POOL for any temporary allocation.
+
+ COPY_ID is the copy_id to use in the node revision ID returned in
+ *ID_P.
+
+ TXN_ID is the Subversion transaction under which this occurs.
+
+ After this call, the node table manager assumes that the new node's
+ contents will change frequently. */
+svn_error_t *svn_fs_base__create_node(const svn_fs_id_t **id_p,
+ svn_fs_t *fs,
+ node_revision_t *noderev,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Create a node revision in FS which is an immediate successor of
+ OLD_ID, whose contents are NEW_NR, as part of TRAIL. Set *NEW_ID_P
+ to the new node revision's ID. Use POOL for any temporary
+ allocation.
+
+ COPY_ID, if non-NULL, is a key into the `copies' table, and
+ indicates that this new node is being created as the result of a
+ copy operation, and specifically which operation that was.
+
+ TXN_ID is the Subversion transaction under which this occurs.
+
+ After this call, the deltification code assumes that the new node's
+ contents will change frequently, and will avoid representing other
+ nodes as deltas against this node's contents. */
+svn_error_t *svn_fs_base__create_successor(const svn_fs_id_t **new_id_p,
+ svn_fs_t *fs,
+ const svn_fs_id_t *old_id,
+ node_revision_t *new_nr,
+ const char *copy_id,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Delete node revision ID from FS's `nodes' table, as part of TRAIL.
+ If ORIGIN_ALSO is set, also delete the record for this ID's node ID
+ from the `node-origins' index table (which is typically only done
+ if the caller thinks that ID points to the only node revision ID in
+ its line of history).
+
+ WARNING: This does not check that the node revision is mutable!
+ Callers should do that check themselves. */
+svn_error_t *svn_fs_base__delete_node_revision(svn_fs_t *fs,
+ const svn_fs_id_t *id,
+ svn_boolean_t origin_also,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_NODE_REV_H */
diff --git a/subversion/libsvn_fs_base/notes/TODO b/subversion/libsvn_fs_base/notes/TODO
new file mode 100644
index 0000000..c72af03
--- /dev/null
+++ b/subversion/libsvn_fs_base/notes/TODO
@@ -0,0 +1,137 @@
+What's happening now
+
+The filesystem needs some path validation stuffs independent of the
+SVN path utilities. A filesystem path is a well-defined Thing that
+should be held a safe distance away from future changes to SVN's
+general path library.
+
+
+Incorrectnesses
+
+We must ensure that node numbers are never reused. If we open a node,
+svn_fs_delete it, and then create new nodes, what happens when the
+original node structure suddenly comes to refer to an entirely
+different node? Files become directories?
+
+We should convert filenames to some canonical Unicode form, for
+comparison.
+
+Does everyone call svn_fs__check_fs who should?
+
+svn_fs_delete will actually delete non-empty directories, if they're
+not cloned. This is inconsistent; should it be fixed?
+
+Does every operation on a deleted node or completed transaction fail
+gracefully?
+
+Produce helpful error messages when filename paths contain null
+characters.
+
+
+Uglinesses
+
+Fix up comments in svn_fs.h for transactions.
+
+Add `public name' member to filesystem structure, to use to identify
+the filesystem in error messages. When driven by DAV, this could be a
+URL.
+
+When a dag function signals an error, it has no idea what the path of
+the relevant node was. But node revision ID's are pretty useless to
+the user. tree.c should probably rewrap some errors.
+
+svn_fs__getsize shouldn't rely on a maximum value for detecting
+overflow.
+
+The use of svn_fs__getsize in svn_fs__parse_id is ugly --- what if
+svn_vernum_t and apr_size_t aren't the same size?
+
+Consider some macros or accessory functions for referencing the pieces
+of the NODE-REVISION skel (instead of seeing stuff like
+node->children->next->next and such other unreadable rubbish)
+
+
+Slownesses
+
+We don't store older node revisions as deltas yet.
+
+The delta algorithm walks the whole tree using a single pool, so the
+memory used is proportional to the size of the target tree. Instead,
+it should use a separate subpool every time it recurses into a new
+directory, and free that subpool as soon as it's done processing that
+subdirectory, so the memory used is proportional to the depth of the
+tree.
+
+We should move as much real content out of the NODE-REVISION skel as
+possible; the skels should be holding only small stuff (node kind,
+flags).
+- File contents and deltas should be moved out to a `contents' table.
+ The NODE-REVISION skel should simply contain a key into that table.
+- Directory contents should be moved out to a `directories' table,
+ with a separate table entry for each directory entry. Keys into the
+ table should be of the form `NODE-ID ENTRY-NAME NODE-REVISION', and
+ values should be node revision ID's, or the word `deleted'; to look
+ up an entry named E in a directory whose node revision is N.R,
+ search for the entry `N E x', where x is the largest number present
+ <= R.
+- Property lists should be moved out to a table `properties', indexed
+ similarly to the above. We could deltify property contents the
+ same way we do file contents.
+
+
+Amenities
+
+Extend svn_fs_copy to handle mutable nodes.
+
+Long term ideas:
+
+- directory entry cache:
+ Create a cache mapping a node revision id X plus a filename component
+ N onto a new node revision id Y, meaning that X is a directory in
+ which the name N is bound to ID Y. If everything were in the cache,
+ this function could run with no I/O except for the final node.
+
+ Since node revisions never change, we wouldn't have to worry about
+ invalidating the cache. Mutable node objects will need special
+ handling, of course.
+
+- fulltext cache:
+ If we've recently computed a node's fulltext, we might want to keep
+ that around in case we need to compute one of its nearby ancestors'
+ fulltext, too. This could be a waste, though --- the access
+ patterns are a mix of linear scan (backwards to reconstruct a given
+ revision) and random (who knows what node we'll hit next), so it's
+ not clear what cache policy would be effective. Best to record some
+ data on how many delta applications a given cache would avoid before
+ implementing it.
+
+- delta cache:
+ As people update, we're going to be recomputing text deltas for the
+ most recently changed files pretty often. It might be worthwhile to
+ cache the deltas for a little while.
+
+- Handle Unicode canonicalization for directory and property names
+ ourselves. People should be able to hand us any valid UTF-8
+ sequence, perhaps with precomposed characters or non-spacing marks
+ in a non-canonical order, and find the appropriate matches, given
+ the rules defined by the Unicode standard.
+
+Keeping repositories alive in the long term: Berkeley DB is infamous
+for changing its file format from one revision to the next. If someone
+saves a Subversion 1.0 repository on a CD somewhere, and then tries to
+read it seven years later, their chance of being able to read it with
+the latest revision of Subversion is nil. The solution:
+
+- Define a simply XML repository dump format for the complete
+ repository data. This should be the same format we use for CVS
+ repository conversion. We'll have an import function.
+
+- Write a program that is simple and self-contained --- does not use
+ Berkeley DB, no fancy XML tools, uses nothing but POSIX read and
+ seek --- that can dump a Subversion repository in that format.
+
+- For each revision of Subversion, make a sample repository, and
+ archive a copy of it away as test data.
+
+- Write a test suite that verifies that the repository dump program
+ can handle all of the archived formats.
diff --git a/subversion/libsvn_fs_base/notes/fs-history b/subversion/libsvn_fs_base/notes/fs-history
new file mode 100644
index 0000000..e4f51a6
--- /dev/null
+++ b/subversion/libsvn_fs_base/notes/fs-history
@@ -0,0 +1,270 @@
+ -*- text -*-
+
+ Subversion Filesystem History
+ (a love song for libsvn_fs, by C. Michael Pilato)
+
+
+The Subversion filesystem can be your best friend, or your worst
+enemy, usually depending on which side of the public API you are
+working on. Callers of the libsvn_fs interfaces do their work in a
+world pleasantly addressed by roots (the name given to a revision or
+transaction snapshot of the versioned directory tree) and paths under
+those roots. But once you swim beneath the surface, you quickly
+realize that there is a beast both beautiful and dangerous lying in
+wait. What looks to the outside world as a sort of coordinate system
+with axes for "Time" and "Location" is, in fact, a complicated DAG
+subsystem, with nodes that represent revisions of versioned locations
+all interconnected in various relationships with each other.
+
+The goal of this document is straightforward: to relay knowledge about
+how to untangle that DAG subsystem -- knowledge which likely lives
+only in the minds of a few developers -- so that the Few might become
+the Many.
+
+
+
+Node-Revisions: The Nodes of the DAG
+
+When working outside the filesystem API, people generally talk about
+their versioned resources in terms of the paths of those resources,
+and the global revisions (or revisions-to-be) in which those paths
+are located. But inside the filesystem, paths are broken down and
+stored as a hierarchical linked-list of path components. Each of
+these path components has its own historical lineage (because
+Subversion versions directories, too!), and each revision of that
+lineage is referred to as a "node-revision". It is these
+node-revisions which are the nodes of the DAG subsystem, or "DAG
+nodes".
+
+DAG nodes are identified by unique keys called "node-revision IDs",
+and are inter-connected in a variety of ways. A DAG node that
+represents a directory stores information about which other DAG nodes
+represent the children of that directory. A DAG node contains
+information about which other DAG node is its historical predecessor.
+By tracing these links from node to node, we can effectively traverse
+both space and time, both the geography and the chronology of the
+filesystem landscape.
+
+For example, the path "/trunk/src/main.c" in revision 4 of the
+filesystem consumes four DAG nodes: one for "/", one for "/trunk", one
+for "/trunk/src", and one for "/trunk/src/main.c". The DAG node for
+"/" contains a list of the names and node-revision IDs of its
+children, among which is the node-revision ID for the child named
+"trunk". Similar links are found in "/trunk" (for "src") and
+"/trunk/src" (for "main.c"). Additionally, if these paths existed in
+different forms in previous revisions of the filesystem, their DAG
+nodes would store the node-revision IDs of their respective
+predecessor nodes.
+
+Whenever someone uses the public API to query for information about a
+versioned path under a particular root, the typical course of action
+under-the-hood is as follows:
+
+ 1. The root refers to a particular snapshot of the DAG node tree,
+ and from this we can learn the node-revision ID of the node
+ which represents the root directory ("/") as it appears in that
+ snapshot. Given this node-revision ID, it's all DAG from here.
+
+ 2. The path is split into components and traversed, beginning with
+ the root node, and walking down toward the full path. Each
+ intermediate node-revision is read, its entries list parsed, and
+ the next component looked up in that entries list to find the
+ next node-revision ID along the traversal path.
+
+ 3. Finally, we wind up with a node-revision ID for our original
+ path. We use it and its associated node-revision to answer the
+ query.
+
+Seems pretty easy, doesn't it? Keep reading.
+
+
+
+All About Node-Revision IDs
+
+As previously mentioned, each node-revision in the filesystem has a
+unique key, referred to as the node-revision ID. This key is
+typically represented as a string which looks like a period-separated
+list of its three components:
+
+ 1. node ID: This key is unique to the members of a single
+ historical lineage. Differing versions of the same versioned
+ resource, irrespective of the paths and revision in which those
+ versions are located, all share this ID. If two node-revisions
+ have different node IDs, their are historically unrelated.
+
+ 2. copy ID: This key uniquely identifies a copy operation, and is
+ sometimes referred to (or at least thought of) as a "branch ID."
+ If two node-revisions have the same copy ID, they are said to be
+ on the same branch. The only exception to this is in the key
+ "0", a special key that means "not branched".
+
+ 3. txn ID: This key uniquely identifies the Subversion transaction
+ in which this node-revision came into existence.
+
+Whenever someone uses the public API to *modify* a versioned resource,
+these actions are much the same as those used when querying. But
+there are important differences.
+
+ 1. The path is traversed in the same manner is described in the
+ previous section. The result is an in-memory linked-list of
+ information about the node-revisions which comprise the
+ components of the path.
+
+ 2. But before any changes can be made to a path, its node-revision
+ and those of its parent directories must first be cloned so that
+ changes to them don't affect previous incarnations of those
+ node-revisions. This process is called "making the path
+ mutable". If previous operations under this transaction caused
+ one or more of the parent directories to be made mutable
+ already, they are not again cloned.
+
+ 3. Once the path and all its parents are mutable, the desired
+ changes can be made to the cloned node-revision, and they in no
+ way affect prior history.
+
+To clone a node-revision means to literally make a duplicate of it
+which is granted its own unique node-revision ID. The new
+node-revision ID consists of the same node ID as the node-revision
+that was cloned (since this is just another point along the historical
+lineage of this versioned resource), a copy ID (which will be
+discussed later), and the txn ID in which this modification is
+occuring.
+
+There are some cool things we can read between the lines above. Since
+the only time a node-revision comes into existence is when it is brand
+new or a fresh clone, and we never do cloning except during a
+modification, then we can use the txn ID as a sort of mutability flag.
+Mutability of a node-revision is determined by comparing the txn ID of
+the node-revision with the ID of the Subversion transaction being used
+to modify the filesystem -- if, and only if, they are the same, the node
+is allowed to be changed inside that transaction.
+
+So, we know how txn IDs come into existence now. And the origin of
+node IDs hardly warrants its own paragraph: brand new lines of history
+(introduced with svn_fs_make_file() and svn_fs_make_dir()) get new
+unique node IDs, and every other node-revision that is created simply
+keeps the same node ID as the node-revision on which it is based.
+
+So what about those copy IDs?
+
+Copy IDs are assigned to nodes-revisions to denote on which "branch"
+of a line of history that node-revision resides. (They are called
+copy IDs for political reasons, really -- Subversion doesn't expose a
+branching API per se, instead promoting the idea that branches are
+just forks in the development of a line of history that can just as
+easily be represented using path semantics.) New copy IDs are
+allocated whenever a branching operation occurs. New node-revisions
+can inherit the copy IDs of their predecessors (in the trivial cloning
+case), inherit the copy-IDs of one of their parents (by nature of
+their parent being copied), or inherit new copy-IDs. In the absence
+of any branching, node-revisions are assigned the special copy ID "0".
+
+
+
+Copies and Copy IDs
+
+Currently there are two kinds of copy operation. The first is a
+"real" copy, and is the direct result of a call to svn_fs_copy().
+When a real copy is made, the node-revision of the copy source is
+cloned, and earns its own brand new unique node-revision ID. This
+node-revision ID is constructed from the original node ID, a brand new
+copy ID, and (as always) the txn ID of the transaction in which the
+copy is being made.
+
+The Subversion filesystem uses a "cheap copy/lazy migration" model.
+This means that when a directory node-revision is copied via
+svn_fs_copy(), only the node-revision of the top of the copied "tree"
+is cloned (again, earning a new copy ID), not every child of that
+tree. This makes the svn_fs_copy() operation quite fast, at least
+compared to the alternative. From that point, any children of the
+copy target are lazily migrated. The first time they are themselves
+modified after the original copy, they are cloned from their original
+source location into the new target location. This lazy migration
+procedure costs about the same as a regular cloning operation, which
+keeps the "cheap copy" cheap, even the morning after.
+
+Copies of files behave no differently than copies of directories. But
+files never have children, so effectively the "tree" being copied is
+exactly one node-revision. This node-revision is explicitly cloned at
+the time of the copy, and there is nothing to lazily migrate
+afterwards.
+
+The second type of copy operation is a "soft" copy. These types of
+copies are not explicitly triggered via the filesystem API, but are
+implementation artifacts of other filesystem operations. A soft copy
+happens whenever a node-revision exists in a different branch than
+that of its parent, and the parent is again branched. Huh?! Let's
+see if an example will help explain this a bit.
+
+Say you have a directory, "/trunk". Now, into "/trunk" you copy a
+file "README" from some other part of the tree. You have now
+effectively branched the original "README"'s history -- part of it
+will live on in the original location, but part of it now thrives in
+its new "/trunk/README" location. The copy operation assigned a brand
+new copy ID to the new node-revision for "/trunk/README", which is
+necessarily different from the copy ID assigned to the node-revision
+for "/trunk".
+
+Later, you decide to copy "/trunk" to "/branches/mine". So the new
+"/branches/mine" also gets a brand new copy ID, since it is now a
+historical branch of "/trunk". But what happens when
+"/branches/mine/README" is cloned later as part of some edits you are
+making? What copy ID does the new clone get? Because "/trunk/README"
+was on a different historical branch than "/trunk", our copy of
+"/trunk" causes (in "README") a branch of a branch. So
+"/branches/mine/README" gets a brand new copy ID, and the filesystem
+remembers that the copy operation associated with that ID was a soft
+copy.
+
+ [### Right about here, C-Mike's memory starts getting fuzzy ###]
+
+The following is the copy ID inheritance algorithm, used to calculate
+what copy ID a node revision will use when cloned for mutability.
+Remember that a node revision is never cloned until its parent is
+first cloned.
+
+ 1. If the node revision is already mutable, its copy ID never
+ changes.
+
+ 2. If the node revision has a copy ID of "0" (which is to say, it's
+ never been itself copied or cloned as a child of a copied
+ parent), then it inherits whatever copy ID its parent winds up
+ with.
+
+ 3. If the node revision is on the same branch as its parent before
+ cloning, it will remain on the same branch as its parent after
+ cloning. A node revision can be said to be on the same branch
+ as its parent if:
+
+ a) their copy IDs are the same, or
+
+ b) the node revision is not a branch point (meaning, it was
+ not the node revision created by the copy associated with
+ its copy ID), or
+
+ c) the node revision is a branch point which being accessed via
+ its copy destination path.
+
+ 4. If, however, the node revision is *not* on the same branch as
+ its parent before cloning, it cannot be on the same branch as
+ its parent after cloning. This breaks down to two cases:
+
+ a) If the node revision was the target of the copy operation
+ whose ID it holds, then it gets to keep its same copy ID.
+
+ b) Otherwise, the node revision is the unedited child of some
+ parent that was copied, and wasn't on the same branch as
+ that parent before the copy. In this special case, the
+ cloned node revision will get a brand new copy ID which
+ points to one of those "soft copy" things we've been
+ talking about.
+
+The initial root directory's node revision, created when the
+filesystem is initialized, begins life with a magical "0" copy ID.
+Afterward, any new nodes (as in, freshly created files and
+directories) begin life with the same copy ID as their parent.
+
+
+Traversing History
+
+ ### todo: put the history harvesting algorithm here
diff --git a/subversion/libsvn_fs_base/notes/structure b/subversion/libsvn_fs_base/notes/structure
new file mode 100644
index 0000000..8e2159f
--- /dev/null
+++ b/subversion/libsvn_fs_base/notes/structure
@@ -0,0 +1,1086 @@
+Subversion on Berkeley DB -*- text -*-
+
+There are many different ways to implement the Subversion filesystem
+interface. You could implement it directly using ordinary POSIX
+filesystem operations; you could build it using an SQL server as a
+back end; you could build it on RCS; and so on.
+
+This implementation of the Subversion filesystem interface is built on
+top of Berkeley DB (http://www.sleepycat.com). Berkeley DB supports
+transactions and recoverability, making it well-suited for Subversion.
+
+
+
+Nodes and Node Revisions
+
+In a Subversion filesystem, a `node' corresponds roughly to an
+`inode' in a Unix filesystem:
+
+ * A node is either a file or a directory.
+
+ * A node's contents change over time.
+
+ * When you change a node's contents, it's still the same node; it's
+ just been changed. So a node's identity isn't bound to a specific
+ set of contents.
+
+ * If you rename a node, it's still the same node, just under a
+ different name. So a node's identity isn't bound to a particular
+ filename.
+
+A `node revision' refers to a node's contents at a specific point in
+time. Changing a node's contents always creates a new revision of that
+node. Once created, a node revision's contents never change.
+
+When we create a node, its initial contents are the initial revision of
+the node. As users make changes to the node over time, we create new
+revisions of that same node. When a user commits a change that deletes
+a file from the filesystem, we don't delete the node, or any revision
+of it --- those stick around to allow us to recreate prior revisions of
+the filesystem. Instead, we just remove the reference to the node
+from the directory.
+
+
+
+ID's
+
+Within the database, we refer to nodes and node revisions using a
+string of three unique identifiers (the "node ID", the "copy ID", and
+the "txn ID"), separated by periods.
+
+ node_revision_id ::= node_id '.' copy_id '.' txn_id
+
+The node ID is unique to a particular node in the filesystem across
+all of revision history. That is, two node revisions who share
+revision history (perhaps because they are different revisions of the
+same node, or because one is a copy of the other, e.g.) have the same
+node ID, whereas two node revisions who have no common revision
+history will not have the same node ID.
+
+The copy ID is a key into the `copies' table (see `Copies' below), and
+identifies that a given node revision, or one of its ancestors,
+resulted from a unique filesystem copy operation.
+
+The txn ID is just an identifier that is unique to a single filesystem
+commit. All node revisions created as part of a commit share this txn
+ID (which, incidentally, gets its name from the fact that this id is
+the same id used as the primary key of Subversion transactions; see
+`Transactions' below).
+
+A directory entry identifies the file or subdirectory it refers to
+using a node revision ID --- not a node ID. This means that a change
+to a file far down in a directory hierarchy requires the parent
+directory of the changed node to be updated, to hold the new node
+revision ID. Now, since that parent directory has changed, its parent
+needs to be updated, and so on to the root. We call this process
+"bubble-up".
+
+If a particular subtree was unaffected by a given commit, the node
+revision ID that appears in its parent will be unchanged. When
+doing an update, we can notice this, and ignore that entire
+subtree. This makes it efficient to find localized changes in
+large trees.
+
+
+
+A Word About Keys
+
+Some of the Subversion database tables use base-36 numbers as their
+keys. Some debate exists about whether the use of base-36 (as opposed
+to, say, regular decimal values) is either necessary or good. It is
+outside the scope of this document to make a claim for or against this
+usage. As such, the reader will please note that for the majority of
+the document, the use of the term "number" when referring to keys of
+database tables should be interpreted to mean "a monotonically
+increasing unique key whose order with respect to other keys in the
+table is irrelevant". :-)
+
+To determine the actual type currently in use for the keys of a given
+table, you are invited to check out the "Appendix: Filesystem
+structure summary" section of this document.
+
+
+
+NODE-REVISION: how we represent a node revision
+
+We represent a given revision of a file or directory node using a list
+skel (see include/private/svn_skel.h for an explanation of skels).
+A node revision skel has the form:
+
+ (HEADER PROP-KEY KIND-SPECIFIC ...)
+
+where HEADER is a header skel, whose structure is common to all nodes,
+PROP-KEY is the key of the representation that contains this node's
+properties list, and the KIND-SPECIFIC elements carry data dependent
+on what kind of node this is --- file, directory, etc.
+
+HEADER has the form:
+
+ (KIND CREATED-PATH [PRED-ID [PRED-COUNT [HAS-MERGEINFO MERGEINFO-COUNT]]])
+
+where:
+
+ * KIND indicates what sort of node this is. It must be one of the
+ following:
+ - "file", indicating that the node is a file (see FILE below).
+ - "dir", indicating that the node is a directory (see DIR below).
+
+ * CREATED-PATH is the canonicalized absolute filesystem path at
+ which this node was created.
+
+ * PRED-ID, if present, indicates the node revision which is the
+ immediate ancestor of this node.
+
+ * PRED-COUNT, if present, indicates the number of predecessors the
+ node revision has (recursively).
+
+ * HAS-MERGEINFO and MERGEINFO-COUNT, if present, indicate ...
+ ### TODO
+
+Note that a node cannot change its kind from one revision to the next.
+A directory node is always a directory; a file node is always a file;
+etc. The fact that the node's kind is stored in each node revision,
+rather than in some revision-independent place, might suggest that
+it's possible for a node to change kinds from revision to revision, but
+Subversion does not allow this.
+
+PROP-KEY is a key into the `representations' table (see REPRESENTATIONS
+below), whose value is a representation pointing to a string
+(see `strings' table) that is a PROPLIST skel.
+
+The KIND-SPECIFIC portions are discussed below.
+
+
+
+PROPLIST: a property list is a list skel of the form:
+
+ (NAME1 VALUE1 NAME2 VALUE2 ...)
+
+where each NAMEi is the name of a property, and VALUEi is the value of
+the property named NAMEi. Every valid property list has an even
+number of elements.
+
+
+
+FILE: how files are represented.
+
+If a NODE-REVISION's header's KIND is "file", then the node-revision
+skel represents a file, and has the form:
+
+ (HEADER PROP-KEY DATA-INFO [EDIT-DATA-KEY])
+
+where
+
+ DATA-INFO ::= DATA-KEY | (DATA-KEY DATA-KEY-UNIQID)
+
+and DATA-KEY identifies the representation for the file's current
+contents, and EDIT-DATA-KEY identifies the representation currently
+available for receiving new contents for the file.
+
+DATA-KEY-UNIQID ...
+### TODO
+
+See discussion of representations later.
+
+
+
+DIR: how directories are represented.
+
+If the header's KIND is "dir", then the node-revision skel
+represents a directory, and has the form:
+
+ (HEADER PROP-KEY ENTRIES-KEY)
+
+where ENTRIES-KEY identifies the representation for the directory's
+entries list (see discussion of representations later). An entries
+list has the form
+
+ (ENTRY ...)
+
+where each entry is
+
+ (NAME ID)
+
+where:
+
+ * NAME is the name of the directory entry, in UTF-8, and
+
+ * ID is the ID of the node revision to which this entry refers
+
+
+
+REPRESENTATIONS: where and how Subversion stores your data.
+
+Some parts of a node revision are essentially constant-length: for
+example, the KIND field and the REV. Other parts can have
+arbitrarily varying length: property lists, file contents, and
+directory entry lists. This variable-length data is often similar
+from one revision to the next, so Subversion stores just the deltas
+between them, instead of successive fulltexts.
+
+The HEADER portion of a node revision holds the constant-length stuff,
+which is never deltified. The rest of a node revision just points to
+data stored outside the node revision proper. This design makes the
+repository code easier to maintain, because deltification and
+undeltification are confined to a layer separate from node revisions,
+and makes the code more efficient, because Subversion can retrieve
+just the parts of a node it needs for a given operation.
+
+Deltifiable data is stored in the `strings' table, as mediated by the
+`representations' table. Here's how it works:
+
+The `strings' table stores only raw bytes. A given string could be
+any one of these:
+
+ - a file's contents
+ - a delta that reconstructs file contents, or part of a file's contents
+ - a directory entry list skel
+ - a delta that reconstructs a dir entry list skel, or part of same
+ - a property list skel
+ - a delta that reconstructs a property list skel, or part of same
+
+There is no way to tell, from looking at a string, what kind of data
+it is. A directory entry list skel is indistinguishable from file
+contents that just happen to look exactly like the unparsed form of a
+directory entry list skel. File contents that just happen to look
+like svndiff data are indistinguishable from delta data.
+
+The code is able to interpret a given string because Subversion
+
+ a) knows whether to be looking for a property list or some
+ kind-specific data,
+
+ b) knows the `kind' of the node revision in question,
+
+ c) always goes through the `representations' table to discover if
+ any undeltification or other transformation is needed.
+
+The `representations' table is an intermediary between node revisions
+and strings. Node revisions never refer directly into the `strings'
+table; instead, they always refer into the `representations' table,
+which knows whether a given string is a fulltext or a delta, and if it
+is a delta, what it is a delta against. That, combined with the
+knowledge in (a) and (b) above, allows Subversion to retrieve the data
+and parse it appropriately. A representation has the form:
+
+ (HEADER KIND-SPECIFIC)
+
+where HEADER is
+
+ (KIND TXN [MD5 [SHA1]])
+
+The KIND is "fulltext" or "delta". TXN is the txn ID for the txn in
+which this representation was created. MD5 is a checksum of the
+representation's contents, that is, what the representation produces,
+regardless of whether it is stored deltified or as fulltext. (For
+compatibility with older versions of Subversion, MD5 may be
+absent, in which case the filesystem behaves as though the checksum is
+there and is correct.) An additional kind of checksum, SHA1, is present
+in newer formats, starting with version ...
+### TODO
+
+The TXN also serves as a kind of mutability flag: if txn T tries to
+change a representation's contents, but the rep's TXN is not T, then
+something has gone horribly wrong and T should leave the rep alone
+(and probably error). Of course, "change a representation" here means
+changing what the rep's consumer sees. Switching a representation's
+storage strategy, for example from fulltext to deltified, wouldn't
+count as a change, since that wouldn't affect what the rep produces.
+
+KIND-SPECIFIC varies considerably depending on the kind of
+representation. Here are the two forms currently recognized:
+
+ (("fulltext" TXN [MD5 [SHA1]]) STRING-KEY)
+ The data is at STRING-KEY in the `strings' table.
+
+ (("delta" TXN [MD5 [SHA1]]) (OFFSET WINDOW) ...)
+ Each OFFSET indicates the point in the fulltext that this
+ element reconstructs, and WINDOW says how to reconstruct it:
+
+ WINDOW ::= (DIFF SIZE REP-KEY [REP-OFFSET]) ;
+ DIFF ::= ("svndiff" VERSION STRING-KEY)
+
+ Notice that a WINDOW holds only metadata. REP-KEY says what
+ the window should be applied against, or none if this is a
+ self-compressed delta; SIZE says how much data this window
+ reconstructs; VERSION says what version of the svndiff format
+ is being used (currently only version 0 is supported); and
+ STRING-KEY says which string contains the actual svndiff data
+ (there is no diff data held directly in the representations
+ table, of course).
+
+ Note also that REP-KEY might refer to a representation that
+ itself requires undeltification. We use a delta combiner to
+ combine all the deltas needed to reproduce the fulltext from
+ some stored plaintext.
+
+ Branko says this is what REP-OFFSET is for:
+ > The offsets embedded in the svndiff are stored in a string;
+ > these offsets would be in the representation. The point is that
+ > you get all the information you need to select the appropriate
+ > windows from the rep skel -- without touching a single
+ > string. This means a bit more space used in the repository, but
+ > lots less memory used on the server.
+
+ We'll see if it turns out to be necessary.
+
+In the future, there may be other representations, for example
+indicating that the text is stored elsewhere in the database, or
+perhaps in an ordinary Unix file.
+
+Let's work through an example node revision:
+
+ (("file" REV COUNT) PROP-KEY "2345")
+
+The entry for key "2345" in `representations' is:
+
+ (("delta" TXN CHECKSUM) (0 (("svndiff" 0 "1729") 65 "2343")))
+
+and the entry for key "2343" in `representations' is:
+
+ (("fulltext" TXN CHECKSUM) "1001")
+
+while the entry for key "1729" in `strings' is:
+
+ <some unprintable glob of svndiff data>
+
+which, when applied to the fulltext at key "1001" in strings, results
+in this new fulltext:
+
+ "((some text) (that looks) (deceptively like) (directory entries))"
+
+Et voila! Subversion knew enough, via the `representations' and
+`strings' tables, to undeltify and get that fulltext; and knew enough,
+because of the node revision's "file" type, to interpret the result as
+file contents, not as a directory entry list.
+
+(Note that the `strings' table stores multiple DB values per key.
+That is, although it's accurate to say there is one string per key,
+the string may be divided into multiple consecutive blocks, all
+sharing that key. You use a Berkeley DB cursor to find the desired
+value[s], when retrieving a particular offset+len in a string.)
+
+Representations know nothing about ancestry -- the `representations'
+table never refers to node revision id's, only to strings or to other
+representations. In other words, while the `nodes' table allows
+recovery of ancestry information, the `representations' and `strings'
+tables together handle deltification and undeltification
+*independently* of ancestry. At present, Subversion generally stores
+the youngest strings in "fulltext" form, and older strings as "delta"s
+against them (unless the delta would save no space compared to the
+fulltext). However, there's nothing magic about that particular
+arrangement. Other interesting alternatives:
+
+ * We could store the N most recently accessed strings as fulltexts,
+ letting access patterns determine the most appropriate
+ representation for each revision.
+
+ * We could occasionally store deltas against the N'th younger
+ revision, storing larger jumps with a frequency inverse to the
+ distance covered, yielding a tree-structured history.
+
+Since the filesystem interface doesn't expose these details, we can
+change the representation pretty much as we please to optimize
+whatever parameter we care about --- storage size, speed, robustness,
+etc.
+
+Representations never share strings - every string is referred to by
+exactly one representation. This is so that when we change a
+representation to a different form (e.g. during deltification), we can
+delete the strings containing the old form, and know that we're not
+messing up any other reps by doing so.
+
+
+Further Notes On Deltifying:
+----------------------------
+
+When a representation is deltified, it is changed in place.
+New strings are created containing the new delta, the representation
+is changed to refer to the new strings, and the original (usually
+fulltext) string or strings are deleted.
+
+The node revisions referring to that representation will not be
+changed; instead, the same rep key will now be associated with
+different value. That way, we get reader locking for free: if someone
+is reading a file while Subversion is deltifying that file, one of the
+two sides will get a DB_DEADLOCK and svn_fs__retry_txn() will retry.
+
+### todo: add a note about cycle-checking here, too.
+
+
+
+The Berkeley DB "nodes" table
+
+The database contains a table called "nodes", which is a btree indexed
+by node revision ID's, mapping them onto REPRESENTATION skels. Node 0
+is always the root directory, and node revision ID 0.0.0 is always the
+empty directory. We use the value of the key 'next-key' to indicate
+the next unused node ID.
+
+Assuming that we store the most recent revision on every branch as
+fulltext, and all other revisions as deltas, we can retrieve any node
+revision by searching for the last revision of the node, and then
+walking backwards to specific revision we desire, applying deltas as
+we go.
+
+
+
+REVISION: filesystem revisions, and the Berkeley DB "revisions" table
+
+We represent a filesystem revision using a skel of the form:
+
+ ("revision" TXN)
+
+where TXN is the key into the `transactions' table (see 'Transactions' below)
+whose value is the transaction that was committed to create this revision.
+
+The database contains a table called "revisions", which is a
+record-number table mapping revision numbers onto REVISION skels.
+Since Berkeley DB record numbers start with 1, whereas Subversion
+filesystem revision numbers start at zero, revision V is stored as
+record number V+1 in the `revisions' table. Filesystem revision zero
+always has node revision 0.0.0 as its root directory; that node
+revision is guaranteed to be an empty directory.
+
+
+
+Transactions
+
+Every transaction ends when it is either successfully committed, or
+aborted. We call a transaction which has been either committed or
+aborted "finished", and one which hasn't "unfinished".
+
+Transactions are identified by unique numbers, called transaction
+ID's. Currently, transaction ID's are never reused, though this is
+not mandated by the schema. In the database, we always represent a
+transaction ID in its shortest ASCII form.
+
+The Berkeley DB `transactions' table records both unfinished and
+committed transactions. Every key in this table is a transaction ID.
+Unfinished transactions have values that are skels of one of the
+following forms:
+
+ ("transaction" ROOT-ID BASE-ID PROPLIST COPIES)
+ ("dead" ROOT-ID BASE-ID PROPLIST COPIES)
+
+where:
+
+ * ROOT-ID is the node revision ID of the transaction's root
+ directory. This is of the form 0.0.THIS-TXN-ID.
+
+ * BASE-ID is the node revision ID of the root of the transaction's
+ base revision. This is of the form 0.0.BASE-TXN-ID - the base
+ transaction is, of course, the transaction of the base revision.
+
+ * PROPLIST is a skel giving the revision properties for the
+ transaction.
+
+ * COPIES contains a list of keys into the `copies' table,
+ referencing all the filesystem copies created inside of this
+ transaction. If the transaction is aborted, these copies get
+ removed from the `copies' table.
+
+ * A "dead" transaction is one that has been requested to be
+ destroyed, and should never, ever, be committed.
+
+Committed transaction, however, have values that are skels of the form:
+
+ ("committed" ROOT-ID REV PROPLIST COPIES)
+
+where:
+
+ * ROOT-ID is the node revision ID of the committed transaction's (or
+ revision's) root node.
+
+ * REV represents the revision that was created when the
+ transaction was committed.
+
+ * PROPLIST is a skel giving the revision properties for the
+ committed transaction.
+
+ * COPIES contains a list of keys into the `copies' table,
+ referencing all the filesystem copies created by this committed
+ transaction. Nothing currently uses this information for
+ committed transactions, but it could be useful in the future.
+
+As the sole exception to the rule above, the `transactions' table
+always has one entry whose key is `next-key', and whose value is the
+lowest transaction ID that has never yet been used. We use this entry
+to allocate ID's for new transactions.
+
+The `transactions' table is a btree, with no particular sort order.
+
+
+
+Changes
+
+As modifications are made (files and dirs added or removed, text and
+properties changed, etc.) on Subversion transaction trees, the
+filesystem tracks the basic change made in the Berkeley DB `changes'
+table.
+
+The `changes' table is a btree with Berkeley's "duplicate keys"
+functionality (and with no particular sort order), and maps the
+one-to-many relationship of a transaction ID to a "change" item.
+Change items are skels of the form:
+
+ ("change" PATH ID CHANGE-KIND TEXT-MOD PROP-MOD)
+
+where:
+
+ * PATH is the path that was operated on to enact this change.
+
+ * ID is the node revision ID of the node changed. The precise
+ meaning varies based on the kind of the change:
+ - "add" or "modify": a new node revision created in the current
+ txn.
+ - "delete": a node revision from a previous txn.
+ - "replace": a replace operation actually acts on two node
+ revisions, one being deleted, one being added. Only the added
+ node-revision ID is recorded in the `changes' table - this is
+ a design flaw.
+ - "reset": no node revision applies. A zero atom is used as a
+ placeholder.
+
+ * CHANGE-KIND is one of the following:
+
+ - "add" : PATH/ID was added to the filesystem.
+ - "delete" : PATH/ID was removed from the filesystem.
+ - "replace" : PATH/ID was removed, then re-added to the filesystem.
+ - "modify" : PATH/ID was otherwise modified.
+ - "reset" : Ignore any previous changes for PATH/ID in this txn.
+ This kind is no longer created by Subversion 1.3.0
+ and later, and can probably be removed at the next
+ schema bump.
+
+ * TEXT-MOD is a bit specifying whether or not the contents of
+ this node was modified.
+
+ * PROP-MOD is a bit specifying whether or not the properties of
+ this node where modified.
+
+In order to fully describe the changes made to any given path as part
+of a single transaction, one must read all the change items associated
+with the transaction's ID, and "collapse" multiple entries that refer
+to that path.
+
+
+
+Copies
+
+Each time a filesystem copy operation is performed, Subversion records
+meta-data about that copy.
+
+Copies are identified by unique numbers called copy ID's. Currently,
+copy ID's are never reused, though this is not mandated by the schema.
+In the database, we always represent a copy ID in its shortest ASCII
+form.
+
+The Berkeley DB `copies' table records all filesystem copies. Every
+key in this table is copy ID, and every value is a skel of one of the
+following forms:
+
+ ("copy" SRC-PATH SRC-TXN DST-NODE-ID)
+ ("soft-copy" SRC-PATH SRC-TXN DST-NODE-ID)
+
+where:
+
+ * "copy" indicates an explicitly requested copy, and "soft-copy"
+ indicates a node that was cloned internally as part of an
+ explicitly requested copy of some parent directory. See the
+ section "Copies and Copy IDs" in the file <fs-history> for
+ details.
+
+ * SRC-PATH and SRC-TXN are the canonicalized absolute path and
+ transaction ID, respectively, of the source of the copy.
+
+ * DST-NODE-ID represents the new node revision created as a result
+ of the copy.
+
+As the sole exception to the rule above, the `copies' table always has
+one entry whose key is `next-key', and whose value is the lowest copy ID
+that has never yet been used. We use this entry to allocate new
+copy ID's.
+
+The `copies' table is a btree, with no particular sort order.
+
+
+
+Locks
+
+When a caller locks a file -- reserving an exclusive right to modify
+or delete it -- an lock object is created in a `locks' table.
+
+The `locks' table is a btree whose key is a UUID string known as
+a "lock-token", and whose value is a skel representing a lock. The
+fields in the skel mirror those of an svn_lock__t (see svn_types.h):
+
+ ("lock" PATH TOKEN OWNER COMMENT XML-P CREATION-DATE EXPIRATION-DATE)
+
+where:
+
+ * PATH is the absolute filesystem path reserved by the lock.
+
+ * TOKEN is the universally unique identifier of the lock, known
+ as the lock-token. This is the same as the row's key.
+
+ * OWNER is the authenticated username that "owns" the lock.
+
+ * COMMENT is a string describing the lock. It may be empty, or it
+ might describe the rationale for locking.
+
+ * XML-P is a boolean (either 0 or 1) indicating whether the COMMENT
+ field is wrapped in an XML tag. (This is something only used by
+ the DAV layer, for webdav interoperabliity.)
+
+ * CREATION-DATE is a string representation of the date/time when
+ the lock was created. (see svn_time_to_cstring())
+
+ * EXPIRATION-DATE is a string representation of the date/time when
+ the lock will cease to be valid. (see svn_time_to_cstring())
+
+In addition to creating a lock in the `locks' table, a new row is
+created in a `lock-tokens' table. The `lock-tokens' table is a btree
+whose key is an absolute path in the filesystem. The value of each
+key is a lock-token (which is a key into the `locks' table.)
+
+To test if a path is locked, simply check if the path is a key in the
+`lock-tokens' table. To see if a certain directory has any locked
+children below, we ask BerkeleyDB to do a "greater or equal match" on
+the directory path, and see if any results come back. If they do,
+then at least one of the directory's children is locked, and thus the
+directory cannot be deleted without further investigation.
+
+Locks are ephemeral things, not historied in any way. They are
+potentially created and deleted quite often. When a lock is
+destroyed, the appropriate row is removed from the `locks' table.
+Additionally, the locked-path is removed from the `lock-tokens' table.
+
+
+
+
+
+Merge rules
+
+The Subversion filesystem must provide the following characteristics:
+
+- clients can submit arbitrary rearrangements of the tree, to be
+ performed as atomic changes to the filesystem tree
+- multiple clients can submit non-overlapping changes at the same time,
+ without blocking
+- readers must never block other readers or writers
+- writers must never block readers
+- writers may block writers
+
+Merging rules:
+
+ The general principle: a series of changes can be merged iff the
+ final outcome is independent of the order you apply them in.
+
+Merging algorithm:
+
+ For each entry NAME in the directory ANCESTOR:
+
+ Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of
+ the name within ANCESTOR, SOURCE, and TARGET respectively.
+ (Possibly null if NAME does not exist in SOURCE or TARGET.)
+
+ If ANCESTOR-ENTRY == SOURCE-ENTRY, then:
+ No changes were made to this entry while the transaction was in
+ progress, so do nothing to the target.
+
+ Else if ANCESTOR-ENTRY == TARGET-ENTRY, then:
+ A change was made to this entry while the transaction was in
+ process, but the transaction did not touch this entry. Replace
+ TARGET-ENTRY with SOURCE-ENTRY.
+
+ Else:
+ Changes were made to this entry both within the transaction and
+ to the repository while the transaction was in progress. They
+ must be merged or declared to be in conflict.
+
+ If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
+ double delete; if one of them is null, that's a delete versus
+ a modification. In any of these cases, flag a conflict.
+
+ If any of the three entries is of type file, declare a conflict.
+
+ If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
+ modification of ANCESTOR-ENTRY (determine by comparing the
+ node-id fields), declare a conflict. A replacement is
+ incompatible with a modification or other replacement--even
+ an identical replacement.
+
+ Direct modifications were made to the directory ANCESTOR-ENTRY
+ in both SOURCE and TARGET. Recursively merge these
+ modifications.
+
+ For each leftover entry NAME in the directory SOURCE:
+
+ If NAME exists in TARGET, declare a conflict. Even if SOURCE and
+ TARGET are adding exactly the same thing, two additions are not
+ auto-mergeable with each other.
+
+ Add NAME to TARGET with the entry from SOURCE.
+
+ Now that we are done merging the changes from SOURCE into the
+ directory TARGET, update TARGET's predecessor to be SOURCE.
+
+The following algorithm was used when the Subversion filesystem was
+initially written, but has been replaced with the simpler and more
+performant algorithm above:
+
+ Merging two nodes, A and B, with respect to a common ancestor
+ ANCESTOR:
+
+ - First, the merge fails unless A, B, and ANCESTOR are all the same
+ kind of node.
+ - If A and B are text files:
+ - If A is an ancestor of B, then B is the merged result.
+ - If A is identical to B, then B (arbitrarily) is the merged
+ result.
+ - Otherwise, the merge fails.
+ - If A and B are both directories:
+ - For every directory entry E in either A, B, or ANCESTOR, here
+ are the cases:
+ - E exists in neither ANCESTOR nor A.
+ - E doesn't exist in ANCESTOR, and has been added to A.
+ - E exists in ANCESTOR, but has been deleted from A.
+ - E exists in both ANCESTOR and A ...
+ - but refers to different nodes.
+ - but refers to different revisions of the same node.
+ - and refers to the same node revision.
+
+ The same set of possible relationships with ANCESTOR holds for B,
+ so there are thirty-six combinations. The matrix is symmetrical
+ with A and B reversed, so we only have to describe one triangular
+ half, including the diagonal --- 21 combinations.
+
+ - (6) E exists in neither ANCESTOR nor A:
+ - (1) E exists in neither ANCESTOR nor B. Can't occur, by
+ assumption that E exists in either A, B, or ancestor.
+ - (1) E has been added to B. Add E in the merged result. ***
+ - (1) E has been deleted from B. Can't occur, by assumption
+ that E doesn't exist in ANCESTOR.
+ - (3) E exists in both ANCESTOR and B. Can't occur, by
+ assumption that E doesn't exist in ancestor.
+ - (5) E doesn't exist in ANCESTOR, and has been added to A.
+ - (1) E doesn't exist in ANCESTOR, and has been added to B.
+ Conflict.
+ - (1) E exists in ANCESTOR, but has been deleted from B.
+ Can't occur, by assumption that E doesn't exist in
+ ANCESTOR.
+ - (3) E exists in both ANCESTOR and B. Can't occur, by
+ assumption that E doesn't exist in ANCESTOR.
+ - (4) E exists in ANCESTOR, but has been deleted from A.
+ - (1) E exists in ANCESTOR, but has been deleted from B. If
+ neither delete was a result of a rename, then omit E from
+ the merged tree. *** Otherwise, conflict.
+ - E exists in both ANCESTOR and B ...
+ - (1) but refers to different nodes. Conflict.
+ - (1) but refers to different revisions of the same node.
+ Conflict.
+ - (1) and refers to the same node revision. Omit E from
+ the merged tree. ***
+ - (3) E exists in both ANCESTOR and A, but refers to different
+ nodes.
+ - (1) E exists in both ANCESTOR and B, but refers to
+ different nodes. Conflict.
+ - (1) E exists in both ANCESTOR and B, but refers to
+ different revisions of the same node. Conflict.
+ - (1) E exists in both ANCESTOR and B, and refers to the same
+ node revision. Replace E with A's node revision. ***
+ - (2) E exists in both ANCESTOR and A, but refers to different
+ revisions of the same node.
+ - (1) E exists in both ANCESTOR and B, but refers to
+ different revisions of the same node. Try to merge A/E and
+ B/E, recursively. ***
+ - (1) E exists in both ANCESTOR and B, and refers to the same
+ node revision. Replace E with A's node revision. ***
+ - (1) E exists in both ANCESTOR and A, and refers to the same
+ node revision.
+ - (1) E exists in both ANCESTOR and B, and refers to the same
+ node revision. Nothing has happened to ANCESTOR/E, so no
+ change is necessary.
+
+ *** == something actually happens
+
+
+Non-Historical Properties
+
+[[Yes, do tell.]]
+
+
+UUIDs: Universally Unique Identifiers
+
+Every filesystem has a UUID. This is represented as record #1 in the
+`uuids' table.
+
+
+Layers
+
+In previous structurings of the code, I had trouble keeping track of
+exactly who has implemented which promises, based on which other
+promises from whom.
+
+I hope the arrangement below will help me keep things straight, and
+make the code more reliable. The files are arranged in order from
+low-level to high-level: each file depends only on services provided
+by the files before it.
+
+skel.c, id.c, dbt.c, convert-size.c
+
+ Low-level utility functions.
+
+fs_skels.c Routines for marshaling between skels and native FS types.
+
+fs.c Creating and destroying filesystem objects.
+
+err.c Error handling.
+
+nodes-table.c, txn-table.c, rev-table.c, reps-table.c, strings-table.c
+
+ Create and open particular database tables.
+ Responsible for intra-record consistency.
+
+node-rev.c Creating, reading, and writing node revisions.
+ Responsible for deciding what gets deltified when.
+
+reps-strings.c
+ Retrieval and storage of represented strings.
+ This will handle delta-based storage,
+
+dag.c Operations on the DAG filesystem. "DAG" because the
+ interface exposes the filesystem's sharing structure.
+ Enforce inter-record consistency.
+
+tree.c Operations on the tree filesystem. This layer is
+ built on top of dag.c, but transparently distinguishes
+ virtual copies, making the underlying DAG look like a
+ real tree. This makes incomplete transactions behave
+ like ordinary mutable filesystems.
+
+delta.c Computing deltas.
+
+
+
+Appendix: Filesystem structure summary
+======================================
+
+Berkeley DB tables
+------------------
+
+ "nodes" : btree(ID -> NODE-REVISION, "next-key" -> NODE-ID)
+ "revisions" : recno(REVISION)
+ "transactions" : btree(TXN -> TRANSACTION, "next-key" -> TXN)
+ "changes" : btree(TXN -> CHANGE)
+ "copies" : btree(CPY -> COPY, "next-key" -> CPY)
+ "strings" : btree(STR -> STRING, "next-key" -> STR)
+ "representations" : btree(REP -> REPRESENTATION, "next-key" -> REP)
+ "uuids" : recno(UUID)
+ "locks" : btree(TOKEN -> LOCK)
+ "lock-tokens" : btree(PATH -> TOKEN)
+ "node-origins" : btree(NODE-ID -> ID)
+ "checksum-reps" : btree(SHA1SUM -> REP, "next-key" -> number-36)
+ "miscellaneous" : btree(STRING -> STRING)
+
+Syntactic elements
+------------------
+
+Table keys:
+
+ ID ::= NODE-REV-ID ;
+ TXN ::= number-36 ;
+ CPY ::= number-36 ;
+ STR ::= number-36 ;
+ REP ::= number-36 ;
+ TOKEN ::= uuid ;
+
+Property lists:
+
+ PROPLIST ::= (PROP ...) ;
+ PROP ::= atom atom ;
+
+
+Filesystem revisions:
+
+ REVISION ::= ("revision" TXN) ;
+
+
+Transactions:
+
+ TRANSACTION ::= UNFINISHED-TXN | COMMITTED-TXN | DEAD-TXN
+ UNFINISHED-TXN ::= ("transaction" ROOT-ID BASE-ID PROPLIST COPIES) ;
+ COMMITTED-TXN ::= ("committed" ROOT-ID REV PROPLIST COPIES) ;
+ DEAD-TXN ::= ("dead" ROOT-ID BASE-ID PROPLIST COPIES) ;
+ ROOT-ID ::= NODE-REV-ID ;
+ BASE-ID ::= NODE-REV-ID ;
+ COPIES ::= (CPY ...) ;
+ REV ::= number ;
+
+
+Changes:
+
+ CHANGE ::= ("change" PATH ID CHANGE-KIND TEXT-MOD PROP-MOD) ;
+ CHANGE-KIND ::= "add" | "delete" | "replace" | "modify" | "reset";
+ TEXT-MOD ::= atom ;
+ PROP-MOD ::= atom ;
+
+
+Copies:
+
+ COPY ::= REAL-COPY | SOFT-COPY
+ REAL-COPY ::= ("copy" SRC-PATH SRC-TXN DST-NODE-ID)
+ SOFT-COPY ::= ("soft-copy" SRC-PATH SRC-TXN DST-NODE-ID)
+ SRC-PATH ::= atom ;
+ SRC-TXN ::= TXN ;
+ DST-NODE-ID ::= NODE-REV-ID ;
+
+
+Entries lists:
+
+ ENTRIES ::= (ENTRY ...) ;
+ ENTRY ::= (NAME ID) ;
+ NAME ::= atom ;
+
+
+Node revisions:
+
+ NODE-REVISION ::= FILE | DIR ;
+ FILE ::= (HEADER PROP-KEY DATA-INFO [EDIT-DATA-KEY]) ;
+ DIR ::= (HEADER PROP-KEY ENTRIES-KEY) ;
+ HEADER ::= (KIND CREATED-PATH
+ [PRED-ID [PRED-COUNT
+ [HAS-MERGEINFO MERGEINFO-COUNT]]]) ;
+ KIND ::= "file" | "dir" ;
+ PRED-ID ::= NODE-REV-ID | "";
+ PRED-COUNT ::= number | "" ;
+ CREATED-PATH ::= atom ;
+ PROP-KEY ::= atom ;
+ DATA-INFO ::= DATA-KEY | (DATA-KEY DATA-KEY-UNIQID)
+ DATA-KEY ::= atom ;
+ DATA-KEY-UNIQID ::= atom ;
+ EDIT-DATA-KEY ::= atom ;
+ HAS-MERGEINFO ::= "0" | "1" ;
+ MERGEINFO-COUNT ::= number ;
+
+
+Representations:
+
+ REPRESENTATION ::= FULLTEXT | DELTA ;
+ FULLTEXT ::= (HEADER STRING-KEY) ;
+ DELTA ::= (HEADER (OFFSET WINDOW) ...) ;
+ WINDOW ::= (DIFF SIZE REP-KEY [REP-OFFSET]) ;
+ DIFF ::= ("svndiff" VERSION STRING-KEY) ;
+ VERSION ::= number ;
+ REP-KEY ::= atom ;
+ STRING-KEY ::= atom ;
+ OFFSET ::= number ;
+ REP-OFFSET ::= number ;
+
+ HEADER ::= (KIND TXN [MD5 [SHA1]]) ;
+ KIND ::= "fulltext" | "delta" ;
+
+ SIZE ::= number ;
+ MD5 ::= ("md5" MD5SUM) ;
+ SHA1 ::= ("sha1" SHA1SUM) ;
+ MD5SUM ::= atom ;
+ SHA1SUM ::= atom ;
+
+
+Strings:
+
+ STRING ::= RAWTEXT | LISTTEXT | DIFFTEXT
+ RAWTEXT ::= /{anything.class}*/ ;
+ LISTTEXT ::= list ;
+ DIFFTEXT ::= /{anything.class}*/ ;
+
+
+Node revision IDs:
+
+ NODE-REV-ID ::= NODE-ID '.' CPY '.' TXN ;
+ NODE-ID ::= number ;
+
+UUIDs:
+ UUID ::= uuid ;
+
+
+Locks:
+
+ LOCK ::= ("lock" PATH TOKEN OWNER
+ COMMENT XML-P CR-DATE [X-DATE]);
+ PATH ::= atom ;
+ OWNER ::= atom ;
+ COMMENT ::= atom ;
+ XML-P ::= "0" | "1" ;
+ CR-DATE ::= atom ;
+ X-DATE ::= atom ;
+
+Lock tokens:
+
+ (the value is just a lock-token, which is a uuid)
+
+
+Node origins:
+
+ NODE-ID ::= NODE-REV-ID ;
+
+
+Lexical elements
+----------------
+
+UUIDs:
+
+ uuid ::= hexits-32 '-' hexits-16 '-' hexits-16 '-'
+ hexits-16 '-' hexits-48 ;
+
+Numbers:
+
+ number ::= /{digit.class}+/ ;
+ number-36 ::= /{base36.class}+/ ;
+ hexits-32 ::= /{base16.class}{8}/ ;
+ hexits-16 ::= /{base16.class}{4}/ ;
+ hexits-48 ::= /{base16.class}{12}/ ;
+
+(Note: the following are described in skel.h)
+Skels:
+
+ skel ::= atom | list;
+ list ::= list.head list.body.opt list.tail ;
+ atom ::= atom.imp-len | atom.exp-len ;
+
+ list.head ::= '(' spaces.opt ;
+ list.tail ::= spaces.opt ')' ;
+ list.body.opt ::= | list.body ;
+ list.body ::= skel | list.body spaces.opt skel ;
+
+ atom.imp-len ::= /{name.class}[^\(\){ws.class}]*/ ;
+ atom.exp-len ::= /({digit.class}+){ws.class}.{\1}/ ;
+
+ spaces.opt ::= /{ws.class}*/ ;
+
+
+Character classes:
+
+ ws.class ::= [\t\n\f\r\ ] ;
+ digit.class ::= [0-9] ;
+ name.class ::= [A-Za-z] ;
+ base16.class ::= [0-9a-f]
+ base36.class ::= [a-z0-9]
+ anything.class ::= anything at all ;
+
+
+
+Appendix: 'miscellaneous' table contents
+======================================
+
+The 'miscellaneous' table contains string keys mapped to string
+values. Here is a table of the supported keys, the descriptions of
+their values, and the filesystem format version in which they were
+introduced.
+
+ Fmt Key Value
+ --- ------------------ ------------------------------------
+ 4 forward-delta-rev Youngest revision in the repository as of
+ the moment when it was upgraded to support
+ forward deltas.
diff --git a/subversion/libsvn_fs_base/reps-strings.c b/subversion/libsvn_fs_base/reps-strings.c
new file mode 100644
index 0000000..553075d
--- /dev/null
+++ b/subversion/libsvn_fs_base/reps-strings.c
@@ -0,0 +1,1617 @@
+/* reps-strings.c : intepreting representations with respect to strings
+ *
+ * ====================================================================
+ * 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 <assert.h>
+
+#include "svn_fs.h"
+#include "svn_pools.h"
+
+#include "fs.h"
+#include "err.h"
+#include "trail.h"
+#include "reps-strings.h"
+
+#include "bdb/reps-table.h"
+#include "bdb/strings-table.h"
+
+#include "../libsvn_fs/fs-loader.h"
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+
+/*** Helper Functions ***/
+
+
+/* Return non-zero iff REP is mutable under transaction TXN_ID. */
+static svn_boolean_t rep_is_mutable(representation_t *rep,
+ const char *txn_id)
+{
+ if ((! rep->txn_id) || (strcmp(rep->txn_id, txn_id) != 0))
+ return FALSE;
+ return TRUE;
+}
+
+/* Helper macro that evaluates to an error message indicating that
+ the representation referred to by X has an unknown node kind. */
+#define UNKNOWN_NODE_KIND(x) \
+ svn_error_createf \
+ (SVN_ERR_FS_CORRUPT, NULL, \
+ _("Unknown node kind for representation '%s'"), x)
+
+/* Return a `fulltext' representation, allocated in POOL, which
+ * references the string STR_KEY.
+ *
+ * If TXN_ID is non-zero and non-NULL, make the representation mutable
+ * under that TXN_ID.
+ *
+ * If STR_KEY is non-null, copy it into an allocation from POOL.
+ *
+ * If MD5_CHECKSUM is non-null, use it as the MD5 checksum for the new
+ * rep; else initialize the rep with an all-zero (i.e., always
+ * successful) MD5 checksum.
+ *
+ * If SHA1_CHECKSUM is non-null, use it as the SHA1 checksum for the new
+ * rep; else initialize the rep with an all-zero (i.e., always
+ * successful) SHA1 checksum.
+ */
+static representation_t *
+make_fulltext_rep(const char *str_key,
+ const char *txn_id,
+ svn_checksum_t *md5_checksum,
+ svn_checksum_t *sha1_checksum,
+ apr_pool_t *pool)
+
+{
+ representation_t *rep = apr_pcalloc(pool, sizeof(*rep));
+ if (txn_id && *txn_id)
+ rep->txn_id = apr_pstrdup(pool, txn_id);
+ rep->kind = rep_kind_fulltext;
+ rep->md5_checksum = svn_checksum_dup(md5_checksum, pool);
+ rep->sha1_checksum = svn_checksum_dup(sha1_checksum, pool);
+ rep->contents.fulltext.string_key
+ = str_key ? apr_pstrdup(pool, str_key) : NULL;
+ return rep;
+}
+
+
+/* Set *KEYS to an array of string keys gleaned from `delta'
+ representation REP. Allocate *KEYS in POOL. */
+static svn_error_t *
+delta_string_keys(apr_array_header_t **keys,
+ const representation_t *rep,
+ apr_pool_t *pool)
+{
+ const char *key;
+ int i;
+ apr_array_header_t *chunks;
+
+ if (rep->kind != rep_kind_delta)
+ return svn_error_create
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Representation is not of type 'delta'"));
+
+ /* Set up a convenience variable. */
+ chunks = rep->contents.delta.chunks;
+
+ /* Initialize *KEYS to an empty array. */
+ *keys = apr_array_make(pool, chunks->nelts, sizeof(key));
+ if (! chunks->nelts)
+ return SVN_NO_ERROR;
+
+ /* Now, push the string keys for each window into *KEYS */
+ for (i = 0; i < chunks->nelts; i++)
+ {
+ rep_delta_chunk_t *chunk = APR_ARRAY_IDX(chunks, i, rep_delta_chunk_t *);
+
+ key = apr_pstrdup(pool, chunk->string_key);
+ APR_ARRAY_PUSH(*keys, const char *) = key;
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Delete the strings associated with array KEYS in FS as part of TRAIL. */
+static svn_error_t *
+delete_strings(const apr_array_header_t *keys,
+ svn_fs_t *fs,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ int i;
+ const char *str_key;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ for (i = 0; i < keys->nelts; i++)
+ {
+ svn_pool_clear(subpool);
+ str_key = APR_ARRAY_IDX(keys, i, const char *);
+ SVN_ERR(svn_fs_bdb__string_delete(fs, str_key, trail, subpool));
+ }
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Reading the contents from a representation. ***/
+
+struct compose_handler_baton
+{
+ /* The combined window, and the pool it's allocated from. */
+ svn_txdelta_window_t *window;
+ apr_pool_t *window_pool;
+
+ /* If the incoming window was self-compressed, and the combined WINDOW
+ exists from previous iterations, SOURCE_BUF will point to the
+ expanded self-compressed window. */
+ char *source_buf;
+
+ /* The trail for this operation. WINDOW_POOL will be a child of
+ TRAIL->pool. No allocations will be made from TRAIL->pool itself. */
+ trail_t *trail;
+
+ /* TRUE when no more windows have to be read/combined. */
+ svn_boolean_t done;
+
+ /* TRUE if we've just started reading a new window. We need this
+ because the svndiff handler will push a NULL window at the end of
+ the stream, and we have to ignore that; but we must also know
+ when it's appropriate to push a NULL window at the combiner. */
+ svn_boolean_t init;
+};
+
+
+/* Handle one window. If BATON is emtpy, copy the WINDOW into it;
+ otherwise, combine WINDOW with the one in BATON, unless WINDOW
+ is self-compressed (i.e., does not copy from the source view),
+ in which case expand. */
+
+static svn_error_t *
+compose_handler(svn_txdelta_window_t *window, void *baton)
+{
+ struct compose_handler_baton *cb = baton;
+ SVN_ERR_ASSERT(!cb->done || window == NULL);
+ SVN_ERR_ASSERT(cb->trail && cb->trail->pool);
+
+ if (!cb->init && !window)
+ return SVN_NO_ERROR;
+
+ /* We should never get here if we've already expanded a
+ self-compressed window. */
+ SVN_ERR_ASSERT(!cb->source_buf);
+
+ if (cb->window)
+ {
+ if (window && (window->sview_len == 0 || window->src_ops == 0))
+ {
+ /* This is a self-compressed window. Don't combine it with
+ the others, because the combiner may go quadratic. Instead,
+ expand it here and signal that the combination has
+ ended. */
+ apr_size_t source_len = window->tview_len;
+ SVN_ERR_ASSERT(cb->window->sview_len == source_len);
+ cb->source_buf = apr_palloc(cb->window_pool, source_len);
+ svn_txdelta_apply_instructions(window, NULL,
+ cb->source_buf, &source_len);
+ cb->done = TRUE;
+ }
+ else
+ {
+ /* Combine the incoming window with whatever's in the baton. */
+ apr_pool_t *composite_pool = svn_pool_create(cb->trail->pool);
+ svn_txdelta_window_t *composite;
+
+ composite = svn_txdelta_compose_windows(window, cb->window,
+ composite_pool);
+ svn_pool_destroy(cb->window_pool);
+ cb->window = composite;
+ cb->window_pool = composite_pool;
+ cb->done = (composite->sview_len == 0 || composite->src_ops == 0);
+ }
+ }
+ else if (window)
+ {
+ /* Copy the (first) window into the baton. */
+ apr_pool_t *window_pool = svn_pool_create(cb->trail->pool);
+ SVN_ERR_ASSERT(cb->window_pool == NULL);
+ cb->window = svn_txdelta_window_dup(window, window_pool);
+ cb->window_pool = window_pool;
+ cb->done = (window->sview_len == 0 || window->src_ops == 0);
+ }
+ else
+ cb->done = TRUE;
+
+ cb->init = FALSE;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Read one delta window from REP[CUR_CHUNK] and push it at the
+ composition handler. */
+
+static svn_error_t *
+get_one_window(struct compose_handler_baton *cb,
+ svn_fs_t *fs,
+ representation_t *rep,
+ int cur_chunk)
+{
+ svn_stream_t *wstream;
+ char diffdata[4096]; /* hunk of svndiff data */
+ svn_filesize_t off; /* offset into svndiff data */
+ apr_size_t amt; /* how much svndiff data to/was read */
+ const char *str_key;
+
+ apr_array_header_t *chunks = rep->contents.delta.chunks;
+ rep_delta_chunk_t *this_chunk, *first_chunk;
+
+ cb->init = TRUE;
+ if (chunks->nelts <= cur_chunk)
+ return compose_handler(NULL, cb);
+
+ /* Set up a window handling stream for the svndiff data. */
+ wstream = svn_txdelta_parse_svndiff(compose_handler, cb, TRUE,
+ cb->trail->pool);
+
+ /* First things first: send the "SVN"{version} header through the
+ stream. ### For now, we will just use the version specified
+ in the first chunk, and then verify that no chunks have a
+ different version number than the one used. In the future,
+ we might simply convert chunks that use a different version
+ of the diff format -- or, heck, a different format
+ altogether -- to the format/version of the first chunk. */
+ first_chunk = APR_ARRAY_IDX(chunks, 0, rep_delta_chunk_t*);
+ diffdata[0] = 'S';
+ diffdata[1] = 'V';
+ diffdata[2] = 'N';
+ diffdata[3] = (char) (first_chunk->version);
+ amt = 4;
+ SVN_ERR(svn_stream_write(wstream, diffdata, &amt));
+ /* FIXME: The stream write handler is borked; assert (amt == 4); */
+
+ /* Get this string key which holds this window's data.
+ ### todo: make sure this is an `svndiff' DIFF skel here. */
+ this_chunk = APR_ARRAY_IDX(chunks, cur_chunk, rep_delta_chunk_t*);
+ str_key = this_chunk->string_key;
+
+ /* Run through the svndiff data, at least as far as necessary. */
+ off = 0;
+ do
+ {
+ amt = sizeof(diffdata);
+ SVN_ERR(svn_fs_bdb__string_read(fs, str_key, diffdata,
+ off, &amt, cb->trail,
+ cb->trail->pool));
+ off += amt;
+ SVN_ERR(svn_stream_write(wstream, diffdata, &amt));
+ }
+ while (amt != 0);
+ SVN_ERR(svn_stream_close(wstream));
+
+ SVN_ERR_ASSERT(!cb->init);
+ SVN_ERR_ASSERT(cb->window != NULL);
+ SVN_ERR_ASSERT(cb->window_pool != NULL);
+ return SVN_NO_ERROR;
+}
+
+
+/* Undeltify a range of data. DELTAS is the set of delta windows to
+ combine, FULLTEXT is the source text, CUR_CHUNK is the index of the
+ delta chunk we're starting from. OFFSET is the relative offset of
+ the requested data within the chunk; BUF and LEN are what we're
+ undeltifying to. */
+
+static svn_error_t *
+rep_undeltify_range(svn_fs_t *fs,
+ const apr_array_header_t *deltas,
+ representation_t *fulltext,
+ int cur_chunk,
+ char *buf,
+ apr_size_t offset,
+ apr_size_t *len,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ apr_size_t len_read = 0;
+
+ do
+ {
+ struct compose_handler_baton cb = { 0 };
+ char *source_buf, *target_buf;
+ apr_size_t target_len;
+ int cur_rep;
+
+ cb.trail = trail;
+ cb.done = FALSE;
+ for (cur_rep = 0; !cb.done && cur_rep < deltas->nelts; ++cur_rep)
+ {
+ representation_t *const rep =
+ APR_ARRAY_IDX(deltas, cur_rep, representation_t*);
+ SVN_ERR(get_one_window(&cb, fs, rep, cur_chunk));
+ }
+
+ if (!cb.window)
+ /* That's it, no more source data is available. */
+ break;
+
+ /* The source view length should not be 0 if there are source
+ copy ops in the window. */
+ SVN_ERR_ASSERT(cb.window->sview_len > 0 || cb.window->src_ops == 0);
+
+ /* cb.window is the combined delta window. Read the source text
+ into a buffer. */
+ if (cb.source_buf)
+ {
+ /* The combiner already created the source text from a
+ self-compressed window. */
+ source_buf = cb.source_buf;
+ }
+ else if (fulltext && cb.window->sview_len > 0 && cb.window->src_ops > 0)
+ {
+ apr_size_t source_len = cb.window->sview_len;
+ source_buf = apr_palloc(cb.window_pool, source_len);
+ SVN_ERR(svn_fs_bdb__string_read
+ (fs, fulltext->contents.fulltext.string_key,
+ source_buf, cb.window->sview_offset, &source_len,
+ trail, pool));
+ if (source_len != cb.window->sview_len)
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Svndiff source length inconsistency"));
+ }
+ else
+ {
+ source_buf = NULL; /* Won't read anything from here. */
+ }
+
+ if (offset > 0)
+ {
+ target_len = *len - len_read + offset;
+ target_buf = apr_palloc(cb.window_pool, target_len);
+ }
+ else
+ {
+ target_len = *len - len_read;
+ target_buf = buf;
+ }
+
+ svn_txdelta_apply_instructions(cb.window, source_buf,
+ target_buf, &target_len);
+ if (offset > 0)
+ {
+ SVN_ERR_ASSERT(target_len > offset);
+ target_len -= offset;
+ memcpy(buf, target_buf + offset, target_len);
+ offset = 0; /* Read from the beginning of the next chunk. */
+ }
+ /* Don't need this window any more. */
+ svn_pool_destroy(cb.window_pool);
+
+ len_read += target_len;
+ buf += target_len;
+ ++cur_chunk;
+ }
+ while (len_read < *len);
+
+ *len = len_read;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Calculate the index of the chunk in REP that contains REP_OFFSET,
+ and find the relative CHUNK_OFFSET within the chunk.
+ Return -1 if offset is beyond the end of the represented data.
+ ### The basic assumption is that all delta windows are the same size
+ and aligned at the same offset, so this number is the same in all
+ dependent deltas. Oh, and the chunks in REP must be ordered. */
+
+static int
+get_chunk_offset(representation_t *rep,
+ svn_filesize_t rep_offset,
+ apr_size_t *chunk_offset)
+{
+ const apr_array_header_t *chunks = rep->contents.delta.chunks;
+ int cur_chunk;
+ assert(chunks->nelts);
+
+ /* ### Yes, this is a linear search. I'll change this to bisection
+ the very second we notice it's slowing us down. */
+ for (cur_chunk = 0; cur_chunk < chunks->nelts; ++cur_chunk)
+ {
+ const rep_delta_chunk_t *const this_chunk
+ = APR_ARRAY_IDX(chunks, cur_chunk, rep_delta_chunk_t*);
+
+ if ((this_chunk->offset + this_chunk->size) > rep_offset)
+ {
+ assert(this_chunk->offset <= rep_offset);
+ assert(rep_offset - this_chunk->offset < SVN_MAX_OBJECT_SIZE);
+ *chunk_offset = (apr_size_t) (rep_offset - this_chunk->offset);
+ return cur_chunk;
+ }
+ }
+
+ return -1;
+}
+
+/* Copy into BUF *LEN bytes starting at OFFSET from the string
+ represented via REP_KEY in FS, as part of TRAIL.
+ The number of bytes actually copied is stored in *LEN. */
+static svn_error_t *
+rep_read_range(svn_fs_t *fs,
+ const char *rep_key,
+ svn_filesize_t offset,
+ char *buf,
+ apr_size_t *len,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+ apr_size_t chunk_offset;
+
+ /* Read in our REP. */
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+ if (rep->kind == rep_kind_fulltext)
+ {
+ SVN_ERR(svn_fs_bdb__string_read(fs, rep->contents.fulltext.string_key,
+ buf, offset, len, trail, pool));
+ }
+ else if (rep->kind == rep_kind_delta)
+ {
+ const int cur_chunk = get_chunk_offset(rep, offset, &chunk_offset);
+ if (cur_chunk < 0)
+ *len = 0;
+ else
+ {
+ svn_error_t *err;
+ /* Preserve for potential use in error message. */
+ const char *first_rep_key = rep_key;
+ /* Make a list of all the rep's we need to undeltify this range.
+ We'll have to read them within this trail anyway, so we might
+ as well do it once and up front. */
+ apr_array_header_t *reps = apr_array_make(pool, 30, sizeof(rep));
+ do
+ {
+ const rep_delta_chunk_t *const first_chunk
+ = APR_ARRAY_IDX(rep->contents.delta.chunks,
+ 0, rep_delta_chunk_t*);
+ const rep_delta_chunk_t *const chunk
+ = APR_ARRAY_IDX(rep->contents.delta.chunks,
+ cur_chunk, rep_delta_chunk_t*);
+
+ /* Verify that this chunk is of the same version as the first. */
+ if (first_chunk->version != chunk->version)
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Diff version inconsistencies in representation '%s'"),
+ rep_key);
+
+ rep_key = chunk->rep_key;
+ APR_ARRAY_PUSH(reps, representation_t *) = rep;
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key,
+ trail, pool));
+ }
+ while (rep->kind == rep_kind_delta
+ && rep->contents.delta.chunks->nelts > cur_chunk);
+
+ /* Right. We've either just read the fulltext rep, or a rep that's
+ too short, in which case we'll undeltify without source data.*/
+ if (rep->kind != rep_kind_delta && rep->kind != rep_kind_fulltext)
+ return UNKNOWN_NODE_KIND(rep_key);
+
+ if (rep->kind == rep_kind_delta)
+ rep = NULL; /* Don't use source data */
+
+ err = rep_undeltify_range(fs, reps, rep, cur_chunk, buf,
+ chunk_offset, len, trail, pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_FS_CORRUPT)
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, err,
+ _("Corruption detected whilst reading delta chain from "
+ "representation '%s' to '%s'"), first_rep_key, rep_key);
+ else
+ return svn_error_trace(err);
+ }
+ }
+ }
+ else /* unknown kind */
+ return UNKNOWN_NODE_KIND(rep_key);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__get_mutable_rep(const char **new_rep_key,
+ const char *rep_key,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ representation_t *rep = NULL;
+ const char *new_str = NULL;
+
+ /* We were passed an existing REP_KEY, so examine it. If it is
+ mutable already, then just return REP_KEY as the mutable result
+ key. */
+ if (rep_key && (rep_key[0] != '\0'))
+ {
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+ if (rep_is_mutable(rep, txn_id))
+ {
+ *new_rep_key = rep_key;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* Either we weren't provided a base key to examine, or the base key
+ we were provided was not mutable. So, let's make a new
+ representation and return its key to the caller. */
+ SVN_ERR(svn_fs_bdb__string_append(fs, &new_str, 0, NULL, trail, pool));
+ rep = make_fulltext_rep(new_str, txn_id,
+ svn_checksum_empty_checksum(svn_checksum_md5,
+ pool),
+ svn_checksum_empty_checksum(svn_checksum_sha1,
+ pool),
+ pool);
+ return svn_fs_bdb__write_new_rep(new_rep_key, fs, rep, trail, pool);
+}
+
+
+svn_error_t *
+svn_fs_base__delete_rep_if_mutable(svn_fs_t *fs,
+ const char *rep_key,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+ if (! rep_is_mutable(rep, txn_id))
+ return SVN_NO_ERROR;
+
+ if (rep->kind == rep_kind_fulltext)
+ {
+ SVN_ERR(svn_fs_bdb__string_delete(fs,
+ rep->contents.fulltext.string_key,
+ trail, pool));
+ }
+ else if (rep->kind == rep_kind_delta)
+ {
+ apr_array_header_t *keys;
+ SVN_ERR(delta_string_keys(&keys, rep, pool));
+ SVN_ERR(delete_strings(keys, fs, trail, pool));
+ }
+ else /* unknown kind */
+ return UNKNOWN_NODE_KIND(rep_key);
+
+ return svn_fs_bdb__delete_rep(fs, rep_key, trail, pool);
+}
+
+
+
+/*** Reading and writing data via representations. ***/
+
+/** Reading. **/
+
+struct rep_read_baton
+{
+ /* The FS from which we're reading. */
+ svn_fs_t *fs;
+
+ /* The representation skel whose contents we want to read. If this
+ is NULL, the rep has never had any contents, so all reads fetch 0
+ bytes.
+
+ Formerly, we cached the entire rep skel here, not just the key.
+ That way we didn't have to fetch the rep from the db every time
+ we want to read a little bit more of the file. Unfortunately,
+ this has a problem: if, say, a file's representation changes
+ while we're reading (changes from fulltext to delta, for
+ example), we'll never know it. So for correctness, we now
+ refetch the representation skel every time we want to read
+ another chunk. */
+ const char *rep_key;
+
+ /* How many bytes have been read already. */
+ svn_filesize_t offset;
+
+ /* If present, the read will be done as part of this trail, and the
+ trail's pool will be used. Otherwise, see `pool' below. */
+ trail_t *trail;
+
+ /* MD5 checksum context. Initialized when the baton is created, updated as
+ we read data, and finalized when the stream is closed. */
+ svn_checksum_ctx_t *md5_checksum_ctx;
+
+ /* Final resting place of the checksum created by md5_checksum_cxt. */
+ svn_checksum_t *md5_checksum;
+
+ /* SHA1 checksum context. Initialized when the baton is created, updated as
+ we read data, and finalized when the stream is closed. */
+ svn_checksum_ctx_t *sha1_checksum_ctx;
+
+ /* Final resting place of the checksum created by sha1_checksum_cxt. */
+ svn_checksum_t *sha1_checksum;
+
+ /* The length of the rep's contents (as fulltext, that is,
+ independent of how the rep actually stores the data.) This is
+ retrieved when the baton is created, and used to determine when
+ we have read the last byte, at which point we compare checksums.
+
+ Getting this at baton creation time makes interleaved reads and
+ writes on the same rep in the same trail impossible. But we're
+ not doing that, and probably no one ever should. And anyway if
+ they do, they should see problems immediately. */
+ svn_filesize_t size;
+
+ /* Set to FALSE when the baton is created, TRUE when the checksum_ctx
+ is digestified. */
+ svn_boolean_t checksum_finalized;
+
+ /* Used for temporary allocations. This pool is cleared at the
+ start of each invocation of the relevant stream read function --
+ see rep_read_contents(). */
+ apr_pool_t *scratch_pool;
+
+};
+
+
+static svn_error_t *
+rep_read_get_baton(struct rep_read_baton **rb_p,
+ svn_fs_t *fs,
+ const char *rep_key,
+ svn_boolean_t use_trail_for_reads,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ struct rep_read_baton *b;
+
+ b = apr_pcalloc(pool, sizeof(*b));
+ b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+ b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
+
+ if (rep_key)
+ SVN_ERR(svn_fs_base__rep_contents_size(&(b->size), fs, rep_key,
+ trail, pool));
+ else
+ b->size = 0;
+
+ b->checksum_finalized = FALSE;
+ b->fs = fs;
+ b->trail = use_trail_for_reads ? trail : NULL;
+ b->scratch_pool = svn_pool_create(pool);
+ b->rep_key = rep_key;
+ b->offset = 0;
+
+ *rb_p = b;
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Retrieving data. ***/
+
+svn_error_t *
+svn_fs_base__rep_contents_size(svn_filesize_t *size_p,
+ svn_fs_t *fs,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+
+ if (rep->kind == rep_kind_fulltext)
+ {
+ /* Get the size by asking Berkeley for the string's length. */
+ SVN_ERR(svn_fs_bdb__string_size(size_p, fs,
+ rep->contents.fulltext.string_key,
+ trail, pool));
+ }
+ else if (rep->kind == rep_kind_delta)
+ {
+ /* Get the size by finding the last window pkg in the delta and
+ adding its offset to its size. This way, we won't even be
+ messed up by overlapping windows, as long as the window pkgs
+ are still ordered. */
+ apr_array_header_t *chunks = rep->contents.delta.chunks;
+ rep_delta_chunk_t *last_chunk;
+
+ SVN_ERR_ASSERT(chunks->nelts);
+
+ last_chunk = APR_ARRAY_IDX(chunks, chunks->nelts - 1,
+ rep_delta_chunk_t *);
+ *size_p = last_chunk->offset + last_chunk->size;
+ }
+ else /* unknown kind */
+ return UNKNOWN_NODE_KIND(rep_key);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__rep_contents_checksums(svn_checksum_t **md5_checksum,
+ svn_checksum_t **sha1_checksum,
+ svn_fs_t *fs,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+ if (md5_checksum)
+ *md5_checksum = svn_checksum_dup(rep->md5_checksum, pool);
+ if (sha1_checksum)
+ *sha1_checksum = svn_checksum_dup(rep->sha1_checksum, pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__rep_contents(svn_string_t *str,
+ svn_fs_t *fs,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_filesize_t contents_size;
+ apr_size_t len;
+ char *data;
+
+ SVN_ERR(svn_fs_base__rep_contents_size(&contents_size, fs, rep_key,
+ trail, pool));
+
+ /* What if the contents are larger than we can handle? */
+ if (contents_size > SVN_MAX_OBJECT_SIZE)
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Rep contents are too large: "
+ "got %s, limit is %s"),
+ apr_psprintf(pool, "%" SVN_FILESIZE_T_FMT, contents_size),
+ apr_psprintf(pool, "%" APR_SIZE_T_FMT, SVN_MAX_OBJECT_SIZE));
+ else
+ str->len = (apr_size_t) contents_size;
+
+ data = apr_palloc(pool, str->len);
+ str->data = data;
+ len = str->len;
+ SVN_ERR(rep_read_range(fs, rep_key, 0, data, &len, trail, pool));
+
+ /* Paranoia. */
+ if (len != str->len)
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Failure reading representation '%s'"), rep_key);
+
+ /* Just the standard paranoia. */
+ {
+ representation_t *rep;
+ svn_checksum_t *checksum, *rep_checksum;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+ rep_checksum = rep->sha1_checksum ? rep->sha1_checksum : rep->md5_checksum;
+ SVN_ERR(svn_checksum(&checksum, rep_checksum->kind, str->data, str->len,
+ pool));
+
+ if (! svn_checksum_match(checksum, rep_checksum))
+ return svn_error_create(SVN_ERR_FS_CORRUPT,
+ svn_checksum_mismatch_err(rep_checksum, checksum, pool,
+ _("Checksum mismatch on representation '%s'"),
+ rep_key),
+ NULL);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct read_rep_args
+{
+ struct rep_read_baton *rb; /* The data source. */
+ char *buf; /* Where to put what we read. */
+ apr_size_t *len; /* How much to read / was read. */
+};
+
+
+/* BATON is of type `read_rep_args':
+
+ Read into BATON->rb->buf the *(BATON->len) bytes starting at
+ BATON->rb->offset from the data represented at BATON->rb->rep_key
+ in BATON->rb->fs, as part of TRAIL.
+
+ Afterwards, *(BATON->len) is the number of bytes actually read, and
+ BATON->rb->offset is incremented by that amount.
+
+ If BATON->rb->rep_key is null, this is assumed to mean the file's
+ contents have no representation, i.e., the file has no contents.
+ In that case, if BATON->rb->offset > 0, return the error
+ SVN_ERR_FS_FILE_CONTENTS_CHANGED, else just set *(BATON->len) to
+ zero and return. */
+static svn_error_t *
+txn_body_read_rep(void *baton, trail_t *trail)
+{
+ struct read_rep_args *args = baton;
+
+ if (args->rb->rep_key)
+ {
+ SVN_ERR(rep_read_range(args->rb->fs,
+ args->rb->rep_key,
+ args->rb->offset,
+ args->buf,
+ args->len,
+ trail,
+ args->rb->scratch_pool));
+
+ args->rb->offset += *(args->len);
+
+ /* We calculate the checksum just once, the moment we see the
+ * last byte of data. But we can't assume there was a short
+ * read. The caller may have known the length of the data and
+ * requested exactly that amount, so there would never be a
+ * short read. (That's why the read baton has to know the
+ * length of the data in advance.)
+ *
+ * On the other hand, some callers invoke the stream reader in a
+ * loop whose termination condition is that the read returned
+ * zero bytes of data -- which usually results in the read
+ * function being called one more time *after* the call that got
+ * a short read (indicating end-of-stream).
+ *
+ * The conditions below ensure that we compare checksums even
+ * when there is no short read associated with the last byte of
+ * data, while also ensuring that it's harmless to repeatedly
+ * read 0 bytes from the stream.
+ */
+ if (! args->rb->checksum_finalized)
+ {
+ SVN_ERR(svn_checksum_update(args->rb->md5_checksum_ctx, args->buf,
+ *(args->len)));
+ SVN_ERR(svn_checksum_update(args->rb->sha1_checksum_ctx, args->buf,
+ *(args->len)));
+
+ if (args->rb->offset == args->rb->size)
+ {
+ representation_t *rep;
+
+ SVN_ERR(svn_checksum_final(&args->rb->md5_checksum,
+ args->rb->md5_checksum_ctx,
+ trail->pool));
+ SVN_ERR(svn_checksum_final(&args->rb->sha1_checksum,
+ args->rb->sha1_checksum_ctx,
+ trail->pool));
+ args->rb->checksum_finalized = TRUE;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, args->rb->fs,
+ args->rb->rep_key,
+ trail, trail->pool));
+
+ if (rep->md5_checksum
+ && (! svn_checksum_match(rep->md5_checksum,
+ args->rb->md5_checksum)))
+ return svn_error_create(SVN_ERR_FS_CORRUPT,
+ svn_checksum_mismatch_err(rep->md5_checksum,
+ args->rb->sha1_checksum, trail->pool,
+ _("MD5 checksum mismatch on representation '%s'"),
+ args->rb->rep_key),
+ NULL);
+
+ if (rep->sha1_checksum
+ && (! svn_checksum_match(rep->sha1_checksum,
+ args->rb->sha1_checksum)))
+ return svn_error_createf(SVN_ERR_FS_CORRUPT,
+ svn_checksum_mismatch_err(rep->sha1_checksum,
+ args->rb->sha1_checksum, trail->pool,
+ _("SHA1 checksum mismatch on representation '%s'"),
+ args->rb->rep_key),
+ NULL);
+ }
+ }
+ }
+ else if (args->rb->offset > 0)
+ {
+ return
+ svn_error_create
+ (SVN_ERR_FS_REP_CHANGED, NULL,
+ _("Null rep, but offset past zero already"));
+ }
+ else
+ *(args->len) = 0;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+rep_read_contents(void *baton, char *buf, apr_size_t *len)
+{
+ struct rep_read_baton *rb = baton;
+ struct read_rep_args args;
+
+ /* Clear the scratch pool of the results of previous invocations. */
+ svn_pool_clear(rb->scratch_pool);
+
+ args.rb = rb;
+ args.buf = buf;
+ args.len = len;
+
+ /* If we got a trail, use it; else make one. */
+ if (rb->trail)
+ SVN_ERR(txn_body_read_rep(&args, rb->trail));
+ else
+ {
+ /* In the case of reading from the db, any returned data should
+ live in our pre-allocated buffer, so the whole operation can
+ happen within a single malloc/free cycle. This prevents us
+ from creating millions of unnecessary trail subpools when
+ reading a big file. */
+ SVN_ERR(svn_fs_base__retry_txn(rb->fs,
+ txn_body_read_rep,
+ &args,
+ TRUE,
+ rb->scratch_pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+/** Writing. **/
+
+
+struct rep_write_baton
+{
+ /* The FS in which we're writing. */
+ svn_fs_t *fs;
+
+ /* The representation skel whose contents we want to write. */
+ const char *rep_key;
+
+ /* The transaction id under which this write action will take
+ place. */
+ const char *txn_id;
+
+ /* If present, do the write as part of this trail, and use trail's
+ pool. Otherwise, see `pool' below. */
+ trail_t *trail;
+
+ /* SHA1 and MD5 checksums. Initialized when the baton is created,
+ updated as we write data, and finalized and stored when the
+ stream is closed. */
+ svn_checksum_ctx_t *md5_checksum_ctx;
+ svn_checksum_t *md5_checksum;
+ svn_checksum_ctx_t *sha1_checksum_ctx;
+ svn_checksum_t *sha1_checksum;
+ svn_boolean_t finalized;
+
+ /* Used for temporary allocations, iff `trail' (above) is null. */
+ apr_pool_t *pool;
+
+};
+
+
+static struct rep_write_baton *
+rep_write_get_baton(svn_fs_t *fs,
+ const char *rep_key,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ struct rep_write_baton *b;
+
+ b = apr_pcalloc(pool, sizeof(*b));
+ b->md5_checksum_ctx = svn_checksum_ctx_create(svn_checksum_md5, pool);
+ b->sha1_checksum_ctx = svn_checksum_ctx_create(svn_checksum_sha1, pool);
+ b->fs = fs;
+ b->trail = trail;
+ b->pool = pool;
+ b->rep_key = rep_key;
+ b->txn_id = txn_id;
+ return b;
+}
+
+
+
+/* Write LEN bytes from BUF into the end of the string represented via
+ REP_KEY in FS, as part of TRAIL. If the representation is not
+ mutable, return the error SVN_FS_REP_NOT_MUTABLE. */
+static svn_error_t *
+rep_write(svn_fs_t *fs,
+ const char *rep_key,
+ const char *buf,
+ apr_size_t len,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+
+ if (! rep_is_mutable(rep, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_REP_NOT_MUTABLE, NULL,
+ _("Rep '%s' is not mutable"), rep_key);
+
+ if (rep->kind == rep_kind_fulltext)
+ {
+ SVN_ERR(svn_fs_bdb__string_append
+ (fs, &(rep->contents.fulltext.string_key), len, buf,
+ trail, pool));
+ }
+ else if (rep->kind == rep_kind_delta)
+ {
+ /* There should never be a case when we have a mutable
+ non-fulltext rep. The only code that creates mutable reps is
+ in this file, and it creates them fulltext. */
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Rep '%s' both mutable and non-fulltext"), rep_key);
+ }
+ else /* unknown kind */
+ return UNKNOWN_NODE_KIND(rep_key);
+
+ return SVN_NO_ERROR;
+}
+
+
+struct write_rep_args
+{
+ struct rep_write_baton *wb; /* Destination. */
+ const char *buf; /* Data. */
+ apr_size_t len; /* How much to write. */
+};
+
+
+/* BATON is of type `write_rep_args':
+ Append onto BATON->wb->rep_key's contents BATON->len bytes of
+ data from BATON->wb->buf, in BATON->rb->fs, as part of TRAIL.
+
+ If the representation is not mutable, return the error
+ SVN_FS_REP_NOT_MUTABLE. */
+static svn_error_t *
+txn_body_write_rep(void *baton, trail_t *trail)
+{
+ struct write_rep_args *args = baton;
+
+ SVN_ERR(rep_write(args->wb->fs,
+ args->wb->rep_key,
+ args->buf,
+ args->len,
+ args->wb->txn_id,
+ trail,
+ trail->pool));
+ SVN_ERR(svn_checksum_update(args->wb->md5_checksum_ctx,
+ args->buf, args->len));
+ SVN_ERR(svn_checksum_update(args->wb->sha1_checksum_ctx,
+ args->buf, args->len));
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+rep_write_contents(void *baton,
+ const char *buf,
+ apr_size_t *len)
+{
+ struct rep_write_baton *wb = baton;
+ struct write_rep_args args;
+
+ /* We toss LEN's indirectness because if not all the bytes are
+ written, it's an error, so we wouldn't be reporting anything back
+ through *LEN anyway. */
+ args.wb = wb;
+ args.buf = buf;
+ args.len = *len;
+
+ /* If we got a trail, use it; else make one. */
+ if (wb->trail)
+ SVN_ERR(txn_body_write_rep(&args, wb->trail));
+ else
+ {
+ /* In the case of simply writing the rep to the db, we're
+ *certain* that there's no data coming back to us that needs
+ to be preserved... so the whole operation can happen within a
+ single malloc/free cycle. This prevents us from creating
+ millions of unnecessary trail subpools when writing a big
+ file. */
+ SVN_ERR(svn_fs_base__retry_txn(wb->fs,
+ txn_body_write_rep,
+ &args,
+ TRUE,
+ wb->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Helper for rep_write_close_contents(); see that doc string for
+ more. BATON is of type `struct rep_write_baton'. */
+static svn_error_t *
+txn_body_write_close_rep(void *baton, trail_t *trail)
+{
+ struct rep_write_baton *wb = baton;
+ representation_t *rep;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, wb->fs, wb->rep_key,
+ trail, trail->pool));
+ rep->md5_checksum = svn_checksum_dup(wb->md5_checksum, trail->pool);
+ rep->sha1_checksum = svn_checksum_dup(wb->sha1_checksum, trail->pool);
+ return svn_fs_bdb__write_rep(wb->fs, wb->rep_key, rep,
+ trail, trail->pool);
+}
+
+
+/* BATON is of type `struct rep_write_baton'.
+ *
+ * Finalize BATON->md5_context and store the resulting digest under
+ * BATON->rep_key.
+ */
+static svn_error_t *
+rep_write_close_contents(void *baton)
+{
+ struct rep_write_baton *wb = baton;
+
+ /* ### Thought: if we fixed apr-util MD5 contexts to allow repeated
+ digestification, then we wouldn't need a stream close function at
+ all -- instead, we could update the stored checksum each time a
+ write occurred, which would have the added advantage of making
+ interleaving reads and writes work. Currently, they'd fail with
+ a checksum mismatch, it just happens that our code never tries to
+ do that anyway. */
+
+ if (! wb->finalized)
+ {
+ SVN_ERR(svn_checksum_final(&wb->md5_checksum, wb->md5_checksum_ctx,
+ wb->pool));
+ SVN_ERR(svn_checksum_final(&wb->sha1_checksum, wb->sha1_checksum_ctx,
+ wb->pool));
+ wb->finalized = TRUE;
+ }
+
+ /* If we got a trail, use it; else make one. */
+ if (wb->trail)
+ return txn_body_write_close_rep(wb, wb->trail);
+ else
+ /* We need to keep our trail pool around this time so the
+ checksums we've calculated survive. */
+ return svn_fs_base__retry_txn(wb->fs, txn_body_write_close_rep,
+ wb, FALSE, wb->pool);
+}
+
+
+/** Public read and write stream constructors. **/
+
+svn_error_t *
+svn_fs_base__rep_contents_read_stream(svn_stream_t **rs_p,
+ svn_fs_t *fs,
+ const char *rep_key,
+ svn_boolean_t use_trail_for_reads,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ struct rep_read_baton *rb;
+
+ SVN_ERR(rep_read_get_baton(&rb, fs, rep_key, use_trail_for_reads,
+ trail, pool));
+ *rs_p = svn_stream_create(rb, pool);
+ svn_stream_set_read(*rs_p, rep_read_contents);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Clear the contents of REP_KEY, so that it represents the empty
+ string, as part of TRAIL. TXN_ID is the id of the Subversion
+ transaction under which this occurs. If REP_KEY is not mutable,
+ return the error SVN_ERR_FS_REP_NOT_MUTABLE. */
+static svn_error_t *
+rep_contents_clear(svn_fs_t *fs,
+ const char *rep_key,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+ const char *str_key;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&rep, fs, rep_key, trail, pool));
+
+ /* Make sure it's mutable. */
+ if (! rep_is_mutable(rep, txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_REP_NOT_MUTABLE, NULL,
+ _("Rep '%s' is not mutable"), rep_key);
+
+ SVN_ERR_ASSERT(rep->kind == rep_kind_fulltext);
+
+ /* If rep has no string, just return success. Else, clear the
+ underlying string. */
+ str_key = rep->contents.fulltext.string_key;
+ if (str_key && *str_key)
+ {
+ SVN_ERR(svn_fs_bdb__string_clear(fs, str_key, trail, pool));
+ rep->md5_checksum = NULL;
+ rep->sha1_checksum = NULL;
+ SVN_ERR(svn_fs_bdb__write_rep(fs, rep_key, rep, trail, pool));
+ }
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__rep_contents_write_stream(svn_stream_t **ws_p,
+ svn_fs_t *fs,
+ const char *rep_key,
+ const char *txn_id,
+ svn_boolean_t use_trail_for_writes,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ struct rep_write_baton *wb;
+
+ /* Clear the current rep contents (free mutability check!). */
+ SVN_ERR(rep_contents_clear(fs, rep_key, txn_id, trail, pool));
+
+ /* Now, generate the write baton and stream. */
+ wb = rep_write_get_baton(fs, rep_key, txn_id,
+ use_trail_for_writes ? trail : NULL, pool);
+ *ws_p = svn_stream_create(wb, pool);
+ svn_stream_set_write(*ws_p, rep_write_contents);
+ svn_stream_set_close(*ws_p, rep_write_close_contents);
+
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Deltified storage. ***/
+
+/* Baton for svn_write_fn_t write_string_set(). */
+struct write_svndiff_strings_baton
+{
+ /* The fs where lives the string we're writing. */
+ svn_fs_t *fs;
+
+ /* The key of the string we're writing to. Typically this is
+ initialized to NULL, so svn_fs_base__string_append() can fill in a
+ value. */
+ const char *key;
+
+ /* The amount of txdelta data written to the current
+ string-in-progress. */
+ apr_size_t size;
+
+ /* The amount of svndiff header information we've written thus far
+ to the strings table. */
+ apr_size_t header_read;
+
+ /* The version number of the svndiff data written. ### You'd better
+ not count on this being populated after the first chunk is sent
+ through the interface, since it lives at the 4th byte of the
+ stream. */
+ apr_byte_t version;
+
+ /* The trail we're writing in. */
+ trail_t *trail;
+
+};
+
+
+/* Function of type `svn_write_fn_t', for writing to a collection of
+ strings; BATON is `struct write_svndiff_strings_baton *'.
+
+ On the first call, BATON->key is null. A new string key in
+ BATON->fs is chosen and stored in BATON->key; each call appends
+ *LEN bytes from DATA onto the string. *LEN is never changed; if
+ the write fails to write all *LEN bytes, an error is returned.
+ BATON->size is used to track the total amount of data written via
+ this handler, and must be reset by the caller to 0 when appropriate. */
+static svn_error_t *
+write_svndiff_strings(void *baton, const char *data, apr_size_t *len)
+{
+ struct write_svndiff_strings_baton *wb = baton;
+ const char *buf = data;
+ apr_size_t nheader = 0;
+
+ /* If we haven't stripped all the header information from this
+ stream yet, keep stripping. If someone sends a first window
+ through here that's shorter than 4 bytes long, this will probably
+ cause a nuclear reactor meltdown somewhere in the American
+ midwest. */
+ if (wb->header_read < 4)
+ {
+ nheader = 4 - wb->header_read;
+ *len -= nheader;
+ buf += nheader;
+ wb->header_read += nheader;
+
+ /* If we have *now* read the full 4-byte header, check that
+ least byte for the version number of the svndiff format. */
+ if (wb->header_read == 4)
+ wb->version = *(buf - 1);
+ }
+
+ /* Append to the current string we're writing (or create a new one
+ if WB->key is NULL). */
+ SVN_ERR(svn_fs_bdb__string_append(wb->fs, &(wb->key), *len,
+ buf, wb->trail, wb->trail->pool));
+
+ /* Make sure we (still) have a key. */
+ if (wb->key == NULL)
+ return svn_error_create(SVN_ERR_FS_GENERAL, NULL,
+ _("Failed to get new string key"));
+
+ /* Restore *LEN to the value it *would* have been were it not for
+ header stripping. */
+ *len += nheader;
+
+ /* Increment our running total of bytes written to this string. */
+ wb->size += *len;
+
+ return SVN_NO_ERROR;
+}
+
+
+typedef struct window_write_t
+{
+ const char *key; /* string key for this window */
+ apr_size_t svndiff_len; /* amount of svndiff data written to the string */
+ svn_filesize_t text_off; /* offset of fulltext represented by this window */
+ apr_size_t text_len; /* amount of fulltext data represented by this window */
+
+} window_write_t;
+
+
+svn_error_t *
+svn_fs_base__rep_deltify(svn_fs_t *fs,
+ const char *target,
+ const char *source,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ svn_stream_t *source_stream; /* stream to read the source */
+ svn_stream_t *target_stream; /* stream to read the target */
+ svn_txdelta_stream_t *txdelta_stream; /* stream to read delta windows */
+
+ /* window-y things, and an array to track them */
+ window_write_t *ww;
+ apr_array_header_t *windows;
+
+ /* stream to write new (deltified) target data and its baton */
+ svn_stream_t *new_target_stream;
+ struct write_svndiff_strings_baton new_target_baton;
+
+ /* window handler/baton for writing to above stream */
+ svn_txdelta_window_handler_t new_target_handler;
+ void *new_target_handler_baton;
+
+ /* yes, we do windows */
+ svn_txdelta_window_t *window;
+
+ /* The current offset into the fulltext that our window is about to
+ write. This doubles, after all windows are written, as the
+ total size of the svndiff data for the deltification process. */
+ svn_filesize_t tview_off = 0;
+
+ /* The total amount of diff data written while deltifying. */
+ svn_filesize_t diffsize = 0;
+
+ /* TARGET's original string keys */
+ apr_array_header_t *orig_str_keys;
+
+ /* The checksums for the representation's fulltext contents. */
+ svn_checksum_t *rep_md5_checksum;
+ svn_checksum_t *rep_sha1_checksum;
+
+ /* MD5 digest */
+ const unsigned char *digest;
+
+ /* pool for holding the windows */
+ apr_pool_t *wpool;
+
+ /* Paranoia: never allow a rep to be deltified against itself,
+ because then there would be no fulltext reachable in the delta
+ chain, and badness would ensue. */
+ if (strcmp(target, source) == 0)
+ return svn_error_createf
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Attempt to deltify '%s' against itself"),
+ target);
+
+ /* Set up a handler for the svndiff data, which will write each
+ window to its own string in the `strings' table. */
+ new_target_baton.fs = fs;
+ new_target_baton.trail = trail;
+ new_target_baton.header_read = FALSE;
+ new_target_stream = svn_stream_create(&new_target_baton, pool);
+ svn_stream_set_write(new_target_stream, write_svndiff_strings);
+
+ /* Get streams to our source and target text data. */
+ SVN_ERR(svn_fs_base__rep_contents_read_stream(&source_stream, fs, source,
+ TRUE, trail, pool));
+ SVN_ERR(svn_fs_base__rep_contents_read_stream(&target_stream, fs, target,
+ TRUE, trail, pool));
+
+ /* Setup a stream to convert the textdelta data into svndiff windows. */
+ svn_txdelta2(&txdelta_stream, source_stream, target_stream, TRUE, pool);
+
+ if (bfd->format >= SVN_FS_BASE__MIN_SVNDIFF1_FORMAT)
+ svn_txdelta_to_svndiff3(&new_target_handler, &new_target_handler_baton,
+ new_target_stream, 1,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+ else
+ svn_txdelta_to_svndiff3(&new_target_handler, &new_target_handler_baton,
+ new_target_stream, 0,
+ SVN_DELTA_COMPRESSION_LEVEL_DEFAULT, pool);
+
+ /* subpool for the windows */
+ wpool = svn_pool_create(pool);
+
+ /* Now, loop, manufacturing and dispatching windows of svndiff data. */
+ windows = apr_array_make(pool, 1, sizeof(ww));
+ do
+ {
+ /* Reset some baton variables. */
+ new_target_baton.size = 0;
+ new_target_baton.key = NULL;
+
+ /* Free the window. */
+ svn_pool_clear(wpool);
+
+ /* Fetch the next window of txdelta data. */
+ SVN_ERR(svn_txdelta_next_window(&window, txdelta_stream, wpool));
+
+ /* Send off this package to be written as svndiff data. */
+ SVN_ERR(new_target_handler(window, new_target_handler_baton));
+ if (window)
+ {
+ /* Add a new window description to our array. */
+ ww = apr_pcalloc(pool, sizeof(*ww));
+ ww->key = new_target_baton.key;
+ ww->svndiff_len = new_target_baton.size;
+ ww->text_off = tview_off;
+ ww->text_len = window->tview_len;
+ APR_ARRAY_PUSH(windows, window_write_t *) = ww;
+
+ /* Update our recordkeeping variables. */
+ tview_off += window->tview_len;
+ diffsize += ww->svndiff_len;
+ }
+
+ } while (window);
+
+ svn_pool_destroy(wpool);
+
+ /* Having processed all the windows, we can query the MD5 digest
+ from the stream. */
+ digest = svn_txdelta_md5_digest(txdelta_stream);
+ if (! digest)
+ return svn_error_createf
+ (SVN_ERR_DELTA_MD5_CHECKSUM_ABSENT, NULL,
+ _("Failed to calculate MD5 digest for '%s'"),
+ source);
+
+ /* Construct a list of the strings used by the old representation so
+ that we can delete them later. While we are here, if the old
+ representation was a fulltext, check to make sure the delta we're
+ replacing it with is actually smaller. (Don't perform this check
+ if we're replacing a delta; in that case, we're going for a time
+ optimization, not a space optimization.) */
+ {
+ representation_t *old_rep;
+ const char *str_key;
+
+ SVN_ERR(svn_fs_bdb__read_rep(&old_rep, fs, target, trail, pool));
+ if (old_rep->kind == rep_kind_fulltext)
+ {
+ svn_filesize_t old_size = 0;
+
+ str_key = old_rep->contents.fulltext.string_key;
+ SVN_ERR(svn_fs_bdb__string_size(&old_size, fs, str_key,
+ trail, pool));
+ orig_str_keys = apr_array_make(pool, 1, sizeof(str_key));
+ APR_ARRAY_PUSH(orig_str_keys, const char *) = str_key;
+
+ /* If the new data is NOT an space optimization, destroy the
+ string(s) we created, and get outta here. */
+ if (diffsize >= old_size)
+ {
+ int i;
+ for (i = 0; i < windows->nelts; i++)
+ {
+ ww = APR_ARRAY_IDX(windows, i, window_write_t *);
+ SVN_ERR(svn_fs_bdb__string_delete(fs, ww->key, trail, pool));
+ }
+ return SVN_NO_ERROR;
+ }
+ }
+ else if (old_rep->kind == rep_kind_delta)
+ SVN_ERR(delta_string_keys(&orig_str_keys, old_rep, pool));
+ else /* unknown kind */
+ return UNKNOWN_NODE_KIND(target);
+
+ /* Save the checksums, since the new rep needs them. */
+ rep_md5_checksum = svn_checksum_dup(old_rep->md5_checksum, pool);
+ rep_sha1_checksum = svn_checksum_dup(old_rep->sha1_checksum, pool);
+ }
+
+ /* Hook the new strings we wrote into the rest of the filesystem by
+ building a new representation to replace our old one. */
+ {
+ representation_t new_rep;
+ rep_delta_chunk_t *chunk;
+ apr_array_header_t *chunks;
+ int i;
+
+ new_rep.kind = rep_kind_delta;
+ new_rep.txn_id = NULL;
+
+ /* Migrate the old rep's checksums to the new rep. */
+ new_rep.md5_checksum = svn_checksum_dup(rep_md5_checksum, pool);
+ new_rep.sha1_checksum = svn_checksum_dup(rep_sha1_checksum, pool);
+
+ chunks = apr_array_make(pool, windows->nelts, sizeof(chunk));
+
+ /* Loop through the windows we wrote, creating and adding new
+ chunks to the representation. */
+ for (i = 0; i < windows->nelts; i++)
+ {
+ ww = APR_ARRAY_IDX(windows, i, window_write_t *);
+
+ /* Allocate a chunk and its window */
+ chunk = apr_palloc(pool, sizeof(*chunk));
+ chunk->offset = ww->text_off;
+
+ /* Populate the window */
+ chunk->version = new_target_baton.version;
+ chunk->string_key = ww->key;
+ chunk->size = ww->text_len;
+ chunk->rep_key = source;
+
+ /* Add this chunk to the array. */
+ APR_ARRAY_PUSH(chunks, rep_delta_chunk_t *) = chunk;
+ }
+
+ /* Put the chunks array into the representation. */
+ new_rep.contents.delta.chunks = chunks;
+
+ /* Write out the new representation. */
+ SVN_ERR(svn_fs_bdb__write_rep(fs, target, &new_rep, trail, pool));
+
+ /* Delete the original pre-deltified strings. */
+ SVN_ERR(delete_strings(orig_str_keys, fs, trail, pool));
+ }
+
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_base/reps-strings.h b/subversion/libsvn_fs_base/reps-strings.h
new file mode 100644
index 0000000..475af0c
--- /dev/null
+++ b/subversion/libsvn_fs_base/reps-strings.h
@@ -0,0 +1,176 @@
+/* reps-strings.h : interpreting representations with respect to strings
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_REPS_STRINGS_H
+#define SVN_LIBSVN_FS_REPS_STRINGS_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_io.h"
+#include "svn_fs.h"
+
+#include "trail.h"
+
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* Get or create a mutable representation in FS, and set *NEW_REP_KEY to its
+ key.
+
+ TXN_ID is the id of the Subversion transaction under which this occurs.
+
+ If REP_KEY is not null and is already a mutable representation, set
+ *NEW_REP_KEY to REP_KEY, else create a brand new rep and set *NEW_REP_KEY
+ to its key, allocated in POOL. */
+svn_error_t *svn_fs_base__get_mutable_rep(const char **new_rep_key,
+ const char *rep_key,
+ svn_fs_t *fs,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Delete REP_KEY from FS if REP_KEY is mutable, as part of trail, or
+ do nothing if REP_KEY is immutable. If a mutable rep is deleted,
+ the string it refers to is deleted as well. TXN_ID is the id of
+ the Subversion transaction under which this occurs.
+
+ If no such rep, return SVN_ERR_FS_NO_SUCH_REPRESENTATION. */
+svn_error_t *svn_fs_base__delete_rep_if_mutable(svn_fs_t *fs,
+ const char *rep_key,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+
+/*** Reading and writing rep contents. ***/
+
+/* Set *SIZE_P to the size of REP_KEY's contents in FS, as part of TRAIL.
+ Note: this is the fulltext size, no matter how the contents are
+ represented in storage. */
+svn_error_t *svn_fs_base__rep_contents_size(svn_filesize_t *size_p,
+ svn_fs_t *fs,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* If MD5_CHECKSUM is non-NULL, set *MD5_CHECKSUM to the MD5 checksum
+ for REP_KEY in FS, as part of TRAIL.
+
+ If SHA1_CHECKSUM is non-NULL, set *SHA1_CHECKSUM to the SHA1
+ checksum for REP_KEY in FS, as part of TRAIL.
+
+ These are the prerecorded checksums for the rep's contents'
+ fulltext. If one or both of the checksums is not stored, do not
+ calculate one dynamically, just put NULL into the respective return
+ value. (By convention, the NULL checksum is considered to match
+ any checksum.) */
+svn_error_t *
+svn_fs_base__rep_contents_checksums(svn_checksum_t **md5_checksum,
+ svn_checksum_t **sha1_checksum,
+ svn_fs_t *fs,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set STR->data to the contents of REP_KEY in FS, and STR->len to the
+ contents' length, as part of TRAIL. The data is allocated in
+ POOL. If an error occurs, the effect on STR->data and
+ STR->len is undefined.
+
+ Note: this is the fulltext contents, no matter how the contents are
+ represented in storage. */
+svn_error_t *svn_fs_base__rep_contents(svn_string_t *str,
+ svn_fs_t *fs,
+ const char *rep_key,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *RS_P to a stream to read the contents of REP_KEY in FS.
+ Allocate the stream in POOL.
+
+ REP_KEY may be null, in which case reads just return 0 bytes.
+
+ If USE_TRAIL_FOR_READS is TRUE, the stream's reads are part
+ of TRAIL; otherwise, each read happens in an internal, one-off
+ trail (though TRAIL is still required). POOL may be TRAIL->pool. */
+svn_error_t *
+svn_fs_base__rep_contents_read_stream(svn_stream_t **rs_p,
+ svn_fs_t *fs,
+ const char *rep_key,
+ svn_boolean_t use_trail_for_reads,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Set *WS_P to a stream to write the contents of REP_KEY. Allocate
+ the stream in POOL. TXN_ID is the id of the Subversion transaction
+ under which this occurs.
+
+ If USE_TRAIL_FOR_WRITES is TRUE, the stream's writes are part
+ of TRAIL; otherwise, each write happens in an internal, one-off
+ trail (though TRAIL is still required). POOL may be TRAIL->pool.
+
+ If REP_KEY is not mutable, writes to *WS_P will return the
+ error SVN_ERR_FS_REP_NOT_MUTABLE. */
+svn_error_t *
+svn_fs_base__rep_contents_write_stream(svn_stream_t **ws_p,
+ svn_fs_t *fs,
+ const char *rep_key,
+ const char *txn_id,
+ svn_boolean_t use_trail_for_writes,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+
+/*** Deltified storage. ***/
+
+/* Offer TARGET the chance to store its contents as a delta against
+ SOURCE, in FS, as part of TRAIL. TARGET and SOURCE are both
+ representation keys.
+
+ This usually results in TARGET's data being stored as a diff
+ against SOURCE; but it might not, if it turns out to be more
+ efficient to store the contents some other way. */
+svn_error_t *svn_fs_base__rep_deltify(svn_fs_t *fs,
+ const char *target,
+ const char *source,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_REPS_STRINGS_H */
diff --git a/subversion/libsvn_fs_base/revs-txns.c b/subversion/libsvn_fs_base/revs-txns.c
new file mode 100644
index 0000000..d218843
--- /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;
+}
diff --git a/subversion/libsvn_fs_base/revs-txns.h b/subversion/libsvn_fs_base/revs-txns.h
new file mode 100644
index 0000000..558a90c
--- /dev/null
+++ b/subversion/libsvn_fs_base/revs-txns.h
@@ -0,0 +1,231 @@
+/* revs-txns.h : internal interface to revision and transactions operations
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_REVS_TXNS_H
+#define SVN_LIBSVN_FS_REVS_TXNS_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include "svn_fs.h"
+
+#include "fs.h"
+#include "trail.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/*** Revisions ***/
+
+/* Set *ROOT_ID_P to the ID of the root directory of revision REV in FS,
+ as part of TRAIL. Allocate the ID in POOL. */
+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);
+
+
+/* Set *TXN_ID_P to the ID of the transaction that was committed to
+ create REV in FS, as part of TRAIL. Allocate the ID in POOL. */
+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);
+
+
+/* Set property NAME to VALUE on REV in FS, as part of TRAIL. */
+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);
+
+
+
+/*** Transactions ***/
+
+/* Convert the unfinished transaction in FS named TXN_NAME to a
+ committed transaction that refers to REVISION as part of TRAIL.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed. */
+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);
+
+
+/* Set *REVISION to the revision which was created when FS transaction
+ TXN_NAME was committed, or to SVN_INVALID_REVNUM if the transaction
+ has not been committed. Do all of this as part of TRAIL. */
+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);
+
+
+/* Retrieve information about the Subversion transaction TXN_NAME from
+ the `transactions' table of FS, as part of TRAIL.
+ Set *ROOT_ID_P to the ID of the transaction's root directory.
+ Set *BASE_ROOT_ID_P to the ID of the root directory of the
+ transaction's base revision.
+
+ If there is no such transaction, SVN_ERR_FS_NO_SUCH_TRANSACTION is
+ the error returned.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed.
+
+ Allocate *ROOT_ID_P and *BASE_ROOT_ID_P in POOL. */
+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);
+
+
+/* Set the root directory of the Subversion transaction TXN_NAME in FS
+ to ROOT_ID, as part of TRAIL. Do any necessary temporary
+ allocation in POOL.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed. */
+svn_error_t *svn_fs_base__set_txn_root(svn_fs_t *fs,
+ const char *txn_name,
+ const svn_fs_id_t *root_id,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+/* Add COPY_ID to the list of copies made under the Subversion
+ transaction TXN_NAME in FS as part of TRAIL.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed. */
+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);
+
+
+/* Set the base root directory of TXN_NAME in FS to NEW_ID, as part of
+ TRAIL. Do any necessary temporary allocation in POOL.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed. */
+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);
+
+
+/* Set a property NAME to VALUE on transaction TXN_NAME in FS as part
+ of TRAIL. Use POOL for any necessary allocations.
+
+ Returns SVN_ERR_FS_TRANSACTION_NOT_MUTABLE if TXN_NAME refers to a
+ transaction that has already been committed. */
+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);
+
+
+/* These functions implement some of the calls in the FS loader
+ library's fs and txn vtables. */
+
+svn_error_t *svn_fs_base__youngest_rev(svn_revnum_t *youngest_p, svn_fs_t *fs,
+ apr_pool_t *pool);
+
+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);
+
+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);
+
+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);
+
+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_error_t *svn_fs_base__open_txn(svn_fs_txn_t **txn, svn_fs_t *fs,
+ const char *name, apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__purge_txn(svn_fs_t *fs, const char *txn_id,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__list_transactions(apr_array_header_t **names_p,
+ svn_fs_t *fs, apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__abort_txn(svn_fs_txn_t *txn, apr_pool_t *pool);
+
+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);
+
+svn_error_t *svn_fs_base__txn_proplist(apr_hash_t **table_p,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+/* Helper func: variant of __txn_proplist that uses an existing TRAIL.
+ * TXN_ID identifies the transaction.
+ * *TABLE_P will be non-null upon success.
+ */
+svn_error_t *svn_fs_base__txn_proplist_in_trail(apr_hash_t **table_p,
+ const char *txn_id,
+ trail_t *trail);
+
+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);
+
+svn_error_t *svn_fs_base__change_txn_props(svn_fs_txn_t *txn,
+ const apr_array_header_t *props,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_REVS_TXNS_H */
diff --git a/subversion/libsvn_fs_base/trail.c b/subversion/libsvn_fs_base/trail.c
new file mode 100644
index 0000000..8fdf9be
--- /dev/null
+++ b/subversion/libsvn_fs_base/trail.c
@@ -0,0 +1,292 @@
+/* trail.c : backing out of aborted Berkeley DB 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.
+ * ====================================================================
+ */
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include <apr_pools.h>
+#include "svn_pools.h"
+#include "svn_fs.h"
+#include "fs.h"
+#include "err.h"
+#include "bdb/bdb-err.h"
+#include "bdb/bdb_compat.h"
+#include "trail.h"
+#include "../libsvn_fs/fs-loader.h"
+
+
+#if defined(SVN_FS__TRAIL_DEBUG)
+
+struct trail_debug_t
+{
+ struct trail_debug_t *prev;
+ const char *table;
+ const char *op;
+};
+
+void
+svn_fs_base__trail_debug(trail_t *trail, const char *table, const char *op)
+{
+ struct trail_debug_t *trail_debug;
+
+ trail_debug = apr_palloc(trail->pool, sizeof(*trail_debug));
+ trail_debug->prev = trail->trail_debug;
+ trail_debug->table = table;
+ trail_debug->op = op;
+ trail->trail_debug = trail_debug;
+}
+
+static void
+print_trail_debug(trail_t *trail,
+ const char *txn_body_fn_name,
+ const char *filename, int line)
+{
+ struct trail_debug_t *trail_debug;
+
+ fprintf(stderr, "(%s, %s, %u, %u): ",
+ txn_body_fn_name, filename, line, trail->db_txn ? 1 : 0);
+
+ trail_debug = trail->trail_debug;
+ while (trail_debug)
+ {
+ fprintf(stderr, "(%s, %s) ", trail_debug->table, trail_debug->op);
+ trail_debug = trail_debug->prev;
+ }
+ fprintf(stderr, "\n");
+}
+#else
+#define print_trail_debug(trail, txn_body_fn_name, filename, line)
+#endif /* defined(SVN_FS__TRAIL_DEBUG) */
+
+
+static svn_error_t *
+begin_trail(trail_t **trail_p,
+ svn_fs_t *fs,
+ svn_boolean_t use_txn,
+ apr_pool_t *pool)
+{
+ base_fs_data_t *bfd = fs->fsap_data;
+ trail_t *trail = apr_pcalloc(pool, sizeof(*trail));
+
+ trail->pool = svn_pool_create(pool);
+ trail->fs = fs;
+ if (use_txn)
+ {
+ /* [*]
+ If we're already inside a trail operation, abort() -- this is
+ a coding problem (and will likely hang the repository anyway). */
+ SVN_ERR_ASSERT(! bfd->in_txn_trail);
+
+ SVN_ERR(BDB_WRAP(fs, N_("beginning Berkeley DB transaction"),
+ bfd->bdb->env->txn_begin(bfd->bdb->env, 0,
+ &trail->db_txn, 0)));
+ bfd->in_txn_trail = TRUE;
+ }
+ else
+ {
+ trail->db_txn = NULL;
+ }
+
+ *trail_p = trail;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+abort_trail(trail_t *trail)
+{
+ svn_fs_t *fs = trail->fs;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ if (trail->db_txn)
+ {
+ /* [**]
+ We have to reset the in_txn_trail flag *before* calling
+ DB_TXN->abort(). If we did it the other way around, the next
+ call to begin_trail() (e.g., as part of a txn retry) would
+ cause an abort, even though there's strictly speaking no
+ programming error involved (see comment [*] above).
+
+ In any case, if aborting the txn fails, restarting it will
+ most likely fail for the same reason, and so it's better to
+ see the returned error than to abort. An obvious example is
+ when DB_TXN->abort() returns DB_RUNRECOVERY. */
+ bfd->in_txn_trail = FALSE;
+ SVN_ERR(BDB_WRAP(fs, N_("aborting Berkeley DB transaction"),
+ trail->db_txn->abort(trail->db_txn)));
+ }
+ svn_pool_destroy(trail->pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+commit_trail(trail_t *trail)
+{
+ int db_err;
+ svn_fs_t *fs = trail->fs;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* According to the example in the Berkeley DB manual, txn_commit
+ doesn't return DB_LOCK_DEADLOCK --- all deadlocks are reported
+ earlier. */
+ if (trail->db_txn)
+ {
+ /* See comment [**] in abort_trail() above.
+ An error during txn commit will abort the transaction anyway. */
+ bfd->in_txn_trail = FALSE;
+ SVN_ERR(BDB_WRAP(fs, N_("committing Berkeley DB transaction"),
+ trail->db_txn->commit(trail->db_txn, 0)));
+ }
+
+ /* Do a checkpoint here, if enough has gone on.
+ The checkpoint parameters below are pretty arbitrary. Perhaps
+ there should be an svn_fs_berkeley_mumble function to set them. */
+ db_err = bfd->bdb->env->txn_checkpoint(bfd->bdb->env, 1024, 5, 0);
+
+ /* Pre-4.1 Berkeley documentation says:
+
+ The DB_ENV->txn_checkpoint function returns a non-zero error
+ value on failure, 0 on success, and returns DB_INCOMPLETE if
+ there were pages that needed to be written to complete the
+ checkpoint but that DB_ENV->memp_sync was unable to write
+ immediately.
+
+ It's safe to ignore DB_INCOMPLETE if we get it while
+ checkpointing. (Post-4.1 Berkeley doesn't have DB_INCOMPLETE
+ anymore, so it's not an issue there.) */
+ if (db_err)
+ {
+#if SVN_BDB_HAS_DB_INCOMPLETE
+ if (db_err != DB_INCOMPLETE)
+#endif /* SVN_BDB_HAS_DB_INCOMPLETE */
+ {
+ return svn_fs_bdb__wrap_db
+ (fs, "checkpointing after Berkeley DB transaction", db_err);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+do_retry(svn_fs_t *fs,
+ svn_error_t *(*txn_body)(void *baton, trail_t *trail),
+ void *baton,
+ svn_boolean_t use_txn,
+ svn_boolean_t destroy_trail_pool,
+ apr_pool_t *pool,
+ const char *txn_body_fn_name,
+ const char *filename,
+ int line)
+{
+ for (;;)
+ {
+ trail_t *trail;
+ svn_error_t *svn_err, *err;
+ svn_boolean_t deadlocked = FALSE;
+
+ SVN_ERR(begin_trail(&trail, fs, use_txn, pool));
+
+ /* Do the body of the transaction. */
+ svn_err = (*txn_body)(baton, trail);
+
+ if (! svn_err)
+ {
+ /* The transaction succeeded! Commit it. */
+ SVN_ERR(commit_trail(trail));
+
+ if (use_txn)
+ print_trail_debug(trail, txn_body_fn_name, filename, line);
+
+ /* If our caller doesn't want us to keep trail memory
+ around, destroy our subpool. */
+ if (destroy_trail_pool)
+ svn_pool_destroy(trail->pool);
+
+ return SVN_NO_ERROR;
+ }
+
+ /* Search for a deadlock error on the stack. */
+ for (err = svn_err; err; err = err->child)
+ if (err->apr_err == SVN_ERR_FS_BERKELEY_DB_DEADLOCK)
+ deadlocked = TRUE;
+
+ /* Is this a real error, or do we just need to retry? */
+ if (! deadlocked)
+ {
+ /* Ignore any error returns. The first error is more valuable. */
+ svn_error_clear(abort_trail(trail));
+ return svn_err;
+ }
+
+ svn_error_clear(svn_err);
+
+ /* We deadlocked. Abort the transaction, and try again. */
+ SVN_ERR(abort_trail(trail));
+ }
+}
+
+
+svn_error_t *
+svn_fs_base__retry_debug(svn_fs_t *fs,
+ svn_error_t *(*txn_body)(void *baton, trail_t *trail),
+ void *baton,
+ svn_boolean_t destroy_trail_pool,
+ apr_pool_t *pool,
+ const char *txn_body_fn_name,
+ const char *filename,
+ int line)
+{
+ return do_retry(fs, txn_body, baton, TRUE, destroy_trail_pool, pool,
+ txn_body_fn_name, filename, line);
+}
+
+
+#if defined(SVN_FS__TRAIL_DEBUG)
+#undef svn_fs_base__retry_txn
+#endif
+
+svn_error_t *
+svn_fs_base__retry_txn(svn_fs_t *fs,
+ svn_error_t *(*txn_body)(void *baton, trail_t *trail),
+ void *baton,
+ svn_boolean_t destroy_trail_pool,
+ apr_pool_t *pool)
+{
+ return do_retry(fs, txn_body, baton, TRUE, destroy_trail_pool, pool,
+ "unknown", "", 0);
+}
+
+
+svn_error_t *
+svn_fs_base__retry(svn_fs_t *fs,
+ svn_error_t *(*txn_body)(void *baton, trail_t *trail),
+ void *baton,
+ svn_boolean_t destroy_trail_pool,
+ apr_pool_t *pool)
+{
+ return do_retry(fs, txn_body, baton, FALSE, destroy_trail_pool, pool,
+ NULL, NULL, 0);
+}
diff --git a/subversion/libsvn_fs_base/trail.h b/subversion/libsvn_fs_base/trail.h
new file mode 100644
index 0000000..87fc313
--- /dev/null
+++ b/subversion/libsvn_fs_base/trail.h
@@ -0,0 +1,239 @@
+/* trail.h : internal interface to backing out of aborted Berkeley DB txns
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_TRAIL_H
+#define SVN_LIBSVN_FS_TRAIL_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include <apr_pools.h>
+#include "svn_fs.h"
+#include "fs.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/* "How do I get a trail object? All these functions in the
+ filesystem expect them, and I can't find a function that returns
+ one."
+
+ Well, there isn't a function that returns a trail. All trails come
+ from svn_fs_base__retry_txn. Here's how to use that:
+
+ When using Berkeley DB transactions to protect the integrity of a
+ database, there are several things you need to keep in mind:
+
+ - Any Berkeley DB operation you perform as part of a Berkeley DB
+ transaction may return DB_LOCK_DEADLOCK, meaning that your
+ operation interferes with some other transaction in progress.
+ When this happens, you must abort the transaction, which undoes
+ all the changes you've made so far, and try it again. So every
+ piece of code you ever write to bang on the DB needs to be
+ wrapped up in a retry loop.
+
+ - If, while you're doing your database operations, you also change
+ some in-memory data structures, then you may want to revert those
+ changes if the transaction deadlocks and needs to be retried.
+
+ - If you get a `real' error (i.e., something other than
+ DB_LOCK_DEADLOCK), you must abort your DB transaction, to release
+ its locks and return the database to its previous state.
+ Similarly, you may want to unroll some changes you've made to
+ in-memory data structures.
+
+ - Since a transaction insulates you from database changes made by
+ other processes, it's often possible to cache information about
+ database contents while the transaction lasts. However, this
+ cache may become stale once your transaction is over. So you may
+ need to clear your cache once the transaction completes, either
+ successfully or unsuccessfully.
+
+ The `svn_fs_base__retry_txn' function and its friends help you manage
+ some of that, in one nice package.
+
+ To use it, write your code in a function like this:
+
+ static svn_error_t *
+ txn_body_do_my_thing (void *baton,
+ trail_t *trail)
+ {
+ ...
+ Do everything which needs to be protected by a Berkeley DB
+ transaction here. Use TRAIL->db_txn as your Berkeley DB
+ transaction, and do your allocation in TRAIL->pool. Pass
+ TRAIL on through to any functions which require one.
+
+ If a Berkeley DB operation returns DB_LOCK_DEADLOCK, just
+ return that using the normal Subversion error mechanism
+ (using DB_ERR, for example); don't write a retry loop. If you
+ encounter some other kind of error, return it in the normal
+ fashion.
+ ...
+ }
+
+ Now, call svn_fs_base__retry_txn, passing a pointer to your function as
+ an argument:
+
+ err = svn_fs_base__retry_txn (fs, txn_body_do_my_thing, baton, pool);
+
+ This will simply invoke your function `txn_body_do_my_thing',
+ passing BATON through unchanged, and providing a fresh TRAIL
+ object, containing a pointer to the filesystem object, a Berkeley
+ DB transaction and an APR pool -- a subpool of POOL -- you should
+ use.
+
+ If your function returns a Subversion error wrapping a Berkeley DB
+ DB_LOCK_DEADLOCK error, `svn_fs_base__retry_txn' will abort the trail's
+ Berkeley DB transaction for you (thus undoing any database changes
+ you've made), free the trail's subpool (thus undoing any allocation
+ you may have done), and try the whole thing again with a new trail,
+ containing a new Berkeley DB transaction and pool.
+
+ If your function returns any other kind of Subversion error,
+ `svn_fs_base__retry_txn' will abort the trail's Berkeley DB transaction,
+ free the subpool, and return your error to its caller.
+
+ If, heavens forbid, your function actually succeeds, returning
+ SVN_NO_ERROR, `svn_fs_base__retry_txn' commits the trail's Berkeley DB
+ transaction, thus making your DB changes permanent, leaves the
+ trail's pool alone so all the objects it contains are still
+ around (unless you request otherwise), and returns SVN_NO_ERROR.
+
+
+ Keep the amount of work done in a trail small. C-Mike Pilato said to me:
+
+ I want to draw your attention to something that you may or may not realize
+ about designing for the BDB backend. The 'trail' objects are (generally)
+ representative of Berkeley DB transactions -- that part I'm sure you know.
+ But you might not realize the value of keeping transactions as small as
+ possible. Berkeley DB will accumulate locks (which I believe are
+ page-level, not as tight as row-level like you might hope) over the course
+ of a transaction, releasing those locks only at transaction commit/abort.
+ Berkeley DB backends are configured to have a maximum number of locks and
+ lockers allowed, and it's easier than you might think to hit the max-locks
+ thresholds (especially under high concurrency) and see an error (typically a
+ "Cannot allocate memory") result from that.
+
+ For example, in [a loop] you are writing a bunch of rows to the
+ `changes' table. Could be 10. Could be 100,000. 100,000 writes and
+ associated locks might be a problem or it might not. But I use it as a way
+ to encourage you to think about reducing the amount of work you spend in any
+ one trail [...].
+*/
+
+struct trail_t
+{
+ /* A Berkeley DB transaction. */
+ DB_TXN *db_txn;
+
+ /* The filesystem object with which this trail is associated. */
+ svn_fs_t *fs;
+
+ /* A pool to allocate things in as part of that transaction --- a
+ subpool of the one passed to `begin_trail'. We destroy this pool
+ if we abort the transaction, and leave it around otherwise. */
+ apr_pool_t *pool;
+
+#if defined(SVN_FS__TRAIL_DEBUG)
+ struct trail_debug_t *trail_debug;
+#endif
+};
+typedef struct trail_t trail_t;
+
+
+/* Try a Berkeley DB transaction repeatedly until it doesn't deadlock.
+
+ That is:
+ - Begin a new Berkeley DB transaction, DB_TXN, in the filesystem FS.
+ - Allocate a subpool of POOL, TXN_POOL.
+ - Start a new trail, TRAIL, pointing to DB_TXN and TXN_POOL.
+ - Apply TXN_BODY to BATON and TRAIL. TXN_BODY should try to do
+ some series of DB operations which needs to be atomic, using
+ TRAIL->db_txn as the transaction, and TRAIL->pool for allocation.
+ If a DB operation deadlocks, or if any other kind of error
+ happens, TXN_BODY should simply return with an appropriate
+ svn_error_t, E.
+ - If TXN_BODY returns SVN_NO_ERROR, then commit the transaction,
+ run any completion functions, and return SVN_NO_ERROR. Do *not*
+ free TXN_POOL (unless DESTROY_TRAIL_POOL is set).
+ - If E is a Berkeley DB error indicating that a deadlock occurred,
+ abort the DB transaction and free TXN_POOL. Then retry the whole
+ thing from the top.
+ - If E is any other kind of error, free TXN_POOL and return E.
+
+ One benefit of using this function is that it makes it easy to
+ ensure that whatever transactions a filesystem function starts, it
+ either aborts or commits before it returns. If we don't somehow
+ complete all our transactions, later operations could deadlock. */
+svn_error_t *
+svn_fs_base__retry_txn(svn_fs_t *fs,
+ svn_error_t *(*txn_body)(void *baton,
+ trail_t *trail),
+ void *baton,
+ svn_boolean_t destroy_trail_pool,
+ apr_pool_t *pool);
+
+svn_error_t *
+svn_fs_base__retry_debug(svn_fs_t *fs,
+ svn_error_t *(*txn_body)(void *baton,
+ trail_t *trail),
+ void *baton,
+ svn_boolean_t destroy_trail_pool,
+ apr_pool_t *pool,
+ const char *txn_body_fn_name,
+ const char *filename,
+ int line);
+
+#if defined(SVN_FS__TRAIL_DEBUG)
+#define svn_fs_base__retry_txn(fs, txn_body, baton, destroy, pool) \
+ svn_fs_base__retry_debug(fs, txn_body, baton, destroy, pool, \
+ #txn_body, __FILE__, __LINE__)
+#endif
+
+
+/* Try an action repeatedly until it doesn't deadlock. This is
+ exactly like svn_fs_base__retry_txn() (whose documentation you really
+ should read) except that no Berkeley DB transaction is created. */
+svn_error_t *svn_fs_base__retry(svn_fs_t *fs,
+ svn_error_t *(*txn_body)(void *baton,
+ trail_t *trail),
+ void *baton,
+ svn_boolean_t destroy_trail_pool,
+ apr_pool_t *pool);
+
+
+/* Record that OPeration is being done on TABLE in the TRAIL. */
+#if defined(SVN_FS__TRAIL_DEBUG)
+void svn_fs_base__trail_debug(trail_t *trail, const char *table,
+ const char *op);
+#else
+#define svn_fs_base__trail_debug(trail, table, operation)
+#endif
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_TRAIL_H */
diff --git a/subversion/libsvn_fs_base/tree.c b/subversion/libsvn_fs_base/tree.c
new file mode 100644
index 0000000..e603af4
--- /dev/null
+++ b/subversion/libsvn_fs_base/tree.c
@@ -0,0 +1,5451 @@
+/* tree.c : tree-like filesystem, built on DAG filesystem
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+
+/* The job of this layer is to take a filesystem with lots of node
+ sharing going on --- the real DAG filesystem as it appears in the
+ database --- and make it look and act like an ordinary tree
+ filesystem, with no sharing.
+
+ We do just-in-time cloning: you can walk from some unfinished
+ transaction's root down into directories and files shared with
+ committed revisions; as soon as you try to change something, the
+ appropriate nodes get cloned (and parent directory entries updated)
+ invisibly, behind your back. Any other references you have to
+ nodes that have been cloned by other changes, even made by other
+ processes, are automatically updated to point to the right clones. */
+
+
+#include <stdlib.h>
+#include <string.h>
+#include <assert.h>
+#include "svn_private_config.h"
+#include "svn_hash.h"
+#include "svn_pools.h"
+#include "svn_error.h"
+#include "svn_path.h"
+#include "svn_mergeinfo.h"
+#include "svn_fs.h"
+#include "svn_sorts.h"
+#include "svn_checksum.h"
+#include "fs.h"
+#include "err.h"
+#include "trail.h"
+#include "node-rev.h"
+#include "key-gen.h"
+#include "dag.h"
+#include "tree.h"
+#include "lock.h"
+#include "revs-txns.h"
+#include "id.h"
+#include "bdb/txn-table.h"
+#include "bdb/rev-table.h"
+#include "bdb/nodes-table.h"
+#include "bdb/changes-table.h"
+#include "bdb/copies-table.h"
+#include "bdb/node-origins-table.h"
+#include "bdb/miscellaneous-table.h"
+#include "../libsvn_fs/fs-loader.h"
+#include "private/svn_fspath.h"
+#include "private/svn_fs_util.h"
+#include "private/svn_mergeinfo_private.h"
+
+
+/* ### I believe this constant will become internal to reps-strings.c.
+ ### see the comment in window_consumer() for more information. */
+
+/* ### the comment also seems to need tweaking: the log file stuff
+ ### is no longer an issue... */
+/* Data written to the filesystem through the svn_fs_apply_textdelta()
+ interface is cached in memory until the end of the data stream, or
+ until a size trigger is hit. Define that trigger here (in bytes).
+ Setting the value to 0 will result in no filesystem buffering at
+ all. The value only really matters when dealing with file contents
+ bigger than the value itself. Above that point, large values here
+ allow the filesystem to buffer more data in memory before flushing
+ to the database, which increases memory usage but greatly decreases
+ the amount of disk access (and log-file generation) in database.
+ Smaller values will limit your overall memory consumption, but can
+ drastically hurt throughput by necessitating more write operations
+ to the database (which also generates more log-files). */
+#define WRITE_BUFFER_SIZE 512000
+
+/* The maximum number of cache items to maintain in the node cache. */
+#define NODE_CACHE_MAX_KEYS 32
+
+
+
+/* The root structure. */
+
+/* Structure for svn_fs_root_t's node_cache hash values. */
+struct dag_node_cache_t
+{
+ dag_node_t *node; /* NODE to be cached. */
+ int idx; /* Index into the keys array for this cache item's key. */
+ apr_pool_t *pool; /* Pool in which NODE is allocated. */
+};
+
+
+typedef struct base_root_data_t
+{
+
+ /* For revision roots, this is a dag node for the revision's root
+ directory. For transaction roots, we open the root directory
+ afresh every time, since the root may have been cloned, or
+ the transaction may have disappeared altogether. */
+ dag_node_t *root_dir;
+
+ /* Cache structures, for mapping const char * PATH to const
+ struct dag_node_cache_t * structures.
+
+ ### Currently this is only used for revision roots. To be safe
+ for transaction roots, you must have the guarantee that there is
+ never more than a single transaction root per Subversion
+ transaction ever open at a given time -- having two roots open to
+ the same Subversion transaction would be a request for pain.
+ Also, you have to ensure that if a 'make_path_mutable()' fails for
+ any reason, you don't leave cached nodes for the portion of that
+ function that succeeded. In other words, this cache must never,
+ ever, lie. */
+ apr_hash_t *node_cache;
+ const char *node_cache_keys[NODE_CACHE_MAX_KEYS];
+ int node_cache_idx;
+} base_root_data_t;
+
+
+static svn_fs_root_t *make_revision_root(svn_fs_t *fs, svn_revnum_t rev,
+ dag_node_t *root_dir,
+ apr_pool_t *pool);
+
+static svn_fs_root_t *make_txn_root(svn_fs_t *fs, const char *txn,
+ svn_revnum_t base_rev, apr_uint32_t flags,
+ apr_pool_t *pool);
+
+
+/*** Node Caching in the Roots. ***/
+
+/* Return NODE for PATH from ROOT's node cache, or NULL if the node
+ isn't cached. */
+static dag_node_t *
+dag_node_cache_get(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ base_root_data_t *brd = root->fsap_data;
+ struct dag_node_cache_t *cache_item;
+
+ /* Assert valid input. */
+ assert(*path == '/');
+
+ /* Only allow revision roots. */
+ if (root->is_txn_root)
+ return NULL;
+
+ /* Look in the cache for our desired item. */
+ cache_item = svn_hash_gets(brd->node_cache, path);
+ if (cache_item)
+ return svn_fs_base__dag_dup(cache_item->node, pool);
+
+ return NULL;
+}
+
+
+/* Add the NODE for PATH to ROOT's node cache. Callers should *NOT*
+ call this unless they are adding a currently un-cached item to the
+ cache, or are replacing the NODE for PATH with a new (different)
+ one. */
+static void
+dag_node_cache_set(svn_fs_root_t *root,
+ const char *path,
+ dag_node_t *node)
+{
+ base_root_data_t *brd = root->fsap_data;
+ const char *cache_path;
+ apr_pool_t *cache_pool;
+ struct dag_node_cache_t *cache_item;
+ int num_keys = apr_hash_count(brd->node_cache);
+
+ /* What? No POOL passed to this function?
+
+ To ensure that our cache values live as long as the svn_fs_root_t
+ in which they are ultimately stored, and to allow us to free()
+ them individually without harming the rest, they are each
+ allocated from a subpool of ROOT's pool. We'll keep one subpool
+ around for each cache slot -- as we start expiring stuff
+ to make room for more entries, we'll re-use the expired thing's
+ pool. */
+
+ /* Assert valid input and state. */
+ assert(*path == '/');
+ assert((brd->node_cache_idx <= num_keys)
+ && (num_keys <= NODE_CACHE_MAX_KEYS));
+
+ /* Only allow revision roots. */
+ if (root->is_txn_root)
+ return;
+
+ /* Special case: the caller wants us to replace an existing cached
+ node with a new one. If the callers aren't mindless, this should
+ only happen when a node is made mutable under a transaction
+ root, and that only happens once under that root. So, we'll be a
+ little bit sloppy here, and count on callers doing the right
+ thing. */
+ cache_item = svn_hash_gets(brd->node_cache, path);
+ if (cache_item)
+ {
+ /* ### This section is somehow broken. I don't know how, but it
+ ### is. And I don't want to spend any more time on it. So,
+ ### callers, use only revision root and don't try to update
+ ### an already-cached thing. -- cmpilato */
+ SVN_ERR_MALFUNCTION_NO_RETURN();
+
+#if 0
+ int cache_index = cache_item->idx;
+ cache_path = brd->node_cache_keys[cache_index];
+ cache_pool = cache_item->pool;
+ cache_item->node = svn_fs_base__dag_dup(node, cache_pool);
+
+ /* Now, move the cache key reference to the end of the keys in
+ the keys array (unless it's already at the end). ### Yes,
+ it's a memmove(), but we're not talking about pages of memory
+ here. */
+ if (cache_index != (num_keys - 1))
+ {
+ int move_num = NODE_CACHE_MAX_KEYS - cache_index - 1;
+ memmove(brd->node_cache_keys + cache_index,
+ brd->node_cache_keys + cache_index + 1,
+ move_num * sizeof(const char *));
+ cache_index = num_keys - 1;
+ brd->node_cache_keys[cache_index] = cache_path;
+ }
+
+ /* Advance the cache pointers. */
+ cache_item->idx = cache_index;
+ brd->node_cache_idx = (cache_index + 1) % NODE_CACHE_MAX_KEYS;
+ return;
+#endif
+ }
+
+ /* We're adding a new cache item. First, see if we have room for it
+ (otherwise, make some room). */
+ if (apr_hash_count(brd->node_cache) == NODE_CACHE_MAX_KEYS)
+ {
+ /* No room. Expire the oldest thing. */
+ cache_path = brd->node_cache_keys[brd->node_cache_idx];
+ cache_item = svn_hash_gets(brd->node_cache, cache_path);
+ svn_hash_sets(brd->node_cache, cache_path, NULL);
+ cache_pool = cache_item->pool;
+ svn_pool_clear(cache_pool);
+ }
+ else
+ {
+ cache_pool = svn_pool_create(root->pool);
+ }
+
+ /* Make the cache item, allocated in its own pool. */
+ cache_item = apr_palloc(cache_pool, sizeof(*cache_item));
+ cache_item->node = svn_fs_base__dag_dup(node, cache_pool);
+ cache_item->idx = brd->node_cache_idx;
+ cache_item->pool = cache_pool;
+
+ /* Now add it to the cache. */
+ cache_path = apr_pstrdup(cache_pool, path);
+ svn_hash_sets(brd->node_cache, cache_path, cache_item);
+ brd->node_cache_keys[brd->node_cache_idx] = cache_path;
+
+ /* Advance the cache pointer. */
+ brd->node_cache_idx = (brd->node_cache_idx + 1) % NODE_CACHE_MAX_KEYS;
+}
+
+
+
+
+/* Creating transaction and revision root nodes. */
+
+struct txn_root_args
+{
+ svn_fs_root_t **root_p;
+ svn_fs_txn_t *txn;
+};
+
+
+static svn_error_t *
+txn_body_txn_root(void *baton,
+ trail_t *trail)
+{
+ struct txn_root_args *args = baton;
+ svn_fs_root_t **root_p = args->root_p;
+ svn_fs_txn_t *txn = args->txn;
+ svn_fs_t *fs = txn->fs;
+ const char *svn_txn_id = txn->id;
+ const svn_fs_id_t *root_id, *base_root_id;
+ svn_fs_root_t *root;
+ apr_hash_t *txnprops;
+ apr_uint32_t flags = 0;
+
+ /* Verify that the transaction actually exists. */
+ SVN_ERR(svn_fs_base__get_txn_ids(&root_id, &base_root_id, fs,
+ svn_txn_id, trail, trail->pool));
+
+ /* Look for special txn props that represent the 'flags' behavior of
+ the transaction. */
+ SVN_ERR(svn_fs_base__txn_proplist_in_trail(&txnprops, svn_txn_id, trail));
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_OOD))
+ flags |= SVN_FS_TXN_CHECK_OOD;
+
+ if (svn_hash_gets(txnprops, SVN_FS__PROP_TXN_CHECK_LOCKS))
+ flags |= SVN_FS_TXN_CHECK_LOCKS;
+
+ root = make_txn_root(fs, svn_txn_id, txn->base_rev, flags, trail->pool);
+
+ *root_p = root;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__txn_root(svn_fs_root_t **root_p,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ struct txn_root_args args;
+
+ args.root_p = &root;
+ args.txn = txn;
+ SVN_ERR(svn_fs_base__retry_txn(txn->fs, txn_body_txn_root, &args,
+ FALSE, pool));
+
+ *root_p = root;
+ return SVN_NO_ERROR;
+}
+
+
+struct revision_root_args
+{
+ svn_fs_root_t **root_p;
+ svn_revnum_t rev;
+};
+
+
+static svn_error_t *
+txn_body_revision_root(void *baton,
+ trail_t *trail)
+{
+ struct revision_root_args *args = baton;
+ dag_node_t *root_dir;
+ svn_fs_root_t *root;
+
+ SVN_ERR(svn_fs_base__dag_revision_root(&root_dir, trail->fs, args->rev,
+ trail, trail->pool));
+ root = make_revision_root(trail->fs, args->rev, root_dir, trail->pool);
+
+ *args->root_p = root;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__revision_root(svn_fs_root_t **root_p,
+ svn_fs_t *fs,
+ svn_revnum_t rev,
+ apr_pool_t *pool)
+{
+ struct revision_root_args args;
+ svn_fs_root_t *root;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ args.root_p = &root;
+ args.rev = rev;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_revision_root, &args,
+ FALSE, pool));
+
+ *root_p = root;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Getting dag nodes for roots. */
+
+
+/* Set *NODE_P to a freshly opened dag node referring to the root
+ directory of ROOT, as part of TRAIL. */
+static svn_error_t *
+root_node(dag_node_t **node_p,
+ svn_fs_root_t *root,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ base_root_data_t *brd = root->fsap_data;
+
+ if (! root->is_txn_root)
+ {
+ /* It's a revision root, so we already have its root directory
+ opened. */
+ *node_p = svn_fs_base__dag_dup(brd->root_dir, pool);
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* It's a transaction root. Open a fresh copy. */
+ return svn_fs_base__dag_txn_root(node_p, root->fs, root->txn,
+ trail, pool);
+ }
+}
+
+
+/* Set *NODE_P to a mutable root directory for ROOT, cloning if
+ necessary, as part of TRAIL. ROOT must be a transaction root. Use
+ ERROR_PATH in error messages. */
+static svn_error_t *
+mutable_root_node(dag_node_t **node_p,
+ svn_fs_root_t *root,
+ const char *error_path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ if (root->is_txn_root)
+ return svn_fs_base__dag_clone_root(node_p, root->fs, root->txn,
+ trail, pool);
+ else
+ /* If it's not a transaction root, we can't change its contents. */
+ return SVN_FS__ERR_NOT_MUTABLE(root->fs, root->rev, error_path);
+}
+
+
+
+/* Traversing directory paths. */
+
+typedef enum copy_id_inherit_t
+{
+ copy_id_inherit_unknown = 0,
+ copy_id_inherit_self,
+ copy_id_inherit_parent,
+ copy_id_inherit_new
+
+} copy_id_inherit_t;
+
+/* A linked list representing the path from a node up to a root
+ directory. We use this for cloning, and for operations that need
+ to deal with both a node and its parent directory. For example, a
+ `delete' operation needs to know that the node actually exists, but
+ also needs to change the parent directory. */
+typedef struct parent_path_t
+{
+
+ /* A node along the path. This could be the final node, one of its
+ parents, or the root. Every parent path ends with an element for
+ the root directory. */
+ dag_node_t *node;
+
+ /* The name NODE has in its parent directory. This is zero for the
+ root directory, which (obviously) has no name in its parent. */
+ char *entry;
+
+ /* The parent of NODE, or zero if NODE is the root directory. */
+ struct parent_path_t *parent;
+
+ /* The copy ID inheritance style. */
+ copy_id_inherit_t copy_inherit;
+
+ /* If copy ID inheritance style is copy_id_inherit_new, this is the
+ path which should be implicitly copied; otherwise, this is NULL. */
+ const char *copy_src_path;
+
+} parent_path_t;
+
+
+/* Return the FS path for the parent path chain object PARENT_PATH,
+ allocated in POOL. */
+static const char *
+parent_path_path(parent_path_t *parent_path,
+ apr_pool_t *pool)
+{
+ const char *path_so_far = "/";
+ if (parent_path->parent)
+ path_so_far = parent_path_path(parent_path->parent, pool);
+ return parent_path->entry
+ ? svn_fspath__join(path_so_far, parent_path->entry, pool)
+ : path_so_far;
+}
+
+
+/* Return the FS path for the parent path chain object CHILD relative
+ to its ANCESTOR in the same chain, allocated in POOL. */
+static const char *
+parent_path_relpath(parent_path_t *child,
+ parent_path_t *ancestor,
+ apr_pool_t *pool)
+{
+ const char *path_so_far = "";
+ parent_path_t *this_node = child;
+ while (this_node != ancestor)
+ {
+ assert(this_node != NULL);
+ path_so_far = svn_relpath_join(this_node->entry, path_so_far, pool);
+ this_node = this_node->parent;
+ }
+ return path_so_far;
+}
+
+
+/* Choose a copy ID inheritance method *INHERIT_P to be used in the
+ event that immutable node CHILD in FS needs to be made mutable. If
+ the inheritance method is copy_id_inherit_new, also return a
+ *COPY_SRC_PATH on which to base the new copy ID (else return NULL
+ for that path). CHILD must have a parent (it cannot be the root
+ node). TXN_ID is the transaction in which these items might be
+ mutable. */
+static svn_error_t *
+get_copy_inheritance(copy_id_inherit_t *inherit_p,
+ const char **copy_src_path,
+ svn_fs_t *fs,
+ parent_path_t *child,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *child_id, *parent_id;
+ const char *child_copy_id, *parent_copy_id;
+ const char *id_path = NULL;
+
+ SVN_ERR_ASSERT(child && child->parent && txn_id);
+
+ /* Initialize our return variables (default: self-inheritance). */
+ *inherit_p = copy_id_inherit_self;
+ *copy_src_path = NULL;
+
+ /* Initialize some convenience variables. */
+ child_id = svn_fs_base__dag_get_id(child->node);
+ parent_id = svn_fs_base__dag_get_id(child->parent->node);
+ child_copy_id = svn_fs_base__id_copy_id(child_id);
+ parent_copy_id = svn_fs_base__id_copy_id(parent_id);
+
+ /* Easy out: if this child is already mutable, we have nothing to do. */
+ if (svn_fs_base__key_compare(svn_fs_base__id_txn_id(child_id), txn_id) == 0)
+ return SVN_NO_ERROR;
+
+ /* If the child and its parent are on the same branch, then the
+ child will inherit the copy ID of its parent when made mutable.
+ This is trivially detectable when the child and its parent have
+ the same copy ID. But that's not the sole indicator of
+ same-branchness. It might be the case that the parent was the
+ result of a copy, but the child has not yet been cloned for
+ mutability since that copy. Detection of this latter case
+ basically means making sure the copy IDs don't differ for some
+ other reason, such as that the child was the direct target of the
+ copy whose ID it has. There is a special case here, too -- if
+ the child's copy ID is the special ID "0", it can't have been the
+ target of any copy, and therefore must be on the same branch as
+ its parent. */
+ if ((strcmp(child_copy_id, "0") == 0)
+ || (svn_fs_base__key_compare(child_copy_id, parent_copy_id) == 0))
+ {
+ *inherit_p = copy_id_inherit_parent;
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ copy_t *copy;
+ SVN_ERR(svn_fs_bdb__get_copy(&copy, fs, child_copy_id, trail, pool));
+ if (svn_fs_base__id_compare(copy->dst_noderev_id, child_id) == -1)
+ {
+ *inherit_p = copy_id_inherit_parent;
+ return SVN_NO_ERROR;
+ }
+ }
+
+ /* If we get here, the child and its parent are not on speaking
+ terms -- there will be no parental inheritance handed down in
+ *this* generation. */
+
+ /* If the child was created at a different path than the one we are
+ expecting its clone to live, one of its parents must have been
+ created via a copy since the child was created. The child isn't
+ on the same branch as its parent (we caught those cases early);
+ it can't keep its current copy ID because there's been an
+ affecting copy (its clone won't be on the same branch as the
+ child is). That leaves only one course of action -- to assign
+ the child a brand new "soft" copy ID. */
+ id_path = svn_fs_base__dag_get_created_path(child->node);
+ if (strcmp(id_path, parent_path_path(child, pool)) != 0)
+ {
+ *inherit_p = copy_id_inherit_new;
+ *copy_src_path = id_path;
+ return SVN_NO_ERROR;
+ }
+
+ /* The node gets to keep its own ID. */
+ return SVN_NO_ERROR;
+}
+
+
+/* Allocate a new parent_path_t node from POOL, referring to NODE,
+ ENTRY, PARENT, and COPY_ID. */
+static parent_path_t *
+make_parent_path(dag_node_t *node,
+ char *entry,
+ parent_path_t *parent,
+ apr_pool_t *pool)
+{
+ parent_path_t *parent_path = apr_pcalloc(pool, sizeof(*parent_path));
+ parent_path->node = node;
+ parent_path->entry = entry;
+ parent_path->parent = parent;
+ parent_path->copy_inherit = copy_id_inherit_unknown;
+ parent_path->copy_src_path = NULL;
+ return parent_path;
+}
+
+
+/* Flags for open_path. */
+typedef enum open_path_flags_t {
+
+ /* The last component of the PATH need not exist. (All parent
+ directories must exist, as usual.) If the last component doesn't
+ exist, simply leave the `node' member of the bottom parent_path
+ component zero. */
+ open_path_last_optional = 1
+
+} open_path_flags_t;
+
+
+/* Open the node identified by PATH in ROOT, as part of TRAIL. Set
+ *PARENT_PATH_P to a path from the node up to ROOT, allocated in
+ TRAIL->pool. The resulting *PARENT_PATH_P value is guaranteed to
+ contain at least one element, for the root directory.
+
+ If resulting *PARENT_PATH_P will eventually be made mutable and
+ modified, or if copy ID inheritance information is otherwise
+ needed, TXN_ID should be the ID of the mutability transaction. If
+ TXN_ID is NULL, no copy ID in heritance information will be
+ calculated for the *PARENT_PATH_P chain.
+
+ If FLAGS & open_path_last_optional is zero, return the error
+ SVN_ERR_FS_NOT_FOUND if the node PATH refers to does not exist. If
+ non-zero, require all the parent directories to exist as normal,
+ but if the final path component doesn't exist, simply return a path
+ whose bottom `node' member is zero. This option is useful for
+ callers that create new nodes --- we find the parent directory for
+ them, and tell them whether the entry exists already.
+
+ NOTE: Public interfaces which only *read* from the filesystem
+ should not call this function directly, but should instead use
+ get_dag().
+*/
+static svn_error_t *
+open_path(parent_path_t **parent_path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ int flags,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = root->fs;
+ dag_node_t *here; /* The directory we're currently looking at. */
+ parent_path_t *parent_path; /* The path from HERE up to the root. */
+ const char *rest; /* The portion of PATH we haven't traversed yet. */
+ const char *canon_path = svn_fs__canonicalize_abspath(path, pool);
+ const char *path_so_far = "/";
+
+ /* Make a parent_path item for the root node, using its own current
+ copy id. */
+ SVN_ERR(root_node(&here, root, trail, pool));
+ parent_path = make_parent_path(here, 0, 0, pool);
+ parent_path->copy_inherit = copy_id_inherit_self;
+
+ rest = canon_path + 1; /* skip the leading '/', it saves in iteration */
+
+ /* Whenever we are at the top of this loop:
+ - HERE is our current directory,
+ - ID is the node revision ID of HERE,
+ - REST is the path we're going to find in HERE, and
+ - PARENT_PATH includes HERE and all its parents. */
+ for (;;)
+ {
+ const char *next;
+ char *entry;
+ dag_node_t *child;
+
+ /* Parse out the next entry from the path. */
+ entry = svn_fs__next_entry_name(&next, rest, pool);
+
+ /* Calculate the path traversed thus far. */
+ path_so_far = svn_fspath__join(path_so_far, entry, pool);
+
+ if (*entry == '\0')
+ {
+ /* Given the behavior of svn_fs__next_entry_name(), this
+ happens when the path either starts or ends with a slash.
+ In either case, we stay put: the current directory stays
+ the same, and we add nothing to the parent path. */
+ child = here;
+ }
+ else
+ {
+ copy_id_inherit_t inherit;
+ const char *copy_path = NULL;
+ svn_error_t *err = SVN_NO_ERROR;
+ dag_node_t *cached_node;
+
+ /* If we found a directory entry, follow it. First, we
+ check our node cache, and, failing that, we hit the DAG
+ layer. */
+ cached_node = dag_node_cache_get(root, path_so_far, pool);
+ if (cached_node)
+ child = cached_node;
+ else
+ err = svn_fs_base__dag_open(&child, here, entry, trail, pool);
+
+ /* "file not found" requires special handling. */
+ if (err && err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ {
+ /* If this was the last path component, and the caller
+ said it was optional, then don't return an error;
+ just put a NULL node pointer in the path. */
+
+ svn_error_clear(err);
+
+ if ((flags & open_path_last_optional)
+ && (! next || *next == '\0'))
+ {
+ parent_path = make_parent_path(NULL, entry, parent_path,
+ pool);
+ break;
+ }
+ else
+ {
+ /* Build a better error message than svn_fs_base__dag_open
+ can provide, giving the root and full path name. */
+ return SVN_FS__NOT_FOUND(root, path);
+ }
+ }
+
+ /* Other errors we return normally. */
+ SVN_ERR(err);
+
+ /* Now, make a parent_path item for CHILD. */
+ parent_path = make_parent_path(child, entry, parent_path, pool);
+ if (txn_id)
+ {
+ SVN_ERR(get_copy_inheritance(&inherit, &copy_path,
+ fs, parent_path, txn_id,
+ trail, pool));
+ parent_path->copy_inherit = inherit;
+ parent_path->copy_src_path = apr_pstrdup(pool, copy_path);
+ }
+
+ /* Cache the node we found (if it wasn't already cached). */
+ if (! cached_node)
+ dag_node_cache_set(root, path_so_far, child);
+ }
+
+ /* Are we finished traversing the path? */
+ if (! next)
+ break;
+
+ /* The path isn't finished yet; we'd better be in a directory. */
+ if (svn_fs_base__dag_node_kind(child) != svn_node_dir)
+ SVN_ERR_W(SVN_FS__ERR_NOT_DIRECTORY(fs, path_so_far),
+ apr_psprintf(pool, _("Failure opening '%s'"), path));
+
+ rest = next;
+ here = child;
+ }
+
+ *parent_path_p = parent_path;
+ return SVN_NO_ERROR;
+}
+
+
+/* Make the node referred to by PARENT_PATH mutable, if it isn't
+ already, as part of TRAIL. ROOT must be the root from which
+ PARENT_PATH descends. Clone any parent directories as needed.
+ Adjust the dag nodes in PARENT_PATH to refer to the clones. Use
+ ERROR_PATH in error messages. */
+static svn_error_t *
+make_path_mutable(svn_fs_root_t *root,
+ parent_path_t *parent_path,
+ const char *error_path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ dag_node_t *cloned_node;
+ const char *txn_id = root->txn;
+ svn_fs_t *fs = root->fs;
+
+ /* Is the node mutable already? */
+ if (svn_fs_base__dag_check_mutable(parent_path->node, txn_id))
+ return SVN_NO_ERROR;
+
+ /* Are we trying to clone the root, or somebody's child node? */
+ if (parent_path->parent)
+ {
+ const svn_fs_id_t *parent_id;
+ const svn_fs_id_t *node_id = svn_fs_base__dag_get_id(parent_path->node);
+ const char *copy_id = NULL;
+ const char *copy_src_path = parent_path->copy_src_path;
+ copy_id_inherit_t inherit = parent_path->copy_inherit;
+ const char *clone_path;
+
+ /* We're trying to clone somebody's child. Make sure our parent
+ is mutable. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent,
+ error_path, trail, pool));
+
+ switch (inherit)
+ {
+ case copy_id_inherit_parent:
+ parent_id = svn_fs_base__dag_get_id(parent_path->parent->node);
+ copy_id = svn_fs_base__id_copy_id(parent_id);
+ break;
+
+ case copy_id_inherit_new:
+ SVN_ERR(svn_fs_bdb__reserve_copy_id(&copy_id, fs, trail, pool));
+ break;
+
+ case copy_id_inherit_self:
+ copy_id = NULL;
+ break;
+
+ case copy_id_inherit_unknown:
+ default:
+ SVN_ERR_MALFUNCTION(); /* uh-oh -- somebody didn't calculate copy-ID
+ inheritance data. */
+ }
+
+ /* Now make this node mutable. */
+ clone_path = parent_path_path(parent_path->parent, pool);
+ SVN_ERR(svn_fs_base__dag_clone_child(&cloned_node,
+ parent_path->parent->node,
+ clone_path,
+ parent_path->entry,
+ copy_id, txn_id,
+ trail, pool));
+
+ /* If we just created a brand new copy ID, we need to store a
+ `copies' table entry for it, as well as a notation in the
+ transaction that should this transaction be terminated, our
+ new copy needs to be removed. */
+ if (inherit == copy_id_inherit_new)
+ {
+ const svn_fs_id_t *new_node_id =
+ svn_fs_base__dag_get_id(cloned_node);
+ SVN_ERR(svn_fs_bdb__create_copy(fs, copy_id, copy_src_path,
+ svn_fs_base__id_txn_id(node_id),
+ new_node_id,
+ copy_kind_soft, trail, pool));
+ SVN_ERR(svn_fs_base__add_txn_copy(fs, txn_id, copy_id,
+ trail, pool));
+ }
+ }
+ else
+ {
+ /* We're trying to clone the root directory. */
+ SVN_ERR(mutable_root_node(&cloned_node, root, error_path, trail, pool));
+ }
+
+ /* Update the PARENT_PATH link to refer to the clone. */
+ parent_path->node = cloned_node;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Walk up PARENT_PATH to the root of the tree, adjusting each node's
+ mergeinfo count by COUNT_DELTA as part of Subversion transaction
+ TXN_ID and TRAIL. Use POOL for allocations. */
+static svn_error_t *
+adjust_parent_mergeinfo_counts(parent_path_t *parent_path,
+ apr_int64_t count_delta,
+ const char *txn_id,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ apr_pool_t *iterpool;
+ parent_path_t *pp = parent_path;
+
+ if (count_delta == 0)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(pool);
+
+ while (pp)
+ {
+ svn_pool_clear(iterpool);
+ SVN_ERR(svn_fs_base__dag_adjust_mergeinfo_count(pp->node, count_delta,
+ txn_id, trail,
+ iterpool));
+ pp = pp->parent;
+ }
+ svn_pool_destroy(iterpool);
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Open the node identified by PATH in ROOT, as part of TRAIL. Set
+ *DAG_NODE_P to the node we find, allocated in TRAIL->pool. Return
+ the error SVN_ERR_FS_NOT_FOUND if this node doesn't exist. */
+static svn_error_t *
+get_dag(dag_node_t **dag_node_p,
+ svn_fs_root_t *root,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ parent_path_t *parent_path;
+ dag_node_t *node = NULL;
+
+ /* Canonicalize the input PATH. */
+ path = svn_fs__canonicalize_abspath(path, pool);
+
+ /* If ROOT is a revision root, we'll look for the DAG in our cache. */
+ node = dag_node_cache_get(root, path, pool);
+ if (! node)
+ {
+ /* Call open_path with no flags, as we want this to return an error
+ if the node for which we are searching doesn't exist. */
+ SVN_ERR(open_path(&parent_path, root, path, 0, NULL, trail, pool));
+ node = parent_path->node;
+
+ /* No need to cache our find -- open_path() will do that for us. */
+ }
+
+ *dag_node_p = node;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Populating the `changes' table. */
+
+/* Add a change to the changes table in FS, keyed on transaction id
+ TXN_ID, and indicated that a change of kind CHANGE_KIND occurred on
+ PATH (whose node revision id is--or was, in the case of a
+ deletion--NODEREV_ID), and optionally that TEXT_MODs or PROP_MODs
+ occurred. Do all this as part of TRAIL. */
+static svn_error_t *
+add_change(svn_fs_t *fs,
+ const char *txn_id,
+ const char *path,
+ const svn_fs_id_t *noderev_id,
+ svn_fs_path_change_kind_t change_kind,
+ svn_boolean_t text_mod,
+ svn_boolean_t prop_mod,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ change_t change;
+ change.path = svn_fs__canonicalize_abspath(path, pool);
+ change.noderev_id = noderev_id;
+ change.kind = change_kind;
+ change.text_mod = text_mod;
+ change.prop_mod = prop_mod;
+ return svn_fs_bdb__changes_add(fs, txn_id, &change, trail, pool);
+}
+
+
+
+/* Generic node operations. */
+
+
+struct node_id_args {
+ const svn_fs_id_t **id_p;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_node_id(void *baton, trail_t *trail)
+{
+ struct node_id_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ *args->id_p = svn_fs_base__id_copy(svn_fs_base__dag_get_id(node),
+ trail->pool);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_node_id(const svn_fs_id_t **id_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ base_root_data_t *brd = root->fsap_data;
+
+ if (! root->is_txn_root
+ && (path[0] == '\0' || ((path[0] == '/') && (path[1] == '\0'))))
+ {
+ /* Optimize the case where we don't need any db access at all.
+ The root directory ("" or "/") node is stored in the
+ svn_fs_root_t object, and never changes when it's a revision
+ root, so we can just reach in and grab it directly. */
+ *id_p = svn_fs_base__id_copy(svn_fs_base__dag_get_id(brd->root_dir),
+ pool);
+ }
+ else
+ {
+ const svn_fs_id_t *id;
+ struct node_id_args args;
+
+ args.id_p = &id;
+ args.root = root;
+ args.path = path;
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_id, &args,
+ FALSE, pool));
+ *id_p = id;
+ }
+ return SVN_NO_ERROR;
+}
+
+
+struct node_created_rev_args {
+ svn_revnum_t revision;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_node_created_rev(void *baton, trail_t *trail)
+{
+ struct node_created_rev_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ return svn_fs_base__dag_get_revision(&(args->revision), node,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_node_created_rev(svn_revnum_t *revision,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct node_created_rev_args args;
+
+ args.revision = SVN_INVALID_REVNUM;
+ args.root = root;
+ args.path = path;
+ SVN_ERR(svn_fs_base__retry_txn
+ (root->fs, txn_body_node_created_rev, &args, TRUE, pool));
+ *revision = args.revision;
+ return SVN_NO_ERROR;
+}
+
+
+struct node_created_path_args {
+ const char **created_path;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_node_created_path(void *baton, trail_t *trail)
+{
+ struct node_created_path_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ *args->created_path = svn_fs_base__dag_get_created_path(node);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_node_created_path(const char **created_path,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct node_created_path_args args;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ args.created_path = created_path;
+ args.root = root;
+ args.path = path;
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_created_path, &args,
+ FALSE, scratch_pool));
+ if (*created_path)
+ *created_path = apr_pstrdup(pool, *created_path);
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+struct node_kind_args {
+ const svn_fs_id_t *id;
+ svn_node_kind_t kind; /* OUT parameter */
+};
+
+
+static svn_error_t *
+txn_body_node_kind(void *baton, trail_t *trail)
+{
+ struct node_kind_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(svn_fs_base__dag_get_node(&node, trail->fs, args->id,
+ trail, trail->pool));
+ args->kind = svn_fs_base__dag_node_kind(node);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+node_kind(svn_node_kind_t *kind_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct node_kind_args args;
+ const svn_fs_id_t *node_id;
+
+ /* Get the node id. */
+ SVN_ERR(base_node_id(&node_id, root, path, pool));
+
+ /* Use the node id to get the real kind. */
+ args.id = node_id;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_kind, &args,
+ TRUE, pool));
+
+ *kind_p = args.kind;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_check_path(svn_node_kind_t *kind_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_error_t *err = node_kind(kind_p, root, path, pool);
+ if (err &&
+ ((err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY)))
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ *kind_p = svn_node_none;
+ }
+
+ return svn_error_trace(err);
+}
+
+
+struct node_prop_args
+{
+ svn_string_t **value_p;
+ svn_fs_root_t *root;
+ const char *path;
+ const char *propname;
+};
+
+
+static svn_error_t *
+txn_body_node_prop(void *baton,
+ trail_t *trail)
+{
+ struct node_prop_args *args = baton;
+ dag_node_t *node;
+ apr_hash_t *proplist;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, node,
+ trail, trail->pool));
+ *(args->value_p) = NULL;
+ if (proplist)
+ *(args->value_p) = svn_hash_gets(proplist, args->propname);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_node_prop(svn_string_t **value_p,
+ svn_fs_root_t *root,
+ const char *path,
+ const char *propname,
+ apr_pool_t *pool)
+{
+ struct node_prop_args args;
+ svn_string_t *value;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ args.value_p = &value;
+ args.root = root;
+ args.path = path;
+ args.propname = propname;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_prop, &args,
+ FALSE, scratch_pool));
+ *value_p = value ? svn_string_dup(value, pool) : NULL;
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+struct node_proplist_args {
+ apr_hash_t **table_p;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_node_proplist(void *baton, trail_t *trail)
+{
+ struct node_proplist_args *args = baton;
+ dag_node_t *node;
+ apr_hash_t *proplist;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, node,
+ trail, trail->pool));
+ *args->table_p = proplist ? proplist : apr_hash_make(trail->pool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_node_proplist(apr_hash_t **table_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ apr_hash_t *table;
+ struct node_proplist_args args;
+
+ args.table_p = &table;
+ args.root = root;
+ args.path = path;
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_node_proplist, &args,
+ FALSE, pool));
+
+ *table_p = table;
+ return SVN_NO_ERROR;
+}
+
+
+struct change_node_prop_args {
+ svn_fs_root_t *root;
+ const char *path;
+ const char *name;
+ const svn_string_t *value;
+};
+
+
+static svn_error_t *
+txn_body_change_node_prop(void *baton,
+ trail_t *trail)
+{
+ struct change_node_prop_args *args = baton;
+ parent_path_t *parent_path;
+ apr_hash_t *proplist;
+ const char *txn_id = args->root->txn;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ SVN_ERR(open_path(&parent_path, args->root, args->path, 0, txn_id,
+ trail, trail->pool));
+
+ /* Check to see if path is locked; if so, check that we can use it.
+ Notice that we're doing this non-recursively, regardless of node kind. */
+ if (args->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_base__allow_locked_operation
+ (args->path, FALSE, trail, trail->pool));
+
+ SVN_ERR(make_path_mutable(args->root, parent_path, args->path,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, parent_path->node,
+ trail, trail->pool));
+
+ /* If there's no proplist, but we're just deleting a property, exit now. */
+ if ((! proplist) && (! args->value))
+ return SVN_NO_ERROR;
+
+ /* Now, if there's no proplist, we know we need to make one. */
+ if (! proplist)
+ proplist = apr_hash_make(trail->pool);
+
+ /* Set the property. */
+ svn_hash_sets(proplist, args->name, args->value);
+
+ /* Overwrite the node's proplist. */
+ SVN_ERR(svn_fs_base__dag_set_proplist(parent_path->node, proplist,
+ txn_id, trail, trail->pool));
+
+ /* If this was a change to the mergeinfo property, and our version
+ of the filesystem cares, we have some extra recording to do.
+
+ ### If the format *doesn't* support mergeinfo recording, should
+ ### we fuss about attempts to change the svn:mergeinfo property
+ ### in any way save to delete it? */
+ if ((bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT)
+ && (strcmp(args->name, SVN_PROP_MERGEINFO) == 0))
+ {
+ svn_boolean_t had_mergeinfo, has_mergeinfo = args->value != NULL;
+
+ /* First, note on our node that it has mergeinfo. */
+ SVN_ERR(svn_fs_base__dag_set_has_mergeinfo(parent_path->node,
+ has_mergeinfo,
+ &had_mergeinfo, txn_id,
+ trail, trail->pool));
+
+ /* If this is a change from the old state, we need to update our
+ node's parents' mergeinfo counts by a factor of 1. */
+ if (parent_path->parent && ((! had_mergeinfo) != (! has_mergeinfo)))
+ SVN_ERR(adjust_parent_mergeinfo_counts(parent_path->parent,
+ has_mergeinfo ? 1 : -1,
+ txn_id, trail, trail->pool));
+ }
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(args->root->fs, txn_id,
+ args->path, svn_fs_base__dag_get_id(parent_path->node),
+ svn_fs_path_change_modify, FALSE, TRUE, trail,
+ trail->pool);
+}
+
+
+static svn_error_t *
+base_change_node_prop(svn_fs_root_t *root,
+ const char *path,
+ const char *name,
+ const svn_string_t *value,
+ apr_pool_t *pool)
+{
+ struct change_node_prop_args args;
+
+ if (! root->is_txn_root)
+ return SVN_FS__NOT_TXN(root);
+
+ args.root = root;
+ args.path = path;
+ args.name = name;
+ args.value = value;
+ return svn_fs_base__retry_txn(root->fs, txn_body_change_node_prop, &args,
+ TRUE, pool);
+}
+
+
+struct things_changed_args
+{
+ svn_boolean_t *changed_p;
+ svn_fs_root_t *root1;
+ svn_fs_root_t *root2;
+ const char *path1;
+ const char *path2;
+ apr_pool_t *pool;
+};
+
+
+static svn_error_t *
+txn_body_props_changed(void *baton, trail_t *trail)
+{
+ struct things_changed_args *args = baton;
+ dag_node_t *node1, *node2;
+
+ SVN_ERR(get_dag(&node1, args->root1, args->path1, trail, trail->pool));
+ SVN_ERR(get_dag(&node2, args->root2, args->path2, trail, trail->pool));
+ return svn_fs_base__things_different(args->changed_p, NULL,
+ node1, node2, trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_props_changed(svn_boolean_t *changed_p,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ apr_pool_t *pool)
+{
+ struct things_changed_args args;
+
+ /* Check that roots are in the same fs. */
+ if (root1->fs != root2->fs)
+ return svn_error_create
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Cannot compare property value between two different filesystems"));
+
+ args.root1 = root1;
+ args.root2 = root2;
+ args.path1 = path1;
+ args.path2 = path2;
+ args.changed_p = changed_p;
+ args.pool = pool;
+
+ return svn_fs_base__retry_txn(root1->fs, txn_body_props_changed, &args,
+ TRUE, pool);
+}
+
+
+
+/* Miscellaneous table handling */
+
+struct miscellaneous_set_args
+{
+ const char *key;
+ const char *val;
+};
+
+static svn_error_t *
+txn_body_miscellaneous_set(void *baton, trail_t *trail)
+{
+ struct miscellaneous_set_args *msa = baton;
+
+ return svn_fs_bdb__miscellaneous_set(trail->fs, msa->key, msa->val, trail,
+ trail->pool);
+}
+
+svn_error_t *
+svn_fs_base__miscellaneous_set(svn_fs_t *fs,
+ const char *key,
+ const char *val,
+ apr_pool_t *pool)
+{
+ struct miscellaneous_set_args msa;
+ msa.key = key;
+ msa.val = val;
+
+ return svn_fs_base__retry_txn(fs, txn_body_miscellaneous_set, &msa,
+ TRUE, pool);
+}
+
+struct miscellaneous_get_args
+{
+ const char *key;
+ const char **val;
+};
+
+static svn_error_t *
+txn_body_miscellaneous_get(void *baton, trail_t *trail)
+{
+ struct miscellaneous_get_args *mga = baton;
+ return svn_fs_bdb__miscellaneous_get(mga->val, trail->fs, mga->key, trail,
+ trail->pool);
+}
+
+svn_error_t *
+svn_fs_base__miscellaneous_get(const char **val,
+ svn_fs_t *fs,
+ const char *key,
+ apr_pool_t *pool)
+{
+ struct miscellaneous_get_args mga;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ mga.key = key;
+ mga.val = val;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_miscellaneous_get, &mga,
+ FALSE, scratch_pool));
+ if (*val)
+ *val = apr_pstrdup(pool, *val);
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Getting a directory's entries */
+
+
+struct dir_entries_args
+{
+ apr_hash_t **table_p;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+/* *(BATON->table_p) will never be NULL on successful return */
+static svn_error_t *
+txn_body_dir_entries(void *baton,
+ trail_t *trail)
+{
+ struct dir_entries_args *args = baton;
+ dag_node_t *node;
+ apr_hash_t *entries;
+
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+
+ /* Get the entries for PARENT_PATH. */
+ SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, trail->pool));
+
+ /* Potentially initialize the return value to an empty hash. */
+ *args->table_p = entries ? entries : apr_hash_make(trail->pool);
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_dir_entries(apr_hash_t **table_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct dir_entries_args args;
+ apr_pool_t *iterpool;
+ apr_hash_t *table;
+ svn_fs_t *fs = root->fs;
+ apr_hash_index_t *hi;
+
+ args.table_p = &table;
+ args.root = root;
+ args.path = path;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_dir_entries, &args,
+ FALSE, pool));
+
+ iterpool = svn_pool_create(pool);
+
+ /* Add in the kind data. */
+ for (hi = apr_hash_first(pool, table); hi; hi = apr_hash_next(hi))
+ {
+ svn_fs_dirent_t *entry;
+ struct node_kind_args nk_args;
+ void *val;
+
+ svn_pool_clear(iterpool);
+
+ /* KEY will be the entry name in ancestor (about which we
+ simply don't care), VAL the dirent. */
+ apr_hash_this(hi, NULL, NULL, &val);
+ entry = val;
+ nk_args.id = entry->id;
+
+ /* We don't need to have the retry function destroy the trail
+ pool because we're already doing that via the use of an
+ iteration pool. */
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_node_kind, &nk_args,
+ FALSE, iterpool));
+ entry->kind = nk_args.kind;
+ }
+
+ svn_pool_destroy(iterpool);
+
+ *table_p = table;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Merges and commits. */
+
+
+struct deltify_committed_args
+{
+ svn_fs_t *fs; /* the filesystem */
+ svn_revnum_t rev; /* revision just committed */
+ const char *txn_id; /* transaction just committed */
+};
+
+
+struct txn_deltify_args
+{
+ /* The transaction ID whose nodes are being deltified. */
+ const char *txn_id;
+
+ /* The target is what we're deltifying. */
+ const svn_fs_id_t *tgt_id;
+
+ /* The base is what we're deltifying against. It's not necessarily
+ the "next" revision of the node; skip deltas mean we sometimes
+ deltify against a successor many generations away. This may be
+ NULL, in which case we'll avoid deltification and simply index
+ TGT_ID's data checksum. */
+ const svn_fs_id_t *base_id;
+
+ /* We only deltify props for directories.
+ ### Didn't we try removing this horrid little optimization once?
+ ### What was the result? I would have thought that skip deltas
+ ### mean directory undeltification is cheap enough now. */
+ svn_boolean_t is_dir;
+};
+
+
+static svn_error_t *
+txn_body_txn_deltify(void *baton, trail_t *trail)
+{
+ struct txn_deltify_args *args = baton;
+ dag_node_t *tgt_node, *base_node;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ SVN_ERR(svn_fs_base__dag_get_node(&tgt_node, trail->fs, args->tgt_id,
+ trail, trail->pool));
+ /* If we have something to deltify against, do so. */
+ if (args->base_id)
+ {
+ SVN_ERR(svn_fs_base__dag_get_node(&base_node, trail->fs, args->base_id,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_deltify(tgt_node, base_node, args->is_dir,
+ args->txn_id, trail, trail->pool));
+ }
+
+ /* If we support rep sharing, and this isn't a directory, record a
+ mapping of TGT_NODE's data checksum to its representation key. */
+ if (bfd->format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT)
+ SVN_ERR(svn_fs_base__dag_index_checksums(tgt_node, trail, trail->pool));
+
+ return SVN_NO_ERROR;
+}
+
+
+struct txn_pred_count_args
+{
+ const svn_fs_id_t *id;
+ int pred_count;
+};
+
+
+static svn_error_t *
+txn_body_pred_count(void *baton, trail_t *trail)
+{
+ node_revision_t *noderev;
+ struct txn_pred_count_args *args = baton;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, trail->fs,
+ args->id, trail, trail->pool));
+ args->pred_count = noderev->predecessor_count;
+ return SVN_NO_ERROR;
+}
+
+
+struct txn_pred_id_args
+{
+ const svn_fs_id_t *id; /* The node id whose predecessor we want. */
+ const svn_fs_id_t *pred_id; /* The returned predecessor id. */
+ apr_pool_t *pool; /* The pool in which to allocate pred_id. */
+};
+
+
+static svn_error_t *
+txn_body_pred_id(void *baton, trail_t *trail)
+{
+ node_revision_t *nr;
+ struct txn_pred_id_args *args = baton;
+
+ SVN_ERR(svn_fs_bdb__get_node_revision(&nr, trail->fs, args->id,
+ trail, trail->pool));
+ if (nr->predecessor_id)
+ args->pred_id = svn_fs_base__id_copy(nr->predecessor_id, args->pool);
+ else
+ args->pred_id = NULL;
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Deltify PATH in ROOT's predecessor iff PATH is mutable under TXN_ID
+ in FS. If PATH is a mutable directory, recurse.
+
+ NODE_ID is the node revision ID for PATH in ROOT, or NULL if that
+ value isn't known. KIND is the node kind for PATH in ROOT, or
+ svn_node_unknown is the kind isn't known.
+
+ Use POOL for necessary allocations. */
+static svn_error_t *
+deltify_mutable(svn_fs_t *fs,
+ svn_fs_root_t *root,
+ const char *path,
+ const svn_fs_id_t *node_id,
+ svn_node_kind_t kind,
+ const char *txn_id,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *id = node_id;
+ apr_hash_t *entries = NULL;
+ struct txn_deltify_args td_args;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ /* Get the ID for PATH under ROOT if it wasn't provided. */
+ if (! node_id)
+ SVN_ERR(base_node_id(&id, root, path, pool));
+
+ /* Check for mutability. Not mutable? Go no further. This is safe
+ to do because for items in the tree to be mutable, their parent
+ dirs must also be mutable. Therefore, if a directory is not
+ mutable under TXN_ID, its children cannot be. */
+ if (strcmp(svn_fs_base__id_txn_id(id), txn_id))
+ return SVN_NO_ERROR;
+
+ /* Is this a directory? */
+ if (kind == svn_node_unknown)
+ SVN_ERR(base_check_path(&kind, root, path, pool));
+
+ /* If this is a directory, read its entries. */
+ if (kind == svn_node_dir)
+ SVN_ERR(base_dir_entries(&entries, root, path, pool));
+
+ /* If there are entries, recurse on 'em. */
+ if (entries)
+ {
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_index_t *hi;
+
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ /* KEY will be the entry name, VAL the dirent */
+ const void *key;
+ void *val;
+ svn_fs_dirent_t *entry;
+ svn_pool_clear(subpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ entry = val;
+ SVN_ERR(deltify_mutable(fs, root,
+ svn_fspath__join(path, key, subpool),
+ entry->id, entry->kind, txn_id, subpool));
+ }
+
+ svn_pool_destroy(subpool);
+ }
+
+ /* Index ID's data checksum. */
+ td_args.txn_id = txn_id;
+ td_args.tgt_id = id;
+ td_args.base_id = NULL;
+ td_args.is_dir = (kind == svn_node_dir);
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_deltify, &td_args,
+ TRUE, pool));
+
+ /* Finally, deltify old data against this node. */
+ {
+ /* Prior to 1.6, we use the following algorithm to deltify nodes:
+
+ Redeltify predecessor node-revisions of the one we added. The
+ idea is to require at most 2*lg(N) deltas to be applied to get
+ to any node-revision in a chain of N predecessors. We do this
+ using a technique derived from skip lists:
+
+ - Always redeltify the immediate parent
+
+ - If the number of predecessors is divisible by 2,
+ redeltify the revision two predecessors back
+
+ - If the number of predecessors is divisible by 4,
+ redeltify the revision four predecessors back
+
+ ... and so on.
+
+ That's the theory, anyway. Unfortunately, if we strictly
+ follow that theory we get a bunch of overhead up front and no
+ great benefit until the number of predecessors gets large. So,
+ stop at redeltifying the parent if the number of predecessors
+ is less than 32, and also skip the second level (redeltifying
+ two predecessors back), since that doesn't help much. Also,
+ don't redeltify the oldest node-revision; it's potentially
+ expensive and doesn't help retrieve any other revision.
+ (Retrieving the oldest node-revision will still be fast, just
+ not as blindingly so.)
+
+ For 1.6 and beyond, we just deltify the current node against its
+ predecessors, using skip deltas similar to the way FSFS does. */
+
+ int pred_count;
+ const svn_fs_id_t *pred_id;
+ struct txn_pred_count_args tpc_args;
+ apr_pool_t *subpools[2];
+ int active_subpool = 0;
+ svn_revnum_t forward_delta_rev = 0;
+
+ tpc_args.id = id;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_count, &tpc_args,
+ TRUE, pool));
+ pred_count = tpc_args.pred_count;
+
+ /* If nothing to deltify, then we're done. */
+ if (pred_count == 0)
+ return SVN_NO_ERROR;
+
+ subpools[0] = svn_pool_create(pool);
+ subpools[1] = svn_pool_create(pool);
+
+ /* If we support the 'miscellaneous' table, check it to see if
+ there is a point in time before which we don't want to do
+ deltification. */
+ /* ### FIXME: I think this is an unnecessary restriction. We
+ ### should be able to do something meaningful for most
+ ### deltification requests -- what that is depends on the
+ ### directory of the deltas for that revision, though. */
+ if (bfd->format >= SVN_FS_BASE__MIN_MISCELLANY_FORMAT)
+ {
+ const char *val;
+ SVN_ERR(svn_fs_base__miscellaneous_get
+ (&val, fs, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE, pool));
+ if (val)
+ SVN_ERR(svn_revnum_parse(&forward_delta_rev, val, NULL));
+ }
+
+ if (bfd->format >= SVN_FS_BASE__MIN_FORWARD_DELTAS_FORMAT
+ && forward_delta_rev <= root->rev)
+ {
+ /**** FORWARD DELTA STORAGE ****/
+
+ /* Decide which predecessor to deltify against. Flip the rightmost '1'
+ bit of the predecessor count to determine which file rev (counting
+ from 0) we want to use. (To see why count & (count - 1) unsets the
+ rightmost set bit, think about how you decrement a binary number. */
+ pred_count = pred_count & (pred_count - 1);
+
+ /* Walk back a number of predecessors equal to the difference between
+ pred_count and the original predecessor count. (For example, if
+ the node has ten predecessors and we want the eighth node, walk back
+ two predecessors. */
+ pred_id = id;
+
+ /* We need to use two alternating pools because the id used in the
+ call to txn_body_pred_id is allocated by the previous inner
+ loop iteration. If we would clear the pool each iteration we
+ would free the previous result. */
+ while ((pred_count++) < tpc_args.pred_count)
+ {
+ struct txn_pred_id_args tpi_args;
+
+ active_subpool = !active_subpool;
+ svn_pool_clear(subpools[active_subpool]);
+
+ tpi_args.id = pred_id;
+ tpi_args.pool = subpools[active_subpool];
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_id, &tpi_args,
+ FALSE, subpools[active_subpool]));
+ pred_id = tpi_args.pred_id;
+
+ if (pred_id == NULL)
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: faulty predecessor count"));
+
+ }
+
+ /* Finally, do the deltification. */
+ td_args.txn_id = txn_id;
+ td_args.tgt_id = id;
+ td_args.base_id = pred_id;
+ td_args.is_dir = (kind == svn_node_dir);
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_deltify, &td_args,
+ TRUE, subpools[active_subpool]));
+ }
+ else
+ {
+ int nlevels, lev, count;
+
+ /**** REVERSE DELTA STORAGE ****/
+
+ /* Decide how many predecessors to redeltify. To save overhead,
+ don't redeltify anything but the immediate predecessor if there
+ are less than 32 predecessors. */
+ nlevels = 1;
+ if (pred_count >= 32)
+ {
+ while (pred_count % 2 == 0)
+ {
+ pred_count /= 2;
+ nlevels++;
+ }
+
+ /* Don't redeltify the oldest revision. */
+ if (1 << (nlevels - 1) == pred_count)
+ nlevels--;
+ }
+
+ /* Redeltify the desired number of predecessors. */
+ count = 0;
+ pred_id = id;
+
+ /* We need to use two alternating pools because the id used in the
+ call to txn_body_pred_id is allocated by the previous inner
+ loop iteration. If we would clear the pool each iteration we
+ would free the previous result. */
+ for (lev = 0; lev < nlevels; lev++)
+ {
+ /* To save overhead, skip the second level (that is, never
+ redeltify the node-revision two predecessors back). */
+ if (lev == 1)
+ continue;
+
+ /* Note that COUNT is not reset between levels, and neither is
+ PREDNODE; we just keep counting from where we were up to
+ where we're supposed to get. */
+ while (count < (1 << lev))
+ {
+ struct txn_pred_id_args tpi_args;
+
+ active_subpool = !active_subpool;
+ svn_pool_clear(subpools[active_subpool]);
+
+ tpi_args.id = pred_id;
+ tpi_args.pool = subpools[active_subpool];
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_id,
+ &tpi_args, FALSE,
+ subpools[active_subpool]));
+ pred_id = tpi_args.pred_id;
+
+ if (pred_id == NULL)
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, 0,
+ _("Corrupt DB: faulty predecessor count"));
+
+ count++;
+ }
+
+ /* Finally, do the deltification. */
+ td_args.txn_id = NULL; /* Don't require mutable reps */
+ td_args.tgt_id = pred_id;
+ td_args.base_id = id;
+ td_args.is_dir = (kind == svn_node_dir);
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_txn_deltify, &td_args,
+ TRUE, subpools[active_subpool]));
+
+ }
+ }
+
+ svn_pool_destroy(subpools[0]);
+ svn_pool_destroy(subpools[1]);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct get_root_args
+{
+ svn_fs_root_t *root;
+ dag_node_t *node;
+};
+
+
+/* Set ARGS->node to the root node of ARGS->root. */
+static svn_error_t *
+txn_body_get_root(void *baton, trail_t *trail)
+{
+ struct get_root_args *args = baton;
+ return get_dag(&(args->node), args->root, "", trail, trail->pool);
+}
+
+
+
+static svn_error_t *
+update_ancestry(svn_fs_t *fs,
+ const svn_fs_id_t *source_id,
+ const svn_fs_id_t *target_id,
+ const char *txn_id,
+ const char *target_path,
+ int source_pred_count,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+
+ /* Set target's predecessor-id to source_id. */
+ if (strcmp(svn_fs_base__id_txn_id(target_id), txn_id))
+ return svn_error_createf
+ (SVN_ERR_FS_NOT_MUTABLE, NULL,
+ _("Unexpected immutable node at '%s'"), target_path);
+ SVN_ERR(svn_fs_bdb__get_node_revision(&noderev, fs, target_id,
+ trail, pool));
+ noderev->predecessor_id = source_id;
+ noderev->predecessor_count = source_pred_count;
+ if (noderev->predecessor_count != -1)
+ noderev->predecessor_count++;
+ return svn_fs_bdb__put_node_revision(fs, target_id, noderev, trail, pool);
+}
+
+
+/* Set the contents of CONFLICT_PATH to PATH, and return an
+ SVN_ERR_FS_CONFLICT error that indicates that there was a conflict
+ at PATH. Perform all allocations in POOL (except the allocation of
+ CONFLICT_PATH, which should be handled outside this function). */
+static svn_error_t *
+conflict_err(svn_stringbuf_t *conflict_path,
+ const char *path)
+{
+ svn_stringbuf_set(conflict_path, path);
+ return svn_error_createf(SVN_ERR_FS_CONFLICT, NULL,
+ _("Conflict at '%s'"), path);
+}
+
+
+/* Merge changes between ANCESTOR and SOURCE into TARGET as part of
+ * TRAIL. ANCESTOR and TARGET must be distinct node revisions.
+ * TARGET_PATH should correspond to TARGET's full path in its
+ * filesystem, and is used for reporting conflict location.
+ *
+ * SOURCE, TARGET, and ANCESTOR are generally directories; this
+ * function recursively merges the directories' contents. If any are
+ * files, this function simply returns an error whenever SOURCE,
+ * TARGET, and ANCESTOR are all distinct node revisions.
+ *
+ * If there are differences between ANCESTOR and SOURCE that conflict
+ * with changes between ANCESTOR and TARGET, this function returns an
+ * SVN_ERR_FS_CONFLICT error, and updates CONFLICT_P to the name of the
+ * conflicting node in TARGET, with TARGET_PATH prepended as a path.
+ *
+ * If there are no conflicting differences, CONFLICT_P is updated to
+ * the empty string.
+ *
+ * CONFLICT_P must point to a valid svn_stringbuf_t.
+ *
+ * Do any necessary temporary allocation in POOL.
+ */
+static svn_error_t *
+merge(svn_stringbuf_t *conflict_p,
+ const char *target_path,
+ dag_node_t *target,
+ dag_node_t *source,
+ dag_node_t *ancestor,
+ const char *txn_id,
+ apr_int64_t *mergeinfo_increment_out,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ const svn_fs_id_t *source_id, *target_id, *ancestor_id;
+ apr_hash_t *s_entries, *t_entries, *a_entries;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+ svn_fs_t *fs;
+ int pred_count;
+ apr_int64_t mergeinfo_increment = 0;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ /* Make sure everyone comes from the same filesystem. */
+ fs = svn_fs_base__dag_get_fs(ancestor);
+ if ((fs != svn_fs_base__dag_get_fs(source))
+ || (fs != svn_fs_base__dag_get_fs(target)))
+ {
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Bad merge; ancestor, source, and target not all in same fs"));
+ }
+
+ /* We have the same fs, now check it. */
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ source_id = svn_fs_base__dag_get_id(source);
+ target_id = svn_fs_base__dag_get_id(target);
+ ancestor_id = svn_fs_base__dag_get_id(ancestor);
+
+ /* It's improper to call this function with ancestor == target. */
+ if (svn_fs_base__id_eq(ancestor_id, target_id))
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(target_id, pool);
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Bad merge; target '%s' has id '%s', same as ancestor"),
+ target_path, id_str->data);
+ }
+
+ svn_stringbuf_setempty(conflict_p);
+
+ /* Base cases:
+ * Either no change made in source, or same change as made in target.
+ * Both mean nothing to merge here.
+ */
+ if (svn_fs_base__id_eq(ancestor_id, source_id)
+ || (svn_fs_base__id_eq(source_id, target_id)))
+ return SVN_NO_ERROR;
+
+ /* Else proceed, knowing all three are distinct node revisions.
+ *
+ * How to merge from this point:
+ *
+ * if (not all 3 are directories)
+ * {
+ * early exit with conflict;
+ * }
+ *
+ * // Property changes may only be made to up-to-date
+ * // directories, because once the client commits the prop
+ * // change, it bumps the directory's revision, and therefore
+ * // must be able to depend on there being no other changes to
+ * // that directory in the repository.
+ * if (target's property list differs from ancestor's)
+ * conflict;
+ *
+ * For each entry NAME in the directory ANCESTOR:
+ *
+ * Let ANCESTOR-ENTRY, SOURCE-ENTRY, and TARGET-ENTRY be the IDs of
+ * the name within ANCESTOR, SOURCE, and TARGET respectively.
+ * (Possibly null if NAME does not exist in SOURCE or TARGET.)
+ *
+ * If ANCESTOR-ENTRY == SOURCE-ENTRY, then:
+ * No changes were made to this entry while the transaction was in
+ * progress, so do nothing to the target.
+ *
+ * Else if ANCESTOR-ENTRY == TARGET-ENTRY, then:
+ * A change was made to this entry while the transaction was in
+ * process, but the transaction did not touch this entry. Replace
+ * TARGET-ENTRY with SOURCE-ENTRY.
+ *
+ * Else:
+ * Changes were made to this entry both within the transaction and
+ * to the repository while the transaction was in progress. They
+ * must be merged or declared to be in conflict.
+ *
+ * If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
+ * double delete; flag a conflict.
+ *
+ * If any of the three entries is of type file, declare a conflict.
+ *
+ * If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
+ * modification of ANCESTOR-ENTRY (determine by comparing the
+ * node-id fields), declare a conflict. A replacement is
+ * incompatible with a modification or other replacement--even
+ * an identical replacement.
+ *
+ * Direct modifications were made to the directory ANCESTOR-ENTRY
+ * in both SOURCE and TARGET. Recursively merge these
+ * modifications.
+ *
+ * For each leftover entry NAME in the directory SOURCE:
+ *
+ * If NAME exists in TARGET, declare a conflict. Even if SOURCE and
+ * TARGET are adding exactly the same thing, two additions are not
+ * auto-mergeable with each other.
+ *
+ * Add NAME to TARGET with the entry from SOURCE.
+ *
+ * Now that we are done merging the changes from SOURCE into the
+ * directory TARGET, update TARGET's predecessor to be SOURCE.
+ */
+
+ if ((svn_fs_base__dag_node_kind(source) != svn_node_dir)
+ || (svn_fs_base__dag_node_kind(target) != svn_node_dir)
+ || (svn_fs_base__dag_node_kind(ancestor) != svn_node_dir))
+ {
+ return conflict_err(conflict_p, target_path);
+ }
+
+
+ /* Possible early merge failure: if target and ancestor have
+ different property lists, then the merge should fail.
+ Propchanges can *only* be committed on an up-to-date directory.
+ ### TODO: see issue #418 about the inelegance of this.
+
+ Another possible, similar, early merge failure: if source and
+ ancestor have different property lists (meaning someone else
+ changed directory properties while our commit transaction was
+ happening), the merge should fail. See issue #2751.
+ */
+ {
+ node_revision_t *tgt_nr, *anc_nr, *src_nr;
+
+ /* Get node revisions for our id's. */
+ SVN_ERR(svn_fs_bdb__get_node_revision(&tgt_nr, fs, target_id,
+ trail, pool));
+ SVN_ERR(svn_fs_bdb__get_node_revision(&anc_nr, fs, ancestor_id,
+ trail, pool));
+ SVN_ERR(svn_fs_bdb__get_node_revision(&src_nr, fs, source_id,
+ trail, pool));
+
+ /* Now compare the prop-keys of the skels. Note that just because
+ the keys are different -doesn't- mean the proplists have
+ different contents. But merge() isn't concerned with contents;
+ it doesn't do a brute-force comparison on textual contents, so
+ it won't do that here either. Checking to see if the propkey
+ atoms are `equal' is enough. */
+ if (! svn_fs_base__same_keys(tgt_nr->prop_key, anc_nr->prop_key))
+ return conflict_err(conflict_p, target_path);
+ if (! svn_fs_base__same_keys(src_nr->prop_key, anc_nr->prop_key))
+ return conflict_err(conflict_p, target_path);
+ }
+
+ /* ### todo: it would be more efficient to simply check for a NULL
+ entries hash where necessary below than to allocate an empty hash
+ here, but another day, another day... */
+ SVN_ERR(svn_fs_base__dag_dir_entries(&s_entries, source, trail, pool));
+ if (! s_entries)
+ s_entries = apr_hash_make(pool);
+ SVN_ERR(svn_fs_base__dag_dir_entries(&t_entries, target, trail, pool));
+ if (! t_entries)
+ t_entries = apr_hash_make(pool);
+ SVN_ERR(svn_fs_base__dag_dir_entries(&a_entries, ancestor, trail, pool));
+ if (! a_entries)
+ a_entries = apr_hash_make(pool);
+
+ /* for each entry E in a_entries... */
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, a_entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_fs_dirent_t *s_entry, *t_entry, *a_entry;
+
+ const void *key;
+ void *val;
+ apr_ssize_t klen;
+
+ svn_pool_clear(iterpool);
+
+ /* KEY will be the entry name in ancestor, VAL the dirent */
+ apr_hash_this(hi, &key, &klen, &val);
+ a_entry = val;
+
+ s_entry = apr_hash_get(s_entries, key, klen);
+ t_entry = apr_hash_get(t_entries, key, klen);
+
+ /* No changes were made to this entry while the transaction was
+ in progress, so do nothing to the target. */
+ if (s_entry && svn_fs_base__id_eq(a_entry->id, s_entry->id))
+ goto end;
+
+ /* A change was made to this entry while the transaction was in
+ process, but the transaction did not touch this entry. */
+ else if (t_entry && svn_fs_base__id_eq(a_entry->id, t_entry->id))
+ {
+ dag_node_t *t_ent_node;
+ apr_int64_t mergeinfo_start;
+ SVN_ERR(svn_fs_base__dag_get_node(&t_ent_node, fs,
+ t_entry->id, trail, iterpool));
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, &mergeinfo_start,
+ t_ent_node, trail,
+ iterpool));
+ mergeinfo_increment -= mergeinfo_start;
+
+ if (s_entry)
+ {
+ dag_node_t *s_ent_node;
+ apr_int64_t mergeinfo_end;
+ SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node, fs,
+ s_entry->id, trail,
+ iterpool));
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL,
+ &mergeinfo_end,
+ s_ent_node, trail,
+ iterpool));
+ mergeinfo_increment += mergeinfo_end;
+ SVN_ERR(svn_fs_base__dag_set_entry(target, key, s_entry->id,
+ txn_id, trail, iterpool));
+ }
+ else
+ {
+ SVN_ERR(svn_fs_base__dag_delete(target, key, txn_id,
+ trail, iterpool));
+ }
+ }
+
+ /* Changes were made to this entry both within the transaction
+ and to the repository while the transaction was in progress.
+ They must be merged or declared to be in conflict. */
+ else
+ {
+ dag_node_t *s_ent_node, *t_ent_node, *a_ent_node;
+ const char *new_tpath;
+ apr_int64_t sub_mergeinfo_increment;
+
+ /* If SOURCE-ENTRY and TARGET-ENTRY are both null, that's a
+ double delete; if one of them is null, that's a delete versus
+ a modification. In any of these cases, flag a conflict. */
+ if (s_entry == NULL || t_entry == NULL)
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ a_entry->name,
+ iterpool));
+
+ /* If either SOURCE-ENTRY or TARGET-ENTRY is not a direct
+ modification of ANCESTOR-ENTRY, declare a conflict. */
+ if (strcmp(svn_fs_base__id_node_id(s_entry->id),
+ svn_fs_base__id_node_id(a_entry->id)) != 0
+ || strcmp(svn_fs_base__id_copy_id(s_entry->id),
+ svn_fs_base__id_copy_id(a_entry->id)) != 0
+ || strcmp(svn_fs_base__id_node_id(t_entry->id),
+ svn_fs_base__id_node_id(a_entry->id)) != 0
+ || strcmp(svn_fs_base__id_copy_id(t_entry->id),
+ svn_fs_base__id_copy_id(a_entry->id)) != 0)
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ a_entry->name,
+ iterpool));
+
+ /* Fetch the nodes for our entries. */
+ SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node, fs,
+ s_entry->id, trail, iterpool));
+ SVN_ERR(svn_fs_base__dag_get_node(&t_ent_node, fs,
+ t_entry->id, trail, iterpool));
+ SVN_ERR(svn_fs_base__dag_get_node(&a_ent_node, fs,
+ a_entry->id, trail, iterpool));
+
+ /* If any of the three entries is of type file, flag a conflict. */
+ if ((svn_fs_base__dag_node_kind(s_ent_node) == svn_node_file)
+ || (svn_fs_base__dag_node_kind(t_ent_node) == svn_node_file)
+ || (svn_fs_base__dag_node_kind(a_ent_node) == svn_node_file))
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ a_entry->name,
+ iterpool));
+
+ /* Direct modifications were made to the directory
+ ANCESTOR-ENTRY in both SOURCE and TARGET. Recursively
+ merge these modifications. */
+ new_tpath = svn_fspath__join(target_path, t_entry->name, iterpool);
+ SVN_ERR(merge(conflict_p, new_tpath,
+ t_ent_node, s_ent_node, a_ent_node,
+ txn_id, &sub_mergeinfo_increment, trail, iterpool));
+ mergeinfo_increment += sub_mergeinfo_increment;
+ }
+
+ /* We've taken care of any possible implications E could have.
+ Remove it from source_entries, so it's easy later to loop
+ over all the source entries that didn't exist in
+ ancestor_entries. */
+ end:
+ apr_hash_set(s_entries, key, klen, NULL);
+ }
+
+ /* For each entry E in source but not in ancestor */
+ for (hi = apr_hash_first(pool, s_entries);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ svn_fs_dirent_t *s_entry, *t_entry;
+ const void *key;
+ void *val;
+ apr_ssize_t klen;
+ dag_node_t *s_ent_node;
+ apr_int64_t mergeinfo_s;
+
+ svn_pool_clear(iterpool);
+
+ apr_hash_this(hi, &key, &klen, &val);
+ s_entry = val;
+ t_entry = apr_hash_get(t_entries, key, klen);
+
+ /* If NAME exists in TARGET, declare a conflict. */
+ if (t_entry)
+ return conflict_err(conflict_p,
+ svn_fspath__join(target_path,
+ t_entry->name,
+ iterpool));
+
+ SVN_ERR(svn_fs_base__dag_get_node(&s_ent_node, fs,
+ s_entry->id, trail, iterpool));
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, &mergeinfo_s,
+ s_ent_node, trail,
+ iterpool));
+ mergeinfo_increment += mergeinfo_s;
+ SVN_ERR(svn_fs_base__dag_set_entry
+ (target, s_entry->name, s_entry->id, txn_id, trail, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+
+ /* Now that TARGET has absorbed all of the history between ANCESTOR
+ and SOURCE, we can update its predecessor to point to SOURCE. */
+ SVN_ERR(svn_fs_base__dag_get_predecessor_count(&pred_count, source,
+ trail, pool));
+ SVN_ERR(update_ancestry(fs, source_id, target_id, txn_id, target_path,
+ pred_count, trail, pool));
+
+ /* Tweak mergeinfo data if our format supports it. */
+ if (bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT)
+ {
+ SVN_ERR(svn_fs_base__dag_adjust_mergeinfo_count(target,
+ mergeinfo_increment,
+ txn_id, trail, pool));
+ }
+
+ if (mergeinfo_increment_out)
+ *mergeinfo_increment_out = mergeinfo_increment;
+
+ return SVN_NO_ERROR;
+}
+
+
+struct merge_args
+{
+ /* The ancestor for the merge. If this is null, then TXN's base is
+ used as the ancestor for the merge. */
+ dag_node_t *ancestor_node;
+
+ /* This is the SOURCE node for the merge. It may not be null. */
+ dag_node_t *source_node;
+
+ /* This is the TARGET of the merge. It may not be null. If
+ ancestor_node above is null, then this txn's base is used as the
+ ancestor for the merge. */
+ svn_fs_txn_t *txn;
+
+ /* If a conflict results, this is updated to the path in the txn that
+ conflicted. It must point to a valid svn_stringbuf_t before calling
+ svn_fs_base__retry_txn, as this determines the pool used to allocate any
+ required memory. */
+ svn_stringbuf_t *conflict;
+};
+
+
+/* Merge changes between an ancestor and BATON->source_node into
+ BATON->txn. The ancestor is either BATON->ancestor_node, or if
+ that is null, BATON->txn's base node.
+
+ If the merge is successful, BATON->txn's base will become
+ BATON->source_node, and its root node will have a new ID, a
+ successor of BATON->source_node. */
+static svn_error_t *
+txn_body_merge(void *baton, trail_t *trail)
+{
+ struct merge_args *args = baton;
+ dag_node_t *source_node, *txn_root_node, *ancestor_node;
+ const svn_fs_id_t *source_id;
+ svn_fs_t *fs = args->txn->fs;
+ const char *txn_id = args->txn->id;
+
+ source_node = args->source_node;
+ ancestor_node = args->ancestor_node;
+ source_id = svn_fs_base__dag_get_id(source_node);
+
+ SVN_ERR(svn_fs_base__dag_txn_root(&txn_root_node, fs, txn_id,
+ trail, trail->pool));
+
+ if (ancestor_node == NULL)
+ {
+ SVN_ERR(svn_fs_base__dag_txn_base_root(&ancestor_node, fs,
+ txn_id, trail, trail->pool));
+ }
+
+ if (svn_fs_base__id_eq(svn_fs_base__dag_get_id(ancestor_node),
+ svn_fs_base__dag_get_id(txn_root_node)))
+ {
+ /* If no changes have been made in TXN since its current base,
+ then it can't conflict with any changes since that base. So
+ we just set *both* its base and root to source, making TXN
+ in effect a repeat of source. */
+
+ /* ### kff todo: this would, of course, be a mighty silly thing
+ for the caller to do, and we might want to consider whether
+ this response is really appropriate. */
+
+ SVN_ERR(svn_fs_base__set_txn_base(fs, txn_id, source_id,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__set_txn_root(fs, txn_id, source_id,
+ trail, trail->pool));
+ }
+ else
+ {
+ int pred_count;
+
+ SVN_ERR(merge(args->conflict, "/", txn_root_node, source_node,
+ ancestor_node, txn_id, NULL, trail, trail->pool));
+
+ SVN_ERR(svn_fs_base__dag_get_predecessor_count(&pred_count,
+ source_node, trail,
+ trail->pool));
+
+ /* After the merge, txn's new "ancestor" is now really the node
+ at source_id, so record that fact. Think of this as
+ ratcheting the txn forward in time, so it can't backslide and
+ forget the merging work that's already been done. */
+ SVN_ERR(update_ancestry(fs, source_id,
+ svn_fs_base__dag_get_id(txn_root_node),
+ txn_id, "/", pred_count, trail, trail->pool));
+ SVN_ERR(svn_fs_base__set_txn_base(fs, txn_id, source_id,
+ trail, trail->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Verify that there are registered with TRAIL->fs all the locks
+ necessary to permit all the changes associated with TXN_NAME. */
+static svn_error_t *
+verify_locks(const char *txn_name,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_hash_t *changes;
+ apr_hash_index_t *hi;
+ apr_array_header_t *changed_paths;
+ svn_stringbuf_t *last_recursed = NULL;
+ int i;
+
+ /* Fetch the changes for this transaction. */
+ SVN_ERR(svn_fs_bdb__changes_fetch(&changes, trail->fs, txn_name,
+ trail, pool));
+
+ /* Make an array of the changed paths, and sort them depth-first-ily. */
+ changed_paths = apr_array_make(pool, apr_hash_count(changes) + 1,
+ sizeof(const char *));
+ for (hi = apr_hash_first(pool, changes); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ apr_hash_this(hi, &key, NULL, NULL);
+ APR_ARRAY_PUSH(changed_paths, const char *) = key;
+ }
+ qsort(changed_paths->elts, changed_paths->nelts,
+ changed_paths->elt_size, svn_sort_compare_paths);
+
+ /* Now, traverse the array of changed paths, verify locks. Note
+ that if we need to do a recursive verification a path, we'll skip
+ over children of that path when we get to them. */
+ for (i = 0; i < changed_paths->nelts; i++)
+ {
+ const char *path;
+ svn_fs_path_change2_t *change;
+ svn_boolean_t recurse = TRUE;
+
+ svn_pool_clear(subpool);
+ path = APR_ARRAY_IDX(changed_paths, i, const char *);
+
+ /* If this path has already been verified as part of a recursive
+ check of one of its parents, no need to do it again. */
+ if (last_recursed
+ && svn_fspath__skip_ancestor(last_recursed->data, path))
+ continue;
+
+ /* Fetch the change associated with our path. */
+ change = svn_hash_gets(changes, path);
+
+ /* What does it mean to succeed at lock verification for a given
+ path? For an existing file or directory getting modified
+ (text, props), it means we hold the lock on the file or
+ directory. For paths being added or removed, we need to hold
+ the locks for that path and any children of that path.
+
+ WHEW! We have no reliable way to determine the node kind of
+ deleted items, but fortunately we are going to do a recursive
+ check on deleted paths regardless of their kind. */
+ if (change->change_kind == svn_fs_path_change_modify)
+ recurse = FALSE;
+ SVN_ERR(svn_fs_base__allow_locked_operation(path, recurse,
+ trail, subpool));
+
+ /* If we just did a recursive check, remember the path we
+ checked (so children can be skipped). */
+ if (recurse)
+ {
+ if (! last_recursed)
+ last_recursed = svn_stringbuf_create(path, pool);
+ else
+ svn_stringbuf_set(last_recursed, path);
+ }
+ }
+ svn_pool_destroy(subpool);
+ return SVN_NO_ERROR;
+}
+
+
+struct commit_args
+{
+ svn_fs_txn_t *txn;
+ svn_revnum_t new_rev;
+};
+
+
+/* Commit ARGS->txn, setting ARGS->new_rev to the resulting new
+ * revision, if ARGS->txn is up-to-date with respect to the repository.
+ *
+ * Up-to-date means that ARGS->txn's base root is the same as the root
+ * of the youngest revision. If ARGS->txn is not up-to-date, the
+ * error SVN_ERR_FS_TXN_OUT_OF_DATE is returned, and the commit fails: no
+ * new revision is created, and ARGS->new_rev is not touched.
+ *
+ * If the commit succeeds, ARGS->txn is destroyed.
+ */
+static svn_error_t *
+txn_body_commit(void *baton, trail_t *trail)
+{
+ struct commit_args *args = baton;
+
+ svn_fs_txn_t *txn = args->txn;
+ svn_fs_t *fs = txn->fs;
+ const char *txn_name = txn->id;
+
+ svn_revnum_t youngest_rev;
+ const svn_fs_id_t *y_rev_root_id;
+ dag_node_t *txn_base_root_node;
+
+ /* Getting the youngest revision locks the revisions table until
+ this trail is done. */
+ SVN_ERR(svn_fs_bdb__youngest_rev(&youngest_rev, fs, trail, trail->pool));
+
+ /* If the root of the youngest revision is the same as txn's base,
+ then no further merging is necessary and we can commit. */
+ SVN_ERR(svn_fs_base__rev_get_root(&y_rev_root_id, fs, youngest_rev,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_txn_base_root(&txn_base_root_node, fs, txn_name,
+ trail, trail->pool));
+ /* ### kff todo: it seems weird to grab the ID for one, and the node
+ for the other. We can certainly do the comparison we need, but
+ it would be nice to grab the same type of information from the
+ start, instead of having to transform one of them. */
+ if (! svn_fs_base__id_eq(y_rev_root_id,
+ svn_fs_base__dag_get_id(txn_base_root_node)))
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(y_rev_root_id,
+ trail->pool);
+ return svn_error_createf
+ (SVN_ERR_FS_TXN_OUT_OF_DATE, NULL,
+ _("Transaction '%s' out-of-date with respect to revision '%s'"),
+ txn_name, id_str->data);
+ }
+
+ /* Locks may have been added (or stolen) between the calling of
+ previous svn_fs.h functions and svn_fs_commit_txn(), so we need
+ to re-examine every changed-path in the txn and re-verify all
+ discovered locks. */
+ SVN_ERR(verify_locks(txn_name, trail, trail->pool));
+
+ /* Else, commit the txn. */
+ return svn_fs_base__dag_commit_txn(&(args->new_rev), txn, trail,
+ trail->pool);
+}
+
+
+/* Note: it is acceptable for this function to call back into
+ top-level FS interfaces because it does not itself use trails. */
+svn_error_t *
+svn_fs_base__commit_txn(const char **conflict_p,
+ svn_revnum_t *new_rev,
+ svn_fs_txn_t *txn,
+ apr_pool_t *pool)
+{
+ /* How do commits work in Subversion?
+ *
+ * When you're ready to commit, here's what you have:
+ *
+ * 1. A transaction, with a mutable tree hanging off it.
+ * 2. A base revision, against which TXN_TREE was made.
+ * 3. A latest revision, which may be newer than the base rev.
+ *
+ * The problem is that if latest != base, then one can't simply
+ * attach the txn root as the root of the new revision, because that
+ * would lose all the changes between base and latest. It is also
+ * not acceptable to insist that base == latest; in a busy
+ * repository, commits happen too fast to insist that everyone keep
+ * their entire tree up-to-date at all times. Non-overlapping
+ * changes should not interfere with each other.
+ *
+ * The solution is to merge the changes between base and latest into
+ * the txn tree [see the function merge()]. The txn tree is the
+ * only one of the three trees that is mutable, so it has to be the
+ * one to adjust.
+ *
+ * You might have to adjust it more than once, if a new latest
+ * revision gets committed while you were merging in the previous
+ * one. For example:
+ *
+ * 1. Jane starts txn T, based at revision 6.
+ * 2. Someone commits (or already committed) revision 7.
+ * 3. Jane's starts merging the changes between 6 and 7 into T.
+ * 4. Meanwhile, someone commits revision 8.
+ * 5. Jane finishes the 6-->7 merge. T could now be committed
+ * against a latest revision of 7, if only that were still the
+ * latest. Unfortunately, 8 is now the latest, so...
+ * 6. Jane starts merging the changes between 7 and 8 into T.
+ * 7. Meanwhile, no one commits any new revisions. Whew.
+ * 8. Jane commits T, creating revision 9, whose tree is exactly
+ * T's tree, except immutable now.
+ *
+ * Lather, rinse, repeat.
+ */
+
+ svn_error_t *err;
+ svn_fs_t *fs = txn->fs;
+ apr_pool_t *subpool = svn_pool_create(pool);
+
+ /* Initialize output params. */
+ *new_rev = SVN_INVALID_REVNUM;
+ if (conflict_p)
+ *conflict_p = NULL;
+
+ while (1729)
+ {
+ struct get_root_args get_root_args;
+ struct merge_args merge_args;
+ struct commit_args commit_args;
+ svn_revnum_t youngish_rev;
+ svn_fs_root_t *youngish_root;
+ dag_node_t *youngish_root_node;
+
+ svn_pool_clear(subpool);
+
+ /* Get the *current* youngest revision, in one short-lived
+ Berkeley transaction. (We don't want the revisions table
+ locked while we do the main merge.) We call it "youngish"
+ because new revisions might get committed after we've
+ obtained it. */
+
+ SVN_ERR(svn_fs_base__youngest_rev(&youngish_rev, fs, subpool));
+ SVN_ERR(svn_fs_base__revision_root(&youngish_root, fs, youngish_rev,
+ subpool));
+
+ /* Get the dag node for the youngest revision, also in one
+ Berkeley transaction. Later we'll use it as the SOURCE
+ argument to a merge, and if the merge succeeds, this youngest
+ root node will become the new base root for the svn txn that
+ was the target of the merge (but note that the youngest rev
+ may have changed by then -- that's why we're careful to get
+ this root in its own bdb txn here). */
+ get_root_args.root = youngish_root;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_root, &get_root_args,
+ FALSE, subpool));
+ youngish_root_node = get_root_args.node;
+
+ /* Try to merge. If the merge succeeds, the base root node of
+ TARGET's txn will become the same as youngish_root_node, so
+ any future merges will only be between that node and whatever
+ the root node of the youngest rev is by then. */
+ merge_args.ancestor_node = NULL;
+ merge_args.source_node = youngish_root_node;
+ merge_args.txn = txn;
+ merge_args.conflict = svn_stringbuf_create_empty(pool); /* use pool */
+ err = svn_fs_base__retry_txn(fs, txn_body_merge, &merge_args,
+ FALSE, subpool);
+ if (err)
+ {
+ if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
+ *conflict_p = merge_args.conflict->data;
+ return svn_error_trace(err);
+ }
+
+ /* Try to commit. */
+ commit_args.txn = txn;
+ err = svn_fs_base__retry_txn(fs, txn_body_commit, &commit_args,
+ FALSE, subpool);
+ if (err && (err->apr_err == SVN_ERR_FS_TXN_OUT_OF_DATE))
+ {
+ /* Did someone else finish committing a new revision while we
+ were in mid-merge or mid-commit? If so, we'll need to
+ loop again to merge the new changes in, then try to
+ commit again. Or if that's not what happened, then just
+ return the error. */
+ svn_revnum_t youngest_rev;
+ svn_error_t *err2 = svn_fs_base__youngest_rev(&youngest_rev, fs,
+ subpool);
+ if (err2)
+ {
+ svn_error_clear(err);
+ return svn_error_trace(err2); /* err2 is bad,
+ it should not occur */
+ }
+ else if (youngest_rev == youngish_rev)
+ return svn_error_trace(err);
+ else
+ svn_error_clear(err);
+ }
+ else if (err)
+ {
+ return svn_error_trace(err);
+ }
+ else
+ {
+ /* Set the return value -- our brand spankin' new revision! */
+ *new_rev = commit_args.new_rev;
+ break;
+ }
+ }
+
+ svn_pool_destroy(subpool);
+ 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. */
+static svn_error_t *
+base_merge(const char **conflict_p,
+ svn_fs_root_t *source_root,
+ const char *source_path,
+ svn_fs_root_t *target_root,
+ const char *target_path,
+ svn_fs_root_t *ancestor_root,
+ const char *ancestor_path,
+ apr_pool_t *pool)
+{
+ dag_node_t *source, *ancestor;
+ struct get_root_args get_root_args;
+ struct merge_args merge_args;
+ svn_fs_txn_t *txn;
+ svn_error_t *err;
+ svn_fs_t *fs;
+
+ if (! target_root->is_txn_root)
+ return SVN_FS__NOT_TXN(target_root);
+
+ /* Paranoia. */
+ fs = ancestor_root->fs;
+ if ((source_root->fs != fs) || (target_root->fs != fs))
+ {
+ return svn_error_create
+ (SVN_ERR_FS_CORRUPT, NULL,
+ _("Bad merge; ancestor, source, and target not all in same fs"));
+ }
+
+ /* ### kff todo: is there any compelling reason to get the nodes in
+ one db transaction? Right now we don't; txn_body_get_root() gets
+ one node at a time. This will probably need to change:
+
+ Jim Blandy <jimb@zwingli.cygnus.com> writes:
+ > svn_fs_merge needs to be a single transaction, to protect it against
+ > people deleting parents of nodes it's working on, etc.
+ */
+
+ /* Get the ancestor node. */
+ get_root_args.root = ancestor_root;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_root, &get_root_args,
+ FALSE, pool));
+ ancestor = get_root_args.node;
+
+ /* Get the source node. */
+ get_root_args.root = source_root;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_root, &get_root_args,
+ FALSE, pool));
+ source = get_root_args.node;
+
+ /* Open a txn for the txn root into which we're merging. */
+ SVN_ERR(svn_fs_base__open_txn(&txn, fs, target_root->txn, pool));
+
+ /* Merge changes between ANCESTOR and SOURCE into TXN. */
+ merge_args.source_node = source;
+ merge_args.ancestor_node = ancestor;
+ merge_args.txn = txn;
+ merge_args.conflict = svn_stringbuf_create_empty(pool);
+ err = svn_fs_base__retry_txn(fs, txn_body_merge, &merge_args, FALSE, pool);
+ if (err)
+ {
+ if ((err->apr_err == SVN_ERR_FS_CONFLICT) && conflict_p)
+ *conflict_p = merge_args.conflict->data;
+ return svn_error_trace(err);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct rev_get_txn_id_args
+{
+ const char **txn_id;
+ svn_revnum_t revision;
+};
+
+
+static svn_error_t *
+txn_body_rev_get_txn_id(void *baton, trail_t *trail)
+{
+ struct rev_get_txn_id_args *args = baton;
+ return svn_fs_base__rev_get_txn_id(args->txn_id, trail->fs,
+ args->revision, trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__deltify(svn_fs_t *fs,
+ svn_revnum_t revision,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root;
+ const char *txn_id;
+ struct rev_get_txn_id_args args;
+ base_fs_data_t *bfd = fs->fsap_data;
+
+ if (bfd->format >= SVN_FS_BASE__MIN_MISCELLANY_FORMAT)
+ {
+ const char *val;
+ svn_revnum_t forward_delta_rev = 0;
+
+ SVN_ERR(svn_fs_base__miscellaneous_get
+ (&val, fs, SVN_FS_BASE__MISC_FORWARD_DELTA_UPGRADE, pool));
+ if (val)
+ SVN_ERR(svn_revnum_parse(&forward_delta_rev, val, NULL));
+
+ /* ### FIXME: Unnecessarily harsh requirement? (cmpilato). */
+ if (revision <= forward_delta_rev)
+ return svn_error_createf
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot deltify revisions prior to r%ld"), forward_delta_rev+1);
+ }
+
+ SVN_ERR(svn_fs_base__revision_root(&root, fs, revision, pool));
+
+ args.txn_id = &txn_id;
+ args.revision = revision;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_rev_get_txn_id, &args,
+ FALSE, pool));
+
+ return deltify_mutable(fs, root, "/", NULL, svn_node_dir, txn_id, pool);
+}
+
+
+/* Modifying directories */
+
+
+struct make_dir_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_make_dir(void *baton,
+ trail_t *trail)
+{
+ struct make_dir_args *args = baton;
+ svn_fs_root_t *root = args->root;
+ const char *path = args->path;
+ parent_path_t *parent_path;
+ dag_node_t *sub_dir;
+ const char *txn_id = root->txn;
+
+ SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
+ txn_id, trail, trail->pool));
+
+ /* If there's already a sub-directory by that name, complain. This
+ also catches the case of trying to make a subdirectory named `/'. */
+ if (parent_path->node)
+ return SVN_FS__ALREADY_EXISTS(root, path);
+
+ /* Check to see if some lock is 'reserving' a file-path or dir-path
+ at that location, or even some child-path; if so, check that we
+ can use it. */
+ if (args->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ SVN_ERR(svn_fs_base__allow_locked_operation(path, TRUE,
+ trail, trail->pool));
+ }
+
+ /* Create the subdirectory. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent, path,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_make_dir(&sub_dir,
+ parent_path->parent->node,
+ parent_path_path(parent_path->parent,
+ trail->pool),
+ parent_path->entry,
+ txn_id,
+ trail, trail->pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(root->fs, txn_id, path,
+ svn_fs_base__dag_get_id(sub_dir),
+ svn_fs_path_change_add, FALSE, FALSE,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_make_dir(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct make_dir_args args;
+
+ if (! root->is_txn_root)
+ return SVN_FS__NOT_TXN(root);
+
+ args.root = root;
+ args.path = path;
+ return svn_fs_base__retry_txn(root->fs, txn_body_make_dir, &args,
+ TRUE, pool);
+}
+
+
+struct delete_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+/* If this returns SVN_ERR_FS_NO_SUCH_ENTRY, it means that the
+ basename of PATH is missing from its parent, that is, the final
+ target of the deletion is missing. */
+static svn_error_t *
+txn_body_delete(void *baton,
+ trail_t *trail)
+{
+ struct delete_args *args = baton;
+ svn_fs_root_t *root = args->root;
+ const char *path = args->path;
+ parent_path_t *parent_path;
+ const char *txn_id = root->txn;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ if (! root->is_txn_root)
+ return SVN_FS__NOT_TXN(root);
+
+ SVN_ERR(open_path(&parent_path, root, path, 0, txn_id,
+ trail, trail->pool));
+
+ /* We can't remove the root of the filesystem. */
+ if (! parent_path->parent)
+ return svn_error_create(SVN_ERR_FS_ROOT_DIR, NULL,
+ _("The root directory cannot be deleted"));
+
+ /* Check to see if path (or any child thereof) is locked; if so,
+ check that we can use the existing lock(s). */
+ if (root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ SVN_ERR(svn_fs_base__allow_locked_operation(path, TRUE,
+ trail, trail->pool));
+ }
+
+ /* Make the parent directory mutable. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent, path,
+ trail, trail->pool));
+
+ /* Decrement mergeinfo counts on the parents of this node by the
+ count it previously carried, if our format supports it. */
+ if (bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT)
+ {
+ apr_int64_t mergeinfo_count;
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL, &mergeinfo_count,
+ parent_path->node,
+ trail, trail->pool));
+ SVN_ERR(adjust_parent_mergeinfo_counts(parent_path->parent,
+ -mergeinfo_count, txn_id,
+ trail, trail->pool));
+ }
+
+ /* Do the deletion. */
+ SVN_ERR(svn_fs_base__dag_delete(parent_path->parent->node,
+ parent_path->entry,
+ txn_id, trail, trail->pool));
+
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(root->fs, txn_id, path,
+ svn_fs_base__dag_get_id(parent_path->node),
+ svn_fs_path_change_delete, FALSE, FALSE, trail,
+ trail->pool);
+}
+
+
+static svn_error_t *
+base_delete_node(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct delete_args args;
+
+ args.root = root;
+ args.path = path;
+ return svn_fs_base__retry_txn(root->fs, txn_body_delete, &args,
+ TRUE, pool);
+}
+
+
+struct copy_args
+{
+ svn_fs_root_t *from_root;
+ const char *from_path;
+ svn_fs_root_t *to_root;
+ const char *to_path;
+ svn_boolean_t preserve_history;
+};
+
+
+static svn_error_t *
+txn_body_copy(void *baton,
+ trail_t *trail)
+{
+ struct copy_args *args = baton;
+ svn_fs_root_t *from_root = args->from_root;
+ const char *from_path = args->from_path;
+ svn_fs_root_t *to_root = args->to_root;
+ const char *to_path = args->to_path;
+ dag_node_t *from_node;
+ parent_path_t *to_parent_path;
+ const char *txn_id = to_root->txn;
+
+ /* Get the NODE for FROM_PATH in FROM_ROOT.*/
+ SVN_ERR(get_dag(&from_node, from_root, from_path, trail, trail->pool));
+
+ /* Build up the parent path from TO_PATH in TO_ROOT. If the last
+ component does not exist, it's not that big a deal. We'll just
+ make one there. */
+ SVN_ERR(open_path(&to_parent_path, to_root, to_path,
+ open_path_last_optional, txn_id, trail, trail->pool));
+
+ /* Check to see if to-path (or any child thereof) is locked, or at
+ least 'reserved', whether it exists or not; if so, check that we
+ can use the existing lock(s). */
+ if (to_root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ SVN_ERR(svn_fs_base__allow_locked_operation(to_path, TRUE,
+ trail, trail->pool));
+ }
+
+ /* If the destination node already exists as the same node as the
+ source (in other words, this operation would result in nothing
+ happening at all), just do nothing an return successfully,
+ proud that you saved yourself from a tiresome task. */
+ if ((to_parent_path->node)
+ && (svn_fs_base__id_compare(svn_fs_base__dag_get_id(from_node),
+ svn_fs_base__dag_get_id
+ (to_parent_path->node)) == 0))
+ return SVN_NO_ERROR;
+
+ if (! from_root->is_txn_root)
+ {
+ svn_fs_path_change_kind_t kind;
+ dag_node_t *new_node;
+ apr_int64_t old_mergeinfo_count = 0, mergeinfo_count;
+ base_fs_data_t *bfd = trail->fs->fsap_data;
+
+ /* If TO_PATH already existed prior to the copy, note that this
+ operation is a replacement, not an addition. */
+ if (to_parent_path->node)
+ kind = svn_fs_path_change_replace;
+ else
+ kind = svn_fs_path_change_add;
+
+ /* Make sure the target node's parents are mutable. */
+ SVN_ERR(make_path_mutable(to_root, to_parent_path->parent,
+ to_path, trail, trail->pool));
+
+ /* If this is a replacement operation, we need to know the old
+ node's mergeinfo count. */
+ if (to_parent_path->node)
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL,
+ &old_mergeinfo_count,
+ to_parent_path->node,
+ trail, trail->pool));
+ /* Do the copy. */
+ SVN_ERR(svn_fs_base__dag_copy(to_parent_path->parent->node,
+ to_parent_path->entry,
+ from_node,
+ args->preserve_history,
+ from_root->rev,
+ from_path, txn_id, trail, trail->pool));
+
+ /* Adjust the mergeinfo counts of the destination's parents if
+ our format supports it. */
+ if (bfd->format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT)
+ {
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(NULL,
+ &mergeinfo_count,
+ from_node, trail,
+ trail->pool));
+ SVN_ERR(adjust_parent_mergeinfo_counts
+ (to_parent_path->parent,
+ mergeinfo_count - old_mergeinfo_count,
+ txn_id, trail, trail->pool));
+ }
+
+ /* Make a record of this modification in the changes table. */
+ SVN_ERR(get_dag(&new_node, to_root, to_path, trail, trail->pool));
+ SVN_ERR(add_change(to_root->fs, txn_id, to_path,
+ svn_fs_base__dag_get_id(new_node),
+ kind, FALSE, FALSE, trail, trail->pool));
+ }
+ else
+ {
+ /* See IZ Issue #436 */
+ /* Copying from transaction roots not currently available.
+
+ ### cmpilato todo someday: make this not so. :-) Note that
+ when copying from mutable trees, you have to make sure that
+ you aren't creating a cyclic graph filesystem, and a simple
+ referencing operation won't cut it. Currently, we should not
+ be able to reach this clause, and the interface reports that
+ this only works from immutable trees anyway, but JimB has
+ stated that this requirement need not be necessary in the
+ future. */
+
+ SVN_ERR_MALFUNCTION();
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+/* Set *SAME_P to TRUE if FS1 and FS2 have the same UUID, else set to FALSE.
+ Use POOL for temporary allocation only.
+ Note: this code is duplicated between libsvn_fs_fs and libsvn_fs_base. */
+static svn_error_t *
+fs_same_p(svn_boolean_t *same_p,
+ svn_fs_t *fs1,
+ svn_fs_t *fs2,
+ apr_pool_t *pool)
+{
+ *same_p = ! strcmp(fs1->uuid, fs2->uuid);
+ return SVN_NO_ERROR;
+}
+
+/* Copy the node at FROM_PATH under FROM_ROOT to TO_PATH under
+ TO_ROOT. If PRESERVE_HISTORY is set, then the copy is recorded in
+ the copies table. Perform temporary allocations in POOL. */
+static svn_error_t *
+copy_helper(svn_fs_root_t *from_root,
+ const char *from_path,
+ svn_fs_root_t *to_root,
+ const char *to_path,
+ svn_boolean_t preserve_history,
+ apr_pool_t *pool)
+{
+ struct copy_args args;
+ svn_boolean_t same_p;
+
+ /* Use an error check, not an assert, because even the caller cannot
+ guarantee that a filesystem's UUID has not changed "on the fly". */
+ SVN_ERR(fs_same_p(&same_p, from_root->fs, to_root->fs, pool));
+ if (! same_p)
+ return svn_error_createf
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Cannot copy between two different filesystems ('%s' and '%s')"),
+ from_root->fs->path, to_root->fs->path);
+
+ if (! to_root->is_txn_root)
+ return SVN_FS__NOT_TXN(to_root);
+
+ if (from_root->is_txn_root)
+ return svn_error_create
+ (SVN_ERR_UNSUPPORTED_FEATURE, NULL,
+ _("Copy from mutable tree not currently supported"));
+
+ args.from_root = from_root;
+ args.from_path = from_path;
+ args.to_root = to_root;
+ args.to_path = to_path;
+ args.preserve_history = preserve_history;
+
+ return svn_fs_base__retry_txn(to_root->fs, txn_body_copy, &args,
+ TRUE, pool);
+}
+
+static svn_error_t *
+base_copy(svn_fs_root_t *from_root,
+ const char *from_path,
+ svn_fs_root_t *to_root,
+ const char *to_path,
+ apr_pool_t *pool)
+{
+ return copy_helper(from_root, from_path, to_root, to_path, TRUE, pool);
+}
+
+
+static svn_error_t *
+base_revision_link(svn_fs_root_t *from_root,
+ svn_fs_root_t *to_root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ return copy_helper(from_root, path, to_root, path, FALSE, pool);
+}
+
+
+struct copied_from_args
+{
+ svn_fs_root_t *root; /* Root for the node whose ancestry we seek. */
+ const char *path; /* Path for the node whose ancestry we seek. */
+
+ svn_revnum_t result_rev; /* Revision, if any, of the ancestor. */
+ const char *result_path; /* Path, if any, of the ancestor. */
+
+ apr_pool_t *pool; /* Allocate `result_path' here. */
+};
+
+
+static svn_error_t *
+txn_body_copied_from(void *baton, trail_t *trail)
+{
+ struct copied_from_args *args = baton;
+ const svn_fs_id_t *node_id, *pred_id;
+ dag_node_t *node;
+ svn_fs_t *fs = args->root->fs;
+
+ /* Clear the return variables. */
+ args->result_path = NULL;
+ args->result_rev = SVN_INVALID_REVNUM;
+
+ /* Fetch the NODE in question. */
+ SVN_ERR(get_dag(&node, args->root, args->path, trail, trail->pool));
+ node_id = svn_fs_base__dag_get_id(node);
+
+ /* Check the node's predecessor-ID. If it doesn't have one, it
+ isn't a copy. */
+ SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id, node,
+ trail, trail->pool));
+ if (! pred_id)
+ return SVN_NO_ERROR;
+
+ /* If NODE's copy-ID is the same as that of its predecessor... */
+ if (svn_fs_base__key_compare(svn_fs_base__id_copy_id(node_id),
+ svn_fs_base__id_copy_id(pred_id)) != 0)
+ {
+ /* ... then NODE was either the target of a copy operation,
+ a copied subtree item. We examine the actual copy record
+ to determine which is the case. */
+ copy_t *copy;
+ SVN_ERR(svn_fs_bdb__get_copy(&copy, fs,
+ svn_fs_base__id_copy_id(node_id),
+ trail, trail->pool));
+ if ((copy->kind == copy_kind_real)
+ && svn_fs_base__id_eq(copy->dst_noderev_id, node_id))
+ {
+ args->result_path = copy->src_path;
+ SVN_ERR(svn_fs_base__txn_get_revision(&(args->result_rev), fs,
+ copy->src_txn_id,
+ trail, trail->pool));
+ }
+ }
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_copied_from(svn_revnum_t *rev_p,
+ const char **path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct copied_from_args args;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+ args.root = root;
+ args.path = path;
+ args.pool = pool;
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_copied_from, &args,
+ FALSE, scratch_pool));
+
+ *rev_p = args.result_rev;
+ *path_p = args.result_path ? apr_pstrdup(pool, args.result_path) : NULL;
+
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Files. */
+
+
+struct make_file_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_make_file(void *baton,
+ trail_t *trail)
+{
+ struct make_file_args *args = baton;
+ svn_fs_root_t *root = args->root;
+ const char *path = args->path;
+ parent_path_t *parent_path;
+ dag_node_t *child;
+ const char *txn_id = root->txn;
+
+ SVN_ERR(open_path(&parent_path, root, path, open_path_last_optional,
+ txn_id, trail, trail->pool));
+
+ /* If there's already a file by that name, complain.
+ This also catches the case of trying to make a file named `/'. */
+ if (parent_path->node)
+ return SVN_FS__ALREADY_EXISTS(root, path);
+
+ /* Check to see if some lock is 'reserving' a file-path or dir-path
+ at that location, or even some child-path; if so, check that we
+ can use it. */
+ if (args->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ {
+ SVN_ERR(svn_fs_base__allow_locked_operation(path, TRUE,
+ trail, trail->pool));
+ }
+
+ /* Create the file. */
+ SVN_ERR(make_path_mutable(root, parent_path->parent, path,
+ trail, trail->pool));
+ SVN_ERR(svn_fs_base__dag_make_file(&child,
+ parent_path->parent->node,
+ parent_path_path(parent_path->parent,
+ trail->pool),
+ parent_path->entry,
+ txn_id,
+ trail, trail->pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(root->fs, txn_id, path,
+ svn_fs_base__dag_get_id(child),
+ svn_fs_path_change_add, TRUE, FALSE,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_make_file(svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct make_file_args args;
+
+ args.root = root;
+ args.path = path;
+ return svn_fs_base__retry_txn(root->fs, txn_body_make_file, &args,
+ TRUE, pool);
+}
+
+
+
+struct file_length_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+ svn_filesize_t length; /* OUT parameter */
+};
+
+static svn_error_t *
+txn_body_file_length(void *baton,
+ trail_t *trail)
+{
+ struct file_length_args *args = baton;
+ dag_node_t *file;
+
+ /* First create a dag_node_t from the root/path pair. */
+ SVN_ERR(get_dag(&file, args->root, args->path, trail, trail->pool));
+
+ /* Now fetch its length */
+ return svn_fs_base__dag_file_length(&args->length, file,
+ trail, trail->pool);
+}
+
+static svn_error_t *
+base_file_length(svn_filesize_t *length_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct file_length_args args;
+
+ args.root = root;
+ args.path = path;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_file_length, &args,
+ TRUE, pool));
+
+ *length_p = args.length;
+ return SVN_NO_ERROR;
+}
+
+
+struct file_checksum_args
+{
+ svn_fs_root_t *root;
+ const char *path;
+ svn_checksum_kind_t kind;
+ svn_checksum_t **checksum; /* OUT parameter */
+};
+
+static svn_error_t *
+txn_body_file_checksum(void *baton,
+ trail_t *trail)
+{
+ struct file_checksum_args *args = baton;
+ dag_node_t *file;
+
+ SVN_ERR(get_dag(&file, args->root, args->path, trail, trail->pool));
+
+ return svn_fs_base__dag_file_checksum(args->checksum, args->kind, file,
+ trail, trail->pool);
+}
+
+static svn_error_t *
+base_file_checksum(svn_checksum_t **checksum,
+ svn_checksum_kind_t kind,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct file_checksum_args args;
+ apr_pool_t *scratch_pool = svn_pool_create(pool);
+
+ args.root = root;
+ args.path = path;
+ args.kind = kind;
+ args.checksum = checksum;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_file_checksum, &args,
+ FALSE, scratch_pool));
+ *checksum = svn_checksum_dup(*checksum, pool);
+ svn_pool_destroy(scratch_pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* --- Machinery for svn_fs_file_contents() --- */
+
+
+/* Local baton type for txn_body_get_file_contents. */
+typedef struct file_contents_baton_t
+{
+ /* The file we want to read. */
+ svn_fs_root_t *root;
+ const char *path;
+
+ /* The dag_node that will be made from the above. */
+ dag_node_t *node;
+
+ /* The pool in which `file_stream' (below) is allocated. */
+ apr_pool_t *pool;
+
+ /* The readable file stream that will be made from the
+ dag_node. (And returned to the caller.) */
+ svn_stream_t *file_stream;
+
+} file_contents_baton_t;
+
+
+/* Main body of svn_fs_file_contents; converts a root/path pair into
+ a readable file stream (in the context of a db txn). */
+static svn_error_t *
+txn_body_get_file_contents(void *baton, trail_t *trail)
+{
+ file_contents_baton_t *fb = (file_contents_baton_t *) baton;
+
+ /* First create a dag_node_t from the root/path pair. */
+ SVN_ERR(get_dag(&(fb->node), fb->root, fb->path, trail, trail->pool));
+
+ /* Then create a readable stream from the dag_node_t. */
+ return svn_fs_base__dag_get_contents(&(fb->file_stream),
+ fb->node, trail, fb->pool);
+}
+
+
+
+static svn_error_t *
+base_file_contents(svn_stream_t **contents,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ file_contents_baton_t *fb = apr_pcalloc(pool, sizeof(*fb));
+ fb->root = root;
+ fb->path = path;
+ fb->pool = pool;
+
+ /* Create the readable stream in the context of a db txn. */
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_get_file_contents, fb,
+ FALSE, pool));
+
+ *contents = fb->file_stream;
+ return SVN_NO_ERROR;
+}
+
+/* --- End machinery for svn_fs_file_contents() --- */
+
+
+
+/* --- Machinery for svn_fs_apply_textdelta() --- */
+
+
+/* Local baton type for all the helper functions below. */
+typedef struct txdelta_baton_t
+{
+ /* This is the custom-built window consumer given to us by the delta
+ library; it uniquely knows how to read data from our designated
+ "source" stream, interpret the window, and write data to our
+ designated "target" stream (in this case, our repos file.) */
+ svn_txdelta_window_handler_t interpreter;
+ void *interpreter_baton;
+
+ /* The original file info */
+ svn_fs_root_t *root;
+ const char *path;
+
+ /* Derived from the file info */
+ dag_node_t *node;
+
+ svn_stream_t *source_stream;
+ svn_stream_t *target_stream;
+ svn_stream_t *string_stream;
+ svn_stringbuf_t *target_string;
+
+ /* Checksums for the base text against which a delta is to be
+ applied, and for the resultant fulltext, respectively. Either or
+ both may be null, in which case ignored. */
+ svn_checksum_t *base_checksum;
+ svn_checksum_t *result_checksum;
+
+ /* Pool used by db txns */
+ apr_pool_t *pool;
+
+} txdelta_baton_t;
+
+
+/* A trail-ready wrapper around svn_fs_base__dag_finalize_edits.
+ * This closes BATON->target_stream.
+ *
+ * Note: If you're confused about how this function relates to another
+ * of similar name, think of it this way:
+ *
+ * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
+ * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits()
+ */
+static svn_error_t *
+txn_body_txdelta_finalize_edits(void *baton, trail_t *trail)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+ SVN_ERR(svn_fs_base__dag_finalize_edits(tb->node,
+ tb->result_checksum,
+ tb->root->txn,
+ trail, trail->pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(tb->root->fs, tb->root->txn, tb->path,
+ svn_fs_base__dag_get_id(tb->node),
+ svn_fs_path_change_modify, TRUE, FALSE, trail,
+ trail->pool);
+}
+
+
+/* ### see comment in window_consumer() regarding this function. */
+
+/* Helper function of generic type `svn_write_fn_t'. Implements a
+ writable stream which appends to an svn_stringbuf_t. */
+static svn_error_t *
+write_to_string(void *baton, const char *data, apr_size_t *len)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+ svn_stringbuf_appendbytes(tb->target_string, data, *len);
+ return SVN_NO_ERROR;
+}
+
+
+
+/* The main window handler returned by svn_fs_apply_textdelta. */
+static svn_error_t *
+window_consumer(svn_txdelta_window_t *window, void *baton)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+
+ /* Send the window right through to the custom window interpreter.
+ In theory, the interpreter will then write more data to
+ cb->target_string. */
+ SVN_ERR(tb->interpreter(window, tb->interpreter_baton));
+
+ /* ### the write_to_string() callback for the txdelta's output stream
+ ### should be doing all the flush determination logic, not here.
+ ### in a drastic case, a window could generate a LOT more than the
+ ### maximum buffer size. we want to flush to the underlying target
+ ### stream much sooner (e.g. also in a streamy fashion). also, by
+ ### moving this logic inside the stream, the stream becomes nice
+ ### and encapsulated: it holds all the logic about buffering and
+ ### flushing.
+ ###
+ ### further: I believe the buffering should be removed from tree.c
+ ### the buffering should go into the target_stream itself, which
+ ### is defined by reps-string.c. Specifically, I think the
+ ### rep_write_contents() function will handle the buffering and
+ ### the spill to the underlying DB. by locating it there, then
+ ### anybody who gets a writable stream for FS content can take
+ ### advantage of the buffering capability. this will be important
+ ### when we export an FS API function for writing a fulltext into
+ ### the FS, rather than forcing that fulltext thru apply_textdelta.
+ */
+
+ /* Check to see if we need to purge the portion of the contents that
+ have been written thus far. */
+ if ((! window) || (tb->target_string->len > WRITE_BUFFER_SIZE))
+ {
+ apr_size_t len = tb->target_string->len;
+ SVN_ERR(svn_stream_write(tb->target_stream,
+ tb->target_string->data,
+ &len));
+ svn_stringbuf_setempty(tb->target_string);
+ }
+
+ /* Is the window NULL? If so, we're done. */
+ if (! window)
+ {
+ /* Close the internal-use stream. ### This used to be inside of
+ txn_body_fulltext_finalize_edits(), but that invoked a nested
+ Berkeley DB transaction -- scandalous! */
+ SVN_ERR(svn_stream_close(tb->target_stream));
+
+ /* Tell the dag subsystem that we're finished with our edits. */
+ SVN_ERR(svn_fs_base__retry_txn(tb->root->fs,
+ txn_body_txdelta_finalize_edits, tb,
+ FALSE, tb->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+txn_body_apply_textdelta(void *baton, trail_t *trail)
+{
+ txdelta_baton_t *tb = (txdelta_baton_t *) baton;
+ parent_path_t *parent_path;
+ const char *txn_id = tb->root->txn;
+
+ /* Call open_path with no flags, as we want this to return an error
+ if the node for which we are searching doesn't exist. */
+ SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, txn_id,
+ trail, trail->pool));
+
+ /* Check to see if path is locked; if so, check that we can use it. */
+ if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_base__allow_locked_operation(tb->path, FALSE,
+ trail, trail->pool));
+
+ /* Now, make sure this path is mutable. */
+ SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path,
+ trail, trail->pool));
+ tb->node = parent_path->node;
+
+ if (tb->base_checksum)
+ {
+ svn_checksum_t *checksum;
+
+ /* Until we finalize the node, its data_key points to the old
+ contents, in other words, the base text. */
+ SVN_ERR(svn_fs_base__dag_file_checksum(&checksum,
+ tb->base_checksum->kind,
+ tb->node, trail, trail->pool));
+ /* TODO: This only compares checksums if they are the same kind, but
+ we're calculating both SHA1 and MD5 checksums somewhere in
+ reps-strings.c. Could we keep them both around somehow so this
+ check could be more comprehensive? */
+ if (!svn_checksum_match(tb->base_checksum, checksum))
+ return svn_checksum_mismatch_err(tb->base_checksum, checksum,
+ trail->pool,
+ _("Base checksum mismatch on '%s'"),
+ tb->path);
+ }
+
+ /* Make a readable "source" stream out of the current contents of
+ ROOT/PATH; obviously, this must done in the context of a db_txn.
+ The stream is returned in tb->source_stream. */
+ SVN_ERR(svn_fs_base__dag_get_contents(&(tb->source_stream),
+ tb->node, trail, tb->pool));
+
+ /* Make a writable "target" stream */
+ SVN_ERR(svn_fs_base__dag_get_edit_stream(&(tb->target_stream), tb->node,
+ txn_id, trail, tb->pool));
+
+ /* Make a writable "string" stream which writes data to
+ tb->target_string. */
+ tb->target_string = svn_stringbuf_create_empty(tb->pool);
+ tb->string_stream = svn_stream_create(tb, tb->pool);
+ svn_stream_set_write(tb->string_stream, write_to_string);
+
+ /* Now, create a custom window handler that uses our two streams. */
+ svn_txdelta_apply(tb->source_stream,
+ tb->string_stream,
+ NULL,
+ tb->path,
+ tb->pool,
+ &(tb->interpreter),
+ &(tb->interpreter_baton));
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_apply_textdelta(svn_txdelta_window_handler_t *contents_p,
+ void **contents_baton_p,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_checksum_t *base_checksum,
+ svn_checksum_t *result_checksum,
+ apr_pool_t *pool)
+{
+ txdelta_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
+
+ tb->root = root;
+ tb->path = path;
+ tb->pool = pool;
+ tb->base_checksum = svn_checksum_dup(base_checksum, pool);
+ tb->result_checksum = svn_checksum_dup(result_checksum, pool);
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_apply_textdelta, tb,
+ FALSE, pool));
+
+ *contents_p = window_consumer;
+ *contents_baton_p = tb;
+ return SVN_NO_ERROR;
+}
+
+/* --- End machinery for svn_fs_apply_textdelta() --- */
+
+/* --- Machinery for svn_fs_apply_text() --- */
+
+/* Baton for svn_fs_apply_text(). */
+struct text_baton_t
+{
+ /* The original file info */
+ svn_fs_root_t *root;
+ const char *path;
+
+ /* Derived from the file info */
+ dag_node_t *node;
+
+ /* The returned stream that will accept the file's new contents. */
+ svn_stream_t *stream;
+
+ /* The actual fs stream that the returned stream will write to. */
+ svn_stream_t *file_stream;
+
+ /* Checksum for the final fulltext written to the file. May
+ be null, in which case ignored. */
+ svn_checksum_t *result_checksum;
+
+ /* Pool used by db txns */
+ apr_pool_t *pool;
+};
+
+
+/* A trail-ready wrapper around svn_fs_base__dag_finalize_edits, but for
+ * fulltext data, not text deltas. Closes BATON->file_stream.
+ *
+ * Note: If you're confused about how this function relates to another
+ * of similar name, think of it this way:
+ *
+ * svn_fs_apply_textdelta() ==> ... ==> txn_body_txdelta_finalize_edits()
+ * svn_fs_apply_text() ==> ... ==> txn_body_fulltext_finalize_edits()
+ */
+static svn_error_t *
+txn_body_fulltext_finalize_edits(void *baton, trail_t *trail)
+{
+ struct text_baton_t *tb = baton;
+ SVN_ERR(svn_fs_base__dag_finalize_edits(tb->node,
+ tb->result_checksum,
+ tb->root->txn,
+ trail, trail->pool));
+
+ /* Make a record of this modification in the changes table. */
+ return add_change(tb->root->fs, tb->root->txn, tb->path,
+ svn_fs_base__dag_get_id(tb->node),
+ svn_fs_path_change_modify, TRUE, FALSE, trail,
+ trail->pool);
+}
+
+/* Write function for the publically returned stream. */
+static svn_error_t *
+text_stream_writer(void *baton,
+ const char *data,
+ apr_size_t *len)
+{
+ struct text_baton_t *tb = baton;
+
+ /* Psst, here's some data. Pass it on to the -real- file stream. */
+ return svn_stream_write(tb->file_stream, data, len);
+}
+
+/* Close function for the publically returned stream. */
+static svn_error_t *
+text_stream_closer(void *baton)
+{
+ struct text_baton_t *tb = baton;
+
+ /* Close the internal-use stream. ### This used to be inside of
+ txn_body_fulltext_finalize_edits(), but that invoked a nested
+ Berkeley DB transaction -- scandalous! */
+ SVN_ERR(svn_stream_close(tb->file_stream));
+
+ /* Need to tell fs that we're done sending text */
+ return svn_fs_base__retry_txn(tb->root->fs,
+ txn_body_fulltext_finalize_edits, tb,
+ FALSE, tb->pool);
+}
+
+
+static svn_error_t *
+txn_body_apply_text(void *baton, trail_t *trail)
+{
+ struct text_baton_t *tb = baton;
+ parent_path_t *parent_path;
+ const char *txn_id = tb->root->txn;
+
+ /* Call open_path with no flags, as we want this to return an error
+ if the node for which we are searching doesn't exist. */
+ SVN_ERR(open_path(&parent_path, tb->root, tb->path, 0, txn_id,
+ trail, trail->pool));
+
+ /* Check to see if path is locked; if so, check that we can use it. */
+ if (tb->root->txn_flags & SVN_FS_TXN_CHECK_LOCKS)
+ SVN_ERR(svn_fs_base__allow_locked_operation(tb->path, FALSE,
+ trail, trail->pool));
+
+ /* Now, make sure this path is mutable. */
+ SVN_ERR(make_path_mutable(tb->root, parent_path, tb->path,
+ trail, trail->pool));
+ tb->node = parent_path->node;
+
+ /* Make a writable stream for replacing the file's text. */
+ SVN_ERR(svn_fs_base__dag_get_edit_stream(&(tb->file_stream), tb->node,
+ txn_id, trail, tb->pool));
+
+ /* Create a 'returnable' stream which writes to the file_stream. */
+ tb->stream = svn_stream_create(tb, tb->pool);
+ svn_stream_set_write(tb->stream, text_stream_writer);
+ svn_stream_set_close(tb->stream, text_stream_closer);
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_apply_text(svn_stream_t **contents_p,
+ svn_fs_root_t *root,
+ const char *path,
+ svn_checksum_t *result_checksum,
+ apr_pool_t *pool)
+{
+ struct text_baton_t *tb = apr_pcalloc(pool, sizeof(*tb));
+
+ tb->root = root;
+ tb->path = path;
+ tb->pool = pool;
+ tb->result_checksum = svn_checksum_dup(result_checksum, pool);
+
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_apply_text, tb,
+ FALSE, pool));
+
+ *contents_p = tb->stream;
+ return SVN_NO_ERROR;
+}
+
+/* --- End machinery for svn_fs_apply_text() --- */
+
+
+/* Note: we're sharing the `things_changed_args' struct with
+ svn_fs_props_changed(). */
+
+static svn_error_t *
+txn_body_contents_changed(void *baton, trail_t *trail)
+{
+ struct things_changed_args *args = baton;
+ dag_node_t *node1, *node2;
+
+ SVN_ERR(get_dag(&node1, args->root1, args->path1, trail, trail->pool));
+ SVN_ERR(get_dag(&node2, args->root2, args->path2, trail, trail->pool));
+ return svn_fs_base__things_different(NULL, args->changed_p,
+ node1, node2, trail, trail->pool);
+}
+
+
+/* Note: it is acceptable for this function to call back into
+ top-level interfaces because it does not itself use trails. */
+static svn_error_t *
+base_contents_changed(svn_boolean_t *changed_p,
+ svn_fs_root_t *root1,
+ const char *path1,
+ svn_fs_root_t *root2,
+ const char *path2,
+ apr_pool_t *pool)
+{
+ struct things_changed_args args;
+
+ /* Check that roots are in the same fs. */
+ if (root1->fs != root2->fs)
+ return svn_error_create
+ (SVN_ERR_FS_GENERAL, NULL,
+ _("Cannot compare file contents between two different filesystems"));
+
+ /* Check that both paths are files. */
+ {
+ svn_node_kind_t kind;
+
+ SVN_ERR(base_check_path(&kind, root1, path1, pool));
+ if (kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path1);
+
+ SVN_ERR(base_check_path(&kind, root2, path2, pool));
+ if (kind != svn_node_file)
+ return svn_error_createf
+ (SVN_ERR_FS_GENERAL, NULL, _("'%s' is not a file"), path2);
+ }
+
+ args.root1 = root1;
+ args.root2 = root2;
+ args.path1 = path1;
+ args.path2 = path2;
+ args.changed_p = changed_p;
+ args.pool = pool;
+
+ return svn_fs_base__retry_txn(root1->fs, txn_body_contents_changed, &args,
+ TRUE, pool);
+}
+
+
+
+/* Public interface to computing file text deltas. */
+
+/* Note: it is acceptable for this function to call back into
+ public FS API interfaces because it does not itself use trails. */
+static svn_error_t *
+base_get_file_delta_stream(svn_txdelta_stream_t **stream_p,
+ svn_fs_root_t *source_root,
+ const char *source_path,
+ svn_fs_root_t *target_root,
+ const char *target_path,
+ apr_pool_t *pool)
+{
+ svn_stream_t *source, *target;
+ svn_txdelta_stream_t *delta_stream;
+
+ /* Get read functions for the source file contents. */
+ if (source_root && source_path)
+ SVN_ERR(base_file_contents(&source, source_root, source_path, pool));
+ else
+ source = svn_stream_empty(pool);
+
+ /* Get read functions for the target file contents. */
+ SVN_ERR(base_file_contents(&target, target_root, target_path, pool));
+
+ /* Create a delta stream that turns the ancestor into the target. */
+ svn_txdelta2(&delta_stream, source, target, TRUE, pool);
+
+ *stream_p = delta_stream;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Finding Changes */
+
+struct paths_changed_args
+{
+ apr_hash_t *changes;
+ svn_fs_root_t *root;
+};
+
+
+static svn_error_t *
+txn_body_paths_changed(void *baton,
+ trail_t *trail)
+{
+ /* WARNING: This is called *without* the protection of a Berkeley DB
+ transaction. If you modify this function, keep that in mind. */
+
+ struct paths_changed_args *args = baton;
+ const char *txn_id;
+ svn_fs_t *fs = args->root->fs;
+
+ /* Get the transaction ID from ROOT. */
+ if (! args->root->is_txn_root)
+ SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id, fs, args->root->rev,
+ trail, trail->pool));
+ else
+ txn_id = args->root->txn;
+
+ return svn_fs_bdb__changes_fetch(&(args->changes), fs, txn_id,
+ trail, trail->pool);
+}
+
+
+static svn_error_t *
+base_paths_changed(apr_hash_t **changed_paths_p,
+ svn_fs_root_t *root,
+ apr_pool_t *pool)
+{
+ struct paths_changed_args args;
+ args.root = root;
+ args.changes = NULL;
+ SVN_ERR(svn_fs_base__retry(root->fs, txn_body_paths_changed, &args,
+ FALSE, pool));
+ *changed_paths_p = args.changes;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Our coolio opaque history object. */
+typedef struct base_history_data_t
+{
+ /* filesystem object */
+ svn_fs_t *fs;
+
+ /* path and revision of historical location */
+ const char *path;
+ svn_revnum_t revision;
+
+ /* internal-use hints about where to resume the history search. */
+ const char *path_hint;
+ svn_revnum_t rev_hint;
+
+ /* FALSE until the first call to svn_fs_history_prev(). */
+ svn_boolean_t is_interesting;
+} base_history_data_t;
+
+
+static svn_fs_history_t *assemble_history(svn_fs_t *fs, const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t is_interesting,
+ const char *path_hint,
+ svn_revnum_t rev_hint,
+ apr_pool_t *pool);
+
+
+static svn_error_t *
+base_node_history(svn_fs_history_t **history_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_node_kind_t kind;
+
+ /* We require a revision root. */
+ if (root->is_txn_root)
+ return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
+
+ /* And we require that the path exist in the root. */
+ SVN_ERR(base_check_path(&kind, root, path, pool));
+ if (kind == svn_node_none)
+ return SVN_FS__NOT_FOUND(root, path);
+
+ /* Okay, all seems well. Build our history object and return it. */
+ *history_p = assemble_history(root->fs,
+ svn_fs__canonicalize_abspath(path, pool),
+ root->rev, FALSE, NULL,
+ SVN_INVALID_REVNUM, pool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Examine the PARENT_PATH structure chain to determine how copy IDs
+ would be doled out in the event that PARENT_PATH was made mutable.
+ Return the ID of the copy that last affected PARENT_PATH (and the
+ COPY itself, if we've already fetched it).
+*/
+static svn_error_t *
+examine_copy_inheritance(const char **copy_id,
+ copy_t **copy,
+ svn_fs_t *fs,
+ parent_path_t *parent_path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ /* The default response -- our current copy ID, and no fetched COPY. */
+ *copy_id = svn_fs_base__id_copy_id
+ (svn_fs_base__dag_get_id(parent_path->node));
+ *copy = NULL;
+
+ /* If we have no parent (we are looking at the root node), or if
+ this node is supposed to inherit from itself, return that fact. */
+ if (! parent_path->parent)
+ return SVN_NO_ERROR;
+
+ /* We could be a branch destination (which would answer our question
+ altogether)! But then, again, we might just have been modified
+ in this revision, so all bets are off. */
+ if (parent_path->copy_inherit == copy_id_inherit_self)
+ {
+ /* A copy ID of "0" means we've never been branched. Therefore,
+ there are no copies relevant to our history. */
+ if (((*copy_id)[0] == '0') && ((*copy_id)[1] == '\0'))
+ return SVN_NO_ERROR;
+
+ /* Get the COPY record. If it was a real copy (not an implicit
+ one), we have our answer. Otherwise, we fall through to the
+ recursive case. */
+ SVN_ERR(svn_fs_bdb__get_copy(copy, fs, *copy_id, trail, pool));
+ if ((*copy)->kind != copy_kind_soft)
+ return SVN_NO_ERROR;
+ }
+
+ /* Otherwise, our answer is dependent upon our parent. */
+ return examine_copy_inheritance(copy_id, copy, fs,
+ parent_path->parent, trail, pool);
+}
+
+
+struct history_prev_args
+{
+ svn_fs_history_t **prev_history_p;
+ svn_fs_history_t *history;
+ svn_boolean_t cross_copies;
+ apr_pool_t *pool;
+};
+
+
+static svn_error_t *
+txn_body_history_prev(void *baton, trail_t *trail)
+{
+ struct history_prev_args *args = baton;
+ svn_fs_history_t **prev_history = args->prev_history_p;
+ svn_fs_history_t *history = args->history;
+ base_history_data_t *bhd = history->fsap_data;
+ const char *commit_path, *src_path, *path = bhd->path;
+ svn_revnum_t commit_rev, src_rev, dst_rev, revision = bhd->revision;
+ apr_pool_t *retpool = args->pool;
+ svn_fs_t *fs = bhd->fs;
+ parent_path_t *parent_path;
+ dag_node_t *node;
+ svn_fs_root_t *root;
+ const svn_fs_id_t *node_id;
+ const char *end_copy_id = NULL;
+ struct revision_root_args rr_args;
+ svn_boolean_t reported = bhd->is_interesting;
+ const char *txn_id;
+ copy_t *copy = NULL;
+ svn_boolean_t retry = FALSE;
+
+ /* Initialize our return value. */
+ *prev_history = NULL;
+
+ /* If our last history report left us hints about where to pickup
+ the chase, then our last report was on the destination of a
+ copy. If we are crossing copies, start from those locations,
+ otherwise, we're all done here. */
+ if (bhd->path_hint && SVN_IS_VALID_REVNUM(bhd->rev_hint))
+ {
+ reported = FALSE;
+ if (! args->cross_copies)
+ return SVN_NO_ERROR;
+ path = bhd->path_hint;
+ revision = bhd->rev_hint;
+ }
+
+ /* Construct a ROOT for the current revision. */
+ rr_args.root_p = &root;
+ rr_args.rev = revision;
+ SVN_ERR(txn_body_revision_root(&rr_args, trail));
+
+ /* Open PATH/REVISION, and get its node and a bunch of other
+ goodies. */
+ SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id, fs, revision, trail,
+ trail->pool));
+ SVN_ERR(open_path(&parent_path, root, path, 0, txn_id,
+ trail, trail->pool));
+ node = parent_path->node;
+ node_id = svn_fs_base__dag_get_id(node);
+ commit_path = svn_fs_base__dag_get_created_path(node);
+ SVN_ERR(svn_fs_base__dag_get_revision(&commit_rev, node,
+ trail, trail->pool));
+
+ /* The Subversion filesystem is written in such a way that a given
+ line of history may have at most one interesting history point
+ per filesystem revision. Either that node was edited (and
+ possibly copied), or it was copied but not edited. And a copy
+ source cannot be from the same revision as its destination. So,
+ if our history revision matches its node's commit revision, we
+ know that ... */
+ if (revision == commit_rev)
+ {
+ if (! reported)
+ {
+ /* ... we either have not yet reported on this revision (and
+ need now to do so) ... */
+ *prev_history = assemble_history(fs,
+ apr_pstrdup(retpool, commit_path),
+ commit_rev, TRUE, NULL,
+ SVN_INVALID_REVNUM, retpool);
+ return SVN_NO_ERROR;
+ }
+ else
+ {
+ /* ... or we *have* reported on this revision, and must now
+ progress toward this node's predecessor (unless there is
+ no predecessor, in which case we're all done!). */
+ const svn_fs_id_t *pred_id;
+
+ SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id, node,
+ trail, trail->pool));
+ if (! pred_id)
+ return SVN_NO_ERROR;
+
+ /* Replace NODE and friends with the information from its
+ predecessor. */
+ SVN_ERR(svn_fs_base__dag_get_node(&node, fs, pred_id,
+ trail, trail->pool));
+ node_id = svn_fs_base__dag_get_id(node);
+ commit_path = svn_fs_base__dag_get_created_path(node);
+ SVN_ERR(svn_fs_base__dag_get_revision(&commit_rev, node,
+ trail, trail->pool));
+ }
+ }
+
+ /* Calculate a possibly relevant copy ID. */
+ SVN_ERR(examine_copy_inheritance(&end_copy_id, &copy, fs,
+ parent_path, trail, trail->pool));
+
+ /* Initialize some state variables. */
+ src_path = NULL;
+ src_rev = SVN_INVALID_REVNUM;
+ dst_rev = SVN_INVALID_REVNUM;
+
+ /* If our current copy ID (which is either the real copy ID of our
+ node, or the last copy ID which would affect our node if it were
+ to be made mutable) diffs at all from that of its predecessor
+ (which is either a real predecessor, or is the node itself
+ playing the predecessor role to an imaginary mutable successor),
+ then we need to report a copy. */
+ if (svn_fs_base__key_compare(svn_fs_base__id_copy_id(node_id),
+ end_copy_id) != 0)
+ {
+ const char *remainder;
+ dag_node_t *dst_node;
+ const char *copy_dst;
+
+ /* Get the COPY record if we haven't already fetched it. */
+ if (! copy)
+ SVN_ERR(svn_fs_bdb__get_copy(&copy, fs, end_copy_id, trail,
+ trail->pool));
+
+ /* Figure out the destination path of the copy operation. */
+ SVN_ERR(svn_fs_base__dag_get_node(&dst_node, fs,
+ copy->dst_noderev_id,
+ trail, trail->pool));
+ copy_dst = svn_fs_base__dag_get_created_path(dst_node);
+
+ /* If our current path was the very destination of the copy,
+ then our new current path will be the copy source. If our
+ current path was instead the *child* of the destination of
+ the copy, then figure out its previous location by taking its
+ path relative to the copy destination and appending that to
+ the copy source. Finally, if our current path doesn't meet
+ one of these other criteria ... ### for now just fallback to
+ the old copy hunt algorithm. */
+ remainder = svn_fspath__skip_ancestor(copy_dst, path);
+
+ if (remainder)
+ {
+ /* If we get here, then our current path is the destination
+ of, or the child of the destination of, a copy. Fill
+ in the return values and get outta here. */
+ SVN_ERR(svn_fs_base__txn_get_revision
+ (&src_rev, fs, copy->src_txn_id, trail, trail->pool));
+ SVN_ERR(svn_fs_base__txn_get_revision
+ (&dst_rev, fs,
+ svn_fs_base__id_txn_id(copy->dst_noderev_id),
+ trail, trail->pool));
+ src_path = svn_fspath__join(copy->src_path, remainder,
+ trail->pool);
+ if (copy->kind == copy_kind_soft)
+ retry = TRUE;
+ }
+ }
+
+ /* If we calculated a copy source path and revision, and the
+ copy source revision doesn't pre-date a revision in which we
+ *know* our node was modified, we'll make a 'copy-style' history
+ object. */
+ if (src_path && SVN_IS_VALID_REVNUM(src_rev) && (src_rev >= commit_rev))
+ {
+ /* It's possible for us to find a copy location that is the same
+ as the history point we've just reported. If that happens,
+ we simply need to take another trip through this history
+ search. */
+ if ((dst_rev == revision) && reported)
+ retry = TRUE;
+
+ *prev_history = assemble_history(fs, apr_pstrdup(retpool, path),
+ dst_rev, ! retry,
+ src_path, src_rev, retpool);
+ }
+ else
+ {
+ *prev_history = assemble_history(fs, apr_pstrdup(retpool, commit_path),
+ commit_rev, TRUE, NULL,
+ SVN_INVALID_REVNUM, retpool);
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_history_prev(svn_fs_history_t **prev_history_p,
+ svn_fs_history_t *history,
+ svn_boolean_t cross_copies,
+ apr_pool_t *pool)
+{
+ svn_fs_history_t *prev_history = NULL;
+ base_history_data_t *bhd = history->fsap_data;
+ svn_fs_t *fs = bhd->fs;
+
+ /* Special case: the root directory changes in every single
+ revision, no exceptions. And, the root can't be the target (or
+ child of a target -- duh) of a copy. So, if that's our path,
+ then we need only decrement our revision by 1, and there you go. */
+ if (strcmp(bhd->path, "/") == 0)
+ {
+ if (! bhd->is_interesting)
+ prev_history = assemble_history(fs, "/", bhd->revision,
+ 1, NULL, SVN_INVALID_REVNUM, pool);
+ else if (bhd->revision > 0)
+ prev_history = assemble_history(fs, "/", bhd->revision - 1,
+ 1, NULL, SVN_INVALID_REVNUM, pool);
+ }
+ else
+ {
+ struct history_prev_args args;
+ prev_history = history;
+
+ while (1)
+ {
+ /* Get a trail, and get to work. */
+
+ args.prev_history_p = &prev_history;
+ args.history = prev_history;
+ args.cross_copies = cross_copies;
+ args.pool = pool;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_history_prev, &args,
+ FALSE, pool));
+ if (! prev_history)
+ break;
+ bhd = prev_history->fsap_data;
+ if (bhd->is_interesting)
+ break;
+ }
+ }
+
+ *prev_history_p = prev_history;
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_history_location(const char **path,
+ svn_revnum_t *revision,
+ svn_fs_history_t *history,
+ apr_pool_t *pool)
+{
+ base_history_data_t *bhd = history->fsap_data;
+
+ *path = apr_pstrdup(pool, bhd->path);
+ *revision = bhd->revision;
+ return SVN_NO_ERROR;
+}
+
+
+static history_vtable_t history_vtable = {
+ base_history_prev,
+ base_history_location
+};
+
+
+
+struct closest_copy_args
+{
+ svn_fs_root_t **root_p;
+ const char **path_p;
+ svn_fs_root_t *root;
+ const char *path;
+ apr_pool_t *pool;
+};
+
+
+static svn_error_t *
+txn_body_closest_copy(void *baton, trail_t *trail)
+{
+ struct closest_copy_args *args = baton;
+ svn_fs_root_t *root = args->root;
+ const char *path = args->path;
+ svn_fs_t *fs = root->fs;
+ parent_path_t *parent_path;
+ const svn_fs_id_t *node_id;
+ const char *txn_id, *copy_id;
+ copy_t *copy = NULL;
+ svn_fs_root_t *copy_dst_root;
+ dag_node_t *path_node_in_copy_dst, *copy_dst_node, *copy_dst_root_node;
+ const char *copy_dst_path;
+ svn_revnum_t copy_dst_rev, created_rev;
+ svn_error_t *err;
+
+ *(args->path_p) = NULL;
+ *(args->root_p) = NULL;
+
+ /* Get the transaction ID associated with our root. */
+ if (root->is_txn_root)
+ txn_id = root->txn;
+ else
+ SVN_ERR(svn_fs_base__rev_get_txn_id(&txn_id, fs, root->rev,
+ trail, trail->pool));
+
+ /* Open PATH in ROOT -- it must exist. */
+ SVN_ERR(open_path(&parent_path, root, path, 0, txn_id,
+ trail, trail->pool));
+ node_id = svn_fs_base__dag_get_id(parent_path->node);
+
+ /* Now, examine the copy inheritance rules in play should our path
+ be made mutable in the future (if it isn't already). This will
+ tell us about the youngest affecting copy. */
+ SVN_ERR(examine_copy_inheritance(&copy_id, &copy, fs, parent_path,
+ trail, trail->pool));
+
+ /* Easy out: if the copy ID is 0, there's nothing of interest here. */
+ if (((copy_id)[0] == '0') && ((copy_id)[1] == '\0'))
+ return SVN_NO_ERROR;
+
+ /* Fetch our copy if examine_copy_inheritance() didn't do it for us. */
+ if (! copy)
+ SVN_ERR(svn_fs_bdb__get_copy(&copy, fs, copy_id, trail, trail->pool));
+
+ /* Figure out the destination path and revision of the copy operation. */
+ SVN_ERR(svn_fs_base__dag_get_node(&copy_dst_node, fs, copy->dst_noderev_id,
+ trail, trail->pool));
+ copy_dst_path = svn_fs_base__dag_get_created_path(copy_dst_node);
+ SVN_ERR(svn_fs_base__dag_get_revision(&copy_dst_rev, copy_dst_node,
+ trail, trail->pool));
+
+ /* Turn that revision into a revision root. */
+ SVN_ERR(svn_fs_base__dag_revision_root(&copy_dst_root_node, fs,
+ copy_dst_rev, trail, args->pool));
+ copy_dst_root = make_revision_root(fs, copy_dst_rev,
+ copy_dst_root_node, args->pool);
+
+ /* It is possible that this node was created from scratch at some
+ revision between COPY_DST_REV and the transaction associated with
+ our ROOT. Make sure that PATH exists as of COPY_DST_REV and is
+ related to this node-rev. */
+ err = get_dag(&path_node_in_copy_dst, copy_dst_root, path,
+ trail, trail->pool);
+ if (err)
+ {
+ if ((err->apr_err == SVN_ERR_FS_NOT_FOUND)
+ || (err->apr_err == SVN_ERR_FS_NOT_DIRECTORY))
+ {
+ svn_error_clear(err);
+ return SVN_NO_ERROR;
+ }
+ return svn_error_trace(err);
+ }
+ if ((svn_fs_base__dag_node_kind(path_node_in_copy_dst) == svn_node_none)
+ || (! (svn_fs_base__id_check_related
+ (node_id, svn_fs_base__dag_get_id(path_node_in_copy_dst)))))
+ {
+ return SVN_NO_ERROR;
+ }
+
+ /* One final check must be done here. If you copy a directory and
+ create a new entity somewhere beneath that directory in the same
+ txn, then we can't claim that the copy affected the new entity.
+ For example, if you do:
+
+ copy dir1 dir2
+ create dir2/new-thing
+ commit
+
+ then dir2/new-thing was not affected by the copy of dir1 to dir2.
+ We detect this situation by asking if PATH@COPY_DST_REV's
+ created-rev is COPY_DST_REV, and that node-revision has no
+ predecessors, then there is no relevant closest copy.
+ */
+ SVN_ERR(svn_fs_base__dag_get_revision(&created_rev, path_node_in_copy_dst,
+ trail, trail->pool));
+ if (created_rev == copy_dst_rev)
+ {
+ const svn_fs_id_t *pred_id;
+ SVN_ERR(svn_fs_base__dag_get_predecessor_id(&pred_id,
+ path_node_in_copy_dst,
+ trail, trail->pool));
+ if (! pred_id)
+ return SVN_NO_ERROR;
+ }
+
+ *(args->path_p) = apr_pstrdup(args->pool, copy_dst_path);
+ *(args->root_p) = copy_dst_root;
+
+ return SVN_NO_ERROR;
+}
+
+
+static svn_error_t *
+base_closest_copy(svn_fs_root_t **root_p,
+ const char **path_p,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ struct closest_copy_args args;
+ svn_fs_t *fs = root->fs;
+ svn_fs_root_t *closest_root = NULL;
+ const char *closest_path = NULL;
+
+ args.root_p = &closest_root;
+ args.path_p = &closest_path;
+ args.root = root;
+ args.path = path;
+ args.pool = pool;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_closest_copy, &args,
+ FALSE, pool));
+ *root_p = closest_root;
+ *path_p = closest_path;
+ return SVN_NO_ERROR;
+}
+
+
+/* Return a new history object (marked as "interesting") for PATH and
+ REVISION, allocated in POOL, and with its members set to the values
+ of the parameters provided. Note that PATH and PATH_HINT are not
+ duped into POOL -- it is the responsibility of the caller to ensure
+ that this happens. */
+static svn_fs_history_t *
+assemble_history(svn_fs_t *fs,
+ const char *path,
+ svn_revnum_t revision,
+ svn_boolean_t is_interesting,
+ const char *path_hint,
+ svn_revnum_t rev_hint,
+ apr_pool_t *pool)
+{
+ svn_fs_history_t *history = apr_pcalloc(pool, sizeof(*history));
+ base_history_data_t *bhd = apr_pcalloc(pool, sizeof(*bhd));
+ bhd->path = path;
+ bhd->revision = revision;
+ bhd->is_interesting = is_interesting;
+ bhd->path_hint = path_hint;
+ bhd->rev_hint = rev_hint;
+ bhd->fs = fs;
+ history->vtable = &history_vtable;
+ history->fsap_data = bhd;
+ return history;
+}
+
+
+svn_error_t *
+svn_fs_base__get_path_kind(svn_node_kind_t *kind,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_revnum_t head_rev;
+ svn_fs_root_t *root;
+ dag_node_t *root_dir, *path_node;
+ svn_error_t *err;
+
+ /* Get HEAD revision, */
+ SVN_ERR(svn_fs_bdb__youngest_rev(&head_rev, trail->fs, trail, pool));
+
+ /* Then convert it into a root_t, */
+ SVN_ERR(svn_fs_base__dag_revision_root(&root_dir, trail->fs, head_rev,
+ trail, pool));
+ root = make_revision_root(trail->fs, head_rev, root_dir, pool);
+
+ /* And get the dag_node for path in the root_t. */
+ err = get_dag(&path_node, root, path, trail, pool);
+ if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND))
+ {
+ svn_error_clear(err);
+ *kind = svn_node_none;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ *kind = svn_fs_base__dag_node_kind(path_node);
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__get_path_created_rev(svn_revnum_t *rev,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool)
+{
+ svn_revnum_t head_rev, created_rev;
+ svn_fs_root_t *root;
+ dag_node_t *root_dir, *path_node;
+ svn_error_t *err;
+
+ /* Get HEAD revision, */
+ SVN_ERR(svn_fs_bdb__youngest_rev(&head_rev, trail->fs, trail, pool));
+
+ /* Then convert it into a root_t, */
+ SVN_ERR(svn_fs_base__dag_revision_root(&root_dir, trail->fs, head_rev,
+ trail, pool));
+ root = make_revision_root(trail->fs, head_rev, root_dir, pool);
+
+ /* And get the dag_node for path in the root_t. */
+ err = get_dag(&path_node, root, path, trail, pool);
+ if (err && (err->apr_err == SVN_ERR_FS_NOT_FOUND))
+ {
+ svn_error_clear(err);
+ *rev = SVN_INVALID_REVNUM;
+ return SVN_NO_ERROR;
+ }
+ else if (err)
+ return svn_error_trace(err);
+
+ /* Find the created_rev of the dag_node. */
+ SVN_ERR(svn_fs_base__dag_get_revision(&created_rev, path_node,
+ trail, pool));
+
+ *rev = created_rev;
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Finding the Origin of a Line of History ***/
+
+/* Set *PREV_PATH and *PREV_REV to the path and revision which
+ represent the location at which PATH in FS was located immediately
+ prior to REVISION iff there was a copy operation (to PATH or one of
+ its parent directories) between that previous location and
+ PATH@REVISION.
+
+ If there was no such copy operation in that portion of PATH's
+ history, set *PREV_PATH to NULL and *PREV_REV to SVN_INVALID_REVNUM.
+
+ WARNING: Do *not* call this from inside a trail. */
+static svn_error_t *
+prev_location(const char **prev_path,
+ svn_revnum_t *prev_rev,
+ svn_fs_t *fs,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ const char *copy_path, *copy_src_path, *remainder;
+ svn_fs_root_t *copy_root;
+ svn_revnum_t copy_src_rev;
+
+ /* Ask about the most recent copy which affected PATH@REVISION. If
+ there was no such copy, we're done. */
+ SVN_ERR(base_closest_copy(&copy_root, &copy_path, root, path, pool));
+ if (! copy_root)
+ {
+ *prev_rev = SVN_INVALID_REVNUM;
+ *prev_path = NULL;
+ return SVN_NO_ERROR;
+ }
+
+ /* Ultimately, it's not the path of the closest copy's source that
+ we care about -- it's our own path's location in the copy source
+ revision. So we'll tack the relative path that expresses the
+ difference between the copy destination and our path in the copy
+ revision onto the copy source path to determine this information.
+
+ In other words, if our path is "/branches/my-branch/foo/bar", and
+ we know that the closest relevant copy was a copy of "/trunk" to
+ "/branches/my-branch", then that relative path under the copy
+ destination is "/foo/bar". Tacking that onto the copy source
+ path tells us that our path was located at "/trunk/foo/bar"
+ before the copy.
+ */
+ SVN_ERR(base_copied_from(&copy_src_rev, &copy_src_path,
+ copy_root, copy_path, pool));
+ remainder = svn_fspath__skip_ancestor(copy_path, path);
+ *prev_path = svn_fspath__join(copy_src_path, remainder, pool);
+ *prev_rev = copy_src_rev;
+ return SVN_NO_ERROR;
+}
+
+
+struct id_created_rev_args {
+ svn_revnum_t revision;
+ const svn_fs_id_t *id;
+ const char *path;
+};
+
+
+static svn_error_t *
+txn_body_id_created_rev(void *baton, trail_t *trail)
+{
+ struct id_created_rev_args *args = baton;
+ dag_node_t *node;
+
+ SVN_ERR(svn_fs_base__dag_get_node(&node, trail->fs, args->id,
+ trail, trail->pool));
+ return svn_fs_base__dag_get_revision(&(args->revision), node,
+ trail, trail->pool);
+}
+
+
+struct get_set_node_origin_args {
+ const svn_fs_id_t *origin_id;
+ const char *node_id;
+};
+
+
+static svn_error_t *
+txn_body_get_node_origin(void *baton, trail_t *trail)
+{
+ struct get_set_node_origin_args *args = baton;
+ return svn_fs_bdb__get_node_origin(&(args->origin_id), trail->fs,
+ args->node_id, trail, trail->pool);
+}
+
+static svn_error_t *
+txn_body_set_node_origin(void *baton, trail_t *trail)
+{
+ struct get_set_node_origin_args *args = baton;
+ return svn_fs_bdb__set_node_origin(trail->fs, args->node_id,
+ args->origin_id, trail, trail->pool);
+}
+
+static svn_error_t *
+base_node_origin_rev(svn_revnum_t *revision,
+ svn_fs_root_t *root,
+ const char *path,
+ apr_pool_t *pool)
+{
+ svn_fs_t *fs = root->fs;
+ base_fs_data_t *bfd = fs->fsap_data;
+ struct get_set_node_origin_args args;
+ const svn_fs_id_t *origin_id = NULL;
+ struct id_created_rev_args icr_args;
+
+ /* Canonicalize the input path so that the path-math that
+ prev_location() does below will work. */
+ path = svn_fs__canonicalize_abspath(path, pool);
+
+ /* If we have support for the node-origins table, we'll try to use
+ it. */
+ if (bfd->format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT)
+ {
+ const svn_fs_id_t *id;
+ svn_error_t *err;
+
+ SVN_ERR(base_node_id(&id, root, path, pool));
+ args.node_id = svn_fs_base__id_node_id(id);
+ err = svn_fs_base__retry_txn(root->fs, txn_body_get_node_origin, &args,
+ FALSE, pool);
+
+ /* If we got a value for the origin node-revision-ID, that's
+ great. If we didn't, that's sad but non-fatal -- we'll just
+ figure it out the hard way, then record it so we don't have
+ suffer again the next time. */
+ if (! err)
+ {
+ origin_id = args.origin_id;
+ }
+ else if (err->apr_err == SVN_ERR_FS_NO_SUCH_NODE_ORIGIN)
+ {
+ svn_error_clear(err);
+ err = SVN_NO_ERROR;
+ }
+ SVN_ERR(err);
+ }
+
+ /* If we haven't yet found a node origin ID, we'll go spelunking for one. */
+ if (! origin_id)
+ {
+ svn_fs_root_t *curroot = root;
+ apr_pool_t *subpool = svn_pool_create(pool);
+ apr_pool_t *predidpool = svn_pool_create(pool);
+ svn_stringbuf_t *lastpath =
+ svn_stringbuf_create(path, pool);
+ svn_revnum_t lastrev = SVN_INVALID_REVNUM;
+ const svn_fs_id_t *pred_id;
+
+ /* Walk the closest-copy chain back to the first copy in our history.
+
+ NOTE: We merely *assume* that this is faster than walking the
+ predecessor chain, because we *assume* that copies of parent
+ directories happen less often than modifications to a given item. */
+ while (1)
+ {
+ svn_revnum_t currev;
+ const char *curpath = lastpath->data;
+
+ /* Get a root pointing to LASTREV. (The first time around,
+ LASTREV is invalid, but that's cool because CURROOT is
+ already initialized.) */
+ if (SVN_IS_VALID_REVNUM(lastrev))
+ SVN_ERR(svn_fs_base__revision_root(&curroot, fs,
+ lastrev, subpool));
+
+ /* Find the previous location using the closest-copy shortcut. */
+ SVN_ERR(prev_location(&curpath, &currev, fs, curroot,
+ curpath, subpool));
+ if (! curpath)
+ break;
+
+ /* Update our LASTPATH and LASTREV variables (which survive
+ SUBPOOL). */
+ svn_stringbuf_set(lastpath, curpath);
+ lastrev = currev;
+ }
+
+ /* Walk the predecessor links back to origin. */
+ SVN_ERR(base_node_id(&pred_id, curroot, lastpath->data, pool));
+ while (1)
+ {
+ struct txn_pred_id_args pid_args;
+ svn_pool_clear(subpool);
+ pid_args.id = pred_id;
+ pid_args.pred_id = NULL;
+ pid_args.pool = subpool;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_pred_id, &pid_args,
+ FALSE, subpool));
+ if (! pid_args.pred_id)
+ break;
+ svn_pool_clear(predidpool);
+ pred_id = svn_fs_base__id_copy(pid_args.pred_id, predidpool);
+ }
+
+ /* Okay. PRED_ID should hold our origin ID now. */
+ origin_id = svn_fs_base__id_copy(pred_id, pool);
+
+ /* If our filesystem version supports it, let's remember this
+ value from now on. */
+ if (bfd->format >= SVN_FS_BASE__MIN_NODE_ORIGINS_FORMAT)
+ {
+ args.origin_id = origin_id;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_set_node_origin,
+ &args, TRUE, subpool));
+ }
+
+ svn_pool_destroy(predidpool);
+ svn_pool_destroy(subpool);
+ }
+
+ /* Okay. We have an origin node-revision-ID. Let's get a created
+ revision from it. */
+ icr_args.id = origin_id;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs, txn_body_id_created_rev, &icr_args,
+ TRUE, pool));
+ *revision = icr_args.revision;
+ return SVN_NO_ERROR;
+}
+
+
+
+/* Mergeinfo Queries */
+
+
+/* Examine directory NODE's immediately children for mergeinfo.
+
+ For those which have explicit mergeinfo, add their mergeinfo to
+ RESULT_CATALOG (allocated in RESULT_CATALOG's pool).
+
+ For those which don't, but sit atop trees which contain mergeinfo
+ somewhere deeper, add them to *CHILDREN_ATOP_MERGEINFO_TREES, a
+ hash mapping dirent names to dag_node_t * objects, allocated
+ from that hash's pool.
+
+ For those which neither have explicit mergeinfo nor sit atop trees
+ which contain mergeinfo, ignore them.
+
+ Use TRAIL->pool for temporary allocations. */
+
+struct get_mergeinfo_data_and_entries_baton
+{
+ svn_mergeinfo_catalog_t result_catalog;
+ apr_hash_t *children_atop_mergeinfo_trees;
+ dag_node_t *node;
+ const char *node_path;
+};
+
+static svn_error_t *
+txn_body_get_mergeinfo_data_and_entries(void *baton, trail_t *trail)
+{
+ struct get_mergeinfo_data_and_entries_baton *args = baton;
+ dag_node_t *node = args->node;
+ apr_hash_t *entries;
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool = svn_pool_create(trail->pool);
+ apr_pool_t *result_pool = apr_hash_pool_get(args->result_catalog);
+ apr_pool_t *children_pool =
+ apr_hash_pool_get(args->children_atop_mergeinfo_trees);
+
+ SVN_ERR_ASSERT(svn_fs_base__dag_node_kind(node) == svn_node_dir);
+
+ SVN_ERR(svn_fs_base__dag_dir_entries(&entries, node, trail, trail->pool));
+ for (hi = apr_hash_first(trail->pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ void *val;
+ svn_fs_dirent_t *dirent;
+ const svn_fs_id_t *child_id;
+ dag_node_t *child_node;
+ svn_boolean_t has_mergeinfo;
+ apr_int64_t kid_count;
+
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, NULL, NULL, &val);
+ dirent = val;
+ child_id = dirent->id;
+
+ /* Get the node for this child. */
+ SVN_ERR(svn_fs_base__dag_get_node(&child_node, trail->fs, child_id,
+ trail, iterpool));
+
+ /* Query the child node's mergeinfo stats. */
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(&has_mergeinfo, &kid_count,
+ child_node, trail,
+ iterpool));
+
+ /* If the child has mergeinfo, add it to the result catalog. */
+ if (has_mergeinfo)
+ {
+ apr_hash_t *plist;
+ svn_mergeinfo_t child_mergeinfo;
+ svn_string_t *pval;
+ svn_error_t *err;
+
+ SVN_ERR(svn_fs_base__dag_get_proplist(&plist, child_node,
+ trail, iterpool));
+ pval = svn_hash_gets(plist, SVN_PROP_MERGEINFO);
+ if (! pval)
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(child_id,
+ iterpool);
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Node-revision '%s' claims to have "
+ "mergeinfo but doesn't"),
+ id_str->data);
+ }
+ /* Issue #3896: If syntactically invalid mergeinfo is present on
+ CHILD_NODE then treat it as if no mergeinfo is present rather
+ than raising a parse error. */
+ err = svn_mergeinfo_parse(&child_mergeinfo, pval->data,
+ result_pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ svn_error_clear(err);
+ else
+ return svn_error_trace(err);
+ }
+ else
+ {
+ svn_hash_sets(args->result_catalog,
+ svn_fspath__join(args->node_path, dirent->name,
+ result_pool),
+ child_mergeinfo);
+ }
+ }
+
+ /* If the child has descendants with mergeinfo -- that is, if
+ the count of descendants beneath it carrying mergeinfo, not
+ including itself, is non-zero -- then add it to the
+ children_atop_mergeinfo_trees hash to be crawled later. */
+ if ((kid_count - (has_mergeinfo ? 1 : 0)) > 0)
+ {
+ if (svn_fs_base__dag_node_kind(child_node) != svn_node_dir)
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(child_id,
+ iterpool);
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Node-revision '%s' claims to sit "
+ "atop a tree containing mergeinfo "
+ "but is not a directory"),
+ id_str->data);
+ }
+ svn_hash_sets(args->children_atop_mergeinfo_trees,
+ apr_pstrdup(children_pool, dirent->name),
+ svn_fs_base__dag_dup(child_node, children_pool));
+ }
+ }
+
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+static svn_error_t *
+crawl_directory_for_mergeinfo(svn_fs_t *fs,
+ dag_node_t *node,
+ const char *node_path,
+ svn_mergeinfo_catalog_t result_catalog,
+ apr_pool_t *pool)
+{
+ struct get_mergeinfo_data_and_entries_baton gmdae_args;
+ apr_hash_t *children_atop_mergeinfo_trees = apr_hash_make(pool);
+ apr_hash_index_t *hi;
+ apr_pool_t *iterpool;
+
+ /* Add mergeinfo for immediate children that have it, and fetch
+ immediate children that *don't* have it but sit atop trees that do. */
+ gmdae_args.result_catalog = result_catalog;
+ gmdae_args.children_atop_mergeinfo_trees = children_atop_mergeinfo_trees;
+ gmdae_args.node = node;
+ gmdae_args.node_path = node_path;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_mergeinfo_data_and_entries,
+ &gmdae_args, FALSE, pool));
+
+ /* If no children sit atop trees with mergeinfo, we're done.
+ Otherwise, recurse on those children. */
+
+ if (apr_hash_count(children_atop_mergeinfo_trees) == 0)
+ return SVN_NO_ERROR;
+
+ iterpool = svn_pool_create(pool);
+ for (hi = apr_hash_first(pool, children_atop_mergeinfo_trees);
+ hi;
+ hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ svn_pool_clear(iterpool);
+ apr_hash_this(hi, &key, NULL, &val);
+ SVN_ERR(crawl_directory_for_mergeinfo(fs, val,
+ svn_fspath__join(node_path, key,
+ iterpool),
+ result_catalog, iterpool));
+ }
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+}
+
+
+/* Calculate the mergeinfo for PATH under revision ROOT using
+ inheritance type INHERIT. Set *MERGEINFO to the mergeinfo, or to
+ NULL if there is none. Results are allocated in POOL; TRAIL->pool
+ is used for temporary allocations. */
+
+struct get_mergeinfo_for_path_baton
+{
+ svn_mergeinfo_t *mergeinfo;
+ svn_fs_root_t *root;
+ const char *path;
+ svn_mergeinfo_inheritance_t inherit;
+ svn_boolean_t adjust_inherited_mergeinfo;
+ apr_pool_t *pool;
+};
+
+static svn_error_t *
+txn_body_get_mergeinfo_for_path(void *baton, trail_t *trail)
+{
+ struct get_mergeinfo_for_path_baton *args = baton;
+ parent_path_t *parent_path, *nearest_ancestor;
+ apr_hash_t *proplist;
+ svn_string_t *mergeinfo_string;
+ apr_pool_t *iterpool;
+ dag_node_t *node = NULL;
+
+ *(args->mergeinfo) = NULL;
+
+ SVN_ERR(open_path(&parent_path, args->root, args->path, 0,
+ NULL, trail, trail->pool));
+
+ /* Init the nearest ancestor. */
+ nearest_ancestor = parent_path;
+ if (args->inherit == svn_mergeinfo_nearest_ancestor)
+ {
+ if (! parent_path->parent)
+ return SVN_NO_ERROR;
+ nearest_ancestor = parent_path->parent;
+ }
+
+ iterpool = svn_pool_create(trail->pool);
+ while (TRUE)
+ {
+ svn_boolean_t has_mergeinfo;
+ apr_int64_t count;
+
+ svn_pool_clear(iterpool);
+
+ node = nearest_ancestor->node;
+ SVN_ERR(svn_fs_base__dag_get_mergeinfo_stats(&has_mergeinfo, &count,
+ node, trail, iterpool));
+ if (has_mergeinfo)
+ break;
+
+ /* No need to loop if we're looking for explicit mergeinfo. */
+ if (args->inherit == svn_mergeinfo_explicit)
+ {
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+
+ nearest_ancestor = nearest_ancestor->parent;
+
+ /* Run out? There's no mergeinfo. */
+ if (! nearest_ancestor)
+ {
+ svn_pool_destroy(iterpool);
+ return SVN_NO_ERROR;
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ SVN_ERR(svn_fs_base__dag_get_proplist(&proplist, node, trail, trail->pool));
+ mergeinfo_string = svn_hash_gets(proplist, SVN_PROP_MERGEINFO);
+ if (! mergeinfo_string)
+ {
+ svn_string_t *id_str =
+ svn_fs_base__id_unparse(svn_fs_base__dag_get_id(node), trail->pool);
+ return svn_error_createf(SVN_ERR_FS_CORRUPT, NULL,
+ _("Node-revision '%s' claims to have "
+ "mergeinfo but doesn't"), id_str->data);
+ }
+
+ /* Parse the mergeinfo; store the result in ARGS->MERGEINFO. */
+ {
+ /* Issue #3896: If a node has syntactically invalid mergeinfo, then
+ treat it as if no mergeinfo is present rather than raising a parse
+ error. */
+ svn_error_t *err = svn_mergeinfo_parse(args->mergeinfo,
+ mergeinfo_string->data,
+ args->pool);
+ if (err)
+ {
+ if (err->apr_err == SVN_ERR_MERGEINFO_PARSE_ERROR)
+ {
+ svn_error_clear(err);
+ err = NULL;
+ args->mergeinfo = NULL;
+ }
+ return svn_error_trace(err);
+ }
+ }
+
+ /* If our nearest ancestor is the very path we inquired about, we
+ can return the mergeinfo results directly. Otherwise, we're
+ inheriting the mergeinfo, so we need to a) remove non-inheritable
+ ranges and b) telescope the merged-from paths. */
+ if (args->adjust_inherited_mergeinfo && (nearest_ancestor != parent_path))
+ {
+ svn_mergeinfo_t tmp_mergeinfo;
+
+ SVN_ERR(svn_mergeinfo_inheritable2(&tmp_mergeinfo, *args->mergeinfo,
+ NULL, SVN_INVALID_REVNUM,
+ SVN_INVALID_REVNUM, TRUE,
+ trail->pool, trail->pool));
+ SVN_ERR(svn_fs__append_to_merged_froms(args->mergeinfo, tmp_mergeinfo,
+ parent_path_relpath(
+ parent_path, nearest_ancestor,
+ trail->pool),
+ args->pool));
+ }
+
+ return SVN_NO_ERROR;
+}
+
+/* Set **NODE to the dag node for PATH in ROOT (allocated in POOL),
+ and query its mergeinfo stats, setting HAS_MERGEINFO and
+ CHILD_MERGEINFO_COUNT appropriately. */
+
+struct get_node_mergeinfo_stats_baton
+{
+ dag_node_t *node;
+ svn_boolean_t has_mergeinfo;
+ apr_int64_t child_mergeinfo_count;
+ svn_fs_root_t *root;
+ const char *path;
+};
+
+static svn_error_t *
+txn_body_get_node_mergeinfo_stats(void *baton, trail_t *trail)
+{
+ struct get_node_mergeinfo_stats_baton *args = baton;
+
+ SVN_ERR(get_dag(&(args->node), args->root, args->path,
+ trail, trail->pool));
+ return svn_fs_base__dag_get_mergeinfo_stats(&(args->has_mergeinfo),
+ &(args->child_mergeinfo_count),
+ args->node, trail,
+ trail->pool);
+}
+
+
+/* Get the mergeinfo for a set of paths, returned in
+ *MERGEINFO_CATALOG. Returned values are allocated in POOL, while
+ temporary values are allocated in a sub-pool. */
+static svn_error_t *
+get_mergeinfos_for_paths(svn_fs_root_t *root,
+ svn_mergeinfo_catalog_t *mergeinfo_catalog,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ svn_mergeinfo_catalog_t result_catalog = apr_hash_make(result_pool);
+ apr_pool_t *iterpool = svn_pool_create(scratch_pool);
+ int i;
+
+ for (i = 0; i < paths->nelts; i++)
+ {
+ svn_mergeinfo_t path_mergeinfo;
+ struct get_mergeinfo_for_path_baton gmfp_args;
+ const char *path = APR_ARRAY_IDX(paths, i, const char *);
+
+ svn_pool_clear(iterpool);
+
+ path = svn_fs__canonicalize_abspath(path, iterpool);
+
+ /* Get the mergeinfo for PATH itself. */
+ gmfp_args.mergeinfo = &path_mergeinfo;
+ gmfp_args.root = root;
+ gmfp_args.path = path;
+ gmfp_args.inherit = inherit;
+ gmfp_args.pool = result_pool;
+ gmfp_args.adjust_inherited_mergeinfo = adjust_inherited_mergeinfo;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs,
+ txn_body_get_mergeinfo_for_path,
+ &gmfp_args, FALSE, iterpool));
+ if (path_mergeinfo)
+ svn_hash_sets(result_catalog, apr_pstrdup(result_pool, path),
+ path_mergeinfo);
+
+ /* If we're including descendants, do so. */
+ if (include_descendants)
+ {
+ svn_boolean_t do_crawl;
+ struct get_node_mergeinfo_stats_baton gnms_args;
+
+ /* Query the node and its mergeinfo stats. */
+ gnms_args.root = root;
+ gnms_args.path = path;
+ SVN_ERR(svn_fs_base__retry_txn(root->fs,
+ txn_body_get_node_mergeinfo_stats,
+ &gnms_args, FALSE, iterpool));
+
+ /* Determine if there's anything worth crawling here. */
+ if (svn_fs_base__dag_node_kind(gnms_args.node) != svn_node_dir)
+ do_crawl = FALSE;
+ else
+ do_crawl = ((gnms_args.child_mergeinfo_count > 1)
+ || ((gnms_args.child_mergeinfo_count == 1)
+ && (! gnms_args.has_mergeinfo)));
+
+ /* If it's worth crawling, crawl. */
+ if (do_crawl)
+ SVN_ERR(crawl_directory_for_mergeinfo(root->fs, gnms_args.node,
+ path, result_catalog,
+ iterpool));
+ }
+ }
+ svn_pool_destroy(iterpool);
+
+ *mergeinfo_catalog = result_catalog;
+ return SVN_NO_ERROR;
+}
+
+
+/* Implements svn_fs_get_mergeinfo. */
+static svn_error_t *
+base_get_mergeinfo(svn_mergeinfo_catalog_t *catalog,
+ svn_fs_root_t *root,
+ const apr_array_header_t *paths,
+ svn_mergeinfo_inheritance_t inherit,
+ svn_boolean_t include_descendants,
+ svn_boolean_t adjust_inherited_mergeinfo,
+ apr_pool_t *result_pool,
+ apr_pool_t *scratch_pool)
+{
+ /* Verify that our filesystem version supports mergeinfo stuff. */
+ SVN_ERR(svn_fs_base__test_required_feature_format
+ (root->fs, "mergeinfo", SVN_FS_BASE__MIN_MERGEINFO_FORMAT));
+
+ /* We require a revision root. */
+ if (root->is_txn_root)
+ return svn_error_create(SVN_ERR_FS_NOT_REVISION_ROOT, NULL, NULL);
+
+ /* Retrieve a path -> mergeinfo mapping. */
+ return get_mergeinfos_for_paths(root, catalog, paths,
+ inherit, include_descendants,
+ adjust_inherited_mergeinfo,
+ result_pool, scratch_pool);
+}
+
+
+
+/* Creating root objects. */
+
+
+static root_vtable_t root_vtable = {
+ base_paths_changed,
+ base_check_path,
+ base_node_history,
+ base_node_id,
+ base_node_created_rev,
+ base_node_origin_rev,
+ base_node_created_path,
+ base_delete_node,
+ base_copied_from,
+ base_closest_copy,
+ base_node_prop,
+ base_node_proplist,
+ base_change_node_prop,
+ base_props_changed,
+ base_dir_entries,
+ base_make_dir,
+ base_copy,
+ base_revision_link,
+ base_file_length,
+ base_file_checksum,
+ base_file_contents,
+ NULL,
+ base_make_file,
+ base_apply_textdelta,
+ base_apply_text,
+ base_contents_changed,
+ base_get_file_delta_stream,
+ base_merge,
+ base_get_mergeinfo,
+};
+
+
+/* Construct a new root object in FS, allocated from POOL. */
+static svn_fs_root_t *
+make_root(svn_fs_t *fs,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root = apr_pcalloc(pool, sizeof(*root));
+ base_root_data_t *brd = apr_palloc(pool, sizeof(*brd));
+
+ root->fs = fs;
+ root->pool = pool;
+
+ /* Init the node ID cache. */
+ brd->node_cache = apr_hash_make(pool);
+ brd->node_cache_idx = 0;
+ root->vtable = &root_vtable;
+ root->fsap_data = brd;
+
+ return root;
+}
+
+
+/* Construct a root object referring to the root of REVISION in FS,
+ whose root directory is ROOT_DIR. Create the new root in POOL. */
+static svn_fs_root_t *
+make_revision_root(svn_fs_t *fs,
+ svn_revnum_t rev,
+ dag_node_t *root_dir,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root = make_root(fs, pool);
+ base_root_data_t *brd = root->fsap_data;
+
+ root->is_txn_root = FALSE;
+ root->rev = rev;
+ brd->root_dir = root_dir;
+
+ return root;
+}
+
+
+/* Construct a root object referring to the root of the transaction
+ named TXN and based on revision BASE_REV in FS. FLAGS represents
+ the behavior of the transaction. Create the new root in POOL. */
+static svn_fs_root_t *
+make_txn_root(svn_fs_t *fs,
+ const char *txn,
+ svn_revnum_t base_rev,
+ apr_uint32_t flags,
+ apr_pool_t *pool)
+{
+ svn_fs_root_t *root = make_root(fs, pool);
+ root->is_txn_root = TRUE;
+ root->txn = apr_pstrdup(root->pool, txn);
+ root->txn_flags = flags;
+ root->rev = base_rev;
+
+ return root;
+}
diff --git a/subversion/libsvn_fs_base/tree.h b/subversion/libsvn_fs_base/tree.h
new file mode 100644
index 0000000..2e81a17
--- /dev/null
+++ b/subversion/libsvn_fs_base/tree.h
@@ -0,0 +1,99 @@
+/* tree.h : internal interface to tree node functions
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_TREE_H
+#define SVN_LIBSVN_FS_TREE_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+#include "svn_props.h"
+
+
+
+/* These functions implement some of the calls in the FS loader
+ library's fs and txn vtables. */
+
+svn_error_t *svn_fs_base__revision_root(svn_fs_root_t **root_p, svn_fs_t *fs,
+ svn_revnum_t rev, apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__deltify(svn_fs_t *fs, svn_revnum_t rev,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__commit_txn(const char **conflict_p,
+ svn_revnum_t *new_rev, svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+svn_error_t *svn_fs_base__txn_root(svn_fs_root_t **root_p, svn_fs_txn_t *txn,
+ apr_pool_t *pool);
+
+
+
+/* Inserting and retrieving miscellany records in the fs */
+
+/* Set the value of miscellaneous records KEY to VAL in FS. To remove
+ a value altogether, pass NULL for VAL.
+
+ KEY and VAL should be NULL-terminated strings. */
+svn_error_t *
+svn_fs_base__miscellaneous_set(svn_fs_t *fs,
+ const char *key,
+ const char *val,
+ apr_pool_t *pool);
+
+/* Retrieve the miscellany records for KEY into *VAL for FS, allocated
+ in POOL. If the fs doesn't support miscellany storage, or the value
+ does not exist, *VAL is set to NULL.
+
+ KEY should be a NULL-terminated string. */
+svn_error_t *
+svn_fs_base__miscellaneous_get(const char **val,
+ svn_fs_t *fs,
+ const char *key,
+ apr_pool_t *pool);
+
+
+
+
+
+/* Helper func: in the context of TRAIL, return the KIND of PATH in
+ head revision. If PATH doesn't exist, set *KIND to svn_node_none.*/
+svn_error_t *svn_fs_base__get_path_kind(svn_node_kind_t *kind,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+/* Helper func: in the context of TRAIL, set *REV to the created-rev
+ of PATH in head revision. If PATH doesn't exist, set *REV to
+ SVN_INVALID_REVNUM. */
+svn_error_t *svn_fs_base__get_path_created_rev(svn_revnum_t *rev,
+ const char *path,
+ trail_t *trail,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_TREE_H */
diff --git a/subversion/libsvn_fs_base/util/fs_skels.c b/subversion/libsvn_fs_base/util/fs_skels.c
new file mode 100644
index 0000000..f3466b9
--- /dev/null
+++ b/subversion/libsvn_fs_base/util/fs_skels.c
@@ -0,0 +1,1515 @@
+/* fs_skels.c --- conversion between fs native types and skeletons
+ *
+ * ====================================================================
+ * 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_md5.h>
+#include <apr_sha1.h>
+
+#include "svn_error.h"
+#include "svn_string.h"
+#include "svn_types.h"
+#include "svn_time.h"
+
+#include "private/svn_skel.h"
+#include "private/svn_dep_compat.h"
+#include "private/svn_subr_private.h"
+
+#include "svn_checksum.h"
+#include "fs_skels.h"
+#include "../id.h"
+
+
+static svn_error_t *
+skel_err(const char *skel_type)
+{
+ return svn_error_createf(SVN_ERR_FS_MALFORMED_SKEL, NULL,
+ "Malformed%s%s skeleton",
+ skel_type ? " " : "",
+ skel_type ? skel_type : "");
+}
+
+
+
+/*** Validity Checking ***/
+
+static svn_boolean_t
+is_valid_checksum_skel(svn_skel_t *skel)
+{
+ if (svn_skel__list_length(skel) != 2)
+ return FALSE;
+
+ if (svn_skel__matches_atom(skel->children, "md5")
+ && skel->children->next->is_atom)
+ return TRUE;
+
+ if (svn_skel__matches_atom(skel->children, "sha1")
+ && skel->children->next->is_atom)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+static svn_boolean_t
+is_valid_revision_skel(svn_skel_t *skel)
+{
+ int len = svn_skel__list_length(skel);
+
+ if ((len == 2)
+ && svn_skel__matches_atom(skel->children, "revision")
+ && skel->children->next->is_atom)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+static svn_boolean_t
+is_valid_transaction_skel(svn_skel_t *skel, transaction_kind_t *kind)
+{
+ int len = svn_skel__list_length(skel);
+
+ if (len != 5)
+ return FALSE;
+
+ /* Determine (and verify) the kind. */
+ if (svn_skel__matches_atom(skel->children, "transaction"))
+ *kind = transaction_kind_normal;
+ else if (svn_skel__matches_atom(skel->children, "committed"))
+ *kind = transaction_kind_committed;
+ else if (svn_skel__matches_atom(skel->children, "dead"))
+ *kind = transaction_kind_dead;
+ else
+ return FALSE;
+
+ if (skel->children->next->is_atom
+ && skel->children->next->next->is_atom
+ && (! skel->children->next->next->next->is_atom)
+ && (! skel->children->next->next->next->next->is_atom))
+ return TRUE;
+
+ return FALSE;
+}
+
+
+static svn_boolean_t
+is_valid_rep_delta_chunk_skel(svn_skel_t *skel)
+{
+ int len;
+ svn_skel_t *window;
+ svn_skel_t *diff;
+
+ /* check the delta skel. */
+ if ((svn_skel__list_length(skel) != 2)
+ || (! skel->children->is_atom))
+ return FALSE;
+
+ /* check the window. */
+ window = skel->children->next;
+ len = svn_skel__list_length(window);
+ if ((len < 3) || (len > 4))
+ return FALSE;
+ if (! ((! window->children->is_atom)
+ && (window->children->next->is_atom)
+ && (window->children->next->next->is_atom)))
+ return FALSE;
+ if ((len == 4)
+ && (! window->children->next->next->next->is_atom))
+ return FALSE;
+
+ /* check the diff. ### currently we support only svndiff version
+ 0 delta data. */
+ diff = window->children;
+ if ((svn_skel__list_length(diff) == 3)
+ && (svn_skel__matches_atom(diff->children, "svndiff"))
+ && ((svn_skel__matches_atom(diff->children->next, "0"))
+ || (svn_skel__matches_atom(diff->children->next, "1")))
+ && (diff->children->next->next->is_atom))
+ return TRUE;
+
+ return FALSE;
+}
+
+
+static svn_boolean_t
+is_valid_representation_skel(svn_skel_t *skel)
+{
+ int len = svn_skel__list_length(skel);
+ svn_skel_t *header;
+ int header_len;
+
+ /* the rep has at least two items in it, a HEADER list, and at least
+ one piece of kind-specific data. */
+ if (len < 2)
+ return FALSE;
+
+ /* check the header. it must have KIND and TXN atoms, and
+ optionally 1 or 2 checksums (which is a list form). */
+ header = skel->children;
+ header_len = svn_skel__list_length(header);
+ if (! (((header_len == 2) /* 2 means old repository, checksum absent */
+ && (header->children->is_atom)
+ && (header->children->next->is_atom))
+ || ((header_len == 3) /* 3 means md5 checksum present */
+ && (header->children->is_atom)
+ && (header->children->next->is_atom)
+ && (is_valid_checksum_skel(header->children->next->next)))
+ || ((header_len == 4) /* 3 means md5 and sha1 checksums present */
+ && (header->children->is_atom)
+ && (header->children->next->is_atom)
+ && (is_valid_checksum_skel(header->children->next->next))
+ && (is_valid_checksum_skel(header->children->next->next->next)))))
+ return FALSE;
+
+ /* check for fulltext rep. */
+ if ((len == 2)
+ && (svn_skel__matches_atom(header->children, "fulltext")))
+ return TRUE;
+
+ /* check for delta rep. */
+ if ((len >= 2)
+ && (svn_skel__matches_atom(header->children, "delta")))
+ {
+ /* it's a delta rep. check the validity. */
+ svn_skel_t *chunk = skel->children->next;
+
+ /* loop over chunks, checking each one. */
+ while (chunk)
+ {
+ if (! is_valid_rep_delta_chunk_skel(chunk))
+ return FALSE;
+ chunk = chunk->next;
+ }
+
+ /* all good on this delta rep. */
+ return TRUE;
+ }
+
+ return FALSE;
+}
+
+
+static svn_boolean_t
+is_valid_node_revision_header_skel(svn_skel_t *skel, svn_skel_t **kind_p)
+{
+ int len = svn_skel__list_length(skel);
+
+ if (len < 2)
+ return FALSE;
+
+ /* set the *KIND_P pointer. */
+ *kind_p = skel->children;
+
+ /* check for valid lengths. */
+ if (! ((len == 2) || (len == 3) || (len == 4) || (len == 6)))
+ return FALSE;
+
+ /* got mergeinfo stuff? */
+ if ((len > 4)
+ && (! (skel->children->next->next->next->next->is_atom
+ && skel->children->next->next->next->next->next->is_atom)))
+ return FALSE;
+
+ /* got predecessor count? */
+ if ((len > 3)
+ && (! skel->children->next->next->next->is_atom))
+ return FALSE;
+
+ /* got predecessor? */
+ if ((len > 2)
+ && (! skel->children->next->next->is_atom))
+ return FALSE;
+
+ /* got the basics? */
+ if (! (skel->children->is_atom
+ && skel->children->next->is_atom
+ && (skel->children->next->data[0] == '/')))
+ return FALSE;
+
+ return TRUE;
+}
+
+
+static svn_boolean_t
+is_valid_node_revision_skel(svn_skel_t *skel)
+{
+ int len = svn_skel__list_length(skel);
+ svn_skel_t *header = skel->children;
+ svn_skel_t *kind;
+
+ if (len < 1)
+ return FALSE;
+
+ if (! is_valid_node_revision_header_skel(header, &kind))
+ return FALSE;
+
+ if (svn_skel__matches_atom(kind, "dir"))
+ {
+ if (! ((len == 3)
+ && header->next->is_atom
+ && header->next->next->is_atom))
+ return FALSE;
+ }
+ else if (svn_skel__matches_atom(kind, "file"))
+ {
+ if (len < 3)
+ return FALSE;
+
+ if (! header->next->is_atom)
+ return FALSE;
+
+ /* As of SVN_FS_BASE__MIN_REP_SHARING_FORMAT version, the
+ DATA-KEY slot can be a 2-tuple. */
+ if (! header->next->next->is_atom)
+ {
+ if (! ((svn_skel__list_length(header->next->next) == 2)
+ && header->next->next->children->is_atom
+ && header->next->next->children->len
+ && header->next->next->children->next->is_atom
+ && header->next->next->children->next->len))
+ return FALSE;
+ }
+
+ if ((len > 3) && (! header->next->next->next->is_atom))
+ return FALSE;
+
+ if (len > 4)
+ return FALSE;
+ }
+
+ return TRUE;
+}
+
+
+static svn_boolean_t
+is_valid_copy_skel(svn_skel_t *skel)
+{
+ return ((svn_skel__list_length(skel) == 4)
+ && (svn_skel__matches_atom(skel->children, "copy")
+ || svn_skel__matches_atom(skel->children, "soft-copy"))
+ && skel->children->next->is_atom
+ && skel->children->next->next->is_atom
+ && skel->children->next->next->next->is_atom);
+}
+
+
+static svn_boolean_t
+is_valid_change_skel(svn_skel_t *skel, svn_fs_path_change_kind_t *kind)
+{
+ if ((svn_skel__list_length(skel) == 6)
+ && svn_skel__matches_atom(skel->children, "change")
+ && skel->children->next->is_atom
+ && skel->children->next->next->is_atom
+ && skel->children->next->next->next->is_atom
+ && skel->children->next->next->next->next->is_atom
+ && skel->children->next->next->next->next->next->is_atom)
+ {
+ svn_skel_t *kind_skel = skel->children->next->next->next;
+
+ /* check the kind (and return it) */
+ if (svn_skel__matches_atom(kind_skel, "reset"))
+ {
+ if (kind)
+ *kind = svn_fs_path_change_reset;
+ return TRUE;
+ }
+ if (svn_skel__matches_atom(kind_skel, "add"))
+ {
+ if (kind)
+ *kind = svn_fs_path_change_add;
+ return TRUE;
+ }
+ if (svn_skel__matches_atom(kind_skel, "delete"))
+ {
+ if (kind)
+ *kind = svn_fs_path_change_delete;
+ return TRUE;
+ }
+ if (svn_skel__matches_atom(kind_skel, "replace"))
+ {
+ if (kind)
+ *kind = svn_fs_path_change_replace;
+ return TRUE;
+ }
+ if (svn_skel__matches_atom(kind_skel, "modify"))
+ {
+ if (kind)
+ *kind = svn_fs_path_change_modify;
+ return TRUE;
+ }
+ }
+ return FALSE;
+}
+
+
+static svn_boolean_t
+is_valid_lock_skel(svn_skel_t *skel)
+{
+ if ((svn_skel__list_length(skel) == 8)
+ && svn_skel__matches_atom(skel->children, "lock")
+ && skel->children->next->is_atom
+ && skel->children->next->next->is_atom
+ && skel->children->next->next->next->is_atom
+ && skel->children->next->next->next->next->is_atom
+ && skel->children->next->next->next->next->next->is_atom
+ && skel->children->next->next->next->next->next->next->is_atom
+ && skel->children->next->next->next->next->next->next->next->is_atom)
+ return TRUE;
+
+ return FALSE;
+}
+
+
+
+/*** Parsing (conversion from skeleton to native FS type) ***/
+
+svn_error_t *
+svn_fs_base__parse_revision_skel(revision_t **revision_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ revision_t *revision;
+
+ /* Validate the skel. */
+ if (! is_valid_revision_skel(skel))
+ return skel_err("revision");
+
+ /* Create the returned structure */
+ revision = apr_pcalloc(pool, sizeof(*revision));
+ revision->txn_id = apr_pstrmemdup(pool, skel->children->next->data,
+ skel->children->next->len);
+
+ /* Return the structure. */
+ *revision_p = revision;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__parse_transaction_skel(transaction_t **transaction_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ transaction_t *transaction;
+ transaction_kind_t kind;
+ svn_skel_t *root_id, *base_id_or_rev, *proplist, *copies;
+ int len;
+
+ /* Validate the skel. */
+ if (! is_valid_transaction_skel(skel, &kind))
+ return skel_err("transaction");
+
+ root_id = skel->children->next;
+ base_id_or_rev = skel->children->next->next;
+ proplist = skel->children->next->next->next;
+ copies = skel->children->next->next->next->next;
+
+ /* Create the returned structure */
+ transaction = apr_pcalloc(pool, sizeof(*transaction));
+
+ /* KIND */
+ transaction->kind = kind;
+
+ /* REVISION or BASE-ID */
+ if (kind == transaction_kind_committed)
+ {
+ /* Committed transactions have a revision number... */
+ transaction->base_id = NULL;
+ transaction->revision =
+ SVN_STR_TO_REV(apr_pstrmemdup(pool, base_id_or_rev->data,
+ base_id_or_rev->len));
+ if (! SVN_IS_VALID_REVNUM(transaction->revision))
+ return skel_err("transaction");
+
+ }
+ else
+ {
+ /* ...where unfinished transactions have a base node-revision-id. */
+ transaction->revision = SVN_INVALID_REVNUM;
+ transaction->base_id = svn_fs_base__id_parse(base_id_or_rev->data,
+ base_id_or_rev->len, pool);
+ }
+
+ /* ROOT-ID */
+ transaction->root_id = svn_fs_base__id_parse(root_id->data,
+ root_id->len, pool);
+
+ /* PROPLIST */
+ SVN_ERR(svn_skel__parse_proplist(&(transaction->proplist),
+ proplist, pool));
+
+ /* COPIES */
+ if ((len = svn_skel__list_length(copies)))
+ {
+ const char *copy_id;
+ apr_array_header_t *txncopies;
+ svn_skel_t *cpy = copies->children;
+
+ txncopies = apr_array_make(pool, len, sizeof(copy_id));
+ while (cpy)
+ {
+ copy_id = apr_pstrmemdup(pool, cpy->data, cpy->len);
+ APR_ARRAY_PUSH(txncopies, const char *) = copy_id;
+ cpy = cpy->next;
+ }
+ transaction->copies = txncopies;
+ }
+
+ /* Return the structure. */
+ *transaction_p = transaction;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__parse_representation_skel(representation_t **rep_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ representation_t *rep;
+ svn_skel_t *header_skel;
+
+ /* Validate the skel. */
+ if (! is_valid_representation_skel(skel))
+ return skel_err("representation");
+ header_skel = skel->children;
+
+ /* Create the returned structure */
+ rep = apr_pcalloc(pool, sizeof(*rep));
+
+ /* KIND */
+ if (svn_skel__matches_atom(header_skel->children, "fulltext"))
+ rep->kind = rep_kind_fulltext;
+ else
+ rep->kind = rep_kind_delta;
+
+ /* TXN */
+ rep->txn_id = apr_pstrmemdup(pool, header_skel->children->next->data,
+ header_skel->children->next->len);
+
+ /* MD5 */
+ if (header_skel->children->next->next)
+ {
+ svn_skel_t *checksum_skel = header_skel->children->next->next;
+ rep->md5_checksum =
+ svn_checksum__from_digest_md5((const unsigned char *)
+ (checksum_skel->children->next->data),
+ pool);
+
+ /* SHA1 */
+ if (header_skel->children->next->next->next)
+ {
+ checksum_skel = header_skel->children->next->next->next;
+ rep->sha1_checksum =
+ svn_checksum__from_digest_sha1(
+ (const unsigned char *)(checksum_skel->children->next->data),
+ pool);
+ }
+ }
+
+ /* KIND-SPECIFIC stuff */
+ if (rep->kind == rep_kind_fulltext)
+ {
+ /* "fulltext"-specific. */
+ rep->contents.fulltext.string_key
+ = apr_pstrmemdup(pool,
+ skel->children->next->data,
+ skel->children->next->len);
+ }
+ else
+ {
+ /* "delta"-specific. */
+ svn_skel_t *chunk_skel = skel->children->next;
+ rep_delta_chunk_t *chunk;
+ apr_array_header_t *chunks;
+
+ /* Alloc the chunk array. */
+ chunks = apr_array_make(pool, svn_skel__list_length(skel) - 1,
+ sizeof(chunk));
+
+ /* Process the chunks. */
+ while (chunk_skel)
+ {
+ svn_skel_t *window_skel = chunk_skel->children->next;
+ svn_skel_t *diff_skel = window_skel->children;
+ apr_int64_t val;
+ apr_uint64_t uval;
+ const char *str;
+
+ /* Allocate a chunk and its window */
+ chunk = apr_palloc(pool, sizeof(*chunk));
+
+ /* Populate the window */
+ str = apr_pstrmemdup(pool, diff_skel->children->next->data,
+ diff_skel->children->next->len);
+ SVN_ERR(svn_cstring_strtoui64(&uval, str, 0, 255, 10));
+ chunk->version = (apr_byte_t)uval;
+
+ chunk->string_key
+ = apr_pstrmemdup(pool,
+ diff_skel->children->next->next->data,
+ diff_skel->children->next->next->len);
+
+ str = apr_pstrmemdup(pool, window_skel->children->next->data,
+ window_skel->children->next->len);
+ SVN_ERR(svn_cstring_strtoui64(&uval, str, 0, APR_SIZE_MAX, 10));
+ chunk->size = (apr_size_t)uval;
+
+ chunk->rep_key
+ = apr_pstrmemdup(pool,
+ window_skel->children->next->next->data,
+ window_skel->children->next->next->len);
+
+ str = apr_pstrmemdup(pool, chunk_skel->children->data,
+ chunk_skel->children->len);
+ SVN_ERR(svn_cstring_strtoi64(&val, str, 0, APR_INT64_MAX, 10));
+ chunk->offset = (svn_filesize_t)val;
+
+ /* Add this chunk to the array. */
+ APR_ARRAY_PUSH(chunks, rep_delta_chunk_t *) = chunk;
+
+ /* Next... */
+ chunk_skel = chunk_skel->next;
+ }
+
+ /* Add the chunks array to the representation. */
+ rep->contents.delta.chunks = chunks;
+ }
+
+ /* Return the structure. */
+ *rep_p = rep;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__parse_node_revision_skel(node_revision_t **noderev_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ node_revision_t *noderev;
+ svn_skel_t *header_skel, *cur_skel;
+
+ /* Validate the skel. */
+ if (! is_valid_node_revision_skel(skel))
+ return skel_err("node-revision");
+ header_skel = skel->children;
+
+ /* Create the returned structure */
+ noderev = apr_pcalloc(pool, sizeof(*noderev));
+
+ /* KIND */
+ if (svn_skel__matches_atom(header_skel->children, "dir"))
+ noderev->kind = svn_node_dir;
+ else
+ noderev->kind = svn_node_file;
+
+ /* CREATED-PATH */
+ noderev->created_path = apr_pstrmemdup(pool,
+ header_skel->children->next->data,
+ header_skel->children->next->len);
+
+ /* PREDECESSOR-ID */
+ if (header_skel->children->next->next)
+ {
+ cur_skel = header_skel->children->next->next;
+ if (cur_skel->len)
+ noderev->predecessor_id = svn_fs_base__id_parse(cur_skel->data,
+ cur_skel->len, pool);
+
+ /* PREDECESSOR-COUNT */
+ noderev->predecessor_count = -1;
+ if (cur_skel->next)
+ {
+ const char *str;
+
+ cur_skel = cur_skel->next;
+ if (cur_skel->len)
+ {
+ str = apr_pstrmemdup(pool, cur_skel->data, cur_skel->len);
+ SVN_ERR(svn_cstring_atoi(&noderev->predecessor_count, str));
+ }
+
+ /* HAS-MERGEINFO and MERGEINFO-COUNT */
+ if (cur_skel->next)
+ {
+ int val;
+
+ cur_skel = cur_skel->next;
+ str = apr_pstrmemdup(pool, cur_skel->data, cur_skel->len);
+ SVN_ERR(svn_cstring_atoi(&val, str));
+ noderev->has_mergeinfo = (val != 0);
+
+ str = apr_pstrmemdup(pool, cur_skel->next->data,
+ cur_skel->next->len);
+ SVN_ERR(svn_cstring_atoi64(&noderev->mergeinfo_count, str));
+ }
+ }
+ }
+
+ /* PROP-KEY */
+ if (skel->children->next->len)
+ noderev->prop_key = apr_pstrmemdup(pool, skel->children->next->data,
+ skel->children->next->len);
+
+ /* DATA-KEY */
+ if (skel->children->next->next->is_atom)
+ {
+ /* This is a real data rep key. */
+ if (skel->children->next->next->len)
+ noderev->data_key = apr_pstrmemdup(pool,
+ skel->children->next->next->data,
+ skel->children->next->next->len);
+ noderev->data_key_uniquifier = NULL;
+ }
+ else
+ {
+ /* This is a 2-tuple with a data rep key and a uniquifier. */
+ noderev->data_key =
+ apr_pstrmemdup(pool,
+ skel->children->next->next->children->data,
+ skel->children->next->next->children->len);
+ noderev->data_key_uniquifier =
+ apr_pstrmemdup(pool,
+ skel->children->next->next->children->next->data,
+ skel->children->next->next->children->next->len);
+ }
+
+ /* EDIT-DATA-KEY (optional, files only) */
+ if ((noderev->kind == svn_node_file)
+ && skel->children->next->next->next
+ && skel->children->next->next->next->len)
+ noderev->edit_key
+ = apr_pstrmemdup(pool, skel->children->next->next->next->data,
+ skel->children->next->next->next->len);
+
+ /* Return the structure. */
+ *noderev_p = noderev;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__parse_copy_skel(copy_t **copy_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ copy_t *copy;
+
+ /* Validate the skel. */
+ if (! is_valid_copy_skel(skel))
+ return skel_err("copy");
+
+ /* Create the returned structure */
+ copy = apr_pcalloc(pool, sizeof(*copy));
+
+ /* KIND */
+ if (svn_skel__matches_atom(skel->children, "soft-copy"))
+ copy->kind = copy_kind_soft;
+ else
+ copy->kind = copy_kind_real;
+
+ /* SRC-PATH */
+ copy->src_path = apr_pstrmemdup(pool,
+ skel->children->next->data,
+ skel->children->next->len);
+
+ /* SRC-TXN-ID */
+ copy->src_txn_id = apr_pstrmemdup(pool,
+ skel->children->next->next->data,
+ skel->children->next->next->len);
+
+ /* DST-NODE-ID */
+ copy->dst_noderev_id
+ = svn_fs_base__id_parse(skel->children->next->next->next->data,
+ skel->children->next->next->next->len, pool);
+
+ /* Return the structure. */
+ *copy_p = copy;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__parse_entries_skel(apr_hash_t **entries_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ apr_hash_t *entries = NULL;
+ int len = svn_skel__list_length(skel);
+ svn_skel_t *elt;
+
+ if (! (len >= 0))
+ return skel_err("entries");
+
+ if (len > 0)
+ {
+ /* Else, allocate a hash and populate it. */
+ entries = apr_hash_make(pool);
+
+ /* Check entries are well-formed as we go along. */
+ for (elt = skel->children; elt; elt = elt->next)
+ {
+ const char *name;
+ svn_fs_id_t *id;
+
+ /* ENTRY must be a list of two elements. */
+ if (svn_skel__list_length(elt) != 2)
+ return skel_err("entries");
+
+ /* Get the entry's name and ID. */
+ name = apr_pstrmemdup(pool, elt->children->data,
+ elt->children->len);
+ id = svn_fs_base__id_parse(elt->children->next->data,
+ elt->children->next->len, pool);
+
+ /* Add the entry to the hash. */
+ apr_hash_set(entries, name, elt->children->len, id);
+ }
+ }
+
+ /* Return the structure. */
+ *entries_p = entries;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__parse_change_skel(change_t **change_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ change_t *change;
+ svn_fs_path_change_kind_t kind;
+
+ /* Validate the skel. */
+ if (! is_valid_change_skel(skel, &kind))
+ return skel_err("change");
+
+ /* Create the returned structure */
+ change = apr_pcalloc(pool, sizeof(*change));
+
+ /* PATH */
+ change->path = apr_pstrmemdup(pool, skel->children->next->data,
+ skel->children->next->len);
+
+ /* NODE-REV-ID */
+ if (skel->children->next->next->len)
+ change->noderev_id = svn_fs_base__id_parse
+ (skel->children->next->next->data, skel->children->next->next->len,
+ pool);
+
+ /* KIND */
+ change->kind = kind;
+
+ /* TEXT-MOD */
+ if (skel->children->next->next->next->next->len)
+ change->text_mod = TRUE;
+
+ /* PROP-MOD */
+ if (skel->children->next->next->next->next->next->len)
+ change->prop_mod = TRUE;
+
+ /* Return the structure. */
+ *change_p = change;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__parse_lock_skel(svn_lock_t **lock_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool)
+{
+ svn_lock_t *lock;
+ const char *timestr;
+
+ /* Validate the skel. */
+ if (! is_valid_lock_skel(skel))
+ return skel_err("lock");
+
+ /* Create the returned structure */
+ lock = apr_pcalloc(pool, sizeof(*lock));
+
+ /* PATH */
+ lock->path = apr_pstrmemdup(pool, skel->children->next->data,
+ skel->children->next->len);
+
+ /* LOCK-TOKEN */
+ lock->token = apr_pstrmemdup(pool,
+ skel->children->next->next->data,
+ skel->children->next->next->len);
+
+ /* OWNER */
+ lock->owner = apr_pstrmemdup(pool,
+ skel->children->next->next->next->data,
+ skel->children->next->next->next->len);
+
+ /* COMMENT (could be just an empty atom) */
+ if (skel->children->next->next->next->next->len)
+ lock->comment =
+ apr_pstrmemdup(pool,
+ skel->children->next->next->next->next->data,
+ skel->children->next->next->next->next->len);
+
+ /* XML_P */
+ if (svn_skel__matches_atom
+ (skel->children->next->next->next->next->next, "1"))
+ lock->is_dav_comment = TRUE;
+ else
+ lock->is_dav_comment = FALSE;
+
+ /* CREATION-DATE */
+ timestr = apr_pstrmemdup
+ (pool,
+ skel->children->next->next->next->next->next->next->data,
+ skel->children->next->next->next->next->next->next->len);
+ SVN_ERR(svn_time_from_cstring(&(lock->creation_date),
+ timestr, pool));
+
+ /* EXPIRATION-DATE (could be just an empty atom) */
+ if (skel->children->next->next->next->next->next->next->next->len)
+ {
+ timestr =
+ apr_pstrmemdup
+ (pool,
+ skel->children->next->next->next->next->next->next->next->data,
+ skel->children->next->next->next->next->next->next->next->len);
+ SVN_ERR(svn_time_from_cstring(&(lock->expiration_date),
+ timestr, pool));
+ }
+
+ /* Return the structure. */
+ *lock_p = lock;
+ return SVN_NO_ERROR;
+}
+
+
+
+/*** Unparsing (conversion from native FS type to skeleton) ***/
+
+svn_error_t *
+svn_fs_base__unparse_revision_skel(svn_skel_t **skel_p,
+ const revision_t *revision,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel;
+
+ /* Create the skel. */
+ skel = svn_skel__make_empty_list(pool);
+
+ /* TXN_ID */
+ svn_skel__prepend(svn_skel__str_atom(revision->txn_id, pool), skel);
+
+ /* "revision" */
+ svn_skel__prepend(svn_skel__str_atom("revision", pool), skel);
+
+ /* Validate and return the skel. */
+ if (! is_valid_revision_skel(skel))
+ return skel_err("revision");
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__unparse_transaction_skel(svn_skel_t **skel_p,
+ const transaction_t *transaction,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel;
+ svn_skel_t *proplist_skel, *copies_skel, *header_skel;
+ svn_string_t *id_str;
+ transaction_kind_t kind;
+
+ /* Create the skel. */
+ skel = svn_skel__make_empty_list(pool);
+
+ switch (transaction->kind)
+ {
+ case transaction_kind_committed:
+ header_skel = svn_skel__str_atom("committed", pool);
+ if ((transaction->base_id)
+ || (! SVN_IS_VALID_REVNUM(transaction->revision)))
+ return skel_err("transaction");
+ break;
+ case transaction_kind_dead:
+ header_skel = svn_skel__str_atom("dead", pool);
+ if ((! transaction->base_id)
+ || (SVN_IS_VALID_REVNUM(transaction->revision)))
+ return skel_err("transaction");
+ break;
+ case transaction_kind_normal:
+ header_skel = svn_skel__str_atom("transaction", pool);
+ if ((! transaction->base_id)
+ || (SVN_IS_VALID_REVNUM(transaction->revision)))
+ return skel_err("transaction");
+ break;
+ default:
+ return skel_err("transaction");
+ }
+
+
+ /* COPIES */
+ copies_skel = svn_skel__make_empty_list(pool);
+ if (transaction->copies && transaction->copies->nelts)
+ {
+ int i;
+ for (i = transaction->copies->nelts - 1; i >= 0; i--)
+ {
+ svn_skel__prepend(svn_skel__str_atom(
+ APR_ARRAY_IDX(transaction->copies, i,
+ const char *),
+ pool),
+ copies_skel);
+ }
+ }
+ svn_skel__prepend(copies_skel, skel);
+
+ /* PROPLIST */
+ SVN_ERR(svn_skel__unparse_proplist(&proplist_skel,
+ transaction->proplist, pool));
+ svn_skel__prepend(proplist_skel, skel);
+
+ /* REVISION or BASE-ID */
+ if (transaction->kind == transaction_kind_committed)
+ {
+ /* Committed transactions have a revision number... */
+ svn_skel__prepend(svn_skel__str_atom(apr_psprintf(pool, "%ld",
+ transaction->revision),
+ pool), skel);
+ }
+ else
+ {
+ /* ...where other transactions have a base node revision ID. */
+ id_str = svn_fs_base__id_unparse(transaction->base_id, pool);
+ svn_skel__prepend(svn_skel__mem_atom(id_str->data, id_str->len, pool),
+ skel);
+ }
+
+ /* ROOT-ID */
+ id_str = svn_fs_base__id_unparse(transaction->root_id, pool);
+ svn_skel__prepend(svn_skel__mem_atom(id_str->data, id_str->len, pool), skel);
+
+ /* KIND (see above) */
+ svn_skel__prepend(header_skel, skel);
+
+ /* Validate and return the skel. */
+ if (! is_valid_transaction_skel(skel, &kind))
+ return skel_err("transaction");
+ if (kind != transaction->kind)
+ return skel_err("transaction");
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+
+/* Construct a skel representing CHECKSUM, allocated in POOL, and prepend
+ * it onto the existing skel SKEL. */
+static svn_error_t *
+prepend_checksum(svn_skel_t *skel,
+ svn_checksum_t *checksum,
+ apr_pool_t *pool)
+{
+ svn_skel_t *checksum_skel = svn_skel__make_empty_list(pool);
+
+ switch (checksum->kind)
+ {
+ case svn_checksum_md5:
+ svn_skel__prepend(svn_skel__mem_atom(checksum->digest,
+ APR_MD5_DIGESTSIZE, pool),
+ checksum_skel);
+ svn_skel__prepend(svn_skel__str_atom("md5", pool), checksum_skel);
+ break;
+
+ case svn_checksum_sha1:
+ svn_skel__prepend(svn_skel__mem_atom(checksum->digest,
+ APR_SHA1_DIGESTSIZE, pool),
+ checksum_skel);
+ svn_skel__prepend(svn_skel__str_atom("sha1", pool), checksum_skel);
+ break;
+
+ default:
+ return skel_err("checksum");
+ }
+ svn_skel__prepend(checksum_skel, skel);
+
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__unparse_representation_skel(svn_skel_t **skel_p,
+ const representation_t *rep,
+ int format,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel = svn_skel__make_empty_list(pool);
+ svn_skel_t *header_skel = svn_skel__make_empty_list(pool);
+
+ /** Some parts of the header are common to all representations; do
+ those parts first. **/
+
+ /* SHA1 */
+ if ((format >= SVN_FS_BASE__MIN_REP_SHARING_FORMAT) && rep->sha1_checksum)
+ SVN_ERR(prepend_checksum(header_skel, rep->sha1_checksum, pool));
+
+ /* MD5 */
+ {
+ svn_checksum_t *md5_checksum = rep->md5_checksum;
+ if (! md5_checksum)
+ md5_checksum = svn_checksum_create(svn_checksum_md5, pool);
+ SVN_ERR(prepend_checksum(header_skel, md5_checksum, pool));
+ }
+
+ /* TXN */
+ if (rep->txn_id)
+ svn_skel__prepend(svn_skel__str_atom(rep->txn_id, pool), header_skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), header_skel);
+
+ /** Do the kind-specific stuff. **/
+
+ if (rep->kind == rep_kind_fulltext)
+ {
+ /*** Fulltext Representation. ***/
+
+ /* STRING-KEY */
+ if ((! rep->contents.fulltext.string_key)
+ || (! *rep->contents.fulltext.string_key))
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+ else
+ svn_skel__prepend(svn_skel__str_atom(rep->contents.fulltext.string_key,
+ pool), skel);
+
+ /* "fulltext" */
+ svn_skel__prepend(svn_skel__str_atom("fulltext", pool), header_skel);
+
+ /* header */
+ svn_skel__prepend(header_skel, skel);
+ }
+ else if (rep->kind == rep_kind_delta)
+ {
+ /*** Delta Representation. ***/
+ int i;
+ apr_array_header_t *chunks = rep->contents.delta.chunks;
+
+ /* Loop backwards through the windows, creating and prepending skels. */
+ for (i = chunks->nelts; i > 0; i--)
+ {
+ svn_skel_t *window_skel = svn_skel__make_empty_list(pool);
+ svn_skel_t *chunk_skel = svn_skel__make_empty_list(pool);
+ svn_skel_t *diff_skel = svn_skel__make_empty_list(pool);
+ const char *size_str, *offset_str, *version_str;
+ rep_delta_chunk_t *chunk = APR_ARRAY_IDX(chunks, i - 1,
+ rep_delta_chunk_t *);
+
+ /* OFFSET */
+ offset_str = apr_psprintf(pool, "%" SVN_FILESIZE_T_FMT,
+ chunk->offset);
+
+ /* SIZE */
+ size_str = apr_psprintf(pool, "%" APR_SIZE_T_FMT, chunk->size);
+
+ /* VERSION */
+ version_str = apr_psprintf(pool, "%d", chunk->version);
+
+ /* DIFF */
+ if ((! chunk->string_key) || (! *chunk->string_key))
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), diff_skel);
+ else
+ svn_skel__prepend(svn_skel__str_atom(chunk->string_key, pool),
+ diff_skel);
+ svn_skel__prepend(svn_skel__str_atom(version_str, pool), diff_skel);
+ svn_skel__prepend(svn_skel__str_atom("svndiff", pool), diff_skel);
+
+ /* REP-KEY */
+ if ((! chunk->rep_key) || (! *(chunk->rep_key)))
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool),
+ window_skel);
+ else
+ svn_skel__prepend(svn_skel__str_atom(chunk->rep_key, pool),
+ window_skel);
+ svn_skel__prepend(svn_skel__str_atom(size_str, pool), window_skel);
+ svn_skel__prepend(diff_skel, window_skel);
+
+ /* window header. */
+ svn_skel__prepend(window_skel, chunk_skel);
+ svn_skel__prepend(svn_skel__str_atom(offset_str, pool),
+ chunk_skel);
+
+ /* Add this window item to the main skel. */
+ svn_skel__prepend(chunk_skel, skel);
+ }
+
+ /* "delta" */
+ svn_skel__prepend(svn_skel__str_atom("delta", pool), header_skel);
+
+ /* header */
+ svn_skel__prepend(header_skel, skel);
+ }
+ else /* unknown kind */
+ SVN_ERR_MALFUNCTION();
+
+ /* Validate and return the skel. */
+ if (! is_valid_representation_skel(skel))
+ return skel_err("representation");
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__unparse_node_revision_skel(svn_skel_t **skel_p,
+ const node_revision_t *noderev,
+ int format,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel;
+ svn_skel_t *header_skel;
+ const char *num_str;
+
+ /* Create the skel. */
+ skel = svn_skel__make_empty_list(pool);
+ header_skel = svn_skel__make_empty_list(pool);
+
+ /* Store mergeinfo stuffs only if the schema level supports it. */
+ if (format >= SVN_FS_BASE__MIN_MERGEINFO_FORMAT)
+ {
+ /* MERGEINFO-COUNT */
+ num_str = apr_psprintf(pool, "%" APR_INT64_T_FMT,
+ noderev->mergeinfo_count);
+ svn_skel__prepend(svn_skel__str_atom(num_str, pool), header_skel);
+
+ /* HAS-MERGEINFO */
+ svn_skel__prepend(svn_skel__mem_atom(noderev->has_mergeinfo ? "1" : "0",
+ 1, pool), header_skel);
+
+ /* PREDECESSOR-COUNT padding (only if we *don't* have a valid
+ value; if we do, we'll pick that up below) */
+ if (noderev->predecessor_count == -1)
+ {
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), header_skel);
+ }
+ }
+
+ /* PREDECESSOR-COUNT */
+ if (noderev->predecessor_count != -1)
+ {
+ const char *count_str = apr_psprintf(pool, "%d",
+ noderev->predecessor_count);
+ svn_skel__prepend(svn_skel__str_atom(count_str, pool), header_skel);
+ }
+
+ /* PREDECESSOR-ID */
+ if (noderev->predecessor_id)
+ {
+ svn_string_t *id_str = svn_fs_base__id_unparse(noderev->predecessor_id,
+ pool);
+ svn_skel__prepend(svn_skel__mem_atom(id_str->data, id_str->len, pool),
+ header_skel);
+ }
+ else
+ {
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), header_skel);
+ }
+
+ /* CREATED-PATH */
+ svn_skel__prepend(svn_skel__str_atom(noderev->created_path, pool),
+ header_skel);
+
+ /* KIND */
+ if (noderev->kind == svn_node_file)
+ svn_skel__prepend(svn_skel__str_atom("file", pool), header_skel);
+ else if (noderev->kind == svn_node_dir)
+ svn_skel__prepend(svn_skel__str_atom("dir", pool), header_skel);
+ else
+ SVN_ERR_MALFUNCTION();
+
+ /* ### do we really need to check *node->FOO_key ? if a key doesn't
+ ### exist, then the field should be NULL ... */
+
+ /* EDIT-DATA-KEY (optional) */
+ if ((noderev->edit_key) && (*noderev->edit_key))
+ svn_skel__prepend(svn_skel__str_atom(noderev->edit_key, pool), skel);
+
+ /* DATA-KEY | (DATA-KEY DATA-KEY-UNIQID) */
+ if ((noderev->data_key_uniquifier) && (*noderev->data_key_uniquifier))
+ {
+ /* Build a 2-tuple with a rep key and uniquifier. */
+ svn_skel_t *data_key_skel = svn_skel__make_empty_list(pool);
+
+ /* DATA-KEY-UNIQID */
+ svn_skel__prepend(svn_skel__str_atom(noderev->data_key_uniquifier,
+ pool),
+ data_key_skel);
+
+ /* DATA-KEY */
+ if ((noderev->data_key) && (*noderev->data_key))
+ svn_skel__prepend(svn_skel__str_atom(noderev->data_key, pool),
+ data_key_skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), data_key_skel);
+
+ /* Add our 2-tuple to the main skel. */
+ svn_skel__prepend(data_key_skel, skel);
+ }
+ else
+ {
+ /* Just store the rep key (or empty placeholder) in the main skel. */
+ if ((noderev->data_key) && (*noderev->data_key))
+ svn_skel__prepend(svn_skel__str_atom(noderev->data_key, pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+ }
+
+ /* PROP-KEY */
+ if ((noderev->prop_key) && (*noderev->prop_key))
+ svn_skel__prepend(svn_skel__str_atom(noderev->prop_key, pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+
+ /* HEADER */
+ svn_skel__prepend(header_skel, skel);
+
+ /* Validate and return the skel. */
+ if (! is_valid_node_revision_skel(skel))
+ return skel_err("node-revision");
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__unparse_copy_skel(svn_skel_t **skel_p,
+ const copy_t *copy,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel;
+ svn_string_t *tmp_str;
+
+ /* Create the skel. */
+ skel = svn_skel__make_empty_list(pool);
+
+ /* DST-NODE-ID */
+ tmp_str = svn_fs_base__id_unparse(copy->dst_noderev_id, pool);
+ svn_skel__prepend(svn_skel__mem_atom(tmp_str->data, tmp_str->len, pool),
+ skel);
+
+ /* SRC-TXN-ID */
+ if ((copy->src_txn_id) && (*copy->src_txn_id))
+ svn_skel__prepend(svn_skel__str_atom(copy->src_txn_id, pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+
+ /* SRC-PATH */
+ if ((copy->src_path) && (*copy->src_path))
+ svn_skel__prepend(svn_skel__str_atom(copy->src_path, pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+
+ /* "copy" */
+ if (copy->kind == copy_kind_real)
+ svn_skel__prepend(svn_skel__str_atom("copy", pool), skel);
+ else
+ svn_skel__prepend(svn_skel__str_atom("soft-copy", pool), skel);
+
+ /* Validate and return the skel. */
+ if (! is_valid_copy_skel(skel))
+ return skel_err("copy");
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__unparse_entries_skel(svn_skel_t **skel_p,
+ apr_hash_t *entries,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel = svn_skel__make_empty_list(pool);
+ apr_hash_index_t *hi;
+
+ /* Create the skel. */
+ if (entries)
+ {
+ /* Loop over hash entries */
+ for (hi = apr_hash_first(pool, entries); hi; hi = apr_hash_next(hi))
+ {
+ const void *key;
+ void *val;
+ apr_ssize_t klen;
+ svn_fs_id_t *value;
+ svn_string_t *id_str;
+ svn_skel_t *entry_skel = svn_skel__make_empty_list(pool);
+
+ apr_hash_this(hi, &key, &klen, &val);
+ value = val;
+
+ /* VALUE */
+ id_str = svn_fs_base__id_unparse(value, pool);
+ svn_skel__prepend(svn_skel__mem_atom(id_str->data, id_str->len,
+ pool),
+ entry_skel);
+
+ /* NAME */
+ svn_skel__prepend(svn_skel__mem_atom(key, klen, pool), entry_skel);
+
+ /* Add entry to the entries skel. */
+ svn_skel__prepend(entry_skel, skel);
+ }
+ }
+
+ /* Return the skel. */
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__unparse_change_skel(svn_skel_t **skel_p,
+ const change_t *change,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel;
+ svn_string_t *tmp_str;
+ svn_fs_path_change_kind_t kind;
+
+ /* Create the skel. */
+ skel = svn_skel__make_empty_list(pool);
+
+ /* PROP-MOD */
+ if (change->prop_mod)
+ svn_skel__prepend(svn_skel__str_atom("1", pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+
+ /* TEXT-MOD */
+ if (change->text_mod)
+ svn_skel__prepend(svn_skel__str_atom("1", pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+
+ /* KIND */
+ switch (change->kind)
+ {
+ case svn_fs_path_change_reset:
+ svn_skel__prepend(svn_skel__str_atom("reset", pool), skel);
+ break;
+ case svn_fs_path_change_add:
+ svn_skel__prepend(svn_skel__str_atom("add", pool), skel);
+ break;
+ case svn_fs_path_change_delete:
+ svn_skel__prepend(svn_skel__str_atom("delete", pool), skel);
+ break;
+ case svn_fs_path_change_replace:
+ svn_skel__prepend(svn_skel__str_atom("replace", pool), skel);
+ break;
+ case svn_fs_path_change_modify:
+ default:
+ svn_skel__prepend(svn_skel__str_atom("modify", pool), skel);
+ break;
+ }
+
+ /* NODE-REV-ID */
+ if (change->noderev_id)
+ {
+ tmp_str = svn_fs_base__id_unparse(change->noderev_id, pool);
+ svn_skel__prepend(svn_skel__mem_atom(tmp_str->data, tmp_str->len, pool),
+ skel);
+ }
+ else
+ {
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+ }
+
+ /* PATH */
+ svn_skel__prepend(svn_skel__str_atom(change->path, pool), skel);
+
+ /* "change" */
+ svn_skel__prepend(svn_skel__str_atom("change", pool), skel);
+
+ /* Validate and return the skel. */
+ if (! is_valid_change_skel(skel, &kind))
+ return skel_err("change");
+ if (kind != change->kind)
+ return skel_err("change");
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
+
+
+svn_error_t *
+svn_fs_base__unparse_lock_skel(svn_skel_t **skel_p,
+ const svn_lock_t *lock,
+ apr_pool_t *pool)
+{
+ svn_skel_t *skel;
+
+ /* Create the skel. */
+ skel = svn_skel__make_empty_list(pool);
+
+ /* EXP-DATE is optional. If not present, just use an empty atom. */
+ if (lock->expiration_date)
+ svn_skel__prepend(svn_skel__str_atom(
+ svn_time_to_cstring(lock->expiration_date, pool),
+ pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+
+ /* CREATION-DATE */
+ svn_skel__prepend(svn_skel__str_atom(
+ svn_time_to_cstring(lock->creation_date, pool),
+ pool), skel);
+
+ /* XML_P */
+ if (lock->is_dav_comment)
+ svn_skel__prepend(svn_skel__str_atom("1", pool), skel);
+ else
+ svn_skel__prepend(svn_skel__str_atom("0", pool), skel);
+
+ /* COMMENT */
+ if (lock->comment)
+ svn_skel__prepend(svn_skel__str_atom(lock->comment, pool), skel);
+ else
+ svn_skel__prepend(svn_skel__mem_atom(NULL, 0, pool), skel);
+
+ /* OWNER */
+ svn_skel__prepend(svn_skel__str_atom(lock->owner, pool), skel);
+
+ /* LOCK-TOKEN */
+ svn_skel__prepend(svn_skel__str_atom(lock->token, pool), skel);
+
+ /* PATH */
+ svn_skel__prepend(svn_skel__str_atom(lock->path, pool), skel);
+
+ /* "lock" */
+ svn_skel__prepend(svn_skel__str_atom("lock", pool), skel);
+
+ /* Validate and return the skel. */
+ if (! is_valid_lock_skel(skel))
+ return skel_err("lock");
+
+ *skel_p = skel;
+ return SVN_NO_ERROR;
+}
diff --git a/subversion/libsvn_fs_base/util/fs_skels.h b/subversion/libsvn_fs_base/util/fs_skels.h
new file mode 100644
index 0000000..63dab80
--- /dev/null
+++ b/subversion/libsvn_fs_base/util/fs_skels.h
@@ -0,0 +1,177 @@
+/* fs_skels.h : headers for conversion between fs native types and
+ * skeletons
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_FS_SKELS_H
+#define SVN_LIBSVN_FS_FS_SKELS_H
+
+#define SVN_WANT_BDB
+#include "svn_private_config.h"
+
+#include <apr_pools.h>
+#include <apr_hash.h>
+
+#include "svn_fs.h"
+#include "../fs.h"
+#include "private/svn_skel.h"
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+/*** Parsing (conversion from skeleton to native FS type) ***/
+
+
+/* Parse a `REVISION' SKEL and set *REVISION_P to the newly allocated
+ result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__parse_revision_skel(revision_t **revision_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+/* Parse a `TRANSACTION' SKEL and set *TRANSACTION_P to the newly allocated
+ result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__parse_transaction_skel(transaction_t **transaction_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+/* Parse a `REPRESENTATION' SKEL and set *REP_P to the newly allocated
+ result. Use POOL for all allocations. */
+
+svn_error_t *
+svn_fs_base__parse_representation_skel(representation_t **rep_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+/* Parse a `NODE-REVISION' SKEL and set *NODEREV_P to the newly allocated
+ result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__parse_node_revision_skel(node_revision_t **noderev_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+/* Parse a `COPY' SKEL and set *COPY_P to the newly allocated result. Use
+ POOL for all allocations. */
+svn_error_t *
+svn_fs_base__parse_copy_skel(copy_t **copy_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+/* Parse an `ENTRIES' SKEL and set *ENTRIES_P to a new hash with const
+ char * names (the directory entry name) and svn_fs_id_t * values
+ (the node-id of the entry), or NULL if SKEL contains no entries.
+ Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__parse_entries_skel(apr_hash_t **entries_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+/* Parse a `CHANGE' SKEL and set *CHANGE_P to the newly allocated result.
+ Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__parse_change_skel(change_t **change_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+/* Parse a `LOCK' SKEL and set *LOCK_P to the newly allocated result. Use
+ POOL for all allocations. */
+svn_error_t *
+svn_fs_base__parse_lock_skel(svn_lock_t **lock_p,
+ svn_skel_t *skel,
+ apr_pool_t *pool);
+
+
+
+/*** Unparsing (conversion from native FS type to skeleton) ***/
+
+
+/* Unparse REVISION into a newly allocated `REVISION' skel and set *SKEL_P
+ to the result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__unparse_revision_skel(svn_skel_t **skel_p,
+ const revision_t *revision,
+ apr_pool_t *pool);
+
+/* Unparse TRANSACTION into a newly allocated `TRANSACTION' skel and set
+ *SKEL_P to the result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__unparse_transaction_skel(svn_skel_t **skel_p,
+ const transaction_t *transaction,
+ apr_pool_t *pool);
+
+/* Unparse REP into a newly allocated `REPRESENTATION' skel and set *SKEL_P
+ to the result. Use POOL for all allocations. FORMAT is the format
+ version of the filesystem. */
+svn_error_t *
+svn_fs_base__unparse_representation_skel(svn_skel_t **skel_p,
+ const representation_t *rep,
+ int format,
+ apr_pool_t *pool);
+
+/* Unparse NODEREV into a newly allocated `NODE-REVISION' skel and set
+ *SKEL_P to the result. Use POOL for all allocations. FORMAT is the
+ format version of the filesystem. */
+svn_error_t *
+svn_fs_base__unparse_node_revision_skel(svn_skel_t **skel_p,
+ const node_revision_t *noderev,
+ int format,
+ apr_pool_t *pool);
+
+/* Unparse COPY into a newly allocated `COPY' skel and set *SKEL_P to the
+ result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__unparse_copy_skel(svn_skel_t **skel_p,
+ const copy_t *copy,
+ apr_pool_t *pool);
+
+/* Unparse an ENTRIES hash, which has const char * names (the entry
+ name) and svn_fs_id_t * values (the node-id of the entry) into a newly
+ allocated `ENTRIES' skel and set *SKEL_P to the result. Use POOL for all
+ allocations. */
+svn_error_t *
+svn_fs_base__unparse_entries_skel(svn_skel_t **skel_p,
+ apr_hash_t *entries,
+ apr_pool_t *pool);
+
+/* Unparse CHANGE into a newly allocated `CHANGE' skel and set *SKEL_P to
+ the result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__unparse_change_skel(svn_skel_t **skel_p,
+ const change_t *change,
+ apr_pool_t *pool);
+
+/* Unparse LOCK into a newly allocated `LOCK' skel and set *SKEL_P to the
+ result. Use POOL for all allocations. */
+svn_error_t *
+svn_fs_base__unparse_lock_skel(svn_skel_t **skel_p,
+ const svn_lock_t *lock,
+ apr_pool_t *pool);
+
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_FS_SKELS_H */
diff --git a/subversion/libsvn_fs_base/uuid.c b/subversion/libsvn_fs_base/uuid.c
new file mode 100644
index 0000000..c865df3
--- /dev/null
+++ b/subversion/libsvn_fs_base/uuid.c
@@ -0,0 +1,116 @@
+/* uuid.c : operations on repository uuids
+ *
+ * ====================================================================
+ * 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 "svn_pools.h"
+#include "fs.h"
+#include "trail.h"
+#include "err.h"
+#include "uuid.h"
+#include "bdb/uuids-table.h"
+#include "../libsvn_fs/fs-loader.h"
+
+#include "private/svn_fs_util.h"
+
+
+struct get_uuid_args
+{
+ int idx;
+ const char **uuid;
+};
+
+
+static svn_error_t *
+txn_body_get_uuid(void *baton, trail_t *trail)
+{
+ struct get_uuid_args *args = baton;
+ return svn_fs_bdb__get_uuid(trail->fs, args->idx, args->uuid,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__populate_uuid(svn_fs_t *fs,
+ apr_pool_t *scratch_pool)
+{
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ /* We hit the database. */
+ {
+ const char *uuid;
+ struct get_uuid_args args;
+
+ args.idx = 1;
+ args.uuid = &uuid;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_get_uuid, &args,
+ FALSE, scratch_pool));
+
+ if (uuid)
+ {
+ /* Toss what we find into the cache. */
+ fs->uuid = apr_pstrdup(fs->pool, uuid);
+ }
+ }
+
+ return SVN_NO_ERROR;
+}
+
+
+struct set_uuid_args
+{
+ int idx;
+ const char *uuid;
+};
+
+
+static svn_error_t *
+txn_body_set_uuid(void *baton, trail_t *trail)
+{
+ struct set_uuid_args *args = baton;
+ return svn_fs_bdb__set_uuid(trail->fs, args->idx, args->uuid,
+ trail, trail->pool);
+}
+
+
+svn_error_t *
+svn_fs_base__set_uuid(svn_fs_t *fs,
+ const char *uuid,
+ apr_pool_t *pool)
+{
+ struct set_uuid_args args;
+
+ SVN_ERR(svn_fs__check_fs(fs, TRUE));
+
+ if (! uuid)
+ uuid = svn_uuid_generate(pool);
+
+ args.idx = 1;
+ args.uuid = uuid;
+ SVN_ERR(svn_fs_base__retry_txn(fs, txn_body_set_uuid, &args, TRUE, pool));
+
+ /* Toss our value into the cache. */
+ if (uuid)
+ fs->uuid = apr_pstrdup(fs->pool, uuid);
+
+ return SVN_NO_ERROR;
+}
+
diff --git a/subversion/libsvn_fs_base/uuid.h b/subversion/libsvn_fs_base/uuid.h
new file mode 100644
index 0000000..453a390
--- /dev/null
+++ b/subversion/libsvn_fs_base/uuid.h
@@ -0,0 +1,49 @@
+/* uuid.h : internal interface to uuid functions
+ *
+ * ====================================================================
+ * 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.
+ * ====================================================================
+ */
+
+#ifndef SVN_LIBSVN_FS_UUID_H
+#define SVN_LIBSVN_FS_UUID_H
+
+#ifdef __cplusplus
+extern "C" {
+#endif /* __cplusplus */
+
+
+
+/* Set FS->UUID to the value read from the database, allocated
+ in FS->POOL. Use SCRATCH_POOL for temporary allocations. */
+svn_error_t *svn_fs_base__populate_uuid(svn_fs_t *fs,
+ apr_pool_t *scratch_pool);
+
+
+/* These functions implement some of the calls in the FS loader
+ library's fs vtable. */
+
+svn_error_t *svn_fs_base__set_uuid(svn_fs_t *fs, const char *uuid,
+ apr_pool_t *pool);
+
+
+#ifdef __cplusplus
+}
+#endif /* __cplusplus */
+
+#endif /* SVN_LIBSVN_FS_UUID_H */
OpenPOWER on IntegriCloud