diff --git a/bin/ssh-connect b/bin/ssh-connect
new file mode 120000
index 000000000..79f7c94d0
--- /dev/null
+++ b/bin/ssh-connect
@@ -0,0 +1 @@
+../scripts/ssh/ssh-connect.php
\ No newline at end of file
diff --git a/scripts/ssh/ssh-connect.php b/scripts/ssh/ssh-connect.php
new file mode 100755
index 000000000..2f2877856
--- /dev/null
+++ b/scripts/ssh/ssh-connect.php
@@ -0,0 +1,71 @@
+#!/usr/bin/env php
+<?php
+
+// This is a wrapper script for Git, Mercurial, and Subversion. It primarily
+// serves to inject "-o StrictHostKeyChecking=no" into the SSH arguments.
+
+$root = dirname(dirname(dirname(__FILE__)));
+require_once $root.'/scripts/__init_script__.php';
+
+$target_name = getenv('PHABRICATOR_SSH_TARGET');
+if (!$target_name) {
+  throw new Exception(pht("No 'PHABRICATOR_SSH_TARGET' in environment!"));
+}
+
+$repository = id(new PhabricatorRepositoryQuery())
+  ->setViewer(PhabricatorUser::getOmnipotentUser())
+  ->withCallsigns(array($target_name))
+  ->executeOne();
+if (!$repository) {
+  throw new Exception(pht('No repository with callsign "%s"!', $target_name));
+}
+
+$pattern = array();
+$arguments = array();
+
+$pattern[] = 'ssh';
+
+$pattern[] = '-o';
+$pattern[] = 'StrictHostKeyChecking=no';
+
+$login = $repository->getSSHLogin();
+if (strlen($login)) {
+  $pattern[] = '-l';
+  $pattern[] = '%P';
+  $arguments[] = new PhutilOpaqueEnvelope($login);
+}
+
+$ssh_identity = null;
+
+$key = $repository->getDetail('ssh-key');
+$keyfile = $repository->getDetail('ssh-keyfile');
+if ($keyfile) {
+  $ssh_identity = $keyfile;
+} else if ($key) {
+  $tmpfile = new TempFile('phabricator-repository-ssh-key');
+  chmod($tmpfile, 0600);
+  Filesystem::writeFile($tmpfile, $key);
+  $ssh_identity = (string)$tmpfile;
+}
+
+if ($ssh_identity) {
+  $pattern[] = '-i';
+  $pattern[] = '%P';
+  $arguments[] = new PhutilOpaqueEnvelope($keyfile);
+}
+
+$pattern[] = '--';
+
+$passthru_args = array_slice($argv, 1);
+foreach ($passthru_args as $passthru_arg) {
+  $pattern[] = '%s';
+  $arguments[] = $passthru_arg;
+}
+
+$pattern = implode(' ', $pattern);
+array_unshift($arguments, $pattern);
+
+$err = newv('PhutilExecPassthru', $arguments)
+  ->execute();
+
+exit($err);
diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
index b2df31969..859c9eff7 100644
--- a/src/applications/repository/storage/PhabricatorRepository.php
+++ b/src/applications/repository/storage/PhabricatorRepository.php
@@ -1,975 +1,1004 @@
 <?php
 
 /**
  * @task uri Repository URI Management
  */
 final class PhabricatorRepository extends PhabricatorRepositoryDAO
   implements
     PhabricatorPolicyInterface,
     PhabricatorFlaggableInterface,
     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';
 
   const SERVE_OFF = 'off';
   const SERVE_READONLY = 'readonly';
   const SERVE_READWRITE = 'readwrite';
 
   protected $name;
   protected $callsign;
   protected $uuid;
   protected $viewPolicy;
   protected $editPolicy;
   protected $pushPolicy;
 
   protected $versionControlSystem;
   protected $details = array();
 
   private $sshKeyfile;
 
   private $commitCount = self::ATTACHABLE;
   private $mostRecentCommit = self::ATTACHABLE;
 
   public static function initializeNewRepository(PhabricatorUser $actor) {
     $app = id(new PhabricatorApplicationQuery())
       ->setViewer($actor)
       ->withClasses(array('PhabricatorApplicationDiffusion'))
       ->executeOne();
 
     $view_policy = $app->getPolicy(DiffusionCapabilityDefaultView::CAPABILITY);
     $edit_policy = $app->getPolicy(DiffusionCapabilityDefaultEdit::CAPABILITY);
     $push_policy = $app->getPolicy(DiffusionCapabilityDefaultPush::CAPABILITY);
 
     return id(new PhabricatorRepository())
       ->setViewPolicy($view_policy)
       ->setEditPolicy($edit_policy)
       ->setPushPolicy($push_policy);
   }
 
   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(
       PhabricatorRepositoryPHIDTypeRepository::TYPECONST);
   }
 
   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 getHumanReadableDetail($key, $default = null) {
     $value = $this->getDetail($key, $default);
 
     switch ($key) {
       case 'branch-filter':
       case 'close-commits-filter':
         $value = array_keys($value);
         $value = implode(', ', $value);
         break;
     }
 
     return $value;
   }
 
   public function setDetail($key, $value) {
     $this->details[$key] = $value;
     return $this;
   }
 
   public function attachCommitCount($count) {
     $this->commitCount = $count;
     return $this;
   }
 
   public function getCommitCount() {
     return $this->assertAttached($this->commitCount);
   }
 
   public function attachMostRecentCommit(
     PhabricatorRepositoryCommit $commit = null) {
     $this->mostRecentCommit = $commit;
     return $this;
   }
 
   public function getMostRecentCommit() {
     return $this->assertAttached($this->mostRecentCommit);
   }
 
   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() {
     $subpath = $this->getDetail('svn-subpath');
     if (!strlen($subpath)) {
       $subpath = null;
     }
     return $this->getSubversionPathURI($subpath);
   }
 
   public function getSubversionPathURI($path = null, $commit = null) {
     $vcs = $this->getVersionControlSystem();
     if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) {
       throw new Exception("Not a subversion repository!");
     }
 
     if ($this->isHosted()) {
       $uri = 'file://'.$this->getLocalPath();
     } else {
       $uri = $this->getDetail('remote-uri');
     }
 
     $uri = rtrim($uri, '/');
 
     if (strlen($path)) {
       $path = rawurlencode($path);
       $path = str_replace('%2F', '/', $path);
       $uri = $uri.'/'.ltrim($path, '/');
     }
 
     if ($path !== null || $commit !== null) {
       $uri .= '@';
     }
 
     if ($commit !== null) {
       $uri .= $commit;
     }
 
     return $uri;
   }
 
