Path: blob/master/src/applications/diffusion/DiffusionLintSaveRunner.php
12241 views
<?php12final class DiffusionLintSaveRunner extends Phobject {3private $arc = 'arc';4private $severity = ArcanistLintSeverity::SEVERITY_ADVICE;5private $all = false;6private $chunkSize = 256;7private $needsBlame = false;89private $svnRoot;10private $lintCommit;11private $branch;12private $conn;13private $deletes = array();14private $inserts = array();15private $blame = array();161718public function setArc($path) {19$this->arc = $path;20return $this;21}2223public function setSeverity($string) {24$this->severity = $string;25return $this;26}2728public function setAll($bool) {29$this->all = $bool;30return $this;31}3233public function setChunkSize($number) {34$this->chunkSize = $number;35return $this;36}3738public function setNeedsBlame($boolean) {39$this->needsBlame = $boolean;40return $this;41}424344public function run($dir) {45$working_copy = ArcanistWorkingCopyIdentity::newFromPath($dir);46$configuration_manager = new ArcanistConfigurationManager();47$configuration_manager->setWorkingCopyIdentity($working_copy);48$api = ArcanistRepositoryAPI::newAPIFromConfigurationManager(49$configuration_manager);5051$this->svnRoot = id(new PhutilURI($api->getSourceControlPath()))->getPath();52if ($api instanceof ArcanistGitAPI) {53$svn_fetch = $api->getGitConfig('svn-remote.svn.fetch');54list($this->svnRoot) = explode(':', $svn_fetch);55if ($this->svnRoot != '') {56$this->svnRoot = '/'.$this->svnRoot;57}58}5960$callsign = $configuration_manager->getConfigFromAnySource(61'repository.callsign');62$uuid = $api->getRepositoryUUID();63$remote_uri = $api->getRemoteURI();6465$repository_query = id(new PhabricatorRepositoryQuery())66->setViewer(PhabricatorUser::getOmnipotentUser());6768if ($callsign) {69$repository_query->withCallsigns(array($callsign));70} else if ($uuid) {71$repository_query->withUUIDs(array($uuid));72} else if ($remote_uri) {73$repository_query->withURIs(array($remote_uri));74}7576$repository = $repository_query->executeOne();77$branch_name = $api->getBranchName();7879if (!$repository) {80throw new Exception(pht('No repository was found.'));81}8283$this->branch = PhabricatorRepositoryBranch::loadOrCreateBranch(84$repository->getID(),85$branch_name);86$this->conn = $this->branch->establishConnection('w');8788$this->lintCommit = null;89if (!$this->all) {90$this->lintCommit = $this->branch->getLintCommit();91}9293if ($this->lintCommit) {94try {95$commit = $this->lintCommit;96if ($this->svnRoot) {97$commit = $api->getCanonicalRevisionName('@'.$commit);98}99$all_files = $api->getChangedFiles($commit);100} catch (ArcanistCapabilityNotSupportedException $ex) {101$this->lintCommit = null;102}103}104105106if (!$this->lintCommit) {107$where = ($this->svnRoot108? qsprintf($this->conn, 'AND path LIKE %>', $this->svnRoot.'/')109: '');110queryfx(111$this->conn,112'DELETE FROM %T WHERE branchID = %d %Q',113PhabricatorRepository::TABLE_LINTMESSAGE,114$this->branch->getID(),115$where);116$all_files = $api->getAllFiles();117}118119$count = 0;120121$files = array();122foreach ($all_files as $file => $val) {123$count++;124if (!$this->lintCommit) {125$file = $val;126} else {127$this->deletes[] = $this->svnRoot.'/'.$file;128if ($val & ArcanistRepositoryAPI::FLAG_DELETED) {129continue;130}131}132$files[$file] = $file;133134if (count($files) >= $this->chunkSize) {135$this->runArcLint($files);136$files = array();137}138}139140$this->runArcLint($files);141$this->saveLintMessages();142143$this->lintCommit = $api->getUnderlyingWorkingCopyRevision();144$this->branch->setLintCommit($this->lintCommit);145$this->branch->save();146147if ($this->blame) {148$this->blameAuthors();149$this->blame = array();150}151152return $count;153}154155156private function runArcLint(array $files) {157if (!$files) {158return;159}160161echo '.';162try {163$future = new ExecFuture(164'%C lint --severity %s --output json %Ls',165$this->arc,166$this->severity,167$files);168169foreach (new LinesOfALargeExecFuture($future) as $json) {170$paths = null;171try {172$paths = phutil_json_decode($json);173} catch (PhutilJSONParserException $ex) {174fprintf(STDERR, pht('Invalid JSON: %s', $json)."\n");175continue;176}177178foreach ($paths as $path => $messages) {179if (!isset($files[$path])) {180continue;181}182183foreach ($messages as $message) {184$line = idx($message, 'line', 0);185186$this->inserts[] = qsprintf(187$this->conn,188'(%d, %s, %d, %s, %s, %s, %s)',189$this->branch->getID(),190$this->svnRoot.'/'.$path,191$line,192idx($message, 'code', ''),193idx($message, 'severity', ''),194idx($message, 'name', ''),195idx($message, 'description', ''));196197if ($line && $this->needsBlame) {198$this->blame[$path][$line] = true;199}200}201202if (count($this->deletes) >= 1024 || count($this->inserts) >= 256) {203$this->saveLintMessages();204}205}206}207208} catch (Exception $ex) {209fprintf(STDERR, $ex->getMessage()."\n");210}211}212213214private function saveLintMessages() {215$this->conn->openTransaction();216217foreach (array_chunk($this->deletes, 1024) as $paths) {218queryfx(219$this->conn,220'DELETE FROM %T WHERE branchID = %d AND path IN (%Ls)',221PhabricatorRepository::TABLE_LINTMESSAGE,222$this->branch->getID(),223$paths);224}225226foreach (array_chunk($this->inserts, 256) as $values) {227queryfx(228$this->conn,229'INSERT INTO %T230(branchID, path, line, code, severity, name, description)231VALUES %LQ',232PhabricatorRepository::TABLE_LINTMESSAGE,233$values);234}235236$this->conn->saveTransaction();237238$this->deletes = array();239$this->inserts = array();240}241242243private function blameAuthors() {244$repository = id(new PhabricatorRepositoryQuery())245->setViewer(PhabricatorUser::getOmnipotentUser())246->withIDs(array($this->branch->getRepositoryID()))247->executeOne();248249$queries = array();250$futures = array();251foreach ($this->blame as $path => $lines) {252$drequest = DiffusionRequest::newFromDictionary(array(253'user' => PhabricatorUser::getOmnipotentUser(),254'repository' => $repository,255'branch' => $this->branch->getName(),256'path' => $path,257'commit' => $this->lintCommit,258));259260// TODO: Restore blame information / generally fix this workflow.261262$query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest);263$queries[$path] = $query;264$futures[$path] = new ImmediateFuture($query->executeInline());265}266267$authors = array();268269$futures = id(new FutureIterator($futures))270->limit(8);271foreach ($futures as $path => $future) {272$queries[$path]->loadFileContentFromFuture($future);273list(, $rev_list, $blame_dict) = $queries[$path]->getBlameData();274foreach (array_keys($this->blame[$path]) as $line) {275$commit_identifier = $rev_list[$line - 1];276$author = idx($blame_dict[$commit_identifier], 'authorPHID');277if ($author) {278$authors[$author][$path][] = $line;279}280}281}282283if ($authors) {284$this->conn->openTransaction();285286foreach ($authors as $author => $paths) {287$where = array();288foreach ($paths as $path => $lines) {289$where[] = qsprintf(290$this->conn,291'(path = %s AND line IN (%Ld))',292$this->svnRoot.'/'.$path,293$lines);294}295queryfx(296$this->conn,297'UPDATE %T SET authorPHID = %s WHERE %LO',298PhabricatorRepository::TABLE_LINTMESSAGE,299$author,300$where);301}302303$this->conn->saveTransaction();304}305}306307}308309310