1
0
Fork 0
mirror of https://we.phorge.it/source/arcanist.git synced 2024-11-22 14:52:40 +01:00

Add ref lookup for username symbols

Summary:
Ref T13490. I'm attempting to update "arc amend", but it needs to fetch revision authors to raise an "amending a revision you don't own" error.

Support user-symbol resolution.

Along the way, this introduces some infrastructure for abstracting away iteration over a multi-page Conduit result set.

Test Plan:
  - Ran "arc inspect ..." for various "user(...)" queries.
  - Set page size to 3 and issued a general query, saw the future page it away properly.

Maniphest Tasks: T13490

Differential Revision: https://secure.phabricator.com/D21092
This commit is contained in:
epriestley 2020-04-12 06:54:46 -07:00
parent 1f18f25fa5
commit 088b157444
9 changed files with 443 additions and 2 deletions

View file

@ -487,6 +487,10 @@ phutil_register_library_map(array(
'ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase.php', 'ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase.php',
'ArcanistUserAbortException' => 'exception/usage/ArcanistUserAbortException.php', 'ArcanistUserAbortException' => 'exception/usage/ArcanistUserAbortException.php',
'ArcanistUserConfigurationSource' => 'config/source/ArcanistUserConfigurationSource.php', 'ArcanistUserConfigurationSource' => 'config/source/ArcanistUserConfigurationSource.php',
'ArcanistUserRef' => 'ref/user/ArcanistUserRef.php',
'ArcanistUserSymbolHardpointQuery' => 'ref/user/ArcanistUserSymbolHardpointQuery.php',
'ArcanistUserSymbolRef' => 'ref/user/ArcanistUserSymbolRef.php',
'ArcanistUserSymbolRefInspector' => 'ref/user/ArcanistUserSymbolRefInspector.php',
'ArcanistVariableReferenceSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableReferenceSpacingXHPASTLinterRule.php', 'ArcanistVariableReferenceSpacingXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableReferenceSpacingXHPASTLinterRule.php',
'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase.php', 'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'lint/linter/xhpast/rules/__tests__/ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase.php',
'ArcanistVariableVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php', 'ArcanistVariableVariableXHPASTLinterRule' => 'lint/linter/xhpast/rules/ArcanistVariableVariableXHPASTLinterRule.php',
@ -527,6 +531,7 @@ phutil_register_library_map(array(
'ConduitClientException' => 'conduit/ConduitClientException.php', 'ConduitClientException' => 'conduit/ConduitClientException.php',
'ConduitClientTestCase' => 'conduit/__tests__/ConduitClientTestCase.php', 'ConduitClientTestCase' => 'conduit/__tests__/ConduitClientTestCase.php',
'ConduitFuture' => 'conduit/ConduitFuture.php', 'ConduitFuture' => 'conduit/ConduitFuture.php',
'ConduitSearchFuture' => 'conduit/ConduitSearchFuture.php',
'ExecFuture' => 'future/exec/ExecFuture.php', 'ExecFuture' => 'future/exec/ExecFuture.php',
'ExecFutureTestCase' => 'future/exec/__tests__/ExecFutureTestCase.php', 'ExecFutureTestCase' => 'future/exec/__tests__/ExecFutureTestCase.php',
'ExecPassthruTestCase' => 'future/exec/__tests__/ExecPassthruTestCase.php', 'ExecPassthruTestCase' => 'future/exec/__tests__/ExecPassthruTestCase.php',
@ -537,6 +542,7 @@ phutil_register_library_map(array(
'FilesystemException' => 'filesystem/FilesystemException.php', 'FilesystemException' => 'filesystem/FilesystemException.php',
'FilesystemTestCase' => 'filesystem/__tests__/FilesystemTestCase.php', 'FilesystemTestCase' => 'filesystem/__tests__/FilesystemTestCase.php',
'Future' => 'future/Future.php', 'Future' => 'future/Future.php',
'FutureAgent' => 'conduit/FutureAgent.php',
'FutureIterator' => 'future/FutureIterator.php', 'FutureIterator' => 'future/FutureIterator.php',
'FutureIteratorTestCase' => 'future/__tests__/FutureIteratorTestCase.php', 'FutureIteratorTestCase' => 'future/__tests__/FutureIteratorTestCase.php',
'FuturePool' => 'future/FuturePool.php', 'FuturePool' => 'future/FuturePool.php',
@ -1455,6 +1461,10 @@ phutil_register_library_map(array(
'ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistUselessOverridingMethodXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistUserAbortException' => 'ArcanistUsageException', 'ArcanistUserAbortException' => 'ArcanistUsageException',
'ArcanistUserConfigurationSource' => 'ArcanistFilesystemConfigurationSource', 'ArcanistUserConfigurationSource' => 'ArcanistFilesystemConfigurationSource',
'ArcanistUserRef' => 'ArcanistRef',
'ArcanistUserSymbolHardpointQuery' => 'ArcanistWorkflowHardpointQuery',
'ArcanistUserSymbolRef' => 'ArcanistSymbolRef',
'ArcanistUserSymbolRefInspector' => 'ArcanistRefInspector',
'ArcanistVariableReferenceSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistVariableReferenceSpacingXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase', 'ArcanistVariableReferenceSpacingXHPASTLinterRuleTestCase' => 'ArcanistXHPASTLinterRuleTestCase',
'ArcanistVariableVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule', 'ArcanistVariableVariableXHPASTLinterRule' => 'ArcanistXHPASTLinterRule',
@ -1495,6 +1505,7 @@ phutil_register_library_map(array(
'ConduitClientException' => 'Exception', 'ConduitClientException' => 'Exception',
'ConduitClientTestCase' => 'PhutilTestCase', 'ConduitClientTestCase' => 'PhutilTestCase',
'ConduitFuture' => 'FutureProxy', 'ConduitFuture' => 'FutureProxy',
'ConduitSearchFuture' => 'FutureAgent',
'ExecFuture' => 'PhutilExecutableFuture', 'ExecFuture' => 'PhutilExecutableFuture',
'ExecFutureTestCase' => 'PhutilTestCase', 'ExecFutureTestCase' => 'PhutilTestCase',
'ExecPassthruTestCase' => 'PhutilTestCase', 'ExecPassthruTestCase' => 'PhutilTestCase',
@ -1505,6 +1516,7 @@ phutil_register_library_map(array(
'FilesystemException' => 'Exception', 'FilesystemException' => 'Exception',
'FilesystemTestCase' => 'PhutilTestCase', 'FilesystemTestCase' => 'PhutilTestCase',
'Future' => 'Phobject', 'Future' => 'Phobject',
'FutureAgent' => 'Future',
'FutureIterator' => array( 'FutureIterator' => array(
'Phobject', 'Phobject',
'Iterator', 'Iterator',

View file

@ -0,0 +1,103 @@
<?php
final class ConduitSearchFuture
extends FutureAgent {
private $conduitEngine;
private $method;
private $constraints;
private $objects = array();
private $cursor;
public function setConduitEngine(ArcanistConduitEngine $conduit_engine) {
$this->conduitEngine = $conduit_engine;
return $this;
}
public function getConduitEngine() {
return $this->conduitEngine;
}
public function setMethod($method) {
$this->method = $method;
return $this;
}
public function getMethod() {
return $this->method;
}
public function setConstraints($constraints) {
$this->constraints = $constraints;
return $this;
}
public function getConstraints() {
return $this->constraints;
}
public function isReady() {
if ($this->hasResult()) {
return true;
}
$futures = $this->getFutures();
$future = head($futures);
if (!$future) {
$future = $this->newFuture();
}
if (!$future->isReady()) {
$this->setFutures(array($future));
return false;
} else {
$this->setFutures(array());
}
$result = $future->resolve();
foreach ($this->readResults($result) as $object) {
$this->objects[] = $object;
}
$cursor = idxv($result, array('cursor', 'after'));
if ($cursor === null) {
$this->setResult($this->objects);
return true;
}
$this->cursor = $cursor;
$future = $this->newFuture();
$this->setFutures(array($future));
return false;
}
private function newFuture() {
$engine = $this->getConduitEngine();
$method = $this->getMethod();
$constraints = $this->getConstraints();
$parameters = array(
'constraints' => $constraints,
);
if ($this->cursor !== null) {
$parameters['after'] = (string)$this->cursor;
}
$conduit_call = $engine->newCall($method, $parameters);
$conduit_future = $engine->newFuture($conduit_call);
return $conduit_future;
}
private function readResults(array $data) {
return idx($data, 'data');
}
}

View file

@ -0,0 +1,38 @@
<?php
abstract class FutureAgent
extends Future {
private $futures = array();
final protected function setFutures(array $futures) {
$this->futures = $futures;
}
final protected function getFutures() {
return $this->futures;
}
final public function getReadSockets() {
$sockets = array();
foreach ($this->getFutures() as $future) {
foreach ($future->getReadSockets() as $read_socket) {
$sockets[] = $read_socket;
}
}
return $sockets;
}
final public function getWriteSockets() {
$sockets = array();
foreach ($this->getFutures() as $future) {
foreach ($future->getWriteSockets() as $read_socket) {
$sockets[] = $read_socket;
}
}
return $sockets;
}
}

View file

@ -0,0 +1,33 @@
<?php
final class ArcanistUserRef
extends ArcanistRef {
private $parameters;
public function getRefDisplayName() {
return pht('User "%s"', $this->getUsername());
}
public static function newFromConduit(array $parameters) {
$ref = new self();
$ref->parameters = $parameters;
return $ref;
}
public static function newFromConduitWhoami(array $parameters) {
// NOTE: The "user.whoami" call returns a different structure than
// "user.search". Mangle the data so it looks similar.
$parameters['fields'] = array(
'username' => idx($parameters, 'userName'),
);
return self::newFromConduit($parameters);
}
public function getUsername() {
return idxv($this->parameters, array('fields', 'username'));
}
}

View file

@ -0,0 +1,155 @@
<?php
final class ArcanistUserSymbolHardpointQuery
extends ArcanistWorkflowHardpointQuery {
public function getHardpoints() {
return array(
ArcanistUserSymbolRef::HARDPOINT_OBJECT,
);
}
protected function canLoadRef(ArcanistRef $ref) {
return ($ref instanceof ArcanistUserSymbolRef);
}
public function loadHardpoint(array $refs, $hardpoint) {
$id_map = array();
$phid_map = array();
$username_map = array();
$function_map = array();
foreach ($refs as $key => $ref) {
switch ($ref->getSymbolType()) {
case ArcanistUserSymbolRef::TYPE_ID:
$id_map[$key] = $ref->getSymbol();
break;
case ArcanistUserSymbolRef::TYPE_PHID:
$phid_map[$key] = $ref->getSymbol();
break;
case ArcanistUserSymbolRef::TYPE_USERNAME:
$username_map[$key] = $ref->getSymbol();
break;
case ArcanistUserSymbolRef::TYPE_FUNCTION:
$symbol = $ref->getSymbol();
if ($symbol !== 'viewer()') {
throw new Exception(
pht(
'Only the function "viewer()" is supported.'));
}
$function_map[$key] = $symbol;
break;
}
}
$futures = array();
if ($function_map) {
// The only function we support is "viewer()".
$function_future = $this->newConduit(
'user.whoami',
array());
$futures[] = $function_future;
} else {
$function_future = null;
}
if ($id_map) {
$id_future = $this->newConduitSearch(
'user.search',
array(
'ids' => array_values(array_fuse($id_map)),
));
$futures[] = $id_future;
} else {
$id_future = null;
}
if ($phid_map) {
$phid_future = $this->newConduitSearch(
'user.search',
array(
'phids' => array_values(array_fuse($phid_map)),
));
$futures[] = $phid_future;
} else {
$phid_future = null;
}
if ($username_map) {
$username_future = $this->newConduitSearch(
'user.search',
array(
'usernames' => array_values(array_fuse($username_map)),
));
$futures[] = $username_future;
} else {
$username_future = null;
}
yield $this->yieldFutures($futures);
$result_map = array();
if ($id_future) {
$id_results = $id_future->resolve();
$id_results = ipull($id_results, null, 'id');
foreach ($id_map as $key => $id) {
$result_map[$key] = idx($id_results, $id);
}
}
if ($phid_future) {
$phid_results = $phid_future->resolve();
$phid_results = ipull($phid_results, null, 'phid');
foreach ($phid_map as $key => $phid) {
$result_map[$key] = idx($phid_results, $phid);
}
}
if ($username_future) {
$raw_results = $username_future->resolve();
$username_results = array();
foreach ($raw_results as $raw_result) {
$username = idxv($raw_result, array('fields', 'username'));
$username_results[$username] = $raw_result;
}
foreach ($username_map as $key => $username) {
$result_map[$key] = idx($username_results, $username);
}
}
foreach ($result_map as $key => $raw_result) {
if ($raw_result === null) {
continue;
}
$result_map[$key] = ArcanistUserRef::newFromConduit($raw_result);
}
if ($function_future) {
$raw_result = $function_future->resolve();
if ($raw_result === null) {
$function_ref = null;
} else {
$function_ref = ArcanistUserRef::newFromConduitWhoami($raw_result);
}
foreach ($function_map as $key => $function) {
$result_map[$key] = $function_ref;
}
}
yield $this->yieldMap($result_map);
}
}

View file

@ -0,0 +1,56 @@
<?php
final class ArcanistUserSymbolRef
extends ArcanistSymbolRef {
private $type;
const TYPE_ID = 'id';
const TYPE_PHID = 'phid';
const TYPE_USERNAME = 'username';
const TYPE_FUNCTION = 'function';
public function getRefDisplayName() {
return pht('User Symbol "%s"', $this->getSymbol());
}
public function getSymbolType() {
return $this->type;
}
protected function resolveSymbol($symbol) {
$matches = null;
$is_id = preg_match('/^([1-9]\d*)\z/', $symbol, $matches);
if ($is_id) {
$this->type = self::TYPE_ID;
return (int)$matches[1];
}
$is_phid = preg_match('/^PHID-USER-\S+\z/', $symbol, $matches);
if ($is_phid) {
$this->type = self::TYPE_PHID;
return $matches[0];
}
$is_function = preg_match('/^\S+\(\s*\)\s*\z/', $symbol, $matches);
if ($is_function) {
$this->type = self::TYPE_FUNCTION;
return $matches[0];
}
$is_username = preg_match('/^@?(\S+)\z/', $symbol, $matches);
if ($is_username) {
$this->type = self::TYPE_USERNAME;
return $matches[1];
}
throw new PhutilArgumentUsageException(
pht(
'The format of user symbol "%s" is unrecognized. Expected a '.
'username like "alice" or "@alice", or a user PHID, or a user '.
'ID, or a special function like "viewer()".',
$symbol));
}
}

View file

@ -0,0 +1,22 @@
<?php
final class ArcanistUserSymbolRefInspector
extends ArcanistRefInspector {
public function getInspectFunctionName() {
return 'user';
}
public function newInspectRef(array $argv) {
if (count($argv) !== 1) {
throw new PhutilArgumentUsageException(
pht(
'Expected exactly one argument to "user(...)" with a '.
'user symbol.'));
}
return id(new ArcanistUserSymbolRef())
->setSymbol($argv[0]);
}
}

View file

@ -51,13 +51,35 @@ abstract class ArcanistWorkflowHardpointQuery
abstract protected function canLoadRef(ArcanistRef $ref); abstract protected function canLoadRef(ArcanistRef $ref);
final public function yieldConduit($method, array $parameters) { final public function newConduitSearch($method, $constraints) {
$conduit_engine = $this->getWorkflow()
->getConduitEngine();
$conduit_future = id(new ConduitSearchFuture())
->setConduitEngine($conduit_engine)
->setMethod($method)
->setConstraints($constraints);
return $conduit_future;
}
final public function yieldConduitSearch($method, $constraints) {
$conduit_future = $this->newConduitSearch($method, $constraints);
return $this->yieldFuture($conduit_future);
}
final public function newConduit($method, $parameters) {
$conduit_engine = $this->getWorkflow() $conduit_engine = $this->getWorkflow()
->getConduitEngine(); ->getConduitEngine();
$call_object = $conduit_engine->newCall($method, $parameters); $call_object = $conduit_engine->newCall($method, $parameters);
$call_future = $conduit_engine->newFuture($call_object); $call_future = $conduit_engine->newFuture($call_object);
return $call_future;
}
final public function yieldConduit($method, array $parameters) {
$call_future = $this->newConduit($method, $parameters);
return $this->yieldFuture($call_future); return $this->yieldFuture($call_future);
} }

View file

@ -53,7 +53,7 @@ EOTEXT
$all_refs = array(); $all_refs = array();
foreach ($objects as $description) { foreach ($objects as $description) {
$matches = null; $matches = null;
$pattern = '/^([\w-]+)(?:\(([^)]+)\))?\z/'; $pattern = '/^([\w-]+)(?:\((.*)\))?\z/';
if (!preg_match($pattern, $description, $matches)) { if (!preg_match($pattern, $description, $matches)) {
throw new PhutilArgumentUsageException( throw new PhutilArgumentUsageException(
pht( pht(