diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -4470,6 +4470,7 @@ 'PhabricatorRepositoryIdentityTransaction' => 'applications/repository/storage/PhabricatorRepositoryIdentityTransaction.php', 'PhabricatorRepositoryIdentityTransactionQuery' => 'applications/repository/query/PhabricatorRepositoryIdentityTransactionQuery.php', 'PhabricatorRepositoryIdentityTransactionType' => 'applications/repository/xaction/PhabricatorRepositoryIdentityTransactionType.php', + 'PhabricatorRepositoryMaintenanceTransaction' => 'applications/repository/xaction/PhabricatorRepositoryMaintenanceTransaction.php', 'PhabricatorRepositoryManagementCacheWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementCacheWorkflow.php', 'PhabricatorRepositoryManagementClusterizeWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementClusterizeWorkflow.php', 'PhabricatorRepositoryManagementDiscoverWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementDiscoverWorkflow.php', @@ -4478,6 +4479,7 @@ 'PhabricatorRepositoryManagementListPathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListPathsWorkflow.php', 'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php', 'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php', + 'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMaintenanceWorkflow.php', 'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php', 'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkReachableWorkflow.php', 'PhabricatorRepositoryManagementMirrorWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php', @@ -10911,6 +10913,7 @@ 'PhabricatorRepositoryIdentityTransaction' => 'PhabricatorModularTransaction', 'PhabricatorRepositoryIdentityTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorRepositoryIdentityTransactionType' => 'PhabricatorModularTransactionType', + 'PhabricatorRepositoryMaintenanceTransaction' => 'PhabricatorRepositoryTransactionType', 'PhabricatorRepositoryManagementCacheWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementClusterizeWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementDiscoverWorkflow' => 'PhabricatorRepositoryManagementWorkflow', @@ -10919,6 +10922,7 @@ 'PhabricatorRepositoryManagementListPathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'PhabricatorRepositoryManagementWorkflow', + 'PhabricatorRepositoryManagementMaintenanceWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMirrorWorkflow' => 'PhabricatorRepositoryManagementWorkflow', diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php --- a/src/applications/diffusion/controller/DiffusionRepositoryController.php +++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php @@ -145,13 +145,26 @@ ->setRight(array($this->branchButton, $actions_button, $clone_button)) ->addClass('diffusion-action-bar'); + $status_view = null; + if ($repository->isReadOnly()) { + $status_view = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_WARNING) + ->setErrors( + array( + phutil_escape_html_newlines( + $repository->getReadOnlyMessageForDisplay()), + )); + } + $view = id(new PHUITwoColumnView()) ->setHeader($header) - ->setFooter(array( - $bar, - $description, - $content, - )); + ->setFooter( + array( + $status_view, + $bar, + $description, + $content, + )); if ($page_has_content) { $view->setTabs($tabs); @@ -327,6 +340,8 @@ if (!$repository->isTracked()) { $header->setStatus('fa-ban', 'dark', pht('Inactive')); + } else if ($repository->isReadOnly()) { + $header->setStatus('fa-wrench', 'indigo', pht('Under Maintenance')); } else if ($repository->isImporting()) { $ratio = $repository->loadImportProgress(); $percentage = sprintf('%.2f%%', 100 * $ratio); diff --git a/src/applications/diffusion/controller/DiffusionServeController.php b/src/applications/diffusion/controller/DiffusionServeController.php --- a/src/applications/diffusion/controller/DiffusionServeController.php +++ b/src/applications/diffusion/controller/DiffusionServeController.php @@ -302,6 +302,12 @@ } if ($is_push) { + if ($repository->isReadOnly()) { + return new PhabricatorVCSResponse( + 503, + $repository->getReadOnlyMessageForDisplay()); + } + $can_write = $repository->canServeProtocol($proto_https, true) || $repository->canServeProtocol($proto_http, true); diff --git a/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php b/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php --- a/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php +++ b/src/applications/diffusion/ssh/DiffusionSSHWorkflow.php @@ -255,6 +255,10 @@ 'user account.')); } + if ($repository->isReadOnly()) { + throw new Exception($repository->getReadOnlyMessageForDisplay()); + } + $protocol = PhabricatorRepositoryURI::BUILTIN_PROTOCOL_SSH; if ($repository->canServeProtocol($protocol, true)) { $can_push = PhabricatorPolicyFilter::hasCapability( diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php --- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php +++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php @@ -52,6 +52,13 @@ $repository = $this->getRepository(); $viewer = PhabricatorUser::getOmnipotentUser(); + if ($repository->isReadOnly()) { + $this->skipPull( + pht( + "Skipping pull on read-only repository.\n\n%s", + $repository->getReadOnlyMessageForDisplay())); + } + $is_hg = false; $is_git = false; $is_svn = false; diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementMaintenanceWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementMaintenanceWorkflow.php new file mode 100644 --- /dev/null +++ b/src/applications/repository/management/PhabricatorRepositoryManagementMaintenanceWorkflow.php @@ -0,0 +1,104 @@ +setName('maintenance') + ->setExamples( + "**maintenance** --start __message__ __repository__ ...\n". + "**maintenance** --stop __repository__") + ->setSynopsis( + pht('Set or clear read-only mode for repository maintenance.')) + ->setArguments( + array( + array( + 'name' => 'start', + 'param' => 'message', + 'help' => pht( + 'Put repositories into maintenance mode.'), + ), + array( + 'name' => 'stop', + 'help' => pht( + 'Take repositories out of maintenance mode, returning them '. + 'to normal serice.'), + ), + array( + 'name' => 'repositories', + 'wildcard' => true, + ), + )); + } + + public function execute(PhutilArgumentParser $args) { + $viewer = $this->getViewer(); + + $repositories = $this->loadRepositories($args, 'repositories'); + if (!$repositories) { + throw new PhutilArgumentUsageException( + pht('Specify one or more repositories to act on.')); + } + + $message = $args->getArg('start'); + $is_start = (bool)strlen($message); + $is_stop = $args->getArg('stop'); + + if (!$is_start && !$is_stop) { + throw new PhutilArgumentUsageException( + pht( + 'Use "--start " to put repositories into maintenance '. + 'mode, or "--stop" to take them out of maintenance mode.')); + } + + if ($is_start && $is_stop) { + throw new PhutilArgumentUsageException( + pht( + 'Specify either "--start" or "--stop", but not both.')); + } + + $content_source = $this->newContentSource(); + $diffusion_phid = id(new PhabricatorDiffusionApplication())->getPHID(); + + if ($is_start) { + $new_value = $message; + } else { + $new_value = null; + } + + foreach ($repositories as $repository) { + $xactions = array(); + + $xactions[] = $repository->getApplicationTransactionTemplate() + ->setTransactionType( + PhabricatorRepositoryMaintenanceTransaction::TRANSACTIONTYPE) + ->setNewValue($new_value); + + $repository->getApplicationTransactionEditor() + ->setActor($viewer) + ->setActingAsPHID($diffusion_phid) + ->setContentSource($content_source) + ->setContinueOnNoEffect(true) + ->setContinueOnMissingFields(true) + ->applyTransactions($repository, $xactions); + + if ($is_start) { + echo tsprintf( + "%s\n", + pht( + 'Put repository "%s" into maintenance mode.', + $repository->getDisplayName())); + } else { + echo tsprintf( + "%s\n", + pht( + 'Took repository "%s" out of maintenance mode.', + $repository->getDisplayName())); + } + } + + return 0; + } + +} diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php --- a/src/applications/repository/storage/PhabricatorRepository.php +++ b/src/applications/repository/storage/PhabricatorRepository.php @@ -1410,6 +1410,12 @@ } } + if ($write) { + if ($this->isReadOnly()) { + return false; + } + } + return false; } @@ -2266,6 +2272,35 @@ return $this->isGit(); } + public function isReadOnly() { + return (bool)$this->getDetail('read-only'); + } + + public function setReadOnly($read_only) { + return $this->setDetail('read-only', $read_only); + } + + public function getReadOnlyMessage() { + return $this->getDetail('read-only-message'); + } + + public function setReadOnlyMessage($message) { + return $this->setDetail('read-only-message', $message); + } + + public function getReadOnlyMessageForDisplay() { + $parts = array(); + $parts[] = pht( + 'This repository is currently in read-only maintenance mode.'); + + $message = $this->getReadOnlyMessage(); + if ($message !== null) { + $parts[] = $message; + } + + return implode("\n\n", $parts); + } + /* -( Repository URIs )---------------------------------------------------- */ diff --git a/src/applications/repository/xaction/PhabricatorRepositoryMaintenanceTransaction.php b/src/applications/repository/xaction/PhabricatorRepositoryMaintenanceTransaction.php new file mode 100644 --- /dev/null +++ b/src/applications/repository/xaction/PhabricatorRepositoryMaintenanceTransaction.php @@ -0,0 +1,43 @@ +getReadOnlyMessage(); + } + + public function applyInternalEffects($object, $value) { + if ($value === null) { + $object + ->setReadOnly(false) + ->setReadOnlyMessage(null); + } else { + $object + ->setReadOnly(true) + ->setReadOnlyMessage($value); + } + } + + public function getTitle() { + $old = $this->getOldValue(); + $new = $this->getNewValue(); + + if (strlen($old) && !strlen($new)) { + return pht( + '%s took this repository out of maintenance mode.', + $this->renderAuthor()); + } else if (!strlen($old) && strlen($new)) { + return pht( + '%s put this repository into maintenance mode.', + $this->renderAuthor()); + } else { + return pht( + '%s updated the maintenance message for this repository.', + $this->renderAuthor()); + } + } + +}