2012-11-09 03:37:15 +01:00
|
|
|
<?php
|
|
|
|
|
|
|
|
final class DiffusionLintSaveRunner {
|
|
|
|
private $arc = 'arc';
|
|
|
|
private $severity = ArcanistLintSeverity::SEVERITY_ADVICE;
|
|
|
|
private $all = false;
|
|
|
|
private $chunkSize = 256;
|
2013-03-05 01:22:30 +01:00
|
|
|
private $needsBlame = false;
|
2012-11-09 03:37:15 +01:00
|
|
|
|
|
|
|
private $svnRoot;
|
|
|
|
private $lintCommit;
|
|
|
|
private $branch;
|
|
|
|
private $conn;
|
|
|
|
private $deletes = array();
|
|
|
|
private $inserts = array();
|
2013-03-05 01:22:30 +01:00
|
|
|
private $blame = array();
|
2012-11-09 03:37:15 +01:00
|
|
|
|
|
|
|
|
|
|
|
public function setArc($path) {
|
|
|
|
$this->arc = $path;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setSeverity($string) {
|
|
|
|
$this->severity = $string;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setAll($bool) {
|
|
|
|
$this->all = $bool;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function setChunkSize($number) {
|
|
|
|
$this->chunkSize = $number;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2013-03-05 01:22:30 +01:00
|
|
|
public function setNeedsBlame($boolean) {
|
|
|
|
$this->needsBlame = $boolean;
|
|
|
|
return $this;
|
|
|
|
}
|
|
|
|
|
2012-11-09 03:37:15 +01:00
|
|
|
|
|
|
|
public function run($dir) {
|
|
|
|
$working_copy = ArcanistWorkingCopyIdentity::newFromPath($dir);
|
2014-03-11 21:07:45 +01:00
|
|
|
$configuration_manager = new ArcanistConfigurationManager();
|
|
|
|
$configuration_manager->setWorkingCopyIdentity($working_copy);
|
|
|
|
$api = ArcanistRepositoryAPI::newAPIFromConfigurationManager(
|
|
|
|
$configuration_manager);
|
2012-12-21 02:26:31 +01:00
|
|
|
|
2012-11-09 03:37:15 +01:00
|
|
|
$this->svnRoot = id(new PhutilURI($api->getSourceControlPath()))->getPath();
|
2012-12-21 02:26:31 +01:00
|
|
|
if ($api instanceof ArcanistGitAPI) {
|
|
|
|
$svn_fetch = $api->getGitConfig('svn-remote.svn.fetch');
|
|
|
|
list($this->svnRoot) = explode(':', $svn_fetch);
|
|
|
|
if ($this->svnRoot != '') {
|
2013-03-05 01:22:30 +01:00
|
|
|
$this->svnRoot = '/'.$this->svnRoot;
|
2012-12-21 02:26:31 +01:00
|
|
|
}
|
|
|
|
}
|
2012-11-09 03:37:15 +01:00
|
|
|
|
|
|
|
$project_id = $working_copy->getProjectID();
|
|
|
|
$project = id(new PhabricatorRepositoryArcanistProject())
|
|
|
|
->loadOneWhere('name = %s', $project_id);
|
|
|
|
if (!$project || !$project->getRepositoryID()) {
|
|
|
|
throw new Exception("Couldn't find repository for {$project_id}.");
|
|
|
|
}
|
|
|
|
|
|
|
|
$branch_name = $api->getBranchName();
|
|
|
|
$this->branch = new PhabricatorRepositoryBranch();
|
|
|
|
$this->conn = $this->branch->establishConnection('w');
|
|
|
|
$this->branch = $this->branch->loadOneWhere(
|
|
|
|
'repositoryID = %d AND name = %s',
|
|
|
|
$project->getRepositoryID(),
|
|
|
|
$branch_name);
|
|
|
|
|
|
|
|
$this->lintCommit = null;
|
|
|
|
if (!$this->branch) {
|
|
|
|
$this->branch = id(new PhabricatorRepositoryBranch())
|
|
|
|
->setRepositoryID($project->getRepositoryID())
|
|
|
|
->setName($branch_name)
|
|
|
|
->save();
|
|
|
|
} else if (!$this->all) {
|
|
|
|
$this->lintCommit = $this->branch->getLintCommit();
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($this->lintCommit) {
|
|
|
|
try {
|
2013-03-01 04:29:25 +01:00
|
|
|
$commit = $this->lintCommit;
|
|
|
|
if ($this->svnRoot) {
|
|
|
|
$commit = $api->getCanonicalRevisionName('@'.$commit);
|
|
|
|
}
|
|
|
|
$all_files = $api->getChangedFiles($commit);
|
2012-11-09 03:37:15 +01:00
|
|
|
} catch (ArcanistCapabilityNotSupportedException $ex) {
|
|
|
|
$this->lintCommit = null;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$this->lintCommit) {
|
|
|
|
$where = ($this->svnRoot
|
|
|
|
? qsprintf($this->conn, 'AND path LIKE %>', $this->svnRoot.'/')
|
|
|
|
: '');
|
|
|
|
queryfx(
|
|
|
|
$this->conn,
|
|
|
|
'DELETE FROM %T WHERE branchID = %d %Q',
|
|
|
|
PhabricatorRepository::TABLE_LINTMESSAGE,
|
|
|
|
$this->branch->getID(),
|
|
|
|
$where);
|
|
|
|
$all_files = $api->getAllFiles();
|
|
|
|
}
|
|
|
|
|
|
|
|
$count = 0;
|
|
|
|
|
|
|
|
$files = array();
|
|
|
|
foreach ($all_files as $file => $val) {
|
|
|
|
$count++;
|
|
|
|
if (!$this->lintCommit) {
|
|
|
|
$file = $val;
|
|
|
|
} else {
|
|
|
|
$this->deletes[] = $this->svnRoot.'/'.$file;
|
|
|
|
if ($val & ArcanistRepositoryAPI::FLAG_DELETED) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
$files[$file] = $file;
|
|
|
|
|
|
|
|
if (count($files) >= $this->chunkSize) {
|
|
|
|
$this->runArcLint($files);
|
|
|
|
$files = array();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->runArcLint($files);
|
|
|
|
$this->saveLintMessages();
|
2013-03-05 01:22:30 +01:00
|
|
|
|
|
|
|
$this->lintCommit = $api->getUnderlyingWorkingCopyRevision();
|
|
|
|
$this->branch->setLintCommit($this->lintCommit);
|
2012-11-09 03:37:15 +01:00
|
|
|
$this->branch->save();
|
|
|
|
|
2013-03-05 01:22:30 +01:00
|
|
|
if ($this->blame) {
|
|
|
|
$this->blameAuthors();
|
|
|
|
$this->blame = array();
|
|
|
|
}
|
|
|
|
|
2012-11-09 03:37:15 +01:00
|
|
|
return $count;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function runArcLint(array $files) {
|
|
|
|
if (!$files) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
echo '.';
|
|
|
|
try {
|
|
|
|
$future = new ExecFuture(
|
|
|
|
'%C lint --severity %s --output json %Ls',
|
|
|
|
$this->arc,
|
|
|
|
$this->severity,
|
|
|
|
$files);
|
|
|
|
|
|
|
|
foreach (new LinesOfALargeExecFuture($future) as $json) {
|
|
|
|
$paths = json_decode($json, true);
|
|
|
|
if (!is_array($paths)) {
|
|
|
|
fprintf(STDERR, "Invalid JSON: {$json}\n");
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($paths as $path => $messages) {
|
|
|
|
if (!isset($files[$path])) {
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($messages as $message) {
|
2013-03-05 01:22:30 +01:00
|
|
|
$line = idx($message, 'line', 0);
|
|
|
|
|
2012-11-09 03:37:15 +01:00
|
|
|
$this->inserts[] = qsprintf(
|
|
|
|
$this->conn,
|
|
|
|
'(%d, %s, %d, %s, %s, %s, %s)',
|
|
|
|
$this->branch->getID(),
|
|
|
|
$this->svnRoot.'/'.$path,
|
2013-03-05 01:22:30 +01:00
|
|
|
$line,
|
2012-11-09 03:37:15 +01:00
|
|
|
idx($message, 'code', ''),
|
|
|
|
idx($message, 'severity', ''),
|
|
|
|
idx($message, 'name', ''),
|
|
|
|
idx($message, 'description', ''));
|
2013-03-05 01:22:30 +01:00
|
|
|
|
|
|
|
if ($line && $this->needsBlame) {
|
|
|
|
$this->blame[$path][$line] = true;
|
|
|
|
}
|
2012-11-09 03:37:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
if (count($this->deletes) >= 1024 || count($this->inserts) >= 256) {
|
2013-03-05 01:22:30 +01:00
|
|
|
$this->saveLintMessages();
|
2012-11-09 03:37:15 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
} catch (Exception $ex) {
|
|
|
|
fprintf(STDERR, $ex->getMessage()."\n");
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function saveLintMessages() {
|
|
|
|
$this->conn->openTransaction();
|
|
|
|
|
|
|
|
foreach (array_chunk($this->deletes, 1024) as $paths) {
|
|
|
|
queryfx(
|
|
|
|
$this->conn,
|
|
|
|
'DELETE FROM %T WHERE branchID = %d AND path IN (%Ls)',
|
|
|
|
PhabricatorRepository::TABLE_LINTMESSAGE,
|
|
|
|
$this->branch->getID(),
|
|
|
|
$paths);
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach (array_chunk($this->inserts, 256) as $values) {
|
|
|
|
queryfx(
|
|
|
|
$this->conn,
|
|
|
|
'INSERT INTO %T
|
|
|
|
(branchID, path, line, code, severity, name, description)
|
|
|
|
VALUES %Q',
|
|
|
|
PhabricatorRepository::TABLE_LINTMESSAGE,
|
|
|
|
implode(', ', $values));
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->conn->saveTransaction();
|
2013-03-05 01:22:30 +01:00
|
|
|
|
|
|
|
$this->deletes = array();
|
|
|
|
$this->inserts = array();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
private function blameAuthors() {
|
2013-09-26 01:54:48 +02:00
|
|
|
$repository = id(new PhabricatorRepositoryQuery())
|
|
|
|
->setViewer(PhabricatorUser::getOmnipotentUser())
|
|
|
|
->withIDs(array($this->branch->getRepositoryID()))
|
|
|
|
->executeOne();
|
2013-03-05 01:22:30 +01:00
|
|
|
|
|
|
|
$queries = array();
|
|
|
|
$futures = array();
|
|
|
|
foreach ($this->blame as $path => $lines) {
|
|
|
|
$drequest = DiffusionRequest::newFromDictionary(array(
|
2013-05-15 00:32:19 +02:00
|
|
|
'user' => PhabricatorUser::getOmnipotentUser(),
|
|
|
|
'initFromConduit' => false,
|
2013-03-05 01:22:30 +01:00
|
|
|
'repository' => $repository,
|
|
|
|
'branch' => $this->branch->getName(),
|
|
|
|
'path' => $path,
|
|
|
|
'commit' => $this->lintCommit,
|
|
|
|
));
|
|
|
|
$query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest)
|
|
|
|
->setNeedsBlame(true);
|
|
|
|
$queries[$path] = $query;
|
|
|
|
$futures[$path] = $query->getFileContentFuture();
|
|
|
|
}
|
|
|
|
|
|
|
|
$authors = array();
|
|
|
|
|
|
|
|
foreach (Futures($futures)->limit(8) as $path => $future) {
|
|
|
|
$queries[$path]->loadFileContentFromFuture($future);
|
|
|
|
list(, $rev_list, $blame_dict) = $queries[$path]->getBlameData();
|
|
|
|
foreach (array_keys($this->blame[$path]) as $line) {
|
|
|
|
$commit_identifier = $rev_list[$line - 1];
|
|
|
|
$author = idx($blame_dict[$commit_identifier], 'authorPHID');
|
|
|
|
if ($author) {
|
|
|
|
$authors[$author][$path][] = $line;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if ($authors) {
|
|
|
|
$this->conn->openTransaction();
|
|
|
|
|
|
|
|
foreach ($authors as $author => $paths) {
|
|
|
|
$where = array();
|
|
|
|
foreach ($paths as $path => $lines) {
|
|
|
|
$where[] = qsprintf(
|
|
|
|
$this->conn,
|
|
|
|
'(path = %s AND line IN (%Ld))',
|
|
|
|
$this->svnRoot.'/'.$path,
|
|
|
|
$lines);
|
|
|
|
}
|
|
|
|
queryfx(
|
|
|
|
$this->conn,
|
|
|
|
'UPDATE %T SET authorPHID = %s WHERE %Q',
|
|
|
|
PhabricatorRepository::TABLE_LINTMESSAGE,
|
|
|
|
$author,
|
|
|
|
implode(' OR ', $where));
|
|
|
|
}
|
|
|
|
|
|
|
|
$this->conn->saveTransaction();
|
|
|
|
}
|
2012-11-09 03:37:15 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|