terry%mozilla.org f390ae9051 Reworked preferences UI. Added ability to turn off "My bugs" link at
bottom.  Made "My bugs" show bugs you own AND bugs you submitted.
Added ability to display your named queries in the footer.  Many
random bugfixes.


git-svn-id: svn://10.0.0.236/trunk@58576 18797224-902f-48f8-a5cc-f745e15eee43
2000-01-25 07:54:57 +00:00

735 lines
22 KiB
Perl
Executable File

#!/usr/bonsaitools/bin/perl -w
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# The contents of this file are subject to the Mozilla Public
# License Version 1.1 (the "License"); you may not use this file
# except in compliance with the License. You may obtain a copy of
# the License at http://www.mozilla.org/MPL/
#
# Software distributed under the License is distributed on an "AS
# IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
# implied. See the License for the specific language governing
# rights and limitations under the License.
#
# The Original Code is the Bugzilla Bug Tracking System.
#
# The Initial Developer of the Original Code is Netscape Communications
# Corporation. Portions created by Netscape are
# Copyright (C) 1998 Netscape Communications Corporation. All
# Rights Reserved.
#
# Contributor(s): Terry Weissman <terry@mozilla.org>,
# Bryce Nesbitt <bryce-mozilla@nextbus.com>
# To recreate the shadow database, run "processmail regenerate" .
use diagnostics;
use strict;
require "globals.pl";
$| = 1;
umask(0);
$::lockcount = 0;
my $regenerate = 0;
my $nametoexclude = "";
my @forcecc;
sub Lock {
if ($::lockcount <= 0) {
$::lockcount = 0;
if (!open(LOCKFID, ">>data/maillock")) {
mkdir "data", 0777;
chmod 0777, "data";
open(LOCKFID, ">>data/maillock") || die "Can't open lockfile.";
}
my $val = flock(LOCKFID,2);
if (!$val) { # '2' is magic 'exclusive lock' const.
print "Lock failed: $val\n";
}
chmod 0666, "data/maillock";
}
$::lockcount++;
}
sub Unlock {
$::lockcount--;
if ($::lockcount <= 0) {
flock(LOCKFID,8); # '8' is magic 'unlock' const.
close LOCKFID;
}
}
sub FileSize {
my ($filename) = (@_);
my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,
$atime,$mtime,$ctime,$blksize,$blocks)
= stat($filename);
if (defined $size) {
return $size;
}
return -1;
}
sub Different {
my ($file1, $file2) = (@_);
my $size1 = FileSize($file1);
my $size2 = FileSize($file2);
if ($size1 != $size2) {
return 1;
}
open(FID1, "<$file1") || die "Can't open $file1";
open(FID2, "<$file2") || die "Can't open $file2";
my $d1;
my $d2;
if (read(FID1, $d1, $size1) ne $size1) {
die "Can't read $size1 bytes from $file1";
}
if (read(FID2, $d2, $size2) ne $size2) {
die "Can't read $size2 bytes from $file2";
}
close FID1;
close FID2;
return ($d1 ne $d2);
}
sub DescCC {
my ($cclist) = (@_);
if (scalar(@$cclist) <= 0) {
return "";
}
return "Cc: " . join(", ", @$cclist) . "\n";
}
sub DescDependencies {
my ($id) = (@_);
if (!Param("usedependencies")) {
return "";
}
my $result = "";
my $me = "blocked";
my $target = "dependson";
my $title = "BugsThisDependsOn";
for (1..2) {
SendSQL("select $target from dependencies where $me = $id order by $target");
my @list;
while (MoreSQLData()) {
push(@list, FetchOneColumn());
}
if (@list) {
my @verbose;
my $count = 0;
foreach my $i (@list) {
SendSQL("select bug_status, resolution from bugs where bug_id = $i");
my ($bug_status, $resolution) = (FetchSQLData());
my $desc;
if ($bug_status eq "NEW" || $bug_status eq "ASSIGNED" ||
$bug_status eq "REOPENED") {
$desc = "";
} else {
$desc = "[$resolution]";
}
push(@verbose, $i . "$desc");
$count++;
}
if ($count > 5) {
$result .= "$title: Big list (more than 5) has been omitted\n";
} else {
$result .= "$title: " . join(', ', @verbose) . "\n";
}
}
my $tmp = $me;
$me = $target;
$target = $tmp;
$title = "OtherBugsDependingOnThis";
}
return $result;
}
sub GetBugText {
my ($id) = (@_);
undef %::bug;
my @collist = ("bug_id", "product", "version", "rep_platform", "op_sys",
"bug_status", "resolution", "priority", "bug_severity",
"assigned_to", "reporter", "bug_file_loc",
"short_desc", "component", "qa_contact", "target_milestone",
"status_whiteboard", "groupset");
my $query = "select " . join(", ", @collist) .
" from bugs where bug_id = $id";
SendSQL($query);
my @row;
if (!(@row = FetchSQLData())) {
return "";
}
foreach my $field (@collist) {
$::bug{$field} = shift @row;
if (!defined $::bug{$field}) {
$::bug{$field} = "";
}
}
$::bug{'assigned_to'} = DBID_to_name($::bug{'assigned_to'});
$::bug{'reporter'} = DBID_to_name($::bug{'reporter'});
my $qa_contact = "";
my $target_milestone = "";
my $status_whiteboard = "";
if (Param('useqacontact') && $::bug{'qa_contact'} > 0) {
$::bug{'qa_contact'} = DBID_to_name($::bug{'qa_contact'});
$qa_contact = "QAContact: $::bug{'qa_contact'}\n";
} else {
$::bug{'qa_contact'} = "";
}
if (Param('usetargetmilestone') && $::bug{'target_milestone'} ne "") {
$target_milestone = "TargetMilestone: $::bug{'target_milestone'}\n";
}
if (Param('usestatuswhiteboard') && $::bug{'status_whiteboard'} ne "") {
$status_whiteboard = "StatusWhiteboard: $::bug{'status_whiteboard'}\n";
}
$::bug{'long_desc'} = GetLongDescription($id);
my @cclist;
@cclist = split(/,/, ShowCcList($id));
my @voterlist;
SendSQL("select profiles.login_name from votes, profiles where votes.bug_id = $id and profiles.userid = votes.who");
while (MoreSQLData()) {
my $v = FetchOneColumn();
push(@voterlist, $v);
}
$::bug{'cclist'} = join(',', @cclist);
$::bug{'voterlist'} = join(',', @voterlist);
if (Param("prettyasciimail")) {
my $temp = formline <<'END',$::bug{'short_desc'},$id,$::bug{'product'},$::bug{'bug_status'},$::bug{'version'},$::bug{'resolution'},$::bug{'rep_platform'},$::bug{'bug_severity'},$::bug{'op_sys'},$::bug{'priority'},$::bug{'component'},$::bug{'assigned_to'},$::bug{'reporter'},$qa_contact,DescCC(\@cclist),$target_milestone,${status_whiteboard},$::bug{'bug_file_loc'},DescDependencies($id);
+============================================================================+
| @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
+----------------------------------------------------------------------------+
| Bug #: @<<<<<<<<<<< Product: @<<<<<<<<<<<<<<<<<<<<<< |
| Status: @<<<<<<<<<<<<<<<<<< Version: @<<<<<<<<<<<<<<<<<<<<<< |
| Resolution: @<<<<<<<<<<<<<<<<<< Platform: @<<<<<<<<<<<<<<<<<<<<<< |
| Severity: @<<<<<<<<<<<<<<<<<< OS/Version: @<<<<<<<<<<<<<<<<<<<<<< |
| Priority: @<<<<<<<<<<<<<<<<<< Component: @<<<<<<<<<<<<<<<<<<<<<< |
+----------------------------------------------------------------------------+
| Assigned To: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
| Reported By: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
| ~QA Contact: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
| ~ CC list: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
+----------------------------------------------------------------------------+
| ~ Milestone: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
|~ Whiteboard: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
| URL: @<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
|~Dependencies: ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< |
+============================================================================+
| DESCRIPTION |
END
my $prettymail = $^A . $::bug{'long_desc'};
return $prettymail;
} else {
return "Bug\#: $id
Product: $::bug{'product'}
Version: $::bug{'version'}
Platform: $::bug{'rep_platform'}
OS/Version: $::bug{'op_sys'}
Status: $::bug{'bug_status'}
Resolution: $::bug{'resolution'}
Severity: $::bug{'bug_severity'}
Priority: $::bug{'priority'}
Component: $::bug{'component'}
AssignedTo: $::bug{'assigned_to'}
ReportedBy: $::bug{'reporter'}
$qa_contact$target_milestone${status_whiteboard}URL: $::bug{'bug_file_loc'}
" . DescCC(\@cclist) . "Summary: $::bug{'short_desc'}
" . DescDependencies($id) . "
$::bug{'long_desc'}
";
}
}
my $didexclude = 0;
my %seen;
my @sentlist;
sub fixaddresses {
my ($field, $list) = (@_);
my @result;
foreach my $i (@$list) {
if (!defined $i || $i eq "") {
next;
}
SendSQL("select emailnotification, groupset & $::bug{'groupset'} from profiles where login_name = " .
SqlQuote($i));
my ($emailnotification, $groupset) = (FetchSQLData());
if ($groupset ne $::bug{'groupset'}) {
next;
}
if ($emailnotification eq "CConly") {
if ($field ne "cc") {
next;
}
}
if ($emailnotification eq "ExcludeSelfChanges" &&
(lc($i) eq $nametoexclude)) {
$didexclude = 1;
next;
}
if (!defined $::nomail{$i} && !defined $seen{$i}) {
push(@result, $i . Param('emailsuffix'));
$seen{$i} = 1;
}
}
return join(", ", @result);
}
sub Log {
my ($str) = (@_);
Lock();
open(FID, ">>data/maillog") || die "Can't write to data/maillog";
print FID time2str("%D %H:%M", time()) . ": $str\n";
close FID;
Unlock();
}
sub FormatTriple {
my ($a, $b, $c) = (@_);
$^A = "";
my $temp = formline << 'END', $a, $b, $c;
^>>>>>>>>>>>>>>>>>>|^<<<<<<<<<<<<<<<<<<<<<<<<<<<|^<<<<<<<<<<<<<<<<<<<<<<<<<<~~
END
; # This semicolon appeases my emacs editor macros. :-)
return $^A;
}
sub FormatDouble {
my ($a, $b) = (@_);
$a .= ":";
$^A = "";
my $temp = formline << 'END', $a, $b;
^>>>>>>>>>>>>>>>>>> ^<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<~~
END
; # This semicolon appeases my emacs editor macros. :-)
return $^A;
}
sub NewProcessOneBug {
my ($id) = (@_);
my @headerlist;
my %values;
my %defmailhead;
my %fielddescription;
my $msg = "";
SendSQL("SELECT name, description, mailhead FROM fielddefs " .
"ORDER BY sortkey");
while (MoreSQLData()) {
my ($field, $description, $mailhead) = (FetchSQLData());
push(@headerlist, $field);
$defmailhead{$field} = $mailhead;
$fielddescription{$field} = $description;
}
SendSQL("SELECT " . join(',', @::log_columns) . ", lastdiffed, now() " .
"FROM bugs WHERE bug_id = $id");
my @row = FetchSQLData();
foreach my $i (@::log_columns) {
$values{$i} = shift(@row);
}
my ($start, $end) = (@row);
$values{'cc'} = ShowCcList($id);
my @voterlist;
SendSQL("SELECT profiles.login_name FROM votes, profiles " .
"WHERE votes.bug_id = $id AND profiles.userid = votes.who");
while (MoreSQLData()) {
push(@voterlist, FetchOneColumn());
}
$values{'assigned_to'} = DBID_to_name($values{'assigned_to'});
$values{'reporter'} = DBID_to_name($values{'reporter'});
if ($values{'qa_contact'}) {
$values{'qa_contact'} = DBID_to_name($values{'qa_contact'});
}
my @diffs;
SendSQL("SELECT profiles.login_name, fielddefs.description, " .
" bug_when, oldvalue, newvalue " .
"FROM bugs_activity, fielddefs, profiles " .
"WHERE bug_id = $id " .
" AND fielddefs.fieldid = bugs_activity.fieldid " .
" AND profiles.userid = who " .
" AND bug_when > '$start' " .
" AND bug_when <= '$end' " .
"ORDER BY bug_when"
);
while (MoreSQLData()) {
my @row = FetchSQLData();
push(@diffs, \@row);
}
my $difftext = "";
my $lastwho = "";
foreach my $ref (@diffs) {
my ($who, $what, $when, $old, $new) = (@$ref);
if ($who ne $lastwho) {
$lastwho = $who;
$difftext .= "\n$who changed:\n\n";
$difftext .= FormatTriple("What ", "Old Value", "New Value");
$difftext .= ('-' x 76) . "\n";
}
$difftext .= FormatTriple($what, $old, $new);
}
$difftext = trim($difftext);
my $deptext = "";
my $resid =
SendSQL("SELECT bugs_activity.bug_id, fielddefs.name, " .
" oldvalue, newvalue " .
"FROM bugs_activity, dependencies, fielddefs ".
"WHERE bugs_activity.bug_id = dependencies.dependson " .
" AND dependencies.blocked = $id " .
" AND fielddefs.fieldid = bugs_activity.fieldid" .
" AND (fielddefs.name = 'bug_status' " .
" OR fielddefs.name = 'resolution') " .
" AND bug_when > '$start' " .
" AND bug_when <= '$end' " .
"ORDER BY bug_when, bug_id");
my $thisdiff = "";
my $lastbug = "";
my $interestingchange = 0;
while (MoreSQLData()) {
my ($bug, $what, $old, $new) = (FetchSQLData());
if ($bug ne $lastbug) {
if ($interestingchange) {
$deptext .= $thisdiff;
}
$lastbug = $bug;
$thisdiff =
"\nThis bug depends on bug $bug, which changed state:\n\n";
$thisdiff .= FormatTriple("What ", "Old Value", "New Value");
$thisdiff .= ('-' x 76) . "\n";
$interestingchange = 0;
}
$thisdiff .= FormatTriple($fielddescription{$what}, $old, $new);
if ($what eq 'bug_status' && IsOpenedState($old) ne IsOpenedState($new)) {
$interestingchange = 1;
}
}
if ($interestingchange) {
$deptext .= $thisdiff;
}
$deptext = trim($deptext);
if ($deptext) {
$difftext = trim($difftext . "\n\n" . $deptext);
}
my $newcomments = GetLongDescription($id, $start, $end);
my $count = 0;
for my $person ($values{'assigned_to'}, $values{'reporter'},
split(/,/, $values{'cc'}),
@voterlist,
@forcecc) {
$count++;
if ($seen{$person}) {
next;
}
SendSQL("SELECT userid, emailnotification, newemailtech," .
" groupset & $values{'groupset'} " .
"FROM profiles WHERE login_name = " . SqlQuote($person));
my ($userid, $emailnotification, $newemailtech,
$groupset) = (FetchSQLData());
if (!$newemailtech || !Param('newemailtech')) {
next;
}
$seen{$person} = 1;
if ($groupset ne $values{'groupset'}) {
next;
}
if ($emailnotification eq "ExcludeSelfChanges" &&
lc($person) eq $nametoexclude) {
$didexclude = 1;
next;
}
if ($emailnotification eq "CCOnly" && $count < 3) {
next;
}
my %mailhead = %defmailhead;
# SendSQL("SELECT name, mailhead, maildiffs FROM diffprefs, fielddefs WHERE fielddefs.fieldid = diffprefs.fieldid AND userid = $userid");
# while (MoreSQLData()) {
# my ($field, $h, $d) = (FetchSQLData());
# $mailhead{$field} = $h;
# $maildiffs{$field} = $d;
# }
# my $maxlen = 0;
# foreach my $f (keys %mailhead) {
# if ($mailhead{$f}) {
# my $l = length($fielddescription{$f});
# if ($maxlen < $l) {
# $maxlen = $l;
# }
# }
# }
my $head = "";
foreach my $f (@headerlist) {
if ($mailhead{$f}) {
my $value = $values{$f};
if (!defined $value) {
# Probaby ought to whine or something. ###
next;
}
my $desc = $fielddescription{$f};
$head .= FormatDouble($desc, $value);
# my $extra = $maxlen - length($desc);
# $head .= ($extra x " ");
# $head .= $desc . ": ";
# while (1) {
# if (length($value) < 70) {
# $head .= $value . "\n";
# last;
# }
# my $pos = rindex($value, " ", 70);
# if ($pos < 0) {
# $pos = rindex($value, ",", 70);
# if ($pos < 0) {
# $pos = 70;
# }
# }
# $head .= substr($value, 0, 70) . "\n";
# $head .= (($extra + 2) x " ");
# $value = substr($value, 70);
# }
}
}
if ($difftext eq "" && $newcomments eq "") {
# Whoops, no differences!
next;
}
my $isnew = ($start !~ m/[1-9]/);
my %substs;
$substs{"neworchanged"} = $isnew ? "New" : "Changed";
$substs{"to"} = $person;
$substs{"cc"} = '';
$substs{"bugid"} = $id;
if ($isnew) {
$substs{"diffs"} = $head . "\n\n" . $newcomments;
} else {
$substs{"diffs"} = $difftext . "\n\n" . $newcomments;
}
$substs{"summary"} = $values{'short_desc'};
my $template = Param("newchangedmail");
my $msg = PerformSubsts($template, \%substs);
open(SENDMAIL, "|/usr/lib/sendmail -t") ||
die "Can't open sendmail";
print SENDMAIL trim($msg) . "\n";
close SENDMAIL;
push(@sentlist, $person);
}
SendSQL("UPDATE bugs SET lastdiffed = '$end', delta_ts = delta_ts " .
"WHERE bug_id = $id");
}
sub ProcessOneBug {
my $i = $_[0];
NewProcessOneBug($i);
my $old = "shadow/$i";
my $new = "shadow/$i.tmp.$$";
my $diffs = "shadow/$i.diffs.$$";
my $verb = "Changed";
if (!stat($old)) {
mkdir "shadow", 0777;
chmod 0777, "shadow";
open(OLD, ">$old") || die "Couldn't create null $old";
close OLD;
$verb = "New";
}
my $text = GetBugText($i);
if ($text eq "") {
die "Couldn't find bug $i.";
}
open(FID, ">$new") || die "Couldn't create $new";
print FID $text;
close FID;
if (Different($old, $new)) {
system("diff -c -b $old $new > $diffs");
my $tolist = fixaddresses("to",
[$::bug{'assigned_to'}, $::bug{'reporter'},
$::bug{'qa_contact'}]);
my @combinedcc;
foreach my $v (split(/,/, "$::bug{'cclist'},$::bug{'voterlist'}")) {
push @combinedcc, $v;
}
push (@combinedcc, (@forcecc));
my $cclist = fixaddresses("cc", \@combinedcc);
my $logstr = "Bug $i $verb";
if ($tolist ne "" || $cclist ne "") {
my %substs;
$substs{"fullbugreport"} = $text; # added ability to include the full bug report
$substs{"to"} = $tolist;
$substs{"cc"} = $cclist;
$substs{"bugid"} = $i;
$substs{"diffs"} = "";
open(DIFFS, "<$diffs") || die "Can't open $diffs";
while (<DIFFS>) {
$substs{"diffs"} .= $_;
}
close DIFFS;
$substs{"neworchanged"} = $verb;
$substs{"summary"} = $::bug{'short_desc'};
my $msg = PerformSubsts(Param("changedmail"), \%substs);
if (!$regenerate) {
# Note: fixaddresses may result in a Cc: only. This seems
# harmless.
open(SENDMAIL, "|/usr/lib/sendmail -t") ||
die "Can't open sendmail";
print SENDMAIL $msg;
close SENDMAIL;
foreach my $n (split(/[, ]+/, "$tolist,$cclist")) {
if ($n ne "") {
push(@sentlist, $n);
}
}
$logstr = "$logstr; mail sent to $tolist, $cclist";
}
}
unlink($diffs);
Log($logstr);
}
if (@sentlist) {
print "<B>Email sent to:</B> " . join(", ", @sentlist) . "\n";
if ($didexclude) {
print qq{<B>Excluding:</B> $nametoexclude (<a href="userprefs.cgi?bank=diffs">change your preferences</a> if you wish not to be excluded)\n};
}
}
rename($new, $old) || die "Can't rename $new to $old";
chmod 0666, $old;
if ($regenerate) {
print "$i ";
}
%seen = ();
@sentlist = ();
}
# Code starts here
ConnectToDatabase();
GetVersionTable();
Lock();
if (open(FID, "<data/nomail")) {
while (<FID>) {
$::nomail{trim($_)} = 1;
}
close FID;
}
# To recreate the shadow database, run "processmail regenerate" .
if ($ARGV[0] eq "regenerate") {
$regenerate = 1;
shift @ARGV;
SendSQL("select bug_id from bugs order by bug_id");
my @regenerate_list;
while (my @row = FetchSQLData()) {
push @regenerate_list, $row[0];
}
foreach my $i (@regenerate_list) {
ProcessOneBug($i);
Unlock();
Lock();
}
print("\n");
exit;
}
if ($ARGV[0] eq "-forcecc") {
shift(@ARGV);
foreach my $i (split(/,/, shift(@ARGV))) {
push(@forcecc, trim($i));
}
}
if (($#ARGV < 0) || ($#ARGV > 1)) {
print "Usage error: processmail {bugid} {nametoexclude}\nOr: processmail regenerate\n";
exit;
}
if ($#ARGV == 1) {
$nametoexclude = lc($ARGV[1]);
}
if ($ARGV[0] eq "rescanall") {
print "<br> Collecting bug ids...\n";
SendSQL("select bug_id from bugs where to_days(now()) - to_days(delta_ts) <= 2 order by bug_id");
my @list;
while (my @row = FetchSQLData()) {
push @list, $row[0];
}
foreach my $id (@list) {
$ARGV[0] = $id;
print "<br> Doing bug $id\n";
ProcessOneBug($ARGV[0]);
}
} else {
ProcessOneBug($ARGV[0]);
}
exit;