1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2025-01-09 06:11:01 +01:00

(stable) Promote 2020 Week 26

This commit is contained in:
epriestley 2020-07-03 13:10:08 -07:00
commit 8795282286
144 changed files with 13526 additions and 4267 deletions

3
.gitignore vendored
View file

@ -33,3 +33,6 @@
# Generated shell completion rulesets.
/support/shell/rules/
# Python extension compiled files.
/support/hg/arc-hg.pyc

View file

@ -55,6 +55,12 @@ $base_args->parsePartial(
'help' => pht('Load a libphutil library.'),
'repeat' => true,
),
array(
'name' => 'library',
'param' => 'path',
'help' => pht('Load a library (same as --load-phutil-library).'),
'repeat' => true,
),
array(
'name' => 'arcrc-file',
'param' => 'filename',
@ -89,7 +95,9 @@ $force_conduit = $base_args->getArg('conduit-uri');
$force_token = $base_args->getArg('conduit-token');
$custom_arcrc = $base_args->getArg('arcrc-file');
$is_anonymous = $base_args->getArg('anonymous');
$load = $base_args->getArg('load-phutil-library');
$load = array_merge(
$base_args->getArg('load-phutil-library'),
$base_args->getArg('library'));
$help = $base_args->getArg('help');
$args = array_values($base_args->getUnconsumedArgumentVector());

View file

@ -49,11 +49,11 @@ phutil_register_library_map(array(
'ArcanistBlacklistedFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistBlacklistedFunctionXHPASTLinterRule.php',
'ArcanistBlacklistedFunctionXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistBlacklistedFunctionXHPASTLinterRuleTestCase.php',
'ArcanistBlindlyTrustHTTPEngineExtension' => 'configuration/ArcanistBlindlyTrustHTTPEngineExtension.php',
'ArcanistBookmarkWorkflow' => 'workflow/ArcanistBookmarkWorkflow.php',
'ArcanistBookmarksWorkflow' => 'workflow/ArcanistBookmarksWorkflow.php',
'ArcanistBoolConfigOption' => 'config/option/ArcanistBoolConfigOption.php',
'ArcanistBraceFormattingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistBraceFormattingXHPASTLinterRule.php',
'ArcanistBraceFormattingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistBraceFormattingXHPASTLinterRuleTestCase.php',
'ArcanistBranchRef' => 'ref/ArcanistBranchRef.php',
'ArcanistBranchWorkflow' => 'workflow/ArcanistBranchWorkflow.php',
'ArcanistBranchesWorkflow' => 'workflow/ArcanistBranchesWorkflow.php',
'ArcanistBrowseCommitHardpointQuery' => 'browse/query/ArcanistBrowseCommitHardpointQuery.php',
'ArcanistBrowseCommitURIHardpointQuery' => 'browse/query/ArcanistBrowseCommitURIHardpointQuery.php',
'ArcanistBrowseObjectNameURIHardpointQuery' => 'browse/query/ArcanistBrowseObjectNameURIHardpointQuery.php',
@ -64,8 +64,14 @@ phutil_register_library_map(array(
'ArcanistBrowseURIHardpointQuery' => 'browse/query/ArcanistBrowseURIHardpointQuery.php',
'ArcanistBrowseURIRef' => 'browse/ref/ArcanistBrowseURIRef.php',
'ArcanistBrowseWorkflow' => 'browse/workflow/ArcanistBrowseWorkflow.php',
'ArcanistBuildPlanRef' => 'ref/ArcanistBuildPlanRef.php',
'ArcanistBuildRef' => 'ref/ArcanistBuildRef.php',
'ArcanistBuildBuildplanHardpointQuery' => 'ref/build/ArcanistBuildBuildplanHardpointQuery.php',
'ArcanistBuildPlanRef' => 'ref/buildplan/ArcanistBuildPlanRef.php',
'ArcanistBuildPlanSymbolRef' => 'ref/buildplan/ArcanistBuildPlanSymbolRef.php',
'ArcanistBuildRef' => 'ref/build/ArcanistBuildRef.php',
'ArcanistBuildSymbolRef' => 'ref/build/ArcanistBuildSymbolRef.php',
'ArcanistBuildableBuildsHardpointQuery' => 'ref/buildable/ArcanistBuildableBuildsHardpointQuery.php',
'ArcanistBuildableRef' => 'ref/buildable/ArcanistBuildableRef.php',
'ArcanistBuildableSymbolRef' => 'ref/buildable/ArcanistBuildableSymbolRef.php',
'ArcanistBundle' => 'parser/ArcanistBundle.php',
'ArcanistBundleTestCase' => 'parser/__tests__/ArcanistBundleTestCase.php',
'ArcanistCSSLintLinter' => 'lint/linter/ArcanistCSSLintLinter.php',
@ -100,6 +106,16 @@ phutil_register_library_map(array(
'ArcanistCommentSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCommentSpacingXHPASTLinterRule.php',
'ArcanistCommentStyleXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCommentStyleXHPASTLinterRule.php',
'ArcanistCommentStyleXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistCommentStyleXHPASTLinterRuleTestCase.php',
'ArcanistCommitGraph' => 'repository/graph/ArcanistCommitGraph.php',
'ArcanistCommitGraphPartition' => 'repository/graph/ArcanistCommitGraphPartition.php',
'ArcanistCommitGraphPartitionQuery' => 'repository/graph/ArcanistCommitGraphPartitionQuery.php',
'ArcanistCommitGraphQuery' => 'repository/graph/query/ArcanistCommitGraphQuery.php',
'ArcanistCommitGraphSet' => 'repository/graph/ArcanistCommitGraphSet.php',
'ArcanistCommitGraphSetQuery' => 'repository/graph/ArcanistCommitGraphSetQuery.php',
'ArcanistCommitGraphSetTreeView' => 'repository/graph/view/ArcanistCommitGraphSetTreeView.php',
'ArcanistCommitGraphSetView' => 'repository/graph/view/ArcanistCommitGraphSetView.php',
'ArcanistCommitGraphTestCase' => 'repository/graph/__tests__/ArcanistCommitGraphTestCase.php',
'ArcanistCommitNode' => 'repository/graph/ArcanistCommitNode.php',
'ArcanistCommitRef' => 'ref/commit/ArcanistCommitRef.php',
'ArcanistCommitSymbolRef' => 'ref/commit/ArcanistCommitSymbolRef.php',
'ArcanistCommitSymbolRefInspector' => 'ref/commit/ArcanistCommitSymbolRefInspector.php',
@ -110,7 +126,8 @@ phutil_register_library_map(array(
'ArcanistComprehensiveLintEngine' => 'lint/engine/ArcanistComprehensiveLintEngine.php',
'ArcanistConcatenationOperatorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistConcatenationOperatorXHPASTLinterRule.php',
'ArcanistConcatenationOperatorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistConcatenationOperatorXHPASTLinterRuleTestCase.php',
'ArcanistConduitCall' => 'conduit/ArcanistConduitCall.php',
'ArcanistConduitAuthenticationException' => 'exception/ArcanistConduitAuthenticationException.php',
'ArcanistConduitCallFuture' => 'conduit/ArcanistConduitCallFuture.php',
'ArcanistConduitEngine' => 'conduit/ArcanistConduitEngine.php',
'ArcanistConduitException' => 'conduit/ArcanistConduitException.php',
'ArcanistConfigOption' => 'config/option/ArcanistConfigOption.php',
@ -162,8 +179,6 @@ phutil_register_library_map(array(
'ArcanistDifferentialDependencyGraph' => 'differential/ArcanistDifferentialDependencyGraph.php',
'ArcanistDifferentialRevisionHash' => 'differential/constants/ArcanistDifferentialRevisionHash.php',
'ArcanistDifferentialRevisionStatus' => 'differential/constants/ArcanistDifferentialRevisionStatus.php',
'ArcanistDisplayRef' => 'ref/ArcanistDisplayRef.php',
'ArcanistDisplayRefInterface' => 'ref/ArcanistDisplayRefInterface.php',
'ArcanistDoubleQuoteXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistDoubleQuoteXHPASTLinterRule.php',
'ArcanistDoubleQuoteXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistDoubleQuoteXHPASTLinterRuleTestCase.php',
'ArcanistDownloadWorkflow' => 'workflow/ArcanistDownloadWorkflow.php',
@ -186,8 +201,6 @@ phutil_register_library_map(array(
'ArcanistExternalLinterTestCase' => 'lint/linter/__tests__/ArcanistExternalLinterTestCase.php',
'ArcanistExtractUseXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistExtractUseXHPASTLinterRule.php',
'ArcanistExtractUseXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistExtractUseXHPASTLinterRuleTestCase.php',
'ArcanistFeatureBaseWorkflow' => 'workflow/ArcanistFeatureBaseWorkflow.php',
'ArcanistFeatureWorkflow' => 'workflow/ArcanistFeatureWorkflow.php',
'ArcanistFileConfigurationSource' => 'config/source/ArcanistFileConfigurationSource.php',
'ArcanistFileDataRef' => 'upload/ArcanistFileDataRef.php',
'ArcanistFileRef' => 'ref/file/ArcanistFileRef.php',
@ -209,10 +222,17 @@ phutil_register_library_map(array(
'ArcanistGeneratedLinterTestCase' => 'lint/linter/__tests__/ArcanistGeneratedLinterTestCase.php',
'ArcanistGetConfigWorkflow' => 'workflow/ArcanistGetConfigWorkflow.php',
'ArcanistGitAPI' => 'repository/api/ArcanistGitAPI.php',
'ArcanistGitCommitGraphQuery' => 'repository/graph/query/ArcanistGitCommitGraphQuery.php',
'ArcanistGitCommitMessageHardpointQuery' => 'query/ArcanistGitCommitMessageHardpointQuery.php',
'ArcanistGitCommitSymbolCommitHardpointQuery' => 'ref/commit/ArcanistGitCommitSymbolCommitHardpointQuery.php',
'ArcanistGitLandEngine' => 'land/ArcanistGitLandEngine.php',
'ArcanistGitLandEngine' => 'land/engine/ArcanistGitLandEngine.php',
'ArcanistGitLocalState' => 'repository/state/ArcanistGitLocalState.php',
'ArcanistGitRawCommit' => 'repository/raw/ArcanistGitRawCommit.php',
'ArcanistGitRawCommitTestCase' => 'repository/raw/__tests__/ArcanistGitRawCommitTestCase.php',
'ArcanistGitRepositoryMarkerQuery' => 'repository/marker/ArcanistGitRepositoryMarkerQuery.php',
'ArcanistGitRepositoryRemoteQuery' => 'repository/remote/ArcanistGitRepositoryRemoteQuery.php',
'ArcanistGitUpstreamPath' => 'repository/api/ArcanistGitUpstreamPath.php',
'ArcanistGitWorkEngine' => 'work/ArcanistGitWorkEngine.php',
'ArcanistGitWorkingCopy' => 'workingcopy/ArcanistGitWorkingCopy.php',
'ArcanistGitWorkingCopyRevisionHardpointQuery' => 'query/ArcanistGitWorkingCopyRevisionHardpointQuery.php',
'ArcanistGlobalVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistGlobalVariableXHPASTLinterRule.php',
@ -221,6 +241,10 @@ phutil_register_library_map(array(
'ArcanistGoLintLinterTestCase' => 'lint/linter/__tests__/ArcanistGoLintLinterTestCase.php',
'ArcanistGoTestResultParser' => 'unit/parser/ArcanistGoTestResultParser.php',
'ArcanistGoTestResultParserTestCase' => 'unit/parser/__tests__/ArcanistGoTestResultParserTestCase.php',
'ArcanistGridCell' => 'console/grid/ArcanistGridCell.php',
'ArcanistGridColumn' => 'console/grid/ArcanistGridColumn.php',
'ArcanistGridRow' => 'console/grid/ArcanistGridRow.php',
'ArcanistGridView' => 'console/grid/ArcanistGridView.php',
'ArcanistHLintLinter' => 'lint/linter/ArcanistHLintLinter.php',
'ArcanistHLintLinterTestCase' => 'lint/linter/__tests__/ArcanistHLintLinterTestCase.php',
'ArcanistHardpoint' => 'hardpoint/ArcanistHardpoint.php',
@ -280,7 +304,11 @@ phutil_register_library_map(array(
'ArcanistKeywordCasingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistKeywordCasingXHPASTLinterRuleTestCase.php',
'ArcanistLambdaFuncFunctionXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLambdaFuncFunctionXHPASTLinterRule.php',
'ArcanistLambdaFuncFunctionXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistLambdaFuncFunctionXHPASTLinterRuleTestCase.php',
'ArcanistLandEngine' => 'land/ArcanistLandEngine.php',
'ArcanistLandCommit' => 'land/ArcanistLandCommit.php',
'ArcanistLandCommitSet' => 'land/ArcanistLandCommitSet.php',
'ArcanistLandEngine' => 'land/engine/ArcanistLandEngine.php',
'ArcanistLandSymbol' => 'land/ArcanistLandSymbol.php',
'ArcanistLandTarget' => 'land/ArcanistLandTarget.php',
'ArcanistLandWorkflow' => 'workflow/ArcanistLandWorkflow.php',
'ArcanistLanguageConstructParenthesesXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLanguageConstructParenthesesXHPASTLinterRule.php',
'ArcanistLanguageConstructParenthesesXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistLanguageConstructParenthesesXHPASTLinterRuleTestCase.php',
@ -309,12 +337,23 @@ phutil_register_library_map(array(
'ArcanistLogMessage' => 'log/ArcanistLogMessage.php',
'ArcanistLogicalOperatorsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLogicalOperatorsXHPASTLinterRule.php',
'ArcanistLogicalOperatorsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistLogicalOperatorsXHPASTLinterRuleTestCase.php',
'ArcanistLookWorkflow' => 'workflow/ArcanistLookWorkflow.php',
'ArcanistLowercaseFunctionsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLowercaseFunctionsXHPASTLinterRule.php',
'ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase.php',
'ArcanistMarkerRef' => 'repository/marker/ArcanistMarkerRef.php',
'ArcanistMarkersWorkflow' => 'workflow/ArcanistMarkersWorkflow.php',
'ArcanistMercurialAPI' => 'repository/api/ArcanistMercurialAPI.php',
'ArcanistMercurialCommitGraphQuery' => 'repository/graph/query/ArcanistMercurialCommitGraphQuery.php',
'ArcanistMercurialCommitMessageHardpointQuery' => 'query/ArcanistMercurialCommitMessageHardpointQuery.php',
'ArcanistMercurialLandEngine' => 'land/engine/ArcanistMercurialLandEngine.php',
'ArcanistMercurialLocalState' => 'repository/state/ArcanistMercurialLocalState.php',
'ArcanistMercurialParser' => 'repository/parser/ArcanistMercurialParser.php',
'ArcanistMercurialParserTestCase' => 'repository/parser/__tests__/ArcanistMercurialParserTestCase.php',
'ArcanistMercurialRepositoryMarkerQuery' => 'repository/marker/ArcanistMercurialRepositoryMarkerQuery.php',
'ArcanistMercurialRepositoryRemoteQuery' => 'repository/remote/ArcanistMercurialRepositoryRemoteQuery.php',
'ArcanistMercurialWorkEngine' => 'work/ArcanistMercurialWorkEngine.php',
'ArcanistMercurialWorkingCopy' => 'workingcopy/ArcanistMercurialWorkingCopy.php',
'ArcanistMercurialWorkingCopyRevisionHardpointQuery' => 'query/ArcanistMercurialWorkingCopyRevisionHardpointQuery.php',
'ArcanistMergeConflictLinter' => 'lint/linter/ArcanistMergeConflictLinter.php',
'ArcanistMergeConflictLinterTestCase' => 'lint/linter/__tests__/ArcanistMergeConflictLinterTestCase.php',
'ArcanistMessageRevisionHardpointQuery' => 'query/ArcanistMessageRevisionHardpointQuery.php',
@ -322,6 +361,7 @@ phutil_register_library_map(array(
'ArcanistMissingLinterException' => 'lint/linter/exception/ArcanistMissingLinterException.php',
'ArcanistModifierOrderingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistModifierOrderingXHPASTLinterRule.php',
'ArcanistModifierOrderingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistModifierOrderingXHPASTLinterRuleTestCase.php',
'ArcanistMultiSourceConfigOption' => 'config/option/ArcanistMultiSourceConfigOption.php',
'ArcanistNamespaceFirstStatementXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistNamespaceFirstStatementXHPASTLinterRule.php',
'ArcanistNamespaceFirstStatementXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistNamespaceFirstStatementXHPASTLinterRuleTestCase.php',
'ArcanistNamingConventionsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistNamingConventionsXHPASTLinterRule.php',
@ -377,6 +417,8 @@ phutil_register_library_map(array(
'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase.php',
'ArcanistProjectConfigurationSource' => 'config/source/ArcanistProjectConfigurationSource.php',
'ArcanistPrompt' => 'toolset/ArcanistPrompt.php',
'ArcanistPromptResponse' => 'toolset/ArcanistPromptResponse.php',
'ArcanistPromptsConfigOption' => 'config/option/ArcanistPromptsConfigOption.php',
'ArcanistPromptsWorkflow' => 'toolset/workflow/ArcanistPromptsWorkflow.php',
'ArcanistPublicPropertyXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistPublicPropertyXHPASTLinterRule.php',
'ArcanistPublicPropertyXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistPublicPropertyXHPASTLinterRuleTestCase.php',
@ -390,17 +432,30 @@ phutil_register_library_map(array(
'ArcanistRaggedClassTreeEdgeXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistRaggedClassTreeEdgeXHPASTLinterRuleTestCase.php',
'ArcanistRef' => 'ref/ArcanistRef.php',
'ArcanistRefInspector' => 'inspector/ArcanistRefInspector.php',
'ArcanistRefView' => 'ref/ArcanistRefView.php',
'ArcanistRemoteRef' => 'repository/remote/ArcanistRemoteRef.php',
'ArcanistRemoteRefInspector' => 'repository/remote/ArcanistRemoteRefInspector.php',
'ArcanistRemoteRepositoryRefsHardpointQuery' => 'repository/remote/ArcanistRemoteRepositoryRefsHardpointQuery.php',
'ArcanistRepositoryAPI' => 'repository/api/ArcanistRepositoryAPI.php',
'ArcanistRepositoryAPIMiscTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIMiscTestCase.php',
'ArcanistRepositoryAPIStateTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php',
'ArcanistRepositoryLocalState' => 'repository/state/ArcanistRepositoryLocalState.php',
'ArcanistRepositoryMarkerQuery' => 'repository/marker/ArcanistRepositoryMarkerQuery.php',
'ArcanistRepositoryQuery' => 'repository/query/ArcanistRepositoryQuery.php',
'ArcanistRepositoryRef' => 'ref/ArcanistRepositoryRef.php',
'ArcanistRepositoryRemoteQuery' => 'repository/remote/ArcanistRepositoryRemoteQuery.php',
'ArcanistRepositoryURINormalizer' => 'repository/remote/ArcanistRepositoryURINormalizer.php',
'ArcanistRepositoryURINormalizerTestCase' => 'repository/remote/__tests__/ArcanistRepositoryURINormalizerTestCase.php',
'ArcanistReusedAsIteratorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistReusedAsIteratorXHPASTLinterRule.php',
'ArcanistReusedAsIteratorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistReusedAsIteratorXHPASTLinterRuleTestCase.php',
'ArcanistReusedIteratorReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistReusedIteratorReferenceXHPASTLinterRule.php',
'ArcanistReusedIteratorReferenceXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistReusedIteratorReferenceXHPASTLinterRuleTestCase.php',
'ArcanistReusedIteratorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistReusedIteratorXHPASTLinterRule.php',
'ArcanistReusedIteratorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistReusedIteratorXHPASTLinterRuleTestCase.php',
'ArcanistRevisionAuthorHardpointQuery' => 'ref/revision/ArcanistRevisionAuthorHardpointQuery.php',
'ArcanistRevisionBuildableHardpointQuery' => 'ref/revision/ArcanistRevisionBuildableHardpointQuery.php',
'ArcanistRevisionCommitMessageHardpointQuery' => 'ref/revision/ArcanistRevisionCommitMessageHardpointQuery.php',
'ArcanistRevisionParentRevisionsHardpointQuery' => 'ref/revision/ArcanistRevisionParentRevisionsHardpointQuery.php',
'ArcanistRevisionRef' => 'ref/revision/ArcanistRevisionRef.php',
'ArcanistRevisionRefSource' => 'ref/ArcanistRevisionRefSource.php',
'ArcanistRevisionSymbolRef' => 'ref/revision/ArcanistRevisionSymbolRef.php',
@ -411,7 +466,6 @@ phutil_register_library_map(array(
'ArcanistRuntime' => 'runtime/ArcanistRuntime.php',
'ArcanistRuntimeConfigurationSource' => 'config/source/ArcanistRuntimeConfigurationSource.php',
'ArcanistRuntimeHardpointQuery' => 'toolset/query/ArcanistRuntimeHardpointQuery.php',
'ArcanistScalarConfigOption' => 'config/option/ArcanistScalarConfigOption.php',
'ArcanistScalarHardpoint' => 'hardpoint/ArcanistScalarHardpoint.php',
'ArcanistScriptAndRegexLinter' => 'lint/linter/ArcanistScriptAndRegexLinter.php',
'ArcanistSelfClassReferenceXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSelfClassReferenceXHPASTLinterRule.php',
@ -424,10 +478,12 @@ phutil_register_library_map(array(
'ArcanistSetting' => 'configuration/ArcanistSetting.php',
'ArcanistSettings' => 'configuration/ArcanistSettings.php',
'ArcanistShellCompleteWorkflow' => 'toolset/workflow/ArcanistShellCompleteWorkflow.php',
'ArcanistSimpleCommitGraphQuery' => 'repository/graph/query/ArcanistSimpleCommitGraphQuery.php',
'ArcanistSimpleSymbolHardpointQuery' => 'ref/simple/ArcanistSimpleSymbolHardpointQuery.php',
'ArcanistSimpleSymbolRef' => 'ref/simple/ArcanistSimpleSymbolRef.php',
'ArcanistSimpleSymbolRefInspector' => 'ref/simple/ArcanistSimpleSymbolRefInspector.php',
'ArcanistSingleLintEngine' => 'lint/engine/ArcanistSingleLintEngine.php',
'ArcanistSingleSourceConfigOption' => 'config/option/ArcanistSingleSourceConfigOption.php',
'ArcanistSlownessXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistSlownessXHPASTLinterRule.php',
'ArcanistSlownessXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistSlownessXHPASTLinterRuleTestCase.php',
'ArcanistSpellingLinter' => 'lint/linter/ArcanistSpellingLinter.php',
@ -435,6 +491,7 @@ phutil_register_library_map(array(
'ArcanistStaticThisXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistStaticThisXHPASTLinterRule.php',
'ArcanistStaticThisXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistStaticThisXHPASTLinterRuleTestCase.php',
'ArcanistStringConfigOption' => 'config/option/ArcanistStringConfigOption.php',
'ArcanistStringListConfigOption' => 'config/option/ArcanistStringListConfigOption.php',
'ArcanistSubversionAPI' => 'repository/api/ArcanistSubversionAPI.php',
'ArcanistSubversionWorkingCopy' => 'workingcopy/ArcanistSubversionWorkingCopy.php',
'ArcanistSummaryLintRenderer' => 'lint/renderer/ArcanistSummaryLintRenderer.php',
@ -506,12 +563,15 @@ phutil_register_library_map(array(
'ArcanistWeldWorkflow' => 'workflow/ArcanistWeldWorkflow.php',
'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php',
'ArcanistWildConfigOption' => 'config/option/ArcanistWildConfigOption.php',
'ArcanistWorkEngine' => 'work/ArcanistWorkEngine.php',
'ArcanistWorkWorkflow' => 'workflow/ArcanistWorkWorkflow.php',
'ArcanistWorkflow' => 'workflow/ArcanistWorkflow.php',
'ArcanistWorkflowArgument' => 'toolset/ArcanistWorkflowArgument.php',
'ArcanistWorkflowEngine' => 'engine/ArcanistWorkflowEngine.php',
'ArcanistWorkflowGitHardpointQuery' => 'query/ArcanistWorkflowGitHardpointQuery.php',
'ArcanistWorkflowInformation' => 'toolset/ArcanistWorkflowInformation.php',
'ArcanistWorkflowMercurialHardpointQuery' => 'query/ArcanistWorkflowMercurialHardpointQuery.php',
'ArcanistWorkingCopy' => 'workingcopy/ArcanistWorkingCopy.php',
'ArcanistWorkingCopyCommitHardpointQuery' => 'query/ArcanistWorkingCopyCommitHardpointQuery.php',
'ArcanistWorkingCopyConfigurationSource' => 'config/source/ArcanistWorkingCopyConfigurationSource.php',
'ArcanistWorkingCopyIdentity' => 'workingcopyidentity/ArcanistWorkingCopyIdentity.php',
'ArcanistWorkingCopyPath' => 'workingcopy/ArcanistWorkingCopyPath.php',
@ -885,6 +945,8 @@ phutil_register_library_map(array(
'mpull' => 'utils/utils.php',
'msort' => 'utils/utils.php',
'msortv' => 'utils/utils.php',
'msortv_internal' => 'utils/utils.php',
'msortv_natural' => 'utils/utils.php',
'newv' => 'utils/utils.php',
'nonempty' => 'utils/utils.php',
'phlog' => 'error/phlog.php',
@ -939,6 +1001,7 @@ phutil_register_library_map(array(
'phutil_loggable_string' => 'utils/utils.php',
'phutil_microseconds_since' => 'utils/utils.php',
'phutil_parse_bytes' => 'utils/viewutils.php',
'phutil_partition' => 'utils/utils.php',
'phutil_passthru' => 'future/exec/execx.php',
'phutil_person' => 'internationalization/pht.php',
'phutil_register_library' => 'init/lib/core.php',
@ -1007,7 +1070,7 @@ phutil_register_library_map(array(
'ArcanistAliasFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistAliasFunctionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistAliasWorkflow' => 'ArcanistWorkflow',
'ArcanistAliasesConfigOption' => 'ArcanistListConfigOption',
'ArcanistAliasesConfigOption' => 'ArcanistMultiSourceConfigOption',
'ArcanistAmendWorkflow' => 'ArcanistArcWorkflow',
'ArcanistAnoidWorkflow' => 'ArcanistArcWorkflow',
'ArcanistArcConfigurationEngineExtension' => 'ArcanistConfigurationEngineExtension',
@ -1031,11 +1094,11 @@ phutil_register_library_map(array(
'ArcanistBlacklistedFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistBlacklistedFunctionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistBlindlyTrustHTTPEngineExtension' => 'PhutilHTTPEngineExtension',
'ArcanistBookmarkWorkflow' => 'ArcanistFeatureBaseWorkflow',
'ArcanistBookmarksWorkflow' => 'ArcanistMarkersWorkflow',
'ArcanistBoolConfigOption' => 'ArcanistSingleSourceConfigOption',
'ArcanistBraceFormattingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistBraceFormattingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistBranchRef' => 'ArcanistRef',
'ArcanistBranchWorkflow' => 'ArcanistFeatureBaseWorkflow',
'ArcanistBranchesWorkflow' => 'ArcanistMarkersWorkflow',
'ArcanistBrowseCommitHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistBrowseCommitURIHardpointQuery' => 'ArcanistBrowseURIHardpointQuery',
'ArcanistBrowseObjectNameURIHardpointQuery' => 'ArcanistBrowseURIHardpointQuery',
@ -1046,8 +1109,14 @@ phutil_register_library_map(array(
'ArcanistBrowseURIHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistBrowseURIRef' => 'ArcanistRef',
'ArcanistBrowseWorkflow' => 'ArcanistArcWorkflow',
'ArcanistBuildPlanRef' => 'Phobject',
'ArcanistBuildRef' => 'Phobject',
'ArcanistBuildBuildplanHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistBuildPlanRef' => 'ArcanistRef',
'ArcanistBuildPlanSymbolRef' => 'ArcanistSimpleSymbolRef',
'ArcanistBuildRef' => 'ArcanistRef',
'ArcanistBuildSymbolRef' => 'ArcanistSimpleSymbolRef',
'ArcanistBuildableBuildsHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistBuildableRef' => 'ArcanistRef',
'ArcanistBuildableSymbolRef' => 'ArcanistSimpleSymbolRef',
'ArcanistBundle' => 'Phobject',
'ArcanistBundleTestCase' => 'PhutilTestCase',
'ArcanistCSSLintLinter' => 'ArcanistExternalLinter',
@ -1082,6 +1151,16 @@ phutil_register_library_map(array(
'ArcanistCommentSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistCommentStyleXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistCommentStyleXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistCommitGraph' => 'Phobject',
'ArcanistCommitGraphPartition' => 'Phobject',
'ArcanistCommitGraphPartitionQuery' => 'Phobject',
'ArcanistCommitGraphQuery' => 'Phobject',
'ArcanistCommitGraphSet' => 'Phobject',
'ArcanistCommitGraphSetQuery' => 'Phobject',
'ArcanistCommitGraphSetTreeView' => 'Phobject',
'ArcanistCommitGraphSetView' => 'Phobject',
'ArcanistCommitGraphTestCase' => 'PhutilTestCase',
'ArcanistCommitNode' => 'Phobject',
'ArcanistCommitRef' => 'ArcanistRef',
'ArcanistCommitSymbolRef' => 'ArcanistSymbolRef',
'ArcanistCommitSymbolRefInspector' => 'ArcanistRefInspector',
@ -1092,7 +1171,8 @@ phutil_register_library_map(array(
'ArcanistComprehensiveLintEngine' => 'ArcanistLintEngine',
'ArcanistConcatenationOperatorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistConcatenationOperatorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistConduitCall' => 'Phobject',
'ArcanistConduitAuthenticationException' => 'Exception',
'ArcanistConduitCallFuture' => 'FutureProxy',
'ArcanistConduitEngine' => 'Phobject',
'ArcanistConduitException' => 'Exception',
'ArcanistConfigOption' => 'Phobject',
@ -1144,10 +1224,6 @@ phutil_register_library_map(array(
'ArcanistDifferentialDependencyGraph' => 'AbstractDirectedGraph',
'ArcanistDifferentialRevisionHash' => 'Phobject',
'ArcanistDifferentialRevisionStatus' => 'Phobject',
'ArcanistDisplayRef' => array(
'Phobject',
'ArcanistTerminalStringInterface',
),
'ArcanistDoubleQuoteXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistDoubleQuoteXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistDownloadWorkflow' => 'ArcanistArcWorkflow',
@ -1170,14 +1246,9 @@ phutil_register_library_map(array(
'ArcanistExternalLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistExtractUseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistExtractUseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistFeatureBaseWorkflow' => 'ArcanistArcWorkflow',
'ArcanistFeatureWorkflow' => 'ArcanistFeatureBaseWorkflow',
'ArcanistFileConfigurationSource' => 'ArcanistFilesystemConfigurationSource',
'ArcanistFileDataRef' => 'Phobject',
'ArcanistFileRef' => array(
'ArcanistRef',
'ArcanistDisplayRefInterface',
),
'ArcanistFileRef' => 'ArcanistRef',
'ArcanistFileSymbolRef' => 'ArcanistSimpleSymbolRef',
'ArcanistFileUploader' => 'Phobject',
'ArcanistFilenameLinter' => 'ArcanistLinter',
@ -1196,10 +1267,17 @@ phutil_register_library_map(array(
'ArcanistGeneratedLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistGetConfigWorkflow' => 'ArcanistWorkflow',
'ArcanistGitAPI' => 'ArcanistRepositoryAPI',
'ArcanistGitCommitGraphQuery' => 'ArcanistCommitGraphQuery',
'ArcanistGitCommitMessageHardpointQuery' => 'ArcanistWorkflowGitHardpointQuery',
'ArcanistGitCommitSymbolCommitHardpointQuery' => 'ArcanistWorkflowGitHardpointQuery',
'ArcanistGitLandEngine' => 'ArcanistLandEngine',
'ArcanistGitLocalState' => 'ArcanistRepositoryLocalState',
'ArcanistGitRawCommit' => 'Phobject',
'ArcanistGitRawCommitTestCase' => 'PhutilTestCase',
'ArcanistGitRepositoryMarkerQuery' => 'ArcanistRepositoryMarkerQuery',
'ArcanistGitRepositoryRemoteQuery' => 'ArcanistRepositoryRemoteQuery',
'ArcanistGitUpstreamPath' => 'Phobject',
'ArcanistGitWorkEngine' => 'ArcanistWorkEngine',
'ArcanistGitWorkingCopy' => 'ArcanistWorkingCopy',
'ArcanistGitWorkingCopyRevisionHardpointQuery' => 'ArcanistWorkflowGitHardpointQuery',
'ArcanistGlobalVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1208,6 +1286,10 @@ phutil_register_library_map(array(
'ArcanistGoLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistGoTestResultParser' => 'ArcanistTestResultParser',
'ArcanistGoTestResultParserTestCase' => 'PhutilTestCase',
'ArcanistGridCell' => 'Phobject',
'ArcanistGridColumn' => 'Phobject',
'ArcanistGridRow' => 'Phobject',
'ArcanistGridView' => 'Phobject',
'ArcanistHLintLinter' => 'ArcanistExternalLinter',
'ArcanistHLintLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistHardpoint' => 'Phobject',
@ -1267,8 +1349,12 @@ phutil_register_library_map(array(
'ArcanistKeywordCasingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistLambdaFuncFunctionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistLambdaFuncFunctionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistLandEngine' => 'Phobject',
'ArcanistLandWorkflow' => 'ArcanistWorkflow',
'ArcanistLandCommit' => 'Phobject',
'ArcanistLandCommitSet' => 'Phobject',
'ArcanistLandEngine' => 'ArcanistWorkflowEngine',
'ArcanistLandSymbol' => 'Phobject',
'ArcanistLandTarget' => 'Phobject',
'ArcanistLandWorkflow' => 'ArcanistArcWorkflow',
'ArcanistLanguageConstructParenthesesXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistLanguageConstructParenthesesXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistLesscLinter' => 'ArcanistExternalLinter',
@ -1289,19 +1375,30 @@ phutil_register_library_map(array(
'ArcanistLintersWorkflow' => 'ArcanistWorkflow',
'ArcanistListAssignmentXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistListAssignmentXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistListConfigOption' => 'ArcanistConfigOption',
'ArcanistListConfigOption' => 'ArcanistSingleSourceConfigOption',
'ArcanistListWorkflow' => 'ArcanistWorkflow',
'ArcanistLocalConfigurationSource' => 'ArcanistWorkingCopyConfigurationSource',
'ArcanistLogEngine' => 'Phobject',
'ArcanistLogMessage' => 'Phobject',
'ArcanistLogicalOperatorsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistLogicalOperatorsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistLookWorkflow' => 'ArcanistArcWorkflow',
'ArcanistLowercaseFunctionsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistMarkerRef' => 'ArcanistRef',
'ArcanistMarkersWorkflow' => 'ArcanistArcWorkflow',
'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI',
'ArcanistMercurialCommitGraphQuery' => 'ArcanistCommitGraphQuery',
'ArcanistMercurialCommitMessageHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery',
'ArcanistMercurialLandEngine' => 'ArcanistLandEngine',
'ArcanistMercurialLocalState' => 'ArcanistRepositoryLocalState',
'ArcanistMercurialParser' => 'Phobject',
'ArcanistMercurialParserTestCase' => 'PhutilTestCase',
'ArcanistMercurialRepositoryMarkerQuery' => 'ArcanistRepositoryMarkerQuery',
'ArcanistMercurialRepositoryRemoteQuery' => 'ArcanistRepositoryRemoteQuery',
'ArcanistMercurialWorkEngine' => 'ArcanistWorkEngine',
'ArcanistMercurialWorkingCopy' => 'ArcanistWorkingCopy',
'ArcanistMercurialWorkingCopyRevisionHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery',
'ArcanistMergeConflictLinter' => 'ArcanistLinter',
'ArcanistMergeConflictLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistMessageRevisionHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
@ -1309,6 +1406,7 @@ phutil_register_library_map(array(
'ArcanistMissingLinterException' => 'Exception',
'ArcanistModifierOrderingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistModifierOrderingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistMultiSourceConfigOption' => 'ArcanistConfigOption',
'ArcanistNamespaceFirstStatementXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistNamespaceFirstStatementXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistNamingConventionsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1348,10 +1446,7 @@ phutil_register_library_map(array(
'ArcanistParenthesesSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistParseStrUseXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistParseStrUseXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistPasteRef' => array(
'ArcanistRef',
'ArcanistDisplayRefInterface',
),
'ArcanistPasteRef' => 'ArcanistRef',
'ArcanistPasteSymbolRef' => 'ArcanistSimpleSymbolRef',
'ArcanistPasteWorkflow' => 'ArcanistArcWorkflow',
'ArcanistPatchWorkflow' => 'ArcanistWorkflow',
@ -1367,6 +1462,8 @@ phutil_register_library_map(array(
'ArcanistPlusOperatorOnStringsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistProjectConfigurationSource' => 'ArcanistWorkingCopyConfigurationSource',
'ArcanistPrompt' => 'Phobject',
'ArcanistPromptResponse' => 'Phobject',
'ArcanistPromptsConfigOption' => 'ArcanistMultiSourceConfigOption',
'ArcanistPromptsWorkflow' => 'ArcanistWorkflow',
'ArcanistPublicPropertyXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistPublicPropertyXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
@ -1380,21 +1477,34 @@ phutil_register_library_map(array(
'ArcanistRaggedClassTreeEdgeXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistRef' => 'ArcanistHardpointObject',
'ArcanistRefInspector' => 'Phobject',
'ArcanistRefView' => array(
'Phobject',
'ArcanistTerminalStringInterface',
),
'ArcanistRemoteRef' => 'ArcanistRef',
'ArcanistRemoteRefInspector' => 'ArcanistRefInspector',
'ArcanistRemoteRepositoryRefsHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistRepositoryAPI' => 'Phobject',
'ArcanistRepositoryAPIMiscTestCase' => 'PhutilTestCase',
'ArcanistRepositoryAPIStateTestCase' => 'PhutilTestCase',
'ArcanistRepositoryLocalState' => 'Phobject',
'ArcanistRepositoryMarkerQuery' => 'ArcanistRepositoryQuery',
'ArcanistRepositoryQuery' => 'Phobject',
'ArcanistRepositoryRef' => 'ArcanistRef',
'ArcanistRepositoryRemoteQuery' => 'ArcanistRepositoryQuery',
'ArcanistRepositoryURINormalizer' => 'Phobject',
'ArcanistRepositoryURINormalizerTestCase' => 'PhutilTestCase',
'ArcanistReusedAsIteratorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistReusedAsIteratorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistReusedIteratorReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistReusedIteratorReferenceXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistReusedIteratorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistReusedIteratorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistRevisionAuthorHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistRevisionBuildableHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistRevisionCommitMessageHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistRevisionRef' => array(
'ArcanistRef',
'ArcanistDisplayRefInterface',
),
'ArcanistRevisionParentRevisionsHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistRevisionRef' => 'ArcanistRef',
'ArcanistRevisionRefSource' => 'Phobject',
'ArcanistRevisionSymbolRef' => 'ArcanistSimpleSymbolRef',
'ArcanistRuboCopLinter' => 'ArcanistExternalLinter',
@ -1403,7 +1513,6 @@ phutil_register_library_map(array(
'ArcanistRubyLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistRuntimeConfigurationSource' => 'ArcanistDictionaryConfigurationSource',
'ArcanistRuntimeHardpointQuery' => 'ArcanistHardpointQuery',
'ArcanistScalarConfigOption' => 'ArcanistConfigOption',
'ArcanistScalarHardpoint' => 'ArcanistHardpoint',
'ArcanistScriptAndRegexLinter' => 'ArcanistLinter',
'ArcanistSelfClassReferenceXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1416,17 +1525,20 @@ phutil_register_library_map(array(
'ArcanistSetting' => 'Phobject',
'ArcanistSettings' => 'Phobject',
'ArcanistShellCompleteWorkflow' => 'ArcanistWorkflow',
'ArcanistSimpleCommitGraphQuery' => 'ArcanistCommitGraphQuery',
'ArcanistSimpleSymbolHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistSimpleSymbolRef' => 'ArcanistSymbolRef',
'ArcanistSimpleSymbolRefInspector' => 'ArcanistRefInspector',
'ArcanistSingleLintEngine' => 'ArcanistLintEngine',
'ArcanistSingleSourceConfigOption' => 'ArcanistConfigOption',
'ArcanistSlownessXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistSlownessXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistSpellingLinter' => 'ArcanistLinter',
'ArcanistSpellingLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistStaticThisXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistStaticThisXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistStringConfigOption' => 'ArcanistScalarConfigOption',
'ArcanistStringConfigOption' => 'ArcanistSingleSourceConfigOption',
'ArcanistStringListConfigOption' => 'ArcanistListConfigOption',
'ArcanistSubversionAPI' => 'ArcanistRepositoryAPI',
'ArcanistSubversionWorkingCopy' => 'ArcanistWorkingCopy',
'ArcanistSummaryLintRenderer' => 'ArcanistLintRenderer',
@ -1434,10 +1546,7 @@ phutil_register_library_map(array(
'ArcanistSymbolRef' => 'ArcanistRef',
'ArcanistSyntaxErrorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistSystemConfigurationSource' => 'ArcanistFilesystemConfigurationSource',
'ArcanistTaskRef' => array(
'ArcanistRef',
'ArcanistDisplayRefInterface',
),
'ArcanistTaskRef' => 'ArcanistRef',
'ArcanistTaskSymbolRef' => 'ArcanistSimpleSymbolRef',
'ArcanistTasksWorkflow' => 'ArcanistWorkflow',
'ArcanistTautologicalExpressionXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1487,10 +1596,7 @@ phutil_register_library_map(array(
'ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistUserAbortException' => 'ArcanistUsageException',
'ArcanistUserConfigurationSource' => 'ArcanistFilesystemConfigurationSource',
'ArcanistUserRef' => array(
'ArcanistRef',
'ArcanistDisplayRefInterface',
),
'ArcanistUserRef' => 'ArcanistRef',
'ArcanistUserSymbolHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistUserSymbolRef' => 'ArcanistSymbolRef',
'ArcanistUserSymbolRefInspector' => 'ArcanistRefInspector',
@ -1503,12 +1609,15 @@ phutil_register_library_map(array(
'ArcanistWeldWorkflow' => 'ArcanistArcWorkflow',
'ArcanistWhichWorkflow' => 'ArcanistWorkflow',
'ArcanistWildConfigOption' => 'ArcanistConfigOption',
'ArcanistWorkEngine' => 'ArcanistWorkflowEngine',
'ArcanistWorkWorkflow' => 'ArcanistArcWorkflow',
'ArcanistWorkflow' => 'Phobject',
'ArcanistWorkflowArgument' => 'Phobject',
'ArcanistWorkflowEngine' => 'Phobject',
'ArcanistWorkflowGitHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistWorkflowInformation' => 'Phobject',
'ArcanistWorkflowMercurialHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistWorkingCopy' => 'Phobject',
'ArcanistWorkingCopyCommitHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistWorkingCopyConfigurationSource' => 'ArcanistFilesystemConfigurationSource',
'ArcanistWorkingCopyIdentity' => 'Phobject',
'ArcanistWorkingCopyPath' => 'Phobject',

View file

@ -1,153 +0,0 @@
<?php
final class ArcanistConduitCall
extends Phobject {
private $key;
private $engine;
private $method;
private $parameters;
private $future;
public function setKey($key) {
$this->key = $key;
return $this;
}
public function getKey() {
return $this->key;
}
public function setEngine(ArcanistConduitEngine $engine) {
$this->engine = $engine;
return $this;
}
public function getEngine() {
return $this->engine;
}
public function setMethod($method) {
$this->method = $method;
return $this;
}
public function getMethod() {
return $this->method;
}
public function setParameters(array $parameters) {
$this->parameters = $parameters;
return $this;
}
public function getParameters() {
return $this->parameters;
}
private function newFuture() {
if ($this->future) {
throw new Exception(
pht(
'Call has previously generated a future. Create a '.
'new call object for each API method invocation.'));
}
$method = $this->getMethod();
$parameters = $this->getParameters();
$future = $this->getEngine()->newFuture($this);
$this->future = $future;
return $this->future;
}
public function resolve() {
if (!$this->future) {
$this->newFuture();
}
return $this->resolveFuture();
}
private function resolveFuture() {
$future = $this->future;
try {
$result = $future->resolve();
} catch (ConduitClientException $ex) {
switch ($ex->getErrorCode()) {
case 'ERR-INVALID-SESSION':
if (!$this->getEngine()->getConduitToken()) {
$this->raiseLoginRequired();
}
break;
case 'ERR-INVALID-AUTH':
$this->raiseInvalidAuth();
break;
}
throw $ex;
}
return $result;
}
private function raiseLoginRequired() {
$conduit_uri = $this->getEngine()->getConduitURI();
$conduit_uri = new PhutilURI($conduit_uri);
$conduit_uri->setPath('/');
$conduit_domain = $conduit_uri->getDomain();
$block = id(new PhutilConsoleBlock())
->addParagraph(
tsprintf(
'**<bg:red> %s </bg>**',
pht('LOGIN REQUIRED')))
->addParagraph(
pht(
'You are trying to connect to a server ("%s") that you do not '.
'have any stored credentials for, but the command you are '.
'running requires authentication.',
$conduit_domain))
->addParagraph(
pht(
'To login and save credentials for this server, run this '.
'command:'))
->addParagraph(
tsprintf(
" $ arc install-certificate %s\n",
$conduit_uri));
throw new ArcanistUsageException($block->drawConsoleString());
}
private function raiseInvalidAuth() {
$conduit_uri = $this->getEngine()->getConduitURI();
$conduit_uri = new PhutilURI($conduit_uri);
$conduit_uri->setPath('/');
$conduit_domain = $conduit_uri->getDomain();
$block = id(new PhutilConsoleBlock())
->addParagraph(
tsprintf(
'**<bg:red> %s </bg>**',
pht('INVALID CREDENTIALS')))
->addParagraph(
pht(
'Your stored credentials for this server ("%s") are not valid.',
$conduit_domain))
->addParagraph(
pht(
'To login and save valid credentials for this server, run this '.
'command:'))
->addParagraph(
tsprintf(
" $ arc install-certificate %s\n",
$conduit_uri));
throw new ArcanistUsageException($block->drawConsoleString());
}
}

View file

@ -0,0 +1,116 @@
<?php
final class ArcanistConduitCallFuture
extends FutureProxy {
private $engine;
public function setEngine(ArcanistConduitEngine $engine) {
$this->engine = $engine;
return $this;
}
public function getEngine() {
return $this->engine;
}
private function raiseLoginRequired() {
$conduit_domain = $this->getConduitDomain();
$message = array(
tsprintf(
"\n\n%W\n\n",
pht(
'You are trying to connect to a server ("%s") that you do not '.
'have any stored credentials for, but the command you are '.
'running requires authentication.',
$conduit_domain)),
tsprintf(
"%W\n\n",
pht(
'To log in and save credentials for this server, run this '.
'command:')),
tsprintf(
'%>',
$this->getInstallCommand()),
);
$this->raiseException(
pht('Conduit API login required.'),
pht('LOGIN REQUIRED'),
$message);
}
private function raiseInvalidAuth() {
$conduit_domain = $this->getConduitDomain();
$message = array(
tsprintf(
"\n\n%W\n\n",
pht(
'Your stored credentials for the server you are trying to connect '.
'to ("%s") are not valid.',
$conduit_domain)),
tsprintf(
"%W\n\n",
pht(
'To log in and save valid credentials for this server, run this '.
'command:')),
tsprintf(
'%>',
$this->getInstallCommand()),
);
$this->raiseException(
pht('Invalid Conduit API credentials.'),
pht('INVALID CREDENTIALS'),
$message);
}
protected function didReceiveResult($result) {
return $result;
}
protected function didReceiveException($exception) {
switch ($exception->getErrorCode()) {
case 'ERR-INVALID-SESSION':
if (!$this->getEngine()->getConduitToken()) {
$this->raiseLoginRequired();
}
break;
case 'ERR-INVALID-AUTH':
$this->raiseInvalidAuth();
break;
}
throw $exception;
}
private function getInstallCommand() {
$conduit_uri = $this->getConduitURI();
return csprintf(
'arc install-certificate %s',
$conduit_uri);
}
private function getConduitURI() {
$conduit_uri = $this->getEngine()->getConduitURI();
$conduit_uri = new PhutilURI($conduit_uri);
$conduit_uri->setPath('/');
return $conduit_uri;
}
private function getConduitDomain() {
$conduit_uri = $this->getConduitURI();
return $conduit_uri->getDomain();
}
private function raiseException($summary, $title, $body) {
throw id(new ArcanistConduitAuthenticationException($summary))
->setTitle($title)
->setBody($body);
}
}

View file

@ -39,28 +39,17 @@ final class ArcanistConduitEngine
return $this->conduitTimeout;
}
public function newCall($method, array $parameters) {
public function newFuture($method, array $parameters) {
if ($this->conduitURI == null && $this->client === null) {
$this->raiseURIException();
}
return id(new ArcanistConduitCall())
->setEngine($this)
->setMethod($method)
->setParameters($parameters);
}
public function resolveCall($method, array $parameters) {
return $this->newCall($method, $parameters)->resolve();
}
public function newFuture(ArcanistConduitCall $call) {
$method = $call->getMethod();
$parameters = $call->getParameters();
$future = $this->getClient()->callMethod($method, $parameters);
return $future;
$call_future = id(new ArcanistConduitCallFuture($future))
->setEngine($this);
return $call_future;
}
private function getClient() {

View file

@ -5,7 +5,6 @@ final class ConduitFuture extends FutureProxy {
private $client;
private $engine;
private $conduitMethod;
private $profilerCallID;
public function setClient(ConduitClient $client, $method) {
$this->client = $client;
@ -13,29 +12,19 @@ final class ConduitFuture extends FutureProxy {
return $this;
}
public function isReady() {
if ($this->profilerCallID === null) {
$profiler = PhutilServiceProfiler::getInstance();
protected function getServiceProfilerStartParameters() {
return array(
'type' => 'conduit',
'method' => $this->conduitMethod,
'size' => $this->getProxiedFuture()->getHTTPRequestByteLength(),
);
}
$this->profilerCallID = $profiler->beginServiceCall(
array(
'type' => 'conduit',
'method' => $this->conduitMethod,
'size' => $this->getProxiedFuture()->getHTTPRequestByteLength(),
));
}
return parent::isReady();
protected function getServiceProfilerResultParameters() {
return array();
}
protected function didReceiveResult($result) {
if ($this->profilerCallID !== null) {
$profiler = PhutilServiceProfiler::getInstance();
$profiler->endServiceCall(
$this->profilerCallID,
array());
}
list($status, $body, $headers) = $result;
if ($status->isError()) {
throw $status;

View file

@ -104,8 +104,7 @@ final class ConduitSearchFuture
$parameters['after'] = (string)$this->cursor;
}
$conduit_call = $engine->newCall($method, $parameters);
$conduit_future = $engine->newFuture($conduit_call);
$conduit_future = $engine->newFuture($method, $parameters);
return $conduit_future;
}

View file

@ -35,4 +35,11 @@ abstract class FutureAgent
return $sockets;
}
protected function getServiceProfilerStartParameters() {
// At least today, the agent construct doesn't add anything interesting
// to the trace and the underlying futures always show up in the trace
// themselves.
return null;
}
}

View file

@ -6,6 +6,7 @@ final class ArcanistArcConfigurationEngineExtension
const EXTENSIONKEY = 'arc';
const KEY_ALIASES = 'aliases';
const KEY_PROMPTS = 'prompts';
public function newConfigurationOptions() {
// TOOLSETS: Restore "load", and maybe this other stuff.
@ -21,46 +22,6 @@ final class ArcanistArcConfigurationEngineExtension
'default' => array(),
'example' => '["/var/arc/customlib/src"]',
),
'arc.feature.start.default' => array(
'type' => 'string',
'help' => pht(
'The name of the default branch to create the new feature branch '.
'off of.'),
'example' => '"develop"',
),
'arc.land.onto.default' => array(
'type' => 'string',
'help' => pht(
'The name of the default branch to land changes onto when '.
'`%s` is run.',
'arc land'),
'example' => '"develop"',
),
'arc.autostash' => array(
'type' => 'bool',
'help' => pht(
'Whether %s should permit the automatic stashing of changes in the '.
'working directory when requiring a clean working copy. This option '.
'should only be used when users understand how to restore their '.
'working directory from the local stash if an Arcanist operation '.
'causes an unrecoverable error.',
'arc'),
'default' => false,
'example' => 'false',
),
'history.immutable' => array(
'type' => 'bool',
'legacy' => 'immutable_history',
'help' => pht(
'If true, %s will never change repository history (e.g., through '.
'amending or rebasing). Defaults to true in Mercurial and false in '.
'Git. This setting has no effect in Subversion.',
'arc'),
'example' => 'false',
),
'editor' => array(
'type' => 'string',
'help' => pht(
@ -111,9 +72,9 @@ final class ArcanistArcConfigurationEngineExtension
->setHelp(
pht(
'Associate the working copy with a specific Phabricator '.
'repository. Normally, `arc` can figure this association out on '.
'its own, but if your setup is unusual you can use this option '.
'to tell it what the desired value is.'))
'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.'))
->setExamples(
array(
'libexample',
@ -145,6 +106,58 @@ final class ArcanistArcConfigurationEngineExtension
pht(
'Configured command aliases. Use the "alias" workflow to define '.
'aliases.')),
id(new ArcanistPromptsConfigOption())
->setKey(self::KEY_PROMPTS)
->setDefaultValue(array())
->setSummary(pht('List of prompt responses.'))
->setHelp(
pht(
'Configured prompt aliases. Use the "prompts" workflow to '.
'show prompts and responses.')),
id(new ArcanistStringListConfigOption())
->setKey('arc.land.onto')
->setDefaultValue(array())
->setSummary(pht('Default list of "onto" refs for "arc land".'))
->setHelp(
pht(
'Specifies the default behavior when "arc land" is run with '.
'no "--onto" flag.'))
->setExamples(
array(
'["master"]',
)),
id(new ArcanistStringListConfigOption())
->setKey('pager')
->setDefaultValue(array())
->setSummary(pht('Default pager command.'))
->setHelp(
pht(
'Specify the pager command to use when displaying '.
'documentation.'))
->setExamples(
array(
'["less", "-R", "--"]',
)),
id(new ArcanistStringConfigOption())
->setKey('arc.land.onto-remote')
->setSummary(pht('Default list of "onto" remote for "arc land".'))
->setHelp(
pht(
'Specifies the default behavior when "arc land" is run with '.
'no "--onto-remote" flag.'))
->setExamples(
array(
'origin',
)),
id(new ArcanistStringConfigOption())
->setKey('arc.land.strategy')
->setSummary(
pht(
'Configure a default merge strategy for "arc land".'))
->setHelp(
pht(
'Specifies the default behavior when "arc land" is run with '.
'no "--strategy" flag.')),
);
}

View file

@ -1,7 +1,7 @@
<?php
final class ArcanistAliasesConfigOption
extends ArcanistListConfigOption {
extends ArcanistMultiSourceConfigOption {
public function getType() {
return 'list<alias>';

View file

@ -0,0 +1,35 @@
<?php
final class ArcanistBoolConfigOption
extends ArcanistSingleSourceConfigOption {
public function getType() {
return 'bool';
}
public function getStorageValueFromStringValue($value) {
if ($value === 'true') {
return true;
}
if ($value === 'false') {
return false;
}
throw new PhutilArgumentUsageException(
pht('Specify either "true" or "false".'));
}
public function getDisplayValueFromValue($value) {
if ($value) {
return 'true';
} else {
return 'false';
}
}
public function getStorageValueFromValue($value) {
return $value;
}
}

View file

@ -1,32 +1,58 @@
<?php
abstract class ArcanistListConfigOption
extends ArcanistConfigOption {
extends ArcanistSingleSourceConfigOption {
public function getValueFromStorageValueList(array $list) {
assert_instances_of($list, 'ArcanistConfigurationSourceValue');
$result_list = array();
foreach ($list as $source_value) {
$source = $source_value->getConfigurationSource();
$storage_value = $this->getStorageValueFromSourceValue($source_value);
$items = $this->getValueFromStorageValue($storage_value);
foreach ($items as $item) {
$result_list[] = new ArcanistConfigurationSourceValue(
$source,
$item);
}
final public function getStorageValueFromStringValue($value) {
try {
$json_value = phutil_json_decode($value);
} catch (PhutilJSONParserException $ex) {
throw new PhutilArgumentUsageException(
pht(
'Value "%s" is not valid, specify a JSON list: %s',
$value,
$ex->getMessage()));
}
$result_list = $this->didReadStorageValueList($result_list);
if (!is_array($json_value) || !phutil_is_natural_list($json_value)) {
throw new PhutilArgumentUsageException(
pht(
'Value "%s" is not valid: expected a list, got "%s".',
$value,
phutil_describe_type($json_value)));
}
return $result_list;
foreach ($json_value as $idx => $item) {
$this->validateListItem($idx, $item);
}
return $json_value;
}
protected function didReadStorageValueList(array $list) {
assert_instances_of($list, 'ArcanistConfigurationSourceValue');
return mpull($list, 'getValue');
final public function getValueFromStorageValue($value) {
if (!is_array($value)) {
throw new Exception(pht('Expected a list!'));
}
if (!phutil_is_natural_list($value)) {
throw new Exception(pht('Expected a natural list!'));
}
foreach ($value as $idx => $item) {
$this->validateListItem($idx, $item);
}
return $value;
}
public function getDisplayValueFromValue($value) {
return json_encode($value);
}
public function getStorageValueFromValue($value) {
return $value;
}
abstract protected function validateListItem($idx, $item);
}

View file

@ -0,0 +1,32 @@
<?php
abstract class ArcanistMultiSourceConfigOption
extends ArcanistConfigOption {
public function getValueFromStorageValueList(array $list) {
assert_instances_of($list, 'ArcanistConfigurationSourceValue');
$result_list = array();
foreach ($list as $source_value) {
$source = $source_value->getConfigurationSource();
$storage_value = $this->getStorageValueFromSourceValue($source_value);
$items = $this->getValueFromStorageValue($storage_value);
foreach ($items as $item) {
$result_list[] = new ArcanistConfigurationSourceValue(
$source,
$item);
}
}
$result_list = $this->didReadStorageValueList($result_list);
return $result_list;
}
protected function didReadStorageValueList(array $list) {
assert_instances_of($list, 'ArcanistConfigurationSourceValue');
return mpull($list, 'getValue');
}
}

View file

@ -0,0 +1,51 @@
<?php
final class ArcanistPromptsConfigOption
extends ArcanistMultiSourceConfigOption {
public function getType() {
return 'map<string, prompt>';
}
public function getValueFromStorageValue($value) {
if (!is_array($value)) {
throw new Exception(pht('Expected a list!'));
}
if (!phutil_is_natural_list($value)) {
throw new Exception(pht('Expected a natural list!'));
}
$responses = array();
foreach ($value as $spec) {
$responses[] = ArcanistPromptResponse::newFromConfig($spec);
}
return $responses;
}
protected function didReadStorageValueList(array $list) {
assert_instances_of($list, 'ArcanistConfigurationSourceValue');
$results = array();
foreach ($list as $spec) {
$source = $spec->getConfigurationSource();
$value = $spec->getValue();
$value->setConfigurationSource($source);
$results[] = $value;
}
return $results;
}
public function getDisplayValueFromValue($value) {
return pht('Use the "prompts" workflow to review prompt responses.');
}
public function getStorageValueFromValue($value) {
return mpull($value, 'getStorageDictionary');
}
}

View file

@ -1,6 +1,6 @@
<?php
abstract class ArcanistScalarConfigOption
abstract class ArcanistSingleSourceConfigOption
extends ArcanistConfigOption {
public function getValueFromStorageValueList(array $list) {

View file

@ -1,7 +1,7 @@
<?php
final class ArcanistStringConfigOption
extends ArcanistScalarConfigOption {
extends ArcanistSingleSourceConfigOption {
public function getType() {
return 'string';

View file

@ -0,0 +1,20 @@
<?php
final class ArcanistStringListConfigOption
extends ArcanistListConfigOption {
public function getType() {
return 'list<string>';
}
protected function validateListItem($idx, $item) {
if (!is_string($item)) {
throw new PhutilArgumentUsageException(
pht(
'Expected a string (at index "%s"), found "%s".',
$idx,
phutil_describe_type($item)));
}
}
}

View file

@ -4,6 +4,7 @@ abstract class ArcanistConfigurationSource
extends Phobject {
const SCOPE_USER = 'user';
const SCOPE_WORKING_COPY = 'working-copy';
abstract public function getSourceDisplayName();
abstract public function getAllKeys();

View file

@ -7,4 +7,12 @@ final class ArcanistLocalConfigurationSource
return pht('Local Config File');
}
public function isWritableConfigurationSource() {
return true;
}
public function getConfigurationSourceScope() {
return ArcanistConfigurationSource::SCOPE_WORKING_COPY;
}
}

View file

@ -26,9 +26,6 @@ class ArcanistConfiguration extends Phobject {
// Special-case "arc --help" to behave like "arc help" instead of telling
// you to type "arc help" without being helpful.
$command = 'help';
} else if ($command == '--version') {
// Special-case "arc --version" to behave like "arc version".
$command = 'version';
}
$workflow = idx($this->buildAllWorkflows(), $command);

View file

@ -65,13 +65,6 @@ final class ArcanistSettings extends Phobject {
'engine is specified by the current project.'),
'example' => '"ExampleUnitTestEngine"',
),
'arc.feature.start.default' => array(
'type' => 'string',
'help' => pht(
'The name of the default branch to create the new feature branch '.
'off of.'),
'example' => '"develop"',
),
'arc.land.onto.default' => array(
'type' => 'string',
'help' => pht(

View file

@ -0,0 +1,56 @@
<?php
final class ArcanistGridCell
extends Phobject {
private $key;
private $content;
private $contentWidth;
private $contentHeight;
public function setKey($key) {
$this->key = $key;
return $this;
}
public function getKey() {
return $this->key;
}
public function setContent($content) {
$this->content = $content;
return $this;
}
public function getContent() {
return $this->content;
}
public function getContentDisplayWidth() {
$lines = $this->getContentDisplayLines();
$width = 0;
foreach ($lines as $line) {
$width = max($width, phutil_utf8_console_strlen($line));
}
return $width;
}
public function getContentDisplayLines() {
$content = $this->getContent();
$content = tsprintf('%B', $content);
$content = phutil_string_cast($content);
$lines = phutil_split_lines($content, false);
$result = array();
foreach ($lines as $line) {
$result[] = tsprintf('%R', $line);
}
return $result;
}
}

View file

@ -0,0 +1,51 @@
<?php
final class ArcanistGridColumn
extends Phobject {
private $key;
private $alignment = self::ALIGNMENT_LEFT;
private $displayWidth;
private $minimumWidth;
const ALIGNMENT_LEFT = 'align.left';
const ALIGNMENT_CENTER = 'align.center';
const ALIGNMENT_RIGHT = 'align.right';
public function setKey($key) {
$this->key = $key;
return $this;
}
public function getKey() {
return $this->key;
}
public function setAlignment($alignment) {
$this->alignment = $alignment;
return $this;
}
public function getAlignment() {
return $this->alignment;
}
public function setDisplayWidth($display_width) {
$this->displayWidth = $display_width;
return $this;
}
public function getDisplayWidth() {
return $this->displayWidth;
}
public function setMinimumWidth($minimum_width) {
$this->minimumWidth = $minimum_width;
return $this;
}
public function getMinimumWidth() {
return $this->minimumWidth;
}
}

View file

@ -0,0 +1,40 @@
<?php
final class ArcanistGridRow
extends Phobject {
private $cells;
public function setCells(array $cells) {
$cells = id(new PhutilArrayCheck())
->setInstancesOf('ArcanistGridCell')
->setUniqueMethod('getKey')
->setContext($this, 'setCells')
->checkValue($cells);
$this->cells = $cells;
return $this;
}
public function getCells() {
return $this->cells;
}
public function hasCell($key) {
return isset($this->cells[$key]);
}
public function getCell($key) {
if (!isset($this->cells[$key])) {
throw new Exception(
pht(
'Row has no cell "%s".\n',
$key));
}
return $this->cells[$key];
}
}

View file

@ -0,0 +1,295 @@
<?php
final class ArcanistGridView
extends Phobject {
private $rows = array();
private $columns = array();
private $displayWidths = array();
public function setColumns(array $columns) {
assert_instances_of($columns, 'ArcanistGridColumn');
$this->columns = mpull($columns, null, 'getKey');
return $this;
}
public function getColumns() {
return $this->columns;
}
public function newColumn($key) {
$column = id(new ArcanistGridColumn())
->setKey($key);
$this->columns[$key] = $column;
return $column;
}
public function newRow(array $cells) {
assert_instances_of($cells, 'ArcanistGridCell');
$row = id(new ArcanistGridRow())
->setCells($cells);
$this->rows[] = $row;
return $row;
}
public function drawGrid() {
$columns = $this->getColumns();
if (!$columns) {
throw new Exception(
pht(
'Can not draw a grid with no columns!'));
}
$rows = array();
foreach ($this->rows as $row) {
$rows[] = $this->drawRow($row);
}
$rows = phutil_glue($rows, tsprintf("\n"));
return tsprintf("%s\n", $rows);
}
private function getDisplayWidth($display_key) {
if (!isset($this->displayWidths[$display_key])) {
$flexible_columns = array();
$columns = $this->getColumns();
foreach ($columns as $key => $column) {
$width = $column->getDisplayWidth();
if ($width === null) {
$width = 1;
foreach ($this->getRows() as $row) {
if (!$row->hasCell($key)) {
continue;
}
$cell = $row->getCell($key);
$width = max($width, $cell->getContentDisplayWidth());
}
}
if ($column->getMinimumWidth() !== null) {
$flexible_columns[] = $key;
}
$this->displayWidths[$key] = $width;
}
$available_width = phutil_console_get_terminal_width();
// Adjust the available width to account for cell spacing.
$available_width -= (2 * (count($columns) - 1));
while (true) {
$total_width = array_sum($this->displayWidths);
if ($total_width <= $available_width) {
break;
}
if (!$flexible_columns) {
break;
}
// NOTE: This is very unsophisticated, and just shortcuts us to a
// reasonable result when only one column is flexible.
foreach ($flexible_columns as $flexible_key) {
$column = $columns[$flexible_key];
$need_width = ($total_width - $available_width);
$old_width = $this->displayWidths[$flexible_key];
$new_width = ($old_width - $need_width);
$new_width = max($new_width, $column->getMinimumWidth());
$this->displayWidths[$flexible_key] = $new_width;
$flexible_columns = array();
break;
}
}
}
return $this->displayWidths[$display_key];
}
public function getColumn($key) {
if (!isset($this->columns[$key])) {
throw new Exception(
pht(
'Grid has no column "%s".',
$key));
}
return $this->columns[$key];
}
public function getRows() {
return $this->rows;
}
private function drawRow(ArcanistGridRow $row) {
$columns = $this->getColumns();
$cells = $row->getCells();
$out = array();
$widths = array();
foreach ($columns as $column_key => $column) {
$display_width = $this->getDisplayWidth($column_key);
$cell = idx($cells, $column_key);
if ($cell) {
$content = $cell->getContentDisplayLines();
} else {
$content = array('');
}
foreach ($content as $line_key => $line) {
$line_width = phutil_utf8_console_strlen($line);
if ($line_width === $display_width) {
continue;
}
if ($line_width < $display_width) {
$line = $this->padContentLineToWidth(
$line,
$line_width,
$display_width,
$column->getAlignment());
} else if ($line_width > $display_width) {
$line = $this->truncateContentLineToWidth(
$line,
$line_width,
$display_width,
$column->getAlignment());
}
$content[$line_key] = $line;
}
$out[] = $content;
$widths[] = $display_width;
}
return $this->drawRowLayout($out, $widths);
}
private function drawRowLayout(array $raw_cells, array $display_widths) {
$line_count = 0;
foreach ($raw_cells as $key => $cells) {
$raw_cells[$key] = array_values($cells);
$line_count = max($line_count, count($cells));
}
$line_head = '';
$cell_separator = ' ';
$line_tail = '';
$out = array();
$cell_count = count($raw_cells);
for ($ii = 0; $ii < $line_count; $ii++) {
$line = array();
for ($jj = 0; $jj < $cell_count; $jj++) {
if (isset($raw_cells[$jj][$ii])) {
$raw_line = $raw_cells[$jj][$ii];
} else {
$display_width = $display_widths[$jj];
$raw_line = str_repeat(' ', $display_width);
}
$line[] = $raw_line;
}
$line = array(
$line_head,
phutil_glue($line, $cell_separator),
$line_tail,
);
$out[] = $line;
}
$out = phutil_glue($out, tsprintf("\n"));
return $out;
}
private function padContentLineToWidth(
$line,
$src_width,
$dst_width,
$alignment) {
$delta = ($dst_width - $src_width);
switch ($alignment) {
case ArcanistGridColumn::ALIGNMENT_LEFT:
$head = null;
$tail = str_repeat(' ', $delta);
break;
case ArcanistGridColumn::ALIGNMENT_CENTER:
$head_delta = (int)floor($delta / 2);
$tail_delta = (int)ceil($delta / 2);
if ($head_delta) {
$head = str_repeat(' ', $head_delta);
} else {
$head = null;
}
if ($tail_delta) {
$tail = str_repeat(' ', $tail_delta);
} else {
$tail = null;
}
break;
case ArcanistGridColumn::ALIGNMENT_RIGHT:
$head = str_repeat(' ', $delta);
$tail = null;
break;
default:
throw new Exception(
pht(
'Unknown column alignment "%s".',
$alignment));
}
$result = array();
if ($head !== null) {
$result[] = $head;
}
$result[] = $line;
if ($tail !== null) {
$result[] = $tail;
}
return $result;
}
private function truncateContentLineToWidth(
$line,
$src_width,
$dst_width,
$alignment) {
$line = phutil_string_cast($line);
return id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs($dst_width)
->truncateString($line);
}
}

View file

@ -57,9 +57,9 @@ final class PhutilConsoleTable extends PhutilConsoleView {
/* -( Data )--------------------------------------------------------------- */
public function addColumn($key, array $column) {
public function addColumn($key, array $column = array()) {
PhutilTypeSpec::checkMap($column, array(
'title' => 'string',
'title' => 'optional string',
'align' => 'optional string',
));
$this->columns[$key] = $column;
@ -85,6 +85,16 @@ final class PhutilConsoleTable extends PhutilConsoleView {
return $this;
}
public function drawRows(array $rows) {
$this->data = array();
$this->widths = array();
foreach ($rows as $row) {
$this->addRow($row);
}
return $this->draw();
}
/* -( Drawing )------------------------------------------------------------ */

View file

@ -0,0 +1,48 @@
<?php
abstract class ArcanistWorkflowEngine
extends Phobject {
private $workflow;
private $viewer;
private $logEngine;
private $repositoryAPI;
final public function setViewer($viewer) {
$this->viewer = $viewer;
return $this;
}
final public function getViewer() {
return $this->viewer;
}
final public function setWorkflow(ArcanistWorkflow $workflow) {
$this->workflow = $workflow;
return $this;
}
final public function getWorkflow() {
return $this->workflow;
}
final public function setRepositoryAPI(
ArcanistRepositoryAPI $repository_api) {
$this->repositoryAPI = $repository_api;
return $this;
}
final public function getRepositoryAPI() {
return $this->repositoryAPI;
}
final public function setLogEngine(ArcanistLogEngine $log_engine) {
$this->logEngine = $log_engine;
return $this;
}
final public function getLogEngine() {
return $this->logEngine;
}
}

View file

@ -0,0 +1,27 @@
<?php
final class ArcanistConduitAuthenticationException
extends Exception {
private $title;
private $body;
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
public function setBody($body) {
$this->body = $body;
return $this;
}
public function getBody() {
return $this->body;
}
}

View file

@ -63,7 +63,7 @@ abstract class Future extends Phobject {
$this->hasStarted = true;
$this->startServiceProfiler();
$this->isReady();
$this->updateFuture();
}
final public function updateFuture() {
@ -115,6 +115,10 @@ abstract class Future extends Phobject {
$params = $this->getServiceProfilerStartParameters();
if ($params === null) {
return;
}
$profiler = PhutilServiceProfiler::getInstance();
$call_id = $profiler->beginServiceCall($params);

View file

@ -27,27 +27,29 @@ abstract class FutureProxy extends Future {
}
public function isReady() {
if ($this->hasResult()) {
if ($this->hasResult() || $this->hasException()) {
return true;
}
$proxied = $this->getProxiedFuture();
$proxied->updateFuture();
$is_ready = $proxied->isReady();
if ($proxied->hasResult() || $proxied->hasException()) {
try {
$result = $proxied->resolve();
$result = $this->didReceiveResult($result);
} catch (Exception $ex) {
$result = $this->didReceiveException($ex);
} catch (Throwable $ex) {
$result = $this->didReceiveException($ex);
}
if ($proxied->hasResult()) {
$result = $proxied->getResult();
$result = $this->didReceiveResult($result);
$this->setResult($result);
return true;
}
return $is_ready;
}
public function resolve() {
$this->getProxiedFuture()->resolve();
$this->isReady();
return $this->getResult();
return false;
}
public function getReadSockets() {
@ -73,4 +75,8 @@ abstract class FutureProxy extends Future {
abstract protected function didReceiveResult($result);
protected function didReceiveException($exception) {
throw $exception;
}
}

View file

@ -20,6 +20,12 @@
*/
final class PhutilExecPassthru extends PhutilExecutableFuture {
private $stdinData;
public function write($data) {
$this->stdinData = $data;
return $this;
}
/* -( Executing Passthru Commands )---------------------------------------- */
@ -34,7 +40,15 @@ final class PhutilExecPassthru extends PhutilExecutableFuture {
public function execute() {
$command = $this->getCommand();
$spec = array(STDIN, STDOUT, STDERR);
$is_write = ($this->stdinData !== null);
if ($is_write) {
$stdin_spec = array('pipe', 'r');
} else {
$stdin_spec = STDIN;
}
$spec = array($stdin_spec, STDOUT, STDERR);
$pipes = array();
$unmasked_command = $command->getUnmaskedString();
@ -81,6 +95,11 @@ final class PhutilExecPassthru extends PhutilExecutableFuture {
$errors));
}
} else {
if ($is_write) {
fwrite($pipes[0], $this->stdinData);
fclose($pipes[0]);
}
$err = proc_close($proc);
}

View file

@ -192,7 +192,8 @@ final class ArcanistHardpointEngine
$wait_futures = $this->waitFutures;
if ($wait_futures) {
if (!$this->futureIterator) {
$iterator = new FutureIterator(array());
$iterator = id(new FutureIterator(array()))
->limit(32);
foreach ($wait_futures as $wait_future) {
$iterator->addFuture($wait_future);
}

View file

@ -5,6 +5,12 @@ abstract class ArcanistHardpointObject
private $hardpointList;
public function __clone() {
if ($this->hardpointList) {
$this->hardpointList = clone $this->hardpointList;
}
}
final public function getHardpoint($hardpoint) {
return $this->getHardpointList()->getHardpoint(
$this,

View file

@ -3,6 +3,17 @@
abstract class ArcanistRefInspector
extends Phobject {
private $workflow;
final public function setWorkflow(ArcanistWorkflow $workflow) {
$this->workflow = $workflow;
return $this;
}
final public function getWorkflow() {
return $this->workflow;
}
abstract public function getInspectFunctionName();
abstract public function newInspectRef(array $argv);

View file

@ -1,913 +0,0 @@
<?php
final class ArcanistGitLandEngine
extends ArcanistLandEngine {
private $localRef;
private $localCommit;
private $sourceCommit;
private $mergedRef;
private $restoreWhenDestroyed;
private $isGitPerforce;
private function setIsGitPerforce($is_git_perforce) {
$this->isGitPerforce = $is_git_perforce;
return $this;
}
private function getIsGitPerforce() {
return $this->isGitPerforce;
}
public function parseArguments() {
$api = $this->getRepositoryAPI();
$onto = $this->getEngineOnto();
$this->setTargetOnto($onto);
$remote = $this->getEngineRemote();
$is_pushable = $api->isPushableRemote($remote);
$is_perforce = $api->isPerforceRemote($remote);
if (!$is_pushable && !$is_perforce) {
throw new PhutilArgumentUsageException(
pht(
'No pushable remote "%s" exists. Use the "--remote" flag to choose '.
'a valid, pushable remote to land changes onto.',
$remote));
}
if ($is_perforce) {
$this->setIsGitPerforce(true);
$this->writeWarn(
pht('P4 MODE'),
pht(
'Operating in Git/Perforce mode after selecting a Perforce '.
'remote.'));
if (!$this->getShouldSquash()) {
throw new PhutilArgumentUsageException(
pht(
'Perforce mode does not support the "merge" land strategy. '.
'Use the "squash" land strategy when landing to a Perforce '.
'remote (you can use "--squash" to select this strategy).'));
}
}
$this->setTargetRemote($remote);
}
public function execute() {
$this->verifySourceAndTargetExist();
$this->fetchTarget();
$this->printLandingCommits();
if ($this->getShouldPreview()) {
$this->writeInfo(
pht('PREVIEW'),
pht('Completed preview of operation.'));
return;
}
$this->saveLocalState();
try {
$this->identifyRevision();
$this->updateWorkingCopy();
if ($this->getShouldHold()) {
$this->didHoldChanges();
} else {
$this->pushChange();
$this->reconcileLocalState();
$api = $this->getRepositoryAPI();
$api->execxLocal('submodule update --init --recursive');
if ($this->getShouldKeep()) {
echo tsprintf(
"%s\n",
pht('Keeping local branch.'));
} else {
$this->destroyLocalBranch();
}
$this->writeOkay(
pht('DONE'),
pht('Landed changes.'));
}
$this->restoreWhenDestroyed = false;
} catch (Exception $ex) {
$this->restoreLocalState();
throw $ex;
}
}
public function __destruct() {
if ($this->restoreWhenDestroyed) {
$this->writeWarn(
pht('INTERRUPTED!'),
pht('Restoring working copy to its original state.'));
$this->restoreLocalState();
}
}
protected function getLandingCommits() {
$api = $this->getRepositoryAPI();
list($out) = $api->execxLocal(
'log --oneline %s..%s --',
$this->getTargetFullRef(),
$this->sourceCommit);
$out = trim($out);
if (!strlen($out)) {
return array();
} else {
return phutil_split_lines($out, false);
}
}
private function identifyRevision() {
$api = $this->getRepositoryAPI();
$api->execxLocal('checkout %s --', $this->getSourceRef());
call_user_func($this->getBuildMessageCallback(), $this);
}
private function verifySourceAndTargetExist() {
$api = $this->getRepositoryAPI();
list($err) = $api->execManualLocal(
'rev-parse --verify %s',
$this->getTargetFullRef());
if ($err) {
$this->writeWarn(
pht('TARGET'),
pht(
'No local ref exists for branch "%s" in remote "%s", attempting '.
'fetch...',
$this->getTargetOnto(),
$this->getTargetRemote()));
$api->execManualLocal(
'fetch %s %s --',
$this->getTargetRemote(),
$this->getTargetOnto());
list($err) = $api->execManualLocal(
'rev-parse --verify %s',
$this->getTargetFullRef());
if ($err) {
throw new Exception(
pht(
'Branch "%s" does not exist in remote "%s".',
$this->getTargetOnto(),
$this->getTargetRemote()));
}
$this->writeInfo(
pht('FETCHED'),
pht(
'Fetched branch "%s" from remote "%s".',
$this->getTargetOnto(),
$this->getTargetRemote()));
}
list($err, $stdout) = $api->execManualLocal(
'rev-parse --verify %s',
$this->getSourceRef());
if ($err) {
throw new Exception(
pht(
'Branch "%s" does not exist in the local working copy.',
$this->getSourceRef()));
}
$this->sourceCommit = trim($stdout);
}
private function fetchTarget() {
$api = $this->getRepositoryAPI();
$ref = $this->getTargetFullRef();
// NOTE: Although this output isn't hugely useful, we need to passthru
// instead of using a subprocess here because `git fetch` may prompt the
// user to enter a password if they're fetching over HTTP with basic
// authentication. See T10314.
if ($this->getIsGitPerforce()) {
$this->writeInfo(
pht('P4 SYNC'),
pht('Synchronizing "%s" from Perforce...', $ref));
$sync_ref = sprintf(
'refs/remotes/%s/%s',
$this->getTargetRemote(),
$this->getTargetOnto());
$err = $api->execPassthru(
'p4 sync --silent --branch %R --',
$sync_ref);
if ($err) {
throw new ArcanistUsageException(
pht(
'Perforce sync failed! Fix the error and run "arc land" again.'));
}
} else {
$this->writeInfo(
pht('FETCH'),
pht('Fetching "%s"...', $ref));
$err = $api->execPassthru(
'fetch --quiet -- %s %s',
$this->getTargetRemote(),
$this->getTargetOnto());
if ($err) {
throw new ArcanistUsageException(
pht(
'Fetch failed! Fix the error and run "arc land" again.'));
}
}
}
private function updateWorkingCopy() {
$api = $this->getRepositoryAPI();
$source = $this->sourceCommit;
$api->execxLocal(
'checkout %s --',
$this->getTargetFullRef());
list($original_author, $original_date) = $this->getAuthorAndDate($source);
try {
if ($this->getShouldSquash()) {
// NOTE: We're explicitly specifying "--ff" to override the presence
// of "merge.ff" options in user configuration.
$api->execxLocal(
'merge --no-stat --no-commit --ff --squash -- %s',
$source);
} else {
$api->execxLocal(
'merge --no-stat --no-commit --no-ff -- %s',
$source);
}
} catch (Exception $ex) {
$api->execManualLocal('merge --abort');
$api->execManualLocal('reset --hard HEAD --');
throw new Exception(
pht(
'Local "%s" does not merge cleanly into "%s". Merge or rebase '.
'local changes so they can merge cleanly.',
$this->getSourceRef(),
$this->getTargetFullRef()));
}
// TODO: This could probably be cleaner by asking the API a question
// about working copy status instead of running a raw diff command. See
// discussion in T11435.
list($changes) = $api->execxLocal('diff --no-ext-diff HEAD --');
$changes = trim($changes);
if (!strlen($changes)) {
throw new Exception(
pht(
'Merging local "%s" into "%s" produces an empty diff. '.
'This usually means these changes have already landed.',
$this->getSourceRef(),
$this->getTargetFullRef()));
}
$api->execxLocal(
'commit --author %s --date %s -F %s --',
$original_author,
$original_date,
$this->getCommitMessageFile());
$this->getWorkflow()->didCommitMerge();
list($stdout) = $api->execxLocal(
'rev-parse --verify %s',
'HEAD');
$this->mergedRef = trim($stdout);
}
private function pushChange() {
$api = $this->getRepositoryAPI();
if ($this->getIsGitPerforce()) {
$this->writeInfo(
pht('SUBMITTING'),
pht('Submitting changes to "%s".', $this->getTargetFullRef()));
$config_argv = array();
// Skip the "git p4 submit" interactive editor workflow. We expect
// the commit message that "arc land" has built to be satisfactory.
$config_argv[] = '-c';
$config_argv[] = 'git-p4.skipSubmitEdit=true';
// Skip the "git p4 submit" confirmation prompt if the user does not edit
// the submit message.
$config_argv[] = '-c';
$config_argv[] = 'git-p4.skipSubmitEditCheck=true';
$flags_argv = array();
// Disable implicit "git p4 rebase" as part of submit. We're allowing
// the implicit "git p4 sync" to go through since this puts us in a
// state which is generally similar to the state after "git push", with
// updated remotes.
// We could do a manual "git p4 sync" with a more narrow "--branch"
// instead, but it's not clear that this is beneficial.
$flags_argv[] = '--disable-rebase';
// Detect moves and submit them to Perforce as move operations.
$flags_argv[] = '-M';
// If we run into a conflict, abort the operation. We expect users to
// fix conflicts and run "arc land" again.
$flags_argv[] = '--conflict=quit';
$err = $api->execPassthru(
'%LR p4 submit %LR --commit %R --',
$config_argv,
$flags_argv,
$this->mergedRef);
if ($err) {
throw new ArcanistUsageException(
pht(
'Submit failed! Fix the error and run "arc land" again.'));
}
} else {
$this->writeInfo(
pht('PUSHING'),
pht('Pushing changes to "%s".', $this->getTargetFullRef()));
$err = $api->execPassthru(
'push -- %s %s:%s',
$this->getTargetRemote(),
$this->mergedRef,
$this->getTargetOnto());
if ($err) {
throw new ArcanistUsageException(
pht(
'Push failed! Fix the error and run "arc land" again.'));
}
}
}
private function reconcileLocalState() {
$api = $this->getRepositoryAPI();
// Try to put the user into the best final state we can. This is very
// complicated because users are incredibly creative and their local
// branches may have the same names as branches in the remote but no
// relationship to them.
if ($this->localRef != $this->getSourceRef()) {
// The user ran `arc land X` but was on a different branch, so just put
// them back wherever they were before.
$this->writeInfo(
pht('RESTORE'),
pht('Switching back to "%s".', $this->localRef));
$this->restoreLocalState();
return;
}
// We're going to try to find a path to the upstream target branch. We
// try in two different ways:
//
// - follow the source branch directly along tracking branches until
// we reach the upstream; or
// - follow a local branch with the same name as the target branch until
// we reach the upstream.
// First, get the path from whatever we landed to wherever it goes.
$local_branch = $this->getSourceRef();
$path = $api->getPathToUpstream($local_branch);
if ($path->getLength()) {
// We may want to discard the thing we landed from the path, if we're
// going to delete it. In this case, we don't want to update it or worry
// if it's dirty.
if ($this->getSourceRef() == $this->getTargetOnto()) {
// In this case, we've done something like land "master" onto itself,
// so we do want to update the actual branch. We're going to use the
// entire path.
} else {
// Otherwise, we're going to delete the branch at the end of the
// workflow, so throw it away the most-local branch that isn't long
// for this world.
$path->removeUpstream($local_branch);
if (!$path->getLength()) {
// The local branch tracked upstream directly; however, it
// may not be the only one to do so. If there's a local
// branch of the same name that tracks the remote, try
// switching to that.
$local_branch = $this->getTargetOnto();
list($err) = $api->execManualLocal(
'rev-parse --verify %s',
$local_branch);
if (!$err) {
$path = $api->getPathToUpstream($local_branch);
}
if (!$path->isConnectedToRemote()) {
$this->writeInfo(
pht('UPDATE'),
pht(
'Local branch "%s" directly tracks remote, staying on '.
'detached HEAD.',
$local_branch));
return;
}
}
$local_branch = head($path->getLocalBranches());
}
} else {
// The source branch has no upstream, so look for a local branch with
// the same name as the target branch. This corresponds to the common
// case where you have "master" and checkout local branches from it
// with "git checkout -b feature", then land onto "master".
$local_branch = $this->getTargetOnto();
list($err) = $api->execManualLocal(
'rev-parse --verify %s',
$local_branch);
if ($err) {
$this->writeInfo(
pht('UPDATE'),
pht(
'Local branch "%s" does not exist, staying on detached HEAD.',
$local_branch));
return;
}
$path = $api->getPathToUpstream($local_branch);
}
if ($path->getCycle()) {
$this->writeWarn(
pht('LOCAL CYCLE'),
pht(
'Local branch "%s" tracks an upstream but following it leads to '.
'a local cycle, staying on detached HEAD.',
$local_branch));
return;
}
$is_perforce = $this->getIsGitPerforce();
if ($is_perforce) {
// If we're in Perforce mode, we don't expect to have a meaningful
// path to the remote: the "p4" remote is not a real remote, and
// "git p4" commands do not configure branch upstreams to provide
// a path.
// Just pretend the target branch is connected directly to the remote,
// since this is effectively the behavior of Perforce and appears to
// do the right thing.
$cascade_branches = array($local_branch);
} else {
if (!$path->isConnectedToRemote()) {
$this->writeInfo(
pht('UPDATE'),
pht(
'Local branch "%s" is not connected to a remote, staying on '.
'detached HEAD.',
$local_branch));
return;
}
$remote_remote = $path->getRemoteRemoteName();
$remote_branch = $path->getRemoteBranchName();
$remote_actual = $remote_remote.'/'.$remote_branch;
$remote_expect = $this->getTargetFullRef();
if ($remote_actual != $remote_expect) {
$this->writeInfo(
pht('UPDATE'),
pht(
'Local branch "%s" is connected to a remote ("%s") other than '.
'the target remote ("%s"), staying on detached HEAD.',
$local_branch,
$remote_actual,
$remote_expect));
return;
}
// If we get this far, we have a sequence of branches which ultimately
// connect to the remote. We're going to try to update them all in reverse
// order, from most-upstream to most-local.
$cascade_branches = $path->getLocalBranches();
$cascade_branches = array_reverse($cascade_branches);
}
// First, check if any of them are ahead of the remote.
$ahead_of_remote = array();
foreach ($cascade_branches as $cascade_branch) {
list($stdout) = $api->execxLocal(
'log %s..%s --',
$this->mergedRef,
$cascade_branch);
$stdout = trim($stdout);
if (strlen($stdout)) {
$ahead_of_remote[$cascade_branch] = $cascade_branch;
}
}
// We're going to handle the last branch (the thing we ultimately intend
// to check out) differently. It's OK if it's ahead of the remote, as long
// as we just landed it.
$local_ahead = isset($ahead_of_remote[$local_branch]);
unset($ahead_of_remote[$local_branch]);
$land_self = ($this->getTargetOnto() === $this->getSourceRef());
// We aren't going to pull anything if anything upstream from us is ahead
// of the remote, or the local is ahead of the remote and we didn't land
// it onto itself.
$skip_pull = ($ahead_of_remote || ($local_ahead && !$land_self));
if ($skip_pull) {
$this->writeInfo(
pht('UPDATE'),
pht(
'Local "%s" is ahead of remote "%s". Checking out "%s" but '.
'not pulling changes.',
nonempty(head($ahead_of_remote), $local_branch),
$this->getTargetFullRef(),
$local_branch));
$this->writeInfo(
pht('CHECKOUT'),
pht(
'Checking out "%s".',
$local_branch));
$api->execxLocal('checkout %s --', $local_branch);
return;
}
// If nothing upstream from our nearest branch is ahead of the remote,
// pull it all.
$cascade_targets = array();
if (!$ahead_of_remote) {
foreach ($cascade_branches as $cascade_branch) {
if ($local_ahead && ($local_branch == $cascade_branch)) {
continue;
}
$cascade_targets[] = $cascade_branch;
}
}
if ($is_perforce) {
// In Perforce, we've already set the remote to the right state with an
// implicit "git p4 sync" during "git p4 submit", and "git pull" isn't a
// meaningful operation. We're going to skip this step and jump down to
// the "git reset --hard" below to get everything into the right state.
} else if ($cascade_targets) {
$this->writeInfo(
pht('UPDATE'),
pht(
'Local "%s" tracks target remote "%s", checking out and '.
'pulling changes.',
$local_branch,
$this->getTargetFullRef()));
foreach ($cascade_targets as $cascade_branch) {
$this->writeInfo(
pht('PULL'),
pht(
'Checking out and pulling "%s".',
$cascade_branch));
$api->execxLocal('checkout %s --', $cascade_branch);
$api->execxLocal(
'pull %s %s --',
$this->getTargetRemote(),
$cascade_branch);
}
if (!$local_ahead) {
return;
}
}
// In this case, the user did something like land a branch onto itself,
// and the branch is tracking the correct remote. We're going to discard
// the local state and reset it to the state we just pushed.
$this->writeInfo(
pht('RESET'),
pht(
'Local "%s" landed into remote "%s", resetting local branch to '.
'remote state.',
$this->getTargetOnto(),
$this->getTargetFullRef()));
$api->execxLocal('checkout %s --', $local_branch);
$api->execxLocal('reset --hard %s --', $this->getTargetFullRef());
return;
}
private function destroyLocalBranch() {
$api = $this->getRepositoryAPI();
$source_ref = $this->getSourceRef();
if ($source_ref == $this->getTargetOnto()) {
// If we landed a branch into a branch with the same name, so don't
// destroy it. This prevents us from cleaning up "master" if you're
// landing master into itself.
return;
}
// TODO: Maybe this should also recover the proper upstream?
// See T10321. If we were not landing a branch, don't try to clean it up.
// This happens most often when landing from a detached HEAD.
$is_branch = $this->isBranch($source_ref);
if (!$is_branch) {
echo tsprintf(
"%s\n",
pht(
'(Source "%s" is not a branch, leaving working copy as-is.)',
$source_ref));
return;
}
$recovery_command = csprintf(
'git checkout -b %R %R',
$source_ref,
$this->sourceCommit);
echo tsprintf(
"%s\n",
pht('Cleaning up branch "%s"...', $source_ref));
echo tsprintf(
"%s\n",
pht('(Use `%s` if you want it back.)', $recovery_command));
$api->execxLocal('branch -D -- %s', $source_ref);
}
/**
* Save the local working copy state so we can restore it later.
*/
private function saveLocalState() {
$api = $this->getRepositoryAPI();
$this->localCommit = $api->getWorkingCopyRevision();
list($ref) = $api->execxLocal('rev-parse --abbrev-ref HEAD');
$ref = trim($ref);
if ($ref === 'HEAD') {
$ref = $this->localCommit;
}
$this->localRef = $ref;
$this->restoreWhenDestroyed = true;
}
/**
* Restore the working copy to the state it was in before we started
* performing writes.
*/
private function restoreLocalState() {
$api = $this->getRepositoryAPI();
$api->execxLocal('checkout %s --', $this->localRef);
$api->execxLocal('reset --hard %s --', $this->localCommit);
$api->execxLocal('submodule update --init --recursive');
$this->restoreWhenDestroyed = false;
}
private function getTargetFullRef() {
return $this->getTargetRemote().'/'.$this->getTargetOnto();
}
private function getAuthorAndDate($commit) {
$api = $this->getRepositoryAPI();
// TODO: This is working around Windows escaping problems, see T8298.
list($info) = $api->execxLocal(
'log -n1 --format=%C %s --',
'%aD%n%an%n%ae',
$commit);
$info = trim($info);
list($date, $author, $email) = explode("\n", $info, 3);
return array(
"$author <{$email}>",
$date,
);
}
private function didHoldChanges() {
if ($this->getIsGitPerforce()) {
$this->writeInfo(
pht('HOLD'),
pht(
'Holding change locally, it has not been submitted.'));
$push_command = csprintf(
'$ git p4 submit -M --commit %R --',
$this->mergedRef);
} else {
$this->writeInfo(
pht('HOLD'),
pht(
'Holding change locally, it has not been pushed.'));
$push_command = csprintf(
'$ git push -- %R %R:%R',
$this->getTargetRemote(),
$this->mergedRef,
$this->getTargetOnto());
}
$restore_command = csprintf(
'$ git checkout %R --',
$this->localRef);
echo tsprintf(
"\n%s\n\n".
"%s\n\n".
" **%s**\n\n".
"%s\n\n".
" **%s**\n\n".
"%s\n",
pht(
'This local working copy now contains the merged changes in a '.
'detached state.'),
pht('You can push the changes manually with this command:'),
$push_command,
pht(
'You can go back to how things were before you ran "arc land" with '.
'this command:'),
$restore_command,
pht(
'Local branches have not been changed, and are still in exactly the '.
'same state as before.'));
}
private function isBranch($ref) {
$api = $this->getRepositoryAPI();
list($err) = $api->execManualLocal(
'show-ref --verify --quiet -- %R',
'refs/heads/'.$ref);
return !$err;
}
private function getEngineOnto() {
$source_ref = $this->getSourceRef();
$onto = $this->getOntoArgument();
if ($onto !== null) {
$this->writeInfo(
pht('TARGET'),
pht(
'Landing onto "%s", selected with the "--onto" flag.',
$onto));
return $onto;
}
$api = $this->getRepositoryAPI();
$path = $api->getPathToUpstream($source_ref);
if ($path->getLength()) {
$cycle = $path->getCycle();
if ($cycle) {
$this->writeWarn(
pht('LOCAL CYCLE'),
pht(
'Local branch tracks an upstream, but following it leads to a '.
'local cycle; ignoring branch upstream.'));
echo tsprintf(
"\n %s\n\n",
implode(' -> ', $cycle));
} else {
if ($path->isConnectedToRemote()) {
$onto = $path->getRemoteBranchName();
$this->writeInfo(
pht('TARGET'),
pht(
'Landing onto "%s", selected by following tracking branches '.
'upstream to the closest remote.',
$onto));
return $onto;
} else {
$this->writeInfo(
pht('NO PATH TO UPSTREAM'),
pht(
'Local branch tracks an upstream, but there is no path '.
'to a remote; ignoring branch upstream.'));
}
}
}
$workflow = $this->getWorkflow();
$config_key = 'arc.land.onto.default';
$onto = $workflow->getConfigFromAnySource($config_key);
if ($onto !== null) {
$this->writeInfo(
pht('TARGET'),
pht(
'Landing onto "%s", selected by "%s" configuration.',
$onto,
$config_key));
return $onto;
}
$onto = 'master';
$this->writeInfo(
pht('TARGET'),
pht(
'Landing onto "%s", the default target under git.',
$onto));
return $onto;
}
private function getEngineRemote() {
$source_ref = $this->getSourceRef();
$remote = $this->getRemoteArgument();
if ($remote !== null) {
$this->writeInfo(
pht('REMOTE'),
pht(
'Using remote "%s", selected with the "--remote" flag.',
$remote));
return $remote;
}
$api = $this->getRepositoryAPI();
$path = $api->getPathToUpstream($source_ref);
$remote = $path->getRemoteRemoteName();
if ($remote !== null) {
$this->writeInfo(
pht('REMOTE'),
pht(
'Using remote "%s", selected by following tracking branches '.
'upstream to the closest remote.',
$remote));
return $remote;
}
$remote = 'p4';
if ($api->isPerforceRemote($remote)) {
$this->writeInfo(
pht('REMOTE'),
pht(
'Using Perforce remote "%s". The existence of this remote implies '.
'this working copy was synchronized from a Perforce repository.',
$remote));
return $remote;
}
$remote = 'origin';
$this->writeInfo(
pht('REMOTE'),
pht(
'Using remote "%s", the default remote under Git.',
$remote));
return $remote;
}
}

View file

@ -0,0 +1,170 @@
<?php
final class ArcanistLandCommit
extends Phobject {
private $hash;
private $summary;
private $displaySummary;
private $parents;
private $explicitRevisionRef;
private $revisionRef = false;
private $parentCommits;
private $isHeadCommit;
private $isImplicitCommit;
private $relatedRevisionRefs = array();
private $directSymbols = array();
private $indirectSymbols = array();
public function setHash($hash) {
$this->hash = $hash;
return $this;
}
public function getHash() {
return $this->hash;
}
public function setSummary($summary) {
$this->summary = $summary;
return $this;
}
public function getSummary() {
return $this->summary;
}
public function getDisplaySummary() {
if ($this->displaySummary === null) {
$this->displaySummary = id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs(64)
->truncateString($this->getSummary());
}
return $this->displaySummary;
}
public function setParents(array $parents) {
$this->parents = $parents;
return $this;
}
public function getParents() {
return $this->parents;
}
public function addDirectSymbol(ArcanistLandSymbol $symbol) {
$this->directSymbols[] = $symbol;
return $this;
}
public function getDirectSymbols() {
return $this->directSymbols;
}
public function addIndirectSymbol(ArcanistLandSymbol $symbol) {
$this->indirectSymbols[] = $symbol;
return $this;
}
public function getIndirectSymbols() {
return $this->indirectSymbols;
}
public function setExplicitRevisionref(ArcanistRevisionRef $ref) {
$this->explicitRevisionRef = $ref;
return $this;
}
public function getExplicitRevisionref() {
return $this->explicitRevisionRef;
}
public function setParentCommits(array $parent_commits) {
$this->parentCommits = $parent_commits;
return $this;
}
public function getParentCommits() {
return $this->parentCommits;
}
public function setIsHeadCommit($is_head_commit) {
$this->isHeadCommit = $is_head_commit;
return $this;
}
public function getIsHeadCommit() {
return $this->isHeadCommit;
}
public function setIsImplicitCommit($is_implicit_commit) {
$this->isImplicitCommit = $is_implicit_commit;
return $this;
}
public function getIsImplicitCommit() {
return $this->isImplicitCommit;
}
public function getAncestorRevisionPHIDs() {
$phids = array();
foreach ($this->getParentCommits() as $parent_commit) {
$phids += $parent_commit->getAncestorRevisionPHIDs();
}
$revision_ref = $this->getRevisionRef();
if ($revision_ref) {
$phids[$revision_ref->getPHID()] = $revision_ref->getPHID();
}
return $phids;
}
public function getRevisionRef() {
if ($this->revisionRef === false) {
$this->revisionRef = $this->newRevisionRef();
}
return $this->revisionRef;
}
private function newRevisionRef() {
$revision_ref = $this->getExplicitRevisionRef();
if ($revision_ref) {
return $revision_ref;
}
$parent_refs = array();
foreach ($this->getParentCommits() as $parent_commit) {
$parent_ref = $parent_commit->getRevisionRef();
if ($parent_ref) {
$parent_refs[$parent_ref->getPHID()] = $parent_ref;
}
}
if (count($parent_refs) > 1) {
throw new Exception(
pht(
'Too many distinct parent refs!'));
}
if ($parent_refs) {
return head($parent_refs);
}
return null;
}
public function setRelatedRevisionRefs(array $refs) {
assert_instances_of($refs, 'ArcanistRevisionRef');
$this->relatedRevisionRefs = $refs;
return $this;
}
public function getRelatedRevisionRefs() {
return $this->relatedRevisionRefs;
}
}

View file

@ -0,0 +1,72 @@
<?php
final class ArcanistLandCommitSet
extends Phobject {
private $revisionRef;
private $commits;
private $isPick;
public function setRevisionRef(ArcanistRevisionRef $revision_ref) {
$this->revisionRef = $revision_ref;
return $this;
}
public function getRevisionRef() {
return $this->revisionRef;
}
public function setCommits(array $commits) {
assert_instances_of($commits, 'ArcanistLandCommit');
$this->commits = $commits;
$revision_phid = $this->getRevisionRef()->getPHID();
foreach ($commits as $commit) {
$revision_ref = $commit->getExplicitRevisionRef();
if ($revision_ref) {
if ($revision_ref->getPHID() === $revision_phid) {
continue;
}
}
$commit->setIsImplicitCommit(true);
}
return $this;
}
public function getCommits() {
return $this->commits;
}
public function hasImplicitCommits() {
foreach ($this->commits as $commit) {
if ($commit->getIsImplicitCommit()) {
return true;
}
}
return false;
}
public function hasDirectSymbols() {
foreach ($this->commits as $commit) {
if ($commit->getDirectSymbols()) {
return true;
}
}
return false;
}
public function setIsPick($is_pick) {
$this->isPick = $is_pick;
return $this;
}
public function getIsPick() {
return $this->isPick;
}
}

View file

@ -1,182 +0,0 @@
<?php
abstract class ArcanistLandEngine extends Phobject {
private $workflow;
private $repositoryAPI;
private $targetRemote;
private $targetOnto;
private $sourceRef;
private $commitMessageFile;
private $shouldHold;
private $shouldKeep;
private $shouldSquash;
private $shouldDeleteRemote;
private $shouldPreview;
private $remoteArgument;
private $ontoArgument;
// TODO: This is really grotesque.
private $buildMessageCallback;
final public function setWorkflow(ArcanistWorkflow $workflow) {
$this->workflow = $workflow;
return $this;
}
final public function getWorkflow() {
return $this->workflow;
}
final public function setRepositoryAPI(
ArcanistRepositoryAPI $repository_api) {
$this->repositoryAPI = $repository_api;
return $this;
}
final public function getRepositoryAPI() {
return $this->repositoryAPI;
}
final public function setShouldHold($should_hold) {
$this->shouldHold = $should_hold;
return $this;
}
final public function getShouldHold() {
return $this->shouldHold;
}
final public function setShouldKeep($should_keep) {
$this->shouldKeep = $should_keep;
return $this;
}
final public function getShouldKeep() {
return $this->shouldKeep;
}
final public function setShouldSquash($should_squash) {
$this->shouldSquash = $should_squash;
return $this;
}
final public function getShouldSquash() {
return $this->shouldSquash;
}
final public function setShouldPreview($should_preview) {
$this->shouldPreview = $should_preview;
return $this;
}
final public function getShouldPreview() {
return $this->shouldPreview;
}
final public function setTargetRemote($target_remote) {
$this->targetRemote = $target_remote;
return $this;
}
final public function getTargetRemote() {
return $this->targetRemote;
}
final public function setTargetOnto($target_onto) {
$this->targetOnto = $target_onto;
return $this;
}
final public function getTargetOnto() {
return $this->targetOnto;
}
final public function setSourceRef($source_ref) {
$this->sourceRef = $source_ref;
return $this;
}
final public function getSourceRef() {
return $this->sourceRef;
}
final public function setBuildMessageCallback($build_message_callback) {
$this->buildMessageCallback = $build_message_callback;
return $this;
}
final public function getBuildMessageCallback() {
return $this->buildMessageCallback;
}
final public function setCommitMessageFile($commit_message_file) {
$this->commitMessageFile = $commit_message_file;
return $this;
}
final public function getCommitMessageFile() {
return $this->commitMessageFile;
}
final public function setRemoteArgument($remote_argument) {
$this->remoteArgument = $remote_argument;
return $this;
}
final public function getRemoteArgument() {
return $this->remoteArgument;
}
final public function setOntoArgument($onto_argument) {
$this->ontoArgument = $onto_argument;
return $this;
}
final public function getOntoArgument() {
return $this->ontoArgument;
}
abstract public function parseArguments();
abstract public function execute();
abstract protected function getLandingCommits();
protected function printLandingCommits() {
$logs = $this->getLandingCommits();
if (!$logs) {
throw new ArcanistUsageException(
pht(
'There are no commits on "%s" which are not already present on '.
'the target.',
$this->getSourceRef()));
}
$list = id(new PhutilConsoleList())
->setWrap(false)
->addItems($logs);
id(new PhutilConsoleBlock())
->addParagraph(
pht(
'These %s commit(s) will be landed:',
new PhutilNumber(count($logs))))
->addList($list)
->draw();
}
protected function writeWarn($title, $message) {
return $this->getWorkflow()->writeWarn($title, $message);
}
protected function writeInfo($title, $message) {
return $this->getWorkflow()->writeInfo($title, $message);
}
protected function writeOkay($title, $message) {
return $this->getWorkflow()->writeOkay($title, $message);
}
}

View file

@ -0,0 +1,27 @@
<?php
final class ArcanistLandSymbol
extends Phobject {
private $symbol;
private $commit;
public function setSymbol($symbol) {
$this->symbol = $symbol;
return $this;
}
public function getSymbol() {
return $this->symbol;
}
public function setCommit($commit) {
$this->commit = $commit;
return $this;
}
public function getCommit() {
return $this->commit;
}
}

View file

@ -0,0 +1,41 @@
<?php
final class ArcanistLandTarget
extends Phobject {
private $remote;
private $ref;
private $commit;
public function setRemote($remote) {
$this->remote = $remote;
return $this;
}
public function getRemote() {
return $this->remote;
}
public function setRef($ref) {
$this->ref = $ref;
return $this;
}
public function getRef() {
return $this->ref;
}
public function getLandTargetKey() {
return sprintf('%s/%s', $this->getRemote(), $this->getRef());
}
public function setLandTargetCommit($commit) {
$this->commit = $commit;
return $this;
}
public function getLandTargetCommit() {
return $this->commit;
}
}

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -115,6 +115,21 @@ final class PhutilArgumentSpellingCorrector extends Phobject {
$options[$key] = $this->normalizeString($option);
}
// In command mode, accept any unique prefix of a command as a shorthand
// for that command.
if ($this->getMode() === self::MODE_COMMANDS) {
$prefixes = array();
foreach ($options as $option) {
if (!strncmp($input, $option, strlen($input))) {
$prefixes[] = $option;
}
}
if (count($prefixes) === 1) {
return $prefixes;
}
}
$distances = array();
$inputv = phutil_utf8v($input);
foreach ($options as $option) {

View file

@ -2,6 +2,17 @@
final class PhutilHelpArgumentWorkflow extends PhutilArgumentWorkflow {
private $workflow;
public function setWorkflow($workflow) {
$this->workflow = $workflow;
return $this;
}
public function getWorkflow() {
return $this->workflow;
}
protected function didConstruct() {
$this->setName('help');
$this->setExamples(<<<EOHELP
@ -29,16 +40,26 @@ EOHELP
$with = $args->getArg('help-with-what');
if (!$with) {
// TODO: Update this to use a pager, too.
$args->printHelpAndExit();
} else {
$out = array();
foreach ($with as $thing) {
echo phutil_console_format(
$out[] = phutil_console_format(
"**%s**\n\n",
pht('%s WORKFLOW', strtoupper($thing)));
echo $args->renderWorkflowHelp($thing, $show_flags = true);
echo "\n";
$out[] = $args->renderWorkflowHelp($thing, $show_flags = true);
$out[] = "\n";
}
$out = implode('', $out);
$workflow = $this->getWorkflow();
if ($workflow) {
$workflow->writeToPager($out);
} else {
echo $out;
}
exit(PhutilArgumentParser::PARSE_ERROR_CODE);
}
}

View file

@ -0,0 +1,36 @@
<?php
final class ArcanistMercurialCommitMessageHardpointQuery
extends ArcanistWorkflowMercurialHardpointQuery {
public function getHardpoints() {
return array(
ArcanistCommitRef::HARDPOINT_MESSAGE,
);
}
protected function canLoadRef(ArcanistRef $ref) {
return ($ref instanceof ArcanistCommitRef);
}
public function loadHardpoint(array $refs, $hardpoint) {
$api = $this->getRepositoryAPI();
$hashes = mpull($refs, 'getCommitHash');
$unique_hashes = array_fuse($hashes);
// TODO: Batch this properly and make it future oriented.
$messages = array();
foreach ($unique_hashes as $unique_hash) {
$messages[$unique_hash] = $api->getCommitMessage($unique_hash);
}
foreach ($hashes as $ref_key => $hash) {
$hashes[$ref_key] = $messages[$hash];
}
yield $this->yieldMap($hashes);
}
}

View file

@ -0,0 +1,76 @@
<?php
final class ArcanistMercurialWorkingCopyRevisionHardpointQuery
extends ArcanistWorkflowMercurialHardpointQuery {
public function getHardpoints() {
return array(
ArcanistWorkingCopyStateRef::HARDPOINT_REVISIONREFS,
);
}
protected function canLoadRef(ArcanistRef $ref) {
return ($ref instanceof ArcanistWorkingCopyStateRef);
}
public function loadHardpoint(array $refs, $hardpoint) {
yield $this->yieldRequests(
$refs,
array(
ArcanistWorkingCopyStateRef::HARDPOINT_COMMITREF,
));
// TODO: This has a lot in common with the Git query in the same role.
$hashes = array();
$map = array();
foreach ($refs as $ref_key => $ref) {
$commit = $ref->getCommitRef();
$commit_hashes = array();
$commit_hashes[] = array(
'hgcm',
$commit->getCommitHash(),
);
foreach ($commit_hashes as $hash) {
$hashes[] = $hash;
$hash_key = $this->getHashKey($hash);
$map[$hash_key][$ref_key] = $ref;
}
}
$results = array_fill_keys(array_keys($refs), array());
if ($hashes) {
$revisions = (yield $this->yieldConduit(
'differential.query',
array(
'commitHashes' => $hashes,
)));
foreach ($revisions as $dict) {
$revision_hashes = idx($dict, 'hashes');
if (!$revision_hashes) {
continue;
}
$revision_ref = ArcanistRevisionRef::newFromConduitQuery($dict);
foreach ($revision_hashes as $revision_hash) {
$hash_key = $this->getHashKey($revision_hash);
$state_refs = idx($map, $hash_key, array());
foreach ($state_refs as $ref_key => $state_ref) {
$results[$ref_key][] = $revision_ref;
}
}
}
}
yield $this->yieldMap($results);
}
private function getHashKey(array $hash) {
return $hash[0].':'.$hash[1];
}
}

View file

@ -0,0 +1,11 @@
<?php
abstract class ArcanistWorkflowMercurialHardpointQuery
extends ArcanistRuntimeHardpointQuery {
final protected function canLoadHardpoint() {
$api = $this->getRepositoryAPI();
return ($api instanceof ArcanistMercurialAPI);
}
}

View file

@ -1,39 +0,0 @@
<?php
final class ArcanistWorkingCopyCommitHardpointQuery
extends ArcanistRuntimeHardpointQuery {
public function getHardpoints() {
return array(
ArcanistWorkingCopyStateRef::HARDPOINT_COMMITREF,
);
}
protected function canLoadRef(ArcanistRef $ref) {
return ($ref instanceof ArcanistWorkingCopyStateRef);
}
public function loadHardpoint(array $refs, $hardpoint) {
yield $this->yieldRequests(
$refs,
array(
ArcanistWorkingCopyStateRef::HARDPOINT_BRANCHREF,
));
$branch_refs = mpull($refs, 'getBranchRef');
yield $this->yieldRequests(
$branch_refs,
array(
ArcanistBranchRef::HARDPOINT_COMMITREF,
));
$results = array();
foreach ($refs as $key => $ref) {
$results[$key] = $ref->getBranchRef()->getCommitRef();
}
yield $this->yieldMap($results);
}
}

View file

@ -1,57 +0,0 @@
<?php
final class ArcanistBranchRef
extends ArcanistRef {
const HARDPOINT_COMMITREF = 'commitRef';
private $branchName;
private $refName;
private $isCurrentBranch;
public function getRefDisplayName() {
return pht('Branch %s', $this->getBranchName());
}
protected function newHardpoints() {
return array(
$this->newHardpoint(self::HARDPOINT_COMMITREF),
);
}
public function setBranchName($branch_name) {
$this->branchName = $branch_name;
return $this;
}
public function getBranchName() {
return $this->branchName;
}
public function setRefName($ref_name) {
$this->refName = $ref_name;
return $this;
}
public function getRefName() {
return $this->refName;
}
public function setIsCurrentBranch($is_current_branch) {
$this->isCurrentBranch = $is_current_branch;
return $this;
}
public function getIsCurrentBranch() {
return $this->isCurrentBranch;
}
public function attachCommitRef(ArcanistCommitRef $ref) {
return $this->attachHardpoint(self::HARDPOINT_COMMITREF, $ref);
}
public function getCommitRef() {
return $this->getHardpoint(self::HARDPOINT_COMMITREF);
}
}

View file

@ -1,25 +0,0 @@
<?php
final class ArcanistBuildPlanRef
extends Phobject {
private $parameters;
public static function newFromConduit(array $data) {
$ref = new self();
$ref->parameters = $data;
return $ref;
}
public function getPHID() {
return $this->parameters['phid'];
}
public function getBehavior($behavior_key, $default = null) {
return idxv(
$this->parameters,
array('fields', 'behaviors', $behavior_key, 'value'),
$default);
}
}

View file

@ -1,140 +0,0 @@
<?php
final class ArcanistBuildRef
extends Phobject {
private $parameters;
public static function newFromConduit(array $data) {
$ref = new self();
$ref->parameters = $data;
return $ref;
}
private function getStatusMap() {
// The modern "harbormaster.build.search" API method returns this in the
// "fields" list; the older API method returns it at the root level.
if (isset($this->parameters['fields']['buildStatus'])) {
$status = $this->parameters['fields']['buildStatus'];
} else if (isset($this->parameters['buildStatus'])) {
$status = $this->parameters['buildStatus'];
} else {
$status = 'unknown';
}
// We may either have an array or a scalar here. The array comes from
// "harbormaster.build.search", or from "harbormaster.querybuilds" if
// the server is newer than August 2016. The scalar comes from older
// versions of that method. See PHI261.
if (is_array($status)) {
$map = $status;
} else {
$map = array(
'value' => $status,
);
}
// If we don't have a name, try to fill one in.
if (!isset($map['name'])) {
$name_map = array(
'inactive' => pht('Inactive'),
'pending' => pht('Pending'),
'building' => pht('Building'),
'passed' => pht('Passed'),
'failed' => pht('Failed'),
'aborted' => pht('Aborted'),
'error' => pht('Error'),
'paused' => pht('Paused'),
'deadlocked' => pht('Deadlocked'),
'unknown' => pht('Unknown'),
);
$map['name'] = idx($name_map, $map['value'], $map['value']);
}
// If we don't have an ANSI color code, try to fill one in.
if (!isset($map['color.ansi'])) {
$color_map = array(
'failed' => 'red',
'passed' => 'green',
);
$map['color.ansi'] = idx($color_map, $map['value'], 'yellow');
}
return $map;
}
public function getID() {
return $this->parameters['id'];
}
public function getPHID() {
return $this->parameters['phid'];
}
public function getName() {
if (isset($this->parameters['fields']['name'])) {
return $this->parameters['fields']['name'];
}
return $this->parameters['name'];
}
public function getStatus() {
$map = $this->getStatusMap();
return $map['value'];
}
public function getStatusName() {
$map = $this->getStatusMap();
return $map['name'];
}
public function getStatusANSIColor() {
$map = $this->getStatusMap();
return $map['color.ansi'];
}
public function getObjectName() {
return pht('Build %d', $this->getID());
}
public function getBuildPlanPHID() {
return idxv($this->parameters, array('fields', 'buildPlanPHID'));
}
public function isComplete() {
switch ($this->getStatus()) {
case 'passed':
case 'failed':
case 'aborted':
case 'error':
case 'deadlocked':
return true;
default:
return false;
}
}
public function isPassed() {
return ($this->getStatus() === 'passed');
}
public function getStatusSortVector() {
$status = $this->getStatus();
// For now, just sort passed builds first.
if ($this->isPassed()) {
$status_class = 1;
} else {
$status_class = 2;
}
return id(new PhutilSortVector())
->addInt($status_class)
->addString($status);
}
}

View file

@ -1,8 +0,0 @@
<?php
interface ArcanistDisplayRefInterface {
public function getDisplayRefObjectName();
public function getDisplayRefTitle();
}

View file

@ -5,8 +5,17 @@ abstract class ArcanistRef
abstract public function getRefDisplayName();
final public function newDisplayRef() {
return id(new ArcanistDisplayRef())
final public function newRefView() {
$ref_view = id(new ArcanistRefView())
->setRef($this);
$this->buildRefView($ref_view);
return $ref_view;
}
protected function buildRefView(ArcanistRefView $view) {
return null;
}
}

View file

@ -1,12 +1,16 @@
<?php
final class ArcanistDisplayRef
final class ArcanistRefView
extends Phobject
implements
ArcanistTerminalStringInterface {
private $objectName;
private $title;
private $ref;
private $uri;
private $lines = array();
private $children = array();
public function setRef(ArcanistRef $ref) {
$this->ref = $ref;
@ -17,6 +21,24 @@ final class ArcanistDisplayRef
return $this->ref;
}
public function setObjectName($object_name) {
$this->objectName = $object_name;
return $this;
}
public function getObjectName() {
return $this->objectName;
}
public function setTitle($title) {
$this->title = $title;
return $this;
}
public function getTitle() {
return $this->title;
}
public function setURI($uri) {
$this->uri = $uri;
return $this;
@ -26,16 +48,29 @@ final class ArcanistDisplayRef
return $this->uri;
}
public function addChild(ArcanistRefView $view) {
$this->children[] = $view;
return $this;
}
private function getChildren() {
return $this->children;
}
public function appendLine($line) {
$this->lines[] = $line;
return $this;
}
public function newTerminalString() {
return $this->newLines(0);
}
private function newLines($indent) {
$ref = $this->getRef();
if ($ref instanceof ArcanistDisplayRefInterface) {
$object_name = $ref->getDisplayRefObjectName();
$title = $ref->getDisplayRefTitle();
} else {
$object_name = null;
$title = $ref->getRefDisplayName();
}
$object_name = $this->getObjectName();
$title = $this->getTitle();
if ($object_name !== null) {
$reserve_width = phutil_utf8_console_strlen($object_name) + 1;
@ -43,10 +78,18 @@ final class ArcanistDisplayRef
$reserve_width = 0;
}
if ($indent) {
$indent_text = str_repeat(' ', $indent);
} else {
$indent_text = '';
}
$indent_width = strlen($indent_text);
$marker_width = 6;
$display_width = phutil_console_get_terminal_width();
$usable_width = ($display_width - $marker_width - $reserve_width);
$usable_width = ($usable_width - $indent_width);
// If the terminal is extremely narrow, don't degrade so much that the
// output is completely unusable.
@ -69,20 +112,34 @@ final class ArcanistDisplayRef
$display_text = $title;
}
$ref = $this->getRef();
$output = array();
$output[] = tsprintf(
"<bg:cyan>** * **</bg> %s\n",
"<bg:cyan>** * **</bg> %s%s\n",
$indent_text,
$display_text);
$uri = $this->getURI();
if ($uri !== null) {
$output[] = tsprintf(
"<bg:cyan>** :// **</bg> __%s__\n",
"<bg:cyan>** :// **</bg> %s__%s__\n",
$indent_text,
$uri);
}
foreach ($this->lines as $line) {
$output[] = tsprintf(
" %s%s\n",
$indent_text,
$line);
}
foreach ($this->getChildren() as $child) {
foreach ($child->newLines($indent + 1) as $line) {
$output[] = $line;
}
}
return $output;
}

View file

@ -3,6 +3,7 @@
final class ArcanistRepositoryRef
extends ArcanistRef {
private $parameters = array();
private $phid;
private $browseURI;
@ -24,6 +25,37 @@ final class ArcanistRepositoryRef
return $this;
}
public static function newFromConduit(array $map) {
$ref = new self();
$ref->parameters = $map;
$ref->phid = $map['phid'];
return $ref;
}
public function getURIs() {
$uris = idxv($this->parameters, array('attachments', 'uris', 'uris'));
if (!$uris) {
return array();
}
$results = array();
foreach ($uris as $uri) {
$effective_uri = idxv($uri, array('fields', 'uri', 'effective'));
if ($effective_uri !== null) {
$results[] = $effective_uri;
}
}
return $results;
}
public function getDisplayName() {
return idxv($this->parameters, array('fields', 'name'));
}
public function newBrowseURI(array $params) {
PhutilTypeSpec::checkMap(
$params,
@ -67,9 +99,48 @@ final class ArcanistRepositoryRef
}
public function getDefaultBranch() {
// TODO: This should read from the remote, and is not correct for
// Mercurial anyway, as "default" would be a better default branch.
return 'master';
$branch = idxv($this->parameters, array('fields', 'defaultBranch'));
if ($branch === null) {
return 'master';
}
return $branch;
}
public function isPermanentRef(ArcanistMarkerRef $ref) {
$rules = idxv(
$this->parameters,
array('fields', 'refRules', 'permanentRefRules'));
if ($rules === null) {
return false;
}
// If the rules exist but there are no specified rules, treat every ref
// as permanent.
if (!$rules) {
return true;
}
// TODO: It would be nice to unify evaluation of permanent ref rules
// across Arcanist and Phabricator.
$ref_name = $ref->getName();
foreach ($rules as $rule) {
$matches = null;
if (preg_match('(^regexp\\((.*)\\)\z)', $rule, $matches)) {
if (preg_match($matches[1], $ref_name)) {
return true;
}
} else {
if ($rule === $ref_name) {
return true;
}
}
}
return false;
}
}

View file

@ -0,0 +1,44 @@
<?php
final class ArcanistBuildBuildplanHardpointQuery
extends ArcanistRuntimeHardpointQuery {
public function getHardpoints() {
return array(
ArcanistBuildRef::HARDPOINT_BUILDPLANREF,
);
}
protected function canLoadRef(ArcanistRef $ref) {
return ($ref instanceof ArcanistBuildRef);
}
public function loadHardpoint(array $refs, $hardpoint) {
$plan_phids = mpull($refs, 'getBuildPlanPHID');
$plan_phids = array_fuse($plan_phids);
$plan_phids = array_values($plan_phids);
$plans = (yield $this->yieldConduitSearch(
'harbormaster.buildplan.search',
array(
'phids' => $plan_phids,
)));
$plan_refs = array();
foreach ($plans as $plan) {
$plan_ref = ArcanistBuildPlanRef::newFromConduit($plan);
$plan_refs[] = $plan_ref;
}
$plan_refs = mpull($plan_refs, null, 'getPHID');
$results = array();
foreach ($refs as $key => $build_ref) {
$plan_phid = $build_ref->getBuildPlanPHID();
$plan = idx($plan_refs, $plan_phid);
$results[$key] = $plan;
}
yield $this->yieldMap($results);
}
}

View file

@ -0,0 +1,102 @@
<?php
final class ArcanistBuildRef
extends ArcanistRef {
const HARDPOINT_BUILDPLANREF = 'ref.build.buildplanRef';
private $parameters;
protected function newHardpoints() {
return array(
$this->newHardpoint(self::HARDPOINT_BUILDPLANREF),
);
}
public function getRefDisplayName() {
return pht('Build %d', $this->getID());
}
public static function newFromConduit(array $parameters) {
$ref = new self();
$ref->parameters = $parameters;
return $ref;
}
public function getID() {
return idx($this->parameters, 'id');
}
public function getPHID() {
return idx($this->parameters, 'phid');
}
public function getName() {
return idxv($this->parameters, array('fields', 'name'));
}
protected function buildRefView(ArcanistRefView $view) {
$view
->setObjectName($this->getRefDisplayName())
->setTitle($this->getName());
}
public function getBuildPlanRef() {
return $this->getHardpoint(self::HARDPOINT_BUILDPLANREF);
}
public function getBuildablePHID() {
return idxv($this->parameters, array('fields', 'buildablePHID'));
}
public function getBuildPlanPHID() {
return idxv($this->parameters, array('fields', 'buildPlanPHID'));
}
public function getStatus() {
return idxv($this->parameters, array('fields', 'buildStatus', 'value'));
}
public function getStatusName() {
return idxv($this->parameters, array('fields', 'buildStatus', 'name'));
}
public function getStatusANSIColor() {
return idxv(
$this->parameters,
array('fields', 'buildStatus', 'color.ansi'));
}
public function isComplete() {
switch ($this->getStatus()) {
case 'passed':
case 'failed':
case 'aborted':
case 'error':
case 'deadlocked':
return true;
default:
return false;
}
}
public function isPassed() {
return ($this->getStatus() === 'passed');
}
public function getStatusSortVector() {
$status = $this->getStatus();
// For now, just sort passed builds first.
if ($this->isPassed()) {
$status_class = 1;
} else {
$status_class = 2;
}
return id(new PhutilSortVector())
->addInt($status_class)
->addString($status);
}
}

View file

@ -0,0 +1,30 @@
<?php
final class ArcanistBuildSymbolRef
extends ArcanistSimpleSymbolRef {
public function getRefDisplayName() {
return pht('Build Symbol "%s"', $this->getSymbol());
}
protected function getSimpleSymbolPHIDType() {
return 'HMBD';
}
public function getSimpleSymbolConduitSearchMethodName() {
return 'harbormaster.build.search';
}
public function getSimpleSymbolConduitSearchAttachments() {
return array();
}
public function getSimpleSymbolInspectFunctionName() {
return 'build';
}
public function newSimpleSymbolObjectRef() {
return new ArcanistBuildRef();
}
}

View file

@ -0,0 +1,43 @@
<?php
final class ArcanistBuildableBuildsHardpointQuery
extends ArcanistRuntimeHardpointQuery {
public function getHardpoints() {
return array(
ArcanistBuildableRef::HARDPOINT_BUILDREFS,
);
}
protected function canLoadRef(ArcanistRef $ref) {
return ($ref instanceof ArcanistBuildableRef);
}
public function loadHardpoint(array $refs, $hardpoint) {
$buildable_phids = mpull($refs, 'getPHID');
$builds = (yield $this->yieldConduitSearch(
'harbormaster.build.search',
array(
'buildables' => $buildable_phids,
)));
$build_refs = array();
foreach ($builds as $build) {
$build_ref = ArcanistBuildRef::newFromConduit($build);
$build_refs[] = $build_ref;
}
$build_refs = mgroup($build_refs, 'getBuildablePHID');
$results = array();
foreach ($refs as $key => $buildable_ref) {
$buildable_phid = $buildable_ref->getPHID();
$buildable_builds = idx($build_refs, $buildable_phid, array());
$results[$key] = $buildable_builds;
}
yield $this->yieldMap($results);
}
}

View file

@ -0,0 +1,65 @@
<?php
final class ArcanistBuildableRef
extends ArcanistRef {
const HARDPOINT_BUILDREFS = 'ref.buildable.buildRefs';
private $parameters;
protected function newHardpoints() {
$object_list = new ArcanistObjectListHardpoint();
return array(
$this->newTemplateHardpoint(
self::HARDPOINT_BUILDREFS,
$object_list),
);
}
public function getRefDisplayName() {
return pht('Buildable "%s"', $this->getMonogram());
}
public static function newFromConduit(array $parameters) {
$ref = new self();
$ref->parameters = $parameters;
return $ref;
}
public function getID() {
return idx($this->parameters, 'id');
}
public function getPHID() {
return idx($this->parameters, 'phid');
}
public function getObjectPHID() {
return idxv($this->parameters, array('fields', 'objectPHID'));
}
public function getMonogram() {
return 'B'.$this->getID();
}
protected function buildRefView(ArcanistRefView $view) {
$view
->setObjectName($this->getMonogram())
->setTitle($this->getRefDisplayName());
}
public function getBuildRefs() {
return $this->getHardpoint(self::HARDPOINT_BUILDREFS);
}
public function getURI() {
$uri = idxv($this->parameters, array('fields', 'uri'));
if ($uri === null) {
$uri = '/'.$this->getMonogram();
}
return $uri;
}
}

View file

@ -0,0 +1,30 @@
<?php
final class ArcanistBuildableSymbolRef
extends ArcanistSimpleSymbolRef {
public function getRefDisplayName() {
return pht('Buildable Symbol "%s"', $this->getSymbol());
}
protected function getSimpleSymbolPHIDType() {
return 'HMBB';
}
public function getSimpleSymbolConduitSearchMethodName() {
return 'harbormaster.buildable.search';
}
public function getSimpleSymbolConduitSearchAttachments() {
return array();
}
public function getSimpleSymbolInspectFunctionName() {
return 'buildable';
}
public function newSimpleSymbolObjectRef() {
return new ArcanistBuildableRef();
}
}

View file

@ -0,0 +1,43 @@
<?php
final class ArcanistBuildPlanRef
extends ArcanistRef {
private $parameters;
public function getRefDisplayName() {
return pht('Build Plan %d', $this->getID());
}
public static function newFromConduit(array $parameters) {
$ref = new self();
$ref->parameters = $parameters;
return $ref;
}
public function getID() {
return idx($this->parameters, 'id');
}
public function getPHID() {
return idx($this->parameters, 'phid');
}
public function getName() {
return idxv($this->parameters, array('fields', 'name'));
}
public function getBehavior($behavior_key, $default = null) {
return idxv(
$this->parameters,
array('fields', 'behaviors', $behavior_key, 'value'),
$default);
}
protected function buildRefView(ArcanistRefView $view) {
$view
->setObjectName($this->getRefDisplayName())
->setTitle($this->getName());
}
}

View file

@ -0,0 +1,30 @@
<?php
final class ArcanistBuildPlanSymbolRef
extends ArcanistSimpleSymbolRef {
public function getRefDisplayName() {
return pht('Build Plan Symbol "%s"', $this->getSymbol());
}
protected function getSimpleSymbolPHIDType() {
return 'HMCP';
}
public function getSimpleSymbolConduitSearchMethodName() {
return 'harbormaster.buildplan.search';
}
public function getSimpleSymbolConduitSearchAttachments() {
return array();
}
public function getSimpleSymbolInspectFunctionName() {
return 'buildplan';
}
public function newSimpleSymbolObjectRef() {
return new ArcanistBuildPlanRef();
}
}

View file

@ -1,9 +1,7 @@
<?php
final class ArcanistFileRef
extends ArcanistRef
implements
ArcanistDisplayRefInterface {
extends ArcanistRef {
private $parameters;
@ -51,12 +49,10 @@ final class ArcanistFileRef
return 'F'.$this->getID();
}
public function getDisplayRefObjectName() {
return $this->getMonogram();
}
public function getDisplayRefTitle() {
return $this->getName();
protected function buildRefView(ArcanistRefView $view) {
$view
->setObjectName($this->getMonogram())
->setTitle($this->getName());
}
}

View file

@ -1,9 +1,7 @@
<?php
final class ArcanistPasteRef
extends ArcanistRef
implements
ArcanistDisplayRefInterface {
extends ArcanistRef {
private $parameters;
@ -47,12 +45,10 @@ final class ArcanistPasteRef
return 'P'.$this->getID();
}
public function getDisplayRefObjectName() {
return $this->getMonogram();
}
public function getDisplayRefTitle() {
return $this->getTitle();
protected function buildRefView(ArcanistRefView $view) {
$view
->setObjectName($this->getMonogram())
->setTitle($this->getTitle());
}
}

View file

@ -0,0 +1,35 @@
<?php
final class ArcanistRevisionAuthorHardpointQuery
extends ArcanistRuntimeHardpointQuery {
public function getHardpoints() {
return array(
ArcanistRevisionRef::HARDPOINT_AUTHORREF,
);
}
protected function canLoadRef(ArcanistRef $ref) {
return ($ref instanceof ArcanistRevisionRef);
}
public function loadHardpoint(array $refs, $hardpoint) {
$symbols = array();
foreach ($refs as $key => $ref) {
$symbols[$key] = id(new ArcanistUserSymbolRef())
->setSymbol($ref->getAuthorPHID());
}
yield $this->yieldRequests(
$symbols,
array(
ArcanistSymbolRef::HARDPOINT_OBJECT,
));
$results = mpull($symbols, 'getObject');
yield $this->yieldMap($results);
}
}

View file

@ -0,0 +1,60 @@
<?php
final class ArcanistRevisionBuildableHardpointQuery
extends ArcanistRuntimeHardpointQuery {
public function getHardpoints() {
return array(
ArcanistRevisionRef::HARDPOINT_BUILDABLEREF,
);
}
protected function canLoadRef(ArcanistRef $ref) {
return ($ref instanceof ArcanistRevisionRef);
}
public function loadHardpoint(array $refs, $hardpoint) {
$diff_map = array();
foreach ($refs as $key => $revision_ref) {
$diff_phid = $revision_ref->getDiffPHID();
if ($diff_phid) {
$diff_map[$key] = $diff_phid;
}
}
if (!$diff_map) {
yield $this->yieldValue($refs, null);
}
$buildables = (yield $this->yieldConduitSearch(
'harbormaster.buildable.search',
array(
'objectPHIDs' => array_values($diff_map),
'manual' => false,
)));
$buildable_refs = array();
foreach ($buildables as $buildable) {
$buildable_ref = ArcanistBuildableRef::newFromConduit($buildable);
$object_phid = $buildable_ref->getObjectPHID();
$buildable_refs[$object_phid] = $buildable_ref;
}
$results = array_fill_keys(array_keys($refs), null);
foreach ($refs as $key => $revision_ref) {
if (!isset($diff_map[$key])) {
continue;
}
$diff_phid = $diff_map[$key];
if (!isset($buildable_refs[$diff_phid])) {
continue;
}
$results[$key] = $buildable_refs[$diff_phid];
}
yield $this->yieldMap($results);
}
}

View file

@ -0,0 +1,82 @@
<?php
final class ArcanistRevisionParentRevisionsHardpointQuery
extends ArcanistRuntimeHardpointQuery {
public function getHardpoints() {
return array(
ArcanistRevisionRef::HARDPOINT_PARENTREVISIONREFS,
);
}
protected function canLoadRef(ArcanistRef $ref) {
return ($ref instanceof ArcanistRevisionRef);
}
public function loadHardpoint(array $refs, $hardpoint) {
$parameters = array(
'sourcePHIDs' => mpull($refs, 'getPHID'),
'types' => array(
'revision.parent',
),
);
$data = array();
while (true) {
$results = (yield $this->yieldConduit(
'edge.search',
$parameters));
foreach ($results['data'] as $item) {
$data[] = $item;
}
if ($results['cursor']['after'] === null) {
break;
}
$parameters['after'] = $results['cursor']['after'];
}
if (!$data) {
yield $this->yieldValue($refs, array());
}
$map = array();
$symbols = array();
foreach ($data as $edge) {
$src = $edge['sourcePHID'];
$dst = $edge['destinationPHID'];
$map[$src][$dst] = $dst;
$symbols[$dst] = id(new ArcanistRevisionSymbolRef())
->setSymbol($dst);
}
yield $this->yieldRequests(
$symbols,
array(
ArcanistSymbolRef::HARDPOINT_OBJECT,
));
$objects = array();
foreach ($symbols as $key => $symbol) {
$object = $symbol->getObject();
if ($object) {
$objects[$key] = $object;
}
}
$results = array_fill_keys(array_keys($refs), array());
foreach ($refs as $ref_key => $ref) {
$revision_phid = $ref->getPHID();
$parent_phids = idx($map, $revision_phid, array());
$parent_refs = array_select_keys($objects, $parent_phids);
$results[$ref_key] = $parent_refs;
}
yield $this->yieldMap($results);
}
}

View file

@ -1,11 +1,12 @@
<?php
final class ArcanistRevisionRef
extends ArcanistRef
implements
ArcanistDisplayRefInterface {
extends ArcanistRef {
const HARDPOINT_COMMITMESSAGE = 'ref.revision.commitmessage';
const HARDPOINT_AUTHORREF = 'ref.revision.authorRef';
const HARDPOINT_BUILDABLEREF = 'ref.revision.buildableRef';
const HARDPOINT_PARENTREVISIONREFS = 'ref.revision.parentRevisionRefs';
private $parameters;
private $sources = array();
@ -15,8 +16,14 @@ final class ArcanistRevisionRef
}
protected function newHardpoints() {
$object_list = new ArcanistObjectListHardpoint();
return array(
$this->newHardpoint(self::HARDPOINT_COMMITMESSAGE),
$this->newHardpoint(self::HARDPOINT_AUTHORREF),
$this->newHardpoint(self::HARDPOINT_BUILDABLEREF),
$this->newTemplateHardpoint(
self::HARDPOINT_PARENTREVISIONREFS,
$object_list),
);
}
@ -42,6 +49,32 @@ final class ArcanistRevisionRef
break;
}
$value_map = array(
'0' => 'needs-review',
'1' => 'needs-revision',
'2' => 'accepted',
'3' => 'published',
'4' => 'abandoned',
'5' => 'changes-planned',
);
$color_map = array(
'needs-review' => 'magenta',
'needs-revision' => 'red',
'accepted' => 'green',
'published' => 'cyan',
'abandoned' => null,
'changes-planned' => 'red',
);
$status_value = idx($value_map, idx($dict, 'status'));
$ansi_color = idx($color_map, $status_value);
$date_created = null;
if (isset($dict['dateCreated'])) {
$date_created = (int)$dict['dateCreated'];
}
$dict['fields'] = array(
'uri' => idx($dict, 'uri'),
'title' => idx($dict, 'title'),
@ -49,7 +82,10 @@ final class ArcanistRevisionRef
'status' => array(
'name' => $status_name,
'closed' => $is_closed,
'value' => $status_value,
'color.ansi' => $ansi_color,
),
'dateCreated' => $date_created,
);
return self::newFromConduit($dict);
@ -59,10 +95,54 @@ final class ArcanistRevisionRef
return 'D'.$this->getID();
}
public function getStatusShortDisplayName() {
if ($this->isStatusNeedsReview()) {
return pht('Review');
}
return idxv($this->parameters, array('fields', 'status', 'name'));
}
public function getStatusDisplayName() {
return idxv($this->parameters, array('fields', 'status', 'name'));
}
public function getStatusANSIColor() {
return idxv($this->parameters, array('fields', 'status', 'color.ansi'));
}
public function getDateCreated() {
return idxv($this->parameters, array('fields', 'dateCreated'));
}
public function isStatusChangesPlanned() {
$status = $this->getStatus();
return ($status === 'changes-planned');
}
public function isStatusAbandoned() {
$status = $this->getStatus();
return ($status === 'abandoned');
}
public function isStatusPublished() {
$status = $this->getStatus();
return ($status === 'published');
}
public function isStatusAccepted() {
$status = $this->getStatus();
return ($status === 'accepted');
}
public function isStatusNeedsReview() {
$status = $this->getStatus();
return ($status === 'needs-review');
}
public function getStatus() {
return idxv($this->parameters, array('fields', 'status', 'value'));
}
public function isClosed() {
return idxv($this->parameters, array('fields', 'status', 'closed'));
}
@ -93,6 +173,10 @@ final class ArcanistRevisionRef
return idx($this->parameters, 'phid');
}
public function getDiffPHID() {
return idxv($this->parameters, array('fields', 'diffPHID'));
}
public function getName() {
return idxv($this->parameters, array('fields', 'title'));
}
@ -114,12 +198,22 @@ final class ArcanistRevisionRef
return $this->getHardpoint(self::HARDPOINT_COMMITMESSAGE);
}
public function getDisplayRefObjectName() {
return $this->getMonogram();
public function getAuthorRef() {
return $this->getHardpoint(self::HARDPOINT_AUTHORREF);
}
public function getDisplayRefTitle() {
return $this->getName();
public function getParentRevisionRefs() {
return $this->getHardpoint(self::HARDPOINT_PARENTREVISIONREFS);
}
public function getBuildableRef() {
return $this->getHardpoint(self::HARDPOINT_BUILDABLEREF);
}
protected function buildRefView(ArcanistRefView $view) {
$view
->setObjectName($this->getMonogram())
->setTitle($this->getName());
}
}

View file

@ -22,6 +22,10 @@ abstract class ArcanistSimpleSymbolRef
$matches = null;
$prefix_pattern = $this->getSimpleSymbolPrefixPattern();
if ($prefix_pattern === null) {
$prefix_pattern = '';
}
$id_pattern = '(^'.$prefix_pattern.'([1-9]\d*)\z)';
$is_id = preg_match($id_pattern, $symbol, $matches);
@ -46,7 +50,10 @@ abstract class ArcanistSimpleSymbolRef
$symbol));
}
abstract protected function getSimpleSymbolPrefixPattern();
protected function getSimpleSymbolPrefixPattern() {
return null;
}
abstract protected function getSimpleSymbolPHIDType();
abstract public function getSimpleSymbolConduitSearchMethodName();
abstract public function getSimpleSymbolInspectFunctionName();

View file

@ -1,9 +1,7 @@
<?php
final class ArcanistTaskRef
extends ArcanistRef
implements
ArcanistDisplayRefInterface {
extends ArcanistRef {
private $parameters;
@ -33,12 +31,10 @@ final class ArcanistTaskRef
return 'T'.$this->getID();
}
public function getDisplayRefObjectName() {
return $this->getMonogram();
}
public function getDisplayRefTitle() {
return $this->getName();
protected function buildRefView(ArcanistRefView $view) {
$view
->setObjectName($this->getMonogram())
->setTitle($this->getName());
}
}

View file

@ -1,9 +1,7 @@
<?php
final class ArcanistUserRef
extends ArcanistRef
implements
ArcanistDisplayRefInterface {
extends ArcanistRef {
private $parameters;
@ -49,18 +47,16 @@ final class ArcanistUserRef
return idxv($this->parameters, array('fields', 'realName'));
}
public function getDisplayRefObjectName() {
return $this->getMonogram();
}
public function getDisplayRefTitle() {
protected function buildRefView(ArcanistRefView $view) {
$real_name = $this->getRealName();
if (strlen($real_name)) {
$real_name = sprintf('(%s)', $real_name);
}
return $real_name;
$view
->setObjectName($this->getMonogram())
->setTitle($real_name);
}
}

View file

@ -20,12 +20,11 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
protected function buildLocalFuture(array $argv) {
$argv[0] = 'git '.$argv[0];
$future = newv('ExecFuture', $argv);
$future->setCWD($this->getPath());
return $future;
return newv('ExecFuture', $argv)
->setCWD($this->getPath());
}
public function execPassthru($pattern /* , ... */) {
public function newPassthru($pattern /* , ... */) {
$args = func_get_args();
static $git = null;
@ -43,10 +42,10 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
$args[0] = $git.' '.$args[0];
return call_user_func_array('phutil_passthru', $args);
return newv('PhutilExecPassthru', $args)
->setCWD($this->getPath());
}
public function getSourceControlSystemName() {
return 'git';
}
@ -598,16 +597,16 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
public function getCanonicalRevisionName($string) {
$match = null;
if (preg_match('/@([0-9]+)$/', $string, $match)) {
$stdout = $this->getHashFromFromSVNRevisionNumber($match[1]);
} else {
list($stdout) = $this->execxLocal(
phutil_is_windows()
? 'show -s --format=%C %s --'
: 'show -s --format=%s %s --',
'show -s --format=%s %s --',
'%H',
$string);
}
return rtrim($stdout);
}
@ -1057,7 +1056,7 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
*
* @return list<dict<string, string>> Dictionary of branch information.
*/
public function getAllBranches() {
private function getAllBranches() {
$field_list = array(
'%(refname)',
'%(objectname)',
@ -1103,27 +1102,6 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
return $result;
}
public function getAllBranchRefs() {
$branches = $this->getAllBranches();
$refs = array();
foreach ($branches as $branch) {
$commit_ref = $this->newCommitRef()
->setCommitHash($branch['hash'])
->setTreeHash($branch['tree'])
->setCommitEpoch($branch['epoch'])
->attachMessage($branch['text']);
$refs[] = $this->newBranchRef()
->setBranchName($branch['name'])
->setRefName($branch['ref'])
->setIsCurrentBranch($branch['current'])
->attachCommitRef($commit_ref);
}
return $refs;
}
public function getBaseCommitRef() {
$base_commit = $this->getBaseCommit();
@ -1564,6 +1542,11 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
return ($uri !== null);
}
public function isFetchableRemote($remote_name) {
$uri = $this->getGitRemoteFetchURI($remote_name);
return ($uri !== null);
}
private function getGitRemoteFetchURI($remote_name) {
return $this->getGitRemoteURI($remote_name, $for_push = false);
}
@ -1739,4 +1722,102 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
return false;
}
protected function newLandEngine() {
return new ArcanistGitLandEngine();
}
protected function newWorkEngine() {
return new ArcanistGitWorkEngine();
}
public function newLocalState() {
return id(new ArcanistGitLocalState())
->setRepositoryAPI($this);
}
public function readRawCommit($hash) {
list($stdout) = $this->execxLocal(
'cat-file commit -- %s',
$hash);
return ArcanistGitRawCommit::newFromRawBlob($stdout);
}
public function writeRawCommit(ArcanistGitRawCommit $commit) {
$blob = $commit->getRawBlob();
$future = $this->execFutureLocal('hash-object -t commit --stdin -w');
$future->write($blob);
list($stdout) = $future->resolvex();
return trim($stdout);
}
protected function newSupportedMarkerTypes() {
return array(
ArcanistMarkerRef::TYPE_BRANCH,
);
}
protected function newMarkerRefQueryTemplate() {
return new ArcanistGitRepositoryMarkerQuery();
}
protected function newRemoteRefQueryTemplate() {
return new ArcanistGitRepositoryRemoteQuery();
}
protected function newNormalizedURI($uri) {
return new ArcanistRepositoryURINormalizer(
ArcanistRepositoryURINormalizer::TYPE_GIT,
$uri);
}
protected function newPublishedCommitHashes() {
$remotes = $this->newRemoteRefQuery()
->execute();
if (!$remotes) {
return array();
}
$markers = $this->newMarkerRefQuery()
->withIsRemoteCache(true)
->execute();
if (!$markers) {
return array();
}
$runtime = $this->getRuntime();
$workflow = $runtime->getCurrentWorkflow();
$workflow->loadHardpoints(
$remotes,
ArcanistRemoteRef::HARDPOINT_REPOSITORYREFS);
$remotes = mpull($remotes, null, 'getRemoteName');
$hashes = array();
foreach ($markers as $marker) {
$remote_name = $marker->getRemoteName();
$remote = idx($remotes, $remote_name);
if (!$remote) {
continue;
}
if (!$remote->isPermanentRef($marker)) {
continue;
}
$hashes[] = $marker->getCommitHash();
}
return $hashes;
}
protected function newCommitGraphQueryTemplate() {
return new ArcanistGitCommitGraphQuery();
}
}

View file

@ -9,8 +9,8 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
private $localCommitInfo;
private $rawDiffCache = array();
private $supportsRebase;
private $supportsPhases;
private $featureResults = array();
private $featureFutures = array();
protected function buildLocalFuture(array $argv) {
$env = $this->getMercurialEnvironmentVariables();
@ -24,18 +24,16 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
return $future;
}
public function execPassthru($pattern /* , ... */) {
public function newPassthru($pattern /* , ... */) {
$args = func_get_args();
$env = $this->getMercurialEnvironmentVariables();
$args[0] = 'hg '.$args[0];
$passthru = newv('PhutilExecPassthru', $args)
return newv('PhutilExecPassthru', $args)
->setEnv($env)
->setCWD($this->getPath());
return $passthru->resolve();
}
public function getSourceControlSystemName() {
@ -51,42 +49,11 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
}
public function getCanonicalRevisionName($string) {
$match = null;
if ($this->isHgSubversionRepo() &&
preg_match('/@([0-9]+)$/', $string, $match)) {
$string = hgsprintf('svnrev(%s)', $match[1]);
}
list($stdout) = $this->execxLocal(
'log -l 1 --template %s -r %s --',
'{node}',
$string);
return $stdout;
}
public function getHashFromFromSVNRevisionNumber($revision_id) {
$matches = array();
$string = hgsprintf('svnrev(%s)', $revision_id);
list($stdout) = $this->execxLocal(
'log -l 1 --template %s -r %s --',
'{node}',
$string);
if (!$stdout) {
throw new ArcanistUsageException(
pht('Cannot find the HG equivalent of %s given.', $revision_id));
}
return $stdout;
}
public function getSVNRevisionNumberFromHash($hash) {
$matches = array();
list($stdout) = $this->execxLocal(
'log -r %s --template {svnrev}', $hash);
if (!$stdout) {
throw new ArcanistUsageException(
pht('Cannot find the SVN equivalent of %s given.', $hash));
}
return $stdout;
}
@ -144,19 +111,10 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
return $base;
}
// Mercurial 2.1 and up have phases which indicate if something is
// published or not. To find which revs are outgoing, it's much
// faster to check the phase instead of actually checking the server.
if ($this->supportsPhases()) {
list($err, $stdout) = $this->execManualLocal(
'log --branch %s -r %s --style default',
$this->getBranchName(),
'draft()');
} else {
list($err, $stdout) = $this->execManualLocal(
'outgoing --branch %s --style default',
$this->getBranchName());
}
list($err, $stdout) = $this->execManualLocal(
'log --branch %s -r %s --style default',
$this->getBranchName(),
'draft()');
if (!$err) {
$logs = ArcanistMercurialParser::parseMercurialLog($stdout);
@ -507,24 +465,6 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
}
}
public function supportsRebase() {
if ($this->supportsRebase === null) {
list($err) = $this->execManualLocal('help rebase');
$this->supportsRebase = $err === 0;
}
return $this->supportsRebase;
}
public function supportsPhases() {
if ($this->supportsPhases === null) {
list($err) = $this->execManualLocal('help phase');
$this->supportsPhases = $err === 0;
}
return $this->supportsPhases;
}
public function supportsCommitRanges() {
return true;
}
@ -533,43 +473,6 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
return true;
}
public function getAllBranches() {
list($branch_info) = $this->execxLocal('bookmarks');
if (trim($branch_info) == 'no bookmarks set') {
return array();
}
$matches = null;
preg_match_all(
'/^\s*(\*?)\s*(.+)\s(\S+)$/m',
$branch_info,
$matches,
PREG_SET_ORDER);
$return = array();
foreach ($matches as $match) {
list(, $current, $name) = $match;
$return[] = array(
'current' => (bool)$current,
'name' => rtrim($name),
);
}
return $return;
}
public function getAllBranchRefs() {
$branches = $this->getAllBranches();
$refs = array();
foreach ($branches as $branch) {
$refs[] = $this->newBranchRef()
->setBranchName($branch['name'])
->setIsCurrentBranch($branch['current']);
}
return $refs;
}
public function getBaseCommitRef() {
$base_commit = $this->getBaseCommit();
@ -955,10 +858,6 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
}
public function isHgSubversionRepo() {
return file_exists($this->getPath('.hg/svn/rev_map'));
}
public function getSubversionInfo() {
$info = array();
$base_path = null;
@ -990,96 +889,21 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
}
public function getActiveBookmark() {
$bookmarks = $this->getBookmarks();
foreach ($bookmarks as $bookmark) {
if ($bookmark['is_active']) {
return $bookmark['name'];
}
$bookmark = $this->newMarkerRefQuery()
->withMarkerTypes(ArcanistMarkerRef::TYPE_BOOKMARK)
->withIsActive(true)
->executeOne();
if (!$bookmark) {
return null;
}
return null;
}
public function isBookmark($name) {
$bookmarks = $this->getBookmarks();
foreach ($bookmarks as $bookmark) {
if ($bookmark['name'] === $name) {
return true;
}
}
return false;
}
public function isBranch($name) {
$branches = $this->getBranches();
foreach ($branches as $branch) {
if ($branch['name'] === $name) {
return true;
}
}
return false;
}
public function getBranches() {
list($stdout) = $this->execxLocal('--debug branches');
$lines = ArcanistMercurialParser::parseMercurialBranches($stdout);
$branches = array();
foreach ($lines as $name => $spec) {
$branches[] = array(
'name' => $name,
'revision' => $spec['rev'],
);
}
return $branches;
}
public function getBookmarks() {
$bookmarks = array();
list($raw_output) = $this->execxLocal('bookmarks');
$raw_output = trim($raw_output);
if ($raw_output !== 'no bookmarks set') {
foreach (explode("\n", $raw_output) as $line) {
// example line: * mybook 2:6b274d49be97
list($name, $revision) = $this->splitBranchOrBookmarkLine($line);
$is_active = false;
if ('*' === $name[0]) {
$is_active = true;
$name = substr($name, 2);
}
$bookmarks[] = array(
'is_active' => $is_active,
'name' => $name,
'revision' => $revision,
);
}
}
return $bookmarks;
}
private function splitBranchOrBookmarkLine($line) {
// branches and bookmarks are printed in the format:
// default 0:a5ead76cdf85 (inactive)
// * mybook 2:6b274d49be97
// this code divides the name half from the revision half
// it does not parse the * and (inactive) bits
$colon_index = strrpos($line, ':');
$before_colon = substr($line, 0, $colon_index);
$start_rev_index = strrpos($before_colon, ' ');
$name = substr($line, 0, $start_rev_index);
$rev = substr($line, $start_rev_index);
return array(trim($name), trim($rev));
return $bookmark->getName();
}
public function getRemoteURI() {
// TODO: Remove this method in favor of RemoteRefQuery.
list($stdout) = $this->execxLocal('paths default');
$stdout = trim($stdout);
@ -1108,4 +932,127 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
return $env;
}
protected function newLandEngine() {
return new ArcanistMercurialLandEngine();
}
protected function newWorkEngine() {
return new ArcanistMercurialWorkEngine();
}
public function newLocalState() {
return id(new ArcanistMercurialLocalState())
->setRepositoryAPI($this);
}
public function willTestMercurialFeature($feature) {
$this->executeMercurialFeatureTest($feature, false);
return $this;
}
public function getMercurialFeature($feature) {
return $this->executeMercurialFeatureTest($feature, true);
}
private function executeMercurialFeatureTest($feature, $resolve) {
if (array_key_exists($feature, $this->featureResults)) {
return $this->featureResults[$feature];
}
if (!array_key_exists($feature, $this->featureFutures)) {
$future = $this->newMercurialFeatureFuture($feature);
$future->start();
$this->featureFutures[$feature] = $future;
}
if (!$resolve) {
return;
}
$future = $this->featureFutures[$feature];
$result = $this->resolveMercurialFeatureFuture($feature, $future);
$this->featureResults[$feature] = $result;
return $result;
}
private function newMercurialFeatureFuture($feature) {
switch ($feature) {
case 'shelve':
return $this->execFutureLocal(
'--config extensions.shelve= shelve --help --');
case 'evolve':
return $this->execFutureLocal('prune --help --');
default:
throw new Exception(
pht(
'Unknown Mercurial feature "%s".',
$feature));
}
}
private function resolveMercurialFeatureFuture($feature, $future) {
// By default, assume the feature is a simple capability test and the
// capability is present if the feature resolves without an error.
list($err) = $future->resolve();
return !$err;
}
protected function newSupportedMarkerTypes() {
return array(
ArcanistMarkerRef::TYPE_BRANCH,
ArcanistMarkerRef::TYPE_BOOKMARK,
);
}
protected function newMarkerRefQueryTemplate() {
return new ArcanistMercurialRepositoryMarkerQuery();
}
protected function newRemoteRefQueryTemplate() {
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,
$uri);
}
protected function newCommitGraphQueryTemplate() {
return new ArcanistMercurialCommitGraphQuery();
}
protected function newPublishedCommitHashes() {
$future = $this->newFuture(
'log --rev %s --template %s',
hgsprintf('parents(draft()) - draft()'),
'{node}\n');
list($lines) = $future->resolve();
$lines = phutil_split_lines($lines, false);
$hashes = array();
foreach ($lines as $line) {
if (!strlen(trim($line))) {
continue;
}
$hashes[] = $line;
}
return $hashes;
}
}

View file

@ -42,6 +42,7 @@ abstract class ArcanistRepositoryAPI extends Phobject {
private $runtime;
private $currentWorkingCopyStateRef = false;
private $currentCommitRef = false;
private $graph;
abstract public function getSourceControlSystemName();
@ -369,15 +370,6 @@ abstract class ArcanistRepositoryAPI extends Phobject {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function getAllBranches() {
// TODO: Implement for Mercurial/SVN and make abstract.
return array();
}
public function getAllBranchRefs() {
throw new ArcanistCapabilityNotSupportedException($this);
}
public function getBaseCommitRef() {
throw new ArcanistCapabilityNotSupportedException($this);
}
@ -675,6 +667,20 @@ abstract class ArcanistRepositoryAPI extends Phobject {
->setResolveOnError(false);
}
public function newPassthru($pattern /* , ... */) {
throw new PhutilMethodNotImplementedException();
}
final public function execPassthru($pattern /* , ... */) {
$args = func_get_args();
$future = call_user_func_array(
array($this, 'newPassthru'),
$args);
return $future->resolve();
}
final public function setRuntime(ArcanistRuntime $runtime) {
$this->runtime = $runtime;
return $this;
@ -731,7 +737,101 @@ abstract class ArcanistRepositoryAPI extends Phobject {
return new ArcanistCommitRef();
}
final public function newBranchRef() {
return new ArcanistBranchRef();
final public function newMarkerRef() {
return new ArcanistMarkerRef();
}
final public function getLandEngine() {
$engine = $this->newLandEngine();
if ($engine) {
$engine->setRepositoryAPI($this);
}
return $engine;
}
protected function newLandEngine() {
return null;
}
final public function getWorkEngine() {
$engine = $this->newWorkEngine();
if ($engine) {
$engine->setRepositoryAPI($this);
}
return $engine;
}
protected function newWorkEngine() {
return null;
}
final public function getSupportedMarkerTypes() {
return $this->newSupportedMarkerTypes();
}
protected function newSupportedMarkerTypes() {
return array();
}
final public function newMarkerRefQuery() {
return id($this->newMarkerRefQueryTemplate())
->setRepositoryAPI($this);
}
protected function newMarkerRefQueryTemplate() {
throw new PhutilMethodNotImplementedException();
}
final public function newRemoteRefQuery() {
return id($this->newRemoteRefQueryTemplate())
->setRepositoryAPI($this);
}
protected function newRemoteRefQueryTemplate() {
throw new PhutilMethodNotImplementedException();
}
final public function newCommitGraphQuery() {
return id($this->newCommitGraphQueryTemplate());
}
protected function newCommitGraphQueryTemplate() {
throw new PhutilMethodNotImplementedException();
}
final public function getDisplayHash($hash) {
return substr($hash, 0, 12);
}
final public function getNormalizedURI($uri) {
$normalized_uri = $this->newNormalizedURI($uri);
return $normalized_uri->getNormalizedURI();
}
protected function newNormalizedURI($uri) {
return $uri;
}
final public function getPublishedCommitHashes() {
return $this->newPublishedCommitHashes();
}
protected function newPublishedCommitHashes() {
return array();
}
final public function getGraph() {
if (!$this->graph) {
$this->graph = id(new ArcanistCommitGraph())
->setRepositoryAPI($this);
}
return $this->graph;
}
}

View file

@ -0,0 +1,55 @@
<?php
final class ArcanistCommitGraph
extends Phobject {
private $repositoryAPI;
private $nodes = array();
public function setRepositoryAPI(ArcanistRepositoryAPI $api) {
$this->repositoryAPI = $api;
return $this;
}
public function getRepositoryAPI() {
return $this->repositoryAPI;
}
public function getNode($hash) {
if (isset($this->nodes[$hash])) {
return $this->nodes[$hash];
} else {
return null;
}
}
public function getNodes() {
return $this->nodes;
}
public function newQuery() {
$api = $this->getRepositoryAPI();
return $api->newCommitGraphQuery()
->setGraph($this);
}
public function newNode($hash) {
if (isset($this->nodes[$hash])) {
throw new Exception(
pht(
'Graph already has a node "%s"!',
$hash));
}
$this->nodes[$hash] = id(new ArcanistCommitNode())
->setCommitHash($hash);
return $this->nodes[$hash];
}
public function newPartitionQuery() {
return id(new ArcanistCommitGraphPartitionQuery())
->setGraph($this);
}
}

View file

@ -0,0 +1,62 @@
<?php
final class ArcanistCommitGraphPartition
extends Phobject {
private $graph;
private $hashes = array();
private $heads = array();
private $tails = array();
private $waypoints = array();
public function setGraph(ArcanistCommitGraph $graph) {
$this->graph = $graph;
return $this;
}
public function getGraph() {
return $this->graph;
}
public function setHashes(array $hashes) {
$this->hashes = $hashes;
return $this;
}
public function getHashes() {
return $this->hashes;
}
public function setHeads(array $heads) {
$this->heads = $heads;
return $this;
}
public function getHeads() {
return $this->heads;
}
public function setTails($tails) {
$this->tails = $tails;
return $this;
}
public function getTails() {
return $this->tails;
}
public function setWaypoints($waypoints) {
$this->waypoints = $waypoints;
return $this;
}
public function getWaypoints() {
return $this->waypoints;
}
public function newSetQuery() {
return id(new ArcanistCommitGraphSetQuery())
->setPartition($this);
}
}

View file

@ -0,0 +1,153 @@
<?php
final class ArcanistCommitGraphPartitionQuery
extends Phobject {
private $graph;
private $heads;
private $hashes;
public function setGraph(ArcanistCommitGraph $graph) {
$this->graph = $graph;
return $this;
}
public function getGraph() {
return $this->graph;
}
public function withHeads(array $heads) {
$this->heads = $heads;
return $this;
}
public function withHashes(array $hashes) {
$this->hashes = $hashes;
return $this;
}
public function execute() {
$graph = $this->getGraph();
$heads = $this->heads;
$heads = array_fuse($heads);
if (!$heads) {
throw new Exception(pht('Partition query requires heads.'));
}
$waypoints = $heads;
$stack = array();
$partitions = array();
$partition_identities = array();
$n = 0;
foreach ($heads as $hash) {
$node = $graph->getNode($hash);
if (!$node) {
echo "TODO: WARNING: Bad hash {$hash}\n";
continue;
}
$partitions[$hash] = $n;
$partition_identities[$n] = array($n => $n);
$n++;
$stack[] = $node;
}
$scope = null;
if ($this->hashes) {
$scope = array_fuse($this->hashes);
}
$leaves = array();
while ($stack) {
$node = array_pop($stack);
$node_hash = $node->getCommitHash();
$node_partition = $partition_identities[$partitions[$node_hash]];
$saw_parent = false;
foreach ($node->getParentNodes() as $parent) {
$parent_hash = $parent->getCommitHash();
if ($scope !== null) {
if (!isset($scope[$parent_hash])) {
continue;
}
}
$saw_parent = true;
if (isset($partitions[$parent_hash])) {
$parent_partition = $partition_identities[$partitions[$parent_hash]];
// If we've reached this node from a child, it clearly is not a
// head.
unset($heads[$parent_hash]);
// If we've reached a node which is already part of another
// partition, we can stop following it and merge the partitions.
$new_partition = $node_partition + $parent_partition;
ksort($new_partition);
if ($node_partition !== $new_partition) {
foreach ($node_partition as $partition_id) {
$partition_identities[$partition_id] = $new_partition;
}
}
if ($parent_partition !== $new_partition) {
foreach ($parent_partition as $partition_id) {
$partition_identities[$partition_id] = $new_partition;
}
}
continue;
} else {
$partitions[$parent_hash] = $partitions[$node_hash];
}
$stack[] = $parent;
}
if (!$saw_parent) {
$leaves[$node_hash] = true;
}
}
$partition_lists = array();
$partition_heads = array();
$partition_waypoints = array();
$partition_leaves = array();
foreach ($partitions as $hash => $partition) {
$partition = reset($partition_identities[$partition]);
$partition_lists[$partition][] = $hash;
if (isset($heads[$hash])) {
$partition_heads[$partition][] = $hash;
}
if (isset($waypoints[$hash])) {
$partition_waypoints[$partition][] = $hash;
}
if (isset($leaves[$hash])) {
$partition_leaves[$partition][] = $hash;
}
}
$results = array();
foreach ($partition_lists as $partition_id => $partition_list) {
$partition_set = array_fuse($partition_list);
$results[] = id(new ArcanistCommitGraphPartition())
->setGraph($graph)
->setHashes($partition_set)
->setHeads($partition_heads[$partition_id])
->setWaypoints($partition_waypoints[$partition_id])
->setTails($partition_leaves[$partition_id]);
}
return $results;
}
}

View file

@ -0,0 +1,97 @@
<?php
final class ArcanistCommitGraphSet
extends Phobject {
private $setID;
private $color;
private $hashes;
private $parentHashes;
private $childHashes;
private $parentSets;
private $childSets;
private $displayDepth;
private $displayChildSets;
public function setColor($color) {
$this->color = $color;
return $this;
}
public function getColor() {
return $this->color;
}
public function setHashes($hashes) {
$this->hashes = $hashes;
return $this;
}
public function getHashes() {
return $this->hashes;
}
public function setSetID($set_id) {
$this->setID = $set_id;
return $this;
}
public function getSetID() {
return $this->setID;
}
public function setParentHashes($parent_hashes) {
$this->parentHashes = $parent_hashes;
return $this;
}
public function getParentHashes() {
return $this->parentHashes;
}
public function setChildHashes($child_hashes) {
$this->childHashes = $child_hashes;
return $this;
}
public function getChildHashes() {
return $this->childHashes;
}
public function setParentSets($parent_sets) {
$this->parentSets = $parent_sets;
return $this;
}
public function getParentSets() {
return $this->parentSets;
}
public function setChildSets($child_sets) {
$this->childSets = $child_sets;
return $this;
}
public function getChildSets() {
return $this->childSets;
}
public function setDisplayDepth($display_depth) {
$this->displayDepth = $display_depth;
return $this;
}
public function getDisplayDepth() {
return $this->displayDepth;
}
public function setDisplayChildSets(array $display_child_sets) {
$this->displayChildSets = $display_child_sets;
return $this;
}
public function getDisplayChildSets() {
return $this->displayChildSets;
}
}

View file

@ -0,0 +1,305 @@
<?php
final class ArcanistCommitGraphSetQuery
extends Phobject {
private $partition;
private $waypointMap;
private $visitedDisplaySets;
public function setPartition($partition) {
$this->partition = $partition;
return $this;
}
public function getPartition() {
return $this->partition;
}
public function setWaypointMap(array $waypoint_map) {
$this->waypointMap = $waypoint_map;
return $this;
}
public function getWaypointMap() {
return $this->waypointMap;
}
public function execute() {
$partition = $this->getPartition();
$graph = $partition->getGraph();
$waypoint_color = array();
$color = array();
$waypoints = $this->getWaypointMap();
foreach ($waypoints as $waypoint => $colors) {
// TODO: Validate that "$waypoint" is in the partition.
// TODO: Validate that "$colors" is a list of scalars.
$waypoint_color[$waypoint] = $this->newColorFromRaw($colors);
}
$stack = array();
$hashes = $partition->getTails();
foreach ($hashes as $hash) {
$stack[] = $graph->getNode($hash);
if (isset($waypoint_color[$hash])) {
$color[$hash] = $waypoint_color[$hash];
} else {
$color[$hash] = true;
}
}
$partition_map = $partition->getHashes();
$wait = array();
foreach ($partition_map as $hash) {
$node = $graph->getNode($hash);
$incoming = $node->getParentNodes();
if (count($incoming) < 2) {
// If the node has one or fewer incoming edges, we can paint it as soon
// as we reach it.
continue;
}
// Discard incoming edges which aren't in the partition.
$need = array();
foreach ($incoming as $incoming_node) {
$incoming_hash = $incoming_node->getCommitHash();
if (!isset($partition_map[$incoming_hash])) {
continue;
}
$need[] = $incoming_hash;
}
$need_count = count($need);
if ($need_count < 2) {
// If we have one or fewer incoming edges in the partition, we can
// paint as soon as we reach the node.
continue;
}
$wait[$hash] = $need_count;
}
while ($stack) {
$node = array_pop($stack);
$node_hash = $node->getCommitHash();
$node_color = $color[$node_hash];
$outgoing_nodes = $node->getChildNodes();
foreach ($outgoing_nodes as $outgoing_node) {
$outgoing_hash = $outgoing_node->getCommitHash();
if (isset($waypoint_color[$outgoing_hash])) {
$color[$outgoing_hash] = $waypoint_color[$outgoing_hash];
} else if (isset($color[$outgoing_hash])) {
$color[$outgoing_hash] = $this->newColorFromColors(
$color[$outgoing_hash],
$node_color);
} else {
$color[$outgoing_hash] = $node_color;
}
if (isset($wait[$outgoing_hash])) {
$wait[$outgoing_hash]--;
if ($wait[$outgoing_hash]) {
continue;
}
unset($wait[$outgoing_hash]);
}
$stack[] = $outgoing_node;
}
}
if ($wait) {
throw new Exception(
pht(
'Did not reach every wait node??'));
}
// Now, we've colored the entire graph. Collect contiguous pieces of it
// with the same color into sets.
static $set_n = 1;
$seen = array();
$sets = array();
foreach ($color as $hash => $node_color) {
if (isset($seen[$hash])) {
continue;
}
$seen[$hash] = true;
$in_set = array();
$in_set[$hash] = true;
$stack = array();
$stack[] = $graph->getNode($hash);
while ($stack) {
$node = array_pop($stack);
$node_hash = $node->getCommitHash();
$nearby = array();
foreach ($node->getParentNodes() as $nearby_node) {
$nearby[] = $nearby_node;
}
foreach ($node->getChildNodes() as $nearby_node) {
$nearby[] = $nearby_node;
}
foreach ($nearby as $nearby_node) {
$nearby_hash = $nearby_node->getCommitHash();
if (isset($seen[$nearby_hash])) {
continue;
}
if (idx($color, $nearby_hash) !== $node_color) {
continue;
}
$seen[$nearby_hash] = true;
$in_set[$nearby_hash] = true;
$stack[] = $nearby_node;
}
}
$set = id(new ArcanistCommitGraphSet())
->setSetID($set_n++)
->setColor($node_color)
->setHashes(array_keys($in_set));
$sets[] = $set;
}
$set_map = array();
foreach ($sets as $set) {
foreach ($set->getHashes() as $hash) {
$set_map[$hash] = $set;
}
}
foreach ($sets as $set) {
$parents = array();
$children = array();
foreach ($set->getHashes() as $hash) {
$node = $graph->getNode($hash);
foreach ($node->getParentNodes() as $edge => $ignored) {
if (isset($set_map[$edge])) {
if ($set_map[$edge] === $set) {
continue;
}
}
$parents[$edge] = true;
}
foreach ($node->getChildNodes() as $edge => $ignored) {
if (isset($set_map[$edge])) {
if ($set_map[$edge] === $set) {
continue;
}
}
$children[$edge] = true;
}
$parent_sets = array();
foreach ($parents as $edge => $ignored) {
if (!isset($set_map[$edge])) {
continue;
}
$adjacent_set = $set_map[$edge];
$parent_sets[$adjacent_set->getSetID()] = $adjacent_set;
}
$child_sets = array();
foreach ($children as $edge => $ignored) {
if (!isset($set_map[$edge])) {
continue;
}
$adjacent_set = $set_map[$edge];
$child_sets[$adjacent_set->getSetID()] = $adjacent_set;
}
}
$set
->setParentHashes(array_keys($parents))
->setChildHashes(array_keys($children))
->setParentSets($parent_sets)
->setChildSets($child_sets);
}
$this->buildDisplayLayout($sets);
return $sets;
}
private function newColorFromRaw($color) {
return array_fuse($color);
}
private function newColorFromColors($u, $v) {
if ($u === true) {
return $v;
}
if ($v === true) {
return $u;
}
return $u + $v;
}
private function buildDisplayLayout(array $sets) {
$this->visitedDisplaySets = array();
foreach ($sets as $set) {
if (!$set->getParentSets()) {
$this->visitDisplaySet($set);
}
}
}
private function visitDisplaySet(ArcanistCommitGraphSet $set) {
// If at least one parent has not been visited yet, don't visit this
// set. We want to put the set at the deepest depth it is reachable
// from.
foreach ($set->getParentSets() as $parent_id => $parent_set) {
if (!isset($this->visitedDisplaySets[$parent_id])) {
return false;
}
}
$set_id = $set->getSetID();
$this->visitedDisplaySets[$set_id] = true;
$display_children = array();
foreach ($set->getChildSets() as $child_id => $child_set) {
$visited = $this->visitDisplaySet($child_set);
if ($visited) {
$display_children[$child_id] = $child_set;
}
}
$set->setDisplayChildSets($display_children);
return true;
}
}

View file

@ -0,0 +1,78 @@
<?php
final class ArcanistCommitNode
extends Phobject {
private $commitHash;
private $childNodes = array();
private $parentNodes = array();
private $commitRef;
private $commitMessage;
private $commitEpoch;
public function setCommitHash($commit_hash) {
$this->commitHash = $commit_hash;
return $this;
}
public function getCommitHash() {
return $this->commitHash;
}
public function addChildNode(ArcanistCommitNode $node) {
$this->childNodes[$node->getCommitHash()] = $node;
return $this;
}
public function setChildNodes(array $nodes) {
$this->childNodes = $nodes;
return $this;
}
public function getChildNodes() {
return $this->childNodes;
}
public function addParentNode(ArcanistCommitNode $node) {
$this->parentNodes[$node->getCommitHash()] = $node;
return $this;
}
public function setParentNodes(array $nodes) {
$this->parentNodes = $nodes;
return $this;
}
public function getParentNodes() {
return $this->parentNodes;
}
public function setCommitMessage($commit_message) {
$this->commitMessage = $commit_message;
return $this;
}
public function getCommitMessage() {
return $this->commitMessage;
}
public function getCommitRef() {
if ($this->commitRef === null) {
$this->commitRef = id(new ArcanistCommitRef())
->setCommitHash($this->getCommitHash())
->attachMessage($this->getCommitMessage());
}
return $this->commitRef;
}
public function setCommitEpoch($commit_epoch) {
$this->commitEpoch = $commit_epoch;
return $this;
}
public function getCommitEpoch() {
return $this->commitEpoch;
}
}

View file

@ -0,0 +1,56 @@
<?php
final class ArcanistCommitGraphTestCase
extends PhutilTestCase {
public function testGraphQuery() {
$this->assertPartitionCount(
1,
pht('Simple Graph'),
array('D'),
'A>B B>C C>D');
$this->assertPartitionCount(
1,
pht('Multiple Heads'),
array('D', 'E'),
'A>B B>C C>D C>E');
$this->assertPartitionCount(
1,
pht('Disjoint Graph, One Head'),
array('B'),
'A>B C>D');
$this->assertPartitionCount(
2,
pht('Disjoint Graph, Two Heads'),
array('B', 'D'),
'A>B C>D');
$this->assertPartitionCount(
1,
pht('Complex Graph'),
array('A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K'),
'A>B B>C B>D B>E E>F E>G E>H C>H A>I C>I B>J J>K I>K');
}
private function assertPartitionCount($expect, $name, $heads, $corpus) {
$graph = new ArcanistCommitGraph();
$query = id(new ArcanistSimpleCommitGraphQuery())
->setGraph($graph);
$query->setCorpus($corpus)->execute();
$partitions = $graph->newPartitionQuery()
->withHeads($heads)
->execute();
$this->assertEqual(
$expect,
count($partitions),
pht('Partition Count for "%s"', $name));
}
}

View file

@ -0,0 +1,79 @@
<?php
abstract class ArcanistCommitGraphQuery
extends Phobject {
private $graph;
private $headHashes;
private $tailHashes;
private $exactHashes;
private $limit;
private $minimumEpoch;
private $maximumEpoch;
final public function setGraph(ArcanistCommitGraph $graph) {
$this->graph = $graph;
return $this;
}
final public function getGraph() {
return $this->graph;
}
final public function withHeadHashes(array $hashes) {
$this->headHashes = $hashes;
return $this;
}
final protected function getHeadHashes() {
return $this->headHashes;
}
final public function withTailHashes(array $hashes) {
$this->tailHashes = $hashes;
return $this;
}
final protected function getTailHashes() {
return $this->tailHashes;
}
final public function withExactHashes(array $hashes) {
$this->exactHashes = $hashes;
return $this;
}
final protected function getExactHashes() {
return $this->exactHashes;
}
final public function setLimit($limit) {
$this->limit = $limit;
return $this;
}
final protected function getLimit() {
return $this->limit;
}
final public function withEpochRange($min, $max) {
$this->minimumEpoch = $min;
$this->maximumEpoch = $max;
return $this;
}
final public function getMinimumEpoch() {
return $this->minimumEpoch;
}
final public function getMaximumEpoch() {
return $this->maximumEpoch;
}
final public function getRepositoryAPI() {
return $this->getGraph()->getRepositoryAPI();
}
abstract public function execute();
}

View file

@ -0,0 +1,209 @@
<?php
final class ArcanistGitCommitGraphQuery
extends ArcanistCommitGraphQuery {
private $seen = array();
private $futures = array();
private $iterators = array();
private $cursors = array();
private $iteratorKey = 0;
public function execute() {
$this->newFutures();
$this->executeIterators();
return $this->seen;
}
private function newFutures() {
$head_hashes = $this->getHeadHashes();
$exact_hashes = $this->getExactHashes();
if (!$head_hashes && !$exact_hashes) {
throw new Exception(pht('Need head hashes or exact hashes!'));
}
$api = $this->getRepositoryAPI();
$ref_lists = array();
if ($head_hashes) {
$refs = array();
if ($head_hashes !== null) {
foreach ($head_hashes as $hash) {
$refs[] = $hash;
}
}
$tail_hashes = $this->getTailHashes();
if ($tail_hashes !== null) {
foreach ($tail_hashes as $tail_hash) {
$refs[] = sprintf('^%s^@', $tail_hash);
}
}
$ref_lists[] = $refs;
}
if ($exact_hashes !== null) {
foreach ($exact_hashes as $exact_hash) {
$ref_list = array();
$ref_list[] = $exact_hash;
$ref_list[] = sprintf('^%s^@', $exact_hash);
$ref_list[] = '--';
$ref_lists[] = $ref_list;
}
}
$flags = array();
$min_epoch = $this->getMinimumEpoch();
if ($min_epoch !== null) {
$flags[] = '--after';
$flags[] = date('c', $min_epoch);
}
$max_epoch = $this->getMaximumEpoch();
if ($max_epoch !== null) {
$flags[] = '--before';
$flags[] = date('c', $max_epoch);
}
foreach ($ref_lists as $ref_list) {
$ref_blob = implode("\n", $ref_list)."\n";
$fields = array(
'%e',
'%H',
'%P',
'%ct',
'%B',
);
$format = implode('%x02', $fields).'%x01';
$future = $api->newFuture(
'log --format=%s %Ls --stdin',
$format,
$flags);
$future->write($ref_blob);
$future->setResolveOnError(true);
$this->futures[] = $future;
}
}
private function executeIterators() {
while ($this->futures || $this->iterators) {
$iterator_limit = 8;
while (count($this->iterators) < $iterator_limit) {
if (!$this->futures) {
break;
}
$future = array_pop($this->futures);
$future->startFuture();
$iterator = id(new LinesOfALargeExecFuture($future))
->setDelimiter("\1");
$iterator->rewind();
$iterator_key = $this->getNextIteratorKey();
$this->iterators[$iterator_key] = $iterator;
}
$limit = $this->getLimit();
foreach ($this->iterators as $iterator_key => $iterator) {
$this->executeIterator($iterator_key, $iterator);
if ($limit) {
if (count($this->seen) >= $limit) {
return;
}
}
}
}
}
private function getNextIteratorKey() {
return $this->iteratorKey++;
}
private function executeIterator($iterator_key, $lines) {
$graph = $this->getGraph();
$limit = $this->getLimit();
$is_done = false;
while (true) {
if (!$lines->valid()) {
$is_done = true;
break;
}
$line = $lines->current();
$lines->next();
if ($line === "\n") {
continue;
}
$fields = explode("\2", $line);
if (count($fields) !== 5) {
throw new Exception(
pht(
'Failed to split line "%s" from "git log".',
$line));
}
list($encoding, $hash, $parents, $commit_epoch, $message) = $fields;
// TODO: Handle encoding, see DiffusionLowLevelCommitQuery.
$node = $graph->getNode($hash);
if (!$node) {
$node = $graph->newNode($hash);
}
$this->seen[$hash] = $node;
$node
->setCommitMessage($message)
->setCommitEpoch((int)$commit_epoch);
if (strlen($parents)) {
$parents = explode(' ', $parents);
$parent_nodes = array();
foreach ($parents as $parent) {
$parent_node = $graph->getNode($parent);
if (!$parent_node) {
$parent_node = $graph->newNode($parent);
}
$parent_nodes[$parent] = $parent_node;
$parent_node->addChildNode($node);
}
$node->setParentNodes($parent_nodes);
} else {
$parents = array();
}
if ($limit) {
if (count($this->seen) >= $limit) {
break;
}
}
}
if ($is_done) {
unset($this->iterators[$iterator_key]);
}
}
}

View file

@ -0,0 +1,216 @@
<?php
final class ArcanistMercurialCommitGraphQuery
extends ArcanistCommitGraphQuery {
private $seen = array();
private $queryFuture;
public function execute() {
$this->beginExecute();
$this->continueExecute();
return $this->seen;
}
protected function beginExecute() {
$head_hashes = $this->getHeadHashes();
$exact_hashes = $this->getExactHashes();
if (!$head_hashes && !$exact_hashes) {
throw new Exception(pht('Need head hashes or exact hashes!'));
}
$api = $this->getRepositoryAPI();
$revsets = array();
if ($head_hashes !== null) {
$revs = array();
foreach ($head_hashes as $hash) {
$revs[] = hgsprintf(
'ancestors(%s)',
$hash);
}
$revsets[] = $this->joinOrRevsets($revs);
}
$tail_hashes = $this->getTailHashes();
if ($tail_hashes !== null) {
$revs = array();
foreach ($tail_hashes as $tail_hash) {
$revs[] = hgsprintf(
'descendants(%s)',
$tail_hash);
}
$revsets[] = $this->joinOrRevsets($revs);
}
if ($revsets) {
$revsets = array(
$this->joinAndRevsets($revsets),
);
}
if ($exact_hashes !== null) {
$revs = array();
foreach ($exact_hashes as $exact_hash) {
$revs[] = hgsprintf(
'%s',
$exact_hash);
}
$revsets[] = $this->joinOrRevsets($revs);
}
$revsets = $this->joinOrRevsets($revsets);
$fields = array(
'', // Placeholder for "encoding".
'{node}',
'{p1node} {p2node}',
'{date|rfc822date}',
'{desc|utf8}',
);
$template = implode("\2", $fields)."\1";
$flags = array();
$min_epoch = $this->getMinimumEpoch();
$max_epoch = $this->getMaximumEpoch();
if ($min_epoch !== null || $max_epoch !== null) {
$flags[] = '--date';
if ($min_epoch !== null) {
$min_epoch = date('c', $min_epoch);
}
if ($max_epoch !== null) {
$max_epoch = date('c', $max_epoch);
}
if ($min_epoch !== null && $max_epoch !== null) {
$flags[] = sprintf(
'%s to %s',
$min_epoch,
$max_epoch);
} else if ($min_epoch) {
$flags[] = sprintf(
'>%s',
$min_epoch);
} else {
$flags[] = sprintf(
'<%s',
$max_epoch);
}
}
$future = $api->newFuture(
'log --rev %s --template %s %Ls --',
$revsets,
$template,
$flags);
$future->setResolveOnError(true);
$future->start();
$lines = id(new LinesOfALargeExecFuture($future))
->setDelimiter("\1");
$lines->rewind();
$this->queryFuture = $lines;
}
protected function continueExecute() {
$graph = $this->getGraph();
$lines = $this->queryFuture;
$limit = $this->getLimit();
$no_parent = str_repeat('0', 40);
while (true) {
if (!$lines->valid()) {
return false;
}
$line = $lines->current();
$lines->next();
if ($line === "\n") {
continue;
}
$fields = explode("\2", $line);
if (count($fields) !== 5) {
throw new Exception(
pht(
'Failed to split line "%s" from "git log".',
$line));
}
list($encoding, $hash, $parents, $commit_epoch, $message) = $fields;
$node = $graph->getNode($hash);
if (!$node) {
$node = $graph->newNode($hash);
}
$this->seen[$hash] = $node;
$node
->setCommitMessage($message)
->setCommitEpoch((int)strtotime($commit_epoch));
if (strlen($parents)) {
$parents = explode(' ', $parents);
$parent_nodes = array();
foreach ($parents as $parent) {
if ($parent === $no_parent) {
continue;
}
$parent_node = $graph->getNode($parent);
if (!$parent_node) {
$parent_node = $graph->newNode($parent);
}
$parent_nodes[$parent] = $parent_node;
$parent_node->addChildNode($node);
}
$node->setParentNodes($parent_nodes);
} else {
$parents = array();
}
if ($limit) {
if (count($this->seen) >= $limit) {
break;
}
}
}
}
private function joinOrRevsets(array $revsets) {
return $this->joinRevsets($revsets, false);
}
private function joinAndRevsets(array $revsets) {
return $this->joinRevsets($revsets, true);
}
private function joinRevsets(array $revsets, $is_and) {
if (!$revsets) {
return array();
}
if (count($revsets) === 1) {
return head($revsets);
}
if ($is_and) {
return '('.implode(' and ', $revsets).')';
} else {
return '('.implode(' or ', $revsets).')';
}
}
}

View file

@ -0,0 +1,50 @@
<?php
final class ArcanistSimpleCommitGraphQuery
extends ArcanistCommitGraphQuery {
private $corpus;
public function setCorpus($corpus) {
$this->corpus = $corpus;
return $this;
}
public function getCorpus() {
return $this->corpus;
}
public function execute() {
$graph = $this->getGraph();
$corpus = $this->getCorpus();
$edges = preg_split('(\s+)', trim($corpus));
foreach ($edges as $edge) {
$matches = null;
$ok = preg_match('(^(?P<parent>\S+)>(?P<child>\S+)\z)', $edge, $matches);
if (!$ok) {
throw new Exception(
pht(
'Failed to match SimpleCommitGraph directive "%s".',
$edge));
}
$parent = $matches['parent'];
$child = $matches['child'];
$pnode = $graph->getNode($parent);
if (!$pnode) {
$pnode = $graph->newNode($parent);
}
$cnode = $graph->getNode($child);
if (!$cnode) {
$cnode = $graph->newNode($child);
}
$cnode->addParentNode($pnode);
$pnode->addChildNode($cnode);
}
}
}

View file

@ -0,0 +1,246 @@
<?php
final class ArcanistCommitGraphSetTreeView
extends Phobject {
private $repositoryAPI;
private $rootSet;
private $markers;
private $markerGroups;
private $stateRefs;
private $setViews;
public function setRootSet($root_set) {
$this->rootSet = $root_set;
return $this;
}
public function getRootSet() {
return $this->rootSet;
}
public function setMarkers($markers) {
$this->markers = $markers;
$this->markerGroups = mgroup($markers, 'getCommitHash');
return $this;
}
public function getMarkers() {
return $this->markers;
}
public function setStateRefs($state_refs) {
$this->stateRefs = $state_refs;
return $this;
}
public function getStateRefs() {
return $this->stateRefs;
}
public function setRepositoryAPI($repository_api) {
$this->repositoryAPI = $repository_api;
return $this;
}
public function getRepositoryAPI() {
return $this->repositoryAPI;
}
public function draw() {
$set = $this->getRootSet();
$this->setViews = array();
$view_root = $this->newSetViews($set);
$view_list = $this->setViews;
$api = $this->getRepositoryAPI();
foreach ($view_list as $view) {
$view_set = $view->getSet();
$hashes = $view_set->getHashes();
$commit_refs = $this->getCommitRefs($hashes);
$revision_refs = $this->getRevisionRefs(head($hashes));
$marker_refs = $this->getMarkerRefs($hashes);
$view
->setRepositoryAPI($api)
->setCommitRefs($commit_refs)
->setRevisionRefs($revision_refs)
->setMarkerRefs($marker_refs);
}
$view_list = $this->collapseViews($view_root, $view_list);
$rows = array();
foreach ($view_list as $view) {
$rows[] = $view->newCellViews();
}
return $rows;
}
private function newSetViews(ArcanistCommitGraphSet $set) {
$set_view = $this->newSetView($set);
$this->setViews[] = $set_view;
foreach ($set->getDisplayChildSets() as $child_set) {
$child_view = $this->newSetViews($child_set);
$child_view->setParentView($set_view);
$set_view->addChildView($child_view);
}
return $set_view;
}
private function newSetView(ArcanistCommitGraphSet $set) {
return id(new ArcanistCommitGraphSetView())
->setSet($set);
}
private function getStateRef($hash) {
$state_refs = $this->getStateRefs();
if (!isset($state_refs[$hash])) {
throw new Exception(
pht(
'Found no state ref for hash "%s".',
$hash));
}
return $state_refs[$hash];
}
private function getRevisionRefs($hash) {
$state_ref = $this->getStateRef($hash);
return $state_ref->getRevisionRefs();
}
private function getCommitRefs(array $hashes) {
$results = array();
foreach ($hashes as $hash) {
$state_ref = $this->getStateRef($hash);
$results[$hash] = $state_ref->getCommitRef();
}
return $results;
}
private function getMarkerRefs(array $hashes) {
$results = array();
foreach ($hashes as $hash) {
$results[$hash] = idx($this->markerGroups, $hash, array());
}
return $results;
}
private function collapseViews($view_root, array $view_list) {
$this->groupViews($view_root);
foreach ($view_list as $view) {
$group = $view->getGroupView();
$group->addMemberView($view);
}
foreach ($view_list as $view) {
$member_views = $view->getMemberViews();
// Break small groups apart.
$count = count($member_views);
if ($count > 1 && $count < 4) {
foreach ($member_views as $member_view) {
$member_view->setGroupView($member_view);
$member_view->setMemberViews(array($member_view));
}
}
}
foreach ($view_list as $view) {
$parent_view = $view->getParentView();
if (!$parent_view) {
$depth = 0;
} else {
$parent_group = $parent_view->getGroupView();
$member_views = $parent_group->getMemberViews();
if (count($member_views) > 1) {
$depth = $parent_group->getViewDepth() + 2;
} else {
$depth = $parent_group->getViewDepth() + 1;
}
}
$view->setViewDepth($depth);
}
foreach ($view_list as $key => $view) {
if (!$view->getMemberViews()) {
unset($view_list[$key]);
}
}
return $view_list;
}
private function groupViews($view) {
$group_view = $this->getGroupForView($view);
$view->setGroupView($group_view);
$children = $view->getChildViews();
foreach ($children as $child) {
$this->groupViews($child);
}
}
private function getGroupForView($view) {
$revision_refs = $view->getRevisionRefs();
if ($revision_refs) {
$has_unpublished_revision = false;
foreach ($revision_refs as $revision_ref) {
if (!$revision_ref->isStatusPublished()) {
$has_unpublished_revision = true;
break;
}
}
if ($has_unpublished_revision) {
return $view;
}
}
$marker_lists = $view->getMarkerRefs();
foreach ($marker_lists as $marker_refs) {
if ($marker_refs) {
return $view;
}
}
// If a view has no children, it is never grouped with other views.
$children = $view->getChildViews();
if (!$children) {
return $view;
}
// If a view is a root, we can't group it.
$parent = $view->getParentView();
if (!$parent) {
return $view;
}
// If a view has siblings, we can't group it with other views.
$siblings = $parent->getChildViews();
if (count($siblings) !== 1) {
return $view;
}
// The view has no children and no other siblings, so add it to the
// parent's group.
return $parent->getGroupView();
}
}

View file

@ -0,0 +1,568 @@
<?php
final class ArcanistCommitGraphSetView
extends Phobject {
private $repositoryAPI;
private $set;
private $parentView;
private $childViews = array();
private $commitRefs;
private $revisionRefs;
private $markerRefs;
private $viewDepth;
private $groupView;
private $memberViews = array();
public function setRepositoryAPI(ArcanistRepositoryAPI $repository_api) {
$this->repositoryAPI = $repository_api;
return $this;
}
public function getRepositoryAPI() {
return $this->repositoryAPI;
}
public function setSet(ArcanistCommitGraphSet $set) {
$this->set = $set;
return $this;
}
public function getSet() {
return $this->set;
}
public function setParentView(ArcanistCommitGraphSetView $parent_view) {
$this->parentView = $parent_view;
return $this;
}
public function getParentView() {
return $this->parentView;
}
public function setGroupView(ArcanistCommitGraphSetView $group_view) {
$this->groupView = $group_view;
return $this;
}
public function getGroupView() {
return $this->groupView;
}
public function addMemberView(ArcanistCommitGraphSetView $member_view) {
$this->memberViews[] = $member_view;
return $this;
}
public function getMemberViews() {
return $this->memberViews;
}
public function setMemberViews(array $member_views) {
$this->memberViews = $member_views;
return $this;
}
public function addChildView(ArcanistCommitGraphSetView $child_view) {
$this->childViews[] = $child_view;
return $this;
}
public function setChildViews(array $child_views) {
assert_instances_of($child_views, __CLASS__);
$this->childViews = $child_views;
return $this;
}
public function getChildViews() {
return $this->childViews;
}
public function setCommitRefs($commit_refs) {
$this->commitRefs = $commit_refs;
return $this;
}
public function getCommitRefs() {
return $this->commitRefs;
}
public function setRevisionRefs($revision_refs) {
$this->revisionRefs = $revision_refs;
return $this;
}
public function getRevisionRefs() {
return $this->revisionRefs;
}
public function setMarkerRefs($marker_refs) {
$this->markerRefs = $marker_refs;
return $this;
}
public function getMarkerRefs() {
return $this->markerRefs;
}
public function setViewDepth($view_depth) {
$this->viewDepth = $view_depth;
return $this;
}
public function getViewDepth() {
return $this->viewDepth;
}
public function newCellViews() {
$set = $this->getSet();
$api = $this->getRepositoryAPI();
$commit_refs = $this->getCommitRefs();
$revision_refs = $this->getRevisionRefs();
$marker_refs = $this->getMarkerRefs();
$merge_strings = array();
foreach ($revision_refs as $revision_ref) {
$summary = $revision_ref->getName();
$merge_key = substr($summary, 0, 32);
$merge_key = phutil_utf8_strtolower($merge_key);
$merge_strings[$merge_key][] = $revision_ref;
}
$merge_map = array();
foreach ($commit_refs as $commit_ref) {
$summary = $commit_ref->getSummary();
$merge_with = null;
if (count($revision_refs) === 1) {
$merge_with = head($revision_refs);
} else {
$merge_key = substr($summary, 0, 32);
$merge_key = phutil_utf8_strtolower($merge_key);
if (isset($merge_strings[$merge_key])) {
$merge_refs = $merge_strings[$merge_key];
if (count($merge_refs) === 1) {
$merge_with = head($merge_refs);
}
}
}
if ($merge_with) {
$revision_phid = $merge_with->getPHID();
$merge_map[$revision_phid][] = $commit_ref;
}
}
$revision_map = mpull($revision_refs, null, 'getPHID');
$result_map = array();
foreach ($merge_map as $merge_phid => $merge_refs) {
if (count($merge_refs) !== 1) {
continue;
}
$merge_ref = head($merge_refs);
$commit_hash = $merge_ref->getCommitHash();
$result_map[$commit_hash] = $revision_map[$merge_phid];
}
$object_layout = array();
$merged_map = array_flip(mpull($result_map, 'getPHID'));
foreach ($revision_refs as $revision_ref) {
$revision_phid = $revision_ref->getPHID();
if (isset($merged_map[$revision_phid])) {
continue;
}
$object_layout[] = array(
'revision' => $revision_ref,
);
}
foreach ($commit_refs as $commit_ref) {
$commit_hash = $commit_ref->getCommitHash();
$revision_ref = idx($result_map, $commit_hash);
$object_layout[] = array(
'commit' => $commit_ref,
'revision' => $revision_ref,
);
}
$items = array();
foreach ($object_layout as $layout) {
$commit_ref = idx($layout, 'commit');
if (!$commit_ref) {
$items[] = $layout;
continue;
}
$commit_hash = $commit_ref->getCommitHash();
$markers = idx($marker_refs, $commit_hash);
if (!$markers) {
$items[] = $layout;
continue;
}
$head_marker = array_shift($markers);
$layout['marker'] = $head_marker;
$items[] = $layout;
if (!$markers) {
continue;
}
foreach ($markers as $marker) {
$items[] = array(
'marker' => $marker,
);
}
}
$items = $this->collapseItems($items);
$marker_view = $this->drawMarkerCell($items);
$commits_view = $this->drawCommitsCell($items);
$status_view = $this->drawStatusCell($items);
$revisions_view = $this->drawRevisionsCell($items);
$messages_view = $this->drawMessagesCell($items);
return array(
id(new ArcanistGridCell())
->setKey('marker')
->setContent($marker_view),
id(new ArcanistGridCell())
->setKey('commits')
->setContent($commits_view),
id(new ArcanistGridCell())
->setKey('status')
->setContent($status_view),
id(new ArcanistGridCell())
->setKey('revisions')
->setContent($revisions_view),
id(new ArcanistGridCell())
->setKey('messages')
->setContent($messages_view),
);
}
private function drawMarkerCell(array $items) {
$api = $this->getRepositoryAPI();
$marker_refs = $this->getMarkerRefs();
$commit_refs = $this->getCommitRefs();
if (count($commit_refs) === 1) {
$commit_ref = head($commit_refs);
$commit_hash = $commit_ref->getCommitHash();
$commit_hash = tsprintf(
'%s',
substr($commit_hash, 0, 7));
$commit_label = $commit_hash;
} else {
$min = head($commit_refs);
$max = last($commit_refs);
$commit_label = tsprintf(
'%s..%s',
substr($min->getCommitHash(), 0, 7),
substr($max->getCommitHash(), 0, 7));
}
$member_views = $this->getMemberViews();
$member_count = count($member_views);
if ($member_count > 1) {
$items[] = array(
'group' => $member_views,
);
}
$terminal_width = phutil_console_get_terminal_width();
$max_depth = (int)floor(3 + (max(0, $terminal_width - 72) / 6));
$depth = $this->getViewDepth();
if ($depth <= $max_depth) {
$display_depth = ($depth * 2);
$is_squished = false;
} else {
$display_depth = ($max_depth * 2);
$is_squished = true;
}
$max_width = ($max_depth * 2) + 16;
$available_width = $max_width - $display_depth;
$mark_ne = "\xE2\x94\x97";
$mark_ew = "\xE2\x94\x81";
$mark_esw = "\xE2\x94\xB3";
$mark_sw = "\xE2\x94\x93";
$mark_bullet = "\xE2\x80\xA2";
$mark_ns_light = "\xE2\x94\x82";
$mark_ne_light = "\xE2\x94\x94";
$mark_esw_light = "\xE2\x94\xAF";
$has_children = $this->getChildViews();
$is_first = true;
$last_key = last_key($items);
$cell = array();
foreach ($items as $item_key => $item) {
$marker_ref = idx($item, 'marker');
$group_ref = idx($item, 'group');
$is_last = ($item_key === $last_key);
if ($marker_ref) {
$marker_name = $marker_ref->getName();
$is_active = $marker_ref->getIsActive();
if ($is_active) {
$marker_width = $available_width - 4;
} else {
$marker_width = $available_width;
}
$marker_name = id(new PhutilUTF8StringTruncator())
->setMaximumGlyphs($marker_width)
->truncateString($marker_name);
if ($marker_ref->getIsActive()) {
$label = tsprintf(
'<bg:green>**%s**</bg> **%s**',
' * ',
$marker_name);
} else {
$label = tsprintf(
'**%s**',
$marker_name);
}
} else if ($group_ref) {
$label = pht(
'(... %s more revisions ...)',
new PhutilNumber(count($group_ref) - 1));
} else if ($is_first) {
$label = $commit_label;
} else {
$label = '';
}
if ($display_depth > 2) {
$indent = str_repeat(' ', $display_depth - 2);
} else {
$indent = '';
}
if ($is_first) {
if ($display_depth === 0) {
$path = $mark_bullet.' ';
} else {
if ($has_children) {
$path = $mark_ne.$mark_ew.$mark_esw.' ';
} else if (!$is_last) {
$path = $mark_ne.$mark_ew.$mark_esw_light.' ';
} else {
$path = $mark_ne.$mark_ew.$mark_ew.' ';
}
}
} else if ($group_ref) {
$path = $mark_ne.'/'.$mark_sw.' ';
} else {
if ($is_last && !$has_children) {
$path = $mark_ne_light.' ';
} else {
$path = $mark_ns_light.' ';
}
if ($display_depth > 0) {
$path = ' '.$path;
}
}
$indent_text = sprintf(
'%s%s',
$indent,
$path);
$cell[] = tsprintf(
"%s%s\n",
$indent_text,
$label);
$is_first = false;
}
return $cell;
}
private function drawCommitsCell(array $items) {
$cell = array();
foreach ($items as $item) {
$count = idx($item, 'collapseCount');
if ($count) {
$cell[] = tsprintf(" : \n");
continue;
}
$commit_ref = idx($item, 'commit');
if (!$commit_ref) {
$cell[] = tsprintf("\n");
continue;
}
$commit_label = $this->drawCommitLabel($commit_ref);
$cell[] = tsprintf("%s\n", $commit_label);
}
return $cell;
}
private function drawCommitLabel(ArcanistCommitRef $commit_ref) {
$api = $this->getRepositoryAPI();
$hash = $commit_ref->getCommitHash();
$hash = substr($hash, 0, 7);
return tsprintf('%s', $hash);
}
private function drawRevisionsCell(array $items) {
$cell = array();
foreach ($items as $item) {
$revision_ref = idx($item, 'revision');
if (!$revision_ref) {
$cell[] = tsprintf("\n");
continue;
}
$revision_label = $this->drawRevisionLabel($revision_ref);
$cell[] = tsprintf("%s\n", $revision_label);
}
return $cell;
}
private function drawRevisionLabel(ArcanistRevisionRef $revision_ref) {
$api = $this->getRepositoryAPI();
$monogram = $revision_ref->getMonogram();
return tsprintf('%s', $monogram);
}
private function drawMessagesCell(array $items) {
$cell = array();
foreach ($items as $item) {
$count = idx($item, 'collapseCount');
if ($count) {
$cell[] = tsprintf(
"%s\n",
pht(
'<... %s more commits ...>',
new PhutilNumber($count)));
continue;
}
$revision_ref = idx($item, 'revision');
if ($revision_ref) {
$cell[] = tsprintf("%s\n", $revision_ref->getName());
continue;
}
$commit_ref = idx($item, 'commit');
if ($commit_ref) {
$cell[] = tsprintf("%s\n", $commit_ref->getSummary());
continue;
}
$cell[] = tsprintf("\n");
}
return $cell;
}
private function drawStatusCell(array $items) {
$cell = array();
foreach ($items as $item) {
$revision_ref = idx($item, 'revision');
if (!$revision_ref) {
$cell[] = tsprintf("\n");
continue;
}
$revision_label = $this->drawRevisionStatus($revision_ref);
$cell[] = tsprintf("%s\n", $revision_label);
}
return $cell;
}
private function drawRevisionStatus(ArcanistRevisionRef $revision_ref) {
if (phutil_console_get_terminal_width() < 120) {
$status = $revision_ref->getStatusShortDisplayName();
} else {
$status = $revision_ref->getStatusDisplayName();
}
$ansi_color = $revision_ref->getStatusANSIColor();
if ($ansi_color) {
$status = tsprintf(
sprintf('<fg:%s>%%s</fg>', $ansi_color),
$status);
}
return tsprintf('%s', $status);
}
private function collapseItems(array $items) {
$show_context = 3;
$map = array();
foreach ($items as $key => $item) {
$can_collapse =
(isset($item['commit'])) &&
(!isset($item['revision'])) &&
(!isset($item['marker']));
$map[$key] = $can_collapse;
}
$map = phutil_partition($map);
foreach ($map as $partition) {
$value = head($partition);
if (!$value) {
break;
}
$count = count($partition);
if ($count < ($show_context * 2) + 3) {
continue;
}
$partition = array_slice($partition, $show_context, -$show_context, true);
$is_first = true;
foreach ($partition as $key => $value) {
if ($is_first) {
$items[$key]['collapseCount'] = $count;
} else {
unset($items[$key]);
}
$is_first = false;
}
}
return $items;
}
}

View file

@ -0,0 +1,195 @@
<?php
final class ArcanistGitRepositoryMarkerQuery
extends ArcanistRepositoryMarkerQuery {
protected function newLocalRefMarkers() {
$api = $this->getRepositoryAPI();
$future = $this->newCurrentBranchNameFuture()->start();
$field_list = array(
'%(refname)',
'%(objectname)',
'%(committerdate:raw)',
'%(tree)',
'%(*objectname)',
'%(subject)',
'%(subject)%0a%0a%(body)',
'%02',
);
$expect_count = count($field_list);
$branch_prefix = 'refs/heads/';
$branch_length = strlen($branch_prefix);
$remote_prefix = 'refs/remotes/';
$remote_length = strlen($remote_prefix);
list($stdout) = $api->newFuture(
'for-each-ref --format %s -- refs/',
implode('%01', $field_list))->resolve();
$markers = array();
$lines = explode("\2", $stdout);
foreach ($lines as $line) {
$line = trim($line);
if (!strlen($line)) {
continue;
}
$fields = explode("\1", $line, $expect_count);
$actual_count = count($fields);
if ($actual_count !== $expect_count) {
throw new Exception(
pht(
'Unexpected field count when parsing line "%s", got %s but '.
'expected %s.',
$line,
new PhutilNumber($actual_count),
new PhutilNumber($expect_count)));
}
list($ref, $hash, $epoch, $tree, $dst_hash, $summary, $text) = $fields;
$remote_name = null;
if (!strncmp($ref, $branch_prefix, $branch_length)) {
$type = ArcanistMarkerRef::TYPE_BRANCH;
$name = substr($ref, $branch_length);
} else if (!strncmp($ref, $remote_prefix, $remote_length)) {
// This isn't entirely correct: the ref may be a tag, etc.
$type = ArcanistMarkerRef::TYPE_BRANCH;
$label = substr($ref, $remote_length);
$parts = explode('/', $label, 2);
$remote_name = $parts[0];
$name = $parts[1];
} else {
// For now, discard other refs.
continue;
}
$marker = id(new ArcanistMarkerRef())
->setName($name)
->setMarkerType($type)
->setEpoch((int)$epoch)
->setMarkerHash($hash)
->setTreeHash($tree)
->setSummary($summary)
->setMessage($text);
if ($remote_name !== null) {
$marker->setRemoteName($remote_name);
}
if (strlen($dst_hash)) {
$commit_hash = $dst_hash;
} else {
$commit_hash = $hash;
}
$marker->setCommitHash($commit_hash);
$commit_ref = $api->newCommitRef()
->setCommitHash($commit_hash)
->attachMessage($text);
$marker->attachCommitRef($commit_ref);
$markers[] = $marker;
}
$current = $this->resolveCurrentBranchNameFuture($future);
if ($current !== null) {
foreach ($markers as $marker) {
if ($marker->getName() === $current) {
$marker->setIsActive(true);
}
}
}
return $markers;
}
private function newCurrentBranchNameFuture() {
$api = $this->getRepositoryAPI();
return $api->newFuture('symbolic-ref --quiet HEAD --')
->setResolveOnError(true);
}
private function resolveCurrentBranchNameFuture($future) {
list($err, $stdout) = $future->resolve();
if ($err) {
return null;
}
$matches = null;
if (!preg_match('(^refs/heads/(.*)\z)', trim($stdout), $matches)) {
return null;
}
return $matches[1];
}
protected function newRemoteRefMarkers(ArcanistRemoteRef $remote) {
$api = $this->getRepositoryAPI();
// NOTE: Since we only care about branches today, we only list branches.
$future = $api->newFuture(
'ls-remote --refs %s %s',
$remote->getRemoteName(),
'refs/heads/*');
list($stdout) = $future->resolve();
$branch_prefix = 'refs/heads/';
$branch_length = strlen($branch_prefix);
$pattern = '(^(?P<hash>\S+)\t(?P<ref>\S+)\z)';
$markers = array();
$lines = phutil_split_lines($stdout, false);
foreach ($lines as $line) {
$matches = null;
$ok = preg_match($pattern, $line, $matches);
if (!$ok) {
throw new Exception(
pht(
'Failed to match "ls-remote" pattern against line "%s".',
$line));
}
$hash = $matches['hash'];
$ref = $matches['ref'];
if (!strncmp($ref, $branch_prefix, $branch_length)) {
$type = ArcanistMarkerRef::TYPE_BRANCH;
$name = substr($ref, $branch_length);
} else {
// For now, discard other refs.
continue;
}
$marker = id(new ArcanistMarkerRef())
->setName($name)
->setMarkerType($type)
->setMarkerHash($hash)
->setCommitHash($hash);
$commit_ref = $api->newCommitRef()
->setCommitHash($hash);
$marker->attachCommitRef($commit_ref);
$markers[] = $marker;
}
return $markers;
}
}

View file

@ -0,0 +1,186 @@
<?php
final class ArcanistMarkerRef
extends ArcanistRef {
const HARDPOINT_COMMITREF = 'arc.marker.commitRef';
const HARDPOINT_WORKINGCOPYSTATEREF = 'arc.marker.workingCopyStateRef';
const HARDPOINT_REMOTEREF = 'arc.marker.remoteRef';
const TYPE_BRANCH = 'branch';
const TYPE_BOOKMARK = 'bookmark';
private $name;
private $markerType;
private $epoch;
private $markerHash;
private $commitHash;
private $displayHash;
private $treeHash;
private $summary;
private $message;
private $isActive = false;
private $remoteName;
public function getRefDisplayName() {
switch ($this->getMarkerType()) {
case self::TYPE_BRANCH:
return pht('Branch "%s"', $this->getName());
case self::TYPE_BOOKMARK:
return pht('Bookmark "%s"', $this->getName());
default:
return pht('Marker "%s"', $this->getName());
}
}
protected function newHardpoints() {
return array(
$this->newHardpoint(self::HARDPOINT_COMMITREF),
$this->newHardpoint(self::HARDPOINT_WORKINGCOPYSTATEREF),
$this->newHardpoint(self::HARDPOINT_REMOTEREF),
);
}
public function setName($name) {
$this->name = $name;
return $this;
}
public function getName() {
return $this->name;
}
public function setMarkerType($marker_type) {
$this->markerType = $marker_type;
return $this;
}
public function getMarkerType() {
return $this->markerType;
}
public function setEpoch($epoch) {
$this->epoch = $epoch;
return $this;
}
public function getEpoch() {
return $this->epoch;
}
public function setMarkerHash($marker_hash) {
$this->markerHash = $marker_hash;
return $this;
}
public function getMarkerHash() {
return $this->markerHash;
}
public function setDisplayHash($display_hash) {
$this->displayHash = $display_hash;
return $this;
}
public function getDisplayHash() {
return $this->displayHash;
}
public function setCommitHash($commit_hash) {
$this->commitHash = $commit_hash;
return $this;
}
public function getCommitHash() {
return $this->commitHash;
}
public function setTreeHash($tree_hash) {
$this->treeHash = $tree_hash;
return $this;
}
public function getTreeHash() {
return $this->treeHash;
}
public function setSummary($summary) {
$this->summary = $summary;
return $this;
}
public function getSummary() {
return $this->summary;
}
public function setMessage($message) {
$this->message = $message;
return $this;
}
public function getMessage() {
return $this->message;
}
public function setIsActive($is_active) {
$this->isActive = $is_active;
return $this;
}
public function getIsActive() {
return $this->isActive;
}
public function setRemoteName($remote_name) {
$this->remoteName = $remote_name;
return $this;
}
public function getRemoteName() {
return $this->remoteName;
}
public function isBookmark() {
return ($this->getMarkerType() === self::TYPE_BOOKMARK);
}
public function isBranch() {
return ($this->getMarkerType() === self::TYPE_BRANCH);
}
public function attachCommitRef(ArcanistCommitRef $ref) {
return $this->attachHardpoint(self::HARDPOINT_COMMITREF, $ref);
}
public function getCommitRef() {
return $this->getHardpoint(self::HARDPOINT_COMMITREF);
}
public function attachWorkingCopyStateRef(ArcanistWorkingCopyStateRef $ref) {
return $this->attachHardpoint(self::HARDPOINT_WORKINGCOPYSTATEREF, $ref);
}
public function getWorkingCopyStateRef() {
return $this->getHardpoint(self::HARDPOINT_WORKINGCOPYSTATEREF);
}
public function attachRemoteRef(ArcanistRemoteRef $ref = null) {
return $this->attachHardpoint(self::HARDPOINT_REMOTEREF, $ref);
}
public function getRemoteRef() {
return $this->getHardpoint(self::HARDPOINT_REMOTEREF);
}
protected function buildRefView(ArcanistRefView $view) {
$title = pht(
'%s %s',
$this->getDisplayHash(),
$this->getSummary());
$view
->setObjectName($this->getRefDisplayName())
->setTitle($title);
}
}

View file

@ -0,0 +1,128 @@
<?php
final class ArcanistMercurialRepositoryMarkerQuery
extends ArcanistRepositoryMarkerQuery {
protected function newLocalRefMarkers() {
return $this->newMarkers();
}
protected function newRemoteRefMarkers(ArcanistRemoteRef $remote = null) {
return $this->newMarkers($remote);
}
private function newMarkers(ArcanistRemoteRef $remote = null) {
$api = $this->getRepositoryAPI();
// In native Mercurial it is difficult to identify remote markers, and
// complicated to identify local markers efficiently. We use an extension
// 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.
if ($remote !== null) {
$tmpfile = new TempFile();
Filesystem::remove($tmpfile);
$argv[] = '--output';
$argv[] = phutil_string_cast($tmpfile);
}
$argv[] = '--';
if ($remote !== null) {
$argv[] = $remote->getRemoteName();
}
if ($remote !== null) {
$passthru = $api->newPassthru('%Ls', $argv);
$err = $passthru->execute();
if ($err) {
throw new Exception(
pht(
'Call to "hg arc-ls-markers" failed with error "%s".',
$err));
}
$raw_data = Filesystem::readFile($tmpfile);
unset($tmpfile);
} else {
$future = $api->newFuture('%Ls', $argv);
list($raw_data) = $future->resolve();
}
$items = phutil_json_decode($raw_data);
$markers = array();
foreach ($items as $item) {
if (!empty($item['isClosed'])) {
// NOTE: For now, we ignore closed branch heads.
continue;
}
$node = $item['node'];
if (!$node) {
// NOTE: For now, we ignore the virtual "current branch" marker.
continue;
}
switch ($item['type']) {
case 'branch':
$marker_type = ArcanistMarkerRef::TYPE_BRANCH;
break;
case 'bookmark':
$marker_type = ArcanistMarkerRef::TYPE_BOOKMARK;
break;
case 'commit':
$marker_type = null;
break;
default:
throw new Exception(
pht(
'Call to "hg arc-ls-markers" returned marker of unknown '.
'type "%s".',
$item['type']));
}
if ($marker_type === null) {
// NOTE: For now, we ignore the virtual "head" marker.
continue;
}
$commit_ref = $api->newCommitRef()
->setCommitHash($node);
$marker_ref = id(new ArcanistMarkerRef())
->setName($item['name'])
->setCommitHash($node)
->attachCommitRef($commit_ref);
if (isset($item['description'])) {
$description = $item['description'];
$commit_ref->attachMessage($description);
$description_lines = phutil_split_lines($description, false);
$marker_ref->setSummary(head($description_lines));
}
$marker_ref
->setMarkerType($marker_type)
->setIsActive(!empty($item['isActive']));
$markers[] = $marker_ref;
}
return $markers;
}
}

View file

@ -0,0 +1,121 @@
<?php
abstract class ArcanistRepositoryMarkerQuery
extends ArcanistRepositoryQuery {
private $isActive;
private $markerTypes;
private $names;
private $commitHashes;
private $ancestorCommitHashes;
private $remotes;
private $isRemoteCache = false;
final public function withMarkerTypes(array $types) {
$this->markerTypes = array_fuse($types);
return $this;
}
final public function withNames(array $names) {
$this->names = array_fuse($names);
return $this;
}
final public function withRemotes(array $remotes) {
assert_instances_of($remotes, 'ArcanistRemoteRef');
$this->remotes = $remotes;
return $this;
}
final public function withIsRemoteCache($is_cache) {
$this->isRemoteCache = $is_cache;
return $this;
}
final public function withIsActive($active) {
$this->isActive = $active;
return $this;
}
final public function execute() {
$remotes = $this->remotes;
if ($remotes !== null) {
$marker_lists = array();
foreach ($remotes as $remote) {
$marker_list = $this->newRemoteRefMarkers($remote);
foreach ($marker_list as $marker) {
$marker->attachRemoteRef($remote);
}
$marker_lists[] = $marker_list;
}
$markers = array_mergev($marker_lists);
} else {
$markers = $this->newLocalRefMarkers();
foreach ($markers as $marker) {
$marker->attachRemoteRef(null);
}
}
$api = $this->getRepositoryAPI();
foreach ($markers as $marker) {
$state_ref = id(new ArcanistWorkingCopyStateRef())
->setCommitRef($marker->getCommitRef());
$marker->attachWorkingCopyStateRef($state_ref);
$hash = $marker->getCommitHash();
$hash = $api->getDisplayHash($hash);
$marker->setDisplayHash($hash);
}
$types = $this->markerTypes;
if ($types !== null) {
foreach ($markers as $key => $marker) {
if (!isset($types[$marker->getMarkerType()])) {
unset($markers[$key]);
}
}
}
$names = $this->names;
if ($names !== null) {
foreach ($markers as $key => $marker) {
if (!isset($names[$marker->getName()])) {
unset($markers[$key]);
}
}
}
if ($this->isActive !== null) {
foreach ($markers as $key => $marker) {
if ($marker->getIsActive() !== $this->isActive) {
unset($markers[$key]);
}
}
}
if ($this->isRemoteCache !== null) {
$want_cache = $this->isRemoteCache;
foreach ($markers as $key => $marker) {
$is_cache = ($marker->getRemoteName() !== null);
if ($is_cache !== $want_cache) {
unset($markers[$key]);
}
}
}
return $markers;
}
final protected function shouldQueryMarkerType($marker_type) {
if ($this->markerTypes === null) {
return true;
}
return isset($this->markerTypes[$marker_type]);
}
abstract protected function newLocalRefMarkers();
abstract protected function newRemoteRefMarkers(ArcanistRemoteRef $remote);
}

View file

@ -0,0 +1,35 @@
<?php
abstract class ArcanistRepositoryQuery
extends Phobject {
private $repositoryAPI;
final public function setRepositoryAPI(ArcanistRepositoryAPI $api) {
$this->repositoryAPI = $api;
return $this;
}
final public function getRepositoryAPI() {
return $this->repositoryAPI;
}
abstract public function execute();
final public function executeOne() {
$refs = $this->execute();
if (!$refs) {
return null;
}
if (count($refs) > 1) {
throw new Exception(
pht(
'Query matched multiple refs, expected zero or one.'));
}
return head($refs);
}
}

View file

@ -0,0 +1,183 @@
<?php
final class ArcanistGitRawCommit
extends Phobject {
private $treeHash;
private $parents = array();
private $rawAuthor;
private $rawCommitter;
private $message;
const GIT_EMPTY_TREE_HASH = '4b825dc642cb6eb9a060e54bf8d69288fbee4904';
public static function newEmptyCommit() {
$raw = new self();
$raw->setTreeHash(self::GIT_EMPTY_TREE_HASH);
return $raw;
}
public static function newFromRawBlob($blob) {
$lines = phutil_split_lines($blob);
$seen = array();
$raw = new self();
$pattern = '(^(\w+) ([^\n]+)\n?\z)';
foreach ($lines as $key => $line) {
unset($lines[$key]);
$is_divider = ($line === "\n");
if ($is_divider) {
break;
}
$matches = null;
$ok = preg_match($pattern, $line, $matches);
if (!$ok) {
throw new Exception(
pht(
'Expected to match pattern "%s" against line "%s" in raw commit '.
'blob: %s',
$pattern,
$line,
$blob));
}
$label = $matches[1];
$value = $matches[2];
// Detect unexpected repeated lines.
if (isset($seen[$label])) {
switch ($label) {
case 'parent':
break;
default:
throw new Exception(
pht(
'Encountered two "%s" lines ("%s", "%s") while parsing raw '.
'commit blob, expected at most one: %s',
$label,
$seen[$label],
$line,
$blob));
}
} else {
$seen[$label] = $line;
}
switch ($label) {
case 'tree':
$raw->setTreeHash($value);
break;
case 'parent':
$raw->addParent($value);
break;
case 'author':
$raw->setRawAuthor($value);
break;
case 'committer':
$raw->setRawCommitter($value);
break;
default:
throw new Exception(
pht(
'Unknown attribute label "%s" in line "%s" while parsing raw '.
'commit blob: %s',
$label,
$line,
$blob));
}
}
$message = implode('', $lines);
$raw->setMessage($message);
return $raw;
}
public function getRawBlob() {
$out = array();
$tree = $this->getTreeHash();
if ($tree !== null) {
$out[] = sprintf("tree %s\n", $tree);
}
$parents = $this->getParents();
foreach ($parents as $parent) {
$out[] = sprintf("parent %s\n", $parent);
}
$raw_author = $this->getRawAuthor();
if ($raw_author !== null) {
$out[] = sprintf("author %s\n", $raw_author);
}
$raw_committer = $this->getRawCommitter();
if ($raw_committer !== null) {
$out[] = sprintf("committer %s\n", $raw_committer);
}
$out[] = "\n";
$message = $this->getMessage();
if ($message !== null) {
$out[] = $message;
}
return implode('', $out);
}
public function setTreeHash($tree_hash) {
$this->treeHash = $tree_hash;
return $this;
}
public function getTreeHash() {
return $this->treeHash;
}
public function setRawAuthor($raw_author) {
$this->rawAuthor = $raw_author;
return $this;
}
public function getRawAuthor() {
return $this->rawAuthor;
}
public function setRawCommitter($raw_committer) {
$this->rawCommitter = $raw_committer;
return $this;
}
public function getRawCommitter() {
return $this->rawCommitter;
}
public function setParents(array $parents) {
$this->parents = $parents;
return $this;
}
public function getParents() {
return $this->parents;
}
public function addParent($hash) {
$this->parents[] = $hash;
return $this;
}
public function setMessage($message) {
$this->message = $message;
return $this;
}
public function getMessage() {
return $this->message;
}
}

View file

@ -0,0 +1,91 @@
<?php
final class ArcanistGitRawCommitTestCase
extends PhutilTestCase {
public function testGitRawCommitParser() {
$cases = array(
array(
'name' => 'empty',
'blob' => array(
'tree fcfd0454eac6a28c729aa6bf7d38a5f1efc5cc5d',
'',
'',
),
'tree' => 'fcfd0454eac6a28c729aa6bf7d38a5f1efc5cc5d',
),
array(
'name' => 'parents',
'blob' => array(
'tree 63ece8fd5a8283f1da2c14735d059669a09ba628',
'parent 4aebaaf60895c3f3dd32a8cadff00db2c8f74899',
'parent 0da1a2e17d921dc27ce9afa76b123cb4c8b73b17',
'author alice',
'committer alice',
'',
'Quack quack quack.',
'',
),
'tree' => '63ece8fd5a8283f1da2c14735d059669a09ba628',
'parents' => array(
'4aebaaf60895c3f3dd32a8cadff00db2c8f74899',
'0da1a2e17d921dc27ce9afa76b123cb4c8b73b17',
),
'author' => 'alice',
'committer' => 'alice',
'message' => "Quack quack quack.\n",
),
);
foreach ($cases as $case) {
$name = $case['name'];
$blob = $case['blob'];
if (is_array($blob)) {
$blob = implode("\n", $blob);
}
$raw = ArcanistGitRawCommit::newFromRawBlob($blob);
$out = $raw->getRawBlob();
$this->assertEqual(
$blob,
$out,
pht(
'Expected read + write to produce the original raw Git commit '.
'blob in case "%s".',
$name));
$tree = idx($case, 'tree');
$this->assertEqual(
$tree,
$raw->getTreeHash(),
pht('Tree hashes in case "%s".', $name));
$parents = idx($case, 'parents', array());
$this->assertEqual(
$parents,
$raw->getParents(),
pht('Parents in case "%s".', $name));
$author = idx($case, 'author');
$this->assertEqual(
$author,
$raw->getRawAuthor(),
pht('Authors in case "%s".', $name));
$committer = idx($case, 'committer');
$this->assertEqual(
$committer,
$raw->getRawCommitter(),
pht('Committer in case "%s".', $name));
$message = idx($case, 'message', '');
$this->assertEqual(
$message,
$raw->getMessage(),
pht('Message in case "%s".', $name));
}
}
}

View file

@ -0,0 +1,63 @@
<?php
final class ArcanistGitRepositoryRemoteQuery
extends ArcanistRepositoryRemoteQuery {
protected function newRemoteRefs() {
$api = $this->getRepositoryAPI();
$future = $api->newFuture('remote --verbose');
list($lines) = $future->resolve();
$pattern =
'(^'.
'(?P<name>[^\t]+)'.
'\t'.
'(?P<uri>[^\s]+)'.
' '.
'\((?P<mode>fetch|push)\)'.
'\z'.
')';
$map = array();
$lines = phutil_split_lines($lines, false);
foreach ($lines as $line) {
$matches = null;
if (!preg_match($pattern, $line, $matches)) {
throw new Exception(
pht(
'Failed to match remote pattern against line "%s".',
$line));
}
$name = $matches['name'];
$uri = $matches['uri'];
$mode = $matches['mode'];
$map[$name][$mode] = $uri;
}
$refs = array();
foreach ($map as $name => $uris) {
$fetch_uri = idx($uris, 'fetch');
$push_uri = idx($uris, 'push');
$ref = id(new ArcanistRemoteRef())
->setRemoteName($name);
if ($fetch_uri !== null) {
$ref->setFetchURI($fetch_uri);
}
if ($push_uri !== null) {
$ref->setPushURI($push_uri);
}
$refs[] = $ref;
}
return $refs;
}
}

View file

@ -0,0 +1,45 @@
<?php
final class ArcanistMercurialRepositoryRemoteQuery
extends ArcanistRepositoryRemoteQuery {
protected function newRemoteRefs() {
$api = $this->getRepositoryAPI();
$future = $api->newFuture('paths');
list($lines) = $future->resolve();
$refs = array();
$pattern = '(^(?P<name>.*?) = (?P<uri>.*)\z)';
$lines = phutil_split_lines($lines, false);
foreach ($lines as $line) {
$matches = null;
if (!preg_match($pattern, $line, $matches)) {
throw new Exception(
pht(
'Failed to match remote pattern against line "%s".',
$line));
}
$name = $matches['name'];
$uri = $matches['uri'];
// NOTE: Mercurial gives some special behavior to "default" and
// "default-push", but these remotes are both fully-formed remotes that
// are fetchable and pushable, they just have rules around selection
// as default targets for operations.
$ref = id(new ArcanistRemoteRef())
->setRemoteName($name)
->setFetchURI($uri)
->setPushURI($uri);
$refs[] = $ref;
}
return $refs;
}
}

View file

@ -0,0 +1,101 @@
<?php
final class ArcanistRemoteRef
extends ArcanistRef {
private $repositoryAPI;
private $remoteName;
private $fetchURI;
private $pushURI;
const HARDPOINT_REPOSITORYREFS = 'arc.remote.repositoryRefs';
public function getRefDisplayName() {
return pht('Remote "%s"', $this->getRemoteName());
}
public function setRepositoryAPI(ArcanistRepositoryAPI $repository_api) {
$this->repositoryAPI = $repository_api;
return $this;
}
public function getRepositoryAPI() {
return $this->repositoryAPI;
}
public function setRemoteName($remote_name) {
$this->remoteName = $remote_name;
return $this;
}
public function getRemoteName() {
return $this->remoteName;
}
public function setFetchURI($fetch_uri) {
$this->fetchURI = $fetch_uri;
return $this;
}
public function getFetchURI() {
return $this->fetchURI;
}
public function setPushURI($push_uri) {
$this->pushURI = $push_uri;
return $this;
}
public function getPushURI() {
return $this->pushURI;
}
protected function buildRefView(ArcanistRefView $view) {
$view->setObjectName($this->getRemoteName());
}
protected function newHardpoints() {
$object_list = new ArcanistObjectListHardpoint();
return array(
$this->newTemplateHardpoint(self::HARDPOINT_REPOSITORYREFS, $object_list),
);
}
private function getRepositoryRefs() {
return $this->getHardpoint(self::HARDPOINT_REPOSITORYREFS);
}
public function getPushRepositoryRef() {
return $this->getRepositoryRefByURI($this->getPushURI());
}
public function getFetchRepositoryRef() {
return $this->getRepositoryRefByURI($this->getFetchURI());
}
private function getRepositoryRefByURI($uri) {
$api = $this->getRepositoryAPI();
$uri = $api->getNormalizedURI($uri);
foreach ($this->getRepositoryRefs() as $repository_ref) {
foreach ($repository_ref->getURIs() as $repository_uri) {
$repository_uri = $api->getNormalizedURI($repository_uri);
if ($repository_uri === $uri) {
return $repository_ref;
}
}
}
return null;
}
public function isPermanentRef(ArcanistMarkerRef $ref) {
$repository_ref = $this->getPushRepositoryRef();
if (!$repository_ref) {
return false;
}
return $repository_ref->isPermanentRef($ref);
}
}

Some files were not shown because too many files have changed in this diff Show more