1#! @PERL@ -T
2# -*-Perl-*-
3
4# Copyright (C) 1994-2005 The Free Software Foundation, Inc.
5
6# This program is free software; you can redistribute it and/or modify
7# it under the terms of the GNU General Public License as published by
8# the Free Software Foundation; either version 2, or (at your option)
9# any later version.
10#
11# This program is distributed in the hope that it will be useful,
12# but WITHOUT ANY WARRANTY; without even the implied warranty of
13# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14# GNU General Public License for more details.
15
16###############################################################################
17###############################################################################
18###############################################################################
19#
20# THIS SCRIPT IS PROBABLY BROKEN.  REMOVING THE -T SWITCH ON THE #! LINE ABOVE
21# WOULD FIX IT, BUT THIS IS INSECURE.  WE RECOMMEND FIXING THE ERRORS WHICH THE
22# -T SWITCH WILL CAUSE PERL TO REPORT BEFORE RUNNING THIS SCRIPT FROM A CVS
23# SERVER TRIGGER.  PLEASE SEND PATCHES CONTAINING THE CHANGES YOU FIND
24# NECESSARY TO RUN THIS SCRIPT WITH THE TAINT-CHECKING ENABLED BACK TO THE
25# <@PACKAGE_BUGREPORT@> MAILING LIST.
26#
27# For more on general Perl security and taint-checking, please try running the
28# `perldoc perlsec' command.
29#
30###############################################################################
31###############################################################################
32###############################################################################
33
34# Perl filter to handle the log messages from the checkin of files in
35# a directory.  This script will group the lists of files by log
36# message, and mail a single consolidated log message at the end of
37# the commit.
38#
39# This file assumes a pre-commit checking program that leaves the
40# names of the first and last commit directories in a temporary file.
41#
42# IMPORTANT: what the above means is, this script interacts with
43# commit_prep, in that they have to agree on the tmpfile name to use.
44# See $LAST_FILE below. 
45#
46# How this works: CVS triggers this script once for each directory
47# involved in the commit -- in other words, a single commit can invoke
48# this script N times.  It knows when it's on the last invocation by
49# examining the contents of $LAST_FILE.  Between invocations, it
50# caches information for its future incarnations in various temporary
51# files in /tmp, which are named according to the process group and
52# the committer (by themselves, neither of these are unique, but
53# together they almost always are, unless the same user is doing two
54# commits simultaneously).  The final invocation is the one that
55# actually sends the mail -- it gathers up the cached information,
56# combines that with what it found out on this pass, and sends a
57# commit message to the appropriate mailing list.
58#
59# (Ask Karl Fogel <kfogel@collab.net> if questions.)
60#
61# Contributed by David Hampton <hampton@cisco.com>
62# Roy Fielding removed useless code and added log/mail of new files
63# Ken Coar added special processing (i.e., no diffs) for binary files
64#
65
66############################################################
67#
68# Configurable options
69#
70############################################################
71#
72# Where do you want the RCS ID and delta info?
73# 0 = none,
74# 1 = in mail only,
75# 2 = in both mail and logs.
76#
77$rcsidinfo = 2;
78
79#if you are using CVS web then set this to some value... if not set it to ""
80#
81# When set properly, this will cause links to aspects of the project to
82# print in the commit emails.
83#$CVSWEB_SCHEME = "http";
84#$CVSWEB_DOMAIN = "nongnu.org";
85#$CVSWEB_PORT = "80";
86#$CVSWEB_URI = "source/browse/";
87#$SEND_URL = "true";
88$SEND_DIFF = "true";
89
90
91# Set this to a domain to have CVS pretend that all users who make
92# commits have mail accounts within that domain.
93#$EMULATE_LOCAL_MAIL_USER="nongnu.org"; 
94
95# Set this to '-c' for context diffs; defaults to '-u' for unidiff format.
96$difftype = '-uN';
97
98############################################################
99#
100# Constants
101#
102############################################################
103$STATE_NONE    = 0;
104$STATE_CHANGED = 1;
105$STATE_ADDED   = 2;
106$STATE_REMOVED = 3;
107$STATE_LOG     = 4;
108
109$TMPDIR        = $ENV{'TMPDIR'} || '/tmp';
110$FILE_PREFIX   = '#cvs.';
111
112$LAST_FILE     = "$TMPDIR/${FILE_PREFIX}lastdir";  # Created by commit_prep!
113$ADDED_FILE    = "$TMPDIR/${FILE_PREFIX}files.added";
114$REMOVED_FILE  = "$TMPDIR/${FILE_PREFIX}files.removed";
115$LOG_FILE      = "$TMPDIR/${FILE_PREFIX}files.log";
116$BRANCH_FILE   = "$TMPDIR/${FILE_PREFIX}files.branch";
117$MLIST_FILE    = "$TMPDIR/${FILE_PREFIX}files.mlist";
118$SUMMARY_FILE  = "$TMPDIR/${FILE_PREFIX}files.summary";
119
120$CVSROOT       = $ENV{'CVSROOT'};
121
122$MAIL_CMD      = "| /usr/lib/sendmail -i -t";
123#$MAIL_CMD      = "| /var/qmail/bin/qmail-inject";
124$MAIL_FROM     = 'commitlogger';  #not needed if EMULATE_LOCAL_MAIL_USER
125$SUBJECT_PRE   = 'CVS update:';
126
127
128############################################################
129#
130# Subroutines
131#
132############################################################
133
134sub format_names {
135    local($dir, @files) = @_;
136    local(@lines);
137
138    $lines[0] = sprintf(" %-08s", $dir);
139    foreach $file (@files) {
140        if (length($lines[$#lines]) + length($file) > 60) {
141            $lines[++$#lines] = sprintf(" %8s", " ");
142        }
143        $lines[$#lines] .= " ".$file;
144    }
145    @lines;
146}
147
148sub cleanup_tmpfiles {
149    local(@files);
150
151    opendir(DIR, $TMPDIR);
152    push(@files, grep(/^${FILE_PREFIX}.*\.${id}\.${cvs_user}$/, readdir(DIR)));
153    closedir(DIR);
154    foreach (@files) {
155        unlink "$TMPDIR/$_";
156    }
157}
158
159sub write_logfile {
160    local($filename, @lines) = @_;
161
162    open(FILE, ">$filename") || die ("Cannot open log file $filename: $!\n");
163    print(FILE join("\n", @lines), "\n");
164    close(FILE);
165}
166
167sub append_to_file {
168    local($filename, $dir, @files) = @_;
169
170    if (@files) {
171        local(@lines) = &format_names($dir, @files);
172        open(FILE, ">>$filename") || die ("Cannot open file $filename: $!\n");
173        print(FILE join("\n", @lines), "\n");
174        close(FILE);
175    }
176}
177
178sub write_line {
179    local($filename, $line) = @_;
180
181    open(FILE, ">$filename") || die("Cannot open file $filename: $!\n");
182    print(FILE $line, "\n");
183    close(FILE);
184}
185
186sub append_line {
187    local($filename, $line) = @_;
188
189    open(FILE, ">>$filename") || die("Cannot open file $filename: $!\n");
190    print(FILE $line, "\n");
191    close(FILE);
192}
193
194sub read_line {
195    local($filename) = @_;
196    local($line);
197
198    open(FILE, "<$filename") || die("Cannot open file $filename: $!\n");
199    $line = <FILE>;
200    close(FILE);
201    chomp($line);
202    $line;
203}
204
205sub read_line_nodie {
206    local($filename) = @_;
207    local($line);
208    open(FILE, "<$filename") || return ("");
209
210    $line = <FILE>;
211    close(FILE);
212    chomp($line);
213    $line;
214}
215
216sub read_file_lines {
217    local($filename) = @_;
218    local(@text) = ();
219
220    open(FILE, "<$filename") || return ();
221    while (<FILE>) {
222        chomp;
223        push(@text, $_);
224    }
225    close(FILE);
226    @text;
227}
228
229sub read_file {
230    local($filename, $leader) = @_;
231    local(@text) = ();
232
233    open(FILE, "<$filename") || return ();
234    while (<FILE>) {
235        chomp;
236        push(@text, sprintf("  %-10s  %s", $leader, $_));
237        $leader = "";
238    }
239    close(FILE);
240    @text;
241}
242
243sub read_logfile {
244    local($filename, $leader) = @_;
245    local(@text) = ();
246
247    open(FILE, "<$filename") || die ("Cannot open log file $filename: $!\n");
248    while (<FILE>) {
249        chomp;
250        push(@text, $leader.$_);
251    }
252    close(FILE);
253    @text;
254}
255
256#
257# do an 'cvs -Qn status' on each file in the arguments, and extract info.
258#
259sub change_summary {
260    local($out, @filenames) = @_;
261    local(@revline);
262    local($file, $rev, $rcsfile, $line, $vhost, $cvsweb_base);
263
264    while (@filenames) {
265        $file = shift @filenames;
266
267        if ("$file" eq "") {
268            next;
269        }
270
271        open(RCS, "-|") || exec "$cvsbin/cvs", '-Qn', 'status', '--', $file;
272
273        $rev = "";
274        $delta = "";
275        $rcsfile = "";
276
277
278        while (<RCS>) {
279            if (/^[ \t]*Repository revision/) {
280                chomp;
281                @revline = split(' ', $_);
282                $rev = $revline[2];
283                $rcsfile = $revline[3];
284                $rcsfile =~ s,^$CVSROOT/,,;
285                $rcsfile =~ s/,v$//;
286            }
287        }
288        close(RCS);
289
290
291        if ($rev ne '' && $rcsfile ne '') {
292            open(RCS, "-|") || exec "$cvsbin/cvs", '-Qn', 'log', "-r$rev",
293				    '--', $file;
294            while (<RCS>) {
295                if (/^date:/) {
296                    chomp;
297                    $delta = $_;
298                    $delta =~ s/^.*;//;
299                    $delta =~ s/^[\s]+lines://;
300                }
301            }
302            close(RCS);
303        }
304
305        $diff = "\n\n";
306        $vhost = $path[0];
307        if ($CVSWEB_PORT eq "80") {
308          $cvsweb_base = "$CVSWEB_SCHEME://$vhost.$CVSWEB_DOMAIN/$CVSWEB_URI";
309        }
310        else {
311          $cvsweb_base = "$CVSWEB_SCHEME://$vhost.$CVSWEB_DOMAIN:$CVSWEB_PORT/$CVSWEB_URI";
312        }
313        if ($SEND_URL eq "true") {
314          $diff .= $cvsweb_base . join("/", @path) . "/$file";
315        }
316
317        #
318        # If this is a binary file, don't try to report a diff; not only is
319        # it meaningless, but it also screws up some mailers.  We rely on
320        # Perl's 'is this binary' algorithm; it's pretty good.  But not
321        # perfect.
322        #
323        if (($file =~ /\.(?:pdf|gif|jpg|mpg)$/i) || (-B $file)) {
324          if ($SEND_URL eq "true") {
325            $diff .= "?rev=$rev&content-type=text/x-cvsweb-markup\n\n";
326          }
327          if ($SEND_DIFF eq "true") {
328            $diff .= "\t<<Binary file>>\n\n";
329          }
330        }
331        else {
332            #
333            # Get the differences between this and the previous revision,
334            # being aware that new files always have revision '1.1' and
335            # new branches always end in '.n.1'.
336            #
337            if ($rev =~ /^(.*)\.([0-9]+)$/) {
338                $prev = $2 - 1;
339                $prev_rev = $1 . '.' .  $prev;
340
341                $prev_rev =~ s/\.[0-9]+\.0$//;# Truncate if first rev on branch
342
343                if ($rev eq '1.1') {
344                  if ($SEND_URL eq "true") {
345                    $diff .= "?rev=$rev&content-type=text/x-cvsweb-markup\n\n";
346                  }
347                  if ($SEND_DIFF eq "true") {
348                    open(DIFF, "-|")
349                      || exec "$cvsbin/cvs", '-Qn', 'update', '-p', '-r1.1',
350			      '--', $file;
351                    $diff .= "Index: $file\n=================================="
352                      . "=================================\n";
353                  }
354                }
355                else {
356                  if ($SEND_URL eq "true") {
357                    $diff .= ".diff?r1=$prev_rev&r2=$rev\n\n";
358                  }
359                  if ($SEND_DIFF eq "true") {
360                    $diff .= "(In the diff below, changes in quantity "
361                      . "of whitespace are not shown.)\n\n";
362                    open(DIFF, "-|")
363                      || exec "$cvsbin/cvs", '-Qn', 'diff', "$difftype",
364                      '-b', "-r$prev_rev", "-r$rev", '--', $file;
365                  }
366                }
367
368                if ($SEND_DIFF eq "true") {
369                  while (<DIFF>) {
370                    $diff .= $_;
371                  }
372                  close(DIFF);
373                }
374                $diff .= "\n\n";
375            }
376        }
377
378        &append_line($out, sprintf("%-9s%-12s%s%s", $rev, $delta,
379                                   $rcsfile, $diff));
380    }
381}
382
383
384sub build_header {
385    local($header);
386    delete $ENV{'TZ'};
387    local($sec,$min,$hour,$mday,$mon,$year) = localtime(time);
388
389    $header = sprintf("  User: %-8s\n  Date: %02d/%02d/%02d %02d:%02d:%02d",
390                       $cvs_user, $year%100, $mon+1, $mday,
391                       $hour, $min, $sec);
392#    $header = sprintf("%-8s    %02d/%02d/%02d %02d:%02d:%02d",
393#                       $login, $year%100, $mon+1, $mday,
394#                       $hour, $min, $sec);
395}
396
397# !!! Destination Mailing-list and history file mappings here !!!
398
399#sub mlist_map
400#{
401#    local($path) = @_;
402#    my $domain = "nongnu.org";
403#    
404#    if ($path =~ /^([^\/]+)/) {
405#        return "cvs\@$1.$domain";
406#    } else {
407#        return "cvs\@$domain";
408#    }
409#}    
410
411sub derive_subject_from_changes_file ()
412{
413  my $subj = "";
414
415  for ($i = 0; ; $i++)
416  {
417    open (CH, "<$CHANGED_FILE.$i.$id.$cvs_user") or last;
418
419    while (my $change = <CH>)
420    {
421      # A changes file looks like this:
422      #
423      #  src      foo.c newfile.html
424      #  www      index.html project_nav.html
425      #
426      # Each line is " Dir File1 File2 ..."
427      # We only care about Dir, since the subject line should
428      # summarize. 
429      
430      $change =~ s/^[ \t]*//;
431      $change =~ /^([^ \t]+)[ \t]*/;
432      my $dir = $1;
433      # Fold to rightmost directory component
434      $dir =~ /([^\/]+)$/;
435      $dir = $1;
436      if ($subj eq "") {
437        $subj = $dir;
438      } else {
439        $subj .= ", $dir"; 
440      }
441    }
442    close (CH);
443  }
444
445  if ($subj ne "") {
446      $subj = "MODIFIED: $subj ..."; 
447  }
448  else {
449      # NPM: See if there's any file-addition notifications.
450      my $added = &read_line_nodie("$ADDED_FILE.$i.$id.$cvs_user");
451      if ($added ne "") {
452          $subj .= "ADDED: $added "; 
453      }
454    
455#    print "derive_subject_from_changes_file().. added== $added \n";
456    
457       ## NPM: See if there's any file-removal notications.
458      my $removed = &read_line_nodie("$REMOVED_FILE.$i.$id.$cvs_user");
459      if ($removed ne "") {
460          $subj .= "REMOVED: $removed "; 
461      }
462    
463#    print "derive_subject_from_changes_file().. removed== $removed \n";
464    
465      ## NPM: See if there's any branch notifications.
466      my $branched = &read_line_nodie("$BRANCH_FILE.$i.$id.$cvs_user");
467      if ($branched ne "") {
468          $subj .= "BRANCHED: $branched"; 
469      }
470    
471#    print "derive_subject_from_changes_file().. branched== $branched \n";
472    
473      ## NPM: DEFAULT: DIRECTORY CREATION (c.f. "Check for a new directory first" in main mody)
474      if ($subj eq "") {
475          my $subject = join("/", @path);
476          $subj = "NEW: $subject"; 
477      }    
478  }
479
480  return $subj;
481}
482
483sub mail_notification
484{
485    local($addr_list, @text) = @_;
486    local($mail_to);
487
488    my $subj = &derive_subject_from_changes_file ();
489
490    if ($EMULATE_LOCAL_MAIL_USER ne "") {
491        $MAIL_FROM = "$cvs_user\@$EMULATE_LOCAL_MAIL_USER";
492    }
493
494    $mail_to = join(", ", @{$addr_list});
495
496    print "Mailing the commit message to $mail_to (from $MAIL_FROM)\n";
497
498    $ENV{'MAILUSER'} = $MAIL_FROM;
499    # Commented out on hocus, so comment it out here.  -kff
500    # $ENV{'QMAILINJECT'} = 'f';
501
502    open(MAIL, "$MAIL_CMD -f$MAIL_FROM");
503    print MAIL "From: $MAIL_FROM\n";
504    print MAIL "To: $mail_to\n";
505    print MAIL "Subject: $SUBJECT_PRE $subj\n\n";
506    print(MAIL join("\n", @text));
507    close(MAIL);
508#    print "Mailing the commit message to $MAIL_TO...\n";
509#
510#    #added by jrobbins@collab.net 1999/12/15
511#    # attempt to get rid of anonymous
512#    $ENV{'MAILUSER'} = 'commitlogger';
513#    $ENV{'QMAILINJECT'} = 'f';
514#
515#    open(MAIL, "| /var/qmail/bin/qmail-inject");
516#    print(MAIL "To: $MAIL_TO\n"); 
517#    print(MAIL "Subject: cvs commit: $ARGV[0]\n"); 
518#    print(MAIL join("\n", @text));
519#    close(MAIL);
520}
521
522## process the command line arguments sent to this script
523## it returns an array of files, %s, sent from the loginfo
524## command
525sub process_argv
526{
527    local(@argv) = @_;
528    local(@files);
529    local($arg);
530    print "Processing log script arguments...\n";
531
532    while (@argv) {
533        $arg = shift @argv;
534
535        if ($arg eq '-u') {
536                $cvs_user = shift @argv;
537        } else {
538                ($donefiles) && die "Too many arguments!\n";
539                $donefiles = 1;
540                $ARGV[0] = $arg;
541                @files = split(' ', $arg);
542        }
543    }
544    return @files;
545}
546
547
548#############################################################
549#
550# Main Body
551#
552############################################################
553#
554# Setup environment
555#
556umask (002);
557
558# Connect to the database
559$cvsbin = "/usr/bin";
560
561#
562# Initialize basic variables
563#
564$id = getpgrp();
565$state = $STATE_NONE;
566$cvs_user = $ENV{'USER'} || getlogin || (getpwuid($<))[0] || sprintf("uid#%d",$<);
567@files = process_argv(@ARGV);
568@path = split('/', $files[0]);
569if ($#path == 0) {
570    $dir = ".";
571} else {
572    $dir = join('/', @path[1..$#path]);
573}
574#print("ARGV  - ", join(":", @ARGV), "\n");
575#print("files - ", join(":", @files), "\n");
576#print("path  - ", join(":", @path), "\n");
577#print("dir   - ", $dir, "\n");
578#print("id    - ", $id, "\n");
579
580#
581# Map the repository directory to an email address for commitlogs to be sent
582# to.
583#
584#$mlist = &mlist_map($files[0]);
585
586##########################
587#
588# Check for a new directory first.  This will always appear as a
589# single item in the argument list, and an empty log message.
590#
591if ($ARGV[0] =~ /New directory/) {
592    $header = &build_header;
593    @text = ();
594    push(@text, $header);
595    push(@text, "");
596    push(@text, "  ".$ARGV[0]);
597    &mail_notification([ $mlist ], @text);
598    exit 0;
599}
600
601#
602# Iterate over the body of the message collecting information.
603#
604while (<STDIN>) {
605    chomp;                      # Drop the newline
606    if (/^Revision\/Branch:/) {
607        s,^Revision/Branch:,,;
608        push (@branch_lines, split);
609        next;
610    }
611#    next if (/^[ \t]+Tag:/ && $state != $STATE_LOG);
612    if (/^Modified Files/) { $state = $STATE_CHANGED; next; }
613    if (/^Added Files/)    { $state = $STATE_ADDED;   next; }
614    if (/^Removed Files/)  { $state = $STATE_REMOVED; next; }
615    if (/^Log Message/)    { $state = $STATE_LOG;     next; }
616    s/[ \t\n]+$//;              # delete trailing space
617    
618    push (@changed_files, split) if ($state == $STATE_CHANGED);
619    push (@added_files,   split) if ($state == $STATE_ADDED);
620    push (@removed_files, split) if ($state == $STATE_REMOVED);
621    if ($state == $STATE_LOG) {
622        if (/^PR:$/i ||
623            /^Reviewed by:$/i ||
624            /^Submitted by:$/i ||
625            /^Obtained from:$/i) {
626            next;
627        }
628        push (@log_lines,     $_);
629    }
630}
631
632#
633# Strip leading and trailing blank lines from the log message.  Also
634# compress multiple blank lines in the body of the message down to a
635# single blank line.
636# (Note, this only does the mail and changes log, not the rcs log).
637#
638while ($#log_lines > -1) {
639    last if ($log_lines[0] ne "");
640    shift(@log_lines);
641}
642while ($#log_lines > -1) {
643    last if ($log_lines[$#log_lines] ne "");
644    pop(@log_lines);
645}
646for ($i = $#log_lines; $i > 0; $i--) {
647    if (($log_lines[$i - 1] eq "") && ($log_lines[$i] eq "")) {
648        splice(@log_lines, $i, 1);
649    }
650}
651
652#
653# Find the log file that matches this log message
654#
655for ($i = 0; ; $i++) {
656    last if (! -e "$LOG_FILE.$i.$id.$cvs_user");
657    @text = &read_logfile("$LOG_FILE.$i.$id.$cvs_user", "");
658    last if ($#text == -1);
659    last if (join(" ", @log_lines) eq join(" ", @text));
660}
661
662#
663# Spit out the information gathered in this pass.
664#
665&write_logfile("$LOG_FILE.$i.$id.$cvs_user", @log_lines);
666&append_to_file("$BRANCH_FILE.$i.$id.$cvs_user",  $dir, @branch_lines);
667&append_to_file("$ADDED_FILE.$i.$id.$cvs_user",   $dir, @added_files);
668&append_to_file("$CHANGED_FILE.$i.$id.$cvs_user", $dir, @changed_files);
669&append_to_file("$REMOVED_FILE.$i.$id.$cvs_user", $dir, @removed_files);
670&append_line("$MLIST_FILE.$i.$id.$cvs_user", $mlist);
671if ($rcsidinfo) {
672    &change_summary("$SUMMARY_FILE.$i.$id.$cvs_user", (@changed_files, @added_files));
673}
674
675#
676# Check whether this is the last directory.  If not, quit.
677#
678if (-e "$LAST_FILE.$id.$cvs_user") {
679   $_ = &read_line("$LAST_FILE.$id.$cvs_user");
680   $tmpfiles = $files[0];
681   $tmpfiles =~ s,([^a-zA-Z0-9_/]),\\$1,g;
682   if (! grep(/$tmpfiles$/, $_)) {
683        print "More commits to come...\n";
684        exit 0
685   }
686}
687
688#
689# This is it.  The commits are all finished.  Lump everything together
690# into a single message, fire a copy off to the mailing list, and drop
691# it on the end of the Changes file.
692#
693$header = &build_header;
694
695#
696# Produce the final compilation of the log messages
697#
698@text = ();
699@mlist_list = ();
700push(@text, $header);
701push(@text, "");
702for ($i = 0; ; $i++) {
703    last if (! -e "$LOG_FILE.$i.$id.$cvs_user");
704    push(@text, &read_file("$BRANCH_FILE.$i.$id.$cvs_user", "Branch:"));
705    push(@text, &read_file("$CHANGED_FILE.$i.$id.$cvs_user", "Modified:"));
706    push(@text, &read_file("$ADDED_FILE.$i.$id.$cvs_user", "Added:"));
707    push(@text, &read_file("$REMOVED_FILE.$i.$id.$cvs_user", "Removed:"));
708    push(@text, "  Log:");
709    push(@text, &read_logfile("$LOG_FILE.$i.$id.$cvs_user", "  "));
710    push(@mlist_list, &read_file_lines("$MLIST_FILE.$i.$id.$cvs_user"));
711    if ($rcsidinfo == 2) {
712        if (-e "$SUMMARY_FILE.$i.$id.$cvs_user") {
713            push(@text, "  ");
714            push(@text, "  Revision  Changes    Path");
715            push(@text, &read_logfile("$SUMMARY_FILE.$i.$id.$cvs_user", "  "));
716        }
717    }
718    push(@text, "");
719}
720
721#
722# Now generate the extra info for the mail message..
723#
724if ($rcsidinfo == 1) {
725    $revhdr = 0;
726    for ($i = 0; ; $i++) {
727        last if (! -e "$LOG_FILE.$i.$id.$cvs_user");
728        if (-e "$SUMMARY_FILE.$i.$id.$cvs_user") {
729            if (!$revhdr++) {
730                push(@text, "Revision  Changes    Path");
731            }
732            push(@text, &read_logfile("$SUMMARY_FILE.$i.$id.$cvs_user", ""));
733        }
734    }
735    if ($revhdr) {
736        push(@text, "");        # consistancy...
737    }
738}
739
740%mlist_hash = ();
741
742foreach (@mlist_list) { $mlist_hash{ $_ } = 1; }
743
744#
745# Mail out the notification.
746#
747&mail_notification([ keys(%mlist_hash) ], @text);
748&cleanup_tmpfiles;
749exit 0;
750