A bit more CVS: an introduction to the use of branches

Questions are invited at any time.

Before you begin: users of pico

You should note that CVS will occasionally drop you into a full-screen editor. By default, this is vi. If you use pico, then you need to let CVS know that that is your editor of choice.

CVS follows the standard convention of using the VISUAL environment variable to specify a full-screen editor. If you want to use pico, append the following line to your .profile file in your home directory:

VISUAL=pico export VISUAL

and the corresponding line to your .cshrc file:

setenv VISUAL pico

then restart your shell (logging in again will do this).


Notation

I've tried to keep this as concise and cook-booky as possible. I indicate command-line sessions using blocks of text as follows, with the > standing in for your prompt. Stuff that you type is indicated in bold. Responses are shown (where they're interesting).

> echo "hello world"
hello world

Contents


Recap of CVS operation

I'll not labour the point further in the notes, but remember that CVS is just one tool available to you as a developer. Successful use of any versioning system in a team setting relies on decent communication channels. All CVS operation should take place in parallel with development mailing list traffic.


Files, directories, history

As you recall, CVS might really be said to version file contents. A CVS module consists of a hierarchy of directories. Directories themselves are unversioned; when you cvs add a directory, it essentially appears "throughout time" in the CVS module.

Each file, however, has a recorded history. When it is added, and every time a modification is checked in, a change record is associated with the file. The information saved includes the date and time of the change, details of what exactly has changed (a diff or patch as it is known), and possibly a commit message supplied by the user.

Every file, then, has its own version number (which has nothing to do with the version of the software product the file is a part of). Even when changes to a set of files are committed together, there is no logical connection between versions of files comprising a change set. In fact, CVS has no real notion of a change set. CVS users are used to this; normally the lack of real change sets goes unnoticed.

CVS gives users the ability to view the state of the repository by checking out a working copy. The most common use of CVS involves keeping one's working copy completely up-to-date with the latest checked-in versions of all files (the so-called HEAD).

Alternatively, if we give CVS a date and time, we can move our working copy backwards in time, viewing an older point in the module's history.

> cvs up -D'Dec 14 20:00:00 GMT 2003'
cvs server: Updating .
cvs server: Updating build
cvs server: Updating htdocs
cvs server: Updating htdocs/jan

The working copy remembers that it holds a non-HEAD view of the repository, as can be seen using the cvs status command.

> cvs status README
===================================================================
File: README            Status: Up-to-date

   Working revision:    1.2
   Repository revision: 1.2     /internal/cvs/cvsrep/example/README,v
   Sticky Tag:          (none)
   Sticky Date:         2003.12.14.20.00.00
   Sticky Options:      (none)

We can bring the working copy back up-to-date with the HEAD as follows:

> cvs up -A
cvs server: Updating .
cvs server: Updating build
cvs server: Updating htdocs
cvs server: Updating htdocs/jan
> cvs status README
===================================================================
File: README            Status: Up-to-date

   Working revision:    1.2
   Repository revision: 1.2     /internal/cvs/cvsrep/example/README,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)

To understand CVS file versions, one should remember that the notion that a working copy is at a particular point or version is simply a convenient fiction. Actually all files are separately versioned. For conceptual convenience, we adopt the simpler view.


Use of tags to recapture the past

Rather than try to remember significant dates associated with a project's development, we can instead assign logical names to those points. This is done by tagging. By tagging the files (or a subset of the files) in a working copy, we associate the tag name with the corresponding version of each file.

> cvs tag -cR mytag
cvs server: Tagging .
T README
cvs server: Tagging build
T build/build.sh
cvs server: Tagging htdocs
T htdocs/index.html
cvs server: Tagging htdocs/jan
T htdocs/jan/index.html

The tagging operation itself does not alter the working copy- that still tracks the HEAD:

