1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-22 06:42:41 +01:00

Update "arc amend" for Toolsets

Summary: Ref T13490. Moves "arc amend" to Toolsets with modern ref/hardpoint code.

Test Plan: Ran "arc amend --show", "--revision", etc. Hit all the prompts and errors, probably?

Maniphest Tasks: T13490

Differential Revision: https://secure.phabricator.com/D21095
This commit is contained in:
epriestley 2020-04-12 11:06:10 -07:00
parent ff4c1e7c81
commit 0f2e277cd9
19 changed files with 476 additions and 311 deletions

View file

@ -411,6 +411,7 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'ArcanistAliasFunctionXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistAliasWorkflow' => 'ArcanistWorkflow',
'ArcanistAliasesConfigOption' => 'ArcanistListConfigOption',
'ArcanistAmendWorkflow' => 'ArcanistWorkflow',
'ArcanistAmendWorkflow' => 'ArcanistArcWorkflow',
'ArcanistAnoidWorkflow' => 'ArcanistArcWorkflow',
'ArcanistArcConfigurationEngineExtension' => 'ArcanistConfigurationEngineExtension',
'ArcanistArcToolset' => 'ArcanistToolset',
@ -1035,14 +1035,14 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'ArcanistCommitRef' => 'ArcanistRef',
'ArcanistCommitSymbolRef' => 'ArcanistSymbolRef',
'ArcanistCommitSymbolRefInspector' => 'ArcanistRefInspector',
'ArcanistCommitUpstreamHardpointQuery' => 'ArcanistWorkflowHardpointQuery',
'ArcanistCommitUpstreamHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistCommitWorkflow' => 'ArcanistWorkflow',
'ArcanistCompilerLintRenderer' => 'ArcanistLintRenderer',
'ArcanistComposerLinter' => 'ArcanistLinter',
@ -1298,7 +1298,7 @@ phutil_register_library_map(array(
'ArcanistMercurialWorkingCopy' => 'ArcanistWorkingCopy',
'ArcanistMergeConflictLinter' => 'ArcanistLinter',
'ArcanistMergeConflictLinterTestCase' => 'ArcanistLinterTestCase',
'ArcanistMessageRevisionHardpointQuery' => 'ArcanistWorkflowHardpointQuery',
'ArcanistMessageRevisionHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistMissingArgumentTerminatorException' => 'Exception',
'ArcanistMissingLinterException' => 'Exception',
'ArcanistModifierOrderingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1381,13 +1381,13 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'ArcanistRubyLinter' => 'ArcanistExternalLinter',
'ArcanistRubyLinterTestCase' => 'ArcanistExternalLinterTestCase',
'ArcanistRuntimeConfigurationSource' => 'ArcanistDictionaryConfigurationSource',
'ArcanistRuntimeHardpointQuery' => 'ArcanistHardpointQuery',
'ArcanistScalarConfigOption' => 'ArcanistConfigOption',
'ArcanistScalarHardpoint' => 'ArcanistHardpoint',
'ArcanistScriptAndRegexLinter' => 'ArcanistLinter',
@ -1478,7 +1479,7 @@ phutil_register_library_map(array(
'ArcanistRef',
'ArcanistDisplayRefInterface',
),
'ArcanistUserSymbolHardpointQuery' => 'ArcanistWorkflowHardpointQuery',
'ArcanistUserSymbolHardpointQuery' => 'ArcanistRuntimeHardpointQuery',
'ArcanistUserSymbolRef' => 'ArcanistSymbolRef',
'ArcanistUserSymbolRefInspector' => 'ArcanistRefInspector',
'ArcanistVariableReferenceSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1492,11 +1493,10 @@ phutil_register_library_map(array(
'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',

View file

@ -1,7 +1,7 @@
<?php
final class ArcanistBrowseCommitHardpointQuery
extends ArcanistWorkflowHardpointQuery {
extends ArcanistRuntimeHardpointQuery {
public function getHardpoints() {
return array(

View file

@ -1,7 +1,7 @@
<?php
abstract class ArcanistBrowseURIHardpointQuery
extends ArcanistWorkflowHardpointQuery {
extends ArcanistRuntimeHardpointQuery {
public function getSupportedBrowseType() {
return $this->getPhobjectClassConstant('BROWSETYPE', 32);

View file

@ -1,7 +1,7 @@
<?php
final class ArcanistCommitUpstreamHardpointQuery
extends ArcanistWorkflowHardpointQuery {
extends ArcanistRuntimeHardpointQuery {
public function getHardpoints() {
return array(

View file

@ -1,7 +1,7 @@
<?php
final class ArcanistMessageRevisionHardpointQuery
extends ArcanistWorkflowHardpointQuery {
extends ArcanistRuntimeHardpointQuery {
public function getHardpoints() {
return array(

View file

@ -1,7 +1,7 @@
<?php
abstract class ArcanistWorkflowGitHardpointQuery
extends ArcanistWorkflowHardpointQuery {
extends ArcanistRuntimeHardpointQuery {
final protected function canLoadHardpoint() {
$api = $this->getRepositoryAPI();

View file

@ -1,7 +1,7 @@
<?php
final class ArcanistWorkingCopyCommitHardpointQuery
extends ArcanistWorkflowHardpointQuery {
extends ArcanistRuntimeHardpointQuery {
public function getHardpoints() {
return array(

View file

@ -1,7 +1,7 @@
<?php
final class ArcanistRevisionCommitMessageHardpointQuery
extends ArcanistWorkflowHardpointQuery {
extends ArcanistRuntimeHardpointQuery {
public function getHardpoints() {
return array(

View file

@ -79,6 +79,10 @@ final class ArcanistRevisionRef
return $this->sources;
}
public function getCommitMessage() {
return $this->getHardpoint(self::HARDPOINT_COMMITMESSAGE);
}
public function getDisplayRefObjectName() {
return $this->getMonogram();
}

View file

@ -1,7 +1,7 @@
<?php
final class ArcanistRevisionSymbolHardpointQuery
extends ArcanistWorkflowHardpointQuery {
extends ArcanistRuntimeHardpointQuery {
public function getHardpoints() {
return array(

View file

@ -8,7 +8,7 @@ final class ArcanistUserRef
private $parameters;
public function getRefDisplayName() {
return pht('User "%s"', $this->getUsername());
return pht('User "%s"', $this->getMonogram());
}
public static function newFromConduit(array $parameters) {
@ -29,6 +29,18 @@ final class ArcanistUserRef
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 @@ final class ArcanistUserRef
}
public function getDisplayRefObjectName() {
return '@'.$this->getUsername();
return $this->getMonogram();
}
public function getDisplayRefTitle() {

View file

@ -1,7 +1,7 @@
<?php
final class ArcanistUserSymbolHardpointQuery
extends ArcanistWorkflowHardpointQuery {
extends ArcanistRuntimeHardpointQuery {
public function getHardpoints() {
return array(

View file

@ -1687,4 +1687,8 @@ final class ArcanistGitAPI extends ArcanistRepositoryAPI {
return null;
}
protected function newCurrentCommitSymbol() {
return 'HEAD';
}
}

View file

@ -39,6 +39,10 @@ abstract class ArcanistRepositoryAPI extends Phobject {
private $symbolicBaseCommit;
private $resolvedBaseCommit;
private $runtime;
private $currentWorkingCopyStateRef = false;
private $currentCommitRef = false;
abstract public function getSourceControlSystemName();
public function getDiffLinesOfContext() {
@ -665,6 +669,64 @@ abstract class ArcanistRepositoryAPI extends Phobject {
return null;
}
final public function newFuture($pattern /* , ... */) {
$args = func_get_args();
return $this->buildLocalFuture($args)
->setResolveOnError(false);
}
final public function setRuntime(ArcanistRuntime $runtime) {
$this->runtime = $runtime;
return $this;
}
final public function getRuntime() {
return $this->runtime;
}
final protected function getSymbolEngine() {
return $this->getRuntime()->getSymbolEngine();
}
final public function getCurrentWorkingCopyStateRef() {
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 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();
}
@ -672,41 +734,4 @@ abstract class ArcanistRepositoryAPI extends Phobject {
final public function newBranchRef() {
return new ArcanistBranchRef();
}
final public function getCurrentCommitRef() {
$commit_hash = $this->newCurrentCommitHash();
return $this->newCommitRef()
->setCommitHash($commit_hash);
}
protected function newCurrentCommitRef() {
$commit_hash = $this->newCurrentCommitHash();
return $this->newCommitRefForSymbol($commit_hash);
}
protected function newCommitRefForSymbol() {
throw new ArcanistCapabilityNotSupportedException($this);
}
protected function newCurrentCommitHash() {
throw new ArcanistCapabilityNotSupportedException($this);
}
final public function getCurrentWorkingCopyStateRef() {
return $this->newCurrentWorkingCopyStateRef();
}
protected function newCurrentWorkingCopyStateRef() {
$commit_ref = $this->getCurrentCommitRef();
return id(new ArcanistWorkingCopyStateRef())
->setCommitRef($commit_ref);
}
final public function newFuture($pattern /* , ... */) {
$args = func_get_args();
return $this->buildLocalFuture($args)
->setResolveOnError(false);
}
}

View file

@ -8,6 +8,12 @@ final class ArcanistRuntime {
private $stack = array();
private $viewer;
private $hardpointEngine;
private $symbolEngine;
private $conduitEngine;
private $workingCopy;
public function execute(array $argv) {
try {
@ -108,6 +114,7 @@ final class ArcanistRuntime {
$this->workflows = $workflows;
$conduit_engine = $this->newConduitEngine($config);
$this->conduitEngine = $conduit_engine;
$phutil_workflows = array();
foreach ($workflows as $key => $workflow) {
@ -301,9 +308,14 @@ final class ArcanistRuntime {
->setArguments($args);
$working_copy = ArcanistWorkingCopy::newFromWorkingDirectory(getcwd());
if ($working_copy) {
$engine->setWorkingCopy($working_copy);
}
$this->workingCopy = $working_copy;
$working_copy
->getRepositoryAPI()
->setRuntime($this);
return $engine;
}
@ -682,6 +694,10 @@ final class ArcanistRuntime {
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 @@ final class ArcanistRuntime {
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;
}
}

View file

@ -1,22 +1,22 @@
<?php
abstract class ArcanistWorkflowHardpointQuery
abstract class ArcanistRuntimeHardpointQuery
extends ArcanistHardpointQuery {
private $workflow;
private $runtime;
private $canLoadHardpoint;
final public function setWorkflow(ArcanistWorkflow $workflow) {
$this->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 class ArcanistWorkflowHardpointQuery
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 @@ abstract class ArcanistWorkflowHardpointQuery
}
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 @@ abstract class ArcanistWorkflowHardpointQuery
}
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.

View file

@ -1,210 +1,278 @@
<?php
/**
* Synchronizes commit messages from Differential.
*/
final class ArcanistAmendWorkflow extends ArcanistWorkflow {
final class ArcanistAmendWorkflow
extends ArcanistArcWorkflow {
public function getWorkflowName() {
return 'amend';
}
public function getCommandSynopses() {
return phutil_console_format(<<<EOTEXT
**amend** [--revision __revision_id__] [--show]
public function getWorkflowInformation() {
$help = pht(<<<EOTEXT
Amend the working copy, synchronizing the local commit message from
Differential.
Supported in Mercurial 2.2 and newer.
EOTEXT
);
return $this->newWorkflowInformation()
->setSynopsis(
pht('Amend the working copy, synchronizing the local commit message.'))
->addExample('**amend** [options] -- ')
->setHelp($help);
}
public function getCommandHelp() {
return phutil_console_format(<<<EOTEXT
Supports: git, hg
Amend the working copy, synchronizing the local commit message from
Differential.
Supported in Mercurial 2.2 and newer.
EOTEXT
);
}
public function requiresWorkingCopy() {
return true;
}
public function requiresConduit() {
return true;
}
public function requiresAuthentication() {
return true;
}
public function requiresRepositoryAPI() {
return true;
}
public function getArguments() {
public function getWorkflowArguments() {
return array(
'show' => array(
'help' => pht(
$this->newWorkflowArgument('show')
->setHelp(
pht(
'Show the amended commit message, without modifying the '.
'working copy.'),
),
'revision' => array(
'param' => 'revision_id',
'help' => pht(
'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.'),
),
'copy.')),
);
}
public function run() {
protected function newPrompts() {
return array(
$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 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(
pht(
'You have uncommitted changes in this branch. Stage and commit '.
'(or revert) them before proceeding.'));
}
}
$revision_id = null;
if ($this->getArgument('revision')) {
$revision_id = $this->normalizeRevisionID($this->getArgument('revision'));
}
$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');
if (!$revision_id) {
if (count($in_working_copy) == 0) {
throw new ArcanistUsageException(
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 <id>'));
} 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 <id>');
throw new ArcanistUsageException($message);
if ($revision_symbol === null) {
$revision_ref = $this->selectRevisionRef($revision_refs);
} 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(
$revision_ref = $symbols->loadRevisionForSymbol($revision_symbol);
if (!$revision_ref) {
throw new PhutilArgumentUsageException(
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();
'Revision "%s" does not exist, or you do not have permission '.
'to see it.',
$revision_symbol));
}
}
}
}
$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}")
);
}
}
$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}"));
}
$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();
}
}
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);
}
$this->loadHardpoints(
$revision_ref,
ArcanistRevisionRef::HARDPOINT_COMMITMESSAGE);
$message = $revision_ref->getCommitMessage();
if ($is_show) {
echo $message."\n";
echo tsprintf(
"%B\n",
$message);
} else {
echo pht(
"Amending commit message to reflect revision %s.\n",
phutil_console_format(
'**D%d: %s**',
$revision_id,
$revision_title));
$repository_api->amendCommit($message);
}
return 0;
}
public function getSupportedRevisionControlSystems() {
return array('git', 'hg');
private function requireAmendSupport(ArcanistRepositoryAPI $api) {
if (!$api->supportsAmend()) {
if ($api instanceof ArcanistMercurialAPI) {
throw new PhutilArgumentUsageException(
pht(
'"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.'));
}
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();
}
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.'));
}
}
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 <id>" '.
'to specify which revision you want to amend.'));
}
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'));
throw new PhutilArgumentUsageException(
pht(
'Use "--revision <id>" to specify which revision you want '.
'to amend.'));
}
return head($revisions);
}
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();
}
}

View file

@ -76,9 +76,6 @@ abstract class ArcanistWorkflow extends Phobject {
private $configurationEngine;
private $configurationSourceList;
private $viewer;
private $hardpointEngine;
private $symbolEngine;
private $promptMap;
final public function setToolset(ArcanistToolset $toolset) {
@ -2303,45 +2300,7 @@ abstract class ArcanistWorkflow extends Phobject {
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 @@ abstract class ArcanistWorkflow extends Phobject {
}
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();
}
}

View file

@ -38,6 +38,23 @@ function xsprintf_terminal($userdata, &$pattern, &$pos, &$value, &$length) {
case 'R':
$type = 's';
break;
case 'W':
$value = PhutilTerminalString::escapeStringValue($value, true);
$value = phutil_console_wrap($value);
$type = 's';
break;
case '!':
$value = tsprintf('<bg:yellow>** <!> %s **</bg>', $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;