2009-08-02 19:35:17 +00:00
|
|
|
|
<?php
|
2009-12-12 22:22:26 +00:00
|
|
|
|
/**
|
2010-12-16 19:15:12 +00:00
|
|
|
|
* This program is free software; you can redistribute it and/or modify
|
|
|
|
|
|
* it under the terms of the GNU General Public License as published by
|
|
|
|
|
|
* the Free Software Foundation; either version 2 of the License, or
|
|
|
|
|
|
* (at your option) any later version.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
|
|
* GNU General Public License for more details.
|
|
|
|
|
|
*
|
|
|
|
|
|
* You should have received a copy of the GNU General Public License along
|
|
|
|
|
|
* with this program; if not, write to the Free Software Foundation, Inc.,
|
|
|
|
|
|
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
|
|
|
|
|
* http://www.gnu.org/copyleft/gpl.html
|
|
|
|
|
|
*
|
2009-12-12 22:22:26 +00:00
|
|
|
|
* @file
|
|
|
|
|
|
* @ingroup Maintenance
|
|
|
|
|
|
* @defgroup Maintenance Maintenance
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2015-04-18 09:37:10 +00:00
|
|
|
|
// Bail on old versions of PHP, or if composer has not been run yet to install
|
2016-06-15 00:14:20 +00:00
|
|
|
|
// dependencies.
|
|
|
|
|
|
require_once __DIR__ . '/../includes/PHPVersionCheck.php';
|
2015-04-18 09:37:10 +00:00
|
|
|
|
wfEntryPointCheck( 'cli' );
|
2012-08-27 21:58:28 +00:00
|
|
|
|
|
2018-01-18 02:24:34 +00:00
|
|
|
|
use MediaWiki\Shell\Shell;
|
2017-02-24 16:17:16 +00:00
|
|
|
|
use Wikimedia\Rdbms\DBReplicationWaitError;
|
|
|
|
|
|
|
2012-02-08 16:55:54 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @defgroup MaintenanceArchive Maintenance archives
|
|
|
|
|
|
* @ingroup Maintenance
|
|
|
|
|
|
*/
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
// Define this so scripts can easily find doMaintenance.php
|
2012-08-27 21:58:28 +00:00
|
|
|
|
define( 'RUN_MAINTENANCE_IF_MAIN', __DIR__ . '/doMaintenance.php' );
|
2018-04-12 16:44:51 +00:00
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @deprecated since 1.31
|
|
|
|
|
|
*/
|
2011-01-13 22:58:55 +00:00
|
|
|
|
define( 'DO_MAINTENANCE', RUN_MAINTENANCE_IF_MAIN ); // original name, harmless
|
|
|
|
|
|
|
2009-09-04 06:51:38 +00:00
|
|
|
|
$maintClass = false;
|
2009-08-02 19:35:17 +00:00
|
|
|
|
|
2017-02-10 18:09:05 +00:00
|
|
|
|
use Wikimedia\Rdbms\IDatabase;
|
2015-04-08 01:11:29 +00:00
|
|
|
|
use MediaWiki\Logger\LoggerFactory;
|
2016-08-28 16:06:57 +00:00
|
|
|
|
use MediaWiki\MediaWikiServices;
|
2017-01-26 18:27:37 +00:00
|
|
|
|
use Wikimedia\Rdbms\LBFactory;
|
2017-03-30 20:46:06 +00:00
|
|
|
|
use Wikimedia\Rdbms\IMaintainableDatabase;
|
2015-04-08 01:11:29 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Abstract maintenance class for quickly writing and churning out
|
|
|
|
|
|
* maintenance scripts with minimal effort. All that _must_ be defined
|
|
|
|
|
|
* is the execute() method. See docs/maintenance.txt for more info
|
|
|
|
|
|
* and a quick demo of how to use it.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.16
|
|
|
|
|
|
* @ingroup Maintenance
|
|
|
|
|
|
*/
|
|
|
|
|
|
abstract class Maintenance {
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Constants for DB access type
|
|
|
|
|
|
* @see Maintenance::getDbType()
|
|
|
|
|
|
*/
|
2013-04-18 18:48:44 +00:00
|
|
|
|
const DB_NONE = 0;
|
|
|
|
|
|
const DB_STD = 1;
|
2009-08-02 19:35:17 +00:00
|
|
|
|
const DB_ADMIN = 2;
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2009-08-02 21:42:47 +00:00
|
|
|
|
// Const for getStdin()
|
|
|
|
|
|
const STDIN_ALL = 'all';
|
2009-08-02 19:35:17 +00:00
|
|
|
|
|
|
|
|
|
|
// This is the desired params
|
2016-02-17 09:09:32 +00:00
|
|
|
|
protected $mParams = [];
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2011-04-06 18:38:48 +00:00
|
|
|
|
// Array of mapping short parameters to long ones
|
2016-02-17 09:09:32 +00:00
|
|
|
|
protected $mShortParamsMap = [];
|
2011-04-06 18:38:48 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
// Array of desired args
|
2016-02-17 09:09:32 +00:00
|
|
|
|
protected $mArgList = [];
|
2009-08-02 19:35:17 +00:00
|
|
|
|
|
|
|
|
|
|
// This is the list of options that were actually passed
|
2016-02-17 09:09:32 +00:00
|
|
|
|
protected $mOptions = [];
|
2009-08-02 19:35:17 +00:00
|
|
|
|
|
|
|
|
|
|
// This is the list of arguments that were actually passed
|
2016-02-17 09:09:32 +00:00
|
|
|
|
protected $mArgs = [];
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2018-08-02 20:10:31 +00:00
|
|
|
|
// Allow arbitrary options to be passed, or only specified ones?
|
|
|
|
|
|
protected $mAllowUnregisteredOptions = false;
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
// Name of the script currently running
|
|
|
|
|
|
protected $mSelf;
|
|
|
|
|
|
|
|
|
|
|
|
// Special vars for params that are always used
|
2009-09-04 08:02:00 +00:00
|
|
|
|
protected $mQuiet = false;
|
|
|
|
|
|
protected $mDbUser, $mDbPass;
|
2009-08-02 19:35:17 +00:00
|
|
|
|
|
2016-01-30 02:48:47 +00:00
|
|
|
|
// A description of the script, children should change this via addDescription()
|
2009-08-02 19:35:17 +00:00
|
|
|
|
protected $mDescription = '';
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
// Have we already loaded our user input?
|
2009-09-04 08:02:00 +00:00
|
|
|
|
protected $mInputLoaded = false;
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2011-03-30 19:00:11 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Batch size. If a script supports this, they should set
|
|
|
|
|
|
* a default with setBatchSize()
|
|
|
|
|
|
*
|
|
|
|
|
|
* @var int
|
|
|
|
|
|
*/
|
2009-08-02 19:35:17 +00:00
|
|
|
|
protected $mBatchSize = null;
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2011-03-01 20:42:55 +00:00
|
|
|
|
// Generic options added by addDefaultParams()
|
2016-02-17 09:09:32 +00:00
|
|
|
|
private $mGenericParameters = [];
|
2011-03-01 20:42:55 +00:00
|
|
|
|
// Generic options which might or not be supported by the script
|
2016-02-17 09:09:32 +00:00
|
|
|
|
private $mDependantParameters = [];
|
2011-03-01 20:42:55 +00:00
|
|
|
|
|
2012-04-07 21:07:32 +00:00
|
|
|
|
/**
|
2014-12-12 08:41:27 +00:00
|
|
|
|
* Used by getDB() / setDB()
|
2017-03-30 20:46:06 +00:00
|
|
|
|
* @var IMaintainableDatabase
|
2012-04-07 21:07:32 +00:00
|
|
|
|
*/
|
2011-05-24 17:48:22 +00:00
|
|
|
|
private $mDb = null;
|
|
|
|
|
|
|
2015-12-31 23:30:16 +00:00
|
|
|
|
/** @var float UNIX timestamp */
|
2016-09-06 01:46:11 +00:00
|
|
|
|
private $lastReplicationWait = 0.0;
|
2015-12-31 23:30:16 +00:00
|
|
|
|
|
2012-11-22 03:53:24 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Used when creating separate schema files.
|
|
|
|
|
|
* @var resource
|
|
|
|
|
|
*/
|
|
|
|
|
|
public $fileHandle;
|
|
|
|
|
|
|
2014-08-07 20:57:14 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Accessible via getConfig()
|
|
|
|
|
|
*
|
|
|
|
|
|
* @var Config
|
|
|
|
|
|
*/
|
|
|
|
|
|
private $config;
|
|
|
|
|
|
|
2016-07-28 21:44:06 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @see Maintenance::requireExtension
|
|
|
|
|
|
* @var array
|
|
|
|
|
|
*/
|
|
|
|
|
|
private $requiredExtensions = [];
|
|
|
|
|
|
|
2015-12-29 22:15:12 +00:00
|
|
|
|
/**
|
2015-12-30 20:43:10 +00:00
|
|
|
|
* Used to read the options in the order they were passed.
|
|
|
|
|
|
* Useful for option chaining (Ex. dumpBackup.php). It will
|
|
|
|
|
|
* be an empty array if the options are passed in through
|
|
|
|
|
|
* loadParamsAndArgs( $self, $opts, $args ).
|
2015-12-29 22:15:12 +00:00
|
|
|
|
*
|
|
|
|
|
|
* This is an array of arrays where
|
2015-12-30 20:43:10 +00:00
|
|
|
|
* 0 => the option and 1 => parameter value.
|
2015-12-29 22:15:12 +00:00
|
|
|
|
*
|
|
|
|
|
|
* @var array
|
|
|
|
|
|
*/
|
2016-02-17 09:09:32 +00:00
|
|
|
|
public $orderedOptions = [];
|
2015-12-29 22:15:12 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
2010-12-13 15:30:41 +00:00
|
|
|
|
* Default constructor. Children should call this *first* if implementing
|
2009-08-02 19:35:17 +00:00
|
|
|
|
* their own constructors
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function __construct() {
|
2010-12-13 15:30:41 +00:00
|
|
|
|
// Setup $IP, using MW_INSTALL_PATH if it exists
|
|
|
|
|
|
global $IP;
|
|
|
|
|
|
$IP = strval( getenv( 'MW_INSTALL_PATH' ) ) !== ''
|
|
|
|
|
|
? getenv( 'MW_INSTALL_PATH' )
|
2012-08-27 19:03:15 +00:00
|
|
|
|
: realpath( __DIR__ . '/..' );
|
2010-12-13 15:30:41 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$this->addDefaultParams();
|
2016-02-17 09:09:32 +00:00
|
|
|
|
register_shutdown_function( [ $this, 'outputChanneled' ], false );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-01-13 22:58:55 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Should we execute the maintenance script, or just allow it to be included
|
|
|
|
|
|
* as a standalone class? It checks that the call stack only includes this
|
2011-07-23 19:50:19 +00:00
|
|
|
|
* function and "requires" (meaning was called from the file scope)
|
2011-01-13 22:58:55 +00:00
|
|
|
|
*
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @return bool
|
2011-01-13 22:58:55 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public static function shouldExecute() {
|
2014-06-25 20:39:58 +00:00
|
|
|
|
global $wgCommandLineMode;
|
|
|
|
|
|
|
|
|
|
|
|
if ( !function_exists( 'debug_backtrace' ) ) {
|
|
|
|
|
|
// If someone has a better idea...
|
|
|
|
|
|
return $wgCommandLineMode;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-01-13 22:58:55 +00:00
|
|
|
|
$bt = debug_backtrace();
|
2011-07-25 20:11:00 +00:00
|
|
|
|
$count = count( $bt );
|
|
|
|
|
|
if ( $count < 2 ) {
|
2011-07-23 19:50:19 +00:00
|
|
|
|
return false; // sanity
|
2011-01-13 22:58:55 +00:00
|
|
|
|
}
|
2018-01-13 00:02:09 +00:00
|
|
|
|
if ( $bt[0]['class'] !== self::class || $bt[0]['function'] !== 'shouldExecute' ) {
|
2011-07-23 19:50:19 +00:00
|
|
|
|
return false; // last call should be to this function
|
|
|
|
|
|
}
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$includeFuncs = [ 'require_once', 'require', 'include', 'include_once' ];
|
2013-04-18 18:48:44 +00:00
|
|
|
|
for ( $i = 1; $i < $count; $i++ ) {
|
2011-07-23 19:50:19 +00:00
|
|
|
|
if ( !in_array( $bt[$i]['function'], $includeFuncs ) ) {
|
|
|
|
|
|
return false; // previous calls should all be "requires"
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2014-04-23 18:08:06 +00:00
|
|
|
|
|
2011-07-23 19:50:19 +00:00
|
|
|
|
return true;
|
2011-01-13 22:58:55 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Do the actual work. All child classes will need to implement this
|
2018-05-20 14:26:46 +00:00
|
|
|
|
*
|
2018-06-24 20:03:48 +00:00
|
|
|
|
* @return bool|null|void True for success, false for failure. Not returning
|
2018-05-20 14:26:46 +00:00
|
|
|
|
* a value, or returning null, is also interpreted as success. Returning
|
|
|
|
|
|
* false for failure will cause doMaintenance.php to exit the process
|
|
|
|
|
|
* with a non-zero exit status.
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
|
|
|
|
|
abstract public function execute();
|
|
|
|
|
|
|
2018-08-02 20:10:31 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Checks to see if a particular option in supported. Normally this means it
|
|
|
|
|
|
* has been registered by the script via addOption.
|
|
|
|
|
|
* @param string $name The name of the option
|
|
|
|
|
|
* @return bool true if the option exists, false otherwise
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function supportsOption( $name ) {
|
|
|
|
|
|
return isset( $this->mParams[$name] );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Add a parameter to the script. Will be displayed on --help
|
|
|
|
|
|
* with the associated description
|
|
|
|
|
|
*
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param string $name The name of the param (help, version, etc)
|
|
|
|
|
|
* @param string $description The description of the param to show on --help
|
|
|
|
|
|
* @param bool $required Is the param required?
|
|
|
|
|
|
* @param bool $withArg Is an argument required with this option?
|
2017-03-30 20:46:06 +00:00
|
|
|
|
* @param string|bool $shortName Character to use as short name
|
2015-12-29 22:15:12 +00:00
|
|
|
|
* @param bool $multiOccurrence Can this option be passed multiple times?
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
2014-04-23 11:45:13 +00:00
|
|
|
|
protected function addOption( $name, $description, $required = false,
|
2015-12-29 22:15:12 +00:00
|
|
|
|
$withArg = false, $shortName = false, $multiOccurrence = false
|
2014-04-23 11:45:13 +00:00
|
|
|
|
) {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$this->mParams[$name] = [
|
2014-04-23 11:45:13 +00:00
|
|
|
|
'desc' => $description,
|
|
|
|
|
|
'require' => $required,
|
|
|
|
|
|
'withArg' => $withArg,
|
2015-12-29 22:15:12 +00:00
|
|
|
|
'shortName' => $shortName,
|
|
|
|
|
|
'multiOccurrence' => $multiOccurrence
|
2016-02-17 09:09:32 +00:00
|
|
|
|
];
|
2014-04-23 11:45:13 +00:00
|
|
|
|
|
2011-04-06 18:38:48 +00:00
|
|
|
|
if ( $shortName !== false ) {
|
|
|
|
|
|
$this->mShortParamsMap[$shortName] = $name;
|
|
|
|
|
|
}
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
2018-08-02 20:10:31 +00:00
|
|
|
|
* Checks to see if a particular option exists.
|
|
|
|
|
|
* @param string $name The name of the option
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @return bool
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
|
|
|
|
|
protected function hasOption( $name ) {
|
2009-08-15 14:25:52 +00:00
|
|
|
|
return isset( $this->mOptions[$name] );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
2015-12-29 22:15:12 +00:00
|
|
|
|
* Get an option, or return the default.
|
|
|
|
|
|
*
|
|
|
|
|
|
* If the option was added to support multiple occurrences,
|
|
|
|
|
|
* this will return an array.
|
|
|
|
|
|
*
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param string $name The name of the param
|
2018-06-26 21:14:43 +00:00
|
|
|
|
* @param mixed|null $default Anything you want, default null
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @return mixed
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
|
|
|
|
|
protected function getOption( $name, $default = null ) {
|
2010-05-22 16:50:39 +00:00
|
|
|
|
if ( $this->hasOption( $name ) ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
return $this->mOptions[$name];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// Set it so we don't have to provide the default again
|
|
|
|
|
|
$this->mOptions[$name] = $default;
|
2014-04-23 18:08:06 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
return $this->mOptions[$name];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
2009-08-18 23:06:24 +00:00
|
|
|
|
* Add some args that are needed
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param string $arg Name of the arg, like 'start'
|
|
|
|
|
|
* @param string $description Short description of the arg
|
|
|
|
|
|
* @param bool $required Is this required?
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
2009-08-18 23:06:24 +00:00
|
|
|
|
protected function addArg( $arg, $description, $required = true ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$this->mArgList[] = [
|
2009-08-18 23:06:24 +00:00
|
|
|
|
'name' => $arg,
|
2010-05-22 16:50:39 +00:00
|
|
|
|
'desc' => $description,
|
|
|
|
|
|
'require' => $required
|
2016-02-17 09:09:32 +00:00
|
|
|
|
];
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2010-08-26 20:52:21 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Remove an option. Useful for removing options that won't be used in your script.
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param string $name The option to remove.
|
2010-08-26 20:52:21 +00:00
|
|
|
|
*/
|
|
|
|
|
|
protected function deleteOption( $name ) {
|
|
|
|
|
|
unset( $this->mParams[$name] );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2018-08-02 20:10:31 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Sets whether to allow unregistered options, which are options passed to
|
|
|
|
|
|
* a script that do not match an expected parameter.
|
|
|
|
|
|
* @param bool $allow Should we allow?
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function setAllowUnregisteredOptions( $allow ) {
|
|
|
|
|
|
$this->mAllowUnregisteredOptions = $allow;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2010-08-26 20:52:21 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Set the description text.
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param string $text The text of the description
|
2010-08-26 20:52:21 +00:00
|
|
|
|
*/
|
|
|
|
|
|
protected function addDescription( $text ) {
|
|
|
|
|
|
$this->mDescription = $text;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Does a given argument exist?
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param int $argId The integer value (from zero) for the arg
|
|
|
|
|
|
* @return bool
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
|
|
|
|
|
protected function hasArg( $argId = 0 ) {
|
2009-08-15 14:25:52 +00:00
|
|
|
|
return isset( $this->mArgs[$argId] );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Get an argument.
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param int $argId The integer value (from zero) for the arg
|
2018-06-26 21:14:43 +00:00
|
|
|
|
* @param mixed|null $default The default if it doesn't exist
|
2009-08-02 19:35:17 +00:00
|
|
|
|
* @return mixed
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function getArg( $argId = 0, $default = null ) {
|
2009-08-15 14:25:52 +00:00
|
|
|
|
return $this->hasArg( $argId ) ? $this->mArgs[$argId] : $default;
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2017-11-05 08:09:51 +00:00
|
|
|
|
* Returns batch size
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.31
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return int|null
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function getBatchSize() {
|
|
|
|
|
|
return $this->mBatchSize;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2009-08-02 19:35:17 +00:00
|
|
|
|
* Set the batch size.
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param int $s The number of operations to do in a batch
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
|
|
|
|
|
protected function setBatchSize( $s = 0 ) {
|
|
|
|
|
|
$this->mBatchSize = $s;
|
2011-08-25 05:15:45 +00:00
|
|
|
|
|
|
|
|
|
|
// If we support $mBatchSize, show the option.
|
|
|
|
|
|
// Used to be in addDefaultParams, but in order for that to
|
|
|
|
|
|
// work, subclasses would have to call this function in the constructor
|
|
|
|
|
|
// before they called parent::__construct which is just weird
|
|
|
|
|
|
// (and really wasn't done).
|
|
|
|
|
|
if ( $this->mBatchSize ) {
|
|
|
|
|
|
$this->addOption( 'batch-size', 'Run this many operations ' .
|
|
|
|
|
|
'per batch, default: ' . $this->mBatchSize, false, true );
|
|
|
|
|
|
if ( isset( $this->mParams['batch-size'] ) ) {
|
|
|
|
|
|
// This seems a little ugly...
|
|
|
|
|
|
$this->mDependantParameters['batch-size'] = $this->mParams['batch-size'];
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Get the script's name
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @return string
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function getName() {
|
|
|
|
|
|
return $this->mSelf;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Return input from stdin.
|
2018-06-26 21:14:43 +00:00
|
|
|
|
* @param int|null $len The number of bytes to read. If null, just return the handle.
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* Maintenance::STDIN_ALL returns the full length
|
|
|
|
|
|
* @return mixed
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
|
|
|
|
|
protected function getStdin( $len = null ) {
|
2017-07-23 01:24:09 +00:00
|
|
|
|
if ( $len == self::STDIN_ALL ) {
|
2009-08-02 21:42:47 +00:00
|
|
|
|
return file_get_contents( 'php://stdin' );
|
2010-05-30 10:08:13 +00:00
|
|
|
|
}
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$f = fopen( 'php://stdin', 'rt' );
|
2010-05-30 10:08:13 +00:00
|
|
|
|
if ( !$len ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
return $f;
|
2010-05-30 10:08:13 +00:00
|
|
|
|
}
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$input = fgets( $f, $len );
|
2009-08-15 14:25:52 +00:00
|
|
|
|
fclose( $f );
|
2014-04-23 18:08:06 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
return rtrim( $input );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2012-02-16 00:54:34 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @return bool
|
|
|
|
|
|
*/
|
2010-10-27 14:38:31 +00:00
|
|
|
|
public function isQuiet() {
|
|
|
|
|
|
return $this->mQuiet;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Throw some output to the user. Scripts can call this with no fears,
|
|
|
|
|
|
* as we handle all --quiet stuff here
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param string $out The text to show to the user
|
2018-06-26 21:14:43 +00:00
|
|
|
|
* @param mixed|null $channel Unique identifier for the channel. See function outputChanneled.
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
2010-01-12 19:06:41 +00:00
|
|
|
|
protected function output( $out, $channel = null ) {
|
2018-01-04 23:19:55 +00:00
|
|
|
|
// This is sometimes called very early, before Setup.php is included.
|
|
|
|
|
|
if ( class_exists( MediaWikiServices::class ) ) {
|
|
|
|
|
|
// Try to periodically flush buffered metrics to avoid OOMs
|
|
|
|
|
|
$stats = MediaWikiServices::getInstance()->getStatsdDataFactory();
|
|
|
|
|
|
if ( $stats->getDataCount() > 1000 ) {
|
|
|
|
|
|
MediaWiki::emitBufferedStatsdData( $stats, $this->getConfig() );
|
|
|
|
|
|
}
|
2017-12-02 21:12:41 +00:00
|
|
|
|
}
|
2018-01-04 23:19:55 +00:00
|
|
|
|
|
2010-05-22 16:50:39 +00:00
|
|
|
|
if ( $this->mQuiet ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
2010-06-04 01:43:56 +00:00
|
|
|
|
if ( $channel === null ) {
|
2010-07-15 16:27:27 +00:00
|
|
|
|
$this->cleanupChanneled();
|
2013-05-09 18:06:03 +00:00
|
|
|
|
print $out;
|
2011-11-20 18:02:38 +00:00
|
|
|
|
} else {
|
2010-06-04 01:43:56 +00:00
|
|
|
|
$out = preg_replace( '/\n\z/', '', $out );
|
|
|
|
|
|
$this->outputChanneled( $out, $channel );
|
|
|
|
|
|
}
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Throw an error to the user. Doesn't respect --quiet, so don't use
|
|
|
|
|
|
* this for non-error output
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param string $err The error to display
|
2017-11-20 00:36:54 +00:00
|
|
|
|
* @param int $die Deprecated since 1.31, use Maintenance::fatalError() instead
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
2011-06-22 21:48:46 +00:00
|
|
|
|
protected function error( $err, $die = 0 ) {
|
2017-11-20 00:36:54 +00:00
|
|
|
|
if ( intval( $die ) !== 0 ) {
|
|
|
|
|
|
wfDeprecated( __METHOD__ . '( $err, $die )', '1.31' );
|
|
|
|
|
|
$this->fatalError( $err, intval( $die ) );
|
|
|
|
|
|
}
|
2010-01-12 19:06:41 +00:00
|
|
|
|
$this->outputChanneled( false );
|
2018-01-24 13:29:23 +00:00
|
|
|
|
if (
|
|
|
|
|
|
( PHP_SAPI == 'cli' || PHP_SAPI == 'phpdbg' ) &&
|
|
|
|
|
|
!defined( 'MW_PHPUNIT_TEST' )
|
|
|
|
|
|
) {
|
2009-09-18 04:26:30 +00:00
|
|
|
|
fwrite( STDERR, $err . "\n" );
|
|
|
|
|
|
} else {
|
2011-11-15 15:04:36 +00:00
|
|
|
|
print $err;
|
2009-09-18 04:26:30 +00:00
|
|
|
|
}
|
2017-11-20 00:36:54 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Output a message and terminate the current script.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param string $msg Error message
|
|
|
|
|
|
* @param int $exitCode PHP exit status. Should be in range 1-254.
|
|
|
|
|
|
* @since 1.31
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function fatalError( $msg, $exitCode = 1 ) {
|
|
|
|
|
|
$this->error( $msg );
|
|
|
|
|
|
exit( $exitCode );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2010-01-12 19:06:41 +00:00
|
|
|
|
private $atLineStart = true;
|
|
|
|
|
|
private $lastChannel = null;
|
2010-06-27 21:48:51 +00:00
|
|
|
|
|
2010-07-15 16:27:27 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Clean up channeled output. Output a newline if necessary.
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function cleanupChanneled() {
|
|
|
|
|
|
if ( !$this->atLineStart ) {
|
2012-04-15 18:59:07 +00:00
|
|
|
|
print "\n";
|
2010-07-15 16:27:27 +00:00
|
|
|
|
$this->atLineStart = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2010-01-12 19:06:41 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Message outputter with channeled message support. Messages on the
|
|
|
|
|
|
* same channel are concatenated, but any intervening messages in another
|
|
|
|
|
|
* channel start a new line.
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param string $msg The message without trailing newline
|
2018-06-26 21:14:43 +00:00
|
|
|
|
* @param string|null $channel Channel identifier or null for no
|
2010-06-27 21:48:51 +00:00
|
|
|
|
* channel. Channel comparison uses ===.
|
2010-01-12 19:06:41 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function outputChanneled( $msg, $channel = null ) {
|
|
|
|
|
|
if ( $msg === false ) {
|
2010-07-15 16:27:27 +00:00
|
|
|
|
$this->cleanupChanneled();
|
2014-04-23 18:08:06 +00:00
|
|
|
|
|
2010-01-12 19:06:41 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// End the current line if necessary
|
|
|
|
|
|
if ( !$this->atLineStart && $channel !== $this->lastChannel ) {
|
2012-04-15 18:59:07 +00:00
|
|
|
|
print "\n";
|
2010-01-12 19:06:41 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-04-15 18:59:07 +00:00
|
|
|
|
print $msg;
|
2010-01-12 19:06:41 +00:00
|
|
|
|
|
|
|
|
|
|
$this->atLineStart = false;
|
|
|
|
|
|
if ( $channel === null ) {
|
|
|
|
|
|
// For unchanneled messages, output trailing newline immediately
|
2012-04-15 18:59:07 +00:00
|
|
|
|
print "\n";
|
2010-01-12 19:06:41 +00:00
|
|
|
|
$this->atLineStart = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->lastChannel = $channel;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
2009-08-09 13:40:43 +00:00
|
|
|
|
* Does the script need different DB access? By default, we give Maintenance
|
|
|
|
|
|
* scripts normal rights to the DB. Sometimes, a script needs admin rights
|
2010-05-30 10:08:13 +00:00
|
|
|
|
* access for a reason and sometimes they want no access. Subclasses should
|
2009-08-09 13:40:43 +00:00
|
|
|
|
* override and return one of the following values, as needed:
|
2009-08-02 19:35:17 +00:00
|
|
|
|
* Maintenance::DB_NONE - For no DB access at all
|
2009-08-09 13:40:43 +00:00
|
|
|
|
* Maintenance::DB_STD - For normal DB access, default
|
|
|
|
|
|
* Maintenance::DB_ADMIN - For admin DB access
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @return int
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
2010-03-10 12:45:38 +00:00
|
|
|
|
public function getDbType() {
|
2017-07-23 01:24:09 +00:00
|
|
|
|
return self::DB_STD;
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Add the default parameters to the scripts
|
|
|
|
|
|
*/
|
2009-09-04 08:02:00 +00:00
|
|
|
|
protected function addDefaultParams() {
|
2011-03-01 20:42:55 +00:00
|
|
|
|
# Generic (non script dependant) options:
|
|
|
|
|
|
|
2011-04-06 18:38:48 +00:00
|
|
|
|
$this->addOption( 'help', 'Display this help message', false, false, 'h' );
|
|
|
|
|
|
$this->addOption( 'quiet', 'Whether to supress non-error output', false, false, 'q' );
|
2010-05-30 10:08:13 +00:00
|
|
|
|
$this->addOption( 'conf', 'Location of LocalSettings.php, if not default', false, true );
|
|
|
|
|
|
$this->addOption( 'wiki', 'For specifying the wiki ID', false, true );
|
|
|
|
|
|
$this->addOption( 'globals', 'Output globals at the end of processing for debugging' );
|
2014-04-23 11:45:13 +00:00
|
|
|
|
$this->addOption(
|
|
|
|
|
|
'memory-limit',
|
|
|
|
|
|
'Set a specific memory limit for the script, '
|
2018-03-28 01:20:07 +00:00
|
|
|
|
. '"max" for no limit or "default" to avoid changing it',
|
|
|
|
|
|
false,
|
|
|
|
|
|
true
|
2014-04-23 11:45:13 +00:00
|
|
|
|
);
|
2010-11-20 11:14:11 +00:00
|
|
|
|
$this->addOption( 'server', "The protocol and server name to use in URLs, e.g. " .
|
2014-04-23 18:08:06 +00:00
|
|
|
|
"http://en.wikipedia.org. This is sometimes necessary because " .
|
|
|
|
|
|
"server name detection may fail in command line scripts.", false, true );
|
2014-11-21 22:54:57 +00:00
|
|
|
|
$this->addOption( 'profiler', 'Profiler output format (usually "text")', false, true );
|
2017-10-08 20:17:35 +00:00
|
|
|
|
// This is named --mwdebug, because --debug would conflict in the phpunit.php CLI script.
|
|
|
|
|
|
$this->addOption( 'mwdebug', 'Enable built-in MediaWiki development settings', false, true );
|
2011-03-01 20:42:55 +00:00
|
|
|
|
|
|
|
|
|
|
# Save generic options to display them separately in help
|
2013-04-18 18:48:44 +00:00
|
|
|
|
$this->mGenericParameters = $this->mParams;
|
2011-03-01 20:42:55 +00:00
|
|
|
|
|
|
|
|
|
|
# Script dependant options:
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
// If we support a DB, show the options
|
2010-05-22 16:50:39 +00:00
|
|
|
|
if ( $this->getDbType() > 0 ) {
|
2010-05-30 10:08:13 +00:00
|
|
|
|
$this->addOption( 'dbuser', 'The DB user to use for this script', false, true );
|
|
|
|
|
|
$this->addOption( 'dbpass', 'The password to use for this script', false, true );
|
2018-06-12 21:49:52 +00:00
|
|
|
|
$this->addOption( 'dbgroupdefault', 'The default DB group to use.', false, true );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2011-08-25 05:15:45 +00:00
|
|
|
|
|
2011-03-01 20:42:55 +00:00
|
|
|
|
# Save additional script dependant options to display
|
2015-09-11 13:44:59 +00:00
|
|
|
|
# them separately in help
|
2011-03-01 20:42:55 +00:00
|
|
|
|
$this->mDependantParameters = array_diff_key( $this->mParams, $this->mGenericParameters );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2014-08-07 20:57:14 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @since 1.24
|
|
|
|
|
|
* @return Config
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function getConfig() {
|
|
|
|
|
|
if ( $this->config === null ) {
|
2016-11-22 23:39:22 +00:00
|
|
|
|
$this->config = MediaWikiServices::getInstance()->getMainConfig();
|
2014-08-07 20:57:14 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
return $this->config;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @since 1.24
|
|
|
|
|
|
* @param Config $config
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setConfig( Config $config ) {
|
|
|
|
|
|
$this->config = $config;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-07-28 21:44:06 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Indicate that the specified extension must be
|
|
|
|
|
|
* loaded before the script can run.
|
|
|
|
|
|
*
|
|
|
|
|
|
* This *must* be called in the constructor.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.28
|
|
|
|
|
|
* @param string $name
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function requireExtension( $name ) {
|
|
|
|
|
|
$this->requiredExtensions[] = $name;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Verify that the required extensions are installed
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.28
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function checkRequiredExtensions() {
|
|
|
|
|
|
$registry = ExtensionRegistry::getInstance();
|
|
|
|
|
|
$missing = [];
|
|
|
|
|
|
foreach ( $this->requiredExtensions as $name ) {
|
|
|
|
|
|
if ( !$registry->isLoaded( $name ) ) {
|
|
|
|
|
|
$missing[] = $name;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ( $missing ) {
|
|
|
|
|
|
$joined = implode( ', ', $missing );
|
|
|
|
|
|
$msg = "The following extensions are required to be installed "
|
|
|
|
|
|
. "for this script to run: $joined. Please enable them and then try again.";
|
2017-11-20 00:36:54 +00:00
|
|
|
|
$this->fatalError( $msg );
|
2016-07-28 21:44:06 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-08-28 16:06:57 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Set triggers like when to try to run deferred updates
|
|
|
|
|
|
* @since 1.28
|
|
|
|
|
|
*/
|
2016-09-15 09:21:21 +00:00
|
|
|
|
public function setAgentAndTriggers() {
|
|
|
|
|
|
if ( function_exists( 'posix_getpwuid' ) ) {
|
|
|
|
|
|
$agent = posix_getpwuid( posix_geteuid() )['name'];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$agent = 'sysadmin';
|
|
|
|
|
|
}
|
|
|
|
|
|
$agent .= '@' . wfHostname();
|
|
|
|
|
|
|
2016-09-01 16:43:01 +00:00
|
|
|
|
$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
|
2016-09-15 09:21:21 +00:00
|
|
|
|
// Add a comment for easy SHOW PROCESSLIST interpretation
|
|
|
|
|
|
$lbFactory->setAgentName(
|
|
|
|
|
|
mb_strlen( $agent ) > 15 ? mb_substr( $agent, 0, 15 ) . '...' : $agent
|
|
|
|
|
|
);
|
2017-12-02 21:12:41 +00:00
|
|
|
|
self::setLBFactoryTriggers( $lbFactory, $this->getConfig() );
|
2016-09-01 16:43:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* @param LBFactory $LBFactory
|
2017-12-02 21:12:41 +00:00
|
|
|
|
* @param Config $config
|
2016-09-01 16:43:01 +00:00
|
|
|
|
* @since 1.28
|
|
|
|
|
|
*/
|
2017-12-02 21:12:41 +00:00
|
|
|
|
public static function setLBFactoryTriggers( LBFactory $LBFactory, Config $config ) {
|
|
|
|
|
|
$services = MediaWikiServices::getInstance();
|
|
|
|
|
|
$stats = $services->getStatsdDataFactory();
|
2016-08-28 16:06:57 +00:00
|
|
|
|
// Hook into period lag checks which often happen in long-running scripts
|
2017-12-02 21:12:41 +00:00
|
|
|
|
$lbFactory = $services->getDBLoadBalancerFactory();
|
2016-08-28 16:06:57 +00:00
|
|
|
|
$lbFactory->setWaitForReplicationListener(
|
|
|
|
|
|
__METHOD__,
|
2017-12-02 21:12:41 +00:00
|
|
|
|
function () use ( $stats, $config ) {
|
2016-09-01 16:43:01 +00:00
|
|
|
|
// Check config in case of JobRunner and unit tests
|
2017-12-02 21:12:41 +00:00
|
|
|
|
if ( $config->get( 'CommandLineMode' ) ) {
|
2016-09-01 16:43:01 +00:00
|
|
|
|
DeferredUpdates::tryOpportunisticExecute( 'run' );
|
|
|
|
|
|
}
|
2017-12-02 21:12:41 +00:00
|
|
|
|
// Try to periodically flush buffered metrics to avoid OOMs
|
|
|
|
|
|
MediaWiki::emitBufferedStatsdData( $stats, $config );
|
2016-09-01 16:43:01 +00:00
|
|
|
|
}
|
|
|
|
|
|
);
|
|
|
|
|
|
// Check for other windows to run them. A script may read or do a few writes
|
|
|
|
|
|
// to the master but mostly be writing to something else, like a file store.
|
|
|
|
|
|
$lbFactory->getMainLB()->setTransactionListener(
|
|
|
|
|
|
__METHOD__,
|
2017-12-02 21:12:41 +00:00
|
|
|
|
function ( $trigger ) use ( $stats, $config ) {
|
2016-09-01 16:43:01 +00:00
|
|
|
|
// Check config in case of JobRunner and unit tests
|
2017-12-02 21:12:41 +00:00
|
|
|
|
if ( $config->get( 'CommandLineMode' ) && $trigger === IDatabase::TRIGGER_COMMIT ) {
|
2016-09-01 16:43:01 +00:00
|
|
|
|
DeferredUpdates::tryOpportunisticExecute( 'run' );
|
|
|
|
|
|
}
|
2017-12-02 21:12:41 +00:00
|
|
|
|
// Try to periodically flush buffered metrics to avoid OOMs
|
|
|
|
|
|
MediaWiki::emitBufferedStatsdData( $stats, $config );
|
2016-09-01 16:43:01 +00:00
|
|
|
|
}
|
2016-08-28 16:06:57 +00:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
2009-09-04 08:02:00 +00:00
|
|
|
|
* Run a child maintenance script. Pass all of the current arguments
|
2009-08-02 19:35:17 +00:00
|
|
|
|
* to it.
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param string $maintClass A name of a child maintenance class
|
2018-06-26 21:14:43 +00:00
|
|
|
|
* @param string|null $classFile Full path of where the child is
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @return Maintenance
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
2010-10-27 14:38:31 +00:00
|
|
|
|
public function runChild( $maintClass, $classFile = null ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
// Make sure the class is loaded first
|
2013-08-08 03:18:34 +00:00
|
|
|
|
if ( !class_exists( $maintClass ) ) {
|
2010-05-22 16:50:39 +00:00
|
|
|
|
if ( $classFile ) {
|
2013-05-17 00:16:59 +00:00
|
|
|
|
require_once $classFile;
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2013-08-08 03:18:34 +00:00
|
|
|
|
if ( !class_exists( $maintClass ) ) {
|
2009-08-02 21:55:10 +00:00
|
|
|
|
$this->error( "Cannot spawn child: $maintClass" );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2011-11-01 23:48:09 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @var $child Maintenance
|
|
|
|
|
|
*/
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$child = new $maintClass();
|
|
|
|
|
|
$child->loadParamsAndArgs( $this->mSelf, $this->mOptions, $this->mArgs );
|
2011-05-24 17:48:22 +00:00
|
|
|
|
if ( !is_null( $this->mDb ) ) {
|
|
|
|
|
|
$child->setDB( $this->mDb );
|
|
|
|
|
|
}
|
2014-04-23 18:08:06 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
return $child;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Do some sanity checking and basic setup
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function setup() {
|
2016-08-12 10:33:37 +00:00
|
|
|
|
global $IP, $wgCommandLineMode;
|
2009-08-02 19:35:17 +00:00
|
|
|
|
|
|
|
|
|
|
# Abort if called from a web server
|
2018-01-03 06:51:44 +00:00
|
|
|
|
# wfIsCLI() is not available yet
|
|
|
|
|
|
if ( PHP_SAPI !== 'cli' && PHP_SAPI !== 'phpdbg' ) {
|
2017-11-20 00:36:54 +00:00
|
|
|
|
$this->fatalError( 'This script must be run from the command line' );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2013-10-03 06:53:30 +00:00
|
|
|
|
if ( $IP === null ) {
|
2017-11-20 00:36:54 +00:00
|
|
|
|
$this->fatalError( "\$IP not set, aborting!\n" .
|
|
|
|
|
|
'(Did you forget to call parent::__construct() in your maintenance script?)' );
|
2013-10-03 06:53:30 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
# Make sure we can handle script parameters
|
2013-10-03 06:33:27 +00:00
|
|
|
|
if ( !defined( 'HPHP_VERSION' ) && !ini_get( 'register_argc_argv' ) ) {
|
2017-11-20 00:36:54 +00:00
|
|
|
|
$this->fatalError( 'Cannot get command line arguments, register_argc_argv is set to false' );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2012-09-24 14:43:30 +00:00
|
|
|
|
// Send PHP warnings and errors to stderr instead of stdout.
|
|
|
|
|
|
// This aids in diagnosing problems, while keeping messages
|
|
|
|
|
|
// out of redirected output.
|
|
|
|
|
|
if ( ini_get( 'display_errors' ) ) {
|
|
|
|
|
|
ini_set( 'display_errors', 'stderr' );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2010-10-16 18:33:45 +00:00
|
|
|
|
$this->loadParamsAndArgs();
|
|
|
|
|
|
$this->maybeHelp();
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
# Set the memory limit
|
2009-09-24 23:03:23 +00:00
|
|
|
|
# Note we need to set it again later in cache LocalSettings changed it
|
2010-10-16 18:33:45 +00:00
|
|
|
|
$this->adjustMemoryLimit();
|
2009-08-02 19:35:17 +00:00
|
|
|
|
|
2009-08-15 20:56:11 +00:00
|
|
|
|
# Set max execution time to 0 (no limit). PHP.net says that
|
|
|
|
|
|
# "When running PHP from the command line the default setting is 0."
|
|
|
|
|
|
# But sometimes this doesn't seem to be the case.
|
|
|
|
|
|
ini_set( 'max_execution_time', 0 );
|
|
|
|
|
|
|
2009-08-15 14:25:52 +00:00
|
|
|
|
# Define us as being in MediaWiki
|
2009-08-02 19:35:17 +00:00
|
|
|
|
define( 'MEDIAWIKI', true );
|
|
|
|
|
|
|
|
|
|
|
|
$wgCommandLineMode = true;
|
2013-02-09 22:39:43 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
# Turn off output buffering if it's on
|
2013-04-18 18:48:44 +00:00
|
|
|
|
while ( ob_get_level() > 0 ) {
|
2013-02-09 22:39:43 +00:00
|
|
|
|
ob_end_flush();
|
|
|
|
|
|
}
|
2009-08-02 19:35:17 +00:00
|
|
|
|
|
|
|
|
|
|
$this->validateParamsAndArgs();
|
|
|
|
|
|
}
|
2010-01-06 03:42:30 +00:00
|
|
|
|
|
2009-09-14 22:10:10 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Normally we disable the memory_limit when running admin scripts.
|
|
|
|
|
|
* Some scripts may wish to actually set a limit, however, to avoid
|
2010-10-16 17:30:34 +00:00
|
|
|
|
* blowing up unexpectedly. We also support a --memory-limit option,
|
|
|
|
|
|
* to allow sysadmins to explicitly set one if they'd prefer to override
|
|
|
|
|
|
* defaults (or for people using Suhosin which yells at you for trying
|
|
|
|
|
|
* to disable the limits)
|
2011-10-18 17:31:54 +00:00
|
|
|
|
* @return string
|
2009-09-14 22:10:10 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function memoryLimit() {
|
2010-10-17 10:18:08 +00:00
|
|
|
|
$limit = $this->getOption( 'memory-limit', 'max' );
|
|
|
|
|
|
$limit = trim( $limit, "\" '" ); // trim quotes in case someone misunderstood
|
|
|
|
|
|
return $limit;
|
2009-09-14 22:10:10 +00:00
|
|
|
|
}
|
2009-08-02 19:35:17 +00:00
|
|
|
|
|
2010-10-16 18:33:45 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Adjusts PHP's memory limit to better suit our needs, if needed.
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function adjustMemoryLimit() {
|
2010-10-17 10:18:08 +00:00
|
|
|
|
$limit = $this->memoryLimit();
|
|
|
|
|
|
if ( $limit == 'max' ) {
|
|
|
|
|
|
$limit = -1; // no memory limit
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( $limit != 'default' ) {
|
|
|
|
|
|
ini_set( 'memory_limit', $limit );
|
2010-10-16 18:33:45 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-11-21 22:54:57 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Activate the profiler (assuming $wgProfiler is set)
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function activateProfiler() {
|
2015-05-09 01:59:42 +00:00
|
|
|
|
global $wgProfiler, $wgProfileLimit, $wgTrxProfilerLimits;
|
2014-11-21 22:54:57 +00:00
|
|
|
|
|
|
|
|
|
|
$output = $this->getOption( 'profiler' );
|
2015-05-07 00:45:48 +00:00
|
|
|
|
if ( !$output ) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ( is_array( $wgProfiler ) && isset( $wgProfiler['class'] ) ) {
|
2014-11-21 22:54:57 +00:00
|
|
|
|
$class = $wgProfiler['class'];
|
2016-10-24 05:03:37 +00:00
|
|
|
|
/** @var Profiler $profiler */
|
2014-11-21 22:54:57 +00:00
|
|
|
|
$profiler = new $class(
|
2016-02-17 09:09:32 +00:00
|
|
|
|
[ 'sampling' => 1, 'output' => [ $output ] ]
|
2015-05-09 01:59:42 +00:00
|
|
|
|
+ $wgProfiler
|
2016-02-17 09:09:32 +00:00
|
|
|
|
+ [ 'threshold' => $wgProfileLimit ]
|
2014-11-21 22:54:57 +00:00
|
|
|
|
);
|
|
|
|
|
|
$profiler->setTemplated( true );
|
|
|
|
|
|
Profiler::replaceStubInstance( $profiler );
|
|
|
|
|
|
}
|
2015-04-08 01:11:29 +00:00
|
|
|
|
|
|
|
|
|
|
$trxProfiler = Profiler::instance()->getTransactionProfiler();
|
|
|
|
|
|
$trxProfiler->setLogger( LoggerFactory::getInstance( 'DBPerformance' ) );
|
2015-04-26 18:26:49 +00:00
|
|
|
|
$trxProfiler->setExpectations( $wgTrxProfilerLimits['Maintenance'], __METHOD__ );
|
2014-11-21 22:54:57 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Clear all params and arguments.
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function clearParamsAndArgs() {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$this->mOptions = [];
|
|
|
|
|
|
$this->mArgs = [];
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$this->mInputLoaded = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2015-12-30 20:43:10 +00:00
|
|
|
|
* Load params and arguments from a given array
|
|
|
|
|
|
* of command-line arguments
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*
|
2015-12-30 20:43:10 +00:00
|
|
|
|
* @since 1.27
|
|
|
|
|
|
* @param array $argv
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
2015-12-30 20:43:10 +00:00
|
|
|
|
public function loadWithArgv( $argv ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$options = [];
|
|
|
|
|
|
$args = [];
|
|
|
|
|
|
$this->orderedOptions = [];
|
2009-08-02 19:35:17 +00:00
|
|
|
|
|
|
|
|
|
|
# Parse arguments
|
2010-05-22 16:50:39 +00:00
|
|
|
|
for ( $arg = reset( $argv ); $arg !== false; $arg = next( $argv ) ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
if ( $arg == '--' ) {
|
|
|
|
|
|
# End of options, remainder should be considered arguments
|
|
|
|
|
|
$arg = next( $argv );
|
2010-05-22 16:50:39 +00:00
|
|
|
|
while ( $arg !== false ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$args[] = $arg;
|
|
|
|
|
|
$arg = next( $argv );
|
|
|
|
|
|
}
|
|
|
|
|
|
break;
|
|
|
|
|
|
} elseif ( substr( $arg, 0, 2 ) == '--' ) {
|
|
|
|
|
|
# Long options
|
|
|
|
|
|
$option = substr( $arg, 2 );
|
|
|
|
|
|
if ( isset( $this->mParams[$option] ) && $this->mParams[$option]['withArg'] ) {
|
|
|
|
|
|
$param = next( $argv );
|
|
|
|
|
|
if ( $param === false ) {
|
2011-07-18 03:30:03 +00:00
|
|
|
|
$this->error( "\nERROR: $option parameter needs a value after it\n" );
|
2009-08-06 00:32:50 +00:00
|
|
|
|
$this->maybeHelp( true );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2015-12-29 22:15:12 +00:00
|
|
|
|
|
|
|
|
|
|
$this->setParam( $options, $option, $param );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
$bits = explode( '=', $option, 2 );
|
2010-05-22 16:50:39 +00:00
|
|
|
|
if ( count( $bits ) > 1 ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$option = $bits[0];
|
|
|
|
|
|
$param = $bits[1];
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$param = 1;
|
|
|
|
|
|
}
|
2015-12-29 22:15:12 +00:00
|
|
|
|
|
|
|
|
|
|
$this->setParam( $options, $option, $param );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2015-02-18 17:55:27 +00:00
|
|
|
|
} elseif ( $arg == '-' ) {
|
|
|
|
|
|
# Lonely "-", often used to indicate stdin or stdout.
|
|
|
|
|
|
$args[] = $arg;
|
2009-08-02 19:35:17 +00:00
|
|
|
|
} elseif ( substr( $arg, 0, 1 ) == '-' ) {
|
|
|
|
|
|
# Short options
|
2014-04-23 11:45:13 +00:00
|
|
|
|
$argLength = strlen( $arg );
|
|
|
|
|
|
for ( $p = 1; $p < $argLength; $p++ ) {
|
2014-04-23 18:08:06 +00:00
|
|
|
|
$option = $arg[$p];
|
2011-04-06 18:38:48 +00:00
|
|
|
|
if ( !isset( $this->mParams[$option] ) && isset( $this->mShortParamsMap[$option] ) ) {
|
|
|
|
|
|
$option = $this->mShortParamsMap[$option];
|
|
|
|
|
|
}
|
2015-12-29 22:15:12 +00:00
|
|
|
|
|
2009-09-05 20:02:38 +00:00
|
|
|
|
if ( isset( $this->mParams[$option]['withArg'] ) && $this->mParams[$option]['withArg'] ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$param = next( $argv );
|
|
|
|
|
|
if ( $param === false ) {
|
2011-07-18 03:30:03 +00:00
|
|
|
|
$this->error( "\nERROR: $option parameter needs a value after it\n" );
|
2009-08-06 00:32:50 +00:00
|
|
|
|
$this->maybeHelp( true );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2015-12-29 22:15:12 +00:00
|
|
|
|
$this->setParam( $options, $option, $param );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
} else {
|
2015-12-29 22:15:12 +00:00
|
|
|
|
$this->setParam( $options, $option, 1 );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$args[] = $arg;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
$this->mOptions = $options;
|
|
|
|
|
|
$this->mArgs = $args;
|
|
|
|
|
|
$this->loadSpecialVars();
|
|
|
|
|
|
$this->mInputLoaded = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-12-29 22:15:12 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Helper function used solely by loadParamsAndArgs
|
|
|
|
|
|
* to prevent code duplication
|
|
|
|
|
|
*
|
|
|
|
|
|
* This sets the param in the options array based on
|
|
|
|
|
|
* whether or not it can be specified multiple times.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.27
|
|
|
|
|
|
* @param array $options
|
|
|
|
|
|
* @param string $option
|
|
|
|
|
|
* @param mixed $value
|
|
|
|
|
|
*/
|
|
|
|
|
|
private function setParam( &$options, $option, $value ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$this->orderedOptions[] = [ $option, $value ];
|
2015-12-29 22:15:12 +00:00
|
|
|
|
|
|
|
|
|
|
if ( isset( $this->mParams[$option] ) ) {
|
|
|
|
|
|
$multi = $this->mParams[$option]['multiOccurrence'];
|
2016-03-10 05:18:13 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
$multi = false;
|
|
|
|
|
|
}
|
|
|
|
|
|
$exists = array_key_exists( $option, $options );
|
|
|
|
|
|
if ( $multi && $exists ) {
|
|
|
|
|
|
$options[$option][] = $value;
|
|
|
|
|
|
} elseif ( $multi ) {
|
|
|
|
|
|
$options[$option] = [ $value ];
|
|
|
|
|
|
} elseif ( !$exists ) {
|
|
|
|
|
|
$options[$option] = $value;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$this->error( "\nERROR: $option parameter given twice\n" );
|
|
|
|
|
|
$this->maybeHelp( true );
|
2015-12-29 22:15:12 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-12-30 20:43:10 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Process command line arguments
|
|
|
|
|
|
* $mOptions becomes an array with keys set to the option names
|
|
|
|
|
|
* $mArgs becomes a zero-based array containing the non-option arguments
|
|
|
|
|
|
*
|
2018-06-26 21:14:43 +00:00
|
|
|
|
* @param string|null $self The name of the script, if any
|
|
|
|
|
|
* @param array|null $opts An array of options, in form of key=>value
|
|
|
|
|
|
* @param array|null $args An array of command line arguments
|
2015-12-30 20:43:10 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function loadParamsAndArgs( $self = null, $opts = null, $args = null ) {
|
|
|
|
|
|
# If we were given opts or args, set those and return early
|
|
|
|
|
|
if ( $self ) {
|
|
|
|
|
|
$this->mSelf = $self;
|
|
|
|
|
|
$this->mInputLoaded = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( $opts ) {
|
|
|
|
|
|
$this->mOptions = $opts;
|
|
|
|
|
|
$this->mInputLoaded = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( $args ) {
|
|
|
|
|
|
$this->mArgs = $args;
|
|
|
|
|
|
$this->mInputLoaded = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# If we've already loaded input (either by user values or from $argv)
|
|
|
|
|
|
# skip on loading it again. The array_shift() will corrupt values if
|
|
|
|
|
|
# it's run again and again
|
|
|
|
|
|
if ( $this->mInputLoaded ) {
|
|
|
|
|
|
$this->loadSpecialVars();
|
|
|
|
|
|
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
global $argv;
|
|
|
|
|
|
$this->mSelf = $argv[0];
|
|
|
|
|
|
$this->loadWithArgv( array_slice( $argv, 1 ) );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Run some validation checks on the params, etc
|
|
|
|
|
|
*/
|
2009-09-04 08:02:00 +00:00
|
|
|
|
protected function validateParamsAndArgs() {
|
2009-08-18 23:06:24 +00:00
|
|
|
|
$die = false;
|
|
|
|
|
|
# Check to make sure we've got all the required options
|
2010-05-22 16:50:39 +00:00
|
|
|
|
foreach ( $this->mParams as $opt => $info ) {
|
|
|
|
|
|
if ( $info['require'] && !$this->hasOption( $opt ) ) {
|
2009-08-18 23:06:24 +00:00
|
|
|
|
$this->error( "Param $opt required!" );
|
|
|
|
|
|
$die = true;
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2009-08-18 23:06:24 +00:00
|
|
|
|
# Check arg list too
|
2010-05-22 16:50:39 +00:00
|
|
|
|
foreach ( $this->mArgList as $k => $info ) {
|
|
|
|
|
|
if ( $info['require'] && !$this->hasArg( $k ) ) {
|
2010-05-30 10:08:13 +00:00
|
|
|
|
$this->error( 'Argument <' . $info['name'] . '> required!' );
|
2009-08-18 23:06:24 +00:00
|
|
|
|
$die = true;
|
|
|
|
|
|
}
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2018-08-02 20:10:31 +00:00
|
|
|
|
if ( !$this->mAllowUnregisteredOptions ) {
|
|
|
|
|
|
# Check for unexpected options
|
|
|
|
|
|
foreach ( $this->mOptions as $opt => $val ) {
|
|
|
|
|
|
if ( !$this->supportsOption( $opt ) ) {
|
|
|
|
|
|
$this->error( "Unexpected option $opt!" );
|
|
|
|
|
|
$die = true;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2010-06-27 21:48:51 +00:00
|
|
|
|
|
2010-05-30 10:08:13 +00:00
|
|
|
|
if ( $die ) {
|
|
|
|
|
|
$this->maybeHelp( true );
|
|
|
|
|
|
}
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Handle the special variables that are global to all scripts
|
|
|
|
|
|
*/
|
2009-09-04 08:02:00 +00:00
|
|
|
|
protected function loadSpecialVars() {
|
2010-05-30 10:08:13 +00:00
|
|
|
|
if ( $this->hasOption( 'dbuser' ) ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$this->mDbUser = $this->getOption( 'dbuser' );
|
2010-05-30 10:08:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
if ( $this->hasOption( 'dbpass' ) ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$this->mDbPass = $this->getOption( 'dbpass' );
|
2010-05-30 10:08:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
if ( $this->hasOption( 'quiet' ) ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$this->mQuiet = true;
|
2010-05-30 10:08:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
if ( $this->hasOption( 'batch-size' ) ) {
|
2011-08-25 05:33:32 +00:00
|
|
|
|
$this->mBatchSize = intval( $this->getOption( 'batch-size' ) );
|
2010-05-30 10:08:13 +00:00
|
|
|
|
}
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Maybe show the help.
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param bool $force Whether to force the help to show, default false
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
2009-09-04 08:02:00 +00:00
|
|
|
|
protected function maybeHelp( $force = false ) {
|
2013-04-18 18:48:44 +00:00
|
|
|
|
if ( !$force && !$this->hasOption( 'help' ) ) {
|
2010-10-25 16:23:56 +00:00
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2013-05-26 13:17:24 +00:00
|
|
|
|
$screenWidth = 80; // TODO: Calculate this!
|
2010-03-14 11:38:18 +00:00
|
|
|
|
$tab = " ";
|
|
|
|
|
|
$descWidth = $screenWidth - ( 2 * strlen( $tab ) );
|
2010-06-27 21:48:51 +00:00
|
|
|
|
|
2009-08-04 00:23:56 +00:00
|
|
|
|
ksort( $this->mParams );
|
2010-10-25 16:23:56 +00:00
|
|
|
|
$this->mQuiet = false;
|
|
|
|
|
|
|
|
|
|
|
|
// Description ...
|
|
|
|
|
|
if ( $this->mDescription ) {
|
2016-08-15 05:40:52 +00:00
|
|
|
|
$this->output( "\n" . wordwrap( $this->mDescription, $screenWidth ) . "\n" );
|
2010-10-25 16:23:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
$output = "\nUsage: php " . basename( $this->mSelf );
|
2010-12-04 03:20:14 +00:00
|
|
|
|
|
2010-10-25 16:23:56 +00:00
|
|
|
|
// ... append parameters ...
|
|
|
|
|
|
if ( $this->mParams ) {
|
2018-02-22 19:24:00 +00:00
|
|
|
|
$output .= " [--" . implode( "|--", array_keys( $this->mParams ) ) . "]";
|
2010-10-25 16:23:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// ... and append arguments.
|
|
|
|
|
|
if ( $this->mArgList ) {
|
2010-11-20 11:11:20 +00:00
|
|
|
|
$output .= ' ';
|
2010-10-25 16:23:56 +00:00
|
|
|
|
foreach ( $this->mArgList as $k => $arg ) {
|
2010-11-20 11:11:20 +00:00
|
|
|
|
if ( $arg['require'] ) {
|
|
|
|
|
|
$output .= '<' . $arg['name'] . '>';
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$output .= '[' . $arg['name'] . ']';
|
|
|
|
|
|
}
|
2013-04-18 18:48:44 +00:00
|
|
|
|
if ( $k < count( $this->mArgList ) - 1 ) {
|
2010-11-20 11:11:20 +00:00
|
|
|
|
$output .= ' ';
|
2013-04-18 18:48:44 +00:00
|
|
|
|
}
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
}
|
2010-10-25 16:23:56 +00:00
|
|
|
|
$this->output( "$output\n\n" );
|
|
|
|
|
|
|
2011-03-01 20:42:55 +00:00
|
|
|
|
# TODO abstract some repetitive code below
|
|
|
|
|
|
|
|
|
|
|
|
// Generic parameters
|
|
|
|
|
|
$this->output( "Generic maintenance parameters:\n" );
|
|
|
|
|
|
foreach ( $this->mGenericParameters as $par => $info ) {
|
2011-04-06 18:38:48 +00:00
|
|
|
|
if ( $info['shortName'] !== false ) {
|
|
|
|
|
|
$par .= " (-{$info['shortName']})";
|
|
|
|
|
|
}
|
2010-10-25 16:23:56 +00:00
|
|
|
|
$this->output(
|
|
|
|
|
|
wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
|
2014-04-23 18:08:06 +00:00
|
|
|
|
"\n$tab$tab" ) . "\n"
|
2010-10-25 16:23:56 +00:00
|
|
|
|
);
|
|
|
|
|
|
}
|
2011-03-01 20:42:55 +00:00
|
|
|
|
$this->output( "\n" );
|
|
|
|
|
|
|
|
|
|
|
|
$scriptDependantParams = $this->mDependantParameters;
|
2013-04-27 11:23:52 +00:00
|
|
|
|
if ( count( $scriptDependantParams ) > 0 ) {
|
2011-03-01 20:42:55 +00:00
|
|
|
|
$this->output( "Script dependant parameters:\n" );
|
|
|
|
|
|
// Parameters description
|
|
|
|
|
|
foreach ( $scriptDependantParams as $par => $info ) {
|
2011-04-06 18:38:48 +00:00
|
|
|
|
if ( $info['shortName'] !== false ) {
|
|
|
|
|
|
$par .= " (-{$info['shortName']})";
|
|
|
|
|
|
}
|
2011-03-01 20:42:55 +00:00
|
|
|
|
$this->output(
|
|
|
|
|
|
wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
|
2014-04-23 18:08:06 +00:00
|
|
|
|
"\n$tab$tab" ) . "\n"
|
2011-03-01 20:42:55 +00:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->output( "\n" );
|
|
|
|
|
|
}
|
2010-10-25 16:23:56 +00:00
|
|
|
|
|
2011-03-01 20:42:55 +00:00
|
|
|
|
// Script specific parameters not defined on construction by
|
|
|
|
|
|
// Maintenance::addDefaultParams()
|
|
|
|
|
|
$scriptSpecificParams = array_diff_key(
|
|
|
|
|
|
# all script parameters:
|
|
|
|
|
|
$this->mParams,
|
|
|
|
|
|
# remove the Maintenance default parameters:
|
|
|
|
|
|
$this->mGenericParameters,
|
|
|
|
|
|
$this->mDependantParameters
|
|
|
|
|
|
);
|
2013-04-27 11:23:52 +00:00
|
|
|
|
if ( count( $scriptSpecificParams ) > 0 ) {
|
2011-03-01 20:42:55 +00:00
|
|
|
|
$this->output( "Script specific parameters:\n" );
|
|
|
|
|
|
// Parameters description
|
|
|
|
|
|
foreach ( $scriptSpecificParams as $par => $info ) {
|
2011-04-06 18:38:48 +00:00
|
|
|
|
if ( $info['shortName'] !== false ) {
|
|
|
|
|
|
$par .= " (-{$info['shortName']})";
|
|
|
|
|
|
}
|
2011-03-01 20:42:55 +00:00
|
|
|
|
$this->output(
|
|
|
|
|
|
wordwrap( "$tab--$par: " . $info['desc'], $descWidth,
|
2014-04-23 18:08:06 +00:00
|
|
|
|
"\n$tab$tab" ) . "\n"
|
2011-03-01 20:42:55 +00:00
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->output( "\n" );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Print arguments
|
2013-04-18 18:48:44 +00:00
|
|
|
|
if ( count( $this->mArgList ) > 0 ) {
|
2011-03-01 20:42:55 +00:00
|
|
|
|
$this->output( "Arguments:\n" );
|
|
|
|
|
|
// Arguments description
|
|
|
|
|
|
foreach ( $this->mArgList as $info ) {
|
|
|
|
|
|
$openChar = $info['require'] ? '<' : '[';
|
|
|
|
|
|
$closeChar = $info['require'] ? '>' : ']';
|
|
|
|
|
|
$this->output(
|
|
|
|
|
|
wordwrap( "$tab$openChar" . $info['name'] . "$closeChar: " .
|
|
|
|
|
|
$info['desc'], $descWidth, "\n$tab$tab" ) . "\n"
|
|
|
|
|
|
);
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->output( "\n" );
|
2010-10-25 16:23:56 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
die( 1 );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Handle some last-minute setup here.
|
|
|
|
|
|
*/
|
2009-08-15 12:24:50 +00:00
|
|
|
|
public function finalSetup() {
|
2018-08-01 14:21:13 +00:00
|
|
|
|
global $wgCommandLineMode, $wgServer, $wgShowExceptionDetails, $wgShowHostnames;
|
2018-06-12 21:49:52 +00:00
|
|
|
|
global $wgDBadminuser, $wgDBadminpassword, $wgDBDefaultGroup;
|
2009-08-02 19:35:17 +00:00
|
|
|
|
global $wgDBuser, $wgDBpassword, $wgDBservers, $wgLBFactoryConf;
|
2009-08-15 12:24:50 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
# Turn off output buffering again, it might have been turned on in the settings files
|
2010-05-22 16:50:39 +00:00
|
|
|
|
if ( ob_get_level() ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
ob_end_flush();
|
|
|
|
|
|
}
|
|
|
|
|
|
# Same with these
|
|
|
|
|
|
$wgCommandLineMode = true;
|
|
|
|
|
|
|
2010-10-29 16:28:50 +00:00
|
|
|
|
# Override $wgServer
|
2013-04-18 18:48:44 +00:00
|
|
|
|
if ( $this->hasOption( 'server' ) ) {
|
2010-10-29 16:28:50 +00:00
|
|
|
|
$wgServer = $this->getOption( 'server', $wgServer );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
# If these were passed, use them
|
2010-05-30 10:08:13 +00:00
|
|
|
|
if ( $this->mDbUser ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$wgDBadminuser = $this->mDbUser;
|
2010-05-30 10:08:13 +00:00
|
|
|
|
}
|
|
|
|
|
|
if ( $this->mDbPass ) {
|
2009-08-15 12:24:50 +00:00
|
|
|
|
$wgDBadminpassword = $this->mDbPass;
|
2010-05-30 10:08:13 +00:00
|
|
|
|
}
|
2018-06-12 21:49:52 +00:00
|
|
|
|
if ( $this->hasOption( 'dbgroupdefault' ) ) {
|
|
|
|
|
|
$wgDBDefaultGroup = $this->getOption( 'dbgroupdefault', null );
|
2018-06-19 17:46:01 +00:00
|
|
|
|
|
|
|
|
|
|
MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->destroy();
|
2018-06-12 21:49:52 +00:00
|
|
|
|
}
|
2009-08-02 19:35:17 +00:00
|
|
|
|
|
2010-02-06 14:29:37 +00:00
|
|
|
|
if ( $this->getDbType() == self::DB_ADMIN && isset( $wgDBadminuser ) ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$wgDBuser = $wgDBadminuser;
|
|
|
|
|
|
$wgDBpassword = $wgDBadminpassword;
|
2009-08-15 12:24:50 +00:00
|
|
|
|
|
2010-05-22 16:50:39 +00:00
|
|
|
|
if ( $wgDBservers ) {
|
2011-10-18 17:31:54 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* @var $wgDBservers array
|
|
|
|
|
|
*/
|
2009-08-02 19:35:17 +00:00
|
|
|
|
foreach ( $wgDBservers as $i => $server ) {
|
|
|
|
|
|
$wgDBservers[$i]['user'] = $wgDBuser;
|
|
|
|
|
|
$wgDBservers[$i]['password'] = $wgDBpassword;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2010-05-22 16:50:39 +00:00
|
|
|
|
if ( isset( $wgLBFactoryConf['serverTemplate'] ) ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$wgLBFactoryConf['serverTemplate']['user'] = $wgDBuser;
|
|
|
|
|
|
$wgLBFactoryConf['serverTemplate']['password'] = $wgDBpassword;
|
|
|
|
|
|
}
|
2016-09-14 11:34:17 +00:00
|
|
|
|
MediaWikiServices::getInstance()->getDBLoadBalancerFactory()->destroy();
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2009-08-15 12:24:50 +00:00
|
|
|
|
|
2017-10-08 20:17:35 +00:00
|
|
|
|
# Apply debug settings
|
|
|
|
|
|
if ( $this->hasOption( 'mwdebug' ) ) {
|
|
|
|
|
|
require __DIR__ . '/../includes/DevelopmentSettings.php';
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2014-11-21 22:54:57 +00:00
|
|
|
|
// Per-script profiling; useful for debugging
|
|
|
|
|
|
$this->activateProfiler();
|
|
|
|
|
|
|
2010-09-07 20:45:04 +00:00
|
|
|
|
$this->afterFinalSetup();
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2018-08-01 14:21:13 +00:00
|
|
|
|
$wgShowExceptionDetails = true;
|
|
|
|
|
|
$wgShowHostnames = true;
|
2014-04-23 11:45:13 +00:00
|
|
|
|
|
2018-02-10 07:52:26 +00:00
|
|
|
|
Wikimedia\suppressWarnings();
|
2015-06-10 18:29:05 +00:00
|
|
|
|
set_time_limit( 0 );
|
2018-02-10 07:52:26 +00:00
|
|
|
|
Wikimedia\restoreWarnings();
|
2014-04-23 11:45:13 +00:00
|
|
|
|
|
2010-10-16 18:33:45 +00:00
|
|
|
|
$this->adjustMemoryLimit();
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2010-09-07 20:45:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Execute a callback function at the end of initialisation
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function afterFinalSetup() {
|
|
|
|
|
|
if ( defined( 'MW_CMDLINE_CALLBACK' ) ) {
|
|
|
|
|
|
call_user_func( MW_CMDLINE_CALLBACK );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Potentially debug globals. Originally a feature only
|
|
|
|
|
|
* for refreshLinks
|
|
|
|
|
|
*/
|
|
|
|
|
|
public function globals() {
|
2010-05-22 16:50:39 +00:00
|
|
|
|
if ( $this->hasOption( 'globals' ) ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
print_r( $GLOBALS );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Generic setup for most installs. Returns the location of LocalSettings
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @return string
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function loadSettings() {
|
2011-06-03 03:38:52 +00:00
|
|
|
|
global $wgCommandLineMode, $IP;
|
2009-08-02 19:35:17 +00:00
|
|
|
|
|
|
|
|
|
|
if ( isset( $this->mOptions['conf'] ) ) {
|
|
|
|
|
|
$settingsFile = $this->mOptions['conf'];
|
2013-04-18 18:48:44 +00:00
|
|
|
|
} elseif ( defined( "MW_CONFIG_FILE" ) ) {
|
2010-12-05 06:43:15 +00:00
|
|
|
|
$settingsFile = MW_CONFIG_FILE;
|
2009-08-02 19:35:17 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
$settingsFile = "$IP/LocalSettings.php";
|
|
|
|
|
|
}
|
|
|
|
|
|
if ( isset( $this->mOptions['wiki'] ) ) {
|
|
|
|
|
|
$bits = explode( '-', $this->mOptions['wiki'] );
|
|
|
|
|
|
if ( count( $bits ) == 1 ) {
|
|
|
|
|
|
$bits[] = '';
|
|
|
|
|
|
}
|
|
|
|
|
|
define( 'MW_DB', $bits[0] );
|
|
|
|
|
|
define( 'MW_PREFIX', $bits[1] );
|
2018-03-27 01:27:24 +00:00
|
|
|
|
} elseif ( isset( $this->mOptions['server'] ) ) {
|
|
|
|
|
|
// Provide the option for site admins to detect and configure
|
|
|
|
|
|
// multiple wikis based on server names. This offers --server
|
|
|
|
|
|
// as alternative to --wiki.
|
|
|
|
|
|
// See https://www.mediawiki.org/wiki/Manual:Wiki_family
|
|
|
|
|
|
$_SERVER['SERVER_NAME'] = $this->mOptions['server'];
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
|
|
|
|
|
if ( !is_readable( $settingsFile ) ) {
|
2017-11-20 00:36:54 +00:00
|
|
|
|
$this->fatalError( "A copy of your installation's LocalSettings.php\n" .
|
2014-04-23 18:08:06 +00:00
|
|
|
|
"must exist and be readable in the source directory.\n" .
|
2017-11-20 00:36:54 +00:00
|
|
|
|
"Use --conf to specify it." );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
$wgCommandLineMode = true;
|
2014-04-23 18:08:06 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
return $settingsFile;
|
|
|
|
|
|
}
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Support function for cleaning up redundant text records
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param bool $delete Whether or not to actually delete the records
|
2009-08-02 19:35:17 +00:00
|
|
|
|
* @author Rob Church <robchur@gmail.com>
|
|
|
|
|
|
*/
|
2010-06-17 09:13:32 +00:00
|
|
|
|
public function purgeRedundantText( $delete = true ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
# Data should come off the master, wrapped in a transaction
|
2011-05-24 17:48:22 +00:00
|
|
|
|
$dbw = $this->getDB( DB_MASTER );
|
2015-12-22 08:51:42 +00:00
|
|
|
|
$this->beginTransaction( $dbw, __METHOD__ );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
|
|
|
|
|
|
# Get "active" text records from the revisions table
|
2016-10-24 05:03:37 +00:00
|
|
|
|
$cur = [];
|
2010-05-30 10:08:13 +00:00
|
|
|
|
$this->output( 'Searching for active text records in revisions table...' );
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$res = $dbw->select( 'revision', 'rev_text_id', [], __METHOD__, [ 'DISTINCT' ] );
|
2010-05-22 16:50:39 +00:00
|
|
|
|
foreach ( $res as $row ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$cur[] = $row->rev_text_id;
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->output( "done.\n" );
|
|
|
|
|
|
|
|
|
|
|
|
# Get "active" text records from the archive table
|
2010-05-30 10:08:13 +00:00
|
|
|
|
$this->output( 'Searching for active text records in archive table...' );
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$res = $dbw->select( 'archive', 'ar_text_id', [], __METHOD__, [ 'DISTINCT' ] );
|
2010-05-22 16:50:39 +00:00
|
|
|
|
foreach ( $res as $row ) {
|
2013-01-15 17:06:01 +00:00
|
|
|
|
# old pre-MW 1.5 records can have null ar_text_id's.
|
|
|
|
|
|
if ( $row->ar_text_id !== null ) {
|
|
|
|
|
|
$cur[] = $row->ar_text_id;
|
|
|
|
|
|
}
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
$this->output( "done.\n" );
|
|
|
|
|
|
|
|
|
|
|
|
# Get the IDs of all text records not in these sets
|
2010-05-30 10:08:13 +00:00
|
|
|
|
$this->output( 'Searching for inactive text records...' );
|
2013-01-22 11:05:43 +00:00
|
|
|
|
$cond = 'old_id NOT IN ( ' . $dbw->makeList( $cur ) . ' )';
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$res = $dbw->select( 'text', 'old_id', [ $cond ], __METHOD__, [ 'DISTINCT' ] );
|
|
|
|
|
|
$old = [];
|
2010-05-22 16:50:39 +00:00
|
|
|
|
foreach ( $res as $row ) {
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$old[] = $row->old_id;
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->output( "done.\n" );
|
|
|
|
|
|
|
|
|
|
|
|
# Inform the user of what we're going to do
|
|
|
|
|
|
$count = count( $old );
|
|
|
|
|
|
$this->output( "$count inactive items found.\n" );
|
|
|
|
|
|
|
|
|
|
|
|
# Delete as appropriate
|
2010-05-22 16:50:39 +00:00
|
|
|
|
if ( $delete && $count ) {
|
2010-05-30 10:08:13 +00:00
|
|
|
|
$this->output( 'Deleting...' );
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$dbw->delete( 'text', [ 'old_id' => $old ], __METHOD__ );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
$this->output( "done.\n" );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Done
|
2015-12-22 08:51:42 +00:00
|
|
|
|
$this->commitTransaction( $dbw, __METHOD__ );
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2009-08-15 14:25:52 +00:00
|
|
|
|
|
2009-08-02 19:35:17 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Get the maintenance directory.
|
2011-10-18 17:31:54 +00:00
|
|
|
|
* @return string
|
2009-08-02 19:35:17 +00:00
|
|
|
|
*/
|
|
|
|
|
|
protected function getDir() {
|
2012-08-27 19:03:15 +00:00
|
|
|
|
return __DIR__;
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
2011-05-24 17:48:22 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Returns a database to be used by current maintenance script. It can be set by setDB().
|
|
|
|
|
|
* If not set, wfGetDB() will be used.
|
|
|
|
|
|
* This function has the same parameters as wfGetDB()
|
|
|
|
|
|
*
|
2017-08-20 11:20:59 +00:00
|
|
|
|
* @param int $db DB index (DB_REPLICA/DB_MASTER)
|
2018-02-24 21:31:10 +00:00
|
|
|
|
* @param string|string[] $groups default: empty array
|
2017-09-09 20:47:04 +00:00
|
|
|
|
* @param string|bool $wiki default: current wiki
|
2017-03-30 20:46:06 +00:00
|
|
|
|
* @return IMaintainableDatabase
|
2011-05-24 17:48:22 +00:00
|
|
|
|
*/
|
2016-02-17 09:09:32 +00:00
|
|
|
|
protected function getDB( $db, $groups = [], $wiki = false ) {
|
2011-05-24 17:48:22 +00:00
|
|
|
|
if ( is_null( $this->mDb ) ) {
|
|
|
|
|
|
return wfGetDB( $db, $groups, $wiki );
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return $this->mDb;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Sets database object to be returned by getDB().
|
|
|
|
|
|
*
|
2017-03-30 20:46:06 +00:00
|
|
|
|
* @param IDatabase $db
|
2011-05-24 17:48:22 +00:00
|
|
|
|
*/
|
2015-12-22 08:51:42 +00:00
|
|
|
|
public function setDB( IDatabase $db ) {
|
2011-05-24 17:48:22 +00:00
|
|
|
|
$this->mDb = $db;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2015-12-22 08:51:42 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Begin a transcation on a DB
|
|
|
|
|
|
*
|
|
|
|
|
|
* This method makes it clear that begin() is called from a maintenance script,
|
|
|
|
|
|
* which has outermost scope. This is safe, unlike $dbw->begin() called in other places.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param IDatabase $dbw
|
|
|
|
|
|
* @param string $fname Caller name
|
|
|
|
|
|
* @since 1.27
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function beginTransaction( IDatabase $dbw, $fname ) {
|
|
|
|
|
|
$dbw->begin( $fname );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2016-09-05 20:14:41 +00:00
|
|
|
|
* Commit the transcation on a DB handle and wait for replica DBs to catch up
|
2015-12-22 08:51:42 +00:00
|
|
|
|
*
|
|
|
|
|
|
* This method makes it clear that commit() is called from a maintenance script,
|
|
|
|
|
|
* which has outermost scope. This is safe, unlike $dbw->commit() called in other places.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param IDatabase $dbw
|
|
|
|
|
|
* @param string $fname Caller name
|
2016-09-05 20:14:41 +00:00
|
|
|
|
* @return bool Whether the replica DB wait succeeded
|
2015-12-22 08:51:42 +00:00
|
|
|
|
* @since 1.27
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function commitTransaction( IDatabase $dbw, $fname ) {
|
|
|
|
|
|
$dbw->commit( $fname );
|
2016-09-06 01:46:11 +00:00
|
|
|
|
try {
|
|
|
|
|
|
$lbFactory = MediaWikiServices::getInstance()->getDBLoadBalancerFactory();
|
|
|
|
|
|
$lbFactory->waitForReplication(
|
|
|
|
|
|
[ 'timeout' => 30, 'ifWritesSince' => $this->lastReplicationWait ]
|
|
|
|
|
|
);
|
|
|
|
|
|
$this->lastReplicationWait = microtime( true );
|
2015-12-31 23:30:16 +00:00
|
|
|
|
|
2016-09-06 01:46:11 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
} catch ( DBReplicationWaitError $e ) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2015-12-22 08:51:42 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
2015-12-31 23:30:16 +00:00
|
|
|
|
* Rollback the transcation on a DB handle
|
2015-12-22 08:51:42 +00:00
|
|
|
|
*
|
|
|
|
|
|
* This method makes it clear that rollback() is called from a maintenance script,
|
|
|
|
|
|
* which has outermost scope. This is safe, unlike $dbw->rollback() called in other places.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @param IDatabase $dbw
|
|
|
|
|
|
* @param string $fname Caller name
|
|
|
|
|
|
* @since 1.27
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function rollbackTransaction( IDatabase $dbw, $fname ) {
|
|
|
|
|
|
$dbw->rollback( $fname );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2010-03-10 21:54:23 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Lock the search index
|
2017-03-30 20:46:06 +00:00
|
|
|
|
* @param IMaintainableDatabase &$db
|
2010-03-10 21:54:23 +00:00
|
|
|
|
*/
|
2015-01-05 19:32:44 +00:00
|
|
|
|
private function lockSearchindex( $db ) {
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$write = [ 'searchindex' ];
|
|
|
|
|
|
$read = [
|
2015-09-27 07:41:06 +00:00
|
|
|
|
'page',
|
|
|
|
|
|
'revision',
|
|
|
|
|
|
'text',
|
|
|
|
|
|
'interwiki',
|
|
|
|
|
|
'l10n_cache',
|
|
|
|
|
|
'user',
|
|
|
|
|
|
'page_restrictions'
|
2016-02-17 09:09:32 +00:00
|
|
|
|
];
|
2010-03-10 21:54:23 +00:00
|
|
|
|
$db->lockTables( $read, $write, __CLASS__ . '::' . __METHOD__ );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Unlock the tables
|
2017-03-30 20:46:06 +00:00
|
|
|
|
* @param IMaintainableDatabase &$db
|
2010-03-10 21:54:23 +00:00
|
|
|
|
*/
|
2015-01-05 19:32:44 +00:00
|
|
|
|
private function unlockSearchindex( $db ) {
|
2013-04-27 11:23:52 +00:00
|
|
|
|
$db->unlockTables( __CLASS__ . '::' . __METHOD__ );
|
2010-03-10 21:54:23 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Unlock and lock again
|
|
|
|
|
|
* Since the lock is low-priority, queued reads will be able to complete
|
2017-03-30 20:46:06 +00:00
|
|
|
|
* @param IMaintainableDatabase &$db
|
2010-03-10 21:54:23 +00:00
|
|
|
|
*/
|
2015-01-05 19:32:44 +00:00
|
|
|
|
private function relockSearchindex( $db ) {
|
2010-03-10 21:54:23 +00:00
|
|
|
|
$this->unlockSearchindex( $db );
|
|
|
|
|
|
$this->lockSearchindex( $db );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Perform a search index update with locking
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param int $maxLockTime The maximum time to keep the search index locked.
|
|
|
|
|
|
* @param string $callback The function that will update the function.
|
2017-03-30 20:46:06 +00:00
|
|
|
|
* @param IMaintainableDatabase $dbw
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param array $results
|
2010-03-10 21:54:23 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function updateSearchIndex( $maxLockTime, $callback, $dbw, $results ) {
|
|
|
|
|
|
$lockTime = time();
|
|
|
|
|
|
|
|
|
|
|
|
# Lock searchindex
|
|
|
|
|
|
if ( $maxLockTime ) {
|
|
|
|
|
|
$this->output( " --- Waiting for lock ---" );
|
|
|
|
|
|
$this->lockSearchindex( $dbw );
|
|
|
|
|
|
$lockTime = time();
|
|
|
|
|
|
$this->output( "\n" );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Loop through the results and do a search update
|
|
|
|
|
|
foreach ( $results as $row ) {
|
|
|
|
|
|
# Allow reads to be processed
|
|
|
|
|
|
if ( $maxLockTime && time() > $lockTime + $maxLockTime ) {
|
|
|
|
|
|
$this->output( " --- Relocking ---" );
|
|
|
|
|
|
$this->relockSearchindex( $dbw );
|
|
|
|
|
|
$lockTime = time();
|
|
|
|
|
|
$this->output( "\n" );
|
|
|
|
|
|
}
|
|
|
|
|
|
call_user_func( $callback, $dbw, $row );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
# Unlock searchindex
|
|
|
|
|
|
if ( $maxLockTime ) {
|
|
|
|
|
|
$this->output( " --- Unlocking --" );
|
|
|
|
|
|
$this->unlockSearchindex( $dbw );
|
|
|
|
|
|
$this->output( "\n" );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Update the searchindex table for a given pageid
|
2017-03-30 20:46:06 +00:00
|
|
|
|
* @param IDatabase $dbw A database write handle
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param int $pageId The page ID to update.
|
2011-10-18 17:31:54 +00:00
|
|
|
|
* @return null|string
|
2010-03-10 21:54:23 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public function updateSearchIndexForPage( $dbw, $pageId ) {
|
|
|
|
|
|
// Get current revision
|
|
|
|
|
|
$rev = Revision::loadFromPageId( $dbw, $pageId );
|
|
|
|
|
|
$title = null;
|
2010-05-22 16:50:39 +00:00
|
|
|
|
if ( $rev ) {
|
2010-03-10 21:54:23 +00:00
|
|
|
|
$titleObj = $rev->getTitle();
|
|
|
|
|
|
$title = $titleObj->getPrefixedDBkey();
|
|
|
|
|
|
$this->output( "$title..." );
|
|
|
|
|
|
# Update searchindex
|
2013-06-19 17:55:33 +00:00
|
|
|
|
$u = new SearchUpdate( $pageId, $titleObj->getText(), $rev->getContent() );
|
2010-03-10 21:54:23 +00:00
|
|
|
|
$u->doUpdate();
|
|
|
|
|
|
$this->output( "\n" );
|
|
|
|
|
|
}
|
2014-04-23 18:08:06 +00:00
|
|
|
|
|
2010-03-10 21:54:23 +00:00
|
|
|
|
return $title;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2017-10-13 00:24:46 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Count down from $seconds to zero on the terminal, with a one-second pause
|
|
|
|
|
|
* between showing each number. If the maintenance script is in quiet mode,
|
|
|
|
|
|
* this function does nothing.
|
|
|
|
|
|
*
|
|
|
|
|
|
* @since 1.31
|
|
|
|
|
|
*
|
|
|
|
|
|
* @codeCoverageIgnore
|
|
|
|
|
|
* @param int $seconds
|
|
|
|
|
|
*/
|
|
|
|
|
|
protected function countDown( $seconds ) {
|
|
|
|
|
|
if ( $this->isQuiet() ) {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
for ( $i = $seconds; $i >= 0; $i-- ) {
|
|
|
|
|
|
if ( $i != $seconds ) {
|
|
|
|
|
|
$this->output( str_repeat( "\x08", strlen( $i + 1 ) ) );
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->output( $i );
|
|
|
|
|
|
if ( $i ) {
|
|
|
|
|
|
sleep( 1 );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
$this->output( "\n" );
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-08-02 22:01:58 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Wrapper for posix_isatty()
|
|
|
|
|
|
* We default as considering stdin a tty (for nice readline methods)
|
|
|
|
|
|
* but treating stout as not a tty to avoid color codes
|
|
|
|
|
|
*
|
2014-12-10 20:00:45 +00:00
|
|
|
|
* @param mixed $fd File descriptor
|
2011-08-02 22:01:58 +00:00
|
|
|
|
* @return bool
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function posix_isatty( $fd ) {
|
2013-08-08 03:18:34 +00:00
|
|
|
|
if ( !function_exists( 'posix_isatty' ) ) {
|
2011-08-02 22:01:58 +00:00
|
|
|
|
return !$fd;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
return posix_isatty( $fd );
|
|
|
|
|
|
}
|
2011-08-15 18:52:25 +00:00
|
|
|
|
}
|
2011-08-02 22:01:58 +00:00
|
|
|
|
|
2010-11-06 22:16:19 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Prompt the console for input
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param string $prompt What to begin the line with, like '> '
|
|
|
|
|
|
* @return string Response
|
2010-11-06 22:16:19 +00:00
|
|
|
|
*/
|
|
|
|
|
|
public static function readconsole( $prompt = '> ' ) {
|
|
|
|
|
|
static $isatty = null;
|
|
|
|
|
|
if ( is_null( $isatty ) ) {
|
2011-08-02 22:01:58 +00:00
|
|
|
|
$isatty = self::posix_isatty( 0 /*STDIN*/ );
|
2010-11-06 22:16:19 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ( $isatty && function_exists( 'readline' ) ) {
|
2017-10-13 00:38:34 +00:00
|
|
|
|
return readline( $prompt );
|
2010-11-06 22:16:19 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
if ( $isatty ) {
|
2010-11-06 22:34:57 +00:00
|
|
|
|
$st = self::readlineEmulation( $prompt );
|
2010-11-06 22:16:19 +00:00
|
|
|
|
} else {
|
|
|
|
|
|
if ( feof( STDIN ) ) {
|
|
|
|
|
|
$st = false;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$st = fgets( STDIN, 1024 );
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2013-04-18 18:48:44 +00:00
|
|
|
|
if ( $st === false ) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
2010-11-06 22:16:19 +00:00
|
|
|
|
$resp = trim( $st );
|
2014-04-23 18:08:06 +00:00
|
|
|
|
|
2010-11-06 22:16:19 +00:00
|
|
|
|
return $resp;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Emulate readline()
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @param string $prompt What to begin the line with, like '> '
|
|
|
|
|
|
* @return string
|
2010-11-06 22:16:19 +00:00
|
|
|
|
*/
|
|
|
|
|
|
private static function readlineEmulation( $prompt ) {
|
2017-10-20 07:36:03 +00:00
|
|
|
|
$bash = ExecutableFinder::findInDefaultPaths( 'bash' );
|
2010-11-06 22:46:37 +00:00
|
|
|
|
if ( !wfIsWindows() && $bash ) {
|
2010-11-06 22:16:19 +00:00
|
|
|
|
$retval = false;
|
|
|
|
|
|
$encPrompt = wfEscapeShellArg( $prompt );
|
|
|
|
|
|
$command = "read -er -p $encPrompt && echo \"\$REPLY\"";
|
|
|
|
|
|
$encCommand = wfEscapeShellArg( $command );
|
2016-02-17 09:09:32 +00:00
|
|
|
|
$line = wfShellExec( "$bash -c $encCommand", $retval, [], [ 'walltime' => 0 ] );
|
2010-11-06 22:16:19 +00:00
|
|
|
|
|
|
|
|
|
|
if ( $retval == 0 ) {
|
|
|
|
|
|
return $line;
|
|
|
|
|
|
} elseif ( $retval == 127 ) {
|
|
|
|
|
|
// Couldn't execute bash even though we thought we saw it.
|
|
|
|
|
|
// Shell probably spit out an error message, sorry :(
|
|
|
|
|
|
// Fall through to fgets()...
|
|
|
|
|
|
} else {
|
|
|
|
|
|
// EOF/ctrl+D
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Fallback... we'll have no editing controls, EWWW
|
|
|
|
|
|
if ( feof( STDIN ) ) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
print $prompt;
|
2014-04-23 18:08:06 +00:00
|
|
|
|
|
2010-11-06 22:16:19 +00:00
|
|
|
|
return fgets( STDIN, 1024 );
|
|
|
|
|
|
}
|
Refactor parser tests
Merge the PHPUnit parser test runner with the old parserTests.inc,
taking the good bits of both. Reviewed, pared down and documented the
setup code. parserTests.php is now a frontend to a fully featured
parser test system, with lots of developer options, whereas PHPUnit
provides a simpler interface with increased isolation between test
cases.
Performance of both frontends is much improved, perhaps 2x faster for
parserTests.php and 10x faster for PHPUnit.
General:
* Split out the pre-Setup.php global variable configuration from
phpunit.php into a new class called TestSetup, also called it from
parserTests.php.
* Factored out the setup of TestsAutoLoader into a static method in
Maintenance.
* In Setup.php improved "caches" debug output.
PHPUnit frontend:
* Delete the entire contents of NewParserTest and replace it with a
small wrapper around ParserTestRunner. It doesn't inherit from
MediaWikiTestCase anymore since integrating the setup code was an
unnecessary complication.
* Rename MediaWikiParserTest to ParserTestTopLevelSuite and made it an
instantiable TestSuite class instead of just a static method. Got rid
of the eval(), just construct TestCase objects directly with a
specified name, it works just as well.
* Introduce ParserTestFileSuite for per-file setup.
* Remove parser-related options from phpunit.php, since we don't
support them anymore. Note that --filter now works just as well as
--regex used to.
* Add CoreParserTestSuite, equivalent to ExtensionsParserTestSuite,
for clarity.
* Make it possible to call MediaWikiTestCase::setupTestDB() more than
once, as is implied by the documentation.
parserTests.php frontend:
* Made parserTests.php into a Maintenance subclass, moved CLI-specific
code to it.
* Renamed ParserTest to ParserTestRunner, this is now the generic
backend.
* Add --upload-dir option which sets up an FSFileBackend, similar
to the old default behaviour
Test file reading and interpretation:
* Rename TestFileIterator to TestFileReader, and make it read and buffer
an entire file, instead of iterating.
* The previous code had an associative array representation of test
specifications. Used this form more widely to pass around test data.
* Remove the idea of !!hooks copying hooks from $wgParser, this is
unnecessary now that all extensions use ParserFirstCallInit. Resurrect
an old interpretation of the feature which was accidentally broken: if
a named hook does not exist, skip all tests in the file.
* Got rid of the "subtest" idea for tidy variants, instead use a
human-readable description that appears in the output.
* When all tests in a file are filtered or skipped, don't create the
articles in them. This greatly speeds up execution time when --regex
matches a small number of tests. It may possibly break extensions, but
they would have been randomly broken anyway since there is no
guarantee of test file execution order.
* Remove integrated testing of OutputPage::addCategoryLinks() category
link formatting, life is complicated enough already. It can go in
OutputPageTest if that's a thing we really need.
Result recording and display:
* Make TestRecorder into a generic plugin interface for progress output
etc., which needs to be abstracted for PHPUnit integration.
* Introduce MultiTestRecorder for recorder chaining, instead of using
a long inheritance chain. All test recorders now directly inherit from
TestRecorder.
* Move all console-related code to the new ParserTestPrinter.
* Introduce PhpunitTestRecorder, which is the recorder for the PHPUnit
frontend. Most events are ignored since they are never emitted in the
PHPUnit frontend, which does not call runTests().
* Put more information into ParserTestResult and use it more often.
Setup and teardown:
* Introduce a new API for setup/teardown where setup functions return a
ScopedCallback object which automatically performs the corresponding
teardown when it goes out of scope.
* Rename setUp() to staticSetup(), rewrite. There was a lot of cruft in
here which was simply copied from Setup.php without review, and had
nothing to do with parser tests.
* Rename setupGlobals() to perTestSetup(), mostly rewrite. For
performance, give staticSetup() precedence in cases where they were
both setting up the same thing.
* In support of merged setup code, allow Hooks::clear() to be called
from parserTests.php.
* Remove wgFileExtensions -- it is only used by UploadBase which we
don't call.
* Remove wgUseImageResize -- superseded by MockMediaHandlerFactory which
I imported from NewParserTest.
* Import MockFileBackend from NewParserTest. But instead of
customising the configuration globals, I injected services.
* Remove thumbnail deletion from upload teardown. This makes glob
handling as in the old parserTests.php unnecessary.
* Remove math file from upload teardown, math is actually an extension
now! Also, the relevant parser tests were removed from the Math
extension two years ago in favour of unit tests.
* Make addArticle() private, and introduce addArticles() instead, which
allows setup/teardown to be done once for each batch of articles
instead of every time.
* Remove $wgNamespaceAliases and $wgNamespaceProtection setup. These were
copied in from Setup.php in 2010, and are redundant since we do
actually run Setup.php.
* Use NullLockManager, don't set up a temporary directory just for
this alone.
Fuzz tests:
* Use the new TestSetup class.
* Updated for ParserTestRunner interface change.
* Remove some obsolete references to fuzz tests from the two frontends
where they used to reside.
Bug: T41473
Change-Id: Ia8e17008cb9d9b62ce5645e15a41a3b402f4026a
2016-09-08 01:25:22 +00:00
|
|
|
|
|
2016-09-28 06:32:17 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Get the terminal size as a two-element array where the first element
|
|
|
|
|
|
* is the width (number of columns) and the second element is the height
|
|
|
|
|
|
* (number of rows).
|
|
|
|
|
|
*
|
|
|
|
|
|
* @return array
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function getTermSize() {
|
|
|
|
|
|
$default = [ 80, 50 ];
|
|
|
|
|
|
if ( wfIsWindows() ) {
|
|
|
|
|
|
return $default;
|
|
|
|
|
|
}
|
2018-06-24 21:07:42 +00:00
|
|
|
|
if ( Shell::isDisabled() ) {
|
|
|
|
|
|
return $default;
|
|
|
|
|
|
}
|
2016-09-28 06:32:17 +00:00
|
|
|
|
// It's possible to get the screen size with VT-100 terminal escapes,
|
|
|
|
|
|
// but reading the responses is not possible without setting raw mode
|
|
|
|
|
|
// (unless you want to require the user to press enter), and that
|
|
|
|
|
|
// requires an ioctl(), which we can't do. So we have to shell out to
|
|
|
|
|
|
// something that can do the relevant syscalls. There are a few
|
|
|
|
|
|
// options. Linux and Mac OS X both have "stty size" which does the
|
|
|
|
|
|
// job directly.
|
2018-01-18 02:24:34 +00:00
|
|
|
|
$result = Shell::command( 'stty', 'size' )
|
|
|
|
|
|
->execute();
|
|
|
|
|
|
if ( $result->getExitCode() !== 0 ) {
|
2016-09-28 06:32:17 +00:00
|
|
|
|
return $default;
|
|
|
|
|
|
}
|
2018-01-18 02:24:34 +00:00
|
|
|
|
if ( !preg_match( '/^(\d+) (\d+)$/', $result->getStdout(), $m ) ) {
|
2016-09-28 06:32:17 +00:00
|
|
|
|
return $default;
|
|
|
|
|
|
}
|
|
|
|
|
|
return [ intval( $m[2] ), intval( $m[1] ) ];
|
|
|
|
|
|
}
|
|
|
|
|
|
|
Refactor parser tests
Merge the PHPUnit parser test runner with the old parserTests.inc,
taking the good bits of both. Reviewed, pared down and documented the
setup code. parserTests.php is now a frontend to a fully featured
parser test system, with lots of developer options, whereas PHPUnit
provides a simpler interface with increased isolation between test
cases.
Performance of both frontends is much improved, perhaps 2x faster for
parserTests.php and 10x faster for PHPUnit.
General:
* Split out the pre-Setup.php global variable configuration from
phpunit.php into a new class called TestSetup, also called it from
parserTests.php.
* Factored out the setup of TestsAutoLoader into a static method in
Maintenance.
* In Setup.php improved "caches" debug output.
PHPUnit frontend:
* Delete the entire contents of NewParserTest and replace it with a
small wrapper around ParserTestRunner. It doesn't inherit from
MediaWikiTestCase anymore since integrating the setup code was an
unnecessary complication.
* Rename MediaWikiParserTest to ParserTestTopLevelSuite and made it an
instantiable TestSuite class instead of just a static method. Got rid
of the eval(), just construct TestCase objects directly with a
specified name, it works just as well.
* Introduce ParserTestFileSuite for per-file setup.
* Remove parser-related options from phpunit.php, since we don't
support them anymore. Note that --filter now works just as well as
--regex used to.
* Add CoreParserTestSuite, equivalent to ExtensionsParserTestSuite,
for clarity.
* Make it possible to call MediaWikiTestCase::setupTestDB() more than
once, as is implied by the documentation.
parserTests.php frontend:
* Made parserTests.php into a Maintenance subclass, moved CLI-specific
code to it.
* Renamed ParserTest to ParserTestRunner, this is now the generic
backend.
* Add --upload-dir option which sets up an FSFileBackend, similar
to the old default behaviour
Test file reading and interpretation:
* Rename TestFileIterator to TestFileReader, and make it read and buffer
an entire file, instead of iterating.
* The previous code had an associative array representation of test
specifications. Used this form more widely to pass around test data.
* Remove the idea of !!hooks copying hooks from $wgParser, this is
unnecessary now that all extensions use ParserFirstCallInit. Resurrect
an old interpretation of the feature which was accidentally broken: if
a named hook does not exist, skip all tests in the file.
* Got rid of the "subtest" idea for tidy variants, instead use a
human-readable description that appears in the output.
* When all tests in a file are filtered or skipped, don't create the
articles in them. This greatly speeds up execution time when --regex
matches a small number of tests. It may possibly break extensions, but
they would have been randomly broken anyway since there is no
guarantee of test file execution order.
* Remove integrated testing of OutputPage::addCategoryLinks() category
link formatting, life is complicated enough already. It can go in
OutputPageTest if that's a thing we really need.
Result recording and display:
* Make TestRecorder into a generic plugin interface for progress output
etc., which needs to be abstracted for PHPUnit integration.
* Introduce MultiTestRecorder for recorder chaining, instead of using
a long inheritance chain. All test recorders now directly inherit from
TestRecorder.
* Move all console-related code to the new ParserTestPrinter.
* Introduce PhpunitTestRecorder, which is the recorder for the PHPUnit
frontend. Most events are ignored since they are never emitted in the
PHPUnit frontend, which does not call runTests().
* Put more information into ParserTestResult and use it more often.
Setup and teardown:
* Introduce a new API for setup/teardown where setup functions return a
ScopedCallback object which automatically performs the corresponding
teardown when it goes out of scope.
* Rename setUp() to staticSetup(), rewrite. There was a lot of cruft in
here which was simply copied from Setup.php without review, and had
nothing to do with parser tests.
* Rename setupGlobals() to perTestSetup(), mostly rewrite. For
performance, give staticSetup() precedence in cases where they were
both setting up the same thing.
* In support of merged setup code, allow Hooks::clear() to be called
from parserTests.php.
* Remove wgFileExtensions -- it is only used by UploadBase which we
don't call.
* Remove wgUseImageResize -- superseded by MockMediaHandlerFactory which
I imported from NewParserTest.
* Import MockFileBackend from NewParserTest. But instead of
customising the configuration globals, I injected services.
* Remove thumbnail deletion from upload teardown. This makes glob
handling as in the old parserTests.php unnecessary.
* Remove math file from upload teardown, math is actually an extension
now! Also, the relevant parser tests were removed from the Math
extension two years ago in favour of unit tests.
* Make addArticle() private, and introduce addArticles() instead, which
allows setup/teardown to be done once for each batch of articles
instead of every time.
* Remove $wgNamespaceAliases and $wgNamespaceProtection setup. These were
copied in from Setup.php in 2010, and are redundant since we do
actually run Setup.php.
* Use NullLockManager, don't set up a temporary directory just for
this alone.
Fuzz tests:
* Use the new TestSetup class.
* Updated for ParserTestRunner interface change.
* Remove some obsolete references to fuzz tests from the two frontends
where they used to reside.
Bug: T41473
Change-Id: Ia8e17008cb9d9b62ce5645e15a41a3b402f4026a
2016-09-08 01:25:22 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Call this to set up the autoloader to allow classes to be used from the
|
|
|
|
|
|
* tests directory.
|
|
|
|
|
|
*/
|
|
|
|
|
|
public static function requireTestsAutoloader() {
|
|
|
|
|
|
require_once __DIR__ . '/../tests/common/TestsAutoLoader.php';
|
|
|
|
|
|
}
|
2009-08-02 19:35:17 +00:00
|
|
|
|
}
|
2010-10-27 14:38:31 +00:00
|
|
|
|
|
2011-09-08 15:52:00 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Fake maintenance wrapper, mostly used for the web installer/updater
|
|
|
|
|
|
*/
|
2010-10-27 14:38:31 +00:00
|
|
|
|
class FakeMaintenance extends Maintenance {
|
2010-12-07 02:42:01 +00:00
|
|
|
|
protected $mSelf = "FakeMaintenanceScript";
|
2014-04-23 18:08:06 +00:00
|
|
|
|
|
2010-10-27 14:38:31 +00:00
|
|
|
|
public function execute() {
|
|
|
|
|
|
return;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2011-08-12 19:11:04 +00:00
|
|
|
|
|
2011-09-08 15:52:00 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Class for scripts that perform database maintenance and want to log the
|
|
|
|
|
|
* update in `updatelog` so we can later skip it
|
|
|
|
|
|
*/
|
2011-08-12 19:11:04 +00:00
|
|
|
|
abstract class LoggedUpdateMaintenance extends Maintenance {
|
|
|
|
|
|
public function __construct() {
|
|
|
|
|
|
parent::__construct();
|
|
|
|
|
|
$this->addOption( 'force', 'Run the update even if it was completed already' );
|
2011-09-08 15:52:00 +00:00
|
|
|
|
$this->setBatchSize( 200 );
|
2011-08-12 19:11:04 +00:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
public function execute() {
|
|
|
|
|
|
$db = $this->getDB( DB_MASTER );
|
|
|
|
|
|
$key = $this->getUpdateKey();
|
|
|
|
|
|
|
2013-12-01 20:39:00 +00:00
|
|
|
|
if ( !$this->hasOption( 'force' )
|
2016-02-17 09:09:32 +00:00
|
|
|
|
&& $db->selectRow( 'updatelog', '1', [ 'ul_key' => $key ], __METHOD__ )
|
2013-12-01 20:39:00 +00:00
|
|
|
|
) {
|
2011-09-08 15:52:00 +00:00
|
|
|
|
$this->output( "..." . $this->updateSkippedMessage() . "\n" );
|
2014-04-23 18:08:06 +00:00
|
|
|
|
|
2011-08-12 19:11:04 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
if ( !$this->doDBUpdates() ) {
|
|
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2016-02-17 09:09:32 +00:00
|
|
|
|
if ( $db->insert( 'updatelog', [ 'ul_key' => $key ], __METHOD__, 'IGNORE' ) ) {
|
2011-08-12 19:11:04 +00:00
|
|
|
|
return true;
|
|
|
|
|
|
} else {
|
|
|
|
|
|
$this->output( $this->updatelogFailedMessage() . "\n" );
|
2014-04-23 18:08:06 +00:00
|
|
|
|
|
2011-08-12 19:11:04 +00:00
|
|
|
|
return false;
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-10-27 18:37:11 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Message to show that the update was done already and was just skipped
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @return string
|
2011-10-27 18:37:11 +00:00
|
|
|
|
*/
|
|
|
|
|
|
protected function updateSkippedMessage() {
|
|
|
|
|
|
$key = $this->getUpdateKey();
|
2014-04-23 18:08:06 +00:00
|
|
|
|
|
2011-10-27 18:37:11 +00:00
|
|
|
|
return "Update '{$key}' already logged as completed.";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-09-08 15:52:00 +00:00
|
|
|
|
/**
|
2014-12-16 00:41:45 +00:00
|
|
|
|
* Message to show that the update log was unable to log the completion of this update
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @return string
|
2011-09-08 15:52:00 +00:00
|
|
|
|
*/
|
|
|
|
|
|
protected function updatelogFailedMessage() {
|
|
|
|
|
|
$key = $this->getUpdateKey();
|
2014-04-23 18:08:06 +00:00
|
|
|
|
|
2011-09-08 15:52:00 +00:00
|
|
|
|
return "Unable to log update '{$key}' as completed.";
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2011-08-12 19:11:04 +00:00
|
|
|
|
/**
|
|
|
|
|
|
* Do the actual work. All child classes will need to implement this.
|
2011-08-12 20:37:35 +00:00
|
|
|
|
* Return true to log the update as done or false (usually on failure).
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @return bool
|
2011-08-12 19:11:04 +00:00
|
|
|
|
*/
|
|
|
|
|
|
abstract protected function doDBUpdates();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
|
* Get the update key name to go in the update log table
|
2014-04-17 20:48:32 +00:00
|
|
|
|
* @return string
|
2011-08-12 19:11:04 +00:00
|
|
|
|
*/
|
|
|
|
|
|
abstract protected function getUpdateKey();
|
|
|
|
|
|
}
|