<commit_before><?php

/**
 * @task uri        Repository URI Management
 * @task autoclose  Autoclose
 */
final class PhabricatorRepository extends PhabricatorRepositoryDAO
  implements
    PhabricatorApplicationTransactionInterface,
    PhabricatorPolicyInterface,
    PhabricatorFlaggableInterface,
    PhabricatorMarkupInterface,
    PhabricatorDestructibleInterface,
    PhabricatorProjectInterface,
    PhabricatorSpacesInterface {

  /**
   * 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 TABLE_PARENTS = 'repository_parents';
  const TABLE_COVERAGE = 'repository_coverage';

  const SERVE_OFF = 'off';
  const SERVE_READONLY = 'readonly';
  const SERVE_READWRITE = 'readwrite';

  const BECAUSE_REPOSITORY_IMPORTING = 'auto/importing';
  const BECAUSE_AUTOCLOSE_DISABLED = 'auto/disabled';
  const BECAUSE_NOT_ON_AUTOCLOSE_BRANCH = 'auto/nobranch';
  const BECAUSE_BRANCH_UNTRACKED = 'auto/notrack';
  const BECAUSE_BRANCH_NOT_AUTOCLOSE = 'auto/noclose';
  const BECAUSE_AUTOCLOSE_FORCED = 'auto/forced';

  protected $name;
  protected $callsign;
  protected $uuid;
  protected $viewPolicy;
  protected $editPolicy;
  protected $pushPolicy;

  protected $versionControlSystem;
  protected $details = array();
  protected $credentialPHID;
  protected $almanacServicePHID;
  protected $spacePHID;

  private $commitCount = self::ATTACHABLE;
  private $mostRecentCommit = self::ATTACHABLE;
  private $projectPHIDs = self::ATTACHABLE;

  public static function initializeNewRepository(PhabricatorUser $actor) {
    $app = id(new PhabricatorApplicationQuery())
      ->setViewer($actor)
      ->withClasses(array('PhabricatorDiffusionApplication'))
      ->executeOne();

    $view_policy = $app->getPolicy(DiffusionDefaultViewCapability::CAPABILITY);
    $edit_policy = $app->getPolicy(DiffusionDefaultEditCapability::CAPABILITY);
    $push_policy = $app->getPolicy(DiffusionDefaultPushCapability::CAPABILITY);

    $repository = id(new PhabricatorRepository())
      ->setViewPolicy($view_policy)
      ->setEditPolicy($edit_policy)
      ->setPushPolicy($push_policy)
      ->setSpacePHID($actor->getDefaultSpacePHID());

    // Put the repository in "Importing" mode until we finish
    // parsing it.
    $repository->setDetail('importing', true);

    return $repository;
  }

  protected function getConfiguration() {
    return array(
      self::CONFIG_AUX_PHID => true,
      self::CONFIG_SERIALIZATION => array(
        'details' => self::SERIALIZATION_JSON,
      ),
      self::CONFIG_COLUMN_SCHEMA => array(
        'name' => 'sort255',
        'callsign' => 'sort32',
        'versionControlSystem' => 'text32',
        'uuid' => 'text64?',
        'pushPolicy' => 'policy',
        'credentialPHID' => 'phid?',
        'almanacServicePHID' => 'phid?',
      ),
      self::CONFIG_KEY_SCHEMA => array(
        'key_phid' => null,
        'phid' => array(
          'columns' => array('phid'),
          'unique' => true,
        ),
        'callsign' => array(
          'columns' => array('callsign'),
          'unique' => true,
        ),
        'key_name' => array(
          'columns' => array('name(128)'),
        ),
        'key_vcs' => array(
          'columns' => array('versionControlSystem'),
        ),
      ),
    ) + parent::getConfiguration();
  }

  public function generatePHID() {
    return PhabricatorPHID::generateNewPHID(
      PhabricatorRepositoryRepositoryPHIDType::TYPECONST);
  }

  public function toDictionary() {
    return array(
      'id'          => $this->getID(),
      'name'        => $this->getName(),
      'phid'        => $this->getPHID(),
      'callsign'    => $this->getCallsign(),
      'monogram'    => $this->getMonogram(),
      'vcs'         => $this->getVersionControlSystem(),
      'uri'         => PhabricatorEnv::getProductionURI($this->getURI()),
      'remoteURI'   => (string)$this->getRemoteURI(),
      'description' => $this->getDetail('description'),
      'isActive'    => $this->isTracked(),
      'isHosted'    => $this->isHosted(),
      'isImporting' => $this->isImporting(),
      'encoding'    => $this->getDetail('encoding'),
      'staging' => array(
        'supported' => $this->supportsStaging(),
        'prefix' => 'phabricator',
        'uri' => $this->getStagingURI(),
      ),
    );
  }

  public function getMonogram() {
    return 'r'.$this->getCallsign();
  }

  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($commit = null) {
    $subpath = $this->getDetail('svn-subpath');
    if (!strlen($subpath)) {
      $subpath = null;
    }
    return $this->getSubversionPathURI($subpath, $commit);
  }

  public function getSubversionPathURI($path = null, $commit = null) {
    $vcs = $this->getVersionControlSystem();
    if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) {
      throw new Exception(pht('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;
  }

  public function attachProjectPHIDs(array $project_phids) {
    $this->projectPHIDs = $project_phids;
    return $this;
  }

  public function getProjectPHIDs() {
    return $this->assertAttached($this->projectPHIDs);
  }


  /**
   * Get the name of the directory this repository should clone or checkout
   * into. For example, if the repository name is "Example Repository", a
   * reasonable name might be "example-repository". This is used to help users
   * get reasonable results when cloning repositories, since they generally do
   * not want to clone into directories called "X/" or "Example Repository/".
   *
   * @return string
   */
  public function getCloneName() {
    $name = $this->getDetail('clone-name');

    // Make some reasonable effort to produce reasonable default directory
    // names from repository names.
    if (!strlen($name)) {
      $name = $this->getName();
      $name = phutil_utf8_strtolower($name);
      $name = preg_replace('@[/ -:]+@', '-', $name);
      $name = trim($name, '-');
      if (!strlen($name)) {
        $name = $this->getCallsign();
      }
    }

    return $name;
  }


/* -(  Remote Command Execution  )------------------------------------------- */


  public function execRemoteCommand($pattern /* , $arg, ... */) {
    $args = func_get_args();
    return $this->newRemoteCommandFuture($args)->resolve();
  }

  public function execxRemoteCommand($pattern /* , $arg, ... */) {
    $args = func_get_args();
    return $this->newRemoteCommandFuture($args)->resolvex();
  }

  public function getRemoteCommandFuture($pattern /* , $arg, ... */) {
    $args = func_get_args();
    return $this->newRemoteCommandFuture($args);
  }

  public function passthruRemoteCommand($pattern /* , $arg, ... */) {
    $args = func_get_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;
  }


/* -(  Local Command Execution  )-------------------------------------------- */


  public function execLocalCommand($pattern /* , $arg, ... */) {
    $args = func_get_args();
    return $this->newLocalCommandFuture($args)->resolve();
  }

  public function execxLocalCommand($pattern /* , $arg, ... */) {
    $args = func_get_args();
    return $this->newLocalCommandFuture($args)->resolvex();
  }

  public function getLocalCommandFuture($pattern /* , $arg, ... */) {
    $args = func_get_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();

    $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;
  }


/* -(  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 "en_US.UTF-8", 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' => 'en_US.UTF-8',

      // Propagate PHABRICATOR_ENV explicitly. For discussion, see T4155.
      'PHABRICATOR_ENV' => PhabricatorEnv::getSelectedEnvironmentName(),
    );

    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(pht('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_CREDENTIAL'] = $this->getCredentialPHID();
      switch ($this->getVersionControlSystem()) {
        case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
          // Force SVN to use `bin/ssh-connect`.
          $env['SVN_SSH'] = $this->getSSHWrapper();
          break;
        case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
          // Force Git to use `bin/ssh-connect`.
          $env['GIT_SSH'] = $this->getSSHWrapper();
          break;
        case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
          // 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(pht('Unrecognized version control system.'));
      }
    }

    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() || $this->shouldUseSVNProtocol()) {
          $flags = array();
          $flag_args = array();
          $flags[] = '--non-interactive';
          $flags[] = '--no-auth-cache';
          if ($this->shouldUseHTTP()) {
            $flags[] = '--trust-server-cert';
          }

          $credential_phid = $this->getCredentialPHID();
          if ($credential_phid) {
            $key = PassphrasePasswordKey::loadFromPHID(
              $credential_phid,
              PhabricatorUser::getOmnipotentUser());
            $flags[] = '--username %P';
            $flags[] = '--password %P';
            $flag_args[] = $key->getUsernameEnvelope();
            $flag_args[] = $key->getPasswordEnvelope();
          }

          $flags = implode(' ', $flags);
          $pattern = "svn {$flags} {$pattern}";
          $args = array_mergev(array($flag_args, $args));
        } else {
          $pattern = "svn --non-interactive {$pattern}";
        }
        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(pht('Unrecognized version control system.'));
    }

    array_unshift($args, $pattern);

    return $args;
  }

  private function formatLocalCommand(array $args) {
    $pattern = $args[0];
    $args = array_slice($args, 1);

    switch ($this->getVersionControlSystem()) {
      case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
        $pattern = "svn --non-interactive {$pattern}";
        break;
      case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
        $pattern = "git {$pattern}";
        break;
      case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
        $pattern = "hg {$pattern}";
        break;
      default:
        throw new Exception(pht('Unrecognized version control system.'));
    }

    array_unshift($args, $pattern);

    return $args;
  }

  /**
   * Sanitize output of an `hg` command invoked with the `--debug` flag to make
   * it usable.
   *
   * @param string Output from `hg --debug ...`
   * @return string Usable output.
   */
  public static function filterMercurialDebugOutput($stdout) {
    // When hg commands are run with `--debug` and some config file isn't
    // trusted, Mercurial prints out a warning to stdout, twice, after Feb 2011.
    //
    // http://selenic.com/pipermail/mercurial-devel/2011-February/028541.html
    //
    // After Jan 2015, it may also fail to write to a revision branch cache.

    $ignore = array(
      'ignoring untrusted configuration option',
      "couldn't write revision branch cache:",
    );

    foreach ($ignore as $key => $pattern) {
      $ignore[$key] = preg_quote($pattern, '/');
    }

    $ignore = '('.implode('|', $ignore).')';

    $lines = preg_split('/(?<=\n)/', $stdout);
    $regex = '/'.$ignore.'.*\n$/';

    foreach ($lines as $key => $line) {
      $lines[$key] = preg_replace($regex, '', $line);
    }

    return implode('', $lines);
  }

  public function getURI() {
    return '/diffusion/'.$this->getCallsign().'/';
  }

  public function getNormalizedPath() {
    $uri = (string)$this->getCloneURIObject();

    switch ($this->getVersionControlSystem()) {
      case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
        $normalized_uri = new PhabricatorRepositoryURINormalizer(
          PhabricatorRepositoryURINormalizer::TYPE_GIT,
          $uri);
        break;
      case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
        $normalized_uri = new PhabricatorRepositoryURINormalizer(
          PhabricatorRepositoryURINormalizer::TYPE_SVN,
          $uri);
        break;
      case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
        $normalized_uri = new PhabricatorRepositoryURINormalizer(
          PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL,
          $uri);
        break;
      default:
        throw new Exception(pht('Unrecognized version control system.'));
    }

    return $normalized_uri->getNormalizedPath();
  }

  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) {
      // If this VCS doesn't use filters, pass everything through.
      return true;
    }


    $filter = $this->getDetail($filter_key, array());

    // If there's no filter set, let everything through.
    if (!$filter) {
      return true;
    }

    // If this branch isn't literally named `regexp(...)`, and it's in the
    // filter list, let it through.
    if (isset($filter[$branch])) {
      if (self::extractBranchRegexp($branch) === null) {
        return true;
      }
    }

    // If the branch matches a regexp, let it through.
    foreach ($filter as $pattern => $ignored) {
      $regexp = self::extractBranchRegexp($pattern);
      if ($regexp !== null) {
        if (preg_match($regexp, $branch)) {
          return true;
        }
      }
    }

    // Nothing matched, so filter this branch out.
    return false;
  }

  public static function extractBranchRegexp($pattern) {
    $matches = null;
    if (preg_match('/^regexp\\((.*)\\)\z/', $pattern, $matches)) {
      return $matches[1];
    }
    return null;
  }

  public function shouldTrackBranch($branch) {
    return $this->isBranchInFilter($branch, 'branch-filter');
  }

  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);
  }


  /**
   * Should this repository publish feed, notifications, audits, and email?
   *
   * We do not publish information about repositories during initial import,
   * or if the repository has been set not to publish.
   */
  public function shouldPublish() {
    if ($this->isImporting()) {
      return false;
    }

    if ($this->getDetail('disable-herald')) {
      return false;
    }

    return true;
  }


