3 final class DiffusionLintSaveRunner
extends Phobject
{
5 private $severity = ArcanistLintSeverity
::SEVERITY_ADVICE
;
7 private $chunkSize = 256;
8 private $needsBlame = false;
14 private $deletes = array();
15 private $inserts = array();
16 private $blame = array();
19 public function setArc($path) {
24 public function setSeverity($string) {
25 $this->severity
= $string;
29 public function setAll($bool) {
34 public function setChunkSize($number) {
35 $this->chunkSize
= $number;
39 public function setNeedsBlame($boolean) {
40 $this->needsBlame
= $boolean;
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());
70 $repository_query->withCallsigns(array($callsign));
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();
81 throw new Exception(pht('No repository was found.'));
84 $this->branch
= PhabricatorRepositoryBranch
::loadOrCreateBranch(
87 $this->conn
= $this->branch
->establishConnection('w');
89 $this->lintCommit
= null;
91 $this->lintCommit
= $this->branch
->getLintCommit();
94 if ($this->lintCommit
) {
96 $commit = $this->lintCommit
;
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
.'/')
113 'DELETE FROM %T WHERE branchID = %d %Q',
114 PhabricatorRepository
::TABLE_LINTMESSAGE
,
115 $this->branch
->getID(),
117 $all_files = $api->getAllFiles();
123 foreach ($all_files as $file => $val) {
125 if (!$this->lintCommit
) {
128 $this->deletes
[] = $this->svnRoot
.'/'.$file;
129 if ($val & ArcanistRepositoryAPI
::FLAG_DELETED
) {
133 $files[$file] = $file;
135 if (count($files) >= $this->chunkSize
) {
136 $this->runArcLint($files);
141 $this->runArcLint($files);
142 $this->saveLintMessages();
144 $this->lintCommit
= $api->getUnderlyingWorkingCopyRevision();
145 $this->branch
->setLintCommit($this->lintCommit
);
146 $this->branch
->save();
149 $this->blameAuthors();
150 $this->blame
= array();
157 private function runArcLint(array $files) {
164 $future = new ExecFuture(
165 '%C lint --severity %s --output json %Ls',
170 foreach (new LinesOfALargeExecFuture($future) as $json) {
173 $paths = phutil_json_decode($json);
174 } catch (PhutilJSONParserException
$ex) {
175 fprintf(STDERR
, pht('Invalid JSON: %s', $json)."\n");
179 foreach ($paths as $path => $messages) {
180 if (!isset($files[$path])) {
184 foreach ($messages as $message) {
185 $line = idx($message, 'line', 0);
187 $this->inserts
[] = qsprintf(
189 '(%d, %s, %d, %s, %s, %s, %s)',
190 $this->branch
->getID(),
191 $this->svnRoot
.'/'.$path,
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) {
221 'DELETE FROM %T WHERE branchID = %d AND path IN (%Ls)',
222 PhabricatorRepository
::TABLE_LINTMESSAGE
,
223 $this->branch
->getID(),
227 foreach (array_chunk($this->inserts
, 256) as $values) {
231 (branchID, path, line, code, severity, name, description)
233 PhabricatorRepository
::TABLE_LINTMESSAGE
,
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()))
252 foreach ($this->blame
as $path => $lines) {
253 $drequest = DiffusionRequest
::newFromDictionary(array(
254 'user' => PhabricatorUser
::getOmnipotentUser(),
255 'repository' => $repository,
256 'branch' => $this->branch
->getName(),
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());
270 $futures = id(new FutureIterator($futures))
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');
279 $authors[$author][$path][] = $line;
285 $this->conn
->openTransaction();
287 foreach ($authors as $author => $paths) {
289 foreach ($paths as $path => $lines) {
292 '(path = %s AND line IN (%Ld))',
293 $this->svnRoot
.'/'.$path,
298 'UPDATE %T SET authorPHID = %s WHERE %LO',
299 PhabricatorRepository
::TABLE_LINTMESSAGE
,
304 $this->conn
->saveTransaction();