Concurrent Versions System (CVS) Tips

Creating Repositories | E-mail Notifications | Exporting from CVS | Repository Permissions | Repository Access Control Lists | Alternate pserver port | Avoid Keyword Expansion

Various tips on the setup, learning, and use of Concurrent Versions System (CVS). Consider using Subversion instead of CVS.

For documentation, consult (or purchase) the free Open Source Development with CVS book.

Notes on CVS & Secure Shell (SSH).

Creating Repositories

Use create-cvs-repo to create testing repositories. Test repositories allow practice with tricky commands such as cvs import. The create-cvs-repo script will create a repository under a random /tmp/cvs-test-repo.* directory, then checkout a sandbox of that repository under /tmp/cvs-test-sandbox.*.

$ create-cvs-repo
notice: using random repository: directory=/tmp/cvs-test-repo.KKfADh
$ export CVSROOT=/tmp/cvs-test-repo.KKfADh

When done experimenting, the random repository and associated sandbox can be removed using the following command.

$ rm -rf /tmp/cvs-test-*

E-mail Notifications

For repositories used by many developers, configuring the repository to send e-mail about changes can help keep everyone up to speed on what work is being done. Another approach would be to publish the change information with Really Simple Syndication (RSS) feeds.

Setup of e-mail notification requires changing and adding various files under the special CVSROOT module.

$ cvs -d /var/cvs checkout CVSROOT

  1. Commit scripts under the CVSROOT module.
  2. The and scripts will need to be committed under CVSROOT, and edited to support local setup needs, such as setting $MAIL_TO and $MLISTHOST in Setting the target address to be a mailing list allows developers or other users to subscribe to the commit e-mail. For some sites, sending the messages to a shared Internet Message Access Protocol (IMAP) mailbox will make more sense.

    The scripts linked to here were extracted from the Apache CVSROOT at some point in the past. Review of them for security problems is highly recommended. Using a safer temporary directory is mandatory if multiple repositories are involved or where the repository is on a multiuser system.

  3. Setup checkoutlist.
  4. The checkoutlist file will need to list the scripts, so that they are properly checked out under the real CVSROOT directory.

    $ fgrep .pl checkoutlist

  5. Set to run from the commitinfo file with the following entry.

  7. Set to run from the loginfo file with the following entry.

Exporting from CVS

Use stage-from-cvs for a cvs export wrapper that uses rsync to create or update an existing target directory. The following example shows how to export the htdocs module under the /cvs repository into /var/www/htdocs. Only files marked with the stable tag will be exported.

$ stage-from-cvs -d /cvs -r stable htdocs /var/www/htdocs

stage-from-cvs uses the rsync checksum flag, so will be slow for large repositories. Remove the -c argument to rsync in the script to disable checksumming. Also, the cvs export is done to a temporary directory under /var/tmp, which could fail if no space is available to duplicate the directory structure in question.

CFEngine configurations can be staged using stage-from-cvs.

Tags remain on removed files

If exporting with the -r tag option, be aware CVS tags remain on removed files. When removing a file, be sure to clear the tag so the file no longer is exported:

$ cvs rm -f thefile
$ cvs tag -d stable thefile
$ cvs ci -m 'Removed file'

To check for tags on a file, use cvs status -v filename. The cvs-tag-parse script contains shell script code to parse the output of cvs status -v.

A commitinfo script could warn when files are removed; cvs status -v cannot be run inside a commitinfo script, as the cvs status will wait forever for the cvs commit lock to go away:



for filename in "$@"; do
# commited file does not exist: removed via 'cvs rm'
if [ ! -e "$filename" ]; then

if [ ! -z "$REMOVED" ]; then
echo "WARNING: check for tags on removed: cvs status -v $REMOVED"

A script to parse for repository files under Attic with tags set might be necessary, in the event a commiter does not remove the tags when removing a file.

Repository Permissions

CFEngine can maintain group write permissions on a repository, while removing access to the administrative files under CVSROOT. The repository below lives under the /cvs directory, allows devel group access to the repository and CVSROOT/history, and limits access to the CVSROOT/passwd* files for pserver use.

# only access for developers
group=devel mode=ug+rw,o-rw
action=fixall r=inf

# allow group read, but not write on administrative files
owner=admin group=devel mode=u+rw,g+r,g-w,o-rw
action=fixall r=inf
exclude=history exclude=passwd exclude=passwd,v

# but need group write on history
owner=admin group=devel mode=ug+rw,o-rw

# for cvs pserver (use Kerberos variety, or switch to SSH)
# cvs pserver user will also need access!
# TODO passwd* glob does not match 'passwd' ?
owner=admin group=admin mode=u+rw,go-rw
owner=admin group=admin mode=u+rw,go-rw

If all devel group users have full access to the repository, only a single permissions entry is required.

/cvs mode=ug+rw,o-rw group=devel action=fixall r=inf

Repository Access Control Lists

Try CVSPermissions to set per-user access rights to directories or tags.

Alternate pserver port

cvs login supports different ports. Append the custom port after the hostname in CVSROOT. The following example will login to a pserver running at port 9999.

cvs -d login

On the CVS server, inetd or xinetd can specify the port the pserver runs at. The following xinetd example shows cvspserver running at the default port of 2401.

service cvspserver
disable = no
socket_type = stream
wait = no
user = cvsuser
group = cvsuser
protocol = tcp
env = 'HOME=/cvs'
env += 'TMP=/var/tmp/cvs'
log_on_failure += USERID
port = 2401
server = /usr/bin/cvs
server_args = -f --allow-root=/cvs pserver
rlimit_as = UNLIMITED

Avoid Keyword Expansion

CVS keyword expansion can be disabled per-file via the -kb option to cvs add. Some text formats allow specific keyword expansions to be disabled, for example via the use of a CDATA entry in an Extensible Markup Language (XML) file to breakup the keyword: