Index: src/applications/diffusion/controller/DiffusionRepositoryController.php =================================================================== --- src/applications/diffusion/controller/DiffusionRepositoryController.php +++ src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -165,6 +165,16 @@ ->setUser($user); $view->addProperty(pht('Callsign'), $repository->getCallsign()); + $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $repository->getPHID(), + PhabricatorEdgeConfig::TYPE_OBJECT_HAS_PROJECT); + if ($project_phids) { + $this->loadHandles($project_phids); + $view->addProperty( + pht('Projects'), + $this->renderHandlesForPHIDs($project_phids)); + } + if ($repository->isHosted()) { $serve_off = PhabricatorRepository::SERVE_OFF; $callsign = $repository->getCallsign(); Index: src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php =================================================================== --- src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php +++ src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php @@ -16,6 +16,7 @@ PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) + ->needProjectPHIDs(true) ->withIDs(array($repository->getID())) ->executeOne(); @@ -33,6 +34,7 @@ if ($request->isFormPost()) { $v_name = $request->getStr('name'); $v_desc = $request->getStr('description'); + $v_projects = $request->getArr('projectPHIDs'); if (!strlen($v_name)) { $e_name = pht('Required'); @@ -47,6 +49,7 @@ $type_name = PhabricatorRepositoryTransaction::TYPE_NAME; $type_desc = PhabricatorRepositoryTransaction::TYPE_DESCRIPTION; + $type_edge = PhabricatorTransactions::TYPE_EDGE; $xactions[] = id(clone $template) ->setTransactionType($type_name) @@ -56,6 +59,16 @@ ->setTransactionType($type_desc) ->setNewValue($v_desc); + $xactions[] = id(clone $template) + ->setTransactionType($type_edge) + ->setMetadataValue( + 'edge:type', + PhabricatorEdgeConfig::TYPE_OBJECT_HAS_PROJECT) + ->setNewValue( + array( + '=' => array_fuse($v_projects), + )); + id(new PhabricatorRepositoryEditor()) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) @@ -78,6 +91,8 @@ ->setErrors($errors); } + $project_handles = $this->loadViewerHandles($repository->getProjectPHIDs()); + $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( @@ -92,6 +107,12 @@ ->setLabel(pht('Description')) ->setValue($v_desc)) ->appendChild( + id(new AphrontFormTokenizerControl()) + ->setDatasource('/typeahead/common/projects/') + ->setName('projectPHIDs') + ->setLabel(pht('Projects')) + ->setValue($project_handles)) + ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save')) ->addCancelButton($edit_uri)) Index: src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php =================================================================== --- src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -253,6 +253,16 @@ $view->addProperty(pht('Type'), $type); $view->addProperty(pht('Callsign'), $repository->getCallsign()); + $project_phids = PhabricatorEdgeQuery::loadDestinationPHIDs( + $repository->getPHID(), + PhabricatorEdgeConfig::TYPE_OBJECT_HAS_PROJECT); + if ($project_phids) { + $this->loadHandles($project_phids); + $view->addProperty( + pht('Projects'), + $this->renderHandlesForPHIDs($project_phids)); + } + $view->addProperty( pht('Status'), $this->buildRepositoryStatus($repository)); Index: src/applications/diffusion/controller/DiffusionRepositoryListController.php =================================================================== --- src/applications/diffusion/controller/DiffusionRepositoryListController.php +++ src/applications/diffusion/controller/DiffusionRepositoryListController.php @@ -30,6 +30,11 @@ $viewer = $this->getRequest()->getUser(); + $project_phids = array_fuse( + array_mergev( + mpull($repositories, 'getProjectPHIDs'))); + $project_handles = $this->loadViewerHandles($project_phids); + $list = new PHUIObjectItemListView(); foreach ($repositories as $repository) { $id = $repository->getID(); @@ -49,7 +54,8 @@ $item->setEpoch($commit->getEpoch()); } - $item->addAttribute( + $item->addIcon( + 'none', PhabricatorRepositoryType::getNameForRepositoryType( $repository->getVersionControlSystem())); @@ -72,6 +78,15 @@ $item->addAttribute(pht('No Commits')); } + $handles = array_select_keys( + $project_handles, + $repository->getProjectPHIDs()); + if ($handles) { + $item->addAttribute( + id(new ManiphestTaskProjectsView()) + ->setHandles($handles)); + } + if (!$repository->isTracked()) { $item->setDisabled(true); $item->addIcon('disable-grey', pht('Inactive')); Index: src/applications/repository/editor/PhabricatorRepositoryEditor.php =================================================================== --- src/applications/repository/editor/PhabricatorRepositoryEditor.php +++ src/applications/repository/editor/PhabricatorRepositoryEditor.php @@ -32,6 +32,7 @@ $types[] = PhabricatorRepositoryTransaction::TYPE_CREDENTIAL; $types[] = PhabricatorRepositoryTransaction::TYPE_DANGEROUS; + $types[] = PhabricatorTransactions::TYPE_EDGE; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; Index: src/applications/repository/query/PhabricatorRepositoryQuery.php =================================================================== --- src/applications/repository/query/PhabricatorRepositoryQuery.php +++ src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -23,6 +23,7 @@ private $needMostRecentCommits; private $needCommitCounts; + private $needProjectPHIDs; public function withIDs(array $ids) { $this->ids = $ids; @@ -69,6 +70,11 @@ return $this; } + public function needProjectPHIDs($need_phids) { + $this->needProjectPHIDs = $need_phids; + return $this; + } + public function setOrder($order) { $this->order = $order; return $this; @@ -116,7 +122,6 @@ } } - return $repositories; } @@ -148,6 +153,27 @@ return $repositories; } + public function didFilterPage(array $repositories) { + if ($this->needProjectPHIDs) { + $type_project = PhabricatorEdgeConfig::TYPE_OBJECT_HAS_PROJECT; + + $edge_query = id(new PhabricatorEdgeQuery()) + ->withSourcePHIDs(mpull($repositories, 'getPHID')) + ->withEdgeTypes(array($type_project)); + $edge_query->execute(); + + foreach ($repositories as $repository) { + $project_phids = $edge_query->getDestinationPHIDs( + array( + $repository->getPHID(), + )); + $repository->attachProjectPHIDs($project_phids); + } + } + + return $repositories; + } + public function getReversePaging() { switch ($this->order) { case self::ORDER_CALLSIGN: Index: src/applications/repository/query/PhabricatorRepositorySearchEngine.php =================================================================== --- src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -17,6 +17,7 @@ public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new PhabricatorRepositoryQuery()) + ->needProjectPHIDs(true) ->needCommitCounts(true) ->needMostRecentCommits(true); Index: src/applications/repository/storage/PhabricatorRepository.php =================================================================== --- src/applications/repository/storage/PhabricatorRepository.php +++ src/applications/repository/storage/PhabricatorRepository.php @@ -43,6 +43,7 @@ private $commitCount = self::ATTACHABLE; private $mostRecentCommit = self::ATTACHABLE; + private $projectPHIDs = self::ATTACHABLE; public static function initializeNewRepository(PhabricatorUser $actor) { $app = id(new PhabricatorApplicationQuery()) @@ -193,6 +194,15 @@ return $uri; } + public function attachProjectPHIDs(array $project_phids) { + $this->projectPHIDs = $project_phids; + return $this; + } + + public function getProjectPHIDs() { + return $this->assertAttached($this->projectPHIDs); + } + /* -( Remote Command Execution )------------------------------------------- */ Index: src/infrastructure/edges/constants/PhabricatorEdgeConfig.php =================================================================== --- src/infrastructure/edges/constants/PhabricatorEdgeConfig.php +++ src/infrastructure/edges/constants/PhabricatorEdgeConfig.php @@ -63,6 +63,9 @@ const TYPE_OBJECT_USES_CREDENTIAL = 39; const TYPE_CREDENTIAL_USED_BY_OBJECT = 40; + const TYPE_OBJECT_HAS_PROJECT = 41; + const TYPE_PROJECT_HAS_OBJECT = 42; + const TYPE_TEST_NO_CYCLE = 9000; const TYPE_PHOB_HAS_ASANATASK = 80001; @@ -142,6 +145,9 @@ self::TYPE_OBJECT_USES_CREDENTIAL => self::TYPE_CREDENTIAL_USED_BY_OBJECT, self::TYPE_CREDENTIAL_USED_BY_OBJECT => self::TYPE_OBJECT_USES_CREDENTIAL, + + self::TYPE_OBJECT_HAS_PROJECT => self::TYPE_PROJECT_HAS_OBJECT, + self::TYPE_PROJECT_HAS_OBJECT => self::TYPE_OBJECT_HAS_PROJECT, ); return idx($map, $edge_type); @@ -213,6 +219,7 @@ return '%s edited member(s), added %d: %s; removed %d: %s.'; case self::TYPE_MEMBER_OF_PROJ: case self::TYPE_COMMIT_HAS_PROJECT: + case self::TYPE_OBJECT_HAS_PROJECT: return '%s edited project(s), added %d: %s; removed %d: %s.'; case self::TYPE_QUESTION_HAS_VOTING_USER: case self::TYPE_ANSWER_HAS_VOTING_USER: @@ -227,6 +234,7 @@ case self::TYPE_UNSUBSCRIBED_FROM_OBJECT: case self::TYPE_FILE_HAS_OBJECT: case self::TYPE_CONTRIBUTED_TO_OBJECT: + case self::TYPE_PROJECT_HAS_OBJECT: return '%s edited object(s), added %d: %s; removed %d: %s.'; case self::TYPE_OBJECT_HAS_UNSUBSCRIBER: return '%s edited unsubcriber(s), added %d: %s; removed %d: %s.'; @@ -287,6 +295,7 @@ return '%s added %d member(s): %s.'; case self::TYPE_MEMBER_OF_PROJ: case self::TYPE_COMMIT_HAS_PROJECT: + case self::TYPE_OBJECT_HAS_PROJECT: return '%s added %d project(s): %s.'; case self::TYPE_QUESTION_HAS_VOTING_USER: case self::TYPE_ANSWER_HAS_VOTING_USER: @@ -319,6 +328,7 @@ case self::TYPE_UNSUBSCRIBED_FROM_OBJECT: case self::TYPE_FILE_HAS_OBJECT: case self::TYPE_CONTRIBUTED_TO_OBJECT: + case self::TYPE_PROJECT_HAS_OBJECT: default: return '%s added %d object(s): %s.'; @@ -356,6 +366,7 @@ return '%s removed %d member(s): %s.'; case self::TYPE_MEMBER_OF_PROJ: case self::TYPE_COMMIT_HAS_PROJECT: + case self::TYPE_OBJECT_HAS_PROJECT: return '%s removed %d project(s): %s.'; case self::TYPE_QUESTION_HAS_VOTING_USER: case self::TYPE_ANSWER_HAS_VOTING_USER: @@ -388,6 +399,7 @@ case self::TYPE_UNSUBSCRIBED_FROM_OBJECT: case self::TYPE_FILE_HAS_OBJECT: case self::TYPE_CONTRIBUTED_TO_OBJECT: + case self::TYPE_PROJECT_HAS_OBJECT: default: return '%s removed %d object(s): %s.'; @@ -423,6 +435,7 @@ return '%s updated members of %s.'; case self::TYPE_MEMBER_OF_PROJ: case self::TYPE_COMMIT_HAS_PROJECT: + case self::TYPE_OBJECT_HAS_PROJECT: return '%s updated projects of %s.'; case self::TYPE_QUESTION_HAS_VOTING_USER: case self::TYPE_ANSWER_HAS_VOTING_USER: @@ -455,6 +468,7 @@ case self::TYPE_UNSUBSCRIBED_FROM_OBJECT: case self::TYPE_FILE_HAS_OBJECT: case self::TYPE_CONTRIBUTED_TO_OBJECT: + case self::TYPE_PROJECT_HAS_OBJECT: default: return '%s updated objects of %s.';