Subversion has a modular design, implemented as a collection
of C libraries. Each library has a well-defined purpose and
interface, and most modules are said to exist in one of three
main layers—the Repository Layer, the Repository Access
(RA) Layer, or the Client Layer. We will examine these layers
shortly, but first, see our brief inventory of Subversion's
libraries in
Table 8.1, “A Brief Inventory of the Subversion Libraries”. For the sake
of consistency, we will refer to the libraries by their
extensionless Unix library names (e.g.: libsvn_fs, libsvn_wc,
mod_dav_svn).
Another benefit of modularity is the ability to replace a
given module with a whole new library that implements the same
API without affecting the rest of the code base. In some sense,
this happens within Subversion already. The libsvn_ra_dav,
libsvn_ra_local, and libsvn_ra_svn all implement the same
interface. And all three communicate with the Repository
Layer—libsvn_ra_dav and libsvn_ra_svn do so across a
network, and libsvn_ra_local connects to it directly. The
libsvn_fs_base and libsvn_fs_fs libraries are another example of
this.
The client itself also highlights modularity in the
Subversion design. While Subversion itself comes with only a
command-line client program, there are several third party
programs which provide various forms of client GUI. These GUIs
use the same APIs that the stock command-line client does.
Subversion's libsvn_client library is the one-stop shop for most
of the functionality necessary for designing a working
Subversion client (see
the section called “Client Layer”).
When referring to Subversion's Repository Layer, we're
generally talking about two libraries—the repository
library, and the filesystem library. These libraries provide
the storage and reporting mechanisms for the various revisions
of your version-controlled data. This layer is connected to
the Client Layer via the Repository Access Layer, and is, from
the perspective of the Subversion user, the stuff at the
“other end of the line.”
The Subversion Filesystem is accessed via the libsvn_fs
API, and is not a kernel-level filesystem that one would
install in an operating system (like the Linux ext2 or NTFS),
but a virtual filesystem. Rather than storing
“files” and “directories” as real
files and directories (as in, the kind you can navigate
through using your favorite shell program), it uses one of two
available abstract storage backends—either a Berkeley DB
database environment, or a flat-file representation. (To
learn more about the two repository back-ends, see
the section called “Repository Data Stores”.) However, there has been
considerable interest by the development community in giving
future releases of Subversion the ability to use other
back-end database systems, perhaps through a mechanism such as
Open Database Connectivity (ODBC).
The filesystem API exported by libsvn_fs contains the
kinds of functionality you would expect from any other
filesystem API: you can create and remove files and
directories, copy and move them around, modify file contents,
and so on. It also has features that are not quite as common,
such as the ability to add, modify, and remove metadata
(“properties”) on each file or directory.
Furthermore, the Subversion Filesystem is a versioning
filesystem, which means that as you make changes to your
directory tree, Subversion remembers what your tree looked
like before those changes. And before the previous changes.
And the previous ones. And so on, all the way back through
versioning time to (and just beyond) the moment you first
started adding things to the filesystem.
All the modifications you make to your tree are done
within the context of a Subversion transaction. The following
is a simplified general routine for modifying your
filesystem:
-
Begin a Subversion transaction.
-
Make your changes (adds, deletes, property
modifications, etc.).
-
Commit your transaction.
Once you have committed your transaction, your filesystem
modifications are permanently stored as historical artifacts.
Each of these cycles generates a single new revision of your
tree, and each revision is forever accessible as an immutable
snapshot of “the way things were.”
Most of the functionality provided by the filesystem
interface comes as an action that occurs on a filesystem path.
That is, from outside of the filesystem, the primary mechanism
for describing and accessing the individual revisions of files
and directories comes through the use of path strings like
/foo/bar
, just as if you were addressing
files and directories through your favorite shell program.
You add new files and directories by passing their paths-to-be
to the right API functions. You query for information about
them by the same mechanism.
Unlike most filesystems, though, a path alone is not
enough information to identify a file or directory in
Subversion. Think of a directory tree as a two-dimensional
system, where a node's siblings represent a sort of
left-and-right motion, and descending into subdirectories a
downward motion.
Figure 8.1, “Files and directories in two dimensions” shows
a typical representation of a tree as exactly that.
Of course, the Subversion filesystem has a nifty third
dimension that most filesystems do not have—Time!
[42]
In the filesystem interface, nearly every function that has a
path
argument also expects a
root
argument. This
svn_fs_root_t argument describes
either a revision or a Subversion transaction (which is
usually just a revision-to-be), and provides that
third-dimensional context needed to understand the difference
between /foo/bar
in revision 32, and the
same path as it exists in revision 98.
Figure 8.2, “Versioning time—the third dimension!” shows revision history as an
added dimension to the Subversion filesystem universe.
As we mentioned earlier, the libsvn_fs API looks and feels
like any other filesystem, except that it has this wonderful
versioning capability. It was designed to be usable by any
program interested in a versioning filesystem. Not
coincidentally, Subversion itself is interested in that
functionality. But while the filesystem API should be
sufficient for basic file and directory versioning support,
Subversion wants more—and that is where libsvn_repos
comes in.
The Subversion repository library (libsvn_repos) is
basically a wrapper library around the filesystem
functionality. This library is responsible for creating the
repository layout, making sure that the underlying filesystem
is initialized, and so on. Libsvn_repos also implements a set
of hooks—scripts that are executed by the repository
code when certain actions take place. These scripts are
useful for notification, authorization, or whatever purposes
the repository administrator desires. This type of
functionality, and other utilities provided by the repository
library, are not strictly related to implementing a versioning
filesystem, which is why it was placed into its own
library.
Developers who wish to use the libsvn_repos API will find
that it is not a complete wrapper around the filesystem
interface. That is, only certain major events in the general
cycle of filesystem activity are wrapped by the repository
interface. Some of these include the creation and commit of
Subversion transactions, and the modification of revision
properties. These particular events are wrapped by the
repository layer because they have hooks associated with them.
In the future, other events may be wrapped by the repository
API. All of the remaining filesystem interaction will
continue to occur directly via the libsvn_fs API, though.
For example, here is a code segment that illustrates the
use of both the repository and filesystem interfaces to create
a new revision of the filesystem in which a directory is
added. Note that in this example (and all others throughout
this book), the SVN_ERR()
macro simply
checks for a non-successful error return from the function it
wraps, and returns that error if it exists.
Example 8.1. Using the Repository Layer
/* Create a new directory at the path NEW_DIRECTORY in the Subversion
repository located at REPOS_PATH. Perform all memory allocation in
POOL. This function will create a new revision for the addition of
NEW_DIRECTORY. */
static svn_error_t *
make_new_directory (const char *repos_path,
const char *new_directory,
apr_pool_t *pool)
{
svn_error_t *err;
svn_repos_t *repos;
svn_fs_t *fs;
svn_revnum_t youngest_rev;
svn_fs_txn_t *txn;
svn_fs_root_t *txn_root;
const char *conflict_str;
/* Open the repository located at REPOS_PATH. */
SVN_ERR (svn_repos_open (&repos, repos_path, pool));
/* Get a pointer to the filesystem object that is stored in
REPOS. */
fs = svn_repos_fs (repos);
/* Ask the filesystem to tell us the youngest revision that
currently exists. */
SVN_ERR (svn_fs_youngest_rev (&youngest_rev, fs, pool));
/* Begin a new transaction that is based on YOUNGEST_REV. We are
less likely to have our later commit rejected as conflicting if we
always try to make our changes against a copy of the latest snapshot
of the filesystem tree. */
SVN_ERR (svn_fs_begin_txn (&txn, fs, youngest_rev, pool));
/* Now that we have started a new Subversion transaction, get a root
object that represents that transaction. */
SVN_ERR (svn_fs_txn_root (&txn_root, txn, pool));
/* Create our new directory under the transaction root, at the path
NEW_DIRECTORY. */
SVN_ERR (svn_fs_make_dir (txn_root, new_directory, pool));
/* Commit the transaction, creating a new revision of the filesystem
which includes our added directory path. */
err = svn_repos_fs_commit_txn (&conflict_str, repos,
&youngest_rev, txn, pool);
if (! err)
{
/* No error? Excellent! Print a brief report of our success. */
printf ("Directory '%s' was successfully added as new revision "
"'%ld'.\n", new_directory, youngest_rev);
}
else if (err->apr_err == SVN_ERR_FS_CONFLICT)
{
/* Uh-oh. Our commit failed as the result of a conflict
(someone else seems to have made changes to the same area
of the filesystem that we tried to modify). Print an error
message. */
printf ("A conflict occurred at path '%s' while attempting "
"to add directory '%s' to the repository at '%s'.\n",
conflict_str, new_directory, repos_path);
}
else
{
/* Some other error has occurred. Print an error message. */
printf ("An error occurred while attempting to add directory '%s' "
"to the repository at '%s'.\n",
new_directory, repos_path);
}
/* Return the result of the attempted commit to our caller. */
return err;
}
In the previous code segment, calls were made to both the
repository and filesystem interfaces. We could just as easily
have committed the transaction using
svn_fs_commit_txn()
. But the filesystem
API knows nothing about the repository library's hook
mechanism. If you want your Subversion repository to
automatically perform some set of non-Subversion tasks every
time you commit a transaction (like, for example, sending an
email that describes all the changes made in that transaction
to your developer mailing list), you need to use the
libsvn_repos-wrapped version of that
function—svn_repos_fs_commit_txn()
.
This function will actually first run the
pre-commit
hook script if one exists, then
commit the transaction, and finally will run a
post-commit
hook script. The hooks provide
a special kind of reporting mechanism that does not really
belong in the core filesystem library itself. (For more
information regarding Subversion's repository hooks, see
the section called “Hook Scripts”.)
The hook mechanism requirement is but one of the reasons
for the abstraction of a separate repository library from the
rest of the filesystem code. The libsvn_repos API provides
several other important utilities to Subversion. These
include the abilities to:
-
create, open, destroy, and perform recovery steps on a
Subversion repository and the filesystem included in that
repository.
-
describe the differences between two filesystem
trees.
-
query for the commit log messages
associated with all (or some) of the revisions in which a
set of files was modified in the filesystem.
-
generate a human-readable “dump” of the
filesystem, a complete representation of the revisions in
the filesystem.
-
parse that dump format, loading the dumped revisions
into a different Subversion repository.
As Subversion continues to evolve, the repository library
will grow with the filesystem library to offer increased
functionality and configurable option support.