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');