(Mail CVS commit notifications)


cvslog [-cDdhilmosvw] [--diff-limit=limit [-a address ...] %{sVv}


CVS 1.10 or later, Perl 5.004 or later, diffstat for the -s option, and a sendmail command that can accept formatted mail messages for delivery.


cvslog is intended to be run out of CVS's loginfo administrative file. It parses the (undocumented) format of CVS's commit notifications, cleans it up and reformats it, and mails the notification to one or more e-mail addresses. Optionally, a diffstat(1) summary of the changes can be added to the notification, and a CVS commit spanning multiple directories can be combined into a single notification (by default, CVS generates a separate notification for each directory).

To combine a commit spanning multiple directories into a single notification, cvslog needs the help of an additional program run from the commitinfo administrative file that records the last directory affected by the commit. See the description in FILES for what files and directories must be created. One such suitable program is cvsprep by the same author.

For information on how to add cvslog to your CVS repository, see INSTALLATION below. cvslog also looks for a configuration file named cvslog.conf; for details on the format of that file, see CONFIGURATION.

The From: header of the mail message sent by cvslog is formed from the user making the commit. The contents of the environment variable CVSUSER or the name of the user doing the commit if CVSUSER isn't set is looked up in the passwd file in the CVS repository, if present, and the fourth field is used as the full name and the fifth as the user's e-mail address. If that user isn't found in passwd, it's looked up in the system password file instead to try to find a full name. Otherwise, that user is just used as an e-mail address.


-a address, --address=address

Send the commit notification to address (possibly in addition to the address defined in cvslog.conf). This option may occur more than once, and all specified addresses will receive a copy of the notification.

-c, --cvsweb

Append the cvsweb URLs for all the diffs covered in the commit message to the message. The base cvsweb URL must be set in the configuration file. For a regular revision, the file name will be added and the r1 and r2 parameters will be appended with the appropriate values, along with f=h to request formatted diff output. For a new file, no revision will be added and the URL will point only to the filename. For a removed file, no URL will be included.

To support pre-configured options, if the base URL contains a ?, the filename will be inserted before the ? and the options as described above will be added to the end of the URL. Currently, the cvsweb URLs are not further configurable.

-D, --debug

Prints out the information cvslog got from CVS as it works. This option is mostly useful for developing cvslog and checking exactly what data CVS provides. The first line of output will be the options passed to cvsweb, separated by |.

-d, --diff

Append the full diff output for each change to the notification message, but suppress it if it's larger than the size limit (200KB by default; see the --diff-limit option). Note that the entire diff output is temporarily stored in memory, even if larger than the limit, so this could result in excessive memory usage in cvslog for very large changes.

When this option is given, cvslog needs to be able to find cvs in the user's PATH.

If one of the committed files is binary and this is detected by cvs, cvslog will suppress the diff and replace it with a note that the file is binary.


Limit appended diffs to no more than limit KB. Diffs longer than this limit will not be included in the notification. The default if this option is not specified is 200KB.

-h, --help

Print out this documentation (which is done simply by feeding the script to perldoc -t).

-i, --include-versions

Include version numbers (in parentheses) after the file names in the lists of added, removed, and changed files. By default, only the file names are given.

-l, --long-subject

Normally, cvslog will just list the number of changed files rather than the complete list of them if the subject would otherwise be too long. This flag disables that behavior and includes the full list of modified files in the subject header of the mail, no matter how long it is.

-m, --merge

Merge multidirectory commits into a single notification. This requires that a program be run from commitinfo to record the last directory affected by the commit. Using this option will cause cvslog to temporarily record information about a commit in progress in TMPDIR or /tmp; see FILES.

-o, --omit-author

Omit the author information from the commit notification. This is useful where all commits are done by the same person (so the author information is just noise) or where the author information isn't actually available.

-s, --summary

Append to each commit notification a summary of the changes, produced by generating diffs and feeding those diffs to diffstat(1). diffstat(1) must be installed to use this option; see also the diffstat configuration parameter in CONFIGURATION.

When this option is given, cvslog needs to be able to find cvs in the user's PATH.

If one of the committed files is binary and this is detected by cvs, cvslog will replace the uninformative diffstat line corresponding to that file (diffstat will indicate that nothing changed) with a note that the file is binary.

-v, --version

Print out the version of cvslog and exit.

-w, --show-directory

Show the working directory from which the commit was made. This is usually not enlightening and when running CVS in server mode will always be some uninteresting directory in /tmp, so the default is to not include this information.


cvslog will look for a configuration file named cvslog.conf in the CVSROOT directory of your repository. Absence of this file is not an error; it just means that all of the defaults will be used. The syntax of this file is one configuration parameter per line in the format:

    parameter: value

The value may be enclosed in double-quotes and must be enclosed in double-quotes if there is trailing whitespace that should be part of the value. There is no way to continue a line; each parameter must be a single line. Lines beginning with # are comments.

The following configuration parameters are supported:


The address or comma-separated list of addresses to which all commit messages should be sent. If this parameter is not given, the default is to send the commit message only to those addresses specified with -a options on the command line, and there must be at least one -a option on the command line.


The base URL for cvsweb diffs for this repository. Only used if the -c option is given; see the description of that option for more information about how the full URL is constructed.


The full path to the diffstat(1) program. If this parameter is not given, the default is to look for diffstat(1) on the user's PATH. Only used if the -s option is given.


The value should be a valid mail header, such as "X-Ticket: cvs". This header will be added to the mail message sent. This configuration parameter may occur multiple times, and all of those headers will be added to the message.


The hostname to append to unqualified addresses given on the command line with -a. If set, an @ and this value will be appended to any address given with -a that doesn't contain @. This parameter exists solely to allow for shorter lines in the loginfo file.


The full path to the sendmail binary. If not given, this setting defaults to either /usr/sbin/sendmail or /usr/lib/sendmail, whichever is found, falling back on /usr/lib/sendmail.


The subject prefix to use for the mailed notifications. Appended to this prefix will be the module or path in the repository of the affected directory and then either a list of files or a count of files depending on the available space. The default is "CVS update of ".


Follow these steps to add cvslog to your project:

  1. Check out CVSROOT for your repository (see the CVS manual if you're not sure how to do this), copy this script into that directory, change the first line to point to your installation of Perl if necessary, and cvs add and commit it.

  2. Add a line like:

        cvslog Unable to check out CVS log notification script cvslog

    to checkoutlist in CVSROOT and commit it.

  3. If needed, create a cvslog.conf file as described above and cvs add and commit it. Most installations will probably want to set address, since the most common CVS configuration is a single repository per project with all commit notifications sent to the same address. If you don't set address, you'll need to add -a options to every invocation of cvslog in loginfo.

  4. If you created a cvslog.conf file, add a line like:

        cvslog.conf Unable to check out cvslog configuration cvslog.conf

    to checkoutlist in CVSROOT and commit it.

  5. Set up your rules in loginfo for those portions of the repository you want to send CVS commit notifications for. A good starting rule is:

        DEFAULT     $CVSROOT/CVSROOT/cvslog %{sVv}

    which will send notifications for every commit to your repository that doesn't have a separate, more specific rule to the value of address in cvslog.conf. You must always invoke cvslog as $CVSROOT/CVSROOT/cvslog; cvslog uses the path it was invoked as to find the root of the repository. If you have different portions of your repository that should send notifications to different places, you can use a series of rules like:

        ^foo/       $CVSROOT/CVSROOT/cvslog -a foo-commit %{sVv}
        ^bar/       $CVSROOT/CVSROOT/cvslog -a bar-commit %{sVv}

    This will send notification of commits to anything in the foo directory tree in the repository to foo-commit (possibly qualified with mailhost from cvslog.conf) and everything in bar to bar-commit. No commit notifications will be sent for any other commits. The %{sVv} string is replaced by CVS with information about the committed files and should always be present.

    If you are using CVS version 1.12.6 or later, the format strings for loginfo rules have changed. Instead of %{sVv}, use -- %p %{sVv}, once you've set UseNewInfoFmtStrings=yes in config. For example:

        DEFAULT     $CVSROOT/CVSROOT/cvslog -- %p %{sVv}

    Any options to cvslog should go before --. See the CVS documentation for more details on the new loginfo format.

  6. If you want summaries of changes, obtain and compile diffstat and add -s to the appropriate lines in loginfo. You may also need to set diffstat in cvslog.conf.

    diffstat is at <>.

  7. If you want merging of multidirectory commits, add -m to the invocations of cvslog, copy cvsprep into your checked out copy of CVSROOT, change the first line of the script if necessary to point to your installation of Perl, and cvs add and cvs commit it. Then, a line like:

        cvsprep Unable to check out CVS log notification script cvsprep

    to checkoutlist in CVSROOT and commit it.

    See WARNINGS for some warnings about the security of multi-directory commit merging.

  8. If your operating system doesn't pass the full path to the cvslog executable to this script when it runs, you'll need to edit the beginning of this script and set $REPOSITORY to the correct path to the root of your repository. This should not normally be necessary. See the comments in this script for additional explanation.


Send all commits under the baz directory to address defined in cvslog.conf:

    ^baz/       $CVSROOT/CVSROOT/cvslog -msw %{sVv}

Multidirectory commits will be merged if cvsprep is also installed, a diffstat(1) summary will be appended to the notification, and the working directory from which the files were committed will also be included. This line should be put in loginfo.

See INSTALLATION for more examples.


can't fork %s: %s

(Fatal) cvslog was unable to run a program that it wanted to run. This may result in no notification being sent or in information missing. Generally this means that the program in question was missing or cvslog couldn't find it for some reason.

can't open %s: %s

(Warning) cvslog was unable to open a file. For the modules file, this means that cvslog won't do any directory to module mapping. For files related to multidirectory commits, this means that cvslog can't gather together information about such a commit and will instead send an individual notification for the files affected in the current directory. (This means that some information may have been lost.)

can't remove %s: %s

(Warning) cvslog was unable to clean up after itself for some reason, and the temporary files from a multidirectory commit have been left behind in TMPDIR or /tmp.

can't save to %s: %s

(Warning) cvslog encountered an error saving information about a multidirectory commit and will instead send an individual notification for the files affected in the current directory.

invalid directory %s

(Warning) Something was strange about the given directory when cvslog went to use it to store information about a multidirectory commit, so instead a separate notification for the affected files in the current directory will be sent. This means that the directory was actually a symlink, wasn't a directory, or wasn't owned by the right user.

invalid config syntax: %s

(Warning) The given line in cvslog.conf was syntactically invalid. See CONFIGURATION for the correct syntax.

no %s given by CVS (no %{sVv}?)

(Fatal) The arguments CVS passes to cvslog should be the directory within the repository that's being changed and a list of files being changed with version information for each file. Something in that was missing. This error generally means that the invocation of cvslog in loginfo doesn't have the magic %{sVv} variable at the end but instead has no variables or some other variable like %s, or means that you're using a version of CVS older than 1.10.

no addresses specified

(Fatal) There was no address parameter in cvslog.conf and no -a options on the command line. At least one recipient address must be specified for the CVS commit notification.

sendmail exit status %d

(Fatal) sendmail exited with a non-zero status. This may mean that the notification message wasn't sent.

unable to determine the repository path

(Fatal) cvslog was unable to find the root of your CVS repository from the path by which it was invoked. See INSTALLATION for hints on how to fix this.

unrecognized config line: %s

(Warning) The given configuration parameter isn't one of the ones that cvslog knows about.


All files relative to $CVSROOT will be found by looking at the full path cvslog was invoked as and pulling off the path before CVSROOT/cvslog. If this doesn't work on your operating system, you'll need to edit this script to set $REPOSITORY.


Read for configuration directives if it exists. See CONFIGURATION.


Read to find the module a given file is part of. Rather than always giving the full path relative to $CVSROOT of the changed files, cvslog tries to find the module that that directory belongs to and replaces the path of that module with the name of the module in angle brackets. Modules are found by reading this file, looking at the last white-space-separated word on each line, and if it contains a /, checking to see if it is a prefix of the path to the files affected by a commit. If so, the first white-space-separated word on that line of modules is taken to be the affected module. The first matching entry is used.


Read to find the full name and e-mail address corresponding to a particular user. The full name is expected to be the fourth field colon-separated field and the e-mail address the fifth. Defaults derived from the system password file are used if these are not provided.


Information about multidirectory commits is read from and stored in this directory. This script will never create this directory (the helper script cvsprep that runs from commitinfo has to do that), but it will read and store information in it and when the commit message is sent, it will delete everything in this directory and remove the directory.

The first %d is the numeric UID of the user running cvslog. The second %d is the process group cvslog is part of. The process group is included in the directory name so that if you're running a shell that calls setpgrp() (any modern shell with job control should), multiple commits won't collide with each other even when done from the same shell.

If TMPDIR isn't set in the environment, /tmp is used for TMPDIR.


cvslog expects this file to contain the name of the final directory affected by a multidirectory commit. Each cvslog invocation will save the data that it's given until cvslog is invoked for this directory, and then all of the saved data will be combined with the data for that directory and sent out as a single notification.

This file must be created by a script such as cvsprep run from commitinfo. If it isn't present, cvslog doesn't attempt to combine multidirectory commits, even if -m is used.



Used to find cvs and diffstat when the -s option is in effect. If the diffstat configuration option is set, diffstat isn't searched for on the user's PATH, but cvs must always be found on the user's PATH in order for diffstat summaries to work.


If set, specifies the temporary directory to use instead of /tmp for storing information about multidirectory commits. Setting this to some private directory is recommended if you're doing CVS commits on a multiuser machine with other untrusted users due to the standard troubles with safely creating files in /tmp. (Note that other programs besides cvslog also use TMPDIR.)


Merging multidirectory commits requires creating predictably-named files to communicate information between different processes. By default, those files are created in /tmp in a directory created for that purpose. While this should be reasonably safe on systems that don't allow one to remove directories owned by other people in /tmp, since a directory is used rather than an individual file and since various sanity checks are made on the directory before using it, this is still inherently risky on a multiuser machine with a world-writeable /tmp directory if any of the other users aren't trusted.

For this reason, I highly recommend setting TMPDIR to some directory, perhaps in your home directory, that only you have access to if you're in that situation. Not only will this make cvslog more secure, it may make some of the other programs you run somewhat more secure (lots of programs will use the value of TMPDIR if set). I really don't trust the security of creating any predictably-named files or directories in /tmp and neither should you.

Multiple separate cvslog invocations in loginfo interact oddly with merging of multidirectory commits. The commit notification will be sent to the addresses and in the style configured for the last invocation of cvslog, even if some of the earlier directories had different notification configurations. As a general rule, it's best not to merge multidirectory commits that span separate portions of the repository with different notification policies.

cvslog doesn't support using commit_prep (which comes with CVS) as a commitinfo script to provide information about multidirectory commits because it writes files directly in /tmp rather than using a subdirectory.

Some file names simply cannot be supported correctly in CVS versions prior to 1.12.6 (with new-style info format strings turned on) because of ambiguities in the output from CVS. For example, file names beginning with spaces are unlikely to produce the correct output, and file names containing newlines will likely result in odd-looking mail messages.


There probably should be a way to specify the path to cvs for generating summaries and diffs, to turn off the automatic module detection stuff, to provide for transformations of the working directory (stripping the domain off the hostname, shortening directory paths in AFS), and to configure the maximum subject length. The cvsweb support could stand to be more customizable.

Many of the logging scripts out there are based on log_accum, which comes with CVS and uses a different output format for multidirectory commits. I prefer the one in cvslog, but it would be nice if cvslog could support either.

File names containing spaces may be wrapped at the space in the lists of files added, modified, or removed. The lists may also be wrapped in the middle of the appended version information if -i is used.

Multi-directory commit merging may mishandle file names that contain embedded newlines even with CVS version 1.12.6 or later due to the file format that cvslog uses to save the intermediate data.


Some parts of this script are horrible hacks because the entirety of commit notification handling in CVS is a horrible, undocumented hack. Better commit notification support in CVS proper would be welcome, even if it would make this script obsolete.


cvs(1), diffstat(1), cvsprep(1).

diffstat is at <>.

Current versions of this program are available from its web site at <>. cvsprep is available from this same location.


Russ Allbery <>.


Copyright 1998, 1999, 2000, 2001, 2002, 2003, 2004, 2006, 2007 Board of Trustees, Leland Stanford Jr. University.

This program is free software; you can redistribute it and/or modify it under the same terms as Perl itself.

Last spun 2022-02-06 from POD modified 2013-09-22