Page MenuHomePhabricator

D19697.diff
No OneTemporary

D19697.diff

diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -49,9 +49,13 @@
'ArcanistAbstractMethodBodyXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistAbstractMethodBodyXHPASTLinterRuleTestCase.php',
'ArcanistAbstractPrivateMethodXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistAbstractPrivateMethodXHPASTLinterRule.php',
'ArcanistAbstractPrivateMethodXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistAbstractPrivateMethodXHPASTLinterRuleTestCase.php',
+ 'ArcanistAlias' => 'toolset/ArcanistAlias.php',
+ 'ArcanistAliasEffect' => 'toolset/ArcanistAliasEffect.php',
+ 'ArcanistAliasEngine' => 'toolset/ArcanistAliasEngine.php',
'ArcanistAliasFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistAliasFunctionXHPASTLinterRule.php',
'ArcanistAliasFunctionXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistAliasFunctionXHPASTLinterRuleTestCase.php',
'ArcanistAliasWorkflow' => 'toolset/workflow/ArcanistAliasWorkflow.php',
+ 'ArcanistAliasesConfigOption' => 'config/option/ArcanistAliasesConfigOption.php',
'ArcanistAmendWorkflow' => 'workflow/ArcanistAmendWorkflow.php',
'ArcanistAnoidWorkflow' => 'workflow/ArcanistAnoidWorkflow.php',
'ArcanistArcConfigurationEngineExtension' => 'config/arc/ArcanistArcConfigurationEngineExtension.php',
@@ -303,6 +307,7 @@
'ArcanistLintersWorkflow' => 'workflow/ArcanistLintersWorkflow.php',
'ArcanistListAssignmentXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistListAssignmentXHPASTLinterRule.php',
'ArcanistListAssignmentXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistListAssignmentXHPASTLinterRuleTestCase.php',
+ 'ArcanistListConfigOption' => 'config/option/ArcanistListConfigOption.php',
'ArcanistListWorkflow' => 'workflow/ArcanistListWorkflow.php',
'ArcanistLocalConfigurationSource' => 'config/source/ArcanistLocalConfigurationSource.php',
'ArcanistLogicalOperatorsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLogicalOperatorsXHPASTLinterRule.php',
@@ -491,6 +496,8 @@
'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php',
'ArcanistWildConfigOption' => 'config/option/ArcanistWildConfigOption.php',
'ArcanistWorkflow' => 'toolset/ArcanistWorkflow.php',
+ 'ArcanistWorkflowArgument' => 'toolset/ArcanistWorkflowArgument.php',
+ 'ArcanistWorkflowInformation' => 'toolset/ArcanistWorkflowInformation.php',
'ArcanistWorkingCopy' => 'workingcopy/ArcanistWorkingCopy.php',
'ArcanistWorkingCopyConfigurationSource' => 'config/source/ArcanistWorkingCopyConfigurationSource.php',
'ArcanistWorkingCopyStateRef' => 'ref/ArcanistWorkingCopyStateRef.php',
@@ -1145,9 +1152,13 @@
'ArcanistAbstractMethodBodyXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistAbstractPrivateMethodXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistAbstractPrivateMethodXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
+ 'ArcanistAlias' => 'Phobject',
+ 'ArcanistAliasEffect' => 'Phobject',
+ 'ArcanistAliasEngine' => 'Phobject',
'ArcanistAliasFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistAliasFunctionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistAliasWorkflow' => 'ArcanistWorkflow',
+ 'ArcanistAliasesConfigOption' => 'ArcanistListConfigOption',
'ArcanistAmendWorkflow' => 'ArcanistWorkflow',
'ArcanistAnoidWorkflow' => 'ArcanistWorkflow',
'ArcanistArcConfigurationEngineExtension' => 'ArcanistConfigurationEngineExtension',
@@ -1399,6 +1410,7 @@
'ArcanistLintersWorkflow' => 'ArcanistWorkflow',
'ArcanistListAssignmentXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistListAssignmentXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
+ 'ArcanistListConfigOption' => 'ArcanistConfigOption',
'ArcanistListWorkflow' => 'ArcanistWorkflow',
'ArcanistLocalConfigurationSource' => 'ArcanistWorkingCopyConfigurationSource',
'ArcanistLogicalOperatorsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@@ -1587,6 +1599,8 @@
'ArcanistWhichWorkflow' => 'ArcanistWorkflow',
'ArcanistWildConfigOption' => 'ArcanistConfigOption',
'ArcanistWorkflow' => 'Phobject',
+ 'ArcanistWorkflowArgument' => 'Phobject',
+ 'ArcanistWorkflowInformation' => 'Phobject',
'ArcanistWorkingCopy' => 'Phobject',
'ArcanistWorkingCopyConfigurationSource' => 'ArcanistFilesystemConfigurationSource',
'ArcanistWorkingCopyStateRef' => 'ArcanistRef',
diff --git a/src/config/ArcanistConfigurationSourceList.php b/src/config/ArcanistConfigurationSourceList.php
--- a/src/config/ArcanistConfigurationSourceList.php
+++ b/src/config/ArcanistConfigurationSourceList.php
@@ -15,16 +15,80 @@
return $this->sources;
}
+ private function getSourcesWithScopes($scopes) {
+ if ($scopes !== null) {
+ $scopes = array_fuse($scopes);
+ }
+
+ $results = array();
+ foreach ($this->getSources() as $source) {
+ if ($scopes !== null) {
+ $scope = $source->getConfigurationSourceScope();
+ if ($scope === null) {
+ continue;
+ }
+ if (!isset($scopes[$scope])) {
+ continue;
+ }
+ }
+
+ $results[] = $source;
+ }
+
+ return $results;
+ }
+
+ public function getWritableSourceFromScope($scope) {
+ $sources = $this->getSourcesWithScopes(array($scope));
+
+ $writable = array();
+ foreach ($sources as $source) {
+ if (!$source->isWritableConfigurationSource()) {
+ continue;
+ }
+
+ $writable[] = $source;
+ }
+
+ if (!$writable) {
+ throw new Exception(
+ pht(
+ 'Unable to write configuration: there is no writable configuration '.
+ 'source in the "%s" scope.',
+ $scope));
+ }
+
+ if (count($writable) > 1) {
+ throw new Exception(
+ pht(
+ 'Unable to write configuration: more than one writable source '.
+ 'exists in the "%s" scope.',
+ $scope));
+ }
+
+ return head($writable);
+ }
+
public function getConfig($key) {
$option = $this->getConfigOption($key);
$values = $this->getStorageValueList($key);
return $option->getValueFromStorageValueList($values);
}
+ public function getConfigFromScopes($key, array $scopes) {
+ $option = $this->getConfigOption($key);
+ $values = $this->getStorageValueListFromScopes($key, $scopes);
+ return $option->getValueFromStorageValueList($values);
+ }
+
public function getStorageValueList($key) {
+ return $this->getStorageValueListFromScopes($key, null);
+ }
+
+ private function getStorageValueListFromScopes($key, $scopes) {
$values = array();
- foreach ($this->getSources() as $source) {
+ foreach ($this->getSourcesWithScopes($scopes) as $source) {
if ($source->hasValueForKey($key)) {
$value = $source->getValueForKey($key);
$values[] = new ArcanistConfigurationSourceValue(
@@ -113,7 +177,13 @@
$source,
$raw_value);
} catch (Exception $ex) {
- throw $ex;
+ throw new PhutilProxyException(
+ pht(
+ 'Configuration value ("%s") defined in source "%s" is not '.
+ 'valid.',
+ $key,
+ $source->getSourceDisplayName()),
+ $ex);
}
}
}
diff --git a/src/config/arc/ArcanistArcConfigurationEngineExtension.php b/src/config/arc/ArcanistArcConfigurationEngineExtension.php
--- a/src/config/arc/ArcanistArcConfigurationEngineExtension.php
+++ b/src/config/arc/ArcanistArcConfigurationEngineExtension.php
@@ -5,6 +5,8 @@
const EXTENSIONKEY = 'arc';
+ const KEY_ALIASES = 'aliases';
+
public function newConfigurationOptions() {
// TOOLSETS: Restore "load", and maybe this other stuff.
@@ -49,12 +51,6 @@
'example' => 'false',
),
- 'aliases' => array(
- 'type' => 'aliases',
- 'help' => pht(
- 'Configured command aliases. Use "arc alias" to define aliases.'),
- ),
-
'history.immutable' => array(
'type' => 'bool',
'legacy' => 'immutable_history',
@@ -160,6 +156,14 @@
array(
'https://phabricator.mycompany.com/',
)),
+ id(new ArcanistAliasesConfigOption())
+ ->setKey(self::KEY_ALIASES)
+ ->setDefaultValue(array())
+ ->setSummary(pht('List of command aliases.'))
+ ->setHelp(
+ pht(
+ 'Configured command aliases. Use the "alias" workflow to define '.
+ 'aliases.')),
);
}
diff --git a/src/config/option/ArcanistAliasesConfigOption.php b/src/config/option/ArcanistAliasesConfigOption.php
new file mode 100644
--- /dev/null
+++ b/src/config/option/ArcanistAliasesConfigOption.php
@@ -0,0 +1,36 @@
+<?php
+
+final class ArcanistAliasesConfigOption
+ extends ArcanistListConfigOption {
+
+ public function getType() {
+ return 'list<alias>';
+ }
+
+ public function getValueFromStorageValue($value) {
+ if (!is_array($value)) {
+ throw new Exception(pht('Expected a list or dictionary!'));
+ }
+
+ $aliases = array();
+ foreach ($value as $key => $spec) {
+ $aliases[] = ArcanistAlias::newFromConfig($key, $spec);
+ }
+
+ return $aliases;
+ }
+
+ protected function didReadStorageValueList(array $list) {
+ assert_instances_of($list, 'ArcanistConfigurationSourceValue');
+ return mpull($list, 'getValue');
+ }
+
+ public function getDisplayValueFromValue($value) {
+ return pht('Use the "alias" workflow to review aliases.');
+ }
+
+ public function getStorageValueFromValue($value) {
+ return mpull($value, 'getStorageDictionary');
+ }
+
+}
diff --git a/src/config/option/ArcanistConfigOption.php b/src/config/option/ArcanistConfigOption.php
--- a/src/config/option/ArcanistConfigOption.php
+++ b/src/config/option/ArcanistConfigOption.php
@@ -67,9 +67,17 @@
abstract public function getType();
abstract public function getValueFromStorageValueList(array $list);
- abstract public function getStorageValueFromStringValue($value);
abstract public function getValueFromStorageValue($value);
abstract public function getDisplayValueFromValue($value);
+ abstract public function getStorageValueFromValue($value);
+
+ public function getStorageValueFromStringValue($value) {
+ throw new Exception(
+ pht(
+ 'This configuration option ("%s") does not support runtime definition '.
+ 'with "--config".',
+ $this->getKey()));
+ }
protected function getStorageValueFromSourceValue(
ArcanistConfigurationSourceValue $source_value) {
@@ -84,5 +92,9 @@
return $value;
}
+ public function writeValue(ArcanistConfigurationSource $source, $value) {
+ $value = $this->getStorageValueFromValue($value);
+ $source->setStorageValueForKey($this->getKey(), $value);
+ }
}
diff --git a/src/config/option/ArcanistListConfigOption.php b/src/config/option/ArcanistListConfigOption.php
new file mode 100644
--- /dev/null
+++ b/src/config/option/ArcanistListConfigOption.php
@@ -0,0 +1,32 @@
+<?php
+
+abstract class ArcanistListConfigOption
+ extends ArcanistConfigOption {
+
+ public function getValueFromStorageValueList(array $list) {
+ assert_instances_of($list, 'ArcanistConfigurationSourceValue');
+
+ $result_list = array();
+ foreach ($list as $source_value) {
+ $source = $source_value->getConfigurationSource();
+ $storage_value = $this->getStorageValueFromSourceValue($source_value);
+
+ $items = $this->getValueFromStorageValue($storage_value);
+ foreach ($items as $item) {
+ $result_list[] = new ArcanistConfigurationSourceValue(
+ $source,
+ $item);
+ }
+ }
+
+ $result_list = $this->didReadStorageValueList($result_list);
+
+ return $result_list;
+ }
+
+ protected function didReadStorageValueList(array $list) {
+ assert_instances_of($list, 'ArcanistConfigurationSourceValue');
+ return mpull($list, 'getValue');
+ }
+
+}
diff --git a/src/config/option/ArcanistStringConfigOption.php b/src/config/option/ArcanistStringConfigOption.php
--- a/src/config/option/ArcanistStringConfigOption.php
+++ b/src/config/option/ArcanistStringConfigOption.php
@@ -15,4 +15,8 @@
return $value;
}
+ public function getStorageValueFromValue($value) {
+ return $value;
+ }
+
}
diff --git a/src/config/source/ArcanistConfigurationSource.php b/src/config/source/ArcanistConfigurationSource.php
--- a/src/config/source/ArcanistConfigurationSource.php
+++ b/src/config/source/ArcanistConfigurationSource.php
@@ -3,15 +3,25 @@
abstract class ArcanistConfigurationSource
extends Phobject {
+ const SCOPE_USER = 'user';
+
abstract public function getSourceDisplayName();
abstract public function getAllKeys();
abstract public function hasValueForKey($key);
abstract public function getValueForKey($key);
+ public function getConfigurationSourceScope() {
+ return null;
+ }
+
public function isStringSource() {
return false;
}
+ public function isWritableConfigurationSource() {
+ return false;
+ }
+
public function didReadUnknownOption($key) {
// TOOLSETS: Standardize this kind of messaging? On ArcanistRuntime?
diff --git a/src/config/source/ArcanistDictionaryConfigurationSource.php b/src/config/source/ArcanistDictionaryConfigurationSource.php
--- a/src/config/source/ArcanistDictionaryConfigurationSource.php
+++ b/src/config/source/ArcanistDictionaryConfigurationSource.php
@@ -29,4 +29,16 @@
return $this->values[$key];
}
+ public function setStorageValueForKey($key, $value) {
+ $this->values[$key] = $value;
+
+ $this->writeToStorage($this->values);
+
+ return $this;
+ }
+
+ protected function writeToStorage($values) {
+ throw new PhutilMethodNotImplementedException();
+ }
+
}
\ No newline at end of file
diff --git a/src/config/source/ArcanistFilesystemConfigurationSource.php b/src/config/source/ArcanistFilesystemConfigurationSource.php
--- a/src/config/source/ArcanistFilesystemConfigurationSource.php
+++ b/src/config/source/ArcanistFilesystemConfigurationSource.php
@@ -35,4 +35,11 @@
return $values;
}
+ protected function writeToStorage($values) {
+ $content = id(new PhutilJSON())
+ ->encodeFormatted($values);
+
+ Filesystem::writeFile($this->path, $content);
+ }
+
}
\ No newline at end of file
diff --git a/src/config/source/ArcanistUserConfigurationSource.php b/src/config/source/ArcanistUserConfigurationSource.php
--- a/src/config/source/ArcanistUserConfigurationSource.php
+++ b/src/config/source/ArcanistUserConfigurationSource.php
@@ -7,6 +7,14 @@
return pht('User Config File');
}
+ public function isWritableConfigurationSource() {
+ return true;
+ }
+
+ public function getConfigurationSourceScope() {
+ return ArcanistConfigurationSource::SCOPE_USER;
+ }
+
public function didReadFilesystemValues(array $values) {
// Before toolsets, the "~/.arcrc" file had separate top-level keys for
// "config", "hosts", and "aliases". Transform this older file format into
diff --git a/src/toolset/ArcanistAlias.php b/src/toolset/ArcanistAlias.php
new file mode 100644
--- /dev/null
+++ b/src/toolset/ArcanistAlias.php
@@ -0,0 +1,145 @@
+<?php
+
+final class ArcanistAlias extends Phobject {
+
+ private $toolset;
+ private $trigger;
+ private $command;
+ private $exception;
+ private $configurationSource;
+
+ public static function newFromConfig($key, $value) {
+ $alias = new self();
+
+ // Parse older style aliases which were always for the "arc" toolset.
+ // When we next write these back into the config file, we'll update them
+ // to the modern format.
+
+ // The old format looked like this:
+ //
+ // {
+ // "draft": ["diff", "--draft"]
+ // }
+ //
+ // The new format looks like this:
+ //
+ // {
+ // [
+ // "toolset": "arc",
+ // "trigger": "draft",
+ // "command": ["diff", "--draft"]
+ // ]
+ // }
+ //
+ // For now, we parse the older format and fill in the toolset as "arc".
+
+ $is_list = false;
+ $is_dict = false;
+ if ($value && is_array($value)) {
+ if (array_keys($value) === range(0, count($value) - 1)) {
+ $is_list = true;
+ } else {
+ $is_dict = true;
+ }
+ }
+
+ if ($is_list) {
+ $alias->trigger = $key;
+ $alias->toolset = 'arc';
+ $alias->command = $value;
+ } else if ($is_dict) {
+ try {
+ PhutilTypeSpec::checkMap(
+ $value,
+ array(
+ 'trigger' => 'string',
+ 'toolset' => 'string',
+ 'command' => 'list<string>',
+ ));
+
+ $alias->trigger = idx($value, 'trigger');
+ $alias->toolset = idx($value, 'toolset');
+ $alias->command = idx($value, 'command');
+ } catch (PhutilTypeCheckException $ex) {
+ $alias->exception = new PhutilProxyException(
+ pht(
+ 'Found invalid alias definition (with key "%s").',
+ $key),
+ $ex);
+ }
+ } else {
+ $alias->exception = new Exception(
+ pht(
+ 'Expected alias definition (with key "%s") to be a dictionary.',
+ $key));
+ }
+
+ return $alias;
+ }
+
+ public function setToolset($toolset) {
+ $this->toolset = $toolset;
+ return $this;
+ }
+
+ public function getToolset() {
+ return $this->toolset;
+ }
+
+ public function setTrigger($trigger) {
+ $this->trigger = $trigger;
+ return $this;
+ }
+
+ public function getTrigger() {
+ return $this->trigger;
+ }
+
+ public function setCommand(array $command) {
+ $this->command = $command;
+ return $this;
+ }
+
+ public function getCommand() {
+ return $this->command;
+ }
+
+ public function setException(Exception $exception) {
+ $this->exception = $exception;
+ return $this;
+ }
+
+ public function getException() {
+ return $this->exception;
+ }
+
+ public function isShellCommandAlias() {
+ $command = $this->getCommand();
+ if (!$command) {
+ return false;
+ }
+
+ $head = head($command);
+ return preg_match('/^!/', $head);
+ }
+
+ public function getStorageDictionary() {
+ return array(
+ 'trigger' => $this->getTrigger(),
+ 'toolset' => $this->getToolset(),
+ 'command' => $this->getCommand(),
+ );
+ }
+
+ public function setConfigurationSource(
+ ArcanistConfigurationSource $configuration_source) {
+ $this->configurationSource = $configuration_source;
+ return $this;
+ }
+
+ public function getConfigurationSource() {
+ return $this->configurationSource;
+ }
+
+}
+
diff --git a/src/toolset/ArcanistAliasEffect.php b/src/toolset/ArcanistAliasEffect.php
new file mode 100644
--- /dev/null
+++ b/src/toolset/ArcanistAliasEffect.php
@@ -0,0 +1,57 @@
+<?php
+
+final class ArcanistAliasEffect
+ extends Phobject {
+
+ private $type;
+ private $command;
+ private $arguments;
+ private $message;
+
+ const EFFECT_MISCONFIGURATION = 'misconfiguration';
+ const EFFECT_SHELL = 'shell';
+ const EFFECT_RESOLUTION = 'resolution';
+ const EFFECT_SUGGEST = 'suggest';
+ const EFFECT_OVERRIDDE = 'override';
+ const EFFECT_ALIAS = 'alias';
+ const EFFECT_NOTFOUND = 'not-found';
+ const EFFECT_CYCLE = 'cycle';
+ const EFFECT_STACK = 'stack';
+
+ public function setType($type) {
+ $this->type = $type;
+ return $this;
+ }
+
+ public function getType() {
+ return $this->type;
+ }
+
+ public function setCommand($command) {
+ $this->command = $command;
+ return $this;
+ }
+
+ public function getCommand() {
+ return $this->command;
+ }
+
+ public function setArguments(array $arguments) {
+ $this->arguments = $arguments;
+ return $this;
+ }
+
+ public function getArguments() {
+ return $this->arguments;
+ }
+
+ public function setMessage($message) {
+ $this->message = $message;
+ return $this;
+ }
+
+ public function getMessage() {
+ return $this->message;
+ }
+
+}
\ No newline at end of file
diff --git a/src/toolset/ArcanistAliasEngine.php b/src/toolset/ArcanistAliasEngine.php
new file mode 100644
--- /dev/null
+++ b/src/toolset/ArcanistAliasEngine.php
@@ -0,0 +1,255 @@
+<?php
+
+final class ArcanistAliasEngine
+ extends Phobject {
+
+ private $runtime;
+ private $toolset;
+ private $workflows;
+ private $configurationSourceList;
+
+ public function setRuntime(ArcanistRuntime $runtime) {
+ $this->runtime = $runtime;
+ return $this;
+ }
+
+ public function getRuntime() {
+ return $this->runtime;
+ }
+
+ public function setToolset(ArcanistToolset $toolset) {
+ $this->toolset = $toolset;
+ return $this;
+ }
+
+ public function getToolset() {
+ return $this->toolset;
+ }
+
+ public function setWorkflows(array $workflows) {
+ assert_instances_of($workflows, 'ArcanistWorkflow');
+ $this->workflows = $workflows;
+ return $this;
+ }
+
+ public function getWorkflows() {
+ return $this->workflows;
+ }
+
+ public function setConfigurationSourceList(
+ ArcanistConfigurationSourceList $config) {
+ $this->configurationSourceList = $config;
+ return $this;
+ }
+
+ public function getConfigurationSourceList() {
+ return $this->configurationSourceList;
+ }
+
+ public function resolveAliases(array $argv) {
+ $aliases_key = ArcanistArcConfigurationEngineExtension::KEY_ALIASES;
+ $source_list = $this->getConfigurationSourceList();
+ $aliases = $source_list->getConfig($aliases_key);
+
+ $results = array();
+
+ // Identify aliases which had some kind of format or specification issue
+ // when loading config. We could possibly do this earlier, but it's nice
+ // to handle all the alias stuff in one place.
+
+ foreach ($aliases as $key => $alias) {
+ $exception = $alias->getException();
+
+ if (!$exception) {
+ continue;
+ }
+
+ // This alias is not defined properly, so we're going to ignore it.
+ unset($aliases[$key]);
+
+ $results[] = $this->newEffect(ArcanistAliasEffect::EFFECT_CONFIGURATION)
+ ->setMessage(
+ pht(
+ 'Configuration source ("%s") defines an invalid alias, which '.
+ 'will be ignored: %s',
+ $alias->getConfigurationSource()->getSourceDisplayName()),
+ $exception->getMessage());
+ }
+
+ $command = array_shift($argv);
+
+ $stack = array();
+ return $this->resolveAliasesForCommand(
+ $aliases,
+ $command,
+ $argv,
+ $results,
+ $stack);
+ }
+
+ private function resolveAliasesForCommand(
+ array $aliases,
+ $command,
+ array $argv,
+ array $results,
+ array $stack) {
+
+ $toolset = $this->getToolset();
+ $toolset_key = $toolset->getToolsetKey();
+
+ // If we have a command which resolves to a real workflow, match it and
+ // finish resolution. You can not overwrite a real workflow with an alias.
+
+ $workflows = $this->getWorkflows();
+ if (isset($workflows[$command])) {
+ $results[] = $this->newEffect(ArcanistAliasEffect::EFFECT_RESOLUTION)
+ ->setCommand($command)
+ ->setArguments($argv);
+ return $results;
+ }
+
+ // Find all the aliases which match whatever the user typed, like "draft".
+ // We look for aliases in other toolsets, too, so we can provide the user
+ // a hint when they type "phage draft" and mean "arc draft".
+
+ $matches = array();
+ $toolset_matches = array();
+ foreach ($aliases as $alias) {
+ if ($alias->getTrigger() === $command) {
+ $matches[] = $alias;
+ if ($alias->getToolset() == $toolset_key) {
+ $toolset_matches[] = $alias;
+ }
+ }
+ }
+
+ if (!$toolset_matches) {
+
+ // If the user typed "phage draft" and meant "arc draft", give them a
+ // hint that the alias exists somewhere else and they may have specified
+ // the wrong toolset.
+
+ foreach ($matches as $match) {
+ $results[] = $this->newEffect(ArcanistAliasEffect::EFFECT_SUGGEST)
+ ->setMessage(
+ pht(
+ 'No "%s %s" alias is defined, did you mean "%s %s"?',
+ $toolset_key,
+ $command,
+ $match->getToolset(),
+ $command));
+ }
+
+ // If the user misspells a command (like "arc hlep") and it doesn't match
+ // anything (no alias or workflow), we want to pass it through unmodified
+ // and let the parser try to correct the spelling into a real workflow
+ // later on.
+
+ // However, if the user correctly types a command (like "arc draft") that
+ // resolves at least once (so it hits a valid alias) but does not
+ // ultimately resolve into a valid workflow, we want to treat this as a
+ // hard failure.
+
+ // This could happen if you manually defined a bad alias, or a workflow
+ // you'd previously aliased to was removed, or you stacked aliases and
+ // then deleted one.
+
+ if ($stack) {
+ $results[] = $this->newEffect(ArcanistAliasEffect::EFFECT_NOTFOUND)
+ ->setMessage(
+ pht(
+ 'Alias resolved to "%s", but this is not a valid workflow or '.
+ 'alias name. This alias or workflow might have previously '.
+ 'existed and been removed.',
+ $command));
+ } else {
+ $results[] = $this->newEffect(ArcanistAliasEffect::EFFECT_RESOLUTION)
+ ->setCommand($command)
+ ->setArguments($argv);
+ }
+
+ return $results;
+ }
+
+ $alias = array_pop($toolset_matches);
+ foreach ($toolset_matches as $ignored_match) {
+ $results[] = $this->newEffect(ArcanistAliasEffect::EFFECT_IGNORED)
+ ->setMessage(
+ pht(
+ 'Multiple configuration sources define an alias for "%s %s". '.
+ 'The definition in "%s" will be ignored.',
+ $toolset_key,
+ $command,
+ $ignored_match->getConfigurationSource()->getSourceDisplayName()));
+ }
+
+ if ($alias->isShellCommandAlias()) {
+ $results[] = $this->newEffect(ArcanistAliasEffect::EFFECT_SHELL)
+ ->setMessage(
+ pht(
+ '%s %s -> $ %s',
+ $toolset_key,
+ $command,
+ $alias->getShellCommand()))
+ ->setCommand($command)
+ ->setArgv($argv);
+ return $results;
+ }
+
+ $alias_argv = $alias->getCommand();
+ $alias_command = array_shift($alias_argv);
+
+ if (isset($stack[$alias_command])) {
+
+ $cycle = array_keys($stack);
+ $cycle[] = $alias_command;
+ $cycle = implode(' -> ', $cycle);
+
+ $results[] = $this->newEffect(ArcanistAliasEffect::EFFECT_CYCLE)
+ ->setMessage(
+ pht(
+ 'Alias definitions form a cycle which can not be resolved: %s.',
+ $cycle));
+
+ return $results;
+ }
+
+ $stack[$alias_command] = true;
+
+ $stack_limit = 16;
+ if (count($stack) >= $stack_limit) {
+ $results[] = $this->newEffect(ArcanistAliasEffect::EFFECT_STACK)
+ ->setMessage(
+ pht(
+ 'Alias definitions form an unreasonably deep stack. A chain of '.
+ 'aliases may not resolve more than %s times.',
+ new PhutilNumber($stack_limit)));
+ return $results;
+ }
+
+ $results[] = $this->newEffect(ArcanistAliasEffect::EFFECT_ALIAS)
+ ->setMessage(
+ pht(
+ '%s %s -> %s %s',
+ $toolset_key,
+ $command,
+ $toolset_key,
+ $alias_command));
+
+ $argv = array_merge($alias_argv, $argv);
+
+ return $this->resolveAliasesForCommand(
+ $aliases,
+ $alias_command,
+ $argv,
+ $results,
+ $stack);
+ }
+
+ protected function newEffect($effect_type) {
+ return id(new ArcanistAliasEffect())
+ ->setType($effect_type);
+ }
+
+}
+
diff --git a/src/toolset/ArcanistWorkflow.php b/src/toolset/ArcanistWorkflow.php
--- a/src/toolset/ArcanistWorkflow.php
+++ b/src/toolset/ArcanistWorkflow.php
@@ -2,6 +2,7 @@
abstract class ArcanistWorkflow extends Phobject {
+ private $runtime;
private $toolset;
private $arguments;
private $configurationEngine;
@@ -37,10 +38,48 @@
return true;
}
+ protected function getWorkflowArguments() {
+ // TOOLSETS: Temporary!
+ return array();
+ }
+
+ protected function getWorkflowInformation() {
+ // TOOLSETS: Temporary!
+ return null;
+ }
+
+
public function newPhutilWorkflow() {
- return id(new ArcanistPhutilWorkflow())
+ $arguments = $this->getWorkflowArguments();
+ assert_instances_of($arguments, 'ArcanistWorkflowArgument');
+
+ $specs = mpull($arguments, 'getPhutilSpecification');
+
+ $phutil_workflow = id(new ArcanistPhutilWorkflow())
->setName($this->getWorkflowName())
- ->setWorkflow($this);
+ ->setWorkflow($this)
+ ->setArguments($specs);
+
+ $information = $this->getWorkflowInformation();
+ if ($information) {
+
+ $examples = $information->getExamples();
+ if ($examples) {
+ $examples = implode("\n", $examples);
+ $phutil_workflow->setExamples($examples);
+ }
+
+ $help = $information->getHelp();
+ if (strlen($help)) {
+ // Unwrap linebreaks in the help text so we don't get weird formatting.
+ $help = preg_replace("/(?<=\S)\n(?=\S)/", " ", $help);
+
+ $phutil_workflow->setHelp($help);
+ }
+
+ }
+
+ return $phutil_workflow;
}
final public function getToolset() {
@@ -52,6 +91,19 @@
return $this;
}
+ final public function setRuntime(ArcanistRuntime $runtime) {
+ $this->runtime = $runtime;
+ return $this;
+ }
+
+ final public function getRuntime() {
+ return $this->runtime;
+ }
+
+ final public function getConfig($key) {
+ return $this->getConfigurationSourceList()->getConfig($key);
+ }
+
final public function setConfigurationSourceList(
ArcanistConfigurationSourceList $config) {
$this->configurationSourceList = $config;
@@ -99,11 +151,17 @@
return $err;
}
- final public function getArgument($key, $default = null) {
- // TOOLSETS: This is a stub for now.
- return $default;
+ final public function getArgument($key) {
+ return $this->arguments->getArg($key);
+ }
+
+ final protected function newWorkflowArgument($key) {
+ return id(new ArcanistWorkflowArgument())
+ ->setKey($key);
+ }
- return $this->arguments->getArg($key, $default);
+ final protected function newWorkflowInformation() {
+ return new ArcanistWorkflowInformation();
}
}
diff --git a/src/toolset/ArcanistWorkflowArgument.php b/src/toolset/ArcanistWorkflowArgument.php
new file mode 100644
--- /dev/null
+++ b/src/toolset/ArcanistWorkflowArgument.php
@@ -0,0 +1,50 @@
+<?php
+
+final class ArcanistWorkflowArgument
+ extends Phobject {
+
+ private $key;
+ private $help;
+ private $wildcard;
+
+ public function setKey($key) {
+ $this->key = $key;
+ return $this;
+ }
+
+ public function getKey() {
+ return $this->key;
+ }
+
+ public function setWildcard($wildcard) {
+ $this->wildcard = $wildcard;
+ return $this;
+ }
+
+ public function getWildcard() {
+ return $this->wildcard;
+ }
+
+ public function getPhutilSpecification() {
+ $spec = array(
+ 'name' => $this->getKey(),
+ );
+
+ if ($this->getWildcard()) {
+ $spec['wildcard'] = true;
+ }
+
+ return $spec;
+ }
+
+ public function setHelp($help) {
+ $this->help = $help;
+ return $this;
+ }
+
+ public function getHelp() {
+ return $this->help;
+ }
+
+}
+
diff --git a/src/toolset/ArcanistWorkflowInformation.php b/src/toolset/ArcanistWorkflowInformation.php
new file mode 100644
--- /dev/null
+++ b/src/toolset/ArcanistWorkflowInformation.php
@@ -0,0 +1,28 @@
+<?php
+
+final class ArcanistWorkflowInformation
+ extends Phobject {
+
+ private $help;
+ private $examples = array();
+
+ public function setHelp($help) {
+ $this->help = $help;
+ return $this;
+ }
+
+ public function getHelp() {
+ return $this->help;
+ }
+
+ public function addExample($example) {
+ $this->examples[] = $example;
+ return $this;
+ }
+
+ public function getExamples() {
+ return $this->examples;
+ }
+
+}
+
diff --git a/src/toolset/workflow/ArcanistAliasWorkflow.php b/src/toolset/workflow/ArcanistAliasWorkflow.php
--- a/src/toolset/workflow/ArcanistAliasWorkflow.php
+++ b/src/toolset/workflow/ArcanistAliasWorkflow.php
@@ -13,232 +13,186 @@
return true;
}
- public function getWorkflowSynopses() {
- return array(
- pht('**alias**'),
- pht('**alias** __command__'),
- pht('**alias** __command__ __target__ -- [__options__]'),
- );
- }
-
- public function getWorkflowHelp() {
- return pht(<<<EOTEXT
-Supports: cli
+ public function getWorkflowInformation() {
+ $help = pht(<<<EOTEXT
Create an alias from __command__ to __target__ (optionally, with __options__).
-For example:
- %s alias fpatch patch -- --force
+Aliases allow you to create shorthands for commands and sets of flags you
+commonly use, like defining "arc draft" as a shorthand for "arc diff --draft".
+
+**Creating Aliases**
+
+You can define "arc draft" as a shorthand for "arc diff --draft" like this:
+
+ $ arc alias draft diff -- --draft
-...will create a new 'arc' command, 'arc fpatch', which invokes
-'arc patch --force ...' when run. NOTE: use "--" before specifying
-options!
+Now, when you run "arc draft", the command will function like
+"arc diff --draft".
-If you start an alias with "!", the remainder of the alias will be
-invoked as a shell command. For example, if you want to implement
-'arc ls', you can do so like this:
+<bg:yellow> NOTE: </bg> Make sure you use "--" before specifying any flags you
+want to pass to the command! Otherwise, the flags will be interpreted as flags
+to "arc alias".
- %s alias ls '!ls'
+**Listing Aliases**
-You can now run "arc ls" and it will behave like "ls". Of course, this
-example is silly and would make your life worse.
+Without any arguments, "arc alias" will list aliases.
-You can not overwrite builtins, including 'alias' itself. The builtin
-will always execute, even if it was added after your alias.
+**Removing Aliases**
To remove an alias, run:
- arc alias fpatch
+ $ arc alias <alias-name>
+
+You will be prompted to remove the alias.
+
+**Shell Commands**
+
+If you begin an alias with "!", the remainder of the alias will be invoked as
+a shell command. For example, if you want to implement "arc ls", you can do so
+like this:
+
+ $ arc alias ls '!ls'
+
+When run, "arc ls" will now behave like "ls".
+
+**Multiple Toolsets**
-Without any arguments, 'arc alias' will list aliases.
+This workflow supports any toolset, even though the examples in this help text
+use "arc". If you are working with another toolset, use the binary for that
+toolset define aliases for it:
+
+ $ phage alias ...
+
+Aliases are bound to the toolset which was used to define them. If you define
+an "arc draft" alias, that does not also define a "phage draft" alias.
+
+**Builtins**
+
+You can not overwrite the behavior of builtin workflows, including "alias"
+itself, and if you install a new workflow it will take precedence over any
+existing aliases with the same name.
EOTEXT
- ,
- $this->getToolsetName());
+);
+
+ return $this->newWorkflowInformation()
+ ->addExample(pht('**alias**'))
+ ->addExample(pht('**alias** __command__'))
+ ->addExample(pht('**alias** __command__ __target__ -- [__options__]'))
+ ->setHelp($help);
}
- public function getArguments() {
+ public function getWorkflowArguments() {
return array(
- '*' => 'argv',
+ $this->newWorkflowArgument('json')
+ ->setHelp(pht('Output aliases in JSON format.')),
+ $this->newWorkflowArgument('argv')
+ ->setWildcard(true),
);
}
- public static function getAliases(
- ArcanistConfigurationManager $configuration_manager) {
- $sources = $configuration_manager->getConfigFromAllSources('aliases');
+ public function runWorkflow() {
+ $argv = $this->getArgument('argv');
- $aliases = array();
- foreach ($sources as $source) {
- $aliases += $source;
- }
+ $is_list = false;
+ $is_delete = false;
- return $aliases;
- }
+ if (!$argv) {
+ $is_list = true;
+ } else if (count($argv) === 1) {
+ $is_delete = true;
+ }
- private function writeAliases(array $aliases) {
- $config = $this->getConfigurationManager()->readUserConfigurationFile();
- $config['aliases'] = $aliases;
- $this->getConfigurationManager()->writeUserConfigurationFile($config);
- }
+ $is_json = $this->getArgument('json');
+ if ($is_json && !$is_list) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'The "--json" argument may only be used when listing aliases.'));
+ }
- public function runWorkflow() {
- $aliases = self::getAliases($this->getConfigurationManager());
+ if ($is_list) {
+ return $this->runListAliases();
+ }
- $argv = $this->getArgument('argv');
- if (count($argv) == 0) {
- $this->printAliases($aliases);
- } else if (count($argv) == 1) {
- $this->removeAlias($aliases, $argv[0]);
- } else {
- $arc_config = $this->getArcanistConfiguration();
- $alias = $argv[0];
-
- if ($arc_config->buildWorkflow($alias)) {
- throw new ArcanistUsageException(
- pht(
- 'You can not create an alias for "%s" because it is a '.
- 'builtin command. "%s" can only create new commands.',
- "arc {$alias}",
- 'arc alias'));
- }
-
- $new_alias = array_slice($argv, 1);
-
- $command = implode(' ', $new_alias);
- if (self::isShellCommandAlias($command)) {
- echo tsprintf(
- "%s\n",
- pht(
- 'Aliased "%s" to shell command "%s".',
- "arc {$alias}",
- substr($command, 1)));
- } else {
- echo tsprintf(
- "%s\n",
- pht(
- 'Aliased "%s" to "%s".',
- "arc {$alias}",
- "arc {$command}"));
- }
-
- $aliases[$alias] = $new_alias;
- $this->writeAliases($aliases);
+ if ($is_delete) {
+ return $this->runDeleteAlias($argv[1]);
}
- return 0;
+ return $this->runCreateAlias($argv);
}
- public static function isShellCommandAlias($command) {
- return preg_match('/^!/', $command);
+ private function runListAliases() {
+ // TOOLSETS: Actually list aliases.
+ return 1;
}
- public static function resolveAliases(
- $command,
- ArcanistRuntime $config,
- array $argv,
- ArcanistConfigurationManager $configuration_manager) {
+ private function runDeleteAlias($alias) {
+ // TOOLSETS: Actually delete aliases.
+ return 1;
+ }
- $aliases = self::getAliases($configuration_manager);
- if (!isset($aliases[$command])) {
- return array(null, $argv);
- }
+ private function runCreateAlias(array $argv) {
+ $trigger = array_shift($argv);
+ $this->validateAliasTrigger($trigger);
- $new_command = head($aliases[$command]);
+ $alias = id(new ArcanistAlias())
+ ->setToolset($this->getToolsetKey())
+ ->setTrigger($trigger)
+ ->setCommand($argv);
- if (self::isShellCommandAlias($new_command)) {
- return array($new_command, $argv);
- }
+ $aliases = $this->readAliasesForWrite();
- $workflow = $config->buildWorkflow($new_command);
- if (!$workflow) {
- return array(null, $argv);
- }
+ // TOOLSETS: Check if the user already has an alias for this trigger, and
+ // prompt them to overwrite it. Needs prompting to work.
- $alias_argv = array_slice($aliases[$command], 1);
- foreach (array_reverse($alias_argv) as $alias_arg) {
- if (!in_array($alias_arg, $argv)) {
- array_unshift($argv, $alias_arg);
- }
- }
+ $aliases[] = $alias;
- return array($new_command, $argv);
+ $this->writeAliases($aliases);
+
+ return 0;
}
- private function printAliases(array $aliases) {
- if (!$aliases) {
- echo tsprintf(
- "%s\n",
- pht('You have not defined any aliases yet.'));
- return;
- }
+ private function validateAliasTrigger($trigger) {
+ $workflows = $this->getRuntime()->getWorkflows();
- $table = id(new PhutilConsoleTable())
- ->addColumn('input', array('title' => pht('Alias')))
- ->addColumn('command', array('title' => pht('Command')))
- ->addColumn('type', array('title' => pht('Type')));
-
- ksort($aliases);
-
- foreach ($aliases as $alias => $binding) {
- $command = implode(' ', $binding);
- if (self::isShellCommandAlias($command)) {
- $command = substr($command, 1);
- $type = pht('Shell Command');
- } else {
- $command = "arc {$command}";
- $type = pht('Arcanist Command');
- }
-
- $row = array(
- 'input' => "arc {$alias}",
- 'type' => $type,
- 'command' => $command,
- );
-
- $table->addRow($row);
+ if (isset($workflows[$trigger])) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'You can not define an alias for "%s" because it is a builtin '.
+ 'workflow for the current toolset ("%s"). The "alias" workflow '.
+ 'can only define new commands as aliases; it can not redefine '.
+ 'existing commands to mean something else.',
+ $trigger,
+ $this->getToolsetKey()));
}
+ }
- $table->draw();
+ private function getEditScope() {
+ return ArcanistConfigurationSource::SCOPE_USER;
}
- private function removeAlias(array $aliases, $alias) {
- if (empty($aliases[$alias])) {
- echo tsprintf(
- "%s\n",
- pht('No alias "%s" to remove.', $alias));
- return;
- }
+ private function getAliasesConfigKey() {
+ return ArcanistArcConfigurationEngineExtension::KEY_ALIASES;
+ }
- $command = implode(' ', $aliases[$alias]);
+ private function readAliasesForWrite() {
+ $key = $this->getAliasesConfigKey();
+ $scope = $this->getEditScope();
+ $source_list = $this->getConfigurationSourceList();
- if (self::isShellCommandAlias($command)) {
- echo tsprintf(
- "%s\n",
- pht(
- '"%s" is currently aliased to shell command "%s".',
- "arc {$alias}",
- substr($command, 1)));
- } else {
- echo tsprintf(
- "%s\n",
- pht(
- '"%s" is currently aliased to "%s".',
- "arc {$alias}",
- "arc {$command}"));
- }
+ return $source_list->getConfigFromScopes($key, array($scope));
+ }
+ private function writeAliases(array $aliases) {
+ assert_instances_of($aliases, 'ArcanistAlias');
- $ok = phutil_console_confirm(pht('Delete this alias?'));
- if (!$ok) {
- throw new ArcanistUserAbortException();
- }
+ $key = $this->getAliasesConfigKey();
+ $scope = $this->getEditScope();
- unset($aliases[$alias]);
- $this->writeAliases($aliases);
+ $source_list = $this->getConfigurationSourceList();
+ $source = $source_list->getWritableSourceFromScope($scope);
+ $option = $source_list->getConfigOption($key);
- echo tsprintf(
- "%s\n",
- pht(
- 'Removed alias "%s".',
- "arc {$alias}"));
+ $option->writeValue($source, $aliases);
}
}
diff --git a/src/workflow/ArcanistLiberateWorkflow.php b/src/workflow/ArcanistLiberateWorkflow.php
--- a/src/workflow/ArcanistLiberateWorkflow.php
+++ b/src/workflow/ArcanistLiberateWorkflow.php
@@ -1,70 +1,32 @@
<?php
-/**
- * Create and update libphutil libraries.
- *
- * This workflow is unusual and involves re-executing 'arc liberate' as a
- * subprocess with `--remap` and `--verify`. This is because there is no way
- * to unload or reload a library, so every process is stuck with the library
- * definition it had when it first loaded. This is normally fine, but
- * problematic in this case because `arc liberate` modifies library definitions.
- */
final class ArcanistLiberateWorkflow extends ArcanistWorkflow {
public function getWorkflowName() {
return 'liberate';
}
- public function getCommandSynopses() {
- return phutil_console_format(<<<EOTEXT
- **liberate** [__path__]
-EOTEXT
- );
- }
+ public function getWorkflowInformation() {
+ // TOOLSETS: Expand this help.
- public function getCommandHelp() {
- return phutil_console_format(<<<EOTEXT
- Supports: libphutil
- Create or update a libphutil library, generating required metadata
- files like \__init__.php.
+ $help = pht(<<<EOTEXT
+Create or update an Arcanist library.
EOTEXT
- );
+);
+
+ return $this->newWorkflowInformation()
+ ->addExample(pht('**liberate**'))
+ ->addExample(pht('**liberate** [__path__]'))
+ ->setHelp($help);
}
- public function getArguments() {
+ public function getWorkflowArguments() {
return array(
- 'all' => array(
- 'help' => pht(
- 'Drop the module cache before liberating. This will completely '.
- 'reanalyze the entire library. Thorough, but slow!'),
- ),
- 'force-update' => array(
- 'help' => pht(
- 'Force the library map to be updated, even in the presence of '.
- 'lint errors.'),
- ),
- 'library-name' => array(
- 'param' => 'name',
- 'help' =>
- pht('Use a flag for library name rather than awaiting user input.'),
- ),
- 'remap' => array(
- 'hide' => true,
- 'help' => pht(
- 'Internal. Run the remap step of liberation. You do not need to '.
- 'run this unless you are debugging the workflow.'),
- ),
- 'verify' => array(
- 'hide' => true,
- 'help' => pht(
- 'Internal. Run the verify step of liberation. You do not need to '.
- 'run this unless you are debugging the workflow.'),
- ),
- 'upgrade' => array(
- 'hide' => true,
- 'help' => pht('Experimental. Upgrade library to v2.'),
- ),
- '*' => 'argv',
+ $this->newWorkflowArgument('clean')
+ ->setHelp(
+ pht('Perform a clean rebuild, ignoring caches. Thorough, but slow.')),
+ $this->newWorkflowArgument('argv')
+ ->setWildcard(true),
);
}
@@ -97,9 +59,6 @@
);
}
- $is_remap = $this->getArgument('remap');
- $is_verify = $this->getArgument('verify');
-
foreach ($paths as $path) {
$this->liberatePath($path);
}
@@ -122,19 +81,10 @@
$version = $this->getLibraryFormatVersion($path);
switch ($version) {
case 1:
- if ($this->getArgument('upgrade')) {
- return $this->upgradeLibrary($path);
- }
throw new ArcanistUsageException(
pht(
- "This library is using libphutil v1, which is no ".
- "longer supported. Run '%s' to upgrade to v2.",
- 'arc liberate --upgrade'));
+ 'This very old library is no longer supported.'));
case 2:
- if ($this->getArgument('upgrade')) {
- throw new ArcanistUsageException(
- pht("Can't upgrade a v2 library!"));
- }
return $this->liberateVersion2($path);
default:
throw new ArcanistUsageException(
@@ -165,25 +115,10 @@
return phutil_passthru(
'php %s %C %s',
$bin,
- $this->getArgument('all') ? '--drop-cache' : '',
+ $this->getArgument('clean') ? '--drop-cache' : '',
$path);
}
- private function upgradeLibrary($path) {
- $inits = id(new FileFinder($path))
- ->withPath('*/__init__.php')
- ->withType('f')
- ->find();
-
- echo pht('Removing %s files...', '__init__.php')."\n";
- foreach ($inits as $init) {
- Filesystem::remove($path.'/'.$init);
- }
-
- echo pht('Upgrading library to v2...')."\n";
- $this->liberateVersion2($path);
- }
-
private function liberateCreateDirectory($path) {
if (Filesystem::pathExists($path)) {
if (!is_dir($path)) {
diff --git a/support/ArcanistRuntime.php b/support/ArcanistRuntime.php
--- a/support/ArcanistRuntime.php
+++ b/support/ArcanistRuntime.php
@@ -2,6 +2,8 @@
final class ArcanistRuntime {
+ private $workflows;
+
public function execute(array $argv) {
try {
@@ -81,12 +83,14 @@
$args->parsePartial($toolset->getToolsetArguments());
$workflows = $this->newWorkflows($toolset);
+ $this->workflows = $workflows;
$phutil_workflows = array();
foreach ($workflows as $key => $workflow) {
$phutil_workflows[$key] = $workflow->newPhutilWorkflow();
$workflow
+ ->setRuntime($this)
->setConfigurationEngine($config_engine)
->setConfigurationSourceList($config);
}
@@ -104,12 +108,16 @@
throw new PhutilArgumentUsageException(pht('Choose a workflow!'));
}
- $result = $this->resolveAliases($workflows, $unconsumed_argv, $config);
- if (is_int($result)) {
- return $result;
- }
+ $alias_effects = id(new ArcanistAliasEngine())
+ ->setRuntime($this)
+ ->setToolset($toolset)
+ ->setWorkflows($workflows)
+ ->setConfigurationSourceList($config)
+ ->resolveAliases($unconsumed_argv);
- $args->setUnconsumedArgumentVector($result);
+ $result_argv = $this->applyAliasEffects($alias_effects, $unconsumed_argv);
+
+ $args->setUnconsumedArgumentVector($result_argv);
return $args->parseWorkflows($phutil_workflows);
}
@@ -468,70 +476,45 @@
return $map;
}
- private function resolveAliases(
- array $workflows,
- array $argv,
- ArcanistConfigurationSourceList $config) {
-
- return $argv;
+ private function logTrace($label, $message) {
+ echo tsprintf(
+ "**<bg:magenta> %s </bg>** %s\n",
+ $label,
+ $message);
+ }
- $command = head($argv);
+ public function getWorkflows() {
+ return $this->workflows;
+ }
- // If this is a match for a recognized workflow, just return the arguments
- // unmodified. You aren't allowed to alias over real workflows.
- if (isset($workflows[$command])) {
- return $argv;
- }
+ private function applyAliasEffects(array $effects, array $argv) {
+ assert_instances_of($effects, 'ArcanistAliasEffect');
+
+ $command = null;
+ $arguments = null;
+ foreach ($effects as $effect) {
+ $message = $effect->getMessage();
+
+ if ($message !== null) {
+ fprintf(
+ STDERR,
+ tsprintf(
+ "**<bg:yellow> %s </bg>** %s\n",
+ pht('ALIAS'),
+ $message));
+ }
- $aliases = ArcanistAliasWorkflow::getAliases($config);
- list($new_command, $new_args) = ArcanistAliasWorkflow::resolveAliases(
- $command,
- $this,
- array_slice($argv, 1),
- $config);
-
- // You can't alias something to itself, so if the new command isn't new,
- // we're all done resolving aliases.
- if ($new_command === $command) {
- return $argv;
+ if ($effect->getCommand()) {
+ $command = $effect->getCommand();
+ $arguments = $effect->getArguments();
+ }
}
- $full_alias = idx($aliases, $command, array());
- $full_alias = implode(' ', $full_alias);
-
- // Run shell command aliases.
- if (ArcanistAliasWorkflow::isShellCommandAlias($new_command)) {
- fwrite(
- STDERR,
- tsprintf(
- '**<bg:green> %s </bg>** arc %s -> $ %s',
- pht('ALIAS'),
- $command,
- $shell_cmd));
-
- $shell_cmd = substr($full_alias, 1);
-
- return phutil_passthru('%C %Ls', $shell_cmd, $args);
+ if ($command !== null) {
+ $argv = array_merge(array($command), $arguments);
}
- fwrite(
- STDERR,
- tsprintf(
- '**<bg:green> %s </bg>** arc %s -> arc %s',
- pht('ALIAS'),
- $command,
- $new_command));
-
- $new_argv = array_merge(array($new_command), $new_args);
-
- return $this->resolveAliases($workflows, $new_argv, $config);
- }
-
- private function logTrace($label, $message) {
- echo tsprintf(
- "**<bg:magenta> %s </bg>** %s\n",
- $label,
- $message);
+ return $argv;
}
}

File Metadata

Mime Type
text/plain
Expires
Thu, Mar 20, 4:37 AM (3 d, 22 h ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/6i/a3/giydbznmlop6qzad
Default Alt Text
D19697.diff (51 KB)

Event Timeline