4 * Resolves references into canonical, stable commit identifiers by examining
7 * This is a counterpart to @{class:DiffusionLowLevelResolveRefsQuery}. This
8 * query offers fast resolution, but can not resolve everything that the
11 * This class can resolve the most common refs (commits, branches, tags) and
12 * can do so cheaply (by examining the database, without needing to make calls
13 * to the VCS or the service host).
15 final class DiffusionCachedResolveRefsQuery
16 extends DiffusionLowLevelQuery
{
21 public function withRefs(array $refs) {
26 public function withTypes(array $types) {
27 $this->types
= $types;
31 protected function executeQuery() {
36 switch ($this->getRepository()->getVersionControlSystem()) {
37 case PhabricatorRepositoryType
::REPOSITORY_TYPE_GIT
:
38 case PhabricatorRepositoryType
::REPOSITORY_TYPE_MERCURIAL
:
39 $result = $this->resolveGitAndMercurialRefs();
41 case PhabricatorRepositoryType
::REPOSITORY_TYPE_SVN
:
42 $result = $this->resolveSubversionRefs();
45 throw new Exception(pht('Unsupported repository type!'));
48 if ($this->types
!== null) {
49 $result = $this->filterRefsByType($result, $this->types
);
56 * Resolve refs in Git and Mercurial repositories.
58 * We can resolve commit hashes from the commits table, and branch and tag
59 * names from the refcursor table.
61 private function resolveGitAndMercurialRefs() {
62 $repository = $this->getRepository();
64 $conn_r = $repository->establishConnection('r');
69 foreach ($this->refs
as $ref) {
70 // We require refs to look like hashes and be at least 4 characters
71 // long. This is similar to the behavior of git.
72 if (preg_match('/^[a-f0-9]{4,}$/', $ref)) {
73 $prefixes[] = qsprintf(
75 '(commitIdentifier LIKE %>)',
81 $commits = queryfx_all(
83 'SELECT commitIdentifier FROM %T
84 WHERE repositoryID = %s AND %LO',
85 id(new PhabricatorRepositoryCommit())->getTableName(),
89 foreach ($commits as $commit) {
90 $hash = $commit['commitIdentifier'];
91 foreach ($this->refs
as $ref) {
92 if (!strncmp($hash, $ref, strlen($ref))) {
93 $results[$ref][] = array(
95 'identifier' => $hash,
102 $name_hashes = array();
103 foreach ($this->refs
as $ref) {
104 $name_hashes[PhabricatorHash
::digestForIndex($ref)] = $ref;
107 $cursors = queryfx_all(
109 'SELECT c.refNameHash, c.refType, p.commitIdentifier, p.isClosed
110 FROM %T c JOIN %T p ON p.cursorID = c.id
111 WHERE c.repositoryPHID = %s AND c.refNameHash IN (%Ls)',
112 id(new PhabricatorRepositoryRefCursor())->getTableName(),
113 id(new PhabricatorRepositoryRefPosition())->getTableName(),
114 $repository->getPHID(),
115 array_keys($name_hashes));
117 foreach ($cursors as $cursor) {
118 if (isset($name_hashes[$cursor['refNameHash']])) {
119 $results[$name_hashes[$cursor['refNameHash']]][] = array(
120 'type' => $cursor['refType'],
121 'identifier' => $cursor['commitIdentifier'],
122 'closed' => (bool)$cursor['isClosed'],
125 // TODO: In Git, we don't store (and thus don't return) the hash
126 // of the tag itself. It would be vaguely nice to do this.
135 * Resolve refs in Subversion repositories.
137 * We can resolve all numeric identifiers and the keyword `HEAD`.
139 private function resolveSubversionRefs() {
140 $repository = $this->getRepository();
142 $max_commit = id(new PhabricatorRepositoryCommit())
144 'repositoryID = %d ORDER BY epoch DESC, id DESC LIMIT 1',
145 $repository->getID());
147 // This repository is empty or hasn't parsed yet, so none of the refs are
152 $max_commit_id = (int)$max_commit->getCommitIdentifier();
155 foreach ($this->refs
as $ref) {
156 if ($ref == 'HEAD') {
157 // Resolve "HEAD" to mean "the most recent commit".
158 $results[$ref][] = array(
160 'identifier' => $max_commit_id,
165 if (!preg_match('/^\d+$/', $ref)) {
166 // This ref is non-numeric, so it doesn't resolve to anything.
170 // Resolve other commits if we can deduce their existence.
172 // TODO: When we import only part of a repository, we won't necessarily
173 // have all of the smaller commits. Should we fail to resolve them here
174 // for repositories with a subpath? It might let us simplify other things
176 if ((int)$ref <= $max_commit_id) {
177 $results[$ref][] = array(
179 'identifier' => (int)$ref,