MediaWiki
REL1_24
|
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 }