]> CyberLeo.Net >> Repos - FreeBSD/releng/9.2.git/blob - contrib/cvs/contrib/log_accum.in
- Copy stable/9 to releng/9.2 as part of the 9.2-RELEASE cycle.
[FreeBSD/releng/9.2.git] / contrib / cvs / contrib / log_accum.in
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
134 sub 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
148 sub 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
159 sub 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
167 sub 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
178 sub 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
186 sub 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
194 sub 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
205 sub 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
216 sub 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
229 sub 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
243 sub 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 #
259 sub 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
384 sub 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
411 sub 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
483 sub 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
525 sub 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 #
556 umask (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]);
569 if ($#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 #
591 if ($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 #
604 while (<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 #
638 while ($#log_lines > -1) {
639     last if ($log_lines[0] ne "");
640     shift(@log_lines);
641 }
642 while ($#log_lines > -1) {
643     last if ($log_lines[$#log_lines] ne "");
644     pop(@log_lines);
645 }
646 for ($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 #
655 for ($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);
671 if ($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 #
678 if (-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 = ();
700 push(@text, $header);
701 push(@text, "");
702 for ($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 #
724 if ($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
742 foreach (@mlist_list) { $mlist_hash{ $_ } = 1; }
743
744 #
745 # Mail out the notification.
746 #
747 &mail_notification([ keys(%mlist_hash) ], @text);
748 &cleanup_tmpfiles;
749 exit 0;