#!/usr/bin/perl
# -*-Perl-*-
#
# $Id: log_accum,v 1.20 2002/10/01 07:22:57 villate Exp $
# 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
#       -G database     - interface to Gnats
#	-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
#       -T text         - use TEXT in temp file names.
#       -C [reponame]   - Generate cvsweb URLS; must be run using %{sVv}
#                         format string.
#       -U URL          - Base URL for cvsweb if -C option (above) is used.
#       -D              - generate diffs as part of the notification mail
#       -u              - Unified Diff instead of context
#       -r TAG		- operate only on changes with tag TAG
#       -r BRANCH	- operate only on changes in branch TAG
#			  Use -r "" for "only changes with no tag or branch".

#
#	Configurable options
#

$TMPDIR = "/var/run/log_accum";

# Set this to something that takes "-s".
#$MAILER	       = "/usr/bin/Mail";
# ... or set this to a sendmail clone:
$SENDMAIL = "/usr/sbin/sendmail";

# Used with sprintf to form name of Gnats notification mailing list.
# %s argument comes from -G option.
$GNATS_MAIL_FORMAT = "%s-gnats\@sourceware.cygnus.com";

# Used with sprintf to form name of Gnats root directory.  Potential
# PR info is appended to see if PR actually exists.  %s argument comes
# from -G option.
$GNATS_ROOT_FORMAT = "/sourceware/gnats/%s-db";

# Base name of cvsweb specification.
$CVSWEB_URL = "http://savannah.gnu.org/cgi-bin/viewcvs/";

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

#
#	Subroutines
#

sub set_temp_vars {
    local ($name) = @_;

    $LAST_FILE     = sprintf ("$TMPDIR/#%s.lastdir", $name);

    $CHANGED_FILE  = sprintf ("$TMPDIR/#%s.files.changed", $name);
    $ADDED_FILE    = sprintf ("$TMPDIR/#%s.files.added", $name);
    $REMOVED_FILE  = sprintf ("$TMPDIR/#%s.files.removed", $name);
    $LOG_FILE      = sprintf ("$TMPDIR/#%s.files.log", $name);
    $URL_FILE      = sprintf ("$TMPDIR/#%s.files.urls", $name);

    # Quote for use in a regexp.
    ($FILE_PREFIX   = sprintf ("#%s.files", $name)) =~ s/(\W)/\\$1/g;
}

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

    $wd = `pwd`;
    chdir("$TMPDIR") || die("Can't chdir(\"$TMPDIR\")\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 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 accum_subject {
    local(@lines) = @_;
    local(@files, $lastdir);

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

    @files;
}

sub compile_subject {
    local(@files) = @_;
    local($text, @a, @b, @c, $dir, $topdir);

    # find the highest common directory
    # FIXME: This function is royally dependent on receiving directory
    # entries, such as `my/dir/'.  It would be nice to rewrite it to
    # robustly discover the greatest common directory, but I haven't the time.
    # --gord
    $dir = '-';
    do {
	$topdir = $dir;
	foreach $file (@files) {
	    if ($file =~ /.*\/$/) {
		if ($dir eq '-') {
		    $dir = $file;
		} else {
		    if (index($dir,$file) == 0) {
			$dir = $file;
		    } elsif (index($file,$dir) != 0) {
			@a = split /\//,$file;
			@b = split /\//,$dir;
			@c = ();
			CMP: while ($#a > 0 && $#b > 0) {
			    if ($a[0] eq $b[0]) {
				push(@c, $a[0]);
				shift @a;
				shift @b;
			    } else {
				last CMP;
			    }
			}
			$dir = join('/',@c) . '/';
		    }
		}
	    }
	}
    } until $dir eq $topdir;

    # strip out directories and the common prefix topdir.
    chop $topdir;

    local ($topdir_offset) = length ($topdir);
    $topdir_offset ++ if ($topdir_offset > 0);

    $topdir = '' if ($topdir eq '.');
    @c = ($modulename);
    $c[0] .= '/' . $topdir if ($topdir ne '');
    foreach $file (@files) {
	if (!($file =~ /.*\/$/)) {
	    # Append the filename stripped of the top directory.
	    push(@c, substr($file, $topdir_offset));
	}
    }

    # put it together and limit the length.
    $text = join(' ',@c);
    if (length($text) > 50) {
	$text = substr($text, 0, 47) . '...';
    }

    if ($branch ne '') {
	$text = "$text[$branch]";
    }
    $text;
}

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 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 build_header {
    local($header);
    local($sec,$min,$hour,$mday,$mon,$year) = localtime(time);
    $header = sprintf("CVSROOT:\t%s\nModule name:\t%s\n",
		      $cvsroot,
		      $modulename);
    if (defined($branch)) {
	$header .= sprintf("Branch: \t%s\n",
		      $branch);
    }
    $header .= sprintf("Changes by:\t%s\t%02d/%02d/%02d %02d:%02d:%02d",
		      "$fullname <$mailname>",
		      $year%100, $mon+1, $mday,
		      $hour, $min, $sec);
}

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

    print STDERR "mail_notification: to $names, subject $subject\n" if ($debug);
    if ($SENDMAIL ne '')
    {
	my ($comma_names) = join (', ', split (/\s+/, $names));
	unshift (@text,
		 "To: $comma_names",
		 "Subject: $subject",
		 "Reply-to: $mailname",
		 ''); # We need this blank line before the message body.
	open (MAIL, "| $SENDMAIL -bm -F\"$fullname\" $names");
    }
    else
    {
	open(MAIL, "| $MAILER -s \"$subject\" $names");
    }
    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);
}

