Correct a parameter order swap in "diffusion.historyquery" for Mercurial
[phabricator.git] / src / applications / diffusion / query / DiffusionCachedResolveRefsQuery.php
blob1666056d637537a0d1ab4a9f69b459fb43bef71b
1 <?php
3 /**
4 * Resolves references into canonical, stable commit identifiers by examining
5 * database caches.
7 * This is a counterpart to @{class:DiffusionLowLevelResolveRefsQuery}. This
8 * query offers fast resolution, but can not resolve everything that the
9 * low-level query can.
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 {
18 private $refs;
19 private $types;
21 public function withRefs(array $refs) {
22 $this->refs = $refs;
23 return $this;
26 public function withTypes(array $types) {
27 $this->types = $types;
28 return $this;
31 protected function executeQuery() {
32 if (!$this->refs) {
33 return array();
36 switch ($this->getRepository()->getVersionControlSystem()) {
37 case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
38 case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
39 $result = $this->resolveGitAndMercurialRefs();
40 break;
41 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
42 $result = $this->resolveSubversionRefs();
43 break;
44 default:
45 throw new Exception(pht('Unsupported repository type!'));
48 if ($this->types !== null) {
49 $result = $this->filterRefsByType($result, $this->types);
52 return $result;
55 /**
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');
66 $results = array();
68 $prefixes = array();
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(
74 $conn_r,
75 '(commitIdentifier LIKE %>)',
76 $ref);
80 if ($prefixes) {
81 $commits = queryfx_all(
82 $conn_r,
83 'SELECT commitIdentifier FROM %T
84 WHERE repositoryID = %s AND %LO',
85 id(new PhabricatorRepositoryCommit())->getTableName(),
86 $repository->getID(),
87 $prefixes);
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(
94 'type' => 'commit',
95 'identifier' => $hash,
102 $name_hashes = array();
103 foreach ($this->refs as $ref) {
104 $name_hashes[PhabricatorHash::digestForIndex($ref)] = $ref;
107 $cursors = queryfx_all(
108 $conn_r,
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.
130 return $results;
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())
143 ->loadOneWhere(
144 'repositoryID = %d ORDER BY epoch DESC, id DESC LIMIT 1',
145 $repository->getID());
146 if (!$max_commit) {
147 // This repository is empty or hasn't parsed yet, so none of the refs are
148 // going to resolve.
149 return array();
152 $max_commit_id = (int)$max_commit->getCommitIdentifier();
154 $results = array();
155 foreach ($this->refs as $ref) {
156 if ($ref == 'HEAD') {
157 // Resolve "HEAD" to mean "the most recent commit".
158 $results[$ref][] = array(
159 'type' => 'commit',
160 'identifier' => $max_commit_id,
162 continue;
165 if (!preg_match('/^\d+$/', $ref)) {
166 // This ref is non-numeric, so it doesn't resolve to anything.
167 continue;
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
175 // elsewhere.
176 if ((int)$ref <= $max_commit_id) {
177 $results[$ref][] = array(
178 'type' => 'commit',
179 'identifier' => (int)$ref,
184 return $results;