@recaptime-dev's working patches + fork for Phorge, a community fork of Phabricator. (Upstream dev and stable branches are at upstream/main and upstream/stable respectively.) hq.recaptime.dev/wiki/Phorge
phorge phabricator
at recaptime-dev/main 1049 lines 29 kB view raw
1<?php 2 3final class DiffusionBrowseController extends DiffusionController { 4 5 private $lintCommit; 6 private $lintMessages; 7 private $corpusButtons = array(); 8 9 public function shouldAllowPublic() { 10 return true; 11 } 12 13 public function handleRequest(AphrontRequest $request) { 14 $response = $this->loadDiffusionContext(); 15 if ($response) { 16 return $response; 17 } 18 19 $drequest = $this->getDiffusionRequest(); 20 21 // Figure out if we're browsing a directory, a file, or a search result 22 // list. 23 24 $grep = $request->getStr('grep'); 25 if (phutil_nonempty_string($grep)) { 26 return $this->browseSearch(); 27 } 28 29 $pager = id(new PHUIPagerView()) 30 ->readFromRequest($request); 31 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 ))); 41 42 $reason = $results->getReasonForEmptyResultSet(); 43 $is_file = ($reason == DiffusionBrowseResultSet::REASON_IS_FILE); 44 45 if ($is_file) { 46 return $this->browseFile(); 47 } 48 49 $paths = $results->getPaths(); 50 $paths = $pager->sliceResults($paths); 51 $results->setPaths($paths); 52 53 return $this->browseDirectory($results, $pager); 54 } 55 56 private function browseSearch() { 57 $drequest = $this->getDiffusionRequest(); 58 $header = $this->buildHeaderView($drequest); 59 $path = nonempty(basename($drequest->getPath()), '/'); 60 61 $search_results = $this->renderSearchResults(); 62 $search_form = $this->renderSearchForm($path); 63 64 $search_form = phutil_tag( 65 'div', 66 array( 67 'class' => 'diffusion-mobile-search-form', 68 ), 69 $search_form); 70 71 $crumbs = $this->buildCrumbs( 72 array( 73 'branch' => true, 74 'path' => true, 75 'view' => 'browse', 76 )); 77 $crumbs->setBorder(true); 78 79 $tabs = $this->buildTabsView('code'); 80 81 $view = id(new PHUITwoColumnView()) 82 ->setHeader($header) 83 ->setTabs($tabs) 84 ->setFooter( 85 array( 86 $search_form, 87 $search_results, 88 )); 89 90 return $this->newPage() 91 ->setTitle( 92 array( 93 nonempty(basename($drequest->getPath()), '/'), 94 $drequest->getRepository()->getDisplayName(), 95 )) 96 ->setCrumbs($crumbs) 97 ->appendChild($view); 98 } 99 100 private function browseFile() { 101 $viewer = $this->getViewer(); 102 $request = $this->getRequest(); 103 $drequest = $this->getDiffusionRequest(); 104 $repository = $drequest->getRepository(); 105 106 $before = $request->getStr('before'); 107 if ($before) { 108 return $this->buildBeforeResponse($before); 109 } 110 111 $path = $drequest->getPath(); 112 $params = array( 113 'commit' => $drequest->getCommit(), 114 'path' => $drequest->getPath(), 115 ); 116 117 $view = $request->getStr('view'); 118 119 $byte_limit = null; 120 if ($view !== 'raw') { 121 $byte_limit = PhabricatorFileStorageEngine::getChunkThreshold(); 122 $time_limit = 10; 123 124 $params += array( 125 'timeout' => $time_limit, 126 'byteLimit' => $byte_limit, 127 ); 128 } 129 130 $response = $this->callConduitWithDiffusionRequest( 131 'diffusion.filecontentquery', 132 $params); 133 134 $hit_byte_limit = $response['tooHuge']; 135 $hit_time_limit = $response['tooSlow']; 136 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 bytes, 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!')); 158 } 159 160 if ($view === 'raw') { 161 return $file->getRedirectResponse(); 162 } 163 164 $data = $file->loadFileData(); 165 166 $lfs_ref = $this->getGitLFSRef($repository, $data); 167 if ($lfs_ref) { 168 if ($view == 'git-lfs') { 169 $file = $this->loadGitLFSFile($lfs_ref); 170 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); 178 179 return $file->getRedirectResponse(); 180 } 181 182 $corpus = $this->buildGitLFSCorpus($lfs_ref); 183 } else { 184 $show_editor = true; 185 186 $ref = id(new PhabricatorDocumentRef()) 187 ->setFile($file); 188 189 $engine = id(new DiffusionDocumentRenderingEngine()) 190 ->setRequest($request) 191 ->setDiffusionRequest($drequest); 192 193 $corpus = $engine->newDocumentView($ref); 194 195 $this->corpusButtons[] = $this->renderFileButton(); 196 } 197 } 198 199 $bar = $this->buildButtonBar($drequest, $show_editor); 200 $header = $this->buildHeaderView($drequest); 201 $header->setHeaderIcon('fa-file-code-o'); 202 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; 222 } 223 } 224 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)); 236 } 237 238 $open_revisions = $this->buildOpenRevisions(); 239 $owners_list = $this->buildOwnersList($drequest); 240 241 $crumbs = $this->buildCrumbs( 242 array( 243 'branch' => true, 244 'path' => true, 245 'view' => 'browse', 246 )); 247 $crumbs->setBorder(true); 248 249 $basename = basename($this->getDiffusionRequest()->getPath()); 250 $tabs = $this->buildTabsView('code'); 251 $bar->setRight($this->corpusButtons); 252 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, 263 )); 264 265 $title = array($basename, $repository->getDisplayName()); 266 267 return $this->newPage() 268 ->setTitle($title) 269 ->setCrumbs($crumbs) 270 ->appendChild( 271 array( 272 $view, 273 )); 274 275 } 276 277 public function browseDirectory( 278 DiffusionBrowseResultSet $results, 279 PHUIPagerView $pager) { 280 281 $request = $this->getRequest(); 282 $drequest = $this->getDiffusionRequest(); 283 $repository = $drequest->getRepository(); 284 285 $reason = $results->getReasonForEmptyResultSet(); 286 287 $this->buildActionButtons($drequest, true); 288 $details = $this->buildPropertyView($drequest); 289 290 $header = $this->buildHeaderView($drequest); 291 $header->setHeaderIcon('fa-folder-open'); 292 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()); 305 306 $title = nonempty(basename($drequest->getPath()), '/'); 307 $icon = 'fa-folder-open'; 308 $browse_header = $this->buildPanelHeaderView($title, $icon); 309 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); 316 } 317 318 $open_revisions = $this->buildOpenRevisions(); 319 $readme = $this->renderDirectoryReadme($results); 320 321 $crumbs = $this->buildCrumbs( 322 array( 323 'branch' => true, 324 'path' => true, 325 'view' => 'browse', 326 )); 327 328 $crumbs->setBorder(true); 329 $locate_file = $this->buildLocateFile(); 330 $tabs = $this->buildTabsView('code'); 331 $owners_list = $this->buildOwnersList($drequest); 332 $bar = id(new PHUILeftRightView()) 333 ->setLeft($locate_file) 334 ->setRight($this->corpusButtons) 335 ->addClass('diffusion-action-bar'); 336 337 $view = id(new PHUITwoColumnView()) 338 ->setHeader($header) 339 ->setTabs($tabs) 340 ->setFooter( 341 array( 342 $bar, 343 $empty_result, 344 $browse_panel, 345 $open_revisions, 346 $owners_list, 347 $readme, 348 )); 349 350 if ($details) { 351 $view->addPropertySection(pht('Details'), $details); 352 } 353 354 return $this->newPage() 355 ->setTitle(array( 356 nonempty(basename($drequest->getPath()), '/'), 357 $repository->getDisplayName(), 358 )) 359 ->setCrumbs($crumbs) 360 ->appendChild( 361 array( 362 $view, 363 )); 364 } 365 366 private function renderSearchResults() { 367 $request = $this->getRequest(); 368 369 $drequest = $this->getDiffusionRequest(); 370 $repository = $drequest->getRepository(); 371 $results = array(); 372 373 $pager = id(new PHUIPagerView()) 374 ->readFromRequest($request); 375 376 $search_mode = null; 377 switch ($repository->getVersionControlSystem()) { 378 case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 379 $results = array(); 380 break; 381 default: 382 if (strlen($this->getRequest()->getStr('grep'))) { 383 $search_mode = 'grep'; 384 $query_string = $request->getStr('grep'); 385 $results = $this->callConduitWithDiffusionRequest( 386 'diffusion.searchquery', 387 array( 388 'grep' => $query_string, 389 'commit' => $drequest->getStableCommit(), 390 'path' => $drequest->getPath(), 391 'limit' => $pager->getPageSize() + 1, 392 'offset' => $pager->getOffset(), 393 )); 394 } 395 break; 396 } 397 $results = $pager->sliceResults($results); 398 399 $table = null; 400 $header = null; 401 if ($search_mode == 'grep') { 402 $table = $this->renderGrepResults($results, $query_string); 403 $title = pht( 404 'File content matching "%s" under "%s"', 405 $query_string, 406 nonempty($drequest->getPath(), '/')); 407 $header = id(new PHUIHeaderView()) 408 ->setHeader($title) 409 ->addClass('diffusion-search-result-header'); 410 } 411 412 return array($header, $table, $pager); 413 414 } 415 416 private function renderGrepResults(array $results, $pattern) { 417 $drequest = $this->getDiffusionRequest(); 418 require_celerity_resource('phabricator-search-results-css'); 419 420 if (!$results) { 421 return id(new PHUIInfoView()) 422 ->setSeverity(PHUIInfoView::SEVERITY_NODATA) 423 ->appendChild( 424 pht( 425 'The pattern you searched for was not found in the content of any '. 426 'files.')); 427 } 428 429 $grouped = array(); 430 foreach ($results as $file) { 431 list($path, $line, $string) = $file; 432 $grouped[$path][] = array($line, $string); 433 } 434 435 $view = array(); 436 foreach ($grouped as $path => $matches) { 437 $view[] = id(new DiffusionPatternSearchView()) 438 ->setPath($path) 439 ->setMatches($matches) 440 ->setPattern($pattern) 441 ->setDiffusionRequest($drequest) 442 ->render(); 443 } 444 445 return $view; 446 } 447 448 private function buildButtonBar( 449 DiffusionRequest $drequest, 450 $show_editor) { 451 452 $viewer = $this->getViewer(); 453 $base_uri = $this->getRequest()->getRequestURI(); 454 455 $repository = $drequest->getRepository(); 456 $path = $drequest->getPath(); 457 $line = nonempty((int)$drequest->getLine(), 1); 458 $buttons = array(); 459 460 $editor_uri = null; 461 $editor_template = null; 462 463 $link_engine = PhabricatorEditorURIEngine::newForViewer($viewer); 464 if ($link_engine) { 465 $link_engine->setRepository($repository); 466 467 $editor_uri = $link_engine->getURIForPath($path, $line); 468 $editor_template = $link_engine->getURITokensForPath($path); 469 } 470 471 $buttons[] = 472 id(new PHUIButtonView()) 473 ->setTag('a') 474 ->setText(pht('Last Change')) 475 ->setColor(PHUIButtonView::GREY) 476 ->setHref( 477 $drequest->generateURI( 478 array( 479 'action' => 'change', 480 ))) 481 ->setIcon('fa-backward'); 482 483 if ($editor_uri) { 484 $buttons[] = 485 id(new PHUIButtonView()) 486 ->setTag('a') 487 ->setText(pht('Open File')) 488 ->setHref($editor_uri) 489 ->setIcon('fa-pencil') 490 ->setID('editor_link') 491 ->setMetadata(array('template' => $editor_template)) 492 ->setDisabled(!$editor_uri) 493 ->setColor(PHUIButtonView::GREY); 494 } 495 496 $bar = id(new PHUILeftRightView()) 497 ->setLeft($buttons) 498 ->addClass('diffusion-action-bar full-mobile-buttons'); 499 return $bar; 500 } 501 502 private function buildOwnersList(DiffusionRequest $drequest) { 503 $viewer = $this->getViewer(); 504 505 $have_owners = PhabricatorApplication::isClassInstalledForViewer( 506 PhabricatorOwnersApplication::class, 507 $viewer); 508 if (!$have_owners) { 509 return null; 510 } 511 512 $repository = $drequest->getRepository(); 513 514 $package_query = id(new PhabricatorOwnersPackageQuery()) 515 ->setViewer($viewer) 516 ->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE)) 517 ->withControl( 518 $repository->getPHID(), 519 array( 520 $drequest->getPath(), 521 )); 522 523 $package_query->execute(); 524 525 $packages = $package_query->getControllingPackagesForPath( 526 $repository->getPHID(), 527 $drequest->getPath()); 528 529 $ownership = id(new PHUIObjectItemListView()) 530 ->setUser($viewer) 531 ->setNoDataString(pht('No Owners')); 532 533 if ($packages) { 534 foreach ($packages as $package) { 535 $item = id(new PHUIObjectItemView()) 536 ->setObject($package) 537 ->setObjectName($package->getMonogram()) 538 ->setHeader($package->getName()) 539 ->setHref($package->getURI()); 540 541 $owners = $package->getOwners(); 542 if ($owners) { 543 $owner_list = $viewer->renderHandleList( 544 mpull($owners, 'getUserPHID')); 545 } else { 546 $owner_list = phutil_tag('em', array(), pht('None')); 547 } 548 $item->addAttribute(pht('Owners: %s', $owner_list)); 549 550 $auto = $package->getAutoReview(); 551 $autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap(); 552 $spec = idx($autoreview_map, $auto, array()); 553 $name = idx($spec, 'name', $auto); 554 $item->addIcon('fa-code', $name); 555 556 $rule = $package->newAuditingRule(); 557 $item->addIcon($rule->getIconIcon(), $rule->getDisplayName()); 558 559 if ($package->isArchived()) { 560 $item->setDisabled(true); 561 } 562 563 $ownership->addItem($item); 564 } 565 } 566 567 $view = id(new PHUIObjectBoxView()) 568 ->setHeaderText(pht('Owner Packages')) 569 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 570 ->addClass('diffusion-mobile-view') 571 ->setObjectList($ownership); 572 573 return $view; 574 } 575 576 private function renderFileButton($file_uri = null, $label = null) { 577 578 $base_uri = $this->getRequest()->getRequestURI(); 579 580 if ($file_uri) { 581 $text = pht('Download File'); 582 $href = $file_uri; 583 $icon = 'fa-download'; 584 } else { 585 $text = pht('Raw File'); 586 $href = $base_uri->alter('view', 'raw'); 587 $icon = 'fa-file-text'; 588 } 589 590 if ($label !== null) { 591 $text = $label; 592 } 593 594 $button = id(new PHUIButtonView()) 595 ->setTag('a') 596 ->setText($text) 597 ->setHref($href) 598 ->setIcon($icon) 599 ->setColor(PHUIButtonView::GREY); 600 601 return $button; 602 } 603 604 private function renderGitLFSButton() { 605 $viewer = $this->getViewer(); 606 607 $uri = $this->getRequest()->getRequestURI(); 608 $href = $uri->alter('view', 'git-lfs'); 609 610 $text = pht('Download from Git LFS'); 611 $icon = 'fa-download'; 612 613 return id(new PHUIButtonView()) 614 ->setTag('a') 615 ->setText($text) 616 ->setHref($href) 617 ->setIcon($icon) 618 ->setColor(PHUIButtonView::GREY); 619 } 620 621 private function buildErrorCorpus($message) { 622 $text = id(new PHUIBoxView()) 623 ->addPadding(PHUI::PADDING_LARGE) 624 ->appendChild($message); 625 626 $header = id(new PHUIHeaderView()) 627 ->setHeader(pht('Details')); 628 629 $box = id(new PHUIObjectBoxView()) 630 ->setHeader($header) 631 ->appendChild($text); 632 633 return $box; 634 } 635 636 private function buildBeforeResponse($before) { 637 $request = $this->getRequest(); 638 $drequest = $this->getDiffusionRequest(); 639 640 // NOTE: We need to get the grandparent so we can capture filename changes 641 // in the parent. 642 643 $parent = $this->loadParentCommitOf($before); 644 $old_filename = null; 645 $was_created = false; 646 if ($parent) { 647 $grandparent = $this->loadParentCommitOf($parent); 648 649 if ($grandparent) { 650 $rename_query = new DiffusionRenameHistoryQuery(); 651 $rename_query->setRequest($drequest); 652 $rename_query->setOldCommit($grandparent); 653 $rename_query->setViewer($request->getUser()); 654 $old_filename = $rename_query->loadOldFilename(); 655 $was_created = $rename_query->getWasCreated(); 656 } 657 } 658 659 $follow = null; 660 if ($was_created) { 661 // If the file was created in history, that means older commits won't 662 // have it. Since we know it existed at 'before', it must have been 663 // created then; jump there. 664 $target_commit = $before; 665 $follow = 'created'; 666 } else if ($parent) { 667 // If we found a parent, jump to it. This is the normal case. 668 $target_commit = $parent; 669 } else { 670 // If there's no parent, this was probably created in the initial commit? 671 // And the "was_created" check will fail because we can't identify the 672 // grandparent. Keep the user at 'before'. 673 $target_commit = $before; 674 $follow = 'first'; 675 } 676 677 $path = $drequest->getPath(); 678 $renamed = null; 679 if ($old_filename !== null && 680 $old_filename !== '/'.$path) { 681 $renamed = $path; 682 $path = $old_filename; 683 } 684 685 $line = null; 686 // If there's a follow error, drop the line so the user sees the message. 687 if (!$follow) { 688 $line = $this->getBeforeLineNumber($target_commit); 689 } 690 691 $before_uri = $drequest->generateURI( 692 array( 693 'action' => 'browse', 694 'commit' => $target_commit, 695 'line' => $line, 696 'path' => $path, 697 )); 698 699 if ($renamed === null) { 700 $before_uri->removeQueryParam('renamed'); 701 } else { 702 $before_uri->replaceQueryParam('renamed', $renamed); 703 } 704 705 if ($follow === null) { 706 $before_uri->removeQueryParam('follow'); 707 } else { 708 $before_uri->replaceQueryParam('follow', $follow); 709 } 710 711 return id(new AphrontRedirectResponse())->setURI($before_uri); 712 } 713 714 private function getBeforeLineNumber($target_commit) { 715 $drequest = $this->getDiffusionRequest(); 716 $viewer = $this->getViewer(); 717 718 $line = $drequest->getLine(); 719 if (!$line) { 720 return null; 721 } 722 723 $diff_info = $this->callConduitWithDiffusionRequest( 724 'diffusion.rawdiffquery', 725 array( 726 'commit' => $drequest->getCommit(), 727 'path' => $drequest->getPath(), 728 'againstCommit' => $target_commit, 729 )); 730 731 $file_phid = $diff_info['filePHID']; 732 $file = id(new PhabricatorFileQuery()) 733 ->setViewer($viewer) 734 ->withPHIDs(array($file_phid)) 735 ->executeOne(); 736 if (!$file) { 737 throw new Exception( 738 pht( 739 'Failed to load file ("%s") returned by "%s".', 740 $file_phid, 741 'diffusion.rawdiffquery.')); 742 } 743 744 $raw_diff = $file->loadFileData(); 745 746 $old_line = 0; 747 $new_line = 0; 748 749 foreach (explode("\n", $raw_diff) as $text) { 750 if ($text[0] == '-' || $text[0] == ' ') { 751 $old_line++; 752 } 753 if ($text[0] == '+' || $text[0] == ' ') { 754 $new_line++; 755 } 756 if ($new_line == $line) { 757 return $old_line; 758 } 759 } 760 761 // We didn't find the target line. 762 return $line; 763 } 764 765 private function loadParentCommitOf($commit) { 766 $drequest = $this->getDiffusionRequest(); 767 $user = $this->getRequest()->getUser(); 768 769 $before_req = DiffusionRequest::newFromDictionary( 770 array( 771 'user' => $user, 772 'repository' => $drequest->getRepository(), 773 'commit' => $commit, 774 )); 775 776 $parents = DiffusionQuery::callConduitWithDiffusionRequest( 777 $user, 778 $before_req, 779 'diffusion.commitparentsquery', 780 array( 781 'commit' => $commit, 782 )); 783 784 return head($parents); 785 } 786 787 protected function markupText($text) { 788 $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine(); 789 $engine->setConfig('viewer', $this->getRequest()->getUser()); 790 $text = $engine->markupText($text); 791 792 $text = phutil_tag( 793 'div', 794 array( 795 'class' => 'phabricator-remarkup', 796 ), 797 $text); 798 799 return $text; 800 } 801 802 protected function buildHeaderView(DiffusionRequest $drequest) { 803 $viewer = $this->getViewer(); 804 $repository = $drequest->getRepository(); 805 806 $commit_tag = $this->renderCommitHashTag($drequest); 807 808 $path = nonempty($drequest->getPath(), '/'); 809 810 $search = $this->renderSearchForm($path); 811 812 $header = id(new PHUIHeaderView()) 813 ->setUser($viewer) 814 ->setHeader($this->renderPathLinks($drequest, $mode = 'browse')) 815 ->addActionItem($search) 816 ->addTag($commit_tag) 817 ->addClass('diffusion-browse-header'); 818 819 if (!$repository->isSVN()) { 820 $branch_tag = $this->renderBranchTag($drequest); 821 $header->addTag($branch_tag); 822 } 823 824 return $header; 825 } 826 827 protected function buildPanelHeaderView($title, $icon) { 828 829 $header = id(new PHUIHeaderView()) 830 ->setHeader($title) 831 ->setHeaderIcon($icon) 832 ->addClass('diffusion-panel-header-view'); 833 834 return $header; 835 836 } 837 838 protected function buildActionButtons( 839 DiffusionRequest $drequest, 840 $is_directory = false) { 841 842 $viewer = $this->getViewer(); 843 $repository = $drequest->getRepository(); 844 $history_uri = $drequest->generateURI(array('action' => 'history')); 845 $behind_head = $drequest->getSymbolicCommit(); 846 $compare = null; 847 $head_uri = $drequest->generateURI( 848 array( 849 'commit' => '', 850 'action' => 'browse', 851 )); 852 853 if ($repository->supportsBranchComparison() && $is_directory) { 854 $compare_uri = $drequest->generateURI(array('action' => 'compare')); 855 $compare = id(new PHUIButtonView()) 856 ->setText(pht('Compare')) 857 ->setIcon('fa-code-fork') 858 ->setWorkflow(true) 859 ->setTag('a') 860 ->setHref($compare_uri) 861 ->setColor(PHUIButtonView::GREY); 862 $this->corpusButtons[] = $compare; 863 } 864 865 $head = null; 866 if ($behind_head) { 867 $head = id(new PHUIButtonView()) 868 ->setTag('a') 869 ->setText(pht('Back to HEAD')) 870 ->setHref($head_uri) 871 ->setIcon('fa-home') 872 ->setColor(PHUIButtonView::GREY); 873 $this->corpusButtons[] = $head; 874 } 875 876 $history = id(new PHUIButtonView()) 877 ->setText(pht('History')) 878 ->setHref($history_uri) 879 ->setTag('a') 880 ->setIcon('fa-history') 881 ->setColor(PHUIButtonView::GREY); 882 $this->corpusButtons[] = $history; 883 884 } 885 886 protected function buildPropertyView( 887 DiffusionRequest $drequest) { 888 889 $viewer = $this->getViewer(); 890 $view = id(new PHUIPropertyListView()) 891 ->setUser($viewer); 892 893 if ($drequest->getSymbolicType() == 'tag') { 894 $symbolic = $drequest->getSymbolicCommit(); 895 $view->addProperty(pht('Tag'), $symbolic); 896 897 $tags = $this->callConduitWithDiffusionRequest( 898 'diffusion.tagsquery', 899 array( 900 'names' => array($symbolic), 901 'needMessages' => true, 902 )); 903 $tags = DiffusionRepositoryTag::newFromConduit($tags); 904 905 $tags = mpull($tags, null, 'getName'); 906 $tag = idx($tags, $symbolic); 907 908 if ($tag && strlen($tag->getMessage())) { 909 $view->addSectionHeader( 910 pht('Tag Content'), 'fa-tag'); 911 $view->addTextContent($this->markupText($tag->getMessage())); 912 } 913 } 914 915 if ($view->hasAnyProperties()) { 916 return $view; 917 } 918 919 return null; 920 } 921 922 private function buildOpenRevisions() { 923 $viewer = $this->getViewer(); 924 925 $drequest = $this->getDiffusionRequest(); 926 $repository = $drequest->getRepository(); 927 $path = $drequest->getPath(); 928 929 $recent = (PhabricatorTime::getNow() - phutil_units('30 days in seconds')); 930 931 $revisions = id(new DifferentialRevisionQuery()) 932 ->setViewer($viewer) 933 ->withPaths(array($path)) 934 ->withRepositoryPHIDs(array($repository->getPHID())) 935 ->withIsOpen(true) 936 ->withUpdatedEpochBetween($recent, null) 937 ->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED) 938 ->setLimit(10) 939 ->needReviewers(true) 940 ->needFlags(true) 941 ->needDrafts(true) 942 ->execute(); 943 944 if (!$revisions) { 945 return null; 946 } 947 948 $header = id(new PHUIHeaderView()) 949 ->setHeader(pht('Recent Open Revisions')); 950 951 $list = id(new DifferentialRevisionListView()) 952 ->setViewer($viewer) 953 ->setRevisions($revisions) 954 ->setNoBox(true); 955 956 $view = id(new PHUIObjectBoxView()) 957 ->setHeader($header) 958 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 959 ->addClass('diffusion-mobile-view') 960 ->appendChild($list); 961 962 return $view; 963 } 964 965 private function getGitLFSRef(PhabricatorRepository $repository, $data) { 966 if (!$repository->canUseGitLFS()) { 967 return null; 968 } 969 970 $lfs_pattern = '(^version https://git-lfs\\.github\\.com/spec/v1[\r\n])'; 971 if (!preg_match($lfs_pattern, $data)) { 972 return null; 973 } 974 975 $matches = null; 976 if (!preg_match('(^oid sha256:(.*)$)m', $data, $matches)) { 977 return null; 978 } 979 980 $hash = $matches[1]; 981 $hash = trim($hash); 982 983 return id(new PhabricatorRepositoryGitLFSRefQuery()) 984 ->setViewer($this->getViewer()) 985 ->withRepositoryPHIDs(array($repository->getPHID())) 986 ->withObjectHashes(array($hash)) 987 ->executeOne(); 988 } 989 990 private function buildGitLFSCorpus(PhabricatorRepositoryGitLFSRef $ref) { 991 // TODO: We should probably test if we can load the file PHID here and 992 // show the user an error if we can't, rather than making them click 993 // through to hit an error. 994 995 $title = basename($this->getDiffusionRequest()->getPath()); 996 $icon = 'fa-archive'; 997 $drequest = $this->getDiffusionRequest(); 998 $this->buildActionButtons($drequest); 999 $header = $this->buildPanelHeaderView($title, $icon); 1000 1001 $severity = PHUIInfoView::SEVERITY_NOTICE; 1002 1003 $messages = array(); 1004 $messages[] = pht( 1005 'This %s file is stored in Git Large File Storage.', 1006 phutil_format_bytes($ref->getByteSize())); 1007 1008 try { 1009 $file = $this->loadGitLFSFile($ref); 1010 $this->corpusButtons[] = $this->renderGitLFSButton(); 1011 } catch (Exception $ex) { 1012 $severity = PHUIInfoView::SEVERITY_ERROR; 1013 $messages[] = pht('The data for this file could not be loaded.'); 1014 } 1015 1016 $this->corpusButtons[] = $this->renderFileButton( 1017 null, pht('View Raw LFS Pointer')); 1018 1019 $corpus = id(new PHUIObjectBoxView()) 1020 ->setHeader($header) 1021 ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY) 1022 ->addClass('diffusion-mobile-view') 1023 ->setCollapsed(true) 1024 ->setInfoView( 1025 id(new PHUIInfoView()) 1026 ->setSeverity($severity) 1027 ->setErrors($messages)); 1028 1029 return $corpus; 1030 } 1031 1032 private function loadGitLFSFile(PhabricatorRepositoryGitLFSRef $ref) { 1033 $viewer = $this->getViewer(); 1034 1035 $file = id(new PhabricatorFileQuery()) 1036 ->setViewer($viewer) 1037 ->withPHIDs(array($ref->getFilePHID())) 1038 ->executeOne(); 1039 if (!$file) { 1040 throw new Exception( 1041 pht( 1042 'Failed to load file object for Git LFS ref "%s"!', 1043 $ref->getObjectHash())); 1044 } 1045 1046 return $file; 1047 } 1048 1049}