Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/diffusion/query/DiffusionCachedResolveRefsQuery.php
12242 views
1
<?php
2
3
/**
4
* Resolves references into canonical, stable commit identifiers by examining
5
* database caches.
6
*
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.
10
*
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).
14
*/
15
final class DiffusionCachedResolveRefsQuery
16
extends DiffusionLowLevelQuery {
17
18
private $refs;
19
private $types;
20
21
public function withRefs(array $refs) {
22
$this->refs = $refs;
23
return $this;
24
}
25
26
public function withTypes(array $types) {
27
$this->types = $types;
28
return $this;
29
}
30
31
protected function executeQuery() {
32
if (!$this->refs) {
33
return array();
34
}
35
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!'));
46
}
47
48
if ($this->types !== null) {
49
$result = $this->filterRefsByType($result, $this->types);
50
}
51
52
return $result;
53
}
54
55
/**
56
* Resolve refs in Git and Mercurial repositories.
57
*
58
* We can resolve commit hashes from the commits table, and branch and tag
59
* names from the refcursor table.
60
*/
61
private function resolveGitAndMercurialRefs() {
62
$repository = $this->getRepository();
63
64
$conn_r = $repository->establishConnection('r');
65
66
$results = array();
67
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);
77
}
78
}
79
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);
88
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,
96
);
97
}
98
}
99
}
100
}
101
102
$name_hashes = array();
103
foreach ($this->refs as $ref) {
104
$name_hashes[PhabricatorHash::digestForIndex($ref)] = $ref;
105
}
106
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));
116
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'],
123
);
124
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.
127
}
128
}
129
130
return $results;
131
}
132
133
134
/**
135
* Resolve refs in Subversion repositories.
136
*
137
* We can resolve all numeric identifiers and the keyword `HEAD`.
138
*/
139
private function resolveSubversionRefs() {
140
$repository = $this->getRepository();
141
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();
150
}
151
152
$max_commit_id = (int)$max_commit->getCommitIdentifier();
153
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,
161
);
162
continue;
163
}
164
165
if (!preg_match('/^\d+$/', $ref)) {
166
// This ref is non-numeric, so it doesn't resolve to anything.
167
continue;
168
}
169
170
// Resolve other commits if we can deduce their existence.
171
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,
180
);
181
}
182
}
183
184
return $results;
185
}
186
187
}
188
189