Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/differential/controller/DifferentialChangesetViewController.php
12256 views
1
<?php
2
3
final class DifferentialChangesetViewController extends DifferentialController {
4
5
public function shouldAllowPublic() {
6
return true;
7
}
8
9
public function handleRequest(AphrontRequest $request) {
10
$viewer = $this->getViewer();
11
12
$rendering_reference = $request->getStr('ref');
13
$parts = explode('/', $rendering_reference);
14
if (count($parts) == 2) {
15
list($id, $vs) = $parts;
16
} else {
17
$id = $parts[0];
18
$vs = 0;
19
}
20
21
$id = (int)$id;
22
$vs = (int)$vs;
23
24
$load_ids = array($id);
25
if ($vs && ($vs != -1)) {
26
$load_ids[] = $vs;
27
}
28
29
$changesets = id(new DifferentialChangesetQuery())
30
->setViewer($viewer)
31
->withIDs($load_ids)
32
->needHunks(true)
33
->execute();
34
$changesets = mpull($changesets, null, 'getID');
35
36
$changeset = idx($changesets, $id);
37
if (!$changeset) {
38
return new Aphront404Response();
39
}
40
41
$vs_changeset = null;
42
if ($vs && ($vs != -1)) {
43
$vs_changeset = idx($changesets, $vs);
44
if (!$vs_changeset) {
45
return new Aphront404Response();
46
}
47
}
48
49
$view = $request->getStr('view');
50
if ($view) {
51
$phid = idx($changeset->getMetadata(), "$view:binary-phid");
52
if ($phid) {
53
return id(new AphrontRedirectResponse())->setURI("/file/info/$phid/");
54
}
55
switch ($view) {
56
case 'new':
57
return $this->buildRawFileResponse($changeset, $is_new = true);
58
case 'old':
59
if ($vs_changeset) {
60
return $this->buildRawFileResponse($vs_changeset, $is_new = true);
61
}
62
return $this->buildRawFileResponse($changeset, $is_new = false);
63
default:
64
return new Aphront400Response();
65
}
66
}
67
68
$old = array();
69
$new = array();
70
if (!$vs) {
71
$right = $changeset;
72
$left = null;
73
74
$right_source = $right->getID();
75
$right_new = true;
76
$left_source = $right->getID();
77
$left_new = false;
78
79
$render_cache_key = $right->getID();
80
81
$old[] = $changeset;
82
$new[] = $changeset;
83
} else if ($vs == -1) {
84
$right = null;
85
$left = $changeset;
86
87
$right_source = $left->getID();
88
$right_new = false;
89
$left_source = $left->getID();
90
$left_new = true;
91
92
$render_cache_key = null;
93
94
$old[] = $changeset;
95
$new[] = $changeset;
96
} else {
97
$right = $changeset;
98
$left = $vs_changeset;
99
100
$right_source = $right->getID();
101
$right_new = true;
102
$left_source = $left->getID();
103
$left_new = true;
104
105
$render_cache_key = null;
106
107
$new[] = $left;
108
$new[] = $right;
109
}
110
111
if ($left) {
112
$changeset = $left->newComparisonChangeset($right);
113
}
114
115
if ($left_new || $right_new) {
116
$diff_map = array();
117
if ($left) {
118
$diff_map[] = $left->getDiff();
119
}
120
if ($right) {
121
$diff_map[] = $right->getDiff();
122
}
123
$diff_map = mpull($diff_map, null, 'getPHID');
124
125
$buildables = id(new HarbormasterBuildableQuery())
126
->setViewer($viewer)
127
->withBuildablePHIDs(array_keys($diff_map))
128
->withManualBuildables(false)
129
->needBuilds(true)
130
->needTargets(true)
131
->execute();
132
$buildables = mpull($buildables, null, 'getBuildablePHID');
133
foreach ($diff_map as $diff_phid => $changeset_diff) {
134
$changeset_diff->attachBuildable(idx($buildables, $diff_phid));
135
}
136
}
137
138
$coverage = null;
139
if ($right_new) {
140
$coverage = $this->loadCoverage($right);
141
}
142
143
$spec = $request->getStr('range');
144
list($range_s, $range_e, $mask) =
145
DifferentialChangesetParser::parseRangeSpecification($spec);
146
147
$diff = $changeset->getDiff();
148
$revision_id = $diff->getRevisionID();
149
150
$can_mark = false;
151
$object_owner_phid = null;
152
$revision = null;
153
if ($revision_id) {
154
$revision = id(new DifferentialRevisionQuery())
155
->setViewer($viewer)
156
->withIDs(array($revision_id))
157
->executeOne();
158
if ($revision) {
159
$can_mark = ($revision->getAuthorPHID() == $viewer->getPHID());
160
$object_owner_phid = $revision->getAuthorPHID();
161
}
162
}
163
164
if ($revision) {
165
$container_phid = $revision->getPHID();
166
} else {
167
$container_phid = $diff->getPHID();
168
}
169
170
$viewstate_engine = id(new PhabricatorChangesetViewStateEngine())
171
->setViewer($viewer)
172
->setObjectPHID($container_phid)
173
->setChangeset($changeset);
174
175
$viewstate = $viewstate_engine->newViewStateFromRequest($request);
176
177
if ($viewstate->getDiscardResponse()) {
178
return new AphrontAjaxResponse();
179
}
180
181
$parser = id(new DifferentialChangesetParser())
182
->setViewer($viewer)
183
->setViewState($viewstate)
184
->setCoverage($coverage)
185
->setChangeset($changeset)
186
->setRenderingReference($rendering_reference)
187
->setRenderCacheKey($render_cache_key)
188
->setRightSideCommentMapping($right_source, $right_new)
189
->setLeftSideCommentMapping($left_source, $left_new);
190
191
if ($left && $right) {
192
$parser->setOriginals($left, $right);
193
}
194
195
// Load both left-side and right-side inline comments.
196
if ($revision) {
197
$inlines = id(new DifferentialDiffInlineCommentQuery())
198
->setViewer($viewer)
199
->withRevisionPHIDs(array($revision->getPHID()))
200
->withPublishableComments(true)
201
->withPublishedComments(true)
202
->needHidden(true)
203
->needInlineContext(true)
204
->execute();
205
206
$inlines = mpull($inlines, 'newInlineCommentObject');
207
208
$inlines = id(new PhabricatorInlineCommentAdjustmentEngine())
209
->setViewer($viewer)
210
->setRevision($revision)
211
->setOldChangesets($old)
212
->setNewChangesets($new)
213
->setInlines($inlines)
214
->execute();
215
} else {
216
$inlines = array();
217
}
218
219
if ($left_new) {
220
$inlines = array_merge(
221
$inlines,
222
$this->buildLintInlineComments($left));
223
}
224
225
if ($right_new) {
226
$inlines = array_merge(
227
$inlines,
228
$this->buildLintInlineComments($right));
229
}
230
231
$phids = array();
232
foreach ($inlines as $inline) {
233
$parser->parseInlineComment($inline);
234
if ($inline->getAuthorPHID()) {
235
$phids[$inline->getAuthorPHID()] = true;
236
}
237
}
238
$phids = array_keys($phids);
239
240
$handles = $this->loadViewerHandles($phids);
241
$parser->setHandles($handles);
242
243
$engine = new PhabricatorMarkupEngine();
244
$engine->setViewer($viewer);
245
246
foreach ($inlines as $inline) {
247
$engine->addObject(
248
$inline,
249
PhabricatorInlineComment::MARKUP_FIELD_BODY);
250
}
251
252
$engine->process();
253
254
$parser
255
->setViewer($viewer)
256
->setMarkupEngine($engine)
257
->setShowEditAndReplyLinks(true)
258
->setCanMarkDone($can_mark)
259
->setObjectOwnerPHID($object_owner_phid)
260
->setRange($range_s, $range_e)
261
->setMask($mask);
262
263
if ($request->isAjax()) {
264
// NOTE: We must render the changeset before we render coverage
265
// information, since it builds some caches.
266
$response = $parser->newChangesetResponse();
267
268
$mcov = $parser->renderModifiedCoverage();
269
270
$coverage_data = array(
271
'differential-mcoverage-'.md5($changeset->getFilename()) => $mcov,
272
);
273
274
$response->setCoverage($coverage_data);
275
276
return $response;
277
}
278
279
$detail = id(new DifferentialChangesetListView())
280
->setUser($this->getViewer())
281
->setChangesets(array($changeset))
282
->setVisibleChangesets(array($changeset))
283
->setRenderingReferences(array($rendering_reference))
284
->setRenderURI('/differential/changeset/')
285
->setDiff($diff)
286
->setTitle(pht('Standalone View'))
287
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
288
->setIsStandalone(true)
289
->setParser($parser);
290
291
if ($revision_id) {
292
$detail->setInlineCommentControllerURI(
293
'/differential/comment/inline/edit/'.$revision_id.'/');
294
}
295
296
$crumbs = $this->buildApplicationCrumbs();
297
298
if ($revision_id) {
299
$crumbs->addTextCrumb('D'.$revision_id, '/D'.$revision_id);
300
}
301
302
$diff_id = $diff->getID();
303
if ($diff_id) {
304
$crumbs->addTextCrumb(
305
pht('Diff %d', $diff_id),
306
$this->getApplicationURI('diff/'.$diff_id));
307
}
308
309
$crumbs->addTextCrumb($changeset->getDisplayFilename());
310
$crumbs->setBorder(true);
311
312
$header = id(new PHUIHeaderView())
313
->setHeader(pht('Changeset View'))
314
->setHeaderIcon('fa-gear');
315
316
$view = id(new PHUITwoColumnView())
317
->setHeader($header)
318
->setFooter($detail);
319
320
return $this->newPage()
321
->setTitle(pht('Changeset View'))
322
->setCrumbs($crumbs)
323
->appendChild($view);
324
}
325
326
private function buildRawFileResponse(
327
DifferentialChangeset $changeset,
328
$is_new) {
329
330
$viewer = $this->getViewer();
331
332
if ($is_new) {
333
$key = 'raw:new:phid';
334
} else {
335
$key = 'raw:old:phid';
336
}
337
338
$metadata = $changeset->getMetadata();
339
340
$file = null;
341
$phid = idx($metadata, $key);
342
if ($phid) {
343
$file = id(new PhabricatorFileQuery())
344
->setViewer($viewer)
345
->withPHIDs(array($phid))
346
->execute();
347
if ($file) {
348
$file = head($file);
349
}
350
}
351
352
if (!$file) {
353
// This is just building a cache of the changeset content in the file
354
// tool, and is safe to run on a read pathway.
355
$unguard = AphrontWriteGuard::beginScopedUnguardedWrites();
356
357
if ($is_new) {
358
$data = $changeset->makeNewFile();
359
} else {
360
$data = $changeset->makeOldFile();
361
}
362
363
$diff = $changeset->getDiff();
364
365
$file = PhabricatorFile::newFromFileData(
366
$data,
367
array(
368
'name' => $changeset->getFilename(),
369
'mime-type' => 'text/plain',
370
'ttl.relative' => phutil_units('24 hours in seconds'),
371
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
372
));
373
374
$file->attachToObject($diff->getPHID());
375
376
$metadata[$key] = $file->getPHID();
377
$changeset->setMetadata($metadata);
378
$changeset->save();
379
380
unset($unguard);
381
}
382
383
return $file->getRedirectResponse();
384
}
385
386
private function buildLintInlineComments($changeset) {
387
$diff = $changeset->getDiff();
388
389
$target_phids = $diff->getBuildTargetPHIDs();
390
if (!$target_phids) {
391
return array();
392
}
393
394
$messages = id(new HarbormasterBuildLintMessage())->loadAllWhere(
395
'buildTargetPHID IN (%Ls) AND path = %s',
396
$target_phids,
397
$changeset->getFilename());
398
399
if (!$messages) {
400
return array();
401
}
402
403
$change_type = $changeset->getChangeType();
404
if (DifferentialChangeType::isDeleteChangeType($change_type)) {
405
// If this is a lint message on a deleted file, show it on the left
406
// side of the UI because there are no source code lines on the right
407
// side of the UI so inlines don't have anywhere to render. See PHI416.
408
$is_new = 0;
409
} else {
410
$is_new = 1;
411
}
412
413
$template = id(new DifferentialInlineComment())
414
->setChangesetID($changeset->getID())
415
->setIsNewFile($is_new)
416
->setLineLength(0);
417
418
$inlines = array();
419
foreach ($messages as $message) {
420
$description = $message->getProperty('description');
421
422
$inlines[] = id(clone $template)
423
->setSyntheticAuthor(pht('Lint: %s', $message->getName()))
424
->setLineNumber($message->getLine())
425
->setContent($description);
426
}
427
428
return $inlines;
429
}
430
431
private function loadCoverage(DifferentialChangeset $changeset) {
432
$viewer = $this->getViewer();
433
434
$target_phids = $changeset->getDiff()->getBuildTargetPHIDs();
435
if (!$target_phids) {
436
return null;
437
}
438
439
$unit = id(new HarbormasterBuildUnitMessageQuery())
440
->setViewer($viewer)
441
->withBuildTargetPHIDs($target_phids)
442
->execute();
443
if (!$unit) {
444
return null;
445
}
446
447
$coverage = array();
448
foreach ($unit as $message) {
449
$test_coverage = $message->getProperty('coverage');
450
if ($test_coverage === null) {
451
continue;
452
}
453
$coverage_data = idx($test_coverage, $changeset->getFileName());
454
if (!strlen($coverage_data)) {
455
continue;
456
}
457
$coverage[] = $coverage_data;
458
}
459
460
if (!$coverage) {
461
return null;
462
}
463
464
return ArcanistUnitTestResult::mergeCoverage($coverage);
465
}
466
467
}
468
469