#! @PERL@
# -*-Perl-*-
#
# file: $ISIP_IFC/util/misc/cvs/isip_log_accum.pl
# version: $Id: isip_log_accum.pl 9611 2004-07-03 18:06:03Z picone $
# 
# 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 <hampton@cisco.com>
#
# hacked greatly by Greg A. Woods <woods@planix.com>

# Usage: log_accum.pl [-d] [-s] [-M module] [[-m mailto] ...] [[-R replyto] ...] [-f logfile]
#	-d		- turn on debugging
#	-m mailto	- send mail to "mailto" (multiple)
#	-R replyto	- set the "Reply-To:" to "replyto" (multiple)
#	-M modulename	- set module name to "modulename"
#	-f logfile	- write commit messages to logfile too
#	-s		- *don't* run "cvs status -v" for each file
#	-w		- show working directory with log message

#
#	Configurable options
#

# set this to something that takes a whole message on stdin
$MAILER	       = "rmail";

#
#	End user configurable options.
#

# Constants (don't change these!)
#
$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";

#
#	Subroutines
#

sub cleanup_tmpfiles {
    local($wd, @files);

    $wd = `pwd`;
    chdir("/tmp") || die("Can't chdir('/tmp')\n");
    opendir(DIR, ".");
    push(@files, grep(/^$FILE_PREFIX\..*\.$id$/, readdir(DIR)));
    closedir(DIR);
    foreach (@files) {
	unlink $_;
    }
    unlink $LAST_FILE . "." . $id;

    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_logfile {
    local($filename, @lines) = @_;

    open(FILE, ">$filename") || die("Cannot open log file $filename.\n");
    print FILE join("\n", @lines), "\n";
    close(FILE);
}

sub format_names {
    local($dir, @files) = @_;
    local(@lines);

    foreach $file (@files) {
	push(@lines, $dir . "/" . $file . "\n");
    }
    @lines;
}

sub append_names_to_file {
    local($filename, $dir, @files) = @_;

    if (@files) {
	open(FILE, ">>$filename") || die("Cannot open file $filename.\n");
	print FILE join("\n", &format_names($dir, @files)),"\n";
	close(FILE);
    }
}

sub read_line {
    local($line);
    local($filename) = @_;

    open(FILE, "<$filename") || die("Cannot open file $filename.\n");
    $line = <FILE>;
    close(FILE);
    chop($line);
    $line;
}

sub read_logfile {
    local(@text);
    local($filename, $leader) = @_;

    open(FILE, "<$filename");
    while (<FILE>) {
	chop;
	push(@text, $leader.$_);
    }
    close(FILE);
    @text;
}

sub read_listfile {
    local(@text);
    local($filename, $leader, $trailer) = @_;

    open(FILE, "<$filename");
    while (<FILE>) {
	chop;
	if (!/^\s*$/) {
	    push(@text, $leader.$_.$trailer);
	}
    }
    close(FILE);
    @text;
}

sub build_header {
    local($header);
    local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
    $header = sprintf("CVSROOT:\t%s\nModule name:\t%s\nRepository:\t%s\nChanges by:\t%s@%s\t%02d/%02d/%02d %02d:%02d:%02d",
		      $cvsroot,
		      $modulename,
		      $dir,
		      $login, $hostdomain,
		      $year%100, $mon+1, $mday,
		      $hour, $min, $sec);
}

sub mail_notification {
    
    $subject = shift(@_);
    local(@text) = @_;

    open(MAIL, "| $MAILER $mailto");
    $name = `whoami`;
    chop($name);
    print MAIL "From " .$name . "\n";
    print MAIL "Subject:   " . $subject . "\n";
    if ($replyto ne "") {
	print MAIL "Reply-To: " . $replyto . "\n";
    }
    print MAIL "\n\n";
    print MAIL join("\n", @text), "\n";
    close(MAIL);
}

sub write_commitlog {
    local($logfile, @text) = @_;

    open(FILE, ">>$logfile");
    print FILE join("\n", @text), "\n";
    close(FILE);
}

#
#	Main Body
#

# Initialize basic variables
#
$debug = 0;
$id = getpgrp();		# note, you *must* use a shell which does setpgrp()
$state = $STATE_NONE;
$replyto = "";

# parse command line arguments (file list is seen as one arg)
#
while (@ARGV) {
    $arg = shift @ARGV;

    if ($arg eq '-d') {
	$debug = 1;
	print STDERR "Debug turned on...\n";
    } elsif ($arg eq '-m') {
	if ($mailto eq '') {
	    $mailto = shift @ARGV;
	} else {
	    $mailto = $mailto . ", " . shift @ARGV;
	}
    } elsif ($arg eq '-R') {
	if ($replyto eq '') {
	    $replyto = shift @ARGV;
	} else {
	    $replyto = $replyto . ", " . shift @ARGV;
	}
    } else {
	($donefiles) && die("Too many arguments!  Check usage.\n");
	$donefiles = 1;
	$files_line = $arg;
	@files = split(/ /, $files_line);
    }
}
($mailto) || die("No mail recipient specified (use -m)\n");


$dir = shift(@files);

if ($debug) {
    print STDERR "dir    - ", $dir, "\n";
    print STDERR "files  - ", join(":", @files), "\n";
    print STDERR "id     - ", $id, "\n";
}

# Check for a new directory first.  This appears with files set as follows:
#
#    files_line - "path/name/newdir - New directory"
#
if ($files_line =~ /- New directory/) {
    local(@text);

    @text = ();

    while (<STDIN>) {
	chop;			# Drop the newline
	push(@text, $_);
    }

    &mail_notification($files_line, @text);

    exit 0;
}

# Check for an import command.  This appears with files set as follows:
#
#    files_line - "path/name - Imported sources"
#
if ($files_line =~ /Imported sources/) {
    local(@text);

    @text = ();

    while (<STDIN>) {
	chop;			# Drop the newline
	push(@text, $_);
    }

    &mail_notification($files_line, @text);

    exit 0;
}

# Iterate over the body of the message collecting information.
#
while (<STDIN>) {
    chop;			# Drop the newline

    if (/^In directory/) {
	next;
    }

    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; }

    if ($state != $STATE_LOG) {
	s/^[ \t\n]+//;		# delete leading whitespace
	s/[ \t\n]+$//;		# delete trailing whitespace
    }
    
    if ($state == $STATE_CHANGED) { push(@changed_files, split); }
    if ($state == $STATE_ADDED)   { push(@added_files,   split); }
    if ($state == $STATE_REMOVED) { push(@removed_files, split); }
    if ($state == $STATE_LOG)     { 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.
#
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);
    }
}

