CVS Notes

Ángel Ortega

CVS is a powerful tool, but it's easy to lose within its options, forget a step or mess with tags. This document says how I do some things with CVS.

Note: I no longer use CVS, but some ideas about source code control exposed here still apply.

I use a special feature of CVS that allows me to include some CVS modules inside of anothers; a typical use of this is when I work simultaneously on a library and on a project that uses it. This is done by tweaking the _modules_ file in the CVSROOT. For example, if I have a library called coollib and an application that uses it called coolapp, I add this to the modules file:

coolapp &coollib

So, everytime I checkout coolapp, coollib is also checked out in a subdirectory. This way I can make modifications to code files in both modules and work on them together. Changes to coollib are commited from inside of that directory.

When I use tags, I always include the name of the project in it. For example, if I want to tag version 0.4.3 of coolapp, I use the tag

coolapp-0_4_3

This way, the submodule coollib is also tagged, but as a snapshot of coolapp. I always separate the project name with a - and each version / subversion / release numbers with _ (remember dots cannot be used in tag names).

When using branches, I mark the tags with special words as 'root', 'branch' and 'merge'; see below.

Nothing special; I run

cvs import app angel start
cd ~/src
cvs co app

I always use 'angel' (myself) as the vendor tag, and 'start' as a special startup tag.

In my projects I use to have a file called VERSION contaning the current version of the program; anytime I think a new release is ready, I modify this file to match the new version and make a commit. I always include the text 'Version X.X.X RELEASED' in the commit log, and immediately after create a version tag. So, for the amazing new 0.5.4 version of coolapp I do:

cvs commit -m "Version 0.5.4 RELEASED"
cvs tag coolapp-0_5_4

Then the usual 'make dist' and publication follows.

Everytime I decide to create a branch, I think of a short string that would describe it to be inserted in tags; for example, if I want to start developing support for awesome 8 channel positron streams to coolapp, I can perfectly get '8chanpos' for the name of the branch. Then I do:

cvs tag coolapp-root-8chanpos
cvs tag -b coolapp-branch-8chanpos
cvs update -r coolapp-branch-8chanpos

The first step creates a tag in the trunk marking the root of the 8 channel positron modifications; the second creates the branch itself (note that includes 'branch' in the tag name), and the third changes the working copy to start working in the new code.

After some very hard work in complicated positron streaming, I will eventually decide the new code is ready to become part of the coolapp main code. So, I do

cvs update -A
cvs update -j coolapp-branch-8chanpos
cvs commit
cvs tag coolapp-merge-8chanpos

The first step changes the working copy with the head of the code, the second merges the changes, the third commits the changes (the merging is conveniently documented in the log), and a new tag is created, marking the merge point (note that the string 'merge' is wisely included as part of the tag).

New directories (and their contents!) created in a branch are not automatically added to the head when merging. To force it, just create them empty in the head and add it to CVS before the merging.

Sometimes, after releasing and tagging a new version, I realize last minute changes (as rebuilding the Changelog or so) were not done. When this happens, I do and commit the changes and move the version tag by using:

cvs tag -F coolapp-0_5_4

CVS renaming of files is cumbersome. To the repository point of view, you just can delete files or add new ones. So, the usual process is

mv oldfile.c newfile.c
cvs delete oldfile.c
cvs add newfile.c

This does work, but you lose all the change information you care to wrote in commit operations during years of hard development and that is probably not what you want. But there is a way; you must have direct access to the repository. First, go there, find the directory where your project is and do the following:

cp oldfile.c,v newfile.c,v

now go to your working directory and do a cvs update ; newfile.c will appear as a new file. Now you can cvs delete oldfile.c and commit.

There are some documents out there, written by famous Internet wisemen, explaining profusely the use of CVS for managing web applications. I'm not that verbose and wise, but I'm going to explain here how I do it.

I have that web application called coolwebapp, correctly written to separate code from output and all those things (yeah, you've heard that before), so site data and templates live in separate directories, and files under CVS control are only source code files (say, Perl modules and CGIs). There is a stable, production site, that is accesible to the rest of the world, and there is another, with bleeding-edge experimental code, that is accesible to developers only. Both servers happen to live in the same machine (but with different host names using Apache virtual hosts and HTTP access control), because I want both development and production systems to share their data (this is not necessary and can be dangerous, and it hasn't been always this way, but let's assume for now that data integrity is ensured). So, coolwebapp code is cheched out in the two following directories:

/var/www/lib/coolwebapp
/var/www/lib/coolwebapp-dev

As you can guess, the outside server uses code from the first directory (that holds the last stable version of the code), and the development server uses code from the second, that holds the trunk, and where potentially dangerous modifications to code are done. Every time the trunk is considered stable after many tests, a tag is created, and the production directory is checked out to match it. This way, the production server always hold known stable code, and it's non-modifyable as code has a 'sticky' tag (of course, source code files can be mistakingly edited, but it's easy to return to the correct code with one hit of 'cvs update').

I also maintain a tag, 'coolwebapp-stable', that is moved to match the latest stable version. This way, when some accidental modification is done to the stable directory, I don't have to remember what was the last damn stable version; I always have 'coolwebapp-stable' checked out there.

As you probably know, CVS has some nasty limitations and annoying behaviour that can drive anyone nuts; a new project, Subversion, has come to rescue us from our hell of unbearable suffering. It brings new interesting ideas and seems to work pretty well, but it has a feature I deeply dislike: it stores all managed files as binary DB files. There probably are good reasons for doing this, as performance as such, but I don't like the idea of losing precious source code because of corrupted files. With CVS, at least, I feel able to recover from mayhem by copying and pasting pieces of code. Anyway, this is just a personal view; Subversion deserves a deeper study.

Update: Subversion no longer depends on binary DBs to store its data; the fsfs format uses plain files. On September 2006 I moved all my data from CVS to Subversion (there are cool tools to do it) and I'm pretty happy with it. I probably talk about it on another document.