1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-29 10:12:41 +01:00

Save blame info to lint messages

Test Plan:
Applied the patch.
Looked at blame and plain blame of SVN and Git file.
Ran the lint saver.
Looked at lint messages list.
/diffusion/lint/

Reviewers: epriestley

Reviewed By: epriestley

CC: aran, Korvin

Differential Revision: https://secure.phabricator.com/D5218
This commit is contained in:
vrana 2013-03-04 16:22:30 -08:00
parent 8ec987dd2f
commit 1091dc7aa1
11 changed files with 178 additions and 69 deletions

View file

@ -0,0 +1,3 @@
ALTER TABLE `{$NAMESPACE}_repository`.`repository_lintmessage`
ADD authorPHID varchar(64) COLLATE utf8_bin AFTER line,
ADD INDEX key_author (authorPHID);

View file

@ -39,6 +39,10 @@ $args = id(new PhutilArgumentParser($argv))
'default' => 256, 'default' => 256,
'help' => "Number of paths passed to `arc` at once.", 'help' => "Number of paths passed to `arc` at once.",
), ),
array(
'name' => 'blame',
'help' => "Assign lint errors to authors who last modified the line.",
),
)); ));
echo "Saving lint errors to database...\n"; echo "Saving lint errors to database...\n";
@ -48,6 +52,7 @@ $count = id(new DiffusionLintSaveRunner())
->setArc($args->getArg('arc')) ->setArc($args->getArg('arc'))
->setSeverity($args->getArg('severity')) ->setSeverity($args->getArg('severity'))
->setChunkSize($args->getArg('chunk-size')) ->setChunkSize($args->getArg('chunk-size'))
->setNeedsBlame($args->getArg('blame'))
->run('.'); ->run('.');
echo "\nProcessed {$count} files.\n"; echo "\nProcessed {$count} files.\n";

View file

