diff --git a/resources/sql/autopatches/20180910.audit.02.string.sql b/resources/sql/autopatches/20180910.audit.02.string.sql new file mode 100644 index 0000000000..4caa4a1724 --- /dev/null +++ b/resources/sql/autopatches/20180910.audit.02.string.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_repository.repository_commit + CHANGE auditStatus auditStatus VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20180910.audit.03.status.php b/resources/sql/autopatches/20180910.audit.03.status.php new file mode 100644 index 0000000000..b7a0bf9f58 --- /dev/null +++ b/resources/sql/autopatches/20180910.audit.03.status.php @@ -0,0 +1,28 @@ +establishConnection('w'); + +$status_map = array( + 0 => 'none', + 1 => 'needs-audit', + 2 => 'concern-raised', + 3 => 'partially-audited', + 4 => 'audited', + 5 => 'needs-verification', +); + +foreach (new LiskMigrationIterator($table) as $commit) { + $status = $commit->getAuditStatus(); + + if (!isset($status_map[$status])) { + continue; + } + + queryfx( + $conn, + 'UPDATE %T SET auditStatus = %s WHERE id = %d', + $table->getTableName(), + $status_map[$status], + $commit->getID()); +} diff --git a/resources/sql/autopatches/20180910.audit.04.xactions.php b/resources/sql/autopatches/20180910.audit.04.xactions.php new file mode 100644 index 0000000000..1ecf9ef320 --- /dev/null +++ b/resources/sql/autopatches/20180910.audit.04.xactions.php @@ -0,0 +1,48 @@ +establishConnection('w'); + +$status_map = array( + 0 => 'none', + 1 => 'needs-audit', + 2 => 'concern-raised', + 3 => 'partially-audited', + 4 => 'audited', + 5 => 'needs-verification', +); + +$state_type = DiffusionCommitStateTransaction::TRANSACTIONTYPE; + +foreach (new LiskMigrationIterator($table) as $xaction) { + if ($xaction->getTransactionType() !== $state_type) { + continue; + } + + $old_value = $xaction->getOldValue(); + $new_value = $xaction->getNewValue(); + + $any_change = false; + + if (isset($status_map[$old_value])) { + $old_value = $status_map[$old_value]; + $any_change = true; + } + + if (isset($status_map[$new_value])) { + $new_value = $status_map[$new_value]; + $any_change = true; + } + + if (!$any_change) { + continue; + } + + queryfx( + $conn, + 'UPDATE %T SET oldValue = %s, newValue = %s WHERE id = %d', + $table->getTableName(), + phutil_json_encode($old_value), + phutil_json_encode($new_value), + $xaction->getID()); +} diff --git a/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php b/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php index eca420ce1b..23964920b7 100644 --- a/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php +++ b/src/applications/audit/constants/PhabricatorAuditCommitStatusConstants.php @@ -1,171 +1,170 @@ $spec) { - if ((int)idx($spec, 'legacy') === (int)$status) { - return self::newForStatus($key); - } + $modern = array(); + foreach ($map as $key => $spec) { + if (isset($spec['legacy'])) { + $modern[$spec['legacy']] = $key; } } - return self::newForStatus($status); + foreach ($values as $key => $value) { + $values[$key] = idx($modern, $value, $value); + } + + return $values; } public static function newForStatus($status) { $result = new self(); $result->key = $status; $map = self::getMap(); if (isset($map[$status])) { $result->spec = $map[$status]; } return $result; } public function getKey() { return $this->key; } public function getIcon() { return idx($this->spec, 'icon'); } public function getColor() { return idx($this->spec, 'color'); } - public function getLegacyKey() { - return idx($this->spec, 'legacy'); - } - public function getName() { return idx($this->spec, 'name', pht('Unknown ("%s")', $this->key)); } public function isNoAudit() { return ($this->key == self::MODERN_NONE); } public function isNeedsAudit() { return ($this->key == self::MODERN_NEEDS_AUDIT); } public function isConcernRaised() { return ($this->key == self::MODERN_CONCERN_RAISED); } public function isNeedsVerification() { return ($this->key == self::MODERN_NEEDS_VERIFICATION); } public function isPartiallyAudited() { return ($this->key == self::MODERN_PARTIALLY_AUDITED); } public function isAudited() { return ($this->key == self::MODERN_AUDITED); } public function getIsClosed() { return idx($this->spec, 'closed'); } public static function getOpenStatusConstants() { $constants = array(); - foreach (self::getMap() as $map) { + foreach (self::getMap() as $key => $map) { if (!$map['closed']) { - $constants[] = $map['legacy']; + $constants[] = $key; } } return $constants; } public static function newOptions() { $map = self::getMap(); return ipull($map, 'name'); } public static function newDeprecatedOptions() { $map = self::getMap(); $results = array(); foreach ($map as $key => $spec) { if (isset($spec['legacy'])) { $results[$spec['legacy']] = $key; } } return $results; } private static function getMap() { return array( self::MODERN_NONE => array( 'name' => pht('No Audits'), 'legacy' => self::NONE, 'icon' => 'fa-check', 'color' => 'bluegrey', 'closed' => true, ), self::MODERN_NEEDS_AUDIT => array( 'name' => pht('Audit Required'), 'legacy' => self::NEEDS_AUDIT, 'icon' => 'fa-exclamation-circle', 'color' => 'orange', 'closed' => false, ), self::MODERN_CONCERN_RAISED => array( 'name' => pht('Concern Raised'), 'legacy' => self::CONCERN_RAISED, 'icon' => 'fa-times-circle', 'color' => 'red', 'closed' => false, ), self::MODERN_PARTIALLY_AUDITED => array( 'name' => pht('Partially Audited'), 'legacy' => self::PARTIALLY_AUDITED, 'icon' => 'fa-check-circle-o', 'color' => 'yellow', 'closed' => false, ), self::MODERN_AUDITED => array( 'name' => pht('Audited'), 'legacy' => self::FULLY_AUDITED, 'icon' => 'fa-check-circle', 'color' => 'green', 'closed' => true, ), self::MODERN_NEEDS_VERIFICATION => array( 'name' => pht('Needs Verification'), 'legacy' => self::NEEDS_VERIFICATION, 'icon' => 'fa-refresh', 'color' => 'indigo', 'closed' => false, ), ); } } diff --git a/src/applications/diffusion/query/DiffusionCommitQuery.php b/src/applications/diffusion/query/DiffusionCommitQuery.php index 9a7e21b0af..0a50e834c3 100644 --- a/src/applications/diffusion/query/DiffusionCommitQuery.php +++ b/src/applications/diffusion/query/DiffusionCommitQuery.php @@ -1,856 +1,852 @@ 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", * 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) { // Some workflows (like blame lookups) can pass in large numbers of // duplicate identifiers. We only care about unique identifiers, so // get rid of duplicates immediately. $identifiers = array_fuse($identifiers); $this->identifiers = $identifiers; 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; } /** * Look up commits in a specific repository. Prefer * @{method:withRepositoryIDs}; the underlying table is keyed by ID such * that this method requires a separate initial query to map PHID to ID. */ public function withRepositoryPHIDs(array $phids) { $this->repositoryPHIDs = $phids; return $this; } /** * 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; } public function withRepositoryIDs(array $repository_ids) { $this->repositoryIDs = array_unique($repository_ids); return $this; } public function needCommitData($need) { $this->needCommitData = $need; return $this; } public function needDrafts($need) { $this->needDrafts = $need; return $this; } public function needIdentities($need) { $this->needIdentities = $need; return $this; } public function needAuditRequests($need) { $this->needAuditRequests = $need; return $this; } public function withAuditIDs(array $ids) { $this->auditIDs = $ids; return $this; } public function withAuditorPHIDs(array $auditor_phids) { $this->auditorPHIDs = $auditor_phids; return $this; } public function withResponsiblePHIDs(array $responsible_phids) { $this->responsiblePHIDs = $responsible_phids; return $this; } public function withPackagePHIDs(array $package_phids) { $this->packagePHIDs = $package_phids; return $this; } public function withUnreachable($unreachable) { $this->unreachable = $unreachable; return $this; } public function withStatuses(array $statuses) { $this->statuses = $statuses; return $this; } public function withEpochRange($min, $max) { $this->epochMin = $min; $this->epochMax = $max; return $this; } public function withImporting($importing) { $this->importing = $importing; return $this; } public function withAncestorsOf(array $refs) { $this->ancestorsOf = $refs; return $this; } public function getIdentifierMap() { if ($this->identifierMap === null) { throw new Exception( pht( 'You must %s the query before accessing the identifier map.', 'execute()')); } return $this->identifierMap; } protected function getPrimaryTableAlias() { return 'commit'; } protected function willExecute() { if ($this->identifierMap === null) { $this->identifierMap = array(); } } public function newResultObject() { return new PhabricatorRepositoryCommit(); } protected function loadPage() { $table = $this->newResultObject(); $conn = $table->establishConnection('r'); $subqueries = array(); if ($this->responsiblePHIDs) { $base_authors = $this->authorPHIDs; $base_auditors = $this->auditorPHIDs; $responsible_phids = $this->responsiblePHIDs; if ($base_authors) { $all_authors = array_merge($base_authors, $responsible_phids); } else { $all_authors = $responsible_phids; } if ($base_auditors) { $all_auditors = array_merge($base_auditors, $responsible_phids); } else { $all_auditors = $responsible_phids; } $this->authorPHIDs = $all_authors; $this->auditorPHIDs = $base_auditors; $subqueries[] = $this->buildStandardPageQuery( $conn, $table->getTableName()); $this->authorPHIDs = $base_authors; $this->auditorPHIDs = $all_auditors; $subqueries[] = $this->buildStandardPageQuery( $conn, $table->getTableName()); } else { $subqueries[] = $this->buildStandardPageQuery( $conn, $table->getTableName()); } if (count($subqueries) > 1) { foreach ($subqueries as $key => $subquery) { $subqueries[$key] = '('.$subquery.')'; } $query = qsprintf( $conn, '%Q %Q %Q', implode(' UNION DISTINCT ', $subqueries), $this->buildOrderClause($conn, true), $this->buildLimitClause($conn)); } else { $query = head($subqueries); } $rows = queryfx_all($conn, '%Q', $query); $rows = $this->didLoadRawRows($rows); return $table->loadAllFromArray($rows); } protected function willFilterPage(array $commits) { $repository_ids = mpull($commits, 'getRepositoryID', 'getRepositoryID'); $repos = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) ->withIDs($repository_ids) ->execute(); $min_qualified = PhabricatorRepository::MINIMUM_QUALIFIED_HASH; $result = array(); foreach ($commits as $key => $commit) { $repo = idx($repos, $commit->getRepositoryID()); if ($repo) { $commit->attachRepository($repo); } else { $this->didRejectResult($commit); unset($commits[$key]); continue; } // Build the identifierMap if ($this->identifiers !== null) { $ids = $this->identifiers; $prefixes = array( 'r'.$commit->getRepository()->getCallsign(), 'r'.$commit->getRepository()->getCallsign().':', 'R'.$commit->getRepository()->getID().':', '', // No prefix is valid too and will only match the commitIdentifier ); $suffix = $commit->getCommitIdentifier(); if ($commit->getRepository()->isSVN()) { foreach ($prefixes as $prefix) { if (isset($ids[$prefix.$suffix])) { $result[$prefix.$suffix][] = $commit; } } } else { // This awkward construction 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); foreach ($prefixes as $prefix) { if (isset($ids[$prefix.$part])) { $result[$prefix.$part][] = $commit; } } } } } } if ($result) { 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; } return $commits; } protected function didFilterPage(array $commits) { $viewer = $this->getViewer(); if ($this->mustFilterRefs) { // If this flag is set, the query has an "Ancestors Of" constraint and // at least one of the constraining refs had too many ancestors for us // to apply the constraint with a big "commitIdentifier IN (%Ls)" clause. // We're going to filter each page and hope we get a full result set // before the query overheats. $ancestor_list = mpull($commits, 'getCommitIdentifier'); $ancestor_list = array_values($ancestor_list); foreach ($this->ancestorsOf as $ref) { try { $ancestor_list = DiffusionQuery::callConduitWithDiffusionRequest( $viewer, DiffusionRequest::newFromDictionary( array( 'repository' => $this->refRepository, 'user' => $viewer, )), 'diffusion.internal.ancestors', array( 'ref' => $ref, 'commits' => $ancestor_list, )); } catch (ConduitClientException $ex) { throw new PhabricatorSearchConstraintException( $ex->getMessage()); } if (!$ancestor_list) { break; } } $ancestor_list = array_fuse($ancestor_list); foreach ($commits as $key => $commit) { $identifier = $commit->getCommitIdentifier(); if (!isset($ancestor_list[$identifier])) { $this->didRejectResult($commit); unset($commits[$key]); } } if (!$commits) { return $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()); if (!$commit_data) { $commit_data = new PhabricatorRepositoryCommitData(); } $commit->attachCommitData($commit_data); } } if ($this->needAuditRequests) { $requests = id(new PhabricatorRepositoryAuditRequest())->loadAllWhere( 'commitPHID IN (%Ls)', mpull($commits, 'getPHID')); $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); } } } if ($this->needIdentities) { $identity_phids = array_merge( mpull($commits, 'getAuthorIdentityPHID'), mpull($commits, 'getCommitterIdentityPHID')); $data = id(new PhabricatorRepositoryIdentityQuery()) ->withPHIDs($identity_phids) ->setViewer($this->getViewer()) ->execute(); $data = mpull($data, null, 'getPHID'); foreach ($commits as $commit) { $author_identity = idx($data, $commit->getAuthorIdentityPHID()); $committer_identity = idx($data, $commit->getCommitterIdentityPHID()); $commit->attachIdentities($author_identity, $committer_identity); } } if ($this->needDrafts) { PhabricatorDraftEngine::attachDrafts( $viewer, $commits); } return $commits; } protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { $where = parent::buildWhereClauseParts($conn); if ($this->repositoryPHIDs !== null) { $map_repositories = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) ->withPHIDs($this->repositoryPHIDs) ->execute(); if (!$map_repositories) { throw new PhabricatorEmptyQueryException(); } $repository_ids = mpull($map_repositories, 'getID'); if ($this->repositoryIDs !== null) { $repository_ids = array_merge($repository_ids, $this->repositoryIDs); } $this->withRepositoryIDs($repository_ids); } if ($this->ancestorsOf !== null) { if (count($this->repositoryIDs) !== 1) { throw new PhabricatorSearchConstraintException( pht( 'To search for commits which are ancestors of particular refs, '. 'you must constrain the search to exactly one repository.')); } $repository_id = head($this->repositoryIDs); $history_limit = $this->getRawResultLimit() * 32; $viewer = $this->getViewer(); $repository = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) ->withIDs(array($repository_id)) ->executeOne(); if (!$repository) { throw new PhabricatorEmptyQueryException(); } if ($repository->isSVN()) { throw new PhabricatorSearchConstraintException( pht( 'Subversion does not support searching for ancestors of '. 'a particular ref. This operation is not meaningful in '. 'Subversion.')); } if ($repository->isHg()) { throw new PhabricatorSearchConstraintException( pht( 'Mercurial does not currently support searching for ancestors of '. 'a particular ref.')); } $can_constrain = true; $history_identifiers = array(); foreach ($this->ancestorsOf as $key => $ref) { try { $raw_history = DiffusionQuery::callConduitWithDiffusionRequest( $viewer, DiffusionRequest::newFromDictionary( array( 'repository' => $repository, 'user' => $viewer, )), 'diffusion.historyquery', array( 'commit' => $ref, 'limit' => $history_limit, )); } catch (ConduitClientException $ex) { throw new PhabricatorSearchConstraintException( $ex->getMessage()); } $ref_identifiers = array(); foreach ($raw_history['pathChanges'] as $change) { $ref_identifiers[] = $change['commitIdentifier']; } // If this ref had fewer total commits than the limit, we're safe to // apply the constraint as a large `IN (...)` query for a list of // commit identifiers. This is efficient. if ($history_limit) { if (count($ref_identifiers) >= $history_limit) { $can_constrain = false; break; } } $history_identifiers += array_fuse($ref_identifiers); } // If all refs had a small number of ancestors, we can just put the // constraint into the query here and we're done. Otherwise, we need // to filter each page after it comes out of the MySQL layer. if ($can_constrain) { $where[] = qsprintf( $conn, 'commit.commitIdentifier IN (%Ls)', $history_identifiers); } else { $this->mustFilterRefs = true; $this->refRepository = $repository; } } if ($this->ids !== null) { $where[] = qsprintf( $conn, 'commit.id IN (%Ld)', $this->ids); } if ($this->phids !== null) { $where[] = qsprintf( $conn, 'commit.phid IN (%Ls)', $this->phids); } if ($this->repositoryIDs !== null) { $where[] = qsprintf( $conn, 'commit.repositoryID IN (%Ld)', $this->repositoryIDs); } if ($this->authorPHIDs !== null) { $where[] = qsprintf( $conn, 'commit.authorPHID IN (%Ls)', $this->authorPHIDs); } if ($this->epochMin !== null) { $where[] = qsprintf( $conn, 'commit.epoch >= %d', $this->epochMin); } if ($this->epochMax !== null) { $where[] = qsprintf( $conn, 'commit.epoch <= %d', $this->epochMax); } if ($this->importing !== null) { if ($this->importing) { $where[] = qsprintf( $conn, '(commit.importStatus & %d) != %d', PhabricatorRepositoryCommit::IMPORTED_ALL, PhabricatorRepositoryCommit::IMPORTED_ALL); } else { $where[] = qsprintf( $conn, '(commit.importStatus & %d) = %d', PhabricatorRepositoryCommit::IMPORTED_ALL, PhabricatorRepositoryCommit::IMPORTED_ALL); } } if ($this->identifiers !== null) { $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('/^(?:[rR]([A-Z]+:?|[0-9]+:))?(.*)$/', $identifier, $matches); $repo = nonempty(rtrim($matches[1], ':'), null); $commit_identifier = nonempty($matches[2], null); if ($repo === null) { if ($this->defaultRepository) { $repo = $this->defaultRepository->getPHID(); } } if ($repo === null) { if (strlen($commit_identifier) < $min_unqualified) { continue; } $bare[] = $commit_identifier; } else { $refs[] = array( 'repository' => $repo, 'identifier' => $commit_identifier, ); } } $sql = array(); foreach ($bare as $identifier) { $sql[] = qsprintf( $conn, '(commit.commitIdentifier LIKE %> AND '. 'LENGTH(commit.commitIdentifier) = 40)', $identifier); } if ($refs) { $repositories = ipull($refs, 'repository'); $repos = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getViewer()) ->withIdentifiers($repositories); $repos->execute(); $repos = $repos->getIdentifierMap(); foreach ($refs as $key => $ref) { $repo = idx($repos, $ref['repository']); if (!$repo) { continue; } if ($repo->isSVN()) { if (!ctype_digit((string)$ref['identifier'])) { continue; } $sql[] = qsprintf( $conn, '(commit.repositoryID = %d AND commit.commitIdentifier = %s)', $repo->getID(), // 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']); } else { if (strlen($ref['identifier']) < $min_qualified) { continue; } $identifier = $ref['identifier']; if (strlen($identifier) == 40) { // MySQL seems to do slightly better with this version if the // clause, so issue it if we have a full commit hash. $sql[] = qsprintf( $conn, '(commit.repositoryID = %d AND commit.commitIdentifier = %s)', $repo->getID(), $identifier); } else { $sql[] = qsprintf( $conn, '(commit.repositoryID = %d AND commit.commitIdentifier LIKE %>)', $repo->getID(), $identifier); } } } } if (!$sql) { // If we discarded all possible identifiers (e.g., they all referenced // bogus repositories or were all too short), make sure the query finds // nothing. throw new PhabricatorEmptyQueryException( pht('No commit identifiers.')); } $where[] = '('.implode(' OR ', $sql).')'; } if ($this->auditIDs !== null) { $where[] = qsprintf( $conn, 'auditor.id IN (%Ld)', $this->auditIDs); } if ($this->auditorPHIDs !== null) { $where[] = qsprintf( $conn, 'auditor.auditorPHID IN (%Ls)', $this->auditorPHIDs); } if ($this->statuses !== null) { - $statuses = array(); - foreach ($this->statuses as $status) { - $object = PhabricatorAuditCommitStatusConstants::newForLegacyStatus( - $status); - $statuses[] = $object->getLegacyKey(); - } + $statuses = PhabricatorAuditCommitStatusConstants::newModernKeys( + $this->statuses); $where[] = qsprintf( $conn, - 'commit.auditStatus IN (%Ld)', + 'commit.auditStatus IN (%Ls)', $statuses); } if ($this->packagePHIDs !== null) { $where[] = qsprintf( $conn, 'package.dst IN (%Ls)', $this->packagePHIDs); } if ($this->unreachable !== null) { if ($this->unreachable) { $where[] = qsprintf( $conn, '(commit.importStatus & %d) = %d', PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE, PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE); } else { $where[] = qsprintf( $conn, '(commit.importStatus & %d) = 0', PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE); } } return $where; } protected function didFilterResults(array $filtered) { if ($this->identifierMap) { foreach ($this->identifierMap as $name => $commit) { if (isset($filtered[$commit->getPHID()])) { unset($this->identifierMap[$name]); } } } } private function shouldJoinAuditor() { return ($this->auditIDs || $this->auditorPHIDs); } private function shouldJoinOwners() { return (bool)$this->packagePHIDs; } protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { $join = parent::buildJoinClauseParts($conn); $audit_request = new PhabricatorRepositoryAuditRequest(); if ($this->shouldJoinAuditor()) { $join[] = qsprintf( $conn, 'JOIN %T auditor ON commit.phid = auditor.commitPHID', $audit_request->getTableName()); } if ($this->shouldJoinOwners()) { $join[] = qsprintf( $conn, 'JOIN %T package ON commit.phid = package.src AND package.type = %s', PhabricatorEdgeConfig::TABLE_NAME_EDGE, DiffusionCommitHasPackageEdgeType::EDGECONST); } return $join; } protected function shouldGroupQueryResultRows() { if ($this->shouldJoinAuditor()) { return true; } if ($this->shouldJoinOwners()) { return true; } return parent::shouldGroupQueryResultRows(); } public function getQueryApplicationClass() { return 'PhabricatorDiffusionApplication'; } public function getOrderableColumns() { return parent::getOrderableColumns() + array( 'epoch' => array( 'table' => $this->getPrimaryTableAlias(), 'column' => 'epoch', 'type' => 'int', 'reverse' => false, ), ); } protected function getPagingValueMap($cursor, array $keys) { $commit = $this->loadCursorObject($cursor); return array( 'id' => $commit->getID(), 'epoch' => $commit->getEpoch(), ); } public function getBuiltinOrders() { $parent = parent::getBuiltinOrders(); // Rename the default ID-based orders. $parent['importnew'] = array( 'name' => pht('Import Date (Newest First)'), ) + $parent['newest']; $parent['importold'] = array( 'name' => pht('Import Date (Oldest First)'), ) + $parent['oldest']; return array( 'newest' => array( 'vector' => array('epoch', 'id'), 'name' => pht('Commit Date (Newest First)'), ), 'oldest' => array( 'vector' => array('-epoch', '-id'), 'name' => pht('Commit Date (Oldest First)'), ), ) + $parent; } } diff --git a/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php index 07194a2af3..21d688a481 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitConcernTransaction.php @@ -1,80 +1,80 @@ setAuditStatus( - PhabricatorAuditCommitStatusConstants::CONCERN_RAISED); + PhabricatorAuditCommitStatusConstants::MODERN_CONCERN_RAISED); } public function applyExternalEffects($object, $value) { $status = PhabricatorAuditStatusConstants::CONCERNED; $actor = $this->getActor(); $this->applyAuditorEffect($object, $actor, $value, $status); } protected function validateAction($object, PhabricatorUser $viewer) { if ($this->isViewerCommitAuthor($object, $viewer)) { throw new Exception( pht( 'You can not raise a concern with this commit because you are '. 'the commit author. You can only raise concerns with commits '. 'you did not author.')); } // Even if you've already raised a concern, you can raise again as long // as the author requested you verify. if ($this->isViewerFullyRejected($object, $viewer)) { if (!$object->isAuditStatusNeedsVerification()) { throw new Exception( pht( 'You can not raise a concern with this commit because you have '. 'already raised a concern with it.')); } } } public function getTitle() { return pht( '%s raised a concern with this commit.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s raised a concern with %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/diffusion/xaction/DiffusionCommitStateTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitStateTransaction.php index bcf6a9e382..ad8b94f548 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitStateTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitStateTransaction.php @@ -1,75 +1,75 @@ getNewValue(); - return PhabricatorAuditCommitStatusConstants::newForLegacyStatus($new); + return PhabricatorAuditCommitStatusConstants::newForStatus($new); } public function getIcon() { return $this->getAuditStatusObject()->getIcon(); } public function getColor() { return $this->getAuditStatusObject()->getColor(); } public function getTitle() { $status = $this->getAuditStatusObject(); switch ($status->getKey()) { case PhabricatorAuditCommitStatusConstants::MODERN_NONE: return pht('This commit no longer requires audit.'); case PhabricatorAuditCommitStatusConstants::MODERN_NEEDS_AUDIT: return pht('This commit now requires audit.'); case PhabricatorAuditCommitStatusConstants::MODERN_CONCERN_RAISED: return pht('This commit now has outstanding concerns.'); case PhabricatorAuditCommitStatusConstants::MODERN_NEEDS_VERIFICATION: return pht('This commit now requires verification by auditors.'); case PhabricatorAuditCommitStatusConstants::MODERN_AUDITED: return pht('All concerns with this commit have now been addressed.'); } return null; } public function getTitleForFeed() { $status = $this->getAuditStatusObject(); switch ($status->getKey()) { case PhabricatorAuditCommitStatusConstants::MODERN_NONE: return pht( '%s no longer requires audit.', $this->renderObject()); case PhabricatorAuditCommitStatusConstants::MODERN_NEEDS_AUDIT: return pht( '%s now requires audit.', $this->renderObject()); case PhabricatorAuditCommitStatusConstants::MODERN_CONCERN_RAISED: return pht( '%s now has outstanding concerns.', $this->renderObject()); case PhabricatorAuditCommitStatusConstants::MODERN_NEEDS_VERIFICATION: return pht( '%s now requires verification by auditors.', $this->renderObject()); case PhabricatorAuditCommitStatusConstants::MODERN_AUDITED: return pht( 'All concerns with %s have now been addressed.', $this->renderObject()); } return null; } } diff --git a/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php b/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php index 3e6abb705f..5464df7fa1 100644 --- a/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php +++ b/src/applications/diffusion/xaction/DiffusionCommitVerifyTransaction.php @@ -1,72 +1,72 @@ setAuditStatus( - PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION); + PhabricatorAuditCommitStatusConstants::MODERN_NEEDS_VERIFICATION); } protected function validateAction($object, PhabricatorUser $viewer) { if (!$this->isViewerCommitAuthor($object, $viewer)) { throw new Exception( pht( 'You can not request verification of this commit because you '. 'are not the author.')); } if (!$object->isAuditStatusConcernRaised()) { throw new Exception( pht( 'You can not request verification of this commit because no '. 'auditors have raised concerns with it.')); } } public function getTitle() { return pht( '%s requested verification of this commit.', $this->renderAuthor()); } public function getTitleForFeed() { return pht( '%s requested verification of %s.', $this->renderAuthor(), $this->renderObject()); } } diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php index 291c895231..7b9f38c388 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php +++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php @@ -1,887 +1,889 @@ repository = $repository; return $this; } public function getRepository($assert_attached = true) { if ($assert_attached) { return $this->assertAttached($this->repository); } return $this->repository; } public function isPartiallyImported($mask) { return (($mask & $this->getImportStatus()) == $mask); } public function isImported() { return $this->isPartiallyImported(self::IMPORTED_ALL); } public function isUnreachable() { return $this->isPartiallyImported(self::IMPORTED_UNREACHABLE); } public function writeImportStatusFlag($flag) { return $this->adjustImportStatusFlag($flag, true); } public function clearImportStatusFlag($flag) { return $this->adjustImportStatusFlag($flag, false); } private function adjustImportStatusFlag($flag, $set) { $conn_w = $this->establishConnection('w'); $table_name = $this->getTableName(); $id = $this->getID(); if ($set) { queryfx( $conn_w, 'UPDATE %T SET importStatus = (importStatus | %d) WHERE id = %d', $table_name, $flag, $id); $this->setImportStatus($this->getImportStatus() | $flag); } else { queryfx( $conn_w, 'UPDATE %T SET importStatus = (importStatus & ~%d) WHERE id = %d', $table_name, $flag, $id); $this->setImportStatus($this->getImportStatus() & ~$flag); } return $this; } protected function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_TIMESTAMPS => false, self::CONFIG_COLUMN_SCHEMA => array( 'commitIdentifier' => 'text40', 'mailKey' => 'bytes20', 'authorPHID' => 'phid?', 'authorIdentityPHID' => 'phid?', 'committerIdentityPHID' => 'phid?', - 'auditStatus' => 'uint32', + 'auditStatus' => 'text32', 'summary' => 'text255', 'importStatus' => 'uint32', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, 'phid' => array( 'columns' => array('phid'), 'unique' => true, ), 'repositoryID' => array( 'columns' => array('repositoryID', 'importStatus'), ), 'authorPHID' => array( 'columns' => array('authorPHID', 'auditStatus', 'epoch'), ), 'repositoryID_2' => array( 'columns' => array('repositoryID', 'epoch'), ), 'key_commit_identity' => array( 'columns' => array('commitIdentifier', 'repositoryID'), 'unique' => true, ), 'key_epoch' => array( 'columns' => array('epoch'), ), 'key_author' => array( 'columns' => array('authorPHID', 'epoch'), ), ), self::CONFIG_NO_MUTATE => array( 'importStatus', ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorRepositoryCommitPHIDType::TYPECONST); } public function loadCommitData() { if (!$this->getID()) { return null; } return id(new PhabricatorRepositoryCommitData())->loadOneWhere( 'commitID = %d', $this->getID()); } public function attachCommitData( PhabricatorRepositoryCommitData $data = null) { $this->commitData = $data; return $this; } public function getCommitData() { return $this->assertAttached($this->commitData); } public function attachAudits(array $audits) { assert_instances_of($audits, 'PhabricatorRepositoryAuditRequest'); $this->audits = $audits; return $this; } public function getAudits() { return $this->assertAttached($this->audits); } public function hasAttachedAudits() { return ($this->audits !== self::ATTACHABLE); } public function attachIdentities( PhabricatorRepositoryIdentity $author = null, PhabricatorRepositoryIdentity $committer = null) { $this->authorIdentity = $author; $this->committerIdentity = $committer; return $this; } public function getAuthorIdentity() { return $this->assertAttached($this->authorIdentity); } public function getCommitterIdentity() { return $this->assertAttached($this->committerIdentity); } public function loadAndAttachAuditAuthority( PhabricatorUser $viewer, $actor_phid = null) { if ($actor_phid === null) { $actor_phid = $viewer->getPHID(); } // TODO: This method is a little weird and sketchy, but worlds better than // what came before it. Eventually, this should probably live in a Query // class. // Figure out which requests the actor has authority over: these are user // requests where they are the auditor, and packages and projects they are // a member of. if (!$actor_phid) { $attach_key = $viewer->getCacheFragment(); $phids = array(); } else { $attach_key = $actor_phid; // At least currently, when modifying your own commits, you act only on // behalf of yourself, not your packages/projects -- the idea being that // you can't accept your own commits. This may change or depend on // config. $actor_is_author = ($actor_phid == $this->getAuthorPHID()); if ($actor_is_author) { $phids = array($actor_phid); } else { $phids = array(); $phids[$actor_phid] = true; $owned_packages = id(new PhabricatorOwnersPackageQuery()) ->setViewer($viewer) ->withAuthorityPHIDs(array($actor_phid)) ->execute(); foreach ($owned_packages as $package) { $phids[$package->getPHID()] = true; } $projects = id(new PhabricatorProjectQuery()) ->setViewer($viewer) ->withMemberPHIDs(array($actor_phid)) ->execute(); foreach ($projects as $project) { $phids[$project->getPHID()] = true; } $phids = array_keys($phids); } } $this->auditAuthorityPHIDs[$attach_key] = array_fuse($phids); return $this; } public function hasAuditAuthority( PhabricatorUser $viewer, PhabricatorRepositoryAuditRequest $audit, $actor_phid = null) { if ($actor_phid === null) { $actor_phid = $viewer->getPHID(); } if (!$actor_phid) { $attach_key = $viewer->getCacheFragment(); } else { $attach_key = $actor_phid; } $map = $this->assertAttachedKey($this->auditAuthorityPHIDs, $attach_key); if (!$actor_phid) { return false; } return isset($map[$audit->getAuditorPHID()]); } public function writeOwnersEdges(array $package_phids) { $src_phid = $this->getPHID(); $edge_type = DiffusionCommitHasPackageEdgeType::EDGECONST; $editor = new PhabricatorEdgeEditor(); $dst_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( $src_phid, $edge_type); foreach ($dst_phids as $dst_phid) { $editor->removeEdge($src_phid, $edge_type, $dst_phid); } foreach ($package_phids as $package_phid) { $editor->addEdge($src_phid, $edge_type, $package_phid); } $editor->save(); return $this; } public function getAuditorPHIDsForEdit() { $audits = $this->getAudits(); return mpull($audits, 'getAuditorPHID'); } public function save() { if (!$this->mailKey) { $this->mailKey = Filesystem::readRandomCharacters(20); } return parent::save(); } public function delete() { $data = $this->loadCommitData(); $audits = id(new PhabricatorRepositoryAuditRequest()) ->loadAllWhere('commitPHID = %s', $this->getPHID()); $this->openTransaction(); if ($data) { $data->delete(); } foreach ($audits as $audit) { $audit->delete(); } $result = parent::delete(); $this->saveTransaction(); return $result; } public function getDateCreated() { // This is primarily to make analysis of commits with the Fact engine work. return $this->getEpoch(); } public function getURI() { return '/'.$this->getMonogram(); } /** * Synchronize a commit's overall audit status with the individual audit * triggers. */ public function updateAuditStatus(array $requests) { assert_instances_of($requests, 'PhabricatorRepositoryAuditRequest'); $any_concern = false; $any_accept = false; $any_need = false; foreach ($requests as $request) { switch ($request->getAuditStatus()) { case PhabricatorAuditStatusConstants::AUDIT_REQUIRED: case PhabricatorAuditStatusConstants::AUDIT_REQUESTED: $any_need = true; break; case PhabricatorAuditStatusConstants::ACCEPTED: $any_accept = true; break; case PhabricatorAuditStatusConstants::CONCERNED: $any_concern = true; break; } } if ($any_concern) { if ($this->isAuditStatusNeedsVerification()) { // If the change is in "Needs Verification", we keep it there as // long as any auditors still have concerns. - $status = PhabricatorAuditCommitStatusConstants::NEEDS_VERIFICATION; + $status = + PhabricatorAuditCommitStatusConstants::MODERN_NEEDS_VERIFICATION; } else { - $status = PhabricatorAuditCommitStatusConstants::CONCERN_RAISED; + $status = PhabricatorAuditCommitStatusConstants::MODERN_CONCERN_RAISED; } } else if ($any_accept) { if ($any_need) { - $status = PhabricatorAuditCommitStatusConstants::PARTIALLY_AUDITED; + $status = + PhabricatorAuditCommitStatusConstants::MODERN_PARTIALLY_AUDITED; } else { - $status = PhabricatorAuditCommitStatusConstants::FULLY_AUDITED; + $status = PhabricatorAuditCommitStatusConstants::MODERN_AUDITED; } } else if ($any_need) { - $status = PhabricatorAuditCommitStatusConstants::NEEDS_AUDIT; + $status = PhabricatorAuditCommitStatusConstants::MODERN_NEEDS_AUDIT; } else { - $status = PhabricatorAuditCommitStatusConstants::NONE; + $status = PhabricatorAuditCommitStatusConstants::MODERN_NONE; } return $this->setAuditStatus($status); } public function getMonogram() { $repository = $this->getRepository(); $callsign = $repository->getCallsign(); $identifier = $this->getCommitIdentifier(); if ($callsign !== null) { return "r{$callsign}{$identifier}"; } else { $id = $repository->getID(); return "R{$id}:{$identifier}"; } } public function getDisplayName() { $repository = $this->getRepository(); $identifier = $this->getCommitIdentifier(); return $repository->formatCommitName($identifier); } /** * Return a local display name for use in the context of the containing * repository. * * In Git and Mercurial, this returns only a short hash, like "abcdef012345". * See @{method:getDisplayName} for a short name that always includes * repository context. * * @return string Short human-readable name for use inside a repository. */ public function getLocalName() { $repository = $this->getRepository(); $identifier = $this->getCommitIdentifier(); return $repository->formatCommitName($identifier, $local = true); } /** * Make a strong effort to find a way to render this commit's committer. * This currently attempts to use @{PhabricatorRepositoryIdentity}, and * falls back to examining the commit detail information. After we force * the migration to using identities, update this method to remove the * fallback. See T12164 for details. */ public function renderAnyCommitter(PhabricatorUser $viewer, $handles) { $committer = $this->renderCommitter($viewer, $handles); if ($committer) { return $committer; } return $this->renderAuthor($viewer, $handles); } public function renderCommitter(PhabricatorUser $viewer, $handles) { $committer_phid = $this->getCommitterDisplayPHID(); if ($committer_phid) { return $handles[$committer_phid]->renderLink(); } $data = $this->getCommitData(); $committer_name = $data->getCommitDetail('committer'); if (strlen($committer_name)) { return DiffusionView::renderName($committer_name); } return null; } public function renderAuthor(PhabricatorUser $viewer, $handles) { $author_phid = $this->getAuthorDisplayPHID(); if ($author_phid) { return $handles[$author_phid]->renderLink(); } $data = $this->getCommitData(); $author_name = $data->getAuthorName(); if (strlen($author_name)) { return DiffusionView::renderName($author_name); } return null; } public function loadIdentities(PhabricatorUser $viewer) { if ($this->authorIdentity !== self::ATTACHABLE) { return $this; } $commit = id(new DiffusionCommitQuery()) ->setViewer($viewer) ->withIDs(array($this->getID())) ->needIdentities(true) ->executeOne(); $author_identity = $commit->getAuthorIdentity(); $committer_identity = $commit->getCommitterIdentity(); return $this->attachIdentities($author_identity, $committer_identity); } public function hasCommitterIdentity() { return ($this->getCommitterIdentity() !== null); } public function hasAuthorIdentity() { return ($this->getAuthorIdentity() !== null); } public function getCommitterDisplayPHID() { if ($this->hasCommitterIdentity()) { return $this->getCommitterIdentity()->getIdentityDisplayPHID(); } $data = $this->getCommitData(); return $data->getCommitDetail('committerPHID'); } public function getAuthorDisplayPHID() { if ($this->hasAuthorIdentity()) { return $this->getAuthorIdentity()->getIdentityDisplayPHID(); } $data = $this->getCommitData(); return $data->getCommitDetail('authorPHID'); } public function getAuditStatusObject() { $status = $this->getAuditStatus(); - return PhabricatorAuditCommitStatusConstants::newForLegacyStatus($status); + return PhabricatorAuditCommitStatusConstants::newForStatus($status); } public function isAuditStatusNoAudit() { return $this->getAuditStatusObject()->isNoAudit(); } public function isAuditStatusNeedsAudit() { return $this->getAuditStatusObject()->isNeedsAudit(); } public function isAuditStatusConcernRaised() { return $this->getAuditStatusObject()->isConcernRaised(); } public function isAuditStatusNeedsVerification() { return $this->getAuditStatusObject()->isNeedsVerification(); } public function isAuditStatusPartiallyAudited() { return $this->getAuditStatusObject()->isPartiallyAudited(); } public function isAuditStatusAudited() { return $this->getAuditStatusObject()->isAudited(); } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getRepository()->getPolicy($capability); case PhabricatorPolicyCapability::CAN_EDIT: return PhabricatorPolicies::POLICY_USER; } } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { return $this->getRepository()->hasAutomaticCapability($capability, $viewer); } public function describeAutomaticCapability($capability) { return pht( 'Commits inherit the policies of the repository they belong to.'); } /* -( PhabricatorTokenReceiverInterface )---------------------------------- */ public function getUsersToNotifyOfTokenGiven() { return array( $this->getAuthorPHID(), ); } /* -( Stuff for serialization )---------------------------------------------- */ /** * NOTE: this is not a complete serialization; only the 'protected' fields are * involved. This is due to ease of (ab)using the Lisk abstraction to get this * done, as well as complexity of the other fields. */ public function toDictionary() { return array( 'repositoryID' => $this->getRepositoryID(), 'phid' => $this->getPHID(), 'commitIdentifier' => $this->getCommitIdentifier(), 'epoch' => $this->getEpoch(), 'mailKey' => $this->getMailKey(), 'authorPHID' => $this->getAuthorPHID(), 'auditStatus' => $this->getAuditStatus(), 'summary' => $this->getSummary(), 'importStatus' => $this->getImportStatus(), ); } public static function newFromDictionary(array $dict) { return id(new PhabricatorRepositoryCommit()) ->loadFromArray($dict); } /* -( HarbormasterBuildableInterface )------------------------------------- */ public function getHarbormasterBuildableDisplayPHID() { return $this->getHarbormasterBuildablePHID(); } public function getHarbormasterBuildablePHID() { return $this->getPHID(); } public function getHarbormasterContainerPHID() { return $this->getRepository()->getPHID(); } public function getBuildVariables() { $results = array(); $results['buildable.commit'] = $this->getCommitIdentifier(); $repo = $this->getRepository(); $results['repository.callsign'] = $repo->getCallsign(); $results['repository.phid'] = $repo->getPHID(); $results['repository.vcs'] = $repo->getVersionControlSystem(); $results['repository.uri'] = $repo->getPublicCloneURI(); return $results; } public function getAvailableBuildVariables() { return array( 'buildable.commit' => pht('The commit identifier, if applicable.'), 'repository.callsign' => pht('The callsign of the repository in Phabricator.'), 'repository.phid' => pht('The PHID of the repository in Phabricator.'), 'repository.vcs' => pht('The version control system, either "svn", "hg" or "git".'), 'repository.uri' => pht('The URI to clone or checkout the repository from.'), ); } public function newBuildableEngine() { return new DiffusionBuildableEngine(); } /* -( HarbormasterCircleCIBuildableInterface )----------------------------- */ public function getCircleCIGitHubRepositoryURI() { $repository = $this->getRepository(); $commit_phid = $this->getPHID(); $repository_phid = $repository->getPHID(); if ($repository->isHosted()) { throw new Exception( pht( 'This commit ("%s") is associated with a hosted repository '. '("%s"). Repositories must be imported from GitHub to be built '. 'with CircleCI.', $commit_phid, $repository_phid)); } $remote_uri = $repository->getRemoteURI(); $path = HarbormasterCircleCIBuildStepImplementation::getGitHubPath( $remote_uri); if (!$path) { throw new Exception( pht( 'This commit ("%s") is associated with a repository ("%s") that '. 'with a remote URI ("%s") that does not appear to be hosted on '. 'GitHub. Repositories must be hosted on GitHub to be built with '. 'CircleCI.', $commit_phid, $repository_phid, $remote_uri)); } return $remote_uri; } public function getCircleCIBuildIdentifierType() { return 'revision'; } public function getCircleCIBuildIdentifier() { return $this->getCommitIdentifier(); } /* -( HarbormasterBuildkiteBuildableInterface )---------------------------- */ public function getBuildkiteBranch() { $viewer = PhabricatorUser::getOmnipotentUser(); $repository = $this->getRepository(); $branches = DiffusionQuery::callConduitWithDiffusionRequest( $viewer, DiffusionRequest::newFromDictionary( array( 'repository' => $repository, 'user' => $viewer, )), 'diffusion.branchquery', array( 'contains' => $this->getCommitIdentifier(), 'repository' => $repository->getPHID(), )); if (!$branches) { throw new Exception( pht( 'Commit "%s" is not an ancestor of any branch head, so it can not '. 'be built with Buildkite.', $this->getCommitIdentifier())); } $branch = head($branches); return 'refs/heads/'.$branch['shortName']; } public function getBuildkiteCommit() { return $this->getCommitIdentifier(); } /* -( PhabricatorCustomFieldInterface )------------------------------------ */ public function getCustomFieldSpecificationForRole($role) { return PhabricatorEnv::getEnvConfig('diffusion.fields'); } public function getCustomFieldBaseClass() { return 'PhabricatorCommitCustomField'; } public function getCustomFields() { return $this->assertAttached($this->customFields); } public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) { $this->customFields = $fields; return $this; } /* -( PhabricatorSubscribableInterface )----------------------------------- */ public function isAutomaticallySubscribed($phid) { // TODO: This should also list auditors, but handling that is a bit messy // right now because we are not guaranteed to have the data. (It should not // include resigned auditors.) return ($phid == $this->getAuthorPHID()); } /* -( PhabricatorApplicationTransactionInterface )------------------------- */ public function getApplicationTransactionEditor() { return new PhabricatorAuditEditor(); } public function getApplicationTransactionObject() { return $this; } public function getApplicationTransactionTemplate() { return new PhabricatorAuditTransaction(); } public function willRenderTimeline( PhabricatorApplicationTransactionView $timeline, AphrontRequest $request) { $xactions = $timeline->getTransactions(); $path_ids = array(); foreach ($xactions as $xaction) { if ($xaction->hasComment()) { $path_id = $xaction->getComment()->getPathID(); if ($path_id) { $path_ids[] = $path_id; } } } $path_map = array(); if ($path_ids) { $path_map = id(new DiffusionPathQuery()) ->withPathIDs($path_ids) ->execute(); $path_map = ipull($path_map, 'path', 'id'); } return $timeline->setPathMap($path_map); } /* -( PhabricatorFulltextInterface )--------------------------------------- */ public function newFulltextEngine() { return new DiffusionCommitFulltextEngine(); } /* -( PhabricatorFerretInterface )----------------------------------------- */ public function newFerretEngine() { return new DiffusionCommitFerretEngine(); } /* -( PhabricatorConduitResultInterface )---------------------------------- */ public function getFieldSpecificationsForConduit() { return array( id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('identifier') ->setType('string') ->setDescription(pht('The commit identifier.')), ); } public function getFieldValuesForConduit() { // NOTE: This data should be similar to the information returned about // commmits by "differential.diff.search" with the "commits" attachment. return array( 'identifier' => $this->getCommitIdentifier(), ); } public function getConduitSearchAttachments() { return array(); } /* -( PhabricatorDraftInterface )------------------------------------------ */ public function newDraftEngine() { return new DiffusionCommitDraftEngine(); } public function getHasDraft(PhabricatorUser $viewer) { return $this->assertAttachedKey($this->drafts, $viewer->getCacheFragment()); } public function attachHasDraft(PhabricatorUser $viewer, $has_draft) { $this->drafts[$viewer->getCacheFragment()] = $has_draft; return $this; } }