diff --git a/resources/sql/autopatches/20160111.repo.01.slugx.sql b/resources/sql/autopatches/20160111.repo.01.slugx.sql new file mode 100644 index 0000000000..41be6517d0 --- /dev/null +++ b/resources/sql/autopatches/20160111.repo.01.slugx.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_repository.repository_transaction + SET transactionType = 'repo:slug' WHERE transactionType = 'repo:clone-name'; diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php index 1e4de51cff..65f7dd0974 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php @@ -1,176 +1,163 @@ loadDiffusionContextForEdit(); if ($response) { return $response; } $viewer = $request->getUser(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/'); $v_name = $repository->getName(); $v_desc = $repository->getDetail('description'); - $v_clone_name = $repository->getRepositorySlug(); + $v_slug = $repository->getRepositorySlug(); $v_projects = PhabricatorEdgeQuery::loadDestinationPHIDs( $repository->getPHID(), PhabricatorProjectObjectHasProjectEdgeType::EDGECONST); $e_name = true; $e_slug = null; $errors = array(); $validation_exception = null; if ($request->isFormPost()) { $v_name = $request->getStr('name'); $v_desc = $request->getStr('description'); $v_projects = $request->getArr('projectPHIDs'); - - if ($repository->isHosted()) { - $v_clone_name = $request->getStr('cloneName'); - } + $v_slug = $request->getStr('slug'); 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; - $type_clone_name = PhabricatorRepositoryTransaction::TYPE_CLONE_NAME; + $type_slug = PhabricatorRepositoryTransaction::TYPE_SLUG; $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_clone_name) - ->setNewValue($v_clone_name); + ->setTransactionType($type_slug) + ->setNewValue($v_slug); $xactions[] = id(clone $template) ->setTransactionType($type_edge) ->setMetadataValue( 'edge:type', PhabricatorProjectObjectHasProjectEdgeType::EDGECONST) ->setNewValue( array( '=' => array_fuse($v_projects), )); $editor = id(new PhabricatorRepositoryEditor()) ->setContinueOnNoEffect(true) ->setContentSourceFromRequest($request) ->setActor($viewer); try { $editor->applyTransactions($repository, $xactions); return id(new AphrontRedirectResponse())->setURI($edit_uri); } catch (PhabricatorApplicationTransactionValidationException $ex) { $validation_exception = $ex; - $e_slug = $ex->getShortMessage($type_clone_name); + $e_slug = $ex->getShortMessage($type_slug); } } } $crumbs = $this->buildApplicationCrumbs(); $crumbs->addTextCrumb(pht('Edit Basics')); $title = pht('Edit %s', $repository->getName()); $form = id(new AphrontFormView()) ->setUser($viewer) ->appendChild( id(new AphrontFormTextControl()) ->setName('name') ->setLabel(pht('Name')) ->setValue($v_name) - ->setError($e_name)); - - if ($repository->isHosted()) { - $form - ->appendChild( - id(new AphrontFormTextControl()) - ->setName('cloneName') - ->setLabel(pht('Clone/Checkout As')) - ->setValue($v_clone_name) - ->setError($e_slug) - ->setCaption( - pht( - 'Optional directory name to use when cloning or checking out '. - 'this repository.'))); - } - - $form + ->setError($e_name)) + ->appendChild( + id(new AphrontFormTextControl()) + ->setName('slug') + ->setLabel(pht('Short Name')) + ->setValue($v_slug) + ->setError($e_slug)) ->appendChild( id(new PhabricatorRemarkupControl()) ->setUser($viewer) ->setName('description') ->setLabel(pht('Description')) ->setValue($v_desc)) ->appendControl( id(new AphrontFormTokenizerControl()) ->setDatasource(new PhabricatorProjectDatasource()) ->setName('projectPHIDs') ->setLabel(pht('Projects')) ->setValue($v_projects)) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue(pht('Save')) ->addCancelButton($edit_uri)) ->appendChild(id(new PHUIFormDividerControl())) ->appendRemarkupInstructions($this->getReadmeInstructions()); $object_box = id(new PHUIObjectBoxView()) ->setHeaderText($title) ->setValidationException($validation_exception) ->setForm($form) ->setFormErrors($errors); return $this->newPage() ->setTitle($title) ->setCrumbs($crumbs) ->appendChild($object_box); } private function getReadmeInstructions() { return pht(<<loadDiffusionContextForEdit(); if ($response) { return $response; } $viewer = $this->getViewer(); $drequest = $this->getDiffusionRequest(); $repository = $drequest->getRepository(); $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(); $supports_staging = $repository->supportsStaging(); $supports_automation = $repository->supportsAutomation(); $crumbs = $this->buildApplicationCrumbs($is_main = true); $title = pht('Edit %s', $repository->getName()); $header = id(new PHUIHeaderView()) ->setHeader($title); if ($repository->isTracked()) { $header->setStatus('fa-check', 'bluegrey', pht('Active')); } else { $header->setStatus('fa-ban', 'dark', 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); $symbols_actions = $this->buildSymbolsActions($repository); $symbols_properties = $this->buildSymbolsProperties($repository, $symbols_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)); } $storage_properties = null; if ($has_local) { $storage_properties = $this->buildStorageProperties( $repository, $this->buildStorageActions($repository)); } $staging_properties = null; if ($supports_staging) { $staging_properties = $this->buildStagingProperties( $repository, $this->buildStagingActions($repository)); } $automation_properties = null; if ($supports_automation) { $automation_properties = $this->buildAutomationProperties( $repository, $this->buildAutomationActions($repository)); } $actions_properties = $this->buildActionsProperties( $repository, $this->buildActionsActions($repository)); $timeline = $this->buildTransactionTimeline( $repository, new PhabricatorRepositoryTransactionQuery()); $timeline->setShouldTerminate(true); $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'); $mirror_info = array(); if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { $mirror_info[] = pht( 'Phabricator is running in silent mode, so changes will not '. 'be pushed to mirrors.'); } $boxes[] = id(new PHUIObjectBoxView()) ->setFormErrors($mirror_info) ->setHeaderText(pht('Mirrors')) ->addPropertyList($mirror_properties); $boxes[] = $mirror_list; } if ($remote_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Remote')) ->addPropertyList($remote_properties); } if ($storage_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Storage')) ->addPropertyList($storage_properties); } if ($staging_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Staging')) ->addPropertyList($staging_properties); } if ($automation_properties) { $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Automation')) ->addPropertyList($automation_properties); } $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Text Encoding')) ->addPropertyList($encoding_properties); $boxes[] = id(new PHUIObjectBoxView()) ->setHeaderText(pht('Symbols')) ->addPropertyList($symbols_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, $timeline, ), array( 'title' => $title, )); } private function buildBasicActions(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Basic Information')) ->setHref($this->getRepositoryControllerURI($repository, 'edit/basic/')); $view->addAction($edit); $edit = id(new PhabricatorActionView()) ->setIcon('fa-refresh') ->setName(pht('Update Now')) ->setWorkflow(true) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/update/')); $view->addAction($edit); $activate = id(new PhabricatorActionView()) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/activate/')) ->setWorkflow(true); if ($repository->isTracked()) { $activate ->setIcon('fa-pause') ->setName(pht('Deactivate Repository')); } else { $activate ->setIcon('fa-play') ->setName(pht('Activate Repository')); } $view->addAction($activate); $view->addAction( id(new PhabricatorActionView()) ->setName(pht('Delete Repository')) ->setIcon('fa-times') ->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) ->setObject($repository) ->setActionList($actions); $type = PhabricatorRepositoryType::getNameForRepositoryType( $repository->getVersionControlSystem()); $view->addProperty(pht('Type'), $type); $view->addProperty(pht('Callsign'), $repository->getCallsign()); - $clone_name = $repository->getRepositorySlug(); - - if ($repository->isHosted()) { - $view->addProperty( - pht('Clone/Checkout As'), - $clone_name - ? $clone_name.'/' - : phutil_tag('em', array(), $repository->getCloneName().'/')); + $short_name = $repository->getRepositorySlug(); + if ($short_name === null) { + $short_name = $repository->getCloneName(); + $short_name = phutil_tag('em', array(), $short_name); } + $view->addProperty(pht('Short Name'), $short_name); $view->invokeWillRenderEvent(); $view->addProperty( pht('Status'), $this->buildRepositoryStatus($repository)); $view->addProperty( pht('Update Frequency'), $this->buildRepositoryUpdateInterval($repository)); $description = $repository->getDetail('description'); $view->addSectionHeader( pht('Description'), PHUIPropertyListView::ICON_SUMMARY); 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()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->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()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->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_parts = array(); if (PhabricatorSpacesNamespaceQuery::getViewerSpacesExist($viewer)) { $space_phid = PhabricatorSpacesNamespaceQuery::getObjectSpacePHID( $repository); $view_parts[] = $viewer->renderHandle($space_phid); } $view_parts[] = $descriptions[PhabricatorPolicyCapability::CAN_VIEW]; $view->addProperty( pht('Visible To'), phutil_implode_html(" \xC2\xB7 ", $view_parts)); $view->addProperty( pht('Editable By'), $descriptions[PhabricatorPolicyCapability::CAN_EDIT]); $pushable = $repository->isHosted() ? $descriptions[DiffusionPushCapability::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()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->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()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->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()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->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()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->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) { $view->addProperty( pht('Credential'), $viewer->renderHandle($credential_phid)); } return $view; } private function buildStorageActions(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Storage')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/storage/')); $view->addAction($edit); return $view; } private function buildStorageProperties( PhabricatorRepository $repository, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setActionList($actions); $service_phid = $repository->getAlmanacServicePHID(); if ($service_phid) { $v_service = $viewer->renderHandle($service_phid); } else { $v_service = phutil_tag( 'em', array(), pht('Local')); } $view->addProperty( pht('Storage Service'), $v_service); $view->addProperty( pht('Storage Path'), $repository->getHumanReadableDetail('local-path')); return $view; } private function buildStagingActions(PhabricatorRepository $repository) { $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Staging')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/staging/')); $view->addAction($edit); return $view; } private function buildStagingProperties( PhabricatorRepository $repository, PhabricatorActionListView $actions) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setActionList($actions); $staging_uri = $repository->getStagingURI(); if (!$staging_uri) { $staging_uri = phutil_tag('em', array(), pht('No Staging Area')); } $view->addProperty( pht('Staging Area'), $staging_uri); return $view; } private function buildAutomationActions(PhabricatorRepository $repository) { $viewer = $this->getViewer(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Automation')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/automation/')); $view->addAction($edit); $can_test = $repository->canPerformAutomation(); $test = id(new PhabricatorActionView()) ->setIcon('fa-gamepad') ->setName(pht('Test Configuration')) ->setWorkflow(true) ->setDisabled(!$can_test) ->setHref( $this->getRepositoryControllerURI( $repository, 'edit/testautomation/')); $view->addAction($test); return $view; } private function buildAutomationProperties( PhabricatorRepository $repository, PhabricatorActionListView $actions) { $viewer = $this->getViewer(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setActionList($actions); $blueprint_phids = $repository->getAutomationBlueprintPHIDs(); if (!$blueprint_phids) { $blueprint_view = phutil_tag('em', array(), pht('Not Configured')); } else { $blueprint_view = id(new DrydockObjectAuthorizationView()) ->setUser($viewer) ->setObjectPHID($repository->getPHID()) ->setBlueprintPHIDs($blueprint_phids); } $view->addProperty(pht('Automation'), $blueprint_view); return $view; } private function buildHostingActions(PhabricatorRepository $repository) { $user = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setUser($user); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Hosting')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/hosting/')); $view->addAction($edit); if ($repository->canAllowDangerousChanges()) { if ($repository->shouldAllowDangerousChanges()) { $changes = id(new PhabricatorActionView()) ->setIcon('fa-shield') ->setName(pht('Prevent Dangerous Changes')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/dangerous/')) ->setWorkflow(true); } else { $changes = id(new PhabricatorActionView()) ->setIcon('fa-bullseye') ->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(); $is_cluster = $repository->getAlmanacServicePHID(); $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(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('Repository Active'))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'bluegrey') ->setTarget(pht('Repository Inactive')) ->setNote( pht('Activate this repository to begin or resume import.'))); return $view; } $binaries = array(); $svnlook_check = false; 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'; $binaries[] = 'svnlook'; $svnlook_check = true; 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'; $binaries[] = 'svnlook'; $svnlook_check = true; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $binaries[] = 'hg'; break; } } } $binaries = array_unique($binaries); if (!$is_cluster) { // We're only checking for binaries if we aren't running with a cluster // configuration. In theory, we could check for binaries on the // repository host machine, but we'd need to make this more complicated // to do that. foreach ($binaries as $binary) { $where = Filesystem::resolveBinary($binary); if (!$where) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_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.", $this->getEnvConfigLink()))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget( pht('Found Binary %s', phutil_tag('tt', array(), $binary))) ->setNote(phutil_tag('tt', array(), $where))); } } // This gets checked generically above. However, for svn commit hooks, we // need this to be in environment.append-paths because subversion strips // PATH. if ($svnlook_check) { $where = Filesystem::resolveBinary('svnlook'); if ($where) { $path = substr($where, 0, strlen($where) - strlen('svnlook')); $dirs = PhabricatorEnv::getEnvConfig('environment.append-paths'); $in_path = false; foreach ($dirs as $dir) { if (Filesystem::isDescendant($path, $dir)) { $in_path = true; break; } } if (!$in_path) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') ->setTarget( pht('Missing Binary %s', phutil_tag('tt', array(), $binary))) ->setNote(pht( 'Unable to find this binary in `%s`. '. 'You need to configure %s and include %s.', 'environment.append-paths', $this->getEnvConfigLink(), $path))); } } } } $doc_href = PhabricatorEnv::getDocLink('Managing Daemons with phd'); $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) { // TODO: In a cluster environment, we need a daemon on this repository's // host, specifically, and we aren't checking for that right now. This // is a reasonable proxy for things being more-or-less correctly set up, // though. $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('Pull Daemon Running'))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_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(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('Task Daemon Running'))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') ->setTarget(pht('Task Daemon Not Running')) ->setNote($daemon_instructions)); } if ($is_cluster) { // Just omit this status check for now in cluster environments. We // could make a service call and pull it from the repository host // eventually. } else if ($repository->usesLocalWorkingCopy()) { $local_parent = dirname($repository->getLocalPath()); if (Filesystem::pathExists($local_parent)) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('Storage Directory OK')) ->setNote(phutil_tag('tt', array(), $local_parent))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_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(PHUIStatusItemView::ICON_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(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('Working Copy OK')) ->setNote(phutil_tag('tt', array(), $local_path))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_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(PHUIStatusItemView::ICON_CLOCK, 'green') ->setTarget(pht('Initializing Working Copy')) ->setNote(pht('Daemons are initializing the working copy.'))); return $view; default: $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') ->setTarget(pht('Unknown Init Status')) ->setNote($message->getStatusCode())); return $view; } } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_CLOCK, '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: $message = $message->getParameter('message'); $suggestion = null; if (preg_match('/Permission denied \(publickey\)./', $message)) { $suggestion = pht( 'Public Key Error: This error usually indicates that the '. 'keypair you have configured does not have permission to '. 'access the repository.'); } $message = phutil_escape_html_newlines($message); if ($suggestion !== null) { $message = array( phutil_tag('strong', array(), $suggestion), phutil_tag('br'), phutil_tag('br'), phutil_tag('em', array(), pht('Raw Error')), phutil_tag('br'), $message, ); } $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_WARNING, 'red') ->setTarget(pht('Update Error')) ->setNote($message)); return $view; case PhabricatorRepositoryStatusMessage::CODE_OKAY: $ago = (PhabricatorTime::getNow() - $message->getEpoch()); $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('Updates OK')) ->setNote( pht( 'Last updated %s (%s ago).', phabricator_datetime($message->getEpoch(), $viewer), phutil_format_relative_time_detailed($ago)))); break; } } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'orange') ->setTarget(pht('Waiting For Update')) ->setNote( pht('Waiting for daemons to read updates.'))); } if ($repository->isImporting()) { $ratio = $repository->loadImportProgress(); $percentage = sprintf('%.2f%%', 100 * $ratio); $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_CLOCK, 'green') ->setTarget(pht('Importing')) ->setNote( pht('%s Complete', $percentage))); } else { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_ACCEPT, 'green') ->setTarget(pht('Fully Imported'))); } if (idx($messages, PhabricatorRepositoryStatusMessage::TYPE_NEEDS_UPDATE)) { $view->addItem( id(new PHUIStatusItemView()) ->setIcon(PHUIStatusItemView::ICON_UP, 'indigo') ->setTarget(pht('Prioritized')) ->setNote(pht('This repository will be updated soon!'))); } return $view; } private function buildRepositoryUpdateInterval( PhabricatorRepository $repository) { $smart_wait = $repository->loadUpdateInterval(); $doc_href = PhabricatorEnv::getDoclink( 'Diffusion User Guide: Repository Updates'); return array( phutil_format_relative_time_detailed($smart_wait), " \xC2\xB7 ", phutil_tag( 'a', array( 'href' => $doc_href, 'target' => '_blank', ), pht('Learn More')), ); } private function buildMirrorActions( PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $mirror_actions = id(new PhabricatorActionListView()) ->setUser($viewer); $new_mirror_uri = $this->getRepositoryControllerURI( $repository, 'mirror/edit/'); $mirror_actions->addAction( id(new PhabricatorActionView()) ->setName(pht('Add Mirror')) ->setIcon('fa-plus') ->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('fa-pencil') ->setHref($edit_uri) ->setWorkflow(true)); $item->addAction( id(new PHUIListItemView()) ->setIcon('fa-times') ->setHref($delete_uri) ->setWorkflow(true)); $mirror_list->addItem($item); } return $mirror_list; } private function buildSymbolsActions(PhabricatorRepository $repository) { $viewer = $this->getRequest()->getUser(); $view = id(new PhabricatorActionListView()) ->setUser($viewer); $edit = id(new PhabricatorActionView()) ->setIcon('fa-pencil') ->setName(pht('Edit Symbols')) ->setHref( $this->getRepositoryControllerURI($repository, 'edit/symbol/')); $view->addAction($edit); return $view; } private function buildSymbolsProperties( PhabricatorRepository $repository, PhabricatorActionListView $actions) { $viewer = $this->getRequest()->getUser(); $view = id(new PHUIPropertyListView()) ->setUser($viewer) ->setActionList($actions); $languages = $repository->getSymbolLanguages(); if ($languages) { $languages = implode(', ', $languages); } else { $languages = phutil_tag('em', array(), pht('Any')); } $view->addProperty(pht('Languages'), $languages); $sources = $repository->getSymbolSources(); if ($sources) { $handles = $viewer->loadHandles($sources); $sources = $handles->renderList(); } else { $sources = phutil_tag('em', array(), pht('This Repository Only')); } $view->addProperty(pht('Use Symbols From'), $sources); return $view; } private function getEnvConfigLink() { $config_href = '/config/edit/environment.append-paths/'; return phutil_tag( 'a', array( 'href' => $config_href, ), 'environment.append-paths'); } } diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php index 9e04a5552f..fc397daa1e 100644 --- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php +++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php @@ -1,521 +1,521 @@ 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(); - case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME: + case PhabricatorRepositoryTransaction::TYPE_SLUG: return $object->getRepositorySlug(); case PhabricatorRepositoryTransaction::TYPE_SERVICE: return $object->getAlmanacServicePHID(); case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: return $object->getSymbolLanguages(); case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: return $object->getSymbolSources(); case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: return $object->getDetail('staging-uri'); case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: return $object->getDetail('automation.blueprintPHIDs', array()); } } 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: case PhabricatorRepositoryTransaction::TYPE_SERVICE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: return $xaction->getNewValue(); - case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME: + case PhabricatorRepositoryTransaction::TYPE_SLUG: $name = $xaction->getNewValue(); if (strlen($name)) { return $name; } return null; 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_CLONE_NAME: + case PhabricatorRepositoryTransaction::TYPE_SLUG: $object->setRepositorySlug($xaction->getNewValue()); return; case PhabricatorRepositoryTransaction::TYPE_SERVICE: $object->setAlmanacServicePHID($xaction->getNewValue()); return; case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: $object->setDetail('symbol-languages', $xaction->getNewValue()); return; case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: $object->setDetail('symbol-sources', $xaction->getNewValue()); return; case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: $object->setDetail('staging-uri', $xaction->getNewValue()); return; case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: $object->setDetail( 'automation.blueprintPHIDs', $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 = new PhabricatorEdgeEditor(); $edge_type = PhabricatorObjectUsesCredentialsEdgeType::EDGECONST; $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; case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: DrydockAuthorization::applyAuthorizationChanges( $this->getActor(), $object->getPHID(), $xaction->getOldValue(), $xaction->getNewValue()); break; } } 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: - case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME: + case PhabricatorRepositoryTransaction::TYPE_SLUG: case PhabricatorRepositoryTransaction::TYPE_SERVICE: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_SOURCES: case PhabricatorRepositoryTransaction::TYPE_SYMBOLS_LANGUAGE: case PhabricatorRepositoryTransaction::TYPE_STAGING_URI: case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: PhabricatorPolicyFilter::requireCapability( $this->requireActor(), $object, PhabricatorPolicyCapability::CAN_EDIT); break; } } protected function validateTransaction( PhabricatorLiskDAO $object, $type, array $xactions) { $errors = parent::validateTransaction($object, $type, $xactions); switch ($type) { case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY: foreach ($xactions as $xaction) { foreach ($xaction->getNewValue() as $pattern) { // Check for invalid regular expressions. $regexp = PhabricatorRepository::extractBranchRegexp($pattern); if ($regexp !== null) { $ok = @preg_match($regexp, ''); if ($ok === false) { $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( 'Expression "%s" is not a valid regular expression. Note '. 'that you must include delimiters.', $regexp), $xaction); $errors[] = $error; continue; } } // Check for formatting mistakes like `regex(...)` instead of // `regexp(...)`. $matches = null; if (preg_match('/^([^(]+)\\(.*\\)\z/', $pattern, $matches)) { switch ($matches[1]) { case 'regexp': break; default: $error = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( 'Matching function "%s(...)" is not recognized. Valid '. 'functions are: regexp(...).', $matches[1]), $xaction); $errors[] = $error; break; } } } } break; case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: foreach ($xactions as $xaction) { $new_uri = $xaction->getNewValue(); try { PhabricatorRepository::assertValidRemoteURI($new_uri); } catch (Exception $ex) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), $ex->getMessage(), $xaction); } } break; case PhabricatorRepositoryTransaction::TYPE_CREDENTIAL: $ok = PassphraseCredentialControl::validateTransactions( $this->getActor(), $xactions); if (!$ok) { foreach ($xactions as $xaction) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( 'The selected credential does not exist, or you do not have '. 'permission to use it.'), $xaction); } } break; case PhabricatorRepositoryTransaction::TYPE_AUTOMATION_BLUEPRINTS: foreach ($xactions as $xaction) { $old = nonempty($xaction->getOldValue(), array()); $new = nonempty($xaction->getNewValue(), array()); $add = array_diff($new, $old); $invalid = PhabricatorObjectQuery::loadInvalidPHIDsForViewer( $this->getActor(), $add); if ($invalid) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), pht( 'Some of the selected automation blueprints are invalid '. 'or restricted: %s.', implode(', ', $invalid)), $xaction); } } break; - case PhabricatorRepositoryTransaction::TYPE_CLONE_NAME: + case PhabricatorRepositoryTransaction::TYPE_SLUG: foreach ($xactions as $xaction) { $old = $xaction->getOldValue(); $new = $xaction->getNewValue(); if (!strlen($new)) { continue; } if ($new === $old) { continue; } try { PhabricatorRepository::asssertValidRepositorySlug($new); } catch (Exception $ex) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Invalid'), $ex->getMessage(), $xaction); continue; } $other = id(new PhabricatorRepositoryQuery()) ->setViewer(PhabricatorUser::getOmnipotentUser()) ->withSlugs(array($new)) ->executeOne(); if ($other && ($other->getID() !== $object->getID())) { $errors[] = new PhabricatorApplicationTransactionValidationError( $type, pht('Duplicate'), pht( 'The selected repository short name is already in use by '. 'another repository. Choose a unique short name.'), $xaction); continue; } } break; } return $errors; } protected function didCatchDuplicateKeyException( PhabricatorLiskDAO $object, array $xactions, Exception $ex) { $errors = array(); $errors[] = new PhabricatorApplicationTransactionValidationError( null, pht('Invalid'), pht( 'The chosen callsign or repository short name is already in '. 'use by another repository.'), null); throw new PhabricatorApplicationTransactionValidationException($errors); } } diff --git a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php index c9077e0236..a10ac0a60b 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php +++ b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php @@ -1,489 +1,489 @@ getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_PUSH_POLICY: case self::TYPE_SERVICE: if ($old) { $phids[] = $old; } if ($new) { $phids[] = $new; } break; case self::TYPE_SYMBOLS_SOURCES: case self::TYPE_AUTOMATION_BLUEPRINTS: if ($old) { $phids = array_merge($phids, $old); } if ($new) { $phids = array_merge($phids, $new); } break; } return $phids; } public function shouldHide() { $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_REMOTE_URI: case self::TYPE_SSH_LOGIN: case self::TYPE_SSH_KEY: case self::TYPE_SSH_KEYFILE: case self::TYPE_HTTP_LOGIN: case self::TYPE_HTTP_PASS: // Hide null vs empty string changes. return (!strlen($old) && !strlen($new)); case self::TYPE_LOCAL_PATH: case self::TYPE_NAME: // Hide these on create, they aren't interesting and we have an // explicit "create" transaction. if (!strlen($old)) { return true; } break; } return parent::shouldHide(); } public function getIcon() { switch ($this->getTransactionType()) { case self::TYPE_VCS: return 'fa-plus'; } return parent::getIcon(); } public function getColor() { switch ($this->getTransactionType()) { case self::TYPE_VCS: return 'green'; } return parent::getIcon(); } public function getTitle() { $author_phid = $this->getAuthorPHID(); $old = $this->getOldValue(); $new = $this->getNewValue(); switch ($this->getTransactionType()) { case self::TYPE_VCS: return pht( '%s created this repository.', $this->renderHandleLink($author_phid)); case self::TYPE_ACTIVATE: if ($new) { return pht( '%s activated this repository.', $this->renderHandleLink($author_phid)); } else { return pht( '%s deactivated this repository.', $this->renderHandleLink($author_phid)); } case self::TYPE_NAME: return pht( '%s renamed this repository from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); case self::TYPE_DESCRIPTION: return pht( '%s updated the description of this repository.', $this->renderHandleLink($author_phid)); case self::TYPE_ENCODING: if (strlen($old) && !strlen($new)) { return pht( '%s removed the "%s" encoding configured for this repository.', $this->renderHandleLink($author_phid), $old); } else if (strlen($new) && !strlen($old)) { return pht( '%s set the encoding for this repository to "%s".', $this->renderHandleLink($author_phid), $new); } else { return pht( '%s changed the repository encoding from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); } case self::TYPE_DEFAULT_BRANCH: if (!strlen($new)) { return pht( '%s removed "%s" as the default branch.', $this->renderHandleLink($author_phid), $old); } else if (!strlen($old)) { return pht( '%s set the default branch to "%s".', $this->renderHandleLink($author_phid), $new); } else { return pht( '%s changed the default branch from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); } break; case self::TYPE_TRACK_ONLY: if (!$new) { return pht( '%s set this repository to track all branches.', $this->renderHandleLink($author_phid)); } else if (!$old) { return pht( '%s set this repository to track branches: %s.', $this->renderHandleLink($author_phid), implode(', ', $new)); } else { return pht( '%s changed track branches from "%s" to "%s".', $this->renderHandleLink($author_phid), implode(', ', $old), implode(', ', $new)); } break; case self::TYPE_AUTOCLOSE_ONLY: if (!$new) { return pht( '%s set this repository to autoclose on all branches.', $this->renderHandleLink($author_phid)); } else if (!$old) { return pht( '%s set this repository to autoclose on branches: %s.', $this->renderHandleLink($author_phid), implode(', ', $new)); } else { return pht( '%s changed autoclose branches from "%s" to "%s".', $this->renderHandleLink($author_phid), implode(', ', $old), implode(', ', $new)); } break; case self::TYPE_UUID: if (!strlen($new)) { return pht( '%s removed "%s" as the repository UUID.', $this->renderHandleLink($author_phid), $old); } else if (!strlen($old)) { return pht( '%s set the repository UUID to "%s".', $this->renderHandleLink($author_phid), $new); } else { return pht( '%s changed the repository UUID from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); } break; case self::TYPE_SVN_SUBPATH: if (!strlen($new)) { return pht( '%s removed "%s" as the Import Only path.', $this->renderHandleLink($author_phid), $old); } else if (!strlen($old)) { return pht( '%s set the repository to import only "%s".', $this->renderHandleLink($author_phid), $new); } else { return pht( '%s changed the import path from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); } break; case self::TYPE_NOTIFY: if ($new) { return pht( '%s enabled notifications and publishing for this repository.', $this->renderHandleLink($author_phid)); } else { return pht( '%s disabled notifications and publishing for this repository.', $this->renderHandleLink($author_phid)); } break; case self::TYPE_AUTOCLOSE: if ($new) { return pht( '%s enabled autoclose for this repository.', $this->renderHandleLink($author_phid)); } else { return pht( '%s disabled autoclose for this repository.', $this->renderHandleLink($author_phid)); } break; case self::TYPE_REMOTE_URI: if (!strlen($old)) { return pht( '%s set the remote URI for this repository to "%s".', $this->renderHandleLink($author_phid), $new); } else if (!strlen($new)) { return pht( '%s removed the remote URI for this repository.', $this->renderHandleLink($author_phid)); } else { return pht( '%s changed the remote URI for this repository from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); } break; case self::TYPE_SSH_LOGIN: return pht( '%s updated the SSH login for this repository.', $this->renderHandleLink($author_phid)); case self::TYPE_SSH_KEY: return pht( '%s updated the SSH key for this repository.', $this->renderHandleLink($author_phid)); case self::TYPE_SSH_KEYFILE: return pht( '%s updated the SSH keyfile for this repository.', $this->renderHandleLink($author_phid)); case self::TYPE_HTTP_LOGIN: return pht( '%s updated the HTTP login for this repository.', $this->renderHandleLink($author_phid)); case self::TYPE_HTTP_PASS: return pht( '%s updated the HTTP password for this repository.', $this->renderHandleLink($author_phid)); case self::TYPE_LOCAL_PATH: return pht( '%s changed the local path from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); case self::TYPE_HOSTING: if ($new) { return pht( '%s changed this repository to be hosted on Phabricator.', $this->renderHandleLink($author_phid)); } else { return pht( '%s changed this repository to track a remote elsewhere.', $this->renderHandleLink($author_phid)); } case self::TYPE_PROTOCOL_HTTP: return pht( '%s changed the availability of this repository over HTTP from '. '"%s" to "%s".', $this->renderHandleLink($author_phid), PhabricatorRepository::getProtocolAvailabilityName($old), PhabricatorRepository::getProtocolAvailabilityName($new)); case self::TYPE_PROTOCOL_SSH: return pht( '%s changed the availability of this repository over SSH from '. '"%s" to "%s".', $this->renderHandleLink($author_phid), PhabricatorRepository::getProtocolAvailabilityName($old), PhabricatorRepository::getProtocolAvailabilityName($new)); case self::TYPE_PUSH_POLICY: return pht( '%s changed the push policy of this repository from "%s" to "%s".', $this->renderHandleLink($author_phid), $this->renderPolicyName($old, 'old'), $this->renderPolicyName($new, 'new')); case self::TYPE_DANGEROUS: if ($new) { return pht( '%s disabled protection against dangerous changes.', $this->renderHandleLink($author_phid)); } else { return pht( '%s enabled protection against dangerous changes.', $this->renderHandleLink($author_phid)); } - case self::TYPE_CLONE_NAME: + case self::TYPE_SLUG: if (strlen($old) && !strlen($new)) { return pht( - '%s removed the clone name of this repository.', + '%s removed the short name of this repository.', $this->renderHandleLink($author_phid)); } else if (strlen($new) && !strlen($old)) { return pht( - '%s set the clone name of this repository to "%s".', + '%s set the short name of this repository to "%s".', $this->renderHandleLink($author_phid), $new); } else { return pht( - '%s changed the clone name of this repository from "%s" to "%s".', + '%s changed the short name of this repository from "%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); } case self::TYPE_SERVICE: if (strlen($old) && !strlen($new)) { return pht( '%s moved storage for this repository from %s to local.', $this->renderHandleLink($author_phid), $this->renderHandleLink($old)); } else if (!strlen($old) && strlen($new)) { // TODO: Possibly, we should distinguish between automatic assignment // on creation vs explicit adjustment. return pht( '%s set storage for this repository to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($new)); } else { return pht( '%s moved storage for this repository from %s to %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink($old), $this->renderHandleLink($new)); } case self::TYPE_SYMBOLS_SOURCES: return pht( '%s changed symbol sources from %s to %s.', $this->renderHandleLink($author_phid), empty($old) ? pht('None') : $this->renderHandleList($old), empty($new) ? pht('None') : $this->renderHandleList($new)); case self::TYPE_SYMBOLS_LANGUAGE: return pht('%s changed indexed languages from %s to %s.', $this->renderHandleLink($author_phid), $old ? implode(', ', $old) : pht('Any'), $new ? implode(', ', $new) : pht('Any')); case self::TYPE_STAGING_URI: if (!$old) { return pht( '%s set "%s" as the staging area for this repository.', $this->renderHandleLink($author_phid), $new); } else if (!$new) { return pht( '%s removed "%s" as the staging area for this repository.', $this->renderHandleLink($author_phid), $old); } else { return pht( '%s changed the staging area for this repository from '. '"%s" to "%s".', $this->renderHandleLink($author_phid), $old, $new); } case self::TYPE_AUTOMATION_BLUEPRINTS: $add = array_diff($new, $old); $rem = array_diff($old, $new); if ($add && $rem) { return pht( '%s changed %s automation blueprint(s), '. 'added %s: %s; removed %s: %s.', $this->renderHandleLink($author_phid), new PhutilNumber(count($add) + count($rem)), new PhutilNumber(count($add)), $this->renderHandleList($add), new PhutilNumber(count($rem)), $this->renderHandleList($rem)); } else if ($add) { return pht( '%s added %s automation blueprint(s): %s.', $this->renderHandleLink($author_phid), new PhutilNumber(count($add)), $this->renderHandleList($add)); } else { return pht( '%s removed %s automation blueprint(s): %s.', $this->renderHandleLink($author_phid), new PhutilNumber(count($rem)), $this->renderHandleList($rem)); } } return parent::getTitle(); } public function hasChangeDetails() { switch ($this->getTransactionType()) { case self::TYPE_DESCRIPTION: return true; } return parent::hasChangeDetails(); } public function renderChangeDetails(PhabricatorUser $viewer) { return $this->renderTextCorpusChangeDetails( $viewer, $this->getOldValue(), $this->getNewValue()); } }