@ -5,6 +5,7 @@ final class DiffusionLintSaveRunner {
private $severity = ArcanistLintSeverity::SEVERITY_ADVICE; private $severity = ArcanistLintSeverity::SEVERITY_ADVICE;
private $all = false; private $all = false;
private $chunkSize = 256; private $chunkSize = 256;
private $needsBlame = false;
private $svnRoot; private $svnRoot;
private $lintCommit; private $lintCommit;
@ -12,6 +13,7 @@ final class DiffusionLintSaveRunner {
private $conn; private $conn;
private $deletes = array(); private $deletes = array();
private $inserts = array(); private $inserts = array();
private $blame = array();
public function setArc($path) { public function setArc($path) {
@ -34,6 +36,11 @@ final class DiffusionLintSaveRunner {
return $this; return $this;
} }
public function setNeedsBlame($boolean) {
$this->needsBlame = $boolean;
return $this;
}
public function run($dir) { public function run($dir) {
$working_copy = ArcanistWorkingCopyIdentity::newFromPath($dir); $working_copy = ArcanistWorkingCopyIdentity::newFromPath($dir);
@ -44,7 +51,7 @@ final class DiffusionLintSaveRunner {
$svn_fetch = $api->getGitConfig('svn-remote.svn.fetch'); $svn_fetch = $api->getGitConfig('svn-remote.svn.fetch');
list($this->svnRoot) = explode(':', $svn_fetch); list($this->svnRoot) = explode(':', $svn_fetch);
if ($this->svnRoot != '') { if ($this->svnRoot != '') {
$this->svnRoot = '/' . $this->svnRoot; $this->svnRoot = '/'.$this->svnRoot;
} }
} }
@ -98,8 +105,6 @@ final class DiffusionLintSaveRunner {
$all_files = $api->getAllFiles(); $all_files = $api->getAllFiles();
} }
$this->deletes = array();
$this->inserts = array();
$count = 0; $count = 0;
$files = array(); $files = array();
@ -123,9 +128,16 @@ final class DiffusionLintSaveRunner {
$this->runArcLint($files); $this->runArcLint($files);
$this->saveLintMessages(); $this->saveLintMessages();
$this->branch->setLintCommit($api->getUnderlyingWorkingCopyRevision());
$this->lintCommit = $api->getUnderlyingWorkingCopyRevision();
$this->branch->setLintCommit($this->lintCommit);
$this->branch->save(); $this->branch->save();
if ($this->blame) {
$this->blameAuthors();
$this->blame = array();
}
return $count; return $count;
} }
@ -156,22 +168,26 @@ final class DiffusionLintSaveRunner {
} }
foreach ($messages as $message) { foreach ($messages as $message) {
$line = idx($message, 'line', 0);
$this->inserts[] = qsprintf( $this->inserts[] = qsprintf(
$this->conn, $this->conn,
'(%d, %s, %d, %s, %s, %s, %s)', '(%d, %s, %d, %s, %s, %s, %s)',
$this->branch->getID(), $this->branch->getID(),
$this->svnRoot.'/'.$path, $this->svnRoot.'/'.$path,
idx($message, 'line', 0), $line,
idx($message, 'code', ''), idx($message, 'code', ''),
idx($message, 'severity', ''), idx($message, 'severity', ''),
idx($message, 'name', ''), idx($message, 'name', ''),
idx($message, 'description', '')); idx($message, 'description', ''));
if ($line && $this->needsBlame) {
$this->blame[$path][$line] = true;
}
} }
if (count($this->deletes) >= 1024 || count($this->inserts) >= 256) { if (count($this->deletes) >= 1024 || count($this->inserts) >= 256) {
$this->saveLintMessages($this->branch); $this->saveLintMessages();
$this->deletes = array();
$this->inserts = array();
} }
} }
} }
@ -205,6 +221,67 @@ final class DiffusionLintSaveRunner {
} }
$this->conn->saveTransaction(); $this->conn->saveTransaction();
$this->deletes = array();
$this->inserts = array();
}
private function blameAuthors() {
$repository = id(new PhabricatorRepository())->load(
$this->branch->getRepositoryID());
$queries = array();
$futures = array();
foreach ($this->blame as $path => $lines) {
$drequest = DiffusionRequest::newFromDictionary(array(
'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();
}
} }
} }

View file