# Return a list of all cvsweb URLs for this commit.
sub generate_cvsweb_urls {
    local ($dir, $branch, @files) = @_;
    local (@sp, @result);
    local ($start) = $CVSWEB_URL;
    local ($args) = '?';
    local ($need_amp) = 0;

# This next if assumes that if a repository name is not given with the
# -C option, we then have to append the project name to the CVSWEB_URL
# and the project name has been defined by the -T option
# (this works for the particular setup in savannah.gnu.org but will have
# to be changed in other sites.)
    if ($cvsweb_name eq '')
    {
	$start .= $temp_name . '/';
    }
    else
    {
	$args .= 'cvsroot=' . $cvsweb_name;
	$need_amp = 1;
    }
    $start .= $dir . '/';
    if ($branch ne '') {
	$args .= '&' if ($need_amp);
	$args .= 'only_with_tag=' . $branch;
	$need_amp = 1;
    }

    # Add an ampersand if we need one.
    $args .= '&' if ($need_amp);
    $need_amp = 0;

    local ($r1,$r2);
    foreach (@files) {
	# List is (FILE OLD-REV NEW-REV).
	@sp = split (',');
	$r1 = $sp[1];
	$r2 = $sp[2];
	if ($r1 eq 'NONE')
        # If it's a new file, it is listed completely
	{
	    push (@result, ($start . $sp[0] . $args. 'rev=' . $r2));
	}
        # If it's a file update, differences are shown. A value of NONE
        # for NEW-REV indicates a file removed and no URL is generated.
	elsif ($r2 ne 'NONE')
	{
	    push (@result, ($start . $sp[0] . '.diff' . $args. 'tr1=' . $r1
			 . '&tr2=' . $r2 . '&r1=text&r2=text'));
	}
    }
    return @result;
}

#
#	Main Body
#

# Initialize basic variables
#
$debug = 0;
$id = getpgrp();		# note, you *must* use a shell which does setpgrp()
$state = $STATE_NONE;
local ($login, $gecos);
$login = $ENV{'USER'};
if (! $login)
{
    ($login, $gecos) = (getpwuid ($<))[0,6];
}
else
{
    $login = "nobody" if (! $login);
    $gecos = (getpwnam ($login))[6];
}

# Determine the mailname and fullname.
if ($gecos =~ /^([^<]*\s+)<(\S+@\S+)>/)
{
    $fullname = $1;
    $mailname = $2;
    $fullname =~ s/\s+$//;
}
else
{
    $fullname = $gecos;
    $fullname =~ s/,.*$//;

    local ($hostdomain, $hostname);
    chop($hostdomain = `hostname -f`);
    if ($hostdomain !~ /\./)
    {
        chop($hostname = `hostname`);
        if ($hostname !~ /\./) {
            chop($domainname = `domainname`);
            $hostdomain = $hostname . "." . $domainname;
        } else {
            $hostdomain = $hostname;
        }
    }
    $mailname = "$login\@$hostdomain";
}

$cvsroot = $ENV{'CVSROOT'};
$do_status = 1;
$modulename = "";
$temp_name = "temp";
$do_cvsweb = 0;
$cvsweb_name = '';
$do_diffs = 0;
$tag = "";
$branch = "";
$have_r_opt = 0;
%diffmon = ();

