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.
Modules
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.
Tags
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.
Importing a new project
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.
Releasing new versions
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.
Starting branches
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.
Merging branches
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.
Moving a tag
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
Renaming a source file without losing change information
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.
Using CVS for maintaining web applications
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.
Digression: Subversion
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.