The University of Queensland Homepage
School of ITEE ITEE Main Website

 Using CVS

Concurrent Version System

From the cvs(1) man page:
CVS is a front end to the rcs(1) revision control system which extends the notion of revision control from a collection of files in a single directory to a hierarchical collection of directories consisting of revision controlled files. (Actually, it doesn't use rcs any more.)

There are a number of things about the CVS system that are not obvious from the man pages. One of the first that you come across is that if a file is imported as /foo/bar/hello.c, when you use "cvs checkout" to get a local copy, it will be copied as foo/bar/hello.c relative to the current directory. So most of the time, if the directory structure of the repository is reasonable, you can checkout from your home directory, or a directory just below it. (But see the administrative files for information on the module file, which may change this).

Creating the initial modules file

Even the excellent online help for CVS is somewhat unclear about how you should create the initial modules file. Here is a step by step guide:
cd ~                   ; Some suitable writable directory
cvs checkout CVSROOT   ; This should create ~/CVSROOT
cd CVSROOT
vi modules
{Add lines similar to these:
modules		CVSROOT modules
CVSROOT		CVSROOT
elfDecoder	loaders/elf
}
cvs add modules        ; So cvs knows about this file
cvs commit             ; To actually add the file
You will be prompted for a log file entry.

Adding a module

Adding a module to CVS is similar. Pick a convenient directory, e.g. your home directory. Checkout the pre-exising module named "modules":
cd ~
cvs checkout modules
cd modules
You can then add an entry to the modules file, in the format "modulename path". For example:
elfDecoder        loaders/elfV0
Then commit the changes:
cvs commit
When this is done, the modules directory can simply be removed, or left there for future additions.

Other sources of information

Current Projects

The number of projects booked into CVS is continally expanding. We attempt to keep up to date the file bintrans/README.CVS. Consult this file for (hopefully) the latest modules and tag names.

For example, to check out a local copy of SparcVersion1_2 of the elfSparc file decoder into the ~/loaders/elfSparc directory and make it:

cd ~/loaders
cvs checkout -r SparcVersion1_2 elfDecoder
cd elfDecoder
make
To check it out into directory ~/loaders/foo inistead, use
cvs checkout -r SparcVersion1_2 -d foo elfDecoder
For our purposes, these are the commonly used commands (in addition to checkout above):
  • import. Reads a new project into CVS.
  • update. Merges other peoples changes with your local copies.
    • update -A. Merge with main branch; use after the import command.
  • commit. Commits (saves) your changes in the repository.
  • remove. Indicates that a file is no longer needed (it is put in the attic, so that if earlier tagged versions require it, it can still be checked out). Any deleted files will have to be removed before you can update, which is needed before you can commit.
  • tag. Tags a set of files with a version name. For example to tag the current working files, first commit them, then
    cvs tag MyNewTag elfDecoder

Adding a New Project to CVS

One of the most baffling things to do is to start an existing project with CVS. The import command appears to be designed for this purpose, but the writers of CVS seem to have it in mind that this command would be used only for Vendor code, and hence all files are placed on a branch. This means that all software revisions will have four digits, e.g. the first revision will be 1.1.1.1. However, it is easy to "move the files to the main branch" using the cvs update -A command. This will have no immediate effect, but any subsequent changes will give sensible revision numbers (e.g. 1.2) to the changed files.

Note when using the import command: The third last argument (repository) is a directory (relative to $CVSROOT), not a module name (as I expected). You don't need a module to check in files; in fact, you don't need a module to check them out either; it just makes it easier (see adding a module). Also, by using the -kb option, you can specify all files as binary, but not selected files. The workaround if importing a mixture of binary and non binary files is to use the cvs admin -kkv <file(s)> command to change a file (or a wildcarded group of files) to non binary. However, you still need to manually edit the CVS/Entries file to remove the "-kb" strings. (This may make more sense when you look inside the Entries file, and realise that the cvs admin command works only on the repository).

In case you don't like the import command, there is an alternative. The only special files to CVS are the files in the CVS directory in your working directory, the repository itself, and a handful of special files in $CVSROOT/CVSROOT. The latter are created during installation, and should only be changed by adding to the modules file (see Adding a module for details).

The CVS directory in your working directory is set up by the checkout command; there should never be need to change these directly.

This leaves the respository. The files of the repository are in $CVSROOT; for example, if module elfDecoder specifies a path of loaders/elf, then the repository is the directory $CVSROOT/loaders/elf. The files in the repository are all rcs(1) format files, usually with the name of the original file and ",v" appended.

So the easiest way to set up a group of files (if you don't like import is as follows; the broad strategy is to copy the source files directly into the repository and use the ci command (RCS Check In) to convert the files into RCS format.

1) Copy the files to the repository, using cp -r or tar or any means.

2) Change to the respository. Remove any binary files, such as *.o and executables, etc.