> cvs status README
===================================================================
File: README            Status: Up-to-date

   Working revision:    1.2
   Repository revision: 1.2     /internal/cvs/cvsrep/example/README,v
   Sticky Tag:          (none)
   Sticky Date:         (none)
   Sticky Options:      (none)

We can, however, ask CVS to produce us a working copy checked out to a particular tag. Rather than face a potential confusion of sticky tags in a single working copy, we utilise a little of the cheap, readily-available disk space to check out a second working copy. Technically, it should be possible to do this with a single command: cvs co -r mytag example - however, there's a bug present in many versions of cvs which can be worked around in two steps as shown below.

> cd ../..
> mkdir mytag
> cd mytag
> cvs co example
cvs server: Updating example
U example/README
cvs server: Updating example/build
U example/build/build.sh
cvs server: Updating example/htdocs
U example/htdocs/index.html
cvs server: Updating example/htdocs/jan
U example/htdocs/jan/index.html
> cvs up -r mytag example
cvs server: Updating example
cvs server: Updating example/build
cvs server: Updating example/htdocs
cvs server: Updating example/htdocs/jan
> cd example
> cvs status README
===================================================================
File: README            Status: Up-to-date

   Working revision:    1.2
   Repository revision: 1.2     /internal/cvs/cvsrep/example/README,v
   Sticky Tag:          mytag (revision: 1.2)
   Sticky Date:         (none)
   Sticky Options:      (none)

One might, for example, put down a tag to capture the state of play at the point you roll a release of your project.

Tags don't have to stay in one place once laid down. It's possible to move a tag to a different version of a file, or to remove it completely from a file. If you update a working copy checked out to a tag, CVS will recover the appropriate versions of any files. cvs tag -H for help on this.


Laying out your working copies

As seen above, it's perfectly possible to have multiple working copies checked out of CVS at the same time. For development projects where you're juggling several things at once, this is not only possible, it's actually pretty prudent, so I'll suggest a typical way to do this that you may want to follow.

Under a parent directory, create a head/ directory. Into this, check out a working copy that tracks the HEAD. Normally, you will do most work in this working copy.

Parallel to the head/ directory, you can create directories named after significant tags (for example, the tags that capture your previous releases and maybe TAG_LATEST_RELEASE).


CVS with branching: forking history


CVS branch: definition

Thus far we've pictured all the versions of the various files of a project stretching backwards through time to the start of the project. Considering the project as a whole rather than as individual files, it has a single linear history. We may have marked points on that history using tags for easy recovery, but at any point in time there's only one corresponding version of each file in the project.

CVS permits a more complex arrangement than this, however. It lets us fork or branch the development of a project. Instead of a single line of history on the HEAD, we have HEAD and another branch. From the point of the split, changes to each branch are tracked separately. We can use CVS to collect up changes from one branch and apply them to another.

CVS isn't limited to two branches. Additional branches can be forked off the mainline (HEAD) - or, in fact, off subsidiary branches. Care is needed, however: although it is technically possible to recover a complex map of branches using cvs, it's easy to get lost. (Sometimes a map on a whiteboard can help!) Before getting intoxicated with the ability to create branches, however, it's worthwhile to figure out why you want them.


When branches are appropriate

There are at least two scenarios where branches may be appropriate. In the first case, a branch may have quite a long lifetime. In the latter, a branch comes into existence briefly and is abandoned once it has served its purpose.

The obvious use of a long-term branch is to track software releases. The division point where a new branch is forked can be thought of like a tag. Every time a software project goes through a release cycle, a branch is created to track that release.

This works just like using tags to track software releases, except that we can continue to commit changes to a branch. Normally most development work will continue on the HEAD; however, bugfixes can also be applied to the release branch (or branches) too. We can then re-roll a maintenance release of the software rather than requiring a whole-version update.

CVS can assist in applying a patch (in this case, a bugfix) from one branch to another, as we'll see, and the results are surprisingly useful. However, it is not a panacea: patches may not apply cleanly without some human intervention, and the usual pre-release testing cycle should be completed.

