Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F15380749
D7600.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
16 KB
Referenced Files
None
Subscribers
None
D7600.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D7600: Simplify Repository remote and local command construction
Attached
Detach File
Event Timeline
Log In to Comment