jenkins-bot has submitted this change and it was merged.
Change subject: \SMW\ContentParser use ContentHandler where available
......................................................................
\SMW\ContentParser use ContentHandler where available
Change-Id: I64533d46a46d0f62d1f58ec056410c4403afea57
---
M includes/ContentParser.php
M includes/FactboxCache.php
M includes/dic/SharedDependencyContainer.php
M tests/phpunit/MockObjectRepository.php
M tests/phpunit/includes/ContentParserTest.php
M tests/phpunit/includes/dic/SharedDependencyContainerTest.php
M tests/phpunit/includes/parserhooks/ParserFunctionIntegrationTest.php
7 files changed, 418 insertions(+), 196 deletions(-)
Approvals:
Mwjames: Looks good to me, approved
jenkins-bot: Verified
diff --git a/includes/ContentParser.php b/includes/ContentParser.php
index 936c278..f16b7d1 100644
--- a/includes/ContentParser.php
+++ b/includes/ContentParser.php
@@ -4,68 +4,56 @@
use ParserOptions;
use Revision;
-use User;
+use Parser;
use Title;
+use User;
/**
- * Fetch page content
+ * Parse page content and generating a ParserOutput object
*
- * @file
+ * Fetches the ParserOutput either by parsing an invoked text component,
+ * re-parsing a text revision, or accessing the ContentHandler to generate a
+ * ParserOutput object
*
- * @license GNU GPL v2+
- * @since 1.9
+ * @ingroup SMW
+ *
+ * @licence GNU GPL v2+
+ * @since 1.9
*
* @author mwjames
- */
-
-/**
- * Fetch page content either by parsing an invoked text compenent, reparsing
- * a text revision, or accessing the ContentHandler to produce a ParserOutput
- * object
- *
- * @ingroup Utility
*/
class ContentParser {
/** @var Title */
protected $title;
- /** @var ParserOutput */
- protected $parserOutput = null;
-
- /** @var Revision */
- protected $revision = null;
-
- /** @var ParserOptions */
- protected $parserOptions = null;
-
/** @var Parser */
protected $parser = null;
- /** @var string */
- protected $text = null;
+ /** @var ParserOutput */
+ protected $parserOutput = null;
/** @var array */
protected $errors = array();
/**
+ * @note Injecting new Parser() alone will not yield an expected result
and
+ * doing new Parser( $GLOBALS['wgParserConf'] brings no benefits
therefore
+ * we stick to the GLOBAL as fallback if no parser is injected.
+ *
* @since 1.9
*
* @param Title $title
+ * @param Parser|null $parser
*/
- public function __construct( Title $title ) {
- $this->title = $title;
- }
+ public function __construct( Title $title, Parser $parser = null ) {
+ $this->title = $title;
+ $this->parser = $parser;
- /**
- * Returns collected errors occurred during processing
- *
- * @since 1.9
- *
- * @return array
- */
- public function getErrors() {
- return $this->errors;
+ if ( $this->parser === null ) {
+ $this->parser = $GLOBALS['wgParser'];
+ }
+
}
/**
@@ -75,7 +63,7 @@
*
* @return Title
*/
- public function getTitel() {
+ public function getTitle() {
return $this->title;
}
@@ -91,34 +79,33 @@
}
/**
- * Invokes text to be parsed
+ * Returns collected errors occurred during processing
*
* @since 1.9
*
- * @return ContentParser|null
+ * @return array
*/
- public function setText( $text ) {
- $this->text = $text;
- return $this;
+ public function getErrors() {
+ return $this->errors;
}
/**
- * Generates or fetches output content from the appropriate source
+ * Generates or fetches the ParserOutput object from an appropriate
source
*
* @since 1.9
*
+ * @param string|null $text
+ *
* @return ContentParser
*/
- public function parse() {
+ public function parse( $text = null ) {
- if ( $this->text !== null ) {
- $this->parseText();
+ if ( $text !== null ) {
+ $this->parseText( $text );
+ } elseif ( $this->hasContentHandler() ) {
+ $this->fetchFromContent( $this->newRevision() );
} else {
-
- // if ( class_exists( 'ContentHandler') ) {
- // $this->fetchFromContentHandler(); // Add unit
test
- // } else {}
- $this->fetchFromParser();
+ $this->fetchFromParser( $this->newRevision() );
}
return $this;
@@ -129,10 +116,14 @@
*
* @since 1.9
*/
- protected function parseText() {
+ protected function parseText( $text ) {
Profiler::In( __METHOD__ );
- $this->parserOutput = $this->getParser()->parse( $this->text,
$this->getTitel(), $this->getParserOptions() );
+ $this->parserOutput = $this->parser->parse(
+ $text,
+ $this->getTitle(),
+ $this->newParserOptions()
+ );
Profiler::Out( __METHOD__ );
}
@@ -140,103 +131,109 @@
/**
* Using the content handler to return a ParserOutput object
*
- * @since 1.9
- */
- protected function fetchFromContentHandler() {
- Profiler::In( __METHOD__ );
-
- $content = $this->getRevision()->getContent( Revision::RAW );
-
- if ( !$content ) {
- // if there is no content, pretend the content is empty
- $content =
$this->getRevision()->getContentHandler()->makeEmptyContent();
- }
-
- // Revision ID must be passed to the parser output to get
revision variables correct
- $this->parserOutput = $content->getParserOutput(
$this->getTitle(), $this->getRevision()->getId(), null, false );
-
- Profiler::Out( __METHOD__ );
- }
-
- /**
- * Reparsing page content from a revision
+ * @note Revision ID must be passed to the parser output to
+ * get revision variables correct
+ *
+ * @note If no content is available create an empty object
*
* @since 1.9
*/
- protected function fetchFromParser() {
+ protected function fetchFromContent( $revision ) {
Profiler::In( __METHOD__ );
- if ( $this->getRevision() !== null ) {
- $this->parserOutput = $this->getParser()->parse(
- $this->getRevision()->getText(),
- $this->getTitel(),
- $this->getParserOptions(),
- true,
- true,
- $this->getRevision()->getID()
+ if ( $revision !== null ) {
+
+ $content = $revision->getContent( Revision::RAW );
+
+ if ( !$content ) {
+ $content =
$revision->getContentHandler()->makeEmptyContent();
+ }
+
+ $this->parserOutput = $content->getParserOutput(
+ $this->getTitle(),
+ $revision->getId(),
+ null,
+ true
);
+
} else {
- $this->errors = array( __METHOD__ . " No revision
available for {$this->getTitel()->getPrefixedDBkey()}" );
+ $this->errors = array( __METHOD__ . " No revision
available for {$this->getTitle()->getPrefixedDBkey()}" );
}
Profiler::Out( __METHOD__ );
}
/**
- * Returns ParserOptions object
+ * Re-parsing page content from a revision
*
* @since 1.9
+ */
+ protected function fetchFromParser( $revision ) {
+ Profiler::In( __METHOD__ );
+
+ if ( $revision !== null ) {
+
+ $this->parserOutput = $this->parser->parse(
+ $revision->getText(),
+ $this->getTitle(),
+ $this->newParserOptions( $revision ),
+ true,
+ true,
+ $revision->getID()
+ );
+
+ } else {
+ $this->errors = array( __METHOD__ . " No revision
available for {$this->getTitle()->getPrefixedDBkey()}" );
+ }
+
+ Profiler::Out( __METHOD__ );
+ }
+
+ /**
+ * @note ContentHandler does not exist prior MW 1.21
+ *
+ * @since 1.9
+ *
+ * @return boolean
+ */
+ protected function hasContentHandler() {
+ return class_exists( 'ContentHandler');
+ }
+
+ /**
+ * @since 1.9
*
* @return ParserOptions
*/
- protected function getParserOptions() {
+ protected function newParserOptions( $revision = null ) {
- if ( $this->parserOptions === null ) {
- if ( $this->revision === null ) {
- $this->parserOptions = new ParserOptions();
- } else {
- $this->parserOptions = new ParserOptions(
User::newFromId( $this->revision->getUser() ) );
- }
+ $user = null;
+
+ if ( $revision !== null ) {
+ $user = User::newFromId( $revision->getUser() );
}
- return $this->parserOptions;
+ return new ParserOptions( $user );
}
/**
- * Returns Revision object
+ * @note Revision::READ_NORMAL is not defined in MW 1.19
*
- * @note Revision::READ_NORMAL does not exists in MW 1.19
- *
- * @since 1.9
+ * @since 1.9
*
* @return Revision
*/
- protected function getRevision() {
+ protected function newRevision() {
- if ( $this->revision === null ) {
- if ( class_exists( 'ContentHandler') ) {
- $this->revision = Revision::newFromTitle(
$this->getTitel(), false, Revision::READ_NORMAL );
- } else{
- $this->revision = Revision::newFromTitle(
$this->getTitel() );
- }
+ $revision = null;
+
+ if ( defined( 'Revision::READ_NORMAL' ) ) {
+ $revision = Revision::newFromTitle( $this->getTitle(),
false, Revision::READ_NORMAL );
+ } else {
+ $revision = Revision::newFromTitle( $this->getTitle() );
}
- return $this->revision;
+ return $revision;
}
- /**
- * Returns Parser object
- *
- * @since 1.9
- *
- * @return Parser
- */
- protected function getParser() {
-
- if ( $this->parser === null ) {
- $this->parser = $GLOBALS['wgParser'];
- }
-
- return $this->parser;
- }
}
diff --git a/includes/FactboxCache.php b/includes/FactboxCache.php
index 3d939dd..10e306b 100644
--- a/includes/FactboxCache.php
+++ b/includes/FactboxCache.php
@@ -206,8 +206,7 @@
'Title' => $this->outputPage->getTitle()
) );
- $contentParser->setText( $factbox->getContent()
)->parse(); // old style
- // $contentParser->parse( $factbox->getContent() );
+ $contentParser->parse( $factbox->getContent() );
$text = $contentParser->getOutput()->getText();
}
diff --git a/includes/dic/SharedDependencyContainer.php
b/includes/dic/SharedDependencyContainer.php
index 507b094..05c928a 100644
--- a/includes/dic/SharedDependencyContainer.php
+++ b/includes/dic/SharedDependencyContainer.php
@@ -94,13 +94,27 @@
);
},
+ /**
+ * ContentParser object definition
+ *
+ * @since 1.9
+ *
+ * @return ContentParser
+ */
'ContentParser' => function ( DependencyBuilder
$builder ) {
- return new ContentParser(
$builder->getArgument( 'Title' ) );
- },
+ return new ContentParser(
$builder->getArgument( 'Title' ) );
+ },
+ /**
+ * ObservableSubjectDispatcher object definition
+ *
+ * @since 1.9
+ *
+ * @return ObservableSubjectDispatcher
+ */
'ObservableUpdateDispatcher' => function (
DependencyBuilder $builder ) {
- return new ObservableSubjectDispatcher(
$builder->newObject( 'UpdateObserver' ) );
- },
+ return new ObservableSubjectDispatcher(
$builder->newObject( 'UpdateObserver' ) );
+ },
'Factbox' => function ( DependencyBuilder $builder ) {
return new Factbox(
diff --git a/tests/phpunit/MockObjectRepository.php
b/tests/phpunit/MockObjectRepository.php
index 2a4f96a..6288627 100644
--- a/tests/phpunit/MockObjectRepository.php
+++ b/tests/phpunit/MockObjectRepository.php
@@ -375,6 +375,10 @@
->method( 'getContent' )
->will( $this->returnValue( $this->builder->setValue(
'getContent' ) ) );
+ $revision->expects( $this->any() )
+ ->method( 'getContentHandler' )
+ ->will( $this->returnValue( $this->builder->setValue(
'getContentHandler' ) ) );
+
return $revision;
}
diff --git a/tests/phpunit/includes/ContentParserTest.php
b/tests/phpunit/includes/ContentParserTest.php
index 05795dc..239d321 100644
--- a/tests/phpunit/includes/ContentParserTest.php
+++ b/tests/phpunit/includes/ContentParserTest.php
@@ -4,18 +4,12 @@
use SMW\ContentParser;
+use TextContentHandler;
+use ContentHandler;
+use TextContent;
+use Revision;
use Title;
-
-/**
- * Tests for the ContentParser class
- *
- * @file
- *
- * @license GNU GPL v2+
- * @since 1.9
- *
- * @author mwjames
- */
+use Parser;
/**
* @covers \SMW\ContentParser
@@ -24,12 +18,15 @@
*
* @group SMW
* @group SMWExtension
+ *
+ * @license GNU GPL v2+
+ * @since 1.9
+ *
+ * @author mwjames
*/
class ContentParserTest extends ParserTestCase {
/**
- * Returns the name of the class to be tested
- *
* @return string|false
*/
public function getClass() {
@@ -37,79 +34,158 @@
}
/**
- * Helper method that returns a ContentParser object
- *
* @since 1.9
*
* @return ContentParser
*/
- private function getInstance( Title $title = null ) {
- return new ContentParser( $title === null ? $this->newTitle() :
$title );
+ private function newInstance( Title $title = null, $parser = null ) {
+
+ if ( $title === null ) {
+ $title = $this->newTitle();
+ }
+
+ return new ContentParser( $title, $parser );
}
/**
- * @test ContentParser::__construct
- *
* @since 1.9
*/
public function testConstructor() {
- $this->assertInstanceOf( $this->getClass(),
$this->getInstance() );
+ $this->assertInstanceOf( $this->getClass(),
$this->newInstance() );
}
/**
- * @test ContentParser::parse
- *
+ * @since 1.9
+ */
+ public function testParse() {
+ $this->assertInstanceOf( $this->getClass(),
$this->newInstance()->parse() );
+ }
+
+ /**
* @since 1.9
*/
public function testParseFromText() {
- $text = $this->newRandomString( 20, __METHOD__ );
- $expected ='<p>' . $text . "\n" . '</p>';
+ $text = 'Foo-1-' . __METHOD__;
+ $expected = '<p>' . $text . "\n" . '</p>';
- $instance = $this->getInstance();
- $instance->setText( $text )->parse();
+ $instance = $this->newInstance();
+ $instance->parse( $text );
- $this->assertInstanceOf( 'ParserOutput', $instance->getOutput()
);
- $this->assertEquals( $expected,
$instance->getOutput()->getText() );
+ $this->assertParserOutput( $expected, $instance );
}
/**
- * @test ContentParser::parse
* @dataProvider titleRevisionDataProvider
*
* @since 1.9
*/
- public function testParseFromRevision( $setup, $expected ) {
+ public function testFetchFromParser( $setup, $expected ) {
+ $instance = $this->newInstance( $setup['title'], new Parser()
);
$reflector = $this->newReflector();
- $instance = $this->getInstance( $setup['title'] );
- $revision = $reflector->getProperty( 'revision' );
- $revision->setAccessible( true );
- $revision->setValue( $instance, $setup['revision'] );
+ $fetchFromParser = $reflector->getMethod( 'fetchFromParser' );
+ $fetchFromParser->setAccessible( true );
- $instance->parse();
+ $fetchFromParser->invoke( $instance, $setup['revision'] );
if ( $expected['error'] ) {
- $this->assertInternalType( 'array',
$instance->getErrors() );
+ $this->assertError( $instance );
} else {
- $this->assertInstanceOf( 'ParserOutput',
$instance->getOutput() );
- $this->assertEquals( $expected['text'],
$instance->getOutput()->getText() );
+ $this->assertParserOutput( $expected['text'], $instance
);
}
}
/**
- * Provides title and wikiPage samples
+ * @dataProvider contentDataProvider
*
+ * @since 1.9
+ */
+ public function testFetchFromContent( $setup, $expected ) {
+
+ if ( !class_exists( 'ContentHandler') ) {
+ $this->markTestSkipped(
+ 'Skipping test due to a missing class (probably
MW 1.20 or lower).'
+ );
+ }
+
+ $instance = $this->newInstance( $setup['title'], new Parser()
);
+ $reflector = $this->newReflector();
+
+ $fetchFromContent = $reflector->getMethod( 'fetchFromContent' );
+ $fetchFromContent->setAccessible( true );
+
+ $fetchFromContent->invoke( $instance, $setup['revision'] );
+
+ $this->assertEquals( $setup['title'], $instance->getTitle() );
+
+ if ( $expected['error'] ) {
+ $this->assertError( $instance );
+ } else {
+ $this->assertParserOutput( $expected['text'], $instance
);
+ }
+
+ }
+
+ /**
+ * @since 1.9
+ */
+ public function assertError( $instance ) {
+
+ $this->assertInternalType(
+ 'array',
+ $instance->getErrors(),
+ 'Asserts that getErrors() returns an array'
+ );
+
+ $this->assertNotEmpty(
+ $instance->getErrors(),
+ 'Asserts that getErrors() is not empty'
+ );
+
+ }
+
+ /**
+ * @since 1.9
+ */
+ public function assertParserOutput( $text, $instance ) {
+
+ $this->assertInstanceOf(
+ 'ParserOutput',
+ $instance->getOutput(),
+ 'Asserts the expected ParserOutput instance'
+ );
+
+ if ( $text !== '' ) {
+
+ $this->assertContains(
+ $text,
+ $instance->getOutput()->getText(),
+ 'Asserts that getText() returns expected text
component'
+ );
+
+ } else {
+
+ $this->assertEmpty(
+ $instance->getOutput()->getText(),
+ 'Asserts that getText() returns empty'
+ );
+
+ }
+
+ }
+
+ /**
* @return array
*/
public function titleRevisionDataProvider() {
$provider = array();
- $text = $this->newRandomString( 20, __METHOD__ );
+ $text = 'Foo-2-' . __METHOD__;
$expected ='<p>' . $text . "\n" . '</p>';
// #0 Title does not exists
@@ -155,6 +231,142 @@
)
);
+ // #2 Null revision
+ $title = $this->newMockBuilder()->newObject( 'Title', array(
+ 'getDBkey' => 'Lula',
+ 'exists' => true,
+ 'getPageLanguage' => $this->getLanguage()
+ ) );
+
+ $revision = null;
+
+ $provider[] = array(
+ array(
+ 'title' => $title,
+ 'revision' => $revision,
+ ),
+ array(
+ 'error' => true,
+ 'text' => ''
+ )
+ );
+
+ return $provider;
+ }
+
+ /**
+ * @return array
+ */
+ public function contentDataProvider() {
+
+ $provider = array();
+
+ if ( !class_exists( 'ContentHandler') ) {
+ $provider[] = array( array(), array() );
+ return $provider;
+ }
+
+ $text = 'Foo-3-' . __METHOD__;
+
+ // #0 Title does not exists
+ $title = $this->newMockBuilder()->newObject( 'Title', array(
+ 'getDBkey' => 'Lila',
+ 'exists' => false,
+ 'getText' => null,
+ 'getPageLanguage' => $this->getLanguage()
+ ) );
+
+ $provider[] = array(
+ array(
+ 'title' => $title,
+ 'revision' => null,
+ ),
+ array(
+ 'error' => true,
+ 'text' => ''
+ )
+ );
+
+ // #1 Valid revision
+ $title = $this->newMockBuilder()->newObject( 'Title', array(
+ 'getDBkey' => 'Lula',
+ 'exists' => true,
+ 'getPageLanguage' => $this->getLanguage()
+ ) );
+
+ $revision = $this->newMockBuilder()->newObject( 'Revision',
array(
+ 'getId' => 9001,
+ 'getUser' => 'Lala',
+ 'getContent' => new TextContent( $text )
+ ) );
+
+ $provider[] = array(
+ array(
+ 'title' => $title,
+ 'revision' => $revision,
+ ),
+ array(
+ 'error' => false,
+ 'text' => $text
+ )
+ );
+
+ // #1 Empty content
+ $title = $this->newMockBuilder()->newObject( 'Title', array(
+ 'getDBkey' => 'Lula',
+ 'exists' => true,
+ 'getPageLanguage' => $this->getLanguage(),
+ 'getContentModel' => CONTENT_MODEL_WIKITEXT
+ ) );
+
+ $revision = $this->newMockBuilder()->newObject( 'Revision',
array(
+ 'getId' => 9001,
+ 'getUser' => 'Lala',
+ 'getContent' => false,
+ 'getContentHandler' => new TextContentHandler()
+ ) );
+
+ $provider[] = array(
+ array(
+ 'title' => $title,
+ 'revision' => $revision,
+ ),
+ array(
+ 'error' => false,
+ 'text' => ''
+ )
+ );
+
+ // #2 "real" revision and content
+ $title = $this->newTitle();
+ $content = ContentHandler::makeContent( $text, $title,
CONTENT_MODEL_WIKITEXT, null );
+
+ $revision = new Revision(
+ array(
+ 'id' => 42,
+ 'page' => 23,
+ 'title' => $title,
+
+ 'content' => $content,
+ 'length' => $content->getSize(),
+ 'comment' => "testing",
+ 'minor_edit' => false,
+
+ 'content_format' => null,
+ )
+ );
+
+ $provider[] = array(
+ array(
+ 'title' => $title,
+ 'revision' => $revision,
+ ),
+ array(
+ 'error' => false,
+ 'text' => $text
+ )
+ );
+
return $provider;
}
diff --git a/tests/phpunit/includes/dic/SharedDependencyContainerTest.php
b/tests/phpunit/includes/dic/SharedDependencyContainerTest.php
index 401c040..4755abc 100644
--- a/tests/phpunit/includes/dic/SharedDependencyContainerTest.php
+++ b/tests/phpunit/includes/dic/SharedDependencyContainerTest.php
@@ -56,11 +56,19 @@
$instance->addArgument( $name, $object );
}
- $this->assertInstanceOf(
- $objectInstance,
- $instance->newObject( $objectName ),
- 'asserts that the DiObject was able to create
an instance'
- );
+ $newInstance = $instance->newObject( $objectName );
+
+ if ( $newInstance !== null ) {
+
+ $this->assertInstanceOf(
+ $objectInstance,
+ $newInstance,
+ 'Asserts that newObject() was able to
create an object instance'
+ );
+
+ }
+
+ $this->assertTrue( true );
}
}
diff --git
a/tests/phpunit/includes/parserhooks/ParserFunctionIntegrationTest.php
b/tests/phpunit/includes/parserhooks/ParserFunctionIntegrationTest.php
index 189aa49..2c841fd 100644
--- a/tests/phpunit/includes/parserhooks/ParserFunctionIntegrationTest.php
+++ b/tests/phpunit/includes/parserhooks/ParserFunctionIntegrationTest.php
@@ -7,31 +7,21 @@
use Title;
/**
- * Integration tests for registered ParserFunction
+ * @covers \SMW\ContentParser
+ * @covers \SMW\AskParserFunction
+ * @covers \SMW\ShowParserFunction
*
- * @file
+ * @group SMW
+ * @group SMWExtension
*
* @license GNU GPL v2+
* @since 1.9
*
* @author mwjames
*/
-
-/**
- * @covers \SMW\ContentParser
- * @covers \SMW\AskParserFunction
- * @covers \SMW\ShowParserFunction
- *
- * @ingroup Test
- *
- * @group SMW
- * @group SMWExtension
- */
class ParserFunctionIntegrationTest extends SemanticMediaWikiTestCase {
/**
- * Returns the name of the class to be tested
- *
* @return string|false
*/
public function getClass() {
@@ -39,13 +29,11 @@
}
/**
- * Helper method that returns a ContentParser object
- *
* @since 1.9
*
* @return ContentParser
*/
- private function newInstance( Title $title = null ) {
+ private function newContentParser( Title $title = null, $context = null
) {
if( $title === null ) {
$title = $this->newTitle();
@@ -55,28 +43,28 @@
}
/**
+ * @dataProvider textDataProvider
+ *
* Check that registered parser functions (especially those as
closures) are
* generally executable during parsing of a standard text
- *
- * @test ContentParser::parse
- * @dataProvider textDataProvider
*
* @since 1.9
*/
public function testParseFromText( $text ) {
- $instance = $this->newInstance();
- $instance->setText( $text )->parse();
+ $instance = $this->newContentParser();
+ $instance->parse( $text );
$this->assertInstanceOf(
- 'ParserOutput', $instance->getOutput(),
- 'asstert that a ParserOutput object is available'
+ 'ParserOutput',
+ $instance->getOutput(),
+ 'Asserts that a ParserOutput object is available'
);
$this->assertInternalType(
'string',
$instance->getOutput()->getText(),
- 'asserts that getText() is returning a string'
+ 'Asserts that getText() is returning a string'
);
}
--
To view, visit https://gerrit.wikimedia.org/r/77504
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: I64533d46a46d0f62d1f58ec056410c4403afea57
Gerrit-PatchSet: 8
Gerrit-Project: mediawiki/extensions/SemanticMediaWiki
Gerrit-Branch: master
Gerrit-Owner: Mwjames <[email protected]>
Gerrit-Reviewer: Mwjames <[email protected]>
Gerrit-Reviewer: jenkins-bot
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits