diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 2ee626fc2c..fdd512917d 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -449,6 +449,9 @@ phutil_register_library_map(array( 'DiffusionBrowseResultSet' => 'applications/diffusion/data/DiffusionBrowseResultSet.php', 'DiffusionBrowseSearchController' => 'applications/diffusion/controller/DiffusionBrowseSearchController.php', 'DiffusionBrowseTableView' => 'applications/diffusion/view/DiffusionBrowseTableView.php', + 'DiffusionCapabilityCreateRepositories' => 'applications/diffusion/capability/DiffusionCapabilityCreateRepositories.php', + 'DiffusionCapabilityDefaultEdit' => 'applications/diffusion/capability/DiffusionCapabilityDefaultEdit.php', + 'DiffusionCapabilityDefaultView' => 'applications/diffusion/capability/DiffusionCapabilityDefaultView.php', 'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php', 'DiffusionCommentListView' => 'applications/diffusion/view/DiffusionCommentListView.php', 'DiffusionCommentView' => 'applications/diffusion/view/DiffusionCommentView.php', @@ -2634,6 +2637,9 @@ phutil_register_library_map(array( 'DiffusionBrowseMainController' => 'DiffusionBrowseController', 'DiffusionBrowseSearchController' => 'DiffusionBrowseController', 'DiffusionBrowseTableView' => 'DiffusionView', + 'DiffusionCapabilityCreateRepositories' => 'PhabricatorPolicyCapability', + 'DiffusionCapabilityDefaultEdit' => 'PhabricatorPolicyCapability', + 'DiffusionCapabilityDefaultView' => 'PhabricatorPolicyCapability', 'DiffusionChangeController' => 'DiffusionController', 'DiffusionCommentListView' => 'AphrontView', 'DiffusionCommentView' => 'AphrontView', diff --git a/src/applications/diffusion/application/PhabricatorApplicationDiffusion.php b/src/applications/diffusion/application/PhabricatorApplicationDiffusion.php index a13fba9528..d14b250a32 100644 --- a/src/applications/diffusion/application/PhabricatorApplicationDiffusion.php +++ b/src/applications/diffusion/application/PhabricatorApplicationDiffusion.php @@ -103,4 +103,17 @@ final class PhabricatorApplicationDiffusion extends PhabricatorApplication { return 0.120; } + protected function getCustomCapabilities() { + return array( + DiffusionCapabilityDefaultView::CAPABILITY => array( + ), + DiffusionCapabilityDefaultEdit::CAPABILITY => array( + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), + DiffusionCapabilityCreateRepositories::CAPABILITY => array( + 'default' => PhabricatorPolicies::POLICY_ADMIN, + ), + ); + } + } diff --git a/src/applications/diffusion/capability/DiffusionCapabilityCreateRepositories.php b/src/applications/diffusion/capability/DiffusionCapabilityCreateRepositories.php new file mode 100644 index 0000000000..c831c95af5 --- /dev/null +++ b/src/applications/diffusion/capability/DiffusionCapabilityCreateRepositories.php @@ -0,0 +1,20 @@ +getRepositoryControllerURI($repository, 'edit/'); } else { + $this->requireApplicationCapability( + DiffusionCapabilityCreateRepositories::CAPABILITY); + $cancel_uri = $this->getApplicationURI(); } @@ -60,15 +63,19 @@ final class DiffusionRepositoryCreateController if ($request->isFormPost()) { $form->readFromRequest($request); if ($form->isComplete()) { + $is_create = ($this->edit === null); - if ($this->edit != 'remote') { - // TODO: This exception is heartwarming but should probably take more - // substantive actions. - throw new Exception("GOOD JOB AT FORM"); + if ($is_create) { + $repository = PhabricatorRepository::initializeNewRepository( + $viewer); } $template = id(new PhabricatorRepositoryTransaction()); + $type_name = PhabricatorRepositoryTransaction::TYPE_NAME; + $type_vcs = PhabricatorRepositoryTransaction::TYPE_VCS; + $type_activate = PhabricatorRepositoryTransaction::TYPE_ACTIVATE; + $type_local_path = PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH; $type_remote_uri = PhabricatorRepositoryTransaction::TYPE_REMOTE_URI; $type_ssh_login = PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN; $type_ssh_key = PhabricatorRepositoryTransaction::TYPE_SSH_KEY; @@ -78,6 +85,44 @@ final class DiffusionRepositoryCreateController $xactions = array(); + // If we're creating a new repository, set all this core stuff. + if ($is_create) { + $callsign = $form->getPage('name') + ->getControl('callsign')->getValue(); + + // We must set this to a unique value to save the repository + // initially, and it's immutable, so we don't bother using + // transactions to apply this change. + $repository->setCallsign($callsign); + + $xactions[] = id(clone $template) + ->setTransactionType($type_name) + ->setNewValue( + $form->getPage('name')->getControl('name')->getValue()); + + $xactions[] = id(clone $template) + ->setTransactionType($type_vcs) + ->setNewValue( + $form->getPage('vcs')->getControl('vcs')->getValue()); + + $activate = $form->getPage('done') + ->getControl('activate')->getValue(); + $xactions[] = id(clone $template) + ->setTransactionType($type_activate) + ->setNewValue( + ($activate == 'start')); + + $default_local_path = PhabricatorEnv::getEnvConfig( + 'repository.default-local-path'); + + $default_local_path = rtrim($default_local_path, '/'); + $default_local_path = $default_local_path.'/'.$callsign.'/'; + + $xactions[] = id(clone $template) + ->setTransactionType($type_local_path) + ->setNewValue($default_local_path); + } + $xactions[] = id(clone $template) ->setTransactionType($type_remote_uri) ->setNewValue( @@ -619,7 +664,7 @@ final class DiffusionRepositoryCreateController pht('Configure More Options First'), pht( 'Configure more options before beginning the repository '. - 'import. This will let you fine-tune settings.. You can '. + 'import. This will let you fine-tune settings. You can '. 'start the import whenever you are ready.'))); } diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php index 32e5c0db30..9642f9266f 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php @@ -336,12 +336,12 @@ final class DiffusionRepositoryEditMainController $view->addProperty(pht('Default Branch'), $default_branch); $track_only = nonempty( - $repository->getHumanReadableDetail('branch-filter'), + $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'), + $repository->getHumanReadableDetail('close-commits-filter', array()), phutil_tag('em', array(), pht('Autoclose On All Branches'))); $view->addProperty(pht('Autoclose Only'), $autoclose_only); diff --git a/src/applications/diffusion/controller/DiffusionRepositoryListController.php b/src/applications/diffusion/controller/DiffusionRepositoryListController.php index dc548dc5c5..cc85aa1009 100644 --- a/src/applications/diffusion/controller/DiffusionRepositoryListController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryListController.php @@ -90,7 +90,6 @@ final class DiffusionRepositoryListController extends DiffusionController $nav = new AphrontSideNavFilterView(); $nav->setBaseURI(new PhutilURI($this->getApplicationURI())); - id(new PhabricatorRepositorySearchEngine()) ->setViewer($viewer) ->addNavigationItems($nav->getMenu()); @@ -100,6 +99,22 @@ final class DiffusionRepositoryListController extends DiffusionController return $nav; } + public function buildApplicationCrumbs() { + $crumbs = parent::buildApplicationCrumbs(); + + $can_create = $this->hasApplicationCapability( + DiffusionCapabilityCreateRepositories::CAPABILITY); + + $crumbs->addAction( + id(new PHUIListItemView()) + ->setName(pht('Import Repository')) + ->setHref($this->getApplicationURI('/create/')) + ->setDisabled(!$can_create) + ->setIcon('create')); + + return $crumbs; + } + private function buildShortcuts() { $shortcuts = id(new PhabricatorRepositoryShortcut())->loadAll(); if ($shortcuts) { diff --git a/src/applications/repository/PhabricatorRepositoryConfigOptions.php b/src/applications/repository/PhabricatorRepositoryConfigOptions.php index 17d52652a8..32fb0bbb33 100644 --- a/src/applications/repository/PhabricatorRepositoryConfigOptions.php +++ b/src/applications/repository/PhabricatorRepositoryConfigOptions.php @@ -16,14 +16,14 @@ final class PhabricatorRepositoryConfigOptions public function getOptions() { return array( - $this->newOption('repository.default-local-path', 'string', null) + $this->newOption('repository.default-local-path', 'string', '/var/repo/') ->setSummary( pht("Default location to store local copies of repositories.")) ->setDescription( pht( "The default location in which to store local copies of ". "repositories. Anything stored in this directory will be assumed ". - "to be under the control of phabricator, which means that ". + "to be under the control of Phabricator, which means that ". "Phabricator will try to do some maintenance on working copies ". "if there are problems (such as a change to the remote origin ". "url). This maintenance may include completely removing (and ". diff --git a/src/applications/repository/conduit/ConduitAPI_repository_create_Method.php b/src/applications/repository/conduit/ConduitAPI_repository_create_Method.php index 1f9a168d65..7fba83c15d 100644 --- a/src/applications/repository/conduit/ConduitAPI_repository_create_Method.php +++ b/src/applications/repository/conduit/ConduitAPI_repository_create_Method.php @@ -62,15 +62,24 @@ final class ConduitAPI_repository_create_Method } protected function execute(ConduitAPIRequest $request) { - if (!$request->getUser()->getIsAdmin()) { - throw new ConduitException('ERR-PERMISSIONS'); - } + $application = id(new PhabricatorApplicationQuery()) + ->setViewer($request->getUser()) + ->withClasses(array('PhabricatorApplicationDiffusion')) + ->executeOne(); + + PhabricatorPolicyFilter::requireCapability( + $request->getUser(), + $application, + DiffusionCapabilityCreateRepositories::CAPABILITY); // TODO: This has some duplication with (and lacks some of the validation // of) the web workflow; refactor things so they can share more code as this - // stabilizes. + // stabilizes. Specifically, this should move to transactions since they + // work properly now. + + $repository = PhabricatorRepository::initializeNewRepository( + $request->getUser()); - $repository = new PhabricatorRepository(); $repository->setName($request->getValue('name')); $callsign = $request->getValue('callsign'); diff --git a/src/applications/repository/editor/PhabricatorRepositoryEditor.php b/src/applications/repository/editor/PhabricatorRepositoryEditor.php index dca02acd8d..cbe0f4d9f4 100644 --- a/src/applications/repository/editor/PhabricatorRepositoryEditor.php +++ b/src/applications/repository/editor/PhabricatorRepositoryEditor.php @@ -6,6 +6,7 @@ final class PhabricatorRepositoryEditor public function getTransactionTypes() { $types = parent::getTransactionTypes(); + $types[] = PhabricatorRepositoryTransaction::TYPE_VCS; $types[] = PhabricatorRepositoryTransaction::TYPE_ACTIVATE; $types[] = PhabricatorRepositoryTransaction::TYPE_NAME; $types[] = PhabricatorRepositoryTransaction::TYPE_DESCRIPTION; @@ -36,6 +37,8 @@ final class PhabricatorRepositoryEditor PhabricatorApplicationTransaction $xaction) { switch ($xaction->getTransactionType()) { + case PhabricatorRepositoryTransaction::TYPE_VCS: + return $object->getVersionControlSystem(); case PhabricatorRepositoryTransaction::TYPE_ACTIVATE: return $object->isTracked(); case PhabricatorRepositoryTransaction::TYPE_NAME: @@ -96,6 +99,7 @@ final class PhabricatorRepositoryEditor case PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN: case PhabricatorRepositoryTransaction::TYPE_HTTP_PASS: case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: + case PhabricatorRepositoryTransaction::TYPE_VCS: return $xaction->getNewValue(); case PhabricatorRepositoryTransaction::TYPE_NOTIFY: case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: @@ -108,6 +112,9 @@ final class PhabricatorRepositoryEditor 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; @@ -219,4 +226,36 @@ final class PhabricatorRepositoryEditor return parent::transactionHasEffect($object, $xaction); } + protected function requireCapabilities( + PhabricatorLiskDAO $object, + PhabricatorApplicationTransaction $xaction) { + + switch ($xaction->getTransactionType()) { + case PhabricatorRepositoryTransaction::TYPE_ACTIVATE: + case PhabricatorRepositoryTransaction::TYPE_NAME: + case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION: + case PhabricatorRepositoryTransaction::TYPE_ENCODING: + case PhabricatorRepositoryTransaction::TYPE_DEFAULT_BRANCH: + case PhabricatorRepositoryTransaction::TYPE_TRACK_ONLY: + case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE_ONLY: + case PhabricatorRepositoryTransaction::TYPE_UUID: + case PhabricatorRepositoryTransaction::TYPE_SVN_SUBPATH: + case PhabricatorRepositoryTransaction::TYPE_REMOTE_URI: + case PhabricatorRepositoryTransaction::TYPE_SSH_LOGIN: + case PhabricatorRepositoryTransaction::TYPE_SSH_KEY: + case PhabricatorRepositoryTransaction::TYPE_SSH_KEYFILE: + case PhabricatorRepositoryTransaction::TYPE_HTTP_LOGIN: + case PhabricatorRepositoryTransaction::TYPE_HTTP_PASS: + case PhabricatorRepositoryTransaction::TYPE_LOCAL_PATH: + case PhabricatorRepositoryTransaction::TYPE_VCS: + case PhabricatorRepositoryTransaction::TYPE_NOTIFY: + case PhabricatorRepositoryTransaction::TYPE_AUTOCLOSE: + PhabricatorPolicyFilter::requireCapability( + $this->requireActor(), + $object, + PhabricatorPolicyCapability::CAN_EDIT); + break; + } + } + } diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php index c395c0f1a7..09e35aff34 100644 --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -29,8 +29,8 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO protected $name; protected $callsign; protected $uuid; - protected $viewPolicy = PhabricatorPolicies::POLICY_USER; - protected $editPolicy = PhabricatorPolicies::POLICY_ADMIN; + protected $viewPolicy; + protected $editPolicy; protected $versionControlSystem; protected $details = array(); @@ -40,6 +40,20 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO private $commitCount = self::ATTACHABLE; private $mostRecentCommit = self::ATTACHABLE; + public static function initializeNewRepository(PhabricatorUser $actor) { + $app = id(new PhabricatorApplicationQuery()) + ->setViewer($actor) + ->withClasses(array('PhabricatorApplicationDiffusion')) + ->executeOne(); + + $view_policy = $app->getPolicy(DiffusionCapabilityDefaultView::CAPABILITY); + $edit_policy = $app->getPolicy(DiffusionCapabilityDefaultEdit::CAPABILITY); + + return id(new PhabricatorRepository()) + ->setViewPolicy($view_policy) + ->setEditPolicy($edit_policy); + } + public function getConfiguration() { return array( self::CONFIG_AUX_PHID => true, diff --git a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php index 888a17b88c..54f0e59eab 100644 --- a/src/applications/repository/storage/PhabricatorRepositoryTransaction.php +++ b/src/applications/repository/storage/PhabricatorRepositoryTransaction.php @@ -3,6 +3,7 @@ final class PhabricatorRepositoryTransaction extends PhabricatorApplicationTransaction { + const TYPE_VCS = 'repo:vcs'; const TYPE_ACTIVATE = 'repo:activate'; const TYPE_NAME = 'repo:name'; const TYPE_DESCRIPTION = 'repo:description'; @@ -34,6 +35,48 @@ final class PhabricatorRepositoryTransaction return null; } + 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 'create'; + } + 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(); @@ -41,6 +84,10 @@ final class PhabricatorRepositoryTransaction $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(