#!/usr/bin/perl -w # include this script in your loginfo as: # /path/to/loginfo.pl sender@domain recipient@domain %{sVv} # # Copyright (c) 1999, 2000 Sascha Schumann # This makes some basic assumptions -- you are only checking # in to a single CVS module. # This also doesn't like files or directories with spaces in them. use strict; use Socket; use POSIX; $SIG{PIPE} = 'IGNORE'; my $last_file = "/var/cvs/lastdir"; my $summary = "/var/cvs/summary"; my $smtpserver = "127.0.0.1"; my $smtpport = 25; my $cvs = "/usr/bin/cvs"; my $cvsroot = $ENV{CVSROOT}."/"; # remove double trailing slash $cvsroot =~ s/\/\/$/\//; my $cvsusers = "/repository/CVSROOT/cvsusers"; # get the id of this process group for use in figuring out # whether this is the last directory with checkins or not my $id = getpgrp(); # the command line looks something like this for a normal commit: # ("user@example.com", "cvsuser", # "module changedfile,1.1,1.2 addedfile,NONE,1.1 removedfile,1.1,NONE") my $mailfrom = shift; my $mailto = $mailfrom; my $envaddr = $mailto; my $cvsuser = shift; my @args = split(" ", $ARGV[0]); my $directory = shift @args; # extract just the module name from the directory my $module = $directory; $module =~ s/\/.+$//; if ($cvsuser eq "changelog" && $module ne "php-gtk") { $envaddr = "php-cvs-daily-private\@lists.php.net"; $mailto = "php-cvs-daily\@lists.php.net"; } # bail when this is a new directory &bail if $args[0] eq '-' && "$args[1] $args[2]" eq 'New directory'; # bail if this is an import &bail if $args[0] eq '-' && $args[1] eq 'Imported'; # find out the last directory being processed open FC, "$last_file.$id" or die "last file does not exist"; my $last_directory = ; chop $last_directory; close FC; # remove the cvsroot from the front $last_directory =~ s/^$cvsroot//; # add our changed files to the summary open(FC, ">>$summary.$id") || die "cannot open summary file"; foreach my $arg (@args) { print FC "$directory/$arg\n"; } close(FC); # is this script already in the last changed directory? # exit if this isn't the last directory &bail if($last_directory ne $directory); # get the log message and tag -- we throw away everything from STDIN # before a line that begins with "Log Message" my ($logmsg,$tag) = &get_log_message(); # now we fork off into the background and generate the email exit 0 if(fork() != 0); $| = 1; #print "Reading summary file\n"; open(FC, "<$summary.$id"); my (@added_files, @removed_files, @modified_files, @modified_files_info); while () { chop; my ($file, $old, $new) = split(","); if($old eq "NONE") { push @added_files, $file; } elsif($new eq "NONE") { push @removed_files, $file; } else { push @modified_files, $file; push @modified_files_info, [ $file, $old, $new ]; } } close FC; #print "Unlinking helper files\n"; # clean up a little bit unlink("$summary.$id"); unlink("$last_file.$id"); #print "Running rdiff\n"; # build a diff (and new files) if necessary my $diffmsg = ''; foreach my $info (@modified_files_info) { my ($file, $old, $new) = @$info; if ($file =~ m|TexConvert/tt.dynamic| || $file =~ m|foxr/londtest| || $file =~ m|purdue| || $file =~ m|vcu/| || $file =~ m|raeburn/private| ) { $diffmsg='Diffs for '.$file.' not shown.'."\n"; next; } open(LOG, "$cvs -Qn rdiff -r $old -r $new -u $file|") || die; while() { s/\r\n/\n/; $diffmsg .= $_; } close(LOG); } # add the added files foreach my $file (@added_files) { next if $file =~ /\.(gif|jpe|jpe?g|pdf|png|exe|class|tgz|tar.gz|jar)$/i or $file !~ /\./; $diffmsg .= "\nIndex: $file\n+++ $file\n"; open(LOG, "$cvs -Qn checkout -p -r1.1 $file |") || die; while() { s/\r\n/\n/; $diffmsg .= $_; } close(LOG); } #print "Building commit email\n"; my $subj_tag = $tag ? "($tag)" : ''; my $body_tag = $tag ? "(Branch: $tag)" : ''; # build our email my $msg = ""; if($#added_files ne -1) { $msg .= "\n Added files: $body_tag"; $msg .= &build_list(@added_files); $body_tag = ''; } if($#removed_files ne -1) { $msg .= "\n Removed files: $body_tag"; $msg .= &build_list(@removed_files); $body_tag = ''; } if($#modified_files ne -1) { $msg .= "\n Modified files: $body_tag"; $msg .= &build_list(@modified_files); $body_tag = ''; } my $subj = ""; my %dirfiles; my @dirs = &get_dirs(@added_files, @removed_files, @modified_files); foreach my $dir (@dirs) { $subj .= "$dir @{ $dirfiles{$dir} } "; } my $msgid = "Message-ID: \n"; my $from; if (open FD, $cvsusers) { while() { chop; if (m/^$cvsuser:(.+?):(.+)$/) { $from = "\"$1\" <$2>"; } } close(FD); } $from ||= "$cvsuser <$mailfrom>"; # "Reply-to: $mailto\n". # "Date: ".localtime()."\n". my (@DAYABBR) = qw(Sun Mon Tue Wed Thu Fri Sat); my (@MONABBR) = qw(Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec); my (@gmtime) = gmtime(); my $rfc822date = sprintf("Date: %s, %02d %s %d %02d:%02d:%02d -0000\n", $DAYABBR[$gmtime[6]], $gmtime[3], $MONABBR[$gmtime[4]], $gmtime[5] + 1900, $gmtime[2], $gmtime[1], $gmtime[0]); no strict; # quiet warnings after here my $email; my $common_header = "". "From: $from\n". "To: $mailto\n". $msgid. $rfc822date. "Subject: cvs: $module$subj_tag $subj\n"; my $common_body = "". "$cvsuser\t\t".localtime()." EDT\n". "$msg". " Log:\n". &indent($logmsg,2)."\n"; my $boundary = $cvsuser.time(); if (length($diffmsg) > 8000) { my $now = POSIX::strftime("%Y%m%d%H%M%S", localtime); $email = $common_header. "MIME-Version: 1.0\n". "Content-Type: multipart/mixed; boundary=\"$boundary\"\n". "\n". "This is a MIME encoded message\n\n". "--$boundary\n". "Content-Type: text/plain\n". "\n". $common_body. "--$boundary\n". "Content-Type: text/plain\n". "Content-Disposition: attachment; filename=\"$cvsuser-$now.txt\"\n". "\n". "$diffmsg\n". "--$boundary--\n"; } else { $email = $common_header. "\n". $common_body. "$diffmsg\n"; } $email =~ s/\r//g; $email =~ s/\n/\r\n/g; # send our email print "Mailing the commit email to $mailto\n"; #print $email; my $paddr = sockaddr_in($smtpport, inet_aton($smtpserver)); socket(SOCK, PF_INET, SOCK_STREAM, 0) || die "socket failed"; connect(SOCK, $paddr) || die "connect $smtpserver:$smtpport failed"; select(SOCK); $|=1; print "HELO cvsserver\r\n". "MAIL FROM:\r\n" . "RCPT TO:<$envaddr>\r\n" . "DATA\r\n". "$email\r\n". ".\r\n". "QUIT\r\n"; while() { alarm(20); }; close(SOCK); exit 0; sub get_log_message { my ($logmsg, $tag); while () { $logmsg .= $_ if defined $logmsg; if (/^Log Message/) { $logmsg = ""; } if (/^\s+Tag:\s+(\w+)/) { $tag = $1; } } return ($logmsg, $tag); } sub build_list { my(@arr) = @_; my($curdir, $curlen, $msg); $msg = ""; $curdir = ""; foreach (@arr) { /^(.*)\/([^\/]+)$/; my $dir = $1; my $file = $2; if($dir ne $curdir) { $curdir = $dir; $msg .= "\n /$curdir\t"; $curlen = length($curdir) + 5; } if(($curlen + length($file)) > 70) { $msg .= "\n ".sprintf("%-".length($curdir)."s", "")."\t"; $curlen = length($curdir) + 5; } $msg .= $file." "; $curlen += length($file) + 1; } $msg .= "\n"; return $msg; } sub get_dirs { my @files = sort @_; foreach my $file (@files) { (my $dir = $file) =~ s#[^/]+$##; $dir =~ s/^$module//; $dir =~ s/(.+)\//$1/; $file =~ s#^.+/(.+)$#$1#; push @{ $dirfiles{$dir} }, $file; } return sort keys %dirfiles; } sub indent { my ($msg,$nr) = @_; my $s = " " x $nr; $msg =~ s/\n/\n$s/g; return $s.$msg; } sub trim { my ($x) = @_; $x =~ s/^\s+//; $x =~ s/\s+$//; return $x; } # eat STDIN (to avoid parent getting SIGPIPE) and exit with supplied exit code sub bail { my @toss = ; exit @_; }