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 @@ +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 @@ 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 @@ + 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 @@ +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 @@ + '', + + "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\d+)/' => 'HarbormasterLintMessagesController', ), - 'hook/(?P[^/]+)/' => 'HarbormasterHookController', + 'hook/' => array( + 'circleci/' => 'HarbormasterCircleCIHookController', + 'buildkite/' => 'HarbormasterBuildkiteHookController', + ), 'log/' => array( 'view/(?P\d+)/(?:\$(?P\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 @@ +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(<< + + Object Type + PHID Example + Description + + + Harbormaster Buildable + `PHID-HMBB-...` + %s + + + Harbormaster Build + `PHID-HMBD-...` + %s + + + Harbormaster Build Target + `PHID-HMBT-...` + %s + + + +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(<<newRemarkupDocumentationView($content); + + return $this->newDocumentationBoxPage($viewer, $title, $content) + ->setAnchor('builds') + ->setIconIcon('fa-cubes'); + } + + private function newCommandsDocumentationBoxPage(PhabricatorUser $viewer) { + $messages = HarbormasterBuildMessageTransaction::getAllMessages(); + + $rows = array(); + + $rows[] = ''; + $rows[] = ''.pht('Message Type').''; + $rows[] = ''.pht('Description').''; + $rows[] = ''; + + foreach ($messages as $message) { + $row = array(); + + $row[] = sprintf( + '`%s`', + $message->getHarbormasterBuildMessageType()); + + $row[] = sprintf( + '%s', + $message->getHarbormasterBuildMessageDescription()); + + $rows[] = sprintf( + '%s', + implode("\n", $row)); + } + + $message_table = sprintf( + '%s
', + implode("\n", $rows)); + + $title = pht('Control Commands'); + + $content = pht(<< + + Object Type + PHID Example + + Description + + + Harbormaster Buildable + `PHID-HMBB-...` + {icon check color=green} + Buildables may receive control commands. + + + Harbormaster Build + `PHID-HMBD-...` + {icon check color=green} + Builds may receive control commands. + + + Harbormaster Build Target + `PHID-HMBT-...` + {icon times color=red} + You may **NOT** send control commands to build targets. + + + +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(<< + + Object Type + PHID Example + + Description + + + Harbormaster Buildable + `PHID-HMBB-...` + {icon times color=red} + Buildables may **NOT** receive status or result messages. + + + Harbormaster Build + `PHID-HMBD-...` + {icon times color=red} + Builds may **NOT** receive status or result messages. + + + Harbormaster Build Target + `PHID-HMBT-...` + {icon check color=green} + Report build status and results to Build Targets. + + + +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(<<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(<<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', 'lint' => 'optional list', + '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 @@ 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 @@ +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 @@ +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 @@ 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 @@ -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( - " %s %s\n", + $this->logInfo( pht('RESTARTING'), pht('Build %d: %s', $build->getID(), $build->getName())); - if (!$build->canRestartBuild()) { - $console->writeOut( - " %s %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( - " %s \n%s\n", - pht('FAILED'), - $message); - continue; + $message->assertCanSendMessage($viewer, $build); + } catch (HarbormasterMessageException $ex) { + $this->logWarn( + pht('INVALID'), + $ex->newDisplayString()); } - $console->writeOut(" %s \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 @@ - 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 @@ 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 @@ 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 @@ +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 @@ +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 @@ +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 @@ +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 @@ +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 @@ +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 @@ +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') ->setDescription(pht('Dominion setting information.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('authority') + ->setType('map') + ->setDescription(pht('Authority setting information.')), id(new PhabricatorConduitSearchFieldSpecification()) ->setKey('ignored') ->setType('map') @@ -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 @@ +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[1-9]\d*)/' => 'PhrictionMoveController', - 'preview/(?P.*/)' => 'PhrictionMarkupPreviewController', + 'preview/' => 'PhrictionMarkupPreviewController', 'diff/(?P[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(<<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(<<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(<<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(<<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(<<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 @@ +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