#!/usr/bin/perl
#
# This script came from the GNOME CVS repo, where it was last called martin-log.pl (Martin Baulig probably hacked it last)
# commit_prep2 must accompany it
#
# -*-Perl-*-
##
#
# This script, taken from the OpenBSD CVS repository.
#
# 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@web.net>

# Usage: log_accum.pl [-d] [-s] [-M module] [[-m mailto] ...] [-f logfile]
#	-d		- turn on debugging
#	-m mailto	- send mail to "mailto" (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

#
#	Configurable options
#



$MAILER      = "/usr/sbin/sendmail";		 # something with UCB sendmail syntax
$MAILFROM    = "Fedora CVS <fedora-extras-commits\@redhat.com>"; # mail comes from
$MAILREPLYTO = "fedora-maintainers\@redhat.com";		 # redirect replies to

$BUGZILLA_URL = "http://bugzilla.redhat.com/bugzilla/show_bug.cgi?id=";

$SUBJHEADER = "FE CVS";

# Constants (don't change these!)
#
$STATE_NONE    = 0;
$STATE_CHANGED = 1;
$STATE_ADDED   = 2;
$STATE_REMOVED = 3;
$STATE_LOG     = 4;

$ID            = getppid();

$LAST_FILE     = "/tmp/#cvs.lastdir.$ID";
$FILE_PREFIX   = "#cvs.files.$ID";

$CHANGED_FILE  = "/tmp/$FILE_PREFIX.changed";
$ADDED_FILE    = "/tmp/$FILE_PREFIX.added";
$REMOVED_FILE  = "/tmp/$FILE_PREFIX.removed";
$MODULES_FILE  = "/tmp/$FILE_PREFIX.modules";
$LOG_FILE      = "/tmp/$FILE_PREFIX.log";

$CVS_SILENT    = 0;

#
#	Subroutines
#

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

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

    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 format_names {
    local($dir, @files) = @_;
    local(@lines);

    if ($dir =~ /^\.\//) {
	$dir = $';
    }
    if ($dir =~ /\/$/) {
	$dir = $`;
    }
    if ($dir eq "") {
	$dir = ".";
    }

    $format = "\t%-" . sprintf("%d", length($dir) > 15 ? length($dir) : 15) . "s%s ";

    $lines[0] = sprintf($format, $dir, ":");

    if ($debug) {
	print STDERR "format_names(): dir = ", $dir, "; files = ", join(":", @files), ".\n";
    }
    foreach $file (@files) {
	if (length($lines[$#lines]) + length($file) > 65) {
	    $lines[++$#lines] = sprintf($format, " ", " ");
	}
	$lines[$#lines] .= $file . " ";
    }

    @lines;
}

sub format_lists {
    local(@lines) = @_;
    local(@text, @files, $lastdir);

    if ($debug) {
	print STDERR "format_lists(): ", join(":", @lines), "\n";
    }
    @text = ();
    @files = ();
    $lastdir = shift @lines;	# first thing is always a directory
    if ($lastdir !~ /.*\/$/) {
	die("Damn, $lastdir doesn't look like a directory!\n");
    }
    foreach $line (@lines) {
	if ($line =~ /.*\/$/) {
	    push(@text, &format_names($lastdir, @files));
	    $lastdir = $line;
	    @files = ();
	} else {
	    push(@files, $line);
	}
    }
    push(@text, &format_names($lastdir, @files));

    @text;
}

sub get_lines_for_module {
    local($module, @lines) = @_;
    local(@text, @files, $lastdir, $lastmodule);

    return if $#lines == -1;

    if ($debug) {
	print STDERR "get_lines_for_module($module): ", join(":", @lines), "\n";
    }
    @text = ();
    @files = ();
    $lastdir = shift @lines;	# first thing is always a directory
    if ($lastdir !~ /^(.*?)\/(.*)\/$/) {
	die("Damn, $lastdir doesn't look like a directory!\n");
    }
    $lastmodule = $1; $lastdir = $2;
    foreach $line (@lines) {
	if ($line =~ /.*\/$/) {
	    if ($lastmodule eq $module) {
		push(@text, &format_names($lastdir, @files));
	    }
	    $lastdir = $line;
	    if ($lastdir !~ /^(.*?)\/(.*)\/$/) {
		die("Damn, $lastdir doesn't look like a directory!\n");
	    }
	    $lastmodule = $1; $lastdir = $2;
	    @files = ();
	} else {
	    push(@files, $line);
	}
    }
    if ($lastmodule eq $module) {
	push(@text, &format_names($lastdir, @files));
    }

    @text;
}

sub get_files_for_module {
    local($module, @lines) = @_;
    local(@text, @files, $lastdir, $lastmodule);

    return if $#lines == -1;

    if ($debug) {
	print STDERR "get_lines_for_module($module): ", join(":", @lines), "\n";
    }
    @text = ();
    @files = ();
    $lastdir = shift @lines;	# first thing is always a directory
    if ($lastdir !~ /^(.*?)\/(.*)\/$/) {
	die("Damn, $lastdir doesn't look like a directory!\n");
    }
    $lastmodule = $1; $lastdir = $2;
    foreach $line (@lines) {
	if ($line =~ /.*\/$/) {
	    if ($lastmodule eq $module) {
		push(@text, @files);
	    }
	    $lastdir = $line;
	    if ($lastdir !~ /^(.*?)\/(.*)\/$/) {
		die("Damn, $lastdir doesn't look like a directory!\n");
	    }
	    $lastmodule = $1; $lastdir = $2;
	    @files = ();
	} else {
	    push(@files, $lastdir . "/" . $line);
	}
    }
    if ($lastmodule eq $module) {
	push(@text, @files);
    }

    @text;
}

sub check_cvssilent {
    local(@files) = @_;

    local (@regexps, $regexp, $file);

    open(FH, $cvsroot . "/CVSROOT/cvssilent");
    while(<FH>) {
	chomp;
	push @regexps, $_;
    }
    close(FH);

    #
    # If there's any file which does not match any of
    # the regexps, then the commit is not silent.
    #
    foreach $file (@files) {
	local ($silent) = 0;

	foreach $regexp (@regexps) {
	    $silent = 1 if $file =~ $regexp;
	}

	return 0 unless $silent;
    }

    return 1;
}

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

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

sub append_modules_file {
    local($filename, $modulename) = @_;

    open(FILE, ">>$filename") || die("Cannot open file $filename.\n");
    print FILE $modulename, "\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);
    if ($debug) {
	print STDERR "Read logfile $filename: (".join(":",@text).")\n";
    }
    @text;
}

sub build_header {
    local($module) = @_;
    local($header);
    local($sec,$min,$hour,$mday,$mon,$year) = localtime($^T);
    $header = sprintf("CVSROOT:\t%s\nModule name:\t%s\n",
		      $cvsroot,
		      $module);
    if (defined($branch)) {
	$header .= sprintf("Branch: \t%s\n",
		      $branch);
    }
    $header .= sprintf("Changes by:\t%s\t%02d/%02d/%02d %02d:%02d:%02d",
		      $login,
		      $year%100, $mon+1, $mday,
		      $hour, $min, $sec);
}

sub mail_notification {
    local($module, $name, $subjmodules, @text) = @_;

    $extra_headers = "";
    if ($CVS_SILENT) {
	$extra_headers .= "X-CVS-Silent: yes\n";
	$subject = "$SUBJHEADER [$login] (silent): $subjmodules";
    } else {
    	$subject = "$SUBJHEADER [$login]: $subjmodules";
    }
    
    $extra_headers .= "X-CVS-Module: $subjmodules\n";
    $extra_headers .= "\n";

    open(MAIL, "| $MAILER -t");
    print MAIL <<EOF ;
From: $MAILFROM
To: $name
Reply-To: $MAILREPLYTO
Subject: $subject
$extra_headers
EOF
    print MAIL join("\n", @text), "\n";
    close(MAIL);
}

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

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

# This is from Bonsai's CGI.pl:
# Quotify a string, suitable for putting into a URL.
sub url_quote {
    my($toencode) = (@_);
    $toencode=~s/([^a-zA-Z0-9_\-.])/uc sprintf("%%%02x",ord($1))/eg;
    return $toencode;
}

sub compose_url {
    my ($before_sec,$before_min,$before_hour,$before_mday,$before_mon,$before_year) = localtime($^T - 60);
    my ($after_sec,$after_min,$after_hour,$after_mday,$after_mon,$after_year) = localtime($^T + 60);

    my $url = "http://cvs.fedoraproject.org/bonsai/cvsquery.cgi?branch=";

    $url .= "&dir=";
    $url .= url_quote($modulename);
    $url .= "&who=";
    $url .= url_quote($login);
    $url .= "&date=explicit&mindate=";

    $url .= sprintf("%04d-%02d-%02d%%20", $before_year+1900, $before_mon+1, $before_mday);
    $url .= sprintf("%02d:%02d", $before_hour, $before_min);

    $url .= "&maxdate=";

    $url .= sprintf("%04d-%02d-%02d%%20", $after_year+1900, $after_mon+1, $after_mday);
    $url .= sprintf("%02d:%02d", $after_hour, $after_min);

    return $url;
}

# replaces instances of <l>bug #123456</l> with the bugzilla
# url that will show that bug 
#
# Args : [array pointer] log text, [string] base url
#
sub bugzillize_log
{
    my ($log, $url) = (@_);
    my @new;
    
    foreach $i (@$log) {
	   $i =~ s/bug \#([0-9]*)/bug \#$1 [$url$1]/g;
	   push (@new, $i);
    }

    @$log = @new;
}

#
#	Main Body
#

# Initialize basic variables
#
$debug = 0;
$state = $STATE_NONE;

$login = $ENV{'CVS_USERNAME'} || $ENV{'CVS_USER'} || getlogin || (getpwuid($<))[0] || "nobody";

chop($hostname = `hostname`);
if ($hostname !~ /\./) {
    chop($domainname = `domainname`);
    $hostdomain = $hostname . "." . $domainname;
} else {
    $hostdomain = $hostname;
}
$cvsroot = $ENV{'CVSROOT'};
$do_status = 1;
$modulename = "";

# 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') {
	$mailto = "$mailto " . shift @ARGV;
    } elsif ($arg eq '-M') {
	$modulename = shift @ARGV;
    } elsif ($arg eq '-s') {
	$do_status = 0;
    } elsif ($arg eq '-f') {
	($commitlog) && die("Too many '-f' args\n");
	$commitlog = shift @ARGV;
	$commitlog_arg = $commitlog;
    } else {
	($donefiles) && die("Too many arguments!  Check usage.\n");
	$donefiles = 1;
	@files = split(/ /, $arg);
    }
}

($mailto) || die("No -m mail recipient specified\n");

# for now, the first "file" is the repository directory being committed,
# relative to the $CVSROOT location
#
@path = split('/', $files[0]);

# XXX there are some ugly assumptions in here about module names and
# XXX directories relative to the $CVSROOT location -- really should
# XXX read $CVSROOT/CVSROOT/modules, but that's not so easy to do, since
# XXX we have to parse it backwards.
#
if ($modulename eq "") {
    $modulename = $path[0];	# I.e. the module name == top-level dir
}
open(FH, $cvsroot . "/CVSROOT/modules.silent");
while(<FH>) {
	chomp;
	if($modulename eq $_) {
		exit 0;
	}
}
close(FH);
if ($commitlog ne "") {
    $commitlog = $cvsroot . "/" . $modulename . "/" . $commitlog unless ($commitlog =~ /^\//);
}
if ($#path == 0) {
    $dir = ".";
} else {
    $dir = join('/', @path[1..$#path]);
}
$dir = $modulename . "/" . $dir . "/";

push (@modules, $modulename);

if ($debug) {
    print STDERR "module - ", $modulename, "\n";
    print STDERR "dir    - ", $dir, "\n";
    print STDERR "path   - ", join(":", @path), "\n";
    print STDERR "files  - ", join(":", @files), "\n";
    print STDERR "id     - ", $ID, "\n";
    print STDERR "login  - ", $login, "\n";
    print STDERR "CVS_USERNAME - ", $ENV{'CVS_USERNAME'}, "\n";
}

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

    @text = ();
    push(@text, &build_header($modulename));
    push(@text, "");
    push(@text, $files[0]);
    push(@text, "");

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

    &mail_notification($modulename, $mailto, $modulename, @text);

    if ($commitlog) {
	&write_commitlog($commitlog, @text);
    }

    exit 0;
}

# Iterate over the body of the message collecting information.
#
while (<STDIN>) {
    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; }
    if (/^Revision\/Branch/) { /^[^:]+:\s*(.*)/; $branch = $+; next; }
#    if (/Tag/) { /:.*: ([^ ]*)/; $branch = $1; next; }

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

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

    @text = ();
    push(@text, &build_header($modulename));
    push(@text, "");

    push(@text, "Log message:");
    while ($#log_lines > -1) {
	push (@text, "    " . $log_lines[0]);
	shift(@log_lines);
    }

    &mail_notification($modulename, $mailto, $modulename, @text);

    if ($commitlog) {
	&write_commitlog($commitlog, @text);
    }

    exit 0;
}

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

    last if (! -e "$LOG_FILE.$i"); # the next available one
    @text = &read_logfile("$LOG_FILE.$i", "");
    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, now writing tmp files.\n";
}

# Spit out the information gathered in this pass.
#
&append_names_to_file("$CHANGED_FILE.$i", $dir, @changed_files);
&append_names_to_file("$ADDED_FILE.$i",   $dir, @added_files);
&append_names_to_file("$REMOVED_FILE.$i", $dir, @removed_files);
&append_modules_file("$MODULES_FILE.$i", $modulename);
&write_logfile("$LOG_FILE.$i", @log_lines);

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

if ($_ ne $cvsroot . "/" . $files[0]) {
    if ($debug) {
	print STDERR sprintf("Current directory %s is not last directory %s.\n", $cvsroot . "/" .$files[0], $_);
    }
    exit 0;
}
if ($debug) {
    print STDERR sprintf("Current directory %s is last directory %s -- all commits done.\n", $files[0], $_);
}

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

@modules = ();
@changed = ();
@added   = ();
@removed = ();
@logs    = ();

for ($i = 0; ; $i++) {
    last if (! -e "$LOG_FILE.$i"); # we're done them all!
    push @modules, &read_logfile("$MODULES_FILE.$i", "");
    push @changed, &read_logfile("$CHANGED_FILE.$i", "");
    push @added,   &read_logfile("$ADDED_FILE.$i", "");
    push @removed, &read_logfile("$REMOVED_FILE.$i", "");
    push @logs,    &read_logfile("$LOG_FILE.$i", "");
}

{
    local (%modules) = ();

    foreach (@modules) {
	$modules{$_} = $_;
    }
    @modules = keys %modules;
}

if ($debug) {
    print STDERR "MODULES: |".join(":",@modules)."|\n";
}

#
# New produce the final compilation of the log message for each module
#

foreach $module (@modules) {
    local (@changed_texts, @added_texts, @removed_texts, @text);

    if ($debug) {
	print STDERR "CHANGED: |$module|".join(":",@changed)."|\n";
	print STDERR "ADDED:   |$module|".join(":",@added)."|\n";
	print STDERR "REMOVED: |$module|".join(":",@removed)."|\n";
    }

    push(@text, &build_header($module));
    push(@text, "");

    @changed_texts = &get_lines_for_module ($module, @changed);
    @added_texts   = &get_lines_for_module ($module, @added);
    @removed_texts = &get_lines_for_module ($module, @removed);

    @changed_files  = ();
    push @changed_files, &get_files_for_module ($module, @changed);
    push @changed_files, &get_files_for_module ($module, @added);
    push @changed_files, &get_files_for_module ($module, @removed);

    $CVS_SILENT = &check_cvssilent (@changed_files);

    if ($debug) {
	print STDERR "FILES: |".join(":",@changed_files)."|\n";
	print STDERR "SILENT: |$CVS_SILENT|\n";
    }

    if ($#changed_texts >= 0) {
	push(@text, "Modified files:");
	push(@text, @changed_texts);
    }
    if ($#added_texts >= 0) {
	push(@text, "Added files:");
	push(@text, @added_texts);
    }
    if ($#removed_texts >= 0) {
	push(@text, "Removed files:");
	push(@text, @removed_texts);
    }
    if ($#text >= 0) {
	push(@text, "");
    }
    if ($#logs >= 0) {
	push(@text, "Log message:");
	bugzillize_log (\@logs, $BUGZILLA_URL);
	push(@text, @logs);
	push(@text, "");
    }

# FIXME: Set up bonsai for Fedora   
#    $URL = &compose_url (@changed_files);
#    push(@text, "URL : $URL");
    push(@text, "");

    $submodules = $module;
    if ($module eq "rpms") {
	foreach $afile (@changed_files) {
		if($afile =~ m|^([^/]+)|s) { 
			$submods{$1} = 1;
		}
	}
	$submodules = join(" ", keys %submods);
    }

    &mail_notification($module, $mailto, $submodules, @text);

    if ($commitlog_arg) {
	if ($commitlog_arg =~ /^\//) {
	    $logfile = $commitlog_arg;
	} else {
	    $logfile = $cvsroot . "/" . $module . "/" . $commitlog_arg;
	}

	if ($debug) {
	    print STDERR "COMMITLOG: |$commitlog|$commitlog_arg|$logfile|\n";
	}

	&write_commitlog($logfile, @text);
    }
}

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

exit 0;
