@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.)
hq.recaptime.dev/wiki/Phorge
phorge
phabricator
1<?php
2
3final 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 $all_files = array();
95 if ($this->lintCommit) {
96 try {
97 $commit = $this->lintCommit;
98 if ($this->svnRoot) {
99 $commit = $api->getCanonicalRevisionName('@'.$commit);
100 }
101 $all_files = $api->getChangedFiles($commit);
102 } catch (ArcanistCapabilityNotSupportedException $ex) {
103 $this->lintCommit = null;
104 }
105 }
106
107
108 if (!$this->lintCommit) {
109 $where = ($this->svnRoot
110 ? qsprintf($this->conn, 'AND path LIKE %>', $this->svnRoot.'/')
111 : '');
112 queryfx(
113 $this->conn,
114 'DELETE FROM %T WHERE branchID = %d %Q',
115 PhabricatorRepository::TABLE_LINTMESSAGE,
116 $this->branch->getID(),
117 $where);
118 $all_files = $api->getAllFiles();
119 }
120
121 $count = 0;
122
123 $files = array();
124 foreach ($all_files as $file => $val) {
125 $count++;
126 if (!$this->lintCommit) {
127 $file = $val;
128 } else {
129 $this->deletes[] = $this->svnRoot.'/'.$file;
130 if ($val & ArcanistRepositoryAPI::FLAG_DELETED) {
131 continue;
132 }
133 }
134 $files[$file] = $file;
135
136 if (count($files) >= $this->chunkSize) {
137 $this->runArcLint($files);
138 $files = array();
139 }
140 }
141
142 $this->runArcLint($files);
143 $this->saveLintMessages();
144
145 $this->lintCommit = $api->getUnderlyingWorkingCopyRevision();
146 $this->branch->setLintCommit($this->lintCommit);
147 $this->branch->save();
148
149 if ($this->blame) {
150 $this->blameAuthors();
151 $this->blame = array();
152 }
153
154 return $count;
155 }
156
157
158 private function runArcLint(array $files) {
159 if (!$files) {
160 return;
161 }
162
163 echo '.';
164 try {
165 $future = new ExecFuture(
166 '%C lint --severity %s --output json %Ls',
167 $this->arc,
168 $this->severity,
169 $files);
170
171 foreach (new LinesOfALargeExecFuture($future) as $json) {
172 $paths = null;
173 try {
174 $paths = phutil_json_decode($json);
175 } catch (PhutilJSONParserException $ex) {
176 fprintf(STDERR, pht('Invalid JSON: %s', $json)."\n");
177 continue;
178 }
179
180 foreach ($paths as $path => $messages) {
181 if (!isset($files[$path])) {
182 continue;
183 }
184
185 foreach ($messages as $message) {
186 $line = idx($message, 'line', 0);
187
188 $this->inserts[] = qsprintf(
189 $this->conn,
190 '(%d, %s, %d, %s, %s, %s, %s)',
191 $this->branch->getID(),
192 $this->svnRoot.'/'.$path,
193 $line,
194 idx($message, 'code', ''),
195 idx($message, 'severity', ''),
196 idx($message, 'name', ''),
197 idx($message, 'description', ''));
198
199 if ($line && $this->needsBlame) {
200 $this->blame[$path][$line] = true;
201 }
202 }
203
204 if (count($this->deletes) >= 1024 || count($this->inserts) >= 256) {
205 $this->saveLintMessages();
206 }
207 }
208 }
209
210 } catch (Exception $ex) {
211 fprintf(STDERR, $ex->getMessage()."\n");
212 }
213 }
214
215
216 private function saveLintMessages() {
217 $this->conn->openTransaction();
218
219 foreach (array_chunk($this->deletes, 1024) as $paths) {
220 queryfx(
221 $this->conn,
222 'DELETE FROM %T WHERE branchID = %d AND path IN (%Ls)',
223 PhabricatorRepository::TABLE_LINTMESSAGE,
224 $this->branch->getID(),
225 $paths);
226 }
227
228 foreach (array_chunk($this->inserts, 256) as $values) {
229 queryfx(
230 $this->conn,
231 'INSERT INTO %T
232 (branchID, path, line, code, severity, name, description)
233 VALUES %LQ',
234 PhabricatorRepository::TABLE_LINTMESSAGE,
235 $values);
236 }
237
238 $this->conn->saveTransaction();
239
240 $this->deletes = array();
241 $this->inserts = array();
242 }
243
244
245 private function blameAuthors() {
246 $repository = id(new PhabricatorRepositoryQuery())
247 ->setViewer(PhabricatorUser::getOmnipotentUser())
248 ->withIDs(array($this->branch->getRepositoryID()))
249 ->executeOne();
250
251 $queries = array();
252 $futures = array();
253 foreach ($this->blame as $path => $lines) {
254 $drequest = DiffusionRequest::newFromDictionary(array(
255 'user' => PhabricatorUser::getOmnipotentUser(),
256 'repository' => $repository,
257 'branch' => $this->branch->getName(),
258 'path' => $path,
259 'commit' => $this->lintCommit,
260 ));
261
262 // TODO: Restore blame information / generally fix this workflow.
263
264 $query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest);
265 $queries[$path] = $query;
266 $futures[$path] = new ImmediateFuture($query->executeInline());
267 }
268
269 $authors = array();
270
271 $futures = id(new FutureIterator($futures))
272 ->limit(8);
273 foreach ($futures as $path => $future) {
274 $queries[$path]->loadFileContentFromFuture($future);
275 list(, $rev_list, $blame_dict) = $queries[$path]->getBlameData();
276 foreach (array_keys($this->blame[$path]) as $line) {
277 $commit_identifier = $rev_list[$line - 1];
278 $author = idx($blame_dict[$commit_identifier], 'authorPHID');
279 if ($author) {
280 $authors[$author][$path][] = $line;
281 }
282 }
283 }
284
285 if ($authors) {
286 $this->conn->openTransaction();
287
288 foreach ($authors as $author => $paths) {
289 $where = array();
290 foreach ($paths as $path => $lines) {
291 $where[] = qsprintf(
292 $this->conn,
293 '(path = %s AND line IN (%Ld))',
294 $this->svnRoot.'/'.$path,
295 $lines);
296 }
297 queryfx(
298 $this->conn,
299 'UPDATE %T SET authorPHID = %s WHERE %LO',
300 PhabricatorRepository::TABLE_LINTMESSAGE,
301 $author,
302 $where);
303 }
304
305 $this->conn->saveTransaction();
306 }
307 }
308
309}