diff --git a/resources/sql/autopatches/20160824.repohint.01.hint.sql b/resources/sql/autopatches/20160824.repohint.01.hint.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20160824.repohint.01.hint.sql
@@ -0,0 +1,8 @@
+CREATE TABLE {$NAMESPACE}_repository.repository_commithint (
+  id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+  repositoryPHID VARBINARY(64) NOT NULL,
+  oldCommitIdentifier VARCHAR(40) NOT NULL COLLATE {$COLLATE_TEXT},
+  newCommitIdentifier VARCHAR(40) COLLATE {$COLLATE_TEXT},
+  hintType VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT},
+  UNIQUE KEY `key_old` (repositoryPHID, oldCommitIdentifier)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -612,6 +612,7 @@
     'DiffusionCommitHash' => 'applications/diffusion/data/DiffusionCommitHash.php',
     'DiffusionCommitHeraldField' => 'applications/diffusion/herald/DiffusionCommitHeraldField.php',
     'DiffusionCommitHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionCommitHeraldFieldGroup.php',
+    'DiffusionCommitHintQuery' => 'applications/diffusion/query/DiffusionCommitHintQuery.php',
     'DiffusionCommitHookEngine' => 'applications/diffusion/engine/DiffusionCommitHookEngine.php',
     'DiffusionCommitHookRejectException' => 'applications/diffusion/exception/DiffusionCommitHookRejectException.php',
     'DiffusionCommitMergeHeraldField' => 'applications/diffusion/herald/DiffusionCommitMergeHeraldField.php',
@@ -3384,6 +3385,7 @@
     'PhabricatorRepositoryCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/PhabricatorRepositoryCommitChangeParserWorker.php',
     'PhabricatorRepositoryCommitData' => 'applications/repository/storage/PhabricatorRepositoryCommitData.php',
     'PhabricatorRepositoryCommitHeraldWorker' => 'applications/repository/worker/PhabricatorRepositoryCommitHeraldWorker.php',
+    'PhabricatorRepositoryCommitHint' => 'applications/repository/storage/PhabricatorRepositoryCommitHint.php',
     'PhabricatorRepositoryCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryCommitMessageParserWorker.php',
     'PhabricatorRepositoryCommitOwnersWorker' => 'applications/repository/worker/PhabricatorRepositoryCommitOwnersWorker.php',
     'PhabricatorRepositoryCommitPHIDType' => 'applications/repository/phid/PhabricatorRepositoryCommitPHIDType.php',
@@ -3404,6 +3406,7 @@
     'PhabricatorRepositoryManagementCacheWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php',
     'PhabricatorRepositoryManagementClusterizeWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementClusterizeWorkflow.php',
     'PhabricatorRepositoryManagementDiscoverWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php',
+    'PhabricatorRepositoryManagementHintWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementHintWorkflow.php',
     'PhabricatorRepositoryManagementImportingWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementImportingWorkflow.php',
     'PhabricatorRepositoryManagementListPathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListPathsWorkflow.php',
     'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php',
@@ -5105,6 +5108,7 @@
     'DiffusionCommitHash' => 'Phobject',
     'DiffusionCommitHeraldField' => 'HeraldField',
     'DiffusionCommitHeraldFieldGroup' => 'HeraldFieldGroup',
+    'DiffusionCommitHintQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
     'DiffusionCommitHookEngine' => 'Phobject',
     'DiffusionCommitHookRejectException' => 'Exception',
     'DiffusionCommitMergeHeraldField' => 'DiffusionCommitHeraldField',
@@ -8350,6 +8354,10 @@
     'PhabricatorRepositoryCommitChangeParserWorker' => 'PhabricatorRepositoryCommitParserWorker',
     'PhabricatorRepositoryCommitData' => 'PhabricatorRepositoryDAO',
     'PhabricatorRepositoryCommitHeraldWorker' => 'PhabricatorRepositoryCommitParserWorker',
+    'PhabricatorRepositoryCommitHint' => array(
+      'PhabricatorRepositoryDAO',
+      'PhabricatorPolicyInterface',
+    ),
     'PhabricatorRepositoryCommitMessageParserWorker' => 'PhabricatorRepositoryCommitParserWorker',
     'PhabricatorRepositoryCommitOwnersWorker' => 'PhabricatorRepositoryCommitParserWorker',
     'PhabricatorRepositoryCommitPHIDType' => 'PhabricatorPHIDType',
