diff --git a/src/applications/diffusion/query/DiffusionCommitHintQuery.php b/src/applications/diffusion/query/DiffusionCommitHintQuery.php
index 28ae1ed70..bfd804513 100644
--- a/src/applications/diffusion/query/DiffusionCommitHintQuery.php
+++ b/src/applications/diffusion/query/DiffusionCommitHintQuery.php
@@ -1,64 +1,116 @@
 <?php
 
 final class DiffusionCommitHintQuery
   extends PhabricatorCursorPagedPolicyAwareQuery {
 
   private $ids;
   private $repositoryPHIDs;
   private $oldCommitIdentifiers;
 
+  private $commits;
+  private $commitMap;
+
   public function withIDs(array $ids) {
     $this->ids = $ids;
     return $this;
   }
 
   public function withRepositoryPHIDs(array $phids) {
     $this->repositoryPHIDs = $phids;
     return $this;
   }
 
   public function withOldCommitIdentifiers(array $identifiers) {
     $this->oldCommitIdentifiers = $identifiers;
     return $this;
   }
 
+  public function withCommits(array $commits) {
+    assert_instances_of($commits, 'PhabricatorRepositoryCommit');
+
+    $repository_phids = array();
+    foreach ($commits as $commit) {
+      $repository_phids[] = $commit->getRepository()->getPHID();
+    }
+
+    $this->repositoryPHIDs = $repository_phids;
+    $this->oldCommitIdentifiers = mpull($commits, 'getCommitIdentifier');
+    $this->commits = $commits;
+
+    return $this;
+  }
+
+  public function getCommitMap() {
+    if ($this->commitMap === null) {
+      throw new PhutilInvalidStateException('execute');
+    }
+
+    return $this->commitMap;
+  }
+
   public function newResultObject() {
     return new PhabricatorRepositoryCommitHint();
   }
 
+  protected function willExecute() {
+    $this->commitMap = array();
+  }
+
   protected function loadPage() {
     return $this->loadStandardPage($this->newResultObject());
   }
 
   protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
     $where = parent::buildWhereClauseParts($conn);
 
     if ($this->ids !== null) {
       $where[] = qsprintf(
         $conn,
         'id IN (%Ld)',
         $this->ids);
     }
 
     if ($this->repositoryPHIDs !== null) {
       $where[] = qsprintf(
         $conn,
         'repositoryPHID IN (%Ls)',
         $this->repositoryPHIDs);
     }
 
     if ($this->oldCommitIdentifiers !== null) {
       $where[] = qsprintf(
         $conn,
         'oldCommitIdentifier IN (%Ls)',
         $this->oldCommitIdentifiers);
     }
 
     return $where;
   }
 
+  protected function didFilterPage(array $hints) {
+    if ($this->commits) {
+      $map = array();
+      foreach ($this->commits as $commit) {
+        $repository_phid = $commit->getRepository()->getPHID();
+        $identifier = $commit->getCommitIdentifier();
+        $map[$repository_phid][$identifier] = $commit->getPHID();
+      }
+
+      foreach ($hints as $hint) {
+        $repository_phid = $hint->getRepositoryPHID();
+        $identifier = $hint->getOldCommitIdentifier();
+        if (isset($map[$repository_phid][$identifier])) {
+          $commit_phid = $map[$repository_phid][$identifier];
+          $this->commitMap[$commit_phid] = $hint;
+        }
+      }
+    }
+
+    return $hints;
+  }
+
   public function getQueryApplicationClass() {
     return 'PhabricatorDiffusionApplication';
   }
 
 }
