mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-28 09:42:40 +01:00
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
This commit is contained in:
parent
e8c3cc3289
commit
599ba0f999
16 changed files with 736 additions and 112 deletions
|
@ -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',
|
||||
|
|
|
@ -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 )------------------------------------------------------------ */
|
||||
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
|
@ -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');
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
125
src/repository/marker/ArcanistGitRepositoryMarkerQuery.php
Normal file
125
src/repository/marker/ArcanistGitRepositoryMarkerQuery.php
Normal file
|
@ -0,0 +1,125 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistGitRepositoryMarkerQuery
|
||||
extends ArcanistRepositoryMarkerQuery {
|
||||
|
||||
|
||||
protected function newRefMarkers() {
|
||||
$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);
|
||||
|
||||
// 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];
|
||||
}
|
||||
|
||||
}
|
120
src/repository/marker/ArcanistMarkerRef.php
Normal file
120
src/repository/marker/ArcanistMarkerRef.php
Normal file
|
@ -0,0 +1,120 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistMarkerRef
|
||||
extends ArcanistRef {
|
||||
|
||||
const HARDPOINT_COMMITREF = 'commitRef';
|
||||
|
||||
const TYPE_BRANCH = 'branch';
|
||||
const TYPE_BOOKMARK = 'bookmark';
|
||||
|
||||
private $name;
|
||||
private $markerType;
|
||||
private $epoch;
|
||||
private $markerHash;
|
||||
private $commitHash;
|
||||
private $treeHash;
|
||||
private $summary;
|
||||
private $message;
|
||||
private $isActive = false;
|
||||
|
||||
public function getRefDisplayName() {
|
||||
return pht('Marker %s', $this->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);
|
||||
}
|
||||
|
||||
}
|
147
src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php
Normal file
147
src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php
Normal file
|
@ -0,0 +1,147 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistMercurialRepositoryMarkerQuery
|
||||
extends ArcanistRepositoryMarkerQuery {
|
||||
|
||||
protected function newRefMarkers() {
|
||||
$markers = array();
|
||||
|
||||
if ($this->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;
|
||||
}
|
||||
|
||||
}
|
63
src/repository/marker/ArcanistRepositoryMarkerQuery.php
Normal file
63
src/repository/marker/ArcanistRepositoryMarkerQuery.php
Normal file
|
@ -0,0 +1,63 @@
|
|||
<?php
|
||||
|
||||
abstract class ArcanistRepositoryMarkerQuery
|
||||
extends Phobject {
|
||||
|
||||
private $repositoryAPI;
|
||||
private $types;
|
||||
private $commitHashes;
|
||||
private $ancestorCommitHashes;
|
||||
|
||||
final public function setRepositoryAPI(ArcanistRepositoryAPI $api) {
|
||||
$this->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]);
|
||||
}
|
||||
|
||||
}
|
43
src/workflow/ArcanistBookmarksWorkflow.php
Normal file
43
src/workflow/ArcanistBookmarksWorkflow.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistBookmarksWorkflow
|
||||
extends ArcanistMarkersWorkflow {
|
||||
|
||||
public function getWorkflowName() {
|
||||
return 'bookmarks';
|
||||
}
|
||||
|
||||
public function getWorkflowArguments() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getWorkflowInformation() {
|
||||
$help = pht(<<<EOHELP
|
||||
Lists bookmarks in the working copy, annotated with additional information
|
||||
about review status.
|
||||
EOHELP
|
||||
);
|
||||
|
||||
return $this->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;
|
||||
}
|
||||
|
||||
}
|
43
src/workflow/ArcanistBranchesWorkflow.php
Normal file
43
src/workflow/ArcanistBranchesWorkflow.php
Normal file
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistBranchesWorkflow
|
||||
extends ArcanistMarkersWorkflow {
|
||||
|
||||
public function getWorkflowName() {
|
||||
return 'branches';
|
||||
}
|
||||
|
||||
public function getWorkflowArguments() {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function getWorkflowInformation() {
|
||||
$help = pht(<<<EOHELP
|
||||
Lists branches in the working copy, annotated with additional information
|
||||
about review status.
|
||||
EOHELP
|
||||
);
|
||||
|
||||
return $this->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;
|
||||
}
|
||||
|
||||
}
|
|
@ -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,
|
||||
|
|
118
src/workflow/ArcanistMarkersWorkflow.php
Normal file
118
src/workflow/ArcanistMarkersWorkflow.php
Normal file
|
@ -0,0 +1,118 @@
|
|||
<?php
|
||||
|
||||
abstract class ArcanistMarkersWorkflow
|
||||
extends ArcanistArcWorkflow {
|
||||
|
||||
abstract protected function getWorkflowMarkerType();
|
||||
|
||||
public function runWorkflow() {
|
||||
$api = $this->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(
|
||||
'<fg:blue>%s</fg>',
|
||||
pht('No Revision'));
|
||||
} else {
|
||||
$status = $revision_ref->getStatusDisplayName();
|
||||
|
||||
$ansi_color = $revision_ref->getStatusANSIColor();
|
||||
if ($ansi_color) {
|
||||
$status = tsprintf(
|
||||
sprintf('<fg:%s>%%s</fg>', $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]);
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue