diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php
index 089ecb6415..9b9bf41c2c 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php
@@ -1,286 +1,292 @@
 <?php
 
 final class DiffusionRepositoryController extends DiffusionController {
 
   public function processRequest() {
     $drequest = $this->diffusionRequest;
 
     $content = array();
 
     $crumbs = $this->buildCrumbs();
     $content[] = $crumbs;
 
     $content[] = $this->buildPropertiesTable($drequest->getRepository());
 
     $history_results = $this->callConduitWithDiffusionRequest(
       'diffusion.historyquery',
       array(
         'commit' => $drequest->getCommit(),
         'path' => $drequest->getPath(),
         'offset' => 0,
         'limit' => 15));
     $history = DiffusionPathChange::newFromConduit(
       $history_results['pathChanges']);
 
     $browse_results = DiffusionBrowseResultSet::newFromConduit(
       $this->callConduitWithDiffusionRequest(
         'diffusion.browsequery',
         array(
           'path' => $drequest->getPath(),
           'commit' => $drequest->getCommit(),
         )));
     $browse_paths = $browse_results->getPaths();
 
     $phids = array();
     foreach ($history as $item) {
       $data = $item->getCommitData();
       if ($data) {
         if ($data->getCommitDetail('authorPHID')) {
           $phids[$data->getCommitDetail('authorPHID')] = true;
         }
         if ($data->getCommitDetail('committerPHID')) {
           $phids[$data->getCommitDetail('committerPHID')] = true;
         }
       }
     }
 
     foreach ($browse_paths as $item) {
       $data = $item->getLastCommitData();
       if ($data) {
         if ($data->getCommitDetail('authorPHID')) {
           $phids[$data->getCommitDetail('authorPHID')] = true;
         }
         if ($data->getCommitDetail('committerPHID')) {
           $phids[$data->getCommitDetail('committerPHID')] = true;
         }
       }
     }
     $phids = array_keys($phids);
     $handles = $this->loadViewerHandles($phids);
 
     $readme = $this->callConduitWithDiffusionRequest(
       'diffusion.readmequery',
       array(
        'paths' => $browse_results->getPathDicts()
         ));
 
     $history_table = new DiffusionHistoryTableView();
     $history_table->setUser($this->getRequest()->getUser());
     $history_table->setDiffusionRequest($drequest);
     $history_table->setHandles($handles);
     $history_table->setHistory($history);
     $history_table->loadRevisions();
     $history_table->setParents($history_results['parents']);
     $history_table->setIsHead(true);
 
     $callsign = $drequest->getRepository()->getCallsign();
     $all = phutil_tag(
       'a',
       array(
         'href' => $drequest->generateURI(
           array(
             'action' => 'history',
           )),
       ),
       pht('View Full Commit History'));
 
     $panel = new AphrontPanelView();
     $panel->setHeader(pht("Recent Commits &middot; %s", $all));
     $panel->appendChild($history_table);
     $panel->setNoBackground();
 
     $content[] = $panel;
 
 
     $browse_table = new DiffusionBrowseTableView();
     $browse_table->setDiffusionRequest($drequest);
     $browse_table->setHandles($handles);
     $browse_table->setPaths($browse_paths);
     $browse_table->setUser($this->getRequest()->getUser());
 
     $browse_panel = new AphrontPanelView();
     $browse_panel->setHeader(phutil_tag(
       'a',
       array('href' => $drequest->generateURI(array('action' => 'browse'))),
       pht('Browse Repository')));
     $browse_panel->appendChild($browse_table);
     $browse_panel->setNoBackground();
 
     $content[] = $browse_panel;
 
     $content[] = $this->buildTagListTable($drequest);
 
     $content[] = $this->buildBranchListTable($drequest);
 
     if ($readme) {
       $box = new PHUIBoxView();
       $box->setShadow(true);
       $box->appendChild($readme);
       $box->addPadding(PHUI::PADDING_LARGE);
 
       $panel = new AphrontPanelView();
       $panel->setHeader(pht('README'));
       $panel->setNoBackground();
       $panel->appendChild($box);
       $content[] = $panel;
     }
 
     return $this->buildApplicationPage(
       $content,
       array(
         'title' => $drequest->getRepository()->getName(),
         'dust' => true,
         'device' => true,
       ));
   }
 
   private function buildPropertiesTable(PhabricatorRepository $repository) {
+    $user = $this->getRequest()->getUser();
 
     $header = id(new PhabricatorHeaderView())
       ->setHeader($repository->getName());
 
-    $view = new PhabricatorPropertyListView();
+    $view = id(new PhabricatorPropertyListView())
+      ->setUser($user);
     $view->addProperty(pht('Callsign'), $repository->getCallsign());
 
     switch ($repository->getVersionControlSystem()) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
       case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
         $view->addProperty(
           pht('Clone URI'),
           $repository->getPublicRemoteURI());
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
         $view->addProperty(
           pht('Repository Root'),
           $repository->getPublicRemoteURI());
         break;
     }
 
     $description = $repository->getDetail('description');
     if (strlen($description)) {
+      $description = PhabricatorMarkupEngine::renderOneObject(
+        $repository,
+        'description',
+        $user);
       $view->addTextContent($description);
     }
 
     return array($header, $view);
   }
 
   private function buildBranchListTable(DiffusionRequest $drequest) {
     if ($drequest->getBranch() !== null) {
       $limit = 15;
 
       $branches = DiffusionBranchInformation::newFromConduit(
         $this->callConduitWithDiffusionRequest(
           'diffusion.branchquery',
           array(
             'limit' => $limit
           )));
       if (!$branches) {
         return null;
       }
 
       $more_branches = (count($branches) > $limit);
       $branches = array_slice($branches, 0, $limit);
 
       $commits = id(new PhabricatorAuditCommitQuery())
         ->withIdentifiers(
           $drequest->getRepository()->getID(),
           mpull($branches, 'getHeadCommitIdentifier'))
         ->needCommitData(true)
         ->execute();
 
       $table = new DiffusionBranchTableView();
       $table->setDiffusionRequest($drequest);
       $table->setBranches($branches);
       $table->setCommits($commits);
       $table->setUser($this->getRequest()->getUser());
 
       $panel = new AphrontPanelView();
       $panel->setHeader(pht('Branches'));
       $panel->setNoBackground();
 
       if ($more_branches) {
         $panel->setCaption(pht('Showing %d branches.', $limit));
       }
 
       $panel->addButton(
         phutil_tag(
           'a',
           array(
             'href' => $drequest->generateURI(
               array(
                 'action' => 'branches',
               )),
             'class' => 'grey button',
           ),
           pht("Show All Branches \xC2\xBB")));
 
       $panel->appendChild($table);
 
       return $panel;
     }
 
     return null;
   }
 
   private function buildTagListTable(DiffusionRequest $drequest) {
     $tag_limit = 15;
     $tags = array();
     try {
       $tags = DiffusionRepositoryTag::newFromConduit(
         $this->callConduitWithDiffusionRequest(
           'diffusion.tagsquery',
           array('limit' => $tag_limit + 1)));
     } catch (ConduitException $e) {
       if ($e->getMessage() != 'ERR-UNSUPPORTED-VCS') {
         throw $e;
       }
     }
 
     if (!$tags) {
       return null;
     }
 
     $more_tags = (count($tags) > $tag_limit);
     $tags = array_slice($tags, 0, $tag_limit);
 
     $commits = id(new PhabricatorAuditCommitQuery())
       ->withIdentifiers(
         $drequest->getRepository()->getID(),
         mpull($tags, 'getCommitIdentifier'))
       ->needCommitData(true)
       ->execute();
 
     $view = new DiffusionTagListView();
     $view->setDiffusionRequest($drequest);
     $view->setTags($tags);
     $view->setUser($this->getRequest()->getUser());
     $view->setCommits($commits);
 
     $phids = $view->getRequiredHandlePHIDs();
     $handles = $this->loadViewerHandles($phids);
     $view->setHandles($handles);
 
     $panel = new AphrontPanelView();
     $panel->setHeader(pht('Tags'));
 
     if ($more_tags) {
       $panel->setCaption(pht('Showing the %d most recent tags.', $tag_limit));
     }
 
     $panel->addButton(
       phutil_tag(
         'a',
         array(
           'href' => $drequest->generateURI(
             array(
               'action' => 'tags',
             )),
           'class' => 'grey button',
         ),
         pht("Show All Tags \xC2\xBB")));
     $panel->appendChild($view);
 
     return $panel;
   }
 
 }
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php
index 9ff999911e..c5ac2e8449 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php
@@ -1,133 +1,133 @@
 <?php
 
 final class DiffusionRepositoryEditBasicController extends DiffusionController {
 
   public function processRequest() {
     $request = $this->getRequest();
     $user = $request->getUser();
     $drequest = $this->diffusionRequest;
     $repository = $drequest->getRepository();
 
     $repository = id(new PhabricatorRepositoryQuery())
       ->setViewer($user)
       ->requireCapabilities(
         array(
           PhabricatorPolicyCapability::CAN_VIEW,
           PhabricatorPolicyCapability::CAN_EDIT,
         ))
       ->withIDs(array($repository->getID()))
       ->executeOne();
 
     if (!$repository) {
       return new Aphront404Response();
     }
 
     $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
 
     $v_name = $repository->getName();
     $v_desc = $repository->getDetail('description');
     $e_name = true;
     $errors = array();
 
     if ($request->isFormPost()) {
       $v_name = $request->getStr('name');
       $v_desc = $request->getStr('description');
 
       if (!strlen($v_name)) {
         $e_name = pht('Required');
         $errors[] = pht('Repository name is required.');
       } else {
         $e_name = null;
       }
 
       if (!$errors) {
         $xactions = array();
         $template = id(new PhabricatorRepositoryTransaction());
 
         $type_name = PhabricatorRepositoryTransaction::TYPE_NAME;
         $type_desc = PhabricatorRepositoryTransaction::TYPE_DESCRIPTION;
 
         $xactions[] = id(clone $template)
           ->setTransactionType($type_name)
           ->setNewValue($v_name);
 
         $xactions[] = id(clone $template)
           ->setTransactionType($type_desc)
           ->setNewValue($v_desc);
 
         id(new PhabricatorRepositoryEditor())
           ->setContinueOnNoEffect(true)
           ->setContentSourceFromRequest($request)
           ->setActor($user)
           ->applyTransactions($repository, $xactions);
 
         return id(new AphrontRedirectResponse())->setURI($edit_uri);
       }
     }
 
     $content = array();
 
     $crumbs = $this->buildCrumbs();
     $crumbs->addCrumb(
       id(new PhabricatorCrumbView())
         ->setName(pht('Edit Basics')));
     $content[] = $crumbs;
 
     $title = pht('Edit %s', $repository->getName());
 
     if ($errors) {
       $content[] = id(new AphrontErrorView())
         ->setTitle(pht('Form Errors'))
         ->setErrors($errors);
     }
 
     $form = id(new AphrontFormView())
       ->setUser($user)
       ->setFlexible(true)
       ->appendChild(
         id(new AphrontFormTextControl())
           ->setName('name')
           ->setLabel(pht('Name'))
           ->setValue($v_name)
           ->setError($e_name))
       ->appendChild(
-        id(new AphrontFormTextAreaControl())
+        id(new PhabricatorRemarkupControl())
           ->setName('description')
           ->setLabel(pht('Description'))
           ->setValue($v_desc))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->setValue(pht('Save'))
           ->addCancelButton($edit_uri))
       ->appendChild(id(new PHUIFormDividerControl()))
       ->appendRemarkupInstructions($this->getReadmeInstructions());
 
     $content[] = $form;
 
     return $this->buildApplicationPage(
       $content,
       array(
         'title' => $title,
         'dust' => true,
         'device' => true,
       ));
   }
 
   private function getReadmeInstructions() {
     return pht(<<<EOTEXT
 You can also create a `README` file at the repository root (or in any
 subdirectory) to provide information about the repository. These formats are
 supported:
 
 | File Name       | Rendered As... |
 |-----------------|----------------|
 | `README`          | Plain Text |
 | `README.txt`      | Plain Text |
 | `README.remarkup` | Remarkup |
 | `README.md`       | Remarkup |
 | `README.rainbow`  | \xC2\xA1Fiesta! |
 EOTEXT
 );
   }
 
 }
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php
index 81d3344030..eead89d4d2 100644
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditController.php
@@ -1,112 +1,119 @@
 <?php
 
 final class DiffusionRepositoryEditController extends DiffusionController {
 
   public function processRequest() {
     $request = $this->getRequest();
     $user = $request->getUser();
     $drequest = $this->diffusionRequest;
     $repository = $drequest->getRepository();
 
     $content = array();
 
     $crumbs = $this->buildCrumbs();
     $crumbs->addCrumb(
       id(new PhabricatorCrumbView())
         ->setName(pht('Edit')));
     $content[] = $crumbs;
 
     $title = pht('Edit %s', $repository->getName());
 
     $content[] = id(new PhabricatorHeaderView())
       ->setHeader($title);
 
     $content[] = $this->buildBasicActions($repository);
     $content[] = $this->buildBasicProperties($repository);
 
 
     $content[] = id(new PhabricatorHeaderView())
       ->setHeader(pht('Edit History'));
 
     $xactions = id(new PhabricatorRepositoryTransactionQuery())
       ->setViewer($user)
       ->withObjectPHIDs(array($repository->getPHID()))
       ->execute();
 
     $engine = id(new PhabricatorMarkupEngine())
       ->setViewer($user);
     foreach ($xactions as $xaction) {
       if ($xaction->getComment()) {
         $engine->addObject(
           $xaction->getComment(),
           PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
       }
     }
     $engine->process();
 
     $xaction_view = id(new PhabricatorApplicationTransactionView())
       ->setUser($user)
       ->setTransactions($xactions)
       ->setMarkupEngine($engine);
 
     $content[] = $xaction_view;
 
 
     return $this->buildApplicationPage(
       $content,
       array(
         'title' => $title,
         'device' => true,
         'dust' => true,
       ));
   }
 
   private function buildBasicActions(PhabricatorRepository $repository) {
     $user = $this->getRequest()->getUser();
 
     $view = id(new PhabricatorActionListView())
       ->setUser($user);
 
     $can_edit = PhabricatorPolicyFilter::hasCapability(
       $user,
       $repository,
       PhabricatorPolicyCapability::CAN_EDIT);
 
     $edit = id(new PhabricatorActionView())
       ->setIcon('edit')
       ->setName(pht('Edit Basic Information'))
       ->setHref($this->getRepositoryControllerURI($repository, 'edit/basic/'))
       ->setDisabled(!$can_edit);
     $view->addAction($edit);
 
     return $view;
   }
 
   private function buildBasicProperties(PhabricatorRepository $repository) {
+    $user = $this->getRequest()->getUser();
+
     $view = id(new PhabricatorPropertyListView())
-      ->setUser($this->getRequest()->getUser())
+      ->setUser($user)
       ->setObject($repository);
 
     $view->addProperty(pht('Name'), $repository->getName());
     $view->addProperty(pht('ID'), $repository->getID());
     $view->addProperty(pht('PHID'), $repository->getPHID());
 
     $type = PhabricatorRepositoryType::getNameForRepositoryType(
       $repository->getVersionControlSystem());
 
     $view->addProperty(pht('Type'), $type);
     $view->addProperty(pht('Callsign'), $repository->getCallsign());
 
     $description = $repository->getDetail('description');
+    $view->addSectionHeader(pht('Description'));
     if (!strlen($description)) {
-      $description = phutil_tag('em', array(), pht('None'));
+      $description = phutil_tag('em', array(), pht('No description provided.'));
+    } else {
+      $description = PhabricatorMarkupEngine::renderOneObject(
+        $repository,
+        'description',
+        $user);
     }
-    $view->addProperty(pht('Description'), $description);
-
+    $view->addTextContent($description);
 
     return $view;
   }
 
 
 
 }
diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
index 51195b9518..eeb6f18a8e 100644
--- a/src/applications/repository/storage/PhabricatorRepository.php
+++ b/src/applications/repository/storage/PhabricatorRepository.php
@@ -1,745 +1,781 @@
 <?php
 
 /**
  * @task uri Repository URI Management
  */
 final class PhabricatorRepository extends PhabricatorRepositoryDAO
-  implements PhabricatorPolicyInterface {
+  implements
+    PhabricatorPolicyInterface,
+    PhabricatorMarkupInterface {
 
   /**
    * Shortest hash we'll recognize in raw "a829f32" form.
    */
   const MINIMUM_UNQUALIFIED_HASH = 7;
 
   /**
    * Shortest hash we'll recognize in qualified "rXab7ef2f8" form.
    */
   const MINIMUM_QUALIFIED_HASH = 5;
 
   const TABLE_PATH = 'repository_path';
   const TABLE_PATHCHANGE = 'repository_pathchange';
   const TABLE_FILESYSTEM = 'repository_filesystem';
   const TABLE_SUMMARY = 'repository_summary';
   const TABLE_BADCOMMIT = 'repository_badcommit';
   const TABLE_LINTMESSAGE = 'repository_lintmessage';
 
   protected $phid;
   protected $name;
   protected $callsign;
   protected $uuid;
 
   protected $versionControlSystem;
   protected $details = array();
 
   private $sshKeyfile;
 
   public function getConfiguration() {
     return array(
       self::CONFIG_AUX_PHID => true,
       self::CONFIG_SERIALIZATION => array(
         'details' => self::SERIALIZATION_JSON,
       ),
     ) + parent::getConfiguration();
   }
 
   public function generatePHID() {
     return PhabricatorPHID::generateNewPHID(
       PhabricatorPHIDConstants::PHID_TYPE_REPO);
   }
 
   public function toDictionary() {
     return array(
       'name'        => $this->getName(),
       'phid'        => $this->getPHID(),
       'callsign'    => $this->getCallsign(),
       'vcs'         => $this->getVersionControlSystem(),
       'uri'         => PhabricatorEnv::getProductionURI($this->getURI()),
       'remoteURI'   => (string)$this->getPublicRemoteURI(),
       'tracking'    => $this->getDetail('tracking-enabled'),
       'description' => $this->getDetail('description'),
     );
   }
 
   public function getDetail($key, $default = null) {
     return idx($this->details, $key, $default);
   }
 
   public function setDetail($key, $value) {
     $this->details[$key] = $value;
     return $this;
   }
 
   public function getDiffusionBrowseURIForPath(
     PhabricatorUser $user,
     $path,
     $line = null,
     $branch = null) {
 
     $drequest = DiffusionRequest::newFromDictionary(
       array(
         'user' => $user,
         'repository' => $this,
         'path' => $path,
         'branch' => $branch,
       ));
 
     return $drequest->generateURI(
       array(
         'action' => 'browse',
         'line'   => $line,
       ));
   }
 
   public function getLocalPath() {
     return $this->getDetail('local-path');
   }
 
   public function getSubversionBaseURI() {
     $vcs = $this->getVersionControlSystem();
     if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) {
       throw new Exception("Not a subversion repository!");
     }
 
     $uri = $this->getDetail('remote-uri');
     $subpath = $this->getDetail('svn-subpath');
 
     return $uri.$subpath;
   }
 
   public function execRemoteCommand($pattern /* , $arg, ... */) {
     $args = func_get_args();
     $args = $this->formatRemoteCommand($args);
     return call_user_func_array('exec_manual', $args);
   }
 
   public function execxRemoteCommand($pattern /* , $arg, ... */) {
     $args = func_get_args();
     $args = $this->formatRemoteCommand($args);
     return call_user_func_array('execx', $args);
   }
 
   public function getRemoteCommandFuture($pattern /* , $arg, ... */) {
     $args = func_get_args();
     $args = $this->formatRemoteCommand($args);
     return newv('ExecFuture', $args);
   }
 
   public function passthruRemoteCommand($pattern /* , $arg, ... */) {
     $args = func_get_args();
     $args = $this->formatRemoteCommand($args);
     return call_user_func_array('phutil_passthru', $args);
   }
 
   public function execLocalCommand($pattern /* , $arg, ... */) {
     $args = func_get_args();
     $args = $this->formatLocalCommand($args);
     return call_user_func_array('exec_manual', $args);
   }
 
   public function execxLocalCommand($pattern /* , $arg, ... */) {
     $args = func_get_args();
     $args = $this->formatLocalCommand($args);
     return call_user_func_array('execx', $args);
   }
 
   public function getLocalCommandFuture($pattern /* , $arg, ... */) {
     $args = func_get_args();
     $args = $this->formatLocalCommand($args);
     return newv('ExecFuture', $args);
   }
 
   public function passthruLocalCommand($pattern /* , $arg, ... */) {
     $args = func_get_args();
     $args = $this->formatLocalCommand($args);
     return call_user_func_array('phutil_passthru', $args);
   }
 
 
   private function formatRemoteCommand(array $args) {
     $pattern = $args[0];
     $args = array_slice($args, 1);
 
     $empty = $this->getEmptyReadableDirectoryPath();
 
     if ($this->shouldUseSSH()) {
       switch ($this->getVersionControlSystem()) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
           $pattern = "SVN_SSH=%s svn --non-interactive {$pattern}";
           array_unshift(
             $args,
             csprintf(
               'ssh -l %s -i %s',
               $this->getSSHLogin(),
               $this->getSSHKeyfile()));
           break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
           $command = call_user_func_array(
             'csprintf',
             array_merge(
               array(
                 "(ssh-add %s && HOME=%s git {$pattern})",
                 $this->getSSHKeyfile(),
                 $empty,
               ),
               $args));
           $pattern = "ssh-agent sh -c %s";
           $args = array($command);
           break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
           $pattern = "hg --config ui.ssh=%s {$pattern}";
           array_unshift(
             $args,
             csprintf(
               'ssh -l %s -i %s',
               $this->getSSHLogin(),
               $this->getSSHKeyfile()));
           break;
         default:
           throw new Exception("Unrecognized version control system.");
       }
     } else if ($this->shouldUseHTTP()) {
       switch ($this->getVersionControlSystem()) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
           $pattern =
             "svn ".
             "--non-interactive ".
             "--no-auth-cache ".
             "--trust-server-cert ".
             "--username %s ".
             "--password %s ".
             $pattern;
           array_unshift(
             $args,
             $this->getDetail('http-login'),
             $this->getDetail('http-pass'));
           break;
         default:
           throw new Exception(
             "No support for HTTP Basic Auth in this version control system.");
       }
     } else if ($this->shouldUseSVNProtocol()) {
       switch ($this->getVersionControlSystem()) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
             $pattern =
               "svn ".
               "--non-interactive ".
               "--no-auth-cache ".
               "--username %s ".
               "--password %s ".
               $pattern;
             array_unshift(
               $args,
               $this->getDetail('http-login'),
               $this->getDetail('http-pass'));
             break;
         default:
           throw new Exception(
             "SVN protocol is SVN only.");
       }
     } else {
       switch ($this->getVersionControlSystem()) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
           $pattern = "svn --non-interactive {$pattern}";
           break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
           $pattern = "HOME=%s git {$pattern}";
           array_unshift($args, $empty);
           break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
           $pattern = "hg {$pattern}";
           break;
         default:
           throw new Exception("Unrecognized version control system.");
       }
     }
 
     array_unshift($args, $pattern);
 
     return $args;
   }
 
   private function formatLocalCommand(array $args) {
     $pattern = $args[0];
     $args = array_slice($args, 1);
 
     $empty = $this->getEmptyReadableDirectoryPath();
 
     switch ($this->getVersionControlSystem()) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
         $pattern = "(cd %s && svn --non-interactive {$pattern})";
         array_unshift($args, $this->getLocalPath());
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         $pattern = "(cd %s && HOME=%s git {$pattern})";
         array_unshift($args, $this->getLocalPath(), $empty);
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
         $hgplain = (phutil_is_windows() ? "set HGPLAIN=1 &&" : "HGPLAIN=1");
         $pattern = "(cd %s && {$hgplain} hg {$pattern})";
         array_unshift($args, $this->getLocalPath());
         break;
       default:
         throw new Exception("Unrecognized version control system.");
     }
 
     array_unshift($args, $pattern);
 
     return $args;
   }
 
   private function getEmptyReadableDirectoryPath() {
     // See T2965. Some time after Git 1.7.5.4, Git started fataling if it can
     // not read $HOME. For many users, $HOME points at /root (this seems to be
     // a default result of Apache setup). Instead, explicitly point $HOME at a
     // readable, empty directory so that Git looks for the config file it's
     // after, fails to locate it, and moves on. This is really silly, but seems
     // like the least damaging approach to mitigating the issue.
     $root = dirname(phutil_get_library_root('phabricator'));
     return $root.'/support/empty/';
   }
 
   private function getSSHLogin() {
     return $this->getDetail('ssh-login');
   }
 
   private function getSSHKeyfile() {
     if ($this->sshKeyfile === null) {
       $key = $this->getDetail('ssh-key');
       $keyfile = $this->getDetail('ssh-keyfile');
       if ($keyfile) {
         // Make sure we can read the file, that it exists, etc.
         Filesystem::readFile($keyfile);
         $this->sshKeyfile = $keyfile;
       } else if ($key) {
         $keyfile = new TempFile('phabricator-repository-ssh-key');
         chmod($keyfile, 0600);
         Filesystem::writeFile($keyfile, $key);
         $this->sshKeyfile = $keyfile;
       } else {
         $this->sshKeyfile = '';
       }
     }
 
     return (string)$this->sshKeyfile;
   }
 
   public function getURI() {
     return '/diffusion/'.$this->getCallsign().'/';
   }
 
   public function isTracked() {
     return $this->getDetail('tracking-enabled', false);
   }
 
   public function getDefaultBranch() {
     $default = $this->getDetail('default-branch');
     if (strlen($default)) {
       return $default;
     }
 
     $default_branches = array(
       PhabricatorRepositoryType::REPOSITORY_TYPE_GIT        => 'master',
       PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL  => 'default',
     );
 
     return idx($default_branches, $this->getVersionControlSystem());
   }
 
   public function getDefaultArcanistBranch() {
     return coalesce($this->getDefaultBranch(), 'svn');
   }
 
   private function isBranchInFilter($branch, $filter_key) {
     $vcs = $this->getVersionControlSystem();
 
     $is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
 
     $use_filter = ($is_git);
 
     if ($use_filter) {
       $filter = $this->getDetail($filter_key, array());
       if ($filter && empty($filter[$branch])) {
         return false;
       }
     }
 
     // By default, all branches pass.
     return true;
   }
 
   public function shouldTrackBranch($branch) {
     return $this->isBranchInFilter($branch, 'branch-filter');
   }
 
   public function shouldAutocloseBranch($branch) {
     if ($this->getDetail('disable-autoclose', false)) {
       return false;
     }
 
     return $this->isBranchInFilter($branch, 'close-commits-filter');
   }
 
   public function shouldAutocloseCommit(
     PhabricatorRepositoryCommit $commit,
     PhabricatorRepositoryCommitData $data) {
 
     if ($this->getDetail('disable-autoclose', false)) {
       return false;
     }
 
     switch ($this->getVersionControlSystem()) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
         return true;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
         return true;
       default:
         throw new Exception("Unrecognized version control system.");
     }
 
     $branches = $data->getCommitDetail('seenOnBranches', array());
     foreach ($branches as $branch) {
       if ($this->shouldAutocloseBranch($branch)) {
         return true;
       }
     }
 
     return false;
   }
 
   public function formatCommitName($commit_identifier) {
     $vcs = $this->getVersionControlSystem();
 
     $type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
     $type_hg = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL;
 
     $is_git = ($vcs == $type_git);
     $is_hg = ($vcs == $type_hg);
     if ($is_git || $is_hg) {
       $short_identifier = substr($commit_identifier, 0, 12);
     } else {
       $short_identifier = $commit_identifier;
     }
 
     return 'r'.$this->getCallsign().$short_identifier;
   }
 
   public static function loadAllByPHIDOrCallsign(array $names) {
     $repositories = array();
     foreach ($names as $name) {
       $repo = id(new PhabricatorRepository())->loadOneWhere(
         'phid = %s OR callsign = %s',
         $name,
         $name);
       if (!$repo) {
         throw new Exception(
           "No repository with PHID or callsign '{$name}' exists!");
       }
       $repositories[$repo->getID()] = $repo;
     }
     return $repositories;
   }
 
 /* -(  Repository URI Management  )------------------------------------------ */
 
 
   /**
    * Get the remote URI for this repository.
    *
    * @return string
    * @task uri
    */
   public function getRemoteURI() {
     return (string)$this->getRemoteURIObject();
   }
 
 
   /**
    * Get the remote URI for this repository, without authentication information.
    *
    * @return string Repository URI.
    * @task uri
    */
   public function getPublicRemoteURI() {
     $uri = $this->getRemoteURIObject();
 
     // Make sure we don't leak anything if this repo is using HTTP Basic Auth
     // with the credentials in the URI or something zany like that.
 
     if ($uri instanceof PhutilGitURI) {
       if (!$this->getDetail('show-user', false)) {
         $uri->setUser(null);
       }
     } else {
       if (!$this->getDetail('show-user', false)) {
         $uri->setUser(null);
       }
       $uri->setPass(null);
     }
 
     return (string)$uri;
   }
 
 
   /**
    * Get the protocol for the repository's remote.
    *
    * @return string Protocol, like "ssh" or "git".
    * @task uri
    */
   public function getRemoteProtocol() {
     $uri = $this->getRemoteURIObject();
 
     if ($uri instanceof PhutilGitURI) {
       return 'ssh';
     } else {
       return $uri->getProtocol();
     }
   }
 
 
   /**
    * Get a parsed object representation of the repository's remote URI. This
    * may be a normal URI (returned as a @{class@libphutil:PhutilURI}) or a git
    * URI (returned as a @{class@libphutil:PhutilGitURI}).
    *
    * @return wild A @{class@libphutil:PhutilURI} or
    *              @{class@libphutil:PhutilGitURI}.
    * @task uri
    */
   private function getRemoteURIObject() {
     $raw_uri = $this->getDetail('remote-uri');
     if (!$raw_uri) {
       return new PhutilURI('');
     }
 
     if (!strncmp($raw_uri, '/', 1)) {
       return new PhutilURI('file://'.$raw_uri);
     }
 
     $uri = new PhutilURI($raw_uri);
     if ($uri->getProtocol()) {
       if ($this->isSSHProtocol($uri->getProtocol())) {
         if ($this->getSSHLogin()) {
           $uri->setUser($this->getSSHLogin());
         }
       }
       return $uri;
     }
 
     $uri = new PhutilGitURI($raw_uri);
     if ($uri->getDomain()) {
       if ($this->getSSHLogin()) {
         $uri->setUser($this->getSSHLogin());
       }
       return $uri;
     }
 
     throw new Exception("Remote URI '{$raw_uri}' could not be parsed!");
   }
 
 
   /**
    * Determine if we should connect to the remote using SSH flags and
    * credentials.
    *
    * @return bool True to use the SSH protocol.
    * @task uri
    */
   private function shouldUseSSH() {
     $protocol = $this->getRemoteProtocol();
     if ($this->isSSHProtocol($protocol)) {
       return (bool)$this->getSSHKeyfile();
     } else {
       return false;
     }
   }
 
 
   /**
    * Determine if we should connect to the remote using HTTP flags and
    * credentials.
    *
    * @return bool True to use the HTTP protocol.
    * @task uri
    */
   private function shouldUseHTTP() {
     $protocol = $this->getRemoteProtocol();
     if ($protocol == 'http' || $protocol == 'https') {
       return (bool)$this->getDetail('http-login');
     } else {
       return false;
     }
   }
 
 
   /**
    * Determine if we should connect to the remote using SVN flags and
    * credentials.
    *
    * @return bool True to use the SVN protocol.
    * @task uri
    */
   private function shouldUseSVNProtocol() {
     $protocol = $this->getRemoteProtocol();
     if ($protocol == 'svn') {
       return (bool)$this->getDetail('http-login');
     } else {
       return false;
     }
   }
 
 
   /**
    * Determine if a protocol is SSH or SSH-like.
    *
    * @param string A protocol string, like "http" or "ssh".
    * @return bool True if the protocol is SSH-like.
    * @task uri
    */
   private function isSSHProtocol($protocol) {
     return ($protocol == 'ssh' || $protocol == 'svn+ssh');
   }
 
   public function delete() {
     $this->openTransaction();
 
       $paths = id(new PhabricatorOwnersPath())
         ->loadAllWhere('repositoryPHID = %s', $this->getPHID());
       foreach ($paths as $path) {
         $path->delete();
       }
 
       $projects = id(new PhabricatorRepositoryArcanistProject())
         ->loadAllWhere('repositoryID = %d', $this->getID());
       foreach ($projects as $project) {
         // note each project deletes its PhabricatorRepositorySymbols
         $project->delete();
       }
 
       $commits = id(new PhabricatorRepositoryCommit())
         ->loadAllWhere('repositoryID = %d', $this->getID());
       foreach ($commits as $commit) {
         // note PhabricatorRepositoryAuditRequests and
         // PhabricatorRepositoryCommitData are deleted here too.
         $commit->delete();
       }
 
       $conn_w = $this->establishConnection('w');
 
       queryfx(
         $conn_w,
         'DELETE FROM %T WHERE repositoryID = %d',
         self::TABLE_FILESYSTEM,
         $this->getID());
 
       queryfx(
         $conn_w,
         'DELETE FROM %T WHERE repositoryID = %d',
         self::TABLE_PATHCHANGE,
         $this->getID());
 
       queryfx(
         $conn_w,
         'DELETE FROM %T WHERE repositoryID = %d',
         self::TABLE_SUMMARY,
         $this->getID());
 
       $result = parent::delete();
 
     $this->saveTransaction();
     return $result;
   }
 
   public function isGit() {
     $vcs = $this->getVersionControlSystem();
     return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT);
   }
 
   public function isSVN() {
     $vcs = $this->getVersionControlSystem();
     return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_SVN);
   }
 
   public function isHg() {
     $vcs = $this->getVersionControlSystem();
     return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL);
   }
 
 
   /**
    * Link external bug tracking system if defined.
    *
    * @param string Plain text.
    * @param string Commit identifier.
    * @return string Remarkup
    */
   public function linkBugtraq($message, $revision = null) {
     $bugtraq_url = PhabricatorEnv::getEnvConfig('bugtraq.url');
     list($bugtraq_re, $id_re) =
       PhabricatorEnv::getEnvConfig('bugtraq.logregex') +
       array('', '');
 
     switch ($this->getVersionControlSystem()) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
         // TODO: Get bugtraq:logregex and bugtraq:url from SVN properties.
         break;
     }
 
     if (!$bugtraq_url || $bugtraq_re == '') {
       return $message;
     }
 
     $matches = null;
     $flags = PREG_SET_ORDER | PREG_OFFSET_CAPTURE;
     preg_match_all('('.$bugtraq_re.')', $message, $matches, $flags);
     foreach ($matches as $match) {
       list($all, $all_offset) = array_shift($match);
 
       if ($id_re != '') {
         // Match substrings with bug IDs
         preg_match_all('('.$id_re.')', $all, $match, PREG_OFFSET_CAPTURE);
         list(, $match) = $match;
       } else {
         $all_offset = 0;
       }
 
       foreach ($match as $val) {
         list($s, $offset) = $val;
         $message = substr_replace(
           $message,
           '[[ '.str_replace('%BUGID%', $s, $bugtraq_url).' | '.$s.' ]]',
           $offset + $all_offset,
           strlen($s));
       }
     }
 
     return $message;
   }
 
 
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
     );
   }
 
   public function getPolicy($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return PhabricatorPolicies::POLICY_USER;
       case PhabricatorPolicyCapability::CAN_EDIT:
         return PhabricatorPolicies::POLICY_ADMIN;
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $user) {
     return false;
   }
 
+
+/* -(  PhabricatorMarkupInterface  )----------------------------------------- */
+
+
+  public function getMarkupFieldKey($field) {
+    $hash = PhabricatorHash::digestForIndex($this->getMarkupText($field));
+    return "repo:{$hash}";
+  }
+
+  public function newMarkupEngine($field) {
+    return PhabricatorMarkupEngine::newMarkupEngine(array());
+  }
+
+  public function getMarkupText($field) {
+    return $this->getDetail('description');
+  }
+
+  public function didMarkupText(
+    $field,
+    $output,
+    PhutilMarkupEngine $engine) {
+    require_celerity_resource('phabricator-remarkup-css');
+    return phutil_tag(
+      'div',
+      array(
+        'class' => 'phabricator-remarkup',
+      ),
+      $output);
+  }
+
+  public function shouldUseMarkupCache($field) {
+    return true;
+  }
+
 }