diff --git a/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php b/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php
index a6f5ca632..f2ac3271a 100644
--- a/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php
+++ b/src/applications/diffusion/controller/file/DiffusionBrowseFileController.php
@@ -1,231 +1,258 @@
 <?php
 
 /*
  * Copyright 2011 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *   http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 class DiffusionBrowseFileController extends DiffusionController {
 
   public function processRequest() {
 
     // Build the view selection form.
     $select_map = array(
       'highlighted' => 'View as Highlighted Text',
       'blame' => 'View as Highlighted Text with Blame',
       'plain' => 'View as Plain Text',
       'plainblame' => 'View as Plain Text with Blame',
     );
 
     $request = $this->getRequest();
 
     $selected = $request->getStr('view');
     $select = '<select name="view">';
     foreach ($select_map as $k => $v) {
       $option = phutil_render_tag(
         'option',
         array(
           'value' => $k,
           'selected' => ($k == $selected) ? 'selected' : null,
         ),
         phutil_escape_html($v));
 
       $select .= $option;
     }
     $select .= '</select>';
 
     $view_select_panel = new AphrontPanelView();
     $view_select_form = phutil_render_tag(
       'form',
       array(
         'action' => $request->getRequestURI(),
         'method' => 'get',
         'style'  => 'display: inline',
       ),
       $select.
       '<button>view</button>');
     $view_select_panel->appendChild($view_select_form);
 
     // Build the content of the file.
     $corpus = $this->buildCorpus($selected);
 
     // Render the page.
     $content = array();
     $content[] = $this->buildCrumbs(
       array(
         'branch' => true,
         'path'   => true,
         'view'   => 'browse',
       ));
     $content[] = $view_select_panel;
     $content[] = $corpus;
 
     $nav = $this->buildSideNav('browse', true);
     $nav->appendChild($content);
 
     return $this->buildStandardPageResponse(
       $nav,
       array(
         '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');
+    $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(
           'textarea',
           array(
             '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',
           array(
             'class' => "diffusion-source remarkup-code PhabricatorMonospaced",
           ),
           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;
+  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 =
             '<th style="background: '.$color.'; width: 9em;"></th>'.
             '<th style="background: '.$color.'"></th>';
         } 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');
-          }
+
+          $color_number = (int)(0xEE -
+            0xEE * ($blame_dict[$rev]['epoch'] - $min) / $range);
+          $color = sprintf('#%02xee%02x', $color_number, $color_number);
+
           $revision_link = self::renderRevision(
-           $drequest,
-           $blamedata[$k][0]);
+            $drequest,
+            substr($rev, 0, 7));
 
-          $author_link = $blamedata[$k][1];
-          $blameinfo =
+          $author_link = $blame_dict[$rev]['author'];
+          $blame_info =
             '<th style="background: '.$color.
               '; width: 9em;">'.$revision_link.'</th>'.
             '<th style="background: '.$color.
               '; font-weight: normal; color: #333;">'.$author_link.'</th>';
-          $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 = '<tr style="background: #ffff00;">';
         $targ = '<a id="scroll_target"></a>';
         Javelin::initBehavior('diffusion-jump-to',
           array('target' => 'scroll_target'));
       } else {
         $tr = '<tr>';
         $targ = null;
       }
 
+      // Create the row display.
       $uri_path = $drequest->getUriPath();
       $uri_rev  = $drequest->getCommit();
 
       $l = phutil_render_tag(
         'a',
         array(
           'href' => $uri_path.';'.$uri_rev.'$'.$n,
         ),
         $n);
 
-      $rows[] = $tr.$blameinfo.'<th>'.$l.'</th><td>'.$targ.$line.'</td></tr>';
+      $rows[] = $tr.$blame_info.'<th>'.$l.'</th><td>'.$targ.$line.'</td></tr>';
       ++$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 7b85e05b1..529bf8288 100644
--- a/src/applications/diffusion/controller/file/__init__.php
+++ b/src/applications/diffusion/controller/file/__init__.php
@@ -1,20 +1,20 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
 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 a832e6cc7..42be58a64 100644
--- a/src/applications/diffusion/query/filecontent/base/DiffusionFileContentQuery.php
+++ b/src/applications/diffusion/query/filecontent/base/DiffusionFileContentQuery.php
@@ -1,81 +1,115 @@
 <?php
 
 /*
  * Copyright 2011 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *   http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 abstract class DiffusionFileContentQuery {
 
   private $request;
   private $needsBlame;
+  private $fileContent;
 
   final private function __construct() {
     // <private>
   }
 
   final public static function newFromDiffusionRequest(
     DiffusionRequest $request) {
 
     $repository = $request->getRepository();
 
     switch ($repository->getVersionControlSystem()) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         $class = 'DiffusionGitFileContentQuery';
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
         $class = 'DiffusionSvnFileContentQuery';
         break;
       default:
         throw new Exception("Unsupported VCS!");
     }
 
     PhutilSymbolLoader::loadClass($class);
     $query = new $class();
 
     $query->request = $request;
 
     return $query;
   }
 
   final protected function getRequest() {
     return $this->request;
   }
 
   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)
   {
     $this->needsBlame = $needs_blame;
   }
 
   public function getNeedsBlame()
   {
     return $this->needsBlame;
   }
 }
diff --git a/src/applications/diffusion/query/filecontent/base/__init__.php b/src/applications/diffusion/query/filecontent/base/__init__.php
index f964d4982..7ad4326f0 100644
--- a/src/applications/diffusion/query/filecontent/base/__init__.php
+++ b/src/applications/diffusion/query/filecontent/base/__init__.php
@@ -1,14 +1,16 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
 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 0465f6ccf..9176fbfeb 100644
--- a/src/applications/diffusion/query/filecontent/git/DiffusionGitFileContentQuery.php
+++ b/src/applications/diffusion/query/filecontent/git/DiffusionGitFileContentQuery.php
@@ -1,80 +1,62 @@
 <?php
 
 /*
  * Copyright 2011 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *   http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 final class DiffusionGitFileContentQuery extends DiffusionFileContentQuery {
 
   protected function executeQuery() {
     $drequest = $this->getRequest();
 
     $repository = $drequest->getRepository();
     $path = $drequest->getPath();
     $commit = $drequest->getCommit();
 
     $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);
     } else {
       list($corpus) = execx(
         '(cd %s && git cat-file blob %s:%s)',
         $local_path,
         $commit,
         $path);
     }
 
     $file_content = new DiffusionFileContent();
     $file_content->setCorpus($corpus);
 
     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);
+  protected function tokenizeLine($line) {
+    $m = array();
+    // 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 27f1b0532..64e7f9f08 100644
--- a/src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php
+++ b/src/applications/diffusion/query/filecontent/svn/DiffusionSvnFileContentQuery.php
@@ -1,90 +1,71 @@
 <?php
 
 /*
  * Copyright 2011 Facebook, Inc.
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * You may obtain a copy of the License at
  *
  *   http://www.apache.org/licenses/LICENSE-2.0
  *
  * Unless required by applicable law or agreed to in writing, software
  * distributed under the License is distributed on an "AS IS" BASIS,
  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
 
 final class DiffusionSvnFileContentQuery extends DiffusionFileContentQuery {
 
   protected function executeQuery() {
     $drequest = $this->getRequest();
 
     $repository = $drequest->getRepository();
     $path = $drequest->getPath();
     $commit = $drequest->getCommit();
 
     $remote_uri = $repository->getDetail('remote-uri');
 
     try {
       list($corpus) = execx(
         'svn --non-interactive %s %s%s@%s',
         $this->getNeedsBlame() ? 'blame' : 'cat',
         $remote_uri,
         $path,
         $commit);
     } catch (CommandException $ex) {
       $stderr = $ex->getStdErr();
       if (preg_match('/path not found$/', trim($stderr))) {
         // TODO: Improve user experience for this. One way to end up here
         // is to have the parser behind and look at a file which was recently
         // nuked; Diffusion will think it still exists and try to grab content
         // at HEAD.
         throw new Exception(
           "Failed to retrieve file content from Subversion. The file may ".
           "have been recently deleted, or the Diffusion cache may be out of ".
           "date.");
       } else {
         throw $ex;
       }
     }
 
     $file_content = new DiffusionFileContent();
     $file_content->setCorpus($corpus);
 
     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);
   }
 
 }