diff --git a/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php index a6f5ca6322..f2ac3271ae 100644 --- a/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php +++ b/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php @@ -82,35 +82,18 @@ class DiffusionBrowseFileController extends DiffusionController { } - public static function renderRevision( - DiffusionRequest $drequest, - $revision) { - - $callsign = $drequest->getCallsign(); - - $name = 'r'.$callsign.$revision; - return phutil_render_tag( - 'a', - array( - 'href' => '/'.$name, - ), - $name - ); - } - - private function buildCorpus($selected) { - $blame = ($selected == 'blame' || $selected == 'plainblame'); + $needs_blame = ($selected == 'blame' || $selected == 'plainblame'); $file_query = DiffusionFileContentQuery::newFromDiffusionRequest( $this->diffusionRequest); - $file_query->setNeedsBlame($blame); + $file_query->setNeedsBlame($needs_blame); + $file_query->loadFileContent(); // TODO: image // TODO: blame of blame. switch ($selected) { case 'plain': - case 'plainblame': $style = "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace"; $corpus = phutil_render_tag( @@ -119,25 +102,49 @@ class DiffusionBrowseFileController extends DiffusionController { 'style' => $style, ), phutil_escape_html($file_query->getRawData())); + break; + case 'plainblame': + $style = + "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace"; + list($text_list, $rev_list, $blame_dict) = + $file_query->getBlameData(); + + $rows = array(); + foreach ($text_list as $k => $line) { + $rev = $rev_list[$k]; + $author = $blame_dict[$rev]['author']; + $rows[] = + sprintf("%-10s %-15s %s", substr($rev, 0, 7), $author, $line); + } + + $corpus = phutil_render_tag( + 'textarea', + array( + 'style' => $style, + ), + phutil_escape_html(implode("\n", $rows))); + + break; + case 'highlighted': case 'blame': default: require_celerity_resource('syntax-highlighting-css'); require_celerity_resource('diffusion-source-css'); - list($data, $blamedata, $revs) = $file_query->getTokenizedData(); + list($text_list, $rev_list, $blame_dict) = $file_query->getBlameData(); $drequest = $this->getDiffusionRequest(); $path = $drequest->getPath(); $highlightEngine = new PhutilDefaultSyntaxHighlighterEngine(); - $data = $highlightEngine->highlightSource($path, $data); - $data = explode("\n", rtrim($data)); + $text_list = explode("\n", $highlightEngine->highlightSource($path, + implode("\n", $text_list))); - $rows = $this->buildDisplayRows($data, $blame, $blamedata, $drequest, - $revs); + $rows = $this->buildDisplayRows($text_list, $rev_list, $blame_dict, + $needs_blame, $drequest); $corpus_table = phutil_render_tag( 'table', @@ -159,49 +166,51 @@ class DiffusionBrowseFileController extends DiffusionController { } - private static function buildDisplayRows($data, $blame, $blamedata, $drequest, - $revs) { - $last = null; + private static function buildDisplayRows($text_list, $rev_list, $blame_dict, + $needs_blame, DiffusionRequest $drequest) { + $last_rev = null; $color = null; $rows = array(); $n = 1; - foreach ($data as $k => $line) { - if ($blame) { - if ($last == $blamedata[$k][0]) { - $blameinfo = + + $epoch_list = ipull($blame_dict, 'epoch'); + $max = max($epoch_list); + $min = min($epoch_list); + $range = $max - $min + 1; + + foreach ($text_list as $k => $line) { + if ($needs_blame) { + // If the line's rev is same as the line above, show empty content + // with same color; otherwise generate blame info. The newer a change + // is, the darker the color. + $rev = $rev_list[$k]; + if ($last_rev == $rev) { + $blame_info = ''. ''; } else { - switch ($drequest->getRepository()->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - // TODO: better color for git. - $color = '#dddddd'; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $color = sprintf( - '#%02xee%02x', - $revs[$blamedata[$k][0]], - $revs[$blamedata[$k][0]]); - break; - default: - throw new Exception('repository type not supported'); - } - $revision_link = self::renderRevision( - $drequest, - $blamedata[$k][0]); - $author_link = $blamedata[$k][1]; - $blameinfo = + $color_number = (int)(0xEE - + 0xEE * ($blame_dict[$rev]['epoch'] - $min) / $range); + $color = sprintf('#%02xee%02x', $color_number, $color_number); + + $revision_link = self::renderRevision( + $drequest, + substr($rev, 0, 7)); + + $author_link = $blame_dict[$rev]['author']; + $blame_info = ''.$revision_link.''. ''.$author_link.''; - $last = $blamedata[$k][0]; + $last_rev = $rev; } } else { - $blameinfo = null; + $blame_info = null; } + // Highlight the line of interest if needed. if ($n == $drequest->getLine()) { $tr = ''; $targ = ''; @@ -212,6 +221,7 @@ class DiffusionBrowseFileController extends DiffusionController { $targ = null; } + // Create the row display. $uri_path = $drequest->getUriPath(); $uri_rev = $drequest->getCommit(); @@ -222,10 +232,27 @@ class DiffusionBrowseFileController extends DiffusionController { ), $n); - $rows[] = $tr.$blameinfo.''.$l.''.$targ.$line.''; + $rows[] = $tr.$blame_info.''.$l.''.$targ.$line.''; ++$n; } return $rows; } + + + private static function renderRevision(DiffusionRequest $drequest, + $revision) { + + $callsign = $drequest->getCallsign(); + + $name = 'r'.$callsign.$revision; + return phutil_render_tag( + 'a', + array( + 'href' => '/'.$name, + ), + $name + ); + } + } diff --git a/src/applications/diffusion/controller/file/__init__.php b/src/applications/diffusion/controller/file/__init__.php index 7b85e05b1f..529bf8288a 100644 --- a/src/applications/diffusion/controller/file/__init__.php +++ b/src/applications/diffusion/controller/file/__init__.php @@ -8,13 +8,13 @@ phutil_require_module('phabricator', 'applications/diffusion/controller/base'); phutil_require_module('phabricator', 'applications/diffusion/query/filecontent/base'); -phutil_require_module('phabricator', 'applications/repository/constants/repositorytype'); phutil_require_module('phabricator', 'infrastructure/celerity/api'); phutil_require_module('phabricator', 'infrastructure/javelin/api'); phutil_require_module('phabricator', 'view/layout/panel'); phutil_require_module('phutil', 'markup'); phutil_require_module('phutil', 'markup/syntax/engine/default'); +phutil_require_module('phutil', 'utils'); phutil_require_source('DiffusionBrowseFileController.php'); diff --git a/src/applications/diffusion/query/filecontent/base/DiffusionFileContentQuery.php b/src/applications/diffusion/query/filecontent/base/DiffusionFileContentQuery.php index a832e6cc77..42be58a645 100644 --- a/src/applications/diffusion/query/filecontent/base/DiffusionFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/base/DiffusionFileContentQuery.php @@ -20,6 +20,7 @@ abstract class DiffusionFileContentQuery { private $request; private $needsBlame; + private $fileContent; final private function __construct() { // @@ -54,20 +55,53 @@ abstract class DiffusionFileContentQuery { } final public function loadFileContent() { - return $this->executeQuery(); + $this->fileContent = $this->executeQuery(); } abstract protected function executeQuery(); final public function getRawData() { - return $this->loadFileContent()->getCorpus(); + return $this->fileContent->getCorpus(); } - final public function getTokenizedData() { - return $this->tokenizeData($this->getRawData()); + final public function getBlameData() { + $raw_data = $this->getRawData(); + + $text_list = array(); + $rev_list = array(); + $blame_dict = array(); + + if (!$this->getNeedsBlame()) { + $text_list = explode("\n", rtrim($raw_data)); + } else { + foreach (explode("\n", rtrim($raw_data)) as $k => $line) { + list($rev_id, $author, $text) = $this->tokenizeLine($line); + + $text_list[$k] = $text; + $rev_list[$k] = $rev_id; + + if (!isset($blame_dict[$rev_id]) && + !isset($blame_dict[$rev_id]['author'] )) { + $blame_dict[$rev_id]['author'] = $author; + } + } + + $repository = $this->getRequest()->getRepository(); + $commits = id(new PhabricatorRepositoryCommit())->loadAllWhere( + 'repositoryID = %d AND commitIdentifier IN (%Ls)', $repository->getID(), + array_unique($rev_list)); + + foreach ($commits as $commit) { + $commitIdentifier = $commit->getCommitIdentifier(); + $epoch = $commit->getEpoch(); + $blame_dict[$commitIdentifier]['epoch'] = $epoch; + } + } + + return array($text_list, $rev_list, $blame_dict); } - abstract protected function tokenizeData($data); + abstract protected function tokenizeLine($line); public function setNeedsBlame($needs_blame) { diff --git a/src/applications/diffusion/query/filecontent/base/__init__.php b/src/applications/diffusion/query/filecontent/base/__init__.php index f964d49824..7ad4326f05 100644 --- a/src/applications/diffusion/query/filecontent/base/__init__.php +++ b/src/applications/diffusion/query/filecontent/base/__init__.php @@ -7,8 +7,10 @@ phutil_require_module('phabricator', 'applications/repository/constants/repositorytype'); +phutil_require_module('phabricator', 'applications/repository/storage/commit'); phutil_require_module('phutil', 'symbols'); +phutil_require_module('phutil', 'utils'); phutil_require_source('DiffusionFileContentQuery.php'); diff --git a/src/applications/diffusion/query/filecontent/git/DiffusionGitFileContentQuery.php b/src/applications/diffusion/query/filecontent/git/DiffusionGitFileContentQuery.php index 0465f6ccf0..9176fbfebb 100644 --- a/src/applications/diffusion/query/filecontent/git/DiffusionGitFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/git/DiffusionGitFileContentQuery.php @@ -28,7 +28,7 @@ final class DiffusionGitFileContentQuery extends DiffusionFileContentQuery { $local_path = $repository->getDetail('local-path'); if ($this->getNeedsBlame()) { list($corpus) = execx( - '(cd %s && git --no-pager blame -c --date short %s -- %s)', + '(cd %s && git --no-pager blame -c -l --date short %s -- %s)', $local_path, $commit, $path); @@ -46,35 +46,17 @@ final class DiffusionGitFileContentQuery extends DiffusionFileContentQuery { return $file_content; } - protected function tokenizeData($data) { + + protected function tokenizeLine($line) { $m = array(); - $blamedata = array(); - $revs = array(); - - if ($this->getNeedsBlame()) { - $data = explode("\n", rtrim($data)); - foreach ($data as $k => $line) { - // sample line: - // d1b4fcdd ( hzhao 2009-05-01 202)function print(); - preg_match('/^\s*?(\S+?)\s*\(\s*(\S+)\s+\S+\s+\d+\)(.*)?$/', - $line, $m); - $data[$k] = idx($m, 3); - $blamedata[$k] = array($m[1], $m[2]); - - $revs[$m[1]] = true; - } - $data = implode("\n", $data); - - krsort($revs); - $ii = 0; - $len = count($revs); - foreach ($revs as $rev => $ignored) { - $revs[$rev] = (int)(0xEE * ($ii / $len)); - ++$ii; - } - } - - return array($data, $blamedata, $revs); + // sample line: + // d1b4fcdd2a7c8c0f8cbdd01ca839d992135424dc + // ( hzhao 2009-05-01 202)function print(); + preg_match('/^\s*?(\S+?)\s*\(\s*(\S+)\s+\S+\s+\d+\)(.*)?$/', $line, $m); + $rev_id = $m[1]; + $author = $m[2]; + $text = idx($m, 3); + return array($rev_id, $author, $text); } } diff --git a/src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php b/src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php index 27f1b05326..64e7f9f085 100644 --- a/src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php @@ -56,35 +56,16 @@ final class DiffusionSvnFileContentQuery extends DiffusionFileContentQuery { return $file_content; } - protected function tokenizeData($data) - { + protected function tokenizeLine($line) { + // sample line: + // 347498 yliu function print(); $m = array(); - $blamedata = array(); - $revs = array(); + preg_match('/^\s*(\d+)\s+(\S+)(?: (.*))?$/', $line, $m); + $rev_id = $m[1]; + $author = $m[2]; + $text = idx($m, 3); - if ($this->getNeedsBlame()) { - $data = explode("\n", rtrim($data)); - foreach ($data as $k => $line) { - // sample line: - // 347498 yliu function print(); - preg_match('/^\s*(\d+)\s+(\S+)(?: (.*))?$/', $line, $m); - $data[$k] = idx($m, 3); - $blamedata[$k] = array($m[1], $m[2]); - - $revs[$m[1]] = true; - } - $data = implode("\n", $data); - - krsort($revs); - $ii = 0; - $len = count($revs); - foreach ($revs as $rev => $ignored) { - $revs[$rev] = (int)(0xEE * ($ii / $len)); - ++$ii; - } - } - - return array($data, $blamedata, $revs); + return array($rev_id, $author, $text); } }