Alexander.lehmann has submitted this change and it was merged.
Change subject: Inserted XML export for individual pages and all changes.
Inserted new hooks to get all changes.
......................................................................
Inserted XML export for individual pages and all changes.
Inserted new hooks to get all changes.
Change-Id: Idcd687cd72e364d37306d7f28c1583ef8ad3ed91
---
M PubSubHubbub.body.php
M PubSubHubbub.php
M PublishChangesJob.php
M i18n/en.json
M i18n/qqq.json
A specials/SpecialPuSHExport.php
M tests/phpunit/PubSubHubbubTest.php
7 files changed, 305 insertions(+), 104 deletions(-)
Approvals:
Nik: Looks good to me, but someone else must approve
WikidataJenkins: Verified
Siebrand: Looks good to me, but someone else must approve
Alexander.lehmann: Looks good to me, approved
diff --git a/PubSubHubbub.body.php b/PubSubHubbub.body.php
index fd7c4f6..3c55b29 100644
--- a/PubSubHubbub.body.php
+++ b/PubSubHubbub.body.php
@@ -29,12 +29,9 @@
use JobQueueGroup;
use OutputPage;
use Page;
-use RawPage;
use Revision;
-use Status;
use Title;
use User;
-use Wikibase\NamespaceUtils;
use WikiPage;
/**
@@ -47,72 +44,93 @@
class PubSubHubbub {
/**
- * Hook executed after an article has been updated.
- * Creates a {@link PublishChangesJob} that publishes that change to
the defined hub and adds it to the job queue.
+ * Called when a revision was inserted due to an edit.
*
- * @see
https://www.mediawiki.org/wiki/Manual:Hooks/PageContentSaveComplete
+ * @see
https://www.mediawiki.org/wiki/Manual:Hooks/NewRevisionFromEditComplete
*
- * @param Page $article WikiPage modified.
- * @param User $user User performing the modification.
- * @param Content $content New content.
- * @param string $summary Edit summary/comment.
- * @param boolean $isMinor Whether or not the edit was marked as minor.
- * @param boolean $isWatch (No longer used.)
- * @param integer $section (No longer used.)
- * @param integer $flags Flags passed to
<code>WikiPage::doEditContent()</code>.
- * @param Revision|null $revision New Revision of the article (can be
null for edits that change nothing).
- * @param Status $status Status object about to be returned by
<code>doEditContent()</code>.
- * @param integer $baseRevId The rev ID (or false) this edit was based
on.
+ * @param Page $wikiPage The WikiPage edited.
+ * @param Revision $rev The new revision.
+ * @param int $baseID The revision ID this was based off, if any.
+ * @param User $user The editing user.
* @return bool whether to continue hook processing. This
implementation always returns <code>true</code>.
*/
- public static function onPageContentSaveComplete( Page $article, User
$user, $content, $summary, $isMinor,
- $isWatch, $section, $flags, $revision, Status $status,
$baseRevId ) {
- if ( !self::isConfigurationSetUp() ) {
- return true;
- }
- if ( !is_null( $revision ) ) {
- $jobParams = array();
- $title = $article->getTitle();
- $job = new PublishChangesJob( $title, $jobParams );
- $jobQueueGroup = JobQueueGroup::singleton()->push( $job
);
- if ( !$jobQueueGroup ) {
- wfLogWarning( "Failed to acquire a
JobQueueGroup for $job" );
- }
- }
+ public static function onNewRevisionFromEditComplete( $wikiPage,
Revision $rev, $baseID, User $user ) {
+ self::createPublishJob( $wikiPage->getTitle() );
return true;
}
/**
- * Create the resource URL for a given {@link Title}.
+ * Occurs after the delete article request has been processed
*
- * @param Title $title The {@link Title} to generate a resource URL for.
+ * @see
https://www.mediawiki.org/wiki/Manual:Hooks/ArticleDeleteComplete
+ *
+ * @param WikiPage|Article $wikiPage The article that was deleted.
WikiPage in MW >= 1.18, Article in 1.17.x and earlier.
+ * @param User $user The user that deleted the article.
+ * @param string $reason The reason the article was deleted.
+ * @param int $id Id of the article that was deleted (added in 1.13).
+ * @param Content $content The content of the deleted article (added in
1.21).
+ * @param ManualLogEntry $logEntry The log entry used to record the
deletion (added in 1.21).
+ * @return bool whether to continue hook processing. This
implementation always returns <code>true</code>.
+ */
+ public static function onArticleDeleteComplete( $wikiPage, $user,
$reason, $id, $content, $logEntry )
+ {
+ self::createPublishJob( $wikiPage->getTitle() );
+ return true;
+ }
+
+ /**
+ * Creates a {@link PublishChangesJob} that publishes that change to
the defined hub and adds it to the job queue.
+ *
+ * @param Title $title The title of the resource.
+ */
+ private static function createPublishJob( Title $title ) {
+ if ( !self::isConfigurationSetUp() ) {
+ return;
+ }
+ $jobParams = array();
+ $job = new PublishChangesJob( $title, $jobParams );
+ $jobQueueGroup = JobQueueGroup::singleton()->push( $job );
+ if ( !$jobQueueGroup ) {
+ wfLogWarning( "[PubSubHubbub] Failed to acquire a
JobQueueGroup" );
+ }
+ }
+
+ /**
+ * Create the resource URL for a given {@link Title}
+ * or the URL for all resources if {@link Title} = null.
+ *
+ * @param Title|null $title The {@link Title} to generate a resource
URL for.
* @return string the generated resource URL.
*/
- public static function getPageURL( Title $title ) {
- if ( defined( 'WB_VERSION' ) ) {
- // Wikibase extension is installed
- if ( NamespaceUtils::isEntityNamespace(
$title->getNamespace() ) ) {
- $specialTitle = Title::makeTitle( -1,
'EntityData' );
- return $specialTitle->getCanonicalURL() . "/" .
$title->getBaseText() . '.json';
- }
+ public static function getPageURL( Title $title = null ) {
+ $specialTitle = Title::makeTitle( -1, 'PuSHExport' );
+
+ if ( is_null( $title ) ) {
+ return $specialTitle->getCanonicalURL();
}
- return $title->getCanonicalURL( array(
- 'action' => 'raw'
- ) );
+ $namespace = $title->getNsText();
+ if ( $namespace != '' ) {
+ $namespace .= ':';
+ }
+
+ $titleText = $namespace . $title->getPartialURL();
+ return $specialTitle->getCanonicalURL() . "/" . $titleText;
}
/**
* Insert an HTTP Link header to the current response.
*
- * @param Article|WikiPage|RawPage $page The resource.
+ * @param Title|null $title The resource.
*/
- public static function insertLinkHeader( $page ) {
+ public static function insertLinkHeader( $title = null ) {
global $wgRequest, $wgPubSubHubbubHubURL;
if ( !self::isConfigurationSetUp() ) {
return;
}
- $wgRequest->response()->header( self::createLinkHeader(
$wgPubSubHubbubHubURL, $page ), false );
+ $wgRequest->response()->header(
+ self::createLinkHeader( $wgPubSubHubbubHubURL, $title
), false
+ );
}
/**
@@ -133,27 +151,12 @@
* Create an HTTP Link header for the given hub and resource.
*
* @param string $hubURL The URL of the hub to point to.
- * @param Article|WikiPage|RawPage $page The resource.
+ * @param Title|null $title The resource.
* @return string the HTTP Link header.
*/
- public static function createLinkHeader( $hubURL, $page ) {
+ public static function createLinkHeader( $hubURL, $title = null ) {
return "Link: <" . $hubURL . ">; rel=\"hub\", <"
- . self::getPageURL( $page->getTitle() ) . ">; rel=\"self\"";
- }
-
- /**
- * Called before displaying a page with <code>action=raw</code>.
- * Add HTTP Link headers.
- *
- * @see
https://www.mediawiki.org/wiki/Manual:Hooks/RawPageViewBeforeOutput
- *
- * @param RawPage $rawPage The {@link RawPage} object.
- * @param string $text The text that's going to be the output.
- * @return bool whether display is allowed. This implementation always
returns <code>true</code>.
- */
- public static function onRawPageViewBeforeOutput( RawPage $rawPage,
$text ) {
- self::insertLinkHeader( $rawPage );
- return true;
+ . self::getPageURL( $title ) . ">; rel=\"self\"";
}
/**
@@ -168,7 +171,7 @@
*/
public static function onOutputPageBeforeHTML( OutputPage &$out, &$text
) {
if ( $out->canUseWikiPage() ) {
- self::insertLinkHeader( $out->getWikiPage() );
+ self::insertLinkHeader( $out->getWikiPage()->getTitle()
);
}
return $out;
}
@@ -186,5 +189,4 @@
$files = array_merge( $files, glob( __DIR__ .
'/tests/phpunit/*Test.php' ) );
return true;
}
-
-}
+}
\ No newline at end of file
diff --git a/PubSubHubbub.php b/PubSubHubbub.php
index 28f3146..617f504 100644
--- a/PubSubHubbub.php
+++ b/PubSubHubbub.php
@@ -31,6 +31,8 @@
global $wgAutoloadClasses;
global $wgHooks;
global $wgJobClasses;
+global $wgSpecialPages;
+global $wgSpecialPageGroups;
$wgExtensionCredits['other'][] = array(
'path' => __FILE__,
@@ -47,11 +49,15 @@
$wgExtensionMessagesFiles['PubSubHubbub'] = $dir . 'PubSubHubbub.i18n.php';
$wgAutoloadClasses['PubSubHubbub\\PubSubHubbub'] = $dir .
'PubSubHubbub.body.php';
+$wgAutoloadClasses['PubSubHubbub\\SpecialPuSHExport'] = $dir .
'specials/SpecialPuSHExport.php';
$wgAutoloadClasses['PubSubHubbub\\PublishChangesJob'] = $dir .
'PublishChangesJob.php';
$wgAutoloadClasses['PubSubHubbub\\Publisher'] = $dir . 'lib/Publisher.php';
-$wgHooks['PageContentSaveComplete'][] =
'PubSubHubbub\\PubSubHubbub::onPageContentSaveComplete';
-$wgHooks['RawPageViewBeforeOutput'][] =
'PubSubHubbub\\PubSubHubbub::onRawPageViewBeforeOutput';
+$wgSpecialPages['PuSHExport'] = 'PubSubHubbub\\SpecialPuSHExport';
+$wgSpecialPageGroups['PuSHExport'] = 'pagetools';
+
+$wgHooks['NewRevisionFromEditComplete'][] =
'PubSubHubbub\\PubSubHubbub::onNewRevisionFromEditComplete';
$wgHooks['OutputPageBeforeHTML'][] =
'PubSubHubbub\\PubSubHubbub::onOutputPageBeforeHTML';
+$wgHooks['ArticleDeleteComplete'][] =
'PubSubHubbub\\PubSubHubbub::onArticleDeleteComplete';
$wgHooks['UnitTestsList'][] = 'PubSubHubbub\\PubSubHubbub::onUnitTestsList';
$wgJobClasses['PublishChanges'] = 'PubSubHubbub\\PublishChangesJob';
diff --git a/PublishChangesJob.php b/PublishChangesJob.php
index 118df61..9d1764d 100644
--- a/PublishChangesJob.php
+++ b/PublishChangesJob.php
@@ -24,7 +24,9 @@
namespace PubSubHubbub;
-class PublishChangesJob extends \Job {
+use Job;
+
+class PublishChangesJob extends Job {
public function __construct( $title, $params ) {
parent::__construct( 'PublishChanges', $title, $params
);
@@ -36,7 +38,7 @@
}
global $wgPubSubHubbubHubURL;
$publisher = new Publisher( $wgPubSubHubbubHubURL );
- if ( $publisher->publish_update( PubSubHubbub::getPageUrl(
$this->title ) ) ) {
+ if ( $publisher->publish_update( $this->getResourcesUrls() ) ) {
wfDebug( "[PubSubHubbub] Publishing change succeeded."
);
return true;
} else {
@@ -46,4 +48,11 @@
}
}
+ private function getResourcesUrls() {
+ return array(
+ PubSubHubbub::getPageUrl( $this->title ),
+ PubSubHubbub::getPageUrl()
+ );
+ }
+
}
diff --git a/i18n/en.json b/i18n/en.json
index e6266dc..a7088df 100644
--- a/i18n/en.json
+++ b/i18n/en.json
@@ -4,5 +4,6 @@
"BP2013N2"
]
},
- "pubsubhubbub-desc": "Publishes changes to a PubSubHubbub hub"
+ "pubsubhubbub-desc": "Publishes changes to a PubSubHubbub hub",
+ "pushexport": "PubSubHubbub export"
}
diff --git a/i18n/qqq.json b/i18n/qqq.json
index b99a3f2..bebcd44 100644
--- a/i18n/qqq.json
+++ b/i18n/qqq.json
@@ -4,5 +4,6 @@
"Raimond Spekking"
]
},
- "pubsubhubbub-desc":
"{{desc|name=PubSubHubbub|url=https://www.mediawiki.org/wiki/Extension:PubSubHubbub}}"
+ "pubsubhubbub-desc":
"{{desc|name=PubSubHubbub|url=https://www.mediawiki.org/wiki/Extension:PubSubHubbub}}",
+ "pushexport": "Page title of [[Special:PuSHExport]], a page used to
export pages for PubSubHubbub."
}
diff --git a/specials/SpecialPuSHExport.php b/specials/SpecialPuSHExport.php
new file mode 100644
index 0000000..7d48f87
--- /dev/null
+++ b/specials/SpecialPuSHExport.php
@@ -0,0 +1,197 @@
+<?php
+/**
+ * The special Page for the data export
+ *
+ * @licence GNU GPL v2+
+ * @author Alexander Lehmann < [email protected] >
+ */
+
+namespace PubSubHubbub;
+
+use DumpOutput;
+use LogEventsList;
+use LogPager;
+use SpecialPage;
+use Title;
+use WikiExporter;
+use Xml;
+use XmlDumpWriter;
+
+class SpecialPuSHExport extends SpecialPage {
+
+ /**
+ * @var WikiExporter
+ */
+ private $exporter;
+
+ /**
+ * @var DatabaseBase
+ */
+ private $dbr;
+
+ /**
+ * @var array
+ *
+ * List of already used Titles
+ */
+ private $titleList = array();
+
+ /**
+ * @var DumpOutput
+ */
+ private $sink;
+
+ /**
+ * @var XmlDumpWriter
+ */
+ private $writer;
+
+ /**
+ * Constructor
+ */
+ public function __construct() {
+ parent::__construct( 'PuSHExport', '', false );
+ $this->writer = new XmlDumpWriter();
+ $this->sink = new DumpOutput();
+ $this->dbr = wfGetDB( DB_SLAVE );
+ $history = WikiExporter::CURRENT;
+ $buffer = WikiExporter::BUFFER;
+ $this->exporter = new WikiExporter( $this->dbr, $history,
$buffer );
+ }
+
+ /**
+ * Main execution function.
+ * @param string|null $par The page to be exported.
+ * @return bool|void
+ */
+ public function execute( $par ) {
+ $title = Title::newFromText( $par );
+
+ $this->setHeaders();
+ $request = $this->getRequest();
+ $this->getOutput()->disable();
+ wfResetOutputBuffers();
+ $request->response()->header( "Content-type: application/xml;
charset=utf-8" );
+ PubSubHubbub::insertLinkHeader( $title );
+
+ if ( is_null( $title ) ) {
+ $this->doExportLastChanges();
+ } else {
+ $this->doExportTitle( $title );
+ }
+ return true;
+ }
+
+ /**
+ * Export the last 5 changes.
+ */
+ private function doExportLastChanges() {
+ $var = array( 'rc_title', 'rc_namespace', 'rc_timestamp' );
+ $options = array( 'ORDER BY' => 'rc_timestamp desc' );
+ $changes = $this->dbr->select('recentchanges', $var, '',
__METHOD__, $options);
+
+ $this->exporter->openStream();
+ $count = 0;
+ while ( $count < 5 ) {
+ $row = $this->dbr->fetchRow( $changes );
+
+ if ( !$row ) {
+ break;
+ }
+
+ if ( $this->isTitleAlreadyListed( $row ) ) {
+ $title = Title::newFromText( $row['rc_title'],
$row['rc_namespace'] );
+ $this->createTitleOutput( $title );
+ $count++;
+ }
+ }
+ $this->exporter->closeStream();
+
+ }
+
+ /**
+ * Export the page or the deletion log.
+ *
+ * @param Title $title
+ */
+ private function createTitleOutput( Title $title ) {
+ if ( $title->exists() ) {
+ $this->exporter->pageByTitle( $title );
+ }
+ else {
+ $this->doExportDeletionLog( $title );
+ }
+ }
+
+ /**
+ * Check if the Title is already used.
+ *
+ * @param array $row
+ * @return bool
+ */
+ private function isTitleAlreadyListed( $row ) {
+ if ( $this->titleList[ strval( $row['rc_namespace'] ) . ':' .
$row['rc_title'] ] ) {
+ return false;
+ }
+ $this->titleList[ strval( $row['rc_namespace'] ) . ':' .
$row['rc_title'] ] = true;
+ return true;
+ }
+
+ /**
+ * Export a single title/page.
+ *
+ * @param Title $title
+ */
+ private function doExportTitle( Title $title ) {
+ $this->exporter->openStream();
+ $this->createTitleOutput( $title );
+ $this->exporter->closeStream();
+ }
+
+ /**
+ * Export the deletion log of the title.
+ *
+ * @param Title $title
+ */
+ private function doExportDeletionLog( Title $title ) {
+ $namespace = $title->getNsText();
+ if ( $namespace != '' ) {
+ $namespace .= ':';
+ }
+ $titleText = $namespace . $title->getBaseText();
+ $logList = new LogEventsList(
+ $this->getContext(),
+ null,
+ LogEventsList::NO_ACTION_LINK
+ );
+
+ $pager = new LogPager( $logList, 'delete', '', $titleText,
false, array(), null, null, '' );
+
+ $pager->doQuery();
+ $res = $pager->getResult();
+ $this->createLogXML( $res );
+ }
+
+ /**
+ * Create XML output for the deletion log.
+ *
+ * @param $res
+ */
+ private function createLogXML( $res ) {
+ $this->sink->write( '<page>' . PHP_EOL );
+ $row = $this->dbr->fetchObject( $res );
+ if ( !$row ) {
+ $this->sink->write(
+ XML::element( 'error', null, 'Page is missing' )
+ );
+ }
+ else {
+ $this->sink->write( '<delete>' . PHP_EOL );
+ $this->sink->write(
+ ' ' . $this->writer->writeLogItem( $row )
+ );
+ $this->sink->write( '</delete>' . PHP_EOL );
+ }
+ $this->sink->write( '</page>' . PHP_EOL );
+ }
+}
diff --git a/tests/phpunit/PubSubHubbubTest.php
b/tests/phpunit/PubSubHubbubTest.php
index 52bf0b5..3a29cf0 100644
--- a/tests/phpunit/PubSubHubbubTest.php
+++ b/tests/phpunit/PubSubHubbubTest.php
@@ -53,19 +53,11 @@
* @param Revision|null $revision The revision (or null) the edit was
based on.
* @param int $count The expected number of jobs in the database.
*/
- public function testOnPageContentSaveComplete( Article $article,
$revision, $count ) {
+ public function testOnNewRevisionFromEditComplete( Article $article,
$revision, $count ) {
$user = $this->getMock( 'User' );
- $content = $this->getMock ( 'Content' );
- $summary = '';
- $isMinor = false;
- $isWatch = false;
- $section = null;
- $flags = null;
- $status = $this->getMock ( 'Status' );
$baseRevId = 1;
- PubSubHubbub::onPageContentSaveComplete( $article, $user,
$content, $summary, $isMinor, $isWatch,
- $section, $flags, $revision, $status, $baseRevId );
+ PubSubHubbub::onNewRevisionFromEditComplete( $article,
$revision, $baseRevId, $user);
$row = array( array( $count ) );
$this->assertSelect( "job", "count(*)", "", $row );
}
@@ -79,31 +71,29 @@
public function testCreateLinkHeader( $titleText, $urlText) {
global $wgScript, $wgServer, $wgPubSubHubbubHubURL;
$title = Title::newFromText( $titleText );
- $article = new Article( $title );
- $titleURL = $wgServer . $wgScript . "?title=" . $urlText .
"&action=raw";
+ $titleURL = $wgServer . $wgScript . "/Special:PuSHExport/" .
$urlText;
$linkHeader = "Link: <" . $wgPubSubHubbubHubURL . ">;
rel=\"hub\", <" . $titleURL . ">; rel=\"self\"";
-
- $this->assertEquals( $linkHeader,
PubSubHubbub::createLinkHeader( $wgPubSubHubbubHubURL, $article ) );
+ $this->assertEquals( $linkHeader,
PubSubHubbub::createLinkHeader( $wgPubSubHubbubHubURL, $title ) );
}
/**
* @dataProvider WBLinkHeaderData
*
* @param string $titleText Text to use for the title.
+ * @param string $nsModel The namespace model
* @param string $urlText Text to identify a title in a URL.
*/
- public function testCreateWBLinkHeader( $titleText, $urlText) {
+ public function testCreateWBLinkHeader( $titleText, $nsModel,
$urlText) {
if ( defined( 'WB_VERSION' ) ) {
global $wgScript, $wgServer, $wgPubSubHubbubHubURL;
- $ns = NamespaceUtils::getEntityNamespace(
'wikibase-item' );
+ $ns = NamespaceUtils::getEntityNamespace( $nsModel );
$title = Title::newFromText( $titleText, $ns );
- $article = new Article( $title );
- $titleURL = $wgServer . $wgScript .
"/Special:EntityData/" . $urlText . ".json";
+ $titleURL = $wgServer . $wgScript .
"/Special:PuSHExport/" . $urlText;
$linkHeader = "Link: <" . $wgPubSubHubbubHubURL . ">;
rel=\"hub\", <" . $titleURL . ">; rel=\"self\"";
- $this->assertEquals( $linkHeader,
PubSubHubbub::createLinkHeader( $wgPubSubHubbubHubURL, $article ) );
+ $this->assertEquals( $linkHeader,
PubSubHubbub::createLinkHeader( $wgPubSubHubbubHubURL, $title ) );
}
else {
$this->markTestSkipped('The Wikibase-extension is not
available.');
@@ -112,12 +102,11 @@
public function testInsertLinkHeader() {
$title = Title::newFromText( "TestTitle" );
- $article = new Article( $title );
- PubSubHubbub::insertLinkHeader( $article );
+ PubSubHubbub::insertLinkHeader( $title );
global $wgRequest, $wgPubSubHubbubHubURL;
- $sExpectedLinkHeader = PubSubHubbub::createLinkHeader(
$wgPubSubHubbubHubURL, $article );
+ $sExpectedLinkHeader = PubSubHubbub::createLinkHeader(
$wgPubSubHubbubHubURL, $title );
$sActualLinkHeader = "Link: " .
$wgRequest->response()->getHeader( "Link" );
$this->assertEquals( $sExpectedLinkHeader, $sActualLinkHeader );
@@ -134,9 +123,6 @@
$title = Title::newFromText( "test" );
$article = new Article( $title );
-
- $revision = null;
- $data[] = array( $article, $revision, 0 );
$revision = new Revision( array(
'id' => 1,
@@ -159,7 +145,7 @@
public function linkHeaderData() {
return array(
array( 'test', 'Test' ),
- array( 'Foo1', 'Foo1' ),
+ array( 'Fo_o1', 'Fo_o1' ),
array( '2bar', '2bar' ),
array( 'Fär', 'F%C3%A4r' )
);
@@ -168,13 +154,13 @@
/**
* The provider of data for testCreateWBLinkHeader.
*
- * @return array[array[string, string]]
+ * @return array[array[string, string, string]]
*/
public function WBLinkHeaderData() {
return array(
- array( 'Item:Q2', 'Q2' ),
- array( 'Item:Q100', 'Q100' ),
- array( 'Item:Q5', 'Q5' ),
+ array( 'Q2', 'wikibase-item', 'Item:Q2' ),
+ array( 'Q100', 'wikibase-item', 'Item:Q100' ),
+ array( 'P5', 'wikibase-property', 'Property:P5' ),
);
}
@@ -189,5 +175,4 @@
array( 'https://www.example.com' )
);
}
-
}
--
To view, visit https://gerrit.wikimedia.org/r/129131
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: merged
Gerrit-Change-Id: Idcd687cd72e364d37306d7f28c1583ef8ad3ed91
Gerrit-PatchSet: 5
Gerrit-Project: mediawiki/extensions/PubSubHubbub
Gerrit-Branch: master
Gerrit-Owner: Alexander.lehmann <[email protected]>
Gerrit-Reviewer: Alexander.lehmann
<[email protected]>
Gerrit-Reviewer: Nik <[email protected]>
Gerrit-Reviewer: Siebrand <[email protected]>
Gerrit-Reviewer: WikidataJenkins <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits