1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-21 22:32:41 +01:00

Collapse Arcanist toolsets from "wilds" into "master", as an overlay layer

Summary:
Depends on D20988. Ref T13395. Ref T13098. Ref T13203.

This brings all the "toolsets" code into "master". We try to run commands as toolsets commands first, then fall back to older code.

Since the "toolsets" class tree is mostly parallel to the older class tree, this isn't completely broken. Currently, all commands fall back.

Test Plan: Created this diff, ran various other commands. But this is probably a long shot from finished.

Maniphest Tasks: T13395, T13203, T13098

Differential Revision: https://secure.phabricator.com/D20990
This commit is contained in:
epriestley 2020-02-13 08:08:35 -08:00
parent acf0607683
commit c471983697
51 changed files with 3254 additions and 26 deletions

27
bin/arc
View file

@ -1,21 +1,10 @@
#!/usr/bin/env bash
#!/usr/bin/env php
<?php
# NOTE: This file is a wrapper script instead of a symlink so it will work in
# the Git Bash environment in Windows.
if (function_exists('pcntl_async_signals')) {
pcntl_async_signals(true);
} else {
declare(ticks = 1);
}
# Do bash magic to resolve the real location of this script through aliases,
# symlinks, etc.
SOURCE="${BASH_SOURCE[0]}";
while [ -h "$SOURCE" ]; do
LINK="$(readlink "$SOURCE")";
if [ "${LINK:0:1}" == "/" ]; then
# absolute symlink
SOURCE="$LINK"
else
# relative symlink
SOURCE="$(cd -P "$(dirname "$SOURCE")" && pwd)/$LINK"
fi
done;
DIR="$(cd -P "$(dirname "$SOURCE")" && pwd)"
exec "$DIR/../scripts/arcanist.php" "$@"
return require_once dirname(__DIR__).'/support/init/init-arcanist.php';

View file

@ -1,2 +1,2 @@
@echo off
php -f "%~dp0..\scripts\arcanist.php" -- %*
php -f "%~dp0..\bin\arc" -- %*

View file

@ -1 +0,0 @@
../scripts/phage.php

10
bin/phage Executable file
View file

@ -0,0 +1,10 @@
#!/usr/bin/env php
<?php
if (function_exists('pcntl_async_signals')) {
pcntl_async_signals(true);
} else {
declare(ticks = 1);
}
return require_once dirname(__DIR__).'/support/init/init-arcanist.php';

View file

