diff --git a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
index 4c22b30000..87e328949b 100644
--- a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
+++ b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
@@ -1,798 +1,805 @@
 <?php
 
 /**
  * Run pull commands on local working copies to keep them up to date. This
  * daemon handles all repository types.
  *
  * By default, the daemon pulls **every** repository. If you want it to be
  * responsible for only some repositories, you can launch it with a list of
  * PHIDs or callsigns:
  *
  *   ./phd launch repositorypulllocal -- X Q Z
  *
  * You can also launch a daemon which is responsible for all //but// one or
  * more repositories:
  *
  *   ./phd launch repositorypulllocal -- --not A --not B
  *
  * If you have a very large number of repositories and some aren't being pulled
  * as frequently as you'd like, you can either change the pull frequency of
  * the less-important repositories to a larger number (so the daemon will skip
  * them more often) or launch one daemon for all the less-important repositories
  * and one for the more important repositories (or one for each more important
  * repository).
  *
  * @task pull   Pulling Repositories
  * @task git    Git Implementation
  * @task hg     Mercurial Implementation
  */
 final class PhabricatorRepositoryPullLocalDaemon
   extends PhabricatorDaemon {
 
   private $commitCache = array();
   private $repair;
   private $discoveryEngines = array();
 
   public function setRepair($repair) {
     $this->repair = $repair;
     return $this;
   }
 
 
 /* -(  Pulling Repositories  )----------------------------------------------- */
 
 
   /**
    * @task pull
    */
   public function run() {
     $argv = $this->getArgv();
     array_unshift($argv, __CLASS__);
     $args = new PhutilArgumentParser($argv);
     $args->parse(
       array(
         array(
           'name'      => 'no-discovery',
           'help'      => 'Pull only, without discovering commits.',
         ),
         array(
           'name'      => 'not',
           'param'     => 'repository',
           'repeat'    => true,
           'help'      => 'Do not pull __repository__.',
         ),
         array(
           'name'      => 'repositories',
           'wildcard'  => true,
           'help'      => 'Pull specific __repositories__ instead of all.',
         ),
       ));
 
     $no_discovery   = $args->getArg('no-discovery');
     $repo_names     = $args->getArg('repositories');
     $exclude_names  = $args->getArg('not');
 
     // Each repository has an individual pull frequency; after we pull it,
     // wait that long to pull it again. When we start up, try to pull everything
     // serially.
     $retry_after = array();
 
     $min_sleep = 15;
 
     while (true) {
       $repositories = $this->loadRepositories($repo_names);
       if ($exclude_names) {
         $exclude = $this->loadRepositories($exclude_names);
         $repositories = array_diff_key($repositories, $exclude);
       }
 
       // Shuffle the repositories, then re-key the array since shuffle()
       // discards keys. This is mostly for startup, we'll use soft priorities
       // later.
       shuffle($repositories);
       $repositories = mpull($repositories, null, 'getID');
 
       // If any repositories have the NEEDS_UPDATE flag set, pull them
       // as soon as possible.
       $type_need_update = PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE;
       $need_update_messages = id(new PhabricatorRepositoryStatusMessage())
         ->loadAllWhere('statusType = %s', $type_need_update);
       foreach ($need_update_messages as $message) {
         $retry_after[$message->getRepositoryID()] = time();
       }
 
       // If any repositories were deleted, remove them from the retry timer map
       // so we don't end up with a retry timer that never gets updated and
       // causes us to sleep for the minimum amount of time.
       $retry_after = array_select_keys(
         $retry_after,
         array_keys($repositories));
 
       // Assign soft priorities to repositories based on how frequently they
       // should pull again.
       asort($retry_after);
       $repositories = array_select_keys(
         $repositories,
         array_keys($retry_after)) + $repositories;
 
       foreach ($repositories as $id => $repository) {
         $after = idx($retry_after, $id, 0);
         if ($after > time()) {
           continue;
         }
 
         $tracked = $repository->isTracked();
         if (!$tracked) {
           continue;
         }
 
         $callsign = $repository->getCallsign();
 
         try {
           $this->log("Updating repository '{$callsign}'.");
 
           id(new PhabricatorRepositoryPullEngine())
             ->setRepository($repository)
             ->pullRepository();
 
           if (!$no_discovery) {
             // TODO: It would be nice to discover only if we pulled something,
             // but this isn't totally trivial. It's slightly more complicated
             // with hosted repositories, too.
 
             $lock_name = get_class($this).':'.$callsign;
             $lock = PhabricatorGlobalLock::newLock($lock_name);
             $lock->lock();
 
             $repository->writeStatusMessage(
               PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE,
               null);
 
             try {
               $this->discoverRepository($repository);
               $repository->writeStatusMessage(
                 PhabricatorRepositoryStatusMessage::TYPE_FETCH,
                 PhabricatorRepositoryStatusMessage::CODE_OKAY);
             } catch (Exception $ex) {
               $repository->writeStatusMessage(
                 PhabricatorRepositoryStatusMessage::TYPE_FETCH,
                 PhabricatorRepositoryStatusMessage::CODE_ERROR,
                 array(
                   'message' => pht(
                     'Error updating working copy: %s', $ex->getMessage()),
                 ));
               $lock->unlock();
               throw $ex;
             }
 
             $lock->unlock();
           }
 
           $sleep_for = $repository->getDetail('pull-frequency', $min_sleep);
           $retry_after[$id] = time() + $sleep_for;
         } catch (PhutilLockException $ex) {
           $retry_after[$id] = time() + $min_sleep;
           $this->log("Failed to acquire lock.");
         } catch (Exception $ex) {
           $retry_after[$id] = time() + $min_sleep;
 
           $proxy = new PhutilProxyException(
             "Error while fetching changes to the '{$callsign}' repository.",
             $ex);
           phlog($proxy);
         }
 
         $this->stillWorking();
       }
 
       if ($retry_after) {
         $sleep_until = max(min($retry_after), time() + $min_sleep);
       } else {
         $sleep_until = time() + $min_sleep;
       }
 
       $this->sleep($sleep_until - time());
     }
   }
 
 
   /**
    * @task pull
    */
   protected function loadRepositories(array $names) {
     $query = id(new PhabricatorRepositoryQuery())
       ->setViewer($this->getViewer());
 
     if ($names) {
       $query->withCallsigns($names);
     }
 
     $repos = $query->execute();
 
     if ($names) {
       $by_callsign = mpull($repos, null, 'getCallsign');
       foreach ($names as $name) {
         if (empty($by_callsign[$name])) {
           throw new Exception(
             "No repository exists with callsign '{$name}'!");
         }
       }
     }
 
     return $repos;
   }
 
   public function discoverRepository(PhabricatorRepository $repository) {
     $vcs = $repository->getVersionControlSystem();
 
     $result = null;
     $refs = null;
     switch ($vcs) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         $result = $this->executeGitDiscover($repository);
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
         $refs = $this->getDiscoveryEngine($repository)
           ->discoverCommits();
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
         $result = $this->executeHgDiscover($repository);
         break;
       default:
         throw new Exception("Unknown VCS '{$vcs}'!");
     }
 
     if ($refs !== null) {
       foreach ($refs as $ref) {
         $this->recordCommit(
           $repository,
           $ref->getIdentifier(),
           $ref->getEpoch(),
           $ref->getBranch());
       }
     }
 
     $this->checkIfRepositoryIsFullyImported($repository);
 
     if ($refs !== null) {
       return (bool)count($refs);
     } else {
       return $result;
     }
   }
 
   private function getDiscoveryEngine(PhabricatorRepository $repository) {
     $id = $repository->getID();
     if (empty($this->discoveryEngines[$id])) {
       $engine = id(new PhabricatorRepositoryDiscoveryEngine())
           ->setRepository($repository)
           ->setVerbose($this->getVerbose())
           ->setRepairMode($this->repair);
 
       $this->discoveryEngines[$id] = $engine;
     }
     return $this->discoveryEngines[$id];
   }
 
   private function isKnownCommit(
     PhabricatorRepository $repository,
     $target) {
 
     if ($this->getCache($repository, $target)) {
       return true;
     }
 
     if ($this->repair) {
       // In repair mode, rediscover the entire repository, ignoring the
       // database state. We can hit the local cache above, but if we miss it
       // stop the script from going to the database cache.
       return false;
     }
 
     $commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
       'repositoryID = %d AND commitIdentifier = %s',
       $repository->getID(),
       $target);
 
     if (!$commit) {
       return false;
     }
 
     $this->setCache($repository, $target);
     while (count($this->commitCache) > 2048) {
       array_shift($this->commitCache);
     }
 
     return true;
   }
 
   private function isKnownCommitOnAnyAutocloseBranch(
     PhabricatorRepository $repository,
     $target) {
 
     $commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
       'repositoryID = %d AND commitIdentifier = %s',
       $repository->getID(),
       $target);
 
     if (!$commit) {
       $callsign = $repository->getCallsign();
 
       $console = PhutilConsole::getConsole();
       $console->writeErr(
         "WARNING: Repository '%s' is missing commits ('%s' is missing from ".
         "history). Run '%s' to repair the repository.\n",
         $callsign,
         $target,
         "bin/repository discover --repair {$callsign}");
 
       return false;
     }
 
     $data = $commit->loadCommitData();
     if (!$data) {
       return false;
     }
 
     if ($repository->shouldAutocloseCommit($commit, $data)) {
       return true;
     }
 
     return false;
   }
 
   private function recordCommit(
     PhabricatorRepository $repository,
     $commit_identifier,
     $epoch,
     $branch = null) {
 
     $commit = new PhabricatorRepositoryCommit();
     $commit->setRepositoryID($repository->getID());
     $commit->setCommitIdentifier($commit_identifier);
     $commit->setEpoch($epoch);
 
     $data = new PhabricatorRepositoryCommitData();
     if ($branch) {
       $data->setCommitDetail('seenOnBranches', array($branch));
     }
 
     try {
       $commit->openTransaction();
         $commit->save();
         $data->setCommitID($commit->getID());
         $data->save();
       $commit->saveTransaction();
 
       $this->insertTask($repository, $commit);
 
       queryfx(
         $repository->establishConnection('w'),
         'INSERT INTO %T (repositoryID, size, lastCommitID, epoch)
           VALUES (%d, 1, %d, %d)
           ON DUPLICATE KEY UPDATE
             size = size + 1,
             lastCommitID =
               IF(VALUES(epoch) > epoch, VALUES(lastCommitID), lastCommitID),
             epoch = IF(VALUES(epoch) > epoch, VALUES(epoch), epoch)',
         PhabricatorRepository::TABLE_SUMMARY,
         $repository->getID(),
         $commit->getID(),
         $epoch);
 
       if ($this->repair) {
         // Normally, the query should throw a duplicate key exception. If we
         // reach this in repair mode, we've actually performed a repair.
         $this->log("Repaired commit '{$commit_identifier}'.");
       }
 
       $this->setCache($repository, $commit_identifier);
 
       PhutilEventEngine::dispatchEvent(
         new PhabricatorEvent(
           PhabricatorEventType::TYPE_DIFFUSION_DIDDISCOVERCOMMIT,
           array(
             'repository'  => $repository,
             'commit'      => $commit,
           )));
 
     } catch (AphrontQueryDuplicateKeyException $ex) {
       $commit->killTransaction();
       // Ignore. This can happen because we discover the same new commit
       // more than once when looking at history, or because of races or
       // data inconsistency or cosmic radiation; in any case, we're still
       // in a good state if we ignore the failure.
       $this->setCache($repository, $commit_identifier);
     }
   }
 
   private function updateCommit(
     PhabricatorRepository $repository,
     $commit_identifier,
     $branch) {
 
     $commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
       'repositoryID = %d AND commitIdentifier = %s',
       $repository->getID(),
       $commit_identifier);
 
     if (!$commit) {
       // This can happen if the phabricator DB doesn't have the commit info,
       // or the commit is so big that phabricator couldn't parse it. In this
       // case we just ignore it.
       return;
     }
 
     $data = id(new PhabricatorRepositoryCommitData())->loadOneWhere(
       'commitID = %d',
       $commit->getID());
     if (!$data) {
       $data = new PhabricatorRepositoryCommitData();
       $data->setCommitID($commit->getID());
     }
     $branches = $data->getCommitDetail('seenOnBranches', array());
     $branches[] = $branch;
     $data->setCommitDetail('seenOnBranches', $branches);
     $data->save();
 
     $this->insertTask(
       $repository,
       $commit,
       array(
         'only' => true
       ));
   }
 
   private function insertTask(
     PhabricatorRepository $repository,
     PhabricatorRepositoryCommit $commit,
     $data = array()) {
 
     $vcs = $repository->getVersionControlSystem();
     switch ($vcs) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         $class = 'PhabricatorRepositoryGitCommitMessageParserWorker';
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
         $class = 'PhabricatorRepositorySvnCommitMessageParserWorker';
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
         $class = 'PhabricatorRepositoryMercurialCommitMessageParserWorker';
         break;
       default:
         throw new Exception("Unknown repository type '{$vcs}'!");
     }
 
     $data['commitID'] = $commit->getID();
 
     PhabricatorWorker::scheduleTask($class, $data);
   }
 
 
   private function setCache(
     PhabricatorRepository $repository,
     $commit_identifier) {
 
     $key = $this->getCacheKey($repository, $commit_identifier);
     $this->commitCache[$key] = true;
   }
 
   private function getCache(
     PhabricatorRepository $repository,
     $commit_identifier) {
 
     $key = $this->getCacheKey($repository, $commit_identifier);
     return idx($this->commitCache, $key, false);
   }
 
   private function getCacheKey(
     PhabricatorRepository $repository,
     $commit_identifier) {
 
     return $repository->getID().':'.$commit_identifier;
   }
 
   private function checkIfRepositoryIsFullyImported(
     PhabricatorRepository $repository) {
 
     // Check if the repository has the "Importing" flag set. We want to clear
     // the flag if we can.
     $importing = $repository->getDetail('importing');
     if (!$importing) {
       // This repository isn't marked as "Importing", so we're done.
       return;
     }
 
     // Look for any commit which hasn't imported.
     $unparsed_commit = queryfx_one(
       $repository->establishConnection('r'),
       'SELECT * FROM %T WHERE repositoryID = %d AND importStatus != %d
         LIMIT 1',
       id(new PhabricatorRepositoryCommit())->getTableName(),
       $repository->getID(),
       PhabricatorRepositoryCommit::IMPORTED_ALL);
     if ($unparsed_commit) {
       // We found a commit which still needs to import, so we can't clear the
       // flag.
       return;
     }
 
     // Clear the "importing" flag.
     $repository->openTransaction();
       $repository->beginReadLocking();
         $repository = $repository->reload();
         $repository->setDetail('importing', false);
         $repository->save();
       $repository->endReadLocking();
     $repository->saveTransaction();
   }
 
 /* -(  Git Implementation  )------------------------------------------------- */
 
 
   /**
    * @task git
    */
   private function executeGitDiscover(
     PhabricatorRepository $repository) {
 
-    list($remotes) = $repository->execxLocalCommand(
-      'remote show -n origin');
+    if (!$repository->isHosted()) {
+      list($remotes) = $repository->execxLocalCommand(
+        'remote show -n origin');
+
+      $matches = null;
+      if (!preg_match('/^\s*Fetch URL:\s*(.*?)\s*$/m', $remotes, $matches)) {
+        throw new Exception(
+          "Expected 'Fetch URL' in 'git remote show -n origin'.");
+      }
 
-    $matches = null;
-    if (!preg_match('/^\s*Fetch URL:\s*(.*?)\s*$/m', $remotes, $matches)) {
-      throw new Exception(
-        "Expected 'Fetch URL' in 'git remote show -n origin'.");
+      self::executeGitVerifySameOrigin(
+        $matches[1],
+        $repository->getRemoteURI(),
+        $repository->getLocalPath());
     }
 
-    self::executeGitVerifySameOrigin(
-      $matches[1],
-      $repository->getRemoteURI(),
-      $repository->getLocalPath());
-
     $refs = id(new DiffusionLowLevelGitRefQuery())
       ->setRepository($repository)
       ->withIsOriginBranch(true)
       ->execute();
 
     $branches = mpull($refs, 'getCommitIdentifier', 'getShortName');
 
     if (!$branches) {
       // This repository has no branches at all, so we don't need to do
       // anything. Generally, this means the repository is empty.
       return;
     }
 
     $callsign = $repository->getCallsign();
 
     $tracked_something = false;
 
     $this->log("Discovering commits in repository '{$callsign}'...");
     foreach ($branches as $name => $commit) {
       $this->log("Examining branch '{$name}', at {$commit}.");
       if (!$repository->shouldTrackBranch($name)) {
         $this->log("Skipping, branch is untracked.");
         continue;
       }
 
       $tracked_something = true;
 
       if ($this->isKnownCommit($repository, $commit)) {
         $this->log("Skipping, HEAD is known.");
         continue;
       }
 
       $this->log("Looking for new commits.");
       $this->executeGitDiscoverCommit($repository, $commit, $name, false);
     }
 
     if (!$tracked_something) {
       $repo_name = $repository->getName();
       $repo_callsign = $repository->getCallsign();
       throw new Exception(
         "Repository r{$repo_callsign} '{$repo_name}' has no tracked branches! ".
         "Verify that your branch filtering settings are correct.");
     }
 
 
     $this->log("Discovering commits on autoclose branches...");
     foreach ($branches as $name => $commit) {
       $this->log("Examining branch '{$name}', at {$commit}'.");
       if (!$repository->shouldTrackBranch($name)) {
         $this->log("Skipping, branch is untracked.");
         continue;
       }
 
       if (!$repository->shouldAutocloseBranch($name)) {
         $this->log("Skipping, branch is not autoclose.");
         continue;
       }
 
       if ($this->isKnownCommitOnAnyAutocloseBranch($repository, $commit)) {
         $this->log("Skipping, commit is known on an autoclose branch.");
         continue;
       }
 
       $this->log("Looking for new autoclose commits.");
       $this->executeGitDiscoverCommit($repository, $commit, $name, true);
     }
   }
 
 
   /**
    * @task git
    */
   private function executeGitDiscoverCommit(
     PhabricatorRepository $repository,
     $commit,
     $branch,
     $autoclose) {
 
     $discover = array($commit);
     $insert = array($commit);
 
     $seen_parent = array();
 
     $stream = new PhabricatorGitGraphStream($repository, $commit);
 
     while (true) {
       $target = array_pop($discover);
       $parents = $stream->getParents($target);
       foreach ($parents as $parent) {
         if (isset($seen_parent[$parent])) {
           // We end up in a loop here somehow when we parse Arcanist if we
           // don't do this. TODO: Figure out why and draw a pretty diagram
           // since it's not evident how parsing a DAG with this causes the
           // loop to stop terminating.
           continue;
         }
         $seen_parent[$parent] = true;
         if ($autoclose) {
           $known = $this->isKnownCommitOnAnyAutocloseBranch(
             $repository,
             $parent);
         } else {
           $known = $this->isKnownCommit($repository, $parent);
         }
         if (!$known) {
           $this->log("Discovered commit '{$parent}'.");
           $discover[] = $parent;
           $insert[] = $parent;
         }
       }
       if (empty($discover)) {
         break;
       }
     }
 
     $n = count($insert);
     if ($autoclose) {
       $this->log("Found {$n} new autoclose commits on branch '{$branch}'.");
     } else {
       $this->log("Found {$n} new commits on branch '{$branch}'.");
     }
 
     while (true) {
       $target = array_pop($insert);
       $epoch = $stream->getCommitDate($target);
       $epoch = trim($epoch);
 
       if ($autoclose) {
         $this->updateCommit($repository, $target, $branch);
       } else {
         $this->recordCommit($repository, $target, $epoch, $branch);
       }
 
       if (empty($insert)) {
         break;
       }
     }
   }
 
 
   /**
    * @task git
    */
   public static function executeGitVerifySameOrigin($remote, $expect, $where) {
     $remote_path = self::getPathFromGitURI($remote);
     $expect_path = self::getPathFromGitURI($expect);
 
     $remote_match = self::executeGitNormalizePath($remote_path);
     $expect_match = self::executeGitNormalizePath($expect_path);
 
     if ($remote_match != $expect_match) {
       throw new Exception(
         "Working copy at '{$where}' has a mismatched origin URL. It has ".
         "origin URL '{$remote}' (with remote path '{$remote_path}'), but the ".
         "configured URL '{$expect}' (with remote path '{$expect_path}') is ".
         "expected. Refusing to proceed because this may indicate that the ".
         "working copy is actually some other repository.");
     }
   }
 
   private static function getPathFromGitURI($raw_uri) {
     $uri = new PhutilURI($raw_uri);
     if ($uri->getProtocol()) {
       return $uri->getPath();
     }
 
     $uri = new PhutilGitURI($raw_uri);
     if ($uri->getDomain()) {
       return $uri->getPath();
     }
 
     return $raw_uri;
   }
 
 
   /**
    * @task git
    */
   private static function executeGitNormalizePath($path) {
     // Strip away "/" and ".git", so similar paths correctly match.
 
     $path = trim($path, '/');
     $path = preg_replace('/\.git$/', '', $path);
     return $path;
   }
 
 
 /* -(  Mercurial Implementation  )------------------------------------------- */
 
 
   private function executeHgDiscover(PhabricatorRepository $repository) {
     // NOTE: "--debug" gives us 40-character hashes.
     list($stdout) = $repository->execxLocalCommand('--debug branches');
 
+    if (!trim($stdout)) {
+      // No branches; likely a newly initialized repository.
+      return false;
+    }
+
     $branches = ArcanistMercurialParser::parseMercurialBranches($stdout);
     $got_something = false;
     foreach ($branches as $name => $branch) {
       $commit = $branch['rev'];
       if ($this->isKnownCommit($repository, $commit)) {
         continue;
       } else {
         $this->executeHgDiscoverCommit($repository, $commit);
         $got_something = true;
       }
     }
 
     return $got_something;
   }
 
   private function executeHgDiscoverCommit(
     PhabricatorRepository $repository,
     $commit) {
 
     $discover = array($commit);
     $insert = array($commit);
 
     $seen_parent = array();
 
     $stream = new PhabricatorMercurialGraphStream($repository);
 
     // For all the new commits at the branch heads, walk backward until we
     // find only commits we've aleady seen.
     while ($discover) {
       $target = array_pop($discover);
 
       $parents = $stream->getParents($target);
 
       foreach ($parents as $parent) {
         if (isset($seen_parent[$parent])) {
           continue;
         }
         $seen_parent[$parent] = true;
         if (!$this->isKnownCommit($repository, $parent)) {
           $discover[] = $parent;
           $insert[] = $parent;
         }
       }
     }
 
     foreach ($insert as $target) {
       $epoch = $stream->getCommitDate($target);
       $this->recordCommit($repository, $target, $epoch);
     }
   }
 
 }
diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php
index 2fa02c6832..154ca4342f 100644
--- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php
+++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php
@@ -1,330 +1,370 @@
 <?php
 
 /**
  * Manages execution of `git pull` and `hg pull` commands for
  * @{class:PhabricatorRepository} objects. Used by
  * @{class:PhabricatorRepositoryPullLocalDaemon}.
  *
+ * This class also covers initial working copy setup through `git clone`,
+ * `git init`, `hg clone`, `hg init`, or `svnadmin create`.
+ *
  * @task pull     Pulling Working Copies
  * @task git      Pulling Git Working Copies
  * @task hg       Pulling Mercurial Working Copies
+ * @task svn      Pulling Subversion Working Copies
  * @task internal Internals
  */
 final class PhabricatorRepositoryPullEngine
   extends PhabricatorRepositoryEngine {
 
 
 /* -(  Pulling Working Copies  )--------------------------------------------- */
 
 
   public function pullRepository() {
     $repository = $this->getRepository();
 
     $is_hg = false;
     $is_git = false;
+    $is_svn = true;
 
     $vcs = $repository->getVersionControlSystem();
     $callsign = $repository->getCallsign();
 
-    if ($repository->isHosted()) {
-      $this->skipPull(
-        pht(
-          'Repository "%s" is hosted, so Phabricator does not pull updates '.
-          'for it.',
-          $callsign));
-      return;
-    }
-
     switch ($vcs) {
       case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
-        // We never pull a local copy of Subversion repositories.
-        $this->skipPull(
-          pht(
-            "Repository '%s' is a Subversion repository, which does not ".
-            "require a local working copy to be pulled.",
-            $callsign));
-        return;
+        // We never pull a local copy of non-hosted Subversion repositories.
+        if (!$repository->isHosted()) {
+          $this->skipPull(
+            pht(
+              "Repository '%s' is a non-hosted Subversion repository, which ".
+              "does not require a local working copy to be pulled.",
+              $callsign));
+          return;
+        }
+        $is_svn = true;
+        break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
         $is_git = true;
         break;
       case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
         $is_hg = true;
         break;
       default:
         $this->abortPull(pht('Unknown VCS "%s"!', $vcs));
     }
 
     $callsign = $repository->getCallsign();
     $local_path = $repository->getLocalPath();
     if ($local_path === null) {
       $this->abortPull(
         pht(
           "No local path is configured for repository '%s'.",
           $callsign));
     }
 
     try {
       $dirname = dirname($local_path);
       if (!Filesystem::pathExists($dirname)) {
         Filesystem::createDirectory($dirname, 0755, $recursive = true);
       }
 
       if (!Filesystem::pathExists($local_path)) {
         $this->logPull(
           pht(
             "Creating a new working copy for repository '%s'.",
             $callsign));
         if ($is_git) {
           $this->executeGitCreate();
-        } else {
+        } else if ($is_hg) {
           $this->executeMercurialCreate();
+        } else {
+          $this->executeSubversionCreate();
         }
       } else {
-        $this->logPull(
-          pht(
-            "Updating the working copy for repository '%s'.",
-            $callsign));
-        if ($is_git) {
-          $this->executeGitUpdate();
+        if ($repository->isHosted()) {
+          $this->logPull(
+            pht(
+              "Repository '%s' is hosted, so Phabricator does not pull ".
+              "updates for it.",
+              $callsign));
         } else {
-          $this->executeMercurialUpdate();
+          $this->logPull(
+            pht(
+              "Updating the working copy for repository '%s'.",
+              $callsign));
+          if ($is_git) {
+            $this->executeGitUpdate();
+          } else {
+            $this->executeMercurialUpdate();
+          }
         }
       }
     } catch (Exception $ex) {
       $this->abortPull(
         pht('Pull of "%s" failed: %s', $callsign, $ex->getMessage()),
         $ex);
     }
 
     $this->donePull();
 
     return $this;
   }
 
   private function skipPull($message) {
-    $this->updateRepositoryInitStatus(null);
     $this->log('%s', $message);
+    $this->donePull();
   }
 
   private function abortPull($message, Exception $ex = null) {
     $code_error = PhabricatorRepositoryStatusMessage::CODE_ERROR;
     $this->updateRepositoryInitStatus($code_error, $message);
     if ($ex) {
       throw $ex;
     } else {
       throw new Exception($message);
     }
   }
 
   private function logPull($message) {
     $code_working = PhabricatorRepositoryStatusMessage::CODE_WORKING;
     $this->updateRepositoryInitStatus($code_working, $message);
     $this->log('%s', $message);
   }
 
   private function donePull() {
     $code_okay = PhabricatorRepositoryStatusMessage::CODE_OKAY;
     $this->updateRepositoryInitStatus($code_okay);
   }
 
   private function updateRepositoryInitStatus($code, $message = null) {
     $this->getRepository()->writeStatusMessage(
       PhabricatorRepositoryStatusMessage::TYPE_INIT,
       $code,
       array(
         'message' => $message
       ));
   }
 
 
 /* -(  Pulling Git Working Copies  )----------------------------------------- */
 
 
   /**
    * @task git
    */
   private function executeGitCreate() {
     $repository = $this->getRepository();
 
-    $repository->execxRemoteCommand(
-      'clone --bare %s %s',
-      $repository->getRemoteURI(),
-      rtrim($repository->getLocalPath(), '/'));
+    $path = rtrim($repository->getLocalPath(), '/');
+
+    if ($repository->isHosted()) {
+      $repository->execxRemoteCommand(
+        'init --bare -- %s',
+        $path);
+    } else {
+      $repository->execxRemoteCommand(
+        'clone --bare -- %s %s',
+        $repository->getRemoteURI(),
+        $path);
+    }
   }
 
 
   /**
    * @task git
    */
   private function executeGitUpdate() {
     $repository = $this->getRepository();
 
     list($err, $stdout) = $repository->execLocalCommand(
       'rev-parse --show-toplevel');
 
     $message = null;
     $path = $repository->getLocalPath();
     if ($err) {
       // Try to raise a more tailored error message in the more common case
       // of the user creating an empty directory. (We could try to remove it,
       // but might not be able to, and it's much simpler to raise a good
       // message than try to navigate those waters.)
       if (is_dir($path)) {
         $files = Filesystem::listDirectory($path, $include_hidden = true);
         if (!$files) {
           $message =
             "Expected to find a git repository at '{$path}', but there ".
             "is an empty directory there. Remove the directory: the daemon ".
             "will run 'git clone' for you.";
         } else {
           $message =
             "Expected to find a git repository at '{$path}', but there is ".
             "a non-repository directory (with other stuff in it) there. Move ".
             "or remove this directory (or reconfigure the repository to use a ".
             "different directory), and then either clone a repository ".
             "yourself or let the daemon do it.";
         }
       } else if (is_file($path)) {
         $message =
           "Expected to find a git repository at '{$path}', but there is a ".
           "file there instead. Remove it and let the daemon clone a ".
           "repository for you.";
       } else {
         $message =
           "Expected to find a git repository at '{$path}', but did not.";
       }
     } else {
       $repo_path = rtrim($stdout, "\n");
 
       if (empty($repo_path)) {
         // This can mean one of two things: we're in a bare repository, or
         // we're inside a git repository inside another git repository. Since
         // the first is dramatically more likely now that we perform bare
         // clones and I don't have a great way to test for the latter, assume
         // we're OK.
       } else if (!Filesystem::pathsAreEquivalent($repo_path, $path)) {
         $err = true;
         $message =
           "Expected to find repo at '{$path}', but the actual ".
           "git repository root for this directory is '{$repo_path}'. ".
           "Something is misconfigured. The repository's 'Local Path' should ".
           "be set to some place where the daemon can check out a working ".
           "copy, and should not be inside another git repository.";
       }
     }
 
     if ($err && $this->canDestroyWorkingCopy($path)) {
       phlog("Repository working copy at '{$path}' failed sanity check; ".
             "destroying and re-cloning. {$message}");
       Filesystem::remove($path);
       $this->executeGitCreate();
     } else if ($err) {
       throw new Exception($message);
     }
 
     $retry = false;
     do {
       // This is a local command, but needs credentials.
       if ($repository->isWorkingCopyBare()) {
         // For bare working copies, we need this magic incantation.
         $future = $repository->getRemoteCommandFuture(
           'fetch origin %s --prune',
           '+refs/heads/*:refs/heads/*');
       } else {
         $future = $repository->getRemoteCommandFuture(
           'fetch --all --prune');
       }
 
       $future->setCWD($path);
       list($err, $stdout, $stderr) = $future->resolve();
 
       if ($err && !$retry && $this->canDestroyWorkingCopy($path)) {
         $retry = true;
         // Fix remote origin url if it doesn't match our configuration
         $origin_url = $repository->execLocalCommand(
           'config --get remote.origin.url');
         $remote_uri = $repository->getDetail('remote-uri');
         if ($origin_url != $remote_uri) {
           $repository->execLocalCommand(
             'remote set-url origin %s',
             $remote_uri);
         }
       } else if ($err) {
         throw new Exception(
           "git fetch failed with error #{$err}:\n".
           "stdout:{$stdout}\n\n".
           "stderr:{$stderr}\n");
       } else {
         $retry = false;
       }
     } while ($retry);
   }
 
 
 /* -(  Pulling Mercurial Working Copies  )----------------------------------- */
 
 
   /**
    * @task hg
    */
   private function executeMercurialCreate() {
     $repository = $this->getRepository();
 
-    $repository->execxRemoteCommand(
-      'clone %s %s',
-      $repository->getRemoteURI(),
-      rtrim($repository->getLocalPath(), '/'));
+    $path = rtrim($repository->getLocalPath(), '/');
+
+    if ($repository->isHosted()) {
+      $repository->execxRemoteCommand(
+        'init -- %s',
+        $path);
+    } else {
+      $repository->execxRemoteCommand(
+        'clone -- %s %s',
+        $repository->getRemoteURI(),
+        $path);
+    }
   }
 
 
   /**
    * @task hg
    */
   private function executeMercurialUpdate() {
     $repository = $this->getRepository();
     $path = $repository->getLocalPath();
 
     // This is a local command, but needs credentials.
     $future = $repository->getRemoteCommandFuture('pull -u');
     $future->setCWD($path);
 
     try {
       $future->resolvex();
     } catch (CommandException $ex) {
       $err = $ex->getError();
       $stdout = $ex->getStdOut();
 
       // NOTE: Between versions 2.1 and 2.1.1, Mercurial changed the behavior
       // of "hg pull" to return 1 in case of a successful pull with no changes.
       // This behavior has been reverted, but users who updated between Feb 1,
       // 2012 and Mar 1, 2012 will have the erroring version. Do a dumb test
       // against stdout to check for this possibility.
       // See: https://github.com/facebook/phabricator/issues/101/
 
       // NOTE: Mercurial has translated versions, which translate this error
       // string. In a translated version, the string will be something else,
       // like "aucun changement trouve". There didn't seem to be an easy way
       // to handle this (there are hard ways but this is not a common problem
       // and only creates log spam, not application failures). Assume English.
 
       // TODO: Remove this once we're far enough in the future that deployment
       // of 2.1 is exceedingly rare?
       if ($err == 1 && preg_match('/no changes found/', $stdout)) {
         return;
       } else {
         throw $ex;
       }
     }
   }
 
 
+/* -(  Pulling Subversion Working Copies  )---------------------------------- */
+
+
+  /**
+   * @task svn
+   */
+  private function executeSubversionCreate() {
+    $repository = $this->getRepository();
+
+    $path = rtrim($repository->getLocalPath(), '/');
+    execx('svnadmin create -- %s', $path);
+  }
+
+
 /* -(  Internals  )---------------------------------------------------------- */
 
 
   private function canDestroyWorkingCopy($path) {
     $default_path = PhabricatorEnv::getEnvConfig(
       'repository.default-local-path');
     return Filesystem::isDescendant($path, $default_path);
   }
 
 }
diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
index 1db2a5cc41..2174bdbadf 100644
--- a/src/applications/repository/storage/PhabricatorRepository.php
+++ b/src/applications/repository/storage/PhabricatorRepository.php
@@ -1,939 +1,953 @@
 <?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() {
     $vcs = $this->getVersionControlSystem();
     if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) {
       throw new Exception("Not a subversion repository!");
     }
 
-    $uri = $this->getDetail('remote-uri');
+    if ($this->isHosted()) {
+      $uri = 'file://'.$this->getLocalPath();
+    } else {
+      $uri = $this->getDetail('remote-uri');
+    }
+
     $subpath = $this->getDetail('svn-subpath');
+    if ($subpath) {
+      $subpath = '/'.ltrim($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, ... */) {
     $this->assertLocalExists();
 
     $args = func_get_args();
     $args = $this->formatLocalCommand($args);
     return call_user_func_array('exec_manual', $args);
   }
 
   public function execxLocalCommand($pattern /* , $arg, ... */) {
     $this->assertLocalExists();
 
     $args = func_get_args();
     $args = $this->formatLocalCommand($args);
     return call_user_func_array('execx', $args);
   }
 
   public function getLocalCommandFuture($pattern /* , $arg, ... */) {
     $this->assertLocalExists();
 
     $args = func_get_args();
     $args = $this->formatLocalCommand($args);
     return newv('ExecFuture', $args);
   }
 
   public function passthruLocalCommand($pattern /* , $arg, ... */) {
     $this->assertLocalExists();
 
     $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 %P -i %P',
               new PhutilOpaqueEnvelope($this->getSSHLogin()),
               new PhutilOpaqueEnvelope($this->getSSHKeyfile())));
           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);
           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())));
           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 %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:
           $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->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
    */
   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() {
+    if ($this->isHosted()) {
+      return false;
+    }
+
     $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() {
+    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() {
     $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() {
-    switch ($this->getVersionControlSystem()) {
-      case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
-        if (!$this->isHosted()) {
-          // For non-hosted SVN repositories, we don't expect a local directory
-          // to exist.
-          return;
-        }
-        break;
+    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;
   }
 
 }