Adamw has uploaded a new change for review.

  https://gerrit.wikimedia.org/r/61274


Change subject: Cleanup
......................................................................

Cleanup

Branch name (not ID) appears in page history.

Add stubs for ConflictResolution.

Move towards MediaWiki coding standards.

Basic tests.

TODO:

* I noticed too late that the Save Draft stuff is a redundant subset of
Extension:SaveDraft.  I haven't removed the code here because it's a
gamble that the other extension will be pushed through to general acceptance.

Change-Id: I90452da7c5a4aa94224b7fc6ee3bbc0678d0e273
---
M Nonlinear.hooks.php
M Nonlinear.i18n.php
M Nonlinear.php
D Nonlinear.setup.php
A includes/Branch.php
A includes/ConflictResolution.php
D includes/branch.php
D includes/branch_store.php
R modules/historyPage/historyPage.css
R modules/historyPage/historyPage.js
M schema/install.sql
A tests/BranchTest.php
A tests/ConflictResolutionTest.php
A tests/NonlinearTests.hooks.php
14 files changed, 387 insertions(+), 148 deletions(-)


  git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Nonlinear 
refs/changes/74/61274/1

diff --git a/Nonlinear.hooks.php b/Nonlinear.hooks.php
index 7714412..148afe4 100644
--- a/Nonlinear.hooks.php
+++ b/Nonlinear.hooks.php
@@ -1,51 +1,90 @@
-<?php
+<?php namespace Nonlinear;
 
