Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F14755530
D16981.id40860.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
74 KB
Referenced Files
None
Subscribers
None
D16981.id40860.diff
View Options
diff --git a/resources/sql/autopatches/20161201.release.1.sql b/resources/sql/autopatches/20161201.release.1.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20161201.release.1.sql
@@ -0,0 +1,58 @@
+CREATE TABLE {$NAMESPACE}_release.release_release (
+ `id` INT unsigned NOT NULL AUTO_INCREMENT PRIMARY KEY ,
+ `phid` VARBINARY(64) NOT NULL,
+ `name` VARCHAR(128) COLLATE {$COLLATE_SORT} NOT NULL,
+ `description` LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ `state` VARCHAR(12) COLLATE {$COLLATE_TEXT} NOT NULL,
+ `mailKey` BINARY(20) NOT NULL,
+ `dateCreated` INT unsigned NOT NULL,
+ `dateModified` INT unsigned NOT NULL,
+ `details` LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ `releaseTemplateKey` VARCHAR(12) COLLATE {$COLLATE_TEXT} NOT NULL,
+ `viewPolicy` VARBINARY(64) NOT NULL,
+ `editPolicy` VARBINARY(64) NOT NULL,
+
+ UNIQUE KEY `key_phid` (`phid`),
+ KEY `key_name` (`name`),
+ KEY `key_state` (`state`)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+
+CREATE TABLE {$NAMESPACE}_release.release_releasetransaction (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ phid VARBINARY(64) NOT NULL,
+ authorPHID VARBINARY(64) NOT NULL,
+ objectPHID VARBINARY(64) NOT NULL,
+ viewPolicy VARBINARY(64) NOT NULL,
+ editPolicy VARBINARY(64) NOT NULL,
+ commentPHID VARBINARY(64) DEFAULT NULL,
+ commentVersion INT UNSIGNED NOT NULL,
+ transactionType VARCHAR(32) COLLATE {$COLLATE_TEXT} NOT NULL,
+ oldValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ newValue LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ contentSource LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ metadata LONGTEXT COLLATE {$COLLATE_TEXT} NOT NULL,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+
+ UNIQUE KEY `key_phid` (`phid`),
+ KEY `key_object` (`objectPHID`)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+
+CREATE TABLE {$NAMESPACE}_release.release_releasetransaction_comment (
+ id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ phid VARBINARY(64) NOT NULL,
+ transactionPHID VARBINARY(64),
+ authorPHID VARBINARY(64) NOT NULL,
+ viewPolicy VARBINARY(64) NOT NULL,
+ editPolicy VARBINARY(64) NOT NULL,
+ commentVersion INT UNSIGNED NOT NULL,
+ content LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
+ contentSource LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
+ isDeleted BOOL NOT NULL,
+ dateCreated INT UNSIGNED NOT NULL,
+ dateModified INT UNSIGNED NOT NULL,
+
+ UNIQUE KEY `key_phid` (phid),
+ UNIQUE KEY `key_version` (transactionPHID, commentVersion)
+
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20161201.release.2.edge.sql b/resources/sql/autopatches/20161201.release.2.edge.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20161201.release.2.edge.sql
@@ -0,0 +1,16 @@
+CREATE TABLE {$NAMESPACE}_release.edge (
+ src VARBINARY(64) NOT NULL,
+ type INT UNSIGNED NOT NULL,
+ dst VARBINARY(64) NOT NULL,
+ dateCreated INT UNSIGNED NOT NULL,
+ seq INT UNSIGNED NOT NULL,
+ dataID INT UNSIGNED,
+ PRIMARY KEY (src, type, dst),
+ KEY `src` (src, type, dateCreated, seq),
+ UNIQUE KEY `key_dst` (dst, type, src)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+
+CREATE TABLE {$NAMESPACE}_release.edgedata (
+ id INT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
+ data LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20161201.release.3.custom.sql b/resources/sql/autopatches/20161201.release.3.custom.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20161201.release.3.custom.sql
@@ -0,0 +1,25 @@
+CREATE TABLE {$NAMESPACE}_release.release_customfieldstorage (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ objectPHID VARBINARY(64) NOT NULL,
+ fieldIndex BINARY(12) NOT NULL,
+ fieldValue LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT},
+ UNIQUE KEY (objectPHID, fieldIndex)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+
+CREATE TABLE {$NAMESPACE}_release.release_customfieldstringindex (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ objectPHID VARBINARY(64) NOT NULL,
+ indexKey BINARY(12) NOT NULL,
+ indexValue LONGTEXT NOT NULL COLLATE {$COLLATE_SORT},
+ KEY `key_join` (objectPHID, indexKey, indexValue(64)),
+ KEY `key_find` (indexKey, indexValue(64))
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
+
+CREATE TABLE {$NAMESPACE}_release.release_customfieldnumericindex (
+ id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
+ objectPHID VARBINARY(64) NOT NULL,
+ indexKey BINARY(12) NOT NULL,
+ indexValue BIGINT NOT NULL,
+ KEY `key_join` (objectPHID, indexKey, indexValue),
+ KEY `key_find` (indexKey, indexValue)
+) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
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
@@ -3500,6 +3500,33 @@
'PhabricatorRedirectController' => 'applications/base/controller/PhabricatorRedirectController.php',
'PhabricatorRefreshCSRFController' => 'applications/auth/controller/PhabricatorRefreshCSRFController.php',
'PhabricatorRegistrationProfile' => 'applications/people/storage/PhabricatorRegistrationProfile.php',
+ 'PhabricatorReleaseApplication' => 'applications/release/application/PhabricatorReleaseApplication.php',
+ 'PhabricatorReleaseDAO' => 'applications/release/storage/PhabricatorReleaseDAO.php',
+ 'PhabricatorReleaseRelease' => 'applications/release/storage/PhabricatorReleaseRelease.php',
+ 'PhabricatorReleaseReleaseCurrentRefTransaction' => 'applications/release/xaction/PhabricatorReleaseReleaseCurrentRefTransaction.php',
+ 'PhabricatorReleaseReleaseCutpointTransaction' => 'applications/release/xaction/PhabricatorReleaseReleaseCutpointTransaction.php',
+ 'PhabricatorReleaseReleaseDatasource' => 'applications/release/typeahead/PhabricatorReleaseReleaseDatasource.php',
+ 'PhabricatorReleaseReleaseDefaultEditCapability' => 'applications/release/capability/PhabricatorReleaseReleaseDefaultEditCapability.php',
+ 'PhabricatorReleaseReleaseDefaultViewCapability' => 'applications/release/capability/PhabricatorReleaseReleaseDefaultViewCapability.php',
+ 'PhabricatorReleaseReleaseDescribeTransaction' => 'applications/release/xaction/PhabricatorReleaseReleaseDescribeTransaction.php',
+ 'PhabricatorReleaseReleaseEditConduitAPIMethod' => 'applications/release/conduit/PhabricatorReleaseReleaseEditConduitAPIMethod.php',
+ 'PhabricatorReleaseReleaseEditEngine' => 'applications/release/editor/PhabricatorReleaseReleaseEditEngine.php',
+ 'PhabricatorReleaseReleaseEditor' => 'applications/release/editor/PhabricatorReleaseReleaseEditor.php',
+ 'PhabricatorReleaseReleaseNameTransaction' => 'applications/release/xaction/PhabricatorReleaseReleaseNameTransaction.php',
+ 'PhabricatorReleaseReleasePHIDType' => 'applications/release/phid/PhabricatorReleaseReleasePHIDType.php',
+ 'PhabricatorReleaseReleaseQuery' => 'applications/release/query/PhabricatorReleaseReleaseQuery.php',
+ 'PhabricatorReleaseReleaseReplyHandler' => 'applications/release/mail/PhabricatorReleaseReleaseReplyHandler.php',
+ 'PhabricatorReleaseReleaseSearchConduitAPIMethod' => 'applications/release/conduit/PhabricatorReleaseReleaseSearchConduitAPIMethod.php',
+ 'PhabricatorReleaseReleaseSearchEngine' => 'applications/release/query/PhabricatorReleaseReleaseSearchEngine.php',
+ 'PhabricatorReleaseReleaseStateTransaction' => 'applications/release/xaction/PhabricatorReleaseReleaseStateTransaction.php',
+ 'PhabricatorReleaseReleaseTemplateTransaction' => 'applications/release/xaction/PhabricatorReleaseReleaseTemplateTransaction.php',
+ 'PhabricatorReleaseReleaseTransaction' => 'applications/release/storage/PhabricatorReleaseReleaseTransaction.php',
+ 'PhabricatorReleaseReleaseTransactionComment' => 'applications/release/storage/PhabricatorReleaseReleaseTransactionComment.php',
+ 'PhabricatorReleaseReleaseTransactionQuery' => 'applications/release/query/PhabricatorReleaseReleaseTransactionQuery.php',
+ 'PhabricatorReleaseReleaseTransactionType' => 'applications/release/xaction/PhabricatorReleaseReleaseTransactionType.php',
+ 'PhabricatorReleaseRemarkupRule' => 'applications/release/remarkup/PhabricatorReleaseRemarkupRule.php',
+ 'PhabricatorReleaseSchemaSpec' => 'applications/release/storage/PhabricatorReleaseSchemaSpec.php',
+ 'PhabricatorReleaseTemplate' => 'applications/release/template/PhabricatorReleaseTemplate.php',
'PhabricatorReleephApplication' => 'applications/releeph/application/PhabricatorReleephApplication.php',
'PhabricatorReleephApplicationConfigOptions' => 'applications/releeph/config/PhabricatorReleephApplicationConfigOptions.php',
'PhabricatorRemarkupControl' => 'view/form/control/PhabricatorRemarkupControl.php',
@@ -4424,6 +4451,13 @@
'ProjectReplyHandler' => 'applications/project/mail/ProjectReplyHandler.php',
'ProjectSearchConduitAPIMethod' => 'applications/project/conduit/ProjectSearchConduitAPIMethod.php',
'QueryFormattingTestCase' => 'infrastructure/storage/__tests__/QueryFormattingTestCase.php',
+ 'ReleaseCustomField' => 'applications/release/customfield/ReleaseCustomField.php',
+ 'ReleaseCustomFieldNumericIndex' => 'applications/release/storage/ReleaseCustomFieldNumericIndex.php',
+ 'ReleaseCustomFieldStorage' => 'applications/release/storage/ReleaseCustomFieldStorage.php',
+ 'ReleaseCustomFieldStringIndex' => 'applications/release/storage/ReleaseCustomFieldStringIndex.php',
+ 'ReleaseReleaseEditController' => 'applications/release/controller/ReleaseReleaseEditController.php',
+ 'ReleaseReleaseListController' => 'applications/release/controller/ReleaseReleaseListController.php',
+ 'ReleaseReleaseViewController' => 'applications/release/controller/ReleaseReleaseViewController.php',
'ReleephAuthorFieldSpecification' => 'applications/releeph/field/specification/ReleephAuthorFieldSpecification.php',
'ReleephBranch' => 'applications/releeph/storage/ReleephBranch.php',
'ReleephBranchAccessController' => 'applications/releeph/controller/branch/ReleephBranchAccessController.php',
@@ -8611,6 +8645,42 @@
'PhabricatorRedirectController' => 'PhabricatorController',
'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController',
'PhabricatorRegistrationProfile' => 'Phobject',
+ 'PhabricatorReleaseApplication' => 'PhabricatorApplication',
+ 'PhabricatorReleaseDAO' => 'PhabricatorLiskDAO',
+ 'PhabricatorReleaseRelease' => array(
+ 'PhabricatorReleaseDAO',
+ 'PhabricatorApplicationTransactionInterface',
+ 'PhabricatorSubscribableInterface',
+ 'PhabricatorFlaggableInterface',
+ 'PhabricatorMentionableInterface',
+ 'PhabricatorConduitResultInterface',
+ 'PhabricatorCustomFieldInterface',
+ 'PhabricatorPolicyInterface',
+ ),
+ 'PhabricatorReleaseReleaseCurrentRefTransaction' => 'PhabricatorReleaseReleaseTransactionType',
+ 'PhabricatorReleaseReleaseCutpointTransaction' => 'PhabricatorReleaseReleaseTransactionType',
+ 'PhabricatorReleaseReleaseDatasource' => 'PhabricatorTypeaheadDatasource',
+ 'PhabricatorReleaseReleaseDefaultEditCapability' => 'PhabricatorPolicyCapability',
+ 'PhabricatorReleaseReleaseDefaultViewCapability' => 'PhabricatorPolicyCapability',
+ 'PhabricatorReleaseReleaseDescribeTransaction' => 'PhabricatorReleaseReleaseTransactionType',
+ 'PhabricatorReleaseReleaseEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
+ 'PhabricatorReleaseReleaseEditEngine' => 'PhabricatorEditEngine',
+ 'PhabricatorReleaseReleaseEditor' => 'PhabricatorApplicationTransactionEditor',
+ 'PhabricatorReleaseReleaseNameTransaction' => 'PhabricatorReleaseReleaseTransactionType',
+ 'PhabricatorReleaseReleasePHIDType' => 'PhabricatorPHIDType',
+ 'PhabricatorReleaseReleaseQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'PhabricatorReleaseReleaseReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
+ 'PhabricatorReleaseReleaseSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
+ 'PhabricatorReleaseReleaseSearchEngine' => 'PhabricatorApplicationSearchEngine',
+ 'PhabricatorReleaseReleaseStateTransaction' => 'PhabricatorReleaseReleaseTransactionType',
+ 'PhabricatorReleaseReleaseTemplateTransaction' => 'PhabricatorReleaseReleaseTransactionType',
+ 'PhabricatorReleaseReleaseTransaction' => 'PhabricatorModularTransaction',
+ 'PhabricatorReleaseReleaseTransactionComment' => 'PhabricatorApplicationTransactionComment',
+ 'PhabricatorReleaseReleaseTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+ 'PhabricatorReleaseReleaseTransactionType' => 'PhabricatorModularTransactionType',
+ 'PhabricatorReleaseRemarkupRule' => 'PhabricatorObjectRemarkupRule',
+ 'PhabricatorReleaseSchemaSpec' => 'PhabricatorConfigSchemaSpec',
+ 'PhabricatorReleaseTemplate' => 'Phobject',
'PhabricatorReleephApplication' => 'PhabricatorApplication',
'PhabricatorReleephApplicationConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorRemarkupControl' => 'AphrontFormTextAreaControl',
@@ -9773,6 +9843,13 @@
'ProjectReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'ProjectSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'QueryFormattingTestCase' => 'PhabricatorTestCase',
+ 'ReleaseCustomField' => 'PhabricatorCustomField',
+ 'ReleaseCustomFieldNumericIndex' => 'PhabricatorCustomFieldNumericIndexStorage',
+ 'ReleaseCustomFieldStorage' => 'PhabricatorCustomFieldStorage',
+ 'ReleaseCustomFieldStringIndex' => 'PhabricatorCustomFieldStringIndexStorage',
+ 'ReleaseReleaseEditController' => 'PhabricatorController',
+ 'ReleaseReleaseListController' => 'PhabricatorController',
+ 'ReleaseReleaseViewController' => 'PhabricatorController',
'ReleephAuthorFieldSpecification' => 'ReleephFieldSpecification',
'ReleephBranch' => array(
'ReleephDAO',
diff --git a/src/applications/release/application/PhabricatorReleaseApplication.php b/src/applications/release/application/PhabricatorReleaseApplication.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/application/PhabricatorReleaseApplication.php
@@ -0,0 +1,66 @@
+<?php
+
+final class PhabricatorReleaseApplication extends PhabricatorApplication {
+
+ public function getName() {
+ return pht('Release');
+ }
+
+ public function getShortDescription() {
+ return pht('Release Code');
+ }
+
+ public function getFlavorText() {
+ return pht('Deliver Software');
+ }
+
+ public function getBaseURI() {
+ return '/release/';
+ }
+
+ public function getIcon() {
+ return 'fa-code-fork';
+ }
+
+ public function getApplicationGroup() {
+ return self::GROUP_UTILITIES;
+ }
+
+ public function isPrototype() {
+ return true;
+ }
+
+ public function getRemarkupRules() {
+ return array(
+ new PhabricatorReleaseRemarkupRule(),
+ );
+ }
+
+ protected function getCustomCapabilities() {
+ return array(
+ // TODO create
+ PhabricatorReleaseReleaseDefaultViewCapability::CAPABILITY => array(
+ 'caption' => pht('Default edit policy for newly created releases.'),
+ 'template' => PhabricatorReleaseReleasePHIDType::TYPECONST,
+ 'default' => PhabricatorPolicies::POLICY_PUBLIC,
+ ),
+ PhabricatorReleaseReleaseDefaultEditCapability::CAPABILITY => array(
+ 'caption' => pht('Default view policy for newly created releases.'),
+ 'template' => PhabricatorReleaseReleasePHIDType::TYPECONST,
+ 'default' => PhabricatorPolicies::POLICY_NOONE,
+ ),
+ );
+ }
+ public function getRoutes() {
+ return array(
+ '/X(?P<id>[1-9]\d*)' => 'ReleaseReleaseViewController',
+ '/release/' => array(
+ $this->getEditRoutePattern('edit/') => 'ReleaseReleaseEditController',
+ '(?:query/(?P<queryKey>[^/]+)/)?' => 'ReleaseReleaseListController',
+ // TODO new release
+
+ ),
+ );
+ }
+
+}
diff --git a/src/applications/release/capability/PhabricatorReleaseReleaseDefaultEditCapability.php b/src/applications/release/capability/PhabricatorReleaseReleaseDefaultEditCapability.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/capability/PhabricatorReleaseReleaseDefaultEditCapability.php
@@ -0,0 +1,12 @@
+<?php
+
+final class PhabricatorReleaseReleaseDefaultEditCapability
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'release.default.edit';
+
+ public function getCapabilityName() {
+ return pht('Default Release Edit Policy');
+ }
+
+}
diff --git a/src/applications/release/capability/PhabricatorReleaseReleaseDefaultViewCapability.php b/src/applications/release/capability/PhabricatorReleaseReleaseDefaultViewCapability.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/capability/PhabricatorReleaseReleaseDefaultViewCapability.php
@@ -0,0 +1,16 @@
+<?php
+
+final class PhabricatorReleaseReleaseDefaultViewCapability
+ extends PhabricatorPolicyCapability {
+
+ const CAPABILITY = 'release.default.view';
+
+ public function getCapabilityName() {
+ return pht('Default Release View Policy');
+ }
+
+ public function shouldAllowPublicPolicySetting() {
+ return true;
+ }
+
+}
diff --git a/src/applications/release/conduit/PhabricatorReleaseReleaseEditConduitAPIMethod.php b/src/applications/release/conduit/PhabricatorReleaseReleaseEditConduitAPIMethod.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/conduit/PhabricatorReleaseReleaseEditConduitAPIMethod.php
@@ -0,0 +1,19 @@
+<?php
+
+final class PhabricatorReleaseReleaseEditConduitAPIMethod
+ extends PhabricatorEditEngineAPIMethod {
+
+ public function getAPIMethodName() {
+ return 'release.edit';
+ }
+
+ public function newEditEngine() {
+ return new PhabricatorReleaseReleaseEditEngine();
+ }
+
+ public function getMethodSummary() {
+ return pht(
+ 'Apply transactions to create a new Release or edit an existing one.');
+ }
+
+}
diff --git a/src/applications/release/conduit/PhabricatorReleaseReleaseSearchConduitAPIMethod.php b/src/applications/release/conduit/PhabricatorReleaseReleaseSearchConduitAPIMethod.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/conduit/PhabricatorReleaseReleaseSearchConduitAPIMethod.php
@@ -0,0 +1,18 @@
+<?php
+
+final class PhabricatorReleaseReleaseSearchConduitAPIMethod
+ extends PhabricatorSearchEngineAPIMethod {
+
+ public function getAPIMethodName() {
+ return 'release.search';
+ }
+
+ public function newSearchEngine() {
+ return new PhabricatorReleaseReleaseSearchEngine();
+ }
+
+ public function getMethodSummary() {
+ return pht('Read information about releases.');
+ }
+
+}
diff --git a/src/applications/release/controller/ReleaseReleaseEditController.php b/src/applications/release/controller/ReleaseReleaseEditController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/controller/ReleaseReleaseEditController.php
@@ -0,0 +1,19 @@
+<?php
+
+final class ReleaseReleaseEditController extends PhabricatorController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+
+ $engine = id(new PhabricatorReleaseReleaseEditEngine())
+ ->setController($this);
+
+ $id = $request->getURIData('id');
+ if (!$id) {
+ // ??
+ }
+
+ return $engine->buildResponse();
+ }
+
+}
diff --git a/src/applications/release/controller/ReleaseReleaseListController.php b/src/applications/release/controller/ReleaseReleaseListController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/controller/ReleaseReleaseListController.php
@@ -0,0 +1,35 @@
+<?php
+
+final class ReleaseReleaseListController extends PhabricatorController {
+
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+
+ $crumbs = $this->buildApplicationCrumbs();
+ $crumbs->addTextCrumb('Releases');
+
+ $querykey = $request->getURIData('queryKey');
+
+ $controller = id(new PhabricatorApplicationSearchController())
+ ->setQueryKey($querykey)
+ ->setSearchEngine(new PhabricatorReleaseReleaseSearchEngine())
+ ->setNavigation($this->buildSideNavView());
+ return $this->delegateToController($controller);
+ }
+
+ public function buildSideNavView() {
+ $viewer = $this->getViewer();
+
+ $nav = new AphrontSideNavFilterView();
+ $nav->setBaseURI(new PhutilURI($this->getApplicationURI()));
+
+ id(new PhabricatorReleaseReleaseSearchEngine())
+ ->setViewer($viewer)
+ ->addNavigationItems($nav->getMenu());
+
+ $nav->selectFilter(null);
+
+ return $nav;
+ }
+
+}
diff --git a/src/applications/release/controller/ReleaseReleaseViewController.php b/src/applications/release/controller/ReleaseReleaseViewController.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/controller/ReleaseReleaseViewController.php
@@ -0,0 +1,205 @@
+<?php
+
+final class ReleaseReleaseViewController extends PhabricatorController {
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $this->getViewer();
+
+ $id = $request->getURIData('id');
+
+ $crumbs = $this->buildApplicationCrumbs();
+
+ $release = id(new PhabricatorReleaseReleaseQuery())
+ ->setViewer($viewer)
+ ->withIds(array($id))
+ ->executeOne();
+ if (!$release) {
+ return new Aphront404Response();
+ }
+
+ $crumbs->addTextCrumb($release->getMonogram());
+ $release_name = $release->getName();
+
+ $properties = id(new PHUIPropertyListView())
+ ->addProperty(pht('Release Type'), $release->getReleaseTemplateName())
+ ->addProperty(pht('Status'), $release->getStateName());
+
+ $field_list = PhabricatorCustomField::getObjectFields(
+ $release,
+ PhabricatorCustomField::ROLE_VIEW);
+ $field_list
+ ->setViewer($viewer)
+ ->readFieldsFromStorage($release);
+
+ $properties->invokeWillRenderEvent();
+
+ $field_list->appendFieldsToPropertyList(
+ $release,
+ $viewer,
+ $properties);
+
+ $description = $release->getDescription();
+ if (strlen($description)) {
+ $properties
+ ->addSectionHeader('Description')
+ ->addTextContent(
+ new PHUIRemarkupView($viewer, $description));
+ }
+
+ $header = id(new PHUIHeaderView())
+ ->setHeader('X'.$release->getID().' '.$release_name)
+ ->setUser($viewer)
+ ->setPolicyObject($release);
+
+ $curtain = $this->buildCurtain($release);
+
+ $timeline = $this->buildTransactionTimeline(
+ $release,
+ new PhabricatorReleaseReleaseTransactionQuery());
+ $timeline->setQuoteRef($release->getMonogram());
+
+ $comment_view = id(new PhabricatorReleaseReleaseEditEngine())
+ ->setViewer($viewer)
+ ->buildEditEngineCommentView($release);
+ $comment_view->setTransactionTimeline($timeline);
+
+ $commits = $this->buildCommitsSection($viewer, $release);
+
+ $view = id(new PHUITwoColumnView())
+ ->setHeader($header)
+ ->setCurtain($curtain)
+ ->setMainColumn(array(
+ $timeline,
+ $comment_view,
+ ))
+ ->addPropertySection(pht('Details'), $properties)
+ ->addPropertySection(pht('Repositories'), $commits);
+
+ return $this->newPage()
+ ->setTitle('X'.$release->getID().' '.$release_name)
+ ->setCrumbs($crumbs)
+ ->setPageObjectPHIDs(
+ array(
+ $release->getPHID(),
+ ))
+ ->appendChild(
+ array(
+ $view,
+ ));
+ }
+
+ private function buildCommitsSection($viewer, $release) {
+ $cutpoints = $release->getCutpoints();
+ $currentrefs = $release->getCurrentRefs();
+
+ $repo_phids = array_keys($cutpoints);
+
+ if (!$repo_phids) {
+ return null;
+ }
+
+ $repositories = id(new PhabricatorRepositoryQuery())
+ ->setViewer($viewer)
+ ->withPHIDs($repo_phids)
+ ->execute();
+ $repositories = mpull($repositories, null, 'getPHID');
+
+ $body = array();
+
+ $commit_phids = id(new DiffusionCommitQuery())
+ ->setViewer($viewer)
+ ->withIdentifiers($cutpoints)
+ ->execute();
+ $commit_phids = mpull($commit_phids, 'getPHID', 'getCommitIdentifier');
+
+
+ $rows = array();
+ foreach ($repositories as $phid => $repo) {
+ $r = $viewer->renderHandle($phid);
+ $cut = idx($cutpoints, $phid);
+
+ $current = idx($currentrefs, $phid);
+
+ $cp = null;
+ if ($current) {
+ if ($cut) {
+ try {
+ list($cp) = $repo->execxLocalCommand(
+ 'rev-list --count %s..%s',
+ $cut,
+ $current);
+
+ if ($cp == 0) {
+ $cp = '--';
+ } else {
+ $cp = hsprintf('%d cherry-picks', $cp);
+ }
+ } catch (Exception $e) {
+ $cp = '??';
+ }
+ }
+
+ if ($release->isRefMutable($current)) {
+ $short = $release->getSimpleRefName($current);
+ $href = hsprintf(
+ '/diffusion/%d/history/%s/',
+ $repo->getID(),
+ $short);
+ $link = phutil_tag('a', array('href' => $href), $short);
+
+ $branch = hsprintf('Release branch: %s', $link);
+ } else {
+ $branch = 'Final commit: '.$repo->formatCommitName($current);
+ }
+
+ } else {
+ $branch = phutil_tag('i', array(), 'No current state');
+ }
+
+ if ($cut) {
+ $phid = idx($commit_phids, $cut);
+ if ($phid) {
+ $cut = $viewer->renderHandle($phid);
+ }
+ } else {
+ $cut = phutil_tag('i', array(), 'No cutpoint specified');
+ }
+
+ $rows[] = array($r, $cut, $cp, $branch);
+ }
+
+ return id(new AphrontTableView($rows))
+ ->setHeaders(array(
+ pht('Repository'),
+ pht('Cut From'),
+ pht('Cherry-picked commits'),
+ pht('Current State'),
+ ))
+ ->setColumnClasses(array(
+ 'link',
+ null,
+ null,
+ 'right',
+ ));
+ }
+
+ private function buildCurtain($release) {
+ $viewer = $this->getViewer();
+ $curtain = $this->newCurtainView($release);
+
+ $id = $release->getID();
+ $edit_uri = $this->getApplicationURI("edit/{$id}/");
+
+ $can_edit = PhabricatorPolicyFilter::hasCapability(
+ $viewer,
+ $release,
+ PhabricatorPolicyCapability::CAN_EDIT);
+
+ $curtain->addAction(id(new PhabricatorActionView())
+ ->setName(pht('Edit Release'))
+ ->setIcon('fa-pencil')
+ ->setHref($edit_uri)
+ ->setDisabled(!$can_edit));
+
+ return $curtain;
+ }
+}
diff --git a/src/applications/release/customfield/ReleaseCustomField.php b/src/applications/release/customfield/ReleaseCustomField.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/customfield/ReleaseCustomField.php
@@ -0,0 +1,17 @@
+<?php
+
+abstract class ReleaseCustomField
+ extends PhabricatorCustomField {
+
+ public function newStorageObject() {
+ return new ReleaseCustomFieldStorage();
+ }
+
+ protected function newStringIndexStorage() {
+ return new ReleaseCustomFieldStringIndex();
+ }
+
+ protected function newNumericIndexStorage() {
+ return new ReleaseCustomFieldNumericIndex();
+ }
+}
diff --git a/src/applications/release/editor/PhabricatorReleaseReleaseEditEngine.php b/src/applications/release/editor/PhabricatorReleaseReleaseEditEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/editor/PhabricatorReleaseReleaseEditEngine.php
@@ -0,0 +1,154 @@
+<?php
+
+final class PhabricatorReleaseReleaseEditEngine
+ extends PhabricatorEditEngine {
+
+ const ENGINECONST = 'release.release';
+
+ public function getEngineName() {
+ return pht('Releases');
+ }
+
+ public function isEngineConfigurable() {
+ // This hides it from the quick-create menu, because the built-in form
+ // doesn't actually do anything.
+ return false;
+ }
+
+ protected function getObjectName() {
+ return pht('Release');
+ }
+
+ public function getSummaryHeader() {
+ return pht('Configure Releases Forms');
+ }
+
+ public function getSummaryText() {
+ return pht('Configure creation and editing Releases.');
+ }
+
+ public function getEngineApplicationClass() {
+ return 'PhabricatorReleaseApplication';
+ }
+
+ protected function newEditableObject() {
+ $viewer = $this->getViewer();
+ return PhabricatorReleaseRelease::initializeNewRelease($viewer);
+ }
+
+ protected function newObjectQuery() {
+ return id(new PhabricatorReleaseReleaseQuery());
+ }
+
+ protected function getObjectCreateTitleText($object) {
+ return pht('Create New Release');
+ }
+
+ protected function getObjectEditTitleText($object) {
+ return pht('Edit Release %s', $object->getName());
+ }
+
+ protected function getEditorURI() {
+ return '/release/edit/';
+ }
+
+ protected function getObjectEditShortText($object) {
+ return $object->getName();
+ }
+
+ protected function getObjectCreateShortText() {
+ return pht('New Release');
+ }
+
+ protected function getCreateNewObjectPolicy() {
+ return $this->getApplication()->getPolicy(
+ PhabricatorReleaseReleaseDefaultEditCapability::CAPABILITY);
+ }
+
+ protected function getCommentViewHeaderText($object) {
+ return pht('Discuss Release');
+ }
+
+ protected function getCommentViewButtonText($object) {
+ return pht('Comment');
+ }
+
+ protected function getObjectViewURI($object) {
+ return $object->getURI();
+ }
+
+ protected function buildCustomEditFields($object) {
+ $templates = PhabricatorReleaseTemplate::getTemplatesMap();
+ $used_template = $object->getReleaseTemplateKey();
+ if (strlen($used_template) && idx($templates, $used_template) == null) {
+ // make sure current value is allowed.
+ $templates[$used_template] = $used_template;
+ }
+
+ $states = PhabricatorReleaseRelease::getStatesMap();
+
+ $is_edit_form = !$this->getIsCreate();
+
+ return array(
+ id(new PhabricatorTextEditField())
+ ->setKey('name')
+ ->setLabel(pht('Name'))
+ ->setIsRequired(true)
+ ->setTransactionType(
+ PhabricatorReleaseReleaseNameTransaction::TRANSACTIONTYPE)
+ ->setDescription(pht('name'))
+ ->setConduitDescription(pht('Rename the Release.'))
+ ->setConduitTypeDescription(pht('New Release title.'))
+ ->setValue($object->getName()),
+ id(new PhabricatorSelectEditField())
+ ->setKey('type')
+ ->setLabel(pht('Release Type'))
+ ->setTransactionType(
+ PhabricatorReleaseReleaseTemplateTransaction::TRANSACTIONTYPE)
+ ->setIsCopyable(true)
+ ->setOptions($templates)
+ ->setIsLocked($is_edit_form)
+ ->setDescription('Libraries / Apps / Manage /...')
+ ->setConduitTypeDescription(pht('Release Type.'))
+ ->setValue($object->getReleaseTemplateKey()),
+ id(new PhabricatorSelectEditField())
+ ->setKey('state')
+ ->setLabel(pht('State'))
+ ->setTransactionType(
+ PhabricatorReleaseReleaseStateTransaction::TRANSACTIONTYPE)
+ ->setIsCopyable(true)
+ ->setOptions($states)
+ ->setValue($object->getState()),
+ id(new PhabricatorRemarkupEditField())
+ ->setKey('description')
+ ->setLabel(pht('Description'))
+ ->setTransactionType(
+ PhabricatorReleaseReleaseDescribeTransaction::TRANSACTIONTYPE)
+ ->setDescription(pht('Free-form text.'))
+ ->setConduitDescription(pht('Description.'))
+ ->setConduitTypeDescription(pht('Remarkup'))
+ ->setValue($object->getDescription()),
+ id(new PhabricatorConduitEditField())
+ ->setKey('cutpoints')
+ ->setLabel(pht('Cut Points'))
+ ->setTransactionType(
+ PhabricatorReleaseReleaseCutpointTransaction::TRANSACTIONTYPE)
+ ->setDescription(pht('Commits where the revision was cut from'))
+ ->setConduitTypeDescription('Map repo phid -> commit')
+ ->setIsConduitOnly(true)
+ ->setValue($object->getDetail(
+ PhabricatorReleaseRelease::DETAIL_CUTPOINTS)),
+ id(new PhabricatorConduitEditField())
+ ->setKey('currentrefs')
+ ->setLabel(pht('Current References'))
+ ->setTransactionType(
+ PhabricatorReleaseReleaseCurrentRefTransaction::TRANSACTIONTYPE)
+ ->setDescription('Commits/branches where the revision currently is')
+ ->setConduitTypeDescription('Map repo phid -> commit/branch')
+ ->setIsConduitOnly(true)
+ ->setValue($object->getDetail(
+ PhabricatorReleaseRelease::DETAIL_CURRENTREF)),
+ );
+ }
+
+}
diff --git a/src/applications/release/editor/PhabricatorReleaseReleaseEditor.php b/src/applications/release/editor/PhabricatorReleaseReleaseEditor.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/editor/PhabricatorReleaseReleaseEditor.php
@@ -0,0 +1,126 @@
+<?php
+
+final class PhabricatorReleaseReleaseEditor
+ extends PhabricatorApplicationTransactionEditor {
+
+ private $modularTypes;
+
+ public function getEditorApplicationClass() {
+ return 'PhabricatorReleaseApplication';
+ }
+
+ public function getEditorObjectsDescription() {
+ return pht('Releases');
+ }
+
+ public function getCreateObjectTitle($author, $object) {
+ return pht('%s created this release.', $author);
+ }
+
+ public function getCreateObjectTitleForFeed($author, $object) {
+ return pht('%s created %s.', $author, $object);
+ }
+
+ protected function applyInitialEffects(
+ PhabricatorLiskDAO $object,
+ array $xactions) {}
+
+ public function getTransactionTypes() {
+ $types = parent::getTransactionTypes();
+
+ $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
+ $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
+ $types[] = PhabricatorTransactions::TYPE_COMMENT;
+
+ return $types;
+ }
+
+ protected function shouldApplyInitialEffects(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+ return true;
+ }
+
+ protected function shouldSendMail(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ return true;
+ }
+
+ protected function getMailSubjectPrefix() {
+ return '[Release]';
+ }
+
+ public function getMailTagsMap() {
+ return array(
+ PhabricatorReleaseReleaseTransaction::MAILTAG_STATE =>
+ pht('A Release state is updated.'),
+ PhabricatorReleaseReleaseTransaction::MAILTAG_OTHER =>
+ pht('Other Release activity not listed above occurs.'),
+ );
+ }
+
+ protected function buildReplyHandler(PhabricatorLiskDAO $object) {
+ return id(new PhabricatorReleaseReleaseReplyHandler())
+ ->setMailReceiver($object);
+ }
+
+
+ protected function buildMailTemplate(PhabricatorLiskDAO $object) {
+ $id = $object->getID();
+ $name = $object->getName();
+
+ return id(new PhabricatorMetaMTAMail())
+ ->setSubject("X{$id} {$name}")
+ ->addHeader('Thread-Topic', "Release {$id}");
+ }
+
+ protected function shouldPublishFeedStory(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ return true;
+ }
+
+ protected function supportsSearch() {
+ return false;
+ }
+
+ protected function expandTransaction(
+ PhabricatorLiskDAO $release,
+ PhabricatorApplicationTransaction $xaction) {
+ $xactions = parent::expandTransaction($release, $xaction);
+
+ // TODO move this to PhabricatorApplicationTransactionEditor, or get rid of.
+ if ($xaction instanceof PhabricatorReleaseReleaseTransaction) {
+ $xactions = array_merge($xactions, $xaction->expandTransaction($release));
+ }
+
+ return $xactions;
+ }
+
+ protected function getMailTo(PhabricatorLiskDAO $object) {
+ $tos = array();
+ return $tos;
+ }
+
+ protected function buildMailBody(
+ PhabricatorLiskDAO $object,
+ array $xactions) {
+
+ $body = parent::buildMailBody($object, $xactions);
+
+ foreach ($xactions as $xaction) {
+ $type = $xaction->getTransactionType();
+ $new = $xaction->getNewValue();
+ }
+
+ $body->addLinkSection(
+ pht('RELEASE DETAIL'),
+ PhabricatorEnv::getProductionURI($object->getURI()));
+
+ return $body;
+ }
+
+}
diff --git a/src/applications/release/mail/PhabricatorReleaseReleaseReplyHandler.php b/src/applications/release/mail/PhabricatorReleaseReleaseReplyHandler.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/mail/PhabricatorReleaseReleaseReplyHandler.php
@@ -0,0 +1,20 @@
+<?php
+
+final class PhabricatorReleaseReleaseReplyHandler
+ extends PhabricatorApplicationTransactionReplyHandler {
+
+ public function validateMailReceiver($mail_receiver) {
+ if (!($mail_receiver instanceof PhabricatorReleaseRelease)) {
+ throw new Exception(
+ pht('Mail receiver is not a %s.', 'PhabricatorReleaseRelease'));
+ }
+ }
+
+ public function getObjectPrefix() {
+ return PhabricatorReleaseReleasePHIDType::TYPECONST;
+ }
+
+ protected function shouldCreateCommentFromMailBody() {
+ return false;
+ }
+}
diff --git a/src/applications/release/phid/PhabricatorReleaseReleasePHIDType.php b/src/applications/release/phid/PhabricatorReleaseReleasePHIDType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/phid/PhabricatorReleaseReleasePHIDType.php
@@ -0,0 +1,81 @@
+<?php
+
+final class PhabricatorReleaseReleasePHIDType extends PhabricatorPHIDType {
+
+ const TYPECONST = 'RELS';
+
+ public function getTypeName() {
+ return pht('Release');
+ }
+
+ public function newObject() {
+ return new PhabricatorReleaseRelease();
+ }
+
+ public function getPHIDTypeApplicationClass() {
+ return 'PhabricatorReleaseApplication';
+ }
+
+ protected function buildQueryForObjects(
+ PhabricatorObjectQuery $query,
+ array $phids) {
+
+ return id(new PhabricatorReleaseReleaseQuery())
+ ->withPHIDs($phids);
+ }
+
+ public function getTypeIcon() {
+ return 'fa-code-fork';
+ }
+
+ public function loadHandles(
+ PhabricatorHandleQuery $query,
+ array $handles,
+ array $objects) {
+
+ foreach ($handles as $phid => $handle) {
+ $release = $objects[$phid];
+
+ $id = $release->getID();
+ $name = $release->getName();
+
+ $handle->setName("X{$id}");
+ $handle->setURI("/X{$id}");
+ $handle->setFullName("X{$id}: {$name}");
+
+ if ($release->isClosed()) {
+ $handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
+ }
+ }
+ }
+
+ public function canLoadNamedObject($name) {
+ return preg_match('/^X[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 PhabricatorReleaseReleaseQuery())
+ ->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/release/query/PhabricatorReleaseReleaseQuery.php b/src/applications/release/query/PhabricatorReleaseReleaseQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/query/PhabricatorReleaseReleaseQuery.php
@@ -0,0 +1,173 @@
+<?php
+
+final class PhabricatorReleaseReleaseQuery
+ extends PhabricatorCursorPagedPolicyAwareQuery {
+
+ private $ids;
+ private $phids;
+ private $names;
+ private $states;
+ private $templates;
+ private $datasourceQuery;
+
+ private $groupBy = 'group-none';
+ const GROUP_NONE = 'group-none';
+ const GROUP_TEMPLATE = 'group-template';
+ const GROUP_STATE = 'group-state';
+
+ public function withIDs(array $ids) {
+ $this->ids = $ids;
+ return $this;
+ }
+
+ public function withPHIDs(array $phids) {
+ $this->phids = $phids;
+ return $this;
+ }
+
+ public function withNames(array $names) {
+ $this->names = $names;
+ return $this;
+ }
+
+ public function withStates(array $states) {
+ $this->states = $states;
+ return $this;
+ }
+
+ public function withTemplateKeys(array $templates) {
+ $this->templates = $templates;
+ return $this;
+ }
+
+ public function withDatasourceQuery($query) {
+ $this->datasourceQuery = $query;
+ return $this;
+ }
+
+ public function newResultObject() {
+ return new PhabricatorReleaseRelease();
+ }
+
+ protected function loadPage() {
+ return $this->loadStandardPage($this->newResultObject());
+ }
+
+ protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
+ $where = parent::buildWhereClauseParts($conn);
+
+ if ($this->ids !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'id IN (%Ld)',
+ $this->ids);
+ }
+
+ if ($this->phids !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'phid IN (%Ls)',
+ $this->phids);
+ }
+
+ if ($this->names !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'name IN (%Ls)',
+ $this->names);
+ }
+
+ if ($this->states !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'state IN (%Ls)',
+ $this->states);
+ }
+
+ if ($this->templates !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'releaseTemplateKey IN (%Ls)',
+ $this->templates);
+ }
+
+ if (strlen($this->datasourceQuery)) {
+ $where[] = qsprintf(
+ $conn,
+ 'name LIKE %~',
+ $this->datasourceQuery);
+ }
+
+ return $where;
+ }
+
+ protected function getDefaultOrderVector() {
+ return array('name');
+ }
+
+ public function getBuiltinOrders() {
+ return array(
+ 'name' => array(
+ 'vector' => array('name'),
+ 'name' => pht('Name'),
+ ),
+ 'select' => array(
+ 'vector' => array('state', 'name'),
+ 'name' => pht('Name'),
+ ),
+ ) + parent::getBuiltinOrders();
+ }
+
+ public function setGroupBy($group) {
+ $this->groupBy = $group;
+ $vector = array();
+
+ switch ($this->groupBy) {
+ case self::GROUP_NONE:
+ $vector = array();
+ break;
+ case self::GROUP_TEMPLATE:
+ $vector = array('releaseTemplateKey');
+ break;
+ case self::GROUP_STATE:
+ $vector = array('state');
+ break;
+ }
+
+ $this->setGroupVector($vector);
+
+ return $this;
+ }
+
+ public function getOrderableColumns() {
+ return parent::getOrderableColumns() + array(
+ 'name' => array(
+ 'table' => $this->getPrimaryTableAlias(),
+ 'column' => 'name',
+ 'reverse' => true,
+ 'type' => 'string',
+ 'unique' => true,
+ ),
+ 'releaseTemplateKey' => array(
+ 'table' => $this->getPrimaryTableAlias(),
+ 'column' => 'releaseTemplateKey',
+ 'reverse' => true,
+ 'type' => 'string',
+ 'unique' => false,
+ ),
+ 'state' => array(
+ 'table' => $this->getPrimaryTableAlias(),
+ 'column' => 'state',
+ 'reverse' => false,
+ 'type' => 'string',
+ 'unique' => false,
+ ),
+ );
+ }
+
+
+ public function getQueryApplicationClass() {
+ return 'PhabricatorReleaseApplication';
+ }
+
+}
diff --git a/src/applications/release/query/PhabricatorReleaseReleaseSearchEngine.php b/src/applications/release/query/PhabricatorReleaseReleaseSearchEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/query/PhabricatorReleaseReleaseSearchEngine.php
@@ -0,0 +1,169 @@
+<?php
+
+final class PhabricatorReleaseReleaseSearchEngine
+ extends PhabricatorApplicationSearchEngine {
+
+ public function getResultTypeDescription() {
+ return pht('Releases');
+ }
+
+ public function getApplicationClassName() {
+ return 'PhabricatorReleaseApplication';
+ }
+
+ public function newQuery() {
+ return id(new PhabricatorReleaseReleaseQuery());
+ }
+
+ protected function buildCustomSearchFields() {
+ return array(
+ id(new PhabricatorSearchStringListField())
+ ->setLabel(pht('Name'))
+ ->setKey('name'),
+ id(new PhabricatorSearchSelectField())
+ ->setLabel('Release Type')
+ ->setKey('template')
+ ->setOptions($this->getTemplateOptions())
+ ->setDefault('all'),
+ id(new PhabricatorSearchSelectField())
+ ->setLabel('State')
+ ->setKey('state')
+ ->setOptions($this->getStateOptions())
+ ->setDefault('all'),
+ id(new PhabricatorSearchSelectField())
+ ->setLabel(pht('Group By'))
+ ->setKey('group')
+ ->setOptions($this->getGroupOptions()),
+ );
+ }
+
+ protected function buildQueryFromParameters(array $map) {
+ $query = $this->newQuery();
+
+ if (idx($map, 'name')) {
+ $query->withNames($map['name']);
+ }
+
+ $state = idx($map, 'state', 'all');
+ if ($state != 'all') {
+ if ($state == 'active') {
+ $state = PhabricatorReleaseRelease::getOpenStates();
+ } else if (!is_array($state)) {
+ $state = array($state);
+ }
+ $query->withStates($state);
+ }
+
+ $template = idx($map, 'template', 'all');
+ if ($template != 'all') {
+ $query->withTemplateKeys(array($template));
+ }
+
+ $group = idx($map, 'group');
+ $group = idx($this->getGroupValues(), $group);
+ if ($group) {
+ $query->setGroupBy($group);
+ }
+
+ return $query;
+ }
+
+ protected function getURI($path) {
+ return '/release/'.$path;
+ }
+
+ protected function getBuiltinQueryNames() {
+ $names = array();
+
+ $names['open'] = pht('Open Releases');
+ $names['prod'] = pht('In Production');
+ $names['all'] = pht('All');
+
+ return $names;
+ }
+
+ public function buildSavedQueryFromBuiltin($query_key) {
+ $query = $this->newSavedQuery();
+ $query->setQueryKey($query_key);
+
+ $viewer_phid = $this->requireViewer()->getPHID();
+
+ switch ($query_key) {
+ case 'all':
+ return $query;
+ case 'open':
+ return $query
+ ->setParameter('state', 'active');
+ case 'prod':
+ return $query
+ ->setParameter(
+ 'state',
+ array(PhabricatorReleaseRelease::STATE_PRODUCTION));
+ }
+
+ return parent::buildSavedQueryFromBuiltin($query_key);
+ }
+
+ private function getGroupOptions() {
+ return array(
+ 'none' => pht('None'),
+ 'template' => pht('Release Type'),
+ 'status' => pht('State'),
+ );
+ }
+
+ private function getGroupValues() {
+ return array(
+ 'none' => PhabricatorReleaseReleaseQuery::GROUP_NONE,
+ 'template' => PhabricatorReleaseReleaseQuery::GROUP_TEMPLATE,
+ 'status' => PhabricatorReleaseReleaseQuery::GROUP_STATE,
+ );
+ }
+
+ private function getTemplateOptions() {
+ return array('all' => 'All Types') +
+ PhabricatorReleaseTemplate::getTemplatesMap();
+ }
+
+ private function getStateOptions() {
+ return array(
+ 'all' => 'Any state',
+ 'active' => 'Active states',
+ ) + PhabricatorReleaseRelease::getStatesMap();
+ }
+
+ protected function getRequiredHandlePHIDsForResultList(
+ array $releases,
+ PhabricatorSavedQuery $query) {
+
+ return array();
+ }
+
+ protected function renderResultList(
+ array $releases,
+ PhabricatorSavedQuery $query,
+ array $handles) {
+
+ assert_instances_of($releases, 'PhabricatorReleaseRelease');
+ $viewer = $this->requireViewer();
+
+
+ $list = new PHUIObjectItemListView();
+
+ foreach ($releases as $release) {
+ $name = $release->getName();
+
+ $item = id(new PHUIObjectItemView())
+ ->setHeader($name)
+ ->setHref($release->getURI())
+ ->addAttribute($release->getReleaseTemplateName())
+ ->addAttribute($release->getStateName());
+
+ $list->addItem($item);
+ }
+
+ return id(new PhabricatorApplicationSearchResultView())
+ ->setObjectList($list)
+ ->setNoDataString(pht('No releases found.'));
+ }
+}
diff --git a/src/applications/release/query/PhabricatorReleaseReleaseTransactionQuery.php b/src/applications/release/query/PhabricatorReleaseReleaseTransactionQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/query/PhabricatorReleaseReleaseTransactionQuery.php
@@ -0,0 +1,53 @@
+<?php
+
+final class PhabricatorReleaseReleaseTransactionQuery
+ extends PhabricatorApplicationTransactionQuery {
+
+ private $newOrOldValues = null;
+ private $newValues = null;
+
+ public function withNewOrOldValues($values) {
+ $this->newOrOldValues = $values;
+ return $this;
+ }
+
+ public function withNewValues($values) {
+ $this->newValues = $values;
+ return $this;
+ }
+
+ public function getTemplateApplicationTransaction() {
+ return new PhabricatorReleaseReleaseTransaction();
+ }
+
+ protected function buildMoreWhereClauses(
+ AphrontDatabaseConnection $conn_r) {
+ $where = array();
+
+ if ($this->newOrOldValues) {
+ $jsonized = array();
+ foreach ($this->newOrOldValues as $value) {
+ $jsonized[] = phutil_json_encode($value);
+ }
+ $where[] = qsprintf(
+ $conn_r,
+ '(newValue in (%Ls) OR oldValue in (%Ls))',
+ $jsonized,
+ $jsonized);
+ }
+
+ if ($this->newValues) {
+ $jsonized = array();
+ foreach ($this->newValues as $value) {
+ $jsonized[] = phutil_json_encode($value);
+ }
+ $where[] = qsprintf(
+ $conn_r,
+ 'newValue in (%Ls)',
+ $jsonized);
+ }
+
+ return $where;
+ }
+
+}
diff --git a/src/applications/release/remarkup/PhabricatorReleaseRemarkupRule.php b/src/applications/release/remarkup/PhabricatorReleaseRemarkupRule.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/remarkup/PhabricatorReleaseRemarkupRule.php
@@ -0,0 +1,18 @@
+<?php
+
+final class PhabricatorReleaseRemarkupRule
+ extends PhabricatorObjectRemarkupRule {
+
+ protected function getObjectNamePrefix() {
+ return 'X';
+ }
+
+ protected function loadObjects(array $ids) {
+ $viewer = $this->getEngine()->getConfig('viewer');
+
+ return id(new PhabricatorReleaseReleaseQuery())
+ ->setViewer($viewer)
+ ->withIDs($ids)
+ ->execute();
+ }
+}
diff --git a/src/applications/release/storage/PhabricatorReleaseDAO.php b/src/applications/release/storage/PhabricatorReleaseDAO.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/storage/PhabricatorReleaseDAO.php
@@ -0,0 +1,9 @@
+<?php
+
+abstract class PhabricatorReleaseDAO extends PhabricatorLiskDAO {
+
+ public function getApplicationName() {
+ return 'release';
+ }
+
+}
diff --git a/src/applications/release/storage/PhabricatorReleaseRelease.php b/src/applications/release/storage/PhabricatorReleaseRelease.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/storage/PhabricatorReleaseRelease.php
@@ -0,0 +1,293 @@
+<?php
+
+final class PhabricatorReleaseRelease extends PhabricatorReleaseDAO
+ implements
+ PhabricatorApplicationTransactionInterface,
+ PhabricatorSubscribableInterface,
+ PhabricatorFlaggableInterface,
+ PhabricatorMentionableInterface,
+ PhabricatorConduitResultInterface,
+ PhabricatorCustomFieldInterface,
+ PhabricatorPolicyInterface {
+
+ const STATE_PLANNED = 'planned';
+ const STATE_TESTING = 'testing';
+ const STATE_PRODUCTION = 'production';
+ const STATE_OBSOLETE = 'obsolete';
+ const STATE_INVALID = 'invalid';
+
+ const DETAIL_CUTPOINTS = 'cutpoints';
+ const DETAIL_CURRENTREF = 'currentref';
+
+ protected $name;
+ protected $state = self::STATE_PLANNED;
+ protected $description;
+ protected $releaseTemplateKey;
+ protected $details = array();
+
+ protected $viewPolicy;
+ protected $editPolicy;
+
+ protected $mailKey;
+
+ public static function initializeNewRelease(PhabricatorUser $actor) {
+ $release_application = id(new PhabricatorApplicationQuery())
+ ->setViewer($actor)
+ ->withClasses(array('PhabricatorReleaseApplication'))
+ ->executeOne();
+
+ $view_policy = $release_application->getPolicy(
+ PhabricatorReleaseReleaseDefaultViewCapability::CAPABILITY);
+
+ $edit_policy = $release_application->getPolicy(
+ PhabricatorReleaseReleaseDefaultEditCapability::CAPABILITY);
+
+ return id(new self())
+ ->setViewPolicy($view_policy)
+ ->setEditPolicy($edit_policy);
+ }
+
+ protected function getConfiguration() {
+ return array(
+ self::CONFIG_AUX_PHID => true,
+ self::CONFIG_SERIALIZATION => array(
+ 'details' => self::SERIALIZATION_JSON,
+ ),
+ self::CONFIG_COLUMN_SCHEMA => array(
+ 'name' => 'sort128',
+ 'state' => 'text12',
+ 'description' => 'text',
+ 'releaseTemplateKey' => 'text12',
+ 'mailKey' => 'bytes20',
+ ),
+ self::CONFIG_KEY_SCHEMA => array(
+ 'key_name' => array(
+ 'columns' => array('name'),
+ 'unique' => false,
+ ),
+ 'key_state' => array(
+ 'columns' => array('state'),
+ 'unique' => false,
+ ),
+ ),
+ ) + parent::getConfiguration();
+ }
+
+ public function generatePHID() {
+ return PhabricatorPHID::generateNewPHID(
+ PhabricatorReleaseReleasePHIDType::TYPECONST);
+ }
+
+ public function save() {
+ if (!$this->getMailKey()) {
+ $this->setMailKey(Filesystem::readRandomCharacters(20));
+ }
+ return parent::save();
+ }
+
+ public function getURI() {
+ return '/X'.$this->getID();
+ }
+
+ public function getMonogram() {
+ return 'X'.$this->getID();
+ }
+
+ public function getStateName() {
+ return idx(self::getStatesMap(), $this->state, $this->state);
+ }
+
+ public function getReleaseTemplateName() {
+ return idx(
+ PhabricatorReleaseTemplate::getTemplatesMap(),
+ $this->releaseTemplateKey,
+ $this->releaseTemplateKey);
+ }
+
+ public function getReleaseTemplate() {
+ return PhabricatorReleaseTemplate::getTemplatesInstance(
+ $this->releaseTemplateKey);
+ }
+
+ /**
+ * closed, deleted, canceled, whatever.
+ */
+ public function isClosed() {
+ switch ($this->state) {
+ case self::STATE_OBSOLETE:
+ case self::STATE_INVALID:
+ return true;
+ }
+ return false;
+ }
+
+ public function getDetail($key, $default = null) {
+ return idx($this->details, $key, $default);
+ }
+
+ public function setDetail($key, $value) {
+ $this->details[$key] = $value;
+ return $this;
+ }
+
+ public static function getStatesMap() {
+ return array(
+ self::STATE_PLANNED => pht('Planned'),
+ self::STATE_TESTING => pht('In Testing'),
+ self::STATE_PRODUCTION => pht('In Production'),
+ self::STATE_OBSOLETE => pht('Obsolete'),
+ self::STATE_INVALID => pht('Invalid'),
+ );
+ }
+ public static function getOpenStates() {
+ return array(
+ self::STATE_PLANNED,
+ self::STATE_TESTING,
+ self::STATE_PRODUCTION,
+ );
+ }
+
+ public function getCurrentRefs() {
+ return $this->getDetail(self::DETAIL_CURRENTREF, array());
+ }
+
+ public function getCutpoints() {
+ return $this->getDetail(self::DETAIL_CUTPOINTS, array());
+ }
+
+ public function isRefMutable($ref) {
+ return !preg_match('/[a-f0-9]{40}/', $ref);
+ }
+
+ public function getSimpleRefName($ref) {
+ if (!$this->isRefMutable($ref)) {
+ return $ref;
+ }
+
+ $count = 0;
+ $branch = preg_replace('/^refs\/heads\//', '', $ref, 1, $count);
+
+ return $branch;
+ }
+
+ public function getNormalizedName() {
+ return preg_replace('/[^0-9a-zA-Z\.-]+|\.\./', '_', $this->getName());
+ }
+
+/* -( PhabricatorApplicationTransactionInterface )------------------------- */
+
+ public function getApplicationTransactionEditor() {
+ return new PhabricatorReleaseReleaseEditor();
+ }
+
+ public function getApplicationTransactionObject() {
+ return $this;
+ }
+
+ public function getApplicationTransactionTemplate() {
+ return new PhabricatorReleaseReleaseTransaction();
+ }
+
+ public function willRenderTimeline(
+ PhabricatorApplicationTransactionView $timeline,
+ AphrontRequest $request) {
+
+ return $timeline;
+ }
+
+
+/* -( PhabricatorSubscribableInterface )----------------------------------- */
+
+ public function isAutomaticallySubscribed($phid) {
+ return false;
+ }
+
+/* -( PhabricatorConduitResultInterface )---------------------------------- */
+
+ public function getFieldSpecificationsForConduit() {
+ return array(
+ id(new PhabricatorConduitSearchFieldSpecification())
+ ->setKey('name')
+ ->setType('string')
+ ->setDescription(pht('The name of the release.')),
+ id(new PhabricatorConduitSearchFieldSpecification())
+ ->setKey('state')
+ ->setType('string')
+ ->setDescription(pht('Status of the release.')),
+ id(new PhabricatorConduitSearchFieldSpecification())
+ ->setKey('type')
+ ->setType('string')
+ ->setDescription(pht('Type of the release.')),
+ id(new PhabricatorConduitSearchFieldSpecification())
+ ->setKey('cut_points')
+ ->setType('map<phid, hash>')
+ ->setDescription(pht('Hashes where this release was cut from')),
+ id(new PhabricatorConduitSearchFieldSpecification())
+ ->setKey('current_refs')
+ ->setType('map<phid, hash>')
+ ->setDescription(pht('Current hashes/branches of this release')),
+ );
+ }
+
+ public function getFieldValuesForConduit() {
+ return array(
+ 'name' => $this->getName(),
+ 'state' => $this->getState(),
+ 'type' => $this->getReleaseTemplateKey(),
+ 'cut_points' => $this->getCutpoints(),
+ 'current_refs' => $this->getCurrentRefs(),
+ );
+ }
+
+ public function getConduitSearchAttachments() {
+ return array();
+ }
+
+/* -( PhabricatorCustomFieldInterface )------------------------------------ */
+
+ private $customFields = self::ATTACHABLE;
+
+ public function getCustomFieldSpecificationForRole($role) {
+ return array(); // TODO ?
+ }
+
+ public function getCustomFieldBaseClass() {
+ return 'ReleaseCustomField';
+ }
+
+ public function getCustomFields() {
+ return $this->assertAttached($this->customFields);
+ }
+
+ public function attachCustomFields(PhabricatorCustomFieldAttachment $fields) {
+ $this->customFields = $fields;
+ return $this;
+ }
+
+
+/* -( PhabricatorPolicyInterface )----------------------------------------- */
+
+ public function getCapabilities() {
+ return array(
+ PhabricatorPolicyCapability::CAN_VIEW,
+ PhabricatorPolicyCapability::CAN_EDIT,
+ );
+ }
+
+ public function getPolicy($capability) {
+ switch ($capability) {
+ case PhabricatorPolicyCapability::CAN_VIEW:
+ return $this->getViewPolicy();
+ case PhabricatorPolicyCapability::CAN_EDIT:
+ return $this->getEditPolicy();
+ }
+ }
+
+ public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
+ return false;
+ }
+
+ public function describeAutomaticCapability($capability) {
+ return null;
+ }
+}
diff --git a/src/applications/release/storage/PhabricatorReleaseReleaseTransaction.php b/src/applications/release/storage/PhabricatorReleaseReleaseTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/storage/PhabricatorReleaseReleaseTransaction.php
@@ -0,0 +1,47 @@
+<?php
+
+final class PhabricatorReleaseReleaseTransaction
+ extends PhabricatorModularTransaction {
+
+ const MAILTAG_STATE = 'release-state';
+ const MAILTAG_OTHER = 'release-content';
+
+ public function getApplicationName() {
+ return 'release';
+ }
+
+ // TODO move to parent class
+ public function expandTransaction($object) {
+ $impl = $this->getTransactionImplementation();
+ if ($impl instanceof PhabricatorReleaseReleaseTransactionType) {
+ return $impl->expandTransaction($object);
+ }
+ return array();
+ }
+
+ public function getApplicationTransactionType() {
+ return PhabricatorReleaseReleasePHIDType::TYPECONST;
+ }
+
+ public function getBaseTransactionClass() {
+ return 'PhabricatorReleaseReleaseTransactionType';
+ }
+
+ public function getApplicationTransactionCommentObject() {
+ return new PhabricatorReleaseReleaseTransactionComment();
+ }
+
+ public function getMailTags() {
+ $tags = parent::getMailTags();
+
+ switch ($this->getTransactionType()) {
+ case PhabricatorReleaseReleaseStateTransaction::TRANSACTIONTYPE:
+ $tags[] = self::MAILTAG_STATE;
+ break;
+ default:
+ $tags[] = self::MAILTAG_OTHER;
+ break;
+ }
+ return $tags;
+ }
+}
diff --git a/src/applications/release/storage/PhabricatorReleaseReleaseTransactionComment.php b/src/applications/release/storage/PhabricatorReleaseReleaseTransactionComment.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/storage/PhabricatorReleaseReleaseTransactionComment.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhabricatorReleaseReleaseTransactionComment
+ extends PhabricatorApplicationTransactionComment {
+
+ public function getApplicationTransactionObject() {
+ return new PhabricatorReleaseReleaseTransaction();
+ }
+
+}
diff --git a/src/applications/release/storage/PhabricatorReleaseSchemaSpec.php b/src/applications/release/storage/PhabricatorReleaseSchemaSpec.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/storage/PhabricatorReleaseSchemaSpec.php
@@ -0,0 +1,10 @@
+<?php
+
+final class PhabricatorReleaseSchemaSpec
+ extends PhabricatorConfigSchemaSpec {
+
+ public function buildSchemata() {
+ $this->buildEdgeSchemata(new PhabricatorReleaseRelease());
+ }
+
+}
diff --git a/src/applications/release/storage/ReleaseCustomFieldNumericIndex.php b/src/applications/release/storage/ReleaseCustomFieldNumericIndex.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/storage/ReleaseCustomFieldNumericIndex.php
@@ -0,0 +1,10 @@
+<?php
+
+final class ReleaseCustomFieldNumericIndex // TODO db create tables
+ extends PhabricatorCustomFieldNumericIndexStorage {
+
+ public function getApplicationName() {
+ return 'release';
+ }
+
+}
diff --git a/src/applications/release/storage/ReleaseCustomFieldStorage.php b/src/applications/release/storage/ReleaseCustomFieldStorage.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/storage/ReleaseCustomFieldStorage.php
@@ -0,0 +1,10 @@
+<?php
+
+final class ReleaseCustomFieldStorage
+ extends PhabricatorCustomFieldStorage {
+
+ public function getApplicationName() {
+ return 'release';
+ }
+
+}
diff --git a/src/applications/release/storage/ReleaseCustomFieldStringIndex.php b/src/applications/release/storage/ReleaseCustomFieldStringIndex.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/storage/ReleaseCustomFieldStringIndex.php
@@ -0,0 +1,10 @@
+<?php
+
+final class ReleaseCustomFieldStringIndex
+ extends PhabricatorCustomFieldStringIndexStorage {
+
+ public function getApplicationName() {
+ return 'release';
+ }
+
+}
diff --git a/src/applications/release/template/PhabricatorReleaseTemplate.php b/src/applications/release/template/PhabricatorReleaseTemplate.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/template/PhabricatorReleaseTemplate.php
@@ -0,0 +1,49 @@
+<?php
+
+// TODO This will be replaced with the Release Plan object, which will look
+// more-or-less like HarbormasterBuildPlan.
+abstract class PhabricatorReleaseTemplate extends Phobject {
+
+ abstract public function getTemplateReadableName();
+
+ final public function getTemplateKey() {
+ return $this->getPhobjectClassConstant('TEMPLATE_KEY');
+ }
+
+ abstract public function validateRepositories(array $repositories);
+
+ private static function getAllTemplates() {
+ return id(new PhutilClassMapQuery())
+ ->setAncestorClass(__CLASS__)
+ ->setUniqueMethod('getTemplateKey')
+ ->setSortMethod('getTemplateReadableName')
+ ->execute();
+ }
+
+ public static function getTemplatesForRepository(
+ PhabricatorRepository $repository) {
+
+ $template_classes = self::getAllTemplates();
+ $valid = array();
+ $test = array($repository);
+ foreach ($template_classes as $template) {
+ if (!$template->validateRepositories($test)) {
+ $valid[] = $template;
+ }
+ }
+
+ return $valid;
+ }
+
+ public static function getTemplatesMap() {
+ $template_classes = self::getAllTemplates();
+
+ return mpull($template_classes, 'getTemplateReadableName');
+ }
+
+ public static function getTemplatesInstance($key) {
+ $template_classes = self::getAllTemplates();
+
+ return idx($template_classes, $key);
+ }
+}
diff --git a/src/applications/release/typeahead/PhabricatorReleaseReleaseDatasource.php b/src/applications/release/typeahead/PhabricatorReleaseReleaseDatasource.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/typeahead/PhabricatorReleaseReleaseDatasource.php
@@ -0,0 +1,65 @@
+<?php
+
+final class PhabricatorReleaseReleaseDatasource
+ extends PhabricatorTypeaheadDatasource {
+
+ public function getBrowseTitle() {
+ return pht('Releases');
+ }
+
+ public function getPlaceholderText() {
+ return pht('Type a release name...');
+ }
+
+ public function getDatasourceApplicationClass() {
+ return 'PhabricatorReleaseApplication';
+ }
+
+ public function withTemplateKeys($template_keys) {
+ $params = $this->getParameters();
+ $params['template_keys'] = $template_keys;
+ $this->setParameters($params);
+ return $this;
+ }
+
+ public function loadResults() {
+ $viewer = $this->getViewer();
+ $raw_query = $this->getRawQuery();
+
+ $query = id(new PhabricatorReleaseReleaseQuery())
+ ->setOrder('select')
+ ->withDatasourceQuery($raw_query);
+
+ $template_keys = $this->getParameter('template_keys');
+ if ($template_keys) {
+ $query->withTemplateKeys($template_keys);
+ }
+
+ $releases = $this->executeQuery($query);
+
+ $results = array();
+ foreach ($releases as $release) {
+ $name = $release->getName();
+ $display_name = $release->getMonogram().' '.$name;
+
+ $token = id(new PhabricatorTypeaheadResult())
+ ->setName($name)
+ ->setDisplayName($display_name)
+ ->setURI($release->getURI())
+ ->addAttribute($release->getReleaseTemplateName())
+ ->addAttribute($release->getStateName())
+ ->setPHID($release->getPHID());
+
+ if ($release->isClosed()) {
+ $token
+ ->setName('zz '.$name)
+ ->setClosed('old');
+ }
+
+ $results[] = $token;
+ }
+
+ return $results;
+ }
+
+}
diff --git a/src/applications/release/xaction/PhabricatorReleaseReleaseCurrentRefTransaction.php b/src/applications/release/xaction/PhabricatorReleaseReleaseCurrentRefTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/PhabricatorReleaseReleaseCurrentRefTransaction.php
@@ -0,0 +1,33 @@
+<?php
+
+final class PhabricatorReleaseReleaseCurrentRefTransaction
+ extends PhabricatorReleaseReleaseTransactionType {
+
+ const TRANSACTIONTYPE = 'release:currentref';
+
+ public function generateOldValue($object) {
+ return $object->getDetail(PhabricatorReleaseRelease::DETAIL_CURRENTREF);
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setDetail(PhabricatorReleaseRelease::DETAIL_CURRENTREF, $value);
+ }
+
+ public function hasChangeDetailView() {
+ return true;
+ }
+
+ public function getTitle() {
+ return pht(
+ '%s edited the current references for this release.',
+ $this->renderAuthor());
+ }
+
+ public function getTitleForFeed() {
+ return pht(
+ '%s edited the current references for release %s',
+ $this->renderAuthor(),
+ $this->renderObject());
+ }
+
+}
diff --git a/src/applications/release/xaction/PhabricatorReleaseReleaseCutpointTransaction.php b/src/applications/release/xaction/PhabricatorReleaseReleaseCutpointTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/PhabricatorReleaseReleaseCutpointTransaction.php
@@ -0,0 +1,33 @@
+<?php
+
+final class PhabricatorReleaseReleaseCutpointTransaction
+ extends PhabricatorReleaseReleaseTransactionType {
+
+ const TRANSACTIONTYPE = 'release:cutpoint';
+
+ public function generateOldValue($object) {
+ return $object->getDetail(PhabricatorReleaseRelease::DETAIL_CUTPOINTS);
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setDetail(PhabricatorReleaseRelease::DETAIL_CUTPOINTS, $value);
+ }
+
+ public function hasChangeDetailView() {
+ return true;
+ }
+
+ public function getTitle() {
+ return pht(
+ '%s edited the cutpoints for this release.',
+ $this->renderAuthor());
+ }
+
+ public function getTitleForFeed() {
+ return pht(
+ '%s edited the cutpoints for release %s',
+ $this->renderAuthor(),
+ $this->renderObject());
+ }
+
+}
diff --git a/src/applications/release/xaction/PhabricatorReleaseReleaseDescribeTransaction.php b/src/applications/release/xaction/PhabricatorReleaseReleaseDescribeTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/PhabricatorReleaseReleaseDescribeTransaction.php
@@ -0,0 +1,41 @@
+<?php
+
+final class PhabricatorReleaseReleaseDescribeTransaction
+ extends PhabricatorReleaseReleaseTransactionType {
+
+ const TRANSACTIONTYPE = 'release:describe';
+
+ public function generateOldValue($object) {
+ return $object->getDescription();
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setDescription($value);
+ }
+
+ public function getTitle() {
+ return pht(
+ '%s edited the release description.',
+ $this->renderAuthor());
+ }
+
+ public function getTitleForFeed() {
+ return pht(
+ '%s edited the description of %s.',
+ $this->renderAuthor(),
+ $this->renderObject());
+ }
+
+ public function hasChangeDetailView() {
+ return ($this->getOldValue() !== null);
+ }
+
+ public function newChangeDetailView() {
+ $viewer = $this->getViewer();
+
+ return id(new PhabricatorApplicationTransactionTextDiffDetailView())
+ ->setViewer($viewer)
+ ->setOldText($this->getOldValue())
+ ->setNewText($this->getNewValue());
+ }
+}
diff --git a/src/applications/release/xaction/PhabricatorReleaseReleaseNameTransaction.php b/src/applications/release/xaction/PhabricatorReleaseReleaseNameTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/PhabricatorReleaseReleaseNameTransaction.php
@@ -0,0 +1,44 @@
+<?php
+
+final class PhabricatorReleaseReleaseNameTransaction
+extends PhabricatorReleaseReleaseTransactionType {
+
+ const TRANSACTIONTYPE = 'release:name';
+
+ public function generateOldValue($object) {
+ return $object->getName();
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setName($value);
+ }
+
+ public function shouldHide() {
+ $old = $this->getOldValue();
+ if (is_array($old) && !$old) {
+ return true;
+ }
+
+ if (!is_array($old) && !strlen($old)) {
+ return true;
+ }
+ }
+
+ public function getTitle() {
+ return pht(
+ '%s renamed the release from "%s" to "%s".',
+ $this->renderAuthor(),
+ $this->getOldValue(),
+ $this->getNewValue());
+ }
+
+ public function getTitleForFeed() {
+ return pht(
+ '%s renamed the release for %s from "%s" to "%s".',
+ $this->renderAuthor(),
+ $this->renderObject(),
+ $this->getOldValue(),
+ $this->getNewValue());
+ }
+
+}
diff --git a/src/applications/release/xaction/PhabricatorReleaseReleaseStateTransaction.php b/src/applications/release/xaction/PhabricatorReleaseReleaseStateTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/PhabricatorReleaseReleaseStateTransaction.php
@@ -0,0 +1,45 @@
+<?php
+
+final class PhabricatorReleaseReleaseStateTransaction
+ extends PhabricatorReleaseReleaseTransactionType {
+
+ const TRANSACTIONTYPE = 'release:state';
+
+ public function generateOldValue($object) {
+ return $object->getState();
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setState($value);
+ }
+
+ public function getColor() {
+ return 'orange';
+ }
+
+ public function getTitle() {
+ $states = PhabricatorReleaseRelease::getStatesMap();
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ return pht(
+ '%s changed the status of this release from "%s" to "%s"',
+ $this->renderAuthor(),
+ idx($states, $old, $old),
+ idx($states, $new, $new));
+ }
+
+ public function getTitleForFeed() {
+ $states = PhabricatorReleaseRelease::getStatesMap();
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ return pht(
+ '%s changed the status of release %s from "%s" to "%s"',
+ $this->renderAuthor(),
+ $this->renderObject(),
+ idx($states, $old, $old),
+ idx($states, $new, $new));
+ }
+
+}
diff --git a/src/applications/release/xaction/PhabricatorReleaseReleaseTemplateTransaction.php b/src/applications/release/xaction/PhabricatorReleaseReleaseTemplateTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/PhabricatorReleaseReleaseTemplateTransaction.php
@@ -0,0 +1,44 @@
+<?php
+
+final class PhabricatorReleaseReleaseTemplateTransaction
+ extends PhabricatorReleaseReleaseTransactionType {
+
+ const TRANSACTIONTYPE = 'release:templatekey';
+
+ public function generateOldValue($object) {
+ return $object->getReleaseTemplateKey();
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setReleaseTemplateKey($value);
+ }
+
+
+ public function validateTransactions($object, array $xactions) {
+ foreach ($xactions as $xaction) {
+ $existing = $object->getReleaseTemplateKey();
+ if ($existing && $existing != $xaction->getNewValue()) {
+ $error = $this->newError(
+ pht('Locked'),
+ pht('Cannot change template after Release is created'));
+
+ return array($error);
+ }
+ }
+ return array();
+ }
+
+ public function getTitle() {
+ return pht(
+ '%s Did the impossible.',
+ $this->renderAuthor());
+ }
+
+ public function getTitleForFeed() {
+ return pht(
+ '%s Did the impossible to %s.',
+ $this->renderAuthor(),
+ $this->renderObject());
+ }
+
+}
diff --git a/src/applications/release/xaction/PhabricatorReleaseReleaseTransactionType.php b/src/applications/release/xaction/PhabricatorReleaseReleaseTransactionType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/release/xaction/PhabricatorReleaseReleaseTransactionType.php
@@ -0,0 +1,28 @@
+<?php
+
+abstract class PhabricatorReleaseReleaseTransactionType
+ extends PhabricatorModularTransactionType {
+
+ public function getIcon() {
+ return 'fa-css3';
+ }
+
+ // TODO move to parent class
+ public function expandTransaction($object) {
+ return array();
+ }
+
+ public function newChangeDetailView() {
+ $viewer = $this->getViewer();
+
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ $json = new PhutilJSON();
+
+ return id(new PhabricatorApplicationTransactionTextDiffDetailView())
+ ->setViewer($viewer)
+ ->setOldText($old ? $json->encodeFormatted($old) : null)
+ ->setNewText($new ? $json->encodeFormatted($new) : null);
+ }
+}
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
@@ -112,6 +112,7 @@
'db.phurl' => array(),
'db.badges' => array(),
'db.packages' => array(),
+ 'db.release' => array(),
'0000.legacy.sql' => array(
'legacy' => 0,
),
File Metadata
Details
Attached
Mime Type
text/plain
Expires
Wed, Jan 22, 4:04 PM (11 h, 48 m)
Storage Engine
blob
Storage Format
Encrypted (AES-256-CBC)
Storage Handle
7034873
Default Alt Text
D16981.id40860.diff (74 KB)
Attached To
Mode
D16981: Initial code-dump of Release
Attached
Detach File
Event Timeline
Log In to Comment