# 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 '-r') {
	$have_r_opt = 1;
	$tag = shift @ARGV;
    } elsif ($arg eq '-M') {
	$modulename = shift @ARGV;
    } elsif ($arg eq '-s') {
	$do_status = 0;
    } elsif ($arg eq '-u') {
	$do_unidiff = 1;
    } elsif ($arg eq '-f') {
	($commitlog) && die("Too many '-f' args\n");
	$commitlog = shift @ARGV;
    } elsif ($arg eq '-G') {
	($gnatsdb) && die("Too many '-G' args\n");
	$gnatsdb = shift @ARGV;
    } elsif ($arg eq '-T') {
	$temp_name = shift @ARGV;
    } elsif ($arg eq '-C') {
	$do_cvsweb = 1;
	$cvsweb_name = shift @ARGV
	    if ($#ARGV >= 0 && $ARGV[0] !~ /^-/);
    } elsif ($arg eq '-U') {
	$CVSWEB_URL = shift @ARGV;
    } elsif ($arg eq '-D') {
	$do_diffs = 2;
    } elsif ($arg =~ /^-D=(.*)$/) {
	$do_diffs = 2;
	$diffmailto = $1;
    } elsif ($arg =~ /^-D([^=]+)$/) {
	$do_diffs = 1 if (! $do_diffs);
	$diffmon{$1} = 'DEFAULT';
    } elsif ($arg =~ /^-D([^=]+)=(.*)$/) {
	$do_diffs = 1 if (! $do_diffs);
	$diffmon{$1} = $2;
    } else {
	($donefiles) && die("Too many arguments!  Check usage.\n");
	$donefiles = 1;
	@files = split(/ /, $arg);
    }
}
($mailto) || die("No -m mail recipient specified\n");
$mailto =~ s/^\s+//;
&set_temp_vars ($temp_name);

