1
0
Fork 0
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:
epriestley 2020-06-08 12:13:37 -07:00
parent 5abf0b96c8
commit 3d64140ff3
15 changed files with 612 additions and 49 deletions

View file

@ -225,6 +225,7 @@ phutil_register_library_map(array(
'ArcanistGitRawCommitTestCase' => 'repository/raw/__tests__/ArcanistGitRawCommitTestCase.php', 'ArcanistGitRawCommitTestCase' => 'repository/raw/__tests__/ArcanistGitRawCommitTestCase.php',
'ArcanistGitRepositoryMarkerQuery' => 'repository/marker/ArcanistGitRepositoryMarkerQuery.php', 'ArcanistGitRepositoryMarkerQuery' => 'repository/marker/ArcanistGitRepositoryMarkerQuery.php',
'ArcanistGitUpstreamPath' => 'repository/api/ArcanistGitUpstreamPath.php', 'ArcanistGitUpstreamPath' => 'repository/api/ArcanistGitUpstreamPath.php',
'ArcanistGitWorkEngine' => 'work/ArcanistGitWorkEngine.php',
'ArcanistGitWorkingCopy' => 'workingcopy/ArcanistGitWorkingCopy.php', 'ArcanistGitWorkingCopy' => 'workingcopy/ArcanistGitWorkingCopy.php',
'ArcanistGitWorkingCopyRevisionHardpointQuery' => 'query/ArcanistGitWorkingCopyRevisionHardpointQuery.php', 'ArcanistGitWorkingCopyRevisionHardpointQuery' => 'query/ArcanistGitWorkingCopyRevisionHardpointQuery.php',
'ArcanistGlobalVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistGlobalVariableXHPASTLinterRule.php', 'ArcanistGlobalVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistGlobalVariableXHPASTLinterRule.php',
@ -336,6 +337,7 @@ phutil_register_library_map(array(
'ArcanistMercurialParser' => 'repository/parser/ArcanistMercurialParser.php', 'ArcanistMercurialParser' => 'repository/parser/ArcanistMercurialParser.php',
'ArcanistMercurialParserTestCase' => 'repository/parser/__tests__/ArcanistMercurialParserTestCase.php', 'ArcanistMercurialParserTestCase' => 'repository/parser/__tests__/ArcanistMercurialParserTestCase.php',
'ArcanistMercurialRepositoryMarkerQuery' => 'repository/marker/ArcanistMercurialRepositoryMarkerQuery.php', 'ArcanistMercurialRepositoryMarkerQuery' => 'repository/marker/ArcanistMercurialRepositoryMarkerQuery.php',
'ArcanistMercurialWorkEngine' => 'work/ArcanistMercurialWorkEngine.php',
'ArcanistMercurialWorkingCopy' => 'workingcopy/ArcanistMercurialWorkingCopy.php', 'ArcanistMercurialWorkingCopy' => 'workingcopy/ArcanistMercurialWorkingCopy.php',
'ArcanistMercurialWorkingCopyRevisionHardpointQuery' => 'query/ArcanistMercurialWorkingCopyRevisionHardpointQuery.php', 'ArcanistMercurialWorkingCopyRevisionHardpointQuery' => 'query/ArcanistMercurialWorkingCopyRevisionHardpointQuery.php',
'ArcanistMergeConflictLinter' => 'lint/linter/ArcanistMergeConflictLinter.php', 'ArcanistMergeConflictLinter' => 'lint/linter/ArcanistMergeConflictLinter.php',
@ -538,8 +540,11 @@ phutil_register_library_map(array(
'ArcanistWeldWorkflow' => 'workflow/ArcanistWeldWorkflow.php', 'ArcanistWeldWorkflow' => 'workflow/ArcanistWeldWorkflow.php',
'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php', 'ArcanistWhichWorkflow' => 'workflow/ArcanistWhichWorkflow.php',
'ArcanistWildConfigOption' => 'config/option/ArcanistWildConfigOption.php', 'ArcanistWildConfigOption' => 'config/option/ArcanistWildConfigOption.php',
'ArcanistWorkEngine' => 'work/ArcanistWorkEngine.php',
'ArcanistWorkWorkflow' => 'workflow/ArcanistWorkWorkflow.php',
'ArcanistWorkflow' => 'workflow/ArcanistWorkflow.php', 'ArcanistWorkflow' => 'workflow/ArcanistWorkflow.php',
'ArcanistWorkflowArgument' => 'toolset/ArcanistWorkflowArgument.php', 'ArcanistWorkflowArgument' => 'toolset/ArcanistWorkflowArgument.php',
'ArcanistWorkflowEngine' => 'engine/ArcanistWorkflowEngine.php',
'ArcanistWorkflowGitHardpointQuery' => 'query/ArcanistWorkflowGitHardpointQuery.php', 'ArcanistWorkflowGitHardpointQuery' => 'query/ArcanistWorkflowGitHardpointQuery.php',
'ArcanistWorkflowInformation' => 'toolset/ArcanistWorkflowInformation.php', 'ArcanistWorkflowInformation' => 'toolset/ArcanistWorkflowInformation.php',
'ArcanistWorkflowMercurialHardpointQuery' => 'query/ArcanistWorkflowMercurialHardpointQuery.php', 'ArcanistWorkflowMercurialHardpointQuery' => 'query/ArcanistWorkflowMercurialHardpointQuery.php',
@ -1253,6 +1258,7 @@ phutil_register_library_map(array(
'ArcanistGitRawCommitTestCase' => 'PhutilTestCase', 'ArcanistGitRawCommitTestCase' => 'PhutilTestCase',
'ArcanistGitRepositoryMarkerQuery' => 'ArcanistRepositoryMarkerQuery', 'ArcanistGitRepositoryMarkerQuery' => 'ArcanistRepositoryMarkerQuery',
'ArcanistGitUpstreamPath' => 'Phobject', 'ArcanistGitUpstreamPath' => 'Phobject',
'ArcanistGitWorkEngine' => 'ArcanistWorkEngine',
'ArcanistGitWorkingCopy' => 'ArcanistWorkingCopy', 'ArcanistGitWorkingCopy' => 'ArcanistWorkingCopy',
'ArcanistGitWorkingCopyRevisionHardpointQuery' => 'ArcanistWorkflowGitHardpointQuery', 'ArcanistGitWorkingCopyRevisionHardpointQuery' => 'ArcanistWorkflowGitHardpointQuery',
'ArcanistGlobalVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistGlobalVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1322,7 +1328,7 @@ phutil_register_library_map(array(
'ArcanistLambdaFuncFunctionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistLambdaFuncFunctionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistLandCommit' => 'Phobject', 'ArcanistLandCommit' => 'Phobject',
'ArcanistLandCommitSet' => 'Phobject', 'ArcanistLandCommitSet' => 'Phobject',
'ArcanistLandEngine' => 'Phobject', 'ArcanistLandEngine' => 'ArcanistWorkflowEngine',
'ArcanistLandSymbol' => 'Phobject', 'ArcanistLandSymbol' => 'Phobject',
'ArcanistLandTarget' => 'Phobject', 'ArcanistLandTarget' => 'Phobject',
'ArcanistLandWorkflow' => 'ArcanistArcWorkflow', 'ArcanistLandWorkflow' => 'ArcanistArcWorkflow',
@ -1355,7 +1361,10 @@ phutil_register_library_map(array(
'ArcanistLogicalOperatorsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistLogicalOperatorsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistLowercaseFunctionsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistLowercaseFunctionsXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistLowercaseFunctionsXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistMarkerRef' => 'ArcanistRef', 'ArcanistMarkerRef' => array(
'ArcanistRef',
'ArcanistDisplayRefInterface',
),
'ArcanistMarkersWorkflow' => 'ArcanistArcWorkflow', 'ArcanistMarkersWorkflow' => 'ArcanistArcWorkflow',
'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI', 'ArcanistMercurialAPI' => 'ArcanistRepositoryAPI',
'ArcanistMercurialCommitMessageHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery', 'ArcanistMercurialCommitMessageHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery',
@ -1364,6 +1373,7 @@ phutil_register_library_map(array(
'ArcanistMercurialParser' => 'Phobject', 'ArcanistMercurialParser' => 'Phobject',
'ArcanistMercurialParserTestCase' => 'PhutilTestCase', 'ArcanistMercurialParserTestCase' => 'PhutilTestCase',
'ArcanistMercurialRepositoryMarkerQuery' => 'ArcanistRepositoryMarkerQuery', 'ArcanistMercurialRepositoryMarkerQuery' => 'ArcanistRepositoryMarkerQuery',
'ArcanistMercurialWorkEngine' => 'ArcanistWorkEngine',
'ArcanistMercurialWorkingCopy' => 'ArcanistWorkingCopy', 'ArcanistMercurialWorkingCopy' => 'ArcanistWorkingCopy',
'ArcanistMercurialWorkingCopyRevisionHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery', 'ArcanistMercurialWorkingCopyRevisionHardpointQuery' => 'ArcanistWorkflowMercurialHardpointQuery',
'ArcanistMergeConflictLinter' => 'ArcanistLinter', 'ArcanistMergeConflictLinter' => 'ArcanistLinter',
@ -1576,8 +1586,11 @@ phutil_register_library_map(array(
'ArcanistWeldWorkflow' => 'ArcanistArcWorkflow', 'ArcanistWeldWorkflow' => 'ArcanistArcWorkflow',
'ArcanistWhichWorkflow' => 'ArcanistWorkflow', 'ArcanistWhichWorkflow' => 'ArcanistWorkflow',
'ArcanistWildConfigOption' => 'ArcanistConfigOption', 'ArcanistWildConfigOption' => 'ArcanistConfigOption',
'ArcanistWorkEngine' => 'ArcanistWorkflowEngine',
'ArcanistWorkWorkflow' => 'ArcanistArcWorkflow',
'ArcanistWorkflow' => 'Phobject', 'ArcanistWorkflow' => 'Phobject',
'ArcanistWorkflowArgument' => 'Phobject', 'ArcanistWorkflowArgument' => 'Phobject',
'ArcanistWorkflowEngine' => 'Phobject',
'ArcanistWorkflowGitHardpointQuery' => 'ArcanistRuntimeHardpointQuery', 'ArcanistWorkflowGitHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistWorkflowInformation' => 'Phobject', 'ArcanistWorkflowInformation' => 'Phobject',
'ArcanistWorkflowMercurialHardpointQuery' => 'ArcanistRuntimeHardpointQuery', 'ArcanistWorkflowMercurialHardpointQuery' => 'ArcanistRuntimeHardpointQuery',

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

View file

@ -5,6 +5,12 @@ abstract class ArcanistHardpointObject
private $hardpointList; private $hardpointList;
public function __clone() {
if ($this->hardpointList) {
$this->hardpointList = clone $this->hardpointList;
}
}
final public function getHardpoint($hardpoint) { final public function getHardpoint($hardpoint) {
return $this->getHardpointList()->getHardpoint( return $this->getHardpointList()->getHardpoint(
$this, $this,

View file

@ -1,11 +1,7 @@
<?php <?php
abstract class ArcanistLandEngine extends Phobject { abstract class ArcanistLandEngine
extends ArcanistWorkflowEngine {
private $workflow;
private $viewer;
private $logEngine;
private $repositoryAPI;
private $sourceRefs; private $sourceRefs;
private $shouldHold; private $shouldHold;
@ -33,15 +29,6 @@ abstract class ArcanistLandEngine extends Phobject {
private $localState; 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) { final public function setOntoRemote($onto_remote) {
$this->ontoRemote = $onto_remote; $this->ontoRemote = $onto_remote;
return $this; return $this;
@ -96,34 +83,6 @@ abstract class ArcanistLandEngine extends Phobject {
return $this->intoLocal; 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) { final public function setShouldHold($should_hold) {
$this->shouldHold = $should_hold; $this->shouldHold = $should_hold;
return $this; return $this;

View file

@ -1746,6 +1746,10 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
return new ArcanistGitLandEngine(); return new ArcanistGitLandEngine();
} }
protected function newWorkEngine() {
return new ArcanistGitWorkEngine();
}
public function newLocalState() { public function newLocalState() {
return id(new ArcanistGitLocalState()) return id(new ArcanistGitLocalState())
->setRepositoryAPI($this); ->setRepositoryAPI($this);

View file

@ -1134,6 +1134,10 @@ final class ArcanistMercurialAPI extends ArcanistRepositoryAPI {
return new ArcanistMercurialLandEngine(); return new ArcanistMercurialLandEngine();
} }
protected function newWorkEngine() {
return new ArcanistMercurialWorkEngine();
}
public function newLocalState() { public function newLocalState() {
return id(new ArcanistMercurialLocalState()) return id(new ArcanistMercurialLocalState())
->setRepositoryAPI($this); ->setRepositoryAPI($this);

View file

@ -763,6 +763,20 @@ abstract class ArcanistRepositoryAPI extends Phobject {
return null; 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() { final public function getSupportedMarkerTypes() {
return $this->newSupportedMarkerTypes(); return $this->newSupportedMarkerTypes();
} }
@ -780,4 +794,8 @@ abstract class ArcanistRepositoryAPI extends Phobject {
throw new PhutilMethodNotImplementedException(); throw new PhutilMethodNotImplementedException();
} }
final public function getDisplayHash($hash) {
return substr($hash, 0, 12);
}
} }

View file

@ -1,9 +1,12 @@
<?php <?php
final class ArcanistMarkerRef 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_BRANCH = 'branch';
const TYPE_BOOKMARK = 'bookmark'; const TYPE_BOOKMARK = 'bookmark';
@ -13,18 +16,38 @@ final class ArcanistMarkerRef
private $epoch; private $epoch;
private $markerHash; private $markerHash;
private $commitHash; private $commitHash;
private $displayHash;
private $treeHash; private $treeHash;
private $summary; private $summary;
private $message; private $message;
private $isActive = false; private $isActive = false;
public function getRefDisplayName() { 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() { protected function newHardpoints() {
return array( return array(
$this->newHardpoint(self::HARDPOINT_COMMITREF), $this->newHardpoint(self::HARDPOINT_COMMITREF),
$this->newHardpoint(self::HARDPOINT_WORKINGCOPYSTATEREF),
); );
} }
@ -64,6 +87,17 @@ final class ArcanistMarkerRef
return $this->markerHash; 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) { public function setCommitHash($commit_hash) {
$this->commitHash = $commit_hash; $this->commitHash = $commit_hash;
return $this; return $this;
@ -125,4 +159,12 @@ final class ArcanistMarkerRef
return $this->getHardpoint(self::HARDPOINT_COMMITREF); 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);
}
} }

View file

@ -100,6 +100,7 @@ final class ArcanistMercurialRepositoryMarkerQuery
} }
$message = $fields[4]; $message = $fields[4];
$message_lines = phutil_split_lines($message, false);
$commit_ref = $api->newCommitRef() $commit_ref = $api->newCommitRef()
->setCommitHash($node) ->setCommitHash($node)
@ -107,6 +108,7 @@ final class ArcanistMercurialRepositoryMarkerQuery
$template = id(new ArcanistMarkerRef()) $template = id(new ArcanistMarkerRef())
->setCommitHash($node) ->setCommitHash($node)
->setSummary(head($message_lines))
->attachCommitRef($commit_ref); ->attachCommitRef($commit_ref);
if ($is_bookmarks) { if ($is_bookmarks) {

View file

@ -6,6 +6,7 @@ abstract class ArcanistRepositoryMarkerQuery
private $repositoryAPI; private $repositoryAPI;
private $isActive; private $isActive;
private $markerTypes; private $markerTypes;
private $names;
private $commitHashes; private $commitHashes;
private $ancestorCommitHashes; private $ancestorCommitHashes;
@ -23,14 +24,47 @@ abstract class ArcanistRepositoryMarkerQuery
return $this; return $this;
} }
final public function withNames(array $names) {
$this->names = array_fuse($names);
return $this;
}
final public function withIsActive($active) { final public function withIsActive($active) {
$this->isActive = $active; $this->isActive = $active;
return $this; 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() { final public function execute() {
$markers = $this->newRefMarkers(); $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; $types = $this->markerTypes;
if ($types !== null) { if ($types !== null) {
foreach ($markers as $key => $marker) { 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) { if ($this->isActive !== null) {
foreach ($markers as $key => $marker) { foreach ($markers as $key => $marker) {
if ($marker->getIsActive() !== $this->isActive) { if ($marker->getIsActive() !== $this->isActive) {
@ -48,6 +91,7 @@ abstract class ArcanistRepositoryMarkerQuery
} }
} }
return $this->sortMarkers($markers); return $this->sortMarkers($markers);
} }

View file

@ -42,7 +42,7 @@ final class ArcanistGitLocalState
} }
$log = $this->getWorkflow()->getLogEngine(); $log = $this->getWorkflow()->getLogEngine();
$log->writeStatus(pht('SAVE STATE'), $where); $log->writeTrace(pht('SAVE STATE'), $where);
} }
protected function executeRestoreLocalState() { protected function executeRestoreLocalState() {

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

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

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

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