From 3d64140ff31c502c8589ed2e4d41fc465ce5d498 Mon Sep 17 00:00:00 2001 From: epriestley Date: Mon, 8 Jun 2020 12:13:37 -0700 Subject: [PATCH] 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 --- src/__phutil_library_map__.php | 17 +- src/engine/ArcanistWorkflowEngine.php | 48 ++++ src/hardpoint/ArcanistHardpointObject.php | 6 + src/land/engine/ArcanistLandEngine.php | 45 +--- src/repository/api/ArcanistGitAPI.php | 4 + src/repository/api/ArcanistMercurialAPI.php | 4 + src/repository/api/ArcanistRepositoryAPI.php | 18 ++ src/repository/marker/ArcanistMarkerRef.php | 48 +++- ...ArcanistMercurialRepositoryMarkerQuery.php | 2 + .../marker/ArcanistRepositoryMarkerQuery.php | 44 ++++ .../state/ArcanistGitLocalState.php | 2 +- src/work/ArcanistGitWorkEngine.php | 57 +++++ src/work/ArcanistMercurialWorkEngine.php | 56 +++++ src/work/ArcanistWorkEngine.php | 215 ++++++++++++++++++ src/workflow/ArcanistWorkWorkflow.php | 95 ++++++++ 15 files changed, 612 insertions(+), 49 deletions(-) create mode 100644 src/engine/ArcanistWorkflowEngine.php create mode 100644 src/work/ArcanistGitWorkEngine.php create mode 100644 src/work/ArcanistMercurialWorkEngine.php create mode 100644 src/work/ArcanistWorkEngine.php create mode 100644 src/workflow/ArcanistWorkWorkflow.php diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 9cac118f..b44d0d44 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -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', diff --git a/src/engine/ArcanistWorkflowEngine.php b/src/engine/ArcanistWorkflowEngine.php new file mode 100644 index 00000000..726ba65b --- /dev/null +++ b/src/engine/ArcanistWorkflowEngine.php @@ -0,0 +1,48 @@ +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; + } + +} diff --git a/src/hardpoint/ArcanistHardpointObject.php b/src/hardpoint/ArcanistHardpointObject.php index 93a3bac9..b2afa8cd 100644 --- a/src/hardpoint/ArcanistHardpointObject.php +++ b/src/hardpoint/ArcanistHardpointObject.php @@ -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, diff --git a/src/land/engine/ArcanistLandEngine.php b/src/land/engine/ArcanistLandEngine.php index da354e27..895cea5c 100644 --- a/src/land/engine/ArcanistLandEngine.php +++ b/src/land/engine/ArcanistLandEngine.php @@ -1,11 +1,7 @@ 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; diff --git a/src/repository/api/ArcanistGitAPI.php b/src/repository/api/ArcanistGitAPI.php index 2c16a570..153655f2 100644 --- a/src/repository/api/ArcanistGitAPI.php +++ b/src/repository/api/ArcanistGitAPI.php @@ -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); diff --git a/src/repository/api/ArcanistMercurialAPI.php b/src/repository/api/ArcanistMercurialAPI.php index e73578b8..2d72805b 100644 --- a/src/repository/api/ArcanistMercurialAPI.php +++ b/src/repository/api/ArcanistMercurialAPI.php @@ -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); diff --git a/src/repository/api/ArcanistRepositoryAPI.php b/src/repository/api/ArcanistRepositoryAPI.php index d1dcf4dc..0649fdb5 100644 --- a/src/repository/api/ArcanistRepositoryAPI.php +++ b/src/repository/api/ArcanistRepositoryAPI.php @@ -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); + } + } diff --git a/src/repository/marker/ArcanistMarkerRef.php b/src/repository/marker/ArcanistMarkerRef.php index acf53390..e3b27a96 100644 --- a/src/repository/marker/ArcanistMarkerRef.php +++ b/src/repository/marker/ArcanistMarkerRef.php @@ -1,9 +1,12 @@ 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); + } + } diff --git a/src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php b/src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php index 9e7a627c..7d4b98a2 100644 --- a/src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php +++ b/src/repository/marker/ArcanistMercurialRepositoryMarkerQuery.php @@ -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) { diff --git a/src/repository/marker/ArcanistRepositoryMarkerQuery.php b/src/repository/marker/ArcanistRepositoryMarkerQuery.php index 1260752d..9b31ad3f 100644 --- a/src/repository/marker/ArcanistRepositoryMarkerQuery.php +++ b/src/repository/marker/ArcanistRepositoryMarkerQuery.php @@ -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); } diff --git a/src/repository/state/ArcanistGitLocalState.php b/src/repository/state/ArcanistGitLocalState.php index d7b9833c..30d29d3a 100644 --- a/src/repository/state/ArcanistGitLocalState.php +++ b/src/repository/state/ArcanistGitLocalState.php @@ -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() { diff --git a/src/work/ArcanistGitWorkEngine.php b/src/work/ArcanistGitWorkEngine.php new file mode 100644 index 00000000..ae5238bd --- /dev/null +++ b/src/work/ArcanistGitWorkEngine.php @@ -0,0 +1,57 @@ +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(); + } + +} diff --git a/src/work/ArcanistMercurialWorkEngine.php b/src/work/ArcanistMercurialWorkEngine.php new file mode 100644 index 00000000..83aa8b71 --- /dev/null +++ b/src/work/ArcanistMercurialWorkEngine.php @@ -0,0 +1,56 @@ +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(); + } + +} diff --git a/src/work/ArcanistWorkEngine.php b/src/work/ArcanistWorkEngine.php new file mode 100644 index 00000000..0d8a01ed --- /dev/null +++ b/src/work/ArcanistWorkEngine.php @@ -0,0 +1,215 @@ +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); + } + +} diff --git a/src/workflow/ArcanistWorkWorkflow.php b/src/workflow/ArcanistWorkWorkflow.php new file mode 100644 index 00000000..5be3c94c --- /dev/null +++ b/src/workflow/ArcanistWorkWorkflow.php @@ -0,0 +1,95 @@ +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(<<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; + } + +}