Correct Aphlict websocket URI construction after PHP8 compatibility changes
[phabricator.git] / src / applications / diffusion / controller / DiffusionBrowseController.php
blob0f7a160fe306518b966d24a2e5add65a4849d403
1 <?php
3 final class DiffusionBrowseController extends DiffusionController {
5 private $lintCommit;
6 private $lintMessages;
7 private $corpusButtons = array();
9 public function shouldAllowPublic() {
10 return true;
13 public function handleRequest(AphrontRequest $request) {
14 $response = $this->loadDiffusionContext();
15 if ($response) {
16 return $response;
19 $drequest = $this->getDiffusionRequest();
21 // Figure out if we're browsing a directory, a file, or a search result
22 // list.
24 $grep = $request->getStr('grep');
25 if (phutil_nonempty_string($grep)) {
26 return $this->browseSearch();
29 $pager = id(new PHUIPagerView())
30 ->readFromRequest($request);
32 $results = DiffusionBrowseResultSet::newFromConduit(
33 $this->callConduitWithDiffusionRequest(
34 'diffusion.browsequery',
35 array(
36 'path' => $drequest->getPath(),
37 'commit' => $drequest->getStableCommit(),
38 'offset' => $pager->getOffset(),
39 'limit' => $pager->getPageSize() + 1,
40 )));
42 $reason = $results->getReasonForEmptyResultSet();
43 $is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE);
45 if ($is_file) {
46 return $this->browseFile();
49 $paths = $results->getPaths();
50 $paths = $pager->sliceResults($paths);
51 $results->setPaths($paths);
53 return $this->browseDirectory($results, $pager);
56 private function browseSearch() {
57 $drequest = $this->getDiffusionRequest();
58 $header = $this->buildHeaderView($drequest);
59 $path = nonempty(basename($drequest->getPath()), '/');
61 $search_results = $this->renderSearchResults();
62 $search_form = $this->renderSearchForm($path);
64 $search_form = phutil_tag(
65 'div',
66 array(
67 'class' => 'diffusion-mobile-search-form',
69 $search_form);
71 $crumbs = $this->buildCrumbs(
72 array(
73 'branch' => true,
74 'path' => true,
75 'view' => 'browse',
76 ));
77 $crumbs->setBorder(true);
79 $tabs = $this->buildTabsView('code');
81 $view = id(new PHUITwoColumnView())
82 ->setHeader($header)
83 ->setTabs($tabs)
84 ->setFooter(
85 array(
86 $search_form,
87 $search_results,
88 ));
90 return $this->newPage()
91 ->setTitle(
92 array(
93 nonempty(basename($drequest->getPath()), '/'),
94 $drequest->getRepository()->getDisplayName(),
96 ->setCrumbs($crumbs)
97 ->appendChild($view);
100 private function browseFile() {
101 $viewer = $this->getViewer();
102 $request = $this->getRequest();
103 $drequest = $this->getDiffusionRequest();
104 $repository = $drequest->getRepository();
106 $before = $request->getStr('before');
107 if ($before) {
108 return $this->buildBeforeResponse($before);
111 $path = $drequest->getPath();
112 $params = array(
113 'commit' => $drequest->getCommit(),
114 'path' => $drequest->getPath(),
117 $view = $request->getStr('view');
119 $byte_limit = null;
120 if ($view !== 'raw') {
121 $byte_limit = PhabricatorFileStorageEngine::getChunkThreshold();
122 $time_limit = 10;
124 $params += array(
125 'timeout' => $time_limit,
126 'byteLimit' => $byte_limit,
130 $response = $this->callConduitWithDiffusionRequest(
131 'diffusion.filecontentquery',
132 $params);
134 $hit_byte_limit = $response['tooHuge'];
135 $hit_time_limit = $response['tooSlow'];
137 $file_phid = $response['filePHID'];
138 $show_editor = false;
139 if ($hit_byte_limit) {
140 $corpus = $this->buildErrorCorpus(
141 pht(
142 'This file is larger than %s byte(s), and too large to display '.
143 'in the web UI.',
144 phutil_format_bytes($byte_limit)));
145 } else if ($hit_time_limit) {
146 $corpus = $this->buildErrorCorpus(
147 pht(
148 'This file took too long to load from the repository (more than '.
149 '%s second(s)).',
150 new PhutilNumber($time_limit)));
151 } else {
152 $file = id(new PhabricatorFileQuery())
153 ->setViewer($viewer)
154 ->withPHIDs(array($file_phid))
155 ->executeOne();
156 if (!$file) {
157 throw new Exception(pht('Failed to load content file!'));
160 if ($view === 'raw') {
161 return $file->getRedirectResponse();
164 $data = $file->loadFileData();
166 $lfs_ref = $this->getGitLFSRef($repository, $data);
167 if ($lfs_ref) {
168 if ($view == 'git-lfs') {
169 $file = $this->loadGitLFSFile($lfs_ref);
171 // Rename the file locally so we generate a better vanity URI for
172 // it. In storage, it just has a name like "lfs-13f9a94c0923...",
173 // since we don't get any hints about possible human-readable names
174 // at upload time.
175 $basename = basename($drequest->getPath());
176 $file->makeEphemeral();
177 $file->setName($basename);
179 return $file->getRedirectResponse();
182 $corpus = $this->buildGitLFSCorpus($lfs_ref);
183 } else {
184 $show_editor = true;
186 $ref = id(new PhabricatorDocumentRef())
187 ->setFile($file);
189 $engine = id(new DiffusionDocumentRenderingEngine())
190 ->setRequest($request)
191 ->setDiffusionRequest($drequest);
193 $corpus = $engine->newDocumentView($ref);
195 $this->corpusButtons[] = $this->renderFileButton();
199 $bar = $this->buildButtonBar($drequest, $show_editor);
200 $header = $this->buildHeaderView($drequest);
201 $header->setHeaderIcon('fa-file-code-o');
203 $follow = $request->getStr('follow');
204 $follow_notice = null;
205 if ($follow) {
206 $follow_notice = id(new PHUIInfoView())
207 ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
208 ->setTitle(pht('Unable to Continue'));
209 switch ($follow) {
210 case 'first':
211 $follow_notice->appendChild(
212 pht(
213 'Unable to continue tracing the history of this file because '.
214 'this commit is the first commit in the repository.'));
215 break;
216 case 'created':
217 $follow_notice->appendChild(
218 pht(
219 'Unable to continue tracing the history of this file because '.
220 'this commit created the file.'));
221 break;
225 $renamed = $request->getStr('renamed');
226 $renamed_notice = null;
227 if ($renamed) {
228 $renamed_notice = id(new PHUIInfoView())
229 ->setSeverity(PHUIInfoView::SEVERITY_NOTICE)
230 ->setTitle(pht('File Renamed'))
231 ->appendChild(
232 pht(
233 'File history passes through a rename from "%s" to "%s".',
234 $drequest->getPath(),
235 $renamed));
238 $open_revisions = $this->buildOpenRevisions();
239 $owners_list = $this->buildOwnersList($drequest);
241 $crumbs = $this->buildCrumbs(
242 array(
243 'branch' => true,
244 'path' => true,
245 'view' => 'browse',
247 $crumbs->setBorder(true);
249 $basename = basename($this->getDiffusionRequest()->getPath());
250 $tabs = $this->buildTabsView('code');
251 $bar->setRight($this->corpusButtons);
253 $view = id(new PHUITwoColumnView())
254 ->setHeader($header)
255 ->setTabs($tabs)
256 ->setFooter(array(
257 $bar,
258 $follow_notice,
259 $renamed_notice,
260 $corpus,
261 $open_revisions,
262 $owners_list,
265 $title = array($basename, $repository->getDisplayName());
267 return $this->newPage()
268 ->setTitle($title)
269 ->setCrumbs($crumbs)
270 ->appendChild(
271 array(
272 $view,
277 public function browseDirectory(
278 DiffusionBrowseResultSet $results,
279 PHUIPagerView $pager) {
281 $request = $this->getRequest();
282 $drequest = $this->getDiffusionRequest();
283 $repository = $drequest->getRepository();
285 $reason = $results->getReasonForEmptyResultSet();
287 $this->buildActionButtons($drequest, true);
288 $details = $this->buildPropertyView($drequest);
290 $header = $this->buildHeaderView($drequest);
291 $header->setHeaderIcon('fa-folder-open');
293 $title = '/';
294 if ($drequest->getPath() !== null) {
295 $title = nonempty(basename($drequest->getPath()), '/');
298 $empty_result = null;
299 $browse_panel = null;
300 if (!$results->isValidResults()) {
301 $empty_result = new DiffusionEmptyResultView();
302 $empty_result->setDiffusionRequest($drequest);
303 $empty_result->setDiffusionBrowseResultSet($results);
304 $empty_result->setView($request->getStr('view'));
305 } else {
306 $browse_table = id(new DiffusionBrowseTableView())
307 ->setDiffusionRequest($drequest)
308 ->setPaths($results->getPaths())
309 ->setUser($request->getUser());
311 $icon = 'fa-folder-open';
312 $browse_header = $this->buildPanelHeaderView($title, $icon);
314 $browse_panel = id(new PHUIObjectBoxView())
315 ->setHeader($browse_header)
316 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
317 ->setTable($browse_table)
318 ->addClass('diffusion-mobile-view')
319 ->setPager($pager);
322 $open_revisions = $this->buildOpenRevisions();
323 $readme = $this->renderDirectoryReadme($results);
325 $crumbs = $this->buildCrumbs(
326 array(
327 'branch' => true,
328 'path' => true,
329 'view' => 'browse',
332 $crumbs->setBorder(true);
333 $tabs = $this->buildTabsView('code');
334 $owners_list = $this->buildOwnersList($drequest);
335 $bar = id(new PHUILeftRightView())
336 ->setRight($this->corpusButtons)
337 ->addClass('diffusion-action-bar');
339 $view = id(new PHUITwoColumnView())
340 ->setHeader($header)
341 ->setTabs($tabs)
342 ->setFooter(
343 array(
344 $bar,
345 $empty_result,
346 $browse_panel,
347 $open_revisions,
348 $owners_list,
349 $readme,
352 if ($details) {
353 $view->addPropertySection(pht('Details'), $details);
356 return $this->newPage()
357 ->setTitle(array(
358 $title,
359 $repository->getDisplayName(),
361 ->setCrumbs($crumbs)
362 ->appendChild(
363 array(
364 $view,
368 private function renderSearchResults() {
369 $request = $this->getRequest();
371 $drequest = $this->getDiffusionRequest();
372 $repository = $drequest->getRepository();
373 $results = array();
375 $pager = id(new PHUIPagerView())
376 ->readFromRequest($request);
378 $search_mode = null;
379 switch ($repository->getVersionControlSystem()) {
380 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
381 $results = array();
382 break;
383 default:
384 if (strlen($this->getRequest()->getStr('grep'))) {
385 $search_mode = 'grep';
386 $query_string = $request->getStr('grep');
387 $results = $this->callConduitWithDiffusionRequest(
388 'diffusion.searchquery',
389 array(
390 'grep' => $query_string,
391 'commit' => $drequest->getStableCommit(),
392 'path' => $drequest->getPath(),
393 'limit' => $pager->getPageSize() + 1,
394 'offset' => $pager->getOffset(),
397 break;
399 $results = $pager->sliceResults($results);
401 $table = null;
402 $header = null;
403 if ($search_mode == 'grep') {
404 $table = $this->renderGrepResults($results, $query_string);
405 $title = pht(
406 'File content matching "%s" under "%s"',
407 $query_string,
408 nonempty($drequest->getPath(), '/'));
409 $header = id(new PHUIHeaderView())
410 ->setHeader($title)
411 ->addClass('diffusion-search-result-header');
414 return array($header, $table, $pager);
418 private function renderGrepResults(array $results, $pattern) {
419 $drequest = $this->getDiffusionRequest();
420 require_celerity_resource('phabricator-search-results-css');
422 if (!$results) {
423 return id(new PHUIInfoView())
424 ->setSeverity(PHUIInfoView::SEVERITY_NODATA)
425 ->appendChild(
426 pht(
427 'The pattern you searched for was not found in the content of any '.
428 'files.'));
431 $grouped = array();
432 foreach ($results as $file) {
433 list($path, $line, $string) = $file;
434 $grouped[$path][] = array($line, $string);
437 $view = array();
438 foreach ($grouped as $path => $matches) {
439 $view[] = id(new DiffusionPatternSearchView())
440 ->setPath($path)
441 ->setMatches($matches)
442 ->setPattern($pattern)
443 ->setDiffusionRequest($drequest)
444 ->render();
447 return $view;
450 private function buildButtonBar(
451 DiffusionRequest $drequest,
452 $show_editor) {
454 $viewer = $this->getViewer();
455 $base_uri = $this->getRequest()->getRequestURI();
457 $repository = $drequest->getRepository();
458 $path = $drequest->getPath();
459 $line = nonempty((int)$drequest->getLine(), 1);
460 $buttons = array();
462 $editor_uri = null;
463 $editor_template = null;
465 $link_engine = PhabricatorEditorURIEngine::newForViewer($viewer);
466 if ($link_engine) {
467 $link_engine->setRepository($repository);
469 $editor_uri = $link_engine->getURIForPath($path, $line);
470 $editor_template = $link_engine->getURITokensForPath($path);
473 $buttons[] =
474 id(new PHUIButtonView())
475 ->setTag('a')
476 ->setText(pht('Last Change'))
477 ->setColor(PHUIButtonView::GREY)
478 ->setHref(
479 $drequest->generateURI(
480 array(
481 'action' => 'change',
483 ->setIcon('fa-backward');
485 if ($editor_uri) {
486 $buttons[] =
487 id(new PHUIButtonView())
488 ->setTag('a')
489 ->setText(pht('Open File'))
490 ->setHref($editor_uri)
491 ->setIcon('fa-pencil')
492 ->setID('editor_link')
493 ->setMetadata(array('template' => $editor_template))
494 ->setDisabled(!$editor_uri)
495 ->setColor(PHUIButtonView::GREY);
498 $bar = id(new PHUILeftRightView())
499 ->setLeft($buttons)
500 ->addClass('diffusion-action-bar full-mobile-buttons');
501 return $bar;
504 private function buildOwnersList(DiffusionRequest $drequest) {
505 $viewer = $this->getViewer();
507 $have_owners = PhabricatorApplication::isClassInstalledForViewer(
508 'PhabricatorOwnersApplication',
509 $viewer);
510 if (!$have_owners) {
511 return null;
514 $repository = $drequest->getRepository();
516 $package_query = id(new PhabricatorOwnersPackageQuery())
517 ->setViewer($viewer)
518 ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE))
519 ->withControl(
520 $repository->getPHID(),
521 array(
522 $drequest->getPath(),
525 $package_query->execute();
527 $packages = $package_query->getControllingPackagesForPath(
528 $repository->getPHID(),
529 $drequest->getPath());
531 $ownership = id(new PHUIObjectItemListView())
532 ->setUser($viewer)
533 ->setNoDataString(pht('No Owners'));
535 if ($packages) {
536 foreach ($packages as $package) {
537 $item = id(new PHUIObjectItemView())
538 ->setObject($package)
539 ->setObjectName($package->getMonogram())
540 ->setHeader($package->getName())
541 ->setHref($package->getURI());
543 $owners = $package->getOwners();
544 if ($owners) {
545 $owner_list = $viewer->renderHandleList(
546 mpull($owners, 'getUserPHID'));
547 } else {
548 $owner_list = phutil_tag('em', array(), pht('None'));
550 $item->addAttribute(pht('Owners: %s', $owner_list));
552 $auto = $package->getAutoReview();
553 $autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
554 $spec = idx($autoreview_map, $auto, array());
555 $name = idx($spec, 'name', $auto);
556 $item->addIcon('fa-code', $name);
558 $rule = $package->newAuditingRule();
559 $item->addIcon($rule->getIconIcon(), $rule->getDisplayName());
561 if ($package->isArchived()) {
562 $item->setDisabled(true);
565 $ownership->addItem($item);
569 $view = id(new PHUIObjectBoxView())
570 ->setHeaderText(pht('Owner Packages'))
571 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
572 ->addClass('diffusion-mobile-view')
573 ->setObjectList($ownership);
575 return $view;
578 private function renderFileButton($file_uri = null, $label = null) {
580 $base_uri = $this->getRequest()->getRequestURI();
582 if ($file_uri) {
583 $text = pht('Download File');
584 $href = $file_uri;
585 $icon = 'fa-download';
586 } else {
587 $text = pht('Raw File');
588 $href = $base_uri->alter('view', 'raw');
589 $icon = 'fa-file-text';
592 if ($label !== null) {
593 $text = $label;
596 $button = id(new PHUIButtonView())
597 ->setTag('a')
598 ->setText($text)
599 ->setHref($href)
600 ->setIcon($icon)
601 ->setColor(PHUIButtonView::GREY);
603 return $button;
606 private function renderGitLFSButton() {
607 $viewer = $this->getViewer();
609 $uri = $this->getRequest()->getRequestURI();
610 $href = $uri->alter('view', 'git-lfs');
612 $text = pht('Download from Git LFS');
613 $icon = 'fa-download';
615 return id(new PHUIButtonView())
616 ->setTag('a')
617 ->setText($text)
618 ->setHref($href)
619 ->setIcon($icon)
620 ->setColor(PHUIButtonView::GREY);
623 private function buildErrorCorpus($message) {
624 $text = id(new PHUIBoxView())
625 ->addPadding(PHUI::PADDING_LARGE)
626 ->appendChild($message);
628 $header = id(new PHUIHeaderView())
629 ->setHeader(pht('Details'));
631 $box = id(new PHUIObjectBoxView())
632 ->setHeader($header)
633 ->appendChild($text);
635 return $box;
638 private function buildBeforeResponse($before) {
639 $request = $this->getRequest();
640 $drequest = $this->getDiffusionRequest();
642 // NOTE: We need to get the grandparent so we can capture filename changes
643 // in the parent.
645 $parent = $this->loadParentCommitOf($before);
646 $old_filename = null;
647 $was_created = false;
648 if ($parent) {
649 $grandparent = $this->loadParentCommitOf($parent);
651 if ($grandparent) {
652 $rename_query = new DiffusionRenameHistoryQuery();
653 $rename_query->setRequest($drequest);
654 $rename_query->setOldCommit($grandparent);
655 $rename_query->setViewer($request->getUser());
656 $old_filename = $rename_query->loadOldFilename();
657 $was_created = $rename_query->getWasCreated();
661 $follow = null;
662 if ($was_created) {
663 // If the file was created in history, that means older commits won't
664 // have it. Since we know it existed at 'before', it must have been
665 // created then; jump there.
666 $target_commit = $before;
667 $follow = 'created';
668 } else if ($parent) {
669 // If we found a parent, jump to it. This is the normal case.
670 $target_commit = $parent;
671 } else {
672 // If there's no parent, this was probably created in the initial commit?
673 // And the "was_created" check will fail because we can't identify the
674 // grandparent. Keep the user at 'before'.
675 $target_commit = $before;
676 $follow = 'first';
679 $path = $drequest->getPath();
680 $renamed = null;
681 if ($old_filename !== null &&
682 $old_filename !== '/'.$path) {
683 $renamed = $path;
684 $path = $old_filename;
687 $line = null;
688 // If there's a follow error, drop the line so the user sees the message.
689 if (!$follow) {
690 $line = $this->getBeforeLineNumber($target_commit);
693 $before_uri = $drequest->generateURI(
694 array(
695 'action' => 'browse',
696 'commit' => $target_commit,
697 'line' => $line,
698 'path' => $path,
701 if ($renamed === null) {
702 $before_uri->removeQueryParam('renamed');
703 } else {
704 $before_uri->replaceQueryParam('renamed', $renamed);
707 if ($follow === null) {
708 $before_uri->removeQueryParam('follow');
709 } else {
710 $before_uri->replaceQueryParam('follow', $follow);
713 return id(new AphrontRedirectResponse())->setURI($before_uri);
716 private function getBeforeLineNumber($target_commit) {
717 $drequest = $this->getDiffusionRequest();
718 $viewer = $this->getViewer();
720 $line = $drequest->getLine();
721 if (!$line) {
722 return null;
725 $diff_info = $this->callConduitWithDiffusionRequest(
726 'diffusion.rawdiffquery',
727 array(
728 'commit' => $drequest->getCommit(),
729 'path' => $drequest->getPath(),
730 'againstCommit' => $target_commit,
733 $file_phid = $diff_info['filePHID'];
734 $file = id(new PhabricatorFileQuery())
735 ->setViewer($viewer)
736 ->withPHIDs(array($file_phid))
737 ->executeOne();
738 if (!$file) {
739 throw new Exception(
740 pht(
741 'Failed to load file ("%s") returned by "%s".',
742 $file_phid,
743 'diffusion.rawdiffquery.'));
746 $raw_diff = $file->loadFileData();
748 $old_line = 0;
749 $new_line = 0;
751 foreach (explode("\n", $raw_diff) as $text) {
752 if ($text[0] == '-' || $text[0] == ' ') {
753 $old_line++;
755 if ($text[0] == '+' || $text[0] == ' ') {
756 $new_line++;
758 if ($new_line == $line) {
759 return $old_line;
763 // We didn't find the target line.
764 return $line;
767 private function loadParentCommitOf($commit) {
768 $drequest = $this->getDiffusionRequest();
769 $user = $this->getRequest()->getUser();
771 $before_req = DiffusionRequest::newFromDictionary(
772 array(
773 'user' => $user,
774 'repository' => $drequest->getRepository(),
775 'commit' => $commit,
778 $parents = DiffusionQuery::callConduitWithDiffusionRequest(
779 $user,
780 $before_req,
781 'diffusion.commitparentsquery',
782 array(
783 'commit' => $commit,
786 return head($parents);
789 protected function markupText($text) {
790 $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine();
791 $engine->setConfig('viewer', $this->getRequest()->getUser());
792 $text = $engine->markupText($text);
794 $text = phutil_tag(
795 'div',
796 array(
797 'class' => 'phabricator-remarkup',
799 $text);
801 return $text;
804 protected function buildHeaderView(DiffusionRequest $drequest) {
805 $viewer = $this->getViewer();
806 $repository = $drequest->getRepository();
808 $commit_tag = $this->renderCommitHashTag($drequest);
810 $path = nonempty($drequest->getPath(), '/');
812 $search = $this->renderSearchForm($path);
814 $header = id(new PHUIHeaderView())
815 ->setUser($viewer)
816 ->setHeader($this->renderPathLinks($drequest, $mode = 'browse'))
817 ->addActionItem($search)
818 ->addTag($commit_tag)
819 ->addClass('diffusion-browse-header');
821 if (!$repository->isSVN()) {
822 $branch_tag = $this->renderBranchTag($drequest);
823 $header->addTag($branch_tag);
826 return $header;
829 protected function buildPanelHeaderView($title, $icon) {
831 $header = id(new PHUIHeaderView())
832 ->setHeader($title)
833 ->setHeaderIcon($icon)
834 ->addClass('diffusion-panel-header-view');
836 return $header;
840 protected function buildActionButtons(
841 DiffusionRequest $drequest,
842 $is_directory = false) {
844 $viewer = $this->getViewer();
845 $repository = $drequest->getRepository();
846 $history_uri = $drequest->generateURI(array('action' => 'history'));
847 $behind_head = $drequest->getSymbolicCommit();
848 $compare = null;
849 $head_uri = $drequest->generateURI(
850 array(
851 'commit' => '',
852 'action' => 'browse',
855 if ($repository->supportsBranchComparison() && $is_directory) {
856 $compare_uri = $drequest->generateURI(array('action' => 'compare'));
857 $compare = id(new PHUIButtonView())
858 ->setText(pht('Compare'))
859 ->setIcon('fa-code-fork')
860 ->setWorkflow(true)
861 ->setTag('a')
862 ->setHref($compare_uri)
863 ->setColor(PHUIButtonView::GREY);
864 $this->corpusButtons[] = $compare;
867 $head = null;
868 if ($behind_head) {
869 $head = id(new PHUIButtonView())
870 ->setTag('a')
871 ->setText(pht('Back to HEAD'))
872 ->setHref($head_uri)
873 ->setIcon('fa-home')
874 ->setColor(PHUIButtonView::GREY);
875 $this->corpusButtons[] = $head;
878 $history = id(new PHUIButtonView())
879 ->setText(pht('History'))
880 ->setHref($history_uri)
881 ->setTag('a')
882 ->setIcon('fa-history')
883 ->setColor(PHUIButtonView::GREY);
884 $this->corpusButtons[] = $history;
888 protected function buildPropertyView(
889 DiffusionRequest $drequest) {
891 $viewer = $this->getViewer();
892 $view = id(new PHUIPropertyListView())
893 ->setUser($viewer);
895 if ($drequest->getSymbolicType() == 'tag') {
896 $symbolic = $drequest->getSymbolicCommit();
897 $view->addProperty(pht('Tag'), $symbolic);
899 $tags = $this->callConduitWithDiffusionRequest(
900 'diffusion.tagsquery',
901 array(
902 'names' => array($symbolic),
903 'needMessages' => true,
905 $tags = DiffusionRepositoryTag::newFromConduit($tags);
907 $tags = mpull($tags, null, 'getName');
908 $tag = idx($tags, $symbolic);
910 if ($tag && strlen($tag->getMessage())) {
911 $view->addSectionHeader(
912 pht('Tag Content'), 'fa-tag');
913 $view->addTextContent($this->markupText($tag->getMessage()));
917 if ($view->hasAnyProperties()) {
918 return $view;
921 return null;
924 private function buildOpenRevisions() {
925 $viewer = $this->getViewer();
927 $drequest = $this->getDiffusionRequest();
928 $repository = $drequest->getRepository();
929 $path = $drequest->getPath();
931 $recent = (PhabricatorTime::getNow() - phutil_units('30 days in seconds'));
933 $revisions = id(new DifferentialRevisionQuery())
934 ->setViewer($viewer)
935 ->withPaths(array($path))
936 ->withRepositoryPHIDs(array($repository->getPHID()))
937 ->withIsOpen(true)
938 ->withUpdatedEpochBetween($recent, null)
939 ->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED)
940 ->setLimit(10)
941 ->needReviewers(true)
942 ->needFlags(true)
943 ->needDrafts(true)
944 ->execute();
946 if (!$revisions) {
947 return null;
950 $header = id(new PHUIHeaderView())
951 ->setHeader(pht('Recent Open Revisions'));
953 $list = id(new DifferentialRevisionListView())
954 ->setViewer($viewer)
955 ->setRevisions($revisions)
956 ->setNoBox(true);
958 $view = id(new PHUIObjectBoxView())
959 ->setHeader($header)
960 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
961 ->addClass('diffusion-mobile-view')
962 ->appendChild($list);
964 return $view;
967 private function getGitLFSRef(PhabricatorRepository $repository, $data) {
968 if (!$repository->canUseGitLFS()) {
969 return null;
972 $lfs_pattern = '(^version https://git-lfs\\.github\\.com/spec/v1[\r\n])';
973 if (!preg_match($lfs_pattern, $data)) {
974 return null;
977 $matches = null;
978 if (!preg_match('(^oid sha256:(.*)$)m', $data, $matches)) {
979 return null;
982 $hash = $matches[1];
983 $hash = trim($hash);
985 return id(new PhabricatorRepositoryGitLFSRefQuery())
986 ->setViewer($this->getViewer())
987 ->withRepositoryPHIDs(array($repository->getPHID()))
988 ->withObjectHashes(array($hash))
989 ->executeOne();
992 private function buildGitLFSCorpus(PhabricatorRepositoryGitLFSRef $ref) {
993 // TODO: We should probably test if we can load the file PHID here and
994 // show the user an error if we can't, rather than making them click
995 // through to hit an error.
997 $title = basename($this->getDiffusionRequest()->getPath());
998 $icon = 'fa-archive';
999 $drequest = $this->getDiffusionRequest();
1000 $this->buildActionButtons($drequest);
1001 $header = $this->buildPanelHeaderView($title, $icon);
1003 $severity = PHUIInfoView::SEVERITY_NOTICE;
1005 $messages = array();
1006 $messages[] = pht(
1007 'This %s file is stored in Git Large File Storage.',
1008 phutil_format_bytes($ref->getByteSize()));
1010 try {
1011 $file = $this->loadGitLFSFile($ref);
1012 $this->corpusButtons[] = $this->renderGitLFSButton();
1013 } catch (Exception $ex) {
1014 $severity = PHUIInfoView::SEVERITY_ERROR;
1015 $messages[] = pht('The data for this file could not be loaded.');
1018 $this->corpusButtons[] = $this->renderFileButton(
1019 null, pht('View Raw LFS Pointer'));
1021 $corpus = id(new PHUIObjectBoxView())
1022 ->setHeader($header)
1023 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
1024 ->addClass('diffusion-mobile-view')
1025 ->setCollapsed(true);
1027 if ($messages) {
1028 $corpus->setInfoView(
1029 id(new PHUIInfoView())
1030 ->setSeverity($severity)
1031 ->setErrors($messages));
1034 return $corpus;
1037 private function loadGitLFSFile(PhabricatorRepositoryGitLFSRef $ref) {
1038 $viewer = $this->getViewer();
1040 $file = id(new PhabricatorFileQuery())
1041 ->setViewer($viewer)
1042 ->withPHIDs(array($ref->getFilePHID()))
1043 ->executeOne();
1044 if (!$file) {
1045 throw new Exception(
1046 pht(
1047 'Failed to load file object for Git LFS ref "%s"!',
1048 $ref->getObjectHash()));
1051 return $file;