diff --git a/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php index 6b2632a658..b7f4a161cd 100644 --- a/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php +++ b/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php @@ -23,12 +23,11 @@ class DiffusionBrowseFileController extends DiffusionController { // Build the view selection form. $select_map = array( 'highlighted' => 'View as Highlighted Text', - //'blame' => 'View as Highlighted Text with Blame', + 'blame' => 'View as Highlighted Text with Blame', 'plain' => 'View as Plain Text', - //'plainblame' => 'View as Plain Text with Blame', + 'plainblame' => 'View as Plain Text with Blame', ); - $drequest = $this->getDiffusionRequest(); $request = $this->getRequest(); $selected = $request->getStr('view'); @@ -58,79 +57,8 @@ class DiffusionBrowseFileController extends DiffusionController { ''); $view_select_panel->appendChild($view_select_form); - $file_query = DiffusionFileContentQuery::newFromDiffusionRequest( - $this->diffusionRequest); - $file_content = $file_query->loadFileContent(); - // Build the content of the file. - // TODO: image - // TODO: blame. - switch ($selected) { - case 'plain': - $style = - "margin: 1em 2em; width: 90%; height: 80em; font-family: monospace"; - $corpus = phutil_render_tag( - 'textarea', - array( - 'style' => $style, - ), - phutil_escape_html($file_content->getCorpus())); - break; - - case 'highlighted': - default: - require_celerity_resource('syntax-highlighting-css'); - require_celerity_resource('diffusion-source-css'); - - $path = $drequest->getPath(); - $highlightEngine = new PhutilDefaultSyntaxHighlighterEngine(); - $data = $highlightEngine->highlightSource($path, - $file_content->getCorpus()); - $data = explode("\n", rtrim($data)); - - $uri_path = $drequest->getUriPath(); - $uri_rev = $drequest->getCommit(); - - $color = null; - $rows = array(); - $n = 1; - foreach ($data as $k => $line) { - if ($n == $drequest->getLine()) { - $tr = ''; - $targ = ''; - Javelin::initBehavior('diffusion-jump-to', - array('target' => 'scroll_target')); - } else { - $tr = ''; - $targ = null; - } - - $l = phutil_render_tag( - 'a', - array( - 'href' => $uri_path.';'.$uri_rev.'$'.$n, - ), - $n); - - $rows[] = $tr.''.$l.''.$targ.$line.''; - ++$n; - } - - $corpus_table = phutil_render_tag( - 'table', - array( - 'class' => "diffusion-source remarkup-code", - ), - implode("\n", $rows)); - $corpus = phutil_render_tag( - 'div', - array( - 'style' => 'padding: 0pt 2em;', - ), - $corpus_table); - - break; - } + $corpus = $this->buildCorpus($selected); // Render the page. $content = array(); @@ -152,4 +80,152 @@ class DiffusionBrowseFileController extends DiffusionController { 'title' => 'Browse', )); } + + + 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'); + + $file_query = DiffusionFileContentQuery::newFromDiffusionRequest( + $this->diffusionRequest); + $file_query->setNeedsBlame($blame); + + // 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( + 'textarea', + array( + 'style' => $style, + ), + phutil_escape_html($file_query->getRawData())); + 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(); + + $drequest = $this->getDiffusionRequest(); + $path = $drequest->getPath(); + $highlightEngine = new PhutilDefaultSyntaxHighlighterEngine(); + $data = $highlightEngine->highlightSource($path, $data); + + $data = explode("\n", rtrim($data)); + + $rows = $this->buildDisplayRows($data, $blame, $blamedata, $drequest, + $revs); + + $corpus_table = phutil_render_tag( + 'table', + array( + 'class' => "diffusion-source remarkup-code", + ), + implode("\n", $rows)); + $corpus = phutil_render_tag( + 'div', + array( + 'style' => 'padding: 0pt 2em;', + ), + $corpus_table); + + break; + } + + return $corpus; + } + + + private static function buildDisplayRows($data, $blame, $blamedata, $drequest, + $revs) { + $last = null; + $color = null; + $rows = array(); + $n = 1; + foreach ($data as $k => $line) { + if ($blame) { + if ($last == $blamedata[$k][0]) { + $blameinfo = + ''. + ''; + } 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 = + ''.$revision_link.''. + ''.$author_link.''; + $last = $blamedata[$k][0]; + } + } else { + $blameinfo = null; + } + + if ($n == $drequest->getLine()) { + $tr = ''; + $targ = ''; + Javelin::initBehavior('diffusion-jump-to', + array('target' => 'scroll_target')); + } else { + $tr = ''; + $targ = null; + } + + $uri_path = $drequest->getUriPath(); + $uri_rev = $drequest->getCommit(); + + $l = phutil_render_tag( + 'a', + array( + 'href' => $uri_path.';'.$uri_rev.'$'.$n, + ), + $n); + + $rows[] = $tr.$blameinfo.''.$l.''.$targ.$line.''; + ++$n; + } + + return $rows; + } } diff --git a/src/applications/diffusion/controller/file/__init__.php b/src/applications/diffusion/controller/file/__init__.php index 971b270b73..7b85e05b1f 100644 --- a/src/applications/diffusion/controller/file/__init__.php +++ b/src/applications/diffusion/controller/file/__init__.php @@ -8,6 +8,7 @@ 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'); diff --git a/src/applications/diffusion/query/filecontent/base/DiffusionFileContentQuery.php b/src/applications/diffusion/query/filecontent/base/DiffusionFileContentQuery.php index 62dc310b34..a832e6cc77 100644 --- a/src/applications/diffusion/query/filecontent/base/DiffusionFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/base/DiffusionFileContentQuery.php @@ -19,6 +19,7 @@ abstract class DiffusionFileContentQuery { private $request; + private $needsBlame; final private function __construct() { // @@ -57,4 +58,24 @@ abstract class DiffusionFileContentQuery { } abstract protected function executeQuery(); + + final public function getRawData() { + return $this->loadFileContent()->getCorpus(); + } + + final public function getTokenizedData() { + return $this->tokenizeData($this->getRawData()); + } + + abstract protected function tokenizeData($data); + + public function setNeedsBlame($needs_blame) + { + $this->needsBlame = $needs_blame; + } + + public function getNeedsBlame() + { + return $this->needsBlame; + } } diff --git a/src/applications/diffusion/query/filecontent/git/DiffusionGitFileContentQuery.php b/src/applications/diffusion/query/filecontent/git/DiffusionGitFileContentQuery.php index a03c464958..0465f6ccf0 100644 --- a/src/applications/diffusion/query/filecontent/git/DiffusionGitFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/git/DiffusionGitFileContentQuery.php @@ -26,12 +26,19 @@ final class DiffusionGitFileContentQuery extends DiffusionFileContentQuery { $commit = $drequest->getCommit(); $local_path = $repository->getDetail('local-path'); - - list($corpus) = execx( - '(cd %s && git cat-file blob %s:%s)', - $local_path, - $commit, - $path); + if ($this->getNeedsBlame()) { + list($corpus) = execx( + '(cd %s && git --no-pager blame -c --date short %s -- %s)', + $local_path, + $commit, + $path); + } else { + list($corpus) = execx( + '(cd %s && git cat-file blob %s:%s)', + $local_path, + $commit, + $path); + } $file_content = new DiffusionFileContent(); $file_content->setCorpus($corpus); @@ -39,4 +46,35 @@ final class DiffusionGitFileContentQuery extends DiffusionFileContentQuery { return $file_content; } + protected function tokenizeData($data) { + $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); + } + } diff --git a/src/applications/diffusion/query/filecontent/git/__init__.php b/src/applications/diffusion/query/filecontent/git/__init__.php index 364b4c0c9e..94c1835f75 100644 --- a/src/applications/diffusion/query/filecontent/git/__init__.php +++ b/src/applications/diffusion/query/filecontent/git/__init__.php @@ -10,6 +10,7 @@ phutil_require_module('phabricator', 'applications/diffusion/data/filecontent'); phutil_require_module('phabricator', 'applications/diffusion/query/filecontent/base'); phutil_require_module('phutil', 'future/exec'); +phutil_require_module('phutil', 'utils'); phutil_require_source('DiffusionGitFileContentQuery.php'); diff --git a/src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php b/src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php index dc3deece81..27f1b05326 100644 --- a/src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php +++ b/src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php @@ -29,7 +29,8 @@ final class DiffusionSvnFileContentQuery extends DiffusionFileContentQuery { try { list($corpus) = execx( - 'svn --non-interactive cat %s%s@%s', + 'svn --non-interactive %s %s%s@%s', + $this->getNeedsBlame() ? 'blame' : 'cat', $remote_uri, $path, $commit); @@ -55,4 +56,35 @@ final class DiffusionSvnFileContentQuery extends DiffusionFileContentQuery { return $file_content; } + protected function tokenizeData($data) + { + $m = array(); + $blamedata = array(); + $revs = array(); + + 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); + } + } diff --git a/src/applications/diffusion/query/filecontent/svn/__init__.php b/src/applications/diffusion/query/filecontent/svn/__init__.php index 3b9b44f8f9..c10e912fa8 100644 --- a/src/applications/diffusion/query/filecontent/svn/__init__.php +++ b/src/applications/diffusion/query/filecontent/svn/__init__.php @@ -10,6 +10,7 @@ phutil_require_module('phabricator', 'applications/diffusion/data/filecontent'); phutil_require_module('phabricator', 'applications/diffusion/query/filecontent/base'); phutil_require_module('phutil', 'future/exec'); +phutil_require_module('phutil', 'utils'); phutil_require_source('DiffusionSvnFileContentQuery.php');