now works and is insync with VC_CVS.pm

This commit is contained in:
kestes%tradinglinx.com 2001-02-15 20:56:58 +00:00
parent cf9234d955
commit 7080294a15

View File

@ -1,8 +1,6 @@
# -*- Mode: perl; indent-tabs-mode: nil -*-
#
# NOT WORKING - please port, following VC_CVS as a guide.
# TinderDB::VC_Bonsai - methods to query the bonsai interface to CVS
# Version Control system and find the users who have checked changes
# into the tree recently and renders this information into HTML for
@ -10,9 +8,11 @@
# few which can see its internal datastructures. The
# TinderHeader::TreeState is queried so that users will be displayed
# in the color which represents the state of the tree at the time they
# checked their code in.
# checked their code in. This module uses the library BonsaiData.pm
# to provide an abstract programatic interface to Bonsai. We assume
# that the Bonsai tree names are the same as the Tinderbox tree names.
# This module is very similar to VC_CVS and the two files should be
# kept in sync.
# The contents of this file are subject to the Mozilla Public
@ -37,29 +37,251 @@
# Contributor(s):
package TinderDB::VC;
$VERSION = ( qw $Revision: 1.3 $ )[1];
package TinderDB::VC_Bonsai;
# the Bonsai (Mozilla.org's SQL interface to CVS) implemenation of the
# Version Control DB for Tinderbox. This column of the status table
# will report who has changed files in the CVS repository and what
# files they have changed.
# Remember CVS does all its work in GMT (UCT), Tinderbox does all its work in
# local time. Thus this file needs to perform the conversions.
# We store the hash of all names who modified the tree at a
# particular time as follows:
# $DATABASE{$tree}{$time}{'author'}{$author}{$file} = $log;
# additionally information about changes in the tree state are stored
# in the variable:
# $DATABASE{$tree}{$time}{'treestate'} = $state;
# we also store information in the metadata structure
# $METADATA{$tree}{'updates_since_trim'} += 1;
#
# where state is either 'Open' or 'Closed' or some other user defined string.
#
# Cell colors are controled by the functions:
# TreeData::get_all_tree_states()
# TreeData::TreeState2color($state)
# Load standard perl libraries
use File::Basename;
use Time::Local;
# Load Tinderbox libraries
use lib '#tinder_libdir#';
use BonsaiData;
use TinderDB::BasicTxtDB;
use Utils;
use HTMLPopUp;
use TreeData;
use VCDisplay;
$VERSION = ( qw $Revision: 1.4 $ )[1];
@ISA = qw(TinderDB::BasicTxtDB);
# Add an empty object, of this DB subclass, to end of the set of all
# HTML columns. This registers the subclass with TinderDB and defines
# the order of the HTML columns.
sub html_legend {
push @TinderDB::HTML_COLUMNS, TinderDB::VC_Bonsai->new();
# remove all records from the database which are older then last_time.
sub trim_db_history {
my ($self, $tree,) = (@_);
my ($last_time) = $main::TIME - $TinderDB::TRIM_SECONDS;
# sort numerically ascending
my (@times) = sort {$a <=> $b} keys %{ $DATABASE{$tree} };
foreach $time (@times) {
($time >= $last_time) && last;
delete $DATABASE{$tree}{$time};
}
return ;
}
# Return the most recent times that we recieved treestate and checkin
# data.
sub find_last_data {
my ($tree) = @_;
# sort numerically descending
my (@times) = sort {$b <=> $a} keys %{ $DATABASE{$tree} };
my ($last_tree_data, $second2last_tree_data, $last_cvs_data);
foreach $time (@times) {
# We must check $second2last_tree_data before $last_tree_data or
# we may end up with both pointing to the same entry.
(defined($last_tree_data)) &&
(!defined($second2last_tree_data)) &&
(defined($DATABASE{$tree}{$time}{'treestate'})) &&
($second2last_tree_data = $time);
(!defined($last_tree_data)) &&
(defined($DATABASE{$tree}{$time}{'treestate'})) &&
($last_tree_data = $time);
(!defined($last_cvs_data)) &&
(defined($DATABASE{$tree}{$time}{'author'})) &&
($last_cvs_data = $time);
# do not iterate through the whole histroy. Stop after we have
# the data we need.
(defined($last_cvs_data)) &&
(defined($second2last_cvs_data)) &&
(defined($last_tree_data)) &&
last;
} # foreach $time (@times)
return ($last_tree_data, $second2last_tree_data, $last_cvs_data);
}
# get the recent data from CVS and the treestate file.
sub apply_db_updates {
my ($self, $tree,) = @_;
my ($tree_state) = TinderHeader::gettree_header('TreeState', $tree);
my ($last_tree_data, $second2last_tree_data, $last_cvs_data) =
find_last_data($tree);
# Store the latest treestate in the database along with the checkin
# data.
# Take this opportunity to perform an optimization of the database.
# We purge duplicate 'treestate' entries (entries at consecutive
# times which have the same state) to keep the DB size down.
# Data::Dumper takes a long time and reducing the data that it needs
# to process really helps speed things up.
# If we delete too many duplicates then we loose information when
# the database is trimmed. We need to keep some duplicates arround
# for debuging and for "redundancy". Only delete duplicates during
# the last hour. Notice we are still removing 90% of the
# duplicates.
# If we have three data points in a row, and all of them have the
# same state and the oldest is less then an hour old, then we can
# delete the middle state. While writing this code I kept trying to
# make do with only one older state being remembered. The problem
# is that if you keep deleting the oldest member you always have
# exactly one entry which is only five minutes old.
if (
defined($last_tree_data) &&
defined($second2last_tree_data) &&
($new_tree_state eq $DATABASE{$tree}{$last_tree_data}{'treestate'}) &&
($new_tree_state eq $DATABASE{$tree}{$second2last_tree_data}{'treestate'}) &&
( ($main::TIME - $second2last_tree_data) < $main::SECONDS_PER_HOUR )
) {
delete $DATABASE{$tree}{$last_tree_data}{'treestate'};
(scalar(%{ $DATABASE{$tree}{$last_tree_data} }) == 0) &&
delete $DATABASE{$tree}{$last_tree_data};
}
$DATABASE{$tree}{$main::TIME}{'treestate'} = $new_tree_state;
($last_cvs_data) ||
($last_cvs_data = $main::TIME - $TinderDB::TRIM_SECONDS );
my $num_updates = 0;
# The data is returned from bonsai as a list of lists. Each row is
# one list. We want to handle the data as little as possible in this
# module, our only goals is to isolate the bonsai interface. So we
# will pass back the data directly to the caller with no intermediate
# passes over it.
# We are provided a hash table to make indexing the check-ins easier
# and more self documenting.
my ($results, $i) =
BonsaiData::get_checkin_data(
$tree,
$TreeData::VC_TREE{$tree}{'module'},
$TreeData::VC_TREE{$tree}{'branch'},
$last_cvs_data,
);
foreach $r (@{ $results }) {
my ($time) = $r->[$$i{'time'}];
my ($author) = $r->[$$i{'author'}];
my ($dir) = $r->[$$i{'dir'}];
my ($file) = $r->[$$i{'file'}];
my ($log) = $r->[$$i{'log'}];
$DATABASE{$tree}{$time}{'author'}{$author}{"$dir/$file"} =
$log;
$num_updates ++;
} # foreach update
$METADATA{$tree}{'updates_since_trim'} += $num_updates;
if ( ($METADATA{$tree}{'updates_since_trim'} >
$TinderDB::MAX_UPDATES_SINCE_TRIM)
) {
$METADATA{$tree}{'updates_since_trim'}=0;
trim_db_history(@_);
}
$self->savetree_db($tree);
return $num_updates;
}
sub status_table_legend {
my ($out)='';
# print all the possible tree states in a cell with the color
$out .= "\t<td align=right valign=top>\n";
$out .= "\t<table>\n";
$out .= "\t<table $TinderDB::LEGEND_BORDER>\n";
$out .= ("\t\t<thead><tr><td align=center>".
"VC Cell Colors".
"</td></tr></thead>\n");
foreach $state (keys %TinderConfig::TREE_STATE2COLOR) {
my $color = TinderConfig::TREE_STATE2COLOR{$state};
foreach $state (TreeData::get_all_tree_states()) {
my $color = TreeData::TreeState2color($state);
$out .= ("\t\t<tr bgcolor=\"$color\">".
"<td>Tree state: $state</td></tr>\n");
"<td>Tree State: $state</td></tr>\n");
}
$out .= "\t</table>\n";
@ -70,177 +292,242 @@ sub html_legend {
}
sub html_header {
sub status_table_header {
return ("\t<th>VC checkins</th>\n");
}
use lib "../bonsai";
require 'globals.pl';
# clear data structures in preparation for printing a new table
$F_DEBUG=1;
$days = 2;
if ($ARGV[0] eq "-days") {
shift @ARGV;
$days = shift @ARGV;
}
$tree = $ARGV[0];
open(SEMFILE, ">>$tree/buildwho.sem") || die "Couldn't open semaphore file!";
if (!flock(SEMFILE, 2 + 4)) { # 2 means "lock"; 4 means "fail immediately if
# lock already taken".
print "buildwho.pl: Another process is currently building the database.\n";
exit(0);
}
require "$tree/treedata.pl";
if( $cvs_root eq '' ){
$CVS_ROOT = '/m/src';
}
else {
$CVS_ROOT = $cvs_root;
}
$CVS_REPOS_SUFIX = $CVS_ROOT;
$CVS_REPOS_SUFIX =~ s/\//_/g;
$CHECKIN_DATA_FILE = "/d/webdocs/projects/bonsai/data/checkinlog${CVS_REPOS_SUFIX}";
$CHECKIN_INDEX_FILE = "/d/webdocs/projects/bonsai/data/index${CVS_REPOS_SUFIX}";
require 'cvsquery.pl';
print "cvsroot='$CVS_ROOT'\n";
&build_who;
flock(SEMFILE, 8); # '8' is magic 'unlock' const.
close SEMFILE;
sub build_who {
open(BUILDLOG, "<$tree/build.dat" );
$line = <BUILDLOG>;
close(BUILDLOG);
#($j,$query_date_min) = split(/\|/, $line);
$query_date_min = time - (60 * 60 * 24 * $days);
if( $F_DEBUG ){
print "Minimum date: $query_date_min\n";
}
$query_module=$cvs_module;
$query_branch=$cvs_branch;
$result = &query_checkins;
$last_who='';
$last_date=0;
open(WHOLOG, ">$tree/who.dat" );
for $ci (@$result) {
if( $ci->[$CI_DATE] != $last_date || $ci->[$CI_WHO] != $last_who ){
print WHOLOG "$ci->[$CI_DATE]|$ci->[$CI_WHO]\n";
}
$last_who=$ci->[$CI_WHO];
$last_date=$ci->[$CI_DATE];
}
close( WHOLOG );
}
sub update_history {
#execute:
#cvs -d $d_args -a -c -D $begin_date $module
#parse:
my ($type, $month_day, $time, $time_zone,
$author, $revision, $basename, $dirname, @ignore)
= split (/ +/, $cvs_line[1])
}
# Check to see if anyone checked in during time slot.
# ex. has_who_list(1); # Check for checkins in most recent time slot.
# ex. has_who_list(1,5); # Check range of times.
sub has_who_list {
my ($time1, $time2) = @_;
if (not defined(@who_check_list)) {
# Build a static array of true/false values for each time slot.
$who_check_list[$time_count] = 0;
my ($t) = 1;
for (; $t<=$time_count; $t++) {
$who_check_list[$t] = 1 if each %{$who_list->[$t]};
}
}
if ($time2) {
for ($ii=$time1; $ii<=$time2; $ii++) {
return 1 if $who_check_list[$ii]
}
return 0
} else {
return $who_check_list[$time1];
}
}
sub load_who {
my ($who_list, $td) = @_;
my $d, $w, $i, $bfound;
sub status_table_start {
my ($self, $row_times, $tree, ) = @_;
open(WHOLOG, "<$td->{name}/who.dat");
while (<WHOLOG>) {
$i = $time_count;
chop;
($d,$w) = split /\|/;
$bfound = 0;
while ($i > 0 and not $bfound) {
if ($d <= $build_time_times->[$i]) {
$who_list->[$i+1]->{$w} = 1;
$bfound = 1;
}
else {
$i--;
# create an ordered list of all times which any data is stored
# sort numerically descending
@DB_TIMES = sort {$b <=> $a} keys %{ $DATABASE{$tree} };
# adjust the $NEXT_DB to skip data which came after the first cell
# at the top of the page. We make the first cell bigger then the
# rest to allow for some overlap between pages.
my ($first_cell_seconds) = 2*($row_times->[0] - $row_times->[1]);
my ($earliest_data) = $row_times->[0] + $first_cell_seconds;
$NEXT_DB = 0;
while ( ($DB_TIMES[$NEXT_DB] > $earliest_data) &&
($NEXT_DB < $#DB_TIMES) ) {
$NEXT_DB++
}
$LAST_TREESTATE = '';
return ;
}
sub status_table_row {
my ($self, $row_times, $row_index, $tree, ) = @_;
my @outrow = ();
# we assume that tree states only change rarely so there are very
# few cells which have more then one state associated with them.
# It does not matter what we do with those cells.
# find all the authors who changed code at any point in this cell
# find the tree state for this cell.
my %authors = ();
while (1) {
my ($time) = $DB_TIMES[$NEXT_DB];
# find the DB entries which are needed for this cell
($time < $row_times->[$row_index]) && last;
$NEXT_DB++;
if ($DATABASE{$tree}{$time}{'treestate'}) {
$LAST_TREESTATE = $DATABASE{$tree}{$time}{'treestate'};
}
# Now invert the data structure.
# Inside each cell, we want all the posts by the same author to
# appear together. The previous data structure allowed us to find
# out what data was in each cell.
foreach $author (keys %{ $DATABASE{$tree}{$time}{'author'} }) {
foreach $file (keys %{ $DATABASE{$tree}{$time}{'author'}{$author} }) {
my ($log) = $DATABASE{$tree}{$time}{'author'}{$author}{$file};
$authors{$author}{$time}{$file} = $log;
}
}
}
} # while (1)
# Ignore the last one
#
if ($time_count > 0) {
$who_list->[$time_count] = {};
# If there is no treestate, then the tree state has not changed
# since an early time. The earliest time was assigned a state in
# apply_db_updates(). It is possible that there are no treestates at
# all this should not prevent the VC column from being rendered.
my $color = TreeData::TreeState2color($LAST_TREESTATE);
($LAST_TREESTATE) && ($color) &&
($color = "bgcolor=$color");
my $query_links = '';
if ( scalar(%authors) ) {
# find the times which bound the cell so that we can set up a
# VC query.
my ($mindate) = $row_times->[$row_index];
my ($maxdate);
if ($row_index > 0){
$maxdate = $row_times->[$row_index - 1];
} else {
$maxdate = $main::TIME;
}
my $format_maxdate = HTMLPopUp::timeHTML($maxdate);
my $format_mindate = HTMLPopUp::timeHTML($mindate);
my $time_interval_str = "$format_maxdate to $format_mindate",
# create a string of all VC data for displaying with the checkin table
my $vc_info;
foreach $key ('module','branch',) {
$vc_info .= "$key: $TreeData::VC_TREE{$tree}{$key} <br>\n";
}
# define a table, to show what was checked in for each author
foreach $author (sort keys %authors) {
my ($table) = '';
my ($num_rows) = 0;
my ($max_length) = 0;
$table .= (
"Checkins by <b>$author</b> <br> for $vc_info \n".
"<table border cellspacing=2>\n".
"");
# add table headers
$table .= (
"\t<tr>".
"<th>Time</th>".
"<th>File</th>".
"<th>Log</th>".
"</tr>\n".
"");
# sort numerically descending
foreach $time ( sort {$b <=> $a} keys %{ $authors{$author} }) {
foreach $file (keys %{ $authors{$author}{$time}}) {
$num_rows++;
my ($log) = $authors{$author}{$time}{$file};
$max_length = main::max(
$max_length ,
(length($file) + length($log)),
);
$table .= (
"\t<tr>".
"<td>".HTMLPopUp::timeHTML($time)."</td>".
"<td>".$file."</td>".
"<td>".$log."</td>".
"</tr>\n".
"");
}
}
$table .= "</table>";
# we display the list of names in 'teletype font' so that the
# names do not bunch together. It seems to make a difference if
# there is a <cr> between each link or not, but it does make a
# difference if we close the <tt> for each author or only for
# the group of links.
my (%popup_args) = (
"linktxt" => "\t\t<tt>$author</tt>",
"windowtxt" => $table,
"windowtitle" => ("VC Info ".
"Author: $author ".
"$time_interval_str "),
"windowheight" => ($num_rows * 50) + 100,
"windowwidth" => ($max_length * 10) + 100,
);
# If you have a VCDisplay implementation you should make the
# link point to its query method otherwise you want a 'mailto:'
# link
my $query_link = "";
if (
($TinderConfig::VCDisplayImpl) &&
($TinderConfig::VCDisplayImpl =~ 'None')
) {
$query_link .=
HTMLPopUp::Link(
"href" => "mailto: $author",
%popup_args,
);
} else {
$query_link .=
VCDisplay::query(
'tree' => $tree,
'mindate' => $mindate,
'maxdate' => $maxdate,
'who' => $author,
%popup_args,
);
}
# put each link on its own line and add good comments so we
# can debug the HTML.
$query_link = "\t\t".$query_link."\n";
my ($date_str) = localtime($mindate)."-".localtime($maxdate);
$query_links .= (
"\t\t<!-- VC: ".("Author: $author, ".
"Time: '$date_str', ".
"Tree: $tree, ".
"").
" -->\n".
"");
$query_links .= $query_link;
}
@outrow = (
"\t<td align=center $color>\n".
$query_links.
"\t</td>\n".
"");
} else {
@outrow = ("\t<!-- skipping: VC: tree: $tree -->".
"<td align=center $color>$HTMLPopUp::EMPTY_TABLE_CELL</td>\n");
}
return @outrow;
}
sub html_row {
"<tr align=center> \@row </tr>\n";
# Guilty
#
print '<td align=center>';
for $who (sort keys %{$who_list->[$tt]} ){
$qr = &who_menu($td1, $build_time_times->[$tt],
$build_time_times->[$tt-1],$who);
$who =~ s/%.*$//;
print " ${qr}$who</a>\n";
}
print '</td>';
}
1;