3 final class PhabricatorRepositoryURI
4 extends PhabricatorRepositoryDAO
6 PhabricatorApplicationTransactionInterface
,
7 PhabricatorPolicyInterface
,
8 PhabricatorExtendedPolicyInterface
,
9 PhabricatorConduitResultInterface
{
11 protected $repositoryPHID;
13 protected $builtinProtocol;
14 protected $builtinIdentifier;
15 protected $credentialPHID;
17 protected $displayType;
18 protected $isDisabled;
20 private $repository = self
::ATTACHABLE
;
22 const BUILTIN_PROTOCOL_SSH
= 'ssh';
23 const BUILTIN_PROTOCOL_HTTP
= 'http';
24 const BUILTIN_PROTOCOL_HTTPS
= 'https';
26 const BUILTIN_IDENTIFIER_ID
= 'id';
27 const BUILTIN_IDENTIFIER_SHORTNAME
= 'shortname';
28 const BUILTIN_IDENTIFIER_CALLSIGN
= 'callsign';
30 const DISPLAY_DEFAULT
= 'default';
31 const DISPLAY_NEVER
= 'never';
32 const DISPLAY_ALWAYS
= 'always';
34 const IO_DEFAULT
= 'default';
35 const IO_OBSERVE
= 'observe';
36 const IO_MIRROR
= 'mirror';
37 const IO_NONE
= 'none';
38 const IO_READ
= 'read';
39 const IO_READWRITE
= 'readwrite';
41 protected function getConfiguration() {
43 self
::CONFIG_AUX_PHID
=> true,
44 self
::CONFIG_COLUMN_SCHEMA
=> array(
46 'builtinProtocol' => 'text32?',
47 'builtinIdentifier' => 'text32?',
48 'credentialPHID' => 'phid?',
50 'displayType' => 'text32',
51 'isDisabled' => 'bool',
53 self
::CONFIG_KEY_SCHEMA
=> array(
54 'key_builtin' => array(
63 ) + parent
::getConfiguration();
66 public static function initializeNewURI() {
68 ->setIoType(self
::IO_DEFAULT
)
69 ->setDisplayType(self
::DISPLAY_DEFAULT
)
73 public function generatePHID() {
74 return PhabricatorPHID
::generateNewPHID(
75 PhabricatorRepositoryURIPHIDType
::TYPECONST
);
78 public function attachRepository(PhabricatorRepository
$repository) {
79 $this->repository
= $repository;
83 public function getRepository() {
84 return $this->assertAttached($this->repository
);
87 public function getRepositoryURIBuiltinKey() {
88 if (!$this->getBuiltinProtocol()) {
93 $this->getBuiltinProtocol(),
94 $this->getBuiltinIdentifier(),
97 return implode('.', $parts);
100 public function isBuiltin() {
101 return (bool)$this->getBuiltinProtocol();
104 public function getEffectiveDisplayType() {
105 $display = $this->getDisplayType();
107 if ($display != self
::DISPLAY_DEFAULT
) {
111 return $this->getDefaultDisplayType();
114 public function getDefaultDisplayType() {
115 switch ($this->getEffectiveIOType()) {
116 case self
::IO_MIRROR
:
117 case self
::IO_OBSERVE
:
119 return self
::DISPLAY_NEVER
;
121 case self
::IO_READWRITE
:
122 // By default, only show the "best" version of the builtin URI, not the
123 // other redundant versions.
124 $repository = $this->getRepository();
125 $other_uris = $repository->getURIs();
127 $identifier_value = array(
128 self
::BUILTIN_IDENTIFIER_SHORTNAME
=> 3,
129 self
::BUILTIN_IDENTIFIER_CALLSIGN
=> 2,
130 self
::BUILTIN_IDENTIFIER_ID
=> 1,
133 $have_identifiers = array();
134 foreach ($other_uris as $other_uri) {
135 if ($other_uri->getIsDisabled()) {
139 $identifier = $other_uri->getBuiltinIdentifier();
144 $have_identifiers[$identifier] = $identifier_value[$identifier];
147 $best_identifier = max($have_identifiers);
148 $this_identifier = $identifier_value[$this->getBuiltinIdentifier()];
150 if ($this_identifier < $best_identifier) {
151 return self
::DISPLAY_NEVER
;
154 return self
::DISPLAY_ALWAYS
;
157 return self
::DISPLAY_NEVER
;
161 public function getEffectiveIOType() {
162 $io = $this->getIoType();
164 if ($io != self
::IO_DEFAULT
) {
168 return $this->getDefaultIOType();
171 public function getDefaultIOType() {
172 if ($this->isBuiltin()) {
173 $repository = $this->getRepository();
174 $other_uris = $repository->getURIs();
176 $any_observe = false;
177 foreach ($other_uris as $other_uri) {
178 if ($other_uri->getIoType() == self
::IO_OBSERVE
) {
185 return self
::IO_READ
;
187 return self
::IO_READWRITE
;
191 return self
::IO_NONE
;
194 public function getNormalizedURI() {
195 $vcs = $this->getRepository()->getVersionControlSystem();
198 PhabricatorRepositoryType
::REPOSITORY_TYPE_GIT
=>
199 ArcanistRepositoryURINormalizer
::TYPE_GIT
,
200 PhabricatorRepositoryType
::REPOSITORY_TYPE_SVN
=>
201 ArcanistRepositoryURINormalizer
::TYPE_SVN
,
202 PhabricatorRepositoryType
::REPOSITORY_TYPE_MERCURIAL
=>
203 ArcanistRepositoryURINormalizer
::TYPE_MERCURIAL
,
207 $display = (string)$this->getDisplayURI();
209 $normalizer = new ArcanistRepositoryURINormalizer($type, $display);
211 $domain_map = self
::getURINormalizerDomainMap();
212 $normalizer->setDomainMap($domain_map);
214 return $normalizer->getNormalizedURI();
217 public function getDisplayURI() {
218 return $this->getURIObject();
221 public function getEffectiveURI() {
222 return $this->getURIObject();
225 public function getURIEnvelope() {
226 $uri = $this->getEffectiveURI();
228 $command_engine = $this->newCommandEngine();
230 $is_http = $command_engine->isAnyHTTPProtocol();
231 // For SVN, we use `--username` and `--password` flags separately in the
232 // CommandEngine, so we don't need to add any credentials here.
233 $is_svn = $this->getRepository()->isSVN();
234 $credential_phid = $this->getCredentialPHID();
236 if ($is_http && !$is_svn && $credential_phid) {
237 $key = PassphrasePasswordKey
::loadFromPHID(
239 PhabricatorUser
::getOmnipotentUser());
241 $uri->setUser($key->getUsernameEnvelope()->openEnvelope());
242 $uri->setPass($key->getPasswordEnvelope()->openEnvelope());
245 return new PhutilOpaqueEnvelope((string)$uri);
248 private function getURIObject() {
249 // Users can provide Git/SCP-style URIs in the form "user@host:path".
250 // In the general case, these are not equivalent to any "ssh://..." form
251 // because the path is relative.
253 if ($this->isBuiltin()) {
254 $builtin_protocol = $this->getForcedProtocol();
255 $builtin_domain = $this->getForcedHost();
256 $raw_uri = "{$builtin_protocol}://{$builtin_domain}";
258 $raw_uri = $this->getURI();
261 $port = $this->getForcedPort();
263 $default_ports = array(
269 $uri = new PhutilURI($raw_uri);
271 // Make sure to remove any password from the URI before we do anything
272 // with it; this should always be provided by the associated credential.
275 $protocol = $this->getForcedProtocol();
277 $uri->setProtocol($protocol);
281 $uri->setPort($port);
284 // Remove any explicitly set default ports.
285 $uri_port = $uri->getPort();
286 $uri_protocol = $uri->getProtocol();
288 $uri_default = idx($default_ports, $uri_protocol);
289 if ($uri_default && ($uri_default == $uri_port)) {
293 $user = $this->getForcedUser();
295 $uri->setUser($user);
298 $host = $this->getForcedHost();
300 $uri->setDomain($host);
303 $path = $this->getForcedPath();
305 $uri->setPath($path);
312 private function getForcedProtocol() {
313 $repository = $this->getRepository();
315 switch ($this->getBuiltinProtocol()) {
316 case self
::BUILTIN_PROTOCOL_SSH
:
317 if ($repository->isSVN()) {
322 case self
::BUILTIN_PROTOCOL_HTTP
:
324 case self
::BUILTIN_PROTOCOL_HTTPS
:
331 private function getForcedUser() {
332 switch ($this->getBuiltinProtocol()) {
333 case self
::BUILTIN_PROTOCOL_SSH
:
334 return AlmanacKeys
::getClusterSSHUser();
340 private function getForcedHost() {
341 $phabricator_uri = PhabricatorEnv
::getURI('/');
342 $phabricator_uri = new PhutilURI($phabricator_uri);
344 $phabricator_host = $phabricator_uri->getDomain();
346 switch ($this->getBuiltinProtocol()) {
347 case self
::BUILTIN_PROTOCOL_SSH
:
348 $ssh_host = PhabricatorEnv
::getEnvConfig('diffusion.ssh-host');
349 if ($ssh_host !== null) {
352 return $phabricator_host;
353 case self
::BUILTIN_PROTOCOL_HTTP
:
354 case self
::BUILTIN_PROTOCOL_HTTPS
:
355 return $phabricator_host;
361 private function getForcedPort() {
362 $protocol = $this->getBuiltinProtocol();
364 if ($protocol == self
::BUILTIN_PROTOCOL_SSH
) {
365 return PhabricatorEnv
::getEnvConfig('diffusion.ssh-port');
368 // If Phabricator is running on a nonstandard port, use that as the default
369 // port for URIs with the same protocol.
371 $is_http = ($protocol == self
::BUILTIN_PROTOCOL_HTTP
);
372 $is_https = ($protocol == self
::BUILTIN_PROTOCOL_HTTPS
);
374 if ($is_http ||
$is_https) {
375 $uri = PhabricatorEnv
::getURI('/');
376 $uri = new PhutilURI($uri);
378 $port = $uri->getPort();
383 $uri_protocol = $uri->getProtocol();
385 ($is_http && ($uri_protocol == 'http')) ||
386 ($is_https && ($uri_protocol == 'https'));
398 private function getForcedPath() {
399 if (!$this->isBuiltin()) {
403 $repository = $this->getRepository();
405 $id = $repository->getID();
406 $callsign = $repository->getCallsign();
407 $short_name = $repository->getRepositorySlug();
409 $clone_name = $repository->getCloneName();
411 if ($repository->isGit()) {
413 } else if ($repository->isHg()) {
420 switch ($this->getBuiltinIdentifier()) {
421 case self
::BUILTIN_IDENTIFIER_ID
:
422 return "/diffusion/{$id}/{$clone_name}{$suffix}";
423 case self
::BUILTIN_IDENTIFIER_SHORTNAME
:
424 return "/source/{$short_name}{$suffix}";
425 case self
::BUILTIN_IDENTIFIER_CALLSIGN
:
426 return "/diffusion/{$callsign}/{$clone_name}{$suffix}";
432 public function getViewURI() {
433 $id = $this->getID();
434 return $this->getRepository()->getPathURI("uri/view/{$id}/");
437 public function getEditURI() {
438 $id = $this->getID();
439 return $this->getRepository()->getPathURI("uri/edit/{$id}/");
442 public function getAvailableIOTypeOptions() {
448 if ($this->isBuiltin()) {
449 $options[] = self
::IO_READ
;
450 $options[] = self
::IO_READWRITE
;
452 $options[] = self
::IO_OBSERVE
;
453 $options[] = self
::IO_MIRROR
;
457 $io_map = self
::getIOTypeMap();
458 foreach ($options as $option) {
459 $spec = idx($io_map, $option, array());
461 $label = idx($spec, 'label', $option);
462 $short = idx($spec, 'short');
464 $name = pht('%s: %s', $label, $short);
465 $map[$option] = $name;
471 public function getAvailableDisplayTypeOptions() {
473 self
::DISPLAY_DEFAULT
,
474 self
::DISPLAY_ALWAYS
,
479 $display_map = self
::getDisplayTypeMap();
480 foreach ($options as $option) {
481 $spec = idx($display_map, $option, array());
483 $label = idx($spec, 'label', $option);
484 $short = idx($spec, 'short');
486 $name = pht('%s: %s', $label, $short);
487 $map[$option] = $name;
493 public static function getIOTypeMap() {
495 self
::IO_DEFAULT
=> array(
496 'label' => pht('Default'),
497 'short' => pht('Use default behavior.'),
499 self
::IO_OBSERVE
=> array(
500 'icon' => 'fa-download',
502 'label' => pht('Observe'),
504 'Changes to this URI will be observed and pulled.'),
505 'short' => pht('Copy from a remote.'),
507 self
::IO_MIRROR
=> array(
508 'icon' => 'fa-upload',
510 'label' => pht('Mirror'),
512 'A copy of any changes will be pushed to this URI.'),
513 'short' => pht('Push a copy to a remote.'),
515 self
::IO_NONE
=> array(
516 'icon' => 'fa-times',
518 'label' => pht('No I/O'),
520 'No changes will be pushed or pulled from this URI.'),
521 'short' => pht('Do not perform any I/O.'),
523 self
::IO_READ
=> array(
524 'icon' => 'fa-folder',
526 'label' => pht('Read Only'),
528 'A read-only copy of the repository will be served from this URI.'),
529 'short' => pht('Serve repository in read-only mode.'),
531 self
::IO_READWRITE
=> array(
532 'icon' => 'fa-folder-open',
534 'label' => pht('Read/Write'),
536 'A read/write copy of the repository will be served from this URI.'),
537 'short' => pht('Serve repository in read/write mode.'),
542 public static function getDisplayTypeMap() {
544 self
::DISPLAY_DEFAULT
=> array(
545 'label' => pht('Default'),
546 'short' => pht('Use default behavior.'),
548 self
::DISPLAY_ALWAYS
=> array(
551 'label' => pht('Visible'),
552 'note' => pht('This URI will be shown to users as a clone URI.'),
553 'short' => pht('Show as a clone URI.'),
555 self
::DISPLAY_NEVER
=> array(
556 'icon' => 'fa-eye-slash',
558 'label' => pht('Hidden'),
560 'This URI will be hidden from users.'),
561 'short' => pht('Do not show as a clone URI.'),
566 public function newCommandEngine() {
567 $repository = $this->getRepository();
569 return DiffusionCommandEngine
::newCommandEngine($repository)
570 ->setCredentialPHID($this->getCredentialPHID())
571 ->setURI($this->getEffectiveURI());
574 public function getURIScore() {
578 self
::IO_READWRITE
=> 200,
579 self
::IO_READ
=> 100,
581 $score +
= idx($io_points, $this->getEffectiveIOType(), 0);
583 $protocol_points = array(
584 self
::BUILTIN_PROTOCOL_SSH
=> 30,
585 self
::BUILTIN_PROTOCOL_HTTPS
=> 20,
586 self
::BUILTIN_PROTOCOL_HTTP
=> 10,
588 $score +
= idx($protocol_points, $this->getBuiltinProtocol(), 0);
590 $identifier_points = array(
591 self
::BUILTIN_IDENTIFIER_SHORTNAME
=> 3,
592 self
::BUILTIN_IDENTIFIER_CALLSIGN
=> 2,
593 self
::BUILTIN_IDENTIFIER_ID
=> 1,
595 $score +
= idx($identifier_points, $this->getBuiltinIdentifier(), 0);
602 /* -( PhabricatorApplicationTransactionInterface )------------------------- */
605 public function getApplicationTransactionEditor() {
606 return new DiffusionURIEditor();
609 public function getApplicationTransactionTemplate() {
610 return new PhabricatorRepositoryURITransaction();
614 /* -( PhabricatorPolicyInterface )----------------------------------------- */
617 public function getCapabilities() {
619 PhabricatorPolicyCapability
::CAN_VIEW
,
620 PhabricatorPolicyCapability
::CAN_EDIT
,
624 public function getPolicy($capability) {
625 switch ($capability) {
626 case PhabricatorPolicyCapability
::CAN_VIEW
:
627 case PhabricatorPolicyCapability
::CAN_EDIT
:
628 return PhabricatorPolicies
::getMostOpenPolicy();
632 public function hasAutomaticCapability($capability, PhabricatorUser
$viewer) {
637 /* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
640 public function getExtendedPolicy($capability, PhabricatorUser
$viewer) {
643 switch ($capability) {
644 case PhabricatorPolicyCapability
::CAN_EDIT
:
645 // To edit a repository URI, you must be able to edit the
646 // corresponding repository.
647 $extended[] = array($this->getRepository(), $capability);
655 /* -( PhabricatorConduitResultInterface )---------------------------------- */
658 public function getFieldSpecificationsForConduit() {
660 id(new PhabricatorConduitSearchFieldSpecification())
661 ->setKey('repositoryPHID')
663 ->setDescription(pht('The associated repository PHID.')),
664 id(new PhabricatorConduitSearchFieldSpecification())
666 ->setType('map<string, string>')
667 ->setDescription(pht('The raw and effective URI.')),
668 id(new PhabricatorConduitSearchFieldSpecification())
670 ->setType('map<string, const>')
672 pht('The raw, default, and effective I/O Type settings.')),
673 id(new PhabricatorConduitSearchFieldSpecification())
675 ->setType('map<string, const>')
677 pht('The raw, default, and effective Display Type settings.')),
678 id(new PhabricatorConduitSearchFieldSpecification())
679 ->setKey('credentialPHID')
682 pht('The associated credential PHID, if one exists.')),
683 id(new PhabricatorConduitSearchFieldSpecification())
686 ->setDescription(pht('True if the URI is disabled.')),
687 id(new PhabricatorConduitSearchFieldSpecification())
689 ->setType('map<string, string>')
691 pht('Information about builtin URIs.')),
692 id(new PhabricatorConduitSearchFieldSpecification())
693 ->setKey('dateCreated')
696 pht('Epoch timestamp when the object was created.')),
697 id(new PhabricatorConduitSearchFieldSpecification())
698 ->setKey('dateModified')
701 pht('Epoch timestamp when the object was last updated.')),
705 public function getFieldValuesForConduit() {
707 'repositoryPHID' => $this->getRepositoryPHID(),
709 'raw' => $this->getURI(),
710 'display' => (string)$this->getDisplayURI(),
711 'effective' => (string)$this->getEffectiveURI(),
712 'normalized' => (string)$this->getNormalizedURI(),
715 'raw' => $this->getIOType(),
716 'default' => $this->getDefaultIOType(),
717 'effective' => $this->getEffectiveIOType(),
720 'raw' => $this->getDisplayType(),
721 'default' => $this->getDefaultDisplayType(),
722 'effective' => $this->getEffectiveDisplayType(),
724 'credentialPHID' => $this->getCredentialPHID(),
725 'disabled' => (bool)$this->getIsDisabled(),
727 'protocol' => $this->getBuiltinProtocol(),
728 'identifier' => $this->getBuiltinIdentifier(),
730 'dateCreated' => $this->getDateCreated(),
731 'dateModified' => $this->getDateModified(),
735 public function getConduitSearchAttachments() {
739 public static function getURINormalizerDomainMap() {
740 $domain_map = array();
742 // See T13435. If the domain for a repository URI is same as the install
743 // base URI, store it as a "<base-uri>" token instead of the actual domain
744 // so that the index does not fall out of date if the install moves.
746 $base_uri = PhabricatorEnv
::getURI('/');
747 $base_uri = new PhutilURI($base_uri);
748 $base_domain = $base_uri->getDomain();
749 $domain_map['<base-uri>'] = $base_domain;
751 // Likewise, store a token for the "SSH Host" domain so it can be changed
752 // without requiring an index rebuild.
754 $ssh_host = PhabricatorEnv
::getEnvConfig('diffusion.ssh-host');
755 if ($ssh_host !== null && strlen($ssh_host)) {
756 $domain_map['<ssh-host>'] = $ssh_host;