@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
1<?php
2
3final class DiffusionLintController extends DiffusionController {
4
5 public function shouldAllowPublic() {
6 return true;
7 }
8
9 public function handleRequest(AphrontRequest $request) {
10 $viewer = $this->getViewer();
11
12 if ($this->getRepositoryIdentifierFromRequest($request)) {
13 $response = $this->loadDiffusionContext();
14 if ($response) {
15 return $response;
16 }
17
18 $drequest = $this->getDiffusionRequest();
19 } else {
20 $drequest = null;
21 }
22
23 $code = $request->getStr('lint');
24 if (strlen($code)) {
25 return $this->buildDetailsResponse();
26 }
27
28 $owners = array();
29 if (!$drequest) {
30 if (!$request->getArr('owner')) {
31 $owners = array($viewer->getPHID());
32 } else {
33 $owners = array(head($request->getArr('owner')));
34 }
35 }
36
37 $codes = $this->loadLintCodes($drequest, $owners);
38
39 if ($codes) {
40 $branches = id(new PhabricatorRepositoryBranch())->loadAllWhere(
41 'id IN (%Ld)',
42 array_unique(ipull($codes, 'branchID')));
43 $branches = mpull($branches, null, 'getID');
44 } else {
45 $branches = array();
46 }
47
48 if ($branches) {
49 $repositories = id(new PhabricatorRepositoryQuery())
50 ->setViewer($viewer)
51 ->withIDs(mpull($branches, 'getRepositoryID'))
52 ->execute();
53 $repositories = mpull($repositories, null, 'getID');
54 } else {
55 $repositories = array();
56 }
57
58
59 $rows = array();
60 $total = 0;
61 foreach ($codes as $code) {
62 $branch = idx($branches, $code['branchID']);
63 if (!$branch) {
64 continue;
65 }
66
67 $repository = idx($repositories, $branch->getRepositoryID());
68 if (!$repository) {
69 continue;
70 }
71
72 $total += $code['n'];
73
74 if ($drequest) {
75 $href_lint = $drequest->generateURI(
76 array(
77 'action' => 'lint',
78 'lint' => $code['code'],
79 ));
80
81 $href_browse = $drequest->generateURI(
82 array(
83 'action' => 'browse',
84 'lint' => $code['code'],
85 ));
86
87 $href_repo = $drequest->generateURI(
88 array(
89 'action' => 'lint',
90 ));
91 } else {
92 $href_lint = $repository->generateURI(
93 array(
94 'action' => 'lint',
95 'lint' => $code['code'],
96 ));
97
98 $href_browse = $repository->generateURI(
99 array(
100 'action' => 'browse',
101 'lint' => $code['code'],
102 ));
103
104 $href_repo = $repository->generateURI(
105 array(
106 'action' => 'lint',
107 ));
108 }
109
110 $rows[] = array(
111 phutil_tag('a', array('href' => $href_lint), $code['n']),
112 phutil_tag('a', array('href' => $href_browse), $code['files']),
113 phutil_tag(
114 'a',
115 array(
116 'href' => $href_repo,
117 ),
118 $repository->getDisplayName()),
119 ArcanistLintSeverity::getStringForSeverity($code['maxSeverity']),
120 $code['code'],
121 $code['maxName'],
122 $code['maxDescription'],
123 );
124 }
125
126 $table = id(new AphrontTableView($rows))
127 ->setHeaders(array(
128 pht('Problems'),
129 pht('Files'),
130 pht('Repository'),
131 pht('Severity'),
132 pht('Code'),
133 pht('Name'),
134 pht('Example'),
135 ))
136 ->setColumnVisibility(array(true, true, !$drequest))
137 ->setColumnClasses(array('n', 'n', '', '', 'pri', '', ''));
138
139 $content = array();
140
141 if (!$drequest) {
142 $form = id(new AphrontFormView())
143 ->setUser($viewer)
144 ->setMethod('GET')
145 ->appendControl(
146 id(new AphrontFormTokenizerControl())
147 ->setDatasource(new PhabricatorPeopleDatasource())
148 ->setLimit(1)
149 ->setName('owner')
150 ->setLabel(pht('Owner'))
151 ->setValue($owners))
152 ->appendChild(
153 id(new AphrontFormSubmitControl())
154 ->setValue(pht('Filter')));
155 $content[] = id(new AphrontListFilterView())->appendChild($form);
156 }
157
158 $content[] = id(new PHUIObjectBoxView())
159 ->setHeaderText(pht('Lint'))
160 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
161 ->setTable($table);
162
163 $title = array('Lint');
164 $crumbs = $this->buildCrumbs(
165 array(
166 'branch' => true,
167 'path' => true,
168 'view' => 'lint',
169 ));
170 $crumbs->setBorder(true);
171
172 if ($drequest) {
173 $title[] = $drequest->getRepository()->getDisplayName();
174 } else {
175 $crumbs->addTextCrumb(pht('All Lint'));
176 }
177
178 if ($drequest) {
179 $branch = $drequest->loadBranch();
180
181 $header = id(new PHUIHeaderView())
182 ->setHeader(pht('Lint: %s', $this->renderPathLinks($drequest, 'lint')))
183 ->setUser($viewer)
184 ->setHeaderIcon('fa-code');
185 $actions = $this->buildActionView($drequest);
186 $properties = $this->buildPropertyView(
187 $drequest,
188 $branch,
189 $total,
190 $actions);
191
192 $object_box = id(new PHUIObjectBoxView())
193 ->setHeader($header)
194 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
195 ->addPropertyList($properties);
196 } else {
197 $object_box = null;
198 $header = id(new PHUIHeaderView())
199 ->setHeader(pht('All Lint'))
200 ->setHeaderIcon('fa-code');
201 }
202
203 $view = id(new PHUITwoColumnView())
204 ->setHeader($header)
205 ->setFooter(array(
206 $object_box,
207 $content,
208 ));
209
210 return $this->newPage()
211 ->setTitle($title)
212 ->setCrumbs($crumbs)
213 ->appendChild(
214 array(
215 $view,
216 ));
217 }
218
219 private function loadLintCodes($drequest, array $owner_phids) {
220 $conn = id(new PhabricatorRepository())->establishConnection('r');
221 $where = array('1 = 1');
222
223 if ($drequest) {
224 $branch = $drequest->loadBranch();
225 if (!$branch) {
226 return array();
227 }
228
229 $where[] = qsprintf($conn, 'branchID = %d', $branch->getID());
230
231 if ($drequest->getPath() != '') {
232 $path = '/'.$drequest->getPath();
233 $is_dir = (substr($path, -1) == '/');
234 $where[] = ($is_dir
235 ? qsprintf($conn, 'path LIKE %>', $path)
236 : qsprintf($conn, 'path = %s', $path));
237 }
238 }
239
240 if ($owner_phids) {
241 $or = array();
242 $or[] = qsprintf($conn, 'authorPHID IN (%Ls)', $owner_phids);
243
244 $paths = array();
245 $packages = id(new PhabricatorOwnersOwner())
246 ->loadAllWhere('userPHID IN (%Ls)', $owner_phids);
247 if ($packages) {
248 $paths = id(new PhabricatorOwnersPath())->loadAllWhere(
249 'packageID IN (%Ld)',
250 mpull($packages, 'getPackageID'));
251 }
252
253 if ($paths) {
254 $repositories = id(new PhabricatorRepositoryQuery())
255 ->setViewer($this->getRequest()->getUser())
256 ->withPHIDs(mpull($paths, 'getRepositoryPHID'))
257 ->execute();
258 $repositories = mpull($repositories, 'getID', 'getPHID');
259
260 $branches = id(new PhabricatorRepositoryBranch())->loadAllWhere(
261 'repositoryID IN (%Ld)',
262 $repositories);
263 $branches = mgroup($branches, 'getRepositoryID');
264 }
265
266 foreach ($paths as $path) {
267 $branch = idx(
268 $branches,
269 idx(
270 $repositories,
271 $path->getRepositoryPHID()));
272 if ($branch) {
273 $condition = qsprintf(
274 $conn,
275 '(branchID IN (%Ld) AND path LIKE %>)',
276 array_keys($branch),
277 $path->getPath());
278 if ($path->getExcluded()) {
279 $where[] = qsprintf($conn, 'NOT %Q', $condition);
280 } else {
281 $or[] = $condition;
282 }
283 }
284 }
285 $where[] = qsprintf($conn, '%LO', $or);
286 }
287
288 return queryfx_all(
289 $conn,
290 'SELECT
291 branchID,
292 code,
293 MAX(severity) AS maxSeverity,
294 MAX(name) AS maxName,
295 MAX(description) AS maxDescription,
296 COUNT(DISTINCT path) AS files,
297 COUNT(*) AS n
298 FROM %T
299 WHERE %LA
300 GROUP BY branchID, code
301 ORDER BY n DESC',
302 PhabricatorRepository::TABLE_LINTMESSAGE,
303 $where);
304 }
305
306 protected function buildActionView(DiffusionRequest $drequest) {
307 $viewer = $this->getRequest()->getUser();
308
309 $view = id(new PhabricatorActionListView())
310 ->setUser($viewer);
311
312 $list_uri = $drequest->generateURI(
313 array(
314 'action' => 'lint',
315 'lint' => '',
316 ));
317
318 $view->addAction(
319 id(new PhabricatorActionView())
320 ->setName(pht('View As List'))
321 ->setHref($list_uri)
322 ->setIcon('fa-list'));
323
324 $history_uri = $drequest->generateURI(
325 array(
326 'action' => 'history',
327 ));
328
329 $view->addAction(
330 id(new PhabricatorActionView())
331 ->setName(pht('View History'))
332 ->setHref($history_uri)
333 ->setIcon('fa-clock-o'));
334
335 $browse_uri = $drequest->generateURI(
336 array(
337 'action' => 'browse',
338 ));
339
340 $view->addAction(
341 id(new PhabricatorActionView())
342 ->setName(pht('Browse Content'))
343 ->setHref($browse_uri)
344 ->setIcon('fa-files-o'));
345
346 return $view;
347 }
348
349 protected function buildPropertyView(
350 DiffusionRequest $drequest,
351 PhabricatorRepositoryBranch $branch,
352 $total,
353 PhabricatorActionListView $actions) {
354
355 $viewer = $this->getRequest()->getUser();
356
357 $view = id(new PHUIPropertyListView())
358 ->setUser($viewer)
359 ->setActionList($actions);
360
361 $lint_commit = $branch->getLintCommit();
362
363 $view->addProperty(
364 pht('Lint Commit'),
365 phutil_tag(
366 'a',
367 array(
368 'href' => $drequest->generateURI(
369 array(
370 'action' => 'commit',
371 'commit' => $lint_commit,
372 )),
373 ),
374 $drequest->getRepository()->formatCommitName($lint_commit)));
375
376 $view->addProperty(
377 pht('Total Messages'),
378 pht('%s', new PhutilNumber($total)));
379
380 return $view;
381 }
382
383
384 private function buildDetailsResponse() {
385 $request = $this->getRequest();
386
387 $limit = 500;
388
389 $pager = id(new PHUIPagerView())
390 ->readFromRequest($request)
391 ->setPageSize($limit);
392
393 $offset = $pager->getOffset();
394
395 $drequest = $this->getDiffusionRequest();
396 $branch = $drequest->loadBranch();
397 $messages = $this->loadLintMessages($branch, $limit, $offset);
398 $is_dir = (substr('/'.$drequest->getPath(), -1) == '/');
399
400 $pager->setHasMorePages(count($messages) >= $limit);
401
402 $authors = $this->loadViewerHandles(ipull($messages, 'authorPHID'));
403
404 $rows = array();
405 foreach ($messages as $message) {
406 $path = phutil_tag(
407 'a',
408 array(
409 'href' => $drequest->generateURI(array(
410 'action' => 'lint',
411 'path' => $message['path'],
412 )),
413 ),
414 substr($message['path'], strlen($drequest->getPath()) + 1));
415
416 $line = phutil_tag(
417 'a',
418 array(
419 'href' => $drequest->generateURI(array(
420 'action' => 'browse',
421 'path' => $message['path'],
422 'line' => $message['line'],
423 'commit' => $branch->getLintCommit(),
424 )),
425 ),
426 $message['line']);
427
428 $author = $message['authorPHID'];
429 if ($author && $authors[$author]) {
430 $author = $authors[$author]->renderLink();
431 }
432
433 $rows[] = array(
434 $path,
435 $line,
436 $author,
437 ArcanistLintSeverity::getStringForSeverity($message['severity']),
438 $message['name'],
439 $message['description'],
440 );
441 }
442
443 $table = id(new AphrontTableView($rows))
444 ->setHeaders(array(
445 pht('Path'),
446 pht('Line'),
447 pht('Author'),
448 pht('Severity'),
449 pht('Name'),
450 pht('Description'),
451 ))
452 ->setColumnClasses(array('', 'n'))
453 ->setColumnVisibility(array($is_dir));
454
455 $content = array();
456
457 $content[] = id(new PHUIObjectBoxView())
458 ->setHeaderText(pht('Lint Details'))
459 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
460 ->setTable($table)
461 ->setPager($pager);
462
463 $crumbs = $this->buildCrumbs(
464 array(
465 'branch' => true,
466 'path' => true,
467 'view' => 'lint',
468 ));
469 $crumbs->setBorder(true);
470
471 $header = id(new PHUIHeaderView())
472 ->setHeader(pht('Lint: %s', $drequest->getRepository()->getDisplayName()))
473 ->setHeaderIcon('fa-code');
474
475 $view = id(new PHUITwoColumnView())
476 ->setHeader($header)
477 ->setFooter(array(
478 $content,
479 ));
480
481 return $this->newPage()
482 ->setTitle(
483 array(
484 pht('Lint'),
485 $drequest->getRepository()->getDisplayName(),
486 ))
487 ->setCrumbs($crumbs)
488 ->appendChild(
489 array(
490 $view,
491 ));
492 }
493
494 private function loadLintMessages(
495 PhabricatorRepositoryBranch $branch,
496 $limit,
497 $offset) {
498
499 $drequest = $this->getDiffusionRequest();
500 if (!$branch) {
501 return array();
502 }
503
504 $conn = $branch->establishConnection('r');
505
506 $where = array(
507 qsprintf($conn, 'branchID = %d', $branch->getID()),
508 );
509
510 if ($drequest->getPath() != '') {
511 $path = '/'.$drequest->getPath();
512 $is_dir = (substr($path, -1) == '/');
513 $where[] = ($is_dir
514 ? qsprintf($conn, 'path LIKE %>', $path)
515 : qsprintf($conn, 'path = %s', $path));
516 }
517
518 if ($drequest->getLint() != '') {
519 $where[] = qsprintf(
520 $conn,
521 'code = %s',
522 $drequest->getLint());
523 }
524
525 return queryfx_all(
526 $conn,
527 'SELECT *
528 FROM %T
529 WHERE %LA
530 ORDER BY path, code, line LIMIT %d OFFSET %d',
531 PhabricatorRepository::TABLE_LINTMESSAGE,
532 $where,
533 $limit,
534 $offset);
535 }
536}