Why are branches useful?
Let's return for a moment to the scenario of the developer who, in the midst of working on a new version of the program, receives a bug report about an older released version. Assuming the developer fixes the problem, she still needs a way to deliver the fix to the customer. It won't help to just find an old copy of the program somewhere, patch it up without CVS's knowledge, and ship it off. There would be no record of what was done; CVS would be unaware of the fix; and later if something was discovered to be wrong with the patch, no one would have a starting point for reproducing the problem.
It's even more ill-advised to fix the bug in the current, unstable version of the sources and ship that to the customer. Sure, the reported bug may be solved, but the rest of the code is in a half-implemented, untested state. It may run, but it's certainly not ready for prime time.
Because the last released version is thought to be stable, aside from this one bug, the ideal solution is to go back and correct the bug in the old release – that is, to create an alternate universe in which the last public release includes this bug fix.
That's where branches come in. The developer splits off a branch, rooted in the main line of development (the trunk) not at its most recent revisions, but back at the point of the last release. Then she checks out a working copy of this branch, makes whatever changes are necessary to fix the bug, and commits them on that branch, so there's a record of the bug fix. Now she can package up an interim release based on the branch and ship it to the customer.
Her change won't have affected the code on the trunk, nor would she want it to without first finding out whether the trunk needs the same bug fix or not. If it does, she can merge the branch changes into the trunk. In a merge, CVS calculates the changes made on the branch between the point where it diverged from the trunk and the branch's tip (its most recent state), then applies those differences to the project at the tip of the trunk. The difference between the branch's root and its tip works out, of course, to be precisely the bug fix.
Another good way to think of a merge is as a special case of updating. The difference is that in a merge, the changes to be incorporated are derived by comparing the branch's root and tip, instead of by comparing the working copy against the repository.
The act of updating is itself similar to receiving patches directly from their authors and applying them by hand. In fact, to do an update, CVS calculates the difference (that's "difference" as in the diff program) between the working copy and the repository and then applies that diff to the working copy just as the patch program would. This mirrors the way in which a developer takes changes from the outside world, by manually applying patch files sent in by contributors.
Thus, merging the bug fix branch into the trunk is just like accepting some outside contributor's patch to fix the bug. The contributor would have made the patch against the last released version, just as the branch's changes are against that version. If that area of code in the current sources hasn't changed much since the last release, the merge will succeed with no problems. If the code is now substantially different, however, the merge will fail with conflict (that is, the patch will be rejected), and some manual fiddling will be necessary. Usually this is accomplished by reading the conflicting area, making the necessary changes by hand, and committing. Figure 2.3 shows a picture of what happens in a branch and merge.
(branch on which bug was fixed) .---------------->---------------. / | / | / | / | / V (<------ point of merge) ====*===================================================================> (main line of development) [Figure 2.3: A branch and then a merge. Time flows left to right.]
We'll now walk through the steps necessary to make this picture happen. Remember that it's not really time that's flowing from left to right in the diagram, but rather the revision history. The branch will not have been made at the time of the release, but is created later, rooted back at the release's revisions.
In our case, let's assume the files in the project have gone through
many revisions since they were tagged as
perhaps files have been added as well. When the bug report regarding
the old release comes in, the first thing we'll want to do is create a
branch rooted at the old release, which we conveniently tagged
One way to do this is to first check out a working copy based on that tag, then create the branch by re-tagging with the -b (branch) option:
floss$ cd .. floss$ ls myproj/ floss$ cvs -q checkout -d myproj_old_release -r Release-1999_05_01 myproj U myproj_old_release/README.txt U myproj_old_release/hello.c U myproj_old_release/a-subdir/whatever.c U myproj_old_release/a-subdir/subsubdir/fish.c U myproj_old_release/b-subdir/random.c floss$ ls myproj/ myproj_old_release/ floss$ cd myproj_old_release floss$ ls CVS/ README.txt a-subdir/ b-subdir/ hello.c floss$ cvs -q tag -b Release-1999_05_01-bugfixes T README.txt T hello.c T a-subdir/whatever.c T a-subdir/subsubdir/fish.c T b-subdir/random.c floss$
Take a good look at that last command. It may seem somewhat arbitrary
that tag is used to create branches, but there's actually a reason for
it: The tag name will serve as a label by which the branch can be
retrieved later. Branch tags do not look any different from non-branch
tags, and are subject to the same naming restrictions. Some people like
to always include the word branch in the tag name itself (for example,
Release-1999_05_01-bugfix-branch), so they can distinguish branch
tags from other kinds of tags. You may want to do this if you find
yourself often retrieving the wrong tag.
(And while we're at it, note the -d myproj_old_release option to checkout in the first CVS command. This tells checkout to put the working copy in a directory called myproj_old_release, so we won't confuse it with the current version in myproj. Be careful not to confuse this use of -d with the global option of the same name, or with the -d option to update.)
Of course, merely running the tag command does not switch this working copy over to the branch. Tagging never affects the working copy; it just records some extra information in the repository to allow you to retrieve that working copy's revisions later on (as a static piece of history or as a branch, as the case may be).
Retrieval can be done one of two ways (you're probably getting used to this motif by now!). You can check out a new working copy on the branch
floss$ pwd /home/whatever floss$ cvs co -d myproj_branch -r Release-1999_05_01-bugfixes myproj
or switch an existing working copy over to it:
floss$ pwd /home/whatever/myproj floss$ cvs update -r Release-1999_05_01-bugfixes
The end result is the same (well, the name of the new working copy's top-level directory may be different, but that's not important for CVS's purposes). If your current working copy has uncommitted changes, you'll probably want to use checkout instead of update to access the branch. Otherwise, CVS attempts to merge your changes into the working copy as it switches it over to the branch. In that case, you might get conflicts, and even if you didn't, you'd still have an impure branch. It won't truly reflect the state of the program as of the designated tag, because some files in the working copy will contain modifications made by you.
Anyway, let's assume that by one method or another you get a working copy on the desired branch:
floss$ cvs -q status hello.c =================================================================== File: hello.c Status: Up-to-date Working revision: 1.5 Tue Apr 20 06:12:56 1999 Repository revision: 1.5 /usr/local/cvs/myproj/hello.c,v Sticky Tag: Release-1999_05_01-bugfixes (branch: 1.5.2) Sticky Date: (none) Sticky Options: (none) floss$ cvs -q status b-subdir/random.c =================================================================== File: random.c Status: Up-to-date Working revision: 1.2 Mon Apr 19 06:35:27 1999 Repository revision: 1.2 /usr/local/cvs/myproj/b-subdir/random.c,v Sticky Tag: Release-1999_05_01-bugfixes (branch: 1.2.2) Sticky Date: (none) Sticky Options: (none) floss$
(The contents of those
Sticky Tag lines will be explained
shortly.) If you modify hello.c and random.c, and commit
floss$ cvs -q update M hello.c M b-subdir/random.c floss$ cvs ci -m "fixed old punctuation bugs" cvs commit: Examining . cvs commit: Examining a-subdir cvs commit: Examining a-subdir/subsubdir cvs commit: Examining b-subdir Checking in hello.c; /usr/local/cvs/myproj/hello.c,v <- hello.c new revision: 220.127.116.11; previous revision: 1.5 done Checking in b-subdir/random.c; /usr/local/cvs/myproj/b-subdir/random.c,v <- random.c new revision: 18.104.22.168; previous revision: 1.2 done floss$
you'll notice that there's something funny going on with the revision numbers:
floss$ cvs -q status hello.c b-subdir/random.c =================================================================== File: hello.c Status: Up-to-date Working revision: 22.214.171.124 Wed May 5 00:13:58 1999 Repository revision: 126.96.36.199 /usr/local/cvs/myproj/hello.c,v Sticky Tag: Release-1999_05_01-bugfixes (branch: 1.5.2) Sticky Date: (none) Sticky Options: (none) =================================================================== File: random.c Status: Up-to-date Working revision: 188.8.131.52 Wed May 5 00:14:25 1999 Repository revision: 184.108.40.206 /usr/local/cvs/myproj/b-subdir/random.c,v Sticky Tag: Release-1999_05_01-bugfixes (branch: 1.2.2) Sticky Date: (none) Sticky Options: (none) floss$
They now have four digits instead of two!
A closer look reveals that each file's revision number is just the
branch number (as shown on the
Sticky Tag line) plus an extra
digit on the end.
What you're seeing is a little bit of CVS's inner workings. Although
you almost always use a branch to mark a project-wide divergence, CVS
actually records the branch on a per-file basis. This project had five
files in it at the point of the branch, so five individual branches were
made, all with the same tag name:
Most people consider this per-file scheme a rather inelegant implementation on CVS's part. It's a bit of the old RCS legacy showing through-RCS didn't know how to group files into projects, and even though CVS does, it still uses code inherited from RCS to handle branches.
Ordinarily, you don't need to be too concerned with how CVS is keeping track of things internally, but in this case, it helps to understand the relationship between branch numbers and revision numbers. Let's look at the hello.c file; everything I'm about to say about hello.c applies to the other files in the branch (with revision/branch numbers adjusted accordingly).
The hello.c file was on revision 1.5 at the point where the branch was
rooted. When we created the branch, a new number was tacked onto the
end to make a branch number (CVS chooses the first unused even, nonzero
integer). Thus, the branch number in this case became
The branch number by itself is not a revision number, but it is the root
(that is, the prefix) of all the revision numbers for hello.c along this
However, when we ran that first CVS status in a branched working copy,
hello.c's revision number showed up as only
220.127.116.11 or something similar. This is because the initial
revision on a branch is always the same as the trunk revision of the
file, where the branch sprouts off. Therefore, CVS shows the trunk
revision number in status output, for as long as the file is the same on
both branch and trunk.
Once we had committed a new revision, hello.c was no longer the same on
both trunk and branch – the branch incarnation of the file had changed,
while the trunk remained the same. Accordingly, hello.c was assigned
its first branch revision number. We saw this in the status output
after the commit, where its revision number is clearly
The same story applies to the random.c file. Its revision number at the
time of branching was
1.2, so its first branch is
and the first new commit of random.c on that branch received the
There is no numeric relationship between
18.104.22.168 – no reason to think that they are part of the same
branch event, except that both files are tagged with
Release-1999_05_01-bugfixes, and the tag is attached to branch
1.2.2 in the respective files.
Therefore, the tag name is your only handle on the branch as a
project-wide entity. Although it is perfectly possible to move a file
to a branch by using the revision number directly
floss$ cvs update -r 22.214.171.124 hello.c U hello.c floss$
it is almost always ill-advised. You would be mixing the branch revision of one file with non-branch revisions of the others. Who knows what losses may result? It is better to use the branch tag to refer to the branch and do all files at once by not specifying any particular file. That way you don't have to know or care what the actual branch revision number is for any particular file.
It is also possible to have branches that sprout off other branches, to
any level of absurdity. For example, if a file has a revision number of
126.96.36.199.2.3, its revision history can be diagrammed like this:
1.1 | 1.2 | 1.3 | 1.4 | 1.5 / \ / \ / \ (1.5.2) (1.5.4) <--- (these are branch numbers) / \ 188.8.131.52 184.108.40.206 | | 220.127.116.11 18.104.22.168 | | (etc) (...) <--- (collapsed 34 revisions for brevity) | 22.214.171.124 / / (126.96.36.199.2) <--- (this is also a branch number) / / 188.8.131.52.2.1 | 184.108.40.206.2.2 | 220.127.116.11.2.3 [Figure 2.4: An unusually high degree of branching. Time flows downward.]
Admittedly, only the rarest circumstances make such a branching depth
necessary, but isn't it nice to know that CVS will go as far as you're
willing to take it? Nested branches are created the same way as any
other branch: Check out a working copy on branch
N, run cvs tag
-b branchname in it, and you'll create branch
N.M in the
N represents the appropriate branch revision
number in each file, such as
M represents the
next available branch at the end of that number, such as