32use Psr\Log\LoggerInterface;
33use Wikimedia\AtEase\AtEase;
70 private static $viewers =
false;
73 private const CONSTRUCTOR_OPTIONS = [
74 MainConfigNames::BaseDirectory,
75 MainConfigNames::CacheDirectory,
76 MainConfigNames::GitBin,
77 MainConfigNames::GitInfoCacheDirectory,
78 MainConfigNames::GitRepositoryViewers,
99 self::CONSTRUCTOR_OPTIONS,
100 MediaWikiServices::getInstance()->getMainConfig()
102 $this->options->assertRequiredOptions( self::CONSTRUCTOR_OPTIONS );
104 $this->cacheFile = $this->getCacheFilePath(
$repoDir );
105 $this->logger = LoggerFactory::getInstance(
'gitinfo' );
106 $this->logger->debug(
107 "Candidate cacheFile={$this->cacheFile} for {$repoDir}"
109 $this->hookRunner = Hooks::runner();
110 if ( $usePrecomputed &&
111 $this->cacheFile !==
null &&
112 is_readable( $this->cacheFile )
114 $this->cache = FormatJson::decode(
115 file_get_contents( $this->cacheFile ),
118 $this->logger->debug(
"Loaded git data from cache for {$repoDir}" );
122 $this->logger->debug(
"Cache incomplete for {$repoDir}" );
123 $this->basedir =
$repoDir . DIRECTORY_SEPARATOR .
'.git';
124 if ( is_readable( $this->basedir ) && !is_dir( $this->basedir ) ) {
125 $GITfile = file_get_contents( $this->basedir );
126 if ( strlen( $GITfile ) > 8 &&
127 substr( $GITfile, 0, 8 ) ===
'gitdir: '
129 $path = rtrim( substr( $GITfile, 8 ),
"\r\n" );
130 if (
$path[0] ===
'/' || substr(
$path, 1, 1 ) ===
':' ) {
132 $this->basedir =
$path;
149 private function getCacheFilePath(
$repoDir ) {
150 $gitInfoCacheDirectory = $this->options->get( MainConfigNames::GitInfoCacheDirectory );
151 if ( $gitInfoCacheDirectory ===
false ) {
152 $gitInfoCacheDirectory = $this->options->get( MainConfigNames::CacheDirectory ) .
'/gitinfo';
154 $baseDir = $this->options->get( MainConfigNames::BaseDirectory );
155 if ( $gitInfoCacheDirectory ) {
158 $realIP = realpath( $baseDir );
160 if ( $repoName ===
false ) {
164 if ( strpos( $repoName, $realIP ) === 0 ) {
166 $repoName = substr( $repoName, strlen( $realIP ) );
170 $repoName = strtr( $repoName, DIRECTORY_SEPARATOR,
'-' );
171 $fileName =
'info' . $repoName .
'.json';
172 $cachePath =
"{$gitInfoCacheDirectory}/{$fileName}";
173 if ( is_readable( $cachePath ) ) {
178 return "$repoDir/gitinfo.json";
186 public static function repo() {
187 if ( self::$repo ===
null ) {
188 self::$repo =
new self( MW_INSTALL_PATH );
200 return (
bool)preg_match(
'/^[0-9A-F]{40}$/i', $str );
209 if ( !isset( $this->cache[
'head'] ) ) {
210 $headFile =
"{$this->basedir}/HEAD";
213 if ( is_readable( $headFile ) ) {
214 $head = file_get_contents( $headFile );
216 if ( preg_match(
"/ref: (.*)/", $head, $m ) ) {
217 $head = rtrim( $m[1] );
219 $head = rtrim( $head );
222 $this->cache[
'head'] = $head;
224 return $this->cache[
'head'];
233 if ( !isset( $this->cache[
'headSHA1'] ) ) {
238 if ( self::isSHA1( $head ) ) {
242 $refFile =
"{$this->basedir}/{$head}";
243 $packedRefs =
"{$this->basedir}/packed-refs";
244 $headRegex = preg_quote( $head,
'/' );
245 if ( is_readable( $refFile ) ) {
246 $sha1 = rtrim( file_get_contents( $refFile ) );
247 } elseif ( is_readable( $packedRefs ) &&
248 preg_match(
"/^([0-9A-Fa-f]{40}) $headRegex$/m", file_get_contents( $packedRefs ),
$matches )
253 $this->cache[
'headSHA1'] = $sha1;
255 return $this->cache[
'headSHA1'];
265 $gitBin = $this->options->get( MainConfigNames::GitBin );
267 if ( !isset( $this->cache[
'headCommitDate'] ) ) {
271 $isFile = AtEase::quietCall(
'is_file', $gitBin );
273 is_executable( $gitBin ) &&
274 !Shell::isDisabled() &&
281 '--format=format:%ct',
284 $gitDir = realpath( $this->basedir );
285 $result = Shell::command( $cmd )
286 ->environment( [
'GIT_DIR' => $gitDir ] )
287 ->restrict( Shell::RESTRICT_DEFAULT | Shell::NO_NETWORK )
288 ->whitelistPaths( [ $gitDir, $this->repoDir ] )
291 if ( $result->getExitCode() === 0 ) {
292 $date = (int)$result->getStdout();
295 $this->cache[
'headCommitDate'] = $date;
297 return $this->cache[
'headCommitDate'];
306 if ( !isset( $this->cache[
'branch'] ) ) {
309 preg_match(
"#^refs/heads/(.*)$#", $branch, $m )
313 $this->cache[
'branch'] = $branch;
315 return $this->cache[
'branch'];
325 if ( $url ===
false ) {
328 foreach ( $this->getViewers() as
$repo => $viewer ) {
329 $pattern =
'#^' .
$repo .
'$#';
330 if ( preg_match( $pattern, $url,
$matches ) ) {
331 $viewerUrl = preg_replace( $pattern, $viewer, $url );
334 '%h' => substr( $headSHA1, 0, 7 ),
339 return strtr( $viewerUrl, $replacements );
350 if ( !isset( $this->cache[
'remoteURL'] ) ) {
351 $config =
"{$this->basedir}/config";
353 if ( is_readable( $config ) ) {
354 AtEase::suppressWarnings();
355 $configArray = parse_ini_file( $config,
true );
356 AtEase::restoreWarnings();
360 if ( isset( $configArray[
'remote origin'] ) ) {
361 $remote = $configArray[
'remote origin'];
362 } elseif ( is_array( $configArray ) ) {
363 foreach ( $configArray as $sectionName => $sectionConf ) {
364 if ( substr( $sectionName, 0, 6 ) ==
'remote' ) {
365 $remote = $sectionConf;
370 if ( $remote !==
false && isset( $remote[
'url'] ) ) {
371 $url = $remote[
'url'];
374 $this->cache[
'remoteURL'] = $url;
376 return $this->cache[
'remoteURL'];
389 return isset( $this->cache[
'head'] ) &&
390 isset( $this->cache[
'headSHA1'] ) &&
391 isset( $this->cache[
'headCommitDate'] ) &&
392 isset( $this->cache[
'branch'] ) &&
393 isset( $this->cache[
'remoteURL'] );
406 if ( $this->cacheFile !==
null ) {
415 $this->logger->debug(
416 "Failed to compute GitInfo for \"{$this->basedir}\""
421 $cacheDir = dirname( $this->cacheFile );
422 if ( !file_exists( $cacheDir ) &&
425 throw new MWException(
"Unable to create GitInfo cache \"{$cacheDir}\"" );
428 file_put_contents( $this->cacheFile, FormatJson::encode( $this->cache ) );
460 private function getViewers() {
461 if ( self::$viewers ===
false ) {
462 self::$viewers = $this->options->get( MainConfigNames::GitRepositoryViewers );
463 $this->hookRunner->onGitViewers( self::$viewers );
466 return self::$viewers;
wfMkdirParents( $dir, $mode=null, $caller=null)
Make directory, and make all parent directories if they don't exist.
static repo()
Get the singleton for the repo at MW_INSTALL_PATH.
getHead()
Get the HEAD of the repo (without any opening "ref: ")
$cacheFile
Path to JSON cache file for pre-computed git information.
$basedir
Location of the .git directory.
getRemoteUrl()
Get the URL of the remote origin.
getHeadCommitDate()
Get the commit date of HEAD entry of the git code repository.
precomputeValues()
Precompute and cache git information.
$cache
Cached git information.
cacheIsComplete()
Check to see if the current cache is fully populated.
static $repo
Singleton for the repo at $IP.
$repoDir
Location of the repository.
getHeadViewUrl()
Get an URL to a web viewer link to the HEAD revision.
__construct( $repoDir, $usePrecomputed=true)
static isSHA1( $str)
Check if a string looks like a hex encoded SHA1 hash.
getHeadSHA1()
Get the SHA1 for the current HEAD of the repo.
getCurrentBranch()
Get the name of the current branch, or HEAD if not found.
A class containing constants representing the names of configuration variables.