<?php # WikiMedia Perforce Extension # # This extension adds support for linking and displaying Perforce # objects such as changes, jobs, and file paths. It works in tandem # with a browse mode P4Web to provide easy access to Perforce data. # In the future it may include the ability to get formatted lists # of jobs and changes. # Alert the user that this is not a valid entry point to MediaWiki # if they try to access the extension file directly. if (!defined('MEDIAWIKI')) { echo <<<EOT To install this plugin, add a section like the following to LocalSettings.php: require_once("\$IP/extensions/Perforce/Perforce.php"); \$wgP4EXEC = 'p4'; //path to the p4 client executable \$wgP4PORT = 'perforce:1666'; //Perforce server address:port \$wgP4USER = 'user'; //Perforce user with at least "list" access \$wgP4CLIENT = 'client'; //Perforce client that maps all files of interest \$wgP4PASSWD = 'password'; //Password/ticket for that user \$wgP4WEBURL = "http://computer.perforce.com:8080/"; The following setting is not needed if you have a server at 2009.1 or higher. \$wgP4DVER = 2010.1; EOT; exit( 1 ); } $dir = dirname(__FILE__) . '/'; require_once("$IP/includes/SpecialPage.php"); $wgHooks['ParserFirstCallInit'][] = 'wfSetupPerforce'; $wgHooks['LanguageGetMagic'][] = 'wfPerforceLanguageGetMagic'; $wgHooks['OutputPageParserOutput'][] = 'wfPerforceParserOutput'; $wgAjaxExportList[] = 'wfPerforceVariantsAjax'; $wgAutoloadClasses['Perforce'] = $dir . 'Perforce.php'; $wgExtensionMessagesFiles['Perforce'] = $dir . 'Perforce.msg.php'; $wgSpecialPages['Perforce'] = 'Perforce'; $version = '$Change: 9449 $'; $version = substr( $version, 9, -2 ); $wgExtensionCredits['parserhook'][] = $wgExtensionCredits['specialpage'][] = array ( 'name' => 'Perforce', 'version' => '@'.$version.'', 'url' => 'http://public.perforce.com/wiki/Perforce_MediaWiki_extension', 'author' => array('Matt Attaway', 'Sam Stafford', 'Marc Wensauer'), 'description' => 'Display Perforce data in wiki pages', ); function wfSetupPerforce( &$parser ) { $parser->setHook( 'p4change', 'tagP4Change' ); $parser->setHook( 'p4changes', 'tagP4Changes' ); $parser->setHook( 'p4print', 'tagP4Print' ); $parser->setHook( 'p4variants', 'tagP4Variants' ); $parser->setFunctionHook( 'p4changes', 'parseP4Changes' ); $parser->setFunctionHook( 'p4chgcats', 'parseP4ChgCats' ); $parser->setFunctionHook( 'p4diff2', 'parseP4Diff2' ); $parser->setFunctionHook( 'p4graph', 'parseP4Graph' ); $parser->setFunctionHook( 'p4info', 'parseP4Info' ); $parser->setFunctionHook( 'p4job', 'parseP4Job' ); $parser->setFunctionHook( 'p4jobs', 'parseP4Jobs' ); $parser->setFunctionHook( 'p4print', 'parseP4Print' ); $parser->setFunctionHook( 'p4variants', 'parseP4Variants' ); return true; } function wfPerforceLanguageGetMagic( &$magicWords, $langCode = "en" ) { switch( $langCode ) { default: # mind the ws and Ws. $magicWords['p4changes'] = array ( 0, 'p4changes' ); $magicWords['p4chgcats'] = array ( 0, 'p4chgcats' ); $magicWords['p4diff2'] = array ( 0, 'p4diff2' ); $magicWords['p4graph'] = array ( 0, 'p4graph' ); $magicWords['p4info'] = array ( 0, 'p4info' ); $magicWords['p4job'] = array ( 0, 'p4job' ); $magicWords['p4jobs'] = array ( 0, 'p4jobs' ); $magicWords['p4print'] = array ( 0, 'p4print' ); $magicWords['p4variants'] = array ( 0, 'p4variants' ); } return true; } function wfPerforceParserOutput( &$outputPage, $parserOutput ) { if ( !empty( $parserOutput->mPerforceJavascriptTag ) ) { global $wgJsMimeType, $wgScriptPath; $outputPage->addScript( "<script type=\"{$wgJsMimeType}\" src=\"{$wgScriptPath}/extensions/Perforce/Perforce.js?1\">" . "</script>\n" ); } return true; } class Perforce extends SpecialPage { function Perforce() { SpecialPage::SpecialPage("Perforce"); } function execute( $par ) { global $wgRequest, $wgOut; $this->setHeaders(); $param = $wgRequest->getText('param'); $pars = explode( '/', $par ); $fn = array_shift( $pars ); $args = explode( '@@', implode( '/', $pars ) ); switch( $fn ) { case 'changes': return $this->execChanges( $args ); case 'graph': return $this->execGraph( $args ); case 'info': return $this->execInfo( $args ); case 'job': return $this->execJob( $args ); case 'jobs': return $this->execJobs( $args ); case 'print': return $this->execPrint( $args ); case 'variants': return $this->execVariants( $args ); } return $this->execDefault(); } function execDefault() { global $wgP4WEBURL, $wgOut; $wgOut->setPagetitle( 'Perforce' ); $out = <<<EOO ==Server info== {{#p4info:}} ==Recent changes== {{#p4changes:7|//...|long}} ==Links== * [$wgP4WEBURL Browse this server via P4Web] * [http://www.perforce.com Perforce Software] * [http://www.perforce.com/perforce/technical.html Perforce Technical Documentation] * [http://kb.perforce.com Perforce Knowledge Base] * [http://public.perforce.com/ Perforce Public Depot] * [http://public.perforce.com/wiki/Perforce_MediaWiki_extension About this extension] EOO; $wgOut->addWikiText( $out ); } function execChanges( $args ) { global $wgOut; $toPath = ''; $byUser = ''; $made = ''; if ( isSet( $args[1] ) && $args[1] != '//...' ) $toPath = ' to '.$args[1]; if ( isSet( $args[3] ) ) $byUser = ' by user '.$args[3]; if ( $toPath || $byUser ) $made = ' made'; if ( !isSet( $args[0] ) ) $args[0] = '40'; $wgOut->setPagetitle( 'Last '.$args[0].' changes'.$made.$toPath.$byUser ); $wgOut->addWikiText( call_user_func_array( 'getP4Changes', $args ) ); } function execGraph( $args ) { global $wgOut; if( substr( $args[0], 0, 2 ) != '//' ) $args[0] = '//' . $args[0]; $wgOut->setPagetitle( 'Graph of '.$args[0] ); $wgOut->addWikiText( call_user_func_array( 'getP4GraphText', $args ) ); } function execInfo( $args ) { global $wgOut, $wgParser; $wgOut->setPagetitle( 'Perforce server info' ); $out = parseP4Info( $wgParser ); $wgOut->addWikiText( $out[0] ); } function execJob( $args ) { global $wgOut; $wgOut->setPagetitle( 'Perforce job '.$args[0].' '.$args[1] ); $wgOut->addWikiText( call_user_func_array( 'getP4Job', $args ) ); } function execJobs( $args ) { global $wgOut; $args = str_replace( '_', ' ', $args ); $wgOut->setPagetitle( 'Perforce jobs: '.$args[0] ); if ( !isSet( $args[0] ) ) $wgOut->setPagetitle( 'Perforce jobs' ); $wgOut->addWikiText( call_user_func_array( 'getP4Jobs', $args ) ); } function execPrint( $args ) { global $wgOut; $wgOut->setPagetitle( $args[0] ); $wgOut->addWikiText( call_user_func_array( 'getP4Print', $args ) ); } function execVariants( $args ) { global $wgOut; $wgOut->setPagetitle( 'Variants of '.$args[0] ); $wgOut->addHtml( getP4Variants( $args[0] ) ); } } # {{#p4changes:num|path|brief/long/full|user}} function parseP4Changes( &$parser, $num = '', $path = '', $desc = '', $user = '' ) { return array( getP4Changes( $num, $path, $desc, $user ), 'noparse' => false ); } function getP4Changes( $num = '', $path = '', $desc = '', $user = '' ) { if ( !$path ) $path = '//...'; $tag = '<p4changes'; if ( $num != '' ) $tag .= " num=\"$num\""; if ( $path != '' ) $tag .= " path=\"$path\""; if ( $desc != '' ) $tag .= " desc=\"$desc\""; if ( $user != '' ) $tag .= " user=\"$user\""; $tag .= '/>'; return $tag; } # {{#p4chgcats:path|fmt|keyword1|cat1|keyword2|cat2|...}} function parseP4ChgCats( &$parser, $path = '', $pre = '*', $fmt = '' ) { $parser->disableCache(); $cats = array(); // list of cats $maps = array(); // key->cat mappings // Build array of categories and keyword map. $arg = 4; //number of named args while ( func_num_args() > $arg + 1 ) { $key = func_get_arg( $arg ); $arg++; $cat = func_get_arg( $arg ); $arg++; $maps[ $key ] = $cat; if ( !in_array( $cat, $cats ) ) { $cats[] = $cat; $chgs[$cat] = array(); } } // mock up specdef for getSpecFields() $output = array(); $output[] = '... specdef change; ;;time; ;;user; ;;client; ;;status; ;;desc; ;;'; // Get ze changes! $paths = explode( '+', $path ); foreach( $paths as $path ) { $cmdline = buildP4Cmd(); $cmdline .= ' -Ztag -Zspecstring changes -l -m 10000 '.wfEscapeShellArg( $path ); exec( $cmdline, $output ); } $data = getSpecFields( $output ); array_shift( $data ); // shift off field list $result = ''; foreach( $cats as $cat ) { $result .= $cat."\n"; foreach( $data as $chg ) { $show = false; foreach( $maps as $key => $map ) { if ( $map != $cat ) continue; if ( !isset( $chg['desc'] ) || stripos( $chg['desc'], $key ) === false ) continue; $show = true; } if ( !$show ) continue; $desc = $chg['desc']; formatField( $fmt, $desc ); $result .= $pre.'<p4change>'.$chg['change'].'</p4change>: '; $result .= $desc."\n"; } } return array($result, 'noparse' => false); } function tagP4Changes( $input, $argv, $parser ) { return renderRecentChanges( $input, $argv, $parser, 'html' ); } # {{#p4diff2:path1|path2|flags}} function parseP4Diff2( &$parser, $path1 = '', $path2 = '', $flags = '' ) { $parser->disableCache(); if( $path1 == '' || $path2 == '' ) return array( 'missing args for #p4diff2', 'noparse' => false ); $quiet = ''; $diff = '-d'; $add = false; $regs = array(); if( preg_match( '/-q/', $flags, $regs ) ) $quiet = '-q '; if( preg_match( '/-A/', $flags, $regs ) ) $add = true; if( preg_match( '/(-d\w+)/', $flags, $regs ) ) $diff = $regs[1]; if( $add ) { $diff .= 's'; $quiet = ''; } if( $diff == '-d' ) $diff = ''; else $diff = wfEscapeShellArg( $diff ).' '; $cmdline = buildP4Cmd(); $cmdline .= ' diff2 '.$quiet.$diff; $cmdline .= wfEscapeShellArg( $path1 ).' '; $cmdline .= wfEscapeShellArg( $path2 ).' 2>&1'; $out = array(); exec( $cmdline, $out ); if( $add ) { $count = 0; foreach( $out as $line ) { if( preg_match( '/add \d+ chunks (\d+) lines/', $line, $regs ) ) $count += $regs[1]; if( preg_match( '/chunks \d+ \/ (\d+) lines/', $line, $regs ) ) $count += $regs[1]; } return array( $count, 'noparse' => false ); } else return array( ' '.implode( "\n ", $out ), 'noparse' => false ); } # {{#p4graph:path|constraint|server|dot}} function parseP4Graph( &$parser, $path, $const= '', $server = '', $dot = '' ) { $parser->disableCache(); return array( getP4GraphText( $path, $const, $server, $dot ), 'noparse' => false ); } function getP4GraphText( $path, $const = '', $server = '', $dot = '' ) { global $wgP4DVER; $flag = '-1 '; if ( $wgP4DVER && $wgP4DVER < 2009.1 ) $flag = ''; $rankdir = 'LR'; if ( $const == 'file' ) $rankdir = 'TD'; if ( substr( $path, 0, 2 ) != '//' ) $path = '//' . $path; $cmdline = buildP4Cmd( $server ); $cmdline .= ' filelog ' . $flag . wfEscapeShellArg( $path ) . ' 2>&1'; $filelog = array(); exec( $cmdline, $filelog ); $out = "<graphviz>\n"; $out .= ' digraph G { rankdir='.$rankdir.'; node [shape=box];'; $out .= "\n"; $todo = array(); $done = array(); $changes = array(); getP4Graph( $filelog, $out, $todo, $done, $changes, $const ); while ( count( $todo ) ) { $file = array_pop( $todo ); if ( !$file ) continue; $filelog = array(); $cmdline = buildP4Cmd( $server ); $cmdline .= ' filelog ' . $flag . wfEscapeShellArg( $file ); exec( $cmdline, $filelog ); getP4Graph( $filelog, $out, $todo, $done, $changes, $const ); } if ( $const == 'change' && count( $changes ) > 1 ) { array_unique( $changes ); sort( $changes, SORT_NUMERIC ); $fchange = array_shift( $changes ); $out .= ' "'.$fchange.'" [style=invis];'."\n"; $out .= ' "'.$fchange; $lchange = array_pop( $changes ); foreach( $changes as $change ) { $out .= '" -> "'.$change.'" [style=invis weight=100];'."\n"; $out .= ' "'.$change.'" [style=invis];'."\n"; $out .= ' "'.$change; } $out .= '" -> "'.$lchange.'" [style=invis weight=100];'."\n"; $out .= ' "'.$lchange.'" [style=invis];'."\n"; } if ( $const == 'file' && count( $done ) > 1 ) { $files = $done; array_unique( $files ); sort ( $files ); $ffile = array_shift( $files ); $out .= ' "'.$ffile.'" [style=invis];'."\n"; $out .= ' "'.$ffile; $lfile = array_pop( $files ); foreach( $files as $file ) { $out .= '" -> "'.$file.'" [style=invis weight=100];'."\n"; $out .= ' "'.$file.'" [style=invis];'."\n"; $out .= ' "'.$file; } $out .= '" -> "'.$lfile.'" [style=invis weight=100];'."\n"; $out .= ' "'.$lfile.'" [style=invis];'."\n"; } if ( $dot ) $out .= ' '.$dot."\n"; $out .= " }\n"; $out .= "</graphviz>"; if ( !count($done) ) return $out; #error #Trim paths if possible. $firstFile = array_shift( $done ); array_unshift( $done, $firstFile ); $firstCmn = $firstFile; $lastCmn = $firstFile; foreach( $done as $file ) { $firstCmn = strFirstCommon( $firstCmn, $file ); $lastCmn = strLastCommon( $lastCmn, $file ); } #If there's only one file, just use the filename. if ( $firstCmn == $firstFile ) { $firstCmn = substr($firstFile,0,-1*(strlen(strrchr($firstFile,'/'))-1)); $lastCmn = ''; } $firstLen = strlen( $firstCmn ); $lastLen = strlen( $lastCmn ); foreach( $done as $file ) { $newname = substr( $file, $firstLen ); if ( $lastLen ) $newname = substr( $newname, 0, -1 * $lastLen ); $out = str_replace( $file, $newname, $out ); } return $out; } # {{#p4info:}} function parseP4Info( &$parser ) { $out = ''; $cmdline = buildP4Cmd() . ' info'; $info = array(); exec( $cmdline, $info ); $out .= "{| id=p4info\n"; foreach( $info as $line ) { $regs = array(); if ( !preg_match( '/^(Server [^:]+:)(.*)/', $line, $regs ) ) continue; $out .= "|-\n"; $out .= "| '''$regs[1]'''\n"; $out .= "| \n"; $out .= "| $regs[2]\n"; } $out .= "|}"; return array($out, 'noparse' => false); } # {{#p4job:job|field|format}} function parseP4Job( &$parser, $job = '', $field = '', $format = '' ) { $parser->disableCache(); return array( getP4Job( $job, $field, $format ), 'noparse' => false ); } function getP4Job( $job = '', $field = '', $format = '' ) { if ( $job == '' ) { return '<b>Please specify a job.</b>'; } global $wgP4WEBURL; if ( $field == '' ) { return '['.$wgP4WEBURL.$job.'?ac=135 '.$job.']'; } $cmdline = buildP4Cmd(); $cmdline .= ' -Ztag -Zspecstring job -o ' . wfEscapeShellArg( $job ); $output = array(); exec( $cmdline, $output ); $fields = getSpecFields( $output ); $result = isset($fields[$job][strtolower($field)]) ? $fields[$job][strtolower($field)] : ''; if ( $format ) formatField( $format, $result ); return $result; } # {{#p4jobs:expr|fields|maxjobs|format| # tableattr|trattr|tdattr|query1|action1|...}} function parseP4Jobs( &$parser, $expr='', $fields='', $maxjobs='', $format='', $tableattr='', $trattr='', $tdattr='' ) { $parser->disableCache(); $extra_args = array(); $arg = 8; // number of standard args while( func_num_args() > $arg + 1 ) { $extra_args[] = func_get_arg( $arg ); $arg++; $extra_args[] = func_get_arg( $arg ); $arg++; } return array( getP4Jobs( $expr, $fields, $maxjobs, $format, $tableattr, $trattr, $tdattr, $extra_args ), 'noparse' => false ); } function getP4Jobs( $expr='', $fields='', $maxjobs='', $format='', $tableattr='', $trattr='', $tdattr='', $extra_args=array() ) { if ( !$maxjobs ) $maxjobs = '20'; $mjobarr = explode( ' ', $maxjobs ); $mjobs = intval($mjobarr[0]); if ( $mjobs > 1000 || $mjobs < 1 ) $mjobs = 100; if( isset( $mjobarr[1] ) ) { $altport = $mjobarr[1]; } else { $altport = ''; } $cmdline = buildP4Cmd( $altport ); $cmdline .= ' -Ztag -Zspecstring jobs -r -m'.$mjobs; $expr = htmlspecialchars_decode( $expr ); // angle brackets! if ( $expr ) $cmdline .= ' -e '.wfEscapeShellArg( $expr ); $output = array(); exec( $cmdline, $output ); $data = getSpecFields( $output ); $rules = array(); $arg = 0; while ( count( $extra_args ) > $arg + 1 ) { $rule = array(); $rule['query'] = $extra_args[ $arg ]; $arg++; $rule['action'] = $extra_args[ $arg ]; $arg++; $rule['jobs'] = array(); applyJobQuery( $rule, $data, $expr, $mjobs ); $rules[] = $rule; } if ( $fields ) { $fields = preg_split( '/\s+/', $fields ); } else { $fields = array_slice( $data['@'], 0, 5 ); } array_shift( $data ); # shift off the specdef data sort( $data ); $fields = array_reverse( $fields ); foreach ( $fields as &$fref ) { $o = +1; if ( substr( $fref, 0, 1 ) == '!' ) { $o = -1; $fref = substr( $fref, 1 ); } # Insert current ordering information into array elements. Ech. foreach( $data as $k => &$jref ) { $jref['@'] = $k; } usort( $data, create_function ( '$a,$b', 'return compKey($a,$b,"'.addslashes(strtolower($fref)).'",'.$o.');' ) ); } $fields = array_reverse( $fields ); $index = array(); foreach( $data as $k => &$row ) { $index[$row['job']] = $k; $row['@attrib'] = ''; $row['@format'] = ''; foreach( $rules as &$rule ) { if ( $rule['query'] == 'ALL' ) $rule['jobs'][] = $row['job']; if ( $rule['query'] == 'ODD' && $k % 2 == 1 ) $rule['jobs'][] = $row['job']; if ( $rule['query'] == 'EVEN' && $k % 2 == 0 ) $rule['jobs'][] = $row['job']; } } applyJobRules( $rules, $data, $index ); $result = '{|'.$tableattr."\n"; $result .= '|-'.$trattr."\n"; foreach( $fields as $f ) { $result .= '!'.$f."\n"; } $result .= "\n"; foreach( $data as $job ) { $result .= '|-'.$trattr.$job['@attrib']."\n"; foreach( $fields as $f ) { $value = ''; if( isset( $job[strtolower($f)] ) ) { $value = $job[strtolower($f)]; } $rawValue = $value; formatField( $format.$job['@format'], $value, $f ); if ( $rawValue == $value && $f == 'Job' && $altport == '' ) { $value = '{{#p4job:'.$value.'}}'; } $result .= '|'.$tdattr.'|'; $result .= $value; $result .= "\n"; } $result .= "\n"; } $result .= '|}'; return $result; } # {{#p4print:path|raw/text/wiki}} function parseP4Print( &$parser, $path = '', $mode = 'raw' ) { $parser->disableCache(); return array( getP4Print( $path, $mode ), 'noparse' => false ); } function getP4Print( $path = '', $mode = 'raw' ) { if ( $mode == 'raw' ) { return "<p4print path=\"$path\"/>"; } $cmdline = buildP4Cmd(); $cmdline .= ' print -q ' . wfEscapeShellArg( $path ); $cmdline .= ' 2>&1'; $text = ""; $output = array(); exec( $cmdline, $output ); foreach( $output as $line ) { if ( $mode == 'text' ) { $line = ' ' . $line; } $text = $text . $line . "\n" ; } return $text; } function tagP4Print( $input, $argv, $parser ) { $parser->disableCache(); $text = '<pre>'; $text .= htmlspecialchars( getP4Print( $argv['path'], 'wiki' ) ); $text .= '</pre>'; return $text; } # {{#p4variants:path}} function parseP4Variants( &$parser, $path = '' ) { return array('<p4variants path="'.trim($path).'"/>', 'noparse' => false); } function tagP4Variants( $input, $argv, $parser ) { global $wgUseAjax, $wgScriptPath; if ( !$wgUseAjax ) return "This MediaWiki instance does not support Ajax."; $parser->mOutput->mPerforceJavascriptTag = true; # flag for use by wfPerforceParserOutput $path = $argv["path"]; if( $path == '' ) return "Please provide a depot path (path attribute)."; $nocache = time(); $html = ''; $html .= '<div class="p4VariantSection">'; $html .= '<div class="p4VariantHead">'; $html .= renderPerforcePathLink( $path, TRUE ); $html .= ' <span class="p4VariantButton">['; $html .= '<a href="#" onclick="'; $html .= "this.href='javascript:void(0)'; perforceGetVariants( '$path', this, '$nocache' )"; $html .= '" title="Search for branches and find outstanding changes -- may take a while!">find variants</a>'; $html .= ']</span>'; $html .= '</div>'; $html .= '<div class="p4VariantBody" style="display:none"><ul><img class="p4VariantLoading" alt="loading..." src="'; $html .= "$wgScriptPath/extensions/Perforce/P4_Busy.gif"; $html .= '"/></ul>'; $html .= '</div>'; $html .= '</div>'; return $html; } function wfPerforceVariantsAjax( $path, $nocache ) { #nocache is a random number passed around to keep this from getting cached. $response = new AjaxResponse(); $response->addText( getP4Variants( $path ) ); return getP4Variants( $path ); return $response; } function tagP4Change( $input, $argv, $parser ) { global $wgP4EXEC; global $wgP4PORT; global $wgP4USER; global $wgP4PASSWD; global $wgP4WEBURL; if( isset($argv["style"]) ) { $range = '@' . $input . ',' . $input; $cmdline = wfEscapeShellArg( $wgP4EXEC ) . " -u " . wfEscapeShellArg( $wgP4USER ) . " -p " . wfEscapeShellArg( $wgP4PORT ); if( $wgP4PASSWD != "" ) $cmdline .= " -P " . wfEscapeShellArg( $wgP4PASSWD ); $cmdline .= " changes " . wfEscapeShellArg( $range ); $text = `{$cmdline}`; return formatShortChange( $text ); } else return "<a href=\"$wgP4WEBURL" . $input . "?ac=10\">" . htmlspecialchars( $input ) . "</a>"; } function renderRecentchanges( $input, $argv, &$parser, $format ) { $parser->disableCache(); $path = isset($argv["path"]) ? $argv["path"] : ""; $num = isset($argv["num"]) ? $argv["num"] : ""; $desc = isset($argv["desc"])? $argv["desc"] : ""; $user = isset($argv["user"])? $argv["user"] : ""; if( $path == "" || $num == "" ) return "Please provide both a depot path(path attribute) and the number of changes to show(num attribute)."; $cmdline = buildP4Cmd(); $cmdline .= " changes -s submitted -m" . escapeshellarg( $num ) . " "; if ( $desc == "long" ) $cmdline .= "-L "; if ( $desc == "full" ) $cmdline .= "-l "; if ( $user != "" ) $cmdline .= "-u " . escapeshellarg( $user ) . " "; if ( $path != '//...' ) $cmdline .= escapeshellarg( $path ) . " "; $cmdline .= '2>&1'; $changes = array(); exec( $cmdline, $changes ); $isDarkStripe = true; $output = "<ul class=\"zebra\">"; $inList = false; $inListList = false; foreach( $changes as $value ) { if ( !preg_match( '/^Change [0-9]+ on/', $value ) ) { if ( !$inListList ) { $output .= '<ul>'; $inListList = true; } $output .= htmlspecialchars( $value ); if ( $value ) $output .= '<br/>'; continue; } if ( $inListList ) { $output .= '</ul>'; $inListList = false; } if ( $inList ) { $output .= "</li>\n"; $inList = false; } if( $isDarkStripe ) $output .= "<li class=\"zebra\">"; else $output .= "<li>"; $inList = true; $output .= formatShortChange( $value, $format ); $isDarkStripe = !$isDarkStripe; } if ( $inListList ) { $output .= '</ul>'; $inListList = false; } if ( $inList ) { $output .= "</li>\n"; $inList = false; } $output .= "</ul>"; return $output; } function formatShortChange( $text, $format='html', $newwin=FALSE ) { global $wgP4WEBURL; $target = ''; if ( $newwin ) $target = 'target="_blank" '; $pattern[0] = '/Change (\d+) /'; $pattern[1] = '/ (\S+)@(\S+)/'; switch( $format ) { default: case 'html': $replacement[0] = 'Change <a '.$target.'href="' . $wgP4WEBURL . '${1}?ac=10\">${1}</a> '; $replacement[1] = ' <a '.$target.'href="' . $wgP4WEBURL . '${1}?ac=17">${1}</a>@<a '.$target.'href="' . $wgP4WEBURL . '${2}?ac=15">${2}</a> '; break; case 'wiki': $replacement[0] = 'Change [' . $wgP4WEBURL . '${1}?ac=10 ${1}] '; $replacement[1] = ' [' . $wgP4WEBURL . '${1}?ac=17 ${1}]@[' . $wgP4WEBURL . '${2}?ac=15 ${2}] '; break; } return preg_replace( $pattern, $replacement, $text ); } function renderPerforcePathLink( $path, $newwin=FALSE ) { global $wgP4WEBURL; $target = ''; $ac = 22; if ( ( strpos( $path, '...' ) !== FALSE ) || ( strpos( $path, '*' ) !== FALSE ) ) $ac = 69; if ( $newwin ) $target = 'target="_blank" '; return '<a '.$target.'href="'. $wgP4WEBURL . $path . '?ac='.$ac.'">'.htmlspecialchars( $path ).'</a>'; } function getP4Variants( $path ) { global $wgP4EXEC; global $wgP4PORT; global $wgP4USER; global $wgP4CLIENT; global $wgP4PASSWD; $path = trim( $path ); if ( substr( $path, -1 ) == '/' ) { $path .= '...'; } $html = '<ul>'; $p4Cmd = $wgP4EXEC . " -u " . $wgP4USER . " -p " . $wgP4PORT . " -c " . $wgP4CLIENT; if( $wgP4PASSWD != "" ) $p4Cmd .= " -P " . $wgP4PASSWD; #Infer branch relationships from integed output. $toFile = ''; $fromFile = ''; $pleft = $path; $pwild = ''; $pright = ''; if ( substr( $path, -1 ) == '*' ) { $pleft = substr( $path, 0, -1 ); $pwild = substr( $path, -1 ); } if ( substr( $path, -3 ) == '...' ) { $pleft = substr( $path, 0, -3 ); $pwild = substr( $path, -3 ); } $pllen = strlen( $pleft ); $prlen = strlen( $pright ); if ( ( strpos( $pleft, '...' ) !== FALSE ) || ( strpos( $pleft, '*' ) !== FALSE ) ) return '<b>Embedded wildcard in '.htmlspecialchars( $path ).' -- unable to figure out branch relationships.</b>'; $cmdline = $p4Cmd . ' -Ztag integrated ' . escapeshellarg( $path ); $descriptors = array ( 1 => array("pipe", "w") ); $branches = array(); $integedproc = proc_open( $cmdline, $descriptors, $pipes ); if ( !is_resource($integedproc) ) return 'Unable to get branch information.'; while ( !feof($pipes[1]) ) { $line = substr( fgets( $pipes[1] ), 0, -1 ); #chomp newline if ( substr( $line, 0, 11 ) == '... toFile ' ) $toFile = substr( $line, 11 ); if ( substr( $line, 0, 13 ) == '... fromFile ' ) $fromFile = substr( $line, 13 ); if ( $toFile && $fromFile ) { #toFile is our path, even if it's really the "from" of the integ. Handy! if ( substr( $toFile, 0, $pllen ) == $pleft ) #this should always be true { $pright = substr( $toFile, $pllen ); $prlen = strlen( $pright ); if ( !$prlen || substr( $fromFile, -$prlen ) == $pright ) #check for a path match { if ( $prlen ) $branch = substr( $fromFile, 0, -$prlen ) . $pwild; else $branch = $fromFile; $branches[$branch] = $branch; } } $toFile = ''; $fromFile = ''; } } sort ( $branches ); if ( !count( $branches ) ) { return '<ul><i>No branches found.</i></ul>'; } set_time_limit( 300 ); #We now have a list of $branches that each corresponds to $path. foreach( $branches as $branch ) { $cmdline = $p4Cmd . ' interchanges ' . escapeshellarg( $branch ) . ' ' . escapeshellarg( $path ); $changes = array(); exec( $cmdline, $changes ); $bvar = FALSE; foreach( $changes as $change ) { $cnum = ''; $cnums = array(); if ( preg_match( '/^Change (\d+) /', $change, $cnums ) ) $cnum = $cnums[1]; else continue; $cvar = FALSE; $cmdline = $p4Cmd . ' -s -Ztag integrate -n ' . escapeshellarg( $branch.'@'.$cnum.',@'.$cnum ) . ' ' . escapeshellarg( $path ); $integs = array(); exec( $cmdline, $integs ); foreach( $integs as $integ ) { if ( $integ == 'info1: action integrate' ) { $cvar = TRUE; break; } $errors = array(); if( preg_match( '/^error: (.*)/', $integ, $errors ) && !preg_match( '/already integrated\.$/', $integ ) ) { $html .= '<font color="red">'.$errors[1].'</font><br/>'; } } if ( $cvar ) { if ( !$bvar ) { $bvar = TRUE; $html .= '<li>'; $html .= renderPerforcePathLink( $branch, TRUE ); $html .= '<ul>'; } $html .= '<li>'; $html .= formatShortChange( $change, 'html', TRUE ); $html .= '</li>'; } } if ( $bvar ) { $html .= '</ul></li>'; } } if ( strpos( $html, '<li>' ) === FALSE ) { $html .= '<li><i>No branches have outstanding changes.</i></li>'; } $html .= '</ul>'; return $html; } # Takes array of filelog output and running count # of what files need doing and what files are done. # Returns GraphViz output. function getP4Graph( &$filelog, &$out, &$todo, &$done, &$changes, &$const ) { $file = ''; $rev = ''; $error = ''; $dirty = FALSE; foreach( $filelog as $line ) { $regs = array(); if ( preg_match( '/^... ... (.*) (\/\/[^#]*)#(.*)/', $line, $regs ) ) { if ( !in_array($regs[2],$todo) && !in_array($regs[2],$done) ) { array_push ( $todo, $regs[2] ); } if ( strpos( $regs[1], ' by' ) || strpos( $regs[1], ' into' ) ) continue; $lbl = $regs[1]; $clr = 'blue'; $sty = 'solid'; switch ( $lbl ) { case 'branch from': case 'copy from': case 'delete from': $lbl = substr( $lbl, 0, -5 ); break; case 'moved from': $lbl = substr( $lbl, 0, -5 ); $clr = 'red'; break; case 'merge from': $lbl = substr( $lbl, 0, -5 ); $sty = 'dashed'; break; case 'ignored': $lbl = substr( $lbl, 0, -1 ); $sty = 'dotted'; break; case 'edit from': $lbl = substr( $lbl, 0, -5 ); $sty = 'dashed'; $clr = 'red'; break; } if ( $dirty ) $clr = 'red'; $src = $regs[3]; if ( strpos( $src, '#' ) ) $src = substr( $src, strpos( $src, '#' ) + 1 ); if ( $file && $rev ) { $out .= ' "'.$regs[2].'#'.$src.'" -> "'.$file.'#'.$rev.'"'; $out .= ' [label="'.$lbl.'" color="'.$clr.'" fontcolor="'.$clr.'" style="'.$sty.'" weight=1];'; $out .= "\n"; } } else if ( preg_match( '/^... #([0-9]+) change ([0-9]+) ([a-z\/]+) /', $line, $regs ) ) { if ( $file && $rev ) { $out .= ' "'.$file.'#'.$regs[1].'" -> "'.$file.'#'.$rev.'" [weight=1000];'."\n"; } $rev = $regs[1]; $dirty = ( $regs[3] == 'edit' || $regs[3] == 'add' ); if ( $const == 'change' ) { array_push( $changes, $regs[2] ); $out .= ' { rank=same "'.$file.'#'.$regs[1].'" -> "'.$regs[2].'" [style=invis]; }'."\n"; } if ( $const == 'file' ) { $out .= ' { rank=same "'.$file.'#'.$regs[1].'" -> "'.$file.'" [style=invis]; }'."\n"; } } else if ( preg_match( '/^\/\//', $line, $regs ) ) { $file = $line; $rev = ''; array_push( $done, $file ); if ( in_array( $file, $todo ) ) { unset( $todo[array_search($file,$todo)] ); } } else $error .= ' ' .$line . "\n"; } if ( $error ) { $out = ' <font color="red">' . substr( $error, 1 ) . "</font>\n" . $out; } return $out; } function buildP4Cmd( $server='' ) { global $wgP4EXEC; global $wgP4PORT; global $wgP4USER; global $wgP4PASSWD; global $wgP4ALTPORTS; $cmdline = $wgP4EXEC . " -u " . $wgP4USER ; if( $wgP4PASSWD != "" ) $cmdline .= " -P " . $wgP4PASSWD ; if ( $server == '' || $server == $wgP4PORT ) { $cmdline .= " -p " . $wgP4PORT; } else { if ( is_array( $wgP4ALTPORTS) && in_array( $server, $wgP4ALTPORTS ) ) $cmdline .= " -p " . $server; else $cmdline .= " -p " . $wgP4PORT; } return $cmdline; } #returns the common leading substring of two strings, split on '/'. function strFirstCommon( $str1, $str2 ) { $path1 = explode( '/', $str1 ); $path2 = explode( '/', $str2 ); $cmn = array(); while ( count( $path1 ) > 1 && count( $path2 ) > 1 ) { $s1 = array_shift( $path1 ); $s2 = array_shift( $path2 ); if ( $s1 != $s2 ) break; array_push( $cmn, $s1 ); } if ( !count( $cmn ) ) return ''; return implode( '/', $cmn ) . '/'; } #returns the common trailing substring of two strings, split on '/'. function strLastCommon( $str1, $str2 ) { $path1 = explode( '/', $str1 ); $path2 = explode( '/', $str2 ); $cmn = array(); while ( count( $path1 ) > 1 && count( $path2 ) > 1 ) { $s1 = array_pop( $path1 ); $s2 = array_pop( $path2 ); if ( $s1 != $s2 ) break; array_unshift( $cmn, $s1 ); } if ( !count( $cmn ) ) return ''; return '/' . implode( '/', $cmn ); } #takes an array of "p4 -Ztag -Zspecstring specs..." output, #returns an array of arrays of spec fields. function getSpecFields( $output ) { $fields = array(); $fields['@'] = array(); $specstring = array_shift( $output ); $specstring = substr( $specstring, 12 ); # remove '... specdef ' $fields['@'] = preg_split( '/;;/', $specstring ); foreach( $fields['@'] as &$field ) { $var = preg_split( '/;/', $field ); $field = array_shift( $var ); } $id = $fields['@'][0]; # name of spec type, e.g. 'job' $i = -1; $spec = array(); foreach( $output as $line ) { $line .= "\n"; $m = array(); if ( preg_match( '/^\.\.\. ([^\s]+)/', $line, $m ) ) { if ( $m[1] == $id ) { # Start of a new spec. if ( array_key_exists( strtolower($id), $spec ) ) { foreach( $spec as &$f ) { $f = chop( $f ); } $fields[$spec[strtolower($id)]] = $spec; } $i = -1; $spec = array(); } if ( in_array( $m[1], $fields['@'] ) && array_search( $m[1], $fields['@'] ) > $i ) { # Start of a new field. $line = substr( $line, 5 + strlen($m[1]) ); #remove '... Field ' $i = array_search( $m[1], $fields['@'] ); $spec[strtolower($fields['@'][$i])] = ''; } } if ( $i < 0 ) { continue; } $spec[strtolower($fields['@'][$i])] .= $line; } if ( array_key_exists( strtolower($id), $spec ) ) { foreach( $spec as &$f ) { $f = chop( $f ); } $fields[$spec[strtolower($id)]] = $spec; } return $fields; } # Compare two arrays by value associated with a particular key. function compKey( $arr1, $arr2, $key, $order = 1 ) { # Sort arrays that don't have the key to the front for easy removal. if ( !array_key_exists( $key, $arr1 ) && !array_key_exists( $key, $arr2 ) ) return 0; if ( !array_key_exists( $key, $arr1 ) ) return -1; if ( !array_key_exists( $key, $arr2 ) ) return +1; if ( $arr1[$key] < $arr2[$key] ) return -1 * $order; if ( $arr1[$key] > $arr2[$key] ) return +1 * $order; # Existing order is kept in ['@']; preserve if there. if ( array_key_exists( '@', $arr1 ) && array_key_exists( '@', $arr2 ) ) { if ( $arr1['@'] < $arr2['@'] ) return -1; if ( $arr1['@'] > $arr2['@'] ) return +1; } return 0; } # Takes a formatting string and field to format (in-place). function formatField( $format, &$field, $fname = '' ) { $field = str_replace( '!', '!', str_replace( '|', '|', $field ) ); $fname = strtolower( $fname ); $fmt = preg_split( '/\s+/', $format ); foreach( $fmt as $f ) { $hpos = strrpos( $f, '#' ); if ( $hpos ) { $flimit = strtolower( substr( $f, $hpos + 1 ) ); if ( $flimit && $flimit != $fname ) continue; $f = substr( $f, 0, $hpos ); } if ( !strncasecmp( $f, 'template:', 9 ) ) { $f = substr( $f, 9 ); $field = '{{'.$f.'|'.$field.'}}'; continue; } if ( strpos( $f, 'chars' ) ) { $field = substr( $field, 0, intval( substr( $f, 0, -5 ) ) ); } if ( strpos( $f, 'words' ) ) { $words = explode( ' ', $field ); $words = array_slice( $words, 0, intval( substr( $f, 0, -5 ) ) ); $field = implode( ' ', $words ); } if ( strpos( $f, 'lines' ) ) { $lines = explode( "\n", $field ); $lines = array_slice( $lines, 0, intval( substr( $f, 0, -5 ) ) ); $field = implode( "\n", $lines ); } if ( strpos( $f, 'paras' ) ) { $paras = explode( "\n\n", $field ); $paras = array_slice( $paras, 0, intval( substr( $f, 0, -5 ) ) ); $field = implode( "\n\n", $paras ); } if ( strpos( $f, 'sents' ) ) { $sents = preg_split( '/(\s+[^\s]+\s+[^\s]+[\.!?]+\s+)/', $field, 0, PREG_SPLIT_DELIM_CAPTURE ); $sents = array_slice( $sents, 0, 2 * intval( substr( $f, 0, -5 ) ) ); $field = implode( '', $sents ); } if ( $f == 'line' ) { $field = trim( preg_replace( '/\s+/', ' ', $field ) ); } if ( $f == 'raw' && strpos( $field, "\n" ) ) { $field = '<pre><nowiki>'.$field.'</nowiki></pre>'; } if ( $f == 'text' && strpos( $field, "\n" ) ) { $lines = explode( "\n", $field ); $field = "\n ".implode( "\n ", $lines ); } } } # Annotate an rule array with list of matching jobs. function applyJobQuery( &$rule, $table, $basequery, $maxjobs ) { $query = $rule['query']; $query = trim( $query ); if ( $query == 'ALL' || $query == 'ODD' || $query == 'EVEN' ) return; $query = strtolower( $query ); $field = ''; $value = ''; if ( !strpos( $query, ' ' ) && !strpos( $query, '&' ) && !strpos( $query, '|' ) && !strpos( $query, '^' ) && !strpos( $query, '*' ) ) { // We can handle simple queries by ourselves. I hope. $pair = explode( '=', $query ); if ( count( $pair ) == 2 ) { $field = $pair[0]; $value = $pair[1]; } else if ( count( $pair ) == 1 ) { $value = $pair[0]; } } if ( $value ) { // Handle it. foreach ( $table as $row ) { $found = false; if ( $field ) { if ( array_key_exists( $field, $row ) && stripos( $row[$field], $value ) !== false ) $found = true; } else { foreach ( $row as $k => $fv ) { if ( stripos( $fv, $value ) !== false ) { $found = true; } } } if ( $found ) $rule['jobs'][] = $row['job']; } } else { // Couldn't handle it. Run a new job query. if ( $basequery ) $newquery = '('.$basequery.') ('.$query.')'; else $newquery = $query; $cmdline = buildP4Cmd(); $cmdline .= ' jobs -m '.$maxjobs.' -e '.escapeshellarg( $newquery ); $jobs = array(); exec( $cmdline, $jobs ); foreach( $jobs as $j ) { $jf = explode( ' ', $j ); if ( array_key_exists( $jf[0], $table ) ) $rule['jobs'][] = $jf[0]; } } } # Process per-row job "rules" and annotate job table with results. function applyJobRules( $rules, &$table, $index ) { foreach( $rules as $rule ) { $key = ''; if ( !strcasecmp( substr( $rule['action'], 0, 7 ), 'attrib:' ) ) { $key = '@attrib'; $action = substr( $rule['action'], 7 ); } if ( !strcasecmp( substr( $rule['action'], 0, 7 ), 'format:' ) ) { $key = '@format'; $action = substr( $rule['action'], 7 ); } if ( !$key ) continue; foreach( $rule['jobs'] as $j ) { $table[$index[$j]][$key] .= ' '.$action; } } } ?>