@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at upstream/main 309 lines 8.3 kB view raw
1<?php 2 3final 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; 9 10 private $svnRoot; 11 private $lintCommit; 12 private $branch; 13 private $conn; 14 private $deletes = array(); 15 private $inserts = array(); 16 private $blame = array(); 17 18 19 public function setArc($path) { 20 $this->arc = $path; 21 return $this; 22 } 23 24 public function setSeverity($string) { 25 $this->severity = $string; 26 return $this; 27 } 28 29 public function setAll($bool) { 30 $this->all = $bool; 31 return $this; 32 } 33 34 public function setChunkSize($number) { 35 $this->chunkSize = $number; 36 return $this; 37 } 38 39 public function setNeedsBlame($boolean) { 40 $this->needsBlame = $boolean; 41 return $this; 42 } 43 44 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); 51 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; 58 } 59 } 60 61 $callsign = $configuration_manager->getConfigFromAnySource( 62 'repository.callsign'); 63 $uuid = $api->getRepositoryUUID(); 64 $remote_uri = $api->getRemoteURI(); 65 66 $repository_query = id(new PhabricatorRepositoryQuery()) 67 ->setViewer(PhabricatorUser::getOmnipotentUser()); 68 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)); 75 } 76 77 $repository = $repository_query->executeOne(); 78 $branch_name = $api->getBranchName(); 79 80 if (!$repository) { 81 throw new Exception(pht('No repository was found.')); 82 } 83 84 $this->branch = PhabricatorRepositoryBranch::loadOrCreateBranch( 85 $repository->getID(), 86 $branch_name); 87 $this->conn = $this->branch->establishConnection('w'); 88 89 $this->lintCommit = null; 90 if (!$this->all) { 91 $this->lintCommit = $this->branch->getLintCommit(); 92 } 93 94 $all_files = array(); 95 if ($this->lintCommit) { 96 try { 97 $commit = $this->lintCommit; 98 if ($this->svnRoot) { 99 $commit = $api->getCanonicalRevisionName('@'.$commit); 100 } 101 $all_files = $api->getChangedFiles($commit); 102 } catch (ArcanistCapabilityNotSupportedException $ex) { 103 $this->lintCommit = null; 104 } 105 } 106 107 108 if (!$this->lintCommit) { 109 $where = ($this->svnRoot 110 ? qsprintf($this->conn, 'AND path LIKE %>', $this->svnRoot.'/') 111 : ''); 112 queryfx( 113 $this->conn, 114 'DELETE FROM %T WHERE branchID = %d %Q', 115 PhabricatorRepository::TABLE_LINTMESSAGE, 116 $this->branch->getID(), 117 $where); 118 $all_files = $api->getAllFiles(); 119 } 120 121 $count = 0; 122 123 $files = array(); 124 foreach ($all_files as $file => $val) { 125 $count++; 126 if (!$this->lintCommit) { 127 $file = $val; 128 } else { 129 $this->deletes[] = $this->svnRoot.'/'.$file; 130 if ($val & ArcanistRepositoryAPI::FLAG_DELETED) { 131 continue; 132 } 133 } 134 $files[$file] = $file; 135 136 if (count($files) >= $this->chunkSize) { 137 $this->runArcLint($files); 138 $files = array(); 139 } 140 } 141 142 $this->runArcLint($files); 143 $this->saveLintMessages(); 144 145 $this->lintCommit = $api->getUnderlyingWorkingCopyRevision(); 146 $this->branch->setLintCommit($this->lintCommit); 147 $this->branch->save(); 148 149 if ($this->blame) { 150 $this->blameAuthors(); 151 $this->blame = array(); 152 } 153 154 return $count; 155 } 156 157 158 private function runArcLint(array $files) { 159 if (!$files) { 160 return; 161 } 162 163 echo '.'; 164 try { 165 $future = new ExecFuture( 166 '%C lint --severity %s --output json %Ls', 167 $this->arc, 168 $this->severity, 169 $files); 170 171 foreach (new LinesOfALargeExecFuture($future) as $json) { 172 $paths = null; 173 try { 174 $paths = phutil_json_decode($json); 175 } catch (PhutilJSONParserException $ex) { 176 fprintf(STDERR, pht('Invalid JSON: %s', $json)."\n"); 177 continue; 178 } 179 180 foreach ($paths as $path => $messages) { 181 if (!isset($files[$path])) { 182 continue; 183 } 184 185 foreach ($messages as $message) { 186 $line = idx($message, 'line', 0); 187 188 $this->inserts[] = qsprintf( 189 $this->conn, 190 '(%d, %s, %d, %s, %s, %s, %s)', 191 $this->branch->getID(), 192 $this->svnRoot.'/'.$path, 193 $line, 194 idx($message, 'code', ''), 195 idx($message, 'severity', ''), 196 idx($message, 'name', ''), 197 idx($message, 'description', '')); 198 199 if ($line && $this->needsBlame) { 200 $this->blame[$path][$line] = true; 201 } 202 } 203 204 if (count($this->deletes) >= 1024 || count($this->inserts) >= 256) { 205 $this->saveLintMessages(); 206 } 207 } 208 } 209 210 } catch (Exception $ex) { 211 fprintf(STDERR, $ex->getMessage()."\n"); 212 } 213 } 214 215 216 private function saveLintMessages() { 217 $this->conn->openTransaction(); 218 219 foreach (array_chunk($this->deletes, 1024) as $paths) { 220 queryfx( 221 $this->conn, 222 'DELETE FROM %T WHERE branchID = %d AND path IN (%Ls)', 223 PhabricatorRepository::TABLE_LINTMESSAGE, 224 $this->branch->getID(), 225 $paths); 226 } 227 228 foreach (array_chunk($this->inserts, 256) as $values) { 229 queryfx( 230 $this->conn, 231 'INSERT INTO %T 232 (branchID, path, line, code, severity, name, description) 233 VALUES %LQ', 234 PhabricatorRepository::TABLE_LINTMESSAGE, 235 $values); 236 } 237 238 $this->conn->saveTransaction(); 239 240 $this->deletes = array(); 241 $this->inserts = array(); 242 } 243 244 245 private function blameAuthors() { 246 $repository = id(new PhabricatorRepositoryQuery()) 247 ->setViewer(PhabricatorUser::getOmnipotentUser()) 248 ->withIDs(array($this->branch->getRepositoryID())) 249 ->executeOne(); 250 251 $queries = array(); 252 $futures = array(); 253 foreach ($this->blame as $path => $lines) { 254 $drequest = DiffusionRequest::newFromDictionary(array( 255 'user' => PhabricatorUser::getOmnipotentUser(), 256 'repository' => $repository, 257 'branch' => $this->branch->getName(), 258 'path' => $path, 259 'commit' => $this->lintCommit, 260 )); 261 262 // TODO: Restore blame information / generally fix this workflow. 263 264 $query = DiffusionFileContentQuery::newFromDiffusionRequest($drequest); 265 $queries[$path] = $query; 266 $futures[$path] = new ImmediateFuture($query->executeInline()); 267 } 268 269 $authors = array(); 270 271 $futures = id(new FutureIterator($futures)) 272 ->limit(8); 273 foreach ($futures as $path => $future) { 274 $queries[$path]->loadFileContentFromFuture($future); 275 list(, $rev_list, $blame_dict) = $queries[$path]->getBlameData(); 276 foreach (array_keys($this->blame[$path]) as $line) { 277 $commit_identifier = $rev_list[$line - 1]; 278 $author = idx($blame_dict[$commit_identifier], 'authorPHID'); 279 if ($author) { 280 $authors[$author][$path][] = $line; 281 } 282 } 283 } 284 285 if ($authors) { 286 $this->conn->openTransaction(); 287 288 foreach ($authors as $author => $paths) { 289 $where = array(); 290 foreach ($paths as $path => $lines) { 291 $where[] = qsprintf( 292 $this->conn, 293 '(path = %s AND line IN (%Ld))', 294 $this->svnRoot.'/'.$path, 295 $lines); 296 } 297 queryfx( 298 $this->conn, 299 'UPDATE %T SET authorPHID = %s WHERE %LO', 300 PhabricatorRepository::TABLE_LINTMESSAGE, 301 $author, 302 $where); 303 } 304 305 $this->conn->saveTransaction(); 306 } 307 } 308 309}