1
0
Fork 0
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:
epriestley 2020-06-08 08:32:03 -07:00
parent e8c3cc3289
commit 599ba0f999
16 changed files with 736 additions and 112 deletions

View file

@ -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',

View file

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

View file

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

View file

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

View file

@ -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');

View file

@ -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();
}
}

View file

@ -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();
}
}

View file

@ -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();
}
}

View 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];
}
}

View 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);
}
}

View 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;
}
}

View 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]);
}
}

View 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;
}
}

View 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;
}
}

View file

@ -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,

View 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]);
}
}