Page MenuHomePhorge

D25036.1740002230.diff
No OneTemporary

D25036.1740002230.diff

diff --git a/resources/celerity/map.php b/resources/celerity/map.php
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -9,8 +9,8 @@
'names' => array(
'conpherence.pkg.css' => '0e3cf785',
'conpherence.pkg.js' => '020aebcf',
- 'core.pkg.css' => '0ae696de',
- 'core.pkg.js' => '68f29322',
+ 'core.pkg.css' => '00a2e7f4',
+ 'core.pkg.js' => 'd2de90d9',
'dark-console.pkg.js' => '187792c2',
'differential.pkg.css' => 'ffb69e3d',
'differential.pkg.js' => '8deec4cd',
@@ -171,7 +171,7 @@
'rsrc/css/phui/phui-invisible-character-view.css' => 'c694c4a4',
'rsrc/css/phui/phui-left-right.css' => '68513c34',
'rsrc/css/phui/phui-lightbox.css' => '4ebf22da',
- 'rsrc/css/phui/phui-list.css' => '2f253c22',
+ 'rsrc/css/phui/phui-list.css' => '0c04affd',
'rsrc/css/phui/phui-object-box.css' => 'b8d7eea0',
'rsrc/css/phui/phui-pager.css' => 'd022c7ad',
'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8',
@@ -246,7 +246,7 @@
'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => 'a9f35511',
'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '3a1b81f6',
'rsrc/externals/javelin/lib/Cookie.js' => '05d290ef',
- 'rsrc/externals/javelin/lib/DOM.js' => '94681e22',
+ 'rsrc/externals/javelin/lib/DOM.js' => 'e4c7622a',
'rsrc/externals/javelin/lib/History.js' => '030b4f7a',
'rsrc/externals/javelin/lib/JSON.js' => '541f81c3',
'rsrc/externals/javelin/lib/Leader.js' => '0d2490ce',
@@ -717,7 +717,7 @@
'javelin-color' => '78f811c9',
'javelin-cookie' => '05d290ef',
'javelin-diffusion-locate-file-source' => '94243d89',
- 'javelin-dom' => '94681e22',
+ 'javelin-dom' => 'e4c7622a',
'javelin-dynval' => '202a2e85',
'javelin-event' => 'c03f2fb4',
'javelin-external-editor-link-engine' => '48a8641f',
@@ -872,7 +872,7 @@
'phui-invisible-character-view-css' => 'c694c4a4',
'phui-left-right-css' => '68513c34',
'phui-lightbox-css' => '4ebf22da',
- 'phui-list-view-css' => '2f253c22',
+ 'phui-list-view-css' => '0c04affd',
'phui-object-box-css' => 'b8d7eea0',
'phui-oi-big-ui-css' => 'fa74cc35',
'phui-oi-color-css' => 'b517bfa0',
@@ -1229,6 +1229,13 @@
'aphront-typeahead-control-css',
'phui-tag-view-css',
),
+ '36821f8d' => array(
+ 'javelin-behavior',
+ 'javelin-util',
+ 'javelin-dom',
+ 'javelin-stratcom',
+ 'javelin-vector',
+ ),
'3829a3cf' => array(
'javelin-behavior',
'javelin-uri',
@@ -1774,13 +1781,6 @@
'javelin-uri',
'javelin-routable',
),
- '94681e22' => array(
- 'javelin-magical-init',
- 'javelin-install',
- 'javelin-util',
- 'javelin-vector',
- 'javelin-stratcom',
- ),
'9623adc1' => array(
'javelin-behavior',
'javelin-stratcom',
@@ -2160,6 +2160,13 @@
'javelin-dom',
'phuix-dropdown-menu',
),
+ 'e4c7622a' => array(
+ 'javelin-magical-init',
+ 'javelin-install',
+ 'javelin-util',
+ 'javelin-vector',
+ 'javelin-stratcom',
+ ),
'e5bdb730' => array(
'javelin-behavior',
'javelin-stratcom',
diff --git a/resources/sprite/manifest/login.json b/resources/sprite/manifest/login.json
--- a/resources/sprite/manifest/login.json
+++ b/resources/sprite/manifest/login.json
@@ -59,7 +59,7 @@
"login-MediaWiki": {
"name": "login-MediaWiki",
"rule": ".login-MediaWiki",
- "hash": "f1f0a9382434081a9a84e7584828c2dd"
+ "hash": "68eba44e85ea942ecf14d3c08992a2e2"
},
"login-PayPal": {
"name": "login-PayPal",
diff --git a/resources/sql/autopatches/20210625.owners.01.authority.sql b/resources/sql/autopatches/20210625.owners.01.authority.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20210625.owners.01.authority.sql
@@ -0,0 +1,2 @@
+ALTER TABLE {$NAMESPACE}_owners.owners_package
+ ADD authorityMode VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT};
diff --git a/resources/sql/autopatches/20210625.owners.02.authority-default.sql b/resources/sql/autopatches/20210625.owners.02.authority-default.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20210625.owners.02.authority-default.sql
@@ -0,0 +1,3 @@
+UPDATE {$NAMESPACE}_owners.owners_package
+ SET authorityMode = 'strong'
+ WHERE authorityMode = '';
diff --git a/resources/sql/autopatches/20210713.harborcommand.01.migrate.sql b/resources/sql/autopatches/20210713.harborcommand.01.migrate.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20210713.harborcommand.01.migrate.sql
@@ -0,0 +1,4 @@
+INSERT IGNORE INTO {$NAMESPACE}_harbormaster.harbormaster_buildmessage
+ (authorPHID, receiverPHID, type, isConsumed, dateCreated, dateModified)
+ SELECT authorPHID, targetPHID, command, 0, dateCreated, dateModified
+ FROM {$NAMESPACE}_harbormaster.harbormaster_buildcommand;
diff --git a/resources/sql/autopatches/20210713.harborcommand.02.drop.sql b/resources/sql/autopatches/20210713.harborcommand.02.drop.sql
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20210713.harborcommand.02.drop.sql
@@ -0,0 +1 @@
+DROP TABLE IF EXISTS {$NAMESPACE}_harbormaster.harbormaster_buildcommand;
diff --git a/resources/sql/autopatches/20210715.harborcommand.01.xactions.php b/resources/sql/autopatches/20210715.harborcommand.01.xactions.php
new file mode 100644
--- /dev/null
+++ b/resources/sql/autopatches/20210715.harborcommand.01.xactions.php
@@ -0,0 +1,34 @@
+<?php
+
+// See T13072. Turn the old "process a command" transaction into modular
+// transactions that each handle one particular type of command.
+
+$xactions_table = new HarbormasterBuildTransaction();
+$xactions_conn = $xactions_table->establishConnection('w');
+$row_iterator = new LiskRawMigrationIterator(
+ $xactions_conn,
+ $xactions_table->getTableName());
+
+$map = array(
+ '"pause"' => 'message/pause',
+ '"abort"' => 'message/abort',
+ '"resume"' => 'message/resume',
+ '"restart"' => 'message/restart',
+);
+
+foreach ($row_iterator as $row) {
+ if ($row['transactionType'] !== 'harbormaster:build:command') {
+ continue;
+ }
+
+ $raw_value = $row['newValue'];
+
+ if (isset($map[$raw_value])) {
+ queryfx(
+ $xactions_conn,
+ 'UPDATE %R SET transactionType = %s WHERE id = %d',
+ $xactions_table,
+ $map[$raw_value],
+ $row['id']);
+ }
+}
diff --git a/resources/sql/patches/20131004.dxreviewers.php b/resources/sql/patches/20131004.dxreviewers.php
--- a/resources/sql/patches/20131004.dxreviewers.php
+++ b/resources/sql/patches/20131004.dxreviewers.php
@@ -29,8 +29,7 @@
foreach ($reviewer_phids as $dst) {
if (phid_get_type($dst) == PhabricatorPHIDConstants::PHID_TYPE_UNKNOWN) {
// At least one old install ran into some issues here. Skip the row if we
- // can't figure out what the destination PHID is. See here:
- // https://github.com/phacility/phabricator/pull/507
+ // can't figure out what the destination PHID is.
continue;
}
diff --git a/scripts/setup/manage_celerity.php b/scripts/setup/manage_celerity.php
--- a/scripts/setup/manage_celerity.php
+++ b/scripts/setup/manage_celerity.php
@@ -2,7 +2,7 @@
<?php
$root = dirname(dirname(dirname(__FILE__)));
-require_once $root.'/scripts/__init_script__.php';
+require_once $root.'/scripts/init/init-setup.php';
$args = new PhutilArgumentParser($argv);
$args->setTagline(pht('manage celerity'));
diff --git a/scripts/sql/manage_storage.php b/scripts/sql/manage_storage.php
--- a/scripts/sql/manage_storage.php
+++ b/scripts/sql/manage_storage.php
@@ -95,7 +95,7 @@
$host = $args->getArg('host');
$ref_key = $args->getArg('ref');
-if (strlen($host) || strlen($ref_key)) {
+if (($host !== null) || ($ref_key !== null)) {
if ($host && $ref_key) {
throw new PhutilArgumentUsageException(
pht(
diff --git a/scripts/ssh/ssh-connect.php b/scripts/ssh/ssh-connect.php
--- a/scripts/ssh/ssh-connect.php
+++ b/scripts/ssh/ssh-connect.php
@@ -154,6 +154,6 @@
array_unshift($arguments, $pattern);
$err = newv('PhutilExecPassthru', $arguments)
- ->execute();
+ ->resolve();
exit($err);
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
@@ -88,6 +88,7 @@
'AlmanacInterfaceSearchEngine' => 'applications/almanac/query/AlmanacInterfaceSearchEngine.php',
'AlmanacInterfaceTableView' => 'applications/almanac/view/AlmanacInterfaceTableView.php',
'AlmanacInterfaceTransaction' => 'applications/almanac/storage/AlmanacInterfaceTransaction.php',
+ 'AlmanacInterfaceTransactionQuery' => 'applications/almanac/query/AlmanacInterfaceTransactionQuery.php',
'AlmanacInterfaceTransactionType' => 'applications/almanac/xaction/AlmanacInterfaceTransactionType.php',
'AlmanacKeys' => 'applications/almanac/util/AlmanacKeys.php',
'AlmanacManageClusterServicesCapability' => 'applications/almanac/capability/AlmanacManageClusterServicesCapability.php',
@@ -338,6 +339,7 @@
'ChatLogConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogConduitAPIMethod.php',
'ChatLogQueryConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogQueryConduitAPIMethod.php',
'ChatLogRecordConduitAPIMethod' => 'applications/chatlog/conduit/ChatLogRecordConduitAPIMethod.php',
+ 'ConduitAPIDocumentationPage' => 'applications/conduit/data/ConduitAPIDocumentationPage.php',
'ConduitAPIMethod' => 'applications/conduit/method/ConduitAPIMethod.php',
'ConduitAPIMethodTestCase' => 'applications/conduit/method/__tests__/ConduitAPIMethodTestCase.php',
'ConduitAPIRequest' => 'applications/conduit/protocol/ConduitAPIRequest.php',
@@ -904,6 +906,7 @@
'DiffusionLowLevelResolveRefsQuery' => 'applications/diffusion/query/lowlevel/DiffusionLowLevelResolveRefsQuery.php',
'DiffusionMercurialBlameQuery' => 'applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php',
'DiffusionMercurialCommandEngine' => 'applications/diffusion/protocol/DiffusionMercurialCommandEngine.php',
+ 'DiffusionMercurialCommandEngineTests' => 'applications/diffusion/protocol/__tests__/DiffusionMercurialCommandEngineTests.php',
'DiffusionMercurialFileContentQuery' => 'applications/diffusion/query/filecontent/DiffusionMercurialFileContentQuery.php',
'DiffusionMercurialFlagInjectionException' => 'applications/diffusion/exception/DiffusionMercurialFlagInjectionException.php',
'DiffusionMercurialRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionMercurialRawDiffQuery.php',
@@ -1386,8 +1389,9 @@
'HarbormasterBuildArtifactPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildArtifactPHIDType.php',
'HarbormasterBuildArtifactQuery' => 'applications/harbormaster/query/HarbormasterBuildArtifactQuery.php',
'HarbormasterBuildAutoplan' => 'applications/harbormaster/autoplan/HarbormasterBuildAutoplan.php',
- 'HarbormasterBuildCommand' => 'applications/harbormaster/storage/HarbormasterBuildCommand.php',
'HarbormasterBuildDependencyDatasource' => 'applications/harbormaster/typeahead/HarbormasterBuildDependencyDatasource.php',
+ 'HarbormasterBuildEditAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildEditAPIMethod.php',
+ 'HarbormasterBuildEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildEditEngine.php',
'HarbormasterBuildEngine' => 'applications/harbormaster/engine/HarbormasterBuildEngine.php',
'HarbormasterBuildFailureException' => 'applications/harbormaster/exception/HarbormasterBuildFailureException.php',
'HarbormasterBuildGraph' => 'applications/harbormaster/engine/HarbormasterBuildGraph.php',
@@ -1407,7 +1411,12 @@
'HarbormasterBuildLogView' => 'applications/harbormaster/view/HarbormasterBuildLogView.php',
'HarbormasterBuildLogViewController' => 'applications/harbormaster/controller/HarbormasterBuildLogViewController.php',
'HarbormasterBuildMessage' => 'applications/harbormaster/storage/HarbormasterBuildMessage.php',
+ 'HarbormasterBuildMessageAbortTransaction' => 'applications/harbormaster/xaction/build/HarbormasterBuildMessageAbortTransaction.php',
+ 'HarbormasterBuildMessagePauseTransaction' => 'applications/harbormaster/xaction/build/HarbormasterBuildMessagePauseTransaction.php',
'HarbormasterBuildMessageQuery' => 'applications/harbormaster/query/HarbormasterBuildMessageQuery.php',
+ 'HarbormasterBuildMessageRestartTransaction' => 'applications/harbormaster/xaction/build/HarbormasterBuildMessageRestartTransaction.php',
+ 'HarbormasterBuildMessageResumeTransaction' => 'applications/harbormaster/xaction/build/HarbormasterBuildMessageResumeTransaction.php',
+ 'HarbormasterBuildMessageTransaction' => 'applications/harbormaster/xaction/build/HarbormasterBuildMessageTransaction.php',
'HarbormasterBuildPHIDType' => 'applications/harbormaster/phid/HarbormasterBuildPHIDType.php',
'HarbormasterBuildPlan' => 'applications/harbormaster/storage/configuration/HarbormasterBuildPlan.php',
'HarbormasterBuildPlanBehavior' => 'applications/harbormaster/plan/HarbormasterBuildPlanBehavior.php',
@@ -1458,6 +1467,7 @@
'HarbormasterBuildTransaction' => 'applications/harbormaster/storage/HarbormasterBuildTransaction.php',
'HarbormasterBuildTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php',
'HarbormasterBuildTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildTransactionQuery.php',
+ 'HarbormasterBuildTransactionType' => 'applications/harbormaster/xaction/build/HarbormasterBuildTransactionType.php',
'HarbormasterBuildUnitMessage' => 'applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php',
'HarbormasterBuildUnitMessageQuery' => 'applications/harbormaster/query/HarbormasterBuildUnitMessageQuery.php',
'HarbormasterBuildView' => 'applications/harbormaster/view/HarbormasterBuildView.php',
@@ -1466,9 +1476,12 @@
'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php',
'HarbormasterBuildableActionController' => 'applications/harbormaster/controller/HarbormasterBuildableActionController.php',
'HarbormasterBuildableAdapterInterface' => 'applications/harbormaster/herald/HarbormasterBuildableAdapterInterface.php',
+ 'HarbormasterBuildableEditAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildableEditAPIMethod.php',
+ 'HarbormasterBuildableEditEngine' => 'applications/harbormaster/editor/HarbormasterBuildableEditEngine.php',
'HarbormasterBuildableEngine' => 'applications/harbormaster/engine/HarbormasterBuildableEngine.php',
'HarbormasterBuildableInterface' => 'applications/harbormaster/interface/HarbormasterBuildableInterface.php',
'HarbormasterBuildableListController' => 'applications/harbormaster/controller/HarbormasterBuildableListController.php',
+ 'HarbormasterBuildableMessageTransaction' => 'applications/harbormaster/xaction/buildable/HarbormasterBuildableMessageTransaction.php',
'HarbormasterBuildablePHIDType' => 'applications/harbormaster/phid/HarbormasterBuildablePHIDType.php',
'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php',
'HarbormasterBuildableSearchAPIMethod' => 'applications/harbormaster/conduit/HarbormasterBuildableSearchAPIMethod.php',
@@ -1477,14 +1490,15 @@
'HarbormasterBuildableTransaction' => 'applications/harbormaster/storage/HarbormasterBuildableTransaction.php',
'HarbormasterBuildableTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php',
'HarbormasterBuildableTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildableTransactionQuery.php',
+ 'HarbormasterBuildableTransactionType' => 'applications/harbormaster/xaction/buildable/HarbormasterBuildableTransactionType.php',
'HarbormasterBuildableViewController' => 'applications/harbormaster/controller/HarbormasterBuildableViewController.php',
- 'HarbormasterBuildkiteBuildStepImplementation' => 'applications/harbormaster/integration/buildkite/HarbormasterBuildkiteBuildStepImplementation.php',
+ 'HarbormasterBuildkiteBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php',
'HarbormasterBuildkiteBuildableInterface' => 'applications/harbormaster/interface/HarbormasterBuildkiteBuildableInterface.php',
- 'HarbormasterBuildkiteHookHandler' => 'applications/harbormaster/integration/buildkite/HarbormasterBuildkiteHookHandler.php',
+ 'HarbormasterBuildkiteHookController' => 'applications/harbormaster/controller/HarbormasterBuildkiteHookController.php',
'HarbormasterBuiltinBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterBuiltinBuildStepGroup.php',
- 'HarbormasterCircleCIBuildStepImplementation' => 'applications/harbormaster/integration/circleci/HarbormasterCircleCIBuildStepImplementation.php',
+ 'HarbormasterCircleCIBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php',
'HarbormasterCircleCIBuildableInterface' => 'applications/harbormaster/interface/HarbormasterCircleCIBuildableInterface.php',
- 'HarbormasterCircleCIHookHandler' => 'applications/harbormaster/integration/circleci/HarbormasterCircleCIHookHandler.php',
+ 'HarbormasterCircleCIHookController' => 'applications/harbormaster/controller/HarbormasterCircleCIHookController.php',
'HarbormasterConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php',
'HarbormasterControlBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterControlBuildStepGroup.php',
'HarbormasterController' => 'applications/harbormaster/controller/HarbormasterController.php',
@@ -1498,8 +1512,6 @@
'HarbormasterExternalBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterExternalBuildStepGroup.php',
'HarbormasterFileArtifact' => 'applications/harbormaster/artifact/HarbormasterFileArtifact.php',
'HarbormasterHTTPRequestBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php',
- 'HarbormasterHookController' => 'applications/harbormaster/controller/HarbormasterHookController.php',
- 'HarbormasterHookHandler' => 'applications/harbormaster/integration/HarbormasterHookHandler.php',
'HarbormasterHostArtifact' => 'applications/harbormaster/artifact/HarbormasterHostArtifact.php',
'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'applications/harbormaster/step/HarbormasterLeaseWorkingCopyBuildStepImplementation.php',
'HarbormasterLintMessagesController' => 'applications/harbormaster/controller/HarbormasterLintMessagesController.php',
@@ -1513,6 +1525,7 @@
'HarbormasterManagementUpdateWorkflow' => 'applications/harbormaster/management/HarbormasterManagementUpdateWorkflow.php',
'HarbormasterManagementWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWorkflow.php',
'HarbormasterManagementWriteLogWorkflow' => 'applications/harbormaster/management/HarbormasterManagementWriteLogWorkflow.php',
+ 'HarbormasterMessageException' => 'applications/harbormaster/exception/HarbormasterMessageException.php',
'HarbormasterMessageType' => 'applications/harbormaster/engine/HarbormasterMessageType.php',
'HarbormasterObject' => 'applications/harbormaster/storage/HarbormasterObject.php',
'HarbormasterOtherBuildStepGroup' => 'applications/harbormaster/stepgroup/HarbormasterOtherBuildStepGroup.php',
@@ -1530,7 +1543,6 @@
'HarbormasterQueryBuildsConduitAPIMethod' => 'applications/harbormaster/conduit/HarbormasterQueryBuildsConduitAPIMethod.php',
'HarbormasterQueryBuildsSearchEngineAttachment' => 'applications/harbormaster/engineextension/HarbormasterQueryBuildsSearchEngineAttachment.php',
'HarbormasterRemarkupRule' => 'applications/harbormaster/remarkup/HarbormasterRemarkupRule.php',
- 'HarbormasterRestartException' => 'applications/harbormaster/exception/HarbormasterRestartException.php',
'HarbormasterRunBuildPlansHeraldAction' => 'applications/harbormaster/herald/HarbormasterRunBuildPlansHeraldAction.php',
'HarbormasterSchemaSpec' => 'applications/harbormaster/storage/HarbormasterSchemaSpec.php',
'HarbormasterScratchTable' => 'applications/harbormaster/storage/HarbormasterScratchTable.php',
@@ -3973,6 +3985,7 @@
'PhabricatorOwnersOwner' => 'applications/owners/storage/PhabricatorOwnersOwner.php',
'PhabricatorOwnersPackage' => 'applications/owners/storage/PhabricatorOwnersPackage.php',
'PhabricatorOwnersPackageAuditingTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageAuditingTransaction.php',
+ 'PhabricatorOwnersPackageAuthorityTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageAuthorityTransaction.php',
'PhabricatorOwnersPackageAutoreviewTransaction' => 'applications/owners/xaction/PhabricatorOwnersPackageAutoreviewTransaction.php',
'PhabricatorOwnersPackageContextFreeGrammar' => 'applications/owners/lipsum/PhabricatorOwnersPackageContextFreeGrammar.php',
'PhabricatorOwnersPackageDatasource' => 'applications/owners/typeahead/PhabricatorOwnersPackageDatasource.php',
@@ -5763,6 +5776,7 @@
'PhutilRemarkupEngine' => 'infrastructure/markup/remarkup/PhutilRemarkupEngine.php',
'PhutilRemarkupEngineTestCase' => 'infrastructure/markup/remarkup/__tests__/PhutilRemarkupEngineTestCase.php',
'PhutilRemarkupEscapeRemarkupRule' => 'infrastructure/markup/markuprule/PhutilRemarkupEscapeRemarkupRule.php',
+ 'PhutilRemarkupEvalRule' => 'infrastructure/markup/markuprule/PhutilRemarkupEvalRule.php',
'PhutilRemarkupHeaderBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupHeaderBlockRule.php',
'PhutilRemarkupHighlightRule' => 'infrastructure/markup/markuprule/PhutilRemarkupHighlightRule.php',
'PhutilRemarkupHorizontalRuleBlockRule' => 'infrastructure/markup/blockrule/PhutilRemarkupHorizontalRuleBlockRule.php',
@@ -6134,6 +6148,7 @@
'AlmanacInterfaceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacInterfaceTableView' => 'AphrontView',
'AlmanacInterfaceTransaction' => 'AlmanacModularTransaction',
+ 'AlmanacInterfaceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacInterfaceTransactionType' => 'AlmanacTransactionType',
'AlmanacKeys' => 'Phobject',
'AlmanacManageClusterServicesCapability' => 'PhabricatorPolicyCapability',
@@ -6424,6 +6439,7 @@
'ChatLogConduitAPIMethod' => 'ConduitAPIMethod',
'ChatLogQueryConduitAPIMethod' => 'ChatLogConduitAPIMethod',
'ChatLogRecordConduitAPIMethod' => 'ChatLogConduitAPIMethod',
+ 'ConduitAPIDocumentationPage' => 'Phobject',
'ConduitAPIMethod' => array(
'Phobject',
'PhabricatorPolicyInterface',
@@ -7047,6 +7063,7 @@
'DiffusionLowLevelResolveRefsQuery' => 'DiffusionLowLevelQuery',
'DiffusionMercurialBlameQuery' => 'DiffusionBlameQuery',
'DiffusionMercurialCommandEngine' => 'DiffusionCommandEngine',
+ 'DiffusionMercurialCommandEngineTests' => 'PhabricatorTestCase',
'DiffusionMercurialFileContentQuery' => 'DiffusionFileContentQuery',
'DiffusionMercurialFlagInjectionException' => 'Exception',
'DiffusionMercurialRawDiffQuery' => 'DiffusionRawDiffQuery',
@@ -7601,8 +7618,9 @@
'HarbormasterBuildArtifactPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildArtifactQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildAutoplan' => 'Phobject',
- 'HarbormasterBuildCommand' => 'HarbormasterDAO',
'HarbormasterBuildDependencyDatasource' => 'PhabricatorTypeaheadDatasource',
+ 'HarbormasterBuildEditAPIMethod' => 'PhabricatorEditEngineAPIMethod',
+ 'HarbormasterBuildEditEngine' => 'PhabricatorEditEngine',
'HarbormasterBuildEngine' => 'Phobject',
'HarbormasterBuildFailureException' => 'Exception',
'HarbormasterBuildGraph' => 'AbstractDirectedGraph',
@@ -7631,7 +7649,12 @@
'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface',
),
+ 'HarbormasterBuildMessageAbortTransaction' => 'HarbormasterBuildMessageTransaction',
+ 'HarbormasterBuildMessagePauseTransaction' => 'HarbormasterBuildMessageTransaction',
'HarbormasterBuildMessageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
+ 'HarbormasterBuildMessageRestartTransaction' => 'HarbormasterBuildMessageTransaction',
+ 'HarbormasterBuildMessageResumeTransaction' => 'HarbormasterBuildMessageTransaction',
+ 'HarbormasterBuildMessageTransaction' => 'HarbormasterBuildTransactionType',
'HarbormasterBuildPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildPlan' => array(
'HarbormasterDAO',
@@ -7702,9 +7725,10 @@
'HarbormasterBuildTargetPHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildTargetQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildTargetSearchEngine' => 'PhabricatorApplicationSearchEngine',
- 'HarbormasterBuildTransaction' => 'PhabricatorApplicationTransaction',
+ 'HarbormasterBuildTransaction' => 'PhabricatorModularTransaction',
'HarbormasterBuildTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'HarbormasterBuildTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+ 'HarbormasterBuildTransactionType' => 'PhabricatorModularTransactionType',
'HarbormasterBuildUnitMessage' => array(
'HarbormasterDAO',
'PhabricatorPolicyInterface',
@@ -7722,22 +7746,26 @@
'PhabricatorDestructibleInterface',
),
'HarbormasterBuildableActionController' => 'HarbormasterController',
+ 'HarbormasterBuildableEditAPIMethod' => 'PhabricatorEditEngineAPIMethod',
+ 'HarbormasterBuildableEditEngine' => 'PhabricatorEditEngine',
'HarbormasterBuildableEngine' => 'Phobject',
'HarbormasterBuildableListController' => 'HarbormasterController',
+ 'HarbormasterBuildableMessageTransaction' => 'HarbormasterBuildableTransactionType',
'HarbormasterBuildablePHIDType' => 'PhabricatorPHIDType',
'HarbormasterBuildableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'HarbormasterBuildableSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'HarbormasterBuildableSearchEngine' => 'PhabricatorApplicationSearchEngine',
'HarbormasterBuildableStatus' => 'Phobject',
- 'HarbormasterBuildableTransaction' => 'PhabricatorApplicationTransaction',
+ 'HarbormasterBuildableTransaction' => 'PhabricatorModularTransaction',
'HarbormasterBuildableTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
'HarbormasterBuildableTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
+ 'HarbormasterBuildableTransactionType' => 'PhabricatorModularTransactionType',
'HarbormasterBuildableViewController' => 'HarbormasterController',
'HarbormasterBuildkiteBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
- 'HarbormasterBuildkiteHookHandler' => 'HarbormasterHookHandler',
+ 'HarbormasterBuildkiteHookController' => 'HarbormasterController',
'HarbormasterBuiltinBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterCircleCIBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
- 'HarbormasterCircleCIHookHandler' => 'HarbormasterHookHandler',
+ 'HarbormasterCircleCIHookController' => 'HarbormasterController',
'HarbormasterConduitAPIMethod' => 'ConduitAPIMethod',
'HarbormasterControlBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterController' => 'PhabricatorController',
@@ -7751,8 +7779,6 @@
'HarbormasterExternalBuildStepGroup' => 'HarbormasterBuildStepGroup',
'HarbormasterFileArtifact' => 'HarbormasterArtifact',
'HarbormasterHTTPRequestBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
- 'HarbormasterHookController' => 'HarbormasterController',
- 'HarbormasterHookHandler' => 'Phobject',
'HarbormasterHostArtifact' => 'HarbormasterDrydockLeaseArtifact',
'HarbormasterLeaseWorkingCopyBuildStepImplementation' => 'HarbormasterBuildStepImplementation',
'HarbormasterLintMessagesController' => 'HarbormasterController',
@@ -7766,6 +7792,7 @@
'HarbormasterManagementUpdateWorkflow' => 'HarbormasterManagementWorkflow',
'HarbormasterManagementWorkflow' => 'PhabricatorManagementWorkflow',
'HarbormasterManagementWriteLogWorkflow' => 'HarbormasterManagementWorkflow',
+ 'HarbormasterMessageException' => 'Exception',
'HarbormasterMessageType' => 'Phobject',
'HarbormasterObject' => 'HarbormasterDAO',
'HarbormasterOtherBuildStepGroup' => 'HarbormasterBuildStepGroup',
@@ -7783,7 +7810,6 @@
'HarbormasterQueryBuildsConduitAPIMethod' => 'HarbormasterConduitAPIMethod',
'HarbormasterQueryBuildsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'HarbormasterRemarkupRule' => 'PhabricatorObjectRemarkupRule',
- 'HarbormasterRestartException' => 'Exception',
'HarbormasterRunBuildPlansHeraldAction' => 'HeraldAction',
'HarbormasterSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'HarbormasterScratchTable' => 'HarbormasterDAO',
@@ -10598,6 +10624,7 @@
'PhabricatorNgramsInterface',
),
'PhabricatorOwnersPackageAuditingTransaction' => 'PhabricatorOwnersPackageTransactionType',
+ 'PhabricatorOwnersPackageAuthorityTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackageAutoreviewTransaction' => 'PhabricatorOwnersPackageTransactionType',
'PhabricatorOwnersPackageContextFreeGrammar' => 'PhutilContextFreeGrammar',
'PhabricatorOwnersPackageDatasource' => 'PhabricatorTypeaheadDatasource',
@@ -12758,6 +12785,7 @@
'PhutilRemarkupEngine' => 'PhutilMarkupEngine',
'PhutilRemarkupEngineTestCase' => 'PhutilTestCase',
'PhutilRemarkupEscapeRemarkupRule' => 'PhutilRemarkupRule',
+ 'PhutilRemarkupEvalRule' => 'PhutilRemarkupRule',
'PhutilRemarkupHeaderBlockRule' => 'PhutilRemarkupBlockRule',
'PhutilRemarkupHighlightRule' => 'PhutilRemarkupRule',
'PhutilRemarkupHorizontalRuleBlockRule' => 'PhutilRemarkupBlockRule',
diff --git a/src/applications/almanac/query/AlmanacInterfaceTransactionQuery.php b/src/applications/almanac/query/AlmanacInterfaceTransactionQuery.php
new file mode 100644
--- /dev/null
+++ b/src/applications/almanac/query/AlmanacInterfaceTransactionQuery.php
@@ -0,0 +1,10 @@
+<?php
+
+final class AlmanacInterfaceTransactionQuery
+ extends PhabricatorApplicationTransactionQuery {
+
+ public function getTemplateApplicationTransaction() {
+ return new AlmanacInterfaceTransaction();
+ }
+
+}
diff --git a/src/applications/almanac/util/__tests__/AlmanacNamesTestCase.php b/src/applications/almanac/util/__tests__/AlmanacNamesTestCase.php
--- a/src/applications/almanac/util/__tests__/AlmanacNamesTestCase.php
+++ b/src/applications/almanac/util/__tests__/AlmanacNamesTestCase.php
@@ -30,7 +30,7 @@
'abc' => true,
'a.b' => true,
- 'db.phacility.instance' => true,
+ 'db.companyname.instance' => true,
'web002.useast.example.com' => true,
'master.example-corp.com' => true,
diff --git a/src/applications/auth/controller/PhabricatorAuthStartController.php b/src/applications/auth/controller/PhabricatorAuthStartController.php
--- a/src/applications/auth/controller/PhabricatorAuthStartController.php
+++ b/src/applications/auth/controller/PhabricatorAuthStartController.php
@@ -252,7 +252,7 @@
$message = pht(
'ERROR: You are making a Conduit API request to "%s", but the correct '.
- 'HTTP request path to use in order to access a COnduit method is "%s" '.
+ 'HTTP request path to use in order to access a Conduit method is "%s" '.
'(for example, "%s"). Check your configuration.',
$request_path,
$conduit_path,
diff --git a/src/applications/base/PhabricatorApplication.php b/src/applications/base/PhabricatorApplication.php
--- a/src/applications/base/PhabricatorApplication.php
+++ b/src/applications/base/PhabricatorApplication.php
@@ -135,10 +135,9 @@
/**
- * Returns true if an application is first-party (developed by Phacility)
- * and false otherwise.
+ * Returns true if an application is first-party and false otherwise.
*
- * @return bool True if this application is developed by Phacility.
+ * @return bool True if this application is first-party.
*/
final public function isFirstParty() {
$where = id(new ReflectionClass($this))->getFileName();
diff --git a/src/applications/calendar/parser/ics/PhutilICSWriter.php b/src/applications/calendar/parser/ics/PhutilICSWriter.php
--- a/src/applications/calendar/parser/ics/PhutilICSWriter.php
+++ b/src/applications/calendar/parser/ics/PhutilICSWriter.php
@@ -128,11 +128,15 @@
$properties[] = $this->newTextProperty(
'PRODID',
- '-//Phacility//Phabricator//EN');
+ self::getICSPRODID());
return $properties;
}
+ public static function getICSPRODID() {
+ return '-//Phacility//Phabricator//EN';
+ }
+
private function getEventNodeProperties(PhutilCalendarEventNode $event) {
$properties = array();
diff --git a/src/applications/calendar/parser/ics/__tests__/PhutilICSWriterTestCase.php b/src/applications/calendar/parser/ics/__tests__/PhutilICSWriterTestCase.php
--- a/src/applications/calendar/parser/ics/__tests__/PhutilICSWriterTestCase.php
+++ b/src/applications/calendar/parser/ics/__tests__/PhutilICSWriterTestCase.php
@@ -138,6 +138,12 @@
private function assertICS($name, $actual) {
$path = dirname(__FILE__).'/data/'.$name;
$data = Filesystem::readFile($path);
+
+ $data = str_replace(
+ '${PRODID}',
+ PhutilICSWriter::getICSPRODID(),
+ $data);
+
$this->assertEqual($data, $actual, pht('ICS: %s', $name));
}
diff --git a/src/applications/calendar/parser/ics/__tests__/data/writer-christmas.ics b/src/applications/calendar/parser/ics/__tests__/data/writer-christmas.ics
--- a/src/applications/calendar/parser/ics/__tests__/data/writer-christmas.ics
+++ b/src/applications/calendar/parser/ics/__tests__/data/writer-christmas.ics
@@ -1,6 +1,6 @@
BEGIN:VCALENDAR
VERSION:2.0
-PRODID:-//Phacility//Phabricator//EN
+PRODID:${PRODID}
BEGIN:VEVENT
UID:christmas-day
CREATED:20160901T232425Z
diff --git a/src/applications/calendar/parser/ics/__tests__/data/writer-office-party.ics b/src/applications/calendar/parser/ics/__tests__/data/writer-office-party.ics
--- a/src/applications/calendar/parser/ics/__tests__/data/writer-office-party.ics
+++ b/src/applications/calendar/parser/ics/__tests__/data/writer-office-party.ics
@@ -1,6 +1,6 @@
BEGIN:VCALENDAR
VERSION:2.0
-PRODID:-//Phacility//Phabricator//EN
+PRODID:${PRODID}
BEGIN:VEVENT
UID:office-party
CREATED:20161001T120000Z
diff --git a/src/applications/calendar/parser/ics/__tests__/data/writer-recurring-christmas.ics b/src/applications/calendar/parser/ics/__tests__/data/writer-recurring-christmas.ics
--- a/src/applications/calendar/parser/ics/__tests__/data/writer-recurring-christmas.ics
+++ b/src/applications/calendar/parser/ics/__tests__/data/writer-recurring-christmas.ics
@@ -1,6 +1,6 @@
BEGIN:VCALENDAR
VERSION:2.0
-PRODID:-//Phacility//Phabricator//EN
+PRODID:${PRODID}
BEGIN:VEVENT
UID:recurring-christmas
CREATED:20001225T000000Z
diff --git a/src/applications/calendar/parser/ics/__tests__/data/writer-tea-time.ics b/src/applications/calendar/parser/ics/__tests__/data/writer-tea-time.ics
--- a/src/applications/calendar/parser/ics/__tests__/data/writer-tea-time.ics
+++ b/src/applications/calendar/parser/ics/__tests__/data/writer-tea-time.ics
@@ -1,6 +1,6 @@
BEGIN:VCALENDAR
VERSION:2.0
-PRODID:-//Phacility//Phabricator//EN
+PRODID:${PRODID}
BEGIN:VEVENT
UID:tea-time
CREATED:20160915T070000Z
diff --git a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
--- a/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
+++ b/src/applications/conduit/controller/PhabricatorConduitConsoleController.php
@@ -88,23 +88,118 @@
$crumbs->addTextCrumb($method->getAPIMethodName());
$crumbs->setBorder(true);
+ $documentation_pages = $method->getDocumentationPages($viewer);
+
+ $documentation_view = $this->newDocumentationView(
+ $method,
+ $documentation_pages);
+
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
+
+ id(new PhabricatorAnchorView())
+ ->setAnchorName('overview'),
$info_box,
- $method->getMethodDocumentation(),
+
+ id(new PhabricatorAnchorView())
+ ->setAnchorName('documentation'),
+ $documentation_view,
+
+ id(new PhabricatorAnchorView())
+ ->setAnchorName('call'),
$form_box,
+
+ id(new PhabricatorAnchorView())
+ ->setAnchorName('examples'),
$this->renderExampleBox($method, null),
));
$title = $method->getAPIMethodName();
+ $nav = $this->newNavigationView($method, $documentation_pages);
+
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
+ ->setNavigation($nav)
->appendChild($view);
}
+ private function newDocumentationView(
+ ConduitAPIMethod $method,
+ array $documentation_pages) {
+ assert_instances_of($documentation_pages, 'ConduitAPIDocumentationPage');
+
+ $viewer = $this->getViewer();
+
+ $description_properties = id(new PHUIPropertyListView());
+
+ $description_properties->addTextContent(
+ new PHUIRemarkupView($viewer, $method->getMethodDescription()));
+
+ $description_box = id(new PHUIObjectBoxView())
+ ->setHeaderText(pht('Method Description'))
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+ ->appendChild($description_properties);
+
+ $view = array();
+ $view[] = $description_box;
+
+ foreach ($documentation_pages as $page) {
+ $view[] = $page->newView();
+ }
+
+ return $view;
+ }
+
+ private function newNavigationView(
+ ConduitAPIMethod $method,
+ array $documentation_pages) {
+ assert_instances_of($documentation_pages, 'ConduitAPIDocumentationPage');
+
+ $console_uri = urisprintf(
+ '/method/%s/',
+ $method->getAPIMethodName());
+ $console_uri = $this->getApplicationURI($console_uri);
+ $console_uri = new PhutilURI($console_uri);
+
+ $nav = id(new AphrontSideNavFilterView())
+ ->setBaseURI($console_uri);
+
+ $nav->selectFilter(null);
+
+ $nav->newLink('overview')
+ ->setHref('#overview')
+ ->setName(pht('Overview'))
+ ->setIcon('fa-list');
+
+ $nav->newLink('documentation')
+ ->setHref('#documentation')
+ ->setName(pht('Documentation'))
+ ->setIcon('fa-book');
+
+ foreach ($documentation_pages as $page) {
+ $nav->newLink($page->getAnchor())
+ ->setHref('#'.$page->getAnchor())
+ ->setName($page->getName())
+ ->setIcon($page->getIconIcon())
+ ->setIndented(true);
+ }
+
+ $nav->newLink('call')
+ ->setHref('#call')
+ ->setName(pht('Call Method'))
+ ->setIcon('fa-play');
+
+ $nav->newLink('examples')
+ ->setHref('#examples')
+ ->setName(pht('Examples'))
+ ->setIcon('fa-folder-open-o');
+
+ return $nav;
+ }
+
private function buildMethodProperties(ConduitAPIMethod $method) {
$viewer = $this->getViewer();
@@ -171,7 +266,6 @@
pht('Errors'),
$error_description);
-
$scope = $method->getRequiredScope();
switch ($scope) {
case ConduitAPIMethod::SCOPE_ALWAYS:
@@ -201,11 +295,6 @@
$oauth_description,
));
- $view->addSectionHeader(
- pht('Description'), PHUIPropertyListView::ICON_SUMMARY);
- $view->addTextContent(
- new PHUIRemarkupView($viewer, $method->getMethodDescription()));
-
return $view;
}
diff --git a/src/applications/conduit/data/ConduitAPIDocumentationPage.php b/src/applications/conduit/data/ConduitAPIDocumentationPage.php
new file mode 100644
--- /dev/null
+++ b/src/applications/conduit/data/ConduitAPIDocumentationPage.php
@@ -0,0 +1,61 @@
+<?php
+
+final class ConduitAPIDocumentationPage
+ extends Phobject {
+
+ private $name;
+ private $anchor;
+ private $iconIcon;
+ private $content = array();
+
+ public function setName($name) {
+ $this->name = $name;
+ return $this;
+ }
+
+ public function getName() {
+ return $this->name;
+ }
+
+ public function setAnchor($anchor) {
+ $this->anchor = $anchor;
+ return $this;
+ }
+
+ public function getAnchor() {
+ return $this->anchor;
+ }
+
+ public function setContent($content) {
+ $this->content = $content;
+ return $this;
+ }
+
+ public function getContent() {
+ return $this->content;
+ }
+
+ public function setIconIcon($icon_icon) {
+ $this->iconIcon = $icon_icon;
+ return $this;
+ }
+
+ public function getIconIcon() {
+ return $this->iconIcon;
+ }
+
+ public function newView() {
+ $anchor_name = $this->getAnchor();
+ $anchor_view = id(new PhabricatorAnchorView())
+ ->setAnchorName($anchor_name);
+
+ $content = $this->content;
+
+ return array(
+ $anchor_view,
+ $content,
+ );
+ }
+
+
+}
diff --git a/src/applications/conduit/method/ConduitAPIMethod.php b/src/applications/conduit/method/ConduitAPIMethod.php
--- a/src/applications/conduit/method/ConduitAPIMethod.php
+++ b/src/applications/conduit/method/ConduitAPIMethod.php
@@ -40,8 +40,33 @@
*/
abstract public function getMethodDescription();
- public function getMethodDocumentation() {
- return null;
+ final public function getDocumentationPages(PhabricatorUser $viewer) {
+ $pages = $this->newDocumentationPages($viewer);
+ return $pages;
+ }
+
+ protected function newDocumentationPages(PhabricatorUser $viewer) {
+ return array();
+ }
+
+ final protected function newDocumentationPage(PhabricatorUser $viewer) {
+ return id(new ConduitAPIDocumentationPage())
+ ->setIconIcon('fa-chevron-right');
+ }
+
+ final protected function newDocumentationBoxPage(
+ PhabricatorUser $viewer,
+ $title,
+ $content) {
+
+ $box_view = id(new PHUIObjectBoxView())
+ ->setHeaderText($title)
+ ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
+ ->setTable($content);
+
+ return $this->newDocumentationPage($viewer)
+ ->setName($title)
+ ->setContent($box_view);
}
abstract protected function defineParamTypes();
diff --git a/src/applications/config/check/PhabricatorBinariesSetupCheck.php b/src/applications/config/check/PhabricatorBinariesSetupCheck.php
--- a/src/applications/config/check/PhabricatorBinariesSetupCheck.php
+++ b/src/applications/config/check/PhabricatorBinariesSetupCheck.php
@@ -120,17 +120,11 @@
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$bad_versions = array(
- // We need 1.9 for HTTP cloning, see T3046.
- '< 1.9' => pht(
- 'The minimum supported version of Mercurial is 1.9, which was '.
- 'released in 2011.'),
- '= 2.1' => pht(
- 'This version of Mercurial returns a bad exit code '.
- 'after a successful pull.'),
- '= 2.2' => pht(
- 'This version of Mercurial has a significant memory leak, fixed '.
- 'in 2.2.1. Pushing fails with this version as well; see %s.',
- 'T3046#54922'),
+ // We need 2.4 for utilizing `{p1node}` keyword in templates, see
+ // D21679 and D21681.
+ '< 2.4' => pht(
+ 'The minimum supported version of Mercurial is 2.4, which was '.
+ 'released in 2012.'),
);
break;
}
diff --git a/src/applications/config/controller/PhabricatorConfigConsoleController.php b/src/applications/config/controller/PhabricatorConfigConsoleController.php
--- a/src/applications/config/controller/PhabricatorConfigConsoleController.php
+++ b/src/applications/config/controller/PhabricatorConfigConsoleController.php
@@ -56,7 +56,7 @@
->setBorder(true);
$box = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Phabricator Configuation'))
+ ->setHeaderText(pht('Phabricator Configuration'))
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setObjectList($menu);
@@ -72,7 +72,7 @@
->setFooter($launcher_view);
return $this->newPage()
- ->setTitle(pht('Phabricator Configuation'))
+ ->setTitle(pht('Phabricator Configuration'))
->setCrumbs($crumbs)
->appendChild($view);
}
diff --git a/src/applications/differential/controller/DifferentialRevisionViewController.php b/src/applications/differential/controller/DifferentialRevisionViewController.php
--- a/src/applications/differential/controller/DifferentialRevisionViewController.php
+++ b/src/applications/differential/controller/DifferentialRevisionViewController.php
@@ -1282,7 +1282,7 @@
}
private function buildUnitMessagesView(
- $diff,
+ DifferentialDiff $diff,
DifferentialRevision $revision) {
$viewer = $this->getViewer();
@@ -1310,14 +1310,8 @@
return null;
}
- $excuse = null;
- if ($diff->hasDiffProperty('arc:unit-excuse')) {
- $excuse = $diff->getProperty('arc:unit-excuse');
- }
-
return id(new HarbormasterUnitSummaryView())
->setViewer($viewer)
- ->setExcuse($excuse)
->setBuildable($diff->getBuildable())
->setUnitMessages($diff->getUnitMessages())
->setLimit(5)
diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php
--- a/src/applications/differential/storage/DifferentialDiff.php
+++ b/src/applications/differential/storage/DifferentialDiff.php
@@ -42,7 +42,7 @@
private $unsavedChangesets = array();
private $changesets = self::ATTACHABLE;
private $revision = self::ATTACHABLE;
- private $properties = array();
+ private $properties = self::ATTACHABLE;
private $buildable = self::ATTACHABLE;
private $unitMessages = self::ATTACHABLE;
@@ -338,6 +338,9 @@
}
public function attachProperty($key, $value) {
+ if (!is_array($this->properties)) {
+ $this->properties = array();
+ }
$this->properties[$key] = $value;
return $this;
}
diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php
--- a/src/applications/differential/storage/DifferentialRevision.php
+++ b/src/applications/differential/storage/DifferentialRevision.php
@@ -311,9 +311,17 @@
// which the actor may be able to use their authority over to gain the
// ability to force-accept for other packages. This query doesn't apply
// dominion rules yet, and we'll bypass those rules later on.
+
+ // See T13657. We ignore "watcher" packages which don't grant their owners
+ // permission to force accept anything.
+
$authority_query = id(new PhabricatorOwnersPackageQuery())
->setViewer($viewer)
->withStatuses(array(PhabricatorOwnersPackage::STATUS_ACTIVE))
+ ->withAuthorityModes(
+ array(
+ PhabricatorOwnersPackage::AUTHORITY_STRONG,
+ ))
->withAuthorityPHIDs(array($viewer->getPHID()))
->withControl($repository_phid, $paths);
$authority_packages = $authority_query->execute();
diff --git a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php
--- a/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php
+++ b/src/applications/diffusion/conduit/DiffusionHistoryQueryConduitAPIMethod.php
@@ -136,28 +136,33 @@
// stop history (this is more consistent with the Mercurial worldview of
// branches).
+ $path_args = array();
if (strlen($path)) {
- $path_arg = csprintf('%s', $path);
+ $path_args[] = $path;
$revset_arg = hgsprintf(
'reverse(ancestors(%s))',
$commit_hash);
} else {
- $path_arg = '';
$revset_arg = hgsprintf(
'reverse(ancestors(%s)) and branch(%s)',
- $drequest->getBranch(),
- $commit_hash);
+ $commit_hash,
+ $drequest->getBranch());
+ }
+
+ $hg_analyzer = PhutilBinaryAnalyzer::getForBinary('hg');
+ if ($hg_analyzer->isMercurialTemplatePnodeAvailable()) {
+ $hg_log_template = '{node} {p1.node} {p2.node}\\n';
+ } else {
+ $hg_log_template = '{node} {p1node} {p2node}\\n';
}
list($stdout) = $repository->execxLocalCommand(
- 'log --debug --template %s --limit %d --rev %s -- %C',
- '{node};{parents}\\n',
+ 'log --template %s --limit %d --rev %s -- %Ls',
+ $hg_log_template,
($offset + $limit), // No '--skip' in Mercurial.
$revset_arg,
- $path_arg);
+ $path_args);
- $stdout = DiffusionMercurialCommandEngine::filterMercurialDebugOutput(
- $stdout);
$lines = explode("\n", trim($stdout));
$lines = array_slice($lines, $offset);
@@ -166,28 +171,19 @@
$last = null;
foreach (array_reverse($lines) as $line) {
- list($hash, $parents) = explode(';', $line);
- $parents = trim($parents);
- if (!$parents) {
- if ($last === null) {
- $parent_map[$hash] = array('...');
- } else {
- $parent_map[$hash] = array($last);
- }
- } else {
- $parents = preg_split('/\s+/', $parents);
- foreach ($parents as $parent) {
- list($plocal, $phash) = explode(':', $parent);
- if (!preg_match('/^0+$/', $phash)) {
- $parent_map[$hash][] = $phash;
- }
- }
- // This may happen for the zeroth commit in repository, both hashes
- // are "000000000...".
- if (empty($parent_map[$hash])) {
- $parent_map[$hash] = array('...');
+ $parts = explode(' ', trim($line));
+ $hash = $parts[0];
+ $parents = array_slice($parts, 1, 2);
+ foreach ($parents as $parent) {
+ if (!preg_match('/^0+\z/', $parent)) {
+ $parent_map[$hash][] = $parent;
}
}
+ // This may happen for the zeroth commit in repository, both hashes
+ // are "000000000...".
+ if (empty($parent_map[$hash])) {
+ $parent_map[$hash] = array('...');
+ }
// The rendering code expects the first commit to be "mainline", like
// Git. Flip the order so it does the right thing.
diff --git a/src/applications/diffusion/controller/DiffusionBrowseController.php b/src/applications/diffusion/controller/DiffusionBrowseController.php
--- a/src/applications/diffusion/controller/DiffusionBrowseController.php
+++ b/src/applications/diffusion/controller/DiffusionBrowseController.php
@@ -298,22 +298,8 @@
$empty_result->setDiffusionBrowseResultSet($results);
$empty_result->setView($request->getStr('view'));
} else {
- $phids = array();
- foreach ($results->getPaths() as $result) {
- $data = $result->getLastCommitData();
- if ($data) {
- if ($data->getCommitDetail('authorPHID')) {
- $phids[$data->getCommitDetail('authorPHID')] = true;
- }
- }
- }
-
- $phids = array_keys($phids);
- $handles = $this->loadViewerHandles($phids);
-
$browse_table = id(new DiffusionBrowseTableView())
->setDiffusionRequest($drequest)
- ->setHandles($handles)
->setPaths($results->getPaths())
->setUser($request->getUser());
diff --git a/src/applications/diffusion/controller/DiffusionRepositoryController.php b/src/applications/diffusion/controller/DiffusionRepositoryController.php
--- a/src/applications/diffusion/controller/DiffusionRepositoryController.php
+++ b/src/applications/diffusion/controller/DiffusionRepositoryController.php
@@ -2,7 +2,6 @@
final class DiffusionRepositoryController extends DiffusionController {
- private $historyFuture;
private $browseFuture;
private $branchButton = null;
private $branchFuture;
@@ -191,15 +190,6 @@
$path = $drequest->getPath();
$futures = array();
- $this->historyFuture = $this->callConduitMethod(
- 'diffusion.historyquery',
- array(
- 'commit' => $commit,
- 'path' => $path,
- 'offset' => 0,
- 'limit' => 15,
- ));
- $futures[] = $this->historyFuture;
$browse_pager = id(new PHUIPagerView())
->readFromRequest($request);
@@ -230,32 +220,8 @@
// Just resolve all the futures before continuing.
}
- $phids = array();
$content = array();
- try {
- $history_results = $this->historyFuture->resolve();
- $history = DiffusionPathChange::newFromConduit(
- $history_results['pathChanges']);
-
- foreach ($history as $item) {
- $data = $item->getCommitData();
- if ($data) {
- if ($data->getCommitDetail('authorPHID')) {
- $phids[$data->getCommitDetail('authorPHID')] = true;
- }
- if ($data->getCommitDetail('committerPHID')) {
- $phids[$data->getCommitDetail('committerPHID')] = true;
- }
- }
- }
- $history_exception = null;
- } catch (Exception $ex) {
- $history_results = null;
- $history = null;
- $history_exception = $ex;
- }
-
try {
$browse_results = $this->browseFuture->resolve();
$browse_results = DiffusionBrowseResultSet::newFromConduit(
@@ -264,18 +230,6 @@
$browse_paths = $browse_results->getPaths();
$browse_paths = $browse_pager->sliceResults($browse_paths);
- foreach ($browse_paths as $item) {
- $data = $item->getLastCommitData();
- if ($data) {
- if ($data->getCommitDetail('authorPHID')) {
- $phids[$data->getCommitDetail('authorPHID')] = true;
- }
- if ($data->getCommitDetail('committerPHID')) {
- $phids[$data->getCommitDetail('committerPHID')] = true;
- }
- }
- }
-
$browse_exception = null;
} catch (Exception $ex) {
$browse_results = null;
@@ -283,9 +237,6 @@
$browse_exception = $ex;
}
- $phids = array_keys($phids);
- $handles = $this->loadViewerHandles($phids);
-
if ($browse_results) {
$readme = $this->renderDirectoryReadme($browse_results);
} else {
@@ -296,7 +247,6 @@
$browse_results,
$browse_paths,
$browse_exception,
- $handles,
$browse_pager);
if ($readme) {
@@ -524,7 +474,6 @@
$browse_results,
$browse_paths,
$browse_exception,
- array $handles,
PHUIPagerView $pager) {
require_celerity_resource('diffusion-icons-css');
@@ -547,8 +496,7 @@
$browse_table = id(new DiffusionBrowseTableView())
->setUser($viewer)
- ->setDiffusionRequest($drequest)
- ->setHandles($handles);
+ ->setDiffusionRequest($drequest);
if ($browse_paths) {
$browse_table->setPaths($browse_paths);
} else {
diff --git a/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php b/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php
--- a/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php
+++ b/src/applications/diffusion/protocol/DiffusionMercurialCommandEngine.php
@@ -71,12 +71,36 @@
//
// Separately, it may fail to write to a different branch cache, and may
// encounter issues reading the branch cache.
+ //
+ // When Mercurial repositories are hosted on external systems with
+ // multi-user environments it's possible that the branch cache is computed
+ // on a revision which does not end up being published. When this happens it
+ // will recompute the cache but also print out "invalid branch cache".
+ //
+ // https://www.mercurial-scm.org/pipermail/mercurial/2014-June/047239.html
+ //
+ // When observing a repository which uses largefiles, the debug output may
+ // also contain extraneous output about largefile changes.
+ //
+ // At some point Mercurial added/improved support for pager used when
+ // command output is large. It includes printing out debug information that
+ // the pager is being started for a command. This seems to happen despite
+ // the output of the command being piped/read from another process.
+ //
+ // When printing color output Mercurial may run into some issue with the
+ // terminal info. This should never happen in Phabricator since color
+ // output should be turned off, however in the event it shows up we should
+ // filter it out anyways.
$ignore = array(
'ignoring untrusted configuration option',
"couldn't write revision branch cache:",
"couldn't write branch cache:",
'invalid branchheads cache',
+ 'invalid branch cache',
+ 'updated patterns: .hglf',
+ 'starting pager for command',
+ 'no terminfo entry for',
);
foreach ($ignore as $key => $pattern) {
diff --git a/src/applications/diffusion/protocol/__tests__/DiffusionMercurialCommandEngineTests.php b/src/applications/diffusion/protocol/__tests__/DiffusionMercurialCommandEngineTests.php
new file mode 100644
--- /dev/null
+++ b/src/applications/diffusion/protocol/__tests__/DiffusionMercurialCommandEngineTests.php
@@ -0,0 +1,89 @@
+<?php
+
+final class DiffusionMercurialCommandEngineTests extends PhabricatorTestCase {
+
+ public function testFilteringDebugOutput() {
+ $map = array(
+ '' => '',
+
+ "quack\n" => "quack\n",
+
+ "ignoring untrusted configuration option x.y = z\nquack\n" =>
+ "quack\n",
+
+ "ignoring untrusted configuration option x.y = z\n".
+ "ignoring untrusted configuration option x.y = z\n".
+ "quack\n" =>
+ "quack\n",
+
+ "ignoring untrusted configuration option x.y = z\n".
+ "ignoring untrusted configuration option x.y = z\n".
+ "ignoring untrusted configuration option x.y = z\n".
+ "quack\n" =>
+ "quack\n",
+
+ "quack\n".
+ "ignoring untrusted configuration option x.y = z\n".
+ "ignoring untrusted configuration option x.y = z\n".
+ "ignoring untrusted configuration option x.y = z\n" =>
+ "quack\n",
+
+ "ignoring untrusted configuration option x.y = z\n".
+ "ignoring untrusted configuration option x.y = z\n".
+ "duck\n".
+ "ignoring untrusted configuration option x.y = z\n".
+ "ignoring untrusted configuration option x.y = z\n".
+ "bread\n".
+ "ignoring untrusted configuration option x.y = z\n".
+ "quack\n" =>
+ "duck\nbread\nquack\n",
+
+ "ignoring untrusted configuration option x.y = z\n".
+ "duckignoring untrusted configuration option x.y = z\n".
+ "quack" =>
+ 'duckquack',
+ );
+
+ foreach ($map as $input => $expect) {
+ $actual = DiffusionMercurialCommandEngine::filterMercurialDebugOutput(
+ $input);
+ $this->assertEqual($expect, $actual, $input);
+ }
+
+ // Output that should be filtered out from the results
+ $output =
+ "ignoring untrusted configuration option\n".
+ "couldn't write revision branch cache:\n".
+ "couldn't write branch cache: blah blah blah\n".
+ "invalid branchheads cache\n".
+ "invalid branch cache (served): tip differs\n".
+ "starting pager for command 'log'\n".
+ "updated patterns: ".
+ ".hglf/project/src/a/b/c/SomeClass.java, ".
+ "project/src/a/b/c/SomeClass.java\n".
+ "no terminfo entry for sitm\n";
+
+ $filtered_output =
+ DiffusionMercurialCommandEngine::filterMercurialDebugOutput($output);
+
+ $this->assertEqual('', $filtered_output);
+
+ // The output that should make it through the filtering
+ $output =
+ "0b33a9e5ceedba14b03214f743957357d7bb46a9;694".
+ ":8b39f63eb209dd2bdfd4bd3d0721a9e38d75a6d3".
+ "-1:0000000000000000000000000000000000000000\n".
+ "8b39f63eb209dd2bdfd4bd3d0721a9e38d75a6d3;693".
+ ":165bce9ce4ccc97024ba19ed5a22f6a066fa6844".
+ "-1:0000000000000000000000000000000000000000\n".
+ "165bce9ce4ccc97024ba19ed5a22f6a066fa6844;692:".
+ "2337bc9e3cf212b3b386b5197801b1c81db64920".
+ "-1:0000000000000000000000000000000000000000\n";
+
+ $filtered_output =
+ DiffusionMercurialCommandEngine::filterMercurialDebugOutput($output);
+
+ $this->assertEqual($output, $filtered_output);
+ }
+
+}
diff --git a/src/applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php b/src/applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php
--- a/src/applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php
+++ b/src/applications/diffusion/query/blame/DiffusionMercurialBlameQuery.php
@@ -6,11 +6,26 @@
$repository = $request->getRepository();
$commit = $request->getCommit();
- // NOTE: We're using "--debug" to make "--changeset" give us the full
- // commit hashes.
+ // NOTE: Using "--template" or "--debug" to get the full commit hashes.
+ $hg_analyzer = PhutilBinaryAnalyzer::getForBinary('hg');
+ if ($hg_analyzer->isMercurialAnnotateTemplatesAvailable()) {
+ // See `hg help annotate --verbose` for more info on the template format.
+ // Use array of arguments so the template line does not need wrapped in
+ // quotes.
+ $template = array(
+ '--template',
+ "{lines % '{node}: {line}'}",
+ );
+ } else {
+ $template = array(
+ '--debug',
+ '--changeset',
+ );
+ }
return $repository->getLocalCommandFuture(
- 'annotate --debug --changeset --rev %s -- %s',
+ 'annotate %Ls --rev %s -- %s',
+ $template,
$commit,
$path);
}
@@ -26,6 +41,21 @@
$lines = phutil_split_lines($stdout);
foreach ($lines as $line) {
+ // If the `--debug` flag was used above instead of `--template` then
+ // there's a good change additional output was included which is not
+ // relevant to the information we want. It should be safe to call this
+ // regardless of whether we used `--debug` or `--template` so there isn't
+ // a need to track which argument was used.
+ $line = DiffusionMercurialCommandEngine::filterMercurialDebugOutput(
+ $line);
+
+ // Just in case new versions of Mercurial add arbitrary output when using
+ // the `--debug`, do a quick sanity check that this line is formatted in
+ // a way we're expecting.
+ if (strpos($line, ':') === false) {
+ phlog(pht('Unexpected output from hg annotate: %s', $line));
+ continue;
+ }
list($commit) = explode(':', $line, 2);
$result[] = $commit;
}
diff --git a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php
--- a/src/applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php
+++ b/src/applications/diffusion/query/lowlevel/DiffusionLowLevelParentsQuery.php
@@ -47,23 +47,23 @@
private function loadMercurialParents() {
$repository = $this->getRepository();
+ $hg_analyzer = PhutilBinaryAnalyzer::getForBinary('hg');
+ if ($hg_analyzer->isMercurialTemplatePnodeAvailable()) {
+ $hg_log_template = '{p1.node} {p2.node}';
+ } else {
+ $hg_log_template = '{p1node} {p2node}';
+ }
+
list($stdout) = $repository->execxLocalCommand(
- 'log --debug --limit 1 --template={parents} --rev %s',
+ 'log --limit 1 --template %s --rev %s',
+ $hg_log_template,
$this->identifier);
- $stdout = DiffusionMercurialCommandEngine::filterMercurialDebugOutput(
- $stdout);
-
$hashes = preg_split('/\s+/', trim($stdout));
foreach ($hashes as $key => $value) {
- // Mercurial parents look like "23:ad9f769d6f786fad9f76d9a" -- we want
- // to strip out the local rev part.
- list($local, $global) = explode(':', $value);
- $hashes[$key] = $global;
-
- // With --debug we get 40-character hashes but also get the "000000..."
- // hash for missing parents; ignore it.
- if (preg_match('/^0+$/', $global)) {
+ // We get 40-character hashes but also get the "000000..." hash for
+ // missing parents; ignore it.
+ if (preg_match('/^0+\z/', $value)) {
unset($hashes[$key]);
}
}
diff --git a/src/applications/diffusion/view/DiffusionBrowseTableView.php b/src/applications/diffusion/view/DiffusionBrowseTableView.php
--- a/src/applications/diffusion/view/DiffusionBrowseTableView.php
+++ b/src/applications/diffusion/view/DiffusionBrowseTableView.php
@@ -3,7 +3,6 @@
final class DiffusionBrowseTableView extends DiffusionView {
private $paths;
- private $handles = array();
public function setPaths(array $paths) {
assert_instances_of($paths, 'DiffusionRepositoryPath');
@@ -11,12 +10,6 @@
return $this;
}
- public function setHandles(array $handles) {
- assert_instances_of($handles, 'PhabricatorObjectHandle');
- $this->handles = $handles;
- return $this;
- }
-
public function render() {
$request = $this->getDiffusionRequest();
$repository = $request->getRepository();
@@ -29,7 +22,6 @@
$need_pull = array();
$rows = array();
- $show_edit = false;
foreach ($this->paths as $path) {
$full_path = $base_path.$path->getPath();
diff --git a/src/applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php b/src/applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php
--- a/src/applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php
+++ b/src/applications/files/engine/PhabricatorLocalDiskFileStorageEngine.php
@@ -26,6 +26,7 @@
public function canWriteFiles() {
$path = PhabricatorEnv::getEnvConfig('storage.local-disk.path');
+ $path = phutil_string_cast($path);
return (bool)strlen($path);
}
diff --git a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php
--- a/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php
+++ b/src/applications/harbormaster/application/PhabricatorHarbormasterApplication.php
@@ -94,7 +94,10 @@
'lint/' => array(
'(?P<id>\d+)/' => 'HarbormasterLintMessagesController',
),
- 'hook/(?P<handler>[^/]+)/' => 'HarbormasterHookController',
+ 'hook/' => array(
+ 'circleci/' => 'HarbormasterCircleCIHookController',
+ 'buildkite/' => 'HarbormasterBuildkiteHookController',
+ ),
'log/' => array(
'view/(?P<id>\d+)/(?:\$(?P<lines>\d+(?:-\d+)?))?'
=> 'HarbormasterBuildLogViewController',
diff --git a/src/applications/harbormaster/conduit/HarbormasterBuildEditAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterBuildEditAPIMethod.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/conduit/HarbormasterBuildEditAPIMethod.php
@@ -0,0 +1,20 @@
+<?php
+
+final class HarbormasterBuildEditAPIMethod
+ extends PhabricatorEditEngineAPIMethod {
+
+ public function getAPIMethodName() {
+ return 'harbormaster.build.edit';
+ }
+
+ public function newEditEngine() {
+ return new HarbormasterBuildEditEngine();
+ }
+
+ public function getMethodSummary() {
+ return pht(
+ 'Apply transactions to create a new build or edit an existing '.
+ 'one.');
+ }
+
+}
diff --git a/src/applications/harbormaster/conduit/HarbormasterBuildableEditAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterBuildableEditAPIMethod.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/conduit/HarbormasterBuildableEditAPIMethod.php
@@ -0,0 +1,20 @@
+<?php
+
+final class HarbormasterBuildableEditAPIMethod
+ extends PhabricatorEditEngineAPIMethod {
+
+ public function getAPIMethodName() {
+ return 'harbormaster.buildable.edit';
+ }
+
+ public function newEditEngine() {
+ return new HarbormasterBuildableEditEngine();
+ }
+
+ public function getMethodSummary() {
+ return pht(
+ 'Apply transactions to create a new buildable or edit an existing '.
+ 'one.');
+ }
+
+}
diff --git a/src/applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php
--- a/src/applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php
+++ b/src/applications/harbormaster/conduit/HarbormasterConduitAPIMethod.php
@@ -7,14 +7,6 @@
'PhabricatorHarbormasterApplication');
}
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
-
- public function getMethodStatusDescription() {
- return pht('All Harbormaster APIs are new and subject to change.');
- }
-
protected function returnArtifactList(array $artifacts) {
$list = array();
diff --git a/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php
--- a/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php
+++ b/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php
@@ -9,18 +9,222 @@
public function getMethodSummary() {
return pht(
- 'Send a message about the status of a build target to Harbormaster, '.
- 'notifying the application of build results in an external system.');
+ 'Modify running builds, and report build results.');
}
public function getMethodDescription() {
+ return pht(<<<EOREMARKUP
+Pause, abort, restart, and report results for builds.
+EOREMARKUP
+ );
+ }
+
+ protected function newDocumentationPages(PhabricatorUser $viewer) {
+ $pages = array();
+
+ $pages[] = $this->newSendingDocumentationBoxPage($viewer);
+ $pages[] = $this->newBuildsDocumentationBoxPage($viewer);
+ $pages[] = $this->newCommandsDocumentationBoxPage($viewer);
+ $pages[] = $this->newTargetsDocumentationBoxPage($viewer);
+ $pages[] = $this->newUnitDocumentationBoxPage($viewer);
+ $pages[] = $this->newLintDocumentationBoxPage($viewer);
+
+ return $pages;
+ }
+
+ private function newSendingDocumentationBoxPage(PhabricatorUser $viewer) {
+ $title = pht('Sending Messages');
+ $content = pht(<<<EOREMARKUP
+Harbormaster build objects work somewhat differently from objects in many other
+applications. Most application objects can be edited directly using synchronous
+APIs (like `maniphest.edit`, `differential.revision.edit`, and so on).
+
+However, builds require long-running background processing and Habormaster
+objects have a more complex lifecycle than most other application objects and
+may spend significant periods of time locked by daemon processes during build
+execition. A synchronous edit might need to wait an arbitrarily long amount of
+time for this lock to become available so the edit could be applied.
+
+Additionally, some edits may also require an arbitrarily long amount of time to
+//complete//. For example, aborting a build may execute cleanup steps which
+take minutes (or even hours) to complete.
+
+Since a synchronous API could not guarantee it could return results to the
+caller in a reasonable amount of time, the edit API for Harbormaster build
+objects is asynchronous: to update a Harbormaster build or build target, use
+this API (`harbormaster.sendmessage`) to send it a message describing an edit
+you would like to effect or additional information you want to provide.
+The message will be processed by the daemons once the build or target reaches
+a suitable state to receive messages.
+
+Select an object to send a message to using the `receiver` parameter. This
+API method can send messages to multiple types of objects:
+
+<table>
+ <tr>
+ <th>Object Type</th>
+ <th>PHID Example</th>
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>Harbormaster Buildable</td>
+ <td>`PHID-HMBB-...`</td>
+ <td>%s</td>
+ </tr>
+ <tr>
+ <td>Harbormaster Build</td>
+ <td>`PHID-HMBD-...`</td>
+ <td>%s</td>
+ </tr>
+ <tr>
+ <td>Harbormaster Build Target</td>
+ <td>`PHID-HMBT-...`</td>
+ <td>%s</td>
+ </tr>
+</table>
+
+See below for specifics on sending messages to different object types.
+EOREMARKUP
+ ,
+ pht(
+ 'Buildables may receive control commands like "abort" and "restart". '.
+ 'Sending a control command to a Buildable is the same as sending it '.
+ 'to each Build for the Buildable.'),
+ pht(
+ 'Builds may receive control commands like "pause", "resume", "abort", '.
+ 'and "restart".'),
+ pht(
+ 'Build Targets may receive build status and result messages, like '.
+ '"pass" or "fail".'));
+
+ $content = $this->newRemarkupDocumentationView($content);
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('sending')
+ ->setIconIcon('fa-envelope-o');
+ }
+
+ private function newBuildsDocumentationBoxPage(PhabricatorUser $viewer) {
+ $title = pht('Updating Builds');
+
+ $content = pht(<<<EOREMARKUP
+You can use this method (`harbormaster.sendmessage`) to send control commands
+to Buildables and Builds.
+
+Specify the Build or Buildable to receive the control command by providing its
+PHID in the `receiver` parameter.
+
+Sending a control command to a Buildable has the same effect as sending it to
+each Build for the Buildable. For example, sending a "Pause" message to a
+Buildable will pause all builds for the Buildable (or at least attempt to).
+
+When sending control commands, the `unit` and `lint` parameters of this API
+method must be omitted. You can not report lint or unit results directly to
+a Build or Buildable, and can not report them alongside a control command.
+
+More broadly, you can not report build results directly to a Build or
+Buildable. Instead, report results to a Build Target.
+
+See below for a list of control commands.
+
+EOREMARKUP
+ );
+
+ $content = $this->newRemarkupDocumentationView($content);
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('builds')
+ ->setIconIcon('fa-cubes');
+ }
+
+ private function newCommandsDocumentationBoxPage(PhabricatorUser $viewer) {
+ $messages = HarbormasterBuildMessageTransaction::getAllMessages();
+
+ $rows = array();
+
+ $rows[] = '<tr>';
+ $rows[] = '<th>'.pht('Message Type').'</th>';
+ $rows[] = '<th>'.pht('Description').'</th>';
+ $rows[] = '</tr>';
+
+ foreach ($messages as $message) {
+ $row = array();
+
+ $row[] = sprintf(
+ '<td>`%s`</td>',
+ $message->getHarbormasterBuildMessageType());
+
+ $row[] = sprintf(
+ '<td>%s</td>',
+ $message->getHarbormasterBuildMessageDescription());
+
+ $rows[] = sprintf(
+ '<tr>%s</tr>',
+ implode("\n", $row));
+ }
+
+ $message_table = sprintf(
+ '<table>%s</table>',
+ implode("\n", $rows));
+
+ $title = pht('Control Commands');
+
+ $content = pht(<<<EOREMARKUP
+You can use this method to send control commands to Buildables and Builds.
+
+This table summarizes which object types may receive control commands:
+
+<table>
+ <tr>
+ <th>Object Type</th>
+ <th>PHID Example</th>
+ <th />
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>Harbormaster Buildable</td>
+ <td>`PHID-HMBB-...`</td>
+ <td>{icon check color=green}</td>
+ <td>Buildables may receive control commands.</td>
+ </tr>
+ <tr>
+ <td>Harbormaster Build</td>
+ <td>`PHID-HMBD-...`</td>
+ <td>{icon check color=green}</td>
+ <td>Builds may receive control commands.</td>
+ </tr>
+ <tr>
+ <td>Harbormaster Build Target</td>
+ <td>`PHID-HMBT-...`</td>
+ <td>{icon times color=red}</td>
+ <td>You may **NOT** send control commands to build targets.</td>
+ </tr>
+</table>
+
+You can send these commands:
+
+%s
+
+To send a command message, specify the PHID of the object you would like to
+receive the message using the `receiver` parameter, and specify the message
+type using the `type` parameter.
+
+EOREMARKUP
+ ,
+ $message_table);
+
+ $content = $this->newRemarkupDocumentationView($content);
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('commands')
+ ->setIconIcon('fa-exclamation-triangle');
+ }
+
+ private function newTargetsDocumentationBoxPage(PhabricatorUser $viewer) {
$messages = HarbormasterMessageType::getAllMessages();
- $head_type = pht('Constant');
- $head_desc = pht('Description');
- $head_key = pht('Key');
$head_type = pht('Type');
- $head_name = pht('Name');
+ $head_desc = pht('Description');
$rows = array();
$rows[] = "| {$head_type} | {$head_desc} |";
@@ -31,6 +235,84 @@
}
$message_table = implode("\n", $rows);
+ $content = pht(<<<EOREMARKUP
+If you run external builds, you can use this method to publish build results
+back into Harbormaster after the external system finishes work (or as it makes
+progress).
+
+To report build status or results, you must send a message to the appropriate
+Build Target. This table summarizes which object types may receive build status
+and result messages:
+
+<table>
+ <tr>
+ <th>Object Type</th>
+ <th>PHID Example</th>
+ <th />
+ <th>Description</th>
+ </tr>
+ <tr>
+ <td>Harbormaster Buildable</td>
+ <td>`PHID-HMBB-...`</td>
+ <td>{icon times color=red}</td>
+ <td>Buildables may **NOT** receive status or result messages.</td>
+ </tr>
+ <tr>
+ <td>Harbormaster Build</td>
+ <td>`PHID-HMBD-...`</td>
+ <td>{icon times color=red}</td>
+ <td>Builds may **NOT** receive status or result messages.</td>
+ </tr>
+ <tr>
+ <td>Harbormaster Build Target</td>
+ <td>`PHID-HMBT-...`</td>
+ <td>{icon check color=green}</td>
+ <td>Report build status and results to Build Targets.</td>
+ </tr>
+</table>
+
+The simplest way to use this method to report build results is to call it once
+after the build finishes with a `pass` or `fail` message. This will record the
+build result, and continue the next step in the build if the build was waiting
+for a result.
+
+When you send a status message about a build target, you can optionally include
+detailed `lint` or `unit` results alongside the message. See below for details.
+
+If you want to report intermediate results but a build hasn't completed yet,
+you can use the `work` message. This message doesn't have any direct effects,
+but allows you to send additional data to update the progress of the build
+target. The target will continue waiting for a completion message, but the UI
+will update to show the progress which has been made.
+
+When sending a message to a build target to report the status or results of
+a build, your message must include a `type` which describes the overall state
+of the build. For example, use `pass` to tell Harbormaster that a build target
+completed successfully.
+
+Supported message types are:
+
+%s
+
+EOREMARKUP
+ ,
+ $message_table);
+
+ $title = pht('Updating Build Targets');
+
+ $content = $this->newRemarkupDocumentationView($content);
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('targets')
+ ->setIconIcon('fa-bullseye');
+ }
+
+ private function newUnitDocumentationBoxPage(PhabricatorUser $viewer) {
+ $head_key = pht('Key');
+ $head_desc = pht('Description');
+ $head_name = pht('Name');
+ $head_type = pht('Type');
+
$rows = array();
$rows[] = "| {$head_key} | {$head_type} | {$head_desc} |";
$rows[] = '|-------------|--------------|--------------|';
@@ -55,6 +337,64 @@
}
$result_table = implode("\n", $rows);
+ $valid_unit = array(
+ array(
+ 'name' => 'PassingTest',
+ 'result' => ArcanistUnitTestResult::RESULT_PASS,
+ ),
+ array(
+ 'name' => 'FailingTest',
+ 'result' => ArcanistUnitTestResult::RESULT_FAIL,
+ ),
+ );
+
+ $json = new PhutilJSON();
+ $valid_unit = $json->encodeAsList($valid_unit);
+
+
+ $title = pht('Reporting Unit Results');
+
+ $content = pht(<<<EOREMARKUP
+You can report test results when updating the state of a build target. The
+simplest way to do this is to report all the results alongside a `pass` or
+`fail` message, but you can also send a `work` message to report intermediate
+results.
+
+
+To provide unit test results, pass a list of results in the `unit`
+parameter. Each result should be a dictionary with these keys:
+
+%s
+
+The `result` parameter recognizes these test results:
+
+%s
+
+This is a simple, valid value for the `unit` parameter. It reports one passing
+test and one failing test:
+
+```lang=json
+%s
+```
+EOREMARKUP
+ ,
+ $unit_table,
+ $result_table,
+ $valid_unit);
+
+ $content = $this->newRemarkupDocumentationView($content);
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('unit');
+ }
+
+ private function newLintDocumentationBoxPage(PhabricatorUser $viewer) {
+
+ $head_key = pht('Key');
+ $head_desc = pht('Description');
+ $head_name = pht('Name');
+ $head_type = pht('Type');
+
$rows = array();
$rows[] = "| {$head_key} | {$head_type} | {$head_desc} |";
$rows[] = '|-------------|--------------|--------------|';
@@ -76,17 +416,6 @@
}
$severity_table = implode("\n", $rows);
- $valid_unit = array(
- array(
- 'name' => 'PassingTest',
- 'result' => ArcanistUnitTestResult::RESULT_PASS,
- ),
- array(
- 'name' => 'FailingTest',
- 'result' => ArcanistUnitTestResult::RESULT_FAIL,
- ),
- );
-
$valid_lint = array(
array(
'name' => pht('Syntax Error'),
@@ -109,104 +438,58 @@
);
$json = new PhutilJSON();
- $valid_unit = $json->encodeAsList($valid_unit);
$valid_lint = $json->encodeAsList($valid_lint);
- return pht(
- "Send a message about the status of a build target to Harbormaster, ".
- "notifying the application of build results in an external system.".
- "\n\n".
- "Sending Messages\n".
- "================\n".
- "If you run external builds, you can use this method to publish build ".
- "results back into Harbormaster after the external system finishes work ".
- "or as it makes progress.".
- "\n\n".
- "The simplest way to use this method is to call it once after the ".
- "build finishes with a `pass` or `fail` message. This will record the ".
- "build result, and continue the next step in the build if the build was ".
- "waiting for a result.".
- "\n\n".
- "When you send a status message about a build target, you can ".
- "optionally include detailed `lint` or `unit` results alongside the ".
- "message. See below for details.".
- "\n\n".
- "If you want to report intermediate results but a build hasn't ".
- "completed yet, you can use the `work` message. This message doesn't ".
- "have any direct effects, but allows you to send additional data to ".
- "update the progress of the build target. The target will continue ".
- "waiting for a completion message, but the UI will update to show the ".
- "progress which has been made.".
- "\n\n".
- "Message Types\n".
- "=============\n".
- "When you send Harbormaster a message, you must include a `type`, ".
- "which describes the overall state of the build. For example, use ".
- "`pass` to tell Harbormaster that a build completed successfully.".
- "\n\n".
- "Supported message types are:".
- "\n\n".
- "%s".
- "\n\n".
- "Unit Results\n".
- "============\n".
- "You can report test results alongside a message. The simplest way to ".
- "do this is to report all the results alongside a `pass` or `fail` ".
- "message, but you can also send a `work` message to report intermediate ".
- "results.\n\n".
- "To provide unit test results, pass a list of results in the `unit` ".
- "parameter. Each result should be a dictionary with these keys:".
- "\n\n".
- "%s".
- "\n\n".
- "The `result` parameter recognizes these test results:".
- "\n\n".
- "%s".
- "\n\n".
- "This is a simple, valid value for the `unit` parameter. It reports ".
- "one passing test and one failing test:\n\n".
- "\n\n".
- "```lang=json\n".
- "%s".
- "```".
- "\n\n".
- "Lint Results\n".
- "============\n".
- "Like unit test results, you can report lint results alongside a ".
- "message. The `lint` parameter should contain results as a list of ".
- "dictionaries with these keys:".
- "\n\n".
- "%s".
- "\n\n".
- "The `severity` parameter recognizes these severity levels:".
- "\n\n".
- "%s".
- "\n\n".
- "This is a simple, valid value for the `lint` parameter. It reports one ".
- "error and one warning:".
- "\n\n".
- "```lang=json\n".
- "%s".
- "```".
- "\n\n",
- $message_table,
- $unit_table,
- $result_table,
- $valid_unit,
+ $title = pht('Reporting Lint Results');
+ $content = pht(<<<EOREMARKUP
+Like unit test results, you can report lint results when updating the state
+of a build target. The `lint` parameter should contain results as a list of
+dictionaries with these keys:
+
+%s
+
+The `severity` parameter recognizes these severity levels:
+
+%s
+
+This is a simple, valid value for the `lint` parameter. It reports one error
+and one warning:
+
+```lang=json
+%s
+```
+
+EOREMARKUP
+ ,
$lint_table,
$severity_table,
$valid_lint);
+
+ $content = $this->newRemarkupDocumentationView($content);
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('lint');
}
protected function defineParamTypes() {
$messages = HarbormasterMessageType::getAllMessages();
+
+ $more_messages = HarbormasterBuildMessageTransaction::getAllMessages();
+ $more_messages = mpull($more_messages, 'getHarbormasterBuildMessageType');
+
+ $messages = array_merge($messages, $more_messages);
+ $messages = array_unique($messages);
+
+ sort($messages);
+
$type_const = $this->formatStringConstants($messages);
return array(
- 'buildTargetPHID' => 'required phid',
+ 'receiver' => 'required string|phid',
'type' => 'required '.$type_const,
'unit' => 'optional list<wild>',
'lint' => 'optional list<wild>',
+ 'buildTargetPHID' => 'deprecated optional phid',
);
}
@@ -215,19 +498,90 @@
}
protected function execute(ConduitAPIRequest $request) {
- $viewer = $request->getUser();
+ $viewer = $request->getViewer();
+
+ $receiver_name = $request->getValue('receiver');
$build_target_phid = $request->getValue('buildTargetPHID');
+ if ($build_target_phid !== null) {
+ if ($receiver_name === null) {
+ $receiver_name = $build_target_phid;
+ } else {
+ throw new Exception(
+ pht(
+ 'Call specifies both "receiver" and "buildTargetPHID". '.
+ 'When using the modern "receiver" parameter, omit the '.
+ 'deprecated "buildTargetPHID" parameter.'));
+ }
+ }
+
+ if (!strlen($receiver_name)) {
+ throw new Exception(
+ pht(
+ 'Call omits required "receiver" parameter. Specify the PHID '.
+ 'of the object you want to send a message to.'));
+ }
+
$message_type = $request->getValue('type');
+ if (!strlen($message_type)) {
+ throw new Exception(
+ pht(
+ 'Call omits required "type" parameter. Specify the type of '.
+ 'message you want to send.'));
+ }
- $build_target = id(new HarbormasterBuildTargetQuery())
+ $receiver_object = id(new PhabricatorObjectQuery())
->setViewer($viewer)
- ->withPHIDs(array($build_target_phid))
+ ->withNames(array($receiver_name))
->executeOne();
- if (!$build_target) {
- throw new Exception(pht('No such build target!'));
+ if (!$receiver_object) {
+ throw new Exception(
+ pht(
+ 'Unable to load object "%s" to receive message.',
+ $receiver_name));
+ }
+
+ $is_target = ($receiver_object instanceof HarbormasterBuildTarget);
+ if ($is_target) {
+ return $this->sendToTarget($request, $message_type, $receiver_object);
+ }
+
+ if ($request->getValue('unit') !== null) {
+ throw new Exception(
+ pht(
+ 'Call includes "unit" parameter. This parameter must be omitted '.
+ 'when the receiver is not a Build Target.'));
+ }
+
+ if ($request->getValue('lint') !== null) {
+ throw new Exception(
+ pht(
+ 'Call includes "lint" parameter. This parameter must be omitted '.
+ 'when the receiver is not a Build Target.'));
+ }
+
+ $is_build = ($receiver_object instanceof HarbormasterBuild);
+ if ($is_build) {
+ return $this->sendToBuild($request, $message_type, $receiver_object);
+ }
+
+ $is_buildable = ($receiver_object instanceof HarbormasterBuildable);
+ if ($is_buildable) {
+ return $this->sendToBuildable($request, $message_type, $receiver_object);
}
+ throw new Exception(
+ pht(
+ 'Receiver object (of class "%s") is not a valid receiver.',
+ get_class($receiver_object)));
+ }
+
+ private function sendToTarget(
+ ConduitAPIRequest $request,
+ $message_type,
+ HarbormasterBuildTarget $build_target) {
+ $viewer = $request->getViewer();
+
$save = array();
$lint_messages = $request->getValue('lint', array());
@@ -270,4 +624,67 @@
return null;
}
+ private function sendToBuild(
+ ConduitAPIRequest $request,
+ $message_type,
+ HarbormasterBuild $build) {
+ $viewer = $request->getViewer();
+
+ $xaction =
+ HarbormasterBuildMessageTransaction::getTransactionObjectForMessageType(
+ $message_type);
+ if (!$xaction) {
+ throw new Exception(
+ pht(
+ 'Message type "%s" is not supported.',
+ $message_type));
+ }
+
+ // NOTE: This is a slightly weaker check than we perform in the web UI.
+ // We allow API callers to send a "pause" message to a pausing build,
+ // for example, even though the message will have no effect.
+ $xaction->assertCanApplyMessage($viewer, $build);
+
+ $build->sendMessage($viewer, $xaction->getHarbormasterBuildMessageType());
+ }
+
+ private function sendToBuildable(
+ ConduitAPIRequest $request,
+ $message_type,
+ HarbormasterBuildable $buildable) {
+ $viewer = $request->getViewer();
+
+ $xaction =
+ HarbormasterBuildMessageTransaction::getTransactionObjectForMessageType(
+ $message_type);
+ if (!$xaction) {
+ throw new Exception(
+ pht(
+ 'Message type "%s" is not supported.',
+ $message_type));
+ }
+
+ // Reload the Buildable to load Builds.
+ $buildable = id(new HarbormasterBuildableQuery())
+ ->setViewer($viewer)
+ ->withIDs(array($buildable->getID()))
+ ->needBuilds(true)
+ ->executeOne();
+
+ $can_send = array();
+ foreach ($buildable->getBuilds() as $build) {
+ if ($xaction->canApplyMessage($viewer, $build)) {
+ $can_send[] = $build;
+ }
+ }
+
+ // NOTE: This doesn't actually apply a transaction to the Buildable,
+ // but that transaction is purely informational and should probably be
+ // implemented as a Message.
+
+ foreach ($can_send as $build) {
+ $build->sendMessage($viewer, $xaction->getHarbormasterBuildMessageType());
+ }
+ }
+
}
diff --git a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php
--- a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php
+++ b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php
@@ -12,6 +12,11 @@
const STATUS_PAUSED = 'paused';
const STATUS_DEADLOCKED = 'deadlocked';
+ const PENDING_PAUSING = 'x-pausing';
+ const PENDING_RESUMING = 'x-resuming';
+ const PENDING_RESTARTING = 'x-restarting';
+ const PENDING_ABORTING = 'x-aborting';
+
private $key;
private $properties;
@@ -56,6 +61,37 @@
return ($this->key === self::STATUS_FAILED);
}
+ public function isAborting() {
+ return ($this->key === self::PENDING_ABORTING);
+ }
+
+ public function isRestarting() {
+ return ($this->key === self::PENDING_RESTARTING);
+ }
+
+ public function isResuming() {
+ return ($this->key === self::PENDING_RESUMING);
+ }
+
+ public function isPausing() {
+ return ($this->key === self::PENDING_PAUSING);
+ }
+
+ public function isPending() {
+ return ($this->key === self::STATUS_PENDING);
+ }
+
+ public function getIconIcon() {
+ return $this->getProperty('icon');
+ }
+
+ public function getIconColor() {
+ return $this->getProperty('color');
+ }
+
+ public function getName() {
+ return $this->getProperty('name');
+ }
/**
* Get a human readable name for a build status constant.
@@ -185,8 +221,8 @@
),
self::STATUS_PAUSED => array(
'name' => pht('Paused'),
- 'icon' => 'fa-minus-circle',
- 'color' => 'dark',
+ 'icon' => 'fa-pause',
+ 'color' => 'yellow',
'color.ansi' => 'yellow',
'isBuilding' => false,
'isComplete' => false,
@@ -199,6 +235,38 @@
'isBuilding' => false,
'isComplete' => true,
),
+ self::PENDING_PAUSING => array(
+ 'name' => pht('Pausing'),
+ 'icon' => 'fa-exclamation-triangle',
+ 'color' => 'red',
+ 'color.ansi' => 'red',
+ 'isBuilding' => false,
+ 'isComplete' => false,
+ ),
+ self::PENDING_RESUMING => array(
+ 'name' => pht('Resuming'),
+ 'icon' => 'fa-exclamation-triangle',
+ 'color' => 'red',
+ 'color.ansi' => 'red',
+ 'isBuilding' => false,
+ 'isComplete' => false,
+ ),
+ self::PENDING_RESTARTING => array(
+ 'name' => pht('Restarting'),
+ 'icon' => 'fa-exclamation-triangle',
+ 'color' => 'red',
+ 'color.ansi' => 'red',
+ 'isBuilding' => false,
+ 'isComplete' => false,
+ ),
+ self::PENDING_ABORTING => array(
+ 'name' => pht('Aborting'),
+ 'icon' => 'fa-exclamation-triangle',
+ 'color' => 'red',
+ 'color.ansi' => 'red',
+ 'isBuilding' => false,
+ 'isComplete' => false,
+ ),
);
}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildActionController.php b/src/applications/harbormaster/controller/HarbormasterBuildActionController.php
--- a/src/applications/harbormaster/controller/HarbormasterBuildActionController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildActionController.php
@@ -22,24 +22,13 @@
return new Aphront404Response();
}
- switch ($action) {
- case HarbormasterBuildCommand::COMMAND_RESTART:
- $can_issue = $build->canRestartBuild();
- break;
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- $can_issue = $build->canPauseBuild();
- break;
- case HarbormasterBuildCommand::COMMAND_RESUME:
- $can_issue = $build->canResumeBuild();
- break;
- case HarbormasterBuildCommand::COMMAND_ABORT:
- $can_issue = $build->canAbortBuild();
- break;
- default:
- return new Aphront400Response();
- }
+ $xaction =
+ HarbormasterBuildMessageTransaction::getTransactionObjectForMessageType(
+ $action);
- $build->assertCanIssueCommand($viewer, $action);
+ if (!$xaction) {
+ return new Aphront404Response();
+ }
switch ($via) {
case 'buildable':
@@ -50,100 +39,29 @@
break;
}
- if ($request->isDialogFormPost() && $can_issue) {
- $build->sendMessage($viewer, $action);
- return id(new AphrontRedirectResponse())->setURI($return_uri);
+ try {
+ $xaction->assertCanSendMessage($viewer, $build);
+ } catch (HarbormasterMessageException $ex) {
+ return $this->newDialog()
+ ->setTitle($ex->getTitle())
+ ->appendChild($ex->getBody())
+ ->addCancelButton($return_uri);
}
- switch ($action) {
- case HarbormasterBuildCommand::COMMAND_RESTART:
- if ($can_issue) {
- $title = pht('Really restart build?');
- $body = pht(
- 'Progress on this build will be discarded and the build will '.
- 'restart. Side effects of the build will occur again. Really '.
- 'restart build?');
- $submit = pht('Restart Build');
- } else {
- try {
- $build->assertCanRestartBuild();
- throw new Exception(pht('Expected to be unable to restart build.'));
- } catch (HarbormasterRestartException $ex) {
- $title = $ex->getTitle();
- $body = $ex->getBody();
- }
- }
- break;
- case HarbormasterBuildCommand::COMMAND_ABORT:
- if ($can_issue) {
- $title = pht('Really abort build?');
- $body = pht(
- 'Progress on this build will be discarded. Really '.
- 'abort build?');
- $submit = pht('Abort Build');
- } else {
- $title = pht('Unable to Abort Build');
- $body = pht('You can not abort this build.');
- }
- break;
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- if ($can_issue) {
- $title = pht('Really pause build?');
- $body = pht(
- 'If you pause this build, work will halt once the current steps '.
- 'complete. You can resume the build later.');
- $submit = pht('Pause Build');
- } else {
- $title = pht('Unable to Pause Build');
- if ($build->isComplete()) {
- $body = pht(
- 'This build is already complete. You can not pause a completed '.
- 'build.');
- } else if ($build->isPaused()) {
- $body = pht(
- 'This build is already paused. You can not pause a build which '.
- 'has already been paused.');
- } else if ($build->isPausing()) {
- $body = pht(
- 'This build is already pausing. You can not reissue a pause '.
- 'command to a pausing build.');
- } else {
- $body = pht(
- 'This build can not be paused.');
- }
- }
- break;
- case HarbormasterBuildCommand::COMMAND_RESUME:
- if ($can_issue) {
- $title = pht('Really resume build?');
- $body = pht(
- 'Work will continue on the build. Really resume?');
- $submit = pht('Resume Build');
- } else {
- $title = pht('Unable to Resume Build');
- if ($build->isResuming()) {
- $body = pht(
- 'This build is already resuming. You can not reissue a resume '.
- 'command to a resuming build.');
- } else if (!$build->isPaused()) {
- $body = pht(
- 'This build is not paused. You can only resume a paused '.
- 'build.');
- }
- }
- break;
+ if ($request->isDialogFormPost()) {
+ $build->sendMessage($viewer, $xaction->getHarbormasterBuildMessageType());
+ return id(new AphrontRedirectResponse())->setURI($return_uri);
}
- $dialog = $this->newDialog()
+ $title = $xaction->newConfirmPromptTitle();
+ $body = $xaction->newConfirmPromptBody();
+ $submit = $xaction->getHarbormasterBuildMessageName();
+
+ return $this->newDialog()
->setTitle($title)
->appendChild($body)
- ->addCancelButton($return_uri);
-
- if ($can_issue) {
- $dialog->addSubmitButton($submit);
- }
-
- return $dialog;
+ ->addCancelButton($return_uri)
+ ->addSubmitButton($submit);
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
--- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php
@@ -32,21 +32,13 @@
->setPolicyObject($build)
->setHeaderIcon('fa-cubes');
- $is_restarting = $build->isRestarting();
-
- if ($is_restarting) {
- $page_header->setStatus(
- 'fa-exclamation-triangle', 'red', pht('Restarting'));
- } else if ($build->isPausing()) {
- $page_header->setStatus(
- 'fa-exclamation-triangle', 'red', pht('Pausing'));
- } else if ($build->isResuming()) {
- $page_header->setStatus(
- 'fa-exclamation-triangle', 'red', pht('Resuming'));
- } else if ($build->isAborting()) {
- $page_header->setStatus(
- 'fa-exclamation-triangle', 'red', pht('Aborting'));
- }
+ $status = $build->getBuildPendingStatusObject();
+
+ $status_icon = $status->getIconIcon();
+ $status_color = $status->getIconColor();
+ $status_name = $status->getName();
+
+ $page_header->setStatus($status_icon, $status_color, $status_name);
$max_generation = (int)$build->getBuildGeneration();
if ($max_generation === 0) {
@@ -55,7 +47,7 @@
$min_generation = 1;
}
- if ($is_restarting) {
+ if ($build->isRestarting()) {
$max_generation = $max_generation + 1;
}
@@ -541,63 +533,31 @@
$curtain = $this->newCurtainView($build);
- $can_restart =
- $build->canRestartBuild() &&
- $build->canIssueCommand(
- $viewer,
- HarbormasterBuildCommand::COMMAND_RESTART);
-
- $can_pause =
- $build->canPauseBuild() &&
- $build->canIssueCommand(
- $viewer,
- HarbormasterBuildCommand::COMMAND_PAUSE);
-
- $can_resume =
- $build->canResumeBuild() &&
- $build->canIssueCommand(
- $viewer,
- HarbormasterBuildCommand::COMMAND_RESUME);
-
- $can_abort =
- $build->canAbortBuild() &&
- $build->canIssueCommand(
- $viewer,
- HarbormasterBuildCommand::COMMAND_ABORT);
-
- $curtain->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Restart Build'))
- ->setIcon('fa-repeat')
- ->setHref($this->getApplicationURI('/build/restart/'.$id.'/'))
- ->setDisabled(!$can_restart)
- ->setWorkflow(true));
-
- if ($build->canResumeBuild()) {
- $curtain->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Resume Build'))
- ->setIcon('fa-play')
- ->setHref($this->getApplicationURI('/build/resume/'.$id.'/'))
- ->setDisabled(!$can_resume)
- ->setWorkflow(true));
- } else {
- $curtain->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Pause Build'))
- ->setIcon('fa-pause')
- ->setHref($this->getApplicationURI('/build/pause/'.$id.'/'))
- ->setDisabled(!$can_pause)
- ->setWorkflow(true));
- }
+ $messages = array(
+ new HarbormasterBuildMessageRestartTransaction(),
+ new HarbormasterBuildMessagePauseTransaction(),
+ new HarbormasterBuildMessageResumeTransaction(),
+ new HarbormasterBuildMessageAbortTransaction(),
+ );
- $curtain->addAction(
- id(new PhabricatorActionView())
- ->setName(pht('Abort Build'))
- ->setIcon('fa-exclamation-triangle')
- ->setHref($this->getApplicationURI('/build/abort/'.$id.'/'))
- ->setDisabled(!$can_abort)
- ->setWorkflow(true));
+ foreach ($messages as $message) {
+ $can_send = $message->canSendMessage($viewer, $build);
+
+ $message_uri = urisprintf(
+ '/build/%s/%d/',
+ $message->getHarbormasterBuildMessageType(),
+ $id);
+ $message_uri = $this->getApplicationURI($message_uri);
+
+ $action = id(new PhabricatorActionView())
+ ->setName($message->getHarbormasterBuildMessageName())
+ ->setIcon($message->getIcon())
+ ->setHref($message_uri)
+ ->setDisabled(!$can_send)
+ ->setWorkflow(true);
+
+ $curtain->addAction($action);
+ }
return $curtain;
}
@@ -624,10 +584,6 @@
pht('Build Plan'),
$handles[$build->getBuildPlanPHID()]->renderLink());
- $properties->addProperty(
- pht('Status'),
- $this->getStatus($build));
-
return id(new PHUIObjectBoxView())
->setHeaderText(pht('Properties'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
@@ -681,31 +637,6 @@
->setTable($table);
}
-
- private function getStatus(HarbormasterBuild $build) {
- $status_view = new PHUIStatusListView();
-
- $item = new PHUIStatusItemView();
-
- if ($build->isPausing()) {
- $status_name = pht('Pausing');
- $icon = PHUIStatusItemView::ICON_RIGHT;
- $color = 'dark';
- } else {
- $status = $build->getBuildStatus();
- $status_name =
- HarbormasterBuildStatus::getBuildStatusName($status);
- $icon = HarbormasterBuildStatus::getBuildStatusIcon($status);
- $color = HarbormasterBuildStatus::getBuildStatusColor($status);
- }
-
- $item->setTarget($status_name);
- $item->setIcon($icon, $color);
- $status_view->addItem($item);
-
- return $status_view;
- }
-
private function buildMessages(array $messages) {
$viewer = $this->getRequest()->getUser();
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableActionController.php b/src/applications/harbormaster/controller/HarbormasterBuildableActionController.php
--- a/src/applications/harbormaster/controller/HarbormasterBuildableActionController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildableActionController.php
@@ -22,299 +22,162 @@
return new Aphront404Response();
}
- $issuable = array();
+ $message =
+ HarbormasterBuildMessageTransaction::getTransactionObjectForMessageType(
+ $action);
+ if (!$message) {
+ return new Aphront404Response();
+ }
+
+ $return_uri = '/'.$buildable->getMonogram();
+ // See T13348. Actions may apply to only a subset of builds, so give the
+ // user a preview of what will happen.
+
+ $can_send = array();
+
+ $rows = array();
$builds = $buildable->getBuilds();
foreach ($builds as $key => $build) {
- switch ($action) {
- case HarbormasterBuildCommand::COMMAND_RESTART:
- if ($build->canRestartBuild()) {
- $issuable[$key] = $build;
- }
- break;
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- if ($build->canPauseBuild()) {
- $issuable[$key] = $build;
- }
- break;
- case HarbormasterBuildCommand::COMMAND_RESUME:
- if ($build->canResumeBuild()) {
- $issuable[$key] = $build;
- }
- break;
- case HarbormasterBuildCommand::COMMAND_ABORT:
- if ($build->canAbortBuild()) {
- $issuable[$key] = $build;
- }
- break;
- default:
- return new Aphront400Response();
+ $exception = null;
+ try {
+ $message->assertCanSendMessage($viewer, $build);
+ $can_send[$key] = $build;
+ } catch (HarbormasterMessageException $ex) {
+ $exception = $ex;
}
- }
- $restricted = false;
- foreach ($issuable as $key => $build) {
- if (!$build->canIssueCommand($viewer, $action)) {
- $restricted = true;
- unset($issuable[$key]);
- }
- }
+ if (!$exception) {
+ $icon_icon = $message->getIcon();
+ $icon_color = 'green';
+
+ $title = $message->getHarbormasterBuildMessageName();
+ $body = $message->getHarbormasterBuildableMessageEffect();
+ } else {
+ $icon_icon = 'fa-times';
+ $icon_color = 'red';
- $building = false;
- foreach ($issuable as $key => $build) {
- if ($build->isBuilding()) {
- $building = true;
- break;
+ $title = $ex->getTitle();
+ $body = $ex->getBody();
}
+
+ $icon = id(new PHUIIconView())
+ ->setIcon($icon_icon)
+ ->setColor($icon_color);
+
+ $build_name = phutil_tag(
+ 'a',
+ array(
+ 'href' => $build->getURI(),
+ 'target' => '_blank',
+ ),
+ pht('%s %s', $build->getObjectName(), $build->getName()));
+
+ $rows[] = array(
+ $icon,
+ $build_name,
+ $title,
+ $body,
+ );
}
- $return_uri = '/'.$buildable->getMonogram();
- if ($request->isDialogFormPost() && $issuable) {
+ $table = id(new AphrontTableView($rows))
+ ->setHeaders(
+ array(
+ null,
+ pht('Build'),
+ pht('Action'),
+ pht('Details'),
+ ))
+ ->setColumnClasses(
+ array(
+ null,
+ null,
+ 'pri',
+ 'wide',
+ ));
+
+ $table = phutil_tag(
+ 'div',
+ array(
+ 'class' => 'mlt mlb',
+ ),
+ $table);
+
+ if ($request->isDialogFormPost() && $can_send) {
$editor = id(new HarbormasterBuildableTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true);
+ $xaction_type = HarbormasterBuildableMessageTransaction::TRANSACTIONTYPE;
+
$xaction = id(new HarbormasterBuildableTransaction())
- ->setTransactionType(HarbormasterBuildableTransaction::TYPE_COMMAND)
+ ->setTransactionType($xaction_type)
->setNewValue($action);
$editor->applyTransactions($buildable, array($xaction));
- $build_editor = id(new HarbormasterBuildTransactionEditor())
- ->setActor($viewer)
- ->setContentSourceFromRequest($request)
- ->setContinueOnNoEffect(true)
- ->setContinueOnMissingFields(true);
-
- foreach ($issuable as $build) {
- $xaction = id(new HarbormasterBuildTransaction())
- ->setTransactionType(HarbormasterBuildTransaction::TYPE_COMMAND)
- ->setNewValue($action);
- $build_editor->applyTransactions($build, array($xaction));
+ foreach ($can_send as $build) {
+ $build->sendMessage(
+ $viewer,
+ $message->getHarbormasterBuildMessageType());
}
return id(new AphrontRedirectResponse())->setURI($return_uri);
}
- $width = AphrontDialogView::WIDTH_DEFAULT;
-
- switch ($action) {
- case HarbormasterBuildCommand::COMMAND_RESTART:
- // See T13348. The "Restart Builds" action may restart only a subset
- // of builds, so show the user a preview of which builds will actually
- // restart.
-
- $body = array();
-
- if ($issuable) {
- $title = pht('Restart Builds');
- $submit = pht('Restart Builds');
- } else {
- $title = pht('Unable to Restart Builds');
- }
-
- if ($builds) {
- $width = AphrontDialogView::WIDTH_FORM;
-
- $body[] = pht('Builds for this buildable:');
-
- $rows = array();
- foreach ($builds as $key => $build) {
- if (isset($issuable[$key])) {
- $icon = id(new PHUIIconView())
- ->setIcon('fa-repeat green');
- $build_note = pht('Will Restart');
- } else {
- $icon = null;
-
- try {
- $build->assertCanRestartBuild();
- } catch (HarbormasterRestartException $ex) {
- $icon = id(new PHUIIconView())
- ->setIcon('fa-times red');
- $build_note = pht(
- '%s: %s',
- phutil_tag('strong', array(), pht('Not Restartable')),
- $ex->getTitle());
- }
-
- if (!$icon) {
- try {
- $build->assertCanIssueCommand($viewer, $action);
- } catch (PhabricatorPolicyException $ex) {
- $icon = id(new PHUIIconView())
- ->setIcon('fa-lock red');
- $build_note = pht(
- '%s: %s',
- phutil_tag('strong', array(), pht('Not Restartable')),
- pht('You do not have permission to restart this build.'));
- }
- }
-
- if (!$icon) {
- $icon = id(new PHUIIconView())
- ->setIcon('fa-times red');
- $build_note = pht('Will Not Restart');
- }
- }
-
- $build_name = phutil_tag(
- 'a',
- array(
- 'href' => $build->getURI(),
- 'target' => '_blank',
- ),
- pht('%s %s', $build->getObjectName(), $build->getName()));
-
- $rows[] = array(
- $icon,
- $build_name,
- $build_note,
- );
- }
-
- $table = id(new AphrontTableView($rows))
- ->setHeaders(
- array(
- null,
- pht('Build'),
- pht('Action'),
- ))
- ->setColumnClasses(
- array(
- null,
- 'pri',
- 'wide',
- ));
-
- $table = phutil_tag(
- 'div',
- array(
- 'class' => 'mlt mlb',
- ),
- $table);
-
- $body[] = $table;
- }
-
- if ($issuable) {
- $warnings = array();
-
- if ($restricted) {
- $warnings[] = pht(
- 'You only have permission to restart some builds.');
- }
-
- if ($building) {
- $warnings[] = pht(
- 'Progress on running builds will be discarded.');
- }
-
- $warnings[] = pht(
- 'When a build is restarted, side effects associated with '.
- 'the build may occur again.');
-
- $body[] = id(new PHUIInfoView())
- ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
- ->setErrors($warnings);
-
- $body[] = pht('Really restart builds?');
- } else {
- if ($restricted) {
- $body[] = pht('You do not have permission to restart any builds.');
- } else {
- $body[] = pht('No builds can be restarted.');
- }
- }
-
- break;
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- if ($issuable) {
- $title = pht('Really pause builds?');
-
- if ($restricted) {
- $body = pht(
- 'You only have permission to pause some builds. Once the '.
- 'current steps complete, work will halt on builds you have '.
- 'permission to pause. You can resume the builds later.');
- } else {
- $body = pht(
- 'If you pause all builds, work will halt once the current steps '.
- 'complete. You can resume the builds later.');
- }
- $submit = pht('Pause Builds');
- } else {
- $title = pht('Unable to Pause Builds');
-
- if ($restricted) {
- $body = pht('You do not have permission to pause any builds.');
- } else {
- $body = pht('No builds can be paused.');
- }
- }
- break;
- case HarbormasterBuildCommand::COMMAND_ABORT:
- if ($issuable) {
- $title = pht('Really abort builds?');
- if ($restricted) {
- $body = pht(
- 'You only have permission to abort some builds. Work will '.
- 'halt immediately on builds you have permission to abort. '.
- 'Progress will be discarded, and builds must be completely '.
- 'restarted if you want them to complete.');
- } else {
- $body = pht(
- 'If you abort all builds, work will halt immediately. Work '.
- 'will be discarded, and builds must be completely restarted.');
- }
- $submit = pht('Abort Builds');
- } else {
- $title = pht('Unable to Abort Builds');
-
- if ($restricted) {
- $body = pht('You do not have permission to abort any builds.');
- } else {
- $body = pht('No builds can be aborted.');
- }
- }
- break;
- case HarbormasterBuildCommand::COMMAND_RESUME:
- if ($issuable) {
- $title = pht('Really resume builds?');
- if ($restricted) {
- $body = pht(
- 'You only have permission to resume some builds. Work will '.
- 'continue on builds you have permission to resume.');
- } else {
- $body = pht('Work will continue on all builds. Really resume?');
- }
-
- $submit = pht('Resume Builds');
- } else {
- $title = pht('Unable to Resume Builds');
- if ($restricted) {
- $body = pht('You do not have permission to resume any builds.');
- } else {
- $body = pht('No builds can be resumed.');
- }
- }
- break;
+ if (!$builds) {
+ $title = pht('No Builds');
+ $body = pht(
+ 'This buildable has no builds, so you can not issue any commands.');
+ } else {
+ if ($can_send) {
+ $title = $message->newBuildableConfirmPromptTitle(
+ $builds,
+ $can_send);
+
+ $body = $message->newBuildableConfirmPromptBody(
+ $builds,
+ $can_send);
+ } else {
+ $title = pht('Unable to Send Command');
+ $body = pht(
+ 'You can not send this command to any of the current builds '.
+ 'for this buildable.');
+ }
+
+ $body = array(
+ pht('Builds for this buildable:'),
+ $table,
+ $body,
+ );
}
- $dialog = id(new AphrontDialogView())
- ->setUser($viewer)
- ->setWidth($width)
+ $warnings = $message->newBuildableConfirmPromptWarnings(
+ $builds,
+ $can_send);
+
+ if ($warnings) {
+ $body[] = id(new PHUIInfoView())
+ ->setSeverity(PHUIInfoView::SEVERITY_WARNING)
+ ->setErrors($warnings);
+ }
+
+ $submit = $message->getHarbormasterBuildableMessageName();
+
+ $dialog = $this->newDialog()
+ ->setWidth(AphrontDialogView::WIDTH_FULL)
->setTitle($title)
->appendChild($body)
->addCancelButton($return_uri);
- if ($issuable) {
+ if ($can_send) {
$dialog->addSubmitButton($submit);
}
- return id(new AphrontDialogResponse())->setDialog($dialog);
+ return $dialog;
}
}
diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
--- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php
@@ -87,75 +87,39 @@
$buildable,
PhabricatorPolicyCapability::CAN_EDIT);
- $can_restart = false;
- $can_resume = false;
- $can_pause = false;
- $can_abort = false;
-
- $command_restart = HarbormasterBuildCommand::COMMAND_RESTART;
- $command_resume = HarbormasterBuildCommand::COMMAND_RESUME;
- $command_pause = HarbormasterBuildCommand::COMMAND_PAUSE;
- $command_abort = HarbormasterBuildCommand::COMMAND_ABORT;
-
- foreach ($buildable->getBuilds() as $build) {
- if ($build->canRestartBuild()) {
- if ($build->canIssueCommand($viewer, $command_restart)) {
- $can_restart = true;
- }
- }
- if ($build->canResumeBuild()) {
- if ($build->canIssueCommand($viewer, $command_resume)) {
- $can_resume = true;
- }
- }
- if ($build->canPauseBuild()) {
- if ($build->canIssueCommand($viewer, $command_pause)) {
- $can_pause = true;
- }
- }
- if ($build->canAbortBuild()) {
- if ($build->canIssueCommand($viewer, $command_abort)) {
- $can_abort = true;
+ $messages = array(
+ new HarbormasterBuildMessageRestartTransaction(),
+ new HarbormasterBuildMessagePauseTransaction(),
+ new HarbormasterBuildMessageResumeTransaction(),
+ new HarbormasterBuildMessageAbortTransaction(),
+ );
+
+ foreach ($messages as $message) {
+
+ // Messages are enabled if they can be sent to at least one build.
+ $can_send = false;
+ foreach ($buildable->getBuilds() as $build) {
+ $can_send = $message->canSendMessage($viewer, $build);
+ if ($can_send) {
+ break;
}
}
- }
- $restart_uri = "buildable/{$id}/restart/";
- $pause_uri = "buildable/{$id}/pause/";
- $resume_uri = "buildable/{$id}/resume/";
- $abort_uri = "buildable/{$id}/abort/";
-
- $curtain->addAction(
- id(new PhabricatorActionView())
- ->setIcon('fa-repeat')
- ->setName(pht('Restart Builds'))
- ->setHref($this->getApplicationURI($restart_uri))
- ->setWorkflow(true)
- ->setDisabled(!$can_restart || !$can_edit));
-
- $curtain->addAction(
- id(new PhabricatorActionView())
- ->setIcon('fa-pause')
- ->setName(pht('Pause Builds'))
- ->setHref($this->getApplicationURI($pause_uri))
- ->setWorkflow(true)
- ->setDisabled(!$can_pause || !$can_edit));
-
- $curtain->addAction(
- id(new PhabricatorActionView())
- ->setIcon('fa-play')
- ->setName(pht('Resume Builds'))
- ->setHref($this->getApplicationURI($resume_uri))
- ->setWorkflow(true)
- ->setDisabled(!$can_resume || !$can_edit));
-
- $curtain->addAction(
- id(new PhabricatorActionView())
- ->setIcon('fa-exclamation-triangle')
- ->setName(pht('Abort Builds'))
- ->setHref($this->getApplicationURI($abort_uri))
- ->setWorkflow(true)
- ->setDisabled(!$can_abort || !$can_edit));
+ $message_uri = urisprintf(
+ '/buildable/%d/%s/',
+ $id,
+ $message->getHarbormasterBuildMessageType());
+ $message_uri = $this->getApplicationURI($message_uri);
+
+ $action = id(new PhabricatorActionView())
+ ->setName($message->getHarbormasterBuildableMessageName())
+ ->setIcon($message->getIcon())
+ ->setHref($message_uri)
+ ->setDisabled(!$can_send || !$can_edit)
+ ->setWorkflow(true);
+
+ $curtain->addAction($action);
+ }
return $curtain;
}
@@ -198,56 +162,17 @@
->setUser($viewer);
foreach ($buildable->getBuilds() as $build) {
$view_uri = $this->getApplicationURI('/build/'.$build->getID().'/');
+
$item = id(new PHUIObjectItemView())
->setObjectName(pht('Build %d', $build->getID()))
->setHeader($build->getName())
->setHref($view_uri);
- $status = $build->getBuildStatus();
- $status_color = HarbormasterBuildStatus::getBuildStatusColor($status);
- $status_name = HarbormasterBuildStatus::getBuildStatusName($status);
- $item->setStatusIcon('fa-dot-circle-o '.$status_color, $status_name);
- $item->addAttribute($status_name);
-
- if ($build->isRestarting()) {
- $item->addIcon('fa-repeat', pht('Restarting'));
- } else if ($build->isPausing()) {
- $item->addIcon('fa-pause', pht('Pausing'));
- } else if ($build->isResuming()) {
- $item->addIcon('fa-play', pht('Resuming'));
- }
+ $status = $build->getBuildPendingStatusObject();
- $build_id = $build->getID();
-
- $restart_uri = "build/restart/{$build_id}/buildable/";
- $resume_uri = "build/resume/{$build_id}/buildable/";
- $pause_uri = "build/pause/{$build_id}/buildable/";
- $abort_uri = "build/abort/{$build_id}/buildable/";
-
- $item->addAction(
- id(new PHUIListItemView())
- ->setIcon('fa-repeat')
- ->setName(pht('Restart'))
- ->setHref($this->getApplicationURI($restart_uri))
- ->setWorkflow(true)
- ->setDisabled(!$build->canRestartBuild()));
-
- if ($build->canResumeBuild()) {
- $item->addAction(
- id(new PHUIListItemView())
- ->setIcon('fa-play')
- ->setName(pht('Resume'))
- ->setHref($this->getApplicationURI($resume_uri))
- ->setWorkflow(true));
- } else {
- $item->addAction(
- id(new PHUIListItemView())
- ->setIcon('fa-pause')
- ->setName(pht('Pause'))
- ->setHref($this->getApplicationURI($pause_uri))
- ->setWorkflow(true)
- ->setDisabled(!$build->canPauseBuild()));
- }
+ $item->setStatusIcon(
+ $status->getIconIcon().' '.$status->getIconColor(),
+ $status->getName());
$targets = $build->getBuildTargets();
diff --git a/src/applications/harbormaster/integration/buildkite/HarbormasterBuildkiteHookHandler.php b/src/applications/harbormaster/controller/HarbormasterBuildkiteHookController.php
rename from src/applications/harbormaster/integration/buildkite/HarbormasterBuildkiteHookHandler.php
rename to src/applications/harbormaster/controller/HarbormasterBuildkiteHookController.php
--- a/src/applications/harbormaster/integration/buildkite/HarbormasterBuildkiteHookHandler.php
+++ b/src/applications/harbormaster/controller/HarbormasterBuildkiteHookController.php
@@ -1,10 +1,10 @@
<?php
-final class HarbormasterBuildkiteHookHandler
- extends HarbormasterHookHandler {
+final class HarbormasterBuildkiteHookController
+ extends HarbormasterController {
- public function getName() {
- return 'buildkite';
+ public function shouldRequireLogin() {
+ return false;
}
/**
diff --git a/src/applications/harbormaster/integration/circleci/HarbormasterCircleCIHookHandler.php b/src/applications/harbormaster/controller/HarbormasterCircleCIHookController.php
rename from src/applications/harbormaster/integration/circleci/HarbormasterCircleCIHookHandler.php
rename to src/applications/harbormaster/controller/HarbormasterCircleCIHookController.php
--- a/src/applications/harbormaster/integration/circleci/HarbormasterCircleCIHookHandler.php
+++ b/src/applications/harbormaster/controller/HarbormasterCircleCIHookController.php
@@ -1,10 +1,10 @@
<?php
-final class HarbormasterCircleCIHookHandler
- extends HarbormasterHookHandler {
+final class HarbormasterCircleCIHookController
+ extends HarbormasterController {
- public function getName() {
- return 'circleci';
+ public function shouldRequireLogin() {
+ return false;
}
/**
diff --git a/src/applications/harbormaster/controller/HarbormasterHookController.php b/src/applications/harbormaster/controller/HarbormasterHookController.php
deleted file mode 100644
--- a/src/applications/harbormaster/controller/HarbormasterHookController.php
+++ /dev/null
@@ -1,21 +0,0 @@
-<?php
-
-final class HarbormasterHookController
- extends HarbormasterController {
-
- public function shouldRequireLogin() {
- return false;
- }
-
- public function handleRequest(AphrontRequest $request) {
- $name = $request->getURIData('handler');
- $handler = HarbormasterHookHandler::getHandler($name);
-
- if (!$handler) {
- throw new Exception(pht('No handler found for %s', $name));
- }
-
- return $handler->handleRequest($request);
- }
-
-}
diff --git a/src/applications/harbormaster/editor/HarbormasterBuildEditEngine.php b/src/applications/harbormaster/editor/HarbormasterBuildEditEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/editor/HarbormasterBuildEditEngine.php
@@ -0,0 +1,87 @@
+<?php
+
+final class HarbormasterBuildEditEngine
+ extends PhabricatorEditEngine {
+
+ const ENGINECONST = 'harbormaster.build';
+
+ public function isEngineConfigurable() {
+ return false;
+ }
+
+ public function getEngineName() {
+ return pht('Harbormaster Builds');
+ }
+
+ public function getSummaryHeader() {
+ return pht('Edit Harbormaster Build Configurations');
+ }
+
+ public function getSummaryText() {
+ return pht('This engine is used to edit Harbormaster builds.');
+ }
+
+ public function getEngineApplicationClass() {
+ return 'PhabricatorHarbormasterApplication';
+ }
+
+ protected function newEditableObject() {
+ $viewer = $this->getViewer();
+ return HarbormasterBuild::initializeNewBuild($viewer);
+ }
+
+ protected function newObjectQuery() {
+ return new HarbormasterBuildQuery();
+ }
+
+ protected function newEditableObjectForDocumentation() {
+ $object = new DifferentialRevision();
+
+ $buildable = id(new HarbormasterBuildable())
+ ->attachBuildableObject($object);
+
+ return $this->newEditableObject()
+ ->attachBuildable($buildable);
+ }
+
+ protected function getObjectCreateTitleText($object) {
+ return pht('Create Build');
+ }
+
+ protected function getObjectCreateButtonText($object) {
+ return pht('Create Build');
+ }
+
+ protected function getObjectEditTitleText($object) {
+ return pht('Edit Build: %s', $object->getName());
+ }
+
+ protected function getObjectEditShortText($object) {
+ return pht('Edit Build');
+ }
+
+ protected function getObjectCreateShortText() {
+ return pht('Create Build');
+ }
+
+ protected function getObjectName() {
+ return pht('Build');
+ }
+
+ protected function getEditorURI() {
+ return '/harbormaster/build/edit/';
+ }
+
+ protected function getObjectCreateCancelURI($object) {
+ return '/harbormaster/';
+ }
+
+ protected function getObjectViewURI($object) {
+ return $object->getURI();
+ }
+
+ protected function buildCustomEditFields($object) {
+ return array();
+ }
+
+}
diff --git a/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php b/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php
--- a/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php
+++ b/src/applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php
@@ -11,115 +11,4 @@
return pht('Harbormaster Builds');
}
- public function getTransactionTypes() {
- $types = parent::getTransactionTypes();
-
- $types[] = HarbormasterBuildTransaction::TYPE_CREATE;
- $types[] = HarbormasterBuildTransaction::TYPE_COMMAND;
-
- return $types;
- }
-
- protected function getCustomTransactionOldValue(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- switch ($xaction->getTransactionType()) {
- case HarbormasterBuildTransaction::TYPE_CREATE:
- case HarbormasterBuildTransaction::TYPE_COMMAND:
- return null;
- }
-
- return parent::getCustomTransactionOldValue($object, $xaction);
- }
-
- protected function getCustomTransactionNewValue(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- switch ($xaction->getTransactionType()) {
- case HarbormasterBuildTransaction::TYPE_CREATE:
- return true;
- case HarbormasterBuildTransaction::TYPE_COMMAND:
- return $xaction->getNewValue();
- }
-
- return parent::getCustomTransactionNewValue($object, $xaction);
- }
-
- protected function applyCustomInternalTransaction(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- switch ($xaction->getTransactionType()) {
- case HarbormasterBuildTransaction::TYPE_CREATE:
- return;
- case HarbormasterBuildTransaction::TYPE_COMMAND:
- return $this->executeBuildCommand($object, $xaction);
- }
-
- return parent::applyCustomInternalTransaction($object, $xaction);
- }
-
- private function executeBuildCommand(
- HarbormasterBuild $build,
- HarbormasterBuildTransaction $xaction) {
-
- $command = $xaction->getNewValue();
-
- switch ($command) {
- case HarbormasterBuildCommand::COMMAND_RESTART:
- $issuable = $build->canRestartBuild();
- break;
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- $issuable = $build->canPauseBuild();
- break;
- case HarbormasterBuildCommand::COMMAND_RESUME:
- $issuable = $build->canResumeBuild();
- break;
- case HarbormasterBuildCommand::COMMAND_ABORT:
- $issuable = $build->canAbortBuild();
- break;
- default:
- throw new Exception(pht('Unknown command %s', $command));
- }
-
- if (!$issuable) {
- return;
- }
-
- $actor = $this->getActor();
- if (!$build->canIssueCommand($actor, $command)) {
- return;
- }
-
- id(new HarbormasterBuildCommand())
- ->setAuthorPHID($xaction->getAuthorPHID())
- ->setTargetPHID($build->getPHID())
- ->setCommand($command)
- ->save();
-
- PhabricatorWorker::scheduleTask(
- 'HarbormasterBuildWorker',
- array(
- 'buildID' => $build->getID(),
- ),
- array(
- 'objectPHID' => $build->getPHID(),
- ));
- }
-
- protected function applyCustomExternalTransaction(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- switch ($xaction->getTransactionType()) {
- case HarbormasterBuildTransaction::TYPE_CREATE:
- case HarbormasterBuildTransaction::TYPE_COMMAND:
- return;
- }
-
- return parent::applyCustomExternalTransaction($object, $xaction);
- }
-
}
diff --git a/src/applications/harbormaster/editor/HarbormasterBuildableEditEngine.php b/src/applications/harbormaster/editor/HarbormasterBuildableEditEngine.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/editor/HarbormasterBuildableEditEngine.php
@@ -0,0 +1,84 @@
+<?php
+
+final class HarbormasterBuildableEditEngine
+ extends PhabricatorEditEngine {
+
+ const ENGINECONST = 'harbormaster.buildable';
+
+ public function isEngineConfigurable() {
+ return false;
+ }
+
+ public function getEngineName() {
+ return pht('Harbormaster Buildables');
+ }
+
+ public function getSummaryHeader() {
+ return pht('Edit Harbormaster Buildable Configurations');
+ }
+
+ public function getSummaryText() {
+ return pht('This engine is used to edit Harbormaster buildables.');
+ }
+
+ public function getEngineApplicationClass() {
+ return 'PhabricatorHarbormasterApplication';
+ }
+
+ protected function newEditableObject() {
+ $viewer = $this->getViewer();
+ return HarbormasterBuildable::initializeNewBuildable($viewer);
+ }
+
+ protected function newObjectQuery() {
+ return new HarbormasterBuildableQuery();
+ }
+
+ protected function newEditableObjectForDocumentation() {
+ $object = new DifferentialRevision();
+
+ return $this->newEditableObject()
+ ->attachBuildableObject($object);
+ }
+
+ protected function getObjectCreateTitleText($object) {
+ return pht('Create Buildable');
+ }
+
+ protected function getObjectCreateButtonText($object) {
+ return pht('Create Buildable');
+ }
+
+ protected function getObjectEditTitleText($object) {
+ return pht('Edit Buildable: %s', $object->getName());
+ }
+
+ protected function getObjectEditShortText($object) {
+ return pht('Edit Buildable');
+ }
+
+ protected function getObjectCreateShortText() {
+ return pht('Create Buildable');
+ }
+
+ protected function getObjectName() {
+ return pht('Buildable');
+ }
+
+ protected function getEditorURI() {
+ return '/harbormaster/buildable/edit/';
+ }
+
+ protected function getObjectCreateCancelURI($object) {
+ return '/harbormaster/';
+ }
+
+ protected function getObjectViewURI($object) {
+ return $object->getURI();
+ }
+
+ protected function buildCustomEditFields($object) {
+ return array();
+ }
+
+}
diff --git a/src/applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php b/src/applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php
--- a/src/applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php
+++ b/src/applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php
@@ -11,66 +11,4 @@
return pht('Harbormaster Buildables');
}
- public function getTransactionTypes() {
- $types = parent::getTransactionTypes();
-
- $types[] = HarbormasterBuildableTransaction::TYPE_CREATE;
- $types[] = HarbormasterBuildableTransaction::TYPE_COMMAND;
-
- return $types;
- }
-
- protected function getCustomTransactionOldValue(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- switch ($xaction->getTransactionType()) {
- case HarbormasterBuildableTransaction::TYPE_CREATE:
- case HarbormasterBuildableTransaction::TYPE_COMMAND:
- return null;
- }
-
- return parent::getCustomTransactionOldValue($object, $xaction);
- }
-
- protected function getCustomTransactionNewValue(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- switch ($xaction->getTransactionType()) {
- case HarbormasterBuildableTransaction::TYPE_CREATE:
- return true;
- case HarbormasterBuildableTransaction::TYPE_COMMAND:
- return $xaction->getNewValue();
- }
-
- return parent::getCustomTransactionNewValue($object, $xaction);
- }
-
- protected function applyCustomInternalTransaction(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- switch ($xaction->getTransactionType()) {
- case HarbormasterBuildableTransaction::TYPE_CREATE:
- case HarbormasterBuildableTransaction::TYPE_COMMAND:
- return;
- }
-
- return parent::applyCustomInternalTransaction($object, $xaction);
- }
-
- protected function applyCustomExternalTransaction(
- PhabricatorLiskDAO $object,
- PhabricatorApplicationTransaction $xaction) {
-
- switch ($xaction->getTransactionType()) {
- case HarbormasterBuildableTransaction::TYPE_CREATE:
- case HarbormasterBuildableTransaction::TYPE_COMMAND:
- return;
- }
-
- return parent::applyCustomExternalTransaction($object, $xaction);
- }
-
}
diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
--- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
+++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php
@@ -49,6 +49,7 @@
}
public function continueBuild() {
+ $viewer = $this->getViewer();
$build = $this->getBuild();
$lock_key = 'harbormaster.build:'.$build->getID();
@@ -68,7 +69,7 @@
$lock->unlock();
- $this->releaseAllArtifacts($build);
+ $build->releaseAllArtifacts($viewer);
throw $ex;
}
@@ -99,56 +100,66 @@
// If we are no longer building for any reason, release all artifacts.
if (!$build->isBuilding()) {
- $this->releaseAllArtifacts($build);
+ $build->releaseAllArtifacts($viewer);
}
}
private function updateBuild(HarbormasterBuild $build) {
- if ($build->isAborting()) {
- $this->releaseAllArtifacts($build);
- $build->setBuildStatus(HarbormasterBuildStatus::STATUS_ABORTED);
- $build->save();
- }
+ $viewer = $this->getViewer();
- if (($build->getBuildStatus() == HarbormasterBuildStatus::STATUS_PENDING) ||
- ($build->isRestarting())) {
- $this->restartBuild($build);
- $build->setBuildStatus(HarbormasterBuildStatus::STATUS_BUILDING);
- $build->save();
- }
+ $content_source = PhabricatorContentSource::newForSource(
+ PhabricatorDaemonContentSource::SOURCECONST);
- if ($build->isResuming()) {
- $build->setBuildStatus(HarbormasterBuildStatus::STATUS_BUILDING);
- $build->save();
+ $acting_phid = $viewer->getPHID();
+ if (!$acting_phid) {
+ $acting_phid = id(new PhabricatorHarbormasterApplication())->getPHID();
}
- if ($build->isPausing() && !$build->isComplete()) {
- $build->setBuildStatus(HarbormasterBuildStatus::STATUS_PAUSED);
- $build->save();
- }
+ $editor = $build->getApplicationTransactionEditor()
+ ->setActor($viewer)
+ ->setActingAsPHID($acting_phid)
+ ->setContentSource($content_source)
+ ->setContinueOnNoEffect(true)
+ ->setContinueOnMissingFields(true);
- $build->deleteUnprocessedCommands();
+ $xactions = array();
- if ($build->getBuildStatus() == HarbormasterBuildStatus::STATUS_BUILDING) {
- $this->updateBuildSteps($build);
- }
- }
+ $messages = $build->getUnprocessedMessagesForApply();
+ foreach ($messages as $message) {
+ $message_type = $message->getType();
- private function restartBuild(HarbormasterBuild $build) {
+ $message_xaction =
+ HarbormasterBuildMessageTransaction::getTransactionTypeForMessageType(
+ $message_type);
- // We're restarting the build, so release all previous artifacts.
- $this->releaseAllArtifacts($build);
+ if (!$message_xaction) {
+ continue;
+ }
- // Increment the build generation counter on the build.
- $build->setBuildGeneration($build->getBuildGeneration() + 1);
+ $xactions[] = $build->getApplicationTransactionTemplate()
+ ->setAuthorPHID($message->getAuthorPHID())
+ ->setTransactionType($message_xaction)
+ ->setNewValue($message_type);
+ }
+
+ if (!$xactions) {
+ if ($build->isPending()) {
+ // TODO: This should be a transaction.
+
+ $build->restartBuild($viewer);
+ $build->setBuildStatus(HarbormasterBuildStatus::STATUS_BUILDING);
+ $build->save();
+ }
+ }
- // Currently running targets should periodically check their build
- // generation (which won't have changed) against the build's generation.
- // If it is different, they will automatically stop what they're doing
- // and abort.
+ if ($xactions) {
+ $editor->applyTransactions($build, $xactions);
+ $build->markUnprocessedMessagesAsProcessed();
+ }
- // Previously we used to delete targets, logs and artifacts here. Instead,
- // leave them around so users can view previous generations of this build.
+ if ($build->getBuildStatus() == HarbormasterBuildStatus::STATUS_BUILDING) {
+ $this->updateBuildSteps($build);
+ }
}
private function updateBuildSteps(HarbormasterBuild $build) {
@@ -596,29 +607,6 @@
->publishBuildable($old, $new);
}
- private function releaseAllArtifacts(HarbormasterBuild $build) {
- $targets = id(new HarbormasterBuildTargetQuery())
- ->setViewer(PhabricatorUser::getOmnipotentUser())
- ->withBuildPHIDs(array($build->getPHID()))
- ->withBuildGenerations(array($build->getBuildGeneration()))
- ->execute();
-
- if (count($targets) === 0) {
- return;
- }
-
- $target_phids = mpull($targets, 'getPHID');
-
- $artifacts = id(new HarbormasterBuildArtifactQuery())
- ->setViewer(PhabricatorUser::getOmnipotentUser())
- ->withBuildTargetPHIDs($target_phids)
- ->withIsReleased(false)
- ->execute();
- foreach ($artifacts as $artifact) {
- $artifact->releaseArtifact();
- }
- }
-
private function releaseQueuedArtifacts() {
foreach ($this->artifactReleaseQueue as $key => $artifact) {
$artifact->releaseArtifact();
diff --git a/src/applications/harbormaster/exception/HarbormasterRestartException.php b/src/applications/harbormaster/exception/HarbormasterMessageException.php
rename from src/applications/harbormaster/exception/HarbormasterRestartException.php
rename to src/applications/harbormaster/exception/HarbormasterMessageException.php
--- a/src/applications/harbormaster/exception/HarbormasterRestartException.php
+++ b/src/applications/harbormaster/exception/HarbormasterMessageException.php
@@ -1,6 +1,6 @@
<?php
-final class HarbormasterRestartException extends Exception {
+final class HarbormasterMessageException extends Exception {
private $title;
private $body = array();
@@ -9,7 +9,11 @@
$this->setTitle($title);
$this->appendParagraph($body);
- parent::__construct($title);
+ parent::__construct(
+ pht(
+ '%s: %s',
+ $title,
+ $body));
}
public function setTitle($title) {
@@ -30,4 +34,13 @@
return $this->body;
}
+ public function newDisplayString() {
+ $title = $this->getTitle();
+
+ $body = $this->getBody();
+ $body = implode("\n\n", $body);
+
+ return pht('%s: %s', $title, $body);
+ }
+
}
diff --git a/src/applications/harbormaster/integration/HarbormasterHookHandler.php b/src/applications/harbormaster/integration/HarbormasterHookHandler.php
deleted file mode 100644
--- a/src/applications/harbormaster/integration/HarbormasterHookHandler.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-abstract class HarbormasterHookHandler
- extends Phobject {
-
- public static function getHandlers() {
- return id(new PhutilClassMapQuery())
- ->setAncestorClass(__CLASS__)
- ->setUniqueMethod('getName')
- ->execute();
- }
-
- public static function getHandler($handler) {
- $base = idx(self::getHandlers(), $handler);
-
- if ($base) {
- return (clone $base);
- }
-
- return null;
- }
-
- abstract public function getName();
-
- abstract public function handleRequest(AphrontRequest $request);
-
-}
diff --git a/src/applications/harbormaster/management/HarbormasterManagementRestartWorkflow.php b/src/applications/harbormaster/management/HarbormasterManagementRestartWorkflow.php
--- a/src/applications/harbormaster/management/HarbormasterManagementRestartWorkflow.php
+++ b/src/applications/harbormaster/management/HarbormasterManagementRestartWorkflow.php
@@ -32,10 +32,10 @@
if (!$ids && !$active) {
throw new PhutilArgumentUsageException(
- pht('Use --id or --active to select builds.'));
+ pht('Use "--id" or "--active" to select builds.'));
} if ($ids && $active) {
throw new PhutilArgumentUsageException(
- pht('Use one of --id or --active to select builds, but not both.'));
+ pht('Use one of "--id" or "--active" to select builds, but not both.'));
}
$query = id(new HarbormasterBuildQuery())
@@ -48,50 +48,41 @@
}
$builds = $query->execute();
- $console = PhutilConsole::getConsole();
$count = count($builds);
if (!$count) {
- $console->writeOut("%s\n", pht('No builds to restart.'));
+ $this->logSkip(
+ pht('SKIP'),
+ pht('No builds to restart.'));
return 0;
}
+
$prompt = pht('Restart %s build(s)?', new PhutilNumber($count));
if (!phutil_console_confirm($prompt)) {
- $console->writeOut("%s\n", pht('Cancelled.'));
- return 1;
+ throw new ArcanistUserAbortException();
}
- $app_phid = id(new PhabricatorHarbormasterApplication())->getPHID();
- $editor = id(new HarbormasterBuildTransactionEditor())
- ->setActor($viewer)
- ->setActingAsPHID($app_phid)
- ->setContentSource($this->newContentSource());
+ $message = new HarbormasterBuildMessageRestartTransaction();
+
foreach ($builds as $build) {
- $console->writeOut(
- "<bg:blue> %s </bg> %s\n",
+ $this->logInfo(
pht('RESTARTING'),
pht('Build %d: %s', $build->getID(), $build->getName()));
- if (!$build->canRestartBuild()) {
- $console->writeOut(
- "<bg:yellow> %s </bg> %s\n",
- pht('INVALID'),
- pht('Cannot be restarted.'));
- continue;
- }
- $xactions = array();
- $xactions[] = id(new HarbormasterBuildTransaction())
- ->setTransactionType(HarbormasterBuildTransaction::TYPE_COMMAND)
- ->setNewValue(HarbormasterBuildCommand::COMMAND_RESTART);
+
try {
- $editor->applyTransactions($build, $xactions);
- } catch (Exception $e) {
- $message = phutil_console_wrap($e->getMessage(), 2);
- $console->writeOut(
- "<bg:red> %s </bg>\n%s\n",
- pht('FAILED'),
- $message);
- continue;
+ $message->assertCanSendMessage($viewer, $build);
+ } catch (HarbormasterMessageException $ex) {
+ $this->logWarn(
+ pht('INVALID'),
+ $ex->newDisplayString());
}
- $console->writeOut("<bg:green> %s </bg>\n", pht('SUCCESS'));
+
+ $build->sendMessage(
+ $viewer,
+ $message->getHarbormasterBuildMessageType());
+
+ $this->logOkay(
+ pht('QUEUED'),
+ pht('Sent a restart message to build.'));
}
return 0;
diff --git a/src/applications/harbormaster/query/HarbormasterBuildQuery.php b/src/applications/harbormaster/query/HarbormasterBuildQuery.php
--- a/src/applications/harbormaster/query/HarbormasterBuildQuery.php
+++ b/src/applications/harbormaster/query/HarbormasterBuildQuery.php
@@ -104,13 +104,13 @@
}
$build_phids = mpull($page, 'getPHID');
- $commands = id(new HarbormasterBuildCommand())->loadAllWhere(
- 'targetPHID IN (%Ls) ORDER BY id ASC',
+ $messages = id(new HarbormasterBuildMessage())->loadAllWhere(
+ 'receiverPHID IN (%Ls) AND isConsumed = 0 ORDER BY id ASC',
$build_phids);
- $commands = mgroup($commands, 'getTargetPHID');
+ $messages = mgroup($messages, 'getReceiverPHID');
foreach ($page as $build) {
- $unprocessed_commands = idx($commands, $build->getPHID(), array());
- $build->attachUnprocessedCommands($unprocessed_commands);
+ $unprocessed_messages = idx($messages, $build->getPHID(), array());
+ $build->attachUnprocessedMessages($unprocessed_messages);
}
if ($this->needBuildTargets) {
diff --git a/src/applications/harbormaster/step/HarbormasterAbortOlderBuildsBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterAbortOlderBuildsBuildStepImplementation.php
--- a/src/applications/harbormaster/step/HarbormasterAbortOlderBuildsBuildStepImplementation.php
+++ b/src/applications/harbormaster/step/HarbormasterAbortOlderBuildsBuildStepImplementation.php
@@ -122,7 +122,7 @@
foreach ($abort_builds as $abort_build) {
$abort_build->sendMessage(
$viewer,
- HarbormasterBuildCommand::COMMAND_ABORT);
+ HarbormasterBuildMessageAbortTransaction::MESSAGETYPE);
}
}
diff --git a/src/applications/harbormaster/integration/buildkite/HarbormasterBuildkiteBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php
rename from src/applications/harbormaster/integration/buildkite/HarbormasterBuildkiteBuildStepImplementation.php
rename to src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php
diff --git a/src/applications/harbormaster/integration/circleci/HarbormasterCircleCIBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php
rename from src/applications/harbormaster/integration/circleci/HarbormasterCircleCIBuildStepImplementation.php
rename to src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php
diff --git a/src/applications/harbormaster/storage/HarbormasterBuildCommand.php b/src/applications/harbormaster/storage/HarbormasterBuildCommand.php
deleted file mode 100644
--- a/src/applications/harbormaster/storage/HarbormasterBuildCommand.php
+++ /dev/null
@@ -1,27 +0,0 @@
-<?php
-
-final class HarbormasterBuildCommand extends HarbormasterDAO {
-
- const COMMAND_PAUSE = 'pause';
- const COMMAND_RESUME = 'resume';
- const COMMAND_RESTART = 'restart';
- const COMMAND_ABORT = 'abort';
-
- protected $authorPHID;
- protected $targetPHID;
- protected $command;
-
- protected function getConfiguration() {
- return array(
- self::CONFIG_COLUMN_SCHEMA => array(
- 'command' => 'text128',
- ),
- self::CONFIG_KEY_SCHEMA => array(
- 'key_target' => array(
- 'columns' => array('targetPHID'),
- ),
- ),
- ) + parent::getConfiguration();
- }
-
-}
diff --git a/src/applications/harbormaster/storage/HarbormasterBuildTransaction.php b/src/applications/harbormaster/storage/HarbormasterBuildTransaction.php
--- a/src/applications/harbormaster/storage/HarbormasterBuildTransaction.php
+++ b/src/applications/harbormaster/storage/HarbormasterBuildTransaction.php
@@ -1,10 +1,7 @@
<?php
final class HarbormasterBuildTransaction
- extends PhabricatorApplicationTransaction {
-
- const TYPE_CREATE = 'harbormaster:build:create';
- const TYPE_COMMAND = 'harbormaster:build:command';
+ extends PhabricatorModularTransaction {
public function getApplicationName() {
return 'harbormaster';
@@ -14,81 +11,8 @@
return HarbormasterBuildPHIDType::TYPECONST;
}
- public function getTitle() {
- $author_phid = $this->getAuthorPHID();
-
- $old = $this->getOldValue();
- $new = $this->getNewValue();
-
- switch ($this->getTransactionType()) {
- case self::TYPE_CREATE:
- return pht(
- '%s created this build.',
- $this->renderHandleLink($author_phid));
- case self::TYPE_COMMAND:
- switch ($new) {
- case HarbormasterBuildCommand::COMMAND_RESTART:
- return pht(
- '%s restarted this build.',
- $this->renderHandleLink($author_phid));
- case HarbormasterBuildCommand::COMMAND_ABORT:
- return pht(
- '%s aborted this build.',
- $this->renderHandleLink($author_phid));
- case HarbormasterBuildCommand::COMMAND_RESUME:
- return pht(
- '%s resumed this build.',
- $this->renderHandleLink($author_phid));
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- return pht(
- '%s paused this build.',
- $this->renderHandleLink($author_phid));
- }
- }
- return parent::getTitle();
+ public function getBaseTransactionClass() {
+ return 'HarbormasterBuildTransactionType';
}
- public function getIcon() {
- $author_phid = $this->getAuthorPHID();
-
- $old = $this->getOldValue();
- $new = $this->getNewValue();
-
- switch ($this->getTransactionType()) {
- case self::TYPE_CREATE:
- return 'fa-plus';
- case self::TYPE_COMMAND:
- switch ($new) {
- case HarbormasterBuildCommand::COMMAND_RESTART:
- return 'fa-backward';
- case HarbormasterBuildCommand::COMMAND_RESUME:
- return 'fa-play';
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- return 'fa-pause';
- case HarbormasterBuildCommand::COMMAND_ABORT:
- return 'fa-exclamation-triangle';
- }
- }
-
- return parent::getIcon();
- }
-
- public function getColor() {
- $author_phid = $this->getAuthorPHID();
-
- $old = $this->getOldValue();
- $new = $this->getNewValue();
-
- switch ($this->getTransactionType()) {
- case self::TYPE_CREATE:
- return 'green';
- case self::TYPE_COMMAND:
- switch ($new) {
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- case HarbormasterBuildCommand::COMMAND_ABORT:
- return 'red';
- }
- }
- return parent::getColor();
- }
}
diff --git a/src/applications/harbormaster/storage/HarbormasterBuildableTransaction.php b/src/applications/harbormaster/storage/HarbormasterBuildableTransaction.php
--- a/src/applications/harbormaster/storage/HarbormasterBuildableTransaction.php
+++ b/src/applications/harbormaster/storage/HarbormasterBuildableTransaction.php
@@ -1,10 +1,7 @@
<?php
final class HarbormasterBuildableTransaction
- extends PhabricatorApplicationTransaction {
-
- const TYPE_CREATE = 'harbormaster:buildable:create';
- const TYPE_COMMAND = 'harbormaster:buildable:command';
+ extends PhabricatorModularTransaction {
public function getApplicationName() {
return 'harbormaster';
@@ -14,74 +11,8 @@
return HarbormasterBuildablePHIDType::TYPECONST;
}
- public function getTitle() {
- $author_phid = $this->getAuthorPHID();
-
- $old = $this->getOldValue();
- $new = $this->getNewValue();
-
- switch ($this->getTransactionType()) {
- case self::TYPE_CREATE:
- return pht(
- '%s created this buildable.',
- $this->renderHandleLink($author_phid));
- case self::TYPE_COMMAND:
- switch ($new) {
- case HarbormasterBuildCommand::COMMAND_RESTART:
- return pht(
- '%s restarted this buildable.',
- $this->renderHandleLink($author_phid));
- case HarbormasterBuildCommand::COMMAND_RESUME:
- return pht(
- '%s resumed this buildable.',
- $this->renderHandleLink($author_phid));
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- return pht(
- '%s paused this buildable.',
- $this->renderHandleLink($author_phid));
- }
- }
- return parent::getTitle();
+ public function getBaseTransactionClass() {
+ return 'HarbormasterBuildableTransactionType';
}
- public function getIcon() {
- $author_phid = $this->getAuthorPHID();
-
- $old = $this->getOldValue();
- $new = $this->getNewValue();
-
- switch ($this->getTransactionType()) {
- case self::TYPE_CREATE:
- return 'fa-plus';
- case self::TYPE_COMMAND:
- switch ($new) {
- case HarbormasterBuildCommand::COMMAND_RESTART:
- return 'fa-backward';
- case HarbormasterBuildCommand::COMMAND_RESUME:
- return 'fa-play';
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- return 'fa-pause';
- }
- }
-
- return parent::getIcon();
- }
-
- public function getColor() {
- $author_phid = $this->getAuthorPHID();
-
- $old = $this->getOldValue();
- $new = $this->getNewValue();
-
- switch ($this->getTransactionType()) {
- case self::TYPE_CREATE:
- return 'green';
- case self::TYPE_COMMAND:
- switch ($new) {
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- return 'red';
- }
- }
- return parent::getColor();
- }
}
diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
--- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php
+++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php
@@ -18,7 +18,7 @@
private $buildable = self::ATTACHABLE;
private $buildPlan = self::ATTACHABLE;
private $buildTargets = self::ATTACHABLE;
- private $unprocessedCommands = self::ATTACHABLE;
+ private $unprocessedMessages = self::ATTACHABLE;
public static function initializeNewBuild(PhabricatorUser $actor) {
return id(new HarbormasterBuild())
@@ -28,7 +28,7 @@
public function delete() {
$this->openTransaction();
- $this->deleteUnprocessedCommands();
+ $this->deleteUnprocessedMessages();
$result = parent::delete();
$this->saveTransaction();
@@ -207,11 +207,25 @@
return $this->getBuildStatusObject()->isFailed();
}
+ public function isPending() {
+ return $this->getBuildstatusObject()->isPending();
+ }
+
public function getURI() {
$id = $this->getID();
return "/harbormaster/build/{$id}/";
}
+ public function getBuildPendingStatusObject() {
+ list($pending_status) = $this->getUnprocessedMessageState();
+
+ if ($pending_status !== null) {
+ return HarbormasterBuildStatus::newBuildStatusObject($pending_status);
+ }
+
+ return $this->getBuildStatusObject();
+ }
+
protected function getBuildStatusObject() {
$status_key = $this->getBuildStatus();
return HarbormasterBuildStatus::newBuildStatusObject($status_key);
@@ -222,263 +236,176 @@
}
-/* -( Build Commands )----------------------------------------------------- */
+/* -( Build Messages )----------------------------------------------------- */
- private function getUnprocessedCommands() {
- return $this->assertAttached($this->unprocessedCommands);
+ private function getUnprocessedMessages() {
+ return $this->assertAttached($this->unprocessedMessages);
}
- public function attachUnprocessedCommands(array $commands) {
- $this->unprocessedCommands = $commands;
- return $this;
- }
+ public function getUnprocessedMessagesForApply() {
+ $unprocessed_state = $this->getUnprocessedMessageState();
+ list($pending_status, $apply_messages) = $unprocessed_state;
- public function canRestartBuild() {
- try {
- $this->assertCanRestartBuild();
- return true;
- } catch (HarbormasterRestartException $ex) {
- return false;
- }
+ return $apply_messages;
}
- public function assertCanRestartBuild() {
- if ($this->isAutobuild()) {
- throw new HarbormasterRestartException(
- pht('Can Not Restart Autobuild'),
- pht(
- 'This build can not be restarted because it is an automatic '.
- 'build.'));
- }
+ private function getUnprocessedMessageState() {
+ // NOTE: If a build has multiple unprocessed messages, we'll ignore
+ // messages that are obsoleted by a later or stronger message.
+ //
+ // For example, if a build has both "pause" and "abort" messages in queue,
+ // we just ignore the "pause" message and perform an "abort", since pausing
+ // first wouldn't affect the final state, so we can just skip it.
+ //
+ // Likewise, if a build has both "restart" and "abort" messages, the most
+ // recent message is controlling: we'll take whichever action a command
+ // was most recently issued for.
- $restartable = HarbormasterBuildPlanBehavior::BEHAVIOR_RESTARTABLE;
- $plan = $this->getBuildPlan();
-
- // See T13526. Users who can't see the "BuildPlan" can end up here with
- // no object. This is highly questionable.
- if (!$plan) {
- throw new HarbormasterRestartException(
- pht('No Build Plan Permission'),
- pht(
- 'You can not restart this build because you do not have '.
- 'permission to access the build plan.'));
- }
+ $is_restarting = false;
+ $is_aborting = false;
+ $is_pausing = false;
+ $is_resuming = false;
- $option = HarbormasterBuildPlanBehavior::getBehavior($restartable)
- ->getPlanOption($plan);
- $option_key = $option->getKey();
-
- $never_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_NEVER;
- $is_never = ($option_key === $never_restartable);
- if ($is_never) {
- throw new HarbormasterRestartException(
- pht('Build Plan Prevents Restart'),
- pht(
- 'This build can not be restarted because the build plan is '.
- 'configured to prevent the build from restarting.'));
- }
+ $apply_messages = array();
- $failed_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_IF_FAILED;
- $is_failed = ($option_key === $failed_restartable);
- if ($is_failed) {
- if (!$this->isFailed()) {
- throw new HarbormasterRestartException(
- pht('Only Restartable if Failed'),
- pht(
- 'This build can not be restarted because the build plan is '.
- 'configured to prevent the build from restarting unless it '.
- 'has failed, and it has not failed.'));
+ foreach ($this->getUnprocessedMessages() as $message_object) {
+ $message_type = $message_object->getType();
+ switch ($message_type) {
+ case HarbormasterBuildMessageRestartTransaction::MESSAGETYPE:
+ $is_restarting = true;
+ $is_aborting = false;
+ $apply_messages = array($message_object);
+ break;
+ case HarbormasterBuildMessageAbortTransaction::MESSAGETYPE:
+ $is_aborting = true;
+ $is_restarting = false;
+ $apply_messages = array($message_object);
+ break;
+ case HarbormasterBuildMessagePauseTransaction::MESSAGETYPE:
+ $is_pausing = true;
+ $is_resuming = false;
+ $apply_messages = array($message_object);
+ break;
+ case HarbormasterBuildMessageResumeTransaction::MESSAGETYPE:
+ $is_resuming = true;
+ $is_pausing = false;
+ $apply_messages = array($message_object);
+ break;
}
}
- if ($this->isRestarting()) {
- throw new HarbormasterRestartException(
- pht('Already Restarting'),
- pht(
- 'This build is already restarting. You can not reissue a restart '.
- 'command to a restarting build.'));
- }
- }
-
- public function canPauseBuild() {
- if ($this->isAutobuild()) {
- return false;
+ $pending_status = null;
+ if ($is_restarting) {
+ $pending_status = HarbormasterBuildStatus::PENDING_RESTARTING;
+ } else if ($is_aborting) {
+ $pending_status = HarbormasterBuildStatus::PENDING_ABORTING;
+ } else if ($is_pausing) {
+ $pending_status = HarbormasterBuildStatus::PENDING_PAUSING;
+ } else if ($is_resuming) {
+ $pending_status = HarbormasterBuildStatus::PENDING_RESUMING;
}
- return !$this->isComplete() &&
- !$this->isPaused() &&
- !$this->isPausing();
+ return array($pending_status, $apply_messages);
}
- public function canAbortBuild() {
- if ($this->isAutobuild()) {
- return false;
- }
-
- return !$this->isComplete();
- }
-
- public function canResumeBuild() {
- if ($this->isAutobuild()) {
- return false;
- }
-
- return $this->isPaused() &&
- !$this->isResuming();
+ public function attachUnprocessedMessages(array $messages) {
+ assert_instances_of($messages, 'HarbormasterBuildMessage');
+ $this->unprocessedMessages = $messages;
+ return $this;
}
public function isPausing() {
- $is_pausing = false;
- foreach ($this->getUnprocessedCommands() as $command_object) {
- $command = $command_object->getCommand();
- switch ($command) {
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- $is_pausing = true;
- break;
- case HarbormasterBuildCommand::COMMAND_RESUME:
- case HarbormasterBuildCommand::COMMAND_RESTART:
- $is_pausing = false;
- break;
- case HarbormasterBuildCommand::COMMAND_ABORT:
- $is_pausing = true;
- break;
- }
- }
-
- return $is_pausing;
+ return $this->getBuildPendingStatusObject()->isPausing();
}
public function isResuming() {
- $is_resuming = false;
- foreach ($this->getUnprocessedCommands() as $command_object) {
- $command = $command_object->getCommand();
- switch ($command) {
- case HarbormasterBuildCommand::COMMAND_RESTART:
- case HarbormasterBuildCommand::COMMAND_RESUME:
- $is_resuming = true;
- break;
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- $is_resuming = false;
- break;
- case HarbormasterBuildCommand::COMMAND_ABORT:
- $is_resuming = false;
- break;
- }
- }
-
- return $is_resuming;
+ return $this->getBuildPendingStatusObject()->isResuming();
}
public function isRestarting() {
- $is_restarting = false;
- foreach ($this->getUnprocessedCommands() as $command_object) {
- $command = $command_object->getCommand();
- switch ($command) {
- case HarbormasterBuildCommand::COMMAND_RESTART:
- $is_restarting = true;
- break;
- }
- }
-
- return $is_restarting;
+ return $this->getBuildPendingStatusObject()->isRestarting();
}
public function isAborting() {
- $is_aborting = false;
- foreach ($this->getUnprocessedCommands() as $command_object) {
- $command = $command_object->getCommand();
- switch ($command) {
- case HarbormasterBuildCommand::COMMAND_ABORT:
- $is_aborting = true;
- break;
- }
+ return $this->getBuildPendingStatusObject()->isAborting();
+ }
+
+ public function markUnprocessedMessagesAsProcessed() {
+ foreach ($this->getUnprocessedMessages() as $key => $message_object) {
+ $message_object
+ ->setIsConsumed(1)
+ ->save();
}
- return $is_aborting;
+ return $this;
}
- public function deleteUnprocessedCommands() {
- foreach ($this->getUnprocessedCommands() as $key => $command_object) {
- $command_object->delete();
- unset($this->unprocessedCommands[$key]);
+ public function deleteUnprocessedMessages() {
+ foreach ($this->getUnprocessedMessages() as $key => $message_object) {
+ $message_object->delete();
+ unset($this->unprocessedMessages[$key]);
}
return $this;
}
- public function canIssueCommand(PhabricatorUser $viewer, $command) {
- try {
- $this->assertCanIssueCommand($viewer, $command);
- return true;
- } catch (Exception $ex) {
- return false;
- }
+ public function sendMessage(PhabricatorUser $viewer, $message_type) {
+ HarbormasterBuildMessage::initializeNewMessage($viewer)
+ ->setReceiverPHID($this->getPHID())
+ ->setType($message_type)
+ ->save();
+
+ PhabricatorWorker::scheduleTask(
+ 'HarbormasterBuildWorker',
+ array(
+ 'buildID' => $this->getID(),
+ ),
+ array(
+ 'objectPHID' => $this->getPHID(),
+ 'containerPHID' => $this->getBuildablePHID(),
+ ));
}
- public function assertCanIssueCommand(PhabricatorUser $viewer, $command) {
- $plan = $this->getBuildPlan();
+ public function releaseAllArtifacts(PhabricatorUser $viewer) {
+ $targets = id(new HarbormasterBuildTargetQuery())
+ ->setViewer($viewer)
+ ->withBuildPHIDs(array($this->getPHID()))
+ ->withBuildGenerations(array($this->getBuildGeneration()))
+ ->execute();
- // See T13526. Users without permission to access the build plan can
- // currently end up here with no "BuildPlan" object.
- if (!$plan) {
- return false;
+ if (!$targets) {
+ return;
}
- $need_edit = true;
- switch ($command) {
- case HarbormasterBuildCommand::COMMAND_RESTART:
- case HarbormasterBuildCommand::COMMAND_PAUSE:
- case HarbormasterBuildCommand::COMMAND_RESUME:
- case HarbormasterBuildCommand::COMMAND_ABORT:
- if ($plan->canRunWithoutEditCapability()) {
- $need_edit = false;
- }
- break;
- default:
- throw new Exception(
- pht(
- 'Invalid Harbormaster build command "%s".',
- $command));
- }
+ $target_phids = mpull($targets, 'getPHID');
- // Issuing these commands requires that you be able to edit the build, to
- // prevent enemy engineers from sabotaging your builds. See T9614.
- if ($need_edit) {
- PhabricatorPolicyFilter::requireCapability(
- $viewer,
- $plan,
- PhabricatorPolicyCapability::CAN_EDIT);
+ $artifacts = id(new HarbormasterBuildArtifactQuery())
+ ->setViewer($viewer)
+ ->withBuildTargetPHIDs($target_phids)
+ ->withIsReleased(false)
+ ->execute();
+ foreach ($artifacts as $artifact) {
+ $artifact->releaseArtifact();
}
}
- public function sendMessage(PhabricatorUser $viewer, $command) {
- // TODO: This should not be an editor transaction, but there are plans to
- // merge BuildCommand into BuildMessage which should moot this. As this
- // exists today, it can race against BuildEngine.
+ public function restartBuild(PhabricatorUser $viewer) {
+ // TODO: This should become transactional.
- // This is a bogus content source, but this whole flow should be obsolete
- // soon.
- $content_source = PhabricatorContentSource::newForSource(
- PhabricatorConsoleContentSource::SOURCECONST);
+ // We're restarting the build, so release all previous artifacts.
+ $this->releaseAllArtifacts($viewer);
- $editor = id(new HarbormasterBuildTransactionEditor())
- ->setActor($viewer)
- ->setContentSource($content_source)
- ->setContinueOnNoEffect(true)
- ->setContinueOnMissingFields(true);
-
- $viewer_phid = $viewer->getPHID();
- if (!$viewer_phid) {
- $acting_phid = id(new PhabricatorHarbormasterApplication())->getPHID();
- $editor->setActingAsPHID($acting_phid);
- }
+ // Increment the build generation counter on the build.
+ $this->setBuildGeneration($this->getBuildGeneration() + 1);
- $xaction = id(new HarbormasterBuildTransaction())
- ->setTransactionType(HarbormasterBuildTransaction::TYPE_COMMAND)
- ->setNewValue($command);
+ // Currently running targets should periodically check their build
+ // generation (which won't have changed) against the build's generation.
+ // If it is different, they will automatically stop what they're doing
+ // and abort.
- $editor->applyTransactions($this, array($xaction));
+ // Previously we used to delete targets, logs and artifacts here. Instead,
+ // leave them around so users can view previous generations of this build.
}
diff --git a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php
--- a/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php
+++ b/src/applications/harbormaster/view/HarbormasterUnitSummaryView.php
@@ -5,7 +5,6 @@
private $buildable;
private $messages;
private $limit;
- private $excuse;
private $showViewAll;
public function setBuildable(HarbormasterBuildable $buildable) {
@@ -23,11 +22,6 @@
return $this;
}
- public function setExcuse($excuse) {
- $this->excuse = $excuse;
- return $this;
- }
-
public function setShowViewAll($show_view_all) {
$this->showViewAll = $show_view_all;
return $this;
@@ -88,21 +82,6 @@
$table->setLimit($this->limit);
}
- $excuse = $this->excuse;
- if (strlen($excuse)) {
- $excuse_icon = id(new PHUIIconView())
- ->setIcon('fa-commenting-o red');
-
- $table->setNotice(
- array(
- $excuse_icon,
- ' ',
- phutil_tag('strong', array(), pht('Excuse:')),
- ' ',
- $excuse,
- ));
- }
-
$box->setTable($table);
return $box;
diff --git a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageAbortTransaction.php b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageAbortTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageAbortTransaction.php
@@ -0,0 +1,117 @@
+<?php
+
+final class HarbormasterBuildMessageAbortTransaction
+ extends HarbormasterBuildMessageTransaction {
+
+ const TRANSACTIONTYPE = 'message/abort';
+ const MESSAGETYPE = 'abort';
+
+ public function getHarbormasterBuildMessageName() {
+ return pht('Abort Build');
+ }
+
+ public function getHarbormasterBuildableMessageName() {
+ return pht('Abort Builds');
+ }
+
+ public function newConfirmPromptTitle() {
+ return pht('Really abort build?');
+ }
+
+ public function getHarbormasterBuildableMessageEffect() {
+ return pht('Build will abort.');
+ }
+
+ public function newConfirmPromptBody() {
+ return pht(
+ 'Progress on this build will be discarded. Really abort build?');
+ }
+
+ public function getHarbormasterBuildMessageDescription() {
+ return pht('Abort the build, discarding progress.');
+ }
+
+ public function newBuildableConfirmPromptTitle(
+ array $builds,
+ array $sendable) {
+ return pht(
+ 'Really abort %s build(s)?',
+ phutil_count($builds));
+ }
+
+ public function newBuildableConfirmPromptBody(
+ array $builds,
+ array $sendable) {
+
+ if (count($sendable) === count($builds)) {
+ return pht(
+ 'If you abort all builds, work will halt immediately. Work '.
+ 'will be discarded, and builds must be completely restarted.');
+ } else {
+ return pht(
+ 'You can only abort some builds. Work will halt immediately on '.
+ 'builds you can abort. Progress will be discarded, and builds must '.
+ 'be completely restarted if you want them to complete.');
+ }
+ }
+
+ public function getTitle() {
+ return pht(
+ '%s aborted this build.',
+ $this->renderAuthor());
+ }
+
+ public function getIcon() {
+ return 'fa-exclamation-triangle';
+ }
+
+ public function getColor() {
+ return 'red';
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $actor = $this->getActor();
+ $build = $object;
+
+ $build->setBuildStatus(HarbormasterBuildStatus::STATUS_ABORTED);
+ }
+
+ public function applyExternalEffects($object, $value) {
+ $actor = $this->getActor();
+ $build = $object;
+
+ $build->releaseAllArtifacts($actor);
+ }
+
+ protected function newCanApplyMessageAssertion(
+ PhabricatorUser $viewer,
+ HarbormasterBuild $build) {
+
+ if ($build->isAutobuild()) {
+ throw new HarbormasterMessageException(
+ pht('Unable to Abort Build'),
+ pht(
+ 'You can not abort a build that uses an autoplan.'));
+ }
+
+ if ($build->isComplete()) {
+ throw new HarbormasterMessageException(
+ pht('Unable to Abort Build'),
+ pht(
+ 'You can not abort this biuld because it is already complete.'));
+ }
+ }
+
+ protected function newCanSendMessageAssertion(
+ PhabricatorUser $viewer,
+ HarbormasterBuild $build) {
+
+ if ($build->isAborting()) {
+ throw new HarbormasterMessageException(
+ pht('Unable to Abort Build'),
+ pht(
+ 'You can not abort this build because it is already aborting.'));
+ }
+ }
+
+}
diff --git a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessagePauseTransaction.php b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessagePauseTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessagePauseTransaction.php
@@ -0,0 +1,126 @@
+<?php
+
+final class HarbormasterBuildMessagePauseTransaction
+ extends HarbormasterBuildMessageTransaction {
+
+ const TRANSACTIONTYPE = 'message/pause';
+ const MESSAGETYPE = 'pause';
+
+ public function getHarbormasterBuildMessageName() {
+ return pht('Pause Build');
+ }
+
+ public function getHarbormasterBuildableMessageName() {
+ return pht('Pause Builds');
+ }
+
+ public function newConfirmPromptTitle() {
+ return pht('Really pause build?');
+ }
+
+ public function getHarbormasterBuildableMessageEffect() {
+ return pht('Build will pause.');
+ }
+
+ public function newConfirmPromptBody() {
+ return pht(
+ 'If you pause this build, work will halt once the current steps '.
+ 'complete. You can resume the build later.');
+ }
+
+
+ public function getHarbormasterBuildMessageDescription() {
+ return pht('Pause the build.');
+ }
+
+ public function newBuildableConfirmPromptTitle(
+ array $builds,
+ array $sendable) {
+ return pht(
+ 'Really pause %s build(s)?',
+ phutil_count($builds));
+ }
+
+ public function newBuildableConfirmPromptBody(
+ array $builds,
+ array $sendable) {
+
+ if (count($sendable) === count($builds)) {
+ return pht(
+ 'If you pause all builds, work will halt once the current steps '.
+ 'complete. You can resume the builds later.');
+ } else {
+ return pht(
+ 'You can only pause some builds. Once the current steps complete, '.
+ 'work will halt on builds you can pause. You can resume the builds '.
+ 'later.');
+ }
+ }
+
+ public function getTitle() {
+ return pht(
+ '%s paused this build.',
+ $this->renderAuthor());
+ }
+
+ public function getIcon() {
+ return 'fa-pause';
+ }
+
+ public function getColor() {
+ return 'red';
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $actor = $this->getActor();
+ $build = $object;
+
+ $build->setBuildStatus(HarbormasterBuildStatus::STATUS_PAUSED);
+ }
+
+ protected function newCanApplyMessageAssertion(
+ PhabricatorUser $viewer,
+ HarbormasterBuild $build) {
+
+ if ($build->isAutobuild()) {
+ throw new HarbormasterMessageException(
+ pht('Unable to Pause Build'),
+ pht('You can not pause a build that uses an autoplan.'));
+ }
+
+ if ($build->isPaused()) {
+ throw new HarbormasterMessageException(
+ pht('Unable to Pause Build'),
+ pht('You can not pause this build because it is already paused.'));
+ }
+
+ if ($build->isComplete()) {
+ throw new HarbormasterMessageException(
+ pht('Unable to Pause Build'),
+ pht('You can not pause this build because it has already completed.'));
+ }
+ }
+
+ protected function newCanSendMessageAssertion(
+ PhabricatorUser $viewer,
+ HarbormasterBuild $build) {
+
+ if ($build->isPausing()) {
+ throw new HarbormasterMessageException(
+ pht('Unable to Pause Build'),
+ pht('You can not pause this build because it is already pausing.'));
+ }
+
+ if ($build->isRestarting()) {
+ throw new HarbormasterMessageException(
+ pht('Unable to Pause Build'),
+ pht('You can not pause this build because it is already restarting.'));
+ }
+
+ if ($build->isAborting()) {
+ throw new HarbormasterMessageException(
+ pht('Unable to Pause Build'),
+ pht('You can not pause this build because it is already aborting.'));
+ }
+ }
+}
diff --git a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageRestartTransaction.php b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageRestartTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageRestartTransaction.php
@@ -0,0 +1,171 @@
+<?php
+
+final class HarbormasterBuildMessageRestartTransaction
+ extends HarbormasterBuildMessageTransaction {
+
+ const TRANSACTIONTYPE = 'message/restart';
+ const MESSAGETYPE = 'restart';
+
+ public function getHarbormasterBuildMessageName() {
+ return pht('Restart Build');
+ }
+
+ public function getHarbormasterBuildableMessageName() {
+ return pht('Restart Builds');
+ }
+
+ public function getHarbormasterBuildableMessageEffect() {
+ return pht('Build will restart.');
+ }
+
+ public function newConfirmPromptTitle() {
+ return pht('Really restart build?');
+ }
+
+ public function newConfirmPromptBody() {
+ return pht(
+ 'Progress on this build will be discarded and the build will restart. '.
+ 'Side effects of the build will occur again. Really restart build?');
+ }
+
+
+ public function getHarbormasterBuildMessageDescription() {
+ return pht('Restart the build, discarding all progress.');
+ }
+
+ public function newBuildableConfirmPromptTitle(
+ array $builds,
+ array $sendable) {
+ return pht(
+ 'Really restart %s build(s)?',
+ phutil_count($builds));
+ }
+
+ public function newBuildableConfirmPromptBody(
+ array $builds,
+ array $sendable) {
+
+ if (count($sendable) === count($builds)) {
+ return pht(
+ 'All builds will restart.');
+ } else {
+ return pht(
+ 'You can only restart some builds.');
+ }
+ }
+
+ public function newBuildableConfirmPromptWarnings(
+ array $builds,
+ array $sendable) {
+
+ $building = false;
+ foreach ($sendable as $build) {
+ if ($build->isBuilding()) {
+ $building = true;
+ break;
+ }
+ }
+
+ $warnings = array();
+
+ if ($building) {
+ $warnings[] = pht(
+ 'Progress on running builds will be discarded.');
+ }
+
+ if ($sendable) {
+ $warnings[] = pht(
+ 'When a build is restarted, side effects associated with '.
+ 'the build may occur again.');
+ }
+
+ return $warnings;
+ }
+
+ public function getTitle() {
+ return pht(
+ '%s restarted this build.',
+ $this->renderAuthor());
+ }
+
+ public function getIcon() {
+ return 'fa-repeat';
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $actor = $this->getActor();
+ $build = $object;
+
+ $build->restartBuild($actor);
+ $build->setBuildStatus(HarbormasterBuildStatus::STATUS_BUILDING);
+ }
+
+ protected function newCanApplyMessageAssertion(
+ PhabricatorUser $viewer,
+ HarbormasterBuild $build) {
+
+ if ($build->isAutobuild()) {
+ throw new HarbormasterMessageException(
+ pht('Can Not Restart Autobuild'),
+ pht(
+ 'This build can not be restarted because it is an automatic '.
+ 'build.'));
+ }
+
+ $restartable = HarbormasterBuildPlanBehavior::BEHAVIOR_RESTARTABLE;
+ $plan = $build->getBuildPlan();
+
+ // See T13526. Users who can't see the "BuildPlan" can end up here with
+ // no object. This is highly questionable.
+ if (!$plan) {
+ throw new HarbormasterMessageException(
+ pht('No Build Plan Permission'),
+ pht(
+ 'You can not restart this build because you do not have '.
+ 'permission to access the build plan.'));
+ }
+
+ $option = HarbormasterBuildPlanBehavior::getBehavior($restartable)
+ ->getPlanOption($plan);
+ $option_key = $option->getKey();
+
+ $never_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_NEVER;
+ $is_never = ($option_key === $never_restartable);
+ if ($is_never) {
+ throw new HarbormasterMessageException(
+ pht('Build Plan Prevents Restart'),
+ pht(
+ 'This build can not be restarted because the build plan is '.
+ 'configured to prevent the build from restarting.'));
+ }
+
+ $failed_restartable = HarbormasterBuildPlanBehavior::RESTARTABLE_IF_FAILED;
+ $is_failed = ($option_key === $failed_restartable);
+ if ($is_failed) {
+ if (!$this->isFailed()) {
+ throw new HarbormasterMessageException(
+ pht('Only Restartable if Failed'),
+ pht(
+ 'This build can not be restarted because the build plan is '.
+ 'configured to prevent the build from restarting unless it '.
+ 'has failed, and it has not failed.'));
+ }
+ }
+
+ }
+
+ protected function newCanSendMessageAssertion(
+ PhabricatorUser $viewer,
+ HarbormasterBuild $build) {
+
+ if ($build->isRestarting()) {
+ throw new HarbormasterMessageException(
+ pht('Already Restarting'),
+ pht(
+ 'This build is already restarting. You can not reissue a restart '.
+ 'command to a restarting build.'));
+ }
+
+ }
+
+}
diff --git a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageResumeTransaction.php b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageResumeTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageResumeTransaction.php
@@ -0,0 +1,119 @@
+<?php
+
+final class HarbormasterBuildMessageResumeTransaction
+ extends HarbormasterBuildMessageTransaction {
+
+ const TRANSACTIONTYPE = 'message/resume';
+ const MESSAGETYPE = 'resume';
+
+ public function getHarbormasterBuildMessageName() {
+ return pht('Resume Build');
+ }
+
+ public function getHarbormasterBuildableMessageName() {
+ return pht('Resume Builds');
+ }
+
+ public function getHarbormasterBuildableMessageEffect() {
+ return pht('Build will resume.');
+ }
+
+ public function newConfirmPromptTitle() {
+ return pht('Really resume build?');
+ }
+
+ public function newConfirmPromptBody() {
+ return pht(
+ 'Work will continue on the build. Really resume?');
+ }
+
+ public function getHarbormasterBuildMessageDescription() {
+ return pht('Resume work on a previously paused build.');
+ }
+
+ public function newBuildableConfirmPromptTitle(
+ array $builds,
+ array $sendable) {
+ return pht(
+ 'Really resume %s build(s)?',
+ phutil_count($builds));
+ }
+
+ public function newBuildableConfirmPromptBody(
+ array $builds,
+ array $sendable) {
+
+ if (count($sendable) === count($builds)) {
+ return pht(
+ 'Work will continue on all builds. Really resume?');
+ } else {
+ return pht(
+ 'You can only resume some builds. Work will continue on builds '.
+ 'you have permission to resume.');
+ }
+ }
+
+ public function getTitle() {
+ return pht(
+ '%s resumed this build.',
+ $this->renderAuthor());
+ }
+
+ public function getIcon() {
+ return 'fa-play';
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $actor = $this->getActor();
+ $build = $object;
+
+ $build->setBuildStatus(HarbormasterBuildStatus::STATUS_BUILDING);
+ }
+
+ protected function newCanApplyMessageAssertion(
+ PhabricatorUser $viewer,
+ HarbormasterBuild $build) {
+
+ if ($build->isAutobuild()) {
+ throw new HarbormasterMessageException(
+ pht('Unable to Resume Build'),
+ pht(
+ 'You can not resume a build that uses an autoplan.'));
+ }
+
+ if (!$build->isPaused() && !$build->isPausing()) {
+ throw new HarbormasterMessageException(
+ pht('Unable to Resume Build'),
+ pht(
+ 'You can not resume this build because it is not paused. You can '.
+ 'only resume a paused build.'));
+ }
+
+ }
+
+ protected function newCanSendMessageAssertion(
+ PhabricatorUser $viewer,
+ HarbormasterBuild $build) {
+
+ if ($build->isResuming()) {
+ throw new HarbormasterMessageException(
+ pht('Unable to Resume Build'),
+ pht(
+ 'You can not resume this build beacuse it is already resuming.'));
+ }
+
+ if ($build->isRestarting()) {
+ throw new HarbormasterMessageException(
+ pht('Unable to Resume Build'),
+ pht('You can not resume this build because it is already restarting.'));
+ }
+
+ if ($build->isAborting()) {
+ throw new HarbormasterMessageException(
+ pht('Unable to Resume Build'),
+ pht('You can not resume this build because it is already aborting.'));
+ }
+
+ }
+
+}
diff --git a/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageTransaction.php b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/xaction/build/HarbormasterBuildMessageTransaction.php
@@ -0,0 +1,158 @@
+<?php
+
+abstract class HarbormasterBuildMessageTransaction
+ extends HarbormasterBuildTransactionType {
+
+ final public function getHarbormasterBuildMessageType() {
+ return $this->getPhobjectClassConstant('MESSAGETYPE');
+ }
+
+ abstract public function getHarbormasterBuildMessageName();
+ abstract public function getHarbormasterBuildMessageDescription();
+ abstract public function getHarbormasterBuildableMessageName();
+ abstract public function getHarbormasterBuildableMessageEffect();
+
+ abstract public function newConfirmPromptTitle();
+ abstract public function newConfirmPromptBody();
+
+ abstract public function newBuildableConfirmPromptTitle(
+ array $builds,
+ array $sendable);
+
+ abstract public function newBuildableConfirmPromptBody(
+ array $builds,
+ array $sendable);
+
+ public function newBuildableConfirmPromptWarnings(
+ array $builds,
+ array $sendable) {
+ return array();
+ }
+
+ final public function generateOldValue($object) {
+ return null;
+ }
+
+ final public function getTransactionTypeForConduit($xaction) {
+ return 'message';
+ }
+
+ final public function getFieldValuesForConduit($xaction, $data) {
+ return array(
+ 'type' => $xaction->getNewValue(),
+ );
+ }
+
+ final public static function getAllMessages() {
+ $message_xactions = id(new PhutilClassMapQuery())
+ ->setAncestorClass(__CLASS__)
+ ->execute();
+
+ return $message_xactions;
+ }
+
+ final public static function getTransactionObjectForMessageType(
+ $message_type) {
+ $message_xactions = self::getAllMessages();
+
+ foreach ($message_xactions as $message_xaction) {
+ $xaction_type = $message_xaction->getHarbormasterBuildMessageType();
+ if ($xaction_type === $message_type) {
+ return $message_xaction;
+ }
+ }
+
+ return null;
+ }
+
+ final public static function getTransactionTypeForMessageType($message_type) {
+ $message_xaction = self::getTransactionObjectForMessageType($message_type);
+
+ if ($message_xaction) {
+ return $message_xaction->getTransactionTypeConstant();
+ }
+
+ return null;
+ }
+
+ final public function getTransactionHasEffect($object, $old, $new) {
+ return $this->canApplyMessage($this->getActor(), $object);
+ }
+
+ final public function canApplyMessage(
+ PhabricatorUser $viewer,
+ HarbormasterBuild $build) {
+
+ try {
+ $this->assertCanApplyMessage($viewer, $build);
+ return true;
+ } catch (HarbormasterMessageException $ex) {
+ return false;
+ }
+ }
+
+ final public function canSendMessage(
+ PhabricatorUser $viewer,
+ HarbormasterBuild $build) {
+
+ try {
+ $this->assertCanSendMessage($viewer, $build);
+ return true;
+ } catch (HarbormasterMessageException $ex) {
+ return false;
+ }
+ }
+
+ final public function assertCanApplyMessage(
+ PhabricatorUser $viewer,
+ HarbormasterBuild $build) {
+ $this->newCanApplyMessageAssertion($viewer, $build);
+ }
+
+ final public function assertCanSendMessage(
+ PhabricatorUser $viewer,
+ HarbormasterBuild $build) {
+ $plan = $build->getBuildPlan();
+
+ // See T13526. Users without permission to access the build plan can
+ // currently end up here with no "BuildPlan" object.
+ if (!$plan) {
+ throw new HarbormasterMessageException(
+ pht('No Build Plan Permission'),
+ pht(
+ 'You can not issue this command because you do not have '.
+ 'permission to access the build plan for this build.'));
+ }
+
+ // Issuing these commands requires that you be able to edit the build, to
+ // prevent enemy engineers from sabotaging your builds. See T9614.
+ if (!$plan->canRunWithoutEditCapability()) {
+ try {
+ PhabricatorPolicyFilter::requireCapability(
+ $viewer,
+ $plan,
+ PhabricatorPolicyCapability::CAN_EDIT);
+ } catch (PhabricatorPolicyException $ex) {
+ throw new HarbormasterMessageException(
+ pht('Insufficent Build Plan Permission'),
+ pht(
+ 'The build plan for this build is configured to prevent '.
+ 'users who can not edit it from issuing commands to the '.
+ 'build, and you do not have permission to edit the build '.
+ 'plan.'));
+ }
+ }
+
+ $this->newCanSendMessageAssertion($viewer, $build);
+ $this->assertCanApplyMessage($viewer, $build);
+ }
+
+ abstract protected function newCanSendMessageAssertion(
+ PhabricatorUser $viewer,
+ HarbormasterBuild $build);
+
+ abstract protected function newCanApplyMessageAssertion(
+ PhabricatorUser $viewer,
+ HarbormasterBuild $build);
+
+}
diff --git a/src/applications/harbormaster/xaction/build/HarbormasterBuildTransactionType.php b/src/applications/harbormaster/xaction/build/HarbormasterBuildTransactionType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/xaction/build/HarbormasterBuildTransactionType.php
@@ -0,0 +1,4 @@
+<?php
+
+abstract class HarbormasterBuildTransactionType
+ extends PhabricatorModularTransactionType {}
diff --git a/src/applications/harbormaster/xaction/buildable/HarbormasterBuildableMessageTransaction.php b/src/applications/harbormaster/xaction/buildable/HarbormasterBuildableMessageTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/xaction/buildable/HarbormasterBuildableMessageTransaction.php
@@ -0,0 +1,65 @@
+<?php
+
+final class HarbormasterBuildableMessageTransaction
+ extends HarbormasterBuildableTransactionType {
+
+ const TRANSACTIONTYPE = 'harbormaster:buildable:command';
+
+ public function generateOldValue($object) {
+ return null;
+ }
+
+ public function getTitle() {
+ $new = $this->getNewValue();
+
+ switch ($new) {
+ case HarbormasterBuildMessageRestartTransaction::MESSAGETYPE:
+ return pht(
+ '%s restarted this buildable.',
+ $this->renderAuthor());
+ case HarbormasterBuildMessageResumeTransaction::MESSAGETYPE:
+ return pht(
+ '%s resumed this buildable.',
+ $this->renderAuthor());
+ case HarbormasterBuildMessagePauseTransaction::MESSAGETYPE:
+ return pht(
+ '%s paused this buildable.',
+ $this->renderAuthor());
+ case HarbormasterBuildMessageAbortTransaction::MESSAGETYPE:
+ return pht(
+ '%s aborted this buildable.',
+ $this->renderAuthor());
+ }
+
+ return parent::getTitle();
+ }
+
+ public function getIcon() {
+ $new = $this->getNewValue();
+
+ switch ($new) {
+ case HarbormasterBuildMessageRestartTransaction::MESSAGETYPE:
+ return 'fa-backward';
+ case HarbormasterBuildMessageResumeTransaction::MESSAGETYPE:
+ return 'fa-play';
+ case HarbormasterBuildMessagePauseTransaction::MESSAGETYPE:
+ return 'fa-pause';
+ case HarbormasterBuildMessageAbortTransaction::MESSAGETYPE:
+ return 'fa-exclamation-triangle';
+ }
+
+ return parent::getIcon();
+ }
+
+ public function getColor() {
+ $new = $this->getNewValue();
+
+ switch ($new) {
+ case HarbormasterBuildMessagePauseTransaction::MESSAGETYPE:
+ return 'red';
+ }
+
+ return parent::getColor();
+ }
+
+}
diff --git a/src/applications/harbormaster/xaction/buildable/HarbormasterBuildableTransactionType.php b/src/applications/harbormaster/xaction/buildable/HarbormasterBuildableTransactionType.php
new file mode 100644
--- /dev/null
+++ b/src/applications/harbormaster/xaction/buildable/HarbormasterBuildableTransactionType.php
@@ -0,0 +1,4 @@
+<?php
+
+abstract class HarbormasterBuildableTransactionType
+ extends PhabricatorModularTransactionType {}
diff --git a/src/applications/meta/query/PhabricatorApplicationQuery.php b/src/applications/meta/query/PhabricatorApplicationQuery.php
--- a/src/applications/meta/query/PhabricatorApplicationQuery.php
+++ b/src/applications/meta/query/PhabricatorApplicationQuery.php
@@ -89,7 +89,7 @@
}
}
- if (strlen($this->nameContains)) {
+ if ($this->nameContains !== null) {
foreach ($apps as $key => $app) {
if (stripos($app->getName(), $this->nameContains) === false) {
unset($apps[$key]);
diff --git a/src/applications/metamta/adapter/PhabricatorMailPostmarkAdapter.php b/src/applications/metamta/adapter/PhabricatorMailPostmarkAdapter.php
--- a/src/applications/metamta/adapter/PhabricatorMailPostmarkAdapter.php
+++ b/src/applications/metamta/adapter/PhabricatorMailPostmarkAdapter.php
@@ -33,10 +33,11 @@
//
// "Configuring Outbound Email" should be updated if this changes.
//
- // These addresses were last updated in January 2019.
+ // These addresses were last updated in December 2021.
'50.31.156.6/32',
'50.31.156.77/32',
'18.217.206.57/32',
+ '3.134.147.250/32',
),
);
}
diff --git a/src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php b/src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php
--- a/src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php
+++ b/src/applications/metamta/parser/PhabricatorMetaMTAEmailBodyParser.php
@@ -11,7 +11,7 @@
*
* Or
*
- * !assign epriestley
+ * !assign alincoln
*
* please, take this task I took; its hard
*
@@ -20,9 +20,9 @@
* commands. For example, this body above might parse as:
*
* array(
- * 'body' => 'please, take this task I took; its hard',
+ * 'body' => 'please, take this task I took; it's hard',
* 'commands' => array(
- * array('assign', 'epriestley'),
+ * array('assign', 'alincoln'),
* ),
* )
*
diff --git a/src/applications/metamta/query/PhabricatorMetaMTAMemberQuery.php b/src/applications/metamta/query/PhabricatorMetaMTAMemberQuery.php
--- a/src/applications/metamta/query/PhabricatorMetaMTAMemberQuery.php
+++ b/src/applications/metamta/query/PhabricatorMetaMTAMemberQuery.php
@@ -60,6 +60,16 @@
}
$package_map[$package->getPHID()] = $package_owners;
}
+
+ // See T13648. We may have packages that no longer exist or can't be
+ // loaded (for example, because they have been destroyed). Give them
+ // empty entries in the map so we return a mapping for all input PHIDs.
+
+ foreach ($package_phids as $package_phid) {
+ if (!isset($package_map[$package_phid])) {
+ $package_map[$package_phid] = array();
+ }
+ }
}
$results = array();
diff --git a/src/applications/owners/controller/PhabricatorOwnersDetailController.php b/src/applications/owners/controller/PhabricatorOwnersDetailController.php
--- a/src/applications/owners/controller/PhabricatorOwnersDetailController.php
+++ b/src/applications/owners/controller/PhabricatorOwnersDetailController.php
@@ -197,6 +197,12 @@
$name = idx($spec, 'short', $dominion);
$view->addProperty(pht('Dominion'), $name);
+ $authority_mode = $package->getAuthorityMode();
+ $authority_map = PhabricatorOwnersPackage::getAuthorityOptionsMap();
+ $spec = idx($authority_map, $authority_mode, array());
+ $name = idx($spec, 'short', $authority_mode);
+ $view->addProperty(pht('Authority'), $name);
+
$auto = $package->getAutoReview();
$autoreview_map = PhabricatorOwnersPackage::getAutoreviewOptionsMap();
$spec = idx($autoreview_map, $auto, array());
diff --git a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php
--- a/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php
+++ b/src/applications/owners/editor/PhabricatorOwnersPackageEditEngine.php
@@ -90,6 +90,9 @@
$dominion_map = PhabricatorOwnersPackage::getDominionOptionsMap();
$dominion_map = ipull($dominion_map, 'name');
+ $authority_map = PhabricatorOwnersPackage::getAuthorityOptionsMap();
+ $authority_map = ipull($authority_map, 'name');
+
return array(
id(new PhabricatorTextEditField())
->setKey('name')
@@ -118,6 +121,16 @@
->setIsCopyable(true)
->setValue($object->getDominion())
->setOptions($dominion_map),
+ id(new PhabricatorSelectEditField())
+ ->setKey('authority')
+ ->setLabel(pht('Authority'))
+ ->setDescription(
+ pht('Change package authority rules.'))
+ ->setTransactionType(
+ PhabricatorOwnersPackageAuthorityTransaction::TRANSACTIONTYPE)
+ ->setIsCopyable(true)
+ ->setValue($object->getAuthorityMode())
+ ->setOptions($authority_map),
id(new PhabricatorSelectEditField())
->setKey('autoReview')
->setLabel(pht('Auto Review'))
diff --git a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php
--- a/src/applications/owners/query/PhabricatorOwnersPackageQuery.php
+++ b/src/applications/owners/query/PhabricatorOwnersPackageQuery.php
@@ -10,6 +10,7 @@
private $repositoryPHIDs;
private $paths;
private $statuses;
+ private $authorityModes;
private $controlMap = array();
private $controlResults;
@@ -77,6 +78,11 @@
return $this;
}
+ public function withAuthorityModes(array $modes) {
+ $this->authorityModes = $modes;
+ return $this;
+ }
+
public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint(
new PhabricatorOwnersPackageNameNgrams(),
@@ -231,6 +237,13 @@
$where[] = qsprintf($conn, '%LO', $clauses);
}
+ if ($this->authorityModes !== null) {
+ $where[] = qsprintf(
+ $conn,
+ 'authorityMode IN (%Ls)',
+ $this->authorityModes);
+ }
+
return $where;
}
diff --git a/src/applications/owners/storage/PhabricatorOwnersPackage.php b/src/applications/owners/storage/PhabricatorOwnersPackage.php
--- a/src/applications/owners/storage/PhabricatorOwnersPackage.php
+++ b/src/applications/owners/storage/PhabricatorOwnersPackage.php
@@ -21,6 +21,7 @@
protected $dominion;
protected $properties = array();
protected $auditingState;
+ protected $authorityMode;
private $paths = self::ATTACHABLE;
private $owners = self::ATTACHABLE;
@@ -41,6 +42,9 @@
const DOMINION_STRONG = 'strong';
const DOMINION_WEAK = 'weak';
+ const AUTHORITY_STRONG = 'strong';
+ const AUTHORITY_WEAK = 'weak';
+
const PROPERTY_IGNORED = 'ignored';
public static function initializeNewPackage(PhabricatorUser $actor) {
@@ -58,6 +62,7 @@
->setAuditingState(PhabricatorOwnersAuditRule::AUDITING_NONE)
->setAutoReview(self::AUTOREVIEW_NONE)
->setDominion(self::DOMINION_STRONG)
+ ->setAuthorityMode(self::AUTHORITY_STRONG)
->setViewPolicy($view_policy)
->setEditPolicy($edit_policy)
->attachPaths(array())
@@ -115,6 +120,19 @@
);
}
+ public static function getAuthorityOptionsMap() {
+ return array(
+ self::AUTHORITY_STRONG => array(
+ 'name' => pht('Strong (Package Owns Paths)'),
+ 'short' => pht('Strong'),
+ ),
+ self::AUTHORITY_WEAK => array(
+ 'name' => pht('Weak (Package Watches Paths)'),
+ 'short' => pht('Weak'),
+ ),
+ );
+ }
+
protected function getConfiguration() {
return array(
// This information is better available from the history table.
@@ -130,6 +148,7 @@
'status' => 'text32',
'autoReview' => 'text32',
'dominion' => 'text32',
+ 'authorityMode' => 'text32',
),
) + parent::getConfiguration();
}
@@ -568,6 +587,10 @@
return PhabricatorOwnersAuditRule::newFromState($this->getAuditingState());
}
+ public function getHasStrongAuthority() {
+ return ($this->getAuthorityMode() === self::AUTHORITY_STRONG);
+ }
+
/* -( PhabricatorPolicyInterface )----------------------------------------- */
@@ -696,6 +719,10 @@
->setKey('dominion')
->setType('map<string, wild>')
->setDescription(pht('Dominion setting information.')),
+ id(new PhabricatorConduitSearchFieldSpecification())
+ ->setKey('authority')
+ ->setType('map<string, wild>')
+ ->setDescription(pht('Authority setting information.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('ignored')
->setType('map<string, wild>')
@@ -747,6 +774,23 @@
'short' => $dominion_short,
);
+
+ $authority_value = $this->getAuthorityMode();
+ $authority_map = self::getAuthorityOptionsMap();
+ if (isset($authority_map[$authority_value])) {
+ $authority_label = $authority_map[$authority_value]['name'];
+ $authority_short = $authority_map[$authority_value]['short'];
+ } else {
+ $authority_label = pht('Unknown ("%s")', $authority_value);
+ $authority_short = pht('Unknown ("%s")', $authority_value);
+ }
+
+ $authority = array(
+ 'value' => $authority_value,
+ 'label' => $authority_label,
+ 'short' => $authority_short,
+ );
+
// Force this to always emit as a JSON object even if empty, never as
// a JSON list.
$ignored = $this->getIgnoredPathAttributes();
@@ -762,6 +806,7 @@
'review' => $review,
'audit' => $audit,
'dominion' => $dominion,
+ 'authority' => $authority,
'ignored' => $ignored,
);
}
diff --git a/src/applications/owners/xaction/PhabricatorOwnersPackageAuthorityTransaction.php b/src/applications/owners/xaction/PhabricatorOwnersPackageAuthorityTransaction.php
new file mode 100644
--- /dev/null
+++ b/src/applications/owners/xaction/PhabricatorOwnersPackageAuthorityTransaction.php
@@ -0,0 +1,56 @@
+<?php
+
+final class PhabricatorOwnersPackageAuthorityTransaction
+ extends PhabricatorOwnersPackageTransactionType {
+
+ const TRANSACTIONTYPE = 'owners.authority';
+
+ public function generateOldValue($object) {
+ return $object->getAuthorityMode();
+ }
+
+ public function validateTransactions($object, array $xactions) {
+ $errors = array();
+
+ $map = PhabricatorOwnersPackage::getAuthorityOptionsMap();
+ foreach ($xactions as $xaction) {
+ $new = $xaction->getNewValue();
+
+ if (empty($map[$new])) {
+ $valid = array_keys($map);
+
+ $errors[] = $this->newInvalidError(
+ pht(
+ 'Authority setting "%s" is not valid. '.
+ 'Valid settings are: %s.',
+ $new,
+ implode(', ', $valid)),
+ $xaction);
+ }
+ }
+
+ return $errors;
+ }
+
+ public function applyInternalEffects($object, $value) {
+ $object->setAuthorityMode($value);
+ }
+
+ public function getTitle() {
+ $map = PhabricatorOwnersPackage::getAuthorityOptionsMap();
+ $map = ipull($map, 'short');
+
+ $old = $this->getOldValue();
+ $new = $this->getNewValue();
+
+ $old = idx($map, $old, $old);
+ $new = idx($map, $new, $new);
+
+ return pht(
+ '%s adjusted package authority rules from %s to %s.',
+ $this->renderAuthor(),
+ $this->renderValue($old),
+ $this->renderValue($new));
+ }
+
+}
diff --git a/src/applications/people/query/PhabricatorPeopleQuery.php b/src/applications/people/query/PhabricatorPeopleQuery.php
--- a/src/applications/people/query/PhabricatorPeopleQuery.php
+++ b/src/applications/people/query/PhabricatorPeopleQuery.php
@@ -341,7 +341,7 @@
(int)$this->isMailingList);
}
- if (strlen($this->nameLike)) {
+ if ($this->nameLike !== null) {
$where[] = qsprintf(
$conn,
'user.username LIKE %~ OR user.realname LIKE %~',
diff --git a/src/applications/people/storage/PhabricatorUser.php b/src/applications/people/storage/PhabricatorUser.php
--- a/src/applications/people/storage/PhabricatorUser.php
+++ b/src/applications/people/storage/PhabricatorUser.php
@@ -275,7 +275,8 @@
$this->setConduitCertificate($this->generateConduitCertificate());
}
- if (!strlen($this->getAccountSecret())) {
+ $secret = $this->getAccountSecret();
+ if (($secret === null) || !strlen($secret)) {
$this->setAccountSecret(Filesystem::readRandomCharacters(64));
}
diff --git a/src/applications/phid/handle/pool/PhabricatorHandleList.php b/src/applications/phid/handle/pool/PhabricatorHandleList.php
--- a/src/applications/phid/handle/pool/PhabricatorHandleList.php
+++ b/src/applications/phid/handle/pool/PhabricatorHandleList.php
@@ -126,22 +126,27 @@
/* -( Iterator )----------------------------------------------------------- */
+ #[\ReturnTypeWillChange]
public function rewind() {
$this->cursor = 0;
}
+ #[\ReturnTypeWillChange]
public function current() {
return $this->getHandle($this->phids[$this->cursor]);
}
+ #[\ReturnTypeWillChange]
public function key() {
return $this->phids[$this->cursor];
}
+ #[\ReturnTypeWillChange]
public function next() {
++$this->cursor;
}
+ #[\ReturnTypeWillChange]
public function valid() {
return ($this->cursor < $this->count);
}
@@ -150,6 +155,7 @@
/* -( ArrayAccess )-------------------------------------------------------- */
+ #[\ReturnTypeWillChange]
public function offsetExists($offset) {
// NOTE: We're intentionally not loading handles here so that isset()
// checks do not trigger fetches. This gives us better bulk loading
@@ -162,6 +168,7 @@
return isset($this->map[$offset]);
}
+ #[\ReturnTypeWillChange]
public function offsetGet($offset) {
if ($this->handles === null) {
$this->loadHandles();
@@ -169,10 +176,12 @@
return $this->handles[$offset];
}
+ #[\ReturnTypeWillChange]
public function offsetSet($offset, $value) {
$this->raiseImmutableException();
}
+ #[\ReturnTypeWillChange]
public function offsetUnset($offset) {
$this->raiseImmutableException();
}
@@ -189,6 +198,7 @@
/* -( Countable )---------------------------------------------------------- */
+ #[\ReturnTypeWillChange]
public function count() {
return $this->count;
}
diff --git a/src/applications/phriction/application/PhabricatorPhrictionApplication.php b/src/applications/phriction/application/PhabricatorPhrictionApplication.php
--- a/src/applications/phriction/application/PhabricatorPhrictionApplication.php
+++ b/src/applications/phriction/application/PhabricatorPhrictionApplication.php
@@ -61,7 +61,7 @@
'new/' => 'PhrictionNewController',
'move/(?P<id>[1-9]\d*)/' => 'PhrictionMoveController',
- 'preview/(?P<slug>.*/)' => 'PhrictionMarkupPreviewController',
+ 'preview/' => 'PhrictionMarkupPreviewController',
'diff/(?P<id>[1-9]\d*)/' => 'PhrictionDiffController',
$this->getEditRoutePattern('document/edit/')
diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php
--- a/src/applications/phriction/controller/PhrictionEditController.php
+++ b/src/applications/phriction/controller/PhrictionEditController.php
@@ -316,9 +316,17 @@
->setBackground(PHUIObjectBoxView::WHITE_CONFIG)
->setForm($form);
+ $preview_uri = '/phriction/preview/';
+ $preview_uri = new PhutilURI(
+ $preview_uri,
+ array(
+ 'slug' => $document->getSlug(),
+ ));
+ $preview_uri = phutil_string_cast($preview_uri);
+
$preview = id(new PHUIRemarkupPreviewPanel())
->setHeader($content->getTitle())
- ->setPreviewURI('/phriction/preview/'.$document->getSlug())
+ ->setPreviewURI($preview_uri)
->setControlID('document-textarea')
->setPreviewType(PHUIRemarkupPreviewPanel::DOCUMENT);
diff --git a/src/applications/phriction/controller/PhrictionMarkupPreviewController.php b/src/applications/phriction/controller/PhrictionMarkupPreviewController.php
--- a/src/applications/phriction/controller/PhrictionMarkupPreviewController.php
+++ b/src/applications/phriction/controller/PhrictionMarkupPreviewController.php
@@ -3,12 +3,29 @@
final class PhrictionMarkupPreviewController
extends PhabricatorController {
- public function processRequest() {
- $request = $this->getRequest();
- $viewer = $request->getUser();
+ public function handleRequest(AphrontRequest $request) {
+ $viewer = $request->getViewer();
$text = $request->getStr('text');
- $slug = $request->getURIData('slug');
+ $slug = $request->getStr('slug');
+
+ $document = id(new PhrictionDocumentQuery())
+ ->setViewer($viewer)
+ ->withSlugs(array($slug))
+ ->needContent(true)
+ ->executeOne();
+ if (!$document) {
+ $document = PhrictionDocument::initializeNewDocument(
+ $viewer,
+ $slug);
+
+ $content = id(new PhrictionContent())
+ ->setSlug($slug);
+
+ $document
+ ->setPHID($document->generatePHID())
+ ->attachContent($content);
+ }
$output = PhabricatorMarkupEngine::renderOneObject(
id(new PhabricatorMarkupOneOff())
@@ -17,10 +34,7 @@
->setContent($text),
'default',
$viewer,
- array(
- 'phriction.isPreview' => true,
- 'phriction.slug' => $slug,
- ));
+ $document);
return id(new AphrontAjaxResponse())
->setContent($output);
diff --git a/src/applications/phriction/markup/PhrictionRemarkupRule.php b/src/applications/phriction/markup/PhrictionRemarkupRule.php
--- a/src/applications/phriction/markup/PhrictionRemarkupRule.php
+++ b/src/applications/phriction/markup/PhrictionRemarkupRule.php
@@ -273,13 +273,6 @@
return null;
}
- // Handle content when it's a preview for the Phriction editor.
- if (is_array($context)) {
- if (idx($context, 'phriction.isPreview')) {
- return idx($context, 'phriction.slug');
- }
- }
-
if ($context instanceof PhrictionContent) {
return $context->getSlug();
}
diff --git a/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php b/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php
--- a/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php
+++ b/src/applications/repository/daemon/PhabricatorMercurialGraphStream.php
@@ -16,13 +16,23 @@
private $local = array();
private $localParents = array();
- public function __construct(PhabricatorRepository $repository, $commit) {
+ public function __construct(PhabricatorRepository $repository,
+ $start_commit = null) {
+
$this->repository = $repository;
+ $command = 'log --template %s --rev %s';
+ $template = '{rev}\1{node}\1{date}\1{parents}\2';
+ if ($start_commit !== null) {
+ $revset = hgsprintf('reverse(ancestors(%s))', $start_commit);
+ } else {
+ $revset = 'reverse(all())';
+ }
+
$future = $repository->getLocalCommandFuture(
- 'log --template %s --rev %s',
- '{rev}\1{node}\1{date}\1{parents}\2',
- hgsprintf('reverse(ancestors(%s))', $commit));
+ $command,
+ $template,
+ $revset);
$this->iterator = new LinesOfALargeExecFuture($future);
$this->iterator->setDelimiter("\2");
diff --git a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php
--- a/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php
+++ b/src/applications/repository/engine/PhabricatorRepositoryDiscoveryEngine.php
@@ -780,8 +780,7 @@
}
private function markUnreachableCommits(PhabricatorRepository $repository) {
- // For now, this is only supported for Git.
- if (!$repository->isGit()) {
+ if (!$repository->isGit() && !$repository->isHg()) {
return;
}
@@ -799,7 +798,11 @@
}
// We can share a single graph stream across all the checks we need to do.
- $stream = new PhabricatorGitGraphStream($repository);
+ if ($repository->isGit()) {
+ $stream = new PhabricatorGitGraphStream($repository);
+ } else if ($repository->isHg()) {
+ $stream = new PhabricatorMercurialGraphStream($repository);
+ }
foreach ($old_refs as $old_ref) {
$identifier = $old_ref->getCommitIdentifier();
@@ -812,7 +815,7 @@
private function markUnreachableFrom(
PhabricatorRepository $repository,
- PhabricatorGitGraphStream $stream,
+ PhabricatorRepositoryGraphStream $stream,
$identifier) {
$unreachable = array();
diff --git a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php
--- a/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php
+++ b/src/applications/repository/engine/PhabricatorRepositoryPullEngine.php
@@ -723,7 +723,6 @@
// This behavior has been reverted, but users who updated between Feb 1,
// 2012 and Mar 1, 2012 will have the erroring version. Do a dumb test
// against stdout to check for this possibility.
- // See: https://github.com/phacility/phabricator/issues/101/
// NOTE: Mercurial has translated versions, which translate this error
// string. In a translated version, the string will be something else,
diff --git a/src/applications/repository/management/PhabricatorRepositoryManagementMarkReachableWorkflow.php b/src/applications/repository/management/PhabricatorRepositoryManagementMarkReachableWorkflow.php
--- a/src/applications/repository/management/PhabricatorRepositoryManagementMarkReachableWorkflow.php
+++ b/src/applications/repository/management/PhabricatorRepositoryManagementMarkReachableWorkflow.php
@@ -48,11 +48,11 @@
}
private function markReachable(PhabricatorRepository $repository) {
- if (!$repository->isGit()) {
+ if (!$repository->isGit() && !$repository->isHg()) {
throw new PhutilArgumentUsageException(
pht(
- 'Only Git repositories are supported, this repository ("%s") is '.
- 'not a Git repository.',
+ 'Only Git and Mercurial repositories are supported, unable to '.
+ 'operate on this repository ("%s").',
$repository->getDisplayName()));
}
@@ -65,7 +65,12 @@
$flag = PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE;
- $graph = new PhabricatorGitGraphStream($repository);
+ if ($repository->isGit()) {
+ $graph = new PhabricatorGitGraphStream($repository);
+ } else if ($repository->isHg()) {
+ $graph = new PhabricatorMercurialGraphStream($repository);
+ }
+
foreach ($commits as $commit) {
$identifier = $commit->getCommitIdentifier();
diff --git a/src/applications/repository/storage/PhabricatorRepository.php b/src/applications/repository/storage/PhabricatorRepository.php
--- a/src/applications/repository/storage/PhabricatorRepository.php
+++ b/src/applications/repository/storage/PhabricatorRepository.php
@@ -499,7 +499,7 @@
public function passthruRemoteCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
- return $this->newRemoteCommandPassthru($args)->execute();
+ return $this->newRemoteCommandPassthru($args)->resolve();
}
private function newRemoteCommandFuture(array $argv) {
@@ -540,7 +540,7 @@
public function passthruLocalCommand($pattern /* , $arg, ... */) {
$args = func_get_args();
- return $this->newLocalCommandPassthru($args)->execute();
+ return $this->newLocalCommandPassthru($args)->resolve();
}
private function newLocalCommandFuture(array $argv) {
@@ -2269,10 +2269,9 @@
$never_proxy);
if (!$client) {
- $result = id(new ConduitCall($method, $params))
- ->setUser($viewer)
- ->execute();
- $future = new ImmediateFuture($result);
+ $conduit_call = id(new ConduitCall($method, $params))
+ ->setUser($viewer);
+ $future = new MethodCallFuture($conduit_call, 'execute');
} else {
$future = $client->callMethod($method, $params);
}
diff --git a/src/applications/repository/storage/PhabricatorRepositoryCommit.php b/src/applications/repository/storage/PhabricatorRepositoryCommit.php
--- a/src/applications/repository/storage/PhabricatorRepositoryCommit.php
+++ b/src/applications/repository/storage/PhabricatorRepositoryCommit.php
@@ -714,8 +714,8 @@
if (!$path) {
throw new Exception(
pht(
- 'This commit ("%s") is associated with a repository ("%s") that '.
- 'with a remote URI ("%s") that does not appear to be hosted on '.
+ 'This commit ("%s") is associated with a repository ("%s") which '.
+ 'has a remote URI ("%s") that does not appear to be hosted on '.
'GitHub. Repositories must be hosted on GitHub to be built with '.
'CircleCI.',
$commit_phid,
diff --git a/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php b/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php
--- a/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php
+++ b/src/applications/repository/storage/__tests__/PhabricatorRepositoryTestCase.php
@@ -100,55 +100,6 @@
}
- public function testFilterMercurialDebugOutput() {
- $map = array(
- '' => '',
-
- "quack\n" => "quack\n",
-
- "ignoring untrusted configuration option x.y = z\nquack\n" =>
- "quack\n",
-
- "ignoring untrusted configuration option x.y = z\n".
- "ignoring untrusted configuration option x.y = z\n".
- "quack\n" =>
- "quack\n",
-
- "ignoring untrusted configuration option x.y = z\n".
- "ignoring untrusted configuration option x.y = z\n".
- "ignoring untrusted configuration option x.y = z\n".
- "quack\n" =>
- "quack\n",
-
- "quack\n".
- "ignoring untrusted configuration option x.y = z\n".
- "ignoring untrusted configuration option x.y = z\n".
- "ignoring untrusted configuration option x.y = z\n" =>
- "quack\n",
-
- "ignoring untrusted configuration option x.y = z\n".
- "ignoring untrusted configuration option x.y = z\n".
- "duck\n".
- "ignoring untrusted configuration option x.y = z\n".
- "ignoring untrusted configuration option x.y = z\n".
- "bread\n".
- "ignoring untrusted configuration option x.y = z\n".
- "quack\n" =>
- "duck\nbread\nquack\n",
-
- "ignoring untrusted configuration option x.y = z\n".
- "duckignoring untrusted configuration option x.y = z\n".
- "quack" =>
- 'duckquack',
- );
-
- foreach ($map as $input => $expect) {
- $actual = DiffusionMercurialCommandEngine::filterMercurialDebugOutput(
- $input);
- $this->assertEqual($expect, $actual, $input);
- }
- }
-
public function testRepositoryShortNameValidation() {
$good = array(
'sensible-repository',
diff --git a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
--- a/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
+++ b/src/applications/search/engine/PhabricatorSearchEngineAPIMethod.php
@@ -60,7 +60,7 @@
PhabricatorEnv::getDoclink('Conduit API: Using Search Endpoints'));
}
- final public function getMethodDocumentation() {
+ final protected function newDocumentationPages(PhabricatorUser $viewer) {
$viewer = $this->getViewer();
$engine = $this->newSearchEngine()
@@ -70,17 +70,18 @@
$out = array();
- $out[] = $this->buildQueriesBox($engine);
- $out[] = $this->buildConstraintsBox($engine);
- $out[] = $this->buildOrderBox($engine, $query);
- $out[] = $this->buildFieldsBox($engine);
- $out[] = $this->buildAttachmentsBox($engine);
- $out[] = $this->buildPagingBox($engine);
+ $out[] = $this->buildQueriesDocumentationPage($viewer, $engine);
+ $out[] = $this->buildConstraintsDocumentationPage($viewer, $engine);
+ $out[] = $this->buildOrderDocumentationPage($viewer, $engine, $query);
+ $out[] = $this->buildFieldsDocumentationPage($viewer, $engine);
+ $out[] = $this->buildAttachmentsDocumentationPage($viewer, $engine);
+ $out[] = $this->buildPagingDocumentationPage($viewer, $engine);
return $out;
}
- private function buildQueriesBox(
+ private function buildQueriesDocumentationPage(
+ PhabricatorUser $viewer,
PhabricatorApplicationSearchEngine $engine) {
$viewer = $this->getViewer();
@@ -140,15 +141,18 @@
null,
));
- return id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Builtin and Saved Queries'))
- ->setCollapsed(true)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->appendChild($this->newRemarkupDocumentationView($info))
- ->appendChild($table);
+ $title = pht('Prebuilt Queries');
+ $content = array(
+ $this->newRemarkupDocumentationView($info),
+ $table,
+ );
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('queries');
}
- private function buildConstraintsBox(
+ private function buildConstraintsDocumentationPage(
+ PhabricatorUser $viewer,
PhabricatorApplicationSearchEngine $engine) {
$info = pht(<<<EOTEXT
@@ -281,16 +285,21 @@
'wide',
));
- return id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Custom Query Constraints'))
- ->setCollapsed(true)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->appendChild($this->newRemarkupDocumentationView($info))
- ->appendChild($table)
- ->appendChild($constant_lists);
+
+ $title = pht('Constraints');
+ $content = array(
+ $this->newRemarkupDocumentationView($info),
+ $table,
+ $constant_lists,
+ );
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('constraints')
+ ->setIconIcon('fa-filter');
}
- private function buildOrderBox(
+ private function buildOrderDocumentationPage(
+ PhabricatorUser $viewer,
PhabricatorApplicationSearchEngine $engine,
$query) {
@@ -388,18 +397,21 @@
'wide',
));
-
- return id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Result Ordering'))
- ->setCollapsed(true)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->appendChild($this->newRemarkupDocumentationView($orders_info))
- ->appendChild($orders_table)
- ->appendChild($this->newRemarkupDocumentationView($columns_info))
- ->appendChild($columns_table);
+ $title = pht('Result Ordering');
+ $content = array(
+ $this->newRemarkupDocumentationView($orders_info),
+ $orders_table,
+ $this->newRemarkupDocumentationView($columns_info),
+ $columns_table,
+ );
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('ordering')
+ ->setIconIcon('fa-sort-numeric-asc');
}
- private function buildFieldsBox(
+ private function buildFieldsDocumentationPage(
+ PhabricatorUser $viewer,
PhabricatorApplicationSearchEngine $engine) {
$info = pht(<<<EOTEXT
@@ -470,15 +482,19 @@
'wide',
));
- return id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Object Fields'))
- ->setCollapsed(true)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->appendChild($this->newRemarkupDocumentationView($info))
- ->appendChild($table);
+ $title = pht('Object Fields');
+ $content = array(
+ $this->newRemarkupDocumentationView($info),
+ $table,
+ );
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('fields')
+ ->setIconIcon('fa-cube');
}
- private function buildAttachmentsBox(
+ private function buildAttachmentsDocumentationPage(
+ PhabricatorUser $viewer,
PhabricatorApplicationSearchEngine $engine) {
$info = pht(<<<EOTEXT
@@ -560,15 +576,19 @@
'wide',
));
- return id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Attachments'))
- ->setCollapsed(true)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->appendChild($this->newRemarkupDocumentationView($info))
- ->appendChild($table);
+ $title = pht('Attachments');
+ $content = array(
+ $this->newRemarkupDocumentationView($info),
+ $table,
+ );
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('attachments')
+ ->setIconIcon('fa-cubes');
}
- private function buildPagingBox(
+ private function buildPagingDocumentationPage(
+ PhabricatorUser $viewer,
PhabricatorApplicationSearchEngine $engine) {
$info = pht(<<<EOTEXT
@@ -631,11 +651,14 @@
EOTEXT
);
- return id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Paging and Limits'))
- ->setCollapsed(true)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->appendChild($this->newRemarkupDocumentationView($info));
+ $title = pht('Paging and Limits');
+ $content = array(
+ $this->newRemarkupDocumentationView($info),
+ );
+
+ return $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('paging')
+ ->setIconIcon('fa-clone');
}
}
diff --git a/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php b/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php
--- a/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php
+++ b/src/applications/transactions/conduit/TransactionSearchConduitAPIMethod.php
@@ -13,7 +13,7 @@
'or an entire object type.');
}
- public function getMethodDocumentation() {
+ protected function newDocumentationPages(PhabricatorUser $viewer) {
$markup = pht(<<<EOREMARKUP
When an object (like a task) is edited, Phabricator creates a "transaction"
and applies it. This list of transactions on each object is the basis for
@@ -77,11 +77,10 @@
$markup = $this->newRemarkupDocumentationView($markup);
- return id(new PHUIObjectBoxView())
- ->setCollapsed(true)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->setHeaderText(pht('Method Details'))
- ->appendChild($markup);
+ return array(
+ $this->newDocumentationBoxPage($viewer, pht('Method Details'), $markup)
+ ->setAnchor('details'),
+ );
}
protected function defineParamTypes() {
diff --git a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
--- a/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
+++ b/src/applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php
@@ -38,9 +38,7 @@
PhabricatorEnv::getDoclink('Conduit API: Using Edit Endpoints'));
}
- final public function getMethodDocumentation() {
- $viewer = $this->getViewer();
-
+ final protected function newDocumentationPages(PhabricatorUser $viewer) {
$engine = $this->newEditEngine()
->setViewer($viewer);
@@ -48,16 +46,15 @@
$out = array();
- $out[] = $this->buildEditTypesBoxes($engine, $types);
-
- return $out;
+ return $this->buildEditTypesDocumentationPages($viewer, $engine, $types);
}
- private function buildEditTypesBoxes(
+ private function buildEditTypesDocumentationPages(
+ PhabricatorUser $viewer,
PhabricatorEditEngine $engine,
array $types) {
- $boxes = array();
+ $pages = array();
$summary_info = pht(
'This endpoint supports these types of transactions. See below for '.
@@ -83,12 +80,14 @@
'wide',
));
- $boxes[] = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Transaction Types'))
- ->setCollapsed(true)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->appendChild($this->buildRemarkup($summary_info))
- ->appendChild($summary_table);
+ $title = pht('Transaction Summary');
+ $content = array(
+ $this->buildRemarkup($summary_info),
+ $summary_table,
+ );
+
+ $pages[] = $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor('types');
foreach ($types as $type) {
$section = array();
@@ -130,15 +129,18 @@
'wide',
));
- $boxes[] = id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Transaction Type: %s', $type->getEditType()))
- ->setCollapsed(true)
- ->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
- ->appendChild($this->buildRemarkup($section))
- ->appendChild($type_table);
+ $title = $type->getEditType();
+ $content = array(
+ $this->buildRemarkup($section),
+ $type_table,
+ );
+
+ $pages[] = $this->newDocumentationBoxPage($viewer, $title, $content)
+ ->setAnchor($type->getEditType())
+ ->setIconIcon('fa-pencil');
}
- return $boxes;
+ return $pages;
}
diff --git a/src/applications/transactions/engineextension/PhabricatorSubtypeEditEngineExtension.php b/src/applications/transactions/engineextension/PhabricatorSubtypeEditEngineExtension.php
--- a/src/applications/transactions/engineextension/PhabricatorSubtypeEditEngineExtension.php
+++ b/src/applications/transactions/engineextension/PhabricatorSubtypeEditEngineExtension.php
@@ -21,7 +21,7 @@
public function supportsObject(
PhabricatorEditEngine $engine,
PhabricatorApplicationTransactionInterface $object) {
- return $engine->supportsSubtypes();
+ return ($object instanceof PhabricatorEditEngineSubtypeInterface);
}
public function buildCustomEditFields(
diff --git a/src/applications/uiexample/examples/PHUIBadgeExample.php b/src/applications/uiexample/examples/PHUIBadgeExample.php
--- a/src/applications/uiexample/examples/PHUIBadgeExample.php
+++ b/src/applications/uiexample/examples/PHUIBadgeExample.php
@@ -19,7 +19,7 @@
$badges1 = array();
$badges1[] = id(new PHUIBadgeView())
->setIcon('fa-users')
- ->setHeader(pht('Phacility High Command'))
+ ->setHeader(pht('High Command'))
->setHref('/')
->setSource('Projects (automatic)')
->addByline(pht('Dec 31, 1969'))
@@ -113,7 +113,7 @@
->setHeader(pht('Lead Developer'))
->setSubhead(pht('Lead Developer of Phabricator'))
->setQuality(PhabricatorBadgesQuality::HEIRLOOM)
- ->setSource(pht('Direct Award (epriestley)'))
+ ->setSource(pht('Direct Award'))
->addByline(pht('Dec 31, 1969'))
->addByline('1 Awarded (0.4%)');
diff --git a/src/infrastructure/cluster/PhabricatorDatabaseRef.php b/src/infrastructure/cluster/PhabricatorDatabaseRef.php
--- a/src/infrastructure/cluster/PhabricatorDatabaseRef.php
+++ b/src/infrastructure/cluster/PhabricatorDatabaseRef.php
@@ -322,6 +322,7 @@
$default_user = PhabricatorEnv::getEnvConfig('mysql.user');
$default_pass = PhabricatorEnv::getEnvConfig('mysql.pass');
+ $default_pass = phutil_string_cast($default_pass);
$default_pass = new PhutilOpaqueEnvelope($default_pass);
$config = PhabricatorEnv::getEnvConfig('cluster.databases');
diff --git a/src/infrastructure/edges/conduit/EdgeSearchConduitAPIMethod.php b/src/infrastructure/edges/conduit/EdgeSearchConduitAPIMethod.php
--- a/src/infrastructure/edges/conduit/EdgeSearchConduitAPIMethod.php
+++ b/src/infrastructure/edges/conduit/EdgeSearchConduitAPIMethod.php
@@ -11,9 +11,7 @@
return pht('Read edge relationships between objects.');
}
- public function getMethodDocumentation() {
- $viewer = $this->getViewer();
-
+ protected function newDocumentationPages(PhabricatorUser $viewer) {
$rows = array();
foreach ($this->getConduitEdgeTypeMap() as $key => $type) {
$inverse_constant = $type->getInverseEdgeConstant();
@@ -48,17 +46,11 @@
'wide',
));
- return id(new PHUIObjectBoxView())
- ->setHeaderText(pht('Edge Types'))
- ->setTable($types_table);
- }
-
- public function getMethodStatus() {
- return self::METHOD_STATUS_UNSTABLE;
- }
- public function getMethodStatusDescription() {
- return pht('This method is new and experimental.');
+ return array(
+ $this->newDocumentationBoxPage($viewer, pht('Edge Types'), $types_table)
+ ->setAnchor('types'),
+ );
}
protected function defineParamTypes() {
diff --git a/src/infrastructure/markup/PhabricatorMarkupEngine.php b/src/infrastructure/markup/PhabricatorMarkupEngine.php
--- a/src/infrastructure/markup/PhabricatorMarkupEngine.php
+++ b/src/infrastructure/markup/PhabricatorMarkupEngine.php
@@ -42,7 +42,7 @@
private $objects = array();
private $viewer;
private $contextObject;
- private $version = 20;
+ private $version = 21;
private $engineCaches = array();
private $auxiliaryConfig = array();
@@ -504,6 +504,7 @@
$rules = array();
$rules[] = new PhutilRemarkupEscapeRemarkupRule();
+ $rules[] = new PhutilRemarkupEvalRule();
$rules[] = new PhutilRemarkupMonospaceRule();
diff --git a/src/infrastructure/markup/markuprule/PhutilRemarkupEvalRule.php b/src/infrastructure/markup/markuprule/PhutilRemarkupEvalRule.php
new file mode 100644
--- /dev/null
+++ b/src/infrastructure/markup/markuprule/PhutilRemarkupEvalRule.php
@@ -0,0 +1,100 @@
+<?php
+
+final class PhutilRemarkupEvalRule extends PhutilRemarkupRule {
+
+ const KEY_EVAL = 'eval';
+
+ public function getPriority() {
+ return 50;
+ }
+
+ public function apply($text) {
+ return preg_replace_callback(
+ '/\${{{(.+?)}}}/',
+ array($this, 'newExpressionToken'),
+ $text);
+ }
+
+ public function newExpressionToken(array $matches) {
+ $expression = $matches[1];
+
+ if (!$this->isFlatText($expression)) {
+ return $matches[0];
+ }
+
+ $engine = $this->getEngine();
+ $token = $engine->storeText($expression);
+
+ $list_key = self::KEY_EVAL;
+ $expression_list = $engine->getTextMetadata($list_key, array());
+
+ $expression_list[] = array(
+ 'token' => $token,
+ 'expression' => $expression,
+ 'original' => $matches[0],
+ );
+
+ $engine->setTextMetadata($list_key, $expression_list);
+
+ return $token;
+ }
+
+ public function didMarkupText() {
+ $engine = $this->getEngine();
+
+ $list_key = self::KEY_EVAL;
+ $expression_list = $engine->getTextMetadata($list_key, array());
+
+ foreach ($expression_list as $expression_item) {
+ $token = $expression_item['token'];
+ $expression = $expression_item['expression'];
+
+ $result = $this->evaluateExpression($expression);
+
+ if ($result === null) {
+ $result = $expression_item['original'];
+ }
+
+ $engine->overwriteStoredText($token, $result);
+ }
+ }
+
+ private function evaluateExpression($expression) {
+ static $string_map;
+
+ if ($string_map === null) {
+ $string_map = array(
+ 'strings' => array(
+ 'platform' => array(
+ 'server' => array(
+ 'name' => pht('Phabricator'),
+ 'path' => pht('phabricator/'),
+ ),
+ 'client' => array(
+ 'name' => pht('Arcanist'),
+ 'path' => pht('arcanist/'),
+ ),
+ ),
+ ),
+ );
+ }
+
+ $parts = explode('.', $expression);
+
+ $cursor = $string_map;
+ foreach ($parts as $part) {
+ if (isset($cursor[$part])) {
+ $cursor = $cursor[$part];
+ } else {
+ break;
+ }
+ }
+
+ if (is_string($cursor)) {
+ return $cursor;
+ }
+
+ return null;
+ }
+
+}
diff --git a/src/infrastructure/query/PhabricatorQuery.php b/src/infrastructure/query/PhabricatorQuery.php
--- a/src/infrastructure/query/PhabricatorQuery.php
+++ b/src/infrastructure/query/PhabricatorQuery.php
@@ -87,7 +87,7 @@
foreach ($this->flattenSubclause($part) as $subpart) {
$result[] = $subpart;
}
- } else if (strlen($part)) {
+ } else if (($part !== null) && strlen($part)) {
$result[] = $part;
}
}
diff --git a/src/infrastructure/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php b/src/infrastructure/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php
--- a/src/infrastructure/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php
+++ b/src/infrastructure/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php
@@ -57,6 +57,13 @@
}
}
+ // See T13588. In PHP 8.1, the default "report mode" for MySQLi has
+ // changed, which causes MySQLi to raise exceptions. Disable exceptions
+ // to align behavior with older default behavior under MySQLi, which
+ // this code expects. Plausibly, this code could be updated to use
+ // MySQLi exceptions to handle errors under a wider range of PHP versions.
+ mysqli_report(MYSQLI_REPORT_OFF);
+
$conn = mysqli_init();
$timeout = $this->getConfiguration('timeout');
diff --git a/src/infrastructure/storage/lisk/LiskDAO.php b/src/infrastructure/storage/lisk/LiskDAO.php
--- a/src/infrastructure/storage/lisk/LiskDAO.php
+++ b/src/infrastructure/storage/lisk/LiskDAO.php
@@ -193,6 +193,8 @@
private static $connections = array();
+ private static $liskMetadata = array();
+
protected $id;
protected $phid;
protected $dateCreated;
@@ -403,10 +405,11 @@
* @task config
*/
public function getConfigOption($option_name) {
- static $options = null;
+ $options = $this->getLiskMetadata('config');
- if (!isset($options)) {
+ if ($options === null) {
$options = $this->getConfiguration();
+ $this->setLiskMetadata('config', $options);
}
return idx($options, $option_name);
@@ -439,7 +442,7 @@
return $this->loadOneWhere(
'%C = %d',
- $this->getIDKeyForUse(),
+ $this->getIDKey(),
$id);
}
@@ -554,7 +557,7 @@
$result = $this->loadOneWhere(
'%C = %d',
- $this->getIDKeyForUse(),
+ $this->getIDKey(),
$this->getID());
if (!$result) {
@@ -579,9 +582,10 @@
* @task load
*/
public function loadFromArray(array $row) {
- static $valid_properties = array();
+ $valid_map = $this->getLiskMetadata('validMap', array());
$map = array();
+ $updated = false;
foreach ($row as $k => $v) {
// We permit (but ignore) extra properties in the array because a
// common approach to building the array is to issue a raw SELECT query
@@ -594,14 +598,15 @@
// path (assigning an invalid property which we've already seen) costs
// an empty() plus an isset().
- if (empty($valid_properties[$k])) {
- if (isset($valid_properties[$k])) {
+ if (empty($valid_map[$k])) {
+ if (isset($valid_map[$k])) {
// The value is set but empty, which means it's false, so we've
// already determined it's not valid. We don't need to check again.
continue;
}
- $valid_properties[$k] = $this->hasProperty($k);
- if (!$valid_properties[$k]) {
+ $valid_map[$k] = $this->hasProperty($k);
+ $updated = true;
+ if (!$valid_map[$k]) {
continue;
}
}
@@ -609,6 +614,10 @@
$map[$k] = $v;
}
+ if ($updated) {
+ $this->setLiskMetadata('validMap', $valid_map);
+ }
+
$this->willReadData($map);
foreach ($map as $prop => $value) {
@@ -686,10 +695,7 @@
* @task save
*/
public function setID($id) {
- static $id_key = null;
- if ($id_key === null) {
- $id_key = $this->getIDKeyForUse();
- }
+ $id_key = $this->getIDKey();
$this->$id_key = $id;
return $this;
}
@@ -704,10 +710,7 @@
* @task info
*/
public function getID() {
- static $id_key = null;
- if ($id_key === null) {
- $id_key = $this->getIDKeyForUse();
- }
+ $id_key = $this->getIDKey();
return $this->$id_key;
}
@@ -742,9 +745,10 @@
* @task info
*/
protected function getAllLiskProperties() {
- static $properties = null;
- if (!isset($properties)) {
- $class = new ReflectionClass(get_class($this));
+ $properties = $this->getLiskMetadata('properties');
+
+ if ($properties === null) {
+ $class = new ReflectionClass(static::class);
$properties = array();
foreach ($class->getProperties(ReflectionProperty::IS_PROTECTED) as $p) {
$properties[strtolower($p->getName())] = $p->getName();
@@ -763,7 +767,10 @@
if ($id_key != 'phid' && !$this->getConfigOption(self::CONFIG_AUX_PHID)) {
unset($properties['phid']);
}
+
+ $this->setLiskMetadata('properties', $properties);
}
+
return $properties;
}
@@ -777,10 +784,7 @@
* @task info
*/
protected function checkProperty($property) {
- static $properties = null;
- if ($properties === null) {
- $properties = $this->getAllLiskProperties();
- }
+ $properties = $this->getAllLiskProperties();
$property = strtolower($property);
if (empty($properties[$property])) {
@@ -996,7 +1000,7 @@
'UPDATE %R SET %LQ WHERE %C = '.(is_int($id) ? '%d' : '%s'),
$this,
$map,
- $this->getIDKeyForUse(),
+ $this->getIDKey(),
$id);
// We can't detect a missing object because updating an object without
// changing any values doesn't affect rows. We could jiggle timestamps
@@ -1023,7 +1027,7 @@
$conn->query(
'DELETE FROM %R WHERE %C = %d',
$this,
- $this->getIDKeyForUse(),
+ $this->getIDKey(),
$this->getID());
$this->didDelete();
@@ -1051,7 +1055,7 @@
// If we are using autoincrement IDs, let MySQL assign the value for the
// ID column, if it is empty. If the caller has explicitly provided a
// value, use it.
- $id_key = $this->getIDKeyForUse();
+ $id_key = $this->getIDKey();
if (empty($data[$id_key])) {
unset($data[$id_key]);
}
@@ -1059,7 +1063,7 @@
case self::IDS_COUNTER:
// If we are using counter IDs, assign a new ID if we don't already have
// one.
- $id_key = $this->getIDKeyForUse();
+ $id_key = $this->getIDKey();
if (empty($data[$id_key])) {
$counter_name = $this->getTableName();
$id = self::loadNextCounterValue($conn, $counter_name);
@@ -1175,19 +1179,6 @@
return 'id';
}
-
- protected function getIDKeyForUse() {
- $id_key = $this->getIDKey();
- if (!$id_key) {
- throw new Exception(
- pht(
- 'This DAO does not have a single-part primary key. The method you '.
- 'called requires a single-part primary key.'));
- }
- return $id_key;
- }
-
-
/**
* Generate a new PHID, used by CONFIG_AUX_PHID.
*
@@ -1592,22 +1583,12 @@
* @task util
*/
public function __call($method, $args) {
- // NOTE: PHP has a bug that static variables defined in __call() are shared
- // across all children classes. Call a different method to work around this
- // bug.
- return $this->call($method, $args);
- }
+ $dispatch_map = $this->getLiskMetadata('dispatchMap', array());
- /**
- * @task util
- */
- final protected function call($method, $args) {
// NOTE: This method is very performance-sensitive (many thousands of calls
// per page on some pages), and thus has some silliness in the name of
// optimizations.
- static $dispatch_map = array();
-
if ($method[0] === 'g') {
if (isset($dispatch_map[$method])) {
$property = $dispatch_map[$method];
@@ -1620,6 +1601,7 @@
throw new Exception(pht('Bad getter call: %s', $method));
}
$dispatch_map[$method] = $property;
+ $this->setLiskMetadata('dispatchMap', $dispatch_map);
}
return $this->readField($property);
@@ -1632,12 +1614,14 @@
if (substr($method, 0, 3) !== 'set') {
throw new Exception(pht("Unable to resolve method '%s'!", $method));
}
+
$property = substr($method, 3);
$property = $this->checkProperty($property);
if (!$property) {
throw new Exception(pht('Bad setter call: %s', $method));
}
$dispatch_map[$method] = $property;
+ $this->setLiskMetadata('dispatchMap', $dispatch_map);
}
$this->writeField($property, $args[0]);
@@ -1909,4 +1893,20 @@
}
+ private function getLiskMetadata($key, $default = null) {
+ if (isset(self::$liskMetadata[static::class][$key])) {
+ return self::$liskMetadata[static::class][$key];
+ }
+
+ if (!isset(self::$liskMetadata[static::class])) {
+ self::$liskMetadata[static::class] = array();
+ }
+
+ return idx(self::$liskMetadata[static::class], $key, $default);
+ }
+
+ private function setLiskMetadata($key, $value) {
+ self::$liskMetadata[static::class][$key] = $value;
+ }
+
}
diff --git a/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php b/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php
--- a/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php
+++ b/src/infrastructure/storage/management/PhabricatorStorageManagementAPI.php
@@ -89,6 +89,17 @@
return $this->namespace.'_'.$fragment;
}
+ public function getInternalDatabaseName($name) {
+ $namespace = $this->getNamespace();
+
+ $prefix = $namespace.'_';
+ if (strncmp($name, $prefix, strlen($prefix))) {
+ return null;
+ }
+
+ return substr($name, strlen($prefix));
+ }
+
public function getDisplayName() {
return $this->getRef()->getDisplayName();
}
diff --git a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php
--- a/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php
+++ b/src/infrastructure/storage/management/workflow/PhabricatorStorageManagementDumpWorkflow.php
@@ -44,6 +44,16 @@
'With __--output__, overwrite the output file if it already '.
'exists.'),
),
+ array(
+ 'name' => 'database',
+ 'param' => 'database-name',
+ 'help' => pht(
+ 'Dump only tables in the named database (or databases, if '.
+ 'the flag is repeated). Specify database names without the '.
+ 'namespace prefix (that is: use "differential", not '.
+ '"phabricator_differential").'),
+ 'repeat' => true,
+ ),
));
}
@@ -58,6 +68,8 @@
$is_noindex = $args->getArg('no-indexes');
$is_replica = $args->getArg('for-replica');
+ $database_filter = $args->getArg('database');
+
if ($is_compress) {
if ($output_file === null) {
throw new PhutilArgumentUsageException(
@@ -128,11 +140,60 @@
$schemata = $actual_map[$ref_key];
$expect = $expect_map[$ref_key];
+ if ($database_filter) {
+ $internal_names = array();
+
+ $expect_databases = $expect->getDatabases();
+ foreach ($expect_databases as $expect_database) {
+ $database_name = $expect_database->getName();
+
+ $internal_name = $api->getInternalDatabaseName($database_name);
+ if ($internal_name !== null) {
+ $internal_names[$internal_name] = $database_name;
+ }
+ }
+
+ ksort($internal_names);
+
+ $seen = array();
+ foreach ($database_filter as $filter) {
+ if (!isset($internal_names[$filter])) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Database "%s" is unknown. This script can only dump '.
+ 'databases known to the current version of Phabricator. '.
+ 'Valid databases are: %s.',
+ $filter,
+ implode(', ', array_keys($internal_names))));
+ }
+
+ if (isset($seen[$filter])) {
+ throw new PhutilArgumentUsageException(
+ pht(
+ 'Database "%s" is specified more than once. Specify each '.
+ 'database at most once.',
+ $filter));
+ }
+
+ $seen[$filter] = true;
+ }
+
+ $dump_databases = array_select_keys($internal_names, $database_filter);
+ $dump_databases = array_fuse($dump_databases);
+ } else {
+ $dump_databases = array_keys($schemata->getDatabases());
+ $dump_databases = array_fuse($dump_databases);
+ }
+
$with_caches = $is_replica;
$with_indexes = !$is_noindex;
$targets = array();
foreach ($schemata->getDatabases() as $database_name => $database) {
+ if (!isset($dump_databases[$database_name])) {
+ continue;
+ }
+
$expect_database = $expect->getDatabase($database_name);
foreach ($database->getTables() as $table_name => $table) {
diff --git a/src/infrastructure/util/PhabricatorHash.php b/src/infrastructure/util/PhabricatorHash.php
--- a/src/infrastructure/util/PhabricatorHash.php
+++ b/src/infrastructure/util/PhabricatorHash.php
@@ -224,7 +224,7 @@
$cache_key = "hmac.key({$hmac_name})";
$hmac_key = $cache->getKey($cache_key);
- if (!strlen($hmac_key)) {
+ if (($hmac_key === null) || !strlen($hmac_key)) {
$hmac_key = self::readHMACKey($hmac_name);
if ($hmac_key === null) {
diff --git a/src/infrastructure/util/PhabricatorMetronome.php b/src/infrastructure/util/PhabricatorMetronome.php
--- a/src/infrastructure/util/PhabricatorMetronome.php
+++ b/src/infrastructure/util/PhabricatorMetronome.php
@@ -49,7 +49,7 @@
}
public function setOffsetFromSeed($seed) {
- $offset = PhabricatorHash::digestToRange($seed, 0, PHP_INT_MAX);
+ $offset = PhabricatorHash::digestToRange($seed, 0, 0x7FFFFFFF);
return $this->setOffset($offset);
}
diff --git a/support/startup/PhabricatorStartup.php b/support/startup/PhabricatorStartup.php
--- a/support/startup/PhabricatorStartup.php
+++ b/support/startup/PhabricatorStartup.php
@@ -392,8 +392,11 @@
ini_set('memory_limit', -1);
// If we have libxml, disable the incredibly dangerous entity loader.
+ // PHP 8 deprecates this function and disables this by default; remove once
+ // PHP 7 is no longer supported or a future version has removed the function
+ // entirely.
if (function_exists('libxml_disable_entity_loader')) {
- libxml_disable_entity_loader(true);
+ @libxml_disable_entity_loader(true);
}
// See T13060. If the locale for this process (the parent process) is not
diff --git a/webroot/rsrc/css/phui/phui-list.css b/webroot/rsrc/css/phui/phui-list.css
--- a/webroot/rsrc/css/phui/phui-list.css
+++ b/webroot/rsrc/css/phui/phui-list.css
@@ -75,11 +75,10 @@
padding: 4px 10px;
}
-.phui-list-sidenav .phui-list-item-has-icon .phui-list-item-indented {
- padding-left: 18px;
+.phabricator-side-menu .phui-list-item-has-icon .phui-list-item-indented {
+ padding-left: 24px;
}
-
.device-desktop .phui-list-sidenav .phui-list-item-href:hover {
background: {$sky};
color: white;
diff --git a/webroot/rsrc/externals/javelin/lib/DOM.js b/webroot/rsrc/externals/javelin/lib/DOM.js
--- a/webroot/rsrc/externals/javelin/lib/DOM.js
+++ b/webroot/rsrc/externals/javelin/lib/DOM.js
@@ -124,7 +124,7 @@
'will not do the right thing with this.');
}
- // TODO(epriestley): May need to deny <option> more broadly, see
+ // TODO: May need to deny <option> more broadly, see
// http://support.microsoft.com/kb/829907 and the whole mess in the
// heavy stack. But I seem to have gotten away without cloning into the
// documentFragment below, so this may be a nonissue.
@@ -147,7 +147,7 @@
wrapper.innerHTML = this._content;
var fragment = document.createDocumentFragment();
while (wrapper.firstChild) {
- // TODO(epriestley): Do we need to do a bunch of cloning junk here?
+ // TODO: Do we need to do a bunch of cloning junk here?
// See heavy stack. I'm disconnecting the nodes instead; this seems
// to work but maybe my test case just isn't extensive enough.
fragment.appendChild(wrapper.removeChild(wrapper.firstChild));
diff --git a/webroot/rsrc/js/core/behavior-fancy-datepicker.js b/webroot/rsrc/js/core/behavior-fancy-datepicker.js
--- a/webroot/rsrc/js/core/behavior-fancy-datepicker.js
+++ b/webroot/rsrc/js/core/behavior-fancy-datepicker.js
@@ -425,6 +425,13 @@
value_m += 12;
value_y--;
}
+ // This relies on months greater than 11 rolling over into the next
+ // year and days less than 1 rolling back into the previous month.
+ var last_date = new Date(value_y, value_m, 0);
+ if (value_d > last_date.getDate()) {
+ // The date falls outside the new month, so stuff it back in.
+ value_d = last_date.getDate();
+ }
break;
case 'd':
// User clicked a day.

File Metadata

Mime Type
text/plain
Expires
Wed, Feb 19, 21:57 (1 d, 20 h ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1175669
Default Alt Text
D25036.1740002230.diff (254 KB)

Event Timeline