#!/usr/bin/perl -U # -*-Perl-*- # # # Perl filter to handle the log messages from the checkin of files in # a directory. This script will group the lists of files by log # message, and mail a single consolidated log message at the end of # the commit. # # This file assumes a pre-commit checking program that leaves the # names of the first and last commit directories in a temporary file. # # Contributed by David Hampton # # hacked greatly by Greg A. Woods # # Heavily modified for the Codendi project at Xerox # Copyright (c) Xerox Corporation, Codendi / Codendi Team, 2004-2006. All Rights Reserved # Usage: log_accum.pl [-d] [-s] [-M module] [[-m mailto] ...] [-f logfile] # -d - turn on debugging # -G database - interface to Gnats # -nodb - suppress default codendi database commit tracking # -m mailto - send mail to "mailto" (multiple) # -M modulename - set module name to "modulename" # -f logfile - write commit messages to logfile too # -s - *don't* run "cvs status -v" for each file # -T text - use TEXT in temp file names. # -C [reponame] - Generate viewvc URLS; must be run using %{sVv} # format string. # (DEPRECATED) -U URL - Base URL for viewvc if -C option (above) is used. # -D - generate diffs as part of the notification mail # # Configurable options # # Codendi specific # # Make sure that log_accum has the s flag and owned by codendiadm.codendiadm # Like this: # -rwsrwxr-x codendiadm codendiadm # # Use LWP for Codendi HTTP API use HTTP::Request::Common qw(POST); use LWP::UserAgent; use Encode; # DB connection use DBI; use Text::Iconv; $utils_path = $ENV{'CODENDI_UTILS_PREFIX'} || "/usr/share/codendi/src/utils"; require $utils_path."/include.pl"; require $utils_path."/group.pl"; require $utils_path."/cvs1/checkins.pl"; require $utils_path."/hudson.pl"; my $codendi_srv; if ($sys_force_ssl) { $codendi_srv="https://$sys_https_host"; } else { $codendi_srv="http://$sys_default_domain"; } $codendi_http_srv="http://$sys_default_domain"; # Base name of viewvc specification $VIEWVC_URL = "$codendi_srv/cvs/viewvc.php/"; $TMPDIR = $cvs_hook_tmp_dir; # Set this to something that takes "-s". #$MAILER = "/usr/bin/Mail"; # ... or set this to a sendmail clone: $SENDMAIL = "/usr/sbin/sendmail"; # Used with sprintf to form name of Gnats notification mailing list. # %s argument comes from -G option. $GNATS_MAIL_FORMAT = "%s-gnats\@sourceware.cygnus.com"; # Used with sprintf to form name of Gnats root directory. Potential # PR info is appended to see if PR actually exists. %s argument comes # from -G option. $GNATS_ROOT_FORMAT = "/sourceware/gnats/%s-db"; # Constants (don't change these!) # $STATE_NONE = 0; $STATE_CHANGED = 1; $STATE_ADDED = 2; $STATE_REMOVED = 3; $STATE_LOG = 4; # arrays to store all references. my %references; my %branches; # group_id my $group_id; # # Subroutines # sub set_temp_vars { local ($name) = @_; $LAST_FILE = sprintf ("$TMPDIR/#%s.lastdir", $name); $CHANGED_FILE = sprintf ("$TMPDIR/#%s.files.changed", $name); $ADDED_FILE = sprintf ("$TMPDIR/#%s.files.added", $name); $REMOVED_FILE = sprintf ("$TMPDIR/#%s.files.removed", $name); $LOG_FILE = sprintf ("$TMPDIR/#%s.files.log", $name); $URL_FILE = sprintf ("$TMPDIR/#%s.files.urls", $name); $DB_FILE = sprintf ("$TMPDIR/#%s.files.DB", $name); # Quote for use in a regexp. ($FILE_PREFIX = sprintf ("#%s.files", $name)) =~ s/(\W)/\\$1/g; } sub cleanup_tmpfiles { local($wd, @files); $wd = `pwd`; chdir("$TMPDIR") || die("Can't chdir(\"$TMPDIR\")\n"); opendir(DIR, "."); push(@files, grep(/^$FILE_PREFIX\..*\.$id$/, readdir(DIR))); closedir(DIR); foreach (@files) { unlink $_; } unlink $LAST_FILE . "." . $id; chdir($wd); } sub write_logfile { local($filename, @lines) = @_; open(FILE, ">$filename") || die("Cannot open log file >$filename $!.\n"); print FILE join("\n", @lines), "\n"; close(FILE); } sub format_names { local($dir, @files) = @_; local(@lines); if ($dir =~ /^\.\//) { $dir = $'; } if ($dir =~ /\/$/) { $dir = $`; } if ($dir eq "") { $dir = "."; } $format = "\t%-" . sprintf("%d", length($dir) > 15 ? length($dir) : 15) . "s%s "; $lines[0] = sprintf($format, $dir, ":"); if ($debug) { print STDERR "format_names(): dir = ", $dir, "; files = ", join(":", @files), ".\n"; } foreach $file (@files) { if (length($lines[$#lines]) + length($file) > 65) { $lines[++$#lines] = sprintf($format, " ", " "); } $lines[$#lines] .= $file . " "; } @lines; } sub format_lists { local(@lines) = @_; local(@text, @files, $lastdir); if ($debug) { print STDERR "format_lists(): ", join(":", @lines), "\n"; } @text = (); @files = (); $lastdir = shift @lines; # first thing is always a directory if ($lastdir !~ /.*\/$/) { die("Damn, $lastdir doesn't look like a directory!\n"); } foreach $line (@lines) { if ($line =~ /.*\/$/) { push(@text, &format_names($lastdir, @files)); $lastdir = $line; @files = (); } else { push(@files, $line); } } push(@text, &format_names($lastdir, @files)); @text; } sub accum_subject { local(@lines) = @_; local(@files, $lastdir); $lastdir = shift @lines; # first thing is always a directory @files = ($lastdir); if ($lastdir !~ /.*\/$/) { die("Damn, $lastdir doesn't look like a directory!\n"); } foreach $line (@lines) { if ($line =~ /.*\/$/) { $lastdir = $line; push(@files, $line); } else { push(@files, $lastdir . $line); } } @files; } sub compile_subject { local(@files) = @_; local($text, @a, @b, @c, $dir, $topdir); # find the highest common directory # FIXME: This function is royally dependent on receiving directory # entries, such as `my/dir/'. It would be nice to rewrite it to # robustly discover the greatest common directory, but I haven't the time. # --gord $dir = '-'; do { $topdir = $dir; foreach $file (@files) { if ($file =~ /.*\/$/) { if ($dir eq '-') { $dir = $file; } else { if (index($dir,$file) == 0) { $dir = $file; } elsif (index($file,$dir) != 0) { @a = split /\//,$file; @b = split /\//,$dir; @c = (); CMP: while ($#a > 0 && $#b > 0) { if ($a[0] eq $b[0]) { push(@c, $a[0]); shift @a; shift @b; } else { last CMP; } } $dir = join('/',@c) . '/'; } } } } } until $dir eq $topdir; # strip out directories and the common prefix topdir. chop $topdir; local ($topdir_offset) = length ($topdir); $topdir_offset ++ if ($topdir_offset > 0); $topdir = '' if ($topdir eq '.'); @c = ($modulename); $c[0] .= '/' . $topdir if ($topdir ne ''); foreach $file (@files) { if (!($file =~ /.*\/$/)) { # Append the filename stripped of the top directory. push(@c, substr($file, $topdir_offset)); } } # put it together and limit the length. $text = join(' ',@c); if (length($text) > 50) { $text = substr($text, 0, 47) . '...'; } $text; } sub checkin_describe { local ($dir) = @_; return "$login,$cvsroot,$modulename,$dir"; } sub db_checkins_add { local($index, $commit_id, @descs) = @_; @checkins = &read_logfile("$DB_FILE.$index.$id", ""); if ($debug) { print STDERR "treating for $DB_FILE.$index.$id \n"; } if ($sys_cvs_convert_cp1252_to_utf8) { $converter = Text::Iconv->new("CP1252", "UTF-8"); @descs = ($converter->convert("@descs")); } my ($dir, $i, $who, $repo); local $when = localtime(time); for ($i = 0; $i <= $#checkins; $i ++) { if ($checkins[$i] =~ /\/$/) { ($who, $repo, $module, $dir) = split(',', $checkins[$i]); } elsif ($checkins[$i] !~ /\//) { local ($added, $removed); $added = ''; $removed = ''; $dir =~ s/\/$//; local ($file, $version, $type, $branch) = split(",",$checkins[$i]); if ($debug) { print STDERR "add checkin for \n"; print STDERR " commit_id ", $commit_id, " \n"; print STDERR " who ", $who, " \n"; print STDERR " repo ", $repo, " \n"; print STDERR " module ", $module, " \n"; print STDERR " when ", $when, " \n"; print STDERR " dir ", $dir, " \n"; print STDERR " file ", $file, " \n"; print STDERR " type ", $type, " \n"; print STDERR " version ", $version, " \n"; print STDERR " branch ", $branch, " \n"; print STDERR " desc ", join("\n", @descs), " \n"; } if ($type eq 'c') { $added = '999'; $removed = '999'; } &db_add_record($commit_id, $who, $repo, $when, $module."/".$dir, $file, $type, $version, $branch, $added, $removed, @descs); } } } sub append_names_to_file { local($filename, $dir, @files) = @_; if (@files) { open(FILE, ">>$filename") || die("Cannot open file >>$filename.\n"); print FILE $dir, "\n"; print FILE join("\n", @files), "\n"; close(FILE); } } sub read_line { local($line); local($filename) = @_; open(FILE, "<$filename") || die("Cannot open file <$filename $!.\n"); $line = ; close(FILE); chop($line); $line; } sub read_logfile { local(@text); local($filename, $leader) = @_; open(FILE, "<$filename"); while () { chop; $line = $leader.$_; push(@text, $line); } close(FILE); @text; } sub build_header { local($header); local($sec,$min,$hour,$mday,$mon,$year,$wday,$yday) = localtime(time); local($timezone); # Compute timezone local($sec,$min,$hourGM,$mday,$mon,$yearGM,$wday,$ydayGM) = gmtime(time); # New Year's Eve.. if ($yearGM<$year) {$hourGM-=24;} elsif ($yearGM>$year) {$hourGM+=24;} elsif ($ydayGM>$yday) {$hourGM+=24;} elsif ($ydayGM<$yday) {$hourGM-=24;} if ($hourGM>$hour) { $timezone="GMT-".($hourGM-$hour); } else { $timezone="GMT+".($hour-$hourGM); } $header = sprintf("CVSROOT:\t%s\nModule name:\t%s\n", $cvsroot, $modulename); if (defined($branch)) { $header .= sprintf("Branch: \t%s\n", $branch); } $header .= sprintf("Changes by:\t%s\t%02d/%02d/%02d %02d:%02d:%02d %s", "$fullname <$mailname>", $year%100, $mon+1, $mday, $hour, $min, $sec, $timezone); } sub mail_notification { local($names, $subject, @text) = @_; print STDERR "mail_notification: to $names, subject $subject\n" if ($debug); $subject = encode("MIME-Header", decode("UTF-8", $subject)); if ($SENDMAIL ne '') { my ($comma_names) = join (', ', split (/\s+/, $names)); # Encode fullname $encfullname = encode('MIME-Q', $fullname); unshift (@text, "To: $comma_names", "Subject: $subject", "Reply-to: $mailname", "Content-type: text/plain; charset=utf-8", ''); # We need this blank line before the message body. open (MAIL, "| $SENDMAIL -bm -F\"$encfullname\" $names"); } else { open(MAIL, "| $MAILER -s \"$subject\" $names"); } print MAIL join("\n", @text), "\n"; close(MAIL); } sub write_commitlog { local($logfile, @text) = @_; open(FILE, ">>$logfile"); print FILE join("\n", @text), "\n\n"; close(FILE); } # Return a list of all ViewVC URLs for this commit. sub generate_viewvc_urls { local ($dir, $branch, @files) = @_; local (@sp, @result); local ($start) = $VIEWVC_URL; local ($args) = '?'; local ($need_amp) = 0; # This next if assumes that if a repository name is not given with the # -C option, we then have to append the project name to the VIEWVC_URL # and the project name has been defined by the -T option # (this works for the particular setup in savannah.gnu.org and Codendi # but will have to be changed in other sites.) if ($viewvc_name eq '') { $start .= $temp_name . '/'; } else { $args .= 'root=' . $viewvc_name; $need_amp = 1; } $start .= $dir . '/'; if ($branch ne '') { $args .= '&' if ($need_amp); $args .= 'pathrev=' . $branch; $need_amp = 1; } # Add an ampersand if we need one. $args .= '&' if ($need_amp); $need_amp = 0; local ($r1,$r2); foreach (@files) { # List is (FILE OLD-REV NEW-REV). @sp = split (','); $r1 = $sp[1]; $r2 = $sp[2]; if ($r1 eq 'NONE') # If it's a new file, it is listed completely { push (@result, ($start . $sp[0] . $args. 'revision=' . $r2 . '&view=markup')); } # If it's a file update, differences are shown. A value of NONE # for NEW-REV indicates a file removed and no URL is generated. elsif ($r2 ne 'NONE') { push (@result, ($start . $sp[0] . $args. 'r1=' . $r1 . '&r2=' . $r2 . '&roottype=cvs')); } } return @result; } sub get_branch { local ($file) = @_; if ($debug) { print STDERR "entering get_branch\n"; } local (@files, @keys, $key, $f); @keys = keys %branches; foreach $key (@keys) { $files = $branches{$key}; if ($debug) { print STDERR $key, " branch value ", $files, " inspection for ", $file, "\n"; } @filelist = split(' ', $files); foreach $f (@filelist) { if ($debug) { print STDERR $key, " branch inspection for ", $file, " and file ", $f, "\n"; } if ($file eq $f) { if ($debug) { print STDERR "branch found: ", $key, "\n"; } return $key; } } } if ($debug) { print STDERR "branch not found\n"; } return 'not found'; } # Return a list of all viewvc URLs for this commit. sub generate_checkins { local ($dir, @files) = @_; local (@sp, @result); if ($debug) { print STDERR "entering generate_checkins with ", $dir, ": ", join(' ', @files), "\n"; } local ($rev,$file); foreach (@files) { if ($debug) { print STDERR $_, " file in generate_checkins \n"; } # List is (FILE OLD-REV NEW-REV). @sp = split (','); $file = $sp[0]; $rev = $sp[2]; if ($debug) { print STDERR "looking for ",$file, $rev, " branch \n"; } # then, go and find the branch if any for file local ($branch) = &get_branch($file); local (@res) = ($file, $rev, $branch); push (@result, join(',', @res)); } if ($debug) { print STDERR " checkin : ", join(',', @res); } return @result; } # Codendi - extract all items that needs to be cross-referenced # in the log message sub extract_xrefs { my (@log) = @_; if ($sys_cvs_convert_cp1252_to_utf8) { $converter = Text::Iconv->new("CP1252", "UTF-8"); @log = ($converter->convert("@log")); } # Use Codendi HTTP API my $ua = LWP::UserAgent->new; $ua->agent('Codendi Perl Agent'); if (!$group_id) { $group_id=100; } $text=join("\n",@log); $cvstype="cvs_commit"; # HTTPS is not supported by LWP on RHEL3 -> use HTTP my $req = POST "$codendi_http_srv/api/reference/extractCross", [ group_id => "$group_id", text => "$text", rev_id=>"$commit_id", login=>"$login", type=>"$cvstype" ]; my $response = $ua->request($req); if ($response->is_success) { my $desc=""; my $match=""; my $link=""; foreach (split(/\n/,$response->content)) { chomp; if (! $_) {;} # skip empty lines elsif (!$desc) {$desc=$_;} elsif (!$match) {$match=$_;} else { $link=$_; $references{"$desc"}{"$match"}=$link; print STDERR "Found match: $match\n" if $debug; $desc=$match=$link=0; } } } else { warn $response->status_line; } } # # Main Body # # Initialize basic variables # $debug = 0; $id = getpgrp(); # note, you *must* use a shell which does setpgrp() $state = $STATE_NONE; local ($login, $gecos); $login = $ENV{'USER'}; if (! $login) { ($login, $gecos) = (getpwuid ($<))[0,6]; } else { $login = "nobody" if (! $login); $gecos = (getpwnam ($login))[6]; } # Determine the mailname and fullname. if ($gecos =~ /^([^<]*\s+)<(\S+@\S+)>/) { $fullname = $1; $mailname = $2; $fullname =~ s/\s+$//; } else { $fullname = $gecos; $fullname =~ s/,.*$//; local ($hostdomain, $hostname); chop($hostdomain = `hostname -f`); if ($hostdomain !~ /\./) { chop($hostname = `hostname`); if ($hostname !~ /\./) { chop($domainname = `domainname`); $hostdomain = $hostname . "." . $domainname; } else { $hostdomain = $hostname; } } $mailname = "$login\@$hostdomain"; } $cvsroot = $ENV{'CVSROOT'}; $do_status = 1; $modulename = ""; $temp_name = "temp"; $do_viewvc = 0; $viewvc_name = ''; $do_diffs = 0; $codendidb = 1; %diffmon = (); # parse command line arguments (file list is seen as one arg) # if ($debug) { print STDERR "Log accum ARGV:\n"; print STDERR @ARGV; print STDERR "\n---\n"; } while (@ARGV) { $arg = shift @ARGV; if ($arg eq '-d') { $debug = 1; print STDERR "Debug turned on...\n"; } elsif ($arg eq '-m') { $mailto = "$mailto " . shift @ARGV; } elsif ($arg eq '-M') { $modulename = shift @ARGV; } elsif ($arg eq '-s') { $do_status = 0; } elsif ($arg eq '-f') { ($commitlog) && die("Too many '-f' args\n"); $commitlog = shift @ARGV; } elsif ($arg eq '-G') { ($gnatsdb) && die("Too many '-G' args\n"); $gnatsdb = shift @ARGV; } elsif ($arg eq '-nodb') { $codendidb = 0; } elsif ($arg eq '-T') { $temp_name = shift @ARGV; } elsif ($arg eq '-C') { $do_viewvc = 1; $viewvc_name = shift @ARGV if ($#ARGV >= 0 && $ARGV[0] !~ /^-/); } elsif ($arg eq '-U') { my $OLD_VIEWVC_URL = shift @ARGV; #DEPRACATED: URL is now computed } elsif ($arg eq '-D') { $do_diffs = 2; } elsif ($arg =~ /^-D=(.*)$/) { $do_diffs = 2; $diffmailto = $1; } elsif ($arg =~ /^-D([^=]+)$/) { $do_diffs = 1 if (! $do_diffs); $diffmon{$1} = 'DEFAULT'; } elsif ($arg =~ /^-D([^=]+)=(.*)$/) { $do_diffs = 1 if (! $do_diffs); $diffmon{$1} = $2; } else { ($donefiles) && die("Too many arguments! Check usage.", $arg, "\n"); $donefiles = 1; # @files = split(/ /, $arg); # There is a problem with filenames containing a space. # example of input: 'module1/module1 test 2.zip,1.8,1.9 test3.zip,1.8,1.9 test 4 or 5.zip,1.8,1.9' # we can't just split on blank chars, so we'll rearrange the list like this: # module1/module1 # test 2.zip,1.8,1.9 # test3.zip,1.8,1.9 # test 4 ou 5.zip,1.8,1.9 ($mod,$filelist) = split(/ /, $arg,2); $arglist="$mod\n"; $line=0; foreach $f (split(/,/,$filelist)) { if ($line && (($line+1)%2) ) { ($v,$f2)=split(/ /,$f,2); if ($f2) {$arglist.="$v\n$f2,";} else {$arglist.="$v";} } else { $arglist.=$f.","; } $line++; } chomp($arglist); @files = split(/\n/, $arglist); if ($debug) { foreach $f (@files) { print STDERR $f, ", "; } } } } if ($debug) { print STDERR "\nentering log_accum\n"; } ($mailto) || ($codendidb) || die("No -m mail recipient specified\n"); $mailto =~ s/^\s+//; &set_temp_vars ($temp_name); # for now, the first "file" is the repository directory being committed, # relative to the $CVSROOT location # @path = split('/', $files[0]); # XXX there are some ugly assumptions in here about module names and # XXX directories relative to the $CVSROOT location -- really should # XXX read $CVSROOT/CVSROOT/modules, but that's not so easy to do, since # XXX we have to parse it backwards. # if ($modulename eq "") { $modulename = $path[0]; # I.e. the module name == top-level dir } if ($commitlog ne "") { $commitlog = $cvsroot . "/" . $modulename . "/" . $commitlog unless ($commitlog =~ /^\//); } if ($#path == 0) { $dir = "."; } else { $dir = join('/', @path[1..$#path]); } $dir = $dir . "/"; if ($debug) { print STDERR "module - ", $modulename, "\n"; print STDERR "dir - ", $dir, "\n"; print STDERR "path - ", join(":", @path), "\n"; print STDERR "files - ", join(":", @files), "\n"; print STDERR "id - ", $id, "\n"; } if ($codendidb) { &db_connect; # retrieve the group_id ($rootnull, $root, $gname) = split ('/', $cvsroot); $group_id = &set_group_info_from_name($gname); if ($debug) { print STDERR "group_repo: ", $gname, "\n"; print STDERR "group_id: ", $group_id, "\n"; } $cvsmailto = &cvsGroup_mailto(); $cvsmailheader = &cvsGroup_mail_header(); if ($debug) { print STDERR "\n header in db: ", $cvsmailheader, "\n"; } if ($cvsmailheader eq 'NULL') { ##$cvsmailheader = "[cvs-".$gname."]"; $cvsmailheader = ""; } if ($debug) { print STDERR "mailto: ", $mailto, "\n"; print STDERR "cvsmailto: ", $cvsmailto, "\n"; } if ($cvsmailto ne 'NULL') { if ($debug) { print STDERR "update mailto with database controled mail\n"; } if ($mailto) { $mailto = "$mailto , $cvsmailto"; } else { $mailto = $cvsmailto; } } if ($debug) { print STDERR "mailto: ", $mailto, "\n"; } } # Check for a new directory first. This appears with files set as follows: # # files[0] - "path/name/newdir" # files[1] - "-" # files[2] - "New" # files[3] - "directory" # if ($files[2] =~ /New/ && $files[3] =~ /directory/) { local(@text); @text = (); push(@text, &build_header()); push(@text, ""); push(@text, $files[0]); push(@text, ""); while () { chop; # Drop the newline push(@text, $_); } ## track in cvs database $db_track = 0; if ($codendidb) { $db_track = &isGroupCvsTracked(); if ($debug) { print STDERR "tracking: ", $db_track, "\n"; } } if ($db_track) { $commit_id = db_get_commit($debug); if ($debug) { print STDERR "desc: ", join ('\n', @text), "\n"; } @descs = split("\n", "\tDirectory\n\n".$files[0]."\n\n\tadded to repository."); &db_add_record($commit_id, $ENV{'USER'}, $cvsroot, '', $files[0], '.', 'a', '', '', 'NULL', 'NULL', @descs); } if ($mailto) { &mail_notification($mailto, $cvsmailheader.$files[0], @text); } if ($commitlog) { &write_commitlog($commitlog, @text); } exit 0; } # Iterate over the body of the message collecting information. # if ($debug) { print STDERR $codendidb, " value for codendidb \n"; } while () { if ($debug) { print STDERR $_, "\n"; } chop; # Drop the newline if (/^Modified Files/) { $state = $STATE_CHANGED; next; } if (/^Added Files/) { $state = $STATE_ADDED; next; } if (/^Removed Files/) { $state = $STATE_REMOVED; next; } if (/^Log Message/) { $state = $STATE_LOG; next; } if (/^\s*Tag:|Revision\/Branch/) { /^[^:]+:\s*(.*)/; $branch = $+; next; } s/^[ \t\n]+//; # delete leading whitespace s/[ \t\n]+$//; # delete trailing whitespace if ($state == $STATE_CHANGED) { push(@changed_files, split); if ($branches{'c,'.$branch}) { $branched = $branches{'c,'.$branch}.' '.$_; $branches{'c,'.$branch} = $branched; } else { $branches{'c,'.$branch} = $_; } } if ($state == $STATE_ADDED) { push(@added_files, split); if ($branches{'a,'.$branch}) { $branched = $branches{'a,'.$branch}.' '.$_; $branches{'a,'.$branch} = $branched; } else { $branches{'a,'.$branch} = $_; } } if ($state == $STATE_REMOVED) { push(@removed_files, split); if ($branches{'r,'.$branch}) { $branched = $branches{'c,'.$branch}.' '.$_; $branches{'r,'.$branch} = $branched; } else { $branches{'r,'.$branch} = $_; } } if ($state == $STATE_LOG) { push(@log_lines, $_); } } # Strip leading and trailing blank lines from the log message. Also # compress multiple blank lines in the body of the message down to a # single blank line. # while ($#log_lines > -1) { last if ($log_lines[0] ne ""); shift(@log_lines); } while ($#log_lines > -1) { last if ($log_lines[$#log_lines] ne ""); pop(@log_lines); } for ($i = $#log_lines; $i > 0; $i--) { if (($log_lines[$i - 1] eq "") && ($log_lines[$i] eq "")) { splice(@log_lines, $i, 1); } } # Check for an import command. This appears with files set as follows: # # files[0] - "path/name" # files[1] - "-" # files[2] - "Imported" # files[3] - "sources" # if ($files[2] =~ /Imported/ && $files[3] =~ /sources/) { local(@text); @text = (); push(@text, &build_header()); push(@text, ""); push(@text, "Log message:"); while ($#log_lines > -1) { push (@text, " " . $log_lines[0]); shift(@log_lines); } if ($mailto) { &mail_notification($mailto, $cvsmailheader." Import $file[0]", @text); } if ($commitlog) { &write_commitlog($commitlog, @text); } $db_track = 0; $db_events_list = 'NULL'; if ($codendidb) { $db_track = &isGroupCvsTracked(); $db_events_list = &cvsGroup_mailto(); if ($debug) { print STDERR "tracking: ", $db_track, "\n"; print STDERR "mailing: ", $db_events_list, "\n"; } $codendidb = $codendidb && (($db_events_list != 'NULL') || $db_track); } if ($db_track) { $commit_id = db_get_commit($debug); if ($debug) { print STDERR "desc: ", join ('\n', @text), "\n"; } &db_add_record($commit_id, $ENV{'USER'}, $cvsroot, '', $modulename, '.', 'a', '', '', 'NULL', 'NULL', @text); } exit 0; } # Compute the list of viewvc URLs if necessary. if ($do_viewvc) { @urls = &generate_viewvc_urls (join ('/', @path), $branch, @files[1 .. $#files]); } if ($codendidb) { $db_track = &isGroupCvsTracked(); $db_events_list = &cvsGroup_mailto(); if ($debug) { print STDERR "group_id: ", $group_id, "\n"; print STDERR "tracking: ", $db_track, "\n"; print STDERR "mailing: ", $db_events_list, "\n"; } $codendidb = $codendidb && (($db_events_list != 'NULL') || $db_track); } if ($codendidb) { if ($debug) { local @keys = keys %branches; print STDERR $#keys , " found branches\n"; print STDERR "calling for checkins with ", join ('/', @path), " with ", join ('|', @keys), " branches and ", join (' ', @files[1 .. $#files]), " files\n"; } @checkins = &generate_checkins (join ('/', @path), @files[1 .. $#files]); } if ($debug) { print STDERR "Searching for log file index..."; } # Find an index to a log file that matches this log message # for ($i = 0; ; $i++) { local(@text); last if (! -e "$LOG_FILE.$i.$id"); # the next available one @text = &read_logfile("$LOG_FILE.$i.$id", ""); last if ($#text == -1); # nothing in this file, use it last if (join(" ", @log_lines) eq join(" ", @text)); # it's the same log message as another } if ($debug) { print STDERR " found log file at $i.$id, now writing tmp files.\n"; } # Spit out the information gathered in this pass. # &append_names_to_file("$CHANGED_FILE.$i.$id", $dir, @changed_files); &append_names_to_file("$ADDED_FILE.$i.$id", $dir, @added_files); &append_names_to_file("$REMOVED_FILE.$i.$id", $dir, @removed_files); &append_names_to_file("$URL_FILE.$i.$id", $dir, @urls); if ($codendidb) { &append_names_to_file("$DB_FILE.$i.$id", &checkin_describe($dir), @checkins); } &write_logfile("$LOG_FILE.$i.$id", @log_lines); # Check whether this is the last directory. If not, quit. # if ($debug) { print STDERR "Checking current dir against last dir.\n"; } $_ = &read_line("$LAST_FILE.$id"); if ($_ ne $cvsroot . "/" . $files[0]) { if ($debug) { print STDERR sprintf("Current directory %s is not last directory %s.\n", $cvsroot . "/" .$files[0], $_); } exit 0; } if ($debug) { print STDERR sprintf("Current directory %s is last directory %s -- all commits done.\n", $files[0], $_); } # # End Of Commits! # # This is it. The commits are all finished. Lump everything together # into a single message, fire a copy off to the mailing list, and drop # it on the end of the Changes file. # # # Produce the final compilation of the log messages # @text = (); @status_txt = (); @diff_txt = (); @subject_files = (); @log_txt = (); push(@text, &build_header()); push(@text, ""); local ($commit_id); if ($db_track) { $commit_id = db_get_commit($debug); } for ($i = 0; ; $i++) { last if (! -e "$LOG_FILE.$i.$id"); # we're done them all! @lines = &read_logfile("$CHANGED_FILE.$i.$id", ""); if ($#lines >= 0) { push(@text, "Modified files:"); push(@text, &format_lists(@lines)); push(@subject_files, &accum_subject(@lines)); } @lines = &read_logfile("$ADDED_FILE.$i.$id", ""); if ($#lines >= 0) { push(@text, "Added files:"); push(@text, &format_lists(@lines)); push(@subject_files, &accum_subject(@lines)); } @lines = &read_logfile("$REMOVED_FILE.$i.$id", ""); if ($#lines >= 0) { push(@text, "Removed files:"); push(@text, &format_lists(@lines)); push(@subject_files, &accum_subject(@lines)); } if ($#text >= 0) { push(@text, ""); } @log_txt = &read_logfile("$LOG_FILE.$i.$id", "\t"); if ($#log_txt >= 0) { push(@text, "Log message:"); push(@text, @log_txt); push(@text, ""); } if ($db_track) { # Codendi: extract references from log message &extract_xrefs(@log_txt) &db_checkins_add($i, $commit_id, @log_txt); } @url_txt = &read_logfile("$URL_FILE.$i.$id", ""); if ($#url_txt >= 0) { push (@text, "Pointers to file changes:"); # Exclude directories listed in the file. push (@text, grep (! /\/$/, @url_txt)); push (@text, ""); } my $desc; my $match; my $initialized=''; foreach $desc (sort(keys(%references))) { if (!$initialized) { push (@text, ""); push (@text, "References:"); $initialized=1; } push (@text, ""); push (@text, "$desc:"); foreach $match (sort(keys %{$references{"$desc"}})) { push (@text," $match: ".$references{"$desc"}{"$match"}); print STDERR "Match: ".$references{"$desc"}{"$match"} if $debug; } } if ($do_status || $do_diffs) { local(@changed_files); @changed_files = (); push(@changed_files, &read_logfile("$CHANGED_FILE.$i.$id", "")); push(@changed_files, &read_logfile("$ADDED_FILE.$i.$id", "")); push(@changed_files, &read_logfile("$REMOVED_FILE.$i.$id", "")); if ($debug) { print STDERR "main: pre-sort changed_files = ", join(":", @changed_files), ".\n"; } # Add the directory to each changed_file. my ($lastdir, $i); for ($i = 0; $i <= $#changed_files; $i ++) { if ($changed_files[$i] =~ /\/$/) { $lastdir = $changed_files[$i]; } elsif ($lastdir ne './' && $changed_files[$i] !~ /\//) { $changed_files[$i] = $lastdir . $changed_files[$i]; } } @changed_files = sort(@changed_files); if ($debug) { print STDERR "main: post-sort changed_files = ", join(":", @changed_files), ".\n"; } foreach $dofile (@changed_files) { if ($dofile =~ /\/$/) { next; # ignore the silly "dir" entries } if ($do_diffs > 1 || $diffmon{$dofile}) { my $addr = 'DEFAULT'; $addr = $diffmailto if ($diffmailto); $addr = $diffmon{$dofile} if ($diffmon{$dofile}); my (@diff) = (); if ($debug) { print STDERR "main(): doing diff on $dofile\n"; } open (DIFF, "-|") || exec 'cvs', '-nQq', 'rdiff', '-tc', "$modulename/$dofile"; while () { # FIXME: Rearrange the diffs so that they make valid # `patch' input. chop; push (@diff, $_); } if ($addr eq 'DEFAULT') { push (@diff_txt, @diff); } else { &mail_notification ($addr, $cvsmailheader." Changes to $modulename/$dofile", @diff); } } if ($do_status) { if ($debug) { print STDERR "main(): doing status on $dofile\n"; } open(STATUS, "-|") || exec 'cvs', '-nQq', 'status', '-v', $dofile; while () { chop; push(@status_txt, $_); } } } } } if ($debug) { print STDERR "mailto: ", $mailto, "\n"; } if ($mailto) { $subject_txt = &compile_subject(@subject_files); # Write to the commitlog file # if ($commitlog) { &write_commitlog($commitlog, @text); } if ($#diff_txt >= 0) { push (@text, "Patches:"); push (@text, @diff_txt); } if ($#status_txt >= 0) { push(@text, @status_txt); } # Mailout the notification. # &mail_notification($mailto, $cvsmailheader.$subject_txt, @text); } # Send mail to Gnats, if required. if ($gnatsdb ne '') { $log_txt = join ("\n", @log_txt); if ($log_txt =~ m,PR ([a-z.]+/[0-9]+),) { $pr = $1; $file = sprintf ($GNATS_ROOT_FORMAT, $gnatsdb) . "/" . $pr; if (-f $file) { &mail_notification(sprintf ($GNATS_MAIL_FORMAT, $gnatsdb), $pr, @text); } } } # Trigger Continuous Integration build if needed. if ($codendidb) { &trigger_hudson_builds($group_id, 'cvs'); } # cleanup # if (! $debug) { &cleanup_tmpfiles(); } exit 0;