зеркало из https://github.com/mozilla/pjs.git
422 строки
13 KiB
Perl
Executable File
422 строки
13 KiB
Perl
Executable File
#!/usr/bonsaitools/bin/perl -wT
|
|
# -*- 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>
|
|
# Andreas Franke <afranke@mathweb.org>
|
|
# Christian Reis <kiko@async.com.br>
|
|
|
|
use diagnostics;
|
|
use strict;
|
|
|
|
use lib qw(.);
|
|
require "CGI.pl";
|
|
|
|
use vars %::FORM;
|
|
|
|
ConnectToDatabase();
|
|
|
|
quietly_check_login();
|
|
|
|
# More warning suppression silliness.
|
|
$::userid = $::userid;
|
|
$::usergroupset = $::usergroupset;
|
|
|
|
######################################################################
|
|
# Begin Data/Security Validation
|
|
######################################################################
|
|
|
|
# Make sure the bug ID is a positive integer representing an existing
|
|
# bug that the user is authorized to access
|
|
ValidateBugID($::FORM{'id'});
|
|
|
|
my $id = $::FORM{'id'};
|
|
my $hide_resolved = $::FORM{'hide_resolved'} || 0;
|
|
my $maxdepth = $::FORM{'maxdepth'} || 0;
|
|
|
|
if ($maxdepth !~ /^\d+$/) { $maxdepth = 0 };
|
|
if ($hide_resolved !~ /^\d+$/ || $hide_resolved != 1) { $hide_resolved = 0 };
|
|
|
|
######################################################################
|
|
# End Data/Security Validation
|
|
######################################################################
|
|
|
|
#
|
|
# Globals
|
|
|
|
# A hash to count visited bugs, and also to avoid processing repeated bugs
|
|
my %seen;
|
|
|
|
# A hash to keep track of the bugs we print for the 'as buglist' links.
|
|
my %printed;
|
|
|
|
# HTML output generated in the parse of the dependency tree. This is a
|
|
# global only to avoid excessive complication in the recursion invocation
|
|
my $html;
|
|
|
|
# Saves the largest of the two actual depths of the trees
|
|
my $realdepth = 0;
|
|
|
|
# The scriptname for use as FORM ACTION.
|
|
my $scriptname = $::ENV{'SCRIPT_NAME'}; # showdependencytree.cgi
|
|
|
|
#
|
|
# Functions
|
|
|
|
# DumpKids recurses through the bug hierarchy starting at bug i, and
|
|
# appends the bug information found to the html global variable. The
|
|
# parameters are not straightforward, so look at the examples.
|
|
#
|
|
# DumpKids(i, target [, depth])
|
|
#
|
|
# Params
|
|
# i: The bug id to analyze
|
|
# target: The type we are looking for; either "blocked" or "dependson"
|
|
# Optional
|
|
# depth: The current dependency depth we are analyzing, used during
|
|
# recursion
|
|
# Globals Modified
|
|
# html: Bug descriptions are appended here
|
|
# realdepth: We set the maximum depth of recursion reached
|
|
# seen: We store the bugs analyzed so far
|
|
# printed: We store those bugs we actually print, for the "buglist" link
|
|
# Globals Referenced
|
|
# maxdepth
|
|
# hide_resolved
|
|
#
|
|
# Examples:
|
|
# DumpKids(163, "blocked");
|
|
# will look for bugs that depend on bug 163
|
|
# DumpKids(163, "dependson");
|
|
# will look for bugs on which bug 163 depends
|
|
|
|
sub DumpKids {
|
|
my ($i, $target, $depth) = (@_);
|
|
my $bgcolor = "#d9d9d9";
|
|
my $fgcolor = "#000000";
|
|
my $me;
|
|
if (! defined $depth) { $depth = 1; }
|
|
|
|
if ($target eq "blocked") {
|
|
$me = "dependson";
|
|
} else {
|
|
$me = "blocked";
|
|
}
|
|
SendSQL("select $target from dependencies where $me=$i order by $target");
|
|
my @list;
|
|
while (MoreSQLData()) {
|
|
push(@list, FetchOneColumn());
|
|
}
|
|
if (@list) {
|
|
my $list_started = 0;
|
|
foreach my $kid (@list) {
|
|
my ($bugid, $stat, $milestone) = ("", "", "");
|
|
my ($userid, $short_desc) = ("", "");
|
|
if (Param('usetargetmilestone')) {
|
|
SendSQL(SelectVisible("select bugs.bug_id, bug_status, target_milestone, assigned_to, short_desc from bugs where bugs.bug_id = $kid", $::userid, $::usergroupset));
|
|
($bugid, $stat, $milestone, $userid, $short_desc) = (FetchSQLData());
|
|
} else {
|
|
SendSQL(SelectVisible("select bugs.bug_id, bug_status, assigned_to, short_desc from bugs where bugs.bug_id = $kid", $::userid, $::usergroupset));
|
|
($bugid, $stat, $userid, $short_desc) = (FetchSQLData());
|
|
|
|
}
|
|
if (! defined $bugid) { next; }
|
|
my $opened = IsOpenedState($stat);
|
|
if ($hide_resolved && ! $opened) { next; }
|
|
|
|
# If we specify a maximum depth, we hide the output when
|
|
# that depth has occured, but continue recursing so we know
|
|
# the real maximum depth of the tree.
|
|
if (! $maxdepth || $depth <= $maxdepth) {
|
|
if (! $list_started) { $html .= "<ul>"; $list_started = 1 }
|
|
$html .= "<li>";
|
|
if (! $opened) {
|
|
$html .= qq|<strike><span style="color: $fgcolor; background-color: $bgcolor;">
|
|
|;
|
|
}
|
|
|
|
if (exists $printed{$kid}) {
|
|
$short_desc = "<<em>This bug appears elsewhere in this tree</em>>";
|
|
}
|
|
else {
|
|
$short_desc = html_quote($short_desc);
|
|
}
|
|
|
|
SendSQL("select login_name from profiles where userid = $userid");
|
|
my ($owner) = (FetchSQLData());
|
|
if ((Param('usetargetmilestone')) && ($milestone)) {
|
|
$html .= qq|
|
|
<a href="show_bug.cgi?id=$kid">$kid [$milestone, $owner]
|
|
- $short_desc.</a>
|
|
|;
|
|
} else {
|
|
$html .= qq|
|
|
<a href="show_bug.cgi?id=$kid">$kid [$owner] -
|
|
$short_desc.</a>\n|;
|
|
}
|
|
if (! $opened) { $html .= "</span></strike>"; }
|
|
|
|
$printed{$kid} = 1;
|
|
} # End hideable output
|
|
|
|
# Store the maximum depth so far
|
|
$realdepth = $realdepth < $depth ? $depth : $realdepth;
|
|
if (!(exists $seen{$kid})) {
|
|
$seen{$kid} = 1;
|
|
DumpKids($kid, $target, $depth + 1);
|
|
}
|
|
}
|
|
if ($list_started) { $html .= "</ul>"; }
|
|
}
|
|
}
|
|
|
|
# makeTreeHTML calls DumpKids and generates the HTML output for a
|
|
# dependency/blocker section.
|
|
#
|
|
# makeTreeHTML(i, linked_id, target);
|
|
#
|
|
# Params
|
|
# i: Bug id
|
|
# linked_id: Linkified bug_id used to linkify the bug
|
|
# target: The type we are looking for; either "blocked" or "dependson"
|
|
# Globals modified
|
|
# html [Also modified in our call to DumpKids]
|
|
# Globals referenced
|
|
# seen [Also modified by DumpKids]
|
|
# maxdepth
|
|
# realdepth
|
|
#
|
|
# Example:
|
|
# $depend_html = makeTreeHTML(83058, <A HREF="...">83058</A>, "dependson");
|
|
# Will generate HTML for bugs that depend on bug 83058
|
|
|
|
sub makeTreeHTML {
|
|
my ($i, $linked_id, $target) = @_;
|
|
|
|
# Clean up globals for this run
|
|
$html = "";
|
|
%seen = ();
|
|
%printed = ();
|
|
|
|
DumpKids($i, $target);
|
|
my $tmphtml = $html;
|
|
|
|
# Output correct heading
|
|
$html = "<h3>Bugs that bug $linked_id ".($target eq "blocked" ?
|
|
"blocks" : "depends on");
|
|
|
|
# If there are some bugs in this tree, add a link to view them
|
|
# as a bug list, and add another link to edit them on the "change
|
|
# several bugs" page if there is more than one and the user has
|
|
# "editbugs" privileges.
|
|
if ((scalar keys %printed) > 0) {
|
|
my $link = "buglist.cgi?bug_id=" . join(',', keys %printed);
|
|
$html .= qq~ (<a href="$link">view as bug list</a>~;
|
|
if (UserInGroup("editbugs") && (scalar keys %printed) > 1) {
|
|
$html .= qq~ | <a href="$link&tweak=1">change several</a>~;
|
|
}
|
|
$html .= ')';
|
|
}
|
|
|
|
# Provide feedback for omitted bugs
|
|
if ($maxdepth || $hide_resolved) {
|
|
$html .= " <small><b>(Only ";
|
|
if ($hide_resolved) { $html .= "open "; }
|
|
$html .= "bugs ";
|
|
if ($maxdepth) { $html .= "whose depth is less than $maxdepth "; }
|
|
$html .= "will be shown)</b></small>";
|
|
}
|
|
|
|
$html .= "</h3>";
|
|
$html .= $tmphtml;
|
|
|
|
# If no bugs were found, say so
|
|
if ((scalar keys %printed) == 0) {
|
|
$html .= " None<p>\n";
|
|
}
|
|
|
|
return $html;
|
|
}
|
|
|
|
# Draw the actual form controls that make up the hide/show resolved and
|
|
# depth control toolbar.
|
|
#
|
|
# drawDepForm()
|
|
#
|
|
# Params
|
|
# none
|
|
# Globals modified
|
|
# none
|
|
# Globals referenced
|
|
# hide_resolved
|
|
# maxdepth
|
|
# realdepth
|
|
|
|
sub drawDepForm {
|
|
my $bgcolor = "#d0d0d0";
|
|
my ($hide_msg, $hide_input);
|
|
|
|
# Set the text and action for the hide resolved button.
|
|
if ($hide_resolved) {
|
|
$hide_input = '<input type="hidden" name="hide_resolved" value="0">';
|
|
$hide_msg = "Show Resolved";
|
|
} else {
|
|
$hide_input = '<input type="hidden" name="hide_resolved" value="1">';
|
|
$hide_msg = "Hide Resolved";
|
|
}
|
|
|
|
print qq|
|
|
<table cellpadding="3" border="0" cellspacing="0">
|
|
<tr>
|
|
|
|
<!-- Hide/show resolved button
|
|
Swaps text depending on the state of hide_resolved -->
|
|
<td bgcolor="$bgcolor" align="center">
|
|
<form method="get" action="$scriptname"
|
|
style="display: inline; margin: 0px;">
|
|
<input name="id" type="hidden" value="$id">
|
|
| . ( $maxdepth ?
|
|
qq|<input name="maxdepth" type="hidden" value="$maxdepth">|
|
|
: "" ) . qq|
|
|
$hide_input
|
|
<input type="submit" value="$hide_msg">
|
|
|
|
</form>
|
|
</td>
|
|
<td bgcolor="$bgcolor">
|
|
|
|
<!-- depth section -->
|
|
Max Depth:
|
|
|
|
</td>
|
|
<td bgcolor="$bgcolor">
|
|
|
|
</td>
|
|
<td bgcolor="$bgcolor">
|
|
<form method="get" action="$scriptname"
|
|
style="display: inline; margin: 0px;">
|
|
|
|
<!-- set to one form -->
|
|
<input type="submit" value=" 1 " |
|
|
. ( $realdepth < 2 || $maxdepth == 1 ? "disabled" : "" ) . qq|>
|
|
<input name="id" type="hidden" value="$id">
|
|
<input name="maxdepth" type="hidden" value="1">
|
|
<input name="hide_resolved" type="hidden" value="$hide_resolved">
|
|
</form>
|
|
</td>
|
|
<td bgcolor="$bgcolor">
|
|
<form method="get" action="$scriptname"
|
|
style="display: inline; margin: 0px;">
|
|
|
|
<!-- Minus one form
|
|
Allow subtracting only when realdepth and maxdepth > 1 -->
|
|
<input name="id" type="hidden" value="$id">
|
|
<input name="maxdepth" type="hidden" value="|
|
|
. ( $maxdepth == 1 ? 1 : ( $maxdepth ?
|
|
$maxdepth-1 : $realdepth-1 ) ) . qq|">
|
|
<input name="hide_resolved" type="hidden" value="$hide_resolved">
|
|
<input type="submit" value=" < " |
|
|
. ( $realdepth < 2 || ( $maxdepth && $maxdepth < 2 ) ?
|
|
"disabled" : "" ) . qq|>
|
|
|
|
</form>
|
|
</td>
|
|
<td bgcolor="$bgcolor">
|
|
<form method="get" action="$scriptname"
|
|
style="display: inline; margin: 0px;">
|
|
|
|
<!-- Limit entry form: the button can not do anything when total depth
|
|
is less than two, so disable it -->
|
|
<input name="maxdepth" size="4" maxlength="4" value="|
|
|
. ( $maxdepth > 0 ? $maxdepth : "" ) . qq|">
|
|
<input name="id" type="hidden" value="$id">
|
|
<input name="hide_resolved" type="hidden"
|
|
value="$hide_resolved">
|
|
<noscript>
|
|
<input type="submit" value="Change" |
|
|
. ( $realdepth < 2 ? "disabled" : "" ) . qq|>
|
|
</noscript>
|
|
|
|
</form>
|
|
</td>
|
|
<td bgcolor="$bgcolor">
|
|
<form method="get" action="$scriptname"
|
|
style="display: inline; margin: 0px;">
|
|
|
|
<!-- plus one form
|
|
Disable button if total depth < 2, or if depth set to unlimited -->
|
|
<input name="id" type="hidden" value="$id">
|
|
| . ( $maxdepth ? qq|
|
|
<input name="maxdepth" type="hidden" value="|.($maxdepth+1).qq|">| : "" )
|
|
. qq|
|
|
<input name="hide_resolved" type="hidden" value="$hide_resolved">
|
|
<input type="submit" value=" > " |
|
|
. ( $realdepth < 2 || ! $maxdepth || $maxdepth >= $realdepth ?
|
|
"disabled" : "" ) . qq|>
|
|
|
|
</form>
|
|
</td>
|
|
<td bgcolor="$bgcolor">
|
|
<form method="get" action="$scriptname"
|
|
style="display: inline; margin: 0px;">
|
|
|
|
<!-- Unlimited button -->
|
|
<input name="id" type="hidden" value="$id">
|
|
<input name="hide_resolved" type="hidden" value="$hide_resolved">
|
|
<input type="submit" value=" Unlimited ">
|
|
|
|
</form>
|
|
</td>
|
|
</tr></table>
|
|
|;
|
|
}
|
|
|
|
######################################################################
|
|
# Main Section
|
|
######################################################################
|
|
|
|
my $linked_id = qq|<a href="show_bug.cgi?id=$id">$id</a>|;
|
|
|
|
# Start the tree walk and save results. The tree walk generates HTML but
|
|
# needs to be called before the page output starts so we have the
|
|
# realdepth, which is necessary for generating the control toolbar.
|
|
|
|
# Get bugs we depend on
|
|
my $depend_html = makeTreeHTML($id, $linked_id, "dependson");
|
|
|
|
my $tmpdepth = $realdepth;
|
|
$realdepth = 0;
|
|
|
|
# Get bugs we block
|
|
my $block_html = makeTreeHTML($id, $linked_id, "blocked");
|
|
|
|
# Select maximum depth found for use in the toolbar
|
|
$realdepth = $realdepth < $tmpdepth ? $tmpdepth : $realdepth;
|
|
|
|
#
|
|
# Actual page output happens here
|
|
|
|
print "Content-type: text/html\n\n";
|
|
PutHeader("Dependency tree for Bug $id", "Dependency tree for Bug $linked_id");
|
|
drawDepForm();
|
|
print $depend_html;
|
|
print $block_html;
|
|
drawDepForm();
|
|
PutFooter();
|