Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F76186
D7368.diff
All Users
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
84 KB
Referenced Files
None
Subscribers
None
D7368.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D7368: Harbormaster v(-2)
Attached
Detach File
Event Timeline
Log In to Comment