#! /usr/bin/perl # cvs-mailcommit - Send CVS commitments via mail # Copyright (c) 1998,2004 Martin Schulze # # $Id: cvs-mailcommit,v 1.2 2004/08/09 16:04:20 joey Exp $ # # This program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111, USA. # For testing, call this program like: # echo "Logmsg" | CVSROOT=/cvs/webwml ./cvs-mailcommit -m $LOGNAME --from "$LOGNAME@`hostname -f`" --cvs 'CVSROOT loginfo,1.10,1.11' --diff # To extract the inline documentation, run the following command: # pod2html cvs-mailcommit > cvs-mailcommit.1 # To read this documentation, just type # perldoc cvs-mailcommit use Getopt::Long; =head1 NAME cvs-mailcommit - Send CVS commitments via mail =head1 SYNOPSIS B [options] [version*] =head1 DESCRIPTION B is a helper application to CVS to help people keep track of CVS repositories via mail. It is hooked into the CVS system via the B/I file. It will read modification information from CVS via B and require version information via the commandline. B will send differences of modified files or entire new files via mail to the denoted address. You'll have to hook it into CVS for each module in a repository that you want to monitor via mail. See B below =cut $sendmail = "/usr/lib/sendmail"; $rcsdiff = "rcsdiff"; $co = "co"; $opt_xloop = ''; $opt_cvs = ''; $opt_from = ''; $opt_replyto = ''; $opt_approved = ''; $opt_diff = 0; $opt_full = 0; $opt_maxlines = 400; @opt_mailto = (); $opt_cvsroot = ''; $opt_dir = ''; # A way to alter the default settings $cfg = "/etc/cvs-mailcommit.pl"; require $cfg if (-r $cfg); my %options = ( 'mailto|m=s' => \@opt_mailto, 'diff|d' => \$opt_diff, 'full' => \$opt_full, 'xloop=s' => \$opt_xloop, 'from=s' => \$opt_from, 'replyto=s' => \$opt_replyto, 'approved=s' => \$opt_approved, 'maxlines=i' => \$opt_maxlines, 'cvs=s' => \$opt_cvs, 'root=s' => \$opt_cvsroot, 'dir=s' => \$opt_dir, ); # Filter out new directory creations # Arguments: '- New directory' NONE NONE # $pivot = 0; while ($pivot <= $#ARGV) { splice (@ARGV, $pivot, $pivot+2) if ($ARGV[$pivot] eq '- New directory'); $pivot++; } GetOptions (%options); =head1 OPTIONS This program supports the following arguments. When the arguments don't make sense the program won't do anything. =over 6 =item B<-m> I
, B<--mailto> I
Send the mail to the specified I
. This option can be specified on the commandline multiple times. =item B<--diff> Generate unified diffs for all modified files. =item B<--full> Include the entire fill for newly created files that were added to the repository. =item B<--maxlines> I You can specify how many lines per file may be quoted in the resulting mail. Limiting the number of quoted lines may be useful for repositories with excessive changes that are similar. The default is to copy 400 lines. =item B<--xloop> I
Include a special B header in the generated mail. This is intended for users to be able to filter CVS mails by a common header line. The line will look like X-Loop: I
=item B<--from> I
Generate a B-line of the form From: CVS User foo > Otherwise the local user the program runs under will be used instead of I
. With this parameter you can ensure that all such mails will be sent with the same from line, which may be useful for moderated lists or some where only subscribers may write. =item B<--replyto> I
Try to redirect replies to CVS mails to another address by setting proper header lines such as Reply-To: I
Mail-Followup-To: I
=item B<--approved> I
Include a special B-line in the mail. This header is intended for moderated mailing-lists to pass the SmartList moderation mechanism. The created header will look like Approved: I
=item B<--cvs> I This option carries the CVS version info from CVS into the program. It will be added automatically by CVS. When installing this program into the B file of a CVS repository, you will need to add the following option: B<--cvs> %{sVv} If you want to test this program manually you'll have to supply the module directory and the modified files including the old and new versions. This will look like --cvs 'CVSROOT loginfo,1.1,1.2' This option is only usful for old-style CVS format strings (i.e. prior to CVS 1.12.6). You can continue using old-style format strings with newer cvs if you write %1{sVv} and set B=I in CVSROOT/config. For more recent versions of CVS you should, however, use the new --root and --dir arguments and place %{sVv} at the end of the commandline. =item B<--root> I Specify the CVS repository directory. This is normally done by the CVS server. Prior to version 1.12 the repository was transmitted to the log processor via the B environment variable. The parameter to this option is normally filled in by CVS using the %r format string: B<--root> %r =item B<--dir> I Specify the directory within the CVS repository in which directories or file were added or modified. The parameter to this option is normally filled in by CVS using the %p format string: B<--dir> %p =back =cut exit 0 if (!@opt_mailto); # $opt_cvs looks like # foo/waz bar,1.4,1.5 foo,1.2,1.3 # or # foo gnatz,1.3,1.4 # if ($opt_cvs) { @cvs_arr = split (/ /,$opt_cvs); $opt_dir = $cvs_arr[0]; $module_dir = $ENV{'CVSROOT'} . "/" . shift(@cvs_arr); } else { if ($opt_cvsroot) { if ($opt_dir) { $module_dir = $opt_cvsroot ."/". $opt_dir; } else { $module_dir = $opt_cvsroot; } while ($#ARGV >= 2) { push (@cvs_arr, shift (@ARGV) .",". shift (@ARGV) .",". shift (@ARGV)); } while ($#ARGV > -1) { printf "Unknown argument '%s', deleting.\n", shift (@ARGV); } } else { print STDERR "No --cvs and no --root (and --dir) specified, aborting.\n"; } } $login = $ENV{'CVS_USER'} || $ENV{'LOGNAME'}|| getlogin || (getpwuid($<))[0] || "nobody"; $logname = $ENV{'LOGNAME'}|| getlogin || (getpwuid($<))[0] || $ENV{'CVS_USER'} || "nobody"; if (open (M, "|$sendmail -t")) { # if (open (M, ">-")) { printf M "To: %s\n", join(",",@opt_mailto); printf M "Subject: CVS %s\n", $opt_dir; if ($opt_from) { printf M "From: \"CVS User %s\" <%s>\n", $login, $opt_from; } else { printf M "From: \"CVS User %s\" <%s>\n", $login, $logname; } if ($opt_replyto) { printf M "Reply-To: %s\n", $opt_replyto; printf M "Mail-Followup-To: %s\n", $opt_replyto; } printf M "Approved: %s\n", $opt_approved if ($opt_approved); printf M "X-Loop: %s\n", $opt_xloop if ($opt_xloop); print M "\n"; print M while (<>); if ($opt_diff) { print M "\n"; foreach $cstr (@cvs_arr) { ($file,$oldver,$newver) = split (/,/,$cstr); next if ($oldver eq "" || $newver eq ""); if ($oldver ne "NONE") { # print "$rcsdiff -r$oldver -r$newver -u $module_dir/$file|\n"; if (open (R, "$rcsdiff -r$oldver -r$newver -u $module_dir/$file 2>/dev/null |")) { $lines = 0; while () { $lines++; print M if ($lines <= $opt_maxlines); } close (R); printf M "\n[%d lines skipped]\n", $lines - $opt_maxlines if ($lines > $opt_maxlines); } } } } # Process new files if ($opt_full) { print M "\n"; foreach $cstr (@cvs_arr) { ($file,$oldver,$newver) = split (/,/,$cstr); next if ($oldver ne "NONE" || $newver ne "1.1"); my ($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = gmtime(time); my $date = sprintf ("%4d/%02d/%02d %02d:%02d:%02d", $year+1900, $mon+1, $mday, $hour,$min,$sec); if (open (R, "$co -p -r$newver $module_dir/$file 2>/dev/null |")) { printf M "--- %s/%s\t%s\tNONE\n", $module_dir, $file, $date; printf M "+++ %s/%s\t%s\t%s\n", $module_dir, $file, $date, $newver; while () { $lines++; print M if ($lines <= $opt_maxlines); } close (R); printf M "\n[%d lines skipped]\n", $lines - $opt_maxlines if ($lines > $opt_maxlines); } else { print M "Cannot open $co, huh, $!\n"; } } } close (M); } =head1 CONFIGURATION FILE B supports an additional configuration file I so that hard-coded default values can be overwritten. The file is included via require and hence needs to contain valid Perl code, which evaluates to I. I.e. place B<1;> at the end of the file. =head1 INSTALLATION The B/I file controls where B log information is sent. The first entry on a line is a regular expression which must match the directory (alias module) that the change is being made to, relative to the $B. If a match is found, then the remainder of the line is a filter program that should expect log information on its standard input. A I line looks like --