Correct a parameter order swap in "diffusion.historyquery" for Mercurial
[phabricator.git] / src / applications / diffusion / DiffusionLintSaveRunner.php
bloba1a2fe7d3234ff24a69357cc5416b03074aed776
1 <?php
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;
10 private $svnRoot;
11 private $lintCommit;
12 private $branch;
13 private $conn;
14 private $deletes = array();
15 private $inserts = array();
16 private $blame = array();
19 public function setArc($path) {
20 $this->arc = $path;
21 return $this;
24 public function setSeverity($string) {
25 $this->severity = $string;
26 return $this;
29 public function setAll($bool) {
30 $this->all = $bool;
31 return $this;
34 public function setChunkSize($number) {
35 $this->chunkSize = $number;
36 return $this;
39 public function setNeedsBlame($boolean) {
40 $this->needsBlame = $boolean;
41 return $this;
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);
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;
61 $callsign = $configuration_manager->getConfigFromAnySource(
62 'repository.callsign');
63 $uuid = $api->getRepositoryUUID();
64 $remote_uri = $api->getRemoteURI();
66 $repository_query = id(new PhabricatorRepositoryQuery())
67 ->setViewer(PhabricatorUser::getOmnipotentUser());
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));
77 $repository = $repository_query->executeOne();
78 $branch_name = $api->getBranchName();
80 if (!$repository) {
81 throw new Exception(pht('No repository was found.'));
84 $this->branch = PhabricatorRepositoryBranch::loadOrCreateBranch(
85 $repository->getID(),
86 $branch_name);
87 $this->conn = $this->branch->establishConnection('w');
89 $this->lintCommit = null;
90 if (!$this->all) {
91 $this->lintCommit = $this->branch->getLintCommit();
94 if ($this->lintCommit) {
95 try {
96 $commit = $this->lintCommit;
97 if ($this->svnRoot) {
98 $commit = $api->getCanonicalRevisionName('@'.$commit);
100 $all_files = $api->getChangedFiles($commit);
101 } catch (ArcanistCapabilityNotSupportedException $ex) {
102 $this->lintCommit = null;
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();
120 $count = 0;
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;
133 $files[$file] = $file;
135 if (count($files) >= $this->chunkSize) {
136 $this->runArcLint($files);
137 $files = array();
141 $this->runArcLint($files);
142 $this->saveLintMessages();
144 $this->lintCommit = $api->getUnderlyingWorkingCopyRevision();
145 $this->branch->setLintCommit($this->lintCommit);
146 $this->branch->save();
148 if ($this->blame) {
149 $this->blameAuthors();
150 $this->blame = array();
153 return $count;
157 private function runArcLint(array $files) {
158 if (!$files) {
159 return;
162 echo '.';
163 try {
164 $future = new ExecFuture(
165 '%C lint --severity %s --output json %Ls',
166 $this->arc,
167 $this->severity,
168 $files);
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;
179 foreach ($paths as $path => $messages) {
180 if (!isset($files[$path])) {
181 continue;
184 foreach ($messages as $message) {
185 $line = idx($message, 'line', 0);
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', ''));
198 if ($line && $this->needsBlame) {
199 $this->blame[$path][$line] = true;
203 if (count($this->deletes) >= 1024 || count($this->inserts) >= 256) {
204 $this->saveLintMessages();
209 } catch (Exception $ex) {
210 fprintf(STDERR, $ex->getMessage()."\n");
215 private function saveLintMessages() {
216 $this->conn->openTransaction();
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);
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);
237 $this->conn->saveTransaction();
239 $this->deletes = array();
240 $this->inserts = array();
244 private function blameAuthors() {
245 $repository = id(new PhabricatorRepositoryQuery())
246 ->setViewer(PhabricatorUser::getOmnipotentUser())
247 ->withIDs(array($this->branch->getRepositoryID()))
248 ->executeOne();
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,
261 // TODO: Restore blame information / generally fix this workflow.
263 $query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest);
264 $queries[$path] = $query;
265 $futures[$path] = new ImmediateFuture($query->executeInline());
268 $authors = array();
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;
284 if ($authors) {
285 $this->conn->openTransaction();
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);
296 queryfx(
297 $this->conn,
298 'UPDATE %T SET authorPHID = %s WHERE %LO',
299 PhabricatorRepository::TABLE_LINTMESSAGE,
300 $author,
301 $where);
304 $this->conn->saveTransaction();