diff --git a/src/applications/differential/parser/changeset/DifferentialChangesetParser.php b/src/applications/differential/parser/changeset/DifferentialChangesetParser.php
index cd684cfef8..58ba4810e4 100644
--- a/src/applications/differential/parser/changeset/DifferentialChangesetParser.php
+++ b/src/applications/differential/parser/changeset/DifferentialChangesetParser.php
@@ -1,1342 +1,1347 @@
 <?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 DifferentialChangesetParser {
 
   protected $visible      = array();
   protected $new          = array();
   protected $old          = array();
   protected $intra        = array();
   protected $newRender    = null;
   protected $oldRender    = null;
   protected $parsedHunk   = false;
 
   protected $filename     = null;
   protected $filetype     = null;
   protected $changesetID  = null;
   protected $missingOld   = array();
   protected $missingNew   = array();
 
   protected $comments     = array();
   protected $specialAttributes = array();
 
   protected $changeset;
   protected $whitespaceMode = null;
 
   protected $subparser;
   protected $oldChangesetID = null;
   protected $noHighlight;
 
   private $handles;
   private $user;
 
   const CACHE_VERSION = 4;
 
   const ATTR_GENERATED  = 'attr:generated';
   const ATTR_DELETED    = 'attr:deleted';
   const ATTR_UNCHANGED  = 'attr:unchanged';
   const ATTR_WHITELINES = 'attr:white';
 
   const LINES_CONTEXT = 8;
 
   const WHITESPACE_SHOW_ALL         = 'show-all';
   const WHITESPACE_IGNORE_TRAILING  = 'ignore-trailing';
   const WHITESPACE_IGNORE_ALL       = 'ignore-all';
 
   public function setRightSideCommentMapping($id, $is_new) {
 
   }
 
   public function setLeftSideCommentMapping($id, $is_new) {
 
   }
 
   public function setChangeset($changeset) {
     $this->changeset = $changeset;
 
     $this->setFilename($changeset->getFilename());
     $this->setChangesetID($changeset->getID());
 
     return $this;
   }
 
   public function setWhitespaceMode($whitespace_mode) {
     $this->whitespaceMode = $whitespace_mode;
     return $this;
   }
 
   public function setOldChangesetID($old_changeset_id) {
     $this->oldChangesetID = $old_changeset_id;
     return $this;
   }
 
   public function setChangesetID($changeset_id) {
     $this->changesetID = $changeset_id;
     return $this;
   }
 
+  public function getChangeset() {
+    return $this->changeset;
+  }
+
   public function getChangesetID() {
     return $this->changesetID;
   }
 
   public function setFilename($filename) {
     $this->filename = $filename;
     if (strpos($filename, '.', 1) !== false) {
       $parts = explode('.', $filename);
       $this->filetype = end($parts);
     }
   }
 
   public function setHandles(array $handles) {
     $this->handles = $handles;
     return $this;
   }
 
   public function setMarkupEngine(PhutilMarkupEngine $engine) {
     $this->markupEngine = $engine;
     return $this;
   }
 
   public function setUser(PhabricatorUser $user) {
     $this->user = $user;
     return $this;
   }
 
   public function parseHunk(DifferentialHunk $hunk) {
     $this->parsedHunk = true;
     $lines = $hunk->getChanges();
 
     // Flatten UTF-8 into "\0". We don't support UTF-8 because the diffing
     // algorithms are byte-oriented (not character oriented) and everyone seems
     // to be in agreement that it's fairly reasonable not to allow UTF-8 in
     // source files. These bytes will later be replaced with a "?" glyph, but
     // in the meantime we replace them with "\0" since Pygments is happy to
     // deal with that.
     $lines = preg_replace('/[\x80-\xFF]/', "\0", $lines);
 
     $lines = str_replace(
       array("\t", "\r\n", "\r"),
       array('  ', "\n",   "\n"),
       $lines);
     $lines = explode("\n", $lines);
 
     $types = array();
     foreach ($lines as $line_index => $line) {
       $lines[$line_index] = $line;
       if (isset($line[0])) {
         $char = $line[0];
         if ($char == ' ') {
           $types[$line_index] = null;
         } else if ($char == '\\' && $line_index > 0) {
           $types[$line_index] = $types[$line_index - 1];
         } else {
           $types[$line_index] = $char;
         }
       } else {
         $types[$line_index] = null;
       }
     }
 
     $old_line = $hunk->getOldOffset();
     $new_line = $hunk->getNewOffset();
     $num_lines = count($lines);
 
     if ($old_line > 1) {
       $this->missingOld[$old_line] = true;
     } else if ($new_line > 1) {
       $this->missingNew[$new_line] = true;
     }
 
     for ($cursor = 0; $cursor < $num_lines; $cursor++) {
       $type = $types[$cursor];
       $data = array(
         'type'  => $type,
         'text'  => (string)substr($lines[$cursor], 1),
         'line'  => $new_line,
       );
       switch ($type) {
         case '+':
           $this->new[] = $data;
           ++$new_line;
           break;
         case '-':
           $data['line'] = $old_line;
           $this->old[] = $data;
           ++$old_line;
           break;
         default:
           $this->new[] = $data;
           $data['line'] = $old_line;
           $this->old[] = $data;
           ++$new_line;
           ++$old_line;
           break;
       }
     }
   }
 
   public function getDisplayLine($offset, $length) {
     $start = 1;
     for ($ii = $offset; $ii > 0; $ii--) {
       if ($this->new[$ii] && $this->new[$ii]['line']) {
         $start = $this->new[$ii]['line'];
         break;
       }
     }
     $end = $start;
     for ($ii = $offset + $length; $ii < count($this->new); $ii++) {
       if ($this->new[$ii] && $this->new[$ii]['line']) {
         $end = $this->new[$ii]['line'];
         break;
       }
     }
     return "{$start},{$end}";
   }
 
   public function parseInlineComment(DifferentialInlineComment $comment) {
     $this->comments[] = $comment;
     return $this;
   }
 
   public function process() {
 
     $old = array();
     $new = array();
 
     $n = 0;
 
     $this->old = array_reverse($this->old);
     $this->new = array_reverse($this->new);
 
     $whitelines = false;
     $changed = false;
 
     $skip_intra = array();
     while (count($this->old) || count($this->new)) {
       $o_desc = array_pop($this->old);
       $n_desc = array_pop($this->new);
 
       $oend = end($this->old);
       if ($oend) {
         $o_next = $oend['type'];
       } else {
         $o_next = null;
       }
 
       $nend = end($this->new);
       if ($nend) {
         $n_next = $nend['type'];
       } else {
         $n_next = null;
       }
 
       if ($o_desc) {
         $o_type = $o_desc['type'];
       } else {
         $o_type = null;
       }
 
       if ($n_desc) {
         $n_type = $n_desc['type'];
       } else {
         $n_type = null;
       }
 
       if (($o_type != null) && ($n_type == null)) {
         $old[] = $o_desc;
         $new[] = null;
         if ($n_desc) {
           array_push($this->new, $n_desc);
         }
         $changed = true;
         continue;
       }
 
       if (($n_type != null) && ($o_type == null)) {
         $old[] = null;
         $new[] = $n_desc;
         if ($o_desc) {
           array_push($this->old, $o_desc);
         }
         $changed = true;
         continue;
       }
 
       if ($this->whitespaceMode != self::WHITESPACE_SHOW_ALL) {
         $similar = false;
         switch ($this->whitespaceMode) {
           case self::WHITESPACE_IGNORE_TRAILING:
             if (rtrim($o_desc['text']) == rtrim($n_desc['text'])) {
               $similar = true;
             }
             break;
         }
         if ($similar) {
           $o_desc['type'] = null;
           $n_desc['type'] = null;
           $skip_intra[count($old)] = true;
           $whitelines = true;
         } else {
           $changed = true;
         }
       } else {
         $changed = true;
       }
 
       $old[] = $o_desc;
       $new[] = $n_desc;
     }
 
     $this->old = $old;
     $this->new = $new;
 
     if ($this->subparser && false) { // TODO: This is bugged
 
       // Use the subparser's side-by-side line information -- notably, the
       // change types -- but replace all the line text with ours. This lets us
       // render whitespace-only changes without marking them as different.
 
       $old = $this->subparser->old;
       $new = $this->subparser->new;
 
       $old_text = ipull($this->old, 'text', 'line');
       $new_text = ipull($this->new, 'text', 'line');
 
       foreach ($old as $k => $desc) {
         if (empty($desc)) {
           continue;
         }
         $old[$k]['text'] = idx($old_text, $desc['line']);
       }
       foreach ($new as $k => $desc) {
         if (empty($desc)) {
           continue;
         }
         $new[$k]['text'] = idx($new_text, $desc['line']);
       }
 
       $this->old = $old;
       $this->new = $new;
     }
 
     $min_length = min(count($this->old), count($this->new));
     for ($ii = 0; $ii < $min_length; $ii++) {
       if ($this->old[$ii] || $this->new[$ii]) {
         if (isset($this->old[$ii]['text'])) {
           $otext = $this->old[$ii]['text'];
         } else {
           $otext = '';
         }
         if (isset($this->new[$ii]['text'])) {
           $ntext = $this->new[$ii]['text'];
         } else {
           $ntext = '';
         }
         if ($otext != $ntext && empty($skip_intra[$ii])) {
           $this->intra[$ii] = ArcanistDiffUtils::generateIntralineDiff(
             $otext,
             $ntext);
         }
       }
     }
 
     $lines_context = self::LINES_CONTEXT;
     $max_length = max(count($this->old), count($this->new));
     $old = $this->old;
     $new = $this->new;
     $visible = false;
     $last = 0;
     for ($cursor = -$lines_context; $cursor < $max_length; $cursor++) {
       $offset = $cursor + $lines_context;
       if ((isset($old[$offset]) && $old[$offset]['type']) ||
           (isset($new[$offset]) && $new[$offset]['type'])) {
         $visible = true;
         $last = $offset;
       } else if ($cursor > $last + $lines_context) {
         $visible = false;
       }
       if ($visible && $cursor > 0) {
         $this->visible[$cursor] = 1;
       }
     }
 
     $old_corpus = ipull($this->old, 'text');
     $old_corpus_block = implode("\n", $old_corpus);
 
     $new_corpus = ipull($this->new, 'text');
     $new_corpus_block = implode("\n", $new_corpus);
 
     if ($this->noHighlight) {
       $this->oldRender = explode("\n", phutil_escape_html($old_corpus_block));
       $this->newRender = explode("\n", phutil_escape_html($new_corpus_block));
     } else {
       $this->oldRender = $this->sourceHighlight($this->old, $old_corpus_block);
       $this->newRender = $this->sourceHighlight($this->new, $new_corpus_block);
     }
 
     $this->applyIntraline(
       $this->oldRender,
       ipull($this->intra, 0),
       $old_corpus);
     $this->applyIntraline(
       $this->newRender,
       ipull($this->intra, 1),
       $new_corpus);
 
     $this->tokenHighlight($this->oldRender);
     $this->tokenHighlight($this->newRender);
 
     $unchanged = false;
     if ($this->subparser && false) {
       $unchanged = $this->subparser->isUnchanged();
       $whitelines = $this->subparser->isWhitespaceOnly();
     } else if (!$changed) {
       $filetype = $this->changeset->getFileType();
       if ($filetype == DifferentialChangeType::FILE_TEXT ||
           $filetype == DifferentialChangeType::FILE_SYMLINK) {
         $unchanged = true;
       }
     }
 
     $generated = (strpos($new_corpus_block, '@'.'generated') !== false);
 
     $this->specialAttributes = array(
       self::ATTR_GENERATED  => $generated,
       self::ATTR_UNCHANGED  => $unchanged,
       self::ATTR_DELETED    => array_filter($this->old) &&
                                !array_filter($this->new),
       self::ATTR_WHITELINES => $whitelines
     );
   }
 
   public function loadCache() {
     if (!$this->changesetID) {
       return false;
     }
 
     $data = null;
 
     $changeset = new DifferentialChangeset();
     $conn_r = $changeset->establishConnection('r');
     $data = queryfx_one(
       $conn_r,
       'SELECT * FROM %T WHERE id = %d',
       $changeset->getTableName().'_parse_cache',
       $this->changesetID);
 
     if (!$data) {
       return false;
     }
 
     $data = json_decode($data['cache'], true);
     if (!is_array($data) || !$data) {
       return false;
     }
 
     foreach (self::getCacheableProperties() as $cache_key) {
       if (!array_key_exists($cache_key, $data)) {
         // If we're missing a cache key, assume we're looking at an old cache
         // and ignore it.
         return false;
       }
     }
 
     if ($data['cacheVersion'] !== self::CACHE_VERSION) {
       return false;
     }
 
     unset($data['cacheVersion'], $data['cacheHost']);
     $cache_prop = array_select_keys($data, self::getCacheableProperties());
     foreach ($cache_prop as $cache_key => $v) {
       $this->$cache_key = $v;
     }
 
     return true;
   }
 
   protected static function getCacheableProperties() {
     return array(
       'visible',
       'new',
       'old',
       'intra',
       'newRender',
       'oldRender',
       'specialAttributes',
       'missingOld',
       'missingNew',
       'cacheVersion',
       'cacheHost',
     );
   }
 
   public function saveCache() {
     if (!$this->changesetID) {
       return false;
     }
 
     $cache = array();
     foreach (self::getCacheableProperties() as $cache_key) {
       switch ($cache_key) {
         case 'cacheVersion':
           $cache[$cache_key] = self::CACHE_VERSION;
           break;
         case 'cacheHost':
           $cache[$cache_key] = php_uname('n');
           break;
         default:
           $cache[$cache_key] = $this->$cache_key;
           break;
       }
     }
     $cache = json_encode($cache);
 
     try {
       $changeset = new DifferentialChangeset();
       $conn_w = $changeset->establishConnection('w');
       queryfx(
         $conn_w,
         'INSERT INTO %T (id, cache) VALUES (%d, %s)
           ON DUPLICATE KEY UPDATE cache = VALUES(cache)',
         $changeset->getTableName().'_parse_cache',
         $this->changesetID,
         $cache);
     } catch (AphrontQueryException $ex) {
       // TODO: uhoh
     }
   }
 
   public function isGenerated() {
     return idx($this->specialAttributes, self::ATTR_GENERATED, false);
   }
 
   public function isDeleted() {
     return idx($this->specialAttributes, self::ATTR_DELETED, false);
   }
 
   public function isUnchanged() {
     return idx($this->specialAttributes, self::ATTR_UNCHANGED, false);
   }
 
   public function isWhitespaceOnly() {
     return idx($this->specialAttributes, self::ATTR_WHITELINES, false);
   }
 
   public function getLength() {
     return max(count($this->old), count($this->new));
   }
 
   protected function applyIntraline(&$render, $intra, $corpus) {
     foreach ($render as $key => $text) {
       if (isset($intra[$key])) {
         $render[$key] = ArcanistDiffUtils::applyIntralineDiff(
           $text,
           $intra[$key]);
       }
       if (isset($corpus[$key]) && strlen($corpus[$key]) > 80) {
         $render[$key] = $this->lineWrap($render[$key]);
       }
     }
   }
 
   protected function lineWrap($l) {
     $c = 0;
     $len = strlen($l);
     $ins = array();
     for ($ii = 0; $ii < $len; ++$ii) {
       if ($l[$ii] == '&') {
         do {
           ++$ii;
         } while ($l[$ii] != ';');
         ++$c;
       } else if ($l[$ii] == '<') {
         do {
           ++$ii;
         } while ($l[$ii] != '>');
       } else {
         ++$c;
       }
       if ($c == 80) {
         $ins[] = ($ii + 1);
         $c = 0;
       }
     }
     while (($pos = array_pop($ins))) {
       $l = substr_replace(
         $l,
         "<span class=\"over-the-line\">\xE2\xAC\x85</span><br />",
         $pos,
         0);
     }
     return $l;
   }
 
 
   protected function tokenHighlight(&$render) {
     foreach ($render as $key => $text) {
       $render[$key] = str_replace(
       "\0",
       '<span class="uu">'."\xEF\xBF\xBD".'</span>',
       $text);
     }
   }
 
   protected function sourceHighlight($data, $corpus) {
     $result = $this->highlightEngine->highlightSource(
       $this->filetype,
       $corpus);
 
     $result_lines = explode("\n", $result);
     foreach ($data as $key => $info) {
       if (!$info) {
         unset($result_lines[$key]);
       }
     }
     return $result_lines;
   }
 
   private function tryCacheStuff() {
     $whitespace_mode = $this->whitespaceMode;
     switch ($whitespace_mode) {
       case self::WHITESPACE_SHOW_ALL:
       case self::WHITESPACE_IGNORE_TRAILING:
         break;
       default:
         $whitespace_mode = self::WHITESPACE_IGNORE_ALL;
         break;
     }
 
     $skip_cache = ($whitespace_mode != self::WHITESPACE_IGNORE_ALL);
     $this->whitespaceMode = $whitespace_mode;
 
     $changeset = $this->changeset;
 
     if ($changeset->getFileType() == DifferentialChangeType::FILE_TEXT ||
         $changeset->getFileType() == DifferentialChangeType::FILE_SYMLINK) {
       if ($skip_cache || !$this->loadCache()) {
         if ($this->whitespaceMode == self::WHITESPACE_IGNORE_ALL) {
 
           // Huge mess. Generate a "-bw" (ignore all whitespace changes) diff,
           // parse it out, and then play a shell game with the parsed format
           // in process() so we highlight only changed lines but render
           // whitespace differences. If we don't do this, we either fail to
           // render whitespace changes (which is incredibly confusing,
           // especially for python) or often produce a much larger set of
           // differences than necessary.
 
           $old_tmp = new TempFile();
           $new_tmp = new TempFile();
           Filesystem::writeFile($old_tmp, $changeset->makeOldFile());
           Filesystem::writeFile($new_tmp, $changeset->makeNewFile());
           list($err, $diff) = exec_manual(
             'diff -bw -U65535 %s %s',
             $old_tmp,
             $new_tmp);
 
           if (!strlen($diff)) {
             // If there's no diff text, that means the files are identical
             // except for whitespace changes. Build a synthetic, changeless
             // diff. TODO: this is incredibly hacky.
             $entire_file = explode("\n", $changeset->makeOldFile());
             foreach ($entire_file as $k => $line) {
               $entire_file[$k] = ' '.$line;
             }
             $len = count($entire_file);
             $entire_file = implode("\n", $entire_file);
 
             $diff = <<<EOSYNTHETIC
 --- ignored 9999-99-99
 +++ ignored 9999-99-99
 @@ -{$len},{$len} +{$len},{$len} @@
 {$entire_file}
 EOSYNTHETIC;
           }
 
           $changes = id(new ArcanistDiffParser())->parseDiff($diff);
 
           $diff = DifferentialDiff::newFromRawChanges($changes);
           $changesets = $diff->getChangesets();
           $alt_changeset = reset($changesets);
 
           $this->subparser = new DifferentialChangesetParser();
           $this->subparser->setChangeset($alt_changeset);
           $this->subparser->setWhitespaceMode(self::WHITESPACE_IGNORE_TRAILING);
         }
         foreach ($changeset->getHunks() as $hunk) {
           $this->parseHunk($hunk);
         }
         $this->process();
         if (!$skip_cache) {
           $this->saveCache();
         }
       }
     }
   }
 
   public function render(
     $range_start  = null,
     $range_len    = null,
     $mask_force   = array()) {
 
     $this->highlightEngine = new PhutilDefaultSyntaxHighlighterEngine();
 
     $this->tryCacheStuff();
 
     $changeset_id = $this->changesetID;
 
     $feedback_mask = array();
 
     switch ($this->changeset->getFileType()) {
       case DifferentialChangeType::FILE_IMAGE:
         $old = null;
         $cur = null;
 
         $metadata = $this->changeset->getMetadata();
         $data = idx($metadata, 'attachment-data');
 
         $old_phid = idx($metadata, 'old:binary-phid');
         $new_phid = idx($metadata, 'new:binary-phid');
         if ($old_phid || $new_phid) {
           if ($old_phid) {
             $old_uri = PhabricatorFileURI::getViewURIForPHID($old_phid);
             $old = phutil_render_tag(
               'img',
               array(
                 'src' => $old_uri,
               ));
           }
           if ($new_phid) {
             $new_uri = PhabricatorFileURI::getViewURIForPHID($new_phid);
             $cur = phutil_render_tag(
               'img',
               array(
                 'src' => $new_uri,
               ));
           }
         }
 
         $output = $this->renderChangesetTable(
           $this->changeset,
           '<tr>'.
             '<th></th>'.
             '<td class="differential-old-image">'.
               '<div class="differential-image-stage">'.
                 $old.
               '</div>'.
             '</td>'.
             '<th></th>'.
             '<td class="differential-new-image">'.
               '<div class="differential-image-stage">'.
                 $cur.
               '</div>'.
             '</td>'.
           '</tr>');
 
         return $output;
       case DifferentialChangeType::FILE_DIRECTORY:
       case DifferentialChangeType::FILE_BINARY:
         $output = $this->renderChangesetTable($this->changeset, null);
         return $output;
     }
 
     $shield = null;
     if ($range_start === null && $range_len === null) {
       if ($this->isGenerated()) {
         $shield = $this->renderShield(
           "This file contains generated code, which does not normally need ".
           "to be reviewed.",
           true);
       } else if ($this->isUnchanged()) {
         if ($this->isWhitespaceOnly()) {
           $shield = $this->renderShield(
             "This file was changed only by adding or removing trailing ".
             "whitespace.",
             false);
         } else {
           $shield = $this->renderShield(
             "The contents of this file were not changed.",
             false);
         }
       } else if ($this->isDeleted()) {
         $shield = $this->renderShield(
           "This file was completely deleted.",
           true);
       } else if ($this->changeset->getAffectedLineCount() > 2500) {
         $lines = number_format($this->changeset->getAffectedLineCount());
         $shield = $this->renderShield(
           "This file has a very large number of changes ({$lines} lines).",
           true);
       } else if (preg_match('/\.sql3$/', $this->changeset->getFilename())) {
         $shield = $this->renderShield(
           ".sql3 files are hidden by default.",
           true);
       }
     }
 
     if ($shield) {
       return $this->renderChangesetTable($this->changeset, $shield);
     }
 
     $old_comments = array();
     $new_comments = array();
 
     $old_mask = array();
     $new_mask = array();
     $feedback_mask = array();
 
     if ($this->comments) {
       foreach ($this->comments as $comment) {
         $start = max($comment->getLineNumber() - self::LINES_CONTEXT, 0);
         $end = $comment->getLineNumber() +
                $comment->getLineLength() +
                self::LINES_CONTEXT;
         $new = $this->isCommentInNewFile($comment);
         for ($ii = $start; $ii <= $end; $ii++) {
           if ($new) {
             $new_mask[$ii] = true;
           } else {
             $old_mask[$ii] = true;
           }
         }
       }
 
       foreach ($this->old as $ii => $old) {
         if (isset($old['line']) && isset($old_mask[$old['line']])) {
           $feedback_mask[$ii] = true;
         }
       }
 
       foreach ($this->new as $ii => $new) {
         if (isset($new['line']) && isset($new_mask[$new['line']])) {
           $feedback_mask[$ii] = true;
         }
       }
       $this->comments = msort($this->comments, 'getID');
       foreach ($this->comments as $comment) {
         $final = $comment->getLineNumber() +
                  $comment->getLineLength();
         if ($this->isCommentInNewFile($comment)) {
           $new_comments[$final][] = $comment;
         } else {
           $old_comments[$final][] = $comment;
         }
       }
     }
 
     $html = $this->renderTextChange(
       $range_start,
       $range_len,
       $mask_force,
       $feedback_mask,
       $old_comments,
       $new_comments);
 
     return $this->renderChangesetTable($this->changeset, $html);
   }
 
   private function isCommentInNewFile(DifferentialInlineComment $comment) {
     if ($this->oldChangesetID) {
       return ($comment->getChangesetID() != $this->oldChangesetID);
     } else {
       return $comment->getIsNewFile();
     }
   }
 
   protected function renderShield($message, $more) {
-    $end = $this->getLength();
-    $changeset_id = $this->getChangesetID();
 
     if ($more) {
+      $end = $this->getLength();
+      $reference = $this->getChangeset()->getRenderingReference();
       $more =
         ' '.
         javelin_render_tag(
           'a',
           array(
             'mustcapture' => true,
             'sigil'       => 'show-more',
             'class'       => 'complete',
             'href'        => '#',
             'meta'        => array(
-              'id'    => $changeset_id,
+              'id'    => $reference,
               'range' => "0-{$end}",
             ),
           ),
           'Show File Contents');
     } else {
       $more = null;
     }
 
     return javelin_render_tag(
       'tr',
       array(
         'sigil' => 'context-target',
       ),
       '<td class="differential-shield" colspan="4">'.
         phutil_escape_html($message).
         $more.
       '</td>');
   }
 
   protected function renderTextChange(
     $range_start,
     $range_len,
     $mask_force,
     $feedback_mask,
     array $old_comments,
     array $new_comments) {
 
     $context_not_available = null;
     if ($this->missingOld || $this->missingNew) {
       $context_not_available = javelin_render_tag(
         'tr',
         array(
           'sigil' => 'context-target',
         ),
         '<td colspan="4" class="show-more">'.
             'Context not available.'.
         '</td>');
       $context_not_available = $context_not_available;
     }
 
     $html = array();
 
     $rows = max(
       count($this->old),
       count($this->new));
 
     if ($range_start === null) {
       $range_start = 0;
     }
 
     if ($range_len === null) {
       $range_len = $rows;
     }
 
     $range_len = min($range_len, $rows - $range_start);
 
     $gaps = array();
     $gap_start = 0;
     $in_gap = false;
     $mask = $this->visible + $mask_force + $feedback_mask;
     $mask[$range_start + $range_len] = true;
     for ($ii = $range_start; $ii <= $range_start + $range_len; $ii++) {
       if (isset($mask[$ii])) {
         if ($in_gap) {
           $gap_length = $ii - $gap_start;
           if ($gap_length <= self::LINES_CONTEXT) {
             for ($jj = $gap_start; $jj <= $gap_start + $gap_length; $jj++) {
               $mask[$jj] = true;
             }
           } else {
             $gaps[] = array($gap_start, $gap_length);
           }
           $in_gap = false;
         }
       } else {
         if (!$in_gap) {
           $gap_start = $ii;
           $in_gap = true;
         }
       }
     }
 
     $gaps = array_reverse($gaps);
 
     $changeset = $this->changesetID;
+    $reference = $this->getChangeset()->getRenderingReference();
 
     for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) {
       if (empty($mask[$ii])) {
         $gap = array_pop($gaps);
         $top = $gap[0];
         $len = $gap[1];
 
         $end   = $top + $len - 20;
 
         $contents = array();
 
         if ($len > 40) {
           $contents[] = javelin_render_tag(
             'a',
             array(
               'href' => '#',
               'mustcapture' => true,
               'sigil'       => 'show-more',
               'meta'        => array(
-                'id'    => $changeset,
+                'id'    => $reference,
                 'range' => "{$top}-{$len}/{$top}-20",
               ),
             ),
             "\xE2\x96\xB2 Show 20 Lines");
         }
 
         $contents[] = javelin_render_tag(
           'a',
           array(
             'href' => '#',
             'mustcapture' => true,
             'sigil'       => 'show-more',
             'meta'        => array(
-              'id'    => $changeset,
+              'id'    => $reference,
               'range' => "{$top}-{$len}/{$top}-{$len}",
             ),
           ),
           'Show All '.$len.' Lines');
 
         if ($len > 40) {
           $contents[] = javelin_render_tag(
             'a',
             array(
               'href' => '#',
               'mustcapture' => true,
               'sigil'       => 'show-more',
               'meta'        => array(
-                'id'    => $changeset,
+                'id'    => $reference,
                 'range' => "{$top}-{$len}/{$end}-20",
               ),
             ),
             "\xE2\x96\xBC Show 20 Lines");
         };
 
         $container = javelin_render_tag(
           'tr',
           array(
             'sigil' => 'context-target',
           ),
           '<td colspan="4" class="show-more">'.
             implode(' &bull; ', $contents).
           '</td>');
 
         $html[] = $container;
 
         $ii += ($len - 1);
         continue;
       }
 
       if (isset($this->old[$ii])) {
         $o_num  = $this->old[$ii]['line'];
         $o_text = isset($this->oldRender[$ii]) ? $this->oldRender[$ii] : null;
         $o_attr = null;
         if ($this->old[$ii]['type']) {
           if (empty($this->new[$ii])) {
             $o_attr = ' class="old old-full"';
           } else {
             $o_attr = ' class="old"';
           }
         }
       } else {
         $o_num  = null;
         $o_text = null;
         $o_attr = null;
       }
 
       if (isset($this->new[$ii])) {
         $n_num  = $this->new[$ii]['line'];
         $n_text = isset($this->newRender[$ii]) ? $this->newRender[$ii] : null;
         $n_attr = null;
         if ($this->new[$ii]['type']) {
           if (empty($this->old[$ii])) {
             $n_attr = ' class="new new-full"';
           } else {
             $n_attr = ' class="new"';
           }
         }
       } else {
         $n_num   = null;
         $n_text  = null;
         $n_attr  = null;
       }
 
 
       if (($o_num && !empty($this->missingOld[$o_num])) ||
           ($n_num && !empty($this->missingNew[$n_num]))) {
         $html[] = $context_not_available;
       }
 
-      if ($o_num) {
+      if ($o_num && $changeset) {
         $o_id = ' id="C'.$changeset.'OL'.$o_num.'"';
       } else {
         $o_id = null;
       }
 
-      if ($n_num) {
+      if ($n_num && $changeset) {
         $n_id = ' id="C'.$changeset.'NL'.$n_num.'"';
       } else {
         $n_id = null;
       }
 
       // NOTE: The Javascript is sensitive to whitespace changes in this
       // block!
       $html[] =
         '<tr>'.
           '<th'.$o_id.'>'.$o_num.'</th>'.
           '<td'.$o_attr.'>'.$o_text.'</td>'.
           '<th'.$n_id.'>'.$n_num.'</th>'.
           '<td'.$n_attr.'>'.$n_text.'</td>'.
         '</tr>';
 
       if ($context_not_available && ($ii == $rows - 1)) {
         $html[] = $context_not_available;
       }
 
       if ($o_num && isset($old_comments[$o_num])) {
         foreach ($old_comments[$o_num] as $comment) {
           $xhp = $this->renderInlineComment($comment);
           $html[] =
             '<tr class="inline"><th /><td>'.
               $xhp.
             '</td><th /><td /></tr>';
         }
       }
       if ($n_num && isset($new_comments[$n_num])) {
         foreach ($new_comments[$n_num] as $comment) {
           $xhp = $this->renderInlineComment($comment);
           $html[] =
             '<tr class="inline"><th /><td /><th /><td>'.
               $xhp.
             '</td></tr>';
         }
       }
     }
 
     return implode('', $html);
   }
 
   private function renderInlineComment(DifferentialInlineComment $comment) {
 
     $user = $this->user;
     $edit = $user &&
             ($comment->getAuthorPHID() == $user->getPHID()) &&
             (!$comment->getCommentID());
 
     $on_right = $this->isCommentInNewFile($comment);
 
     return id(new DifferentialInlineCommentView())
       ->setInlineComment($comment)
       ->setOnRight($on_right)
       ->setHandles($this->handles)
       ->setMarkupEngine($this->markupEngine)
       ->setEditable($edit)
       ->render();
   }
 
   protected function renderPropertyChangeHeader($changeset) {
     $old = $changeset->getOldProperties();
     $new = $changeset->getNewProperties();
 
     if ($old === $new) {
       return null;
     }
 
     if ($changeset->getChangeType() == DifferentialChangeType::TYPE_ADD &&
         $new == array('unix:filemode' => '100644')) {
       return null;
     }
 
     if ($changeset->getChangeType() == DifferentialChangeType::TYPE_DELETE &&
         $old == array('unix:filemode' => '100644')) {
       return null;
     }
 
     return null;
 /*
   TODO
 
     $table = <table class="differential-property-table" />;
     $table->appendChild(
       <tr class="property-table-header">
         <th>Property Changes</th>
         <td class="oval">Old Value</td>
         <td class="nval">New Value</td>
       </tr>);
 
     $keys = array_keys($old + $new);
     sort($keys);
     foreach ($keys as $key) {
       $oval = idx($old, $key);
       $nval = idx($new, $key);
       if ($oval !== $nval) {
         if ($oval === null) {
           $oval = <em>null</em>;
         }
         if ($nval === null) {
           $nval = <em>null</em>;
         }
         $table->appendChild(
           <tr>
             <th>{$key}</th>
             <td class="oval">{$oval}</td>
             <td class="nval">{$nval}</td>
           </tr>);
       }
     }
 
     return $table;
 */
   }
 
   protected function renderChangesetTable($changeset, $contents) {
     $props  = $this->renderPropertyChangeHeader($this->changeset);
     $table = null;
     if ($contents) {
       $table =
         '<table class="differential-diff remarkup-code">'.
           $contents.
         '</table>';
     }
 
     if (!$table && !$props) {
       $notice = $this->renderChangeTypeHeader($this->changeset, true);
     } else {
       $notice = $this->renderChangeTypeHeader($this->changeset, false);
     }
 
     return implode(
       "\n",
       array(
         $notice,
         $props,
         $table,
       ));
   }
 
   protected function renderChangeTypeHeader($changeset, $force) {
 
     static $articles = array(
       DifferentialChangeType::FILE_IMAGE      => 'an',
     );
 
     static $files = array(
       DifferentialChangeType::FILE_TEXT       => 'file',
       DifferentialChangeType::FILE_IMAGE      => 'image',
       DifferentialChangeType::FILE_DIRECTORY  => 'directory',
       DifferentialChangeType::FILE_BINARY     => 'binary file',
       DifferentialChangeType::FILE_SYMLINK    => 'symlink',
     );
 
     static $changes = array(
       DifferentialChangeType::TYPE_ADD        => 'added',
       DifferentialChangeType::TYPE_CHANGE     => 'changed',
       DifferentialChangeType::TYPE_DELETE     => 'deleted',
       DifferentialChangeType::TYPE_MOVE_HERE  => 'moved from',
       DifferentialChangeType::TYPE_COPY_HERE  => 'copied from',
       DifferentialChangeType::TYPE_MOVE_AWAY  => 'moved to',
       DifferentialChangeType::TYPE_COPY_AWAY  => 'copied to',
       DifferentialChangeType::TYPE_MULTICOPY
         => 'deleted after being copied to',
     );
 
     $change = $changeset->getChangeType();
     $file = $changeset->getFileType();
 
     $message = null;
     if ($change == DifferentialChangeType::TYPE_CHANGE &&
         $file   == DifferentialChangeType::FILE_TEXT) {
       if ($force) {
         // We have to force something to render because there were no changes
         // of other kinds.
         $message = "This {$files[$file]} was not modified.";
       } else {
         // Default case of changes to a text file, no metadata.
         return null;
       }
     } else {
       $verb = idx($changes, $change, 'changed');
       switch ($change) {
         default:
           $message = "This {$files[$file]} was <strong>{$verb}</strong>.";
           break;
         case DifferentialChangeType::TYPE_MOVE_HERE:
         case DifferentialChangeType::TYPE_COPY_HERE:
           $message =
             "This {$files[$file]} was {$verb} ".
             "<strong>{$changeset->getOldFile()}</strong>.";
           break;
         case DifferentialChangeType::TYPE_MOVE_AWAY:
         case DifferentialChangeType::TYPE_COPY_AWAY:
         case DifferentialChangeType::TYPE_MULTICOPY:
           $paths = $changeset->getAwayPaths();
           if (count($paths) > 1) {
             $message =
               "This {$files[$file]} was {$verb}: ".
               "<strong>".implode(', ', $paths)."</strong>.";
           } else {
             $message =
               "This {$files[$file]} was {$verb} ".
               "<strong>".reset($paths)."</strong>.";
           }
           break;
         case DifferentialChangeType::TYPE_CHANGE:
           $message = "This is ".idx($articles, $file, 'a')." {$files[$file]}.";
           break;
       }
     }
 
     return
       '<div class="differential-meta-notice">'.
         $message.
       '</div>';
   }
 
   public function renderForEmail() {
     $ret = '';
 
     $min = min(count($this->old), count($this->new));
     for ($i = 0; $i < $min; $i++) {
       $o = $this->old[$i];
       $n = $this->new[$i];
 
       if (!isset($this->visible[$i])) {
         continue;
       }
 
       if ($o['line'] && $n['line']) {
         // It is quite possible there are better ways to achieve this. For
         // example, "white-space: pre;" can do a better job, WERE IT NOT for
         // broken email clients like OWA which use newlines to do weird
         // wrapping. So dont give them newlines.
         if (isset($this->intra[$i])) {
           $ret .= sprintf(
             "<font color=\"red\">-&nbsp;%s</font><br/>",
             str_replace(" ", "&nbsp;", phutil_escape_html($o['text']))
           );
           $ret .= sprintf(
             "<font color=\"green\">+&nbsp;%s</font><br/>",
             str_replace(" ", "&nbsp;", phutil_escape_html($n['text']))
           );
         } else {
           $ret .= sprintf("&nbsp;&nbsp;%s<br/>",
             str_replace(" ", "&nbsp;", phutil_escape_html($n['text']))
           );
         }
       } else if ($o['line'] && !$n['line']) {
         $ret .= sprintf(
           "<font color=\"red\">-&nbsp;%s</font><br/>",
           str_replace(" ", "&nbsp;", phutil_escape_html($o['text']))
         );
       } else {
         $ret .= sprintf(
           "<font color=\"green\">+&nbsp;%s</font><br/>",
           str_replace(" ", "&nbsp;", phutil_escape_html($n['text']))
         );
       }
     }
 
     return $ret;
   }
 
 }
diff --git a/src/applications/differential/storage/changeset/DifferentialChangeset.php b/src/applications/differential/storage/changeset/DifferentialChangeset.php
index 9fde61affb..14c2bb8e8c 100644
--- a/src/applications/differential/storage/changeset/DifferentialChangeset.php
+++ b/src/applications/differential/storage/changeset/DifferentialChangeset.php
@@ -1,147 +1,160 @@
 <?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 DifferentialChangeset extends DifferentialDAO {
 
   protected $diffID;
   protected $oldFile;
   protected $fileName;
   protected $awayPaths;
   protected $changeType;
   protected $fileType;
   protected $metadata;
   protected $oldProperties;
   protected $newProperties;
   protected $addLines;
   protected $delLines;
 
   private $unsavedHunks = array();
   private $hunks;
+  private $renderingReference;
 
   protected function getConfiguration() {
     return array(
       self::CONFIG_SERIALIZATION => array(
         'metadata'      => self::SERIALIZATION_JSON,
         'oldProperties' => self::SERIALIZATION_JSON,
         'newProperties' => self::SERIALIZATION_JSON,
         'awayPaths'     => self::SERIALIZATION_JSON,
       )) + parent::getConfiguration();
   }
 
   public function getAffectedLineCount() {
     return $this->getAddLines() + $this->getDelLines();
   }
 
   public function getFileType() {
     return $this->fileType;
   }
 
   public function getChangeType() {
     return $this->changeType;
   }
 
   public function attachHunks(array $hunks) {
     $this->hunks = $hunks;
     return $this;
   }
 
   public function getHunks() {
     if ($this->hunks === null) {
       throw new Exception("Must load and attach hunks first!");
     }
     return $this->hunks;
   }
 
   public function getDisplayFilename() {
     $name = $this->getFilename();
     if ($this->getFileType() == DifferentialChangeType::FILE_DIRECTORY) {
       $name .= '/';
     }
     return $name;
   }
 
+  public function setRenderingReference($rendering_reference) {
+    $this->renderingReference = $rendering_reference;
+    return $this;
+  }
+
+  public function getRenderingReference() {
+    if ($this->renderingReference) {
+      return $this->renderingReference;
+    }
+    return $this->getID();
+  }
+
   public function addUnsavedHunk(DifferentialHunk $hunk) {
     if ($this->hunks === null) {
       $this->hunks = array();
     }
     $this->hunks[] = $hunk;
     $this->unsavedHunks[] = $hunk;
     return $this;
   }
 
   public function loadHunks() {
     if (!$this->getID()) {
       return array();
     }
     return id(new DifferentialHunk())->loadAllWhere(
       'changesetID = %d',
       $this->getID());
   }
 
   public function save() {
 // TODO: Sort out transactions
 //    $this->openTransaction();
       $ret = parent::save();
       foreach ($this->unsavedHunks as $hunk) {
         $hunk->setChangesetID($this->getID());
         $hunk->save();
       }
 //    $this->saveTransaction();
     return $ret;
   }
 
   public function delete() {
 //    $this->openTransaction();
       foreach ($this->loadHunks() as $hunk) {
         $hunk->delete();
       }
       $this->_hunks = array();
     $ret = parent::delete();
 //    $this->saveTransaction();
     return $ret;
   }
 
   public function getSortKey() {
     $sort_key = $this->getFilename();
     // Sort files with ".h" in them first, so headers (.h, .hpp) come before
     // implementations (.c, .cpp, .cs).
     $sort_key = str_replace('.h', '.!h', $sort_key);
     return $sort_key;
   }
 
   public function makeNewFile() {
     $file = array();
     foreach ($this->getHunks() as $hunk) {
       $file[] = $hunk->makeNewFile();
     }
     return implode("\n", $file);
   }
 
   public function makeOldFile() {
     $file = array();
     foreach ($this->getHunks() as $hunk) {
       $file[] = $hunk->makeOldFile();
     }
     return implode("\n", $file);
   }
 
   public function getAnchorName() {
     return substr(md5($this->getFilename()), 0, 8);
   }
 
 }
diff --git a/src/applications/differential/view/changesetlistview/DifferentialChangesetListView.php b/src/applications/differential/view/changesetlistview/DifferentialChangesetListView.php
index 66fa6dd472..1dc86e05d8 100644
--- a/src/applications/differential/view/changesetlistview/DifferentialChangesetListView.php
+++ b/src/applications/differential/view/changesetlistview/DifferentialChangesetListView.php
@@ -1,134 +1,136 @@
 <?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 DifferentialChangesetListView extends AphrontView {
 
   private $changesets = array();
   private $editable;
   private $revision;
   private $renderURI = '/differential/changeset/';
   private $vsMap = array();
 
   public function setChangesets($changesets) {
     $this->changesets = $changesets;
     return $this;
   }
 
   public function setEditable($editable) {
     $this->editable = $editable;
     return $this;
   }
 
   public function setRevision(DifferentialRevision $revision) {
     $this->revision = $revision;
     return $this;
   }
 
   public function setVsMap(array $vs_map) {
     $this->vsMap = $vs_map;
     return $this;
   }
 
   public function setRenderURI($render_uri) {
     $this->renderURI = $render_uri;
     return $this;
   }
 
   public function render() {
     require_celerity_resource('differential-changeset-view-css');
 
     $vs_map = $this->vsMap;
     $changesets = $this->changesets;
 
     $output = array();
     $mapping = array();
     foreach ($changesets as $key => $changeset) {
       $file = $changeset->getFilename();
       $class = 'differential-changeset';
       if (!$this->editable) {
         $class .= ' differential-changeset-noneditable';
       }
       $id = $changeset->getID();
       if ($id) {
         $vs_id = idx($vs_map, $id);
       } else {
         $vs_id = null;
       }
 
-      $detail_uri = new PhutilURI('/differential/changeset/');
+      $ref = $changeset->getRenderingReference();
+
+      $detail_uri = new PhutilURI($this->renderURI);
       $detail_uri->setQueryParams(
         array(
-          'id'          => $id,
+          'id'          => $ref,
           'vs'          => $vs_id,
           'whitespace'  => 'TODO',
         ));
 
       $detail_button = phutil_render_tag(
         'a',
         array(
           'style'   => 'float: right',
           'class'   => 'button small grey',
           'href'    => $detail_uri,
           'target'  => '_blank',
         ),
         'Standalone View');
 
       $uniq_id = celerity_generate_unique_node_id();
 
       $detail = new DifferentialChangesetDetailView();
       $detail->setChangeset($changeset);
       $detail->addButton($detail_button);
       $detail->appendChild(
         phutil_render_tag(
           'div',
           array(
             'id' => $uniq_id,
           ),
           '<div class="differential-loading">Loading...</div>'));
       $output[] = $detail->render();
 
       $mapping[$uniq_id] = array(
-        $changeset->getID(),
+        $ref,
         $vs_id);
     }
 
     $whitespace = null;
     Javelin::initBehavior('differential-populate', array(
       'registry'    => $mapping,
       'whitespace'  => $whitespace,
       'uri'         => $this->renderURI,
     ));
 
     Javelin::initBehavior('differential-show-more', array(
       'uri' => $this->renderURI,
     ));
 
     if ($this->editable) {
       $revision = $this->revision;
       Javelin::initBehavior('differential-edit-inline-comments', array(
         'uri' => '/differential/comment/inline/edit/'.$revision->getID().'/',
       ));
     }
 
     return
       '<div class="differential-review-stage">'.
         implode("\n", $output).
       '</div>';
   }
 
 }
diff --git a/src/applications/diffusion/controller/commit/DiffusionCommitController.php b/src/applications/diffusion/controller/commit/DiffusionCommitController.php
index 0c62de6e0a..0c7f4ecfc8 100644
--- a/src/applications/diffusion/controller/commit/DiffusionCommitController.php
+++ b/src/applications/diffusion/controller/commit/DiffusionCommitController.php
@@ -1,119 +1,146 @@
 <?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 DiffusionCommitController extends DiffusionController {
 
   public function processRequest() {
     $drequest = $this->getDiffusionRequest();
 
+    $callsign = $drequest->getRepository()->getCallsign();
+
     $content = array();
     $content[] = $this->buildCrumbs(array(
       'commit' => true,
     ));
 
     $detail_panel = new AphrontPanelView();
 
     $repository = $drequest->getRepository();
     $commit = $drequest->loadCommit();
 
     if (!$commit) {
       // TODO: Make more user-friendly.
       throw new Exception('This commit has not parsed yet.');
     }
 
     $commit_data = $drequest->loadCommitData();
 
     require_celerity_resource('diffusion-commit-view-css');
 
     $detail_panel->appendChild(
       '<div class="diffusion-commit-view">'.
         '<div class="diffusion-commit-dateline">'.
-          'r'.$repository->getCallsign().$commit->getCommitIdentifier().
+          'r'.$callsign.$commit->getCommitIdentifier().
           ' &middot; '.
           date('F jS, Y g:i A', $commit->getEpoch()).
         '</div>'.
         '<h1>Revision Detail</h1>'.
         '<div class="diffusion-commit-details">'.
           '<table class="diffusion-commit-properties">'.
             '<tr>'.
               '<th>Author:</th>'.
               '<td>'.phutil_escape_html($commit_data->getAuthorName()).'</td>'.
             '</tr>'.
           '</table>'.
           '<hr />'.
           '<div class="diffusion-commit-message">'.
             phutil_escape_html($commit_data->getCommitMessage()).
           '</div>'.
         '</div>'.
       '</div>');
 
     $content[] = $detail_panel;
 
     $change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
       $drequest);
     $changes = $change_query->loadChanges();
 
     $change_table = new DiffusionCommitChangeTableView();
     $change_table->setDiffusionRequest($drequest);
     $change_table->setPathChanges($changes);
 
     // TODO: Large number of modified files check.
 
     $count = number_format(count($changes));
 
     $bad_commit = null;
     if ($count == 0) {
       $bad_commit = queryfx_one(
         id(new PhabricatorRepository())->establishConnection('r'),
         'SELECT * FROM %T WHERE fullCommitName = %s',
         PhabricatorRepository::TABLE_BADCOMMIT,
-        'r'.$repository->getCallsign().$commit->getCommitIdentifier());
+        'r'.$callsign.$commit->getCommitIdentifier());
     }
 
     if ($bad_commit) {
       $error_panel = new AphrontErrorView();
       $error_panel->setWidth(AphrontErrorView::WIDTH_WIDE);
       $error_panel->setTitle('Bad Commit');
       $error_panel->appendChild(
         phutil_escape_html($bad_commit['description']));
 
       $content[] = $error_panel;
     } else {
       $change_panel = new AphrontPanelView();
       $change_panel->setHeader("Changes ({$count})");
       $change_panel->appendChild($change_table);
 
       $content[] = $change_panel;
 
-      $change_list =
-        '<div style="margin: 2em; color: #666; padding: 1em;
-          background: #eee;">'.
-          '(list of changes goes here)'.
-        '</div>';
+      if ($changes) {
+        $changesets = DiffusionPathChange::convertToDifferentialChangesets(
+          $changes);
+        foreach ($changesets as $changeset) {
+          $branch = $drequest->getBranchURIComponent(
+            $drequest->getBranch());
+          $filename = $changeset->getFilename();
+          $commit = $drequest->getCommit();
+          $reference = "{$branch}{$filename};{$commit}";
+          $changeset->setRenderingReference($reference);
+        }
+
+        $change_list = new DifferentialChangesetListView();
+        $change_list->setChangesets($changesets);
+        $change_list->setRenderURI('/diffusion/'.$callsign.'/diff/');
+
+        // TODO: This is pretty awkward, unify the CSS between Diffusion and
+        // Differential better.
+        require_celerity_resource('differential-core-view-css');
+        $change_list =
+          '<div class="differential-primary-pane">'.
+            $change_list->render().
+          '</div>';
+      } else {
+        $change_list =
+          '<div style="margin: 2em; color: #666; padding: 1em;
+            background: #eee;">'.
+            '(no changes blah blah)'.
+          '</div>';
+      }
 
       $content[] = $change_list;
     }
 
     return $this->buildStandardPageResponse(
       $content,
       array(
         'title' => 'Diffusion',
       ));
   }
 
 }
