mirror of
https://we.phorge.it/source/arcanist.git
synced 2024-11-21 22:32:41 +01:00
Implement "arc work", to replace "arc feature"
Summary: Ref T13546. Fixes T2928. Adds a new "arc work" workflow which functions like the older "arc feature" workflow, but with modern infrastructure. Test Plan: Used "arc work" to begin work on branches, bookmarks, and revisions in Git and Mercurial. Maniphest Tasks: T13546, T2928 Differential Revision: https://secure.phabricator.com/D21336
This commit is contained in:
parent
5abf0b96c8
commit
3d64140ff3
15 changed files with 612 additions and 49 deletions
|
@ -225,6 +225,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistGitRawCommitTestCase' => 'repository/raw/__tests__/ArcanistGitRawCommitTestCase.php',
|
||||
'ArcanistGitRepositoryMarkerQuery' => 'repository/marker/ArcanistGitRepositoryMarkerQuery.php',
|
||||
'ArcanistGitUpstreamPath' => 'repository/api/ArcanistGitUpstreamPath.php',
|
||||
'ArcanistGitWorkEngine' => 'work/ArcanistGitWorkEngine.php',
|
||||
'ArcanistGitWorkingCopy' => 'workingcopy/ArcanistGitWorkingCopy.php',
|
||||
'ArcanistGitWorkingCopyRevisionHardpointQuery' => 'query/ArcanistGitWorkingCopyRevisionHardpointQuery.php',
|
||||
'ArcanistGlobalVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistGlobalVariableXHPASTLinterRule.php',
|
||||
|
@ -336,6 +337,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistMercurialParser' => 'repository/parser/ArcanistMercurialParser.php',
|
||||
'ArcanistMercurialParserTestCase' => 'repository/parser/__tests__/ArcanistMercurialParserTestCase.php',
|
||||
'ArcanistMercurialRepositoryMarkerQuery' => 'repository/marker/ArcanistMercurialRepositoryMarkerQuery.php',
|
||||
'ArcanistMercurialWorkEngine' => 'work/ArcanistMercurialWorkEngine.php',
|
||||
'ArcanistMercurialWorkingCopy' => 'workingcopy/ArcanistMercurialWorkingCopy.php',
|
||||
'ArcanistMercurialWorkingCopyRevisionHardpointQuery' => 'query/ArcanistMercurialWorkingCopyRevisionHardpointQuery.php',
|
||||
'ArcanistMergeConflictLinter' => 'lint/linter/ArcanistMergeConflictLinter.php',
|
||||
|
@ -538,8 +540,11 @@ phutil_register_library_map(array(
|
|||
'ArcanistWeldWorkflow' => 'workflow/ArcanistWeldWorkflow.php',
|
||||
'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php',
|
||||
'ArcanistWildConfigOption' => 'config/option/ArcanistWildConfigOption.php',
|
||||
'ArcanistWorkEngine' => 'work/ArcanistWorkEngine.php',
|
||||
'ArcanistWorkWorkflow' => 'workflow/ArcanistWorkWorkflow.php',
|
||||
'ArcanistWorkflow' => 'workflow/ArcanistWorkflow.php',
|
||||
'ArcanistWorkflowArgument' => 'toolset/ArcanistWorkflowArgument.php',
|
||||
'ArcanistWorkflowEngine' => 'engine/ArcanistWorkflowEngine.php',
|
||||
'ArcanistWorkflowGitHardpointQuery' => 'query/ArcanistWorkflowGitHardpointQuery.php',
|
||||
'ArcanistWorkflowInformation' => 'toolset/ArcanistWorkflowInformation.php',
|
||||
'ArcanistWorkflowMercurialHardpointQuery' => 'query/ArcanistWorkflowMercurialHardpointQuery.php',
|
||||
|
@ -1253,6 +1258,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistGitRawCommitTestCase' => 'PhutilTestCase',
|
||||
'ArcanistGitRepositoryMarkerQuery' => 'ArcanistRepositoryMarkerQuery',
|
||||
'ArcanistGitUpstreamPath' => 'Phobject',
|
||||
'ArcanistGitWorkEngine' => 'ArcanistWorkEngine',
|
||||
'ArcanistGitWorkingCopy' => 'ArcanistWorkingCopy',
|
||||
'ArcanistGitWorkingCopyRevisionHardpointQuery' => 'ArcanistWorkflowGitHardpointQuery',
|
||||
'ArcanistGlobalVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
|
@ -1322,7 +1328,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistLambdaFuncFunctionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||
'ArcanistLandCommit' => 'Phobject',
|
||||
'ArcanistLandCommitSet' => 'Phobject',
|
||||
'ArcanistLandEngine' => 'Phobject',
|
||||
'ArcanistLandEngine' => 'ArcanistWorkflowEngine',
|
||||
'ArcanistLandSymbol' => 'Phobject',
|
||||
'ArcanistLandTarget' => 'Phobject',
|
||||
'ArcanistLandWorkflow' => 'ArcanistArcWorkflow',
|
||||
|
@ -1355,7 +1361,10 @@ phutil_register_library_map(array(
|
|||
'ArcanistLogicalOperatorsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||
'ArcanistLowercaseFunctionsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
|
||||
'ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
|
||||
'ArcanistMarkerRef' => 'ArcanistRef',
|
||||
'ArcanistMarkerRef' => array(
|
||||
'ArcanistRef',
|
||||
'ArcanistDisplayRefInterface',
|
||||
),
|
||||
'ArcanistMarkersWorkflow' => 'ArcanistArcWorkflow',
|
||||
'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI',
|
||||
'ArcanistMercurialCommitMessageHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery',
|
||||
|
@ -1364,6 +1373,7 @@ phutil_register_library_map(array(
|
|||
'ArcanistMercurialParser' => 'Phobject',
|
||||
'ArcanistMercurialParserTestCase' => 'PhutilTestCase',
|
||||
'ArcanistMercurialRepositoryMarkerQuery' => 'ArcanistRepositoryMarkerQuery',
|
||||
'ArcanistMercurialWorkEngine' => 'ArcanistWorkEngine',
|
||||
'ArcanistMercurialWorkingCopy' => 'ArcanistWorkingCopy',
|
||||
'ArcanistMercurialWorkingCopyRevisionHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery',
|
||||
'ArcanistMergeConflictLinter' => 'ArcanistLinter',
|
||||
|
@ -1576,8 +1586,11 @@ phutil_register_library_map(array(
|
|||
'ArcanistWeldWorkflow' => 'ArcanistArcWorkflow',
|
||||
'ArcanistWhichWorkflow' => 'ArcanistWorkflow',
|
||||
'ArcanistWildConfigOption' => 'ArcanistConfigOption',
|
||||
'ArcanistWorkEngine' => 'ArcanistWorkflowEngine',
|
||||
'ArcanistWorkWorkflow' => 'ArcanistArcWorkflow',
|
||||
'ArcanistWorkflow' => 'Phobject',
|
||||
'ArcanistWorkflowArgument' => 'Phobject',
|
||||
'ArcanistWorkflowEngine' => 'Phobject',
|
||||
'ArcanistWorkflowGitHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
|
||||
'ArcanistWorkflowInformation' => 'Phobject',
|
||||
'ArcanistWorkflowMercurialHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
|
||||
|
|
48
src/engine/ArcanistWorkflowEngine.php
Normal file
48
src/engine/ArcanistWorkflowEngine.php
Normal file
|
@ -0,0 +1,48 @@
|
|||
<?php
|
||||
|
||||
abstract class ArcanistWorkflowEngine
|
||||
extends Phobject {
|
||||
|
||||
private $workflow;
|
||||
private $viewer;
|
||||
private $logEngine;
|
||||
private $repositoryAPI;
|
||||
|
||||
final public function setViewer($viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
final public function setWorkflow(ArcanistWorkflow $workflow) {
|
||||
$this->workflow = $workflow;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getWorkflow() {
|
||||
return $this->workflow;
|
||||
}
|
||||
|
||||
final public function setRepositoryAPI(
|
||||
ArcanistRepositoryAPI $repository_api) {
|
||||
$this->repositoryAPI = $repository_api;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getRepositoryAPI() {
|
||||
return $this->repositoryAPI;
|
||||
}
|
||||
|
||||
final public function setLogEngine(ArcanistLogEngine $log_engine) {
|
||||
$this->logEngine = $log_engine;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getLogEngine() {
|
||||
return $this->logEngine;
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,12 @@ abstract class ArcanistHardpointObject
|
|||
|
||||
private $hardpointList;
|
||||
|
||||
public function __clone() {
|
||||
if ($this->hardpointList) {
|
||||
$this->hardpointList = clone $this->hardpointList;
|
||||
}
|
||||
}
|
||||
|
||||
final public function getHardpoint($hardpoint) {
|
||||
return $this->getHardpointList()->getHardpoint(
|
||||
$this,
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
<?php
|
||||
|
||||
abstract class ArcanistLandEngine extends Phobject {
|
||||
|
||||
private $workflow;
|
||||
private $viewer;
|
||||
private $logEngine;
|
||||
private $repositoryAPI;
|
||||
abstract class ArcanistLandEngine
|
||||
extends ArcanistWorkflowEngine {
|
||||
|
||||
private $sourceRefs;
|
||||
private $shouldHold;
|
||||
|
@ -33,15 +29,6 @@ abstract class ArcanistLandEngine extends Phobject {
|
|||
|
||||
private $localState;
|
||||
|
||||
final public function setViewer($viewer) {
|
||||
$this->viewer = $viewer;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getViewer() {
|
||||
return $this->viewer;
|
||||
}
|
||||
|
||||
final public function setOntoRemote($onto_remote) {
|
||||
$this->ontoRemote = $onto_remote;
|
||||
return $this;
|
||||
|
@ -96,34 +83,6 @@ abstract class ArcanistLandEngine extends Phobject {
|
|||
return $this->intoLocal;
|
||||
}
|
||||
|
||||
final public function setWorkflow($workflow) {
|
||||
$this->workflow = $workflow;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getWorkflow() {
|
||||
return $this->workflow;
|
||||
}
|
||||
|
||||
final public function setRepositoryAPI(
|
||||
ArcanistRepositoryAPI $repository_api) {
|
||||
$this->repositoryAPI = $repository_api;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getRepositoryAPI() {
|
||||
return $this->repositoryAPI;
|
||||
}
|
||||
|
||||
final public function setLogEngine(ArcanistLogEngine $log_engine) {
|
||||
$this->logEngine = $log_engine;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getLogEngine() {
|
||||
return $this->logEngine;
|
||||
}
|
||||
|
||||
final public function setShouldHold($should_hold) {
|
||||
$this->shouldHold = $should_hold;
|
||||
return $this;
|
||||
|
|
|
@ -1746,6 +1746,10 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
|
|||
return new ArcanistGitLandEngine();
|
||||
}
|
||||
|
||||
protected function newWorkEngine() {
|
||||
return new ArcanistGitWorkEngine();
|
||||
}
|
||||
|
||||
public function newLocalState() {
|
||||
return id(new ArcanistGitLocalState())
|
||||
->setRepositoryAPI($this);
|
||||
|
|
|
@ -1134,6 +1134,10 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
|
|||
return new ArcanistMercurialLandEngine();
|
||||
}
|
||||
|
||||
protected function newWorkEngine() {
|
||||
return new ArcanistMercurialWorkEngine();
|
||||
}
|
||||
|
||||
public function newLocalState() {
|
||||
return id(new ArcanistMercurialLocalState())
|
||||
->setRepositoryAPI($this);
|
||||
|
|
|
@ -763,6 +763,20 @@ abstract class ArcanistRepositoryAPI extends Phobject {
|
|||
return null;
|
||||
}
|
||||
|
||||
final public function getWorkEngine() {
|
||||
$engine = $this->newWorkEngine();
|
||||
|
||||
if ($engine) {
|
||||
$engine->setRepositoryAPI($this);
|
||||
}
|
||||
|
||||
return $engine;
|
||||
}
|
||||
|
||||
protected function newWorkEngine() {
|
||||
return null;
|
||||
}
|
||||
|
||||
final public function getSupportedMarkerTypes() {
|
||||
return $this->newSupportedMarkerTypes();
|
||||
}
|
||||
|
@ -780,4 +794,8 @@ abstract class ArcanistRepositoryAPI extends Phobject {
|
|||
throw new PhutilMethodNotImplementedException();
|
||||
}
|
||||
|
||||
final public function getDisplayHash($hash) {
|
||||
return substr($hash, 0, 12);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -1,9 +1,12 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistMarkerRef
|
||||
extends ArcanistRef {
|
||||
extends ArcanistRef
|
||||
implements
|
||||
ArcanistDisplayRefInterface {
|
||||
|
||||
const HARDPOINT_COMMITREF = 'commitRef';
|
||||
const HARDPOINT_COMMITREF = 'arc.marker.commitRef';
|
||||
const HARDPOINT_WORKINGCOPYSTATEREF = 'arc.marker.workingCopyStateRef';
|
||||
|
||||
const TYPE_BRANCH = 'branch';
|
||||
const TYPE_BOOKMARK = 'bookmark';
|
||||
|
@ -13,18 +16,38 @@ final class ArcanistMarkerRef
|
|||
private $epoch;
|
||||
private $markerHash;
|
||||
private $commitHash;
|
||||
private $displayHash;
|
||||
private $treeHash;
|
||||
private $summary;
|
||||
private $message;
|
||||
private $isActive = false;
|
||||
|
||||
public function getRefDisplayName() {
|
||||
return pht('Marker %s', $this->getName());
|
||||
return $this->getDisplayRefObjectName();
|
||||
}
|
||||
|
||||
public function getDisplayRefObjectName() {
|
||||
switch ($this->getMarkerType()) {
|
||||
case self::TYPE_BRANCH:
|
||||
return pht('Branch "%s"', $this->getName());
|
||||
case self::TYPE_BOOKMARK:
|
||||
return pht('Bookmark "%s"', $this->getName());
|
||||
default:
|
||||
return pht('Marker "%s"', $this->getName());
|
||||
}
|
||||
}
|
||||
|
||||
public function getDisplayRefTitle() {
|
||||
return pht(
|
||||
'%s %s',
|
||||
$this->getDisplayHash(),
|
||||
$this->getSummary());
|
||||
}
|
||||
|
||||
protected function newHardpoints() {
|
||||
return array(
|
||||
$this->newHardpoint(self::HARDPOINT_COMMITREF),
|
||||
$this->newHardpoint(self::HARDPOINT_WORKINGCOPYSTATEREF),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -64,6 +87,17 @@ final class ArcanistMarkerRef
|
|||
return $this->markerHash;
|
||||
}
|
||||
|
||||
public function setDisplayHash($display_hash) {
|
||||
$this->displayHash = $display_hash;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDisplayHash() {
|
||||
return $this->displayHash;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public function setCommitHash($commit_hash) {
|
||||
$this->commitHash = $commit_hash;
|
||||
return $this;
|
||||
|
@ -125,4 +159,12 @@ final class ArcanistMarkerRef
|
|||
return $this->getHardpoint(self::HARDPOINT_COMMITREF);
|
||||
}
|
||||
|
||||
public function attachWorkingCopyStateRef(ArcanistWorkingCopyStateRef $ref) {
|
||||
return $this->attachHardpoint(self::HARDPOINT_WORKINGCOPYSTATEREF, $ref);
|
||||
}
|
||||
|
||||
public function getWorkingCopyStateRef() {
|
||||
return $this->getHardpoint(self::HARDPOINT_WORKINGCOPYSTATEREF);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -100,6 +100,7 @@ final class ArcanistMercurialRepositoryMarkerQuery
|
|||
}
|
||||
|
||||
$message = $fields[4];
|
||||
$message_lines = phutil_split_lines($message, false);
|
||||
|
||||
$commit_ref = $api->newCommitRef()
|
||||
->setCommitHash($node)
|
||||
|
@ -107,6 +108,7 @@ final class ArcanistMercurialRepositoryMarkerQuery
|
|||
|
||||
$template = id(new ArcanistMarkerRef())
|
||||
->setCommitHash($node)
|
||||
->setSummary(head($message_lines))
|
||||
->attachCommitRef($commit_ref);
|
||||
|
||||
if ($is_bookmarks) {
|
||||
|
|
|
@ -6,6 +6,7 @@ abstract class ArcanistRepositoryMarkerQuery
|
|||
private $repositoryAPI;
|
||||
private $isActive;
|
||||
private $markerTypes;
|
||||
private $names;
|
||||
private $commitHashes;
|
||||
private $ancestorCommitHashes;
|
||||
|
||||
|
@ -23,14 +24,47 @@ abstract class ArcanistRepositoryMarkerQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
final public function withNames(array $names) {
|
||||
$this->names = array_fuse($names);
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function withIsActive($active) {
|
||||
$this->isActive = $active;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function executeOne() {
|
||||
$markers = $this->execute();
|
||||
|
||||
if (!$markers) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (count($markers) > 1) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Query matched multiple markers, expected zero or one.'));
|
||||
}
|
||||
|
||||
return head($markers);
|
||||
}
|
||||
|
||||
final public function execute() {
|
||||
$markers = $this->newRefMarkers();
|
||||
|
||||
$api = $this->getRepositoryAPI();
|
||||
foreach ($markers as $marker) {
|
||||
$state_ref = id(new ArcanistWorkingCopyStateRef())
|
||||
->setCommitRef($marker->getCommitRef());
|
||||
|
||||
$marker->attachWorkingCopyStateRef($state_ref);
|
||||
|
||||
$hash = $marker->getCommitHash();
|
||||
$hash = $api->getDisplayHash($hash);
|
||||
$marker->setDisplayHash($hash);
|
||||
}
|
||||
|
||||
$types = $this->markerTypes;
|
||||
if ($types !== null) {
|
||||
foreach ($markers as $key => $marker) {
|
||||
|
@ -40,6 +74,15 @@ abstract class ArcanistRepositoryMarkerQuery
|
|||
}
|
||||
}
|
||||
|
||||
$names = $this->names;
|
||||
if ($names !== null) {
|
||||
foreach ($markers as $key => $marker) {
|
||||
if (!isset($names[$marker->getName()])) {
|
||||
unset($markers[$key]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($this->isActive !== null) {
|
||||
foreach ($markers as $key => $marker) {
|
||||
if ($marker->getIsActive() !== $this->isActive) {
|
||||
|
@ -48,6 +91,7 @@ abstract class ArcanistRepositoryMarkerQuery
|
|||
}
|
||||
}
|
||||
|
||||
|
||||
return $this->sortMarkers($markers);
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ final class ArcanistGitLocalState
|
|||
}
|
||||
|
||||
$log = $this->getWorkflow()->getLogEngine();
|
||||
$log->writeStatus(pht('SAVE STATE'), $where);
|
||||
$log->writeTrace(pht('SAVE STATE'), $where);
|
||||
}
|
||||
|
||||
protected function executeRestoreLocalState() {
|
||||
|
|
57
src/work/ArcanistGitWorkEngine.php
Normal file
57
src/work/ArcanistGitWorkEngine.php
Normal file
|
@ -0,0 +1,57 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistGitWorkEngine
|
||||
extends ArcanistWorkEngine {
|
||||
|
||||
protected function getDefaultStartSymbol() {
|
||||
$api = $this->getRepositoryAPI();
|
||||
|
||||
// NOTE: In Git, we're trying to find the current branch name because the
|
||||
// behavior of "--track" depends on the symbol we pass.
|
||||
|
||||
$marker = $api->newMarkerRefQuery()
|
||||
->withIsActive(true)
|
||||
->withMarkerTypes(array(ArcanistMarkerRef::TYPE_BRANCH))
|
||||
->executeOne();
|
||||
if ($marker) {
|
||||
return $marker->getName();
|
||||
}
|
||||
|
||||
return $api->getWorkingCopyRevision();
|
||||
}
|
||||
|
||||
protected function newMarker($symbol, $start) {
|
||||
$api = $this->getRepositoryAPI();
|
||||
$log = $this->getLogEngine();
|
||||
|
||||
$log->writeStatus(
|
||||
pht('NEW BRANCH'),
|
||||
pht(
|
||||
'Creating new branch "%s" from "%s".',
|
||||
$symbol,
|
||||
$start));
|
||||
|
||||
$future = $api->newFuture(
|
||||
'checkout --track -b %s %s --',
|
||||
$symbol,
|
||||
$start);
|
||||
$future->resolve();
|
||||
}
|
||||
|
||||
protected function moveToMarker(ArcanistMarkerRef $marker) {
|
||||
$api = $this->getRepositoryAPI();
|
||||
$log = $this->getLogEngine();
|
||||
|
||||
$log->writeStatus(
|
||||
pht('BRANCH'),
|
||||
pht(
|
||||
'Checking out branch "%s".',
|
||||
$marker->getName()));
|
||||
|
||||
$future = $api->newFuture(
|
||||
'checkout %s --',
|
||||
$marker->getName());
|
||||
$future->resolve();
|
||||
}
|
||||
|
||||
}
|
56
src/work/ArcanistMercurialWorkEngine.php
Normal file
56
src/work/ArcanistMercurialWorkEngine.php
Normal file
|
@ -0,0 +1,56 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistMercurialWorkEngine
|
||||
extends ArcanistWorkEngine {
|
||||
|
||||
protected function getDefaultStartSymbol() {
|
||||
$api = $this->getRepositoryAPI();
|
||||
return $api->getWorkingCopyRevision();
|
||||
}
|
||||
|
||||
protected function newMarker($symbol, $start) {
|
||||
$api = $this->getRepositoryAPI();
|
||||
$log = $this->getLogEngine();
|
||||
|
||||
$log->writeStatus(
|
||||
pht('NEW BOOKMARK'),
|
||||
pht(
|
||||
'Creating new bookmark "%s" from "%s".',
|
||||
$symbol,
|
||||
$start));
|
||||
|
||||
if ($start !== $this->getDefaultStartSymbol()) {
|
||||
$future = $api->newFuture('update -- %s', $start);
|
||||
$future->resolve();
|
||||
}
|
||||
|
||||
$future = $api->newFuture('bookmark %s --', $symbol);
|
||||
$future->resolve();
|
||||
}
|
||||
|
||||
protected function moveToMarker(ArcanistMarkerRef $marker) {
|
||||
$api = $this->getRepositoryAPI();
|
||||
$log = $this->getLogEngine();
|
||||
|
||||
if ($marker->isBookmark()) {
|
||||
$log->writeStatus(
|
||||
pht('BOOKMARK'),
|
||||
pht(
|
||||
'Checking out bookmark "%s".',
|
||||
$marker->getName()));
|
||||
} else {
|
||||
$log->writeStatus(
|
||||
pht('BRANCH'),
|
||||
pht(
|
||||
'Checking out branch "%s".',
|
||||
$marker->getName()));
|
||||
}
|
||||
|
||||
$future = $api->newFuture(
|
||||
'checkout %s --',
|
||||
$marker->getName());
|
||||
|
||||
$future->resolve();
|
||||
}
|
||||
|
||||
}
|
215
src/work/ArcanistWorkEngine.php
Normal file
215
src/work/ArcanistWorkEngine.php
Normal file
|
@ -0,0 +1,215 @@
|
|||
<?php
|
||||
|
||||
abstract class ArcanistWorkEngine
|
||||
extends ArcanistWorkflowEngine {
|
||||
|
||||
private $symbolArgument;
|
||||
private $startArgument;
|
||||
|
||||
final public function setSymbolArgument($symbol_argument) {
|
||||
$this->symbolArgument = $symbol_argument;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getSymbolArgument() {
|
||||
return $this->symbolArgument;
|
||||
}
|
||||
|
||||
final public function setStartArgument($start_argument) {
|
||||
$this->startArgument = $start_argument;
|
||||
return $this;
|
||||
}
|
||||
|
||||
final public function getStartArgument() {
|
||||
return $this->startArgument;
|
||||
}
|
||||
|
||||
final public function execute() {
|
||||
$workflow = $this->getWorkflow();
|
||||
$api = $this->getRepositoryAPI();
|
||||
|
||||
$local_state = $api->newLocalState()
|
||||
->setWorkflow($workflow)
|
||||
->saveLocalState();
|
||||
|
||||
$symbol = $this->getSymbolArgument();
|
||||
|
||||
$markers = $api->newMarkerRefQuery()
|
||||
->withNames(array($symbol))
|
||||
->execute();
|
||||
|
||||
if ($markers) {
|
||||
if (count($markers) > 1) {
|
||||
|
||||
// TODO: This almost certainly means the symbol is a Mercurial branch
|
||||
// with multiple heads. We can pick some head.
|
||||
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Symbol "%s" is ambiguous.',
|
||||
$symbol));
|
||||
}
|
||||
|
||||
$target = head($markers);
|
||||
$this->moveToMarker($target);
|
||||
$local_state->discardLocalState();
|
||||
return;
|
||||
}
|
||||
|
||||
$revision_marker = $this->workOnRevision($symbol);
|
||||
if ($revision_marker) {
|
||||
$this->moveToMarker($revision_marker);
|
||||
$local_state->discardLocalState();
|
||||
return;
|
||||
}
|
||||
|
||||
$task_marker = $this->workOnTask($symbol);
|
||||
if ($task_marker) {
|
||||
$this->moveToMarker($task_marker);
|
||||
$local_state->discardLocalState();
|
||||
return;
|
||||
}
|
||||
|
||||
// NOTE: We're resolving this symbol so we can raise an error message if
|
||||
// it's bogus, but we're using the symbol (not the resolved version) to
|
||||
// actually create the new marker. This matters in Git because it impacts
|
||||
// the behavior of "--track" when we pass a branch name.
|
||||
|
||||
$start = $this->getStartArgument();
|
||||
if ($start !== null) {
|
||||
$start_commit = $api->getCanonicalRevisionName($start);
|
||||
if (!$start_commit) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Unable to resolve startpoint "%s".',
|
||||
$start));
|
||||
}
|
||||
} else {
|
||||
$start = $this->getDefaultStartSymbol();
|
||||
}
|
||||
|
||||
$this->newMarker($symbol, $start);
|
||||
$local_state->discardLocalState();
|
||||
}
|
||||
|
||||
abstract protected function newMarker($symbol, $start);
|
||||
abstract protected function moveToMarker(ArcanistMarkerRef $marker);
|
||||
abstract protected function getDefaultStartSymbol();
|
||||
|
||||
private function workOnRevision($symbol) {
|
||||
$workflow = $this->getWorkflow();
|
||||
$api = $this->getRepositoryAPI();
|
||||
$log = $this->getLogEngine();
|
||||
|
||||
try {
|
||||
$revision_symbol = id(new ArcanistRevisionSymbolRef())
|
||||
->setSymbol($symbol);
|
||||
} catch (Exception $ex) {
|
||||
return;
|
||||
}
|
||||
|
||||
$workflow->loadHardpoints(
|
||||
$revision_symbol,
|
||||
ArcanistSymbolRef::HARDPOINT_OBJECT);
|
||||
|
||||
$revision_ref = $revision_symbol->getObject();
|
||||
if (!$revision_ref) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'No revision "%s" exists, or you do not have permission to '.
|
||||
'view it.',
|
||||
$symbol));
|
||||
}
|
||||
|
||||
$markers = $api->newMarkerRefQuery()
|
||||
->execute();
|
||||
|
||||
$state_refs = mpull($markers, 'getWorkingCopyStateRef');
|
||||
|
||||
$workflow->loadHardpoints(
|
||||
$state_refs,
|
||||
ArcanistWorkingCopyStateRef::HARDPOINT_REVISIONREFS);
|
||||
|
||||
$selected = array();
|
||||
foreach ($markers as $marker) {
|
||||
$state_ref = $marker->getWorkingCopyStateRef();
|
||||
$revision_refs = $state_ref->getRevisionRefs();
|
||||
$revision_refs = mpull($revision_refs, null, 'getPHID');
|
||||
|
||||
if (isset($revision_refs[$revision_ref->getPHID()])) {
|
||||
$selected[] = $marker;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$selected) {
|
||||
|
||||
// TODO: We could patch/load here.
|
||||
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Revision "%s" was not found anywhere in this working copy.',
|
||||
$revision_ref->getMonogram()));
|
||||
}
|
||||
|
||||
if (count($selected) > 1) {
|
||||
$selected = msort($selected, 'getEpoch');
|
||||
|
||||
echo tsprintf(
|
||||
"\n%!\n%W\n\n",
|
||||
pht('AMBIGUOUS MARKER'),
|
||||
pht(
|
||||
'More than one marker in the local working copy is associated '.
|
||||
'with the revision "%s", using the most recent one.',
|
||||
$revision_ref->getMonogram()));
|
||||
|
||||
foreach ($selected as $marker) {
|
||||
echo tsprintf('%s', $marker->newDisplayRef());
|
||||
}
|
||||
|
||||
echo tsprintf("\n");
|
||||
|
||||
$target = last($selected);
|
||||
} else {
|
||||
$target = head($selected);
|
||||
}
|
||||
|
||||
$log->writeStatus(
|
||||
pht('REVISION'),
|
||||
pht('Resuming work on revision:'));
|
||||
|
||||
echo tsprintf('%s', $revision_ref->newDisplayRef());
|
||||
echo tsprintf("\n");
|
||||
|
||||
return $target;
|
||||
}
|
||||
|
||||
private function workOnTask($symbol) {
|
||||
$workflow = $this->getWorkflow();
|
||||
|
||||
try {
|
||||
$task_symbol = id(new ArcanistTaskSymbolRef())
|
||||
->setSymbol($symbol);
|
||||
} catch (Exception $ex) {
|
||||
return;
|
||||
}
|
||||
|
||||
$workflow->loadHardpoints(
|
||||
$task_symbol,
|
||||
ArcanistSymbolRef::HARDPOINT_OBJECT);
|
||||
|
||||
$task_ref = $task_symbol->getObject();
|
||||
if (!$task_ref) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'No task "%s" exists, or you do not have permission to view it.',
|
||||
$symbol));
|
||||
}
|
||||
|
||||
throw new Exception(pht('TODO: Implement this workflow.'));
|
||||
|
||||
$this->loadHardpoints(
|
||||
$task_ref,
|
||||
ArcanistTaskRef::HARDPOINT_REVISIONREFS);
|
||||
}
|
||||
|
||||
}
|
95
src/workflow/ArcanistWorkWorkflow.php
Normal file
95
src/workflow/ArcanistWorkWorkflow.php
Normal file
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
final class ArcanistWorkWorkflow
|
||||
extends ArcanistArcWorkflow {
|
||||
|
||||
public function getWorkflowName() {
|
||||
return 'work';
|
||||
}
|
||||
|
||||
public function getWorkflowArguments() {
|
||||
return array(
|
||||
$this->newWorkflowArgument('start')
|
||||
->setParameter('symbol')
|
||||
->setHelp(
|
||||
pht(
|
||||
'When creating a new branch or bookmark, use this as the '.
|
||||
'branch point.')),
|
||||
$this->newWorkflowArgument('symbol')
|
||||
->setWildcard(true),
|
||||
);
|
||||
}
|
||||
|
||||
public function getWorkflowInformation() {
|
||||
$help = pht(<<<EOHELP
|
||||
Begin or resume work on a branch, bookmark, task, or revision.
|
||||
|
||||
The __symbol__ may be a branch or bookmark name, a revision name (like "D123"),
|
||||
a task name (like "T123"), or a new symbol.
|
||||
|
||||
If you provide a symbol which currently does not identify any ongoing work,
|
||||
Arcanist will create a new branch or bookmark with the name you provide.
|
||||
|
||||
If you provide the name of an existing branch or bookmark, Arcanist will switch
|
||||
to that branch or bookmark.
|
||||
|
||||
If you provide the name of a revision or task, Arcanist will look for a related
|
||||
branch or bookmark that exists in the working copy. If it finds one, it will
|
||||
switch to it. If it does not find one, it will attempt to create a new branch
|
||||
or bookmark.
|
||||
|
||||
When "arc work" creates a branch or bookmark, it will use "--start" as the
|
||||
branchpoint if it is provided. Otherwise, the current working copy state will
|
||||
serve as the starting point.
|
||||
EOHELP
|
||||
);
|
||||
|
||||
return $this->newWorkflowInformation()
|
||||
->setSynopsis(pht('Begin or resume work.'))
|
||||
->addExample(pht('**work** [--start __start__] __symbol__'))
|
||||
->setHelp($help);
|
||||
}
|
||||
|
||||
public function runWorkflow() {
|
||||
$api = $this->getRepositoryAPI();
|
||||
|
||||
$work_engine = $api->getWorkEngine();
|
||||
if (!$work_engine) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'"arc work" must be run in a Git or Mercurial working copy.'));
|
||||
}
|
||||
|
||||
$argv = $this->getArgument('symbol');
|
||||
if (count($argv) === 0) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Provide a branch, bookmark, task, or revision name to begin '.
|
||||
'or resume work on.'));
|
||||
} else if (count($argv) === 1) {
|
||||
$symbol_argument = $argv[0];
|
||||
if (!strlen($symbol_argument)) {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Provide a nonempty symbol to begin or resume work on.'));
|
||||
}
|
||||
} else {
|
||||
throw new PhutilArgumentUsageException(
|
||||
pht(
|
||||
'Too many arguments: provide exactly one argument.'));
|
||||
}
|
||||
|
||||
$start_argument = $this->getArgument('start');
|
||||
|
||||
$work_engine
|
||||
->setViewer($this->getViewer())
|
||||
->setWorkflow($this)
|
||||
->setLogEngine($this->getLogEngine())
|
||||
->setSymbolArgument($symbol_argument)
|
||||
->setStartArgument($start_argument)
|
||||
->execute();
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
Loading…
Reference in a new issue