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