2013-02-27 17:04:54 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class DiffusionCommitQuery
|
|
|
|
extends PhabricatorCursorPagedPolicyAwareQuery {
|
|
|
|
|
2013-05-17 12:51:33 +02:00
|
|
|
private $ids;
|
2013-02-27 17:04:54 +01:00
|
|
|
private $identifiers;
|
2013-02-27 19:54:39 +01:00
|
|
|
private $phids;
|
2013-05-07 03:05:33 +02:00
|
|
|
private $defaultRepository;
|
2013-07-21 19:57:07 +02:00
|
|
|
private $identifierMap;
|
2013-10-30 21:06:28 +01:00
|
|
|
private $repositoryIDs;
|
|
|
|
|
|
|
|
private $needCommitData;
|
2013-02-27 17:04:54 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Load commits by partial or full identifiers, e.g. "rXab82393", "rX1234",
|
|
|
|
* or "a9caf12". When an identifier matches multiple commits, they will all
|
|
|
|
* be returned; callers should be prepared to deal with more results than
|
|
|
|
* they queried for.
|
|
|
|
*/
|
|
|
|
public function withIdentifiers(array $identifiers) {
|
|
|
|
$this->identifiers = $identifiers;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-05-07 03:05:33 +02:00
|
|
|
/**
|
|
|
|
* If a default repository is provided, ambiguous commit identifiers will
|
|
|
|
* be assumed to belong to the default repository.
|
|
|
|
*
|
|
|
|
* For example, "r123" appearing in a commit message in repository X is
|
|
|
|
* likely to be unambiguously "rX123". Normally the reference would be
|
|
|
|
* considered ambiguous, but if you provide a default repository it will
|
|
|
|
* be correctly resolved.
|
|
|
|
*/
|
|
|
|
public function withDefaultRepository(PhabricatorRepository $repository) {
|
|
|
|
$this->defaultRepository = $repository;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-10-30 21:06:28 +01:00
|
|
|
public function withRepositoryIDs(array $repository_ids) {
|
|
|
|
$this->repositoryIDs = $repository_ids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-05-17 12:51:33 +02:00
|
|
|
public function withIDs(array $ids) {
|
|
|
|
$this->ids = $ids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-02-27 19:54:39 +01:00
|
|
|
public function withPHIDs(array $phids) {
|
|
|
|
$this->phids = $phids;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-11-07 21:10:43 +01:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Look up commits in a specific repository. This is a shorthand for calling
|
|
|
|
* @{method:withDefaultRepository} and @{method:withRepositoryIDs}.
|
|
|
|
*/
|
|
|
|
public function withRepository(PhabricatorRepository $repository) {
|
|
|
|
$this->withDefaultRepository($repository);
|
|
|
|
$this->withRepositoryIDs(array($repository->getID()));
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-10-30 21:06:28 +01:00
|
|
|
public function needCommitData($need) {
|
|
|
|
$this->needCommitData = $need;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-07-21 19:57:07 +02:00
|
|
|
public function getIdentifierMap() {
|
|
|
|
if ($this->identifierMap === null) {
|
|
|
|
throw new Exception(
|
|
|
|
"You must execute() the query before accessing the identifier map.");
|
|
|
|
}
|
|
|
|
return $this->identifierMap;
|
|
|
|
}
|
|
|
|
|
2013-12-31 20:08:08 +01:00
|
|
|
protected function willExecute() {
|
2013-07-21 19:57:07 +02:00
|
|
|
if ($this->identifierMap === null) {
|
|
|
|
$this->identifierMap = array();
|
|
|
|
}
|
2013-12-31 20:08:08 +01:00
|
|
|
}
|
2013-07-21 19:57:07 +02:00
|
|
|
|
2013-12-31 20:08:08 +01:00
|
|
|
protected function loadPage() {
|
2013-02-27 17:04:54 +01:00
|
|
|
$table = new PhabricatorRepositoryCommit();
|
|
|
|
$conn_r = $table->establishConnection('r');
|
|
|
|
|
|
|
|
$data = queryfx_all(
|
|
|
|
$conn_r,
|
|
|
|
'SELECT * FROM %T %Q %Q %Q',
|
|
|
|
$table->getTableName(),
|
|
|
|
$this->buildWhereClause($conn_r),
|
|
|
|
$this->buildOrderClause($conn_r),
|
|
|
|
$this->buildLimitClause($conn_r));
|
|
|
|
|
|
|
|
return $table->loadAllFromArray($data);
|
|
|
|
}
|
|
|
|
|
2013-10-30 21:06:28 +01:00
|
|
|
protected function willFilterPage(array $commits) {
|
2013-02-27 17:04:54 +01:00
|
|
|
$repository_ids = mpull($commits, 'getRepositoryID', 'getRepositoryID');
|
|
|
|
$repos = id(new PhabricatorRepositoryQuery())
|
|
|
|
->setViewer($this->getViewer())
|
|
|
|
->withIDs($repository_ids)
|
|
|
|
->execute();
|
|
|
|
|
|
|
|
foreach ($commits as $key => $commit) {
|
|
|
|
$repo = idx($repos, $commit->getRepositoryID());
|
|
|
|
if ($repo) {
|
|
|
|
$commit->attachRepository($repo);
|
|
|
|
} else {
|
|
|
|
unset($commits[$key]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-07-21 19:57:07 +02:00
|
|
|
if ($this->identifiers !== null) {
|
|
|
|
$ids = array_fuse($this->identifiers);
|
|
|
|
$min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH;
|
|
|
|
|
|
|
|
$result = array();
|
|
|
|
foreach ($commits as $commit) {
|
|
|
|
$prefix = 'r'.$commit->getRepository()->getCallsign();
|
|
|
|
$suffix = $commit->getCommitIdentifier();
|
|
|
|
|
|
|
|
if ($commit->getRepository()->isSVN()) {
|
|
|
|
if (isset($ids[$prefix.$suffix])) {
|
|
|
|
$result[$prefix.$suffix][] = $commit;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
// This awkward contruction is so we can link the commits up in O(N)
|
|
|
|
// time instead of O(N^2).
|
|
|
|
for ($ii = $min_qualified; $ii <= strlen($suffix); $ii++) {
|
|
|
|
$part = substr($suffix, 0, $ii);
|
|
|
|
if (isset($ids[$prefix.$part])) {
|
|
|
|
$result[$prefix.$part][] = $commit;
|
|
|
|
}
|
|
|
|
if (isset($ids[$part])) {
|
|
|
|
$result[$part][] = $commit;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($result as $identifier => $matching_commits) {
|
|
|
|
if (count($matching_commits) == 1) {
|
|
|
|
$result[$identifier] = head($matching_commits);
|
|
|
|
} else {
|
|
|
|
// This reference is ambiguous (it matches more than one commit) so
|
|
|
|
// don't link it
|
|
|
|
unset($result[$identifier]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->identifierMap += $result;
|
|
|
|
}
|
|
|
|
|
2013-02-27 17:04:54 +01:00
|
|
|
return $commits;
|
|
|
|
}
|
|
|
|
|
2013-10-30 21:06:28 +01:00
|
|
|
protected function didFilterPage(array $commits) {
|
|
|
|
|
|
|
|
if ($this->needCommitData) {
|
|
|
|
$data = id(new PhabricatorRepositoryCommitData())->loadAllWhere(
|
|
|
|
'commitID in (%Ld)',
|
|
|
|
mpull($commits, 'getID'));
|
|
|
|
$data = mpull($data, null, 'getCommitID');
|
|
|
|
foreach ($commits as $commit) {
|
|
|
|
$commit_data = idx($data, $commit->getID());
|
|
|
|
$commit->attachCommitData($commit_data);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $commits;
|
|
|
|
}
|
|
|
|
|
2013-02-27 17:04:54 +01:00
|
|
|
private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
|
|
|
$where = array();
|
|
|
|
|
|
|
|
if ($this->identifiers) {
|
|
|
|
$min_unqualified = PhabricatorRepository::MINIMUM_UNQUALIFIED_HASH;
|
|
|
|
$min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH;
|
|
|
|
|
|
|
|
$refs = array();
|
|
|
|
$bare = array();
|
|
|
|
foreach ($this->identifiers as $identifier) {
|
|
|
|
$matches = null;
|
|
|
|
preg_match('/^(?:r([A-Z]+))?(.*)$/', $identifier, $matches);
|
|
|
|
$repo = nonempty($matches[1], null);
|
|
|
|
$identifier = nonempty($matches[2], null);
|
|
|
|
|
2013-05-07 03:05:33 +02:00
|
|
|
if ($repo === null) {
|
|
|
|
if ($this->defaultRepository) {
|
|
|
|
$repo = $this->defaultRepository->getCallsign();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-02-27 17:04:54 +01:00
|
|
|
if ($repo === null) {
|
|
|
|
if (strlen($identifier) < $min_unqualified) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$bare[] = $identifier;
|
|
|
|
} else {
|
|
|
|
$refs[] = array(
|
|
|
|
'callsign' => $repo,
|
|
|
|
'identifier' => $identifier,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$sql = array();
|
|
|
|
|
|
|
|
foreach ($bare as $identifier) {
|
|
|
|
$sql[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'(commitIdentifier LIKE %> AND LENGTH(commitIdentifier) = 40)',
|
|
|
|
$identifier);
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($refs) {
|
|
|
|
$callsigns = ipull($refs, 'callsign');
|
|
|
|
$repos = id(new PhabricatorRepositoryQuery())
|
|
|
|
->setViewer($this->getViewer())
|
|
|
|
->withCallsigns($callsigns)
|
|
|
|
->execute();
|
|
|
|
$repos = mpull($repos, null, 'getCallsign');
|
|
|
|
|
|
|
|
foreach ($refs as $key => $ref) {
|
|
|
|
$repo = idx($repos, $ref['callsign']);
|
|
|
|
if (!$repo) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($repo->isSVN()) {
|
2013-03-19 23:30:16 +01:00
|
|
|
if (!ctype_digit($ref['identifier'])) {
|
|
|
|
continue;
|
|
|
|
}
|
2013-02-27 17:04:54 +01:00
|
|
|
$sql[] = qsprintf(
|
|
|
|
$conn_r,
|
2013-06-14 03:01:40 +02:00
|
|
|
'(repositoryID = %d AND commitIdentifier = %s)',
|
2013-02-27 17:04:54 +01:00
|
|
|
$repo->getID(),
|
2013-06-14 03:01:40 +02:00
|
|
|
// NOTE: Because the 'commitIdentifier' column is a string, MySQL
|
|
|
|
// ignores the index if we hand it an integer. Hand it a string.
|
|
|
|
// See T3377.
|
|
|
|
(int)$ref['identifier']);
|
2013-02-27 17:04:54 +01:00
|
|
|
} else {
|
|
|
|
if (strlen($ref['identifier']) < $min_qualified) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
$sql[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'(repositoryID = %d AND commitIdentifier LIKE %>)',
|
|
|
|
$repo->getID(),
|
|
|
|
$ref['identifier']);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-03-01 20:28:02 +01:00
|
|
|
if (!$sql) {
|
2013-02-27 17:04:54 +01:00
|
|
|
// If we discarded all possible identifiers (e.g., they all referenced
|
|
|
|
// bogus repositories or were all too short), make sure the query finds
|
|
|
|
// nothing.
|
2013-03-01 20:28:02 +01:00
|
|
|
throw new PhabricatorEmptyQueryException('No commit identifiers.');
|
2013-02-27 17:04:54 +01:00
|
|
|
}
|
2013-03-01 20:28:02 +01:00
|
|
|
|
|
|
|
$where[] = '('.implode(' OR ', $sql).')';
|
2013-02-27 17:04:54 +01:00
|
|
|
}
|
|
|
|
|
2013-05-17 12:51:33 +02:00
|
|
|
if ($this->ids) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'id IN (%Ld)',
|
|
|
|
$this->ids);
|
|
|
|
}
|
|
|
|
|
2013-02-27 19:54:39 +01:00
|
|
|
if ($this->phids) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'phid IN (%Ls)',
|
|
|
|
$this->phids);
|
|
|
|
}
|
|
|
|
|
2013-10-30 21:06:28 +01:00
|
|
|
if ($this->repositoryIDs) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$conn_r,
|
|
|
|
'repositoryID IN (%Ld)',
|
|
|
|
$this->repositoryIDs);
|
|
|
|
}
|
|
|
|
|
2013-02-27 17:04:54 +01:00
|
|
|
return $this->formatWhereClause($where);
|
|
|
|
}
|
|
|
|
|
2013-07-21 19:57:07 +02:00
|
|
|
public function didFilterResults(array $filtered) {
|
|
|
|
if ($this->identifierMap) {
|
|
|
|
foreach ($this->identifierMap as $name => $commit) {
|
|
|
|
if (isset($filtered[$commit->getPHID()])) {
|
|
|
|
unset($this->identifierMap[$name]);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
Lock policy queries to their applications
Summary:
While we mostly have reasonable effective object accessibility when you lock a user out of an application, it's primarily enforced at the controller level. Users can still, e.g., load the handles of objects they can't actually see. Instead, lock the queries to the applications so that you can, e.g., never load a revision if you don't have access to Differential.
This has several parts:
- For PolicyAware queries, provide an application class name method.
- If the query specifies a class name and the user doesn't have permission to use it, fail the entire query unconditionally.
- For handles, simplify query construction and count all the PHIDs as "restricted" so we get a UI full of "restricted" instead of "unknown" handles.
Test Plan:
- Added a unit test to verify I got all the class names right.
- Browsed around, logged in/out as a normal user with public policies on and off.
- Browsed around, logged in/out as a restricted user with public policies on and off. With restrictions, saw all traces of restricted apps removed or restricted.
Reviewers: btrahan
Reviewed By: btrahan
CC: aran
Differential Revision: https://secure.phabricator.com/D7367
2013-10-22 02:20:27 +02:00
|
|
|
public function getQueryApplicationClass() {
|
|
|
|
return 'PhabricatorApplicationDiffusion';
|
|
|
|
}
|
|
|
|
|
2013-02-27 17:04:54 +01:00
|
|
|
}
|