Correct a parameter order swap in "diffusion.historyquery" for Mercurial
[phabricator.git] / src / applications / diffusion / controller / DiffusionBrowseController.php
blob2b81ab89cc62faedf549058356c9c3dd30000132
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 (strlen($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 $empty_result = null;
294 $browse_panel = null;
295 if (!$results->isValidResults()) {
296 $empty_result = new DiffusionEmptyResultView();
297 $empty_result->setDiffusionRequest($drequest);
298 $empty_result->setDiffusionBrowseResultSet($results);
299 $empty_result->setView($request->getStr('view'));
300 } else {
301 $browse_table = id(new DiffusionBrowseTableView())
302 ->setDiffusionRequest($drequest)
303 ->setPaths($results->getPaths())
304 ->setUser($request->getUser());
306 $title = nonempty(basename($drequest->getPath()), '/');
307 $icon = 'fa-folder-open';
308 $browse_header = $this->buildPanelHeaderView($title, $icon);
310 $browse_panel = id(new PHUIObjectBoxView())
311 ->setHeader($browse_header)
312 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
313 ->setTable($browse_table)
314 ->addClass('diffusion-mobile-view')
315 ->setPager($pager);
318 $open_revisions = $this->buildOpenRevisions();
319 $readme = $this->renderDirectoryReadme($results);
321 $crumbs = $this->buildCrumbs(
322 array(
323 'branch' => true,
324 'path' => true,
325 'view' => 'browse',
328 $crumbs->setBorder(true);
329 $tabs = $this->buildTabsView('code');
330 $owners_list = $this->buildOwnersList($drequest);
331 $bar = id(new PHUILeftRightView())
332 ->setRight($this->corpusButtons)
333 ->addClass('diffusion-action-bar');
335 $view = id(new PHUITwoColumnView())
336 ->setHeader($header)
337 ->setTabs($tabs)
338 ->setFooter(
339 array(
340 $bar,
341 $empty_result,
342 $browse_panel,
343 $open_revisions,
344 $owners_list,
345 $readme,
348 if ($details) {
349 $view->addPropertySection(pht('Details'), $details);
352 return $this->newPage()
353 ->setTitle(array(
354 nonempty(basename($drequest->getPath()), '/'),
355 $repository->getDisplayName(),
357 ->setCrumbs($crumbs)
358 ->appendChild(
359 array(
360 $view,
364 private function renderSearchResults() {
365 $request = $this->getRequest();
367 $drequest = $this->getDiffusionRequest();
368 $repository = $drequest->getRepository();
369 $results = array();
371 $pager = id(new PHUIPagerView())
372 ->readFromRequest($request);
374 $search_mode = null;
375 switch ($repository->getVersionControlSystem()) {
376 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
377 $results = array();
378 break;
379 default:
380 if (strlen($this->getRequest()->getStr('grep'))) {
381 $search_mode = 'grep';
382 $query_string = $request->getStr('grep');
383 $results = $this->callConduitWithDiffusionRequest(
384 'diffusion.searchquery',
385 array(
386 'grep' => $query_string,
387 'commit' => $drequest->getStableCommit(),
388 'path' => $drequest->getPath(),
389 'limit' => $pager->getPageSize() + 1,
390 'offset' => $pager->getOffset(),
393 break;
395 $results = $pager->sliceResults($results);
397 $table = null;
398 $header = null;
399 if ($search_mode == 'grep') {
400 $table = $this->renderGrepResults($results, $query_string);
401 $title = pht(
402 'File content matching "%s" under "%s"',
403 $query_string,
404 nonempty($drequest->getPath(), '/'));
405 $header = id(new PHUIHeaderView())
406 ->setHeader($title)
407 ->addClass('diffusion-search-result-header');
410 return array($header, $table, $pager);
414 private function renderGrepResults(array $results, $pattern) {
415 $drequest = $this->getDiffusionRequest();
416 require_celerity_resource('phabricator-search-results-css');
418 if (!$results) {
419 return id(new PHUIInfoView())
420 ->setSeverity(PHUIInfoView::SEVERITY_NODATA)
421 ->appendChild(
422 pht(
423 'The pattern you searched for was not found in the content of any '.
424 'files.'));
427 $grouped = array();
428 foreach ($results as $file) {
429 list($path, $line, $string) = $file;
430 $grouped[$path][] = array($line, $string);
433 $view = array();
434 foreach ($grouped as $path => $matches) {
435 $view[] = id(new DiffusionPatternSearchView())
436 ->setPath($path)
437 ->setMatches($matches)
438 ->setPattern($pattern)
439 ->setDiffusionRequest($drequest)
440 ->render();
443 return $view;
446 private function buildButtonBar(
447 DiffusionRequest $drequest,
448 $show_editor) {
450 $viewer = $this->getViewer();
451 $base_uri = $this->getRequest()->getRequestURI();
453 $repository = $drequest->getRepository();
454 $path = $drequest->getPath();
455 $line = nonempty((int)$drequest->getLine(), 1);
456 $buttons = array();
458 $editor_uri = null;
459 $editor_template = null;
461 $link_engine = PhabricatorEditorURIEngine::newForViewer($viewer);
462 if ($link_engine) {
463 $link_engine->setRepository($repository);
465 $editor_uri = $link_engine->getURIForPath($path, $line);
466 $editor_template = $link_engine->getURITokensForPath($path);
469 $buttons[] =
470 id(new PHUIButtonView())
471 ->setTag('a')
472 ->setText(pht('Last Change'))
473 ->setColor(PHUIButtonView::GREY)
474 ->setHref(
475 $drequest->generateURI(
476 array(
477 'action' => 'change',
479 ->setIcon('fa-backward');
481 if ($editor_uri) {
482 $buttons[] =
483 id(new PHUIButtonView())
484 ->setTag('a')
485 ->setText(pht('Open File'))
486 ->setHref($editor_uri)
487 ->setIcon('fa-pencil')
488 ->setID('editor_link')
489 ->setMetadata(array('template' => $editor_template))
490 ->setDisabled(!$editor_uri)
491 ->setColor(PHUIButtonView::GREY);
494 $bar = id(new PHUILeftRightView())
495 ->setLeft($buttons)
496 ->addClass('diffusion-action-bar full-mobile-buttons');
497 return $bar;
500 private function buildOwnersList(DiffusionRequest $drequest) {
501 $viewer = $this->getViewer();
503 $have_owners = PhabricatorApplication::isClassInstalledForViewer(
504 'PhabricatorOwnersApplication',
505 $viewer);
506 if (!$have_owners) {
507 return null;
510 $repository = $drequest->getRepository();
512 $package_query = id(new PhabricatorOwnersPackageQuery())
513 ->setViewer($viewer)
514 ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE))
515 ->withControl(
516 $repository->getPHID(),
517 array(
518 $drequest->getPath(),
521 $package_query->execute();
523 $packages = $package_query->getControllingPackagesForPath(
524 $repository->getPHID(),
525 $drequest->getPath());
527 $ownership = id(new PHUIObjectItemListView())
528 ->setUser($viewer)
529 ->setNoDataString(pht('No Owners'));
531 if ($packages) {
532 foreach ($packages as $package) {
533 $item = id(new PHUIObjectItemView())
534 ->setObject($package)
535 ->setObjectName($package->getMonogram())
536 ->setHeader($package->getName())
537 ->setHref($package->getURI());
539 $owners = $package->getOwners();
540 if ($owners) {
541 $owner_list = $viewer->renderHandleList(
542 mpull($owners, 'getUserPHID'));
543 } else {
544 $owner_list = phutil_tag('em', array(), pht('None'));
546 $item->addAttribute(pht('Owners: %s', $owner_list));
548 $auto = $package->getAutoReview();
549 $autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
550 $spec = idx($autoreview_map, $auto, array());
551 $name = idx($spec, 'name', $auto);
552 $item->addIcon('fa-code', $name);
554 $rule = $package->newAuditingRule();
555 $item->addIcon($rule->getIconIcon(), $rule->getDisplayName());
557 if ($package->isArchived()) {
558 $item->setDisabled(true);
561 $ownership->addItem($item);
565 $view = id(new PHUIObjectBoxView())
566 ->setHeaderText(pht('Owner Packages'))
567 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
568 ->addClass('diffusion-mobile-view')
569 ->setObjectList($ownership);
571 return $view;
574 private function renderFileButton($file_uri = null, $label = null) {
576 $base_uri = $this->getRequest()->getRequestURI();
578 if ($file_uri) {
579 $text = pht('Download File');
580 $href = $file_uri;
581 $icon = 'fa-download';
582 } else {
583 $text = pht('Raw File');
584 $href = $base_uri->alter('view', 'raw');
585 $icon = 'fa-file-text';
588 if ($label !== null) {
589 $text = $label;
592 $button = id(new PHUIButtonView())
593 ->setTag('a')
594 ->setText($text)
595 ->setHref($href)
596 ->setIcon($icon)
597 ->setColor(PHUIButtonView::GREY);
599 return $button;
602 private function renderGitLFSButton() {
603 $viewer = $this->getViewer();
605 $uri = $this->getRequest()->getRequestURI();
606 $href = $uri->alter('view', 'git-lfs');
608 $text = pht('Download from Git LFS');
609 $icon = 'fa-download';
611 return id(new PHUIButtonView())
612 ->setTag('a')
613 ->setText($text)
614 ->setHref($href)
615 ->setIcon($icon)
616 ->setColor(PHUIButtonView::GREY);
619 private function buildErrorCorpus($message) {
620 $text = id(new PHUIBoxView())
621 ->addPadding(PHUI::PADDING_LARGE)
622 ->appendChild($message);
624 $header = id(new PHUIHeaderView())
625 ->setHeader(pht('Details'));
627 $box = id(new PHUIObjectBoxView())
628 ->setHeader($header)
629 ->appendChild($text);
631 return $box;
634 private function buildBeforeResponse($before) {
635 $request = $this->getRequest();
636 $drequest = $this->getDiffusionRequest();
638 // NOTE: We need to get the grandparent so we can capture filename changes
639 // in the parent.
641 $parent = $this->loadParentCommitOf($before);
642 $old_filename = null;
643 $was_created = false;
644 if ($parent) {
645 $grandparent = $this->loadParentCommitOf($parent);
647 if ($grandparent) {
648 $rename_query = new DiffusionRenameHistoryQuery();
649 $rename_query->setRequest($drequest);
650 $rename_query->setOldCommit($grandparent);
651 $rename_query->setViewer($request->getUser());
652 $old_filename = $rename_query->loadOldFilename();
653 $was_created = $rename_query->getWasCreated();
657 $follow = null;
658 if ($was_created) {
659 // If the file was created in history, that means older commits won't
660 // have it. Since we know it existed at 'before', it must have been
661 // created then; jump there.
662 $target_commit = $before;
663 $follow = 'created';
664 } else if ($parent) {
665 // If we found a parent, jump to it. This is the normal case.
666 $target_commit = $parent;
667 } else {
668 // If there's no parent, this was probably created in the initial commit?
669 // And the "was_created" check will fail because we can't identify the
670 // grandparent. Keep the user at 'before'.
671 $target_commit = $before;
672 $follow = 'first';
675 $path = $drequest->getPath();
676 $renamed = null;
677 if ($old_filename !== null &&
678 $old_filename !== '/'.$path) {
679 $renamed = $path;
680 $path = $old_filename;
683 $line = null;
684 // If there's a follow error, drop the line so the user sees the message.
685 if (!$follow) {
686 $line = $this->getBeforeLineNumber($target_commit);
689 $before_uri = $drequest->generateURI(
690 array(
691 'action' => 'browse',
692 'commit' => $target_commit,
693 'line' => $line,
694 'path' => $path,
697 if ($renamed === null) {
698 $before_uri->removeQueryParam('renamed');
699 } else {
700 $before_uri->replaceQueryParam('renamed', $renamed);
703 if ($follow === null) {
704 $before_uri->removeQueryParam('follow');
705 } else {
706 $before_uri->replaceQueryParam('follow', $follow);
709 return id(new AphrontRedirectResponse())->setURI($before_uri);
712 private function getBeforeLineNumber($target_commit) {
713 $drequest = $this->getDiffusionRequest();
714 $viewer = $this->getViewer();
716 $line = $drequest->getLine();
717 if (!$line) {
718 return null;
721 $diff_info = $this->callConduitWithDiffusionRequest(
722 'diffusion.rawdiffquery',
723 array(
724 'commit' => $drequest->getCommit(),
725 'path' => $drequest->getPath(),
726 'againstCommit' => $target_commit,
729 $file_phid = $diff_info['filePHID'];
730 $file = id(new PhabricatorFileQuery())
731 ->setViewer($viewer)
732 ->withPHIDs(array($file_phid))
733 ->executeOne();
734 if (!$file) {
735 throw new Exception(
736 pht(
737 'Failed to load file ("%s") returned by "%s".',
738 $file_phid,
739 'diffusion.rawdiffquery.'));
742 $raw_diff = $file->loadFileData();
744 $old_line = 0;
745 $new_line = 0;
747 foreach (explode("\n", $raw_diff) as $text) {
748 if ($text[0] == '-' || $text[0] == ' ') {
749 $old_line++;
751 if ($text[0] == '+' || $text[0] == ' ') {
752 $new_line++;
754 if ($new_line == $line) {
755 return $old_line;
759 // We didn't find the target line.
760 return $line;
763 private function loadParentCommitOf($commit) {
764 $drequest = $this->getDiffusionRequest();
765 $user = $this->getRequest()->getUser();
767 $before_req = DiffusionRequest::newFromDictionary(
768 array(
769 'user' => $user,
770 'repository' => $drequest->getRepository(),
771 'commit' => $commit,
774 $parents = DiffusionQuery::callConduitWithDiffusionRequest(
775 $user,
776 $before_req,
777 'diffusion.commitparentsquery',
778 array(
779 'commit' => $commit,
782 return head($parents);
785 protected function markupText($text) {
786 $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine();
787 $engine->setConfig('viewer', $this->getRequest()->getUser());
788 $text = $engine->markupText($text);
790 $text = phutil_tag(
791 'div',
792 array(
793 'class' => 'phabricator-remarkup',
795 $text);
797 return $text;
800 protected function buildHeaderView(DiffusionRequest $drequest) {
801 $viewer = $this->getViewer();
802 $repository = $drequest->getRepository();
804 $commit_tag = $this->renderCommitHashTag($drequest);
806 $path = nonempty($drequest->getPath(), '/');
808 $search = $this->renderSearchForm($path);
810 $header = id(new PHUIHeaderView())
811 ->setUser($viewer)
812 ->setHeader($this->renderPathLinks($drequest, $mode = 'browse'))
813 ->addActionItem($search)
814 ->addTag($commit_tag)
815 ->addClass('diffusion-browse-header');
817 if (!$repository->isSVN()) {
818 $branch_tag = $this->renderBranchTag($drequest);
819 $header->addTag($branch_tag);
822 return $header;
825 protected function buildPanelHeaderView($title, $icon) {
827 $header = id(new PHUIHeaderView())
828 ->setHeader($title)
829 ->setHeaderIcon($icon)
830 ->addClass('diffusion-panel-header-view');
832 return $header;
836 protected function buildActionButtons(
837 DiffusionRequest $drequest,
838 $is_directory = false) {
840 $viewer = $this->getViewer();
841 $repository = $drequest->getRepository();
842 $history_uri = $drequest->generateURI(array('action' => 'history'));
843 $behind_head = $drequest->getSymbolicCommit();
844 $compare = null;
845 $head_uri = $drequest->generateURI(
846 array(
847 'commit' => '',
848 'action' => 'browse',
851 if ($repository->supportsBranchComparison() && $is_directory) {
852 $compare_uri = $drequest->generateURI(array('action' => 'compare'));
853 $compare = id(new PHUIButtonView())
854 ->setText(pht('Compare'))
855 ->setIcon('fa-code-fork')
856 ->setWorkflow(true)
857 ->setTag('a')
858 ->setHref($compare_uri)
859 ->setColor(PHUIButtonView::GREY);
860 $this->corpusButtons[] = $compare;
863 $head = null;
864 if ($behind_head) {
865 $head = id(new PHUIButtonView())
866 ->setTag('a')
867 ->setText(pht('Back to HEAD'))
868 ->setHref($head_uri)
869 ->setIcon('fa-home')
870 ->setColor(PHUIButtonView::GREY);
871 $this->corpusButtons[] = $head;
874 $history = id(new PHUIButtonView())
875 ->setText(pht('History'))
876 ->setHref($history_uri)
877 ->setTag('a')
878 ->setIcon('fa-history')
879 ->setColor(PHUIButtonView::GREY);
880 $this->corpusButtons[] = $history;
884 protected function buildPropertyView(
885 DiffusionRequest $drequest) {
887 $viewer = $this->getViewer();
888 $view = id(new PHUIPropertyListView())
889 ->setUser($viewer);
891 if ($drequest->getSymbolicType() == 'tag') {
892 $symbolic = $drequest->getSymbolicCommit();
893 $view->addProperty(pht('Tag'), $symbolic);
895 $tags = $this->callConduitWithDiffusionRequest(
896 'diffusion.tagsquery',
897 array(
898 'names' => array($symbolic),
899 'needMessages' => true,
901 $tags = DiffusionRepositoryTag::newFromConduit($tags);
903 $tags = mpull($tags, null, 'getName');
904 $tag = idx($tags, $symbolic);
906 if ($tag && strlen($tag->getMessage())) {
907 $view->addSectionHeader(
908 pht('Tag Content'), 'fa-tag');
909 $view->addTextContent($this->markupText($tag->getMessage()));
913 if ($view->hasAnyProperties()) {
914 return $view;
917 return null;
920 private function buildOpenRevisions() {
921 $viewer = $this->getViewer();
923 $drequest = $this->getDiffusionRequest();
924 $repository = $drequest->getRepository();
925 $path = $drequest->getPath();
927 $recent = (PhabricatorTime::getNow() - phutil_units('30 days in seconds'));
929 $revisions = id(new DifferentialRevisionQuery())
930 ->setViewer($viewer)
931 ->withPaths(array($path))
932 ->withRepositoryPHIDs(array($repository->getPHID()))
933 ->withIsOpen(true)
934 ->withUpdatedEpochBetween($recent, null)
935 ->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED)
936 ->setLimit(10)
937 ->needReviewers(true)
938 ->needFlags(true)
939 ->needDrafts(true)
940 ->execute();
942 if (!$revisions) {
943 return null;
946 $header = id(new PHUIHeaderView())
947 ->setHeader(pht('Recent Open Revisions'));
949 $list = id(new DifferentialRevisionListView())
950 ->setViewer($viewer)
951 ->setRevisions($revisions)
952 ->setNoBox(true);
954 $view = id(new PHUIObjectBoxView())
955 ->setHeader($header)
956 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
957 ->addClass('diffusion-mobile-view')
958 ->appendChild($list);
960 return $view;
963 private function getGitLFSRef(PhabricatorRepository $repository, $data) {
964 if (!$repository->canUseGitLFS()) {
965 return null;
968 $lfs_pattern = '(^version https://git-lfs\\.github\\.com/spec/v1[\r\n])';
969 if (!preg_match($lfs_pattern, $data)) {
970 return null;
973 $matches = null;
974 if (!preg_match('(^oid sha256:(.*)$)m', $data, $matches)) {
975 return null;
978 $hash = $matches[1];
979 $hash = trim($hash);
981 return id(new PhabricatorRepositoryGitLFSRefQuery())
982 ->setViewer($this->getViewer())
983 ->withRepositoryPHIDs(array($repository->getPHID()))
984 ->withObjectHashes(array($hash))
985 ->executeOne();
988 private function buildGitLFSCorpus(PhabricatorRepositoryGitLFSRef $ref) {
989 // TODO: We should probably test if we can load the file PHID here and
990 // show the user an error if we can't, rather than making them click
991 // through to hit an error.
993 $title = basename($this->getDiffusionRequest()->getPath());
994 $icon = 'fa-archive';
995 $drequest = $this->getDiffusionRequest();
996 $this->buildActionButtons($drequest);
997 $header = $this->buildPanelHeaderView($title, $icon);
999 $severity = PHUIInfoView::SEVERITY_NOTICE;
1001 $messages = array();
1002 $messages[] = pht(
1003 'This %s file is stored in Git Large File Storage.',
1004 phutil_format_bytes($ref->getByteSize()));
1006 try {
1007 $file = $this->loadGitLFSFile($ref);
1008 $this->corpusButtons[] = $this->renderGitLFSButton();
1009 } catch (Exception $ex) {
1010 $severity = PHUIInfoView::SEVERITY_ERROR;
1011 $messages[] = pht('The data for this file could not be loaded.');
1014 $this->corpusButtons[] = $this->renderFileButton(
1015 null, pht('View Raw LFS Pointer'));
1017 $corpus = id(new PHUIObjectBoxView())
1018 ->setHeader($header)
1019 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
1020 ->addClass('diffusion-mobile-view')
1021 ->setCollapsed(true);
1023 if ($messages) {
1024 $corpus->setInfoView(
1025 id(new PHUIInfoView())
1026 ->setSeverity($severity)
1027 ->setErrors($messages));
1030 return $corpus;
1033 private function loadGitLFSFile(PhabricatorRepositoryGitLFSRef $ref) {
1034 $viewer = $this->getViewer();
1036 $file = id(new PhabricatorFileQuery())
1037 ->setViewer($viewer)
1038 ->withPHIDs(array($ref->getFilePHID()))
1039 ->executeOne();
1040 if (!$file) {
1041 throw new Exception(
1042 pht(
1043 'Failed to load file object for Git LFS ref "%s"!',
1044 $ref->getObjectHash()));
1047 return $file;