diff --git a/resources/sql/autopatches/20160428.repo.1.urixaction.sql b/resources/sql/autopatches/20160428.repo.1.urixaction.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20160428.repo.1.urixaction.sql @@ -0,0 +1,19 @@ +CREATE TABLE {$NAMESPACE}_repository.repository_uritransaction ( + id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY, + phid VARBINARY(64) NOT NULL, + authorPHID VARBINARY(64) NOT NULL, + objectPHID VARBINARY(64) NOT NULL, + viewPolicy VARBINARY(64) NOT NULL, + editPolicy VARBINARY(64) NOT NULL, + commentPHID VARBINARY(64) DEFAULT NULL, + commentVersion INT UNSIGNED NOT NULL, + transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL, + oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL, + dateCreated INT UNSIGNED NOT NULL, + dateModified INT UNSIGNED NOT NULL, + UNIQUE KEY `key_phid` (`phid`), + KEY `key_object` (`objectPHID`) +) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT}; diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -789,6 +789,7 @@ 'DiffusionRepositorySymbolsManagementPanel' => 'applications/diffusion/management/DiffusionRepositorySymbolsManagementPanel.php', 'DiffusionRepositoryTag' => 'applications/diffusion/data/DiffusionRepositoryTag.php', 'DiffusionRepositoryTestAutomationController' => 'applications/diffusion/controller/DiffusionRepositoryTestAutomationController.php', + 'DiffusionRepositoryURIEditController' => 'applications/diffusion/controller/DiffusionRepositoryURIEditController.php', 'DiffusionRepositoryURIsIndexEngineExtension' => 'applications/diffusion/engineextension/DiffusionRepositoryURIsIndexEngineExtension.php', 'DiffusionRepositoryURIsManagementPanel' => 'applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php', 'DiffusionRequest' => 'applications/diffusion/request/DiffusionRequest.php', @@ -814,6 +815,8 @@ 'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php', 'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php', 'DiffusionTagsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php', + 'DiffusionURIEditEngine' => 'applications/diffusion/editor/DiffusionURIEditEngine.php', + 'DiffusionURIEditor' => 'applications/diffusion/editor/DiffusionURIEditor.php', 'DiffusionURITestCase' => 'applications/diffusion/request/__tests__/DiffusionURITestCase.php', 'DiffusionUpdateCoverageConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionUpdateCoverageConduitAPIMethod.php', 'DiffusionView' => 'applications/diffusion/view/DiffusionView.php', @@ -3237,7 +3240,10 @@ 'PhabricatorRepositoryURIIndex' => 'applications/repository/storage/PhabricatorRepositoryURIIndex.php', 'PhabricatorRepositoryURINormalizer' => 'applications/repository/data/PhabricatorRepositoryURINormalizer.php', 'PhabricatorRepositoryURINormalizerTestCase' => 'applications/repository/data/__tests__/PhabricatorRepositoryURINormalizerTestCase.php', + 'PhabricatorRepositoryURIPHIDType' => 'applications/repository/phid/PhabricatorRepositoryURIPHIDType.php', + 'PhabricatorRepositoryURIQuery' => 'applications/repository/query/PhabricatorRepositoryURIQuery.php', 'PhabricatorRepositoryURITestCase' => 'applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php', + 'PhabricatorRepositoryURITransaction' => 'applications/repository/storage/PhabricatorRepositoryURITransaction.php', 'PhabricatorRepositoryVCSPassword' => 'applications/repository/storage/PhabricatorRepositoryVCSPassword.php', 'PhabricatorRepositoryVersion' => 'applications/repository/constants/PhabricatorRepositoryVersion.php', 'PhabricatorRepositoryWorkingCopyVersion' => 'applications/repository/storage/PhabricatorRepositoryWorkingCopyVersion.php', @@ -5009,6 +5015,7 @@ 'DiffusionRepositorySymbolsManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRepositoryTag' => 'Phobject', 'DiffusionRepositoryTestAutomationController' => 'DiffusionRepositoryEditController', + 'DiffusionRepositoryURIEditController' => 'DiffusionRepositoryEditController', 'DiffusionRepositoryURIsIndexEngineExtension' => 'PhabricatorIndexEngineExtension', 'DiffusionRepositoryURIsManagementPanel' => 'DiffusionRepositoryManagementPanel', 'DiffusionRequest' => 'Phobject', @@ -5034,6 +5041,8 @@ 'DiffusionTagListController' => 'DiffusionController', 'DiffusionTagListView' => 'DiffusionView', 'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', + 'DiffusionURIEditEngine' => 'PhabricatorEditEngine', + 'DiffusionURIEditor' => 'PhabricatorApplicationTransactionEditor', 'DiffusionURITestCase' => 'PhutilTestCase', 'DiffusionUpdateCoverageConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionView' => 'AphrontView', @@ -7913,11 +7922,19 @@ 'PhabricatorRepositoryTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorRepositoryTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorRepositoryType' => 'Phobject', - 'PhabricatorRepositoryURI' => 'PhabricatorRepositoryDAO', + 'PhabricatorRepositoryURI' => array( + 'PhabricatorRepositoryDAO', + 'PhabricatorApplicationTransactionInterface', + 'PhabricatorPolicyInterface', + 'PhabricatorExtendedPolicyInterface', + ), 'PhabricatorRepositoryURIIndex' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryURINormalizer' => 'Phobject', 'PhabricatorRepositoryURINormalizerTestCase' => 'PhabricatorTestCase', + 'PhabricatorRepositoryURIPHIDType' => 'PhabricatorPHIDType', + 'PhabricatorRepositoryURIQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorRepositoryURITestCase' => 'PhabricatorTestCase', + 'PhabricatorRepositoryURITransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorRepositoryVCSPassword' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryVersion' => 'Phobject', 'PhabricatorRepositoryWorkingCopyVersion' => 'PhabricatorRepositoryDAO', diff --git a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php --- a/src/applications/diffusion/application/PhabricatorDiffusionApplication.php +++ b/src/applications/diffusion/application/PhabricatorDiffusionApplication.php @@ -91,6 +91,8 @@ => 'DiffusionCommitEditController', 'manage/(?:(?P[^/]+)/)?' => 'DiffusionRepositoryManageController', + $this->getEditRoutePattern('uri/edit/') + => 'DiffusionRepositoryURIEditController', 'edit/' => array( '' => 'DiffusionRepositoryEditMainController', 'basic/' => 'DiffusionRepositoryEditBasicController', diff --git a/src/applications/diffusion/controller/DiffusionRepositoryURIEditController.php b/src/applications/diffusion/controller/DiffusionRepositoryURIEditController.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/controller/DiffusionRepositoryURIEditController.php @@ -0,0 +1,21 @@ +loadDiffusionContextForEdit(); + if ($response) { + return $response; + } + + $drequest = $this->getDiffusionRequest(); + $repository = $drequest->getRepository(); + + return id(new DiffusionURIEditEngine()) + ->setController($this) + ->setRepository($repository) + ->buildResponse(); + } + +} diff --git a/src/applications/diffusion/editor/DiffusionURIEditEngine.php b/src/applications/diffusion/editor/DiffusionURIEditEngine.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/editor/DiffusionURIEditEngine.php @@ -0,0 +1,93 @@ +repository = $repository; + return $this; + } + + public function getRepository() { + return $this->repository; + } + + public function isEngineConfigurable() { + return false; + } + + public function getEngineName() { + return pht('Repository URIs'); + } + + public function getSummaryHeader() { + return pht('Edit Repository URI'); + } + + public function getSummaryText() { + return pht('Creates and edits repository URIs.'); + } + + public function getEngineApplicationClass() { + return 'PhabricatorDiffusionApplication'; + } + + protected function newEditableObject() { + $repository = $this->getRepository(); + return PhabricatorRepositoryURI::initializeNewURI($repository); + } + + protected function newObjectQuery() { + return new PhabricatorRepositoryURIQuery(); + } + + protected function getObjectCreateTitleText($object) { + return pht('Create Repository URI'); + } + + protected function getObjectCreateButtonText($object) { + return pht('Create Repository URI'); + } + + protected function getObjectEditTitleText($object) { + return pht('Edit Repository URI: %s', $object->getDisplayURI()); + } + + protected function getObjectEditShortText($object) { + return $object->getDisplayURI(); + } + + protected function getObjectCreateShortText() { + return pht('Create Repository URI'); + } + + protected function getObjectName() { + return pht('Repository URI'); + } + + protected function getObjectViewURI($object) { + $repository = $this->getRepository(); + return $repository->getPathURI('manage/uris/'); + } + + protected function buildCustomEditFields($object) { + $viewer = $this->getViewer(); + + return array( + id(new PhabricatorTextEditField()) + ->setKey('uri') + ->setLabel(pht('URI')) + ->setIsRequired(true) + ->setTransactionType(PhabricatorRepositoryURITransaction::TYPE_URI) + ->setDescription(pht('The repository URI.')) + ->setConduitDescription(pht('Change the repository URI.')) + ->setConduitTypeDescription(pht('New repository URI.')) + ->setValue($object->getURI()), + ); + } + +} diff --git a/src/applications/diffusion/editor/DiffusionURIEditor.php b/src/applications/diffusion/editor/DiffusionURIEditor.php new file mode 100644 --- /dev/null +++ b/src/applications/diffusion/editor/DiffusionURIEditor.php @@ -0,0 +1,99 @@ +getTransactionType()) { + case PhabricatorRepositoryURITransaction::TYPE_URI: + return $object->getURI(); + } + + return parent::getCustomTransactionOldValue($object, $xaction); + } + + protected function getCustomTransactionNewValue( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorRepositoryURITransaction::TYPE_URI: + return $xaction->getNewValue(); + } + + return parent::getCustomTransactionNewValue($object, $xaction); + } + + protected function applyCustomInternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorRepositoryURITransaction::TYPE_URI: + $object->setURI($xaction->getNewValue()); + break; + } + } + + protected function applyCustomExternalTransaction( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorRepositoryURITransaction::TYPE_URI: + 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_URI: + $missing = $this->validateIsEmptyTextField( + $object->getURI(), + $xactions); + + if ($missing) { + $error = new PhabricatorApplicationTransactionValidationError( + $type, + pht('Required'), + pht('Repository URI is required.'), + nonempty(last($xactions), null)); + + $error->setIsMissingFieldError(true); + $errors[] = $error; + break; + } + break; + } + + return $errors; + } + +} diff --git a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php --- a/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php +++ b/src/applications/diffusion/management/DiffusionRepositoryURIsManagementPanel.php @@ -16,8 +16,6 @@ public function buildManagementPanelContent() { $repository = $this->getRepository(); $viewer = $this->getViewer(); - - $repository->attachURIs(array()); $uris = $repository->getURIs(); Javelin::initBehavior('phabricator-tooltips'); @@ -25,6 +23,12 @@ foreach ($uris as $uri) { $uri_name = $uri->getDisplayURI(); + $uri_name = phutil_tag( + 'a', + array( + 'href' => $repository->getPathURI('uri/edit/'.$uri->getID().'/'), + ), + $uri_name); if ($uri->getIsDisabled()) { $status_icon = 'fa-times grey'; diff --git a/src/applications/diffusion/request/DiffusionRequest.php b/src/applications/diffusion/request/DiffusionRequest.php --- a/src/applications/diffusion/request/DiffusionRequest.php +++ b/src/applications/diffusion/request/DiffusionRequest.php @@ -142,7 +142,8 @@ $query = id(new PhabricatorRepositoryQuery()) ->setViewer($viewer) - ->withIdentifiers(array($identifier)); + ->withIdentifiers(array($identifier)) + ->needURIs(true); if ($need_edit) { $query->requireCapabilities( diff --git a/src/applications/repository/phid/PhabricatorRepositoryURIPHIDType.php b/src/applications/repository/phid/PhabricatorRepositoryURIPHIDType.php new file mode 100644 --- /dev/null +++ b/src/applications/repository/phid/PhabricatorRepositoryURIPHIDType.php @@ -0,0 +1,40 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $uri = $objects[$phid]; + + $handle->setName( + pht('URI %d %s', $uri->getID(), $uri->getDisplayURI())); + } + } + +} diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -34,6 +34,7 @@ private $needMostRecentCommits; private $needCommitCounts; private $needProjectPHIDs; + private $needURIs; public function withIDs(array $ids) { $this->ids = $ids; @@ -148,6 +149,11 @@ return $this; } + public function needURIs($need_uris) { + $this->needURIs = $need_uris; + return $this; + } + public function getBuiltinOrders() { return array( 'committed' => array( @@ -348,6 +354,20 @@ } } + $viewer = $this->getViewer(); + + if ($this->needURIs) { + $uris = id(new PhabricatorRepositoryURIQuery()) + ->setViewer($viewer) + ->withRepositories($repositories) + ->execute(); + $uri_groups = mgroup($uris, 'getRepositoryPHID'); + foreach ($repositories as $repository) { + $repository_uris = idx($uri_groups, $repository->getPHID(), array()); + $repository->attachURIs($repository_uris); + } + } + return $repositories; } diff --git a/src/applications/repository/query/PhabricatorRepositoryURIQuery.php b/src/applications/repository/query/PhabricatorRepositoryURIQuery.php new file mode 100644 --- /dev/null +++ b/src/applications/repository/query/PhabricatorRepositoryURIQuery.php @@ -0,0 +1,106 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withRepositoryPHIDs(array $phids) { + $this->repositoryPHIDs = $phids; + return $this; + } + + public function withRepositories(array $repositories) { + $repositories = mpull($repositories, null, 'getPHID'); + $this->withRepositoryPHIDs(array_keys($repositories)); + $this->repositories = $repositories; + return $this; + } + + public function withObjectHashes(array $hashes) { + $this->objectHashes = $hashes; + return $this; + } + + public function newResultObject() { + return new PhabricatorRepositoryURI(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'phid IN (%Ls)', + $this->phids); + } + + if ($this->repositoryPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'repositoryPHID IN (%Ls)', + $this->repositoryPHIDs); + } + + return $where; + } + + protected function willFilterPage(array $uris) { + $repositories = $this->repositories; + + $repository_phids = mpull($uris, 'getRepositoryPHID'); + $repository_phids = array_fuse($repository_phids); + $repository_phids = array_diff_key($repository_phids, $repositories); + + if ($repository_phids) { + $more_repositories = id(new PhabricatorRepositoryQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs($repository_phids) + ->execute(); + $repositories += mpull($more_repositories, null, 'getPHID'); + } + + foreach ($uris as $key => $uri) { + $repository_phid = $uri->getRepositoryPHID(); + $repository = idx($repositories, $repository_phid); + if (!$repository) { + $this->didRejectResult($uri); + unset($uris[$key]); + continue; + } + $uri->attachRepository($repository); + } + + return $uris; + } + + public function getQueryApplicationClass() { + return 'PhabricatorDiffusionApplication'; + } + +} diff --git a/src/applications/repository/storage/PhabricatorRepositoryURI.php b/src/applications/repository/storage/PhabricatorRepositoryURI.php --- a/src/applications/repository/storage/PhabricatorRepositoryURI.php +++ b/src/applications/repository/storage/PhabricatorRepositoryURI.php @@ -1,7 +1,11 @@ setIsDisabled(0); } + public function generatePHID() { + return PhabricatorPHID::generateNewPHID( + PhabricatorRepositoryURIPHIDType::TYPECONST); + } + public function attachRepository(PhabricatorRepository $repository) { $this->repository = $repository; return $this; @@ -176,7 +185,7 @@ } } - return self::IO_IGNORE; + return self::IO_NONE; } @@ -298,4 +307,71 @@ } } + +/* -( PhabricatorApplicationTransactionInterface )------------------------- */ + + + public function getApplicationTransactionEditor() { + return new DiffusionURIEditor(); + } + + public function getApplicationTransactionObject() { + return $this; + } + + public function getApplicationTransactionTemplate() { + return new PhabricatorRepositoryURITransaction(); + } + + public function willRenderTimeline( + PhabricatorApplicationTransactionView $timeline, + AphrontRequest $request) { + return $timeline; + } + + +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + PhabricatorPolicyCapability::CAN_EDIT, + ); + } + + public function getPolicy($capability) { + switch ($capability) { + case PhabricatorPolicyCapability::CAN_VIEW: + case PhabricatorPolicyCapability::CAN_EDIT: + return PhabricatorPolicies::getMostOpenPolicy(); + } + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; + } + + public function describeAutomaticCapability($capability) { + return null; + } + + +/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ + + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + $extended = array(); + + switch ($capability) { + case PhabricatorPolicyCapability::CAN_EDIT: + // To edit a repository URI, you must be able to edit the + // corresponding repository. + $extended[] = array($this->getRepository(), $capability); + break; + } + + return $extended; + } + } diff --git a/src/applications/repository/storage/PhabricatorRepositoryURITransaction.php b/src/applications/repository/storage/PhabricatorRepositoryURITransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/repository/storage/PhabricatorRepositoryURITransaction.php @@ -0,0 +1,20 @@ +