Page MenuHomePhabricator

D7499.id16923.diff

D7499.id16923.diff

diff --git a/resources/sql/patches/20131105.buildstep.sql b/resources/sql/patches/20131105.buildstep.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/patches/20131105.buildstep.sql
@@ -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;
diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php
--- a/src/__celerity_resource_map__.php
+++ b/src/__celerity_resource_map__.php
@@ -3985,7 +3985,7 @@
),
'phui-workboard-view-css' =>
array(
- 'uri' => '/res/445c7c7e/rsrc/css/phui/phui-workboard-view.css',
+ 'uri' => '/res/44fcb197/rsrc/css/phui/phui-workboard-view.css',
'type' => 'css',
'requires' =>
array(
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -91,6 +91,7 @@
'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 @@
'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 @@
'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 @@
'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 @@
'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,11 +683,9 @@
'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',
'HeraldAction' => 'applications/herald/storage/HeraldAction.php',
'HeraldAdapter' => 'applications/herald/adapter/HeraldAdapter.php',
@@ -2190,6 +2196,7 @@
'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 +2593,8 @@
'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery',
'DifferentialInlineCommentView' => 'AphrontView',
'DifferentialJIRAIssuesFieldSpecification' => 'DifferentialFieldSpecification',
+ 'DifferentialLandingActionMenuEventListener' => 'PhabricatorEventListener',
+ 'DifferentialLandingToHostedGit' => 'DifferentialLandingStrategy',
'DifferentialLinesFieldSpecification' => 'DifferentialFieldSpecification',
'DifferentialLintFieldSpecification' => 'DifferentialFieldSpecification',
'DifferentialLocalCommitsView' => 'AphrontView',
@@ -2623,6 +2632,7 @@
'DifferentialRevisionEditor' => 'PhabricatorEditor',
'DifferentialRevisionIDFieldParserTestCase' => 'PhabricatorTestCase',
'DifferentialRevisionIDFieldSpecification' => 'DifferentialFieldSpecification',
+ 'DifferentialRevisionLandController' => 'DifferentialController',
'DifferentialRevisionListController' =>
array(
0 => 'DifferentialController',
@@ -2864,15 +2874,21 @@
'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,15 +2911,13 @@
'HarbormasterPHIDTypeBuildable' => 'PhabricatorPHIDType',
'HarbormasterPlanController' => 'PhabricatorController',
'HarbormasterPlanEditController' => 'HarbormasterPlanController',
- 'HarbormasterPlanExecuteController' => 'HarbormasterPlanController',
'HarbormasterPlanListController' =>
array(
0 => 'HarbormasterPlanController',
1 => 'PhabricatorApplicationSearchResultsControllerInterface',
),
'HarbormasterPlanViewController' => 'HarbormasterPlanController',
'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject',
- 'HarbormasterRunnerWorker' => 'PhabricatorWorker',
'HarbormasterScratchTable' => 'HarbormasterDAO',
'HeraldAction' => 'HeraldDAO',
'HeraldApplyTranscript' => 'HeraldDAO',
@@ -4643,6 +4657,7 @@
'ReleephStatusFieldSpecification' => 'ReleephFieldSpecification',
'ReleephSummaryFieldSpecification' => 'ReleephFieldSpecification',
'ReleephUserView' => 'AphrontView',
+ 'SleepBuildStepImplementation' => 'BuildStepImplementation',
'SlowvoteEmbedView' => 'AphrontView',
'SlowvoteRemarkupRule' => 'PhabricatorRemarkupRuleObject',
),
diff --git a/src/applications/differential/DifferentialGetWorkingCopy.php b/src/applications/differential/DifferentialGetWorkingCopy.php
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/DifferentialGetWorkingCopy.php
@@ -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;
+ }
+
+}
diff --git a/src/applications/differential/application/PhabricatorApplicationDifferential.php b/src/applications/differential/application/PhabricatorApplicationDifferential.php
--- a/src/applications/differential/application/PhabricatorApplicationDifferential.php
+++ b/src/applications/differential/application/PhabricatorApplicationDifferential.php
@@ -32,6 +32,7 @@
return array(
new DifferentialActionMenuEventListener(),
new DifferentialHovercardEventListener(),
+ new DifferentialLandingActionMenuEventListener(),
);
}
@@ -48,6 +49,8 @@
'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',
diff --git a/src/applications/differential/controller/DifferentialRevisionLandController.php b/src/applications/differential/controller/DifferentialRevisionLandController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/controller/DifferentialRevisionLandController.php
@@ -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;
+ }
+}
+
diff --git a/src/applications/differential/landing/DifferentialLandingActionMenuEventListener.php b/src/applications/differential/landing/DifferentialLandingActionMenuEventListener.php
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/landing/DifferentialLandingActionMenuEventListener.php
@@ -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);
+ }
+ }
+
+}
+
diff --git a/src/applications/differential/landing/DifferentialLandingStrategy.php b/src/applications/differential/landing/DifferentialLandingStrategy.php
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/landing/DifferentialLandingStrategy.php
@@ -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);
+ }
+ }
+}
diff --git a/src/applications/differential/landing/DifferentialLandingToHostedGit.php b/src/applications/differential/landing/DifferentialLandingToHostedGit.php
new file mode 100644
--- /dev/null
+++ b/src/applications/differential/landing/DifferentialLandingToHostedGit.php
@@ -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);
+ }
+}
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
--- a/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryEditMainController.php
@@ -726,27 +726,27 @@
->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) {
diff --git a/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php b/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
--- a/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
+++ b/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
@@ -44,13 +44,13 @@
=> 'HarbormasterBuildableListController',
'buildable/' => array(
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableEditController',
+ 'apply/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableApplyController',
),
'plan/' => array(
'(?:query/(?P<queryKey>[^/]+)/)?'
=> 'HarbormasterPlanListController',
'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanEditController',
'(?P<id>\d+)/' => 'HarbormasterPlanViewController',
- 'execute/(?P<id>\d+)/' => 'HarbormasterPlanExecuteController',
),
),
);
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableApplyController.php b/src/applications/harbormaster/controller/HarbormasterBuildableApplyController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/controller/HarbormasterBuildableApplyController.php
@@ -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);
+ }
+
+}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableListController.php b/src/applications/harbormaster/controller/HarbormasterBuildableListController.php
--- a/src/applications/harbormaster/controller/HarbormasterBuildableListController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildableListController.php
@@ -36,7 +36,7 @@
$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}");
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
--- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
@@ -37,6 +37,32 @@
$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;
+ }
$build_list->addItem($item);
}
@@ -80,6 +106,15 @@
->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;
}
diff --git a/src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php b/src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php
deleted file mode 100644
--- a/src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php
+++ /dev/null
@@ -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);
- }
-
-}
diff --git a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php
--- a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php
@@ -88,14 +88,6 @@
->setDisabled(!$can_edit)
->setIcon('edit'));
- $list->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Manually Execute Plan'))
- ->setHref($this->getApplicationURI("plan/execute/{$id}/"))
- ->setWorkflow(true)
- ->setDisabled(!$can_edit)
- ->setIcon('arrow_right'));
-
return $list;
}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildQuery.php b/src/applications/harbormaster/query/HarbormasterBuildQuery.php
--- a/src/applications/harbormaster/query/HarbormasterBuildQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildQuery.php
@@ -5,6 +5,7 @@
private $ids;
private $phids;
+ private $buildStatuses;
private $buildablePHIDs;
private $buildPlanPHIDs;
@@ -20,6 +21,11 @@
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 @@
$this->phids);
}
+ if ($this->buildStatuses) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'buildStatus in (%Ls)',
+ $this->buildStatuses);
+ }
+
if ($this->buildablePHIDs) {
$where[] = qsprintf(
$conn_r,
diff --git a/src/applications/harbormaster/query/HarbormasterBuildStepQuery.php b/src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
--- a/src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
@@ -5,6 +5,7 @@
private $ids;
private $phids;
+ private $buildPlanPHIDs;
public function withIDs(array $ids) {
$this->ids = $ids;
@@ -16,6 +17,11 @@
return $this;
}
+ public function withBuildPlanPHIDs(array $phids) {
+ $this->buildPlanPHIDs = $phids;
+ return $this;
+ }
+
protected function loadPage() {
$table = new HarbormasterBuildStep();
$conn_r = $table->establishConnection('r');
@@ -48,11 +54,43 @@
$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';
}
diff --git a/src/applications/harbormaster/step/BuildStepImplementation.php b/src/applications/harbormaster/step/BuildStepImplementation.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/step/BuildStepImplementation.php
@@ -0,0 +1,48 @@
+<?php
+
+abstract class BuildStepImplementation {
+
+ private $settings;
+
+ /**
+ * The name of the implementation.
+ */
+ abstract public function getName();
+
+ /**
+ * The description of the implementation.
+ */
+ 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.
+ */
+ protected function getSettings() {
+ return $this->settings;
+ }
+
+ /**
+ * Loads the settings for this build step implementation from the build step.
+ */
+ public final function loadSettings(HarbormasterBuildStep $build_step) {
+ $this->settings = array();
+ foreach ($this->getSettingDefinitions() as $name => $opt) {
+ $this->settings[$name] = $build_step->getDetail($name);
+ }
+ return $this->settings;
+ }
+
+ /**
+ * Return an array of settings for this step implementation.
+ */
+ public function getSettingDefinitions() {
+ return array();
+ }
+}
diff --git a/src/applications/harbormaster/step/SleepBuildStepImplementation.php b/src/applications/harbormaster/step/SleepBuildStepImplementation.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/step/SleepBuildStepImplementation.php
@@ -0,0 +1,24 @@
+<?php
+
+final class SleepBuildStepImplementation extends BuildStepImplementation {
+
+ public function getName() {
+ return pht('Sleep');
+ }
+
+ public function getDescription() {
+ return pht('Sleep for a specified number of seconds.');
+ }
+
+ public function execute(HarbormasterBuild $build) {
+ $settings = $this->getSettings();
+
+ sleep($settings['seconds']);
+ }
+
+ public function getSettingDefinitions() {
+ return array(
+ 'seconds' => array());
+ }
+
+}
diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php
--- a/src/applications/harbormaster/storage/HarbormasterBuildable.php
+++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php
@@ -12,10 +12,12 @@
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() {
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
--- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
@@ -10,9 +10,39 @@
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';
+
public static function initializeNewBuild(PhabricatorUser $actor) {
return id(new HarbormasterBuild())
- ->setBuildStatus('building'); // TODO: Sort this.
+ ->setBuildStatus(self::STATUS_INACTIVE);
}
public function getConfiguration() {
diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
--- a/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
+++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
@@ -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,47 @@
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.");
+ }
+
+ // TODO: We should look up the class in phutil's system to ensure
+ // that it actually extends BuildStepImplementation.
+ $class = $this->className;
+ $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 it\'s build plan.');
+ }
}
diff --git a/src/applications/harbormaster/worker/HarbormasterBuildWorker.php b/src/applications/harbormaster/worker/HarbormasterBuildWorker.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/worker/HarbormasterBuildWorker.php
@@ -0,0 +1,65 @@
+<?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();
+ $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_FAILED);
+ $build->save();
+ throw $e;
+ }
+ }
+
+}
diff --git a/src/applications/harbormaster/worker/HarbormasterRunnerWorker.php b/src/applications/harbormaster/worker/HarbormasterRunnerWorker.php
deleted file mode 100644
--- a/src/applications/harbormaster/worker/HarbormasterRunnerWorker.php
+++ /dev/null
@@ -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;
- }
-
-}
diff --git a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
--- a/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
+++ b/src/applications/repository/daemon/PhabricatorRepositoryPullLocalDaemon.php
@@ -750,8 +750,7 @@
$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 {
diff --git a/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php b/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php
--- a/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php
+++ b/src/applications/transactions/feed/PhabricatorApplicationTransactionFeedStory.php
@@ -11,16 +11,19 @@
}
public function getRequiredObjectPHIDs() {
- return array(
- $this->getPrimaryTransactionPHID(),
- );
+ return $this->getValue('transactionPHIDs');
}
public function getRequiredHandlePHIDs() {
$phids = array();
- $phids[] = array($this->getValue('objectPHID'));
- $phids[] = $this->getPrimaryTransaction()->getRequiredHandlePHIDs();
- return array_mergev($phids);
+ $phids[] = $this->getValue('objectPHID');
+ foreach ($this->getValue('transactionPHIDs') as $xaction_phid) {
+ $xaction = $this->getObject($xaction_phid);
+ foreach ($xaction->getRequiredHandlePHIDs() as $handle_phid) {
+ $phids[] = $handle_phid;
+ }
+ }
+ return $phids;
}
protected function getPrimaryTransactionPHID() {
@@ -40,18 +43,23 @@
$view->setAppIconFromPHID($handle->getPHID());
$xaction_phids = $this->getValue('transactionPHIDs');
- $xaction = $this->getObject(head($xaction_phids));
+ $xaction = $this->getPrimaryTransaction();
$xaction->setHandles($this->getHandles());
$view->setTitle($xaction->getTitleForFeed($this));
- $body = $xaction->getBodyForFeed($this);
- if (nonempty($body)) {
- $view->appendChild($body);
+
+ foreach ($xaction_phids as $xaction_phid) {
+ $secondary_xaction = $this->getObject($xaction_phid);
+ $secondary_xaction->setHandles($this->getHandles());
+
+ $body = $secondary_xaction->getBodyForFeed($this);
+ if (nonempty($body)) {
+ $view->appendChild($body);
+ }
}
$view->setImage(
- $this->getHandle(
- $this->getPrimaryTransaction()->getAuthorPHID())->getImageURI());
+ $this->getHandle($xaction->getAuthorPHID())->getImageURI());
return $view;
}
diff --git a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
--- a/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
+++ b/src/infrastructure/storage/patch/PhabricatorBuiltinPatchList.php
@@ -1720,6 +1720,10 @@
'type' => 'sql',
'name' => $this->getPatchPath('20131031.vcspassword.sql'),
),
+ '20131105.buildstep.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20131105.buildstep.sql'),
+ ),
);
}
}
diff --git a/webroot/rsrc/css/phui/phui-workboard-view.css b/webroot/rsrc/css/phui/phui-workboard-view.css
--- a/webroot/rsrc/css/phui/phui-workboard-view.css
+++ b/webroot/rsrc/css/phui/phui-workboard-view.css
@@ -15,6 +15,15 @@
box-shadow: inset 0 0 5px rgba(0,0,0,.5);
}
+.phui-workboard-view-shadow::-webkit-scrollbar {
+ height: 12px;
+}
+
+.phui-workboard-view-shadow::-webkit-scrollbar-thumb {
+ background: {$lightbluetext};
+ border-radius: 10px;
+}
+
.phui-workboard-action-list {
width: 60px;
float: left;

File Metadata

Mime Type
text/x-diff
Storage Engine
amazon-s3
Storage Format
Raw Data
Storage Handle
phabricator/hb/ev/2kpjxejzkddchkkf
Default Alt Text
D7499.id16923.diff (52 KB)

Event Timeline