MarcoAurelio has uploaded a new change for review.
https://gerrit.wikimedia.org/r/280695
Change subject: Bring back requests.php
......................................................................
Bring back requests.php
Won't be working until major updates are made to the code, though.
Change-Id: I015974413a388cf0e85c537569223544d8f2be5e
---
A Requests/database.class.php
A Requests/requests.php
2 files changed, 647 insertions(+), 0 deletions(-)
git pull ssh://gerrit.wikimedia.org:29418/labs/tools/stewardbots
refs/changes/95/280695/1
diff --git a/Requests/database.class.php b/Requests/database.class.php
new file mode 100644
index 0000000..040c641
--- /dev/null
+++ b/Requests/database.class.php
@@ -0,0 +1,315 @@
+<?php
+class TSDatabase
+{
+ //Singleton implementation
+ /*
+ IRC 3 Dec 2009
+ s1: enwiki
+ s2: various medium-sized wikis
+ s3: all wikis not on another cluster
+ s4: commons
+ s5: dewiki
+ s6: fr/ja/ruwiki
+
+ sql-toolserver: toolserver
+ sql: user databases
+
+ Distinct servers:
+ s1, s2+s5, s3+s4+s6, sql, sql-toolserver
+
+ Commons is available on all 3 servers.
+ */
+ private static $instance;
+
+ public $link = array();
+ public $status = array();
+ private $_randServer = '';
+ private $_servers = array('sql', 'sql-s1', 'sql-s2', 'sql-s3', 'sql-s4',
'sql-s5', 'sql-s6');
+ private $_replag = array();
+ private $_dbconn = array();
+ private $_mysqlconf;
+
+ public function __clone() {}
+
+ private function __construct()
+ {
+ $this->_setAllStatus();
+ $this->_mysqlconf =
parse_ini_file('/data/project/stewardbots/.my.cnf');
+ $this->_randServer = $this->_getRandServer();
+ $this->_connectHost($this->_randServer); // Need a connection for
mysql_real_escape_string
+
+ }
+
+ public static function singleton()
+ {
+ if (!isset(self::$instance)) {
+ $c = __CLASS__;
+ self::$instance = new $c();
+ }
+
+ return self::$instance;
+ }
+
+ function __destruct()
+ {
+ foreach($this->_servers as $s) {
+ if ($this->_dbconn[$s] === True) {
+ mysql_close($this->link[$s]);
+ }
+ }
+ }
+
+ /****************************************************
+ Public functions
+ ****************************************************/
+ function performQuery($sql, $server = 'any')
+ {
+ // Query can be performed on any server.
+ if ($server == 'any') {
+ $server = $this->_randServer;
+ }
+
+ if (!isset($this->_dbconn[$server])) {
+ $this->_connectHost($server);
+ }
+
+ if ($this->_dbconn[$server] === True) {
+ $link = $this->link[$server];
+ $q = mysql_query($sql, $link);
+ return $q;
+ } else {
+ return False;
+ }
+
+ }
+
+ //Backwards compatibility
+ function performUserQuery($sql)
+ {
+ return $this->performQuery($sql, $server = 'sql');
+
+ }
+
+ function getReplag($server, $machine = False)
+ {
+ if (!array_key_exists($server, $this->_replag)) {
+ $this->_setReplag();
+ }
+
+ if ($machine) {
+ return $this->_replag[$server][0];
+ } else {
+ return $this->_replag[$server][1];
+ }
+ }
+
+ function getAllReplag($machine = False)
+ {
+ $replag = array();
+ foreach($this->_servers as $s) {
+ if ($s != 'sql') {
+ $replag[$s] = $this->getReplag($s);
+ }
+ }
+ return $replag;
+ }
+
+ function getWarning()
+ {
+ $warning = '';
+
+ foreach ($this->_servers as $s) {
+ if ($this->status[$s][0] == 'ERRO' || $this->status[$s][0] ==
'DOWN') {
+ $class = 'erro';
+ }
+ if ($this->status[$s][0] != 'OK' ) {
+ $warning .= '<li ' . ($class ? ' class="' . $class . '"' : '')
. '>Cluster ' . $s . ': ' . $this->status[$s][0] . ' - ' . $this->status[$s][1]
. '</li>';
+ }
+ }
+
+ if (!empty($warning)) {
+ $warning = '<h3>Database status:</h3><ul>' . $warning . '</ul>';
+ }
+
+ if (file_exists('/var/www/sitenotice')) {
+ $notice = file_get_contents('/var/www/sitenotice');
+ }
+
+ $notice = (!empty($notice) ? '<h3>Notification:</h3>' . $notice : '');
+
+ if (!empty($warning) || !empty($notice)) {
+ return '<div class="warning">' . $warning . $notice . '</div>';
+ } else {
+ return '';
+ }
+ }
+
+ function getCluster($domain)
+ {
+ $sql = "SELECT server FROM toolserver.wiki WHERE domain = '" . $domain
. "'";
+ $q = $this->performQuery($sql, $server = 'any');
+ if ($q) {
+ $result = mysql_fetch_assoc($q);
+ return 'sql-s' . $result['server'];
+ }
+ }
+
+ function getDatabase($domain)
+ {
+ $sql = "SELECT dbname FROM toolserver.wiki WHERE domain = '" . $domain
. "'";
+ $q = $this->performQuery($sql, $server = 'any');
+ if ($q) {
+ $result = mysql_fetch_assoc($q);
+ return $result['dbname'];
+ }
+ }
+
+ function getDomain($dbname)
+ {
+ $sql = "SELECT domain FROM toolserver.wiki WHERE dbname = '" . $dbname
. "'";
+ $q = $this->performQuery($sql, $server = 'any');
+ if ($q) {
+ $result = mysql_fetch_assoc($q);
+ return $result['domain'];
+ }
+ }
+
+
+ function getNamespace($ns_id, $db_name)
+ {
+ $sql = "SELECT ns_name FROM toolserver.namespace WHERE dbname = '" .
$db_name . "' AND ns_id = " . $ns_id;
+ $q = $this->performQuery($sql, $server = 'any');
+ if ($q) {
+ $result = mysql_fetch_assoc($q);
+ if ($result['ns_name'] == 'Article') {
+ return '';
+ } else {
+ return $result['ns_name'];
+ }
+ }
+ }
+
+ function getNamespaceID($ns_name, $db_name)
+ {
+ $sql = "SELECT ns_id FROM toolserver.namespace WHERE dbname = '" .
$db_name . "' AND ns_name = '" . $ns_name . "'";
+ $q = $this->performQuery($sql, $server = 'any');
+ if ($q) {
+ $result = mysql_fetch_assoc($q);
+ return $result['ns_id'];
+ }
+ }
+
+
+ /****************************************************
+ Private functions
+ ****************************************************/
+ private function _setStatus($text, $server)
+ {
+ // Don't use named groups, annoying php
+ // $match = preg_match('/^(?P<status>[a-zA-Z]+?)\;(?P<msg>.*?)$/m',
$txt, $m);
+ $match = preg_match('/^([a-zA-Z]+?)\;(.*?)$/m', $text, $m);
+ if ($match) {
+ $this->status[$server] = array($m[1], $m[2]);
+ } else {
+ $this->status[$server] = array('UNKNOWN', '');
+ }
+
+ if ($this->status[$server][0] == 'ERRO' || $this->status[$server][0]
== 'DOWN') {
+ $this->_dbup[$server] = False;
+ } else {
+ $this->_dbup[$server] = True;
+ }
+ }
+
+ private function _setAllStatus()
+ {
+ foreach ($this->_servers as $s) {
+ if ($s != 'sql') {
+ $f = '/var/www/status_' . substr($s, 4);
+ } else {
+ $f = '/var/www/status_sql';
+ }
+
+ if (file_exists($f)) {
+ $status = file_get_contents($f);
+ $this->_setStatus($status, $s);
+ } else {
+ $this->_setStatus('Unknown', $s);
+ }
+ }
+ }
+
+ private function _getRandServer()
+ {
+ $servers = $this->_servers;
+ while (count($servers) > 0) {
+ $randKey = array_rand($servers);
+ $s = $servers[$randKey];
+ if ($this->_dbup[$s]) {
+ return $s;
+ } else {
+ unset($server[$randKey]);
+ }
+ }
+ }
+
+ private function _connectHost($host)
+ {
+ $this->link[$host] = @mysql_connect($host, $this->_mysqlconf['user'],
$this->_mysqlconf['password']);
+ if ($this->link[$host]) {
+ $this->_dbconn[$host] = True;
+ } else {
+ $this->_dbconn[$host] = False;
+ }
+
+ }
+ private function _setReplag()
+ {
+ foreach ($this->_servers as $s) {
+ if ($s != 'sql') {
+ unset($r);
+ switch($s) {
+ case 'sql-s1':
+ $dbname = 'enwiki_p';
+ break;
+ case 'sql-s2':
+ $dbname = 'nlwiki_p';
+ break;
+ case 'sql-s3':
+ $dbname = 'eswiki_p';
+ break;
+ case 'sql-s4':
+ $dbname = 'commonswiki_p';
+ break;
+ case 'sql-s5':
+ $dbname = 'dewiki_p';
+ break;
+ case 'sql-s6':
+ $dbname = 'frwiki_p';
+ break;
+ }
+
+ if (isset($dbname)) {
+ $sql = 'SELECT
time_to_sec(timediff(now()+0,rev_timestamp)) FROM ' . $dbname . '.revision
ORDER BY rev_timestamp DESC LIMIT 1';
+ $q = $this->performQuery($sql, $s);
+ if ($q) {
+ $result = mysql_fetch_array($q, MYSQL_NUM);
+ $r = array($result[0], $this->_timeDiff($result[0]));
+ }
+ }
+
+ $this->_replag[$s] = (isset($r) ? $r : array(-1, 'infinite'));
+ }
+ }
+ }
+
+ private function _timeDiff($time)
+ {
+ $days = ($time - ($time % 86400))/86400;
+ $hours = (($time - $days*86400) - (($time - $days*86400) % 3600))/3600;
+ $minutes = (($time - $days*86400 - $hours*3600) - (($time -
$days*86400 - $hours*3600) % 60))/60;
+ $seconds = $time - $days*86400 - $hours*3600 - $minutes*60;
+ return $days . 'd ' . $hours . 'h ' . $minutes . 'm ' . $seconds . 's
(' . $time .'s)';
+ }
+}
+?>
diff --git a/Requests/requests.php b/Requests/requests.php
new file mode 100644
index 0000000..cea026c
--- /dev/null
+++ b/Requests/requests.php
@@ -0,0 +1,332 @@
+<?php
+function getPages($titles)
+{
+ $URL =
'http://meta.wikimedia.org/w/api.php?action=query&format=php&prop=revisions&rvprop=content&titles=';
+
+ if (is_array($titles)) {
+ foreach ($titles as $t) {
+ $URL .= urlencode($t) . '|';
+ }
+ $URL = rtrim($URL, '|');
+ } else {
+ $URL .= urlencode($titles);
+ }
+ $ch = curl_init($URL);
+ curl_setopt($ch, CURLOPT_RETURNTRANSFER, True);
+ curl_setopt($ch, CURLOPT_USERAGENT, 'Toolserver Bot -
http://toolserver.org/~erwin85');
+ $result = unserialize(curl_exec($ch));
+ curl_close($ch);
+
+ $output = array();
+ if ($result['query']['pages']) {
+ foreach($result['query']['pages'] as $page) {
+ $output[] = array('title' => $page['title'],
+ 'content' => $page['revisions'][0]['*']);
+ }
+
+ return $output;
+ } else {
+ return False;
+ }
+}
+
+function isSteward($user) {
+ global $cluster;
+ global $db;
+ $user = mysql_real_escape_string($user);
+ $sql = 'SELECT 1
+ FROM metawiki_p.user
+ JOIN metawiki_p.user_groups
+ ON ug_user = user_id
+ WHERE user_name = \'' . $user . '\'
+ AND ug_group = \'steward\' LIMIT 1;';
+ $q = $db->performQuery($sql, $cluster);
+ if ($q) {
+ if (mysql_num_rows($q) == 1) {
+ return True;
+ }
+ }
+
+ return False;
+}
+
+function anchorencode($text) {
+ $a = trim($text);
+ $a = preg_replace('/\[\[(?:[^\]]*?)\|([^\]]*?)\]\]/', '${1}', $a);
+ $a = preg_replace('/\[\[([^\]]*?)\]\]/', '${1}', $a);
+
+ // Rest of function from CoreParserFunctions.php
+ // phase3/includes/parser/CoreParserFunctions.php (r54220)
+ $a = urlencode( $a );
+ $a = strtr( $a, array( '%' => '.', '+' => '_' ) );
+ # leave colons alone, however
+ $a = str_replace( '.3A', ':', $a );
+ return $a;
+}
+
+require_once
'/home/project/s/t/e/stewardbots/public_html/inc/database.class.php';
+
+$db = TSDatabase::singleton();
+
+// Page content
+$cacheFile = './cache/requests.php';
+?>
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" dir="ltr">
+<head>
+ <title>Steward requests</title>
+ <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
+ <link href="/~stewardbots/Common.css" rel="stylesheet" type="text/css" />
+ <script type="text/javascript" language="javascript"
src="sorting.js"></script>
+</head>
+<body>
+ <div id="globalWrapper">
+ <div id="content">
+ <h2>Steward requests</h2>
+ <p><b>steward requests</b> is an overview of the steward request
pages.</p>
+<?php
+// Used cached version?
+if ( file_exists($cacheFile) ) {
+ $useCache = True;
+ if ( $_GET['action'] == 'purge' && (time() - filemtime($cacheFile)) > 60) {
+ $useCache = False;
+ }
+} else {
+ $useCache = False;
+}
+
+if ( $useCache ) {
+ echo '<p style="font-style:italic">Using cached data from ' .
strftime('%H:%M, %e %B %Y', filemtime($cacheFile)) . ' (UTC), <a href="' .
$_SERVER['php_self'] . '?action=purge">purge</a>.</p>';
+
+ // User tried to purge, but we decided to use the cache anyway.
+ if ($_GET['action'] == 'purge') {
+ echo '<p style="font-weight:bold;">Note: data can only be purged once
every minute. Please be patient.</p>';
+ }
+
+ include_once $cacheFile;
+} else {
+ // Start output buffering to regenerate chache
+ ob_start();
+
+ $domain = 'meta.wikimedia.org';
+ $cluster = $db->getCluster($domain);
+
+ $requestPages = array(
+ 'Steward requests/Checkuser' => array('title' =>
'Checkuser', 'level' => 3),
+ 'Steward requests/Global' => array('title' =>
'Global', 'level' => 3),
+ 'Steward requests/Global permissions' => array('title'
=> 'Global permissions', 'level' => 3),
+ 'Steward requests/Bot status' => array('title' => 'Bot
status', 'level' => 3),
+ 'Steward requests/Permissions' => array('title' =>
'Permissions', 'level' => 4),
+ 'Steward requests/Username changes' => array('title'
=> 'Username changes', 'level' => 3),
+ 'Steward requests/SUL requests' => array('title' =>
'SUL requests', 'level' => 3),
+ 'Steward requests/Speedy deletions' => array('title'
=> 'Speedy deletions', 'level' => 4),
+ );
+
+ $pages = getPages(array_keys($requestPages));
+ // Loop over steward request pages.
+ foreach($pages as $page) {
+ $requestPage = $requestPages[$page['title']];
+ $sqlTitle = str_replace(' ', '_', $page['title']);
+
+ echo '<h2><a href="http://' . $domain . '/wiki/' . $sqlTitle . '">' .
$requestPage['title'] . '</a></h2>';
+
+ // Get contents.
+ $content = $page['content'];
+
+ $iOpen = 0;
+ $iUnhandled = 0;
+ $aOldest = array(time(), '');
+ $aUsers = array();
+ $aRequests = array();
+
+ if (!$content) {
+ trigger_error('Could not get [[' . $page . ']].', E_USER_WARNING);
+ }
+
+ $offset = (strpos($content, '<!-- bof -->') ? strpos($content, '<!--
bof -->') : 0);
+
+ $requests = preg_split('/^\={' . $requestPage['level'] .
'}([^\=]*?)\={' . $requestPage['level'] . '}$/m', substr($content, $offset), 0,
PREG_SPLIT_NO_EMPTY | PREG_SPLIT_DELIM_CAPTURE);
+
+ // Loop requests
+ for($i = 1; $i < count($requests); $i = $i + 2) {
+ $title = $requests[$i];
+ $text = $requests[$i + 1];
+ $handled = False;
+ $info = array();
+
+ // Ignore closed requests;
+ if (preg_match('/\{\{[Cc]losed(?:\||\}\})/', $text)
+ || preg_match('/\s*?\|status\s*?=\s*?[Dd]one/', $text)
+ || preg_match('/\s*?\|status\s*?=\s*?[Nn]ot done/', $text)
+ || preg_match('/\{\{[Ss]tatus\|(?:[Dd]one|[Nn]ot
done)(?:\||\}\})/', $text)) {
+ $info['status'] = 'Closed';
+ }
+
+ // Find timestamps
+ $timestamps = array();
+ preg_match_all('/\d{2}:\d{2}, \d{1,2} .*? \d{4} \(UTC\)/', $text,
$matches);
+ foreach($matches[0] as $m) {
+ $timestamps[] = strtotime($m);
+ }
+
+ sort($timestamps);
+
+ // If no timestamp is found it's probably an example.
+ if ($timestamps[0] == 0) {
+ $info['status'] = 'Ignored';
+ }
+
+ if ($timestamps[0] < $aOldest[0] && !isset($info['status'])) {
+ $aOldest = array($timestamps[0], $title);
+ }
+
+ $info['t_old'] = strftime('%H:%M, %e %B %Y', $timestamps[0]);
+ $info['t_new'] = strftime('%H:%M, %e %B %Y',
$timestamps[count($timestamps)-1]);
+
+ // Find users
+ $users = array();
+
preg_match_all('/\[\[([Uu]ser:|Special:Contributions\/)(?P<user>[^\|\]]*?)\|[^\]]*?\]\]/',
$text, $matches);
+
+ foreach($matches['user'] as $u) {
+ if(isSteward($u)) {
+ $handled = True;
+ $item = array('name' => $u, 'steward' => True);
+ } else {
+ $item = array('name' => $u, 'steward' => False);
+ }
+ if (!in_array($item, $users)) {
+ $users[] = $item;
+ }
+ }
+ $info['users'] = $users;
+
+ // Update number of unhandled requests.
+ if (!isset($info['status'])) {
+ $iOpen++;
+ if (!$handled) {
+ $iUnhandled++;
+ $info['status'] = 'Unhandled';
+ } else {
+ $info['status'] = 'Handled';
+ }
+ }
+
+ // Add array to main array
+ $aRequests[$title] = $info;
+ }
+
+ // Show statistics
+
+ $sql = 'SELECT user_name
+ FROM metawiki_p.revision
+ JOIN metawiki_p.page
+ ON page_id = rev_page
+ JOIN metawiki_p.user
+ ON user_id = rev_user
+ JOIN metawiki_p.user_groups
+ ON ug_user = user_id
+ WHERE page_namespace = 0
+ AND page_title = \'' . $sqlTitle . '\'
+ AND ug_group = \'steward\'
+ AND rev_timestamp > DATE_SUB(NOW(), INTERVAL 1 MONTH) + 0
+ GROUP BY user_id
+ ORDER BY user_name ASC';
+ $q = $db->performQuery($sql, $cluster);
+
+ if (!$q) {
+ $stewards = 'Unknown';
+ } else {
+ $stewards = array();
+ while($row = mysql_fetch_assoc($q)) {
+ $stewards[] = $row['user_name'];
+ }
+ $stewards = join(', ', $stewards);
+ }
+
+ echo '<p>';
+ echo 'Open requests: ' . $iOpen . ' (<b>' . $iUnhandled . '
unhandled</b>).<br />';
+ if ($iOpen > 0) {
+ echo 'Oldest open request: <i>' . $aOldest[1] . '</i> opened at
<b>' . strftime('%H:%M, %e %B %Y', $aOldest[0]) . '</b>. <br />';
+ }
+ echo 'Recent stewards: <i>' . $stewards . '</i>.';
+ echo '</p>';
+
+ // List open requests
+ if ($iOpen > 0) {
+ echo '<table class="prettytable sortable" style="width: 100%">';
+ echo '<tr><th style="width: 30%">Title</th><th style="width:
20%">First comment</th><th style="width: 20%">Last comment</th><th
style="width: 30%">Users</th>';
+ foreach($aRequests as $title => $info) {
+ if($info['status'] == 'Handled' || $info['status'] ==
'Unhandled'){
+ $sUsers = '';
+ foreach ($info['users'] as $u) {
+ if($u['steward']) {
+ $sUsers .= ', <b>' . $u['name'] . '</b>';
+ } else {
+ $sUsers .= ', ' . $u['name'];
+ }
+ }
+ $sUsers = (strlen($sUsers) > 2 ? substr($sUsers, 2) :
$sUsers);
+ $link = '<a href="http://' . $domain . '/wiki/' .
$sqlTitle . '#' . anchorencode($title) . '">' . $title . '</a>';
+ echo '<tr>';
+ echo '<td>' . $link . '</td><td>' . $info['t_old'] .
'</td><td>' . $info['t_new'] . '</td><td>' . $sUsers . '</td>';
+ echo '</tr>';
+ }
+ }
+ echo '</table>';
+ }
+ }
+
+ // Save results to cache
+ $f = fopen($cacheFile, 'w');
+ fwrite($f, ob_get_contents());
+ fclose($f);
+
+ // Send the output to the browser
+ ob_end_flush();
+}
+?>
+ </div>
+
+ <div id="column-one">
+ <div class="portlet" id="p-logo">
+ <a style="background-image:
url(http://upload.wikimedia.org/wikipedia/commons/thumb/b/be/Wikimedia_Community_Logo-Toolserver.svg/135px-Wikimedia_Community_Logo-Toolserver.svg.png);"
href="<?=$_SERVER['PHP_SELF'];?>"></a>
+ </div>
+ <div class="portlet" id="p-navigation">
+ <h5>Stewards</h5>
+ <div class="pBody">
+ <ul>
+ <li><a
href="http://meta.wikimedia.org/wiki/Stewards">Stewards</a></li>
+ <li><a
href="http://meta.wikimedia.org/wiki/Stewards_policy">Policy</a></li>
+ <li><a
href="http://meta.wikimedia.org/wiki/Steward_handbook">Handbook</a><li>
+ </ul>
+ </div>
+ </div>
+ <div class="portlet" id="p-navigation2">
+ <h5>Steward requests</h5>
+ <div class="pBody">
+ <ul>
+ <li><a
href="http://meta.wikimedia.org/wiki/Steward_requests/Bot_status">Bot
status</a></li>
+ <li><a
href="http://meta.wikimedia.org/wiki/Steward_requests/Checkuser">Checkuser</a></li>
+ <li><a
href="http://meta.wikimedia.org/wiki/Steward_requests/Global">Global</a></li>
+ <li><a
href="http://meta.wikimedia.org/wiki/Steward_requests/Global_permissions">Global
permissions</a></li>
+ <li><a
href="http://meta.wikimedia.org/wiki/Steward_requests/Permissions">Permissions</a></li>
+ <li><a
href="http://meta.wikimedia.org/wiki/Steward_requests/SUL_requests">SUL
requests</a></li>
+ <li><a
href="http://meta.wikimedia.org/wiki/Steward_requests/Username_changes">Username
changes</a></li>
+ <li><a
href="http://meta.wikimedia.org/wiki/Steward_requests/Speedy_deletions">Speedy
deletions</a></li>
+ </ul>
+ </div>
+ </div>
+ </div>
+
+ <div id="footer">
+ <div id="f-poweredbyico">
+ <a href="/"><img style = "border:0; float:left; padding: 5px;"
src="http://tools.wikimedia.de/images/wikimedia-toolserver-button.png"
alt="Powered by Wikimedia Toolserver" title="Powered by Wikimedia Toolserver"
height="31" width="88" /></a>
+ </div>
+ <ul id="f-list">
+ <li id="lastmod">This page was last modified 8 October
2011.</li>
+ <li id="about">This tool is written by <a
href="http://meta.wikimedia.org/wiki/User:Erwin">Erwin</a>.</li>
+ </ul>
+ </div>
+ </div>
+</body>
+</html>
--
To view, visit https://gerrit.wikimedia.org/r/280695
To unsubscribe, visit https://gerrit.wikimedia.org/r/settings
Gerrit-MessageType: newchange
Gerrit-Change-Id: I015974413a388cf0e85c537569223544d8f2be5e
Gerrit-PatchSet: 1
Gerrit-Project: labs/tools/stewardbots
Gerrit-Branch: master
Gerrit-Owner: MarcoAurelio <[email protected]>
_______________________________________________
MediaWiki-commits mailing list
[email protected]
https://lists.wikimedia.org/mailman/listinfo/mediawiki-commits