Page MenuHomePhabricator

D7600.diff
No OneTemporary

D7600.diff

Index: bin/ssh-connect
===================================================================
--- /dev/null
+++ bin/ssh-connect
@@ -0,0 +1 @@
+../scripts/ssh/ssh-connect.php
\ No newline at end of file
Index: scripts/ssh/ssh-connect.php
===================================================================
--- /dev/null
+++ 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);
Index: src/applications/repository/storage/PhabricatorRepository.php
===================================================================
--- src/applications/repository/storage/PhabricatorRepository.php
+++ src/applications/repository/storage/PhabricatorRepository.php
@@ -194,108 +194,179 @@
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 ".
@@ -308,45 +379,37 @@
$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);
@@ -358,21 +421,15 @@
$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.");
@@ -383,42 +440,10 @@
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().'/';
}
@@ -642,10 +667,14 @@
$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;
}

File Metadata

Mime Type
text/plain
Expires
Sat, Mar 15, 4:18 AM (1 w, 4 h ago)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7658290
Default Alt Text
D7600.diff (16 KB)

Event Timeline