Page MenuHomePhabricator

D7391.id16657.diff
No OneTemporary

D7391.id16657.diff

Index: resources/sql/patches/20131024.diffusionhost.sql
===================================================================
--- /dev/null
+++ resources/sql/patches/20131024.diffusionhost.sql
@@ -0,0 +1,5 @@
+ALTER TABLE {$NAMESPACE}_repository.repository
+ADD COLUMN pushPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin;
+
+ALTER TABLE {$NAMESPACE}_repository.repository
+ADD COLUMN hostingPath VARCHAR(255) NULL DEFAULT NULL COLLATE utf8_bin;
Index: resources/sql/patches/20131024.vcspassword.sql
===================================================================
--- /dev/null
+++ resources/sql/patches/20131024.vcspassword.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_user.user
+ADD COLUMN vcsPassword VARCHAR(10) NULL DEFAULT NULL COLLATE utf8_bin;
Index: src/__phutil_library_map__.php
===================================================================
--- src/__phutil_library_map__.php
+++ src/__phutil_library_map__.php
@@ -18,6 +18,7 @@
'AphrontAjaxResponse' => 'aphront/response/AphrontAjaxResponse.php',
'AphrontApplicationConfiguration' => 'aphront/configuration/AphrontApplicationConfiguration.php',
'AphrontBarView' => 'view/widget/bars/AphrontBarView.php',
+ 'AphrontBasicAuthResponse' => 'aphront/response/AphrontBasicAuthResponse.php',
'AphrontCSRFException' => 'aphront/exception/AphrontCSRFException.php',
'AphrontCalendarEventView' => 'applications/calendar/view/AphrontCalendarEventView.php',
'AphrontCalendarMonthView' => 'applications/calendar/view/AphrontCalendarMonthView.php',
@@ -51,6 +52,7 @@
'AphrontFormToggleButtonsControl' => 'view/form/control/AphrontFormToggleButtonsControl.php',
'AphrontFormTokenizerControl' => 'view/form/control/AphrontFormTokenizerControl.php',
'AphrontFormView' => 'view/form/AphrontFormView.php',
+ 'AphrontGitResponse' => 'aphront/response/AphrontGitResponse.php',
'AphrontGlyphBarView' => 'view/widget/bars/AphrontGlyphBarView.php',
'AphrontHTMLResponse' => 'aphront/response/AphrontHTMLResponse.php',
'AphrontHTTPSink' => 'aphront/sink/AphrontHTTPSink.php',
@@ -475,6 +477,7 @@
'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionGitFileContentQuery.php',
'DiffusionGitRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionGitRawDiffQuery.php',
'DiffusionGitRequest' => 'applications/diffusion/request/DiffusionGitRequest.php',
+ 'DiffusionGitServeController' => 'applications/diffusion/controller/DiffusionGitServeController.php',
'DiffusionGitStableCommitNameQuery' => 'applications/diffusion/query/stablecommitname/DiffusionGitStableCommitNameQuery.php',
'DiffusionHistoryController' => 'applications/diffusion/controller/DiffusionHistoryController.php',
'DiffusionHistoryTableView' => 'applications/diffusion/view/DiffusionHistoryTableView.php',
@@ -508,6 +511,7 @@
'DiffusionRepositoryEditBasicController' => 'applications/diffusion/controller/DiffusionRepositoryEditBasicController.php',
'DiffusionRepositoryEditController' => 'applications/diffusion/controller/DiffusionRepositoryEditController.php',
'DiffusionRepositoryEditEncodingController' => 'applications/diffusion/controller/DiffusionRepositoryEditEncodingController.php',
+ 'DiffusionRepositoryEditHostingController' => 'applications/diffusion/controller/DiffusionRepositoryEditHostingController.php',
'DiffusionRepositoryEditPolicyController' => 'applications/diffusion/controller/DiffusionRepositoryEditPolicyController.php',
'DiffusionRepositoryListController' => 'applications/diffusion/controller/DiffusionRepositoryListController.php',
'DiffusionRepositoryPath' => 'applications/diffusion/data/DiffusionRepositoryPath.php',
@@ -619,6 +623,7 @@
'FileCreateMailReceiver' => 'applications/files/mail/FileCreateMailReceiver.php',
'FileMailReceiver' => 'applications/files/mail/FileMailReceiver.php',
'FileReplyHandler' => 'applications/files/mail/FileReplyHandler.php',
+ 'GitHTTPServer' => 'infrastructure/git/GitHTTPServer.php',
'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php',
'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php',
'HarbormasterBuildItem' => 'applications/harbormaster/storage/build/HarbormasterBuildItem.php',
@@ -1528,6 +1533,7 @@
'PhabricatorPolicyCapability' => 'applications/policy/capability/PhabricatorPolicyCapability.php',
'PhabricatorPolicyCapabilityCanEdit' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanEdit.php',
'PhabricatorPolicyCapabilityCanJoin' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanJoin.php',
+ 'PhabricatorPolicyCapabilityCanPush' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanPush.php',
'PhabricatorPolicyCapabilityCanView' => 'applications/policy/capability/PhabricatorPolicyCapabilityCanView.php',
'PhabricatorPolicyConfigOptions' => 'applications/policy/config/PhabricatorPolicyConfigOptions.php',
'PhabricatorPolicyConstants' => 'applications/policy/constants/PhabricatorPolicyConstants.php',
@@ -1699,6 +1705,7 @@
'PhabricatorSettingsPanelPassword' => 'applications/settings/panel/PhabricatorSettingsPanelPassword.php',
'PhabricatorSettingsPanelSSHKeys' => 'applications/settings/panel/PhabricatorSettingsPanelSSHKeys.php',
'PhabricatorSettingsPanelSearchPreferences' => 'applications/settings/panel/PhabricatorSettingsPanelSearchPreferences.php',
+ 'PhabricatorSettingsPanelVCSPassword' => 'applications/settings/panel/PhabricatorSettingsPanelVCSPassword.php',
'PhabricatorSetupCheck' => 'applications/config/check/PhabricatorSetupCheck.php',
'PhabricatorSetupCheckAPC' => 'applications/config/check/PhabricatorSetupCheckAPC.php',
'PhabricatorSetupCheckAuth' => 'applications/config/check/PhabricatorSetupCheckAuth.php',
@@ -2201,6 +2208,7 @@
'AphrontAbstractAttachedFileView' => 'AphrontView',
'AphrontAjaxResponse' => 'AphrontResponse',
'AphrontBarView' => 'AphrontView',
+ 'AphrontBasicAuthResponse' => 'AphrontResponse',
'AphrontCSRFException' => 'AphrontException',
'AphrontCalendarEventView' => 'AphrontView',
'AphrontCalendarMonthView' => 'AphrontView',
@@ -2234,6 +2242,7 @@
'AphrontFormToggleButtonsControl' => 'AphrontFormControl',
'AphrontFormTokenizerControl' => 'AphrontFormControl',
'AphrontFormView' => 'AphrontView',
+ 'AphrontGitResponse' => 'AphrontResponse',
'AphrontGlyphBarView' => 'AphrontBarView',
'AphrontHTMLResponse' => 'AphrontResponse',
'AphrontHTTPSinkTestCase' => 'PhabricatorTestCase',
@@ -2646,6 +2655,7 @@
'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery',
'DiffusionGitRawDiffQuery' => 'DiffusionRawDiffQuery',
'DiffusionGitRequest' => 'DiffusionRequest',
+ 'DiffusionGitServeController' => 'DiffusionController',
'DiffusionGitStableCommitNameQuery' => 'DiffusionStableCommitNameQuery',
'DiffusionHistoryController' => 'DiffusionController',
'DiffusionHistoryTableView' => 'DiffusionView',
@@ -2673,6 +2683,7 @@
'DiffusionRepositoryEditBasicController' => 'DiffusionController',
'DiffusionRepositoryEditController' => 'DiffusionController',
'DiffusionRepositoryEditEncodingController' => 'DiffusionController',
+ 'DiffusionRepositoryEditHostingController' => 'DiffusionController',
'DiffusionRepositoryEditPolicyController' => 'DiffusionController',
'DiffusionRepositoryListController' =>
array(
@@ -3817,6 +3828,7 @@
'PhabricatorPolicyCapability' => 'Phobject',
'PhabricatorPolicyCapabilityCanEdit' => 'PhabricatorPolicyCapability',
'PhabricatorPolicyCapabilityCanJoin' => 'PhabricatorPolicyCapability',
+ 'PhabricatorPolicyCapabilityCanPush' => 'PhabricatorPolicyCapability',
'PhabricatorPolicyCapabilityCanView' => 'PhabricatorPolicyCapability',
'PhabricatorPolicyConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPolicyController' => 'PhabricatorController',
@@ -3997,6 +4009,7 @@
'PhabricatorSettingsPanelPassword' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsPanelSSHKeys' => 'PhabricatorSettingsPanel',
'PhabricatorSettingsPanelSearchPreferences' => 'PhabricatorSettingsPanel',
+ 'PhabricatorSettingsPanelVCSPassword' => 'PhabricatorSettingsPanel',
'PhabricatorSetupCheckAPC' => 'PhabricatorSetupCheck',
'PhabricatorSetupCheckAuth' => 'PhabricatorSetupCheck',
'PhabricatorSetupCheckBaseURI' => 'PhabricatorSetupCheck',
Index: src/aphront/response/AphrontBasicAuthResponse.php
===================================================================
--- /dev/null
+++ src/aphront/response/AphrontBasicAuthResponse.php
@@ -0,0 +1,23 @@
+<?php
+
+final class AphrontBasicAuthResponse extends AphrontResponse {
+
+ public function buildResponseString() {
+ return "";
+ }
+
+ public function getHeaders() {
+ return array(
+ array('WWW-Authenticate', 'Basic realm="Phabricator Git Repository"'));
+ }
+
+ public function getCacheHeaders() {
+ return array();
+ }
+
+ public function getHTTPResponseCode() {
+ return 401;
+ }
+
+}
+
Index: src/aphront/response/AphrontGitResponse.php
===================================================================
--- /dev/null
+++ src/aphront/response/AphrontGitResponse.php
@@ -0,0 +1,47 @@
+<?php
+
+final class AphrontGitResponse extends AphrontResponse {
+
+ private $httpCode;
+ private $headers;
+ private $response;
+
+ public function setGitData($data) {
+ // We have to parse lines up until the HTTP double newline. First
+ // split on \r\n\r\n to get the headers and content as separate entries.
+ $headeridx = strpos($data, "\r\n\r\n");
+ $this->response = substr($data, $headeridx + 4);
+
+ // Now parse the headers, the CGI command can potentially output "Status:"
+ // to change the HTTP status code.
+ $lines = explode("\r\n", substr($data, 0, $headeridx));
+
+ $this->headers = array();
+ if (substr($lines[0], 0, 7) === "Status:") {
+ $this->httpCode = substr($lines[0], 8, 3);
+ } else {
+ $this->httpCode = 200;
+ }
+ for ($i = 1; $i < count($lines); $i++) {
+ $this->headers[] = explode(": ", $lines[$i]);
+ }
+ }
+
+ public function buildResponseString() {
+ return $this->response;
+ }
+
+ public function getHeaders() {
+ return $this->headers;
+ }
+
+ public function getCacheHeaders() {
+ return array();
+ }
+
+ public function getHTTPResponseCode() {
+ return $this->httpCode;
+ }
+
+}
+
Index: src/applications/diffusion/application/PhabricatorApplicationDiffusion.php
===================================================================
--- src/applications/diffusion/application/PhabricatorApplicationDiffusion.php
+++ src/applications/diffusion/application/PhabricatorApplicationDiffusion.php
@@ -44,6 +44,7 @@
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'DiffusionRepositoryListController',
'create/' => 'DiffusionRepositoryCreateController',
+ 'git/(?P<callsign>[A-Z]+)(?<url>.*)' => 'DiffusionGitServeController',
'(?P<callsign>[A-Z]+)/' => array(
'' => 'DiffusionRepositoryController',
@@ -69,6 +70,7 @@
'encoding/' => 'DiffusionRepositoryEditEncodingController',
'activate/' => 'DiffusionRepositoryEditActivateController',
'policy/' => 'DiffusionRepositoryEditPolicyController',
+ 'hosting/' => 'DiffusionRepositoryEditHostingController',
),
),
'inline/' => array(
Index: src/applications/diffusion/controller/DiffusionGitServeController.php
===================================================================
--- /dev/null
+++ src/applications/diffusion/controller/DiffusionGitServeController.php
@@ -0,0 +1,113 @@
+<?php
+
+final class DiffusionGitServeController extends DiffusionController {
+
+ private $callsign;
+ private $url;
+
+ public function willProcessRequest(array $data) {
+ $this->callsign = idx($data, 'callsign');
+ $this->url = idx($data, 'url');
+ }
+
+ public function shouldRequireLogin() {
+ return false;
+ }
+
+ public function shouldRequireAdmin() {
+ return false;
+ }
+
+ public function shouldRequireEnabledUser() {
+ return false;
+ }
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ private function isReadOnlyRequest() {
+ return !$this->isReadWriteRequest();
+ }
+
+ private function isWriteRequest() {
+ return isset($_GET['service']) && $_GET['service'] == 'git-receive-pack';
+ }
+
+ public function processRequest() {
+ // Retrieve the associated hosted repository.
+ $repository = id(new PhabricatorRepository)
+ ->loadOneWhere("callsign = %s", $this->callsign);
+ if ($repository === null) {
+ throw new Exception(
+ "Repository not found (callsign: ".$this->callsign.")!");
+ }
+ if (!$repository->isHosted()) {
+ throw new Exception("This is not a hosted repository.");
+ }
+ $path = $repository->getHostingPath();
+
+ // Start with not authorized state.
+ $permitted = false;
+
+ // If this is a read-only operation, and the view policy of the
+ // repository is PhabricatorPolicies::POLICY_PUBLIC, then we don't
+ // need to prompt for authorization.
+ if (!$this->isWriteRequest() &&
+ $repository->getViewPolicy() == PhabricatorPolicies::POLICY_PUBLIC) {
+ $permitted = true;
+ }
+
+ // If we're not permitted at this point, then we do need basic HTTP
+ // authorization to be completed by the user.
+ if (!$permitted) {
+ if (!isset($_SERVER['PHP_AUTH_USER']) ||
+ !isset($_SERVER['PHP_AUTH_PW'])) {
+ return new AphrontBasicAuthResponse();
+ }
+
+ // Validate who the user is based on Git settings. We get provide users
+ // with a fake password they can send over basic HTTP; that way, even
+ // people who login with other providers such as Google.
+ $user = id(new PhabricatorUser())
+ ->loadOneWhere(
+ "userName = %s AND vcsPassword = %s",
+ $_SERVER['PHP_AUTH_USER'],
+ $_SERVER['PHP_AUTH_PW']);
+ if ($user === null) {
+ return new AphrontBasicAuthResponse();
+ }
+
+ // Check policies. viewPolicy allows GET, editPolicy allows POST / PUT.
+ if (!$this->isWriteRequest()) {
+ $permitted = PhabricatorPolicyFilter::hasCapability(
+ $user,
+ $repository,
+ PhabricatorPolicyCapability::CAN_VIEW);
+ }
+ if ($this->isWriteRequest()) {
+ $permitted = PhabricatorPolicyFilter::hasCapability(
+ $user,
+ $repository,
+ PhabricatorPolicyCapability::CAN_PUSH);
+ }
+ }
+
+ // If not permitted, throw 403 Forbidden.
+ if (!$permitted) {
+ return new Aphront403Response();
+ }
+
+ // TODO: Switch type of server based on
+ // $repository->getVersionControlSystem()!
+ $server = new GitHTTPServer();
+ $server->setProjectRoot($path);
+ $data = $server->acceptRequest($this->url);
+
+ $response = new AphrontGitResponse();
+ $response->setGitData($data);
+ return $response;
+ }
+
+}
+
Index: src/applications/diffusion/controller/DiffusionRepositoryEditController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionRepositoryEditController.php
+++ src/applications/diffusion/controller/DiffusionRepositoryEditController.php
@@ -97,6 +97,14 @@
->setWorkflow(!$can_edit);
$view->addAction($edit);
+ $edit = id(new PhabricatorActionView())
+ ->setIcon('world')
+ ->setName(pht('Edit Hosting Information'))
+ ->setHref($this->getRepositoryControllerURI($repository, 'edit/hosting/'))
+ ->setDisabled(!$can_edit)
+ ->setWorkflow(!$can_edit);
+ $view->addAction($edit);
+
$activate = id(new PhabricatorActionView())
->setHref(
$this->getRepositoryControllerURI($repository, 'edit/activate/'))
@@ -137,6 +145,11 @@
$view->addProperty(pht('Type'), $type);
$view->addProperty(pht('Callsign'), $repository->getCallsign());
+ $view->addProperty(
+ pht('Hosted'),
+ $repository->isHosted()
+ ? pht('Yes, available at %s', $repository->getHostingURI())
+ : pht('No'));
$description = $repository->getDetail('description');
$view->addSectionHeader(pht('Description'));
@@ -245,6 +258,11 @@
pht('Editable By'),
$descriptions[PhabricatorPolicyCapability::CAN_EDIT]);
+ if ($repository->isHosted()) {
+ $view->addProperty(
+ pht('Pushable By'),
+ $descriptions[PhabricatorPolicyCapability::CAN_PUSH]);
+ }
return $view;
}
Index: src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php
===================================================================
--- /dev/null
+++ src/applications/diffusion/controller/DiffusionRepositoryEditHostingController.php
@@ -0,0 +1,129 @@
+<?php
+
+final class DiffusionRepositoryEditHostingController
+ extends DiffusionController {
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $user = $request->getUser();
+ $drequest = $this->diffusionRequest;
+ $repository = $drequest->getRepository();
+
+ $repository = id(new PhabricatorRepositoryQuery())
+ ->setViewer($user)
+ ->requireCapabilities(
+ array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ ))
+ ->withIDs(array($repository->getID()))
+ ->executeOne();
+
+ if (!$repository) {
+ return new Aphront404Response();
+ }
+
+ $edit_uri = $this->getRepositoryControllerURI($repository, 'edit/');
+
+ $v_hosting = $repository->getHostingPath();
+ $e_hosting = null;
+ $errors = array();
+
+ if ($request->isFormPost()) {
+ $v_hosting = $request->getStr('hosting');
+
+ if (empty($v_hosting)) {
+ $v_hosting = null;
+ }
+
+ if ($v_hosting !== null) {
+ if (!is_dir($v_hosting)) {
+ $errors[] = pht(
+ 'The directory does not exist. You must create the directory '.
+ 'on the server and initialise the repository.');
+ }
+ }
+
+ if (!$errors) {
+ $xactions = array();
+ $template = id(new PhabricatorRepositoryTransaction());
+
+ $type_hosting = PhabricatorRepositoryTransaction::TYPE_HOSTING;
+
+ $xactions[] = id(clone $template)
+ ->setTransactionType($type_hosting)
+ ->setNewValue($v_hosting);
+
+ try {
+ id(new PhabricatorRepositoryEditor())
+ ->setContinueOnNoEffect(true)
+ ->setContentSourceFromRequest($request)
+ ->setActor($user)
+ ->applyTransactions($repository, $xactions);
+
+ return id(new AphrontRedirectResponse())->setURI($edit_uri);
+ } catch (Exception $ex) {
+ $errors[] = $ex->getMessage();
+ }
+ }
+ }
+
+ $crumbs = $this->buildCrumbs();
+ $crumbs->addCrumb(
+ id(new PhabricatorCrumbView())
+ ->setName(pht('Edit Hosting')));
+
+ $title = pht('Edit %s', $repository->getName());
+
+ $error_view = null;
+ if ($errors) {
+ $error_view = id(new AphrontErrorView())
+ ->setTitle(pht('Form Errors'))
+ ->setErrors($errors);
+ }
+
+ $form = id(new AphrontFormView())
+ ->setUser($user)
+ ->appendRemarkupInstructions($this->getEncodingInstructions(
+ $repository->getName()))
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setName('hosting')
+ ->setLabel(pht('Local Hosting Path'))
+ ->setValue($v_hosting)
+ ->setError($e_hosting))
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue(pht('Save Hosting'))
+ ->addCancelButton($edit_uri));
+
+ $object_box = id(new PHUIObjectBoxView())
+ ->setHeaderText($title)
+ ->setForm($form)
+ ->setFormError($error_view);
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $object_box,
+ ),
+ array(
+ 'title' => $title,
+ 'device' => true,
+ ));
+ }
+
+ private function getEncodingInstructions($name) {
+ return pht(<<<EOT
+If you leave this field blank, hosting is disabled for this repository.
+
+If you want Phabricator to host this repository, supply a local directory that
+contains an existing repository. Generally you'll be providing a directory
+that looks like `/var/phabricator/repo/$name`.
+
+**In future this will be much nicer to set up.**
+EOT
+ );
+ }
+
+}
Index: src/applications/diffusion/controller/DiffusionRepositoryEditPolicyController.php
===================================================================
--- src/applications/diffusion/controller/DiffusionRepositoryEditPolicyController.php
+++ src/applications/diffusion/controller/DiffusionRepositoryEditPolicyController.php
@@ -14,7 +14,7 @@
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
- PhabricatorPolicyCapability::CAN_EDIT,
+ PhabricatorPolicyCapability::CAN_EDIT
))
->withIDs(array($repository->getID()))
->executeOne();
@@ -27,16 +27,21 @@
$v_view = $repository->getViewPolicy();
$v_edit = $repository->getEditPolicy();
+ $v_push = $repository->getPushPolicy();
if ($request->isFormPost()) {
$v_view = $request->getStr('viewPolicy');
$v_edit = $request->getStr('editPolicy');
+ if ($repository->isHosted()) {
+ $v_push = $request->getStr('pushPolicy');
+ }
$xactions = array();
$template = id(new PhabricatorRepositoryTransaction());
$type_view = PhabricatorTransactions::TYPE_VIEW_POLICY;
$type_edit = PhabricatorTransactions::TYPE_EDIT_POLICY;
+ $type_push = PhabricatorTransactions::TYPE_PUSH_POLICY;
$xactions[] = id(clone $template)
->setTransactionType($type_view)
@@ -46,6 +51,12 @@
->setTransactionType($type_edit)
->setNewValue($v_edit);
+ if ($repository->isHosted()) {
+ $xactions[] = id(clone $template)
+ ->setTransactionType($type_push)
+ ->setNewValue($v_push);
+ }
+
id(new PhabricatorRepositoryEditor())
->setContinueOnNoEffect(true)
->setContentSourceFromRequest($request)
@@ -84,7 +95,18 @@
->setCapability(PhabricatorPolicyCapability::CAN_EDIT)
->setPolicyObject($repository)
->setPolicies($policies)
- ->setName('editPolicy'))
+ ->setName('editPolicy'));
+ if ($repository->isHosted()) {
+ $form
+ ->appendChild(
+ id(new AphrontFormPolicyControl())
+ ->setUser($viewer)
+ ->setCapability(PhabricatorPolicyCapability::CAN_PUSH)
+ ->setPolicyObject($repository)
+ ->setPolicies($policies)
+ ->setName('pushPolicy'));
+ }
+ $form
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Save Policies'))
Index: src/applications/people/storage/PhabricatorUser.php
===================================================================
--- src/applications/people/storage/PhabricatorUser.php
+++ src/applications/people/storage/PhabricatorUser.php
@@ -25,6 +25,7 @@
protected $consoleTab = '';
protected $conduitCertificate;
+ protected $vcsPassword;
protected $isSystemAgent = 0;
protected $isAdmin = 0;
@@ -111,6 +112,9 @@
if (!$this->getConduitCertificate()) {
$this->setConduitCertificate($this->generateConduitCertificate());
}
+ if (!$this->getVCSPassword()) {
+ $this->setVCSPassword($this->generateVCSPassword());
+ }
$result = parent::save();
if ($this->profile) {
@@ -129,6 +133,10 @@
return Filesystem::readRandomCharacters(255);
}
+ private function generateVCSPassword() {
+ return Filesystem::readRandomCharacters(10);
+ }
+
public function comparePassword(PhutilOpaqueEnvelope $envelope) {
if (!strlen($envelope->openEnvelope())) {
return false;
Index: src/applications/policy/capability/PhabricatorPolicyCapability.php
===================================================================
--- src/applications/policy/capability/PhabricatorPolicyCapability.php
+++ src/applications/policy/capability/PhabricatorPolicyCapability.php
@@ -5,6 +5,7 @@
const CAN_VIEW = 'view';
const CAN_EDIT = 'edit';
const CAN_JOIN = 'join';
+ const CAN_PUSH = 'push';
/**
* Get the unique key identifying this capability. This key must be globally
Index: src/applications/policy/capability/PhabricatorPolicyCapabilityCanPush.php
===================================================================
--- /dev/null
+++ src/applications/policy/capability/PhabricatorPolicyCapabilityCanPush.php
@@ -0,0 +1,18 @@
+<?php
+
+final class PhabricatorPolicyCapabilityCanPush
+ extends PhabricatorPolicyCapability {
+
+ public function getCapabilityKey() {
+ return self::CAN_PUSH;
+ }
+
+ public function getCapabilityName() {
+ return pht('Can Push');
+ }
+
+ public function describeCapabilityRejection() {
+ return pht('You do not have permission to push to this object.');
+ }
+
+}
Index: src/applications/repository/controller/PhabricatorRepositoryEditController.php
===================================================================
--- src/applications/repository/controller/PhabricatorRepositoryEditController.php
+++ src/applications/repository/controller/PhabricatorRepositoryEditController.php
@@ -29,6 +29,7 @@
$views = array(
'basic' => 'Basics',
'tracking' => 'Tracking',
+ 'hosting' => 'Hosting'
);
$this->repository = $repository;
@@ -54,6 +55,8 @@
return $this->processBasicRequest();
case 'tracking':
return $this->processTrackingRequest();
+ case 'hosting':
+ return $this->processHostingRequest();
default:
throw new Exception("Unknown view.");
}
@@ -693,4 +696,242 @@
));
}
+ private function processHostingRequest() {
+ $request = $this->getRequest();
+ $user = $request->getUser();
+ $repository = $this->repository;
+ $repository_id = $repository->getID();
+
+ $errors = array();
+
+ if ($request->isFormPost()) {
+ $hosting = ($request->getStr('hosting') == 'enabled' ? true : false);
+
+ if ($hosting) {
+ // Before we go and enable any hosting, attempt to set up the directory
+ // that will hold the repository first.
+ $path = $request->getStr('path');
+ if (empty($path)) {
+ $errors[] =
+ 'The path must be set before when repository '.
+ 'hosting is enabled.';
+ }
+
+ if (count($errors) === 0) {
+ // Check if the directory exists.
+ if (is_dir($path)) {
+ // The directory already exists. This could be an existing
+ // repository, or a directory that already has contents.
+
+ // TODO: Check if the directory contains a repository and report
+ // and error if it doesn't.
+ } else {
+ // The directory does not exist. Try to create it and report
+ // on failure.
+ if (!mkdir($path, 0700)) {
+ $errors[] =
+ 'Unable to create the directory to host the repository.';
+ } else {
+ // The directory was created successfully. Now perform the
+ // source control initialization.
+ switch ($repository->getVersionControlSystem()) {
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
+ // TODO: Should this be exec_manual to show a normal error
+ // when `git init` fails?
+ list($out, $err) = execx("%C init --bare %s", "git", $path);
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
+ $errors[] =
+ 'Subversion hosting is not supported.';
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
+ $errors[] =
+ 'Mercurial hosting is not supported.';
+ break;
+ default:
+ throw new Exception("Unsupported VCS!");
+ }
+
+ }
+ }
+ }
+ }
+
+ // Now modify the hosted repository information.
+ if (count($errors) === 0 || !$hosting) {
+ if ($hosting && !$repository->isHosted()) {
+ $repository->setHostingPath($request->getStr('path'));
+ } else if (!$hosting && $repository->isHosted()) {
+ $repository->setHostingPath(null);
+ } else if ($repository->isHosted()) {
+ $repository->setHostingPath($request->getStr('path'));
+ $repository->setPushPolicy($request->getStr('can_push'));
+ }
+ $repository->save();
+ }
+
+ if (!$errors) {
+ $repository->save();
+ return id(new AphrontRedirectResponse())
+ ->setURI('/repository/edit/'.$repository_id.'/hosting/?saved=true');
+ }
+ }
+
+ $error_view = null;
+ if ($errors) {
+ $error_view = new AphrontErrorView();
+ $error_view->setErrors($errors);
+ $error_view->setTitle('Form Errors');
+ } else if ($request->getStr('saved')) {
+ $error_view = new AphrontErrorView();
+ $error_view->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
+ $error_view->setTitle('Changes Saved');
+ $error_view->appendChild('Hosting changes were saved.');
+ } else if (!$repository->isHosted()) {
+ $error_view = new AphrontErrorView();
+ $error_view->setSeverity(AphrontErrorView::SEVERITY_WARNING);
+ $error_view->setTitle('Repository Not Hosted');
+ $error_view->appendChild(
+ 'Hosting is currently "Disabled" for this repository, so it will '.
+ 'not be served by Phabricator. You can enable it below.');
+ }
+
+ $doc_href = PhabricatorEnv::getDoclink('article/Diffusion_User_Guide.html');
+ $user_guide_link = phutil_tag(
+ 'a',
+ array(
+ 'href' => $doc_href,
+ ),
+ 'Diffusion User Guide');
+
+ $form = new AphrontFormView();
+ $form
+ ->setUser($user)
+ ->setAction('/repository/edit/'.$repository->getID().'/hosting/')
+ ->appendChild(hsprintf(
+ '<p class="aphront-form-instructions">Phabricator can host '.
+ 'repositories. More information is available in the '.
+ '<strong>%s</strong>.</p>',
+ $user_guide_link));
+
+ $form
+ ->appendChild(
+ id(new AphrontFormInsetView())
+ ->setTitle('Basics')
+ ->appendChild(id(new AphrontFormStaticControl())
+ ->setLabel('Repository Name')
+ ->setValue($repository->getName()))
+ ->appendChild(id(new AphrontFormSelectControl())
+ ->setName('hosting')
+ ->setLabel('Hosting')
+ ->setOptions(array(
+ 'disabled' => 'Disabled',
+ 'enabled' => 'Enabled',
+ ))
+ ->setValue(
+ $repository->isHosted()
+ ? 'enabled'
+ : 'disabled')));
+
+ $inset = new AphrontFormInsetView();
+ $inset->setTitle('Local Storage');
+
+ $init_command = "unknown";
+ switch ($repository->getVersionControlSystem()) {
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
+ $init_command = "git init --bare";
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_HG:
+ $init_command = "hg init";
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
+ $init_command = "svn init";
+ break;
+ }
+
+ $inset->appendChild(hsprintf(
+ '<p class="aphront-form-instructions">Select a path on the local disk '.
+ 'where the repository should be stored. If this path does not exist, '.
+ 'or the directory is empty and not already initialized, Phabricator '.
+ 'will run <tt>%s</tt> for you.</p>',
+ $init_command));
+ $inset
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setName('path')
+ ->setLabel('Local Path')
+ ->setID('path')
+ ->setValue($repository->getHostingPath()));
+
+ $form->appendChild($inset);
+
+ $inset = new AphrontFormInsetView();
+ $inset->setTitle('Policies');
+ $inset->appendChild(hsprintf(
+ '<p class="aphront-form-instructions">Specify which Phabricator '.
+ 'users can push new changes to the repository.</p>'));
+
+ $policies = id(new PhabricatorPolicyQuery())
+ ->setViewer($user)
+ ->setObject($repository)
+ ->execute();
+
+ $inset->appendChild(
+ id(new AphrontFormPolicyControl())
+ ->setUser($user)
+ ->setName('can_push')
+ ->setPolicyObject($repository)
+ ->setPolicies($policies)
+ ->setCapability(PhabricatorPolicyCapability::CAN_PUSH));
+
+ $form->appendChild($inset);
+
+ $inset = new AphrontFormInsetView();
+ $inset->setTitle('Repository URIs');
+ $inset->appendChild(hsprintf(
+ '<p class="aphront-form-instructions">The following URIs specify where '.
+ 'your users can clone or checkout the repository from.</p>'));
+
+ switch ($repository->getVersionControlSystem()) {
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
+ $baseURI = trim(
+ PhabricatorEnv::getEnvConfig('phabricator.base-uri'),
+ '/');
+ $inset
+ ->appendChild(id(new AphrontFormStaticControl())
+ ->setLabel('Read URI')
+ ->setValue($baseURI."/diffusion/git/".$repository->getCallsign()));
+ $inset
+ ->appendChild(id(new AphrontFormStaticControl())
+ ->setLabel('Write URI')
+ ->setValue($baseURI."/diffusion/git/".$repository->getCallsign()));
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_HG:
+ break;
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
+ break;
+ }
+
+ $form->appendChild($inset);
+
+ $form
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue('Save Configuration'));
+
+ $form_box = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Edit Repository Hosting'))
+ ->setFormError($error_view)
+ ->setForm($form);
+
+ $nav = $this->sideNav;
+ $nav->appendChild($form_box);
+
+ return $this->buildApplicationPage(
+ $nav,
+ array(
+ 'title' => pht('Edit Repository Hosting'),
+ ));
+ }
+
}
Index: src/applications/repository/editor/PhabricatorRepositoryEditor.php
===================================================================
--- src/applications/repository/editor/PhabricatorRepositoryEditor.php
+++ src/applications/repository/editor/PhabricatorRepositoryEditor.php
@@ -10,8 +10,10 @@
$types[] = PhabricatorRepositoryTransaction::TYPE_NAME;
$types[] = PhabricatorRepositoryTransaction::TYPE_DESCRIPTION;
$types[] = PhabricatorRepositoryTransaction::TYPE_ENCODING;
+ $types[] = PhabricatorRepositoryTransaction::TYPE_HOSTING;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
+ $types[] = PhabricatorTransactions::TYPE_PUSH_POLICY;
return $types;
}
@@ -29,6 +31,8 @@
return $object->getDetail('description');
case PhabricatorRepositoryTransaction::TYPE_ENCODING:
return $object->getDetail('encoding');
+ case PhabricatorRepositoryTransaction::TYPE_HOSTING:
+ return $object->getHostingPath();
}
}
@@ -41,6 +45,7 @@
case PhabricatorRepositoryTransaction::TYPE_NAME:
case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION:
case PhabricatorRepositoryTransaction::TYPE_ENCODING:
+ case PhabricatorRepositoryTransaction::TYPE_HOSTING:
return $xaction->getNewValue();
}
}
@@ -59,6 +64,9 @@
case PhabricatorRepositoryTransaction::TYPE_DESCRIPTION:
$object->setDetail('description', $xaction->getNewValue());
break;
+ case PhabricatorRepositoryTransaction::TYPE_HOSTING:
+ $object->setHostingPath($xaction->getNewValue());
+ break;
case PhabricatorRepositoryTransaction::TYPE_ENCODING:
// Make sure the encoding is valid by converting to UTF-8. This tests
// that the user has mbstring installed, and also that they didn't type
Index: src/applications/repository/storage/PhabricatorRepository.php
===================================================================
--- src/applications/repository/storage/PhabricatorRepository.php
+++ src/applications/repository/storage/PhabricatorRepository.php
@@ -30,9 +30,11 @@
protected $uuid;
protected $viewPolicy = PhabricatorPolicies::POLICY_USER;
protected $editPolicy = PhabricatorPolicies::POLICY_ADMIN;
+ protected $pushPolicy = PhabricatorPolicies::POLICY_USER;
protected $versionControlSystem;
protected $details = array();
+ protected $hostingPath;
private $sshKeyfile;
@@ -356,6 +358,20 @@
return $this->getDetail('tracking-enabled', false);
}
+ public function isHosted() {
+ return $this->getHostingPath() !== null;
+ }
+
+ public function getHostingURI() {
+ $baseURI = trim(PhabricatorEnv::getEnvConfig('phabricator.base-uri'), '/');
+ switch ($this->getVersionControlSystem()) {
+ case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
+ return $baseURI."/diffusion/git/".$this->getCallsign();
+ default:
+ throw new Exception("Unrecognized version control system.");
+ }
+ }
+
public function getDefaultBranch() {
$default = $this->getDetail('default-branch');
if (strlen($default)) {
@@ -471,6 +487,10 @@
* @task uri
*/
public function getPublicRemoteURI() {
+ if ($this->isHosted()) {
+ return $this->getHostingURI();
+ }
+
$uri = $this->getRemoteURIObject();
// Make sure we don't leak anything if this repo is using HTTP Basic Auth
@@ -684,6 +704,7 @@
return array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
+ PhabricatorPolicyCapability::CAN_PUSH,
);
}
@@ -693,6 +714,8 @@
return $this->getViewPolicy();
case PhabricatorPolicyCapability::CAN_EDIT:
return $this->getEditPolicy();
+ case PhabricatorPolicyCapability::CAN_PUSH:
+ return $this->getPushPolicy();
}
}
Index: src/applications/repository/storage/PhabricatorRepositoryTransaction.php
===================================================================
--- src/applications/repository/storage/PhabricatorRepositoryTransaction.php
+++ src/applications/repository/storage/PhabricatorRepositoryTransaction.php
@@ -7,6 +7,7 @@
const TYPE_NAME = 'repo:name';
const TYPE_DESCRIPTION = 'repo:description';
const TYPE_ENCODING = 'repo:encoding';
+ const TYPE_HOSTING = 'repo:hosting';
public function getApplicationName() {
return 'repository';
@@ -47,6 +48,10 @@
return pht(
'%s updated the description of this repository.',
$this->renderHandleLink($author_phid));
+ case self::TYPE_HOSTING:
+ return pht(
+ '%s updated the hosting configuration of this repository.',
+ $this->renderHandleLink($author_phid));
case self::TYPE_ENCODING:
if (strlen($old) && !strlen($new)) {
return pht(
Index: src/applications/settings/panel/PhabricatorSettingsPanelVCSPassword.php
===================================================================
--- /dev/null
+++ src/applications/settings/panel/PhabricatorSettingsPanelVCSPassword.php
@@ -0,0 +1,102 @@
+<?php
+
+final class PhabricatorSettingsPanelVCSPassword
+ extends PhabricatorSettingsPanel {
+
+ public function getPanelKey() {
+ return 'vcspassword';
+ }
+
+ public function getPanelName() {
+ return pht('Version Control');
+ }
+
+ public function getPanelGroup() {
+ return pht('Authentication');
+ }
+
+ public function processRequest(AphrontRequest $request) {
+ $user = $request->getUser();
+
+ if ($request->isFormPost()) {
+ if (!$request->isDialogFormPost()) {
+ $dialog = new AphrontDialogView();
+ $dialog->setUser($user);
+ $dialog->setTitle(pht('Really regenerate password?'));
+ $dialog->setSubmitURI($this->getPanelURI());
+ $dialog->addSubmitButton(pht('Regenerate'));
+ $dialog->addCancelbutton($this->getPanelURI());
+ $dialog->appendChild(phutil_tag('p', array(), pht(
+ 'Really destroy the old password? Any existing '.
+ 'version control configurations will need to be '.
+ 'updated.')));
+
+ return id(new AphrontDialogResponse())
+ ->setDialog($dialog);
+ }
+
+ // This implicitly regenerates the version control password.
+ $user->setVCSPassword(null);
+ $user->save();
+ return id(new AphrontRedirectResponse())
+ ->setURI($this->getPanelURI('?regenerated=true'));
+ }
+
+ if ($request->getStr('regenerated')) {
+ $notice = new AphrontErrorView();
+ $notice->setSeverity(AphrontErrorView::SEVERITY_NOTICE);
+ $notice->setTitle(pht('Version Control Password Regenerated'));
+ $notice->appendChild(phutil_tag(
+ 'p',
+ array(),
+ pht('Your old password has been destroyed and you have been issued '.
+ 'a new password. Update your version control client if needed.')));
+ $notice = $notice->render();
+ } else {
+ $notice = null;
+ }
+
+ $cert_form = new AphrontFormView();
+ $cert_form
+ ->setUser($user)
+ ->appendChild(hsprintf(
+ '<p class="aphront-form-instructions">%s</p>',
+ pht(
+ 'This password is used to contribute to Phabricator '.
+ 'hosted repositories. Instead of using your normal password '.
+ 'when connecting to a host repository, provide the password '.
+ 'below.')))
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel(pht('Password'))
+ ->setValue($user->getVCSPassword()));
+
+ $cert_form = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Version Control Password'))
+ ->setForm($cert_form);
+
+ $regen_instruction = pht('You can regenerate this password, which '.
+ 'will invalidate the old password and issue a new one.');
+
+ $regen_form = new AphrontFormView();
+ $regen_form
+ ->setUser($user)
+ ->setAction($this->getPanelURI())
+ ->setWorkflow(true)
+ ->appendChild(hsprintf(
+ '<p class="aphront-form-instructions">%s</p>', $regen_instruction))
+ ->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue(pht('Regenerate Password')));
+
+ $regen_form = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Regenerate Password'))
+ ->setForm($regen_form);
+
+ return array(
+ $notice,
+ $cert_form,
+ $regen_form,
+ );
+ }
+}
Index: src/applications/transactions/constants/PhabricatorTransactions.php
===================================================================
--- src/applications/transactions/constants/PhabricatorTransactions.php
+++ src/applications/transactions/constants/PhabricatorTransactions.php
@@ -7,6 +7,7 @@
const TYPE_VIEW_POLICY = 'core:view-policy';
const TYPE_EDIT_POLICY = 'core:edit-policy';
const TYPE_JOIN_POLICY = 'core:join-policy';
+ const TYPE_PUSH_POLICY = 'core:push-policy';
const TYPE_EDGE = 'core:edge';
const TYPE_CUSTOMFIELD = 'core:customfield';
Index: src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
===================================================================
--- src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
+++ src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php
@@ -139,6 +139,8 @@
return $object->getViewPolicy();
case PhabricatorTransactions::TYPE_EDIT_POLICY:
return $object->getEditPolicy();
+ case PhabricatorTransactions::TYPE_PUSH_POLICY:
+ return $object->getPushPolicy();
case PhabricatorTransactions::TYPE_EDGE:
$edge_type = $xaction->getMetadataValue('edge:type');
if (!$edge_type) {
@@ -175,6 +177,7 @@
return $this->getPHIDTransactionNewValue($xaction);
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
+ case PhabricatorTransactions::TYPE_PUSH_POLICY:
return $xaction->getNewValue();
case PhabricatorTransactions::TYPE_EDGE:
return $this->getEdgeTransactionNewValue($xaction);
@@ -235,6 +238,9 @@
case PhabricatorTransactions::TYPE_EDIT_POLICY:
$object->setEditPolicy($xaction->getNewValue());
break;
+ case PhabricatorTransactions::TYPE_PUSH_POLICY:
+ $object->setPushPolicy($xaction->getNewValue());
+ break;
case PhabricatorTransactions::TYPE_CUSTOMFIELD:
$field = $this->getCustomFieldForTransaction($object, $xaction);
return $field->applyApplicationTransactionInternalEffects($xaction);
Index: src/applications/transactions/storage/PhabricatorApplicationTransaction.php
===================================================================
--- src/applications/transactions/storage/PhabricatorApplicationTransaction.php
+++ src/applications/transactions/storage/PhabricatorApplicationTransaction.php
@@ -149,6 +149,7 @@
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
+ case PhabricatorTransactions::TYPE_PUSH_POLICY:
if (!PhabricatorPolicyQuery::isGlobalPolicy($old)) {
$phids[] = array($old);
}
@@ -228,6 +229,7 @@
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
+ case PhabricatorTransactions::TYPE_PUSH_POLICY:
return 'lock';
case PhabricatorTransactions::TYPE_EDGE:
return 'link';
@@ -245,6 +247,7 @@
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
+ case PhabricatorTransactions::TYPE_PUSH_POLICY:
if ($this->getOldValue() === null) {
return true;
} else {
@@ -277,6 +280,10 @@
return pht(
'This %s already has that join policy.',
$this->getApplicationObjectTypeName());
+ case PhabricatorTransactions::TYPE_PUSH_POLICY:
+ return pht(
+ 'This %s already has that push policy.',
+ $this->getApplicationObjectTypeName());
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return pht(
'All users are already subscribed to this %s.',
@@ -320,6 +327,13 @@
$this->getApplicationObjectTypeName(),
$this->renderPolicyName($old),
$this->renderPolicyName($new));
+ case PhabricatorTransactions::TYPE_PUSH_POLICY:
+ return pht(
+ '%s changed the push policy of this %s from "%s" to "%s".',
+ $this->renderHandleLink($author_phid),
+ $this->getApplicationObjectTypeName(),
+ $this->renderPolicyName($old),
+ $this->renderPolicyName($new));
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
$add = array_diff($new, $old);
$rem = array_diff($old, $new);
@@ -439,6 +453,11 @@
'%s changed the join policy for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
+ case PhabricatorTransactions::TYPE_PUSH_POLICY:
+ return pht(
+ '%s changed the push policy for %s.',
+ $this->renderHandleLink($author_phid),
+ $this->renderHandleLink($object_phid));
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return pht(
'%s updated subscribers of %s.',
@@ -507,6 +526,7 @@
case PhabricatorTransactions::TYPE_VIEW_POLICY:
case PhabricatorTransactions::TYPE_EDIT_POLICY:
case PhabricatorTransactions::TYPE_JOIN_POLICY:
+ case PhabricatorTransactions::TYPE_PUSH_POLICY:
return pht('Changed Policy');
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
return pht('Changed Subscribers');
Index: src/infrastructure/git/GitHTTPServer.php
===================================================================
--- /dev/null
+++ src/infrastructure/git/GitHTTPServer.php
@@ -0,0 +1,74 @@
+<?php
+
+/**
+ * @phutil-external-symbol class PhabricatorStartup
+ */
+final class GitHTTPServer {
+
+ private $path;
+ private $projectRoot;
+
+ public function __construct($path = null) {
+ $this->path = $path;
+ if ($this->path === null) {
+ $this->path = "/usr/lib/git/git-http-backend";
+ }
+ }
+
+ public function setProjectRoot($root) {
+ $this->projectRoot = $root;
+ }
+
+ /**
+ * Accepts an Aphront request and returns the
+ * HTTP result from git-http-backend.
+ */
+ public function acceptRequest($url) {
+ if (!function_exists("proc_open")) {
+ throw new Exception("proc_open does not exist");
+ }
+
+ // Set up environment and pipes.
+ $env = array(
+ 'REQUEST_METHOD' => $_SERVER['REQUEST_METHOD'],
+ 'QUERY_STRING' => $_SERVER['QUERY_STRING'],
+ 'CONTENT_TYPE' => $_SERVER['CONTENT_TYPE'],
+ 'REMOTE_USER' => "",
+ 'REMOTE_ADDR' => $_SERVER['REMOTE_ADDR'],
+ 'GIT_PROJECT_ROOT' => $this->projectRoot,
+ 'GIT_HTTP_EXPORT_ALL' => '1',
+ 'PATH_INFO' => $url);
+ $descriptors = array(
+ 0 => array("pipe", "r"),
+ 1 => array("pipe", "w"),
+ 2 => array("pipe", "w"));
+ $pwd = getcwd();
+
+ // Execute git-http-backend. We have to use `proc_open`
+ $pipes = array();
+ $process = proc_open($this->path, $descriptors, $pipes, $pwd, $env);
+ if (!is_resource($process)) {
+ throw new Exception("proc_open did not start git-http-backend");
+ }
+
+ // Pass in the standard input, which may contain content pushed
+ // to Git.
+ fwrite($pipes[0], PhabricatorStartup::getRawInput());
+ fclose($pipes[0]);
+
+ // Read out the response, including HTTP headers.
+ $stdout = stream_get_contents($pipes[1]);
+ fclose($pipes[1]);
+ fclose($pipes[2]);
+
+ // Close the process.
+ $err = proc_close($process);
+
+ // Returns the output of git-http-backend, which
+ // is the direct HTTP response we should return (including
+ // HTTP headers).
+ return $stdout;
+ }
+
+}
+
Index: src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
===================================================================
--- src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -1704,6 +1704,14 @@
'type' => 'sql',
'name' => $this->getPatchPath('20131020.harbormaster.sql'),
),
+ '20131024.diffusionhost.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20131024.diffusionhost.sql'),
+ ),
+ '20131024.vcspassword.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20131024.vcspassword.sql'),
+ ),
);
}
}
Index: src/view/form/control/AphrontFormPolicyControl.php
===================================================================
--- src/view/form/control/AphrontFormPolicyControl.php
+++ src/view/form/control/AphrontFormPolicyControl.php
@@ -24,6 +24,7 @@
PhabricatorPolicyCapability::CAN_VIEW => pht('Visible To'),
PhabricatorPolicyCapability::CAN_EDIT => pht('Editable By'),
PhabricatorPolicyCapability::CAN_JOIN => pht('Joinable By'),
+ PhabricatorPolicyCapability::CAN_PUSH => pht('Pushable By'),
);
$this->setLabel(idx($labels, $this->capability, pht('Unknown Policy')));

File Metadata

Mime Type
text/plain
Expires
Sun, Nov 10, 1:58 AM (1 w, 1 d ago)
Storage Engine
amazon-s3
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
phabricator/secure/xf/sw/nh6jlxkyawklkzw6
Default Alt Text
D7391.id16657.diff (51 KB)

Event Timeline