1
0
Fork 0
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:
Chad Little 2013-11-05 19:05:22 -08:00
commit b6c88bd6c1
34 changed files with 1332 additions and 184 deletions

View 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;

View file

@ -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',
),

View file

@ -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;
}

View file

@ -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);

View 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;
}
}

View file

@ -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',

View file

@ -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;
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}
}

View file

@ -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);
}
}

View file

@ -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))

View file

@ -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) {

View file

@ -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;
}
}

View file

@ -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(

View file

@ -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',
),
),
);

View file

@ -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);
}
}

View file

@ -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}");

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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;
}

View file

@ -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);
}
}

View file

@ -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);
}
}

View file

@ -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."'.");
}
}
}

View file

@ -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,

View file

@ -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';
}

View file

@ -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();
}
}

View file

@ -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));
}
}

View file

@ -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() {

View file

@ -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() {

View file

@ -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.');
}
}

View file

@ -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;
}
}
}

View file

@ -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;
}
}

View file

@ -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 {

View file

@ -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'),
),
);
}
}