diff --git a/src/applications/diffusion/remarkup/DiffusionCommitRemarkupRule.php b/src/applications/diffusion/remarkup/DiffusionCommitRemarkupRule.php
index 2722e9d81..4d2d130c3 100644
--- a/src/applications/diffusion/remarkup/DiffusionCommitRemarkupRule.php
+++ b/src/applications/diffusion/remarkup/DiffusionCommitRemarkupRule.php
@@ -1,28 +1,47 @@
 <?php
 
 final class DiffusionCommitRemarkupRule extends PhabricatorObjectRemarkupRule {
 
   protected function getObjectNamePrefix() {
     return '';
   }
 
   protected function getObjectNamePrefixBeginsWithWordCharacter() {
     return true;
   }
 
   protected function getObjectIDPattern() {
     return PhabricatorRepositoryCommitPHIDType::getCommitObjectNamePattern();
   }
 
+  protected function getObjectNameText(
+    $object,
+    PhabricatorObjectHandle $handle,
+    $id) {
+
+    // If this commit is unreachable, return the handle name instead of the
+    // normal text because it may be able to tell the user that the commit
+    // was rewritten and where to find the new one.
+
+    // By default, we try to preserve what the user actually typed as
+    // faithfully as possible, but if they're referencing a deleted commit
+    // it's more valuable to try to pick up any rewrite. See T11522.
+    if ($object->isUnreachable()) {
+      return $handle->getName();
+    }
+
+    return parent::getObjectNameText($object, $handle, $id);
+  }
+
   protected function loadObjects(array $ids) {
     $viewer = $this->getEngine()->getConfig('viewer');
 
     $query = id(new DiffusionCommitQuery())
       ->setViewer($viewer)
       ->withIdentifiers($ids);
 
     $query->execute();
     return $query->getIdentifierMap();
   }
 
 }
diff --git a/src/applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php b/src/applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php
index 5d3ce3c96..df84f2dcf 100644
--- a/src/applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php
+++ b/src/applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php
@@ -1,86 +1,117 @@
 <?php
 
 final class PhabricatorRepositoryCommitPHIDType extends PhabricatorPHIDType {
 
   const TYPECONST = 'CMIT';
 
   public function getTypeName() {
     return pht('Diffusion Commit');
   }
 
   public function newObject() {
     return new PhabricatorRepositoryCommit();
   }
 
   public function getPHIDTypeApplicationClass() {
     return 'PhabricatorDiffusionApplication';
   }
 
   protected function buildQueryForObjects(
     PhabricatorObjectQuery $query,
     array $phids) {
 
     return id(new DiffusionCommitQuery())
       ->withPHIDs($phids);
   }
 
   public function loadHandles(
     PhabricatorHandleQuery $query,
     array $handles,
     array $objects) {
 
+    $unreachable = array();
+    foreach ($handles as $phid => $handle) {
+      $commit = $objects[$phid];
+      if ($commit->isUnreachable()) {
+        $unreachable[$phid] = $commit;
+      }
+    }
+
+    if ($unreachable) {
+      $query = id(new DiffusionCommitHintQuery())
+        ->setViewer($query->getViewer())
+        ->withCommits($unreachable);
+
+      $query->execute();
+
+      $hints = $query->getCommitMap();
+    } else {
+      $hints = array();
+    }
+
     foreach ($handles as $phid => $handle) {
       $commit = $objects[$phid];
       $repository = $commit->getRepository();
       $commit_identifier = $commit->getCommitIdentifier();
 
       $name = $repository->formatCommitName($commit_identifier);
+
+      if ($commit->isUnreachable()) {
+        $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
+
+        // If we have a hint about this commit being rewritten, add the
+        // rewrite target to the handle name. This reduces the chance users
+        // will be caught offguard by the rewrite.
+        $hint = idx($hints, $phid);
+        if ($hint && $hint->isRewritten()) {
+          $new_name = $hint->getNewCommitIdentifier();
+          $new_name = $repository->formatCommitName($new_name);
+          $name = pht("%s \xE2\x99\xBB %s",  $name, $new_name);
+        }
+      }
+
       $summary = $commit->getSummary();
       if (strlen($summary)) {
         $full_name = $name.': '.$summary;
       } else {
         $full_name = $name;
       }
 
       $handle->setName($name);
       $handle->setFullName($full_name);
       $handle->setURI($commit->getURI());
       $handle->setTimestamp($commit->getEpoch());
-
-      if ($commit->isUnreachable()) {
-        $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
-      }
     }
   }
 
   public static function getCommitObjectNamePattern() {
     $min_unqualified = PhabricatorRepository::MINIMUM_UNQUALIFIED_HASH;
     $min_qualified   = PhabricatorRepository::MINIMUM_QUALIFIED_HASH;
 
     return
       '(?:r[A-Z]+:?|R[0-9]+:)[1-9]\d*'.
       '|'.
       '(?:r[A-Z]+:?|R[0-9]+:)[a-f0-9]{'.$min_qualified.',40}'.
       '|'.
       '[a-f0-9]{'.$min_unqualified.',40}';
   }
 
   public function canLoadNamedObject($name) {
     $pattern = self::getCommitObjectNamePattern();
     return preg_match('(^'.$pattern.'$)', $name);
   }
 
   public function loadNamedObjects(
     PhabricatorObjectQuery $query,
     array $names) {
 
     $query = id(new DiffusionCommitQuery())
       ->setViewer($query->getViewer())
       ->withIdentifiers($names);
 
     $query->execute();
 
     return $query->getIdentifierMap();
   }
 
 }
