MediaWiki  REL1_24
GitInfo.php
Go to the documentation of this file.
00001 <?php
00026 class GitInfo {
00027 
00031     protected static $repo = null;
00032 
00036     protected $basedir;
00037 
00041     protected $cacheFile;
00042 
00046     protected $cache = array();
00047 
00051     private static $viewers = false;
00052 
00058     public function __construct( $repoDir, $usePrecomputed = true ) {
00059         $this->cacheFile = self::getCacheFilePath( $repoDir );
00060         wfDebugLog( 'gitinfo',
00061             "Computed cacheFile={$this->cacheFile} for {$repoDir}"
00062         );
00063         if ( $usePrecomputed &&
00064             $this->cacheFile !== null &&
00065             is_readable( $this->cacheFile )
00066         ) {
00067             $this->cache = FormatJson::decode(
00068                 file_get_contents( $this->cacheFile ),
00069                 true
00070             );
00071             wfDebugLog( 'gitinfo', "Loaded git data from cache for {$repoDir}" );
00072         }
00073 
00074         if ( !$this->cacheIsComplete() ) {
00075             wfDebugLog( 'gitinfo', "Cache incomplete for {$repoDir}" );
00076             $this->basedir = $repoDir . DIRECTORY_SEPARATOR . '.git';
00077             if ( is_readable( $this->basedir ) && !is_dir( $this->basedir ) ) {
00078                 $GITfile = file_get_contents( $this->basedir );
00079                 if ( strlen( $GITfile ) > 8 &&
00080                     substr( $GITfile, 0, 8 ) === 'gitdir: '
00081                 ) {
00082                     $path = rtrim( substr( $GITfile, 8 ), "\r\n" );
00083                     if ( $path[0] === '/' || substr( $path, 1, 1 ) === ':' ) {
00084                         // Path from GITfile is absolute
00085                         $this->basedir = $path;
00086                     } else {
00087                         $this->basedir = $repoDir . DIRECTORY_SEPARATOR . $path;
00088                     }
00089                 }
00090             }
00091         }
00092     }
00093 
00102     protected static function getCacheFilePath( $repoDir ) {
00103         global $IP, $wgGitInfoCacheDirectory;
00104 
00105         if ( $wgGitInfoCacheDirectory ) {
00106             // Convert both $IP and $repoDir to canonical paths to protect against
00107             // $IP having changed between the settings files and runtime.
00108             $realIP = realpath( $IP );
00109             $repoName = realpath( $repoDir );
00110             if ( $repoName === false ) {
00111                 // Unit tests use fake path names
00112                 $repoName = $repoDir;
00113             }
00114             if ( strpos( $repoName, $realIP ) === 0 ) {
00115                 // Strip $IP from path
00116                 $repoName = substr( $repoName, strlen( $realIP ) );
00117             }
00118             // Transform path to git repo to something we can safely embed in
00119             // a filename
00120             $repoName = strtr( $repoName, DIRECTORY_SEPARATOR, '-' );
00121             $fileName = 'info' . $repoName . '.json';
00122             return "{$wgGitInfoCacheDirectory}/{$fileName}";
00123         }
00124         return null;
00125     }
00126 
00132     public static function repo() {
00133         if ( is_null( self::$repo ) ) {
00134             global $IP;
00135             self::$repo = new self( $IP );
00136         }
00137         return self::$repo;
00138     }
00139 
00146     public static function isSHA1( $str ) {
00147         return !!preg_match( '/^[0-9A-F]{40}$/i', $str );
00148     }
00149 
00155     public function getHead() {
00156         if ( !isset( $this->cache['head'] ) ) {
00157             $headFile = "{$this->basedir}/HEAD";
00158             $head = false;
00159 
00160             if ( is_readable( $headFile ) ) {
00161                 $head = file_get_contents( $headFile );
00162 
00163                 if ( preg_match( "/ref: (.*)/", $head, $m ) ) {
00164                     $head = rtrim( $m[1] );
00165                 } else {
00166                     $head = rtrim( $head );
00167                 }
00168             }
00169             $this->cache['head'] = $head;
00170         }
00171         return $this->cache['head'];
00172     }
00173 
00179     public function getHeadSHA1() {
00180         if ( !isset( $this->cache['headSHA1'] ) ) {
00181             $head = $this->getHead();
00182             $sha1 = false;
00183 
00184             // If detached HEAD may be a SHA1
00185             if ( self::isSHA1( $head ) ) {
00186                 $sha1 = $head;
00187             } else {
00188                 // If not a SHA1 it may be a ref:
00189                 $refFile = "{$this->basedir}/{$head}";
00190                 if ( is_readable( $refFile ) ) {
00191                     $sha1 = rtrim( file_get_contents( $refFile ) );
00192                 }
00193             }
00194             $this->cache['headSHA1'] = $sha1;
00195         }
00196         return $this->cache['headSHA1'];
00197     }
00198 
00205     public function getHeadCommitDate() {
00206         global $wgGitBin;
00207 
00208         if ( !isset( $this->cache['headCommitDate'] ) ) {
00209             $date = false;
00210             if ( is_file( $wgGitBin ) &&
00211                 is_executable( $wgGitBin ) &&
00212                 $this->getHead() !== false
00213             ) {
00214                 $environment = array( "GIT_DIR" => $this->basedir );
00215                 $cmd = wfEscapeShellArg( $wgGitBin ) .
00216                     " show -s --format=format:%ct HEAD";
00217                 $retc = false;
00218                 $commitDate = wfShellExec( $cmd, $retc, $environment );
00219                 if ( $retc === 0 ) {
00220                     $date = (int)$commitDate;
00221                 }
00222             }
00223             $this->cache['headCommitDate'] = $date;
00224         }
00225         return $this->cache['headCommitDate'];
00226     }
00227 
00233     public function getCurrentBranch() {
00234         if ( !isset( $this->cache['branch'] ) ) {
00235             $branch = $this->getHead();
00236             if ( $branch &&
00237                 preg_match( "#^refs/heads/(.*)$#", $branch, $m )
00238             ) {
00239                 $branch = $m[1];
00240             }
00241             $this->cache['branch'] = $branch;
00242         }
00243         return $this->cache['branch'];
00244     }
00245 
00251     public function getHeadViewUrl() {
00252         $url = $this->getRemoteUrl();
00253         if ( $url === false ) {
00254             return false;
00255         }
00256         if ( substr( $url, -4 ) !== '.git' ) {
00257             $url .= '.git';
00258         }
00259         foreach ( self::getViewers() as $repo => $viewer ) {
00260             $pattern = '#^' . $repo . '$#';
00261             if ( preg_match( $pattern, $url, $matches ) ) {
00262                 $viewerUrl = preg_replace( $pattern, $viewer, $url );
00263                 $headSHA1 = $this->getHeadSHA1();
00264                 $replacements = array(
00265                     '%h' => substr( $headSHA1, 0, 7 ),
00266                     '%H' => $headSHA1,
00267                     '%r' => urlencode( $matches[1] ),
00268                 );
00269                 return strtr( $viewerUrl, $replacements );
00270             }
00271         }
00272         return false;
00273     }
00274 
00279     protected function getRemoteUrl() {
00280         if ( !isset( $this->cache['remoteURL'] ) ) {
00281             $config = "{$this->basedir}/config";
00282             $url = false;
00283             if ( is_readable( $config ) ) {
00284                 wfSuppressWarnings();
00285                 $configArray = parse_ini_file( $config, true );
00286                 wfRestoreWarnings();
00287                 $remote = false;
00288 
00289                 // Use the "origin" remote repo if available or any other repo if not.
00290                 if ( isset( $configArray['remote origin'] ) ) {
00291                     $remote = $configArray['remote origin'];
00292                 } elseif ( is_array( $configArray ) ) {
00293                     foreach ( $configArray as $sectionName => $sectionConf ) {
00294                         if ( substr( $sectionName, 0, 6 ) == 'remote' ) {
00295                             $remote = $sectionConf;
00296                         }
00297                     }
00298                 }
00299 
00300                 if ( $remote !== false && isset( $remote['url'] ) ) {
00301                     $url = $remote['url'];
00302                 }
00303             }
00304             $this->cache['remoteURL'] = $url;
00305         }
00306         return $this->cache['remoteURL'];
00307     }
00308 
00318     public function cacheIsComplete() {
00319         return isset( $this->cache['head'] ) &&
00320             isset( $this->cache['headSHA1'] ) &&
00321             isset( $this->cache['headCommitDate'] ) &&
00322             isset( $this->cache['branch'] ) &&
00323             isset( $this->cache['remoteURL'] );
00324     }
00325 
00335     public function precomputeValues() {
00336         if ( $this->cacheFile !== null ) {
00337             // Try to completely populate the cache
00338             $this->getHead();
00339             $this->getHeadSHA1();
00340             $this->getHeadCommitDate();
00341             $this->getCurrentBranch();
00342             $this->getRemoteUrl();
00343 
00344             if ( !$this->cacheIsComplete() ) {
00345                 wfDebugLog( 'gitinfo',
00346                     "Failed to compute GitInfo for \"{$this->basedir}\""
00347                 );
00348                 return;
00349             }
00350 
00351             $cacheDir = dirname( $this->cacheFile );
00352             if ( !file_exists( $cacheDir ) &&
00353                 !wfMkdirParents( $cacheDir, null, __METHOD__ )
00354             ) {
00355                 throw new MWException( "Unable to create GitInfo cache \"{$cacheDir}\"" );
00356             }
00357 
00358             file_put_contents( $this->cacheFile, FormatJson::encode( $this->cache ) );
00359         }
00360     }
00361 
00366     public static function headSHA1() {
00367         return self::repo()->getHeadSHA1();
00368     }
00369 
00374     public static function currentBranch() {
00375         return self::repo()->getCurrentBranch();
00376     }
00377 
00382     public static function headViewUrl() {
00383         return self::repo()->getHeadViewUrl();
00384     }
00385 
00390     protected static function getViewers() {
00391         global $wgGitRepositoryViewers;
00392 
00393         if ( self::$viewers === false ) {
00394             self::$viewers = $wgGitRepositoryViewers;
00395             wfRunHooks( 'GitViewers', array( &self::$viewers ) );
00396         }
00397 
00398         return self::$viewers;
00399     }
00400 }