+
+/* -(  Remote Command Execution  )------------------------------------------- */
+
+
   public function execRemoteCommand($pattern /* , $arg, ... */) {
     $args = func_get_args();
-    $args = $this->formatRemoteCommand($args);
-    return call_user_func_array('exec_manual', $args);
+    return $this->newRemoteCommandFuture($args)->resolve();
   }
 
   public function execxRemoteCommand($pattern /* , $arg, ... */) {
     $args = func_get_args();
-    $args = $this->formatRemoteCommand($args);
-    return call_user_func_array('execx', $args);
+    return $this->newRemoteCommandFuture($args)->resolvex();
   }
 
   public function getRemoteCommandFuture($pattern /* , $arg, ... */) {
     $args = func_get_args();
-    $args = $this->formatRemoteCommand($args);
-    return newv('ExecFuture', $args);
+    return $this->newRemoteCommandFuture($args);
   }
 
   public function passthruRemoteCommand($pattern /* , $arg, ... */) {
     $args = func_get_args();
-    $args = $this->formatRemoteCommand($args);
-    return call_user_func_array('phutil_passthru', $args);
+    return $this->newRemoteCommandPassthru($args)->execute();
+  }
+
+  private function newRemoteCommandFuture(array $argv) {
+    $argv = $this->formatRemoteCommand($argv);
+    $future = newv('ExecFuture', $argv);
+    $future->setEnv($this->getRemoteCommandEnvironment());
+    return $future;
+  }
+
+  private function newRemoteCommandPassthru(array $argv) {
+    $argv = $this->formatRemoteCommand($argv);
+    $passthru = newv('PhutilExecPassthru', $argv);
+    $passthru->setEnv($this->getRemoteCommandEnvironment());
+    return $passthru;
   }
 