As an alternative motivation, consider the use of CVS amongst a team of developers. As more people contribute to a module, the level of discipline required to keep the wheels rolling smoothly goes up.

As an absolute minimum requirement, the rule that your commits should not break the build is often adopted. That is, after a set of commits, a working copy resulting from checking out a clean copy of the repo should build without error. Build here may be extended to include passing a series of unit or regression tests. It is possible to get the CVS repo to collaborate in this regard; that is, to reject commits that fail some automated process. However, such draconian measures are probably not warranted in many situations - in addition, the lack of atomic change sets in CVS means that the guarantees that such a system makes are not always as strong as you might think.

As the constraints on a team-friendly commit go up, the granularity of commits will correspondingly tend to come down. Developers tend to keep more local, transient (and most importantly, unsaved!) state in their working copies. They cannot share this work in progress until it is in a state where it can be checked in.

In such situations, a short-lived development branch may be appropriate. The developer creates a branch to hold their work on a particular task. Most development continues on the HEAD; however, the developer can make fine-grained commits to his branch with impunity. He is free to check in changes to that branch that cause build instabilities; these will be localised to that branch.

If two or more developers are interested in developing the same piece of functionality, they can both check out working copies of the branch to collaborate in (although again, the level of discipline required may go up), or simply to track the progress of the developer in question.

Once the development on the branch is complete, one of the developers has the task of merging the work into the HEAD branch. This involves merging the accumulated changes from the side-branch to the a working copy on the main branch, resolving any conflicts, ensuring that the resulting copy meets commit constraints (ie, fixing any last-minute problems), and committing the code.

We'll look at the mechanics of this now.


Working with branches


Getting the example repository

CVS is organised into modules. Each cvs repository consists of one or more modules. You can think of a module as a file hierarchy with a history.

In day-to-day operation, you do your work in a local copy of a module called your working copy. Getting an initial copy of a module is called checking out the module.

In order to check out a module, you need to tell CVS where the repository is located and the name of the module. The CVS repository location is specified in your CVSROOT environment variable (which you configured as a part of the prep for this session).

We'll be using the module from the previous session for this work. You can check it out as follows:

> mkdir -p workspace/head
> cd workspace/head
> cvs co example
cvs server: Updating example
U example/README
cvs server: Updating example/build
U example/build/build.sh
cvs server: Updating example/htdocs
U example/htdocs/index.html
cvs server: Updating example/htdocs/jan
U example/htdocs/jan/index.html

Now is the time to yell if this doesn't work.

You should now have a small directory hierarchy starting with example in your current directory. You can cd into the directory and have a look around.

You'll notice that as well as normal files and subdirectories, each directory has a subdirectory called CVS. The CVS directory contains a number of text files that cvs uses to do its book-keeping. You will not normally need to edit these files.


Creating a branch

For this step, everyone is going to create their own branch. This is simply so that everyone can see the mechanics of branch operation. In "real world use" branch operation is generally not so profligate.

It is important to adopt a naming convention for branches. It's less important exactly what that convention is, as long as all the developers on your team can stick to it. For today, you'll create a branch called B_JOHN (or whatever your name is).

> pwd
/home/john/workspace/head/example
> cvs tag -cRb B_JOHN
cvs server: Tagging .
T README
cvs server: Tagging build
T build/build.sh
cvs server: Tagging htdocs
T htdocs/index.html
cvs server: Tagging htdocs/jan
T htdocs/jan/index.html

The -c option to cvs tag ensures that there are no pending changes in your working copy before doing the tag. You pretty much always want to use this option, so you may want to add the following line to your ~/.cvsrc:

tag -c

Now, create room for a working copy checked out to this branch. Do this outside any CVS-controlled directories.