diff --git a/src/applications/diffusion/controller/commit/__init__.php b/src/applications/diffusion/controller/commit/__init__.php
index 6e629ab204..36ce108d3e 100644
--- a/src/applications/diffusion/controller/commit/__init__.php
+++ b/src/applications/diffusion/controller/commit/__init__.php
@@ -1,22 +1,24 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
+phutil_require_module('phabricator', 'applications/differential/view/changesetlistview');
 phutil_require_module('phabricator', 'applications/diffusion/controller/base');
+phutil_require_module('phabricator', 'applications/diffusion/data/pathchange');
 phutil_require_module('phabricator', 'applications/diffusion/query/pathchange/base');
 phutil_require_module('phabricator', 'applications/diffusion/view/commitchangetable');
 phutil_require_module('phabricator', 'applications/repository/storage/repository');
 phutil_require_module('phabricator', 'infrastructure/celerity/api');
 phutil_require_module('phabricator', 'storage/queryfx');
 phutil_require_module('phabricator', 'view/form/error');
 phutil_require_module('phabricator', 'view/layout/panel');
 
 phutil_require_module('phutil', 'markup');
 phutil_require_module('phutil', 'utils');
 
 
 phutil_require_source('DiffusionCommitController.php');
diff --git a/src/applications/diffusion/data/pathchange/DiffusionPathChange.php b/src/applications/diffusion/data/pathchange/DiffusionPathChange.php
index 00e7d5e080..65d93d520b 100644
--- a/src/applications/diffusion/data/pathchange/DiffusionPathChange.php
+++ b/src/applications/diffusion/data/pathchange/DiffusionPathChange.php
@@ -1,129 +1,157 @@
 <?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 DiffusionPathChange {
 
   private $path;
   private $commitIdentifier;
   private $commit;
   private $commitData;
 
   private $changeType;
   private $fileType;
   private $targetPath;
   private $targetCommitIdentifier;
   private $awayPaths = array();
 
   final public function setPath($path) {
     $this->path = $path;
     return $this;
   }
 
   final public function getPath() {
     return $this->path;
   }
 
   public function setChangeType($change_type) {
     $this->changeType = $change_type;
     return $this;
   }
 
   public function getChangeType() {
     return $this->changeType;
   }
 
   public function setFileType($file_type) {
     $this->fileType = $file_type;
     return $this;
   }
 
   public function getFileType() {
     return $this->fileType;
   }
 
   public function setTargetPath($target_path) {
     $this->targetPath = $target_path;
     return $this;
   }
 
   public function getTargetPath() {
     return $this->targetPath;
   }
 
   public function setAwayPaths(array $away_paths) {
     $this->awayPaths = $away_paths;
     return $this;
   }
 
   public function getAwayPaths() {
     return $this->awayPaths;
   }
 
   final public function setCommitIdentifier($commit) {
     $this->commitIdentifier = $commit;
     return $this;
   }
 
   final public function getCommitIdentifier() {
     return $this->commitIdentifier;
   }
 
   final public function setCommit($commit) {
     $this->commit = $commit;
     return $this;
   }
 
   final public function getCommit() {
     return $this->commit;
   }
 
   final public function setCommitData($commit_data) {
     $this->commitData = $commit_data;
     return $this;
   }
 
   final public function getCommitData() {
     return $this->commitData;
   }
 
 
   final public function getEpoch() {
     if ($this->getCommit()) {
       return $this->getCommit()->getEpoch();
     }
     return null;
   }
 
   final public function getAuthorName() {
     if ($this->getCommitData()) {
       return $this->getCommitData()->getAuthorName();
     }
     return null;
   }
 
   final public function getSummary() {
     if (!$this->getCommitData()) {
       return null;
     }
     $message = $this->getCommitData()->getCommitMessage();
     $first = idx(explode("\n", $message), 0);
     return substr($first, 0, 80);
   }
 
+  final public static function convertToArcanistChanges(array $changes) {
+    $direct = array();
+    $result = array();
+    foreach ($changes as $path) {
+      $change = new ArcanistDiffChange();
+      $change->setCurrentPath($path->getPath());
+      $direct[] = $path->getPath();
+      $change->setType($path->getChangeType());
+      $file_type = $path->getFileType();
+      if ($file_type == DifferentialChangeType::FILE_NORMAL) {
+        $file_type = DifferentialChangeType::FILE_TEXT;
+      }
+      $change->setFileType($file_type);
+      $change->setOldPath($path->getTargetPath());
+      foreach ($path->getAwayPaths() as $away_path) {
+        $change->addAwayPath($away_path);
+      }
+      $result[$path->getPath()] = $change;
+    }
+
+    return array_select_keys($result, $direct);
+  }
+
+  final public static function convertToDifferentialChangesets(array $changes) {
+    $arcanist_changes = self::convertToArcanistChanges($changes);
+    $diff = DifferentialDiff::newFromRawChanges($arcanist_changes);
+    return $diff->getChangesets();
+  }
 
 }