-namespace Nonlinear;
-
-class Hook
+class Hooks
 {
-       static function onLoadExtensionSchemaUpdates(\DatabaseUpdater $du)
-       {
-               MediawikiBranchStore::addSchemaUpdates($du);
-               return true;
-       }
+       static function onAlternateEdit( $page ) {
+               global $wgRequest;
 
-       static function onPageHistoryLineEnding($pager, $row, &$summary, 
$classes)
-       {
-               $summary = \Xml::wrapClass($row->branch_id, "branch-label") . 
$summary;
-               return true;
-       }
-
-       static function onRevisionInsertComplete($revision, $data, $flags)
-       {
-               try {
-                       $parent_branch = new Branch(array(
-                               'rev_id' => $revision->getParentId()
-                       ));
-               }
-               catch (\MWException $ex) {
-                       //pass
-               }
-
-               //XXX if branch is still master...
-               //XXX check article branching mode
-
-               if ($parent_branch) {
-                       $parent_branch->adopt_revision($revision->getId());
+               if ( $wgRequest->getVal( 'wpDraft' ) ) {
+                       $page->getTitle();
                }
                return true;
        }
 
-       static function onGetQueryInfo($pager, &$queryInfo)
-       {
-               MediawikiBranchStore::alterPageHistoryQuery($queryInfo);
+       static function onShowEditFormInitial( &$page, &$out ) {
+               global $wgRequest;
+
+               if ( $wgRequest->getVal( 'wpDraft' ) ) {
+                       $out->addHtml(
+                               \Html::rawElement( 'div', array( 'class' => 
'mw-warning' ),
+                                       "FOOooo" . "<p>When your changes are 
ready for review, click the \"Send for review\" button. Resume editing by 
visiting your " . \Linker::link( \SpecialPage::getTitleFor( "Drafts" ), 
"drafts" ) . " page."
+                               )
+                       );
+                       /*
+                       $page->save = false;
+                       $page->diff = true;
+                       return false;
+                       */
+               }
                return true;
        }
 
-       //TODO take a less-global hit
-       static function addUi($page, $skin)
-       {
-               $page->addModules('ext.nonlinear.ui');
+       static function onEditPageBeforeEditButtons( &$page, &$buttons, 
&$tabindex ) {
+               // TODO: core hook to check "will otherwise go live" condition. 
If not, then override the "save" functionality by branching
+               /*
+               if ( is_callable( '\\FlaggablePageView::singleton' ) ) {
+                       $view = FlaggablePageView::singleton();
+               }
+               */
+
+               $button_desc = array(
+                       'id' => 'wpDraft',
+                       'name' => 'wpDraft',
+                       'type' => 'submit',
+                       'tabindex' => ++$tabindex,
+                       'value' => wfMessage( 'nonlinear-savedraft' )->text(),
+                       'accesskey' => wfMessage( 
'nonlinear-savedraft-accesskey' )->text(),
+                       'title' => wfMessage( 'nonlinear-savedraft-tooltip' 
)->text() . ' [' . wfMessage( 'nonlinear-savedraft-accesskey' )->text() . ']',
+               );
+               $buttons['draft'] = \Xml::element( 'input', $button_desc, '' );
+
+               return true;
+       }
+
+       static function onGetQueryInfo( $pager, &$queryInfo ) {
+               BranchStore::alterPageHistoryQuery( $queryInfo );
+               return true;
+       }
+
+       static function onPageHistoryLineEnding( $pager, $row, &$summary, 
$classes ) {
+               $summary = \Xml::wrapClass( $row->branch_name, "branch-label" ) 
. $summary;
+               return true;
+       }
+
+       static function onRevisionInsertComplete( $revision, $data, $flags ) {
+               $parentRevision = \Revision::newFromId( 
$revision->getParentId() );
+               if ( $parentRevision ) {
+                       $parentBranch = Branch::find( $parentRevision );
+
+                       //XXX check article branching mode
+
+                       if ( $parentBranch ) {
+                               $parentBranch->adopt( $revision );
+                       }
+               }
+               return true;
+       }
+
+       static function addUi( $page, $skin ) {
+               if ( $page->getRequest()->getVal( 'action' ) === 'history' ) {
+                       $page->addModules( 'ext.nonlinear.historyPage.ui' );
+               }
+               return true;
+       }
+
+       static function onLoadExtensionSchemaUpdates( \DatabaseUpdater $du ) {
+               $du->addExtensionUpdate( array( 'addTable',
+                       'nonlinear_branch', __DIR__ . "/schema/install.sql", 
true ) );
                return true;
        }
 }
diff --git a/Nonlinear.i18n.php b/Nonlinear.i18n.php
index 6022520..a57bf49 100644
--- a/Nonlinear.i18n.php
+++ b/Nonlinear.i18n.php
@@ -11,6 +11,10 @@
 $messages['en'] = array(
        'nonlinear-branch-not-found' => 'Unable to find branch',
        'nonlinear-desc' => 'Helps manage page revision history, providing an 
interface to fork and merge graphs',
+
+       'nonlinear-savedraft' => 'Save draft',
+       'nonlinear-savedraft-tooltip' => 'Save your changes to a sandbox 
location',
+       'nonlinear-savedraft-accesskey' => 'd',
 );
 
 /** Message documentation (Message documentation)
@@ -19,6 +23,10 @@
 $messages['qqq'] = array(
        'nonlinear-branch-not-found' => 'Displayed when a branch id does not 
correspond to an existing database record.',
        'nonlinear-desc' => 
'{{desc|name=Nonlinear|url=http://www.mediawiki.org/wiki/Extension:Nonlinear}}',
+
+       'nonlinear-savedraft' => 'Button label for the "Save draft" feature, 
displayed on the Edit page',
+       'nonlinear-savedraft-tooltip' => 'Tooltip help for the "Save draft" 
button',
+       'nonlinear-savedraft-accesskey' => 'Hotkey for saving a draft',
 );
 
 /** Asturian (asturianu)
diff --git a/Nonlinear.php b/Nonlinear.php
index d42d12d..9c957ba 100644
--- a/Nonlinear.php
+++ b/Nonlinear.php
@@ -16,40 +16,54 @@
        'descriptionmsg' => 'nonlinear-desc',
 );
 
-$wgHooks['BeforePageDisplay'][] = 'Nonlinear\Hook::addUi';
-$wgHooks['LoadExtensionSchemaUpdates'][] = array('Nonlinear\Hook');
+$wgHooks['BeforePageDisplay'][] = 'Nonlinear\Hooks::addUi';
+$wgHooks['LoadExtensionSchemaUpdates'][] = 
'Nonlinear\Hooks::onLoadExtensionSchemaUpdates';
 
-$wgHooks['RevisionInsertComplete'][] = 
array('Nonlinear\Hook::onRevisionInsertComplete');
+$wgHooks['RevisionInsertComplete'][] = 
'Nonlinear\Hooks::onRevisionInsertComplete';
 
-#$wgHooks['PageHistoryBeforeList'][] = 
array('Nonlinear\Hook::alterPageHistory');
-$wgHooks['PageHistoryPager::getQueryInfo'][] = 
array('Nonlinear\Hook::onGetQueryInfo');
-$wgHooks['PageHistoryLineEnding'][] = 
array('Nonlinear\Hook::onPageHistoryLineEnding');
+$wgHooks['PageHistoryPager::getQueryInfo'][] = 
'Nonlinear\Hooks::onGetQueryInfo';
+$wgHooks['PageHistoryLineEnding'][] = 
'Nonlinear\Hooks::onPageHistoryLineEnding';
+
+// Disable "Save Draft" stuff for now
+if ( false ) {
+       $wgHooks['AlternateEdit'][] = 'Nonlinear\Hooks::onAlternateEdit';
+       $wgHooks['EditPageBeforeEditButtons'][] = 
'Nonlinear\Hooks::onEditPageBeforeEditButtons';
+       $wgHooks['EditPage::showEditForm:initial'][] = 
'Nonlinear\Hooks::onShowEditFormInitial';
+}
+
+$wgHooks['UnitTestsList'][] = 'Nonlinear\Tests\Hooks::onUnitTestsList';
 
 
-$ext_dir = dirname( __FILE__ );
+$ext_dir = __DIR__ . "/";
 
-$wgExtensionMessagesFiles['Nonlinear'] = $ext_dir . '/Nonlinear.i18n.php';
+$wgExtensionMessagesFiles['Nonlinear'] = $ext_dir . 'Nonlinear.i18n.php';
 
-$includes = $ext_dir . "/includes/";
+$includes = $ext_dir . "includes/";
+$tests = $ext_dir . "tests/";
 
 $wgAutoloadClasses += array(
-       'Nonlinear\Hook' => $ext_dir . "/Nonlinear.hooks.php",
-       'Nonlinear\Branch' => $includes . "branch.php",
-       'Nonlinear\MediawikiBranchStore' => $includes . "branch_store.php",
+       'Nonlinear\Hooks' => $ext_dir . "Nonlinear.hooks.php",
+
+       'Nonlinear\Branch' => $includes . "Branch.php",
+       'Nonlinear\BranchRevisionStore' => $includes . "Branch.php",
+       'Nonlinear\BranchStore' => $includes . "Branch.php",
+       'Nonlinear\ConflictResolution' => $includes . "ConflictResolution.php",
+
+       'Nonlinear\Tests\Hooks' => $tests . "NonlinearTests.hooks.php",
 );
 
 $wgResourceModules += array(
-    'ext.nonlinear.ui' => array(
-               'localBasePath' => $ext_dir . '/modules',
-               'remoteExtPath' => 'Nonlinear/modules',
+       'ext.nonlinear.historyPage.ui' => array(
+               'localBasePath' => $ext_dir . "modules/historyPage",
+               'remoteExtPath' => "Nonlinear/modules/historyPage",
                'group' => 'ext.nonlinear',
-        'scripts' => 'ext.nonlinear.ui.js',
-        'styles' => 'ext.nonlinear.ui.css',
-        'messages' => array(
-        ),
-        'dependencies' => array(
-            'mediawiki.util',
-            'jquery.client',
-        ),
-    ),
+               'scripts' => 'historyPage.js',
+               'styles' => 'historyPage.css',
+               'messages' => array(
+               ),
+               'dependencies' => array(
+                       'mediawiki.util',
+                       'jquery.client',
+               ),
+       ),
 );
diff --git a/Nonlinear.setup.php b/Nonlinear.setup.php
deleted file mode 100644
index a4abe2d..0000000
--- a/Nonlinear.setup.php
+++ /dev/null
@@ -1,2 +0,0 @@
-<?php
-
diff --git a/includes/Branch.php b/includes/Branch.php
new file mode 100644
index 0000000..79d72d6
--- /dev/null
+++ b/includes/Branch.php
@@ -0,0 +1,130 @@
+<?php namespace Nonlinear;
+
+/**
+ * Tracks article revisions when they diverge from the main history
+ *
+ * 
+ * We mark every revision on the branch, to reduce the cost of searches.  Only
+ * the parent revision needs to be checked.
+ */
+class Branch
+{
+       protected $id;
+
+       // Restrict to internal factory apis
+       protected function __construct( $id ) {
+               $this->id = $id;
+       }
+
+       function getName() {
+               //FIXME: this is lame, we will be killed by replag.  Just keep 
track
+               // of fields in the object.
+               return BranchStore::getName( $this->id );
+       }
+
+       /**
+        * Search for 
+        * @return Branch or null if not found
+        */
+       static function find( \Revision $rev ) {
+               $branchId = BranchRevisionStore::findByRev( $rev->getId() );
+               if ( $branchId ) {
+                       return new Branch( $branchId );
+               }
+               return null;
+       }
+
+       /**
+        * Mark the first revision to diverge
+        *
+        * @return Branch newly created branch
+        */
+       static function create( \Revision $rev, $name = null ) {
+               $branchId = BranchStore::createBranch( $name );
+
+               $branch = new Branch( $branchId );
+               $branch->adopt( $rev );
+
+               return $branch;
+       }
+
+       function adopt( \Revision $rev ) {
+               //FIXME: this is a tricky operation re: permissions. We
+               // would rather set during revision create, to reuse all the
+               // same checks.  Also, there might be a race condition where the
+               // revision is temporarily on trunk.
+               BranchRevisionStore::setRevBranch( $rev->getId(), $this->id );
+       }
+}
+
+/**
+ * DB implementation of a branch
+ */
+class BranchStore {
+       static function createBranch( $name = null ) {
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->insert(
+                       'nonlinear_branch',
+                       array(
+                               'branch_name' => $name,
+                       )
+               );
+               return $dbw->insertId();
+       }
+
+       static function alterPageHistoryQuery( &$queryInfo ) {
+               $queryInfo['tables'][] = 'nonlinear_branch_revision';
+               $queryInfo['tables'][] = 'nonlinear_branch';
+               $queryInfo['fields'][] = 'branch_id';
+               $queryInfo['fields'][] = 'branch_name';
+               $queryInfo['join_conds']['nonlinear_branch_revision'] = array(
+                       'LEFT JOIN',
+                       'br_rev_id = rev_id'
+               );
+               $queryInfo['join_conds']['nonlinear_branch'] = array(
+                       'LEFT JOIN',
+                       'br_branch_id = branch_id'
+               );
+
+               #TODO: per-user branch filter conditions
+       }
+
+       static function getName( $branchId ) {
+               $dbw = wfGetDB( DB_SLAVE );
+               return $dbw->selectField(
+                       'nonlinear_branch',
+                       'branch_name',
+                       array('branch_id' => $branchId),
+                       __METHOD__
+               );
+       }
+}
+
+/**
+ * DB implementation of a branch-revision link
+ */
+class BranchRevisionStore {
+       static function findByRev( $rev_id ) {
+               $dbw = wfGetDB( DB_SLAVE );
+               return $dbw->selectField(
+                       'nonlinear_branch_revision',
+                       'br_branch_id',
+                       array('br_rev_id' => $rev_id),
+                       __METHOD__
+               );
+       }
+
+       static function setRevBranch( $rev_id, $branch_id ) {
+               $dbw = wfGetDB( DB_MASTER );
+               $dbw->insert(
+                       'nonlinear_branch_revision',
+                       array(
+                               'br_rev_id' => $rev_id,
+                               'br_branch_id' => $branch_id,
+                       )
+               );
+       }
+
+       static function findOrphanedRevisions( $page_id ) {
+       }
+}
diff --git a/includes/ConflictResolution.php b/includes/ConflictResolution.php
new file mode 100644
index 0000000..f265826
--- /dev/null
+++ b/includes/ConflictResolution.php
@@ -0,0 +1,29 @@
+<?php namespace Nonlinear;
+
+/**
+ * We encourage the resolution of branches back into trunk by maintaining
+ * a conflict resolution workflow.
+ */
+class ConflictResolution {
+       function __construct( Branch $branch, \User $user ) {
+       }
+
+       function addComment( $comment, $position = null ) {
+       }
+
+       function markAsResolved() {
+               //TODO
+       }
+
+       function markAsRejected() {
+       }
+
+       function reopen() {
+       }
+
+       function tag( $tagName ) {
+       }
+
+       function untag( $tagName ) {
+       }
+}
diff --git a/includes/branch.php b/includes/branch.php
deleted file mode 100644
index fc472e5..0000000
--- a/includes/branch.php
+++ /dev/null
@@ -1,29 +0,0 @@
-<?php
-
-namespace Nonlinear;
-
-/**
- * Interface to a branch.
- */
-class Branch
-{
-       function __construct($params)
-       {
-               $this->store = new MediawikiBranchStore();
-
-               if (!empty($params['rev_id'])) {
-                       $this->id = 
$this->store->find_by_rev_id($params['rev_id']);
-               }
-
-               if (empty($this->id)) {
-                       throw new \MWException('Unable to find branch');
-               }
-       }
-
-       function adopt_revision($rev_id)
-       {
-               //XXX this is a tricky operation re: permissions. We
-               // would rather set during revision create.
-               $this->store->set_revision_branch($rev_id, $this->id);
-       }
-}
diff --git a/includes/branch_store.php b/includes/branch_store.php
deleted file mode 100644
index 0caa3e9..0000000
--- a/includes/branch_store.php
+++ /dev/null
@@ -1,50 +0,0 @@
-<?php
-
-namespace Nonlinear;
-
-/**
- * Implementation of branch store, saved in the mediawiki database.
- */
-class MediawikiBranchStore
-{
-       function find_by_rev_id($rev_id)
-       {
-               $dbw = wfGetDB( DB_SLAVE );
-               return $dbw->selectField(
-                       'branch',
-                       'branch_id',
-                       array('branch_rev_id' => $rev_id),
-                       __METHOD__
-               );
-       }
-
-       function set_revision_branch($rev_id, $branch_id)
-       {
-               $dbw = wfGetDB( DB_MASTER );
-               $dbw->insert(
-                       'branch',
-                       array(
-                               'branch_rev_id' => $rev_id,
-                               'branch_id' => $branch_id,
-                       )
-               );
-       }
-
-       static function addSchemaUpdates(\DatabaseUpdater $du)
-       {
-               $base = dirname(__FILE__);
-               $du->addExtensionUpdate( array( 'addTable',
-                       'branch', "$base/../schema/install.sql", true ) );
-       }
-
-       static function alterPageHistoryQuery(&$queryInfo)
-       {
-               $queryInfo['tables'][] = 'branch';
-               $queryInfo['fields'][] = 'branch_id';
-               #XXX per-user branch filter conds
-               $queryInfo['join_conds']['branch'] = array(
-                       'LEFT JOIN',
-                       'branch_rev_id = rev_id'
-               );
-       }
-}
diff --git a/modules/ext.nonlinear.ui.css b/modules/historyPage/historyPage.css
similarity index 100%
rename from modules/ext.nonlinear.ui.css
rename to modules/historyPage/historyPage.css
diff --git a/modules/ext.nonlinear.ui.js b/modules/historyPage/historyPage.js
similarity index 100%
rename from modules/ext.nonlinear.ui.js
rename to modules/historyPage/historyPage.js
diff --git a/schema/install.sql b/schema/install.sql
index 55f8ee5..b227330 100644
--- a/schema/install.sql
+++ b/schema/install.sql
@@ -1,7 +1,28 @@
-CREATE TABLE IF NOT EXISTS /*_*/branch (
-  branch_id integer unsigned NOT NULL,
-  -- Foreign key to revision.revision.id
-  branch_rev_id integer unsigned NOT NULL
+-- Description of a branch
+CREATE TABLE /*_*/nonlinear_branch (
+  branch_id integer unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+
+  branch_name varchar(255) DEFAULT NULL
 ) /*$wgDBTableOptions*/;
 
-CREATE INDEX /*i*/branch_rev_id ON /*_*/branch (branch_rev_id);
+-- Link a revision to a branch
+CREATE TABLE /*_*/nonlinear_branch_revision (
+  br_id integer unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+
+  -- Foreign key to nonlinear_branch.branch_id
+  br_branch_id integer unsigned NOT NULL,
+
+  -- Foreign key to revision.rev_id
+  br_rev_id integer unsigned NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/br_branch_id ON /*_*/nonlinear_branch_revision 
(br_branch_id);
+CREATE INDEX /*i*/br_rev_id ON /*_*/nonlinear_branch_revision (br_rev_id);
+
+-- Conflict resolution work queue
+CREATE TABLE /*_*/nonlinear_conflict_resolution (
+  cr_id integer unsigned NOT NULL PRIMARY KEY AUTO_INCREMENT,
+
+  -- Foreign key to nonlinear_branch.branch_id
+  cr_branch_id integer unsigned NOT NULL
+) /*$wgDBTableOptions*/;
+CREATE INDEX /*i*/cr_branch_id ON /*_*/nonlinear_conflict_resolution 
(cr_branch_id);
diff --git a/tests/BranchTest.php b/tests/BranchTest.php
new file mode 100644
index 0000000..ec0117b
--- /dev/null
+++ b/tests/BranchTest.php
@@ -0,0 +1,65 @@
+<?php
+
+use Nonlinear\Branch;
+use Nonlinear\BranchRevisionStore;
+use Nonlinear\BranchStore;
+
+/**
+ * @group Database
+ * @group medium
+ */
+class BranchTest extends MediaWikiTestCase {
+       protected $page;
+       protected $trunkRevision;
+       protected $iterationMarker = 1;
+
+       function setUp() {
+               parent::setUp();
+
+               $title = Title::newFromText( "aTitle", 
$this->getDefaultWikitextNS() );
+               $this->page = new WikiPage( $title );
+
+               $this->trunkRevision = $this->doEdit();
+       }
+
+       function tearDown() {
+               if ( $this->page->exists() ) {
+                       $this->page->doDeleteArticle( "test done" );
+               }
+
+               parent::tearDown();
+       }
+
+       protected function doEdit() {
+               $text = "aContent version " . $this->iterationMarker++;
+               $content = ContentHandler::makeContent( $text, 
$this->page->getTitle() );
+               $status = $this->page->doEditContent( $content, "testing" );
+
+               $this->assertTrue( $status->isGood() );
+
+               return $this->page->getRevision();
+       }
+
+       function testCreateBranch() {
+               $rev = $this->doEdit();
+
+               $name = "abranchname";
+               $branch = Branch::create( $rev, $name );
+
+               $this->assertEquals( $name, $branch->getName() );
+               $this->assertNotNull( BranchRevisionStore::findByRev( 
$rev->getId() ) );
+       }
+
+       function testFindBranch() {
+               $rev = $this->doEdit();
+
+               $name = "abranchname2";
+               $branch = Branch::create( $rev, $name );
+
+               $this->assertNull( Branch::find( $this->trunkRevision ) );
+
+               $foundBranch = Branch::find( $rev );
+               $this->assertNotNull( $foundBranch );
+               $this->assertEquals( $name, $foundBranch->getName() );
+       }
+}
diff --git a/tests/ConflictResolutionTest.php b/tests/ConflictResolutionTest.php
new file mode 100644
index 0000000..4815dc1
--- /dev/null
+++ b/tests/ConflictResolutionTest.php
@@ -0,0 +1,4 @@
+<?php
+
+class ConflictResolutionTest extends MediaWikiTestCase {
+}
diff --git a/tests/NonlinearTests.hooks.php b/tests/NonlinearTests.hooks.php
new file mode 100644
index 0000000..b439761
--- /dev/null
+++ b/tests/NonlinearTests.hooks.php
@@ -0,0 +1,10 @@
+<?php namespace Nonlinear\Tests;
+
+class Hooks {
+       static function onUnitTestsList( &$files ) {
+               $files[] = __DIR__ . 'BranchTest.php';
+               $files[] = __DIR__ . 'ConflictResolutionTest.php';
+
+               return true;
+       }
+}

-- 
To view, visit https://gerrit.wikimedia.org/r/61274
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings

Gerrit-MessageType: newchange
Gerrit-Change-Id: I90452da7c5a4aa94224b7fc6ee3bbc0678d0e273
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Nonlinear
Gerrit-Branch: master
Gerrit-Owner: Adamw <[email protected]>

_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits

Reply via email to