[ Index ] |
PHP Cross Reference of MediaWiki-1.24.0 |
[Summary view] [Print] [Text view]
1 <?php 2 /** 3 * A class to help return information about a git repo MediaWiki may be inside 4 * This is used by Special:Version and is also useful for the LocalSettings.php 5 * of anyone working on large branches in git to setup config that show up only 6 * when specific branches are currently checked out. 7 * 8 * This program is free software; you can redistribute it and/or modify 9 * it under the terms of the GNU General Public License as published by 10 * the Free Software Foundation; either version 2 of the License, or 11 * (at your option) any later version. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of 15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 16 * GNU General Public License for more details. 17 * 18 * You should have received a copy of the GNU General Public License along 19 * with this program; if not, write to the Free Software Foundation, Inc., 20 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. 21 * http://www.gnu.org/copyleft/gpl.html 22 * 23 * @file 24 */ 25 26 class GitInfo { 27 28 /** 29 * Singleton for the repo at $IP 30 */ 31 protected static $repo = null; 32 33 /** 34 * Location of the .git directory 35 */ 36 protected $basedir; 37 38 /** 39 * Path to JSON cache file for pre-computed git information. 40 */ 41 protected $cacheFile; 42 43 /** 44 * Cached git information. 45 */ 46 protected $cache = array(); 47 48 /** 49 * Map of repo URLs to viewer URLs. Access via static method getViewers(). 50 */ 51 private static $viewers = false; 52 53 /** 54 * @param string $repoDir The root directory of the repo where .git can be found 55 * @param bool $usePrecomputed Use precomputed information if available 56 * @see precomputeValues 57 */ 58 public function __construct( $repoDir, $usePrecomputed = true ) { 59 $this->cacheFile = self::getCacheFilePath( $repoDir ); 60 wfDebugLog( 'gitinfo', 61 "Computed cacheFile={$this->cacheFile} for {$repoDir}" 62 ); 63 if ( $usePrecomputed && 64 $this->cacheFile !== null && 65 is_readable( $this->cacheFile ) 66 ) { 67 $this->cache = FormatJson::decode( 68 file_get_contents( $this->cacheFile ), 69 true 70 ); 71 wfDebugLog( 'gitinfo', "Loaded git data from cache for {$repoDir}" ); 72 } 73 74 if ( !$this->cacheIsComplete() ) { 75 wfDebugLog( 'gitinfo', "Cache incomplete for {$repoDir}" ); 76 $this->basedir = $repoDir . DIRECTORY_SEPARATOR . '.git'; 77 if ( is_readable( $this->basedir ) && !is_dir( $this->basedir ) ) { 78 $GITfile = file_get_contents( $this->basedir ); 79 if ( strlen( $GITfile ) > 8 && 80 substr( $GITfile, 0, 8 ) === 'gitdir: ' 81 ) { 82 $path = rtrim( substr( $GITfile, 8 ), "\r\n" ); 83 if ( $path[0] === '/' || substr( $path, 1, 1 ) === ':' ) { 84 // Path from GITfile is absolute 85 $this->basedir = $path; 86 } else { 87 $this->basedir = $repoDir . DIRECTORY_SEPARATOR . $path; 88 } 89 } 90 } 91 } 92 } 93 94 /** 95 * Compute the path to the cache file for a given directory. 96 * 97 * @param string $repoDir The root directory of the repo where .git can be found 98 * @return string Path to GitInfo cache file in $wgGitInfoCacheDirectory or 99 * null if $wgGitInfoCacheDirectory is false (cache disabled). 100 * @since 1.24 101 */ 102 protected static function getCacheFilePath( $repoDir ) { 103 global $IP, $wgGitInfoCacheDirectory; 104 105 if ( $wgGitInfoCacheDirectory ) { 106 // Convert both $IP and $repoDir to canonical paths to protect against 107 // $IP having changed between the settings files and runtime. 108 $realIP = realpath( $IP ); 109 $repoName = realpath( $repoDir ); 110 if ( $repoName === false ) { 111 // Unit tests use fake path names 112 $repoName = $repoDir; 113 } 114 if ( strpos( $repoName, $realIP ) === 0 ) { 115 // Strip $IP from path 116 $repoName = substr( $repoName, strlen( $realIP ) ); 117 } 118 // Transform path to git repo to something we can safely embed in 119 // a filename 120 $repoName = strtr( $repoName, DIRECTORY_SEPARATOR, '-' ); 121 $fileName = 'info' . $repoName . '.json'; 122 return "{$wgGitInfoCacheDirectory}/{$fileName}"; 123 } 124 return null; 125 } 126 127 /** 128 * Get the singleton for the repo at $IP 129 * 130 * @return GitInfo 131 */ 132 public static function repo() { 133 if ( is_null( self::$repo ) ) { 134 global $IP; 135 self::$repo = new self( $IP ); 136 } 137 return self::$repo; 138 } 139 140 /** 141 * Check if a string looks like a hex encoded SHA1 hash 142 * 143 * @param string $str The string to check 144 * @return bool Whether or not the string looks like a SHA1 145 */ 146 public static function isSHA1( $str ) { 147 return !!preg_match( '/^[0-9A-F]{40}$/i', $str ); 148 } 149 150 /** 151 * Get the HEAD of the repo (without any opening "ref: ") 152 * 153 * @return string|bool The HEAD (git reference or SHA1) or false 154 */ 155 public function getHead() { 156 if ( !isset( $this->cache['head'] ) ) { 157 $headFile = "{$this->basedir}/HEAD"; 158 $head = false; 159 160 if ( is_readable( $headFile ) ) { 161 $head = file_get_contents( $headFile ); 162 163 if ( preg_match( "/ref: (.*)/", $head, $m ) ) { 164 $head = rtrim( $m[1] ); 165 } else { 166 $head = rtrim( $head ); 167 } 168 } 169 $this->cache['head'] = $head; 170 } 171 return $this->cache['head']; 172 } 173 174 /** 175 * Get the SHA1 for the current HEAD of the repo 176 * 177 * @return string|bool A SHA1 or false 178 */ 179 public function getHeadSHA1() { 180 if ( !isset( $this->cache['headSHA1'] ) ) { 181 $head = $this->getHead(); 182 $sha1 = false; 183 184 // If detached HEAD may be a SHA1 185 if ( self::isSHA1( $head ) ) { 186 $sha1 = $head; 187 } else { 188 // If not a SHA1 it may be a ref: 189 $refFile = "{$this->basedir}/{$head}"; 190 if ( is_readable( $refFile ) ) { 191 $sha1 = rtrim( file_get_contents( $refFile ) ); 192 } 193 } 194 $this->cache['headSHA1'] = $sha1; 195 } 196 return $this->cache['headSHA1']; 197 } 198 199 /** 200 * Get the commit date of HEAD entry of the git code repository 201 * 202 * @since 1.22 203 * @return int|bool Commit date (UNIX timestamp) or false 204 */ 205 public function getHeadCommitDate() { 206 global $wgGitBin; 207 208 if ( !isset( $this->cache['headCommitDate'] ) ) { 209 $date = false; 210 if ( is_file( $wgGitBin ) && 211 is_executable( $wgGitBin ) && 212 $this->getHead() !== false 213 ) { 214 $environment = array( "GIT_DIR" => $this->basedir ); 215 $cmd = wfEscapeShellArg( $wgGitBin ) . 216 " show -s --format=format:%ct HEAD"; 217 $retc = false; 218 $commitDate = wfShellExec( $cmd, $retc, $environment ); 219 if ( $retc === 0 ) { 220 $date = (int)$commitDate; 221 } 222 } 223 $this->cache['headCommitDate'] = $date; 224 } 225 return $this->cache['headCommitDate']; 226 } 227 228 /** 229 * Get the name of the current branch, or HEAD if not found 230 * 231 * @return string|bool The branch name, HEAD, or false 232 */ 233 public function getCurrentBranch() { 234 if ( !isset( $this->cache['branch'] ) ) { 235 $branch = $this->getHead(); 236 if ( $branch && 237 preg_match( "#^refs/heads/(.*)$#", $branch, $m ) 238 ) { 239 $branch = $m[1]; 240 } 241 $this->cache['branch'] = $branch; 242 } 243 return $this->cache['branch']; 244 } 245 246 /** 247 * Get an URL to a web viewer link to the HEAD revision. 248 * 249 * @return string|bool String if a URL is available or false otherwise 250 */ 251 public function getHeadViewUrl() { 252 $url = $this->getRemoteUrl(); 253 if ( $url === false ) { 254 return false; 255 } 256 if ( substr( $url, -4 ) !== '.git' ) { 257 $url .= '.git'; 258 } 259 foreach ( self::getViewers() as $repo => $viewer ) { 260 $pattern = '#^' . $repo . '$#'; 261 if ( preg_match( $pattern, $url, $matches ) ) { 262 $viewerUrl = preg_replace( $pattern, $viewer, $url ); 263 $headSHA1 = $this->getHeadSHA1(); 264 $replacements = array( 265 '%h' => substr( $headSHA1, 0, 7 ), 266 '%H' => $headSHA1, 267 '%r' => urlencode( $matches[1] ), 268 ); 269 return strtr( $viewerUrl, $replacements ); 270 } 271 } 272 return false; 273 } 274 275 /** 276 * Get the URL of the remote origin. 277 * @return string|bool String if a URL is available or false otherwise. 278 */ 279 protected function getRemoteUrl() { 280 if ( !isset( $this->cache['remoteURL'] ) ) { 281 $config = "{$this->basedir}/config"; 282 $url = false; 283 if ( is_readable( $config ) ) { 284 wfSuppressWarnings(); 285 $configArray = parse_ini_file( $config, true ); 286 wfRestoreWarnings(); 287 $remote = false; 288 289 // Use the "origin" remote repo if available or any other repo if not. 290 if ( isset( $configArray['remote origin'] ) ) { 291 $remote = $configArray['remote origin']; 292 } elseif ( is_array( $configArray ) ) { 293 foreach ( $configArray as $sectionName => $sectionConf ) { 294 if ( substr( $sectionName, 0, 6 ) == 'remote' ) { 295 $remote = $sectionConf; 296 } 297 } 298 } 299 300 if ( $remote !== false && isset( $remote['url'] ) ) { 301 $url = $remote['url']; 302 } 303 } 304 $this->cache['remoteURL'] = $url; 305 } 306 return $this->cache['remoteURL']; 307 } 308 309 /** 310 * Check to see if the current cache is fully populated. 311 * 312 * Note: This method is public only to make unit testing easier. There's 313 * really no strong reason that anything other than a test should want to 314 * call this method. 315 * 316 * @return bool True if all expected cache keys exist, false otherwise 317 */ 318 public function cacheIsComplete() { 319 return isset( $this->cache['head'] ) && 320 isset( $this->cache['headSHA1'] ) && 321 isset( $this->cache['headCommitDate'] ) && 322 isset( $this->cache['branch'] ) && 323 isset( $this->cache['remoteURL'] ); 324 } 325 326 /** 327 * Precompute and cache git information. 328 * 329 * Creates a JSON file in the cache directory associated with this 330 * GitInfo instance. This cache file will be used by subsequent GitInfo objects referencing 331 * the same directory to avoid needing to examine the .git directory again. 332 * 333 * @since 1.24 334 */ 335 public function precomputeValues() { 336 if ( $this->cacheFile !== null ) { 337 // Try to completely populate the cache 338 $this->getHead(); 339 $this->getHeadSHA1(); 340 $this->getHeadCommitDate(); 341 $this->getCurrentBranch(); 342 $this->getRemoteUrl(); 343 344 if ( !$this->cacheIsComplete() ) { 345 wfDebugLog( 'gitinfo', 346 "Failed to compute GitInfo for \"{$this->basedir}\"" 347 ); 348 return; 349 } 350 351 $cacheDir = dirname( $this->cacheFile ); 352 if ( !file_exists( $cacheDir ) && 353 !wfMkdirParents( $cacheDir, null, __METHOD__ ) 354 ) { 355 throw new MWException( "Unable to create GitInfo cache \"{$cacheDir}\"" ); 356 } 357 358 file_put_contents( $this->cacheFile, FormatJson::encode( $this->cache ) ); 359 } 360 } 361 362 /** 363 * @see self::getHeadSHA1 364 * @return string 365 */ 366 public static function headSHA1() { 367 return self::repo()->getHeadSHA1(); 368 } 369 370 /** 371 * @see self::getCurrentBranch 372 * @return string 373 */ 374 public static function currentBranch() { 375 return self::repo()->getCurrentBranch(); 376 } 377 378 /** 379 * @see self::getHeadViewUrl() 380 * @return bool|string 381 */ 382 public static function headViewUrl() { 383 return self::repo()->getHeadViewUrl(); 384 } 385 386 /** 387 * Gets the list of repository viewers 388 * @return array 389 */ 390 protected static function getViewers() { 391 global $wgGitRepositoryViewers; 392 393 if ( self::$viewers === false ) { 394 self::$viewers = $wgGitRepositoryViewers; 395 wfRunHooks( 'GitViewers', array( &self::$viewers ) ); 396 } 397 398 return self::$viewers; 399 } 400 }
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
Generated: Fri Nov 28 14:03:12 2014 | Cross-referenced by PHPXref 0.7.1 |