Changeset View
Changeset View
Standalone View
Standalone View
src/applications/diffusion/query/DiffusionCommitQuery.php
<?php | <?php | ||||
final class DiffusionCommitQuery | final class DiffusionCommitQuery | ||||
extends PhabricatorCursorPagedPolicyAwareQuery { | extends PhabricatorCursorPagedPolicyAwareQuery { | ||||
private $ids; | private $ids; | ||||
private $identifiers; | |||||
private $phids; | private $phids; | ||||
private $authorPHIDs; | |||||
private $defaultRepository; | private $defaultRepository; | ||||
private $identifierMap; | private $identifiers; | ||||
private $repositoryIDs; | private $repositoryIDs; | ||||
private $identifierMap; | |||||
private $needAuditRequests; | |||||
private $auditIDs; | |||||
private $auditorPHIDs; | |||||
private $auditAwaitingUser; | |||||
private $auditStatus; | |||||
const AUDIT_STATUS_ANY = 'audit-status-any'; | |||||
const AUDIT_STATUS_OPEN = 'audit-status-open'; | |||||
const AUDIT_STATUS_CONCERN = 'audit-status-concern'; | |||||
private $loadAuditIds; | |||||
private $needCommitData; | private $needCommitData; | ||||
public function withIDs(array $ids) { | |||||
$this->ids = $ids; | |||||
return $this; | |||||
} | |||||
public function withPHIDs(array $phids) { | |||||
$this->phids = $phids; | |||||
return $this; | |||||
} | |||||
public function withAuthorPHIDs(array $phids) { | |||||
$this->authorPHIDs = $phids; | |||||
return $this; | |||||
} | |||||
/** | /** | ||||
* Load commits by partial or full identifiers, e.g. "rXab82393", "rX1234", | * Load commits by partial or full identifiers, e.g. "rXab82393", "rX1234", | ||||
* or "a9caf12". When an identifier matches multiple commits, they will all | * or "a9caf12". When an identifier matches multiple commits, they will all | ||||
* be returned; callers should be prepared to deal with more results than | * be returned; callers should be prepared to deal with more results than | ||||
* they queried for. | * they queried for. | ||||
*/ | */ | ||||
public function withIdentifiers(array $identifiers) { | public function withIdentifiers(array $identifiers) { | ||||
$this->identifiers = $identifiers; | $this->identifiers = $identifiers; | ||||
return $this; | return $this; | ||||
} | } | ||||
/** | /** | ||||
* 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; | |||||
} | |||||
/** | |||||
* If a default repository is provided, ambiguous commit identifiers will | * If a default repository is provided, ambiguous commit identifiers will | ||||
* be assumed to belong to the default repository. | * be assumed to belong to the default repository. | ||||
* | * | ||||
* For example, "r123" appearing in a commit message in repository X is | * For example, "r123" appearing in a commit message in repository X is | ||||
* likely to be unambiguously "rX123". Normally the reference would be | * likely to be unambiguously "rX123". Normally the reference would be | ||||
* considered ambiguous, but if you provide a default repository it will | * considered ambiguous, but if you provide a default repository it will | ||||
* be correctly resolved. | * be correctly resolved. | ||||
*/ | */ | ||||
public function withDefaultRepository(PhabricatorRepository $repository) { | public function withDefaultRepository(PhabricatorRepository $repository) { | ||||
$this->defaultRepository = $repository; | $this->defaultRepository = $repository; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function withRepositoryIDs(array $repository_ids) { | public function withRepositoryIDs(array $repository_ids) { | ||||
$this->repositoryIDs = $repository_ids; | $this->repositoryIDs = $repository_ids; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function withIDs(array $ids) { | public function needCommitData($need) { | ||||
$this->ids = $ids; | $this->needCommitData = $need; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function withPHIDs(array $phids) { | public function needAuditRequests($need) { | ||||
$this->phids = $phids; | $this->needAuditRequests = $need; | ||||
return $this; | return $this; | ||||
} | } | ||||
public function getAuditRequests() { | |||||
return | |||||
$this->needAuditRequests || | |||||
$this->auditIDs || | |||||
$this->auditorPHIDs || | |||||
$this->auditAwaitingUser || | |||||
$this->auditStatus; | |||||
} | |||||
/** | public function withAuditIDs(array $ids) { | ||||
* Look up commits in a specific repository. This is a shorthand for calling | $this->auditIDs = $ids; | ||||
* @{method:withDefaultRepository} and @{method:withRepositoryIDs}. | |||||
*/ | |||||
public function withRepository(PhabricatorRepository $repository) { | |||||
$this->withDefaultRepository($repository); | |||||
$this->withRepositoryIDs(array($repository->getID())); | |||||
return $this; | return $this; | ||||
} | } | ||||
public function needCommitData($need) { | public function withAuditorPHIDs(array $auditor_phids) { | ||||
$this->needCommitData = $need; | $this->auditorPHIDs = $auditor_phids; | ||||
return $this; | |||||
} | |||||
public function withAuditAwaitingUser(PhabricatorUser $user) { | |||||
$this->auditAwaitingUser = $user; | |||||
return $this; | |||||
} | |||||
public function withAuditStatus($status) { | |||||
$this->auditStatus = $status; | |||||
return $this; | return $this; | ||||
} | } | ||||
public function getIdentifierMap() { | public function getIdentifierMap() { | ||||
if ($this->identifierMap === null) { | if ($this->identifierMap === null) { | ||||
throw new Exception( | throw new Exception( | ||||
"You must execute() the query before accessing the identifier map."); | "You must execute() the query before accessing the identifier map."); | ||||
} | } | ||||
return $this->identifierMap; | return $this->identifierMap; | ||||
} | } | ||||
protected function getPagingColumn() { | |||||
return 'commit.id'; | |||||
} | |||||
protected function willExecute() { | protected function willExecute() { | ||||
if ($this->identifierMap === null) { | if ($this->identifierMap === null) { | ||||
$this->identifierMap = array(); | $this->identifierMap = array(); | ||||
} | } | ||||
} | } | ||||
protected function loadPage() { | protected function loadPage() { | ||||
$table = new PhabricatorRepositoryCommit(); | $table = new PhabricatorRepositoryCommit(); | ||||
$conn_r = $table->establishConnection('r'); | $conn_r = $table->establishConnection('r'); | ||||
$data = queryfx_all( | $data = queryfx_all( | ||||
$conn_r, | $conn_r, | ||||
'SELECT * FROM %T %Q %Q %Q', | 'SELECT commit.* %Q FROM %T commit %Q %Q %Q %Q', | ||||
$this->buildAuditSelect($conn_r), | |||||
$table->getTableName(), | $table->getTableName(), | ||||
$this->buildJoinClause($conn_r), | |||||
$this->buildWhereClause($conn_r), | $this->buildWhereClause($conn_r), | ||||
$this->buildOrderClause($conn_r), | $this->buildOrderClause($conn_r), | ||||
$this->buildLimitClause($conn_r)); | $this->buildLimitClause($conn_r)); | ||||
if ($this->getAuditRequests()) { | |||||
$this->loadAuditIds = ipull($data, 'audit_id'); | |||||
} | |||||
return $table->loadAllFromArray($data); | return $table->loadAllFromArray($data); | ||||
} | } | ||||
private function buildAuditSelect($conn_r) { | |||||
if ($this->getAuditRequests()) { | |||||
return qsprintf( | |||||
$conn_r, | |||||
', audit.id as audit_id'); | |||||
} | |||||
return ''; | |||||
} | |||||
protected function willFilterPage(array $commits) { | protected function willFilterPage(array $commits) { | ||||
$repository_ids = mpull($commits, 'getRepositoryID', 'getRepositoryID'); | $repository_ids = mpull($commits, 'getRepositoryID', 'getRepositoryID'); | ||||
$repos = id(new PhabricatorRepositoryQuery()) | $repos = id(new PhabricatorRepositoryQuery()) | ||||
->setViewer($this->getViewer()) | ->setViewer($this->getViewer()) | ||||
->withIDs($repository_ids) | ->withIDs($repository_ids) | ||||
->execute(); | ->execute(); | ||||
foreach ($commits as $key => $commit) { | foreach ($commits as $key => $commit) { | ||||
▲ Show 20 Lines • Show All 57 Lines • ▼ Show 20 Lines | if ($this->needCommitData) { | ||||
mpull($commits, 'getID')); | mpull($commits, 'getID')); | ||||
$data = mpull($data, null, 'getCommitID'); | $data = mpull($data, null, 'getCommitID'); | ||||
foreach ($commits as $commit) { | foreach ($commits as $commit) { | ||||
$commit_data = idx($data, $commit->getID()); | $commit_data = idx($data, $commit->getID()); | ||||
$commit->attachCommitData($commit_data); | $commit->attachCommitData($commit_data); | ||||
} | } | ||||
} | } | ||||
if ($this->getAuditRequests()) { | |||||
$requests = id(new PhabricatorRepositoryAuditRequest()) | |||||
->loadAllWhere('id IN (%Ld)', $this->loadAuditIds); | |||||
$requests = mgroup($requests, 'getCommitPHID'); | |||||
foreach ($commits as $commit) { | |||||
$audit_requests = idx($requests, $commit->getPHID(), array()); | |||||
$commit->attachAudits($audit_requests); | |||||
foreach ($audit_requests as $audit_request) { | |||||
$audit_request->attachCommit($commit); | |||||
} | |||||
} | |||||
} | |||||
return $commits; | return $commits; | ||||
} | } | ||||
private function buildWhereClause(AphrontDatabaseConnection $conn_r) { | private function buildWhereClause(AphrontDatabaseConnection $conn_r) { | ||||
$where = array(); | $where = array(); | ||||
if ($this->ids) { | |||||
$where[] = qsprintf( | |||||
$conn_r, | |||||
'commit.id IN (%Ld)', | |||||
$this->ids); | |||||
} | |||||
if ($this->phids) { | |||||
$where[] = qsprintf( | |||||
$conn_r, | |||||
'commit.phid IN (%Ls)', | |||||
$this->phids); | |||||
} | |||||
if ($this->repositoryIDs) { | |||||
$where[] = qsprintf( | |||||
$conn_r, | |||||
'commit.repositoryID IN (%Ld)', | |||||
$this->repositoryIDs); | |||||
} | |||||
if ($this->authorPHIDs) { | |||||
$where[] = qsprintf( | |||||
$conn_r, | |||||
'commit.authorPHID IN (%Ls)', | |||||
$this->authorPHIDs); | |||||
} | |||||
if ($this->identifiers) { | if ($this->identifiers) { | ||||
$min_unqualified = PhabricatorRepository::MINIMUM_UNQUALIFIED_HASH; | $min_unqualified = PhabricatorRepository::MINIMUM_UNQUALIFIED_HASH; | ||||
$min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH; | $min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH; | ||||
$refs = array(); | $refs = array(); | ||||
$bare = array(); | $bare = array(); | ||||
foreach ($this->identifiers as $identifier) { | foreach ($this->identifiers as $identifier) { | ||||
$matches = null; | $matches = null; | ||||
Show All 20 Lines | if ($this->identifiers) { | ||||
} | } | ||||
} | } | ||||
$sql = array(); | $sql = array(); | ||||
foreach ($bare as $identifier) { | foreach ($bare as $identifier) { | ||||
$sql[] = qsprintf( | $sql[] = qsprintf( | ||||
$conn_r, | $conn_r, | ||||
'(commitIdentifier LIKE %> AND LENGTH(commitIdentifier) = 40)', | '(commit.commitIdentifier LIKE %> AND '. | ||||
'LENGTH(commit.commitIdentifier) = 40)', | |||||
$identifier); | $identifier); | ||||
} | } | ||||
if ($refs) { | if ($refs) { | ||||
$callsigns = ipull($refs, 'callsign'); | $callsigns = ipull($refs, 'callsign'); | ||||
$repos = id(new PhabricatorRepositoryQuery()) | $repos = id(new PhabricatorRepositoryQuery()) | ||||
->setViewer($this->getViewer()) | ->setViewer($this->getViewer()) | ||||
->withCallsigns($callsigns) | ->withCallsigns($callsigns) | ||||
->execute(); | ->execute(); | ||||
$repos = mpull($repos, null, 'getCallsign'); | $repos = mpull($repos, null, 'getCallsign'); | ||||
foreach ($refs as $key => $ref) { | foreach ($refs as $key => $ref) { | ||||
$repo = idx($repos, $ref['callsign']); | $repo = idx($repos, $ref['callsign']); | ||||
if (!$repo) { | if (!$repo) { | ||||
continue; | continue; | ||||
} | } | ||||
if ($repo->isSVN()) { | if ($repo->isSVN()) { | ||||
if (!ctype_digit($ref['identifier'])) { | if (!ctype_digit($ref['identifier'])) { | ||||
continue; | continue; | ||||
} | } | ||||
$sql[] = qsprintf( | $sql[] = qsprintf( | ||||
$conn_r, | $conn_r, | ||||
'(repositoryID = %d AND commitIdentifier = %s)', | '(commit.repositoryID = %d AND commit.commitIdentifier = %s)', | ||||
$repo->getID(), | $repo->getID(), | ||||
// NOTE: Because the 'commitIdentifier' column is a string, MySQL | // NOTE: Because the 'commitIdentifier' column is a string, MySQL | ||||
// ignores the index if we hand it an integer. Hand it a string. | // ignores the index if we hand it an integer. Hand it a string. | ||||
// See T3377. | // See T3377. | ||||
(int)$ref['identifier']); | (int)$ref['identifier']); | ||||
} else { | } else { | ||||
if (strlen($ref['identifier']) < $min_qualified) { | if (strlen($ref['identifier']) < $min_qualified) { | ||||
continue; | continue; | ||||
} | } | ||||
$sql[] = qsprintf( | $sql[] = qsprintf( | ||||
$conn_r, | $conn_r, | ||||
'(repositoryID = %d AND commitIdentifier LIKE %>)', | '(commit.repositoryID = %d AND commit.commitIdentifier LIKE %>)', | ||||
$repo->getID(), | $repo->getID(), | ||||
$ref['identifier']); | $ref['identifier']); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
if (!$sql) { | if (!$sql) { | ||||
// If we discarded all possible identifiers (e.g., they all referenced | // If we discarded all possible identifiers (e.g., they all referenced | ||||
// bogus repositories or were all too short), make sure the query finds | // bogus repositories or were all too short), make sure the query finds | ||||
// nothing. | // nothing. | ||||
throw new PhabricatorEmptyQueryException( | throw new PhabricatorEmptyQueryException( | ||||
pht('No commit identifiers.')); | pht('No commit identifiers.')); | ||||
} | } | ||||
$where[] = '('.implode(' OR ', $sql).')'; | $where[] = '('.implode(' OR ', $sql).')'; | ||||
} | } | ||||
if ($this->ids) { | if ($this->auditIDs) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn_r, | ||||
'id IN (%Ld)', | 'audit.id IN (%Ld)', | ||||
$this->ids); | $this->auditIDs); | ||||
} | } | ||||
if ($this->phids) { | if ($this->auditorPHIDs) { | ||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn_r, | ||||
'phid IN (%Ls)', | 'audit.auditorPHID IN (%Ls)', | ||||
$this->phids); | $this->auditorPHIDs); | ||||
} | } | ||||
if ($this->repositoryIDs) { | if ($this->auditAwaitingUser) { | ||||
$awaiting_user_phid = $this->auditAwaitingUser->getPHID(); | |||||
// Exclude package and project audits associated with commits where | |||||
// the user is the author. | |||||
$where[] = qsprintf( | $where[] = qsprintf( | ||||
$conn_r, | $conn_r, | ||||
'repositoryID IN (%Ld)', | '(commit.authorPHID IS NULL OR commit.authorPHID != %s) | ||||
$this->repositoryIDs); | OR (audit.auditorPHID = %s)', | ||||
$awaiting_user_phid, | |||||
$awaiting_user_phid); | |||||
} | |||||
$status = $this->auditStatus; | |||||
if ($status !== null) { | |||||
switch ($status) { | |||||
case self::AUDIT_STATUS_CONCERN: | |||||
$where[] = qsprintf( | |||||
$conn_r, | |||||
'audit.auditStatus = %s', | |||||
PhabricatorAuditStatusConstants::CONCERNED); | |||||
break; | |||||
case self::AUDIT_STATUS_OPEN: | |||||
$where[] = qsprintf( | |||||
$conn_r, | |||||
'audit.auditStatus in (%Ls)', | |||||
PhabricatorAuditStatusConstants::getOpenStatusConstants()); | |||||
if ($this->auditAwaitingUser) { | |||||
$where[] = qsprintf( | |||||
$conn_r, | |||||
'awaiting.auditStatus IS NULL OR awaiting.auditStatus != %s', | |||||
PhabricatorAuditStatusConstants::RESIGNED); | |||||
} | |||||
break; | |||||
case self::AUDIT_STATUS_ANY: | |||||
break; | |||||
default: | |||||
$valid = array( | |||||
self::AUDIT_STATUS_ANY, | |||||
self::AUDIT_STATUS_OPEN, | |||||
self::AUDIT_STATUS_CONCERN, | |||||
); | |||||
throw new Exception( | |||||
"Unknown audit status '{$status}'! Valid statuses are: ". | |||||
implode(', ', $valid)); | |||||
} | |||||
} | } | ||||
$where[] = $this->buildPagingClause($conn_r); | $where[] = $this->buildPagingClause($conn_r); | ||||
return $this->formatWhereClause($where); | return $this->formatWhereClause($where); | ||||
} | } | ||||
public function didFilterResults(array $filtered) { | public function didFilterResults(array $filtered) { | ||||
if ($this->identifierMap) { | if ($this->identifierMap) { | ||||
foreach ($this->identifierMap as $name => $commit) { | foreach ($this->identifierMap as $name => $commit) { | ||||
if (isset($filtered[$commit->getPHID()])) { | if (isset($filtered[$commit->getPHID()])) { | ||||
unset($this->identifierMap[$name]); | unset($this->identifierMap[$name]); | ||||
} | } | ||||
} | } | ||||
} | } | ||||
} | } | ||||
private function buildJoinClause($conn_r) { | |||||
$joins = array(); | |||||
$audit_request = new PhabricatorRepositoryAuditRequest(); | |||||
if ($this->getAuditRequests()) { | |||||
$joins[] = qsprintf( | |||||
$conn_r, | |||||
'JOIN %T audit ON commit.phid = audit.commitPHID', | |||||
$audit_request->getTableName()); | |||||
} | |||||
if ($this->auditAwaitingUser) { | |||||
// Join the request table on the awaiting user's requests, so we can | |||||
// filter out package and project requests which the user has resigned | |||||
// from. | |||||
$joins[] = qsprintf( | |||||
$conn_r, | |||||
'LEFT JOIN %T awaiting ON audit.commitPHID = awaiting.commitPHID AND | |||||
awaiting.auditorPHID = %s', | |||||
$audit_request->getTableName(), | |||||
$this->auditAwaitingUser->getPHID()); | |||||
} | |||||
if ($joins) { | |||||
return implode(' ', $joins); | |||||
} else { | |||||
return ''; | |||||
} | |||||
} | |||||
public function getQueryApplicationClass() { | public function getQueryApplicationClass() { | ||||
return 'PhabricatorApplicationDiffusion'; | return 'PhabricatorApplicationDiffusion'; | ||||
} | } | ||||
} | } |