1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-09 16:32:39 +01:00

Harbormaster v(-2)

Summary:
Ref T1049. I don't really want to sink too much time into this right now, but a seemingly reasonable architecture came to me in a dream. Here's a high-level overview of how things fit together:

  - **"Build"**: In Harbormaster, "build" means any process we want to run against a working copy. It might actually be building an executable, but it might also be running lint, running unit tests, generating documentation, generating symbols, running a deploy, setting up a sandcastle, etc.
  - `HarbormasterBuildable`: A "buildable" is some piece of code which build operations can run on. Generally, this is either a Differential diff or a Diffusion commit. The Buildable class just wraps those objects and provides a layer of abstraction. Currently, you can manually create a buildable from a commit. In the future, this will be done automatically.
  - `HarbormasterBuildStep`: A "build step" is an individual build operation, like "run lint", "run unit", "build docs", etc. The step defines how to perform the operation (for example, "run unit tests by executing 'arc unit'"). In this diff, this barely exists.
  - `HarbormasterBuildPlan`: This glues together build steps into groups or sequences. For example, you might want to "run unit", and then "deploy" if the tests pass. You can create a build plan which says "run step "unit tests", then run step "deploy" on success" or whatever. In the future, these will also contain triggers/conditions ("Automatically run this build plan against every commit") and probably be able to define failure actions ("If this plan fails, send someone an email"). Because build plans will run commands, only administrators can manage them.
  - `HarbormasterBuild`: This is the concrete result of running a `BuildPlan` against a `Buildable`. It tracks the build status and collects results, so you can see if the build is running/successful/failed. A `Buildable` may have several `Build`s, because you can execute more than one `BuildPlan` against it. For example, you might have a "documentation" build plan which you run continuously against HEAD, but a "unit" build plan which you want to run against every commit.
  - `HarbormasterBuildTarget`: This is the concrete result of running a `BuildStep` against a `Buildable`. These are children of `Build`. A step might be able to produce multiple targets, but generally this is something like "Unit Tests" or "Lint" and has an overall status, so you can see at a glance that unit tests were fine but lint had some issues.
  - `HarbormasterBuildItem`: An optional subitem for a target. For lint, this might be an individual file. For unit tests, an individual test. For normal builds, an executable. For deploys, a server. For documentation generation, there might just not be subitems.
  - `HarbormasterBuildLog`: Provides extra information, like command/execution transcripts. This is where stdout/stderr will get dumped, and general details and other messages.
  - `HarbormasterBuildArtifact`: Stores side effects or results from build steps. For example, something which builds a binary might put the binary in "Files" and then put its PHID here. Unit tests might put coverage information here. Generally, any build step which produces some high-level output object can use this table to record its existence.

This diff implements almost nothing and does nothing useful, but puts most of these object relationships in place. The two major things you can't easily do with these objects are:

  1) Run arbitrary cron jobs. Jenkins does this, but it feels tacked on and I don't know of anyone using it for that. We could create fake Buildables to get a similar effect, but if we need to do this I'd rather do it elsewhere in general. Build and cron/service/monitoring feel like pretty different problems to me.
  2) Run parameterized/matrix steps (maybe?). Bamboo has this plan/stage/task/job breakdown where a build step can generate a zillion actual jobs, like "build client on x86", "build server on x86", "build client on ARM", "build server on ARM", etc. We can sort of do this by having a Step map to multiple Targets, but I haven't really thought about it too much and it may end up being not-great. I'd guess we have like an 80% chance of getting a clean implementation if/when we get there. I suspect no one actually needs this, or when they do they'll just implement a custom Step and it can be parameterized at that level. I'm not too worried about this overall.

The major difference between this and Jenkins/Bamboo/TravisCI is that all three of those are **plan-centric**: the primary object in the system is a build plan, and the dashboard shows you all your build plans and the current status. I don't think this is the right model. One disadvantage is that you basically end up with top-level messaging that says "Trunk is broken", not "Trunk was broken by commit af32f392f". Harbormaster is **buildable-centric**: the primary object in the system is stuff you can run build operations against (commits/branches/revisions), and actual build plans are secondary. The main view will be "recent commits on this branch, and whether they're good or not" -- which I think is what's most important in a larger/more complex product -- not the pass/fail status of all jobs. This also makes it easier and more natural to integrate with Differential and Diffusion, which both care about the overall status of the commit/revision, not the current status of jobs.

Test Plan: Poked around, but this doesn't really do anything yet.

Reviewers: btrahan

Reviewed By: btrahan

CC: zeeg, chad, aran, seporaitis

Maniphest Tasks: T1049

Differential Revision: https://secure.phabricator.com/D7368
This commit is contained in:
epriestley 2013-10-22 15:01:06 -07:00
parent fd27538e89
commit b5a009337f
43 changed files with 2619 additions and 1 deletions

View file

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

View file

@ -619,8 +619,46 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'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 @@ phutil_register_library_map(array(
'PhabricatorApplicationFeed' => 'PhabricatorApplication',
'PhabricatorApplicationFiles' => 'PhabricatorApplication',
'PhabricatorApplicationFlags' => 'PhabricatorApplication',
'PhabricatorApplicationHarbormaster' => 'PhabricatorApplication',
'PhabricatorApplicationHerald' => 'PhabricatorApplication',
'PhabricatorApplicationLaunchView' => 'AphrontView',
'PhabricatorApplicationLegalpad' => 'PhabricatorApplication',

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,10 @@
<?php
final class HarbormasterBuildPlanTransactionQuery
extends PhabricatorApplicationTransactionQuery {
public function getTemplateApplicationTransaction() {
return new HarbormasterBuildPlanTransaction();
}
}

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -0,0 +1,7 @@
<?php
final class HarbormasterBuildLog extends HarbormasterDAO {
protected $buildItemPHID;
}

View file

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

View file

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

View file

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

View file

@ -0,0 +1,10 @@
<?php
final class HarbormasterBuildPlanTransactionComment
extends PhabricatorApplicationTransactionComment {
public function getApplicationTransactionObject() {
return new HarbormasterBuildPlan();
}
}

View file

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

View file

@ -43,7 +43,7 @@ final class PhabricatorPolicyExplainController
->setViewer($viewer)
->withPHIDs(array($phid))
->executeOne();
$object_uri = $handle->getURI();
$object_uri = nonempty($handle->getURI(), '/');
$explanation = PhabricatorPolicy::getPolicyExplanation(
$viewer,

View file

@ -1700,6 +1700,10 @@ final class PhabricatorBuiltinPatchList extends PhabricatorSQLPatchList {
'type' => 'php',
'name' => $this->getPatchPath('20131020.pxactionmig.php'),
),
'20131020.harbormaster.sql' => array(
'type' => 'sql',
'name' => $this->getPatchPath('20131020.harbormaster.sql'),
),
);
}
}