@@ -8374,6 +8382,7 @@
     'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementClusterizeWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementDiscoverWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
+    'PhabricatorRepositoryManagementHintWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementImportingWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementListPathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
     'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
diff --git a/src/applications/diffusion/query/DiffusionCommitHintQuery.php b/src/applications/diffusion/query/DiffusionCommitHintQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/diffusion/query/DiffusionCommitHintQuery.php
@@ -0,0 +1,64 @@
+<?php
+
+final class DiffusionCommitHintQuery
+  extends PhabricatorCursorPagedPolicyAwareQuery {
+
+  private $ids;
+  private $repositoryPHIDs;
+  private $oldCommitIdentifiers;
+
+  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 newResultObject() {
+    return new PhabricatorRepositoryCommitHint();
+  }
+
+  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,
+        'reositoryPHID IN (%Ls)',
+        $this->repositoryPHIDs);
+    }
+
+    if ($this->oldCommitIdentifiers !== null) {
+      $where[] = qsprintf(
+        $conn,
+        'oldCommitIdentifier IN (%Ls)',
+        $this->oldCommitIdentifiers);
+    }
+
+    return $where;
+  }
+
+  public function getQueryApplicationClass() {
+    return 'PhabricatorDiffusionApplication';
+  }
+
+}
diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementHintWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementHintWorkflow.php
new file mode 100644
--- /dev/null
+++ b/src/applications/repository/management/PhabricatorRepositoryManagementHintWorkflow.php
@@ -0,0 +1,97 @@
+<?php
+
+final class PhabricatorRepositoryManagementHintWorkflow
+  extends PhabricatorRepositoryManagementWorkflow {
+
+  protected function didConstruct() {
+    $this
+      ->setName('hint')
+      ->setExamples('**hint** [options] ...')
+      ->setSynopsis(
+        pht(
+          'Write hints about unusual (rewritten or unreadable) commits.'))
+      ->setArguments(array());
+  }
+
+  public function execute(PhutilArgumentParser $args) {
+    $viewer = $this->getViewer();
+
+    echo tsprintf(
+      "%s\n",
+      pht('Reading list of hints from stdin...'));
+
+    $hints = file_get_contents('php://stdin');
+    if ($hints === false) {
+      throw new PhutilArgumentUsageException(pht('Failed to read stdin.'));
+    }
+
+    try {
+      $hints = phutil_json_decode($hints);
+    } catch (Exception $ex) {
+      throw new PhutilArgumentUsageException(
+        pht(
+          'Expected a list of hints in JSON format: %s',
+          $ex->getMessage()));
+    }
+
+    $repositories = array();
+    foreach ($hints as $idx => $hint) {
+      if (!is_array($hint)) {
+        throw new PhutilArgumentUsageException(
+          pht(
+            'Each item in the list of hints should be a JSON object, but '.
+            'the item at index "%s" is not.',
+            $idx));
+      }
+
+      try {
+        PhutilTypeSpec::checkMap(
+          $hint,
+          array(
+            'repository' => 'string|int',
+            'old' => 'string',
+            'new' => 'optional string|null',
+            'hint' => 'string',
+          ));
+      } catch (Exception $ex) {
+        throw new PhutilArgumentUsageException(
+          pht(
+            'Unexpected hint format at index "%s": %s',
+            $idx,
+            $ex->getMessage()));
+      }
+
+      $repository_identifier = $hint['repository'];
+      $repository = idx($repositories, $repository_identifier);
+      if (!$repository) {
+        $repository = id(new PhabricatorRepositoryQuery())
+          ->setViewer($viewer)
+          ->withIdentifiers(array($repository_identifier))
+          ->executeOne();
+        if (!$repository) {
+          throw new PhutilArgumentUsageException(
+            pht(
+              'Repository identifier "%s" (in hint at index "%s") does not '.
+              'identify a valid repository.',
+              $repository_identifier,
+              $idx));
+        }
+
+        $repositories[$repository_identifier] = $repository;
+      }
+
+      PhabricatorRepositoryCommitHint::updateHint(
+        $repository->getPHID(),
+        $hint['old'],
+        idx($hint, 'new'),
+        $hint['hint']);
+
+      echo tsprintf(
+        "%s\n",
+        pht(
+          'Updated hint for "%s".',
+          $hint['old']));
+    }
+  }
+
+}
diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommitHint.php b/src/applications/repository/storage/PhabricatorRepositoryCommitHint.php
new file mode 100644
--- /dev/null
+++ b/src/applications/repository/storage/PhabricatorRepositoryCommitHint.php
@@ -0,0 +1,128 @@
+<?php
+
+final class PhabricatorRepositoryCommitHint
+  extends PhabricatorRepositoryDAO
+  implements PhabricatorPolicyInterface {
+
+  protected $repositoryPHID;
+  protected $oldCommitIdentifier;
+  protected $newCommitIdentifier;
+  protected $hintType;
+
+  const HINT_NONE = 'none';
+  const HINT_REWRITTEN = 'rewritten';
+  const HINT_UNREADABLE = 'unreadable';
+
+  protected function getConfiguration() {
+    return array(
+      self::CONFIG_TIMESTAMPS => false,
+      self::CONFIG_COLUMN_SCHEMA => array(
+        'oldCommitIdentifier' => 'text40',
+        'newCommitIdentifier' => 'text40?',
+        'hintType' => 'text32',
+      ),
+      self::CONFIG_KEY_SCHEMA => array(
+        'key_old' => array(
+          'columns' => array('repositoryPHID', 'oldCommitIdentifier'),
+          'unique' => true,
+        ),
+      ),
+    ) + parent::getConfiguration();
+  }
+
+  public static function getAllHintTypes() {
+    return array(
+      self::HINT_NONE,
+      self::HINT_REWRITTEN,
+      self::HINT_UNREADABLE,
+    );
+  }
+
+  public static function updateHint($repository_phid, $old, $new, $type) {
+    switch ($type) {
+      case self::HINT_NONE:
+        break;
+      case self::HINT_REWRITTEN:
+        if (!$new) {
+          throw new Exception(
+            pht(
+              'When hinting a commit ("%s") as rewritten, you must provide '.
+              'the commit it was rewritten into.',
+              $old));
+        }
+        break;
+      case self::HINT_UNREADABLE:
+        if ($new) {
+          throw new Exception(
+            pht(
+              'When hinting a commit ("%s") as unreadable, you must not '.
+              'provide a new commit ("%s").',
+              $old,
+              $new));
+        }
+        break;
+      default:
+        $all_types = self::getAllHintTypes();
+        throw new Exception(
+          pht(
+            'Hint type ("%s") for commit ("%s") is not valid. Valid hints '.
+            'are: %s.',
+            $type,
+            $old,
+            implode(', ', $all_types)));
+    }
+
+    $table = new self();
+    $table_name = $table->getTableName();
+    $conn = $table->establishConnection('w');
+
+    if ($type == self::HINT_NONE) {
+      queryfx(
+        $conn,
+        'DELETE FROM %T WHERE repositoryPHID = %s AND oldCommitIdentifier = %s',
+        $table_name,
+        $repository_phid,
+        $old);
+    } else {
+      queryfx(
+        $conn,
+        'INSERT INTO %T
+          (repositoryPHID, oldCommitIdentifier, newCommitIdentifier, hintType)
+          VALUES (%s, %s, %ns, %s)
+          ON DUPLICATE KEY UPDATE
+            newCommitIdentifier = VALUES(newCommitIdentifier),
+            hintType = VALUES(hintType)',
+        $table_name,
+        $repository_phid,
+        $old,
+        $new,
+        $type);
+    }
+  }
+
+
+/* -(  PhabricatorPolicyInterface  )----------------------------------------- */
+
+
+  public function getCapabilities() {
+    return array(
+      PhabricatorPolicyCapability::CAN_VIEW,
+    );
+  }
+
+  public function getPolicy($capability) {
+    switch ($capability) {
+      case PhabricatorPolicyCapability::CAN_VIEW:
+        return PhabricatorPolicies::getMostOpenPolicy();
+    }
+  }
+
+  public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+    return false;
+  }
+
+  public function describeAutomaticCapability($capability) {
+    return null;
+  }
+
+}