1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-22 14:52:40 +01:00

Port "arc upgrade" to Toolsets

Summary: Ref T13490. Allows "arc upgrade" to run through the new Toolsets infrastructure.

Test Plan: Ran "arc upgrade", although you can't upgrade feature branches so this can't be entirely tested until it hits "master".

Maniphest Tasks: T13490

Differential Revision: https://secure.phabricator.com/D21006
This commit is contained in:
epriestley 2020-02-17 09:32:30 -08:00
parent d4e4271b57
commit ee66b15bd4
7 changed files with 177 additions and 114 deletions

View file

@ -95,6 +95,7 @@ phutil_register_library_map(array(
'ArcanistClosureLinterTestCase' => 'lint/linter/__tests__/ArcanistClosureLinterTestCase.php', 'ArcanistClosureLinterTestCase' => 'lint/linter/__tests__/ArcanistClosureLinterTestCase.php',
'ArcanistCoffeeLintLinter' => 'lint/linter/ArcanistCoffeeLintLinter.php', 'ArcanistCoffeeLintLinter' => 'lint/linter/ArcanistCoffeeLintLinter.php',
'ArcanistCoffeeLintLinterTestCase' => 'lint/linter/__tests__/ArcanistCoffeeLintLinterTestCase.php', 'ArcanistCoffeeLintLinterTestCase' => 'lint/linter/__tests__/ArcanistCoffeeLintLinterTestCase.php',
'ArcanistCommand' => 'toolset/command/ArcanistCommand.php',
'ArcanistCommentRemover' => 'parser/ArcanistCommentRemover.php', 'ArcanistCommentRemover' => 'parser/ArcanistCommentRemover.php',
'ArcanistCommentRemoverTestCase' => 'parser/__tests__/ArcanistCommentRemoverTestCase.php', 'ArcanistCommentRemoverTestCase' => 'parser/__tests__/ArcanistCommentRemoverTestCase.php',
'ArcanistCommentSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCommentSpacingXHPASTLinterRule.php', 'ArcanistCommentSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCommentSpacingXHPASTLinterRule.php',
@ -1036,6 +1037,7 @@ phutil_register_library_map(array(
'ArcanistClosureLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistClosureLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistCoffeeLintLinter' => 'ArcanistExternalLinter', 'ArcanistCoffeeLintLinter' => 'ArcanistExternalLinter',
'ArcanistCoffeeLintLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistCoffeeLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistCommand' => 'Phobject',
'ArcanistCommentRemover' => 'Phobject', 'ArcanistCommentRemover' => 'Phobject',
'ArcanistCommentRemoverTestCase' => 'PhutilTestCase', 'ArcanistCommentRemoverTestCase' => 'PhutilTestCase',
'ArcanistCommentSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistCommentSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1399,7 +1401,7 @@ phutil_register_library_map(array(
'ArcanistUnnecessarySymbolAliasXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistUnnecessarySymbolAliasXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistUnsafeDynamicStringXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistUnsafeDynamicStringXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistUnsafeDynamicStringXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistUnsafeDynamicStringXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistUpgradeWorkflow' => 'ArcanistWorkflow', 'ArcanistUpgradeWorkflow' => 'ArcanistArcWorkflow',
'ArcanistUploadWorkflow' => 'ArcanistWorkflow', 'ArcanistUploadWorkflow' => 'ArcanistWorkflow',
'ArcanistUsageException' => 'Exception', 'ArcanistUsageException' => 'Exception',
'ArcanistUseStatementNamespacePrefixXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistUseStatementNamespacePrefixXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',

View file

@ -33,7 +33,6 @@ final class ExecFuture extends PhutilExecutableFuture {
private $stdoutPos = 0; private $stdoutPos = 0;
private $stderrPos = 0; private $stderrPos = 0;
private $command = null;
private $readBufferSize; private $readBufferSize;
private $stdoutSizeLimit = PHP_INT_MAX; private $stdoutSizeLimit = PHP_INT_MAX;
@ -55,42 +54,13 @@ final class ExecFuture extends PhutilExecutableFuture {
2 => array('pipe', 'w'), // stderr 2 => array('pipe', 'w'), // stderr
); );
protected function didConstruct() {
/* -( Creating ExecFutures )----------------------------------------------- */
/**
* Create a new ExecFuture.
*
* $future = new ExecFuture('wc -l %s', $file_path);
*
* @param string `sprintf()`-style command string which will be passed
* through @{function:csprintf} with the rest of the arguments.
* @param ... Zero or more additional arguments for @{function:csprintf}.
* @return ExecFuture ExecFuture for running the specified command.
* @task create
*/
public function __construct($command) {
$argv = func_get_args();
$this->command = call_user_func_array('csprintf', $argv);
$this->stdin = new PhutilRope(); $this->stdin = new PhutilRope();
} }
/* -( Command Information )------------------------------------------------ */ /* -( Command Information )------------------------------------------------ */
/**
* Retrieve the raw command to be executed.
*
* @return string Raw command.
* @task info
*/
public function getCommand() {
return $this->command;
}
/** /**
* Retrieve the byte limit for the stderr buffer. * Retrieve the byte limit for the stderr buffer.
* *
@ -349,7 +319,7 @@ final class ExecFuture extends PhutilExecutableFuture {
public function resolvex($timeout = null) { public function resolvex($timeout = null) {
list($err, $stdout, $stderr) = $this->resolve($timeout); list($err, $stdout, $stderr) = $this->resolve($timeout);
if ($err) { if ($err) {
$cmd = $this->command; $cmd = $this->getCommand();
if ($this->getWasKilledByTimeout()) { if ($this->getWasKilledByTimeout()) {
// NOTE: The timeout can be a float and PhutilNumber only handles // NOTE: The timeout can be a float and PhutilNumber only handles
@ -385,7 +355,7 @@ final class ExecFuture extends PhutilExecutableFuture {
public function resolveJSON($timeout = null) { public function resolveJSON($timeout = null) {
list($stdout, $stderr) = $this->resolvex($timeout); list($stdout, $stderr) = $this->resolvex($timeout);
if (strlen($stderr)) { if (strlen($stderr)) {
$cmd = $this->command; $cmd = $this->getCommand();
throw new CommandException( throw new CommandException(
pht( pht(
"JSON command '%s' emitted text to stderr when none was expected: %d", "JSON command '%s' emitted text to stderr when none was expected: %d",
@ -399,7 +369,7 @@ final class ExecFuture extends PhutilExecutableFuture {
try { try {
return phutil_json_decode($stdout); return phutil_json_decode($stdout);
} catch (PhutilJSONParserException $ex) { } catch (PhutilJSONParserException $ex) {
$cmd = $this->command; $cmd = $this->getCommand();
throw new CommandException( throw new CommandException(
pht( pht(
"JSON command '%s' did not produce a valid JSON object on stdout: %s", "JSON command '%s' did not produce a valid JSON object on stdout: %s",
@ -579,7 +549,7 @@ final class ExecFuture extends PhutilExecutableFuture {
$this->profilerCallID = $profiler->beginServiceCall( $this->profilerCallID = $profiler->beginServiceCall(
array( array(
'type' => 'exec', 'type' => 'exec',
'command' => (string)$this->command, 'command' => phutil_string_cast($this->getCommand()),
)); ));
} }
@ -588,10 +558,8 @@ final class ExecFuture extends PhutilExecutableFuture {
$this->start = microtime(true); $this->start = microtime(true);
} }
$unmasked_command = $this->command; $unmasked_command = $this->getCommand();
if ($unmasked_command instanceof PhutilCommandString) {
$unmasked_command = $unmasked_command->getUnmaskedString(); $unmasked_command = $unmasked_command->getUnmaskedString();
}
$pipes = array(); $pipes = array();
@ -674,7 +642,7 @@ final class ExecFuture extends PhutilExecutableFuture {
pht( pht(
'Call to "proc_open()" to open a subprocess failed: %s', 'Call to "proc_open()" to open a subprocess failed: %s',
$err), $err),
$this->command, $this->getCommand(),
1, 1,
'', '',
''); '');

View file

@ -20,30 +20,12 @@
*/ */
final class PhutilExecPassthru extends PhutilExecutableFuture { final class PhutilExecPassthru extends PhutilExecutableFuture {
private $command;
private $passthruResult; private $passthruResult;
/* -( Executing Passthru Commands )---------------------------------------- */ /* -( Executing Passthru Commands )---------------------------------------- */
/**
* Build a new passthru command.
*
* $exec = new PhutilExecPassthru('ls %s', $dir);
*
* @param string Command pattern. See @{function:csprintf}.
* @param ... Pattern arguments.
*
* @task command
*/
public function __construct($pattern /* , ... */) {
$args = func_get_args();
$this->command = call_user_func_array('csprintf', $args);
}
/** /**
* Execute this command. * Execute this command.
* *
@ -52,7 +34,7 @@ final class PhutilExecPassthru extends PhutilExecutableFuture {
* @task command * @task command
*/ */
public function execute() { public function execute() {
$command = $this->command; $command = $this->getCommand();
$profiler = PhutilServiceProfiler::getInstance(); $profiler = PhutilServiceProfiler::getInstance();
$call_id = $profiler->beginServiceCall( $call_id = $profiler->beginServiceCall(
@ -65,11 +47,7 @@ final class PhutilExecPassthru extends PhutilExecutableFuture {
$spec = array(STDIN, STDOUT, STDERR); $spec = array(STDIN, STDOUT, STDERR);
$pipes = array(); $pipes = array();
if ($command instanceof PhutilCommandString) {
$unmasked_command = $command->getUnmaskedString(); $unmasked_command = $command->getUnmaskedString();
} else {
$unmasked_command = $command;
}
if ($this->hasEnv()) { if ($this->hasEnv()) {
$env = $this->getEnv(); $env = $this->getEnv();

View file

@ -5,10 +5,38 @@
*/ */
abstract class PhutilExecutableFuture extends Future { abstract class PhutilExecutableFuture extends Future {
private $command;
private $env; private $env;
private $cwd; private $cwd;
final public function __construct($pattern /* , ... */) {
$args = func_get_args();
if ($pattern instanceof PhutilCommandString) {
if (count($args) !== 1) {
throw new Exception(
pht(
'Command (of class "%s") was constructed with a '.
'"PhutilCommandString", but also passed arguments. '.
'When using a preprebuilt command, you must not pass '.
'arguments.',
get_class($this)));
}
$this->command = $pattern;
} else {
$this->command = call_user_func_array('csprintf', $args);
}
$this->didConstruct();
}
protected function didConstruct() {
return;
}
final public function getCommand() {
return $this->command;
}
/** /**
* Set environmental variables for the command. * Set environmental variables for the command.

View file

@ -0,0 +1,59 @@
<?php
final class ArcanistCommand
extends Phobject {
private $logEngine;
private $executableFuture;
public function setExecutableFuture(PhutilExecutableFuture $future) {
$this->executableFuture = $future;
return $this;
}
public function getExecutableFuture() {
return $this->executableFuture;
}
public function setLogEngine(ArcanistLogEngine $log_engine) {
$this->logEngine = $log_engine;
return $this;
}
public function getLogEngine() {
return $this->logEngine;
}
public function execute() {
$log = $this->getLogEngine();
$future = $this->getExecutableFuture();
$command = $future->getCommand();
$log->writeNewline();
$log->writeStatus(
' $ ',
tsprintf('**%s**', phutil_string_cast($command)));
$log->writeNewline();
$err = $future->resolve();
$log->writeNewline();
if ($err) {
$log->writeError(
pht('ERROR'),
pht(
'Command exited with error code %d.',
$err));
throw new CommandException(
pht('Command exited with nonzero error code.'),
$command,
$err,
'',
'');
}
}
}

View file

@ -1,30 +1,31 @@
<?php <?php
/** final class ArcanistUpgradeWorkflow
* Upgrade arcanist itself. extends ArcanistArcWorkflow {
*/
final class ArcanistUpgradeWorkflow extends ArcanistWorkflow {
public function getWorkflowName() { public function getWorkflowName() {
return 'upgrade'; return 'upgrade';
} }
public function getCommandSynopses() { public function getWorkflowInformation() {
return phutil_console_format(<<<EOTEXT $help = pht(<<<EOTEXT
**upgrade** Upgrade Arcanist to the latest version.
EOTEXT EOTEXT
); );
return $this->newWorkflowInformation()
->setSynopsis(pht('Upgrade Arcanist to the latest version.'))
->addExample(pht('**upgrade**'))
->setHelp($help);
} }
public function getCommandHelp() { public function getWorkflowArguments() {
return phutil_console_format(<<<EOTEXT return array();
Supports: cli
Upgrade arcanist and libphutil to the latest versions.
EOTEXT
);
} }
public function run() { public function runWorkflow() {
$log = $this->getLogEngine();
$roots = array( $roots = array(
'arcanist' => dirname(phutil_get_library_root('arcanist')), 'arcanist' => dirname(phutil_get_library_root('arcanist')),
); );
@ -32,41 +33,46 @@ EOTEXT
$supported_branches = array( $supported_branches = array(
'master', 'master',
'stable', 'stable',
'experimental',
); );
$supported_branches = array_fuse($supported_branches); $supported_branches = array_fuse($supported_branches);
foreach ($roots as $lib => $root) { foreach ($roots as $library => $root) {
echo phutil_console_format( $log->writeStatus(
"%s\n", pht('PREPARING'),
pht('Upgrading %s...', $lib));
$working_copy = ArcanistWorkingCopyIdentity::newFromPath($root);
$configuration_manager = clone $this->getConfigurationManager();
$configuration_manager->setWorkingCopyIdentity($working_copy);
$repository = ArcanistRepositoryAPI::newAPIFromConfigurationManager(
$configuration_manager);
if (!Filesystem::pathExists($repository->getMetadataPath())) {
throw new ArcanistUsageException(
pht( pht(
"%s must be in its git working copy to be automatically upgraded. ". 'Preparing to upgrade "%s"...',
"This copy of %s (in '%s') is not in a git working copy.", $library));
$lib,
$lib, $is_git = false;
$root));
$working_copy = ArcanistWorkingCopy::newFromWorkingDirectory($root);
if ($working_copy) {
$repository_api = $working_copy->newRepositoryAPI();
if ($repository_api instanceof ArcanistGitAPI) {
$is_git = true;
}
} }
$this->setRepositoryAPI($repository); if (!$is_git) {
throw new PhutilArgumentUsageException(
pht(
'The "arc upgrade" workflow uses "git pull" to upgrade '.
'Arcanist, but the "arcanist/" directory (in "%s") is not a Git '.
'working copy. You must leave "arcanist/" as a Git '.
'working copy to use "arc upgrade".',
$root));
}
// NOTE: Don't use requireCleanWorkingCopy() here because it tries to // NOTE: Don't use requireCleanWorkingCopy() here because it tries to
// amend changes and generally move the workflow forward. We just want to // amend changes and generally move the workflow forward. We just want to
// abort if there are local changes and make the user sort things out. // abort if there are local changes and make the user sort things out.
$uncommitted = $repository->getUncommittedStatus(); $uncommitted = $repository_api->getUncommittedStatus();
if ($uncommitted) { if ($uncommitted) {
$message = pht( $message = pht(
'You have uncommitted changes in the working copy for this '. 'You have uncommitted changes in the working copy ("%s") for this '.
'library:'); 'library ("%s"):',
$root,
$library);
$list = id(new PhutilConsoleList()) $list = id(new PhutilConsoleList())
->setWrap(false) ->setWrap(false)
@ -75,29 +81,45 @@ EOTEXT
id(new PhutilConsoleBlock()) id(new PhutilConsoleBlock())
->addParagraph($message) ->addParagraph($message)
->addList($list) ->addList($list)
->addParagraph(
pht(
'Discard these changes before running "arc upgrade".'))
->draw(); ->draw();
throw new ArcanistUsageException( throw new PhutilArgumentUsageException(
pht('`arc upgrade` can only upgrade clean working copies.')); pht('"arc upgrade" can only upgrade clean working copies.'));
} }
$branch_name = $repository->getBranchName(); $branch_name = $repository_api->getBranchName();
if (!isset($supported_branches[$branch_name])) { if (!isset($supported_branches[$branch_name])) {
throw new ArcanistUsageException( throw new PhutilArgumentUsageException(
pht( pht(
'Library "%s" (in "%s") is on branch "%s", but this branch is '. 'Library "%s" (in "%s") is on branch "%s", but this branch is '.
'not supported for automatic upgrades. Supported branches are: '. 'not supported for automatic upgrades. Supported branches are: '.
'%s.', '%s.',
$lib, $library,
$root, $root,
$branch_name, $branch_name,
implode(', ', array_keys($supported_branches)))); implode(', ', array_keys($supported_branches))));
} }
chdir($root); $log->writeStatus(
pht('UPGRADING'),
pht(
'Upgrading "%s" (on branch "%s").',
$library,
$branch_name));
$command = csprintf(
'git pull --rebase origin -- %R',
$branch_name);
$future = (new PhutilExecPassthru($command))
->setCWD($root);
try { try {
execx('git pull --rebase'); $this->newCommand($future)
->execute();
} catch (Exception $ex) { } catch (Exception $ex) {
// If we failed, try to go back to the old state, then throw the // If we failed, try to go back to the old state, then throw the
// original exception. // original exception.
@ -106,10 +128,10 @@ EOTEXT
} }
} }
echo phutil_console_format( $log->writeSuccess(
"**%s** %s\n", pht('UPGRADED'),
pht('Updated!'), pht('Your copy of Arcanist is now up to date.'));
pht('Your copy of arc is now up to date.'));
return 0; return 0;
} }

View file

@ -2243,4 +2243,10 @@ abstract class ArcanistWorkflow extends Phobject {
return false; return false;
} }
final public function newCommand(PhutilExecutableFuture $future) {
return id(new ArcanistCommand())
->setLogEngine($this->getLogEngine())
->setExecutableFuture($future);
}
} }