jenkins-bot has submitted this change and it was merged. (
https://gerrit.wikimedia.org/r/337227 )
Change subject: Dispatching Importers
......................................................................
Dispatching Importers
Change-Id: I26d6166ca1e8e4e155dae3f1a4ecbf4ddfd6fbd6
---
M extension.json
D src/ExternalMediaWikiFile.php
A src/Generic/DispatchingImporter.php
A src/Generic/ImportAdjustments.php
A src/Generic/ImportDetails.php
A src/Generic/ImportTargetException.php
A src/Generic/Importer.php
A src/Generic/TargetUrl.php
D src/Importable.php
R src/MediaWiki/HostBasedSiteTableLookup.php
A src/MediaWiki/MediaWikiImporter.php
M src/ServiceWiring.php
M src/SpecialImportFile.php
A tests/Generic/TargetUrlTest.php
R tests/MediaWiki/HostBasedSiteTableLookupTest.php
15 files changed, 379 insertions(+), 105 deletions(-)
Approvals:
WMDE-Fisch: Looks good to me, approved
jenkins-bot: Verified
diff --git a/extension.json b/extension.json
index e9edcc8..345c4b3 100644
--- a/extension.json
+++ b/extension.json
@@ -19,15 +19,25 @@
"ImportFile": "FileImporter\\SpecialImportFile"
},
"AutoloadClasses": {
- "FileImporter\\ExternalMediaWikiFile":
"src/ExternalMediaWikiFile.php",
- "FileImporter\\Importable": "src/Importable.php",
+ "FileImporter\\Generic\\DispatchingImporter":
"src/Generic/DispatchingImporter.php",
+ "FileImporter\\Generic\\ImportAdjustments":
"src/Generic/ImportAdjustments.php",
+ "FileImporter\\Generic\\ImportDetails":
"src/Generic/ImportDetails.php",
+ "FileImporter\\Generic\\Importer": "src/Generic/Importer.php",
+ "FileImporter\\Generic\\ImportTargetException":
"src/Generic/ImportTargetException.php",
+ "FileImporter\\Generic\\TargetUrl": "src/Generic/TargetUrl.php",
+ "FileImporter\\MediaWiki\\HostBasedSiteTableLookup":
"src/MediaWiki/HostBasedSiteTableLookup.php",
+ "FileImporter\\MediaWiki\\MediaWikiImporter":
"src/MediaWiki/MediaWikiImporter.php",
"FileImporter\\FileImporterHooks": "src/FileImporterHooks.php",
- "FileImporter\\SpecialImportFile": "src/SpecialImportFile.php",
- "FileImporter\\UrlBasedSiteLookup": "src/UrlBasedSiteLookup.php"
+ "FileImporter\\SpecialImportFile": "src/SpecialImportFile.php"
},
"ServiceWiringFiles": [
"src/ServiceWiring.php"
],
+ "config": {
+ "FileImporterImporterServices": [
+ "FileImporterMediaWikiImporter"
+ ]
+ },
"ResourceModules": {
"ext.FileImporter.Special": {
"styles": [
diff --git a/src/ExternalMediaWikiFile.php b/src/ExternalMediaWikiFile.php
deleted file mode 100644
index 22cb263..0000000
--- a/src/ExternalMediaWikiFile.php
+++ /dev/null
@@ -1,20 +0,0 @@
-<?php
-
-namespace FileImporter;
-
-// TODO implement real logic here
-class ExternalMediaWikiFile implements Importable {
-
- public function getTitle() {
- return 'Berlin_Montage_4';
- }
-
- public function getImageUrl() {
- return
'https://upload.wikimedia.org/wikipedia/commons/5/52/Berlin_Montage_4.jpg';
- }
-
- public function getTargetUrl() {
- return
'https://en.wikipedia.org/wiki/File:Berlin_Montage_4.jpg';
- }
-
-}
diff --git a/src/Generic/DispatchingImporter.php
b/src/Generic/DispatchingImporter.php
new file mode 100644
index 0000000..453cc78
--- /dev/null
+++ b/src/Generic/DispatchingImporter.php
@@ -0,0 +1,59 @@
+<?php
+
+namespace FileImporter\Generic;
+
+class DispatchingImporter implements Importer {
+
+ /**
+ * @var Importer[]
+ */
+ private $services;
+
+ /**
+ * @param Importer[] $services
+ */
+ public function __construct( array $services ) {
+ $this->services = $services;
+ }
+
+ /**
+ * @param TargetUrl $targetUrl
+ *
+ * @return Importer
+ * @throws ImportTargetException
+ */
+ private function getServiceForTarget( TargetUrl $targetUrl ) {
+ foreach ( $this->services as $service ) {
+ if ( $service->canImport( $targetUrl ) ) {
+ return $service;
+ }
+ }
+ throw new ImportTargetException();
+ }
+
+ public function canImport( TargetUrl $targetUrl ) {
+ try {
+ $this->getServiceForTarget( $targetUrl );
+ return true;
+ } catch ( ImportTargetException $e ) {
+ return false;
+ }
+ }
+
+ public function getImportDetails( TargetUrl $targetUrl ) {
+ $importer = $this->getServiceForTarget( $targetUrl );
+ return $importer->getImportDetails( $targetUrl );
+ }
+
+ /**
+ * @param TargetUrl $targetUrl
+ * @param ImportAdjustments $importAdjustments
+ *
+ * @return bool success
+ */
+ public function import( TargetUrl $targetUrl, ImportAdjustments
$importAdjustments ) {
+ $importer = $this->getServiceForTarget( $targetUrl );
+ return $importer->import( $targetUrl, $importAdjustments );
+ }
+
+}
diff --git a/src/Generic/ImportAdjustments.php
b/src/Generic/ImportAdjustments.php
new file mode 100644
index 0000000..f3d4754
--- /dev/null
+++ b/src/Generic/ImportAdjustments.php
@@ -0,0 +1,7 @@
+<?php
+
+namespace FileImporter\Generic;
+
+class ImportAdjustments {
+
+}
diff --git a/src/Generic/ImportDetails.php b/src/Generic/ImportDetails.php
new file mode 100644
index 0000000..452d438
--- /dev/null
+++ b/src/Generic/ImportDetails.php
@@ -0,0 +1,44 @@
+<?php
+
+namespace FileImporter\Generic;
+
+class ImportDetails {
+
+ /**
+ * @var TargetUrl
+ */
+ private $targetUrl;
+
+ /**
+ * @var string
+ */
+ private $titleText;
+
+ /**
+ * @var string
+ */
+ private $imageDisplayUrl;
+
+ public function __construct(
+ TargetUrl $targetUrl,
+ $titleText,
+ $imageDisplayUrl
+ ) {
+ $this->targetUrl = $targetUrl;
+ $this->titleText = $titleText;
+ $this->imageDisplayUrl = $imageDisplayUrl;
+ }
+
+ public function getTitleText() {
+ return $this->titleText;
+ }
+
+ public function getImageDisplayUrl() {
+ return $this->imageDisplayUrl;
+ }
+
+ public function getTargetUrl() {
+ return $this->targetUrl;
+ }
+
+}
diff --git a/src/Generic/ImportTargetException.php
b/src/Generic/ImportTargetException.php
new file mode 100644
index 0000000..ce93391
--- /dev/null
+++ b/src/Generic/ImportTargetException.php
@@ -0,0 +1,9 @@
+<?php
+
+namespace FileImporter\Generic;
+
+use RuntimeException;
+
+class ImportTargetException extends RuntimeException{
+
+}
diff --git a/src/Generic/Importer.php b/src/Generic/Importer.php
new file mode 100644
index 0000000..e012c43
--- /dev/null
+++ b/src/Generic/Importer.php
@@ -0,0 +1,31 @@
+<?php
+
+namespace FileImporter\Generic;
+
+interface Importer {
+
+ /**
+ * @param TargetUrl $targetUrl
+ *
+ * @return bool
+ */
+ public function canImport( TargetUrl $targetUrl );
+
+ /**
+ * @param TargetUrl $targetUrl
+ *
+ * @return ImportDetails
+ * @throws ImportTargetException if the given target can't be imported
by this importer
+ */
+ public function getImportDetails( TargetUrl $targetUrl );
+
+ /**
+ * @param TargetUrl $targetUrl
+ * @param ImportAdjustments $importAdjustments
+ *
+ * @return bool success
+ * @throws ImportTargetException if the given target can't be imported
by this importer
+ */
+ public function import( TargetUrl $targetUrl, ImportAdjustments
$importAdjustments );
+
+}
diff --git a/src/Generic/TargetUrl.php b/src/Generic/TargetUrl.php
new file mode 100644
index 0000000..cac9d34
--- /dev/null
+++ b/src/Generic/TargetUrl.php
@@ -0,0 +1,39 @@
+<?php
+
+namespace FileImporter\Generic;
+
+class TargetUrl {
+
+ /**
+ * @var string
+ */
+ private $url;
+
+ /**
+ * @var string[]|null|bool
+ */
+ private $parsed;
+
+ /**
+ * @param string $url
+ */
+ public function __construct( $url ) {
+ $this->url = $url;
+ }
+
+ public function getUrl() {
+ return $this->url;
+ }
+
+ public function getParsedUrl() {
+ if ( $this->parsed === null ) {
+ $this->parsed = wfParseUrl( $this->url );
+ }
+ return $this->parsed;
+ }
+
+ public function isParsable() {
+ return (bool)$this->getParsedUrl();
+ }
+
+}
diff --git a/src/Importable.php b/src/Importable.php
deleted file mode 100644
index ae8b8c2..0000000
--- a/src/Importable.php
+++ /dev/null
@@ -1,24 +0,0 @@
-<?php
-
-namespace FileImporter;
-
-interface Importable {
-
- /**
- * @return string The text to be used as the title.
- * e.g. in Mediawiki File:Berlin would simply be "Berlin"
- */
- public function getTitle();
-
- /**
- * @return string A URL that can be used to display the image
- * This could be a URL on an external site.
- */
- public function getImageUrl();
-
- /**
- * @return string The URL that the import was initiated using.
- */
- public function getTargetUrl();
-
-}
diff --git a/src/UrlBasedSiteLookup.php
b/src/MediaWiki/HostBasedSiteTableLookup.php
similarity index 60%
rename from src/UrlBasedSiteLookup.php
rename to src/MediaWiki/HostBasedSiteTableLookup.php
index 20c1d03..01c32cc 100644
--- a/src/UrlBasedSiteLookup.php
+++ b/src/MediaWiki/HostBasedSiteTableLookup.php
@@ -1,11 +1,11 @@
<?php
-namespace FileImporter;
+namespace FileImporter\MediaWiki;
use Site;
use SiteLookup;
-class UrlBasedSiteLookup {
+class HostBasedSiteTableLookup {
/**
* @var SiteLookup
@@ -17,17 +17,15 @@
}
/**
- * @param string[] $parsedUrl result of wfParseUrl
+ * @param string $host e.g. en.wikipedia.org or commons.wikimedia.org
*
* @return Site|null
*/
- public function getSite( array $parsedUrl ) {
- $requestedDomain = $parsedUrl['host'];
-
+ public function getSite( $host ) {
/** @var Site[] $sites */
$sites = $this->siteLookup->getSites();
foreach ( $sites as $site ) {
- if ( $site->getDomain() === $requestedDomain ) {
+ if ( $site->getDomain() === $host ) {
return $site;
}
}
diff --git a/src/MediaWiki/MediaWikiImporter.php
b/src/MediaWiki/MediaWikiImporter.php
new file mode 100644
index 0000000..e3ffda4
--- /dev/null
+++ b/src/MediaWiki/MediaWikiImporter.php
@@ -0,0 +1,61 @@
+<?php
+
+namespace FileImporter\MediaWiki;
+
+use FileImporter\Generic\ImportAdjustments;
+use FileImporter\Generic\ImportDetails;
+use FileImporter\Generic\Importer;
+use FileImporter\Generic\TargetUrl;
+use MediaWiki\MediaWikiServices;
+use NaiveForeignTitleFactory;
+
+class MediaWikiImporter implements Importer {
+
+ /**
+ * @param TargetUrl $targetUrl
+ *
+ * @return bool
+ */
+ public function canImport( TargetUrl $targetUrl ) {
+ // TODO inject
+ /** @var HostBasedSiteTableLookup $lookup */
+ $lookup = MediaWikiServices::getInstance()->getService(
'FileImporterUrlBasedSiteLookup' );
+ return $lookup->getSite( $targetUrl->getParsedUrl()['host'] )
!== null;
+ }
+
+ /**
+ * @param TargetUrl $targetUrl
+ *
+ * @return ImportDetails
+ */
+ public function getImportDetails( TargetUrl $targetUrl ) {
+
+ $parsed = $targetUrl->getParsedUrl();
+ $bits = explode( '/', $parsed['path'] );
+ $fullTitle = array_pop( $bits );
+
+ // TODO inject?
+ // TODO use NamespaceAwareForeignTitleFactory using api /db of
the foreign / local site
+ $titleFactory = new NaiveForeignTitleFactory();
+ $foreignTitle = $titleFactory->createForeignTitle( $fullTitle,
NS_FILE );
+
+ return new ImportDetails(
+ $targetUrl,
+ $foreignTitle->getText(),
+ // TODO actually get real file url
+
'https://upload.wikimedia.org/wikipedia/commons/5/52/Berlin_Montage_4.jpg'
+ );
+ }
+
+ /**
+ * @param TargetUrl $targetUrl
+ * @param ImportAdjustments $importAdjustments
+ *
+ * @return bool success
+ */
+ public function import( TargetUrl $targetUrl, ImportAdjustments
$importAdjustments ) {
+ // TODO implement
+ return false;
+ }
+
+}
diff --git a/src/ServiceWiring.php b/src/ServiceWiring.php
index 8f60d82..cd34dff 100644
--- a/src/ServiceWiring.php
+++ b/src/ServiceWiring.php
@@ -2,11 +2,31 @@
namespace FileImporter;
+use FileImporter\Generic\DispatchingImporter;
+use FileImporter\MediaWiki\MediaWikiImporter;
+use FileImporter\MediaWiki\HostBasedSiteTableLookup;
use MediaWiki\MediaWikiServices;
use Psr\Log\LoggerInterface;
return [
+
'FileImporterUrlBasedSiteLookup' => function( MediaWikiServices
$services ) {
- return new UrlBasedSiteLookup( $services->getSiteLookup() );
+ return new HostBasedSiteTableLookup( $services->getSiteLookup()
);
},
+
+ 'FileImporterDispatchingImporter' => function( MediaWikiServices
$services ) {
+ $config = $services->getMainConfig();
+
+ $importers = [];
+ foreach ( $config->get( 'FileImporterImporterServices' ) as
$serviceName ) {
+ $importers[] = $services->getService( $serviceName );
+ }
+
+ return new DispatchingImporter( $importers );
+ },
+
+ 'FileImporterMediaWikiImporter' => function( MediaWikiServices
$services ) {
+ return new MediaWikiImporter();
+ }
+
];
diff --git a/src/SpecialImportFile.php b/src/SpecialImportFile.php
index 2fa5fd7..0a03d2d 100644
--- a/src/SpecialImportFile.php
+++ b/src/SpecialImportFile.php
@@ -2,6 +2,8 @@
namespace FileImporter;
+use FileImporter\Generic\Importer;
+use FileImporter\Generic\TargetUrl;
use Html;
use Linker;
use MediaWiki\MediaWikiServices;
@@ -21,44 +23,37 @@
$out->setPageTitle( new Message( 'fileimporter-specialpage' ) );
$out->enableOOUI();
- $rawUrl = $out->getRequest()->getVal( 'clientUrl', '' );
+ $targetUrl = new TargetUrl( $out->getRequest()->getVal(
'clientUrl', '' ) );
$wasPosted = $out->getRequest()->wasPosted();
- if ( !$rawUrl ) {
+ if ( !$targetUrl->getUrl() ) {
$this->showUrlEntryPage();
return;
}
- $parsedUrl = wfParseUrl( $rawUrl );
- if ( $parsedUrl === false ) {
- $this->showUnparsableUrlMessage( $rawUrl );
+ // TODO inject!
+ /** @var Importer $importer */
+ $importer = MediaWikiServices::getInstance()
+ ->getService( 'FileImporterDispatchingImporter' );
+
+ if ( !$targetUrl->isParsable() ) {
+ $this->showUnparsableUrlMessage( $targetUrl->getUrl() );
$this->showUrlEntryPage();
- } elseif ( !$this->urlAllowed( $parsedUrl ) ) {
+ } elseif ( !$importer->canImport( $targetUrl ) ) {
$this->showDisallowedUrlMessage();
$this->showUrlEntryPage();
} else {
if ( $wasPosted ) {
- $this->doImport();
+ $this->doImport( $importer, $targetUrl );
} else {
- $this->showImportPage( $rawUrl );
+ $this->showImportPage( $importer, $targetUrl );
}
}
}
- private function doImport() {
+ private function doImport( Importer $importer, TargetUrl $targetUrl ) {
// TODO implement importing
$this->getOutput()->addHTML( 'TODO do the import' );
- }
-
- /**
- * @param string[] $parsedUrl return of wfParseUrl
- *
- * @return bool
- */
- private function urlAllowed( array $parsedUrl ) {
- /** @var UrlBasedSiteLookup $lookup */
- $lookup = MediaWikiServices::getInstance()->getService(
'FileImporterUrlBasedSiteLookup' );
- return $lookup->getSite( $parsedUrl ) !== null;
}
private function showUnparsableUrlMessage( $rawUrl ) {
@@ -85,31 +80,38 @@
$this->showInputForm();
}
- /**
- * @param string $rawUrl
- */
- private function showImportPage( $rawUrl ) {
- // TODO actually make the correct file?
- $file = new ExternalMediaWikiFile();
+ private function showImportPage( Importer $importer, TargetUrl $target
) {
+ $importDetails = $importer->getImportDetails( $target );
$out = $this->getOutput();
$this->getOutput()->addModuleStyles( 'ext.FileImporter.Special'
);
- $this->showInputForm( $file->getTargetUrl() );
+ $this->showInputForm( $importDetails->getTargetUrl() );
$out->addHTML(
Html::rawElement(
'p',
[],
( new Message(
'fileimporter-importfilefromprefix' ) )->plain() . ': ' .
- Linker::makeExternalLink(
$file->getTargetUrl(), $file->getTargetUrl() )
+ Linker::makeExternalLink(
+
$importDetails->getTargetUrl()->getUrl(),
+ $importDetails->getTargetUrl()->getUrl()
+ )
)
);
- $out->addHTML( Html::element( 'p', [], $file->getTitle() ) );
- $out->addHTML( Linker::makeExternalImage( $file->getImageUrl(),
$file->getTitle() ) );
+ $out->addHTML( Html::element( 'p', [],
$importDetails->getTitleText() ) );
+ $out->addHTML(
+ Linker::makeExternalImage(
+ $importDetails->getImageDisplayUrl(),
+ $importDetails->getTitleText()
+ )
+ );
}
- private function showInputForm( $clientUrl = null ) {
+ /**
+ * @param TargetUrl|null $targetUrl
+ */
+ private function showInputForm( $targetUrl = null ) {
$out = $this->getOutput();
$out->addHTML(
@@ -126,7 +128,7 @@
'autofocus' => true,
'required' => true,
'type' => 'url',
- 'value' => $clientUrl ? $clientUrl : '',
+ 'value' => $targetUrl ?
$targetUrl->getUrl() : '',
'placeholder' => ( new Message(
'fileimporter-exampleprefix' ) )->plain() .
':
https://en.wikipedia.org/wiki/File:Berlin_Skyline'
]
@@ -140,14 +142,14 @@
) . Html::closeElement( 'form' )
);
- if ( $clientUrl ) {
- $this->showImportForm( $clientUrl );
+ if ( $targetUrl ) {
+ $this->showImportForm( $targetUrl );
}
$out->addHTML( Html::closeElement( 'div' ) );
}
- private function showImportForm( $clientUrl ) {
+ private function showImportForm( TargetUrl $targetUrl ) {
$this->getOutput()->addHTML(
Html::openElement(
'form',
@@ -160,7 +162,7 @@
[
'type' => 'hidden',
'name' => 'clientUrl',
- 'value' => $clientUrl,
+ 'value' => $targetUrl->getUrl(),
]
). new ButtonInputWidget(
[
diff --git a/tests/Generic/TargetUrlTest.php b/tests/Generic/TargetUrlTest.php
new file mode 100644
index 0000000..179d206
--- /dev/null
+++ b/tests/Generic/TargetUrlTest.php
@@ -0,0 +1,40 @@
+<?php
+
+namespace FileImporter\Generic\Test;
+
+use FileImporter\Generic\TargetUrl;
+use PHPUnit_Framework_TestCase;
+
+class TargetUrlTest extends PHPUnit_Framework_TestCase {
+
+ public function provideConstruction() {
+ return [
+ [
+ 'foooooooo',
+ false,
+ false
+ ],
+ [
+ 'https://en.wikipedia.org/wiki/File:Foo.jpg',
+ true,
+ [
+ 'scheme' => 'https',
+ 'host' => 'en.wikipedia.org',
+ 'delimiter' => '://',
+ 'path' => '/wiki/File:Foo.jpg',
+ ],
+ ],
+ ];
+ }
+
+ /**
+ * @dataProvider provideConstruction
+ */
+ public function testConstruction( $url, $expectedIsParsable,
$expectedParsed ) {
+ $targetUrl = new TargetUrl( $url );
+ $this->assertEquals( $url, $targetUrl->getUrl() );
+ $this->assertEquals( $expectedParsed,
$targetUrl->getParsedUrl() );
+ $this->assertEquals( $expectedIsParsable,
$targetUrl->isParsable() );
+ }
+
+}
diff --git a/tests/UrlBasedSiteLookupTest.php
b/tests/MediaWiki/HostBasedSiteTableLookupTest.php
similarity index 61%
rename from tests/UrlBasedSiteLookupTest.php
rename to tests/MediaWiki/HostBasedSiteTableLookupTest.php
index 88e701b..0ad1cf2 100644
--- a/tests/UrlBasedSiteLookupTest.php
+++ b/tests/MediaWiki/HostBasedSiteTableLookupTest.php
@@ -1,14 +1,14 @@
<?php
-namespace FileImporter\Test;
+namespace FileImporter\MediaWiki\Test;
-use FileImporter\UrlBasedSiteLookup;
+use FileImporter\MediaWiki\HostBasedSiteTableLookup;
use HashSiteStore;
use MediaWikiSite;
use PHPUnit_Framework_TestCase;
use Site;
-class UrlBasedSiteLookupTest extends PHPUnit_Framework_TestCase {
+class HostBasedSiteTableLookupTest extends PHPUnit_Framework_TestCase {
private function getSite( $globalId, $domain ) {
$mockSite = $this->getMock( Site::class );
@@ -26,29 +26,27 @@
public function provideGetSite() {
return [
- [ 'http://google.com' ],
- 'commons' => [
'http://commons.wikimedia.org/wiki/File:Foo', 'commonswiki' ],
- 'enwiki http' => [
'http://en.wikipedia.org/wiki/File:Foo', 'enwiki' ],
- 'enwiki https' => [
'https://en.wikipedia.org/wiki/File:Foo', 'enwiki' ],
- 'dewiki index.php' => [
-
'https://de.wikipedia.org/w/index.php?title=File:Foo', 'dewiki',
- ],
+ 'google' => [ 'google.com' ],
+ 'commons' => [ 'commons.wikimedia.org', 'commonswiki' ],
+ 'enwiki http' => [ 'en.wikipedia.org', 'enwiki' ],
+ 'enwiki https' => [ 'en.wikipedia.org', 'enwiki' ],
+ 'dewiki index.php' => [ 'de.wikipedia.org', 'dewiki', ],
];
}
/**
* @dataProvider provideGetSite
*/
- public function testGetSite( $url, $expected = null ) {
+ public function testGetSite( $host, $expected = null ) {
$hashSiteStore = new HashSiteStore( [
$this->getSite( 'enwiki', 'en.wikipedia.org' ),
$this->getSite( 'dewiki', 'de.wikipedia.org' ),
$this->getSite( 'commonswiki', 'commons.wikimedia.org'
),
] );
- $store = new UrlBasedSiteLookup( $hashSiteStore );
+ $lookup = new HostBasedSiteTableLookup( $hashSiteStore );
- $result = $store->getSite( wfParseUrl( $url ) );
+ $result = $lookup->getSite( $host );
if ( $expected === null ) {
$this->assertEquals( null, $result );
} else {
--
To view, visit https://gerrit.wikimedia.org/r/337227
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I26d6166ca1e8e4e155dae3f1a4ecbf4ddfd6fbd6
Gerrit-PatchSet: 13
Gerrit-Project: mediawiki/extensions/FileImporter
Gerrit-Branch: master
Gerrit-Owner: Addshore <[email protected]>
Gerrit-Reviewer: Addshore <[email protected]>
Gerrit-Reviewer: Andrew-WMDE <[email protected]>
Gerrit-Reviewer: Tobias Gritschacher <[email protected]>
Gerrit-Reviewer: WMDE-Fisch <[email protected]>
Gerrit-Reviewer: jenkins-bot <>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits