jenkins-bot has submitted this change and it was merged. (
https://gerrit.wikimedia.org/r/392041 )
Change subject: xkill - lazy track labels/sitelinks/claims
......................................................................
xkill - lazy track labels/sitelinks/claims
This reverts I11676ed, which reverted Iac5ac53.
Bug: T179923
Change-Id: I6731897b67aa2e1ff996fa0d520e150c5da79a69
---
M client/config/WikibaseClient.default.php
M client/includes/DataAccess/Scribunto/EntityAccessor.php
M client/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseEntityLibrary.php
M client/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseLibrary.php
M client/includes/DataAccess/Scribunto/WikibaseLuaEntityBindings.php
M client/includes/DataAccess/Scribunto/mw.wikibase.entity.lua
M client/tests/phpunit/includes/DataAccess/Scribunto/EntityAccessorTest.php
M
client/tests/phpunit/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseEntityLibraryTest.php
M
client/tests/phpunit/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseLibraryTest.php
M docs/options.wiki
10 files changed, 259 insertions(+), 43 deletions(-)
Approvals:
WMDE-leszek: Looks good to me, approved
jenkins-bot: Verified
diff --git a/client/config/WikibaseClient.default.php
b/client/config/WikibaseClient.default.php
index c8943de..312edac 100644
--- a/client/config/WikibaseClient.default.php
+++ b/client/config/WikibaseClient.default.php
@@ -83,6 +83,8 @@
'disabledUsageAspects' => [],
+ 'fineGrainedLuaTracking' => true,
+
// The type of object cache to use. Use CACHE_XXX constants.
// This is both a repo and client setting, and should be set to
the same value in
// repo and clients for multiwiki setups.
diff --git a/client/includes/DataAccess/Scribunto/EntityAccessor.php
b/client/includes/DataAccess/Scribunto/EntityAccessor.php
index ee878fc..e529ae2 100644
--- a/client/includes/DataAccess/Scribunto/EntityAccessor.php
+++ b/client/includes/DataAccess/Scribunto/EntityAccessor.php
@@ -69,6 +69,24 @@
*/
private $termsLanguages;
+ /**
+ * @var bool
+ */
+ private $fineGrainedLuaTracking;
+
+ /**
+ * @param EntityIdParser $entityIdParser
+ * @param EntityLookup $entityLookup
+ * @param UsageAccumulator $usageAccumulator
+ * @param Serializer $entitySerializer
+ * @param Serializer $statementSerializer
+ * @param PropertyDataTypeLookup $dataTypeLookup
+ * @param LanguageFallbackChain $fallbackChain
+ * @param Language $language
+ * @param ContentLanguages $termsLanguages
+ * @param bool $fineGrainedLuaTracking Whether to track each used aspect
+ * separately in Lua or just track the All (X) usage.
+ */
public function __construct(
EntityIdParser $entityIdParser,
EntityLookup $entityLookup,
@@ -78,7 +96,8 @@
PropertyDataTypeLookup $dataTypeLookup,
LanguageFallbackChain $fallbackChain,
Language $language,
- ContentLanguages $termsLanguages
+ ContentLanguages $termsLanguages,
+ $fineGrainedLuaTracking
) {
$this->entityIdParser = $entityIdParser;
$this->entityLookup = $entityLookup;
@@ -89,6 +108,7 @@
$this->fallbackChain = $fallbackChain;
$this->language = $language;
$this->termsLanguages = $termsLanguages;
+ $this->fineGrainedLuaTracking = $fineGrainedLuaTracking;
}
/**
@@ -121,8 +141,9 @@
$entityId = $this->entityIdParser->parse( $prefixedEntityId );
- $this->usageAccumulator->addAllUsage( $entityId );
-
+ if ( !$this->fineGrainedLuaTracking ) {
+ $this->usageAccumulator->addAllUsage( $entityId );
+ }
try {
$entityObject = $this->entityLookup->getEntity(
$entityId );
} catch ( RevisionedUnresolvedRedirectException $ex ) {
diff --git
a/client/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseEntityLibrary.php
b/client/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseEntityLibrary.php
index a2b2c94..61b9542 100644
---
a/client/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseEntityLibrary.php
+++
b/client/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseEntityLibrary.php
@@ -135,6 +135,44 @@
}
/**
+ * Add a label usage (called once specific labels are accessed).
+ *
+ * @param string $entityId The Entity from which the labels were
accessed.
+ * @param string $langCode Language code of the labels accessed.
+ */
+ public function addLabelUsage( $entityId, $langCode ) {
+ $this->getImplementation()->addLabelUsage( $entityId, $langCode
);
+ }
+
+ /**
+ * Add a description usage (called once specific descriptions are
accessed).
+ *
+ * @param string $entityId The Entity from which the descriptions were
accessed.
+ * @param string $langCode Language code of the descriptions accessed.
+ */
+ public function addDescriptionUsage( $entityId, $langCode ) {
+ $this->getImplementation()->addDescriptionUsage( $entityId,
$langCode );
+ }
+
+ /**
+ * Add a sitelinks usage (called once specific sitelinks are accessed).
+ *
+ * @param string $entityId The Entity from which the sitelinks were
accessed.
+ */
+ public function addSiteLinksUsage( $entityId ) {
+ $this->getImplementation()->addSiteLinksUsage( $entityId );
+ }
+
+ /**
+ * Add an other (O) usage (called once the otherwise not covered aspect
is used).
+ *
+ * @param string $entityId The Entity from which something was accessed.
+ */
+ public function addOtherUsage( $entityId ) {
+ $this->getImplementation()->addOtherUsage( $entityId );
+ }
+
+ /**
* Register mw.wikibase.entity.lua library
*
* @return array
@@ -149,6 +187,11 @@
'formatStatements' => [ $this, 'formatStatements' ],
'formatPropertyValues' => [ $this,
'formatPropertyValues' ],
'addStatementUsage' => [ $this, 'addStatementUsage' ],
+ 'addLabelUsage' => [ $this, 'addLabelUsage' ],
+ 'addDescriptionUsage' => [ $this, 'addDescriptionUsage'
],
+ 'addSiteLinksUsage' => [ $this, 'addSiteLinksUsage' ],
+ 'addOtherUsage' => [ $this, 'addOtherUsage' ],
+ 'getSetting' => [ $this, 'getSetting' ],
];
return $this->getEngine()->registerInterface(
@@ -157,6 +200,19 @@
}
/**
+ * Wrapper for getSetting
+ *
+ * @param string $setting
+ *
+ * @return array
+ */
+ public function getSetting( $setting ) {
+ $this->checkType( 'setting', 1, $setting, 'string' );
+ $settings = WikibaseClient::getDefaultInstance()->getSettings();
+ return [ $settings->getSetting( $setting ) ];
+ }
+
+ /**
* Wrapper for getGlobalSiteId in WikibaseLuaEntityBindings
*
* @return string[]
diff --git
a/client/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseLibrary.php
b/client/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseLibrary.php
index a701969..c191718 100644
--- a/client/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseLibrary.php
+++ b/client/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseLibrary.php
@@ -200,7 +200,7 @@
private function newEntityAccessor() {
$wikibaseClient = WikibaseClient::getDefaultInstance();
-
+ $settings = $wikibaseClient->getSettings();
return new EntityAccessor(
$this->getEntityIdParser(),
$wikibaseClient->getRestrictedEntityLookup(),
@@ -210,7 +210,8 @@
$wikibaseClient->getPropertyDataTypeLookup(),
$this->getLanguageFallbackChain(),
$this->getLanguage(),
- $wikibaseClient->getTermsLanguages()
+ $wikibaseClient->getTermsLanguages(),
+ $settings->getSetting( 'fineGrainedLuaTracking' )
);
}
diff --git a/client/includes/DataAccess/Scribunto/WikibaseLuaEntityBindings.php
b/client/includes/DataAccess/Scribunto/WikibaseLuaEntityBindings.php
index a2715ca..306a3ff 100644
--- a/client/includes/DataAccess/Scribunto/WikibaseLuaEntityBindings.php
+++ b/client/includes/DataAccess/Scribunto/WikibaseLuaEntityBindings.php
@@ -123,6 +123,48 @@
}
/**
+ * Add a label usage (called once specific labels are accessed).
+ *
+ * @param string $entityId The Entity from which the labels were
accessed.
+ * @param string $langCode Language code the labels accessed.
+ */
+ public function addLabelUsage( $entityId, $langCode ) {
+ $entityId = $this->entityIdParser->parse( $entityId );
+ $this->usageAccumulator->addLabelUsage( $entityId, $langCode );
+ }
+
+ /**
+ * Add a description usage (called once specific descriptions are
accessed).
+ *
+ * @param string $entityId The Entity from which the descriptions were
accessed.
+ * @param string $langCode Language code the descriptions accessed.
+ */
+ public function addDescriptionUsage( $entityId, $langCode ) {
+ $entityId = $this->entityIdParser->parse( $entityId );
+ $this->usageAccumulator->addDescriptionUsage( $entityId,
$langCode );
+ }
+
+ /**
+ * Add a other usage.
+ *
+ * @param string $entityId The Entity from which something was accessed.
+ */
+ public function addOtherUsage( $entityId ) {
+ $entityId = $this->entityIdParser->parse( $entityId );
+ $this->usageAccumulator->addOtherUsage( $entityId );
+ }
+
+ /**
+ * Add a sitelink usage (called once any sitelink is accessed).
+ *
+ * @param string $entityId The Entity from which the sitelinks were
accessed.
+ */
+ public function addSiteLinksUsage( $entityId ) {
+ $entityId = $this->entityIdParser->parse( $entityId );
+ $this->usageAccumulator->addSiteLinksUsage( $entityId );
+ }
+
+ /**
* Get global site ID (e.g. "enwiki")
* This is basically a helper function.
* @TODO: Make this part of mw.site in the Scribunto extension.
diff --git a/client/includes/DataAccess/Scribunto/mw.wikibase.entity.lua
b/client/includes/DataAccess/Scribunto/mw.wikibase.entity.lua
index 6c2ce4d..7715eda 100644
--- a/client/includes/DataAccess/Scribunto/mw.wikibase.entity.lua
+++ b/client/includes/DataAccess/Scribunto/mw.wikibase.entity.lua
@@ -33,45 +33,76 @@
return type( propertyId ) == 'string' and propertyId:match(
'^P[1-9]%d*$' )
end
--- Function to mask an entity's claims table in order to log access
--- to individual claims of an entity.
+-- Log access to claims of entity
+--
+-- @param {string} entityId
+-- @param {string} propertyId
+local addStatementUsage = function( entityId, propertyId )
+ if isValidPropertyId( propertyId ) then
+ -- Only attempt to track the usage if we have a valid property
id.
+ php.addStatementUsage( entityId, propertyId )
+ end
+end
+
+-- Function to mask an entity's subtables in order to log access
-- Code for logging based on: http://www.lua.org/pil/13.4.4.html
--
-- @param {table} entity
-local maskClaimsTable = function( entity )
- if entity.claims == nil then
- return entity
+-- @param {string} tableName
+-- @param {function} usageFunc
+local maskEntityTable = function( entity, tableName, usageFunc )
+ if entity[tableName] == nil then
+ return
end
- local actualEntityClaims = entity.claims
- entity.claims = {}
+ local actualEntityTable = entity[tableName]
+ entity[tableName] = {}
- local pseudoClaimsMetatable = {}
- pseudoClaimsMetatable.__index = function( _, propertyId )
- if isValidPropertyId( propertyId ) then
- -- Only attempt to track the usage if we have a valid
property id.
- php.addStatementUsage( entity.id, propertyId )
+ local pseudoTableMetatable = {}
+ pseudoTableMetatable.__index = function( _, key )
+ usageFunc( entity.id, key )
+ return actualEntityTable[key]
+ end
+
+ pseudoTableMetatable.__newindex = function( _, _, _ )
+ error( 'Entity cannot be modified', 2 )
+ end
+
+ local logNext = function( _, key )
+ local k, v = next( actualEntityTable, key )
+ if k ~= nil then
+ usageFunc( entity.id, k )
end
-
- return actualEntityClaims[propertyId]
+ return k, v
end
- pseudoClaimsMetatable.__newindex = function( _, _, _ )
- error( 'Entity cannot be modified' )
- end
-
- local logNext = function( _, propertyId )
- if isValidPropertyId( propertyId ) then
- php.addStatementUsage( entity.id, propertyId )
- end
- return next( actualEntityClaims, propertyId )
- end
-
- pseudoClaimsMetatable.__pairs = function( _ )
+ pseudoTableMetatable.__pairs = function( _ )
return logNext, {}, nil
end
- setmetatable( entity.claims, pseudoClaimsMetatable )
- return entity
+ setmetatable( entity[tableName], pseudoTableMetatable )
+end
+
+local noUsageTracking = function()
+end
+
+-- Function to mask an entity's subtables in order to log access and prevent
modifications
+--
+-- @param {table} entity
+-- @param {bool} fineGrainedTracking
+local maskEntityTables = function ( entity, fineGrainedTracking )
+ if fineGrainedTracking then
+ maskEntityTable( entity, 'claims', addStatementUsage )
+ maskEntityTable( entity, 'labels', php.addLabelUsage )
+ maskEntityTable( entity, 'sitelinks', php.addSiteLinksUsage )
+ maskEntityTable( entity, 'descriptions',
php.addDescriptionUsage )
+ maskEntityTable( entity, 'aliases', php.addOtherUsage )
+ else
+ maskEntityTable( entity, 'claims', noUsageTracking )
+ maskEntityTable( entity, 'labels', noUsageTracking )
+ maskEntityTable( entity, 'sitelinks', noUsageTracking )
+ maskEntityTable( entity, 'descriptions', noUsageTracking )
+ maskEntityTable( entity, 'aliases', noUsageTracking )
+ end
end
-- Create new entity object from given data
@@ -91,9 +122,10 @@
error( 'mw.wikibase.entity must not be constructed using legacy
data' )
end
- local entity = maskClaimsTable( data )
- setmetatable( entity, metatable )
+ local entity = data
+ maskEntityTables( entity, php.getSetting( 'fineGrainedLuaTracking' ) )
+ setmetatable( entity, metatable )
return entity
end
diff --git
a/client/tests/phpunit/includes/DataAccess/Scribunto/EntityAccessorTest.php
b/client/tests/phpunit/includes/DataAccess/Scribunto/EntityAccessorTest.php
index fc8db6a..3dfcf04 100644
--- a/client/tests/phpunit/includes/DataAccess/Scribunto/EntityAccessorTest.php
+++ b/client/tests/phpunit/includes/DataAccess/Scribunto/EntityAccessorTest.php
@@ -47,7 +47,8 @@
private function getEntityAccessor(
EntityLookup $entityLookup = null,
UsageAccumulator $usageAccumulator = null,
- $langCode = 'en'
+ $langCode = 'en',
+ $fineGrainedTracking = true
) {
$language = new Language( $langCode );
$serializerFactory = new SerializerFactory(
@@ -77,7 +78,8 @@
$propertyDataTypeLookup,
$fallbackChain,
$language,
- new StaticContentLanguages( [ 'de', $langCode, 'es',
'ja' ] )
+ new StaticContentLanguages( [ 'de', $langCode, 'es',
'ja' ] ),
+ $fineGrainedTracking
);
}
@@ -103,18 +105,24 @@
}
}
- public function testGetEntity_usage() {
+ /**
+ * @testWith [true]
+ * [false]
+ */
+ public function testGetEntity_usage( $fineGrainedTracking ) {
$item = $this->getItem();
$itemId = $item->getId();
$entityLookup = new MockRepository();
$usages = new HashUsageAccumulator();
- $entityAccessor = $this->getEntityAccessor( $entityLookup,
$usages );
+ $entityAccessor = $this->getEntityAccessor( $entityLookup,
$usages, 'en', $fineGrainedTracking );
$entityAccessor->getEntity( $itemId->getSerialization() );
- $this->assertTrue(
- $this->hasUsage( $usages->getUsages(), $item->getId(),
EntityUsage::ALL_USAGE ), 'all usage'
+ // Only access to specific labels/claims/etc will result in
actual usage
+ $this->assertEquals(
+ $this->hasUsage( $usages->getUsages(), $item->getId(),
EntityUsage::ALL_USAGE ),
+ !$fineGrainedTracking
);
}
diff --git
a/client/tests/phpunit/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseEntityLibraryTest.php
b/client/tests/phpunit/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseEntityLibraryTest.php
index 015c8bf..74c2564 100644
---
a/client/tests/phpunit/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseEntityLibraryTest.php
+++
b/client/tests/phpunit/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseEntityLibraryTest.php
@@ -217,6 +217,36 @@
$this->assertSame( $allowDataAccessInUserLanguage, $cacheSplit
);
}
+ public function testAddLabelUsage() {
+ $luaWikibaseLibrary = $this->newScribuntoLuaWikibaseLibrary();
+ $luaWikibaseLibrary->addLabelUsage( 'Q32488', 'he' );
+ $usages =
$luaWikibaseLibrary->getUsageAccumulator()->getUsages();
+
+ $this->assertArrayHasKey( 'Q32488#L.he', $usages );
+ }
+
+ public function testAddDescriptionUsage() {
+ $luaWikibaseLibrary = $this->newScribuntoLuaWikibaseLibrary();
+ $luaWikibaseLibrary->addDescriptionUsage( 'Q32488', 'he' );
+ $usages =
$luaWikibaseLibrary->getUsageAccumulator()->getUsages();
+
+ $this->assertArrayHasKey( 'Q32488#D.he', $usages );
+ }
+
+ public function testAddSitelinksUsage() {
+ $luaWikibaseLibrary = $this->newScribuntoLuaWikibaseLibrary();
+ $luaWikibaseLibrary->addSiteLinksUsage( 'Q32488' );
+ $usages =
$luaWikibaseLibrary->getUsageAccumulator()->getUsages();
+ $this->assertArrayHasKey( 'Q32488#S', $usages );
+ }
+
+ public function testAddOtherUsage() {
+ $luaWikibaseLibrary = $this->newScribuntoLuaWikibaseLibrary();
+ $luaWikibaseLibrary->addOtherUsage( 'Q32488' );
+ $usages =
$luaWikibaseLibrary->getUsageAccumulator()->getUsages();
+ $this->assertArrayHasKey( 'Q32488#O', $usages );
+ }
+
/**
* @param bool &$cacheSplit Will become true when the ParserCache has
been split
* @param Language|null $userLang The user's language
diff --git
a/client/tests/phpunit/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseLibraryTest.php
b/client/tests/phpunit/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseLibraryTest.php
index 0dc35a1..74245d7 100644
---
a/client/tests/phpunit/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseLibraryTest.php
+++
b/client/tests/phpunit/includes/DataAccess/Scribunto/Scribunto_LuaWikibaseLibraryTest.php
@@ -282,8 +282,19 @@
$this->assertSame( $allowDataAccessInUserLanguage, $cacheSplit
);
}
- public function testRenderSnak_languageFallback() {
+ public function fineGrainedLuaTrackingProvider() {
+ return [
+ [ true, [ 'Q885588#L' ] ],
+ [ false, [ 'Q32488#X', 'Q885588#L' ] ],
+ ];
+ }
+
+ /**
+ * @dataProvider fineGrainedLuaTrackingProvider
+ */
+ public function testRenderSnak_languageFallback( $fineGrainedTracking,
$expectedUsage ) {
$this->setAllowDataAccessInUserLanguage( true );
+ $this->setFineGrainedLuaTracking( $fineGrainedTracking );
$cacheSplit = false;
$lang = Language::factory( 'ku' );
@@ -298,7 +309,7 @@
// All languages in the fallback chain from 'ku' to 'ku-latn'
count as "used".
$usage =
$luaWikibaseLibrary->getUsageAccumulator()->getUsages();
- $this->assertSame( [ 'Q32488#X', 'Q885588#L' ], array_keys(
$usage ) );
+ $this->assertSame( $expectedUsage, array_keys( $usage ) );
$this->assertSame( true, $cacheSplit );
}
@@ -318,6 +329,10 @@
[ '<span>Q885588</span>' ],
$luaWikibaseLibrary->formatValue( $snak )
);
+
+ $usage =
$luaWikibaseLibrary->getUsageAccumulator()->getUsages();
+ $this->assertArrayHasKey( 'Q885588#L.de', $usage );
+ $this->assertArrayHasKey( 'Q885588#T', $usage );
}
/**
@@ -497,4 +512,12 @@
$settings->setSetting( 'allowDataAccessInUserLanguage', $value
);
}
+ /**
+ * @param bool $value
+ */
+ private function setFineGrainedLuaTracking( $value ) {
+ $settings = WikibaseClient::getDefaultInstance()->getSettings();
+ $settings->setSetting( 'fineGrainedLuaTracking', $value );
+ }
+
}
diff --git a/docs/options.wiki b/docs/options.wiki
index 5299af4..00ebabc 100644
--- a/docs/options.wiki
+++ b/docs/options.wiki
@@ -118,6 +118,7 @@
;sendEchoNotification: If true, allows users on the client wiki to get a
notification when a page they created is connected to a repo item. This
requires the Echo extension.
;echoIcon: If <code>sendEchoNotification</code> is set to <code>true</code>,
you can also provide what icon the user will see. The correct syntax is <code>[
'url' => '...' ]</code> or <code>[ 'path' => '...' ]</code> where
<code>path</code> is relative to <code>$wgExtensionAssetsPath</code>. Defaults
to <code>false</code> which means that there will be the default Echo icon.
;disabledUsageAspects: Array of usage aspects that should not be saved in the
<code>wbc_entity_usage</code> table. This supports aspect codes (like "T", "L"
or "X"), but not full aspect keys (like "L.de"). For example <code>[ 'D', 'C'
]</code> can be used to disable description and statement usages. Also a
replacement usage type can be given in the form of <code>[
'usage-type-to-replace' => 'replacement' ]</code>.
+;fineGrainedLuaTracking: Enable fine grained tracking on entities accessed
through Lua in client. No all (X) usage will be recorded, but each aspect will
be recorded individually based on actual usage.
;wikiPageUpdaterDbBatchSize: DEPRECATED. If set, acts as a default for
purgeCacheBatchSize and recentChangesBatchSize.
;purgeCacheBatchSize: Number of pages to process in each HTMLCacheUpdateJob, a
job used to send client wikis notifications about relevant changes to entities.
Higher value mean fewer jobs but longer run-time per job. Defaults to
wikiPageUpdaterDbBatchSize (for backwards compatibility) or MediaWiki core's
$wgUpdateRowsPerJob (which currently defaults to 300).
;recentChangesBatchSize: Number of <code>recentchanges</code> table rows to
create in each InjectRCRecordsJob, a job used to send client wikis
notifications about relevant changes to entities. Higher value mean fewer jobs
but longer run-time per job. Defaults to wikiPageUpdaterDbBatchSize (for
backwards compatibility) or MediaWiki core's $wgUpdateRowsPerJob (which
currently defaults to 300).
--
To view, visit https://gerrit.wikimedia.org/r/392041
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I6731897b67aa2e1ff996fa0d520e150c5da79a69
Gerrit-PatchSet: 6
Gerrit-Project: mediawiki/extensions/Wikibase
Gerrit-Branch: master
Gerrit-Owner: Ladsgroup <[email protected]>
Gerrit-Reviewer: Anomie <[email protected]>
Gerrit-Reviewer: Daniel Kinzler <[email protected]>
Gerrit-Reviewer: Eranroz <[email protected]>
Gerrit-Reviewer: Hoo man <[email protected]>
Gerrit-Reviewer: Jackmcbarn <[email protected]>
Gerrit-Reviewer: Ladsgroup <[email protected]>
Gerrit-Reviewer: Matěj Suchánek <[email protected]>
Gerrit-Reviewer: Thiemo Kreuz (WMDE) <[email protected]>
Gerrit-Reviewer: Tim Starling <[email protected]>
Gerrit-Reviewer: WMDE-leszek <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits