@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 DiffusionCommitGraphView
4 extends DiffusionView {
5
6 private $history;
7 private $commits;
8 private $isHead;
9 private $isTail;
10 private $parents;
11 private $filterParents;
12 private $noDataString;
13
14 private $commitMap;
15 private $buildableMap;
16 private $revisionMap;
17
18 private $showAuditors;
19
20 /**
21 * @param array<DiffusionPathChange> $history
22 */
23 public function setHistory(array $history) {
24 assert_instances_of($history, DiffusionPathChange::class);
25 $this->history = $history;
26 return $this;
27 }
28
29 public function getHistory() {
30 return $this->history;
31 }
32
33 /**
34 * @param array<PhabricatorRepositoryCommit> $commits
35 */
36 public function setCommits(array $commits) {
37 assert_instances_of($commits, PhabricatorRepositoryCommit::class);
38 $this->commits = $commits;
39 return $this;
40 }
41
42 public function getCommits() {
43 return $this->commits;
44 }
45
46 public function setShowAuditors($show_auditors) {
47 $this->showAuditors = $show_auditors;
48 return $this;
49 }
50
51 public function getShowAuditors() {
52 return $this->showAuditors;
53 }
54
55 public function setParents(array $parents) {
56 $this->parents = $parents;
57 return $this;
58 }
59
60 public function getParents() {
61 return $this->parents;
62 }
63
64 public function setIsHead($is_head) {
65 $this->isHead = $is_head;
66 return $this;
67 }
68
69 public function getIsHead() {
70 return $this->isHead;
71 }
72
73 public function setIsTail($is_tail) {
74 $this->isTail = $is_tail;
75 return $this;
76 }
77
78 public function getIsTail() {
79 return $this->isTail;
80 }
81
82 public function setFilterParents($filter_parents) {
83 $this->filterParents = $filter_parents;
84 return $this;
85 }
86
87 public function getFilterParents() {
88 return $this->filterParents;
89 }
90
91 public function setNoDataString($no_data_string) {
92 $this->noDataString = $no_data_string;
93 return $this;
94 }
95
96 private function getRepository() {
97 $drequest = $this->getDiffusionRequest();
98
99 if (!$drequest) {
100 return null;
101 }
102
103 return $drequest->getRepository();
104 }
105
106 public function newObjectItemListView() {
107 $list_view = new PHUIObjectItemListView();
108
109 $item_views = $this->newObjectItemViews();
110 foreach ($item_views as $item_view) {
111 $list_view->addItem($item_view);
112 }
113
114 return $list_view;
115 }
116
117 private function newObjectItemViews() {
118 $viewer = $this->getViewer();
119
120 require_celerity_resource('diffusion-css');
121
122 $show_builds = $this->shouldShowBuilds();
123 $show_revisions = $this->shouldShowRevisions();
124 $show_auditors = $this->shouldShowAuditors();
125
126 $phids = array();
127
128 if ($show_revisions) {
129 $revision_map = $this->getRevisionMap();
130 foreach ($revision_map as $revisions) {
131 foreach ($revisions as $revision) {
132 $phids[] = $revision->getPHID();
133 }
134 }
135 }
136
137 $commits = $this->getCommitMap();
138
139 foreach ($commits as $commit) {
140 $author_phid = $commit->getAuthorDisplayPHID();
141 if ($author_phid !== null) {
142 $phids[] = $author_phid;
143 }
144 }
145
146 if ($show_auditors) {
147 foreach ($commits as $commit) {
148 $audits = $commit->getAudits();
149 foreach ($audits as $auditor) {
150 $phids[] = $auditor->getAuditorPHID();
151 }
152 }
153 }
154
155 $handles = $viewer->loadHandles($phids);
156
157 $views = array();
158
159 $items = $this->newHistoryItems();
160 foreach ($items as $hash => $item) {
161 $content = array();
162
163 $commit = $item['commit'];
164
165 $commit_description = $this->getCommitDescription($commit);
166 $commit_link = $this->getCommitURI($hash);
167
168 $short_hash = $this->getCommitObjectName($hash);
169 $is_disabled = $this->getCommitIsDisabled($commit);
170
171 $item_view = id(new PHUIObjectItemView())
172 ->setViewer($viewer)
173 ->setHeader($commit_description)
174 ->setObjectName($short_hash)
175 ->setHref($commit_link)
176 ->setDisabled($is_disabled);
177
178 $this->addBrowseAction($item_view, $hash);
179
180 if ($show_builds) {
181 $this->addBuildAction($item_view, $hash);
182 }
183
184 // hide Audit entry on /diffusion/commit/query/all if Audit is disabled
185 if (id(new PhabricatorAuditApplication())->isInstalled()) {
186 $this->addAuditAction($item_view, $hash);
187 }
188
189 if ($show_auditors) {
190 $auditor_list = $item_view->newMapView();
191 if ($commit) {
192 $auditors = $this->newAuditorList($commit, $handles);
193 $auditor_list->newItem()
194 ->setName(pht('Auditors'))
195 ->setValue($auditors);
196 }
197 }
198
199 $property_list = $item_view->newMapView();
200
201 if ($commit) {
202 $author_view = $this->getCommitAuthorView($commit);
203 if ($author_view) {
204 $property_list->newItem()
205 ->setName(pht('Author'))
206 ->setValue($author_view);
207 }
208 }
209
210 if ($show_revisions) {
211 if ($commit) {
212 $revisions = $this->getRevisions($commit);
213 if ($revisions) {
214 $list_view = $handles->newSublist(mpull($revisions, 'getPHID'))
215 ->newListView();
216
217 $property_list->newItem()
218 ->setName(pht('Revisions'))
219 ->setValue($list_view);
220 }
221 }
222 }
223
224 $views[$hash] = $item_view;
225 }
226
227 return $views;
228 }
229
230 /**
231 * @return array<PHUIObjectItemListView>
232 */
233 private function newObjectItemRows() {
234 $viewer = $this->getViewer();
235
236 $items = $this->newHistoryItems();
237 $views = $this->newObjectItemViews();
238
239 $last_date = null;
240 $rows = array();
241 foreach ($items as $hash => $item) {
242 $item_epoch = $item['epoch'];
243 $item_date = phabricator_date($item_epoch, $viewer);
244 if ($item_date !== $last_date) {
245 $last_date = $item_date;
246 $header = $item_date;
247 } else {
248 $header = null;
249 }
250
251 $item_view = $views[$hash];
252
253 $list_view = id(new PHUIObjectItemListView())
254 ->setViewer($viewer)
255 ->setFlush(true)
256 ->addItem($item_view);
257
258 if ($header !== null) {
259 $list_view->setHeader($header);
260 }
261
262 $rows[] = $list_view;
263 }
264
265 return $rows;
266 }
267
268 public function render() {
269 $rows = $this->newObjectItemRows();
270
271 $graph = $this->newGraphView();
272 foreach ($rows as $idx => $row) {
273 $cells = array();
274
275 if ($graph) {
276 $cells[] = phutil_tag(
277 'td',
278 array(
279 'class' => 'diffusion-commit-graph-path-cell',
280 ),
281 $graph[$idx]);
282 }
283
284 $cells[] = phutil_tag(
285 'td',
286 array(
287 'class' => 'diffusion-commit-graph-content-cell',
288 ),
289 $row);
290
291 $rows[$idx] = phutil_tag('tr', array(), $cells);
292 }
293
294 if ($rows) {
295 $table = phutil_tag(
296 'table',
297 array(
298 'class' => 'diffusion-commit-graph-table',
299 ),
300 $rows);
301 } else {
302 $table = id(new PHUIObjectItemListView())
303 ->setNoDataString($this->noDataString);
304 }
305
306 return $table;
307 }
308
309 private function newGraphView() {
310 if (!$this->getParents()) {
311 return null;
312 }
313
314 $parents = $this->getParents();
315
316 // If we're filtering parents, remove relationships which point to
317 // commits that are not part of the visible graph. Otherwise, we get
318 // a big tree of nonsense when viewing release branches like "stable"
319 // versus "master".
320 if ($this->getFilterParents()) {
321 foreach ($parents as $key => $nodes) {
322 foreach ($nodes as $nkey => $node) {
323 if (empty($parents[$node])) {
324 unset($parents[$key][$nkey]);
325 }
326 }
327 }
328 }
329
330 return id(new PHUIDiffGraphView())
331 ->setIsHead($this->getIsHead())
332 ->setIsTail($this->getIsTail())
333 ->renderGraph($parents);
334 }
335
336 private function shouldShowBuilds() {
337 $viewer = $this->getViewer();
338
339 $show_builds = PhabricatorApplication::isClassInstalledForViewer(
340 PhabricatorHarbormasterApplication::class,
341 $this->getUser());
342
343 return $show_builds;
344 }
345
346 private function shouldShowRevisions() {
347 $viewer = $this->getViewer();
348
349 $show_revisions = PhabricatorApplication::isClassInstalledForViewer(
350 PhabricatorDifferentialApplication::class,
351 $viewer);
352
353 return $show_revisions;
354 }
355
356 private function shouldShowAuditors() {
357 return $this->getShowAuditors();
358 }
359
360 private function newHistoryItems() {
361 $items = array();
362
363 $history = $this->getHistory();
364 if ($history !== null) {
365 foreach ($history as $history_item) {
366 $commit_hash = $history_item->getCommitIdentifier();
367
368 $items[$commit_hash] = array(
369 'epoch' => $history_item->getEpoch(),
370 'hash' => $commit_hash,
371 'commit' => $this->getCommit($commit_hash),
372 );
373 }
374 } else {
375 $commits = $this->getCommitMap();
376 foreach ($commits as $commit) {
377 $commit_hash = $commit->getCommitIdentifier();
378
379 $items[$commit_hash] = array(
380 'epoch' => $commit->getEpoch(),
381 'hash' => $commit_hash,
382 'commit' => $commit,
383 );
384 }
385 }
386
387 return $items;
388 }
389
390 private function getCommitDescription($commit) {
391 if (!$commit) {
392 return phutil_tag('em', array(), pht("Discovering\xE2\x80\xA6"));
393 }
394
395 // We can show details once the message and change have been imported.
396 $partial_import = PhabricatorRepositoryCommit::IMPORTED_MESSAGE |
397 PhabricatorRepositoryCommit::IMPORTED_CHANGE;
398 if (!$commit->isPartiallyImported($partial_import)) {
399 return phutil_tag('em', array(), pht("Importing\xE2\x80\xA6"));
400 }
401
402 return $commit->getCommitData()->getSummary();
403 }
404
405 private function getCommitURI($hash) {
406 $repository = $this->getRepository();
407
408 if ($repository) {
409 return $repository->getCommitURI($hash);
410 }
411
412 $commit = $this->getCommit($hash);
413 if ($commit) {
414 return $commit->getURI();
415 }
416
417 return null;
418 }
419
420 private function getCommitObjectName($hash) {
421 $repository = $this->getRepository();
422
423 if ($repository) {
424 return $repository->formatCommitName(
425 $hash,
426 $is_local = true);
427 }
428
429 $commit = $this->getCommit($hash);
430 if ($commit) {
431 return $commit->getDisplayName();
432 }
433
434 return null;
435 }
436
437 private function getCommitIsDisabled($commit) {
438 if (!$commit) {
439 return true;
440 }
441
442 if ($commit->isUnreachable()) {
443 return true;
444 }
445
446 return false;
447 }
448
449 private function getCommitAuthorView($commit) {
450 if (!$commit) {
451 return null;
452 }
453
454 $viewer = $this->getViewer();
455
456 $author_phid = $commit->getAuthorDisplayPHID();
457 if ($author_phid) {
458 return $viewer->loadHandles(array($author_phid))
459 ->newListView();
460 }
461
462 return $commit->newCommitAuthorView($viewer);
463 }
464
465 private function getCommit($hash) {
466 $commit_map = $this->getCommitMap();
467 return idx($commit_map, $hash);
468 }
469
470 private function getCommitMap() {
471 if ($this->commitMap === null) {
472 $commit_list = $this->newCommitList();
473 $this->commitMap = mpull($commit_list, null, 'getCommitIdentifier');
474 }
475
476 return $this->commitMap;
477 }
478
479 private function addBrowseAction(PHUIObjectItemView $item, $hash) {
480 $repository = $this->getRepository();
481
482 if (!$repository) {
483 return;
484 }
485
486 $drequest = $this->getDiffusionRequest();
487 $path = $drequest->getPath();
488
489 $uri = $drequest->generateURI(
490 array(
491 'action' => 'browse',
492 'path' => $path,
493 'commit' => $hash,
494 ));
495
496 $menu_item = $item->newMenuItem()
497 ->setName(pht('Browse Repository'))
498 ->setURI($uri);
499
500 $menu_item->newIcon()
501 ->setIcon('fa-folder-open-o')
502 ->setColor('bluegrey');
503 }
504
505 private function addBuildAction(PHUIObjectItemView $item, $hash) {
506 $is_disabled = true;
507
508 $buildable = null;
509
510 $commit = $this->getCommit($hash);
511 if ($commit) {
512 $buildable = $this->getBuildable($commit);
513 }
514
515 if ($buildable) {
516 $icon = $buildable->getStatusIcon();
517 $color = $buildable->getStatusColor();
518 $name = $buildable->getStatusDisplayName();
519 $uri = $buildable->getURI();
520 } else {
521 $icon = 'fa-times';
522 $color = 'grey';
523 $name = pht('No Builds');
524 $uri = null;
525 }
526
527 $menu_item = $item->newMenuItem()
528 ->setName($name)
529 ->setURI($uri)
530 ->setDisabled(($uri === null));
531
532 $menu_item->newIcon()
533 ->setIcon($icon)
534 ->setColor($color);
535 }
536
537 private function addAuditAction(PHUIObjectItemView $item_view, $hash) {
538 $commit = $this->getCommit($hash);
539
540 if ($commit) {
541 $status = $commit->getAuditStatusObject();
542
543 $text = $status->getName();
544 $icon = $status->getIcon();
545
546 $is_disabled = $status->isNoAudit();
547 if ($is_disabled) {
548 $uri = null;
549 $color = 'grey';
550 } else {
551 $color = $status->getColor();
552 $uri = $commit->getURI();
553 }
554 } else {
555 $text = pht('No Audit');
556 $color = 'grey';
557 $icon = 'fa-times';
558 $uri = null;
559
560 $is_disabled = true;
561 }
562
563 $menu_item = $item_view->newMenuItem()
564 ->setName($text)
565 ->setURI($uri)
566 ->setBackgroundColor($color)
567 ->setDisabled($is_disabled);
568
569 $menu_item->newIcon()
570 ->setIcon($icon)
571 ->setColor($color);
572 }
573
574 private function getBuildable(PhabricatorRepositoryCommit $commit) {
575 $buildable_map = $this->getBuildableMap();
576 return idx($buildable_map, $commit->getPHID());
577 }
578
579 private function getBuildableMap() {
580 if ($this->buildableMap === null) {
581 $commits = $this->getCommitMap();
582 $buildables = $this->loadBuildables($commits);
583 $this->buildableMap = $buildables;
584 }
585
586 return $this->buildableMap;
587 }
588
589 private function getRevisions(PhabricatorRepositoryCommit $commit) {
590 $revision_map = $this->getRevisionMap();
591 return idx($revision_map, $commit->getPHID(), array());
592 }
593
594 private function getRevisionMap() {
595 if ($this->revisionMap === null) {
596 $this->revisionMap = $this->newRevisionMap();
597 }
598
599 return $this->revisionMap;
600 }
601
602 private function newRevisionMap() {
603 $viewer = $this->getViewer();
604 $commits = $this->getCommitMap();
605
606 return DiffusionCommitRevisionQuery::loadRevisionMapForCommits(
607 $viewer,
608 $commits);
609 }
610
611 private function newCommitList() {
612 $commits = $this->getCommits();
613 if ($commits !== null) {
614 return $commits;
615 }
616
617 $repository = $this->getRepository();
618 if (!$repository) {
619 return array();
620 }
621
622 $history = $this->getHistory();
623 if ($history === null) {
624 return array();
625 }
626
627 $identifiers = array();
628 foreach ($history as $item) {
629 $identifiers[] = $item->getCommitIdentifier();
630 }
631
632 if (!$identifiers) {
633 return array();
634 }
635
636 $viewer = $this->getViewer();
637
638 $commits = id(new DiffusionCommitQuery())
639 ->setViewer($viewer)
640 ->withRepository($repository)
641 ->withIdentifiers($identifiers)
642 ->needCommitData(true)
643 ->needIdentities(true)
644 ->execute();
645
646 return $commits;
647 }
648
649 private function newAuditorList(
650 PhabricatorRepositoryCommit $commit,
651 $handles) {
652
653 $auditors = $commit->getAudits();
654 if (!$auditors) {
655 return phutil_tag('em', array(), pht('None'));
656 }
657
658 $auditor_phids = mpull($auditors, 'getAuditorPHID');
659 $auditor_list = $handles->newSublist($auditor_phids)
660 ->newListView();
661
662 return $auditor_list;
663 }
664
665}