#!/usr/bin/perl # # Perl filter to handle the log messages from the checkin of files in # a directory. This script will group the lists of files by log # message, and mail a single consolidated log message at the end of # the commit. # # This file assumes a pre-commit checking program that leaves the # names of the first and last commit directories in a temporary file. # # Contributed by David Hampton # Roy Fielding removed useless code and added log/mail of new files # Jon Jensen messed around with a bunch of things # # Modifications by Manuel Odendahl to mail to ARGV[] ############################################################ # # Configurable options # ############################################################ # # Where do you want the RCS ID and delta info? # 0 = none, # 1 = in mail only, # 2 = rcsids in both mail and logs. # $rcsidinfo = 2; ############################################################ # # Constants # ############################################################ $STATE_NONE = 0; $STATE_CHANGED = 1; $STATE_ADDED = 2; $STATE_REMOVED = 3; $STATE_LOG = 4; $TMPDIR = $ENV{TMPDIR} || '/tmp'; $FILE_PREFIX = '#cvs.'; $LAST_FILE = "$TMPDIR/${FILE_PREFIX}lastdir"; $CHANGED_FILE = "$TMPDIR/${FILE_PREFIX}files.changed"; $ADDED_FILE = "$TMPDIR/${FILE_PREFIX}files.added"; $REMOVED_FILE = "$TMPDIR/${FILE_PREFIX}files.removed"; $LOG_FILE = "$TMPDIR/${FILE_PREFIX}files.log"; $BRANCH_FILE = "$TMPDIR/${FILE_PREFIX}files.branch"; $SUMMARY_FILE = "$TMPDIR/${FILE_PREFIX}files.summary"; $COUNT_FILE = "$TMPDIR/${FILE_PREFIX}files.count"; $CVSROOT = $ENV{CVSROOT}; ############################################################ # # Subroutines # ############################################################ sub format_names { local($dir, @files) = @_; local(@lines); $lines[0] = sprintf(" %-08s", $dir); foreach $file (@files) { if (length($lines[$#lines]) + length($file) > 60) { $lines[++$#lines] = sprintf(" %8s", " "); } $lines[$#lines] .= " ".$file; } @lines; } sub cleanup_tmpfiles { local(@files); opendir(DIR, $TMPDIR); push(@files, grep(/^${FILE_PREFIX}.*\.${id}$/, readdir(DIR))); closedir(DIR); foreach (@files) { unlink "$TMPDIR/$_"; } } sub write_logfile { local($filename, @lines) = @_; open(FILE, ">$filename") || die ("Cannot open log file $filename: $!\n"); print(FILE join("\n", @lines), "\n"); close(FILE); } sub append_to_file { local($filename, $dir, @files) = @_; if (@files) { local(@lines) = &format_names($dir, @files); open(FILE, ">>$filename") || die ("Cannot open file $filename: $!\n"); print(FILE join("\n", @lines), "\n"); close(FILE); } } sub write_line { local($filename, $line) = @_; open(FILE, ">$filename") || die("Cannot open file $filename: $!\n"); print(FILE $line, "\n"); close(FILE); } sub append_line { local($filename, $line) = @_; open(FILE, ">>$filename") || die("Cannot open file $filename: $!\n"); print(FILE $line, "\n"); close(FILE); } sub read_line { local($filename) = @_; local($line); open(FILE, "<$filename") || die("Cannot open file $filename: $!\n"); $line = ; close(FILE); chomp($line); $line; } sub read_file { local($filename, $leader) = @_; local(@text) = (); open(FILE, "<$filename") || return (); while () { chomp; push @text, sprintf("%-10s", $leader) . $_; } close FILE; @text; } sub read_logfile { local($filename, $leader) = @_; local(@text) = (); open(FILE, "<$filename") || die ("Cannot open log file $filename: $!\n"); while () { chomp; push(@text, $leader.$_); } close(FILE); @text; } # # do a 'cvs -Qn status' on each file in the arguments, and extract info. # sub change_summary { local($out, @filenames) = @_; local(@revline); local($file, $rev, $rcsfile, $line); while (@filenames) { $file = shift @filenames; next if $file eq ""; open(RCS, "-|") || exec 'cvs', '-Qn', 'status', $file; $rev = ""; $delta = ""; $rcsfile = ""; while () { if (/^[ \t]*Repository revision/) { chomp; @revline = split(' ', $_); $rev = $revline[2]; $rcsfile = $revline[3]; $rcsfile =~ s,^$CVSROOT/,,; $rcsfile =~ s/,v$//; } } close(RCS); if ($rev ne '' && $rcsfile ne '') { open(RCS, "-|") || exec 'cvs', '-Qn', 'log', "-r$rev", $file; while () { if (/^date:/) { chomp; $delta = $_; $delta =~ s/^.*;//; $delta =~ s/^[\s]+lines://; } } close(RCS); } $diff = "\n\n"; # If this is a binary file, don't try to report a diff; not only is # it meaningless, but it also screws up some mailers. We rely on # Perl's 'is this binary' algorithm; it's pretty good. if (-B $file) { $filetype = `file $file`; chomp $filetype; $diff .= "<<$filetype>>\n\n"; } # Get the differences between this and the previous revision, # being aware that new files always have revision '1.1' and # new branches always end in '.n.1'. elsif ($rev =~ /^(.*)\.([0-9]+)$/) { $prev = $2 - 1; $prev_rev = $1 . '.' . $prev; $prev_rev =~ s/\.[0-9]+\.0$//; # Truncate if first rev on branch $diff .= "rev $rev, prev_rev $prev_rev\n"; # if ($rev eq '1.1' || $prev_rev eq '1.1') { if ($rev eq '1.1') { open(DIFF, "-|") || exec 'cvs', '-Qn', 'update', '-p', "-r$rev", $file; $diff .= "Index: $file\n==================================" . "=================================\n"; } else { open(DIFF, "-|") || exec 'cvs', '-Qn', 'diff', '-u', "-r$prev_rev", "-r$rev", $file; } while () { $diff .= $_; } close(DIFF); $diff .= "\n\n"; } &append_line($out, sprintf("%-9s%-12s%s", $rev, $delta, $rcsfile)); &append_line($out, $diff); } } sub build_header { my $header; my ($sec,$min,$hour,$mday,$mon,$year) = gmtime(); $header = "User: $login\n"; $header .= "Date: " . sprintf( "%04d-%02d-%02d %02d:%02d:%02d GMT", $year+1900, $mon+1, $mday, $hour, $min, $sec ); } sub do_changes_file { # we don't use this for Interchange at the moment return; local($category, @text) = @_; local($changes); $changes = "$CVSROOT/CVSROOT/commitlogs/$category"; if (open(CHANGES, ">>$changes")) { print CHANGES $_, "\n" for @text; print CHANGES "\n"; close(CHANGES); } else { warn "Cannot open $changes: $!\n"; } } sub mail_notification { my (@text) = @_; my ($repos, $file, $subject); $file = $ARGV[0]; $repos = $1 . ' - ' if $file =~ s:^([^/ ]+)[/ ]::; if ($ARGV[0] =~ /New directory/) { $file =~ s/\s.*//; $subject = $repos . $login . ' added directory ' . $file; } else { $file =~ s:\s+:/:; $subject = $repos . $login . ' modified '; my $count = scalar( &read_logfile("$COUNT_FILE.$id", "") ); $subject .= ($count > 1) ? "$count files" : $file; } $subject =~ s/"/\\"/g; $subject = "[CVS] $subject"; for my $to (splice(@ARGV, 1)) { print "Mailing the commit message to '$to' ...\n"; open MAIL, qq{| /usr/bin/mail -s "$subject" $to}; print MAIL $_, "\n" for @text; close MAIL; } # open(MAIL, qq{| /usr/bin/Mail -s "$subject" $MAIL_TO}); # open(MAIL, "| /usr/exim/bin/exim -t -odq"); # print MAIL "To: $MAIL_TO\n"; # print MAIL "Subject: cvs commit: $ARGV[0]\n"; # print MAIL "\n"; } ############################################################# # # Main Body # ############################################################ # # Setup environment # umask (002); # # Initialize basic variables # $id = getpgrp(); $state = $STATE_NONE; $login = $ENV{USER} || getlogin || (getpwuid($<))[0] || sprintf("uid#%d",$<); @files = split(' ', $ARGV[0]); @path = split('/', $files[0]); $repository = $path[0]; if ($#path == 0) { $dir = "."; } else { $dir = join('/', @path[1..$#path]); } #print("ARGV - ", join(":", @ARGV), "\n"); #print("files - ", join(":", @files), "\n"); #print("path - ", join(":", @path), "\n"); #print("dir - ", $dir, "\n"); #print("id - ", $id, "\n"); ########################## # # Check for a new directory first. This will always appear as a # single item in the argument list, and an empty log message. # if ($ARGV[0] =~ /New directory/) { &append_line("$COUNT_FILE.$id", "NEW-DIRECTORY"); $header = &build_header; @text = (); push(@text, $header); push(@text, ""); push(@text, $ARGV[0]); &do_changes_file($mlist, @text); &mail_notification(@text); exit 0; } # add filenames for this pass for future counting &append_line("$COUNT_FILE.$id", join("\n", @files[1..$#files])); # # Iterate over the body of the message collecting information. # while () { chomp; if (/^Revision\/Branch:/) { s,^Revision/Branch:,,; push (@branch_lines, split); next; } # next if (/^[ \t]+Tag:/ && $state != $STATE_LOG); if (/^Modified Files/) { $state = $STATE_CHANGED; next; } if (/^Added Files/) { $state = $STATE_ADDED; next; } if (/^Removed Files/) { $state = $STATE_REMOVED; next; } if (/^Log Message/) { $state = $STATE_LOG; next; } s/\s+$//; push (@changed_files, split) if ($state == $STATE_CHANGED); push (@added_files, split) if ($state == $STATE_ADDED); push (@removed_files, split) if ($state == $STATE_REMOVED); if ($state == $STATE_LOG) { if (/^PR:$/i || /^Reviewed by:$/i || /^Submitted by:$/i || /^Obtained from:$/i) { next; } push (@log_lines, $_); } } # # Strip leading and trailing blank lines from the log message. Also # compress multiple blank lines in the body of the message down to a # single blank line. # (Note, this only does the mail and changes log, not the rcs log). # while ($#log_lines > -1) { last if ($log_lines[0] ne ""); shift(@log_lines); } while ($#log_lines > -1) { last if ($log_lines[$#log_lines] ne ""); pop(@log_lines); } for ($i = $#log_lines; $i > 0; $i--) { if (($log_lines[$i - 1] eq "") && ($log_lines[$i] eq "")) { splice(@log_lines, $i, 1); } } # # Find the log file that matches this log message # for ($i = 0; ; $i++) { last if (! -e "$LOG_FILE.$i.$id"); @text = &read_logfile("$LOG_FILE.$i.$id", ""); last if ($#text == -1); last if (join(" ", @log_lines) eq join(" ", @text)); } # # Spit out the information gathered in this pass. # &write_logfile("$LOG_FILE.$i.$id", @log_lines); &append_to_file("$BRANCH_FILE.$i.$id", $dir, @branch_lines); &append_to_file("$ADDED_FILE.$i.$id", $dir, @added_files); &append_to_file("$CHANGED_FILE.$i.$id", $dir, @changed_files); &append_to_file("$REMOVED_FILE.$i.$id", $dir, @removed_files); if ($rcsidinfo) { &change_summary("$SUMMARY_FILE.$i.$id", (@changed_files, @added_files)); } # # Check whether this is the last directory. If not, quit. # if (-e "$LAST_FILE.$id") { $_ = &read_line("$LAST_FILE.$id"); $tmpfiles = $files[0]; $tmpfiles =~ s,([^a-zA-Z0-9_/]),\\$1,g; if (! grep(/$tmpfiles$/, $_)) { print "More commits to come...\n"; exit 0 } } # # This is it. The commits are all finished. Lump everything together # into a single message, fire a copy off to the mailing list, and drop # it on the end of the Changes file. # $header = &build_header(); # # Produce the final compilation of the log messages # @text = (); push(@text, $header); for ($i = 0; ; $i++) { last if (! -e "$LOG_FILE.$i.$id"); push(@text, &read_file("$BRANCH_FILE.$i.$id", "Branch:")); push(@text, &read_file("$CHANGED_FILE.$i.$id", "Modified:")); push(@text, &read_file("$ADDED_FILE.$i.$id", "Added:")); push(@text, &read_file("$REMOVED_FILE.$i.$id", "Removed:")); push(@text, "Log:"); push(@text, &read_logfile("$LOG_FILE.$i.$id", "")); if ($rcsidinfo == 2) { if (-e "$SUMMARY_FILE.$i.$id") { push(@text, ""); push(@text, "Revision Changes Path"); push(@text, &read_logfile("$SUMMARY_FILE.$i.$id", "")); } } push(@text, ""); } # # Append the log message to the commitlogs/ file # &do_changes_file($mlist, @text); # # Now generate the extra info for the mail message.. # if ($rcsidinfo == 1) { $revhdr = 0; for ($i = 0; ; $i++) { last if ! -e "$LOG_FILE.$i.$id"; if (-e "$SUMMARY_FILE.$i.$id") { if (!$revhdr++) { push @text, "Revision Changes Path"; } push @text, &read_logfile("$SUMMARY_FILE.$i.$id", ""); } } if ($revhdr) { push @text, ""; # consistency... } } # # Mail out the notification. # &mail_notification(@text); &cleanup_tmpfiles; exit 0; ## # Local variables: # mode: perl # tab-width: 2 # perl-indent-level: 2 # indent-tabs-mode: nil # End: ##