if ($debug) {
    print STDERR "Searching for log file index...";
}
# Find an index to a log file that matches this log message
#
for ($i = 0; ; $i++) {
    local(@text);
    
    if ($debug) {
	print "comparing log message $i\n";
    }

    last if (! -e "$LOG_FILE.$i.$id"); # the next available one
    @text = &read_logfile("$LOG_FILE.$i.$id", "");
    last if ($#text == -1);	# nothing in this file, use it
    last if (join(" ", @log_lines) eq join(" ", @text)); # it's the same log message as another
}
if ($debug) {
    print STDERR " found log file at $i.$id, now writing tmp files.\n";
}

# Spit out the information gathered in this pass.
#
&append_names_to_file("$CHANGED_FILE.$i.$id", $dir, @changed_files);
&append_names_to_file("$ADDED_FILE.$i.$id",   $dir, @added_files);
&append_names_to_file("$REMOVED_FILE.$i.$id", $dir, @removed_files);
if (! -e "$LOG_FILE.$i.$id") {
    &write_logfile("$LOG_FILE.$i.$id", @log_lines);
}

# Check whether this is the last directory.  If not, quit.
#
if ($debug) {
    print STDERR "Checking current dir against last dir.\n";
}
$last_dir = &read_line("$LAST_FILE.$id");

$full_dir = "$ENV{CVSROOT}/$dir";

if ($last_dir ne $full_dir) {
    if ($debug) {
	print STDERR sprintf("Current directory %s is not last directory %s.\n", $full_dir, $last_dir);
    }
    exit 0;
}
if ($debug) {
    print STDERR sprintf("Current directory %s is last directory %s -- all commits done.\n", $full_dir, $last_dir);
}

#
#	End Of Commits!
#

# 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.
#

#
# Produce the final compilation of the log messages
#
@text = ();
push(@text,"");

$subject = "";

for ($i = 0; ; $i++) {
    last if (! -e "$LOG_FILE.$i.$id"); # we're done them all!
    @lines = &read_logfile("$LOG_FILE.$i.$id", "");
    if ($#lines >= 0) {
	push(@text, @lines);
	push(@text, "");
    }
    @lines = &read_listfile("$CHANGED_FILE.$i.$id", "   ", " (modified)");
    if ($#lines >= 0) {
	push(@text, @lines);
	if ($subject eq "") {
	    $subject = $lines[0];
	}
    }
    @lines = &read_listfile("$ADDED_FILE.$i.$id", "   ", " (added)");
    if ($#lines >= 0) {
	push(@text, @lines);
	if ($subject eq "") {
	    $subject = $lines[0];
	}
    }
    @lines = &read_listfile("$REMOVED_FILE.$i.$id", "   ", " (removed)");
    if ($#lines >= 0) {
	push(@text, @lines);
	if ($subject eq "") {
	    $subject = $lines[0];
	}
    }
    push(@text, "");
}

# Mailout the notification.
#
$subject = "CVS checkin -- $subject";

&mail_notification($subject, @text);

# cleanup
#
if (! $debug) {
    &cleanup_tmpfiles();
}

exit 0;
