<?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/"; \$wgSWARMURL = "http://swarm.perforce.com"; 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: 8384 $'; $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, $wgSWARMURL, $wgOut; $wgOut->setPagetitle( 'Perforce' ); $p4webLink = ""; $swarmLink = ""; if( $wgSWARMURL != "" ) $swarmLink = "* [$wgSWARMURL Browse this server via Swarm]"; if( $wgP4WEBURL != "" ) $p4webLink = "* [$wgP4WEBURL Browse this server via P4Web]"; $out = <<<EOO ==Server info== {{#p4info:}} ==Recent changes== {{#p4changes:7|//...|long}} ==Links== $swarmLink $p4webLink * [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; $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'; $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>'; } if ( $field == '' ) { return '['.buildURL( 'job', $job ).' '.$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; 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=\"" . buildURL( 'change', $input ) . "\">" . 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 ) { $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="' . buildURL( 'change', ${1} ) . '\">${1}</a> '; $replacement[1] = ' <a '.$target.'href="' . buildURL( 'user', ${1} ) . '">${1}</a>@<a '.$target.'href="' . buildURL( 'client', ${2} ) . '>${2}</a> '; break; case 'wiki': $replacement[0] = 'Change [' . buildURL( 'change', ${1} ) . ' ${1}] '; $replacement[1] = ' [' . buildURL( 'user', ${1} ) . ' ${1}]@[' . buildURL( 'client', ${2} ) . ' ${2}] '; break; } return preg_replace( $pattern, $replacement, $text ); } function renderPerforcePathLink( $path, $newwin=FALSE ) { $target = ''; $type = 'depot_dir'; if ( ( strpos( $path, '...' ) !== FALSE ) || ( strpos( $path, '*' ) !== FALSE ) ) $type = 'depot_file'; if ( $newwin ) $target = 'target="_blank" '; return '<a '.$target.'href="'. buildURL( $type, $path ) .'">'.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; } # build a URL for the Perforce web tool of your choosing # if you specify both Swarm and P4Web, it will choose Swarm function buildURL( $type, $value ) { global $wgP4WEBURL; global $wgSWARMURL; if( $wgSWARMURL != "" ) return buildSwarmURL( $type, $value ); elseif( $wgP4WEBURL != "" ) return buildP4WebURL( $type, $value ); else return "http://www.priceisrightfail.com"; } function buildP4WebURL( $type, $value ) { global $wgP4WEBURL; $target = ""; switch( $type ) { case 'change': $target = "/$value?ac=10"; break; case 'job': $target = "/$value?ac=111"; break; case 'depot_file': $target = "$value?ac=22"; break; case 'depot_dir': $target = "$value?ac=22"; break; case 'user': $target = "/$value?ac=17"; break; case 'client': $target = "/$value?ac=15"; break; } return $wgP4WEBURL . $target; } function buildSwarmURL( $type, $value ) { global $wgSWARMURL; $target = ""; switch( $type ) { case 'change': $target = "/changes/$value"; break; case 'job': $target = "/jobs/$value"; break; case 'depot_file': $target = "/files$value"; break; case 'depot_dir': $target = "files" . preg_replace('/[\.*]+\Z/', '', $value); break; case 'user': $target = "/users/$value"; break; case 'client': # waiting on support for this from the Swarm team break; } return $wgSWARMURL . $target; } 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; } } } ?>
# | Change | User | Description | Committed | |
---|---|---|---|---|---|
#5 | 8384 | Matt Attaway | Fix missing $ in my variable name. | ||
#4 | 8319 | Matt Attaway |
Add support for Swarm to the Perforce Mediawiki plugin This code still needs to be properly tested, but it theoretically allows you to swap Swarm for P4Web in your site configuration. |
||
#3 | 8318 | Matt Attaway | Get the latest changes from @sam_stafford's version of the Mediawiki plugin | ||
#2 | 6272 | Matt Attaway | Get the latest version of the Perforce mediawiki plugin | ||
#1 | 5922 | Matt Attaway |
Add rudimentary mediwiki extensions. Embedder adds the ability to embed random webpages into a wiki. It's still very rough at this point, but it does work. I can't recommend it on a public wiki, but it is very handy if you can trust the users. Perforce adds the ability to convert a change number into the 'p4 changes' output, but with links to a p4web instance. This is also very rough at this point, but does work. More coming soon to both of these. |