diff --git a/scripts/sql/manage_storage.php b/scripts/sql/manage_storage.php --- a/scripts/sql/manage_storage.php +++ b/scripts/sql/manage_storage.php @@ -63,16 +63,17 @@ $default_namespace), ), array( - 'name' => 'dryrun', - 'help' => pht( + 'name' => 'dryrun', + 'help' => pht( 'Do not actually change anything, just show what would be changed.'), ), array( - 'name' => 'disable-utf8mb4', - 'help' => pht( - 'Disable utf8mb4, even if the database supports it. This is an '. + 'name' => 'disable-utf8mb4', + 'help' => pht( + 'Disable %s, even if the database supports it. This is an '. 'advanced feature used for testing changes to Phabricator; you '. - 'should not normally use this flag.'), + 'should not normally use this flag.', + 'utf8mb4'), ), )); } catch (PhutilArgumentUsageException $ex) { @@ -83,12 +84,12 @@ // First, test that the Phabricator configuration is set up correctly. After // we know this works we'll test any administrative credentials specifically. -$test_api = new PhabricatorStorageManagementAPI(); -$test_api->setUser($default_user); -$test_api->setHost($default_host); -$test_api->setPort($default_port); -$test_api->setPassword($conf->getPassword()); -$test_api->setNamespace($args->getArg('namespace')); +$test_api = id(new PhabricatorStorageManagementAPI()) + ->setUser($default_user) + ->setHost($default_host) + ->setPort($default_port) + ->setPassword($conf->getPassword()) + ->setNamespace($args->getArg('namespace')); try { queryfx( @@ -113,13 +114,10 @@ '--password'), pht('Raw MySQL Error'), $ex->getMessage()); - echo phutil_console_wrap($message); - exit(1); } - if ($args->getArg('password') === null) { // This is already a PhutilOpaqueEnvelope. $password = $conf->getPassword(); @@ -129,14 +127,14 @@ PhabricatorEnv::overrideConfig('mysql.pass', $args->getArg('password')); } -$api = new PhabricatorStorageManagementAPI(); -$api->setUser($args->getArg('user')); -PhabricatorEnv::overrideConfig('mysql.user', $args->getArg('user')); -$api->setHost($default_host); -$api->setPort($default_port); -$api->setPassword($password); -$api->setNamespace($args->getArg('namespace')); -$api->setDisableUTF8MB4($args->getArg('disable-utf8mb4')); +$api = id(new PhabricatorStorageManagementAPI()) + ->setUser($args->getArg('user')) + ->setHost($default_host) + ->setPort($default_port) + ->setPassword($password) + ->setNamespace($args->getArg('namespace')) + ->setDisableUTF8MB4($args->getArg('disable-utf8mb4')); +PhabricatorEnv::overrideConfig('mysql.user', $api->getUser()); try { queryfx( @@ -154,9 +152,7 @@ '--password'), pht('Raw MySQL Error'), $ex->getMessage()); - echo phutil_console_wrap($message); - exit(1); } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementAdjustWorkflow.php @@ -25,12 +25,12 @@ } public function execute(PhutilArgumentParser $args) { - $force = $args->getArg('force'); + parent::execute($args); + $unsafe = $args->getArg('unsafe'); - $dry_run = $args->getArg('dryrun'); $this->requireAllPatchesApplied(); - return $this->adjustSchemata($force, $unsafe, $dry_run); + return $this->adjustSchemata($unsafe); } private function requireAllPatchesApplied() { diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDatabasesWorkflow.php @@ -11,12 +11,13 @@ } public function execute(PhutilArgumentParser $args) { - $api = $this->getAPI(); + parent::execute($args); + + $api = $this->getAPI(); $patches = $this->getPatches(); - $databases = $api->getDatabaseList($patches, $only_living = true); + $databases = $api->getDatabaseList($patches, true); echo implode("\n", $databases)."\n"; - return 0; } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDestroyWorkflow.php @@ -21,28 +21,30 @@ } public function execute(PhutilArgumentParser $args) { - $is_dry = $args->getArg('dryrun'); - $is_force = $args->getArg('force'); + parent::execute($args); - if (!$is_dry && !$is_force) { - echo phutil_console_wrap( - pht( - 'Are you completely sure you really want to permanently destroy all '. - 'storage for Phabricator data? This operation can not be undone and '. - 'your data will not be recoverable if you proceed.')); + $console = PhutilConsole::getConsole(); + + if (!$this->isDryRun() && !$this->isForce()) { + $console->writeOut( + phutil_console_wrap( + pht( + 'Are you completely sure you really want to permanently destroy '. + 'all storage for Phabricator data? This operation can not be '. + 'undone and your data will not be recoverable if you proceed.'))); if (!phutil_console_confirm(pht('Permanently destroy all data?'))) { - echo pht('Cancelled.')."\n"; + $console->writeOut("%s\n", pht('Cancelled.')); exit(1); } if (!phutil_console_confirm(pht('Really destroy all data forever?'))) { - echo pht('Cancelled.')."\n"; + $console->writeOut("%s\n", pht('Cancelled.')); exit(1); } } - $api = $this->getAPI(); + $api = $this->getAPI(); $patches = $this->getPatches(); if ($args->getArg('unittest-fixtures')) { @@ -55,18 +57,23 @@ PhabricatorTestCase::NAMESPACE_PREFIX); $databases = ipull($databases, 'db'); } else { - $databases = $api->getDatabaseList($patches); + $databases = $api->getDatabaseList($patches); $databases[] = $api->getDatabaseName('meta_data'); + // These are legacy databases that were dropped long ago. See T2237. $databases[] = $api->getDatabaseName('phid'); $databases[] = $api->getDatabaseName('directory'); } foreach ($databases as $database) { - if ($is_dry) { - echo pht("DRYRUN: Would drop database '%s'.", $database)."\n"; + if ($this->isDryRun()) { + $console->writeOut( + "%s\n", + pht("DRYRUN: Would drop database '%s'.", $database)); } else { - echo pht("Dropping database '%s'...", $database)."\n"; + $console->writeOut( + "%s\n", + pht("Dropping database '%s'...", $database)); queryfx( $api->getConn(null), 'DROP DATABASE IF EXISTS %T', @@ -74,8 +81,8 @@ } } - if (!$is_dry) { - echo pht('Storage was destroyed.')."\n"; + if (!$this->isDryRun()) { + $console->writeOut("%s\n", pht('Storage was destroyed.')); } return 0; diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php @@ -11,10 +11,13 @@ } public function execute(PhutilArgumentParser $args) { - $console = PhutilConsole::getConsole(); - $api = $this->getAPI(); + parent::execute($args); + + $api = $this->getAPI(); $patches = $this->getPatches(); + $console = PhutilConsole::getConsole(); + $applied = $api->getAppliedPatches(); if ($applied === null) { $namespace = $api->getNamespace(); @@ -24,11 +27,11 @@ 'initialized in this storage namespace ("%s"). Use '. '**%s** to initialize storage.', $namespace, - 'storage upgrade')); + './bin/storage upgrade')); return 1; } - $databases = $api->getDatabaseList($patches, $only_living = true); + $databases = $api->getDatabaseList($patches, true); list($host, $port) = $this->getBareHostAndPort($api->getHost()); diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementProbeWorkflow.php @@ -11,14 +11,16 @@ } public function execute(PhutilArgumentParser $args) { + parent::execute($args); + $console = PhutilConsole::getConsole(); $console->writeErr( "%s\n", pht('Analyzing table sizes (this may take a moment)...')); - $api = $this->getAPI(); - $patches = $this->getPatches(); - $databases = $api->getDatabaseList($patches, $only_living = true); + $api = $this->getAPI(); + $patches = $this->getPatches(); + $databases = $api->getDatabaseList($patches, true); $conn_r = $api->getConn(null); diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementQuickstartWorkflow.php @@ -22,6 +22,8 @@ } public function execute(PhutilArgumentParser $args) { + parent::execute($args); + $output = $args->getArg('output'); if (!$output) { throw new PhutilArgumentUsageException( @@ -38,8 +40,10 @@ throw new PhutilArgumentUsageException( pht( 'You can only generate a new quickstart file if MySQL supports '. - 'the utf8mb4 character set (available in MySQL 5.5 and newer). The '. - 'configured server does not support utf8mb4.')); + 'the %s character set (available in MySQL 5.5 and newer). The '. + 'configured server does not support %s.', + 'utf8mb4', + 'utf8mb4')); } $err = phutil_passthru( @@ -139,7 +143,7 @@ $dump = preg_replace('/^--.*$/m', '', $dump); // Remove table drops, locks, and unlocks. These are never relevant when - // performing q quickstart. + // performing a quickstart. $dump = preg_replace( '/^(DROP TABLE|LOCK TABLES|UNLOCK TABLES).*$/m', '', diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementRenamespaceWorkflow.php @@ -31,24 +31,32 @@ } public function execute(PhutilArgumentParser $args) { + parent::execute($args); + $console = PhutilConsole::getConsole(); $in = $args->getArg('in'); if (!strlen($in)) { throw new PhutilArgumentUsageException( - pht('Specify the dumpfile to read with --in.')); + pht( + 'Specify the dumpfile to read with %s.', + '--in')); } $from = $args->getArg('from'); if (!strlen($from)) { throw new PhutilArgumentUsageException( - pht('Specify namespace to rename from with --from.')); + pht( + 'Specify namespace to rename from with %s.', + '--from')); } $to = $args->getArg('to'); if (!strlen($to)) { throw new PhutilArgumentUsageException( - pht('Specify namespace to rename to with --to.')); + pht( + 'Specify namespace to rename to with %s.', + '--to')); } $patterns = array( diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementShellWorkflow.php @@ -11,6 +11,8 @@ } public function execute(PhutilArgumentParser $args) { + parent::execute($args); + $api = $this->getAPI(); list($host, $port) = $this->getBareHostAndPort($api->getHost()); diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementStatusWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementStatusWorkflow.php --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementStatusWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementStatusWorkflow.php @@ -11,7 +11,9 @@ } public function execute(PhutilArgumentParser $args) { - $api = $this->getAPI(); + parent::execute($args); + + $api = $this->getAPI(); $patches = $this->getPatches(); $applied = $api->getAppliedPatches(); @@ -20,18 +22,18 @@ echo phutil_console_format( "**%s**: %s\n", pht('Database Not Initialized'), - pht('Run **%s** to initialize.', 'storage upgrade')); + pht('Run **%s** to initialize.', './bin/storage upgrade')); return 1; } $table = id(new PhutilConsoleTable()) ->setShowHeader(false) - ->addColumn('id', array('title' => pht('ID'))) - ->addColumn('status', array('title' => pht('Status'))) + ->addColumn('id', array('title' => pht('ID'))) + ->addColumn('status', array('title' => pht('Status'))) ->addColumn('duration', array('title' => pht('Duration'))) - ->addColumn('type', array('title' => pht('Type'))) - ->addColumn('name', array('title' => pht('Name'))); + ->addColumn('type', array('title' => pht('Type'))) + ->addColumn('name', array('title' => pht('Name'))); $durations = $api->getPatchDurations(); diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementUpgradeWorkflow.php @@ -21,7 +21,7 @@ array( 'name' => 'no-quickstart', 'help' => pht( - 'Build storage patch-by-patch from scatch, even if it could '. + 'Build storage patch-by-patch from scratch, even if it could '. 'be loaded from the quickstart template.'), ), array( @@ -38,22 +38,22 @@ } public function execute(PhutilArgumentParser $args) { - $is_dry = $args->getArg('dryrun'); - $is_force = $args->getArg('force'); + parent::execute($args); - $api = $this->getAPI(); + $console = PhutilConsole::getConsole(); $patches = $this->getPatches(); - if (!$is_dry && !$is_force) { - echo phutil_console_wrap( - pht( - 'Before running storage upgrades, you should take down the '. - 'Phabricator web interface and stop any running Phabricator '. - 'daemons (you can disable this warning with %s).', - '--force')); + if (!$this->isDryRun() && !$this->isForce()) { + $console->writeOut( + phutil_console_wrap( + pht( + 'Before running storage upgrades, you should take down the '. + 'Phabricator web interface and stop any running Phabricator '. + 'daemons (you can disable this warning with %s).', + '--force'))); if (!phutil_console_confirm(pht('Are you ready to continue?'))) { - echo pht('Cancelled.')."\n"; + $console->writeOut("%s\n", pht('Cancelled.')); return 1; } } @@ -67,163 +67,23 @@ "Use '%s' to show patch status.", '--apply', $apply_only, - 'storage status')); + './bin/storage status')); } } $no_quickstart = $args->getArg('no-quickstart'); - $init_only = $args->getArg('init-only'); - $no_adjust = $args->getArg('no-adjust'); - - $applied = $api->getAppliedPatches(); - if ($applied === null) { - - if ($is_dry) { - echo pht( - "DRYRUN: Patch metadata storage doesn't exist yet, ". - "it would be created.\n"); - return 0; - } - - if ($apply_only) { - throw new PhutilArgumentUsageException( - pht( - 'Storage has not been initialized yet, you must initialize '. - 'storage before selectively applying patches.')); - return 1; - } - - $legacy = $api->getLegacyPatches($patches); - if ($legacy || $no_quickstart || $init_only) { - - // If we have legacy patches, we can't quickstart. - - $api->createDatabase('meta_data'); - $api->createTable( - 'meta_data', - 'patch_status', - array( - 'patch VARCHAR(255) NOT NULL PRIMARY KEY COLLATE utf8_general_ci', - 'applied INT UNSIGNED NOT NULL', - )); - - foreach ($legacy as $patch) { - $api->markPatchApplied($patch); - } - } else { - echo pht('Loading quickstart template...')."\n"; - $root = dirname(phutil_get_library_root('phabricator')); - $sql = $root.'/resources/sql/quickstart.sql'; - $api->applyPatchSQL($sql); - } - } - - if ($init_only) { - echo pht('Storage initialized.')."\n"; - return 0; - } + $init_only = $args->getArg('init-only'); + $no_adjust = $args->getArg('no-adjust'); - $applied = $api->getAppliedPatches(); - $applied = array_fuse($applied); + $this->upgradeSchemata($apply_only, $no_quickstart, $init_only); - $skip_mark = false; - if ($apply_only) { - if (isset($applied[$apply_only])) { - - unset($applied[$apply_only]); - $skip_mark = true; - - if (!$is_force && !$is_dry) { - echo phutil_console_wrap( - pht( - "Patch '%s' has already been applied. Are you sure you want ". - "to apply it again? This may put your storage in a state ". - "that the upgrade scripts can not automatically manage.", - $apply_only)); - if (!phutil_console_confirm(pht('Apply patch again?'))) { - echo pht('Cancelled.')."\n"; - return 1; - } - } - } - } - - while (true) { - $applied_something = false; - foreach ($patches as $key => $patch) { - if (isset($applied[$key])) { - unset($patches[$key]); - continue; - } - - if ($apply_only && $apply_only != $key) { - unset($patches[$key]); - continue; - } - - $can_apply = true; - foreach ($patch->getAfter() as $after) { - if (empty($applied[$after])) { - if ($apply_only) { - echo pht( - "Unable to apply patch '%s' because it depends ". - "on patch '%s', which has not been applied.\n", - $apply_only, - $after); - return 1; - } - $can_apply = false; - break; - } - } - - if (!$can_apply) { - continue; - } - - $applied_something = true; - - if ($is_dry) { - echo pht("DRYRUN: Would apply patch '%s'.", $key)."\n"; - } else { - echo pht("Applying patch '%s'...", $key)."\n"; - - $t_begin = microtime(true); - $api->applyPatch($patch); - $t_end = microtime(true); - - if (!$skip_mark) { - $api->markPatchApplied($key, ($t_end - $t_begin)); - } - } - - unset($patches[$key]); - $applied[$key] = true; - } - - if (!$applied_something) { - if (count($patches)) { - throw new Exception( - pht( - 'Some patches could not be applied: %s', - implode(', ', array_keys($patches)))); - } else if (!$is_dry && !$apply_only) { - echo pht( - "Storage is up to date. Use '%s' for details.", - 'storage status')."\n"; - } - break; - } - } - - $console = PhutilConsole::getConsole(); if ($no_adjust || $init_only || $apply_only) { $console->writeOut( "%s\n", pht('Declining to apply storage adjustments.')); return 0; } else { - return $this->adjustSchemata($is_force, $unsafe = false, $is_dry); + return $this->adjustSchemata(false); } } diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php --- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php +++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementWorkflow.php @@ -3,12 +3,35 @@ abstract class PhabricatorStorageManagementWorkflow extends PhabricatorManagementWorkflow { - private $patches; private $api; + private $dryRun; + private $force; + private $patches; - public function setPatches(array $patches) { - assert_instances_of($patches, 'PhabricatorStoragePatch'); - $this->patches = $patches; + final public function getAPI() { + return $this->api; + } + + final public function setAPI(PhabricatorStorageManagementAPI $api) { + $this->api = $api; + return $this; + } + + final protected function isDryRun() { + return $this->dryRun; + } + + final protected function setDryRun($dry_run) { + $this->dryRun = $dry_run; + return $this; + } + + final protected function isForce() { + return $this->force; + } + + final protected function setForce($force) { + $this->force = $force; return $this; } @@ -16,13 +39,16 @@ return $this->patches; } - final public function setAPI(PhabricatorStorageManagementAPI $api) { - $this->api = $api; + public function setPatches(array $patches) { + assert_instances_of($patches, 'PhabricatorStoragePatch'); + $this->patches = $patches; return $this; } - final public function getAPI() { - return $this->api; + + public function execute(PhutilArgumentParser $args) { + $this->setDryRun($args->getArg('dryrun')); + $this->setForce($args->getArg('force')); } private function loadSchemata() { @@ -36,7 +62,20 @@ return array($comp, $expect, $actual); } - protected function adjustSchemata($force, $unsafe, $dry_run) { + final protected function adjustSchemata($unsafe) { + $lock = $this->lock(); + + try { + $this->doAdjustSchemata($unsafe); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } + + $lock->unlock(); + } + + final private function doAdjustSchemata($unsafe) { $console = PhutilConsole::getConsole(); $console->writeOut( @@ -54,7 +93,7 @@ return $this->printErrors($errors, 0); } - if (!$force && !$api->isCharacterSetAvailable('utf8mb4')) { + if (!$this->force && !$api->isCharacterSetAvailable('utf8mb4')) { $message = pht( "You have an old version of MySQL (older than 5.5) which does not ". "support the utf8mb4 character set. We strongly recomend upgrading to ". @@ -110,12 +149,12 @@ $table->draw(); - if ($dry_run) { + if ($this->dryRun) { $console->writeOut( "%s\n", pht('DRYRUN: Would apply adjustments.')); return 0; - } else if (!$force) { + } else if (!$this->force) { $console->writeOut( "\n%s\n", pht( @@ -665,6 +704,171 @@ return 2; } + final protected function upgradeSchemata( + $apply_only = null, + $no_quickstart = false, + $init_only = false) { + + $lock = $this->lock(); + + try { + $this->doUpgradeSchemata($apply_only, $no_quickstart, $init_only); + } catch (Exception $ex) { + $lock->unlock(); + throw $ex; + } + + $lock->unlock(); + } + + final private function doUpgradeSchemata( + $apply_only, + $no_quickstart, + $init_only) { + + $api = $this->getAPI(); + + $applied = $this->getApi()->getAppliedPatches(); + if ($applied === null) { + if ($this->dryRun) { + echo pht( + "DRYRUN: Patch metadata storage doesn't exist yet, ". + "it would be created.\n"); + return 0; + } + + if ($apply_only) { + throw new PhutilArgumentUsageException( + pht( + 'Storage has not been initialized yet, you must initialize '. + 'storage before selectively applying patches.')); + return 1; + } + + $legacy = $api->getLegacyPatches($this->patches); + if ($legacy || $no_quickstart || $init_only) { + + // If we have legacy patches, we can't quickstart. + + $api->createDatabase('meta_data'); + $api->createTable( + 'meta_data', + 'patch_status', + array( + 'patch VARCHAR(255) NOT NULL PRIMARY KEY COLLATE utf8_general_ci', + 'applied INT UNSIGNED NOT NULL', + )); + + foreach ($legacy as $patch) { + $api->markPatchApplied($patch); + } + } else { + echo pht('Loading quickstart template...')."\n"; + $root = dirname(phutil_get_library_root('phabricator')); + $sql = $root.'/resources/sql/quickstart.sql'; + $api->applyPatchSQL($sql); + } + } + + if ($init_only) { + echo pht('Storage initialized.')."\n"; + return 0; + } + + $applied = $api->getAppliedPatches(); + $applied = array_fuse($applied); + + $skip_mark = false; + if ($apply_only) { + if (isset($applied[$apply_only])) { + + unset($applied[$apply_only]); + $skip_mark = true; + + if (!$this->force && !$this->dryRun) { + echo phutil_console_wrap( + pht( + "Patch '%s' has already been applied. Are you sure you want ". + "to apply it again? This may put your storage in a state ". + "that the upgrade scripts can not automatically manage.", + $apply_only)); + if (!phutil_console_confirm(pht('Apply patch again?'))) { + echo pht('Cancelled.')."\n"; + return 1; + } + } + } + } + + while (true) { + $applied_something = false; + foreach ($this->patches as $key => $patch) { + if (isset($applied[$key])) { + unset($this->patches[$key]); + continue; + } + + if ($apply_only && $apply_only != $key) { + unset($this->patches[$key]); + continue; + } + + $can_apply = true; + foreach ($patch->getAfter() as $after) { + if (empty($applied[$after])) { + if ($apply_only) { + echo pht( + "Unable to apply patch '%s' because it depends ". + "on patch '%s', which has not been applied.\n", + $apply_only, + $after); + return 1; + } + $can_apply = false; + break; + } + } + + if (!$can_apply) { + continue; + } + + $applied_something = true; + + if ($this->dryRun) { + echo pht("DRYRUN: Would apply patch '%s'.", $key)."\n"; + } else { + echo pht("Applying patch '%s'...", $key)."\n"; + + $t_begin = microtime(true); + $api->applyPatch($patch); + $t_end = microtime(true); + + if (!$skip_mark) { + $api->markPatchApplied($key, ($t_end - $t_begin)); + } + } + + unset($this->patches[$key]); + $applied[$key] = true; + } + + if (!$applied_something) { + if (count($this->patches)) { + throw new Exception( + pht( + 'Some patches could not be applied: %s', + implode(', ', array_keys($this->patches)))); + } else if (!$this->dryRun && !$apply_only) { + echo pht( + "Storage is up to date. Use '%s' for details.", + 'storage status')."\n"; + } + break; + } + } + } + final protected function getBareHostAndPort($host) { // Split out port information, since the command-line client requires a // separate flag for the port. @@ -680,4 +884,15 @@ return array($bare_hostname, $port); } + /** + * Acquires a @{class:PhabricatorGlobalLock}. + * + * @return PhabricatorGlobalLock + */ + final protected function lock() { + return PhabricatorGlobalLock::newLock(__CLASS__) + ->useSpecificConnection($this->getApi()->getConn(null)) + ->lock(); + } + } diff --git a/src/infrastructure/util/PhabricatorGlobalLock.php b/src/infrastructure/util/PhabricatorGlobalLock.php --- a/src/infrastructure/util/PhabricatorGlobalLock.php +++ b/src/infrastructure/util/PhabricatorGlobalLock.php @@ -62,6 +62,11 @@ return $lock; } + public function useSpecificConnection(AphrontMySQLiDatabaseConnection $conn) { + $this->conn = $conn; + return $this; + } + /* -( Implementation )----------------------------------------------------- */ @@ -86,14 +91,14 @@ // NOTE: Using "force_new" to make sure each lock is on its own // connection. $conn = $dao->establishConnection('w', $force_new = true); - - // NOTE: Since MySQL will disconnect us if we're idle for too long, we set - // the wait_timeout to an enormous value, to allow us to hold the - // connection open indefinitely (or, at least, for 24 days). - $max_allowed_timeout = 2147483; - queryfx($conn, 'SET wait_timeout = %d', $max_allowed_timeout); } + // NOTE: Since MySQL will disconnect us if we're idle for too long, we set + // the wait_timeout to an enormous value, to allow us to hold the + // connection open indefinitely (or, at least, for 24 days). + $max_allowed_timeout = 2147483; + queryfx($conn, 'SET wait_timeout = %d', $max_allowed_timeout); + $result = queryfx_one( $conn, 'SELECT GET_LOCK(%s, %f)',