From 599ba0f999fd5a1dfef700dc30f2ac7e19f89f36 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 8 Jun 2020 08:32:03 -0700 Subject: [PATCH] Provide a more powerful query mechanism for "markers" (branches/bookmarks) Summary: Ref T13546. Various Arcanist workflows, and particularly the MercurialAPI, currently repeat quite a lot of code around parsing branches and bookmarks. In modern Mercurial, we can generally use the "head()" and "bookmark()" revsets to do this fairly sensibly. This change mostly adds //more// code (and introduces "arc bookmarks" and "arc branches" as replacements for "arc bookmark" and "arc branch") but followups should be able to mostly delete code. Test Plan: Ran "arc branches" and "arc bookmarks" in Git and Mercurial. Maniphest Tasks: T13546 Differential Revision: https://secure.phabricator.com/D21333 --- src/__phutil_library_map__.php | 18 ++- src/console/view/PhutilConsoleTable.php | 14 +- ...rcanistWorkingCopyCommitHardpointQuery.php | 39 ----- src/ref/ArcanistBranchRef.php | 57 ------- src/ref/revision/ArcanistRevisionRef.php | 4 + src/repository/api/ArcanistGitAPI.php | 18 ++- src/repository/api/ArcanistMercurialAPI.php | 12 +- src/repository/api/ArcanistRepositoryAPI.php | 21 ++- .../ArcanistGitRepositoryMarkerQuery.php | 125 +++++++++++++++ src/repository/marker/ArcanistMarkerRef.php | 120 ++++++++++++++ ...ArcanistMercurialRepositoryMarkerQuery.php | 147 ++++++++++++++++++ .../marker/ArcanistRepositoryMarkerQuery.php | 63 ++++++++ src/workflow/ArcanistBookmarksWorkflow.php | 43 +++++ src/workflow/ArcanistBranchesWorkflow.php | 43 +++++ src/workflow/ArcanistFeatureBaseWorkflow.php | 6 +- src/workflow/ArcanistMarkersWorkflow.php | 118 ++++++++++++++ 16 files changed, 736 insertions(+), 112 deletions(-) delete mode 100644 src/query/ArcanistWorkingCopyCommitHardpointQuery.php delete mode 100644 src/ref/ArcanistBranchRef.php create mode 100644 src/repository/marker/ArcanistGitRepositoryMarkerQuery.php create mode 100644 src/repository/marker/ArcanistMarkerRef.php create mode 100644 src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php create mode 100644 src/repository/marker/ArcanistRepositoryMarkerQuery.php create mode 100644 src/workflow/ArcanistBookmarksWorkflow.php create mode 100644 src/workflow/ArcanistBranchesWorkflow.php create mode 100644 src/workflow/ArcanistMarkersWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 8d45be14..9cac118f 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -50,11 +50,12 @@ phutil_register_library_map(array( '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', @@ -222,6 +223,7 @@ phutil_register_library_map(array( 'ArcanistGitLocalState' => 'repository/state/ArcanistGitLocalState.php', 'ArcanistGitRawCommit' => 'repository/raw/ArcanistGitRawCommit.php', 'ArcanistGitRawCommitTestCase' => 'repository/raw/__tests__/ArcanistGitRawCommitTestCase.php', + 'ArcanistGitRepositoryMarkerQuery' => 'repository/marker/ArcanistGitRepositoryMarkerQuery.php', 'ArcanistGitUpstreamPath' => 'repository/api/ArcanistGitUpstreamPath.php', 'ArcanistGitWorkingCopy' => 'workingcopy/ArcanistGitWorkingCopy.php', 'ArcanistGitWorkingCopyRevisionHardpointQuery' => 'query/ArcanistGitWorkingCopyRevisionHardpointQuery.php', @@ -325,12 +327,15 @@ phutil_register_library_map(array( 'ArcanistLogicalOperatorsXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistLogicalOperatorsXHPASTLinterRuleTestCase.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', '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', 'ArcanistMercurialWorkingCopy' => 'workingcopy/ArcanistMercurialWorkingCopy.php', 'ArcanistMercurialWorkingCopyRevisionHardpointQuery' => 'query/ArcanistMercurialWorkingCopyRevisionHardpointQuery.php', 'ArcanistMergeConflictLinter' => 'lint/linter/ArcanistMergeConflictLinter.php', @@ -415,6 +420,7 @@ phutil_register_library_map(array( 'ArcanistRepositoryAPIMiscTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIMiscTestCase.php', 'ArcanistRepositoryAPIStateTestCase' => 'repository/api/__tests__/ArcanistRepositoryAPIStateTestCase.php', 'ArcanistRepositoryLocalState' => 'repository/state/ArcanistRepositoryLocalState.php', + 'ArcanistRepositoryMarkerQuery' => 'repository/marker/ArcanistRepositoryMarkerQuery.php', 'ArcanistRepositoryRef' => 'ref/ArcanistRepositoryRef.php', 'ArcanistReusedAsIteratorXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistReusedAsIteratorXHPASTLinterRule.php', 'ArcanistReusedAsIteratorXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistReusedAsIteratorXHPASTLinterRuleTestCase.php', @@ -538,7 +544,6 @@ phutil_register_library_map(array( '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', @@ -1059,11 +1064,12 @@ phutil_register_library_map(array( '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', @@ -1245,6 +1251,7 @@ phutil_register_library_map(array( 'ArcanistGitLocalState' => 'ArcanistRepositoryLocalState', 'ArcanistGitRawCommit' => 'Phobject', 'ArcanistGitRawCommitTestCase' => 'PhutilTestCase', + 'ArcanistGitRepositoryMarkerQuery' => 'ArcanistRepositoryMarkerQuery', 'ArcanistGitUpstreamPath' => 'Phobject', 'ArcanistGitWorkingCopy' => 'ArcanistWorkingCopy', 'ArcanistGitWorkingCopyRevisionHardpointQuery' => 'ArcanistWorkflowGitHardpointQuery', @@ -1348,12 +1355,15 @@ phutil_register_library_map(array( 'ArcanistLogicalOperatorsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistLowercaseFunctionsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', + 'ArcanistMarkerRef' => 'ArcanistRef', + 'ArcanistMarkersWorkflow' => 'ArcanistArcWorkflow', 'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI', 'ArcanistMercurialCommitMessageHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery', 'ArcanistMercurialLandEngine' => 'ArcanistLandEngine', 'ArcanistMercurialLocalState' => 'ArcanistRepositoryLocalState', 'ArcanistMercurialParser' => 'Phobject', 'ArcanistMercurialParserTestCase' => 'PhutilTestCase', + 'ArcanistMercurialRepositoryMarkerQuery' => 'ArcanistRepositoryMarkerQuery', 'ArcanistMercurialWorkingCopy' => 'ArcanistWorkingCopy', 'ArcanistMercurialWorkingCopyRevisionHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery', 'ArcanistMergeConflictLinter' => 'ArcanistLinter', @@ -1441,6 +1451,7 @@ phutil_register_library_map(array( 'ArcanistRepositoryAPIMiscTestCase' => 'PhutilTestCase', 'ArcanistRepositoryAPIStateTestCase' => 'PhutilTestCase', 'ArcanistRepositoryLocalState' => 'Phobject', + 'ArcanistRepositoryMarkerQuery' => 'Phobject', 'ArcanistRepositoryRef' => 'ArcanistRef', 'ArcanistReusedAsIteratorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistReusedAsIteratorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', @@ -1571,7 +1582,6 @@ phutil_register_library_map(array( 'ArcanistWorkflowInformation' => 'Phobject', 'ArcanistWorkflowMercurialHardpointQuery' => 'ArcanistRuntimeHardpointQuery', 'ArcanistWorkingCopy' => 'Phobject', - 'ArcanistWorkingCopyCommitHardpointQuery' => 'ArcanistRuntimeHardpointQuery', 'ArcanistWorkingCopyConfigurationSource' => 'ArcanistFilesystemConfigurationSource', 'ArcanistWorkingCopyIdentity' => 'Phobject', 'ArcanistWorkingCopyPath' => 'Phobject', diff --git a/src/console/view/PhutilConsoleTable.php b/src/console/view/PhutilConsoleTable.php index 7c29f991..392ff813 100644 --- a/src/console/view/PhutilConsoleTable.php +++ b/src/console/view/PhutilConsoleTable.php @@ -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 )------------------------------------------------------------ */ diff --git a/src/query/ArcanistWorkingCopyCommitHardpointQuery.php b/src/query/ArcanistWorkingCopyCommitHardpointQuery.php deleted file mode 100644 index 9f2b4672..00000000 --- a/src/query/ArcanistWorkingCopyCommitHardpointQuery.php +++ /dev/null @@ -1,39 +0,0 @@ -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); - } - -} diff --git a/src/ref/ArcanistBranchRef.php b/src/ref/ArcanistBranchRef.php deleted file mode 100644 index 067e5a2e..00000000 --- a/src/ref/ArcanistBranchRef.php +++ /dev/null @@ -1,57 +0,0 @@ -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); - } - -} diff --git a/src/ref/revision/ArcanistRevisionRef.php b/src/ref/revision/ArcanistRevisionRef.php index a6cd3c01..9ec2fb87 100644 --- a/src/ref/revision/ArcanistRevisionRef.php +++ b/src/ref/revision/ArcanistRevisionRef.php @@ -72,6 +72,10 @@ final class ArcanistRevisionRef return idxv($this->parameters, array('fields', 'status', 'name')); } + public function getStatusANSIColor() { + return idxv($this->parameters, array('fields', 'status', 'color.ansi')); + } + public function isStatusChangesPlanned() { $status = $this->getStatus(); return ($status === 'changes-planned'); diff --git a/src/repository/api/ArcanistGitAPI.php b/src/repository/api/ArcanistGitAPI.php index 0cf1713e..2c16a570 100644 --- a/src/repository/api/ArcanistGitAPI.php +++ b/src/repository/api/ArcanistGitAPI.php @@ -1113,10 +1113,9 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI { ->setCommitEpoch($branch['epoch']) ->attachMessage($branch['text']); - $refs[] = $this->newBranchRef() - ->setBranchName($branch['name']) - ->setRefName($branch['ref']) - ->setIsCurrentBranch($branch['current']) + $refs[] = $this->newMarkerRef() + ->setName($branch['name']) + ->setIsActive($branch['current']) ->attachCommitRef($commit_ref); } @@ -1770,4 +1769,15 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI { return trim($stdout); } + protected function newSupportedMarkerTypes() { + return array( + ArcanistMarkerRef::TYPE_BRANCH, + ); + } + + protected function newMarkerRefQueryTemplate() { + return new ArcanistGitRepositoryMarkerQuery(); + } + + } diff --git a/src/repository/api/ArcanistMercurialAPI.php b/src/repository/api/ArcanistMercurialAPI.php index dd968b29..25cbd3b3 100644 --- a/src/repository/api/ArcanistMercurialAPI.php +++ b/src/repository/api/ArcanistMercurialAPI.php @@ -542,7 +542,7 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI { $commit_ref = $this->newCommitRef() ->setCommitHash($branch['hash']); - $refs[] = $this->newBranchRef() + $refs[] = $this->newMarkerRef() ->setBranchName($branch['name']) ->setIsCurrentBranch($branch['current']) ->attachCommitRef($commit_ref); @@ -1190,5 +1190,15 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI { return !$err; } + protected function newSupportedMarkerTypes() { + return array( + ArcanistMarkerRef::TYPE_BRANCH, + ArcanistMarkerRef::TYPE_BOOKMARK, + ); + } + + protected function newMarkerRefQueryTemplate() { + return new ArcanistMercurialRepositoryMarkerQuery(); + } } diff --git a/src/repository/api/ArcanistRepositoryAPI.php b/src/repository/api/ArcanistRepositoryAPI.php index d5e18ada..d1dcf4dc 100644 --- a/src/repository/api/ArcanistRepositoryAPI.php +++ b/src/repository/api/ArcanistRepositoryAPI.php @@ -745,8 +745,8 @@ 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() { @@ -763,4 +763,21 @@ abstract class ArcanistRepositoryAPI extends Phobject { 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(); + } + } diff --git a/src/repository/marker/ArcanistGitRepositoryMarkerQuery.php b/src/repository/marker/ArcanistGitRepositoryMarkerQuery.php new file mode 100644 index 00000000..0c0ede61 --- /dev/null +++ b/src/repository/marker/ArcanistGitRepositoryMarkerQuery.php @@ -0,0 +1,125 @@ +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); + + // NOTE: Since we only return branches today, we restrict this operation + // to branches. + + list($stdout) = $api->newFuture( + 'for-each-ref --format %s -- refs/heads/', + 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; + + 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) + ->setEpoch((int)$epoch) + ->setMarkerHash($hash) + ->setTreeHash($tree) + ->setSummary($summary) + ->setMessage($text); + + 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]; + } + +} diff --git a/src/repository/marker/ArcanistMarkerRef.php b/src/repository/marker/ArcanistMarkerRef.php new file mode 100644 index 00000000..42aeb0e0 --- /dev/null +++ b/src/repository/marker/ArcanistMarkerRef.php @@ -0,0 +1,120 @@ +getName()); + } + + protected function newHardpoints() { + return array( + $this->newHardpoint(self::HARDPOINT_COMMITREF), + ); + } + + 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 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 attachCommitRef(ArcanistCommitRef $ref) { + return $this->attachHardpoint(self::HARDPOINT_COMMITREF, $ref); + } + + public function getCommitRef() { + return $this->getHardpoint(self::HARDPOINT_COMMITREF); + } + +} diff --git a/src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php b/src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php new file mode 100644 index 00000000..9e7a627c --- /dev/null +++ b/src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php @@ -0,0 +1,147 @@ +shouldQueryMarkerType(ArcanistMarkerRef::TYPE_BRANCH)) { + $markers[] = $this->newBranchOrBookmarkMarkers(false); + } + + if ($this->shouldQueryMarkerType(ArcanistMarkerRef::TYPE_BOOKMARK)) { + $markers[] = $this->newBranchOrBookmarkMarkers(true); + } + + return array_mergev($markers); + } + + private function newBranchOrBookmarkMarkers($is_bookmarks) { + $api = $this->getRepositoryAPI(); + + $is_branches = !$is_bookmarks; + + // NOTE: This is a bit clumsy, but it allows us to get most bookmark and + // branch information in a single command, including full hashes, without + // using "--debug" or matching any human readable strings in the output. + + // NOTE: We can't get branches and bookmarks together in a single command + // because if we query for "heads() + bookmark()", we can't tell if a + // bookmarked result is a branch head or not. + + $template_fields = array( + '{node}', + '{branch}', + '{join(bookmarks, "\3")}', + '{activebookmark}', + '{desc}', + ); + $expect_fields = count($template_fields); + + $template = implode('\2', $template_fields).'\1'; + + if ($is_bookmarks) { + $query = hgsprintf('bookmark()'); + } else { + $query = hgsprintf('head()'); + } + + $future = $api->newFuture( + 'log --rev %s --template %s --', + $query, + $template); + + list($lines) = $future->resolve(); + + $markers = array(); + + $lines = explode("\1", $lines); + foreach ($lines as $line) { + if (!strlen(trim($line))) { + continue; + } + + $fields = explode("\2", $line, $expect_fields); + $actual_fields = count($fields); + if ($actual_fields !== $expect_fields) { + throw new Exception( + pht( + 'Unexpected number of fields in line "%s", expected %s but '. + 'found %s.', + $line, + new PhutilNumber($expect_fields), + new PhutilNumber($actual_fields))); + } + + $node = $fields[0]; + + $branch = $fields[1]; + if (!strlen($branch)) { + $branch = 'default'; + } + + if ($is_bookmarks) { + $bookmarks = $fields[2]; + if (strlen($bookmarks)) { + $bookmarks = explode("\3", $fields[2]); + } else { + $bookmarks = array(); + } + + if (strlen($fields[3])) { + $active_bookmark = $fields[3]; + } else { + $active_bookmark = null; + } + } else { + $bookmarks = array(); + $active_bookmark = null; + } + + $message = $fields[4]; + + $commit_ref = $api->newCommitRef() + ->setCommitHash($node) + ->attachMessage($message); + + $template = id(new ArcanistMarkerRef()) + ->setCommitHash($node) + ->attachCommitRef($commit_ref); + + if ($is_bookmarks) { + foreach ($bookmarks as $bookmark) { + $is_active = ($bookmark === $active_bookmark); + + $markers[] = id(clone $template) + ->setMarkerType(ArcanistMarkerRef::TYPE_BOOKMARK) + ->setName($bookmark) + ->setIsActive($is_active); + } + } + + if ($is_branches) { + $markers[] = id(clone $template) + ->setMarkerType(ArcanistMarkerRef::TYPE_BRANCH) + ->setName($branch); + } + } + + if ($is_branches) { + $current_hash = $api->getCanonicalRevisionName('.'); + + foreach ($markers as $marker) { + if ($marker->getMarkerType() !== ArcanistMarkerRef::TYPE_BRANCH) { + continue; + } + + if ($marker->getCommitHash() === $current_hash) { + $marker->setIsActive(true); + } + } + } + + return $markers; + } + +} diff --git a/src/repository/marker/ArcanistRepositoryMarkerQuery.php b/src/repository/marker/ArcanistRepositoryMarkerQuery.php new file mode 100644 index 00000000..91d1c3fc --- /dev/null +++ b/src/repository/marker/ArcanistRepositoryMarkerQuery.php @@ -0,0 +1,63 @@ +repositoryAPI = $api; + return $this; + } + + final public function getRepositoryAPI() { + return $this->repositoryAPI; + } + + final public function withTypes(array $types) { + $this->types = array_fuse($types); + return $this; + } + + final public function execute() { + $markers = $this->newRefMarkers(); + + $types = $this->types; + if ($types !== null) { + foreach ($markers as $key => $marker) { + if (!isset($types[$marker->getMarkerType()])) { + unset($markers[$key]); + } + } + } + + return $this->sortMarkers($markers); + } + + private function sortMarkers(array $markers) { + // Sort the list in natural order. If we apply a stable sort later, + // markers will sort in "feature1", "feature2", etc., order if they + // don't otherwise have a unique position. + + // This can improve behavior if two branches were updated at the same + // time, as is common when cascading rebases after changes land. + + $map = mpull($markers, 'getName'); + natcasesort($map); + $markers = array_select_keys($markers, array_keys($map)); + + return $markers; + } + + final protected function shouldQueryMarkerType($marker_type) { + if ($this->types === null) { + return true; + } + + return isset($this->types[$marker_type]); + } + +} diff --git a/src/workflow/ArcanistBookmarksWorkflow.php b/src/workflow/ArcanistBookmarksWorkflow.php new file mode 100644 index 00000000..0deda2e8 --- /dev/null +++ b/src/workflow/ArcanistBookmarksWorkflow.php @@ -0,0 +1,43 @@ +newWorkflowInformation() + ->setSynopsis( + pht('Show an enhanced view of bookmarks in the working copy.')) + ->addExample(pht('**bookmarks**')) + ->setHelp($help); + } + + protected function getWorkflowMarkerType() { + $api = $this->getRepositoryAPI(); + $marker_type = ArcanistMarkerRef::TYPE_BOOKMARK; + + if (!$this->hasMarkerTypeSupport($marker_type)) { + throw new PhutilArgumentUsageException( + pht( + 'The version control system ("%s") in the current working copy '. + 'does not support bookmarks.', + $api->getSourceControlSystemName())); + } + + return $marker_type; + } + +} diff --git a/src/workflow/ArcanistBranchesWorkflow.php b/src/workflow/ArcanistBranchesWorkflow.php new file mode 100644 index 00000000..1adb2afb --- /dev/null +++ b/src/workflow/ArcanistBranchesWorkflow.php @@ -0,0 +1,43 @@ +newWorkflowInformation() + ->setSynopsis( + pht('Show an enhanced view of branches in the working copy.')) + ->addExample(pht('**branches**')) + ->setHelp($help); + } + + protected function getWorkflowMarkerType() { + $api = $this->getRepositoryAPI(); + $marker_type = ArcanistMarkerRef::TYPE_BRANCH; + + if (!$this->hasMarkerTypeSupport($marker_type)) { + throw new PhutilArgumentUsageException( + pht( + 'The version control system ("%s") in the current working copy '. + 'does not support branches.', + $api->getSourceControlSystemName())); + } + + return $marker_type; + } + +} diff --git a/src/workflow/ArcanistFeatureBaseWorkflow.php b/src/workflow/ArcanistFeatureBaseWorkflow.php index b6224d34..2b15c803 100644 --- a/src/workflow/ArcanistFeatureBaseWorkflow.php +++ b/src/workflow/ArcanistFeatureBaseWorkflow.php @@ -191,7 +191,7 @@ EOHELP } } - if (!$this->getArgument('view-all') && !$branch->getIsCurrentBranch()) { + if (!$this->getArgument('view-all') && !$branch->getIsActive()) { if ($status == 'Closed' || $status == 'Abandoned') { continue; } @@ -216,8 +216,8 @@ EOHELP } $out[] = array( - 'name' => $branch->getBranchName(), - 'current' => $branch->getIsCurrentBranch(), + 'name' => $branch->getName(), + 'current' => $branch->getIsActive(), 'status' => $status, 'desc' => $desc, 'revision' => $revision ? $revision->getID() : null, diff --git a/src/workflow/ArcanistMarkersWorkflow.php b/src/workflow/ArcanistMarkersWorkflow.php new file mode 100644 index 00000000..c92808cc --- /dev/null +++ b/src/workflow/ArcanistMarkersWorkflow.php @@ -0,0 +1,118 @@ +getRepositoryAPI(); + + $marker_type = $this->getWorkflowMarkerType(); + + $markers = $api->newMarkerRefQuery() + ->withTypes(array($marker_type)) + ->execute(); + + $states = array(); + foreach ($markers as $marker) { + $state_ref = id(new ArcanistWorkingCopyStateRef()) + ->setCommitRef($marker->getCommitRef()); + + $states[] = array( + 'marker' => $marker, + 'state' => $state_ref, + ); + } + + $this->loadHardpoints( + ipull($states, 'state'), + ArcanistWorkingCopyStateRef::HARDPOINT_REVISIONREFS); + + $vectors = array(); + foreach ($states as $key => $state) { + $marker_ref = $state['marker']; + $state_ref = $state['state']; + + $vector = id(new PhutilSortVector()) + ->addInt($marker_ref->getIsActive() ? 1 : 0) + ->addInt($marker_ref->getEpoch()); + + $vectors[$key] = $vector; + } + + $vectors = msortv($vectors, 'getSelf'); + $states = array_select_keys($states, array_keys($vectors)); + + $table = id(new PhutilConsoleTable()) + ->setShowHeader(false) + ->addColumn('active') + ->addColumn('name') + ->addColumn('status') + ->addColumn('description'); + + $rows = array(); + foreach ($states as $state) { + $marker_ref = $state['marker']; + $state_ref = $state['state']; + $revision_ref = null; + $commit_ref = $marker_ref->getCommitRef(); + + $marker_name = tsprintf('**%s**', $marker_ref->getName()); + + if ($state_ref->hasAmbiguousRevisionRefs()) { + $status = pht('Ambiguous'); + } else { + $revision_ref = $state_ref->getRevisionRef(); + if (!$revision_ref) { + $status = tsprintf( + '%s', + pht('No Revision')); + } else { + $status = $revision_ref->getStatusDisplayName(); + + $ansi_color = $revision_ref->getStatusANSIColor(); + if ($ansi_color) { + $status = tsprintf( + sprintf('%%s', $ansi_color), + $status); + } + } + } + + if ($revision_ref) { + $description = $revision_ref->getFullName(); + } else { + $description = $commit_ref->getSummary(); + } + + if ($marker_ref->getIsActive()) { + $active_mark = '*'; + } else { + $active_mark = ' '; + } + $is_active = tsprintf('** %s **', $active_mark); + + $rows[] = array( + 'active' => $is_active, + 'name' => $marker_name, + 'status' => $status, + 'description' => $description, + ); + } + + $table->drawRows($rows); + + return 0; + } + + final protected function hasMarkerTypeSupport($marker_type) { + $api = $this->getRepositoryAPI(); + + $types = $api->getSupportedMarkerTypes(); + $types = array_fuse($types); + + return isset($types[$marker_type]); + } + +}