-  public function execLocalCommand($pattern /* , $arg, ... */) {
-    $this->assertLocalExists();
 
+/* -(  Local Command Execution  )-------------------------------------------- */
+
+
+  public function execLocalCommand($pattern /* , $arg, ... */) {
     $args = func_get_args();
-    $args = $this->formatLocalCommand($args);
-    return call_user_func_array('exec_manual', $args);
+    return $this->newLocalCommandFuture($args)->resolve();
   }
 
   public function execxLocalCommand($pattern /* , $arg, ... */) {
-    $this->assertLocalExists();
-
     $args = func_get_args();
-    $args = $this->formatLocalCommand($args);
-    return call_user_func_array('execx', $args);
+    return $this->newLocalCommandFuture($args)->resolvex();
   }
 
   public function getLocalCommandFuture($pattern /* , $arg, ... */) {
-    $this->assertLocalExists();
-
     $args = func_get_args();
-    $args = $this->formatLocalCommand($args);
-    return newv('ExecFuture', $args);
+    return $this->newLocalCommandFuture($args);
   }
 
   public function passthruLocalCommand($pattern /* , $arg, ... */) {
+    $args = func_get_args();
+    return $this->newLocalCommandPassthru($args)->execute();
+  }
+
+  private function newLocalCommandFuture(array $argv) {
     $this->assertLocalExists();
 
-    $args = func_get_args();
-    $args = $this->formatLocalCommand($args);
-    return call_user_func_array('phutil_passthru', $args);
+    $argv = $this->formatLocalCommand($argv);
+    $future = newv('ExecFuture', $argv);
+    $future->setEnv($this->getLocalCommandEnvironment());
+
+    if ($this->usesLocalWorkingCopy()) {
+      $future->setCWD($this->getLocalPath());
+    }
+
+    return $future;
   }
 
+  private function newLocalCommandPassthru(array $argv) {
+    $this->assertLocalExists();
+
+    $argv = $this->formatLocalCommand($argv);
+    $future = newv('PhutilExecPassthru', $argv);
+    $future->setEnv($this->getLocalCommandEnvironment());
+
+    if ($this->usesLocalWorkingCopy()) {
+      $future->setCWD($this->getLocalPath());
+    }
+
+    return $future;
+  }
 
-  private function formatRemoteCommand(array $args) {
-    $pattern = $args[0];
-    $args = array_slice($args, 1);
 
-    $empty = $this->getEmptyReadableDirectoryPath();
+/* -(  Command Infrastructure  )--------------------------------------------- */
+
+
+  private function getSSHWrapper() {
+    $root = dirname(phutil_get_library_root('phabricator'));
+    return $root.'/bin/ssh-connect';
+  }
+
+  private function getCommonCommandEnvironment() {
+    $env = array(
+      // NOTE: Force the language to "C", which overrides locale settings.
+      // This makes stuff print in English instead of, e.g., French, so we can
+      // parse the output of some commands, error messages, etc.
+      'LANG' => 'C',
+    );
+
+    switch ($this->getVersionControlSystem()) {
+      case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
+        break;
+      case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
+        // NOTE: 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'));
+        $env['HOME'] = $root.'/support/empty/';
+        break;
+      case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
+        // NOTE: This overrides certain configuration, extensions, and settings
+        // which make Mercurial commands do random unusual things.
+        $env['HGPLAIN'] = 1;
+        break;
+      default:
+        throw new Exception("Unrecognized version control system.");
+    }
+
+    return $env;
+  }
+
+  private function getLocalCommandEnvironment() {
+    return $this->getCommonCommandEnvironment();
+  }
+
+  private function getRemoteCommandEnvironment() {
+    $env = $this->getCommonCommandEnvironment();
 
     if ($this->shouldUseSSH()) {
+      // NOTE: This is read by `bin/ssh-connect`, and tells it which credentials
+      // to use.
+      $env['PHABRICATOR_SSH_TARGET'] = $this->getCallsign();
       switch ($this->getVersionControlSystem()) {
         case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
-          $pattern = "SVN_SSH=%s svn --non-interactive {$pattern}";
-          array_unshift(
-            $args,
-            csprintf(
-              'ssh -l %P -i %P',
-              new PhutilOpaqueEnvelope($this->getSSHLogin()),
-              new PhutilOpaqueEnvelope($this->getSSHKeyfile())));
+          // Force SVN to use `bin/ssh-connect`.
+          $env['SVN_SSH'] = $this->getSSHWrapper();
           break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
-          $command = call_user_func_array(
-            'csprintf',
-            array_merge(
-              array(
-                "(ssh-add %P && HOME=%s git {$pattern})",
-                new PhutilOpaqueEnvelope($this->getSSHKeyfile()),
-                $empty,
-              ),
-              $args));
-          $pattern = "ssh-agent sh -c %s";
-          $args = array($command);
+          // Force Git to use `bin/ssh-connect`.
+          $env['GIT_SSH'] = $this->getSSHWrapper();
           break;
         case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
-          $pattern = "hg --config ui.ssh=%s {$pattern}";
-          array_unshift(
-            $args,
-            csprintf(
-              'ssh -l %P -i %P',
-              new PhutilOpaqueEnvelope($this->getSSHLogin()),
-              new PhutilOpaqueEnvelope($this->getSSHKeyfile())));
+          // We force Mercurial through `bin/ssh-connect` too, but it uses a
+          // command-line flag instead of an environmental variable.
           break;
         default:
           throw new Exception("Unrecognized version control system.");
       }
-    } else if ($this->shouldUseHTTP()) {
-      switch ($this->getVersionControlSystem()) {
-        case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
+    }
+
+    return $env;
+  }
+
+  private function formatRemoteCommand(array $args) {
+    $pattern = $args[0];
+    $args = array_slice($args, 1);
+
+    switch ($this->getVersionControlSystem()) {
+      case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
+        if ($this->shouldUseHTTP()) {
           $pattern =
             "svn ".
             "--non-interactive ".
             "--no-auth-cache ".
             "--trust-server-cert ".
             "--username %P ".
             "--password %P ".
             $pattern;
           array_unshift(
             $args,
             new PhutilOpaqueEnvelope($this->getDetail('http-login')),
             new PhutilOpaqueEnvelope($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 %P ".
-              "--password %P ".
-              $pattern;
-            array_unshift(
-              $args,
-              new PhutilOpaqueEnvelope($this->getDetail('http-login')),
-              new PhutilOpaqueEnvelope($this->getDetail('http-pass')));
-            break;
-        default:
-          throw new Exception(
-            "SVN protocol is SVN only.");
-      }
-    } else {
-      switch ($this->getVersionControlSystem()) {
-        case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
+        } else if ($this->shouldUseSVNProtocol()) {
+          $pattern =
+            "svn ".
+            "--non-interactive ".
+            "--no-auth-cache ".
+            "--username %P ".
+            "--password %P ".
+            $pattern;
+          array_unshift(
+            $args,
+            new PhutilOpaqueEnvelope($this->getDetail('http-login')),
+            new PhutilOpaqueEnvelope($this->getDetail('http-pass')));
+        } else {
           $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:
+        }
+        break;
+      case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
+        $pattern = "git {$pattern}";
+        break;
+      case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
+        if ($this->shouldUseSSH()) {
+          $pattern = "hg --config ui.ssh=%s {$pattern}";
+          array_unshift(
+            $args,
+            $this->getSSHWrapper());
+        } else {
           $pattern = "hg {$pattern}";
-          break;
-        default:
-          throw new Exception("Unrecognized version control system.");
-      }
+        }
+        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());
+        $pattern = "svn --non-interactive {$pattern}";
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
-        $pattern = "(cd %s && HOME=%s git {$pattern})";
-        array_unshift($args, $this->getLocalPath(), $empty);
+        $pattern = "git {$pattern}";
         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());
+        $pattern = "hg {$pattern}";
         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() {
+  public 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->isImporting()) {
       return false;
     }
 
     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 function isImporting() {
     return (bool)$this->getDetail('importing', false);
   }
 
 /* -(  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
    */
   public 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() {
     if ($this->isHosted()) {
       return false;
     }
 
     $protocol = $this->getRemoteProtocol();
     if ($this->isSSHProtocol($protocol)) {
-      return (bool)$this->getSSHKeyfile();
-    } else {
-      return false;
+      $key = $this->getDetail('ssh-key');
+      $keyfile = $this->getDetail('ssh-keyfile');
+      if ($key || $keyfile) {
+        return true;
+      }
     }
+
+    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() {
     if ($this->isHosted()) {
       return false;
     }
 
     $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() {
     if ($this->isHosted()) {
       return false;
     }
 
     $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);
   }
 
   public function isHosted() {
     return (bool)$this->getDetail('hosting-enabled', false);
   }
 
   public function setHosted($enabled) {
     return $this->setDetail('hosting-enabled', $enabled);
   }
 
   public function getServeOverHTTP() {
     if ($this->isSVN()) {
       return self::SERVE_OFF;
     }
     $serve = $this->getDetail('serve-over-http', self::SERVE_OFF);
     return $this->normalizeServeConfigSetting($serve);
   }
 
   public function setServeOverHTTP($mode) {
     return $this->setDetail('serve-over-http', $mode);
   }
 
   public function getServeOverSSH() {
     $serve = $this->getDetail('serve-over-ssh', self::SERVE_OFF);
     return $this->normalizeServeConfigSetting($serve);
   }
 
   public function setServeOverSSH($mode) {
     return $this->setDetail('serve-over-ssh', $mode);
   }
 
   public static function getProtocolAvailabilityName($constant) {
     switch ($constant) {
       case self::SERVE_OFF:
         return pht('Off');
       case self::SERVE_READONLY:
         return pht('Read Only');
       case self::SERVE_READWRITE:
         return pht('Read/Write');
       default:
         return pht('Unknown');
     }
   }
 
   private function normalizeServeConfigSetting($value) {
     switch ($value) {
       case self::SERVE_OFF:
       case self::SERVE_READONLY:
         return $value;
       case self::SERVE_READWRITE:
         if ($this->isHosted()) {
           return self::SERVE_READWRITE;
         } else {
           return self::SERVE_READONLY;
         }
       default:
         return self::SERVE_OFF;
     }
   }
 
 
   /**
    * Raise more useful errors when there are basic filesystem problems.
    */
   private function assertLocalExists() {
     if (!$this->usesLocalWorkingCopy()) {
       return;
     }
 
     $local = $this->getLocalPath();
     Filesystem::assertExists($local);
     Filesystem::assertIsDirectory($local);
     Filesystem::assertReadable($local);
   }
 
   /**
    * Determine if the working copy is bare or not. In Git, this corresponds
    * to `--bare`. In Mercurial, `--noupdate`.
    */
   public function isWorkingCopyBare() {
     switch ($this->getVersionControlSystem()) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
       case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
         return false;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         $local = $this->getLocalPath();
         if (Filesystem::pathExists($local.'/.git')) {
           return false;
         } else {
           return true;
         }
     }
   }
 
   public function usesLocalWorkingCopy() {
     switch ($this->getVersionControlSystem()) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
         return $this->isHosted();
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
       case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
         return true;
     }
   }
 
   public function writeStatusMessage(
     $status_type,
     $status_code,
     array $parameters = array()) {
 
     $table = new PhabricatorRepositoryStatusMessage();
     $conn_w = $table->establishConnection('w');
     $table_name = $table->getTableName();
 
     if ($status_code === null) {
       queryfx(
         $conn_w,
         'DELETE FROM %T WHERE repositoryID = %d AND statusType = %s',
         $table_name,
         $this->getID(),
         $status_type);
     } else {
       queryfx(
         $conn_w,
         'INSERT INTO %T
           (repositoryID, statusType, statusCode, parameters, epoch)
           VALUES (%d, %s, %s, %s, %d)
           ON DUPLICATE KEY UPDATE
             statusCode = VALUES(statusCode),
             parameters = VALUES(parameters),
             epoch = VALUES(epoch)',
         $table_name,
         $this->getID(),
         $status_type,
         $status_code,
         json_encode($parameters),
         time());
     }
 
     return $this;
   }
 
 
 /* -(  PhabricatorPolicyInterface  )----------------------------------------- */
 
 
   public function getCapabilities() {
     return array(
       PhabricatorPolicyCapability::CAN_VIEW,
       PhabricatorPolicyCapability::CAN_EDIT,
       DiffusionCapabilityPush::CAPABILITY,
     );
   }
 
   public function getPolicy($capability) {
     switch ($capability) {
       case PhabricatorPolicyCapability::CAN_VIEW:
         return $this->getViewPolicy();
       case PhabricatorPolicyCapability::CAN_EDIT:
         return $this->getEditPolicy();
       case DiffusionCapabilityPush::CAPABILITY:
         return $this->getPushPolicy();
     }
   }
 
   public function hasAutomaticCapability($capability, PhabricatorUser $user) {
     return false;
   }
 
   public function describeAutomaticCapability($capability) {
     return null;
   }
 
 
 
 /* -(  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;
   }
 
 }