#!/usr/local/bin/perl -w # # 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 # ############################################################ # # Configurable options # ############################################################ # # Do cisco Systems, Inc. specific nonsense. # $cisco_systems = 1; # # Recipient of all mail messages # $mailto = "sw-notification@cisco.com"; ############################################################ # # Constants # ############################################################ $STATE_NONE = 0; $STATE_CHANGED = 1; $STATE_ADDED = 2; $STATE_REMOVED = 3; $STATE_LOG = 4; $LAST_FILE = "/tmp/#cvs.lastdir"; $CHANGED_FILE = "/tmp/#cvs.files.changed"; $ADDED_FILE = "/tmp/#cvs.files.added"; $REMOVED_FILE = "/tmp/#cvs.files.removed"; $LOG_FILE = "/tmp/#cvs.files.log"; $FILE_PREFIX = "#cvs.files"; $VERSION_FILE = "version"; $TRUNKREV_FILE = "TrunkRev"; $CHANGES_FILE = "Changes"; $CHANGES_TEMP = "Changes.tmp"; ############################################################ # # 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($all) = @_; local($wd, @files); $wd = `pwd`; chdir("/tmp"); opendir(DIR, "."); if ($all == 1) { push(@files, grep(/$id$/, readdir(DIR))); } else { push(@files, grep(/^$FILE_PREFIX.*$id$/, readdir(DIR))); } closedir(DIR); foreach (@files) { unlink $_; } chdir($wd); } 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 read_line { local($line); local($filename) = @_; open(FILE, "<$filename") || die("Cannot open file $filename.\n"); $line = ; close(FILE); chop($line); $line; } sub read_file { local(@text); local($filename, $leader) = @_; open(FILE, "<$filename") || return (); while () { chop; push(@text, sprintf(" %-10s %s", $leader, $_)); $leader = ""; } close(FILE); @text; } sub read_logfile { local(@text); local($filename, $leader) = @_; open(FILE, "<$filename") || die ("Cannot open log file $filename.\n"); while () { chop; push(@text, $leader.$_); } close(FILE); @text; } sub bump_version { local($trunkrev, $editnum, $version); $trunkrev = &read_line("$ENV{'CVSROOT'}/$repository/$TRUNKREV_FILE"); $editnum = &read_line("$ENV{'CVSROOT'}/$repository/$VERSION_FILE"); &write_line("$ENV{'CVSROOT'}/$repository/$VERSION_FILE", $editnum+1); $version = $trunkrev . "(" . $editnum . ")"; } sub build_header { local($version) = @_; local($header); local($sec,$min,$hour,$mday,$mon,$year) = localtime(time); $header = sprintf("%-8s %s %02d/%02d/%02d %02d:%02d:%02d", $login, $version, $year%100, $mon+1, $mday, $hour, $min, $sec); } sub do_changes_file { local($changes, $tmpchanges); local(@text) = @_; $changes = "$ENV{'CVSROOT'}/$repository/$CHANGES_FILE"; $tmpchanges = "$ENV{'CVSROOT'}/$repository/$CHANGES_TEMP"; if (rename($changes, $tmpchanges) != 1) { die("Cannot rename $changes to $tmpchanges.\n"); } open(CHANGES, ">$changes") || die("Cannot open $changes.\n"); open(TMPCHANGES, "<$tmpchanges") || die("Cannot open $tmpchanges.\n"); print(CHANGES join("\n", @text), "\n\n"); print(CHANGES ); close(CHANGES); close(TMPCHANGES); unlink($tmpchanges); } sub mail_notification { local($name, @text) = @_; open(MAIL, "| mail -s \"Source Repository Modification\" $name"); print(MAIL join("\n", @text)); close(MAIL); } ############################################################# # # Main Body # ############################################################ # # Initialize basic variables # $id = getpgrp(); $state = $STATE_NONE; $login = getlogin || (getpwuid($<))[0] || die("Unknown user $<.\n"); @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/) { $version = &bump_version if ($cisco_systems != 0); $header = &build_header($version); @text = (); push(@text, $header); push(@text, ""); push(@text, " ".$ARGV[0]); &do_changes_file(@text) if ($cisco_systems != 0); &mail_notification($mailto, @text); exit 0; } # # Iterate over the body of the message collecting information. # while () { chop; # Drop the newline 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/^[ \t\n]+//; # delete leading space s/[ \t\n]+$//; # delete trailing space push (@changed_files, split) if ($state == $STATE_CHANGED); push (@added_files, split) if ($state == $STATE_ADDED); push (@removed_files, split) if ($state == $STATE_REMOVED); push (@log_lines, $_) if ($state == $STATE_LOG); } # # 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. # 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("$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); # # Check whether this is the last directory. If not, quit. # $_ = &read_line("$LAST_FILE.$id"); exit 0 if (! grep(/$files[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. # # Get the full version number # $version = &bump_version if ($cisco_systems != 0); $header = &build_header($version); # # Produce the final compilation of the log messages # @text = (); push(@text, $header); push(@text, ""); for ($i = 0; ; $i++) { last if (! -e "$LOG_FILE.$i.$id"); 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", " ")); push(@text, ""); } if ($cisco_systems != 0) { @ddts = grep(/^CSCdi/, split(' ', join(" ", @text))); $text[0] .= " " . join(" ", @ddts); } # # Put the log message at the beginning of the Changes file and mail # out the notification. # &do_changes_file(@text) if ($cisco_systems != 0); &mail_notification($mailto, @text); &cleanup_tmpfiles(1); exit 0;