Changeset View
Changeset View
Standalone View
Standalone View
src/applications/repository/storage/PhabricatorRepository.php
| Show First 20 Lines • Show All 188 Lines • ▼ Show 20 Lines | public function getSubversionPathURI($path = null, $commit = null) { | ||||
| if ($commit !== null) { | if ($commit !== null) { | ||||
| $uri .= $commit; | $uri .= $commit; | ||||
| } | } | ||||
| return $uri; | return $uri; | ||||
| } | } | ||||
| /* -( Remote Command Execution )------------------------------------------- */ | |||||
| public function execRemoteCommand($pattern /* , $arg, ... */) { | public function execRemoteCommand($pattern /* , $arg, ... */) { | ||||
| $args = func_get_args(); | $args = func_get_args(); | ||||
| $args = $this->formatRemoteCommand($args); | return $this->newRemoteCommandFuture($args)->resolve(); | ||||
| return call_user_func_array('exec_manual', $args); | |||||
| } | } | ||||
| public function execxRemoteCommand($pattern /* , $arg, ... */) { | public function execxRemoteCommand($pattern /* , $arg, ... */) { | ||||
| $args = func_get_args(); | $args = func_get_args(); | ||||
| $args = $this->formatRemoteCommand($args); | return $this->newRemoteCommandFuture($args)->resolvex(); | ||||
| return call_user_func_array('execx', $args); | |||||
| } | } | ||||
| public function getRemoteCommandFuture($pattern /* , $arg, ... */) { | public function getRemoteCommandFuture($pattern /* , $arg, ... */) { | ||||
| $args = func_get_args(); | $args = func_get_args(); | ||||
| $args = $this->formatRemoteCommand($args); | return $this->newRemoteCommandFuture($args); | ||||
| return newv('ExecFuture', $args); | |||||
| } | } | ||||
| public function passthruRemoteCommand($pattern /* , $arg, ... */) { | public function passthruRemoteCommand($pattern /* , $arg, ... */) { | ||||
| $args = func_get_args(); | $args = func_get_args(); | ||||
| $args = $this->formatRemoteCommand($args); | return $this->newRemoteCommandPassthru($args)->execute(); | ||||
| return call_user_func_array('phutil_passthru', $args); | |||||
| } | } | ||||
| public function execLocalCommand($pattern /* , $arg, ... */) { | private function newRemoteCommandFuture(array $argv) { | ||||
| $this->assertLocalExists(); | $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(); | $args = func_get_args(); | ||||
| $args = $this->formatLocalCommand($args); | return $this->newLocalCommandFuture($args)->resolve(); | ||||
| return call_user_func_array('exec_manual', $args); | |||||
| } | } | ||||
| public function execxLocalCommand($pattern /* , $arg, ... */) { | public function execxLocalCommand($pattern /* , $arg, ... */) { | ||||
| $this->assertLocalExists(); | |||||
| $args = func_get_args(); | $args = func_get_args(); | ||||
| $args = $this->formatLocalCommand($args); | return $this->newLocalCommandFuture($args)->resolvex(); | ||||
| return call_user_func_array('execx', $args); | |||||
| } | } | ||||
| public function getLocalCommandFuture($pattern /* , $arg, ... */) { | public function getLocalCommandFuture($pattern /* , $arg, ... */) { | ||||
| $this->assertLocalExists(); | |||||
| $args = func_get_args(); | $args = func_get_args(); | ||||
| $args = $this->formatLocalCommand($args); | return $this->newLocalCommandFuture($args); | ||||
| return newv('ExecFuture', $args); | |||||
| } | } | ||||
| public function passthruLocalCommand($pattern /* , $arg, ... */) { | public function passthruLocalCommand($pattern /* , $arg, ... */) { | ||||
| $args = func_get_args(); | |||||
| return $this->newLocalCommandPassthru($args)->execute(); | |||||
| } | |||||
| private function newLocalCommandFuture(array $argv) { | |||||
| $this->assertLocalExists(); | $this->assertLocalExists(); | ||||
| $args = func_get_args(); | $argv = $this->formatLocalCommand($argv); | ||||
| $args = $this->formatLocalCommand($args); | $future = newv('ExecFuture', $argv); | ||||
| return call_user_func_array('phutil_passthru', $args); | $future->setEnv($this->getLocalCommandEnvironment()); | ||||
| if ($this->usesLocalWorkingCopy()) { | |||||
| $future->setCWD($this->getLocalPath()); | |||||
| } | } | ||||
| return $future; | |||||
| } | |||||
| private function formatRemoteCommand(array $args) { | private function newLocalCommandPassthru(array $argv) { | ||||
| $pattern = $args[0]; | $this->assertLocalExists(); | ||||
| $args = array_slice($args, 1); | |||||
| $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 "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(); | |||||
| } | |||||
| $empty = $this->getEmptyReadableDirectoryPath(); | private function getRemoteCommandEnvironment() { | ||||
| $env = $this->getCommonCommandEnvironment(); | |||||
| if ($this->shouldUseSSH()) { | 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()) { | switch ($this->getVersionControlSystem()) { | ||||
| case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: | case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: | ||||
| $pattern = "SVN_SSH=%s svn --non-interactive {$pattern}"; | // Force SVN to use `bin/ssh-connect`. | ||||
| array_unshift( | $env['SVN_SSH'] = $this->getSSHWrapper(); | ||||
| $args, | |||||
| csprintf( | |||||
| 'ssh -l %P -i %P', | |||||
| new PhutilOpaqueEnvelope($this->getSSHLogin()), | |||||
| new PhutilOpaqueEnvelope($this->getSSHKeyfile()))); | |||||
| break; | break; | ||||
| case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: | case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: | ||||
| $command = call_user_func_array( | // Force Git to use `bin/ssh-connect`. | ||||
| 'csprintf', | $env['GIT_SSH'] = $this->getSSHWrapper(); | ||||
| 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; | break; | ||||
| case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: | case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: | ||||
| $pattern = "hg --config ui.ssh=%s {$pattern}"; | // We force Mercurial through `bin/ssh-connect` too, but it uses a | ||||
| array_unshift( | // command-line flag instead of an environmental variable. | ||||
| $args, | |||||
| csprintf( | |||||
| 'ssh -l %P -i %P', | |||||
| new PhutilOpaqueEnvelope($this->getSSHLogin()), | |||||
| new PhutilOpaqueEnvelope($this->getSSHKeyfile()))); | |||||
| break; | break; | ||||
| default: | default: | ||||
| throw new Exception("Unrecognized version control system."); | throw new Exception("Unrecognized version control system."); | ||||
| } | } | ||||
| } else if ($this->shouldUseHTTP()) { | } | ||||
| return $env; | |||||
| } | |||||
| private function formatRemoteCommand(array $args) { | |||||
| $pattern = $args[0]; | |||||
| $args = array_slice($args, 1); | |||||
| switch ($this->getVersionControlSystem()) { | switch ($this->getVersionControlSystem()) { | ||||
| case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: | case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: | ||||
| if ($this->shouldUseHTTP()) { | |||||
| $pattern = | $pattern = | ||||
| "svn ". | "svn ". | ||||
| "--non-interactive ". | "--non-interactive ". | ||||
| "--no-auth-cache ". | "--no-auth-cache ". | ||||
| "--trust-server-cert ". | "--trust-server-cert ". | ||||
| "--username %P ". | "--username %P ". | ||||
| "--password %P ". | "--password %P ". | ||||
| $pattern; | $pattern; | ||||
| array_unshift( | array_unshift( | ||||
| $args, | $args, | ||||
| new PhutilOpaqueEnvelope($this->getDetail('http-login')), | new PhutilOpaqueEnvelope($this->getDetail('http-login')), | ||||
| new PhutilOpaqueEnvelope($this->getDetail('http-pass'))); | 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()) { | } else if ($this->shouldUseSVNProtocol()) { | ||||
| switch ($this->getVersionControlSystem()) { | |||||
| case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: | |||||
| $pattern = | $pattern = | ||||
| "svn ". | "svn ". | ||||
| "--non-interactive ". | "--non-interactive ". | ||||
| "--no-auth-cache ". | "--no-auth-cache ". | ||||
| "--username %P ". | "--username %P ". | ||||
| "--password %P ". | "--password %P ". | ||||
| $pattern; | $pattern; | ||||
| array_unshift( | array_unshift( | ||||
| $args, | $args, | ||||
| new PhutilOpaqueEnvelope($this->getDetail('http-login')), | new PhutilOpaqueEnvelope($this->getDetail('http-login')), | ||||
| new PhutilOpaqueEnvelope($this->getDetail('http-pass'))); | new PhutilOpaqueEnvelope($this->getDetail('http-pass'))); | ||||
| break; | |||||
| default: | |||||
| throw new Exception( | |||||
| "SVN protocol is SVN only."); | |||||
| } | |||||
| } else { | } else { | ||||
| switch ($this->getVersionControlSystem()) { | |||||
| case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: | |||||
| $pattern = "svn --non-interactive {$pattern}"; | $pattern = "svn --non-interactive {$pattern}"; | ||||
| } | |||||
| break; | break; | ||||
| case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: | case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: | ||||
| $pattern = "HOME=%s git {$pattern}"; | $pattern = "git {$pattern}"; | ||||
| array_unshift($args, $empty); | |||||
| break; | break; | ||||
| case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: | case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: | ||||
| if ($this->shouldUseSSH()) { | |||||
| $pattern = "hg --config ui.ssh=%s {$pattern}"; | |||||
| array_unshift( | |||||
| $args, | |||||
| $this->getSSHWrapper()); | |||||
| } else { | |||||
| $pattern = "hg {$pattern}"; | $pattern = "hg {$pattern}"; | ||||
| } | |||||
| break; | break; | ||||
| default: | default: | ||||
| throw new Exception("Unrecognized version control system."); | throw new Exception("Unrecognized version control system."); | ||||
| } | } | ||||
| } | |||||
| array_unshift($args, $pattern); | array_unshift($args, $pattern); | ||||
| return $args; | return $args; | ||||
| } | } | ||||
| private function formatLocalCommand(array $args) { | private function formatLocalCommand(array $args) { | ||||
| $pattern = $args[0]; | $pattern = $args[0]; | ||||
| $args = array_slice($args, 1); | $args = array_slice($args, 1); | ||||
| $empty = $this->getEmptyReadableDirectoryPath(); | |||||
| switch ($this->getVersionControlSystem()) { | switch ($this->getVersionControlSystem()) { | ||||
| case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: | case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: | ||||
| $pattern = "(cd %s && svn --non-interactive {$pattern})"; | $pattern = "svn --non-interactive {$pattern}"; | ||||
| array_unshift($args, $this->getLocalPath()); | |||||
| break; | break; | ||||
| case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: | case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: | ||||
| $pattern = "(cd %s && HOME=%s git {$pattern})"; | $pattern = "git {$pattern}"; | ||||
| array_unshift($args, $this->getLocalPath(), $empty); | |||||
| break; | break; | ||||
| case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: | case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: | ||||
| $hgplain = (phutil_is_windows() ? "set HGPLAIN=1 &&" : "HGPLAIN=1"); | $pattern = "hg {$pattern}"; | ||||
| $pattern = "(cd %s && {$hgplain} hg {$pattern})"; | |||||
| array_unshift($args, $this->getLocalPath()); | |||||
| break; | break; | ||||
| default: | default: | ||||
| throw new Exception("Unrecognized version control system."); | throw new Exception("Unrecognized version control system."); | ||||
| } | } | ||||
| array_unshift($args, $pattern); | array_unshift($args, $pattern); | ||||
| return $args; | return $args; | ||||
| } | } | ||||
| private function getEmptyReadableDirectoryPath() { | public function getSSHLogin() { | ||||
| // 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'); | 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() { | public function getURI() { | ||||
| return '/diffusion/'.$this->getCallsign().'/'; | return '/diffusion/'.$this->getCallsign().'/'; | ||||
| } | } | ||||
| public function isTracked() { | public function isTracked() { | ||||
| return $this->getDetail('tracking-enabled', false); | return $this->getDetail('tracking-enabled', false); | ||||
| } | } | ||||
| ▲ Show 20 Lines • Show All 207 Lines • ▼ Show 20 Lines | /* -( Repository URI Management )------------------------------------------ */ | ||||
| */ | */ | ||||
| private function shouldUseSSH() { | private function shouldUseSSH() { | ||||
| if ($this->isHosted()) { | if ($this->isHosted()) { | ||||
| return false; | return false; | ||||
| } | } | ||||
| $protocol = $this->getRemoteProtocol(); | $protocol = $this->getRemoteProtocol(); | ||||
| if ($this->isSSHProtocol($protocol)) { | if ($this->isSSHProtocol($protocol)) { | ||||
| return (bool)$this->getSSHKeyfile(); | $key = $this->getDetail('ssh-key'); | ||||
| } else { | $keyfile = $this->getDetail('ssh-keyfile'); | ||||
| return false; | if ($key || $keyfile) { | ||||
| return true; | |||||
| } | } | ||||
| } | } | ||||
| return false; | |||||
| } | |||||
| /** | /** | ||||
| * Determine if we should connect to the remote using HTTP flags and | * Determine if we should connect to the remote using HTTP flags and | ||||
| * credentials. | * credentials. | ||||
| * | * | ||||
| * @return bool True to use the HTTP protocol. | * @return bool True to use the HTTP protocol. | ||||
| * @task uri | * @task uri | ||||
| */ | */ | ||||
| ▲ Show 20 Lines • Show All 317 Lines • Show Last 20 Lines | |||||