@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 DiffusionBlameController extends DiffusionController {
4
5 public function shouldAllowPublic() {
6 return true;
7 }
8
9 public function handleRequest(AphrontRequest $request) {
10 $response = $this->loadDiffusionContext();
11 if ($response) {
12 return $response;
13 }
14
15 $viewer = $this->getViewer();
16 $drequest = $this->getDiffusionRequest();
17 $repository = $drequest->getRepository();
18
19 $blame = $this->loadBlame();
20
21 $identifiers = array_fuse($blame);
22 if ($identifiers) {
23 $commits = id(new DiffusionCommitQuery())
24 ->setViewer($viewer)
25 ->withRepository($repository)
26 ->withIdentifiers($identifiers)
27 ->needIdentities(true)
28 // See PHI1014. If identities haven't been built yet, we may need to
29 // fall back to raw commit data.
30 ->needCommitData(true)
31 ->execute();
32 $commits = mpull($commits, null, 'getCommitIdentifier');
33 } else {
34 $commits = array();
35 }
36
37 $commit_map = mpull($commits, 'getCommitIdentifier', 'getPHID');
38
39 $revision_map = DiffusionCommitRevisionQuery::loadRevisionMapForCommits(
40 $viewer,
41 $commits);
42
43 $base_href = (string)$drequest->generateURI(
44 array(
45 'action' => 'browse',
46 'stable' => true,
47 ));
48
49 $skip_text = pht('Skip Past This Commit');
50 $skip_icon = id(new PHUIIconView())
51 ->setIcon('fa-backward');
52
53 Javelin::initBehavior('phabricator-tooltips');
54
55 $handle_phids = array();
56 foreach ($commits as $commit) {
57 $handle_phids[] = $commit->getAuthorDisplayPHID();
58 }
59
60 foreach ($revision_map as $revisions) {
61 foreach ($revisions as $revision) {
62 $handle_phids[] = $revision->getAuthorPHID();
63 }
64 }
65
66 $handles = $viewer->loadHandles($handle_phids);
67
68 $map = array();
69 $epochs = array();
70 foreach ($identifiers as $identifier) {
71 $skip_href = $base_href.'?before='.$identifier;
72
73 $skip_link = javelin_tag(
74 'a',
75 array(
76 'href' => $skip_href,
77 'sigil' => 'has-tooltip',
78 'meta' => array(
79 'tip' => $skip_text,
80 'align' => 'E',
81 'size' => 300,
82 ),
83 ),
84 $skip_icon);
85
86 // We may not have a commit object for a given identifier if the commit
87 // has not imported yet.
88
89 // At time of writing, this can also happen if a line was part of the
90 // initial import: blame produces a "^abc123" identifier in Git, which
91 // doesn't correspond to a real commit.
92
93 $commit = idx($commits, $identifier);
94
95 $revision = null;
96 if ($commit) {
97 $revisions = idx($revision_map, $commit->getPHID());
98
99 // There may be multiple edges between this commit and revisions in the
100 // database. If there are, just pick one arbitrarily.
101 if ($revisions) {
102 $revision = head($revisions);
103 }
104 }
105
106 $author_phid = null;
107
108 if ($commit) {
109 $author_phid = $commit->getAuthorDisplayPHID();
110 }
111
112 if (!$author_phid) {
113 // This means we couldn't identify an author for the commit or the
114 // revision. We just render a blank for alignment.
115 $author_style = null;
116 $author_href = null;
117 $author_sigil = null;
118 $author_meta = null;
119 } else {
120 $author_src = $handles[$author_phid]->getImageURI();
121 $author_style = 'background-image: url('.$author_src.');';
122 $author_href = $handles[$author_phid]->getURI();
123 $author_sigil = 'has-tooltip';
124 $author_meta = array(
125 'tip' => $handles[$author_phid]->getName(),
126 'align' => 'E',
127 'size' => 'auto',
128 );
129 }
130
131 $author_link = javelin_tag(
132 $author_href ? 'a' : 'span',
133 array(
134 'class' => 'phabricator-source-blame-author',
135 'style' => $author_style,
136 'href' => $author_href,
137 'sigil' => $author_sigil,
138 'meta' => $author_meta,
139 ));
140
141 if ($commit) {
142 $commit_link = javelin_tag(
143 'a',
144 array(
145 'href' => $commit->getURI(),
146 'sigil' => 'has-tooltip',
147 'meta' => array(
148 'tip' => $this->renderCommitTooltip($commit, $handles),
149 'align' => 'E',
150 'size' => 600,
151 ),
152 ),
153 $commit->getLocalName());
154 } else {
155 $commit_link = null;
156 }
157
158 $info = array(
159 $author_link,
160 $commit_link,
161 );
162
163 if ($revision) {
164 $revision_link = javelin_tag(
165 'a',
166 array(
167 'href' => $revision->getURI(),
168 'sigil' => 'has-tooltip',
169 'meta' => array(
170 'tip' => $this->renderRevisionTooltip($revision, $handles),
171 'align' => 'E',
172 'size' => 600,
173 ),
174 ),
175 $revision->getMonogram());
176
177 $info = array(
178 $info,
179 " \xC2\xB7 ",
180 $revision_link,
181 );
182 }
183
184 if ($commit) {
185 $epoch = $commit->getEpoch();
186 } else {
187 $epoch = 0;
188 }
189
190 $epochs[] = $epoch;
191
192 $data = array(
193 'skip' => $skip_link,
194 'info' => hsprintf('%s', $info),
195 'epoch' => $epoch,
196 );
197
198 $map[$identifier] = $data;
199 }
200
201 if (empty($epochs)) {
202 $epochs[] = 0;
203 }
204
205 $epoch_min = min($epochs);
206 $epoch_max = max($epochs);
207
208 return id(new AphrontAjaxResponse())->setContent(
209 array(
210 'blame' => $blame,
211 'map' => $map,
212 'epoch' => array(
213 'min' => $epoch_min,
214 'max' => $epoch_max,
215 ),
216 ));
217 }
218
219 private function loadBlame() {
220 $drequest = $this->getDiffusionRequest();
221
222 $commit = $drequest->getCommit();
223 $path = $drequest->getPath();
224
225 $blame_timeout = 15;
226
227 $blame = $this->callConduitWithDiffusionRequest(
228 'diffusion.blame',
229 array(
230 'commit' => $commit,
231 'paths' => array($path),
232 'timeout' => $blame_timeout,
233 ));
234
235 return idx($blame, $path, array());
236 }
237
238 private function renderRevisionTooltip(
239 DifferentialRevision $revision,
240 $handles) {
241 $viewer = $this->getViewer();
242
243 $date = phabricator_date($revision->getDateModified(), $viewer);
244 $monogram = $revision->getMonogram();
245 $title = $revision->getTitle();
246 $header = "{$monogram} {$title}";
247
248 $author = $handles[$revision->getAuthorPHID()]->getName();
249
250 return "{$header}\n{$date} \xC2\xB7 {$author}";
251 }
252
253 private function renderCommitTooltip(
254 PhabricatorRepositoryCommit $commit,
255 $handles) {
256
257 $viewer = $this->getViewer();
258
259 $date = phabricator_date($commit->getEpoch(), $viewer);
260 $summary = trim($commit->getSummary());
261
262 $author_phid = $commit->getAuthorPHID();
263 if ($author_phid && isset($handles[$author_phid])) {
264 $author_name = $handles[$author_phid]->getName();
265 } else {
266 $author_name = null;
267 }
268
269 if ($author_name) {
270 return "{$summary}\n{$date} \xC2\xB7 {$author_name}";
271 } else {
272 return "{$summary}\n{$date}";
273 }
274 }
275
276}