#!/usr/bin/perl -w
# -*- perl -*-
use P4CGI ;
use strict ;
use CGI::Carp ;
#
#################################################################
# CONFIGURATION INFORMATION
# All config info should be in P4CGI.pm
#
#################################################################
#
# P4 file log viewer
#
#################################################################
## Check if we have GD (required for branchGraph.cgi).
my $GD_AVAILABLE=undef ;
{
my $tmpDie = $SIG{'__DIE__'} ;
$SIG{'__DIE__'} = "" ;
eval ' use GD ; ' ;
if(length($@) == 0) {
$GD_AVAILABLE= "Y" ;
}
$SIG{'__DIE__'} = $tmpDie ;
}
sub offsetOf($@ ) {
my $v = shift @_ ;
my $pos = 0 ;
while(@_ > 0) {
if($v eq (shift @_)) {
return $pos ;
}
$pos++ ;
}
return -1 ;
}
my $err2null = &P4CGI::REDIRECT_ERROR_TO_NULL_DEVICE() ;
my $err2stdout = &P4CGI::REDIRECT_ERROR_TO_STDOUT() ;
my ($serverYear,$serverNo) = &P4CGI::SERVER_VERSION() ;
local *P ;
# File argument
my $file = P4CGI::cgi()->param("FSPC") ;
&P4CGI::bail("No file spec") unless defined $file ;
# Label x-reference argument
my $listLabel = P4CGI::cgi()->param("LISTLAB") ;
$listLabel = "No" unless defined $listLabel ;
# Show branch info argument
my $showBranch = P4CGI::cgi()->param("SHOWBRANCH") ;
$showBranch="No" unless defined $showBranch ;
# Show full change text
my $showFullDesc = P4CGI::cgi()->param("FULLDESC") ;
if(&P4CGI::SHOW_FULL_DESC() == 0) {
$showFullDesc="No" unless defined $showFullDesc and $showFullDesc eq "Yes" ;
}
else {
$showFullDesc="Yes" unless defined $showFullDesc and $showFullDesc eq "No" ;
} ;
# Get file data
my @filelog ;
if($serverYear >= 2002) {
# For servers from 2002 and later we use the -l flag
# for the filelog command to always get log descriptions.
# We will then trucate the description if the user asked for
# "truncated description"
&P4CGI::p4call(\@filelog,"filelog -l \"$file\"") ;
}
else {
# For 2001 servers and earlier we assume the -l flag does not
# exist. Here we us the "change" command to get the long
# descriptions, but only when we need it.
# (I do not actually _know_ when the -l flag was implemented
# so I assume it was for 2002.1 to make my life simpler)
&P4CGI::p4call(\@filelog,"filelog \"$file\"") ;
};
&P4CGI::bail("No data for file \"$file\"") if @filelog == 0 ;
# Get info about opened status
&P4CGI::p4call(*P,"opened -a \"$file\" $err2null") ;
my %opened ;
my $openedText = "" ;
while(<P>) {
$openedText = "Opened by" ;
chomp ;
/\w+\#(\d+) - .* by (\w+)\@(\S+)/ or
&P4CGI::bail("Can not read info from \"p4 opened\"") ;
my $user = &P4CGI::ahref(-url => "userView.cgi",
"USER=$2",
"HELP=View user info",
"$2") ;
my $client = &P4CGI::ahref(-url => "clientView.cgi",
"CLIENT=$3",
"HELP=View client info",
"$3") ;
if(exists $opened{$1}) {
$opened{$1} .= "<br> and $user\@$client" ;
} else {
$opened{$1} = "$user\@$client" ;
} ;
} ;
close *P ;
##
## Get Label cross reference
##
my %fileToLabels ; # Hash containing labels by file name and version
my @labels ; # Labels containing file
#
# Find labels containing file
#
# There are two ways to do this, pre and
# post 2001.1 version. Pre 2001.1 the
# "p4 labels" command did not take a file
# name and could not be used. A more complicated
# algorithm must be used that made cross
# referenceing slow and it is thus optional.
# Post 2001.1 it is fast and does not have to be
# optional anymore.
if($serverYear < 2001) {
# OK. We have an older server
# Get list of all labels (if $listLabel is set)
if($listLabel eq "Yes") {
&P4CGI::p4call(*P,"labels") ;
while(<P>) {
/^Label (\S+)/ and do { push @labels,$1 ; } ;
}
close P ;
}
if(@labels > 0) {
# Speed things up by looking up
# file view for each label and removing all
# labels that don't match
# This is an act of desperation because in our
# p4 depot the label search takes forever (well..
# a long time, 20 secs or so...)
my $l ;
my @l ;
LABEL: foreach $l (@labels) {
my %data ;
&P4CGI::p4readform("label -o \"$l\"",\%data) ;
if(exists $data{"View"}) {
my @v = split("\n",$data{"View"}) ;
foreach (@v) {
# p4-to-perl regexp conversion
my $in = $_ ;
my $re = "" ;
while($in =~ s/(.*?)(\Q...\E|\Q*\E)//) {
$re .= "\Q$1\E" ;
if($2 eq "...") { $re .= ".*" ; }
else { $re .= "[^/]*" ; }
}
$re .= "\Q$in\E" ;
if($file =~ /$re/) {
push @l,$l ;
next LABEL ;
}
}
}
}
my $lb = @labels ;
my $la = @l ;
&P4CGI::ERRLOG("reduced from $lb to $la labels") ; # DEBUG
@labels = @l ;
# <RANT>
# Frankly, I find it very strange that I can speed
# up the search by "manually" reading all label
# specs, parsing them, and checking if the file
# matches any part of the view before actually
# asking p4 to do it. Some developer must have had
# a bad day at perforce. And p4 is not open
# source.... sigh.
# </RANT>
}
}
else {
# We have a newer server
$listLabel = "Yes" ;
&P4CGI::p4call(\@labels,"labels $file") ;
map { s/^Label (\S+).*/$1/ ; } @labels ;
}
if(@labels > 0) {
my $filelabels = "" ;
foreach (@labels) {
$filelabels .= " \"$file\@$_\"" ;
}
my @filesInLabels ;
&P4CGI::p4call(\@filesInLabels,"files $filelabels $err2stdout") ;
my $l ;
# Remove labels not in list
# NOTE! The errors (file not in label-messages)
# are printed to stderr and there
# is no guarantee that output from stderr and
# stdout will come in order. This is why
# we first must figure out which labels
# that NOT affected the file
foreach $l (reverse map {/.*@(\S+)\s.*not in label/?$1:()} @filesInLabels) {
my $offset = offsetOf($l,@labels) ;
splice @labels,$offset,1 ;
}
# Build file-to-label hash. Use only data from
# stdout (not stderr). (grep is used to filter)
foreach (grep(!/not in label/,@filesInLabels)) {
my $lab = shift @labels ;
$lab = &P4CGI::ahref(-url => "labelView.cgi",
"LABEL=$lab",
"HELP=View label",
"$lab") ;
/^(\S+)/ ;
if(defined $fileToLabels{$1}) {
$fileToLabels{$1} .= "<br>$lab" ;
}
else {
$fileToLabels{$1} = "$lab" ;
}
}
} ;
my @legendList ;
push @legendList,
"<b>Revision Number</b> -- see the file text",
"<b>Action</b> -- see the deltas (diffs)",
"<b>User</b> -- see info about user",
"<b>Change</b> -- see the complete change description, including other files",
&P4CGI::ahref("-url","changeList.cgi",
"FSPC=$file",
"Changes") . "-- see list of all changes for this file" ;
my @parsListLab ;
my @parsShowBranch ;
my $p ;
foreach $p (&P4CGI::cgi()->param()) {
push @parsListLab, "$p=" . &P4CGI::cgi()->param($p) unless $p eq "LISTLAB" ;
push @parsShowBranch, "$p=" . &P4CGI::cgi()->param($p) unless $p eq "SHOWBRANCH" ;
}
if($listLabel ne "Yes") {
push @legendList,
&P4CGI::ahref(@parsListLab,
"LISTLAB=Yes",
"List labels") . "-- list cross ref. for labels" ;
} ;
if($showBranch ne "No") {
push @legendList,
&P4CGI::ahref(@parsShowBranch,
"SHOWBRANCH=No",
"Hide branch info") . "-- hide info about branches, merges and copy of file" ;
}
else {
push @legendList,
&P4CGI::ahref(@parsShowBranch,
"SHOWBRANCH=Yes",
"Show branch info") . "-- show info about branches, merges and copy of file" ;
} ;
# Get file directory part
my $fileDir=$file ;
$fileDir =~ s#/[^/]+$## ;
push @legendList,
&P4CGI::ahref("-url","depotTreeBrowser.cgi",
"FSPC=$fileDir",
"Browse directory") .
"-- Browse depot tree at $fileDir" ;
my %vars ;
{
my $par ;
foreach $par (&P4CGI::cgi()->param()) {
$vars{$par} = &P4CGI::cgi()->param($par);
} ;
} ;
if($showFullDesc eq "Yes") {
$vars{"FULLDESC"}="No" ;
my @pars ;
foreach (keys %vars) {
push @pars,"$_=$vars{$_}" ;
} ;
push @legendList, &P4CGI::ahref(@pars,
"Show truncated descriptions") ;
}
else {
$vars{"FULLDESC"}="Yes" ;
my @pars ;
foreach (keys %vars) {
push @pars,"$_=$vars{$_}" ;
} ;
push @legendList, &P4CGI::ahref(@pars,
"Show full descriptions") ;
} ;
if($GD_AVAILABLE) {
push
@legendList,
&P4CGI::ahref("-url","branchGraph.cgi",
"FSPC=$file",
"Graph Branches/Merges") .
"-- Graph branches and merges to/from this file";
} else {
push
@legendList,
"Graph Branches/Merges-- Graph branches and merges to/from this file ".
"(requires GD package)" ;
}
print "",&P4CGI::start_page("File log<br>$file",&P4CGI::ul_list(@legendList)) ;
my $labelHead ="";
if($listLabel eq "Yes") {
$labelHead="In label(s)" ;
} ;
print
"",
&P4CGI::start_table("width=100%"),
&P4CGI::table_header("Rev/view<br> file",
"Action/view<br> diff",
"Date",
"User/view<br> user",
"Change/view<br> change",
"Type",
"Desc",
$labelHead,
$openedText) ;
my @revs ;
my %relatedFiles ;
my ($rev,$change,$act,$date,$user,$client,$type,$desc) ;
my $chbuffer = "" ;
while(@filelog) {
$_ = &P4CGI::fixSpecChar(shift @filelog) ;
if(/^\.\.\. \#(\d+) \S+ (\d+) (\S+) on (\S+) by (\S*)@(\S*) (\S*)\s*'(.*)'/ or
/^\.\.\. \#(\d+) \S+ (\d+) (\S+) on (\S+) by (\S*)@(\S*) (\S*)/) {
print $chbuffer ;
$chbuffer = "" ;
($rev,$change,$act,$date,
$user,$client,$type,$desc) = ($1,$2,$3,$4,$5,$6,$7,$8) ;
if(!$desc) {
shift @filelog ;
my $l ;
$desc = "" ;
while(@filelog) {
$l = shift @filelog ;
chomp $l ;
$l =~ s/^\t// ;
last if(length($l) == 0) ;
$desc .= "\n" if($desc ne "") ;
$desc .= $l ;
}
} ;
$type =~ s/\((.*)\)/$1/ ;
if($showFullDesc eq "Yes" and $serverYear < 2002) {
my %changeData ;
&P4CGI::p4readform("change -o $change",\%changeData) ;
$desc = $changeData{"Description"} if exists $changeData{"Description"} ;
} ;
if($showFullDesc ne "Yes") {
# See earlier comment about "$serverYear >= 2002"
if($serverYear >= 2002) {
$desc =~ s/\n.*$//s ;
$desc = substr($desc,0,40) . "..." ;
}
else {
$desc .="..." if length($desc) >= 31;
}
}
$desc = &P4CGI::fixSpecChar($desc) ;
$desc = &P4CGI::magic($desc) ;
$desc =~ s/\n/<br>\n/gm ;
$desc =~ s/ / /gm ;
push @revs,$rev ;
my $labels = $fileToLabels{"$file\#$rev"} ;
$labels = "" unless defined $labels ;
$labels = "<b>$labels</b>" ;
$type="<small>$type</small>" ;
$desc="<tt>$desc</tt>" ;
my %desc = ( -text => "$desc" ,
) ;
if ($act eq 'branch') {
$chbuffer .=
&P4CGI::table_row(-valign => "top",
&P4CGI::ahref("-url","fileViewer.cgi",
"FSPC=$file",
"REV=$rev",
"HELP=View file",
"$rev"),
"$act",
"$date",
&P4CGI::ahref(-url => "userView.cgi" ,
"USER=$user",
"HELP=View user info",
"$user"),
&P4CGI::ahref("-url","changeView.cgi",
"CH=$change",
"HELP=View change",
"$change"),
"$type",
\%desc,
$labels,
exists $opened{$rev}?$opened{$rev}:"") ;
}
elsif ($act eq 'delete') {
$chbuffer .=
&P4CGI::table_row(-valign => "top",
"$rev",
"<strike>delete</strike>",
"$date",
&P4CGI::ahref(-url => "userView.cgi" ,
"USER=$user",
"HELP=View user info",
"$user"),
&P4CGI::ahref("-url","changeView.cgi",
"CH=$change",
"HELP=View change",
"$change"),
"$type",
\%desc,
$labels,
exists $opened{$rev}?$opened{$rev}:"") ;
}
else {
$chbuffer .=
&P4CGI::table_row(-valign => "top",
&P4CGI::ahref("-url","fileViewer.cgi",
"FSPC=$file",
"REV=$rev",
"HELP=View file",
"$rev"),
&P4CGI::ahref("-url","fileDiffView.cgi",
"FSPC=$file",
"REV=$rev",
"ACT=$act",
($act ne "add") ? "HELP=View diff" : "",
"$act"),
"$date",
&P4CGI::ahref(-url => "userView.cgi" ,
"USER=$user",
"HELP=View user info",
"$user"),
&P4CGI::ahref("-url","changeView.cgi",
"CH=$change",
"HELP=View change",
"$change"),
"$type",
\%desc,
$labels,
exists $opened{$rev}?$opened{$rev}:"") ;
}
}
else {
if(/^\.\.\. \.\.\. (\w+) (\w+) (\S+?)\#(\S+)/) {
my ($op,$direction,$ofile,$orev) = ($1,$2,$3,$4) ;
my $file = $ofile ;
$file =~ s/\#.*$// ;
$relatedFiles{$file} = 1 ;
if($showBranch ne "No") {
my ($b1,$b2) = ("","") ;
if($op eq "copy") {
($b1,$b2) = ("<b> ! ","</b>") ;
}
my $d = &P4CGI::table_row(-valign => "top",
"",
undef,
undef,
undef,
undef,
undef,
undef,
undef,
undef,
"$b1$op $direction ".
&P4CGI::ahref("-url","fileLogView.cgi",
"FSPC=$ofile",
"HELP=View file log",
"$ofile\#$orev"). "$b2") ;
if($direction ne "from") {
$chbuffer = "$d\n$chbuffer" ;
}
else {
print "$chbuffer\n$d\n" ;
$chbuffer = "" ;
}
}
}
}
}
print "$chbuffer\n" ;
print
"",
&P4CGI::end_table("") ;
if(@revs > 2) {
print
"<hr>",
&P4CGI::cgi()->startform("-action","fileDiffView.cgi",
"-method","GET"),
&P4CGI::cgi()->hidden("-name","FSPC",
"-value",&P4CGI::fixspaces("$file")),
&P4CGI::cgi()->hidden("-name","ACT",
"-value","edit"),
"\nShow diff between revision: ",
&P4CGI::cgi()->popup_menu(-name =>"REV",
"-values" =>\@revs);
shift @revs ;
print
" and ",
&P4CGI::cgi()->popup_menu(-name =>"REV2",
"-values" =>\@revs),
" ",
&P4CGI::cgi()->submit(-name =>"Go",
-value =>"Go"),
&P4CGI::cgi()->endform() ;
} ;
my @fixes ;
if($showBranch ne "No") {
&P4CGI::p4call(\@fixes,"fixes -i \"$file\"") ;
} else {
&P4CGI::p4call(\@fixes,"fixes \"$file\"") ;
}
if(@fixes) {
my @jobs ;
&P4CGI::p4call(\@jobs,"jobs -i \"$file\"") ;
my %job2desc ;
%job2desc = map { /(\S+) on .* \'(.*)\'/ ;
my ($job,$desc) = ($1,$2) ;
$desc .= "..." if length($desc) > 30 ;
($job,$desc) ; } @jobs ;
if($showFullDesc eq "Yes") {
my $j ;
foreach $j (keys %job2desc) {
my %jobData ;
&P4CGI::p4readform("job -o \"$j\"",\%jobData) ;
if(exists $jobData{"Description"}) {
my $d = $jobData{"Description"} ;
$d =~ s/\n/<br>\n/gm ;
$job2desc{$j} = $d ;
}
}
}
print "<hr><b><font size=+1>Fixes</font></b>",
&P4CGI::start_table(""),
&P4CGI::table_header("Job/view job",
"Change/view change",
"Date",
"User/view user",
"Description") ;
my $fix ;
foreach $fix (sort { my $ach = $a ;
my $bch = $b ;
$ach =~ s/.* by change (\d+) .*/$1/ ;
$bch =~ s/.* by change (\d+) .*/$1/ ;
$bch <=> $ach } @fixes)
{
$fix =~ /(.*) fixed by change (\d+) on (\S+) by (\S+)@(\S+)/ ;
my ($job,$change,$date,$user,$client) = ($1,$2,$3,$4,$5) ;
print
&P4CGI::table_row(-valign => "top",
&P4CGI::ahref("-url","jobView.cgi",
"JOB=$job",
"HELP=View job",
"$job"),
&P4CGI::ahref("-url","changeView.cgi",
"CH=$change",
"HELP=View change",
"$change"),
"$date",
&P4CGI::ahref(-url => "userView.cgi" ,
"USER=$user",
"HELP=View user info",
"$user"),
"<tt>$job2desc{$job}</tt>") ;
}
print
&P4CGI::end_table("") ;
}
sub getRelatedFiles($ )
{
my $file = shift @_ ;
my @data ;
&P4CGI::p4call(\@data,"filelog \"$file\"") ;
my %res ;
map { if(/^\.\.\. \.\.\. \w+ \w+ (\S+?)\#/) { $res{$1} = 1 ; } ; } @data ;
return ( sort keys %res ) ;
} ;
if((keys %relatedFiles) > 0) {
my @rel = sort keys %relatedFiles ;
my @fileLinks = map { &P4CGI::ahref("-url","fileLogView.cgi",
"FSPC=$_",
"HELP=View file log",
"$_") ; } @rel ;
my %indrel ;
$relatedFiles{$file} = 1 ;
while(@rel > 0) {
my $r ;
foreach $r (map { exists $relatedFiles{$_} ? () : $_ } getRelatedFiles(shift @rel)) {
&P4CGI::ERRLOG("found: $r") ;
$indrel{$r} = 1;
push @rel, $r ;
$relatedFiles{$r} = 1 ;
}
}
my @indFileLinks = map { &P4CGI::ahref("-url","fileLogView.cgi",
"FSPC=$_",
"HELP=View file log",
"$_") ; } sort keys %indrel ;
print
"<hr>",
&P4CGI::start_table(),
&P4CGI::table_row({ -valign => "top",
-align => "right",
-type => "th",
-text => "Related files:" },
{ -text => &P4CGI::ul_list(@fileLinks) }) ;
if(@indFileLinks > 0) {
print "", &P4CGI::table_row({ -valign => "top",
-align => "right",
-type => "th",
-text => "Indirect:" },
{ -text => &P4CGI::ul_list(@indFileLinks) }) ;
} ;
print "", &P4CGI::end_table() ;
} ;
print
"",
&P4CGI::end_page() ;
#
# That's all folks
#
| # | Change | User | Description | Committed | |
|---|---|---|---|---|---|
| #14 | 4990 | Fredric Fredricson | P4DB: Improved error handling: | ||
| #13 | 4306 | Fredric Fredricson |
P4DB: Hardened P4DB against malicious parameters (cross site scripting), performed some cleanup and increased version to 3.1.1. |
||
| #12 | 4216 | Fredric Fredricson | P4DB: Another partial submit on my way to P4DB 3.1... | ||
| #11 | 4152 | Fredric Fredricson | P4DB: Some more work on tha way to version 3.1.... | ||
| #10 | 4069 | Fredric Fredricson | P4DB: More changes on the way to 3.1 | ||
| #9 | 2875 | Fredric Fredricson | P4DB 3.0 first beta... | ||
| #8 | 1927 | Fredric Fredricson | P4DB: Removed some prints to the httpd error log | ||
| #7 | 1926 | Fredric Fredricson |
P4DB: Replaced call to CGI::Vars() with code that works for older CGI.pm versions as well. (Will make upgrades so much easier). |
||
| #6 | 1924 | Fredric Fredricson |
P4DB: Fixed a bug in fileLogView.cgi and also made the code take advantage of the -l flag for the filelog command. |
||
| #5 | 1920 | Fredric Fredricson |
P4DB: Mainly some user interface fixes: * Added a small arrow that points to selection in list of options * Added tooltip help * Added user prefereces to turn the above off (or on) * Some other user interface fixes And fixed a bug in jobList.cgi and some minor bugs in label and branch viewers. |
||
| #4 | 1913 | Fredric Fredricson |
P4DB: Updated file log view with a link to branch graph and the README with information about the branchGraph cgi |
||
| #3 | 1870 | Fredric Fredricson |
P4DB: Fixed problem when description in file log view contained special characters such as <, > and & |
||
| #2 | 1646 | Fredric Fredricson |
P4DB: file log can now show full descriptions. Added a new "preference" that makes the full descriptions default or not. |
||
| #1 | 1638 | Fredric Fredricson | P4DB: Added all (I think) files for P4DB |