@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.

Add color to diffusion blame and improve plain view

Summary:
query the database to get the epoch info for the commits, then
calculate the color depending on the epoch (the newer the commit, the
dark its color). Also improved the plain blame view for git, as the
git-blame doesn't produce a good display by default. Now we format the
output it from the data we fetches from the database.

Test Plan:
verify both git and svn browsing page work for 'plain',
'plainblame', 'highlighted' and 'highlightedblame' view.

Reviewed By: epriestley
Reviewers: epriestley
CC: epriestley, jungejason
Differential Revision: 93

+141 -115
+80 -53
src/applications/diffusion/controller/file/DiffusionBrowseFileController.php
··· 82 82 } 83 83 84 84 85 - public static function renderRevision( 86 - DiffusionRequest $drequest, 87 - $revision) { 88 - 89 - $callsign = $drequest->getCallsign(); 90 - 91 - $name = 'r'.$callsign.$revision; 92 - return phutil_render_tag( 93 - 'a', 94 - array( 95 - 'href' => '/'.$name, 96 - ), 97 - $name 98 - ); 99 - } 100 - 101 - 102 85 private function buildCorpus($selected) { 103 - $blame = ($selected == 'blame' || $selected == 'plainblame'); 86 + $needs_blame = ($selected == 'blame' || $selected == 'plainblame'); 104 87 105 88 $file_query = DiffusionFileContentQuery::newFromDiffusionRequest( 106 89 $this->diffusionRequest); 107 - $file_query->setNeedsBlame($blame); 90 + $file_query->setNeedsBlame($needs_blame); 91 + $file_query->loadFileContent(); 108 92 109 93 // TODO: image 110 94 // TODO: blame of blame. 111 95 switch ($selected) { 112 96 case 'plain': 113 - case 'plainblame': 114 97 $style = 115 98 "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace"; 116 99 $corpus = phutil_render_tag( ··· 119 102 'style' => $style, 120 103 ), 121 104 phutil_escape_html($file_query->getRawData())); 105 + 122 106 break; 123 107 108 + case 'plainblame': 109 + $style = 110 + "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace"; 111 + list($text_list, $rev_list, $blame_dict) = 112 + $file_query->getBlameData(); 113 + 114 + $rows = array(); 115 + foreach ($text_list as $k => $line) { 116 + $rev = $rev_list[$k]; 117 + $author = $blame_dict[$rev]['author']; 118 + $rows[] = 119 + sprintf("%-10s %-15s %s", substr($rev, 0, 7), $author, $line); 120 + } 121 + 122 + $corpus = phutil_render_tag( 123 + 'textarea', 124 + array( 125 + 'style' => $style, 126 + ), 127 + phutil_escape_html(implode("\n", $rows))); 128 + 129 + break; 130 + 124 131 case 'highlighted': 125 132 case 'blame': 126 133 default: 127 134 require_celerity_resource('syntax-highlighting-css'); 128 135 require_celerity_resource('diffusion-source-css'); 129 136 130 - list($data, $blamedata, $revs) = $file_query->getTokenizedData(); 137 + list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData(); 131 138 132 139 $drequest = $this->getDiffusionRequest(); 133 140 $path = $drequest->getPath(); 134 141 $highlightEngine = new PhutilDefaultSyntaxHighlighterEngine(); 135 - $data = $highlightEngine->highlightSource($path, $data); 136 142 137 - $data = explode("\n", rtrim($data)); 143 + $text_list = explode("\n", $highlightEngine->highlightSource($path, 144 + implode("\n", $text_list))); 138 145 139 - $rows = $this->buildDisplayRows($data, $blame, $blamedata, $drequest, 140 - $revs); 146 + $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, 147 + $needs_blame, $drequest); 141 148 142 149 $corpus_table = phutil_render_tag( 143 150 'table', ··· 159 166 } 160 167 161 168 162 - private static function buildDisplayRows($data, $blame, $blamedata, $drequest, 163 - $revs) { 164 - $last = null; 169 + private static function buildDisplayRows($text_list, $rev_list, $blame_dict, 170 + $needs_blame, DiffusionRequest $drequest) { 171 + $last_rev = null; 165 172 $color = null; 166 173 $rows = array(); 167 174 $n = 1; 168 - foreach ($data as $k => $line) { 169 - if ($blame) { 170 - if ($last == $blamedata[$k][0]) { 171 - $blameinfo = 175 + 176 + $epoch_list = ipull($blame_dict, 'epoch'); 177 + $max = max($epoch_list); 178 + $min = min($epoch_list); 179 + $range = $max - $min + 1; 180 + 181 + foreach ($text_list as $k => $line) { 182 + if ($needs_blame) { 183 + // If the line's rev is same as the line above, show empty content 184 + // with same color; otherwise generate blame info. The newer a change 185 + // is, the darker the color. 186 + $rev = $rev_list[$k]; 187 + if ($last_rev == $rev) { 188 + $blame_info = 172 189 '<th style="background: '.$color.'; width: 9em;"></th>'. 173 190 '<th style="background: '.$color.'"></th>'; 174 191 } else { 175 - switch ($drequest->getRepository()->getVersionControlSystem()) { 176 - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: 177 - // TODO: better color for git. 178 - $color = '#dddddd'; 179 - break; 180 - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: 181 - $color = sprintf( 182 - '#%02xee%02x', 183 - $revs[$blamedata[$k][0]], 184 - $revs[$blamedata[$k][0]]); 185 - break; 186 - default: 187 - throw new Exception('repository type not supported'); 188 - } 192 + 193 + $color_number = (int)(0xEE - 194 + 0xEE * ($blame_dict[$rev]['epoch'] - $min) / $range); 195 + $color = sprintf('#%02xee%02x', $color_number, $color_number); 196 + 189 197 $revision_link = self::renderRevision( 190 - $drequest, 191 - $blamedata[$k][0]); 198 + $drequest, 199 + substr($rev, 0, 7)); 192 200 193 - $author_link = $blamedata[$k][1]; 194 - $blameinfo = 201 + $author_link = $blame_dict[$rev]['author']; 202 + $blame_info = 195 203 '<th style="background: '.$color. 196 204 '; width: 9em;">'.$revision_link.'</th>'. 197 205 '<th style="background: '.$color. 198 206 '; font-weight: normal; color: #333;">'.$author_link.'</th>'; 199 - $last = $blamedata[$k][0]; 207 + $last_rev = $rev; 200 208 } 201 209 } else { 202 - $blameinfo = null; 210 + $blame_info = null; 203 211 } 204 212 213 + // Highlight the line of interest if needed. 205 214 if ($n == $drequest->getLine()) { 206 215 $tr = '<tr style="background: #ffff00;">'; 207 216 $targ = '<a id="scroll_target"></a>'; ··· 212 221 $targ = null; 213 222 } 214 223 224 + // Create the row display. 215 225 $uri_path = $drequest->getUriPath(); 216 226 $uri_rev = $drequest->getCommit(); 217 227 ··· 222 232 ), 223 233 $n); 224 234 225 - $rows[] = $tr.$blameinfo.'<th>'.$l.'</th><td>'.$targ.$line.'</td></tr>'; 235 + $rows[] = $tr.$blame_info.'<th>'.$l.'</th><td>'.$targ.$line.'</td></tr>'; 226 236 ++$n; 227 237 } 228 238 229 239 return $rows; 230 240 } 241 + 242 + 243 + private static function renderRevision(DiffusionRequest $drequest, 244 + $revision) { 245 + 246 + $callsign = $drequest->getCallsign(); 247 + 248 + $name = 'r'.$callsign.$revision; 249 + return phutil_render_tag( 250 + 'a', 251 + array( 252 + 'href' => '/'.$name, 253 + ), 254 + $name 255 + ); 256 + } 257 + 231 258 }
+1 -1
src/applications/diffusion/controller/file/__init__.php
··· 8 8 9 9 phutil_require_module('phabricator', 'applications/diffusion/controller/base'); 10 10 phutil_require_module('phabricator', 'applications/diffusion/query/filecontent/base'); 11 - phutil_require_module('phabricator', 'applications/repository/constants/repositorytype'); 12 11 phutil_require_module('phabricator', 'infrastructure/celerity/api'); 13 12 phutil_require_module('phabricator', 'infrastructure/javelin/api'); 14 13 phutil_require_module('phabricator', 'view/layout/panel'); 15 14 16 15 phutil_require_module('phutil', 'markup'); 17 16 phutil_require_module('phutil', 'markup/syntax/engine/default'); 17 + phutil_require_module('phutil', 'utils'); 18 18 19 19 20 20 phutil_require_source('DiffusionBrowseFileController.php');
+39 -5
src/applications/diffusion/query/filecontent/base/DiffusionFileContentQuery.php
··· 20 20 21 21 private $request; 22 22 private $needsBlame; 23 + private $fileContent; 23 24 24 25 final private function __construct() { 25 26 // <private> ··· 54 55 } 55 56 56 57 final public function loadFileContent() { 57 - return $this->executeQuery(); 58 + $this->fileContent = $this->executeQuery(); 58 59 } 59 60 60 61 abstract protected function executeQuery(); 61 62 62 63 final public function getRawData() { 63 - return $this->loadFileContent()->getCorpus(); 64 + return $this->fileContent->getCorpus(); 64 65 } 65 66 66 - final public function getTokenizedData() { 67 - return $this->tokenizeData($this->getRawData()); 67 + final public function getBlameData() { 68 + $raw_data = $this->getRawData(); 69 + 70 + $text_list = array(); 71 + $rev_list = array(); 72 + $blame_dict = array(); 73 + 74 + if (!$this->getNeedsBlame()) { 75 + $text_list = explode("\n", rtrim($raw_data)); 76 + } else { 77 + foreach (explode("\n", rtrim($raw_data)) as $k => $line) { 78 + list($rev_id, $author, $text) = $this->tokenizeLine($line); 79 + 80 + $text_list[$k] = $text; 81 + $rev_list[$k] = $rev_id; 82 + 83 + if (!isset($blame_dict[$rev_id]) && 84 + !isset($blame_dict[$rev_id]['author'] )) { 85 + $blame_dict[$rev_id]['author'] = $author; 86 + } 87 + } 88 + 89 + $repository = $this->getRequest()->getRepository(); 90 + $commits = id(new PhabricatorRepositoryCommit())->loadAllWhere( 91 + 'repositoryID = %d AND commitIdentifier IN (%Ls)', $repository->getID(), 92 + array_unique($rev_list)); 93 + 94 + foreach ($commits as $commit) { 95 + $commitIdentifier = $commit->getCommitIdentifier(); 96 + $epoch = $commit->getEpoch(); 97 + $blame_dict[$commitIdentifier]['epoch'] = $epoch; 98 + } 99 + } 100 + 101 + return array($text_list, $rev_list, $blame_dict); 68 102 } 69 103 70 - abstract protected function tokenizeData($data); 104 + abstract protected function tokenizeLine($line); 71 105 72 106 public function setNeedsBlame($needs_blame) 73 107 {
+2
src/applications/diffusion/query/filecontent/base/__init__.php
··· 7 7 8 8 9 9 phutil_require_module('phabricator', 'applications/repository/constants/repositorytype'); 10 + phutil_require_module('phabricator', 'applications/repository/storage/commit'); 10 11 11 12 phutil_require_module('phutil', 'symbols'); 13 + phutil_require_module('phutil', 'utils'); 12 14 13 15 14 16 phutil_require_source('DiffusionFileContentQuery.php');
+11 -29
src/applications/diffusion/query/filecontent/git/DiffusionGitFileContentQuery.php
··· 28 28 $local_path = $repository->getDetail('local-path'); 29 29 if ($this->getNeedsBlame()) { 30 30 list($corpus) = execx( 31 - '(cd %s && git --no-pager blame -c --date short %s -- %s)', 31 + '(cd %s && git --no-pager blame -c -l --date short %s -- %s)', 32 32 $local_path, 33 33 $commit, 34 34 $path); ··· 46 46 return $file_content; 47 47 } 48 48 49 - protected function tokenizeData($data) { 50 - $m = array(); 51 - $blamedata = array(); 52 - $revs = array(); 53 - 54 - if ($this->getNeedsBlame()) { 55 - $data = explode("\n", rtrim($data)); 56 - foreach ($data as $k => $line) { 57 - // sample line: 58 - // d1b4fcdd ( hzhao 2009-05-01 202)function print(); 59 - preg_match('/^\s*?(\S+?)\s*\(\s*(\S+)\s+\S+\s+\d+\)(.*)?$/', 60 - $line, $m); 61 - $data[$k] = idx($m, 3); 62 - $blamedata[$k] = array($m[1], $m[2]); 63 - 64 - $revs[$m[1]] = true; 65 - } 66 - $data = implode("\n", $data); 67 49 68 - krsort($revs); 69 - $ii = 0; 70 - $len = count($revs); 71 - foreach ($revs as $rev => $ignored) { 72 - $revs[$rev] = (int)(0xEE * ($ii / $len)); 73 - ++$ii; 74 - } 75 - } 76 - 77 - return array($data, $blamedata, $revs); 50 + protected function tokenizeLine($line) { 51 + $m = array(); 52 + // sample line: 53 + // d1b4fcdd2a7c8c0f8cbdd01ca839d992135424dc 54 + // ( hzhao 2009-05-01 202)function print(); 55 + preg_match('/^\s*?(\S+?)\s*\(\s*(\S+)\s+\S+\s+\d+\)(.*)?$/', $line, $m); 56 + $rev_id = $m[1]; 57 + $author = $m[2]; 58 + $text = idx($m, 3); 59 + return array($rev_id, $author, $text); 78 60 } 79 61 80 62 }
+8 -27
src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php
··· 56 56 return $file_content; 57 57 } 58 58 59 - protected function tokenizeData($data) 60 - { 59 + protected function tokenizeLine($line) { 60 + // sample line: 61 + // 347498 yliu function print(); 61 62 $m = array(); 62 - $blamedata = array(); 63 - $revs = array(); 63 + preg_match('/^\s*(\d+)\s+(\S+)(?: (.*))?$/', $line, $m); 64 + $rev_id = $m[1]; 65 + $author = $m[2]; 66 + $text = idx($m, 3); 64 67 65 - if ($this->getNeedsBlame()) { 66 - $data = explode("\n", rtrim($data)); 67 - foreach ($data as $k => $line) { 68 - // sample line: 69 - // 347498 yliu function print(); 70 - preg_match('/^\s*(\d+)\s+(\S+)(?: (.*))?$/', $line, $m); 71 - $data[$k] = idx($m, 3); 72 - $blamedata[$k] = array($m[1], $m[2]); 73 - 74 - $revs[$m[1]] = true; 75 - } 76 - $data = implode("\n", $data); 77 - 78 - krsort($revs); 79 - $ii = 0; 80 - $len = count($revs); 81 - foreach ($revs as $rev => $ignored) { 82 - $revs[$rev] = (int)(0xEE * ($ii / $len)); 83 - ++$ii; 84 - } 85 - } 86 - 87 - return array($data, $blamedata, $revs); 68 + return array($rev_id, $author, $text); 88 69 } 89 70 90 71 }