CVS branching in UW Calendar

Most of the ideas here were taken from Pragmatic Version Control Using CVS, by David Thomas and Andrew Hunt. The book covers branching in chapter 7. It's a good book. You should buy it.

Most CVS projects live on the mainline, an unbroken string of versions of the files in the project. A branch is a diversion from the mainline, sort of an 'alternate universe', where files are allowed to be different than the mainline. This can be useful in many situations. The classic reason for branching occurs when you create a release. The release may have bugs. You want to be able to create a future release with the bugs fixed, but no new features. But you want to continue working on the codebase as well. So you create a release branch, a sort of CVS deadend that only contains that release and any bugfix updates to it. Meanwhile, you continue developing on the mainline.

CVS allows you to create branches arbitrarily, including on other branches, which can create incredibly convoluted 'trees' of code. To avoid the confusion of arbitrary branching, we will never branch off branches, only off the mainline. And we will only create branches in two cases:

  1. Release branches, just prior to release. These will be dead-ends from the mainline. Bugs can be fixed on the branch and the fixes propagated to the mainline (and vice versa), but eventually we will stop working on these branches. These branches will never be merged back into the mainline.
  2. Experimental branches, when a set of developers wishes to work on a possibly complex change to the code. For example, if someone were to work on adding group calendaring to UW Calendar, it would be best to work on a branch. Working on a branch shields the new project from changes to the mainline, and vice versa. These branches may be merged back into the mainline, if they are successful. If the experiment doesn't work out, these branches can be abandoned.

With that in mind, here are branching scenarios and how they should be handled.

Scenario 1: Creating a release branch.

This is what you do before you release, to prepare for a release without having to worry about other people checking in code. Presumably I'm the only one who does this.

I recently created a branch for release 2.2. I did this by getting an up-to-date version of the mainline code (cvs update; cvs commit), then running:

cvs rtag -b RB_2_2 calendar2

The 2_2 part changes depending on the release number.

Scenario 2: Working with the release branch

To work on this branch, instead of the mainline, you need to check out the code. By default, checking out will put your code in a directory named calendar2. To avoid confusion, it is better to leave that directory for mainline work. Instead, check out into a directory whose name makes it clear that you're working on a branch.

cvs co -r RB_2_2 -d rb2.2 calendar2

This will put a copy of release branch 2.2's code in a directory called rb2.2. Again, obviously, the name will change depending on the release number.

Scenario 3: Fixing a bug

Let's say before you release 2.2 you want to fix bug #1234 in Bugzilla. Presumably you also want to fix this bug in the mainline, but first you do your work on the branch. The key is you want to tag your work both before and after you fix the bug, so that the changes can be easily made to the mainline.

First, check out a copy of release branch 2.2, as in Scenario #2. Then:

     cd ~/rb2.2
     cvs tag PRE_1234
     // fix bug
     cvs commit
     cvs tag POST_1234
Note that these are 'tags', not 'rtags'.

If you want to make a fix that doesn't have an attached Bugzilla bug number, you should make up a similar tag that is hopefully unique, involving your institution or initials and the date. For example, PRE_gsb20050222, PRE_uw20051225. Or you could piggyback a fix onto a small Bugzilla bug fix, and tag it all with the Bugzilla bug number.

If a bug has to be fixed twice, you should add the date to the end of the tags.

Scenario 4: Applying a bugfix to the mainline

To fix the corresponding bug in the mainline, let's presume your copy of the mainline's code is in ~/calendar2.

     cd ~/calendar2
     cvs update			// make sure you have the current version
     cvs update -j PRE_1234 -j POST_1234
     // test, fix conflicts, etc.
     cvs commit

Note that you must commit after you do the update -j --- update does not change the repository. Note also that update -j doesn't magically do everything for you. If someone's moved around the files affected by your bugfix, you might have to fix things by hand. And there could be conflicts. Best to do some testing. Testing would of course be easiest if you had written unit or other tests back when you made the fix in the release branch. [hint, hint]

Also note that you can fix bugs on the mainline and move the changes to a release branch in a similar manner, as long as you tag before and after the bug fix.

Scenario 5: Generating a release

Again, presumably I'm the only one doing this for now.

 	cd ~/rb2.2
 	cvs update
        // test
 	cvs tag REL_2_2

Unfortunately, it is rarely the case that a release works when tagged, even after testing. So you can think of the above tag as a candidate release tag. After we've confirmed that everything works, and release notes have been updated in the README files, I tag again as, for example, REL_2_2_RELEASED. You can checkout this tag to get the code as released.

I presume after the release we might still use the branch in case someone finds a bug, or we want to make other similar easy changes. Subsequent releases would be called 2.2.1, 2.2.2, etc. But once we move to release 2.3, we would create a new branch and the 2.2 branch would pretty much be dead.

Scenario 6: Experimental branches

If you're about to start on an involved change, it would be wise to create a branch and work on the branch. This prevents other people from messing your work up, and vice versa. Basically, this is the same as scenarios 1 and 2, except with a different branch name:

cvs rtag -b try_gb_20050222 calendar2

Use your initials instead of gb. Instead of your initials, you could also use an abbreviation for the feature itself, like try_newdb20050222.

To put this branch in a directory called try_gb:

cvs co -r try_gb_20050222 -d try_gb calendar2

When you are done, if everything works well, you can merge all the changes in a branch back into the mainline.

     cd ~/try_gb
     cvs commit
     cd ~/calendar2
     cvs update
     cvs update -j try_gb_20050222
     // test, etc.
     cvs commit

A merge like this should only be done once. After the merge, the branch should no longer be used and you should work on the mainline or create a new branch.

Greg Barnes
February 28, 2005
University of Washington
Computing & Communications