> cd ../..
> pwd
/home/john/workspace
> mkdir B_JOHN
> cd B_JOHN
> cvs co example
cvs server: Updating example
U example/README
cvs server: Updating example/build
U example/build/build.sh
cvs server: Updating example/htdocs
U example/htdocs/index.html
cvs server: Updating example/htdocs/jan
U example/htdocs/jan/index.html
> cvs up -r B_JOHN example
cvs server: Updating example
cvs server: Updating example/build
cvs server: Updating example/htdocs
cvs server: Updating example/htdocs/jan
> cd example

Getting a copy of someone else's branch

The process to check out a copy of the branch named after the person sitting next to you is just the same.

> cd ../..
> pwd
/home/john/workspace
> mkdir B_JANE
> cd B_JANE
> cvs co example
cvs server: Updating example
U example/README
cvs server: Updating example/build
U example/build/build.sh
cvs server: Updating example/htdocs
U example/htdocs/index.html
cvs server: Updating example/htdocs/jan
U example/htdocs/jan/index.html
> cvs up -r B_JANE example
cvs server: Updating example
cvs server: Updating example/build
cvs server: Updating example/htdocs
cvs server: Updating example/htdocs/jan
> cd example

This is all very well so far, but currently the HEAD and all child branches are identical. Time to make some changes.

Before making any changes, however, you need to understand how the branch naming works. Just as HEAD is the conventional name that tracks the latest copy of all files on the main line, B_JOHN will track the latest copy of all files on that branch. When we come to merging our changes into the main tree, we'll need to be able to refer to the start of the branch too. We do that by laying down a tag on all the files in the branch before we make any changes. This tag we'll call T_START_JOHN. This is such a common operation that you should really think of it as part and parcel of creating a branch.

> cd ../../B_JOHN/example
> cvs tag -cR T_START_JOHN
cvs server: Tagging .
T README
cvs server: Tagging build
T build/build.sh
cvs server: Tagging htdocs
T htdocs/index.html
cvs server: Tagging htdocs/jan
T htdocs/jan/index.html

