diff --git a/src/applications/diffusion/editor/DiffusionURIEditor.php b/src/applications/diffusion/editor/DiffusionURIEditor.php index 9059430d0b..9115b00083 100644 --- a/src/applications/diffusion/editor/DiffusionURIEditor.php +++ b/src/applications/diffusion/editor/DiffusionURIEditor.php @@ -1,453 +1,463 @@ 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; } $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 display setting for this URI. '. 'Available types for this URI are: %s.', 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($object->getRepositoryPHID())) + ->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(); if ($io_type == PhabricatorRepositoryURI::IO_READWRITE) { if ($no_readwrite) { $readwite_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.', implode(', ', array_keys($available)))); } } 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(); $observe_uri = null; foreach ($uris as $uri) { if ($uri->getIoType() != PhabricatorRepositoryURI::IO_OBSERVE) { continue; } $observe_uri = $uri; break; } 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(); return $xactions; } } diff --git a/src/docs/user/userguide/diffusion.diviner b/src/docs/user/userguide/diffusion.diviner index 19fd0a70c2..86bee6314d 100644 --- a/src/docs/user/userguide/diffusion.diviner +++ b/src/docs/user/userguide/diffusion.diviner @@ -1,90 +1,94 @@ @title Diffusion User Guide @group userguide Guide to Diffusion, the Phabricator application for hosting and browsing repositories. Overview ======== Diffusion allows you to create repositories so that you can browse them from the web and interact with them from other applications. Diffusion can host repositories locally, or observe existing remote repositories which are hosted elsewhere (for example, on GitHub, Bitbucket, or other existing hosting). Both types of repositories can be browsed and interacted with, but hosted repositories support some additional triggers and access controls which are not available for observed repositories. Diffusion is integrated with the other tools in the Phabricator suite. For instance: - when you commit Differential revisions to a tracked repository, they are automatically updated and linked to the corresponding commits; - you can add Herald rules to notify you about commits that match certain rules; - for hosted repositories, Herald can enforce granular access control rules; - in all the tools, commit names are automatically linked. The remainder of this document walks through creating, configuring, and managing repositories. Adding Repositories =================== Repository administration is accomplished through Diffusion. You can use the web interface in Diffusion to observe an external repository or create a new hosted repository. - For hosted repositories, make sure you go through the setup instructions in @{article:Diffusion User Guide: Repository Hosting} first. - For all repositories, you'll need to be running the daemons. If you have not set them up yet, see @{article:Managing Daemons with phd}. By default, you must be an administrator to create a new repository. You can change this in the application settings. Managing Repositories ===================== Diffusion repositories have an array of configurable options and behaviors. For details on the available options and guidance on managing and administrating repositories, see @{article:Diffusion User Guilde: Managing Repositories}. +Repositories can also be managed via the API. For an overview on using the +API to create and edit repositories, see +@{article:Diffusion User Guide: Repositories API}. + Repository Clustering ===================== Phabricator repository hosts can be set up in a cluster configuration so you can lose hosts with minimal downtime and data loss. This is an advanced feature which most installs do not need to pursue. To get started with clustering, see @{article:Clustering Introduction}. For details on repository clustering, see @{article:Cluster: Repositories}. Next Steps ========== Continue by: - learning how to creating a symbol index at @{article:Diffusion User Guide: Symbol Indexes}; or - setting up repository hosting with @{article:Diffusion User Guide: Repository Hosting}; or - managing repository hooks with @{article:Diffusion User Guide: Commit Hooks}; or - understanding daemons in more detail with @{article:Managing Daemons with phd}. If you're having trouble getting things working, these topic guides may be helpful: - get details about automatically closing tasks and revisions in response to commits in @{article:Diffusion User Guide: Autoclose}; or - understand how Phabricator updates repositories with @{article:Diffusion User Guide: Repository Updates}; or - fix issues with repository imports with @{article:Troubleshooting Repository Imports}. diff --git a/src/docs/user/userguide/diffusion_api.diviner b/src/docs/user/userguide/diffusion_api.diviner new file mode 100644 index 0000000000..8ddd6d4bf6 --- /dev/null +++ b/src/docs/user/userguide/diffusion_api.diviner @@ -0,0 +1,182 @@ +@title Diffusion User Guide: Repositories API +@group userguide + +Managing repositories with the API. + +Overview +======== + +You can create and update Diffusion repositories using the Conduit API. This +may be useful if you have a large number of existing repositories you want +to import or apply bulk actions to. + +For an introduction to Conduit, see @{article:Conduit API Overview}. + +In general, you'll use these API methods: + + - `diffusion.repository.edit`: Create and edit repositorie. + - `diffusion.uri.edit`: Create and edit repository URIs to configure + observation, mirroring, and cloning. + +To create a repository, you'll generally do this: + + - Call `diffusion.repository.edit` to create a new object and configure + basic information. + - Optionally, call `diffusion.uri.edit` to add URIs to observe or mirror. + - Call `diffusion.repository.edit` to activate the repository. + +This workflow mirrors the workflow from the web UI. The remainder of this +document walks through this workflow in greater detail. + + +Create a Repository +=================== + +To create a repository, call `diffusion.repository.edit`, providing any +properties you want to set. For simplicity these examples will use the +builtin `arc call-conduit` client, but you can use whatever Conduit client +you prefer. + +When creating a repository, you must provide a `vcs` transaction to choose +a repository type, one of: `git`, `hg` or `svn`. + +You must also provide a `name`. + +Other properties are optional. Review the Conduit method documentation from the +web UI for an exhaustive list. + +``` +$ echo '{ + "transactions": [ + { + "type": "vcs", + "value": "git" + }, + { + "type": "name", + "value": "Poetry" + } + ] +}' | arc call-conduit diffusion.repository.edit +``` + +If things work, you should get a result that looks something like this: + +```lang=json +{ + ... + "response": { + "object": { + "id": 1, + "phid": "PHID-REPO-7vm42oayez2rxcmpwhuv" + }, + ... + } + ... +} +``` + +If so, your new repository has been created. It hasn't been activated yet so +it will not show up in the default repository list, but you can find it in the +web UI by browsing to {nav Diffusion > All Repositories}. + +Continue to the next step to configure URIs. + + +Configure URIs +============== + +Now that the repository exists, you can add URIs to it. This is optional, +and if you're creating a //hosted// repository you may be able to skip this +step. + +However, if you want Phabricator to observe an existing remote, you'll +configure it here by adding a URI in "Observe" mode. Use the PHID from the +previous step to identify the repository you want to add a URI to, and call +`diffusion.uri.edit` to create a new URI in Observe mode for the repository. + +You need to provide a `repository` to add the URI to, and the `uri` itself. + +To add the URI in Observe mode, provide an `io` transaction selecting +`observe` mode. + +You may also want to provide a `credential`. + +``` +$ echo '{ + "transactions": [ + { + "type": "repository", + "value": "PHID-REPO-7vm42oayez2rxcmpwhuv" + }, + { + "type": "uri", + "value": "https://github.com/epriestley/poems.git" + }, + { + "type": "io", + "value": "observe" + } + ] +}' | arc call-conduit diffusion.uri.edit +``` + +You should get a response that looks something like this: + +```lang=json +{ + ... + "response": { + "object": { + "id": 1, + "phid": "PHID-RURI-zwtho5o7h3m6rjzgsgrh" + }, + ... + } + ... +} +``` + +If so, your URI has been created. You can review it in the web UI, under +{nav Manage Repository > URIs}. + +When satisfied, continue to the next step to activate the repository. + + +Activate the Repository +======================= + +Now that any URIs have been configured, activate the repository with another +call to `diffusion.repository.edit`. This time, modify the existing repostitory +instead of creating a new one: + +``` +$ echo '{ + "objectIdentifier": "PHID-REPO-7vm42oayez2rxcmpwhuv", + "transactions": [ + { + "type": "status", + "value": "active" + } + ] +}' | arc call-conduit diffusion.repository.edit +``` + +If that goes through cleanly, you should be all set. You can review the +repository from the web UI. + + +Editing Repositories +==================== + +To edit an existing repository, apply changes normally with +`diffusion.repository.edit`. For more details on using edit endpoints, see +@{article:Conduit API: Using Edit Endpoints}. + + +Next Steps +========== + +Continue by: + + - returning to the @{article:Diffusion User Guide}.