#!/usr/local/bin/perl
#
# perfbrowse.perl -- CGI browser for PERFORCE
#
# $Id: //depot/r98.2/p4-tools/web/perfbrowse.perl#2 $
#
# 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)
#
# 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} = "p4:1666";
$ENV{P4CLIENT} = "p4browse";
$ENV{PATH} .= ":/usr/local/bin";
# 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;
@HTMLHEADER = (
"Content-type: text/html\n",
"\n",
"\n",
"\n",
"\n" );
@HTMLERROR = (
"Content-type: text/html\n",
"\n",
"\n",
"\n",
"\n" );
@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) {
# print @HTMLHEADER,
# "
\n"; } } } elsif ( @ARGV[0] eq "\@users" ) { &p4open( 'P4', "users|" ); print @HTMLHEADER, "Perforce Users \n", "", "This browser allows you to view information about ", " Perforce users.\n", "The first step is to select which user you want to view\n", " Perforce Users
\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 (\n"; # Sample: # jam User Full Name", " Last Accessed (Jeffrey A. Marshall) accessed 1998/07/03 while( ) { # print " \n"; if (local( $user, $email, $fullname, $accessed ) = /(\S+) <(\S+)> \((.*)\) accessed (\S+)/) { print " $_ ", " \n"; } } print "", &url ( "\@user+$user", "$user"), " ", &mailto ( "$email" ), " ", "$fullname", " ", "$accessed", " ) { 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:/); } print @HTMLHEADER, " Perforce User Information for $user \n", "", "This browser allows you to view information about ", " a given Perforce user.\n Perforce User Information for $user
\n
", "Full Name
$fullname\n", "", &mailto ("$email"), "\n", "Last Access
$access\n", "JobView
$jobview\n", "Reviews
\n"; while (\n"; close P4; @OTHER_FOOTERS = (" | ", , &url ("\@opened+user+$user", "Files Opened by $user")); } elsif ( @ARGV[0] eq "\@clients" ) { &p4open( 'P4', "clients|" ); print @HTMLHEADER, ") { print; } print " Perforce Clients \n", "", "This browser allows you to view information about ", " Perforce clients.\n", "The first step is to select which client you want to view\n", " Perforce Clients
\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 (\n"; # Sample: # Client oak.template 1998/06/25 root /tmp 'OAK client template ' while( Client Date Root Directory", " Description ) { if (local( $client, $date, $root, $descrip ) = /Client (\S+) (\S+) root (\S+) '(.*)'/) { print " ", " \n" } } print "", &url ( "\@client+$client", "$client"), " $date", " $root", " $descrip", " ) { next if (/^Client:/); next if (/^Date:/ && (( $date ) = /^Date:\s*(.*)$/ )); next if (/^Owner:/ && (( $owner ) = /^Owner:\s*(\S+)$/ )); last if (/^Description:/); } print @HTMLHEADER, " Perforce Client Information for $client \n", "", "This browser allows you to view information about ", " a given Perforce client.\n Perforce Client Information for $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 ("; } 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|" ); print @HTMLHEADER, ") { last if (/^$/); print; } print " Perforce Jobs \n", "", "This browser allows you to view information about ", " Perforce Jobs.\n", "The first step is to select which job you want to view\n", " Perforce Jobs
\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|" ); print @HTMLHEADER, "\n"; # Sample: # job000011 on 1998/07/03 by jam *open* 'Another test. ' while( Job Name Date User", " Status Description ) { if (local( $name, $date, $user, $status, $descrip ) = /^(\S+) on (\S+) by (\S+) \*(\S+)\* '(.*) '$/) { print " ", " \n"; } } print "", &url ( "\@job+$name", "$name"), " $date", " ", &url ( "\@user+$user", "$user"), " $status", " $descrip", " Perforce ${type}es \n", "", "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", " Perforce ${type}es
\n
", "", "
\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 (\n"; # Sample: # Branch test 1998/07/03 'Created by jam. ' # Label example-label.template 1998/06/26 'Label tempalte for the example ' while( $type Name Date Description ) { if (local( $name, $date, $descrip ) = /^$type (\S+) (\S+) '(.*) '$/) { print " ", " \n"; } } print "", &url ( "\@$viewer+$name", "$name"), " $date", " $descrip", " ) { next if (/^$type:/); next if (/^Date:/ && (( $date ) = /^Date:\s*(.*)$/ )); next if (/^Owner:/ && (( $owner ) = /^Owner:\s*(\S+)$/ )); last if (/^Description:/); } print @HTMLHEADER, " Perforce $type Information for $name \n", "", "This browser allows you to view information about ", " a given Perforce $cmd.\n Perforce $type Information for $name
\n
"; if ("$date" eq "") { print "$type $name doesn't exist
"; } else { print "Date
$date\n", "User
", &url ("\@user+$owner", "$owner"), "\n", "Description
\n"; while ("; if ("$opts" ne "") { print ") { next if (/^$/); last if (/^View:/); last if (/^Options:/ && (( $opts ) = /^Options:\s*(.*)$/ )); print; } print " Options
$opts\n"; while () { last if (/^View:/); } } print " View
"; $filespec = "" if ("$cmd" eq "branch"); while ("; } 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]|" ); print @HTMLHEADER, ") { last if (/^$/); if ("$cmd" eq "branch") { local ($spec) = /^\s*\S+ (\S+)$/; $filespec = "$filespec+$spec"; } print; } print " Changes for $ARGV[1] \n", "\n", "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", " Changes for $ARGV[1]
\n"; while (
\n"; close P4; } ################################ # # describe change # # describe a change # ################################ elsif( $ARGV[0] eq "\@describe" ) { &p4open( 'P4', "describe -s $ARGV[1]|" ); $_ =) { 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 "; ( local($chn, $user, $client, $date, $time) = /^Change (\d+) by (\S*)@(\S*) on (\S*) (\S*)$/ ) || &bail( $_ ); print @HTMLHEADER, " Change $chn \n", "\n", "This form displays the details of a change. For each of the\n", "files affected, you can click on:\n", " Change $chn
\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(\n"; last if /^Affected files/; } print "", ") { last if /^\S/; print $_; } } print "
\n"; } print "Files
\n", "\n", "
\n"; close P4; } ################################ # # filelog file # # show filelog of the file # ################################ elsif ($ARGV[0] eq "\@filelog") { local( $name ) = $ARGV[1]; &p4open( 'P4', "filelog $name|" ); $name =", "
\n"; # Sample: # ... //depot/main/p4/Jamrules#71 edit while( File Rev Action ) { if( local( $file, $rev, $act ) = /^\.\.\. (\S*)#(\d*) (\S*)$/ ) { print " ", " \n"; } } print "", &url( "\@filelog+$file", "$file" ), " ", &url( "\@print+$file+$rev", "$rev" ), " ", &url( "\@diff+$file+$rev+$act", "$act" ), " ; chop $name; print @HTMLHEADER, " Filelog $name \n", "\n", "This form shows the history of an individual file across\n", "changes. You can click on the following:\n", " Filelog $name
\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 "", "
\n"; close P4; } elsif ($ARGV[0] eq "\@files") { &p4open( 'P4', "files @ARGV[1..$#ARGV]|" ); print @HTMLHEADER, "\n"; # Sample: # ... #78 change 1477 edit on 04/18/1996 by user@client 'Fix NT mkdi' while( Rev Action Date", " User\@client Change Desc ) { 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 " ", " \n"; } elsif ($act eq 'delete') { 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"; } else { print "", &url( "\@print+$name+$rev", "$rev" ), " $DEL$act$DELEND", " $date", " ", &url ("\@user+$user", "$user"), "\@", &url ("\@client+$client", "$client"), " ", &url( "\@describe+$change", "$change" ), " $desc", " ", " \n"; } } } 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", " Files for $ARGV[1..$#ARGV] \n", "\n", "This form displays files in the depot for a given revision.\n", "For each of the files, you can click on:\n", " Files for @ARGV[1..$#ARGV]
\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", "
\n"; close P4; } elsif ($ARGV[0] eq "\@opened") { &p4open( 'P4', "opened -a|" ); print @HTMLHEADER, "", "
\n"; # Sample: # //example/find/TypeExpr.java#1 - add change 5 (ktext) while( File Rev Action Change ) { if( local( $file, $rev, $act, $change, $type ) = /^(\S+)#(\d*) - (\S+) change (\d*) \((\S+)\)$/ ) { print " ", " \n"; } } print "", &url( "\@filelog+$file", "$file" ), " ", &url( "\@print+$file+$rev", "$rev" ), " ", &url( "\@diff+$file+$rev+$act", "$act" ), " ", &url( "\@describe+$change", "$change" ), # " ", "$type", " Opened files for @ARGV[1..$#ARGV] \n", "\n", "This form displays files opened by the specified @ARGV[1].\n", "For each of the files, you can click on:\n", " Opened files for @ARGV[1..$#ARGV]
\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", "
\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) $_ =", "
\n"; # Sample: # //foo/file.java#2 - edit default change (text) by user@client while( File Rev Action Change List", " Type User\@Client ) { 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 " ", " \n"; } } print "", &url( "\@filelog+$file", "$file" ), " ", &url( "\@print+$file+$rev", "$rev" ), " $act $change $type", " ", &url( "\@user+$user", "$user" ), "\@", &url( "\@client+$client", "$client" ), " ; local( $name, $rev, $type ) = m!^(\S+)\#(\d+) - \S+ \S+ \S+ \((\w+)\)!; print @HTMLHEADER, " File $name \n", "\n", "This form shows you the raw contents of a file, as long as \n", "it isn't binary.", " File $name#$rev
\n"; if( $type eq "binary" || $type eq "xbinary" ) { print "$type
\n"; } else { print "\n"; while(\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; print @HTMLHEADER, ") { s/&/&/g; s/\"/"/g; s/</g; s/>/>/g; print $_; } print " $name#$rev \n", "\n", "This form shows you the deltas (diffs) that lead from the\n", "previous to the current revision.\n", " $name#$rev - $mode
\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*)/ ) ); last if ( /^Description/ ); } print @HTMLHEADER, " Job $job \n", "\n", "This form displays the details of a job. You can click on a\n", "change number to see its description.\n", "", " Job $job
\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", "
\n" if( $count ); close P4; } ################################ # # None of the above. # ################################ else { &bail( "Invalid invocation @ARGV" ); } # Trailer @HTMLFOOTER = ( "", "
\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 " Change Date User\@Client ", " \n"; } } print "", &url( "\@describe+$change", "$change" ), " ", $date, " ", &url ("\@user+$user", "$user"), "\@", &url ("\@client+$client", "$client"), " * * *
", &url ("", "Top"), " | ", &url ("\@clients", "Clients"), " | ", &url ("\@users", "Users"), " | ", &url ("\@branches", "Branches"), " | ", &url ("\@labels", "Labels"), " | ", &url ("\@jobs", "Jobs"), @OTHER_FOOTERS, "\n"); print @HTMLFOOTER; ################################################################## ################################################################## # # Subroutines. # ################################################################## ################################################################## sub url { local( $url, $name ) = @_; return qq($name) ; } sub mailto { # local( $uname ) = @_; return qq(@_) ; } sub bail { print @HTMLERROR, @_, "\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 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; } }