Correct a parameter order swap in "diffusion.historyquery" for Mercurial
[phabricator.git] / src / applications / diffusion / protocol / DiffusionCommandEngine.php
bloba0f1adaf1f93663b559158f1003decd6ca7f052b
1 <?php
3 abstract class DiffusionCommandEngine extends Phobject {
5 private $repository;
6 private $protocol;
7 private $credentialPHID;
8 private $argv;
9 private $passthru;
10 private $connectAsDevice;
11 private $sudoAsDaemon;
12 private $uri;
14 public static function newCommandEngine(PhabricatorRepository $repository) {
15 $engines = self::newCommandEngines();
17 foreach ($engines as $engine) {
18 if ($engine->canBuildForRepository($repository)) {
19 return id(clone $engine)
20 ->setRepository($repository);
24 throw new Exception(
25 pht(
26 'No registered command engine can build commands for this '.
27 'repository ("%s").',
28 $repository->getDisplayName()));
31 private static function newCommandEngines() {
32 return id(new PhutilClassMapQuery())
33 ->setAncestorClass(__CLASS__)
34 ->execute();
37 abstract protected function canBuildForRepository(
38 PhabricatorRepository $repository);
40 abstract protected function newFormattedCommand($pattern, array $argv);
41 abstract protected function newCustomEnvironment();
43 public function setRepository(PhabricatorRepository $repository) {
44 $this->repository = $repository;
45 return $this;
48 public function getRepository() {
49 return $this->repository;
52 public function setURI(PhutilURI $uri) {
53 $this->uri = $uri;
54 $this->setProtocol($uri->getProtocol());
55 return $this;
58 public function getURI() {
59 return $this->uri;
62 public function setProtocol($protocol) {
63 $this->protocol = $protocol;
64 return $this;
67 public function getProtocol() {
68 return $this->protocol;
71 public function getDisplayProtocol() {
72 return $this->getProtocol().'://';
75 public function setCredentialPHID($credential_phid) {
76 $this->credentialPHID = $credential_phid;
77 return $this;
80 public function getCredentialPHID() {
81 return $this->credentialPHID;
84 public function setArgv(array $argv) {
85 $this->argv = $argv;
86 return $this;
89 public function getArgv() {
90 return $this->argv;
93 public function setPassthru($passthru) {
94 $this->passthru = $passthru;
95 return $this;
98 public function getPassthru() {
99 return $this->passthru;
102 public function setConnectAsDevice($connect_as_device) {
103 $this->connectAsDevice = $connect_as_device;
104 return $this;
107 public function getConnectAsDevice() {
108 return $this->connectAsDevice;
111 public function setSudoAsDaemon($sudo_as_daemon) {
112 $this->sudoAsDaemon = $sudo_as_daemon;
113 return $this;
116 public function getSudoAsDaemon() {
117 return $this->sudoAsDaemon;
120 public function newFuture() {
121 $argv = $this->newCommandArgv();
122 $env = $this->newCommandEnvironment();
123 $is_passthru = $this->getPassthru();
125 if ($this->getSudoAsDaemon()) {
126 $command = call_user_func_array('csprintf', $argv);
127 $command = PhabricatorDaemon::sudoCommandAsDaemonUser($command);
128 $argv = array('%C', $command);
131 if ($is_passthru) {
132 $future = newv('PhutilExecPassthru', $argv);
133 } else {
134 $future = newv('ExecFuture', $argv);
137 $future->setEnv($env);
139 // See T13108. By default, don't let any cluster command run indefinitely
140 // to try to avoid cases where `git fetch` hangs for some reason and we're
141 // left sitting with a held lock forever.
142 $repository = $this->getRepository();
143 if (!$is_passthru) {
144 $future->setTimeout($repository->getEffectiveCopyTimeLimit());
147 return $future;
150 private function newCommandArgv() {
151 $argv = $this->argv;
152 $pattern = $argv[0];
153 $argv = array_slice($argv, 1);
155 list($pattern, $argv) = $this->newFormattedCommand($pattern, $argv);
157 return array_merge(array($pattern), $argv);
160 private function newCommandEnvironment() {
161 $env = $this->newCommonEnvironment() + $this->newCustomEnvironment();
162 foreach ($env as $key => $value) {
163 if ($value === null) {
164 unset($env[$key]);
167 return $env;
170 private function newCommonEnvironment() {
171 $repository = $this->getRepository();
173 $env = array();
174 // NOTE: Force the language to "en_US.UTF-8", which overrides locale
175 // settings. This makes stuff print in English instead of, e.g., French,
176 // so we can parse the output of some commands, error messages, etc.
177 $env['LANG'] = 'en_US.UTF-8';
179 // Propagate PHABRICATOR_ENV explicitly. For discussion, see T4155.
180 $env['PHABRICATOR_ENV'] = PhabricatorEnv::getSelectedEnvironmentName();
182 $as_device = $this->getConnectAsDevice();
183 $credential_phid = $this->getCredentialPHID();
185 if ($as_device) {
186 $device = AlmanacKeys::getLiveDevice();
187 if (!$device) {
188 throw new Exception(
189 pht(
190 'Attempting to build a repository command (for repository "%s") '.
191 'as device, but this host ("%s") is not configured as a cluster '.
192 'device.',
193 $repository->getDisplayName(),
194 php_uname('n')));
197 if ($credential_phid) {
198 throw new Exception(
199 pht(
200 'Attempting to build a repository command (for repository "%s"), '.
201 'but the CommandEngine is configured to connect as both the '.
202 'current cluster device ("%s") and with a specific credential '.
203 '("%s"). These options are mutually exclusive. Connections must '.
204 'authenticate as one or the other, not both.',
205 $repository->getDisplayName(),
206 $device->getName(),
207 $credential_phid));
212 if ($this->isAnySSHProtocol()) {
213 if ($credential_phid) {
214 $env['PHABRICATOR_CREDENTIAL'] = $credential_phid;
216 if ($as_device) {
217 $env['PHABRICATOR_AS_DEVICE'] = 1;
221 $env += $repository->getPassthroughEnvironmentalVariables();
223 return $env;
226 public function isSSHProtocol() {
227 return ($this->getProtocol() == 'ssh');
230 public function isSVNProtocol() {
231 return ($this->getProtocol() == 'svn');
234 public function isSVNSSHProtocol() {
235 return ($this->getProtocol() == 'svn+ssh');
238 public function isHTTPProtocol() {
239 return ($this->getProtocol() == 'http');
242 public function isHTTPSProtocol() {
243 return ($this->getProtocol() == 'https');
246 public function isAnyHTTPProtocol() {
247 return ($this->isHTTPProtocol() || $this->isHTTPSProtocol());
250 public function isAnySSHProtocol() {
251 return ($this->isSSHProtocol() || $this->isSVNSSHProtocol());
254 public function isCredentialSupported() {
255 return ($this->getPassphraseProvidesCredentialType() !== null);
258 public function isCredentialOptional() {
259 if ($this->isAnySSHProtocol()) {
260 return false;
263 return true;
266 public function getPassphraseCredentialLabel() {
267 if ($this->isAnySSHProtocol()) {
268 return pht('SSH Key');
271 if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) {
272 return pht('Password');
275 return null;
278 public function getPassphraseDefaultCredentialType() {
279 if ($this->isAnySSHProtocol()) {
280 return PassphraseSSHPrivateKeyTextCredentialType::CREDENTIAL_TYPE;
283 if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) {
284 return PassphrasePasswordCredentialType::CREDENTIAL_TYPE;
287 return null;
290 public function getPassphraseProvidesCredentialType() {
291 if ($this->isAnySSHProtocol()) {
292 return PassphraseSSHPrivateKeyCredentialType::PROVIDES_TYPE;
295 if ($this->isAnyHTTPProtocol() || $this->isSVNProtocol()) {
296 return PassphrasePasswordCredentialType::PROVIDES_TYPE;
299 return null;
302 protected function getSSHWrapper() {
303 $root = dirname(phutil_get_library_root('phabricator'));
304 return $root.'/bin/ssh-connect';