==================================================================== NEW SECTION FOR CHAPTER 1 ==================================================================== A Quick Start Some people have trouble absorbing a new technology by reading the sort of "top down" approach provided by this book. This section is a very short introduction to Subversion, and is designed to give "bottom up" learners a fighting chance. If you're one of those folks who prefers to learn by experimentation, the following demonstration will get you up and running. Along the way, we give links to the relevant chapters of this book. If you're new to the entire concept of version control or to the copy-modify-merge model used by both CVS and Subversion, then you should read before going any further. The following example assumes that you have svn, the Subversion commandline client, and svnadmin, the administrative tool, ready to go. It also assumes that your svn client has been compiled against Berkeley DB. To verify this, run svn --version and make sure the ra_local module is available. Without this module, the client cannot access file:// URLs. Subversion stores all versioned data in a central repository. To begin, create a new repository: $ svnadmin create /path/to/repos $ ls /path/to/repos conf/ dav/ db/ format hooks/ locks/ README.txt This command creates a new directory /path/to/repos which contains a Subversion repository. Make sure that this directory lives on a local disk, not a network share. This new directory mainly contains a collection of Berkeley DB database files. You won't see your versioned files if you peek inside. For more information about repository creation and maintenance, see . Next, create a tree of files and directories to import into the repository. For reasons that will be clear later on (see ), your structure should contain three top-level directories named branches, tags, and trunk: /tmp/project/branches/ /tmp/project/tags/ /tmp/project/trunk/ foo.c bar.c Makefile … Once you have a tree of data ready to go, import the data into the repository with the svn import command (see ): $ svn import /tmp/project file:///path/to/repos -m "initial import" Adding /tmp/project/branches Adding /tmp/project/tags Adding /tmp/project/trunk Adding /tmp/project/trunk/foo.c Adding /tmp/project/trunk/bar.c Adding /tmp/project/trunk/Makefile … Committed revision 1. $ Now the repository contains your tree of data. At this point, you create a working copy of the trunk directory. This is where your actual work will happen: $ svn checkout file://path/to/repos/trunk project A project/foo.c A project/bar.c A project/Makefile … Checked out revision 1. Now you have a personal copy of part of the repository in a new directory named project. You can edit the files in your working copy and then commit those changes back into the repository. Enter your working copy and edit a file's contents. Run svn diff to see unified diff output of your changes. Run svn commit to commit the new version of your file to the repository. Run svn update to bring your working copy up-to-date with the repository. For a full tour of all the things you can do with your working copy, read . At this point, you have the option of making your repository available to others over a network. See to learn about the different sorts of server processes available and how to configure them. ==================================================================== NEW SECTION FOR CHAPTER 4 ==================================================================== Noticing or Ignoring Ancestry When conversing with a Subversion developer, you might very likely hear reference to the term ancestry. This word is used to describe the relationship between two objects in a repository: if they're related to each other, than one object is said to be an ancestor of the other. For example, suppose you commit revision 100, which includes a change to a file foo.c. Then foo.c@99 is an ancestor of foo.c@100. On the other hand, suppose you commit the deletion of foo.c in revision 101, and then add a new file by the same name in revision 102. In this case, foo.c@99 and foo.c@102 may appear to be related (they have the same path), but in fact are completely different objects in the repository. They share no history or ancestry. The reason for bringing this up is to point out an important difference between svn diff and svn merge. The former command ignores ancestry, while the latter command is quite sensitive to it. For example, if you asked svn diff to compare revisions 99 and 102 of foo.c, you would see line-based diffs; the diff command is blindly comparing two paths. But if you asked svn merge to compare the same two objects, it would notice that they're unrelated and first attempt to delete the old file, then add the new file; you would see a D foo.c followed by a A foo.c. Most merges involve comparing trees that are ancestrally related to one another, and therefore svn merge defaults to this behavior. Occasionally, however, you may want the merge command to compare two unrelated trees. For example, you may have imported two source-code trees representing different vendor releases of a software project (see ). If you asked svn merge to compare the two trees, you'd see the entire first tree being deleted, followed by an add of the entire second tree! In these situations, you'll want svn merge to do a path-based comparison only, ignoring any relations between files and directories. Add the option to your merge command, and it will behave just like svn diff. (And conversely, the option will cause svn diff to behave like the merge command.) ==================================================================== NEW SECTION FOR CHAPTER 7 ==================================================================== Automatic Property Setting Properties are a powerful feature of Subversion, acting as key components of many Subversion features discussed elsewhere in this and other chapters—textual diff and merge support, keyword substitution, newline translation, etc. But to get the full benefit of properties, they must be set on the right files and directories. Unfortunately, that can be a step easily forgotten in the routine of things, especially since failing to set a property doesn't usually result in an obvious error condition (at least compared to, say, failing to add a file to version control). To help your properties get applied to the places that need them, Subversion provides a couple of simple but useful features. Whenever you introduce a file to version control using the svn add or svn import commands, Subversion runs a very basic heuristic to determine if that file consists of human-readable or non-human-readable content. If the latter is the decision made, Subversion will automatically set the svn:mime-type property on that file to application/octet-stream (the generic this is a collection of bytes MIME type). Of course, if Subversion guesses incorrectly, or if you wish to set the svn:mime-type property to something more precise—perhaps image/png or application/x-shockwave-flash—you can always remove or edit that property. Subversion also provides the auto-props feature, which allows you to create mappings of filename patterns to property names and values. These mappings are made in your runtime configuration area. They again affect adds and imports, and not only can override any default MIME type decision made by Subversion during those operations, they can also set additional Subversion or custom properties, too. For example, you might create a mapping that says that any time you add JPEG files—ones that match the pattern *.jpg—Subversion should automatically set the svn:mime-type property on those files to image/jpeg. Or perhaps any files that match *.cpp should have svn:eol-style set to native, and svn:keywords set to Id. Auto-prop support is perhaps the handiest property related tool in the Subversion toolbox. See for more about configuring that support. ==================================================================== REPLACEMENT SECTION FOR CHAPTER 7 ==================================================================== General Vendor Branch Management Procedure Managing vendor branches generally works like this. You create a top-level directory (such as /vendor) to hold the vendor branches. Then you import the third party code into a subdirectory of that top-level directory. You then copy that subdirectory into your main development branch (for example, /trunk) at the appropriate location. You always make your local changes in the main development branch. With each new release of the code you are tracking you bring it into the vendor branch and merge the changes into /trunk, resolving whatever conflicts occur between your local changes and the upstream changes. Perhaps an example will help to clarify this algorithm. We'll use a scenario where your development team is creating a calculator program that links against a third-party complex number arithmetic library, libcomplex. We'll begin with the initial creation of the vendor branch, and the import of the first vendor drop. We'll call our vendor branch directory libcomplex, and our code drops will go into a subdirectory of our vendor branch called current. And since svn import creates all the intermediate parent directories it needs, we can actually accomplish both of these steps with a single command. $ svn import /path/to/libcomplex-1.0 \ http://svn.example.com/repos/vendor/libcomplex/current \ -m 'importing initial 1.0 vendor drop' … We now have the current version of the libcomplex source code in /vendor/libcomplex/current. Now, we tag that version (see ) and then copy it into the main development branch. Our copy will create a new directory called libcomplex in our existing calc project directory. It is in this copied version of the vendor data that we will make our customizations. $ svn copy http://svn.example.com/repos/vendor/libcomplex/current \ http://svn.example.com/repos/vendor/libcomplex/1.0 \ -m 'tagging libcomplex-1.0' … $ svn copy http://svn.example.com/repos/vendor/libcomplex/1.0 \ http://svn.example.com/repos/calc/libcomplex \ -m 'bringing libcomplex-1.0 into the main branch' … We check out our project's main branch—which now includes a copy of the first vendor drop—and we get to work customizing the libcomplex code. Before we know it, our modified version of libcomplex is now completely integrated into our calculator program. And entirely bug-free, of course! A few weeks later, the developers of libcomplex release a new version of their library—version 1.1—which contains some features and functionality that we really want. We'd like to upgrade to this new version, but without losing the customizations we made to the existing version. What we essentially would like to do is to replace our current baseline version of libcomplex 1.0 with a copy of libcomplex 1.1, and then re-apply the custom modifications we previously made to that library to the new version. But we actually approach the problem from the other direction, applying the changes made to libcomplex between versions 1.0 and 1.1 to our modified copy of it. To perform this upgrade, we checkout a copy of our vendor branch, and replace the code in the current directory with the new libcomplex 1.1 source code. We quite literally copy new files on top of existing files, perhaps exploding the libcomplex 1.1 release tarball atop our existing files and directories. The goal here is to make our current directory contain only the libcomplex 1.1 code, and to ensure that all that code is under version control. Oh, and we want to do this with as little version control history disturbance as possible. After replacing the 1.0 code with 1.1 code, svn status will show files with local modifications as well as, perhaps, some unversioned or missing files. If we did what we were supposed to do, the unversioned files are only those new files introduced in the 1.1 release of libcomplex—we run svn add on those to get them under version control. The missing files are files that were in 1.0 but not in 1.1, and on those paths we run svn remove. Finally, once our current working copy contains only the libcomplex 1.1 code, we commit the changes we made to get it looking that way. Our current branch now contains the new vendor drop. We tag the new version (in the same way we previously tagged the version 1.0 vendor drop), and then merge the differences between the tag of the previous version and the new current version into our main development branch. $ cd working-copies/calc $ svn merge http://svn.example.com/repos/vendor/libcomplex/1.0 \ http://svn.example.com/repos/vendor/libcomplex/current \ libcomplex … # resolve all the conflicts between their changes and our changes $ svn commit -m 'merging libcomplex-1.1 into the main branch' … In the trivial use-case, the new version of our third-party tool would look, from a files-and-directories point of view, just like the previous version. None of the libcomplex source files would have been deleted, renamed or moved to different locations—the new version would contain only textual modifications against the previous one. In a perfect world, our modifications would apply cleanly to the new version of the library, with absolutely no complications or conflicts. But things aren't always that simple, and in fact it is quite common for source files to get moved around between releases of software. This complicates the process of ensuring that our modifications are still valid for the new version of code, and can quickly degrade into a situation where we have to manually recreate our customizations in the new version. Once Subversion knows about the history of a given source file—including all its previous locations—the process of merging in the new version of the library is pretty simple. But we are responsible for telling Subversion how the source file layout changed from vendor drop to vendor drop. ==================================================================== NEW SECTION FOR CHAPTER 9 ==================================================================== <command>svnserve</command> svnserve allows access to Subversion repositories using the svn network protocol. You can run svnserve either as a standalone server process, or you can have another process, such as inetd, xinetd or sshd, launch it for you. Once the client has selected a repository by transmitting its URL, svnserve reads a file named conf/svnserve.conf in the repository directory to determine repository-specific settings such as what authentication database to use and what authorization policies to apply. See for details of the svnserve.conf file. <command>svnserve</command> Switches Unlike the previous commands we've described. svnserve has no subcommands—svnserve is controlled exclusively by switches. () Causes svnserve to run in daemon mode. svnserve backgrounds itself and accepts and serves TCP/IP connections on the svn port (3690, by default). =PORT Causes svnserve to listen on PORT when run in daemon mode. =HOST Causes svnserve to listen on the interface specified by HOST, which may be either a hostname or an IP address. When used together with , this switch causes svnserve to stay in the foreground. This switch is mainly useful for debugging. () Causes svnserve to use the stdin/stdout file descriptors, as is appropriate for a daemon running out of inetd. () Displays a usage summary and exits. =ROOT (=ROOT) Sets the virtual root for repositories served by svnserve. The pathname in URLs provided by the client will be interpreted relative to this root, and will not be allowed to escape this root. () Causes svnserve to run in tunnel mode, which is just like the inetd mode of operation (serve one connection over stdin/stdout) except that the connection is considered to be pre-authenticated with the username of the current uid. This flag is selected by the client when running over a tunnel agent such as ssh. () When running in daemon mode, causes svnserve to spawn a thread instead of a process for each connection. The svnserve process still backgrounds itself at startup time. () Causes svnserve to accept one connection on the svn port, serve it, and exit. This option is mainly useful for debugging.