mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-25 00:02: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(
|
||||
'name' => 'conduit-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(
|
||||
'name' => 'conduit-token',
|
||||
|
@ -85,7 +88,8 @@ $base_args->parsePartial(
|
|||
'repeat' => true,
|
||||
'help' => pht(
|
||||
'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(
|
||||
"%s\n\n - %s\n - %s\n - %s\n",
|
||||
pht(
|
||||
'This command requires arc to connect to a Phabricator install, '.
|
||||
'but no Phabricator installation is configured. To configure a '.
|
||||
'Phabricator URI:'),
|
||||
'This command requires %s to connect to a %s (or compatible '.
|
||||
'software) server, but no %s server is configured. To configure a '.
|
||||
'%s server URI:',
|
||||
PlatformSymbols::getPlatformClientName(),
|
||||
PlatformSymbols::getPlatformServerName(),
|
||||
PlatformSymbols::getPlatformServerName(),
|
||||
PlatformSymbols::getPlatformServerName()),
|
||||
pht(
|
||||
'set a default location with `%s`; or',
|
||||
'arc set-config default <uri>'),
|
||||
|
@ -688,10 +696,12 @@ function arcanist_load_libraries(
|
|||
"**<bg:yellow> %s </bg>** %s\n",
|
||||
pht('VERY META'),
|
||||
pht(
|
||||
'You are running one copy of Arcanist (at path "%s") against '.
|
||||
'another copy of Arcanist (at path "%s"). Code in the current '.
|
||||
'You are running one copy of %s (at path "%s") against '.
|
||||
'another copy of %s (at path "%s"). Code in the current '.
|
||||
'working directory will not be loaded or executed.',
|
||||
PlatformSymbols::getPlatformClientName(),
|
||||
$executing_directory,
|
||||
PlatformSymbols::getPlatformClientName(),
|
||||
$working_directory)));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -188,6 +188,8 @@ phutil_register_library_map(array(
|
|||
'ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase.php',
|
||||
'ArcanistDynamicDefineXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDynamicDefineXHPASTLinterRule.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',
|
||||
'ArcanistElseIfUsageXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistElseIfUsageXHPASTLinterRuleTestCase.php',
|
||||
'ArcanistEmptyFileXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistEmptyFileXHPASTLinterRule.php',
|
||||
|
@ -264,6 +266,8 @@ phutil_register_library_map(array(
|
|||
'ArcanistHgProxyClient' => 'hgdaemon/ArcanistHgProxyClient.php',
|
||||
'ArcanistHgProxyServer' => 'hgdaemon/ArcanistHgProxyServer.php',
|
||||
'ArcanistHgServerChannel' => 'hgdaemon/ArcanistHgServerChannel.php',
|
||||
'ArcanistHostMemorySnapshot' => 'filesystem/memory/ArcanistHostMemorySnapshot.php',
|
||||
'ArcanistHostMemorySnapshotTestCase' => 'filesystem/memory/__tests__/ArcanistHostMemorySnapshotTestCase.php',
|
||||
'ArcanistImplicitConstructorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitConstructorXHPASTLinterRule.php',
|
||||
'ArcanistImplicitConstructorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistImplicitConstructorXHPASTLinterRuleTestCase.php',
|
||||
'ArcanistImplicitFallthroughXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistImplicitFallthroughXHPASTLinterRule.php',
|
||||
|
@ -346,6 +350,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistMercurialAPI' => 'repository/api/ArcanistMercurialAPI.php',
|
||||
'ArcanistMercurialCommitGraphQuery' => 'repository/graph/query/ArcanistMercurialCommitGraphQuery.php',
|
||||
'ArcanistMercurialCommitMessageHardpointQuery' => 'query/ArcanistMercurialCommitMessageHardpointQuery.php',
|
||||
'ArcanistMercurialCommitSymbolCommitHardpointQuery' => 'ref/commit/ArcanistMercurialCommitSymbolCommitHardpointQuery.php',
|
||||
'ArcanistMercurialLandEngine' => 'land/engine/ArcanistMercurialLandEngine.php',
|
||||
'ArcanistMercurialLocalState' => 'repository/state/ArcanistMercurialLocalState.php',
|
||||
'ArcanistMercurialParser' => 'repository/parser/ArcanistMercurialParser.php',
|
||||
|
@ -419,6 +424,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistPhutilXHPASTLinterStandard' => 'lint/linter/standards/phutil/ArcanistPhutilXHPASTLinterStandard.php',
|
||||
'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPlusOperatorOnStringsXHPASTLinterRule.php',
|
||||
'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase.php',
|
||||
'ArcanistProductNameLiteralXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistProductNameLiteralXHPASTLinterRule.php',
|
||||
'ArcanistProjectConfigurationSource' => 'config/source/ArcanistProjectConfigurationSource.php',
|
||||
'ArcanistPrompt' => 'toolset/ArcanistPrompt.php',
|
||||
'ArcanistPromptResponse' => 'toolset/ArcanistPromptResponse.php',
|
||||
|
@ -632,6 +638,8 @@ phutil_register_library_map(array(
|
|||
'LinesOfALargeFile' => 'filesystem/linesofalarge/LinesOfALargeFile.php',
|
||||
'LinesOfALargeFileTestCase' => 'filesystem/linesofalarge/__tests__/LinesOfALargeFileTestCase.php',
|
||||
'MFilterTestHelper' => 'utils/__tests__/MFilterTestHelper.php',
|
||||
'MethodCallFuture' => 'future/MethodCallFuture.php',
|
||||
'MethodCallFutureTestCase' => 'future/__tests__/MethodCallFutureTestCase.php',
|
||||
'NoseTestEngine' => 'unit/engine/NoseTestEngine.php',
|
||||
'PHPASTParserTestCase' => 'parser/xhpast/__tests__/PHPASTParserTestCase.php',
|
||||
'PhageAction' => 'phage/action/PhageAction.php',
|
||||
|
@ -907,6 +915,7 @@ phutil_register_library_map(array(
|
|||
'PhutilVeryWowEnglishLocale' => 'internationalization/locales/PhutilVeryWowEnglishLocale.php',
|
||||
'PhutilWordPressFuture' => 'future/wordpress/PhutilWordPressFuture.php',
|
||||
'PhutilXHPASTBinary' => 'parser/xhpast/bin/PhutilXHPASTBinary.php',
|
||||
'PlatformSymbols' => 'platform/PlatformSymbols.php',
|
||||
'PytestTestEngine' => 'unit/engine/PytestTestEngine.php',
|
||||
'TempFile' => 'filesystem/TempFile.php',
|
||||
'TestAbstractDirectedGraph' => 'utils/__tests__/TestAbstractDirectedGraph.php',
|
||||
|
@ -959,6 +968,7 @@ phutil_register_library_map(array(
|
|||
'nonempty' => 'utils/utils.php',
|
||||
'phlog' => 'error/phlog.php',
|
||||
'pht' => 'internationalization/pht.php',
|
||||
'pht_list' => 'internationalization/pht.php',
|
||||
'phutil_build_http_querystring' => 'utils/utils.php',
|
||||
'phutil_build_http_querystring_from_pairs' => 'utils/utils.php',
|
||||
'phutil_censor_credentials' => 'utils/utils.php',
|
||||
|
@ -972,7 +982,6 @@ phutil_register_library_map(array(
|
|||
'phutil_count' => 'internationalization/pht.php',
|
||||
'phutil_date_format' => 'utils/viewutils.php',
|
||||
'phutil_decode_mime_header' => 'utils/utils.php',
|
||||
'phutil_deprecated' => 'init/lib/moduleutils.php',
|
||||
'phutil_describe_type' => 'utils/utils.php',
|
||||
'phutil_encode_log' => 'utils/utils.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_loggable_string' => '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_partition' => 'utils/utils.php',
|
||||
'phutil_passthru' => 'future/exec/execx.php',
|
||||
|
@ -1245,6 +1257,8 @@ phutil_register_library_map(array(
|
|||
'ArcanistDuplicateSwitchCaseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||
'ArcanistDynamicDefineXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistDynamicDefineXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||
'ArcanistEachUseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistEachUseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||
'ArcanistElseIfUsageXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistElseIfUsageXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||
'ArcanistEmptyFileXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
|
@ -1321,6 +1335,8 @@ phutil_register_library_map(array(
|
|||
'ArcanistHgProxyClient' => 'Phobject',
|
||||
'ArcanistHgProxyServer' => 'Phobject',
|
||||
'ArcanistHgServerChannel' => 'PhutilProtocolChannel',
|
||||
'ArcanistHostMemorySnapshot' => 'Phobject',
|
||||
'ArcanistHostMemorySnapshotTestCase' => 'PhutilTestCase',
|
||||
'ArcanistImplicitConstructorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistImplicitConstructorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||
'ArcanistImplicitFallthroughXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
|
@ -1403,6 +1419,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI',
|
||||
'ArcanistMercurialCommitGraphQuery' => 'ArcanistCommitGraphQuery',
|
||||
'ArcanistMercurialCommitMessageHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery',
|
||||
'ArcanistMercurialCommitSymbolCommitHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery',
|
||||
'ArcanistMercurialLandEngine' => 'ArcanistLandEngine',
|
||||
'ArcanistMercurialLocalState' => 'ArcanistRepositoryLocalState',
|
||||
'ArcanistMercurialParser' => 'Phobject',
|
||||
|
@ -1476,6 +1493,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistPhutilXHPASTLinterStandard' => 'ArcanistLinterStandard',
|
||||
'ArcanistPlusOperatorOnStringsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||
'ArcanistProductNameLiteralXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistProjectConfigurationSource' => 'ArcanistWorkingCopyConfigurationSource',
|
||||
'ArcanistPrompt' => 'Phobject',
|
||||
'ArcanistPromptResponse' => 'Phobject',
|
||||
|
@ -1696,6 +1714,8 @@ phutil_register_library_map(array(
|
|||
'LinesOfALargeFile' => 'LinesOfALarge',
|
||||
'LinesOfALargeFileTestCase' => 'PhutilTestCase',
|
||||
'MFilterTestHelper' => 'Phobject',
|
||||
'MethodCallFuture' => 'Future',
|
||||
'MethodCallFutureTestCase' => 'PhutilTestCase',
|
||||
'NoseTestEngine' => 'ArcanistUnitTestEngine',
|
||||
'PHPASTParserTestCase' => 'PhutilTestCase',
|
||||
'PhageAction' => 'Phobject',
|
||||
|
@ -1991,6 +2011,7 @@ phutil_register_library_map(array(
|
|||
'PhutilVeryWowEnglishLocale' => 'PhutilLocale',
|
||||
'PhutilWordPressFuture' => 'FutureProxy',
|
||||
'PhutilXHPASTBinary' => 'Phobject',
|
||||
'PlatformSymbols' => 'Phobject',
|
||||
'PytestTestEngine' => 'ArcanistUnitTestEngine',
|
||||
'TempFile' => 'Phobject',
|
||||
'TestAbstractDirectedGraph' => 'AbstractDirectedGraph',
|
||||
|
|
|
@ -125,8 +125,8 @@ class PhutilLibraryTestCase extends PhutilTestCase {
|
|||
$failures[] = pht(
|
||||
'Class "%s" implements method "%s" with the wrong visibility. '.
|
||||
'The method has visibility "%s", but it is defined in parent '.
|
||||
'"%s" with visibility "%s". In Phabricator, a method which '.
|
||||
'overrides another must always have the same visibility.',
|
||||
'"%s" with visibility "%s". A method which overrides another '.
|
||||
'must always have the same visibility.',
|
||||
$class_name,
|
||||
$method_name,
|
||||
$this->getVisibility($method),
|
||||
|
|
|
@ -89,8 +89,8 @@ final class ArcanistConduitEngine
|
|||
$block = id(new PhutilConsoleBlock())
|
||||
->addParagraph(
|
||||
pht(
|
||||
'This command needs to communicate with Phabricator, but no '.
|
||||
'Phabricator URI is configured.'))
|
||||
'This command needs to communicate with a server, but no '.
|
||||
'server URI is configured.'))
|
||||
->addList($list);
|
||||
|
||||
throw new ArcanistUsageException($block->drawConsoleString());
|
||||
|
|
|
@ -71,10 +71,10 @@ final class ArcanistArcConfigurationEngineExtension
|
|||
->setSummary(pht('Repository for the current working copy.'))
|
||||
->setHelp(
|
||||
pht(
|
||||
'Associate the working copy with a specific Phabricator '.
|
||||
'repository. Normally, Arcanist can figure this association '.
|
||||
'out on its own, but if your setup is unusual you can use '.
|
||||
'this option to tell it what the desired value is.'))
|
||||
'Associate the working copy with a specific repository. Normally, '.
|
||||
'this association can be determined automatically, but if your '.
|
||||
'setup is unusual you can use this option to tell it what the '.
|
||||
'desired value is.'))
|
||||
->setExamples(
|
||||
array(
|
||||
'libexample',
|
||||
|
@ -89,14 +89,15 @@ final class ArcanistArcConfigurationEngineExtension
|
|||
'conduit_uri',
|
||||
'default',
|
||||
))
|
||||
->setSummary(pht('Phabricator install to connect to.'))
|
||||
->setSummary(pht('Server to connect to.'))
|
||||
->setHelp(
|
||||
pht(
|
||||
'Associates this working copy with a specific installation of '.
|
||||
'Phabricator.'))
|
||||
'%s (or compatible software).',
|
||||
PlatformSymbols::getPlatformServerName()))
|
||||
->setExamples(
|
||||
array(
|
||||
'https://phabricator.mycompany.com/',
|
||||
'https://devtools.example.com/',
|
||||
)),
|
||||
id(new ArcanistAliasesConfigOption())
|
||||
->setKey(self::KEY_ALIASES)
|
||||
|
|
|
@ -15,7 +15,7 @@ final class ArcanistBlindlyTrustHTTPEngineExtension
|
|||
}
|
||||
|
||||
public function getExtensionName() {
|
||||
return pht('Arcanist HTTPS Trusted Domains');
|
||||
return pht('HTTPS Trusted Domains');
|
||||
}
|
||||
|
||||
public function shouldTrustAnySSLAuthorityForURI(PhutilURI $uri) {
|
||||
|
|
|
@ -258,7 +258,7 @@ final class ArcanistConfigurationManager extends Phobject {
|
|||
}
|
||||
|
||||
public function getUserConfigurationFileLocation() {
|
||||
if (strlen($this->customArcrcFilename)) {
|
||||
if ($this->customArcrcFilename !== null) {
|
||||
return $this->customArcrcFilename;
|
||||
}
|
||||
|
||||
|
|
|
@ -7,11 +7,11 @@ final class ArcanistSettings extends Phobject {
|
|||
'default' => array(
|
||||
'type' => 'string',
|
||||
'help' => pht(
|
||||
'The URI of a Phabricator install to connect to by default, if '.
|
||||
'%s is run in a project without a Phabricator URI or run outside '.
|
||||
'The URI of a server to connect to by default, if '.
|
||||
'%s is run in a project without a configured URI or run outside '.
|
||||
'of a project.',
|
||||
'arc'),
|
||||
'example' => '"http://phabricator.example.com/"',
|
||||
'example' => '"http://devtools.example.com/"',
|
||||
),
|
||||
'base' => array(
|
||||
'type' => 'string',
|
||||
|
@ -35,7 +35,7 @@ final class ArcanistSettings extends Phobject {
|
|||
'type' => 'string',
|
||||
'example' => '"X"',
|
||||
'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 '.
|
||||
'your setup is unusual you can use this option to tell it what the '.
|
||||
'desired value is.',
|
||||
|
@ -44,10 +44,9 @@ final class ArcanistSettings extends Phobject {
|
|||
'phabricator.uri' => array(
|
||||
'type' => 'string',
|
||||
'legacy' => 'conduit_uri',
|
||||
'example' => '"https://phabricator.mycompany.com/"',
|
||||
'example' => '"https://devtools.example.com/"',
|
||||
'help' => pht(
|
||||
'Associates this working copy with a specific installation of '.
|
||||
'Phabricator.'),
|
||||
'Associates this working copy with a specific server.'),
|
||||
),
|
||||
'lint.engine' => array(
|
||||
'type' => 'string',
|
||||
|
@ -96,8 +95,8 @@ final class ArcanistSettings extends Phobject {
|
|||
'https.cabundle' => array(
|
||||
'type' => 'string',
|
||||
'help' => pht(
|
||||
"Path to a custom CA bundle file to be used for arcanist's cURL ".
|
||||
"calls. This is used primarily when your conduit endpoint is ".
|
||||
"Path to a custom CA bundle file to be used for cURL calls. ".
|
||||
"This is used primarily when your conduit endpoint is ".
|
||||
"behind HTTPS signed by your organization's internal CA."),
|
||||
'example' => 'support/yourca.pem',
|
||||
),
|
||||
|
@ -118,7 +117,7 @@ final class ArcanistSettings extends Phobject {
|
|||
'Whether %s should permit the automatic stashing of changes in the '.
|
||||
'working directory when requiring a clean working copy. This option '.
|
||||
'should only be used when users understand how to restore their '.
|
||||
'working directory from the local stash if an Arcanist operation '.
|
||||
'working directory from the local stash if an operation '.
|
||||
'causes an unrecoverable error.',
|
||||
'arc'),
|
||||
'default' => false,
|
||||
|
|
|
@ -22,25 +22,40 @@ final class PhutilConsoleFormatter extends Phobject {
|
|||
|
||||
public static function getDisableANSI() {
|
||||
if (self::$disableANSI === null) {
|
||||
$term = phutil_utf8_strtolower(getenv('TERM'));
|
||||
// ansicon enables ANSI support on Windows
|
||||
if (!$term && getenv('ANSICON')) {
|
||||
$term = 'ansi';
|
||||
}
|
||||
|
||||
if (phutil_is_windows() && $term !== 'cygwin' && $term !== 'ansi') {
|
||||
self::$disableANSI = true;
|
||||
} else if (!defined('STDOUT')) {
|
||||
self::$disableANSI = true;
|
||||
} else if (function_exists('posix_isatty') && !posix_isatty(STDOUT)) {
|
||||
self::$disableANSI = true;
|
||||
} else {
|
||||
self::$disableANSI = false;
|
||||
}
|
||||
self::$disableANSI = self::newShouldDisableAnsi();
|
||||
}
|
||||
return self::$disableANSI;
|
||||
}
|
||||
|
||||
private static function newShouldDisableANSI() {
|
||||
$term = phutil_utf8_strtolower(getenv('TERM'));
|
||||
|
||||
// ansicon enables ANSI support on Windows
|
||||
if (!$term && getenv('ANSICON')) {
|
||||
$term = 'ansi';
|
||||
}
|
||||
|
||||
|
||||
if (phutil_is_windows()) {
|
||||
if ($term !== 'cygwin' && $term !== 'ansi') {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
$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 /* ... */) {
|
||||
$args = func_get_args();
|
||||
$args[0] = self::interpretFormat($args[0]);
|
||||
|
|
|
@ -22,6 +22,7 @@ final class PhutilInteractiveEditor extends Phobject {
|
|||
private $offset = 0;
|
||||
private $preferred;
|
||||
private $fallback;
|
||||
private $taskMessage;
|
||||
|
||||
|
||||
/* -( Creating a New Editor )---------------------------------------------- */
|
||||
|
@ -74,6 +75,20 @@ final class PhutilInteractiveEditor extends Phobject {
|
|||
$editor = $this->getEditor();
|
||||
$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);
|
||||
|
||||
if ($err) {
|
||||
|
@ -85,7 +100,6 @@ final class PhutilInteractiveEditor extends Phobject {
|
|||
'vim' => true,
|
||||
);
|
||||
|
||||
$binary = basename($editor);
|
||||
if (isset($vi_binaries[$binary])) {
|
||||
// This runs "Q" (an invalid command), then "q" (a valid command,
|
||||
// meaning "quit"). Vim binaries with behavior that makes them poor
|
||||
|
@ -266,6 +280,33 @@ final class PhutilInteractiveEditor extends Phobject {
|
|||
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
|
||||
|
|
|
@ -139,10 +139,10 @@ final class ArcanistDifferentialCommitMessage extends Phobject {
|
|||
throw new ArcanistUsageException(
|
||||
pht(
|
||||
'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".',
|
||||
'D123',
|
||||
'https://phabricator.example.com/D123',
|
||||
'https://devtools.example.com/D123',
|
||||
$revision_value));
|
||||
}
|
||||
|
||||
|
|
|
@ -181,7 +181,6 @@ final class PhutilErrorHandler extends Phobject {
|
|||
* @task internal
|
||||
*/
|
||||
public static function handleError($num, $str, $file, $line, $ctx = null) {
|
||||
|
||||
foreach (self::$traps as $trap) {
|
||||
$trap->addError($num, $str, $file, $line);
|
||||
}
|
||||
|
@ -378,7 +377,7 @@ final class PhutilErrorHandler extends Phobject {
|
|||
* @task internal
|
||||
*/
|
||||
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) {
|
||||
case self::ERROR:
|
||||
|
@ -425,16 +424,6 @@ final class PhutilErrorHandler extends Phobject {
|
|||
$metadata['file'],
|
||||
$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;
|
||||
error_log($default_message);
|
||||
break;
|
||||
|
|
|
@ -28,6 +28,8 @@ final class PhutilDirectoryFixture extends Phobject {
|
|||
}
|
||||
|
||||
public function getPath($to_file = null) {
|
||||
$to_file = phutil_string_cast($to_file);
|
||||
|
||||
return $this->path.'/'.ltrim($to_file, '/');
|
||||
}
|
||||
|
||||
|
|
|
@ -94,7 +94,7 @@ final class PhutilErrorLog
|
|||
|
||||
if (strlen($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_INJECTION = 'injection';
|
||||
const CAPABILITY_TEMPLATE_PNODE = 'template_pnode';
|
||||
const CAPABILTIY_ANNOTATE_TEMPLATES = 'annotate_templates';
|
||||
|
||||
protected function newBinaryVersion() {
|
||||
$future = id(new ExecFuture('hg --version --quiet'))
|
||||
|
@ -60,6 +62,33 @@ final class PhutilMercurialBinaryAnalyzer
|
|||
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(
|
||||
$mercurial_version,
|
||||
|
@ -70,6 +99,10 @@ final class PhutilMercurialBinaryAnalyzer
|
|||
return version_compare($mercurial_version, '3.2', '>=');
|
||||
case self::CAPABILITY_INJECTION:
|
||||
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:
|
||||
throw new Exception(
|
||||
pht(
|
||||
|
|
|
@ -212,7 +212,7 @@ abstract class LinesOfALarge extends Phobject implements Iterator {
|
|||
if (strlen($this->buf)) {
|
||||
$this->num++;
|
||||
$this->line = $this->buf;
|
||||
$this->buf = null;
|
||||
$this->buf = '';
|
||||
} else {
|
||||
$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;
|
||||
}
|
||||
|
||||
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 {
|
||||
$xml = @(new SimpleXMLElement($body));
|
||||
} catch (Exception $ex) {
|
||||
phlog($ex);
|
||||
$xml = null;
|
||||
}
|
||||
|
||||
|
@ -155,9 +156,28 @@ abstract class PhutilAWSFuture extends FutureProxy {
|
|||
);
|
||||
if ($xml) {
|
||||
$params['RequestID'] = $xml->RequestID[0];
|
||||
$errors = array($xml->Error);
|
||||
foreach ($errors as $error) {
|
||||
$params['Errors'][] = array($error->Code, $error->Message);
|
||||
|
||||
// NOTE: The S3 and EC2 APIs return slightly different error responses.
|
||||
|
||||
// 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
|
||||
*/
|
||||
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(
|
||||
$stdout,
|
||||
(string)substr($this->stderr, $this->stderrPos),
|
||||
$stdout_value,
|
||||
$stderr_value,
|
||||
);
|
||||
|
||||
$this->stderrPos = strlen($this->stderr);
|
||||
$this->stderrPos = $this->getStderrBufferLength();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
@ -209,8 +216,16 @@ final class ExecFuture extends PhutilExecutableFuture {
|
|||
$this->updateFuture(); // Sync
|
||||
}
|
||||
|
||||
$result = (string)substr($this->stdout, $this->stdoutPos);
|
||||
$this->stdoutPos = strlen($this->stdout);
|
||||
$stdout = $this->stdout;
|
||||
|
||||
if ($stdout === null) {
|
||||
$result = '';
|
||||
} else {
|
||||
$result = substr($stdout, $this->stdoutPos);
|
||||
}
|
||||
|
||||
$this->stdoutPos = $this->getStdoutBufferLength();
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
@ -475,7 +490,7 @@ final class ExecFuture extends PhutilExecutableFuture {
|
|||
* @task internal
|
||||
*/
|
||||
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_stderr_read_bytes = PHP_INT_MAX;
|
||||
if ($read_buffer_size !== null) {
|
||||
$max_stdout_read_bytes = $read_buffer_size - strlen($this->stdout);
|
||||
$max_stderr_read_bytes = $read_buffer_size - strlen($this->stderr);
|
||||
$stdout_len = $this->getStdoutBufferLength();
|
||||
$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) {
|
||||
$this->stdout .= $this->readAndDiscard(
|
||||
$stdout,
|
||||
$this->getStdoutSizeLimit() - strlen($this->stdout),
|
||||
$this->getStdoutSizeLimit() - $this->getStdoutBufferLength(),
|
||||
'stdout',
|
||||
$max_stdout_read_bytes);
|
||||
}
|
||||
|
@ -772,7 +790,7 @@ final class ExecFuture extends PhutilExecutableFuture {
|
|||
if ($max_stderr_read_bytes > 0) {
|
||||
$this->stderr .= $this->readAndDiscard(
|
||||
$stderr,
|
||||
$this->getStderrSizeLimit() - strlen($this->stderr),
|
||||
$this->getStderrSizeLimit() - $this->getStderrBufferLength(),
|
||||
'stderr',
|
||||
$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
|
||||
* line scripts.
|
||||
*
|
||||
* $exec = new PhutilExecPassthru('ls %s', $dir);
|
||||
* $err = $exec->execute();
|
||||
* $exec = new PhutilExecPassthru('nano -- %s', $filename);
|
||||
* $err = $exec->resolve();
|
||||
*
|
||||
* You can set the current working directory for the command with
|
||||
* @{method:setCWD}, and set the environment with @{method:setEnv}.
|
||||
|
@ -30,6 +30,14 @@ final class PhutilExecPassthru extends PhutilExecutableFuture {
|
|||
/* -( 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.
|
||||
*
|
||||
|
@ -37,7 +45,7 @@ final class PhutilExecPassthru extends PhutilExecutableFuture {
|
|||
*
|
||||
* @task command
|
||||
*/
|
||||
public function execute() {
|
||||
private function executeCommand() {
|
||||
$command = $this->getCommand();
|
||||
|
||||
$is_write = ($this->stdinData !== null);
|
||||
|
@ -45,10 +53,14 @@ final class PhutilExecPassthru extends PhutilExecutableFuture {
|
|||
if ($is_write) {
|
||||
$stdin_spec = array('pipe', 'r');
|
||||
} else {
|
||||
$stdin_spec = STDIN;
|
||||
$stdin_spec = PhutilSystem::getStdinHandle();
|
||||
}
|
||||
|
||||
$spec = array($stdin_spec, STDOUT, STDERR);
|
||||
$spec = array(
|
||||
$stdin_spec,
|
||||
PhutilSystem::getStdoutHandle(),
|
||||
PhutilSystem::getStderrHandle(),
|
||||
);
|
||||
$pipes = array();
|
||||
|
||||
$unmasked_command = $command->getUnmaskedString();
|
||||
|
@ -116,7 +128,7 @@ final class PhutilExecPassthru extends PhutilExecutableFuture {
|
|||
// make it easier to share code with ExecFuture.
|
||||
|
||||
if (!$this->hasResult()) {
|
||||
$result = $this->execute();
|
||||
$result = $this->executeCommand();
|
||||
$this->setResult($result);
|
||||
}
|
||||
|
||||
|
|
|
@ -19,7 +19,7 @@ abstract class PhutilExecutableFuture extends Future {
|
|||
pht(
|
||||
'Command (of class "%s") was constructed with a '.
|
||||
'"PhutilCommandString", but also passed arguments. '.
|
||||
'When using a preprebuilt command, you must not pass '.
|
||||
'When using a prebuilt command, you must not pass '.
|
||||
'arguments.',
|
||||
get_class($this)));
|
||||
}
|
||||
|
|
|
@ -11,7 +11,7 @@ final class ExecPassthruTestCase extends PhutilTestCase {
|
|||
$bin = $this->getSupportExecutable('exit');
|
||||
|
||||
$exec = new PhutilExecPassthru('php -f %R', $bin);
|
||||
$err = $exec->execute();
|
||||
$err = $exec->resolve();
|
||||
$this->assertEqual(0, $err);
|
||||
}
|
||||
|
||||
|
|
|
@ -206,12 +206,14 @@ abstract class BaseHTTPFuture extends Future {
|
|||
* @task config
|
||||
*/
|
||||
public function getHeaders($filter = null) {
|
||||
$filter = strtolower($filter);
|
||||
if ($filter !== null) {
|
||||
$filter = phutil_utf8_strtolower($filter);
|
||||
}
|
||||
|
||||
$result = array();
|
||||
foreach ($this->headers as $header) {
|
||||
list($name, $value) = $header;
|
||||
if (!$filter || ($filter == strtolower($name))) {
|
||||
if (($filter === null) || ($filter === phutil_utf8_strtolower($name))) {
|
||||
$result[] = $header;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -269,7 +269,7 @@ final class HTTPSFuture extends BaseHTTPFuture {
|
|||
curl_setopt($curl, CURLOPT_REDIR_PROTOCOLS, $allowed_protocols);
|
||||
}
|
||||
|
||||
if (strlen($this->rawBody)) {
|
||||
if ($this->rawBody !== null) {
|
||||
if ($this->getData()) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
|
|
|
@ -36,18 +36,6 @@ function phutil_get_current_library_name() {
|
|||
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) {
|
||||
PhutilBootloader::getInstance()->loadLibrary($path);
|
||||
}
|
||||
|
|
|
@ -44,3 +44,7 @@ function phutil_count($countable) {
|
|||
function phutil_person(PhutilPerson $person) {
|
||||
return $person;
|
||||
}
|
||||
|
||||
function pht_list(array $items) {
|
||||
return implode(', ', $items);
|
||||
}
|
||||
|
|
|
@ -1407,6 +1407,21 @@ abstract class ArcanistLandEngine
|
|||
ArcanistLandCommitSet $set,
|
||||
$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(
|
||||
ArcanistLandCommitSet $set,
|
||||
$into_commit);
|
||||
|
@ -1415,12 +1430,35 @@ abstract class ArcanistLandEngine
|
|||
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);
|
||||
|
||||
/**
|
||||
* 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(
|
||||
$into_commit,
|
||||
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);
|
||||
|
||||
private function selectMergeStrategy() {
|
||||
|
|
|
@ -6,6 +6,8 @@ final class ArcanistMercurialLandEngine
|
|||
private $ontoBranchMarker;
|
||||
private $ontoMarkers;
|
||||
|
||||
private $rebasedActiveCommit;
|
||||
|
||||
protected function getDefaultSymbols() {
|
||||
$api = $this->getRepositoryAPI();
|
||||
$log = $this->getLogEngine();
|
||||
|
@ -698,8 +700,14 @@ final class ArcanistMercurialLandEngine
|
|||
$commit_map = array();
|
||||
foreach ($symbols as $symbol) {
|
||||
$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) {
|
||||
list($commits) = $api->execxLocal(
|
||||
'log --rev %s --template %s --',
|
||||
|
@ -789,8 +797,14 @@ final class ArcanistMercurialLandEngine
|
|||
|
||||
$commits = $set->getCommits();
|
||||
|
||||
$min_commit = last($commits)->getHash();
|
||||
$max_commit = head($commits)->getHash();
|
||||
// confirmCommits() reverses the order of the commits as they're ordered
|
||||
// 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();
|
||||
$commit_message = $revision_ref->getCommitMessage();
|
||||
|
@ -820,13 +834,19 @@ final class ArcanistMercurialLandEngine
|
|||
$argv[] = '--keep';
|
||||
$argv[] = '--collapse';
|
||||
|
||||
$future = $api->execFutureLocal('rebase %Ls', $argv);
|
||||
$future = $api->execFutureLocalWithExtension(
|
||||
'rebase',
|
||||
'rebase %Ls',
|
||||
$argv);
|
||||
$future->write($commit_message);
|
||||
$future->resolvex();
|
||||
|
||||
} catch (CommandException $ex) {
|
||||
// TODO
|
||||
// $api->execManualLocal('rebase --abort');
|
||||
// Aborting the rebase should restore the same state prior to running the
|
||||
// rebase command.
|
||||
$api->execManualLocalWithExtension(
|
||||
'rebase',
|
||||
'rebase --abort');
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
|
@ -855,13 +875,21 @@ final class ArcanistMercurialLandEngine
|
|||
list($stdout) = $api->execxLocal('log --rev tip --template %s', '{node}');
|
||||
$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;
|
||||
}
|
||||
|
||||
protected function pushChange($into_commit) {
|
||||
$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) {
|
||||
$api->execxLocal('%Ls', $command);
|
||||
|
@ -876,10 +904,20 @@ final class ArcanistMercurialLandEngine
|
|||
'Push failed! Fix the error and run "arc land" again.'));
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
foreach ($tail as $command) {
|
||||
|
||||
foreach ($tail_pass as $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();
|
||||
$body_commands = array();
|
||||
$tail_commands = array();
|
||||
$tail_pass_commands = array();
|
||||
$tail_fail_commands = array();
|
||||
|
||||
$bookmarks = array();
|
||||
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
|
||||
// "push commit X as bookmark Y" in Mercurial.)
|
||||
|
||||
$restore = array();
|
||||
$restore_bookmarks = array();
|
||||
if ($bookmarks) {
|
||||
$markers = $api->newMarkerRefQuery()
|
||||
->withNames(mpull($bookmarks, 'getName'))
|
||||
|
@ -934,7 +973,9 @@ final class ArcanistMercurialLandEngine
|
|||
hgsprintf('%s', $new_position),
|
||||
$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.
|
||||
|
||||
foreach ($restore as $bookmark_name => $old_position) {
|
||||
$tail = array();
|
||||
$tail[] = 'bookmark';
|
||||
if ($restore_bookmarks) {
|
||||
// Instead of restoring the previous state, assume landing onto bookmarks
|
||||
// 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) {
|
||||
$tail[] = '--delete';
|
||||
} else {
|
||||
$tail[] = '--force';
|
||||
$tail[] = '--rev';
|
||||
$tail[] = hgsprintf('%s', $api->getDisplayHash($old_position));
|
||||
foreach ($restore_bookmarks as $bookmark_name => $old_position) {
|
||||
$tail[] = '--bookmark';
|
||||
$tail[] = $bookmark_name;
|
||||
|
||||
// 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)),
|
||||
);
|
||||
}
|
||||
|
||||
$tail[] = '--';
|
||||
$tail[] = $bookmark_name;
|
||||
|
||||
$tail_commands[] = $tail;
|
||||
if ($tail) {
|
||||
$tail_pass_commands[] = $tail;
|
||||
}
|
||||
}
|
||||
|
||||
return array(
|
||||
$head_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?
|
||||
|
||||
try {
|
||||
$api->execxLocal(
|
||||
$api->execxLocalWithExtension(
|
||||
'rebase',
|
||||
'rebase --source %s --dest %s --keep --keepbranches',
|
||||
$child_hash,
|
||||
$new_commit);
|
||||
} 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;
|
||||
}
|
||||
}
|
||||
|
@ -1040,6 +1099,8 @@ final class ArcanistMercurialLandEngine
|
|||
$revs = array();
|
||||
$obsolete_map = array();
|
||||
|
||||
$using_evolve = $api->getMercurialFeature('evolve');
|
||||
|
||||
// We've rebased all descendants already, so we can safely delete all
|
||||
// of these commits.
|
||||
|
||||
|
@ -1047,10 +1108,22 @@ final class ArcanistMercurialLandEngine
|
|||
foreach ($sets as $set) {
|
||||
$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();
|
||||
$max_commit = last($commits)->getHash();
|
||||
|
||||
$revs[] = hgsprintf('%s::%s', $min_commit, $max_commit);
|
||||
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);
|
||||
}
|
||||
|
||||
foreach ($commits as $commit) {
|
||||
$obsolete_map[$commit->getHash()] = true;
|
||||
|
@ -1071,10 +1144,7 @@ final class ArcanistMercurialLandEngine
|
|||
// bookmarks which point at these now-obsoleted commits.
|
||||
|
||||
$bookmark_refs = $api->newMarkerRefQuery()
|
||||
->withMarkerTypes(
|
||||
array(
|
||||
ArcanistMarkerRef::TYPE_BOOKMARK,
|
||||
))
|
||||
->withMarkerTypes(array(ArcanistMarkerRef::TYPE_BOOKMARK))
|
||||
->execute();
|
||||
foreach ($bookmark_refs as $bookmark_ref) {
|
||||
$bookmark_hash = $bookmark_ref->getCommitHash();
|
||||
|
@ -1093,13 +1163,14 @@ final class ArcanistMercurialLandEngine
|
|||
$bookmark_name);
|
||||
}
|
||||
|
||||
if ($api->getMercurialFeature('evolve')) {
|
||||
if ($using_evolve) {
|
||||
$api->execxLocal(
|
||||
'prune --rev %s',
|
||||
$rev_set);
|
||||
} else {
|
||||
$api->execxLocal(
|
||||
'--config extensions.strip= strip --rev %s',
|
||||
$api->execxLocalWithExtension(
|
||||
'strip',
|
||||
'strip --rev %s',
|
||||
$rev_set);
|
||||
}
|
||||
}
|
||||
|
@ -1108,7 +1179,54 @@ final class ArcanistMercurialLandEngine
|
|||
$into_commit,
|
||||
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();
|
||||
}
|
||||
|
@ -1120,8 +1238,9 @@ final class ArcanistMercurialLandEngine
|
|||
$message = pht(
|
||||
'Holding changes locally, they have not been pushed.');
|
||||
|
||||
list($head, $body, $tail) = $this->newPushCommands($into_commit);
|
||||
$commands = array_merge($head, $body, $tail);
|
||||
list($head, $body, $tail_pass, $tail_fail) = $this->newPushCommands(
|
||||
$into_commit);
|
||||
$commands = array_merge($head, $body, $tail_pass);
|
||||
|
||||
echo tsprintf(
|
||||
"\n%!\n%s\n\n",
|
||||
|
|
|
@ -64,11 +64,11 @@ final class ArcanistCSharpLinter extends ArcanistLinter {
|
|||
throw new Exception(
|
||||
pht(
|
||||
"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 ".
|
||||
"severity using the StyleCop settings dialog (usually accessible ".
|
||||
"from within your IDE). StyleCop settings for your project will ".
|
||||
"be used when linting for Arcanist.",
|
||||
"be used when linting.",
|
||||
'.arclint'));
|
||||
}
|
||||
}
|
||||
|
@ -132,8 +132,8 @@ final class ArcanistCSharpLinter extends ArcanistLinter {
|
|||
} else if ($ver > self::SUPPORTED_VERSION) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Arcanist does not support this version of %s (it is newer). '.
|
||||
'You can try upgrading Arcanist with `%s`.',
|
||||
'This version of %s is not supported (it is too new). '.
|
||||
'You can try upgrading with `%s`.',
|
||||
'cslint',
|
||||
'arc upgrade'));
|
||||
}
|
||||
|
|
|
@ -304,9 +304,9 @@ final class ArcanistPhutilLibraryLinter extends ArcanistLinter {
|
|||
$details = pht(
|
||||
"Common causes are:\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".
|
||||
" Update this copy of Arcanist:\n".
|
||||
" Update this copy of %s:\n".
|
||||
"\n".
|
||||
" %s\n".
|
||||
"\n".
|
||||
|
@ -324,6 +324,8 @@ final class ArcanistPhutilLibraryLinter extends ArcanistLinter {
|
|||
" - This symbol is defined in an external library.\n".
|
||||
" Use \"@phutil-external-symbol\" to annotate it.\n".
|
||||
" Use \"grep\" to find examples of usage.",
|
||||
PlatformSymbols::getPlatformClientName(),
|
||||
PlatformSymbols::getPlatformClientName(),
|
||||
$arcanist_root);
|
||||
|
||||
$message = implode(
|
||||
|
|
|
@ -27,7 +27,11 @@ final class ArcanistXMLLinter extends ArcanistLinter {
|
|||
}
|
||||
|
||||
public function getCacheVersion() {
|
||||
return LIBXML_VERSION;
|
||||
if (defined('LIBXML_VERSION')) {
|
||||
return LIBXML_VERSION;
|
||||
} else {
|
||||
return 'unavailable';
|
||||
}
|
||||
}
|
||||
|
||||
public function lintPath($path) {
|
||||
|
|
|
@ -51,6 +51,13 @@ abstract class ArcanistLinterTestCase extends PhutilTestCase {
|
|||
private function lintFile($file, ArcanistLinter $linter) {
|
||||
$linter = clone $linter;
|
||||
|
||||
if (!$linter->canRun()) {
|
||||
$this->assertSkipped(
|
||||
pht(
|
||||
'Linter "%s" can not run.',
|
||||
get_class($linter)));
|
||||
}
|
||||
|
||||
$contents = Filesystem::readFile($file);
|
||||
$contents = preg_split('/^~{4,}\n/m', $contents);
|
||||
if (count($contents) < 2) {
|
||||
|
@ -283,9 +290,12 @@ abstract class ArcanistLinterTestCase extends PhutilTestCase {
|
|||
}
|
||||
|
||||
private function compareTransform($expected, $actual) {
|
||||
$expected = phutil_string_cast($expected);
|
||||
|
||||
if (!strlen($expected)) {
|
||||
return;
|
||||
}
|
||||
|
||||
$this->assertEqual(
|
||||
$expected,
|
||||
$actual,
|
||||
|
|
|
@ -17,6 +17,14 @@ final class ArcanistCommentStyleXHPASTLinterRule
|
|||
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(
|
||||
$comment->getOffset(),
|
||||
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) {
|
||||
$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(
|
||||
$node,
|
||||
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(
|
||||
$class_ref,
|
||||
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(
|
||||
$class_ref,
|
||||
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();
|
||||
|
||||
$original = $message->getOriginalText();
|
||||
$original = phutil_string_cast($original);
|
||||
|
||||
$replacement = $message->getReplacementText();
|
||||
|
||||
$line = $message->getLine();
|
||||
|
|
|
@ -19,7 +19,7 @@ final class ArcanistLogEngine
|
|||
}
|
||||
|
||||
private function writeBytes($bytes) {
|
||||
fprintf(STDERR, '%s', $bytes);
|
||||
PhutilSystem::writeStderr($bytes);
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,6 @@
|
|||
final class PhutilLibraryMapBuilder extends Phobject {
|
||||
|
||||
private $root;
|
||||
private $quiet = true;
|
||||
private $subprocessLimit = 8;
|
||||
|
||||
private $fileSymbolMap;
|
||||
|
@ -38,19 +37,6 @@ final class PhutilLibraryMapBuilder extends Phobject {
|
|||
$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.
|
||||
*
|
||||
|
@ -108,25 +94,9 @@ final class PhutilLibraryMapBuilder extends Phobject {
|
|||
public function buildAndWriteMap() {
|
||||
$library_map = $this->buildMap();
|
||||
|
||||
$this->log(pht('Writing 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 )---------------------------------------------------- */
|
||||
|
||||
|
@ -236,11 +206,7 @@ final class PhutilLibraryMapBuilder extends Phobject {
|
|||
}
|
||||
|
||||
$json = json_encode($cache);
|
||||
try {
|
||||
Filesystem::writeFile($cache_file, $json);
|
||||
} catch (FilesystemException $ex) {
|
||||
$this->log(pht('Unable to save the cache!'));
|
||||
}
|
||||
Filesystem::writeFile($cache_file, $json);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -251,7 +217,6 @@ final class PhutilLibraryMapBuilder extends Phobject {
|
|||
* @task symbol
|
||||
*/
|
||||
public function dropSymbolCache() {
|
||||
$this->log(pht('Dropping symbol cache...'));
|
||||
Filesystem::remove($this->getPathForSymbolCache());
|
||||
}
|
||||
|
||||
|
@ -451,14 +416,10 @@ EOPHP;
|
|||
*/
|
||||
private function analyzeLibrary() {
|
||||
// Identify all the ".php" source files in the library.
|
||||
$this->log(pht('Finding source files...'));
|
||||
$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
|
||||
// to remap libraries quickly by analyzing only changed files.
|
||||
$this->log(pht('Loading symbol cache...'));
|
||||
$symbol_cache = $this->loadSymbolCache();
|
||||
|
||||
// If the XHPAST binary is not up-to-date, build it now. Otherwise,
|
||||
|
@ -481,23 +442,12 @@ EOPHP;
|
|||
}
|
||||
$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.
|
||||
if ($futures) {
|
||||
$limit = $this->subprocessLimit;
|
||||
|
||||
$this->log(
|
||||
pht(
|
||||
'Analyzing %s file(s) with %s subprocess(es)...',
|
||||
phutil_count($futures),
|
||||
new PhutilNumber($limit)));
|
||||
|
||||
$progress = new PhutilConsoleProgressBar();
|
||||
if ($this->quiet) {
|
||||
$progress->setQuiet(true);
|
||||
}
|
||||
$progress->setTotal(count($futures));
|
||||
|
||||
$futures = id(new FutureIterator($futures))
|
||||
|
@ -525,8 +475,6 @@ EOPHP;
|
|||
$this->writeSymbolCache($symbol_map, $source_map);
|
||||
|
||||
// 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);
|
||||
}
|
||||
|
||||
|
|
|
@ -33,22 +33,27 @@ abstract class Phobject implements Iterator {
|
|||
get_class($this).'::'.$name));
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current() {
|
||||
$this->throwOnAttemptedIteration();
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key() {
|
||||
$this->throwOnAttemptedIteration();
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next() {
|
||||
$this->throwOnAttemptedIteration();
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind() {
|
||||
$this->throwOnAttemptedIteration();
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid() {
|
||||
$this->throwOnAttemptedIteration();
|
||||
}
|
||||
|
|
|
@ -33,7 +33,7 @@ final class ArcanistBaseCommitParser extends Phobject {
|
|||
|
||||
private function log($message) {
|
||||
if ($this->verbose) {
|
||||
fwrite(STDERR, $message."\n");
|
||||
PhutilSystem::writeStderr($message."\n");
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -639,8 +639,7 @@ final class ArcanistBundle extends Phobject {
|
|||
$old_path = $change->getOldPath();
|
||||
$type = $change->getType();
|
||||
|
||||
if (!strlen($old_path) ||
|
||||
$type == ArcanistDiffChangeType::TYPE_ADD) {
|
||||
if ($old_path === '' || $type == ArcanistDiffChangeType::TYPE_ADD) {
|
||||
$old_path = null;
|
||||
}
|
||||
|
||||
|
@ -1023,7 +1022,7 @@ final class ArcanistBundle extends Phobject {
|
|||
if ($is_64bit) {
|
||||
for ($count = 4; $count >= 0; $count--) {
|
||||
$val = $accum % 85;
|
||||
$accum = $accum / 85;
|
||||
$accum = (int)($accum / 85);
|
||||
$slice .= $map[$val];
|
||||
}
|
||||
} else {
|
||||
|
|
|
@ -217,7 +217,7 @@ final class ArcanistDiffParser extends Phobject {
|
|||
$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
|
||||
// changes here.
|
||||
$change = $this->buildChange(null)
|
||||
|
@ -582,10 +582,13 @@ final class ArcanistDiffParser extends Phobject {
|
|||
|
||||
$ok = false;
|
||||
$match = null;
|
||||
foreach ($patterns as $pattern) {
|
||||
$ok = preg_match('@^'.$pattern.'@', $line, $match);
|
||||
if ($ok) {
|
||||
break;
|
||||
|
||||
if ($line !== null) {
|
||||
foreach ($patterns as $pattern) {
|
||||
$ok = preg_match('@^'.$pattern.'@', $line, $match);
|
||||
if ($ok) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -774,7 +777,7 @@ final class ArcanistDiffParser extends Phobject {
|
|||
$this->nextLine();
|
||||
$this->parseGitBinaryPatch();
|
||||
$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).
|
||||
// If there are two blocks, parse both.
|
||||
$this->parseGitBinaryPatch();
|
||||
|
@ -920,11 +923,11 @@ final class ArcanistDiffParser extends Phobject {
|
|||
$hunk->setNewOffset($matches[3]);
|
||||
|
||||
// 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)) {
|
||||
$old_len = 1;
|
||||
}
|
||||
$new_len = idx($matches, 4);
|
||||
$new_len = idx($matches, 4, '');
|
||||
if (!strlen($new_len)) {
|
||||
$new_len = 1;
|
||||
}
|
||||
|
@ -1041,7 +1044,7 @@ final class ArcanistDiffParser extends Phobject {
|
|||
$line = $this->nextNonemptyLine();
|
||||
}
|
||||
|
||||
} while (preg_match('/^@@ /', $line));
|
||||
} while (($line !== null) && preg_match('/^@@ /', $line));
|
||||
}
|
||||
|
||||
protected function buildChange($path = null) {
|
||||
|
|
|
@ -85,7 +85,7 @@ final class PhutilBugtraqParser extends Phobject {
|
|||
$captured_text = $capture['text'];
|
||||
$captured_offset = $capture['at'];
|
||||
|
||||
if (strlen($select_regexp)) {
|
||||
if ($select_regexp !== null) {
|
||||
$selections = null;
|
||||
preg_match_all(
|
||||
$select_regexp,
|
||||
|
|
|
@ -13,6 +13,7 @@ final class PhutilEmailAddress extends Phobject {
|
|||
private $domainName;
|
||||
|
||||
public function __construct($email_address = null) {
|
||||
$email_address = phutil_string_cast($email_address);
|
||||
$email_address = trim($email_address);
|
||||
|
||||
$matches = null;
|
||||
|
@ -41,12 +42,13 @@ final class PhutilEmailAddress extends Phobject {
|
|||
|
||||
public function __toString() {
|
||||
$address = $this->getAddress();
|
||||
if (strlen($this->displayName)) {
|
||||
|
||||
if (phutil_nonempty_string($this->displayName)) {
|
||||
$display_name = $this->encodeDisplayName($this->displayName);
|
||||
return $display_name.' <'.$address.'>';
|
||||
} else {
|
||||
return $address;
|
||||
}
|
||||
|
||||
return $address;
|
||||
}
|
||||
|
||||
public function setDisplayName($display_name) {
|
||||
|
@ -89,7 +91,7 @@ final class PhutilEmailAddress extends Phobject {
|
|||
|
||||
public function getAddress() {
|
||||
$address = $this->localPart;
|
||||
if (strlen($this->domainName)) {
|
||||
if ($this->domainName !== null && strlen($this->domainName)) {
|
||||
$address .= '@'.$this->domainName;
|
||||
}
|
||||
return $address;
|
||||
|
|
|
@ -154,9 +154,9 @@ final class PhutilURI extends Phobject {
|
|||
|
||||
$user = $this->user;
|
||||
$pass = $this->pass;
|
||||
if (strlen($user) && strlen($pass)) {
|
||||
if (phutil_nonempty_string($user) && phutil_nonempty_string($pass)) {
|
||||
$auth = rawurlencode($user).':'.rawurlencode($pass).'@';
|
||||
} else if (strlen($user)) {
|
||||
} else if (phutil_nonempty_string($user)) {
|
||||
$auth = rawurlencode($user).'@';
|
||||
} else {
|
||||
$auth = null;
|
||||
|
@ -166,19 +166,24 @@ final class PhutilURI extends Phobject {
|
|||
if ($this->isGitURI()) {
|
||||
$protocol = null;
|
||||
} else {
|
||||
if (strlen($auth)) {
|
||||
if ($auth !== null) {
|
||||
$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()) {
|
||||
$prefix = "{$auth}{$domain}";
|
||||
} else {
|
||||
$prefix = "{$protocol}://{$auth}{$domain}";
|
||||
}
|
||||
|
||||
if (strlen($port)) {
|
||||
if ($has_port) {
|
||||
$prefix .= ':'.$port;
|
||||
}
|
||||
}
|
||||
|
@ -189,7 +194,7 @@ final class PhutilURI extends Phobject {
|
|||
$query = null;
|
||||
}
|
||||
|
||||
if (strlen($this->getFragment())) {
|
||||
if (phutil_nonempty_string($this->getFragment())) {
|
||||
$fragment = '#'.$this->getFragment();
|
||||
} else {
|
||||
$fragment = null;
|
||||
|
@ -428,7 +433,7 @@ final class PhutilURI extends Phobject {
|
|||
if ($this->isGitURI()) {
|
||||
// Git URIs use relative paths which do not need to begin with "/".
|
||||
} else {
|
||||
if ($this->domain && strlen($path) && $path[0] !== '/') {
|
||||
if ($this->domain && phutil_nonempty_string($path) && $path[0] !== '/') {
|
||||
$path = '/'.$path;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -80,6 +80,7 @@ final class AASTNodeList
|
|||
|
||||
/* -( Countable )---------------------------------------------------------- */
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count() {
|
||||
return count($this->ids);
|
||||
}
|
||||
|
@ -87,22 +88,27 @@ final class AASTNodeList
|
|||
|
||||
/* -( Iterator )----------------------------------------------------------- */
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current() {
|
||||
return $this->list[$this->key()];
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key() {
|
||||
return $this->ids[$this->pos];
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next() {
|
||||
$this->pos++;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind() {
|
||||
$this->pos = 0;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid() {
|
||||
return $this->pos < count($this->ids);
|
||||
}
|
||||
|
|
|
@ -625,6 +625,33 @@ final class PhutilArgumentParser extends Phobject {
|
|||
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() {
|
||||
return $this->argv;
|
||||
}
|
||||
|
@ -769,7 +796,12 @@ final class PhutilArgumentParser extends Phobject {
|
|||
pht('There is no **%s** workflow.', $workflow_name));
|
||||
} else {
|
||||
$out[] = $this->indent($indent, $workflow->getExamples());
|
||||
$out[] = $this->indent($indent, $workflow->getSynopsis());
|
||||
|
||||
$synopsis = $workflow->getSynopsis();
|
||||
if ($synopsis !== null) {
|
||||
$out[] = $this->indent($indent, $workflow->getSynopsis());
|
||||
}
|
||||
|
||||
if ($show_details) {
|
||||
$full_help = $workflow->getHelp();
|
||||
if ($full_help) {
|
||||
|
@ -800,7 +832,7 @@ final class PhutilArgumentParser extends Phobject {
|
|||
|
||||
|
||||
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) {
|
||||
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 {
|
||||
|
||||
/**
|
||||
* 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 $localCommitInfo;
|
||||
private $rawDiffCache = array();
|
||||
|
@ -13,25 +20,24 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
|||
private $featureFutures = array();
|
||||
|
||||
protected function buildLocalFuture(array $argv) {
|
||||
$env = $this->getMercurialEnvironmentVariables();
|
||||
$argv[0] = self::ROOT_HG_COMMAND.$argv[0];
|
||||
|
||||
$argv[0] = 'hg '.$argv[0];
|
||||
|
||||
$future = newv('ExecFuture', $argv)
|
||||
->setEnv($env)
|
||||
->setCWD($this->getPath());
|
||||
|
||||
return $future;
|
||||
return $this->newConfiguredFuture(newv('ExecFuture', $argv));
|
||||
}
|
||||
|
||||
public function newPassthru($pattern /* , ... */) {
|
||||
$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();
|
||||
|
||||
$args[0] = 'hg '.$args[0];
|
||||
|
||||
return newv('PhutilExecPassthru', $args)
|
||||
return $future
|
||||
->setEnv($env)
|
||||
->setCWD($this->getPath());
|
||||
}
|
||||
|
@ -448,6 +454,10 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
|||
}
|
||||
}
|
||||
|
||||
protected function newCurrentCommitSymbol() {
|
||||
return $this->getWorkingCopyRevision();
|
||||
}
|
||||
|
||||
public function getWorkingCopyRevision() {
|
||||
return '.';
|
||||
}
|
||||
|
@ -655,37 +665,117 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
|||
public function doCommit($message) {
|
||||
$tmp_file = new TempFile();
|
||||
Filesystem::writeFile($tmp_file, $message);
|
||||
$this->execxLocal('commit -l %s', $tmp_file);
|
||||
$this->execxLocal('commit --logfile %s', $tmp_file);
|
||||
$this->reloadWorkingCopy();
|
||||
}
|
||||
|
||||
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('.');
|
||||
}
|
||||
|
||||
$tmp_file = new TempFile();
|
||||
Filesystem::writeFile($tmp_file, $message);
|
||||
|
||||
try {
|
||||
$this->execxLocal(
|
||||
'commit --amend -l %s',
|
||||
$tmp_file);
|
||||
} catch (CommandException $ex) {
|
||||
if (preg_match('/nothing changed/', $ex->getStdout())) {
|
||||
// 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 {
|
||||
if ($this->getMercurialFeature('evolve')) {
|
||||
$this->execxLocal('amend --logfile %s --', $tmp_file);
|
||||
try {
|
||||
$this->execxLocal('evolve --all --');
|
||||
} catch (CommandException $ex) {
|
||||
$this->execxLocal('evolve --abort --');
|
||||
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();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
if ($commit == 'null') {
|
||||
return pht('(The Empty Void)');
|
||||
|
@ -957,6 +1047,129 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
|||
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) {
|
||||
if (array_key_exists($feature, $this->featureResults)) {
|
||||
return $this->featureResults[$feature];
|
||||
|
@ -982,8 +1195,9 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
|||
private function newMercurialFeatureFuture($feature) {
|
||||
switch ($feature) {
|
||||
case 'shelve':
|
||||
return $this->execFutureLocal(
|
||||
'--config extensions.shelve= shelve --help --');
|
||||
return $this->execFutureLocalWithExtension(
|
||||
'shelve',
|
||||
'shelve --help --');
|
||||
case 'evolve':
|
||||
return $this->execFutureLocal('prune --help --');
|
||||
default:
|
||||
|
@ -1017,17 +1231,6 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
|||
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) {
|
||||
return new ArcanistRepositoryURINormalizer(
|
||||
ArcanistRepositoryURINormalizer::TYPE_MERCURIAL,
|
||||
|
|
|
@ -19,12 +19,6 @@ final class ArcanistMercurialRepositoryMarkerQuery
|
|||
// to provide a command which works like "git for-each-ref" locally and
|
||||
// "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
|
||||
// because it's a remote command and may prompt the user to provide
|
||||
// credentials interactively. In local mode, we can just read stdout.
|
||||
|
@ -33,20 +27,17 @@ final class ArcanistMercurialRepositoryMarkerQuery
|
|||
$tmpfile = new TempFile();
|
||||
Filesystem::remove($tmpfile);
|
||||
|
||||
$argv = array();
|
||||
$argv[] = '--output';
|
||||
$argv[] = phutil_string_cast($tmpfile);
|
||||
}
|
||||
|
||||
$argv[] = '--';
|
||||
|
||||
if ($remote !== null) {
|
||||
$argv[] = '--';
|
||||
$argv[] = $remote->getRemoteName();
|
||||
}
|
||||
|
||||
if ($remote !== null) {
|
||||
$passthru = $api->newPassthru('%Ls', $argv);
|
||||
$err = $api->execPassthruWithExtension(
|
||||
'arc-hg',
|
||||
'arc-ls-markers %Ls',
|
||||
$argv);
|
||||
|
||||
$err = $passthru->execute();
|
||||
if ($err) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
|
@ -57,8 +48,10 @@ final class ArcanistMercurialRepositoryMarkerQuery
|
|||
$raw_data = Filesystem::readFile($tmpfile);
|
||||
unset($tmpfile);
|
||||
} else {
|
||||
$future = $api->newFuture('%Ls', $argv);
|
||||
list($raw_data) = $future->resolve();
|
||||
$future = $api->execFutureLocalWithExtension(
|
||||
'arc-hg',
|
||||
'arc-ls-markers --');
|
||||
list($err, $raw_data) = $future->resolve();
|
||||
}
|
||||
|
||||
$items = phutil_json_decode($raw_data);
|
||||
|
|
|
@ -64,8 +64,10 @@ abstract class ArcanistRepositoryMarkerQuery
|
|||
$marker->attachWorkingCopyStateRef($state_ref);
|
||||
|
||||
$hash = $marker->getCommitHash();
|
||||
$hash = $api->getDisplayHash($hash);
|
||||
$marker->setDisplayHash($hash);
|
||||
if ($hash !== null) {
|
||||
$hash = $api->getDisplayHash($hash);
|
||||
$marker->setDisplayHash($hash);
|
||||
}
|
||||
}
|
||||
|
||||
$types = $this->markerTypes;
|
||||
|
|
|
@ -7,6 +7,14 @@ final class ArcanistMercurialLocalState
|
|||
private $localBranch;
|
||||
private $localBookmark;
|
||||
|
||||
public function getLocalCommit() {
|
||||
return $this->localCommit;
|
||||
}
|
||||
|
||||
public function getLocalBookmark() {
|
||||
return $this->localBookmark;
|
||||
}
|
||||
|
||||
protected function executeSaveLocalState() {
|
||||
$api = $this->getRepositoryAPI();
|
||||
$log = $this->getWorkflow()->getLogEngine();
|
||||
|
@ -152,8 +160,9 @@ final class ArcanistMercurialLocalState
|
|||
'arc-%s',
|
||||
Filesystem::readRandomCharacters(12));
|
||||
|
||||
$api->execxLocal(
|
||||
'--config extensions.shelve= shelve --unknown --name %s --',
|
||||
$api->execxLocalWithExtension(
|
||||
'shelve',
|
||||
'shelve --unknown --name %s --',
|
||||
$stash_ref);
|
||||
|
||||
$log->writeStatus(
|
||||
|
@ -171,16 +180,18 @@ final class ArcanistMercurialLocalState
|
|||
pht('UNSHELVE'),
|
||||
pht('Restoring uncommitted changes to working copy.'));
|
||||
|
||||
$api->execxLocal(
|
||||
'--config extensions.shelve= unshelve --keep --name %s --',
|
||||
$api->execxLocalWithExtension(
|
||||
'shelve',
|
||||
'unshelve --keep --name %s --',
|
||||
$stash_ref);
|
||||
}
|
||||
|
||||
protected function discardStash($stash_ref) {
|
||||
$api = $this->getRepositoryAPI();
|
||||
|
||||
$api->execxLocal(
|
||||
'--config extensions.shelve= shelve --delete %s --',
|
||||
$api->execxLocalWithExtension(
|
||||
'shelve',
|
||||
'shelve --delete %s --',
|
||||
$stash_ref);
|
||||
}
|
||||
|
||||
|
|
|
@ -192,10 +192,28 @@ abstract class ArcanistRepositoryLocalState
|
|||
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() {
|
||||
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) {
|
||||
throw new PhutilMethodNotImplementedException();
|
||||
}
|
||||
|
|
|
@ -270,9 +270,9 @@ final class ArcanistRuntime {
|
|||
$problems[] = sprintf(
|
||||
'The build of PHP you are running was compiled with the configure '.
|
||||
'flag "%s", which means it does not support the function "%s()". '.
|
||||
'This function is required for Arcanist to run. Install a standard '.
|
||||
'build of PHP or rebuild it without this flag. You may also be '.
|
||||
'able to build or install the relevant extension separately.',
|
||||
'This function is required for this software to run. Install a '.
|
||||
'standard build of PHP or rebuild it without this flag. You may '.
|
||||
'also be able to build or install the relevant extension separately.',
|
||||
$which,
|
||||
$fname);
|
||||
continue;
|
||||
|
@ -477,8 +477,8 @@ final class ArcanistRuntime {
|
|||
$log->writeWarn(
|
||||
pht('VERY META'),
|
||||
pht(
|
||||
'You are running one copy of Arcanist (at path "%s") against '.
|
||||
'another copy of Arcanist (at path "%s"). Code in the current '.
|
||||
'You are running one copy of this software (at path "%s") against '.
|
||||
'another copy of this software (at path "%s"). Code in the current '.
|
||||
'working directory will not be loaded or executed.',
|
||||
$executing_directory,
|
||||
$working_directory));
|
||||
|
@ -519,10 +519,10 @@ final class ArcanistRuntime {
|
|||
if (!isset($toolsets[$binary])) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Arcanist toolset "%s" is unknown. The Arcanist binary should '.
|
||||
'be executed so that "argv[0]" identifies a supported toolset. '.
|
||||
'Rename the binary or install the library that provides the '.
|
||||
'desired toolset. Current available toolsets: %s.',
|
||||
'Toolset "%s" is unknown. The binary should be executed so that '.
|
||||
'"argv[0]" identifies a supported toolset. Rename the binary or '.
|
||||
'install the library that provides the desired toolset. Current '.
|
||||
'available toolsets: %s.',
|
||||
$binary,
|
||||
implode(', ', array_keys($toolsets))));
|
||||
}
|
||||
|
|
|
@ -137,7 +137,7 @@ final class PhutilServiceProfiler extends Phobject {
|
|||
|
||||
$uri = phutil_censor_credentials($data['uri']);
|
||||
|
||||
if (strlen($proxy)) {
|
||||
if ($proxy !== null) {
|
||||
$desc = "{$proxy} >> {$uri}";
|
||||
} else {
|
||||
$desc = $uri;
|
||||
|
@ -203,6 +203,10 @@ final class PhutilServiceProfiler extends Phobject {
|
|||
}
|
||||
|
||||
private static function escapeProfilerStringForDisplay($string) {
|
||||
if ($string === null) {
|
||||
return '';
|
||||
}
|
||||
|
||||
// Convert tabs and newlines to spaces and collapse blocks of whitespace,
|
||||
// most often formatting in queries.
|
||||
$string = preg_replace('/\s{2,}/', ' ', $string);
|
||||
|
|
|
@ -226,8 +226,8 @@ final class PhutilClassMapQuery extends Phobject {
|
|||
$unique = $this->uniqueMethod;
|
||||
$sort = $this->sortMethod;
|
||||
|
||||
if (strlen($expand)) {
|
||||
if (!strlen($unique)) {
|
||||
if ($expand !== null) {
|
||||
if ($unique === null) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Trying to execute a class map query for descendants of class '.
|
||||
|
@ -245,7 +245,7 @@ final class PhutilClassMapQuery extends Phobject {
|
|||
->loadObjects();
|
||||
|
||||
// Apply the "expand" mechanism, if it is configured.
|
||||
if (strlen($expand)) {
|
||||
if ($expand !== null) {
|
||||
$list = array();
|
||||
foreach ($objects as $object) {
|
||||
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.
|
||||
if (strlen($unique)) {
|
||||
if ($unique !== null) {
|
||||
$map = array();
|
||||
foreach ($list as $object) {
|
||||
$key = call_user_func(array($object, $unique));
|
||||
|
@ -287,12 +287,12 @@ final class PhutilClassMapQuery extends Phobject {
|
|||
}
|
||||
|
||||
// Apply the "filter" mechanism, if it is configured.
|
||||
if (strlen($filter)) {
|
||||
if ($filter !== null) {
|
||||
$map = mfilter($map, $filter);
|
||||
}
|
||||
|
||||
// Apply the "sort" mechanism, if it is configured.
|
||||
if (strlen($sort)) {
|
||||
if ($sort !== null) {
|
||||
if ($map) {
|
||||
// The "sort" method may return scalars (which we want to sort with
|
||||
// "msort()"), or may return PhutilSortVector objects (which we want
|
||||
|
|
|
@ -296,11 +296,12 @@ final class PhutilSymbolLoader {
|
|||
// library without breaking library startup.
|
||||
if ($should_continue) {
|
||||
// We may not have `pht()` yet.
|
||||
fprintf(
|
||||
STDERR,
|
||||
$message = sprintf(
|
||||
"%s: %s\n",
|
||||
'IGNORING CLASS LOAD FAILURE',
|
||||
$caught->getMessage());
|
||||
|
||||
@file_put_contents('php://stderr', $message);
|
||||
} else {
|
||||
throw $caught;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ final class ArcanistArcToolset extends ArcanistToolset {
|
|||
array(
|
||||
'name' => 'conduit-uri',
|
||||
'param' => 'uri',
|
||||
'help' => pht('Connect to Phabricator install specified by __uri__.'),
|
||||
'help' => pht('Connect to server specified by __uri__.'),
|
||||
),
|
||||
array(
|
||||
'name' => 'conduit-token',
|
||||
|
|
|
@ -14,7 +14,7 @@ final class ArcanistShellCompleteWorkflow
|
|||
public function getWorkflowInformation() {
|
||||
$help = pht(<<<EOTEXT
|
||||
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.
|
||||
|
||||
|
@ -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
|
||||
the updated configuration.
|
||||
|
||||
Once installed, completion should work across all Arcanist toolsets.
|
||||
Once installed, completion should work across all toolsets.
|
||||
|
||||
**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 install new Arcanist toolsets; or
|
||||
- you move the Arcanist directory; or
|
||||
- you upgrade Arcanist and the new version fixes shell completion bugs.
|
||||
- you install new toolsets; or
|
||||
- you move this software on disk; or
|
||||
- you upgrade this software and the new version fixes shell completion bugs.
|
||||
EOTEXT
|
||||
);
|
||||
|
||||
|
|
|
@ -170,16 +170,14 @@ final class PhutilUnitTestEngine extends ArcanistUnitTestEngine {
|
|||
if (!$library_name) {
|
||||
throw new Exception(
|
||||
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".
|
||||
" %s\n\n".
|
||||
"This probably means one of two things:\n\n".
|
||||
" - You may need to add this library to %s.\n".
|
||||
" - You may be running tests on a copy of libphutil or ".
|
||||
"arcanist using a different copy of libphutil or arcanist. ".
|
||||
"This operation is not supported.\n",
|
||||
$library_root,
|
||||
'.arcconfig.'));
|
||||
"Make sure this library is configured to load.\n\n".
|
||||
"(In rare cases, this may be because you are attempting to run ".
|
||||
"one copy of this software against a different copy of this ".
|
||||
"software. This operation is not supported.)",
|
||||
$library_root));
|
||||
}
|
||||
|
||||
$path = Filesystem::resolvePath($path, $root);
|
||||
|
|
|
@ -154,6 +154,147 @@ abstract class PhutilTestCase extends Phobject {
|
|||
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 )------------------------------------------------- */
|
||||
|
||||
|
|
|
@ -65,9 +65,10 @@ final class ArcanistUnitConsoleRenderer extends ArcanistUnitRenderer {
|
|||
50 => "<fg:green>%s</fg><fg:yellow>{$star}</fg> ",
|
||||
200 => '<fg:green>%s</fg> ',
|
||||
500 => '<fg:yellow>%s</fg> ',
|
||||
INF => '<fg:red>%s</fg> ',
|
||||
);
|
||||
|
||||
$least_acceptable = '<fg:red>%s</fg> ';
|
||||
|
||||
$milliseconds = $seconds * 1000;
|
||||
$duration = $this->formatTime($seconds);
|
||||
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(end($acceptableness), $duration);
|
||||
|
||||
return phutil_console_format($least_acceptable, $duration);
|
||||
}
|
||||
|
||||
private function formatTime($seconds) {
|
||||
|
|
|
@ -313,7 +313,7 @@ final class ArcanistFileUploader extends Phobject {
|
|||
* @task internal
|
||||
*/
|
||||
private function writeStatus($message) {
|
||||
fwrite(STDERR, $message."\n");
|
||||
PhutilSystem::writeStderr($message."\n");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -29,6 +29,7 @@ abstract class PhutilArray
|
|||
/* -( Countable Interface )------------------------------------------------ */
|
||||
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function count() {
|
||||
return count($this->data);
|
||||
}
|
||||
|
@ -37,22 +38,27 @@ abstract class PhutilArray
|
|||
/* -( Iterator Interface )------------------------------------------------- */
|
||||
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function current() {
|
||||
return current($this->data);
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function key() {
|
||||
return key($this->data);
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function next() {
|
||||
return next($this->data);
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function rewind() {
|
||||
reset($this->data);
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function valid() {
|
||||
return (key($this->data) !== null);
|
||||
}
|
||||
|
@ -61,18 +67,22 @@ abstract class PhutilArray
|
|||
/* -( ArrayAccess Interface )---------------------------------------------- */
|
||||
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetExists($key) {
|
||||
return array_key_exists($key, $this->data);
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetGet($key) {
|
||||
return $this->data[$key];
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetSet($key, $value) {
|
||||
$this->data[$key] = $value;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function offsetUnset($key) {
|
||||
unset($this->data[$key]);
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ final class PhutilCallbackFilterIterator extends FilterIterator {
|
|||
$this->callback = $callback;
|
||||
}
|
||||
|
||||
#[\ReturnTypeWillChange]
|
||||
public function accept() {
|
||||
return call_user_func($this->callback, $this->current());
|
||||
}
|
||||
|
|
|
@ -3,10 +3,73 @@
|
|||
/**
|
||||
* Interact with the operating system.
|
||||
*
|
||||
* @task stdio Interacting with Standard I/O
|
||||
* @task memory Interacting with System Memory
|
||||
*/
|
||||
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.
|
||||
|
|
|
@ -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.
|
||||
*/
|
||||
function phutil_utf8_console_strlen($string) {
|
||||
$string = phutil_string_cast($string);
|
||||
|
||||
// Formatting and colors don't contribute any width in the console.
|
||||
$string = preg_replace("/\x1B\[\d*m/", '', $string);
|
||||
|
||||
|
|
|
@ -2094,3 +2094,128 @@ function phutil_raise_preg_exception($function, array $argv) {
|
|||
|
||||
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);
|
||||
$label = array_shift($labels);
|
||||
while ($n >= $scale && count($labels)) {
|
||||
$remainder += ($n % $scale) * $accum;
|
||||
$remainder += ((int)$n % $scale) * $accum;
|
||||
$n /= $scale;
|
||||
$accum *= $scale;
|
||||
$label = array_shift($labels);
|
||||
|
|
|
@ -164,8 +164,6 @@ EOTEXT
|
|||
->execute();
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
if ($api->getUncommittedChanges()) {
|
||||
// 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.
|
||||
- Results are written to stdout as a JSON blob.
|
||||
|
||||
This workflow is primarily useful for writing scripts which integrate
|
||||
with Phabricator. Examples:
|
||||
This workflow is primarily useful for writing scripts. Examples:
|
||||
|
||||
$ echo '{}' | arc call-conduit -- conduit.ping
|
||||
$ echo '{"phid":"PHID-FILE-xxxx"}' | arc call-conduit -- file.download
|
||||
|
|
|
@ -115,8 +115,7 @@ EOTEXT
|
|||
'raw' => array(
|
||||
'help' => pht(
|
||||
'Read diff from stdin, not from the working copy. This disables '.
|
||||
'many Arcanist/Phabricator features which depend on having access '.
|
||||
'to the working copy.'),
|
||||
'many features which depend on having access to the working copy.'),
|
||||
'conflicts' => array(
|
||||
'apply-patches' => pht('%s disables lint.', '--raw'),
|
||||
'never-apply-patches' => pht('%s disables lint.', '--raw'),
|
||||
|
@ -138,8 +137,8 @@ EOTEXT
|
|||
'param' => 'command',
|
||||
'help' => pht(
|
||||
'Generate diff by executing a specified command, not from the '.
|
||||
'working copy. This disables many Arcanist/Phabricator features '.
|
||||
'which depend on having access to the working copy.'),
|
||||
'working copy. This disables many features which depend on having '.
|
||||
'access to the working copy.'),
|
||||
'conflicts' => array(
|
||||
'apply-patches' => pht('%s disables lint.', '--raw-command'),
|
||||
'never-apply-patches' => pht('%s disables lint.', '--raw-command'),
|
||||
|
@ -326,9 +325,8 @@ EOTEXT
|
|||
'head' => array(
|
||||
'param' => 'commit',
|
||||
'help' => pht(
|
||||
'Specify the end of the commit range. This disables many '.
|
||||
'Arcanist/Phabricator features which depend on having access to '.
|
||||
'the working copy.'),
|
||||
'Specify the end of the commit range. This disables many features '.
|
||||
'which depend on having access to the working copy.'),
|
||||
'supports' => array('git'),
|
||||
'nosupport' => array(
|
||||
'svn' => pht('Subversion does not support commit ranges.'),
|
||||
|
@ -517,7 +515,7 @@ EOTEXT
|
|||
if ($is_draft) {
|
||||
throw new ArcanistUsageException(
|
||||
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 '.
|
||||
'the flag or upgrade the server software.'));
|
||||
}
|
||||
|
@ -674,6 +672,8 @@ EOTEXT
|
|||
if ($should_edit) {
|
||||
$edited = $this->newInteractiveEditor($remote_corpus)
|
||||
->setName('differential-edit-revision-info')
|
||||
->setTaskMessage(pht(
|
||||
'Update the details for a revision, then save and exit.'))
|
||||
->editInteractively();
|
||||
if ($edited != $remote_corpus) {
|
||||
$remote_corpus = $edited;
|
||||
|
@ -699,7 +699,7 @@ EOTEXT
|
|||
$this->revisionID = $revision_id;
|
||||
|
||||
$revision['message'] = $this->getArgument('message');
|
||||
if (!strlen($revision['message'])) {
|
||||
if ($revision['message'] === null) {
|
||||
$update_messages = $this->readScratchJSONFile('update-messages.json');
|
||||
|
||||
$update_messages[$revision_id] = $this->getUpdateMessage(
|
||||
|
@ -806,7 +806,10 @@ EOTEXT
|
|||
if ($is_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');
|
||||
} else if ($this->getArgument('raw-command')) {
|
||||
list($raw_diff) = execx('%C', $this->getArgument('raw-command'));
|
||||
|
@ -947,7 +950,7 @@ EOTEXT
|
|||
} catch (ConduitClientException $e) {
|
||||
if ($e->getErrorCode() == 'ERR-BAD-ARCANIST-PROJECT') {
|
||||
echo phutil_console_wrap(
|
||||
pht('Lookup of encoding in arcanist project failed: %s',
|
||||
pht('Lookup of encoding in project failed: %s',
|
||||
$e->getMessage())."\n");
|
||||
} else {
|
||||
throw $e;
|
||||
|
@ -988,10 +991,10 @@ EOTEXT
|
|||
'these files will be marked as binary.',
|
||||
phutil_count($utf8_problems)),
|
||||
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 ".
|
||||
"correct encoding problems) by reading 'User Guide: UTF-8 and ".
|
||||
"Character Encoding' in the Phabricator documentation."),
|
||||
"Character Encoding' in the documentation."),
|
||||
pht(
|
||||
'%s AFFECTED FILE(S)',
|
||||
phutil_count($utf8_problems)));
|
||||
|
@ -1476,6 +1479,8 @@ EOTEXT
|
|||
} else {
|
||||
$new_template = $this->newInteractiveEditor($template)
|
||||
->setName('new-commit')
|
||||
->setTaskMessage(pht(
|
||||
'Provide the details for a new revision, then save and exit.'))
|
||||
->editInteractively();
|
||||
}
|
||||
$first = false;
|
||||
|
@ -1736,9 +1741,12 @@ EOTEXT
|
|||
if ($template == '') {
|
||||
$comments = $this->getDefaultUpdateMessage();
|
||||
|
||||
$comments = phutil_string_cast($comments);
|
||||
$comments = rtrim($comments);
|
||||
|
||||
$template = sprintf(
|
||||
"%s\n\n# %s\n#\n# %s\n# %s\n#\n# %s\n# $ %s\n\n",
|
||||
rtrim($comments),
|
||||
$comments,
|
||||
pht(
|
||||
'Updating %s: %s',
|
||||
"D{$fields['revisionID']}",
|
||||
|
@ -1752,6 +1760,8 @@ EOTEXT
|
|||
|
||||
$comments = $this->newInteractiveEditor($template)
|
||||
->setName('differential-update-comments')
|
||||
->setTaskMessage(pht(
|
||||
'Update the revision comments, then save and exit.'))
|
||||
->editInteractively();
|
||||
|
||||
return $comments;
|
||||
|
@ -2354,7 +2364,7 @@ EOTEXT
|
|||
if (strlen($branch)) {
|
||||
$upstream_path = $api->getPathToUpstream($branch);
|
||||
$remote_branch = $upstream_path->getRemoteBranchName();
|
||||
if (strlen($remote_branch)) {
|
||||
if ($remote_branch !== null) {
|
||||
return array(
|
||||
array(
|
||||
'type' => 'branch',
|
||||
|
@ -2368,7 +2378,7 @@ EOTEXT
|
|||
// If "arc.land.onto.default" is configured, use that.
|
||||
$config_key = 'arc.land.onto.default';
|
||||
$onto = $this->getConfigFromAnySource($config_key);
|
||||
if (strlen($onto)) {
|
||||
if ($onto !== null) {
|
||||
return array(
|
||||
array(
|
||||
'type' => 'branch',
|
||||
|
@ -2643,7 +2653,7 @@ EOTEXT
|
|||
if (!$supported) {
|
||||
$this->writeInfo(
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
|
@ -19,12 +19,12 @@ EOTEXT
|
|||
public function getCommandHelp() {
|
||||
return phutil_console_format(<<<EOTEXT
|
||||
Supports: http, https
|
||||
Installs Conduit credentials into your ~/.arcrc for the given install
|
||||
of Phabricator. You need to do this before you can use 'arc', as it
|
||||
enables 'arc' to link your command-line activity with your account on
|
||||
the web. Run this command from within a project directory to install
|
||||
that project's certificate, or specify an explicit URI (like
|
||||
"https://phabricator.example.com/").
|
||||
Installs Conduit credentials into your ~/.arcrc for the given server.
|
||||
You need to do this before you can use 'arc', as it enables 'arc' to
|
||||
link your command-line activity with your account on the web. Run
|
||||
this command from within a project directory to install that
|
||||
project's certificate, or specify an explicit URI (like
|
||||
"https://devtools.example.com/").
|
||||
EOTEXT
|
||||
);
|
||||
}
|
||||
|
@ -91,12 +91,11 @@ EOTEXT
|
|||
// Ignore.
|
||||
}
|
||||
|
||||
echo phutil_console_format("**%s**\n", pht('LOGIN TO PHABRICATOR'));
|
||||
echo phutil_console_format("**%s**\n", pht('LOG IN'));
|
||||
echo phutil_console_format(
|
||||
"%s\n\n%s\n\n%s",
|
||||
pht(
|
||||
'Open this page in your browser and login to '.
|
||||
'Phabricator if necessary:'),
|
||||
'Open this page in your browser and log in if necessary:'),
|
||||
$token_uri,
|
||||
pht('Then paste the API Token on that page below.'));
|
||||
|
||||
|
@ -204,7 +203,7 @@ EOTEXT
|
|||
$uri = $conduit_uri;
|
||||
}
|
||||
|
||||
$example = 'https://phabricator.example.com/';
|
||||
$example = 'https://devtools.example.com/';
|
||||
|
||||
$uri_object = new PhutilURI($uri);
|
||||
$protocol = $uri_object->getProtocol();
|
||||
|
|
|
@ -11,13 +11,13 @@ final class ArcanistLiberateWorkflow
|
|||
// TOOLSETS: Expand this help.
|
||||
|
||||
$help = pht(<<<EOTEXT
|
||||
Create or update an Arcanist library.
|
||||
Create or update a library.
|
||||
EOTEXT
|
||||
);
|
||||
|
||||
return $this->newWorkflowInformation()
|
||||
->setSynopsis(
|
||||
pht('Create or update an Arcanist library.'))
|
||||
pht('Create or update a library.'))
|
||||
->addExample(pht('**liberate**'))
|
||||
->addExample(pht('**liberate** [__path__]'))
|
||||
->setHelp($help);
|
||||
|
@ -79,22 +79,35 @@ EOTEXT
|
|||
);
|
||||
}
|
||||
|
||||
$any_errors = false;
|
||||
foreach ($paths as $path) {
|
||||
$log->writeStatus(
|
||||
pht('WORK'),
|
||||
pht(
|
||||
'Updating library: %s',
|
||||
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));
|
||||
}
|
||||
}
|
||||
|
||||
$log->writeSuccess(
|
||||
pht('DONE'),
|
||||
pht('Updated %s librarie(s).', phutil_count($paths)));
|
||||
if (!$any_errors) {
|
||||
$log->writeSuccess(
|
||||
pht('DONE'),
|
||||
pht('Updated %s librarie(s).', phutil_count($paths)));
|
||||
}
|
||||
|
||||
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) {
|
||||
if (!Filesystem::pathExists($path.'/__phutil_library_init__.php')) {
|
||||
echo tsprintf(
|
||||
|
@ -103,8 +116,7 @@ EOTEXT
|
|||
'No library currently exists at the path "%s"...',
|
||||
$path));
|
||||
$this->liberateCreateDirectory($path);
|
||||
$this->liberateCreateLibrary($path);
|
||||
return;
|
||||
return $this->liberateCreateLibrary($path);
|
||||
}
|
||||
|
||||
$version = $this->getLibraryFormatVersion($path);
|
||||
|
@ -119,8 +131,6 @@ EOTEXT
|
|||
throw new ArcanistUsageException(
|
||||
pht("Unknown library version '%s'!", $version));
|
||||
}
|
||||
|
||||
echo tsprintf("%s\n", pht('Done.'));
|
||||
}
|
||||
|
||||
private function getLibraryFormatVersion($path) {
|
||||
|
@ -140,6 +150,10 @@ EOTEXT
|
|||
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) {
|
||||
$bin = $this->getScriptPath('support/lib/rebuild-map.php');
|
||||
|
||||
|
@ -181,10 +195,14 @@ EOTEXT
|
|||
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) {
|
||||
$init_path = $path.'/__phutil_library_init__.php';
|
||||
if (Filesystem::pathExists($init_path)) {
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
|
||||
echo pht("Creating new libphutil library in '%s'.", $path)."\n";
|
||||
|
@ -213,7 +231,7 @@ EOTEXT
|
|||
'__phutil_library_init__.php',
|
||||
$path);
|
||||
Filesystem::writeFile($init_path, $template);
|
||||
$this->liberateVersion2($path);
|
||||
return $this->liberateVersion2($path);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -707,7 +707,7 @@ EOTEXT
|
|||
'git apply --whitespace nowarn --index --reject -- %s',
|
||||
$patchfile);
|
||||
$passthru->setCWD($repository_api->getPath());
|
||||
$err = $passthru->execute();
|
||||
$err = $passthru->resolve();
|
||||
|
||||
if ($err) {
|
||||
echo phutil_console_format(
|
||||
|
@ -890,8 +890,8 @@ EOTEXT
|
|||
'revision_id' => $revision_id,
|
||||
));
|
||||
$prompt_message = pht(
|
||||
' Note arcanist failed to load the commit message '.
|
||||
'from differential for revision %s.',
|
||||
' NOTE: Failed to load the commit message from Differential (for '.
|
||||
'revision "%s".)',
|
||||
"D{$revision_id}");
|
||||
}
|
||||
|
||||
|
@ -909,6 +909,8 @@ EOTEXT
|
|||
|
||||
$commit_message = $this->newInteractiveEditor($template)
|
||||
->setName('arcanist-patch-commit-message')
|
||||
->setTaskMessage(pht(
|
||||
'Supply a commit message for this patch, then save and exit.'))
|
||||
->editInteractively();
|
||||
|
||||
$commit_message = ArcanistCommentRemover::removeComments($commit_message);
|
||||
|
|
|
@ -9,12 +9,12 @@ final class ArcanistUpgradeWorkflow
|
|||
|
||||
public function getWorkflowInformation() {
|
||||
$help = pht(<<<EOTEXT
|
||||
Upgrade Arcanist to the latest version.
|
||||
Upgrade this program to the latest version.
|
||||
EOTEXT
|
||||
);
|
||||
|
||||
return $this->newWorkflowInformation()
|
||||
->setSynopsis(pht('Upgrade Arcanist to the latest version.'))
|
||||
->setSynopsis(pht('Upgrade this program to the latest version.'))
|
||||
->addExample(pht('**upgrade**'))
|
||||
->setHelp($help);
|
||||
}
|
||||
|
@ -51,10 +51,10 @@ EOTEXT
|
|||
if (!$is_git) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'The "arc upgrade" workflow uses "git pull" to upgrade '.
|
||||
'Arcanist, but the "arcanist/" directory (in "%s") is not a Git '.
|
||||
'working copy. You must leave "arcanist/" as a Git '.
|
||||
'working copy to use "arc upgrade".',
|
||||
'The "upgrade" workflow uses "git pull" to upgrade, but '.
|
||||
'the software directory (in "%s") is not a Git working '.
|
||||
'copy. You must leave this directory as a Git working copy to '.
|
||||
'use "arc upgrade".',
|
||||
$root));
|
||||
}
|
||||
|
||||
|
@ -125,7 +125,7 @@ EOTEXT
|
|||
|
||||
$log->writeSuccess(
|
||||
pht('UPGRADED'),
|
||||
pht('Your copy of Arcanist is now up to date.'));
|
||||
pht('This software is now up to date.'));
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ final class ArcanistUploadWorkflow
|
|||
|
||||
public function getWorkflowInformation() {
|
||||
$help = pht(<<<EOTEXT
|
||||
Upload one or more files from local disk to Phabricator.
|
||||
Upload one or more files from local disk.
|
||||
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.
|
||||
|
||||
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
|
||||
to that branch or bookmark.
|
||||
If you provide the name of an existing branch or bookmark, the working copy
|
||||
will be switched to that branch or bookmark.
|
||||
|
||||
If you provide the name of a revision or task, Arcanist will look for a related
|
||||
branch or bookmark that exists in the working copy. If it finds one, it will
|
||||
switch to it. If it does not find one, it will attempt to create a new branch
|
||||
or bookmark.
|
||||
If you provide the name of a revision or task, the workflow will look for a
|
||||
related branch or bookmark that already exists in the working copy. If one is
|
||||
found, it will switch to it. If it does not find one, it will attempt to create
|
||||
a new branch or bookmark.
|
||||
|
||||
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
|
||||
|
|
|
@ -143,7 +143,7 @@ abstract class ArcanistWorkflow extends Phobject {
|
|||
|
||||
if ($information) {
|
||||
$synopsis = $information->getSynopsis();
|
||||
if (strlen($synopsis)) {
|
||||
if ($synopsis !== null) {
|
||||
$phutil_workflow->setSynopsis($synopsis);
|
||||
}
|
||||
|
||||
|
@ -154,7 +154,7 @@ abstract class ArcanistWorkflow extends Phobject {
|
|||
}
|
||||
|
||||
$help = $information->getHelp();
|
||||
if (strlen($help)) {
|
||||
if ($help !== null) {
|
||||
// Unwrap linebreaks in the help text so we don't get weird formatting.
|
||||
$help = preg_replace("/(?<=\S)\n(?=\S)/", ' ', $help);
|
||||
|
||||
|
@ -534,7 +534,7 @@ abstract class ArcanistWorkflow extends Phobject {
|
|||
$conduit_uri = $this->conduitURI;
|
||||
$message = phutil_console_format(
|
||||
"\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("The server '%s' rejected your request:", $conduit_uri),
|
||||
$ex->getMessage());
|
||||
|
@ -1234,6 +1234,9 @@ abstract class ArcanistWorkflow extends Phobject {
|
|||
|
||||
$commit_message = $this->newInteractiveEditor($template)
|
||||
->setName(pht('commit-message'))
|
||||
->setTaskMessage(pht(
|
||||
'Supply commit message for uncommitted changes, then save and '.
|
||||
'exit.'))
|
||||
->editInteractively();
|
||||
|
||||
if ($commit_message === $template) {
|
||||
|
@ -1562,7 +1565,7 @@ abstract class ArcanistWorkflow extends Phobject {
|
|||
* @return void
|
||||
*/
|
||||
final protected function writeStatusMessage($msg) {
|
||||
fwrite(STDERR, $msg);
|
||||
PhutilSystem::writeStderr($msg);
|
||||
}
|
||||
|
||||
final public function writeInfo($title, $message) {
|
||||
|
@ -1954,11 +1957,10 @@ abstract class ArcanistWorkflow extends Phobject {
|
|||
} catch (ConduitClientException $ex) {
|
||||
if ($ex->getErrorCode() == 'ERR-CONDUIT-CALL') {
|
||||
$reasons[] = pht(
|
||||
'This version of Arcanist is more recent than the version of '.
|
||||
'Phabricator you are connecting to: the Phabricator install is '.
|
||||
'out of date and does not have support for identifying '.
|
||||
'repositories by callsign or URI. Update Phabricator to enable '.
|
||||
'these features.');
|
||||
'This software version on the server you are connecting to is out '.
|
||||
'of date and does not have support for identifying repositories '.
|
||||
'by callsign or URI. Update the server sofwware to enable these '.
|
||||
'features.');
|
||||
return array(null, $reasons);
|
||||
}
|
||||
throw $ex;
|
||||
|
@ -2201,9 +2203,8 @@ abstract class ArcanistWorkflow extends Phobject {
|
|||
|
||||
throw new ArcanistUsageException(
|
||||
pht(
|
||||
"Unable to find a browser command to run. Set '%s' in your ".
|
||||
"Arcanist config to specify a command to use.",
|
||||
'browser'));
|
||||
'Unable to find a browser command to run. Set "browser" in your '.
|
||||
'configuration to specify a command to use.'));
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -21,18 +21,123 @@ from mercurial import (
|
|||
hg,
|
||||
i18n,
|
||||
node,
|
||||
registrar,
|
||||
)
|
||||
|
||||
_ = i18n._
|
||||
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(
|
||||
b'arc-ls-markers',
|
||||
[(b'', b'output', b'',
|
||||
_(b'file to output refs to'), _(b'FILE')),
|
||||
] + cmdutil.remoteopts,
|
||||
[
|
||||
(b'',
|
||||
b'output',
|
||||
b'',
|
||||
_(b'file to output refs to'),
|
||||
_(b'FILE')),
|
||||
] + remoteopts,
|
||||
_(b'[--output FILENAME] [SOURCE]'))
|
||||
def lsmarkers(ui, repo, source=None, **opts):
|
||||
"""list markers
|
||||
|
@ -168,7 +273,7 @@ def remotemarkers(ui, repo, source, opts):
|
|||
|
||||
markers = []
|
||||
|
||||
source, branches = hg.parseurl(ui.expandpath(source))
|
||||
source, branches = parseurl(ui.expandpath(source))
|
||||
remote = hg.peer(repo, opts, source)
|
||||
|
||||
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