# 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
}
if ($commitlog ne "") {
    $commitlog = $cvsroot . "/" . $modulename . "/" . $commitlog unless ($commitlog =~ /^\//);
}
if ($#path == 0) {
    $dir = ".";
} else {
    $dir = join('/', @path[1..$#path]);
}
$dir = $dir . "/";

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

# 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());
    push(@text, "");
    push(@text, $files[0]);
    push(@text, "");

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

    &mail_notification($mailto, $files[0], @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 (/^\s*Tag:|Revision\/Branch/) { /^[^:]+:\s*(.*)/; $branch = $+; 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());
    push(@text, "");

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

    &mail_notification($mailto, "Import $file[0]", @text);

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

    exit 0;
}

# Compute the list of cvsweb URLs if necessary.
if ($do_cvsweb) {
    @urls = &generate_cvsweb_urls (join ('/', @path), $branch,
				   @files[1 .. $#files]);
}

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.$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);
&append_names_to_file("$URL_FILE.$i.$id",     $dir, @urls);
&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";
}
$_ = &read_line("$LAST_FILE.$id");

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

#
# Produce the final compilation of the log messages
#
@text = ();
@status_txt = ();
@diff_txt = ();
@subject_files = ();
@log_txt = ();
push(@text, &build_header());
push(@text, "");

for ($i = 0; ; $i++) {
    last if (! -e "$LOG_FILE.$i.$id"); # we're done them all!
    @lines = &read_logfile("$CHANGED_FILE.$i.$id", "");
    if ($#lines >= 0) {
	push(@text, "Modified files:");
	push(@text, &format_lists(@lines));
	push(@subject_files, &accum_subject(@lines));
    }
    @lines = &read_logfile("$ADDED_FILE.$i.$id", "");
    if ($#lines >= 0) {
	push(@text, "Added files:");
	push(@text, &format_lists(@lines));
	push(@subject_files, &accum_subject(@lines));
    }
    @lines = &read_logfile("$REMOVED_FILE.$i.$id", "");
    if ($#lines >= 0) {
	push(@text, "Removed files:");
	push(@text, &format_lists(@lines));
	push(@subject_files, &accum_subject(@lines));
    }
    if ($#text >= 0) {
	push(@text, "");
    }
    @log_txt = &read_logfile("$LOG_FILE.$i.$id", "\t");
    if ($#log_txt >= 0) {
	push(@text, "Log message:");
	push(@text, @log_txt);
	push(@text, "");
    }
    @url_txt = &read_logfile("$URL_FILE.$i.$id", "");
    if ($#url_txt >= 0) {
	push (@text, "CVSWeb URLs:");
	# Exclude directories listed in the file.
	push (@text, grep (! /\/$/, @url_txt));
	push (@text, "");
    }
    if ($do_status || $do_diffs) {
	local(@changed_files);

	@changed_files = ();
	push(@changed_files, &read_logfile("$CHANGED_FILE.$i.$id", ""));
	push(@changed_files, &read_logfile("$ADDED_FILE.$i.$id", ""));
	push(@changed_files, &read_logfile("$REMOVED_FILE.$i.$id", ""));

	if ($debug) {
	    print STDERR "main: pre-sort changed_files = ", join(":", @changed_files), ".\n";
	}

	# Add the directory to each changed_file.
	my ($lastdir, $i);
	for ($i = 0; $i <= $#changed_files; $i ++)
	{
	    if ($changed_files[$i] =~ /\/$/)
	    {
		$lastdir = $changed_files[$i];
	    }
	    elsif ($lastdir ne './' && $changed_files[$i] !~ /\//)
	    {
		$changed_files[$i] = $lastdir . $changed_files[$i];
	    }
	}

	@changed_files = sort(@changed_files);
	if ($debug) {
	    print STDERR "main: post-sort changed_files = ", join(":", @changed_files), ".\n";
	}

	foreach $dofile (@changed_files) {
	    if ($dofile =~ /\/$/) {
		next;		# ignore the silly "dir" entries
	    }
	    if (($do_diffs > 1 || $diffmon{$dofile})
		&& (! $have_r_opt || $tag eq $branch))
	    {
		my $addr = 'DEFAULT';
		$addr = $diffmailto if ($diffmailto);
		$addr = $diffmon{$dofile} if ($diffmon{$dofile});
		my (@diff) = ();
		if ($debug) {
		    print STDERR "main(): doing diff on $dofile\n";
		}
		if ($branch ne '') {
		    my $prior_revision = '0.0';
		    my $current_revision = '0.0';
		    open (LOG, "-|")
			|| exec 'cvs', '-nQq', 'rlog', '-r', "$branch.", "$modulename/$dofile";
		    while (<LOG>) {
			if (/^revision ((?:\d+\.)+)(\d+)/) {
			    $current_revision = $1 . $2;
			    $prior_revision = $1 . ($2 - 1);
			    last;
			}
		    }

		    open (DIFF, "-|")
			|| exec 'cvs', '-nQq', 'rdiff', '-r', $prior_revision, '-r', $current_revision, ($do_unidiff ? '-u' : '-c'), "$modulename/$dofile";
		} else {
		    open (DIFF, "-|")
			|| exec 'cvs', '-nQq', 'rdiff', '-t', ($do_unidiff ? '-u' : '-c'), "$modulename/$dofile";
		}

		while (<DIFF>) {
		    # FIXME: Rearrange the diffs so that they make valid
		    # `patch' input.
		    chop;
		    push (@diff, $_);
		}

		if ($addr eq 'DEFAULT')
		{
		    push (@diff_txt, @diff);
		}
		else
		{
		    local($subj);
		    $subj = "Changes to $modulename/$dofile";
		    if ($branch ne '') {
			$subj = "$subj[$branch]";
		    }
		    &mail_notification ($addr,
					$subj,
					@diff);
		}
	    }
	    if ($do_status)
	    {
		if ($debug) {
		    print STDERR "main(): doing status on $dofile\n";
		}
		open(STATUS, "-|")
		    || exec 'cvs', '-nQq', 'status', '-v', $dofile;
		while (<STATUS>) {
		    chop;
		    push(@status_txt, $_);
		}
	    }
	}
    }
}

$subject_txt = &compile_subject(@subject_files);

# Write to the commitlog file
#
if ($commitlog) {
    &write_commitlog($commitlog, @text);
}

if ($#diff_txt >= 0) {
    push (@text, "Patches:");
    push (@text, @diff_txt);
}

if ($#status_txt >= 0) {
    push(@text, @status_txt);
}

# Mailout the notification.
#
if (! $have_r_opt || $tag eq $branch) {
    &mail_notification($mailto, $subject_txt, @text);
}

# Send mail to Gnats, if required.
if ($gnatsdb ne '') {
    $log_txt = join ("\n", @log_txt);
    if ($log_txt =~ m,PR ([a-z.]+/[0-9]+),) {
	$pr = $1;
	$file = sprintf ($GNATS_ROOT_FORMAT, $gnatsdb) . "/" . $pr;
	if (-f $file) {
	    &mail_notification(sprintf ($GNATS_MAIL_FORMAT, $gnatsdb),
			       $pr, @text);
	}
    }
}

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

exit 0;
