diff --git a/src/applications/diffusion/editor/DiffusionURIEditor.php b/src/applications/diffusion/editor/DiffusionURIEditor.php index 674efbc158..90ced865f0 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditor.php +++ b/src/applications/diffusion/editor/DiffusionURIEditor.php @@ -1,517 +1,517 @@ getTransactionType()) { case PhabricatorRepositoryURITransaction::TYPE_URI: return $object->getURI(); case PhabricatorRepositoryURITransaction::TYPE_IO: return $object->getIOType(); case PhabricatorRepositoryURITransaction::TYPE_DISPLAY: return $object->getDisplayType(); case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY: return $object->getRepositoryPHID(); case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL: return $object->getCredentialPHID(); case PhabricatorRepositoryURITransaction::TYPE_DISABLE: return (int)$object->getIsDisabled(); } return parent::getCustomTransactionOldValue($object, $xaction); } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorRepositoryURITransaction::TYPE_URI: case PhabricatorRepositoryURITransaction::TYPE_IO: case PhabricatorRepositoryURITransaction::TYPE_DISPLAY: case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY: case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL: return $xaction->getNewValue(); case PhabricatorRepositoryURITransaction::TYPE_DISABLE: return (int)$xaction->getNewValue(); } return parent::getCustomTransactionNewValue($object, $xaction); } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorRepositoryURITransaction::TYPE_URI: if (!$this->getIsNewObject()) { $old_uri = $object->getEffectiveURI(); } else { $old_uri = null; // When creating a URI via the API, we may not have processed the // repository transaction yet. Attach the repository here to make // sure we have it for the calls below. if ($this->repository) { $object->attachRepository($this->repository); } } $object->setURI($xaction->getNewValue()); // If we've changed the domain or protocol of the URI, remove the // current credential. This improves behavior in several cases: // If a user switches between protocols with different credential // types, like HTTP and SSH, the old credential won't be valid anyway. // It's cleaner to remove it than leave a bad credential in place. // If a user switches hosts, the old credential is probably not // correct (and potentially confusing/misleading). Removing it forces // users to double check that they have the correct credentials. // If an attacker can't see a symmetric credential like a username and // password, they could still potentially capture it by changing the // host for a URI that uses it to `evil.com`, a server they control, // then observing the requests. Removing the credential prevents this // kind of escalation. // Since port and path changes are less likely to fall among these // cases, they don't trigger a credential wipe. $new_uri = $object->getEffectiveURI(); if ($old_uri) { $new_proto = ($old_uri->getProtocol() != $new_uri->getProtocol()); $new_domain = ($old_uri->getDomain() != $new_uri->getDomain()); if ($new_proto || $new_domain) { $object->setCredentialPHID(null); } } break; case PhabricatorRepositoryURITransaction::TYPE_IO: $object->setIOType($xaction->getNewValue()); break; case PhabricatorRepositoryURITransaction::TYPE_DISPLAY: $object->setDisplayType($xaction->getNewValue()); break; case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY: $object->setRepositoryPHID($xaction->getNewValue()); $object->attachRepository($this->repository); break; case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL: $object->setCredentialPHID($xaction->getNewValue()); break; case PhabricatorRepositoryURITransaction::TYPE_DISABLE: $object->setIsDisabled($xaction->getNewValue()); break; } } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorRepositoryURITransaction::TYPE_URI: case PhabricatorRepositoryURITransaction::TYPE_IO: case PhabricatorRepositoryURITransaction::TYPE_DISPLAY: case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY: case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL: case PhabricatorRepositoryURITransaction::TYPE_DISABLE: return; } return parent::applyCustomExternalTransaction($object, $xaction); } protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { case PhabricatorRepositoryURITransaction::TYPE_REPOSITORY: // Save this, since we need it to validate TYPE_IO transactions. $this->repositoryPHID = $object->getRepositoryPHID(); $missing = $this->validateIsEmptyTextField( $object->getRepositoryPHID(), $xactions); if ($missing) { // NOTE: This isn't being marked as a missing field error because // it's a fundamental, required property of the URI. $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht( 'When creating a repository URI, you must specify which '. 'repository the URI will belong to.'), nonempty(last($xactions), null)); break; } $viewer = $this->getActor(); foreach ($xactions as $xaction) { $repository_phid = $xaction->getNewValue(); // If this isn't changing anything, let it through as-is. if ($repository_phid == $object->getRepositoryPHID()) { continue; } if (!$this->getIsNewObject()) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( 'The repository a URI is associated with is immutable, and '. 'can not be changed after the URI is created.'), $xaction); continue; } $repository = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) ->withPHIDs(array($repository_phid)) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) ->executeOne(); if (!$repository) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( 'To create a URI for a repository ("%s"), it must exist and '. 'you must have permission to edit it.', $repository_phid), $xaction); continue; } $this->repository = $repository; $this->repositoryPHID = $repository_phid; } break; case PhabricatorRepositoryURITransaction::TYPE_CREDENTIAL: $viewer = $this->getActor(); foreach ($xactions as $xaction) { $credential_phid = $xaction->getNewValue(); if ($credential_phid == $object->getCredentialPHID()) { continue; } // Anyone who can edit a URI can remove the credential. if ($credential_phid === null) { continue; } $credential = id(new PassphraseCredentialQuery()) ->setViewer($viewer) ->withPHIDs(array($credential_phid)) ->executeOne(); if (!$credential) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( 'You can only associate a credential ("%s") with a repository '. 'URI if it exists and you have permission to see it.', $credential_phid), $xaction); continue; } } break; case PhabricatorRepositoryURITransaction::TYPE_URI: $missing = $this->validateIsEmptyTextField( $object->getURI(), $xactions); if ($missing) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Required'), pht('A repository URI must have a nonempty URI.'), nonempty(last($xactions), null)); $error->setIsMissingFieldError(true); $errors[] = $error; break; } foreach ($xactions as $xaction) { $new_uri = $xaction->getNewValue(); if ($new_uri == $object->getURI()) { continue; } try { PhabricatorRepository::assertValidRemoteURI($new_uri); } catch (Exception $ex) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), $ex->getMessage(), $xaction); continue; } } break; case PhabricatorRepositoryURITransaction::TYPE_IO: $available = $object->getAvailableIOTypeOptions(); foreach ($xactions as $xaction) { $new = $xaction->getNewValue(); if (empty($available[$new])) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( 'Value "%s" is not a valid IO setting for this URI. '. 'Available types for this URI are: %s.', $new, implode(', ', array_keys($available))), $xaction); continue; } // If we are setting this URI to use "Observe", we must have no // other "Observe" URIs and must also have no "Read/Write" URIs. // If we are setting this URI to "Read/Write", we must have no // other "Observe" URIs. It's OK to have other "Read/Write" URIs. $no_observers = false; $no_readwrite = false; switch ($new) { case PhabricatorRepositoryURI::IO_OBSERVE: $no_readwrite = true; $no_observers = true; break; case PhabricatorRepositoryURI::IO_READWRITE: $no_observers = true; break; } if ($no_observers || $no_readwrite) { $repository = id(new PhabricatorRepositoryQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withPHIDs(array($this->repositoryPHID)) ->needURIs(true) ->executeOne(); $uris = $repository->getURIs(); $observe_conflict = null; $readwrite_conflict = null; foreach ($uris as $uri) { // If this is the URI being edited, it can not conflict with // itself. if ($uri->getID() == $object->getID()) { continue; } - $io_type = $uri->getIoType(); + $io_type = $uri->getEffectiveIOType(); if ($io_type == PhabricatorRepositoryURI::IO_READWRITE) { if ($no_readwrite) { - $readwite_conflict = $uri; + $readwrite_conflict = $uri; break; } } if ($io_type == PhabricatorRepositoryURI::IO_OBSERVE) { if ($no_observers) { $observe_conflict = $uri; break; } } } if ($observe_conflict) { if ($new == PhabricatorRepositoryURI::IO_OBSERVE) { $message = pht( 'You can not set this URI to use Observe IO because '. 'another URI for this repository is already configured '. 'in Observe IO mode. A repository can not observe two '. 'different remotes simultaneously. Turn off IO for the '. 'other URI first.'); } else { $message = pht( 'You can not set this URI to use Read/Write IO because '. 'another URI for this repository is already configured '. 'in Observe IO mode. An observed repository can not be '. 'made writable. Turn off IO for the other URI first.'); } $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), $message, $xaction); continue; } if ($readwrite_conflict) { $message = pht( 'You can not set this URI to use Observe IO because '. 'another URI for this repository is already configured '. 'in Read/Write IO mode. A repository can not simultaneously '. 'be writable and observe a remote. Turn off IO for the '. 'other URI first.'); $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), $message, $xaction); continue; } } } break; case PhabricatorRepositoryURITransaction::TYPE_DISPLAY: $available = $object->getAvailableDisplayTypeOptions(); foreach ($xactions as $xaction) { $new = $xaction->getNewValue(); if (empty($available[$new])) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( 'Value "%s" is not a valid display setting for this URI. '. 'Available types for this URI are: %s.', $new, implode(', ', array_keys($available)))); } } break; case PhabricatorRepositoryURITransaction::TYPE_DISABLE: $old = $object->getIsDisabled(); foreach ($xactions as $xaction) { $new = $xaction->getNewValue(); if ($old == $new) { continue; } if (!$object->isBuiltin()) { continue; } $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht('You can not manually disable builtin URIs.')); } break; } return $errors; } protected function applyFinalEffects( PhabricatorLiskDAO $object, array $xactions) { // Synchronize the repository state based on the presence of an "Observe" // URI. $repository = $object->getRepository(); $uris = id(new PhabricatorRepositoryURIQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withRepositories(array($repository)) ->execute(); // Reattach the current URIs to the repository: we're going to rebuild // the index explicitly below, and want to include any changes made to // this URI in the index update. $repository->attachURIs($uris); $observe_uri = null; foreach ($uris as $uri) { if ($uri->getIoType() != PhabricatorRepositoryURI::IO_OBSERVE) { continue; } $observe_uri = $uri; break; } $was_hosted = $repository->isHosted(); if ($observe_uri) { $repository ->setHosted(false) ->setDetail('remote-uri', (string)$observe_uri->getEffectiveURI()) ->setCredentialPHID($observe_uri->getCredentialPHID()); } else { $repository ->setHosted(true) ->setDetail('remote-uri', null) ->setCredentialPHID(null); } $repository->save(); // Explicitly update the URI index. $repository->updateURIIndex(); $is_hosted = $repository->isHosted(); // If we've swapped the repository from hosted to observed or vice versa, // reset all the cluster version clocks. if ($was_hosted != $is_hosted) { $cluster_engine = id(new DiffusionRepositoryClusterEngine()) ->setViewer($this->getActor()) ->setRepository($repository) ->synchronizeWorkingCopyAfterHostingChange(); } $repository->writeStatusMessage( PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE, null); return $xactions; } } diff --git a/src/docs/user/userguide/diffusion_uris.diviner b/src/docs/user/userguide/diffusion_uris.diviner index 140948f55e..a19d38d3a5 100644 --- a/src/docs/user/userguide/diffusion_uris.diviner +++ b/src/docs/user/userguide/diffusion_uris.diviner @@ -1,309 +1,319 @@ @title Diffusion User Guide: URIs @group userguide Guide to configuring repository URIs for fetching, cloning and mirroring. Overview ======== Phabricator can create, host, observe, mirror, proxy, and import repositories. For example, you can: **Host Repositories**: Phabricator can host repositories locally. Phabricator maintains the writable master version of the repository, and you can push and pull the repository. This is the most straightforward kind of repository configuration, and similar to repositories on other services like GitHub or Bitbucket. **Observe Repositories**: Phabricator can create a copy of an repository which is hosted elsewhere (like GitHub or Bitbucket) and track updates to the remote repository. This will create a read-only copy of the repository in Phabricator. **Mirror Repositories**: Phabricator can publish any repository to mirrors, overwriting them with an exact copy of the repository that stays up to date as the source changes. This works with both local repositories that Phabricator is hosting and remote repositories that Phabricator is observing. **Proxy Repositories**: If you are observing a repository, you can allow users to read Phabricator's copy of the repository. Phabricator supports granular read permissions, so this can let you open a private repository up a little bit in a flexible way. **Import Repositories**: If you have a repository elsewhere that you want to host on Phabricator, you can observe the remote repository first, then turn the tracking off once the repository fully synchronizes. This allows you to copy an existing repository and begin hosting it in Phabricator. You can also import repositories by creating an empty hosted repository and then pushing everything to the repository directly. You configure the behavior of a Phabricator repository by adding and configuring URIs and marking them to be fetched from, mirrored to, clonable, and so on. By configuring all the URIs that a repository should interact with and expose to users, you configure the read, write, and mirroring behavior of the repository. The remainder of this document walks through this configuration in greater detail. Host a Repository ================= You can create new repositories that Phabricator will host, like you would create repositories on services like GitHub or Bitbucket. Phabricator will serve a read-write copy of the repository and you can clone it from Phabricator and push changes to Phabricator. If you haven't already, you may need to configure Phabricator for hosting before you can create your first hosted repository. For a detailed guide, see @{article:Diffusion User Guide: Repository Hosting}. This is the default mode for new repositories. To host a repository: - Create a new repository. - Activate it. Phabricator will create an empty repository and allow you to fetch from it and push to it. Observe a Repository ==================== If you have an existing repository hosted on another service (like GitHub, Bitbucket, or a private server) that you want to work with in Phabricator, you can configure Phabricator to observe it. When observing a repository, Phabricator will keep track of changes in the remote repository and allow you to browse and interact with the repository from the web UI in Diffusion and other applications, but you can continue hosting it elsewhere. To observe a repository: - Create a new repository, but don't activate it yet. - Add the remote URI you want to observe as a repository URI. - Set the **I/O Type** for the URI to **Observe**. - If necessary, configure a credential. - Activate the repository. Phabricator will perform an initial import of the repository, creating a local read-only copy. Once this process completes, it will continue keeping track of changes in the remote, fetching them, and reflecting them in the UI. Mirror a Repository =================== NOTE: Mirroring is not supported in Subversion. You can create a read-only mirror of an existing repository. Phabricator will continuously publish the state of the source repository to the mirror, creating an exact copy. For example, if you have a repository hosted in Phabricator that you want to mirror to GitHub, you can configure Phabricator to automatically maintain the mirror. This is how the upstream repositories are set up. The mirror copy must be read-only for users because any writes made to the mirror will be undone when Phabricator updates it. The mirroring process copies the entire repository state exactly, so the remote state will be completely replaced with an exact copy of the source repository. This may remove or destroy information. Normally, you should only mirror to an empty repository. You can mirror any repository, even if Phabricator is only observing it and not hosting it directly. To begin mirroring a repository: - Create a hosted or observed repository by following the relevant instructions above. - Add the remote URI you want to mirror to as a repository URI. - Set the **I/O Type** for the URI to **Mirror**. - If necessary, configure a credential. To stop mirroring: - Disable the mirror URI; or - Change the **I/O Type** for the URI to **None**. Import a Repository =================== If you have an existing repository that you want to move so it is hosted on Phabricator, there are three ways to do it: **Observe First**: //(Git, Mercurial)// Observe the existing repository first, according to the instructions above. Once Phabricator's copy of the repository is fully synchronized, change the **I/O Type** for the **Observe** URI to **None** to stop fetching changes from the remote. By default, this will automatically make Phabricator's copy of the repository writable, and you can begin pushing to it. If you've adjusted URI configuration away from the defaults, you may need to set at least one URI to **Read/Write** mode so you can push to it. **Push Everything**: //(Git, Mercurial, Subversion)// Create a new empty hosted repository according to the instructions above. Once the empty repository initializes, push your entire existing repository to it. In Subversion, you can do this with the `svnsync` tool. **Copy on Disk**: //(Git, Mercurial, Subversion)// Create a new empty hosted repository according to the instructions above, but do not activate it yet. Using the **Storage** tab, find the location of the repository's working copy on disk, and place a working copy of the repository you wish to import there. For Git and Mercurial, use a bare working copy for best results. This is the only way to import a Subversion repository because only the master copy of the repository has history. Once you've put a working copy in the right place on disk, activate the repository. Builtin Clone URIs ================== By default, Phabricator automatically exposes and activates HTTP, HTTPS and SSH clone URIs by examining configuration. **HTTP**: The `http://` clone URI will be available if these conditions are satisfied: - `diffusion.allow-http-auth` must be enabled or the repository view policy must be "Public". - The repository must be a Git or Mercurial repository. - `security.require-https` must be disabled. **HTTPS**: The `https://` clone URI will be available if these conditions are satisfied: - `diffusion.allow-http-auth` must be enabled or the repository view policy must be "Public". - The repository must be a Git or Mercurial repository. - The `phabricator.base-uri` protocol must be `https://`. **SSH**: The `ssh://` or `svn+ssh://` clone URI will be available if these conditions are satisfied: - `phd.user` must be configured. Customizing Displayed Clone URIs ================================ If you have an unusual configuration and want the UI to offers users specific clone URIs other than the URIs that Phabricator serves or interacts with, you can add those URIs with the **I/O Type** set to **None** and then set their **Display Type** to **Always**. Likewise, you can set the **Display Type** of any URIs you do //not// want to be visible to **Never**. This allows you to precisely configure which clone URIs are shown to users for a repository. Reference: I/O Types ==================== This section details the available **I/O Type** options for URIs. Each repository has some **builtin** URIs. These are URIs hosted by Phabricator itself. The modes available for each URI depend primarily on whether it is a builtin URI or not. **Default**: This setting has Phabricator guess the correct option for the URI. For **builtin** URIs, the default behavior is //Read/Write// if the repository is hosted, and //Read-Only// if the repository is observed. For custom URIs, the default type is //None// because we can not automatically guess if you want to ignore, observe, or mirror a URI and //None// is the safest default. **Observe**: Phabricator will observe this repository and regularly fetch any changes made to it to a local read-only copy. You can not observe builtin URIs because reading a repository from itself does not make sense. You can not add a URI in Observe mode if an existing builtin URI is in //Read/Write// mode, because this would mean the repository had two different authorities: the observed remote copy and the hosted local copy. Take the other URI out of //Read/Write// mode first. +WARNING: If you observe a remote repository, the entire state of the working +copy that Phabricator maintains will be deleted and replaced with the state of +the remote. If some changes are present only in Phabricator's working copy, +they will be unrecoverably destroyed. + **Mirror**: Phabricator will push any changes made to this repository to the remote URI, keeping a read-only mirror hosted at that URI up to date. This works for both observed and hosted repositories. This option is not available for builtin URIs because it does not make sense to mirror a repository to itself. It is possible to mirror a repository to another repository that is also hosted by Phabricator by adding that other repository's URI, although this is silly and probably very rarely of any use. +WARNING: If you mirror to a remote repository, the entire state of that remote +will be replaced with the state of the working copy Phabricator maintains. If +some changes are present only in the remote, they will be unrecoverably +destroyed. + **None**: Phabricator will not fetch changes from or push changes to this URI. For builtin URIs, it will not let users fetch changes from or push changes to this URI. You can use this mode to turn off an Observe URI after an import, stop a Mirror URI from updating, or to add URIs that you're only using to customize which clone URIs are displayed to the user but don't want Phabricator to interact with directly. **Read Only**: Phabricator will serve the repository from this URI in read-only mode. Users will be able to fetch from it but will not be able to push to it. Because Phabricator must be able to serve the repository from URIs configured in this mode, this option is only available for builtin URIs. **Read/Write**: Phabricator will serve the repository from this URI in read/write mode. Users will be able to fetch from it and push to it. URIs can not be set into this mode if another URI is set to //Observe// mode, because that would mean the repository had two different authorities: the observed remote copy and the hosted local copy. Take the other URI out of //Observe// mode first. Because Phabricator must be able to serve the repository from URIs configured in this mode, this option is only available for builtin URIs. Reference: Display Types ======================== This section details the available **Display Type** options for URIs. **Default**: Phabricator will guess the correct option for the URI. It guesses based on the configured **I/O Type** and whether the URI is **builtin** or not. For //Observe//, //Mirror// and //None// URIs, the default is //Never//. For builtin URIs in //Read Only// or //Read/Write// mode, the most human-readable URI defaults to //Always// and the others default to //Never//. **Always**: This URI will be shown to users as a clone/checkout URI. You can add URIs in this mode to customize exactly what users are shown. **Never**: This URI will not be shown to users. You can hide less-preferred URIs to guide users to the URIs they should be using to interact with the repository. Next Steps ========== Continue by: - configuring Phabricator to host repositories with @{article:Diffusion User Guide: Repository Hosting}.