Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2983880
D25036.1740002230.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
254 KB
Referenced Files
None
Subscribers
None
D25036.1740002230.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D25036: Catch up the master branch to upstream
Attached
Detach File
Event Timeline
Log In to Comment