diff --git a/.gitignore b/.gitignore --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ # libphutil /src/.phutil_module_cache +/src/.cache # User extensions /externals/includes/* diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -40,6 +40,7 @@ 'ArcanistBookmarkWorkflow' => 'workflow/ArcanistBookmarkWorkflow.php', 'ArcanistBraceFormattingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistBraceFormattingXHPASTLinterRule.php', 'ArcanistBraceFormattingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistBraceFormattingXHPASTLinterRuleTestCase.php', + 'ArcanistBranchRef' => 'ref/ArcanistBranchRef.php', 'ArcanistBranchWorkflow' => 'workflow/ArcanistBranchWorkflow.php', 'ArcanistBrowseWorkflow' => 'workflow/ArcanistBrowseWorkflow.php', 'ArcanistBundle' => 'parser/ArcanistBundle.php', @@ -76,6 +77,7 @@ 'ArcanistCommentSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCommentSpacingXHPASTLinterRule.php', 'ArcanistCommentStyleXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistCommentStyleXHPASTLinterRule.php', 'ArcanistCommentStyleXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistCommentStyleXHPASTLinterRuleTestCase.php', + 'ArcanistCommitRef' => 'ref/ArcanistCommitRef.php', 'ArcanistCommitWorkflow' => 'workflow/ArcanistCommitWorkflow.php', 'ArcanistCompilerLintRenderer' => 'lint/renderer/ArcanistCompilerLintRenderer.php', 'ArcanistComposerLinter' => 'lint/linter/ArcanistComposerLinter.php', @@ -156,7 +158,9 @@ 'ArcanistGeneratedLinterTestCase' => 'lint/linter/__tests__/ArcanistGeneratedLinterTestCase.php', 'ArcanistGetConfigWorkflow' => 'workflow/ArcanistGetConfigWorkflow.php', 'ArcanistGitAPI' => 'repository/api/ArcanistGitAPI.php', + 'ArcanistGitHardpointLoader' => 'loader/ArcanistGitHardpointLoader.php', 'ArcanistGitLandEngine' => 'land/ArcanistGitLandEngine.php', + 'ArcanistGitRevisionHardpointLoader' => 'loader/ArcanistGitRevisionHardpointLoader.php', 'ArcanistGitUpstreamPath' => 'repository/api/ArcanistGitUpstreamPath.php', 'ArcanistGlobalVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistGlobalVariableXHPASTLinterRule.php', 'ArcanistGlobalVariableXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistGlobalVariableXHPASTLinterRuleTestCase.php', @@ -166,6 +170,7 @@ 'ArcanistGoTestResultParserTestCase' => 'unit/parser/__tests__/ArcanistGoTestResultParserTestCase.php', 'ArcanistHLintLinter' => 'lint/linter/ArcanistHLintLinter.php', 'ArcanistHLintLinterTestCase' => 'lint/linter/__tests__/ArcanistHLintLinterTestCase.php', + 'ArcanistHardpointLoader' => 'loader/ArcanistHardpointLoader.php', 'ArcanistHelpWorkflow' => 'workflow/ArcanistHelpWorkflow.php', 'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule.php', 'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase.php', @@ -239,10 +244,14 @@ 'ArcanistLowercaseFunctionsXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistLowercaseFunctionsXHPASTLinterRule.php', 'ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase.php', 'ArcanistMercurialAPI' => 'repository/api/ArcanistMercurialAPI.php', + 'ArcanistMercurialBranchCommitHardpointLoader' => 'loader/ArcanistMercurialBranchCommitHardpointLoader.php', + 'ArcanistMercurialHardpointLoader' => 'loader/ArcanistMercurialHardpointLoader.php', 'ArcanistMercurialParser' => 'repository/parser/ArcanistMercurialParser.php', 'ArcanistMercurialParserTestCase' => 'repository/parser/__tests__/ArcanistMercurialParserTestCase.php', + 'ArcanistMercurialWorkingCopyCommitHardpointLoader' => 'loader/ArcanistMercurialWorkingCopyCommitHardpointLoader.php', 'ArcanistMergeConflictLinter' => 'lint/linter/ArcanistMergeConflictLinter.php', 'ArcanistMergeConflictLinterTestCase' => 'lint/linter/__tests__/ArcanistMergeConflictLinterTestCase.php', + 'ArcanistMessageRevisionHardpointLoader' => 'loader/ArcanistMessageRevisionHardpointLoader.php', 'ArcanistMissingLinterException' => 'lint/linter/exception/ArcanistMissingLinterException.php', 'ArcanistModifierOrderingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistModifierOrderingXHPASTLinterRule.php', 'ArcanistModifierOrderingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistModifierOrderingXHPASTLinterRuleTestCase.php', @@ -306,6 +315,8 @@ 'ArcanistPyLintLinterTestCase' => 'lint/linter/__tests__/ArcanistPyLintLinterTestCase.php', 'ArcanistRaggedClassTreeEdgeXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistRaggedClassTreeEdgeXHPASTLinterRule.php', 'ArcanistRaggedClassTreeEdgeXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistRaggedClassTreeEdgeXHPASTLinterRuleTestCase.php', + 'ArcanistRef' => 'ref/ArcanistRef.php', + 'ArcanistRefQuery' => 'ref/ArcanistRefQuery.php', 'ArcanistRepositoryAPI' => 'repository/api/ArcanistRepositoryAPI.php', 'ArcanistRepositoryAPIMiscTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIMiscTestCase.php', 'ArcanistRepositoryAPIStateTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php', @@ -316,6 +327,8 @@ 'ArcanistReusedIteratorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistReusedIteratorXHPASTLinterRule.php', 'ArcanistReusedIteratorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistReusedIteratorXHPASTLinterRuleTestCase.php', 'ArcanistRevertWorkflow' => 'workflow/ArcanistRevertWorkflow.php', + 'ArcanistRevisionRef' => 'ref/ArcanistRevisionRef.php', + 'ArcanistRevisionRefSource' => 'ref/ArcanistRevisionRefSource.php', 'ArcanistRuboCopLinter' => 'lint/linter/ArcanistRuboCopLinter.php', 'ArcanistRuboCopLinterTestCase' => 'lint/linter/__tests__/ArcanistRuboCopLinterTestCase.php', 'ArcanistRubyLinter' => 'lint/linter/ArcanistRubyLinter.php', @@ -397,6 +410,7 @@ 'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php', 'ArcanistWorkflow' => 'workflow/ArcanistWorkflow.php', 'ArcanistWorkingCopyIdentity' => 'workingcopyidentity/ArcanistWorkingCopyIdentity.php', + 'ArcanistWorkingCopyStateRef' => 'ref/ArcanistWorkingCopyStateRef.php', 'ArcanistXHPASTLintNamingHook' => 'lint/linter/xhpast/ArcanistXHPASTLintNamingHook.php', 'ArcanistXHPASTLintNamingHookTestCase' => 'lint/linter/xhpast/__tests__/ArcanistXHPASTLintNamingHookTestCase.php', 'ArcanistXHPASTLintSwitchHook' => 'lint/linter/xhpast/ArcanistXHPASTLintSwitchHook.php', @@ -454,6 +468,7 @@ 'ArcanistBookmarkWorkflow' => 'ArcanistFeatureWorkflow', 'ArcanistBraceFormattingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistBraceFormattingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', + 'ArcanistBranchRef' => 'ArcanistRef', 'ArcanistBranchWorkflow' => 'ArcanistFeatureWorkflow', 'ArcanistBrowseWorkflow' => 'ArcanistWorkflow', 'ArcanistBundle' => 'Phobject', @@ -490,6 +505,7 @@ 'ArcanistCommentSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistCommentStyleXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistCommentStyleXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', + 'ArcanistCommitRef' => 'ArcanistRef', 'ArcanistCommitWorkflow' => 'ArcanistWorkflow', 'ArcanistCompilerLintRenderer' => 'ArcanistLintRenderer', 'ArcanistComposerLinter' => 'ArcanistLinter', @@ -570,7 +586,9 @@ 'ArcanistGeneratedLinterTestCase' => 'ArcanistLinterTestCase', 'ArcanistGetConfigWorkflow' => 'ArcanistWorkflow', 'ArcanistGitAPI' => 'ArcanistRepositoryAPI', + 'ArcanistGitHardpointLoader' => 'ArcanistHardpointLoader', 'ArcanistGitLandEngine' => 'ArcanistLandEngine', + 'ArcanistGitRevisionHardpointLoader' => 'ArcanistGitHardpointLoader', 'ArcanistGitUpstreamPath' => 'Phobject', 'ArcanistGlobalVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistGlobalVariableXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', @@ -580,6 +598,7 @@ 'ArcanistGoTestResultParserTestCase' => 'PhutilTestCase', 'ArcanistHLintLinter' => 'ArcanistExternalLinter', 'ArcanistHLintLinterTestCase' => 'ArcanistExternalLinterTestCase', + 'ArcanistHardpointLoader' => 'Phobject', 'ArcanistHelpWorkflow' => 'ArcanistWorkflow', 'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistHexadecimalNumericScalarCasingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', @@ -653,10 +672,14 @@ 'ArcanistLowercaseFunctionsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI', + 'ArcanistMercurialBranchCommitHardpointLoader' => 'ArcanistMercurialHardpointLoader', + 'ArcanistMercurialHardpointLoader' => 'ArcanistHardpointLoader', 'ArcanistMercurialParser' => 'Phobject', 'ArcanistMercurialParserTestCase' => 'PhutilTestCase', + 'ArcanistMercurialWorkingCopyCommitHardpointLoader' => 'ArcanistMercurialHardpointLoader', 'ArcanistMergeConflictLinter' => 'ArcanistLinter', 'ArcanistMergeConflictLinterTestCase' => 'ArcanistLinterTestCase', + 'ArcanistMessageRevisionHardpointLoader' => 'ArcanistHardpointLoader', 'ArcanistMissingLinterException' => 'Exception', 'ArcanistModifierOrderingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistModifierOrderingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', @@ -720,6 +743,8 @@ 'ArcanistPyLintLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistRaggedClassTreeEdgeXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistRaggedClassTreeEdgeXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', + 'ArcanistRef' => 'Phobject', + 'ArcanistRefQuery' => 'Phobject', 'ArcanistRepositoryAPI' => 'Phobject', 'ArcanistRepositoryAPIMiscTestCase' => 'PhutilTestCase', 'ArcanistRepositoryAPIStateTestCase' => 'PhutilTestCase', @@ -730,6 +755,8 @@ 'ArcanistReusedIteratorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistReusedIteratorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistRevertWorkflow' => 'ArcanistWorkflow', + 'ArcanistRevisionRef' => 'ArcanistRef', + 'ArcanistRevisionRefSource' => 'Phobject', 'ArcanistRuboCopLinter' => 'ArcanistExternalLinter', 'ArcanistRuboCopLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistRubyLinter' => 'ArcanistExternalLinter', @@ -811,6 +838,7 @@ 'ArcanistWhichWorkflow' => 'ArcanistWorkflow', 'ArcanistWorkflow' => 'Phobject', 'ArcanistWorkingCopyIdentity' => 'Phobject', + 'ArcanistWorkingCopyStateRef' => 'ArcanistRef', 'ArcanistXHPASTLintNamingHook' => 'Phobject', 'ArcanistXHPASTLintNamingHookTestCase' => 'PhutilTestCase', 'ArcanistXHPASTLintSwitchHook' => 'Phobject', diff --git a/src/loader/ArcanistGitHardpointLoader.php b/src/loader/ArcanistGitHardpointLoader.php new file mode 100644 --- /dev/null +++ b/src/loader/ArcanistGitHardpointLoader.php @@ -0,0 +1,10 @@ +newQuery($refs) + ->needHardpoints( + array( + 'commitRef', + )) + ->execute(); + + $hashes = array(); + $map = array(); + foreach ($refs as $ref_key => $ref) { + $commit = $ref->getCommitRef(); + + $commit_hashes = array(); + + $commit_hashes[] = array( + 'gtcm', + $commit->getCommitHash(), + ); + + $commit_hashes[] = array( + 'gttr', + $commit->getTreeHash(), + ); + + foreach ($commit_hashes as $hash) { + $hashes[] = $hash; + $hash_key = $this->getHashKey($hash); + $map[$hash_key][$ref_key] = $ref; + } + } + + $results = array(); + if ($hashes) { + $revisions = $this->callMethod( + 'differential.query', + array( + 'commitHashes' => $hashes, + )); + + foreach ($revisions as $dict) { + $revision_hashes = idx($dict, 'hashes'); + if (!$revision_hashes) { + continue; + } + + $revision_ref = ArcanistRevisionRef::newFromConduit($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; + } + } + } + } + + return $results; + } + + private function getHashKey(array $hash) { + return $hash[0].':'.$hash[1]; + } + +} diff --git a/src/loader/ArcanistHardpointLoader.php b/src/loader/ArcanistHardpointLoader.php new file mode 100644 --- /dev/null +++ b/src/loader/ArcanistHardpointLoader.php @@ -0,0 +1,58 @@ +query = $query; + return $this; + } + + final public function getQuery() { + return $this->query; + } + + final public function getConduitClient() { + return $this->getQuery()->getConduitClient(); + } + + final protected function newQuery(array $refs) { + return id(new ArcanistRefQuery()) + ->setRepositoryAPI($this->getQuery()->getRepositoryAPI()) + ->setConduitClient($this->getQuery()->getConduitClient()) + ->setRefs($refs); + } + + final public function getLoaderKey() { + return $this->getPhobjectClassConstant('LOADERKEY', 64); + } + + final public static function getAllLoaders() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->setUniqueMethod('getLoaderKey') + ->execute(); + } + + final public function callMethod($method, array $parameters) { + return $this->newConduitCall($method, $parameters)->resolve(); + } + + final public function newConduitCall($method, array $parameters) { + return $this->getConduitClient()->callMethod($method, $parameters); + } + + final protected function newFutureIterator(array $futures) { + return id(new FutureIterator($futures)) + ->limit(16); + } + +} diff --git a/src/loader/ArcanistMercurialBranchCommitHardpointLoader.php b/src/loader/ArcanistMercurialBranchCommitHardpointLoader.php new file mode 100644 --- /dev/null +++ b/src/loader/ArcanistMercurialBranchCommitHardpointLoader.php @@ -0,0 +1,49 @@ +getQuery()->getRepositoryAPI(); + + $futures = array(); + foreach ($refs as $ref_key => $branch) { + $branch_name = $branch->getBranchName(); + + $futures[$ref_key] = $api->execFutureLocal( + 'log -l 1 --template %s -r %s', + "{node}\1{date|hgdate}\1{p1node}\1{desc|firstline}\1{desc}", + hgsprintf('%s', $branch_name)); + } + + $results = array(); + + $iterator = $this->newFutureIterator($futures); + foreach ($iterator as $ref_key => $future) { + list($info) = $future->resolvex(); + + $fields = explode("\1", trim($info), 5); + list($hash, $epoch, $parent, $desc, $text) = $fields; + + $commit_ref = $api->newCommitRef() + ->setCommitHash($hash) + ->setCommitEpoch((int)$epoch) + ->attachMessage($text); + + $results[$ref_key] = $commit_ref; + } + + return $results; + } + +} diff --git a/src/loader/ArcanistMercurialHardpointLoader.php b/src/loader/ArcanistMercurialHardpointLoader.php new file mode 100644 --- /dev/null +++ b/src/loader/ArcanistMercurialHardpointLoader.php @@ -0,0 +1,10 @@ + $ref) { + if ($ref->hasAttachedHardpoint('branchRef')) { + $branch_refs[$ref_key] = $ref->getBranchRef(); + } + } + + if ($branch_refs) { + $this->newQuery($branch_refs) + ->needHardpoints( + array( + 'commitRef', + )) + ->execute(); + } + + return mpull($branch_refs, 'getCommitRef'); + } + +} diff --git a/src/loader/ArcanistMessageRevisionHardpointLoader.php b/src/loader/ArcanistMessageRevisionHardpointLoader.php new file mode 100644 --- /dev/null +++ b/src/loader/ArcanistMessageRevisionHardpointLoader.php @@ -0,0 +1,82 @@ +newQuery($refs) + ->needHardpoints( + array( + 'commitRef', + )) + ->execute(); + + $commit_refs = array(); + foreach ($refs as $ref) { + $commit_refs[] = $ref->getCommitRef(); + } + + $this->newQuery($commit_refs) + ->needHardpoints( + array( + 'message', + )) + ->execute(); + + $map = array(); + foreach ($refs as $ref_key => $ref) { + $commit_ref = $ref->getCommitRef(); + $corpus = $commit_ref->getMessage(); + + $id = null; + try { + $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($corpus); + $id = $message->getRevisionID(); + } catch (ArcanistUsageException $ex) { + continue; + } + + if (!$id) { + continue; + } + + $map[$id][$ref_key] = $ref; + } + + $results = array(); + if ($map) { + $revisions = $this->callMethod( + 'differential.query', + array( + 'ids' => array_keys($map), + )); + + foreach ($revisions as $dict) { + $revision_ref = ArcanistRevisionRef::newFromConduit($dict); + $id = $dict['id']; + + $state_refs = idx($map, $id, array()); + foreach ($state_refs as $ref_key => $state_ref) { + $results[$ref_key][] = $revision_ref; + } + } + } + + return $results; + } + +} diff --git a/src/ref/ArcanistBranchRef.php b/src/ref/ArcanistBranchRef.php new file mode 100644 --- /dev/null +++ b/src/ref/ArcanistBranchRef.php @@ -0,0 +1,57 @@ +getBranchName()); + } + + public function defineHardpoints() { + return array( + 'commitRef' => array( + 'type' => 'ArcanistCommitRef', + ), + ); + } + + 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('commitRef', $ref); + } + + public function getCommitRef() { + return $this->getHardpoint('commitRef'); + } + +} diff --git a/src/ref/ArcanistCommitRef.php b/src/ref/ArcanistCommitRef.php new file mode 100644 --- /dev/null +++ b/src/ref/ArcanistCommitRef.php @@ -0,0 +1,76 @@ +getCommitHash()); + } + + public function defineHardpoints() { + return array( + 'message' => array( + 'type' => 'string', + ), + ); + } + + 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 setCommitEpoch($commit_epoch) { + $this->commitEpoch = $commit_epoch; + return $this; + } + + public function getCommitEpoch() { + return $this->commitEpoch; + } + + public function setAuthorEpoch($author_epoch) { + $this->authorEpoch = $author_epoch; + return $this; + } + + public function getAuthorEpoch() { + return $this->authorEpoch; + } + + public function getSummary() { + $message = $this->getMessage(); + + $message = trim($message); + $lines = phutil_split_lines($message, false); + + return head($lines); + } + + public function attachMessage($message) { + return $this->attachHardpoint('message', $message); + } + + public function getMessage() { + return $this->getHardpoint('message'); + } + +} diff --git a/src/ref/ArcanistRef.php b/src/ref/ArcanistRef.php new file mode 100644 --- /dev/null +++ b/src/ref/ArcanistRef.php @@ -0,0 +1,106 @@ +getHardpointMap(); + return isset($map[$hardpoint]); + } + + final public function hasAttachedHardpoint($hardpoint) { + if (array_key_exists($hardpoint, $this->hardpoints)) { + return true; + } + + return $this->canReadHardpoint($hardpoint); + } + + final public function attachHardpoint($hardpoint, $value) { + if (!$this->hasHardpoint($hardpoint)) { + throw new Exception(pht('No hardpoint "%s".', $hardpoint)); + } + + $this->hardpoints[$hardpoint] = $value; + + return $this; + } + + final public function appendHardpoint($hardpoint, array $value) { + if (!$this->isVectorHardpoint($hardpoint)) { + throw new Exception( + pht( + 'Hardpoint "%s" is not a vector hardpoint.', + $hardpoint)); + } + + if (!isset($this->hardpoints[$hardpoint])) { + $this->hardpoints[$hardpoint] = array(); + } + + $this->hardpoints[$hardpoint] = $this->mergeHardpoint( + $hardpoint, + $this->hardpoints[$hardpoint], + $value); + + return $this; + } + + protected function mergeHardpoint($hardpoint, array $src, array $new) { + foreach ($new as $value) { + $src[] = $value; + } + return $src; + } + + final public function isVectorHardpoint($hardpoint) { + if (!$this->hasHardpoint($hardpoint)) { + return false; + } + + $map = $this->getHardpointMap(); + $spec = idx($map, $hardpoint, array()); + + return (idx($spec, 'vector') === true); + } + + final public function getHardpoint($hardpoint) { + if (!$this->hasAttachedHardpoint($hardpoint)) { + if (!$this->hasHardpoint($hardpoint)) { + throw new Exception( + pht( + 'Ref does not have hardpoint "%s"!', + $hardpoint)); + } else { + throw new Exception( + pht( + 'Hardpoint "%s" is not attached!', + $hardpoint)); + } + } + + if (array_key_exists($hardpoint, $this->hardpoints)) { + return $this->hardpoints[$hardpoint]; + } + + return $this->readHardpoint($hardpoint); + } + + private function getHardpointMap() { + return $this->defineHardpoints(); + } + + protected function canReadHardpoint($hardpoint) { + return false; + } + + protected function readHardpoint($hardpoint) { + throw new Exception(pht('Can not read hardpoint "%s".', $hardpoint)); + } + +} diff --git a/src/ref/ArcanistRefQuery.php b/src/ref/ArcanistRefQuery.php new file mode 100644 --- /dev/null +++ b/src/ref/ArcanistRefQuery.php @@ -0,0 +1,147 @@ +refs = $refs; + return $this; + } + + public function getRefs() { + return $this->refs; + } + + public function setRepositoryAPI(ArcanistRepositoryAPI $repository_api) { + $this->repositoryAPI = $repository_api; + return $this; + } + + public function getRepositoryAPI() { + return $this->repositoryAPI; + } + + public function setConduitClient(ConduitClient $conduit_client) { + $this->conduitClient = $conduit_client; + return $this; + } + + public function getConduitClient() { + return $this->conduitClient; + } + + public function needHardpoints(array $hardpoints) { + $this->hardpoints = $hardpoints; + return $this; + } + + public function execute() { + $refs = $this->getRefs(); + + if ($this->refs === null) { + throw new PhutilInvalidStateException('setRefs'); + } + + if ($this->hardpoints === null) { + throw new PhutilInvalidStateException('needHardpoints'); + } + + $api = $this->getRepositoryAPI(); + $all_loaders = ArcanistHardpointLoader::getAllLoaders(); + + $loaders = array(); + foreach ($all_loaders as $loader_key => $loader) { + if (!$loader->canLoadRepositoryAPI($api)) { + continue; + } + + $loaders[$loader_key] = id(clone $loader) + ->setQuery($this); + } + + foreach ($this->hardpoints as $hardpoint) { + $load = array(); + $need = array(); + $has_hardpoint = false; + foreach ($refs as $ref_key => $ref) { + if (!$ref->hasHardpoint($hardpoint)) { + continue; + } + + $has_hardpoint = true; + + if ($ref->hasAttachedHardpoint($hardpoint)) { + continue; + } + + foreach ($loaders as $loader_key => $loader) { + if (!$loader->canLoadRef($ref)) { + continue; + } + + if (!$loader->canLoadHardpoint($ref, $hardpoint)) { + continue; + } + + $load[$loader_key][$ref_key] = $ref; + } + + $need[$ref_key] = $ref_key; + } + + if ($refs && !$has_hardpoint) { + throw new Exception( + pht( + 'No ref in query has hardpoint "%s".', + $hardpoint)); + } + + $vectors = array(); + foreach ($need as $ref_key) { + $ref = $refs[$ref_key]; + if ($ref->isVectorHardpoint($hardpoint)) { + $vectors[$ref_key] = $ref_key; + $ref->attachHardpoint($hardpoint, array()); + } + } + + foreach ($load as $loader_key => $loader_refs) { + $loader_refs = array_select_keys($loader_refs, $need); + + $loader = $loaders[$loader_key]; + $data = $loader->loadHardpoints($loader_refs, $hardpoint); + + foreach ($data as $ref_key => $value) { + $ref = $refs[$ref_key]; + if (isset($vectors[$ref_key])) { + $ref->appendHardpoint($hardpoint, $value); + } else { + unset($need[$ref_key]); + $ref->attachHardpoint($hardpoint, $value); + } + } + } + + foreach ($vectors as $ref_key) { + unset($need[$ref_key]); + } + + if ($need) { + throw new Exception( + pht( + 'Nothing could attach data to hardpoint "%s" for ref "%s".', + $hardpoint, + $refs[head($need)]->getRefIdentifier())); + } + } + + return $refs; + } + +} diff --git a/src/ref/ArcanistRevisionRef.php b/src/ref/ArcanistRevisionRef.php new file mode 100644 --- /dev/null +++ b/src/ref/ArcanistRevisionRef.php @@ -0,0 +1,52 @@ +getMonogram()); + } + + public function defineHardpoints() { + return array(); + } + + public static function newFromConduit(array $dict) { + $ref = new self(); + $ref->parameters = $dict; + return $ref; + } + + public function getMonogram() { + return 'D'.$this->getID(); + } + + public function getStatusDisplayName() { + return idx($this->parameters, 'statusName'); + } + + public function getFullName() { + return pht('%s: %s', $this->getMonogram(), $this->getName()); + } + + public function getID() { + return idx($this->parameters, 'id'); + } + + public function getName() { + return idx($this->parameters, 'title'); + } + + public function addSource(ArcanistRevisionRefSource $source) { + $this->sources[] = $source; + return $this; + } + + public function getSources() { + return $this->sources; + } + +} diff --git a/src/ref/ArcanistRevisionRefSource.php b/src/ref/ArcanistRevisionRefSource.php new file mode 100644 --- /dev/null +++ b/src/ref/ArcanistRevisionRefSource.php @@ -0,0 +1,4 @@ + array( + 'type' => 'ArcanistCommitRef', + ), + 'branchRef' => array( + 'type' => 'ArcanistBranchRef', + ), + 'revisionRefs' => array( + 'type' => 'ArcanistRevisionRef', + 'vector' => true, + ), + ); + } + + public function attachBranchRef(ArcanistBranchRef $branch_ref) { + return $this->attachHardpoint('branchRef', $branch_ref); + } + + public function getBranchRef() { + return $this->getHardpoint('branchRef'); + } + + public function setCommitRef(ArcanistCommitRef $commit_ref) { + return $this->attachHardpoint('commitRef', $commit_ref); + } + + public function getCommitRef() { + return $this->getHardpoint('commitRef'); + } + + public function getRevisionRefs() { + return $this->getHardpoint('revisionRefs'); + } + + public function getRevisionRef() { + if ($this->hasAmbiguousRevisionRefs()) { + throw new Exception( + pht('State has multiple ambiguous revisions refs.')); + } + + $refs = $this->getRevisionRefs(); + if ($refs) { + return head($refs); + } + + return null; + } + + public function hasAmbiguousRevisionRefs() { + return (count($this->getRevisionRefs()) > 1); + } + + protected function canReadHardpoint($hardpoint) { + switch ($hardpoint) { + case 'commitRef': + // If we have a branch ref, we can try to read the commit ref from the + // branch ref. + if ($this->hasAttachedHardpoint('branchRef')) { + if ($this->getBranchRef()->hasAttachedHardpoint('commitRef')) { + return true; + } + } + break; + } + + return false; + } + + protected function readHardpoint($hardpoint) { + switch ($hardpoint) { + case 'commitRef': + return $this->getBranchRef()->getCommitRef(); + } + + return parent::readHardpoint($hardpoint); + } + + protected function mergeHardpoint($hardpoint, array $src, array $new) { + if ($hardpoint == 'revisionRefs') { + $src = mpull($src, null, 'getID'); + $new = mpull($new, null, 'getID'); + + foreach ($new as $id => $ref) { + if (isset($src[$id])) { + foreach ($ref->getSources() as $source) { + $src[$id]->addSource($source); + } + } else { + $src[$id] = $ref; + } + } + + return array_values($src); + } + + return parent::mergeHardpoint($hardpoint, $src, $new); + } + +} diff --git a/src/repository/api/ArcanistGitAPI.php b/src/repository/api/ArcanistGitAPI.php --- a/src/repository/api/ArcanistGitAPI.php +++ b/src/repository/api/ArcanistGitAPI.php @@ -1014,6 +1014,7 @@ $result[] = array( 'current' => ($branch === $current), 'name' => $branch, + 'ref' => $ref, 'hash' => $hash, 'tree' => $tree, 'epoch' => (int)$epoch, @@ -1026,6 +1027,27 @@ 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 getWorkingCopyRevision() { list($stdout) = $this->execxLocal('rev-parse HEAD'); return rtrim($stdout, "\n"); diff --git a/src/repository/api/ArcanistMercurialAPI.php b/src/repository/api/ArcanistMercurialAPI.php --- a/src/repository/api/ArcanistMercurialAPI.php +++ b/src/repository/api/ArcanistMercurialAPI.php @@ -583,6 +583,19 @@ 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 hasLocalCommit($commit) { try { $this->getCanonicalRevisionName($commit); diff --git a/src/repository/api/ArcanistRepositoryAPI.php b/src/repository/api/ArcanistRepositoryAPI.php --- a/src/repository/api/ArcanistRepositoryAPI.php +++ b/src/repository/api/ArcanistRepositoryAPI.php @@ -375,6 +375,10 @@ return array(); } + public function getAllBranchRefs() { + throw new ArcanistCapabilityNotSupportedException($this); + } + public function hasLocalCommit($commit) { throw new ArcanistCapabilityNotSupportedException($this); } @@ -668,4 +672,12 @@ return null; } + final public function newCommitRef() { + return new ArcanistCommitRef(); + } + + final public function newBranchRef() { + return new ArcanistBranchRef(); + } + } diff --git a/src/workflow/ArcanistFeatureWorkflow.php b/src/workflow/ArcanistFeatureWorkflow.php --- a/src/workflow/ArcanistFeatureWorkflow.php +++ b/src/workflow/ArcanistFeatureWorkflow.php @@ -86,15 +86,26 @@ return $this->checkoutBranch($names); } - $branches = $repository_api->getAllBranches(); + $branches = $repository_api->getAllBranchRefs(); if (!$branches) { throw new ArcanistUsageException( pht('No branches in this working copy.')); } - $branches = $this->loadCommitInfo($branches); - $revisions = $this->loadRevisions($branches); - $this->printBranches($branches, $revisions); + $states = array(); + foreach ($branches as $branch) { + $states[] = $this->newWorkingCopyStateRef() + ->attachBranchRef($branch); + } + + $this->newRefQuery($states) + ->needHardpoints( + array( + 'revisionRefs', + )) + ->execute(); + + $this->printBranches($states); return 0; } @@ -171,99 +182,7 @@ return $err; } - private function loadCommitInfo(array $branches) { - $repository_api = $this->getRepositoryAPI(); - - $branches = ipull($branches, null, 'name'); - - if ($repository_api instanceof ArcanistMercurialAPI) { - $futures = array(); - foreach ($branches as $branch) { - $futures[$branch['name']] = $repository_api->execFutureLocal( - 'log -l 1 --template %s -r %s', - "{node}\1{date|hgdate}\1{p1node}\1{desc|firstline}\1{desc}", - hgsprintf('%s', $branch['name'])); - } - - $futures = id(new FutureIterator($futures)) - ->limit(16); - foreach ($futures as $name => $future) { - list($info) = $future->resolvex(); - - $fields = explode("\1", trim($info), 5); - list($hash, $epoch, $tree, $desc, $text) = $fields; - - $branches[$name] += array( - 'hash' => $hash, - 'desc' => $desc, - 'tree' => $tree, - 'epoch' => (int)$epoch, - 'text' => $text, - ); - } - } - - foreach ($branches as $name => $branch) { - $text = $branch['text']; - - try { - $message = ArcanistDifferentialCommitMessage::newFromRawCorpus($text); - $id = $message->getRevisionID(); - - $branch['revisionID'] = $id; - } catch (ArcanistUsageException $ex) { - // In case of invalid commit message which fails the parsing, - // do nothing. - $branch['revisionID'] = null; - } - - $branches[$name] = $branch; - } - - return $branches; - } - - private function loadRevisions(array $branches) { - $ids = array(); - $hashes = array(); - - foreach ($branches as $branch) { - if ($branch['revisionID']) { - $ids[] = $branch['revisionID']; - } - $hashes[] = array('gtcm', $branch['hash']); - $hashes[] = array('gttr', $branch['tree']); - } - - $calls = array(); - - if ($ids) { - $calls[] = $this->getConduit()->callMethod( - 'differential.query', - array( - 'ids' => $ids, - )); - } - - if ($hashes) { - $calls[] = $this->getConduit()->callMethod( - 'differential.query', - array( - 'commitHashes' => $hashes, - )); - } - - $results = array(); - foreach (new FutureIterator($calls) as $call) { - $results[] = $call->resolve(); - } - - return array_mergev($results); - } - - private function printBranches(array $branches, array $revisions) { - $revisions = ipull($revisions, null, 'id'); - + private function printBranches(array $states) { static $color_map = array( 'Closed' => 'cyan', 'Needs Review' => 'magenta', @@ -282,48 +201,45 @@ ); $out = array(); - foreach ($branches as $branch) { - $revision = idx($revisions, idx($branch, 'revisionID')); - - // If we haven't identified a revision by ID, try to identify it by hash. - if (!$revision) { - foreach ($revisions as $rev) { - $hashes = idx($rev, 'hashes', array()); - foreach ($hashes as $hash) { - if (($hash[0] == 'gtcm' && $hash[1] == $branch['hash']) || - ($hash[0] == 'gttr' && $hash[1] == $branch['tree'])) { - $revision = $rev; - break; - } - } - } - } + foreach ($states as $state) { + $branch = $state->getBranchRef(); - if ($revision) { - $desc = 'D'.$revision['id'].': '.$revision['title']; - $status = $revision['statusName']; + $revision = null; + if ($state->hasAmbiguousRevisionRefs()) { + $status = pht('Ambiguous Revision'); } else { - $desc = $branch['desc']; - $status = pht('No Revision'); + $revision = $state->getRevisionRef(); + if ($revision) { + $status = $revision->getStatusDisplayName(); + } else { + $status = pht('No Revision'); + } } - if (!$this->getArgument('view-all') && !$branch['current']) { + if (!$this->getArgument('view-all') && !$branch->getIsCurrentBranch()) { if ($status == 'Closed' || $status == 'Abandoned') { continue; } } - $epoch = $branch['epoch']; + $commit = $branch->getCommitRef(); + $epoch = $commit->getCommitEpoch(); $color = idx($color_map, $status, 'default'); $ssort = sprintf('%d%012d', idx($ssort_map, $status, 0), $epoch); + if ($revision) { + $desc = $revision->getFullName(); + } else { + $desc = $commit->getSummary(); + } + $out[] = array( - 'name' => $branch['name'], - 'current' => $branch['current'], + 'name' => $branch->getBranchName(), + 'current' => $branch->getIsCurrentBranch(), 'status' => $status, 'desc' => $desc, - 'revision' => $revision ? $revision['id'] : null, + 'revision' => $revision ? $revision->getID() : null, 'color' => $color, 'esort' => $epoch, 'epoch' => $epoch, diff --git a/src/workflow/ArcanistWorkflow.php b/src/workflow/ArcanistWorkflow.php --- a/src/workflow/ArcanistWorkflow.php +++ b/src/workflow/ArcanistWorkflow.php @@ -2062,5 +2062,16 @@ return $map; } + final protected function newWorkingCopyStateRef() { + return new ArcanistWorkingCopyStateRef(); + } + + final protected function newRefQuery(array $refs) { + assert_instances_of($refs, 'ArcanistRef'); + return id(new ArcanistRefQuery()) + ->setRepositoryAPI($this->getRepositoryAPI()) + ->setConduitClient($this->getConduit()) + ->setRefs($refs); + } }