diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index f1fa375736..d2ee235162 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -1,568 +1,578 @@ getDiffusionRequest(); $repository = $drequest->getRepository(); $content = array(); $crumbs = $this->buildCrumbs(); $content[] = $crumbs; $content[] = $this->buildPropertiesTable($drequest->getRepository()); $phids = array(); try { $history_results = $this->callConduitWithDiffusionRequest( 'diffusion.historyquery', array( 'commit' => $drequest->getCommit(), 'path' => $drequest->getPath(), 'offset' => 0, 'limit' => 15)); $history = DiffusionPathChange::newFromConduit( $history_results['pathChanges']); foreach ($history as $item) { $data = $item->getCommitData(); if ($data) { if ($data->getCommitDetail('authorPHID')) { $phids[$data->getCommitDetail('authorPHID')] = true; } if ($data->getCommitDetail('committerPHID')) { $phids[$data->getCommitDetail('committerPHID')] = true; } } } $history_exception = null; } catch (Exception $ex) { $history_results = null; $history = null; $history_exception = $ex; } try { $browse_results = DiffusionBrowseResultSet::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.browsequery', array( 'path' => $drequest->getPath(), 'commit' => $drequest->getCommit(), ))); $browse_paths = $browse_results->getPaths(); foreach ($browse_paths as $item) { $data = $item->getLastCommitData(); if ($data) { if ($data->getCommitDetail('authorPHID')) { $phids[$data->getCommitDetail('authorPHID')] = true; } if ($data->getCommitDetail('committerPHID')) { $phids[$data->getCommitDetail('committerPHID')] = true; } } } $browse_exception = null; } catch (Exception $ex) { $browse_results = null; $browse_paths = null; $browse_exception = $ex; } $phids = array_keys($phids); $handles = $this->loadViewerHandles($phids); if ($browse_results) { $readme = $this->callConduitWithDiffusionRequest( 'diffusion.readmequery', array( 'paths' => $browse_results->getPathDicts() )); } else { $readme = null; } $content[] = $this->buildHistoryTable( $history_results, $history, $history_exception, $handles); $content[] = $this->buildBrowseTable( $browse_results, $browse_paths, $browse_exception, $handles); try { $content[] = $this->buildTagListTable($drequest); } catch (Exception $ex) { if (!$repository->isImporting()) { $content[] = $this->renderStatusMessage( pht('Unable to Load Tags'), $ex->getMessage()); } } try { $content[] = $this->buildBranchListTable($drequest); } catch (Exception $ex) { if (!$repository->isImporting()) { $content[] = $this->renderStatusMessage( pht('Unable to Load Branches'), $ex->getMessage()); } } if ($readme) { $box = new PHUIBoxView(); $box->setShadow(true); $box->appendChild($readme); $box->addPadding(PHUI::PADDING_LARGE); $panel = new AphrontPanelView(); $panel->setHeader(pht('README')); $panel->setNoBackground(); $panel->appendChild($box); $content[] = $panel; } return $this->buildApplicationPage( $content, array( 'title' => $drequest->getRepository()->getName(), 'device' => true, )); } private function buildPropertiesTable(PhabricatorRepository $repository) { $user = $this->getRequest()->getUser(); $header = id(new PHUIHeaderView()) ->setHeader($repository->getName()) ->setUser($user) ->setPolicyObject($repository); if (!$repository->isTracked()) { $header->setStatus('policy-noone', '', pht('Inactive')); } else if ($repository->isImporting()) { $header->setStatus('time', 'red', pht('Importing...')); } else { $header->setStatus('oh-ok', '', pht('Active')); } $actions = $this->buildActionList($repository); $view = id(new PHUIPropertyListView()) ->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(); $repo_path = '/diffusion/'.$callsign.'/'; $serve_ssh = $repository->getServeOverSSH(); if ($serve_ssh !== $serve_off) { $uri = new PhutilURI(PhabricatorEnv::getProductionURI($repo_path)); if ($repository->isSVN()) { $uri->setProtocol('svn+ssh'); } else { $uri->setProtocol('ssh'); } $ssh_user = PhabricatorEnv::getEnvConfig('diffusion.ssh-user'); if ($ssh_user) { $uri->setUser($ssh_user); } $uri->setPort(PhabricatorEnv::getEnvConfig('diffusion.ssh-port')); $clone_uri = $this->renderCloneURI( $uri, $serve_ssh, '/settings/panel/ssh/'); $view->addProperty(pht('Clone URI (SSH)'), $clone_uri); } $serve_http = $repository->getServeOverHTTP(); if ($serve_http !== $serve_off) { $http_uri = PhabricatorEnv::getProductionURI($repo_path); $clone_uri = $this->renderCloneURI( $http_uri, $serve_http, PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth') ? '/settings/panel/vcspassword/' : null); $view->addProperty(pht('Clone URI (HTTP)'), $clone_uri); } } else { switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $view->addProperty( pht('Clone URI'), $this->renderCloneURI( $repository->getPublicRemoteURI())); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $view->addProperty( pht('Repository Root'), $this->renderCloneURI( $repository->getPublicRemoteURI())); break; } } $description = $repository->getDetail('description'); if (strlen($description)) { $description = PhabricatorMarkupEngine::renderOneObject( $repository, 'description', $user); $view->addSectionHeader(pht('Description')); $view->addTextContent($description); } $view->setActionList($actions); return id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($view); } private function buildBranchListTable(DiffusionRequest $drequest) { $viewer = $this->getRequest()->getUser(); if ($drequest->getBranch() === null) { return null; } $limit = 15; $branches = DiffusionBranchInformation::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.branchquery', array( 'limit' => $limit + 1, ))); if (!$branches) { return null; } $more_branches = (count($branches) > $limit); $branches = array_slice($branches, 0, $limit); $commits = id(new DiffusionCommitQuery()) ->setViewer($viewer) ->withIdentifiers(mpull($branches, 'getHeadCommitIdentifier')) ->withRepository($drequest->getRepository()) ->execute(); $table = id(new DiffusionBranchTableView()) ->setUser($viewer) ->setDiffusionRequest($drequest) ->setBranches($branches) ->setCommits($commits); $panel = id(new AphrontPanelView()) ->setHeader(pht('Branches')) ->setNoBackground(); if ($more_branches) { $panel->setCaption(pht('Showing %d branches.', $limit)); } $panel->addButton( phutil_tag( 'a', array( 'href' => $drequest->generateURI( array( 'action' => 'branches', )), 'class' => 'grey button', ), pht("Show All Branches \xC2\xBB"))); $panel->appendChild($table); return $panel; } private function buildTagListTable(DiffusionRequest $drequest) { $viewer = $this->getRequest()->getUser(); $tag_limit = 15; $tags = array(); try { $tags = DiffusionRepositoryTag::newFromConduit( $this->callConduitWithDiffusionRequest( 'diffusion.tagsquery', array( // On the home page, we want to find tags on any branch. 'commit' => null, 'limit' => $tag_limit + 1, ))); } catch (ConduitException $e) { if ($e->getMessage() != 'ERR-UNSUPPORTED-VCS') { throw $e; } } if (!$tags) { return null; } $more_tags = (count($tags) > $tag_limit); $tags = array_slice($tags, 0, $tag_limit); $commits = id(new DiffusionCommitQuery()) ->setViewer($viewer) ->withIdentifiers(mpull($tags, 'getCommitIdentifier')) ->withRepository($drequest->getRepository()) ->needCommitData(true) ->execute(); $view = id(new DiffusionTagListView()) ->setUser($viewer) ->setDiffusionRequest($drequest) ->setTags($tags) ->setCommits($commits); $phids = $view->getRequiredHandlePHIDs(); $handles = $this->loadViewerHandles($phids); $view->setHandles($handles); $panel = id(new AphrontPanelView()) ->setHeader(pht('Tags')) ->setNoBackground(true); if ($more_tags) { $panel->setCaption(pht('Showing the %d most recent tags.', $tag_limit)); } $panel->addButton( phutil_tag( 'a', array( 'href' => $drequest->generateURI( array( 'action' => 'tags', )), 'class' => 'grey button', ), pht("Show All Tags \xC2\xBB"))); $panel->appendChild($view); return $panel; } private function buildActionList(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $view_uri = $this->getApplicationURI($repository->getCallsign().'/'); $edit_uri = $this->getApplicationURI($repository->getCallsign().'/edit/'); $view = id(new PhabricatorActionListView()) ->setUser($viewer) ->setObject($repository) ->setObjectURI($view_uri); $can_edit = PhabricatorPolicyFilter::hasCapability( $viewer, $repository, PhabricatorPolicyCapability::CAN_EDIT); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Edit Repository')) ->setIcon('edit') ->setHref($edit_uri) ->setWorkflow(!$can_edit) ->setDisabled(!$can_edit)); if ($repository->isHosted()) { $callsign = $repository->getCallsign(); $push_uri = $this->getApplicationURI( 'pushlog/?repositories=r'.$callsign); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('View Push Logs')) ->setIcon('transcript') ->setHref($push_uri)); } return $view; } private function buildHistoryTable( $history_results, $history, $history_exception, array $handles) { $request = $this->getRequest(); $viewer = $request->getUser(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); if ($history_exception) { if ($repository->isImporting()) { return $this->renderStatusMessage( pht('Still Importing...'), pht( 'This repository is still importing. History is not yet '. 'available.')); } else { return $this->renderStatusMessage( pht('Unable to Retrieve History'), $history_exception->getMessage()); } } $history_table = id(new DiffusionHistoryTableView()) ->setUser($viewer) ->setDiffusionRequest($drequest) ->setHandles($handles) ->setHistory($history); // TODO: Super sketchy. $history_table->loadRevisions(); if ($history_results) { $history_table->setParents($history_results['parents']); } $history_table->setIsHead(true); $callsign = $drequest->getRepository()->getCallsign(); $all = phutil_tag( 'a', array( 'href' => $drequest->generateURI( array( 'action' => 'history', )), ), pht('View Full Commit History')); $panel = new AphrontPanelView(); $panel->setHeader(pht("Recent Commits · %s", $all)); $panel->appendChild($history_table); $panel->setNoBackground(); return $panel; } private function buildBrowseTable( $browse_results, $browse_paths, $browse_exception, array $handles) { $request = $this->getRequest(); $viewer = $request->getUser(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); if ($browse_exception) { if ($repository->isImporting()) { // The history table renders a useful message. return null; } else { return $this->renderStatusMessage( pht('Unable to Retrieve Paths'), $browse_exception->getMessage()); } } $browse_table = id(new DiffusionBrowseTableView()) ->setUser($viewer) ->setDiffusionRequest($drequest) ->setHandles($handles); if ($browse_paths) { $browse_table->setPaths($browse_paths); } else { $browse_table->setPaths(array()); } $browse_uri = $drequest->generateURI(array('action' => 'browse')); $browse_panel = new AphrontPanelView(); $browse_panel->setHeader( phutil_tag( 'a', array('href' => $browse_uri), pht('Browse Repository'))); $browse_panel->appendChild($browse_table); $browse_panel->setNoBackground(); return $browse_panel; } private function renderCloneURI( $uri, $serve_mode = null, $manage_uri = null) { require_celerity_resource('diffusion-icons-css'); Javelin::initBehavior('select-on-click'); $input = javelin_tag( 'input', array( 'type' => 'text', 'value' => (string)$uri, 'class' => 'diffusion-clone-uri', 'sigil' => 'select-on-click', 'readonly' => 'true', )); $extras = array(); if ($serve_mode) { if ($serve_mode === PhabricatorRepository::SERVE_READONLY) { $extras[] = pht('(Read Only)'); } } if ($manage_uri) { if ($this->getRequest()->getUser()->isLoggedIn()) { $extras[] = phutil_tag( 'a', array( 'href' => $manage_uri, ), pht('Manage Credentials')); } } if ($extras) { $extras = phutil_implode_html(' ', $extras); $extras = phutil_tag( 'div', array( 'class' => 'diffusion-clone-extras', ), $extras); } return array($input, $extras); } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php index 7729b5d164..f946b4dcdd 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php @@ -1,133 +1,154 @@ getRequest(); $user = $request->getUser(); $drequest = $this->diffusionRequest; $repository = $drequest->getRepository(); $repository = id(new PhabricatorRepositoryQuery()) ->setViewer($user) ->requireCapabilities( array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, )) + ->needProjectPHIDs(true) ->withIDs(array($repository->getID())) ->executeOne(); if (!$repository) { return new Aphront404Response(); } $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); $v_name = $repository->getName(); $v_desc = $repository->getDetail('description'); $e_name = true; $errors = array(); 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'); $errors[] = pht('Repository name is required.'); } else { $e_name = null; } if (!$errors) { $xactions = array(); $template = id(new PhabricatorRepositoryTransaction()); $type_name = PhabricatorRepositoryTransaction::TYPE_NAME; $type_desc = PhabricatorRepositoryTransaction::TYPE_DESCRIPTION; + $type_edge = PhabricatorTransactions::TYPE_EDGE; $xactions[] = id(clone $template) ->setTransactionType($type_name) ->setNewValue($v_name); $xactions[] = id(clone $template) ->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) ->setActor($user) ->applyTransactions($repository, $xactions); return id(new AphrontRedirectResponse())->setURI($edit_uri); } } $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Edit Basics')); $title = pht('Edit %s', $repository->getName()); $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle(pht('Form Errors')) ->setErrors($errors); } + $project_handles = $this->loadViewerHandles($repository->getProjectPHIDs()); + $form = id(new AphrontFormView()) ->setUser($user) ->appendChild( id(new AphrontFormTextControl()) ->setName('name') ->setLabel(pht('Name')) ->setValue($v_name) ->setError($e_name)) ->appendChild( id(new PhabricatorRemarkupControl()) ->setName('description') ->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)) ->appendChild(id(new PHUIFormDividerControl())) ->appendRemarkupInstructions($this->getReadmeInstructions()); $object_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setForm($form) ->setFormError($error_view); return $this->buildApplicationPage( array( $crumbs, $object_box), array( 'title' => $title, 'device' => true, )); } private function getReadmeInstructions() { return pht(<<getRequest(); $viewer = $request->getUser(); $drequest = $this->diffusionRequest; $repository = $drequest->getRepository(); PhabricatorPolicyFilter::requireCapability( $viewer, $repository, PhabricatorPolicyCapability::CAN_EDIT); $is_svn = false; $is_git = false; $is_hg = false; switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $is_git = true; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $is_svn = true; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $is_hg = true; break; } $has_branches = ($is_git || $is_hg); $has_local = $repository->usesLocalWorkingCopy(); $crumbs = $this->buildApplicationCrumbs($is_main = true); $title = pht('Edit %s', $repository->getName()); $header = id(new PHUIHeaderView()) ->setHeader($title); if ($repository->isTracked()) { $header->setStatus('oh-ok', '', pht('Active')); } else { $header->setStatus('policy-noone', '', pht('Inactive')); } $basic_actions = $this->buildBasicActions($repository); $basic_properties = $this->buildBasicProperties($repository, $basic_actions); $policy_actions = $this->buildPolicyActions($repository); $policy_properties = $this->buildPolicyProperties($repository, $policy_actions); $remote_properties = null; if (!$repository->isHosted()) { $remote_properties = $this->buildRemoteProperties( $repository, $this->buildRemoteActions($repository)); } $encoding_actions = $this->buildEncodingActions($repository); $encoding_properties = $this->buildEncodingProperties($repository, $encoding_actions); $hosting_properties = $this->buildHostingProperties( $repository, $this->buildHostingActions($repository)); $branches_properties = null; if ($has_branches) { $branches_properties = $this->buildBranchesProperties( $repository, $this->buildBranchesActions($repository)); } $subversion_properties = null; if ($is_svn) { $subversion_properties = $this->buildSubversionProperties( $repository, $this->buildSubversionActions($repository)); } $local_properties = null; if ($has_local) { $local_properties = $this->buildLocalProperties( $repository, $this->buildLocalActions($repository)); } $actions_properties = $this->buildActionsProperties( $repository, $this->buildActionsActions($repository)); $xactions = id(new PhabricatorRepositoryTransactionQuery()) ->setViewer($viewer) ->withObjectPHIDs(array($repository->getPHID())) ->execute(); $engine = id(new PhabricatorMarkupEngine()) ->setViewer($viewer); foreach ($xactions as $xaction) { if ($xaction->getComment()) { $engine->addObject( $xaction->getComment(), PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT); } } $engine->process(); $xaction_view = id(new PhabricatorApplicationTransactionView()) ->setUser($viewer) ->setObjectPHID($repository->getPHID()) ->setTransactions($xactions) ->setMarkupEngine($engine); $boxes = array(); $boxes[] = id(new PHUIObjectBoxView()) ->setHeader($header) ->addPropertyList($basic_properties); $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Policies')) ->addPropertyList($policy_properties); $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Hosting')) ->addPropertyList($hosting_properties); if ($repository->canMirror()) { $mirror_actions = $this->buildMirrorActions($repository); $mirror_properties = $this->buildMirrorProperties( $repository, $mirror_actions); $mirrors = id(new PhabricatorRepositoryMirrorQuery()) ->setViewer($viewer) ->withRepositoryPHIDs(array($repository->getPHID())) ->execute(); $mirror_list = $this->buildMirrorList($repository, $mirrors); $boxes[] = id(new PhabricatorAnchorView())->setAnchorName('mirrors'); $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Mirrors')) ->addPropertyList($mirror_properties); $boxes[] = $mirror_list; } if ($remote_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Remote')) ->addPropertyList($remote_properties); } if ($local_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Local')) ->addPropertyList($local_properties); } $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Text Encoding')) ->addPropertyList($encoding_properties); if ($branches_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Branches')) ->addPropertyList($branches_properties); } if ($subversion_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Subversion')) ->addPropertyList($subversion_properties); } $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Actions')) ->addPropertyList($actions_properties); return $this->buildApplicationPage( array( $crumbs, $boxes, $xaction_view, ), array( 'title' => $title, 'device' => true, )); } private function buildBasicActions(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('edit') ->setName(pht('Edit Basic Information')) ->setHref($this->getRepositoryControllerURI($repository, 'edit/basic/')); $view->addAction($edit); $activate = id(new PhabricatorActionView()) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/activate/')) ->setWorkflow(true); if ($repository->isTracked()) { $activate ->setIcon('disable') ->setName(pht('Deactivate Repository')); } else { $activate ->setIcon('enable') ->setName(pht('Activate Repository')); } $view->addAction($activate); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete Repository')) ->setIcon('delete') ->setHref( $this->getRepositoryControllerURI($repository, 'edit/delete/')) ->setDisabled(true) ->setWorkflow(true)); return $view; } private function buildBasicProperties( PhabricatorRepository $repository, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setActionList($actions); $type = PhabricatorRepositoryType::getNameForRepositoryType( $repository->getVersionControlSystem()); $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)); $description = $repository->getDetail('description'); $view->addSectionHeader(pht('Description')); if (!strlen($description)) { $description = phutil_tag('em', array(), pht('No description provided.')); } else { $description = PhabricatorMarkupEngine::renderOneObject( $repository, 'description', $viewer); } $view->addTextContent($description); return $view; } private function buildEncodingActions(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('edit') ->setName(pht('Edit Text Encoding')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/encoding/')); $view->addAction($edit); return $view; } private function buildEncodingProperties( PhabricatorRepository $repository, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setActionList($actions); $encoding = $repository->getDetail('encoding'); if (!$encoding) { $encoding = phutil_tag('em', array(), pht('Use Default (UTF-8)')); } $view->addProperty(pht('Encoding'), $encoding); return $view; } private function buildPolicyActions(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('edit') ->setName(pht('Edit Policies')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/policy/')); $view->addAction($edit); return $view; } private function buildPolicyProperties( PhabricatorRepository $repository, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setActionList($actions); $descriptions = PhabricatorPolicyQuery::renderPolicyDescriptions( $viewer, $repository); $view->addProperty( pht('Visible To'), $descriptions[PhabricatorPolicyCapability::CAN_VIEW]); $view->addProperty( pht('Editable By'), $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); $pushable = $repository->isHosted() ? $descriptions[DiffusionCapabilityPush::CAPABILITY] : phutil_tag('em', array(), pht('Not a Hosted Repository')); $view->addProperty(pht('Pushable By'), $pushable); return $view; } private function buildBranchesActions(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('edit') ->setName(pht('Edit Branches')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/branches/')); $view->addAction($edit); return $view; } private function buildBranchesProperties( PhabricatorRepository $repository, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setActionList($actions); $default_branch = nonempty( $repository->getHumanReadableDetail('default-branch'), phutil_tag('em', array(), $repository->getDefaultBranch())); $view->addProperty(pht('Default Branch'), $default_branch); $track_only = nonempty( $repository->getHumanReadableDetail('branch-filter', array()), phutil_tag('em', array(), pht('Track All Branches'))); $view->addProperty(pht('Track Only'), $track_only); $autoclose_only = nonempty( $repository->getHumanReadableDetail('close-commits-filter', array()), phutil_tag('em', array(), pht('Autoclose On All Branches'))); if ($repository->getDetail('disable-autoclose')) { $autoclose_only = phutil_tag('em', array(), pht('Disabled')); } $view->addProperty(pht('Autoclose Only'), $autoclose_only); return $view; } private function buildSubversionActions(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('edit') ->setName(pht('Edit Subversion Info')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/subversion/')); $view->addAction($edit); return $view; } private function buildSubversionProperties( PhabricatorRepository $repository, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setActionList($actions); $svn_uuid = nonempty( $repository->getUUID(), phutil_tag('em', array(), pht('Not Configured'))); $view->addProperty(pht('Subversion UUID'), $svn_uuid); $svn_subpath = nonempty( $repository->getHumanReadableDetail('svn-subpath'), phutil_tag('em', array(), pht('Import Entire Repository'))); $view->addProperty(pht('Import Only'), $svn_subpath); return $view; } private function buildActionsActions(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('edit') ->setName(pht('Edit Actions')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/actions/')); $view->addAction($edit); return $view; } private function buildActionsProperties( PhabricatorRepository $repository, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setActionList($actions); $notify = $repository->getDetail('herald-disabled') ? pht('Off') : pht('On'); $notify = phutil_tag('em', array(), $notify); $view->addProperty(pht('Publish/Notify'), $notify); $autoclose = $repository->getDetail('disable-autoclose') ? pht('Off') : pht('On'); $autoclose = phutil_tag('em', array(), $autoclose); $view->addProperty(pht('Autoclose'), $autoclose); return $view; } private function buildRemoteActions(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('edit') ->setName(pht('Edit Remote')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/remote/')); $view->addAction($edit); return $view; } private function buildRemoteProperties( PhabricatorRepository $repository, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setActionList($actions); $view->addProperty( pht('Remote URI'), $repository->getHumanReadableDetail('remote-uri')); $credential_phid = $repository->getCredentialPHID(); if ($credential_phid) { $this->loadHandles(array($credential_phid)); $view->addProperty( pht('Credential'), $this->getHandle($credential_phid)->renderLink()); } return $view; } private function buildLocalActions(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('edit') ->setName(pht('Edit Local')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/local/')); $view->addAction($edit); return $view; } private function buildLocalProperties( PhabricatorRepository $repository, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setActionList($actions); $view->addProperty( pht('Local Path'), $repository->getHumanReadableDetail('local-path')); return $view; } private function buildHostingActions(PhabricatorRepository $repository) { $user = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($user); $edit = id(new PhabricatorActionView()) ->setIcon('edit') ->setName(pht('Edit Hosting')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/hosting/')); $view->addAction($edit); if ($repository->canAllowDangerousChanges()) { if ($repository->shouldAllowDangerousChanges()) { $changes = id(new PhabricatorActionView()) ->setIcon('blame') ->setName(pht('Prevent Dangerous Changes')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/dangerous/')) ->setWorkflow(true); } else { $changes = id(new PhabricatorActionView()) ->setIcon('warning') ->setName(pht('Allow Dangerous Changes')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/dangerous/')) ->setWorkflow(true); } $view->addAction($changes); } return $view; } private function buildHostingProperties( PhabricatorRepository $repository, PhabricatorActionListView $actions) { $user = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($user) ->setActionList($actions); $hosting = $repository->isHosted() ? pht('Hosted on Phabricator') : pht('Hosted Elsewhere'); $view->addProperty(pht('Hosting'), phutil_tag('em', array(), $hosting)); $view->addProperty( pht('Serve over HTTP'), phutil_tag( 'em', array(), PhabricatorRepository::getProtocolAvailabilityName( $repository->getServeOverHTTP()))); $view->addProperty( pht('Serve over SSH'), phutil_tag( 'em', array(), PhabricatorRepository::getProtocolAvailabilityName( $repository->getServeOverSSH()))); if ($repository->canAllowDangerousChanges()) { if ($repository->shouldAllowDangerousChanges()) { $description = pht('Allowed'); } else { $description = pht('Not Allowed'); } $view->addProperty( pht('Dangerous Changes'), $description); } return $view; } private function buildRepositoryStatus( PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $view = new PHUIStatusListView(); $messages = id(new PhabricatorRepositoryStatusMessage()) ->loadAllWhere('repositoryID = %d', $repository->getID()); $messages = mpull($messages, null, 'getStatusType'); if ($repository->isTracked()) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('accept-green') ->setTarget(pht('Repository Active'))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('warning') ->setTarget(pht('Repository Inactive')) ->setNote( pht('Activate this repository to begin or resume import.'))); return $view; } $binaries = array(); switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $binaries[] = 'git'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $binaries[] = 'svn'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $binaries[] = 'hg'; break; } if ($repository->isHosted()) { if ($repository->getServeOverHTTP() != PhabricatorRepository::SERVE_OFF) { switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $binaries[] = 'git-http-backend'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $binaries[] = 'svnserve'; $binaries[] = 'svnadmin'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $binaries[] = 'hg'; break; } } if ($repository->getServeOverSSH() != PhabricatorRepository::SERVE_OFF) { switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $binaries[] = 'git-receive-pack'; $binaries[] = 'git-upload-pack'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $binaries[] = 'svnserve'; $binaries[] = 'svnadmin'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $binaries[] = 'hg'; break; } } } $binaries = array_unique($binaries); foreach ($binaries as $binary) { $where = Filesystem::resolveBinary($binary); if (!$where) { $config_href = '/config/edit/environment.append-paths/'; $config_link = phutil_tag( 'a', array( 'href' => $config_href, ), 'environment.append-paths'); $view->addItem( id(new PHUIStatusItemView()) ->setIcon('warning-red') ->setTarget( pht('Missing Binary %s', phutil_tag('tt', array(), $binary))) ->setNote(pht( "Unable to find this binary in the webserver's PATH. You may ". "need to configure %s.", $config_link))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('accept-green') ->setTarget( pht('Found Binary %s', phutil_tag('tt', array(), $binary))) ->setNote(phutil_tag('tt', array(), $where))); } } $doc_href = PhabricatorEnv::getDocLink( 'article/Managing_Daemons_with_phd.html'); $daemon_instructions = pht( 'Use %s to start daemons. See %s.', phutil_tag('tt', array(), 'bin/phd start'), phutil_tag( 'a', array( 'href' => $doc_href, ), pht('Managing Daemons with phd'))); $pull_daemon = id(new PhabricatorDaemonLogQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) ->withDaemonClasses(array('PhabricatorRepositoryPullLocalDaemon')) ->setLimit(1) ->execute(); if ($pull_daemon) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('accept-green') ->setTarget(pht('Pull Daemon Running'))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('warning-red') ->setTarget(pht('Pull Daemon Not Running')) ->setNote($daemon_instructions)); } $task_daemon = id(new PhabricatorDaemonLogQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withStatus(PhabricatorDaemonLogQuery::STATUS_ALIVE) ->withDaemonClasses(array('PhabricatorTaskmasterDaemon')) ->setLimit(1) ->execute(); if ($task_daemon) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('accept-green') ->setTarget(pht('Task Daemon Running'))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('warning-red') ->setTarget(pht('Task Daemon Not Running')) ->setNote($daemon_instructions)); } if ($repository->usesLocalWorkingCopy()) { $local_parent = dirname($repository->getLocalPath()); if (Filesystem::pathExists($local_parent)) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('accept-green') ->setTarget(pht('Storage Directory OK')) ->setNote(phutil_tag('tt', array(), $local_parent))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('warning-red') ->setTarget(pht('No Storage Directory')) ->setNote( pht( 'Storage directory %s does not exist, or is not readable by '. 'the webserver. Create this directory or make it readable.', phutil_tag('tt', array(), $local_parent)))); return $view; } $local_path = $repository->getLocalPath(); $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_INIT); if ($message) { switch ($message->getStatusCode()) { case PhabricatorRepositoryStatusMessage::CODE_ERROR: $view->addItem( id(new PHUIStatusItemView()) ->setIcon('warning-red') ->setTarget(pht('Initialization Error')) ->setNote($message->getParameter('message'))); return $view; case PhabricatorRepositoryStatusMessage::CODE_OKAY: if (Filesystem::pathExists($local_path)) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('accept-green') ->setTarget(pht('Working Copy OK')) ->setNote(phutil_tag('tt', array(), $local_path))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('warning-red') ->setTarget(pht('Working Copy Error')) ->setNote( pht( 'Working copy %s has been deleted, or is not '. 'readable by the webserver. Make this directory '. 'readable. If it has been deleted, the daemons should '. 'restore it automatically.', phutil_tag('tt', array(), $local_path)))); return $view; } break; case PhabricatorRepositoryStatusMessage::CODE_WORKING: $view->addItem( id(new PHUIStatusItemView()) ->setIcon('time-green') ->setTarget(pht('Initializing Working Copy')) ->setNote(pht('Daemons are initializing the working copy.'))); return $view; default: $view->addItem( id(new PHUIStatusItemView()) ->setIcon('warning-red') ->setTarget(pht('Unknown Init Status')) ->setNote($message->getStatusCode())); return $view; } } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('time-orange') ->setTarget(pht('No Working Copy Yet')) ->setNote( pht('Waiting for daemons to build a working copy.'))); return $view; } } $message = idx($messages, PhabricatorRepositoryStatusMessage::TYPE_FETCH); if ($message) { switch ($message->getStatusCode()) { case PhabricatorRepositoryStatusMessage::CODE_ERROR: $view->addItem( id(new PHUIStatusItemView()) ->setIcon('warning-red') ->setTarget(pht('Update Error')) ->setNote($message->getParameter('message'))); return $view; case PhabricatorRepositoryStatusMessage::CODE_OKAY: $view->addItem( id(new PHUIStatusItemView()) ->setIcon('accept-green') ->setTarget(pht('Updates OK')) ->setNote( pht( 'Last updated %s.', phabricator_datetime($message->getEpoch(), $viewer)))); break; } } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('time-orange') ->setTarget(pht('Waiting For Update')) ->setNote( pht('Waiting for daemons to read updates.'))); } if ($repository->isImporting()) { $progress = queryfx_all( $repository->establishConnection('r'), 'SELECT importStatus, count(*) N FROM %T WHERE repositoryID = %d GROUP BY importStatus', id(new PhabricatorRepositoryCommit())->getTableName(), $repository->getID()); $done = 0; $total = 0; foreach ($progress as $row) { $total += $row['N'] * 4; $status = $row['importStatus']; if ($status & PhabricatorRepositoryCommit::IMPORTED_MESSAGE) { $done += $row['N']; } if ($status & PhabricatorRepositoryCommit::IMPORTED_CHANGE) { $done += $row['N']; } if ($status & PhabricatorRepositoryCommit::IMPORTED_OWNERS) { $done += $row['N']; } if ($status & PhabricatorRepositoryCommit::IMPORTED_HERALD) { $done += $row['N']; } } if ($total) { $percentage = 100 * ($done / $total); } else { $percentage = 0; } $percentage = sprintf('%.1f%%', $percentage); $view->addItem( id(new PHUIStatusItemView()) ->setIcon('time-green') ->setTarget(pht('Importing')) ->setNote( pht('%s Complete', $percentage))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('accept-green') ->setTarget(pht('Fully Imported'))); } if (idx($messages, PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE)) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon('up') ->setTarget(pht('Prioritized')) ->setNote(pht('This repository will be updated soon.'))); } return $view; } private function buildMirrorActions( PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $mirror_actions = id(new PhabricatorActionListView()) ->setObjectURI($this->getRequest()->getRequestURI()) ->setUser($viewer); $new_mirror_uri = $this->getRepositoryControllerURI( $repository, 'mirror/edit/'); $mirror_actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Add Mirror')) ->setIcon('new') ->setHref($new_mirror_uri) ->setWorkflow(true)); return $mirror_actions; } private function buildMirrorProperties( PhabricatorRepository $repository, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); $mirror_properties = id(new PHUIPropertyListView()) ->setUser($viewer) ->setActionList($actions); $mirror_properties->addProperty( '', phutil_tag( 'em', array(), pht('Automatically push changes into other remotes.'))); return $mirror_properties; } private function buildMirrorList( PhabricatorRepository $repository, array $mirrors) { assert_instances_of($mirrors, 'PhabricatorRepositoryMirror'); $mirror_list = id(new PHUIObjectItemListView()) ->setNoDataString(pht('This repository has no configured mirrors.')); foreach ($mirrors as $mirror) { $item = id(new PHUIObjectItemView()) ->setHeader($mirror->getRemoteURI()); $edit_uri = $this->getRepositoryControllerURI( $repository, 'mirror/edit/'.$mirror->getID().'/'); $delete_uri = $this->getRepositoryControllerURI( $repository, 'mirror/delete/'.$mirror->getID().'/'); $item->addAction( id(new PHUIListItemView()) ->setIcon('edit') ->setHref($edit_uri) ->setWorkflow(true)); $item->addAction( id(new PHUIListItemView()) ->setIcon('delete') ->setHref($delete_uri) ->setWorkflow(true)); $mirror_list->addItem($item); } return $mirror_list; } } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryListController.php b/src/applications/diffusion/controller/DiffusionRepositoryListController.php index 6883abdf6e..920d165e6f 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryListController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryListController.php @@ -1,117 +1,132 @@ queryKey = idx($data, 'queryKey'); } public function processRequest() { $request = $this->getRequest(); $controller = id(new PhabricatorApplicationSearchController($request)) ->setQueryKey($this->queryKey) ->setSearchEngine(new PhabricatorRepositorySearchEngine()) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); } public function renderResultsList( array $repositories, PhabricatorSavedQuery $query) { assert_instances_of($repositories, 'PhabricatorRepository'); $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(); $item = id(new PHUIObjectItemView()) ->setUser($viewer) ->setHeader($repository->getName()) ->setHref($this->getApplicationURI($repository->getCallsign().'/')); $commit = $repository->getMostRecentCommit(); if ($commit) { $commit_link = DiffusionView::linkCommit( $repository, $commit->getCommitIdentifier(), $commit->getSummary()); $item->setSubhead($commit_link); $item->setEpoch($commit->getEpoch()); } - $item->addAttribute( + $item->addIcon( + 'none', PhabricatorRepositoryType::getNameForRepositoryType( $repository->getVersionControlSystem())); $size = $repository->getCommitCount(); if ($size) { $history_uri = DiffusionRequest::generateDiffusionURI( array( 'callsign' => $repository->getCallsign(), 'action' => 'history', )); $item->addAttribute( phutil_tag( 'a', array( 'href' => $history_uri, ), pht('%s Commit(s)', new PhutilNumber($size)))); } else { $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')); } $list->addItem($item); } return $list; } public function buildSideNavView($for_app = false) { $viewer = $this->getRequest()->getUser(); $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); id(new PhabricatorRepositorySearchEngine()) ->setViewer($viewer) ->addNavigationItems($nav->getMenu()); $nav->selectFilter(null); return $nav; } public function buildApplicationCrumbs() { $crumbs = parent::buildApplicationCrumbs(); $can_create = $this->hasApplicationCapability( DiffusionCapabilityCreateRepositories::CAPABILITY); $crumbs->addAction( id(new PHUIListItemView()) ->setName(pht('New Repository')) ->setHref($this->getApplicationURI('new/')) ->setDisabled(!$can_create) ->setIcon('create')); return $crumbs; } } diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php index f7af1a3598..19a747cbc0 100644 --- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php +++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php @@ -1,304 +1,305 @@ getTransactionType()) { case PhabricatorRepositoryTransaction::TYPE_VCS: return $object->getVersionControlSystem(); case PhabricatorRepositoryTransaction::TYPE_ACTIVATE: return $object->isTracked(); case PhabricatorRepositoryTransaction::TYPE_NAME: return $object->getName(); case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION: return $object->getDetail('description'); case PhabricatorRepositoryTransaction::TYPE_ENCODING: return $object->getDetail('encoding'); case PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH: return $object->getDetail('default-branch'); case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY: return array_keys($object->getDetail('branch-filter', array())); case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY: return array_keys($object->getDetail('close-commits-filter', array())); case PhabricatorRepositoryTransaction::TYPE_UUID: return $object->getUUID(); case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH: return $object->getDetail('svn-subpath'); case PhabricatorRepositoryTransaction::TYPE_NOTIFY: return (int)!$object->getDetail('herald-disabled'); case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: return (int)!$object->getDetail('disable-autoclose'); case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: return $object->getDetail('remote-uri'); case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: return $object->getDetail('local-path'); case PhabricatorRepositoryTransaction::TYPE_HOSTING: return $object->isHosted(); case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP: return $object->getServeOverHTTP(); case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH: return $object->getServeOverSSH(); case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: return $object->getPushPolicy(); case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: return $object->getCredentialPHID(); case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: return $object->shouldAllowDangerousChanges(); } } protected function getCustomTransactionNewValue( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorRepositoryTransaction::TYPE_ACTIVATE: case PhabricatorRepositoryTransaction::TYPE_NAME: case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION: case PhabricatorRepositoryTransaction::TYPE_ENCODING: case PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH: case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY: case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY: case PhabricatorRepositoryTransaction::TYPE_UUID: case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH: case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: case PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN: case PhabricatorRepositoryTransaction::TYPE_SSH_KEY: case PhabricatorRepositoryTransaction::TYPE_SSH_KEYFILE: case PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN: case PhabricatorRepositoryTransaction::TYPE_HTTP_PASS: case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: case PhabricatorRepositoryTransaction::TYPE_VCS: case PhabricatorRepositoryTransaction::TYPE_HOSTING: case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP: case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH: case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: return $xaction->getNewValue(); case PhabricatorRepositoryTransaction::TYPE_NOTIFY: case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: return (int)$xaction->getNewValue(); } } protected function applyCustomInternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorRepositoryTransaction::TYPE_VCS: $object->setVersionControlSystem($xaction->getNewValue()); break; case PhabricatorRepositoryTransaction::TYPE_ACTIVATE: $object->setDetail('tracking-enabled', $xaction->getNewValue()); break; case PhabricatorRepositoryTransaction::TYPE_NAME: $object->setName($xaction->getNewValue()); break; case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION: $object->setDetail('description', $xaction->getNewValue()); break; case PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH: $object->setDetail('default-branch', $xaction->getNewValue()); break; case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY: $object->setDetail( 'branch-filter', array_fill_keys($xaction->getNewValue(), true)); break; case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY: $object->setDetail( 'close-commits-filter', array_fill_keys($xaction->getNewValue(), true)); break; case PhabricatorRepositoryTransaction::TYPE_UUID: $object->setUUID($xaction->getNewValue()); break; case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH: $object->setDetail('svn-subpath', $xaction->getNewValue()); break; case PhabricatorRepositoryTransaction::TYPE_NOTIFY: $object->setDetail('herald-disabled', (int)!$xaction->getNewValue()); break; case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: $object->setDetail('disable-autoclose', (int)!$xaction->getNewValue()); break; case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: $object->setDetail('remote-uri', $xaction->getNewValue()); break; case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: $object->setDetail('local-path', $xaction->getNewValue()); break; case PhabricatorRepositoryTransaction::TYPE_HOSTING: return $object->setHosted($xaction->getNewValue()); case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP: return $object->setServeOverHTTP($xaction->getNewValue()); case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH: return $object->setServeOverSSH($xaction->getNewValue()); case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: return $object->setPushPolicy($xaction->getNewValue()); case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: return $object->setCredentialPHID($xaction->getNewValue()); case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: $object->setDetail('allow-dangerous-changes', $xaction->getNewValue()); return; case PhabricatorRepositoryTransaction::TYPE_ENCODING: // Make sure the encoding is valid by converting to UTF-8. This tests // that the user has mbstring installed, and also that they didn't type // a garbage encoding name. Note that we're converting from UTF-8 to // the target encoding, because mbstring is fine with converting from // a nonsense encoding. $encoding = $xaction->getNewValue(); if (strlen($encoding)) { try { phutil_utf8_convert('.', $encoding, 'UTF-8'); } catch (Exception $ex) { throw new PhutilProxyException( pht( "Error setting repository encoding '%s': %s'", $encoding, $ex->getMessage()), $ex); } } $object->setDetail('encoding', $encoding); break; } } protected function applyCustomExternalTransaction( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: // Adjust the object <-> credential edge for this repository. $old_phid = $xaction->getOldValue(); $new_phid = $xaction->getNewValue(); $editor = id(new PhabricatorEdgeEditor()) ->setActor($this->requireActor()); $edge_type = PhabricatorEdgeConfig::TYPE_OBJECT_USES_CREDENTIAL; $src_phid = $object->getPHID(); if ($old_phid) { $editor->removeEdge($src_phid, $edge_type, $old_phid); } if ($new_phid) { $editor->addEdge($src_phid, $edge_type, $new_phid); } $editor->save(); break; } } protected function mergeTransactions( PhabricatorApplicationTransaction $u, PhabricatorApplicationTransaction $v) { $type = $u->getTransactionType(); switch ($type) { } return parent::mergeTransactions($u, $v); } protected function transactionHasEffect( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); $type = $xaction->getTransactionType(); switch ($type) { } return parent::transactionHasEffect($object, $xaction); } protected function requireCapabilities( PhabricatorLiskDAO $object, PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { case PhabricatorRepositoryTransaction::TYPE_ACTIVATE: case PhabricatorRepositoryTransaction::TYPE_NAME: case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION: case PhabricatorRepositoryTransaction::TYPE_ENCODING: case PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH: case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY: case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY: case PhabricatorRepositoryTransaction::TYPE_UUID: case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH: case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: case PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN: case PhabricatorRepositoryTransaction::TYPE_SSH_KEY: case PhabricatorRepositoryTransaction::TYPE_SSH_KEYFILE: case PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN: case PhabricatorRepositoryTransaction::TYPE_HTTP_PASS: case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: case PhabricatorRepositoryTransaction::TYPE_VCS: case PhabricatorRepositoryTransaction::TYPE_NOTIFY: case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: case PhabricatorRepositoryTransaction::TYPE_HOSTING: case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_HTTP: case PhabricatorRepositoryTransaction::TYPE_PROTOCOL_SSH: case PhabricatorRepositoryTransaction::TYPE_PUSH_POLICY: case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: case PhabricatorRepositoryTransaction::TYPE_DANGEROUS: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, PhabricatorPolicyCapability::CAN_EDIT); break; } } } diff --git a/src/applications/repository/query/PhabricatorRepositoryQuery.php b/src/applications/repository/query/PhabricatorRepositoryQuery.php index 327fe36ba8..dad3595745 100644 --- a/src/applications/repository/query/PhabricatorRepositoryQuery.php +++ b/src/applications/repository/query/PhabricatorRepositoryQuery.php @@ -1,338 +1,364 @@ ids = $ids; return $this; } public function withPHIDs(array $phids) { $this->phids = $phids; return $this; } public function withCallsigns(array $callsigns) { $this->callsigns = $callsigns; return $this; } public function withStatus($status) { $this->status = $status; return $this; } public function withTypes(array $types) { $this->types = $types; return $this; } public function withUUIDs(array $uuids) { $this->uuids = $uuids; return $this; } public function withNameContains($contains) { $this->nameContains = $contains; return $this; } public function needCommitCounts($need_counts) { $this->needCommitCounts = $need_counts; return $this; } public function needMostRecentCommits($need_commits) { $this->needMostRecentCommits = $need_commits; return $this; } + public function needProjectPHIDs($need_phids) { + $this->needProjectPHIDs = $need_phids; + return $this; + } + public function setOrder($order) { $this->order = $order; return $this; } protected function loadPage() { $table = new PhabricatorRepository(); $conn_r = $table->establishConnection('r'); $data = queryfx_all( $conn_r, 'SELECT * FROM %T r %Q %Q %Q %Q', $table->getTableName(), $this->buildJoinsClause($conn_r), $this->buildWhereClause($conn_r), $this->buildOrderClause($conn_r), $this->buildLimitClause($conn_r)); $repositories = $table->loadAllFromArray($data); if ($this->needCommitCounts) { $sizes = ipull($data, 'size', 'id'); foreach ($repositories as $id => $repository) { $repository->attachCommitCount(nonempty($sizes[$id], 0)); } } if ($this->needMostRecentCommits) { $commit_ids = ipull($data, 'lastCommitID', 'id'); $commit_ids = array_filter($commit_ids); if ($commit_ids) { $commits = id(new DiffusionCommitQuery()) ->setViewer($this->getViewer()) ->withIDs($commit_ids) ->execute(); } else { $commits = array(); } foreach ($repositories as $id => $repository) { $commit = null; if (idx($commit_ids, $id)) { $commit = idx($commits, $commit_ids[$id]); } $repository->attachMostRecentCommit($commit); } } - return $repositories; } public function willFilterPage(array $repositories) { assert_instances_of($repositories, 'PhabricatorRepository'); // TODO: Denormalize repository status into the PhabricatorRepository // table so we can do this filtering in the database. foreach ($repositories as $key => $repo) { $status = $this->status; switch ($status) { case self::STATUS_OPEN: if (!$repo->isTracked()) { unset($repositories[$key]); } break; case self::STATUS_CLOSED: if ($repo->isTracked()) { unset($repositories[$key]); } break; case self::STATUS_ALL: break; default: throw new Exception("Unknown status '{$status}'!"); } } 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: case self::ORDER_NAME: return true; } return false; } protected function getPagingColumn() { $order = $this->order; switch ($order) { case self::ORDER_CREATED: return 'r.id'; case self::ORDER_COMMITTED: return 's.epoch'; case self::ORDER_CALLSIGN: return 'r.callsign'; case self::ORDER_NAME: return 'r.name'; default: throw new Exception("Unknown order '{$order}!'"); } } private function loadCursorObject($id) { $query = id(new PhabricatorRepositoryQuery()) ->setViewer($this->getPagingViewer()) ->withIDs(array((int)$id)); if ($this->order == self::ORDER_COMMITTED) { $query->needMostRecentCommits(true); } $results = $query->execute(); return head($results); } protected function buildPagingClause(AphrontDatabaseConnection $conn_r) { $default = parent::buildPagingClause($conn_r); $before_id = $this->getBeforeID(); $after_id = $this->getAfterID(); if (!$before_id && !$after_id) { return $default; } $order = $this->order; if ($order == self::ORDER_CREATED) { return $default; } if ($before_id) { $cursor = $this->loadCursorObject($before_id); } else { $cursor = $this->loadCursorObject($after_id); } if (!$cursor) { return null; } $id_column = array( 'name' => 'r.id', 'type' => 'int', 'value' => $cursor->getID(), ); $columns = array(); switch ($order) { case self::ORDER_COMMITTED: $commit = $cursor->getMostRecentCommit(); if (!$commit) { return null; } $columns[] = array( 'name' => 's.epoch', 'type' => 'int', 'value' => $commit->getEpoch(), ); $columns[] = $id_column; break; case self::ORDER_CALLSIGN: $columns[] = array( 'name' => 'r.callsign', 'type' => 'string', 'value' => $cursor->getCallsign(), 'reverse' => true, ); break; case self::ORDER_NAME: $columns[] = array( 'name' => 'r.name', 'type' => 'string', 'value' => $cursor->getName(), 'reverse' => true, ); $columns[] = $id_column; break; default: throw new Exception("Unknown order '{$order}'!"); } return $this->buildPagingClauseFromMultipleColumns( $conn_r, $columns, array( // TODO: Clean up the column ordering stuff and then make this // depend on getReversePaging(). 'reversed' => (bool)($before_id), )); } private function buildJoinsClause(AphrontDatabaseConnection $conn_r) { $joins = array(); $join_summary_table = $this->needCommitCounts || $this->needMostRecentCommits || ($this->order == self::ORDER_COMMITTED); if ($join_summary_table) { $joins[] = qsprintf( $conn_r, 'LEFT JOIN %T s ON r.id = s.repositoryID', PhabricatorRepository::TABLE_SUMMARY); } return implode(' ', $joins); } private function buildWhereClause(AphrontDatabaseConnection $conn_r) { $where = array(); if ($this->ids) { $where[] = qsprintf( $conn_r, 'r.id IN (%Ld)', $this->ids); } if ($this->phids) { $where[] = qsprintf( $conn_r, 'r.phid IN (%Ls)', $this->phids); } if ($this->callsigns) { $where[] = qsprintf( $conn_r, 'r.callsign IN (%Ls)', $this->callsigns); } if ($this->types) { $where[] = qsprintf( $conn_r, 'r.versionControlSystem IN (%Ls)', $this->types); } if ($this->uuids) { $where[] = qsprintf( $conn_r, 'r.uuid IN (%Ls)', $this->uuids); } if (strlen($this->nameContains)) { $where[] = qsprintf( $conn_r, 'name LIKE %~', $this->nameContains); } $where[] = $this->buildPagingClause($conn_r); return $this->formatWhereClause($where); } public function getQueryApplicationClass() { return 'PhabricatorApplicationDiffusion'; } } diff --git a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php index 41dff1056e..98fb1af778 100644 --- a/src/applications/repository/query/PhabricatorRepositorySearchEngine.php +++ b/src/applications/repository/query/PhabricatorRepositorySearchEngine.php @@ -1,167 +1,168 @@ setParameter('callsigns', $request->getStrList('callsigns')); $saved->setParameter('status', $request->getStr('status')); $saved->setParameter('order', $request->getStr('order')); $saved->setParameter('types', $request->getArr('types')); $saved->setParameter('name', $request->getStr('name')); return $saved; } public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) { $query = id(new PhabricatorRepositoryQuery()) + ->needProjectPHIDs(true) ->needCommitCounts(true) ->needMostRecentCommits(true); $callsigns = $saved->getParameter('callsigns'); if ($callsigns) { $query->withCallsigns($callsigns); } $status = $saved->getParameter('status'); $status = idx($this->getStatusValues(), $status); if ($status) { $query->withStatus($status); } $order = $saved->getParameter('order'); $order = idx($this->getOrderValues(), $order); if ($order) { $query->setOrder($order); } else { $query->setOrder(head($this->getOrderValues())); } $types = $saved->getParameter('types'); if ($types) { $query->withTypes($types); } $name = $saved->getParameter('name'); if (strlen($name)) { $query->withNameContains($name); } return $query; } public function buildSearchForm( AphrontFormView $form, PhabricatorSavedQuery $saved_query) { $callsigns = $saved_query->getParameter('callsigns', array()); $types = $saved_query->getParameter('types', array()); $types = array_fuse($types); $name = $saved_query->getParameter('name'); $form ->appendChild( id(new AphrontFormTextControl()) ->setName('callsigns') ->setLabel(pht('Callsigns')) ->setValue(implode(', ', $callsigns))) ->appendChild( id(new AphrontFormTextControl()) ->setName('name') ->setLabel(pht('Name Contains')) ->setValue($name)) ->appendChild( id(new AphrontFormSelectControl()) ->setName('status') ->setLabel(pht('Status')) ->setValue($saved_query->getParameter('status')) ->setOptions($this->getStatusOptions())); $type_control = id(new AphrontFormCheckboxControl()) ->setLabel(pht('Types')); $all_types = PhabricatorRepositoryType::getAllRepositoryTypes(); foreach ($all_types as $key => $name) { $type_control->addCheckbox( 'types[]', $key, $name, isset($types[$key])); } $form ->appendChild($type_control) ->appendChild( id(new AphrontFormSelectControl()) ->setName('order') ->setLabel(pht('Order')) ->setValue($saved_query->getParameter('order')) ->setOptions($this->getOrderOptions())); } protected function getURI($path) { return '/diffusion/'.$path; } public function getBuiltinQueryNames() { $names = array( 'active' => pht('Active Repositories'), 'all' => pht('All Repositories'), ); return $names; } public function buildSavedQueryFromBuiltin($query_key) { $query = $this->newSavedQuery(); $query->setQueryKey($query_key); switch ($query_key) { case 'active': return $query->setParameter('status', 'open'); case 'all': return $query; } return parent::buildSavedQueryFromBuiltin($query_key); } private function getStatusOptions() { return array( '' => pht('Active and Inactive Repositories'), 'open' => pht('Active Repositories'), 'closed' => pht('Inactive Repositories'), ); } private function getStatusValues() { return array( '' => PhabricatorRepositoryQuery::STATUS_ALL, 'open' => PhabricatorRepositoryQuery::STATUS_OPEN, 'closed' => PhabricatorRepositoryQuery::STATUS_CLOSED, ); } private function getOrderOptions() { return array( 'committed' => pht('Most Recent Commit'), 'name' => pht('Name'), 'callsign' => pht('Callsign'), 'created' => pht('Date Created'), ); } private function getOrderValues() { return array( 'committed' => PhabricatorRepositoryQuery::ORDER_COMMITTED, 'name' => PhabricatorRepositoryQuery::ORDER_NAME, 'callsign' => PhabricatorRepositoryQuery::ORDER_CALLSIGN, 'created' => PhabricatorRepositoryQuery::ORDER_CREATED, ); } } diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 64ce761e72..bcfd48c2b6 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1,1080 +1,1090 @@ setViewer($actor) ->withClasses(array('PhabricatorApplicationDiffusion')) ->executeOne(); $view_policy = $app->getPolicy(DiffusionCapabilityDefaultView::CAPABILITY); $edit_policy = $app->getPolicy(DiffusionCapabilityDefaultEdit::CAPABILITY); $push_policy = $app->getPolicy(DiffusionCapabilityDefaultPush::CAPABILITY); return id(new PhabricatorRepository()) ->setViewPolicy($view_policy) ->setEditPolicy($edit_policy) ->setPushPolicy($push_policy); } public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, self::CONFIG_SERIALIZATION => array( 'details' => self::SERIALIZATION_JSON, ), ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID( PhabricatorRepositoryPHIDTypeRepository::TYPECONST); } public function toDictionary() { return array( 'name' => $this->getName(), 'phid' => $this->getPHID(), 'callsign' => $this->getCallsign(), 'vcs' => $this->getVersionControlSystem(), 'uri' => PhabricatorEnv::getProductionURI($this->getURI()), 'remoteURI' => (string)$this->getRemoteURI(), 'tracking' => $this->getDetail('tracking-enabled'), 'description' => $this->getDetail('description'), ); } public function getDetail($key, $default = null) { return idx($this->details, $key, $default); } public function getHumanReadableDetail($key, $default = null) { $value = $this->getDetail($key, $default); switch ($key) { case 'branch-filter': case 'close-commits-filter': $value = array_keys($value); $value = implode(', ', $value); break; } return $value; } public function setDetail($key, $value) { $this->details[$key] = $value; return $this; } public function attachCommitCount($count) { $this->commitCount = $count; return $this; } public function getCommitCount() { return $this->assertAttached($this->commitCount); } public function attachMostRecentCommit( PhabricatorRepositoryCommit $commit = null) { $this->mostRecentCommit = $commit; return $this; } public function getMostRecentCommit() { return $this->assertAttached($this->mostRecentCommit); } public function getDiffusionBrowseURIForPath( PhabricatorUser $user, $path, $line = null, $branch = null) { $drequest = DiffusionRequest::newFromDictionary( array( 'user' => $user, 'repository' => $this, 'path' => $path, 'branch' => $branch, )); return $drequest->generateURI( array( 'action' => 'browse', 'line' => $line, )); } public function getLocalPath() { return $this->getDetail('local-path'); } public function getSubversionBaseURI($commit = null) { $subpath = $this->getDetail('svn-subpath'); if (!strlen($subpath)) { $subpath = null; } return $this->getSubversionPathURI($subpath, $commit); } public function getSubversionPathURI($path = null, $commit = null) { $vcs = $this->getVersionControlSystem(); if ($vcs != PhabricatorRepositoryType::REPOSITORY_TYPE_SVN) { throw new Exception("Not a subversion repository!"); } if ($this->isHosted()) { $uri = 'file://'.$this->getLocalPath(); } else { $uri = $this->getDetail('remote-uri'); } $uri = rtrim($uri, '/'); if (strlen($path)) { $path = rawurlencode($path); $path = str_replace('%2F', '/', $path); $uri = $uri.'/'.ltrim($path, '/'); } if ($path !== null || $commit !== null) { $uri .= '@'; } if ($commit !== null) { $uri .= $commit; } 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 )------------------------------------------- */ public function execRemoteCommand($pattern /* , $arg, ... */) { $args = func_get_args(); return $this->newRemoteCommandFuture($args)->resolve(); } public function execxRemoteCommand($pattern /* , $arg, ... */) { $args = func_get_args(); return $this->newRemoteCommandFuture($args)->resolvex(); } public function getRemoteCommandFuture($pattern /* , $arg, ... */) { $args = func_get_args(); return $this->newRemoteCommandFuture($args); } public function passthruRemoteCommand($pattern /* , $arg, ... */) { $args = func_get_args(); return $this->newRemoteCommandPassthru($args)->execute(); } private function newRemoteCommandFuture(array $argv) { $argv = $this->formatRemoteCommand($argv); $future = newv('ExecFuture', $argv); $future->setEnv($this->getRemoteCommandEnvironment()); return $future; } private function newRemoteCommandPassthru(array $argv) { $argv = $this->formatRemoteCommand($argv); $passthru = newv('PhutilExecPassthru', $argv); $passthru->setEnv($this->getRemoteCommandEnvironment()); return $passthru; } /* -( Local Command Execution )-------------------------------------------- */ public function execLocalCommand($pattern /* , $arg, ... */) { $args = func_get_args(); return $this->newLocalCommandFuture($args)->resolve(); } public function execxLocalCommand($pattern /* , $arg, ... */) { $args = func_get_args(); return $this->newLocalCommandFuture($args)->resolvex(); } public function getLocalCommandFuture($pattern /* , $arg, ... */) { $args = func_get_args(); return $this->newLocalCommandFuture($args); } public function passthruLocalCommand($pattern /* , $arg, ... */) { $args = func_get_args(); return $this->newLocalCommandPassthru($args)->execute(); } private function newLocalCommandFuture(array $argv) { $this->assertLocalExists(); $argv = $this->formatLocalCommand($argv); $future = newv('ExecFuture', $argv); $future->setEnv($this->getLocalCommandEnvironment()); if ($this->usesLocalWorkingCopy()) { $future->setCWD($this->getLocalPath()); } return $future; } private function newLocalCommandPassthru(array $argv) { $this->assertLocalExists(); $argv = $this->formatLocalCommand($argv); $future = newv('PhutilExecPassthru', $argv); $future->setEnv($this->getLocalCommandEnvironment()); if ($this->usesLocalWorkingCopy()) { $future->setCWD($this->getLocalPath()); } return $future; } /* -( Command Infrastructure )--------------------------------------------- */ private function getSSHWrapper() { $root = dirname(phutil_get_library_root('phabricator')); return $root.'/bin/ssh-connect'; } private function getCommonCommandEnvironment() { $env = array( // NOTE: Force the language to "en_US.UTF-8", which overrides locale // settings. This makes stuff print in English instead of, e.g., French, // so we can parse the output of some commands, error messages, etc. 'LANG' => 'en_US.UTF-8', // Propagate PHABRICATOR_ENV explicitly. For discussion, see T4155. 'PHABRICATOR_ENV' => PhabricatorEnv::getSelectedEnvironmentName(), ); switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: // NOTE: See T2965. Some time after Git 1.7.5.4, Git started fataling if // it can not read $HOME. For many users, $HOME points at /root (this // seems to be a default result of Apache setup). Instead, explicitly // point $HOME at a readable, empty directory so that Git looks for the // config file it's after, fails to locate it, and moves on. This is // really silly, but seems like the least damaging approach to // mitigating the issue. $root = dirname(phutil_get_library_root('phabricator')); $env['HOME'] = $root.'/support/empty/'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: // NOTE: This overrides certain configuration, extensions, and settings // which make Mercurial commands do random unusual things. $env['HGPLAIN'] = 1; break; default: throw new Exception("Unrecognized version control system."); } return $env; } private function getLocalCommandEnvironment() { return $this->getCommonCommandEnvironment(); } private function getRemoteCommandEnvironment() { $env = $this->getCommonCommandEnvironment(); if ($this->shouldUseSSH()) { // NOTE: This is read by `bin/ssh-connect`, and tells it which credentials // to use. $env['PHABRICATOR_CREDENTIAL'] = $this->getCredentialPHID(); switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: // Force SVN to use `bin/ssh-connect`. $env['SVN_SSH'] = $this->getSSHWrapper(); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: // Force Git to use `bin/ssh-connect`. $env['GIT_SSH'] = $this->getSSHWrapper(); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: // We force Mercurial through `bin/ssh-connect` too, but it uses a // command-line flag instead of an environmental variable. break; default: throw new Exception("Unrecognized version control system."); } } return $env; } private function formatRemoteCommand(array $args) { $pattern = $args[0]; $args = array_slice($args, 1); switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: if ($this->shouldUseHTTP() || $this->shouldUseSVNProtocol()) { $flags = array(); $flag_args = array(); $flags[] = '--non-interactive'; $flags[] = '--no-auth-cache'; if ($this->shouldUseHTTP()) { $flags[] = '--trust-server-cert'; } $credential_phid = $this->getCredentialPHID(); if ($credential_phid) { $key = PassphrasePasswordKey::loadFromPHID( $credential_phid, PhabricatorUser::getOmnipotentUser()); $flags[] = '--username %P'; $flags[] = '--password %P'; $flag_args[] = $key->getUsernameEnvelope(); $flag_args[] = $key->getPasswordEnvelope(); } $flags = implode(' ', $flags); $pattern = "svn {$flags} {$pattern}"; $args = array_mergev(array($flag_args, $args)); } else { $pattern = "svn --non-interactive {$pattern}"; } break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $pattern = "git {$pattern}"; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: if ($this->shouldUseSSH()) { $pattern = "hg --config ui.ssh=%s {$pattern}"; array_unshift( $args, $this->getSSHWrapper()); } else { $pattern = "hg {$pattern}"; } break; default: throw new Exception("Unrecognized version control system."); } array_unshift($args, $pattern); return $args; } private function formatLocalCommand(array $args) { $pattern = $args[0]; $args = array_slice($args, 1); switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $pattern = "svn --non-interactive {$pattern}"; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $pattern = "git {$pattern}"; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $pattern = "hg {$pattern}"; break; default: throw new Exception("Unrecognized version control system."); } array_unshift($args, $pattern); return $args; } /** * Sanitize output of an `hg` command invoked with the `--debug` flag to make * it usable. * * @param string Output from `hg --debug ...` * @return string Usable output. */ public static function filterMercurialDebugOutput($stdout) { // When hg commands are run with `--debug` and some config file isn't // trusted, Mercurial prints out a warning to stdout, twice, after Feb 2011. // // http://selenic.com/pipermail/mercurial-devel/2011-February/028541.html $lines = preg_split('/(?<=\n)/', $stdout); $regex = '/ignoring untrusted configuration option .*\n$/'; foreach ($lines as $key => $line) { $lines[$key] = preg_replace($regex, '', $line); } return implode('', $lines); } public function getURI() { return '/diffusion/'.$this->getCallsign().'/'; } public function isTracked() { return $this->getDetail('tracking-enabled', false); } public function getDefaultBranch() { $default = $this->getDetail('default-branch'); if (strlen($default)) { return $default; } $default_branches = array( PhabricatorRepositoryType::REPOSITORY_TYPE_GIT => 'master', PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL => 'default', ); return idx($default_branches, $this->getVersionControlSystem()); } public function getDefaultArcanistBranch() { return coalesce($this->getDefaultBranch(), 'svn'); } private function isBranchInFilter($branch, $filter_key) { $vcs = $this->getVersionControlSystem(); $is_git = ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT); $use_filter = ($is_git); if ($use_filter) { $filter = $this->getDetail($filter_key, array()); if ($filter && empty($filter[$branch])) { return false; } } // By default, all branches pass. return true; } public function shouldTrackBranch($branch) { return $this->isBranchInFilter($branch, 'branch-filter'); } public function shouldAutocloseBranch($branch) { if ($this->isImporting()) { return false; } if ($this->getDetail('disable-autoclose', false)) { return false; } return $this->isBranchInFilter($branch, 'close-commits-filter'); } public function shouldAutocloseCommit( PhabricatorRepositoryCommit $commit, PhabricatorRepositoryCommitData $data) { if ($this->getDetail('disable-autoclose', false)) { return false; } switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: return true; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: return true; default: throw new Exception("Unrecognized version control system."); } $branches = $data->getCommitDetail('seenOnBranches', array()); foreach ($branches as $branch) { if ($this->shouldAutocloseBranch($branch)) { return true; } } return false; } public function formatCommitName($commit_identifier) { $vcs = $this->getVersionControlSystem(); $type_git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; $type_hg = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL; $is_git = ($vcs == $type_git); $is_hg = ($vcs == $type_hg); if ($is_git || $is_hg) { $short_identifier = substr($commit_identifier, 0, 12); } else { $short_identifier = $commit_identifier; } return 'r'.$this->getCallsign().$short_identifier; } public function isImporting() { return (bool)$this->getDetail('importing', false); } /* -( Repository URI Management )------------------------------------------ */ /** * Get the remote URI for this repository. * * @return string * @task uri */ public function getRemoteURI() { return (string)$this->getRemoteURIObject(); } /** * Get the remote URI for this repository, including credentials if they're * used by this repository. * * @return PhutilOpaqueEnvelope URI, possibly including credentials. * @task uri */ public function getRemoteURIEnvelope() { $uri = $this->getRemoteURIObject(); $remote_protocol = $this->getRemoteProtocol(); if ($remote_protocol == 'http' || $remote_protocol == 'https') { // For SVN, we use `--username` and `--password` flags separately, so // don't add any credentials here. if (!$this->isSVN()) { $credential_phid = $this->getCredentialPHID(); if ($credential_phid) { $key = PassphrasePasswordKey::loadFromPHID( $credential_phid, PhabricatorUser::getOmnipotentUser()); $uri->setUser($key->getUsernameEnvelope()->openEnvelope()); $uri->setPass($key->getPasswordEnvelope()->openEnvelope()); } } } return new PhutilOpaqueEnvelope((string)$uri); } /** * Get the remote URI for this repository, without authentication information. * * @return string Repository URI. * @task uri */ public function getPublicRemoteURI() { $uri = $this->getRemoteURIObject(); // Make sure we don't leak anything if this repo is using HTTP Basic Auth // with the credentials in the URI or something zany like that. // If repository is not accessed over SSH we remove both username and // password. if (!$this->shouldUseSSH()) { $uri->setUser(null); // This might be a Git URI or a normal URI. If it's Git, there's no // password support. if ($uri instanceof PhutilURI) { $uri->setPass(null); } } return (string)$uri; } /** * Get the protocol for the repository's remote. * * @return string Protocol, like "ssh" or "git". * @task uri */ public function getRemoteProtocol() { $uri = $this->getRemoteURIObject(); if ($uri instanceof PhutilGitURI) { return 'ssh'; } else { return $uri->getProtocol(); } } /** * Get a parsed object representation of the repository's remote URI. This * may be a normal URI (returned as a @{class@libphutil:PhutilURI}) or a git * URI (returned as a @{class@libphutil:PhutilGitURI}). * * @return wild A @{class@libphutil:PhutilURI} or * @{class@libphutil:PhutilGitURI}. * @task uri */ public function getRemoteURIObject() { $raw_uri = $this->getDetail('remote-uri'); if (!$raw_uri) { return new PhutilURI(''); } if (!strncmp($raw_uri, '/', 1)) { return new PhutilURI('file://'.$raw_uri); } $uri = new PhutilURI($raw_uri); if ($uri->getProtocol()) { return $uri; } $uri = new PhutilGitURI($raw_uri); if ($uri->getDomain()) { return $uri; } throw new Exception("Remote URI '{$raw_uri}' could not be parsed!"); } /** * Determine if we should connect to the remote using SSH flags and * credentials. * * @return bool True to use the SSH protocol. * @task uri */ private function shouldUseSSH() { if ($this->isHosted()) { return false; } $protocol = $this->getRemoteProtocol(); if ($this->isSSHProtocol($protocol)) { return true; } return false; } /** * Determine if we should connect to the remote using HTTP flags and * credentials. * * @return bool True to use the HTTP protocol. * @task uri */ private function shouldUseHTTP() { if ($this->isHosted()) { return false; } $protocol = $this->getRemoteProtocol(); return ($protocol == 'http' || $protocol == 'https'); } /** * Determine if we should connect to the remote using SVN flags and * credentials. * * @return bool True to use the SVN protocol. * @task uri */ private function shouldUseSVNProtocol() { if ($this->isHosted()) { return false; } $protocol = $this->getRemoteProtocol(); return ($protocol == 'svn'); } /** * Determine if a protocol is SSH or SSH-like. * * @param string A protocol string, like "http" or "ssh". * @return bool True if the protocol is SSH-like. * @task uri */ private function isSSHProtocol($protocol) { return ($protocol == 'ssh' || $protocol == 'svn+ssh'); } public function delete() { $this->openTransaction(); $paths = id(new PhabricatorOwnersPath()) ->loadAllWhere('repositoryPHID = %s', $this->getPHID()); foreach ($paths as $path) { $path->delete(); } $projects = id(new PhabricatorRepositoryArcanistProject()) ->loadAllWhere('repositoryID = %d', $this->getID()); foreach ($projects as $project) { // note each project deletes its PhabricatorRepositorySymbols $project->delete(); } $commits = id(new PhabricatorRepositoryCommit()) ->loadAllWhere('repositoryID = %d', $this->getID()); foreach ($commits as $commit) { // note PhabricatorRepositoryAuditRequests and // PhabricatorRepositoryCommitData are deleted here too. $commit->delete(); } $mirrors = id(new PhabricatorRepositoryMirror()) ->loadAllWhere('repositoryPHID = %s', $this->getPHID()); foreach ($mirrors as $mirror) { $mirror->delete(); } $conn_w = $this->establishConnection('w'); queryfx( $conn_w, 'DELETE FROM %T WHERE repositoryID = %d', self::TABLE_FILESYSTEM, $this->getID()); queryfx( $conn_w, 'DELETE FROM %T WHERE repositoryID = %d', self::TABLE_PATHCHANGE, $this->getID()); queryfx( $conn_w, 'DELETE FROM %T WHERE repositoryID = %d', self::TABLE_SUMMARY, $this->getID()); $result = parent::delete(); $this->saveTransaction(); return $result; } public function isGit() { $vcs = $this->getVersionControlSystem(); return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_GIT); } public function isSVN() { $vcs = $this->getVersionControlSystem(); return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_SVN); } public function isHg() { $vcs = $this->getVersionControlSystem(); return ($vcs == PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL); } public function isHosted() { return (bool)$this->getDetail('hosting-enabled', false); } public function setHosted($enabled) { return $this->setDetail('hosting-enabled', $enabled); } public function getServeOverHTTP() { if ($this->isSVN()) { return self::SERVE_OFF; } $serve = $this->getDetail('serve-over-http', self::SERVE_OFF); return $this->normalizeServeConfigSetting($serve); } public function setServeOverHTTP($mode) { return $this->setDetail('serve-over-http', $mode); } public function getServeOverSSH() { $serve = $this->getDetail('serve-over-ssh', self::SERVE_OFF); return $this->normalizeServeConfigSetting($serve); } public function setServeOverSSH($mode) { return $this->setDetail('serve-over-ssh', $mode); } public static function getProtocolAvailabilityName($constant) { switch ($constant) { case self::SERVE_OFF: return pht('Off'); case self::SERVE_READONLY: return pht('Read Only'); case self::SERVE_READWRITE: return pht('Read/Write'); default: return pht('Unknown'); } } private function normalizeServeConfigSetting($value) { switch ($value) { case self::SERVE_OFF: case self::SERVE_READONLY: return $value; case self::SERVE_READWRITE: if ($this->isHosted()) { return self::SERVE_READWRITE; } else { return self::SERVE_READONLY; } default: return self::SERVE_OFF; } } /** * Raise more useful errors when there are basic filesystem problems. */ private function assertLocalExists() { if (!$this->usesLocalWorkingCopy()) { return; } $local = $this->getLocalPath(); Filesystem::assertExists($local); Filesystem::assertIsDirectory($local); Filesystem::assertReadable($local); } /** * Determine if the working copy is bare or not. In Git, this corresponds * to `--bare`. In Mercurial, `--noupdate`. */ public function isWorkingCopyBare() { switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: return false; case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $local = $this->getLocalPath(); if (Filesystem::pathExists($local.'/.git')) { return false; } else { return true; } } } public function usesLocalWorkingCopy() { switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: return $this->isHosted(); case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: return true; } } public function canDestroyWorkingCopy() { if ($this->isHosted()) { // Never destroy hosted working copies. return false; } $default_path = PhabricatorEnv::getEnvConfig( 'repository.default-local-path'); return Filesystem::isDescendant($this->getLocalPath(), $default_path); } public function canMirror() { if (!$this->isHosted()) { return false; } if ($this->isGit()) { return true; } return false; } public function canAllowDangerousChanges() { if (!$this->isHosted()) { return false; } if ($this->isGit()) { return true; } return false; } public function shouldAllowDangerousChanges() { return (bool)$this->getDetail('allow-dangerous-changes'); } public function writeStatusMessage( $status_type, $status_code, array $parameters = array()) { $table = new PhabricatorRepositoryStatusMessage(); $conn_w = $table->establishConnection('w'); $table_name = $table->getTableName(); if ($status_code === null) { queryfx( $conn_w, 'DELETE FROM %T WHERE repositoryID = %d AND statusType = %s', $table_name, $this->getID(), $status_type); } else { queryfx( $conn_w, 'INSERT INTO %T (repositoryID, statusType, statusCode, parameters, epoch) VALUES (%d, %s, %s, %s, %d) ON DUPLICATE KEY UPDATE statusCode = VALUES(statusCode), parameters = VALUES(parameters), epoch = VALUES(epoch)', $table_name, $this->getID(), $status_type, $status_code, json_encode($parameters), time()); } return $this; } /* -( PhabricatorPolicyInterface )----------------------------------------- */ public function getCapabilities() { return array( PhabricatorPolicyCapability::CAN_VIEW, PhabricatorPolicyCapability::CAN_EDIT, DiffusionCapabilityPush::CAPABILITY, ); } public function getPolicy($capability) { switch ($capability) { case PhabricatorPolicyCapability::CAN_VIEW: return $this->getViewPolicy(); case PhabricatorPolicyCapability::CAN_EDIT: return $this->getEditPolicy(); case DiffusionCapabilityPush::CAPABILITY: return $this->getPushPolicy(); } } public function hasAutomaticCapability($capability, PhabricatorUser $user) { return false; } public function describeAutomaticCapability($capability) { return null; } /* -( PhabricatorMarkupInterface )----------------------------------------- */ public function getMarkupFieldKey($field) { $hash = PhabricatorHash::digestForIndex($this->getMarkupText($field)); return "repo:{$hash}"; } public function newMarkupEngine($field) { return PhabricatorMarkupEngine::newMarkupEngine(array()); } public function getMarkupText($field) { return $this->getDetail('description'); } public function didMarkupText( $field, $output, PhutilMarkupEngine $engine) { require_celerity_resource('phabricator-remarkup-css'); return phutil_tag( 'div', array( 'class' => 'phabricator-remarkup', ), $output); } public function shouldUseMarkupCache($field) { return true; } } diff --git a/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php b/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php index 16b33f373c..81e851625f 100644 --- a/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php +++ b/src/infrastructure/edges/constants/PhabricatorEdgeConfig.php @@ -1,464 +1,478 @@ self::TYPE_COMMIT_HAS_TASK, self::TYPE_COMMIT_HAS_TASK => self::TYPE_TASK_HAS_COMMIT, self::TYPE_TASK_DEPENDS_ON_TASK => self::TYPE_TASK_DEPENDED_ON_BY_TASK, self::TYPE_TASK_DEPENDED_ON_BY_TASK => self::TYPE_TASK_DEPENDS_ON_TASK, self::TYPE_DREV_DEPENDS_ON_DREV => self::TYPE_DREV_DEPENDED_ON_BY_DREV, self::TYPE_DREV_DEPENDED_ON_BY_DREV => self::TYPE_DREV_DEPENDS_ON_DREV, self::TYPE_BLOG_HAS_POST => self::TYPE_POST_HAS_BLOG, self::TYPE_POST_HAS_BLOG => self::TYPE_BLOG_HAS_POST, self::TYPE_BLOG_HAS_BLOGGER => self::TYPE_BLOGGER_HAS_BLOG, self::TYPE_BLOGGER_HAS_BLOG => self::TYPE_BLOG_HAS_BLOGGER, self::TYPE_TASK_HAS_RELATED_DREV => self::TYPE_DREV_HAS_RELATED_TASK, self::TYPE_DREV_HAS_RELATED_TASK => self::TYPE_TASK_HAS_RELATED_DREV, self::TYPE_PROJ_MEMBER => self::TYPE_MEMBER_OF_PROJ, self::TYPE_MEMBER_OF_PROJ => self::TYPE_PROJ_MEMBER, self::TYPE_COMMIT_HAS_PROJECT => self::TYPE_PROJECT_HAS_COMMIT, self::TYPE_PROJECT_HAS_COMMIT => self::TYPE_COMMIT_HAS_PROJECT, self::TYPE_QUESTION_HAS_VOTING_USER => self::TYPE_VOTING_USER_HAS_QUESTION, self::TYPE_VOTING_USER_HAS_QUESTION => self::TYPE_QUESTION_HAS_VOTING_USER, self::TYPE_ANSWER_HAS_VOTING_USER => self::TYPE_VOTING_USER_HAS_ANSWER, self::TYPE_VOTING_USER_HAS_ANSWER => self::TYPE_ANSWER_HAS_VOTING_USER, self::TYPE_OBJECT_HAS_SUBSCRIBER => self::TYPE_SUBSCRIBED_TO_OBJECT, self::TYPE_SUBSCRIBED_TO_OBJECT => self::TYPE_OBJECT_HAS_SUBSCRIBER, self::TYPE_OBJECT_HAS_UNSUBSCRIBER => self::TYPE_UNSUBSCRIBED_FROM_OBJECT, self::TYPE_UNSUBSCRIBED_FROM_OBJECT => self::TYPE_OBJECT_HAS_UNSUBSCRIBER, self::TYPE_OBJECT_HAS_FILE => self::TYPE_FILE_HAS_OBJECT, self::TYPE_FILE_HAS_OBJECT => self::TYPE_OBJECT_HAS_FILE, self::TYPE_ACCOUNT_HAS_MEMBER => self::TYPE_MEMBER_HAS_ACCOUNT, self::TYPE_MEMBER_HAS_ACCOUNT => self::TYPE_ACCOUNT_HAS_MEMBER, self::TYPE_DREV_HAS_COMMIT => self::TYPE_COMMIT_HAS_DREV, self::TYPE_COMMIT_HAS_DREV => self::TYPE_DREV_HAS_COMMIT, self::TYPE_OBJECT_HAS_CONTRIBUTOR => self::TYPE_SUBSCRIBED_TO_OBJECT, self::TYPE_CONTRIBUTED_TO_OBJECT => self::TYPE_OBJECT_HAS_CONTRIBUTOR, self::TYPE_TASK_HAS_MOCK => self::TYPE_MOCK_HAS_TASK, self::TYPE_MOCK_HAS_TASK => self::TYPE_TASK_HAS_MOCK, self::TYPE_PHOB_HAS_ASANATASK => self::TYPE_ASANATASK_HAS_PHOB, self::TYPE_ASANATASK_HAS_PHOB => self::TYPE_PHOB_HAS_ASANATASK, self::TYPE_PHOB_HAS_ASANASUBTASK => self::TYPE_ASANASUBTASK_HAS_PHOB, self::TYPE_ASANASUBTASK_HAS_PHOB => self::TYPE_PHOB_HAS_ASANASUBTASK, self::TYPE_DREV_HAS_REVIEWER => self::TYPE_REVIEWER_FOR_DREV, self::TYPE_REVIEWER_FOR_DREV => self::TYPE_DREV_HAS_REVIEWER, self::TYPE_PHOB_HAS_JIRAISSUE => self::TYPE_JIRAISSUE_HAS_PHOB, self::TYPE_JIRAISSUE_HAS_PHOB => self::TYPE_PHOB_HAS_JIRAISSUE, 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); } public static function shouldPreventCycles($edge_type) { static $map = array( self::TYPE_TEST_NO_CYCLE => true, self::TYPE_TASK_DEPENDS_ON_TASK => true, self::TYPE_DREV_DEPENDS_ON_DREV => true, ); return isset($map[$edge_type]); } public static function establishConnection($phid_type, $conn_type) { $map = PhabricatorPHIDType::getAllTypes(); if (isset($map[$phid_type])) { $type = $map[$phid_type]; $object = $type->newObject(); if ($object) { return $object->establishConnection($conn_type); } } static $class_map = array( PhabricatorPHIDConstants::PHID_TYPE_TOBJ => 'HarbormasterObject', PhabricatorPHIDConstants::PHID_TYPE_ACNT => 'PhortuneAccount', PhabricatorPHIDConstants::PHID_TYPE_PRCH => 'PhortunePurchase', PhabricatorPHIDConstants::PHID_TYPE_CHRG => 'PhortuneCharge', PhabricatorPHIDConstants::PHID_TYPE_XOBJ => 'DoorkeeperExternalObject', ); $class = idx($class_map, $phid_type); if (!$class) { throw new Exception( "Edges are not available for objects of type '{$phid_type}'!"); } return newv($class, array())->establishConnection($conn_type); } public static function getEditStringForEdgeType($type) { switch ($type) { case self::TYPE_TASK_HAS_COMMIT: case self::TYPE_PROJECT_HAS_COMMIT: case self::TYPE_DREV_HAS_COMMIT: return '%s edited commit(s), added %d: %s; removed %d: %s.'; case self::TYPE_COMMIT_HAS_TASK: case self::TYPE_TASK_DEPENDS_ON_TASK: case self::TYPE_TASK_DEPENDED_ON_BY_TASK: case self::TYPE_DREV_HAS_RELATED_TASK: case self::TYPE_MOCK_HAS_TASK: return '%s edited task(s), added %d: %s; removed %d: %s.'; case self::TYPE_DREV_DEPENDS_ON_DREV: case self::TYPE_DREV_DEPENDED_ON_BY_DREV: case self::TYPE_TASK_HAS_RELATED_DREV: case self::TYPE_COMMIT_HAS_DREV: case self::TYPE_REVIEWER_FOR_DREV: return '%s edited revision(s), added %d: %s; removed %d: %s.'; case self::TYPE_BLOG_HAS_POST: return '%s edited post(s), added %d: %s; removed %d: %s.'; case self::TYPE_POST_HAS_BLOG: case self::TYPE_BLOGGER_HAS_BLOG: return '%s edited blog(s), added %d: %s; removed %d: %s.'; case self::TYPE_BLOG_HAS_BLOGGER: return '%s edited blogger(s), added %d: %s; removed %d: %s.'; case self::TYPE_PROJ_MEMBER: 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: return '%s edited voting user(s), added %d: %s; removed %d: %s.'; case self::TYPE_VOTING_USER_HAS_QUESTION: return '%s edited question(s), added %d: %s; removed %d: %s.'; case self::TYPE_VOTING_USER_HAS_ANSWER: return '%s edited answer(s), added %d: %s; removed %d: %s.'; case self::TYPE_OBJECT_HAS_SUBSCRIBER: return '%s edited subscriber(s), added %d: %s; removed %d: %s.'; case self::TYPE_SUBSCRIBED_TO_OBJECT: 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.'; case self::TYPE_OBJECT_HAS_FILE: return '%s edited file(s), added %d: %s; removed %d: %s.'; case self::TYPE_ACCOUNT_HAS_MEMBER: return '%s edited member(s), added %d: %s; removed %d: %s.'; case self::TYPE_MEMBER_HAS_ACCOUNT: return '%s edited account(s), added %d: %s; removed %d: %s.'; case self::TYPE_PURCAHSE_HAS_CHARGE: return '%s edited charge(s), added %d: %s; removed %d: %s.'; case self::TYPE_CHARGE_HAS_PURCHASE: return '%s edited purchase(s), added %d: %s; removed %d: %s.'; case self::TYPE_OBJECT_HAS_CONTRIBUTOR: return '%s edited contributor(s), added %d: %s; removed %d: %s.'; case self::TYPE_DREV_HAS_REVIEWER: return '%s edited reviewer(s), added %d: %s; removed %d: %s.'; case self::TYPE_TASK_HAS_MOCK: return '%s edited mock(s), added %d: %s; removed %d: %s.'; case self::TYPE_SUBSCRIBED_TO_OBJECT: case self::TYPE_UNSUBSCRIBED_FROM_OBJECT: case self::TYPE_FILE_HAS_OBJECT: case self::TYPE_CONTRIBUTED_TO_OBJECT: default: return '%s edited object(s), added %d: %s; removed %d: %s.'; } } public static function getAddStringForEdgeType($type) { switch ($type) { case self::TYPE_TASK_HAS_COMMIT: case self::TYPE_PROJECT_HAS_COMMIT: case self::TYPE_DREV_HAS_COMMIT: return '%s added %d commit(s): %s.'; case self::TYPE_TASK_DEPENDS_ON_TASK: return '%s added %d dependencie(s): %s.'; case self::TYPE_TASK_DEPENDED_ON_BY_TASK: return '%s added %d dependent task(s): %s.'; case self::TYPE_COMMIT_HAS_TASK: case self::TYPE_DREV_HAS_RELATED_TASK: case self::TYPE_MOCK_HAS_TASK: return '%s added %d task(s): %s.'; case self::TYPE_DREV_DEPENDS_ON_DREV: case self::TYPE_DREV_DEPENDED_ON_BY_DREV: case self::TYPE_TASK_HAS_RELATED_DREV: case self::TYPE_COMMIT_HAS_DREV: case self::TYPE_REVIEWER_FOR_DREV: return '%s added %d revision(s): %s.'; case self::TYPE_BLOG_HAS_POST: return '%s added %d post(s): %s.'; case self::TYPE_POST_HAS_BLOG: case self::TYPE_BLOGGER_HAS_BLOG: return '%s added %d blog(s): %s.'; case self::TYPE_BLOG_HAS_BLOGGER: return '%s added %d blogger(s): %s.'; case self::TYPE_PROJ_MEMBER: 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: return '%s added %d voting user(s): %s.'; case self::TYPE_VOTING_USER_HAS_QUESTION: return '%s added %d question(s): %s.'; case self::TYPE_VOTING_USER_HAS_ANSWER: return '%s added %d answer(s): %s.'; case self::TYPE_OBJECT_HAS_SUBSCRIBER: return '%s added %d subscriber(s): %s.'; case self::TYPE_OBJECT_HAS_UNSUBSCRIBER: return '%s added %d unsubcriber(s): %s.'; case self::TYPE_OBJECT_HAS_FILE: return '%s added %d file(s): %s.'; case self::TYPE_ACCOUNT_HAS_MEMBER: return '%s added %d member(s): %s.'; case self::TYPE_MEMBER_HAS_ACCOUNT: return '%s added %d account(s): %s.'; case self::TYPE_PURCAHSE_HAS_CHARGE: return '%s added %d charge(s): %s.'; case self::TYPE_CHARGE_HAS_PURCHASE: return '%s added %d purchase(s): %s.'; case self::TYPE_OBJECT_HAS_CONTRIBUTOR: return '%s added %d contributor(s): %s.'; case self::TYPE_DREV_HAS_REVIEWER: return '%s added %d reviewer(s): %s.'; case self::TYPE_TASK_HAS_MOCK: return '%s added %d mock(s): %s.'; case self::TYPE_SUBSCRIBED_TO_OBJECT: 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.'; } } public static function getRemoveStringForEdgeType($type) { switch ($type) { case self::TYPE_TASK_HAS_COMMIT: case self::TYPE_PROJECT_HAS_COMMIT: case self::TYPE_DREV_HAS_COMMIT: return '%s removed %d commit(s): %s.'; case self::TYPE_TASK_DEPENDS_ON_TASK: return '%s removed %d dependencie(s): %s.'; case self::TYPE_TASK_DEPENDED_ON_BY_TASK: return '%s removed %d dependent task(s): %s.'; case self::TYPE_COMMIT_HAS_TASK: case self::TYPE_DREV_HAS_RELATED_TASK: case self::TYPE_MOCK_HAS_TASK: return '%s removed %d task(s): %s.'; case self::TYPE_DREV_DEPENDS_ON_DREV: case self::TYPE_DREV_DEPENDED_ON_BY_DREV: case self::TYPE_TASK_HAS_RELATED_DREV: case self::TYPE_COMMIT_HAS_DREV: case self::TYPE_REVIEWER_FOR_DREV: return '%s removed %d revision(s): %s.'; case self::TYPE_BLOG_HAS_POST: return '%s removed %d post(s): %s.'; case self::TYPE_POST_HAS_BLOG: case self::TYPE_BLOGGER_HAS_BLOG: return '%s removed %d blog(s): %s.'; case self::TYPE_BLOG_HAS_BLOGGER: return '%s removed %d blogger(s): %s.'; case self::TYPE_PROJ_MEMBER: 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: return '%s removed %d voting user(s): %s.'; case self::TYPE_VOTING_USER_HAS_QUESTION: return '%s removed %d question(s): %s.'; case self::TYPE_VOTING_USER_HAS_ANSWER: return '%s removed %d answer(s): %s.'; case self::TYPE_OBJECT_HAS_SUBSCRIBER: return '%s removed %d subscriber(s): %s.'; case self::TYPE_OBJECT_HAS_UNSUBSCRIBER: return '%s removed %d unsubcriber(s): %s.'; case self::TYPE_OBJECT_HAS_FILE: return '%s removed %d file(s): %s.'; case self::TYPE_ACCOUNT_HAS_MEMBER: return '%s removed %d member(s): %s.'; case self::TYPE_MEMBER_HAS_ACCOUNT: return '%s removed %d account(s): %s.'; case self::TYPE_PURCAHSE_HAS_CHARGE: return '%s removed %d charge(s): %s.'; case self::TYPE_CHARGE_HAS_PURCHASE: return '%s removed %d purchase(s): %s.'; case self::TYPE_OBJECT_HAS_CONTRIBUTOR: return '%s removed %d contributor(s): %s.'; case self::TYPE_DREV_HAS_REVIEWER: return '%s removed %d reviewer(s): %s.'; case self::TYPE_TASK_HAS_MOCK: return '%s removed %d mock(s): %s.'; case self::TYPE_SUBSCRIBED_TO_OBJECT: 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.'; } } public static function getFeedStringForEdgeType($type) { switch ($type) { case self::TYPE_TASK_HAS_COMMIT: case self::TYPE_PROJECT_HAS_COMMIT: case self::TYPE_DREV_HAS_COMMIT: return '%s updated commits of %s.'; case self::TYPE_COMMIT_HAS_TASK: case self::TYPE_TASK_DEPENDS_ON_TASK: case self::TYPE_TASK_DEPENDED_ON_BY_TASK: case self::TYPE_DREV_HAS_RELATED_TASK: case self::TYPE_MOCK_HAS_TASK: return '%s updated tasks of %s.'; case self::TYPE_DREV_DEPENDS_ON_DREV: case self::TYPE_DREV_DEPENDED_ON_BY_DREV: case self::TYPE_TASK_HAS_RELATED_DREV: case self::TYPE_COMMIT_HAS_DREV: case self::TYPE_REVIEWER_FOR_DREV: return '%s updated revisions of %s.'; case self::TYPE_BLOG_HAS_POST: return '%s updated posts of %s.'; case self::TYPE_POST_HAS_BLOG: case self::TYPE_BLOGGER_HAS_BLOG: return '%s updated blogs of %s.'; case self::TYPE_BLOG_HAS_BLOGGER: return '%s updated bloggers of %s.'; case self::TYPE_PROJ_MEMBER: 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: return '%s updated voting users of %s.'; case self::TYPE_VOTING_USER_HAS_QUESTION: return '%s updated questions of %s.'; case self::TYPE_VOTING_USER_HAS_ANSWER: return '%s updated answers of %s.'; case self::TYPE_OBJECT_HAS_SUBSCRIBER: return '%s updated subscribers of %s.'; case self::TYPE_OBJECT_HAS_UNSUBSCRIBER: return '%s updated unsubcribers of %s.'; case self::TYPE_OBJECT_HAS_FILE: return '%s updated files of %s.'; case self::TYPE_ACCOUNT_HAS_MEMBER: return '%s updated members of %s.'; case self::TYPE_MEMBER_HAS_ACCOUNT: return '%s updated accounts of %s.'; case self::TYPE_PURCAHSE_HAS_CHARGE: return '%s updated charges of %s.'; case self::TYPE_CHARGE_HAS_PURCHASE: return '%s updated purchases of %s.'; case self::TYPE_OBJECT_HAS_CONTRIBUTOR: return '%s updated contributors of %s.'; case self::TYPE_DREV_HAS_REVIEWER: return '%s updated reviewers of %s.'; case self::TYPE_TASK_HAS_MOCK: return '%s updated mocks of %s.'; case self::TYPE_SUBSCRIBED_TO_OBJECT: 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.'; } } }