Change into the example/htdocs/ directory in your branch. Create a new directory named after you (if there's already one there from the previous session, create john2). Add the directory to CVS.

> cd htdocs
> mkdir john
> cvs add john
Directory /usr/local/cvsroot/example/htdocs/john added to the repository
--> Using per-directory sticky tag `B_JOHN'

The response from CVS is it letting us know that any files created in the new subdirectory will automatically come into existence on the B_JOHN branch.

Now, create an index.html file in your new directory. You can use the one in example/htdocs/jan/ as the basis for this. Make some changes to it and check it in. Remember to update before a checkin. For larger commits, this gives you the opportunity to view and resolve any merge problems before you commit. This is just a good habit to get into with CVS.

> cp jan/index.html john/index.html
> vi john/index.html
...
> cvs add john/index.html
cvs server: scheduling file `john/index.html' for addition on branch `B_JOHN'
cvs server: use 'cvs commit' to add this file permanently
> cvs up john
cvs server: Updating john
A john/index.html
> cvs ci -m "All about me" john
cvs commit: Examining john
RCS file: /usr/local/cvsroot/example/htdocs/john/Attic/index.html,v
done
Checking in john/index.html;
/usr/local/cvsroot/example/htdocs/john/Attic/index.html,v  <--  index.html
new revision: 1.1.2.1; previous revision: 1.1
done

We can finish our work on this branch by modifying the example/htdocs/index.html file to include a pointer to our new directory.

> pwd
/home/john/workspace/B_JOHN/example/htdocs
> vi index.html
[add the following line to the list of pages]
<li> <a href="john/">john's pages</a> <li>
> cvs up index.html
M index.html
> cvs ci -m "Added pointer to my page" index.html
Checking in index.html;
/usr/local/cvsroot/example/htdocs/index.html,v  <--  index.html
new revision: 1.1.4.1; previous revision: 1.1
done

Now everyone can update their working copies and have a look at the various changes that have been made to each one. Currently the HEAD branch is just like we left it.

> cd ../../..
> pwd
/home/john/workspace
> cvs up -d head/example B_JOHN/example B_JANE/example
cvs server: Updating head/example
cvs server: Updating head/example/build
cvs server: Updating head/example/htdocs
cvs server: Updating head/example/htdocs/jan
cvs server: Updating head/example/htdocs/john
cvs server: Updating head/example/htdocs/jane
cvs server: Updating B_JOHN/example
cvs server: Updating B_JOHN/example/build
cvs server: Updating B_JOHN/example/htdocs
cvs server: Updating B_JOHN/example/htdocs/jan
cvs server: Updating B_JOHN/example/htdocs/john
cvs server: Updating B_JOHN/example/htdocs/jane
cvs server: Updating B_JANE/example
cvs server: Updating B_JANE/example/build
cvs server: Updating B_JANE/example/htdocs
cvs server: Updating B_JANE/example/htdocs/jan
cvs server: Updating B_JANE/example/htdocs/john
cvs server: Updating B_JANE/example/htdocs/jane
U B_JANE/example/htdocs/jane/index.html

Ok, so things aren't quite like we left them. Remember that CVS doesn't really version directories; once they exist, they exist throughout time and across all branches. By giving the -d option, we asked CVS to pull down from the repo any directories that we don't currently have in our working copies. It has consequently created the directories that have been added on each branch.

The closest we can get to avoiding this is to get CVS to automatically prune empty directories from a working copy. You can do this automatically by adding the following line to your ~/.cvsrc:

update -dP

Directory handling is one of the less satisfactory aspects of CVS operation. Generally this somewhat odd behaviour is worth bearing in mind - for example, if you want to create an empty directory in a module that can be used by build scripts to compile code into, you may need to get your build script to ensure that the directory exists before attempting to target it.


Finishing with a branch

When a branch is finished with, we can simply abandon it and continue our development on another branch (or HEAD). The abandoned branch remains in our repository (and is available for perusal or recovery at a later date).

No special action is required to retire a branch; simply stop committing to it. You can remove the working copy corresponding to the branch from your workspace (but check that any pending commits have been made before you do so).


Browsing branches with CVSWeb

We'll take a quick breather and detour through the CVSWeb installation http://cvs.ilrt.org/cvsweb/ to look at those branches.


Merging between branches


The use of update to merge changes

Now that we've successfully made our changes on our branch (you might try them out using the build script), we'd like to merge those changes back into the main development line.

We'll go around the room now and do this in turn.

The way we perform a merge is as follows. We select a start point and an end point. We can do this by specifying dates, explicit versions, or more conveniently, by giving the names of tags that mark the points we're interested in. For each file, CVS slurps up the changes between the start point and the end point and applies them to the working copy.

That explains why we laid down a tag before we started working in our branches - what about the end point? Well, the name of the branch itself marks the latest checked-in copy of all files on that branch.

Managing a merge is straightforward if you go about it in a systematic way. To begin with, get yourself a clean, up-to-date working copy of the target branch (HEAD in this case).

> cd head/example
> cvs up
... output elided ...

Now, we apply the changes that we've made to our working branch. We do this by specifying the tags that name the beginning and end of our changes.

> cvs up -j T_START_JOHN -j B_JOHN
cvs server: Updating .
cvs server: Updating build
cvs server: Updating htdocs
RCS file: /usr/local/cvsroot/example/htdocs/index.html,v
retrieving revision 1.1
retrieving revision 1.1.4.1
Merging differences between 1.1 and 1.1.4.1 into index.html
cvs server: Updating htdocs/jan
cvs server: Updating htdocs/john
U htdocs/john/index.html

We can confirm that the first merge has completed successfully by using cvs up to look for merge problems; we might also use the build script to deploy directly from the working copy in order to check the changes we're about to commit.

> cvs up
cvs server: Updating .
cvs server: Updating build
cvs server: Updating htdocs
M htdocs/index.html
cvs server: Updating htdocs/jan
cvs server: Updating htdocs/john
A htdocs/john/index.html

Having seen that everything looks ok, we put back our merge.

> cvs ci -m "Merge from B_JOHN"
cvs commit: Examining .
cvs commit: Examining build
cvs commit: Examining htdocs
cvs commit: Examining htdocs/jan
cvs commit: Examining htdocs/john
Checking in htdocs/index.html;
/usr/local/cvsroot/example/htdocs/index.html,v  <--  index.html
new revision: 1.2; previous revision: 1.1
done
Checking in htdocs/john/index.html;
/usr/local/cvsroot/example/htdocs/john/index.html,v  <--  index.html
new revision: 1.2; previous revision: 1.1
done

We'll continue around the room. As we go, we may see conflicts arising in the example/htdocs/index.html file:

... update as before ...
> cvs up -j T_START_JANE -j B_JANE
...
cvs server: Updating htdocs
RCS file: /internal/cvs/cvsrep/example/htdocs/index.html,v
retrieving revision 1.1
retrieving revision 1.1.4.1
Merging differences between 1.1 and 1.1.4.1 into index.html
rcsmerge: warning: conflicts during merge
...
> cvs up
cvs server: Updating .
cvs server: Updating build
cvs server: Updating htdocs
C htdocs/index.html
cvs server: Updating htdocs/jan
cvs server: Updating htdocs/john
cvs server: Updating htdocs/jane
A htdocs/jane/index.html

Here, the C indicates a conflict arising during the merge. We edit the file to remove the conflict, just as with any other merge. Once cvs up reports that no conflicts exist, we can commit as before.


The double-merge problem

There is one final common problem that can occur when working with a branch. We may decide, rather than abandoning a branch, to carry on additional development work. Here is an example:

> cd ../../B_JOHN/example
> pwd
/home/john/workspace/B_JOHN/example
> vi htdocs/index.html
...
> cvs diff -u htdocs/index.html
Index: htdocs/index.html
===================================================================
RCS file: /usr/local/cvsroot/example/htdocs/index.html,v
retrieving revision 1.1.4.3
diff -u -u -r1.1.4.3 index.html
--- htdocs/index.html   15 Dec 2003 16:03:05 -0000      1.1.4.3
+++ htdocs/index.html   15 Dec 2003 16:05:18 -0000
@@ -10,7 +10,7 @@

 <ul>
 <li> <a href="jan/">jan's pages</a> </li>
-<li> <a href="john/">john's pages</a> </li>
+<li> <a href="john/">john's other pages</a> </li>
 </ul>

 </body>
> cvs up
cvs server: Updating .
cvs server: Updating build
cvs server: Updating htdocs
M htdocs/index.html
cvs server: Updating htdocs/jan
cvs server: Updating htdocs/john
> cvs ci -m "Fixed text pointing at my index page"
cvs commit: Examining .
cvs commit: Examining build
cvs commit: Examining htdocs
cvs commit: Examining htdocs/jan
cvs commit: Examining htdocs/john
Checking in htdocs/index.html;
/usr/local/cvsroot/example/htdocs/index.html,v  <--  index.html
new revision: 1.1.4.4; previous revision: 1.1.4.3
done

Some time later, we may come to merge our updates into the main branch. Here's how not to do it:

> cd ../../head/example
> cvs up
cvs server: Updating .
cvs server: Updating build
cvs server: Updating htdocs
cvs server: Updating htdocs/jan
cvs server: Updating htdocs/john
> cvs up -j T_START_JOHN -j B_JOHN
cvs server: Updating .
cvs server: Updating build
cvs server: Updating htdocs
RCS file: /usr/local/cvsroot/example/htdocs/index.html,v
retrieving revision 1.1
retrieving revision 1.1.4.4
Merging differences between 1.1 and 1.1.4.4 into index.html
rcsmerge: warning: conflicts during merge
cvs server: Updating htdocs/jan
cvs server: Updating htdocs/john
cvs server: file htdocs/john/index.html exists, but has been added in revision B_JOHN

Urk! What went wrong?

> cat htdocs/index.html
<html>
<head>
<title> An introduction to CVS </title>
</head>
<body>

<h1> These documents are under source-code control </h1>

<p> People on the course were: jan </p>

<ul>
<li> <a href="jan/">jan's pages</a> </li>
<<<<<<< index.html
<li> <a href="john/">john's pages</a> </li>
=======
<li> <a href="john/">john's other pages</a> </li>
>>>>>>> 1.1.4.4
</ul>

</body>
</html>

The problem is that CVS has already merged some of these changes. We originally asked it to apply the changes between T_START_JOHN and B_JOHN. After we made some additional changes, we again asked CVS to apply changes between the original start tag, T_START_JOHN and our latest B_JOHN. What we'd really have liked was for CVS to remember that we'd already done one merge, and only apply the latest changes. Unfortunately, CVS isn't that smart, so we have to be smart for it.

The way to avoid the problem is this: once we have successfully merged changes from a branch into the HEAD, we lay down another tag on the branch to act as a "we got this far" marker. It's possible to use a single tag for this purpose, or to create a new tag every time we perform a merge.

... successful merge and ci to head ...
> cd ../../B_JOHN/example
> cvs tag -cR T_JOHN_MERGE_20031215
cvs server: Tagging .
T README
cvs server: Tagging build
T build/build.sh
cvs server: Tagging htdocs
T htdocs/index.html
cvs server: Tagging htdocs/jan
T htdocs/jan/index.html
cvs server: Tagging htdocs/john
T htdocs/john/index.html

Now we can perform a second merge from this branch by specifying the endpoints appropriately: cvs up -j T_JOHN_MERGE_20031215 -j B_JOHN in this case. To get this to work with a minimum of fuss you need to lay down the tag before doing any other work on the branch! Consequently, it is probably worthwhile getting into the habit of tagging a branch after a successful merge, even if you do not immediately plan to continue work on that branch.

Finally, you might be asking yourself how you can keep track of the various tags and branches in use. It's a good question. You can make your task easier by adopting a sensible naming strategy. From the command line, you can call up a list of the tags on files as follows:

> cvs log -t README
... output elided ...
symbolic names:
        T_JOHN_MERGE_20031215: 1.1.4.4
        T_START_JOHN: 1.1
        B_JAN: 1.1.0.4
        B_JOHN: 1.1.0.2
        mytag: 1.1
        T_INITIAL: 1.1
... output elided ...

You'll need to specify a filename because CVS doesn't keep this kind of information about directories.


Planning your merge strategy

A good naming strategy for tags and branches is pretty much vital, as we've seen. Additionally, it's worth thinking a bit before branching development. There is additional work involved in managing merges and a bit of bookkeeping - so sometimes it may be better to do all development work on the HEAD. Sometimes, however, you simply can't do without a branch.

When that's the case, particularly for short-lived branches, it's useful to plan out your work before you begin. It helps if you can clarify what work you will be doing on the branch (rather than letting all development that you do slop over into it) and to figure out what your milestones are; also, to try to identify the point where your work on the branch will be complete.

Although it's not always possible to look into the future with 100% accuracy, a little foresight can be worthwhile. Drop a line to your technical development list outlining what you'll be doing the branch for. Include the tag names you'll be doing. Then stick to the plan.

The email doesn't need to be hugely expansive, but my own preference is to go into sufficient detail that others will be able to follow the work. If nothing else, this encourages the airing of ideas on the -dev list. Here's one I did earlier, with the names changed to protect the guilty:

From: Jan Grant <Jan.Grant _at_ bristol.ac.uk>
Subject: UP plan

Here's the plan. First, if anyone has any pending checkings, please get
let me know and get the repo into a buildable state; I need to tag and
branch - which I'll do at 11.30 this morning (it should take no more
than 10 minutes). This is easier to do if I'm the only one with my hands
in the repo at the time.

