Page MenuHomePhabricator

D7368.diff

diff --git a/resources/sql/patches/20131020.harbormaster.sql b/resources/sql/patches/20131020.harbormaster.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/patches/20131020.harbormaster.sql
@@ -0,0 +1,74 @@
+CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildable (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ buildablePHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ containerPHID VARCHAR(64) COLLATE utf8_bin,
+ buildStatus VARCHAR(32) NOT NULL COLLATE utf8_bin,
+ buildableStatus VARCHAR(32) NOT NULL COLLATE utf8_bin,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ KEY `key_buildable` (buildablePHID),
+ KEY `key_container` (containerPHID),
+ UNIQUE KEY `key_phid` (phid)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
+
+CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildartifact (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ buildablePHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ artifactType VARCHAR(32) NOT NULL COLLATE utf8_bin,
+ artifactIndex VARCHAR(12) NOT NULL COLLATE utf8_bin,
+ artifactKey VARCHAR(255) NOT NULL COLLATE utf8_bin,
+ artifactData LONGTEXT NOT NULL COLLATE utf8_bin,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY `key_artifact` (buildablePHID, artifactType, artifactIndex),
+ UNIQUE KEY `key_artifact_type` (artifactType, artifactIndex),
+ KEY `key_garbagecollect` (artifactType, dateCreated)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
+
+CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplan (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ name VARCHAR(255) NOT NULL,
+ planStatus VARCHAR(32) NOT NULL COLLATE utf8_bin,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY `key_phid` (phid),
+ KEY `key_status` (planStatus)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
+
+CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_buildplantransaction (
+ id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ authorPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ objectPHID VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ viewPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ editPolicy VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ commentPHID VARCHAR(64) COLLATE utf8_bin,
+ commentVersion INT UNSIGNED NOT NULL,
+ transactionType VARCHAR(32) NOT NULL COLLATE utf8_bin,
+ oldValue LONGTEXT NOT NULL COLLATE utf8_bin,
+ newValue LONGTEXT NOT NULL COLLATE utf8_bin,
+ contentSource LONGTEXT NOT NULL COLLATE utf8_bin,
+ metadata LONGTEXT NOT NULL COLLATE utf8_bin,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+
+ UNIQUE KEY `key_phid` (phid),
+ KEY `key_object` (objectPHID)
+
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
+
+CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_build (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARCHAR(64) NOT NULL COLLATE utf8_bin,
+ buildablePHID varchar(64) NOT NULL COLLATE utf8_bin,
+ buildPlanPHID varchar(64) NOT NULL COLLATE utf8_bin,
+ buildStatus VARCHAR(32) NOT NULL COLLATE utf8_bin,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+ UNIQUE KEY `key_phid` (phid),
+ KEY `key_buildable` (buildablePHID),
+ KEY `key_plan` (buildPlanPHID),
+ KEY `key_status` (buildStatus)
+) ENGINE=InnoDB, COLLATE utf8_general_ci;
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
@@ -619,8 +619,46 @@
'FileCreateMailReceiver' => 'applications/files/mail/FileCreateMailReceiver.php',
'FileMailReceiver' => 'applications/files/mail/FileMailReceiver.php',
'FileReplyHandler' => 'applications/files/mail/FileReplyHandler.php',
+ 'HarbormasterBuild' => 'applications/harbormaster/storage/build/HarbormasterBuild.php',
+ 'HarbormasterBuildArtifact' => 'applications/harbormaster/storage/build/HarbormasterBuildArtifact.php',
+ 'HarbormasterBuildItem' => 'applications/harbormaster/storage/build/HarbormasterBuildItem.php',
+ 'HarbormasterBuildItemQuery' => 'applications/harbormaster/query/HarbormasterBuildItemQuery.php',
+ 'HarbormasterBuildLog' => 'applications/harbormaster/storage/build/HarbormasterBuildLog.php',
+ 'HarbormasterBuildPlan' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php',
+ 'HarbormasterBuildPlanEditor' => 'applications/harbormaster/editor/HarbormasterBuildPlanEditor.php',
+ 'HarbormasterBuildPlanQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanQuery.php',
+ 'HarbormasterBuildPlanSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php',
+ 'HarbormasterBuildPlanTransaction' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php',
+ 'HarbormasterBuildPlanTransactionComment' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php',
+ 'HarbormasterBuildPlanTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php',
+ 'HarbormasterBuildQuery' => 'applications/harbormaster/query/HarbormasterBuildQuery.php',
+ 'HarbormasterBuildStep' => 'applications/harbormaster/storage/configuration/HarbormasterBuildStep.php',
+ 'HarbormasterBuildStepQuery' => 'applications/harbormaster/query/HarbormasterBuildStepQuery.php',
+ 'HarbormasterBuildTarget' => 'applications/harbormaster/storage/build/HarbormasterBuildTarget.php',
+ 'HarbormasterBuildTargetQuery' => 'applications/harbormaster/query/HarbormasterBuildTargetQuery.php',
+ 'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php',
+ 'HarbormasterBuildableArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php',
+ 'HarbormasterBuildableEditController' => 'applications/harbormaster/controller/HarbormasterBuildableEditController.php',
+ 'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php',
+ 'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php',
+ 'HarbormasterBuildableSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildableSearchEngine.php',
+ 'HarbormasterBuildableViewController' => 'applications/harbormaster/controller/HarbormasterBuildableViewController.php',
+ 'HarbormasterCapabilityManagePlans' => 'applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php',
+ 'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php',
'HarbormasterDAO' => 'applications/harbormaster/storage/HarbormasterDAO.php',
'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php',
+ 'HarbormasterPHIDTypeBuild' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php',
+ 'HarbormasterPHIDTypeBuildItem' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php',
+ 'HarbormasterPHIDTypeBuildPlan' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php',
+ 'HarbormasterPHIDTypeBuildStep' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php',
+ 'HarbormasterPHIDTypeBuildTarget' => 'applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php',
+ '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',
@@ -850,6 +888,7 @@
'PhabricatorApplicationFeed' => 'applications/feed/application/PhabricatorApplicationFeed.php',
'PhabricatorApplicationFiles' => 'applications/files/application/PhabricatorApplicationFiles.php',
'PhabricatorApplicationFlags' => 'applications/flag/application/PhabricatorApplicationFlags.php',
+ 'PhabricatorApplicationHarbormaster' => 'applications/harbormaster/application/PhabricatorApplicationHarbormaster.php',
'PhabricatorApplicationHerald' => 'applications/herald/application/PhabricatorApplicationHerald.php',
'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php',
'PhabricatorApplicationLegalpad' => 'applications/legalpad/application/PhabricatorApplicationLegalpad.php',
@@ -2752,8 +2791,71 @@
'FileCreateMailReceiver' => 'PhabricatorMailReceiver',
'FileMailReceiver' => 'PhabricatorObjectMailReceiver',
'FileReplyHandler' => 'PhabricatorMailReplyHandler',
+ 'HarbormasterBuild' =>
+ array(
+ 0 => 'HarbormasterDAO',
+ 1 => 'PhabricatorPolicyInterface',
+ ),
+ 'HarbormasterBuildArtifact' =>
+ array(
+ 0 => 'HarbormasterDAO',
+ 1 => 'PhabricatorPolicyInterface',
+ ),
+ 'HarbormasterBuildItem' => 'HarbormasterDAO',
+ 'HarbormasterBuildItemQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'HarbormasterBuildLog' => 'HarbormasterDAO',
+ 'HarbormasterBuildPlan' =>
+ array(
+ 0 => 'HarbormasterDAO',
+ 1 => 'PhabricatorPolicyInterface',
+ 2 => 'PhabricatorSubscribableInterface',
+ ),
+ 'HarbormasterBuildPlanEditor' => 'PhabricatorApplicationTransactionEditor',
+ 'HarbormasterBuildPlanQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'HarbormasterBuildPlanSearchEngine' => 'PhabricatorApplicationSearchEngine',
+ 'HarbormasterBuildPlanTransaction' => 'PhabricatorApplicationTransaction',
+ 'HarbormasterBuildPlanTransactionComment' => 'PhabricatorApplicationTransactionComment',
+ 'HarbormasterBuildPlanTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+ 'HarbormasterBuildQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'HarbormasterBuildStep' => 'HarbormasterDAO',
+ 'HarbormasterBuildStepQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'HarbormasterBuildTarget' => 'HarbormasterDAO',
+ 'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'HarbormasterBuildable' =>
+ array(
+ 0 => 'HarbormasterDAO',
+ 1 => 'PhabricatorPolicyInterface',
+ ),
+ 'HarbormasterBuildableArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'HarbormasterBuildableEditController' => 'HarbormasterController',
+ 'HarbormasterBuildableListController' =>
+ array(
+ 0 => 'HarbormasterController',
+ 1 => 'PhabricatorApplicationSearchResultsControllerInterface',
+ ),
+ 'HarbormasterBuildableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'HarbormasterBuildableSearchEngine' => 'PhabricatorApplicationSearchEngine',
+ 'HarbormasterBuildableViewController' => 'HarbormasterController',
+ 'HarbormasterCapabilityManagePlans' => 'PhabricatorPolicyCapability',
+ 'HarbormasterController' => 'PhabricatorController',
'HarbormasterDAO' => 'PhabricatorLiskDAO',
'HarbormasterObject' => 'HarbormasterDAO',
+ 'HarbormasterPHIDTypeBuild' => 'PhabricatorPHIDType',
+ 'HarbormasterPHIDTypeBuildItem' => 'PhabricatorPHIDType',
+ 'HarbormasterPHIDTypeBuildPlan' => 'PhabricatorPHIDType',
+ 'HarbormasterPHIDTypeBuildStep' => 'PhabricatorPHIDType',
+ 'HarbormasterPHIDTypeBuildTarget' => 'PhabricatorPHIDType',
+ 'HarbormasterPHIDTypeBuildable' => 'PhabricatorPHIDType',
+ 'HarbormasterPlanController' => 'PhabricatorController',
+ 'HarbormasterPlanEditController' => 'HarbormasterPlanController',
+ 'HarbormasterPlanExecuteController' => 'HarbormasterPlanController',
+ 'HarbormasterPlanListController' =>
+ array(
+ 0 => 'HarbormasterPlanController',
+ 1 => 'PhabricatorApplicationSearchResultsControllerInterface',
+ ),
+ 'HarbormasterPlanViewController' => 'HarbormasterPlanController',
+ 'HarbormasterRemarkupRule' => 'PhabricatorRemarkupRuleObject',
'HarbormasterRunnerWorker' => 'PhabricatorWorker',
'HarbormasterScratchTable' => 'HarbormasterDAO',
'HeraldAction' => 'HeraldDAO',
@@ -3008,6 +3110,7 @@
'PhabricatorApplicationFeed' => 'PhabricatorApplication',
'PhabricatorApplicationFiles' => 'PhabricatorApplication',
'PhabricatorApplicationFlags' => 'PhabricatorApplication',
+ 'PhabricatorApplicationHarbormaster' => 'PhabricatorApplication',
'PhabricatorApplicationHerald' => 'PhabricatorApplication',
'PhabricatorApplicationLaunchView' => 'AphrontView',
'PhabricatorApplicationLegalpad' => 'PhabricatorApplication',
diff --git a/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php b/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/application/PhabricatorApplicationHarbormaster.php
@@ -0,0 +1,68 @@
+<?php
+
+final class PhabricatorApplicationHarbormaster extends PhabricatorApplication {
+
+ public function getBaseURI() {
+ return '/harbormaster/';
+ }
+
+ public function getShortDescription() {
+ return pht('Continuous Build');
+ }
+
+ public function getIconName() {
+ return 'harbormaster';
+ }
+
+ public function getTitleGlyph() {
+ return "\xE2\x99\xBB";
+ }
+
+ public function getFlavorText() {
+ return pht('Ship Some Freight');
+ }
+
+ public function getApplicationGroup() {
+ return self::GROUP_UTILITIES;
+ }
+
+ public function isBeta() {
+ return true;
+ }
+
+ public function getRemarkupRules() {
+ return array(
+ new HarbormasterRemarkupRule(),
+ );
+ }
+
+ public function getRoutes() {
+ return array(
+ '/B(?P<id>[1-9]\d*)' => 'HarbormasterBuildableViewController',
+ '/harbormaster/' => array(
+ '(?:query/(?P<queryKey>[^/]+)/)?'
+ => 'HarbormasterBuildableListController',
+ 'buildable/' => array(
+ 'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterBuildableEditController',
+ ),
+ 'plan/' => array(
+ '(?:query/(?P<queryKey>[^/]+)/)?'
+ => 'HarbormasterPlanListController',
+ 'edit/(?:(?P<id>\d+)/)?' => 'HarbormasterPlanEditController',
+ '(?P<id>\d+)/' => 'HarbormasterPlanViewController',
+ 'execute/(?P<id>\d+)/' => 'HarbormasterPlanExecuteController',
+ ),
+ ),
+ );
+ }
+
+ public function getCustomCapabilities() {
+ return array(
+ HarbormasterCapabilityManagePlans::CAPABILITY => array(
+ 'caption' => pht('Can create and manage build plans.'),
+ 'default' => PhabricatorPolicies::POLICY_ADMIN,
+ ),
+ );
+ }
+
+}
diff --git a/src/applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php b/src/applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/capability/HarbormasterCapabilityManagePlans.php
@@ -0,0 +1,21 @@
+<?php
+
+final class HarbormasterCapabilityManagePlans
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'harbormaster.plans';
+
+ public function getCapabilityKey() {
+ return self::CAPABILITY;
+ }
+
+ public function getCapabilityName() {
+ return pht('Can Manage Build Plans');
+ }
+
+ public function describeCapabilityRejection() {
+ return pht(
+ 'You do not have permission to manage Harbormaster build plans.');
+ }
+
+}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableEditController.php b/src/applications/harbormaster/controller/HarbormasterBuildableEditController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/controller/HarbormasterBuildableEditController.php
@@ -0,0 +1,144 @@
+<?php
+
+final class HarbormasterBuildableEditController
+ 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);
+
+ if ($this->id) {
+ $buildable = id(new HarbormasterBuildableQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->executeOne();
+ if (!$buildable) {
+ return new Aphront404Response();
+ }
+ } else {
+ $buildable = HarbormasterBuildable::initializeNewBuildable($viewer);
+ }
+
+ $e_name = true;
+ $v_name = null;
+
+ $errors = array();
+ if ($request->isFormPost()) {
+ $v_name = $request->getStr('buildablePHID');
+
+ if ($v_name) {
+ $object = id(new PhabricatorObjectQuery())
+ ->setViewer($viewer)
+ ->withNames(array($v_name))
+ ->executeOne();
+
+ if ($object instanceof DifferentialRevision) {
+ throw new Exception(
+ "TODO: We need to assign PHIDs to diffs before this will work.");
+ } else if ($object instanceof PhabricatorRepositoryCommit) {
+ $buildable
+ ->setBuildablePHID($object->getPHID())
+ ->setContainerPHID($object->getRepository()->getPHID());
+ } else {
+ $e_name = pht('Invalid');
+ $errors[] = pht('Enter the name of a revision or commit.');
+ }
+ } else {
+ $e_name = pht('Required');
+ $errors[] = pht('You must choose a revision or commit to build.');
+ }
+
+ if (!$errors) {
+ $buildable->save();
+
+ $buildable_uri = '/B'.$buildable->getID();
+ return id(new AphrontRedirectResponse())->setURI($buildable_uri);
+ }
+ }
+
+ if ($errors) {
+ $errors = id(new AphrontErrorView())->setErrors($errors);
+ }
+
+ $is_new = (!$buildable->getID());
+ if ($is_new) {
+ $title = pht('New Buildable');
+ $cancel_uri = $this->getApplicationURI();
+ $save_button = pht('Create Buildable');
+ } else {
+ $id = $buildable->getID();
+
+ $title = pht('Edit Buildable');
+ $cancel_uri = "/B{$id}";
+ $save_button = pht('Save Buildable');
+ }
+
+ $form = id(new AphrontFormView())
+ ->setUser($viewer);
+
+ if ($is_new) {
+ $form
+ ->appendRemarkupInstructions(
+ pht(
+ 'Enter the name of a commit or revision, like `rX123456789` '.
+ 'or `D123`.'))
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel('Buildable Name')
+ ->setName('buildablePHID')
+ ->setError($e_name)
+ ->setValue($v_name));
+ } else {
+ $form->appendChild(
+ id(new AphrontFormMarkupControl())
+ ->setLabel(pht('Buildable'))
+ ->setValue($buildable->getBuildableHandle()->renderLink()));
+ }
+
+ $form->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue($save_button)
+ ->addCancelButton($cancel_uri));
+
+ $box = id(new PHUIObjectBoxView())
+ ->setHeaderText($title)
+ ->setFormError($errors)
+ ->setForm($form);
+
+ $crumbs = $this->buildApplicationCrumbs();
+ if ($is_new) {
+ $crumbs->addCrumb(
+ id(new PhabricatorCrumbView())
+ ->setName(pht('New Buildable')));
+ } else {
+ $id = $buildable->getID();
+ $crumbs->addCrumb(
+ id(new PhabricatorCrumbView())
+ ->setName("B{$id}")
+ ->setHref("/B{$id}"));
+ $crumbs->addCrumb(
+ id(new PhabricatorCrumbView())
+ ->setName(pht('Edit')));
+ }
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $box,
+ ),
+ array(
+ 'title' => $title,
+ 'device' => true,
+ ));
+ }
+
+}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableListController.php b/src/applications/harbormaster/controller/HarbormasterBuildableListController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/controller/HarbormasterBuildableListController.php
@@ -0,0 +1,78 @@
+<?php
+
+final class HarbormasterBuildableListController
+ extends HarbormasterController
+ implements PhabricatorApplicationSearchResultsControllerInterface {
+
+ private $queryKey;
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ public function willProcessRequest(array $data) {
+ $this->queryKey = idx($data, 'queryKey');
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $controller = id(new PhabricatorApplicationSearchController($request))
+ ->setQueryKey($this->queryKey)
+ ->setSearchEngine(new HarbormasterBuildableSearchEngine())
+ ->setNavigation($this->buildSideNavView());
+
+ return $this->delegateToController($controller);
+ }
+
+ public function renderResultsList(
+ array $buildables,
+ PhabricatorSavedQuery $query) {
+ assert_instances_of($buildables, 'HarbormasterBuildable');
+
+ $viewer = $this->getRequest()->getUser();
+
+ $list = new PHUIObjectItemListView();
+ foreach ($buildables as $buildable) {
+ $id = $buildable->getID();
+
+ $item = id(new PHUIObjectItemView())
+ ->setHeader(pht('Build %d', $buildable->getID()));
+
+ if ($id) {
+ $item->setHref("/B{$id}");
+ }
+
+ $list->addItem($item);
+ }
+
+ return $list;
+ }
+
+ public function buildSideNavView($for_app = false) {
+ $user = $this->getRequest()->getUser();
+
+ $nav = new AphrontSideNavFilterView();
+ $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
+
+ id(new HarbormasterBuildableSearchEngine())
+ ->setViewer($user)
+ ->addNavigationItems($nav->getMenu());
+
+ if ($for_app) {
+ $nav->addFilter('new/', pht('New Build Plan'));
+ }
+
+ $nav->addLabel('Utilities');
+ $nav->addFilter('buildable/edit/', pht('New Manual Build'));
+ $nav->addFilter('plan/', pht('Manage Build Plans'));
+
+ $nav->selectFilter(null);
+
+ return $nav;
+ }
+
+ public function buildApplicationMenu() {
+ return $this->buildSideNavView(true)->getMenu();
+ }
+
+}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
@@ -0,0 +1,105 @@
+<?php
+
+final class HarbormasterBuildableViewController
+ 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))
+ ->needBuildableHandles(true)
+ ->needContainerObjects(true)
+ ->executeOne();
+ if (!$buildable) {
+ return new Aphront404Response();
+ }
+
+ $builds = id(new HarbormasterBuildQuery())
+ ->setViewer($viewer)
+ ->withBuildablePHIDs(array($buildable->getPHID()))
+ ->needBuildPlans(true)
+ ->execute();
+
+ $build_list = id(new PHUIObjectItemListView())
+ ->setUser($viewer);
+ foreach ($builds as $build) {
+ $item = id(new PHUIObjectItemView())
+ ->setObjectName(pht('Build %d', $build->getID()))
+ ->setHeader($build->getName());
+ $build_list->addItem($item);
+ }
+
+ $title = pht("Buildable %d", $id);
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader($title)
+ ->setUser($viewer)
+ ->setPolicyObject($buildable);
+
+ $box = id(new PHUIObjectBoxView())
+ ->setHeader($header);
+
+ $actions = $this->buildActionList($buildable);
+ $this->buildPropertyLists($box, $buildable, $actions);
+
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addCrumb(
+ id(new PhabricatorCrumbView())
+ ->setName("B{$id}"));
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $box,
+ $build_list,
+ ),
+ array(
+ 'title' => $title,
+ 'device' => true,
+ ));
+ }
+
+ private function buildActionList(HarbormasterBuildable $buildable) {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+ $id = $buildable->getID();
+
+ $list = id(new PhabricatorActionListView())
+ ->setUser($viewer)
+ ->setObject($buildable)
+ ->setObjectURI("/B{$id}");
+
+ return $list;
+ }
+
+ private function buildPropertyLists(
+ PHUIObjectBoxView $box,
+ HarbormasterBuildable $buildable,
+ PhabricatorActionListView $actions) {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $properties = id(new PHUIPropertyListView())
+ ->setUser($viewer)
+ ->setObject($buildable)
+ ->setActionList($actions);
+ $box->addPropertyList($properties);
+
+ $properties->addProperty(
+ pht('Buildable'),
+ $buildable->getBuildableHandle()->renderLink());
+
+ }
+
+}
diff --git a/src/applications/harbormaster/controller/HarbormasterController.php b/src/applications/harbormaster/controller/HarbormasterController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/controller/HarbormasterController.php
@@ -0,0 +1,17 @@
+<?php
+
+abstract class HarbormasterController extends PhabricatorController {
+
+ public function buildApplicationCrumbs() {
+ $crumbs = parent::buildApplicationCrumbs();
+
+ $crumbs->addAction(
+ id(new PHUIListItemView())
+ ->setName(pht('New Build Plan'))
+ ->setHref($this->getApplicationURI('plan/edit/'))
+ ->setIcon('create'));
+
+ return $crumbs;
+ }
+
+}
diff --git a/src/applications/harbormaster/controller/HarbormasterPlanController.php b/src/applications/harbormaster/controller/HarbormasterPlanController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/controller/HarbormasterPlanController.php
@@ -0,0 +1,16 @@
+<?php
+
+abstract class HarbormasterPlanController extends PhabricatorController {
+
+ public function buildApplicationCrumbs() {
+ $crumbs = parent::buildApplicationCrumbs();
+
+ $crumbs->addCrumb(
+ id(new PhabricatorCrumbView())
+ ->setName(pht('Build Plans'))
+ ->setHref($this->getApplicationURI('plan/')));
+
+ return $crumbs;
+ }
+
+}
diff --git a/src/applications/harbormaster/controller/HarbormasterPlanEditController.php b/src/applications/harbormaster/controller/HarbormasterPlanEditController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/controller/HarbormasterPlanEditController.php
@@ -0,0 +1,121 @@
+<?php
+
+final class HarbormasterPlanEditController
+ extends HarbormasterPlanController {
+
+ 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);
+
+ if ($this->id) {
+ $plan = id(new HarbormasterBuildPlanQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($this->id))
+ ->executeOne();
+ if (!$plan) {
+ return new Aphront404Response();
+ }
+ } else {
+ $plan = HarbormasterBuildPlan::initializeNewBuildPlan($viewer);
+ }
+
+ $e_name = true;
+ $v_name = $plan->getName();
+ $validation_exception = null;
+ if ($request->isFormPost()) {
+ $xactions = array();
+
+ $v_name = $request->getStr('name');
+ $e_name = null;
+ $type_name = HarbormasterBuildPlanTransaction::TYPE_NAME;
+
+ $xactions[] = id(new HarbormasterBuildPlanTransaction())
+ ->setTransactionType($type_name)
+ ->setNewValue($v_name);
+
+ $editor = id(new HarbormasterBuildPlanEditor())
+ ->setActor($viewer)
+ ->setContentSourceFromRequest($request);
+
+ try {
+ $editor->applyTransactions($plan, $xactions);
+ return id(new AphrontRedirectResponse())
+ ->setURI($this->getApplicationURI('plan/'.$plan->getID().'/'));
+ } catch (PhabricatorApplicationTransactionValidationException $ex) {
+ $validation_exception = $ex;
+
+ $e_name = $validation_exception->getShortMessage(
+ HarbormasterBuildPlanTransaction::TYPE_NAME);
+ }
+
+ }
+
+ $is_new = (!$plan->getID());
+ if ($is_new) {
+ $title = pht('New Build Plan');
+ $cancel_uri = $this->getApplicationURI();
+ $save_button = pht('Create Build Plan');
+ } else {
+ $id = $plan->getID();
+
+ $title = pht('Edit Build Plan');
+ $cancel_uri = "/B{$id}";
+ $save_button = pht('Save Build Plan');
+ }
+
+ $form = id(new AphrontFormView())
+ ->setUser($viewer)
+ ->appendChild(
+ id(new AphrontFormTextControl())
+ ->setLabel('Plan Name')
+ ->setName('name')
+ ->setError($e_name)
+ ->setValue($v_name));
+
+ $form->appendChild(
+ id(new AphrontFormSubmitControl())
+ ->setValue($save_button)
+ ->addCancelButton($cancel_uri));
+
+ $box = id(new PHUIObjectBoxView())
+ ->setHeaderText($title)
+ ->setValidationException($validation_exception)
+ ->setForm($form);
+
+ $crumbs = $this->buildApplicationCrumbs();
+ if ($is_new) {
+ $crumbs->addCrumb(
+ id(new PhabricatorCrumbView())
+ ->setName(pht('New Build Plan')));
+ } else {
+ $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')));
+ }
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $box,
+ ),
+ array(
+ 'title' => $title,
+ 'device' => true,
+ ));
+ }
+
+}
diff --git a/src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php b/src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/controller/HarbormasterPlanExecuteController.php
@@ -0,0 +1,88 @@
+<?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/HarbormasterPlanListController.php b/src/applications/harbormaster/controller/HarbormasterPlanListController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/controller/HarbormasterPlanListController.php
@@ -0,0 +1,69 @@
+<?php
+
+final class HarbormasterPlanListController
+ extends HarbormasterPlanController
+ implements PhabricatorApplicationSearchResultsControllerInterface {
+
+ private $queryKey;
+
+ public function shouldAllowPublic() {
+ return true;
+ }
+
+ public function willProcessRequest(array $data) {
+ $this->queryKey = idx($data, 'queryKey');
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $controller = id(new PhabricatorApplicationSearchController($request))
+ ->setQueryKey($this->queryKey)
+ ->setSearchEngine(new HarbormasterBuildPlanSearchEngine())
+ ->setNavigation($this->buildSideNavView());
+
+ return $this->delegateToController($controller);
+ }
+
+ public function renderResultsList(
+ array $plans,
+ PhabricatorSavedQuery $query) {
+ assert_instances_of($plans, 'HarbormasterBuildPlan');
+
+ $viewer = $this->getRequest()->getUser();
+
+ $list = new PHUIObjectItemListView();
+ foreach ($plans as $plan) {
+ $id = $plan->getID();
+
+ $item = id(new PHUIObjectItemView())
+ ->setObjectName(pht('Plan %d', $plan->getID()))
+ ->setHeader($plan->getName());
+
+ $item->setHref($this->getApplicationURI("plan/{$id}/"));
+
+ $list->addItem($item);
+ }
+
+ return $list;
+ }
+
+ public function buildSideNavView($for_app = false) {
+ $user = $this->getRequest()->getUser();
+
+ $nav = new AphrontSideNavFilterView();
+ $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
+
+ id(new HarbormasterBuildPlanSearchEngine())
+ ->setViewer($user)
+ ->addNavigationItems($nav->getMenu());
+
+ $nav->selectFilter(null);
+
+ return $nav;
+ }
+
+ public function buildApplicationMenu() {
+ return $this->buildSideNavView(true)->getMenu();
+ }
+
+}
diff --git a/src/applications/harbormaster/controller/HarbormasterPlanViewController.php b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/controller/HarbormasterPlanViewController.php
@@ -0,0 +1,121 @@
+<?php
+
+final class HarbormasterPlanViewController
+ extends HarbormasterPlanController {
+
+ private $id;
+
+ public function willProcessRequest(array $data) {
+ $this->id = $data['id'];
+ }
+
+ public function processRequest() {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $id = $this->id;
+
+ $plan = id(new HarbormasterBuildPlanQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($id))
+ ->executeOne();
+ if (!$plan) {
+ return new Aphront404Response();
+ }
+
+ $xactions = id(new HarbormasterBuildPlanTransactionQuery())
+ ->setViewer($viewer)
+ ->withObjectPHIDs(array($plan->getPHID()))
+ ->execute();
+
+ $engine = id(new PhabricatorMarkupEngine())
+ ->setViewer($viewer);
+
+ $xaction_view = id(new PhabricatorApplicationTransactionView())
+ ->setUser($viewer)
+ ->setObjectPHID($plan->getPHID())
+ ->setTransactions($xactions)
+ ->setMarkupEngine($engine);
+
+ $title = pht("Plan %d", $id);
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader($title)
+ ->setUser($viewer)
+ ->setPolicyObject($plan);
+
+ $box = id(new PHUIObjectBoxView())
+ ->setHeader($header);
+
+ $actions = $this->buildActionList($plan);
+ $this->buildPropertyLists($box, $plan, $actions);
+
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addCrumb(
+ id(new PhabricatorCrumbView())
+ ->setName(pht("Plan %d", $id)));
+
+ return $this->buildApplicationPage(
+ array(
+ $crumbs,
+ $box,
+ $xaction_view,
+ ),
+ array(
+ 'title' => $title,
+ 'device' => true,
+ ));
+ }
+
+ private function buildActionList(HarbormasterBuildPlan $plan) {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+ $id = $plan->getID();
+
+ $list = id(new PhabricatorActionListView())
+ ->setUser($viewer)
+ ->setObject($plan)
+ ->setObjectURI($this->getApplicationURI("plan/{$id}/"));
+
+ $can_edit = $this->hasApplicationCapability(
+ HarbormasterCapabilityManagePlans::CAPABILITY);
+
+ $list->addAction(
+ id(new PhabricatorActionView())
+ ->setName(pht('Edit Plan'))
+ ->setHref($this->getApplicationURI("plan/edit/{$id}/"))
+ ->setWorkflow(!$can_edit)
+ ->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;
+ }
+
+ private function buildPropertyLists(
+ PHUIObjectBoxView $box,
+ HarbormasterBuildPlan $plan,
+ PhabricatorActionListView $actions) {
+ $request = $this->getRequest();
+ $viewer = $request->getUser();
+
+ $properties = id(new PHUIPropertyListView())
+ ->setUser($viewer)
+ ->setObject($plan)
+ ->setActionList($actions);
+ $box->addPropertyList($properties);
+
+ $properties->addProperty(
+ pht('Created'),
+ phabricator_datetime($plan->getDateCreated(), $viewer));
+
+ }
+
+}
diff --git a/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/editor/HarbormasterBuildPlanEditor.php
@@ -0,0 +1,91 @@
+<?php
+
+final class HarbormasterBuildPlanEditor
+ extends PhabricatorApplicationTransactionEditor {
+
+ public function getTransactionTypes() {
+ $types = parent::getTransactionTypes();
+ $types[] = HarbormasterBuildPlanTransaction::TYPE_NAME;
+ $types[] = PhabricatorTransactions::TYPE_COMMENT;
+ return $types;
+ }
+
+ protected function getCustomTransactionOldValue(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+ switch ($xaction->getTransactionType()) {
+ case HarbormasterBuildPlanTransaction::TYPE_NAME:
+ if ($this->getIsNewObject()) {
+ return null;
+ }
+ return $object->getName();
+ }
+
+ return parent::getCustomTransactionOldValue($object, $xaction);
+ }
+
+ protected function getCustomTransactionNewValue(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+ switch ($xaction->getTransactionType()) {
+ case HarbormasterBuildPlanTransaction::TYPE_NAME:
+ return $xaction->getNewValue();
+ }
+ return parent::getCustomTransactionNewValue($object, $xaction);
+ }
+
+ protected function applyCustomInternalTransaction(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+ switch ($xaction->getTransactionType()) {
+ case HarbormasterBuildPlanTransaction::TYPE_NAME:
+ $object->setName($xaction->getNewValue());
+ return;
+ }
+ return parent::applyCustomInternalTransaction($object, $xaction);
+ }
+
+ protected function applyCustomExternalTransaction(
+ PhabricatorLiskDAO $object,
+ PhabricatorApplicationTransaction $xaction) {
+ switch ($xaction->getTransactionType()) {
+ case HarbormasterBuildPlanTransaction::TYPE_NAME:
+ return;
+ }
+ return parent::applyCustomExternalTransaction($object, $xaction);
+ }
+
+ protected function validateTransaction(
+ PhabricatorLiskDAO $object,
+ $type,
+ array $xactions) {
+
+ $errors = parent::validateTransaction($object, $type, $xactions);
+
+ switch ($type) {
+ case HarbormasterBuildPlanTransaction::TYPE_NAME:
+ $missing_name = true;
+ if (strlen($object->getName()) && empty($xactions)) {
+ $missing_name = false;
+ } else if (strlen(last($xactions)->getNewValue())) {
+ $missing_name = false;
+ }
+
+ if ($missing_name) {
+ $error = new PhabricatorApplicationTransactionValidationError(
+ $type,
+ pht('Required'),
+ pht('Plan name is required.'),
+ last($xactions));
+
+ $error->setIsMissingFieldError(true);
+ $errors[] = $error;
+ }
+ break;
+ }
+
+ return $errors;
+ }
+
+
+}
diff --git a/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuild.php
@@ -0,0 +1,37 @@
+<?php
+
+final class HarbormasterPHIDTypeBuild extends PhabricatorPHIDType {
+
+ const TYPECONST = 'HMBD';
+
+ public function getTypeConstant() {
+ return self::TYPECONST;
+ }
+
+ public function getTypeName() {
+ return pht('Build');
+ }
+
+ public function newObject() {
+ return new HarbormasterBuild();
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new HarbormasterBuildQuery())
+ ->withPHIDs($phids);
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) {
+ $build_plan = $objects[$phid];
+ }
+ }
+
+}
diff --git a/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildItem.php
@@ -0,0 +1,37 @@
+<?php
+
+final class HarbormasterPHIDTypeBuildItem extends PhabricatorPHIDType {
+
+ const TYPECONST = 'HMBI';
+
+ public function getTypeConstant() {
+ return self::TYPECONST;
+ }
+
+ public function getTypeName() {
+ return pht('Build Item');
+ }
+
+ public function newObject() {
+ return new HarbormasterBuildItem();
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new HarbormasterBuildItemQuery())
+ ->withPHIDs($phids);
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) {
+ $build_item = $objects[$phid];
+ }
+ }
+
+}
diff --git a/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildPlan.php
@@ -0,0 +1,37 @@
+<?php
+
+final class HarbormasterPHIDTypeBuildPlan extends PhabricatorPHIDType {
+
+ const TYPECONST = 'HMCP';
+
+ public function getTypeConstant() {
+ return self::TYPECONST;
+ }
+
+ public function getTypeName() {
+ return pht('Build Plan');
+ }
+
+ public function newObject() {
+ return new HarbormasterBuildPlan();
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new HarbormasterBuildPlanQuery())
+ ->withPHIDs($phids);
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) {
+ $build_plan = $objects[$phid];
+ }
+ }
+
+}
diff --git a/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildStep.php
@@ -0,0 +1,37 @@
+<?php
+
+final class HarbormasterPHIDTypeBuildStep extends PhabricatorPHIDType {
+
+ const TYPECONST = 'HMCS';
+
+ public function getTypeConstant() {
+ return self::TYPECONST;
+ }
+
+ public function getTypeName() {
+ return pht('Build Step');
+ }
+
+ public function newObject() {
+ return new HarbormasterBuildStep();
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new HarbormasterBuildStepQuery())
+ ->withPHIDs($phids);
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) {
+ $build_step = $objects[$phid];
+ }
+ }
+
+}
diff --git a/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildTarget.php
@@ -0,0 +1,37 @@
+<?php
+
+final class HarbormasterPHIDTypeBuildTarget extends PhabricatorPHIDType {
+
+ const TYPECONST = 'HMBT';
+
+ public function getTypeConstant() {
+ return self::TYPECONST;
+ }
+
+ public function getTypeName() {
+ return pht('Build Target');
+ }
+
+ public function newObject() {
+ return new HarbormasterBuildTarget();
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new HarbormasterBuildTargetQuery())
+ ->withPHIDs($phids);
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) {
+ $build_target = $objects[$phid];
+ }
+ }
+
+}
diff --git a/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/phid/HarbormasterPHIDTypeBuildable.php
@@ -0,0 +1,74 @@
+<?php
+
+final class HarbormasterPHIDTypeBuildable extends PhabricatorPHIDType {
+
+ const TYPECONST = 'HMBB';
+
+ public function getTypeConstant() {
+ return self::TYPECONST;
+ }
+
+ public function getTypeName() {
+ return pht('Buildable');
+ }
+
+ public function newObject() {
+ return new HarbormasterBuildable();
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new HarbormasterBuildableQuery())
+ ->withPHIDs($phids)
+ ->needBuildableHandles(true);
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) {
+ $buildable = $objects[$phid];
+
+ $id = $buildable->getID();
+ $target = $buildable->getBuildableHandle()->getFullName();
+
+ $handle->setURI("/B{$id}");
+ $handle->setName("B{$id}");
+ $handle->setFullName("B{$id}: ".$target);
+ }
+ }
+
+ public function canLoadNamedObject($name) {
+ return preg_match('/^B\d*[1-9]\d*$/i', $name);
+ }
+
+ public function loadNamedObjects(
+ PhabricatorObjectQuery $query,
+ array $names) {
+
+ $id_map = array();
+ foreach ($names as $name) {
+ $id = (int)substr($name, 1);
+ $id_map[$id][] = $name;
+ }
+
+ $objects = id(new HarbormasterBuildableQuery())
+ ->setViewer($query->getViewer())
+ ->withIDs(array_keys($id_map))
+ ->execute();
+
+ $results = array();
+ foreach ($objects as $id => $object) {
+ foreach (idx($id_map, $id, array()) as $name) {
+ $results[$name] = $object;
+ }
+ }
+
+ return $results;
+ }
+
+}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildItemQuery.php b/src/applications/harbormaster/query/HarbormasterBuildItemQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/query/HarbormasterBuildItemQuery.php
@@ -0,0 +1,60 @@
+<?php
+
+final class HarbormasterBuildItemQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ protected function loadPage() {
+ $table = new HarbormasterBuildItem();
+ $conn_r = $table->establishConnection('r');
+
+ $data = queryfx_all(
+ $conn_r,
+ 'SELECT * FROM %T %Q %Q %Q',
+ $table->getTableName(),
+ $this->buildWhereClause($conn_r),
+ $this->buildOrderClause($conn_r),
+ $this->buildLimitClause($conn_r));
+
+ return $table->loadAllFromArray($data);
+ }
+
+ private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+ $where = array();
+
+ if ($this->ids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'phid in (%Ls)',
+ $this->phids);
+ }
+
+ $where[] = $this->buildPagingClause($conn_r);
+
+ return $this->formatWhereClause($where);
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorApplicationHarbormaster';
+ }
+
+}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php b/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/query/HarbormasterBuildPlanQuery.php
@@ -0,0 +1,61 @@
+<?php
+
+final class HarbormasterBuildPlanQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ protected function loadPage() {
+ $table = new HarbormasterBuildPlan();
+ $conn_r = $table->establishConnection('r');
+
+ $data = queryfx_all(
+ $conn_r,
+ 'SELECT * FROM %T %Q %Q %Q',
+ $table->getTableName(),
+ $this->buildWhereClause($conn_r),
+ $this->buildOrderClause($conn_r),
+ $this->buildLimitClause($conn_r));
+
+ return $table->loadAllFromArray($data);
+ }
+
+
+ private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+ $where = array();
+
+ if ($this->ids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'phid IN (%Ls)',
+ $this->phids);
+ }
+
+ $where[] = $this->buildPagingClause($conn_r);
+
+ return $this->formatWhereClause($where);
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorApplicationHarbormaster';
+ }
+
+}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/query/HarbormasterBuildPlanSearchEngine.php
@@ -0,0 +1,49 @@
+<?php
+
+final class HarbormasterBuildPlanSearchEngine
+ extends PhabricatorApplicationSearchEngine {
+
+ public function buildSavedQueryFromRequest(AphrontRequest $request) {
+ $saved = new PhabricatorSavedQuery();
+
+ return $saved;
+ }
+
+ public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
+ $query = id(new HarbormasterBuildPlanQuery());
+
+ return $query;
+ }
+
+ public function buildSearchForm(
+ AphrontFormView $form,
+ PhabricatorSavedQuery $saved_query) {
+
+ }
+
+ protected function getURI($path) {
+ return '/harbormaster/plan/'.$path;
+ }
+
+ public function getBuiltinQueryNames() {
+ $names = array(
+ 'all' => pht('All Plans'),
+ );
+
+ return $names;
+ }
+
+ public function buildSavedQueryFromBuiltin($query_key) {
+
+ $query = $this->newSavedQuery();
+ $query->setQueryKey($query_key);
+
+ switch ($query_key) {
+ case 'all':
+ return $query;
+ }
+
+ return parent::buildSavedQueryFromBuiltin($query_key);
+ }
+
+}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php b/src/applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/query/HarbormasterBuildPlanTransactionQuery.php
@@ -0,0 +1,10 @@
+<?php
+
+final class HarbormasterBuildPlanTransactionQuery
+ extends PhabricatorApplicationTransactionQuery {
+
+ public function getTemplateApplicationTransaction() {
+ return new HarbormasterBuildPlanTransaction();
+ }
+
+}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildQuery.php b/src/applications/harbormaster/query/HarbormasterBuildQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/query/HarbormasterBuildQuery.php
@@ -0,0 +1,141 @@
+<?php
+
+final class HarbormasterBuildQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+ private $buildablePHIDs;
+ private $buildPlanPHIDs;
+
+ private $needBuildPlans;
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ public function withBuildablePHIDs(array $buildable_phids) {
+ $this->buildablePHIDs = $buildable_phids;
+ return $this;
+ }
+
+ public function withBuildPlanPHIDs(array $build_plan_phids) {
+ $this->buildPlanPHIDs = $build_plan_phids;
+ return $this;
+ }
+
+ public function needBuildPlans($need_plans) {
+ $this->needBuildPlans = $need_plans;
+ return $this;
+ }
+
+ protected function loadPage() {
+ $table = new HarbormasterBuild();
+ $conn_r = $table->establishConnection('r');
+
+ $data = queryfx_all(
+ $conn_r,
+ 'SELECT * FROM %T %Q %Q %Q',
+ $table->getTableName(),
+ $this->buildWhereClause($conn_r),
+ $this->buildOrderClause($conn_r),
+ $this->buildLimitClause($conn_r));
+
+ return $table->loadAllFromArray($data);
+ }
+
+ protected function willFilterPage(array $page) {
+ $buildables = array();
+
+ $buildable_phids = array_filter(mpull($page, 'getBuildablePHID'));
+ if ($buildable_phids) {
+ $buildables = id(new PhabricatorObjectQuery())
+ ->setViewer($this->getViewer())
+ ->withPHIDs($buildable_phids)
+ ->setParentQuery($this)
+ ->execute();
+ $buildables = mpull($buildables, null, 'getPHID');
+ }
+
+ foreach ($page as $key => $build) {
+ $buildable_phid = $build->getBuildablePHID();
+ if (empty($buildables[$buildable_phid])) {
+ unset($page[$key]);
+ continue;
+ }
+ $build->attachBuildable($buildables[$buildable_phid]);
+ }
+
+ return $page;
+ }
+
+ protected function didFilterPage(array $page) {
+ if ($this->needBuildPlans) {
+ $plans = array();
+
+ $plan_phids = array_filter(mpull($page, 'getBuildPlanPHID'));
+ if ($plan_phids) {
+ $plans = id(new PhabricatorObjectQuery())
+ ->setViewer($this->getViewer())
+ ->withPHIDs($plan_phids)
+ ->setParentQuery($this)
+ ->execute();
+ $plans = mpull($plans, null, 'getPHID');
+ }
+
+ foreach ($page as $key => $build) {
+ $plan_phid = $build->getBuildPlanPHID();
+ $build->attachBuildPlan(idx($plans, $plan_phid));
+ }
+ }
+
+ return $page;
+ }
+
+ private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+ $where = array();
+
+ if ($this->ids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'phid in (%Ls)',
+ $this->phids);
+ }
+
+ if ($this->buildablePHIDs) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'buildablePHID IN (%Ls)',
+ $this->buildablePHIDs);
+ }
+
+ if ($this->buildPlanPHIDs) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'buildPlanPHID IN (%Ls)',
+ $this->buildPlanPHIDs);
+ }
+
+ $where[] = $this->buildPagingClause($conn_r);
+
+ return $this->formatWhereClause($where);
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorApplicationHarbormaster';
+ }
+
+}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildStepQuery.php b/src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/query/HarbormasterBuildStepQuery.php
@@ -0,0 +1,60 @@
+<?php
+
+final class HarbormasterBuildStepQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ protected function loadPage() {
+ $table = new HarbormasterBuildStep();
+ $conn_r = $table->establishConnection('r');
+
+ $data = queryfx_all(
+ $conn_r,
+ 'SELECT * FROM %T %Q %Q %Q',
+ $table->getTableName(),
+ $this->buildWhereClause($conn_r),
+ $this->buildOrderClause($conn_r),
+ $this->buildLimitClause($conn_r));
+
+ return $table->loadAllFromArray($data);
+ }
+
+ private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+ $where = array();
+
+ if ($this->ids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'phid in (%Ls)',
+ $this->phids);
+ }
+
+ $where[] = $this->buildPagingClause($conn_r);
+
+ return $this->formatWhereClause($where);
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorApplicationHarbormaster';
+ }
+
+}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php b/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/query/HarbormasterBuildTargetQuery.php
@@ -0,0 +1,60 @@
+<?php
+
+final class HarbormasterBuildTargetQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ protected function loadPage() {
+ $table = new HarbormasterBuildTarget();
+ $conn_r = $table->establishConnection('r');
+
+ $data = queryfx_all(
+ $conn_r,
+ 'SELECT * FROM %T %Q %Q %Q',
+ $table->getTableName(),
+ $this->buildWhereClause($conn_r),
+ $this->buildOrderClause($conn_r),
+ $this->buildLimitClause($conn_r));
+
+ return $table->loadAllFromArray($data);
+ }
+
+ private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+ $where = array();
+
+ if ($this->ids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'phid in (%Ls)',
+ $this->phids);
+ }
+
+ $where[] = $this->buildPagingClause($conn_r);
+
+ return $this->formatWhereClause($where);
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorApplicationHarbormaster';
+ }
+
+}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php b/src/applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/query/HarbormasterBuildableArtifactQuery.php
@@ -0,0 +1,116 @@
+<?php
+
+final class HarbormasterBuildableArtifactQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $buildablePHIDs;
+ private $artifactTypes;
+ private $artifactKeys;
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withBuildablePHIDs(array $buildable_phids) {
+ $this->buildablePHIDs = $buildable_phids;
+ return $this;
+ }
+
+ public function withArtifactTypes(array $artifact_types) {
+ $this->artifactTypes = $artifact_types;
+ return $this;
+ }
+
+ public function withArtifactKeys(array $artifact_keys) {
+ $this->artifactKeys = $artifact_keys;
+ return $this;
+ }
+
+ protected function loadPage() {
+ $table = new HarbormasterBuildArtifact();
+ $conn_r = $table->establishConnection('r');
+
+ $data = queryfx_all(
+ $conn_r,
+ 'SELECT * FROM %T %Q %Q %Q',
+ $table->getTableName(),
+ $this->buildWhereClause($conn_r),
+ $this->buildOrderClause($conn_r),
+ $this->buildLimitClause($conn_r));
+
+ return $table->loadAllFromArray($data);
+ }
+
+ protected function willFilterPage(array $page) {
+ $buildables = array();
+
+ $buildable_phids = array_filter(mpull($page, 'getBuildablePHID'));
+ if ($buildable_phids) {
+ $buildables = id(new PhabricatorObjectQuery())
+ ->setViewer($this->getViewer())
+ ->withPHIDs($buildable_phids)
+ ->setParentQuery($this)
+ ->execute();
+ $buildables = mpull($buildables, null, 'getPHID');
+ }
+
+ foreach ($page as $key => $artifact) {
+ $buildable_phid = $artifact->getBuildablePHID();
+ if (empty($buildables[$buildable_phid])) {
+ unset($page[$key]);
+ continue;
+ }
+ $artifact->attachBuildable($buildables[$buildable_phid]);
+ }
+
+ return $page;
+ }
+
+ private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+ $where = array();
+
+ if ($this->ids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->buildablePHIDs) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'buildablePHID IN (%Ls)',
+ $this->buildablePHIDs);
+ }
+
+ if ($this->artifactTypes) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'artifactType in (%Ls)',
+ $this->artifactTypes);
+ }
+
+ if ($this->artifactKeys) {
+ $indexes = array();
+ foreach ($this->artifactKeys as $key) {
+ $indexes[] = PhabricatorHash::digestForIndex($key);
+ }
+
+ $where[] = qsprintf(
+ $conn_r,
+ 'artifactIndex IN (%Ls)',
+ $indexes);
+ }
+
+ $where[] = $this->buildPagingClause($conn_r);
+
+ return $this->formatWhereClause($where);
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorApplicationHarbormaster';
+ }
+
+}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildableQuery.php b/src/applications/harbormaster/query/HarbormasterBuildableQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/query/HarbormasterBuildableQuery.php
@@ -0,0 +1,165 @@
+<?php
+
+final class HarbormasterBuildableQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+ private $buildablePHIDs;
+ private $containerPHIDs;
+
+ private $needContainerObjects;
+ private $needBuildableHandles;
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ public function withBuildablePHIDs(array $buildable_phids) {
+ $this->buildablePHIDs = $buildable_phids;
+ return $this;
+ }
+
+ public function withContainerPHIDs(array $container_phids) {
+ $this->containerPHIDs = $container_phids;
+ return $this;
+ }
+
+ public function needContainerObjects($need) {
+ $this->needContainerObjects = $need;
+ return $this;
+ }
+
+ public function needBuildableHandles($need) {
+ $this->needBuildableHandles = $need;
+ return $this;
+ }
+
+ protected function loadPage() {
+ $table = new HarbormasterBuildable();
+ $conn_r = $table->establishConnection('r');
+
+ $data = queryfx_all(
+ $conn_r,
+ 'SELECT * FROM %T %Q %Q %Q',
+ $table->getTableName(),
+ $this->buildWhereClause($conn_r),
+ $this->buildOrderClause($conn_r),
+ $this->buildLimitClause($conn_r));
+
+ return $table->loadAllFromArray($data);
+ }
+
+ protected function willFilterPage(array $page) {
+ $buildables = array();
+
+ $buildable_phids = array_filter(mpull($page, 'getBuildablePHID'));
+ if ($buildable_phids) {
+ $buildables = id(new PhabricatorObjectQuery())
+ ->setViewer($this->getViewer())
+ ->withPHIDs($buildable_phids)
+ ->setParentQuery($this)
+ ->execute();
+ $buildables = mpull($buildables, null, 'getPHID');
+ }
+
+ foreach ($page as $key => $buildable) {
+ $buildable_phid = $buildable->getBuildablePHID();
+ if (empty($buildables[$buildable_phid])) {
+ unset($page[$key]);
+ continue;
+ }
+ $buildable->attachBuildableObject($buildables[$buildable_phid]);
+ }
+
+ return $page;
+ }
+
+ protected function didFilterPage(array $page) {
+ if ($this->needContainerObjects) {
+ $containers = array();
+
+ $container_phids = array_filter(mpull($page, 'getContainerPHID'));
+ if ($container_phids) {
+ $containers = id(new PhabricatorObjectQuery())
+ ->setViewer($this->getViewer())
+ ->withPHIDs($container_phids)
+ ->setParentQuery($this)
+ ->execute();
+ $containers = mpull($containers, null, 'getPHID');
+ }
+
+ foreach ($page as $key => $buildable) {
+ $container_phid = $buildable->getContainerPHID();
+ $buildable->attachContainerObject(idx($containers, $container_phid));
+ }
+ }
+
+ if ($this->needBuildableHandles) {
+ $handles = array();
+
+ $handle_phids = array_filter(mpull($page, 'getBuildablePHID'));
+ if ($handle_phids) {
+ $handles = id(new PhabricatorHandleQuery())
+ ->setViewer($this->getViewer())
+ ->withPHIDs($handle_phids)
+ ->setParentQuery($this)
+ ->execute();
+ }
+
+ foreach ($page as $key => $buildable) {
+ $handle_phid = $buildable->getBuildablePHID();
+ $buildable->attachBuildableHandle(idx($handles, $handle_phid));
+ }
+ }
+
+ return $page;
+ }
+
+ private function buildWhereClause(AphrontDatabaseConnection $conn_r) {
+ $where = array();
+
+ if ($this->ids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'phid IN (%Ls)',
+ $this->phids);
+ }
+
+ if ($this->buildablePHIDs) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'buildablePHID IN (%Ls)',
+ $this->buildablePHIDs);
+ }
+
+ if ($this->containerPHIDs) {
+ $where[] = qsprintf(
+ $conn_r,
+ 'containerPHID in (%Ls)',
+ $this->containerPHIDs);
+ }
+
+ $where[] = $this->buildPagingClause($conn_r);
+
+ return $this->formatWhereClause($where);
+ }
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorApplicationHarbormaster';
+ }
+
+}
diff --git a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php
@@ -0,0 +1,49 @@
+<?php
+
+final class HarbormasterBuildableSearchEngine
+ extends PhabricatorApplicationSearchEngine {
+
+ public function buildSavedQueryFromRequest(AphrontRequest $request) {
+ $saved = new PhabricatorSavedQuery();
+
+ return $saved;
+ }
+
+ public function buildQueryFromSavedQuery(PhabricatorSavedQuery $saved) {
+ $query = id(new HarbormasterBuildableQuery());
+
+ return $query;
+ }
+
+ public function buildSearchForm(
+ AphrontFormView $form,
+ PhabricatorSavedQuery $saved_query) {
+
+ }
+
+ protected function getURI($path) {
+ return '/harbormaster/'.$path;
+ }
+
+ public function getBuiltinQueryNames() {
+ $names = array(
+ 'all' => pht('All Buildables'),
+ );
+
+ return $names;
+ }
+
+ public function buildSavedQueryFromBuiltin($query_key) {
+
+ $query = $this->newSavedQuery();
+ $query->setQueryKey($query_key);
+
+ switch ($query_key) {
+ case 'all':
+ return $query;
+ }
+
+ return parent::buildSavedQueryFromBuiltin($query_key);
+ }
+
+}
diff --git a/src/applications/harbormaster/remarkup/HarbormasterRemarkupRule.php b/src/applications/harbormaster/remarkup/HarbormasterRemarkupRule.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/remarkup/HarbormasterRemarkupRule.php
@@ -0,0 +1,18 @@
+<?php
+
+final class HarbormasterRemarkupRule
+ extends PhabricatorRemarkupRuleObject {
+
+ protected function getObjectNamePrefix() {
+ return 'B';
+ }
+
+ protected function loadObjects(array $ids) {
+ $viewer = $this->getEngine()->getConfig('viewer');
+ return id(new HarbormasterBuildableQuery())
+ ->setViewer($viewer)
+ ->withIDs($ids)
+ ->execute();
+ }
+
+}
diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php
@@ -0,0 +1,85 @@
+<?php
+
+final class HarbormasterBuildable extends HarbormasterDAO
+ implements PhabricatorPolicyInterface {
+
+ protected $buildablePHID;
+ protected $containerPHID;
+ protected $buildStatus;
+ protected $buildableStatus;
+
+ private $buildableObject = self::ATTACHABLE;
+ private $containerObject = self::ATTACHABLE;
+ private $buildableHandle = self::ATTACHABLE;
+
+ public static function initializeNewBuildable(PhabricatorUser $actor) {
+ return id(new HarbormasterBuildable())
+ ->setBuildStatus('new') // TODO: Define these.
+ ->setBuildableStatus('active'); // TODO: Define these, too.
+ }
+
+ public function getConfiguration() {
+ return array(
+ self::CONFIG_AUX_PHID => true,
+ ) + parent::getConfiguration();
+ }
+
+ public function generatePHID() {
+ return PhabricatorPHID::generateNewPHID(
+ HarbormasterPHIDTypeBuildable::TYPECONST);
+ }
+
+ public function attachBuildableObject($buildable_object) {
+ $this->buildableObject = $buildable_object;
+ return $this;
+ }
+
+ public function getBuildableObject() {
+ return $this->assertAttached($this->buildableObject);
+ }
+
+ public function attachContainerObject($container_object) {
+ $this->containerObject = $container_object;
+ return $this;
+ }
+
+ public function getContainerObject() {
+ return $this->assertAttached($this->containerObject);
+ }
+
+ public function attachBuildableHandle($buildable_handle) {
+ $this->buildableHandle = $buildable_handle;
+ return $this;
+ }
+
+ public function getBuildableHandle() {
+ return $this->assertAttached($this->buildableHandle);
+ }
+
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ );
+ }
+
+ public function getPolicy($capability) {
+ return $this->getBuildableObject()->getPolicy($capability);
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return $this->getBuildableObject()->hasAutomaticCapability(
+ $capability,
+ $viewer);
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return pht(
+ 'Users must be able to see the revision or repository to see a '.
+ 'buildable.');
+ }
+
+}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
@@ -0,0 +1,80 @@
+<?php
+
+final class HarbormasterBuild extends HarbormasterDAO
+ implements PhabricatorPolicyInterface {
+
+ protected $buildablePHID;
+ protected $buildPlanPHID;
+ protected $buildStatus;
+
+ private $buildable = self::ATTACHABLE;
+ private $buildPlan = self::ATTACHABLE;
+
+ public static function initializeNewBuild(PhabricatorUser $actor) {
+ return id(new HarbormasterBuild())
+ ->setBuildStatus('building'); // TODO: Sort this.
+ }
+
+ public function getConfiguration() {
+ return array(
+ self::CONFIG_AUX_PHID => true,
+ ) + parent::getConfiguration();
+ }
+
+ public function generatePHID() {
+ return PhabricatorPHID::generateNewPHID(
+ HarbormasterPHIDTypeBuildPlan::TYPECONST);
+ }
+
+ public function attachBuildable(HarbormasterBuildable $buildable) {
+ $this->buildable = $buildable;
+ return $this;
+ }
+
+ public function getBuildable() {
+ return $this->assertAttached($this->buildable);
+ }
+
+ public function getName() {
+ if ($this->getBuildPlan()) {
+ return $this->getBuildPlan()->getName();
+ }
+ return pht('Build');
+ }
+
+ public function attachBuildPlan(
+ HarbormasterBuildPlan $build_plan = null) {
+ $this->buildPlan = $build_plan;
+ return $this;
+ }
+
+ public function getBuildPlan() {
+ return $this->assertAttached($this->buildPlan);
+ }
+
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ );
+ }
+
+ public function getPolicy($capability) {
+ return $this->getBuildable()->getPolicy($capability);
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return $this->getBuildable()->hasAutomaticCapability(
+ $capability,
+ $viewer);
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return pht(
+ 'Users must be able to see a buildable to view its build plans.');
+ }
+
+}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuildArtifact.php
@@ -0,0 +1,60 @@
+<?php
+
+final class HarbormasterBuildArtifact extends HarbormasterDAO
+ implements PhabricatorPolicyInterface {
+
+ protected $buildablePHID;
+ protected $artifactType;
+ protected $artifactIndex;
+ protected $artifactKey;
+ protected $artifactData = array();
+
+ public function getConfiguration() {
+ return array(
+ self::CONFIG_SERIALIZATION => array(
+ 'artifactData' => self::SERIALIZATION_JSON,
+ ),
+ ) + parent::getConfiguration();
+ }
+
+ public function attachBuildable(HarbormasterBuildable $buildable) {
+ $this->buildable = $buildable;
+ return $this;
+ }
+
+ public function getBuildable() {
+ return $this->assertAttached($this->buildable);
+ }
+
+ public function setArtifactKey($key) {
+ $this->artifactIndex = PhabricatorHash::digestForIndex($key);
+ $this->artifactKey = $key;
+ return $this;
+ }
+
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ );
+ }
+
+ public function getPolicy($capability) {
+ return $this->getBuildable()->getPolicy($capability);
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return $this->getBuildable()->hasAutomaticCapability(
+ $capability,
+ $viewer);
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return pht(
+ 'Users must be able to see a buildable to see its artifacts.');
+ }
+
+}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildItem.php b/src/applications/harbormaster/storage/build/HarbormasterBuildItem.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuildItem.php
@@ -0,0 +1,18 @@
+<?php
+
+final class HarbormasterBuildItem extends HarbormasterDAO {
+
+ protected $name;
+
+ public function getConfiguration() {
+ return array(
+ self::CONFIG_AUX_PHID => true,
+ ) + parent::getConfiguration();
+ }
+
+ public function generatePHID() {
+ return PhabricatorPHID::generateNewPHID(
+ HarbormasterPHIDTypeBuildItem::TYPECONST);
+ }
+
+}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuildLog.php
@@ -0,0 +1,7 @@
+<?php
+
+final class HarbormasterBuildLog extends HarbormasterDAO {
+
+ protected $buildItemPHID;
+
+}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuildTarget.php
@@ -0,0 +1,16 @@
+<?php
+
+final class HarbormasterBuildTarget extends HarbormasterDAO {
+
+ public function getConfiguration() {
+ return array(
+ self::CONFIG_AUX_PHID => true,
+ ) + parent::getConfiguration();
+ }
+
+ public function generatePHID() {
+ return PhabricatorPHID::generateNewPHID(
+ HarbormasterPHIDTypeBuildTarget::TYPECONST);
+ }
+
+}
diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php
@@ -0,0 +1,71 @@
+<?php
+
+final class HarbormasterBuildPlan extends HarbormasterDAO
+ implements
+ PhabricatorPolicyInterface,
+ PhabricatorSubscribableInterface {
+
+ protected $name;
+ protected $planStatus;
+
+ private $buildSteps = self::ATTACHABLE;
+
+ public static function initializeNewBuildPlan(PhabricatorUser $actor) {
+ return id(new HarbormasterBuildPlan())
+ ->setPlanStatus('active'); // TODO: Figure this out.
+ }
+
+ public function getConfiguration() {
+ return array(
+ self::CONFIG_AUX_PHID => true,
+ ) + parent::getConfiguration();
+ }
+
+ public function generatePHID() {
+ return PhabricatorPHID::generateNewPHID(
+ HarbormasterPHIDTypeBuildPlan::TYPECONST);
+ }
+
+ public function attachBuildSteps(array $steps) {
+ assert_instances_of($steps, 'HarbormasterBuildStep');
+ $this->buildSteps = $steps;
+ return $this;
+ }
+
+ public function getBuildSteps() {
+ return $this->assertAttached($this->buildSteps);
+ }
+
+
+/* -( PhabricatorSubscribableInterface )----------------------------------- */
+
+
+ public function isAutomaticallySubscribed($phid) {
+ return false;
+ }
+
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ );
+ }
+
+ public function getPolicy($capability) {
+ switch ($capability) {
+ case PhabricatorPolicyCapability::CAN_VIEW:
+ return PhabricatorPolicies::getMostOpenPolicy();
+ }
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return false;
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return null;
+ }
+}
diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransaction.php
@@ -0,0 +1,74 @@
+<?php
+
+final class HarbormasterBuildPlanTransaction
+ extends PhabricatorApplicationTransaction {
+
+ const TYPE_NAME = 'harbormaster:name';
+
+ public function getApplicationName() {
+ return 'harbormaster';
+ }
+
+ public function getApplicationTransactionType() {
+ return HarbormasterPHIDTypeBuildPlan::TYPECONST;
+ }
+
+ public function getApplicationTransactionCommentObject() {
+ return new HarbormasterBuildPlanTransactionComment();
+ }
+
+ public function getIcon() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ switch ($this->getTransactionType()) {
+ case self::TYPE_NAME:
+ if ($old === null) {
+ return 'create';
+ }
+ break;
+ }
+
+ return parent::getIcon();
+ }
+
+ public function getColor() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ switch ($this->getTransactionType()) {
+ case self::TYPE_NAME:
+ if ($old === null) {
+ return 'green';
+ }
+ break;
+ }
+
+ return parent::getIcon();
+ }
+
+ public function getTitle() {
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+ $author_handle = $this->renderHandleLink($this->getAuthorPHID());
+
+ switch ($this->getTransactionType()) {
+ case self::TYPE_NAME:
+ if ($old === null) {
+ return pht(
+ '%s created this build plan.',
+ $author_handle);
+ } else {
+ return pht(
+ '%s renamed this build plan from "%s" to "%s".',
+ $author_handle,
+ $old,
+ $new);
+ }
+ break;
+ }
+
+ return parent::getTitle();
+ }
+
+}
diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildPlanTransactionComment.php
@@ -0,0 +1,10 @@
+<?php
+
+final class HarbormasterBuildPlanTransactionComment
+ extends PhabricatorApplicationTransactionComment {
+
+ public function getApplicationTransactionObject() {
+ return new HarbormasterBuildPlan();
+ }
+
+}
diff --git a/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php b/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/storage/configuration/HarbormasterBuildStep.php
@@ -0,0 +1,29 @@
+<?php
+
+final class HarbormasterBuildStep extends HarbormasterDAO {
+
+ protected $buildPlanPHID;
+
+ private $buildPlan = self::ATTACHABLE;
+
+ public function getConfiguration() {
+ return array(
+ self::CONFIG_AUX_PHID => true,
+ ) + parent::getConfiguration();
+ }
+
+ public function generatePHID() {
+ return PhabricatorPHID::generateNewPHID(
+ HarbormasterPHIDTypeBuildStep::TYPECONST);
+ }
+
+ public function attachBuildPlan(HarbormasterBuildPlan $plan) {
+ $this->buildPlan = $plan;
+ return $this;
+ }
+
+ public function getBuildPlan() {
+ return $this->assertAttached($this->buildPlan);
+ }
+
+}
diff --git a/src/applications/policy/controller/PhabricatorPolicyExplainController.php b/src/applications/policy/controller/PhabricatorPolicyExplainController.php
--- a/src/applications/policy/controller/PhabricatorPolicyExplainController.php
+++ b/src/applications/policy/controller/PhabricatorPolicyExplainController.php
@@ -43,7 +43,7 @@
->setViewer($viewer)
->withPHIDs(array($phid))
->executeOne();
- $object_uri = $handle->getURI();
+ $object_uri = nonempty($handle->getURI(), '/');
$explanation = PhabricatorPolicy::getPolicyExplanation(
$viewer,
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
@@ -1700,6 +1700,10 @@
'type' => 'php',
'name' => $this->getPatchPath('20131020.pxactionmig.php'),
),
+ '20131020.harbormaster.sql' => array(
+ 'type' => 'sql',
+ 'name' => $this->getPatchPath('20131020.harbormaster.sql'),
+ ),
);
}
}

File Metadata

Mime Type
text/x-diff
Storage Engine
amazon-s3
Storage Format
Raw Data
Storage Handle
phabricator/p4/s7/pvqbikpog7sezh6x
Default Alt Text
D7368.diff (84 KB)

Event Timeline