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