* gpg.c (reopen_std): New function to reopen fd 0, 1, or 2 if we are
[gnupg.git] / scripts / log_accum
blob0629abb4860bbd542f2825a601312c6dfd33b199
1 #! /usr/bin/perl
2 # -*-Perl-*-
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.
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.
12 # Contributed by David Hampton <hampton@cisco.com>
14 # hacked greatly by Greg A. Woods <woods@planix.com>
16 # Modified by werner.koch@guug.de to add support for
17 # automagically extraction of ChangeLog entries 1998-12-29
19 # Usage: log_accum.pl [-d] [-s] [-M module] [[-m mailto] ...] [[-R replyto] ...] [-f logfile]
20 # -d - turn on debugging
21 # -m mailto - send mail to "mailto" (multiple)
22 # -R replyto - set the "Reply-To:" to "replyto" (multiple)
23 # -M modulename - set module name to "modulename"
24 # -f logfile - write commit messages to logfile too
25 # -s - *don't* run "cvs status -v" for each file
26 # -w - show working directory with log message
29 # Configurable options
32 # set this to something that takes a whole message on stdin
33 $MAILER = "/usr/lib/sendmail -t";
36 # End user configurable options.
39 # Constants (don't change these!)
41 $STATE_NONE = 0;
42 $STATE_CHANGED = 1;
43 $STATE_ADDED = 2;
44 $STATE_REMOVED = 3;
45 $STATE_LOG = 4;
47 $LAST_FILE = "/tmp/#cvs.lastdir";
49 $CHANGED_FILE = "/tmp/#cvs.files.changed";
50 $ADDED_FILE = "/tmp/#cvs.files.added";
51 $REMOVED_FILE = "/tmp/#cvs.files.removed";
52 $LOG_FILE = "/tmp/#cvs.files.log";
54 $FILE_PREFIX = "#cvs.files";
57 # Subroutines
60 sub cleanup_tmpfiles {
61 local($wd, @files);
63 $wd = `pwd`;
64 chdir("/tmp") || die("Can't chdir('/tmp')\n");
65 opendir(DIR, ".");
66 push(@files, grep(/^$FILE_PREFIX\..*\.$id$/, readdir(DIR)));
67 closedir(DIR);
68 foreach (@files) {
69 unlink $_;
71 unlink $LAST_FILE . "." . $id;
73 chdir($wd);
76 sub write_logfile {
77 local($filename, @lines) = @_;
79 open(FILE, ">$filename") || die("Cannot open log file $filename.\n");
80 print FILE join("\n", @lines), "\n";
81 close(FILE);
84 sub append_to_logfile {
85 local($filename, @lines) = @_;
87 open(FILE, ">$filename") || die("Cannot open log file $filename.\n");
88 print FILE join("\n", @lines), "\n";
89 close(FILE);
92 sub format_names {
93 local($dir, @files) = @_;
94 local(@lines);
96 $format = "\t%-" . sprintf("%d", length($dir)) . "s%s ";
98 $lines[0] = sprintf($format, $dir, ":");
100 if ($debug) {
101 print STDERR "format_names(): dir = ", $dir, "; files = ", join(":", @files), ".\n";
103 foreach $file (@files) {
104 if (length($lines[$#lines]) + length($file) > 65) {
105 $lines[++$#lines] = sprintf($format, " ", " ");
107 $lines[$#lines] .= $file . " ";
110 @lines;
113 sub format_lists {
114 local(@lines) = @_;
115 local(@text, @files, $lastdir);
117 if ($debug) {
118 print STDERR "format_lists(): ", join(":", @lines), "\n";
120 @text = ();
121 @files = ();
122 $lastdir = shift @lines; # first thing is always a directory
123 if ($lastdir !~ /.*\/$/) {
124 die("Damn, $lastdir doesn't look like a directory!\n");
126 foreach $line (@lines) {
127 if ($line =~ /.*\/$/) {
128 push(@text, &format_names($lastdir, @files));
129 $lastdir = $line;
130 @files = ();
131 } else {
132 push(@files, $line);
135 push(@text, &format_names($lastdir, @files));
137 @text;
140 sub append_names_to_file {
141 local($filename, $dir, @files) = @_;
143 if (@files) {
144 open(FILE, ">>$filename") || die("Cannot open file $filename.\n");
145 print FILE $dir, "\n";
146 print FILE join("\n", @files), "\n";
147 close(FILE);
151 sub read_line {
152 local($line);
153 local($filename) = @_;
155 open(FILE, "<$filename") || die("Cannot open file $filename.\n");
156 $line = <FILE>;
157 close(FILE);
158 chop($line);
159 $line;
162 sub read_logfile {
163 local(@text);
164 local($filename, $leader) = @_;
166 open(FILE, "<$filename");
167 while (<FILE>) {
168 chop;
169 push(@text, $leader.$_);
171 close(FILE);
172 @text;
175 sub build_header {
176 local($header);
177 local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
178 $header = sprintf("CVSROOT:\t%s\nModule name:\t%s\nRepository:\t%s\nChanges by:\t%s@%s\t%02d/%02d/%02d %02d:%02d:%02d",
179 $cvsroot,
180 $modulename,
181 $dir,
182 $login, $hostdomain,
183 $year%100, $mon+1, $mday,
184 $hour, $min, $sec);
187 sub mail_notification {
188 local(@text) = @_;
190 # if only we had strftime()... stuff stolen from perl's ctime.pl:
191 local($[) = 0;
193 @DoW = ('Sun','Mon','Tue','Wed','Thu','Fri','Sat');
194 @MoY = ('Jan','Feb','Mar','Apr','May','Jun',
195 'Jul','Aug','Sep','Oct','Nov','Dec');
197 # Determine what time zone is in effect.
198 # Use GMT if TZ is defined as null, local time if TZ undefined.
199 # There's no portable way to find the system default timezone.
201 $TZ = defined($ENV{'TZ'}) ? ( $ENV{'TZ'} ? $ENV{'TZ'} : 'GMT' ) : '';
203 # Hack to deal with 'PST8PDT' format of TZ
204 # Note that this can't deal with all the esoteric forms, but it
205 # does recognize the most common: [:]STDoff[DST[off][,rule]]
207 if ($TZ =~ /^([^:\d+\-,]{3,})([+-]?\d{1,2}(:\d{1,2}){0,2})([^\d+\-,]{3,})?/) {
208 $TZ = $isdst ? $4 : $1;
209 $tzoff = sprintf("%05d", -($2) * 100);
212 # perl-4.036 doesn't have the $zone or $gmtoff...
213 ($sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst, $zone, $gmtoff) =
214 ($TZ eq 'GMT') ? gmtime(time) : localtime(time);
216 $year += ($year < 70) ? 2000 : 1900;
218 if ($gmtoff != 0) {
219 $tzoff = sprintf("%05d", ($gmtoff / 60) * 100);
221 if ($zone ne '') {
222 $TZ = $zone;
225 # ok, let's try....
226 $rfc822date = sprintf("%s, %2d %s %4d %2d:%02d:%02d %s (%s)",
227 $DoW[$wday], $mday, $MoY[$mon], $year,
228 $hour, $min, $sec, $tzoff, $TZ);
230 open(MAIL, "| $MAILER");
231 print MAIL "Date: " . $rfc822date . "\n";
232 print MAIL "Subject: CVS Update: " . $modulename . "\n";
233 print MAIL "To: " . $mailto . "\n";
234 print MAIL "Reply-To: " . $replyto . "\n";
235 print MAIL "\n";
236 print MAIL join("\n", @text), "\n";
237 close(MAIL);
240 sub write_commitlog {
241 local($logfile, @text) = @_;
243 open(FILE, ">>$logfile");
244 print FILE join("\n", @text), "\n";
245 close(FILE);
249 # Main Body
252 # Initialize basic variables
254 $debug = 0;
255 $id = getpgrp(); # note, you *must* use a shell which does setpgrp()
256 $state = $STATE_NONE;
257 $login = getlogin || (getpwuid($<))[0] || "nobody";
258 chop($hostname = `hostname`);
259 chop($domainname = `domainname`);
260 if ($domainname !~ '^\..*') {
261 $domainname = '.' . $domainname;
263 $hostdomain = $hostname . $domainname;
264 $cvsroot = $ENV{'CVSROOT'};
265 $do_status = 1; # moderately useful
266 $show_wd = 0; # useless in client/server
267 $modulename = "";
269 # parse command line arguments (file list is seen as one arg)
271 while (@ARGV) {
272 $arg = shift @ARGV;
274 if ($arg eq '-d') {
275 $debug = 1;
276 print STDERR "Debug turned on...\n";
277 } elsif ($arg eq '-m') {
278 if ($mailto eq '') {
279 $mailto = shift @ARGV;
280 } else {
281 $mailto = $mailto . ", " . shift @ARGV;
283 } elsif ($arg eq '-R') {
284 if ($replyto eq '') {
285 $replyto = shift @ARGV;
286 } else {
287 $replyto = $replyto . ", " . shift @ARGV;
289 } elsif ($arg eq '-M') {
290 $modulename = shift @ARGV;
291 } elsif ($arg eq '-s') {
292 $do_status = 0;
293 } elsif ($arg eq '-w') {
294 $show_wd = 1;
295 } elsif ($arg eq '-f') {
296 ($commitlog) && die("Too many '-f' args\n");
297 $commitlog = shift @ARGV;
298 } else {
299 ($donefiles) && die("Too many arguments! Check usage.\n");
300 $donefiles = 1;
301 @files = split(/ /, $arg);
304 ($mailto) || die("No mail recipient specified (use -m)\n");
305 if ($replyto eq '') {
306 $replyto = $login;
309 # for now, the first "file" is the repository directory being committed,
310 # relative to the $CVSROOT location
312 @path = split('/', $files[0]);
314 # XXX There are some ugly assumptions in here about module names and
315 # XXX directories relative to the $CVSROOT location -- really should
316 # XXX read $CVSROOT/CVSROOT/modules, but that's not so easy to do, since
317 # XXX we have to parse it backwards.
318 # XXX
319 # XXX Fortunately it's relatively easy for the user to specify the
320 # XXX module name as appropriate with a '-M' via the directory
321 # XXX matching in loginfo.
323 if ($modulename eq "") {
324 $modulename = $path[0]; # I.e. the module name == top-level dir
326 if ($#path == 0) {
327 $dir = ".";
328 } else {
329 $dir = join('/', @path);
331 $dir = $dir . "/";
333 if ($debug) {
334 print STDERR "module - ", $modulename, "\n";
335 print STDERR "dir - ", $dir, "\n";
336 print STDERR "path - ", join(":", @path), "\n";
337 print STDERR "files - ", join(":", @files), "\n";
338 print STDERR "id - ", $id, "\n";
341 # Check for a new directory first. This appears with files set as follows:
343 # files[0] - "path/name/newdir"
344 # files[1] - "-"
345 # files[2] - "New"
346 # files[3] - "directory"
348 if ($files[2] =~ /New/ && $files[3] =~ /directory/) {
349 local(@text);
351 @text = ();
352 push(@text, &build_header());
353 push(@text, "");
354 push(@text, $files[0]);
355 push(@text, "");
357 while (<STDIN>) {
358 chop; # Drop the newline
359 push(@text, $_);
362 &mail_notification($mailto, @text);
364 exit 0;
367 # Check for an import command. This appears with files set as follows:
369 # files[0] - "path/name"
370 # files[1] - "-"
371 # files[2] - "Imported"
372 # files[3] - "sources"
374 if ($files[2] =~ /Imported/ && $files[3] =~ /sources/) {
375 local(@text);
377 @text = ();
378 push(@text, &build_header());
379 push(@text, "");
380 push(@text, $files[0]);
381 push(@text, "");
383 while (<STDIN>) {
384 chop; # Drop the newline
385 push(@text, $_);
388 &mail_notification(@text);
390 exit 0;
393 # Iterate over the body of the message collecting information.
395 while (<STDIN>) {
396 chop; # Drop the newline
398 if (/^In directory/) {
399 if ($show_wd) { # useless in client/server mode
400 push(@log_lines, $_);
401 push(@log_lines, "");
403 next;
406 if (/^Modified Files/) { $state = $STATE_CHANGED; next; }
407 if (/^Added Files/) { $state = $STATE_ADDED; next; }
408 if (/^Removed Files/) { $state = $STATE_REMOVED; next; }
409 if (/^Log Message/) { $state = $STATE_LOG; next; }
411 s/^[ \t\n]+//; # delete leading whitespace
412 s/[ \t\n]+$//; # delete trailing whitespace
414 if ($state == $STATE_CHANGED) { push(@changed_files, split); }
415 if ($state == $STATE_ADDED) { push(@added_files, split); }
416 if ($state == $STATE_REMOVED) { push(@removed_files, split); }
417 if ($state == $STATE_LOG) {
418 if( /^See[ ]ChangeLog:[ ](.*)/ ) {
419 $changelog = $1;
420 $okay = false;
421 open(RCS, "-|") || exec 'cvs', '-Qn', 'update', '-p', 'ChangeLog';
422 while (<RCS>) {
423 if( /^$changelog .*/ ) {
424 $okay = true;
425 push(@log_lines, $_);
427 elsif( $okay ) {
428 last if( /^[A-Z]+.*/ );
429 push(@log_lines, $_);
432 while (<RCS>) { ; }
433 close(RCS);
435 else {
436 push(@log_lines, $_);
441 # Strip leading and trailing blank lines from the log message. Also
442 # compress multiple blank lines in the body of the message down to a
443 # single blank line.
445 while ($#log_lines > -1) {
446 last if ($log_lines[0] ne "");
447 shift(@log_lines);
449 while ($#log_lines > -1) {
450 last if ($log_lines[$#log_lines] ne "");
451 pop(@log_lines);
453 for ($i = $#log_lines; $i > 0; $i--) {
454 if (($log_lines[$i - 1] eq "") && ($log_lines[$i] eq "")) {
455 splice(@log_lines, $i, 1);
459 if ($debug) {
460 print STDERR "Searching for log file index...";
462 # Find an index to a log file that matches this log message
464 for ($i = 0; ; $i++) {
465 local(@text);
467 last if (! -e "$LOG_FILE.$i.$id"); # the next available one
468 @text = &read_logfile("$LOG_FILE.$i.$id", "");
469 last if ($#text == -1); # nothing in this file, use it
470 last if (join(" ", @log_lines) eq join(" ", @text)); # it's the same log message as another
472 if ($debug) {
473 print STDERR " found log file at $i.$id, now writing tmp files.\n";
476 # Spit out the information gathered in this pass.
478 &append_names_to_file("$CHANGED_FILE.$i.$id", $dir, @changed_files);
479 &append_names_to_file("$ADDED_FILE.$i.$id", $dir, @added_files);
480 &append_names_to_file("$REMOVED_FILE.$i.$id", $dir, @removed_files);
481 &write_logfile("$LOG_FILE.$i.$id", @log_lines);
483 # Check whether this is the last directory. If not, quit.
485 if ($debug) {
486 print STDERR "Checking current dir against last dir.\n";
488 $_ = &read_line("$LAST_FILE.$id");
490 if ($_ ne $cvsroot . "/" . $files[0]) {
491 if ($debug) {
492 print STDERR sprintf("Current directory %s is not last directory %s.\n", $cvsroot . "/" .$files[0], $_);
494 exit 0;
496 if ($debug) {
497 print STDERR sprintf("Current directory %s is last directory %s -- all commits done.\n", $files[0], $_);
501 # End Of Commits!
504 # This is it. The commits are all finished. Lump everything together
505 # into a single message, fire a copy off to the mailing list, and drop
506 # it on the end of the Changes file.
510 # Produce the final compilation of the log messages
512 @text = ();
513 @status_txt = ();
514 push(@text, &build_header());
515 push(@text, "");
517 for ($i = 0; ; $i++) {
518 last if (! -e "$LOG_FILE.$i.$id"); # we're done them all!
519 @lines = &read_logfile("$CHANGED_FILE.$i.$id", "");
520 if ($#lines >= 0) {
521 push(@text, "Modified files:");
522 push(@text, &format_lists(@lines));
524 @lines = &read_logfile("$ADDED_FILE.$i.$id", "");
525 if ($#lines >= 0) {
526 push(@text, "Added files:");
527 push(@text, &format_lists(@lines));
529 @lines = &read_logfile("$REMOVED_FILE.$i.$id", "");
530 if ($#lines >= 0) {
531 push(@text, "Removed files:");
532 push(@text, &format_lists(@lines));
534 if ($#text >= 0) {
535 push(@text, "");
537 @lines = &read_logfile("$LOG_FILE.$i.$id", "\t");
538 if ($#lines >= 0) {
539 push(@text, "Log message:");
540 push(@text, @lines);
541 push(@text, "");
543 if ($do_status) {
544 local(@changed_files);
546 @changed_files = ();
547 push(@changed_files, &read_logfile("$CHANGED_FILE.$i.$id", ""));
548 push(@changed_files, &read_logfile("$ADDED_FILE.$i.$id", ""));
549 push(@changed_files, &read_logfile("$REMOVED_FILE.$i.$id", ""));
551 if ($debug) {
552 print STDERR "main: pre-sort changed_files = ", join(":", @changed_files), ".\n";
554 sort(@changed_files);
555 if ($debug) {
556 print STDERR "main: post-sort changed_files = ", join(":", @changed_files), ".\n";
559 foreach $dofile (@changed_files) {
560 if ($dofile =~ /\/$/) {
561 next; # ignore the silly "dir" entries
563 if ($debug) {
564 print STDERR "main(): doing 'cvs -nQq status -v $dofile'\n";
566 open(STATUS, "-|") || exec 'cvs', '-nQq', 'status', '-v', $dofile;
567 while (<STATUS>) {
568 chop;
569 push(@status_txt, $_);
575 # Write to the commitlog file
577 if ($commitlog) {
578 &write_commitlog($commitlog, @text);
581 if ($#status_txt >= 0) {
582 push(@text, @status_txt);
585 # Mailout the notification.
587 &mail_notification(@text);
589 # cleanup
591 if (! $debug) {
592 &cleanup_tmpfiles();
595 exit 0;