diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php index 95841e54bf..34937bde11 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -1,611 +1,617 @@ 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->appendChild($readme); $box->addPadding(PHUI::PADDING_LARGE); $panel = new PHUIObjectBoxView(); $panel->setHeaderText(pht('README')); $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()) { $ssh_uri = $repository->getSSHCloneURIObject(); if ($ssh_uri) { $clone_uri = $this->renderCloneCommand( $repository, $ssh_uri, $repository->getServeOverSSH(), '/settings/panel/ssh/'); $view->addProperty( $repository->isSVN() ? pht('Checkout (SSH)') : pht('Clone (SSH)'), $clone_uri); } $http_uri = $repository->getHTTPCloneURIObject(); if ($http_uri) { $clone_uri = $this->renderCloneCommand( $repository, $http_uri, $repository->getServeOverHTTP(), PhabricatorEnv::getEnvConfig('diffusion.allow-http-auth') ? '/settings/panel/vcspassword/' : null); $view->addProperty( $repository->isSVN() ? pht('Checkout (HTTP)') : pht('Clone (HTTP)'), $clone_uri); } } else { switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $view->addProperty( pht('Clone'), $this->renderCloneCommand( $repository, $repository->getPublicCloneURI())); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $view->addProperty( pht('Checkout'), $this->renderCloneCommand( $repository, $repository->getPublicCloneURI())); 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 = $this->callConduitWithDiffusionRequest( 'diffusion.branchquery', array( 'limit' => $limit + 1, )); if (!$branches) { return null; } $more_branches = (count($branches) > $limit); $branches = array_slice($branches, 0, $limit); $branches = DiffusionRepositoryRef::loadAllFromDictionaries($branches); $commits = id(new DiffusionCommitQuery()) ->setViewer($viewer) ->withIdentifiers(mpull($branches, 'getCommitIdentifier')) ->withRepository($drequest->getRepository()) ->execute(); $table = id(new DiffusionBranchTableView()) ->setUser($viewer) ->setDiffusionRequest($drequest) ->setBranches($branches) ->setCommits($commits); $panel = new PHUIObjectBoxView(); $header = new PHUIHeaderView(); $header->setHeader(pht('Branches')); if ($more_branches) { $header->setSubHeader(pht('Showing %d branches.', $limit)); } $icon = id(new PHUIIconView()) ->setSpriteSheet(PHUIIconView::SPRITE_ICONS) ->setSpriteIcon('fork'); $button = new PHUIButtonView(); $button->setText(pht("Show All Branches")); $button->setTag('a'); $button->setIcon($icon); $button->setHref($drequest->generateURI( array( 'action' => 'branches', ))); $header->addActionLink($button); $panel->setHeader($header); $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 = new PHUIObjectBoxView(); $header = new PHUIHeaderView(); $header->setHeader(pht('Tags')); if ($more_tags) { $header->setSubHeader( pht('Showing the %d most recent tags.', $tag_limit)); } $icon = id(new PHUIIconView()) ->setSpriteSheet(PHUIIconView::SPRITE_ICONS) ->setSpriteIcon('tag'); $button = new PHUIButtonView(); $button->setText(pht("Show All Tags")); $button->setTag('a'); $button->setIcon($icon); $button->setHref($drequest->generateURI( array( 'action' => 'tags', ))); $header->addActionLink($button); $panel->setHeader($header); $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(); $icon = id(new PHUIIconView()) ->setSpriteSheet(PHUIIconView::SPRITE_ICONS) ->setSpriteIcon('transcript'); $button = id(new PHUIButtonView()) ->setText(pht('View Full History')) ->setHref($drequest->generateURI( array( 'action' => 'history', ))) ->setTag('a') ->setIcon($icon); $panel = new PHUIObjectBoxView(); $header = id(new PHUIHeaderView()) ->setHeader(pht('Recent Commits')) ->addActionLink($button); $panel->setHeader($header); $panel->appendChild($history_table); 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 PHUIObjectBoxView(); $header = id(new PHUIHeaderView()) ->setHeader(pht('Repository')); $icon = id(new PHUIIconView()) ->setSpriteSheet(PHUIIconView::SPRITE_ICONS) ->setSpriteIcon('data'); $button = new PHUIButtonView(); $button->setText(pht('Browse Repository')); $button->setTag('a'); $button->setIcon($icon); $button->setHref($browse_uri); $header->addActionLink($button); $browse_panel->setHeader($header); $browse_panel->appendChild($browse_table); return $browse_panel; } private function renderCloneCommand( PhabricatorRepository $repository, $uri, $serve_mode = null, $manage_uri = null) { require_celerity_resource('diffusion-icons-css'); Javelin::initBehavior('select-on-click'); switch ($repository->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $command = csprintf( 'git clone %R', $uri); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $command = csprintf( 'hg clone %R', $uri); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: - $command = csprintf( - 'svn checkout %R %R', - $uri, - $repository->getCloneName()); + if ($repository->isHosted()) { + $command = csprintf( + 'svn checkout %R %R', + $uri, + $repository->getCloneName()); + } else { + $command = csprintf( + 'svn checkout %R', + $uri); + } break; } $input = javelin_tag( 'input', array( 'type' => 'text', 'value' => (string)$command, '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 039edcb9a8..401f5ddcbf 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditBasicController.php @@ -1,162 +1,171 @@ 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'); $v_clone_name = $repository->getDetail('clone-name'); $e_name = true; $errors = array(); if ($request->isFormPost()) { $v_name = $request->getStr('name'); $v_desc = $request->getStr('description'); $v_projects = $request->getArr('projectPHIDs'); - $v_clone_name = $request->getStr('cloneName'); + + if ($repository->isHosted()) { + $v_clone_name = $request->getStr('cloneName'); + } 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; $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); $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()); $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 AphrontFormTextControl()) - ->setName('cloneName') - ->setLabel(pht('Clone/Checkout As')) - ->setValue($v_clone_name) - ->setCaption( - pht( - 'Optional directory name to use when cloning or checking out '. - 'this repository.'))) + ->setError($e_name)); + + if ($repository->isHosted()) { + $form + ->appendChild( + id(new AphrontFormTextControl()) + ->setName('cloneName') + ->setLabel(pht('Clone/Checkout As')) + ->setValue($v_clone_name) + ->setCaption( + pht( + 'Optional directory name to use when cloning or checking out '. + 'this repository.'))); + } + + $form ->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) ->setFormErrors($errors); 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()); $clone_name = $repository->getDetail('clone-name'); - $view->addProperty( - pht('Clone/Checkout As'), - $clone_name - ? $clone_name.'/' - : phutil_tag('em', array(), $repository->getCloneName().'/')); + if ($repository->isHosted()) { + $view->addProperty( + pht('Clone/Checkout As'), + $clone_name + ? $clone_name.'/' + : phutil_tag('em', array(), $repository->getCloneName().'/')); + } $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; } // Cap this at "99.99%", because it's confusing to users when the actual // fraction is "99.996%" and it rounds up to "100.00%". if ($percentage > 99.99) { $percentage = 99.99; } $percentage = sprintf('%.2f%%', $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/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index 8661f56f60..19b1e1999c 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1,1299 +1,1320 @@ 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( 'id' => $this->getID(), 'name' => $this->getName(), 'phid' => $this->getPHID(), 'callsign' => $this->getCallsign(), 'monogram' => $this->getMonogram(), 'vcs' => $this->getVersionControlSystem(), 'uri' => PhabricatorEnv::getProductionURI($this->getURI()), 'remoteURI' => (string)$this->getRemoteURI(), 'description' => $this->getDetail('description'), 'isActive' => $this->isTracked(), 'isHosted' => $this->isHosted(), 'isImporting' => $this->isImporting(), ); } public function getMonogram() { return 'r'.$this->getCallsign(); } 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); } /** * Get the name of the directory this repository should clone or checkout * into. For example, if the repository name is "Example Repository", a * reasonable name might be "example-repository". This is used to help users * get reasonable results when cloning repositories, since they generally do * not want to clone into directories called "X/" or "Example Repository/". * * @return string */ public function getCloneName() { $name = $this->getDetail('clone-name'); // Make some reasonable effort to produce reasonable default directory // names from repository names. if (!strlen($name)) { $name = $this->getName(); $name = phutil_utf8_strtolower($name); $name = preg_replace('@[/ -:]+@', '-', $name); $name = trim($name, '-'); if (!strlen($name)) { $name = $this->getCallsign(); } } return $name; } /* -( 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 getNormalizedPath() { $uri = (string)$this->getCloneURIObject(); switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: $normalized_uri = new PhabricatorRepositoryURINormalizer( PhabricatorRepositoryURINormalizer::TYPE_GIT, $uri); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $normalized_uri = new PhabricatorRepositoryURINormalizer( PhabricatorRepositoryURINormalizer::TYPE_SVN, $uri); break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: $normalized_uri = new PhabricatorRepositoryURINormalizer( PhabricatorRepositoryURINormalizer::TYPE_MERCURIAL, $uri); break; default: throw new Exception("Unrecognized version control system."); } return $normalized_uri->getNormalizedPath(); } 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; } if (!$this->shouldTrackBranch($branch)) { 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."); } $closeable_flag = PhabricatorRepositoryCommit::IMPORTED_CLOSEABLE; if ($commit->isPartiallyImported($closeable_flag)) { return true; } // TODO: Remove this eventually, it's no longer written to by the import // pipeline (however, old tasks may still be queued which don't reflect // the new data format). $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 clone (or checkout) URI for this repository, without authentication * information. * * @return string Repository URI. * @task uri */ public function getPublicCloneURI() { $uri = $this->getCloneURIObject(); // 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->isHosted()) { 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!"); } /** * Get the "best" clone/checkout URI for this repository, on any protocol. */ public function getCloneURIObject() { if (!$this->isHosted()) { - return $this->getRemoteURIObject(); + if ($this->isSVN()) { + // Make sure we pick up the "Import Only" path for Subversion, so + // the user clones the repository starting at the correct path, not + // from the root. + $base_uri = $this->getSubversionBaseURI(); + $base_uri = new PhutilURI($base_uri); + $path = $base_uri->getPath(); + if (!$path) { + $path = '/'; + } + + // If the trailing "@" is not required to escape the URI, strip it for + // readability. + if (!preg_match('/@.*@/', $path)) { + $path = rtrim($path, '@'); + } + + $base_uri->setPath($path); + return $base_uri; + } else { + return $this->getRemoteURIObject(); + } } // Choose the best URI: pick a read/write URI over a URI which is not // read/write, and SSH over HTTP. $serve_ssh = $this->getServeOverSSH(); $serve_http = $this->getServeOverHTTP(); if ($serve_ssh === self::SERVE_READWRITE) { return $this->getSSHCloneURIObject(); } else if ($serve_http === self::SERVE_READWRITE) { return $this->getHTTPCloneURIObject(); } else if ($serve_ssh !== self::SERVE_OFF) { return $this->getSSHCloneURIObject(); } else if ($serve_http !== self::SERVE_OFF) { return $this->getHTTPCloneURIObject(); } else { return null; } } /** * Get the repository's SSH clone/checkout URI, if one exists. */ public function getSSHCloneURIObject() { if (!$this->isHosted()) { if ($this->shouldUseSSH()) { return $this->getRemoteURIObject(); } else { return null; } } $serve_ssh = $this->getServeOverSSH(); if ($serve_ssh === self::SERVE_OFF) { return null; } $uri = new PhutilURI(PhabricatorEnv::getProductionURI($this->getURI())); if ($this->isSVN()) { $uri->setProtocol('svn+ssh'); } else { $uri->setProtocol('ssh'); } if ($this->isGit()) { $uri->setPath($uri->getPath().$this->getCloneName().'.git'); } else if ($this->isHg()) { $uri->setPath($uri->getPath().$this->getCloneName().'/'); } $ssh_user = PhabricatorEnv::getEnvConfig('diffusion.ssh-user'); if ($ssh_user) { $uri->setUser($ssh_user); } $uri->setPort(PhabricatorEnv::getEnvConfig('diffusion.ssh-port')); return $uri; } /** * Get the repository's HTTP clone/checkout URI, if one exists. */ public function getHTTPCloneURIObject() { if (!$this->isHosted()) { if ($this->shouldUseHTTP()) { return $this->getRemoteURIObject(); } else { return null; } } $serve_http = $this->getServeOverHTTP(); if ($serve_http === self::SERVE_OFF) { return null; } $uri = PhabricatorEnv::getProductionURI($this->getURI()); $uri = new PhutilURI($uri); if ($this->isGit()) { $uri->setPath($uri->getPath().$this->getCloneName().'.git'); } else if ($this->isHg()) { $uri->setPath($uri->getPath().$this->getCloneName().'/'); } return $uri; } /** * 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(); } $ref_cursors = id(new PhabricatorRepositoryRefCursor()) ->loadAllWhere('repositoryPHID = %s', $this->getPHID()); foreach ($ref_cursors as $cursor) { $cursor->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 getHookDirectories() { $directories = array(); if (!$this->isHosted()) { return $directories; } $root = $this->getLocalPath(); switch ($this->getVersionControlSystem()) { case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: if ($this->isWorkingCopyBare()) { $directories[] = $root.'/hooks/pre-receive-phabricator.d/'; } else { $directories[] = $root.'/.git/hooks/pre-receive-phabricator.d/'; } break; case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: $directories[] = $root.'/hooks/pre-commit-phabricator.d/'; break; case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: // NOTE: We don't support custom Mercurial hooks for now because they're // messy and we can't easily just drop a `hooks.d/` directory next to // the hooks. break; } return $directories; } 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->isGit() || $this->isHg()) { return true; } return false; } public function canAllowDangerousChanges() { if (!$this->isHosted()) { return false; } if ($this->isGit() || $this->isHg()) { 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/applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php b/src/applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php index 5c5d0f87c5..4d0384fe93 100644 --- a/src/applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php +++ b/src/applications/repository/storage/__tests__/PhabricatorRepositoryURITestCase.php @@ -1,95 +1,108 @@ true, ); } public function testURIGeneration() { $svn = PhabricatorRepositoryType::REPOSITORY_TYPE_SVN; $git = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT; $hg = PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL; $user = $this->generateNewTestUser(); $http_secret = id(new PassphraseSecret())->setSecretData('quack')->save(); $http_credential = PassphraseCredential::initializeNewCredential($user) ->setCredentialType(PassphraseCredentialTypePassword::CREDENTIAL_TYPE) ->setProvidesType(PassphraseCredentialTypePassword::PROVIDES_TYPE) ->setUsername('duck') ->setSecretID($http_secret->getID()) ->save(); $repo = PhabricatorRepository::initializeNewRepository($user) ->setVersionControlSystem($svn) ->setName('Test Repo') ->setCallsign('TESTREPO') ->setCredentialPHID($http_credential->getPHID()) ->save(); // Test HTTP URIs. $repo->setDetail('remote-uri', 'http://example.com/'); $repo->setVersionControlSystem($svn); $this->assertEqual('http://example.com/', $repo->getRemoteURI()); $this->assertEqual('http://example.com/', $repo->getPublicCloneURI()); $this->assertEqual('http://example.com/', $repo->getRemoteURIEnvelope()->openEnvelope()); $repo->setVersionControlSystem($git); $this->assertEqual('http://example.com/', $repo->getRemoteURI()); $this->assertEqual('http://example.com/', $repo->getPublicCloneURI()); $this->assertEqual('http://duck:quack@example.com/', $repo->getRemoteURIEnvelope()->openEnvelope()); $repo->setVersionControlSystem($hg); $this->assertEqual('http://example.com/', $repo->getRemoteURI()); $this->assertEqual('http://example.com/', $repo->getPublicCloneURI()); $this->assertEqual('http://duck:quack@example.com/', $repo->getRemoteURIEnvelope()->openEnvelope()); // Test SSH URIs. $repo->setDetail('remote-uri', 'ssh://example.com/'); $repo->setVersionControlSystem($svn); $this->assertEqual('ssh://example.com/', $repo->getRemoteURI()); $this->assertEqual('ssh://example.com/', $repo->getPublicCloneURI()); $this->assertEqual('ssh://example.com/', $repo->getRemoteURIEnvelope()->openEnvelope()); $repo->setVersionControlSystem($git); $this->assertEqual('ssh://example.com/', $repo->getRemoteURI()); $this->assertEqual('ssh://example.com/', $repo->getPublicCloneURI()); $this->assertEqual('ssh://example.com/', $repo->getRemoteURIEnvelope()->openEnvelope()); $repo->setVersionControlSystem($hg); $this->assertEqual('ssh://example.com/', $repo->getRemoteURI()); $this->assertEqual('ssh://example.com/', $repo->getPublicCloneURI()); $this->assertEqual('ssh://example.com/', $repo->getRemoteURIEnvelope()->openEnvelope()); // Test Git URIs. $repo->setDetail('remote-uri', 'git@example.com:path.git'); $repo->setVersionControlSystem($git); $this->assertEqual('git@example.com:path.git', $repo->getRemoteURI()); $this->assertEqual('git@example.com:path.git', $repo->getPublicCloneURI()); $this->assertEqual('git@example.com:path.git', $repo->getRemoteURIEnvelope()->openEnvelope()); + // Test SVN "Import Only" paths. + + $repo->setDetail('remote-uri', 'http://example.com/'); + $repo->setVersionControlSystem($svn); + $repo->setDetail('svn-subpath', 'projects/example/'); + + $this->assertEqual('http://example.com/', $repo->getRemoteURI()); + $this->assertEqual( + 'http://example.com/projects/example/', + $repo->getPublicCloneURI()); + $this->assertEqual('http://example.com/', + $repo->getRemoteURIEnvelope()->openEnvelope()); + } }