diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -390,7 +390,7 @@
     'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'f01586dc',
     'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '1db13e70',
     'rsrc/js/application/drydock/drydock-live-operation-status.js' => '901935ef',
-    'rsrc/js/application/files/behavior-document-engine.js' => 'ed539253',
+    'rsrc/js/application/files/behavior-document-engine.js' => '6760beb4',
     'rsrc/js/application/files/behavior-icon-composer.js' => '8499b6ab',
     'rsrc/js/application/files/behavior-launch-icon-composer.js' => '48086888',
     'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => '191b4909',
@@ -604,7 +604,7 @@
     'javelin-behavior-diffusion-jump-to' => '73d09eef',
     'javelin-behavior-diffusion-locate-file' => '6d3e1947',
     'javelin-behavior-diffusion-pull-lastmodified' => 'f01586dc',
-    'javelin-behavior-document-engine' => 'ed539253',
+    'javelin-behavior-document-engine' => '6760beb4',
     'javelin-behavior-doorkeeper-tag' => '1db13e70',
     'javelin-behavior-drydock-live-operation-status' => '901935ef',
     'javelin-behavior-durable-column' => '2ae077e1',
@@ -1398,6 +1398,11 @@
       'javelin-json',
       'phuix-form-control-view',
     ),
+    '6760beb4' => array(
+      'javelin-behavior',
+      'javelin-dom',
+      'javelin-stratcom',
+    ),
     '680ea2c8' => array(
       'javelin-install',
       'javelin-dom',
@@ -2116,11 +2121,6 @@
       'javelin-stratcom',
       'javelin-vector',
     ),
-    'ed539253' => array(
-      'javelin-behavior',
-      'javelin-dom',
-      'javelin-stratcom',
-    ),
     'edf8a145' => array(
       'javelin-behavior',
       'javelin-uri',
diff --git a/src/view/layout/PhabricatorSourceCodeView.php b/src/view/layout/PhabricatorSourceCodeView.php
--- a/src/view/layout/PhabricatorSourceCodeView.php
+++ b/src/view/layout/PhabricatorSourceCodeView.php
@@ -130,8 +130,8 @@
         $lines = idx($blame_map, $line_number);
 
         if ($lines) {
-          $skip_blame = 'skip;'.$lines;
-          $info_blame = 'info;'.$lines;
+          $skip_blame = 'skip';
+          $info_blame = 'info';
         } else {
           $skip_blame = null;
           $info_blame = null;
@@ -149,6 +149,7 @@
             array(
               'class' => 'phabricator-source-blame-info',
               'data-blame' => $info_blame,
+              'data-blame-lines' => $lines,
             )),
         );
       } else {
diff --git a/webroot/rsrc/js/application/files/behavior-document-engine.js b/webroot/rsrc/js/application/files/behavior-document-engine.js
--- a/webroot/rsrc/js/application/files/behavior-document-engine.js
+++ b/webroot/rsrc/js/application/files/behavior-document-engine.js
@@ -239,31 +239,48 @@
     // We're ready to render.
     var viewport = JX.$(data.viewportID);
 
-    var cells = JX.DOM.scry(viewport, 'th');
+    var row_nodes = JX.DOM.scry(viewport, 'tr');
+    var row_list = [];
+    var ii;
 
-    for (var ii = 0; ii < cells.length; ii++) {
-      var cell = cells[ii];
+    for (ii = 0; ii < row_nodes.length; ii++) {
+      var row = {};
+      var keep = false;
+      var node = row_nodes[ii];
 
-      var spec = cell.getAttribute('data-blame');
-      if (!spec) {
-        continue;
-      }
+      for (var jj = 0; jj < node.childNodes.length; jj++) {
+        var child = node.childNodes[jj];
 
-      spec = spec.split(';');
-      var type = spec[0];
-      var lines = spec[1];
+        if (!JX.DOM.isType(child, 'th')) {
+          continue;
+        }
 
-      var content = null;
-      switch (type) {
-        case 'skip':
-          content = renderSkip(data.blame.value, lines);
-          break;
-        case 'info':
-          content = renderInfo(data.blame.value, lines);
-          break;
+        var spec = child.getAttribute('data-blame');
+        if (spec) {
+          row[spec] = child;
+          keep = true;
+        }
+
+        if (spec === 'info') {
+          row.lines = child.getAttribute('data-blame-lines');
+        }
+      }
+
+      if (keep) {
+        row_list.push(row);
       }
+    }
+
+    var last = null;
+    for (ii = 0; ii < row_list.length; ii++) {
+      var commit = data.blame.value.blame[row_list[ii].lines - 1];
+      row_list[ii].commit = commit;
+      row_list[ii].last = last;
+      last = commit;
+    }
 
-      JX.DOM.setContent(cell, content);
+    for (ii = 0; ii < row_list.length; ii++) {
+      renderBlame(row_list[ii], data.blame.value);
     }
   }
 
@@ -273,26 +290,25 @@
     blame(data);
   }
 
-  function renderSkip(blame, lines) {
-    var commit = blame.blame[lines - 1];
-    if (!commit) {
-      return null;
-    }
+  function renderBlame(row, blame) {
+    var spec = blame.map[row.commit];
 
-    var spec = blame.map[commit];
 
-    return JX.$H(spec.skip);
-  }
+    var info = null;
+    var skip = null;
 
-  function renderInfo(blame, lines) {
-    var commit = blame.blame[lines - 1];
-    if (!commit) {
-      return null;
+    if (spec && (row.commit != row.last)) {
+      skip = JX.$H(spec.skip);
+      info = JX.$H(spec.info);
     }
 
-    var spec = blame.map[commit];
+    if (row.skip) {
+      JX.DOM.setContent(row.skip, skip);
+    }
 
-    return JX.$H(spec.info);
+    if (row.info) {
+      JX.DOM.setContent(row.info, info);
+    }
   }
 
   if (!statics.initialized) {