@ -19,11 +19,17 @@ phutil_register_library_map(array(
'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' => 'workflow/ArcanistAliasWorkflow.php',
'ArcanistAliasesConfigOption' => 'config/option/ArcanistAliasesConfigOption.php',
'ArcanistAmendWorkflow' => 'workflow/ArcanistAmendWorkflow.php',
'ArcanistAnoidWorkflow' => 'workflow/ArcanistAnoidWorkflow.php',
'ArcanistArcConfigurationEngineExtension' => 'config/arc/ArcanistArcConfigurationEngineExtension.php',
'ArcanistArcToolset' => 'toolset/ArcanistArcToolset.php',
'ArcanistArrayCombineXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArrayCombineXHPASTLinterRule.php',
'ArcanistArrayCombineXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistArrayCombineXHPASTLinterRuleTestCase.php',
'ArcanistArrayIndexSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistArrayIndexSpacingXHPASTLinterRule.php',
@ -103,10 +109,17 @@ phutil_register_library_map(array(
'ArcanistConcatenationOperatorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistConcatenationOperatorXHPASTLinterRuleTestCase.php',
'ArcanistConduitCall' => 'conduit/ArcanistConduitCall.php',
'ArcanistConduitEngine' => 'conduit/ArcanistConduitEngine.php',
'ArcanistConduitException' => 'conduit/ArcanistConduitException.php',
'ArcanistConfigOption' => 'config/option/ArcanistConfigOption.php',
'ArcanistConfiguration' => 'configuration/ArcanistConfiguration.php',
'ArcanistConfigurationDrivenLintEngine' => 'lint/engine/ArcanistConfigurationDrivenLintEngine.php',
'ArcanistConfigurationDrivenUnitTestEngine' => 'unit/engine/ArcanistConfigurationDrivenUnitTestEngine.php',
'ArcanistConfigurationEngine' => 'config/ArcanistConfigurationEngine.php',
'ArcanistConfigurationEngineExtension' => 'config/ArcanistConfigurationEngineExtension.php',
'ArcanistConfigurationManager' => 'configuration/ArcanistConfigurationManager.php',
'ArcanistConfigurationSource' => 'config/source/ArcanistConfigurationSource.php',
'ArcanistConfigurationSourceList' => 'config/ArcanistConfigurationSourceList.php',
'ArcanistConfigurationSourceValue' => 'config/ArcanistConfigurationSourceValue.php',
'ArcanistConsoleLintRenderer' => 'lint/renderer/ArcanistConsoleLintRenderer.php',
'ArcanistConsoleLintRendererTestCase' => 'lint/renderer/__tests__/ArcanistConsoleLintRendererTestCase.php',
'ArcanistConstructorParenthesesXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistConstructorParenthesesXHPASTLinterRule.php',
@ -126,8 +139,10 @@ phutil_register_library_map(array(
'ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase.php',
'ArcanistDefaultParametersXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDefaultParametersXHPASTLinterRule.php',
'ArcanistDefaultParametersXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDefaultParametersXHPASTLinterRuleTestCase.php',
'ArcanistDefaultsConfigurationSource' => 'config/source/ArcanistDefaultsConfigurationSource.php',
'ArcanistDeprecationXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDeprecationXHPASTLinterRule.php',
'ArcanistDeprecationXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDeprecationXHPASTLinterRuleTestCase.php',
'ArcanistDictionaryConfigurationSource' => 'config/source/ArcanistDictionaryConfigurationSource.php',
'ArcanistDiffByteSizeException' => 'exception/ArcanistDiffByteSizeException.php',
'ArcanistDiffChange' => 'parser/diff/ArcanistDiffChange.php',
'ArcanistDiffChangeType' => 'parser/diff/ArcanistDiffChangeType.php',
@ -165,10 +180,12 @@ phutil_register_library_map(array(
'ArcanistExtractUseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistExtractUseXHPASTLinterRule.php',
'ArcanistExtractUseXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistExtractUseXHPASTLinterRuleTestCase.php',
'ArcanistFeatureWorkflow' => 'workflow/ArcanistFeatureWorkflow.php',
'ArcanistFileConfigurationSource' => 'config/source/ArcanistFileConfigurationSource.php',
'ArcanistFileDataRef' => 'upload/ArcanistFileDataRef.php',
'ArcanistFileUploader' => 'upload/ArcanistFileUploader.php',
'ArcanistFilenameLinter' => 'lint/linter/ArcanistFilenameLinter.php',
'ArcanistFilenameLinterTestCase' => 'lint/linter/__tests__/ArcanistFilenameLinterTestCase.php',
'ArcanistFilesystemConfigurationSource' => 'config/source/ArcanistFilesystemConfigurationSource.php',
'ArcanistFlagWorkflow' => 'workflow/ArcanistFlagWorkflow.php',
'ArcanistFlake8Linter' => 'lint/linter/ArcanistFlake8Linter.php',
'ArcanistFlake8LinterTestCase' => 'lint/linter/__tests__/ArcanistFlake8LinterTestCase.php',
@ -186,6 +203,7 @@ phutil_register_library_map(array(
'ArcanistGitLandEngine' => 'land/ArcanistGitLandEngine.php',
'ArcanistGitRevisionHardpointLoader' => 'loader/ArcanistGitRevisionHardpointLoader.php',
'ArcanistGitUpstreamPath' => 'repository/api/ArcanistGitUpstreamPath.php',
'ArcanistGitWorkingCopy' => 'workingcopy/ArcanistGitWorkingCopy.php',
'ArcanistGlobalVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistGlobalVariableXHPASTLinterRule.php',
'ArcanistGlobalVariableXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistGlobalVariableXHPASTLinterRuleTestCase.php',
'ArcanistGoLintLinter' => 'lint/linter/ArcanistGoLintLinter.php',
@ -248,7 +266,6 @@ phutil_register_library_map(array(
'ArcanistLesscLinter' => 'lint/linter/ArcanistLesscLinter.php',
'ArcanistLesscLinterTestCase' => 'lint/linter/__tests__/ArcanistLesscLinterTestCase.php',
'ArcanistLiberateWorkflow' => 'workflow/ArcanistLiberateWorkflow.php',
'ArcanistLibraryTestCase' => '__tests__/ArcanistLibraryTestCase.php',
'ArcanistLintEngine' => 'lint/engine/ArcanistLintEngine.php',
'ArcanistLintMessage' => 'lint/ArcanistLintMessage.php',
'ArcanistLintMessageTestCase' => 'lint/__tests__/ArcanistLintMessageTestCase.php',
@ -264,7 +281,11 @@ phutil_register_library_map(array(
'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',
'ArcanistLogEngine' => 'log/ArcanistLogEngine.php',
'ArcanistLogMessage' => 'log/ArcanistLogMessage.php',
'ArcanistLogicalOperatorsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLogicalOperatorsXHPASTLinterRule.php',
'ArcanistLogicalOperatorsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistLogicalOperatorsXHPASTLinterRuleTestCase.php',
'ArcanistLowercaseFunctionsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLowercaseFunctionsXHPASTLinterRule.php',
@ -274,6 +295,7 @@ phutil_register_library_map(array(
'ArcanistMercurialHardpointLoader' => 'loader/ArcanistMercurialHardpointLoader.php',
'ArcanistMercurialParser' => 'repository/parser/ArcanistMercurialParser.php',
'ArcanistMercurialParserTestCase' => 'repository/parser/__tests__/ArcanistMercurialParserTestCase.php',
'ArcanistMercurialWorkingCopy' => 'workingcopy/ArcanistMercurialWorkingCopy.php',
'ArcanistMercurialWorkingCopyCommitHardpointLoader' => 'loader/ArcanistMercurialWorkingCopyCommitHardpointLoader.php',
'ArcanistMergeConflictLinter' => 'lint/linter/ArcanistMergeConflictLinter.php',
'ArcanistMergeConflictLinterTestCase' => 'lint/linter/__tests__/ArcanistMergeConflictLinterTestCase.php',
@ -295,6 +317,7 @@ phutil_register_library_map(array(
'ArcanistNoLintLinterTestCase' => 'lint/linter/__tests__/ArcanistNoLintLinterTestCase.php',
'ArcanistNoParentScopeXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistNoParentScopeXHPASTLinterRule.php',
'ArcanistNoParentScopeXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistNoParentScopeXHPASTLinterRuleTestCase.php',
'ArcanistNoURIConduitException' => 'conduit/ArcanistNoURIConduitException.php',
'ArcanistNoneLintRenderer' => 'lint/renderer/ArcanistNoneLintRenderer.php',
'ArcanistObjectOperatorSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistObjectOperatorSpacingXHPASTLinterRule.php',
'ArcanistObjectOperatorSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistObjectOperatorSpacingXHPASTLinterRuleTestCase.php',
@ -320,6 +343,7 @@ phutil_register_library_map(array(
'ArcanistParseStrUseXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistParseStrUseXHPASTLinterRuleTestCase.php',
'ArcanistPasteWorkflow' => 'workflow/ArcanistPasteWorkflow.php',
'ArcanistPatchWorkflow' => 'workflow/ArcanistPatchWorkflow.php',
'ArcanistPhageToolset' => 'toolset/ArcanistPhageToolset.php',
'ArcanistPhpLinter' => 'lint/linter/ArcanistPhpLinter.php',
'ArcanistPhpLinterTestCase' => 'lint/linter/__tests__/ArcanistPhpLinterTestCase.php',
'ArcanistPhpcsLinter' => 'lint/linter/ArcanistPhpcsLinter.php',
@ -327,11 +351,14 @@ phutil_register_library_map(array(
'ArcanistPhpunitTestResultParser' => 'unit/parser/ArcanistPhpunitTestResultParser.php',
'ArcanistPhrequentWorkflow' => 'workflow/ArcanistPhrequentWorkflow.php',
'ArcanistPhutilLibraryLinter' => 'lint/linter/ArcanistPhutilLibraryLinter.php',
'ArcanistPhutilWorkflow' => 'toolset/ArcanistPhutilWorkflow.php',
'ArcanistPhutilXHPASTLinterStandard' => 'lint/linter/standards/phutil/ArcanistPhutilXHPASTLinterStandard.php',
'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPlusOperatorOnStringsXHPASTLinterRule.php',
'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase.php',
'ArcanistPregQuoteMisuseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPregQuoteMisuseXHPASTLinterRule.php',
'ArcanistPregQuoteMisuseXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPregQuoteMisuseXHPASTLinterRuleTestCase.php',
'ArcanistProjectConfigurationSource' => 'config/source/ArcanistProjectConfigurationSource.php',
'ArcanistPrompt' => 'toolset/ArcanistPrompt.php',
'ArcanistPublicPropertyXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPublicPropertyXHPASTLinterRule.php',
'ArcanistPublicPropertyXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPublicPropertyXHPASTLinterRuleTestCase.php',
'ArcanistPuppetLintLinter' => 'lint/linter/ArcanistPuppetLintLinter.php',
@ -361,6 +388,9 @@ phutil_register_library_map(array(
'ArcanistRuboCopLinterTestCase' => 'lint/linter/__tests__/ArcanistRuboCopLinterTestCase.php',
'ArcanistRubyLinter' => 'lint/linter/ArcanistRubyLinter.php',
'ArcanistRubyLinterTestCase' => 'lint/linter/__tests__/ArcanistRubyLinterTestCase.php',
'ArcanistRuntime' => 'runtime/ArcanistRuntime.php',
'ArcanistRuntimeConfigurationSource' => 'config/source/ArcanistRuntimeConfigurationSource.php',
'ArcanistScalarConfigOption' => 'config/option/ArcanistScalarConfigOption.php',
'ArcanistScriptAndRegexLinter' => 'lint/linter/ArcanistScriptAndRegexLinter.php',
'ArcanistSelfClassReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSelfClassReferenceXHPASTLinterRule.php',
'ArcanistSelfClassReferenceXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistSelfClassReferenceXHPASTLinterRuleTestCase.php',
@ -381,9 +411,12 @@ phutil_register_library_map(array(
'ArcanistStaticThisXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistStaticThisXHPASTLinterRule.php',
'ArcanistStaticThisXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistStaticThisXHPASTLinterRuleTestCase.php',
'ArcanistStopWorkflow' => 'workflow/ArcanistStopWorkflow.php',
'ArcanistStringConfigOption' => 'config/option/ArcanistStringConfigOption.php',
'ArcanistSubversionAPI' => 'repository/api/ArcanistSubversionAPI.php',
'ArcanistSubversionWorkingCopy' => 'workingcopy/ArcanistSubversionWorkingCopy.php',
'ArcanistSummaryLintRenderer' => 'lint/renderer/ArcanistSummaryLintRenderer.php',
'ArcanistSyntaxErrorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSyntaxErrorXHPASTLinterRule.php',
'ArcanistSystemConfigurationSource' => 'config/source/ArcanistSystemConfigurationSource.php',
'ArcanistTasksWorkflow' => 'workflow/ArcanistTasksWorkflow.php',
'ArcanistTautologicalExpressionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistTautologicalExpressionXHPASTLinterRule.php',
'ArcanistTautologicalExpressionXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistTautologicalExpressionXHPASTLinterRuleTestCase.php',
@ -399,6 +432,7 @@ phutil_register_library_map(array(
'ArcanistTodoCommentXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistTodoCommentXHPASTLinterRule.php',
'ArcanistTodoCommentXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistTodoCommentXHPASTLinterRuleTestCase.php',
'ArcanistTodoWorkflow' => 'workflow/ArcanistTodoWorkflow.php',
'ArcanistToolset' => 'toolset/ArcanistToolset.php',
'ArcanistUSEnglishTranslation' => 'internationalization/ArcanistUSEnglishTranslation.php',
'ArcanistUnableToParseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnableToParseXHPASTLinterRule.php',
'ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRule.php',
@ -431,6 +465,7 @@ phutil_register_library_map(array(
'ArcanistUselessOverridingMethodXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistUselessOverridingMethodXHPASTLinterRule.php',
'ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase.php',
'ArcanistUserAbortException' => 'exception/usage/ArcanistUserAbortException.php',
'ArcanistUserConfigurationSource' => 'config/source/ArcanistUserConfigurationSource.php',
'ArcanistVariableReferenceSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableReferenceSpacingXHPASTLinterRule.php',
'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase.php',
'ArcanistVariableVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php',
@ -438,8 +473,14 @@ phutil_register_library_map(array(
'ArcanistVersionWorkflow' => 'workflow/ArcanistVersionWorkflow.php',
'ArcanistWeldWorkflow' => 'workflow/ArcanistWeldWorkflow.php',
'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php',
'ArcanistWildConfigOption' => 'config/option/ArcanistWildConfigOption.php',
'ArcanistWorkflow' => 'workflow/ArcanistWorkflow.php',
'ArcanistWorkflowArgument' => 'toolset/ArcanistWorkflowArgument.php',
'ArcanistWorkflowInformation' => 'toolset/ArcanistWorkflowInformation.php',
'ArcanistWorkingCopy' => 'workingcopy/ArcanistWorkingCopy.php',
'ArcanistWorkingCopyConfigurationSource' => 'config/source/ArcanistWorkingCopyConfigurationSource.php',
'ArcanistWorkingCopyIdentity' => 'workingcopyidentity/ArcanistWorkingCopyIdentity.php',
'ArcanistWorkingCopyPath' => 'workingcopy/ArcanistWorkingCopyPath.php',
'ArcanistWorkingCopyStateRef' => 'ref/ArcanistWorkingCopyStateRef.php',
'ArcanistXHPASTLintNamingHook' => 'lint/linter/xhpast/ArcanistXHPASTLintNamingHook.php',
'ArcanistXHPASTLintNamingHookTestCase' => 'lint/linter/xhpast/__tests__/ArcanistXHPASTLintNamingHookTestCase.php',
@ -916,11 +957,17 @@ phutil_register_library_map(array(
'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',
'ArcanistArcToolset' => 'ArcanistToolset',
'ArcanistArrayCombineXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistArrayCombineXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistArrayIndexSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1000,10 +1047,17 @@ phutil_register_library_map(array(
'ArcanistConcatenationOperatorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistConduitCall' => 'Phobject',
'ArcanistConduitEngine' => 'Phobject',
'ArcanistConduitException' => 'Exception',
'ArcanistConfigOption' => 'Phobject',
'ArcanistConfiguration' => 'Phobject',
'ArcanistConfigurationDrivenLintEngine' => 'ArcanistLintEngine',
'ArcanistConfigurationDrivenUnitTestEngine' => 'ArcanistUnitTestEngine',
'ArcanistConfigurationEngine' => 'Phobject',
'ArcanistConfigurationEngineExtension' => 'Phobject',
'ArcanistConfigurationManager' => 'Phobject',
'ArcanistConfigurationSource' => 'Phobject',
'ArcanistConfigurationSourceList' => 'Phobject',
'ArcanistConfigurationSourceValue' => 'Phobject',
'ArcanistConsoleLintRenderer' => 'ArcanistLintRenderer',
'ArcanistConsoleLintRendererTestCase' => 'PhutilTestCase',
'ArcanistConstructorParenthesesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1023,8 +1077,10 @@ phutil_register_library_map(array(
'ArcanistDeclarationParenthesesXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistDefaultParametersXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistDefaultParametersXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistDefaultsConfigurationSource' => 'ArcanistDictionaryConfigurationSource',
'ArcanistDeprecationXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistDeprecationXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistDictionaryConfigurationSource' => 'ArcanistConfigurationSource',
'ArcanistDiffByteSizeException' => 'Exception',
'ArcanistDiffChange' => 'Phobject',
'ArcanistDiffChangeType' => 'Phobject',
@ -1062,10 +1118,12 @@ phutil_register_library_map(array(
'ArcanistExtractUseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistExtractUseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistFeatureWorkflow' => 'ArcanistWorkflow',
'ArcanistFileConfigurationSource' => 'ArcanistFilesystemConfigurationSource',
'ArcanistFileDataRef' => 'Phobject',
'ArcanistFileUploader' => 'Phobject',
'ArcanistFilenameLinter' => 'ArcanistLinter',
'ArcanistFilenameLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistFilesystemConfigurationSource' => 'ArcanistDictionaryConfigurationSource',
'ArcanistFlagWorkflow' => 'ArcanistWorkflow',
'ArcanistFlake8Linter' => 'ArcanistExternalLinter',
'ArcanistFlake8LinterTestCase' => 'ArcanistExternalLinterTestCase',
@ -1083,6 +1141,7 @@ phutil_register_library_map(array(
'ArcanistGitLandEngine' => 'ArcanistLandEngine',
'ArcanistGitRevisionHardpointLoader' => 'ArcanistGitHardpointLoader',
'ArcanistGitUpstreamPath' => 'Phobject',
'ArcanistGitWorkingCopy' => 'ArcanistWorkingCopy',
'ArcanistGlobalVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistGlobalVariableXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistGoLintLinter' => 'ArcanistExternalLinter',
@ -1145,7 +1204,6 @@ phutil_register_library_map(array(
'ArcanistLesscLinter' => 'ArcanistExternalLinter',
'ArcanistLesscLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistLiberateWorkflow' => 'ArcanistWorkflow',
'ArcanistLibraryTestCase' => 'PhutilLibraryTestCase',
'ArcanistLintEngine' => 'Phobject',
'ArcanistLintMessage' => 'Phobject',
'ArcanistLintMessageTestCase' => 'PhutilTestCase',
@ -1161,7 +1219,11 @@ phutil_register_library_map(array(
'ArcanistLintersWorkflow' => 'ArcanistWorkflow',
'ArcanistListAssignmentXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistListAssignmentXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistListConfigOption' => 'ArcanistConfigOption',
'ArcanistListWorkflow' => 'ArcanistWorkflow',
'ArcanistLocalConfigurationSource' => 'ArcanistWorkingCopyConfigurationSource',
'ArcanistLogEngine' => 'Phobject',
'ArcanistLogMessage' => 'Phobject',
'ArcanistLogicalOperatorsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistLogicalOperatorsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistLowercaseFunctionsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1171,6 +1233,7 @@ phutil_register_library_map(array(
'ArcanistMercurialHardpointLoader' => 'ArcanistHardpointLoader',
'ArcanistMercurialParser' => 'Phobject',
'ArcanistMercurialParserTestCase' => 'PhutilTestCase',
'ArcanistMercurialWorkingCopy' => 'ArcanistWorkingCopy',
'ArcanistMercurialWorkingCopyCommitHardpointLoader' => 'ArcanistMercurialHardpointLoader',
'ArcanistMergeConflictLinter' => 'ArcanistLinter',
'ArcanistMergeConflictLinterTestCase' => 'ArcanistLinterTestCase',
@ -1192,6 +1255,7 @@ phutil_register_library_map(array(
'ArcanistNoLintLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistNoParentScopeXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistNoParentScopeXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistNoURIConduitException' => 'ArcanistConduitException',
'ArcanistNoneLintRenderer' => 'ArcanistLintRenderer',
'ArcanistObjectOperatorSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistObjectOperatorSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
@ -1217,6 +1281,7 @@ phutil_register_library_map(array(
'ArcanistParseStrUseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistPasteWorkflow' => 'ArcanistWorkflow',
'ArcanistPatchWorkflow' => 'ArcanistWorkflow',
'ArcanistPhageToolset' => 'ArcanistToolset',
'ArcanistPhpLinter' => 'ArcanistExternalLinter',
'ArcanistPhpLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistPhpcsLinter' => 'ArcanistExternalLinter',
@ -1224,11 +1289,14 @@ phutil_register_library_map(array(
'ArcanistPhpunitTestResultParser' => 'ArcanistTestResultParser',
'ArcanistPhrequentWorkflow' => 'ArcanistWorkflow',
'ArcanistPhutilLibraryLinter' => 'ArcanistLinter',
'ArcanistPhutilWorkflow' => 'PhutilArgumentWorkflow',
'ArcanistPhutilXHPASTLinterStandard' => 'ArcanistLinterStandard',
'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistPregQuoteMisuseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistPregQuoteMisuseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistProjectConfigurationSource' => 'ArcanistWorkingCopyConfigurationSource',
'ArcanistPrompt' => 'Phobject',
'ArcanistPublicPropertyXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistPublicPropertyXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistPuppetLintLinter' => 'ArcanistExternalLinter',
@ -1258,6 +1326,8 @@ phutil_register_library_map(array(
'ArcanistRuboCopLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistRubyLinter' => 'ArcanistExternalLinter',
'ArcanistRubyLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistRuntimeConfigurationSource' => 'ArcanistDictionaryConfigurationSource',
'ArcanistScalarConfigOption' => 'ArcanistConfigOption',
'ArcanistScriptAndRegexLinter' => 'ArcanistLinter',
'ArcanistSelfClassReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistSelfClassReferenceXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
@ -1278,9 +1348,12 @@ phutil_register_library_map(array(
'ArcanistStaticThisXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistStaticThisXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistStopWorkflow' => 'ArcanistPhrequentWorkflow',
'ArcanistStringConfigOption' => 'ArcanistScalarConfigOption',
'ArcanistSubversionAPI' => 'ArcanistRepositoryAPI',
'ArcanistSubversionWorkingCopy' => 'ArcanistWorkingCopy',
'ArcanistSummaryLintRenderer' => 'ArcanistLintRenderer',
'ArcanistSyntaxErrorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistSystemConfigurationSource' => 'ArcanistFilesystemConfigurationSource',
'ArcanistTasksWorkflow' => 'ArcanistWorkflow',
'ArcanistTautologicalExpressionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistTautologicalExpressionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
@ -1296,6 +1369,7 @@ phutil_register_library_map(array(
'ArcanistTodoCommentXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistTodoCommentXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistTodoWorkflow' => 'ArcanistWorkflow',
'ArcanistToolset' => 'Phobject',
'ArcanistUSEnglishTranslation' => 'PhutilTranslation',
'ArcanistUnableToParseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistUnaryPostfixExpressionSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1328,6 +1402,7 @@ phutil_register_library_map(array(
'ArcanistUselessOverridingMethodXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistUserAbortException' => 'ArcanistUsageException',
'ArcanistUserConfigurationSource' => 'ArcanistFilesystemConfigurationSource',
'ArcanistVariableReferenceSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistVariableVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1335,8 +1410,14 @@ phutil_register_library_map(array(
'ArcanistVersionWorkflow' => 'ArcanistWorkflow',
'ArcanistWeldWorkflow' => 'ArcanistWorkflow',
'ArcanistWhichWorkflow' => 'ArcanistWorkflow',
'ArcanistWildConfigOption' => 'ArcanistConfigOption',
'ArcanistWorkflow' => 'Phobject',
'ArcanistWorkflowArgument' => 'Phobject',
'ArcanistWorkflowInformation' => 'Phobject',
'ArcanistWorkingCopy' => 'Phobject',
'ArcanistWorkingCopyConfigurationSource' => 'ArcanistFilesystemConfigurationSource',
'ArcanistWorkingCopyIdentity' => 'Phobject',
'ArcanistWorkingCopyPath' => 'Phobject',
'ArcanistWorkingCopyStateRef' => 'ArcanistRef',
'ArcanistXHPASTLintNamingHook' => 'Phobject',
'ArcanistXHPASTLintNamingHookTestCase' => 'PhutilTestCase',

View file

@ -1,3 +0,0 @@
<?php
final class ArcanistLibraryTestCase extends PhutilLibraryTestCase {}

View file

@ -0,0 +1,3 @@
<?php
abstract class ArcanistConduitException extends Exception {}

View file

@ -0,0 +1,4 @@
<?php
final class ArcanistNoURIConduitException
extends ArcanistConduitException {}

View file

@ -0,0 +1,231 @@
<?php
final class ArcanistConfigurationEngine
extends Phobject {
private $workingCopy;
private $arguments;
private $toolset;
public function setWorkingCopy(ArcanistWorkingCopy $working_copy) {
$this->workingCopy = $working_copy;
return $this;
}
public function getWorkingCopy() {
return $this->workingCopy;
}
public function setArguments(PhutilArgumentParser $arguments) {
$this->arguments = $arguments;
return $this;
}
public function getArguments() {
if (!$this->arguments) {
throw new PhutilInvalidStateException('setArguments');
}
return $this->arguments;
}
public function newConfigurationSourceList() {
$list = new ArcanistConfigurationSourceList();
$list->addSource(new ArcanistDefaultsConfigurationSource());
$arguments = $this->getArguments();
// If the invoker has provided one or more configuration files with
// "--config-file" arguments, read those files instead of the system
// and user configuration files. Otherwise, read the system and user
// configuration files.
$config_files = $arguments->getArg('config-file');
if ($config_files) {
foreach ($config_files as $config_file) {
$list->addSource(new ArcanistFileConfigurationSource($config_file));
}
} else {
$system_path = $this->getSystemConfigurationFilePath();
$list->addSource(new ArcanistSystemConfigurationSource($system_path));
$user_path = $this->getUserConfigurationFilePath();
$list->addSource(new ArcanistUserConfigurationSource($user_path));
}
// If we're running in a working copy, load the ".arcconfig" and any
// local configuration.
$working_copy = $this->getWorkingCopy();
if ($working_copy) {
$project_path = $working_copy->getProjectConfigurationFilePath();
if ($project_path !== null) {
$list->addSource(new ArcanistProjectConfigurationSource($project_path));
}
$local_path = $working_copy->getLocalConfigurationFilePath();
if ($local_path !== null) {
$list->addSource(new ArcanistLocalConfigurationSource($local_path));
}
}
// If the invoker has provided "--config" arguments, parse those now.
$runtime_args = $arguments->getArg('config');
if ($runtime_args) {
$list->addSource(new ArcanistRuntimeConfigurationSource($runtime_args));
}
return $list;
}
private function getSystemConfigurationFilePath() {
if (phutil_is_windows()) {
return Filesystem::resolvePath(
'Phabricator/Arcanist/config',
getenv('ProgramData'));
} else {
return '/etc/arcconfig';
}
}
private function getUserConfigurationFilePath() {
if (phutil_is_windows()) {
return getenv('APPDATA').'/.arcrc';
} else {
return getenv('HOME').'/.arcrc';
}
}
public function newDefaults() {
$map = $this->newConfigOptionsMap();
return mpull($map, 'getDefaultValue');
}
public function newConfigOptionsMap() {
$extensions = $this->newEngineExtensions();
$map = array();
$alias_map = array();
foreach ($extensions as $extension) {
$options = $extension->newConfigurationOptions();
foreach ($options as $option) {
$key = $option->getKey();
$this->validateConfigOptionKey($key, $extension);
if (isset($map[$key])) {
throw new Exception(
pht(
'Configuration option ("%s") defined by extension "%s" '.
'conflicts with an existing option. Each option must have '.
'a unique key.',
$key,
get_class($extension)));
}
if (isset($alias_map[$key])) {
throw new Exception(
pht(
'Configuration option ("%s") defined by extension "%s" '.
'conflicts with an alias for another option ("%s"). The '.
'key and aliases of each option must be unique.',
$key,
get_class($extension),
$alias_map[$key]->getKey()));
}
$map[$key] = $option;
foreach ($option->getAliases() as $alias) {
$this->validateConfigOptionKey($alias, $extension, $key);
if (isset($map[$alias])) {
throw new Exception(
pht(
'Configuration option ("%s") defined by extension "%s" '.
'has an alias ("%s") which conflicts with an existing '.
'option. The key and aliases of each option must be '.
'unique.',
$key,
get_class($extension),
$alias));
}
if (isset($alias_map[$alias])) {
throw new Exception(
pht(
'Configuration option ("%s") defined by extension "%s" '.
'has an alias ("%s") which conflicts with the alias of '.
'another configuration option ("%s"). The key and aliases '.
'of each option must be unique.',
$key,
get_class($extension),
$alias,
$alias_map[$alias]->getKey()));
}
$alias_map[$alias] = $option;
}
}
}
return $map;
}
private function validateConfigOptionKey(
$key,
ArcanistConfigurationEngineExtension $extension,
$is_alias_of = null) {
$reserved = array(
// The presence of this key is used to detect old "~/.arcrc" files, so
// configuration options may not use it.
'config',
);
$reserved = array_fuse($reserved);
if (isset($reserved[$key])) {
throw new Exception(
pht(
'Extension ("%s") defines invalid configuration with key "%s". '.
'This key is reserved.',
get_class($extension),
$key));
}
$is_ok = preg_match('(^[a-z][a-z0-9._-]{2,}\z)', $key);
if (!$is_ok) {
if ($is_alias_of === null) {
throw new Exception(
pht(
'Extension ("%s") defines invalid configuration with key "%s". '.
'Configuration keys: may only contain lowercase letters, '.
'numbers, hyphens, underscores, and periods; must start with a '.
'letter; and must be at least three characters long.',
get_class($extension),
$key));
} else {
throw new Exception(
pht(
'Extension ("%s") defines invalid alias ("%s") for configuration '.
'key ("%s"). Configuration keys and aliases: may only contain '.
'lowercase letters, numbers, hyphens, underscores, and periods; '.
'must start with a letter; and must be at least three characters '.
'long.',
get_class($extension),
$key,
$is_alias_of));
}
}
}
private function newEngineExtensions() {
return id(new PhutilClassMapQuery())
->setAncestorClass('ArcanistConfigurationEngineExtension')
->setUniqueMethod('getExtensionKey')
->setContinueOnFailure(true)
->execute();
}
}

View file

@ -0,0 +1,10 @@
<?php
abstract class ArcanistConfigurationEngineExtension
extends Phobject {
final public function getExtensionKey() {
return $this->getPhobjectClassConstant('EXTENSIONKEY');
}
}

View file

@ -0,0 +1,202 @@
<?php
final class ArcanistConfigurationSourceList
extends Phobject {
private $sources = array();
private $configOptions;
public function addSource(ArcanistConfigurationSource $source) {
$this->sources[] = $source;
return $this;
}
public function getSources() {
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->getSourcesWithScopes($scopes) as $source) {
if ($source->hasValueForKey($key)) {
$value = $source->getValueForKey($key);
$values[] = new ArcanistConfigurationSourceValue(
$source,
$source->getValueForKey($key));
}
}
return $values;
}
public function getConfigOption($key) {
$options = $this->getConfigOptions();
if (!isset($options[$key])) {
throw new Exception(
pht(
'Configuration option ("%s") is unrecognized. You can only read '.
'recognized configuration options.',
$key));
}
return $options[$key];
}
public function setConfigOptions(array $config_options) {
assert_instances_of($config_options, 'ArcanistConfigOption');
$config_options = mpull($config_options, null, 'getKey');
$this->configOptions = $config_options;
return $this;
}
public function getConfigOptions() {
if ($this->configOptions === null) {
throw new PhutilInvalidStateException('setConfigOptions');
}
return $this->configOptions;
}
public function validateConfiguration(ArcanistRuntime $runtime) {
$options = $this->getConfigOptions();
$aliases = array();
foreach ($options as $key => $option) {
foreach ($option->getAliases() as $alias) {
$aliases[$alias] = $key;
}
}
// TOOLSETS: Handle the case where config specifies both a value and an
// alias for that value. The alias should be ignored and we should emit
// a warning. This also needs to be implemented when actually reading
// configuration.
$value_lists = array();
foreach ($this->getSources() as $source) {
$keys = $source->getAllKeys();
foreach ($keys as $key) {
$resolved_key = idx($aliases, $key, $key);
$option = idx($options, $resolved_key);
// If there's no option object for this config, this value is
// unrecognized. Sources are free to handle this however they want:
// for config files we emit a warning; for "--config" we fatal.
if (!$option) {
$source->didReadUnknownOption($runtime, $key);
continue;
}
$raw_value = $source->getValueForKey($key);
// Make sure we can convert whatever value the configuration source is
// providing into a legitimate runtime value.
try {
$value = $raw_value;
if ($source->isStringSource()) {
$value = $option->getStorageValueFromStringValue($value);
}
$option->getValueFromStorageValue($value);
$value_lists[$resolved_key][] = new ArcanistConfigurationSourceValue(
$source,
$raw_value);
} catch (Exception $ex) {
throw new PhutilProxyException(
pht(
'Configuration value ("%s") defined in source "%s" is not '.
'valid.',
$key,
$source->getSourceDisplayName()),
$ex);
}
}
}
// Make sure each value list can be merged.
foreach ($value_lists as $key => $value_list) {
try {
$options[$key]->getValueFromStorageValueList($value_list);
} catch (Exception $ex) {
throw $ex;
}
}
}
}

View file

@ -0,0 +1,22 @@
<?php
final class ArcanistConfigurationSourceValue
extends Phobject {
private $source;
private $value;
public function __construct(ArcanistConfigurationSource $source, $value) {
$this->source = $source;
$this->value = $value;
}
public function getConfigurationSource() {
return $this->source;
}
public function getValue() {
return $this->value;
}
}

View file

@ -0,0 +1,159 @@
<?php
final class ArcanistArcConfigurationEngineExtension
extends ArcanistConfigurationEngineExtension {
const EXTENSIONKEY = 'arc';
const KEY_ALIASES = 'aliases';
public function newConfigurationOptions() {
// TOOLSETS: Restore "load", and maybe this other stuff.
/*
'load' => array(
'type' => 'list',
'legacy' => 'phutil_libraries',
'help' => pht(
'A list of paths to phutil libraries that should be loaded at '.
'startup. This can be used to make classes available, like lint '.
'or unit test engines.'),
'default' => array(),
'example' => '["/var/arc/customlib/src"]',
),
'arc.feature.start.default' => array(
'type' => 'string',
'help' => pht(
'The name of the default branch to create the new feature branch '.
'off of.'),
'example' => '"develop"',
),
'arc.land.onto.default' => array(
'type' => 'string',
'help' => pht(
'The name of the default branch to land changes onto when '.
'`%s` is run.',
'arc land'),
'example' => '"develop"',
),
'arc.autostash' => array(
'type' => 'bool',
'help' => pht(
'Whether %s should permit the automatic stashing of changes in the '.
'working directory when requiring a clean working copy. This option '.
'should only be used when users understand how to restore their '.
'working directory from the local stash if an Arcanist operation '.
'causes an unrecoverable error.',
'arc'),
'default' => false,
'example' => 'false',
),
'history.immutable' => array(
'type' => 'bool',
'legacy' => 'immutable_history',
'help' => pht(
'If true, %s will never change repository history (e.g., through '.
'amending or rebasing). Defaults to true in Mercurial and false in '.
'Git. This setting has no effect in Subversion.',
'arc'),
'example' => 'false',
),
'editor' => array(
'type' => 'string',
'help' => pht(
'Command to use to invoke an interactive editor, like `%s` or `%s`. '.
'This setting overrides the %s environmental variable.',
'nano',
'vim',
'EDITOR'),
'example' => '"nano"',
),
'https.cabundle' => array(
'type' => 'string',
'help' => pht(
"Path to a custom CA bundle file to be used for arcanist's cURL ".
"calls. This is used primarily when your conduit endpoint is ".
"behind HTTPS signed by your organization's internal CA."),
'example' => 'support/yourca.pem',
),
'https.blindly-trust-domains' => array(
'type' => 'list',
'help' => pht(
'List of domains to blindly trust SSL certificates for. '.
'Disables peer verification.'),
'default' => array(),
'example' => '["secure.mycompany.com"]',
),
'browser' => array(
'type' => 'string',
'help' => pht('Command to use to invoke a web browser.'),
'example' => '"gnome-www-browser"',
),
*/
return array(
id(new ArcanistStringConfigOption())
->setKey('base')
->setSummary(pht('Ruleset for selecting commit ranges.'))
->setHelp(
pht(
'Base commit ruleset to invoke when determining the start of a '.
'commit range. See "Arcanist User Guide: Commit Ranges" for '.
'details.'))
->setExamples(
array(
'arc:amended, arc:prompt',
)),
id(new ArcanistStringConfigOption())
->setKey('repository')
->setAliases(
array(
'repository.callsign',
))
->setSummary(pht('Repository for the current working copy.'))
->setHelp(
pht(
'Associate the working copy with a specific Phabricator '.
'repository. Normally, `arc` can figure this association out on '.
'its own, but if your setup is unusual you can use this option '.
'to tell it what the desired value is.'))
->setExamples(
array(
'libexample',
'XYZ',
'R123',
'123',
)),
id(new ArcanistStringConfigOption())
->setKey('phabricator.uri')
->setAliases(
array(
'conduit_uri',
'default',
))
->setSummary(pht('Phabricator install to connect to.'))
->setHelp(
pht(
'Associates this working copy with a specific installation of '.
'Phabricator.'))
->setExamples(
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.')),
);
}
}

View file

@ -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');
}
}

View file

@ -0,0 +1,100 @@
<?php
abstract class ArcanistConfigOption
extends Phobject {
private $key;
private $help;
private $summary;
private $aliases = array();
private $examples = array();
private $defaultValue;
public function setKey($key) {
$this->key = $key;
return $this;
}
public function getKey() {
return $this->key;
}
public function setAliases($aliases) {
$this->aliases = $aliases;
return $this;
}
public function getAliases() {
return $this->aliases;
}
public function setSummary($summary) {
$this->summary = $summary;
return $this;
}
public function getSummary() {
return $this->summary;
}
public function setHelp($help) {
$this->help = $help;
return $this;
}
public function getHelp() {
return $this->help;
}
public function setExamples(array $examples) {
$this->examples = $examples;
return $this;
}
public function getExamples() {
return $this->examples;
}
public function setDefaultValue($default_value) {
$this->defaultValue = $default_value;
return $this;
}
public function getDefaultValue() {
return $this->defaultValue;
}
abstract public function getType();
abstract public function getValueFromStorageValueList(array $list);
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) {
$value = $source_value->getValue();
$source = $source_value->getConfigurationSource();
if ($source->isStringSource()) {
$value = $this->getStorageValueFromStringValue($value);
}
return $value;
}
public function writeValue(ArcanistConfigurationSource $source, $value) {
$value = $this->getStorageValueFromValue($value);
$source->setStorageValueForKey($this->getKey(), $value);
}
}

View file

@ -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');
}
}

View file

@ -0,0 +1,19 @@
<?php
abstract class ArcanistScalarConfigOption
extends ArcanistConfigOption {
public function getValueFromStorageValueList(array $list) {
assert_instances_of($list, 'ArcanistConfigurationSourceValue');
$source_value = last($list);
$storage_value = $this->getStorageValueFromSourceValue($source_value);
return $this->getValueFromStorageValue($storage_value);
}
public function getValueFromStorageValue($value) {
return $value;
}
}

View file

@ -0,0 +1,22 @@
<?php
final class ArcanistStringConfigOption
extends ArcanistScalarConfigOption {
public function getType() {
return 'string';
}
public function getStorageValueFromStringValue($value) {
return (string)$value;
}
public function getDisplayValueFromValue($value) {
return $value;
}
public function getStorageValueFromValue($value) {
return $value;
}
}

View file

@ -0,0 +1,39 @@
<?php
/**
* This option type makes it easier to manage unknown options with unknown
* types.
*/
final class ArcanistWildConfigOption
extends ArcanistConfigOption {
public function getType() {
return 'wild';
}
public function getStorageValueFromStringValue($value) {
return (string)$value;
}
public function getDisplayValueFromValue($value) {
return json_encode($value);
}
public function getValueFromStorageValueList(array $list) {
assert_instances_of($list, 'ArcanistConfigurationSourceValue');
$source_value = last($list);
$storage_value = $this->getStorageValueFromSourceValue($source_value);
return $this->getValueFromStorageValue($storage_value);
}
public function getValueFromStorageValue($value) {
return $value;
}
public function getStorageValueFromValue($value) {
return $value;
}
}

View file

@ -0,0 +1,39 @@
<?php
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(ArcanistRuntime $runtime, $key) {
// TOOLSETS: Restore this warning once the new "arc" flow is in better
// shape.
return;
$runtime->getLogEngine()->writeWarning(
pht('UNKNOWN CONFIGURATION'),
pht(
'Ignoring unrecognized configuration option ("%s") from source: %s.',
$key,
$this->getSourceDisplayName()));
}
}

View file

@ -0,0 +1,17 @@
<?php
final class ArcanistDefaultsConfigurationSource
extends ArcanistDictionaryConfigurationSource {
public function getSourceDisplayName() {
return pht('Builtin Defaults');
}
public function __construct() {
$values = id(new ArcanistConfigurationEngine())
->newDefaults();
parent::__construct($values);
}
}

View file

@ -0,0 +1,44 @@
<?php
abstract class ArcanistDictionaryConfigurationSource
extends ArcanistConfigurationSource {
private $values;
public function __construct(array $dictionary) {
$this->values = $dictionary;
}
public function getAllKeys() {
return array_keys($this->values);
}
public function hasValueForKey($key) {
return array_key_exists($key, $this->values);
}
public function getValueForKey($key) {
if (!$this->hasValueForKey($key)) {
throw new Exception(
pht(
'Configuration source ("%s") has no value for key ("%s").',
get_class($this),
$key));
}
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();
}
}

View file

@ -0,0 +1,10 @@
<?php
final class ArcanistFileConfigurationSource
extends ArcanistFilesystemConfigurationSource {
public function getFileKindDisplayName() {
return pht('Config File');
}
}

View file

@ -0,0 +1,45 @@
<?php
abstract class ArcanistFilesystemConfigurationSource
extends ArcanistDictionaryConfigurationSource {
private $path;
public function __construct($path) {
$this->path = $path;
$values = array();
if (Filesystem::pathExists($path)) {
$contents = Filesystem::readFile($path);
if (strlen(trim($contents))) {
$values = phutil_json_decode($contents);
}
}
$values = $this->didReadFilesystemValues($values);
parent::__construct($values);
}
public function getPath() {
return $this->path;
}
public function getSourceDisplayName() {
return pht('%s (%s)', $this->getFileKindDisplayName(), $this->getPath());
}
abstract public function getFileKindDisplayName();
protected function didReadFilesystemValues(array $values) {
return $values;
}
protected function writeToStorage($values) {
$content = id(new PhutilJSON())
->encodeFormatted($values);
Filesystem::writeFile($this->path, $content);
}
}

View file

@ -0,0 +1,10 @@
<?php
final class ArcanistLocalConfigurationSource
extends ArcanistWorkingCopyConfigurationSource {
public function getFileKindDisplayName() {
return pht('Local Config File');
}
}

View file

@ -0,0 +1,10 @@
<?php
final class ArcanistProjectConfigurationSource
extends ArcanistWorkingCopyConfigurationSource {
public function getFileKindDisplayName() {
return pht('Project Config File');
}
}

View file

@ -0,0 +1,49 @@
<?php
final class ArcanistRuntimeConfigurationSource
extends ArcanistDictionaryConfigurationSource {
public function __construct(array $argv) {
$map = array();
foreach ($argv as $raw) {
$parts = explode('=', $raw, 2);
if (count($parts) !== 2) {
throw new PhutilArgumentUsageException(
pht(
'Configuration option "%s" is not valid. Configuration options '.
'passed with command line flags must be in the form "name=value".',
$raw));
}
list($key, $value) = $parts;
if (isset($map[$key])) {
throw new PhutilArgumentUsageException(
pht(
'Configuration option "%s" was provided multiple times with '.
'"--config" flags. Specify each option no more than once.',
$key));
}
$map[$key] = $value;
}
parent::__construct($map);
}
public function didReadUnknownOption(ArcanistRuntime $runtime, $key) {
throw new PhutilArgumentUsageException(
pht(
'Configuration option ("%s") specified with "--config" flag is not '.
'a recognized option.',
$key));
}
public function getSourceDisplayName() {
return pht('Runtime "--config" Flags');
}
public function isStringSource() {
return true;
}
}

View file

@ -0,0 +1,10 @@
<?php
final class ArcanistSystemConfigurationSource
extends ArcanistFilesystemConfigurationSource {
public function getFileKindDisplayName() {
return pht('System Config File');
}
}

View file

@ -0,0 +1,56 @@
<?php
final class ArcanistUserConfigurationSource
extends ArcanistFilesystemConfigurationSource {
public function getFileKindDisplayName() {
return pht('User Config File');
}
public function isWritableConfigurationSource() {
return true;
}
public function getConfigurationSourceScope() {
return ArcanistConfigurationSource::SCOPE_USER;
}
protected 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
// a more modern format.
if (!isset($values['config'])) {
// This isn't an older file, so just return the values unmodified.
return $values;
}
// Make the keys in "config" top-level keys. Then add in whatever other
// top level keys exist, other than "config", preferring keys that already
// exist in the "config" dictionary.
// For example, this older configuration file:
//
// {
// "hosts": ...,
// "config": {x: ..., y: ...},
// "aliases": ...
// }
//
// ...becomes this modern file:
//
// {
// "x": ...,
// "y": ...,
// "hosts": ...,
// "aliases": ...
// }
$result = $values['config'];
unset($values['config']);
$result += $values;
return $result;
}
}

View file

@ -0,0 +1,4 @@
<?php
abstract class ArcanistWorkingCopyConfigurationSource
extends ArcanistFilesystemConfigurationSource {}

View file

@ -0,0 +1,95 @@
<?php
final class ArcanistLogEngine
extends Phobject {
private $showTraceMessages;
public function setShowTraceMessages($show_trace_messages) {
$this->showTraceMessages = $show_trace_messages;
return $this;
}
public function getShowTraceMessages() {
return $this->showTraceMessages;
}
public function newMessage() {
return new ArcanistLogMessage();
}
private function writeBytes($bytes) {
fprintf(STDERR, '%s', $bytes);
return $this;
}
public function writeNewline() {
return $this->writeBytes("\n");
}
public function writeMessage(ArcanistLogMessage $message) {
$color = $message->getColor();
$this->writeBytes(
tsprintf(
"**<bg:".$color."> %s </bg>** %s\n",
$message->getLabel(),
$message->getMessage()));
return $message;
}
public function writeWarning($label, $message) {
return $this->writeMessage(
$this->newMessage()
->setColor('yellow')
->setLabel($label)
->setMessage($message));
}
public function writeError($label, $message) {
return $this->writeMessage(
$this->newMessage()
->setColor('red')
->setLabel($label)
->setMessage($message));
}
public function writeSuccess($label, $message) {
return $this->writeMessage(
$this->newMessage()
->setColor('green')
->setLabel($label)
->setMessage($message));
}
public function writeStatus($label, $message) {
return $this->writeMessage(
$this->newMessage()
->setColor('blue')
->setLabel($label)
->setMessage($message));
}
public function writeTrace($label, $message) {
$trace = $this->newMessage()
->setColor('magenta')
->setLabel($label)
->setMessage($message);
if ($this->getShowTraceMessages()) {
$this->writeMessage($trace);
}
return $trace;
}
public function writeHint($label, $message) {
return $this->writeMessage(
$this->newMessage()
->setColor('cyan')
->setLabel($label)
->setMessage($message));
}
}

View file

@ -0,0 +1,47 @@
<?php
final class ArcanistLogMessage
extends Phobject {
private $label;
private $message;
private $color;
private $channel;
public function setLabel($label) {
$this->label = $label;
return $this;
}
public function getLabel() {
return $this->label;
}
public function setColor($color) {
$this->color = $color;
return $this;
}
public function getColor() {
return $this->color;
}
public function setChannel($channel) {
$this->channel = $channel;
return $this;
}
public function getChannel() {
return $this->channel;
}
public function setMessage($message) {
$this->message = $message;
return $this;
}
public function getMessage() {
return $this->message;
}
}

View file

@ -0,0 +1,653 @@
<?php
final class ArcanistRuntime {
private $workflows;
private $logEngine;
private $lastInterruptTime;
private $stack = array();
public function execute(array $argv) {
try {
$this->checkEnvironment();
} catch (Exception $ex) {
echo "CONFIGURATION ERROR\n\n";
echo $ex->getMessage();
echo "\n\n";
return 1;
}
PhutilTranslator::getInstance()
->setLocale(PhutilLocale::loadLocale('en_US'))
->setTranslations(PhutilTranslation::getTranslationMapForLocale('en_US'));
$log = new ArcanistLogEngine();
$this->logEngine = $log;
try {
return $this->executeCore($argv);
} catch (ArcanistConduitException $ex) {
$log->writeError(pht('CONDUIT'), $ex->getMessage());
} catch (PhutilArgumentUsageException $ex) {
$log->writeError(pht('USAGE EXCEPTION'), $ex->getMessage());
} catch (ArcanistUserAbortException $ex) {
$log->writeError(pht('---'), $ex->getMessage());
}
return 1;
}
private function executeCore(array $argv) {
$log = $this->getLogEngine();
$config_args = array(
array(
'name' => 'library',
'param' => 'path',
'help' => pht('Load a library.'),
'repeat' => true,
),
array(
'name' => 'config',
'param' => 'key=value',
'repeat' => true,
'help' => pht('Specify a runtime configuration value.'),
),
array(
'name' => 'config-file',
'param' => 'path',
'repeat' => true,
'help' => pht(
'Load one or more configuration files. If this flag is provided, '.
'the system and user configuration files are ignored.'),
),
);
$args = id(new PhutilArgumentParser($argv))
->parseStandardArguments();
$is_trace = $args->getArg('trace');
$log->setShowTraceMessages($is_trace);
$log->writeTrace(pht('ARGV'), csprintf('%Ls', $argv));
// We're installing the signal handler after parsing "--trace" so that it
// can emit debugging messages. This means there's a very small window at
// startup where signals have no special handling, but we couldn't really
// route them or do anything interesting with them anyway.
$this->installSignalHandler();
$args->parsePartial($config_args, true);
$config_engine = $this->loadConfiguration($args);
$config = $config_engine->newConfigurationSourceList();
$this->loadLibraries($args, $config);
// Now that we've loaded libraries, we can validate configuration.
// Do this before continuing since configuration can impact other
// behaviors immediately and we want to catch any issues right away.
$config->setConfigOptions($config_engine->newConfigOptionsMap());
$config->validateConfiguration($this);
$toolset = $this->newToolset($argv);
$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);
}
$unconsumed_argv = $args->getUnconsumedArgumentVector();
if (!$unconsumed_argv) {
// TOOLSETS: This means the user just ran "arc" or some other top-level
// toolset without any workflow argument. We should give them a summary
// of the toolset, a list of workflows, and a pointer to "arc help" for
// more details.
// A possible exception is "arc --help", which should perhaps pass
// through and act like "arc help".
throw new PhutilArgumentUsageException(pht('Choose a workflow!'));
}
$alias_effects = id(new ArcanistAliasEngine())
->setRuntime($this)
->setToolset($toolset)
->setWorkflows($workflows)
->setConfigurationSourceList($config)
->resolveAliases($unconsumed_argv);
$result_argv = $this->applyAliasEffects($alias_effects, $unconsumed_argv);
$args->setUnconsumedArgumentVector($result_argv);
// TOOLSETS: Some day, stop falling through to the old "arc" runtime.
try {
return $args->parseWorkflowsFull($phutil_workflows);
} catch (PhutilArgumentUsageException $usage_exception) {
$log->writeHint(
pht('(::)'),
pht(
'Workflow is unrecognized by modern "arc", falling through '.
'to classic mode.'));
}
$arcanist_root = phutil_get_library_root('arcanist');
$arcanist_root = dirname($arcanist_root);
$bin = $arcanist_root.'/scripts/arcanist.php';
$err = phutil_passthru(
'php -f %R -- %Ls',
$bin,
array_slice($argv, 1));
return $err;
}
/**
* Perform some sanity checks against the possible diversity of PHP builds in
* the wild, like very old versions and builds that were compiled with flags
* that exclude core functionality.
*/
private function checkEnvironment() {
// NOTE: We don't have phutil_is_windows() yet here.
$is_windows = (DIRECTORY_SEPARATOR != '/');
// We use stream_socket_pair() which is not available on Windows earlier.
$min_version = ($is_windows ? '5.3.0' : '5.2.3');
$cur_version = phpversion();
if (version_compare($cur_version, $min_version, '<')) {
$message = sprintf(
'You are running a version of PHP ("%s"), which is older than the '.
'minimum supported version ("%s"). Update PHP to continue.',
$cur_version,
$min_version);
throw new Exception($message);
}
if ($is_windows) {
$need_functions = array(
'curl_init' => array('builtin-dll', 'php_curl.dll'),
);
} else {
$need_functions = array(
'curl_init' => array(
'text',
"You need to install the cURL PHP extension, maybe with ".
"'apt-get install php5-curl' or 'yum install php53-curl' or ".
"something similar.",
),
'json_decode' => array('flag', '--without-json'),
);
}
$problems = array();
$config = null;
$show_config = false;
foreach ($need_functions as $fname => $resolution) {
if (function_exists($fname)) {
continue;
}
static $info;
if ($info === null) {
ob_start();
phpinfo(INFO_GENERAL);
$info = ob_get_clean();
$matches = null;
if (preg_match('/^Configure Command =>\s*(.*?)$/m', $info, $matches)) {
$config = $matches[1];
}
}
list($what, $which) = $resolution;
if ($what == 'flag' && strpos($config, $which) !== false) {
$show_config = true;
$problems[] = sprintf(
'The build of PHP you are running was compiled with the configure '.
'flag "%s", which means it does not support the function "%s()". '.
'This function is required for Arcanist to run. Install a standard '.
'build of PHP or rebuild it without this flag. You may also be '.
'able to build or install the relevant extension separately.',
$which,
$fname);
continue;
}
if ($what == 'builtin-dll') {
$problems[] = sprintf(
'The build of PHP you are running does not have the "%s" extension '.
'enabled. Edit your php.ini file and uncomment the line which '.
'reads "extension=%s".',
$which,
$which);
continue;
}
if ($what == 'text') {
$problems[] = $which;
continue;
}
$problems[] = sprintf(
'The build of PHP you are running is missing the required function '.
'"%s()". Rebuild PHP or install the extension which provides "%s()".',
$fname,
$fname);
}
if ($problems) {
if ($show_config) {
$problems[] = "PHP was built with this configure command:\n\n{$config}";
}
$problems = implode("\n\n", $problems);
throw new Exception($problems);
}
}
private function loadConfiguration(PhutilArgumentParser $args) {
$engine = id(new ArcanistConfigurationEngine())
->setArguments($args);
$working_copy = ArcanistWorkingCopy::newFromWorkingDirectory(getcwd());
if ($working_copy) {
$engine->setWorkingCopy($working_copy);
}
return $engine;
}
private function loadLibraries(
PhutilArgumentParser $args,
ArcanistConfigurationSourceList $config) {
// TOOLSETS: Make this work again -- or replace it entirely with package
// management?
return;
$is_trace = $args->getArg('trace');
$load = array();
$working_copy = $this->getWorkingCopy();
$cli_libraries = $args->getArg('library');
if ($cli_libraries) {
$load[] = array(
'--library',
$cli_libraries,
);
} else {
$system_config = $config->readSystemArcConfig();
$load[] = array(
$config->getSystemArcConfigLocation(),
idx($system_config, 'load', array()),
);
$global_config = $config->readUserArcConfig();
$load[] = array(
$config->getUserConfigurationFileLocation(),
idx($global_config, 'load', array()),
);
$load[] = array(
'.arcconfig',
$working_copy->getProjectConfig('load'),
);
$load[] = array(
// TODO: We could explain exactly where this is coming from more
// clearly.
'./.../arc/config',
$working_copy->getLocalConfig('load'),
);
$load[] = array(
'--config load=...',
$config->getRuntimeConfig('load', array()),
);
}
foreach ($load as $spec) {
list($source, $libraries) = $spec;
if ($is_trace) {
$this->logTrace(
pht('LOAD'),
pht(
'Loading libraries from "%s"...',
$source));
}
if (!$libraries) {
if ($is_trace) {
$this->logTrace(pht('NONE'), pht('Nothing to load.'));
}
continue;
}
if (!is_array($libraries)) {
throw new PhutilArgumentUsageException(
pht(
'Libraries specified by "%s" are not formatted correctly. '.
'Expected a list of paths. Check your configuration.',
$source));
}
foreach ($libraries as $library) {
$this->loadLibrary($source, $library, $working_copy, $is_trace);
}
}
}
private function loadLibrary(
$source,
$location,
ArcanistWorkingCopyIdentity $working_copy,
$is_trace) {
// Try to resolve the library location. We look in several places, in
// order:
//
// 1. Inside the working copy. This is for phutil libraries within the
// project. For instance "library/src" will resolve to
// "./library/src" if it exists.
// 2. In the same directory as the working copy. This allows you to
// check out a library alongside a working copy and reference it.
// If we haven't resolved yet, "library/src" will try to resolve to
// "../library/src" if it exists.
// 3. Using normal libphutil resolution rules. Generally, this means
// that it checks for libraries next to libphutil, then libraries
// in the PHP include_path.
//
// Note that absolute paths will just resolve absolutely through rule (1).
$resolved = false;
// Check inside the working copy. This also checks absolute paths, since
// they'll resolve absolute and just ignore the project root.
$resolved_location = Filesystem::resolvePath(
$location,
$working_copy->getProjectRoot());
if (Filesystem::pathExists($resolved_location)) {
$location = $resolved_location;
$resolved = true;
}
// If we didn't find anything, check alongside the working copy.
if (!$resolved) {
$resolved_location = Filesystem::resolvePath(
$location,
dirname($working_copy->getProjectRoot()));
if (Filesystem::pathExists($resolved_location)) {
$location = $resolved_location;
$resolved = true;
}
}
if ($is_trace) {
$this->logTrace(
pht('LOAD'),
pht('Loading phutil library from "%s"...', $location));
}
$error = null;
try {
phutil_load_library($location);
} catch (PhutilBootloaderException $ex) {
fwrite(
STDERR,
'%s',
tsprintf(
"**<bg:red> %s </bg>** %s\n",
pht(
'Failed to load phutil library at location "%s". This library '.
'is specified by "%s". Check that the setting is correct and '.
'the library is located in the right place.',
$location,
$source)));
$prompt = pht('Continue without loading library?');
if (!phutil_console_confirm($prompt)) {
throw $ex;
}
} catch (PhutilLibraryConflictException $ex) {
if ($ex->getLibrary() != 'arcanist') {
throw $ex;
}
// NOTE: If you are running `arc` against itself, we ignore the library
// conflict created by loading the local `arc` library (in the current
// working directory) and continue without loading it.
// This means we only execute code in the `arcanist/` directory which is
// associated with the binary you are running, whereas we would normally
// execute local code.
// This can make `arc` development slightly confusing if your setup is
// especially bizarre, but it allows `arc` to be used in automation
// workflows more easily. For some context, see PHI13.
$executing_directory = dirname(dirname(__FILE__));
$working_directory = dirname($location);
fwrite(
STDERR,
tsprintf(
"**<bg:yellow> %s </bg>** %s\n",
pht('VERY META'),
pht(
'You are running one copy of Arcanist (at path "%s") against '.
'another copy of Arcanist (at path "%s"). Code in the current '.
'working directory will not be loaded or executed.',
$executing_directory,
$working_directory)));
}
}
private function newToolset(array $argv) {
$binary = basename($argv[0]);
$toolsets = ArcanistToolset::newToolsetMap();
if (!isset($toolsets[$binary])) {
throw new PhutilArgumentUsageException(
pht(
'Arcanist toolset "%s" is unknown. The Arcanist binary should '.
'be executed so that "argv[0]" identifies a supported toolset. '.
'Rename the binary or install the library that provides the '.
'desired toolset. Current available toolsets: %s.',
$binary,
implode(', ', array_keys($toolsets))));
}
return $toolsets[$binary];
}
private function newWorkflows(ArcanistToolset $toolset) {
$workflows = id(new PhutilClassMapQuery())
->setAncestorClass('ArcanistWorkflow')
->execute();
foreach ($workflows as $key => $workflow) {
if (!$workflow->supportsToolset($toolset)) {
unset($workflows[$key]);
}
}
$map = array();
foreach ($workflows as $workflow) {
$key = $workflow->getWorkflowName();
if (isset($map[$key])) {
throw new Exception(
pht(
'Two workflows ("%s" and "%s") both have the same name ("%s") '.
'and both support the current toolset ("%s", "%s"). Each '.
'workflow in a given toolset must have a unique name.',
get_class($workflow),
get_class($map[$key]),
get_class($toolset),
$toolset->getToolsetKey()));
}
$map[$key] = id(clone $workflow)
->setToolset($toolset);
}
return $map;
}
public function getWorkflows() {
return $this->workflows;
}
public function getLogEngine() {
return $this->logEngine;
}
private function applyAliasEffects(array $effects, array $argv) {
assert_instances_of($effects, 'ArcanistAliasEffect');
$log = $this->getLogEngine();
$command = null;
$arguments = null;
foreach ($effects as $effect) {
$message = $effect->getMessage();
if ($message !== null) {
$log->writeInfo(pht('ALIAS'), $message);
}
if ($effect->getCommand()) {
$command = $effect->getCommand();
$arguments = $effect->getArguments();
}
}
if ($command !== null) {
$argv = array_merge(array($command), $arguments);
}
return $argv;
}
private function installSignalHandler() {
$log = $this->getLogEngine();
if (!function_exists('pcntl_signal')) {
$log->writeTrace(
pht('PCNTL'),
pht(
'Unable to install signal handler, pcntl_signal() unavailable. '.
'Continuing without signal handling.'));
return;
}
// NOTE: SIGHUP, SIGTERM and SIGWINCH are handled by "PhutilSignalRouter".
// This logic is largely similar to the logic there, but more specific to
// Arcanist workflows.
pcntl_signal(SIGINT, array($this, 'routeSignal'));
}
public function routeSignal($signo) {
switch ($signo) {
case SIGINT:
$this->routeInterruptSignal($signo);
break;
}
}
private function routeInterruptSignal($signo) {
$log = $this->getLogEngine();
$last_interrupt = $this->lastInterruptTime;
$now = microtime(true);
$this->lastInterruptTime = $now;
$should_exit = false;
// If we received another SIGINT recently, always exit. This implements
// "press ^C twice in quick succession to exit" regardless of what the
// workflow may decide to do.
$interval = 2;
if ($last_interrupt !== null) {
if ($now - $last_interrupt < $interval) {
$should_exit = true;
}
}
$handler = null;
if (!$should_exit) {
// Look for an interrupt handler in the current workflow stack.
$stack = $this->getWorkflowStack();
foreach ($stack as $workflow) {
if ($workflow->canHandleSignal($signo)) {
$handler = $workflow;
break;
}
}
// If no workflow in the current execution stack can handle an interrupt
// signal, just exit on the first interrupt.
if (!$handler) {
$should_exit = true;
}
}
// It's common for users to ^C on prompts. Write a newline before writing
// a response to the interrupt so the behavior is a little cleaner. This
// also avoids lines that read "^C [ INTERRUPT ] ...".
$log->writeNewline();
if ($should_exit) {
$log->writeHint(
pht('INTERRUPT'),
pht('Interrupted by SIGINT (^C).'));
exit(128 + $signo);
}
$log->writeHint(
pht('INTERRUPT'),
pht('Press ^C again to exit.'));
$handler->handleSignal($signo);
}
public function pushWorkflow(ArcanistWorkflow $workflow) {
$this->stack[] = $workflow;
return $this;
}
public function popWorkflow() {
if (!$this->stack) {
throw new Exception(pht('Trying to pop an empty workflow stack!'));
}
return array_pop($this->stack);
}
public function getWorkflowStack() {
return $this->stack;
}
}

View file

@ -0,0 +1,144 @@
<?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;
}
}

View file

@ -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;
}
}

View file

@ -0,0 +1,254 @@
<?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);
}
}

View file

@ -0,0 +1,26 @@
<?php
final class ArcanistArcToolset extends ArcanistToolset {
const TOOLSETKEY = 'arc';
public function getToolsetArguments() {
return array(
array(
'name' => 'conduit-uri',
'param' => 'uri',
'help' => pht('Connect to Phabricator install specified by __uri__.'),
),
array(
'name' => 'conduit-token',
'param' => 'token',
'help' => pht('Use a specific authentication token.'),
),
array(
'name' => 'anonymous',
'help' => pht('Run workflow as a public user, without authenticating.'),
),
);
}
}

View file

@ -0,0 +1,8 @@
<?php
final class ArcanistPhageToolset extends ArcanistToolset {
const TOOLSETKEY = 'phage';
}

View file

@ -0,0 +1,24 @@
<?php
final class ArcanistPhutilWorkflow extends PhutilArgumentWorkflow {
private $workflow;
public function setWorkflow(ArcanistWorkflow $workflow) {
$this->workflow = $workflow;
return $this;
}
public function getWorkflow() {
return $this->workflow;
}
public function isExecutable() {
return true;
}
public function execute(PhutilArgumentParser $args) {
return $this->getWorkflow()->executeWorkflow($args);
}
}

View file

@ -0,0 +1,151 @@
<?php
final class ArcanistPrompt
extends Phobject {
private $key;
private $workflow;
private $description;
private $query;
public function setKey($key) {
$this->key = $key;
return $this;
}
public function getKey() {
return $this->key;
}
public function setWorkflow(ArcanistWorkflow $workflow) {
$this->workflow = $workflow;
return $this;
}
public function getWorkflow() {
return $this->workflow;
}
public function setDescription($description) {
$this->description = $description;
return $this;
}
public function getDescription() {
return $this->description;
}
public function setQuery($query) {
$this->query = $query;
return $this;
}
public function getQuery() {
return $this->query;
}
public function execute() {
$workflow = $this->getWorkflow();
if ($workflow) {
$workflow_ok = $workflow->hasPrompt($this->getKey());
} else {
$workflow_ok = false;
}
if (!$workflow_ok) {
throw new Exception(
pht(
'Prompt ("%s") is executing, but it is not properly bound to the '.
'invoking workflow. You may have called "newPrompt()" to execute a '.
'prompt instead of "getPrompt()". Use "newPrompt()" when defining '.
'prompts and "getPrompt()" when executing them.',
$this->getKey()));
}
$query = $this->getQuery();
if (!strlen($query)) {
throw new Exception(
pht(
'Prompt ("%s") has no query text!',
$this->getKey()));
}
$options = '[y/N]';
$default = 'N';
try {
phutil_console_require_tty();
} catch (PhutilConsoleStdinNotInteractiveException $ex) {
// TOOLSETS: Clean this up to provide more details to the user about how
// they can configure prompts to be answered.
// Throw after echoing the prompt so the user has some idea what happened.
echo $query."\n";
throw $ex;
}
// NOTE: We're making stdin nonblocking so that we can respond to signals
// immediately. If we don't, and you ^C during a prompt, the program does
// not handle the signal until fgets() returns.
$stdin = fopen('php://stdin', 'r');
if (!$stdin) {
throw new Exception(pht('Failed to open stdin for reading.'));
}
$ok = stream_set_blocking($stdin, false);
if (!$ok) {
throw new Exception(pht('Unable to set stdin nonblocking.'));
}
echo "\n";
$result = null;
while (true) {
echo tsprintf(
'**<bg:cyan> %s </bg>** %s %s ',
'>>>',
$query,
$options);
while (true) {
$read = array($stdin);
$write = array();
$except = array();
$ok = stream_select($read, $write, $except, 1);
if ($ok === false) {
throw new Exception(pht('stream_select() failed!'));
}
$response = fgets($stdin);
if (!strlen($response)) {
continue;
}
break;
}
$response = trim($response);
if (!strlen($response)) {
$response = $default;
}
if (phutil_utf8_strtolower($response) == 'y') {
$result = true;
break;
}
if (phutil_utf8_strtolower($response) == 'n') {
$result = false;
break;
}
}
if (!$result) {
throw new ArcanistUserAbortException();
}
}
}

View file

@ -0,0 +1,22 @@
<?php
abstract class ArcanistToolset extends Phobject {
final public function getToolsetKey() {
return $this->getPhobjectClassConstant('TOOLSETKEY');
}
final public static function newToolsetMap() {
$toolsets = id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getToolsetKey')
->execute();
return $toolsets;
}
public function getToolsetArguments() {
return array();
}
}

View file

@ -0,0 +1,74 @@
<?php
final class ArcanistWorkflowArgument
extends Phobject {
private $key;
private $help;
private $wildcard;
private $parameter;
private $isPathArgument;
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;
}
$parameter = $this->getParameter();
if ($parameter !== null) {
$spec['param'] = $parameter;
}
return $spec;
}
public function setHelp($help) {
$this->help = $help;
return $this;
}
public function getHelp() {
return $this->help;
}
public function setParameter($parameter) {
$this->parameter = $parameter;
return $this;
}
public function getParameter() {
return $this->parameter;
}
public function setIsPathArgument($is_path_argument) {
$this->isPathArgument = $is_path_argument;
return $this;
}
public function getIsPathArgument() {
return $this->isPathArgument;
}
}

View file

@ -0,0 +1,27 @@
<?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;
}
}

View file

@ -141,6 +141,8 @@ EOTEXT
throw new ArcanistUsageException(
pht("Unknown library version '%s'!", $version));
}
echo tsprintf("%s\n", pht('Done.'));
}
private function getLibraryFormatVersion($path) {

View file

@ -108,6 +108,10 @@ abstract class ArcanistWorkflow extends Phobject {
*/
abstract public function getCommandHelp();
final public function supportsToolset($toolset) {
return ($toolset === 'arc');
}
/* -( Conduit )------------------------------------------------------------ */

View file

@ -0,0 +1,25 @@
<?php
final class ArcanistGitWorkingCopy
extends ArcanistWorkingCopy {
public function getMetadataDirectory() {
return $this->getPath('.git');
}
protected function newWorkingCopyFromDirectories(
$working_directory,
$ancestor_directory) {
if (!Filesystem::pathExists($ancestor_directory.'/.git')) {
return null;
}
return new self();
}
public function newRepositoryAPI() {
return new ArcanistGitAPI($this->getPath());
}
}

View file

@ -0,0 +1,25 @@
<?php
final class ArcanistMercurialWorkingCopy
extends ArcanistWorkingCopy {
public function getMetadataDirectory() {
return $this->getPath('.hg');
}
protected function newWorkingCopyFromDirectories(
$working_directory,
$ancestor_directory) {
if (!Filesystem::pathExists($ancestor_directory.'/.hg')) {
return null;
}
return new self();
}
public function newRepositoryAPI() {
return new ArcanistMercurialAPI($this->getPath());
}
}

View file

@ -0,0 +1,77 @@
<?php
final class ArcanistSubversionWorkingCopy
extends ArcanistWorkingCopy {
public function getProjectConfigurationFilePath() {
// In Subversion, we allow ".arcconfig" to appear at any level of the
// filesystem between the working directory and the working copy root.
// We allow this because Subversion repositories are hierarchical and
// may have a "projects/xyz/" directory which is meaningfully an entirely
// different project from "projects/abc/".
// You can checkout "projects/" and have the ".svn/" directory appear
// there, then change into "abc/" and expect "arc" to operate within the
// context of the "abc/" project.
$paths = Filesystem::walkToRoot($this->getWorkingDirectory());
$root = $this->getPath();
foreach ($paths as $path) {
if (!Filesystem::isDescendant($path, $root)) {
break;
}
$candidate = $path.'/.arcconfig';
if (Filesystem::pathExists($candidate)) {
return $candidate;
}
}
return parent::getProjectConfigurationFilePath();
}
public function getMetadataDirectory() {
return $this->getPath('.svn');
}
protected function newWorkingCopyFromDirectories(
$working_directory,
$ancestor_directory) {
if (!Filesystem::pathExists($ancestor_directory.'/.svn')) {
return null;
}
return new self();
}
protected function selectFromNestedWorkingCopies(array $candidates) {
// To select the best working copy in Subversion, we first walk up the
// tree looking for a working copy with an ".arcconfig" file. If we find
// one, this anchors us.
foreach (array_reverse($candidates) as $candidate) {
$arcconfig = $candidate->getPath('.arcconfig');
if (Filesystem::pathExists($arcconfig)) {
return $candidate;
}
}
// If we didn't find one, we select the outermost working copy. This is
// because older versions of Subversion (prior to 1.7) put a ".svn" file
// in every directory, and all versions of Subversion allow you to check
// out any subdirectory of the project as a working copy.
// We could possibly refine this by testing if the working copy was made
// with a recent version of Subversion and picking the deepest working copy
// if it was, similar to Git and Mercurial.
return head($candidates);
}
public function newRepositoryAPI() {
return new ArcanistSubversionAPI($this->getPath());
}
}

View file

@ -0,0 +1,115 @@
<?php
abstract class ArcanistWorkingCopy
extends Phobject {
private $path;
private $workingDirectory;
public static function newFromWorkingDirectory($path) {
$working_types = id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->execute();
$paths = Filesystem::walkToRoot($path);
$paths = array_reverse($paths);
$candidates = array();
foreach ($paths as $path_key => $ancestor_path) {
foreach ($working_types as $working_type) {
$working_copy = $working_type->newWorkingCopyFromDirectories(
$path,
$ancestor_path);
if (!$working_copy) {
continue;
}
$working_copy->path = $ancestor_path;
$working_copy->workingDirectory = $path;
$candidates[] = $working_copy;
}
}
// If we've found multiple candidate working copies, we need to pick one.
// We let the innermost working copy pick the best candidate from among
// candidates of the same type. The rules for Git and Mercurial differ
// slightly from the rules for Subversion.
if ($candidates) {
$deepest = last($candidates);
foreach ($candidates as $key => $candidate) {
if (get_class($candidate) != get_class($deepest)) {
unset($candidates[$key]);
}
}
$candidates = array_values($candidates);
return $deepest->selectFromNestedWorkingCopies($candidates);
}
return null;
}
abstract protected function newWorkingCopyFromDirectories(
$working_directory,
$ancestor_directory);
final public function getPath($to_file = null) {
return Filesystem::concatenatePaths(
array(
$this->path,
$to_file,
));
}
final public function getWorkingDirectory() {
return $this->workingDirectory;
}
public function getProjectConfigurationFilePath() {
return $this->getPath('.arcconfig');
}
public function getLocalConfigurationFilePath() {
if ($this->hasMetadataDirectory()) {
return $this->getMetadataPath('arc/config');
}
return null;
}
public function getMetadataDirectory() {
return null;
}
final public function hasMetadataDirectory() {
return ($this->getMetadataDirectory() !== null);
}
final public function getMetadataPath($to_file = null) {
if (!$this->hasMetadataDirectory()) {
throw new Exception(
pht(
'This working copy has no metadata directory, so you can not '.
'resolve metadata paths within it.'));
}
return Filesystem::concatenatePaths(
array(
$this->getMetadataDirectory(),
$to_file,
));
}
protected function selectFromNestedWorkingCopies(array $candidates) {
// Normally, the best working copy in a stack is the deepest working copy.
// Subversion uses slightly different rules.
return last($candidates);
}
abstract public function newRepositoryAPI();
}

View file

@ -0,0 +1,129 @@
<?php
final class ArcanistWorkingCopyPath
extends Phobject {
private $path;
private $mode;
private $data;
private $binary;
private $dataAsLines;
private $charMap;
private $lineMap;
public function setPath($path) {
$this->path = $path;
return $this;
}
public function getPath() {
return $this->path;
}
public function setData($data) {
$this->data = $data;
return $this;
}
public function getData() {
if ($this->data === null) {
throw new Exception(
pht(
'No data provided for path "%s".',
$this->getDescription()));
}
return $this->data;
}
public function getDataAsLines() {
if ($this->dataAsLines === null) {
$lines = phutil_split_lines($this->getData());
$this->dataAsLines = $lines;
}
return $this->dataAsLines;
}
public function setMode($mode) {
$this->mode = $mode;
return $this;
}
public function getMode() {
if ($this->mode === null) {
throw new Exception(
pht(
'No mode provided for path "%s".',
$this->getDescription()));
}
return $this->mode;
}
public function isExecutable() {
$mode = $this->getMode();
return (bool)($mode & 0111);
}
public function isBinary() {
if ($this->binary === null) {
$data = $this->getData();
$is_binary = ArcanistDiffUtils::isHeuristicBinaryFile($data);
$this->binary = $is_binary;
}
return $this->binary;
}
public function getMimeType() {
if ($this->mimeType === null) {
// TOOLSETS: This is not terribly efficient on real repositories since
// it re-writes files which are often already on disk, but is good for
// unit tests.
$tmp = new TempFile();
Filesystem::writeFile($tmp, $this->getData());
$mime = Filesystem::getMimeType($tmp);
$this->mimeType = $mime;
}
return $this->mimeType;
}
public function getBasename() {
return basename($this->getPath());
}
public function getLineAndCharFromOffset($offset) {
if ($this->charMap === null) {
$char_map = array();
$line_map = array();
$lines = $this->getDataAsLines();
$line_number = 0;
$line_start = 0;
foreach ($lines as $line) {
$len = strlen($line);
$line_map[] = $line_start;
$line_start += $len;
for ($ii = 0; $ii < $len; $ii++) {
$char_map[] = $line_number;
}
$line_number++;
}
$this->charMap = $char_map;
$this->lineMap = $line_map;
}
$line = $this->charMap[$offset];
$char = $offset - $this->lineMap[$line];
return array($line, $char);
}
}

View file

@ -0,0 +1,6 @@
<?php
require_once dirname(__FILE__).'/init-script.php';
$runtime = new ArcanistRuntime();
return $runtime->execute($argv);

View file

@ -56,6 +56,20 @@ function __arcanist_init_script__() {
ini_set($config_key, $config_value);
}
$php_version = phpversion();
$min_version = '5.5.0';
if (version_compare($php_version, $min_version, '<')) {
echo sprintf(
'UPGRADE PHP: '.
'The installed version of PHP ("%s") is too old to run Arcanist. '.
'Update PHP to at least the minimum required version ("%s").',
$php_version,
$min_version);
echo "\n";
exit(1);
}
if (!ini_get('date.timezone')) {
// If the timezone isn't set, PHP issues a warning whenever you try to parse
// a date (like those from Git or Mercurial logs), even if the date contains