#!/usr/bin/perl
#
# perfbrowse.perl -- CGI browser for PERFORCE
#
# $Id: //guest/mitch_stuart/perforce/utils/perfbrowse/perfbrowse.perl#1 $
#
# Point P4PORT and P4CLIENT differently if you're not using the defaults.
# PATH is set to pick up the Perforce client program.
#
# Updated by Jeff Marshall at Paragon Software, Inc.
# (jam@paragon-software.com)
#
# Fixed for 99.1 by Rob Chandhok
# - Anywhere the program was looking for "Date:", I made it also accept
# headers like "Access:" and "Update:"
#
# Updated by Randy Gellens to fix HTML tags.
#
# Please email me your improvements
#
# My changes:
# - Added white background
# - New browsing goodies -- like:
# o clients
# o users
# o opened files, by client and user
# o branches
# o labels
# o jobs
# o files within a given client
# - Footer on every page with a link to the "top browser page" and links
# to the browsing functionality listed above
# - Also put in hyperlinks anywhere a perforce user, client or mail address
# is seen.
#
# Tested with p4d 98.2 on a solaris 2.6 sparc server.
#
# Note: I'm not a perl programmer...
$ENV{P4PORT} = "YYYY";
$ENV{P4CLIENT} = "p4browse";
$ENV{P4USER} = "XXXXX";
$ENV{PATH} .= ":/usr/local/bin";
$ENV{CODELINES} = "projects.list";
# Boilerplate
$myname = $ENV{SCRIPT_NAME};
$BLUE = qq();
$GREEN = qq();
$RED = qq();
$END = qq();
$ADD = $BLUE;
$ADDEND = $END;
$DEL = "$RED";
$DELEND = "$END";
$MAXCONTEXT = 30;
$NCONTEXT = 10;
@OTHER_FOOTERS = ();
#
# Switch on ARGV[0]
#
# handle isindex compatibility
unshift( @ARGV, "\@changes" ) if( @ARGV && @ARGV[0] !~ m!^@! );
################################
#
# No arguments.
#
# Put up the introductory screen.
#
################################
#if (!@ARGV) {
# &html_header ( "Perforce Browser" );
# print "This browser allows you to view information about your",
# " Perforce server.\n",
# "The first step is to select what you want to view\n",
# "
";
#
# print "", &url( "\@browse", "Browse Depots"), "\n";
# print "", &url( "\@users", "Browse Users"), "\n";
# print "", &url( "\@clients", "Browse Clients"), "\n";
# print "", &url( "\@jobs", "Browse Jobs"), "\n";
#}
if ( !@ARGV) {
if ($ENV{CODELINES}) {
open( P4, "$ENV{CODELINES}" ) || &bail( "No codelines file." );
@CODELINES = ;
}
else
{
# Default codelines data is just a simple list of everything.
# If $CODELINES is set in the environment, the codelines comes
# from that. The format is:
#
# description1
# //path1
# description2
# //path2
@CODELINES = (
"Full Depot Source\n",
"//depot/...\n");
}
&html_header ( "Perforce Change Browser" );
print
"This browser allows you to view the history of a Perforce depot.\n",
"The first step is to select the files you want history for.\n",
"\n";
foreach ( @CODELINES )
{
chop;
if( m:^/: )
{
print "", &url( "\@changes+$_", $_ ), "\n";
}
elsif( !/^\s*$/ )
{
print "$_
\n";
}
}
}
elsif ( @ARGV[0] eq "\@users" ) {
&p4open( 'P4', "users|" );
&html_header ( "Perforce Users" );
print
"This browser allows you to view information about ",
" Perforce users.\n",
"The first step is to select which user you want to view\n",
"
";
print
"",
"User | Email | Full Name",
" | Last Accessed |
\n";
# Sample:
# jam (Jeffrey A. Marshall) accessed 1998/07/03
while( ) {
# print "$_ |
\n";
if (local( $user, $email, $fullname, $accessed ) =
/(\S+) <(\S+)> \((.*)\) accessed (\S+)/)
{
print "",
"", &url ( "\@user+$user", "$user"),
" | ", &mailto ( "$email" ),
" | ", "$fullname",
" | ", "$accessed",
" |
\n";
}
}
print "
\n";
close P4;
} elsif ( @ARGV[0] eq "\@user" ) {
local( $user, $email, $access, $fullname, $jobview );
$user = $ARGV[1];
&p4open ('P4', "user -o $user|");
while ()
{
next if (/^User:/);
next if (/^Email:/ && (( $email ) = /^Email:\s*(.*)$/ ));
next if (/^Access:/ && (( $access ) = /^Access:\s*(.*)$/ ));
next if (/^FullName:/ && (( $fullname ) = /^FullName:\s*(.*)$/ ));
next if (/^JobView:/ && (( $jobview ) = /^JobView:\s*(.*)$/ ));
last if (/^Reviews:/);
}
&html_header ( "Perforce User Information for $user" );
print
"This browser allows you to view information about ",
" a given Perforce user.\n
",
"Full Name
$fullname
\n",
"Email
", &mailto ("$email"), "
\n",
"Last Access
$access
\n",
"JobView
$jobview
\n",
"Reviews
\n";
while ()
{
print;
}
print "
\n";
close P4;
@OTHER_FOOTERS = (" | ",
, &url ("\@opened+user+$user",
"Files Opened by $user"));
} elsif ( @ARGV[0] eq "\@clients" ) {
&p4open( 'P4', "clients|" );
&html_header ( "Perforce Clients" );
print
"This browser allows you to view information about ",
" Perforce clients.\n",
"The first step is to select which client you want to view\n",
"
";
print
"",
"Client | Date | Root Directory",
" | Description |
\n";
# Sample:
# Client oak.template 1998/06/25 root /tmp 'OAK client template '
while( ) {
if (local( $client, $date, $root, $descrip ) =
/Client (\S+) (\S+) root (\S+) '(.*)'/)
{
print "",
"", &url ( "\@client+$client", "$client"),
" | $date",
" | $root",
" | $descrip",
" |
\n"
}
}
print "
\n";
close P4;
} elsif ( @ARGV[0] eq "\@client" ) {
local( $client, $date, $owner, $root, $opts );
$client = $ARGV[1];
&p4open ('P4', "client -o $client|");
while ()
{
next if (/^Client:/);
next if (/^Date:/ && (( $date ) = /^Date:\s*(.*)$/ ));
next if (/^Access:/ && (( $date ) = /^Access:\s*(.*)$/ ));
next if (/^Owner:/ && (( $owner ) = /^Owner:\s*(\S+)$/ ));
last if (/^Description:/);
}
&html_header ( "Perforce Client Information for $client" );
print
"This browser allows you to view information about ",
" a given Perforce client.\n
";
if ("$date" eq "") {
print "Client $client doesn't exist
";
}
else
{
print "Date
$date
\n",
"User
",
&url ("\@user+$owner", "$owner"), "
\n",
"Description
\n";
while ()
{
next if (/^$/);
last if (/^Root:/ && (( $root ) = /^Root:\s*(.*)$/ ));
print;
}
while ()
{
next if (/^Options:/ && (( $opts ) = /^Options:\s*(.*)$/ ));
last if (/^View:/);
}
print "
Root Directory
$root
\n",
"Options
$opts
\n",
"View
";
while ()
{
last if (/^$/);
print;
}
print "
";
}
close P4;
@OTHER_FOOTERS = (" | ", &url ("\@files+\@$client", "Files in $client"),
" | ", &url ("\@opened+client+$client", "Files Opened in $client"));
} elsif ( @ARGV[0] eq "\@jobs" ) {
&p4open( 'P4', "jobs|" );
&html_header ( "Perforce Jobs" );
print
"This browser allows you to view information about ",
" Perforce Jobs.\n",
"The first step is to select which job you want to view\n",
"
";
print
"",
"Job Name | Date | User",
" | Status | Description |
\n";
# Sample:
# job000011 on 1998/07/03 by jam *open* 'Another test. '
while( ) {
if (local( $name, $date, $user, $status, $descrip ) =
/^(\S+) on (\S+) by (\S+) \*(\S+)\* '(.*) '$/)
{
print "",
"", &url ( "\@job+$name", "$name"),
" | $date",
" | ", &url ( "\@user+$user", "$user"),
" | $status",
" | $descrip",
" |
\n";
}
}
print "
\n";
close P4;
} elsif ( @ARGV[0] eq "\@branches" || @ARGV[0] eq "\@labels" ) {
local ($type, $cmd, $viewer);
if (@ARGV[0] eq "\@branches")
{
$type = "Branch";
$cmd = "branches";
$viewer = "branch";
}
else
{
$type = "Label";
$cmd = "labels";
$viewer = "label";
}
&p4open( 'P4', "$cmd|" );
&html_header ( "Perforce ${type}es" );
print
"This browser allows you to view information about ",
" Perforce ${type}es.\n",
"The first step is to select which $viewer you want to view\n",
"
",
"",
"$type Name | Date | Description |
\n";
# Sample:
# Branch test 1998/07/03 'Created by jam. '
# Label example-label.template 1998/06/26 'Label tempalte for the example '
while( ) {
if (local( $name, $date, $descrip ) =
/^$type (\S+) (\S+) '(.*) '$/)
{
print "",
"", &url ( "\@$viewer+$name", "$name"),
" | $date",
" | $descrip",
" |
\n";
}
}
print "
\n";
close P4;
} elsif ( @ARGV[0] eq "\@branch" || @ARGV[0] eq "\@label") {
local( $type, $cmd, $name, $date, $owner, $opts, $filespec );
$name = $ARGV[1];
if (@ARGV[0] eq "\@branch")
{
$type = "Branch";
$cmd = "branch";
$filespec = "//$name/...";
}
else
{
$type = "Label";
$cmd = "label";
$filespec = "+\@$name";
}
&p4open ('P4', "$cmd -o $name|");
while ()
{
next if (/^$type:/);
next if (/^Date:/ && (( $date ) = /^Date:\s*(.*)$/ ));
next if (/^Access:/ && (( $date ) = /^Access:\s*(.*)$/ ));
next if (/^Update:/ && (( $date ) = /^Update:\s*(.*)$/ ));
next if (/^Owner:/ && (( $owner ) = /^Owner:\s*(\S+)$/ ));
last if (/^Description:/);
}
&html_header ( "Perforce $type Information for $name");
print
"This browser allows you to view information about ",
" a given Perforce $cmd.\n
";
if ("$date" eq "") {
print "$type $name doesn't exist
";
}
else
{
print "Date
$date
\n",
"User
",
&url ("\@user+$owner", "$owner"), "
\n",
"Description
\n";
while ()
{
next if (/^$/);
last if (/^View:/);
last if (/^Options:/ && (( $opts ) = /^Options:\s*(.*)$/ ));
print;
}
print "
";
if ("$opts" ne "")
{
print "Options
$opts
\n";
while ()
{
last if (/^View:/);
}
}
print "View
";
$filespec = "" if ("$cmd" eq "branch");
while ()
{
last if (/^$/);
if ("$cmd" eq "branch")
{
local ($spec) = /^\s*\S+ (\S+)$/;
$filespec = "$filespec+$spec";
}
print;
}
print "
";
}
close P4;
@OTHER_FOOTERS = (" | ", &url ("\@files$filespec", "Files in this $type"));
}
################################
#
# changes [ path ]
#
# show changes for path
#
################################
elsif( $ARGV[0] eq "\@changes" ) {
&p4open( 'P4', "changes -l $ARGV[1]|" );
&html_header ( "Changes for $ARGV[1]" );
print
"This form displays the changes for the files you've selected.\n",
"Click on the change number to see details of a change. Changes\n",
"are listed in reverse chronological order, so you see what's\n",
"most recent first.\n",
"
\n";
while () {
s/&/&/g;
s/\"/"/g;
s/</g;
s/>/>/g;
if( local( $change, $on, $user, $client ) = /^Change (\d+) on (\S+) by (\S+)@(\S+)$/ )
{
print
"- ", &url( "\@describe+$change", "Change $change" ),
" on $on by ", &url ("\@user+$user", "$user"),
"\@", &url ("\@client+$client", "$client"), "
- \n";
}
else
{
chop;
print "$_
\n";
}
}
print "
\n";
close P4;
}
################################
#
# describe change
#
# describe a change
#
################################
elsif( $ARGV[0] eq "\@describe" ) {
&p4open( 'P4', "describe -s $ARGV[1]|" );
$_ = ;
( local($chn, $user, $client, $date, $time) =
/^Change (\d+) by (\S*)@(\S*) on (\S*) (\S*)$/ )
|| &bail( $_ );
&html_header ( "Change $chn" );
print
"This form displays the details of a change. For each of the\n",
"files affected, you can click on:\n",
"\n",
"- Filename -- to see the complete file history\n",
"
- Revision Number -- to see the file text\n",
"
- Action -- to see the deltas (diffs)\n",
"
",
"
\n",
"Author ", &url ("\@user+$user", "$user"), "\n",
"Client ", &url ("\@client+$client", "$client"), "\n",
"Date $time $date\n",
"
\n",
"Description
\n",
"\n";
while() {
next if /^\s*$/;
last if /^Jobs fixed/;
last if /^Affected files/;
print $_;
}
print
"
",
"
\n";
# display jobs
if( /^Jobs fixed/ )
{
print
"Jobs Fixed
\n",
"\n";
while ( ) {
local( $job, $time, $user, $client );
while( ( $job, $time, $user, $client ) =
/(\S*) fixed on (\S*) by (\S*)@(\S*)/ )
{
print
"",
&url( "\@job+$job", $job ),
"
\n";
while() {
last if /^\S/;
print $_;
}
}
print "
\n";
last if /^Affected files/;
}
print
"",
"
\n";
}
print
"Files
\n",
"\n",
"",
"File | Rev | Action |
\n";
# Sample:
# ... //depot/main/p4/Jamrules#71 edit
while() {
if( local( $file, $rev, $act ) = /^\.\.\. (\S*)#(\d*) (\S*)$/ )
{
print
"",
"", &url( "\@filelog+$file", "$file" ),
" | ", &url( "\@print+$file+$rev", "$rev" ),
" | ", &url( "\@diff+$file+$rev+$act", "$act" ),
" |
\n";
}
}
print
"
\n";
close P4;
}
################################
#
# filelog file
#
# show filelog of the file
#
################################
elsif ($ARGV[0] eq "\@filelog") {
local( $name ) = $ARGV[1];
&p4open( 'P4', "filelog $name|" );
$name = ;
chop $name;
&html_header ( "Filelog $name" );
print
"This form shows the history of an individual file across\n",
"changes. You can click on the following:\n",
"\n",
"- Revision Number -- to see the file text\n",
"
- Action -- to see the deltas (diffs)\n",
"
- Change -- to see the complete change description, including\n",
"other files.\n",
"
",
"
\n";
print
"",
"Rev | Action | Date",
" | User\@client | Change | Desc |
\n";
# Sample:
# ... #78 change 1477 edit on 04/18/1996 by user@client 'Fix NT mkdi'
while( ) {
if (local( $rev, $change, $act, $date, $user, $client, $edit_type, $desc ) =
/^\.\.\. \#(\d+) \S+ (\d+) (\S+) on (\S+) by (\S*)@(\S*) \((\S+)\) '(.*)'/)
{
if ($act eq 'branch') {
$_ = ;
my ($fromname,$fromrev) = /^.*branch from (\S+?)\#(\d+).*/;
print
"",
"", &url( "\@print+$name+$rev", "$rev" ),
" | ", &url( "\@filelog+$fromname+$fromrev", $act ),
" | $date",
" | ", &url ("\@user+$user", "$user"), "\@",
&url ("\@client+$client", "$client"),
" | ", &url( "\@describe+$change", "$change" ),
" | $desc",
" |
\n";
}
elsif ($act eq 'delete') {
print
"",
"", &url( "\@print+$name+$rev", "$rev" ),
" | $DEL$act$DELEND",
" | $date",
" | ", &url ("\@user+$user", "$user"), "\@",
&url ("\@client+$client", "$client"),
" | ", &url( "\@describe+$change", "$change" ),
" | $desc",
" |
\n";
}
else {
print
"",
"", &url( "\@print+$name+$rev", "$rev" ),
" | ", &url( "\@diff+$name+$rev+$act", $act ),
" | $date",
" | ", &url ("\@user+$user", "$user"), "\@",
&url ("\@client+$client", "$client"),
" | ", &url( "\@describe+$change", "$change" ),
" | $desc",
" |
\n";
}
}
}
print "
\n";
close P4;
}
elsif ($ARGV[0] eq "\@files") {
&p4open( 'P4', "files @ARGV[1..$#ARGV]|" );
&html_header ( "Files for @ARGV[1..$#ARGV]" );
print
"This form displays files in the depot for a given revision.\n",
"For each of the files, you can click on:\n",
"\n",
"- Filename -- to see the complete file history\n",
"
- Revision Number -- to see the file text\n",
"
- Action -- to see the deltas (diffs)\n",
"
- Change -- to see the complete change description, including\n",
"other files.\n",
"
",
"
\n";
print
"Files
\n",
"\n",
"",
"File | Rev | Action | Change |
\n";
# Sample:
# //example/find/TypeExpr.java#1 - add change 5 (ktext)
while() {
if( local( $file, $rev, $act, $change, $type ) =
/^(\S+)#(\d*) - (\S+) change (\d*) \((\S+)\)$/ )
{
print
"",
"", &url( "\@filelog+$file", "$file" ),
" | ", &url( "\@print+$file+$rev", "$rev" ),
" | ", &url( "\@diff+$file+$rev+$act", "$act" ),
" | ", &url( "\@describe+$change", "$change" ),
# " | ", "$type",
" |
\n";
}
}
print
"
\n";
close P4;
}
elsif ($ARGV[0] eq "\@opened") {
&p4open( 'P4', "opened -a|" );
&html_header ( "Opened files for @ARGV[1..$#ARGV]" );
print
"This form displays files opened by the specified @ARGV[1].\n",
"For each of the files, you can click on:\n",
"\n",
"- Filename -- to see the complete file history\n",
"
- Revision Number -- to see the file text\n",
"
- User -- to see the a user description\n",
"
- Client -- to see the a client description\n",
"
",
"
\n";
print
"Files
\n",
"\n",
"",
"File | Rev | Action | Change List",
" | Type | User\@Client |
\n";
# Sample:
# //foo/file.java#2 - edit default change (text) by user@client
while() {
if (local( $file, $rev, $act, $change, $type, $user, $client ) =
/^(\S+)#(\d*) - (\S+) (\S+) change \((\S+)\) by (\S+)@(\S+)$/)
{
next if ((@ARGV[1] eq "user" ? $user : $client) ne @ARGV[2]);
print
"",
"", &url( "\@filelog+$file", "$file" ),
" | ", &url( "\@print+$file+$rev", "$rev" ),
" | $act | $change | $type",
" | ", &url( "\@user+$user", "$user" ), "\@",
&url( "\@client+$client", "$client" ),
" |
\n";
}
}
print
"
\n";
close P4;
}
################################
#
# print file rev action
#
# print file text
#
################################
elsif ($ARGV[0] eq "\@print") {
local($name, $rev) = @ARGV[1..2];
&p4open( 'P4', "print $name#$rev|" );
# Get header line
# //depot/main/jam/Jamfile#39 - edit change 1749 (text)
$_ = ;
local( $name, $rev, $type ) = m!^(\S+)\#(\d+) - \S+ \S+ \S+ \((\w+)\)!;
&html_header ( "File $name#$rev" );
print
"This form shows you the raw contents of a file, as long as \n",
"it isn't binary.",
"
\n";
if( $type eq "binary" || $type eq "xbinary" )
{
print "$type
\n";
}
else
{
print "\n";
while( ) {
s/&/&/g;
s/\"/"/g;
s/</g;
s/>/>/g;
print $_;
}
print "
\n";
}
close P4;
}
################################
#
# diff file rev action
#
# describe a change
#
################################
elsif ($ARGV[0] eq "\@diff") {
local( $name, $rev, $mode ) = @ARGV[1..3];
local( $nchunk ) = 0;
&html_header ( "$name#$rev -- $mode" );
print
"This form shows you the deltas (diffs) that lead from the\n",
"previous to the current revision.\n",
"
\n";
if ($mode ne 'add' && $mode ne 'delete' && $mode ne 'branch') {
local($f1) = "$name#" . ($rev - 1);
local($f2) = "$name#" . ($rev);
&p4open('P4', "diff2 $f1 $f2|");
$_ = ;
while () {
local( $dels, $adds );
local( $la, $lb, $op, $ra, $rb ) =
/(\d+),?(\d*)([acd])(\d+),?(\d*)/;
next unless $ra;
if( !$lb ) { $lb = $op ne 'a' ? $la : $la - 1; }
if( !$rb ) { $rb = $op ne 'd' ? $ra : $ra - 1; }
$start[ $nchunk ] = $op ne 'd' ? $ra : $ra + 1;
$dels[ $nchunk ] = $dels = $lb - $la + 1;
$adds[ $nchunk ] = $adds = $rb - $ra + 1;
@lines[ $nchunk ] = ();
# deletes
while( $dels-- ) {
$_ = ;
s/^. //;
if (/[&<>]/) {
s/&/\&/g;
s/\</g;
s/>/\>/g;
}
@lines[ $nchunk ] .= $_;
}
# separator
if ($op eq 'c') {
$_ = ;
}
# adds
while( $adds-- ) {
$_ = ;
}
$nchunk++;
}
close P4;
}
# Now walk through the diff chunks, reading the current rev and
# displaying it as necessary.
print
"",
"$ADD added lines $ADDEND\n",
"$DEL deleted lines $DELEND\n",
"
\n";
local( $curlin ) = 1;
&p4open('P4', "print -q $name#$rev|");
for( $n = 0; $n < $nchunk; $n++ )
{
# print up to this chunk.
&catchup( 'P4', $start[ $n ] - $curlin );
# display deleted lines -- we saved these from the diff
if( $dels[ $n ] )
{
print "$DEL";
print @lines[ $n ];
print "$DELEND";
}
# display added lines -- these are in the file stream.
if( $adds[ $n ] )
{
print "$ADD";
&display( 'P4', $adds[ $n ] );
print "$ADDEND";
}
$curlin = $start[ $n ] + $adds[ $n ];
}
&catchup( 'P4', 999999999 );
close P4;
}
################################
#
# job job
#
# describe a job
#
################################
elsif ($ARGV[0] eq "\@job") {
local( $user, $job, $status, $time, $date );
&p4open( 'P4', "job -o $ARGV[1]|" );
while( )
{
next if ( /^Job/ && ( ( $job ) = /^Job:\s(\S*)/ ) );
next if ( /^User/ && ( ( $user ) = /^User:\s*(\S*)/ ) );
next if ( /^Status/ && ( ( $status ) = /^Status:\s*(\S*)/ ) );
next if ( /^Date/ && ( ( $date, $time ) = /^Date:\s*(\S*) (\S*)/ ) );
next if ( /^Access/ && ( ( $date, $time ) = /^Access:\s*(\S*) (\S*)/ ) );
last if ( /^Description/ );
}
&html_header ( "Job $job" );
print
"This form displays the details of a job. You can click on a\n",
"change number to see its description.\n",
"",
"
\n",
"User ", &url ("\@user+$user", "$user"), "\n",
"Status $status\n",
"Date $time $date\n",
"
\n",
"Description
\n",
"\n";
while() {
print $_;
}
print
"
",
"
\n";
close P4;
# display fixes
&p4open( 'P4', "fixes -j $ARGV[1]|" );
$count = 0;
while( )
{
print
"Fixes
\n",
"\n",
"",
"Change | Date | User\@Client |
\n"
if( !$count++ );
# jobx fixed by change N on 1997/04/25 by user@host
if( local( $change, $date, $user, $client )
= /^\S* fixed by change (\S*) on (\S*) by (\S*)@(\S*)/ )
{
print
"",
"", &url( "\@describe+$change", "$change" ),
" | ", $date,
" | ", &url ("\@user+$user", "$user"), "\@",
&url ("\@client+$client", "$client"),
" |
\n";
}
}
print "
\n"
if( $count );
close P4;
}
################################
#
# None of the above.
#
################################
else {
&bail( "Invalid invocation @ARGV" );
}
# Trailer
@HTMLFOOTER = (
"* * *
",
&url ("", "Top"), " | ",
&url ("\@clients", "Clients"), " | ",
&url ("\@users", "Users"), " | ",
&url ("\@branches", "Branches"), " | ",
&url ("\@labels", "Labels"), " | ",
&url ("\@jobs", "Jobs"),
@OTHER_FOOTERS,
"
\n",
"$title
\n\n";
}
sub html_error_header {
local( $title ) = @_;
print "Content-type: text/html\n",
"\n",
"\n",
"\n",
" $title\n",
"\n",
"\n",
"$title
\n\n";
}
sub catchup {
local( $handle, $to ) = @_;
if( $to > $MAXCONTEXT )
{
local( $skipped ) = $to - $NCONTEXT * 2;
&display( $handle, $NCONTEXT );
$skipped -= &skip( $handle, $skipped );
print
"
",
"$skipped lines skipped",
"
\n" if( $skipped );
&display( $handle, $NCONTEXT );
}
else
{
&display;
}
}
\n");
print @HTMLFOOTER;
##################################################################
##################################################################
#
# Subroutines.
#
##################################################################
##################################################################
sub url {
local( $url, $name ) = @_;
return qq($name) ;
}
sub mailto {
# local( $uname ) = @_;
return qq(@_) ;
}
sub bail {
&html_error_header ( "Perforce Browser Error" );
print @_, "\n";
die @_;
}
sub p4open {
local( $handle, @command ) = @_;
open( $handle, "p4 @command" ) || &bail( "p4 @command failed" );
}
# Support for processing diff chunks.
#
# skip: skip lines in source file
# display: display lines in source file, handling funny chars
# catchup: display & skip as necessary
#
sub skip {
local( $handle, $to ) = @_;
while( $to > 0 && ( $_ = <$handle> ) ) {
$to--;
}
return $to;
}
sub display {
local( $handle, $to ) = @_;
while( $to-- > 0 && ( $_ = <$handle> ) ) {
if (/[&<>]/)
{
s/&/\&/g;
s/\</g;
s/>/\>/g;
}
print $_;
}
}
sub html_header {
local( $title ) = @_;
print "Content-type: text/html\n",
"\n",
"\n",
"