Werdna has uploaded a new change for review.
https://gerrit.wikimedia.org/r/78085
Change subject: Convert Flow to use binary IDs (better performance and more
compatible
......................................................................
Convert Flow to use binary IDs (better performance and more compatible
Change-Id: I80d84a44a1b760864dcf6eefd55964f19e487c9f
---
M Flow.php
M flow.sql
M includes/Data/ObjectManager.php
M includes/Model/AbstractRevision.php
M includes/Model/Definition.php
M includes/Model/PostRevision.php
M includes/Model/Summary.php
M includes/Model/TopicListEntry.php
A includes/Model/UUID.php
M includes/Model/Workflow.php
M includes/Repository/MultiGetList.php
M special/SpecialFlow.php
12 files changed, 159 insertions(+), 56 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/mediawiki/extensions/Flow
refs/changes/85/78085/1
diff --git a/Flow.php b/Flow.php
index 03b6c70..e660276 100755
--- a/Flow.php
+++ b/Flow.php
@@ -62,6 +62,7 @@
$wgAutoloadClasses['Flow\Model\Summary'] = $dir . 'includes/Model/Summary.php';
$wgAutoloadClasses['Flow\Model\TopicListEntry'] = $dir .
'includes/Model/TopicListEntry.php';
$wgAutoloadClasses['Flow\Model\Workflow'] = $dir .
'includes/Model/Workflow.php';
+$wgAutoloadClasses['Flow\Model\UUID'] = "$dir/includes/Model/UUID.php";
// Classes that deal with database interaction between database and the models
$wgAutoloadClasses['Flow\Repository\TreeRepository'] = $dir .
'includes/Repository/TreeRepository.php';
diff --git a/flow.sql b/flow.sql
index b35e473..fc914fc 100644
--- a/flow.sql
+++ b/flow.sql
@@ -2,7 +2,7 @@
-- This file contains only the unsharded global data
CREATE TABLE /*_*/flow_definition (
- definition_id decimal(39) unsigned NOT NULL,
+ definition_id binary(16) NOT NULL,
definition_wiki varchar(32) binary NOT NULL,
definition_name varchar(32) binary NOT NULL,
definition_type varchar(32) binary NOT NULL,
@@ -12,7 +12,7 @@
CREATE UNIQUE INDEX /*i*/flow_definition_unique_name ON flow_definition
(definition_wiki, definition_name);
CREATE TABLE /*_*/flow_workflow (
- workflow_id decimal(39) unsigned not null,
+ workflow_id binary(16) not null,
workflow_wiki varchar(16) binary not null,
workflow_namespace int not null,
workflow_page_id int unsigned not null,
@@ -27,7 +27,7 @@
-- TODO: is this usefull as a bitfield? may be premature optimization,
a string
-- or list of strings may be simpler and use only a little more space.
workflow_lock_state int unsigned not null,
- workflow_definition_id decimal(39) unsigned not null,
+ workflow_definition_id binary(16) not null,
PRIMARY KEY (workflow_id)
) /*$wgDBTableOptions*/;
@@ -43,8 +43,8 @@
-- TopicList Tables
CREATE TABLE /*_*/flow_topic_list (
- topic_list_id decimal(39) unsigned not null,
- topic_id decimal(39) unsigned
+ topic_list_id binary(16) not null,
+ topic_id binary(16)
) /*$wgDBTableOptions*/;
CREATE UNIQUE INDEX /*i*/flow_topic_list_pk ON /*_*/flow_topic_list(
topic_list_id, topic_id);
@@ -53,16 +53,16 @@
-- also denormalizes information commonly needed with a revision
CREATE TABLE /*_*/flow_tree_revision (
-- the id of the post in the post tree
- tree_rev_descendant decimal(39) unsigned not null,
+ tree_rev_descendant binary(16) not null,
-- fk to flow_revision
- tree_rev_id decimal(39) unsigned not null,
+ tree_rev_id binary(16) not null,
-- denormalized so we dont need to keep finding the first revision of a
post
tree_orig_create_time varchar(12) binary not null,
tree_orig_user_id bigint unsigned not null,
tree_orig_user_text varchar(255) binary not null,
-- denormalize post parent as well? Prevents an extra query when
building
-- tree from closure table. unnecessary?
- tree_parent_id decimal(39) unsigned,
+ tree_parent_id binary(16),
PRIMARY KEY( tree_rev_id )
) /*$wgDBTableOptions*/;
@@ -74,8 +74,8 @@
-- or something? Main limit in current setup can only associate one summary
per
-- workflow
CREATE TABLE /*_*/flow_summary_revision (
- summary_workflow_id decimal(39) unsigned not null,
- summary_rev_id decimal(39) unsigned not null,
+ summary_workflow_id binary(16) not null,
+ summary_rev_id binary(16) not null,
PRIMARY KEY ( summary_workflow_id, summary_rev_id )
) /*$wgDBTableOptions*/;
@@ -95,7 +95,7 @@
--
CREATE TABLE /*_*/flow_revision (
-- UID::newTimestampedUID128()
- rev_id decimal(39) unsigned not null,
+ rev_id binary(16) not null,
-- What kind of revision is this: tree/summary/etc.
rev_type varchar(16) binary not null,
-- user id creating the revision
@@ -107,10 +107,13 @@
-- revision suppression
rev_deleted tinyint unsigned not null default 0,
-- rev_id of parent or null if no previous revision
- rev_parent_id decimal(39) unsigned,
+ rev_parent_id binary(16),
-- content of the revision
rev_text_id int unsigned not null,
+
+ rev_flags varchar(255) binary null,
+ rev_comment varchar(255) binary null,
PRIMARY KEY (rev_id)
) /*$wgDBTableOptions*/;
@@ -132,8 +135,8 @@
-- Closure table implementation of tree storage in sql
-- We may be able to go simpler than this
CREATE TABLE /*_*/flow_tree_node (
- tree_ancestor decimal(39) unsigned not null,
- tree_descendant decimal(39) unsigned not null,
+ tree_ancestor binary(16) not null,
+ tree_descendant binary(16) not null,
tree_depth smallint not null
) /*$wgDBTableOptions*/;
@@ -144,5 +147,5 @@
INSERT INTO flow_definition
( definition_id, definition_wiki, definition_name, definition_type,
definition_options )
VALUES
- ( 6645733872243863389540699858102420002, 'wiki', 'topic', 'topic', NULL
),
- ( 6645733872272877609211450958295368226, 'wiki', 'discussion',
'discussion',
'a:2:{s:19:"topic_definition_id";s:37:"6645733872243863389540699858102420002";s:6:"unique";b:1;}'
);
+ ( unhex('4ffebfa36a3155f2416080027a082220'), 'wiki', 'topic', 'topic',
NULL ), -- UUID 6645733872243863389540699858102420002
+ ( unhex('4ffebfa368b155f2416080027a082220'), 'wiki', 'discussion',
'discussion',
'a:2:{s:19:"topic_definition_id";s:32:"4ffebfa36a3155f2416080027a082220";s:6:"unique";b:1;}'
); -- 6645733872272877609211450958295368226
diff --git a/includes/Data/ObjectManager.php b/includes/Data/ObjectManager.php
index 64e7a9e..4cd9e96 100644
--- a/includes/Data/ObjectManager.php
+++ b/includes/Data/ObjectManager.php
@@ -165,7 +165,7 @@
}
$keys = array_keys( reset( $queries ) );
if ( isset( $options['sort'] ) && !is_array( $options['sort'] )
) {
- $options['sort'] = (array) $options['sort'];
+ $options['sort'] = ObjectManager::makeArray(
$options['sort'] );
}
if ( isset( $options['limit'] ) ) {
$limit = $options['limit'];
@@ -210,7 +210,7 @@
$pk = $this->storage->getPrimaryKeyColumns();
$queries = array();
foreach ( $objectIds as $id ) {
- $queries[] = array_combine( $pk, (array) $id );
+ $queries[] = array_combine( $pk,
ObjectManager::makeArray( $id ) );
}
// primary key is unique, but indexes still return their
results as array
// to be consistent. undo that for a flat result array
@@ -352,6 +352,14 @@
return true;
}
+ static public function makeArray( $input ) {
+ if ( is_array( $input ) ) {
+ return $input;
+ } else {
+ return array( $input );
+ }
+ }
+
public function remove( $object ) {
try {
$old = $this->loaded[$object];
@@ -461,7 +469,7 @@
$res = $dbw->update(
$this->table,
$this->calcUpdates( $old, $new ),
- $pk,
+ self::convertUUIDs( $pk ),
__METHOD__
);
// update returns boolean true/false as $res
@@ -496,7 +504,7 @@
throw new PersistenceException( 'Row has null primary
key: ' . implode( $missing ) );
}
$dbw = $this->dbFactory->getDB( DB_MASTER );
- $res = $dbw->delete( $this->table, $pk, __METHOD__ );
+ $res = $dbw->delete( $this->table, self::convertUUIDs( $pk ),
__METHOD__ );
return $res && $dbw->affectedRows();
}
@@ -505,10 +513,16 @@
* success.
*/
public function find( array $attributes, array $options = array() ) {
+ wfDebug( "Running search on table {$this->table}\n" );
+
+ foreach( $attributes as $key => $value ) {
+ wfDebug( " -- $key = $value\n" );
+ }
+
$res = $this->dbFactory->getDB( DB_MASTER )->select(
$this->table,
'*',
- $attributes,
+ self::convertUUIDs( $attributes ),
__METHOD__,
$options
);
@@ -549,6 +563,16 @@
public function getPrimaryKeyColumns() {
return $this->primaryKey;
+ }
+
+ public static function convertUUIDs( $array ) {
+ foreach( ObjectManager::makeArray( $array ) as $key => $value )
{
+ if ( is_a( $value, 'Flow\Model\UUID' ) ) {
+ $array[$key] = $value->getBinary();
+ }
+ }
+
+ return $array;
}
}
@@ -654,6 +678,7 @@
$keyToQuery[$key] = $query;
}
}
+
// Retreive from cache
$cached = $this->cache->getMulti( array_keys( $keyToIdx ) );
// expand partial results
@@ -689,6 +714,13 @@
protected function cacheKey( array $attributes ) {
+ foreach( $attributes as $key => $attr ) {
+ if ( strlen($attr) == 16 && preg_match( '/_id$/', $key
) ) {
+ $uuid = new \Flow\Model\UUID( $attr );
+ $attributes[$key] = $uuid->getHex();
+ }
+ }
+
return wfForeignMemcKey( 'flow', '', $this->prefix, implode(
':', $attributes ) );
}
@@ -721,6 +753,7 @@
$cached[$key][$k] = $row + $query;
}
}
+
return $cached;
}
}
diff --git a/includes/Model/AbstractRevision.php
b/includes/Model/AbstractRevision.php
index d5c212a..6c819bc 100644
--- a/includes/Model/AbstractRevision.php
+++ b/includes/Model/AbstractRevision.php
@@ -3,7 +3,6 @@
namespace Flow\Model;
use User;
-use UIDGenerator;
abstract class AbstractRevision {
protected $revId;
@@ -21,11 +20,11 @@
static public function fromStorageRow( array $row ) {
$obj = new static;
- $obj->revId = $row['rev_id'];
+ $obj->revId = new UUID( $row['rev_id'] );
$obj->userId = $row['rev_user_id'];
$obj->userText = $row['rev_user_text'];
$obj->flags = explode( ',', $row['rev_flags'] );
- $obj->prevRevision = $row['rev_parent_id'];
+ $obj->prevRevision = new UUID( $row['rev_parent_id'] );
$obj->comment = $row['rev_comment'];
$obj->textId = $row['rev_text_id'];
@@ -34,12 +33,16 @@
}
static public function toStorageRow( $obj ) {
+ $prevRevision = null;
+ if ( $obj->prevRevision ) {
+ $prevRevision = $obj->prevRevision->getBinary();
+ }
return array(
- 'rev_id' => $obj->revId,
+ 'rev_id' => $obj->revId->getBinary(),
'rev_user_id' => $obj->userId,
'rev_user_text' => $obj->userText,
'rev_flags' => implode( ',', $obj->flags ),
- 'rev_parent_id' => $obj->prevRevision,
+ 'rev_parent_id' => $prevRevision,
'rev_comment' => $obj->comment,
'rev_text_id' => $obj->textId,
@@ -52,7 +55,7 @@
// TODO: how do we know this is the latest revision? we dont ...
// basically, this is very very wrong :-(
$obj = clone $this;
- $obj->revId = UIDGenerator::newTimestampedUID128();
+ $obj->revId = new UUID();
$obj->userId = $user->getId();
$obj->userText = $user->getName();
$obj->prevRevision = $this->revId;
diff --git a/includes/Model/Definition.php b/includes/Model/Definition.php
index 75fc1c0..3b782f3 100644
--- a/includes/Model/Definition.php
+++ b/includes/Model/Definition.php
@@ -12,7 +12,11 @@
static public function fromStorageRow( array $row ) {
$obj = new self;
- $obj->id = $row['definition_id'];
+ if ( ! $row['definition_wiki'] ) {
+ die( var_dump( $row ) );
+ throw new \MWException( "No definition_wiki" );
+ }
+ $obj->id = new UUID( $row['definition_id'] );
$obj->type = $row['definition_type'];
$obj->wiki = $row['definition_wiki'];
$obj->name = $row['definition_name'];
@@ -22,7 +26,7 @@
static public function toStorageRow( Definition $obj ) {
return array(
- 'definition_id' => $obj->id,
+ 'definition_id' => $obj->id->getBinary(),
'definition_type' => $obj->type,
'definition_wiki' => $obj->wiki,
'definition_name' => $obj->name,
@@ -32,7 +36,7 @@
static public function create( $name, $type, array $options = array() )
{
$obj = new self;
- $obj->id = \UIDGenerator::newTimestampedUID128();
+ $obj->id = new UUID();
$obj->wiki = wfWikiId();
$obj->name = $name;
$obj->type = $type;
diff --git a/includes/Model/PostRevision.php b/includes/Model/PostRevision.php
index 2b8f0e3..db4a646 100644
--- a/includes/Model/PostRevision.php
+++ b/includes/Model/PostRevision.php
@@ -2,7 +2,6 @@
namespace Flow\Model;
-use UIDGenerator;
use User;
class PostRevision extends AbstractRevision {
@@ -23,7 +22,7 @@
// @param string $content The title of the topic(they are revisionable
as well)
static public function create( Workflow $topic, $content ) {
$obj = new self;
- $obj->revId = UIDGenerator::newTimestampedUID128();
+ $obj->revId = new UUID();
$obj->postId = $topic->getId();
$obj->content = $content;
$obj->origUserId = $obj->userId = $topic->getUserId();
@@ -43,8 +42,8 @@
}
$obj = parent::fromStorageRow( $row );
- $obj->replyToId = $row['tree_parent_id'];
- $obj->postId = $row['tree_rev_descendant'];
+ $obj->replyToId = new UUID( $row['tree_parent_id'] );
+ $obj->postId = new UUID( $row['tree_rev_descendant'] );
$obj->origCreateTime = $row['tree_orig_create_time'];
$obj->origUserId = $row['tree_orig_user_id'];
$obj->origUserText = $row['tree_orig_user_text'];
@@ -53,11 +52,16 @@
}
static public function toStorageRow( $rev ) {
+ $replyToId = null;
+
+ if ( $rev->replyToId ) {
+ $replyToId = $rev->replyToId->getBinary();
+ }
return parent::toStorageRow( $rev ) + array(
'rev_type' => 'post',
- 'tree_parent_id' => $rev->replyToId,
- 'tree_rev_descendant' => $rev->postId,
- 'tree_rev_id' => $rev->revId,
+ 'tree_parent_id' => $replyToId,
+ 'tree_rev_descendant' => $rev->postId->getBinary(),
+ 'tree_rev_id' => $rev->revId->getBinary(),
// rest of tree_ is denormalized data about first post
revision
'tree_orig_create_time' => $rev->origCreateTime,
'tree_orig_user_id' => $rev->origUserId,
@@ -68,7 +72,7 @@
public function reply( User $user, $content ) {
$reply = new self;
// No great reason to create two uuid's, a post and its first
revision can share a uuid
- $reply->revId = $reply->postId =
UIDGenerator::newTimestampedUID128();
+ $reply->revId = $reply->postId = new UUID();
$reply->userId = $reply->origUserId = $user->getId();
$reply->userText = $reply->origUserText = $user->getName();
$reply->origCreateTime = wfTimestampNow();
@@ -114,7 +118,7 @@
* TODO: better name. This is if the POST is newer, not the revision.
*/
public function compareCreateTime( PostRevision $rev ) {
- return strcmp( $rev->postId, $this->postId );
+ return strcmp( $rev->postId->getNumber(),
$this->postId->getNumber() );
}
}
diff --git a/includes/Model/Summary.php b/includes/Model/Summary.php
index b31fcbc..f8b5d86 100644
--- a/includes/Model/Summary.php
+++ b/includes/Model/Summary.php
@@ -3,14 +3,13 @@
namespace Flow\Model;
use User;
-use UIDGenerator;
class Summary extends AbstractRevision {
protected $workflowId;
static public function create( Workflow $workflow, User $user, $content
) {
$obj = new self;
- $obj->revId = UIDGenerator::newTimestampedUID128();
+ $obj->revId = new UUID();
$obj->workflowId = $workflow->getId();
$obj->content = $content;
$obj->userId = $user->getId();
@@ -24,15 +23,15 @@
throw new \MWException( 'Wrong revision type: ' .
$row['rev_type'] );
}
$obj = parent::fromStorageRow( $row );
- $obj->workflowId = $row['summary_workflow_id'];
+ $obj->workflowId = new UUID( $row['summary_workflow_id'] );
return $obj;
}
static public function toStorageRow( $obj ) {
return parent::toStorageRow( $obj ) + array(
'rev_type' => 'summary',
- 'summary_rev_id' => $obj->revId,
- 'summary_workflow_id' => $obj->workflowId,
+ 'summary_rev_id' => $obj->revId->getBinary(),
+ 'summary_workflow_id' => $obj->workflowId->getBinary(),
);
}
}
diff --git a/includes/Model/TopicListEntry.php
b/includes/Model/TopicListEntry.php
index 866ab32..d117a08 100644
--- a/includes/Model/TopicListEntry.php
+++ b/includes/Model/TopicListEntry.php
@@ -8,6 +8,10 @@
protected $topicId;
static public function create( Workflow $topicList, Workflow
$topic ) {
+ // die( var_dump( array(
+ // 'topicList' => $topicList,
+ // 'topic' => $topic,
+ // )));
$obj = new self;
$obj->topicListId = $topicList->getId();
$obj->topicId = $topic->getId();
@@ -16,15 +20,15 @@
static public function fromStorageRow( array $row ) {
$obj = new self;
- $obj->topicListId = $row['topic_list_id'];
- $obj->topicId = $row['topic_id'];
+ $obj->topicListId = new UUID(
$row['topic_list_id'] );
+ $obj->topicId = new UUID( $row['topic_id'] );
return $obj;
}
static public function toStorageRow( TopicListEntry $obj ) {
return array(
- 'topic_list_id' =>
$obj->topicListId,
- 'topic_id' => $obj->topicId,
+ 'topic_list_id' =>
$obj->topicListId->getBinary(),
+ 'topic_id' =>
$obj->topicId->getBinary(),
);
}
diff --git a/includes/Model/UUID.php b/includes/Model/UUID.php
new file mode 100644
index 0000000..e91a3f7
--- /dev/null
+++ b/includes/Model/UUID.php
@@ -0,0 +1,43 @@
+<?php
+
+namespace Flow\Model;
+
+class UUID {
+ protected $binaryValue;
+
+ function __construct( $input = false ) {
+ if ( is_object( $input ) ) {
+ if ( $input instanceof UUID ) {
+ $this->binaryValue = $input->getBinary();
+ } else {
+ throw new MWException( "Got unknown input of
type " . get_class( $input ) );
+ }
+ } elseif ( strlen($input) == 16 ) {
+ $this->binaryValue = $input;
+ } elseif ( strlen($input) == 32 && preg_match(
'/^[a-fA-F0-9]+$/', $input ) ) {
+ $this->binaryValue = pack( 'H*', $input );
+ } elseif ( is_numeric( $input ) ) {
+ $this->binaryValue = pack( 'H*', wfBaseConvert( $input,
10, 16 ) );
+ } elseif ( $input === false ) {
+ $this->binaryValue = pack( 'H*',
\UIDGenerator::newTimestampedUID128( 16 ) );
+ } else {
+ throw new \MWException( "Unknown input to UUID class" );
+ }
+ }
+
+ public function __toString() {
+ return $this->getHex();
+ }
+
+ public function getHex() {
+ return bin2hex( $this->binaryValue );
+ }
+
+ public function getBinary() {
+ return $this->binaryValue;
+ }
+
+ public function getNumber() {
+ return wfBaseConvert( $this->getHex(), 16, 10 );
+ }
+}
\ No newline at end of file
diff --git a/includes/Model/Workflow.php b/includes/Model/Workflow.php
index c5b7b5b..b976296 100644
--- a/includes/Model/Workflow.php
+++ b/includes/Model/Workflow.php
@@ -3,7 +3,6 @@
namespace Flow\Model;
use Title;
-use UIDGenerator;
use User;
class Workflow {
@@ -22,21 +21,24 @@
static public function fromStorageRow( array $row ) {
$obj = new self;
- $obj->id = $row['workflow_id'];
+ $obj->id = new UUID( $row['workflow_id'] );
$obj->wiki = $row['workflow_wiki'];
- $obj->pageId = $row['workflow_page_id'];
+ $obj->pageId = new UUID( $row['workflow_page_id'] );
$obj->namespace = (int) $row['workflow_namespace'];
$obj->titleText = $row['workflow_title_text'];
$obj->userId = $row['workflow_user_id'];
$obj->userText = $row['workflow_user_text'];
$obj->lockState = $row['workflow_lock_state'];
- $obj->definitionId = $row['workflow_definition_id'];
+ $obj->definitionId = new UUID( $row['workflow_definition_id'] );
return $obj;
}
static public function toStorageRow( Workflow $obj ) {
+ if ( ! $obj->definitionId instanceof UUID ) {
+ die( var_dump( $obj->definitionId, $obj ) );
+ }
return array(
- 'workflow_id' => $obj->id,
+ 'workflow_id' => $obj->id->getBinary(),
'workflow_wiki' => $obj->wiki,
'workflow_page_id' => $obj->pageId,
'workflow_namespace' => $obj->namespace,
@@ -44,7 +46,7 @@
'workflow_user_id' => $obj->userId,
'workflow_user_text' => $obj->userText,
'workflow_lock_state' => $obj->lockState,
- 'workflow_definition_id' => $obj->definitionId,
+ 'workflow_definition_id' =>
$obj->definitionId->getBinary(),
);
}
@@ -62,7 +64,7 @@
$obj = new self;
// Probably unnecessary to create id up front?
// simpler in prototype to give everything an id up front?
- $obj->id = UIDGenerator::newTimestampedUID128();
+ $obj->id = new UUID();
$obj->isNew = true; // new as of this request
$obj->wiki = $definition->getWiki();
$obj->pageId = $title->getArticleID();
@@ -72,6 +74,10 @@
$obj->userText = $user->getName();
$obj->lockState = 0;
$obj->definitionId = $definition->getId();
+
+ if ( ! $obj->definitionId instanceof UUID ) {
+ die( var_dump( $obj, $definition ) );
+ }
return $obj;
}
diff --git a/includes/Repository/MultiGetList.php
b/includes/Repository/MultiGetList.php
index 0f9622e..bbdfae3 100644
--- a/includes/Repository/MultiGetList.php
+++ b/includes/Repository/MultiGetList.php
@@ -14,7 +14,9 @@
$key = implode( ':', (array) $key );
$cacheKeys = array();
foreach ( $ids as $id ) {
- if ( !is_scalar( $id ) ) {
+ if ( $id instanceof \Flow\Model\UUID ) {
+ $id = $id->getHex();
+ } elseif ( !is_scalar( $id ) ) {
throw new \InvalidArgumentException( 'Not
scalar:' . gettype( $id ) );
}
$cacheKeys[wfForeignMemcKey( 'flow', '', $key, $id )] =
$id;
diff --git a/special/SpecialFlow.php b/special/SpecialFlow.php
index 3e33c2a..988589e 100644
--- a/special/SpecialFlow.php
+++ b/special/SpecialFlow.php
@@ -77,6 +77,7 @@
$repo = $this->container['storage.definition'];
$id = $this->getRequest()->getVal( 'definition' );
if ( $id !== null ) {
+ $id = new Flow\Model\UUID( $id );
$definition = $repo->get( $id );
if ( $definition === null ) {
throw new MWException( "Unknown flow id '$id'
requested" );
--
To view, visit https://gerrit.wikimedia.org/r/78085
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I80d84a44a1b760864dcf6eefd55964f19e487c9f
Gerrit-PatchSet: 1
Gerrit-Project: mediawiki/extensions/Flow
Gerrit-Branch: master
Gerrit-Owner: Werdna <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits