diff --git a/bin/ssh-connect b/bin/ssh-connect new file mode 120000 index 000000000..79f7c94d0 --- /dev/null +++ b/bin/ssh-connect @@ -0,0 +1 @@ +../scripts/ssh/ssh-connect.php \ No newline at end of file diff --git a/scripts/ssh/ssh-connect.php b/scripts/ssh/ssh-connect.php new file mode 100755 index 000000000..2f2877856 --- /dev/null +++ b/scripts/ssh/ssh-connect.php @@ -0,0 +1,71 @@ +#!/usr/bin/env php +<?php + +// This is a wrapper script for Git, Mercurial, and Subversion. It primarily +// serves to inject "-o StrictHostKeyChecking=no" into the SSH arguments. + +$root = dirname(dirname(dirname(__FILE__))); +require_once $root.'/scripts/__init_script__.php'; + +$target_name = getenv('PHABRICATOR_SSH_TARGET'); +if (!$target_name) { + throw new Exception(pht("No 'PHABRICATOR_SSH_TARGET' in environment!")); +} + +$repository = id(new PhabricatorRepositoryQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withCallsigns(array($target_name)) + ->executeOne(); +if (!$repository) { + throw new Exception(pht('No repository with callsign "%s"!', $target_name)); +} + +$pattern = array(); +$arguments = array(); + +$pattern[] = 'ssh'; + +$pattern[] = '-o'; +$pattern[] = 'StrictHostKeyChecking=no'; + +$login = $repository->getSSHLogin(); +if (strlen($login)) { + $pattern[] = '-l'; + $pattern[] = '%P'; + $arguments[] = new PhutilOpaqueEnvelope($login); +} + +$ssh_identity = null; + +$key = $repository->getDetail('ssh-key'); +$keyfile = $repository->getDetail('ssh-keyfile'); +if ($keyfile) { + $ssh_identity = $keyfile; +} else if ($key) { + $tmpfile = new TempFile('phabricator-repository-ssh-key'); + chmod($tmpfile, 0600); + Filesystem::writeFile($tmpfile, $key); + $ssh_identity = (string)$tmpfile; +} + +if ($ssh_identity) { + $pattern[] = '-i'; + $pattern[] = '%P'; + $arguments[] = new PhutilOpaqueEnvelope($keyfile); +} + +$pattern[] = '--'; + +$passthru_args = array_slice($argv, 1); +foreach ($passthru_args as $passthru_arg) { + $pattern[] = '%s'; + $arguments[] = $passthru_arg; +} + +$pattern = implode(' ', $pattern); +array_unshift($arguments, $pattern); + +$err = newv('PhutilExecPassthru', $arguments) + ->execute(); + +exit($err); diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index b2df31969..859c9eff7 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1,975 +1,1004 @@ <?php /** * @task uri Repository URI Management */ final class PhabricatorRepository extends PhabricatorRepositoryDAO implements PhabricatorPolicyInterface, PhabricatorFlaggableInterface, PhabricatorMarkupInterface { /** * Shortest hash we'll recognize in raw "a829f32" form. */ const MINIMUM_UNQUALIFIED_HASH = 7; /** * Shortest hash we'll recognize in qualified "rXab7ef2f8" form. */ const MINIMUM_QUALIFIED_HASH = 5; const TABLE_PATH = 'repository_path'; const TABLE_PATHCHANGE = 'repository_pathchange'; const TABLE_FILESYSTEM = 'repository_filesystem'; const TABLE_SUMMARY = 'repository_summary'; const TABLE_BADCOMMIT = 'repository_badcommit'; const TABLE_LINTMESSAGE = 'repository_lintmessage'; const SERVE_OFF = 'off'; const SERVE_READONLY = 'readonly'; const SERVE_READWRITE = 'readwrite'; protected $name; protected $callsign; protected $uuid; protected $viewPolicy; protected $editPolicy; protected $pushPolicy; protected $versionControlSystem; protected $details = array(); private $sshKeyfile; private $commitCount = self::ATTACHABLE; private $mostRecentCommit = self::ATTACHABLE; public static function initializeNewRepository(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) ->setViewer($actor) ->withClasses(array('PhabricatorApplicationDiffusion')) ->executeOne(); $view_policy = $app->getPolicy(DiffusionCapabilityDefaultView::CAPABILITY); $edit_policy = $app->getPolicy(DiffusionCapabilityDefaultEdit::CAPABILITY); $push_policy = $app->getPolicy(DiffusionCapabilityDefaultPush::CAPABILITY); return id(new PhabricatorRepository()) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy) ->setPushPolicy($push_policy); } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'details' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorRepositoryPHIDTypeRepository::TYPECONST); } public function toDictionary() { return array( 'name' => $this->getName(), 'phid' => $this->getPHID(), 'callsign' => $this->getCallsign(), 'vcs' => $this->getVersionControlSystem(), 'uri' => PhabricatorEnv::getProductionURI($this->getURI()), 'remoteURI' => (string)$this->getPublicRemoteURI(), 'tracking' => $this->getDetail('tracking-enabled'), 'description' => $this->getDetail('description'), ); } public function getDetail($key, $default = null) { return idx($this->details, $key, $default); } public function getHumanReadableDetail($key, $default = null) { $value = $this->getDetail($key, $default); switch ($key) { case 'branch-filter': case 'close-commits-filter': $value = array_keys($value); $value = implode(', ', $value); break; } return $value; } public function setDetail($key, $value) { $this->details[$key] = $value; return $this; } public function attachCommitCount($count) { $this->commitCount = $count; return $this; } public function getCommitCount() { return $this->assertAttached($this->commitCount); } public function attachMostRecentCommit( PhabricatorRepositoryCommit $commit = null) { $this->mostRecentCommit = $commit; return $this; } public function getMostRecentCommit() { return $this->assertAttached($this->mostRecentCommit); } public function getDiffusionBrowseURIForPath( PhabricatorUser $user, $path, $line = null, $branch = null) { $drequest = DiffusionRequest::newFromDictionary( array( 'user' => $user, 'repository' => $this, 'path' => $path, 'branch' => $branch, )); return $drequest->generateURI( array( 'action' => 'browse', 'line' => $line, )); } public function getLocalPath() { return $this->getDetail('local-path'); } public function getSubversionBaseURI() { $subpath = $this->getDetail('svn-subpath'); if (!strlen($subpath)) { $subpath = null; } return $this->getSubversionPathURI($subpath); } public function getSubversionPathURI($path = null, $commit = null) { $vcs = $this->getVersionControlSystem(); if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) { throw new Exception("Not a subversion repository!"); } if ($this->isHosted()) { $uri = 'file://'.$this->getLocalPath(); } else { $uri = $this->getDetail('remote-uri'); } $uri = rtrim($uri, '/'); if (strlen($path)) { $path = rawurlencode($path); $path = str_replace('%2F', '/', $path); $uri = $uri.'/'.ltrim($path, '/'); } if ($path !== null || $commit !== null) { $uri .= '@'; } if ($commit !== null) { $uri .= $commit; } return $uri; } + +/* -( Remote Command Execution )------------------------------------------- */ + + public function execRemoteCommand($pattern /* , $arg, ... */) { $args = func_get_args(); - $args = $this->formatRemoteCommand($args); - return call_user_func_array('exec_manual', $args); + return $this->newRemoteCommandFuture($args)->resolve(); } public function execxRemoteCommand($pattern /* , $arg, ... */) { $args = func_get_args(); - $args = $this->formatRemoteCommand($args); - return call_user_func_array('execx', $args); + return $this->newRemoteCommandFuture($args)->resolvex(); } public function getRemoteCommandFuture($pattern /* , $arg, ... */) { $args = func_get_args(); - $args = $this->formatRemoteCommand($args); - return newv('ExecFuture', $args); + return $this->newRemoteCommandFuture($args); } public function passthruRemoteCommand($pattern /* , $arg, ... */) { $args = func_get_args(); - $args = $this->formatRemoteCommand($args); - return call_user_func_array('phutil_passthru', $args); + return $this->newRemoteCommandPassthru($args)->execute(); + } + + private function newRemoteCommandFuture(array $argv) { + $argv = $this->formatRemoteCommand($argv); + $future = newv('ExecFuture', $argv); + $future->setEnv($this->getRemoteCommandEnvironment()); + return $future; + } + + private function newRemoteCommandPassthru(array $argv) { + $argv = $this->formatRemoteCommand($argv); + $passthru = newv('PhutilExecPassthru', $argv); + $passthru->setEnv($this->getRemoteCommandEnvironment()); + return $passthru; } - public function execLocalCommand($pattern /* , $arg, ... */) { - $this->assertLocalExists(); +/* -( Local Command Execution )-------------------------------------------- */ + + + public function execLocalCommand($pattern /* , $arg, ... */) { $args = func_get_args(); - $args = $this->formatLocalCommand($args); - return call_user_func_array('exec_manual', $args); + return $this->newLocalCommandFuture($args)->resolve(); } public function execxLocalCommand($pattern /* , $arg, ... */) { - $this->assertLocalExists(); - $args = func_get_args(); - $args = $this->formatLocalCommand($args); - return call_user_func_array('execx', $args); + return $this->newLocalCommandFuture($args)->resolvex(); } public function getLocalCommandFuture($pattern /* , $arg, ... */) { - $this->assertLocalExists(); - $args = func_get_args(); - $args = $this->formatLocalCommand($args); - return newv('ExecFuture', $args); + return $this->newLocalCommandFuture($args); } public function passthruLocalCommand($pattern /* , $arg, ... */) { + $args = func_get_args(); + return $this->newLocalCommandPassthru($args)->execute(); + } + + private function newLocalCommandFuture(array $argv) { $this->assertLocalExists(); - $args = func_get_args(); - $args = $this->formatLocalCommand($args); - return call_user_func_array('phutil_passthru', $args); + $argv = $this->formatLocalCommand($argv); + $future = newv('ExecFuture', $argv); + $future->setEnv($this->getLocalCommandEnvironment()); + + if ($this->usesLocalWorkingCopy()) { + $future->setCWD($this->getLocalPath()); + } + + return $future; } + private function newLocalCommandPassthru(array $argv) { + $this->assertLocalExists(); + + $argv = $this->formatLocalCommand($argv); + $future = newv('PhutilExecPassthru', $argv); + $future->setEnv($this->getLocalCommandEnvironment()); + + if ($this->usesLocalWorkingCopy()) { + $future->setCWD($this->getLocalPath()); + } + + return $future; + } - private function formatRemoteCommand(array $args) { - $pattern = $args[0]; - $args = array_slice($args, 1); - $empty = $this->getEmptyReadableDirectoryPath(); +/* -( Command Infrastructure )--------------------------------------------- */ + + + private function getSSHWrapper() { + $root = dirname(phutil_get_library_root('phabricator')); + return $root.'/bin/ssh-connect'; + } + + private function getCommonCommandEnvironment() { + $env = array( + // NOTE: Force the language to "C", which overrides locale settings. + // This makes stuff print in English instead of, e.g., French, so we can + // parse the output of some commands, error messages, etc. + 'LANG' => 'C', + ); + + switch ($this->getVersionControlSystem()) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + // NOTE: See T2965. Some time after Git 1.7.5.4, Git started fataling if + // it can not read $HOME. For many users, $HOME points at /root (this + // seems to be a default result of Apache setup). Instead, explicitly + // point $HOME at a readable, empty directory so that Git looks for the + // config file it's after, fails to locate it, and moves on. This is + // really silly, but seems like the least damaging approach to + // mitigating the issue. + + $root = dirname(phutil_get_library_root('phabricator')); + $env['HOME'] = $root.'/support/empty/'; + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: + // NOTE: This overrides certain configuration, extensions, and settings + // which make Mercurial commands do random unusual things. + $env['HGPLAIN'] = 1; + break; + default: + throw new Exception("Unrecognized version control system."); + } + + return $env; + } + + private function getLocalCommandEnvironment() { + return $this->getCommonCommandEnvironment(); + } + + private function getRemoteCommandEnvironment() { + $env = $this->getCommonCommandEnvironment(); if ($this->shouldUseSSH()) { + // NOTE: This is read by `bin/ssh-connect`, and tells it which credentials + // to use. + $env['PHABRICATOR_SSH_TARGET'] = $this->getCallsign(); switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $pattern = "SVN_SSH=%s svn --non-interactive {$pattern}"; - array_unshift( - $args, - csprintf( - 'ssh -l %P -i %P', - new PhutilOpaqueEnvelope($this->getSSHLogin()), - new PhutilOpaqueEnvelope($this->getSSHKeyfile()))); + // Force SVN to use `bin/ssh-connect`. + $env['SVN_SSH'] = $this->getSSHWrapper(); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $command = call_user_func_array( - 'csprintf', - array_merge( - array( - "(ssh-add %P && HOME=%s git {$pattern})", - new PhutilOpaqueEnvelope($this->getSSHKeyfile()), - $empty, - ), - $args)); - $pattern = "ssh-agent sh -c %s"; - $args = array($command); + // Force Git to use `bin/ssh-connect`. + $env['GIT_SSH'] = $this->getSSHWrapper(); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $pattern = "hg --config ui.ssh=%s {$pattern}"; - array_unshift( - $args, - csprintf( - 'ssh -l %P -i %P', - new PhutilOpaqueEnvelope($this->getSSHLogin()), - new PhutilOpaqueEnvelope($this->getSSHKeyfile()))); + // We force Mercurial through `bin/ssh-connect` too, but it uses a + // command-line flag instead of an environmental variable. break; default: throw new Exception("Unrecognized version control system."); } - } else if ($this->shouldUseHTTP()) { - switch ($this->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + } + + return $env; + } + + private function formatRemoteCommand(array $args) { + $pattern = $args[0]; + $args = array_slice($args, 1); + + switch ($this->getVersionControlSystem()) { + case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + if ($this->shouldUseHTTP()) { $pattern = "svn ". "--non-interactive ". "--no-auth-cache ". "--trust-server-cert ". "--username %P ". "--password %P ". $pattern; array_unshift( $args, new PhutilOpaqueEnvelope($this->getDetail('http-login')), new PhutilOpaqueEnvelope($this->getDetail('http-pass'))); - break; - default: - throw new Exception( - "No support for HTTP Basic Auth in this version control system."); - } - } else if ($this->shouldUseSVNProtocol()) { - switch ($this->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $pattern = - "svn ". - "--non-interactive ". - "--no-auth-cache ". - "--username %P ". - "--password %P ". - $pattern; - array_unshift( - $args, - new PhutilOpaqueEnvelope($this->getDetail('http-login')), - new PhutilOpaqueEnvelope($this->getDetail('http-pass'))); - break; - default: - throw new Exception( - "SVN protocol is SVN only."); - } - } else { - switch ($this->getVersionControlSystem()) { - case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: + } else if ($this->shouldUseSVNProtocol()) { + $pattern = + "svn ". + "--non-interactive ". + "--no-auth-cache ". + "--username %P ". + "--password %P ". + $pattern; + array_unshift( + $args, + new PhutilOpaqueEnvelope($this->getDetail('http-login')), + new PhutilOpaqueEnvelope($this->getDetail('http-pass'))); + } else { $pattern = "svn --non-interactive {$pattern}"; - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $pattern = "HOME=%s git {$pattern}"; - array_unshift($args, $empty); - break; - case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: + } + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: + $pattern = "git {$pattern}"; + break; + case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: + if ($this->shouldUseSSH()) { + $pattern = "hg --config ui.ssh=%s {$pattern}"; + array_unshift( + $args, + $this->getSSHWrapper()); + } else { $pattern = "hg {$pattern}"; - break; - default: - throw new Exception("Unrecognized version control system."); - } + } + break; + default: + throw new Exception("Unrecognized version control system."); } array_unshift($args, $pattern); return $args; } private function formatLocalCommand(array $args) { $pattern = $args[0]; $args = array_slice($args, 1); - $empty = $this->getEmptyReadableDirectoryPath(); - switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $pattern = "(cd %s && svn --non-interactive {$pattern})"; - array_unshift($args, $this->getLocalPath()); + $pattern = "svn --non-interactive {$pattern}"; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: - $pattern = "(cd %s && HOME=%s git {$pattern})"; - array_unshift($args, $this->getLocalPath(), $empty); + $pattern = "git {$pattern}"; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: - $hgplain = (phutil_is_windows() ? "set HGPLAIN=1 &&" : "HGPLAIN=1"); - $pattern = "(cd %s && {$hgplain} hg {$pattern})"; - array_unshift($args, $this->getLocalPath()); + $pattern = "hg {$pattern}"; break; default: throw new Exception("Unrecognized version control system."); } array_unshift($args, $pattern); return $args; } - private function getEmptyReadableDirectoryPath() { - // See T2965. Some time after Git 1.7.5.4, Git started fataling if it can - // not read $HOME. For many users, $HOME points at /root (this seems to be - // a default result of Apache setup). Instead, explicitly point $HOME at a - // readable, empty directory so that Git looks for the config file it's - // after, fails to locate it, and moves on. This is really silly, but seems - // like the least damaging approach to mitigating the issue. - $root = dirname(phutil_get_library_root('phabricator')); - return $root.'/support/empty/'; - } - - private function getSSHLogin() { + public function getSSHLogin() { return $this->getDetail('ssh-login'); } - private function getSSHKeyfile() { - if ($this->sshKeyfile === null) { - $key = $this->getDetail('ssh-key'); - $keyfile = $this->getDetail('ssh-keyfile'); - if ($keyfile) { - // Make sure we can read the file, that it exists, etc. - Filesystem::readFile($keyfile); - $this->sshKeyfile = $keyfile; - } else if ($key) { - $keyfile = new TempFile('phabricator-repository-ssh-key'); - chmod($keyfile, 0600); - Filesystem::writeFile($keyfile, $key); - $this->sshKeyfile = $keyfile; - } else { - $this->sshKeyfile = ''; - } - } - - return (string)$this->sshKeyfile; - } - public function getURI() { return '/diffusion/'.$this->getCallsign().'/'; } public function isTracked() { return $this->getDetail('tracking-enabled', false); } public function getDefaultBranch() { $default = $this->getDetail('default-branch'); if (strlen($default)) { return $default; } $default_branches = array( PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => 'master', PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => 'default', ); return idx($default_branches, $this->getVersionControlSystem()); } public function getDefaultArcanistBranch() { return coalesce($this->getDefaultBranch(), 'svn'); } private function isBranchInFilter($branch, $filter_key) { $vcs = $this->getVersionControlSystem(); $is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT); $use_filter = ($is_git); if ($use_filter) { $filter = $this->getDetail($filter_key, array()); if ($filter && empty($filter[$branch])) { return false; } } // By default, all branches pass. return true; } public function shouldTrackBranch($branch) { return $this->isBranchInFilter($branch, 'branch-filter'); } public function shouldAutocloseBranch($branch) { if ($this->isImporting()) { return false; } if ($this->getDetail('disable-autoclose', false)) { return false; } return $this->isBranchInFilter($branch, 'close-commits-filter'); } public function shouldAutocloseCommit( PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data) { if ($this->getDetail('disable-autoclose', false)) { return false; } switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: return true; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: return true; default: throw new Exception("Unrecognized version control system."); } $branches = $data->getCommitDetail('seenOnBranches', array()); foreach ($branches as $branch) { if ($this->shouldAutocloseBranch($branch)) { return true; } } return false; } public function formatCommitName($commit_identifier) { $vcs = $this->getVersionControlSystem(); $type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; $type_hg = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL; $is_git = ($vcs == $type_git); $is_hg = ($vcs == $type_hg); if ($is_git || $is_hg) { $short_identifier = substr($commit_identifier, 0, 12); } else { $short_identifier = $commit_identifier; } return 'r'.$this->getCallsign().$short_identifier; } public function isImporting() { return (bool)$this->getDetail('importing', false); } /* -( Repository URI Management )------------------------------------------ */ /** * Get the remote URI for this repository. * * @return string * @task uri */ public function getRemoteURI() { return (string)$this->getRemoteURIObject(); } /** * Get the remote URI for this repository, without authentication information. * * @return string Repository URI. * @task uri */ public function getPublicRemoteURI() { $uri = $this->getRemoteURIObject(); // Make sure we don't leak anything if this repo is using HTTP Basic Auth // with the credentials in the URI or something zany like that. if ($uri instanceof PhutilGitURI) { if (!$this->getDetail('show-user', false)) { $uri->setUser(null); } } else { if (!$this->getDetail('show-user', false)) { $uri->setUser(null); } $uri->setPass(null); } return (string)$uri; } /** * Get the protocol for the repository's remote. * * @return string Protocol, like "ssh" or "git". * @task uri */ public function getRemoteProtocol() { $uri = $this->getRemoteURIObject(); if ($uri instanceof PhutilGitURI) { return 'ssh'; } else { return $uri->getProtocol(); } } /** * Get a parsed object representation of the repository's remote URI. This * may be a normal URI (returned as a @{class@libphutil:PhutilURI}) or a git * URI (returned as a @{class@libphutil:PhutilGitURI}). * * @return wild A @{class@libphutil:PhutilURI} or * @{class@libphutil:PhutilGitURI}. * @task uri */ public function getRemoteURIObject() { $raw_uri = $this->getDetail('remote-uri'); if (!$raw_uri) { return new PhutilURI(''); } if (!strncmp($raw_uri, '/', 1)) { return new PhutilURI('file://'.$raw_uri); } $uri = new PhutilURI($raw_uri); if ($uri->getProtocol()) { if ($this->isSSHProtocol($uri->getProtocol())) { if ($this->getSSHLogin()) { $uri->setUser($this->getSSHLogin()); } } return $uri; } $uri = new PhutilGitURI($raw_uri); if ($uri->getDomain()) { if ($this->getSSHLogin()) { $uri->setUser($this->getSSHLogin()); } return $uri; } throw new Exception("Remote URI '{$raw_uri}' could not be parsed!"); } /** * Determine if we should connect to the remote using SSH flags and * credentials. * * @return bool True to use the SSH protocol. * @task uri */ private function shouldUseSSH() { if ($this->isHosted()) { return false; } $protocol = $this->getRemoteProtocol(); if ($this->isSSHProtocol($protocol)) { - return (bool)$this->getSSHKeyfile(); - } else { - return false; + $key = $this->getDetail('ssh-key'); + $keyfile = $this->getDetail('ssh-keyfile'); + if ($key || $keyfile) { + return true; + } } + + return false; } /** * Determine if we should connect to the remote using HTTP flags and * credentials. * * @return bool True to use the HTTP protocol. * @task uri */ private function shouldUseHTTP() { if ($this->isHosted()) { return false; } $protocol = $this->getRemoteProtocol(); if ($protocol == 'http' || $protocol == 'https') { return (bool)$this->getDetail('http-login'); } else { return false; } } /** * Determine if we should connect to the remote using SVN flags and * credentials. * * @return bool True to use the SVN protocol. * @task uri */ private function shouldUseSVNProtocol() { if ($this->isHosted()) { return false; } $protocol = $this->getRemoteProtocol(); if ($protocol == 'svn') { return (bool)$this->getDetail('http-login'); } else { return false; } } /** * Determine if a protocol is SSH or SSH-like. * * @param string A protocol string, like "http" or "ssh". * @return bool True if the protocol is SSH-like. * @task uri */ private function isSSHProtocol($protocol) { return ($protocol == 'ssh' || $protocol == 'svn+ssh'); } public function delete() { $this->openTransaction(); $paths = id(new PhabricatorOwnersPath()) ->loadAllWhere('repositoryPHID = %s', $this->getPHID()); foreach ($paths as $path) { $path->delete(); } $projects = id(new PhabricatorRepositoryArcanistProject()) ->loadAllWhere('repositoryID = %d', $this->getID()); foreach ($projects as $project) { // note each project deletes its PhabricatorRepositorySymbols $project->delete(); } $commits = id(new PhabricatorRepositoryCommit()) ->loadAllWhere('repositoryID = %d', $this->getID()); foreach ($commits as $commit) { // note PhabricatorRepositoryAuditRequests and // PhabricatorRepositoryCommitData are deleted here too. $commit->delete(); } $conn_w = $this->establishConnection('w'); queryfx( $conn_w, 'DELETE FROM %T WHERE repositoryID = %d', self::TABLE_FILESYSTEM, $this->getID()); queryfx( $conn_w, 'DELETE FROM %T WHERE repositoryID = %d', self::TABLE_PATHCHANGE, $this->getID()); queryfx( $conn_w, 'DELETE FROM %T WHERE repositoryID = %d', self::TABLE_SUMMARY, $this->getID()); $result = parent::delete(); $this->saveTransaction(); return $result; } public function isGit() { $vcs = $this->getVersionControlSystem(); return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT); } public function isSVN() { $vcs = $this->getVersionControlSystem(); return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_SVN); } public function isHg() { $vcs = $this->getVersionControlSystem(); return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL); } public function isHosted() { return (bool)$this->getDetail('hosting-enabled', false); } public function setHosted($enabled) { return $this->setDetail('hosting-enabled', $enabled); } public function getServeOverHTTP() { if ($this->isSVN()) { return self::SERVE_OFF; } $serve = $this->getDetail('serve-over-http', self::SERVE_OFF); return $this->normalizeServeConfigSetting($serve); } public function setServeOverHTTP($mode) { return $this->setDetail('serve-over-http', $mode); } public function getServeOverSSH() { $serve = $this->getDetail('serve-over-ssh', self::SERVE_OFF); return $this->normalizeServeConfigSetting($serve); } public function setServeOverSSH($mode) { return $this->setDetail('serve-over-ssh', $mode); } public static function getProtocolAvailabilityName($constant) { switch ($constant) { case self::SERVE_OFF: return pht('Off'); case self::SERVE_READONLY: return pht('Read Only'); case self::SERVE_READWRITE: return pht('Read/Write'); default: return pht('Unknown'); } } private function normalizeServeConfigSetting($value) { switch ($value) { case self::SERVE_OFF: case self::SERVE_READONLY: return $value; case self::SERVE_READWRITE: if ($this->isHosted()) { return self::SERVE_READWRITE; } else { return self::SERVE_READONLY; } default: return self::SERVE_OFF; } } /** * Raise more useful errors when there are basic filesystem problems. */ private function assertLocalExists() { if (!$this->usesLocalWorkingCopy()) { return; } $local = $this->getLocalPath(); Filesystem::assertExists($local); Filesystem::assertIsDirectory($local); Filesystem::assertReadable($local); } /** * Determine if the working copy is bare or not. In Git, this corresponds * to `--bare`. In Mercurial, `--noupdate`. */ public function isWorkingCopyBare() { switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: return false; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $local = $this->getLocalPath(); if (Filesystem::pathExists($local.'/.git')) { return false; } else { return true; } } } public function usesLocalWorkingCopy() { switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: return $this->isHosted(); case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: return true; } } public function writeStatusMessage( $status_type, $status_code, array $parameters = array()) { $table = new PhabricatorRepositoryStatusMessage(); $conn_w = $table->establishConnection('w'); $table_name = $table->getTableName(); if ($status_code === null) { queryfx( $conn_w, 'DELETE FROM %T WHERE repositoryID = %d AND statusType = %s', $table_name, $this->getID(), $status_type); } else { queryfx( $conn_w, 'INSERT INTO %T (repositoryID, statusType, statusCode, parameters, epoch) VALUES (%d, %s, %s, %s, %d) ON DUPLICATE KEY UPDATE statusCode = VALUES(statusCode), parameters = VALUES(parameters), epoch = VALUES(epoch)', $table_name, $this->getID(), $status_type, $status_code, json_encode($parameters), time()); } return $this; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, DiffusionCapabilityPush::CAPABILITY, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); case DiffusionCapabilityPush::CAPABILITY: return $this->getPushPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $user) { return false; } public function describeAutomaticCapability($capability) { return null; } /* -( PhabricatorMarkupInterface )----------------------------------------- */ public function getMarkupFieldKey($field) { $hash = PhabricatorHash::digestForIndex($this->getMarkupText($field)); return "repo:{$hash}"; } public function newMarkupEngine($field) { return PhabricatorMarkupEngine::newMarkupEngine(array()); } public function getMarkupText($field) { return $this->getDetail('description'); } public function didMarkupText( $field, $output, PhutilMarkupEngine $engine) { require_celerity_resource('phabricator-remarkup-css'); return phutil_tag( 'div', array( 'class' => 'phabricator-remarkup', ), $output); } public function shouldUseMarkupCache($field) { return true; } }