Third-Party Tools
*****************

Many people have written programs to augment CVS.  I call these third-party tools because they have their own maintainers, separate from the CVS development team.  Most of these programs are not distributed with CVS, although some are.  This chapter covers third-party tools that I have found useful, but that are not distributed with CVS.

Although there are some very popular and widely used non-command-line or non-Unix interfaces to CVS (download sites are listed in Repository Administration), this chapter does not discuss most of them.  Their popularity makes it easy to find out more about them from mailing lists and newsgroups.  One exception to this is the Emacs pcl-cvs interface, which is very useful, but sometimes tricky to install.

pcl-cvs -- An Emacs Interface To CVS
====================================

Depends on: Emacs, Elib

URLs:

  * ftp://rum.cs.yale.edu/pub/monnier/pcl-cvs/

  * ftp://ftp.lysator.liu.se/pub/emacs/pcl-cvs-1.05.tar.gz

  * ftp://ftp.red-bean.com/pub/kfogel/pcl-cvs-1.05.tar.gz

Authors: Per Cederqvist and Stefan Monnier (current maintainer)

pcl-cvs is one of two Emacs/CVS interfaces.  The other is the native VC (Version Control) interface built into Emacs.  I prefer pcl-cvs because it was written exclusively for CVS and, therefore, works smoothly with the CVS way of doing things.  VC, on the other hand, was designed to work with several different back-end version control systems -- RCS and SCCS, as well as CVS -- and is not really "tuned" for CVS. For example, VC presents a file-based rather than a directory-based interface to revision control.

The advantages of pcl-cvs are strong enough that many people choose to download and install it rather than use VC.  Unfortunately, pcl-cvs has two disadvantages: It can be a bit tricky to install (much of this section is devoted to overcoming possible installation hurdles), and its recent releases are a bit unstable.

The latter problem is likely to be temporary, but it does raise the question of which version to use.  Stefan Monnier has just recently taken over the pcl-cvs maintainership; the latest release, 2.9.6 (available from the first URL in the preceding list), was a bit bumpy when I tried it.  No doubt the problems will be smoothed out soon, but in the meantime, you might want to use an older version.  Because I've been using Version 1.05 daily for a long time now and it's performed quite well, I'm going to document that version here.  Fortunately, the installation procedures don't change much from version to version.  If you decide to use pcl-cvs, I suggest that you check Monnier's download site for a version newer than 2.9.6; if there is one, try it out before regressing all the way to 1.05.

You'll notice that two URLs are given for Version 1.05.  The first is Per Cederqvist's site, where he still makes available an old archive of pcl-cvs.  However, since I'm not sure how much longer his archive will stay around, I'm also making the 1.05 distribution available from ftp.red-bean.com.

Although the rest of these instructions use examples from a Version 1.05 distribution, they should apply to later versions as well.

Installing pcl-cvs
------------------

If you don't normally deal with Emacs installation and site-maintenance issues, the pcl-cvs installation procedure may seem a bit daunting.  A little background on how Emacs works may help.

Most higher-level Emacs features are written in a language called "Emacs Lisp" (Emacs itself is essentially an interpreter for this language). People add new features to Emacs by distributing files of Emacs Lisp code.  pcl-cvs is written in this language, and it depends on a library of useful, generic Emacs Lisp functions called Elib (also written in part by Per Cederqvist, but distributed separately from pcl-cvs).

Elib is not included in the regular Emacs distribution (at least not FSF Emacs; I don't know about XEmacs), so you may have to download and install it yourself before you can use pcl-cvs.  You can get it from ftp://ftp.lysator.liu.se/pub/emacs/elib-1.0.tar.gz.  Installation instructions are contained within the package.

Once Elib is installed, you're ready to build and install pcl-cvs. These instructions applies both to Version 1.05 and the 2.x series (although you should check the NEWS and INSTALL files in newer distributions to see what's changed).

