mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-25 08:12:40 +01:00
Merge Phacility/master into phorge
This commit is contained in:
commit
9b4bcc8349
102 changed files with 3619 additions and 2353 deletions
File diff suppressed because it is too large
Load diff
|
@ -68,7 +68,10 @@ $base_args->parsePartial(
|
||||||
array(
|
array(
|
||||||
'name' => 'conduit-uri',
|
'name' => 'conduit-uri',
|
||||||
'param' => 'uri',
|
'param' => 'uri',
|
||||||
'help' => pht('Connect to Phabricator install specified by __uri__.'),
|
'help' => pht(
|
||||||
|
'Connect to the %s (or compatible software) server specified by '.
|
||||||
|
'__uri__.',
|
||||||
|
PlatformSymbols::getPlatformServerName()),
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'name' => 'conduit-token',
|
'name' => 'conduit-token',
|
||||||
|
@ -85,7 +88,8 @@ $base_args->parsePartial(
|
||||||
'repeat' => true,
|
'repeat' => true,
|
||||||
'help' => pht(
|
'help' => pht(
|
||||||
'Specify a runtime configuration value. This will take precedence '.
|
'Specify a runtime configuration value. This will take precedence '.
|
||||||
'over static values, and only affect the current arcanist invocation.'),
|
'over static values, and only affect the current process: the '.
|
||||||
|
'setting is not saved anywhere.'),
|
||||||
),
|
),
|
||||||
));
|
));
|
||||||
|
|
||||||
|
@ -310,9 +314,13 @@ try {
|
||||||
$message = phutil_console_format(
|
$message = phutil_console_format(
|
||||||
"%s\n\n - %s\n - %s\n - %s\n",
|
"%s\n\n - %s\n - %s\n - %s\n",
|
||||||
pht(
|
pht(
|
||||||
'This command requires arc to connect to a Phabricator install, '.
|
'This command requires %s to connect to a %s (or compatible '.
|
||||||
'but no Phabricator installation is configured. To configure a '.
|
'software) server, but no %s server is configured. To configure a '.
|
||||||
'Phabricator URI:'),
|
'%s server URI:',
|
||||||
|
PlatformSymbols::getPlatformClientName(),
|
||||||
|
PlatformSymbols::getPlatformServerName(),
|
||||||
|
PlatformSymbols::getPlatformServerName(),
|
||||||
|
PlatformSymbols::getPlatformServerName()),
|
||||||
pht(
|
pht(
|
||||||
'set a default location with `%s`; or',
|
'set a default location with `%s`; or',
|
||||||
'arc set-config default <uri>'),
|
'arc set-config default <uri>'),
|
||||||
|
@ -688,10 +696,12 @@ function arcanist_load_libraries(
|
||||||
"**<bg:yellow> %s </bg>** %s\n",
|
"**<bg:yellow> %s </bg>** %s\n",
|
||||||
pht('VERY META'),
|
pht('VERY META'),
|
||||||
pht(
|
pht(
|
||||||
'You are running one copy of Arcanist (at path "%s") against '.
|
'You are running one copy of %s (at path "%s") against '.
|
||||||
'another copy of Arcanist (at path "%s"). Code in the current '.
|
'another copy of %s (at path "%s"). Code in the current '.
|
||||||
'working directory will not be loaded or executed.',
|
'working directory will not be loaded or executed.',
|
||||||
|
PlatformSymbols::getPlatformClientName(),
|
||||||
$executing_directory,
|
$executing_directory,
|
||||||
|
PlatformSymbols::getPlatformClientName(),
|
||||||
$working_directory)));
|
$working_directory)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -188,6 +188,8 @@ phutil_register_library_map(array(
|
||||||
'ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase.php',
|
'ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase.php',
|
||||||
'ArcanistDynamicDefineXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDynamicDefineXHPASTLinterRule.php',
|
'ArcanistDynamicDefineXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDynamicDefineXHPASTLinterRule.php',
|
||||||
'ArcanistDynamicDefineXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDynamicDefineXHPASTLinterRuleTestCase.php',
|
'ArcanistDynamicDefineXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDynamicDefineXHPASTLinterRuleTestCase.php',
|
||||||
|
'ArcanistEachUseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistEachUseXHPASTLinterRule.php',
|
||||||
|
'ArcanistEachUseXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistEachUseXHPASTLinterRuleTestCase.php',
|
||||||
'ArcanistElseIfUsageXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistElseIfUsageXHPASTLinterRule.php',
|
'ArcanistElseIfUsageXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistElseIfUsageXHPASTLinterRule.php',
|
||||||
'ArcanistElseIfUsageXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistElseIfUsageXHPASTLinterRuleTestCase.php',
|
'ArcanistElseIfUsageXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistElseIfUsageXHPASTLinterRuleTestCase.php',
|
||||||
'ArcanistEmptyFileXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistEmptyFileXHPASTLinterRule.php',
|
'ArcanistEmptyFileXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistEmptyFileXHPASTLinterRule.php',
|
||||||
|
@ -264,6 +266,8 @@ phutil_register_library_map(array(
|
||||||
'ArcanistHgProxyClient' => 'hgdaemon/ArcanistHgProxyClient.php',
|
'ArcanistHgProxyClient' => 'hgdaemon/ArcanistHgProxyClient.php',
|
||||||
'ArcanistHgProxyServer' => 'hgdaemon/ArcanistHgProxyServer.php',
|
'ArcanistHgProxyServer' => 'hgdaemon/ArcanistHgProxyServer.php',
|
||||||
'ArcanistHgServerChannel' => 'hgdaemon/ArcanistHgServerChannel.php',
|
'ArcanistHgServerChannel' => 'hgdaemon/ArcanistHgServerChannel.php',
|
||||||
|
'ArcanistHostMemorySnapshot' => 'filesystem/memory/ArcanistHostMemorySnapshot.php',
|
||||||
|
'ArcanistHostMemorySnapshotTestCase' => 'filesystem/memory/__tests__/ArcanistHostMemorySnapshotTestCase.php',
|
||||||
'ArcanistImplicitConstructorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitConstructorXHPASTLinterRule.php',
|
'ArcanistImplicitConstructorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitConstructorXHPASTLinterRule.php',
|
||||||
'ArcanistImplicitConstructorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistImplicitConstructorXHPASTLinterRuleTestCase.php',
|
'ArcanistImplicitConstructorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistImplicitConstructorXHPASTLinterRuleTestCase.php',
|
||||||
'ArcanistImplicitFallthroughXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitFallthroughXHPASTLinterRule.php',
|
'ArcanistImplicitFallthroughXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitFallthroughXHPASTLinterRule.php',
|
||||||
|
@ -346,6 +350,7 @@ phutil_register_library_map(array(
|
||||||
'ArcanistMercurialAPI' => 'repository/api/ArcanistMercurialAPI.php',
|
'ArcanistMercurialAPI' => 'repository/api/ArcanistMercurialAPI.php',
|
||||||
'ArcanistMercurialCommitGraphQuery' => 'repository/graph/query/ArcanistMercurialCommitGraphQuery.php',
|
'ArcanistMercurialCommitGraphQuery' => 'repository/graph/query/ArcanistMercurialCommitGraphQuery.php',
|
||||||
'ArcanistMercurialCommitMessageHardpointQuery' => 'query/ArcanistMercurialCommitMessageHardpointQuery.php',
|
'ArcanistMercurialCommitMessageHardpointQuery' => 'query/ArcanistMercurialCommitMessageHardpointQuery.php',
|
||||||
|
'ArcanistMercurialCommitSymbolCommitHardpointQuery' => 'ref/commit/ArcanistMercurialCommitSymbolCommitHardpointQuery.php',
|
||||||
'ArcanistMercurialLandEngine' => 'land/engine/ArcanistMercurialLandEngine.php',
|
'ArcanistMercurialLandEngine' => 'land/engine/ArcanistMercurialLandEngine.php',
|
||||||
'ArcanistMercurialLocalState' => 'repository/state/ArcanistMercurialLocalState.php',
|
'ArcanistMercurialLocalState' => 'repository/state/ArcanistMercurialLocalState.php',
|
||||||
'ArcanistMercurialParser' => 'repository/parser/ArcanistMercurialParser.php',
|
'ArcanistMercurialParser' => 'repository/parser/ArcanistMercurialParser.php',
|
||||||
|
@ -419,6 +424,7 @@ phutil_register_library_map(array(
|
||||||
'ArcanistPhutilXHPASTLinterStandard' => 'lint/linter/standards/phutil/ArcanistPhutilXHPASTLinterStandard.php',
|
'ArcanistPhutilXHPASTLinterStandard' => 'lint/linter/standards/phutil/ArcanistPhutilXHPASTLinterStandard.php',
|
||||||
'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPlusOperatorOnStringsXHPASTLinterRule.php',
|
'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPlusOperatorOnStringsXHPASTLinterRule.php',
|
||||||
'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase.php',
|
'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase.php',
|
||||||
|
'ArcanistProductNameLiteralXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistProductNameLiteralXHPASTLinterRule.php',
|
||||||
'ArcanistProjectConfigurationSource' => 'config/source/ArcanistProjectConfigurationSource.php',
|
'ArcanistProjectConfigurationSource' => 'config/source/ArcanistProjectConfigurationSource.php',
|
||||||
'ArcanistPrompt' => 'toolset/ArcanistPrompt.php',
|
'ArcanistPrompt' => 'toolset/ArcanistPrompt.php',
|
||||||
'ArcanistPromptResponse' => 'toolset/ArcanistPromptResponse.php',
|
'ArcanistPromptResponse' => 'toolset/ArcanistPromptResponse.php',
|
||||||
|
@ -632,6 +638,8 @@ phutil_register_library_map(array(
|
||||||
'LinesOfALargeFile' => 'filesystem/linesofalarge/LinesOfALargeFile.php',
|
'LinesOfALargeFile' => 'filesystem/linesofalarge/LinesOfALargeFile.php',
|
||||||
'LinesOfALargeFileTestCase' => 'filesystem/linesofalarge/__tests__/LinesOfALargeFileTestCase.php',
|
'LinesOfALargeFileTestCase' => 'filesystem/linesofalarge/__tests__/LinesOfALargeFileTestCase.php',
|
||||||
'MFilterTestHelper' => 'utils/__tests__/MFilterTestHelper.php',
|
'MFilterTestHelper' => 'utils/__tests__/MFilterTestHelper.php',
|
||||||
|
'MethodCallFuture' => 'future/MethodCallFuture.php',
|
||||||
|
'MethodCallFutureTestCase' => 'future/__tests__/MethodCallFutureTestCase.php',
|
||||||
'NoseTestEngine' => 'unit/engine/NoseTestEngine.php',
|
'NoseTestEngine' => 'unit/engine/NoseTestEngine.php',
|
||||||
'PHPASTParserTestCase' => 'parser/xhpast/__tests__/PHPASTParserTestCase.php',
|
'PHPASTParserTestCase' => 'parser/xhpast/__tests__/PHPASTParserTestCase.php',
|
||||||
'PhageAction' => 'phage/action/PhageAction.php',
|
'PhageAction' => 'phage/action/PhageAction.php',
|
||||||
|
@ -907,6 +915,7 @@ phutil_register_library_map(array(
|
||||||
'PhutilVeryWowEnglishLocale' => 'internationalization/locales/PhutilVeryWowEnglishLocale.php',
|
'PhutilVeryWowEnglishLocale' => 'internationalization/locales/PhutilVeryWowEnglishLocale.php',
|
||||||
'PhutilWordPressFuture' => 'future/wordpress/PhutilWordPressFuture.php',
|
'PhutilWordPressFuture' => 'future/wordpress/PhutilWordPressFuture.php',
|
||||||
'PhutilXHPASTBinary' => 'parser/xhpast/bin/PhutilXHPASTBinary.php',
|
'PhutilXHPASTBinary' => 'parser/xhpast/bin/PhutilXHPASTBinary.php',
|
||||||
|
'PlatformSymbols' => 'platform/PlatformSymbols.php',
|
||||||
'PytestTestEngine' => 'unit/engine/PytestTestEngine.php',
|
'PytestTestEngine' => 'unit/engine/PytestTestEngine.php',
|
||||||
'TempFile' => 'filesystem/TempFile.php',
|
'TempFile' => 'filesystem/TempFile.php',
|
||||||
'TestAbstractDirectedGraph' => 'utils/__tests__/TestAbstractDirectedGraph.php',
|
'TestAbstractDirectedGraph' => 'utils/__tests__/TestAbstractDirectedGraph.php',
|
||||||
|
@ -959,6 +968,7 @@ phutil_register_library_map(array(
|
||||||
'nonempty' => 'utils/utils.php',
|
'nonempty' => 'utils/utils.php',
|
||||||
'phlog' => 'error/phlog.php',
|
'phlog' => 'error/phlog.php',
|
||||||
'pht' => 'internationalization/pht.php',
|
'pht' => 'internationalization/pht.php',
|
||||||
|
'pht_list' => 'internationalization/pht.php',
|
||||||
'phutil_build_http_querystring' => 'utils/utils.php',
|
'phutil_build_http_querystring' => 'utils/utils.php',
|
||||||
'phutil_build_http_querystring_from_pairs' => 'utils/utils.php',
|
'phutil_build_http_querystring_from_pairs' => 'utils/utils.php',
|
||||||
'phutil_censor_credentials' => 'utils/utils.php',
|
'phutil_censor_credentials' => 'utils/utils.php',
|
||||||
|
@ -972,7 +982,6 @@ phutil_register_library_map(array(
|
||||||
'phutil_count' => 'internationalization/pht.php',
|
'phutil_count' => 'internationalization/pht.php',
|
||||||
'phutil_date_format' => 'utils/viewutils.php',
|
'phutil_date_format' => 'utils/viewutils.php',
|
||||||
'phutil_decode_mime_header' => 'utils/utils.php',
|
'phutil_decode_mime_header' => 'utils/utils.php',
|
||||||
'phutil_deprecated' => 'init/lib/moduleutils.php',
|
|
||||||
'phutil_describe_type' => 'utils/utils.php',
|
'phutil_describe_type' => 'utils/utils.php',
|
||||||
'phutil_encode_log' => 'utils/utils.php',
|
'phutil_encode_log' => 'utils/utils.php',
|
||||||
'phutil_error_listener_example' => 'error/phlog.php',
|
'phutil_error_listener_example' => 'error/phlog.php',
|
||||||
|
@ -1008,6 +1017,9 @@ phutil_register_library_map(array(
|
||||||
'phutil_load_library' => 'init/lib/moduleutils.php',
|
'phutil_load_library' => 'init/lib/moduleutils.php',
|
||||||
'phutil_loggable_string' => 'utils/utils.php',
|
'phutil_loggable_string' => 'utils/utils.php',
|
||||||
'phutil_microseconds_since' => 'utils/utils.php',
|
'phutil_microseconds_since' => 'utils/utils.php',
|
||||||
|
'phutil_nonempty_scalar' => 'utils/utils.php',
|
||||||
|
'phutil_nonempty_string' => 'utils/utils.php',
|
||||||
|
'phutil_nonempty_stringlike' => 'utils/utils.php',
|
||||||
'phutil_parse_bytes' => 'utils/viewutils.php',
|
'phutil_parse_bytes' => 'utils/viewutils.php',
|
||||||
'phutil_partition' => 'utils/utils.php',
|
'phutil_partition' => 'utils/utils.php',
|
||||||
'phutil_passthru' => 'future/exec/execx.php',
|
'phutil_passthru' => 'future/exec/execx.php',
|
||||||
|
@ -1245,6 +1257,8 @@ phutil_register_library_map(array(
|
||||||
'ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
'ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||||
'ArcanistDynamicDefineXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
'ArcanistDynamicDefineXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||||
'ArcanistDynamicDefineXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
'ArcanistDynamicDefineXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||||
|
'ArcanistEachUseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||||
|
'ArcanistEachUseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||||
'ArcanistElseIfUsageXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
'ArcanistElseIfUsageXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||||
'ArcanistElseIfUsageXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
'ArcanistElseIfUsageXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||||
'ArcanistEmptyFileXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
'ArcanistEmptyFileXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||||
|
@ -1321,6 +1335,8 @@ phutil_register_library_map(array(
|
||||||
'ArcanistHgProxyClient' => 'Phobject',
|
'ArcanistHgProxyClient' => 'Phobject',
|
||||||
'ArcanistHgProxyServer' => 'Phobject',
|
'ArcanistHgProxyServer' => 'Phobject',
|
||||||
'ArcanistHgServerChannel' => 'PhutilProtocolChannel',
|
'ArcanistHgServerChannel' => 'PhutilProtocolChannel',
|
||||||
|
'ArcanistHostMemorySnapshot' => 'Phobject',
|
||||||
|
'ArcanistHostMemorySnapshotTestCase' => 'PhutilTestCase',
|
||||||
'ArcanistImplicitConstructorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
'ArcanistImplicitConstructorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||||
'ArcanistImplicitConstructorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
'ArcanistImplicitConstructorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||||
'ArcanistImplicitFallthroughXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
'ArcanistImplicitFallthroughXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||||
|
@ -1403,6 +1419,7 @@ phutil_register_library_map(array(
|
||||||
'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI',
|
'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI',
|
||||||
'ArcanistMercurialCommitGraphQuery' => 'ArcanistCommitGraphQuery',
|
'ArcanistMercurialCommitGraphQuery' => 'ArcanistCommitGraphQuery',
|
||||||
'ArcanistMercurialCommitMessageHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery',
|
'ArcanistMercurialCommitMessageHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery',
|
||||||
|
'ArcanistMercurialCommitSymbolCommitHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery',
|
||||||
'ArcanistMercurialLandEngine' => 'ArcanistLandEngine',
|
'ArcanistMercurialLandEngine' => 'ArcanistLandEngine',
|
||||||
'ArcanistMercurialLocalState' => 'ArcanistRepositoryLocalState',
|
'ArcanistMercurialLocalState' => 'ArcanistRepositoryLocalState',
|
||||||
'ArcanistMercurialParser' => 'Phobject',
|
'ArcanistMercurialParser' => 'Phobject',
|
||||||
|
@ -1476,6 +1493,7 @@ phutil_register_library_map(array(
|
||||||
'ArcanistPhutilXHPASTLinterStandard' => 'ArcanistLinterStandard',
|
'ArcanistPhutilXHPASTLinterStandard' => 'ArcanistLinterStandard',
|
||||||
'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||||
'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||||
|
'ArcanistProductNameLiteralXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||||
'ArcanistProjectConfigurationSource' => 'ArcanistWorkingCopyConfigurationSource',
|
'ArcanistProjectConfigurationSource' => 'ArcanistWorkingCopyConfigurationSource',
|
||||||
'ArcanistPrompt' => 'Phobject',
|
'ArcanistPrompt' => 'Phobject',
|
||||||
'ArcanistPromptResponse' => 'Phobject',
|
'ArcanistPromptResponse' => 'Phobject',
|
||||||
|
@ -1696,6 +1714,8 @@ phutil_register_library_map(array(
|
||||||
'LinesOfALargeFile' => 'LinesOfALarge',
|
'LinesOfALargeFile' => 'LinesOfALarge',
|
||||||
'LinesOfALargeFileTestCase' => 'PhutilTestCase',
|
'LinesOfALargeFileTestCase' => 'PhutilTestCase',
|
||||||
'MFilterTestHelper' => 'Phobject',
|
'MFilterTestHelper' => 'Phobject',
|
||||||
|
'MethodCallFuture' => 'Future',
|
||||||
|
'MethodCallFutureTestCase' => 'PhutilTestCase',
|
||||||
'NoseTestEngine' => 'ArcanistUnitTestEngine',
|
'NoseTestEngine' => 'ArcanistUnitTestEngine',
|
||||||
'PHPASTParserTestCase' => 'PhutilTestCase',
|
'PHPASTParserTestCase' => 'PhutilTestCase',
|
||||||
'PhageAction' => 'Phobject',
|
'PhageAction' => 'Phobject',
|
||||||
|
@ -1991,6 +2011,7 @@ phutil_register_library_map(array(
|
||||||
'PhutilVeryWowEnglishLocale' => 'PhutilLocale',
|
'PhutilVeryWowEnglishLocale' => 'PhutilLocale',
|
||||||
'PhutilWordPressFuture' => 'FutureProxy',
|
'PhutilWordPressFuture' => 'FutureProxy',
|
||||||
'PhutilXHPASTBinary' => 'Phobject',
|
'PhutilXHPASTBinary' => 'Phobject',
|
||||||
|
'PlatformSymbols' => 'Phobject',
|
||||||
'PytestTestEngine' => 'ArcanistUnitTestEngine',
|
'PytestTestEngine' => 'ArcanistUnitTestEngine',
|
||||||
'TempFile' => 'Phobject',
|
'TempFile' => 'Phobject',
|
||||||
'TestAbstractDirectedGraph' => 'AbstractDirectedGraph',
|
'TestAbstractDirectedGraph' => 'AbstractDirectedGraph',
|
||||||
|
|
|
@ -125,8 +125,8 @@ class PhutilLibraryTestCase extends PhutilTestCase {
|
||||||
$failures[] = pht(
|
$failures[] = pht(
|
||||||
'Class "%s" implements method "%s" with the wrong visibility. '.
|
'Class "%s" implements method "%s" with the wrong visibility. '.
|
||||||
'The method has visibility "%s", but it is defined in parent '.
|
'The method has visibility "%s", but it is defined in parent '.
|
||||||
'"%s" with visibility "%s". In Phabricator, a method which '.
|
'"%s" with visibility "%s". A method which overrides another '.
|
||||||
'overrides another must always have the same visibility.',
|
'must always have the same visibility.',
|
||||||
$class_name,
|
$class_name,
|
||||||
$method_name,
|
$method_name,
|
||||||
$this->getVisibility($method),
|
$this->getVisibility($method),
|
||||||
|
|
|
@ -89,8 +89,8 @@ final class ArcanistConduitEngine
|
||||||
$block = id(new PhutilConsoleBlock())
|
$block = id(new PhutilConsoleBlock())
|
||||||
->addParagraph(
|
->addParagraph(
|
||||||
pht(
|
pht(
|
||||||
'This command needs to communicate with Phabricator, but no '.
|
'This command needs to communicate with a server, but no '.
|
||||||
'Phabricator URI is configured.'))
|
'server URI is configured.'))
|
||||||
->addList($list);
|
->addList($list);
|
||||||
|
|
||||||
throw new ArcanistUsageException($block->drawConsoleString());
|
throw new ArcanistUsageException($block->drawConsoleString());
|
||||||
|
|
|
@ -71,10 +71,10 @@ final class ArcanistArcConfigurationEngineExtension
|
||||||
->setSummary(pht('Repository for the current working copy.'))
|
->setSummary(pht('Repository for the current working copy.'))
|
||||||
->setHelp(
|
->setHelp(
|
||||||
pht(
|
pht(
|
||||||
'Associate the working copy with a specific Phabricator '.
|
'Associate the working copy with a specific repository. Normally, '.
|
||||||
'repository. Normally, Arcanist can figure this association '.
|
'this association can be determined automatically, but if your '.
|
||||||
'out on its own, but if your setup is unusual you can use '.
|
'setup is unusual you can use this option to tell it what the '.
|
||||||
'this option to tell it what the desired value is.'))
|
'desired value is.'))
|
||||||
->setExamples(
|
->setExamples(
|
||||||
array(
|
array(
|
||||||
'libexample',
|
'libexample',
|
||||||
|
@ -89,14 +89,15 @@ final class ArcanistArcConfigurationEngineExtension
|
||||||
'conduit_uri',
|
'conduit_uri',
|
||||||
'default',
|
'default',
|
||||||
))
|
))
|
||||||
->setSummary(pht('Phabricator install to connect to.'))
|
->setSummary(pht('Server to connect to.'))
|
||||||
->setHelp(
|
->setHelp(
|
||||||
pht(
|
pht(
|
||||||
'Associates this working copy with a specific installation of '.
|
'Associates this working copy with a specific installation of '.
|
||||||
'Phabricator.'))
|
'%s (or compatible software).',
|
||||||
|
PlatformSymbols::getPlatformServerName()))
|
||||||
->setExamples(
|
->setExamples(
|
||||||
array(
|
array(
|
||||||
'https://phabricator.mycompany.com/',
|
'https://devtools.example.com/',
|
||||||
)),
|
)),
|
||||||
id(new ArcanistAliasesConfigOption())
|
id(new ArcanistAliasesConfigOption())
|
||||||
->setKey(self::KEY_ALIASES)
|
->setKey(self::KEY_ALIASES)
|
||||||
|
|
|
@ -15,7 +15,7 @@ final class ArcanistBlindlyTrustHTTPEngineExtension
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getExtensionName() {
|
public function getExtensionName() {
|
||||||
return pht('Arcanist HTTPS Trusted Domains');
|
return pht('HTTPS Trusted Domains');
|
||||||
}
|
}
|
||||||
|
|
||||||
public function shouldTrustAnySSLAuthorityForURI(PhutilURI $uri) {
|
public function shouldTrustAnySSLAuthorityForURI(PhutilURI $uri) {
|
||||||
|
|
|
@ -258,7 +258,7 @@ final class ArcanistConfigurationManager extends Phobject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getUserConfigurationFileLocation() {
|
public function getUserConfigurationFileLocation() {
|
||||||
if (strlen($this->customArcrcFilename)) {
|
if ($this->customArcrcFilename !== null) {
|
||||||
return $this->customArcrcFilename;
|
return $this->customArcrcFilename;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,11 +7,11 @@ final class ArcanistSettings extends Phobject {
|
||||||
'default' => array(
|
'default' => array(
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'help' => pht(
|
'help' => pht(
|
||||||
'The URI of a Phabricator install to connect to by default, if '.
|
'The URI of a server to connect to by default, if '.
|
||||||
'%s is run in a project without a Phabricator URI or run outside '.
|
'%s is run in a project without a configured URI or run outside '.
|
||||||
'of a project.',
|
'of a project.',
|
||||||
'arc'),
|
'arc'),
|
||||||
'example' => '"http://phabricator.example.com/"',
|
'example' => '"http://devtools.example.com/"',
|
||||||
),
|
),
|
||||||
'base' => array(
|
'base' => array(
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
|
@ -35,7 +35,7 @@ final class ArcanistSettings extends Phobject {
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'example' => '"X"',
|
'example' => '"X"',
|
||||||
'help' => pht(
|
'help' => pht(
|
||||||
'Associate the working copy with a specific Phabricator repository. '.
|
'Associate the working copy with a specific repository. '.
|
||||||
'Normally, %s can figure this association out on its own, but if '.
|
'Normally, %s can figure this association out on its own, but if '.
|
||||||
'your setup is unusual you can use this option to tell it what the '.
|
'your setup is unusual you can use this option to tell it what the '.
|
||||||
'desired value is.',
|
'desired value is.',
|
||||||
|
@ -44,10 +44,9 @@ final class ArcanistSettings extends Phobject {
|
||||||
'phabricator.uri' => array(
|
'phabricator.uri' => array(
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'legacy' => 'conduit_uri',
|
'legacy' => 'conduit_uri',
|
||||||
'example' => '"https://phabricator.mycompany.com/"',
|
'example' => '"https://devtools.example.com/"',
|
||||||
'help' => pht(
|
'help' => pht(
|
||||||
'Associates this working copy with a specific installation of '.
|
'Associates this working copy with a specific server.'),
|
||||||
'Phabricator.'),
|
|
||||||
),
|
),
|
||||||
'lint.engine' => array(
|
'lint.engine' => array(
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
|
@ -96,8 +95,8 @@ final class ArcanistSettings extends Phobject {
|
||||||
'https.cabundle' => array(
|
'https.cabundle' => array(
|
||||||
'type' => 'string',
|
'type' => 'string',
|
||||||
'help' => pht(
|
'help' => pht(
|
||||||
"Path to a custom CA bundle file to be used for arcanist's cURL ".
|
"Path to a custom CA bundle file to be used for cURL calls. ".
|
||||||
"calls. This is used primarily when your conduit endpoint is ".
|
"This is used primarily when your conduit endpoint is ".
|
||||||
"behind HTTPS signed by your organization's internal CA."),
|
"behind HTTPS signed by your organization's internal CA."),
|
||||||
'example' => 'support/yourca.pem',
|
'example' => 'support/yourca.pem',
|
||||||
),
|
),
|
||||||
|
@ -118,7 +117,7 @@ final class ArcanistSettings extends Phobject {
|
||||||
'Whether %s should permit the automatic stashing of changes in the '.
|
'Whether %s should permit the automatic stashing of changes in the '.
|
||||||
'working directory when requiring a clean working copy. This option '.
|
'working directory when requiring a clean working copy. This option '.
|
||||||
'should only be used when users understand how to restore their '.
|
'should only be used when users understand how to restore their '.
|
||||||
'working directory from the local stash if an Arcanist operation '.
|
'working directory from the local stash if an operation '.
|
||||||
'causes an unrecoverable error.',
|
'causes an unrecoverable error.',
|
||||||
'arc'),
|
'arc'),
|
||||||
'default' => false,
|
'default' => false,
|
||||||
|
|
|
@ -22,23 +22,38 @@ final class PhutilConsoleFormatter extends Phobject {
|
||||||
|
|
||||||
public static function getDisableANSI() {
|
public static function getDisableANSI() {
|
||||||
if (self::$disableANSI === null) {
|
if (self::$disableANSI === null) {
|
||||||
|
self::$disableANSI = self::newShouldDisableAnsi();
|
||||||
|
}
|
||||||
|
return self::$disableANSI;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static function newShouldDisableANSI() {
|
||||||
$term = phutil_utf8_strtolower(getenv('TERM'));
|
$term = phutil_utf8_strtolower(getenv('TERM'));
|
||||||
|
|
||||||
// ansicon enables ANSI support on Windows
|
// ansicon enables ANSI support on Windows
|
||||||
if (!$term && getenv('ANSICON')) {
|
if (!$term && getenv('ANSICON')) {
|
||||||
$term = 'ansi';
|
$term = 'ansi';
|
||||||
}
|
}
|
||||||
|
|
||||||
if (phutil_is_windows() && $term !== 'cygwin' && $term !== 'ansi') {
|
|
||||||
self::$disableANSI = true;
|
if (phutil_is_windows()) {
|
||||||
} else if (!defined('STDOUT')) {
|
if ($term !== 'cygwin' && $term !== 'ansi') {
|
||||||
self::$disableANSI = true;
|
return true;
|
||||||
} else if (function_exists('posix_isatty') && !posix_isatty(STDOUT)) {
|
|
||||||
self::$disableANSI = true;
|
|
||||||
} else {
|
|
||||||
self::$disableANSI = false;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return self::$disableANSI;
|
|
||||||
|
$stdout = PhutilSystem::getStdoutHandle();
|
||||||
|
if ($stdout === null) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (function_exists('posix_isatty')) {
|
||||||
|
if (!posix_isatty($stdout)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function formatString($format /* ... */) {
|
public static function formatString($format /* ... */) {
|
||||||
|
|
|
@ -22,6 +22,7 @@ final class PhutilInteractiveEditor extends Phobject {
|
||||||
private $offset = 0;
|
private $offset = 0;
|
||||||
private $preferred;
|
private $preferred;
|
||||||
private $fallback;
|
private $fallback;
|
||||||
|
private $taskMessage;
|
||||||
|
|
||||||
|
|
||||||
/* -( Creating a New Editor )---------------------------------------------- */
|
/* -( Creating a New Editor )---------------------------------------------- */
|
||||||
|
@ -74,6 +75,20 @@ final class PhutilInteractiveEditor extends Phobject {
|
||||||
$editor = $this->getEditor();
|
$editor = $this->getEditor();
|
||||||
$offset = $this->getLineOffset();
|
$offset = $this->getLineOffset();
|
||||||
|
|
||||||
|
$binary = basename($editor);
|
||||||
|
|
||||||
|
// This message is primarily an assistance to users with GUI-based
|
||||||
|
// editors configured. Users with terminal-based editors won't have a
|
||||||
|
// chance to see this prior to the editor being launched.
|
||||||
|
echo tsprintf(
|
||||||
|
"%s\n",
|
||||||
|
pht('Launching editor "%s"...', $binary));
|
||||||
|
|
||||||
|
$task_message = $this->getTaskMessage();
|
||||||
|
if ($task_message !== null) {
|
||||||
|
echo tsprintf("%s\n", $task_message);
|
||||||
|
}
|
||||||
|
|
||||||
$err = $this->invokeEditor($editor, $path, $offset);
|
$err = $this->invokeEditor($editor, $path, $offset);
|
||||||
|
|
||||||
if ($err) {
|
if ($err) {
|
||||||
|
@ -85,7 +100,6 @@ final class PhutilInteractiveEditor extends Phobject {
|
||||||
'vim' => true,
|
'vim' => true,
|
||||||
);
|
);
|
||||||
|
|
||||||
$binary = basename($editor);
|
|
||||||
if (isset($vi_binaries[$binary])) {
|
if (isset($vi_binaries[$binary])) {
|
||||||
// This runs "Q" (an invalid command), then "q" (a valid command,
|
// This runs "Q" (an invalid command), then "q" (a valid command,
|
||||||
// meaning "quit"). Vim binaries with behavior that makes them poor
|
// meaning "quit"). Vim binaries with behavior that makes them poor
|
||||||
|
@ -266,6 +280,33 @@ final class PhutilInteractiveEditor extends Phobject {
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set the message that identifies the task for which the editor is being
|
||||||
|
* launched, displayed to the user prior to it being launched.
|
||||||
|
*
|
||||||
|
* @param string The message to display to the user.
|
||||||
|
* @return $this
|
||||||
|
*
|
||||||
|
* @task config
|
||||||
|
*/
|
||||||
|
public function setTaskMessage($task_message) {
|
||||||
|
$this->taskMessage = $task_message;
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve the current message that will display to the user just prior to
|
||||||
|
* invoking the editor.
|
||||||
|
*
|
||||||
|
* @return string The message that will display to the user, or null if no
|
||||||
|
* message will be displayed.
|
||||||
|
*
|
||||||
|
* @task config
|
||||||
|
*/
|
||||||
|
public function getTaskMessage() {
|
||||||
|
return $this->taskMessage;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the name of the editor program to use. The value of the environmental
|
* Get the name of the editor program to use. The value of the environmental
|
||||||
|
|
|
@ -139,10 +139,10 @@ final class ArcanistDifferentialCommitMessage extends Phobject {
|
||||||
throw new ArcanistUsageException(
|
throw new ArcanistUsageException(
|
||||||
pht(
|
pht(
|
||||||
'Invalid "Differential Revision" field in commit message. This field '.
|
'Invalid "Differential Revision" field in commit message. This field '.
|
||||||
'should have a revision identifier like "%s" or a Phabricator URI '.
|
'should have a revision identifier like "%s" or a server URI '.
|
||||||
'like "%s", but has "%s".',
|
'like "%s", but has "%s".',
|
||||||
'D123',
|
'D123',
|
||||||
'https://phabricator.example.com/D123',
|
'https://devtools.example.com/D123',
|
||||||
$revision_value));
|
$revision_value));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -181,7 +181,6 @@ final class PhutilErrorHandler extends Phobject {
|
||||||
* @task internal
|
* @task internal
|
||||||
*/
|
*/
|
||||||
public static function handleError($num, $str, $file, $line, $ctx = null) {
|
public static function handleError($num, $str, $file, $line, $ctx = null) {
|
||||||
|
|
||||||
foreach (self::$traps as $trap) {
|
foreach (self::$traps as $trap) {
|
||||||
$trap->addError($num, $str, $file, $line);
|
$trap->addError($num, $str, $file, $line);
|
||||||
}
|
}
|
||||||
|
@ -378,7 +377,7 @@ final class PhutilErrorHandler extends Phobject {
|
||||||
* @task internal
|
* @task internal
|
||||||
*/
|
*/
|
||||||
public static function dispatchErrorMessage($event, $value, $metadata) {
|
public static function dispatchErrorMessage($event, $value, $metadata) {
|
||||||
$timestamp = strftime('%Y-%m-%d %H:%M:%S');
|
$timestamp = date('Y-m-d H:i:s');
|
||||||
|
|
||||||
switch ($event) {
|
switch ($event) {
|
||||||
case self::ERROR:
|
case self::ERROR:
|
||||||
|
@ -425,16 +424,6 @@ final class PhutilErrorHandler extends Phobject {
|
||||||
$metadata['file'],
|
$metadata['file'],
|
||||||
$metadata['line']);
|
$metadata['line']);
|
||||||
|
|
||||||
$metadata['default_message'] = $default_message;
|
|
||||||
error_log($default_message);
|
|
||||||
break;
|
|
||||||
case self::DEPRECATED:
|
|
||||||
$default_message = sprintf(
|
|
||||||
'[%s] DEPRECATED: %s is deprecated; %s',
|
|
||||||
$timestamp,
|
|
||||||
$value,
|
|
||||||
$metadata['why']);
|
|
||||||
|
|
||||||
$metadata['default_message'] = $default_message;
|
$metadata['default_message'] = $default_message;
|
||||||
error_log($default_message);
|
error_log($default_message);
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -28,6 +28,8 @@ final class PhutilDirectoryFixture extends Phobject {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getPath($to_file = null) {
|
public function getPath($to_file = null) {
|
||||||
|
$to_file = phutil_string_cast($to_file);
|
||||||
|
|
||||||
return $this->path.'/'.ltrim($to_file, '/');
|
return $this->path.'/'.ltrim($to_file, '/');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,7 @@ final class PhutilErrorLog
|
||||||
|
|
||||||
if (strlen($message)) {
|
if (strlen($message)) {
|
||||||
$message = tsprintf("%B\n", $message);
|
$message = tsprintf("%B\n", $message);
|
||||||
@fwrite(STDERR, $message);
|
PhutilSystem::writeStderr($message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -7,6 +7,8 @@ final class PhutilMercurialBinaryAnalyzer
|
||||||
|
|
||||||
const CAPABILITY_FILES = 'files';
|
const CAPABILITY_FILES = 'files';
|
||||||
const CAPABILITY_INJECTION = 'injection';
|
const CAPABILITY_INJECTION = 'injection';
|
||||||
|
const CAPABILITY_TEMPLATE_PNODE = 'template_pnode';
|
||||||
|
const CAPABILTIY_ANNOTATE_TEMPLATES = 'annotate_templates';
|
||||||
|
|
||||||
protected function newBinaryVersion() {
|
protected function newBinaryVersion() {
|
||||||
$future = id(new ExecFuture('hg --version --quiet'))
|
$future = id(new ExecFuture('hg --version --quiet'))
|
||||||
|
@ -60,6 +62,33 @@ final class PhutilMercurialBinaryAnalyzer
|
||||||
self::CAPABILITY_INJECTION);
|
self::CAPABILITY_INJECTION);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* When using `--template` the format for accessing individual parents
|
||||||
|
* changed from `{p1node}` to `{p1.node}` in Mercurial 4.9.
|
||||||
|
*
|
||||||
|
* @return boolean True if the version of Mercurial is new enough to support
|
||||||
|
* the `{p1.node}` format in templates, or false if otherwise.
|
||||||
|
*/
|
||||||
|
public function isMercurialTemplatePnodeAvailable() {
|
||||||
|
return self::versionHasCapability(
|
||||||
|
$this->requireBinaryVersion(),
|
||||||
|
self::CAPABILITY_TEMPLATE_PNODE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The `hg annotate` command did not accept the `--template` argument until
|
||||||
|
* version 4.6. It appears to function in version 4.5 however it's not
|
||||||
|
* documented and wasn't announced until the 4.6 release.
|
||||||
|
*
|
||||||
|
* @return boolean True if the version of Mercurial is new enough to support
|
||||||
|
* the `--template` option when using `hg annotate`, or false if otherwise.
|
||||||
|
*/
|
||||||
|
public function isMercurialAnnotateTemplatesAvailable() {
|
||||||
|
return self::versionHasCapability(
|
||||||
|
$this->requireBinaryVersion(),
|
||||||
|
self::CAPABILTIY_ANNOTATE_TEMPLATES);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
public static function versionHasCapability(
|
public static function versionHasCapability(
|
||||||
$mercurial_version,
|
$mercurial_version,
|
||||||
|
@ -70,6 +99,10 @@ final class PhutilMercurialBinaryAnalyzer
|
||||||
return version_compare($mercurial_version, '3.2', '>=');
|
return version_compare($mercurial_version, '3.2', '>=');
|
||||||
case self::CAPABILITY_INJECTION:
|
case self::CAPABILITY_INJECTION:
|
||||||
return version_compare($mercurial_version, '3.2.4', '<');
|
return version_compare($mercurial_version, '3.2.4', '<');
|
||||||
|
case self::CAPABILITY_TEMPLATE_PNODE:
|
||||||
|
return version_compare($mercurial_version, '4.9', '>=');
|
||||||
|
case self::CAPABILTIY_ANNOTATE_TEMPLATES:
|
||||||
|
return version_compare($mercurial_version, '4.6', '>=');
|
||||||
default:
|
default:
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
|
|
|
@ -212,7 +212,7 @@ abstract class LinesOfALarge extends Phobject implements Iterator {
|
||||||
if (strlen($this->buf)) {
|
if (strlen($this->buf)) {
|
||||||
$this->num++;
|
$this->num++;
|
||||||
$this->line = $this->buf;
|
$this->line = $this->buf;
|
||||||
$this->buf = null;
|
$this->buf = '';
|
||||||
} else {
|
} else {
|
||||||
$this->valid = false;
|
$this->valid = false;
|
||||||
}
|
}
|
||||||
|
|
156
src/filesystem/memory/ArcanistHostMemorySnapshot.php
Normal file
156
src/filesystem/memory/ArcanistHostMemorySnapshot.php
Normal file
|
@ -0,0 +1,156 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class ArcanistHostMemorySnapshot
|
||||||
|
extends Phobject {
|
||||||
|
|
||||||
|
private $memorySnapshot;
|
||||||
|
|
||||||
|
public static function newFromRawMeminfo($meminfo_source, $meminfo_raw) {
|
||||||
|
$snapshot = new self();
|
||||||
|
|
||||||
|
$snapshot->memorySnapshot = $snapshot->readMeminfoSnapshot(
|
||||||
|
$meminfo_source,
|
||||||
|
$meminfo_raw);
|
||||||
|
|
||||||
|
return $snapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getTotalSwapBytes() {
|
||||||
|
$info = $this->getMemorySnapshot();
|
||||||
|
return $info['swap.total'];
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getMemorySnapshot() {
|
||||||
|
if ($this->memorySnapshot === null) {
|
||||||
|
$this->memorySnapshot = $this->newMemorySnapshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $this->memorySnapshot;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function newMemorySnapshot() {
|
||||||
|
$meminfo_source = '/proc/meminfo';
|
||||||
|
list($meminfo_raw) = execx('cat %s', $meminfo_source);
|
||||||
|
return $this->readMeminfoSnapshot($meminfo_source, $meminfo_raw);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readMeminfoSnapshot($meminfo_source, $meminfo_raw) {
|
||||||
|
$meminfo_pattern = '/^([^:]+):\s+(\S+)(?:\s+(kB))?\z/';
|
||||||
|
|
||||||
|
$meminfo_map = array();
|
||||||
|
|
||||||
|
$meminfo_lines = phutil_split_lines($meminfo_raw, false);
|
||||||
|
foreach ($meminfo_lines as $meminfo_line) {
|
||||||
|
$meminfo_parts = phutil_preg_match($meminfo_pattern, $meminfo_line);
|
||||||
|
|
||||||
|
if (!$meminfo_parts) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Unable to parse line in meminfo source "%s": "%s".',
|
||||||
|
$meminfo_source,
|
||||||
|
$meminfo_line));
|
||||||
|
}
|
||||||
|
|
||||||
|
$meminfo_key = $meminfo_parts[1];
|
||||||
|
$meminfo_value = $meminfo_parts[2];
|
||||||
|
$meminfo_unit = idx($meminfo_parts, 3);
|
||||||
|
|
||||||
|
if (isset($meminfo_map[$meminfo_key])) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Encountered duplicate meminfo key "%s" in meminfo source "%s".',
|
||||||
|
$meminfo_key,
|
||||||
|
$meminfo_source));
|
||||||
|
}
|
||||||
|
|
||||||
|
$meminfo_map[$meminfo_key] = array(
|
||||||
|
'value' => $meminfo_value,
|
||||||
|
'unit' => $meminfo_unit,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$swap_total_bytes = $this->readMeminfoBytes(
|
||||||
|
$meminfo_source,
|
||||||
|
$meminfo_map,
|
||||||
|
'SwapTotal');
|
||||||
|
|
||||||
|
return array(
|
||||||
|
'swap.total' => $swap_total_bytes,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readMeminfoBytes(
|
||||||
|
$meminfo_source,
|
||||||
|
$meminfo_map,
|
||||||
|
$meminfo_key) {
|
||||||
|
|
||||||
|
$meminfo_integer = $this->readMeminfoIntegerValue(
|
||||||
|
$meminfo_source,
|
||||||
|
$meminfo_map,
|
||||||
|
$meminfo_key);
|
||||||
|
|
||||||
|
$meminfo_unit = $meminfo_map[$meminfo_key]['unit'];
|
||||||
|
if ($meminfo_unit === null) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Expected to find a byte unit for meminfo key "%s" in meminfo '.
|
||||||
|
'source "%s", found no unit.',
|
||||||
|
$meminfo_key,
|
||||||
|
$meminfo_source));
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($meminfo_unit !== 'kB') {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Expected unit for meminfo key "%s" in meminfo source "%s" '.
|
||||||
|
'to be "kB", found "%s".',
|
||||||
|
$meminfo_key,
|
||||||
|
$meminfo_source,
|
||||||
|
$meminfo_unit));
|
||||||
|
}
|
||||||
|
|
||||||
|
$meminfo_bytes = ($meminfo_integer * 1024);
|
||||||
|
|
||||||
|
return $meminfo_bytes;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readMeminfoIntegerValue(
|
||||||
|
$meminfo_source,
|
||||||
|
$meminfo_map,
|
||||||
|
$meminfo_key) {
|
||||||
|
|
||||||
|
$meminfo_value = $this->readMeminfoValue(
|
||||||
|
$meminfo_source,
|
||||||
|
$meminfo_map,
|
||||||
|
$meminfo_key);
|
||||||
|
|
||||||
|
if (!phutil_preg_match('/^\d+\z/', $meminfo_value)) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Expected to find an integer value for meminfo key "%s" in '.
|
||||||
|
'meminfo source "%s", found "%s".',
|
||||||
|
$meminfo_key,
|
||||||
|
$meminfo_source,
|
||||||
|
$meminfo_value));
|
||||||
|
}
|
||||||
|
|
||||||
|
return (int)$meminfo_value;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function readMeminfoValue(
|
||||||
|
$meminfo_source,
|
||||||
|
$meminfo_map,
|
||||||
|
$meminfo_key) {
|
||||||
|
|
||||||
|
if (!isset($meminfo_map[$meminfo_key])) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Expected to find meminfo key "%s" in meminfo source "%s".',
|
||||||
|
$meminfo_key,
|
||||||
|
$meminfo_source));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $meminfo_map[$meminfo_key]['value'];
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,51 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class ArcanistHostMemorySnapshotTestCase
|
||||||
|
extends PhutilTestCase {
|
||||||
|
|
||||||
|
public function testSnapshotSwapTotalBytes() {
|
||||||
|
$test_cases = array(
|
||||||
|
'meminfo_swap_normal.txt' => 4294963200,
|
||||||
|
'meminfo_swap_zero.txt' => 0,
|
||||||
|
'meminfo_swap_missing.txt' => false,
|
||||||
|
'meminfo_swap_invalid.txt' => false,
|
||||||
|
'meminfo_swap_badunits.txt' => false,
|
||||||
|
'meminfo_swap_duplicate.txt' => false,
|
||||||
|
);
|
||||||
|
|
||||||
|
$test_dir = dirname(__FILE__).'/data/';
|
||||||
|
|
||||||
|
foreach ($test_cases as $test_file => $expect) {
|
||||||
|
$test_data = Filesystem::readFile($test_dir.$test_file);
|
||||||
|
|
||||||
|
$caught = null;
|
||||||
|
$actual = null;
|
||||||
|
try {
|
||||||
|
$snapshot = ArcanistHostMemorySnapshot::newFromRawMeminfo(
|
||||||
|
$test_file,
|
||||||
|
$test_data);
|
||||||
|
$actual = $snapshot->getTotalSwapBytes();
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
if ($expect === false) {
|
||||||
|
$caught = $ex;
|
||||||
|
} else {
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
} catch (Throwable $ex) {
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($expect === false) {
|
||||||
|
$this->assertTrue(
|
||||||
|
($caught instanceof Exception),
|
||||||
|
pht('Expected exception for "%s".', $test_file));
|
||||||
|
} else {
|
||||||
|
$this->assertEqual(
|
||||||
|
$expect,
|
||||||
|
$actual,
|
||||||
|
pht('Result for "%s".', $test_file));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,2 @@
|
||||||
|
MemTotal: 8140924 kB
|
||||||
|
SwapTotal: 4194 mB
|
|
@ -0,0 +1,3 @@
|
||||||
|
MemTotal: 8140924 kB
|
||||||
|
SwapTotal: 4194300 kB
|
||||||
|
SwapTotal: 4194300 kB
|
|
@ -0,0 +1,2 @@
|
||||||
|
MemTotal: 8140924 kB
|
||||||
|
SwapTotal: aardvark kB
|
|
@ -0,0 +1 @@
|
||||||
|
MemTotal: 8140924 kB
|
51
src/filesystem/memory/__tests__/data/meminfo_swap_normal.txt
Normal file
51
src/filesystem/memory/__tests__/data/meminfo_swap_normal.txt
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
MemTotal: 8140924 kB
|
||||||
|
MemFree: 1760456 kB
|
||||||
|
MemAvailable: 4264888 kB
|
||||||
|
Buffers: 36784 kB
|
||||||
|
Cached: 2654788 kB
|
||||||
|
SwapCached: 2132 kB
|
||||||
|
Active: 331128 kB
|
||||||
|
Inactive: 5677400 kB
|
||||||
|
Active(anon): 22692 kB
|
||||||
|
Inactive(anon): 3325560 kB
|
||||||
|
Active(file): 308436 kB
|
||||||
|
Inactive(file): 2351840 kB
|
||||||
|
Unevictable: 23012 kB
|
||||||
|
Mlocked: 18476 kB
|
||||||
|
SwapTotal: 4194300 kB
|
||||||
|
SwapFree: 2440444 kB
|
||||||
|
Dirty: 44 kB
|
||||||
|
Writeback: 0 kB
|
||||||
|
AnonPages: 3337944 kB
|
||||||
|
Mapped: 117424 kB
|
||||||
|
Shmem: 19872 kB
|
||||||
|
KReclaimable: 124728 kB
|
||||||
|
Slab: 240640 kB
|
||||||
|
SReclaimable: 124728 kB
|
||||||
|
SUnreclaim: 115912 kB
|
||||||
|
KernelStack: 6736 kB
|
||||||
|
PageTables: 42044 kB
|
||||||
|
NFS_Unstable: 0 kB
|
||||||
|
Bounce: 0 kB
|
||||||
|
WritebackTmp: 0 kB
|
||||||
|
CommitLimit: 8264760 kB
|
||||||
|
Committed_AS: 6994880 kB
|
||||||
|
VmallocTotal: 34359738367 kB
|
||||||
|
VmallocUsed: 16656 kB
|
||||||
|
VmallocChunk: 0 kB
|
||||||
|
Percpu: 9856 kB
|
||||||
|
HardwareCorrupted: 0 kB
|
||||||
|
AnonHugePages: 0 kB
|
||||||
|
ShmemHugePages: 0 kB
|
||||||
|
ShmemPmdMapped: 0 kB
|
||||||
|
FileHugePages: 0 kB
|
||||||
|
FilePmdMapped: 0 kB
|
||||||
|
HugePages_Total: 0
|
||||||
|
HugePages_Free: 0
|
||||||
|
HugePages_Rsvd: 0
|
||||||
|
HugePages_Surp: 0
|
||||||
|
Hugepagesize: 2048 kB
|
||||||
|
Hugetlb: 0 kB
|
||||||
|
DirectMap4k: 270336 kB
|
||||||
|
DirectMap2M: 8118272 kB
|
||||||
|
DirectMap1G: 1048576 kB
|
51
src/filesystem/memory/__tests__/data/meminfo_swap_zero.txt
Normal file
51
src/filesystem/memory/__tests__/data/meminfo_swap_zero.txt
Normal file
|
@ -0,0 +1,51 @@
|
||||||
|
MemTotal: 8140924 kB
|
||||||
|
MemFree: 1760456 kB
|
||||||
|
MemAvailable: 4264888 kB
|
||||||
|
Buffers: 36784 kB
|
||||||
|
Cached: 2654788 kB
|
||||||
|
SwapCached: 2132 kB
|
||||||
|
Active: 331128 kB
|
||||||
|
Inactive: 5677400 kB
|
||||||
|
Active(anon): 22692 kB
|
||||||
|
Inactive(anon): 3325560 kB
|
||||||
|
Active(file): 308436 kB
|
||||||
|
Inactive(file): 2351840 kB
|
||||||
|
Unevictable: 23012 kB
|
||||||
|
Mlocked: 18476 kB
|
||||||
|
SwapTotal: 0 kB
|
||||||
|
SwapFree: 2440444 kB
|
||||||
|
Dirty: 44 kB
|
||||||
|
Writeback: 0 kB
|
||||||
|
AnonPages: 3337944 kB
|
||||||
|
Mapped: 117424 kB
|
||||||
|
Shmem: 19872 kB
|
||||||
|
KReclaimable: 124728 kB
|
||||||
|
Slab: 240640 kB
|
||||||
|
SReclaimable: 124728 kB
|
||||||
|
SUnreclaim: 115912 kB
|
||||||
|
KernelStack: 6736 kB
|
||||||
|
PageTables: 42044 kB
|
||||||
|
NFS_Unstable: 0 kB
|
||||||
|
Bounce: 0 kB
|
||||||
|
WritebackTmp: 0 kB
|
||||||
|
CommitLimit: 8264760 kB
|
||||||
|
Committed_AS: 6994880 kB
|
||||||
|
VmallocTotal: 34359738367 kB
|
||||||
|
VmallocUsed: 16656 kB
|
||||||
|
VmallocChunk: 0 kB
|
||||||
|
Percpu: 9856 kB
|
||||||
|
HardwareCorrupted: 0 kB
|
||||||
|
AnonHugePages: 0 kB
|
||||||
|
ShmemHugePages: 0 kB
|
||||||
|
ShmemPmdMapped: 0 kB
|
||||||
|
FileHugePages: 0 kB
|
||||||
|
FilePmdMapped: 0 kB
|
||||||
|
HugePages_Total: 0
|
||||||
|
HugePages_Free: 0
|
||||||
|
HugePages_Rsvd: 0
|
||||||
|
HugePages_Surp: 0
|
||||||
|
Hugepagesize: 2048 kB
|
||||||
|
Hugetlb: 0 kB
|
||||||
|
DirectMap4k: 270336 kB
|
||||||
|
DirectMap2M: 8118272 kB
|
||||||
|
DirectMap1G: 1048576 kB
|
36
src/future/MethodCallFuture.php
Normal file
36
src/future/MethodCallFuture.php
Normal file
|
@ -0,0 +1,36 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Degenerate future which resolves by calling a method.
|
||||||
|
*
|
||||||
|
* $future = new MethodCallFuture($calculator, 'add', 1, 2);
|
||||||
|
*
|
||||||
|
* This future is similar to @{class:ImmediateFuture}, but may make it easier
|
||||||
|
* to implement exception behavior correctly. See T13666.
|
||||||
|
*/
|
||||||
|
final class MethodCallFuture extends Future {
|
||||||
|
|
||||||
|
private $callObject;
|
||||||
|
private $callMethod;
|
||||||
|
private $callArgv;
|
||||||
|
|
||||||
|
public function __construct($object, $method /* , ...*/ ) {
|
||||||
|
$argv = func_get_args();
|
||||||
|
|
||||||
|
$this->callObject = $object;
|
||||||
|
$this->callMethod = $method;
|
||||||
|
$this->callArgv = array_slice($argv, 2);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isReady() {
|
||||||
|
|
||||||
|
$call = array($this->callObject, $this->callMethod);
|
||||||
|
$argv = $this->callArgv;
|
||||||
|
|
||||||
|
$result = call_user_func_array($call, $argv);
|
||||||
|
$this->setResult($result);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
57
src/future/__tests__/MethodCallFutureTestCase.php
Normal file
57
src/future/__tests__/MethodCallFutureTestCase.php
Normal file
|
@ -0,0 +1,57 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class MethodCallFutureTestCase extends PhutilTestCase {
|
||||||
|
|
||||||
|
public function testMethodCallFutureSums() {
|
||||||
|
$future = new MethodCallFuture($this, 'getSum', 1, 2, 3);
|
||||||
|
$result = $future->resolve();
|
||||||
|
|
||||||
|
$this->assertEqual(6, $result, pht('MethodCallFuture: getSum(1, 2, 3)'));
|
||||||
|
|
||||||
|
$future = new MethodCallFuture($this, 'getSum');
|
||||||
|
$result = $future->resolve();
|
||||||
|
|
||||||
|
$this->assertEqual(0, $result, pht('MethodCallFuture: getSum()'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function testMethodCallFutureExceptions() {
|
||||||
|
$future = new MethodCallFuture($this, 'raiseException');
|
||||||
|
|
||||||
|
// See T13666. Using "FutureIterator" to advance the future until it is
|
||||||
|
// ready to resolve should NOT throw an exception.
|
||||||
|
|
||||||
|
foreach (new FutureIterator(array($future)) as $resolvable) {
|
||||||
|
// Continue below...
|
||||||
|
}
|
||||||
|
|
||||||
|
$caught = null;
|
||||||
|
try {
|
||||||
|
$future->resolve();
|
||||||
|
} catch (PhutilMethodNotImplementedException $ex) {
|
||||||
|
$caught = $ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertTrue(
|
||||||
|
($caught instanceof PhutilMethodNotImplementedException),
|
||||||
|
pht('MethodCallFuture: exceptions raise at resolution.'));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getSum(/* ... */) {
|
||||||
|
$args = func_get_args();
|
||||||
|
|
||||||
|
$sum = 0;
|
||||||
|
foreach ($args as $arg) {
|
||||||
|
$sum += $arg;
|
||||||
|
}
|
||||||
|
|
||||||
|
return $sum;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function raiseException() {
|
||||||
|
// We just want to throw any narrow exception so the test isn't catching
|
||||||
|
// too broad an exception type. This is simulating some exception during
|
||||||
|
// future resolution.
|
||||||
|
throw new PhutilMethodNotImplementedException();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -49,4 +49,24 @@ final class PhutilAWSException extends Exception {
|
||||||
return $this->httpStatus;
|
return $this->httpStatus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function isNotFoundError() {
|
||||||
|
if ($this->hasErrorCode('InvalidVolume.NotFound')) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function hasErrorCode($code) {
|
||||||
|
$errors = idx($this->params, 'Errors', array());
|
||||||
|
|
||||||
|
foreach ($errors as $error) {
|
||||||
|
if ($error[0] === $code) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -142,6 +142,7 @@ abstract class PhutilAWSFuture extends FutureProxy {
|
||||||
try {
|
try {
|
||||||
$xml = @(new SimpleXMLElement($body));
|
$xml = @(new SimpleXMLElement($body));
|
||||||
} catch (Exception $ex) {
|
} catch (Exception $ex) {
|
||||||
|
phlog($ex);
|
||||||
$xml = null;
|
$xml = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,9 +156,28 @@ abstract class PhutilAWSFuture extends FutureProxy {
|
||||||
);
|
);
|
||||||
if ($xml) {
|
if ($xml) {
|
||||||
$params['RequestID'] = $xml->RequestID[0];
|
$params['RequestID'] = $xml->RequestID[0];
|
||||||
$errors = array($xml->Error);
|
|
||||||
foreach ($errors as $error) {
|
// NOTE: The S3 and EC2 APIs return slightly different error responses.
|
||||||
$params['Errors'][] = array($error->Code, $error->Message);
|
|
||||||
|
// In S3 responses, there's a simple top-level "<Error>" element.
|
||||||
|
$s3_error = $xml->Error;
|
||||||
|
if ($s3_error) {
|
||||||
|
$params['Errors'][] = array(
|
||||||
|
phutil_string_cast($s3_error->Code),
|
||||||
|
phutil_string_cast($s3_error->Message),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In EC2 responses, there's an "<Errors>" element with "<Error>"
|
||||||
|
// children.
|
||||||
|
$ec2_errors = $xml->Errors[0];
|
||||||
|
if ($ec2_errors) {
|
||||||
|
foreach ($ec2_errors as $error) {
|
||||||
|
$params['Errors'][] = array(
|
||||||
|
phutil_string_cast($error->Code),
|
||||||
|
phutil_string_cast($error->Message),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -192,14 +192,21 @@ final class ExecFuture extends PhutilExecutableFuture {
|
||||||
* @task interact
|
* @task interact
|
||||||
*/
|
*/
|
||||||
public function read() {
|
public function read() {
|
||||||
$stdout = $this->readStdout();
|
$stdout_value = $this->readStdout();
|
||||||
|
|
||||||
|
$stderr = $this->stderr;
|
||||||
|
if ($stderr === null) {
|
||||||
|
$stderr_value = '';
|
||||||
|
} else {
|
||||||
|
$stderr_value = substr($stderr, $this->stderrPos);
|
||||||
|
}
|
||||||
|
|
||||||
$result = array(
|
$result = array(
|
||||||
$stdout,
|
$stdout_value,
|
||||||
(string)substr($this->stderr, $this->stderrPos),
|
$stderr_value,
|
||||||
);
|
);
|
||||||
|
|
||||||
$this->stderrPos = strlen($this->stderr);
|
$this->stderrPos = $this->getStderrBufferLength();
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
@ -209,8 +216,16 @@ final class ExecFuture extends PhutilExecutableFuture {
|
||||||
$this->updateFuture(); // Sync
|
$this->updateFuture(); // Sync
|
||||||
}
|
}
|
||||||
|
|
||||||
$result = (string)substr($this->stdout, $this->stdoutPos);
|
$stdout = $this->stdout;
|
||||||
$this->stdoutPos = strlen($this->stdout);
|
|
||||||
|
if ($stdout === null) {
|
||||||
|
$result = '';
|
||||||
|
} else {
|
||||||
|
$result = substr($stdout, $this->stdoutPos);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->stdoutPos = $this->getStdoutBufferLength();
|
||||||
|
|
||||||
return $result;
|
return $result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -475,7 +490,7 @@ final class ExecFuture extends PhutilExecutableFuture {
|
||||||
* @task internal
|
* @task internal
|
||||||
*/
|
*/
|
||||||
public function isReadBufferEmpty() {
|
public function isReadBufferEmpty() {
|
||||||
return !strlen($this->stdout);
|
return !$this->getStdoutBufferLength();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -757,14 +772,17 @@ final class ExecFuture extends PhutilExecutableFuture {
|
||||||
$max_stdout_read_bytes = PHP_INT_MAX;
|
$max_stdout_read_bytes = PHP_INT_MAX;
|
||||||
$max_stderr_read_bytes = PHP_INT_MAX;
|
$max_stderr_read_bytes = PHP_INT_MAX;
|
||||||
if ($read_buffer_size !== null) {
|
if ($read_buffer_size !== null) {
|
||||||
$max_stdout_read_bytes = $read_buffer_size - strlen($this->stdout);
|
$stdout_len = $this->getStdoutBufferLength();
|
||||||
$max_stderr_read_bytes = $read_buffer_size - strlen($this->stderr);
|
$stderr_len = $this->getStderrBufferLength();
|
||||||
|
|
||||||
|
$max_stdout_read_bytes = $read_buffer_size - $stdout_len;
|
||||||
|
$max_stderr_read_bytes = $read_buffer_size - $stderr_len;
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($max_stdout_read_bytes > 0) {
|
if ($max_stdout_read_bytes > 0) {
|
||||||
$this->stdout .= $this->readAndDiscard(
|
$this->stdout .= $this->readAndDiscard(
|
||||||
$stdout,
|
$stdout,
|
||||||
$this->getStdoutSizeLimit() - strlen($this->stdout),
|
$this->getStdoutSizeLimit() - $this->getStdoutBufferLength(),
|
||||||
'stdout',
|
'stdout',
|
||||||
$max_stdout_read_bytes);
|
$max_stdout_read_bytes);
|
||||||
}
|
}
|
||||||
|
@ -772,7 +790,7 @@ final class ExecFuture extends PhutilExecutableFuture {
|
||||||
if ($max_stderr_read_bytes > 0) {
|
if ($max_stderr_read_bytes > 0) {
|
||||||
$this->stderr .= $this->readAndDiscard(
|
$this->stderr .= $this->readAndDiscard(
|
||||||
$stderr,
|
$stderr,
|
||||||
$this->getStderrSizeLimit() - strlen($this->stderr),
|
$this->getStderrSizeLimit() - $this->getStderrBufferLength(),
|
||||||
'stderr',
|
'stderr',
|
||||||
$max_stderr_read_bytes);
|
$max_stderr_read_bytes);
|
||||||
}
|
}
|
||||||
|
@ -1013,5 +1031,20 @@ final class ExecFuture extends PhutilExecutableFuture {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private function getStdoutBufferLength() {
|
||||||
|
if ($this->stdout === null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strlen($this->stdout);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function getStderrBufferLength() {
|
||||||
|
if ($this->stderr === null) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
return strlen($this->stderr);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,8 +10,8 @@
|
||||||
* This is primarily useful for executing things like `$EDITOR` from command
|
* This is primarily useful for executing things like `$EDITOR` from command
|
||||||
* line scripts.
|
* line scripts.
|
||||||
*
|
*
|
||||||
* $exec = new PhutilExecPassthru('ls %s', $dir);
|
* $exec = new PhutilExecPassthru('nano -- %s', $filename);
|
||||||
* $err = $exec->execute();
|
* $err = $exec->resolve();
|
||||||
*
|
*
|
||||||
* You can set the current working directory for the command with
|
* You can set the current working directory for the command with
|
||||||
* @{method:setCWD}, and set the environment with @{method:setEnv}.
|
* @{method:setCWD}, and set the environment with @{method:setEnv}.
|
||||||
|
@ -30,6 +30,14 @@ final class PhutilExecPassthru extends PhutilExecutableFuture {
|
||||||
/* -( Executing Passthru Commands )---------------------------------------- */
|
/* -( Executing Passthru Commands )---------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
public function execute() {
|
||||||
|
phlog(
|
||||||
|
pht(
|
||||||
|
'The "execute()" method of "PhutilExecPassthru" is deprecated and '.
|
||||||
|
'calls should be replaced with "resolve()". See T13660.'));
|
||||||
|
return $this->resolve();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Execute this command.
|
* Execute this command.
|
||||||
*
|
*
|
||||||
|
@ -37,7 +45,7 @@ final class PhutilExecPassthru extends PhutilExecutableFuture {
|
||||||
*
|
*
|
||||||
* @task command
|
* @task command
|
||||||
*/
|
*/
|
||||||
public function execute() {
|
private function executeCommand() {
|
||||||
$command = $this->getCommand();
|
$command = $this->getCommand();
|
||||||
|
|
||||||
$is_write = ($this->stdinData !== null);
|
$is_write = ($this->stdinData !== null);
|
||||||
|
@ -45,10 +53,14 @@ final class PhutilExecPassthru extends PhutilExecutableFuture {
|
||||||
if ($is_write) {
|
if ($is_write) {
|
||||||
$stdin_spec = array('pipe', 'r');
|
$stdin_spec = array('pipe', 'r');
|
||||||
} else {
|
} else {
|
||||||
$stdin_spec = STDIN;
|
$stdin_spec = PhutilSystem::getStdinHandle();
|
||||||
}
|
}
|
||||||
|
|
||||||
$spec = array($stdin_spec, STDOUT, STDERR);
|
$spec = array(
|
||||||
|
$stdin_spec,
|
||||||
|
PhutilSystem::getStdoutHandle(),
|
||||||
|
PhutilSystem::getStderrHandle(),
|
||||||
|
);
|
||||||
$pipes = array();
|
$pipes = array();
|
||||||
|
|
||||||
$unmasked_command = $command->getUnmaskedString();
|
$unmasked_command = $command->getUnmaskedString();
|
||||||
|
@ -116,7 +128,7 @@ final class PhutilExecPassthru extends PhutilExecutableFuture {
|
||||||
// make it easier to share code with ExecFuture.
|
// make it easier to share code with ExecFuture.
|
||||||
|
|
||||||
if (!$this->hasResult()) {
|
if (!$this->hasResult()) {
|
||||||
$result = $this->execute();
|
$result = $this->executeCommand();
|
||||||
$this->setResult($result);
|
$this->setResult($result);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,7 +19,7 @@ abstract class PhutilExecutableFuture extends Future {
|
||||||
pht(
|
pht(
|
||||||
'Command (of class "%s") was constructed with a '.
|
'Command (of class "%s") was constructed with a '.
|
||||||
'"PhutilCommandString", but also passed arguments. '.
|
'"PhutilCommandString", but also passed arguments. '.
|
||||||
'When using a preprebuilt command, you must not pass '.
|
'When using a prebuilt command, you must not pass '.
|
||||||
'arguments.',
|
'arguments.',
|
||||||
get_class($this)));
|
get_class($this)));
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,7 @@ final class ExecPassthruTestCase extends PhutilTestCase {
|
||||||
$bin = $this->getSupportExecutable('exit');
|
$bin = $this->getSupportExecutable('exit');
|
||||||
|
|
||||||
$exec = new PhutilExecPassthru('php -f %R', $bin);
|
$exec = new PhutilExecPassthru('php -f %R', $bin);
|
||||||
$err = $exec->execute();
|
$err = $exec->resolve();
|
||||||
$this->assertEqual(0, $err);
|
$this->assertEqual(0, $err);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -206,12 +206,14 @@ abstract class BaseHTTPFuture extends Future {
|
||||||
* @task config
|
* @task config
|
||||||
*/
|
*/
|
||||||
public function getHeaders($filter = null) {
|
public function getHeaders($filter = null) {
|
||||||
$filter = strtolower($filter);
|
if ($filter !== null) {
|
||||||
|
$filter = phutil_utf8_strtolower($filter);
|
||||||
|
}
|
||||||
|
|
||||||
$result = array();
|
$result = array();
|
||||||
foreach ($this->headers as $header) {
|
foreach ($this->headers as $header) {
|
||||||
list($name, $value) = $header;
|
list($name, $value) = $header;
|
||||||
if (!$filter || ($filter == strtolower($name))) {
|
if (($filter === null) || ($filter === phutil_utf8_strtolower($name))) {
|
||||||
$result[] = $header;
|
$result[] = $header;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -269,7 +269,7 @@ final class HTTPSFuture extends BaseHTTPFuture {
|
||||||
curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, $allowed_protocols);
|
curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, $allowed_protocols);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strlen($this->rawBody)) {
|
if ($this->rawBody !== null) {
|
||||||
if ($this->getData()) {
|
if ($this->getData()) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
|
|
|
@ -36,18 +36,6 @@ function phutil_get_current_library_name() {
|
||||||
return phutil_get_library_name_for_root($root);
|
return phutil_get_library_name_for_root($root);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Warns about use of deprecated behavior.
|
|
||||||
*/
|
|
||||||
function phutil_deprecated($what, $why) {
|
|
||||||
PhutilErrorHandler::dispatchErrorMessage(
|
|
||||||
PhutilErrorHandler::DEPRECATED,
|
|
||||||
$what,
|
|
||||||
array(
|
|
||||||
'why' => $why,
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
function phutil_load_library($path) {
|
function phutil_load_library($path) {
|
||||||
PhutilBootloader::getInstance()->loadLibrary($path);
|
PhutilBootloader::getInstance()->loadLibrary($path);
|
||||||
}
|
}
|
||||||
|
|
|
@ -44,3 +44,7 @@ function phutil_count($countable) {
|
||||||
function phutil_person(PhutilPerson $person) {
|
function phutil_person(PhutilPerson $person) {
|
||||||
return $person;
|
return $person;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function pht_list(array $items) {
|
||||||
|
return implode(', ', $items);
|
||||||
|
}
|
||||||
|
|
|
@ -1407,6 +1407,21 @@ abstract class ArcanistLandEngine
|
||||||
ArcanistLandCommitSet $set,
|
ArcanistLandCommitSet $set,
|
||||||
$into_commit);
|
$into_commit);
|
||||||
abstract protected function pushChange($into_commit);
|
abstract protected function pushChange($into_commit);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update all local refs that depend on refs selected-and-modified during the
|
||||||
|
* land. E.g. with branches named change1 -> change2 -> change3 and using
|
||||||
|
* `arc land change2`, in the general case the local change3 should be
|
||||||
|
* rebased onto the landed version of change2 so that it no longer has
|
||||||
|
* out-of-date ancestors.
|
||||||
|
*
|
||||||
|
* When multiple revisions are landed at once this will be called in a loop
|
||||||
|
* for each set, in order of max to min, where max is the latest descendant
|
||||||
|
* and min is the earliest ancestor. This is done so that non-landing commits
|
||||||
|
* that are descendants of the latest revision will only be rebased once.
|
||||||
|
*
|
||||||
|
* @param ArcanistLandCommitSet The current commit set to cascade.
|
||||||
|
*/
|
||||||
abstract protected function cascadeState(
|
abstract protected function cascadeState(
|
||||||
ArcanistLandCommitSet $set,
|
ArcanistLandCommitSet $set,
|
||||||
$into_commit);
|
$into_commit);
|
||||||
|
@ -1415,12 +1430,35 @@ abstract class ArcanistLandEngine
|
||||||
return ($this->getStrategy() === 'squash');
|
return ($this->getStrategy() === 'squash');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Prunes the given sets of commits. This should be called after the sets
|
||||||
|
* have been merged.
|
||||||
|
*
|
||||||
|
* @param array The list of ArcanistLandCommitSet to prune, in order of
|
||||||
|
* min to max commit set, where min is the earliest ancestor and max
|
||||||
|
* is the latest descendant.
|
||||||
|
*/
|
||||||
abstract protected function pruneBranches(array $sets);
|
abstract protected function pruneBranches(array $sets);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restore the local repository to an expected state after landing. This
|
||||||
|
* should only be called after all changes have been merged, pruned, and
|
||||||
|
* pushed.
|
||||||
|
*
|
||||||
|
* @param string The commit hash that was landed into.
|
||||||
|
* @param ArcanistRepositoryLocalState The local state that was captured
|
||||||
|
* at the beginning of the land process. This may include stashed changes.
|
||||||
|
*/
|
||||||
abstract protected function reconcileLocalState(
|
abstract protected function reconcileLocalState(
|
||||||
$into_commit,
|
$into_commit,
|
||||||
ArcanistRepositoryLocalState $state);
|
ArcanistRepositoryLocalState $state);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Display information to the user about how to proceed since the land
|
||||||
|
* process was not fully completed. The merged branch has not been pushed.
|
||||||
|
*
|
||||||
|
* @param string The commit hash that was landed into.
|
||||||
|
*/
|
||||||
abstract protected function didHoldChanges($into_commit);
|
abstract protected function didHoldChanges($into_commit);
|
||||||
|
|
||||||
private function selectMergeStrategy() {
|
private function selectMergeStrategy() {
|
||||||
|
|
|
@ -6,6 +6,8 @@ final class ArcanistMercurialLandEngine
|
||||||
private $ontoBranchMarker;
|
private $ontoBranchMarker;
|
||||||
private $ontoMarkers;
|
private $ontoMarkers;
|
||||||
|
|
||||||
|
private $rebasedActiveCommit;
|
||||||
|
|
||||||
protected function getDefaultSymbols() {
|
protected function getDefaultSymbols() {
|
||||||
$api = $this->getRepositoryAPI();
|
$api = $this->getRepositoryAPI();
|
||||||
$log = $this->getLogEngine();
|
$log = $this->getLogEngine();
|
||||||
|
@ -698,8 +700,14 @@ final class ArcanistMercurialLandEngine
|
||||||
$commit_map = array();
|
$commit_map = array();
|
||||||
foreach ($symbols as $symbol) {
|
foreach ($symbols as $symbol) {
|
||||||
$symbol_commit = $symbol->getCommit();
|
$symbol_commit = $symbol->getCommit();
|
||||||
$template = '{node}-{parents}-';
|
$template = '{node}-{parents % \'{node} \'}-{desc|firstline}\\n';
|
||||||
|
|
||||||
|
// The returned array of commits is expected to be ordered by max to min
|
||||||
|
// where the max commit has no descendants in the range and the min
|
||||||
|
// commit has no ancestors in the range. Use 'reverse()' in the template
|
||||||
|
// so the output is ordered with the max commit as the first line. The
|
||||||
|
// max/min terms are used in a topological sense as chronological terms
|
||||||
|
// for commits may be misleading or incorrect in some situations.
|
||||||
if ($into_commit === null) {
|
if ($into_commit === null) {
|
||||||
list($commits) = $api->execxLocal(
|
list($commits) = $api->execxLocal(
|
||||||
'log --rev %s --template %s --',
|
'log --rev %s --template %s --',
|
||||||
|
@ -789,8 +797,14 @@ final class ArcanistMercurialLandEngine
|
||||||
|
|
||||||
$commits = $set->getCommits();
|
$commits = $set->getCommits();
|
||||||
|
|
||||||
$min_commit = last($commits)->getHash();
|
// confirmCommits() reverses the order of the commits as they're ordered
|
||||||
$max_commit = head($commits)->getHash();
|
// above in selectCommits(). Now the head of the list is the min commit and
|
||||||
|
// the last is the max commit, where within the range the max commit has no
|
||||||
|
// descendants and the min commit has no ancestors. The min/max terms are
|
||||||
|
// used in a topological sense as chronological terms for commits can be
|
||||||
|
// misleading or incorrect in certain situations.
|
||||||
|
$min_commit = head($commits)->getHash();
|
||||||
|
$max_commit = last($commits)->getHash();
|
||||||
|
|
||||||
$revision_ref = $set->getRevisionRef();
|
$revision_ref = $set->getRevisionRef();
|
||||||
$commit_message = $revision_ref->getCommitMessage();
|
$commit_message = $revision_ref->getCommitMessage();
|
||||||
|
@ -820,13 +834,19 @@ final class ArcanistMercurialLandEngine
|
||||||
$argv[] = '--keep';
|
$argv[] = '--keep';
|
||||||
$argv[] = '--collapse';
|
$argv[] = '--collapse';
|
||||||
|
|
||||||
$future = $api->execFutureLocal('rebase %Ls', $argv);
|
$future = $api->execFutureLocalWithExtension(
|
||||||
|
'rebase',
|
||||||
|
'rebase %Ls',
|
||||||
|
$argv);
|
||||||
$future->write($commit_message);
|
$future->write($commit_message);
|
||||||
$future->resolvex();
|
$future->resolvex();
|
||||||
|
|
||||||
} catch (CommandException $ex) {
|
} catch (CommandException $ex) {
|
||||||
// TODO
|
// Aborting the rebase should restore the same state prior to running the
|
||||||
// $api->execManualLocal('rebase --abort');
|
// rebase command.
|
||||||
|
$api->execManualLocalWithExtension(
|
||||||
|
'rebase',
|
||||||
|
'rebase --abort');
|
||||||
throw $ex;
|
throw $ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -855,13 +875,21 @@ final class ArcanistMercurialLandEngine
|
||||||
list($stdout) = $api->execxLocal('log --rev tip --template %s', '{node}');
|
list($stdout) = $api->execxLocal('log --rev tip --template %s', '{node}');
|
||||||
$new_cursor = trim($stdout);
|
$new_cursor = trim($stdout);
|
||||||
|
|
||||||
|
// If any of the commits that were rebased was the active commit before the
|
||||||
|
// workflow started, track the new commit so it can be used as the working
|
||||||
|
// directory after the land has succeeded.
|
||||||
|
if (isset($obsolete_map[$this->getLocalState()->getLocalCommit()])) {
|
||||||
|
$this->rebasedActiveCommit = $new_cursor;
|
||||||
|
}
|
||||||
|
|
||||||
return $new_cursor;
|
return $new_cursor;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function pushChange($into_commit) {
|
protected function pushChange($into_commit) {
|
||||||
$api = $this->getRepositoryAPI();
|
$api = $this->getRepositoryAPI();
|
||||||
|
|
||||||
list($head, $body, $tail) = $this->newPushCommands($into_commit);
|
list($head, $body, $tail_pass, $tail_fail) = $this->newPushCommands(
|
||||||
|
$into_commit);
|
||||||
|
|
||||||
foreach ($head as $command) {
|
foreach ($head as $command) {
|
||||||
$api->execxLocal('%Ls', $command);
|
$api->execxLocal('%Ls', $command);
|
||||||
|
@ -876,10 +904,20 @@ final class ArcanistMercurialLandEngine
|
||||||
'Push failed! Fix the error and run "arc land" again.'));
|
'Push failed! Fix the error and run "arc land" again.'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} finally {
|
|
||||||
foreach ($tail as $command) {
|
foreach ($tail_pass as $command) {
|
||||||
$api->execxLocal('%Ls', $command);
|
$api->execxLocal('%Ls', $command);
|
||||||
}
|
}
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
foreach ($tail_fail as $command) {
|
||||||
|
$api->execxLocal('%Ls', $command);
|
||||||
|
}
|
||||||
|
throw $ex;
|
||||||
|
} catch (Throwable $ex) {
|
||||||
|
foreach ($tail_fail as $command) {
|
||||||
|
$api->execxLocal('%Ls', $command);
|
||||||
|
}
|
||||||
|
throw $ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -888,7 +926,8 @@ final class ArcanistMercurialLandEngine
|
||||||
|
|
||||||
$head_commands = array();
|
$head_commands = array();
|
||||||
$body_commands = array();
|
$body_commands = array();
|
||||||
$tail_commands = array();
|
$tail_pass_commands = array();
|
||||||
|
$tail_fail_commands = array();
|
||||||
|
|
||||||
$bookmarks = array();
|
$bookmarks = array();
|
||||||
foreach ($this->ontoMarkers as $onto_marker) {
|
foreach ($this->ontoMarkers as $onto_marker) {
|
||||||
|
@ -902,7 +941,7 @@ final class ArcanistMercurialLandEngine
|
||||||
// to the merge commit. (There doesn't seem to be any way to specify
|
// to the merge commit. (There doesn't seem to be any way to specify
|
||||||
// "push commit X as bookmark Y" in Mercurial.)
|
// "push commit X as bookmark Y" in Mercurial.)
|
||||||
|
|
||||||
$restore = array();
|
$restore_bookmarks = array();
|
||||||
if ($bookmarks) {
|
if ($bookmarks) {
|
||||||
$markers = $api->newMarkerRefQuery()
|
$markers = $api->newMarkerRefQuery()
|
||||||
->withNames(mpull($bookmarks, 'getName'))
|
->withNames(mpull($bookmarks, 'getName'))
|
||||||
|
@ -934,7 +973,9 @@ final class ArcanistMercurialLandEngine
|
||||||
hgsprintf('%s', $new_position),
|
hgsprintf('%s', $new_position),
|
||||||
$bookmark_name);
|
$bookmark_name);
|
||||||
|
|
||||||
$restore[$bookmark_name] = $old_position;
|
if ($old_position !== null) {
|
||||||
|
$restore_bookmarks[$bookmark_name] = $old_position;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -963,28 +1004,41 @@ final class ArcanistMercurialLandEngine
|
||||||
|
|
||||||
// Finally, restore the bookmarks.
|
// Finally, restore the bookmarks.
|
||||||
|
|
||||||
foreach ($restore as $bookmark_name => $old_position) {
|
if ($restore_bookmarks) {
|
||||||
$tail = array();
|
// Instead of restoring the previous state, assume landing onto bookmarks
|
||||||
$tail[] = 'bookmark';
|
// also updates those bookmarks in the remote. After pushing, pull the
|
||||||
|
// latest state of these bookmarks. Mercurial allows pulling multiple
|
||||||
|
// bookmarks in a single pull command which will be faster than pulling
|
||||||
|
// them from a remote individually.
|
||||||
|
$tail = array(
|
||||||
|
'pull',
|
||||||
|
);
|
||||||
|
|
||||||
if ($old_position === null) {
|
foreach ($restore_bookmarks as $bookmark_name => $old_position) {
|
||||||
$tail[] = '--delete';
|
$tail[] = '--bookmark';
|
||||||
} else {
|
|
||||||
$tail[] = '--force';
|
|
||||||
$tail[] = '--rev';
|
|
||||||
$tail[] = hgsprintf('%s', $api->getDisplayHash($old_position));
|
|
||||||
}
|
|
||||||
|
|
||||||
$tail[] = '--';
|
|
||||||
$tail[] = $bookmark_name;
|
$tail[] = $bookmark_name;
|
||||||
|
|
||||||
$tail_commands[] = $tail;
|
// In the failure case restore the state of the bookmark. Mercurial
|
||||||
|
// does not provide a way to move multiple bookmarks in a single
|
||||||
|
// command however these commands do not involve the remote.
|
||||||
|
$tail_fail_commands[] = array(
|
||||||
|
'bookmark',
|
||||||
|
'--force',
|
||||||
|
'--rev',
|
||||||
|
hgsprintf('%s', $api->getDisplayHash($old_position)),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($tail) {
|
||||||
|
$tail_pass_commands[] = $tail;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return array(
|
return array(
|
||||||
$head_commands,
|
$head_commands,
|
||||||
$body_commands,
|
$body_commands,
|
||||||
$tail_commands,
|
$tail_pass_commands,
|
||||||
|
$tail_fail_commands,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1015,12 +1069,17 @@ final class ArcanistMercurialLandEngine
|
||||||
// be deleted, we can skip this rebase?
|
// be deleted, we can skip this rebase?
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$api->execxLocal(
|
$api->execxLocalWithExtension(
|
||||||
|
'rebase',
|
||||||
'rebase --source %s --dest %s --keep --keepbranches',
|
'rebase --source %s --dest %s --keep --keepbranches',
|
||||||
$child_hash,
|
$child_hash,
|
||||||
$new_commit);
|
$new_commit);
|
||||||
} catch (CommandException $ex) {
|
} catch (CommandException $ex) {
|
||||||
// TODO: Recover state.
|
// Aborting the rebase should restore the same state prior to running
|
||||||
|
// the rebase command.
|
||||||
|
$api->execManualLocalWithExtension(
|
||||||
|
'rebase',
|
||||||
|
'rebase --abort');
|
||||||
throw $ex;
|
throw $ex;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1040,6 +1099,8 @@ final class ArcanistMercurialLandEngine
|
||||||
$revs = array();
|
$revs = array();
|
||||||
$obsolete_map = array();
|
$obsolete_map = array();
|
||||||
|
|
||||||
|
$using_evolve = $api->getMercurialFeature('evolve');
|
||||||
|
|
||||||
// We've rebased all descendants already, so we can safely delete all
|
// We've rebased all descendants already, so we can safely delete all
|
||||||
// of these commits.
|
// of these commits.
|
||||||
|
|
||||||
|
@ -1047,10 +1108,22 @@ final class ArcanistMercurialLandEngine
|
||||||
foreach ($sets as $set) {
|
foreach ($sets as $set) {
|
||||||
$commits = $set->getCommits();
|
$commits = $set->getCommits();
|
||||||
|
|
||||||
|
// In the commit set the min commit should be the commit with no
|
||||||
|
// ancestors and the max commit should be the commit with no descendants.
|
||||||
|
// The min/max terms are used in a toplogical sense as chronological
|
||||||
|
// terms for commits may be misleading or incorrect in some situations.
|
||||||
$min_commit = head($commits)->getHash();
|
$min_commit = head($commits)->getHash();
|
||||||
$max_commit = last($commits)->getHash();
|
$max_commit = last($commits)->getHash();
|
||||||
|
|
||||||
|
if ($using_evolve) {
|
||||||
|
// If non-head series of commits are rebased while the evolve extension
|
||||||
|
// is in use, the rebase leaves behind the entire series of descendants
|
||||||
|
// in which case the entire chain needs removed, not just a section.
|
||||||
|
// Otherwise this results in the prune leaving behind orphaned commits.
|
||||||
|
$revs[] = hgsprintf('%s::', $min_commit);
|
||||||
|
} else {
|
||||||
$revs[] = hgsprintf('%s::%s', $min_commit, $max_commit);
|
$revs[] = hgsprintf('%s::%s', $min_commit, $max_commit);
|
||||||
|
}
|
||||||
|
|
||||||
foreach ($commits as $commit) {
|
foreach ($commits as $commit) {
|
||||||
$obsolete_map[$commit->getHash()] = true;
|
$obsolete_map[$commit->getHash()] = true;
|
||||||
|
@ -1071,10 +1144,7 @@ final class ArcanistMercurialLandEngine
|
||||||
// bookmarks which point at these now-obsoleted commits.
|
// bookmarks which point at these now-obsoleted commits.
|
||||||
|
|
||||||
$bookmark_refs = $api->newMarkerRefQuery()
|
$bookmark_refs = $api->newMarkerRefQuery()
|
||||||
->withMarkerTypes(
|
->withMarkerTypes(array(ArcanistMarkerRef::TYPE_BOOKMARK))
|
||||||
array(
|
|
||||||
ArcanistMarkerRef::TYPE_BOOKMARK,
|
|
||||||
))
|
|
||||||
->execute();
|
->execute();
|
||||||
foreach ($bookmark_refs as $bookmark_ref) {
|
foreach ($bookmark_refs as $bookmark_ref) {
|
||||||
$bookmark_hash = $bookmark_ref->getCommitHash();
|
$bookmark_hash = $bookmark_ref->getCommitHash();
|
||||||
|
@ -1093,13 +1163,14 @@ final class ArcanistMercurialLandEngine
|
||||||
$bookmark_name);
|
$bookmark_name);
|
||||||
}
|
}
|
||||||
|
|
||||||
if ($api->getMercurialFeature('evolve')) {
|
if ($using_evolve) {
|
||||||
$api->execxLocal(
|
$api->execxLocal(
|
||||||
'prune --rev %s',
|
'prune --rev %s',
|
||||||
$rev_set);
|
$rev_set);
|
||||||
} else {
|
} else {
|
||||||
$api->execxLocal(
|
$api->execxLocalWithExtension(
|
||||||
'--config extensions.strip= strip --rev %s',
|
'strip',
|
||||||
|
'strip --rev %s',
|
||||||
$rev_set);
|
$rev_set);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1108,7 +1179,54 @@ final class ArcanistMercurialLandEngine
|
||||||
$into_commit,
|
$into_commit,
|
||||||
ArcanistRepositoryLocalState $state) {
|
ArcanistRepositoryLocalState $state) {
|
||||||
|
|
||||||
// TODO: For now, just leave users wherever they ended up.
|
$api = $this->getRepositoryAPI();
|
||||||
|
|
||||||
|
// If the starting working state was not part of land process just update
|
||||||
|
// to that original working state.
|
||||||
|
if ($this->rebasedActiveCommit === null) {
|
||||||
|
$update_marker = $this->getLocalState()->getLocalCommit();
|
||||||
|
if ($this->getLocalState()->getLocalBookmark() !== null) {
|
||||||
|
$update_marker = $this->getLocalState()->getLocalBookmark();
|
||||||
|
}
|
||||||
|
|
||||||
|
$api->execxLocal(
|
||||||
|
'update -- %s',
|
||||||
|
$update_marker);
|
||||||
|
|
||||||
|
$state->discardLocalState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the working state was landed into multiple destinations then the
|
||||||
|
// resulting working state is ambiguous.
|
||||||
|
if (count($this->ontoMarkers) != 1) {
|
||||||
|
$state->discardLocalState();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current state of bookmarks
|
||||||
|
$bookmark_refs = $api->newMarkerRefQuery()
|
||||||
|
->withMarkerTypes(array(ArcanistMarkerRef::TYPE_BOOKMARK))
|
||||||
|
->execute();
|
||||||
|
|
||||||
|
$update_marker = $this->rebasedActiveCommit;
|
||||||
|
|
||||||
|
// Find any bookmarks which exist on the commit which is the result of the
|
||||||
|
// starting working directory's rebase. If any of those bookmarks are also
|
||||||
|
// the destination marker then we use that bookmark as the update in order
|
||||||
|
// for it to become active.
|
||||||
|
$onto_marker = $this->ontoMarkers[0]->getName();
|
||||||
|
foreach ($bookmark_refs as $bookmark_ref) {
|
||||||
|
if ($bookmark_ref->getCommitHash() == $this->rebasedActiveCommit &&
|
||||||
|
$bookmark_ref->getName() == $onto_marker) {
|
||||||
|
$update_marker = $onto_marker;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
$api->execxLocal(
|
||||||
|
'update -- %s',
|
||||||
|
$update_marker);
|
||||||
|
|
||||||
$state->discardLocalState();
|
$state->discardLocalState();
|
||||||
}
|
}
|
||||||
|
@ -1120,8 +1238,9 @@ final class ArcanistMercurialLandEngine
|
||||||
$message = pht(
|
$message = pht(
|
||||||
'Holding changes locally, they have not been pushed.');
|
'Holding changes locally, they have not been pushed.');
|
||||||
|
|
||||||
list($head, $body, $tail) = $this->newPushCommands($into_commit);
|
list($head, $body, $tail_pass, $tail_fail) = $this->newPushCommands(
|
||||||
$commands = array_merge($head, $body, $tail);
|
$into_commit);
|
||||||
|
$commands = array_merge($head, $body, $tail_pass);
|
||||||
|
|
||||||
echo tsprintf(
|
echo tsprintf(
|
||||||
"\n%!\n%s\n\n",
|
"\n%!\n%s\n\n",
|
||||||
|
|
|
@ -64,11 +64,11 @@ final class ArcanistCSharpLinter extends ArcanistLinter {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
"In order to keep StyleCop integration with IDEs and other tools ".
|
"In order to keep StyleCop integration with IDEs and other tools ".
|
||||||
"consistent with Arcanist results, you aren't permitted to ".
|
"consistent with lint results, you aren't permitted to ".
|
||||||
"disable StyleCop rules within '%s'. Instead configure the ".
|
"disable StyleCop rules within '%s'. Instead configure the ".
|
||||||
"severity using the StyleCop settings dialog (usually accessible ".
|
"severity using the StyleCop settings dialog (usually accessible ".
|
||||||
"from within your IDE). StyleCop settings for your project will ".
|
"from within your IDE). StyleCop settings for your project will ".
|
||||||
"be used when linting for Arcanist.",
|
"be used when linting.",
|
||||||
'.arclint'));
|
'.arclint'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -132,8 +132,8 @@ final class ArcanistCSharpLinter extends ArcanistLinter {
|
||||||
} else if ($ver > self::SUPPORTED_VERSION) {
|
} else if ($ver > self::SUPPORTED_VERSION) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
'Arcanist does not support this version of %s (it is newer). '.
|
'This version of %s is not supported (it is too new). '.
|
||||||
'You can try upgrading Arcanist with `%s`.',
|
'You can try upgrading with `%s`.',
|
||||||
'cslint',
|
'cslint',
|
||||||
'arc upgrade'));
|
'arc upgrade'));
|
||||||
}
|
}
|
||||||
|
|
|
@ -304,9 +304,9 @@ final class ArcanistPhutilLibraryLinter extends ArcanistLinter {
|
||||||
$details = pht(
|
$details = pht(
|
||||||
"Common causes are:\n".
|
"Common causes are:\n".
|
||||||
"\n".
|
"\n".
|
||||||
" - Your copy of Arcanist is out of date.\n".
|
" - Your copy of %s is out of date.\n".
|
||||||
" This is the most common cause.\n".
|
" This is the most common cause.\n".
|
||||||
" Update this copy of Arcanist:\n".
|
" Update this copy of %s:\n".
|
||||||
"\n".
|
"\n".
|
||||||
" %s\n".
|
" %s\n".
|
||||||
"\n".
|
"\n".
|
||||||
|
@ -324,6 +324,8 @@ final class ArcanistPhutilLibraryLinter extends ArcanistLinter {
|
||||||
" - This symbol is defined in an external library.\n".
|
" - This symbol is defined in an external library.\n".
|
||||||
" Use \"@phutil-external-symbol\" to annotate it.\n".
|
" Use \"@phutil-external-symbol\" to annotate it.\n".
|
||||||
" Use \"grep\" to find examples of usage.",
|
" Use \"grep\" to find examples of usage.",
|
||||||
|
PlatformSymbols::getPlatformClientName(),
|
||||||
|
PlatformSymbols::getPlatformClientName(),
|
||||||
$arcanist_root);
|
$arcanist_root);
|
||||||
|
|
||||||
$message = implode(
|
$message = implode(
|
||||||
|
|
|
@ -27,7 +27,11 @@ final class ArcanistXMLLinter extends ArcanistLinter {
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getCacheVersion() {
|
public function getCacheVersion() {
|
||||||
|
if (defined('LIBXML_VERSION')) {
|
||||||
return LIBXML_VERSION;
|
return LIBXML_VERSION;
|
||||||
|
} else {
|
||||||
|
return 'unavailable';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public function lintPath($path) {
|
public function lintPath($path) {
|
||||||
|
|
|
@ -51,6 +51,13 @@ abstract class ArcanistLinterTestCase extends PhutilTestCase {
|
||||||
private function lintFile($file, ArcanistLinter $linter) {
|
private function lintFile($file, ArcanistLinter $linter) {
|
||||||
$linter = clone $linter;
|
$linter = clone $linter;
|
||||||
|
|
||||||
|
if (!$linter->canRun()) {
|
||||||
|
$this->assertSkipped(
|
||||||
|
pht(
|
||||||
|
'Linter "%s" can not run.',
|
||||||
|
get_class($linter)));
|
||||||
|
}
|
||||||
|
|
||||||
$contents = Filesystem::readFile($file);
|
$contents = Filesystem::readFile($file);
|
||||||
$contents = preg_split('/^~{4,}\n/m', $contents);
|
$contents = preg_split('/^~{4,}\n/m', $contents);
|
||||||
if (count($contents) < 2) {
|
if (count($contents) < 2) {
|
||||||
|
@ -283,9 +290,12 @@ abstract class ArcanistLinterTestCase extends PhutilTestCase {
|
||||||
}
|
}
|
||||||
|
|
||||||
private function compareTransform($expected, $actual) {
|
private function compareTransform($expected, $actual) {
|
||||||
|
$expected = phutil_string_cast($expected);
|
||||||
|
|
||||||
if (!strlen($expected)) {
|
if (!strlen($expected)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->assertEqual(
|
$this->assertEqual(
|
||||||
$expected,
|
$expected,
|
||||||
$actual,
|
$actual,
|
||||||
|
|
|
@ -17,6 +17,14 @@ final class ArcanistCommentStyleXHPASTLinterRule
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Don't warn about PHP comment directives. In particular, we need
|
||||||
|
// to use "#[\ReturnTypeWillChange]" to implement "Iterator" in a way
|
||||||
|
// that is compatible with PHP 8.1 and older versions of PHP prior
|
||||||
|
// to the introduction of return types. See T13588.
|
||||||
|
if (preg_match('/^#\\[\\\\/', $value)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
$this->raiseLintAtOffset(
|
$this->raiseLintAtOffset(
|
||||||
$comment->getOffset(),
|
$comment->getOffset(),
|
||||||
pht(
|
pht(
|
||||||
|
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class ArcanistEachUseXHPASTLinterRule
|
||||||
|
extends ArcanistXHPASTLinterRule {
|
||||||
|
|
||||||
|
const ID = 133;
|
||||||
|
|
||||||
|
public function getLintName() {
|
||||||
|
return pht('Use of Removed Function "each()"');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(XHPASTNode $root) {
|
||||||
|
$calls = $this->getFunctionCalls($root, array('each'));
|
||||||
|
|
||||||
|
foreach ($calls as $call) {
|
||||||
|
$this->raiseLintAtNode(
|
||||||
|
$call,
|
||||||
|
pht(
|
||||||
|
'Do not use "each()". This function was deprecated in PHP 7.2 '.
|
||||||
|
'and removed in PHP 8.0'));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -155,7 +155,9 @@ final class ArcanistPHPCompatibilityXHPASTLinterRule
|
||||||
if ($this->windowsVersion) {
|
if ($this->windowsVersion) {
|
||||||
$windows = idx($compat_info['functions_windows'], $name);
|
$windows = idx($compat_info['functions_windows'], $name);
|
||||||
|
|
||||||
if ($windows === false) {
|
if ($windows === null) {
|
||||||
|
// This function has no special Windows considerations.
|
||||||
|
} else if ($windows === false) {
|
||||||
$this->raiseLintAtNode(
|
$this->raiseLintAtNode(
|
||||||
$node,
|
$node,
|
||||||
pht(
|
pht(
|
||||||
|
|
|
@ -55,7 +55,12 @@ final class ArcanistParentMemberReferenceXHPASTLinterRule
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version_compare($this->version, '5.4.0', '>=') || !$in_closure) {
|
$version_target = $this->version;
|
||||||
|
if ($version_target === null) {
|
||||||
|
$version_target = phpversion();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version_compare($version_target, '5.4.0', '>=') || !$in_closure) {
|
||||||
$this->raiseLintAtNode(
|
$this->raiseLintAtNode(
|
||||||
$class_ref,
|
$class_ref,
|
||||||
pht(
|
pht(
|
||||||
|
|
|
@ -0,0 +1,67 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class ArcanistProductNameLiteralXHPASTLinterRule
|
||||||
|
extends ArcanistXHPASTLinterRule {
|
||||||
|
|
||||||
|
const ID = 134;
|
||||||
|
|
||||||
|
public function getLintName() {
|
||||||
|
return pht('Use of Product Name Literal');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLintSeverity() {
|
||||||
|
return ArcanistLintSeverity::SEVERITY_WARNING;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function process(XHPASTNode $root) {
|
||||||
|
$calls = $root->selectDescendantsOfType('n_FUNCTION_CALL');
|
||||||
|
|
||||||
|
$product_names = PlatformSymbols::getProductNames();
|
||||||
|
foreach ($product_names as $k => $product_name) {
|
||||||
|
$product_names[$k] = preg_quote($product_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
$search_pattern = '(\b(?:'.implode('|', $product_names).')\b)i';
|
||||||
|
|
||||||
|
foreach ($calls as $call) {
|
||||||
|
$name = $call->getChildByIndex(0)->getConcreteString();
|
||||||
|
|
||||||
|
if ($name !== 'pht') {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$parameters = $call->getChildByIndex(1);
|
||||||
|
|
||||||
|
if (!$parameters->getChildren()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$identifier = $parameters->getChildByIndex(0);
|
||||||
|
if (!$identifier->isConstantString()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$literal_value = $identifier->evalStatic();
|
||||||
|
|
||||||
|
$matches = phutil_preg_match_all($search_pattern, $literal_value);
|
||||||
|
if (!$matches[0]) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$name_list = array();
|
||||||
|
foreach ($matches[0] as $match) {
|
||||||
|
$name_list[phutil_utf8_strtolower($match)] = $match;
|
||||||
|
}
|
||||||
|
$name_list = implode(', ', $name_list);
|
||||||
|
|
||||||
|
$this->raiseLintAtNode(
|
||||||
|
$identifier,
|
||||||
|
pht(
|
||||||
|
'Avoid use of product name literals in "pht()": use generic '.
|
||||||
|
'language or an appropriate method from the "PlatformSymbols" class '.
|
||||||
|
'instead so the software can be forked. String uses names: %s.',
|
||||||
|
$name_list));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -42,7 +42,12 @@ final class ArcanistSelfMemberReferenceXHPASTLinterRule
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (version_compare($this->version, '5.4.0', '>=') || !$in_closure) {
|
$version_target = $this->version;
|
||||||
|
if (!$version_target) {
|
||||||
|
$version_target = phpversion();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (version_compare($version_target, '5.4.0', '>=') || !$in_closure) {
|
||||||
$this->raiseLintAtNode(
|
$this->raiseLintAtNode(
|
||||||
$class_ref,
|
$class_ref,
|
||||||
pht(
|
pht(
|
||||||
|
|
|
@ -0,0 +1,10 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class ArcanistEachUseXHPASTLinterRuleTestCase
|
||||||
|
extends ArcanistXHPASTLinterRuleTestCase {
|
||||||
|
|
||||||
|
public function testLinter() {
|
||||||
|
$this->executeTestsInDirectory(dirname(__FILE__).'/each-use/');
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class X implements Iterator {
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function reset() {
|
||||||
|
# See T13588 for PHP8.1 compatibility information.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
~~~~~~~~~~
|
||||||
|
error:7:5:XHP18:Comment Style
|
||||||
|
~~~~~~~~~~
|
||||||
|
<?php
|
||||||
|
|
||||||
|
class X implements Iterator {
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
|
public function reset() {
|
||||||
|
// See T13588 for PHP8.1 compatibility information.
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,6 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
$a = range(1, 2);
|
||||||
|
each($a);
|
||||||
|
~~~~~~~~~~
|
||||||
|
error:4:1:XHP133
|
|
@ -112,6 +112,8 @@ final class ArcanistConsoleLintRenderer extends ArcanistLintRenderer {
|
||||||
$message = $message->newTrimmedMessage();
|
$message = $message->newTrimmedMessage();
|
||||||
|
|
||||||
$original = $message->getOriginalText();
|
$original = $message->getOriginalText();
|
||||||
|
$original = phutil_string_cast($original);
|
||||||
|
|
||||||
$replacement = $message->getReplacementText();
|
$replacement = $message->getReplacementText();
|
||||||
|
|
||||||
$line = $message->getLine();
|
$line = $message->getLine();
|
||||||
|
|
|
@ -19,7 +19,7 @@ final class ArcanistLogEngine
|
||||||
}
|
}
|
||||||
|
|
||||||
private function writeBytes($bytes) {
|
private function writeBytes($bytes) {
|
||||||
fprintf(STDERR, '%s', $bytes);
|
PhutilSystem::writeStderr($bytes);
|
||||||
return $this;
|
return $this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
final class PhutilLibraryMapBuilder extends Phobject {
|
final class PhutilLibraryMapBuilder extends Phobject {
|
||||||
|
|
||||||
private $root;
|
private $root;
|
||||||
private $quiet = true;
|
|
||||||
private $subprocessLimit = 8;
|
private $subprocessLimit = 8;
|
||||||
|
|
||||||
private $fileSymbolMap;
|
private $fileSymbolMap;
|
||||||
|
@ -38,19 +37,6 @@ final class PhutilLibraryMapBuilder extends Phobject {
|
||||||
$this->root = $root;
|
$this->root = $root;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Control status output. Use `--quiet` to set this.
|
|
||||||
*
|
|
||||||
* @param bool If true, don't show status output.
|
|
||||||
* @return this
|
|
||||||
*
|
|
||||||
* @task map
|
|
||||||
*/
|
|
||||||
public function setQuiet($quiet) {
|
|
||||||
$this->quiet = $quiet;
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Control subprocess parallelism limit. Use `--limit` to set this.
|
* Control subprocess parallelism limit. Use `--limit` to set this.
|
||||||
*
|
*
|
||||||
|
@ -108,25 +94,9 @@ final class PhutilLibraryMapBuilder extends Phobject {
|
||||||
public function buildAndWriteMap() {
|
public function buildAndWriteMap() {
|
||||||
$library_map = $this->buildMap();
|
$library_map = $this->buildMap();
|
||||||
|
|
||||||
$this->log(pht('Writing map...'));
|
|
||||||
$this->writeLibraryMap($library_map);
|
$this->writeLibraryMap($library_map);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Write a status message to the user, if not running in quiet mode.
|
|
||||||
*
|
|
||||||
* @param string Message to write.
|
|
||||||
* @return this
|
|
||||||
*
|
|
||||||
* @task map
|
|
||||||
*/
|
|
||||||
private function log($message) {
|
|
||||||
if (!$this->quiet) {
|
|
||||||
@fprintf(STDERR, "%s\n", $message);
|
|
||||||
}
|
|
||||||
return $this;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
/* -( Path Management )---------------------------------------------------- */
|
/* -( Path Management )---------------------------------------------------- */
|
||||||
|
|
||||||
|
@ -236,11 +206,7 @@ final class PhutilLibraryMapBuilder extends Phobject {
|
||||||
}
|
}
|
||||||
|
|
||||||
$json = json_encode($cache);
|
$json = json_encode($cache);
|
||||||
try {
|
|
||||||
Filesystem::writeFile($cache_file, $json);
|
Filesystem::writeFile($cache_file, $json);
|
||||||
} catch (FilesystemException $ex) {
|
|
||||||
$this->log(pht('Unable to save the cache!'));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -251,7 +217,6 @@ final class PhutilLibraryMapBuilder extends Phobject {
|
||||||
* @task symbol
|
* @task symbol
|
||||||
*/
|
*/
|
||||||
public function dropSymbolCache() {
|
public function dropSymbolCache() {
|
||||||
$this->log(pht('Dropping symbol cache...'));
|
|
||||||
Filesystem::remove($this->getPathForSymbolCache());
|
Filesystem::remove($this->getPathForSymbolCache());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -451,14 +416,10 @@ EOPHP;
|
||||||
*/
|
*/
|
||||||
private function analyzeLibrary() {
|
private function analyzeLibrary() {
|
||||||
// Identify all the ".php" source files in the library.
|
// Identify all the ".php" source files in the library.
|
||||||
$this->log(pht('Finding source files...'));
|
|
||||||
$source_map = $this->loadSourceFileMap();
|
$source_map = $this->loadSourceFileMap();
|
||||||
$this->log(
|
|
||||||
pht('Found %s files.', new PhutilNumber(count($source_map))));
|
|
||||||
|
|
||||||
// Load the symbol cache with existing parsed symbols. This allows us
|
// Load the symbol cache with existing parsed symbols. This allows us
|
||||||
// to remap libraries quickly by analyzing only changed files.
|
// to remap libraries quickly by analyzing only changed files.
|
||||||
$this->log(pht('Loading symbol cache...'));
|
|
||||||
$symbol_cache = $this->loadSymbolCache();
|
$symbol_cache = $this->loadSymbolCache();
|
||||||
|
|
||||||
// If the XHPAST binary is not up-to-date, build it now. Otherwise,
|
// If the XHPAST binary is not up-to-date, build it now. Otherwise,
|
||||||
|
@ -481,23 +442,12 @@ EOPHP;
|
||||||
}
|
}
|
||||||
$futures[$file] = $this->buildSymbolAnalysisFuture($file);
|
$futures[$file] = $this->buildSymbolAnalysisFuture($file);
|
||||||
}
|
}
|
||||||
$this->log(
|
|
||||||
pht('Found %s files in cache.', new PhutilNumber(count($symbol_map))));
|
|
||||||
|
|
||||||
// Run the analyzer on any files which need analysis.
|
// Run the analyzer on any files which need analysis.
|
||||||
if ($futures) {
|
if ($futures) {
|
||||||
$limit = $this->subprocessLimit;
|
$limit = $this->subprocessLimit;
|
||||||
|
|
||||||
$this->log(
|
|
||||||
pht(
|
|
||||||
'Analyzing %s file(s) with %s subprocess(es)...',
|
|
||||||
phutil_count($futures),
|
|
||||||
new PhutilNumber($limit)));
|
|
||||||
|
|
||||||
$progress = new PhutilConsoleProgressBar();
|
$progress = new PhutilConsoleProgressBar();
|
||||||
if ($this->quiet) {
|
|
||||||
$progress->setQuiet(true);
|
|
||||||
}
|
|
||||||
$progress->setTotal(count($futures));
|
$progress->setTotal(count($futures));
|
||||||
|
|
||||||
$futures = id(new FutureIterator($futures))
|
$futures = id(new FutureIterator($futures))
|
||||||
|
@ -525,8 +475,6 @@ EOPHP;
|
||||||
$this->writeSymbolCache($symbol_map, $source_map);
|
$this->writeSymbolCache($symbol_map, $source_map);
|
||||||
|
|
||||||
// Our map is up to date, so either show it on stdout or write it to disk.
|
// Our map is up to date, so either show it on stdout or write it to disk.
|
||||||
$this->log(pht('Building library map...'));
|
|
||||||
|
|
||||||
$this->librarySymbolMap = $this->buildLibraryMap($symbol_map);
|
$this->librarySymbolMap = $this->buildLibraryMap($symbol_map);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -33,22 +33,27 @@ abstract class Phobject implements Iterator {
|
||||||
get_class($this).'::'.$name));
|
get_class($this).'::'.$name));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function current() {
|
public function current() {
|
||||||
$this->throwOnAttemptedIteration();
|
$this->throwOnAttemptedIteration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function key() {
|
public function key() {
|
||||||
$this->throwOnAttemptedIteration();
|
$this->throwOnAttemptedIteration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function next() {
|
public function next() {
|
||||||
$this->throwOnAttemptedIteration();
|
$this->throwOnAttemptedIteration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function rewind() {
|
public function rewind() {
|
||||||
$this->throwOnAttemptedIteration();
|
$this->throwOnAttemptedIteration();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function valid() {
|
public function valid() {
|
||||||
$this->throwOnAttemptedIteration();
|
$this->throwOnAttemptedIteration();
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,7 +33,7 @@ final class ArcanistBaseCommitParser extends Phobject {
|
||||||
|
|
||||||
private function log($message) {
|
private function log($message) {
|
||||||
if ($this->verbose) {
|
if ($this->verbose) {
|
||||||
fwrite(STDERR, $message."\n");
|
PhutilSystem::writeStderr($message."\n");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -639,8 +639,7 @@ final class ArcanistBundle extends Phobject {
|
||||||
$old_path = $change->getOldPath();
|
$old_path = $change->getOldPath();
|
||||||
$type = $change->getType();
|
$type = $change->getType();
|
||||||
|
|
||||||
if (!strlen($old_path) ||
|
if ($old_path === '' || $type == ArcanistDiffChangeType::TYPE_ADD) {
|
||||||
$type == ArcanistDiffChangeType::TYPE_ADD) {
|
|
||||||
$old_path = null;
|
$old_path = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1023,7 +1022,7 @@ final class ArcanistBundle extends Phobject {
|
||||||
if ($is_64bit) {
|
if ($is_64bit) {
|
||||||
for ($count = 4; $count >= 0; $count--) {
|
for ($count = 4; $count >= 0; $count--) {
|
||||||
$val = $accum % 85;
|
$val = $accum % 85;
|
||||||
$accum = $accum / 85;
|
$accum = (int)($accum / 85);
|
||||||
$slice .= $map[$val];
|
$slice .= $map[$val];
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -217,7 +217,7 @@ final class ArcanistDiffParser extends Phobject {
|
||||||
$line = $this->nextLine();
|
$line = $this->nextLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strlen($message)) {
|
if ($message !== null && strlen($message)) {
|
||||||
// If we found a message during pre-parse steps, add it to the resulting
|
// If we found a message during pre-parse steps, add it to the resulting
|
||||||
// changes here.
|
// changes here.
|
||||||
$change = $this->buildChange(null)
|
$change = $this->buildChange(null)
|
||||||
|
@ -582,12 +582,15 @@ final class ArcanistDiffParser extends Phobject {
|
||||||
|
|
||||||
$ok = false;
|
$ok = false;
|
||||||
$match = null;
|
$match = null;
|
||||||
|
|
||||||
|
if ($line !== null) {
|
||||||
foreach ($patterns as $pattern) {
|
foreach ($patterns as $pattern) {
|
||||||
$ok = preg_match('@^'.$pattern.'@', $line, $match);
|
$ok = preg_match('@^'.$pattern.'@', $line, $match);
|
||||||
if ($ok) {
|
if ($ok) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (!$ok) {
|
if (!$ok) {
|
||||||
if ($line === null ||
|
if ($line === null ||
|
||||||
|
@ -774,7 +777,7 @@ final class ArcanistDiffParser extends Phobject {
|
||||||
$this->nextLine();
|
$this->nextLine();
|
||||||
$this->parseGitBinaryPatch();
|
$this->parseGitBinaryPatch();
|
||||||
$line = $this->getLine();
|
$line = $this->getLine();
|
||||||
if (preg_match('/^literal/', $line)) {
|
if ($line !== null && preg_match('/^literal/', $line)) {
|
||||||
// We may have old/new binaries (change) or just a new binary (hg add).
|
// We may have old/new binaries (change) or just a new binary (hg add).
|
||||||
// If there are two blocks, parse both.
|
// If there are two blocks, parse both.
|
||||||
$this->parseGitBinaryPatch();
|
$this->parseGitBinaryPatch();
|
||||||
|
@ -920,11 +923,11 @@ final class ArcanistDiffParser extends Phobject {
|
||||||
$hunk->setNewOffset($matches[3]);
|
$hunk->setNewOffset($matches[3]);
|
||||||
|
|
||||||
// Cover for the cases where length wasn't present (implying one line).
|
// Cover for the cases where length wasn't present (implying one line).
|
||||||
$old_len = idx($matches, 2);
|
$old_len = idx($matches, 2, '');
|
||||||
if (!strlen($old_len)) {
|
if (!strlen($old_len)) {
|
||||||
$old_len = 1;
|
$old_len = 1;
|
||||||
}
|
}
|
||||||
$new_len = idx($matches, 4);
|
$new_len = idx($matches, 4, '');
|
||||||
if (!strlen($new_len)) {
|
if (!strlen($new_len)) {
|
||||||
$new_len = 1;
|
$new_len = 1;
|
||||||
}
|
}
|
||||||
|
@ -1041,7 +1044,7 @@ final class ArcanistDiffParser extends Phobject {
|
||||||
$line = $this->nextNonemptyLine();
|
$line = $this->nextNonemptyLine();
|
||||||
}
|
}
|
||||||
|
|
||||||
} while (preg_match('/^@@ /', $line));
|
} while (($line !== null) && preg_match('/^@@ /', $line));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function buildChange($path = null) {
|
protected function buildChange($path = null) {
|
||||||
|
|
|
@ -85,7 +85,7 @@ final class PhutilBugtraqParser extends Phobject {
|
||||||
$captured_text = $capture['text'];
|
$captured_text = $capture['text'];
|
||||||
$captured_offset = $capture['at'];
|
$captured_offset = $capture['at'];
|
||||||
|
|
||||||
if (strlen($select_regexp)) {
|
if ($select_regexp !== null) {
|
||||||
$selections = null;
|
$selections = null;
|
||||||
preg_match_all(
|
preg_match_all(
|
||||||
$select_regexp,
|
$select_regexp,
|
||||||
|
|
|
@ -13,6 +13,7 @@ final class PhutilEmailAddress extends Phobject {
|
||||||
private $domainName;
|
private $domainName;
|
||||||
|
|
||||||
public function __construct($email_address = null) {
|
public function __construct($email_address = null) {
|
||||||
|
$email_address = phutil_string_cast($email_address);
|
||||||
$email_address = trim($email_address);
|
$email_address = trim($email_address);
|
||||||
|
|
||||||
$matches = null;
|
$matches = null;
|
||||||
|
@ -41,12 +42,13 @@ final class PhutilEmailAddress extends Phobject {
|
||||||
|
|
||||||
public function __toString() {
|
public function __toString() {
|
||||||
$address = $this->getAddress();
|
$address = $this->getAddress();
|
||||||
if (strlen($this->displayName)) {
|
|
||||||
|
if (phutil_nonempty_string($this->displayName)) {
|
||||||
$display_name = $this->encodeDisplayName($this->displayName);
|
$display_name = $this->encodeDisplayName($this->displayName);
|
||||||
return $display_name.' <'.$address.'>';
|
return $display_name.' <'.$address.'>';
|
||||||
} else {
|
|
||||||
return $address;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return $address;
|
||||||
}
|
}
|
||||||
|
|
||||||
public function setDisplayName($display_name) {
|
public function setDisplayName($display_name) {
|
||||||
|
@ -89,7 +91,7 @@ final class PhutilEmailAddress extends Phobject {
|
||||||
|
|
||||||
public function getAddress() {
|
public function getAddress() {
|
||||||
$address = $this->localPart;
|
$address = $this->localPart;
|
||||||
if (strlen($this->domainName)) {
|
if ($this->domainName !== null && strlen($this->domainName)) {
|
||||||
$address .= '@'.$this->domainName;
|
$address .= '@'.$this->domainName;
|
||||||
}
|
}
|
||||||
return $address;
|
return $address;
|
||||||
|
|
|
@ -154,9 +154,9 @@ final class PhutilURI extends Phobject {
|
||||||
|
|
||||||
$user = $this->user;
|
$user = $this->user;
|
||||||
$pass = $this->pass;
|
$pass = $this->pass;
|
||||||
if (strlen($user) && strlen($pass)) {
|
if (phutil_nonempty_string($user) && phutil_nonempty_string($pass)) {
|
||||||
$auth = rawurlencode($user).':'.rawurlencode($pass).'@';
|
$auth = rawurlencode($user).':'.rawurlencode($pass).'@';
|
||||||
} else if (strlen($user)) {
|
} else if (phutil_nonempty_string($user)) {
|
||||||
$auth = rawurlencode($user).'@';
|
$auth = rawurlencode($user).'@';
|
||||||
} else {
|
} else {
|
||||||
$auth = null;
|
$auth = null;
|
||||||
|
@ -166,19 +166,24 @@ final class PhutilURI extends Phobject {
|
||||||
if ($this->isGitURI()) {
|
if ($this->isGitURI()) {
|
||||||
$protocol = null;
|
$protocol = null;
|
||||||
} else {
|
} else {
|
||||||
if (strlen($auth)) {
|
if ($auth !== null) {
|
||||||
$protocol = nonempty($this->protocol, 'http');
|
$protocol = nonempty($this->protocol, 'http');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strlen($protocol) || strlen($auth) || strlen($domain)) {
|
$has_protocol = ($protocol !== null) && strlen($protocol);
|
||||||
|
$has_auth = ($auth !== null);
|
||||||
|
$has_domain = ($domain !== null) && strlen($domain);
|
||||||
|
$has_port = ($port !== null) && strlen($port);
|
||||||
|
|
||||||
|
if ($has_protocol || $has_auth || $has_domain) {
|
||||||
if ($this->isGitURI()) {
|
if ($this->isGitURI()) {
|
||||||
$prefix = "{$auth}{$domain}";
|
$prefix = "{$auth}{$domain}";
|
||||||
} else {
|
} else {
|
||||||
$prefix = "{$protocol}://{$auth}{$domain}";
|
$prefix = "{$protocol}://{$auth}{$domain}";
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strlen($port)) {
|
if ($has_port) {
|
||||||
$prefix .= ':'.$port;
|
$prefix .= ':'.$port;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -189,7 +194,7 @@ final class PhutilURI extends Phobject {
|
||||||
$query = null;
|
$query = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (strlen($this->getFragment())) {
|
if (phutil_nonempty_string($this->getFragment())) {
|
||||||
$fragment = '#'.$this->getFragment();
|
$fragment = '#'.$this->getFragment();
|
||||||
} else {
|
} else {
|
||||||
$fragment = null;
|
$fragment = null;
|
||||||
|
@ -428,7 +433,7 @@ final class PhutilURI extends Phobject {
|
||||||
if ($this->isGitURI()) {
|
if ($this->isGitURI()) {
|
||||||
// Git URIs use relative paths which do not need to begin with "/".
|
// Git URIs use relative paths which do not need to begin with "/".
|
||||||
} else {
|
} else {
|
||||||
if ($this->domain && strlen($path) && $path[0] !== '/') {
|
if ($this->domain && phutil_nonempty_string($path) && $path[0] !== '/') {
|
||||||
$path = '/'.$path;
|
$path = '/'.$path;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -80,6 +80,7 @@ final class AASTNodeList
|
||||||
|
|
||||||
/* -( Countable )---------------------------------------------------------- */
|
/* -( Countable )---------------------------------------------------------- */
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function count() {
|
public function count() {
|
||||||
return count($this->ids);
|
return count($this->ids);
|
||||||
}
|
}
|
||||||
|
@ -87,22 +88,27 @@ final class AASTNodeList
|
||||||
|
|
||||||
/* -( Iterator )----------------------------------------------------------- */
|
/* -( Iterator )----------------------------------------------------------- */
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function current() {
|
public function current() {
|
||||||
return $this->list[$this->key()];
|
return $this->list[$this->key()];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function key() {
|
public function key() {
|
||||||
return $this->ids[$this->pos];
|
return $this->ids[$this->pos];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function next() {
|
public function next() {
|
||||||
$this->pos++;
|
$this->pos++;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function rewind() {
|
public function rewind() {
|
||||||
$this->pos = 0;
|
$this->pos = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function valid() {
|
public function valid() {
|
||||||
return $this->pos < count($this->ids);
|
return $this->pos < count($this->ids);
|
||||||
}
|
}
|
||||||
|
|
|
@ -625,6 +625,33 @@ final class PhutilArgumentParser extends Phobject {
|
||||||
return $this->specs[$name]->getDefault();
|
return $this->specs[$name]->getDefault();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getArgAsInteger($name) {
|
||||||
|
$value = $this->getArg($name);
|
||||||
|
|
||||||
|
if ($value === null) {
|
||||||
|
return $value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!preg_match('/^-?\d+\z/', $value)) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'Parameter provided to argument "--%s" must be an integer.',
|
||||||
|
$name));
|
||||||
|
}
|
||||||
|
|
||||||
|
$intvalue = (int)$value;
|
||||||
|
|
||||||
|
if (phutil_string_cast($intvalue) !== phutil_string_cast($value)) {
|
||||||
|
throw new PhutilArgumentUsageException(
|
||||||
|
pht(
|
||||||
|
'Parameter provided to argument "--%s" is too large to '.
|
||||||
|
'parse as an integer.',
|
||||||
|
$name));
|
||||||
|
}
|
||||||
|
|
||||||
|
return $intvalue;
|
||||||
|
}
|
||||||
|
|
||||||
public function getUnconsumedArgumentVector() {
|
public function getUnconsumedArgumentVector() {
|
||||||
return $this->argv;
|
return $this->argv;
|
||||||
}
|
}
|
||||||
|
@ -769,7 +796,12 @@ final class PhutilArgumentParser extends Phobject {
|
||||||
pht('There is no **%s** workflow.', $workflow_name));
|
pht('There is no **%s** workflow.', $workflow_name));
|
||||||
} else {
|
} else {
|
||||||
$out[] = $this->indent($indent, $workflow->getExamples());
|
$out[] = $this->indent($indent, $workflow->getExamples());
|
||||||
|
|
||||||
|
$synopsis = $workflow->getSynopsis();
|
||||||
|
if ($synopsis !== null) {
|
||||||
$out[] = $this->indent($indent, $workflow->getSynopsis());
|
$out[] = $this->indent($indent, $workflow->getSynopsis());
|
||||||
|
}
|
||||||
|
|
||||||
if ($show_details) {
|
if ($show_details) {
|
||||||
$full_help = $workflow->getHelp();
|
$full_help = $workflow->getHelp();
|
||||||
if ($full_help) {
|
if ($full_help) {
|
||||||
|
@ -800,7 +832,7 @@ final class PhutilArgumentParser extends Phobject {
|
||||||
|
|
||||||
|
|
||||||
private function logMessage($message) {
|
private function logMessage($message) {
|
||||||
fwrite(STDERR, $message);
|
PhutilSystem::writeStderr($message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
21
src/platform/PlatformSymbols.php
Normal file
21
src/platform/PlatformSymbols.php
Normal file
|
@ -0,0 +1,21 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class PlatformSymbols
|
||||||
|
extends Phobject {
|
||||||
|
|
||||||
|
public static function getPlatformClientName() {
|
||||||
|
return 'Arcanist';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getPlatformServerName() {
|
||||||
|
return 'Phabricator';
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function getProductNames() {
|
||||||
|
return array(
|
||||||
|
self::getPlatformClientName(),
|
||||||
|
self::getPlatformServerName(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -110,6 +110,6 @@ final class PhutilConsoleProgressSink
|
||||||
}
|
}
|
||||||
|
|
||||||
private function printLine($line) {
|
private function printLine($line) {
|
||||||
fprintf(STDERR, '%s', $line);
|
PhutilSystem::writeStderr($line);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,204 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
final class ArcanistMercurialCommitSymbolCommitHardpointQuery
|
||||||
|
extends ArcanistWorkflowMercurialHardpointQuery {
|
||||||
|
|
||||||
|
public function getHardpoints() {
|
||||||
|
return array(
|
||||||
|
ArcanistCommitSymbolRef::HARDPOINT_OBJECT,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function canLoadRef(ArcanistRef $ref) {
|
||||||
|
return ($ref instanceof ArcanistCommitSymbolRef);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadHardpoint(array $refs, $hardpoint) {
|
||||||
|
$symbol_map = array();
|
||||||
|
foreach ($refs as $key => $ref) {
|
||||||
|
$symbol_map[$key] = $ref->getSymbol();
|
||||||
|
}
|
||||||
|
|
||||||
|
$symbol_set = array_fuse($symbol_map);
|
||||||
|
foreach ($symbol_set as $symbol) {
|
||||||
|
$this->validateSymbol($symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
$api = $this->getRepositoryAPI();
|
||||||
|
|
||||||
|
// Using "hg log" with repeated "--rev arguments will have the following
|
||||||
|
// behaviors which need accounted for:
|
||||||
|
// 1. If any one revision is invalid then the entire command will fail. To
|
||||||
|
// work around this the revset uses a trick where specifying a pattern
|
||||||
|
// for the bookmark() or tag() predicates instead of a literal won't
|
||||||
|
// result in failure if the pattern isn't found.
|
||||||
|
// 2. Multiple markers that resolve to the same node will only be included
|
||||||
|
// once in the output. Because of this the order of output can't be
|
||||||
|
// relied upon to match up with the requested symbol. To work around
|
||||||
|
// this, the template used must also output any associated symbols to
|
||||||
|
// match back to. Because of this there is no reasonable way to resolve
|
||||||
|
// symbols with Mercurial-supported modifiers such as 'symbol^'.
|
||||||
|
// 3. The working directory can't be identified directly, instead a special
|
||||||
|
// template conditional is used to include 'CWD' as the second item in
|
||||||
|
// the output if the node is also the working directory, or 'NOTCWD'
|
||||||
|
// otherwise. This needs included before the tags/bookmarks in order to
|
||||||
|
// distinguish it from some repository using that same name for a tag or
|
||||||
|
// bookmark.
|
||||||
|
|
||||||
|
$pattern = array();
|
||||||
|
$arguments = array();
|
||||||
|
|
||||||
|
$pattern[] = 'log';
|
||||||
|
|
||||||
|
$pattern[] = '--template %s';
|
||||||
|
$arguments[] = "{rev}\1".
|
||||||
|
"{node}\1".
|
||||||
|
"{ifcontains(rev, revset('parents()'), 'CWD', 'NOTCWD')}\1".
|
||||||
|
"{tags % '{tag}\2'}{bookmarks % '{bookmark}\2'}\3";
|
||||||
|
|
||||||
|
foreach ($symbol_set as $symbol) {
|
||||||
|
// This is the one symbol that wouldn't be a bookmark or tag
|
||||||
|
if ($symbol === '.') {
|
||||||
|
$pattern[] = '--rev .';
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$predicates = array();
|
||||||
|
|
||||||
|
if (ctype_xdigit($symbol)) {
|
||||||
|
// Commit hashes are 40 characters
|
||||||
|
if (strlen($symbol) <= 40) {
|
||||||
|
$predicates[] = hgsprintf('id("%s")', $symbol);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ctype_digit($symbol)) {
|
||||||
|
// This is 2^32-1 which is (typically) the maximum size of an int in
|
||||||
|
// Python -- passing anything higher than this to rev() will result
|
||||||
|
// in a Python exception.
|
||||||
|
if ($symbol <= 2147483647) {
|
||||||
|
$predicates[] = hgsprintf('rev("%s")', $symbol);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Mercurial disallows using numbers as marker names.
|
||||||
|
$re_symbol = preg_quote($symbol);
|
||||||
|
$predicates[] = hgsprintf('bookmark("re:^%s$")', $re_symbol);
|
||||||
|
$predicates[] = hgsprintf('tag("re:^%s$")', $re_symbol);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern[] = '--rev %s';
|
||||||
|
$arguments[] = implode(' or ', $predicates);
|
||||||
|
}
|
||||||
|
|
||||||
|
$pattern = implode(' ', $pattern);
|
||||||
|
array_unshift($arguments, $pattern);
|
||||||
|
|
||||||
|
$future = call_user_func_array(
|
||||||
|
array($api, 'newFuture'),
|
||||||
|
$arguments);
|
||||||
|
|
||||||
|
list($stdout) = (yield $this->yieldFuture($future));
|
||||||
|
|
||||||
|
$lines = explode("\3", $stdout);
|
||||||
|
|
||||||
|
$hash_map = array();
|
||||||
|
$node_list = array();
|
||||||
|
|
||||||
|
foreach ($lines as $line) {
|
||||||
|
$parts = explode("\1", $line, 4);
|
||||||
|
|
||||||
|
if (empty(array_filter($parts))) {
|
||||||
|
continue;
|
||||||
|
} else if (count($parts) === 3) {
|
||||||
|
list($rev, $node, $cwd) = $parts;
|
||||||
|
$markers = array();
|
||||||
|
} else if (count($parts) === 4) {
|
||||||
|
list($rev, $node, $cwd, $markers) = $parts;
|
||||||
|
$markers = array_filter(explode("\2", $markers));
|
||||||
|
} else {
|
||||||
|
throw new Exception(
|
||||||
|
pht('Execution of "hg log" emitted an unexpected line ("%s").',
|
||||||
|
$line));
|
||||||
|
}
|
||||||
|
|
||||||
|
$node_list[] = $node;
|
||||||
|
|
||||||
|
if (in_array($rev, $symbol_set)) {
|
||||||
|
if (!isset($hash_map[$rev])) {
|
||||||
|
$hash_map[$rev] = $node;
|
||||||
|
} else if ($hash_map[$rev] !== $node) {
|
||||||
|
$hash_map[$rev] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($markers as $marker) {
|
||||||
|
if (!isset($hash_map[$marker])) {
|
||||||
|
$hash_map[$marker] = $node;
|
||||||
|
} else if ($hash_map[$marker] !== $node) {
|
||||||
|
$hash_map[$marker] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// The log template will mark the working directory node with 'CWD' which
|
||||||
|
// we insert for the special marker '.' for the working directory, used
|
||||||
|
// by ArcanistMercurialAPI::newCurrentCommitSymbol().
|
||||||
|
if ($cwd === 'CWD') {
|
||||||
|
if (!isset($hash_map['.'])) {
|
||||||
|
$hash_map['.'] = $node;
|
||||||
|
} else if ($hash_map['.'] !== $node) {
|
||||||
|
$hash_map['.'] = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Changeset hashes can be prefixes but also collide with other markers.
|
||||||
|
// Consider 'cafe' which could be a bookmark or also a changeset hash
|
||||||
|
// prefix. Mercurial will always allow markers to take precedence over
|
||||||
|
// changeset hashes when resolving, so only populate symbols that match
|
||||||
|
// hashes after all other entries are populated, to avoid the hash taing
|
||||||
|
// a spot which a marker might match.
|
||||||
|
foreach ($node_list as $node) {
|
||||||
|
foreach ($symbol_set as $symbol) {
|
||||||
|
if (strncmp($node, $symbol, strlen($symbol)) === 0) {
|
||||||
|
if (!isset($hash_map[$symbol])) {
|
||||||
|
$hash_map[$symbol] = $node;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Remove entries resulting in collisions, which set empty string values
|
||||||
|
$hash_map = array_filter($hash_map);
|
||||||
|
|
||||||
|
$results = array();
|
||||||
|
foreach ($symbol_map as $key => $symbol) {
|
||||||
|
if (isset($hash_map[$symbol])) {
|
||||||
|
$results[$key] = $hash_map[$symbol];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
foreach ($results as $key => $result) {
|
||||||
|
if ($result === null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
$ref = id(new ArcanistCommitRef())
|
||||||
|
->setCommitHash($result);
|
||||||
|
|
||||||
|
$results[$key] = $ref;
|
||||||
|
}
|
||||||
|
|
||||||
|
yield $this->yieldMap($results);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function validateSymbol($symbol) {
|
||||||
|
if (strpos($symbol, "\n") !== false) {
|
||||||
|
throw new Exception(
|
||||||
|
pht(
|
||||||
|
'Commit symbol "%s" contains a newline. This is not a valid '.
|
||||||
|
'character in a Mercurial commit symbol.',
|
||||||
|
addcslashes($symbol, "\\\n")));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -5,6 +5,13 @@
|
||||||
*/
|
*/
|
||||||
final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mercurial deceptively indicates that the default encoding is UTF-8 however
|
||||||
|
* however the actual default appears to be "something else", at least on
|
||||||
|
* Windows systems. Force all mercurial commands to use UTF-8 encoding.
|
||||||
|
*/
|
||||||
|
const ROOT_HG_COMMAND = 'hg --encoding utf-8 ';
|
||||||
|
|
||||||
private $branch;
|
private $branch;
|
||||||
private $localCommitInfo;
|
private $localCommitInfo;
|
||||||
private $rawDiffCache = array();
|
private $rawDiffCache = array();
|
||||||
|
@ -13,25 +20,24 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
||||||
private $featureFutures = array();
|
private $featureFutures = array();
|
||||||
|
|
||||||
protected function buildLocalFuture(array $argv) {
|
protected function buildLocalFuture(array $argv) {
|
||||||
$env = $this->getMercurialEnvironmentVariables();
|
$argv[0] = self::ROOT_HG_COMMAND.$argv[0];
|
||||||
|
|
||||||
$argv[0] = 'hg '.$argv[0];
|
return $this->newConfiguredFuture(newv('ExecFuture', $argv));
|
||||||
|
|
||||||
$future = newv('ExecFuture', $argv)
|
|
||||||
->setEnv($env)
|
|
||||||
->setCWD($this->getPath());
|
|
||||||
|
|
||||||
return $future;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public function newPassthru($pattern /* , ... */) {
|
public function newPassthru($pattern /* , ... */) {
|
||||||
$args = func_get_args();
|
$args = func_get_args();
|
||||||
|
$args[0] = self::ROOT_HG_COMMAND.$args[0];
|
||||||
|
|
||||||
|
return $this->newConfiguredFuture(newv('PhutilExecPassthru', $args));
|
||||||
|
}
|
||||||
|
|
||||||
|
private function newConfiguredFuture(PhutilExecutableFuture $future) {
|
||||||
|
$args = func_get_args();
|
||||||
|
|
||||||
$env = $this->getMercurialEnvironmentVariables();
|
$env = $this->getMercurialEnvironmentVariables();
|
||||||
|
|
||||||
$args[0] = 'hg '.$args[0];
|
return $future
|
||||||
|
|
||||||
return newv('PhutilExecPassthru', $args)
|
|
||||||
->setEnv($env)
|
->setEnv($env)
|
||||||
->setCWD($this->getPath());
|
->setCWD($this->getPath());
|
||||||
}
|
}
|
||||||
|
@ -448,6 +454,10 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function newCurrentCommitSymbol() {
|
||||||
|
return $this->getWorkingCopyRevision();
|
||||||
|
}
|
||||||
|
|
||||||
public function getWorkingCopyRevision() {
|
public function getWorkingCopyRevision() {
|
||||||
return '.';
|
return '.';
|
||||||
}
|
}
|
||||||
|
@ -655,37 +665,117 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
||||||
public function doCommit($message) {
|
public function doCommit($message) {
|
||||||
$tmp_file = new TempFile();
|
$tmp_file = new TempFile();
|
||||||
Filesystem::writeFile($tmp_file, $message);
|
Filesystem::writeFile($tmp_file, $message);
|
||||||
$this->execxLocal('commit -l %s', $tmp_file);
|
$this->execxLocal('commit --logfile %s', $tmp_file);
|
||||||
$this->reloadWorkingCopy();
|
$this->reloadWorkingCopy();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function amendCommit($message = null) {
|
public function amendCommit($message = null) {
|
||||||
if ($message === null) {
|
$path_statuses = $this->buildUncommittedStatus();
|
||||||
|
|
||||||
|
$existing_message = $this->getCommitMessage(
|
||||||
|
$this->getWorkingCopyRevision());
|
||||||
|
|
||||||
|
if ($message === null || $message == $existing_message) {
|
||||||
|
if (empty($path_statuses)) {
|
||||||
|
// If there are no changes to the working directory and the message is
|
||||||
|
// not being changed then there's nothing to amend. Notably Mercurial
|
||||||
|
// will return an error code if trying to amend a commit with no change
|
||||||
|
// to the commit metadata or file changes.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
$message = $this->getCommitMessage('.');
|
$message = $this->getCommitMessage('.');
|
||||||
}
|
}
|
||||||
|
|
||||||
$tmp_file = new TempFile();
|
$tmp_file = new TempFile();
|
||||||
Filesystem::writeFile($tmp_file, $message);
|
Filesystem::writeFile($tmp_file, $message);
|
||||||
|
|
||||||
|
if ($this->getMercurialFeature('evolve')) {
|
||||||
|
$this->execxLocal('amend --logfile %s --', $tmp_file);
|
||||||
try {
|
try {
|
||||||
$this->execxLocal(
|
$this->execxLocal('evolve --all --');
|
||||||
'commit --amend -l %s',
|
|
||||||
$tmp_file);
|
|
||||||
} catch (CommandException $ex) {
|
} catch (CommandException $ex) {
|
||||||
if (preg_match('/nothing changed/', $ex->getStdout())) {
|
$this->execxLocal('evolve --abort --');
|
||||||
// NOTE: Mercurial considers it an error to make a no-op amend. Although
|
|
||||||
// we generally defer to the underlying VCS to dictate behavior, this
|
|
||||||
// one seems a little goofy, and we use amend as part of various
|
|
||||||
// workflows under the assumption that no-op amends are fine. If this
|
|
||||||
// amend failed because it's a no-op, just continue.
|
|
||||||
} else {
|
|
||||||
throw $ex;
|
throw $ex;
|
||||||
}
|
}
|
||||||
|
$this->reloadWorkingCopy();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the child nodes of the current changeset.
|
||||||
|
list($children) = $this->execxLocal(
|
||||||
|
'log --template %s --rev %s --',
|
||||||
|
'{node} ',
|
||||||
|
'children(.)');
|
||||||
|
$child_nodes = array_filter(explode(' ', $children));
|
||||||
|
|
||||||
|
// For a head commit we can simply use `commit --amend` for both new commit
|
||||||
|
// message and amending changes from the working directory.
|
||||||
|
if (empty($child_nodes)) {
|
||||||
|
$this->execxLocal('commit --amend --logfile %s --', $tmp_file);
|
||||||
|
} else {
|
||||||
|
$this->amendNonHeadCommit($child_nodes, $tmp_file);
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->reloadWorkingCopy();
|
$this->reloadWorkingCopy();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Amends a non-head commit with a new message and file changes. This
|
||||||
|
* strategy is for Mercurial repositories without the evolve extension.
|
||||||
|
*
|
||||||
|
* 1. Run 'arc-amend' which uses Mercurial internals to amend the current
|
||||||
|
* commit with updated message/file-changes. It results in a new commit
|
||||||
|
* from the right parent
|
||||||
|
* 2. For each branch from the original commit, rebase onto the new commit,
|
||||||
|
* removing the original branch. Note that there is potential for this to
|
||||||
|
* cause a conflict but this is something the user has to address.
|
||||||
|
* 3. Strip the original commit.
|
||||||
|
*
|
||||||
|
* @param array The list of child changesets off the original commit.
|
||||||
|
* @param file The file containing the new commit message.
|
||||||
|
*/
|
||||||
|
private function amendNonHeadCommit($child_nodes, $tmp_file) {
|
||||||
|
list($current) = $this->execxLocal(
|
||||||
|
'log --template %s --rev . --',
|
||||||
|
'{node}');
|
||||||
|
|
||||||
|
$this->execxLocalWithExtension(
|
||||||
|
'arc-hg',
|
||||||
|
'arc-amend --logfile %s',
|
||||||
|
$tmp_file);
|
||||||
|
|
||||||
|
list($new_commit) = $this->execxLocal(
|
||||||
|
'log --rev tip --template %s --',
|
||||||
|
'{node}');
|
||||||
|
|
||||||
|
try {
|
||||||
|
$rebase_args = array(
|
||||||
|
'--dest',
|
||||||
|
$new_commit,
|
||||||
|
);
|
||||||
|
foreach ($child_nodes as $child) {
|
||||||
|
$rebase_args[] = '--source';
|
||||||
|
$rebase_args[] = $child;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->execxLocalWithExtension(
|
||||||
|
'rebase',
|
||||||
|
'rebase %Ls --',
|
||||||
|
$rebase_args);
|
||||||
|
} catch (CommandException $ex) {
|
||||||
|
$this->execxLocalWithExtension(
|
||||||
|
'rebase',
|
||||||
|
'rebase --abort --');
|
||||||
|
throw $ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->execxLocalWithExtension(
|
||||||
|
'strip',
|
||||||
|
'strip --rev %s --',
|
||||||
|
$current);
|
||||||
|
}
|
||||||
|
|
||||||
public function getCommitSummary($commit) {
|
public function getCommitSummary($commit) {
|
||||||
if ($commit == 'null') {
|
if ($commit == 'null') {
|
||||||
return pht('(The Empty Void)');
|
return pht('(The Empty Void)');
|
||||||
|
@ -957,6 +1047,129 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
||||||
return $this->executeMercurialFeatureTest($feature, true);
|
return $this->executeMercurialFeatureTest($feature, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the necessary flag for using a Mercurial extension. This will
|
||||||
|
* enable Mercurial built-in extensions and the "arc-hg" extension that is
|
||||||
|
* included with Arcanist. This will not enable other extensions, e.g.
|
||||||
|
* "evolve".
|
||||||
|
*
|
||||||
|
* @param string The name of the extension to enable.
|
||||||
|
* @return string A new command pattern that includes the necessary flags to
|
||||||
|
* enable the specified extension.
|
||||||
|
*/
|
||||||
|
private function getMercurialExtensionFlag($extension) {
|
||||||
|
switch ($extension) {
|
||||||
|
case 'arc-hg':
|
||||||
|
$path = phutil_get_library_root('arcanist');
|
||||||
|
$path = dirname($path);
|
||||||
|
$path = $path.'/support/hg/arc-hg.py';
|
||||||
|
$ext_config = 'extensions.arc-hg='.$path;
|
||||||
|
break;
|
||||||
|
case 'rebase':
|
||||||
|
$ext_config = 'extensions.rebase=';
|
||||||
|
break;
|
||||||
|
case 'shelve':
|
||||||
|
$ext_config = 'extensions.shelve=';
|
||||||
|
break;
|
||||||
|
case 'strip':
|
||||||
|
$ext_config = 'extensions.strip=';
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new Exception(
|
||||||
|
pht('Unknown Mercurial Extension: "%s".', $extension));
|
||||||
|
}
|
||||||
|
|
||||||
|
return csprintf('--config %s', $ext_config);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Produces the arguments that should be passed to Mercurial command
|
||||||
|
* execution that enables a desired extension.
|
||||||
|
*
|
||||||
|
* @param string The name of the extension to enable.
|
||||||
|
* @param string The command pattern that will be run with the extension
|
||||||
|
* enabled.
|
||||||
|
* @param array Parameters for the command pattern argument.
|
||||||
|
* @return array An array where the first item is a Mercurial command
|
||||||
|
* pattern that includes the necessary flag for enabling the
|
||||||
|
* desired extension, and all remaining items are parameters
|
||||||
|
* to that command pattern.
|
||||||
|
*/
|
||||||
|
private function buildMercurialExtensionCommand(
|
||||||
|
$extension,
|
||||||
|
$pattern /* , ... */) {
|
||||||
|
|
||||||
|
$args = func_get_args();
|
||||||
|
|
||||||
|
$pattern_args = array_slice($args, 2);
|
||||||
|
|
||||||
|
$ext_flag = $this->getMercurialExtensionFlag($extension);
|
||||||
|
|
||||||
|
$full_cmd = $ext_flag.' '.$pattern;
|
||||||
|
|
||||||
|
$args = array_merge(
|
||||||
|
array($full_cmd),
|
||||||
|
$pattern_args);
|
||||||
|
|
||||||
|
return $args;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execxLocalWithExtension(
|
||||||
|
$extension,
|
||||||
|
$pattern /* , ... */) {
|
||||||
|
|
||||||
|
$args = func_get_args();
|
||||||
|
$extended_args = call_user_func_array(
|
||||||
|
array($this, 'buildMercurialExtensionCommand'),
|
||||||
|
$args);
|
||||||
|
|
||||||
|
return call_user_func_array(
|
||||||
|
array($this, 'execxLocal'),
|
||||||
|
$extended_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execFutureLocalWithExtension(
|
||||||
|
$extension,
|
||||||
|
$pattern /* , ... */) {
|
||||||
|
|
||||||
|
$args = func_get_args();
|
||||||
|
$extended_args = call_user_func_array(
|
||||||
|
array($this, 'buildMercurialExtensionCommand'),
|
||||||
|
$args);
|
||||||
|
|
||||||
|
return call_user_func_array(
|
||||||
|
array($this, 'execFutureLocal'),
|
||||||
|
$extended_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execPassthruWithExtension(
|
||||||
|
$extension,
|
||||||
|
$pattern /* , ... */) {
|
||||||
|
|
||||||
|
$args = func_get_args();
|
||||||
|
$extended_args = call_user_func_array(
|
||||||
|
array($this, 'buildMercurialExtensionCommand'),
|
||||||
|
$args);
|
||||||
|
|
||||||
|
return call_user_func_array(
|
||||||
|
array($this, 'execPassthru'),
|
||||||
|
$extended_args);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function execManualLocalWithExtension(
|
||||||
|
$extension,
|
||||||
|
$pattern /* , ... */) {
|
||||||
|
|
||||||
|
$args = func_get_args();
|
||||||
|
$extended_args = call_user_func_array(
|
||||||
|
array($this, 'buildMercurialExtensionCommand'),
|
||||||
|
$args);
|
||||||
|
|
||||||
|
return call_user_func_array(
|
||||||
|
array($this, 'execManualLocal'),
|
||||||
|
$extended_args);
|
||||||
|
}
|
||||||
|
|
||||||
private function executeMercurialFeatureTest($feature, $resolve) {
|
private function executeMercurialFeatureTest($feature, $resolve) {
|
||||||
if (array_key_exists($feature, $this->featureResults)) {
|
if (array_key_exists($feature, $this->featureResults)) {
|
||||||
return $this->featureResults[$feature];
|
return $this->featureResults[$feature];
|
||||||
|
@ -982,8 +1195,9 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
||||||
private function newMercurialFeatureFuture($feature) {
|
private function newMercurialFeatureFuture($feature) {
|
||||||
switch ($feature) {
|
switch ($feature) {
|
||||||
case 'shelve':
|
case 'shelve':
|
||||||
return $this->execFutureLocal(
|
return $this->execFutureLocalWithExtension(
|
||||||
'--config extensions.shelve= shelve --help --');
|
'shelve',
|
||||||
|
'shelve --help --');
|
||||||
case 'evolve':
|
case 'evolve':
|
||||||
return $this->execFutureLocal('prune --help --');
|
return $this->execFutureLocal('prune --help --');
|
||||||
default:
|
default:
|
||||||
|
@ -1017,17 +1231,6 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
||||||
return new ArcanistMercurialRepositoryRemoteQuery();
|
return new ArcanistMercurialRepositoryRemoteQuery();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function getMercurialExtensionArguments() {
|
|
||||||
$path = phutil_get_library_root('arcanist');
|
|
||||||
$path = dirname($path);
|
|
||||||
$path = $path.'/support/hg/arc-hg.py';
|
|
||||||
|
|
||||||
return array(
|
|
||||||
'--config',
|
|
||||||
'extensions.arc-hg='.$path,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function newNormalizedURI($uri) {
|
protected function newNormalizedURI($uri) {
|
||||||
return new ArcanistRepositoryURINormalizer(
|
return new ArcanistRepositoryURINormalizer(
|
||||||
ArcanistRepositoryURINormalizer::TYPE_MERCURIAL,
|
ArcanistRepositoryURINormalizer::TYPE_MERCURIAL,
|
||||||
|
|
|
@ -19,12 +19,6 @@ final class ArcanistMercurialRepositoryMarkerQuery
|
||||||
// to provide a command which works like "git for-each-ref" locally and
|
// to provide a command which works like "git for-each-ref" locally and
|
||||||
// "git ls-remote" when given a remote.
|
// "git ls-remote" when given a remote.
|
||||||
|
|
||||||
$argv = array();
|
|
||||||
foreach ($api->getMercurialExtensionArguments() as $arg) {
|
|
||||||
$argv[] = $arg;
|
|
||||||
}
|
|
||||||
$argv[] = 'arc-ls-markers';
|
|
||||||
|
|
||||||
// NOTE: In remote mode, we're using passthru and a tempfile on this
|
// NOTE: In remote mode, we're using passthru and a tempfile on this
|
||||||
// because it's a remote command and may prompt the user to provide
|
// because it's a remote command and may prompt the user to provide
|
||||||
// credentials interactively. In local mode, we can just read stdout.
|
// credentials interactively. In local mode, we can just read stdout.
|
||||||
|
@ -33,20 +27,17 @@ final class ArcanistMercurialRepositoryMarkerQuery
|
||||||
$tmpfile = new TempFile();
|
$tmpfile = new TempFile();
|
||||||
Filesystem::remove($tmpfile);
|
Filesystem::remove($tmpfile);
|
||||||
|
|
||||||
|
$argv = array();
|
||||||
$argv[] = '--output';
|
$argv[] = '--output';
|
||||||
$argv[] = phutil_string_cast($tmpfile);
|
$argv[] = phutil_string_cast($tmpfile);
|
||||||
}
|
|
||||||
|
|
||||||
$argv[] = '--';
|
$argv[] = '--';
|
||||||
|
|
||||||
if ($remote !== null) {
|
|
||||||
$argv[] = $remote->getRemoteName();
|
$argv[] = $remote->getRemoteName();
|
||||||
}
|
|
||||||
|
|
||||||
if ($remote !== null) {
|
$err = $api->execPassthruWithExtension(
|
||||||
$passthru = $api->newPassthru('%Ls', $argv);
|
'arc-hg',
|
||||||
|
'arc-ls-markers %Ls',
|
||||||
|
$argv);
|
||||||
|
|
||||||
$err = $passthru->execute();
|
|
||||||
if ($err) {
|
if ($err) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
|
@ -57,8 +48,10 @@ final class ArcanistMercurialRepositoryMarkerQuery
|
||||||
$raw_data = Filesystem::readFile($tmpfile);
|
$raw_data = Filesystem::readFile($tmpfile);
|
||||||
unset($tmpfile);
|
unset($tmpfile);
|
||||||
} else {
|
} else {
|
||||||
$future = $api->newFuture('%Ls', $argv);
|
$future = $api->execFutureLocalWithExtension(
|
||||||
list($raw_data) = $future->resolve();
|
'arc-hg',
|
||||||
|
'arc-ls-markers --');
|
||||||
|
list($err, $raw_data) = $future->resolve();
|
||||||
}
|
}
|
||||||
|
|
||||||
$items = phutil_json_decode($raw_data);
|
$items = phutil_json_decode($raw_data);
|
||||||
|
|
|
@ -64,9 +64,11 @@ abstract class ArcanistRepositoryMarkerQuery
|
||||||
$marker->attachWorkingCopyStateRef($state_ref);
|
$marker->attachWorkingCopyStateRef($state_ref);
|
||||||
|
|
||||||
$hash = $marker->getCommitHash();
|
$hash = $marker->getCommitHash();
|
||||||
|
if ($hash !== null) {
|
||||||
$hash = $api->getDisplayHash($hash);
|
$hash = $api->getDisplayHash($hash);
|
||||||
$marker->setDisplayHash($hash);
|
$marker->setDisplayHash($hash);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
$types = $this->markerTypes;
|
$types = $this->markerTypes;
|
||||||
if ($types !== null) {
|
if ($types !== null) {
|
||||||
|
|
|
@ -7,6 +7,14 @@ final class ArcanistMercurialLocalState
|
||||||
private $localBranch;
|
private $localBranch;
|
||||||
private $localBookmark;
|
private $localBookmark;
|
||||||
|
|
||||||
|
public function getLocalCommit() {
|
||||||
|
return $this->localCommit;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getLocalBookmark() {
|
||||||
|
return $this->localBookmark;
|
||||||
|
}
|
||||||
|
|
||||||
protected function executeSaveLocalState() {
|
protected function executeSaveLocalState() {
|
||||||
$api = $this->getRepositoryAPI();
|
$api = $this->getRepositoryAPI();
|
||||||
$log = $this->getWorkflow()->getLogEngine();
|
$log = $this->getWorkflow()->getLogEngine();
|
||||||
|
@ -152,8 +160,9 @@ final class ArcanistMercurialLocalState
|
||||||
'arc-%s',
|
'arc-%s',
|
||||||
Filesystem::readRandomCharacters(12));
|
Filesystem::readRandomCharacters(12));
|
||||||
|
|
||||||
$api->execxLocal(
|
$api->execxLocalWithExtension(
|
||||||
'--config extensions.shelve= shelve --unknown --name %s --',
|
'shelve',
|
||||||
|
'shelve --unknown --name %s --',
|
||||||
$stash_ref);
|
$stash_ref);
|
||||||
|
|
||||||
$log->writeStatus(
|
$log->writeStatus(
|
||||||
|
@ -171,16 +180,18 @@ final class ArcanistMercurialLocalState
|
||||||
pht('UNSHELVE'),
|
pht('UNSHELVE'),
|
||||||
pht('Restoring uncommitted changes to working copy.'));
|
pht('Restoring uncommitted changes to working copy.'));
|
||||||
|
|
||||||
$api->execxLocal(
|
$api->execxLocalWithExtension(
|
||||||
'--config extensions.shelve= unshelve --keep --name %s --',
|
'shelve',
|
||||||
|
'unshelve --keep --name %s --',
|
||||||
$stash_ref);
|
$stash_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function discardStash($stash_ref) {
|
protected function discardStash($stash_ref) {
|
||||||
$api = $this->getRepositoryAPI();
|
$api = $this->getRepositoryAPI();
|
||||||
|
|
||||||
$api->execxLocal(
|
$api->execxLocalWithExtension(
|
||||||
'--config extensions.shelve= shelve --delete %s --',
|
'shelve',
|
||||||
|
'shelve --delete %s --',
|
||||||
$stash_ref);
|
$stash_ref);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -192,10 +192,28 @@ abstract class ArcanistRepositoryLocalState
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stash uncommitted changes temporarily. Use {@method:restoreStash()} to
|
||||||
|
* bring these changes back.
|
||||||
|
*
|
||||||
|
* Note that saving and restoring changes may not behave as expected if used
|
||||||
|
* in a non-stack manner, i.e. proper use involves only restoring stashes in
|
||||||
|
* the reverse order they were saved.
|
||||||
|
*
|
||||||
|
* @return wild A reference object that refers to the changes which were
|
||||||
|
* saved. When restoring changes this should be passed to
|
||||||
|
* {@method:restoreStash()}.
|
||||||
|
*/
|
||||||
protected function saveStash() {
|
protected function saveStash() {
|
||||||
throw new PhutilMethodNotImplementedException();
|
throw new PhutilMethodNotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Restores changes that were previously stashed by {@method:saveStash()}.
|
||||||
|
*
|
||||||
|
* @param wild A reference object referring to which previously stashed
|
||||||
|
* changes to restore, from invoking {@method:saveStash()}.
|
||||||
|
*/
|
||||||
protected function restoreStash($ref) {
|
protected function restoreStash($ref) {
|
||||||
throw new PhutilMethodNotImplementedException();
|
throw new PhutilMethodNotImplementedException();
|
||||||
}
|
}
|
||||||
|
|
|
@ -270,9 +270,9 @@ final class ArcanistRuntime {
|
||||||
$problems[] = sprintf(
|
$problems[] = sprintf(
|
||||||
'The build of PHP you are running was compiled with the configure '.
|
'The build of PHP you are running was compiled with the configure '.
|
||||||
'flag "%s", which means it does not support the function "%s()". '.
|
'flag "%s", which means it does not support the function "%s()". '.
|
||||||
'This function is required for Arcanist to run. Install a standard '.
|
'This function is required for this software to run. Install a '.
|
||||||
'build of PHP or rebuild it without this flag. You may also be '.
|
'standard build of PHP or rebuild it without this flag. You may '.
|
||||||
'able to build or install the relevant extension separately.',
|
'also be able to build or install the relevant extension separately.',
|
||||||
$which,
|
$which,
|
||||||
$fname);
|
$fname);
|
||||||
continue;
|
continue;
|
||||||
|
@ -477,8 +477,8 @@ final class ArcanistRuntime {
|
||||||
$log->writeWarn(
|
$log->writeWarn(
|
||||||
pht('VERY META'),
|
pht('VERY META'),
|
||||||
pht(
|
pht(
|
||||||
'You are running one copy of Arcanist (at path "%s") against '.
|
'You are running one copy of this software (at path "%s") against '.
|
||||||
'another copy of Arcanist (at path "%s"). Code in the current '.
|
'another copy of this software (at path "%s"). Code in the current '.
|
||||||
'working directory will not be loaded or executed.',
|
'working directory will not be loaded or executed.',
|
||||||
$executing_directory,
|
$executing_directory,
|
||||||
$working_directory));
|
$working_directory));
|
||||||
|
@ -519,10 +519,10 @@ final class ArcanistRuntime {
|
||||||
if (!isset($toolsets[$binary])) {
|
if (!isset($toolsets[$binary])) {
|
||||||
throw new PhutilArgumentUsageException(
|
throw new PhutilArgumentUsageException(
|
||||||
pht(
|
pht(
|
||||||
'Arcanist toolset "%s" is unknown. The Arcanist binary should '.
|
'Toolset "%s" is unknown. The binary should be executed so that '.
|
||||||
'be executed so that "argv[0]" identifies a supported toolset. '.
|
'"argv[0]" identifies a supported toolset. Rename the binary or '.
|
||||||
'Rename the binary or install the library that provides the '.
|
'install the library that provides the desired toolset. Current '.
|
||||||
'desired toolset. Current available toolsets: %s.',
|
'available toolsets: %s.',
|
||||||
$binary,
|
$binary,
|
||||||
implode(', ', array_keys($toolsets))));
|
implode(', ', array_keys($toolsets))));
|
||||||
}
|
}
|
||||||
|
|
|
@ -137,7 +137,7 @@ final class PhutilServiceProfiler extends Phobject {
|
||||||
|
|
||||||
$uri = phutil_censor_credentials($data['uri']);
|
$uri = phutil_censor_credentials($data['uri']);
|
||||||
|
|
||||||
if (strlen($proxy)) {
|
if ($proxy !== null) {
|
||||||
$desc = "{$proxy} >> {$uri}";
|
$desc = "{$proxy} >> {$uri}";
|
||||||
} else {
|
} else {
|
||||||
$desc = $uri;
|
$desc = $uri;
|
||||||
|
@ -203,6 +203,10 @@ final class PhutilServiceProfiler extends Phobject {
|
||||||
}
|
}
|
||||||
|
|
||||||
private static function escapeProfilerStringForDisplay($string) {
|
private static function escapeProfilerStringForDisplay($string) {
|
||||||
|
if ($string === null) {
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
// Convert tabs and newlines to spaces and collapse blocks of whitespace,
|
// Convert tabs and newlines to spaces and collapse blocks of whitespace,
|
||||||
// most often formatting in queries.
|
// most often formatting in queries.
|
||||||
$string = preg_replace('/\s{2,}/', ' ', $string);
|
$string = preg_replace('/\s{2,}/', ' ', $string);
|
||||||
|
|
|
@ -226,8 +226,8 @@ final class PhutilClassMapQuery extends Phobject {
|
||||||
$unique = $this->uniqueMethod;
|
$unique = $this->uniqueMethod;
|
||||||
$sort = $this->sortMethod;
|
$sort = $this->sortMethod;
|
||||||
|
|
||||||
if (strlen($expand)) {
|
if ($expand !== null) {
|
||||||
if (!strlen($unique)) {
|
if ($unique === null) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
'Trying to execute a class map query for descendants of class '.
|
'Trying to execute a class map query for descendants of class '.
|
||||||
|
@ -245,7 +245,7 @@ final class PhutilClassMapQuery extends Phobject {
|
||||||
->loadObjects();
|
->loadObjects();
|
||||||
|
|
||||||
// Apply the "expand" mechanism, if it is configured.
|
// Apply the "expand" mechanism, if it is configured.
|
||||||
if (strlen($expand)) {
|
if ($expand !== null) {
|
||||||
$list = array();
|
$list = array();
|
||||||
foreach ($objects as $object) {
|
foreach ($objects as $object) {
|
||||||
foreach (call_user_func(array($object, $expand)) as $instance) {
|
foreach (call_user_func(array($object, $expand)) as $instance) {
|
||||||
|
@ -257,7 +257,7 @@ final class PhutilClassMapQuery extends Phobject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the "unique" mechanism, if it is configured.
|
// Apply the "unique" mechanism, if it is configured.
|
||||||
if (strlen($unique)) {
|
if ($unique !== null) {
|
||||||
$map = array();
|
$map = array();
|
||||||
foreach ($list as $object) {
|
foreach ($list as $object) {
|
||||||
$key = call_user_func(array($object, $unique));
|
$key = call_user_func(array($object, $unique));
|
||||||
|
@ -287,12 +287,12 @@ final class PhutilClassMapQuery extends Phobject {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the "filter" mechanism, if it is configured.
|
// Apply the "filter" mechanism, if it is configured.
|
||||||
if (strlen($filter)) {
|
if ($filter !== null) {
|
||||||
$map = mfilter($map, $filter);
|
$map = mfilter($map, $filter);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Apply the "sort" mechanism, if it is configured.
|
// Apply the "sort" mechanism, if it is configured.
|
||||||
if (strlen($sort)) {
|
if ($sort !== null) {
|
||||||
if ($map) {
|
if ($map) {
|
||||||
// The "sort" method may return scalars (which we want to sort with
|
// The "sort" method may return scalars (which we want to sort with
|
||||||
// "msort()"), or may return PhutilSortVector objects (which we want
|
// "msort()"), or may return PhutilSortVector objects (which we want
|
||||||
|
|
|
@ -296,11 +296,12 @@ final class PhutilSymbolLoader {
|
||||||
// library without breaking library startup.
|
// library without breaking library startup.
|
||||||
if ($should_continue) {
|
if ($should_continue) {
|
||||||
// We may not have `pht()` yet.
|
// We may not have `pht()` yet.
|
||||||
fprintf(
|
$message = sprintf(
|
||||||
STDERR,
|
|
||||||
"%s: %s\n",
|
"%s: %s\n",
|
||||||
'IGNORING CLASS LOAD FAILURE',
|
'IGNORING CLASS LOAD FAILURE',
|
||||||
$caught->getMessage());
|
$caught->getMessage());
|
||||||
|
|
||||||
|
@file_put_contents('php://stderr', $message);
|
||||||
} else {
|
} else {
|
||||||
throw $caught;
|
throw $caught;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ final class ArcanistArcToolset extends ArcanistToolset {
|
||||||
array(
|
array(
|
||||||
'name' => 'conduit-uri',
|
'name' => 'conduit-uri',
|
||||||
'param' => 'uri',
|
'param' => 'uri',
|
||||||
'help' => pht('Connect to Phabricator install specified by __uri__.'),
|
'help' => pht('Connect to server specified by __uri__.'),
|
||||||
),
|
),
|
||||||
array(
|
array(
|
||||||
'name' => 'conduit-token',
|
'name' => 'conduit-token',
|
||||||
|
|
|
@ -14,7 +14,7 @@ final class ArcanistShellCompleteWorkflow
|
||||||
public function getWorkflowInformation() {
|
public function getWorkflowInformation() {
|
||||||
$help = pht(<<<EOTEXT
|
$help = pht(<<<EOTEXT
|
||||||
Install shell completion so you can use the "tab" key to autocomplete
|
Install shell completion so you can use the "tab" key to autocomplete
|
||||||
commands and flags in your shell for Arcanist toolsets and workflows.
|
commands and flags in your shell for toolsets and workflows.
|
||||||
|
|
||||||
The **bash** shell is supported.
|
The **bash** shell is supported.
|
||||||
|
|
||||||
|
@ -28,7 +28,7 @@ This will install shell completion into your current shell. After installing,
|
||||||
you may need to start a new shell (or open a new terminal window) to pick up
|
you may need to start a new shell (or open a new terminal window) to pick up
|
||||||
the updated configuration.
|
the updated configuration.
|
||||||
|
|
||||||
Once installed, completion should work across all Arcanist toolsets.
|
Once installed, completion should work across all toolsets.
|
||||||
|
|
||||||
**Using Completion**
|
**Using Completion**
|
||||||
|
|
||||||
|
@ -53,9 +53,9 @@ You can update shell completion without reinstalling it by running:
|
||||||
|
|
||||||
You may need to update shell completion if:
|
You may need to update shell completion if:
|
||||||
|
|
||||||
- you install new Arcanist toolsets; or
|
- you install new toolsets; or
|
||||||
- you move the Arcanist directory; or
|
- you move this software on disk; or
|
||||||
- you upgrade Arcanist and the new version fixes shell completion bugs.
|
- you upgrade this software and the new version fixes shell completion bugs.
|
||||||
EOTEXT
|
EOTEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -170,16 +170,14 @@ final class PhutilUnitTestEngine extends ArcanistUnitTestEngine {
|
||||||
if (!$library_name) {
|
if (!$library_name) {
|
||||||
throw new Exception(
|
throw new Exception(
|
||||||
pht(
|
pht(
|
||||||
"Attempting to run unit tests on a libphutil library which has ".
|
"Attempting to run unit tests on a library which has ".
|
||||||
"not been loaded, at:\n\n".
|
"not been loaded, at:\n\n".
|
||||||
" %s\n\n".
|
" %s\n\n".
|
||||||
"This probably means one of two things:\n\n".
|
"Make sure this library is configured to load.\n\n".
|
||||||
" - You may need to add this library to %s.\n".
|
"(In rare cases, this may be because you are attempting to run ".
|
||||||
" - You may be running tests on a copy of libphutil or ".
|
"one copy of this software against a different copy of this ".
|
||||||
"arcanist using a different copy of libphutil or arcanist. ".
|
"software. This operation is not supported.)",
|
||||||
"This operation is not supported.\n",
|
$library_root));
|
||||||
$library_root,
|
|
||||||
'.arcconfig.'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$path = Filesystem::resolvePath($path, $root);
|
$path = Filesystem::resolvePath($path, $root);
|
||||||
|
|
|
@ -154,6 +154,147 @@ abstract class PhutilTestCase extends Phobject {
|
||||||
throw new PhutilTestSkippedException($message);
|
throw new PhutilTestSkippedException($message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final protected function assertCaught(
|
||||||
|
$expect,
|
||||||
|
$actual,
|
||||||
|
$message = null) {
|
||||||
|
|
||||||
|
if ($message !== null) {
|
||||||
|
$message = phutil_string_cast($message);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($actual === null) {
|
||||||
|
// This is okay: no exception.
|
||||||
|
} else if ($actual instanceof Exception) {
|
||||||
|
// This is also okay.
|
||||||
|
} else if ($actual instanceof Throwable) {
|
||||||
|
// And this is okay too.
|
||||||
|
} else {
|
||||||
|
// Anything else is no good.
|
||||||
|
|
||||||
|
if ($message !== null) {
|
||||||
|
$output = pht(
|
||||||
|
'Call to "assertCaught(..., <junk>, ...)" for test case "%s" '.
|
||||||
|
'passed bad value for test result. Expected null, Exception, '.
|
||||||
|
'or Throwable; got: %s.',
|
||||||
|
$message,
|
||||||
|
phutil_describe_type($actual));
|
||||||
|
} else {
|
||||||
|
$output = pht(
|
||||||
|
'Call to "assertCaught(..., <junk>, ...)" passed bad value for '.
|
||||||
|
'test result. Expected null, Exception, or Throwable; got: %s.',
|
||||||
|
phutil_describe_type($actual));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->failTest($output);
|
||||||
|
|
||||||
|
throw new PhutilTestTerminatedException($output);
|
||||||
|
}
|
||||||
|
|
||||||
|
$expect_list = null;
|
||||||
|
|
||||||
|
if ($expect === false) {
|
||||||
|
$expect_list = array();
|
||||||
|
} else if ($expect === true) {
|
||||||
|
$expect_list = array(
|
||||||
|
'Exception',
|
||||||
|
'Throwable',
|
||||||
|
);
|
||||||
|
} else if (is_string($expect) || is_array($expect)) {
|
||||||
|
$list = (array)$expect;
|
||||||
|
|
||||||
|
$items_ok = true;
|
||||||
|
foreach ($list as $key => $item) {
|
||||||
|
if (!phutil_nonempty_stringlike($item)) {
|
||||||
|
$items_ok = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$list[$key] = phutil_string_cast($item);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($items_ok) {
|
||||||
|
$expect_list = $list;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($expect_list === null) {
|
||||||
|
if ($message !== null) {
|
||||||
|
$output = pht(
|
||||||
|
'Call to "assertCaught(<junk>, ...)" for test case "%s" '.
|
||||||
|
'passed bad expected value. Expected bool, class name as a string, '.
|
||||||
|
'or a list of class names. Got: %s.',
|
||||||
|
$message,
|
||||||
|
phutil_describe_type($expect));
|
||||||
|
} else {
|
||||||
|
$output = pht(
|
||||||
|
'Call to "assertCaught(<junk>, ...)" passed bad expected value. '.
|
||||||
|
'expected result. Expected null, Exception, or Throwable; got: %s.',
|
||||||
|
phutil_describe_type($expect));
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->failTest($output);
|
||||||
|
|
||||||
|
throw new PhutilTestTerminatedException($output);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($actual === null) {
|
||||||
|
$is_match = !$expect_list;
|
||||||
|
} else {
|
||||||
|
$is_match = false;
|
||||||
|
foreach ($expect_list as $exception_class) {
|
||||||
|
if ($actual instanceof $exception_class) {
|
||||||
|
$is_match = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($is_match) {
|
||||||
|
$this->assertions++;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$caller = self::getCallerInfo();
|
||||||
|
$file = $caller['file'];
|
||||||
|
$line = $caller['line'];
|
||||||
|
|
||||||
|
$output = array();
|
||||||
|
|
||||||
|
if ($message !== null) {
|
||||||
|
$output[] = pht(
|
||||||
|
'Assertion of caught exception failed (at %s:%d in test case "%s").',
|
||||||
|
$file,
|
||||||
|
$line,
|
||||||
|
$message);
|
||||||
|
} else {
|
||||||
|
$output[] = pht(
|
||||||
|
'Assertion of caught exception failed (at %s:%d).',
|
||||||
|
$file,
|
||||||
|
$line);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($actual === null) {
|
||||||
|
$output[] = pht('Expected any exception, got no exception.');
|
||||||
|
} else if (!$expect_list) {
|
||||||
|
$output[] = pht(
|
||||||
|
'Expected no exception, got exception of class "%s".',
|
||||||
|
get_class($actual));
|
||||||
|
} else {
|
||||||
|
$expected_classes = implode(', ', $expect_list);
|
||||||
|
$output[] = pht(
|
||||||
|
'Expected exception (in class(es): %s), got exception of class "%s".',
|
||||||
|
$expected_classes,
|
||||||
|
get_class($actual));
|
||||||
|
}
|
||||||
|
|
||||||
|
$output = implode("\n\n", $output);
|
||||||
|
|
||||||
|
$this->failTest($output);
|
||||||
|
|
||||||
|
throw new PhutilTestTerminatedException($output);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/* -( Exception Handling )------------------------------------------------- */
|
/* -( Exception Handling )------------------------------------------------- */
|
||||||
|
|
||||||
|
|
|
@ -65,9 +65,10 @@ final class ArcanistUnitConsoleRenderer extends ArcanistUnitRenderer {
|
||||||
50 => "<fg:green>%s</fg><fg:yellow>{$star}</fg> ",
|
50 => "<fg:green>%s</fg><fg:yellow>{$star}</fg> ",
|
||||||
200 => '<fg:green>%s</fg> ',
|
200 => '<fg:green>%s</fg> ',
|
||||||
500 => '<fg:yellow>%s</fg> ',
|
500 => '<fg:yellow>%s</fg> ',
|
||||||
INF => '<fg:red>%s</fg> ',
|
|
||||||
);
|
);
|
||||||
|
|
||||||
|
$least_acceptable = '<fg:red>%s</fg> ';
|
||||||
|
|
||||||
$milliseconds = $seconds * 1000;
|
$milliseconds = $seconds * 1000;
|
||||||
$duration = $this->formatTime($seconds);
|
$duration = $this->formatTime($seconds);
|
||||||
foreach ($acceptableness as $upper_bound => $formatting) {
|
foreach ($acceptableness as $upper_bound => $formatting) {
|
||||||
|
@ -75,7 +76,8 @@ final class ArcanistUnitConsoleRenderer extends ArcanistUnitRenderer {
|
||||||
return phutil_console_format($formatting, $duration);
|
return phutil_console_format($formatting, $duration);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return phutil_console_format(end($acceptableness), $duration);
|
|
||||||
|
return phutil_console_format($least_acceptable, $duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
private function formatTime($seconds) {
|
private function formatTime($seconds) {
|
||||||
|
|
|
@ -313,7 +313,7 @@ final class ArcanistFileUploader extends Phobject {
|
||||||
* @task internal
|
* @task internal
|
||||||
*/
|
*/
|
||||||
private function writeStatus($message) {
|
private function writeStatus($message) {
|
||||||
fwrite(STDERR, $message."\n");
|
PhutilSystem::writeStderr($message."\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ abstract class PhutilArray
|
||||||
/* -( Countable Interface )------------------------------------------------ */
|
/* -( Countable Interface )------------------------------------------------ */
|
||||||
|
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function count() {
|
public function count() {
|
||||||
return count($this->data);
|
return count($this->data);
|
||||||
}
|
}
|
||||||
|
@ -37,22 +38,27 @@ abstract class PhutilArray
|
||||||
/* -( Iterator Interface )------------------------------------------------- */
|
/* -( Iterator Interface )------------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function current() {
|
public function current() {
|
||||||
return current($this->data);
|
return current($this->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function key() {
|
public function key() {
|
||||||
return key($this->data);
|
return key($this->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function next() {
|
public function next() {
|
||||||
return next($this->data);
|
return next($this->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function rewind() {
|
public function rewind() {
|
||||||
reset($this->data);
|
reset($this->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function valid() {
|
public function valid() {
|
||||||
return (key($this->data) !== null);
|
return (key($this->data) !== null);
|
||||||
}
|
}
|
||||||
|
@ -61,18 +67,22 @@ abstract class PhutilArray
|
||||||
/* -( ArrayAccess Interface )---------------------------------------------- */
|
/* -( ArrayAccess Interface )---------------------------------------------- */
|
||||||
|
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function offsetExists($key) {
|
public function offsetExists($key) {
|
||||||
return array_key_exists($key, $this->data);
|
return array_key_exists($key, $this->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function offsetGet($key) {
|
public function offsetGet($key) {
|
||||||
return $this->data[$key];
|
return $this->data[$key];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function offsetSet($key, $value) {
|
public function offsetSet($key, $value) {
|
||||||
$this->data[$key] = $value;
|
$this->data[$key] = $value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function offsetUnset($key) {
|
public function offsetUnset($key) {
|
||||||
unset($this->data[$key]);
|
unset($this->data[$key]);
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,6 +18,7 @@ final class PhutilCallbackFilterIterator extends FilterIterator {
|
||||||
$this->callback = $callback;
|
$this->callback = $callback;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[\ReturnTypeWillChange]
|
||||||
public function accept() {
|
public function accept() {
|
||||||
return call_user_func($this->callback, $this->current());
|
return call_user_func($this->callback, $this->current());
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,10 +3,73 @@
|
||||||
/**
|
/**
|
||||||
* Interact with the operating system.
|
* Interact with the operating system.
|
||||||
*
|
*
|
||||||
|
* @task stdio Interacting with Standard I/O
|
||||||
* @task memory Interacting with System Memory
|
* @task memory Interacting with System Memory
|
||||||
*/
|
*/
|
||||||
final class PhutilSystem extends Phobject {
|
final class PhutilSystem extends Phobject {
|
||||||
|
|
||||||
|
private static $stdin = false;
|
||||||
|
private static $stderr = false;
|
||||||
|
private static $stdout = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task stdio
|
||||||
|
*/
|
||||||
|
public static function getStdinHandle() {
|
||||||
|
if (self::$stdin === false) {
|
||||||
|
self::$stdin = self::getStdioHandle('STDIN');
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$stdin;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task stdio
|
||||||
|
*/
|
||||||
|
public static function getStdoutHandle() {
|
||||||
|
if (self::$stdout === false) {
|
||||||
|
self::$stdout = self::getStdioHandle('STDOUT');
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$stdout;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task stdio
|
||||||
|
*/
|
||||||
|
public static function getStderrHandle() {
|
||||||
|
if (self::$stderr === false) {
|
||||||
|
self::$stderr = self::getStdioHandle('STDERR');
|
||||||
|
}
|
||||||
|
|
||||||
|
return self::$stderr;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task stdio
|
||||||
|
*/
|
||||||
|
public static function writeStderr($message) {
|
||||||
|
$stderr = self::getStderrHandle();
|
||||||
|
|
||||||
|
if ($stderr === null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$message = phutil_string_cast($message);
|
||||||
|
@fwrite($stderr, $message);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @task stdio
|
||||||
|
*/
|
||||||
|
private static function getStdioHandle($ref) {
|
||||||
|
if (defined($ref)) {
|
||||||
|
return constant($ref);
|
||||||
|
}
|
||||||
|
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get information about total and free memory on the system.
|
* Get information about total and free memory on the system.
|
||||||
|
|
|
@ -1000,4 +1000,74 @@ final class PhutilUtilsTestCase extends PhutilTestCase {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function testEmptyStringMethods() {
|
||||||
|
|
||||||
|
$uri = new PhutilURI('http://example.org/');
|
||||||
|
|
||||||
|
$map = array(
|
||||||
|
array(null, false, false, false, 'literal null'),
|
||||||
|
array('', false, false, false, 'empty string'),
|
||||||
|
array('x', true, true, true, 'nonempty string'),
|
||||||
|
array(false, null, null, null, 'bool'),
|
||||||
|
array(1, null, null, true, 'integer'),
|
||||||
|
array($uri, null, true, true, 'uri object'),
|
||||||
|
array(2.5, null, null, true, 'float'),
|
||||||
|
array(array(), null, null, null, 'array'),
|
||||||
|
array((object)array(), null, null, null, 'object'),
|
||||||
|
);
|
||||||
|
|
||||||
|
foreach ($map as $test_case) {
|
||||||
|
$input = $test_case[0];
|
||||||
|
|
||||||
|
$expect_string = $test_case[1];
|
||||||
|
$expect_stringlike = $test_case[2];
|
||||||
|
$expect_scalar = $test_case[3];
|
||||||
|
|
||||||
|
$test_name = $test_case[4];
|
||||||
|
|
||||||
|
$this->executeEmptyStringTest(
|
||||||
|
$input,
|
||||||
|
$expect_string,
|
||||||
|
'phutil_nonempty_string',
|
||||||
|
$test_name);
|
||||||
|
|
||||||
|
$this->executeEmptyStringTest(
|
||||||
|
$input,
|
||||||
|
$expect_stringlike,
|
||||||
|
'phutil_nonempty_stringlike',
|
||||||
|
$test_name);
|
||||||
|
|
||||||
|
$this->executeEmptyStringTest(
|
||||||
|
$input,
|
||||||
|
$expect_scalar,
|
||||||
|
'phutil_nonempty_scalar',
|
||||||
|
$test_name);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private function executeEmptyStringTest($input, $expect, $call, $name) {
|
||||||
|
$name = sprintf('%s(<%s>)', $call, $name);
|
||||||
|
|
||||||
|
$caught = null;
|
||||||
|
try {
|
||||||
|
$actual = call_user_func($call, $input);
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
$caught = $ex;
|
||||||
|
} catch (Throwable $ex) {
|
||||||
|
$caught = $ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($expect === null) {
|
||||||
|
$expect_exceptions = array('InvalidArgumentException');
|
||||||
|
} else {
|
||||||
|
$expect_exceptions = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->assertCaught($expect_exceptions, $caught, $name);
|
||||||
|
if (!$caught) {
|
||||||
|
$this->assertEqual($expect, $actual, $name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -314,6 +314,8 @@ function phutil_utf8_strlen($string) {
|
||||||
* @return int The console display length of the string.
|
* @return int The console display length of the string.
|
||||||
*/
|
*/
|
||||||
function phutil_utf8_console_strlen($string) {
|
function phutil_utf8_console_strlen($string) {
|
||||||
|
$string = phutil_string_cast($string);
|
||||||
|
|
||||||
// Formatting and colors don't contribute any width in the console.
|
// Formatting and colors don't contribute any width in the console.
|
||||||
$string = preg_replace("/\x1B\[\d*m/", '', $string);
|
$string = preg_replace("/\x1B\[\d*m/", '', $string);
|
||||||
|
|
||||||
|
|
|
@ -2094,3 +2094,128 @@ function phutil_raise_preg_exception($function, array $argv) {
|
||||||
|
|
||||||
throw new PhutilRegexException($message);
|
throw new PhutilRegexException($message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if a value is a nonempty string.
|
||||||
|
*
|
||||||
|
* The value "null" and the empty string are considered empty; all other
|
||||||
|
* strings are considered nonempty.
|
||||||
|
*
|
||||||
|
* This method raises an exception if passed a value which is neither null
|
||||||
|
* nor a string.
|
||||||
|
*
|
||||||
|
* @param Value to test.
|
||||||
|
* @return bool True if the parameter is a nonempty string.
|
||||||
|
*/
|
||||||
|
function phutil_nonempty_string($value) {
|
||||||
|
if ($value === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
pht(
|
||||||
|
'Call to phutil_nonempty_string() expected null or a string, got: %s.',
|
||||||
|
phutil_describe_type($value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if a value is a nonempty, stringlike value.
|
||||||
|
*
|
||||||
|
* The value "null", the empty string, and objects which have a "__toString()"
|
||||||
|
* method which returns the empty string are empty.
|
||||||
|
*
|
||||||
|
* Other strings, and objects with a "__toString()" method that returns a
|
||||||
|
* string other than the empty string are considered nonempty.
|
||||||
|
*
|
||||||
|
* This method raises an exception if passed any other value.
|
||||||
|
*
|
||||||
|
* @param Value to test.
|
||||||
|
* @return bool True if the parameter is a nonempty, stringlike value.
|
||||||
|
*/
|
||||||
|
function phutil_nonempty_stringlike($value) {
|
||||||
|
if ($value === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_object($value)) {
|
||||||
|
try {
|
||||||
|
$string = phutil_string_cast($value);
|
||||||
|
return phutil_nonempty_string($string);
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
// Continue below.
|
||||||
|
} catch (Throwable $ex) {
|
||||||
|
// Continue below.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
pht(
|
||||||
|
'Call to phutil_nonempty_stringlike() expected a string or stringlike '.
|
||||||
|
'object, got: %s.',
|
||||||
|
phutil_describe_type($value)));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test if a value is a nonempty, scalar value.
|
||||||
|
*
|
||||||
|
* The value "null", the empty string, and objects which have a "__toString()"
|
||||||
|
* method which returns the empty string are empty.
|
||||||
|
*
|
||||||
|
* Other strings, objects with a "__toString()" method which returns a
|
||||||
|
* string other than the empty string, integers, and floats are considered
|
||||||
|
* scalar.
|
||||||
|
*
|
||||||
|
* This method raises an exception if passed any other value.
|
||||||
|
*
|
||||||
|
* @param Value to test.
|
||||||
|
* @return bool True if the parameter is a nonempty, scalar value.
|
||||||
|
*/
|
||||||
|
function phutil_nonempty_scalar($value) {
|
||||||
|
if ($value === null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($value === '') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_string($value) || is_int($value) || is_float($value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (is_object($value)) {
|
||||||
|
try {
|
||||||
|
$string = phutil_string_cast($value);
|
||||||
|
return phutil_nonempty_string($string);
|
||||||
|
} catch (Exception $ex) {
|
||||||
|
// Continue below.
|
||||||
|
} catch (Throwable $ex) {
|
||||||
|
// Continue below.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new InvalidArgumentException(
|
||||||
|
pht(
|
||||||
|
'Call to phutil_nonempty_scalar() expected: a string; or stringlike '.
|
||||||
|
'object; or int; or float. Got: %s.',
|
||||||
|
phutil_describe_type($value)));
|
||||||
|
}
|
||||||
|
|
|
@ -141,7 +141,7 @@ function phutil_format_units_generic(
|
||||||
$scale = array_shift($scales);
|
$scale = array_shift($scales);
|
||||||
$label = array_shift($labels);
|
$label = array_shift($labels);
|
||||||
while ($n >= $scale && count($labels)) {
|
while ($n >= $scale && count($labels)) {
|
||||||
$remainder += ($n % $scale) * $accum;
|
$remainder += ((int)$n % $scale) * $accum;
|
||||||
$n /= $scale;
|
$n /= $scale;
|
||||||
$accum *= $scale;
|
$accum *= $scale;
|
||||||
$label = array_shift($labels);
|
$label = array_shift($labels);
|
||||||
|
|
|
@ -164,8 +164,6 @@ EOTEXT
|
||||||
->execute();
|
->execute();
|
||||||
}
|
}
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
if ($api->getUncommittedChanges()) {
|
if ($api->getUncommittedChanges()) {
|
||||||
// TODO: Make this class of error show the uncommitted changes.
|
// TODO: Make this class of error show the uncommitted changes.
|
||||||
|
|
||||||
|
|
|
@ -15,8 +15,7 @@ Allows you to make a raw Conduit method call:
|
||||||
- Call parameters are required, and read as a JSON blob from stdin.
|
- Call parameters are required, and read as a JSON blob from stdin.
|
||||||
- Results are written to stdout as a JSON blob.
|
- Results are written to stdout as a JSON blob.
|
||||||
|
|
||||||
This workflow is primarily useful for writing scripts which integrate
|
This workflow is primarily useful for writing scripts. Examples:
|
||||||
with Phabricator. Examples:
|
|
||||||
|
|
||||||
$ echo '{}' | arc call-conduit -- conduit.ping
|
$ echo '{}' | arc call-conduit -- conduit.ping
|
||||||
$ echo '{"phid":"PHID-FILE-xxxx"}' | arc call-conduit -- file.download
|
$ echo '{"phid":"PHID-FILE-xxxx"}' | arc call-conduit -- file.download
|
||||||
|
|
|
@ -115,8 +115,7 @@ EOTEXT
|
||||||
'raw' => array(
|
'raw' => array(
|
||||||
'help' => pht(
|
'help' => pht(
|
||||||
'Read diff from stdin, not from the working copy. This disables '.
|
'Read diff from stdin, not from the working copy. This disables '.
|
||||||
'many Arcanist/Phabricator features which depend on having access '.
|
'many features which depend on having access to the working copy.'),
|
||||||
'to the working copy.'),
|
|
||||||
'conflicts' => array(
|
'conflicts' => array(
|
||||||
'apply-patches' => pht('%s disables lint.', '--raw'),
|
'apply-patches' => pht('%s disables lint.', '--raw'),
|
||||||
'never-apply-patches' => pht('%s disables lint.', '--raw'),
|
'never-apply-patches' => pht('%s disables lint.', '--raw'),
|
||||||
|
@ -138,8 +137,8 @@ EOTEXT
|
||||||
'param' => 'command',
|
'param' => 'command',
|
||||||
'help' => pht(
|
'help' => pht(
|
||||||
'Generate diff by executing a specified command, not from the '.
|
'Generate diff by executing a specified command, not from the '.
|
||||||
'working copy. This disables many Arcanist/Phabricator features '.
|
'working copy. This disables many features which depend on having '.
|
||||||
'which depend on having access to the working copy.'),
|
'access to the working copy.'),
|
||||||
'conflicts' => array(
|
'conflicts' => array(
|
||||||
'apply-patches' => pht('%s disables lint.', '--raw-command'),
|
'apply-patches' => pht('%s disables lint.', '--raw-command'),
|
||||||
'never-apply-patches' => pht('%s disables lint.', '--raw-command'),
|
'never-apply-patches' => pht('%s disables lint.', '--raw-command'),
|
||||||
|
@ -326,9 +325,8 @@ EOTEXT
|
||||||
'head' => array(
|
'head' => array(
|
||||||
'param' => 'commit',
|
'param' => 'commit',
|
||||||
'help' => pht(
|
'help' => pht(
|
||||||
'Specify the end of the commit range. This disables many '.
|
'Specify the end of the commit range. This disables many features '.
|
||||||
'Arcanist/Phabricator features which depend on having access to '.
|
'which depend on having access to the working copy.'),
|
||||||
'the working copy.'),
|
|
||||||
'supports' => array('git'),
|
'supports' => array('git'),
|
||||||
'nosupport' => array(
|
'nosupport' => array(
|
||||||
'svn' => pht('Subversion does not support commit ranges.'),
|
'svn' => pht('Subversion does not support commit ranges.'),
|
||||||
|
@ -517,7 +515,7 @@ EOTEXT
|
||||||
if ($is_draft) {
|
if ($is_draft) {
|
||||||
throw new ArcanistUsageException(
|
throw new ArcanistUsageException(
|
||||||
pht(
|
pht(
|
||||||
'You have specified "--draft", but the version of Phabricator '.
|
'You have specified "--draft", but the software version '.
|
||||||
'on the server is too old to support draft revisions. Omit '.
|
'on the server is too old to support draft revisions. Omit '.
|
||||||
'the flag or upgrade the server software.'));
|
'the flag or upgrade the server software.'));
|
||||||
}
|
}
|
||||||
|
@ -674,6 +672,8 @@ EOTEXT
|
||||||
if ($should_edit) {
|
if ($should_edit) {
|
||||||
$edited = $this->newInteractiveEditor($remote_corpus)
|
$edited = $this->newInteractiveEditor($remote_corpus)
|
||||||
->setName('differential-edit-revision-info')
|
->setName('differential-edit-revision-info')
|
||||||
|
->setTaskMessage(pht(
|
||||||
|
'Update the details for a revision, then save and exit.'))
|
||||||
->editInteractively();
|
->editInteractively();
|
||||||
if ($edited != $remote_corpus) {
|
if ($edited != $remote_corpus) {
|
||||||
$remote_corpus = $edited;
|
$remote_corpus = $edited;
|
||||||
|
@ -699,7 +699,7 @@ EOTEXT
|
||||||
$this->revisionID = $revision_id;
|
$this->revisionID = $revision_id;
|
||||||
|
|
||||||
$revision['message'] = $this->getArgument('message');
|
$revision['message'] = $this->getArgument('message');
|
||||||
if (!strlen($revision['message'])) {
|
if ($revision['message'] === null) {
|
||||||
$update_messages = $this->readScratchJSONFile('update-messages.json');
|
$update_messages = $this->readScratchJSONFile('update-messages.json');
|
||||||
|
|
||||||
$update_messages[$revision_id] = $this->getUpdateMessage(
|
$update_messages[$revision_id] = $this->getUpdateMessage(
|
||||||
|
@ -806,7 +806,10 @@ EOTEXT
|
||||||
if ($is_raw) {
|
if ($is_raw) {
|
||||||
|
|
||||||
if ($this->getArgument('raw')) {
|
if ($this->getArgument('raw')) {
|
||||||
fwrite(STDERR, pht('Reading diff from stdin...')."\n");
|
PhutilSystem::writeStderr(
|
||||||
|
tsprintf(
|
||||||
|
"%s\n",
|
||||||
|
pht('Reading diff from stdin...')));
|
||||||
$raw_diff = file_get_contents('php://stdin');
|
$raw_diff = file_get_contents('php://stdin');
|
||||||
} else if ($this->getArgument('raw-command')) {
|
} else if ($this->getArgument('raw-command')) {
|
||||||
list($raw_diff) = execx('%C', $this->getArgument('raw-command'));
|
list($raw_diff) = execx('%C', $this->getArgument('raw-command'));
|
||||||
|
@ -947,7 +950,7 @@ EOTEXT
|
||||||
} catch (ConduitClientException $e) {
|
} catch (ConduitClientException $e) {
|
||||||
if ($e->getErrorCode() == 'ERR-BAD-ARCANIST-PROJECT') {
|
if ($e->getErrorCode() == 'ERR-BAD-ARCANIST-PROJECT') {
|
||||||
echo phutil_console_wrap(
|
echo phutil_console_wrap(
|
||||||
pht('Lookup of encoding in arcanist project failed: %s',
|
pht('Lookup of encoding in project failed: %s',
|
||||||
$e->getMessage())."\n");
|
$e->getMessage())."\n");
|
||||||
} else {
|
} else {
|
||||||
throw $e;
|
throw $e;
|
||||||
|
@ -988,10 +991,10 @@ EOTEXT
|
||||||
'these files will be marked as binary.',
|
'these files will be marked as binary.',
|
||||||
phutil_count($utf8_problems)),
|
phutil_count($utf8_problems)),
|
||||||
pht(
|
pht(
|
||||||
"You can learn more about how Phabricator handles character ".
|
"You can learn more about how this software handles character ".
|
||||||
"encodings (and how to configure encoding settings and detect and ".
|
"encodings (and how to configure encoding settings and detect and ".
|
||||||
"correct encoding problems) by reading 'User Guide: UTF-8 and ".
|
"correct encoding problems) by reading 'User Guide: UTF-8 and ".
|
||||||
"Character Encoding' in the Phabricator documentation."),
|
"Character Encoding' in the documentation."),
|
||||||
pht(
|
pht(
|
||||||
'%s AFFECTED FILE(S)',
|
'%s AFFECTED FILE(S)',
|
||||||
phutil_count($utf8_problems)));
|
phutil_count($utf8_problems)));
|
||||||
|
@ -1476,6 +1479,8 @@ EOTEXT
|
||||||
} else {
|
} else {
|
||||||
$new_template = $this->newInteractiveEditor($template)
|
$new_template = $this->newInteractiveEditor($template)
|
||||||
->setName('new-commit')
|
->setName('new-commit')
|
||||||
|
->setTaskMessage(pht(
|
||||||
|
'Provide the details for a new revision, then save and exit.'))
|
||||||
->editInteractively();
|
->editInteractively();
|
||||||
}
|
}
|
||||||
$first = false;
|
$first = false;
|
||||||
|
@ -1736,9 +1741,12 @@ EOTEXT
|
||||||
if ($template == '') {
|
if ($template == '') {
|
||||||
$comments = $this->getDefaultUpdateMessage();
|
$comments = $this->getDefaultUpdateMessage();
|
||||||
|
|
||||||
|
$comments = phutil_string_cast($comments);
|
||||||
|
$comments = rtrim($comments);
|
||||||
|
|
||||||
$template = sprintf(
|
$template = sprintf(
|
||||||
"%s\n\n# %s\n#\n# %s\n# %s\n#\n# %s\n# $ %s\n\n",
|
"%s\n\n# %s\n#\n# %s\n# %s\n#\n# %s\n# $ %s\n\n",
|
||||||
rtrim($comments),
|
$comments,
|
||||||
pht(
|
pht(
|
||||||
'Updating %s: %s',
|
'Updating %s: %s',
|
||||||
"D{$fields['revisionID']}",
|
"D{$fields['revisionID']}",
|
||||||
|
@ -1752,6 +1760,8 @@ EOTEXT
|
||||||
|
|
||||||
$comments = $this->newInteractiveEditor($template)
|
$comments = $this->newInteractiveEditor($template)
|
||||||
->setName('differential-update-comments')
|
->setName('differential-update-comments')
|
||||||
|
->setTaskMessage(pht(
|
||||||
|
'Update the revision comments, then save and exit.'))
|
||||||
->editInteractively();
|
->editInteractively();
|
||||||
|
|
||||||
return $comments;
|
return $comments;
|
||||||
|
@ -2354,7 +2364,7 @@ EOTEXT
|
||||||
if (strlen($branch)) {
|
if (strlen($branch)) {
|
||||||
$upstream_path = $api->getPathToUpstream($branch);
|
$upstream_path = $api->getPathToUpstream($branch);
|
||||||
$remote_branch = $upstream_path->getRemoteBranchName();
|
$remote_branch = $upstream_path->getRemoteBranchName();
|
||||||
if (strlen($remote_branch)) {
|
if ($remote_branch !== null) {
|
||||||
return array(
|
return array(
|
||||||
array(
|
array(
|
||||||
'type' => 'branch',
|
'type' => 'branch',
|
||||||
|
@ -2368,7 +2378,7 @@ EOTEXT
|
||||||
// If "arc.land.onto.default" is configured, use that.
|
// If "arc.land.onto.default" is configured, use that.
|
||||||
$config_key = 'arc.land.onto.default';
|
$config_key = 'arc.land.onto.default';
|
||||||
$onto = $this->getConfigFromAnySource($config_key);
|
$onto = $this->getConfigFromAnySource($config_key);
|
||||||
if (strlen($onto)) {
|
if ($onto !== null) {
|
||||||
return array(
|
return array(
|
||||||
array(
|
array(
|
||||||
'type' => 'branch',
|
'type' => 'branch',
|
||||||
|
@ -2643,7 +2653,7 @@ EOTEXT
|
||||||
if (!$supported) {
|
if (!$supported) {
|
||||||
$this->writeInfo(
|
$this->writeInfo(
|
||||||
pht('SKIP STAGING'),
|
pht('SKIP STAGING'),
|
||||||
pht('Phabricator does not support staging areas for this repository.'));
|
pht('The server does not support staging areas for this repository.'));
|
||||||
return self::STAGING_REPOSITORY_UNSUPPORTED;
|
return self::STAGING_REPOSITORY_UNSUPPORTED;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -19,12 +19,12 @@ EOTEXT
|
||||||
public function getCommandHelp() {
|
public function getCommandHelp() {
|
||||||
return phutil_console_format(<<<EOTEXT
|
return phutil_console_format(<<<EOTEXT
|
||||||
Supports: http, https
|
Supports: http, https
|
||||||
Installs Conduit credentials into your ~/.arcrc for the given install
|
Installs Conduit credentials into your ~/.arcrc for the given server.
|
||||||
of Phabricator. You need to do this before you can use 'arc', as it
|
You need to do this before you can use 'arc', as it enables 'arc' to
|
||||||
enables 'arc' to link your command-line activity with your account on
|
link your command-line activity with your account on the web. Run
|
||||||
the web. Run this command from within a project directory to install
|
this command from within a project directory to install that
|
||||||
that project's certificate, or specify an explicit URI (like
|
project's certificate, or specify an explicit URI (like
|
||||||
"https://phabricator.example.com/").
|
"https://devtools.example.com/").
|
||||||
EOTEXT
|
EOTEXT
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -91,12 +91,11 @@ EOTEXT
|
||||||
// Ignore.
|
// Ignore.
|
||||||
}
|
}
|
||||||
|
|
||||||
echo phutil_console_format("**%s**\n", pht('LOGIN TO PHABRICATOR'));
|
echo phutil_console_format("**%s**\n", pht('LOG IN'));
|
||||||
echo phutil_console_format(
|
echo phutil_console_format(
|
||||||
"%s\n\n%s\n\n%s",
|
"%s\n\n%s\n\n%s",
|
||||||
pht(
|
pht(
|
||||||
'Open this page in your browser and login to '.
|
'Open this page in your browser and log in if necessary:'),
|
||||||
'Phabricator if necessary:'),
|
|
||||||
$token_uri,
|
$token_uri,
|
||||||
pht('Then paste the API Token on that page below.'));
|
pht('Then paste the API Token on that page below.'));
|
||||||
|
|
||||||
|
@ -204,7 +203,7 @@ EOTEXT
|
||||||
$uri = $conduit_uri;
|
$uri = $conduit_uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
$example = 'https://phabricator.example.com/';
|
$example = 'https://devtools.example.com/';
|
||||||
|
|
||||||
$uri_object = new PhutilURI($uri);
|
$uri_object = new PhutilURI($uri);
|
||||||
$protocol = $uri_object->getProtocol();
|
$protocol = $uri_object->getProtocol();
|
||||||
|
|
|
@ -11,13 +11,13 @@ final class ArcanistLiberateWorkflow
|
||||||
// TOOLSETS: Expand this help.
|
// TOOLSETS: Expand this help.
|
||||||
|
|
||||||
$help = pht(<<<EOTEXT
|
$help = pht(<<<EOTEXT
|
||||||
Create or update an Arcanist library.
|
Create or update a library.
|
||||||
EOTEXT
|
EOTEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
return $this->newWorkflowInformation()
|
return $this->newWorkflowInformation()
|
||||||
->setSynopsis(
|
->setSynopsis(
|
||||||
pht('Create or update an Arcanist library.'))
|
pht('Create or update a library.'))
|
||||||
->addExample(pht('**liberate**'))
|
->addExample(pht('**liberate**'))
|
||||||
->addExample(pht('**liberate** [__path__]'))
|
->addExample(pht('**liberate** [__path__]'))
|
||||||
->setHelp($help);
|
->setHelp($help);
|
||||||
|
@ -79,22 +79,35 @@ EOTEXT
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$any_errors = false;
|
||||||
foreach ($paths as $path) {
|
foreach ($paths as $path) {
|
||||||
$log->writeStatus(
|
$log->writeStatus(
|
||||||
pht('WORK'),
|
pht('WORK'),
|
||||||
pht(
|
pht(
|
||||||
'Updating library: %s',
|
'Updating library: %s',
|
||||||
Filesystem::readablePath($path).DIRECTORY_SEPARATOR));
|
Filesystem::readablePath($path).DIRECTORY_SEPARATOR));
|
||||||
$this->liberatePath($path);
|
$exit_code = $this->liberatePath($path);
|
||||||
|
if ($exit_code !== 0) {
|
||||||
|
$any_errors = true;
|
||||||
|
$log->writeError(
|
||||||
|
pht('ERROR'),
|
||||||
|
pht('Failed to update library: %s', $path));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!$any_errors) {
|
||||||
$log->writeSuccess(
|
$log->writeSuccess(
|
||||||
pht('DONE'),
|
pht('DONE'),
|
||||||
pht('Updated %s librarie(s).', phutil_count($paths)));
|
pht('Updated %s librarie(s).', phutil_count($paths)));
|
||||||
|
}
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int The exit code of running the rebuild-map.php script, which
|
||||||
|
* will be 0 to indicate success or non-zero for failure.
|
||||||
|
*/
|
||||||
private function liberatePath($path) {
|
private function liberatePath($path) {
|
||||||
if (!Filesystem::pathExists($path.'/__phutil_library_init__.php')) {
|
if (!Filesystem::pathExists($path.'/__phutil_library_init__.php')) {
|
||||||
echo tsprintf(
|
echo tsprintf(
|
||||||
|
@ -103,8 +116,7 @@ EOTEXT
|
||||||
'No library currently exists at the path "%s"...',
|
'No library currently exists at the path "%s"...',
|
||||||
$path));
|
$path));
|
||||||
$this->liberateCreateDirectory($path);
|
$this->liberateCreateDirectory($path);
|
||||||
$this->liberateCreateLibrary($path);
|
return $this->liberateCreateLibrary($path);
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$version = $this->getLibraryFormatVersion($path);
|
$version = $this->getLibraryFormatVersion($path);
|
||||||
|
@ -119,8 +131,6 @@ EOTEXT
|
||||||
throw new ArcanistUsageException(
|
throw new ArcanistUsageException(
|
||||||
pht("Unknown library version '%s'!", $version));
|
pht("Unknown library version '%s'!", $version));
|
||||||
}
|
}
|
||||||
|
|
||||||
echo tsprintf("%s\n", pht('Done.'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private function getLibraryFormatVersion($path) {
|
private function getLibraryFormatVersion($path) {
|
||||||
|
@ -140,6 +150,10 @@ EOTEXT
|
||||||
return 1;
|
return 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int The exit code of running the rebuild-map.php script, which
|
||||||
|
* will be 0 to indicate success or non-zero for failure.
|
||||||
|
*/
|
||||||
private function liberateVersion2($path) {
|
private function liberateVersion2($path) {
|
||||||
$bin = $this->getScriptPath('support/lib/rebuild-map.php');
|
$bin = $this->getScriptPath('support/lib/rebuild-map.php');
|
||||||
|
|
||||||
|
@ -181,10 +195,14 @@ EOTEXT
|
||||||
execx('mkdir -p %R', $path);
|
execx('mkdir -p %R', $path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return int The exit code of running the rebuild-map.php script, which
|
||||||
|
* will be 0 to indicate success or non-zero for failure.
|
||||||
|
*/
|
||||||
private function liberateCreateLibrary($path) {
|
private function liberateCreateLibrary($path) {
|
||||||
$init_path = $path.'/__phutil_library_init__.php';
|
$init_path = $path.'/__phutil_library_init__.php';
|
||||||
if (Filesystem::pathExists($init_path)) {
|
if (Filesystem::pathExists($init_path)) {
|
||||||
return;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
echo pht("Creating new libphutil library in '%s'.", $path)."\n";
|
echo pht("Creating new libphutil library in '%s'.", $path)."\n";
|
||||||
|
@ -213,7 +231,7 @@ EOTEXT
|
||||||
'__phutil_library_init__.php',
|
'__phutil_library_init__.php',
|
||||||
$path);
|
$path);
|
||||||
Filesystem::writeFile($init_path, $template);
|
Filesystem::writeFile($init_path, $template);
|
||||||
$this->liberateVersion2($path);
|
return $this->liberateVersion2($path);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -707,7 +707,7 @@ EOTEXT
|
||||||
'git apply --whitespace nowarn --index --reject -- %s',
|
'git apply --whitespace nowarn --index --reject -- %s',
|
||||||
$patchfile);
|
$patchfile);
|
||||||
$passthru->setCWD($repository_api->getPath());
|
$passthru->setCWD($repository_api->getPath());
|
||||||
$err = $passthru->execute();
|
$err = $passthru->resolve();
|
||||||
|
|
||||||
if ($err) {
|
if ($err) {
|
||||||
echo phutil_console_format(
|
echo phutil_console_format(
|
||||||
|
@ -890,8 +890,8 @@ EOTEXT
|
||||||
'revision_id' => $revision_id,
|
'revision_id' => $revision_id,
|
||||||
));
|
));
|
||||||
$prompt_message = pht(
|
$prompt_message = pht(
|
||||||
' Note arcanist failed to load the commit message '.
|
' NOTE: Failed to load the commit message from Differential (for '.
|
||||||
'from differential for revision %s.',
|
'revision "%s".)',
|
||||||
"D{$revision_id}");
|
"D{$revision_id}");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -909,6 +909,8 @@ EOTEXT
|
||||||
|
|
||||||
$commit_message = $this->newInteractiveEditor($template)
|
$commit_message = $this->newInteractiveEditor($template)
|
||||||
->setName('arcanist-patch-commit-message')
|
->setName('arcanist-patch-commit-message')
|
||||||
|
->setTaskMessage(pht(
|
||||||
|
'Supply a commit message for this patch, then save and exit.'))
|
||||||
->editInteractively();
|
->editInteractively();
|
||||||
|
|
||||||
$commit_message = ArcanistCommentRemover::removeComments($commit_message);
|
$commit_message = ArcanistCommentRemover::removeComments($commit_message);
|
||||||
|
|
|
@ -9,12 +9,12 @@ final class ArcanistUpgradeWorkflow
|
||||||
|
|
||||||
public function getWorkflowInformation() {
|
public function getWorkflowInformation() {
|
||||||
$help = pht(<<<EOTEXT
|
$help = pht(<<<EOTEXT
|
||||||
Upgrade Arcanist to the latest version.
|
Upgrade this program to the latest version.
|
||||||
EOTEXT
|
EOTEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
return $this->newWorkflowInformation()
|
return $this->newWorkflowInformation()
|
||||||
->setSynopsis(pht('Upgrade Arcanist to the latest version.'))
|
->setSynopsis(pht('Upgrade this program to the latest version.'))
|
||||||
->addExample(pht('**upgrade**'))
|
->addExample(pht('**upgrade**'))
|
||||||
->setHelp($help);
|
->setHelp($help);
|
||||||
}
|
}
|
||||||
|
@ -51,10 +51,10 @@ EOTEXT
|
||||||
if (!$is_git) {
|
if (!$is_git) {
|
||||||
throw new PhutilArgumentUsageException(
|
throw new PhutilArgumentUsageException(
|
||||||
pht(
|
pht(
|
||||||
'The "arc upgrade" workflow uses "git pull" to upgrade '.
|
'The "upgrade" workflow uses "git pull" to upgrade, but '.
|
||||||
'Arcanist, but the "arcanist/" directory (in "%s") is not a Git '.
|
'the software directory (in "%s") is not a Git working '.
|
||||||
'working copy. You must leave "arcanist/" as a Git '.
|
'copy. You must leave this directory as a Git working copy to '.
|
||||||
'working copy to use "arc upgrade".',
|
'use "arc upgrade".',
|
||||||
$root));
|
$root));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -125,7 +125,7 @@ EOTEXT
|
||||||
|
|
||||||
$log->writeSuccess(
|
$log->writeSuccess(
|
||||||
pht('UPGRADED'),
|
pht('UPGRADED'),
|
||||||
pht('Your copy of Arcanist is now up to date.'));
|
pht('This software is now up to date.'));
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,7 @@ final class ArcanistUploadWorkflow
|
||||||
|
|
||||||
public function getWorkflowInformation() {
|
public function getWorkflowInformation() {
|
||||||
$help = pht(<<<EOTEXT
|
$help = pht(<<<EOTEXT
|
||||||
Upload one or more files from local disk to Phabricator.
|
Upload one or more files from local disk.
|
||||||
EOTEXT
|
EOTEXT
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|
|
@ -28,15 +28,15 @@ The __symbol__ may be a branch or bookmark name, a revision name (like "D123"),
|
||||||
a task name (like "T123"), or a new symbol.
|
a task name (like "T123"), or a new symbol.
|
||||||
|
|
||||||
If you provide a symbol which currently does not identify any ongoing work,
|
If you provide a symbol which currently does not identify any ongoing work,
|
||||||
Arcanist will create a new branch or bookmark with the name you provide.
|
a new branch or bookmark will be created with the name you provide.
|
||||||
|
|
||||||
If you provide the name of an existing branch or bookmark, Arcanist will switch
|
If you provide the name of an existing branch or bookmark, the working copy
|
||||||
to that branch or bookmark.
|
will be switched to that branch or bookmark.
|
||||||
|
|
||||||
If you provide the name of a revision or task, Arcanist will look for a related
|
If you provide the name of a revision or task, the workflow will look for a
|
||||||
branch or bookmark that exists in the working copy. If it finds one, it will
|
related branch or bookmark that already exists in the working copy. If one is
|
||||||
switch to it. If it does not find one, it will attempt to create a new branch
|
found, it will switch to it. If it does not find one, it will attempt to create
|
||||||
or bookmark.
|
a new branch or bookmark.
|
||||||
|
|
||||||
When "arc work" creates a branch or bookmark, it will use **--start** as the
|
When "arc work" creates a branch or bookmark, it will use **--start** as the
|
||||||
branchpoint if it is provided. Otherwise, the current working copy state will
|
branchpoint if it is provided. Otherwise, the current working copy state will
|
||||||
|
|
|
@ -143,7 +143,7 @@ abstract class ArcanistWorkflow extends Phobject {
|
||||||
|
|
||||||
if ($information) {
|
if ($information) {
|
||||||
$synopsis = $information->getSynopsis();
|
$synopsis = $information->getSynopsis();
|
||||||
if (strlen($synopsis)) {
|
if ($synopsis !== null) {
|
||||||
$phutil_workflow->setSynopsis($synopsis);
|
$phutil_workflow->setSynopsis($synopsis);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,7 +154,7 @@ abstract class ArcanistWorkflow extends Phobject {
|
||||||
}
|
}
|
||||||
|
|
||||||
$help = $information->getHelp();
|
$help = $information->getHelp();
|
||||||
if (strlen($help)) {
|
if ($help !== null) {
|
||||||
// Unwrap linebreaks in the help text so we don't get weird formatting.
|
// Unwrap linebreaks in the help text so we don't get weird formatting.
|
||||||
$help = preg_replace("/(?<=\S)\n(?=\S)/", ' ', $help);
|
$help = preg_replace("/(?<=\S)\n(?=\S)/", ' ', $help);
|
||||||
|
|
||||||
|
@ -534,7 +534,7 @@ abstract class ArcanistWorkflow extends Phobject {
|
||||||
$conduit_uri = $this->conduitURI;
|
$conduit_uri = $this->conduitURI;
|
||||||
$message = phutil_console_format(
|
$message = phutil_console_format(
|
||||||
"\n%s\n\n %s\n\n%s\n%s",
|
"\n%s\n\n %s\n\n%s\n%s",
|
||||||
pht('YOU NEED TO __INSTALL A CERTIFICATE__ TO LOGIN TO PHABRICATOR'),
|
pht('YOU NEED TO __INSTALL A CERTIFICATE__ TO LOG IN'),
|
||||||
pht('To do this, run: **%s**', 'arc install-certificate'),
|
pht('To do this, run: **%s**', 'arc install-certificate'),
|
||||||
pht("The server '%s' rejected your request:", $conduit_uri),
|
pht("The server '%s' rejected your request:", $conduit_uri),
|
||||||
$ex->getMessage());
|
$ex->getMessage());
|
||||||
|
@ -1234,6 +1234,9 @@ abstract class ArcanistWorkflow extends Phobject {
|
||||||
|
|
||||||
$commit_message = $this->newInteractiveEditor($template)
|
$commit_message = $this->newInteractiveEditor($template)
|
||||||
->setName(pht('commit-message'))
|
->setName(pht('commit-message'))
|
||||||
|
->setTaskMessage(pht(
|
||||||
|
'Supply commit message for uncommitted changes, then save and '.
|
||||||
|
'exit.'))
|
||||||
->editInteractively();
|
->editInteractively();
|
||||||
|
|
||||||
if ($commit_message === $template) {
|
if ($commit_message === $template) {
|
||||||
|
@ -1562,7 +1565,7 @@ abstract class ArcanistWorkflow extends Phobject {
|
||||||
* @return void
|
* @return void
|
||||||
*/
|
*/
|
||||||
final protected function writeStatusMessage($msg) {
|
final protected function writeStatusMessage($msg) {
|
||||||
fwrite(STDERR, $msg);
|
PhutilSystem::writeStderr($msg);
|
||||||
}
|
}
|
||||||
|
|
||||||
final public function writeInfo($title, $message) {
|
final public function writeInfo($title, $message) {
|
||||||
|
@ -1954,11 +1957,10 @@ abstract class ArcanistWorkflow extends Phobject {
|
||||||
} catch (ConduitClientException $ex) {
|
} catch (ConduitClientException $ex) {
|
||||||
if ($ex->getErrorCode() == 'ERR-CONDUIT-CALL') {
|
if ($ex->getErrorCode() == 'ERR-CONDUIT-CALL') {
|
||||||
$reasons[] = pht(
|
$reasons[] = pht(
|
||||||
'This version of Arcanist is more recent than the version of '.
|
'This software version on the server you are connecting to is out '.
|
||||||
'Phabricator you are connecting to: the Phabricator install is '.
|
'of date and does not have support for identifying repositories '.
|
||||||
'out of date and does not have support for identifying '.
|
'by callsign or URI. Update the server sofwware to enable these '.
|
||||||
'repositories by callsign or URI. Update Phabricator to enable '.
|
'features.');
|
||||||
'these features.');
|
|
||||||
return array(null, $reasons);
|
return array(null, $reasons);
|
||||||
}
|
}
|
||||||
throw $ex;
|
throw $ex;
|
||||||
|
@ -2201,9 +2203,8 @@ abstract class ArcanistWorkflow extends Phobject {
|
||||||
|
|
||||||
throw new ArcanistUsageException(
|
throw new ArcanistUsageException(
|
||||||
pht(
|
pht(
|
||||||
"Unable to find a browser command to run. Set '%s' in your ".
|
'Unable to find a browser command to run. Set "browser" in your '.
|
||||||
"Arcanist config to specify a command to use.",
|
'configuration to specify a command to use.'));
|
||||||
'browser'));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -21,18 +21,123 @@ from mercurial import (
|
||||||
hg,
|
hg,
|
||||||
i18n,
|
i18n,
|
||||||
node,
|
node,
|
||||||
registrar,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
_ = i18n._
|
_ = i18n._
|
||||||
cmdtable = {}
|
cmdtable = {}
|
||||||
command = registrar.command(cmdtable)
|
|
||||||
|
# Older veresions of Mercurial (~4.7) moved the command function and the
|
||||||
|
# remoteopts object to different modules. Using try/except here to attempt
|
||||||
|
# allowing this module to load properly, despite whether individual commands
|
||||||
|
# will work properly on older versions of Mercurial or not.
|
||||||
|
# https://phab.mercurial-scm.org/rHG46ba2cdda476ac53a8a8f50e4d9435d88267db60
|
||||||
|
# https://phab.mercurial-scm.org/rHG04baab18d60a5c833ab3190506147e01b3c6d12c
|
||||||
|
try:
|
||||||
|
from mercurial import registrar
|
||||||
|
command = registrar.command(cmdtable)
|
||||||
|
except:
|
||||||
|
command = cmdutil.command(cmdtable)
|
||||||
|
|
||||||
|
try:
|
||||||
|
remoteopts = cmdutil.remoteopts
|
||||||
|
except:
|
||||||
|
from mercurial import commands
|
||||||
|
remoteopts = commands.remoteopts
|
||||||
|
|
||||||
|
try:
|
||||||
|
parseurl = hg.parseurl
|
||||||
|
except:
|
||||||
|
from mercurial import utils
|
||||||
|
parseurl = utils.urlutil.parseurl
|
||||||
|
|
||||||
|
@command(
|
||||||
|
b'arc-amend',
|
||||||
|
[
|
||||||
|
(b'l',
|
||||||
|
b'logfile',
|
||||||
|
b'',
|
||||||
|
_(b'read commit message from file'),
|
||||||
|
_(b'FILE')),
|
||||||
|
(b'm',
|
||||||
|
b'message',
|
||||||
|
b'',
|
||||||
|
_(b'use text as commit message'),
|
||||||
|
_(b'TEXT')),
|
||||||
|
(b'u',
|
||||||
|
b'user',
|
||||||
|
b'',
|
||||||
|
_(b'record the specified user as committer'),
|
||||||
|
_(b'USER')),
|
||||||
|
(b'd',
|
||||||
|
b'date',
|
||||||
|
b'',
|
||||||
|
_(b'record the specified date as commit date'),
|
||||||
|
_(b'DATE')),
|
||||||
|
(b'A',
|
||||||
|
b'addremove',
|
||||||
|
False,
|
||||||
|
_(b'mark new/missing files as added/removed before committing')),
|
||||||
|
(b'n',
|
||||||
|
b'note',
|
||||||
|
b'',
|
||||||
|
_(b'store a note on amend'),
|
||||||
|
_(b'TEXT')),
|
||||||
|
],
|
||||||
|
_(b'[OPTION]'))
|
||||||
|
def amend(ui, repo, source=None, **opts):
|
||||||
|
"""amend
|
||||||
|
|
||||||
|
Uses Mercurial internal API to amend changes to a non-head commit.
|
||||||
|
|
||||||
|
(This is an Arcanist extension to Mercurial.)
|
||||||
|
|
||||||
|
Returns 0 if amending succeeds, 1 otherwise.
|
||||||
|
"""
|
||||||
|
|
||||||
|
# The option keys seem to come in as 'str' type but the cmdutil.amend() code
|
||||||
|
# expects them as binary. To account for both Python 2 and Python 3
|
||||||
|
# compatibility, insert the value under both 'str' and binary type.
|
||||||
|
newopts = {}
|
||||||
|
for key in opts:
|
||||||
|
val = opts.get(key)
|
||||||
|
newopts[key] = val
|
||||||
|
if isinstance(key, str):
|
||||||
|
newkey = key.encode('UTF-8')
|
||||||
|
newopts[newkey] = val
|
||||||
|
|
||||||
|
orig = repo[b'.']
|
||||||
|
extra = {}
|
||||||
|
pats = []
|
||||||
|
cmdutil.amend(ui, repo, orig, extra, pats, newopts)
|
||||||
|
|
||||||
|
"""
|
||||||
|
# This will allow running amend on older versions of Mercurial, ~3.5, however
|
||||||
|
# the behavior on those versions will squash child commits of the working
|
||||||
|
# directory into the amended commit which is undesired.
|
||||||
|
try:
|
||||||
|
cmdutil.amend(ui, repo, orig, extra, pats, newopts)
|
||||||
|
except:
|
||||||
|
def commitfunc(ui, repo, message, match, opts):
|
||||||
|
return repo.commit(
|
||||||
|
message,
|
||||||
|
opts.get('user') or orig.user(),
|
||||||
|
opts.get('date') or orig.date(),
|
||||||
|
match,
|
||||||
|
extra=extra)
|
||||||
|
cmdutil.amend(ui, repo, commitfunc, orig, extra, pats, newopts)
|
||||||
|
"""
|
||||||
|
|
||||||
|
return 0
|
||||||
|
|
||||||
@command(
|
@command(
|
||||||
b'arc-ls-markers',
|
b'arc-ls-markers',
|
||||||
[(b'', b'output', b'',
|
[
|
||||||
_(b'file to output refs to'), _(b'FILE')),
|
(b'',
|
||||||
] + cmdutil.remoteopts,
|
b'output',
|
||||||
|
b'',
|
||||||
|
_(b'file to output refs to'),
|
||||||
|
_(b'FILE')),
|
||||||
|
] + remoteopts,
|
||||||
_(b'[--output FILENAME] [SOURCE]'))
|
_(b'[--output FILENAME] [SOURCE]'))
|
||||||
def lsmarkers(ui, repo, source=None, **opts):
|
def lsmarkers(ui, repo, source=None, **opts):
|
||||||
"""list markers
|
"""list markers
|
||||||
|
@ -168,7 +273,7 @@ def remotemarkers(ui, repo, source, opts):
|
||||||
|
|
||||||
markers = []
|
markers = []
|
||||||
|
|
||||||
source, branches = hg.parseurl(ui.expandpath(source))
|
source, branches = parseurl(ui.expandpath(source))
|
||||||
remote = hg.peer(repo, opts, source)
|
remote = hg.peer(repo, opts, source)
|
||||||
|
|
||||||
with remote.commandexecutor() as e:
|
with remote.commandexecutor() as e:
|
||||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Reference in a new issue