diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -411,6 +411,7 @@ 'ArcanistRubyLinterTestCase' => 'lint/linter/__tests__/ArcanistRubyLinterTestCase.php', 'ArcanistRuntime' => 'runtime/ArcanistRuntime.php', 'ArcanistRuntimeConfigurationSource' => 'config/source/ArcanistRuntimeConfigurationSource.php', + 'ArcanistRuntimeHardpointQuery' => 'toolset/query/ArcanistRuntimeHardpointQuery.php', 'ArcanistScalarConfigOption' => 'config/option/ArcanistScalarConfigOption.php', 'ArcanistScalarHardpoint' => 'hardpoint/ArcanistScalarHardpoint.php', 'ArcanistScriptAndRegexLinter' => 'lint/linter/ArcanistScriptAndRegexLinter.php', @@ -507,7 +508,6 @@ 'ArcanistWorkflow' => 'workflow/ArcanistWorkflow.php', 'ArcanistWorkflowArgument' => 'toolset/ArcanistWorkflowArgument.php', 'ArcanistWorkflowGitHardpointQuery' => 'query/ArcanistWorkflowGitHardpointQuery.php', - 'ArcanistWorkflowHardpointQuery' => 'toolset/query/ArcanistWorkflowHardpointQuery.php', 'ArcanistWorkflowInformation' => 'toolset/ArcanistWorkflowInformation.php', 'ArcanistWorkingCopy' => 'workingcopy/ArcanistWorkingCopy.php', 'ArcanistWorkingCopyCommitHardpointQuery' => 'query/ArcanistWorkingCopyCommitHardpointQuery.php', @@ -1006,7 +1006,7 @@ 'ArcanistAliasFunctionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistAliasWorkflow' => 'ArcanistWorkflow', 'ArcanistAliasesConfigOption' => 'ArcanistListConfigOption', - 'ArcanistAmendWorkflow' => 'ArcanistWorkflow', + 'ArcanistAmendWorkflow' => 'ArcanistArcWorkflow', 'ArcanistAnoidWorkflow' => 'ArcanistArcWorkflow', 'ArcanistArcConfigurationEngineExtension' => 'ArcanistConfigurationEngineExtension', 'ArcanistArcToolset' => 'ArcanistToolset', @@ -1035,14 +1035,14 @@ 'ArcanistBraceFormattingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistBranchRef' => 'ArcanistRef', 'ArcanistBranchWorkflow' => 'ArcanistFeatureBaseWorkflow', - 'ArcanistBrowseCommitHardpointQuery' => 'ArcanistWorkflowHardpointQuery', + 'ArcanistBrowseCommitHardpointQuery' => 'ArcanistRuntimeHardpointQuery', 'ArcanistBrowseCommitURIHardpointQuery' => 'ArcanistBrowseURIHardpointQuery', 'ArcanistBrowseObjectNameURIHardpointQuery' => 'ArcanistBrowseURIHardpointQuery', 'ArcanistBrowsePathURIHardpointQuery' => 'ArcanistBrowseURIHardpointQuery', 'ArcanistBrowseRef' => 'ArcanistRef', 'ArcanistBrowseRefInspector' => 'ArcanistRefInspector', 'ArcanistBrowseRevisionURIHardpointQuery' => 'ArcanistBrowseURIHardpointQuery', - 'ArcanistBrowseURIHardpointQuery' => 'ArcanistWorkflowHardpointQuery', + 'ArcanistBrowseURIHardpointQuery' => 'ArcanistRuntimeHardpointQuery', 'ArcanistBrowseURIRef' => 'ArcanistRef', 'ArcanistBrowseWorkflow' => 'ArcanistArcWorkflow', 'ArcanistBuildPlanRef' => 'Phobject', @@ -1085,7 +1085,7 @@ 'ArcanistCommitRef' => 'ArcanistRef', 'ArcanistCommitSymbolRef' => 'ArcanistSymbolRef', 'ArcanistCommitSymbolRefInspector' => 'ArcanistRefInspector', - 'ArcanistCommitUpstreamHardpointQuery' => 'ArcanistWorkflowHardpointQuery', + 'ArcanistCommitUpstreamHardpointQuery' => 'ArcanistRuntimeHardpointQuery', 'ArcanistCommitWorkflow' => 'ArcanistWorkflow', 'ArcanistCompilerLintRenderer' => 'ArcanistLintRenderer', 'ArcanistComposerLinter' => 'ArcanistLinter', @@ -1298,7 +1298,7 @@ 'ArcanistMercurialWorkingCopy' => 'ArcanistWorkingCopy', 'ArcanistMergeConflictLinter' => 'ArcanistLinter', 'ArcanistMergeConflictLinterTestCase' => 'ArcanistLinterTestCase', - 'ArcanistMessageRevisionHardpointQuery' => 'ArcanistWorkflowHardpointQuery', + 'ArcanistMessageRevisionHardpointQuery' => 'ArcanistRuntimeHardpointQuery', 'ArcanistMissingArgumentTerminatorException' => 'Exception', 'ArcanistMissingLinterException' => 'Exception', 'ArcanistModifierOrderingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', @@ -1381,13 +1381,13 @@ 'ArcanistReusedIteratorXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistReusedIteratorXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistRevertWorkflow' => 'ArcanistWorkflow', - 'ArcanistRevisionCommitMessageHardpointQuery' => 'ArcanistWorkflowHardpointQuery', + 'ArcanistRevisionCommitMessageHardpointQuery' => 'ArcanistRuntimeHardpointQuery', 'ArcanistRevisionRef' => array( 'ArcanistRef', 'ArcanistDisplayRefInterface', ), 'ArcanistRevisionRefSource' => 'Phobject', - 'ArcanistRevisionSymbolHardpointQuery' => 'ArcanistWorkflowHardpointQuery', + 'ArcanistRevisionSymbolHardpointQuery' => 'ArcanistRuntimeHardpointQuery', 'ArcanistRevisionSymbolRef' => 'ArcanistSymbolRef', 'ArcanistRevisionSymbolRefInspector' => 'ArcanistRefInspector', 'ArcanistRuboCopLinter' => 'ArcanistExternalLinter', @@ -1395,6 +1395,7 @@ 'ArcanistRubyLinter' => 'ArcanistExternalLinter', 'ArcanistRubyLinterTestCase' => 'ArcanistExternalLinterTestCase', 'ArcanistRuntimeConfigurationSource' => 'ArcanistDictionaryConfigurationSource', + 'ArcanistRuntimeHardpointQuery' => 'ArcanistHardpointQuery', 'ArcanistScalarConfigOption' => 'ArcanistConfigOption', 'ArcanistScalarHardpoint' => 'ArcanistHardpoint', 'ArcanistScriptAndRegexLinter' => 'ArcanistLinter', @@ -1478,7 +1479,7 @@ 'ArcanistRef', 'ArcanistDisplayRefInterface', ), - 'ArcanistUserSymbolHardpointQuery' => 'ArcanistWorkflowHardpointQuery', + 'ArcanistUserSymbolHardpointQuery' => 'ArcanistRuntimeHardpointQuery', 'ArcanistUserSymbolRef' => 'ArcanistSymbolRef', 'ArcanistUserSymbolRefInspector' => 'ArcanistRefInspector', 'ArcanistVariableReferenceSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', @@ -1492,11 +1493,10 @@ 'ArcanistWildConfigOption' => 'ArcanistConfigOption', 'ArcanistWorkflow' => 'Phobject', 'ArcanistWorkflowArgument' => 'Phobject', - 'ArcanistWorkflowGitHardpointQuery' => 'ArcanistWorkflowHardpointQuery', - 'ArcanistWorkflowHardpointQuery' => 'ArcanistHardpointQuery', + 'ArcanistWorkflowGitHardpointQuery' => 'ArcanistRuntimeHardpointQuery', 'ArcanistWorkflowInformation' => 'Phobject', 'ArcanistWorkingCopy' => 'Phobject', - 'ArcanistWorkingCopyCommitHardpointQuery' => 'ArcanistWorkflowHardpointQuery', + 'ArcanistWorkingCopyCommitHardpointQuery' => 'ArcanistRuntimeHardpointQuery', 'ArcanistWorkingCopyConfigurationSource' => 'ArcanistFilesystemConfigurationSource', 'ArcanistWorkingCopyIdentity' => 'Phobject', 'ArcanistWorkingCopyPath' => 'Phobject', diff --git a/src/browse/query/ArcanistBrowseCommitHardpointQuery.php b/src/browse/query/ArcanistBrowseCommitHardpointQuery.php --- a/src/browse/query/ArcanistBrowseCommitHardpointQuery.php +++ b/src/browse/query/ArcanistBrowseCommitHardpointQuery.php @@ -1,7 +1,7 @@ getPhobjectClassConstant('BROWSETYPE', 32); diff --git a/src/query/ArcanistCommitUpstreamHardpointQuery.php b/src/query/ArcanistCommitUpstreamHardpointQuery.php --- a/src/query/ArcanistCommitUpstreamHardpointQuery.php +++ b/src/query/ArcanistCommitUpstreamHardpointQuery.php @@ -1,7 +1,7 @@ getRepositoryAPI(); diff --git a/src/query/ArcanistWorkingCopyCommitHardpointQuery.php b/src/query/ArcanistWorkingCopyCommitHardpointQuery.php --- a/src/query/ArcanistWorkingCopyCommitHardpointQuery.php +++ b/src/query/ArcanistWorkingCopyCommitHardpointQuery.php @@ -1,7 +1,7 @@ sources; } + public function getCommitMessage() { + return $this->getHardpoint(self::HARDPOINT_COMMITMESSAGE); + } + public function getDisplayRefObjectName() { return $this->getMonogram(); } diff --git a/src/ref/revision/ArcanistRevisionSymbolHardpointQuery.php b/src/ref/revision/ArcanistRevisionSymbolHardpointQuery.php --- a/src/ref/revision/ArcanistRevisionSymbolHardpointQuery.php +++ b/src/ref/revision/ArcanistRevisionSymbolHardpointQuery.php @@ -1,7 +1,7 @@ getUsername()); + return pht('User "%s"', $this->getMonogram()); } public static function newFromConduit(array $parameters) { @@ -29,6 +29,18 @@ return self::newFromConduit($parameters); } + public function getID() { + return idx($this->parameters, 'id'); + } + + public function getPHID() { + return idx($this->parameters, 'phid'); + } + + public function getMonogram() { + return '@'.$this->getUsername(); + } + public function getUsername() { return idxv($this->parameters, array('fields', 'username')); } @@ -38,7 +50,7 @@ } public function getDisplayRefObjectName() { - return '@'.$this->getUsername(); + return $this->getMonogram(); } public function getDisplayRefTitle() { diff --git a/src/ref/user/ArcanistUserSymbolHardpointQuery.php b/src/ref/user/ArcanistUserSymbolHardpointQuery.php --- a/src/ref/user/ArcanistUserSymbolHardpointQuery.php +++ b/src/ref/user/ArcanistUserSymbolHardpointQuery.php @@ -1,7 +1,7 @@ newCurrentCommitHash(); - return $this->newCommitRef() - ->setCommitHash($commit_hash); + final public function newFuture($pattern /* , ... */) { + $args = func_get_args(); + return $this->buildLocalFuture($args) + ->setResolveOnError(false); } - protected function newCurrentCommitRef() { - $commit_hash = $this->newCurrentCommitHash(); - return $this->newCommitRefForSymbol($commit_hash); + final public function setRuntime(ArcanistRuntime $runtime) { + $this->runtime = $runtime; + return $this; } - protected function newCommitRefForSymbol() { - throw new ArcanistCapabilityNotSupportedException($this); + final public function getRuntime() { + return $this->runtime; } - protected function newCurrentCommitHash() { - throw new ArcanistCapabilityNotSupportedException($this); + final protected function getSymbolEngine() { + return $this->getRuntime()->getSymbolEngine(); } final public function getCurrentWorkingCopyStateRef() { - return $this->newCurrentWorkingCopyStateRef(); + if ($this->currentWorkingCopyStateRef === false) { + $ref = $this->newCurrentWorkingCopyStateRef(); + $this->currentWorkingCopyStateRef = $ref; + } + + return $this->currentWorkingCopyStateRef; } protected function newCurrentWorkingCopyStateRef() { $commit_ref = $this->getCurrentCommitRef(); + if (!$commit_ref) { + return null; + } + return id(new ArcanistWorkingCopyStateRef()) ->setCommitRef($commit_ref); } - final public function newFuture($pattern /* , ... */) { - $args = func_get_args(); - return $this->buildLocalFuture($args) - ->setResolveOnError(false); + final public function getCurrentCommitRef() { + if ($this->currentCommitRef === false) { + $this->currentCommitRef = $this->newCurrentCommitRef(); + } + return $this->currentCommitRef; + } + + protected function newCurrentCommitRef() { + $symbols = $this->getSymbolEngine(); + + $commit_symbol = $this->newCurrentCommitSymbol(); + + return $symbols->loadCommitForSymbol($commit_symbol); } + protected function newCurrentCommitSymbol() { + throw new ArcanistCapabilityNotSupportedException($this); + } + + final public function newCommitRef() { + return new ArcanistCommitRef(); + } + + final public function newBranchRef() { + return new ArcanistBranchRef(); + } } diff --git a/src/runtime/ArcanistRuntime.php b/src/runtime/ArcanistRuntime.php --- a/src/runtime/ArcanistRuntime.php +++ b/src/runtime/ArcanistRuntime.php @@ -8,6 +8,12 @@ private $stack = array(); + private $viewer; + private $hardpointEngine; + private $symbolEngine; + private $conduitEngine; + private $workingCopy; + public function execute(array $argv) { try { @@ -108,6 +114,7 @@ $this->workflows = $workflows; $conduit_engine = $this->newConduitEngine($config); + $this->conduitEngine = $conduit_engine; $phutil_workflows = array(); foreach ($workflows as $key => $workflow) { @@ -301,9 +308,14 @@ ->setArguments($args); $working_copy = ArcanistWorkingCopy::newFromWorkingDirectory(getcwd()); - if ($working_copy) { - $engine->setWorkingCopy($working_copy); - } + + $engine->setWorkingCopy($working_copy); + + $this->workingCopy = $working_copy; + + $working_copy + ->getRepositoryAPI() + ->setRuntime($this); return $engine; } @@ -682,6 +694,10 @@ return $this->stack; } + public function getCurrentWorkflow() { + return last($this->stack); + } + private function newConduitEngine(ArcanistConfigurationSourceList $config) { $conduit_uri = $config->getConfig('phabricator.uri'); @@ -744,4 +760,81 @@ return phutil_passthru('%Ls', $effect->getArguments()); } + public function getSymbolEngine() { + if ($this->symbolEngine === null) { + $this->symbolEngine = $this->newSymbolEngine(); + } + return $this->symbolEngine; + } + + private function newSymbolEngine() { + return id(new ArcanistSymbolEngine()) + ->setWorkflow($this); + } + + public function getHardpointEngine() { + if ($this->hardpointEngine === null) { + $this->hardpointEngine = $this->newHardpointEngine(); + } + return $this->hardpointEngine; + } + + private function newHardpointEngine() { + $engine = new ArcanistHardpointEngine(); + + $queries = ArcanistRuntimeHardpointQuery::getAllQueries(); + + foreach ($queries as $key => $query) { + $queries[$key] = id(clone $query) + ->setRuntime($this); + } + + $engine->setQueries($queries); + + return $engine; + } + + public function getViewer() { + if (!$this->viewer) { + $viewer = $this->getSymbolEngine() + ->loadUserForSymbol('viewer()'); + + // TODO: Deal with anonymous stuff. + if (!$viewer) { + throw new Exception(pht('No viewer!')); + } + + $this->viewer = $viewer; + } + + return $this->viewer; + } + + public function loadHardpoints($objects, $requests) { + if (!is_array($objects)) { + $objects = array($objects); + } + + if (!is_array($requests)) { + $requests = array($requests); + } + + $engine = $this->getHardpointEngine(); + + $requests = $engine->requestHardpoints( + $objects, + $requests); + + // TODO: Wait for only the required requests. + $engine->waitForRequests(array()); + } + + public function getWorkingCopy() { + return $this->workingCopy; + } + + public function getConduitEngine() { + return $this->conduitEngine; + } + } diff --git a/src/toolset/query/ArcanistWorkflowHardpointQuery.php b/src/toolset/query/ArcanistRuntimeHardpointQuery.php rename from src/toolset/query/ArcanistWorkflowHardpointQuery.php rename to src/toolset/query/ArcanistRuntimeHardpointQuery.php --- a/src/toolset/query/ArcanistWorkflowHardpointQuery.php +++ b/src/toolset/query/ArcanistRuntimeHardpointQuery.php @@ -1,22 +1,22 @@ workflow = $workflow; + final public function setRuntime(ArcanistRuntime $runtime) { + $this->runtime = $runtime; return $this; } - final public function getWorkflow() { - return $this->workflow; + final public function getRuntime() { + return $this->runtime; } final public function getWorkingCopy() { - return $this->getWorkflow()->getWorkingCopy(); + return $this->getRuntime()->getWorkingCopy(); } final public function getRepositoryAPI() { @@ -52,7 +52,7 @@ abstract protected function canLoadRef(ArcanistRef $ref); final public function newConduitSearch($method, $constraints) { - $conduit_engine = $this->getWorkflow() + $conduit_engine = $this->getRuntime() ->getConduitEngine(); $conduit_future = id(new ConduitSearchFuture()) @@ -69,7 +69,7 @@ } final public function newConduit($method, $parameters) { - $conduit_engine = $this->getWorkflow() + $conduit_engine = $this->getRuntime() ->getConduitEngine(); $call_object = $conduit_engine->newCall($method, $parameters); @@ -84,7 +84,10 @@ } final public function yieldRepositoryRef() { - $workflow = $this->getWorkflow(); + // TODO: This should probably move to Runtime. + + $runtime = $this->getRuntime(); + $workflow = $runtime->getCurrentWorkflow(); // TODO: This is currently a blocking request, but should yield to the // hardpoint engine in the future. diff --git a/src/workflow/ArcanistAmendWorkflow.php b/src/workflow/ArcanistAmendWorkflow.php --- a/src/workflow/ArcanistAmendWorkflow.php +++ b/src/workflow/ArcanistAmendWorkflow.php @@ -1,210 +1,278 @@ newWorkflowInformation() + ->setSynopsis( + pht('Amend the working copy, synchronizing the local commit message.')) + ->addExample('**amend** [options] -- ') + ->setHelp($help); } - public function requiresConduit() { - return true; - } - - public function requiresAuthentication() { - return true; - } - - public function requiresRepositoryAPI() { - return true; + public function getWorkflowArguments() { + return array( + $this->newWorkflowArgument('show') + ->setHelp( + pht( + 'Show the amended commit message, without modifying the '. + 'working copy.')), + $this->newWorkflowArgument('revision') + ->setParameter('id') + ->setHelp( + pht( + 'Use the message from a specific revision. If you do not specify '. + 'a revision, arc will guess which revision is in the working '. + 'copy.')), + ); } - public function getArguments() { + protected function newPrompts() { return array( - 'show' => array( - 'help' => pht( - 'Show the amended commit message, without modifying the '. - 'working copy.'), - ), - 'revision' => array( - 'param' => 'revision_id', - 'help' => pht( - 'Use the message from a specific revision. If you do not specify '. - 'a revision, arc will guess which revision is in the working '. - 'copy.'), - ), + $this->newPrompt('arc.amend.unrelated') + ->setDescription( + pht( + 'Confirms use of a revision that does not appear to be '. + 'present in the working copy.')), + $this->newPrompt('arc.amend.author') + ->setDescription( + pht( + 'Confirms use of a revision that you are not the author '. + 'of.')), + $this->newPrompt('arc.amend.immutable') + ->setDescription( + pht( + 'Confirms history mutation in a working copy marked as '. + 'immutable.')), ); } - public function run() { + public function runWorkflow() { + $symbols = $this->getSymbolEngine(); + $is_show = $this->getArgument('show'); $repository_api = $this->getRepositoryAPI(); if (!$is_show) { - if (!$repository_api->supportsAmend()) { - throw new ArcanistUsageException( - pht( - "You may only run '%s' in a git or hg ". - "(version 2.2 or newer) working copy.", - 'arc amend')); - } + $this->requireAmendSupport($repository_api); + } - if ($this->isHistoryImmutable()) { - throw new ArcanistUsageException( - pht( - 'This project is marked as adhering to a conservative history '. - 'mutability doctrine (having an immutable local history), which '. - 'precludes amending commit messages.')); - } + $revision_symbol = $this->getArgument('revision'); + + // We only care about the local working copy state if we need it to + // figure out which revision we're operating on, or we're planning to + // mutate it. If the caller is running "arc amend --show --revision X", + // the local state does not matter. + + $need_state = + ($revision_symbol === null) || + (!$is_show); + + if ($need_state) { + $state_ref = $repository_api->getCurrentWorkingCopyStateRef(); + + $this->loadHardpoints( + $state_ref, + ArcanistWorkingCopyStateRef::HARDPOINT_REVISIONREFS); + + $revision_refs = $state_ref->getRevisionRefs(); + } - if ($repository_api->getUncommittedChanges()) { - throw new ArcanistUsageException( + if ($revision_symbol === null) { + $revision_ref = $this->selectRevisionRef($revision_refs); + } else { + $revision_ref = $symbols->loadRevisionForSymbol($revision_symbol); + if (!$revision_ref) { + throw new PhutilArgumentUsageException( pht( - 'You have uncommitted changes in this branch. Stage and commit '. - '(or revert) them before proceeding.')); + 'Revision "%s" does not exist, or you do not have permission '. + 'to see it.', + $revision_symbol)); } } - $revision_id = null; - if ($this->getArgument('revision')) { - $revision_id = $this->normalizeRevisionID($this->getArgument('revision')); + if (!$is_show) { + echo tsprintf( + "%s\n\n%s\n", + pht('Amending commit message to reflect revision:'), + $revision_ref->newDisplayRef()); + + $this->confirmAmendAuthor($revision_ref); + $this->confirmAmendNotFound($revision_ref, $state_ref); } - $repository_api->setBaseCommitArgumentRules('arc:this'); - $in_working_copy = $repository_api->loadWorkingCopyDifferentialRevisions( - $this->getConduit(), - array( - 'status' => 'status-any', - )); - $in_working_copy = ipull($in_working_copy, null, 'id'); + $this->loadHardpoints( + $revision_ref, + ArcanistRevisionRef::HARDPOINT_COMMITMESSAGE); + + $message = $revision_ref->getCommitMessage(); + + if ($is_show) { + echo tsprintf( + "%B\n", + $message); + } else { + $repository_api->amendCommit($message); + } - if (!$revision_id) { - if (count($in_working_copy) == 0) { - throw new ArcanistUsageException( + return 0; + } + + private function requireAmendSupport(ArcanistRepositoryAPI $api) { + if (!$api->supportsAmend()) { + if ($api instanceof ArcanistMercurialAPI) { + throw new PhutilArgumentUsageException( pht( - "No revision specified with '%s', and no revisions found ". - "in the working copy. Use '%s' to specify which revision ". - "you want to amend.", - '--revision', - '--revision ')); - } else if (count($in_working_copy) > 1) { - $message = pht( - "More than one revision was found in the working copy:\n%s\n". - "Use '%s' to specify which revision you want to amend.", - $this->renderRevisionList($in_working_copy), - '--revision '); - throw new ArcanistUsageException($message); - } else { - $revision_id = key($in_working_copy); - $revision = $in_working_copy[$revision_id]; - if ($revision['authorPHID'] != $this->getUserPHID()) { - $other_author = $this->getConduit()->callMethodSynchronous( - 'user.query', - array( - 'phids' => array($revision['authorPHID']), - )); - $other_author = ipull($other_author, 'userName', 'phid'); - $other_author = $other_author[$revision['authorPHID']]; - $rev_title = $revision['title']; - $ok = phutil_console_confirm( - pht( - "You are amending the revision '%s' but you are not ". - "the author. Amend this revision by %s?", - "D{$revision_id}: {$rev_title}", - $other_author)); - if (!$ok) { - throw new ArcanistUserAbortException(); - } - } + '"arc amend" is only supported under Mercurial 2.2 or newer. '. + 'Older versions of Mercurial do not support the "--amend" flag '. + 'to "hg commit ...", which this workflow requires.')); } + + throw new PhutilArgumentUsageException( + pht( + '"arc amend" must be run from inside a working copy of a '. + 'repository using a version control system that supports '. + 'amending commits, like Git or Mercurial.')); } - $conduit = $this->getConduit(); - try { - $message = $conduit->callMethodSynchronous( - 'differential.getcommitmessage', - array( - 'revision_id' => $revision_id, - 'edit' => false, - )); - } catch (ConduitClientException $ex) { - if (strpos($ex->getMessage(), 'ERR_NOT_FOUND') === false) { - throw $ex; - } else { - throw new ArcanistUsageException( - pht("Revision '%s' does not exist.", "D{$revision_id}") - ); - } + if ($this->isHistoryImmutable()) { + echo tsprintf( + "%!\n\n%W\n", + pht('IMMUTABLE WORKING COPY'), + pht( + 'This working copy is configured to have an immutable local '. + 'history, using the "history.immutable" configuration option. '. + 'Amending the working copy will mutate local history.')); + + $prompt = pht('Are you sure you want to mutate history?'); + + $this->getPrompt('arc.amend.immutable') + ->setQuery($prompt) + ->execute(); } - $revision = $conduit->callMethodSynchronous( - 'differential.query', - array( - 'ids' => array($revision_id), - )); - if (empty($revision)) { - throw new Exception( - pht("Failed to lookup information for '%s'!", "D{$revision_id}")); + return; + + if ($api->getUncommittedChanges()) { + // TODO: Make this class of error show the uncommitted changes. + + // TODO: This only needs to check for staged-but-uncommitted changes. + // We can safely amend with untracked and unstaged changes. + + throw new PhutilArgumentUsageException( + pht( + 'You have uncommitted changes in this working copy. Commit or '. + 'revert them before proceeding.')); } - $revision = head($revision); - $revision_title = $revision['title']; + } - if (!$is_show) { - if ($revision_id && empty($in_working_copy[$revision_id])) { - $ok = phutil_console_confirm( - pht( - "The revision '%s' does not appear to be in the working copy. Are ". - "you sure you want to amend HEAD with the commit message for '%s'?", - "D{$revision_id}", - "D{$revision_id}: {$revision_title}")); - if (!$ok) { - throw new ArcanistUserAbortException(); - } - } + private function selectRevisionRef(array $revisions) { + if (!$revisions) { + throw new PhutilArgumentUsageException( + pht( + 'No revision specified with "--revision", and no revisions found '. + 'that match the current working copy state. Use "--revision " '. + 'to specify which revision you want to amend.')); } - if ($is_show) { - echo $message."\n"; - } else { - echo pht( - "Amending commit message to reflect revision %s.\n", - phutil_console_format( - '**D%d: %s**', - $revision_id, - $revision_title)); + if (count($revisions) > 1) { + echo tsprintf( + "%!\n%W\n\n%B\n", + pht('MULTIPLE REVISIONS IN WORKING COPY'), + pht('More than one revision was found in the working copy:'), + mpull($revisions, 'newDisplayRef')); - $repository_api->amendCommit($message); + throw new PhutilArgumentUsageException( + pht( + 'Use "--revision " to specify which revision you want '. + 'to amend.')); } - return 0; + return head($revisions); } - public function getSupportedRevisionControlSystems() { - return array('git', 'hg'); + private function confirmAmendAuthor(ArcanistRevisionRef $revision_ref) { + $viewer = $this->getViewer(); + $viewer_phid = $viewer->getPHID(); + + $author_phid = $revision_ref->getAuthorPHID(); + + if ($viewer_phid === $author_phid) { + return; + } + + $symbols = $this->getSymbolEngine(); + $author_ref = $symbols->loadUserForSymbol($author_phid); + if (!$author_ref) { + // If we don't have any luck loading the author, skip this warning. + return; + } + + echo tsprintf( + "%!\n\n%W\n\n%s", + pht('NOT REVISION AUTHOR'), + array( + pht( + 'You are amending the working copy using information from '. + 'a revision you are not the author of.'), + "\n\n", + pht( + 'The author of this revision (%s) is:', + $revision_ref->getMonogram()), + ), + $author_ref->newDisplayRef()); + + $prompt = pht( + 'Amend working copy using revision owned by %s?', + $author_ref->getMonogram()); + + $this->getPrompt('arc.amend.author') + ->setQuery($prompt) + ->execute(); + } + + private function confirmAmendNotFound( + ArcanistRevisionRef $revision_ref, + ArcanistWorkingCopyStateRef $state_ref) { + + $local_refs = $state_ref->getRevisionRefs(); + $local_refs = mpull($local_refs, null, 'getPHID'); + + $revision_phid = $revision_ref->getPHID(); + $is_local = isset($local_refs[$revision_phid]); + + if ($is_local) { + return; + } + + echo tsprintf( + "%!\n\n%W\n", + pht('UNRELATED REVISION'), + pht( + 'You are amending the working copy using information from '. + 'a revision that does not appear to be associated with the '. + 'current state of the working copy.')); + + $prompt = pht( + 'Amend working copy using unrelated revision %s?', + $revision_ref->getMonogram()); + + $this->getPrompt('arc.amend.unrelated') + ->setQuery($prompt) + ->execute(); } } diff --git a/src/workflow/ArcanistWorkflow.php b/src/workflow/ArcanistWorkflow.php --- a/src/workflow/ArcanistWorkflow.php +++ b/src/workflow/ArcanistWorkflow.php @@ -76,9 +76,6 @@ private $configurationEngine; private $configurationSourceList; - private $viewer; - private $hardpointEngine; - private $symbolEngine; private $promptMap; final public function setToolset(ArcanistToolset $toolset) { @@ -2303,45 +2300,7 @@ final public function loadHardpoints( $objects, $requests) { - - if (!is_array($objects)) { - $objects = array($objects); - } - - if (!is_array($requests)) { - $requests = array($requests); - } - - $engine = $this->getHardpointEngine(); - - $requests = $engine->requestHardpoints( - $objects, - $requests); - - // TODO: Wait for only the required requests. - $engine->waitForRequests(array()); - } - - private function getHardpointEngine() { - if ($this->hardpointEngine === null) { - $this->hardpointEngine = $this->newHardpointEngine(); - } - return $this->hardpointEngine; - } - - private function newHardpointEngine() { - $engine = new ArcanistHardpointEngine(); - - $queries = ArcanistWorkflowHardpointQuery::getAllQueries(); - - foreach ($queries as $key => $query) { - $queries[$key] = id(clone $query) - ->setWorkflow($this); - } - - $engine->setQueries($queries); - - return $engine; + return $this->getRuntime()->loadHardpoints($objects, $requests); } protected function newPrompts() { @@ -2404,31 +2363,11 @@ } final protected function getSymbolEngine() { - if ($this->symbolEngine === null) { - $this->symbolEngine = $this->newSymbolEngine(); - } - return $this->symbolEngine; - } - - private function newSymbolEngine() { - return id(new ArcanistSymbolEngine()) - ->setWorkflow($this); + return $this->getRuntime()->getSymbolEngine(); } final protected function getViewer() { - if (!$this->viewer) { - $viewer = $this->getSymbolEngine() - ->loadUserForSymbol('viewer()'); - - // TODO: Deal with anonymous stuff. - if (!$viewer) { - throw new Exception(pht('No viewer!')); - } - - $this->viewer = $viewer; - } - - return $this->viewer; + return $this->getRuntime()->getViewer(); } } diff --git a/src/xsprintf/tsprintf.php b/src/xsprintf/tsprintf.php --- a/src/xsprintf/tsprintf.php +++ b/src/xsprintf/tsprintf.php @@ -38,6 +38,23 @@ case 'R': $type = 's'; break; + case 'W': + $value = PhutilTerminalString::escapeStringValue($value, true); + $value = phutil_console_wrap($value); + $type = 's'; + break; + case '!': + $value = tsprintf('** %s **', $value); + $value = PhutilTerminalString::escapeStringValue($value, false); + $type = 's'; + break; + default: + throw new Exception( + pht( + 'Unsupported escape sequence "%s" found in pattern: %s', + $type, + $pattern)); + break; } $pattern[$pos] = $type;