For each directory:
3) Make a list of files with ls > files. Manually remove the names of subdirectories, and the file files.

4) Edit the file and prepend "ci -tdescript " to the start of each line. In vi, use ":1,$s/^/ci -tdescript /". Note no space after the "-t".

5) Create a file descript with the description (1 line) of what the project is about.

6) Make files executable (chmod +x files), and execute it.

7) Remove the files descript and files. Repeat for subdirectories.

To test that it has worked, create a module for the project, and check it out into a test directory (say testdir):

cvs checkout -d testdir ModuleName
cd testdir
You can omit the -d testdir if ModuleName is a suitable directory name.

More likely, you want to use the original directory to make changes to the project. If so, go to the parent, and check out the files into the original directory. It takes courage to do this, but remember that CVS won't let you overwrite a file that has changed, so it's quite safe. If a file has changed between when you created the repository and are ready to check it out, then CVS will issue a message like "move away dir/file; it is in the way". Rename the file, and check out the files again. Use diff (ordinary diff; cvs does not know about your renamed file). Fix the file to your satisfaction, (probably my moving the renamed file back to its original name), and commit. Remake the project (all the sources now have a later time) to make sure all is well.
Done!

When File Names Change

Sometimes you want to book in a project with a bit of history, where some of the names change (save verbose.c to verbose.cc), from one tag to the next. To accomplish this, first enter the files for the first version (tag) as outlined above, and use a command like
cvs tag Version1
to make a tag for the first version. Test.

