MediaWiki REL1_28
GitInfo.php
Go to the documentation of this file.
1<?php
26class GitInfo {
27
31 protected static $repo = null;
32
36 protected $basedir;
37
41 protected $cacheFile;
42
46 protected $cache = [];
47
51 private static $viewers = false;
52
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
102 protected static function getCacheFilePath( $repoDir ) {
104
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 $cachePath = "{$wgGitInfoCacheDirectory}/{$fileName}";
123 if ( is_readable( $cachePath ) ) {
124 return $cachePath;
125 }
126 }
127
128 return "$repoDir/gitinfo.json";
129 }
130
136 public static function repo() {
137 if ( is_null( self::$repo ) ) {
138 global $IP;
139 self::$repo = new self( $IP );
140 }
141 return self::$repo;
142 }
143
150 public static function isSHA1( $str ) {
151 return !!preg_match( '/^[0-9A-F]{40}$/i', $str );
152 }
153
159 public function getHead() {
160 if ( !isset( $this->cache['head'] ) ) {
161 $headFile = "{$this->basedir}/HEAD";
162 $head = false;
163
164 if ( is_readable( $headFile ) ) {
165 $head = file_get_contents( $headFile );
166
167 if ( preg_match( "/ref: (.*)/", $head, $m ) ) {
168 $head = rtrim( $m[1] );
169 } else {
170 $head = rtrim( $head );
171 }
172 }
173 $this->cache['head'] = $head;
174 }
175 return $this->cache['head'];
176 }
177
183 public function getHeadSHA1() {
184 if ( !isset( $this->cache['headSHA1'] ) ) {
185 $head = $this->getHead();
186 $sha1 = false;
187
188 // If detached HEAD may be a SHA1
189 if ( self::isSHA1( $head ) ) {
190 $sha1 = $head;
191 } else {
192 // If not a SHA1 it may be a ref:
193 $refFile = "{$this->basedir}/{$head}";
194 if ( is_readable( $refFile ) ) {
195 $sha1 = rtrim( file_get_contents( $refFile ) );
196 }
197 }
198 $this->cache['headSHA1'] = $sha1;
199 }
200 return $this->cache['headSHA1'];
201 }
202
209 public function getHeadCommitDate() {
211
212 if ( !isset( $this->cache['headCommitDate'] ) ) {
213 $date = false;
214 if ( is_file( $wgGitBin ) &&
215 is_executable( $wgGitBin ) &&
216 $this->getHead() !== false
217 ) {
218 $environment = [ "GIT_DIR" => $this->basedir ];
219 $cmd = wfEscapeShellArg( $wgGitBin ) .
220 " show -s --format=format:%ct HEAD";
221 $retc = false;
222 $commitDate = wfShellExec( $cmd, $retc, $environment );
223 if ( $retc === 0 ) {
224 $date = (int)$commitDate;
225 }
226 }
227 $this->cache['headCommitDate'] = $date;
228 }
229 return $this->cache['headCommitDate'];
230 }
231
237 public function getCurrentBranch() {
238 if ( !isset( $this->cache['branch'] ) ) {
239 $branch = $this->getHead();
240 if ( $branch &&
241 preg_match( "#^refs/heads/(.*)$#", $branch, $m )
242 ) {
243 $branch = $m[1];
244 }
245 $this->cache['branch'] = $branch;
246 }
247 return $this->cache['branch'];
248 }
249
255 public function getHeadViewUrl() {
256 $url = $this->getRemoteUrl();
257 if ( $url === false ) {
258 return false;
259 }
260 foreach ( self::getViewers() as $repo => $viewer ) {
261 $pattern = '#^' . $repo . '$#';
262 if ( preg_match( $pattern, $url, $matches ) ) {
263 $viewerUrl = preg_replace( $pattern, $viewer, $url );
264 $headSHA1 = $this->getHeadSHA1();
265 $replacements = [
266 '%h' => substr( $headSHA1, 0, 7 ),
267 '%H' => $headSHA1,
268 '%r' => urlencode( $matches[1] ),
269 '%R' => $matches[1],
270 ];
271 return strtr( $viewerUrl, $replacements );
272 }
273 }
274 return false;
275 }
276
281 protected function getRemoteUrl() {
282 if ( !isset( $this->cache['remoteURL'] ) ) {
283 $config = "{$this->basedir}/config";
284 $url = false;
285 if ( is_readable( $config ) ) {
286 MediaWiki\suppressWarnings();
287 $configArray = parse_ini_file( $config, true );
288 MediaWiki\restoreWarnings();
289 $remote = false;
290
291 // Use the "origin" remote repo if available or any other repo if not.
292 if ( isset( $configArray['remote origin'] ) ) {
293 $remote = $configArray['remote origin'];
294 } elseif ( is_array( $configArray ) ) {
295 foreach ( $configArray as $sectionName => $sectionConf ) {
296 if ( substr( $sectionName, 0, 6 ) == 'remote' ) {
297 $remote = $sectionConf;
298 }
299 }
300 }
301
302 if ( $remote !== false && isset( $remote['url'] ) ) {
303 $url = $remote['url'];
304 }
305 }
306 $this->cache['remoteURL'] = $url;
307 }
308 return $this->cache['remoteURL'];
309 }
310
320 public function cacheIsComplete() {
321 return isset( $this->cache['head'] ) &&
322 isset( $this->cache['headSHA1'] ) &&
323 isset( $this->cache['headCommitDate'] ) &&
324 isset( $this->cache['branch'] ) &&
325 isset( $this->cache['remoteURL'] );
326 }
327
337 public function precomputeValues() {
338 if ( $this->cacheFile !== null ) {
339 // Try to completely populate the cache
340 $this->getHead();
341 $this->getHeadSHA1();
342 $this->getHeadCommitDate();
343 $this->getCurrentBranch();
344 $this->getRemoteUrl();
345
346 if ( !$this->cacheIsComplete() ) {
347 wfDebugLog( 'gitinfo',
348 "Failed to compute GitInfo for \"{$this->basedir}\""
349 );
350 return;
351 }
352
353 $cacheDir = dirname( $this->cacheFile );
354 if ( !file_exists( $cacheDir ) &&
355 !wfMkdirParents( $cacheDir, null, __METHOD__ )
356 ) {
357 throw new MWException( "Unable to create GitInfo cache \"{$cacheDir}\"" );
358 }
359
360 file_put_contents( $this->cacheFile, FormatJson::encode( $this->cache ) );
361 }
362 }
363
368 public static function headSHA1() {
369 return self::repo()->getHeadSHA1();
370 }
371
376 public static function currentBranch() {
377 return self::repo()->getCurrentBranch();
378 }
379
384 public static function headViewUrl() {
385 return self::repo()->getHeadViewUrl();
386 }
387
392 protected static function getViewers() {
394
395 if ( self::$viewers === false ) {
396 self::$viewers = $wgGitRepositoryViewers;
397 Hooks::run( 'GitViewers', [ &self::$viewers ] );
398 }
399
400 return self::$viewers;
401 }
402}
$wgGitRepositoryViewers
Map GIT repository URLs to viewer URLs to provide links in Special:Version.
$wgGitInfoCacheDirectory
Directory where GitInfo will look for pre-computed cache files.
$wgGitBin
Fully specified path to git binary.
wfShellExec( $cmd, &$retval=null, $environ=[], $limits=[], $options=[])
Execute a shell command, with time and memory limits mirrored from the PHP configuration if supported...
wfEscapeShellArg()
Version of escapeshellarg() that works better on Windows.
wfDebugLog( $logGroup, $text, $dest='all', array $context=[])
Send a line to a supplementary debug log file, if configured, or main debug log if not.
wfMkdirParents( $dir, $mode=null, $caller=null)
Make directory, and make all parent directories if they don't exist.
$IP
Definition WebStart.php:58
static repo()
Get the singleton for the repo at $IP.
Definition GitInfo.php:136
getHead()
Get the HEAD of the repo (without any opening "ref: ")
Definition GitInfo.php:159
static getCacheFilePath( $repoDir)
Compute the path to the cache file for a given directory.
Definition GitInfo.php:102
$cacheFile
Path to JSON cache file for pre-computed git information.
Definition GitInfo.php:41
static $viewers
Map of repo URLs to viewer URLs.
Definition GitInfo.php:51
$basedir
Location of the .git directory.
Definition GitInfo.php:36
getRemoteUrl()
Get the URL of the remote origin.
Definition GitInfo.php:281
static headSHA1()
Definition GitInfo.php:368
getHeadCommitDate()
Get the commit date of HEAD entry of the git code repository.
Definition GitInfo.php:209
static headViewUrl()
Definition GitInfo.php:384
precomputeValues()
Precompute and cache git information.
Definition GitInfo.php:337
$cache
Cached git information.
Definition GitInfo.php:46
cacheIsComplete()
Check to see if the current cache is fully populated.
Definition GitInfo.php:320
static $repo
Singleton for the repo at $IP.
Definition GitInfo.php:31
static currentBranch()
Definition GitInfo.php:376
getHeadViewUrl()
Get an URL to a web viewer link to the HEAD revision.
Definition GitInfo.php:255
__construct( $repoDir, $usePrecomputed=true)
Definition GitInfo.php:58
static isSHA1( $str)
Check if a string looks like a hex encoded SHA1 hash.
Definition GitInfo.php:150
getHeadSHA1()
Get the SHA1 for the current HEAD of the repo.
Definition GitInfo.php:183
getCurrentBranch()
Get the name of the current branch, or HEAD if not found.
Definition GitInfo.php:237
static getViewers()
Gets the list of repository viewers.
Definition GitInfo.php:392
MediaWiki exception.
when a variable name is used in a it is silently declared as a new local masking the global
Definition design.txt:95
This document is intended to provide useful advice for parties seeking to redistribute MediaWiki to end users It s targeted particularly at maintainers for Linux since it s been observed that distribution packages of MediaWiki often break We ve consistently had to recommend that users seeking support use official tarballs instead of their distribution s and this often solves whatever problem the user is having It would be nice if this could such as
injection txt This is an overview of how MediaWiki makes use of dependency injection The design described here grew from the discussion of RFC T384 The term dependency this means that anything an object needs to operate should be injected from the the object itself should only know narrow no concrete implementation of the logic it relies on The requirement to inject everything typically results in an architecture that based on two main types of and essentially stateless service objects that use other service objects to operate on the value objects As of the beginning MediaWiki is only starting to use the DI approach Much of the code still relies on global state or direct resulting in a highly cyclical dependency which acts as the top level factory for services in MediaWiki which can be used to gain access to default instances of various services MediaWikiServices however also allows new services to be defined and default services to be redefined Services are defined or redefined by providing a callback the instantiator that will return a new instance of the service When it will create an instance of MediaWikiServices and populate it with the services defined in the files listed by thereby bootstrapping the DI framework Per $wgServiceWiringFiles lists includes ServiceWiring php
Definition injection.txt:37
you have access to all of the normal MediaWiki so you can get a DB use the cache