#!/usr/bin/perl # # perfbrowse.perl -- CGI browser for PERFORCE # # $Id: //guest/randall_gellens/perfbrowse/perfbrowse.perl#4 $ # # 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 11/18/99 by Alan Bird # Support for filenames/directories with spaces # # 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... #*************************************************************************** # Configuration Section #*************************************************************************** # Set environment variables needed for Perforce $ENV{'P4PORT'} = "perforceserver:port"; $ENV{'P4CLIENT'} = "perforceclient"; $ENV{'P4USER'} = "username"; $ENV{'P4PASSWD'} = "XXXXXXX"; # 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" ); @HTMLBODY = ( "\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, # "Perforce Browser\n", # @HTMLBODY, # "

Perforce Browser

\n
", # "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"); } print @HTMLHEADER, "Perforce Change Browser\n", @HTMLBODY, "

    Perforce Change Browser

    \n
    ", "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|" ); print @HTMLHEADER, "Perforce Users\n", @HTMLBODY, "

    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", "
    "; print "", "\n"; # Sample: # jam (Jeffrey A. Marshall) accessed 1998/07/03 while( ) { # print "\n"; if (local( $user, $email, $fullname, $accessed ) = /(\S+) <(\S+)> \((.*)\) accessed (\S+)/) { print "", "\n"; } } print "
    UserEmailFull Name", "Last Accessed
    $_
    ", &url ( "\@user+$user", "$user"), "", &mailto ( "$email" ), "", "$fullname", "", "$accessed", "
    \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:/); } print @HTMLHEADER, "Perforce User Information for $user\n", @HTMLBODY, "

    Perforce User Information for $user

    \n
    ", "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|" ); print @HTMLHEADER, "Perforce Clients\n", @HTMLBODY, "

    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", "
    "; print "", "\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 "", "\n" } } print "
    ClientDateRoot Directory", "Description
    ", &url ( "\@client+$client", "$client"), "$date", "$root", "$descrip", "
    \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:/); } print @HTMLHEADER, "Perforce Client Information for $client\n", @HTMLBODY, "

    Perforce Client Information for $client

    \n
    ", "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|" ); print @HTMLHEADER, "Perforce Jobs\n", @HTMLBODY, "

    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", "
    "; print "", "\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 "", "\n"; } } print "
    Job NameDateUser", "StatusDescription
    ", &url ( "\@job+$name", "$name"), "$date", "", &url ( "\@user+$user", "$user"), "$status", "$descrip", "
    \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, "Perforce ${type}es\n", @HTMLBODY, "

    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", "
    ", "", "\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 "", "\n"; } } print "
    $type NameDateDescription
    ", &url ( "\@$viewer+$name", "$name"), "$date", "$descrip", "
    \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:/); } print @HTMLHEADER, "Perforce $type Information for $name\n", @HTMLBODY, "

    Perforce $type Information for $name

    \n
    ", "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]|" ); print @HTMLHEADER, "Changes for $ARGV[1]\n", @HTMLBODY, "

    Changes for $ARGV[1]

    \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", "
    \n"; while () { 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( $_ ); print @HTMLHEADER, "Change $chn\n", @HTMLBODY, "

    Change $chn

    \n", "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", "", "\n"; # Sample: # ... //depot/main/p4/Jamrules#71 edit while() { # (alb) I changed the following match expression 7/30/99 # It was /^\.\.\. (\S*)#(\d*) (\S*)$/ # it wouldn't match filenames with spaces if( local( $file, $rev, $act ) = /^\.\.\. (.*)#(\d*) (\S*)$/ ) { $fileurl = $file; $fileurl =~ s/ /%20/; print "", "\n"; } } print "
        FileRevAction
        ", &url( "\@filelog+$fileurl", "$file" ), "", &url( "\@print+$fileurl+$rev", "$rev" ), "", &url( "\@diff+$fileurl+$rev+$act", "$act" ), "
      \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; print @HTMLHEADER, "Filelog $name\n", @HTMLBODY, "

      Filelog $name

      \n", "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 &dop4pr($name,"Click here for line by line annotation of who changed each line of this file and when (can take a few minutes)"), "
      WARNING: THIS TAKES A LOT OF CPU, DON'T JUST CLICK FOR GIGGLES!", "
      \n"; print "", "\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 "", "\n"; } elsif ($act eq 'delete') { print "", "\n"; } else { print "", "\n"; } } } print "
      RevActionDate", "User\@clientChangeDesc
      ", &url( "\@print+$name+$rev", "$rev" ), "", &url( "\@filelog+$fromname+$fromrev", $act ), "$date", "", &url ("\@user+$user", "$user"), "\@", &url ("\@client+$client", "$client"), "", &url( "\@describe+$change", "$change" ), "$desc", "
      ", &url( "\@print+$name+$rev", "$rev" ), "$DEL$act$DELEND", "$date", "", &url ("\@user+$user", "$user"), "\@", &url ("\@client+$client", "$client"), "", &url( "\@describe+$change", "$change" ), "$desc", "
      ", &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"; close P4; } elsif ($ARGV[0] eq "\@files") { &p4open( 'P4', "files @ARGV[1..$#ARGV]|" ); print @HTMLHEADER, "Files for $ARGV[1..$#ARGV]\n", @HTMLBODY, "

      Files for @ARGV[1..$#ARGV]

      \n", "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", "", "\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 "", "\n"; } } print "
        FileRevActionChange
        ", &url( "\@filelog+$file", "$file" ), "", &url( "\@print+$file+$rev", "$rev" ), "", &url( "\@diff+$file+$rev+$act", "$act" ), "", &url( "\@describe+$change", "$change" ), # "", "$type", "
      \n"; close P4; } elsif ($ARGV[0] eq "\@opened") { &p4open( 'P4', "opened -a|" ); print @HTMLHEADER, "Opened files for @ARGV[1..$#ARGV]\n", @HTMLBODY, "

      Opened files for @ARGV[1..$#ARGV]

      \n", "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", "", "\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 "", "\n"; } } print "
        FileRevActionChange List", "TypeUser\@Client
        ", &url( "\@filelog+$file", "$file" ), "", &url( "\@print+$file+$rev", "$rev" ), "$act$change$type", "", &url( "\@user+$user", "$user" ), "\@", &url( "\@client+$client", "$client" ), "
      \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+)\)!; print @HTMLHEADER, "File $name\n", @HTMLBODY, "

      File $name#$rev

      \n", "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;
      	    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; print @HTMLHEADER, "$name#$rev\n", @HTMLBODY, "

      $name#$rev - $mode

      \n", "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; } @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/ );
          }
      
          print 
      	@HTMLHEADER,
      	"Job $job\n",
      	@HTMLBODY,
      	"

      Job $job

      \n", "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", "", "\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 "", "\n"; } } print "
        ChangeDateUser\@Client
        ", &url( "\@describe+$change", "$change" ), "", $date, "", &url ("\@user+$user", "$user"), "\@", &url ("\@client+$client", "$client"), "
      \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"); print @HTMLFOOTER; ################################################################## ################################################################## # # Subroutines. # ################################################################## ################################################################## sub url { local( $url, $name ) = @_; return qq($name) ; } sub dop4pr { 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; } 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; } }