<?xml version="1.0" encoding="utf-8" standalone="no"?> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <title>Creating and Configuring Your Repository</title> <link rel="stylesheet" type="text/css" href="styles.css" /> <meta name="generator" content="DocBook XSL Stylesheets V1.76.1" /> <style type="text/css"> body { background-image: url('images/draft.png'); background-repeat: no-repeat; background-position: top left; /* The following properties make the watermark "fixed" on the page. */ /* I think that's just a bit too distracting for the reader... */ /* background-attachment: fixed; */ /* background-position: center center; */ }</style> <link rel="home" href="index.html" title="Version Control with Subversion [DRAFT]" /> <link rel="up" href="svn.reposadmin.html" title="Chapter 5. Repository Administration" /> <link rel="prev" href="svn.reposadmin.planning.html" title="Strategies for Repository Deployment" /> <link rel="next" href="svn.reposadmin.maint.html" title="Repository Maintenance" /> </head> <body> <div xmlns="" id="vcws-version-notice"> <p>This text is a work in progress—highly subject to change—and may not accurately describe any released version of the Apache™ Subversion® software. Bookmarking or otherwise referring others to this page is probably not such a smart idea. Please visit <a href="http://www.svnbook.com/">http://www.svnbook.com/</a> for stable versions of this book.</p> </div> <div class="navheader"> <table width="100%" summary="Navigation header"> <tr> <th colspan="3" align="center">Creating and Configuring Your Repository</th> </tr> <tr> <td width="20%" align="left"><a accesskey="p" href="svn.reposadmin.planning.html">Prev</a> </td> <th width="60%" align="center">Chapter 5. Repository Administration</th> <td width="20%" align="right"> <a accesskey="n" href="svn.reposadmin.maint.html">Next</a></td> </tr> </table> <hr /> </div> <div class="sect1" title="Creating and Configuring Your Repository"> <div class="titlepage"> <div> <div> <h2 class="title" style="clear: both"><a id="svn.reposadmin.create"></a>Creating and Configuring Your Repository</h2> </div> </div> </div> <p>Earlier in this chapter (in <a class="xref" href="svn.reposadmin.planning.html" title="Strategies for Repository Deployment">the section called “Strategies for Repository Deployment”</a>), we looked at some of the important decisions that should be made before creating and configuring your Subversion repository. Now, we finally get to get our hands dirty! In this section, we'll see how to actually create a Subversion repository and configure it to perform custom actions when special repository events occur.</p> <div class="sect2" title="Creating the Repository"> <div class="titlepage"> <div> <div> <h3 class="title"><a id="svn.reposadmin.basics.creating"></a>Creating the Repository</h3> </div> </div> </div> <p>Subversion repository creation is an incredibly simple task. The <span class="command"><strong>svnadmin</strong></span> utility that comes with Subversion provides a subcommand (<span class="command"><strong>svnadmin create</strong></span>) for doing just that.</p> <div class="informalexample"> <pre class="screen"> $ # Create a repository $ svnadmin create /var/svn/repos $ </pre> </div> <p>Assuming that the parent directory <code class="filename">/var/svn</code> exists and that you have sufficient permissions to modify that directory, the previous command creates a new repository in the directory <code class="filename">/var/svn/repos</code>, and with the default filesystem data store (FSFS). You can explicitly choose the filesystem type using the <code class="option">--fs-type</code> argument, which accepts as a parameter either <code class="literal">fsfs</code> or <code class="literal">bdb</code>.</p> <div class="informalexample"> <pre class="screen"> $ # Create an FSFS-backed repository $ svnadmin create --fs-type fsfs /var/svn/repos $ </pre> </div> <div class="informalexample"> <pre class="screen"> # Create a Berkeley-DB-backed repository $ svnadmin create --fs-type bdb /var/svn/repos $ </pre> </div> <p>After running this simple command, you have a Subversion repository. Depending on how users will access this new repository, you might need to fiddle with its filesystem permissions. But since basic system administration is rather outside the scope of this text, we'll leave further exploration of that topic as an exercise to the reader.</p> <div class="tip" title="Tip" style="margin-left: 0.5in; margin-right: 0.5in;"> <table border="0" summary="Tip"> <tr> <td rowspan="2" align="center" valign="top" width="25"> <img alt="[Tip]" src="images/tip.png" /> </td> <th align="left">Tip</th> </tr> <tr> <td align="left" valign="top"> <p>The path argument to <span class="command"><strong>svnadmin</strong></span> is just a regular filesystem path and not a URL like the <span class="command"><strong>svn</strong></span> client program uses when referring to repositories. Both <span class="command"><strong>svnadmin</strong></span> and <span class="command"><strong>svnlook</strong></span> are considered server-side utilities—they are used on the machine where the repository resides to examine or modify aspects of the repository, and are in fact unable to perform tasks across a network. A common mistake made by Subversion newcomers is trying to pass URLs (even <span class="quote">“<span class="quote">local</span>”</span> <code class="literal">file://</code> ones) to these two programs.</p> </td> </tr> </table> </div> <p>Present in the <code class="filename">db/</code> subdirectory of your repository is the implementation of the versioned filesystem. Your new repository's versioned filesystem begins life at revision 0, which is defined to consist of nothing but the top-level root (<code class="filename">/</code>) directory. Initially, revision 0 also has a single revision property, <code class="literal">svn:date</code>, set to the time at which the repository was created.</p> <p>Now that you have a repository, it's time to customize it.</p> <div class="warning" title="Warning" style="margin-left: 0.5in; margin-right: 0.5in;"> <table border="0" summary="Warning"> <tr> <td rowspan="2" align="center" valign="top" width="25"> <img alt="[Warning]" src="images/warning.png" /> </td> <th align="left">Warning</th> </tr> <tr> <td align="left" valign="top"> <p>While some parts of a Subversion repository—such as the configuration files and hook scripts—are meant to be examined and modified manually, you shouldn't (and shouldn't need to) tamper with the other parts of the repository <span class="quote">“<span class="quote">by hand.</span>”</span> The <span class="command"><strong>svnadmin</strong></span> tool should be sufficient for any changes necessary to your repository, or you can look to third-party tools (such as Berkeley DB's tool suite) for tweaking relevant subsections of the repository. Do <span class="emphasis"><em>not</em></span> attempt manual manipulation of your version control history by poking and prodding around in your repository's data store files!</p> </td> </tr> </table> </div> </div> <div class="sect2" title="Implementing Repository Hooks"> <div class="titlepage"> <div> <div> <h3 class="title"><a id="svn.reposadmin.hooks"></a>Implementing Repository Hooks</h3> </div> </div> </div> <p> <a id="idp14343552" class="indexterm"></a> <a id="idp14344592" class="indexterm"></a> <a id="idp14346080" class="indexterm"></a>A <em class="firstterm">hook</em> is a program triggered by some repository event, such as the creation of a new revision or the modification of an unversioned property. Some hooks (the so-called <span class="quote">“<span class="quote">pre hooks</span>”</span>) run in advance of a repository operation and provide a means by which to both report what is about to happen and prevent it from happening at all. Other hooks (the <span class="quote">“<span class="quote">post hooks</span>”</span>) run after the completion of a repository event and are useful for performing tasks that examine—but don't modify—the repository. Each hook is handed enough information to tell what that event is (or was), the specific repository changes proposed (or completed), and the username of the person who triggered the event.</p> <p>The <code class="filename">hooks</code> subdirectory is, by default, filled with templates for various repository hooks:</p> <div class="informalexample"> <pre class="screen"> $ ls repos/hooks/ post-commit.tmpl post-unlock.tmpl pre-revprop-change.tmpl post-lock.tmpl pre-commit.tmpl pre-unlock.tmpl post-revprop-change.tmpl pre-lock.tmpl start-commit.tmpl $ </pre> </div> <p>There is one template for each hook that the Subversion repository supports; by examining the contents of those template scripts, you can see what triggers each script to run and what data is passed to that script. Also present in many of these templates are examples of how one might use that script, in conjunction with other Subversion-supplied programs, to perform common useful tasks. To actually install a working hook, you need only place some executable program or script into the <code class="filename">repos/hooks</code> directory, which can be executed as the name (such as <span class="command"><strong>start-commit</strong></span> or <span class="command"><strong>post-commit</strong></span>) of the hook.</p> <p>On Unix platforms, this means supplying a script or program (which could be a shell script, a Python program, a compiled C binary, or any number of other things) named exactly like the name of the hook. Of course, the template files are present for more than just informational purposes—the easiest way to install a hook on Unix platforms is to simply copy the appropriate template file to a new file that lacks the <code class="filename">.tmpl</code> extension, customize the hook's contents, and ensure that the script is executable. Windows, however, uses file extensions to determine whether a program is executable, so you would need to supply a program whose basename is the name of the hook and whose extension is one of the special extensions recognized by Windows for executable programs, such as <code class="filename">.exe</code> for programs and <code class="filename">.bat</code> for batch files.</p> <p>Subversion executes hooks as the same user who owns the process that is accessing the Subversion repository. In most cases, the repository is being accessed via a Subversion server, so this user is the same user as whom the server runs on the system. The hooks themselves will need to be configured with OS-level permissions that allow that user to execute them. Also, this means that any programs or files (including the Subversion repository) accessed directly or indirectly by the hook will be accessed as the same user. In other words, be alert to potential permission-related problems that could prevent the hook from performing the tasks it is designed to perform.</p> <p>There are several hooks implemented by the Subversion repository, and you can get details about each of them in <a class="xref" href="svn.ref.reposhooks.html" title="Subversion Repository Hook Reference">Subversion Repository Hook Reference</a>. As a repository administrator, you'll need to decide which hooks you wish to implement (by way of providing an appropriately named and permissioned hook program), and how. When you make this decision, keep in mind the big picture of how your repository is deployed. For example, if you are using server configuration to determine which users are permitted to commit changes to your repository, you don't need to do this sort of access control via the hook system.</p> <div class="sect3" title="Hook script environment configuration"> <div class="titlepage"> <div> <div> <h4 class="title"><a id="svn.reposadmin.hooks.configuration"></a>Hook script environment configuration</h4> </div> </div> </div> <p>By default, Subversion executes hook scripts with an empty environment—that is, no environment variables are set at all, not even <code class="literal">$PATH</code> (or <code class="literal">%PATH%</code>, under Windows). Because of this, many administrators are baffled when their hook program runs fine by hand, but doesn't work when invoked by Subversion. Administrators have historically worked around this problem by manually setting all the environment variables their hook scripts need in the scripts themselves.</p> <p>Subversion 1.8 introduces a new way to manage the environment of Subversion-executed hook scripts—the hook script environment configuration file. If a Subversion server finds a file named <code class="filename">hooks-env</code> in the repository's <code class="filename">conf/</code> subdirectory, it parses that file as an INI-formatted configuration file and applies the option names and variables found therein to the hook script's execution environment as environment variables.</p> <p>The syntax of the <code class="filename">hooks-env</code> file is pretty straightforward: each section name is the name of a hook script (such as <code class="literal">pre-commit</code> or <code class="literal">post-revprop-change</code>, and the configuration items inside that section are treated as mappings of environment variable names to desired values. Additionally, there is a special <code class="literal">[default]</code> section, which can be used to configure environment variable mappings that should be applied to <span class="emphasis"><em>all</em></span> hook scripts (unless explicitly overridden by per-hook-script settings). See <a class="xref" href="svn.reposadmin.create.html#svn.reposadmin.hooks.configuration.ex-1" title="Example 5.1. hooks-env (custom hook script environment configuration)">Example 5.1, “hooks-env (custom hook script environment configuration)”</a> for a sample <code class="filename">hooks-env</code> configuration file.</p> <div class="example"> <a id="svn.reposadmin.hooks.configuration.ex-1"></a> <p class="title"> <strong>Example 5.1. hooks-env (custom hook script environment configuration)</strong> </p> <div class="example-contents"> <pre class="programlisting"> # All scripts should use a UTF-8 locale and have our hook script # utilities directory on the search path. [default] LANG = en_US.UTF-8 PATH = /usr/local/svn/tools:/usr/bin # The post-commit and post-revprop-change scripts want to run # programs from our custom synctools replication software suite, too. [post-commit] PATH = /usr/local/synctools-1.1/bin:%(PATH)s [post-revprop-change] PATH = /usr/local/synctools-1.1/bin:%(PATH)s </pre> </div> </div> <br class="example-break" /> <div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"> <table border="0" summary="Note"> <tr> <td rowspan="2" align="center" valign="top" width="25"> <img alt="[Note]" src="images/note.png" /> </td> <th align="left">Note</th> </tr> <tr> <td align="left" valign="top"> <p><a class="xref" href="svn.reposadmin.create.html#svn.reposadmin.hooks.configuration.ex-1" title="Example 5.1. hooks-env (custom hook script environment configuration)">Example 5.1, “hooks-env (custom hook script environment configuration)”</a> also demonstrates the nifty string substitution syntax found in Subversion's configuration file parser. In this example, the value of the <code class="literal">PATH</code> option—pulled from the <code class="literal">[default]</code> section of the file—is substituted in place of the <code class="literal">%(PATH)s</code> placeholder text in the per-hook sections. For more about this special syntax, see the <code class="filename">README.txt</code> file which lives in the Subversion runtime configuration directory. (And for more information about that directory, see <a class="xref" href="svn.advanced.confarea.html" title="Runtime Configuration Area">the section called “Runtime Configuration Area”</a>.)</p> </td> </tr> </table> </div> <p>Of course, having exact duplicates of your custom hook script environment configuration files in every single repository's <code class="filename">conf/</code> directory could get cumbersome, especially when you need to make changes to them all. So Subversion's servers allow you to specify an alternate (possibly shared) location for this configuration information.</p> </div> <div class="sect3" title="Common uses for hook scripts"> <div class="titlepage"> <div> <div> <h4 class="title"><a id="svn.reposadmin.hooks.uses"></a>Common uses for hook scripts</h4> </div> </div> </div> <p>Repository hook scripts can offer a wide range of utility, but most tend to fall into a few basic categories: notification, validation, and replication.</p> <p>Notification scripts are those which tell someone that something happened. The most common of these found in a Subversion service offering involve programs which send commit and revision property change notification emails to project members, driven by the post-commit and post-revprop-change hooks, respectively. There are numerous other notification approaches, from issue tracker integration scripts to scripts which operate as IRC bots to announce that something's changed in the repository.</p> <p>On the validation side of things, the start-commit and pre-commit hooks are widely used to allow or disallow commits based on various criteria: the author of the commit, the formatting and/or content of the log message which describes the commit, and even the low-level details of the changes made to files and directories in the commit. Likewise, the pre-revprop-change hook acts as the gateway to revision property changes, which is an especially valuable role considering the fact that revision properties are not themselves versioned, and can therefore only be modified destructively.</p> <p>One special class of change validation that has seen widespread use since Subversion 1.5 was released is validation of the committing client software itself. When Subversion's merge tracking feature (described extensively in <a class="xref" href="svn.branchmerge.html" title="Chapter 4. Branching and Merging">Chapter 4, <em>Branching and Merging</em></a>) was introduced in that release, Subversion administrators needed a way to ensure that once users of their repositories started using the new feature that <span class="emphasis"><em>all</em></span> their merges were tracked. To reduce the chance of someone committing an untracked merge to the repository, they used start-commit hooks to examine the feature capabilities string advertised by Subversion clients. If the committing client didn't advertise support for merge tracking, the commit was denied with instructions to the user to immediately update their Subversion client! <a class="xref" href="svn.reposadmin.create.html#svn.reposadmin.hooks.uses.ex-1" title="Example 5.2. start-commit hook to require merge tracking support">Example 5.2, “start-commit hook to require merge tracking support”</a> provides an example of a start-commit script which does precisely this.</p> <div class="example"> <a id="svn.reposadmin.hooks.uses.ex-1"></a> <p class="title"> <strong>Example 5.2. start-commit hook to require merge tracking support</strong> </p> <div class="example-contents"> <pre class="programlisting"> #!/usr/bin/env python import sys # sys.argv[3] is a colon-delimited capabilities list if 'mergeinfo' not in sys.argv[3].split(':'): sys.stderr.write("""\ ERROR: Commits to this repository must be made using Subversion clients which support the merge tracking feature. Please upgrade your client to at least Subversion 1.5.0. """) sys.exit(1) </pre> </div> </div> <br class="example-break" /> <p> <a id="idp14394320" class="indexterm"></a>Beginning in Subversion 1.8, clients committing against a Subversion 1.8 server will still provide the feature capabilities string, but will also provide additional information about themselves by way of <em class="firstterm">ephemeral transaction properties</em>. Ephemeral transaction properties are essentially revision properties which are set on the commit transaction by the client at the earliest opportunity while committing, but which are automatically removed by the server immediately prior to the transaction becoming a finalized revision. You can inspect these properties using the same tools with which you'd inspect other unversioned properties set on commit transactions during the timeframe between which the start-commit and pre-commit repository hook scripts would operate.</p> <p>The following are the ephemeral transaction properties which Subversion currently provides and implements:</p> <div class="variablelist"> <dl> <dt> <span class="term"> <code class="literal">svn:txn-client-compat-version</code> </span> </dt> <dd> <p>Carries the Subversion library version string with which the committing client claims compatibility. This is useful for deciding whether the client supports the minimal feature set required for proper handling of the repository data.</p> </dd> <dt> <span class="term"> <code class="literal">svn:txn-user-agent</code> </span> </dt> <dd> <p>Carries the <span class="quote">“<span class="quote">user agent</span>”</span> string which describes the committing client program. Subversion's libraries define the initial portion of this string, but third-party consumers of the API (GUI clients, etc.) can append custom information to it.</p> </dd> </dl> </div> <div class="note" title="Note" style="margin-left: 0.5in; margin-right: 0.5in;"> <table border="0" summary="Note"> <tr> <td rowspan="2" align="center" valign="top" width="25"> <img alt="[Note]" src="images/note.png" /> </td> <th align="left">Note</th> </tr> <tr> <td align="left" valign="top"> <p>While most clients will transmit ephemeral transaction properties early enough in the commit process that they may be inspected by the start-commit hook script, some configurations of Subversion will cause those properties to not be set on the transaction until later in the commit process. Administrators should consider performing any validation based on ephemeral transaction properties in both the start-commit and pre-commit hooks—the former to rule out invalid clients before those clients transmit the commit payload; the latter <span class="quote">“<span class="quote">just in case</span>”</span> the validation checks couldn't be performed by the start-commit hook.</p> </td> </tr> </table> </div> <p>As noted before, ephemeral transaction properties are removed from the transaction just before it is promoted to a new revision. Some administrators may wish to preserve the information in those properties indefinitely. We suggest that you do so by using the pre-commit hook script to copy the values of those properties to new property names. In fact, the Subversion source code distribution provides a <code class="filename">persist-ephemeral-txnprops.py</code> script (in the <code class="filename">tools/hook-scripts/</code> subdirectory) for doing precisely that.</p> <p>The third common type of hook script usage is for the purpose of replication. Whether you are driving a simple backup process or a more involved remote repository mirroring scenario, hook scripts can be critical. See <a class="xref" href="svn.reposadmin.maint.html#svn.reposadmin.maint.backup" title="Repository Backup">the section called “Repository Backup”</a> and <a class="xref" href="svn.reposadmin.maint.html#svn.reposadmin.maint.replication" title="Repository Replication">the section called “Repository Replication”</a> for more information about these aspects of repository maintenance.</p> </div> <div class="sect3" title="Finding hook scripts or rolling your own"> <div class="titlepage"> <div> <div> <h4 class="title"><a id="svn.reposadmin.hooks.summary"></a>Finding hook scripts or rolling your own</h4> </div> </div> </div> <p>As you might imagine, there is no shortage of Subversion hook programs and scripts that are freely available either from the Subversion community itself or elsewhere. In fact, the Subversion distribution provides several commonly used hook scripts in its <code class="filename">tools/hook-scripts/</code> subdirectory. However, if you are unable to find one that meets your specific needs, you might consider writing your own. See <a class="xref" href="svn.developer.html" title="Chapter 8. Embedding Subversion">Chapter 8, <em>Embedding Subversion</em></a> for information about developing software using Subversion's public APIs.</p> <div class="warning" title="Warning" style="margin-left: 0.5in; margin-right: 0.5in;"> <table border="0" summary="Warning"> <tr> <td rowspan="2" align="center" valign="top" width="25"> <img alt="[Warning]" src="images/warning.png" /> </td> <th align="left">Warning</th> </tr> <tr> <td align="left" valign="top"> <p>While hook scripts can do almost anything, there is one dimension in which hook script authors should show restraint: do <span class="emphasis"><em>not</em></span> modify a commit transaction using hook scripts. While it might be tempting to use hook scripts to automatically correct errors, shortcomings, or policy violations present in the files being committed, doing so can cause problems. Subversion keeps client-side caches of certain bits of repository data, and if you change a commit transaction in this way, those caches become indetectably stale. This inconsistency can lead to surprising and unexpected behavior. Instead of modifying the transaction, you should simply <span class="emphasis"><em>validate</em></span> the transaction in the <code class="filename">pre-commit</code> hook and reject the commit if it does not meet the desired requirements. As a bonus, your users will learn the value of careful, compliance-minded work habits.</p> </td> </tr> </table> </div> </div> </div> <div class="sect2" title="Berkeley DB Configuration"> <div class="titlepage"> <div> <div> <h3 class="title"><a id="svn.reposadmin.create.bdb"></a>Berkeley DB Configuration</h3> </div> </div> </div> <p>A Berkeley DB environment is an encapsulation of one or more databases, logfiles, region files, and configuration files. The Berkeley DB environment has its own set of default configuration values for things such as the number of database locks allowed to be taken out at any given time, the maximum size of the journaling logfiles, and so on. Subversion's filesystem logic additionally chooses default values for some of the Berkeley DB configuration options. However, sometimes your particular repository, with its unique collection of data and access patterns, might require a different set of configuration option values.</p> <p>The producers of Berkeley DB understand that different applications and database environments have different requirements, so they have provided a mechanism for overriding at runtime many of the configuration values for the Berkeley DB environment. BDB checks for the presence of a file named <code class="filename">DB_CONFIG</code> in the environment directory (namely, the repository's <code class="filename">db</code> subdirectory), and parses the options found in that file. Subversion itself creates this file when it creates the rest of the repository. The file initially contains some default options, as well as pointers to the Berkeley DB online documentation so that you can read about what those options do. Of course, you are free to add any of the supported Berkeley DB options to your <code class="filename">DB_CONFIG</code> file. Just be aware that while Subversion never attempts to read or interpret the contents of the file and makes no direct use of the option settings in it, you'll want to avoid any configuration changes that may cause Berkeley DB to behave in a fashion that is at odds with what Subversion might expect. Also, changes made to <code class="filename">DB_CONFIG</code> won't take effect until you recover the database environment (using <span class="command"><strong>svnadmin recover</strong></span>).</p> </div> <div class="sect2" title="FSFS Configuration"> <div class="titlepage"> <div> <div> <h3 class="title"><a id="svn.reposadmin.create.fsfs"></a>FSFS Configuration</h3> </div> </div> </div> <p>As of Subversion 1.6, FSFS filesystems have several configurable parameters which an administrator can use to fine-tune the performance or disk usage of their repositories. You can find these options—and the documentation for them—in the <code class="filename">db/fsfs.conf</code> file in the repository.</p> </div> </div> <div class="navfooter"> <hr /> <table width="100%" summary="Navigation footer"> <tr> <td width="40%" align="left"><a accesskey="p" href="svn.reposadmin.planning.html">Prev</a> </td> <td width="20%" align="center"> <a accesskey="u" href="svn.reposadmin.html">Up</a> </td> <td width="40%" align="right"> <a accesskey="n" href="svn.reposadmin.maint.html">Next</a></td> </tr> <tr> <td width="40%" align="left" valign="top">Strategies for Repository Deployment </td> <td width="20%" align="center"> <a accesskey="h" href="index.html">Home</a> </td> <td width="40%" align="right" valign="top"> Repository Maintenance</td> </tr> </table> </div> <div xmlns="" id="vcws-footer"> <hr /> <img src="images/cc-by.png" style="float: right;" /> <p>You are reading <em>Version Control with Subversion</em> (for Subversion 1.8), by Ben Collins-Sussman, Brian W. Fitzpatrick, and C. Michael Pilato.</p> <p>This work is licensed under the <a href="http://creativecommons.org/licenses/by/2.0/">Creative Commons Attribution License v2.0</a>.</p> <p>To submit comments, corrections, or other contributions to the text, please visit <a href="http://www.svnbook.com/">http://www.svnbook.com/</a>.</p> </div> </body> </html>