Path: blob/master/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php
12242 views
<?php12/**3* Resolves references into canonical, stable commit identifiers by examining4* database caches.5*6* This is a counterpart to @{class:DiffusionLowLevelResolveRefsQuery}. This7* query offers fast resolution, but can not resolve everything that the8* low-level query can.9*10* This class can resolve the most common refs (commits, branches, tags) and11* can do so cheaply (by examining the database, without needing to make calls12* to the VCS or the service host).13*/14final class DiffusionCachedResolveRefsQuery15extends DiffusionLowLevelQuery {1617private $refs;18private $types;1920public function withRefs(array $refs) {21$this->refs = $refs;22return $this;23}2425public function withTypes(array $types) {26$this->types = $types;27return $this;28}2930protected function executeQuery() {31if (!$this->refs) {32return array();33}3435switch ($this->getRepository()->getVersionControlSystem()) {36case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:37case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:38$result = $this->resolveGitAndMercurialRefs();39break;40case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:41$result = $this->resolveSubversionRefs();42break;43default:44throw new Exception(pht('Unsupported repository type!'));45}4647if ($this->types !== null) {48$result = $this->filterRefsByType($result, $this->types);49}5051return $result;52}5354/**55* Resolve refs in Git and Mercurial repositories.56*57* We can resolve commit hashes from the commits table, and branch and tag58* names from the refcursor table.59*/60private function resolveGitAndMercurialRefs() {61$repository = $this->getRepository();6263$conn_r = $repository->establishConnection('r');6465$results = array();6667$prefixes = array();68foreach ($this->refs as $ref) {69// We require refs to look like hashes and be at least 4 characters70// long. This is similar to the behavior of git.71if (preg_match('/^[a-f0-9]{4,}$/', $ref)) {72$prefixes[] = qsprintf(73$conn_r,74'(commitIdentifier LIKE %>)',75$ref);76}77}7879if ($prefixes) {80$commits = queryfx_all(81$conn_r,82'SELECT commitIdentifier FROM %T83WHERE repositoryID = %s AND %LO',84id(new PhabricatorRepositoryCommit())->getTableName(),85$repository->getID(),86$prefixes);8788foreach ($commits as $commit) {89$hash = $commit['commitIdentifier'];90foreach ($this->refs as $ref) {91if (!strncmp($hash, $ref, strlen($ref))) {92$results[$ref][] = array(93'type' => 'commit',94'identifier' => $hash,95);96}97}98}99}100101$name_hashes = array();102foreach ($this->refs as $ref) {103$name_hashes[PhabricatorHash::digestForIndex($ref)] = $ref;104}105106$cursors = queryfx_all(107$conn_r,108'SELECT c.refNameHash, c.refType, p.commitIdentifier, p.isClosed109FROM %T c JOIN %T p ON p.cursorID = c.id110WHERE c.repositoryPHID = %s AND c.refNameHash IN (%Ls)',111id(new PhabricatorRepositoryRefCursor())->getTableName(),112id(new PhabricatorRepositoryRefPosition())->getTableName(),113$repository->getPHID(),114array_keys($name_hashes));115116foreach ($cursors as $cursor) {117if (isset($name_hashes[$cursor['refNameHash']])) {118$results[$name_hashes[$cursor['refNameHash']]][] = array(119'type' => $cursor['refType'],120'identifier' => $cursor['commitIdentifier'],121'closed' => (bool)$cursor['isClosed'],122);123124// TODO: In Git, we don't store (and thus don't return) the hash125// of the tag itself. It would be vaguely nice to do this.126}127}128129return $results;130}131132133/**134* Resolve refs in Subversion repositories.135*136* We can resolve all numeric identifiers and the keyword `HEAD`.137*/138private function resolveSubversionRefs() {139$repository = $this->getRepository();140141$max_commit = id(new PhabricatorRepositoryCommit())142->loadOneWhere(143'repositoryID = %d ORDER BY epoch DESC, id DESC LIMIT 1',144$repository->getID());145if (!$max_commit) {146// This repository is empty or hasn't parsed yet, so none of the refs are147// going to resolve.148return array();149}150151$max_commit_id = (int)$max_commit->getCommitIdentifier();152153$results = array();154foreach ($this->refs as $ref) {155if ($ref == 'HEAD') {156// Resolve "HEAD" to mean "the most recent commit".157$results[$ref][] = array(158'type' => 'commit',159'identifier' => $max_commit_id,160);161continue;162}163164if (!preg_match('/^\d+$/', $ref)) {165// This ref is non-numeric, so it doesn't resolve to anything.166continue;167}168169// Resolve other commits if we can deduce their existence.170171// TODO: When we import only part of a repository, we won't necessarily172// have all of the smaller commits. Should we fail to resolve them here173// for repositories with a subpath? It might let us simplify other things174// elsewhere.175if ((int)$ref <= $max_commit_id) {176$results[$ref][] = array(177'type' => 'commit',178'identifier' => (int)$ref,179);180}181}182183return $results;184}185186}187188189