Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
freebsd
GitHub Repository: freebsd/phabricator
Path: blob/master/src/applications/diffusion/controller/DiffusionCommitController.php
12242 views
1
<?php
2
3
final class DiffusionCommitController extends DiffusionController {
4
5
const CHANGES_LIMIT = 100;
6
7
private $commitParents;
8
private $commitRefs;
9
private $commitMerges;
10
private $commitErrors;
11
private $commitExists;
12
13
public function shouldAllowPublic() {
14
return true;
15
}
16
17
public function handleRequest(AphrontRequest $request) {
18
$response = $this->loadDiffusionContext();
19
if ($response) {
20
return $response;
21
}
22
23
$drequest = $this->getDiffusionRequest();
24
$viewer = $request->getUser();
25
$repository = $drequest->getRepository();
26
$commit_identifier = $drequest->getCommit();
27
28
// If this page is being accessed via "/source/xyz/commit/...", redirect
29
// to the canonical URI.
30
$repo_callsign = $request->getURIData('repositoryCallsign');
31
$has_callsign = $repo_callsign !== null && strlen($repo_callsign);
32
$repo_id = $request->getURIData('repositoryID');
33
$has_id = $repo_id !== null && strlen($repo_id);
34
35
if (!$has_callsign && !$has_id) {
36
$canonical_uri = $repository->getCommitURI($commit_identifier);
37
return id(new AphrontRedirectResponse())
38
->setURI($canonical_uri);
39
}
40
41
if ($request->getStr('diff')) {
42
return $this->buildRawDiffResponse($drequest);
43
}
44
45
$commits = id(new DiffusionCommitQuery())
46
->setViewer($viewer)
47
->withRepository($repository)
48
->withIdentifiers(array($commit_identifier))
49
->needCommitData(true)
50
->needAuditRequests(true)
51
->needAuditAuthority(array($viewer))
52
->setLimit(100)
53
->needIdentities(true)
54
->execute();
55
56
$multiple_results = count($commits) > 1;
57
58
$crumbs = $this->buildCrumbs(array(
59
'commit' => !$multiple_results,
60
));
61
$crumbs->setBorder(true);
62
63
if (!$commits) {
64
if (!$this->getCommitExists()) {
65
return new Aphront404Response();
66
}
67
68
$error = id(new PHUIInfoView())
69
->setTitle(pht('Commit Still Parsing'))
70
->appendChild(
71
pht(
72
'Failed to load the commit because the commit has not been '.
73
'parsed yet.'));
74
75
$title = pht('Commit Still Parsing');
76
77
return $this->newPage()
78
->setTitle($title)
79
->setCrumbs($crumbs)
80
->appendChild($error);
81
} else if ($multiple_results) {
82
83
$warning_message =
84
pht(
85
'The identifier %s is ambiguous and matches more than one commit.',
86
phutil_tag(
87
'strong',
88
array(),
89
$commit_identifier));
90
91
$error = id(new PHUIInfoView())
92
->setTitle(pht('Ambiguous Commit'))
93
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
94
->appendChild($warning_message);
95
96
$list = id(new DiffusionCommitGraphView())
97
->setViewer($viewer)
98
->setCommits($commits);
99
100
$crumbs->addTextCrumb(pht('Ambiguous Commit'));
101
102
$matched_commits = id(new PHUITwoColumnView())
103
->setFooter(array(
104
$error,
105
$list,
106
));
107
108
return $this->newPage()
109
->setTitle(pht('Ambiguous Commit'))
110
->setCrumbs($crumbs)
111
->appendChild($matched_commits);
112
} else {
113
$commit = head($commits);
114
}
115
116
$audit_requests = $commit->getAudits();
117
118
$commit_data = $commit->getCommitData();
119
$is_foreign = $commit_data->getCommitDetail('foreign-svn-stub');
120
$error_panel = null;
121
$unpublished_panel = null;
122
123
$hard_limit = 2000;
124
125
if ($commit->isImported()) {
126
$change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
127
$drequest);
128
$change_query->setLimit($hard_limit + 1);
129
$changes = $change_query->loadChanges();
130
} else {
131
$changes = array();
132
}
133
134
$was_limited = (count($changes) > $hard_limit);
135
if ($was_limited) {
136
$changes = array_slice($changes, 0, $hard_limit);
137
}
138
139
$count = count($changes);
140
141
$is_unreadable = false;
142
$hint = null;
143
if (!$count || $commit->isUnreachable()) {
144
$hint = id(new DiffusionCommitHintQuery())
145
->setViewer($viewer)
146
->withRepositoryPHIDs(array($repository->getPHID()))
147
->withOldCommitIdentifiers(array($commit->getCommitIdentifier()))
148
->executeOne();
149
if ($hint) {
150
$is_unreadable = $hint->isUnreadable();
151
}
152
}
153
154
if ($is_foreign) {
155
$subpath = $commit_data->getCommitDetail('svn-subpath');
156
157
$error_panel = new PHUIInfoView();
158
$error_panel->setTitle(pht('Commit Not Tracked'));
159
$error_panel->setSeverity(PHUIInfoView::SEVERITY_WARNING);
160
$error_panel->appendChild(
161
pht(
162
"This Diffusion repository is configured to track only one ".
163
"subdirectory of the entire Subversion repository, and this commit ".
164
"didn't affect the tracked subdirectory ('%s'), so no ".
165
"information is available.",
166
$subpath));
167
} else {
168
$engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
169
$engine->setConfig('viewer', $viewer);
170
171
$commit_tag = $this->renderCommitHashTag($drequest);
172
$header = id(new PHUIHeaderView())
173
->setHeader(nonempty($commit->getSummary(), pht('Commit Detail')))
174
->setHeaderIcon('fa-code-fork')
175
->addTag($commit_tag);
176
177
if (!$commit->isAuditStatusNoAudit()) {
178
$status = $commit->getAuditStatusObject();
179
180
$icon = $status->getIcon();
181
$color = $status->getColor();
182
$status = $status->getName();
183
184
$header->setStatus($icon, $color, $status);
185
}
186
187
$curtain = $this->buildCurtain($commit, $repository);
188
$details = $this->buildPropertyListView(
189
$commit,
190
$commit_data,
191
$audit_requests);
192
193
$message = $commit_data->getCommitMessage();
194
195
$revision = $commit->getCommitIdentifier();
196
$message = $this->linkBugtraq($message);
197
$message = $engine->markupText($message);
198
199
$detail_list = new PHUIPropertyListView();
200
$detail_list->addTextContent(
201
phutil_tag(
202
'div',
203
array(
204
'class' => 'diffusion-commit-message phabricator-remarkup',
205
),
206
$message));
207
208
if ($commit->isUnreachable()) {
209
$did_rewrite = false;
210
if ($hint) {
211
if ($hint->isRewritten()) {
212
$rewritten = id(new DiffusionCommitQuery())
213
->setViewer($viewer)
214
->withRepository($repository)
215
->withIdentifiers(array($hint->getNewCommitIdentifier()))
216
->executeOne();
217
if ($rewritten) {
218
$did_rewrite = true;
219
$rewritten_uri = $rewritten->getURI();
220
$rewritten_name = $rewritten->getLocalName();
221
222
$rewritten_link = phutil_tag(
223
'a',
224
array(
225
'href' => $rewritten_uri,
226
),
227
$rewritten_name);
228
229
$this->commitErrors[] = pht(
230
'This commit was rewritten after it was published, which '.
231
'changed the commit hash. This old version of the commit is '.
232
'no longer reachable from any branch, tag or ref. The new '.
233
'version of this commit is %s.',
234
$rewritten_link);
235
}
236
}
237
}
238
239
if (!$did_rewrite) {
240
$this->commitErrors[] = pht(
241
'This commit has been deleted in the repository: it is no longer '.
242
'reachable from any branch, tag, or ref.');
243
}
244
}
245
if (!$commit->isPermanentCommit()) {
246
$nonpermanent_tag = id(new PHUITagView())
247
->setType(PHUITagView::TYPE_SHADE)
248
->setName(pht('Unpublished'))
249
->setColor(PHUITagView::COLOR_ORANGE);
250
251
$header->addTag($nonpermanent_tag);
252
253
$holds = $commit_data->newPublisherHoldReasons();
254
255
$reasons = array();
256
foreach ($holds as $hold) {
257
$reasons[] = array(
258
phutil_tag('strong', array(), pht('%s:', $hold->getName())),
259
' ',
260
$hold->getSummary(),
261
);
262
}
263
264
if (!$holds) {
265
$reasons[] = pht('No further details are available.');
266
}
267
268
$doc_href = PhabricatorEnv::getDoclink(
269
'Diffusion User Guide: Permanent Refs');
270
$doc_link = phutil_tag(
271
'a',
272
array(
273
'href' => $doc_href,
274
'target' => '_blank',
275
),
276
pht('Learn More'));
277
278
$title = array(
279
pht('Unpublished Commit'),
280
pht(" \xC2\xB7 "),
281
$doc_link,
282
);
283
284
$unpublished_panel = id(new PHUIInfoView())
285
->setTitle($title)
286
->setErrors($reasons)
287
->setSeverity(PHUIInfoView::SEVERITY_WARNING);
288
}
289
290
291
if ($this->getCommitErrors()) {
292
$error_panel = id(new PHUIInfoView())
293
->appendChild($this->getCommitErrors())
294
->setSeverity(PHUIInfoView::SEVERITY_WARNING);
295
}
296
}
297
298
$timeline = $this->buildComments($commit);
299
$merge_table = $this->buildMergesTable($commit);
300
301
$show_changesets = false;
302
$info_panel = null;
303
$change_list = null;
304
$change_table = null;
305
if ($is_unreadable) {
306
$info_panel = $this->renderStatusMessage(
307
pht('Unreadable Commit'),
308
pht(
309
'This commit has been marked as unreadable by an administrator. '.
310
'It may have been corrupted or created improperly by an external '.
311
'tool.'));
312
} else if ($is_foreign) {
313
// Don't render anything else.
314
} else if (!$commit->isImported()) {
315
$info_panel = $this->renderStatusMessage(
316
pht('Still Importing...'),
317
pht(
318
'This commit is still importing. Changes will be visible once '.
319
'the import finishes.'));
320
} else if (!count($changes)) {
321
$info_panel = $this->renderStatusMessage(
322
pht('Empty Commit'),
323
pht(
324
'This commit is empty and does not affect any paths.'));
325
} else if ($was_limited) {
326
$info_panel = $this->renderStatusMessage(
327
pht('Very Large Commit'),
328
pht(
329
'This commit is very large, and affects more than %d files. '.
330
'Changes are not shown.',
331
$hard_limit));
332
} else if (!$this->getCommitExists()) {
333
$info_panel = $this->renderStatusMessage(
334
pht('Commit No Longer Exists'),
335
pht('This commit no longer exists in the repository.'));
336
} else {
337
$show_changesets = true;
338
339
// The user has clicked "Show All Changes", and we should show all the
340
// changes inline even if there are more than the soft limit.
341
$show_all_details = $request->getBool('show_all');
342
343
$change_header = id(new PHUIHeaderView())
344
->setHeader(pht('Changes (%s)', new PhutilNumber($count)));
345
346
$warning_view = null;
347
if ($count > self::CHANGES_LIMIT && !$show_all_details) {
348
$button = id(new PHUIButtonView())
349
->setText(pht('Show All Changes'))
350
->setHref('?show_all=true')
351
->setTag('a')
352
->setIcon('fa-files-o');
353
354
$warning_view = id(new PHUIInfoView())
355
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
356
->setTitle(pht('Very Large Commit'))
357
->appendChild(
358
pht('This commit is very large. Load each file individually.'));
359
360
$change_header->addActionLink($button);
361
}
362
363
$changesets = DiffusionPathChange::convertToDifferentialChangesets(
364
$viewer,
365
$changes);
366
367
// TODO: This table and panel shouldn't really be separate, but we need
368
// to clean up the "Load All Files" interaction first.
369
$change_table = $this->buildTableOfContents(
370
$changesets,
371
$change_header,
372
$warning_view);
373
374
$vcs = $repository->getVersionControlSystem();
375
switch ($vcs) {
376
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
377
$vcs_supports_directory_changes = true;
378
break;
379
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
380
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
381
$vcs_supports_directory_changes = false;
382
break;
383
default:
384
throw new Exception(pht('Unknown VCS.'));
385
}
386
387
$references = array();
388
foreach ($changesets as $key => $changeset) {
389
$file_type = $changeset->getFileType();
390
if ($file_type == DifferentialChangeType::FILE_DIRECTORY) {
391
if (!$vcs_supports_directory_changes) {
392
unset($changesets[$key]);
393
continue;
394
}
395
}
396
397
$references[$key] = $drequest->generateURI(
398
array(
399
'action' => 'rendering-ref',
400
'path' => $changeset->getFilename(),
401
));
402
}
403
404
// TODO: Some parts of the views still rely on properties of the
405
// DifferentialChangeset. Make the objects ephemeral to make sure we don't
406
// accidentally save them, and then set their ID to the appropriate ID for
407
// this application (the path IDs).
408
$path_ids = array_flip(mpull($changes, 'getPath'));
409
foreach ($changesets as $changeset) {
410
$changeset->makeEphemeral();
411
$changeset->setID($path_ids[$changeset->getFilename()]);
412
}
413
414
if ($count <= self::CHANGES_LIMIT || $show_all_details) {
415
$visible_changesets = $changesets;
416
} else {
417
$visible_changesets = array();
418
419
$inlines = id(new DiffusionDiffInlineCommentQuery())
420
->setViewer($viewer)
421
->withCommitPHIDs(array($commit->getPHID()))
422
->withPublishedComments(true)
423
->withPublishableComments(true)
424
->execute();
425
$inlines = mpull($inlines, 'newInlineCommentObject');
426
427
$path_ids = mpull($inlines, null, 'getPathID');
428
foreach ($changesets as $key => $changeset) {
429
if (array_key_exists($changeset->getID(), $path_ids)) {
430
$visible_changesets[$key] = $changeset;
431
}
432
}
433
}
434
435
$change_list_title = $commit->getDisplayName();
436
437
$change_list = new DifferentialChangesetListView();
438
$change_list->setTitle($change_list_title);
439
$change_list->setChangesets($changesets);
440
$change_list->setVisibleChangesets($visible_changesets);
441
$change_list->setRenderingReferences($references);
442
$change_list->setRenderURI($repository->getPathURI('diff/'));
443
$change_list->setRepository($repository);
444
$change_list->setUser($viewer);
445
$change_list->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
446
447
// TODO: Try to setBranch() to something reasonable here?
448
449
$change_list->setStandaloneURI(
450
$repository->getPathURI('diff/'));
451
452
$change_list->setRawFileURIs(
453
// TODO: Implement this, somewhat tricky if there's an octopus merge
454
// or whatever?
455
null,
456
$repository->getPathURI('diff/?view=r'));
457
458
$change_list->setInlineCommentControllerURI(
459
'/diffusion/inline/edit/'.phutil_escape_uri($commit->getPHID()).'/');
460
461
}
462
463
$add_comment = $this->renderAddCommentPanel(
464
$commit,
465
$timeline);
466
467
$filetree = id(new DifferentialFileTreeEngine())
468
->setViewer($viewer)
469
->setDisabled(!$show_changesets);
470
471
if ($show_changesets) {
472
$filetree->setChangesets($changesets);
473
}
474
475
$description_box = id(new PHUIObjectBoxView())
476
->setHeaderText(pht('Description'))
477
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
478
->appendChild($detail_list);
479
480
$detail_box = id(new PHUIObjectBoxView())
481
->setHeaderText(pht('Details'))
482
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
483
->appendChild($details);
484
485
$view = id(new PHUITwoColumnView())
486
->setHeader($header)
487
->setCurtain($curtain)
488
->setMainColumn(
489
array(
490
$unpublished_panel,
491
$error_panel,
492
$description_box,
493
$detail_box,
494
$timeline,
495
$merge_table,
496
$info_panel,
497
))
498
->setFooter(
499
array(
500
$change_table,
501
$change_list,
502
$add_comment,
503
));
504
505
$main_content = array(
506
$crumbs,
507
$view,
508
);
509
510
$main_content = $filetree->newView($main_content);
511
if (!$filetree->getDisabled()) {
512
$change_list->setFormationView($main_content);
513
}
514
515
$page = $this->newPage()
516
->setTitle($commit->getDisplayName())
517
->setPageObjectPHIDS(array($commit->getPHID()))
518
->appendChild($main_content);
519
520
return $page;
521
522
}
523
524
private function buildPropertyListView(
525
PhabricatorRepositoryCommit $commit,
526
PhabricatorRepositoryCommitData $data,
527
array $audit_requests) {
528
529
$viewer = $this->getViewer();
530
$commit_phid = $commit->getPHID();
531
$drequest = $this->getDiffusionRequest();
532
$repository = $drequest->getRepository();
533
534
$view = id(new PHUIPropertyListView())
535
->setUser($this->getRequest()->getUser())
536
->setObject($commit);
537
538
$edge_query = id(new PhabricatorEdgeQuery())
539
->withSourcePHIDs(array($commit_phid))
540
->withEdgeTypes(array(
541
DiffusionCommitHasTaskEdgeType::EDGECONST,
542
DiffusionCommitHasRevisionEdgeType::EDGECONST,
543
DiffusionCommitRevertsCommitEdgeType::EDGECONST,
544
DiffusionCommitRevertedByCommitEdgeType::EDGECONST,
545
));
546
547
$edges = $edge_query->execute();
548
549
$task_phids = array_keys(
550
$edges[$commit_phid][DiffusionCommitHasTaskEdgeType::EDGECONST]);
551
$revision_phid = key(
552
$edges[$commit_phid][DiffusionCommitHasRevisionEdgeType::EDGECONST]);
553
554
$reverts_phids = array_keys(
555
$edges[$commit_phid][DiffusionCommitRevertsCommitEdgeType::EDGECONST]);
556
$reverted_by_phids = array_keys(
557
$edges[$commit_phid][DiffusionCommitRevertedByCommitEdgeType::EDGECONST]);
558
559
$phids = $edge_query->getDestinationPHIDs(array($commit_phid));
560
561
562
if ($data->getCommitDetail('reviewerPHID')) {
563
$phids[] = $data->getCommitDetail('reviewerPHID');
564
}
565
566
$phids[] = $commit->getCommitterDisplayPHID();
567
$phids[] = $commit->getAuthorDisplayPHID();
568
569
// NOTE: We should never normally have more than a single push log, but
570
// it can occur naturally if a commit is pushed, then the branch it was
571
// on is deleted, then the commit is pushed again (or through other similar
572
// chains of events). This should be rare, but does not indicate a bug
573
// or data issue.
574
575
// NOTE: We never query push logs in SVN because the committer is always
576
// the pusher and the commit time is always the push time; the push log
577
// is redundant and we save a query by skipping it.
578
579
$push_logs = array();
580
if ($repository->isHosted() && !$repository->isSVN()) {
581
$push_logs = id(new PhabricatorRepositoryPushLogQuery())
582
->setViewer($viewer)
583
->withRepositoryPHIDs(array($repository->getPHID()))
584
->withNewRefs(array($commit->getCommitIdentifier()))
585
->withRefTypes(array(PhabricatorRepositoryPushLog::REFTYPE_COMMIT))
586
->execute();
587
foreach ($push_logs as $log) {
588
$phids[] = $log->getPusherPHID();
589
}
590
}
591
592
$handles = array();
593
if ($phids) {
594
$handles = $this->loadViewerHandles($phids);
595
}
596
597
$props = array();
598
599
if ($audit_requests) {
600
$user_requests = array();
601
$other_requests = array();
602
603
foreach ($audit_requests as $audit_request) {
604
if ($audit_request->isUser()) {
605
$user_requests[] = $audit_request;
606
} else {
607
$other_requests[] = $audit_request;
608
}
609
}
610
611
if ($user_requests) {
612
$view->addProperty(
613
pht('Auditors'),
614
$this->renderAuditStatusView($commit, $user_requests));
615
}
616
617
if ($other_requests) {
618
$view->addProperty(
619
pht('Group Auditors'),
620
$this->renderAuditStatusView($commit, $other_requests));
621
}
622
}
623
624
$provenance_list = new PHUIStatusListView();
625
626
$author_view = $commit->newCommitAuthorView($viewer);
627
if ($author_view) {
628
$author_date = $data->getAuthorEpoch();
629
$author_date = phabricator_datetime($author_date, $viewer);
630
631
$provenance_list->addItem(
632
id(new PHUIStatusItemView())
633
->setTarget($author_view)
634
->setNote(pht('Authored on %s', $author_date)));
635
}
636
637
if (!$commit->isAuthorSameAsCommitter()) {
638
$committer_view = $commit->newCommitCommitterView($viewer);
639
if ($committer_view) {
640
$committer_date = $commit->getEpoch();
641
$committer_date = phabricator_datetime($committer_date, $viewer);
642
643
$provenance_list->addItem(
644
id(new PHUIStatusItemView())
645
->setTarget($committer_view)
646
->setNote(pht('Committed on %s', $committer_date)));
647
}
648
}
649
650
if ($push_logs) {
651
$pushed_list = new PHUIStatusListView();
652
653
foreach ($push_logs as $push_log) {
654
$pusher_date = $push_log->getEpoch();
655
$pusher_date = phabricator_datetime($pusher_date, $viewer);
656
657
$pusher_view = $handles[$push_log->getPusherPHID()]->renderLink();
658
659
$provenance_list->addItem(
660
id(new PHUIStatusItemView())
661
->setTarget($pusher_view)
662
->setNote(pht('Pushed on %s', $pusher_date)));
663
}
664
}
665
666
$view->addProperty(pht('Provenance'), $provenance_list);
667
668
$reviewer_phid = $data->getCommitDetail('reviewerPHID');
669
if ($reviewer_phid) {
670
$view->addProperty(
671
pht('Reviewer'),
672
$handles[$reviewer_phid]->renderLink());
673
}
674
675
if ($revision_phid) {
676
$view->addProperty(
677
pht('Differential Revision'),
678
$handles[$revision_phid]->renderLink());
679
}
680
681
$parents = $this->getCommitParents();
682
if ($parents) {
683
$view->addProperty(
684
pht('Parents'),
685
$viewer->renderHandleList(mpull($parents, 'getPHID')));
686
}
687
688
if ($this->getCommitExists()) {
689
$view->addProperty(
690
pht('Branches'),
691
phutil_tag(
692
'span',
693
array(
694
'id' => 'commit-branches',
695
),
696
pht('Unknown')));
697
698
$view->addProperty(
699
pht('Tags'),
700
phutil_tag(
701
'span',
702
array(
703
'id' => 'commit-tags',
704
),
705
pht('Unknown')));
706
707
$identifier = $commit->getCommitIdentifier();
708
$root = $repository->getPathURI("commit/{$identifier}");
709
Javelin::initBehavior(
710
'diffusion-commit-branches',
711
array(
712
$root.'/branches/' => 'commit-branches',
713
$root.'/tags/' => 'commit-tags',
714
));
715
}
716
717
$refs = $this->getCommitRefs();
718
if ($refs) {
719
$ref_links = array();
720
foreach ($refs as $ref_data) {
721
$ref_links[] = phutil_tag(
722
'a',
723
array(
724
'href' => $ref_data['href'],
725
),
726
$ref_data['ref']);
727
}
728
$view->addProperty(
729
pht('References'),
730
phutil_implode_html(', ', $ref_links));
731
}
732
733
if ($reverts_phids) {
734
$view->addProperty(
735
pht('Reverts'),
736
$viewer->renderHandleList($reverts_phids));
737
}
738
739
if ($reverted_by_phids) {
740
$view->addProperty(
741
pht('Reverted By'),
742
$viewer->renderHandleList($reverted_by_phids));
743
}
744
745
if ($task_phids) {
746
$task_list = array();
747
foreach ($task_phids as $phid) {
748
$task_list[] = $handles[$phid]->renderLink();
749
}
750
$task_list = phutil_implode_html(phutil_tag('br'), $task_list);
751
$view->addProperty(
752
pht('Tasks'),
753
$task_list);
754
}
755
756
return $view;
757
}
758
759
private function buildComments(PhabricatorRepositoryCommit $commit) {
760
$timeline = $this->buildTransactionTimeline(
761
$commit,
762
new PhabricatorAuditTransactionQuery());
763
764
$timeline->setQuoteRef($commit->getMonogram());
765
766
return $timeline;
767
}
768
769
private function renderAddCommentPanel(
770
PhabricatorRepositoryCommit $commit,
771
$timeline) {
772
773
$request = $this->getRequest();
774
$viewer = $request->getUser();
775
776
// TODO: This is pretty awkward, unify the CSS between Diffusion and
777
// Differential better.
778
require_celerity_resource('differential-core-view-css');
779
780
$comment_view = id(new DiffusionCommitEditEngine())
781
->setViewer($viewer)
782
->buildEditEngineCommentView($commit);
783
784
$comment_view->setTransactionTimeline($timeline);
785
786
return $comment_view;
787
}
788
789
private function buildMergesTable(PhabricatorRepositoryCommit $commit) {
790
$viewer = $this->getViewer();
791
$drequest = $this->getDiffusionRequest();
792
$repository = $drequest->getRepository();
793
794
$merges = $this->getCommitMerges();
795
if (!$merges) {
796
return null;
797
}
798
799
$limit = $this->getMergeDisplayLimit();
800
801
$caption = null;
802
if (count($merges) > $limit) {
803
$merges = array_slice($merges, 0, $limit);
804
$caption = new PHUIInfoView();
805
$caption->setSeverity(PHUIInfoView::SEVERITY_NOTICE);
806
$caption->appendChild(
807
pht(
808
'This commit merges a very large number of changes. '.
809
'Only the first %s are shown.',
810
new PhutilNumber($limit)));
811
}
812
813
$commit_list = id(new DiffusionCommitGraphView())
814
->setViewer($viewer)
815
->setDiffusionRequest($drequest)
816
->setHistory($merges);
817
818
$panel = id(new PHUIObjectBoxView())
819
->setHeaderText(pht('Merged Changes'))
820
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
821
->setObjectList($commit_list->newObjectItemListView());
822
if ($caption) {
823
$panel->setInfoView($caption);
824
}
825
826
return $panel;
827
}
828
829
private function buildCurtain(
830
PhabricatorRepositoryCommit $commit,
831
PhabricatorRepository $repository) {
832
833
$request = $this->getRequest();
834
$viewer = $this->getViewer();
835
$curtain = $this->newCurtainView($commit);
836
837
$can_edit = PhabricatorPolicyFilter::hasCapability(
838
$viewer,
839
$commit,
840
PhabricatorPolicyCapability::CAN_EDIT);
841
842
$id = $commit->getID();
843
$edit_uri = $this->getApplicationURI("/commit/edit/{$id}/");
844
845
$action = id(new PhabricatorActionView())
846
->setName(pht('Edit Commit'))
847
->setHref($edit_uri)
848
->setIcon('fa-pencil')
849
->setDisabled(!$can_edit)
850
->setWorkflow(!$can_edit);
851
$curtain->addAction($action);
852
853
$action = id(new PhabricatorActionView())
854
->setName(pht('Download Raw Diff'))
855
->setHref($request->getRequestURI()->alter('diff', true))
856
->setIcon('fa-download');
857
$curtain->addAction($action);
858
859
$relationship_list = PhabricatorObjectRelationshipList::newForObject(
860
$viewer,
861
$commit);
862
863
$relationship_submenu = $relationship_list->newActionMenu();
864
if ($relationship_submenu) {
865
$curtain->addAction($relationship_submenu);
866
}
867
868
return $curtain;
869
}
870
871
private function buildRawDiffResponse(DiffusionRequest $drequest) {
872
$diff_info = $this->callConduitWithDiffusionRequest(
873
'diffusion.rawdiffquery',
874
array(
875
'commit' => $drequest->getCommit(),
876
'path' => $drequest->getPath(),
877
));
878
879
$file_phid = $diff_info['filePHID'];
880
881
$file = id(new PhabricatorFileQuery())
882
->setViewer($this->getViewer())
883
->withPHIDs(array($file_phid))
884
->executeOne();
885
if (!$file) {
886
throw new Exception(
887
pht(
888
'Failed to load file ("%s") returned by "%s".',
889
$file_phid,
890
'diffusion.rawdiffquery'));
891
}
892
893
return $file->getRedirectResponse();
894
}
895
896
private function renderAuditStatusView(
897
PhabricatorRepositoryCommit $commit,
898
array $audit_requests) {
899
assert_instances_of($audit_requests, 'PhabricatorRepositoryAuditRequest');
900
$viewer = $this->getViewer();
901
902
$view = new PHUIStatusListView();
903
foreach ($audit_requests as $request) {
904
$status = $request->getAuditRequestStatusObject();
905
906
$item = new PHUIStatusItemView();
907
$item->setIcon(
908
$status->getIconIcon(),
909
$status->getIconColor(),
910
$status->getStatusName());
911
912
$auditor_phid = $request->getAuditorPHID();
913
$target = $viewer->renderHandle($auditor_phid);
914
$item->setTarget($target);
915
916
if ($commit->hasAuditAuthority($viewer, $request)) {
917
$item->setHighlighted(true);
918
}
919
920
$view->addItem($item);
921
}
922
923
return $view;
924
}
925
926
private function linkBugtraq($corpus) {
927
$url = PhabricatorEnv::getEnvConfig('bugtraq.url');
928
if ($url === null || !strlen($url)) {
929
return $corpus;
930
}
931
932
$regexes = PhabricatorEnv::getEnvConfig('bugtraq.logregex');
933
if (!$regexes) {
934
return $corpus;
935
}
936
937
$parser = id(new PhutilBugtraqParser())
938
->setBugtraqPattern("[[ {$url} | %BUGID% ]]")
939
->setBugtraqCaptureExpression(array_shift($regexes));
940
941
$select = array_shift($regexes);
942
if ($select) {
943
$parser->setBugtraqSelectExpression($select);
944
}
945
946
return $parser->processCorpus($corpus);
947
}
948
949
private function buildTableOfContents(
950
array $changesets,
951
$header,
952
$info_view) {
953
954
$drequest = $this->getDiffusionRequest();
955
$viewer = $this->getViewer();
956
957
$toc_view = id(new PHUIDiffTableOfContentsListView())
958
->setUser($viewer)
959
->setHeader($header)
960
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
961
962
if ($info_view) {
963
$toc_view->setInfoView($info_view);
964
}
965
966
// TODO: This is hacky, we just want access to the linkX() methods on
967
// DiffusionView.
968
$diffusion_view = id(new DiffusionEmptyResultView())
969
->setDiffusionRequest($drequest);
970
971
$have_owners = PhabricatorApplication::isClassInstalledForViewer(
972
'PhabricatorOwnersApplication',
973
$viewer);
974
975
if (!$changesets) {
976
$have_owners = false;
977
}
978
979
if ($have_owners) {
980
if ($viewer->getPHID()) {
981
$packages = id(new PhabricatorOwnersPackageQuery())
982
->setViewer($viewer)
983
->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE))
984
->withAuthorityPHIDs(array($viewer->getPHID()))
985
->execute();
986
$toc_view->setAuthorityPackages($packages);
987
}
988
989
$repository = $drequest->getRepository();
990
$repository_phid = $repository->getPHID();
991
992
$control_query = id(new PhabricatorOwnersPackageQuery())
993
->setViewer($viewer)
994
->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE))
995
->withControl($repository_phid, mpull($changesets, 'getFilename'));
996
$control_query->execute();
997
}
998
999
foreach ($changesets as $changeset_id => $changeset) {
1000
$path = $changeset->getFilename();
1001
$anchor = $changeset->getAnchorName();
1002
1003
$history_link = $diffusion_view->linkHistory($path);
1004
$browse_link = $diffusion_view->linkBrowse(
1005
$path,
1006
array(
1007
'type' => $changeset->getFileType(),
1008
));
1009
1010
$item = id(new PHUIDiffTableOfContentsItemView())
1011
->setChangeset($changeset)
1012
->setAnchor($anchor)
1013
->setContext(
1014
array(
1015
$history_link,
1016
' ',
1017
$browse_link,
1018
));
1019
1020
if ($have_owners) {
1021
$packages = $control_query->getControllingPackagesForPath(
1022
$repository_phid,
1023
$changeset->getFilename());
1024
$item->setPackages($packages);
1025
}
1026
1027
$toc_view->addItem($item);
1028
}
1029
1030
return $toc_view;
1031
}
1032
1033
private function loadCommitState() {
1034
$viewer = $this->getViewer();
1035
$drequest = $this->getDiffusionRequest();
1036
$repository = $drequest->getRepository();
1037
$commit = $drequest->getCommit();
1038
1039
// TODO: We could use futures here and resolve these calls in parallel.
1040
1041
$exceptions = array();
1042
1043
try {
1044
$parent_refs = $this->callConduitWithDiffusionRequest(
1045
'diffusion.commitparentsquery',
1046
array(
1047
'commit' => $commit,
1048
));
1049
1050
if ($parent_refs) {
1051
$parents = id(new DiffusionCommitQuery())
1052
->setViewer($viewer)
1053
->withRepository($repository)
1054
->withIdentifiers($parent_refs)
1055
->execute();
1056
} else {
1057
$parents = array();
1058
}
1059
1060
$this->commitParents = $parents;
1061
} catch (Exception $ex) {
1062
$this->commitParents = false;
1063
$exceptions[] = $ex;
1064
}
1065
1066
$merge_limit = $this->getMergeDisplayLimit();
1067
1068
try {
1069
if ($repository->isSVN()) {
1070
$this->commitMerges = array();
1071
} else {
1072
$merges = $this->callConduitWithDiffusionRequest(
1073
'diffusion.mergedcommitsquery',
1074
array(
1075
'commit' => $commit,
1076
'limit' => $merge_limit + 1,
1077
));
1078
$this->commitMerges = DiffusionPathChange::newFromConduit($merges);
1079
}
1080
} catch (Exception $ex) {
1081
$this->commitMerges = false;
1082
$exceptions[] = $ex;
1083
}
1084
1085
1086
try {
1087
if ($repository->isGit()) {
1088
$refs = $this->callConduitWithDiffusionRequest(
1089
'diffusion.refsquery',
1090
array(
1091
'commit' => $commit,
1092
));
1093
} else {
1094
$refs = array();
1095
}
1096
1097
$this->commitRefs = $refs;
1098
} catch (Exception $ex) {
1099
$this->commitRefs = false;
1100
$exceptions[] = $ex;
1101
}
1102
1103
if ($exceptions) {
1104
$exists = $this->callConduitWithDiffusionRequest(
1105
'diffusion.existsquery',
1106
array(
1107
'commit' => $commit,
1108
));
1109
1110
if ($exists) {
1111
$this->commitExists = true;
1112
foreach ($exceptions as $exception) {
1113
$this->commitErrors[] = $exception->getMessage();
1114
}
1115
} else {
1116
$this->commitExists = false;
1117
$this->commitErrors[] = pht(
1118
'This commit no longer exists in the repository. It may have '.
1119
'been part of a branch which was deleted.');
1120
}
1121
} else {
1122
$this->commitExists = true;
1123
$this->commitErrors = array();
1124
}
1125
}
1126
1127
private function getMergeDisplayLimit() {
1128
return 50;
1129
}
1130
1131
private function getCommitExists() {
1132
if ($this->commitExists === null) {
1133
$this->loadCommitState();
1134
}
1135
1136
return $this->commitExists;
1137
}
1138
1139
private function getCommitParents() {
1140
if ($this->commitParents === null) {
1141
$this->loadCommitState();
1142
}
1143
1144
return $this->commitParents;
1145
}
1146
1147
private function getCommitRefs() {
1148
if ($this->commitRefs === null) {
1149
$this->loadCommitState();
1150
}
1151
1152
return $this->commitRefs;
1153
}
1154
1155
private function getCommitMerges() {
1156
if ($this->commitMerges === null) {
1157
$this->loadCommitState();
1158
}
1159
1160
return $this->commitMerges;
1161
}
1162
1163
private function getCommitErrors() {
1164
if ($this->commitErrors === null) {
1165
$this->loadCommitState();
1166
}
1167
1168
return $this->commitErrors;
1169
}
1170
1171
1172
}
1173
1174