diff --git a/resources/sql/autopatches/20150616.divinerrepository.sql b/resources/sql/autopatches/20150616.divinerrepository.sql new file mode 100644 --- /dev/null +++ b/resources/sql/autopatches/20150616.divinerrepository.sql @@ -0,0 +1,5 @@ +ALTER TABLE {$NAMESPACE}_diviner.diviner_livebook + ADD COLUMN repositoryPHID VARBINARY(64) AFTER name; + +ALTER TABLE {$NAMESPACE}_diviner.diviner_livesymbol + ADD COLUMN repositoryPHID VARBINARY(64) AFTER bookPHID; diff --git a/src/applications/diviner/controller/DivinerAtomController.php b/src/applications/diviner/controller/DivinerAtomController.php --- a/src/applications/diviner/controller/DivinerAtomController.php +++ b/src/applications/diviner/controller/DivinerAtomController.php @@ -85,6 +85,7 @@ if ($atom) { $this->buildDefined($properties, $symbol); $this->buildExtendsAndImplements($properties, $symbol); + $this->buildRepository($properties, $symbol); $warnings = $atom->getWarnings(); if ($warnings) { @@ -295,6 +296,15 @@ } } + private function buildRepository( + PHUIPropertyListView $view, + DivinerLiveSymbol $symbol) { + + $view->addProperty( + pht('Repository'), + $this->getViewer()->renderHandle($symbol->getRepositoryPHID())); + } + private function renderAtomTag(DivinerLiveSymbol $symbol) { return id(new PHUITagView()) ->setType(PHUITagView::TYPE_OBJECT) diff --git a/src/applications/diviner/controller/DivinerBookController.php b/src/applications/diviner/controller/DivinerBookController.php --- a/src/applications/diviner/controller/DivinerBookController.php +++ b/src/applications/diviner/controller/DivinerBookController.php @@ -14,6 +14,7 @@ $book = id(new DivinerBookQuery()) ->setViewer($viewer) ->withNames(array($book_name)) + ->needRepositories(true) ->executeOne(); if (!$book) { @@ -43,6 +44,15 @@ ->setEpoch($book->getDateModified()) ->addActionLink($action_button); + // TODO: This could probably look better. + if ($book->getRepositoryPHID()) { + $header->addTag( + id(new PHUITagView()) + ->setType(PHUITagView::TYPE_STATE) + ->setBackgroundColor(PHUITagView::COLOR_BLUE) + ->setName($book->getRepository()->getMonogram())); + } + $document = new PHUIDocumentView(); $document->setHeader($header); $document->addClass('diviner-view'); diff --git a/src/applications/diviner/controller/DivinerBookEditController.php b/src/applications/diviner/controller/DivinerBookEditController.php --- a/src/applications/diviner/controller/DivinerBookEditController.php +++ b/src/applications/diviner/controller/DivinerBookEditController.php @@ -75,6 +75,16 @@ ->setName('projectPHIDs') ->setLabel(pht('Projects')) ->setValue($book->getProjectPHIDs())) + ->appendControl( + id(new AphrontFormTokenizerControl()) + ->setDatasource(new DiffusionRepositoryDatasource()) + ->setName('repositoryPHIDs') + ->setLabel(pht('Repository')) + ->setDisableBehavior(true) + ->setLimit(1) + ->setValue($book->getRepositoryPHID() + ? array($book->getRepositoryPHID()) + : null)) ->appendChild( id(new AphrontFormPolicyControl()) ->setName('viewPolicy') diff --git a/src/applications/diviner/publisher/DivinerLivePublisher.php b/src/applications/diviner/publisher/DivinerLivePublisher.php --- a/src/applications/diviner/publisher/DivinerLivePublisher.php +++ b/src/applications/diviner/publisher/DivinerLivePublisher.php @@ -4,7 +4,7 @@ private $book; - private function loadBook() { + protected function getBook() { if (!$this->book) { $book_name = $this->getConfig('name'); @@ -20,7 +20,24 @@ ->save(); } - $book->setConfigurationData($this->getConfigurationData())->save(); + $conn_w = $book->establishConnection('w'); + $conn_w->openTransaction(); + + $book + ->setRepositoryPHID($this->getRepositoryPHID()) + ->setConfigurationData($this->getConfigurationData()) + ->save(); + + // TODO: This is gross. Without this, the repository won't be updated for + // atoms which have already been published. + queryfx( + $conn_w, + 'UPDATE %T SET repositoryPHID = %s WHERE bookPHID = %s', + id(new DivinerLiveSymbol())->getTableName(), + $this->getRepositoryPHID(), + $book->getPHID()); + + $conn_w->saveTransaction(); $this->book = $book; id(new PhabricatorSearchIndexer()) @@ -33,7 +50,7 @@ private function loadSymbolForAtom(DivinerAtom $atom) { $symbol = id(new DivinerAtomQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withBookPHIDs(array($this->loadBook()->getPHID())) + ->withBookPHIDs(array($atom->getBook())) ->withTypes(array($atom->getType())) ->withNames(array($atom->getName())) ->withContexts(array($atom->getContext())) @@ -45,7 +62,7 @@ } return id(new DivinerLiveSymbol()) - ->setBookPHID($this->loadBook()->getPHID()) + ->setBookPHID($this->getBook()->getPHID()) ->setType($atom->getType()) ->setName($atom->getName()) ->setContext($atom->getContext()) @@ -68,7 +85,7 @@ protected function loadAllPublishedHashes() { $symbols = id(new DivinerAtomQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withBookPHIDs(array($this->loadBook()->getPHID())) + ->withBookPHIDs(array($this->getBook()->getPHID())) ->withGhosts(false) ->execute(); @@ -113,6 +130,7 @@ $is_documentable = $this->shouldGenerateDocumentForAtom($atom); $symbol + ->setRepositoryPHID($this->getRepositoryPHID()) ->setGraphHash($hash) ->setIsDocumentable((int)$is_documentable) ->setTitle($ref->getTitle()) diff --git a/src/applications/diviner/publisher/DivinerPublisher.php b/src/applications/diviner/publisher/DivinerPublisher.php --- a/src/applications/diviner/publisher/DivinerPublisher.php +++ b/src/applications/diviner/publisher/DivinerPublisher.php @@ -9,6 +9,7 @@ private $config; private $symbolReverseMap; private $dropCaches; + private $repositoryPHID; final public function setDropCaches($drop_caches) { $this->dropCaches = $drop_caches; @@ -163,4 +164,13 @@ return true; } + final public function getRepositoryPHID() { + return $this->repositoryPHID; + } + + final public function setRepositoryPHID($repository_phid) { + $this->repositoryPHID = $repository_phid; + return $this; + } + } diff --git a/src/applications/diviner/query/DivinerAtomQuery.php b/src/applications/diviner/query/DivinerAtomQuery.php --- a/src/applications/diviner/query/DivinerAtomQuery.php +++ b/src/applications/diviner/query/DivinerAtomQuery.php @@ -14,10 +14,12 @@ private $nodeHashes; private $titles; private $nameContains; + private $repositoryPHIDs; private $needAtoms; private $needExtends; private $needChildren; + private $needRepositories; public function withIDs(array $ids) { $this->ids = $ids; @@ -109,6 +111,16 @@ return $this; } + public function withRepositoryPHIDs(array $repository_phids) { + $this->repositoryPHIDs = $repository_phids; + return $this; + } + + public function needRepositories($need_repositories) { + $this->needRepositories = $need_repositories; + return $this; + } + protected function loadPage() { $table = new DivinerLiveSymbol(); $conn_r = $table->establishConnection('r'); @@ -125,6 +137,8 @@ } protected function willFilterPage(array $atoms) { + assert_instances_of($atoms, 'DivinerLiveSymbol'); + $books = array_unique(mpull($atoms, 'getBookPHID')); $books = id(new DivinerBookQuery()) @@ -257,6 +271,31 @@ $this->attachAllChildren($atoms, $children, $this->needExtends); } + if ($this->needRepositories) { + $repositories = id(new PhabricatorRepositoryQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(mpull($atoms, 'getRepositoryPHID')) + ->execute(); + $repositories = mpull($repositories, null, 'getPHID'); + + foreach ($atoms as $key => $atom) { + if ($atom->getRepositoryPHID() === null) { + $atom->attachRepository(null); + continue; + } + + $repository = idx($repositories, $atom->getRepositoryPHID()); + + if (!$repository) { + $this->didRejectResult($atom); + unset($atom[$key]); + continue; + } + + $atom->attachRepository($repository); + } + } + return $atoms; } @@ -381,6 +420,13 @@ $this->nameContains); } + if ($this->repositoryPHIDs) { + $where[] = qsprintf( + $conn_r, + 'repositoryPHID IN (%Ls)', + $this->repositoryPHIDs); + } + $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); diff --git a/src/applications/diviner/query/DivinerAtomSearchEngine.php b/src/applications/diviner/query/DivinerAtomSearchEngine.php --- a/src/applications/diviner/query/DivinerAtomSearchEngine.php +++ b/src/applications/diviner/query/DivinerAtomSearchEngine.php @@ -14,20 +14,22 @@ $saved = new PhabricatorSavedQuery(); $saved->setParameter( + 'repositoryPHIDs', + $this->readPHIDsFromRequest($request, 'repositoryPHIDs')); + $saved->setParameter('name', $request->getStr('name')); + $saved->setParameter( 'types', $this->readListFromRequest($request, 'types')); - $saved->setParameter('name', $request->getStr('name')); - return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new DivinerAtomQuery()); - $types = $saved->getParameter('types'); - if ($types) { - $query->withTypes($types); + $repository_phids = $saved->getParameter('repositoryPHIDs'); + if ($repository_phids) { + $query->withRepositoryPHIDs($repository_phids); } $name = $saved->getParameter('name'); @@ -35,6 +37,11 @@ $query->withNameContains($name); } + $types = $saved->getParameter('types'); + if ($types) { + $query->withTypes($types); + } + return $query; } @@ -42,6 +49,12 @@ AphrontFormView $form, PhabricatorSavedQuery $saved) { + $form->appendChild( + id(new AphrontFormTextControl()) + ->setLabel(pht('Name Contains')) + ->setName('name') + ->setValue($saved->getParameter('name'))); + $all_types = array(); foreach (DivinerAtom::getAllTypes() as $type) { $all_types[$type] = DivinerAtom::getAtomTypeNameString($type); @@ -59,14 +72,14 @@ $name, isset($types[$type])); } - - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setLabel(pht('Name Contains')) - ->setName('name') - ->setValue($saved->getParameter('name'))) - ->appendChild($type_control); + $form->appendChild($type_control); + + $form->appendControl( + id(new AphrontFormTokenizerControl()) + ->setLabel(pht('Repositories')) + ->setName('repositoryPHIDs') + ->setDatasource(new DiffusionRepositoryDatasource()) + ->setValue($saved->getParameter('repositoryPHIDs'))); } protected function getURI($path) { diff --git a/src/applications/diviner/query/DivinerBookQuery.php b/src/applications/diviner/query/DivinerBookQuery.php --- a/src/applications/diviner/query/DivinerBookQuery.php +++ b/src/applications/diviner/query/DivinerBookQuery.php @@ -5,8 +5,10 @@ private $ids; private $phids; private $names; + private $repositoryPHIDs; private $needProjectPHIDs; + private $needRepositories; public function withIDs(array $ids) { $this->ids = $ids; @@ -23,11 +25,21 @@ return $this; } + public function withRepositoryPHIDs(array $repository_phids) { + $this->repositoryPHIDs = $repository_phids; + return $this; + } + public function needProjectPHIDs($need_phids) { $this->needProjectPHIDs = $need_phids; return $this; } + public function needRepositories($need_repositories) { + $this->needRepositories = $need_repositories; + return $this; + } + protected function loadPage() { $table = new DivinerLiveBook(); $conn_r = $table->establishConnection('r'); @@ -46,6 +58,31 @@ protected function didFilterPage(array $books) { assert_instances_of($books, 'DivinerLiveBook'); + if ($this->needRepositories) { + $repositories = id(new PhabricatorRepositoryQuery()) + ->setViewer($this->getViewer()) + ->withPHIDs(mpull($books, 'getRepositoryPHID')) + ->execute(); + $repositories = mpull($repositories, null, 'getPHID'); + + foreach ($books as $key => $book) { + if ($book->getRepositoryPHID() === null) { + $book->attachRepository(null); + continue; + } + + $repository = idx($repositories, $book->getRepositoryPHID()); + + if (!$repository) { + $this->didRejectResult($book); + unset($books[$key]); + continue; + } + + $book->attachRepository($repository); + } + } + if ($this->needProjectPHIDs) { $edge_query = id(new PhabricatorEdgeQuery()) ->withSourcePHIDs(mpull($books, 'getPHID')) @@ -91,6 +128,13 @@ $this->names); } + if ($this->repositoryPHIDs !== null) { + $where[] = qsprintf( + $conn_r, + 'repositoryPHID IN (%Ls)', + $this->repositoryPHIDs); + } + $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); diff --git a/src/applications/diviner/search/DivinerAtomSearchIndexer.php b/src/applications/diviner/search/DivinerAtomSearchIndexer.php --- a/src/applications/diviner/search/DivinerAtomSearchIndexer.php +++ b/src/applications/diviner/search/DivinerAtomSearchIndexer.php @@ -30,6 +30,12 @@ PhabricatorTime::getNow()); $doc->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY, + $atom->getRepositoryPHID(), + PhabricatorRepositoryRepositoryPHIDType::TYPECONST, + PhabricatorTime::getNow()); + + $doc->addRelationship( $atom->getGraphHash() ? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED : PhabricatorSearchRelationship::RELATIONSHIP_OPEN, diff --git a/src/applications/diviner/search/DivinerBookSearchIndexer.php b/src/applications/diviner/search/DivinerBookSearchIndexer.php --- a/src/applications/diviner/search/DivinerBookSearchIndexer.php +++ b/src/applications/diviner/search/DivinerBookSearchIndexer.php @@ -18,6 +18,12 @@ PhabricatorSearchDocumentFieldType::FIELD_BODY, $book->getPreface()); + $doc->addRelationship( + PhabricatorSearchRelationship::RELATIONSHIP_REPOSITORY, + $book->getRepositoryPHID(), + PhabricatorRepositoryRepositoryPHIDType::TYPECONST, + $book->getDateCreated()); + $this->indexTransactions( $doc, new DivinerLiveBookTransactionQuery(), diff --git a/src/applications/diviner/storage/DivinerLiveBook.php b/src/applications/diviner/storage/DivinerLiveBook.php --- a/src/applications/diviner/storage/DivinerLiveBook.php +++ b/src/applications/diviner/storage/DivinerLiveBook.php @@ -8,11 +8,13 @@ PhabricatorApplicationTransactionInterface { protected $name; + protected $repositoryPHID; protected $viewPolicy; protected $editPolicy; protected $configurationData = array(); private $projectPHIDs = self::ATTACHABLE; + private $repository = self::ATTACHABLE; protected function getConfiguration() { return array( @@ -22,6 +24,7 @@ ), self::CONFIG_COLUMN_SCHEMA => array( 'name' => 'text64', + 'repositoryPHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, @@ -68,6 +71,15 @@ return idx($spec, 'name', $group); } + public function attachRepository(PhabricatorRepository $repository = null) { + $this->repository = $repository; + return $this; + } + + public function getRepository() { + return $this->assertAttached($this->repository); + } + public function attachProjectPHIDs(array $project_phids) { $this->projectPHIDs = $project_phids; return $this; @@ -98,7 +110,7 @@ } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - return false; + return false; } public function describeAutomaticCapability($capability) { diff --git a/src/applications/diviner/storage/DivinerLiveSymbol.php b/src/applications/diviner/storage/DivinerLiveSymbol.php --- a/src/applications/diviner/storage/DivinerLiveSymbol.php +++ b/src/applications/diviner/storage/DivinerLiveSymbol.php @@ -7,6 +7,7 @@ PhabricatorDestructibleInterface { protected $bookPHID; + protected $repositoryPHID; protected $context; protected $type; protected $name; @@ -22,6 +23,7 @@ protected $isDocumentable = 0; private $book = self::ATTACHABLE; + private $repository = self::ATTACHABLE; private $atom = self::ATTACHABLE; private $extends = self::ATTACHABLE; private $children = self::ATTACHABLE; @@ -43,6 +45,7 @@ 'summary' => 'text?', 'isDocumentable' => 'bool', 'nodeHash' => 'text64?', + 'repositoryPHID' => 'phid?', ), self::CONFIG_KEY_SCHEMA => array( 'key_phid' => null, @@ -94,6 +97,15 @@ return $this; } + public function getRepository() { + return $this->assertAttached($this->repository); + } + + public function attachRepository(PhabricatorRepository $repository = null) { + $this->repository = $repository; + return $this; + } + public function getAtom() { return $this->assertAttached($this->atom); } diff --git a/src/applications/diviner/workflow/DivinerGenerateWorkflow.php b/src/applications/diviner/workflow/DivinerGenerateWorkflow.php --- a/src/applications/diviner/workflow/DivinerGenerateWorkflow.php +++ b/src/applications/diviner/workflow/DivinerGenerateWorkflow.php @@ -25,6 +25,11 @@ 'help' => pht('Specify a subclass of %s.', 'DivinerPublisher'), 'default' => 'DivinerLivePublisher', ), + array( + 'name' => 'repository', + 'param' => 'callsign', + 'help' => pht('Repository that the documentation belongs to.'), + ), )); } @@ -187,6 +192,24 @@ } $publisher = newv($publisher_class, array()); + $callsign = $args->getArg('repository'); + $repository = null; + if ($callsign) { + $repository = id(new PhabricatorRepositoryQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withCallsigns(array($callsign)) + ->executeOne(); + + if (!$repository) { + throw new PhutilArgumentUsageException( + pht( + "Repository '%s' does not exist.", + $callsign)); + } + + $publisher->setRepositoryPHID($repository->getPHID()); + } + $this->publishDocumentation($args->getArg('clean'), $publisher); } diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1913,7 +1913,25 @@ PhabricatorDestructionEngine $engine) { $this->openTransaction(); - $this->delete(); + + $this->delete(); + + $books = id(new DivinerBookQuery()) + ->setViewer($engine->getViewer()) + ->withRepositoryPHIDs(array($this->getPHID())) + ->execute(); + foreach ($books as $book) { + $engine->destroyObject($book); + } + + $atoms = id(new DivinerAtomQuery()) + ->setViewer($engine->getViewer()) + ->withRepositoryPHIDs(array($this->getPHID())) + ->execute(); + foreach ($atoms as $atom) { + $engine->destroyObject($atom); + } + $this->saveTransaction(); }