diff --git a/src/applications/diffusion/data/pathchange/__init__.php b/src/applications/diffusion/data/pathchange/__init__.php
index 0256f567bf..46cfbb20fa 100644
--- a/src/applications/diffusion/data/pathchange/__init__.php
+++ b/src/applications/diffusion/data/pathchange/__init__.php
@@ -1,12 +1,17 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
+phutil_require_module('arcanist', 'parser/diff/change');
+
+phutil_require_module('phabricator', 'applications/differential/constants/changetype');
+phutil_require_module('phabricator', 'applications/differential/storage/diff');
+
 phutil_require_module('phutil', 'utils');
 
 
 phutil_require_source('DiffusionPathChange.php');
diff --git a/src/applications/diffusion/query/diff/svn/DiffusionSvnDiffQuery.php b/src/applications/diffusion/query/diff/svn/DiffusionSvnDiffQuery.php
index ad3d91e901..f2bc4390b2 100644
--- a/src/applications/diffusion/query/diff/svn/DiffusionSvnDiffQuery.php
+++ b/src/applications/diffusion/query/diff/svn/DiffusionSvnDiffQuery.php
@@ -1,137 +1,137 @@
 <?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 DiffusionSvnDiffQuery extends DiffusionDiffQuery {
 
   protected function executeQuery() {
     $drequest = $this->getRequest();
 
     $path_change_query = DiffusionPathChangeQuery::newFromDiffusionRequest(
       $drequest);
     $path_changes = $path_change_query->loadChanges();
 
     $path = null;
     foreach ($path_changes as $change) {
       if ($change->getPath() == $drequest->getPath()) {
         $path = $change;
       }
     }
 
     if (!$path) {
       return null;
     }
 
     $change_type = $path->getChangeType();
     switch ($change_type) {
       case DifferentialChangeType::TYPE_MULTICOPY:
       case DifferentialChangeType::TYPE_DELETE:
         if ($path->getTargetPath()) {
           $old = array(
             $path->getTargetPath(),
             $path->getTargetCommitIdentifier());
         } else {
           $old = array($path->getPath(), $path->getCommitIdentifier() - 1);
         }
         $old_name = $path->getPath();
         $new_name = '';
         $new = null;
         break;
       case DifferentialChangeType::TYPE_ADD:
         $old = null;
         $new = array($path->getPath(), $path->getCommitIdentifier());
         $old_name = '';
         $new_name = $path->getPath();
         break;
       case DifferentialChangeType::TYPE_MOVE_HERE:
       case DifferentialChangeType::TYPE_COPY_HERE:
         $old = array(
           $path->getTargetPath(),
           $path->getTargetCommitIdentifier());
         $new = array($path->getPath(), $path->getCommitIdentifier());
         $old_name = $path->getTargetPath();
         $new_name = $path->getPath();
         break;
       default:
         $old = array($path->getPath(), $path->getCommitIdentifier() - 1);
         $new = array($path->getPath(), $path->getCommitIdentifier());
         $old_name = $path->getPath();
         $new_name = $path->getPath();
         break;
     }
 
     $futures = array(
       'old' => $this->buildContentFuture($old),
       'new' => $this->buildContentFuture($new),
     );
     $futures = array_filter($futures);
 
     foreach (Futures($futures) as $key => $future) {
       $futures[$key] = $future->resolvex();
     }
 
     $old_data = idx($futures, 'old', '');
     $new_data = idx($futures, 'new', '');
 
     $old_tmp = new TempFile();
     $new_tmp = new TempFile();
 
     Filesystem::writeFile($old_tmp, $old_data);
     Filesystem::writeFile($new_tmp, $new_data);
 
     list($err, $raw_diff) = exec_manual(
       'diff -L %s -L %s -U65535 %s %s',
       nonempty($old_name, '/dev/universe').' 9999-99-99',
       nonempty($new_name, '/dev/universe').' 9999-99-99',
       $old_tmp,
       $new_tmp);
 
     $parser = new ArcanistDiffParser();
     $parser->setDetectBinaryFiles(true);
 
     $change = $parser->parseDiffusionPathChangesAndRawDiff(
       $drequest->getPath(),
       $path_changes,
       $raw_diff);
 
     $diff = DifferentialDiff::newFromRawChanges(array($change));
     $changesets = $diff->getChangesets();
     $changeset = reset($changesets);
 
-    $id = $drequest->getPath().';'.$drequest->getCommit();
-    $changeset->setID($id);
+    $reference = $drequest->getPath().';'.$drequest->getCommit();
+    $changeset->setRenderingReference($reference);
 
     return $changeset;
   }
 
   private function buildContentFuture($spec) {
     if (!$spec) {
       return null;
     }
 
     $drequest = $this->getRequest();
     $repository = $drequest->getRepository();
 
     list($ref, $rev) = $spec;
     return new ExecFuture(
       'svn --non-interactive cat %s%s@%d',
       $repository->getDetail('remote-uri'),
       $ref,
       $rev);
   }
 
 }