@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
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Mostly make blame work with DocumentEngine

Summary: Ref T13105. This needs refinement but blame sort of works again, now.

Test Plan: Viewed files in Diffusion and Files; saw blame in Diffusion when viewing in source mode.

Reviewers: mydeveloperday

Reviewed By: mydeveloperday

Maniphest Tasks: T13105

Differential Revision: https://secure.phabricator.com/D19309

+513 -446
+16 -26
resources/celerity/map.php
··· 11 11 'conpherence.pkg.js' => '15191c65', 12 12 'core.pkg.css' => '4a83e174', 13 13 'core.pkg.js' => '1ea38af8', 14 - 'differential.pkg.css' => '113e692c', 15 - 'differential.pkg.js' => '3da2650a', 14 + 'differential.pkg.css' => '06dc617c', 15 + 'differential.pkg.js' => 'c2ca903a', 16 16 'diffusion.pkg.css' => 'a2d17c7d', 17 17 'diffusion.pkg.js' => '6134c5a1', 18 18 'maniphest.pkg.css' => '4845691a', ··· 61 61 'rsrc/css/application/dashboard/dashboard.css' => 'fe5b1869', 62 62 'rsrc/css/application/diff/inline-comment-summary.css' => 'f23d4e8f', 63 63 'rsrc/css/application/differential/add-comment.css' => 'c47f8c40', 64 - 'rsrc/css/application/differential/changeset-view.css' => 'bf84345b', 64 + 'rsrc/css/application/differential/changeset-view.css' => 'db34a142', 65 65 'rsrc/css/application/differential/core.css' => '5b7b8ff4', 66 66 'rsrc/css/application/differential/phui-inline-comment.css' => '65ae3bc2', 67 67 'rsrc/css/application/differential/revision-comment.css' => '14b8565a', ··· 71 71 'rsrc/css/application/diffusion/diffusion-icons.css' => '0c15255e', 72 72 'rsrc/css/application/diffusion/diffusion-readme.css' => '419dd5b6', 73 73 'rsrc/css/application/diffusion/diffusion-repository.css' => 'ee6f20ec', 74 - 'rsrc/css/application/diffusion/diffusion-source.css' => '5f35a3bd', 75 74 'rsrc/css/application/diffusion/diffusion.css' => '45727264', 76 75 'rsrc/css/application/feed/feed.css' => 'ecd4ec57', 77 76 'rsrc/css/application/files/global-drag-and-drop.css' => 'b556a948', ··· 120 119 'rsrc/css/font/font-lato.css' => 'c7ccd872', 121 120 'rsrc/css/font/phui-font-icon-base.css' => '870a7360', 122 121 'rsrc/css/layout/phabricator-filetree-view.css' => 'b912ad97', 123 - 'rsrc/css/layout/phabricator-source-code-view.css' => 'c6fc6834', 122 + 'rsrc/css/layout/phabricator-source-code-view.css' => 'af54e277', 124 123 'rsrc/css/phui/button/phui-button-bar.css' => 'f1ff5494', 125 124 'rsrc/css/phui/button/phui-button-simple.css' => '8e1baf68', 126 125 'rsrc/css/phui/button/phui-button.css' => '1863cc6e', ··· 387 386 'rsrc/js/application/diffusion/behavior-commit-graph.js' => '75b83cbb', 388 387 'rsrc/js/application/diffusion/behavior-diffusion-browse-file.js' => '054a0f0b', 389 388 'rsrc/js/application/diffusion/behavior-jump-to.js' => '73d09eef', 390 - 'rsrc/js/application/diffusion/behavior-load-blame.js' => '42126667', 391 389 'rsrc/js/application/diffusion/behavior-locate-file.js' => '6d3e1947', 392 390 'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc', 393 391 'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70', 394 392 'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef', 395 - 'rsrc/js/application/files/behavior-document-engine.js' => '9108ee1a', 393 + 'rsrc/js/application/files/behavior-document-engine.js' => 'ac52a3be', 396 394 'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab', 397 395 'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888', 398 396 'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909', ··· 543 541 'conpherence-thread-manager' => '4d863052', 544 542 'conpherence-transaction-css' => '85129c68', 545 543 'd3' => 'a11a5ff2', 546 - 'differential-changeset-view-css' => 'bf84345b', 544 + 'differential-changeset-view-css' => 'db34a142', 547 545 'differential-core-view-css' => '5b7b8ff4', 548 546 'differential-revision-add-comment-css' => 'c47f8c40', 549 547 'differential-revision-comment-css' => '14b8565a', ··· 554 552 'diffusion-icons-css' => '0c15255e', 555 553 'diffusion-readme-css' => '419dd5b6', 556 554 'diffusion-repository-css' => 'ee6f20ec', 557 - 'diffusion-source-css' => '5f35a3bd', 558 555 'diviner-shared-css' => '896f1d43', 559 556 'font-fontawesome' => 'e838e088', 560 557 'font-lato' => 'c7ccd872', ··· 607 604 'javelin-behavior-diffusion-jump-to' => '73d09eef', 608 605 'javelin-behavior-diffusion-locate-file' => '6d3e1947', 609 606 'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc', 610 - 'javelin-behavior-document-engine' => '9108ee1a', 607 + 'javelin-behavior-document-engine' => 'ac52a3be', 611 608 'javelin-behavior-doorkeeper-tag' => '1db13e70', 612 609 'javelin-behavior-drydock-live-operation-status' => '901935ef', 613 610 'javelin-behavior-durable-column' => '2ae077e1', ··· 624 621 'javelin-behavior-launch-icon-composer' => '48086888', 625 622 'javelin-behavior-lightbox-attachments' => '6b31879a', 626 623 'javelin-behavior-line-chart' => 'e4232876', 627 - 'javelin-behavior-load-blame' => '42126667', 628 624 'javelin-behavior-maniphest-batch-selector' => 'ad54037e', 629 625 'javelin-behavior-maniphest-list-editor' => 'a9f88de2', 630 626 'javelin-behavior-maniphest-subpriority-editor' => '71237763', ··· 784 780 'phabricator-search-results-css' => '505dd8cf', 785 781 'phabricator-shaped-request' => '7cbe244b', 786 782 'phabricator-slowvote-css' => 'a94b7230', 787 - 'phabricator-source-code-view-css' => 'c6fc6834', 783 + 'phabricator-source-code-view-css' => 'af54e277', 788 784 'phabricator-standard-page-view' => '34ee718b', 789 785 'phabricator-textareautils' => '320810c8', 790 786 'phabricator-title' => '485aaa6c', ··· 1153 1149 'phabricator-diff-changeset-list', 1154 1150 'phabricator-diff-changeset', 1155 1151 ), 1156 - 42126667 => array( 1157 - 'javelin-behavior', 1158 - 'javelin-dom', 1159 - 'javelin-request', 1160 - ), 1161 1152 '4250a34e' => array( 1162 1153 'javelin-behavior', 1163 1154 'javelin-dom', ··· 1632 1623 'javelin-stratcom', 1633 1624 'javelin-vector', 1634 1625 ), 1635 - '9108ee1a' => array( 1636 - 'javelin-behavior', 1637 - 'javelin-dom', 1638 - 'javelin-stratcom', 1639 - ), 1640 1626 '92b9ec77' => array( 1641 1627 'javelin-behavior', 1642 1628 'javelin-stratcom', ··· 1772 1758 'javelin-dom', 1773 1759 'javelin-typeahead-normalizer', 1774 1760 ), 1761 + 'ac52a3be' => array( 1762 + 'javelin-behavior', 1763 + 'javelin-dom', 1764 + 'javelin-stratcom', 1765 + ), 1775 1766 'acd29eee' => array( 1776 1767 'javelin-behavior', 1777 1768 'javelin-stratcom', ··· 1902 1893 'javelin-stratcom', 1903 1894 'javelin-dom', 1904 1895 ), 1905 - 'bf84345b' => array( 1906 - 'phui-inline-comment-view-css', 1907 - ), 1908 1896 'bff6884b' => array( 1909 1897 'javelin-install', 1910 1898 'javelin-dom', ··· 2020 2008 'javelin-dom', 2021 2009 'javelin-util', 2022 2010 'phabricator-shaped-request', 2011 + ), 2012 + 'db34a142' => array( 2013 + 'phui-inline-comment-view-css', 2023 2014 ), 2024 2015 'dca75c0e' => array( 2025 2016 'multirow-row-manager', ··· 2367 2358 'javelin-behavior-aphront-drag-and-drop-textarea', 2368 2359 'javelin-behavior-phabricator-object-selector', 2369 2360 'javelin-behavior-repository-crossreference', 2370 - 'javelin-behavior-load-blame', 2371 2361 'javelin-behavior-differential-user-select', 2372 2362 'javelin-behavior-aphront-more', 2373 2363 'phabricator-diff-inline',
-1
resources/celerity/packages.php
··· 199 199 'javelin-behavior-aphront-drag-and-drop-textarea', 200 200 'javelin-behavior-phabricator-object-selector', 201 201 'javelin-behavior-repository-crossreference', 202 - 'javelin-behavior-load-blame', 203 202 204 203 'javelin-behavior-differential-user-select', 205 204 'javelin-behavior-aphront-more',
+2
src/__phutil_library_map__.php
··· 632 632 'DiffusionAuditorsAddSelfHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsAddSelfHeraldAction.php', 633 633 'DiffusionAuditorsHeraldAction' => 'applications/diffusion/herald/DiffusionAuditorsHeraldAction.php', 634 634 'DiffusionBlameConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBlameConduitAPIMethod.php', 635 + 'DiffusionBlameController' => 'applications/diffusion/controller/DiffusionBlameController.php', 635 636 'DiffusionBlameQuery' => 'applications/diffusion/query/blame/DiffusionBlameQuery.php', 636 637 'DiffusionBlockHeraldAction' => 'applications/diffusion/herald/DiffusionBlockHeraldAction.php', 637 638 'DiffusionBranchListView' => 'applications/diffusion/view/DiffusionBranchListView.php', ··· 5889 5890 'DiffusionAuditorsAddSelfHeraldAction' => 'DiffusionAuditorsHeraldAction', 5890 5891 'DiffusionAuditorsHeraldAction' => 'HeraldAction', 5891 5892 'DiffusionBlameConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 5893 + 'DiffusionBlameController' => 'DiffusionController', 5892 5894 'DiffusionBlameQuery' => 'DiffusionQuery', 5893 5895 'DiffusionBlockHeraldAction' => 'HeraldAction', 5894 5896 'DiffusionBranchListView' => 'DiffusionView',
+2
src/applications/diffusion/application/PhabricatorDiffusionApplication.php
··· 55 55 'browse/(?P<dblob>.*)' => 'DiffusionBrowseController', 56 56 'document/(?P<dblob>.*)' 57 57 => 'DiffusionDocumentController', 58 + 'blame/(?P<dblob>.*)' 59 + => 'DiffusionBlameController', 58 60 'lastmodified/(?P<dblob>.*)' => 'DiffusionLastModifiedController', 59 61 'diff/' => 'DiffusionDiffController', 60 62 'tags/(?P<dblob>.*)' => 'DiffusionTagListController',
+254
src/applications/diffusion/controller/DiffusionBlameController.php
··· 1 + <?php 2 + 3 + final class DiffusionBlameController extends DiffusionController { 4 + 5 + public function shouldAllowPublic() { 6 + return true; 7 + } 8 + 9 + public function handleRequest(AphrontRequest $request) { 10 + $response = $this->loadDiffusionContext(); 11 + if ($response) { 12 + return $response; 13 + } 14 + 15 + $viewer = $this->getViewer(); 16 + $drequest = $this->getDiffusionRequest(); 17 + $repository = $drequest->getRepository(); 18 + 19 + $blame = $this->loadBlame(); 20 + 21 + $identifiers = array_fuse($blame); 22 + if ($identifiers) { 23 + $commits = id(new DiffusionCommitQuery()) 24 + ->setViewer($viewer) 25 + ->withRepository($repository) 26 + ->withIdentifiers($identifiers) 27 + ->execute(); 28 + $commits = mpull($commits, null, 'getCommitIdentifier'); 29 + } else { 30 + $commits = array(); 31 + } 32 + 33 + $commit_map = mpull($commits, 'getCommitIdentifier', 'getPHID'); 34 + 35 + $revisions = array(); 36 + $revision_map = array(); 37 + if ($commits) { 38 + $revision_ids = id(new DifferentialRevision()) 39 + ->loadIDsByCommitPHIDs(array_keys($commit_map)); 40 + if ($revision_ids) { 41 + $revisions = id(new DifferentialRevisionQuery()) 42 + ->setViewer($viewer) 43 + ->withIDs($revision_ids) 44 + ->execute(); 45 + $revisions = mpull($revisions, null, 'getID'); 46 + } 47 + 48 + foreach ($revision_ids as $commit_phid => $revision_id) { 49 + // If the viewer can't actually see this revision, skip it. 50 + if (!isset($revisions[$revision_id])) { 51 + continue; 52 + } 53 + $revision_map[$commit_map[$commit_phid]] = $revision_id; 54 + } 55 + } 56 + 57 + $base_href = (string)$drequest->generateURI( 58 + array( 59 + 'action' => 'browse', 60 + 'stable' => true, 61 + )); 62 + 63 + $skip_text = pht('Skip Past This Commit'); 64 + $skip_icon = id(new PHUIIconView()) 65 + ->setIcon('fa-backward'); 66 + 67 + Javelin::initBehavior('phabricator-tooltips'); 68 + 69 + $handle_phids = array(); 70 + foreach ($commits as $commit) { 71 + $author_phid = $commit->getAuthorPHID(); 72 + if ($author_phid) { 73 + $handle_phids[] = $author_phid; 74 + } 75 + } 76 + 77 + foreach ($revisions as $revision) { 78 + $handle_phids[] = $revision->getAuthorPHID(); 79 + } 80 + 81 + $handles = $viewer->loadHandles($handle_phids); 82 + 83 + $map = array(); 84 + foreach ($identifiers as $identifier) { 85 + $revision_id = idx($revision_map, $identifier); 86 + if ($revision_id) { 87 + $revision = idx($revisions, $revision_id); 88 + } else { 89 + $revision = null; 90 + } 91 + 92 + $skip_href = $base_href.'?before='.$identifier; 93 + 94 + $skip_link = javelin_tag( 95 + 'a', 96 + array( 97 + 'href' => $skip_href, 98 + 'sigil' => 'has-tooltip', 99 + 'meta' => array( 100 + 'tip' => $skip_text, 101 + 'align' => 'E', 102 + 'size' => 300, 103 + ), 104 + ), 105 + $skip_icon); 106 + 107 + $commit = $commits[$identifier]; 108 + 109 + $author_phid = $commit->getAuthorPHID(); 110 + if (!$author_phid && $revision) { 111 + $author_phid = $revision->getAuthorPHID(); 112 + } 113 + 114 + if (!$author_phid) { 115 + // This means we couldn't identify an author for the commit or the 116 + // revision. We just render a blank for alignment. 117 + $author_style = null; 118 + $author_href = null; 119 + $author_sigil = null; 120 + $author_meta = null; 121 + } else { 122 + $author_src = $handles[$author_phid]->getImageURI(); 123 + $author_style = 'background-image: url('.$author_src.');'; 124 + $author_href = $handles[$author_phid]->getURI(); 125 + $author_sigil = 'has-tooltip'; 126 + $author_meta = array( 127 + 'tip' => $handles[$author_phid]->getName(), 128 + 'align' => 'E', 129 + ); 130 + } 131 + 132 + $author_link = javelin_tag( 133 + $author_href ? 'a' : 'span', 134 + array( 135 + 'class' => 'phabricator-source-blame-author', 136 + 'style' => $author_style, 137 + 'href' => $author_href, 138 + 'sigil' => $author_sigil, 139 + 'meta' => $author_meta, 140 + )); 141 + 142 + $commit_link = javelin_tag( 143 + 'a', 144 + array( 145 + 'href' => $commit->getURI(), 146 + 'sigil' => 'has-tooltip', 147 + 'meta' => array( 148 + 'tip' => $this->renderCommitTooltip($commit, $handles), 149 + 'align' => 'E', 150 + 'size' => 600, 151 + ), 152 + ), 153 + $commit->getLocalName()); 154 + 155 + $info = array( 156 + $author_link, 157 + $commit_link, 158 + ); 159 + 160 + if ($revision) { 161 + $revision_link = phutil_tag( 162 + 'a', 163 + array( 164 + 'href' => $revision->getURI(), 165 + 'sigil' => 'has-tooltip', 166 + 'meta' => array( 167 + 'tip' => $this->renderRevisionTooltip($revision, $handles), 168 + 'align' => 'E', 169 + 'size' => 600, 170 + ), 171 + ), 172 + $revision->getMonogram()); 173 + 174 + 175 + $info = array( 176 + $info, 177 + ' / ', 178 + $revision_link, 179 + ); 180 + } 181 + 182 + $data = array( 183 + 'skip' => $skip_link, 184 + 'info' => hsprintf('%s', $info), 185 + ); 186 + 187 + $map[$identifier] = $data; 188 + } 189 + 190 + return id(new AphrontAjaxResponse())->setContent( 191 + array( 192 + 'blame' => $blame, 193 + 'map' => $map, 194 + )); 195 + } 196 + 197 + private function loadBlame() { 198 + $drequest = $this->getDiffusionRequest(); 199 + 200 + $commit = $drequest->getCommit(); 201 + $path = $drequest->getPath(); 202 + 203 + $blame_timeout = 15; 204 + 205 + $blame = $this->callConduitWithDiffusionRequest( 206 + 'diffusion.blame', 207 + array( 208 + 'commit' => $commit, 209 + 'paths' => array($path), 210 + 'timeout' => $blame_timeout, 211 + )); 212 + 213 + return idx($blame, $path, array()); 214 + } 215 + 216 + private function renderRevisionTooltip( 217 + DifferentialRevision $revision, 218 + $handles) { 219 + $viewer = $this->getViewer(); 220 + 221 + $date = phabricator_date($revision->getDateModified(), $viewer); 222 + $monogram = $revision->getMonogram(); 223 + $title = $revision->getTitle(); 224 + $header = "{$monogram} {$title}"; 225 + 226 + $author = $handles[$revision->getAuthorPHID()]->getName(); 227 + 228 + return "{$header}\n{$date} \xC2\xB7 {$author}"; 229 + } 230 + 231 + private function renderCommitTooltip( 232 + PhabricatorRepositoryCommit $commit, 233 + $handles) { 234 + 235 + $viewer = $this->getViewer(); 236 + 237 + $date = phabricator_date($commit->getEpoch(), $viewer); 238 + $summary = trim($commit->getSummary()); 239 + 240 + $author_phid = $commit->getAuthorPHID(); 241 + if ($author_phid && isset($handles[$author_phid])) { 242 + $author_name = $handles[$author_phid]->getName(); 243 + } else { 244 + $author_name = null; 245 + } 246 + 247 + if ($author_name) { 248 + return "{$summary}\n{$date} \xC2\xB7 {$author_name}"; 249 + } else { 250 + return "{$summary}\n{$date}"; 251 + } 252 + } 253 + 254 + }
+2 -262
src/applications/diffusion/controller/DiffusionBrowseController.php
··· 110 110 } 111 111 112 112 $path = $drequest->getPath(); 113 - 114 - // We need the blame information if blame is on and this is an Ajax request. 115 - // If blame is on and this is a colorized request, we don't show blame at 116 - // first (we ajax it in afterward) so we don't need to query for it. 117 - $needs_blame = $request->isAjax(); 118 - 119 113 $params = array( 120 114 'commit' => $drequest->getCommit(), 121 115 'path' => $drequest->getPath(), ··· 184 178 $file->setName($basename); 185 179 186 180 return $file->getRedirectResponse(); 187 - } else { 188 - $corpus = $this->buildGitLFSCorpus($lfs_ref); 189 181 } 182 + 183 + $corpus = $this->buildGitLFSCorpus($lfs_ref); 190 184 } else { 191 - $this->loadLintMessages(); 192 185 $this->coverage = $drequest->loadCoverage(); 193 186 $show_editor = true; 194 187 ··· 204 197 $this->corpusButtons[] = $this->renderFileButton(); 205 198 } 206 199 } 207 - 208 - if ($request->isAjax()) { 209 - return id(new AphrontAjaxResponse())->setContent($corpus); 210 - } 211 - 212 - require_celerity_resource('diffusion-source-css'); 213 200 214 201 $bar = $this->buildButtonBar($drequest, $show_editor); 215 202 $header = $this->buildHeaderView($drequest); ··· 480 467 return $view; 481 468 } 482 469 483 - private function loadLintMessages() { 484 - $drequest = $this->getDiffusionRequest(); 485 - $branch = $drequest->loadBranch(); 486 - 487 - if (!$branch || !$branch->getLintCommit()) { 488 - return; 489 - } 490 - 491 - $this->lintCommit = $branch->getLintCommit(); 492 - 493 - $conn = id(new PhabricatorRepository())->establishConnection('r'); 494 - 495 - $where = ''; 496 - if ($drequest->getLint()) { 497 - $where = qsprintf( 498 - $conn, 499 - 'AND code = %s', 500 - $drequest->getLint()); 501 - } 502 - 503 - $this->lintMessages = queryfx_all( 504 - $conn, 505 - 'SELECT * FROM %T WHERE branchID = %d %Q AND path = %s', 506 - PhabricatorRepository::TABLE_LINTMESSAGE, 507 - $branch->getID(), 508 - $where, 509 - '/'.$drequest->getPath()); 510 - } 511 - 512 470 private function buildButtonBar( 513 471 DiffusionRequest $drequest, 514 472 $show_editor) { ··· 550 508 ->setColor(PHUIButtonView::GREY); 551 509 } 552 510 553 - $href = null; 554 - $show_lint = true; 555 - if ($this->getRequest()->getStr('lint') !== null) { 556 - $lint_text = pht('Hide Lint'); 557 - $href = $base_uri->alter('lint', null); 558 - 559 - } else if ($this->lintCommit === null) { 560 - $show_lint = false; 561 - } else { 562 - $lint_text = pht('Show Lint'); 563 - $href = $this->getDiffusionRequest()->generateURI(array( 564 - 'action' => 'browse', 565 - 'commit' => $this->lintCommit, 566 - ))->alter('lint', ''); 567 - } 568 - 569 - if ($show_lint) { 570 - $buttons[] = 571 - id(new PHUIButtonView()) 572 - ->setTag('a') 573 - ->setText($lint_text) 574 - ->setHref($href) 575 - ->setIcon('fa-exclamation-triangle') 576 - ->setDisabled(!$href) 577 - ->setColor(PHUIButtonView::GREY); 578 - } 579 - 580 511 $bar = id(new PHUILeftRightView()) 581 512 ->setLeft($buttons) 582 513 ->addClass('diffusion-action-bar full-mobile-buttons'); ··· 705 636 ->setColor(PHUIButtonView::GREY); 706 637 } 707 638 708 - private function renderInlines( 709 - array $inlines, 710 - $has_coverage, 711 - $engine) { 712 - 713 - $rows = array(); 714 - foreach ($inlines as $inline) { 715 - 716 - // TODO: This should use modern scaffolding code. 717 - 718 - $inline_view = id(new PHUIDiffInlineCommentDetailView()) 719 - ->setUser($this->getViewer()) 720 - ->setMarkupEngine($engine) 721 - ->setInlineComment($inline) 722 - ->render(); 723 - 724 - $row = array_fill(0, 3, phutil_tag('th')); 725 - 726 - $row[] = phutil_tag('td', array(), $inline_view); 727 - 728 - if ($has_coverage) { 729 - $row[] = phutil_tag( 730 - 'td', 731 - array( 732 - 'class' => 'cov cov-I', 733 - )); 734 - } 735 - 736 - $rows[] = phutil_tag('tr', array('class' => 'inline'), $row); 737 - } 738 - 739 - return $rows; 740 - } 741 - 742 639 private function buildErrorCorpus($message) { 743 640 $text = id(new PHUIBoxView()) 744 641 ->addPadding(PHUI::PADDING_LARGE) ··· 898 795 return head($parents); 899 796 } 900 797 901 - private function renderRevisionTooltip( 902 - DifferentialRevision $revision, 903 - $handles) { 904 - $viewer = $this->getRequest()->getUser(); 905 - 906 - $date = phabricator_date($revision->getDateModified(), $viewer); 907 - $id = $revision->getID(); 908 - $title = $revision->getTitle(); 909 - $header = "D{$id} {$title}"; 910 - 911 - $author = $handles[$revision->getAuthorPHID()]->getName(); 912 - 913 - return "{$header}\n{$date} \xC2\xB7 {$author}"; 914 - } 915 - 916 - private function renderCommitTooltip( 917 - PhabricatorRepositoryCommit $commit, 918 - $author) { 919 - 920 - $viewer = $this->getRequest()->getUser(); 921 - 922 - $date = phabricator_date($commit->getEpoch(), $viewer); 923 - $summary = trim($commit->getSummary()); 924 - 925 - return "{$summary}\n{$date} \xC2\xB7 {$author}"; 926 - } 927 - 928 798 protected function markupText($text) { 929 799 $engine = PhabricatorMarkupEngine::newDiffusionMarkupEngine(); 930 800 $engine->setConfig('viewer', $this->getRequest()->getUser()); ··· 1108 978 return $view; 1109 979 } 1110 980 1111 - private function loadBlame($path, $commit, $timeout) { 1112 - $blame = $this->callConduitWithDiffusionRequest( 1113 - 'diffusion.blame', 1114 - array( 1115 - 'commit' => $commit, 1116 - 'paths' => array($path), 1117 - 'timeout' => $timeout, 1118 - )); 1119 - 1120 - $identifiers = idx($blame, $path, null); 1121 - 1122 - if ($identifiers) { 1123 - $viewer = $this->getViewer(); 1124 - $drequest = $this->getDiffusionRequest(); 1125 - $repository = $drequest->getRepository(); 1126 - 1127 - $commits = id(new DiffusionCommitQuery()) 1128 - ->setViewer($viewer) 1129 - ->withRepository($repository) 1130 - ->withIdentifiers($identifiers) 1131 - // TODO: We only fetch this to improve author display behavior, but 1132 - // shouldn't really need to? 1133 - ->needCommitData(true) 1134 - ->execute(); 1135 - $commits = mpull($commits, null, 'getCommitIdentifier'); 1136 - } else { 1137 - $commits = array(); 1138 - } 1139 - 1140 - return array($identifiers, $commits); 1141 - } 1142 - 1143 - private function renderAuthorLinks(array $authors, $handles) { 1144 - $links = array(); 1145 - 1146 - foreach ($authors as $phid) { 1147 - if (!strlen($phid)) { 1148 - // This means we couldn't identify an author for the commit or the 1149 - // revision. We just render a blank for alignment. 1150 - $style = null; 1151 - $href = null; 1152 - $sigil = null; 1153 - $meta = null; 1154 - } else { 1155 - $src = $handles[$phid]->getImageURI(); 1156 - $style = 'background-image: url('.$src.');'; 1157 - $href = $handles[$phid]->getURI(); 1158 - $sigil = 'has-tooltip'; 1159 - $meta = array( 1160 - 'tip' => $handles[$phid]->getName(), 1161 - 'align' => 'E', 1162 - ); 1163 - } 1164 - 1165 - $links[$phid] = javelin_tag( 1166 - $href ? 'a' : 'span', 1167 - array( 1168 - 'class' => 'diffusion-author-link', 1169 - 'style' => $style, 1170 - 'href' => $href, 1171 - 'sigil' => $sigil, 1172 - 'meta' => $meta, 1173 - )); 1174 - } 1175 - 1176 - return $links; 1177 - } 1178 - 1179 - private function renderCommitLinks(array $commits, $handles) { 1180 - $links = array(); 1181 - foreach ($commits as $identifier => $commit) { 1182 - $tooltip = $this->renderCommitTooltip( 1183 - $commit, 1184 - $commit->renderAuthorShortName($handles)); 1185 - 1186 - $commit_link = javelin_tag( 1187 - 'a', 1188 - array( 1189 - 'href' => $commit->getURI(), 1190 - 'sigil' => 'has-tooltip', 1191 - 'meta' => array( 1192 - 'tip' => $tooltip, 1193 - 'align' => 'E', 1194 - 'size' => 600, 1195 - ), 1196 - ), 1197 - $commit->getLocalName()); 1198 - 1199 - $links[$identifier] = $commit_link; 1200 - } 1201 - 1202 - return $links; 1203 - } 1204 - 1205 - private function renderRevisionLinks(array $revisions, $handles) { 1206 - $links = array(); 1207 - 1208 - foreach ($revisions as $revision) { 1209 - $revision_id = $revision->getID(); 1210 - 1211 - $tooltip = $this->renderRevisionTooltip($revision, $handles); 1212 - 1213 - $revision_link = javelin_tag( 1214 - 'a', 1215 - array( 1216 - 'href' => '/'.$revision->getMonogram(), 1217 - 'sigil' => 'has-tooltip', 1218 - 'meta' => array( 1219 - 'tip' => $tooltip, 1220 - 'align' => 'E', 1221 - 'size' => 600, 1222 - ), 1223 - ), 1224 - $revision->getMonogram()); 1225 - 1226 - $links[$revision_id] = $revision_link; 1227 - } 1228 - 1229 - return $links; 1230 - } 1231 - 1232 981 private function getGitLFSRef(PhabricatorRepository $repository, $data) { 1233 982 if (!$repository->canUseGitLFS()) { 1234 983 return null; ··· 1375 1124 ->setTable($history_table); 1376 1125 } 1377 1126 1378 - private function getLineNumberBaseURI() { 1379 - $drequest = $this->getDiffusionRequest(); 1380 - 1381 - return (string)$drequest->generateURI( 1382 - array( 1383 - 'action' => 'browse', 1384 - 'stable' => true, 1385 - )); 1386 - } 1387 1127 }
+11 -1
src/applications/diffusion/document/DiffusionDocumentRenderingEngine.php
··· 70 70 } 71 71 72 72 protected function willRenderRef(PhabricatorDocumentRef $ref) { 73 - $ref->setSymbolMetadata($this->getSymbolMetadata()); 73 + $drequest = $this->getDiffusionRequest(); 74 + 75 + $blame_uri = (string)$drequest->generateURI( 76 + array( 77 + 'action' => 'blame', 78 + 'stable' => true, 79 + )); 80 + 81 + $ref 82 + ->setSymbolMetadata($this->getSymbolMetadata()) 83 + ->setBlameURI($blame_uri); 74 84 } 75 85 76 86 private function getSymbolMetadata() {
+4
src/applications/files/document/PhabricatorDocumentEngine.php
··· 38 38 return false; 39 39 } 40 40 41 + public function canBlame(PhabricatorDocumentRef $ref) { 42 + return false; 43 + } 44 + 41 45 final public function setEncodingConfiguration($config) { 42 46 $this->encodingConfiguration = $config; 43 47 return $this;
+8
src/applications/files/document/PhabricatorDocumentRef.php
··· 9 9 private $byteLength; 10 10 private $snippet; 11 11 private $symbolMetadata = array(); 12 + private $blameURI; 12 13 13 14 public function setFile(PhabricatorFile $file) { 14 15 $this->file = $file; ··· 141 142 return $this->symbolMetadata; 142 143 } 143 144 145 + public function setBlameURI($blame_uri) { 146 + $this->blameURI = $blame_uri; 147 + return $this; 148 + } 144 149 150 + public function getBlameURI() { 151 + return $this->blameURI; 152 + } 145 153 146 154 }
+13 -1
src/applications/files/document/PhabricatorSourceDocumentEngine.php
··· 13 13 return true; 14 14 } 15 15 16 + public function canBlame(PhabricatorDocumentRef $ref) { 17 + return true; 18 + } 19 + 16 20 protected function getDocumentIconIcon(PhabricatorDocumentRef $ref) { 17 21 return 'fa-code'; 18 22 } ··· 45 49 } 46 50 } 47 51 52 + $options = array(); 53 + if ($ref->getBlameURI()) { 54 + $content = phutil_split_lines($content); 55 + $blame = range(1, count($content)); 56 + $blame = array_fuse($blame); 57 + $options['blame'] = $blame; 58 + } 59 + 48 60 return array( 49 61 $messages, 50 - $this->newTextDocumentContent($ref, $content), 62 + $this->newTextDocumentContent($ref, $content, $options), 51 63 ); 52 64 } 53 65
+19 -2
src/applications/files/document/PhabricatorTextDocumentEngine.php
··· 15 15 16 16 protected function newTextDocumentContent( 17 17 PhabricatorDocumentRef $ref, 18 - $content) { 19 - $lines = phutil_split_lines($content); 18 + $content, 19 + array $options = array()) { 20 + 21 + PhutilTypeSpec::checkMap( 22 + $options, 23 + array( 24 + 'blame' => 'optional wild', 25 + )); 26 + 27 + if (is_array($content)) { 28 + $lines = $content; 29 + } else { 30 + $lines = phutil_split_lines($content); 31 + } 20 32 21 33 $view = id(new PhabricatorSourceCodeView()) 22 34 ->setHighlights($this->getHighlightedLines()) 23 35 ->setLines($lines) 24 36 ->setSymbolMetadata($ref->getSymbolMetadata()); 37 + 38 + $blame = idx($options, 'blame'); 39 + if ($blame !== null) { 40 + $view->setBlameMap($blame); 41 + } 25 42 26 43 $message = null; 27 44 if ($this->encodingMessage !== null) {
+15 -5
src/applications/files/document/render/PhabricatorDocumentRenderingEngine.php
··· 91 91 'viewURI' => $view_uri, 92 92 'loadingMarkup' => hsprintf('%s', $loading), 93 93 'canEncode' => $candidate_engine->canConfigureEncoding($ref), 94 - 'canHighlight' => $candidate_engine->CanConfigureHighlighting($ref), 94 + 'canHighlight' => $candidate_engine->canConfigureHighlighting($ref), 95 + 'canBlame' => $candidate_engine->canBlame($ref), 95 96 ); 96 97 } 97 98 ··· 99 100 $control_id = celerity_generate_unique_node_id(); 100 101 $icon = $engine->newDocumentIcon($ref); 101 102 103 + $config = array( 104 + 'controlID' => $control_id, 105 + ); 106 + 102 107 if ($engine->shouldRenderAsync($ref)) { 103 108 $content = $engine->newLoadingContent($ref); 104 - $config = array( 105 - 'renderControlID' => $control_id, 106 - ); 109 + $config['next'] = 'render'; 107 110 } else { 108 111 $this->willRenderRef($ref); 109 112 $content = $engine->newDocument($ref); 110 - $config = array(); 113 + 114 + if ($engine->canBlame($ref)) { 115 + $config['next'] = 'blame'; 116 + } 111 117 } 112 118 113 119 Javelin::initBehavior('document-engine', $config); ··· 134 140 'name' => pht('Highlight As...'), 135 141 'uri' => '/services/highlight/', 136 142 'value' => $highlight_setting, 143 + ), 144 + 'blame' => array( 145 + 'uri' => $ref->getBlameURI(), 146 + 'value' => null, 137 147 ), 138 148 ); 139 149
+2
src/applications/repository/storage/PhabricatorRepository.php
··· 703 703 case 'history': 704 704 case 'graph': 705 705 case 'clone': 706 + case 'blame': 706 707 case 'browse': 707 708 case 'document': 708 709 case 'change': ··· 782 783 case 'change': 783 784 case 'history': 784 785 case 'graph': 786 + case 'blame': 785 787 case 'browse': 786 788 case 'document': 787 789 case 'lastmodified':
-22
src/applications/repository/storage/PhabricatorRepositoryCommit.php
··· 416 416 return $repository->formatCommitName($identifier, $local = true); 417 417 } 418 418 419 - public function renderAuthorLink($handles) { 420 - $author_phid = $this->getAuthorPHID(); 421 - if ($author_phid && isset($handles[$author_phid])) { 422 - return $handles[$author_phid]->renderLink(); 423 - } 424 - 425 - return $this->renderAuthorShortName($handles); 426 - } 427 - 428 - public function renderAuthorShortName($handles) { 429 - $author_phid = $this->getAuthorPHID(); 430 - if ($author_phid && isset($handles[$author_phid])) { 431 - return $handles[$author_phid]->getName(); 432 - } 433 - 434 - $data = $this->getCommitData(); 435 - $name = $data->getAuthorName(); 436 - 437 - $parsed = new PhutilEmailAddress($name); 438 - return nonempty($parsed->getDisplayName(), $parsed->getAddress()); 439 - } 440 - 441 419 442 420 /* -( PhabricatorPolicyInterface )----------------------------------------- */ 443 421
+43 -1
src/view/layout/PhabricatorSourceCodeView.php
··· 9 9 private $truncatedFirstBytes = false; 10 10 private $truncatedFirstLines = false; 11 11 private $symbolMetadata; 12 + private $blameMap; 12 13 13 14 public function setLines(array $lines) { 14 15 $this->lines = $lines; ··· 49 50 return $this->symbolMetadata; 50 51 } 51 52 53 + public function setBlameMap(array $map) { 54 + $this->blameMap = $map; 55 + return $this; 56 + } 57 + 58 + public function getBlameMap() { 59 + return $this->blameMap; 60 + } 61 + 52 62 public function render() { 63 + $blame_map = $this->getBlameMap(); 64 + $has_blame = ($blame_map !== null); 65 + 53 66 require_celerity_resource('phabricator-source-code-view-css'); 54 67 require_celerity_resource('syntax-highlighting-css'); 55 68 ··· 85 98 86 99 $base_uri = (string)$this->uri; 87 100 foreach ($lines as $line) { 88 - 89 101 // NOTE: See phabricator-oncopy behavior. 90 102 $content_line = hsprintf("\xE2\x80\x8B%s", $line); 91 103 ··· 114 126 $line_number); 115 127 } 116 128 129 + if ($has_blame) { 130 + $lines = idx($blame_map, $line_number); 131 + 132 + if ($lines) { 133 + $skip_blame = 'skip;'.$lines; 134 + $info_blame = 'info;'.$lines; 135 + } else { 136 + $skip_blame = null; 137 + $info_blame = null; 138 + } 139 + 140 + $blame_cells = array( 141 + phutil_tag( 142 + 'th', 143 + array( 144 + 'class' => 'phabricator-source-blame-skip', 145 + 'data-blame' => $skip_blame, 146 + )), 147 + phutil_tag( 148 + 'th', 149 + array( 150 + 'class' => 'phabricator-source-blame-info', 151 + 'data-blame' => $info_blame, 152 + )), 153 + ); 154 + } else { 155 + $blame_cells = null; 156 + } 157 + 117 158 $rows[] = phutil_tag( 118 159 'tr', 119 160 $row_attributes, 120 161 array( 162 + $blame_cells, 121 163 phutil_tag( 122 164 'th', 123 165 array(
-4
webroot/rsrc/css/application/differential/changeset-view.css
··· 165 165 padding: 0; 166 166 } 167 167 168 - .diffusion-source td.cov { 169 - padding: 0 8px; 170 - } 171 - 172 168 td.cov-U { 173 169 background: #dd8866; 174 170 }
-102
webroot/rsrc/css/application/diffusion/diffusion-source.css
··· 1 - /** 2 - * @provides diffusion-source-css 3 - */ 4 - 5 - .diffusion-source { 6 - width: 100%; 7 - background: {$page.content}; 8 - overflow: hidden; 9 - } 10 - 11 - .device-phone .diffusion-source-wrap { 12 - overflow: scroll; 13 - -webkit-overflow-scrolling: touch; 14 - } 15 - 16 - .diffusion-source tr.phabricator-source-highlight { 17 - background: {$sh-yellowbackground}; 18 - } 19 - 20 - .diffusion-source th { 21 - text-align: right; 22 - vertical-align: top; 23 - background: {$lightgreybackground}; 24 - color: {$bluetext}; 25 - border-right: 1px solid {$thinblueborder}; 26 - } 27 - 28 - .diffusion-source td { 29 - vertical-align: top; 30 - white-space: pre-wrap; 31 - padding-top: 1px; 32 - padding-bottom: 1px; 33 - padding-left: 8px; 34 - width: 100%; 35 - word-break: break-all; 36 - } 37 - 38 - .device .diffusion-source td { 39 - word-break: normal; 40 - white-space: nowrap; 41 - } 42 - 43 - .diffusion-browse-type-form { 44 - float: right; 45 - } 46 - 47 - .diffusion-blame-link, 48 - .diffusion-rev-link { 49 - white-space: nowrap; 50 - } 51 - 52 - .diffusion-blame-link { 53 - min-width: 28px; 54 - } 55 - 56 - .diffusion-source th.diffusion-rev-link { 57 - text-align: left; 58 - min-width: 130px; 59 - } 60 - 61 - .diffusion-blame-link a, 62 - .diffusion-rev-link a, 63 - .diffusion-line-link a { 64 - color: {$darkbluetext}; 65 - } 66 - 67 - .diffusion-rev-link a { 68 - margin: 0 8px 0 0; 69 - display: inline-block; 70 - } 71 - 72 - .diffusion-rev-link span { 73 - display: inline-block; 74 - margin-right: 4px; 75 - margin-left: -4px; 76 - color: {$lightgreytext}; 77 - } 78 - 79 - .diffusion-blame-link a, 80 - .diffusion-line-link a { 81 - /* Give the user a larger click target. */ 82 - display: block; 83 - padding: 2px 8px; 84 - } 85 - 86 - .diffusion-line-link { 87 - -moz-user-select: -moz-none; 88 - -khtml-user-select: none; 89 - -webkit-user-select: none; 90 - -ms-user-select: none; 91 - user-select: none; 92 - } 93 - 94 - .diffusion-rev-link .diffusion-author-link { 95 - display: inline-block; 96 - padding: 0; 97 - margin: 2px 6px -4px 8px; 98 - width: 16px; 99 - height: 16px; 100 - background-size: 100% 100%; 101 - background-repeat: no-repeat; 102 - }
+43
webroot/rsrc/css/layout/phabricator-source-code-view.css
··· 68 68 .phabricator-source-code-summary .phabricator-source-code { 69 69 white-space: nowrap; 70 70 } 71 + 72 + 73 + .phabricator-source-blame-skip, 74 + .phabricator-source-blame-info { 75 + -moz-user-select: -moz-none; 76 + -khtml-user-select: none; 77 + -webkit-user-select: none; 78 + -ms-user-select: none; 79 + user-select: none; 80 + } 81 + 82 + .phabricator-source-blame-skip { 83 + min-width: 28px; 84 + border-right: 1px solid {$thinblueborder}; 85 + } 86 + 87 + .phabricator-source-blame-info { 88 + color: {$lightgreytext}; 89 + white-space: nowrap; 90 + min-width: 130px; 91 + border-right: 1px solid {$paste.border}; 92 + padding-right: 8px; 93 + } 94 + 95 + .phabricator-source-blame-skip a { 96 + /* Give the user a larger click target. */ 97 + display: block; 98 + padding: 2px 8px; 99 + } 100 + 101 + .device-desktop .phabricator-source-blame-skip a:hover { 102 + background: {$bluebackground}; 103 + } 104 + 105 + .phabricator-source-blame-author { 106 + display: inline-block; 107 + padding: 0; 108 + margin: 2px 6px -4px 8px; 109 + width: 16px; 110 + height: 16px; 111 + background-size: 100% 100%; 112 + background-repeat: no-repeat; 113 + }
-14
webroot/rsrc/js/application/diffusion/behavior-load-blame.js
··· 1 - /** 2 - * @provides javelin-behavior-load-blame 3 - * @requires javelin-behavior 4 - * javelin-dom 5 - * javelin-request 6 - */ 7 - 8 - JX.behavior('load-blame', function(config) { 9 - 10 - new JX.Request(location.href, function (response) { 11 - JX.DOM.setContent(JX.$(config.id), JX.$H(response)); 12 - }).send(); 13 - 14 - });
+79 -5
webroot/rsrc/js/application/files/behavior-document-engine.js
··· 7 7 8 8 JX.behavior('document-engine', function(config, statics) { 9 9 10 - 11 - 12 10 function onmenu(e) { 13 11 var node = e.getNode('document-engine-view-dropdown'); 14 12 var data = JX.Stratcom.getData(node); ··· 213 211 JX.DOM.setContent(viewport, JX.$H(r.markup)); 214 212 } 215 213 214 + function blame(data) { 215 + if (!data.blame.uri) { 216 + return; 217 + } 218 + 219 + if (!data.blame.value) { 220 + new JX.Request(data.blame.uri, JX.bind(null, onblame, data)) 221 + .send(); 222 + return; 223 + } 224 + 225 + var viewport = JX.$(data.viewportID); 226 + 227 + var cells = JX.DOM.scry(viewport, 'th'); 228 + 229 + for (var ii = 0; ii < cells.length; ii++) { 230 + var cell = cells[ii]; 231 + 232 + var spec = cell.getAttribute('data-blame'); 233 + if (!spec) { 234 + continue; 235 + } 236 + 237 + spec = spec.split(';'); 238 + var type = spec[0]; 239 + var lines = spec[1]; 240 + 241 + var content = null; 242 + switch (type) { 243 + case 'skip': 244 + content = renderSkip(data.blame.value, lines); 245 + break; 246 + case 'info': 247 + content = renderInfo(data.blame.value, lines); 248 + break; 249 + } 250 + 251 + JX.DOM.setContent(cell, content); 252 + } 253 + } 254 + 255 + function onblame(data, r) { 256 + data.blame.value = r; 257 + blame(data); 258 + } 259 + 260 + function renderSkip(blame, lines) { 261 + var commit = blame.blame[lines - 1]; 262 + if (!commit) { 263 + return null; 264 + } 265 + 266 + var spec = blame.map[commit]; 267 + 268 + return JX.$H(spec.skip); 269 + } 270 + 271 + function renderInfo(blame, lines) { 272 + var commit = blame.blame[lines - 1]; 273 + if (!commit) { 274 + return null; 275 + } 276 + 277 + var spec = blame.map[commit]; 278 + 279 + return JX.$H(spec.info); 280 + } 281 + 216 282 if (!statics.initialized) { 217 283 JX.Stratcom.listen('click', 'document-engine-view-dropdown', onmenu); 218 284 statics.initialized = true; 219 285 } 220 286 221 - if (config && config.renderControlID) { 222 - var control = JX.$(config.renderControlID); 287 + if (config && config.controlID) { 288 + var control = JX.$(config.controlID); 223 289 var data = JX.Stratcom.getData(control); 224 - onview(data, null, true); 290 + 291 + switch (config.next) { 292 + case 'render': 293 + onview(data, null, true); 294 + break; 295 + case 'blame': 296 + blame(data); 297 + break; 298 + } 225 299 } 226 300 227 301 });