diff --git a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php
index d5addbc55..35c0ecfad 100644
--- a/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php
+++ b/src/infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php
@@ -1,383 +1,390 @@
 <?php
 
 abstract class PhabricatorObjectRemarkupRule extends PhutilRemarkupRule {
 
   const KEY_RULE_OBJECT = 'rule.object';
   const KEY_MENTIONED_OBJECTS = 'rule.object.mentioned';
 
   abstract protected function getObjectNamePrefix();
   abstract protected function loadObjects(array $ids);
 
   public function getPriority() {
     return 450.0;
   }
 
   protected function getObjectNamePrefixBeginsWithWordCharacter() {
     $prefix = $this->getObjectNamePrefix();
     return preg_match('/^\w/', $prefix);
   }
 
   protected function getObjectIDPattern() {
     return '[1-9]\d*';
   }
 
   protected function shouldMarkupObject(array $params) {
     return true;
   }
 
+  protected function getObjectNameText(
+    $object,
+    PhabricatorObjectHandle $handle,
+    $id) {
+    return $this->getObjectNamePrefix().$id;
+  }
+
   protected function loadHandles(array $objects) {
     $phids = mpull($objects, 'getPHID');
 
     $viewer = $this->getEngine()->getConfig('viewer');
     $handles = $viewer->loadHandles($phids);
     $handles = iterator_to_array($handles);
 
     $result = array();
     foreach ($objects as $id => $object) {
       $result[$id] = $handles[$object->getPHID()];
     }
     return $result;
   }
 
   protected function getObjectHref(
     $object,
     PhabricatorObjectHandle $handle,
     $id) {
 
     $uri = $handle->getURI();
 
     if ($this->getEngine()->getConfig('uri.full')) {
       $uri = PhabricatorEnv::getURI($uri);
     }
 
     return $uri;
   }
 
   protected function renderObjectRefForAnyMedia(
     $object,
     PhabricatorObjectHandle $handle,
     $anchor,
     $id) {
 
     $href = $this->getObjectHref($object, $handle, $id);
-    $text = $this->getObjectNamePrefix().$id;
+    $text = $this->getObjectNameText($object, $handle, $id);
 
     if ($anchor) {
       $href = $href.'#'.$anchor;
       $text = $text.'#'.$anchor;
     }
 
     if ($this->getEngine()->isTextMode()) {
       return PhabricatorEnv::getProductionURI($href);
     } else if ($this->getEngine()->isHTMLMailMode()) {
       $href = PhabricatorEnv::getProductionURI($href);
       return $this->renderObjectTagForMail($text, $href, $handle);
     }
 
     return $this->renderObjectRef($object, $handle, $anchor, $id);
 
   }
 
   protected function renderObjectRef(
     $object,
     PhabricatorObjectHandle $handle,
     $anchor,
     $id) {
 
     $href = $this->getObjectHref($object, $handle, $id);
-    $text = $this->getObjectNamePrefix().$id;
+    $text = $this->getObjectNameText($object, $handle, $id);
     $status_closed = PhabricatorObjectHandle::STATUS_CLOSED;
 
     if ($anchor) {
       $href = $href.'#'.$anchor;
       $text = $text.'#'.$anchor;
     }
 
     $attr = array(
       'phid'    => $handle->getPHID(),
       'closed'  => ($handle->getStatus() == $status_closed),
     );
 
     return $this->renderHovertag($text, $href, $attr);
   }
 
   protected function renderObjectEmbedForAnyMedia(
     $object,
     PhabricatorObjectHandle $handle,
     $options) {
 
     $name = $handle->getFullName();
     $href = $handle->getURI();
 
     if ($this->getEngine()->isTextMode()) {
       return $name.' <'.PhabricatorEnv::getProductionURI($href).'>';
     } else if ($this->getEngine()->isHTMLMailMode()) {
       $href = PhabricatorEnv::getProductionURI($href);
       return $this->renderObjectTagForMail($name, $href, $handle);
     }
 
     return $this->renderObjectEmbed($object, $handle, $options);
   }
 
   protected function renderObjectEmbed(
     $object,
     PhabricatorObjectHandle $handle,
     $options) {
 
     $name = $handle->getFullName();
     $href = $handle->getURI();
     $status_closed = PhabricatorObjectHandle::STATUS_CLOSED;
     $attr = array(
       'phid' => $handle->getPHID(),
       'closed'  => ($handle->getStatus() == $status_closed),
     );
 
     return $this->renderHovertag($name, $href, $attr);
   }
 
   protected function renderObjectTagForMail(
     $text,
     $href,
     PhabricatorObjectHandle $handle) {
 
     $status_closed = PhabricatorObjectHandle::STATUS_CLOSED;
     $strikethrough = $handle->getStatus() == $status_closed ?
       'text-decoration: line-through;' :
       'text-decoration: none;';
 
     return phutil_tag(
       'a',
       array(
         'href' => $href,
         'style' => 'background-color: #e7e7e7;
           border-color: #e7e7e7;
           border-radius: 3px;
           padding: 0 4px;
           font-weight: bold;
           color: black;'
           .$strikethrough,
       ),
       $text);
   }
 
   protected function renderHovertag($name, $href, array $attr = array()) {
     return id(new PHUITagView())
       ->setName($name)
       ->setHref($href)
       ->setType(PHUITagView::TYPE_OBJECT)
       ->setPHID(idx($attr, 'phid'))
       ->setClosed(idx($attr, 'closed'))
       ->render();
   }
 
   public function apply($text) {
     $text = preg_replace_callback(
       $this->getObjectEmbedPattern(),
       array($this, 'markupObjectEmbed'),
       $text);
 
     $text = preg_replace_callback(
       $this->getObjectReferencePattern(),
       array($this, 'markupObjectReference'),
       $text);
 
     return $text;
   }
 
   private function getObjectEmbedPattern() {
     $prefix = $this->getObjectNamePrefix();
     $prefix = preg_quote($prefix);
     $id = $this->getObjectIDPattern();
 
     return '(\B{'.$prefix.'('.$id.')([,\s](?:[^}\\\\]|\\\\.)*)?}\B)u';
   }
 
   private function getObjectReferencePattern() {
     $prefix = $this->getObjectNamePrefix();
     $prefix = preg_quote($prefix);
 
     $id = $this->getObjectIDPattern();
 
     // If the prefix starts with a word character (like "D"), we want to
     // require a word boundary so that we don't match "XD1" as "D1". If the
     // prefix does not start with a word character, we want to require no word
     // boundary for the same reasons. Test if the prefix starts with a word
     // character.
     if ($this->getObjectNamePrefixBeginsWithWordCharacter()) {
       $boundary = '\\b';
     } else {
       $boundary = '\\B';
     }
 
     // The "(?<![#@-])" prevents us from linking "#abcdef" or similar, and
     // "ABC-T1" (see T5714), and from matching "@T1" as a task (it is a user)
     // (see T9479).
 
     // The "\b" allows us to link "(abcdef)" or similar without linking things
     // in the middle of words.
 
     return '((?<![#@-])'.$boundary.$prefix.'('.$id.')(?:#([-\w\d]+))?(?!\w))u';
   }
 
 
   /**
    * Extract matched object references from a block of text.
    *
    * This is intended to make it easy to write unit tests for object remarkup
    * rules. Production code is not normally expected to call this method.
    *
    * @param   string  Text to match rules against.
    * @return  wild    Matches, suitable for writing unit tests against.
    */
   public function extractReferences($text) {
     $embed_matches = null;
     preg_match_all(
       $this->getObjectEmbedPattern(),
       $text,
       $embed_matches,
       PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
 
     $ref_matches = null;
     preg_match_all(
       $this->getObjectReferencePattern(),
       $text,
       $ref_matches,
       PREG_OFFSET_CAPTURE | PREG_SET_ORDER);
 
     $results = array();
     $sets = array(
       'embed' => $embed_matches,
       'ref' => $ref_matches,
     );
     foreach ($sets as $type => $matches) {
       $formatted = array();
       foreach ($matches as $match) {
         $format = array(
           'offset' => $match[1][1],
           'id' => $match[1][0],
         );
         if (isset($match[2][0])) {
           $format['tail'] = $match[2][0];
         }
         $formatted[] = $format;
       }
       $results[$type] = $formatted;
     }
 
     return $results;
   }
 
   public function markupObjectEmbed(array $matches) {
     if (!$this->isFlatText($matches[0])) {
       return $matches[0];
     }
 
     return $this->markupObject(array(
       'type' => 'embed',
       'id' => $matches[1],
       'options' => idx($matches, 2),
       'original' => $matches[0],
     ));
   }
 
   public function markupObjectReference(array $matches) {
     if (!$this->isFlatText($matches[0])) {
       return $matches[0];
     }
 
     return $this->markupObject(array(
       'type' => 'ref',
       'id' => $matches[1],
       'anchor' => idx($matches, 2),
       'original' => $matches[0],
     ));
   }
 
   private function markupObject(array $params) {
     if (!$this->shouldMarkupObject($params)) {
       return $params['original'];
     }
 
     $regex = trim(
       PhabricatorEnv::getEnvConfig('remarkup.ignored-object-names'));
     if ($regex && preg_match($regex, $params['original'])) {
       return $params['original'];
     }
 
     $engine = $this->getEngine();
     $token = $engine->storeText('x');
 
     $metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix();
     $metadata = $engine->getTextMetadata($metadata_key, array());
 
     $metadata[] = array(
       'token'   => $token,
     ) + $params;
 
     $engine->setTextMetadata($metadata_key, $metadata);
 
     return $token;
   }
 
   public function didMarkupText() {
     $engine = $this->getEngine();
     $metadata_key = self::KEY_RULE_OBJECT.'.'.$this->getObjectNamePrefix();
     $metadata = $engine->getTextMetadata($metadata_key, array());
 
     if (!$metadata) {
       return;
     }
 
 
     $ids = ipull($metadata, 'id');
     $objects = $this->loadObjects($ids);
 
     // For objects that are invalid or which the user can't see, just render
     // the original text.
 
     // TODO: We should probably distinguish between these cases and render a
     // "you can't see this" state for nonvisible objects.
 
     foreach ($metadata as $key => $spec) {
       if (empty($objects[$spec['id']])) {
         $engine->overwriteStoredText(
           $spec['token'],
           $spec['original']);
         unset($metadata[$key]);
       }
     }
 
     $phids = $engine->getTextMetadata(self::KEY_MENTIONED_OBJECTS, array());
     foreach ($objects as $object) {
       $phids[$object->getPHID()] = $object->getPHID();
     }
     $engine->setTextMetadata(self::KEY_MENTIONED_OBJECTS, $phids);
 
     $handles = $this->loadHandles($objects);
     foreach ($metadata as $key => $spec) {
       $handle = $handles[$spec['id']];
       $object = $objects[$spec['id']];
       switch ($spec['type']) {
         case 'ref':
 
           $view = $this->renderObjectRefForAnyMedia(
             $object,
             $handle,
             $spec['anchor'],
             $spec['id']);
           break;
         case 'embed':
           $spec['options'] = $this->assertFlatText($spec['options']);
           $view = $this->renderObjectEmbedForAnyMedia(
             $object,
             $handle,
             $spec['options']);
           break;
       }
       $engine->overwriteStoredText($spec['token'], $view);
     }
 
     $engine->setTextMetadata($metadata_key, array());
   }
 
 }