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:
commit
8795282286
144 changed files with 13526 additions and 4267 deletions
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -33,3 +33,6 @@
|
|||
|
||||
# Generated shell completion rulesets.
|
||||
/support/shell/rules/
|
||||
|
||||
# Python extension compiled files.
|
||||
/support/hg/arc-hg.pyc
|
||||
|
|
|
@ -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());
|
||||
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
116
src/conduit/ArcanistConduitCallFuture.php
Normal file
116
src/conduit/ArcanistConduitCallFuture.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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.')),
|
||||
);
|
||||
}
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistAliasesConfigOption
|
||||
extends ArcanistListConfigOption {
|
||||
extends ArcanistMultiSourceConfigOption {
|
||||
|
||||
public function getType() {
|
||||
return 'list<alias>';
|
||||
|
|
35
src/config/option/ArcanistBoolConfigOption.php
Normal file
35
src/config/option/ArcanistBoolConfigOption.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
}
|
||||
|
|
32
src/config/option/ArcanistMultiSourceConfigOption.php
Normal file
32
src/config/option/ArcanistMultiSourceConfigOption.php
Normal 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');
|
||||
}
|
||||
|
||||
}
|
51
src/config/option/ArcanistPromptsConfigOption.php
Normal file
51
src/config/option/ArcanistPromptsConfigOption.php
Normal 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');
|
||||
}
|
||||
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
<?php
|
||||
|
||||
abstract class ArcanistScalarConfigOption
|
||||
abstract class ArcanistSingleSourceConfigOption
|
||||
extends ArcanistConfigOption {
|
||||
|
||||
public function getValueFromStorageValueList(array $list) {
|
|
@ -1,7 +1,7 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistStringConfigOption
|
||||
extends ArcanistScalarConfigOption {
|
||||
extends ArcanistSingleSourceConfigOption {
|
||||
|
||||
public function getType() {
|
||||
return 'string';
|
||||
|
|
20
src/config/option/ArcanistStringListConfigOption.php
Normal file
20
src/config/option/ArcanistStringListConfigOption.php
Normal 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)));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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(
|
||||
|
|
56
src/console/grid/ArcanistGridCell.php
Normal file
56
src/console/grid/ArcanistGridCell.php
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
51
src/console/grid/ArcanistGridColumn.php
Normal file
51
src/console/grid/ArcanistGridColumn.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
40
src/console/grid/ArcanistGridRow.php
Normal file
40
src/console/grid/ArcanistGridRow.php
Normal 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];
|
||||
}
|
||||
|
||||
|
||||
}
|
295
src/console/grid/ArcanistGridView.php
Normal file
295
src/console/grid/ArcanistGridView.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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 )------------------------------------------------------------ */
|
||||
|
||||
|
|
48
src/engine/ArcanistWorkflowEngine.php
Normal file
48
src/engine/ArcanistWorkflowEngine.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
27
src/exception/ArcanistConduitAuthenticationException.php
Normal file
27
src/exception/ArcanistConduitAuthenticationException.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
170
src/land/ArcanistLandCommit.php
Normal file
170
src/land/ArcanistLandCommit.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
72
src/land/ArcanistLandCommitSet.php
Normal file
72
src/land/ArcanistLandCommitSet.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
27
src/land/ArcanistLandSymbol.php
Normal file
27
src/land/ArcanistLandSymbol.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
41
src/land/ArcanistLandTarget.php
Normal file
41
src/land/ArcanistLandTarget.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
1608
src/land/engine/ArcanistGitLandEngine.php
Normal file
1608
src/land/engine/ArcanistGitLandEngine.php
Normal file
File diff suppressed because it is too large
Load diff
1577
src/land/engine/ArcanistLandEngine.php
Normal file
1577
src/land/engine/ArcanistLandEngine.php
Normal file
File diff suppressed because it is too large
Load diff
1093
src/land/engine/ArcanistMercurialLandEngine.php
Normal file
1093
src/land/engine/ArcanistMercurialLandEngine.php
Normal file
File diff suppressed because it is too large
Load diff
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
36
src/query/ArcanistMercurialCommitMessageHardpointQuery.php
Normal file
36
src/query/ArcanistMercurialCommitMessageHardpointQuery.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
|
||||
}
|
11
src/query/ArcanistWorkflowMercurialHardpointQuery.php
Normal file
11
src/query/ArcanistWorkflowMercurialHardpointQuery.php
Normal file
|
@ -0,0 +1,11 @@
|
|||
<?php
|
||||
|
||||
abstract class ArcanistWorkflowMercurialHardpointQuery
|
||||
extends ArcanistRuntimeHardpointQuery {
|
||||
|
||||
final protected function canLoadHardpoint() {
|
||||
$api = $this->getRepositoryAPI();
|
||||
return ($api instanceof ArcanistMercurialAPI);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -1,8 +0,0 @@
|
|||
<?php
|
||||
|
||||
interface ArcanistDisplayRefInterface {
|
||||
|
||||
public function getDisplayRefObjectName();
|
||||
public function getDisplayRefTitle();
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
44
src/ref/build/ArcanistBuildBuildplanHardpointQuery.php
Normal file
44
src/ref/build/ArcanistBuildBuildplanHardpointQuery.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
102
src/ref/build/ArcanistBuildRef.php
Normal file
102
src/ref/build/ArcanistBuildRef.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
30
src/ref/build/ArcanistBuildSymbolRef.php
Normal file
30
src/ref/build/ArcanistBuildSymbolRef.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
43
src/ref/buildable/ArcanistBuildableBuildsHardpointQuery.php
Normal file
43
src/ref/buildable/ArcanistBuildableBuildsHardpointQuery.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
65
src/ref/buildable/ArcanistBuildableRef.php
Normal file
65
src/ref/buildable/ArcanistBuildableRef.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
30
src/ref/buildable/ArcanistBuildableSymbolRef.php
Normal file
30
src/ref/buildable/ArcanistBuildableSymbolRef.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
43
src/ref/buildplan/ArcanistBuildPlanRef.php
Normal file
43
src/ref/buildplan/ArcanistBuildPlanRef.php
Normal 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());
|
||||
}
|
||||
|
||||
}
|
30
src/ref/buildplan/ArcanistBuildPlanSymbolRef.php
Normal file
30
src/ref/buildplan/ArcanistBuildPlanSymbolRef.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
35
src/ref/revision/ArcanistRevisionAuthorHardpointQuery.php
Normal file
35
src/ref/revision/ArcanistRevisionAuthorHardpointQuery.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
60
src/ref/revision/ArcanistRevisionBuildableHardpointQuery.php
Normal file
60
src/ref/revision/ArcanistRevisionBuildableHardpointQuery.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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());
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
55
src/repository/graph/ArcanistCommitGraph.php
Normal file
55
src/repository/graph/ArcanistCommitGraph.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
62
src/repository/graph/ArcanistCommitGraphPartition.php
Normal file
62
src/repository/graph/ArcanistCommitGraphPartition.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
153
src/repository/graph/ArcanistCommitGraphPartitionQuery.php
Normal file
153
src/repository/graph/ArcanistCommitGraphPartitionQuery.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
97
src/repository/graph/ArcanistCommitGraphSet.php
Normal file
97
src/repository/graph/ArcanistCommitGraphSet.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
305
src/repository/graph/ArcanistCommitGraphSetQuery.php
Normal file
305
src/repository/graph/ArcanistCommitGraphSetQuery.php
Normal 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;
|
||||
}
|
||||
|
||||
|
||||
}
|
78
src/repository/graph/ArcanistCommitNode.php
Normal file
78
src/repository/graph/ArcanistCommitNode.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
|
||||
}
|
79
src/repository/graph/query/ArcanistCommitGraphQuery.php
Normal file
79
src/repository/graph/query/ArcanistCommitGraphQuery.php
Normal 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();
|
||||
|
||||
}
|
209
src/repository/graph/query/ArcanistGitCommitGraphQuery.php
Normal file
209
src/repository/graph/query/ArcanistGitCommitGraphQuery.php
Normal 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]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
216
src/repository/graph/query/ArcanistMercurialCommitGraphQuery.php
Normal file
216
src/repository/graph/query/ArcanistMercurialCommitGraphQuery.php
Normal 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).')';
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
246
src/repository/graph/view/ArcanistCommitGraphSetTreeView.php
Normal file
246
src/repository/graph/view/ArcanistCommitGraphSetTreeView.php
Normal 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();
|
||||
}
|
||||
|
||||
}
|
568
src/repository/graph/view/ArcanistCommitGraphSetView.php
Normal file
568
src/repository/graph/view/ArcanistCommitGraphSetView.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
195
src/repository/marker/ArcanistGitRepositoryMarkerQuery.php
Normal file
195
src/repository/marker/ArcanistGitRepositoryMarkerQuery.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
186
src/repository/marker/ArcanistMarkerRef.php
Normal file
186
src/repository/marker/ArcanistMarkerRef.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
128
src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php
Normal file
128
src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
121
src/repository/marker/ArcanistRepositoryMarkerQuery.php
Normal file
121
src/repository/marker/ArcanistRepositoryMarkerQuery.php
Normal 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);
|
||||
|
||||
}
|
35
src/repository/query/ArcanistRepositoryQuery.php
Normal file
35
src/repository/query/ArcanistRepositoryQuery.php
Normal 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);
|
||||
}
|
||||
|
||||
}
|
183
src/repository/raw/ArcanistGitRawCommit.php
Normal file
183
src/repository/raw/ArcanistGitRawCommit.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
63
src/repository/remote/ArcanistGitRepositoryRemoteQuery.php
Normal file
63
src/repository/remote/ArcanistGitRepositoryRemoteQuery.php
Normal 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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
101
src/repository/remote/ArcanistRemoteRef.php
Normal file
101
src/repository/remote/ArcanistRemoteRef.php
Normal 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
Loading…
Reference in a new issue