A Crash Course in Subversion, Part 2
Working with Branches and Tags
Another of Subversion's improvements over CVS is its ability to create branches and tags in constant time and space, as opposed to in time and space proportional to the number of files involved, as in CVS. Subversion accomplishes this by leveraging the design of its versioning repository filesystem. Branches and tags are copies of existing directories, usually placed in specific directories so that users know they're "special."
There are a variety of reasons you might want to create a branch or tag. Generally, you use tags to mark a particular version of your project so you can easily refer back to it later. You might want to make a tag when you release a new version of your project, so that later on you don't have to remember what revision of the repository corresponds to that particular release. Instead of remembering what revision of the trunk corresponds to the release, you can just look at the copy you placed under the tags directory.
Creating Branches and Tags
Although you could create a branch or tag by checking out the top level of the repository and doing a standard svn copy in your working copy, that would be quite inefficient. You would use up a huge amount of disk space and possibly network bandwidth in creating the initial working copy, because you would be checking out every branch and tag in the repository simultaneously, and then svn copy would have to do a lot of extra disk I/O copying files around, only to end up wasting a lot of bandwidth again committing your branch or tag to the repository. Fortunately, the Subversion developers anticipated this problem and designed the svn copy command to run against the repository directly as well as within the working copy. Creating a tag is as simple as this:
$ svn copy file:///path/to/repos/trunk file:///path/to/repos/tags/ 0.8-release \ -m "tagging 0.8 release" Committed revision 11. $ svn diff file:///path/to/repos/trunk file:///path/to/repos/tags/ 0.8-release $
As you can see from the output of svn diff, the tag is identical to the HEAD revision of /trunk. Creating a new branch is done in exactly the same way. The only difference is that a tag is intended to never be modified again (this may be enforced by various means; see Chapter 3 for how to do it with hook scripts and Chapter 5 for how to do it with mod_authz_svn), whereas a branch will be used for further development.
Note: mod_authz_svn is an Apache module that works with Subversion's Apache-based server to allow you to control which users have access to various parts of your repository. mod_authz_svn might be used to control who can create new branches or tags, or who can write to a particular branch.
Merging Changes Between Branches
For a branch to be of much use, the version control system needs to provide a way to manage the process of merging changes between related branches. Generally, a branch is created for some kind of development, and at some point in the future either changes from the branch will have to be merged back into the trunk (or wherever the branch originated) or changes from the trunk will have to be merged into the branch (this is common in the case of long-lived branches to support released versions of software). In Subversion, the process of merging changes is handled by the svn merge command. The merge command has two modes of operation. In the first, you give the command two source URLs (with optional revisions specified with @REVNUM at the end of the URL), and it will merge the differences between the two sources into your current working directory (or another directory if it's specified on the command line). The process looks like this:
$ svn copy file:///path/to/repos/tags/0.9-release \ file://path/to/repos/branches/my-development-branch \ -m "create development branch from version 0.9 tag" Committed revision 12. $ svn checkout file:///path/to/repos/branches/my-development-branch wc A wc/Makefile A wc/foo.c A wc/zot.c A wc/main.c Checked out revision 12. $ cd wc [ ... do some work, make some changes, commit them to the branch ... ] $ [ ... time passes,changes are made in the trunk,version 1.0 is released, and now you want to merge those changes into your branch ... ] $svn merge file:///path/to/repos/branches/my-development-branch \ file:///path/to/repos/tags/1.0-release \ . A bar.c A README A baz.c D foo.c $ svn status A + README A + bar.c A + baz.c D foo.c $ svn commit -m "merge changes from trunk up to 1.0 release into dev branch" Adding README Adding bar.c Adding baz.c Deleting foo.c Transmitting file data ... Committed revision 20. $
First, you make a development branch by copying the 0.9-release tag. Then you use the branch for some private development, committing changes into it as desired. In the meantime, more changes are made to the version in trunk and the 1.0 version is tagged as tags/1.0-release. At this point, you merge the changes that occurred between the point you created your branch and the time 1.0 was tagged into your local working copy (which was checked out from the branch) and commit them. Now your branch is up to date with regard to all the changes that were made to the trunk. Note that just like when you run svn update, when you merge changes into your working copy there's always the chance of a conflict, and any that occur will have to be resolved before you can continue. Likewise, as with any other change to a working copy, you'll have to svn commit the results of the merge for them to be propagated back into the repository.
More often, you'll need to merge a particular change or set of changes from one branch into another. For example, say you have a branch that's being used to manage the "stable" release of your software, separate from the main development trunk, where more complex, destabilizing changes are being made. When a bug fix is made to the main version of the code in the trunk, you might want to merge that particular change into the stable branch, so it will be in the next release. In this case, when the change you're merging is a particular revision, you can use a slightly simpler version of the svn merge, where there's a single source (which can be a URL or a working copy path), the revisions of which are specified via the standard -r START:END syntax. The changes to that source path over that range of revisions will be merged into your current working directory (or another working copy directory if one is given on the command line). The process looks like this:
$ svn merge -r 12:13 file:///path/to/repos/branches/ my-development-branch U main.c U zot.c $ svn status M main.c M zot.c $ svn commit -m "merged changes from development branch into trunk" Sending main.c Sending zot.c Transmitting file data .. Committed revision 30. $
Here the change to /branches/my-development-branch from revision 13 is has been merged into a checked-out version of the trunk. Once the user verifies that the change is correct, it can be committed and the process will be complete.
|Using svn merge to Revert a Change|
|A less common use of svn merge is to revert the change committed in a particular revision. To do this, you simply reverse the order of the revision arguments. For example, to revert the change to main.c that occurred in revision 5, you run svn merge -r 5:4 main.c. This means, literally, apply the change needed to go from revision 5 of main.c to revision 4 of main.c to the version of main.c in your current working directory. In practice, you can use this to revert a particular change that shouldn't have been committed.|
Switching to Another Branch
Because creating branches in Subversion is cheap (in terms of time and disk space at least), some thought has gone into how to make them easier to work with. The majority of this work will be done after the 1.0 release of Subversion is complete (specifically, the process of merging changes between branches will be made easier), but there is one feature available now that makes working with branches considerably simpler, the svn switch command. svn switch lets you move your checked-out working copy (and any changes you made to it, assuming they don't conflict) to another branch in the repository. The standard use case is either to migrate an entire working copy from one branch to another or, more commonly, to move specific parts of your working copy to another branch.
The best way to understand this concept is probably through an example. Say that you're adding a new feature to Subversion itself, but the work is going to take some time to complete. Subversion uses an unstable trunk policy, which means that most development is done right in the main trunk branch. However, for changes that are likely to be either disruptive to other developers (by breaking existing functionality for a time, for example) or that are highly experimental and might never be destined to be merged into the trunk, a branch will be created.
The Subversion code base is split into several libraries, each of which lives in its own subdirectory. Because most changes of this sort will be limited to one of those libraries, you might make the branch via an svn copy command targeting the repository directly, and then switch the affected subdirectory over to that branch. As you continue to make modifications to that subdirectory, you can safely commit them to the repository, and none of the other developers will have to see them unless they specifically check out (or switch to) that branch. You can safely svn update all the other directories in your working copy to track the latest changes other developers are making to the trunk.
This ensures that your modifications to one library will continue to work with the latest version of the software (making the eventual merging of my changes into the trunk easier), while avoiding the need to inconvenience other developers by making disruptive changes in the trunk. The only thing you need to remember is that other developers could be making changes to the same library you're working on in the trunk, so as those changes occur, you need to merge them into your development branch to ensure that your final merge doesn't obliterate the other developers' changes. Let's see a quick example of how this can work:
$ svn list file:///path/to/repos/trunk library_one/ library_two/ library_three/ main.c $ svn copy -m "create branch for development of library one"\ file:///path/to/repos/trunk \ file:///path/to/repos/branches/library-one-dev Committed revision 10. $ svn checkout file:///path/to/repos/trunk wc [ ... lots of output ... ] $ cd wc $ svn info library_one | grep ^URL URL:file:///path/to/repos/trunk/main.c $ svn switch file:///path/to/repos/branches/library-one-dev/library_one \ library_one At revision 10. $ svn info library_one |grep ^URL URL:file:///path/to/repos/branches/library-one-dev/library_one $ svn status S library_one $
In this example, you start out by making a new branch by copying the trunk, which contains a single C file, main.c, and several subdirectories, each of which contains an independent library of code that's used by main.c.
You then check out the trunk, so your copy of the library_one directory has a URL of file:///path/to/repos/trunk/library_one. Next, you use svn switch to switch your copy of library_one over to the development branch. Its URL now points to the version of library_one on the development branch you created, and when you run svn status you can see that it's switched because of the S in the fifth column of the status output.
Going forward, each time you run any command that involves library_one or any of its contents, it will refer to the versions of those files that live on the branch. Updates will pull down changes that occur on the branch, commits of changes you make to it will be committed to the branch, and so forth. This allows you to track the changes that are being made on the branch in the context of the changes to the rest of the project that are going on in the trunk, which can be quite useful for a couple of reasons. First off, it allows the developers not working on changes to the library_one directory to avoid having to deal with your changes, which could be experimental or large enough that they introduce temporary breakage. Second, it allows you to make sure your new changes work with what others are doing in the trunk, without having to explicitly merge those changes over to the trunk, which simplifies the process of working on your branch.
Page 2 of 6