/* -(  Autoclose  )---------------------------------------------------------- */
<commit_msg>Fix issue with "Publish/Notify" handling in repositories<commit_after><?php

/**
 * @task uri        Repository URI Management
 * @task autoclose  Autoclose
 */
final class PhabricatorRepository extends PhabricatorRepositoryDAO
  implements
    PhabricatorApplicationTransactionInterface,
    PhabricatorPolicyInterface,
    PhabricatorFlaggableInterface,
    PhabricatorMarkupInterface,
    PhabricatorDestructibleInterface,
    PhabricatorProjectInterface,
    PhabricatorSpacesInterface {

  /**
   * 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 TABLE_PARENTS = 'repository_parents';
  const TABLE_COVERAGE = 'repository_coverage';

  const SERVE_OFF = 'off';
  const SERVE_READONLY = 'readonly';
  const SERVE_READWRITE = 'readwrite';

  const BECAUSE_REPOSITORY_IMPORTING = 'auto/importing';
  const BECAUSE_AUTOCLOSE_DISABLED = 'auto/disabled';
  const BECAUSE_NOT_ON_AUTOCLOSE_BRANCH = 'auto/nobranch';
  const BECAUSE_BRANCH_UNTRACKED = 'auto/notrack';
  const BECAUSE_BRANCH_NOT_AUTOCLOSE = 'auto/noclose';
  const BECAUSE_AUTOCLOSE_FORCED = 'auto/forced';

  protected $name;
  protected $callsign;
  protected $uuid;
  protected $viewPolicy;
  protected $editPolicy;
  protected $pushPolicy;

  protected $versionControlSystem;
  protected $details = array();
  protected $credentialPHID;
  protected $almanacServicePHID;
  protected $spacePHID;

  private $commitCount = self::ATTACHABLE;
  private $mostRecentCommit = self::ATTACHABLE;
  private $projectPHIDs = self::ATTACHABLE;

  public static function initializeNewRepository(PhabricatorUser $actor) {
    $app = id(new PhabricatorApplicationQuery())
      ->setViewer($actor)
      ->withClasses(array('PhabricatorDiffusionApplication'))
      ->executeOne();

    $view_policy = $app->getPolicy(DiffusionDefaultViewCapability::CAPABILITY);
    $edit_policy = $app->getPolicy(DiffusionDefaultEditCapability::CAPABILITY);
    $push_policy = $app->getPolicy(DiffusionDefaultPushCapability::CAPABILITY);

    $repository = id(new PhabricatorRepository())
      ->setViewPolicy($view_policy)
      ->setEditPolicy($edit_policy)
      ->setPushPolicy($push_policy)
      ->setSpacePHID($actor->getDefaultSpacePHID());

    // Put the repository in "Importing" mode until we finish
    // parsing it.
    $repository->setDetail('importing', true);

    return $repository;
  }

  protected function getConfiguration() {
    return array(
      self::CONFIG_AUX_PHID => true,
      self::CONFIG_SERIALIZATION => array(
        'details' => self::SERIALIZATION_JSON,
      ),
      self::CONFIG_COLUMN_SCHEMA => array(
        'name' => 'sort255',
        'callsign' => 'sort32',
        'versionControlSystem' => 'text32',
        'uuid' => 'text64?',
        'pushPolicy' => 'policy',
        'credentialPHID' => 'phid?',
        'almanacServicePHID' => 'phid?',
      ),
      self::CONFIG_KEY_SCHEMA => array(
        'key_phid' => null,
        'phid' => array(
          'columns' => array('phid'),
          'unique' => true,
        ),
        'callsign' => array(
          'columns' => array('callsign'),
          'unique' => true,
        ),
        'key_name' => array(
          'columns' => array('name(128)'),
        ),
        'key_vcs' => array(
          'columns' => array('versionControlSystem'),
        ),
      ),
    ) + parent::getConfiguration();
  }

  public function generatePHID() {
    return PhabricatorPHID::generateNewPHID(
      PhabricatorRepositoryRepositoryPHIDType::TYPECONST);
  }

  public function toDictionary() {
    return array(
      'id'          => $this->getID(),
      'name'        => $this->getName(),
      'phid'        => $this->getPHID(),
      'callsign'    => $this->getCallsign(),
      'monogram'    => $this->getMonogram(),
      'vcs'         => $this->getVersionControlSystem(),
      'uri'         => PhabricatorEnv::getProductionURI($this->getURI()),
      'remoteURI'   => (string)$this->getRemoteURI(),
      'description' => $this->getDetail('description'),
      'isActive'    => $this->isTracked(),
      'isHosted'    => $this->isHosted(),
      'isImporting' => $this->isImporting(),
      'encoding'    => $this->getDetail('encoding'),
      'staging' => array(
        'supported' => $this->supportsStaging(),
        'prefix' => 'phabricator',
        'uri' => $this->getStagingURI(),
      ),
    );
  }

  public function getMonogram() {
    return 'r'.$this->getCallsign();
  }

  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($commit = null) {
    $subpath = $this->getDetail('svn-subpath');
    if (!strlen($subpath)) {
      $subpath = null;
    }
    return $this->getSubversionPathURI($subpath, $commit);
  }

  public function getSubversionPathURI($path = null, $commit = null) {
    $vcs = $this->getVersionControlSystem();
    if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) {
      throw new Exception(pht('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;
  }

  public function attachProjectPHIDs(array $project_phids) {
    $this->projectPHIDs = $project_phids;
    return $this;
  }

  public function getProjectPHIDs() {
    return $this->assertAttached($this->projectPHIDs);
  }


  /**
   * Get the name of the directory this repository should clone or checkout
   * into. For example, if the repository name is "Example Repository", a
   * reasonable name might be "example-repository". This is used to help users
   * get reasonable results when cloning repositories, since they generally do
   * not want to clone into directories called "X/" or "Example Repository/".
   *
   * @return string
   */
  public function getCloneName() {
    $name = $this->getDetail('clone-name');

    // Make some reasonable effort to produce reasonable default directory
    // names from repository names.
    if (!strlen($name)) {
      $name = $this->getName();
      $name = phutil_utf8_strtolower($name);
      $name = preg_replace('@[/ -:]+@', '-', $name);
      $name = trim($name, '-');
      if (!strlen($name)) {
        $name = $this->getCallsign();
      }
    }

    return $name;
  }


/* -(  Remote Command Execution  )------------------------------------------- */


  public function execRemoteCommand($pattern /* , $arg, ... */) {
    $args = func_get_args();
    return $this->newRemoteCommandFuture($args)->resolve();
  }

  public function execxRemoteCommand($pattern /* , $arg, ... */) {
    $args = func_get_args();
    return $this->newRemoteCommandFuture($args)->resolvex();
  }

  public function getRemoteCommandFuture($pattern /* , $arg, ... */) {
    $args = func_get_args();
    return $this->newRemoteCommandFuture($args);
  }

  public function passthruRemoteCommand($pattern /* , $arg, ... */) {
    $args = func_get_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;
  }


/* -(  Local Command Execution  )-------------------------------------------- */


  public function execLocalCommand($pattern /* , $arg, ... */) {
    $args = func_get_args();
    return $this->newLocalCommandFuture($args)->resolve();
  }

  public function execxLocalCommand($pattern /* , $arg, ... */) {
    $args = func_get_args();
    return $this->newLocalCommandFuture($args)->resolvex();
  }

  public function getLocalCommandFuture($pattern /* , $arg, ... */) {
    $args = func_get_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();

    $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;
  }


/* -(  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 "en_US.UTF-8", 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' => 'en_US.UTF-8',

      // Propagate PHABRICATOR_ENV explicitly. For discussion, see T4155.
      'PHABRICATOR_ENV' => PhabricatorEnv::getSelectedEnvironmentName(),
    );

    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(pht('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_CREDENTIAL'] = $this->getCredentialPHID();
      switch ($this->getVersionControlSystem()) {
        case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
          // Force SVN to use `bin/ssh-connect`.
          $env['SVN_SSH'] = $this->getSSHWrapper();
          break;
        case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
          // Force Git to use `bin/ssh-connect`.
          $env['GIT_SSH'] = $this->getSSHWrapper();
          break;
        case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
          // 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(pht('Unrecognized version control system.'));
      }
    }

    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() || $this->shouldUseSVNProtocol()) {
          $flags = array();
          $flag_args = array();
          $flags[] = '--non-interactive';
          $flags[] = '--no-auth-cache';
          if ($this->shouldUseHTTP()) {
            $flags[] = '--trust-server-cert';
          }

          $credential_phid = $this->getCredentialPHID();
          if ($credential_phid) {
            $key = PassphrasePasswordKey::loadFromPHID(
              $credential_phid,
              PhabricatorUser::getOmnipotentUser());
            $flags[] = '--username %P';
            $flags[] = '--password %P';
            $flag_args[] = $key->getUsernameEnvelope();
            $flag_args[] = $key->getPasswordEnvelope();
          }

          $flags = implode(' ', $flags);
          $pattern = "svn {$flags} {$pattern}";
          $args = array_mergev(array($flag_args, $args));
        } else {
          $pattern = "svn --non-interactive {$pattern}";
        }
        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(pht('Unrecognized version control system.'));
    }

    array_unshift($args, $pattern);

    return $args;
  }

  private function formatLocalCommand(array $args) {
    $pattern = $args[0];
    $args = array_slice($args, 1);

    switch ($this->getVersionControlSystem()) {
      case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
        $pattern = "svn --non-interactive {$pattern}";
        break;
      case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
        $pattern = "git {$pattern}";
        break;
      case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
        $pattern = "hg {$pattern}";
        break;
      default:
        throw new Exception(pht('Unrecognized version control system.'));
    }

    array_unshift($args, $pattern);

    return $args;
  }

  /**
   * Sanitize output of an `hg` command invoked with the `--debug` flag to make
   * it usable.
   *
   * @param string Output from `hg --debug ...`
   * @return string Usable output.
   */
  public static function filterMercurialDebugOutput($stdout) {
    // When hg commands are run with `--debug` and some config file isn't
    // trusted, Mercurial prints out a warning to stdout, twice, after Feb 2011.
    //
    // http://selenic.com/pipermail/mercurial-devel/2011-February/028541.html
    //
    // After Jan 2015, it may also fail to write to a revision branch cache.

    $ignore = array(
      'ignoring untrusted configuration option',
      "couldn't write revision branch cache:",
    );

    foreach ($ignore as $key => $pattern) {
      $ignore[$key] = preg_quote($pattern, '/');
    }

    $ignore = '('.implode('|', $ignore).')';

    $lines = preg_split('/(?<=\n)/', $stdout);
    $regex = '/'.$ignore.'.*\n$/';

    foreach ($lines as $key => $line) {
      $lines[$key] = preg_replace($regex, '', $line);
    }

    return implode('', $lines);
  }

  public function getURI() {
    return '/diffusion/'.$this->getCallsign().'/';
  }

  public function getNormalizedPath() {
    $uri = (string)$this->getCloneURIObject();

    switch ($this->getVersionControlSystem()) {
      case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
        $normalized_uri = new PhabricatorRepositoryURINormalizer(
          PhabricatorRepositoryURINormalizer::TYPE_GIT,
          $uri);
        break;
      case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
        $normalized_uri = new PhabricatorRepositoryURINormalizer(
          PhabricatorRepositoryURINormalizer::TYPE_SVN,
          $uri);
        break;
      case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
        $normalized_uri = new PhabricatorRepositoryURINormalizer(
          PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL,
          $uri);
        break;
      default:
        throw new Exception(pht('Unrecognized version control system.'));
    }

    return $normalized_uri->getNormalizedPath();
  }

  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) {
      // If this VCS doesn't use filters, pass everything through.
      return true;
    }


    $filter = $this->getDetail($filter_key, array());

    // If there's no filter set, let everything through.
    if (!$filter) {
      return true;
    }

    // If this branch isn't literally named `regexp(...)`, and it's in the
    // filter list, let it through.
    if (isset($filter[$branch])) {
      if (self::extractBranchRegexp($branch) === null) {
        return true;
      }
    }

    // If the branch matches a regexp, let it through.
    foreach ($filter as $pattern => $ignored) {
      $regexp = self::extractBranchRegexp($pattern);
      if ($regexp !== null) {
        if (preg_match($regexp, $branch)) {
          return true;
        }
      }
    }

    // Nothing matched, so filter this branch out.
    return false;
  }

  public static function extractBranchRegexp($pattern) {
    $matches = null;
    if (preg_match('/^regexp\\((.*)\\)\z/', $pattern, $matches)) {
      return $matches[1];
    }
    return null;
  }

  public function shouldTrackBranch($branch) {
    return $this->isBranchInFilter($branch, 'branch-filter');
  }

  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);
  }


  /**
   * Should this repository publish feed, notifications, audits, and email?
   *
   * We do not publish information about repositories during initial import,
   * or if the repository has been set not to publish.
   */
  public function shouldPublish() {
    if ($this->isImporting()) {
      return false;
    }

    if ($this->getDetail('herald-disabled')) {
      return false;
    }

    return true;
  }


/* -(  Autoclose  )---------------------------------------------------------- */

{'fraction_non_alphanumeric': 0.14867463260541133, 'fraction_numerical': 0.002975781714965893, 'mean_word_length': 2.865422049194833, 'pattern_counts': {'":': 0, '<': 7, '<?xml version=': 0, '>': 347, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 2, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4625816', 'n_tokens_mistral': 13660, 'n_tokens_neox': 13217, 'n_words': 2844}