I'll email once before I do the tag and branch, then once when it's
complete.

I propose a three-step integration of the new code:

1. I'm going to factor out interface and implementation for the UP
package. That is, the existing UP code will go into an implementation
sub-package and all that will be left in the main package are minimal
interfaces to provide the required functionality.

This work is considered complete (milestone 1) when:

- everything builds
- existing test cases operate as before
- the system passes integration testing (in this case, that means I'll
be asking Susan to make sure that the portal looks like it works while
exercising the bits of xsearch that need the UP)

Once that work is done, I'll merge it to the main branch as a first step
and tag the branch at that stage.

2. Bring the updated code across as an implementation of the same
existing UP interface. Basically, that means I'll hammer out convenience
methods until it compiles against the interfaces it claims to do.

This work is complete (milestone 2) when:

- the new profile implementation compiles as an implementation of the
existing UP interface;
- all the appropriate test cases pass (the Command stuff is hidden in
the new implementation)
- new test cases (as appropriate) also work, including the
"testTickleBug" case.

3. Integrate the new code (milestone 3). This means:

- update the main configuration files to use the new implementation
- update the build process to add new targets to install the new schema
in the database
- test by hand again, including persistence layer and the alerting code.

When this is complete I'll merge it to the main branch and stop working
on the branch.


