Branches in CVS

Branches are a little confusing in cvs, so it seemed worth writing a short introduction

What is a Branch?

CVS is able to handle branches, collections of revisions that for some reason should not be committed onto the mail trunk of development. For example, if I wanted to try some experiment, and wasn't sure that it would work, I wouldn't want to check it into the main line, as it might well not work. I could work on it secretly, never checking any files into the repository --- but then I'd lose all the advantages of using cvs. Or I could work on my own branch, without disturbing anyone else.

The original set of versions, before the branch was created, is called the main line or main branch. It's what you get when you simply say cvs co my_prog. After a branch is created the main line is still the default version. Unless you take special pains to merge changes from a branch into the main line or vice-versa, the changes that you make on a branch will never appear on the main line, and vice-versa.

For example, when we cut a version of photo to bring to Fermilab for integration, we put it on a branch. If in the ensuing months some terrible bug were discovered in the cut version, without a branch we'd have two options: either cut a new version (based on whatever work we'd done since the last cut version) with all the attendent trouble and risk of bugs, or else ask the users of photo to struggle on working around the bug. With a branch we have another choice: we can fix the bug on the branch and cut a new version based on the branch. Because the only change made to photo since the last version is the bug fix, the normal worries and fresh bugs associated with a newly cut version are minimised.

Creating Branches

Creating a branch is easy, but it's also easy to do wrong. The basic point to remember is that cvs uses tags (symbolic names, usually used for naming cut versions) in two distinct ways on branches, both as the name of the branch and the name of the revision on the branch.

The simplest way to create a branch called branchname is to cd to a copy of the code that you want to branch, say my_prog, and say:

   cvs tag branchname_0
   cvs tag -r branchname_0 -b branchname
which first names the branch's point of attachment (branchname_0), and then actually creates the branch. It's probably a good idea to update first to check that your version really is up-to-date; I usually say cvs -nq update. You are now ready to check out a copy of your branch.

If you want the original version of my_prog to reside on the new branch (i.e. the version that you ran the taq command on), now say

   cvs update -r branchname

If you'd rather keep a copy of the main line as well as the new branch, go to some directory that doesn't have the main line checked out in it, and say

   cvs co -r branchname my_prog

After either one of these last two paragraphs, any further work that you do on the branched copy of my_prog will automatically be committed onto the branch. You can get very confused if you forget that your files have been `poisoned' by the branch tag --- see the section on sticky tags for details.

Let's ask cvs what it knows about some file on this branch; both the branch name and the attachment point name are visible:

   > cvs log my_prog/rhl.c
     ...
   symbolic names:
           branchname_0: 1.15
           branchname: 1.15.0.2
           v1_2: 1.9
           V1_0: 1.1
Note that the name branchname_0 refers to revision 1.15, that is before the branch was made (branches on the revision have names like 1.15.2.1; see the section on branch numbers for a discussion).

Checking things onto a Branch

If your file is already on the branch (i.e. it's already got a sticky tag set) you don't have to do anything special, just a regular cvs ci file will commit it to the branch. If you have changed a file that isn't on the branch, you can commit it to the branch with
cvs ci -r branchname file
The file will have a sticky tag set after this operation, that is cvs will treat it as if it's on the branch, even though the rest of the directory is still on the main line. (You can use cvs update -A to get it back onto the main line but then your changes will seem to disappear. See the section on merging for ways to get your changes onto both the main line and the branch, as is often desireable).

Due to a bug in cvs 1.3 (on which rcvs is currently based), if you add a new file in a directory checked out on a branch, the file will appear on the main line, not on your branch. Caveat scriptor.

What is a Sticky Tag?

When you start using branches, things called sticky tags will start being attached to your files. You can see them with the cvs status command. For example:
   > cvs status my_prog/rhl.c
   ===================================================================
   File: rhl.c. Status: Up-to-date
   
       Version:            1.15.2.1        Mon Dec 12 12:05:04 1994
       RCS Version:        1.15.2.1        /u/cvs/src/my_prog/rhl.c
       Sticky Tag:         branchname (revision: 1.15.2.1)
       Sticky Date:        (none)
       Sticky Options:     (none)

Tags themselves should be familiar --- they are simply what cvs calls the names that given to revisions, typically cut versions, and sticky tags are basically the corresponding names given to branches. They are called sticky because they stay attached to a file; once a file has a tag stuck to it, all future cvs commands applied to that file refer to the branch rather than to the main line of development.

For example, if you check in a revision onto the branch branchname with the command cvs ci -r branchname rhl.c and then someone adds some derogatory comments on the main branch (i.e. they don't specify a tag with -r), if you issue the command cvs update rhl.c you will not see the changes. You'd have to say cvs update -A rhl.c to see them; the -A flag tells cvs to ignore sticky tags (and other sticky attributes --- see the cvs manual for details). After using -A your code is no longer on the branch, and update will no longer update your directory with changes made to the branch.

Merging changes from a Branch into the Main Line

You can use co or update with the -j flag, for example to merge all changes made on branch branchname into the mainline, you'd find some directory which didn't have my_prog checked out, and say:
   cvs co -j branchname my_prog
(you could use update instead).

The very next thing to do is to tag the branch --- if you don't, you'll never again be able to merge the branch into the main line without gnashing of teeth. So do it:

   cvs rtag -r branchname branchname_1 my_prog
(that reads, ``give the name branchname_1 to the top of branch branchname'').

If ever you want to merge from the branch again, if you say:

   cvs co -j branchname my_prog
you'll get lots of merge conflicts, because cvs will try to merge the changes made before branchname_1 for a second time. What you want to say is:
   cvs co -j branchname_1 -j branchname my_prog
As an alternative, you could cd to the main line, and say
   cvs update -j branchname_1 -j branchname
After either of these commands you naturally immediately typed:
   cvs rtag -r branchname branchname_2 my_prog
so as to be able to continue merging if more bugs appear in the future.

If you now said cvs log, the output is discussed in the section on branch numbers.

Significance of Branch Numbers

You should never need to know this, but just in case you do, here it is. When you say cvs log you get a list of branch numbers as maintained by RCS, the programme that cvs uses to actually manage files. They are numbers such as 1.15 or 1.15.0.2. It sometimes helps to be able to interpret them when dealing with branches.

At the end of the section on merging, the output of cvs log would look something like:

   > cvs log my_prog/rhl.c
     ...
   symbolic names:
           branchname_2: 1.15.2.2
           branchname_1: 1.15.2.1
           branchname_0: 1.15
           branchname: 1.15.0.2
           v1_2: 1.9
           V1_0: 1.1
Revisions on the main line have names such as 1.9, which means the 9th revision since the file was created. RCS can handle numbers such as 2.13, but cvs never asks it to (although you could ask cvs to ask RCS to do so, there'd be no point). Note that branchname_0 is on the main line; that's not unexpected as it was tagged before any changes were committed to the branch.

The branch's tag branchname has an RCS number of the form 1.15.0.2; the .0.2 is cvs magic, but it means, ``a branch starting from 1.15''.

The numbers on the branch itself are 1.15.2.1 and 1.15.2.2 for the first and second changes made on the branch. Note that they are not 1.15.0.3 as you might have expected; more cvs magic.

If you branched the branch (which you could do if you were really masochistic) you'd get version numbers such as 1.15.2.3.0.2 for the branch (twig?), and 1.15.2.3.2.1 for the first change committed to the branch.