Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/diffusion/DiffusionLintSaveRunner.php
12241 views
1
<?php
2
3
final class DiffusionLintSaveRunner extends Phobject {
4
private $arc = 'arc';
5
private $severity = ArcanistLintSeverity::SEVERITY_ADVICE;
6
private $all = false;
7
private $chunkSize = 256;
8
private $needsBlame = false;
9
10
private $svnRoot;
11
private $lintCommit;
12
private $branch;
13
private $conn;
14
private $deletes = array();
15
private $inserts = array();
16
private $blame = array();
17
18
19
public function setArc($path) {
20
$this->arc = $path;
21
return $this;
22
}
23
24
public function setSeverity($string) {
25
$this->severity = $string;
26
return $this;
27
}
28
29
public function setAll($bool) {
30
$this->all = $bool;
31
return $this;
32
}
33
34
public function setChunkSize($number) {
35
$this->chunkSize = $number;
36
return $this;
37
}
38
39
public function setNeedsBlame($boolean) {
40
$this->needsBlame = $boolean;
41
return $this;
42
}
43
44
45
public function run($dir) {
46
$working_copy = ArcanistWorkingCopyIdentity::newFromPath($dir);
47
$configuration_manager = new ArcanistConfigurationManager();
48
$configuration_manager->setWorkingCopyIdentity($working_copy);
49
$api = ArcanistRepositoryAPI::newAPIFromConfigurationManager(
50
$configuration_manager);
51
52
$this->svnRoot = id(new PhutilURI($api->getSourceControlPath()))->getPath();
53
if ($api instanceof ArcanistGitAPI) {
54
$svn_fetch = $api->getGitConfig('svn-remote.svn.fetch');
55
list($this->svnRoot) = explode(':', $svn_fetch);
56
if ($this->svnRoot != '') {
57
$this->svnRoot = '/'.$this->svnRoot;
58
}
59
}
60
61
$callsign = $configuration_manager->getConfigFromAnySource(
62
'repository.callsign');
63
$uuid = $api->getRepositoryUUID();
64
$remote_uri = $api->getRemoteURI();
65
66
$repository_query = id(new PhabricatorRepositoryQuery())
67
->setViewer(PhabricatorUser::getOmnipotentUser());
68
69
if ($callsign) {
70
$repository_query->withCallsigns(array($callsign));
71
} else if ($uuid) {
72
$repository_query->withUUIDs(array($uuid));
73
} else if ($remote_uri) {
74
$repository_query->withURIs(array($remote_uri));
75
}
76
77
$repository = $repository_query->executeOne();
78
$branch_name = $api->getBranchName();
79
80
if (!$repository) {
81
throw new Exception(pht('No repository was found.'));
82
}
83
84
$this->branch = PhabricatorRepositoryBranch::loadOrCreateBranch(
85
$repository->getID(),
86
$branch_name);
87
$this->conn = $this->branch->establishConnection('w');
88
89
$this->lintCommit = null;
90
if (!$this->all) {
91
$this->lintCommit = $this->branch->getLintCommit();
92
}
93
94
if ($this->lintCommit) {
95
try {
96
$commit = $this->lintCommit;
97
if ($this->svnRoot) {
98
$commit = $api->getCanonicalRevisionName('@'.$commit);
99
}
100
$all_files = $api->getChangedFiles($commit);
101
} catch (ArcanistCapabilityNotSupportedException $ex) {
102
$this->lintCommit = null;
103
}
104
}
105
106
107
if (!$this->lintCommit) {
108
$where = ($this->svnRoot
109
? qsprintf($this->conn, 'AND path LIKE %>', $this->svnRoot.'/')
110
: '');
111
queryfx(
112
$this->conn,
113
'DELETE FROM %T WHERE branchID = %d %Q',
114
PhabricatorRepository::TABLE_LINTMESSAGE,
115
$this->branch->getID(),
116
$where);
117
$all_files = $api->getAllFiles();
118
}
119
120
$count = 0;
121
122
$files = array();
123
foreach ($all_files as $file => $val) {
124
$count++;
125
if (!$this->lintCommit) {
126
$file = $val;
127
} else {
128
$this->deletes[] = $this->svnRoot.'/'.$file;
129
if ($val & ArcanistRepositoryAPI::FLAG_DELETED) {
130
continue;
131
}
132
}
133
$files[$file] = $file;
134
135
if (count($files) >= $this->chunkSize) {
136
$this->runArcLint($files);
137
$files = array();
138
}
139
}
140
141
$this->runArcLint($files);
142
$this->saveLintMessages();
143
144
$this->lintCommit = $api->getUnderlyingWorkingCopyRevision();
145
$this->branch->setLintCommit($this->lintCommit);
146
$this->branch->save();
147
148
if ($this->blame) {
149
$this->blameAuthors();
150
$this->blame = array();
151
}
152
153
return $count;
154
}
155
156
157
private function runArcLint(array $files) {
158
if (!$files) {
159
return;
160
}
161
162
echo '.';
163
try {
164
$future = new ExecFuture(
165
'%C lint --severity %s --output json %Ls',
166
$this->arc,
167
$this->severity,
168
$files);
169
170
foreach (new LinesOfALargeExecFuture($future) as $json) {
171
$paths = null;
172
try {
173
$paths = phutil_json_decode($json);
174
} catch (PhutilJSONParserException $ex) {
175
fprintf(STDERR, pht('Invalid JSON: %s', $json)."\n");
176
continue;
177
}
178
179
foreach ($paths as $path => $messages) {
180
if (!isset($files[$path])) {
181
continue;
182
}
183
184
foreach ($messages as $message) {
185
$line = idx($message, 'line', 0);
186
187
$this->inserts[] = qsprintf(
188
$this->conn,
189
'(%d, %s, %d, %s, %s, %s, %s)',
190
$this->branch->getID(),
191
$this->svnRoot.'/'.$path,
192
$line,
193
idx($message, 'code', ''),
194
idx($message, 'severity', ''),
195
idx($message, 'name', ''),
196
idx($message, 'description', ''));
197
198
if ($line && $this->needsBlame) {
199
$this->blame[$path][$line] = true;
200
}
201
}
202
203
if (count($this->deletes) >= 1024 || count($this->inserts) >= 256) {
204
$this->saveLintMessages();
205
}
206
}
207
}
208
209
} catch (Exception $ex) {
210
fprintf(STDERR, $ex->getMessage()."\n");
211
}
212
}
213
214
215
private function saveLintMessages() {
216
$this->conn->openTransaction();
217
218
foreach (array_chunk($this->deletes, 1024) as $paths) {
219
queryfx(
220
$this->conn,
221
'DELETE FROM %T WHERE branchID = %d AND path IN (%Ls)',
222
PhabricatorRepository::TABLE_LINTMESSAGE,
223
$this->branch->getID(),
224
$paths);
225
}
226
227
foreach (array_chunk($this->inserts, 256) as $values) {
228
queryfx(
229
$this->conn,
230
'INSERT INTO %T
231
(branchID, path, line, code, severity, name, description)
232
VALUES %LQ',
233
PhabricatorRepository::TABLE_LINTMESSAGE,
234
$values);
235
}
236
237
$this->conn->saveTransaction();
238
239
$this->deletes = array();
240
$this->inserts = array();
241
}
242
243
244
private function blameAuthors() {
245
$repository = id(new PhabricatorRepositoryQuery())
246
->setViewer(PhabricatorUser::getOmnipotentUser())
247
->withIDs(array($this->branch->getRepositoryID()))
248
->executeOne();
249
250
$queries = array();
251
$futures = array();
252
foreach ($this->blame as $path => $lines) {
253
$drequest = DiffusionRequest::newFromDictionary(array(
254
'user' => PhabricatorUser::getOmnipotentUser(),
255
'repository' => $repository,
256
'branch' => $this->branch->getName(),
257
'path' => $path,
258
'commit' => $this->lintCommit,
259
));
260
261
// TODO: Restore blame information / generally fix this workflow.
262
263
$query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest);
264
$queries[$path] = $query;
265
$futures[$path] = new ImmediateFuture($query->executeInline());
266
}
267
268
$authors = array();
269
270
$futures = id(new FutureIterator($futures))
271
->limit(8);
272
foreach ($futures as $path => $future) {
273
$queries[$path]->loadFileContentFromFuture($future);
274
list(, $rev_list, $blame_dict) = $queries[$path]->getBlameData();
275
foreach (array_keys($this->blame[$path]) as $line) {
276
$commit_identifier = $rev_list[$line - 1];
277
$author = idx($blame_dict[$commit_identifier], 'authorPHID');
278
if ($author) {
279
$authors[$author][$path][] = $line;
280
}
281
}
282
}
283
284
if ($authors) {
285
$this->conn->openTransaction();
286
287
foreach ($authors as $author => $paths) {
288
$where = array();
289
foreach ($paths as $path => $lines) {
290
$where[] = qsprintf(
291
$this->conn,
292
'(path = %s AND line IN (%Ld))',
293
$this->svnRoot.'/'.$path,
294
$lines);
295
}
296
queryfx(
297
$this->conn,
298
'UPDATE %T SET authorPHID = %s WHERE %LO',
299
PhabricatorRepository::TABLE_LINTMESSAGE,
300
$author,
301
$where);
302
}
303
304
$this->conn->saveTransaction();
305
}
306
}
307
308
}
309
310