Tags will be as follows:

the branch is BRANCH_NEW_UP
on the branch, post branch: TAG_POST_BRANCH_NEW_UP

Milestone 1, on the branch: TAG_NEW_UP_M1
Main code, after merge1: TAG_POST_MERGE_NEW_UP_M1

Milestone 2, on the branch: TAG_NEW_UP_M2

Milestone 3, on the branch: TAG_NEW_UP_M3
Main code, after merge of M2 and M3: TAG_POST_MERGE_NEW_UP_M3

Finally


If things go wrong

Don't panic.

There are a few kinds of things that generally go wrong: you commit code to the wrong branch; you get into a mess with a merge; you perform a double-merge.

Committing code to the wrong branch is easy to do. You back it out just like you would any other CVS change; although obviously it's better to not make the commit in the first place. Things that can help are:

Sometimes a merge just goes plain wrong. On those occasions it helps to take a deep breath and start again, merging pieces of code a few at a time and testing as you go.

If you begin with a pristine working copy of the target branch, then there is less room for grief. That is, ensure that you have no pending changes in your target working copy (or even check out a fresh working copy to manage the merge in). That way, there is no loss of uncommitted changes if you decide to just remove the working copy and try again.

Double-merges can be a bit of a pain to deal with. The best approach is to form the habit of always tagging the source branch once you have committed a successful merge. If you are planning on bidirectional merges (from a branch to mainline and from mainline to branch - as you might if you are managing a maintenance release) then don't be afraid to tag everything at every step of the way.

Pick a naming strategy for your tags and branches and stick to it.


Further reading on branch management strategies

As we've seen, cvs up is agnostic about what file versions it uses to create the patch it applies. Providing you're careful to avoid double merges, you can take patches between branches pretty much ad hoc.

It's therefore useful to consider how you're going to manage branches as you plan out your project's use of CVS. There are a number of common patterns detailed in the CVS book (http://cvsbook.red-bean.com/) which you may be interested in looking into more closely.

The two common scenarios I have already outlined, however, are generally sufficient for the majority of projects: you can go a long way with a simple branching strategy and a clear head!


Congratulations

Well done. You've just earned another gold star - you need feel no shame when conversing with other developers should the topic of conversation come around to source-code control. Hold your head up high!