@ -202,11 +202,7 @@ final class DiffusionBrowseFileController extends DiffusionController {
$rows = array(); $rows = array();
foreach ($text_list as $k => $line) { foreach ($text_list as $k => $line) {
$rev = $rev_list[$k]; $rev = $rev_list[$k];
if (isset($blame_dict[$rev]['handle'])) {
$author = $blame_dict[$rev]['handle']->getName();
} else {
$author = $blame_dict[$rev]['author']; $author = $blame_dict[$rev]['author'];
}
$rows[] = $rows[] =
sprintf("%-10s %-20s %s", substr($rev, 0, 7), $author, $line); sprintf("%-10s %-20s %s", substr($rev, 0, 7), $author, $line);
} }
@ -430,11 +426,13 @@ final class DiffusionBrowseFileController extends DiffusionController {
DiffusionFileContentQuery $file_query, DiffusionFileContentQuery $file_query,
$selected) { $selected) {
$handles = array();
if ($blame_dict) { if ($blame_dict) {
$epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch'); $epoch_list = ipull(ifilter($blame_dict, 'epoch'), 'epoch');
$epoch_min = min($epoch_list); $epoch_min = min($epoch_list);
$epoch_max = max($epoch_list); $epoch_max = max($epoch_list);
$epoch_range = ($epoch_max - $epoch_min) + 1; $epoch_range = ($epoch_max - $epoch_min) + 1;
$handles = $this->loadViewerHandles(ipull($blame_dict, 'authorPHID'));
} }
$line_arr = array(); $line_arr = array();
@ -499,8 +497,9 @@ final class DiffusionBrowseFileController extends DiffusionController {
$display_line['color'] = $color; $display_line['color'] = $color;
$display_line['commit'] = $rev; $display_line['commit'] = $rev;
if (isset($blame['handle'])) { $author_phid = idx($blame, 'authorPHID');
$author_link = $blame['handle']->renderLink(); if ($author_phid && $handles[$author_phid]) {
$author_link = $handles[$author_phid]->renderLink();
} else { } else {
$author_link = phutil_tag( $author_link = phutil_tag(
'span', 'span',

View file

@ -169,18 +169,19 @@ final class DiffusionLintController extends DiffusionController {
} }
if ($owner_phids) { if ($owner_phids) {
$or = array();
$or[] = qsprintf($conn, 'authorPHID IN (%Ls)', $owner_phids);
$paths = array();
$packages = id(new PhabricatorOwnersOwner()) $packages = id(new PhabricatorOwnersOwner())
->loadAllWhere('userPHID IN (%Ls)', $owner_phids); ->loadAllWhere('userPHID IN (%Ls)', $owner_phids);
if (!$packages) { if ($packages) {
return array(); $paths = id(new PhabricatorOwnersPath())->loadAllWhere(
} 'packageID IN (%Ld)',
mpull($packages, 'getPackageID'));
$paths = id(new PhabricatorOwnersPath())
->loadAllWhere('packageID IN (%Ld)', array_keys($packages));
if (!$paths) {
return array();
} }
if ($paths) {
$repositories = id(new PhabricatorRepository())->loadAllWhere( $repositories = id(new PhabricatorRepository())->loadAllWhere(
'phid IN (%Ls)', 'phid IN (%Ls)',
array_unique(mpull($paths, 'getRepositoryPHID'))); array_unique(mpull($paths, 'getRepositoryPHID')));
@ -190,8 +191,8 @@ final class DiffusionLintController extends DiffusionController {
'repositoryID IN (%Ld)', 'repositoryID IN (%Ld)',
$repositories); $repositories);
$branches = mgroup($branches, 'getRepositoryID'); $branches = mgroup($branches, 'getRepositoryID');
}
$or = array();
foreach ($paths as $path) { foreach ($paths as $path) {
$branch = idx($branches, $repositories[$path->getRepositoryPHID()]); $branch = idx($branches, $repositories[$path->getRepositoryPHID()]);
if ($branch) { if ($branch) {
@ -207,9 +208,6 @@ final class DiffusionLintController extends DiffusionController {
} }
} }
} }
if (!$or) {
return array();
}
$where[] = '('.implode(' OR ', $or).')'; $where[] = '('.implode(' OR ', $or).')';
} }

View file

@ -11,6 +11,8 @@ final class DiffusionLintDetailsController extends DiffusionController {
$messages = $this->loadLintMessages($branch, $limit, $offset); $messages = $this->loadLintMessages($branch, $limit, $offset);
$is_dir = (substr('/'.$drequest->getPath(), -1) == '/'); $is_dir = (substr('/'.$drequest->getPath(), -1) == '/');
$authors = $this->loadViewerHandles(ipull($messages, 'authorPHID'));
$rows = array(); $rows = array();
foreach ($messages as $message) { foreach ($messages as $message) {
$path = hsprintf( $path = hsprintf(
@ -31,9 +33,15 @@ final class DiffusionLintDetailsController extends DiffusionController {
)), )),
$message['line']); $message['line']);
$author = $message['authorPHID'];
if ($author && $authors[$author]) {
$author = $authors[$author]->renderLink();
}
$rows[] = array( $rows[] = array(
$path, $path,
$line, $line,
$author,
ArcanistLintSeverity::getStringForSeverity($message['severity']), ArcanistLintSeverity::getStringForSeverity($message['severity']),
$message['name'], $message['name'],
$message['description'], $message['description'],
@ -44,11 +52,12 @@ final class DiffusionLintDetailsController extends DiffusionController {
->setHeaders(array( ->setHeaders(array(
'Path', 'Path',
'Line', 'Line',
'Author',
'Severity', 'Severity',
'Name', 'Name',
'Description', 'Description',
)) ))
->setColumnClasses(array('', 'n', '', '', '')) ->setColumnClasses(array('', 'n'))
->setColumnVisibility(array($is_dir)); ->setColumnVisibility(array($is_dir));
$content = array(); $content = array();

View file

@ -11,8 +11,11 @@ abstract class DiffusionFileContentQuery extends DiffusionQuery {
return parent::newQueryObject(__CLASS__, $request); return parent::newQueryObject(__CLASS__, $request);
} }
final public function loadFileContent() { abstract public function getFileContentFuture();
$this->fileContent = $this->executeQuery(); abstract protected function executeQueryFromFuture(Future $future);
final public function loadFileContentFromFuture(Future $future) {
$this->fileContent = $this->executeQueryFromFuture($future);
$repository = $this->getRequest()->getRepository(); $repository = $this->getRequest()->getRepository();
$try_encoding = $repository->getDetail('encoding'); $try_encoding = $repository->getDetail('encoding');
@ -25,6 +28,14 @@ abstract class DiffusionFileContentQuery extends DiffusionQuery {
return $this->fileContent; return $this->fileContent;
} }
final protected function executeQuery() {
return $this->loadFileContentFromFuture($this->getFileContentFuture());
}
final public function loadFileContent() {
return $this->executeQuery();
}
final public function getRawData() { final public function getRawData() {
return $this->fileContent->getCorpus(); return $this->fileContent->getCorpus();
} }
@ -72,30 +83,22 @@ abstract class DiffusionFileContentQuery extends DiffusionQuery {
$commit->getEpoch(); $commit->getEpoch();
} }
$commits_data = array();
if ($commits) { if ($commits) {
$commits_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere( $commits_data = id(new PhabricatorRepositoryCommitData())->loadAllWhere(
'commitID IN (%Ls)', 'commitID IN (%Ls)',
mpull($commits, 'getID')); mpull($commits, 'getID'));
}
$phids = array();
foreach ($commits_data as $data) {
$phids[] = $data->getCommitDetail('authorPHID');
}
$loader = new PhabricatorObjectHandleData(array_unique($phids));
$loader->setViewer($this->viewer);
$handles = $loader->loadHandles();
foreach ($commits_data as $data) { foreach ($commits_data as $data) {
if ($data->getCommitDetail('authorPHID')) { $author_phid = $data->getCommitDetail('authorPHID');
$commit_identifier = if (!$author_phid) {
$commits[$data->getCommitID()]->getCommitIdentifier(); continue;
$blame_dict[$commit_identifier]['handle'] = }
$handles[$data->getCommitDetail('authorPHID')]; $commit = $commits[$data->getCommitID()];
$commit_identifier = $commit->getCommitIdentifier();
$blame_dict[$commit_identifier]['authorPHID'] = $author_phid;
} }
} }
} }
return array($text_list, $rev_list, $blame_dict); return array($text_list, $rev_list, $blame_dict);

View file

@ -2,7 +2,7 @@
final class DiffusionGitFileContentQuery extends DiffusionFileContentQuery { final class DiffusionGitFileContentQuery extends DiffusionFileContentQuery {
protected function executeQuery() { public function getFileContentFuture() {
$drequest = $this->getRequest(); $drequest = $this->getRequest();
$repository = $drequest->getRepository(); $repository = $drequest->getRepository();
@ -10,16 +10,20 @@ final class DiffusionGitFileContentQuery extends DiffusionFileContentQuery {
$commit = $drequest->getCommit(); $commit = $drequest->getCommit();
if ($this->getNeedsBlame()) { if ($this->getNeedsBlame()) {
list($corpus) = $repository->execxLocalCommand( return $repository->getLocalCommandFuture(
'--no-pager blame -c -l --date=short %s -- %s', '--no-pager blame -c -l --date=short %s -- %s',
$commit, $commit,
$path); $path);
} else { } else {
list($corpus) = $repository->execxLocalCommand( return $repository->getLocalCommandFuture(
'cat-file blob %s:%s', 'cat-file blob %s:%s',
$commit, $commit,
$path); $path);
} }
}
protected function executeQueryFromFuture(Future $future) {
list($corpus) = $future->resolvex();
$file_content = new DiffusionFileContent(); $file_content = new DiffusionFileContent();
$file_content->setCorpus($corpus); $file_content->setCorpus($corpus);
@ -27,7 +31,6 @@ final class DiffusionGitFileContentQuery extends DiffusionFileContentQuery {
return $file_content; return $file_content;
} }
protected function tokenizeLine($line) { protected function tokenizeLine($line) {
$m = array(); $m = array();
// sample lines: // sample lines:

View file

@ -3,7 +3,7 @@
final class DiffusionMercurialFileContentQuery final class DiffusionMercurialFileContentQuery
extends DiffusionFileContentQuery { extends DiffusionFileContentQuery {
protected function executeQuery() { public function getFileContentFuture() {
$drequest = $this->getRequest(); $drequest = $this->getRequest();
$repository = $drequest->getRepository(); $repository = $drequest->getRepository();
@ -13,16 +13,20 @@ final class DiffusionMercurialFileContentQuery
if ($this->getNeedsBlame()) { if ($this->getNeedsBlame()) {
// NOTE: We're using "--number" instead of "--changeset" because there is // NOTE: We're using "--number" instead of "--changeset" because there is
// no way to get "--changeset" to show us the full commit hashes. // no way to get "--changeset" to show us the full commit hashes.
list($corpus) = $repository->execxLocalCommand( return $repository->getLocalCommandFuture(
'annotate --user --number --rev %s -- %s', 'annotate --user --number --rev %s -- %s',
$commit, $commit,
$path); $path);
} else { } else {
list($corpus) = $repository->execxLocalCommand( return $repository->getLocalCommandFuture(
'cat --rev %s -- %s', 'cat --rev %s -- %s',
$commit, $commit,
$path); $path);
} }
}
protected function executeQueryFromFuture(Future $future) {
list($corpus) = $future->resolvex();
$file_content = new DiffusionFileContent(); $file_content = new DiffusionFileContent();
$file_content->setCorpus($corpus); $file_content->setCorpus($corpus);

View file

@ -2,7 +2,7 @@
final class DiffusionSvnFileContentQuery extends DiffusionFileContentQuery { final class DiffusionSvnFileContentQuery extends DiffusionFileContentQuery {
protected function executeQuery() { public function getFileContentFuture() {
$drequest = $this->getRequest(); $drequest = $this->getRequest();
$repository = $drequest->getRepository(); $repository = $drequest->getRepository();
@ -11,13 +11,17 @@ final class DiffusionSvnFileContentQuery extends DiffusionFileContentQuery {
$remote_uri = $repository->getRemoteURI(); $remote_uri = $repository->getRemoteURI();
try { return $repository->getRemoteCommandFuture(
list($corpus) = $repository->execxRemoteCommand(
'%C %s%s@%s', '%C %s%s@%s',
$this->getNeedsBlame() ? 'blame --force' : 'cat', $this->getNeedsBlame() ? 'blame --force' : 'cat',
$remote_uri, $remote_uri,
phutil_escape_uri($path), phutil_escape_uri($path),
$commit); $commit);
}
protected function executeQueryFromFuture(Future $future) {
try {
list($corpus) = $future->resolvex();
} catch (CommandException $ex) { } catch (CommandException $ex) {
$stderr = $ex->getStdErr(); $stderr = $ex->getStdErr();
if (preg_match('/path not found$/', trim($stderr))) { if (preg_match('/path not found$/', trim($stderr))) {

View file

@ -1161,6 +1161,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'sql', 'type' => 'sql',
'name' => $this->getPatchPath('20131302.maniphestvalue.sql'), 'name' => $this->getPatchPath('20131302.maniphestvalue.sql'),
), ),
'20130304.lintauthor.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20130304.lintauthor.sql'),
),
); );
} }