First, unpack pcl-cvs (I'm using Version 1.05, but it could just as easily have been 2.9.6)

     floss$ zcat pcl-cvs-1.05.tar.gz | tar xvf -
     pcl-cvs-1.05/
     pcl-cvs-1.05/README
     pcl-cvs-1.05/NEWS
     pcl-cvs-1.05/INSTALL
     pcl-cvs-1.05/ChangeLog
     pcl-cvs-1.05/pcl-cvs.el
     pcl-cvs-1.05/pcl-cvs.texinfo
     pcl-cvs-1.05/compile-all.el
     pcl-cvs-1.05/pcl-cvs-lucid.el
     pcl-cvs-1.05/pcl-cvs-startup.el
     pcl-cvs-1.05/pcl-cvs.info
     pcl-cvs-1.05/Makefile
     pcl-cvs-1.05/texinfo.tex

and go into the source tree's top level:

     floss$ cd pcl-cvs-1.05/

A Makefile is supplied there.  According to the instructions in the INSTALL file, you're supposed to edit a few paths at the top of the Makefile and then run:

     floss$ make install

If that works, great.  However, this sometimes results in an error (the pcl-cvs code itself is very portable, but its installation procedures sometimes are not).  Do this if you get an error:

     floss$ make clean
     floss$ make

If all goes well, these commands accomplish a significant part of the installation by byte-compiling all of the Emacs Lisp files. (Byte-compiling converts a file of human-readable Emacs Lisp code -- an .el file -- into a more compact and efficient representation -- an .elc file.  Emacs can load and run an .elc file with better performance than it can a plain .el file.)

I'll proceed as though the byte-compilation stage has succeeded.  If the byte compilation does not appear to succeed, don't worry: The .elc files are a luxury, not a necessity.  They improve performance slightly, but you can run pcl-cvs from the raw .el files with no problem.

If the make install failed, the next step is to get the Emacs Lisp (whether .el or .elc) into a directory where Emacs can load it automatically.  Emacs has a designated directory on the system for locally installed Lisp.  To find this directory -- it will have a file named default.el in it -- check the following locations, in this order:

 1. /usr/share/emacs/site-lisp/

 2. /usr/local/share/emacs/site-lisp/

 3. /usr/lib/emacs/site-lisp/

 4. /usr/local/lib/emacs/site-lisp/

Once you've found your site-lisp directory, copy all of the Lisp files to it (you may have to be root to do this):

     floss# cp -f *.el *.elc /usr/share/emacs/site-lisp/

The last step is to tell Emacs about the entry points to pcl-cvs (the main one being the function cvs-update), so it will know to load the pcl-cvs code on demand.  Because Emacs always reads the default.el file when it starts up, that's where you need to list the pcl-cvs entry points.

Fortunately, pcl-cvs provides the necessary content for default.el. Simply put the contents of pcl-cvs-startup.el into default.el (or perhaps into your .emacs, if you're just installing this for yourself) and restart your Emacs.

You may also want to copy the .info files into your info tree and add pcl-cvs to the table of contents in the dir file.

Using pcl-cvs
-------------

Once installed, pcl-cvs is very easy to use.  You just run the function cvs-update, and pcl-cvs brings up a buffer showing what files in your working copy have been modified or updated.  From there, you can commit, do diffs, and so on.

Because cvs-update is the main entry point, I suggest that you bind it to a convenient key sequence before going any further.  I have it bound to Ctrl+c v in my .emacs:

     (global-set-key "\C-cv" 'cvs-update)

Otherwise, you can run it by typing M-x cvs-update (also known as Esc-x cvs-update).

When invoked, cvs-update runs cvs update as if in the directory of the file in the current buffer -- just as if you typed cvs update on the command line in that directory.  Here's an example of what you might see inside Emacs:

     PCL-CVS release 1.05 from CVS release $Name:  $.
     Copyright (C) 1992, 1993 Per Cederqvist
     Pcl-cvs comes with absolutely no warranty; for details consult the manual.
     This is free software, and you are welcome to redistribute it under certain
     conditions; again, consult the TeXinfo manual for details.
      Modified ci README.txt
      Modified ci fish.c
     ---------- End ----

Two files have been locally modified (some versions of pcl-cvs show the subdirectories where the files are located).  The next logical action is to commit one or both of the files, which is what the ci on each line means.  To commit one of them, go to its line and type c.  You are brought to a log message buffer, where you can type a log message as long as you want (real log message editing is the major advantage of pcl-cvs over the command line).  Type Ctrl+c Ctrl+c when done to complete the commit.

If you want to commit multiple files at once, sharing a log message, first use m to mark the files that you intend to commit.  An asterisk appears next to each file as you mark it:

     PCL-CVS release 1.05 from CVS release $Name:  $.
     Copyright (C) 1992, 1993 Per Cederqvist
     Pcl-cvs comes with absolutely no warranty; for details consult the manual.
     This is free software, and you are welcome to redistribute it under certain
     conditions; again, consult the TeXinfo manual for details.
     * Modified ci README.txt
     * Modified ci fish.c
     ---------- End ----

Now when you type c anywhere, it applies to all (and only) the marked files.  Write the log message and commit them with Ctrl+c Ctrl+c as before.

You can also type d to run cvs diff on a file (or on marked files) and f to bring a file into Emacs for editing.  Other commands are available; type Ctrl+h m in the update buffer to see what else you can do.

Error Handling In pcl-cvs
-------------------------

The pcl-cvs program has historically had an odd way of dealing with error and informational messages from CVS (although this may be corrected in the latest versions).  When it encounters a message from CVS that it doesn't know about, it gets hysterical and throws you into a mail buffer, ready to send a pregenerated bug report to the author of pcl-cvs.  Unfortunately, among the CVS messages that pcl-cvs may not know about are the ones associated with conflicting merges, which, although not common, certainly do occur from time to time.

If pcl-cvs suddenly dumps you into a mail buffer, don't panic.  Read over the contents of the buffer carefully -- the offending CVS output should be in there somewhere.  If it looks like a merge, you can just get rid of the mail buffer and rerun cvs-update.  It should now succeed, because CVS won't output any merge messages (because the merge has already taken place).

(Update: this problem appears to have been fixed in more recent versions of pcl-cvs, so very probably you can ignore this entire warning.)

The Future Of pcl-cvs
---------------------

Although I may be giving you the impression that pcl-cvs is barely maintained and a risky investment, the instability appears to be temporary.  Stefan Monnier is a responsive maintainer (I contacted him several times during the writing of this chapter, and he always answered right away; he is already making headway on some of the bugs in Version 2.9.6).  Very likely by the time this is published, you will be able to download Version 2.9.7 or later with confidence.

In fact, I just now got an encouraging email on this topic from Greg Woods, a former maintainer of pcl-cvs, reprinted here with his permission:

     From: woods@most.weird.com (Greg A. Woods)
     Subject: Re: pcl-cvs maintenance status, stability of recent "release"s?
     To: kfogel@red-bean.com
     Date: Sun, 29 Aug 1999 18:59:19 -0400 (EDT)

     [...]
     I've been using Stefan's releases for some time now, and indeed I have
     abandoned my own branch of it.

     He's done a lot of really good work on PCL-CVS and except for a few odd
     quirks in the 2.9.6 version I'm using daily now it is quite usable (and
     is approximately infinitely more usable with modern CVS than the one
     that was in the CVS distribution! ;-).

     I've added a pcl-cvs.README file to my FTP site to point out that the
     files there are indeed quite old (at least in Internet time! ;-) and to
     give a pointer to Stefan's FTP site too.

     [...]

In a later email, Greg said that the FSF is considering including pcl-cvs in their next release of Emacs (20.5), which would render most of the preceding installation advice obsolete.  Sigh.  It's hard to keep up with free software, sometimes.

cvsutils -- General Utilities For Use With CVS
==============================================

Depends on: Perl

URLs:

  * http://www.red-bean.com/cvsutils

Authors: Tom Tromey (original author) and Pavel Roskin (current maintainer)

The suite of small programs called cvsutils generally (although not always) performs offline operations in the CVS working copy. Offline operations are those that can be done without contacting the repository, while still leaving the working copy in a consistent state for the next time the repository is contacted.  Offline behavior can be extremely handy when your network connection to the repository is slow or unreliable.

The cvsutils programs are listed below in approximate order of usefulness (according to my opinion), with the more useful ones coming first.  Coincidentally, this also arranges them by safety.  Safety is an issue because some of these utilities can, in their normal course of operation, cause you to lose local modifications or files from your working copy.  Therefore, read the descriptions carefully before using these utilities.

This documentation is accurate as of Version 0.1.4.  Be sure to read the README file in any later versions for more up-to-date information.

cvsu
----

Danger level: None

Contacts repository: No

This does an offline cvs update by comparing the timestamps of files on disk with their timestamps recorded in CVS/Entries.  You can thus tell which files have been locally modified and which files are not known to be under CVS control.  Unlike cvs update, cvsu does not bring down changes from the repository.

Although it can take various options, cvsu is most commonly invoked without any options:

     floss$ cvsu
     ? ./bar
     ? ./chapter-10.html
     M ./chapter-10.sgml
     D ./out
     ? ./safe.sh
     D ./tools

The left-side codes are like the output of cvs update, except that D means directory.  This example shows that chapter-10.sgml has been modified locally.  What the example doesn't show is that cvsu ran instantly, whereas a normal cvs update would have required half a minute or so over my slow modem line.  Run

     floss$ cvsu --help

to see a list of options.

cvsdo
-----

Danger level: Low to none

Contacts repository: No

This can simulate the working copy effects of cvs add and cvs remove, but without contacting the repository.  Of course, you'd still have to commit the changes to make them take effect in the repository, but at least the add and remove commands themselves can be sped up this way. Here's how to use it

     floss$ cvsdo add FILENAME

or

     floss$ cvsdo remove FILENAME

To see a list of further options, run:

     floss$ cvsdo --help

cvschroot
---------

Danger level: Low

Contacts repository: No

This deals with a repository move by tweaking the working copy to point to the new repository.  This is useful when a repository is copied en masse to a new location.  When that happens, none of the revisions are affected, but the CVS/Root (and possibly the CVS/Repository) file of every working copy must be updated to point to the new location.  Using cvschroot is a lot faster than checking out a new copy.  Another advantage is that it doesn't lose your local changes.

Usage:

     floss$ cvschroot NEW_REPOS

For example:

     floss$ cvschroot :pserver:newuser@newhost.wherever.com:/home/cvs/myproj

cvsrmadm
--------

Danger level: Low to medium

Contacts repository: No

This removes all of the CVS/ administrative subdirectories in your working copy, leaving behind a tree similar to that created by cvs export.

Although you won't lose any local changes by using cvsrmadm, your working copy will no longer be a working copy.

Use with caution.

cvspurge
--------

Danger level: Medium

Contacts repository: No

This removes all non-CVS-controlled files in your working copy.  It does not undo any local changes to CVS-controlled files.

Use with caution.

cvsdiscard
----------

Danger level: Medium to high

Contacts repository: Maybe

This is the complement of cvspurge.  Instead of removing unknown files but keeping your local changes, cvsdiscard undoes any local changes (replacing those files with fresh copies from the repository), but keeps unknown files.

Use with extreme caution.

cvsco
-----

Danger level: High

Contacts repository: Maybe

This is the union of cvspurge and cvsdiscard.  It undoes any local changes and removes unknown files from the working copy.

Use with truly paranoid caution.

cvsdate
-------

This script is apparently incomplete and possibly may never be finished. (See the README file for details.)

cvs2cl -- Generate GNU-Style ChangeLogs
=======================================

Depends on: Perl

URL: http://www.red-bean.com/~kfogel/cvs2cl.shtml

cvs2cl.pl condenses and reformats the output of cvs log to create a GNU-style ChangeLog file for your project.  ChangeLogs are chronologically organized documents showing the change history of a project, with a format designed specifically for human-readability (see the following examples).

The problem with the cvs log command is that it presents its output on a per-file basis, with no acknowledgement that the same log message, appearing at roughly the same time in different files, implies that those revisions were all part of a single commit.  Thus, reading over log output to get an overview of project development is a hopeless task -- you can really only see the history of one file at a time.

In the ChangeLog produced by cvs2cl.pl, identical log messages are unified, so that a single commit involving a group of files shows up as one entry.  For example:

     floss$ cvs2cl.pl -r
     cvs log: Logging .
     cvs log: Logging a-subdir
     cvs log: Logging a-subdir/subsubdir
     cvs log: Logging b-subdir
     floss$ cat ChangeLog
     ...
     1999-08-29 05:44  jrandom

        * README (1.6), hello.c (2.1), a-subdir/whatever.c (2.1),
        a-subdir/subsubdir/fish.c (2.1): Committing from pcl-cvs 2.9, just
        for kicks.

     1999-08-23 22:48  jrandom

        * README (1.5): [no log message]

     1999-08-22 19:34  jrandom

        * README (1.4): trivial change
     ...
     floss$

The first entry shows that four files were committed at once, with the log message, "Committing from pcl-cvs 2.9, just for kicks.".  (The -r option was used to show the revision number of each file associated with that log message.)

Like CVS itself, cvs2cl.pl takes the current directory as an implied argument but acts on individual files if given file name arguments. Following are a few of the most commonly used options.

  * h, --help

     Show usage (including a complete list of options).

  * -r, --revisions

     Show revision numbers in output.  If used in conjunction with -b, branches are shown as BRANCHNAME.N, where N is the revision on the branch.

  * -t, --tags

     Show tags (symbolic names) for revisions that have them.

  * -b, --branches

     Show the branch name for revisions on that branch.  (See also -r.)

  * -g OPTS, --global-opts OPTS

     Pass OPTS as global arguments to cvs.  Internally, cvs2cl.pl invokes cvs to get the raw log data; thus, OPTS are passed right after the cvs in that invocation.  For example, to achieve quiet behavior and compression, you can do this:

          floss$ cvs2cl.pl -g "-Q -z3"

  * -l OPTS, --log-opts OPTS

     Similar to -g, except that OPTS are passed as command options instead of global options.  To generate a ChangeLog showing only commits that happened between July 26 and August 15, you can do this:

          floss$ cvs2cl.pl -l "'-d1999-07-26<1999-08-15'"

     Notice the double-layered quoting -- this is necessary in Unix because the shell that invokes cvs log (inside cvs2cl.pl) interprets the < as a shell redirection symbol.  Therefore, the quotes have to be passed as part of the argument, making it necessary to surround the whole thing with an additional set of quotes.

  * -d, --distributed

     Put an individual ChangeLog in each subdirectory, covering only commits in that subdirectory (as opposed to building one ChangeLog that covers the directory where cvs2cl.pl was invoked and all subdirectories underneath it).

cvsq -- Queue CVS Commands For Later Connection
===============================================

Depends on: Bash

URL: http://www.volny.cz/v.slavik/lt/cvsq.html

Vaclav Slavik <v.slavik@volny.cz>, the author of cvsq, has this to say about it:

cvsq stands for "cvs queued" and it is a small bash script that wraps around CVS.  It makes working with CVS repository a bit easier for people connected via dial-up, because it can queue CVS commands and pass them to "real cvs" later.

For example, you can commit files immediately after editing them, when being offline, so you don't forget about them:

         cvsq commit -m "change 1" file1.c
         cvsq commit -m "change 2" file2.c
         cvsq commit -m "change 3" file3.c

And then, when you go online, you simply type

         cvsq upload

and all changes will be commited into the repository.  If uploading of a particular file fails, it won't be lost -- instead, you'll see error message and the file will stay in cvsq queue.

You can use cvsq even for commands that make no sense when offline -- in that case, the command is immediately passed to cvs and not queued.  For example, you can call cvsq update and it won't be put into the queue but executed immediately.  In fact, you can start using cvsq as a replacement for cvs.

cvsq is in public domain.

cvslock -- Lock Repositories For Atomicity
==========================================

Depends on: C compiler for installation; nothing for runtime

URL: ftp://riemann.iam.uni-bonn.de/pub/users/roessler/cvslock/

This program locks a CVS repository (either for reading or writing) in the same way that CVS does, so that CVS will honor the locks.  This can be useful when, for example, you need to make a copy of the whole repository and want to avoid catching parts of commits or other people's lockfiles.

The cvslock distribution is packaged extremely well and can be installed according to the usual GNU procedures.  Here's a transcript of an install session:

     floss$ zcat cvslock-0.1.tar.gz | tar xvf -
     cvslock-0.1/
     cvslock-0.1/Makefile.in
     cvslock-0.1/README
     cvslock-0.1/COPYING
     cvslock-0.1/Makefile.am
     cvslock-0.1/acconfig.h
     cvslock-0.1/aclocal.m4
     cvslock-0.1/config.h.in
     cvslock-0.1/configure
     cvslock-0.1/configure.in
     cvslock-0.1/install-sh
     cvslock-0.1/missing
     cvslock-0.1/mkinstalldirs
     cvslock-0.1/stamp-h.in
     cvslock-0.1/cvslock.c
     cvslock-0.1/cvslock.1
     cvslock-0.1/snprintf.c
     cvslock-0.1/cvslssh
     cvslock-0.1/VERSION
     floss$ cd cvslock-0.1
     floss$ ./configure
      ...
     floss$ make
     gcc -DHAVE_CONFIG_H -I. -I. -I.   -g -O2 -c cvslock.c
     gcc -g -O2  -o cvslock  cvslock.o
     floss$ make install
      ...
     floss$

(Note that you may have to do the make install step as root).

Now, cvslock is installed as /usr/local/bin/cvslock.  When you invoke it, you can specify the repository with -d or via the $CVSROOT environment variable, just as with CVS itself (the following examples use -d).  Its only required argument is the name of the directory to lock, relative to the top of the repository.  That directory and all of its subdirectories will be locked.  In this example, there are no subdirectories, so only one lockfile is created:

     floss$ ls /usr/local/newrepos/myproj/b-subdir/
     random.c,v
     floss$ cvslock -d /usr/local/newrepos  myproj/b-subdir
     floss$ ls /usr/local/newrepos/myproj/b-subdir/
     #cvs.rfl.cvslock.floss.27378  random.c,v
     floss$ cvslock -u -p 27378 -d /usr/local/newrepos  myproj/b-subdir
     floss$ ls /usr/local/newrepos/myproj/b-subdir/
     random.c,v
     floss$

Notice that when I cleared the lock (-u for unlock), I had to specify -p 27378.  That's because cvslock uses Unix process IDs when creating lockfile names to ensure that its locks are unique. When you unlock, you have to tell cvslock which lock instance to remove, even if there's only one instance present.  Thus, the -p flag tells cvslock which previous instance of itself it's cleaning up after (you can use -p with or without -u, though).

If you're going to be working in the repository for a while, doing various operations directly in the file system, you can use the -s option to have cvslock start up a new shell for you.  It then consults the $SHELL environment variable in your current shell to determine which shell to use:

     floss$ cvslock -s -d /usr/local/newrepos myproj

The locks remain present until you exit the shell, at which time they are automatically removed.  You can also use the -c option to execute a command while the repository is locked.  Just as with -s, the locks are put in place before the command starts and removed when it's finished. In the following example, we lock the repository just long enough to display a listing of all of the lockfiles:

     floss$ cvslock -c 'find . -name "*cvslock*" ' -d /usr/local/newrepos myproj
     cvslock: '/usr/local/newrepos/myproj' locked successfully.
     cvslock: Starting 'find . -name "*cvslock*" -print'...
     ./a-subdir/subsubdir/#cvs.rfl.cvslock.floss.27452
     ./a-subdir/#cvs.rfl.cvslock.floss.27452
     ./b-subdir/#cvs.rfl.cvslock.floss.27452
     ./#cvs.rfl.cvslock.floss.27452
     floss$ find /usr/local/newrepos/myproj -name "*cvslock*" -print
     floss$

The command (the argument to the -c option) is run with the specified repository directory as its working directory.

By default, cvslock creates read-locks.  You can tell it to use write-locks instead by passing the -W option.  (You can pass -R to specify read-locks, but that's the default anyway.)  Always remove any locks when you're finished, so that other users' CVS processes don't wait needlessly.

Note that cvslock must be run on the machine where the repository resides -- you cannot specify a remote repository.  (For more information, run man cvslock, which is a manual page installed when you ran make install.)

Other Packages
==============

Many other third-party packages are available for CVS.  Following are pointers to some of these.

CVSUp (Part Of The FreeBSD Project)
===================================

CVSUp is an efficient generic mirroring tool with special built-in support for mirroring CVS repositories.  The FreeBSD operating system uses it to distribute changes from their master repository, so users can keep up to date conveniently.

For more information on CVSUp in general, check out http://www.polstra.com/projects/freeware/CVSup/.

For its use in FreeBSD in particular, see http://www.freebsd.org/handbook/synching.html#CVSUP.

CVSWeb: A Web Interface To CVS Repositories
===========================================

CVSWeb provides a Web interface to browsing CVS repositories.  A more accurate name might be "RCSWeb", because what it actually does is allow you to browse revisions directly in a repository, viewing log messages and diffs.  Although I've never found it to be a particularly compelling interface myself, I have to admit that it is intuitive enough and a lot of sites use it.

Although the software was originally written by Bill Fenner, the version most actively under development right now seems to be Henner Zeller's, at http://linux.fh-heilbronn.de/~zeller/cgi/cvsweb.cgi/.

You may also want to visit Fenner's original site at http://www.freebsd.org/~fenner/cvsweb/ and possibly this summary of the CVSWeb scene at http://www.cvshome.org/dev/addoncvsweb.html.

Finally, if you'd like to see CVSWeb in action, a good example can be browsed at http://sourceware.cygnus.com/cgi-bin/cvsweb.cgi/.

The CVS contrib/ Directory
==========================

As mentioned in Repository Administration, a number of third-party tools are shipped with CVS and are collected in the contrib/ directory. Although I'm not aware of any formal rule for determining which tools are distributed with CVS, an effort may be in process to gather most of the widely used third-party tools and put them in contrib/ so people know where to find them.  Until that happens, the best way to find such tools is to look in contrib/, look at various CVS Web sites, and ask on the mailing list.

Writing Your Own Tools
======================

CVS can at times seem like a bewildering collection of improvised standards.  There's RCS format, various output formats (history, annotate, log, update, and so on), several repository administrative file formats, working copy administrative file formats, the client/server protocol, the lockfile protocol....  (Are you numb yet? I could keep going, you know.)

Fortunately, these standards remain fairly consistent from release to release -- so if you're trying to write a tool to work with CVS, you at least don't have to worry about hitting a moving target.  For every internal standard, there are usually a few people on the info-cvs@gnu.org mailing list who know it extremely well (several of them helped me out during the writing of this book).  There is also the documentation that comes with the CVS distribution (especially doc/cvs.texinfo, doc/cvsclient.texi, and doc/RCSFILES). Finally, there is the CVS source code itself, the last word on any question of implementation or behavior.

With all of this at your disposal, there's no reason to hesitate.  If you can think of some utility that would make your life with CVS easier, go ahead and write it -- chances are other people have been wanting it, too.  Unlike a change to CVS itself, a small, standalone external utility can get wide distribution very quickly, resulting in quicker feedback for its author and faster bug fixes for all of the users.