I see Leo Tolstoy has joined the project. Welcome, Leo! :-)
-K
(Ben said: "Holy cow, he just committed an operating system!")
jimb@tigris.org writes:
> User: jimb
> Date: 00/10/27 14:43:50
>
> Modified: subversion/include svn_fs.h
> subversion/libsvn_fs Makefile.am TODO dbt.c dbt.h dir.c
> dir.h err.c err.h fs.h id.c id.h node.c node.h
> skel.c skel.h structure txn.c txn.h version.c
> Removed: subversion/libsvn_fs proplist.c proplist.h
> Log:
> "I'm glad to see the Ministry's continuing its tradition of recruiting
> the brightest and best, sir."
>
> Use skels directly, instead of building more C-like structures. This
> gives us less to build up and tear down when accessing mutable nodes.
> The contents of a mutable node cannot be cached, except within a
> Berkeley DB transaction --- we have to read them and throw them out
> every time.
> * libsvn_fs/fs.h (struct svn_fs_node_t, kind_t): Delete definitons.
> Make nodes opaque outside of node.c.
> (struct svn_fs_file_t, struct svn_fs_dir_t): Delete definitions.
> We can't cache any of the node's contents, so there's not much we can
> put in these structures anyway. The interface still uses them, but
> they're always incomplete types.
> * libsvn_fs/node.c (struct svn_fs_node_t, kind_t): Declare these here.
> Don't cache the node's property list --- on a mutable node, that could
> change at any time.
> (header_values_t, parse_header): New type, new function.
> (svn_fs__init_node): Deleted.
> (svn_fs__open_node_by_id): Don't parse kind-specific data at all; just
> check for kind and mutability.
> (svn_fs__get_node_version, svn_fs__put_node_version,
> svn_fs__reopen_node): New functions.
> * libsvn_fs/node.h (svn_fs__init_node): Delete declaration.
> (svn_fs__get_node_version, svn_fs__put_node_version,
> svn_fs__reopen_node): New declarations.
> * libsvn_fs/dir.c (svn_fs_open_node): Use `svn_fs__reopen_node', instead of
> bumping the open count ourselves.
> (build_entries, compare_dirents, svn_fs__dir_from_skel,
> svn_fs_dir_entries): Deleted.
> (search): New function, to search skels directly.
> (svn_fs_open_node): Tweaked thoroughly. Use `search'.
> * libsvn_fs/dir.h (svn_fs__dir_from_skel): Delete declaration.
>
> Begin implementation of filesystem transactions.
> * libsvn_fs/fs.h (struct svn_fs_txn_t): Make this opaque outside of txn.c.
> (Possibly a stupid idea.)
> * libsvn_fs/txn.c (struct svn_fs_txn_t): Declare here.
> (corrupt_txn, dangling_txn_id, no_such_txn, bad_txn_root): New error functions.
> (get_transaction_skel): New function.
> (begin_txn_body): Renamed from create_txn_body.
> (svn_fs_begin_txn, begin_txn_body): Reworked to use svn_fs__retry_txn.
> (struct replace_root_args): New structure.
> (replace_root_body, svn_fs_replace_root, svn_fs__txn_id,
> svn_fs_txn_name): New functions.
> * libsvn_fs/txn.h (svn_fs__txn_id): New declaration.
> (DB_ERR): New macro.
> * libsvn_fs/node.c (get_representation_skel, get_node_version_skel,
> svn_fs__open_node_by_id): Operate as part of a Berkeley DB
> transaction.
> (put_representation_skel): Rearrange args for consistency.
> (new_node_id, svn_fs__create_node, last_key_before, new_successor_id,
> svn_fs__create_successor, svn_fs__stable_node, svn_fs__node_fs,
> svn_fs__node_id, svn_fs_node_is_mutable): New functions.
> * libsvn_fs/node.h (svn_fs__open_node_by_id): Update declaration.
> (svn_fs__create_node, svn_fs__create_successor, svn_fs__node_fs,
> svn_fs__node_id, svn_fs__stable_node): New declarations.
> * libsvn_fs/skel.c: (svn_fs__make_atom, svn_fs__make_empty_list,
> svn_fs__prepend, svn_fs__copy_skel): New functions.
> * libsvn_fs/skel.h: Declare them.
> * libsvn_fs/dir.c (node_not_mutable): New error function.
> (struct delete_args): New local type.
> (svn_fs_delete, delete_body): New functions.
> * libsvn_fs/id.c (svn_fs__is_parent): New function.
> * libsvn_fs/id.h (svn_fs__is_parent): New declaration.
>
> Wrap up the logic for Berkeley DB transaction retries and aborts in
> a single function:
> * libsvn_fs/err.c (svn_fs__retry_txn): New function.
> * libsvn_fs/err.h (svn_fs__retry_txn): New declaration.
> * libsvn_fs/txn.c (svn_fs_begin_txn, begin_txn_body): Reworked to use
> svn_fs__retry_txn.
>
> Remove property list objects. Let the user retrieve single properties
> directly from the node; for more demanding applications, just give
> them a hash table and let them do as they please.
> * include/svn_fs.h (svn_fs_proplist_t, svn_fs_proplist_get,
> svn_fs_proplist_names, svn_fs_proplist_hash_table,
> svn_fs_compare_prop_names): Delete declarations.
> (svn_fs_get_node_prop, svn_fs_get_node_proplist): New declarations.
> * libsvn_fs/fs.h: (struct svn_fs_proplist_t): Delete.
> * libsvn_fs/proplist.[ch]: Deleted.
> * libsvn_fs/Makefile.am (libsvn_fs_la_SOURCES): Remove proplist.c
> * libsvn_fs/dir.c: Don't #include "proplist.h".
> * libsvn_fs/node.c (svn_fs_get_node_prop, svn_fs_get_node_proplist):
> New functions.
>
> Simplify directory entry reading interface.
> * include/svn_fs.h: (svn_fs_dir_entries): Don't return a sorted list,
> shared with our internal data. Give the user a hash table, allocated
> in their own pool.
> (svn_fs_compare_dirents): Prototype deleted.
> * libsvn_fs/dir.c (svn_fs_dir_entries): Rewritten.
> (svn_fs_compare_dirents): Function deleted.
>
> Simplify `nodes' table structure. We can do without `N.head' entries
> in the table by doing other kinds of range queries, so drop them.
> * libsvn_fs/structure: Update.
> * libsvn_fs/id.c (svn_fs__parse_id): Drop `flags' argument. Never
> recognize `.head' at the end of an ID.
> * libsvn_fs/id.h (svn_fs__key_id): Delete.
> (svn_fs__parse_id): Update declaration.
> * libsvn_fs/node.c (compare_ids): Simplify accordingly.
> * libsvn_fs/version.c (svn_fs__version_root): No need to pass `flags'
> argument.
>
> Provide simple functions for making DBT's from node ID's, skels, and
> strings, and DBT's that request no data.
> * libsvn_fs/dbt.c: #include "id.h" and "skel.h".
> (svn_fs__nodata_dbt, svn_fs__id_to_dbt, svn_fs__skel_to_dbt,
> svn_fs__str_to_dbt): New functions.
> * libsvn_fs/dbt.h: New prototypes for above.
> * libsvn_fs/node.c (get_representation_skel, put_representation_skel,
> new_successor_id): Use them.
> * libsvn_fs/txn.c (put_transaction_skel): Same.
> * libsvn_fs/version.c (put_version_skel): Same.
>
> Make `nodes' key comparison more robust.
> * libsvn_fs/node.c (parse_node_version_dbt): New function.
> (compare_nodes_keys): Use it, to verify that every key is a node
> version ID, not a node ID.
>
> Change NODE-VERSION structure to insulate kind-specific data from
> changes to the common node structure.
> * libsvn_fs/structure: Document changes.
> * libsvn_fs/nodes.c (make_nodes): Update form of root directory.
>
> * libsvn_fs/version.c (put_version_skel): Rearrange args for
> consistency.
>
> * libsvn_fs/dir.c (not_a_directory): Fix SVN error code.
>
> * libsvn_fs/dir.c (svn_fs_open_root): Just use the filesystem's root,
> not a subpool. Don't forget to close root_node if it's bogus.
>
> * libsvn_fs/dir.c (svn_fs_open_root): Check the FS argument.
>
> * include/svn_fs.h (svn_fs_node_version): Deleted; this isn't well-defined.
>
> * include/svn_fs.h (svn_fs_open_node, svn_fs_delete): Add a `pool'
> argument, for temporary allocation.
>
> * include/svn_fs.h (svn_fs_node_is_mutable): Renamed from
> svn_fs_is_mutable.
>
> * include/svn_fs.h (svn_fs_file_contents): Clarify lifetime of
> CONTENTS / CONTENTS_BATON.
>
> * libsvn_fs/Makefile.am (delta.c, file.c): Set aside for now.
>
> Revision Changes Path
> 1.23 +70 -144 subversion/subversion/include/svn_fs.h
>
> Index: svn_fs.h
> ===================================================================
> RCS file: /cvs/subversion/subversion/include/svn_fs.h,v
> retrieving revision 1.22
> retrieving revision 1.23
> diff -u -r1.22 -r1.23
> --- svn_fs.h 2000/10/17 16:12:53 1.22
> +++ svn_fs.h 2000/10/27 21:43:49 1.23
> @@ -284,81 +284,6 @@
> svn_fs_id_t *svn_fs_copy_id (svn_fs_id_t *id, apr_pool_t *pool);
>
>
> -/* Accessing properties and property lists. */
> -
> -/* We use a two-step process for accessing property lists:
> - - you call a function to get something's property list, yielding a
> - property list object
> - - you apply functions to that property list object to read a
> - property, get a list of property names, or get a hash table of all
> - the properties.
> -
> - The call to get a node's property list is guaranteed to succeed.
> - And the property list object is allocated in the underlying
> - object's pool, so you needn't free it. So, while a two-step
> - process is annoying, it's less so than you'd expect because you
> - needn't check for the failure of the first call, or capture its
> - value to be freed later. */
> -
> -
> -/* The type representing a property list of a file, directory, or
> - directory entry. */
> -typedef struct svn_fs_proplist_t svn_fs_proplist_t;
> -
> -
> -/* Set *VALUE to the value of the property in PROPLIST named NAME.
> - Set *VALUE to zero if there is no such property.
> -
> - If POOL is zero, allocate *VALUE in pool of the object PROPLIST
> - came from; it will be freed when the filesystem is closed. If POOL
> - is non-zero, do allocation there. */
> -svn_error_t *svn_fs_proplist_get (svn_string_t **value,
> - svn_fs_proplist_t *proplist,
> - svn_string_t *name,
> - apr_pool_t *pool);
> -
> -
> -/* Set *NAMES to point to a null-terminated array of pointers to
> - strings giving the names of the properties in PROPLIST. The name
> - list is sorted, according to the order defined by
> - `svn_fs_compare_prop_names', below.
> -
> - If POOL is zero, allocate *NAMES in the pool of the object PROPLIST
> - came from; it will be freed when the filesystem is closed. If POOL
> - is non-zero, do allocation there. */
> -svn_error_t *svn_fs_proplist_names (svn_string_t ***names,
> - svn_fs_proplist_t *proplist,
> - apr_pool_t *pool);
> -
> -
> -/* Set *TABLE to an APR hash table containing the property list of
> - PROPLIST. Each key in *TABLE is the text of a property name; each
> - value is a pointer to an `svn_string_t' object holding the
> - property's value.
> -
> - If POOL is zero, allocate *TABLE in the pool of the object PROPLIST
> - came from; it will be freed when that object is closed. If POOL is
> - non-zero, do allocation there. */
> -svn_error_t *svn_fs_proplist_hash_table (apr_hash_t **table,
> - svn_fs_proplist_t *proplist,
> - apr_pool_t *pool);
> -
> -
> -/* Compare two property names A and B, with the same ordering used by
> - `svn_fs_proplist_names'. Return zero if the names are equivalent,
> - a value < 0 if A precedes B, or a value > 0 if B precedes A.
> -
> - As a convenience, a null pointer is considered to come "after" any
> - non-zero string pointer. This means that the null pointer
> - terminating an array returned by `svn_fs_proplist_names' can be
> - safely compared with valid entries.
> -
> - Within any given property list, every property name is guaranteed
> - to be distinct, according to this comparison. */
> -int svn_fs_compare_prop_names (svn_string_t *a, svn_string_t *b);
> -
> -
> -
> /* Nodes. */
>
> /* svn_fs_node_t is the common "superclass" for files, directories,
> @@ -397,18 +322,23 @@
> function in POOL. */
> void svn_fs_run_cleanup_node (apr_pool_t *pool, svn_fs_node_t *node);
>
> -
> -/* Return the filesystem version number of NODE.
> - [[I think this is ill-defined. What should this return for a node
> - which appears in multiple versions of the filesystem?]] */
> -svn_vernum_t svn_fs_node_version (svn_fs_node_t *node);
>
> -
> -/* Return the property list of NODE. The property list object will
> - live exactly as long as NODE does. Multiple calls to this function
> - on the same node object will return the same property list object. */
> -svn_fs_proplist_t *svn_fs_node_proplist (svn_fs_node_t *node);
> +/* Set *VALUE_P to the value of the property of NODE named PROPNAME.
> + If NODE has no property by that name, set *VALUE_P to zero.
> + Allocate the result in POOL. */
> +svn_error_t *svn_fs_get_node_prop (svn_string_t **value_p,
> + svn_fs_node_t *node,
> + svn_string_t *propname,
> + apr_pool_t *pool);
> +
>
> +/* Set *TABLE_P to the entire property list of NODE, as an APR hash
> + table allocated in POOL. The resulting table maps property names
> + to pointers to svn_string_t objects containing the property value. */
> +svn_error_t *svn_fs_get_node_proplist (apr_hash_t **table_p,
> + svn_fs_node_t *node,
> + apr_pool_t *pool);
> +
>
>
> /* Reading and traversing directories. */
> @@ -441,11 +371,15 @@
> svn_fs_t *fs,
> svn_vernum_t v);
>
> +
> +/* Set *CHILD_P to a node object representing the node named NAME in
> + PARENT_DIR. NAME is a directory path.
>
> -/* Set *NODE to a node object representing the node named NAME in
> - PARENT_DIR. NAME is a directory path.
> + Do any necessary temporary allocation in POOL. (The returned node
> + is *not* allocated in POOL; call svn_fs_cleanup_node if you want that
> + behavior.)
>
> - The details about NAME:
> + The details about NAME's syntax:
>
> - NAME must be a series of path components, encoded using UTF-8,
> and separated by slash characters (U+002f).
> @@ -462,9 +396,10 @@
> SVN_ERR_FS_PATH_SYNTAX error. If you want to process absolute
> paths, you'll need to provide a root directory object as
> PARENT_DIR, and strip off the leading slash. */
> -svn_error_t *svn_fs_open_node (svn_fs_node_t **child,
> +svn_error_t *svn_fs_open_node (svn_fs_node_t **child_p,
> svn_fs_dir_t *parent_dir,
> - svn_string_t *name);
> + svn_string_t *name,
> + apr_pool_t *pool);
>
>
> /* Free the directory object DIR. */
> @@ -482,36 +417,15 @@
>
> } svn_fs_dirent_t;
>
> -
> -/* Return a list of DIR's contents. Set *ENTRIES to point to a
> - null-terminated array of pointers to `svn_fs_dirent_t' structures.
> - The entries are sorted, according to `svn_fs_compare_dirents'.
> -
> - All calls to `svn_fs_dir_entries' on a given directory object will
> - return a pointer to the same array; the directory object caches the
> - list of entries, so calls after the first one should be very quick.
> - The array will live as long as the directory object.
> -
> - The caller must not free or modify the elements of the array, or
> - the strings or ID's it refers to. */
> -svn_error_t *svn_fs_dir_entries (svn_fs_dirent_t ***entries,
> - svn_fs_dir_t *dir);
> -
> -
> -/* Compare two directory entries, A and B, by name. If the names are
> - identical, return zero. If A's name precedes B's, return a number
> - < 0. If A's name comes after B's, return a number > 0.
> -
> - As a convenience, a null pointer is considered to come "after" any
> - real directory entry. This means that the null pointer terminating
> - an array returned by `svn_fs_dir_entries' can be safely compared
> - with valid entries.
> -
> - Within any given directory, every entry is guaranteed to be
> - distinct, according to this comparison. */
> -int svn_fs_compare_dirents (const svn_fs_dirent_t *a,
> - const svn_fs_dirent_t *b);
>
> +/* Set *TABLE_P to a newly allocated APR hash table containing the
> + entries of the directory DIR. The keys of the table are entry
> + names, as byte strings; the table's values are pointers to
> + svn_fs_dirent_t structures. Allocate the table and its contents in
> + POOL. */
> +svn_error_t *svn_fs_dir_entries (apr_hash_t **table_p,
> + svn_fs_dir_t *dir,
> + apr_pool_t *pool);
>
>
> /* Accessing files. */
> @@ -551,8 +465,8 @@
> `svn_delta.h'. Set *CONTENTS_BATON to a baton to pass to CONTENTS.
> Allocate the baton in POOL.
>
> - You must keep FILE open until you are done reading data using
> - CONTENTS and CONTENTS_BATON. */
> + You may only use CONTENTS and CONTENTS_BATON for as long as the
> + underlying filesystem is open. */
> svn_error_t *svn_fs_file_contents (svn_read_fn_t **contents,
> void **contents_baton,
> svn_fs_file_t *file,
> @@ -634,29 +548,39 @@
> to cache changes accurately; and so on.
>
>
> - There are two kinds of node, file, and directory objects: mutable,
> - and immutable. The functions above create immutable node objects,
> - that refer to nodes in extant versions of the filesystem. Since
> - those nodes are in the history, they can never change. The
> - functions below, for performing transactions, create mutable node
> - objects. They refer to new nodes (or new versions of existing
> - nodes), which you can change as necessary to create the new version
> - the way you like it.
> + There are two kinds of nodes: mutable, and immutable. The
> + committed versions in the filesystem consist entirely of immutable
> + nodes, whose contents never change. An incomplete transaction,
> + which the user is in the process of constructing, uses mutable
> + nodes for those nodes which have been changed so far, and refer
> + back to immutable nodes for portions of the tree which haven't been
> + changed yet in this transaction. Immutable nodes, as part of
> + committed transactions, never refer to mutable nodes, which are
> + part of uncommitted transactions.
> +
> + The functions above start from some version's root directory, and
> + walk down from there; they can only access immutable nodes, in
> + extant versions of the filesystem. Since those nodes are in the
> + history, they can never change. The functions below, for building
> + transactions, create mutable nodes. They refer to new nodes (or
> + new versions of existing nodes), which you can change as necessary
> + to create the new version the way you want.
>
> In other words, you use immutable nodes for reading committed,
> fixed versions of the filesystem, and you use mutable nodes for
> building new directories and files, as part of a transaction.
>
> - Note that the terms "mutable" and "immutable" describe the role of
> - the node objects --- not the permissions on the nodes they refer
> - to. Even if you aren't authorized to modify the filesystem's root
> - directory, you could still have a mutable directory object
> - referring to it. Since it's mutable, you could call
> - `svn_fs_replace_subdir' to get another mutable directory object
> - referring to a directory you do have permission to change.
> - Mutability refers to the role of the object --- reading an existing
> - version, or writing a new one --- which is independent of your
> - authorization to make changes in a particular place.
> + Note that the terms "immutable" and "mutable" describe whether the
> + nodes are part of a committed filesystem version or not --- not the
> + permissions on the nodes they refer to. Even if you aren't
> + authorized to modify the filesystem's root directory, you could
> + still have a mutable directory object referring to it. Since it's
> + mutable, you could call `svn_fs_replace_subdir' to get another
> + mutable directory object referring to a directory you do have
> + permission to change. Mutability refers to the role of the node
> + --- part of an existing version, or part of a new one --- which is
> + independent of your authorization to make changes in a particular
> + place.
>
> A pattern to note in the interface below: you can't get mutable
> objects from immutable objects. If you have an immutable directory
> @@ -757,8 +681,10 @@
>
>
> /* Delete the entry named NAME from the directory DIR. DIR must be a
> - mutable directory object. */
> -svn_error_t *svn_fs_delete (svn_fs_dir_t *dir, svn_string_t *name);
> + mutable directory object. Do any necessary temporary allocation in
> + POOL. */
> +svn_error_t *svn_fs_delete (svn_fs_dir_t *dir, svn_string_t *name,
> + apr_pool_t *pool);
>
>
> /* Create a new subdirectory of PARENT named NAME. PARENT must be
> @@ -847,7 +773,7 @@
>
> /* Return non-zero iff NODE is a mutable node --- one that can be
> changed as part of a transaction. */
> -int svn_fs_is_mutable (svn_fs_node_t *node);
> +int svn_fs_node_is_mutable (svn_fs_node_t *node);
>
>
>
> @@ -870,12 +796,12 @@
> set. */
>
>
> -/* Set *NAME to the name of the transaction TXN.
> +/* Set *NAME_P to the name of the transaction TXN.
>
> If POOL is zero, allocate NAME in the transaction's pool; it will
> be freed when the transaction is committed or aborted. If POOL is
> non-zero, do allocation there. */
> -svn_error_t *svn_fs_txn_name (svn_string_t **name,
> +svn_error_t *svn_fs_txn_name (svn_string_t **name_p,
> svn_fs_txn_t *txn,
> apr_pool_t *pool);
>
>
>
>
> 1.9 +4 -3 subversion/subversion/libsvn_fs/Makefile.am
>
> Index: Makefile.am
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/Makefile.am,v
> retrieving revision 1.8
> retrieving revision 1.9
> diff -u -r1.8 -r1.9
> --- Makefile.am 2000/10/25 15:58:07 1.8
> +++ Makefile.am 2000/10/27 21:43:49 1.9
> @@ -4,17 +4,18 @@
> lib_LTLIBRARIES = libsvn_fs.la
> libsvn_fs_la_SOURCES = convert-size.c \
> dbt.c \
> - delta.c \
> dir.c \
> err.c \
> - file.c \
> fs.c \
> id.c \
> node.c \
> - proplist.c \
> skel.c \
> txn.c \
> version.c
> +# Set aside for the moment:
> +# file.c \
> +# delta.c \
> +
>
> INCLUDES = -I../include -I../../apr/include -I../../expat-lite
>
>
>
>
> 1.10 +17 -0 subversion/subversion/libsvn_fs/TODO
>
> Index: TODO
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/TODO,v
> retrieving revision 1.9
> retrieving revision 1.10
> diff -u -r1.9 -r1.10
> --- TODO 2000/10/16 02:51:37 1.9
> +++ TODO 2000/10/27 21:43:49 1.10
> @@ -1,3 +1,14 @@
> +It would be nice to organize this file somehow.
> +
> +The 'delete' operation should actually delete mutable nodes, not just
> +leave them around for the commit to clean up. When this happens,
> +svn_fs_open_node needs to use a transaction.
> +
> +Change the transaction name things to return ordinary C strings, since
> +we know transaction names won't contain null characters.
> +
> +Does everyone call svn_fs__check_fs who should?
> +
> Do transactions need an open_count field too?
>
> We don't store older node versions as deltas yet.
> @@ -7,6 +18,9 @@
> Produce helpful error messages when filename paths contain null
> characters.
>
> +We should convert filenames to some canonical Unicode form, for
> +comparison.
> +
> 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
> @@ -23,6 +37,9 @@
> the string objects while the user builds the transaction, and then
> move the transaction nodes into the real node table without having to
> touch all that text --- we just touch all that metainformation.
> +
> +The skel syntax should be changed, so that implicit-length atoms don't
> +require a trailing space. That's too different from common practice.
>
> svn_fs__getsize shouldn't rely on a maximum value for detecting
> overflow.
>
>
>
> 1.7 +52 -0 subversion/subversion/libsvn_fs/dbt.c
>
> Index: dbt.c
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/dbt.c,v
> retrieving revision 1.6
> retrieving revision 1.7
> diff -u -r1.6 -r1.7
> --- dbt.c 2000/10/14 06:11:12 1.6
> +++ dbt.c 2000/10/27 21:43:49 1.7
> @@ -62,6 +62,19 @@
> }
>
>
> +DBT *svn_fs__nodata_dbt (DBT *dbt)
> +{
> + svn_fs__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__set_dbt (DBT *dbt, void *data, u_int32_t size)
> {
> @@ -115,4 +128,43 @@
> 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__id_to_dbt (DBT *dbt,
> + svn_fs_id_t *id,
> + apr_pool_t *pool)
> +{
> + svn_string_t *unparsed_id = svn_fs__unparse_id (id, pool);
> + svn_fs__set_dbt (dbt, unparsed_id->data, unparsed_id->len);
> + return dbt;
> +}
> +
> +
> +/* Set DBT to the unparsed form of SKEL; allocate memory form POOL. */
> +DBT *
> +svn_fs__skel_to_dbt (DBT *dbt,
> + skel_t *skel,
> + apr_pool_t *pool)
> +{
> + svn_string_t *unparsed_skel = svn_fs__unparse_skel (skel, pool);
> + svn_fs__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__str_to_dbt (DBT *dbt, char *str)
> +{
> + svn_fs__set_dbt (dbt, str, strlen (str));
> + return dbt;
> }
>
>
>
> 1.6 +23 -0 subversion/subversion/libsvn_fs/dbt.h
>
> Index: dbt.h
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/dbt.h,v
> retrieving revision 1.5
> retrieving revision 1.6
> diff -u -r1.5 -r1.6
> --- dbt.h 2000/10/14 06:11:12 1.5
> +++ dbt.h 2000/10/27 21:43:49 1.6
> @@ -51,11 +51,19 @@
>
> #include "db.h"
> #include "apr_pools.h"
> +#include "id.h"
> +#include "skel.h"
>
> /* Set all fields of DBT to zero. Return DBT. */
> DBT *svn_fs__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__nodata_dbt (DBT *dbt);
> +
> +
> /* Set DBT to refer to the SIZE bytes at DATA. Return DBT. */
> DBT *svn_fs__set_dbt (DBT *dbt, void *data, u_int32_t size);
>
> @@ -96,6 +104,21 @@
>
> /* Compare two DBT values in byte-by-byte lexicographic order. */
> int svn_fs__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__id_to_dbt (DBT *dbt, 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__skel_to_dbt (DBT *dbt, 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__str_to_dbt (DBT *dbt, char *str);
>
>
> #endif /* SVN_LIBSVN_FS_DBT_H */
>
>
>
> 1.7 +308 -301 subversion/subversion/libsvn_fs/dir.c
>
> Index: dir.c
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/dir.c,v
> retrieving revision 1.6
> retrieving revision 1.7
> diff -u -r1.6 -r1.7
> --- dir.c 2000/10/26 19:32:36 1.6
> +++ dir.c 2000/10/27 21:43:49 1.7
> @@ -1,4 +1,4 @@
> -/* dir.c --- implementing directories
> +/* dir.c : operations on directories
> *
> * ================================================================
> * Copyright (c) 2000 Collab.Net. All rights reserved.
> @@ -46,68 +46,74 @@
> * individuals on behalf of Collab.Net.
> */
>
> -#include <stdlib.h>
> -#include <string.h>
> +#include "string.h"
>
> #include "svn_fs.h"
> -
> #include "fs.h"
> #include "node.h"
> #include "dir.h"
> #include "version.h"
> +#include "err.h"
> #include "id.h"
> #include "skel.h"
> -#include "proplist.h"
> -
> -
> -/* Forward declarations for functions. */
> -
> -static int build_entries (skel_t *entries_skel,
> - svn_fs_dirent_t ***entries_p,
> - int *num_entries_p,
> - int *entries_size_p,
> - apr_pool_t *pool);
>
>
> /* Building error objects. */
>
> static svn_error_t *
> -corrupt_node_version (svn_fs_t *fs, svn_fs_id_t *id)
> +path_syntax (svn_fs_t *fs, svn_string_t *path)
> {
> - svn_string_t *unparsed_id = svn_fs__unparse_id (id, fs->pool);
> -
> + /* Sloppy. What if PATH contains null characters? */
> return
> svn_error_createf
> - (SVN_ERR_FS_CORRUPT, 0, 0, fs->pool,
> - "corrupt node version for node `%s' in filesystem `%s'",
> - unparsed_id->data, fs->env_path);
> + (SVN_ERR_FS_PATH_SYNTAX, 0, 0, fs->pool,
> + "malformed path: `%s'",
> + path->data);
> }
>
>
> static svn_error_t *
> -path_syntax (svn_fs_t *fs, svn_string_t *path)
> +path_not_found (svn_fs_t *fs, svn_string_t *path)
> {
> - /* Sloppy. What if PATH contains null characters? */
> return
> svn_error_createf
> - (SVN_ERR_FS_PATH_SYNTAX, 0, 0, fs->pool,
> - "misformed path `%s' looked up in filesystem `%s'",
> + (SVN_ERR_FS_NOT_FOUND, 0, 0, fs->pool,
> + "file `%s' not found in filesystem `%s'",
> path->data, fs->env_path);
> }
>
>
> static svn_error_t *
> -path_not_found (svn_fs_t *fs, svn_string_t *path)
> +corrupt_node_version (svn_fs_node_t *node)
> {
> + svn_fs_t *fs = svn_fs__node_fs (node);
> + svn_fs_id_t *id = svn_fs__node_id (node);
> + svn_string_t *unparsed_id = svn_fs__unparse_id (id, fs->pool);
> +
> return
> svn_error_createf
> - (SVN_ERR_FS_NOT_FOUND, 0, 0, fs->pool,
> - "file `%s' not found in filesystem `%s'",
> - path->data, fs->env_path);
> + (SVN_ERR_FS_CORRUPT, 0, 0, fs->pool,
> + "corrupt node version for node `%s' in filesystem `%s'",
> + unparsed_id->data, fs->env_path);
> }
>
>
> static svn_error_t *
> +node_not_mutable (svn_fs_node_t *node)
> +{
> + svn_fs_t *fs = svn_fs__node_fs (node);
> + svn_fs_id_t *id = svn_fs__node_id (node);
> + svn_string_t *unparsed_id = svn_fs__unparse_id (id, fs->pool);
> +
> + return
> + svn_error_createf
> + (SVN_ERR_FS_NOT_MUTABLE, 0, 0, fs->pool,
> + "attempt to change immutable node `%s' in filesystem `%s'",
> + unparsed_id->data, fs->env_path);
> +}
> +
> +
> +static svn_error_t *
> not_a_directory (svn_fs_t *fs, char *path, int len)
> {
> char *copy = alloca (len + 1);
> @@ -117,24 +123,44 @@
>
> return
> svn_error_createf
> - (SVN_ERR_FS_NOT_FOUND, 0, 0, fs->pool,
> + (SVN_ERR_FS_NOT_DIRECTORY, 0, 0, fs->pool,
> "path `%s' is not a directory in filesystem `%s'",
> copy, fs->env_path);
> }
>
>
> -/* Building directory objects. */
> +/* Finding a version's root directory. */
>
> -
> -/* A comparison function we can pass to qsort to sort directory entries. */
> -static int
> -compare_dirents (const void *a, const void *b)
> +svn_error_t *
> +svn_fs_open_root (svn_fs_dir_t **dir_p,
> + svn_fs_t *fs,
> + svn_vernum_t v)
> {
> - return svn_fs_compare_dirents (* (const svn_fs_dirent_t * const *) a,
> - * (const svn_fs_dirent_t * const *) b);
> + svn_fs_id_t *root_id;
> + svn_fs_node_t *root_node;
> +
> + SVN_ERR (svn_fs__check_fs (fs));
> + SVN_ERR (svn_fs__version_root (&root_id, fs, v, fs->pool));
> + SVN_ERR (svn_fs__open_node_by_id (&root_node, fs, root_id, 0));
> + if (! svn_fs_node_is_dir (root_node))
> + {
> + svn_fs_close_node (root_node);
> + return
> + svn_error_createf
> + (SVN_ERR_FS_CORRUPT, 0, 0, fs->pool,
> + "the root of version %ld in filesystem `%s' is not a directory",
> + v, fs->env_path);
> + }
> +
> + *dir_p = svn_fs_node_to_dir (root_node);
> + return 0;
> }
>
>
> +
> +/* Opening nodes by name. */
> +
> +
> /* Return true iff the LEN bytes at DATA are a valid directory entry
> name. A valid directory entry name must be non-null, valid UTF-8,
> and contain no slash characters or null characters. */
> @@ -195,220 +221,65 @@
> return 1;
> }
>
> -
> -/* Produce an array of directory entries, given a list of ENTRY skels.
>
> - Parse ENTRIES_SKEL as a list of directory ENTRY skels; set *ENTRIES
> - to a null-terminated array of pointers to directory entry
> - structures, set *NUM_ENTRIES_P to the number of entries in the
> - array, and set *ENTRIES_SIZE_P to the number of entries allocated
> - to the array. Return non-zero on success, or zero if ENTRIES_SKEL
> - is not a well-formed entries list.
> -
> - Do all allocation in POOL. */
> -static int
> -build_entries (skel_t *entries_skel,
> - svn_fs_dirent_t ***entries_p,
> - int *num_entries_p,
> - int *entries_size_p,
> - apr_pool_t *pool)
> -{
> - /* How many entries are there in the list? */
> - int num_entries = svn_fs__list_length (entries_skel);
> - svn_fs_dirent_t **entries;
> -
> - if (num_entries < 0)
> - return 0;
> -
> - entries = NEWARRAY (pool, svn_fs_dirent_t *, num_entries + 1);
> -
> - /* Walk the skel and build the individual directory entries. */
> - {
> - skel_t *entry;
> - int i;
> -
> - for (i = 0, entry = entries_skel->children;
> - entry;
> - entry = entry->next, i++)
> - {
> - svn_fs_id_t *id;
> - skel_t *name_skel, *id_skel;
> +static svn_error_t *
> +search (svn_fs_id_t **id_p,
> + svn_fs_dir_t *dir,
> + char *name,
> + apr_size_t name_len,
> + DB_TXN *db_txn,
> + apr_pool_t *pool)
> +{
> + svn_fs_node_t *dir_node = svn_fs_dir_to_node (dir);
> + skel_t *dir_skel, *entry_list, *entry;
> +
> + /* Read the contents of DIR. */
> + SVN_ERR (svn_fs__get_node_version (&dir_skel, dir_node, db_txn, pool));
> +
> + entry_list = dir_skel->children->next;
> + if (! entry_list || entry_list->is_atom)
> + return corrupt_node_version (dir_node);
>
> - if (svn_fs__list_length (entry) != 2
> - || ! entry->children->is_atom
> - || ! entry->children->next->is_atom)
> - return 0;
> + for (entry = entry_list->children; entry; entry = entry->next)
> + {
> + skel_t *entry_name, *entry_id;
> + if (svn_fs__list_length (entry) != 2
> + || ! entry->children->is_atom
> + || ! entry->children->next->is_atom)
> + return corrupt_node_version (dir_node);
>
> - name_skel = entry->children;
> - id_skel = entry->children->next;
> + entry_name = entry->children;
> + entry_id = entry->children->next;
>
> - id = svn_fs__parse_id (id_skel->data, id_skel->len, 0, pool);
> - if (! id)
> - return 0;
> + if (entry_name->len == name_len
> + && ! memcmp (entry_name->data, name, name_len))
> + {
> + svn_fs_id_t *id = svn_fs__parse_id (entry_id->data,
> + entry_id->len,
> + pool);
> + if (! id)
> + return corrupt_node_version (dir_node);
>
> - /* Check for invalid names. */
> - if (! is_valid_dirent_name (name_skel->data, name_skel->len))
> + *id_p = id;
> return 0;
> -
> - /* Build a directory entry for this. */
> - entries[i] = NEW (pool, svn_fs_dirent_t);
> - entries[i]->name = svn_string_ncreate (name_skel->data,
> - name_skel->len,
> - pool);
> - entries[i]->id = id;
> - }
> -
> - entries[i] = 0;
> -
> - if (i != num_entries)
> - abort ();
> - }
> -
> - /* Sort the list. */
> - qsort (entries, num_entries, sizeof (entries[0]), compare_dirents);
> -
> - /* Check the list for duplicate names. */
> - if (num_entries > 1)
> - {
> - int i;
> -
> - for (i = 1; i < num_entries; i++)
> - {
> - if (svn_string_compare (entries[i - 1]->name, entries[i]->name))
> - return 0;
> }
> }
> -
> - *entries_p = entries;
> - *num_entries_p = num_entries;
> - *entries_size_p = num_entries + 1;
> - return 1;
> -}
> -
> -
> -svn_error_t *
> -svn_fs__dir_from_skel (svn_fs_node_t **node,
> - svn_fs_t *fs,
> - svn_fs_id_t *id,
> - skel_t *nv,
> - apr_pool_t *skel_pool)
> -{
> - svn_fs_dir_t *dir = 0;
> -
> - /* Do a quick check of the syntax of the skel, before we do any more
> - work. */
> - if (svn_fs__list_length (nv) != 3
> - || ! nv->children->next->is_atom
> - || nv->children->next->next->is_atom)
> - goto corrupt;
> -
> - /* Allocate the node itself. */
> - dir = ((svn_fs_dir_t *)
> - svn_fs__init_node (sizeof (*dir), fs, id, kind_dir));
> -
> - /* Try to parse the dir's property list. */
> - dir->node.proplist = svn_fs__make_proplist (nv->children->next,
> - dir->node.pool);
> - if (! dir->node.proplist)
> - goto corrupt;
> -
> - /* Parse the dir's contents. */
> - if (! build_entries (nv->children->next->next,
> - &dir->entries, &dir->num_entries, &dir->entries_size,
> - dir->node.pool))
> - goto corrupt;
> -
> - /* ANSI C guarantees that, if the first element of `struct S' has
> - type `T', then casting a `struct S *' to `T *' and a pointer to
> - S's first element are equivalent. */
> - *node = &dir->node;
> - return 0;
> -
> - corrupt:
> - if (dir)
> - apr_destroy_pool (dir->node.pool);
> - return corrupt_node_version (fs, id);
> -}
> -
> -
> -
> -/* Casting, typing, and other trivial bookkeeping operations on dirs. */
> -
> -svn_fs_dir_t *
> -svn_fs_node_to_dir (svn_fs_node_t *node)
> -{
> - if (node->kind != kind_dir)
> - return 0;
> - else
> - return (svn_fs_dir_t *) node;
> -}
> -
> -
> -svn_fs_node_t *
> -svn_fs_dir_to_node (svn_fs_dir_t *dir)
> -{
> - return &dir->node;
> -}
> -
> -
> -void
> -svn_fs_close_dir (svn_fs_dir_t *dir)
> -{
> - svn_fs_close_node (svn_fs_dir_to_node (dir));
> -}
> -
> -
> -
> -/* Accessing directory contents. */
> -
> -svn_error_t *
> -svn_fs_dir_entries (svn_fs_dirent_t ***entries,
> - svn_fs_dir_t *dir)
> -{
> - *entries = dir->entries;
> -
> - return 0;
> -}
> -
> -
> -svn_error_t *
> -svn_fs_open_root (svn_fs_dir_t **dir,
> - svn_fs_t *fs,
> - svn_vernum_t v)
> -{
> - svn_fs_id_t *id;
> - svn_fs_node_t *root;
> - apr_pool_t *pool = svn_pool_create (fs->pool);
> -
> - SVN_ERR (svn_fs__version_root (&id, fs, v, pool));
> - SVN_ERR (svn_fs__open_node_by_id (&root, fs, id));
> - if (! svn_fs_node_is_dir (root))
> - {
> - apr_destroy_pool (pool);
> - return
> - svn_error_createf
> - (SVN_ERR_FS_CORRUPT, 0, 0, fs->pool,
> - "the root of version %ld in filesystem `%s' is not a directory",
> - v, fs->env_path);
> - }
>
> - *dir = svn_fs_node_to_dir (root);
> - apr_destroy_pool (pool);
> + *id_p = 0;
> return 0;
> }
>
>
> svn_error_t *
> -svn_fs_open_node (svn_fs_node_t **child,
> +svn_fs_open_node (svn_fs_node_t **child_p,
> svn_fs_dir_t *parent_dir,
> - svn_string_t *name)
> + svn_string_t *name,
> + apr_pool_t *pool)
> {
> - svn_error_t *svn_err;
> - svn_fs_t *fs = parent_dir->node.fs;
> + svn_fs_t *fs = svn_fs__node_fs (svn_fs_dir_to_node (parent_dir));
> svn_fs_dir_t *dir;
> svn_fs_node_t *node;
> - char *name_end = name->data + name->len;
> - char *scan;
> + char *scan, *name_end;
>
> /* NAME must not be empty. Also, the Subversion filesystem
> interface doesn't support absolute paths; to avoid
> @@ -417,71 +288,52 @@
> || name->data[0] == '/')
> return path_syntax (fs, name);
>
> - dir = parent_dir;
> - scan = name->data;
> -
> - /* Pretend we re-opened the top directory ourselves. As we walk
> - down the directory tree, we close each directory object after
> - we've traversed it, but we don't want to close PARENT_DIR ---
> - that's the caller's object. Bumping the open count has the same
> - effect as re-opening the directory ourselves, so we have the
> - right to close it --- once.
> -
> - Perhaps it would be a good idea to have a "reopen" call in the
> - public interface, and just use that. */
> - dir->node.open_count++;
> + /* Get our own `open' of PARENT_NODE, so we can close it without
> + affecting the caller. */
> + dir = (svn_fs_node_to_dir
> + (svn_fs__reopen_node
> + (svn_fs_dir_to_node (parent_dir))));
>
> - /* Walk down from PARENT_DIR to the desired node, traversing NAME one path
> - component at a time. */
> + scan = name->data;
> + name_end = name->data + name->len;
> +
> + /* Walk down PARENT_DIR to the desired node, traversing NAME one
> + path component at a time. */
> for (;;)
> {
> - char *start;
> - svn_fs_dirent_t **entry;
> -
> - /* At this point, we know scan points to something that isn't a
> - slash, and that there's at least one character there ---
> - there's a path component. Scan for its end. */
> - start = scan;
> + svn_error_t *svn_err;
> + svn_fs_id_t *entry_id;
> + char *start = scan;
> +
> + /* At this point, we know scan points to at least one character,
> + and that character is not a slash --- so it's a valid path
> + component. Scan for its end. */
> scan = memchr (start, '/', name_end - start);
> -
> - /* If we hit the end of the string, then everything to the end
> - is the next component. */
> if (! scan)
> scan = name_end;
>
> - /* Now everything from start to scan is a filename component. */
> -
> - /* Yes, but is it a *valid* filename component? */
> if (! is_valid_dirent_name (start, scan - start))
> {
> - svn_fs_close_dir (dir);
> - return path_syntax (fs, name);
> - }
> -
> - /* Try to find a matching entry in dir. */
> - for (entry = dir->entries; *entry; entry++)
> - if ((*entry)->name->len == scan - start
> - && ! memcmp ((*entry)->name->data, start, scan - start))
> - break;
> -
> - /* If we didn't find a matching entry, then return an error. */
> - if (! *entry)
> - {
> svn_fs_close_dir (dir);
> - return path_not_found (fs, name);
> + return path_syntax (fs ,name);
> }
>
> - /* Try to open that node. */
> - svn_err = svn_fs__open_node_by_id (&node, fs, (*entry)->id);
> - if (svn_err)
> - {
> - svn_fs_close_dir (dir);
> - return svn_err;
> - }
> + /* Try to find a entry by that name in DIR. */
> + svn_err = search (&entry_id, dir, start, scan - start, 0, pool);
>
> /* Close the parent directory. */
> svn_fs_close_dir (dir);
>
> + /* Handle any error returned by `search'. */
> + SVN_ERR (svn_err);
> +
> + /* If we didn't find a matching entry, then return an error. */
> + if (! entry_id)
> + return path_not_found (fs, name);
> +
> + /* Try to open the node whose ID we've found. */
> + SVN_ERR (svn_fs__open_node_by_id (&node, fs, entry_id, 0));
> +
> /* Are we done with the name? */
> if (scan >= name_end)
> break;
> @@ -505,33 +357,188 @@
> break;
> }
>
> - *child = node;
> + *child_p = node;
> return 0;
> }
>
>
>
> -/* The directory entry sort order. */
> +/* Listing directory contents. */
>
> -int
> -svn_fs_compare_dirents (const svn_fs_dirent_t *a,
> - const svn_fs_dirent_t *b)
> -{
> - if (a == b)
> - return 0;
> - else if (! a)
> - return 1;
> - else if (! b)
> - return -1;
> - else
> +
> +svn_error_t *
> +svn_fs_dir_entries (apr_hash_t **table_p,
> + svn_fs_dir_t *dir,
> + apr_pool_t *pool)
> +{
> + svn_fs_node_t *dir_node = svn_fs_dir_to_node (dir);
> + int dir_node_is_mutable = svn_fs_node_is_mutable (dir_node);
> + skel_t *dir_skel, *entry;
> + apr_hash_t *table;
> +
> + SVN_ERR (svn_fs__get_node_version (&dir_skel, dir_node, 0, pool));
> + table = apr_make_hash (pool);
> +
> + /* Walk DIR's list of entries, adding an entry to TABLE for each one. */
> + if (svn_fs__list_length (dir_skel) != 2
> + || dir_skel->children->next->is_atom)
> + return corrupt_node_version (dir_node);
> +
> + for (entry = dir_skel->children->next->children; entry; entry = entry->next)
> {
> - int cmp = memcmp (a->name->data, b->name->data,
> - (a->name->len > b->name->len
> - ? b->name->len
> - : a->name->len));
> - if (cmp)
> - return cmp;
> + skel_t *name_skel, *id_skel;
> + svn_fs_dirent_t *dirent;
> +
> + if (svn_fs__list_length (entry) != 2
> + || ! entry->children->is_atom
> + || ! entry->children->next->is_atom)
> + return corrupt_node_version (dir_node);
> +
> + name_skel = entry->children;
> + id_skel = entry->children->next;
> +
> + dirent = NEW (pool, svn_fs_dirent_t);
> +
> + /* If the node is immutable, name_skel points into the node's
> + copy of the data, so we need to copy it. Otherwise, we know
> + it's already allocated in pool, so we can just reference it. */
> + if (dir_node_is_mutable)
> + {
> + dirent->name = NEW (pool, svn_string_t);
> + dirent->name->data = name_skel->data;
> + dirent->name->len = name_skel->len;
> + }
> else
> - return a->name->len - b->name->len;
> + dirent->name = svn_string_ncreate (name_skel->data, name_skel->len,
> + pool);
> +
> + dirent->id = svn_fs__parse_id (id_skel->data, id_skel->len, pool);
> +
> + if (! dirent->id)
> + return corrupt_node_version (dir_node);
> +
> + apr_hash_set (table, dirent->name->data, dirent->name->len, dirent);
> }
> +
> + *table_p = table;
> + return 0;
> +}
> +
> +
> +
> +/* Deleting files. */
> +
> +
> +/* The arguments to delete_body, to be passed through svn_fs__retry_txn. */
> +struct delete_args {
> + svn_fs_node_t *dir_node;
> + svn_string_t *name;
> + apr_pool_t *pool;
> +};
> +
> +
> +static svn_error_t *
> +delete_body (void *baton,
> + DB_TXN *db_txn)
> +{
> + /* Unpack the arguments, passed through svn_fs__retry_txn. */
> + struct delete_args *args = baton;
> + svn_fs_node_t *dir_node = args->dir_node;
> + svn_string_t *name = args->name;
> + apr_pool_t *pool = args->pool;
> +
> + svn_fs_t *fs = svn_fs__node_fs (dir_node);
> + skel_t *skel, *entry_list, **entry;
> +
> + if (! is_valid_dirent_name (name->data, name->len))
> + return path_syntax (fs, name);
> +
> + /* Make sure this is a mutable node. */
> + if (! svn_fs_node_is_mutable (dir_node))
> + return node_not_mutable (dir_node);
> +
> + /* Read the node's contents. */
> + SVN_ERR (svn_fs__get_node_version (&skel, dir_node, db_txn, pool));
> +
> + /* Find an entry whose name is NAME, and delete it. */
> + entry_list = skel->children->next;
> + if (entry_list->is_atom)
> + return corrupt_node_version (dir_node);
> +
> + for (entry = &entry_list->children; *entry; entry = &(*entry)->next)
> + {
> + skel_t *entry_name, *entry_id;
> + if (svn_fs__list_length (*entry) != 2
> + || ! (*entry)->children->is_atom
> + || ! (*entry)->children->next->is_atom)
> + return corrupt_node_version (dir_node);
> +
> + entry_name = (*entry)->children;
> + entry_id = (*entry)->children->next;
> +
> + if (entry_name->len == name->len
> + && ! memcmp (entry_name->data, name->data, name->len))
> + break;
> + }
> +
> + /* Did we find a matching entry? */
> + if (! *entry)
> + return path_not_found (fs, name);
> +
> + /* Remove the entry, and write back the directory. We just drop all
> + references to the node; the commit process will notice that it's
> + not referenced and clean it up.
> +
> + If we change this, then svn_fs_open_node will need to use a
> + transaction to make sure directories don't go away while it
> + works. */
> + *entry = (*entry)->next;
> +
> + SVN_ERR (svn_fs__put_node_version (dir_node, skel, db_txn));
> +
> + return 0;
> +}
> +
> +
> +svn_error_t *
> +svn_fs_delete (svn_fs_dir_t *dir,
> + svn_string_t *name,
> + apr_pool_t *pool)
> +{
> + svn_fs_node_t *dir_node = svn_fs_dir_to_node (dir);
> + svn_fs_t *fs = svn_fs__node_fs (dir_node);
> + struct delete_args args;
> +
> + args.dir_node = dir_node;
> + args.name = name;
> + args.pool = pool;
> +
> + return svn_fs__retry_txn (fs, delete_body, &args);
> +}
> +
> +
> +/* Trivial bookkeeping operations on directories. */
> +
> +
> +svn_fs_dir_t *
> +svn_fs_node_to_dir (svn_fs_node_t *node)
> +{
> + if (svn_fs_node_is_dir (node))
> + return (svn_fs_dir_t *) node;
> + else
> + return 0;
> +}
> +
> +
> +svn_fs_node_t *
> +svn_fs_dir_to_node (svn_fs_dir_t *dir)
> +{
> + return (svn_fs_node_t *) dir;
> +}
> +
> +
> +void
> +svn_fs_close_dir (svn_fs_dir_t *dir)
> +{
> + svn_fs_close_node (svn_fs_dir_to_node (dir));
> }
>
>
>
> 1.2 +0 -22 subversion/subversion/libsvn_fs/dir.h
>
> Index: dir.h
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/dir.h,v
> retrieving revision 1.1
> retrieving revision 1.2
> diff -u -r1.1 -r1.2
> --- dir.h 2000/10/07 04:47:00 1.1
> +++ dir.h 2000/10/27 21:43:49 1.2
> @@ -53,26 +53,4 @@
> #ifndef SVN_LIBSVN_FS_DIR_H
> #define SVN_LIBSVN_FS_DIR_H
>
> -#include "apr_pools.h"
> -#include "svn_fs.h"
> -#include "skel.h"
> -
> -/* Set *NODE to a new a svn_fs_file_t for node ID in filesystem FS,
> - whose NODE-VERSION skel is NV. NV is allocated in SKEL_POOL, as is
> - the data it points to. NV must be a list skel of at least two
> - elements, whose first element is the atom "file".
> -
> - The new node must not be added to the node cache, and its open
> - count must be zero.
> -
> - This function will use SKEL_POOL for any temporary storage it needs
> - while constructing the new node. (Any memory actually used by NODE
> - itself goes in NODE's pool, of course.) */
> -svn_error_t *svn_fs__dir_from_skel (svn_fs_node_t **node,
> - svn_fs_t *fs,
> - svn_fs_id_t *id,
> - skel_t *nv,
> - apr_pool_t *skel_pool);
> -
> -
> #endif /* SVN_LIBSVN_FS_DIR_H */
>
>
>
> 1.10 +42 -0 subversion/subversion/libsvn_fs/err.c
>
> Index: err.c
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/err.c,v
> retrieving revision 1.9
> retrieving revision 1.10
> diff -u -r1.9 -r1.10
> --- err.c 2000/10/13 23:24:55 1.9
> +++ err.c 2000/10/27 21:43:49 1.10
> @@ -105,3 +105,45 @@
> return svn_error_create (SVN_ERR_FS_NOT_OPEN, 0, 0, fs->pool,
> "filesystem object has not been opened yet");
> }
> +
> +
> +svn_error_t *
> +svn_fs__retry_txn (svn_fs_t *fs,
> + svn_error_t *(*txn_body) (void *baton,
> + DB_TXN *db_txn),
> + void *baton)
> +{
> + for (;;)
> + {
> + DB_TXN *db_txn;
> + svn_error_t *svn_err;
> +
> + SVN_ERR (DB_WRAP (fs, "creating transaction (beginning DB transaction)",
> + txn_begin (fs->env, 0, &db_txn, 0)));
> +
> + /* Do the body of the transaction. */
> + svn_err = (*txn_body) (baton, db_txn);
> +
> + if (! svn_err)
> + {
> + /* The transaction succeeded! Commit it. */
> + SVN_ERR (DB_WRAP (fs,
> + "creating transaction (committing DB transaction)",
> + txn_commit (db_txn, 0)));
> + return 0;
> + }
> +
> + /* Is this a real error, or do we just need to retry? */
> + if (svn_err->apr_err != SVN_ERR_BERKELEY_DB
> + || svn_err->src_err != DB_LOCK_DEADLOCK)
> + {
> + /* Ignore any error returns. The first error is more valuable. */
> + txn_abort (db_txn);
> + return svn_err;
> + }
> +
> + /* We deadlocked. Abort the transaction, and try again. */
> + SVN_ERR (DB_WRAP (fs, "creating transaction (aborting DB transaction)",
> + txn_abort (db_txn)));
> + }
> +}
>
>
>
> 1.7 +32 -0 subversion/subversion/libsvn_fs/err.h
>
> Index: err.h
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/err.h,v
> retrieving revision 1.6
> retrieving revision 1.7
> diff -u -r1.6 -r1.7
> --- err.h 2000/10/13 23:24:55 1.6
> +++ err.h 2000/10/27 21:43:49 1.7
> @@ -94,9 +94,41 @@
> #define DB_WRAP(fs, op, err) (svn_fs__wrap_db ((fs), (op), (err)))
>
>
> +/* 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 DB_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__check_fs (svn_fs_t *fs);
>
> +
> +/* 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.
> + - Apply TXN_BODY to BATON and DB_TXN. TXN_BODY should try to do
> + some series of DB operations which needs to be atomic, using
> + DB_TXN as the transaction. If a DB operation deadlocks, or if
> + any other kind of error happens, TXN_BODY should simply return
> + with an appropriate svn_error_t.
> + - If TXN_BODY returns an error indicating that a deadlock occurred,
> + retry the operation.
> + - Otherwise, return what TXN_BODY returned.
> +
> + 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__retry_txn (svn_fs_t *fs,
> + svn_error_t *(*txn_body) (void *baton,
> + DB_TXN *db_txn),
> + void *baton);
>
> #endif /* SVN_LIBSVN_FS_ERR_H */
>
>
>
> 1.9 +3 -141 subversion/subversion/libsvn_fs/fs.h
>
> Index: fs.h
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/fs.h,v
> retrieving revision 1.8
> retrieving revision 1.9
> diff -u -r1.8 -r1.9
> --- fs.h 2000/10/14 06:11:12 1.8
> +++ fs.h 2000/10/27 21:43:49 1.9
> @@ -86,8 +86,9 @@
> the ID's of the nodes that are part of that transaction. */
> DB *transactions;
>
> - /* A cache of nodes we've read in, mapping svn_fs_id_t arrays onto
> - pointers to nodes. */
> + /* A cache of nodes we've read in, mapping svn_fs_id_t arrays (not
> + including the terminating -1) onto pointers to svn_fs_node_t
> + objects. */
> apr_hash_t *node_cache;
>
> /* A table of all currently open transactions. Each value is a
> @@ -121,145 +122,6 @@
> fs_cleanup won't overwrite a pointer to an existing svn_error_t
> if it finds one. */
> svn_error_t **cleanup_error;
> -};
> -
> -
> -
> -/* Property lists. */
> -
> -
> -/* The structure underlying the public svn_fs_proplist_t typedef. */
> -
> -struct svn_fs_proplist_t {
> -
> - /* A hash table, mapping property names (as byte strings) onto
> - svn_string_t objects holding the values. */
> - apr_hash_t *hash;
> -
> - /* The pool of the underlying object. */
> - apr_pool_t *pool;
> -
> -};
> -
> -
> -
> -/* Transactions. */
> -
> -/* The private structure underlying the public svn_fs_txn_t typedef. */
> -
> -struct svn_fs_txn_t {
> -
> - /* This transaction's private pool, a subpool of fs->pool.
> -
> - Freeing this must completely clean up the transaction object,
> - write back any buffered data, and release any database or system
> - resources it holds. (But don't confused the transaction object
> - with the transaction it represents: freeing this does *not* abort
> - the transaction.) */
> - apr_pool_t *pool;
> -
> - /* The filesystem to which this transaction belongs. */
> - svn_fs_t *fs;
> -
> - /* The ID of this transaction --- a null-terminated string.
> - This is the key into the `transactions' table. */
> - char *id;
> -
> - /* The root directory for this transaction, or zero if the user
> - hasn't called svn_fs_replace_root yet. */
> - svn_fs_id_t *root;
> -};
> -
> -
> -/* Nodes. */
> -
> -
> -/* The different kinds of filesystem nodes. */
> -typedef enum {
> - kind_file,
> - kind_dir
> -} kind_t;
> -
> -
> -/* The private structure underlying the public svn_fs_node_t typedef.
> -
> - All the more specific node structures --- files, directories,
> - etc. --- include one of these as their first member. ANSI
> - guarantees the right behavior when you cast a pointer to a
> - structure to a pointer to its first member, and back. So this is
> - effectively the superclass for files and directories. */
> -
> -struct svn_fs_node_t {
> -
> - /* The `open count' --- how many times this object has been opened,
> - minus the number of times it has been closed. If this count is
> - zero, then we can free the object (although we may keep it around
> - anyway as part of a cache). */
> - int open_count;
> -
> - /* The filesystem to which we belong. */
> - svn_fs_t *fs;
> -
> - /* This node's private pool, a subpool of fs->pool.
> - Freeing this must completely clean up the node, and release any
> - database or system resources it holds. It must also remove the
> - node from the filesystem's node cache. */
> - apr_pool_t *pool;
> -
> - /* The node version ID of this node.
> -
> - If this node is part of a transaction, the immediate ancestor of
> - this node is the one we're merging against. If this node has no
> - immediate ancestor, then it's new in this transaction. */
> - svn_fs_id_t *id;
> -
> - /* What kind of node this is, more specifically. */
> - kind_t kind;
> -
> - /* The node's property list. */
> - svn_fs_proplist_t *proplist;
> -
> - /* On mutable nodes, this points to transaction the node belongs to.
> - On immutable nodes, this is zero. */
> - svn_fs_txn_t *txn;
> -};
> -
> -
> -
> -/* Files. */
> -
> -/* The private structure underlying the public svn_fs_file_t typedef. */
> -
> -struct svn_fs_file_t {
> -
> - /* The node structure carries information common to all nodes. */
> - svn_fs_node_t node;
> -
> - /* The contents of the file. In the future, we should replace this
> - with a reference to some database record we can read as needed. */
> - svn_string_t *contents;
> -
> -};
> -
> -
> -
> -/* Directories. */
> -
> -/* The private structure underlying the public svn_fs_dir_t typedef. */
> -
> -struct svn_fs_dir_t {
> -
> - /* The node structure carries information common to all nodes. */
> - svn_fs_node_t node;
> -
> - /* An array of pointers to the entries of this directory, terminated
> - by a null pointer. */
> - svn_fs_dirent_t **entries;
> -
> - /* The number of directory entries here, and the number of elements
> - allocated to the entries array. */
> - int num_entries;
> - int entries_size;
> };
>
>
>
>
>
> 1.5 +51 -39 subversion/subversion/libsvn_fs/id.c
>
> Index: id.c
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/id.c,v
> retrieving revision 1.4
> retrieving revision 1.5
> diff -u -r1.4 -r1.5
> --- id.c 2000/10/12 21:21:50 1.4
> +++ id.c 2000/10/27 21:43:49 1.5
> @@ -182,13 +182,45 @@
> }
>
>
> +int
> +svn_fs__is_parent (svn_fs_id_t *parent,
> + svn_fs_id_t *child)
> +{
> + int i;
> +
> + for (i = 0; parent[i] == child[i]; i++)
> + {
> + /* If they're completely identical, then CHILD isn't a direct
> + child of PARENT. */
> + if (parent[i] == -1)
> + return 0;
> + }
> +
> + /* Is CHILD the next version of PARENT? */
> + if ((i & 1) == 1
> + && child[i] == parent[i] + 1
> + && child[i + 1] == -1
> + && parent[i + 1] == -1)
> + return 1;
> +
> + /* Is CHILD the first version of any branch from PARENT? */
> + if ((i & 1) == 0
> + && parent[i] == -1
> + && child[i + 1] != -1
> + && child[i + 2] == 1)
> + return 1;
> +
> + /* Anything else is no good. */
> + return 0;
> +}
> +
> +
>
> /* Parsing and unparsing node ID's. */
>
> svn_fs_id_t *
> svn_fs__parse_id (char *data,
> apr_size_t data_len,
> - int flags,
> apr_pool_t *pool)
> {
> svn_fs_id_t *id;
> @@ -210,15 +242,6 @@
> return 0;
> last_start = i + 1;
> id_len++;
> -
> - /* If we're parsing a `nodes' key, check for '.head'. */
> - if (flags & svn_fs__key_id
> - && i + 5 == data_len
> - && ! memcmp (data + i, ".head", 5))
> - {
> - i += 5;
> - break;
> - }
> }
> else if ('0' <= data[i] && data[i] <= '9')
> ;
> @@ -236,39 +259,28 @@
> else
> id = (svn_fs_id_t *) malloc (sizeof (*id) * (id_len + 1));
>
> - for (;;)
> - {
> - int i = 0;
> - char *next = data;
> - char *end = data + data_len;
> + {
> + int i = 0;
> + char *end = data + data_len;
>
> - for (;;)
> - {
> - id[i++] = svn_fs__getsize (data,
> - end - data,
> - &next,
> - 100000000);
> - if (! next)
> - return 0;
> - if (next == end)
> - break;
> - if (*next != '.')
> + for (;;)
> + {
> + char *next;
> + id[i++] = svn_fs__getsize (data, end - data, &next, 100000000);
> + if (next == end)
> + break;
> + if (! next
> + || *next != '.')
> + {
> + if (! pool) free (id);
> return 0;
> + }
>
> - data = next + 1;
> + data = next + 1;
> + }
>
> - /* If we're parsing a `nodes' key, check for ".head". */
> - if (flags & svn_fs__key_id
> - && end - data == 4
> - && ! memcmp (data, "head", 4))
> - {
> - id[i++] = -2;
> - break;
> - }
> - }
> -
> - id[i] = -1;
> - }
> + id[i] = -1;
> + }
>
> return id;
> }
>
>
>
> 1.3 +5 -19 subversion/subversion/libsvn_fs/id.h
>
> Index: id.h
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/id.h,v
> retrieving revision 1.2
> retrieving revision 1.3
> diff -u -r1.2 -r1.3
> --- id.h 2000/10/11 18:18:29 1.2
> +++ id.h 2000/10/27 21:43:49 1.3
> @@ -54,31 +54,13 @@
> #include "svn_fs.h"
>
>
> -enum {
> - /* Recognize id strings with the `.head' suffix, as found in the
> - nodes table. */
> - svn_fs__key_id = 1,
> -};
> -
> -
> /* Parse the LEN bytes at DATA as a node ID. Return zero if the bytes
> are not a properly-formed node ID.
>
> - A well-formed ID has the form "[0-9](\.[0-9])*". In the `nodes'
> - table, we also use ID's of the form "[0-9](\.[0-9])*\.head", to
> - help us find the most recent version of a node.
> -
> - If FLAGS is zero, accept only the first form of ID.
> -
> - If FLAGS & svn_fs__key_id is non-zero, also accept the second form,
> - with the `.head' suffix. Represent the `.head' element in the
> - returned array as a -2.
> -
> Allocate the parsed ID in POOL. As a special case for the Berkeley
> DB comparison function, if POOL is zero, malloc the ID. It's
> generally better to use a pool if you can. */
> -svn_fs_id_t *svn_fs__parse_id (char *data, apr_size_t len, int flags,
> - apr_pool_t *pool);
> +svn_fs_id_t *svn_fs__parse_id (char *data, apr_size_t len, apr_pool_t *pool);
>
>
> /* Return a Subversion string containing the unparsed form of the node
> @@ -86,6 +68,10 @@
> svn_string_t *svn_fs__unparse_id (svn_fs_id_t *id,
> apr_pool_t *pool);
>
> +
> +/* Return true iff PARENT is a direct ancestor of CHILD. */
> +int svn_fs__is_parent (svn_fs_id_t *parent,
> + svn_fs_id_t *child);
>
>
> #endif /* SVN_LIBSVN_FS_ID_H */
>
>
>
> 1.8 +886 -187 subversion/subversion/libsvn_fs/node.c
>
> Index: node.c
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/node.c,v
> retrieving revision 1.7
> retrieving revision 1.8
> diff -u -r1.7 -r1.8
> --- node.c 2000/10/13 23:40:42 1.7
> +++ node.c 2000/10/27 21:43:49 1.8
> @@ -64,10 +64,57 @@
> #include "skel.h"
> #include "err.h"
> #include "dbt.h"
> +#include "txn.h"
>
>
> -/* Building some often-used error objects. */
> +/* The node structure. */
>
> +/* The different kinds of nodes. */
> +typedef enum {
> + kind_file,
> + kind_directory
> +} kind_t;
> +
> +
> +/* This is the structure underlying the public svn_fs_node_t typedef. */
> +
> +struct svn_fs_node_t {
> +
> + /* The node's pool, a subpool of FS->pool. */
> + apr_pool_t *pool;
> +
> + /* The filesystem to which this node belongs. */
> + svn_fs_t *fs;
> +
> + /* This node's ID in FS. */
> + svn_fs_id_t *id;
> +
> + /* How many times this node has been opened. If this count is zero,
> + we can safely free it, without annoying code outside this file. */
> + int open_count;
> +
> + /* What kind of node this is. We can cache this even if the node is
> + mutable, since nodes don't change their types. */
> + kind_t kind;
> +
> + /* If this is a mutable node, the ID of the transaction to which it
> + belongs. Otherwise, this is zero. */
> + char *txn_id;
> +
> + /* If this is an immutable node, this is its NODE-VERSION skel. The
> + skel, and the data it points into, are allocated from POOL.
> +
> + On mutable nodes, this should be zero. We don't cache the
> + NODE-VERSION skel of mutable nodes, since it could change while
> + we have the node open. */
> + skel_t *node_version;
> +
> +};
> +
> +
> +
> +/* Building error objects. */
> +
> static svn_error_t *
> corrupt_id (const char *fmt, svn_fs_id_t *id, svn_fs_t *fs)
> {
> @@ -104,112 +151,376 @@
> id, fs);
> }
>
> +
> +static svn_error_t *
> +not_a_node_version_id (svn_fs_t *fs, svn_fs_id_t *id)
> +{
> + return
> + corrupt_id ("Bogus node version id `%s' appears in filesystem `%s'",
> + id, fs);
> +}
> +
> +
> +static svn_error_t *
> +corrupt_nodes_key (svn_fs_t *fs)
> +{
> + return
> + svn_error_createf
> + (SVN_ERR_FS_CORRUPT, 0, 0, fs->pool,
> + "malformed ID as key in `nodes' table of filesystem `%s'", fs->env_path);
> +}
> +
> +
>
> -/* Reading node REPRESENTATION skels from the database. */
> +/* Storing and retrieving node version REPRESENTATION skels. */
>
>
> -/* Set *SKEL to point to the REPRESENTATION skel for the node ID in FS.
> - Allocate the skel and the data it points into in POOL.
> +/* Set *SKEL_P to point to the REPRESENTATION skel for the node ID in
> + FS, as part of the Berkeley DB transaction DB_TXN. Allocate the
> + skel and the data it points into in POOL.
>
> Beyond verifying that it's a syntactically valid skel, this doesn't
> validate the data returned at all. */
> static svn_error_t *
> -get_representation_skel (skel_t **skel, svn_fs_t *fs, svn_fs_id_t *id,
> +get_representation_skel (skel_t **skel_p,
> + svn_fs_t *fs,
> + svn_fs_id_t *id,
> + DB_TXN *db_txn,
> apr_pool_t *pool)
> {
> - DBT key, value;
> int db_err;
> - svn_string_t *unparsed_id;
> - skel_t *rep_skel;
> -
> - SVN_ERR (svn_fs__check_fs (fs));
> + DBT key, value;
> + skel_t *skel;
>
> /* Generate the ASCII form of the node version ID. */
> - unparsed_id = svn_fs__unparse_id (id, pool);
> - svn_fs__set_dbt (&key, unparsed_id->data, unparsed_id->len);
> - svn_fs__result_dbt (&value);
> - db_err = fs->nodes->get (fs->nodes, 0, /* no transaction */ &key, &value, 0);
> + db_err = fs->nodes->get (fs->nodes, db_txn,
> + svn_fs__id_to_dbt (&key, id, pool),
> + svn_fs__result_dbt (&value),
> + 0);
> if (db_err == DB_NOTFOUND)
> return corrupt_dangling_id (fs, id);
> SVN_ERR (DB_WRAP (fs, "reading node representation", db_err));
> svn_fs__track_dbt (&value, pool);
>
> - rep_skel = svn_fs__parse_skel (value.data, value.size, pool);
> - if (! rep_skel)
> + skel = svn_fs__parse_skel (value.data, value.size, pool);
> + if (! skel)
> return corrupt_representation (fs, id);
>
> - *skel = rep_skel;
> + *skel_p = skel;
> return 0;
> }
>
> -
> -
> -/* Writing node REPRESENTATION skels to the database. */
> -
>
> -/* Set the representation skel for node ID in filesystem FS to
> - REPRESENTATION_SKEL, as part of the Berkeley DB transaction TXN.
> - TXN may be zero, in which case the change is done outside of any
> - transaction. Do any necessary temporary allocation in POOL. */
> +/* Set the REPRESENTATION skel for node ID in filesystem FS to SKEL,
> + as part of the Berkeley DB transaction TXN. TXN may be zero, in
> + which case the change is done outside of any transaction. Do any
> + necessary temporary allocation in POOL. */
> static svn_error_t *
> -put_representation_skel (svn_fs_t *fs, DB_TXN *txn,
> - svn_fs_id_t *id, skel_t *representation_skel,
> +put_representation_skel (svn_fs_t *fs,
> + svn_fs_id_t *id,
> + skel_t *skel,
> + DB_TXN *txn,
> apr_pool_t *pool)
> {
> - svn_string_t *unparsed_id;
> - svn_string_t *unparsed_rep;
> DBT key, value;
>
> - SVN_ERR (svn_fs__check_fs (fs));
> -
> - unparsed_id = svn_fs__unparse_id (id, pool);
> - svn_fs__set_dbt (&key, unparsed_id->data, unparsed_id->len);
> -
> - unparsed_rep = svn_fs__unparse_skel (representation_skel, pool);
> - svn_fs__set_dbt (&value, unparsed_rep->data, unparsed_rep->len);
> -
> SVN_ERR (DB_WRAP (fs, "storing node representation",
> - fs->nodes->put (fs->nodes, txn, &key, &value, 0)));
> + fs->nodes->put (fs->nodes, txn,
> + svn_fs__id_to_dbt (&key, id, pool),
> + svn_fs__skel_to_dbt (&value, skel, pool),
> + 0)));
>
> return 0;
> }
>
>
>
> -/* Recovering the full text of NODE-VERSION skels from the database. */
> +/* Storing and retrieving NODE-VERSION skels. */
>
>
> -/* Set SKEL to point to the NODE-VERSION skel for the node ID in FS.
> - Allocate the skel and the data it points into in POOL.
> +/* Set *SKEL_P to point to the NODE-VERSION skel for the node ID in FS,
> + as part of the Berkeley DB transaction DB_TXN. Allocate the skel
> + and the data it points into in POOL.
>
> This takes care of applying any necessary deltas to reconstruct the
> node version. */
>
> static svn_error_t *
> -get_node_version_skel (skel_t **skel, svn_fs_t *fs, svn_fs_id_t *id,
> +get_node_version_skel (skel_t **skel_p,
> + svn_fs_t *fs,
> + svn_fs_id_t *id,
> + DB_TXN *db_txn,
> apr_pool_t *pool)
> {
> - skel_t *rep;
> + skel_t *skel;
>
> - /* Well, this would take care of applying any necessary deltas, but
> - we don't have anything that generates vcdiff-format output yet,
> - so I can't store deltas in the database.
> -
> - So for now, every node is stored using the "fulltext"
> - representation. I'm off the hook!!
> -
> - In the future, it would be nice to have a cache of fulltexts, to
> - help us compute nearby versions quickly. */
> - SVN_ERR (get_representation_skel (&rep, fs, id, pool));
> - if (svn_fs__list_length (rep) != 2
> - || ! svn_fs__is_atom (rep->children, "fulltext"))
> + /* This is where we would handle diffy representations, to construct
> + a NODE-VERSION given its REPRESENTATION. But I want to get the
> + essentials working before I add that to the mix. */
> + SVN_ERR (get_representation_skel (&skel, fs, id, db_txn, pool));
> + if (svn_fs__list_length (skel) != 2
> + || ! svn_fs__is_atom (skel->children, "fulltext"))
> return corrupt_representation (fs, id);
> +
> + *skel_p = skel->children->next;
> + return 0;
> +}
> +
> +
> +/* Store SKEL as the NODE-VERSION skel for the node ID in FS, as part
> + of the Berkeley DB transaction DB_TXN. Use POOL for any necessary
> + temporary allocation. */
> +static svn_error_t *
> +put_node_version_skel (svn_fs_t *fs,
> + svn_fs_id_t *id,
> + skel_t *skel,
> + DB_TXN *db_txn,
> + apr_pool_t *pool)
> +{
> + /* We always write out new nodes as fulltext. Converting older
> + nodes to deltas against this one happens later, when we call
> + svn_fs__stable_node. */
> +
> + /* Start with a dummy "fulltext" skel, and just drop in the
> + NODE-VERSION skel for this node. */
> + static skel_t rep[] = {
> + { 0, 0, 0, &rep[1], 0 },
> + { 1, "fulltext", 8, 0, 0 }
> + };
> +
> + rep[1].next = skel;
> + skel->next = 0;
> +
> + SVN_ERR (put_representation_skel (fs, id, &rep[0], db_txn, pool));
> +
> + return 0;
> +}
> +
> +
> +
> +/* Examining and building HEADER skels. */
> +
> +
> +/* A structure for the various items that might be in a HEADER skel. */
> +typedef struct header_values_t {
> +
> + /* The header's KIND field. */
> + skel_t *kind;
> +
> + /* The header's property list. */
> + skel_t *proplist;
> +
> + /* The transaction ID in the header's "mutable" flag, or zero
> + if the flag is absent. */
> + skel_t *mutable;
> +
> +} header_values_t;
> +
> +
> +/* Parse the HEADER in the NODE-VERSION skel SKEL, and fill in VALUES
> + with pointers to the appropriate sub-skels. Return non-zero if
> + HEADER is well-formed, zero otherwise. */
> +
> +static int
> +parse_header (skel_t *skel, header_values_t *values)
> +{
> + skel_t *header;
> +
> + memset (values, 0, sizeof (*values));
> +
> + /* The NODE-VERSION skel must be a list of at least one element. */
> + if (skel->is_atom
> + || ! skel->children)
> + return 0;
> + header = skel->children;
> +
> + /* Check the form of the HEADER skel, up to the flags. */
> + if (header->is_atom
> + || ! header->children
> + || ! header->children->is_atom
> + || ! header->children->next
> + || header->children->next->is_atom)
> + return 0;
> +
> + /* Extract the fixed portion. */
> + values->kind = header->children;
> + values->proplist = header->children->next;
> +
> + /* Parse the FLAG ... list. */
> + {
> + skel_t *flag;
> +
> + for (flag = header->children->next->next; flag; flag = flag->next)
> + /* Is it a "mutable" flag? */
> + if (svn_fs__list_length (flag) == 2
> + && svn_fs__is_atom (flag->children, "mutable")
> + && flag->children->next->is_atom)
> + values->mutable = flag->children->next;
> + else
> + return 0;
> + }
> +
> + return 1;
> +}
> +
> +
> +
> +/* Creating and opening a filesystem's `nodes' table. */
> +
> +
> +/* Compare two node ID's, according to the rules in `structure'. */
> +static int
> +compare_ids (svn_fs_id_t *a, svn_fs_id_t *b)
> +{
> + int i = 0;
> +
> + while (a[i] == b[i])
> + {
> + if (a[i] == -1)
> + return 0;
> + i++;
> + }
>
> - *skel = rep->children->next;
> + /* Different nodes, or different branches, are ordered by their
> + node / branch numbers. */
> + if ((i & 1) == 0)
> + return a[i] - b[i];
> +
> + /* This function is only prepared to handle node version ID's. */
> + if (a[i] == -1 || b[i] == -1)
> + abort ();
> +
> + /* Different versions of the same node are ordered by version number. */
> + if (a[i + 1] == -1 && b[i + 1] == -1)
> + return a[i] - b[i];
> +
> + /* A branch off of any version of a node comes after all versions
> + of that node. */
> + if (a[i + 1] == -1)
> + return -1;
> + if (b[i + 1] == -1)
> + return 1;
> +
> + /* Branches are ordered by increasing version number. */
> + return a[i] - b[i];
> +}
> +
> +
> +/* Parse a node version ID from D.
> + Return zero if D does not contain a well-formed node version ID. */
> +static svn_fs_id_t *
> +parse_node_version_dbt (const DBT *d)
> +{
> + svn_fs_id_t *id = svn_fs__parse_id (d->data, d->size, 0);
> +
> + if (! id)
> + return 0;
> +
> + /* It must be a node version ID, not a node ID. */
> + if (svn_fs_id_length (id) & 1)
> + {
> + free (id);
> + return 0;
> + }
> +
> + return id;
> +}
> +
> +
> +/* The key comparison function for the `nodes' table.
> +
> + Strictly speaking, this function only needs to handle strings that
> + we actually use as keys in the table. However, if we happen to
> + insert garbage keys, and this comparison function doesn't do
> + something consistent with them (i.e., something transitive and
> + reflexive), we can actually corrupt the btree structure. Which
> + seems unfriendly.
> +
> + So this function tries to act as a proper comparison for any two
> + arbitrary byte strings. Two well-formed node versions ID's compare
> + according to the rules described in the `structure' file; any
> + malformed key comes before any well-formed key; and two malformed
> + keys come in byte-by-byte order. */
> +static int
> +compare_nodes_keys (const DBT *ak, const DBT *bk)
> +{
> + svn_fs_id_t *a = parse_node_version_dbt (ak);
> + svn_fs_id_t *b = parse_node_version_dbt (bk);
> + int result;
> +
> + /* Two well-formed keys are compared by the rules in `structure'. */
> + if (a && b)
> + result = compare_ids (a, b);
> +
> + /* Malformed keys come before well-formed keys. */
> + else if (a)
> + result = 1;
> + else if (b)
> + result = -1;
> +
> + /* Two malformed keys are compared byte-by-byte. */
> + else
> + result = svn_fs__compare_dbt (ak, bk);
> +
> + if (a) free (a);
> + if (b) free (b);
> +
> + return result;
> +}
> +
> +
> +/* Open / create FS's `nodes' table. FS->env must already be open;
> + this function initializes FS->nodes. If CREATE is non-zero, assume
> + we are creating the filesystem afresh; otherwise, assume we are
> + simply opening an existing database. */
> +static svn_error_t *
> +make_nodes (svn_fs_t *fs, int create)
> +{
> + DB *nodes;
> +
> + SVN_ERR (DB_WRAP (fs, "allocating `nodes' table object",
> + db_create (&nodes, fs->env, 0)));
> + SVN_ERR (DB_WRAP (fs, "setting `nodes' comparison function",
> + nodes->set_bt_compare (nodes, compare_nodes_keys)));
> + SVN_ERR (DB_WRAP (fs,
> + (create
> + ? "creating `nodes' table"
> + : "opening `nodes' table"),
> + nodes->open (nodes, "nodes", 0, DB_BTREE,
> + create ? (DB_CREATE | DB_EXCL) : 0,
> + 0666)));
> +
> + if (create)
> + {
> + /* Create node 1.1, the initial root directory. */
> + static char node_1_1[] = "(fulltext ((directory ()) ()))";
> + skel_t *rep_skel = svn_fs__parse_skel (node_1_1,
> + sizeof (node_1_1) - 1,
> + fs->pool);
> + static svn_fs_id_t id_1_1[] = { 1, 1, -1 };
> +
> + SVN_ERR (put_representation_skel (fs, id_1_1, rep_skel, 0, fs->pool));
> + }
> +
> + fs->nodes = nodes;
> return 0;
> }
>
>
> +svn_error_t *
> +svn_fs__create_nodes (svn_fs_t *fs)
> +{
> + return make_nodes (fs, 1);
> +}
> +
> +
> +svn_error_t *
> +svn_fs__open_nodes (svn_fs_t *fs)
> +{
> + return make_nodes (fs, 0);
> +}
> +
> +
>
> /* The node cache. */
>
> @@ -323,7 +634,8 @@
> svn_error_t *
> svn_fs__open_node_by_id (svn_fs_node_t **node_p,
> svn_fs_t *fs,
> - svn_fs_id_t *id)
> + svn_fs_id_t *id,
> + DB_TXN *db_txn)
> {
> svn_fs_node_t *node = get_cached_node (fs, id);
>
> @@ -332,24 +644,53 @@
> if (! node)
> {
> apr_pool_t *skel_pool = svn_pool_create (fs->pool);
> - skel_t *nv, *kind;
> + skel_t *nv;
> + header_values_t values;
>
> - SVN_ERR (get_node_version_skel (&nv, fs, id, skel_pool));
> - if (svn_fs__list_length (nv) < 2
> - || ! nv->children->is_atom)
> + SVN_ERR (get_node_version_skel (&nv, fs, id, db_txn, skel_pool));
> + if (! parse_header (nv, &values))
> return corrupt_node_version (fs, id);
>
> - kind = nv->children;
> - if (svn_fs__is_atom (kind, "file"))
> - SVN_ERR (svn_fs__file_from_skel (&node, fs, id, nv, skel_pool));
> - else if (svn_fs__is_atom (kind, "dir"))
> - SVN_ERR (svn_fs__dir_from_skel (&node, fs, id, nv, skel_pool));
> + /* If the node is immutable, use SKEL_POOL as the node's pool.
> + We're going to keep the skel around, so this puts the node
> + and its skel in the same pool.
> +
> + For mutable nodes, we allocate the node in a separate pool
> + and toss the node's skel, because it may change. */
> + {
> + apr_pool_t *node_pool
> + = values.mutable ? svn_pool_create (fs->pool) : skel_pool;
> +
> + node = apr_pcalloc (node_pool, sizeof (*node));
> + node->pool = node_pool;
> + }
> +
> + node->fs = fs;
> + node->id = svn_fs_copy_id (id, node->pool);
> +
> + /* What kind of node is this? */
> + if (svn_fs__is_atom (values.kind, "file"))
> + node->kind = kind_file;
> + else if (svn_fs__is_atom (values.kind, "dir"))
> + node->kind = kind_directory;
> else
> return corrupt_node_version (fs, id);
>
> + if (values.mutable)
> + /* For mutable nodes, we record the transaction ID. */
> + node->txn_id = apr_pstrndup (node->pool,
> + values.mutable->data,
> + values.mutable->len);
> + else
> + /* For immutable nodes, we save the node version skel. */
> + node->node_version = nv;
> +
> + /* Add NODE to the filesystem's cache. */
> cache_node (node);
>
> - apr_destroy_pool (skel_pool);
> + /* Free the skel, if appropriate. */
> + if (skel_pool != node->pool)
> + apr_destroy_pool (skel_pool);
> }
>
> *node_p = node;
> @@ -358,181 +699,517 @@
>
>
>
> -/* Common initialization for all new node objects. */
> +/* Creating new nodes. */
>
> -svn_fs_node_t *
> -svn_fs__init_node (apr_size_t size,
> - svn_fs_t *fs,
> - svn_fs_id_t *id,
> - kind_t kind)
> -{
> - /* Create the node's subpool. */
> - apr_pool_t *pool = svn_pool_create (fs->pool);
> - svn_fs_node_t *node = apr_pcalloc (pool, size);
>
> +/* Check FS's `nodes' table to find an unused node number, and set *ID_P
> + to the ID of the first version of an entirely new node in FS, as
> + part of DB_TXN. Allocate the new ID in POOL. */
> +static svn_error_t *
> +new_node_id (svn_fs_id_t **id_p,
> + svn_fs_t *fs,
> + DB_TXN *db_txn,
> + apr_pool_t *pool)
> +{
> + int db_err;
> + DBC *cursor = 0;
> + DBT key, value;
> + svn_fs_id_t *id;
> +
> + /* Create a database cursor. */
> + SVN_ERR (DB_WRAP (fs, "choosing new node ID (creating cursor)",
> + fs->nodes->cursor (fs->nodes, db_txn, &cursor, 0)));
> +
> + /* Find the last entry in the `nodes' table, and increment its node
> + number. */
> + db_err = cursor->c_get (cursor,
> + svn_fs__result_dbt (&key),
> + svn_fs__nodata_dbt (&value),
> + DB_LAST);
> + if (db_err)
> + {
> + /* Free the cursor. Ignore any error value --- the error above
> + is more interesting. */
> + cursor->c_close (cursor);
> +
> + if (db_err == DB_NOTFOUND)
> + /* The root directory should always be present, at least. */
> + return
> + svn_error_createf
> + (SVN_ERR_FS_CORRUPT, 0, 0, fs->pool,
> + "root directory missing from `nodes' table, in filesystem `%s'",
> + fs->env_path);
> +
> + SVN_ERR (DB_WRAP (fs, "choosing new node ID (finding last entry)",
> + db_err));
> + }
> + svn_fs__track_dbt (&key, pool);
> +
> + /* Try to parse the key as a node ID. */
> + id = svn_fs__parse_id (key.data, key.size, pool);
> + if (! id)
> + {
> + cursor->c_close (cursor);
> + return corrupt_nodes_key (fs);
> + }
> +
> + /* We've got the value; close the cursor. */
> + SVN_ERR (DB_WRAP (fs, "choosing new node ID (closing cursor)",
> + cursor->c_close (cursor)));
> +
> + *id_p = id;
> + return 0;
> +}
> +
> +
> +svn_error_t *
> +svn_fs__create_node (svn_fs_node_t **node_p,
> + svn_fs_t *fs,
> + skel_t *skel,
> + DB_TXN *db_txn)
> +{
> + svn_fs_node_t *node;
> + header_values_t values;
> +
> + /* Check that SKEL has a well-formed header, with its "mutable" flag set. */
> + if (! parse_header (skel, &values)
> + || ! values.mutable)
> + abort ();
> +
> + /* Allocate a new node structure. */
> + {
> + apr_pool_t *pool = svn_pool_create (fs->pool);
> + node = NEW (pool, svn_fs_node_t);
> + memset (node, 0, sizeof (*node));
> + node->pool = pool;
> + }
> node->fs = fs;
> - node->pool = pool;
> - node->id = svn_fs_copy_id (id, node->pool);
> - node->kind = kind;
> + node->txn_id = apr_pstrndup (node->pool,
> + values.mutable->data,
> + values.mutable->len);
> +
> + /* Find an ID for the node. */
> + SVN_ERR (new_node_id (&node->id, node->fs, db_txn, node->pool));
> +
> + /* Store its representation. */
> + SVN_ERR (put_representation_skel (node->fs, node->id, skel, db_txn,
> + node->pool));
>
> - return node;
> + /* Add it to the cache. */
> + cache_node (node);
> +
> + *node_p = node;
> + return 0;
> }
>
>
>
> -/* Creating and opening a filesystem's `nodes' table. */
> +/* Creating successor node versions. */
>
>
> -/* Compare two node ID's, according to the rules in `structure'. */
> +/* Find the last entry before KEY in the btree table DB.
> +
> + KEY must be initialized as for any normal Berkeley DB operation.
> + The settings of KEY->flags and KEY's other members control how the
> + value is returned.
> +
> + If DB_TXN is non-zero, perform the operation as part of that
> + Berkeley DB transaction. */
> static int
> -compare_ids (svn_fs_id_t *a, svn_fs_id_t *b)
> +last_key_before (DB *db,
> + DB_TXN *db_txn,
> + DBT *key)
> {
> - int i = 0;
> + int db_err;
> + DBC *cursor;
> + DBT temp_key, value;
>
> - while (a[i] == b[i])
> + /* Create a cursor into the table. */
> + DB_ERR (db->cursor (db, db_txn, &cursor, 0));
> +
> + /* Position CURSOR to the first table entry at or after KEY.
> + Don't bother retrieving the key or value we find there. */
> + svn_fs__nodata_dbt (&temp_key);
> + temp_key.data = key->data;
> + temp_key.size = key->size;
> + svn_fs__nodata_dbt (&value);
> + db_err = cursor->c_get (cursor, &temp_key, &value, DB_SET_RANGE);
> + if (db_err && db_err != DB_NOTFOUND)
> {
> - if (a[i] == -1)
> - return 0;
> - i++;
> + cursor->c_close (cursor);
> + return db_err;
> }
>
> - /* Different nodes, or different branches, are ordered by their
> - node / branch numbers. */
> - if ((i & 1) == 0)
> - return a[i] - b[i];
> + /* If db_err == 0, we found the first table entry at or after KEY;
> + the record we want comes immediately before that.
>
> - /* An id that ends after a node/branch number isn't well-formed. */
> - if (a[i] == -1)
> - return -1;
> - if (b[i] == -1)
> - return 1;
> -
> - /* Different versions of the same node are ordered by version
> - number, with "head" coming after all versions. */
> - if (a[i + 1] == -1 && b[i + 1] == -1)
> + If db_err == DB_NOTFOUND, then we couldn't find any entry at or
> + after KEY, so the record we want must be the last record in the
> + table. */
> + db_err = cursor->c_get (cursor, key, svn_fs__nodata_dbt (&value),
> + db_err == DB_NOTFOUND ? DB_LAST : DB_PREV);
> + if (db_err)
> {
> - if (a[i] == -2)
> - return 1;
> - if (b[i] == -2)
> - return -1;
> - return a[i] - b[i];
> + cursor->c_close (cursor);
> + return db_err;
> }
>
> - /* A branch off of any version of a node comes after all versions
> - of that node. */
> - if (a[i + 1] == -1)
> - return -1;
> - if (b[i + 1] == -1)
> - return 1;
> + /* We're finished with the cursor now. */
> + DB_ERR (cursor->c_close (cursor));
>
> - /* Branches are ordered by increasing version number. */
> - return a[i] - b[i];
> + return 0;
> }
>
> -
> -/* The key comparison function for the `nodes' table.
> -
> - Strictly speaking, this function only needs to handle strings that
> - we actually use as keys in the table. However, if we happen to
> - insert garbage keys, and this comparison function doesn't do
> - something consistent with them (i.e., something transitive and
> - reflexive), we can actually corrupt the btree structure. Which
> - seems unfriendly.
>
> - So this function tries to act as a proper comparison for any two
> - arbitrary byte strings. Two well-formed node versions ID's compare
> - according to the rules described in the `structure' file; any
> - malformed key comes before any well-formed key; and two malformed
> - keys come in byte-by-byte order. */
> -static int
> -compare_nodes_keys (const DBT *ak, const DBT *bk)
> +/* Set *SUCCESSOR_P to the ID of an immediate successor to node
> + version ID in FS that does not exist yet, as part of the Berkeley
> + DB transaction DB_TXN. Do any needed temporary allocation in POOL.
> +
> + If ID is the youngest version of its node, then the successor is
> + simply ID with its rightmost version number increased; otherwise,
> + the successor is a new branch from ID. */
> +static svn_error_t *
> +new_successor_id (svn_fs_id_t **successor_p,
> + svn_fs_t *fs,
> + svn_fs_id_t *id,
> + DB_TXN *db_txn,
> + apr_pool_t *pool)
> {
> - svn_fs_id_t *a = svn_fs__parse_id (ak->data, ak->size, svn_fs__key_id, 0);
> - svn_fs_id_t *b = svn_fs__parse_id (bk->data, bk->size, svn_fs__key_id, 0);
> + int id_len = svn_fs_id_length (id);
> + svn_fs_id_t *new_id;
> + DBT key, value;
> + int db_err;
>
> - /* Is either a or b malformed? */
> - if (! a && ! b)
> - return svn_fs__compare_dbt (ak, bk);
> - else if (! a)
> - {
> - free (b);
> - return -1;
> - }
> - else if (! b)
> + /* Make sure ID is really a node version ID. */
> + if (id_len & 1)
> + return not_a_node_version_id (fs, id);
> +
> + /* Set NEW_ID to the next node version after ID. Allocate some
> + extra room, in case we need to construct a branch ID below. */
> + new_id = NEWARRAY (pool, svn_fs_id_t, (id_len + 3) * sizeof (*id));
> + memcpy (new_id, id, (id_len + 1) * sizeof (*id)); /* copy the -1 */
> + new_id[id_len - 1]++; /* increment the version number */
> +
> + /* Check to see if there already exists a node whose ID is NEW_ID. */
> + db_err = fs->nodes->get (fs->nodes, db_txn,
> + svn_fs__id_to_dbt (&key, new_id, pool),
> + svn_fs__nodata_dbt (&value),
> + 0);
> + if (db_err == DB_NOTFOUND)
> {
> - free (a);
> - return 1;
> + /* NEW_ID isn't currently in use, so return that. */
> + *successor_p = new_id;
> + return 0;
> }
> + else
> + SVN_ERR (DB_WRAP (fs, "checking for next node version", db_err));
>
> - /* Okay, we've got two well-formed keys. Compare them according to
> - the ordering described in `structure'. */
> + /* Okay, the next version of ID already exists, so we'll need to make
> + a new branch. What's the next available branch number?
> +
> + The sort order for the nodes table says that all versions of a
> + node come together, followed by all branches from any version of
> + that node; the branches are sorted by the version they branch
> + from, and then by branch number.
> +
> + So, if our node version ID is N.V, then all its branches will
> + come immediately before the first branch from N.(V+1). So we
> + find the last node in the table before node ID N.(V+1).1.1; that
> + node is (perhaps a branch from) the last branch from N.V.
> +
> + NEW_ID is currently N.(V+1); stick on the ".1.1". */
> + new_id[id_len + 0] = 1;
> + new_id[id_len + 1] = 1;
> + new_id[id_len + 2] = -1;
> + SVN_ERR (DB_WRAP (fs, "checking for next node branch",
> + last_key_before (fs->nodes, db_txn,
> + svn_fs__result_dbt (&key))));
> +
> {
> - int cmp = compare_ids (a, b);
> - free (a);
> - free (b);
> - return cmp;
> + svn_fs_id_t *last_branch_id = svn_fs__parse_id (key.data, key.size, pool);
> + int last_branch_len;
> + if (! last_branch_id)
> + return corrupt_nodes_key (fs);
> + last_branch_len = svn_fs_id_length (last_branch_id);
> +
> + /* Only node version ID's may appear as keys in the `nodes' table. */
> + if (last_branch_len & 1)
> + return corrupt_nodes_key (fs);
> +
> + /* If the last key before NEW_ID is just another version of node N,
> + then there are no branches. */
> + if (last_branch_len == id_len)
> + {
> + memcpy (new_id, id, id_len * sizeof (*id));
> + new_id[id_len + 0] = 1;
> + new_id[id_len + 1] = 1;
> + new_id[id_len + 2] = -1;
> +
> + *successor_p = new_id;
> + return 0;
> + }
> +
> + /* If the last key before NEW_ID is a branch off of ID, then
> + choose the next branch number. */
> + else if (last_branch_len > id_len)
> + {
> + memcpy (new_id, last_branch_id, (id_len + 1) * sizeof (*id));
> + new_id[id_len + 0]++;
> + new_id[id_len + 1] = 1;
> + new_id[id_len + 2] = -1;
> +
> + *successor_p = new_id;
> + return 0;
> + }
> +
> + /* Otherwise, something strange is going on. */
> + else
> + return corrupt_nodes_key (fs);
> }
> }
>
>
> -/* Open / create FS's `nodes' table. FS->env must already be open;
> - this function initializes FS->nodes. If CREATE is non-zero, assume
> - we are creating the filesystem afresh; otherwise, assume we are
> - simply opening an existing database. */
> -static svn_error_t *
> -make_nodes (svn_fs_t *fs, int create)
> -{
> - DB *nodes;
> +svn_error_t *
> +svn_fs__create_successor (svn_fs_node_t **new_p,
> + svn_fs_node_t *old,
> + svn_fs_txn_t *svn_txn,
> + DB_TXN *db_txn)
> +{
> + svn_fs_node_t *new;
> + char *svn_txn_id = svn_fs__txn_id (svn_txn);
> + skel_t *old_skel, *new_skel;
>
> - SVN_ERR (DB_WRAP (fs, "allocating `nodes' table object",
> - db_create (&nodes, fs->env, 0)));
> - SVN_ERR (DB_WRAP (fs, "setting `nodes' comparison function",
> - nodes->set_bt_compare (nodes, compare_nodes_keys)));
> - SVN_ERR (DB_WRAP (fs,
> - (create
> - ? "creating `nodes' table"
> - : "opening `nodes' table"),
> - nodes->open (nodes, "nodes", 0, DB_BTREE,
> - create ? (DB_CREATE | DB_EXCL) : 0,
> - 0666)));
> + /* We should never be creating successors of mutable nodes. */
> + if (svn_fs_node_is_mutable (old))
> + abort ();
>
> - if (create)
> + /* Allocate the new node. */
> + {
> + apr_pool_t *pool = svn_pool_create (old->fs->pool);
> + new = NEW (pool, svn_fs_node_t);
> + new->pool = pool;
> + }
> + new->fs = old->fs;
> + new->kind = old->kind;
> + new->txn_id = apr_pstrdup (new->pool, svn_txn_id);
> +
> + /* Choose an ID for the new node, and store it in the database. */
> + SVN_ERR (new_successor_id (&new->id, new->fs, old->id, db_txn, new->pool));
> +
> + /* Get a copy of the old node's contents. */
> + SVN_ERR (svn_fs__get_node_version (&old_skel, old, db_txn, new->pool));
> + new_skel = svn_fs__copy_skel (old_skel, new->pool);
> +
> + /* Add a "mutable" flag to the HEADER skel for SVN_TXN. */
> + {
> + /* Build a skel of the form ("mutable" TXN-ID). */
> + skel_t *mutable_flag = svn_fs__make_empty_list (new->pool);
> +
> + svn_fs__prepend (svn_fs__make_atom (svn_txn_id, new->pool), mutable_flag);
> + svn_fs__prepend (svn_fs__make_atom ("mutable", new->pool), mutable_flag);
> +
> + /* Insert this at the beginning of the new skel's flag list. We
> + know there is no "mutable" flag there already, since we've
> + checked that OLD is immutable. */
> {
> - /* Create node 0.0, the initial root directory. */
> - static char node_0_0[] = "(fulltext (directory () ()))";
> - skel_t *rep_skel = svn_fs__parse_skel (node_0_0,
> - sizeof (node_0_0) - 1,
> - fs->pool);
> - static svn_fs_id_t id_0_0[] = { 0, 0, -1 };
> -
> - SVN_ERR (put_representation_skel (fs, 0, id_0_0, rep_skel, fs->pool));
> + skel_t *proplist = new_skel->children->children->next;
> + mutable_flag->next = proplist;
> + proplist->next = mutable_flag;
> }
> + }
>
> - fs->nodes = nodes;
> + /* Write this out as the new node's contents. */
> + SVN_ERR (put_node_version_skel (new->fs, new->id, new_skel, db_txn,
> + new->pool));
> +
> + /* Add it to the cache. */
> + cache_node (new);
> +
> + *new_p = new;
> return 0;
> }
>
> +
> +
> +/* Public functions for reading and writing node contents. */
> +
> svn_error_t *
> -svn_fs__create_nodes (svn_fs_t *fs)
> +svn_fs__get_node_version (skel_t **skel_p,
> + svn_fs_node_t *node,
> + DB_TXN *db_txn,
> + apr_pool_t *pool)
> {
> - return make_nodes (fs, 1);
> + skel_t *skel;
> + header_values_t values;
> +
> + SVN_ERR (get_node_version_skel (&skel, node->fs, node->id, db_txn, pool));
> +
> + if (! parse_header (skel, &values))
> + return corrupt_node_version (node->fs, node->id);
> +
> + *skel_p = skel;
> + return 0;
> }
>
>
> svn_error_t *
> -svn_fs__open_nodes (svn_fs_t *fs)
> +svn_fs__put_node_version (svn_fs_node_t *node,
> + skel_t *skel,
> + DB_TXN *db_txn)
> +{
> + header_values_t values;
> +
> + /* Check that SKEL has a well-formed header, with its "mutable" flag set. */
> + if (! parse_header (skel, &values)
> + || ! values.mutable)
> + abort ();
> +
> + SVN_ERR (put_node_version_skel (node->fs, node->id, skel, db_txn,
> + node->pool));
> +
> + return 0;
> +}
> +
> +
> +
> +/* Deltifying nodes. */
> +
> +
> +svn_error_t *
> +svn_fs__stable_node (svn_fs_node_t *node)
> {
> - return make_nodes (fs, 0);
> + /* Not yet. */
> + return 0;
> }
>
>
>
> +/* Retrieving node properties. */
> +
> +svn_error_t *
> +svn_fs_get_node_prop (svn_string_t **value_p,
> + svn_fs_node_t *node,
> + svn_string_t *propname,
> + apr_pool_t *pool)
> +{
> + apr_pool_t *skel_pool;
> + skel_t *node_version;
> + header_values_t values;
> +
> + /* If the node is mutable, we'll get our own copy of the entire
> + node skeleton. Don't keep that around. */
> + if (svn_fs_node_is_mutable (node))
> + skel_pool = svn_pool_create (pool);
> + else
> + skel_pool = pool;
> +
> + SVN_ERR (svn_fs__get_node_version (&node_version, node, 0, skel_pool));
> +
> + if (! parse_header (node_version, &values))
> + return corrupt_node_version (node->fs, node->id);
> +
> + /* Scan the property list for a property with the right name. */
> + {
> + skel_t *prop;
> +
> + /* Walk the property list two elements at a time. */
> + for (prop = values.proplist->children; prop; prop = prop->next->next)
> + {
> + /* The proplist must be composed of pairs of atoms. */
> + if (! prop->is_atom
> + || ! prop->next
> + || ! prop->next->is_atom)
> + return corrupt_node_version (node->fs, node->id);
> +
> + if (prop->len == propname->len
> + && ! memcmp (prop->data, propname->data, prop->len))
> + break;
> + }
> +
> + /* Did we find something? */
> + if (prop)
> + *value_p = svn_string_ncreate (prop->next->data,
> + prop->next->len,
> + pool);
> + else
> + *value_p = 0;
> +
> + /* If we read in a copy of the skel just for this operation, free it. */
> + if (skel_pool != pool)
> + apr_destroy_pool (skel_pool);
> +
> + return 0;
> + }
> +}
> +
> +
> +svn_error_t *
> +svn_fs_get_node_proplist (apr_hash_t **table_p,
> + svn_fs_node_t *node,
> + apr_pool_t *pool)
> +{
> + apr_pool_t *skel_pool;
> + skel_t *node_version;
> + header_values_t values;
> + apr_hash_t *table;
> +
> + /* If the node is mutable, we'll get our own copy of the entire
> + node skeleton. Don't keep that around. */
> + if (svn_fs_node_is_mutable (node))
> + skel_pool = svn_pool_create (pool);
> + else
> + skel_pool = pool;
> +
> + SVN_ERR (svn_fs__get_node_version (&node_version, node, 0, skel_pool));
> +
> + if (! parse_header (node_version, &values))
> + return corrupt_node_version (node->fs, node->id);
> +
> + table = apr_make_hash (pool);
> +
> + /* Scan the property list and build a hash table. */
> + {
> + skel_t *prop;
> +
> + /* Walk the property list two elements at a time. */
> + for (prop = values.proplist->children; prop; prop = prop->next->next)
> + {
> + /* The proplist must be composed of pairs of atoms. */
> + if (! prop->is_atom
> + || ! prop->next
> + || ! prop->next->is_atom)
> + return corrupt_node_version (node->fs, node->id);
> +
> + /* Copy the name and value. If NODE is mutable, the originals
> + are allocated in skel_pool, which will go away soon. If
> + it's immutable, they're allocated in the node's pool, which
> + will go away when the node is closed. */
> + {
> + char *name = apr_pstrndup (pool, prop->data, prop->len);
> + svn_string_t *value = svn_string_ncreate (prop->next->data,
> + prop->next->len,
> + pool);
> +
> + apr_hash_set (table, name, prop->len, value);
> + }
> + }
> + }
> +
> + if (skel_pool != pool)
> + apr_destroy_pool (skel_pool);
> +
> + *table_p = table;
> + return 0;
> +}
> +
> +
> +
> /* Casting, typing, and other trivial bookkeeping operations on nodes. */
>
> int
> svn_fs_node_is_dir (svn_fs_node_t *node)
> {
> - return node->kind == kind_dir;
> + return node->kind == kind_directory;
> }
>
>
> @@ -549,11 +1226,33 @@
> close_node (node);
> }
>
> +
> +svn_fs_node_t *
> +svn_fs__reopen_node (svn_fs_node_t *node)
> +{
> + node->open_count++;
> + return node;
> +}
> +
> +
> +int
> +svn_fs_node_is_mutable (svn_fs_node_t *node)
> +{
> + return node->txn_id != 0;
> +}
> +
> +
> +svn_fs_t *
> +svn_fs__node_fs (svn_fs_node_t *node)
> +{
> + return node->fs;
> +}
> +
>
> -svn_fs_proplist_t *
> -svn_fs_node_proplist (svn_fs_node_t *node)
> +svn_fs_id_t *
> +svn_fs__node_id (svn_fs_node_t *node)
> {
> - return node->proplist;
> + return node->id;
> }
>
>
>
>
>
> 1.4 +83 -22 subversion/subversion/libsvn_fs/node.h
>
> Index: node.h
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/node.h,v
> retrieving revision 1.3
> retrieving revision 1.4
> diff -u -r1.3 -r1.4
> --- node.h 2000/10/11 18:18:29 1.3
> +++ node.h 2000/10/27 21:43:49 1.4
> @@ -49,7 +49,15 @@
> #ifndef SVN_LIBSVN_FS_NODE_H
> #define SVN_LIBSVN_FS_NODE_H
>
> +#include "db.h"
> +#include "svn_fs.h"
> +#include "skel.h"
>
> +
> +
> +/* Creating and opening the Berkeley DB `nodes' table. */
> +
> +
> /* Create a new `nodes' table for the new filesystem FS. FS->env must
> already be open; this sets FS->nodes. */
> svn_error_t *svn_fs__create_nodes (svn_fs_t *fs);
> @@ -60,32 +68,85 @@
> svn_error_t *svn_fs__open_nodes (svn_fs_t *fs);
>
>
> -/* Set *NODE to the node identified by ID in FS.
> +
> +/* Operations on nodes. */
>
> - If POOL is non-zero, allocate the new node there.
> -
> - Multiple opens of the same ID return the same object. If we
> - already have a node object for that node, increment that object's
> - open count and, return a pointer to it. */
> -svn_error_t *svn_fs__open_node_by_id (svn_fs_node_t **node,
> +/* Open the node identified by ID in FS, and set *NODE_P to point to
> + it, as part of the Berkeley DB transaction DB_TXN. */
> +svn_error_t *svn_fs__open_node_by_id (svn_fs_node_t **node_p,
> svn_fs_t *fs,
> - svn_fs_id_t *id);
> + svn_fs_id_t *id,
> + DB_TXN *db_txn);
> +
> +
> +/* Create and open an entirely new, mutable node in the filesystem FS,
> + whose NODE-VERSION skel is SKEL, and set *NODE_P to point to it, as
> + part of the Berkeley DB transaction DB_TXN. SKEL must have a
> + well-formed HEADER, with the "mutable" flag set.
> +
> + After this call, the node table manager assumes that the new node's
> + contents will change frequently. */
> +svn_error_t *svn_fs__create_node (svn_fs_node_t **node_p,
> + svn_fs_t *fs,
> + skel_t *skel,
> + DB_TXN *db_txn);
> +
> +
> +/* Create and open a mutable node which is an immediate successor of
> + OLD, and set *NEW_P to point to it. Do this as part of the
> + Berkeley DB transaction DB_TXN, and the Subversion transaction
> + SVN_TXN.
> +
> + After this call, the node table manager assumes that the new node's
> + contents will change frequently. */
> +svn_error_t *svn_fs__create_successor (svn_fs_node_t **new_p,
> + svn_fs_node_t *old,
> + svn_fs_txn_t *svn_txn,
> + DB_TXN *db_txn);
> +
> +
> +/* Open a new reference to NODE. The returned node will remain open
> + after NODE is closed, and can be closed without closing NODE. */
> +svn_fs_node_t *svn_fs__reopen_node (svn_fs_node_t *node);
> +
> +
> +/* Return the filesystem NODE lives in. */
> +svn_fs_t *svn_fs__node_fs (svn_fs_node_t *node);
> +
> +
> +/* Return the ID of NODE. The result is live for as long as NODE is. */
> +svn_fs_id_t *svn_fs__node_id (svn_fs_node_t *node);
> +
> +
> +/* Set *SKEL_P to the NODE-VERSION skel for NODE, as part of the
> + Berkeley DB transaction DB_TXN. *SKEL_P is guaranteed to be a list
> + at least one element long, whose first element is a well-formed
> + HEADER skel.
> +
> + If NODE is mutable, the skel, and the data it points into, are
> + allocated in POOL. If NODE is immutable, the skel is owned by the
> + node object, and the caller must not change it. */
> +svn_error_t *svn_fs__get_node_version (skel_t **skel_p,
> + svn_fs_node_t *node,
> + DB_TXN *db_txn,
> + apr_pool_t *pool);
> +
> +
> +/* Store SKEL as the NODE-VERSION skel for NODE, as part of the
> + Berkeley DB transaction DB_TXN.
> +
> + After this call, the node table manager assumes that NODE's
> + contents will change frequently. */
> +svn_error_t *svn_fs__put_node_version (svn_fs_node_t *node,
> + skel_t *skel,
> + DB_TXN *db_txn);
>
>
> -/* Allocate a node, and initialize the svn_fs_node_t portion of it.
> - SIZE should be the overall size of the object (e.g., svn_fs_file_t,
> - svn_fs_dir_t). ID should be the node's version id in the
> - filesystem FS. KIND indicates what kind of node this is.
> -
> - The new node lives in its own subpool of FS.
> -
> - The new node is not yet added to FS's node cache, and its open
> - count is zero. */
> -svn_fs_node_t *
> -svn_fs__init_node (apr_size_t size,
> - svn_fs_t *fs,
> - svn_fs_id_t *id,
> - kind_t kind);
> +/* Indicate that the contents of NODE are expected to be stable. This
> + suggests to the node table manager that it would be effective to
> + represent other nodes' contents as deltas against NODE's contents,
> + if it so desired. */
> +svn_error_t *svn_fs__stable_node (svn_fs_node_t *node);
>
>
> #endif /* SVN_LIBSVN_FS_NODE_H */
>
>
>
> 1.6 +79 -0 subversion/subversion/libsvn_fs/skel.c
>
> Index: skel.c
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/skel.c,v
> retrieving revision 1.5
> retrieving revision 1.6
> diff -u -r1.5 -r1.6
> --- skel.c 2000/10/12 21:21:50 1.5
> +++ skel.c 2000/10/27 21:43:49 1.6
> @@ -459,6 +459,42 @@
>
>
>
> +/* Building skels. */
> +
> +
> +skel_t *
> +svn_fs__make_atom (char *str, apr_pool_t *pool)
> +{
> + skel_t *skel = NEW (pool, skel_t);
> + skel->is_atom = 1;
> + skel->data = str;
> + skel->len = strlen (str);
> +
> + return skel;
> +}
> +
> +
> +skel_t *
> +svn_fs__make_empty_list (apr_pool_t *pool)
> +{
> + skel_t *skel = NEW (pool, skel_t);
> +
> + skel->is_atom = 0;
> + skel->children = 0;
> +
> + return skel;
> +}
> +
> +
> +void
> +svn_fs__prepend (skel_t *skel, skel_t *list)
> +{
> + skel->next = list->children;
> + list->children = skel->next;
> +}
> +
> +
> +
> /* Examining skels. */
>
>
> @@ -495,3 +531,46 @@
> return len;
> }
> }
> +
> +
> +
> +/* Copying skels. */
> +
> +
> +skel_t *
> +svn_fs__copy_skel (skel_t *skel, apr_pool_t *pool)
> +{
> + skel_t *copy = NEW (pool, skel_t);
> +
> + if (skel->is_atom)
> + {
> + apr_size_t len = skel->len;
> +
> + copy->is_atom = 1;
> + copy->data = NEWARRAY (pool, char, len);
> + copy->len = len;
> + memcpy (copy->data, skel->data, len);
> + copy->children = copy->next = 0;
> + }
> + else
> + {
> + skel_t *skel_child, **copy_child_ptr;
> +
> + copy->is_atom = 0;
> + copy->data = 0;
> + copy->len = 0;
> +
> + copy_child_ptr = ©->children;
> + for (skel_child = skel->children;
> + skel_child;
> + skel_child = skel_child->next)
> + {
> + *copy_child_ptr = svn_fs__copy_skel (skel_child, pool);
> + copy_child_ptr = &(*copy_child_ptr)->next;
> + }
> + *copy_child_ptr = 0;
> + }
> +
> + return copy;
> +}
> +
>
>
>
> 1.4 +19 -5 subversion/subversion/libsvn_fs/skel.h
>
> Index: skel.h
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/skel.h,v
> retrieving revision 1.3
> retrieving revision 1.4
> diff -u -r1.3 -r1.4
> --- skel.h 2000/10/06 18:04:39 1.3
> +++ skel.h 2000/10/27 21:43:49 1.4
> @@ -123,8 +123,9 @@
>
>
>
> -/* Parsing and unparsing skels. */
> +/* Operations on skels. */
>
> +
> /* Parse the LEN bytes at DATA as the concrete representation of a
> skel, and return a skel object allocated from POOL describing its
> contents. If the data is not a properly-formed SKEL object, return
> @@ -142,15 +143,25 @@
> skel_t *svn_fs__parse_skel (char *data, apr_size_t len,
> apr_pool_t *pool);
>
> +
> +/* Create an atom skel whose contents are the C string STR, allocated
> + from POOL. */
> +skel_t *svn_fs__make_atom (char *str, apr_pool_t *pool);
> +
> +
> +/* Create an empty list skel, allocated from POOL. */
> +skel_t *svn_fs__make_empty_list (apr_pool_t *pool);
> +
> +
> +/* Prepend SKEL to LIST. */
> +void svn_fs__prepend (skel_t *skel, skel_t *list);
> +
> +
> /* Return a string whose contents are a concrete representation of
> SKEL. Allocate the string from POOL. */
> svn_string_t *svn_fs__unparse_skel (skel_t *skel, apr_pool_t *pool);
>
>
> -
> -/* Examining skels. */
> -
> -
> /* Return true iff SKEL is an atom whose data is the same as STR. */
> int svn_fs__is_atom (skel_t *skel, const char *str);
>
> @@ -158,5 +169,8 @@
> /* Return the length of the list skel SKEL. Atoms have a length of -1. */
> int svn_fs__list_length (skel_t *skel);
>
> +
> +/* Make a copy of SKEL and its data in POOL. */
> +skel_t *svn_fs__copy_skel (skel_t *skel, apr_pool_t *pool);
>
> #endif /* SVN_LIBSVN_FS_SKEL_H */
>
>
>
> 1.13 +89 -42 subversion/subversion/libsvn_fs/structure
>
> Index: structure
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/structure,v
> retrieving revision 1.12
> retrieving revision 1.13
> diff -u -r1.12 -r1.13
> --- structure 2000/10/19 22:54:23 1.12
> +++ structure 2000/10/27 21:43:49 1.13
> @@ -59,6 +59,9 @@
> Node version numbers start with 1. Thus, N.1 is the first version
> of node N.
>
> +Node / branch numbers start with 1. Thus, N.M.1 is the first
> +branch off of N.M.
> +
> A directory entry identifies the file or subdirectory it refers to
> using a node version number --- not a node number. This means that
> a change to a file far down in a directory hierarchy requires the
> @@ -105,32 +108,36 @@
> numbers, separated by dots.
>
>
> -NODE-VERSION: how we represent a node version
> +NODE-VERSION and HEADER: how we represent a node version
>
> We represent a given version of a file or directory node using a list
> -skel (see skel.h for an explanation of skels). The first element of a
> -node version skel is always an atom indicating what kind of node this
> -is. The second element is always the node's property list. Any
> -remaining elements carry the contents of the node, in a form
> -that depends on the node's kind.
> -
> -A node version skel has one of the following forms:
> -
> -("file" PROPLIST DATA)
> - This node version is a file. DATA is an atom giving the full
> - contents of the file. (In the future, DATA may be a list,
> - indicating that the fulltext of the file is stored elsewhere in
> - the database, or perhaps in an ordinary Unix file.)
> -
> -("directory" PROPLIST (ENTRY ...))
> - This node version is a directory. The list (ENTRY ...) gives the
> - directory entries. Each ENTRY has the form:
> -
> - (NAME ID)
> -
> - where:
> - - NAME is the name of the directory entry, in UTF-8
> - - ID is the ID of the nv to which this entry refers, and
> +skel (see skel.h for an explanation of skels). A node version skel
> +has the form:
> +
> + (HEADER KIND-SPECIFIC ...)
> +
> +where HEADER is a header skel, whose structure is common to all nodes,
> +and the KIND-SPECIFIC elements carry data dependent on what kind of
> +node this is --- file, directory, etc.
> +
> +HEADER has the form:
> +
> + (KIND PROPLIST FLAG ...)
> +
> +where:
> +- KIND indicates what sort of node this is,
> +- PROPLIST is the node's property list, and
> +- FLAG ... is a sequence of zero or more flags.
> +
> +KIND 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).
> +
> +Each FLAG has one of the following forms:
> +
> +("mutable" TXN)
> + This is a mutable node, part of the transaction whose ID is TXN.
> + The "mutable" flag may occur only once in the FLAG ... list.
>
> Note that a node cannot change its kind from one version to the next.
> A directory node is always a directory; a file node is always a file;
> @@ -146,6 +153,35 @@
> overhead of looking in two places.
>
>
> +FILE: how files are represented.
> +
> +If a NODE-VERSION's header's KIND is "file", then the node-version
> +skel represents a file, and has the form:
> +
> + (HEADER DATA)
> +
> +where DATA is an atom giving the full contents of the file. (In the
> +future, DATA may have other alternate forms, indicating that the
> +fulltext of the file is stored elsewhere in the database, or perhaps
> +in an ordinary Unix file.)
> +
> +
> +DIR:
> +
> +If the header's KIND is "dir", then the node-version skel
> +represents a directory, and has the form:
> +
> + (HEADER (ENTRY ...))
> +
> +The list (ENTRY ...) gives the directory entries; each ENTRY has the form:
> +
> + (NAME ID)
> +
> +where:
> + - NAME is the name of the directory entry, in UTF-8, and
> + - ID is the ID of the node version to which this entry refers.
> +
> +
> PROPLIST: property lists for nodes and directory entries
>
> A property list is a list skel of the form:
> @@ -253,10 +289,6 @@
> - Nodes are sorted by their node number.
> - All the versions of a given node come together, in order of
> increasing version number.
> -- Each node's versions are followed by a record whose key is
> - "NODENUM.head". This lets us quickly find the head of any given
> - chain of node versions by looking for "NODENUM.head", and then going
> - back one record.
> - All branches off any version of a node come immediately after that
> node, ordered by increasing version number.
>
> @@ -267,28 +299,35 @@
> 13.2
> 13.3
> 13.4
> -13.head
> 13.2.1.1 ; a branch from the second version of 13, with three versions
> 13.2.1.2
> 13.2.1.3
> -13.2.1.head
> 13.2.2.1 ; another branch off the same version of 13, with four versions
> 13.2.2.2
> 13.2.2.3
> 13.2.2.4
> -13.2.2.head
> 13.4.1.1 ; another branch off a later version of 13, with two versions
> 13.4.1.2
> -13.4.1.head
> 14.1 ; another node
> 14.2
> 14.3
> -14.head
> +
> +We can find the latest version of node N by searching the `nodes'
> +table for the last entry before N.1.1.1, the first branch off the
> +node's first version. According to the sort order, this node
> +immediately follows the last version of node N.
> +
> +Similarly, we can find the last branch off a given node version N.V by
> +searching the `nodes' table for the last entry before N.(V+1).1.1.
> +According to the sort order, this node immediately follows the last
> +descendant of of N.V. This last descendent could be a branch off a
> +branch off a branch ..., but by truncating the node version ID
> +appropriately, we can find the last branch off of N.V.
>
> Assuming that we store the most recent version on every branch as
> fulltext, and all other versions as deltas, we can retrieve any node
> -version NODE.VERSION by searching for NODE.head, and then walking
> -backwards to NODE.VERSION, applying deltas as we go.
> +version NODE.VERSION by searching for the last version of NODE, and
> +then walking backwards to NODE.VERSION, applying deltas as we go.
>
> Since we don't want to corrupt our btree, even if we accidentally
> insert garbage keys into the table, we extend our ordering to handle
> @@ -429,12 +468,18 @@
>
> However, it's important to note that the `bubbling up' need happen
> only once per *complete transaction*, not once per change within the
> -transaction. Once we have created single new version of a directory
> -for a given transaction, we can make as many changes as we like within
> -that directory. In other words, once the transaction contains the new
> -node versions required by the `bubble up' rule, further changes to the
> -same nodes needn't create any new versions --- they can proceed much
> -like writes to an ordinary filesystem.
> +transaction. Once we have created a new version of a directory for a
> +given transaction, we can make as many changes as we like within that
> +new directory version. In other words, once the transaction contains
> +the new node versions required by the `bubble up' rule, further
> +changes to the same nodes needn't create any new versions --- they can
> +proceed much like writes to an ordinary filesystem.
> +
> +Thus, node versions which are part of an uncommitted transaction are
> +called "mutable" node versions, since they may be changed in place
> +until the transaction is committed. Once a transaction is committed,
> +the nodes it contains become "immutable" nodes, whose contents must
> +never change.
>
> For each active transaction, we store a transaction header skel of the
> form:
> @@ -447,7 +492,7 @@
> root directory for this transaction, ROOT is the empty string.
>
> The database contains a table called "transactions", which is a btree
> -table indexed by transaction ID, mapping each one onto a transaction
> +table indexed by transaction ID, mapping each one onto a TRANSACTION
> header, and a list of all the nodes that are part of that transaction.
> The header and the nodes are stored as duplicate entries under the
> same key: the first value stored is the transaction header skel, and
> @@ -460,6 +505,8 @@
> "transactions" table are the shorted ASCII decimal representation of
> the transaction ID. The entries of the "transactions" table are
> sorted by the ID's numeric value.
> +
> +
>
> If the transaction succeeds, the transaction's root directory becomes
> the new version's root directory, after any necessary merging. If the
>
>
>
> 1.5 +303 -65 subversion/subversion/libsvn_fs/txn.c
>
> Index: txn.c
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/txn.c,v
> retrieving revision 1.4
> retrieving revision 1.5
> diff -u -r1.4 -r1.5
> --- txn.c 2000/10/20 21:33:47 1.4
> +++ txn.c 2000/10/27 21:43:49 1.5
> @@ -53,12 +53,87 @@
> #include "svn_fs.h"
> #include "fs.h"
> #include "txn.h"
> +#include "version.h"
> +#include "node.h"
> #include "skel.h"
> #include "convert-size.h"
> #include "dbt.h"
> #include "err.h"
>
>
> +/* The private structure underlying the public svn_fs_txn_t typedef. */
> +
> +struct svn_fs_txn_t {
> +
> + /* This transaction's private pool, a subpool of fs->pool.
> +
> + Freeing this must completely clean up the transaction object,
> + write back any buffered data, and release any database or system
> + resources it holds. (But don't confused the transaction object
> + with the transaction it represents: freeing this does *not* abort
> + the transaction.) */
> + apr_pool_t *pool;
> +
> + /* The filesystem to which this transaction belongs. */
> + svn_fs_t *fs;
> +
> + /* The ID of this transaction --- a null-terminated string.
> + This is the key into the `transactions' table. */
> + char *id;
> +
> + /* The root directory for this transaction, or zero if the user
> + hasn't called svn_fs_replace_root yet. */
> + svn_fs_id_t *root;
> +};
> +
> +
> +
> +/* Building error objects. */
> +
> +
> +static svn_error_t *
> +corrupt_txn (svn_fs_txn_t *txn)
> +{
> + return svn_error_createf (SVN_ERR_FS_CORRUPT, 0, 0, txn->fs->pool,
> + "corrupt transaction `%s' in filesystem `%s'",
> + txn->id, txn->fs->env_path);
> +}
> +
> +
> +static svn_error_t *
> +dangling_txn_id (svn_fs_txn_t *txn)
> +{
> + return svn_error_createf (SVN_ERR_FS_CORRUPT, 0, 0, txn->fs->pool,
> + "dangling transaction id `%s' in filesystem `%s'",
> + txn->id, txn->fs->env_path);
> +}
> +
> +
> +#if 0
> +static svn_error_t *
> +no_such_txn (svn_fs_txn_t *txn)
> +{
> + return svn_error_createf (SVN_ERR_FS_NO_SUCH_TRANSACTION, 0, 0,
> + txn->fs->pool,
> + "no transaction `%s' in filesystem `%s'",
> + txn->id, txn->fs->env_path);
> +}
> +#endif
> +
> +
> +static svn_error_t *
> +bad_txn_root (svn_fs_txn_t *txn, svn_vernum_t version)
> +{
> + return
> + svn_error_createf
> + (SVN_ERR_FS_BAD_REPLACE_ROOT, 0, 0, txn->fs->pool,
> + "the root directory of transaction `%s' is not a direct descendent\n"
> + "of the root of version `%ld', in filesystem `%s'",
> + txn->id, version, txn->fs->env_path);
> +}
> +
> +
> +
> /* Creating and opening the database's `transactions' table. */
>
>
> @@ -152,7 +227,7 @@
>
>
>
> -/* Writing TRANSACTION skels to the database. */
> +/* Storing and retrieving TRANSACTION skels. */
>
> /* Store the skel TXN_SKEL in the `transactions' table under the
> transaction id ID. If CREATE is non-zero, return an error if an
> @@ -170,19 +245,51 @@
> apr_pool_t *pool)
> {
> DB *transactions = fs->transactions;
> - svn_string_t *unparsed_txn;
> DBT key, value;
>
> - unparsed_txn = svn_fs__unparse_skel (txn_skel, pool);
> - svn_fs__set_dbt (&key, id, strlen (id));
> - svn_fs__set_dbt (&value, unparsed_txn->data, unparsed_txn->len);
> SVN_ERR (DB_WRAP (fs, "storing transaction skel",
> - transactions->put (transactions, db_txn, &key, &value,
> + transactions->put (transactions, db_txn,
> + svn_fs__str_to_dbt (&key, id),
> + svn_fs__skel_to_dbt (&value, txn_skel,
> + pool),
> create ? DB_NOOVERWRITE : 0)));
>
> return 0;
> }
>
> +/* Set *SKEL_P to point to the TRANSACTION skel for SVN_TXN, as part
> + of the Berkeley DB transaction DB_TXN. Allocate the skel and the
> + data it points to in POOL.
> +
> + Beyond verifying that it's a syntactically valid skel, this doesn't
> + validate the data in *SKEL_P at all. */
> +static svn_error_t *
> +get_transaction_skel (skel_t **skel_p,
> + svn_fs_txn_t *svn_txn,
> + DB_TXN *db_txn,
> + apr_pool_t *pool)
> +{
> + svn_fs_t *fs = svn_txn->fs;
> + DBT key, value;
> + int db_err;
> + skel_t *txn_skel;
> +
> + svn_fs__set_dbt (&key, svn_txn->id, strlen (svn_txn->id));
> + svn_fs__result_dbt (&value);
> + db_err = fs->transactions->get (fs->transactions, db_txn, &key, &value, 0);
> + if (db_err == DB_NOTFOUND)
> + return dangling_txn_id (svn_txn);
> + SVN_ERR (DB_WRAP (fs, "reading transaction", db_err));
> + svn_fs__track_dbt (&value, pool);
> +
> + txn_skel = svn_fs__parse_skel (value.data, value.size, pool);
> + if (! txn_skel)
> + return corrupt_txn (svn_txn);
> +
> + *skel_p = txn_skel;
> + return 0;
> +}
> +
>
>
> /* Creating transactions. */
> @@ -203,32 +310,29 @@
> caller's responsibility to free whatever resources the transaction
> body allocates --- in this case, a cursor. */
> static svn_error_t *
> -create_txn_body (svn_fs_txn_t *svn_txn,
> - DB_TXN *db_txn,
> - DBC **cursor_p)
> +begin_txn_body (void *baton,
> + DB_TXN *db_txn)
> {
> + svn_fs_txn_t *svn_txn = baton;
> + svn_error_t *svn_err;
> DB *transactions = svn_txn->fs->transactions;
> - DBC *cursor;
> + DBC *cursor = 0;
> DBT key, value;
> int id;
>
> - /* Jim, svn_fs__nodata_dbt keeps getting an `implicit declaration'
> - warning. It's helpful right now have a tree that compiles
> - without warning unless something's wrong, so I've put this bogus
> - declaration here to sidestep the warning. -karl */
> - void svn_fs__nodata_dbt (DBT *value);
> -
> /* Create a cursor. */
> SVN_ERR (DB_WRAP (svn_txn->fs, "creating transaction (allocating cursor)",
> - transactions->cursor (transactions, db_txn, cursor_p, 0)));
> - cursor = *cursor_p;
> + transactions->cursor (transactions, db_txn, &cursor, 0)));
>
> /* Use that cursor to get the ID of the last entry in the table.
> We only need to know the key; don't actually read any of the value. */
> - svn_fs__result_dbt (&key);
> - svn_fs__nodata_dbt (&value);
> - SVN_ERR (DB_WRAP (svn_txn->fs, "creating transaction (getting max id)",
> - cursor->c_get (cursor, &key, &value, DB_LAST)));
> + svn_err = DB_WRAP (svn_txn->fs, "creating transaction (getting max id)",
> + cursor->c_get (cursor,
> + svn_fs__result_dbt (&key),
> + svn_fs__nodata_dbt (&value),
> + DB_LAST));
> + if (svn_err)
> + goto error;
>
> /* Try to parse the key as a number. */
> {
> @@ -238,10 +342,13 @@
> /* If we didn't consume the entire key as the number, then it's a
> bogus key. */
> if (end != (char *) key.data + key.size)
> - return (svn_error_createf
> - (SVN_ERR_FS_CORRUPT, 0, 0, svn_txn->fs->pool,
> - "malformed ID in transaction table of filesystem `%s'",
> - svn_txn->fs->env_path));
> + {
> + svn_err = (svn_error_createf
> + (SVN_ERR_FS_CORRUPT, 0, 0, svn_txn->fs->pool,
> + "malformed ID in transaction table of filesystem `%s'",
> + svn_txn->fs->env_path));
> + goto error;
> + }
> }
>
> /* Choose a new, distinct ID. */
> @@ -261,14 +368,23 @@
>
> /* Store the transaction skel in the database, under this ID. */
> id_text[id_len] = 0;
> - SVN_ERR (put_transaction_skel (svn_txn->fs, db_txn, id_text,
> - &new_txn_skel[0], 1, svn_txn->pool));
> + svn_err = put_transaction_skel (svn_txn->fs, db_txn, id_text,
> + &new_txn_skel[0], 1, svn_txn->pool);
> + if (svn_err)
> + goto error;
>
> /* Store the ID in the transaction object. */
> svn_txn->id = apr_pstrdup (svn_txn->pool, id_text);
> }
>
> + SVN_ERR (DB_WRAP (svn_txn->fs, "creating transaction (closing cursor)",
> + cursor->c_close (cursor)));
> return 0;
> +
> + error:
> + if (cursor)
> + cursor->c_close (cursor);
> + return svn_err;
> }
>
>
> @@ -289,50 +405,172 @@
>
> /* Choose an id for this transaction, and create the transaction
> record in the database. */
> - for (;;)
> + SVN_ERR (svn_fs__retry_txn (fs, begin_txn_body, txn));
> +
> + /* Add the transaction to the filesystem's table of open transactions. */
> + apr_hash_set (fs->open_txns, txn->id, 0, txn);
> +
> + *txn_p = txn;
> + return 0;
> +}
> +
> +
> +
> +/* Creating a new root directory for a transaction. */
> +
> +
> +struct replace_root_args {
> + svn_fs_dir_t **root_p;
> + svn_fs_txn_t *svn_txn;
> + svn_vernum_t version;
> +};
> +
> +
> +static svn_error_t *
> +replace_root_body (void *baton,
> + DB_TXN *db_txn)
> +{
> + /* Unpack the true arguments, passed through svn_fs__retry_txn. */
> + struct replace_root_args *args = baton;
> + svn_fs_dir_t **root_p = args->root_p;
> + svn_fs_txn_t *svn_txn = args->svn_txn;
> + svn_vernum_t version = args->version;
> +
> + svn_fs_id_t *version_root_id;
> + svn_fs_node_t *txn_root;
> +
> + /* The TRANSACTION skel for SVN_TXN, and the sub-skel that holds its
> + root directory ID. These get read in *only* if we don't
> + have the root directory cached in the SVN_TXN object already. */
> + skel_t *txn_skel, *root_skel;
> +
> + /* Find the root of VERSION in the transaction's filesystem. */
> + SVN_ERR (svn_fs__version_root (&version_root_id, svn_txn->fs, version,
> + svn_txn->pool));
> +
> + /* Have we cached the transaction's root directory ID? */
> + if (! svn_txn->root)
> {
> - DB_TXN *db_txn;
> - DBC *cursor = 0;
> - svn_error_t *svn_err;
> -
> - SVN_ERR (DB_WRAP (fs, "creating transaction (beginning DB transaction)",
> - txn_begin (fs->env, 0, &db_txn, 0)));
> -
> - svn_err = create_txn_body (txn, db_txn, &cursor);
> - if (! svn_err)
> - {
> - /* The transaction succeeded! Commit it. */
> - if (cursor)
> - SVN_ERR (DB_WRAP (fs, "creating transaction (closing cursor)",
> - cursor->c_close (cursor)));
> - SVN_ERR (DB_WRAP (fs,
> - "creating transaction (committing DB transaction)",
> - txn_commit (db_txn, 0)));
> - break;
> - }
> + /* Read in SVN_TXN's TRANSACTION skel, and try to find the root
> + directory ID there. */
> + SVN_ERR (get_transaction_skel (&txn_skel, svn_txn, db_txn,
> + svn_txn->pool));
> + if (svn_fs__list_length (txn_skel) != 2
> + || ! txn_skel->children->is_atom
> + || ! txn_skel->children->next->is_atom)
> + return corrupt_txn (svn_txn);
> + root_skel = txn_skel->children->next;
>
> - /* Is this a real error, or do we just need to retry? */
> - if (svn_err->apr_err != SVN_ERR_BERKELEY_DB
> - || svn_err->src_err != DB_LOCK_DEADLOCK)
> + /* If there is a node ID, try to parse it. */
> + if (root_skel->len > 0)
> {
> - /* Free the cursor and abort the transaction, but ignore any
> - error returns. The first error is more valuable. */
> - if (cursor)
> - cursor->c_close (cursor);
> - txn_abort (db_txn);
> - return svn_err;
> + svn_txn->root = svn_fs__parse_id (root_skel->data, root_skel->len,
> + svn_txn->pool);
> + if (! svn_txn->root)
> + return corrupt_txn (svn_txn);
> }
> + else
> + svn_txn->root = 0;
> + }
> +
> + /* At this point, the cache svn_txn->root is up-to-date: it is zero
> + iff the transaction has no root directory yet. */
> + if (svn_txn->root)
> + {
> + /* Yes, we have a root directory. Make sure it's a direct
> + ancestor of the root version. */
> + if (! svn_fs__is_parent (version_root_id, svn_txn->root))
> + return bad_txn_root (svn_txn, version);
> +
> + /* The root directory ID looks reasonable, so open the actual node. */
> + SVN_ERR (svn_fs__open_node_by_id (&txn_root, svn_txn->fs,
> + svn_txn->root, db_txn));
> + }
> + else
> + {
> + /* No, this transaction has no root directory yet. */
> + svn_error_t *svn_err;
> + svn_fs_node_t *version_root;
>
> - /* We deadlocked. Abort the transaction, and try again. */
> - if (cursor)
> - cursor->c_close (cursor);
> - SVN_ERR (DB_WRAP (fs, "creating transaction (aborting DB transaction)",
> - txn_abort (db_txn)));
> + /* Open VERSION's root directory, create an immediate successor
> + to it, and establish that as SVN_TXN's root. */
> + SVN_ERR (svn_fs__open_node_by_id (&version_root,
> + svn_txn->fs, version_root_id,
> + db_txn));
> + svn_err = svn_fs__create_successor (&txn_root, version_root,
> + svn_txn, db_txn);
> + svn_fs_close_node (version_root);
> + if (svn_err)
> + return svn_err;
> +
> + /* Record this transaction's new root directory ID. We know that
> + txn_skel has been read in, and root_skel set, because svn_txn had
> + no root directory when we began. */
> + {
> + svn_string_t *unparsed_txn_root_id
> + = svn_fs__unparse_id (svn_fs__node_id (txn_root), svn_txn->pool);
> + root_skel->data = unparsed_txn_root_id->data;
> + root_skel->len = unparsed_txn_root_id->len;
> +
> + svn_err = put_transaction_skel (svn_txn->fs, db_txn,
> + svn_txn->id, txn_skel, 0,
> + svn_txn->pool);
> + if (svn_err)
> + {
> + svn_fs_close_node (txn_root);
> + return svn_err;
> + }
> + }
> }
>
> - /* Add the transaction to the filesystem's table of open transactions. */
> - apr_hash_set (fs->open_txns, txn->id, 0, txn);
> + /* Make sure it's a mutable directory, as it must be. */
> + if (! svn_fs_node_is_dir (txn_root)
> + || ! svn_fs_node_is_mutable (txn_root))
> + {
> + svn_fs_close_node (txn_root);
> + return
> + svn_error_createf
> + (SVN_ERR_FS_CORRUPT, 0, 0, svn_txn->fs->pool,
> + "the root of transaction `%s' in filesystem `%s' is not a"
> + " mutable directory",
> + svn_txn->id, svn_txn->fs->env_path);
> + }
>
> - *txn_p = txn;
> + *root_p = svn_fs_node_to_dir (txn_root);
> + return 0;
> +}
> +
> +
> +svn_error_t *
> +svn_fs_replace_root (svn_fs_dir_t **root_p,
> + svn_fs_txn_t *txn,
> + svn_vernum_t version)
> +{
> + struct replace_root_args args;
> +
> + args.root_p = root_p;
> + args.svn_txn = txn;
> + args.version = version;
> +
> + return svn_fs__retry_txn (txn->fs, replace_root_body, &args);
> +}
> +
> +
> +
> +/* Miscellaneous trivial transaction functions. */
> +
> +char *
> +svn_fs__txn_id (svn_fs_txn_t *txn)
> +{
> + return txn->id;
> +}
> +
> +
> +svn_error_t *
> +svn_fs_txn_name (svn_string_t **name_p,
> + svn_fs_txn_t *txn,
> + apr_pool_t *pool)
> +{
> + *name_p = svn_string_ncreate (txn->id, strlen (txn->id), pool);
> return 0;
> }
>
>
>
> 1.2 +5 -0 subversion/subversion/libsvn_fs/txn.h
>
> Index: txn.h
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/txn.h,v
> retrieving revision 1.1
> retrieving revision 1.2
> diff -u -r1.1 -r1.2
> --- txn.h 2000/10/14 06:11:12 1.1
> +++ txn.h 2000/10/27 21:43:49 1.2
> @@ -59,4 +59,9 @@
> svn_error_t *svn_fs__open_transactions (svn_fs_t *fs);
>
>
> +/* Return a pointer to the ID of TXN. The return value is live for as
> + long as TXN is. */
> +char *svn_fs__txn_id (svn_fs_txn_t *txn);
> +
> +
> #endif /* SVN_LIBSVN_FS_TXN_H */
>
>
>
> 1.7 +16 -18 subversion/subversion/libsvn_fs/version.c
>
> Index: version.c
> ===================================================================
> RCS file: /cvs/subversion/subversion/libsvn_fs/version.c,v
> retrieving revision 1.6
> retrieving revision 1.7
> diff -u -r1.6 -r1.7
> --- version.c 2000/10/26 19:32:36 1.6
> +++ version.c 2000/10/27 21:43:49 1.7
> @@ -147,7 +147,7 @@
> if (! id_skel->is_atom)
> goto corrupt;
>
> - id = svn_fs__parse_id (id_skel->data, id_skel->len, 0, pool);
> + id = svn_fs__parse_id (id_skel->data, id_skel->len, pool);
> if (! id)
> goto corrupt;
>
> @@ -164,26 +164,23 @@
> /* Writing versions. */
>
>
> -/* Add VERSION_SKEL as a new version to FS's `versions' table. Set *V
> - to the number of the new version created.
> +/* Add SKEL as a new version to FS's `versions' table. Set *V_P to
> + the number of the new version created. Do this as part of the
> + Berkeley DB transaction TXN; if TXN is zero, then make the change
> + without transaction protection.
>
> - Do this as part of the Berkeley DB transaction TXN; if TXN is zero,
> - then make the change without transaction protection.
> -
> Do any necessary temporary allocation in POOL. */
> static svn_error_t *
> -put_version_skel (svn_fs_t *fs,
> +put_version_skel (svn_vernum_t *v_p,
> + svn_fs_t *fs,
> + skel_t *skel,
> DB_TXN *txn,
> - skel_t *version_skel,
> - svn_vernum_t *v,
> apr_pool_t *pool)
> {
> - svn_string_t *version = svn_fs__unparse_skel (version_skel, pool);
> db_recno_t recno;
> + DB *versions = fs->versions;
> DBT key, value;
>
> - SVN_ERR (svn_fs__check_fs (fs));
> -
> /* Since we use the DB_APPEND flag, the `put' call sets recno to the record
> number of the new version. */
> recno = 0;
> @@ -192,15 +189,16 @@
> key.size = key.ulen = sizeof (recno);
> key.flags |= DB_DBT_USERMEM;
>
> - svn_fs__set_dbt (&value, version->data, version->len);
> SVN_ERR (DB_WRAP (fs, "adding new version",
> - fs->versions->put (fs->versions, txn, &key, &value,
> - DB_APPEND)));
> + versions->put (versions, txn,
> + &key,
> + svn_fs__skel_to_dbt (&value, skel, pool),
> + DB_APPEND)));
>
> /* Turn the record number into a Subversion version number.
> Versions are numbered starting with zero; Berkeley DB record numbers
> begin with one. */
> - *v = recno - 1;
> + *v_p = recno - 1;
> return 0;
> }
>
> @@ -231,12 +229,12 @@
> if (create)
> {
> /* Create the initial version. */
> - static char version_0[] = "(version 3 0.0 ())";
> + static char version_0[] = "(version 3 1.1 ())";
> skel_t *version_skel = svn_fs__parse_skel (version_0,
> sizeof (version_0) - 1,
> fs->pool);
> svn_vernum_t v;
> - SVN_ERR (put_version_skel (fs, 0, version_skel, &v, fs->pool));
> + SVN_ERR (put_version_skel (&v, fs, version_skel, 0, fs->pool));
>
> /* That had better have created version zero. */
> if (v != 0)
>
>
>
Received on Sat Oct 21 14:36:13 2006