Once this is working, you can start deleting files that are no longer needed in the next version, and updating files that have just changed contents. For each file that is deleted, remove it from the repository as well (don't worry: it will still be available (via the attic) for when you checkout version one):

cvs remove file1.c file2.c
(You can't remove *.c, because the files have to be deleted first).

For each new file added to the project, use the add command:

cvs add *.cc
But note: using wildcards with add may not always have the same effect as adding the files separately (if there are error messages).

When all is well, test, then tag the current files with the next version's tag.

When CVS repository paths change

I received a nasty shock today when I tried to check out an old version of a module. This was the error message:
cvs checkout: existing repository /net/olympic/u6/olympic/CVSROOT/loaders/Loader
does not match /net/olympic/u6/olympic/CVSROOT/loaders/elf
cvs checkout: ignoring module loaders/elf
No files were checked out. It seems that $CVSROOT/loaders/elf was called $CVSROOT/loaders/Loader at one stage. I just renamed the directory, did the checkout, and restored the directory name, and all was fine. Just a minor gotcha, but it can save a lot of grief if you know what to expect.

Reversing a commit

Sometimes you just want to reverse a commit (ci). Rather than attempt to delete the original version, it's best to get a copy of the appropriate old version of the file (use cvs status and cvs diff -rprev-rev to do this). Don't use
cvs update -r prev-rev filename
as this will make the revision sticky, so you can't book it in again. Instead, use
cvs update -p -r prev-rev filename > filename.
This gets a non sticky old version to stdout, and you redirect that over the top of the file. You can then check that all is well with cvs diff, then just commit the file again. Be sure to add decent comments, so you can figure out what the hell happened in the future with cvs log.

Using Branches

Branches are useful things, but it's not always obvious how to use them. There seems to be two main uses for them: when starting a "side project", and also when a "side project" has developed, but hasn't been booked in yet.

The first case is the easiest, and is covered in the documentation. Just create a branch tag with cvs tag -b branchname, then check out a new copy of that source with cvs co -r branchname.

In the second case, you have a set of files that has been extensively modified, and occasionally updated, but never checked in, and you don't want the changes to be reflected in the main branch. In this case, you begin as above by creating a branch tag:

cvs tag -b branchname
Now you want to check in the modified files, to the branch. Use the -r option, as follows:
cvs commit -r branchname
Now the modified files will have a sticky tag. They can be modified and checked in with the usual cvs commit command, and they won't affect files on the main branch. Improvements made on the main branch can be incorporated by merging with the main branch, without sticking to the main branch, but first you need to create a tag on the main branch:
cd path to files on main branch
cvs tag maintag
cd path to files on side branch
cvs update -j maintag
This will find the changes between the latest main branch and the point where the side branch originated, and applies those changes to the current branch files. This is ideal where you have a subproject with much of the code common with the main branch, and yet significant local modifications.

Or you can just do this:

cvs update -j HEAD -d -A
There will be many conflicts, mostly just due to revision numbers. The -d is to get new directories created on the main trunk, and the -A is to remove the stickyness (assumes you are ready to book back into the main trunk). Delaying the -A until later means a second set of merges.

I had some problems checking in the whole project in one hit; I had to check in each subdirectory separately, and then check in the root directory with cvs commit -l -r branchname (to only look at the local files, not subdirectories).

Files can be added and removed as usual (e.g. cvs add filename), without having to specify the -r branchname.

Generated Files

If you have a large file that is generated from another file, and that generated file has something like "#line" statements in it (guaranteeing that any small change to the source file will cause thousands of changes to the generated file), then the repository file (,v file) can get very large very quickly. This is annoying, since you probably don't want the revision history for the generated file; you just want it in the repository for completeness (it may be difficult to generate on some platforms, for example).

This appears to be a rare case where the admin -o (outdate) is useful:

$ cvs admin -o1.2::1.28 decoder.cc
RCS file: /u0/luna/CVSROOT/bintrans/driver/386Dir/decoder.cc,v
deleting revision 1.27
deleting revision 1.26
cvs admin: cannot remove revision 1.25 because it has tags
cvs admin: cannot modify RCS file for `decoder.cc'
$ cvs admin -o1.2::1.24 decoder.cc
RCS file: /u0/luna/CVSROOT/bintrans/driver/386Dir/decoder.cc,v
deleting revision 1.23
deleting revision 1.22
deleting revision 1.21
deleting revision 1.20
deleting revision 1.19
cvs admin: cannot remove revision 1.18 because it has tags
cvs admin: cannot modify RCS file for `decoder.cc'
You can get an idea of what version numbers can be deleted by simply examining the first few pages of the ,v file (e.g. less $CVSROOT/bintrans/driver/386Dir/decoder.cc,v). It is in relatively plain ascii format.

Note that when a deletion fails as above because of tags, you have to do it all again to really do the delete. The revision numbers are not inclusive. When you outdate a revision, you obviously can't request that revision back again, so this is not recommended for non generated files.

If you change your password

There are several ways that a cvs client can talk to a cvs server. One of these is the "pserver" (passwords handled by the server) method. You are using this if your CVS/Root entry starts with ":pserver".

If you are using pserver and your password changes on the machine running the cvs client, you can find yourself unable to use cvs commands. This is because your password is cached in a file, $CVS_PASSFILE (if CVS_PASSFILE is not set, $HOME/.cvspass). You could remove that file, or the entry relating to the cvs server you are using; that will prompt you to enter the "cvs login" command. Or of course just enter the "cvs login" command. The .cvspass file makes it easy for you so you don't have to enter the password all the time, but if your password changes, you need to send your new password.

Last modified 01/Jul/2005: Use -d and -A with "update -j HEAD"