mirror of
https://we.phorge.it/source/phorge.git
synced 2024-11-20 13:52:40 +01:00
Merge branch 'master' of github.com:facebook/phabricator
This commit is contained in:
commit
b6c88bd6c1
34 changed files with 1332 additions and 184 deletions
11
resources/sql/patches/20131105.buildstep.sql
Normal file
11
resources/sql/patches/20131105.buildstep.sql
Normal file
|
@ -0,0 +1,11 @@
|
|||
CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildstep (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
buildPlanPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
|
||||
className VARCHAR(255) NOT NULL COLLATE utf8_bin,
|
||||
details LONGTEXT CHARACTER SET utf8 COLLATE utf8_bin NOT NULL,
|
||||
dateCreated INT UNSIGNED NOT NULL,
|
||||
dateModified INT UNSIGNED NOT NULL,
|
||||
KEY `key_plan` (buildPlanPHID),
|
||||
UNIQUE KEY `key_phid` (phid)
|
||||
) ENGINE=InnoDB, COLLATE utf8_general_ci;
|
|
@ -91,6 +91,7 @@ phutil_register_library_map(array(
|
|||
'AphrontView' => 'view/AphrontView.php',
|
||||
'AphrontWebpageResponse' => 'aphront/response/AphrontWebpageResponse.php',
|
||||
'AuditActionMenuEventListener' => 'applications/audit/events/AuditActionMenuEventListener.php',
|
||||
'BuildStepImplementation' => 'applications/harbormaster/step/BuildStepImplementation.php',
|
||||
'CelerityAPI' => 'infrastructure/celerity/CelerityAPI.php',
|
||||
'CelerityPhabricatorResourceController' => 'infrastructure/celerity/CelerityPhabricatorResourceController.php',
|
||||
'CelerityResourceController' => 'infrastructure/celerity/CelerityResourceController.php',
|
||||
|
@ -369,6 +370,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialFieldValidationException' => 'applications/differential/field/exception/DifferentialFieldValidationException.php',
|
||||
'DifferentialFreeformFieldSpecification' => 'applications/differential/field/specification/DifferentialFreeformFieldSpecification.php',
|
||||
'DifferentialFreeformFieldTestCase' => 'applications/differential/field/specification/__tests__/DifferentialFreeformFieldTestCase.php',
|
||||
'DifferentialGetWorkingCopy' => 'applications/differential/DifferentialGetWorkingCopy.php',
|
||||
'DifferentialGitSVNIDFieldSpecification' => 'applications/differential/field/specification/DifferentialGitSVNIDFieldSpecification.php',
|
||||
'DifferentialHostFieldSpecification' => 'applications/differential/field/specification/DifferentialHostFieldSpecification.php',
|
||||
'DifferentialHovercardEventListener' => 'applications/differential/event/DifferentialHovercardEventListener.php',
|
||||
|
@ -383,6 +385,9 @@ phutil_register_library_map(array(
|
|||
'DifferentialInlineCommentQuery' => 'applications/differential/query/DifferentialInlineCommentQuery.php',
|
||||
'DifferentialInlineCommentView' => 'applications/differential/view/DifferentialInlineCommentView.php',
|
||||
'DifferentialJIRAIssuesFieldSpecification' => 'applications/differential/field/specification/DifferentialJIRAIssuesFieldSpecification.php',
|
||||
'DifferentialLandingActionMenuEventListener' => 'applications/differential/landing/DifferentialLandingActionMenuEventListener.php',
|
||||
'DifferentialLandingStrategy' => 'applications/differential/landing/DifferentialLandingStrategy.php',
|
||||
'DifferentialLandingToHostedGit' => 'applications/differential/landing/DifferentialLandingToHostedGit.php',
|
||||
'DifferentialLinesFieldSpecification' => 'applications/differential/field/specification/DifferentialLinesFieldSpecification.php',
|
||||
'DifferentialLintFieldSpecification' => 'applications/differential/field/specification/DifferentialLintFieldSpecification.php',
|
||||
'DifferentialLintStatus' => 'applications/differential/constants/DifferentialLintStatus.php',
|
||||
|
@ -420,6 +425,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionEditor' => 'applications/differential/editor/DifferentialRevisionEditor.php',
|
||||
'DifferentialRevisionIDFieldParserTestCase' => 'applications/differential/field/specification/__tests__/DifferentialRevisionIDFieldParserTestCase.php',
|
||||
'DifferentialRevisionIDFieldSpecification' => 'applications/differential/field/specification/DifferentialRevisionIDFieldSpecification.php',
|
||||
'DifferentialRevisionLandController' => 'applications/differential/controller/DifferentialRevisionLandController.php',
|
||||
'DifferentialRevisionListController' => 'applications/differential/controller/DifferentialRevisionListController.php',
|
||||
'DifferentialRevisionListView' => 'applications/differential/view/DifferentialRevisionListView.php',
|
||||
'DifferentialRevisionMailReceiver' => 'applications/differential/mail/DifferentialRevisionMailReceiver.php',
|
||||
|
@ -656,7 +662,9 @@ phutil_register_library_map(array(
|
|||
'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php',
|
||||
'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php',
|
||||
'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php',
|
||||
'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php',
|
||||
'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php',
|
||||
'HarbormasterBuildableApplyController' => 'applications/harbormaster/controller/HarbormasterBuildableApplyController.php',
|
||||
'HarbormasterBuildableArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php',
|
||||
'HarbormasterBuildableEditController' => 'applications/harbormaster/controller/HarbormasterBuildableEditController.php',
|
||||
'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php',
|
||||
|
@ -675,12 +683,13 @@ phutil_register_library_map(array(
|
|||
'HarbormasterPHIDTypeBuildable' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php',
|
||||
'HarbormasterPlanController' => 'applications/harbormaster/controller/HarbormasterPlanController.php',
|
||||
'HarbormasterPlanEditController' => 'applications/harbormaster/controller/HarbormasterPlanEditController.php',
|
||||
'HarbormasterPlanExecuteController' => 'applications/harbormaster/controller/HarbormasterPlanExecuteController.php',
|
||||
'HarbormasterPlanListController' => 'applications/harbormaster/controller/HarbormasterPlanListController.php',
|
||||
'HarbormasterPlanViewController' => 'applications/harbormaster/controller/HarbormasterPlanViewController.php',
|
||||
'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php',
|
||||
'HarbormasterRunnerWorker' => 'applications/harbormaster/worker/HarbormasterRunnerWorker.php',
|
||||
'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php',
|
||||
'HarbormasterStepAddController' => 'applications/harbormaster/controller/HarbormasterStepAddController.php',
|
||||
'HarbormasterStepDeleteController' => 'applications/harbormaster/controller/HarbormasterStepDeleteController.php',
|
||||
'HarbormasterStepEditController' => 'applications/harbormaster/controller/HarbormasterStepEditController.php',
|
||||
'HeraldAction' => 'applications/herald/storage/HeraldAction.php',
|
||||
'HeraldAdapter' => 'applications/herald/adapter/HeraldAdapter.php',
|
||||
'HeraldApplyTranscript' => 'applications/herald/storage/transcript/HeraldApplyTranscript.php',
|
||||
|
@ -2190,6 +2199,7 @@ phutil_register_library_map(array(
|
|||
'ReleephStatusFieldSpecification' => 'applications/releeph/field/specification/ReleephStatusFieldSpecification.php',
|
||||
'ReleephSummaryFieldSpecification' => 'applications/releeph/field/specification/ReleephSummaryFieldSpecification.php',
|
||||
'ReleephUserView' => 'applications/releeph/view/user/ReleephUserView.php',
|
||||
'SleepBuildStepImplementation' => 'applications/harbormaster/step/SleepBuildStepImplementation.php',
|
||||
'SlowvoteEmbedView' => 'applications/slowvote/view/SlowvoteEmbedView.php',
|
||||
'SlowvoteRemarkupRule' => 'applications/slowvote/remarkup/SlowvoteRemarkupRule.php',
|
||||
),
|
||||
|
@ -2586,6 +2596,8 @@ phutil_register_library_map(array(
|
|||
'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery',
|
||||
'DifferentialInlineCommentView' => 'AphrontView',
|
||||
'DifferentialJIRAIssuesFieldSpecification' => 'DifferentialFieldSpecification',
|
||||
'DifferentialLandingActionMenuEventListener' => 'PhabricatorEventListener',
|
||||
'DifferentialLandingToHostedGit' => 'DifferentialLandingStrategy',
|
||||
'DifferentialLinesFieldSpecification' => 'DifferentialFieldSpecification',
|
||||
'DifferentialLintFieldSpecification' => 'DifferentialFieldSpecification',
|
||||
'DifferentialLocalCommitsView' => 'AphrontView',
|
||||
|
@ -2623,6 +2635,7 @@ phutil_register_library_map(array(
|
|||
'DifferentialRevisionEditor' => 'PhabricatorEditor',
|
||||
'DifferentialRevisionIDFieldParserTestCase' => 'PhabricatorTestCase',
|
||||
'DifferentialRevisionIDFieldSpecification' => 'DifferentialFieldSpecification',
|
||||
'DifferentialRevisionLandController' => 'DifferentialController',
|
||||
'DifferentialRevisionListController' =>
|
||||
array(
|
||||
0 => 'DifferentialController',
|
||||
|
@ -2864,15 +2877,21 @@ phutil_register_library_map(array(
|
|||
'HarbormasterBuildPlanTransactionComment' => 'PhabricatorApplicationTransactionComment',
|
||||
'HarbormasterBuildPlanTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'HarbormasterBuildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HarbormasterBuildStep' => 'HarbormasterDAO',
|
||||
'HarbormasterBuildStep' =>
|
||||
array(
|
||||
0 => 'HarbormasterDAO',
|
||||
1 => 'PhabricatorPolicyInterface',
|
||||
),
|
||||
'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HarbormasterBuildTarget' => 'HarbormasterDAO',
|
||||
'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HarbormasterBuildWorker' => 'PhabricatorWorker',
|
||||
'HarbormasterBuildable' =>
|
||||
array(
|
||||
0 => 'HarbormasterDAO',
|
||||
1 => 'PhabricatorPolicyInterface',
|
||||
),
|
||||
'HarbormasterBuildableApplyController' => 'HarbormasterController',
|
||||
'HarbormasterBuildableArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HarbormasterBuildableEditController' => 'HarbormasterController',
|
||||
'HarbormasterBuildableListController' =>
|
||||
|
@ -2895,7 +2914,6 @@ phutil_register_library_map(array(
|
|||
'HarbormasterPHIDTypeBuildable' => 'PhabricatorPHIDType',
|
||||
'HarbormasterPlanController' => 'PhabricatorController',
|
||||
'HarbormasterPlanEditController' => 'HarbormasterPlanController',
|
||||
'HarbormasterPlanExecuteController' => 'HarbormasterPlanController',
|
||||
'HarbormasterPlanListController' =>
|
||||
array(
|
||||
0 => 'HarbormasterPlanController',
|
||||
|
@ -2903,8 +2921,10 @@ phutil_register_library_map(array(
|
|||
),
|
||||
'HarbormasterPlanViewController' => 'HarbormasterPlanController',
|
||||
'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject',
|
||||
'HarbormasterRunnerWorker' => 'PhabricatorWorker',
|
||||
'HarbormasterScratchTable' => 'HarbormasterDAO',
|
||||
'HarbormasterStepAddController' => 'HarbormasterController',
|
||||
'HarbormasterStepDeleteController' => 'HarbormasterController',
|
||||
'HarbormasterStepEditController' => 'HarbormasterController',
|
||||
'HeraldAction' => 'HeraldDAO',
|
||||
'HeraldApplyTranscript' => 'HeraldDAO',
|
||||
'HeraldCapabilityManageGlobalRules' => 'PhabricatorPolicyCapability',
|
||||
|
@ -4643,6 +4663,7 @@ phutil_register_library_map(array(
|
|||
'ReleephStatusFieldSpecification' => 'ReleephFieldSpecification',
|
||||
'ReleephSummaryFieldSpecification' => 'ReleephFieldSpecification',
|
||||
'ReleephUserView' => 'AphrontView',
|
||||
'SleepBuildStepImplementation' => 'BuildStepImplementation',
|
||||
'SlowvoteEmbedView' => 'AphrontView',
|
||||
'SlowvoteRemarkupRule' => 'PhabricatorRemarkupRuleObject',
|
||||
),
|
||||
|
|
|
@ -222,6 +222,7 @@ class AphrontDefaultApplicationConfiguration
|
|||
|
||||
$response = new AphrontWebpageResponse();
|
||||
$response->setContent($view->render());
|
||||
$response->setHTTPResponseCode(500);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
@ -268,6 +269,7 @@ class AphrontDefaultApplicationConfiguration
|
|||
|
||||
$response = new AphrontDialogResponse();
|
||||
$response->setDialog($dialog);
|
||||
$response->setHTTPResponseCode(500);
|
||||
|
||||
return $response;
|
||||
}
|
||||
|
|
|
@ -249,9 +249,10 @@ abstract class PhabricatorController extends AphrontController {
|
|||
$view->appendChild(hsprintf(
|
||||
'<div style="padding: 2em 0;">%s</div>',
|
||||
$response->buildResponseString()));
|
||||
$response = new AphrontWebpageResponse();
|
||||
$response->setContent($view->render());
|
||||
return $response;
|
||||
$page_response = new AphrontWebpageResponse();
|
||||
$page_response->setContent($view->render());
|
||||
$page_response->setHTTPResponseCode($response->getHTTPResponseCode());
|
||||
return $page_response;
|
||||
} else {
|
||||
$response->getDialog()->setIsStandalone(true);
|
||||
|
||||
|
|
39
src/applications/differential/DifferentialGetWorkingCopy.php
Normal file
39
src/applications/differential/DifferentialGetWorkingCopy.php
Normal file
|
@ -0,0 +1,39 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Can't find a good place for this, so I'm putting it in the most notably
|
||||
* wrong place.
|
||||
*/
|
||||
final class DifferentialGetWorkingCopy {
|
||||
|
||||
/**
|
||||
* Creates and/or cleans a workspace for the requested repo.
|
||||
*
|
||||
* return ArcanistGitAPI
|
||||
*/
|
||||
public static function getCleanGitWorkspace(
|
||||
PhabricatorRepository $repo) {
|
||||
|
||||
$origin_path = $repo->getLocalPath();
|
||||
|
||||
$path = rtrim($origin_path, '/');
|
||||
$path = $path . '__workspace';
|
||||
|
||||
if (!Filesystem::pathExists($path)) {
|
||||
$repo->execxLocalCommand(
|
||||
'clone -- file://%s %s',
|
||||
$origin_path,
|
||||
$path);
|
||||
}
|
||||
|
||||
$workspace = new ArcanistGitAPI($path);
|
||||
$workspace->execxLocal('clean -f -d');
|
||||
$workspace->execxLocal('checkout master');
|
||||
$workspace->execxLocal('fetch');
|
||||
$workspace->execxLocal('reset --hard origin/master');
|
||||
$workspace->reloadWorkingCopy();
|
||||
|
||||
return $workspace;
|
||||
}
|
||||
|
||||
}
|
|
@ -32,6 +32,7 @@ final class PhabricatorApplicationDifferential extends PhabricatorApplication {
|
|||
return array(
|
||||
new DifferentialActionMenuEventListener(),
|
||||
new DifferentialHovercardEventListener(),
|
||||
new DifferentialLandingActionMenuEventListener(),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -48,6 +49,8 @@ final class PhabricatorApplicationDifferential extends PhabricatorApplication {
|
|||
'changeset/' => 'DifferentialChangesetViewController',
|
||||
'revision/edit/(?:(?P<id>[1-9]\d*)/)?'
|
||||
=> 'DifferentialRevisionEditController',
|
||||
'revision/land/(?:(?P<id>[1-9]\d*))/(?P<strategy>[^/]+)/'
|
||||
=> 'DifferentialRevisionLandController',
|
||||
'comment/' => array(
|
||||
'preview/(?P<id>[1-9]\d*)/' => 'DifferentialCommentPreviewController',
|
||||
'save/' => 'DifferentialCommentSaveController',
|
||||
|
|
|
@ -0,0 +1,130 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialRevisionLandController extends DifferentialController {
|
||||
|
||||
private $revisionID;
|
||||
private $strategyClass;
|
||||
private $pushStrategy;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->revisionID = $data['id'];
|
||||
$this->strategyClass = $data['strategy'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$revision_id = $this->revisionID;
|
||||
|
||||
$revision = id(new DifferentialRevisionQuery())
|
||||
->withIDs(array($revision_id))
|
||||
->setViewer($viewer)
|
||||
->executeOne();
|
||||
if (!$revision) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
if (is_subclass_of($this->strategyClass, 'DifferentialLandingStrategy')) {
|
||||
$this->pushStrategy = newv($this->strategyClass, array());
|
||||
} else {
|
||||
throw new Exception(
|
||||
"Strategy type must be a valid class name and must subclass ".
|
||||
"DifferentialLandingStrategy. ".
|
||||
"'{$this->strategyClass}' is not a subclass of ".
|
||||
"DifferentialLandingStrategy.");
|
||||
}
|
||||
|
||||
if ($request->isDialogFormPost()) {
|
||||
try {
|
||||
$this->attemptLand($revision, $request);
|
||||
$title = pht("Success!");
|
||||
$text = pht("Revision was successfully landed.");
|
||||
} catch (Exception $ex) {
|
||||
$title = pht("Failed to land revision");
|
||||
$text = 'moo';
|
||||
if ($ex instanceof PhutilProxyException) {
|
||||
$text = hsprintf(
|
||||
'%s:<br><pre>%s</pre>',
|
||||
$ex->getMessage(),
|
||||
$ex->getPreviousException()->getMessage());
|
||||
} else {
|
||||
$text = hsprintf('<pre>%s</pre>', $ex->getMessage());
|
||||
}
|
||||
$text = id(new AphrontErrorView())
|
||||
->appendChild($text);
|
||||
}
|
||||
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($viewer)
|
||||
->setTitle($title)
|
||||
->appendChild(phutil_tag('p', array(), $text))
|
||||
->setSubmitURI('/D'.$revision_id)
|
||||
->addSubmitButton(pht('Done'));
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
$prompt = hsprintf('%s<br><br>%s',
|
||||
pht(
|
||||
'This will squash and rebase revision %s, and push it to '.
|
||||
'origin/master.',
|
||||
$revision_id),
|
||||
pht('It is an experimental feature and may not work.'));
|
||||
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($viewer)
|
||||
->setTitle(pht("Land Revision %s?", $revision_id))
|
||||
->appendChild($prompt)
|
||||
->setSubmitURI($request->getRequestURI())
|
||||
->addSubmitButton(pht('Land it!'))
|
||||
->addCancelButton('/D'.$revision_id);
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
private function attemptLand($revision, $request) {
|
||||
$status = $revision->getStatus();
|
||||
if ($status != ArcanistDifferentialRevisionStatus::ACCEPTED) {
|
||||
throw new Exception("Only Accepted revisions can be landed.");
|
||||
}
|
||||
|
||||
$repository = $revision->getRepository();
|
||||
|
||||
if ($repository === null) {
|
||||
throw new Exception("revision is not attached to a repository.");
|
||||
}
|
||||
|
||||
$can_push = PhabricatorPolicyFilter::hasCapability(
|
||||
$request->getUser(),
|
||||
$repository,
|
||||
DiffusionCapabilityPush::CAPABILITY);
|
||||
|
||||
if (!$can_push) {
|
||||
throw new Exception(
|
||||
pht('You do not have permission to push to this repository.'));
|
||||
}
|
||||
|
||||
$lock = $this->lockRepository($repository);
|
||||
|
||||
try {
|
||||
$this->pushStrategy->processLandRequest(
|
||||
$request,
|
||||
$revision,
|
||||
$repository);
|
||||
} catch (Exception $e) {
|
||||
$lock->unlock();
|
||||
throw $e;
|
||||
}
|
||||
|
||||
$lock->unlock();
|
||||
}
|
||||
|
||||
private function lockRepository($repository) {
|
||||
$lock_name = __CLASS__.':'.($repository->getCallsign());
|
||||
$lock = PhabricatorGlobalLock::newLock($lock_name);
|
||||
$lock->lock();
|
||||
return $lock;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialLandingActionMenuEventListener
|
||||
extends PhabricatorEventListener {
|
||||
|
||||
public function register() {
|
||||
$this->listen(PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS);
|
||||
}
|
||||
|
||||
public function handleEvent(PhutilEvent $event) {
|
||||
switch ($event->getType()) {
|
||||
case PhabricatorEventType::TYPE_UI_DIDRENDERACTIONS:
|
||||
$this->handleActionsEvent($event);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
private function handleActionsEvent(PhutilEvent $event) {
|
||||
$object = $event->getValue('object');
|
||||
|
||||
$actions = null;
|
||||
if ($object instanceof DifferentialRevision) {
|
||||
$actions = $this->renderRevisionAction($event);
|
||||
}
|
||||
|
||||
$this->addActionMenuItems($event, $actions);
|
||||
}
|
||||
|
||||
private function renderRevisionAction(PhutilEvent $event) {
|
||||
if (!$this->canUseApplication($event->getUser())) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$revision = $event->getValue('object');
|
||||
|
||||
$repository = $revision->getRepository();
|
||||
if ($repository === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$strategies = id(new PhutilSymbolLoader())
|
||||
->setAncestorClass('DifferentialLandingStrategy')
|
||||
->loadObjects();
|
||||
foreach ($strategies as $strategy) {
|
||||
$actions = $strategy->createMenuItems(
|
||||
$event->getUser(),
|
||||
$revision,
|
||||
$repository);
|
||||
$this->addActionMenuItems($event, $actions);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,43 @@
|
|||
<?php
|
||||
|
||||
abstract class DifferentialLandingStrategy {
|
||||
|
||||
public abstract function processLandRequest(
|
||||
AphrontRequest $request,
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorRepository $repository);
|
||||
|
||||
/**
|
||||
* returns PhabricatorActionView or an array of PhabricatorActionView or null.
|
||||
*/
|
||||
abstract function createMenuItems(
|
||||
PhabricatorUser $viewer,
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorRepository $repository);
|
||||
|
||||
/**
|
||||
* returns PhabricatorActionView which can be attached to the revision view.
|
||||
*/
|
||||
protected function createActionView($revision, $name, $disabled = false) {
|
||||
$strategy = get_class($this);
|
||||
$revision_id = $revision->getId();
|
||||
return id(new PhabricatorActionView())
|
||||
->setRenderAsForm(true)
|
||||
->setName($name)
|
||||
->setHref("/differential/revision/land/{$revision_id}/{$strategy}/")
|
||||
->setDisabled($disabled);
|
||||
}
|
||||
|
||||
/**
|
||||
* might break if repository is not Git.
|
||||
*/
|
||||
protected function getGitWorkspace(PhabricatorRepository $repository) {
|
||||
try {
|
||||
return DifferentialGetWorkingCopy::getCleanGitWorkspace($repository);
|
||||
} catch (Exception $e) {
|
||||
throw new PhutilProxyException (
|
||||
'Failed to allocate a workspace',
|
||||
$e);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,136 @@
|
|||
<?php
|
||||
|
||||
final class DifferentialLandingToHostedGit
|
||||
extends DifferentialLandingStrategy {
|
||||
|
||||
public function processLandRequest(
|
||||
AphrontRequest $request,
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorRepository $repository) {
|
||||
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$workspace = $this->getGitWorkspace($repository);
|
||||
|
||||
try {
|
||||
$this->commitRevisionToWorkspace(
|
||||
$revision,
|
||||
$workspace,
|
||||
$viewer);
|
||||
} catch (Exception $e) {
|
||||
throw new PhutilProxyException(
|
||||
'Failed to commit patch',
|
||||
$e);
|
||||
}
|
||||
|
||||
try {
|
||||
$this->pushWorkspaceRepository(
|
||||
$repository,
|
||||
$workspace,
|
||||
$viewer);
|
||||
} catch (Exception $e) {
|
||||
throw new PhutilProxyException(
|
||||
'Failed to push changes upstream',
|
||||
$e);
|
||||
}
|
||||
}
|
||||
|
||||
public function commitRevisionToWorkspace(
|
||||
DifferentialRevision $revision,
|
||||
ArcanistRepositoryAPI $workspace,
|
||||
PhabricatorUser $user) {
|
||||
|
||||
$diff_id = $revision->loadActiveDiff()->getID();
|
||||
|
||||
$call = new ConduitCall(
|
||||
'differential.getrawdiff',
|
||||
array(
|
||||
'diffID' => $diff_id,
|
||||
));
|
||||
|
||||
$call->setUser($user);
|
||||
$raw_diff = $call->execute();
|
||||
|
||||
$missing_binary =
|
||||
"\nindex "
|
||||
. "0000000000000000000000000000000000000000.."
|
||||
. "0000000000000000000000000000000000000000\n";
|
||||
if (strpos($raw_diff, $missing_binary) !== false) {
|
||||
throw new Exception("Patch is missing content for a binary file");
|
||||
}
|
||||
|
||||
$future = $workspace->execFutureLocal('apply --index -');
|
||||
$future->write($raw_diff);
|
||||
$future->resolvex();
|
||||
|
||||
$workspace->reloadWorkingCopy();
|
||||
|
||||
$call = new ConduitCall(
|
||||
'differential.getcommitmessage',
|
||||
array(
|
||||
'revision_id' => $revision->getID(),
|
||||
));
|
||||
|
||||
$call->setUser($user);
|
||||
$message = $call->execute();
|
||||
|
||||
$author = id(new PhabricatorUser())->loadOneWhere(
|
||||
'phid = %s',
|
||||
$revision->getAuthorPHID());
|
||||
|
||||
$author_string = sprintf(
|
||||
'%s <%s>',
|
||||
$author->getRealName(),
|
||||
$author->loadPrimaryEmailAddress());
|
||||
$author_date = $revision->getDateCreated();
|
||||
|
||||
$workspace->execxLocal(
|
||||
'-c user.name=%s -c user.email=%s ' .
|
||||
'commit --date=%s --author=%s '.
|
||||
'--message=%s',
|
||||
// -c will set the 'committer'
|
||||
$user->getRealName(),
|
||||
$user->loadPrimaryEmailAddress(),
|
||||
$author_date,
|
||||
$author_string,
|
||||
$message);
|
||||
}
|
||||
|
||||
|
||||
public function pushWorkspaceRepository(
|
||||
PhabricatorRepository $repository,
|
||||
ArcanistRepositoryAPI $workspace,
|
||||
PhabricatorUser $user) {
|
||||
|
||||
$workspace->execxLocal("push origin HEAD:master");
|
||||
}
|
||||
|
||||
public function createMenuItems(
|
||||
PhabricatorUser $viewer,
|
||||
DifferentialRevision $revision,
|
||||
PhabricatorRepository $repository) {
|
||||
|
||||
$vcs = $repository->getVersionControlSystem();
|
||||
if ($vcs !== PhabricatorRepositoryType::REPOSITORY_TYPE_GIT) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$repository->isHosted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$repository->isWorkingCopyBare()) {
|
||||
return;
|
||||
}
|
||||
|
||||
$can_push = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
$repository,
|
||||
DiffusionCapabilityPush::CAPABILITY);
|
||||
|
||||
return $this->createActionView(
|
||||
$revision,
|
||||
pht('Land to Hosted Repository'),
|
||||
!$can_push);
|
||||
}
|
||||
}
|
|
@ -8,6 +8,8 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
$request = $this->getRequest();
|
||||
$uri = $request->getRequestURI();
|
||||
|
||||
$user_agent = idx($_SERVER, 'HTTP_USER_AGENT');
|
||||
|
||||
// Check if this is a VCS request, e.g. from "git clone", "hg clone", or
|
||||
// "svn checkout". If it is, we jump off into repository serving code to
|
||||
// process the request.
|
||||
|
@ -27,6 +29,8 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
//
|
||||
// ...to get a human-readable error.
|
||||
$vcs = $request->getExists('__vcs__');
|
||||
} else if (strncmp($user_agent, "git/", 4) === 0) {
|
||||
$vcs = PhabricatorRepositoryType::REPOSITORY_TYPE_GIT;
|
||||
} else if ($request->getExists('service')) {
|
||||
$service = $request->getStr('service');
|
||||
// We get this initially for `info/refs`.
|
||||
|
@ -541,6 +545,16 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
return null;
|
||||
}
|
||||
|
||||
if (!strlen($username)) {
|
||||
// No username.
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!strlen($password->openEnvelope())) {
|
||||
// No password.
|
||||
return null;
|
||||
}
|
||||
|
||||
$user = id(new PhabricatorPeopleQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withUsernames(array($username))
|
||||
|
|
|
@ -726,27 +726,27 @@ final class DiffusionRepositoryEditMainController
|
|||
->setNote($daemon_instructions));
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
|
||||
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) {
|
||||
|
|
|
@ -32,11 +32,15 @@ final class DiffusionGitRequest extends DiffusionRequest {
|
|||
return $this->commit;
|
||||
}
|
||||
|
||||
return $this->getResolvableBranchName($this->getBranch());
|
||||
}
|
||||
|
||||
protected function getResolvableBranchName($branch) {
|
||||
if ($this->repository->isWorkingCopyBare()) {
|
||||
return $this->getBranch();
|
||||
return $branch;
|
||||
} else {
|
||||
$remote = DiffusionBranchInformation::DEFAULT_GIT_REMOTE;
|
||||
return $remote.'/'.$this->getBranch();
|
||||
return $remote.'/'.$branch;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -643,7 +643,7 @@ abstract class DiffusionRequest {
|
|||
}
|
||||
|
||||
if ($this->getSupportsBranches()) {
|
||||
$branch = $this->getBranch();
|
||||
$branch = $this->getResolvableBranchName($this->getBranch());
|
||||
} else {
|
||||
$branch = 'HEAD';
|
||||
}
|
||||
|
@ -660,6 +660,10 @@ abstract class DiffusionRequest {
|
|||
return $this->stableCommitName;
|
||||
}
|
||||
|
||||
protected function getResolvableBranchName($branch) {
|
||||
return $branch;
|
||||
}
|
||||
|
||||
private function resolveRefs(array $refs) {
|
||||
if ($this->shouldInitFromConduit()) {
|
||||
return DiffusionQuery::callConduitWithDiffusionRequest(
|
||||
|
|
|
@ -44,13 +44,18 @@ final class PhabricatorApplicationHarbormaster extends PhabricatorApplication {
|
|||
=> 'HarbormasterBuildableListController',
|
||||
'buildable/' => array(
|
||||
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableEditController',
|
||||
'apply/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableApplyController',
|
||||
),
|
||||
'step/' => array(
|
||||
'add/(?:(?P<id>\d+)/)?' => 'HarbormasterStepAddController',
|
||||
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterStepEditController',
|
||||
'delete/(?:(?P<id>\d+)/)?' => 'HarbormasterStepDeleteController',
|
||||
),
|
||||
'plan/' => array(
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?'
|
||||
=> 'HarbormasterPlanListController',
|
||||
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanEditController',
|
||||
'(?P<id>\d+)/' => 'HarbormasterPlanViewController',
|
||||
'execute/(?P<id>\d+)/' => 'HarbormasterPlanExecuteController',
|
||||
),
|
||||
),
|
||||
);
|
||||
|
|
|
@ -0,0 +1,81 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterBuildableApplyController
|
||||
extends HarbormasterController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$id = $this->id;
|
||||
|
||||
$buildable = id(new HarbormasterBuildableQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if ($buildable === null) {
|
||||
throw new Exception("Buildable not found!");
|
||||
}
|
||||
|
||||
$buildable_uri = '/B'.$buildable->getID();
|
||||
|
||||
if ($request->isDialogFormPost()) {
|
||||
$plan = id(new HarbormasterBuildPlanQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($request->getInt('build-plan')))
|
||||
->executeOne();
|
||||
|
||||
$build = HarbormasterBuild::initializeNewBuild($viewer);
|
||||
$build->setBuildablePHID($buildable->getPHID());
|
||||
$build->setBuildPlanPHID($plan->getPHID());
|
||||
$build->setBuildStatus(HarbormasterBuild::STATUS_PENDING);
|
||||
$build->save();
|
||||
|
||||
PhabricatorWorker::scheduleTask(
|
||||
'HarbormasterBuildWorker',
|
||||
array(
|
||||
'buildID' => $build->getID()
|
||||
));
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($buildable_uri);
|
||||
}
|
||||
|
||||
$plans = id(new HarbormasterBuildPlanQuery())
|
||||
->setViewer($viewer)
|
||||
->execute();
|
||||
|
||||
$options = array();
|
||||
foreach ($plans as $plan) {
|
||||
$options[$plan->getID()] = $plan->getName();
|
||||
}
|
||||
|
||||
// FIXME: I'd really like to use the dialog that "Edit Differential
|
||||
// Revisions" uses, but that code is quite hard-coded for the particular
|
||||
// uses, so for now we just give a single dropdown.
|
||||
|
||||
$dialog = new AphrontDialogView();
|
||||
$dialog->setTitle(pht('Apply which plan?'))
|
||||
->setUser($viewer)
|
||||
->addSubmitButton(pht('Apply'))
|
||||
->addCancelButton($buildable_uri);
|
||||
$dialog->appendChild(
|
||||
phutil_tag(
|
||||
'p',
|
||||
array(),
|
||||
pht(
|
||||
'Select what build plan you want to apply to this buildable:')));
|
||||
$dialog->appendChild(
|
||||
id(new AphrontFormSelectControl())
|
||||
->setUser($viewer)
|
||||
->setName('build-plan')
|
||||
->setOptions($options));
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
}
|
|
@ -36,7 +36,7 @@ final class HarbormasterBuildableListController
|
|||
$id = $buildable->getID();
|
||||
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setHeader(pht('Build %d', $buildable->getID()));
|
||||
->setHeader(pht('Buildable %d', $buildable->getID()));
|
||||
|
||||
if ($id) {
|
||||
$item->setHref("/B{$id}");
|
||||
|
|
|
@ -37,6 +37,36 @@ final class HarbormasterBuildableViewController
|
|||
$item = id(new PHUIObjectItemView())
|
||||
->setObjectName(pht('Build %d', $build->getID()))
|
||||
->setHeader($build->getName());
|
||||
switch ($build->getBuildStatus()) {
|
||||
case HarbormasterBuild::STATUS_INACTIVE:
|
||||
$item->setBarColor('grey');
|
||||
$item->addAttribute(pht('Inactive'));
|
||||
break;
|
||||
case HarbormasterBuild::STATUS_PENDING:
|
||||
$item->setBarColor('blue');
|
||||
$item->addAttribute(pht('Pending'));
|
||||
break;
|
||||
case HarbormasterBuild::STATUS_WAITING:
|
||||
$item->setBarColor('blue');
|
||||
$item->addAttribute(pht('Waiting on Resource'));
|
||||
break;
|
||||
case HarbormasterBuild::STATUS_BUILDING:
|
||||
$item->setBarColor('yellow');
|
||||
$item->addAttribute(pht('Building'));
|
||||
break;
|
||||
case HarbormasterBuild::STATUS_PASSED:
|
||||
$item->setBarColor('green');
|
||||
$item->addAttribute(pht('Passed'));
|
||||
break;
|
||||
case HarbormasterBuild::STATUS_FAILED:
|
||||
$item->setBarColor('red');
|
||||
$item->addAttribute(pht('Failed'));
|
||||
break;
|
||||
case HarbormasterBuild::STATUS_ERROR:
|
||||
$item->setBarColor('red');
|
||||
$item->addAttribute(pht('Unexpected Error'));
|
||||
break;
|
||||
}
|
||||
$build_list->addItem($item);
|
||||
}
|
||||
|
||||
|
@ -80,6 +110,15 @@ final class HarbormasterBuildableViewController
|
|||
->setObject($buildable)
|
||||
->setObjectURI("/B{$id}");
|
||||
|
||||
$apply_uri = $this->getApplicationURI('/buildable/apply/'.$id.'/');
|
||||
|
||||
$list->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Apply Build Plan'))
|
||||
->setIcon('edit')
|
||||
->setHref($apply_uri)
|
||||
->setWorkflow(true));
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
||||
|
|
|
@ -1,88 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterPlanExecuteController
|
||||
extends HarbormasterPlanController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$this->requireApplicationCapability(
|
||||
HarbormasterCapabilityManagePlans::CAPABILITY);
|
||||
|
||||
$id = $this->id;
|
||||
|
||||
$plan = id(new HarbormasterBuildPlanQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if (!$plan) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$cancel_uri = $this->getApplicationURI("plan/{$id}/");
|
||||
|
||||
$v_buildable = null;
|
||||
$e_buildable = null;
|
||||
|
||||
$errors = array();
|
||||
if ($request->isFormPost()) {
|
||||
$v_buildable = $request->getStr('buildable');
|
||||
|
||||
if ($v_buildable) {
|
||||
$buildable = id(new HarbormasterBuildableQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array(trim($v_buildable, 'B')))
|
||||
->executeOne();
|
||||
if (!$buildable) {
|
||||
$e_buildable = pht('Invalid');
|
||||
}
|
||||
} else {
|
||||
$e_buildable = pht('Required');
|
||||
$errors[] = pht('You must provide a buildable.');
|
||||
}
|
||||
|
||||
if (!$errors) {
|
||||
$build_plan = HarbormasterBuild::initializeNewBuild($viewer)
|
||||
->setBuildablePHID($buildable->getPHID())
|
||||
->setBuildPlanPHID($plan->getPHID())
|
||||
->save();
|
||||
|
||||
$buildable_id = $buildable->getID();
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI("/B{$buildable_id}");
|
||||
}
|
||||
}
|
||||
|
||||
if ($errors) {
|
||||
$errors = id(new AphrontErrorView())->setErrors($errors);
|
||||
}
|
||||
|
||||
$form = id(new PHUIFormLayoutView())
|
||||
->appendChild(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel(pht('Buildable'))
|
||||
->setName('buildable')
|
||||
->setValue($v_buildable)
|
||||
->setError($e_buildable));
|
||||
|
||||
$dialog = id(new AphrontDialogView())
|
||||
->setUser($viewer)
|
||||
->setTitle(pht('Execute Build Plan'))
|
||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||
->appendChild($errors)
|
||||
->appendChild($form)
|
||||
->addSubmitButton(pht('Execute Build Plan'))
|
||||
->addCancelButton($cancel_uri);
|
||||
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
}
|
|
@ -55,10 +55,13 @@ final class HarbormasterPlanViewController
|
|||
id(new PhabricatorCrumbView())
|
||||
->setName(pht("Plan %d", $id)));
|
||||
|
||||
$step_list = $this->buildStepList($plan);
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$box,
|
||||
$step_list,
|
||||
$xaction_view,
|
||||
),
|
||||
array(
|
||||
|
@ -67,6 +70,56 @@ final class HarbormasterPlanViewController
|
|||
));
|
||||
}
|
||||
|
||||
private function buildStepList(HarbormasterBuildPlan $plan) {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$steps = id(new HarbormasterBuildStepQuery())
|
||||
->setViewer($viewer)
|
||||
->withBuildPlanPHIDs(array($plan->getPHID()))
|
||||
->execute();
|
||||
|
||||
$can_edit = $this->hasApplicationCapability(
|
||||
HarbormasterCapabilityManagePlans::CAPABILITY);
|
||||
|
||||
$i = 1;
|
||||
$step_list = id(new PHUIObjectItemListView())
|
||||
->setUser($viewer);
|
||||
foreach ($steps as $step) {
|
||||
$implementation = $step->getStepImplementation();
|
||||
$item = id(new PHUIObjectItemView())
|
||||
->setObjectName("Step ".$i++)
|
||||
->setHeader($implementation->getName());
|
||||
|
||||
if (!$implementation->validateSettings()) {
|
||||
$item
|
||||
->setBarColor('red')
|
||||
->addAttribute(pht('This step is not configured correctly.'));
|
||||
} else {
|
||||
$item->addAttribute($implementation->getDescription());
|
||||
}
|
||||
|
||||
if ($can_edit) {
|
||||
$edit_uri = $this->getApplicationURI("step/edit/".$step->getID()."/");
|
||||
$item
|
||||
->setHref($edit_uri)
|
||||
->addAction(
|
||||
id(new PHUIListItemView())
|
||||
->setIcon('delete')
|
||||
->addSigil('harbormaster-build-step-delete')
|
||||
->setWorkflow(true)
|
||||
->setRenderNameAsTooltip(true)
|
||||
->setName(pht("Delete"))
|
||||
->setHref(
|
||||
$this->getApplicationURI("step/delete/".$step->getID()."/")));
|
||||
}
|
||||
|
||||
$step_list->addItem($item);
|
||||
}
|
||||
|
||||
return $step_list;
|
||||
}
|
||||
|
||||
private function buildActionList(HarbormasterBuildPlan $plan) {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
@ -90,11 +143,11 @@ final class HarbormasterPlanViewController
|
|||
|
||||
$list->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Manually Execute Plan'))
|
||||
->setHref($this->getApplicationURI("plan/execute/{$id}/"))
|
||||
->setWorkflow(true)
|
||||
->setName(pht('Add Build Step'))
|
||||
->setHref($this->getApplicationURI("step/add/{$id}/"))
|
||||
->setWorkflow($can_edit)
|
||||
->setDisabled(!$can_edit)
|
||||
->setIcon('arrow_right'));
|
||||
->setIcon('new'));
|
||||
|
||||
return $list;
|
||||
}
|
||||
|
|
|
@ -0,0 +1,84 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterStepAddController
|
||||
extends HarbormasterController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$this->requireApplicationCapability(
|
||||
HarbormasterCapabilityManagePlans::CAPABILITY);
|
||||
|
||||
$id = $this->id;
|
||||
|
||||
$plan = id(new HarbormasterBuildPlanQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if ($plan === null) {
|
||||
throw new Exception("Build plan not found!");
|
||||
}
|
||||
|
||||
$implementations = BuildStepImplementation::getImplementations();
|
||||
|
||||
$cancel_uri = $this->getApplicationURI('plan/'.$plan->getID().'/');
|
||||
|
||||
if ($request->isDialogFormPost()) {
|
||||
$class = $request->getStr('step-type');
|
||||
if (!in_array($class, $implementations)) {
|
||||
return $this->createDialog($implementations);
|
||||
}
|
||||
|
||||
$step = new HarbormasterBuildStep();
|
||||
$step->setBuildPlanPHID($plan->getPHID());
|
||||
$step->setClassName($class);
|
||||
$step->setDetails(array());
|
||||
$step->save();
|
||||
|
||||
$edit_uri = $this->getApplicationURI("step/edit/".$step->getID()."/");
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($edit_uri);
|
||||
}
|
||||
|
||||
return $this->createDialog($implementations, $cancel_uri);
|
||||
}
|
||||
|
||||
function createDialog(array $implementations, $cancel_uri) {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$control = id(new AphrontFormRadioButtonControl())
|
||||
->setName('step-type');
|
||||
|
||||
foreach ($implementations as $implementation_name) {
|
||||
$implementation = new $implementation_name();
|
||||
$control
|
||||
->addButton(
|
||||
$implementation_name,
|
||||
$implementation->getName(),
|
||||
$implementation->getGenericDescription());
|
||||
}
|
||||
|
||||
$dialog = new AphrontDialogView();
|
||||
$dialog->setTitle(pht('Add New Step'))
|
||||
->setUser($viewer)
|
||||
->addSubmitButton(pht('Add Build Step'))
|
||||
->addCancelButton($cancel_uri);
|
||||
$dialog->appendChild(
|
||||
phutil_tag(
|
||||
'p',
|
||||
array(),
|
||||
pht(
|
||||
'Select what type of build step you want to add: ')));
|
||||
$dialog->appendChild($control);
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterStepDeleteController
|
||||
extends HarbormasterController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = $data['id'];
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$this->requireApplicationCapability(
|
||||
HarbormasterCapabilityManagePlans::CAPABILITY);
|
||||
|
||||
$id = $this->id;
|
||||
|
||||
$step = id(new HarbormasterBuildStepQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($id))
|
||||
->executeOne();
|
||||
if ($step === null) {
|
||||
throw new Exception("Build step not found!");
|
||||
}
|
||||
|
||||
$plan_id = $step->getBuildPlan()->getID();
|
||||
$done_uri = $this->getApplicationURI('plan/'.$plan_id.'/');
|
||||
|
||||
if ($request->isDialogFormPost()) {
|
||||
$step->delete();
|
||||
return id(new AphrontRedirectResponse())->setURI($done_uri);
|
||||
}
|
||||
|
||||
$dialog = new AphrontDialogView();
|
||||
$dialog->setTitle(pht('Really Delete Step?'))
|
||||
->setUser($viewer)
|
||||
->addSubmitButton(pht('Delete Build Step'))
|
||||
->addCancelButton($done_uri);
|
||||
$dialog->appendChild(
|
||||
phutil_tag(
|
||||
'p',
|
||||
array(),
|
||||
pht(
|
||||
'Are you sure you want to delete this '.
|
||||
'step? This can\'t be undone!')));
|
||||
return id(new AphrontDialogResponse())->setDialog($dialog);
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterStepEditController
|
||||
extends HarbormasterController {
|
||||
|
||||
private $id;
|
||||
|
||||
public function willProcessRequest(array $data) {
|
||||
$this->id = idx($data, 'id');
|
||||
}
|
||||
|
||||
public function processRequest() {
|
||||
$request = $this->getRequest();
|
||||
$viewer = $request->getUser();
|
||||
|
||||
$this->requireApplicationCapability(
|
||||
HarbormasterCapabilityManagePlans::CAPABILITY);
|
||||
|
||||
$step = id(new HarbormasterBuildStepQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($this->id))
|
||||
->executeOne();
|
||||
if (!$step) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$plan = id(new HarbormasterBuildPlanQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($step->getBuildPlanPHID()))
|
||||
->executeOne();
|
||||
if (!$plan) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$implementation = $step->getStepImplementation();
|
||||
$implementation->validateSettingDefinitions();
|
||||
$settings = $implementation->getSettings();
|
||||
|
||||
$errors = array();
|
||||
if ($request->isFormPost()) {
|
||||
foreach ($implementation->getSettingDefinitions() as $name => $opt) {
|
||||
$readable_name = $this->getReadableName($name, $opt);
|
||||
$value = $this->getValueFromRequest($request, $name, $opt['type']);
|
||||
|
||||
// TODO: This won't catch any validation issues unless the field
|
||||
// is missing completely. How should we check if the user is
|
||||
// required to enter an integer?
|
||||
if ($value === null) {
|
||||
$errors[] = $readable_name.' is not valid.';
|
||||
} else {
|
||||
$step->setDetail($name, $value);
|
||||
}
|
||||
}
|
||||
|
||||
if (count($errors) === 0) {
|
||||
$step->save();
|
||||
|
||||
return id(new AphrontRedirectResponse())
|
||||
->setURI($this->getApplicationURI('plan/'.$plan->getID().'/'));
|
||||
}
|
||||
}
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setUser($viewer);
|
||||
|
||||
// We need to render out all of the fields for the settings that
|
||||
// the implementation has.
|
||||
foreach ($implementation->getSettingDefinitions() as $name => $opt) {
|
||||
if ($request->isFormPost()) {
|
||||
$value = $this->getValueFromRequest($request, $name, $opt['type']);
|
||||
} else {
|
||||
$value = $settings[$name];
|
||||
}
|
||||
|
||||
switch ($opt['type']) {
|
||||
case BuildStepImplementation::SETTING_TYPE_STRING:
|
||||
case BuildStepImplementation::SETTING_TYPE_INTEGER:
|
||||
$control = id(new AphrontFormTextControl())
|
||||
->setLabel($this->getReadableName($name, $opt))
|
||||
->setName($name)
|
||||
->setValue($value);
|
||||
break;
|
||||
case BuildStepImplementation::SETTING_TYPE_BOOLEAN:
|
||||
$control = id(new AphrontFormCheckboxControl())
|
||||
->setLabel($this->getReadableName($name, $opt))
|
||||
->setName($name)
|
||||
->setValue($value);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unable to render field with unknown type.");
|
||||
}
|
||||
|
||||
if (isset($opt['description'])) {
|
||||
$control->setCaption($opt['description']);
|
||||
}
|
||||
|
||||
$form->appendChild($control);
|
||||
}
|
||||
|
||||
$form->appendChild(
|
||||
id(new AphrontFormSubmitControl())
|
||||
->setValue(pht('Save Step Configuration'))
|
||||
->addCancelButton(
|
||||
$this->getApplicationURI('plan/'.$plan->getID().'/')));
|
||||
|
||||
$box = id(new PHUIObjectBoxView())
|
||||
->setHeaderText('Edit Step: '.$implementation->getName())
|
||||
->setValidationException(null)
|
||||
->setForm($form);
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs();
|
||||
$id = $plan->getID();
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName(pht("Plan %d", $id))
|
||||
->setHref($this->getApplicationURI("plan/{$id}/")));
|
||||
$crumbs->addCrumb(
|
||||
id(new PhabricatorCrumbView())
|
||||
->setName(pht('Edit Step')));
|
||||
|
||||
return $this->buildApplicationPage(
|
||||
array(
|
||||
$crumbs,
|
||||
$box,
|
||||
),
|
||||
array(
|
||||
'title' => $implementation->getName(),
|
||||
'device' => true,
|
||||
));
|
||||
}
|
||||
|
||||
public function getReadableName($name, $opt) {
|
||||
$readable_name = $name;
|
||||
if (isset($opt['name'])) {
|
||||
$readable_name = $opt['name'];
|
||||
}
|
||||
return $readable_name;
|
||||
}
|
||||
|
||||
public function getValueFromRequest(AphrontRequest $request, $name, $type) {
|
||||
switch ($type) {
|
||||
case BuildStepImplementation::SETTING_TYPE_STRING:
|
||||
return $request->getStr($name);
|
||||
break;
|
||||
case BuildStepImplementation::SETTING_TYPE_INTEGER:
|
||||
return $request->getInt($name);
|
||||
break;
|
||||
case BuildStepImplementation::SETTING_TYPE_BOOLEAN:
|
||||
return $request->getBool($name);
|
||||
break;
|
||||
default:
|
||||
throw new Exception("Unsupported setting type '".$type."'.");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -5,6 +5,7 @@ final class HarbormasterBuildQuery
|
|||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $buildStatuses;
|
||||
private $buildablePHIDs;
|
||||
private $buildPlanPHIDs;
|
||||
|
||||
|
@ -20,6 +21,11 @@ final class HarbormasterBuildQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withBuildStatuses(array $build_statuses) {
|
||||
$this->buildStatuses = $build_statuses;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withBuildablePHIDs(array $buildable_phids) {
|
||||
$this->buildablePHIDs = $buildable_phids;
|
||||
return $this;
|
||||
|
@ -115,6 +121,13 @@ final class HarbormasterBuildQuery
|
|||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->buildStatuses) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'buildStatus in (%Ls)',
|
||||
$this->buildStatuses);
|
||||
}
|
||||
|
||||
if ($this->buildablePHIDs) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
|
|
|
@ -5,6 +5,7 @@ final class HarbormasterBuildStepQuery
|
|||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $buildPlanPHIDs;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
|
@ -16,6 +17,19 @@ final class HarbormasterBuildStepQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withBuildPlanPHIDs(array $phids) {
|
||||
$this->buildPlanPHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getPagingColumn() {
|
||||
return 'id';
|
||||
}
|
||||
|
||||
public function getReversePaging() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
$table = new HarbormasterBuildStep();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
@ -48,11 +62,43 @@ final class HarbormasterBuildStepQuery
|
|||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->buildPlanPHIDs) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
'buildPlanPHID in (%Ls)',
|
||||
$this->buildPlanPHIDs);
|
||||
}
|
||||
|
||||
$where[] = $this->buildPagingClause($conn_r);
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
}
|
||||
|
||||
protected function willFilterPage(array $page) {
|
||||
$plans = array();
|
||||
|
||||
$buildplan_phids = array_filter(mpull($page, 'getBuildPlanPHID'));
|
||||
if ($buildplan_phids) {
|
||||
$plans = id(new PhabricatorObjectQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withPHIDs($buildplan_phids)
|
||||
->setParentQuery($this)
|
||||
->execute();
|
||||
$plans = mpull($plans, null, 'getPHID');
|
||||
}
|
||||
|
||||
foreach ($page as $key => $build) {
|
||||
$buildable_phid = $build->getBuildPlanPHID();
|
||||
if (empty($plans[$buildable_phid])) {
|
||||
unset($page[$key]);
|
||||
continue;
|
||||
}
|
||||
$build->attachBuildPlan($plans[$buildable_phid]);
|
||||
}
|
||||
|
||||
return $page;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorApplicationHarbormaster';
|
||||
}
|
||||
|
|
|
@ -0,0 +1,88 @@
|
|||
<?php
|
||||
|
||||
abstract class BuildStepImplementation {
|
||||
|
||||
private $settings;
|
||||
|
||||
const SETTING_TYPE_STRING = 'string';
|
||||
const SETTING_TYPE_INTEGER = 'integer';
|
||||
const SETTING_TYPE_BOOLEAN = 'boolean';
|
||||
|
||||
public static function getImplementations() {
|
||||
$symbols = id(new PhutilSymbolLoader())
|
||||
->setAncestorClass("BuildStepImplementation")
|
||||
->setConcreteOnly(true)
|
||||
->selectAndLoadSymbols();
|
||||
return ipull($symbols, 'name');
|
||||
}
|
||||
|
||||
/**
|
||||
* The name of the implementation.
|
||||
*/
|
||||
abstract public function getName();
|
||||
|
||||
/**
|
||||
* The generic description of the implementation.
|
||||
*/
|
||||
public function getGenericDescription() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* The description of the implementation, based on the current settings.
|
||||
*/
|
||||
public function getDescription() {
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Run the build step against the specified build.
|
||||
*/
|
||||
abstract public function execute(HarbormasterBuild $build);
|
||||
|
||||
/**
|
||||
* Gets the settings for this build step.
|
||||
*/
|
||||
public function getSettings() {
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate the current settings of this build step.
|
||||
*/
|
||||
public function validate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the settings for this build step implementation from the build step.
|
||||
*/
|
||||
public final function loadSettings(HarbormasterBuildStep $build_step) {
|
||||
$this->settings = array();
|
||||
$this->validateSettingDefinitions();
|
||||
foreach ($this->getSettingDefinitions() as $name => $opt) {
|
||||
$this->settings[$name] = $build_step->getDetail($name);
|
||||
}
|
||||
return $this->settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates that the setting definitions for this implementation are valid.
|
||||
*/
|
||||
public final function validateSettingDefinitions() {
|
||||
foreach ($this->getSettingDefinitions() as $name => $opt) {
|
||||
if (!isset($opt['type'])) {
|
||||
throw new Exception(
|
||||
'Setting definition \''.$name.
|
||||
'\' is missing type requirement.');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array of settings for this step implementation.
|
||||
*/
|
||||
public function getSettingDefinitions() {
|
||||
return array();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,45 @@
|
|||
<?php
|
||||
|
||||
final class SleepBuildStepImplementation extends BuildStepImplementation {
|
||||
|
||||
public function getName() {
|
||||
return pht('Sleep');
|
||||
}
|
||||
|
||||
public function getGenericDescription() {
|
||||
return pht('Sleep for a specified number of seconds.');
|
||||
}
|
||||
|
||||
public function getDescription() {
|
||||
$settings = $this->getSettings();
|
||||
|
||||
return pht('Sleep for %s seconds.', $settings['seconds']);
|
||||
}
|
||||
|
||||
public function execute(HarbormasterBuild $build) {
|
||||
$settings = $this->getSettings();
|
||||
|
||||
sleep($settings['seconds']);
|
||||
}
|
||||
|
||||
public function validateSettings() {
|
||||
$settings = $this->getSettings();
|
||||
|
||||
if ($settings['seconds'] === null) {
|
||||
return false;
|
||||
}
|
||||
if (!is_int($settings['seconds'])) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getSettingDefinitions() {
|
||||
return array(
|
||||
'seconds' => array(
|
||||
'name' => 'Seconds',
|
||||
'description' => 'The number of seconds to sleep for.',
|
||||
'type' => BuildStepImplementation::SETTING_TYPE_INTEGER));
|
||||
}
|
||||
|
||||
}
|
|
@ -12,10 +12,12 @@ final class HarbormasterBuildable extends HarbormasterDAO
|
|||
private $containerObject = self::ATTACHABLE;
|
||||
private $buildableHandle = self::ATTACHABLE;
|
||||
|
||||
const STATUS_WHATEVER = 'whatever';
|
||||
|
||||
public static function initializeNewBuildable(PhabricatorUser $actor) {
|
||||
return id(new HarbormasterBuildable())
|
||||
->setBuildStatus('new') // TODO: Define these.
|
||||
->setBuildableStatus('active'); // TODO: Define these, too.
|
||||
->setBuildStatus(self::STATUS_WHATEVER)
|
||||
->setBuildableStatus(self::STATUS_WHATEVER);
|
||||
}
|
||||
|
||||
public function getConfiguration() {
|
||||
|
|
|
@ -10,9 +10,44 @@ final class HarbormasterBuild extends HarbormasterDAO
|
|||
private $buildable = self::ATTACHABLE;
|
||||
private $buildPlan = self::ATTACHABLE;
|
||||
|
||||
/**
|
||||
* Not currently being built.
|
||||
*/
|
||||
const STATUS_INACTIVE = 'inactive';
|
||||
|
||||
/**
|
||||
* Pending pick up by the Harbormaster daemon.
|
||||
*/
|
||||
const STATUS_PENDING = 'pending';
|
||||
|
||||
/**
|
||||
* Waiting for a resource to be allocated (not yet relevant).
|
||||
*/
|
||||
const STATUS_WAITING = 'waiting';
|
||||
|
||||
/**
|
||||
* Current building the buildable.
|
||||
*/
|
||||
const STATUS_BUILDING = 'building';
|
||||
|
||||
/**
|
||||
* The build has passed.
|
||||
*/
|
||||
const STATUS_PASSED = 'passed';
|
||||
|
||||
/**
|
||||
* The build has failed.
|
||||
*/
|
||||
const STATUS_FAILED = 'failed';
|
||||
|
||||
/**
|
||||
* The build encountered an unexpected error.
|
||||
*/
|
||||
const STATUS_ERROR = 'error';
|
||||
|
||||
public static function initializeNewBuild(PhabricatorUser $actor) {
|
||||
return id(new HarbormasterBuild())
|
||||
->setBuildStatus('building'); // TODO: Sort this.
|
||||
->setBuildStatus(self::STATUS_INACTIVE);
|
||||
}
|
||||
|
||||
public function getConfiguration() {
|
||||
|
|
|
@ -1,14 +1,20 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterBuildStep extends HarbormasterDAO {
|
||||
final class HarbormasterBuildStep extends HarbormasterDAO
|
||||
implements PhabricatorPolicyInterface {
|
||||
|
||||
protected $buildPlanPHID;
|
||||
protected $className;
|
||||
protected $details = array();
|
||||
|
||||
private $buildPlan = self::ATTACHABLE;
|
||||
|
||||
public function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_AUX_PHID => true,
|
||||
self::CONFIG_SERIALIZATION => array(
|
||||
'details' => self::SERIALIZATION_JSON,
|
||||
)
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
|
@ -26,4 +32,54 @@ final class HarbormasterBuildStep extends HarbormasterDAO {
|
|||
return $this->assertAttached($this->buildPlan);
|
||||
}
|
||||
|
||||
public function getDetail($key, $default = null) {
|
||||
return idx($this->details, $key, $default);
|
||||
}
|
||||
|
||||
public function setDetail($key, $value) {
|
||||
$this->details[$key] = $value;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getStepImplementation() {
|
||||
if ($this->className === null) {
|
||||
throw new Exception("No implementation set for the given step.");
|
||||
}
|
||||
|
||||
static $implementations = null;
|
||||
if ($implementations === null) {
|
||||
$implementations = BuildStepImplementation::getImplementations();
|
||||
}
|
||||
|
||||
$class = $this->className;
|
||||
if (!in_array($class, $implementations)) {
|
||||
throw new Exception(
|
||||
"Class name '".$class."' does not extend BuildStepImplementation.");
|
||||
}
|
||||
$implementation = newv($class, array());
|
||||
$implementation->loadSettings($this);
|
||||
return $implementation;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
return $this->getBuildPlan()->getPolicy($capability);
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return $this->getBuildPlan()->hasAutomaticCapability($capability, $viewer);
|
||||
}
|
||||
|
||||
public function describeAutomaticCapability($capability) {
|
||||
return pht('A build step has the same policies as its build plan.');
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,69 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Run builds
|
||||
*/
|
||||
final class HarbormasterBuildWorker extends PhabricatorWorker {
|
||||
|
||||
public function getRequiredLeaseTime() {
|
||||
return 60 * 60 * 24;
|
||||
}
|
||||
|
||||
public function doWork() {
|
||||
$data = $this->getTaskData();
|
||||
$id = idx($data, 'buildID');
|
||||
|
||||
// Get a reference to the build.
|
||||
$build = id(new HarbormasterBuildQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withBuildStatuses(array(HarbormasterBuild::STATUS_PENDING))
|
||||
->withIDs(array($id))
|
||||
->needBuildPlans(true)
|
||||
->executeOne();
|
||||
if (!$build) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
pht('Invalid build ID "%s".', $id));
|
||||
}
|
||||
|
||||
try {
|
||||
$build->setBuildStatus(HarbormasterBuild::STATUS_BUILDING);
|
||||
$build->save();
|
||||
|
||||
$buildable = $build->getBuildable();
|
||||
$plan = $build->getBuildPlan();
|
||||
|
||||
$steps = id(new HarbormasterBuildStepQuery())
|
||||
->setViewer(PhabricatorUser::getOmnipotentUser())
|
||||
->withBuildPlanPHIDs(array($plan->getPHID()))
|
||||
->execute();
|
||||
|
||||
// Perform the build.
|
||||
foreach ($steps as $step) {
|
||||
$implementation = $step->getStepImplementation();
|
||||
if (!$implementation->validateSettings()) {
|
||||
$build->setBuildStatus(HarbormasterBuild::STATUS_ERROR);
|
||||
break;
|
||||
}
|
||||
$implementation->execute($build);
|
||||
if ($build->getBuildStatus() !== HarbormasterBuild::STATUS_BUILDING) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we get to here, then the build has finished. Set it to passed
|
||||
// if no build step explicitly set the status.
|
||||
if ($build->getBuildStatus() === HarbormasterBuild::STATUS_BUILDING) {
|
||||
$build->setBuildStatus(HarbormasterBuild::STATUS_PASSED);
|
||||
}
|
||||
$build->save();
|
||||
} catch (Exception $e) {
|
||||
// If any exception is raised, the build is marked as a failure and
|
||||
// the exception is re-thrown (this ensures we don't leave builds
|
||||
// in an inconsistent state).
|
||||
$build->setBuildStatus(HarbormasterBuild::STATUS_ERROR);
|
||||
$build->save();
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -1,53 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterRunnerWorker extends PhabricatorWorker {
|
||||
|
||||
public function getRequiredLeaseTime() {
|
||||
return 60 * 60 * 24;
|
||||
}
|
||||
|
||||
protected function doWork() {
|
||||
$data = $this->getTaskData();
|
||||
$id = idx($data, 'commitID');
|
||||
|
||||
$commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
|
||||
'id = %d',
|
||||
$id);
|
||||
|
||||
if (!$commit) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
"Commit '{$id}' does not exist!");
|
||||
}
|
||||
|
||||
// TODO: (T603) Policy interaction?
|
||||
$repository = id(new PhabricatorRepository())->loadOneWhere(
|
||||
'id = %d',
|
||||
$commit->getRepositoryID());
|
||||
|
||||
if (!$repository) {
|
||||
throw new PhabricatorWorkerPermanentFailureException(
|
||||
"Unable to load repository for commit '{$id}'!");
|
||||
}
|
||||
|
||||
$lease = id(new DrydockLease())
|
||||
->setResourceType('working-copy')
|
||||
->setAttributes(
|
||||
array(
|
||||
'repositoryID' => $repository->getID(),
|
||||
'commit' => $commit->getCommitIdentifier(),
|
||||
))
|
||||
->releaseOnDestruction()
|
||||
->waitUntilActive();
|
||||
|
||||
$cmd = $lease->getInterface('command');
|
||||
list($json) = $cmd
|
||||
->setWorkingDirectory($lease->getResource()->getAttribute('path'))
|
||||
->execx('arc unit --everything --json');
|
||||
$lease->release();
|
||||
|
||||
// TODO: Do something actually useful with this. Requires Harbormaster
|
||||
// buildout.
|
||||
echo $json;
|
||||
}
|
||||
|
||||
}
|
|
@ -750,8 +750,7 @@ final class PhabricatorRepositoryPullLocalDaemon
|
|||
$branches = mpull($branches, 'getHeadCommitIdentifier', 'getName');
|
||||
|
||||
$got_something = false;
|
||||
foreach ($branches as $name => $branch) {
|
||||
$commit = $branch['rev'];
|
||||
foreach ($branches as $name => $commit) {
|
||||
if ($this->isKnownCommit($repository, $commit)) {
|
||||
continue;
|
||||
} else {
|
||||
|
|
|
@ -1720,6 +1720,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
|
|||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('20131031.vcspassword.sql'),
|
||||
),
|
||||
'20131105.buildstep.sql' => array(
|
||||
'type' => 'sql',
|
||||
'name' => $this->getPatchPath('20131105.buildstep.sql'),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue