diff --git a/resources/celerity/map.php b/resources/celerity/map.php index f3ead4de53..32ccb9e5f9 100644 --- a/resources/celerity/map.php +++ b/resources/celerity/map.php @@ -10,10 +10,10 @@ return array( 'conpherence.pkg.css' => 'e68cf1fa', 'conpherence.pkg.js' => '15191c65', 'core.pkg.css' => 'e4f098a5', - 'core.pkg.js' => '3ac6e174', + 'core.pkg.js' => 'bd19de1c', 'darkconsole.pkg.js' => '1f9a31bc', 'differential.pkg.css' => '113e692c', - 'differential.pkg.js' => '5d53d5ce', + 'differential.pkg.js' => 'f6d809c0', 'diffusion.pkg.css' => 'a2d17c7d', 'diffusion.pkg.js' => '6134c5a1', 'favicon.ico' => '30672e08', @@ -396,7 +396,7 @@ return array( 'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375', 'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63', 'rsrc/js/application/diff/DiffChangeset.js' => 'b49b59d6', - 'rsrc/js/application/diff/DiffChangesetList.js' => '1f2e5265', + 'rsrc/js/application/diff/DiffChangesetList.js' => 'e74b7517', 'rsrc/js/application/diff/DiffInline.js' => 'e83d28f3', 'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832', 'rsrc/js/application/differential/behavior-comment-preview.js' => '51c5ad07', @@ -506,7 +506,7 @@ return array( 'rsrc/js/core/behavior-reorder-applications.js' => '76b9fc3e', 'rsrc/js/core/behavior-reveal-content.js' => '60821bc7', 'rsrc/js/core/behavior-scrollbar.js' => '834a1173', - 'rsrc/js/core/behavior-search-typeahead.js' => 'd0a99ab4', + 'rsrc/js/core/behavior-search-typeahead.js' => 'c3e917d9', 'rsrc/js/core/behavior-select-content.js' => 'bf5374ef', 'rsrc/js/core/behavior-select-on-click.js' => '4e3e79a6', 'rsrc/js/core/behavior-setup-check-https.js' => '491416b3', @@ -528,7 +528,7 @@ return array( 'rsrc/js/phui/behavior-phui-tab-group.js' => '0a0b10e9', 'rsrc/js/phuix/PHUIXActionListView.js' => 'b5c256b8', 'rsrc/js/phuix/PHUIXActionView.js' => '442efd08', - 'rsrc/js/phuix/PHUIXAutocomplete.js' => 'e0731603', + 'rsrc/js/phuix/PHUIXAutocomplete.js' => '7fa5c915', 'rsrc/js/phuix/PHUIXButtonView.js' => '8a91e1ac', 'rsrc/js/phuix/PHUIXDropdownMenu.js' => '04b2ae03', 'rsrc/js/phuix/PHUIXExample.js' => '68af71ca', @@ -663,7 +663,7 @@ return array( 'javelin-behavior-phabricator-oncopy' => '2926fff2', 'javelin-behavior-phabricator-remarkup-assist' => 'acd29eee', 'javelin-behavior-phabricator-reveal-content' => '60821bc7', - 'javelin-behavior-phabricator-search-typeahead' => 'd0a99ab4', + 'javelin-behavior-phabricator-search-typeahead' => 'c3e917d9', 'javelin-behavior-phabricator-show-older-transactions' => '8f29b364', 'javelin-behavior-phabricator-tooltips' => 'c420b0b9', 'javelin-behavior-phabricator-transaction-comment-form' => 'b23b49e6', @@ -776,7 +776,7 @@ return array( 'phabricator-darkmessage' => 'c48cccdd', 'phabricator-dashboard-css' => 'fe5b1869', 'phabricator-diff-changeset' => 'b49b59d6', - 'phabricator-diff-changeset-list' => '1f2e5265', + 'phabricator-diff-changeset-list' => 'e74b7517', 'phabricator-diff-inline' => 'e83d28f3', 'phabricator-drag-and-drop-file-upload' => '58dea2fa', 'phabricator-draggable-list' => 'bea6e7f4', @@ -881,7 +881,7 @@ return array( 'phui-workpanel-view-css' => 'a3a63478', 'phuix-action-list-view' => 'b5c256b8', 'phuix-action-view' => '442efd08', - 'phuix-autocomplete' => 'e0731603', + 'phuix-autocomplete' => '7fa5c915', 'phuix-button-view' => '8a91e1ac', 'phuix-dropdown-menu' => '04b2ae03', 'phuix-form-control-view' => '16ad6224', @@ -1044,10 +1044,6 @@ return array( 'javelin-uri', 'javelin-routable', ), - '1f2e5265' => array( - 'javelin-install', - 'phuix-button-view', - ), '1f6794f6' => array( 'javelin-behavior', 'javelin-stratcom', @@ -1561,6 +1557,12 @@ return array( '7f243deb' => array( 'javelin-install', ), + '7fa5c915' => array( + 'javelin-install', + 'javelin-dom', + 'phuix-icon-view', + 'phabricator-prefab', + ), '81144dfa' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1922,6 +1924,17 @@ return array( 'javelin-dom', 'javelin-vector', ), + 'c3e917d9' => array( + 'javelin-behavior', + 'javelin-typeahead-ondemand-source', + 'javelin-typeahead', + 'javelin-dom', + 'javelin-uri', + 'javelin-util', + 'javelin-stratcom', + 'phabricator-prefab', + 'phuix-icon-view', + ), 'c420b0b9' => array( 'javelin-behavior', 'javelin-behavior-device', @@ -1973,17 +1986,6 @@ return array( 'phabricator-notification', 'conpherence-thread-manager', ), - 'd0a99ab4' => array( - 'javelin-behavior', - 'javelin-typeahead-ondemand-source', - 'javelin-typeahead', - 'javelin-dom', - 'javelin-uri', - 'javelin-util', - 'javelin-stratcom', - 'phabricator-prefab', - 'phuix-icon-view', - ), 'd0c516d5' => array( 'javelin-behavior', 'javelin-dom', @@ -2037,12 +2039,6 @@ return array( 'javelin-typeahead-ondemand-source', 'javelin-dom', ), - 'e0731603' => array( - 'javelin-install', - 'javelin-dom', - 'phuix-icon-view', - 'phabricator-prefab', - ), 'e1d25dfb' => array( 'javelin-behavior', 'javelin-stratcom', @@ -2093,6 +2089,10 @@ return array( 'javelin-workflow', 'javelin-magical-init', ), + 'e74b7517' => array( + 'javelin-install', + 'phuix-button-view', + ), 'e83d28f3' => array( 'javelin-dom', ), diff --git a/resources/sql/autopatches/20161213.diff.01.hunks.php b/resources/sql/autopatches/20161213.diff.01.hunks.php index 574adb3b4f..a3863275f1 100644 --- a/resources/sql/autopatches/20161213.diff.01.hunks.php +++ b/resources/sql/autopatches/20161213.diff.01.hunks.php @@ -25,9 +25,9 @@ foreach (new LiskRawMigrationIterator($conn, $src_table) as $row) { $row['oldLen'], $row['newOffset'], $row['newLen'], - DifferentialModernHunk::DATATYPE_TEXT, + DifferentialHunk::DATATYPE_TEXT, 'utf8', - DifferentialModernHunk::DATAFORMAT_RAW, + DifferentialHunk::DATAFORMAT_RAW, // In rare cases, this could be NULL. See T12090. (string)$row['changes'], $row['dateCreated'], diff --git a/resources/sql/autopatches/20180210.hunk.01.droplegacy.sql b/resources/sql/autopatches/20180210.hunk.01.droplegacy.sql new file mode 100644 index 0000000000..129d3927d8 --- /dev/null +++ b/resources/sql/autopatches/20180210.hunk.01.droplegacy.sql @@ -0,0 +1 @@ +DROP TABLE {$NAMESPACE}_differential.differential_hunk; diff --git a/resources/sql/autopatches/20180210.hunk.02.renamemodern.sql b/resources/sql/autopatches/20180210.hunk.02.renamemodern.sql new file mode 100644 index 0000000000..d341fbedf2 --- /dev/null +++ b/resources/sql/autopatches/20180210.hunk.02.renamemodern.sql @@ -0,0 +1,2 @@ +RENAME TABLE {$NAMESPACE}_differential.differential_hunk_modern + TO {$NAMESPACE}_differential.differential_hunk; diff --git a/resources/sql/autopatches/20180212.harbor.01.receiver.sql b/resources/sql/autopatches/20180212.harbor.01.receiver.sql new file mode 100644 index 0000000000..84e9611db2 --- /dev/null +++ b/resources/sql/autopatches/20180212.harbor.01.receiver.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildmessage + CHANGE buildTargetPHID receiverPHID VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20180214.harbor.01.aborted.php b/resources/sql/autopatches/20180214.harbor.01.aborted.php new file mode 100644 index 0000000000..689d5625f5 --- /dev/null +++ b/resources/sql/autopatches/20180214.harbor.01.aborted.php @@ -0,0 +1,27 @@ +establishConnection('w'); + +foreach (new LiskMigrationIterator($table) as $buildable) { + if ($buildable->getBuildableStatus() !== 'building') { + continue; + } + + $aborted = queryfx_one( + $conn, + 'SELECT * FROM %T WHERE buildablePHID = %s AND buildStatus = %s', + id(new HarbormasterBuild())->getTableName(), + $buildable->getPHID(), + 'aborted'); + if (!$aborted) { + continue; + } + + queryfx( + $conn, + 'UPDATE %T SET buildableStatus = %s WHERE id = %d', + $table->getTableName(), + 'failed', + $buildable->getID()); +} diff --git a/resources/sql/autopatches/20180215.phriction.01.phidcol.sql b/resources/sql/autopatches/20180215.phriction.01.phidcol.sql new file mode 100644 index 0000000000..658b05d9e1 --- /dev/null +++ b/resources/sql/autopatches/20180215.phriction.01.phidcol.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_content + ADD phid VARBINARY(64) NOT NULL; diff --git a/resources/sql/autopatches/20180215.phriction.02.phidvalues.php b/resources/sql/autopatches/20180215.phriction.02.phidvalues.php new file mode 100644 index 0000000000..c0a55cac85 --- /dev/null +++ b/resources/sql/autopatches/20180215.phriction.02.phidvalues.php @@ -0,0 +1,17 @@ +establishConnection('w'); + +foreach (new LiskMigrationIterator($table) as $row) { + if (strlen($row->getPHID())) { + continue; + } + + queryfx( + $conn, + 'UPDATE %T SET phid = %s WHERE id = %d', + $table->getTableName(), + $table->generatePHID(), + $row->getID()); +} diff --git a/resources/sql/autopatches/20180215.phriction.03.descempty.sql b/resources/sql/autopatches/20180215.phriction.03.descempty.sql new file mode 100644 index 0000000000..c41df5285a --- /dev/null +++ b/resources/sql/autopatches/20180215.phriction.03.descempty.sql @@ -0,0 +1,2 @@ +UPDATE {$NAMESPACE}_phriction.phriction_content + SET description = '' WHERE description IS NULL; diff --git a/resources/sql/autopatches/20180215.phriction.04.descnull.sql b/resources/sql/autopatches/20180215.phriction.04.descnull.sql new file mode 100644 index 0000000000..3ff017cd64 --- /dev/null +++ b/resources/sql/autopatches/20180215.phriction.04.descnull.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_content + CHANGE description description LONGTEXT NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20180215.phriction.05.statustext.sql b/resources/sql/autopatches/20180215.phriction.05.statustext.sql new file mode 100644 index 0000000000..756f7ac968 --- /dev/null +++ b/resources/sql/autopatches/20180215.phriction.05.statustext.sql @@ -0,0 +1,2 @@ +ALTER TABLE {$NAMESPACE}_phriction.phriction_document + CHANGE status status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT}; diff --git a/resources/sql/autopatches/20180215.phriction.06.statusvalue.sql b/resources/sql/autopatches/20180215.phriction.06.statusvalue.sql new file mode 100644 index 0000000000..381de77643 --- /dev/null +++ b/resources/sql/autopatches/20180215.phriction.06.statusvalue.sql @@ -0,0 +1,11 @@ +UPDATE {$NAMESPACE}_phriction.phriction_document + SET status = 'active' WHERE status = '0'; + +UPDATE {$NAMESPACE}_phriction.phriction_document + SET status = 'deleted' WHERE status = '1'; + +UPDATE {$NAMESPACE}_phriction.phriction_document + SET status = 'moved' WHERE status = '2'; + +UPDATE {$NAMESPACE}_phriction.phriction_document + SET status = 'stub' WHERE status = '3'; diff --git a/scripts/drydock/drydock_control.php b/scripts/drydock/drydock_control.php index 21ff9f8ee8..f22032b286 100755 --- a/scripts/drydock/drydock_control.php +++ b/scripts/drydock/drydock_control.php @@ -2,7 +2,7 @@ setTagline(pht('manage drydock software resources')); diff --git a/scripts/init/init-script-with-signals.php b/scripts/init/init-script-with-signals.php new file mode 100644 index 0000000000..a479c4b758 --- /dev/null +++ b/scripts/init/init-script-with-signals.php @@ -0,0 +1,11 @@ + 'applications/differential/query/DifferentialInlineCommentQuery.php', 'DifferentialJIRAIssuesCommitMessageField' => 'applications/differential/field/DifferentialJIRAIssuesCommitMessageField.php', 'DifferentialJIRAIssuesField' => 'applications/differential/customfield/DifferentialJIRAIssuesField.php', - 'DifferentialLegacyHunk' => 'applications/differential/storage/DifferentialLegacyHunk.php', 'DifferentialLegacyQuery' => 'applications/differential/constants/DifferentialLegacyQuery.php', 'DifferentialLineAdjustmentMap' => 'applications/differential/parser/DifferentialLineAdjustmentMap.php', 'DifferentialLintField' => 'applications/differential/customfield/DifferentialLintField.php', @@ -490,7 +489,6 @@ phutil_register_library_map(array( 'DifferentialMailEngineExtension' => 'applications/differential/engineextension/DifferentialMailEngineExtension.php', 'DifferentialMailView' => 'applications/differential/mail/DifferentialMailView.php', 'DifferentialManiphestTasksField' => 'applications/differential/customfield/DifferentialManiphestTasksField.php', - 'DifferentialModernHunk' => 'applications/differential/storage/DifferentialModernHunk.php', 'DifferentialParseCacheGarbageCollector' => 'applications/differential/garbagecollector/DifferentialParseCacheGarbageCollector.php', 'DifferentialParseCommitMessageConduitAPIMethod' => 'applications/differential/conduit/DifferentialParseCommitMessageConduitAPIMethod.php', 'DifferentialParseRenderTestCase' => 'applications/differential/__tests__/DifferentialParseRenderTestCase.php', @@ -718,6 +716,7 @@ phutil_register_library_map(array( 'DiffusionController' => 'applications/diffusion/controller/DiffusionController.php', 'DiffusionCreateRepositoriesCapability' => 'applications/diffusion/capability/DiffusionCreateRepositoriesCapability.php', 'DiffusionDaemonLockException' => 'applications/diffusion/exception/DiffusionDaemonLockException.php', + 'DiffusionDatasourceEngineExtension' => 'applications/diffusion/engineextension/DiffusionDatasourceEngineExtension.php', 'DiffusionDefaultEditCapability' => 'applications/diffusion/capability/DiffusionDefaultEditCapability.php', 'DiffusionDefaultPushCapability' => 'applications/diffusion/capability/DiffusionDefaultPushCapability.php', 'DiffusionDefaultViewCapability' => 'applications/diffusion/capability/DiffusionDefaultViewCapability.php', @@ -813,6 +812,8 @@ phutil_register_library_map(array( 'DiffusionPreCommitContentHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentHeraldField.php', 'DiffusionPreCommitContentMergeHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentMergeHeraldField.php', 'DiffusionPreCommitContentMessageHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentMessageHeraldField.php', + 'DiffusionPreCommitContentPackageHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPackageHeraldField.php', + 'DiffusionPreCommitContentPackageOwnerHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPackageOwnerHeraldField.php', 'DiffusionPreCommitContentPusherHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherHeraldField.php', 'DiffusionPreCommitContentPusherIsCommitterHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherIsCommitterHeraldField.php', 'DiffusionPreCommitContentPusherProjectsHeraldField' => 'applications/diffusion/herald/DiffusionPreCommitContentPusherProjectsHeraldField.php', @@ -846,7 +847,6 @@ phutil_register_library_map(array( 'DiffusionQueryCommitsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryCommitsConduitAPIMethod.php', 'DiffusionQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryConduitAPIMethod.php', 'DiffusionQueryPathsConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionQueryPathsConduitAPIMethod.php', - 'DiffusionQuickSearchEngineExtension' => 'applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php', 'DiffusionRawDiffQuery' => 'applications/diffusion/query/rawdiff/DiffusionRawDiffQuery.php', 'DiffusionRawDiffQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionRawDiffQueryConduitAPIMethod.php', 'DiffusionReadmeView' => 'applications/diffusion/view/DiffusionReadmeView.php', @@ -1002,6 +1002,7 @@ phutil_register_library_map(array( 'DoorkeeperSchemaSpec' => 'applications/doorkeeper/storage/DoorkeeperSchemaSpec.php', 'DoorkeeperTagView' => 'applications/doorkeeper/view/DoorkeeperTagView.php', 'DoorkeeperTagsController' => 'applications/doorkeeper/controller/DoorkeeperTagsController.php', + 'DrydockAcquiredBrokenResourceException' => 'applications/drydock/exception/DrydockAcquiredBrokenResourceException.php', 'DrydockAlmanacServiceHostBlueprintImplementation' => 'applications/drydock/blueprint/DrydockAlmanacServiceHostBlueprintImplementation.php', 'DrydockApacheWebrootInterface' => 'applications/drydock/interface/webroot/DrydockApacheWebrootInterface.php', 'DrydockAuthorization' => 'applications/drydock/storage/DrydockAuthorization.php', @@ -1043,7 +1044,6 @@ phutil_register_library_map(array( 'DrydockCommandInterface' => 'applications/drydock/interface/command/DrydockCommandInterface.php', 'DrydockCommandQuery' => 'applications/drydock/query/DrydockCommandQuery.php', 'DrydockConsoleController' => 'applications/drydock/controller/DrydockConsoleController.php', - 'DrydockConstants' => 'applications/drydock/constants/DrydockConstants.php', 'DrydockController' => 'applications/drydock/controller/DrydockController.php', 'DrydockCreateBlueprintsCapability' => 'applications/drydock/capability/DrydockCreateBlueprintsCapability.php', 'DrydockDAO' => 'applications/drydock/storage/DrydockDAO.php', @@ -1057,6 +1057,7 @@ phutil_register_library_map(array( 'DrydockLeaseActivatedLogType' => 'applications/drydock/logtype/DrydockLeaseActivatedLogType.php', 'DrydockLeaseActivationFailureLogType' => 'applications/drydock/logtype/DrydockLeaseActivationFailureLogType.php', 'DrydockLeaseActivationYieldLogType' => 'applications/drydock/logtype/DrydockLeaseActivationYieldLogType.php', + 'DrydockLeaseAllocationFailureLogType' => 'applications/drydock/logtype/DrydockLeaseAllocationFailureLogType.php', 'DrydockLeaseController' => 'applications/drydock/controller/DrydockLeaseController.php', 'DrydockLeaseDatasource' => 'applications/drydock/typeahead/DrydockLeaseDatasource.php', 'DrydockLeaseDestroyedLogType' => 'applications/drydock/logtype/DrydockLeaseDestroyedLogType.php', @@ -1067,6 +1068,7 @@ phutil_register_library_map(array( 'DrydockLeasePHIDType' => 'applications/drydock/phid/DrydockLeasePHIDType.php', 'DrydockLeaseQuery' => 'applications/drydock/query/DrydockLeaseQuery.php', 'DrydockLeaseQueuedLogType' => 'applications/drydock/logtype/DrydockLeaseQueuedLogType.php', + 'DrydockLeaseReacquireLogType' => 'applications/drydock/logtype/DrydockLeaseReacquireLogType.php', 'DrydockLeaseReclaimLogType' => 'applications/drydock/logtype/DrydockLeaseReclaimLogType.php', 'DrydockLeaseReleaseController' => 'applications/drydock/controller/DrydockLeaseReleaseController.php', 'DrydockLeaseReleasedLogType' => 'applications/drydock/logtype/DrydockLeaseReleasedLogType.php', @@ -1108,10 +1110,12 @@ phutil_register_library_map(array( 'DrydockResource' => 'applications/drydock/storage/DrydockResource.php', 'DrydockResourceActivationFailureLogType' => 'applications/drydock/logtype/DrydockResourceActivationFailureLogType.php', 'DrydockResourceActivationYieldLogType' => 'applications/drydock/logtype/DrydockResourceActivationYieldLogType.php', + 'DrydockResourceAllocationFailureLogType' => 'applications/drydock/logtype/DrydockResourceAllocationFailureLogType.php', 'DrydockResourceController' => 'applications/drydock/controller/DrydockResourceController.php', 'DrydockResourceDatasource' => 'applications/drydock/typeahead/DrydockResourceDatasource.php', 'DrydockResourceListController' => 'applications/drydock/controller/DrydockResourceListController.php', 'DrydockResourceListView' => 'applications/drydock/view/DrydockResourceListView.php', + 'DrydockResourceLockException' => 'applications/drydock/exception/DrydockResourceLockException.php', 'DrydockResourcePHIDType' => 'applications/drydock/phid/DrydockResourcePHIDType.php', 'DrydockResourceQuery' => 'applications/drydock/query/DrydockResourceQuery.php', 'DrydockResourceReclaimLogType' => 'applications/drydock/logtype/DrydockResourceReclaimLogType.php', @@ -1274,6 +1278,7 @@ phutil_register_library_map(array( 'HarbormasterBuildablePHIDType' => 'applications/harbormaster/phid/HarbormasterBuildablePHIDType.php', 'HarbormasterBuildableQuery' => 'applications/harbormaster/query/HarbormasterBuildableQuery.php', 'HarbormasterBuildableSearchEngine' => 'applications/harbormaster/query/HarbormasterBuildableSearchEngine.php', + 'HarbormasterBuildableStatus' => 'applications/harbormaster/constants/HarbormasterBuildableStatus.php', 'HarbormasterBuildableTransaction' => 'applications/harbormaster/storage/HarbormasterBuildableTransaction.php', 'HarbormasterBuildableTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildableTransactionEditor.php', 'HarbormasterBuildableTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildableTransactionQuery.php', @@ -2742,8 +2747,11 @@ phutil_register_library_map(array( 'PhabricatorDatabaseRef' => 'infrastructure/cluster/PhabricatorDatabaseRef.php', 'PhabricatorDatabaseRefParser' => 'infrastructure/cluster/PhabricatorDatabaseRefParser.php', 'PhabricatorDatabaseSetupCheck' => 'applications/config/check/PhabricatorDatabaseSetupCheck.php', + 'PhabricatorDatasourceApplicationEngineExtension' => 'applications/meta/engineextension/PhabricatorDatasourceApplicationEngineExtension.php', 'PhabricatorDatasourceEditField' => 'applications/transactions/editfield/PhabricatorDatasourceEditField.php', 'PhabricatorDatasourceEditType' => 'applications/transactions/edittype/PhabricatorDatasourceEditType.php', + 'PhabricatorDatasourceEngine' => 'applications/search/engine/PhabricatorDatasourceEngine.php', + 'PhabricatorDatasourceEngineExtension' => 'applications/search/engineextension/PhabricatorDatasourceEngineExtension.php', 'PhabricatorDateFormatSetting' => 'applications/settings/setting/PhabricatorDateFormatSetting.php', 'PhabricatorDateTimeSettingsPanel' => 'applications/settings/panel/PhabricatorDateTimeSettingsPanel.php', 'PhabricatorDebugController' => 'applications/system/controller/PhabricatorDebugController.php', @@ -3148,7 +3156,6 @@ phutil_register_library_map(array( 'PhabricatorJSONExportFormat' => 'infrastructure/export/format/PhabricatorJSONExportFormat.php', 'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/PhabricatorJavelinLinter.php', 'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php', - 'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php', 'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php', 'PhabricatorKeyValueSerializingCacheProxy' => 'applications/cache/PhabricatorKeyValueSerializingCacheProxy.php', 'PhabricatorKeyboardRemarkupRule' => 'infrastructure/markup/rule/PhabricatorKeyboardRemarkupRule.php', @@ -3313,7 +3320,7 @@ phutil_register_library_map(array( 'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php', 'PhabricatorModularTransaction' => 'applications/transactions/storage/PhabricatorModularTransaction.php', 'PhabricatorModularTransactionType' => 'applications/transactions/storage/PhabricatorModularTransactionType.php', - 'PhabricatorMonogramQuickSearchEngineExtension' => 'applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php', + 'PhabricatorMonogramDatasourceEngineExtension' => 'applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php', 'PhabricatorMonospacedFontSetting' => 'applications/settings/setting/PhabricatorMonospacedFontSetting.php', 'PhabricatorMonospacedTextareasSetting' => 'applications/settings/setting/PhabricatorMonospacedTextareasSetting.php', 'PhabricatorMotivatorProfileMenuItem' => 'applications/search/menuitem/PhabricatorMotivatorProfileMenuItem.php', @@ -3415,6 +3422,7 @@ phutil_register_library_map(array( 'PhabricatorObjectRelationshipSource' => 'applications/search/relationship/PhabricatorObjectRelationshipSource.php', 'PhabricatorObjectRemarkupRule' => 'infrastructure/markup/rule/PhabricatorObjectRemarkupRule.php', 'PhabricatorObjectSelectorDialog' => 'view/control/PhabricatorObjectSelectorDialog.php', + 'PhabricatorObjectStatus' => 'infrastructure/status/PhabricatorObjectStatus.php', 'PhabricatorOffsetPagedQuery' => 'infrastructure/query/PhabricatorOffsetPagedQuery.php', 'PhabricatorOldWorldContentSource' => 'infrastructure/contentsource/PhabricatorOldWorldContentSource.php', 'PhabricatorOlderInlinesSetting' => 'applications/settings/setting/PhabricatorOlderInlinesSetting.php', @@ -3611,6 +3619,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleCreateController' => 'applications/people/controller/PhabricatorPeopleCreateController.php', 'PhabricatorPeopleCreateGuidanceContext' => 'applications/people/guidance/PhabricatorPeopleCreateGuidanceContext.php', 'PhabricatorPeopleDatasource' => 'applications/people/typeahead/PhabricatorPeopleDatasource.php', + 'PhabricatorPeopleDatasourceEngineExtension' => 'applications/people/engineextension/PhabricatorPeopleDatasourceEngineExtension.php', 'PhabricatorPeopleDeleteController' => 'applications/people/controller/PhabricatorPeopleDeleteController.php', 'PhabricatorPeopleDetailsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleDetailsProfileMenuItem.php', 'PhabricatorPeopleDisableController' => 'applications/people/controller/PhabricatorPeopleDisableController.php', @@ -3643,7 +3652,6 @@ phutil_register_library_map(array( 'PhabricatorPeopleProfileTasksController' => 'applications/people/controller/PhabricatorPeopleProfileTasksController.php', 'PhabricatorPeopleProfileViewController' => 'applications/people/controller/PhabricatorPeopleProfileViewController.php', 'PhabricatorPeopleQuery' => 'applications/people/query/PhabricatorPeopleQuery.php', - 'PhabricatorPeopleQuickSearchEngineExtension' => 'applications/people/engineextension/PhabricatorPeopleQuickSearchEngineExtension.php', 'PhabricatorPeopleRenameController' => 'applications/people/controller/PhabricatorPeopleRenameController.php', 'PhabricatorPeopleRevisionsProfileMenuItem' => 'applications/people/menuitem/PhabricatorPeopleRevisionsProfileMenuItem.php', 'PhabricatorPeopleSearchEngine' => 'applications/people/query/PhabricatorPeopleSearchEngine.php', @@ -3906,8 +3914,6 @@ phutil_register_library_map(array( 'PhabricatorQueryOrderItem' => 'infrastructure/query/order/PhabricatorQueryOrderItem.php', 'PhabricatorQueryOrderTestCase' => 'infrastructure/query/order/__tests__/PhabricatorQueryOrderTestCase.php', 'PhabricatorQueryOrderVector' => 'infrastructure/query/order/PhabricatorQueryOrderVector.php', - 'PhabricatorQuickSearchApplicationEngineExtension' => 'applications/meta/engineextension/PhabricatorQuickSearchApplicationEngineExtension.php', - 'PhabricatorQuickSearchEngine' => 'applications/search/engine/PhabricatorQuickSearchEngine.php', 'PhabricatorQuickSearchEngineExtension' => 'applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php', 'PhabricatorRateLimitRequestExceptionHandler' => 'aphront/handler/PhabricatorRateLimitRequestExceptionHandler.php', 'PhabricatorRecaptchaConfigOptions' => 'applications/config/option/PhabricatorRecaptchaConfigOptions.php', @@ -4377,6 +4383,7 @@ phutil_register_library_map(array( 'PhabricatorTypeaheadMonogramDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadMonogramDatasource.php', 'PhabricatorTypeaheadResult' => 'applications/typeahead/storage/PhabricatorTypeaheadResult.php', 'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'applications/typeahead/datasource/PhabricatorTypeaheadRuntimeCompositeDatasource.php', + 'PhabricatorTypeaheadTestNumbersDatasource' => 'applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php', 'PhabricatorTypeaheadTokenView' => 'applications/typeahead/view/PhabricatorTypeaheadTokenView.php', 'PhabricatorUIConfigOptions' => 'applications/config/option/PhabricatorUIConfigOptions.php', 'PhabricatorUIExample' => 'applications/uiexample/examples/PhabricatorUIExample.php', @@ -4840,9 +4847,15 @@ phutil_register_library_map(array( 'PhrictionConduitAPIMethod' => 'applications/phriction/conduit/PhrictionConduitAPIMethod.php', 'PhrictionConstants' => 'applications/phriction/constants/PhrictionConstants.php', 'PhrictionContent' => 'applications/phriction/storage/PhrictionContent.php', + 'PhrictionContentPHIDType' => 'applications/phriction/phid/PhrictionContentPHIDType.php', + 'PhrictionContentQuery' => 'applications/phriction/query/PhrictionContentQuery.php', + 'PhrictionContentSearchConduitAPIMethod' => 'applications/phriction/conduit/PhrictionContentSearchConduitAPIMethod.php', + 'PhrictionContentSearchEngine' => 'applications/phriction/query/PhrictionContentSearchEngine.php', + 'PhrictionContentSearchEngineAttachment' => 'applications/phriction/engineextension/PhrictionContentSearchEngineAttachment.php', 'PhrictionController' => 'applications/phriction/controller/PhrictionController.php', 'PhrictionCreateConduitAPIMethod' => 'applications/phriction/conduit/PhrictionCreateConduitAPIMethod.php', 'PhrictionDAO' => 'applications/phriction/storage/PhrictionDAO.php', + 'PhrictionDatasourceEngineExtension' => 'applications/phriction/engineextension/PhrictionDatasourceEngineExtension.php', 'PhrictionDeleteController' => 'applications/phriction/controller/PhrictionDeleteController.php', 'PhrictionDiffController' => 'applications/phriction/controller/PhrictionDiffController.php', 'PhrictionDocument' => 'applications/phriction/storage/PhrictionDocument.php', @@ -4850,6 +4863,7 @@ phutil_register_library_map(array( 'PhrictionDocumentContentHeraldField' => 'applications/phriction/herald/PhrictionDocumentContentHeraldField.php', 'PhrictionDocumentContentTransaction' => 'applications/phriction/xaction/PhrictionDocumentContentTransaction.php', 'PhrictionDocumentController' => 'applications/phriction/controller/PhrictionDocumentController.php', + 'PhrictionDocumentDatasource' => 'applications/phriction/typeahead/PhrictionDocumentDatasource.php', 'PhrictionDocumentDeleteTransaction' => 'applications/phriction/xaction/PhrictionDocumentDeleteTransaction.php', 'PhrictionDocumentFerretEngine' => 'applications/phriction/search/PhrictionDocumentFerretEngine.php', 'PhrictionDocumentFulltextEngine' => 'applications/phriction/search/PhrictionDocumentFulltextEngine.php', @@ -4861,6 +4875,8 @@ phutil_register_library_map(array( 'PhrictionDocumentPHIDType' => 'applications/phriction/phid/PhrictionDocumentPHIDType.php', 'PhrictionDocumentPathHeraldField' => 'applications/phriction/herald/PhrictionDocumentPathHeraldField.php', 'PhrictionDocumentQuery' => 'applications/phriction/query/PhrictionDocumentQuery.php', + 'PhrictionDocumentSearchConduitAPIMethod' => 'applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php', + 'PhrictionDocumentSearchEngine' => 'applications/phriction/query/PhrictionDocumentSearchEngine.php', 'PhrictionDocumentStatus' => 'applications/phriction/constants/PhrictionDocumentStatus.php', 'PhrictionDocumentTitleHeraldField' => 'applications/phriction/herald/PhrictionDocumentTitleHeraldField.php', 'PhrictionDocumentTitleTransaction' => 'applications/phriction/xaction/PhrictionDocumentTitleTransaction.php', @@ -4877,7 +4893,6 @@ phutil_register_library_map(array( 'PhrictionRemarkupRule' => 'applications/phriction/markup/PhrictionRemarkupRule.php', 'PhrictionReplyHandler' => 'applications/phriction/mail/PhrictionReplyHandler.php', 'PhrictionSchemaSpec' => 'applications/phriction/storage/PhrictionSchemaSpec.php', - 'PhrictionSearchEngine' => 'applications/phriction/query/PhrictionSearchEngine.php', 'PhrictionTransaction' => 'applications/phriction/storage/PhrictionTransaction.php', 'PhrictionTransactionComment' => 'applications/phriction/storage/PhrictionTransactionComment.php', 'PhrictionTransactionEditor' => 'applications/phriction/editor/PhrictionTransactionEditor.php', @@ -4946,12 +4961,12 @@ phutil_register_library_map(array( 'ProjectConduitAPIMethod' => 'applications/project/conduit/ProjectConduitAPIMethod.php', 'ProjectCreateConduitAPIMethod' => 'applications/project/conduit/ProjectCreateConduitAPIMethod.php', 'ProjectCreateProjectsCapability' => 'applications/project/capability/ProjectCreateProjectsCapability.php', + 'ProjectDatasourceEngineExtension' => 'applications/project/engineextension/ProjectDatasourceEngineExtension.php', 'ProjectDefaultEditCapability' => 'applications/project/capability/ProjectDefaultEditCapability.php', 'ProjectDefaultJoinCapability' => 'applications/project/capability/ProjectDefaultJoinCapability.php', 'ProjectDefaultViewCapability' => 'applications/project/capability/ProjectDefaultViewCapability.php', 'ProjectEditConduitAPIMethod' => 'applications/project/conduit/ProjectEditConduitAPIMethod.php', 'ProjectQueryConduitAPIMethod' => 'applications/project/conduit/ProjectQueryConduitAPIMethod.php', - 'ProjectQuickSearchEngineExtension' => 'applications/project/engineextension/ProjectQuickSearchEngineExtension.php', 'ProjectRemarkupRule' => 'applications/project/remarkup/ProjectRemarkupRule.php', 'ProjectRemarkupRuleTestCase' => 'applications/project/remarkup/__tests__/ProjectRemarkupRuleTestCase.php', 'ProjectReplyHandler' => 'applications/project/mail/ProjectReplyHandler.php', @@ -5644,7 +5659,6 @@ phutil_register_library_map(array( 'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery', 'DifferentialJIRAIssuesCommitMessageField' => 'DifferentialCommitMessageCustomField', 'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField', - 'DifferentialLegacyHunk' => 'DifferentialHunk', 'DifferentialLegacyQuery' => 'Phobject', 'DifferentialLineAdjustmentMap' => 'Phobject', 'DifferentialLintField' => 'DifferentialHarbormasterField', @@ -5653,7 +5667,6 @@ phutil_register_library_map(array( 'DifferentialMailEngineExtension' => 'PhabricatorMailEngineExtension', 'DifferentialMailView' => 'Phobject', 'DifferentialManiphestTasksField' => 'DifferentialCoreCustomField', - 'DifferentialModernHunk' => 'DifferentialHunk', 'DifferentialParseCacheGarbageCollector' => 'PhabricatorGarbageCollector', 'DifferentialParseCommitMessageConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialParseRenderTestCase' => 'PhabricatorTestCase', @@ -5899,6 +5912,7 @@ phutil_register_library_map(array( 'DiffusionController' => 'PhabricatorController', 'DiffusionCreateRepositoriesCapability' => 'PhabricatorPolicyCapability', 'DiffusionDaemonLockException' => 'Exception', + 'DiffusionDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'DiffusionDefaultEditCapability' => 'PhabricatorPolicyCapability', 'DiffusionDefaultPushCapability' => 'PhabricatorPolicyCapability', 'DiffusionDefaultViewCapability' => 'PhabricatorPolicyCapability', @@ -5997,6 +6011,8 @@ phutil_register_library_map(array( 'DiffusionPreCommitContentHeraldField' => 'HeraldField', 'DiffusionPreCommitContentMergeHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentMessageHeraldField' => 'DiffusionPreCommitContentHeraldField', + 'DiffusionPreCommitContentPackageHeraldField' => 'DiffusionPreCommitContentHeraldField', + 'DiffusionPreCommitContentPackageOwnerHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentPusherHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentPusherIsCommitterHeraldField' => 'DiffusionPreCommitContentHeraldField', 'DiffusionPreCommitContentPusherProjectsHeraldField' => 'DiffusionPreCommitContentHeraldField', @@ -6030,7 +6046,6 @@ phutil_register_library_map(array( 'DiffusionQueryCommitsConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionQueryConduitAPIMethod' => 'DiffusionConduitAPIMethod', 'DiffusionQueryPathsConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', - 'DiffusionQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'DiffusionRawDiffQuery' => 'DiffusionFileFutureQuery', 'DiffusionRawDiffQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionReadmeView' => 'DiffusionView', @@ -6200,6 +6215,7 @@ phutil_register_library_map(array( 'DoorkeeperSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'DoorkeeperTagView' => 'AphrontView', 'DoorkeeperTagsController' => 'PhabricatorController', + 'DrydockAcquiredBrokenResourceException' => 'Exception', 'DrydockAlmanacServiceHostBlueprintImplementation' => 'DrydockBlueprintImplementation', 'DrydockApacheWebrootInterface' => 'DrydockWebrootInterface', 'DrydockAuthorization' => array( @@ -6259,7 +6275,6 @@ phutil_register_library_map(array( 'DrydockCommandInterface' => 'DrydockInterface', 'DrydockCommandQuery' => 'DrydockQuery', 'DrydockConsoleController' => 'DrydockController', - 'DrydockConstants' => 'Phobject', 'DrydockController' => 'PhabricatorController', 'DrydockCreateBlueprintsCapability' => 'PhabricatorPolicyCapability', 'DrydockDAO' => 'PhabricatorLiskDAO', @@ -6276,6 +6291,7 @@ phutil_register_library_map(array( 'DrydockLeaseActivatedLogType' => 'DrydockLogType', 'DrydockLeaseActivationFailureLogType' => 'DrydockLogType', 'DrydockLeaseActivationYieldLogType' => 'DrydockLogType', + 'DrydockLeaseAllocationFailureLogType' => 'DrydockLogType', 'DrydockLeaseController' => 'DrydockController', 'DrydockLeaseDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockLeaseDestroyedLogType' => 'DrydockLogType', @@ -6286,11 +6302,12 @@ phutil_register_library_map(array( 'DrydockLeasePHIDType' => 'PhabricatorPHIDType', 'DrydockLeaseQuery' => 'DrydockQuery', 'DrydockLeaseQueuedLogType' => 'DrydockLogType', + 'DrydockLeaseReacquireLogType' => 'DrydockLogType', 'DrydockLeaseReclaimLogType' => 'DrydockLogType', 'DrydockLeaseReleaseController' => 'DrydockLeaseController', 'DrydockLeaseReleasedLogType' => 'DrydockLogType', 'DrydockLeaseSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'DrydockLeaseStatus' => 'DrydockConstants', + 'DrydockLeaseStatus' => 'PhabricatorObjectStatus', 'DrydockLeaseUpdateWorker' => 'DrydockWorker', 'DrydockLeaseViewController' => 'DrydockLeaseController', 'DrydockLeaseWaitingForResourcesLogType' => 'DrydockLogType', @@ -6336,16 +6353,18 @@ phutil_register_library_map(array( ), 'DrydockResourceActivationFailureLogType' => 'DrydockLogType', 'DrydockResourceActivationYieldLogType' => 'DrydockLogType', + 'DrydockResourceAllocationFailureLogType' => 'DrydockLogType', 'DrydockResourceController' => 'DrydockController', 'DrydockResourceDatasource' => 'PhabricatorTypeaheadDatasource', 'DrydockResourceListController' => 'DrydockResourceController', 'DrydockResourceListView' => 'AphrontView', + 'DrydockResourceLockException' => 'Exception', 'DrydockResourcePHIDType' => 'PhabricatorPHIDType', 'DrydockResourceQuery' => 'DrydockQuery', 'DrydockResourceReclaimLogType' => 'DrydockLogType', 'DrydockResourceReleaseController' => 'DrydockResourceController', 'DrydockResourceSearchEngine' => 'PhabricatorApplicationSearchEngine', - 'DrydockResourceStatus' => 'DrydockConstants', + 'DrydockResourceStatus' => 'PhabricatorObjectStatus', 'DrydockResourceUpdateWorker' => 'DrydockWorker', 'DrydockResourceViewController' => 'DrydockResourceController', 'DrydockSFTPFilesystemInterface' => 'DrydockFilesystemInterface', @@ -6553,6 +6572,7 @@ phutil_register_library_map(array( 'HarbormasterBuildablePHIDType' => 'PhabricatorPHIDType', 'HarbormasterBuildableQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'HarbormasterBuildableSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'HarbormasterBuildableStatus' => 'Phobject', 'HarbormasterBuildableTransaction' => 'PhabricatorApplicationTransaction', 'HarbormasterBuildableTransactionEditor' => 'PhabricatorApplicationTransactionEditor', 'HarbormasterBuildableTransactionQuery' => 'PhabricatorApplicationTransactionQuery', @@ -8247,8 +8267,11 @@ phutil_register_library_map(array( 'PhabricatorDatabaseRef' => 'Phobject', 'PhabricatorDatabaseRefParser' => 'Phobject', 'PhabricatorDatabaseSetupCheck' => 'PhabricatorSetupCheck', + 'PhabricatorDatasourceApplicationEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'PhabricatorDatasourceEditField' => 'PhabricatorTokenizerEditField', 'PhabricatorDatasourceEditType' => 'PhabricatorPHIDListEditType', + 'PhabricatorDatasourceEngine' => 'Phobject', + 'PhabricatorDatasourceEngineExtension' => 'Phobject', 'PhabricatorDateFormatSetting' => 'PhabricatorSelectSetting', 'PhabricatorDateTimeSettingsPanel' => 'PhabricatorEditEngineSettingsPanel', 'PhabricatorDebugController' => 'PhabricatorController', @@ -8697,7 +8720,6 @@ phutil_register_library_map(array( 'PhabricatorJSONExportFormat' => 'PhabricatorExportFormat', 'PhabricatorJavelinLinter' => 'ArcanistLinter', 'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType', - 'PhabricatorJumpNavHandler' => 'Phobject', 'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache', 'PhabricatorKeyValueSerializingCacheProxy' => 'PhutilKeyValueCacheProxy', 'PhabricatorKeyboardRemarkupRule' => 'PhutilRemarkupRule', @@ -8873,7 +8895,7 @@ phutil_register_library_map(array( 'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock', 'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorModularTransactionType' => 'Phobject', - 'PhabricatorMonogramQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', + 'PhabricatorMonogramDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'PhabricatorMonospacedFontSetting' => 'PhabricatorStringSetting', 'PhabricatorMonospacedTextareasSetting' => 'PhabricatorSelectSetting', 'PhabricatorMotivatorProfileMenuItem' => 'PhabricatorProfileMenuItem', @@ -8992,6 +9014,7 @@ phutil_register_library_map(array( 'PhabricatorObjectRelationshipSource' => 'Phobject', 'PhabricatorObjectRemarkupRule' => 'PhutilRemarkupRule', 'PhabricatorObjectSelectorDialog' => 'Phobject', + 'PhabricatorObjectStatus' => 'Phobject', 'PhabricatorOffsetPagedQuery' => 'PhabricatorQuery', 'PhabricatorOldWorldContentSource' => 'PhabricatorContentSource', 'PhabricatorOlderInlinesSetting' => 'PhabricatorSelectSetting', @@ -9240,6 +9263,7 @@ phutil_register_library_map(array( 'PhabricatorPeopleCreateController' => 'PhabricatorPeopleController', 'PhabricatorPeopleCreateGuidanceContext' => 'PhabricatorGuidanceContext', 'PhabricatorPeopleDatasource' => 'PhabricatorTypeaheadDatasource', + 'PhabricatorPeopleDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'PhabricatorPeopleDeleteController' => 'PhabricatorPeopleController', 'PhabricatorPeopleDetailsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorPeopleDisableController' => 'PhabricatorPeopleController', @@ -9272,7 +9296,6 @@ phutil_register_library_map(array( 'PhabricatorPeopleProfileTasksController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleProfileViewController' => 'PhabricatorPeopleProfileController', 'PhabricatorPeopleQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', - 'PhabricatorPeopleQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'PhabricatorPeopleRenameController' => 'PhabricatorPeopleController', 'PhabricatorPeopleRevisionsProfileMenuItem' => 'PhabricatorProfileMenuItem', 'PhabricatorPeopleSearchEngine' => 'PhabricatorApplicationSearchEngine', @@ -9590,9 +9613,7 @@ phutil_register_library_map(array( 'Phobject', 'Iterator', ), - 'PhabricatorQuickSearchApplicationEngineExtension' => 'PhabricatorQuickSearchEngineExtension', - 'PhabricatorQuickSearchEngine' => 'Phobject', - 'PhabricatorQuickSearchEngineExtension' => 'Phobject', + 'PhabricatorQuickSearchEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'PhabricatorRateLimitRequestExceptionHandler' => 'PhabricatorRequestExceptionHandler', 'PhabricatorRecaptchaConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorRedirectController' => 'PhabricatorController', @@ -10151,6 +10172,7 @@ phutil_register_library_map(array( 'PhabricatorTypeaheadMonogramDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorTypeaheadResult' => 'Phobject', 'PhabricatorTypeaheadRuntimeCompositeDatasource' => 'PhabricatorTypeaheadCompositeDatasource', + 'PhabricatorTypeaheadTestNumbersDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorTypeaheadTokenView' => 'AphrontTagView', 'PhabricatorUIConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorUIExample' => 'Phobject', @@ -10745,11 +10767,19 @@ phutil_register_library_map(array( 'PhrictionConstants' => 'Phobject', 'PhrictionContent' => array( 'PhrictionDAO', - 'PhabricatorMarkupInterface', + 'PhabricatorPolicyInterface', + 'PhabricatorDestructibleInterface', + 'PhabricatorConduitResultInterface', ), + 'PhrictionContentPHIDType' => 'PhabricatorPHIDType', + 'PhrictionContentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', + 'PhrictionContentSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'PhrictionContentSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhrictionContentSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'PhrictionController' => 'PhabricatorController', 'PhrictionCreateConduitAPIMethod' => 'PhrictionConduitAPIMethod', 'PhrictionDAO' => 'PhabricatorLiskDAO', + 'PhrictionDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'PhrictionDeleteController' => 'PhrictionController', 'PhrictionDiffController' => 'PhrictionController', 'PhrictionDocument' => array( @@ -10763,11 +10793,13 @@ phutil_register_library_map(array( 'PhabricatorFerretInterface', 'PhabricatorProjectInterface', 'PhabricatorApplicationTransactionInterface', + 'PhabricatorConduitResultInterface', ), 'PhrictionDocumentAuthorHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentContentHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentContentTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentController' => 'PhrictionController', + 'PhrictionDocumentDatasource' => 'PhabricatorTypeaheadDatasource', 'PhrictionDocumentDeleteTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentFerretEngine' => 'PhabricatorFerretEngine', 'PhrictionDocumentFulltextEngine' => 'PhabricatorFulltextEngine', @@ -10779,7 +10811,9 @@ phutil_register_library_map(array( 'PhrictionDocumentPHIDType' => 'PhabricatorPHIDType', 'PhrictionDocumentPathHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', - 'PhrictionDocumentStatus' => 'PhrictionConstants', + 'PhrictionDocumentSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', + 'PhrictionDocumentSearchEngine' => 'PhabricatorApplicationSearchEngine', + 'PhrictionDocumentStatus' => 'PhabricatorObjectStatus', 'PhrictionDocumentTitleHeraldField' => 'PhrictionDocumentHeraldField', 'PhrictionDocumentTitleTransaction' => 'PhrictionDocumentTransactionType', 'PhrictionDocumentTransactionType' => 'PhabricatorModularTransactionType', @@ -10795,7 +10829,6 @@ phutil_register_library_map(array( 'PhrictionRemarkupRule' => 'PhutilRemarkupRule', 'PhrictionReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'PhrictionSchemaSpec' => 'PhabricatorConfigSchemaSpec', - 'PhrictionSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhrictionTransaction' => 'PhabricatorModularTransaction', 'PhrictionTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhrictionTransactionEditor' => 'PhabricatorApplicationTransactionEditor', @@ -10885,12 +10918,12 @@ phutil_register_library_map(array( 'ProjectConduitAPIMethod' => 'ConduitAPIMethod', 'ProjectCreateConduitAPIMethod' => 'ProjectConduitAPIMethod', 'ProjectCreateProjectsCapability' => 'PhabricatorPolicyCapability', + 'ProjectDatasourceEngineExtension' => 'PhabricatorDatasourceEngineExtension', 'ProjectDefaultEditCapability' => 'PhabricatorPolicyCapability', 'ProjectDefaultJoinCapability' => 'PhabricatorPolicyCapability', 'ProjectDefaultViewCapability' => 'PhabricatorPolicyCapability', 'ProjectEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'ProjectQueryConduitAPIMethod' => 'ProjectConduitAPIMethod', - 'ProjectQuickSearchEngineExtension' => 'PhabricatorQuickSearchEngineExtension', 'ProjectRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'ProjectRemarkupRuleTestCase' => 'PhabricatorTestCase', 'ProjectReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', diff --git a/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php b/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php index c09aa88308..067cea30e7 100644 --- a/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php +++ b/src/applications/auth/engine/PhabricatorAuthPasswordEngine.php @@ -300,6 +300,10 @@ final class PhabricatorAuthPasswordEngine $password->upgradePasswordHasher($envelope, $this->getObject()); $new_hasher = $password->getHasher(); + // NOTE: We must save the change before applying transactions because + // the editor will reload the object to obtain a read lock. + $password->save(); + $xactions = array(); $xactions[] = $password->getApplicationTransactionTemplate() diff --git a/src/applications/config/option/PhabricatorCoreConfigOptions.php b/src/applications/config/option/PhabricatorCoreConfigOptions.php index 6efd27d9a3..0c9e69ebf2 100644 --- a/src/applications/config/option/PhabricatorCoreConfigOptions.php +++ b/src/applications/config/option/PhabricatorCoreConfigOptions.php @@ -37,6 +37,24 @@ final class PhabricatorCoreConfigOptions $proto_doc_name = pht('User Guide: Prototype Applications'); $applications_app_href = '/applications/'; + $silent_description = $this->deformat(pht(<<newOption('phabricator.base-uri', 'string', null) ->setLocked(true) @@ -232,21 +250,7 @@ final class PhabricatorCoreConfigOptions pht('Run Normally'), )) ->setSummary(pht('Stop Phabricator from sending any email, etc.')) - ->setDescription( - pht( - 'This option allows you to stop Phabricator from sending '. - 'any data to external services. Among other things, it will '. - 'disable email, SMS, repository mirroring, and HTTP hooks.'. - "\n\n". - 'This option is intended to allow a Phabricator instance to '. - 'be exported, copied, imported, and run in a test environment '. - 'without impacting users. For example, if you are migrating '. - 'to new hardware, you could perform a test migration first, '. - 'make sure things work, and then do a production cutover '. - 'later with higher confidence and less disruption. Without '. - 'this flag, users would receive duplicate email during the '. - 'time the test instance and old production instance were '. - 'both in operation.')), + ->setDescription($silent_description), ); } diff --git a/src/applications/conpherence/editor/ConpherenceEditor.php b/src/applications/conpherence/editor/ConpherenceEditor.php index 7896055f64..3d451e78d3 100644 --- a/src/applications/conpherence/editor/ConpherenceEditor.php +++ b/src/applications/conpherence/editor/ConpherenceEditor.php @@ -99,24 +99,6 @@ final class ConpherenceEditor extends PhabricatorApplicationTransactionEditor { return pht('%s created this room.', $author); } - /** - * We really only need a read lock if we have a comment. In that case, we - * must update the messagesCount field on the conpherence and - * seenMessagesCount(s) for the participant(s). - */ - protected function shouldReadLock( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - - $lock = false; - switch ($xaction->getTransactionType()) { - case PhabricatorTransactions::TYPE_COMMENT: - $lock = true; - break; - } - - return $lock; - } protected function applyBuiltinInternalTransaction( PhabricatorLiskDAO $object, diff --git a/src/applications/differential/customfield/DifferentialUnitField.php b/src/applications/differential/customfield/DifferentialUnitField.php index 7b3d491cbd..4c1eb72cc8 100644 --- a/src/applications/differential/customfield/DifferentialUnitField.php +++ b/src/applications/differential/customfield/DifferentialUnitField.php @@ -44,12 +44,12 @@ final class DifferentialUnitField ->executeOne(); if ($buildable) { switch ($buildable->getBuildableStatus()) { - case HarbormasterBuildable::STATUS_BUILDING: + case HarbormasterBuildableStatus::STATUS_BUILDING: $warnings[] = pht( 'These changes have not finished building yet and may have build '. 'failures.'); break; - case HarbormasterBuildable::STATUS_FAILED: + case HarbormasterBuildableStatus::STATUS_FAILED: $warnings[] = pht( 'These changes have failed to build.'); break; diff --git a/src/applications/differential/editor/DifferentialTransactionEditor.php b/src/applications/differential/editor/DifferentialTransactionEditor.php index 063df6c602..a092bf2693 100644 --- a/src/applications/differential/editor/DifferentialTransactionEditor.php +++ b/src/applications/differential/editor/DifferentialTransactionEditor.php @@ -365,21 +365,22 @@ final class DifferentialTransactionEditor $diff->setRevisionID($object->getID()); $diff->save(); - // Update Harbormaster to set the containerPHID correctly for any - // existing buildables. We may otherwise have buildables stuck with - // the old (`null`) container. + // If there are any outstanding buildables for this diff, tell + // Harbormaster that their containers need to be updated. This is + // common, because `arc` creates buildables so it can upload lint + // and unit results. - // TODO: This is a bit iffy, maybe we can find a cleaner approach? - // In particular, this could (rarely) be overwritten by Harbormaster - // workers. - $table = new HarbormasterBuildable(); - $conn_w = $table->establishConnection('w'); - queryfx( - $conn_w, - 'UPDATE %T SET containerPHID = %s WHERE buildablePHID = %s', - $table->getTableName(), - $object->getPHID(), - $diff->getPHID()); + $buildables = id(new HarbormasterBuildableQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withManualBuildables(false) + ->withBuildablePHIDs(array($diff->getPHID())) + ->execute(); + foreach ($buildables as $buildable) { + $buildable->sendMessage( + $this->getActor(), + HarbormasterMessageType::BUILDABLE_CONTAINER, + true); + } return; } diff --git a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php index c426c411e7..6fb5e70de5 100644 --- a/src/applications/differential/engine/DifferentialDiffExtractionEngine.php +++ b/src/applications/differential/engine/DifferentialDiffExtractionEngine.php @@ -217,8 +217,8 @@ final class DifferentialDiffExtractionEngine extends Phobject { // -echo "test"; // -(empty line) - $hunk = id(new DifferentialModernHunk())->setChanges($context); - $vs_hunk = id(new DifferentialModernHunk())->setChanges($vs_context); + $hunk = id(new DifferentialHunk())->setChanges($context); + $vs_hunk = id(new DifferentialHunk())->setChanges($vs_context); if ($hunk->makeOldFile() != $vs_hunk->makeOldFile() || $hunk->makeNewFile() != $vs_hunk->makeNewFile()) { return true; diff --git a/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php b/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php index a6b2e7c36e..966b306580 100644 --- a/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php +++ b/src/applications/differential/herald/HeraldDifferentialDiffAdapter.php @@ -57,4 +57,8 @@ final class HeraldDifferentialDiffAdapter extends HeraldDifferentialAdapter { return pht('New Diff'); } + public function supportsWebhooks() { + return false; + } + } diff --git a/src/applications/differential/management/PhabricatorDifferentialMigrateHunkWorkflow.php b/src/applications/differential/management/PhabricatorDifferentialMigrateHunkWorkflow.php index 9c0e0071e4..99125645e7 100644 --- a/src/applications/differential/management/PhabricatorDifferentialMigrateHunkWorkflow.php +++ b/src/applications/differential/management/PhabricatorDifferentialMigrateHunkWorkflow.php @@ -32,8 +32,8 @@ final class PhabricatorDifferentialMigrateHunkWorkflow $storage = $args->getArg('to'); switch ($storage) { - case DifferentialModernHunk::DATATYPE_TEXT: - case DifferentialModernHunk::DATATYPE_FILE: + case DifferentialHunk::DATATYPE_TEXT: + case DifferentialHunk::DATATYPE_FILE: break; default: throw new PhutilArgumentUsageException( @@ -44,13 +44,13 @@ final class PhabricatorDifferentialMigrateHunkWorkflow $old_data = $hunk->getChanges(); switch ($storage) { - case DifferentialModernHunk::DATATYPE_TEXT: + case DifferentialHunk::DATATYPE_TEXT: $hunk->saveAsText(); $this->logOkay( pht('TEXT'), pht('Convereted hunk to text storage.')); break; - case DifferentialModernHunk::DATATYPE_FILE: + case DifferentialHunk::DATATYPE_FILE: $hunk->saveAsFile(); $this->logOkay( pht('FILE'), @@ -71,7 +71,7 @@ final class PhabricatorDifferentialMigrateHunkWorkflow } private function loadHunk($id) { - $hunk = id(new DifferentialModernHunk())->load($id); + $hunk = id(new DifferentialHunk())->load($id); if (!$hunk) { throw new PhutilArgumentUsageException( pht( diff --git a/src/applications/differential/parser/__tests__/DifferentialChangesetParserTestCase.php b/src/applications/differential/parser/__tests__/DifferentialChangesetParserTestCase.php index 9ca88db8ce..d8d10169b5 100644 --- a/src/applications/differential/parser/__tests__/DifferentialChangesetParserTestCase.php +++ b/src/applications/differential/parser/__tests__/DifferentialChangesetParserTestCase.php @@ -3,7 +3,7 @@ final class DifferentialChangesetParserTestCase extends PhabricatorTestCase { public function testDiffChangesets() { - $hunk = new DifferentialModernHunk(); + $hunk = new DifferentialHunk(); $hunk->setChanges("+a\n b\n-c"); $hunk->setNewOffset(1); $hunk->setNewLen(2); @@ -20,7 +20,7 @@ final class DifferentialChangesetParserTestCase extends PhabricatorTestCase { ); foreach ($tests as $changes => $expected) { - $hunk = new DifferentialModernHunk(); + $hunk = new DifferentialHunk(); $hunk->setChanges($changes); $hunk->setNewOffset(11); $hunk->setNewLen(3); diff --git a/src/applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php b/src/applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php index d1d2501b94..644e7c0b88 100644 --- a/src/applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php +++ b/src/applications/differential/parser/__tests__/DifferentialHunkParserTestCase.php @@ -14,7 +14,7 @@ final class DifferentialHunkParserTestCase extends PhabricatorTestCase { $new_len, $changes) { - $hunk = id(new DifferentialModernHunk()) + $hunk = id(new DifferentialHunk()) ->setOldOffset($old_offset) ->setOldLen($old_len) ->setNewOffset($new_offset) diff --git a/src/applications/differential/query/DifferentialHunkQuery.php b/src/applications/differential/query/DifferentialHunkQuery.php index c8e35cb607..981c2b4782 100644 --- a/src/applications/differential/query/DifferentialHunkQuery.php +++ b/src/applications/differential/query/DifferentialHunkQuery.php @@ -30,25 +30,12 @@ final class DifferentialHunkQuery } } + public function newResultObject() { + return new DifferentialHunk(); + } + protected function loadPage() { - $all_results = array(); - - // Load modern hunks. - $table = new DifferentialModernHunk(); - $conn_r = $table->establishConnection('r'); - - $modern_data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - $modern_results = $table->loadAllFromArray($modern_data); - - // Strip all the IDs off since they're not unique and nothing should be - // using them. - return array_values($modern_results); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $hunks) { @@ -76,8 +63,8 @@ final class DifferentialHunkQuery return $hunks; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); if (!$this->changesets) { throw new Exception( @@ -87,13 +74,11 @@ final class DifferentialHunkQuery } $where[] = qsprintf( - $conn_r, + $conn, 'changesetID IN (%Ld)', mpull($this->changesets, 'getID')); - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/differential/storage/DifferentialChangeset.php b/src/applications/differential/storage/DifferentialChangeset.php index dbb06fe72b..4832f452e6 100644 --- a/src/applications/differential/storage/DifferentialChangeset.php +++ b/src/applications/differential/storage/DifferentialChangeset.php @@ -118,11 +118,11 @@ final class DifferentialChangeset public function delete() { $this->openTransaction(); - $modern_hunks = id(new DifferentialModernHunk())->loadAllWhere( + $hunks = id(new DifferentialHunk())->loadAllWhere( 'changesetID = %d', $this->getID()); - foreach ($modern_hunks as $modern_hunk) { - $modern_hunk->delete(); + foreach ($hunks as $hunk) { + $hunk->delete(); } $this->unsavedHunks = array(); @@ -292,7 +292,7 @@ final class DifferentialChangeset PhabricatorDestructionEngine $engine) { $this->openTransaction(); - $hunks = id(new DifferentialModernHunk())->loadAllWhere( + $hunks = id(new DifferentialHunk())->loadAllWhere( 'changesetID = %d', $this->getID()); foreach ($hunks as $hunk) { diff --git a/src/applications/differential/storage/DifferentialDiff.php b/src/applications/differential/storage/DifferentialDiff.php index d70cdbbdf5..85ffccc4a3 100644 --- a/src/applications/differential/storage/DifferentialDiff.php +++ b/src/applications/differential/storage/DifferentialDiff.php @@ -189,7 +189,7 @@ final class DifferentialDiff $hunks = $change->getHunks(); if ($hunks) { foreach ($hunks as $hunk) { - $dhunk = new DifferentialModernHunk(); + $dhunk = new DifferentialHunk(); $dhunk->setOldOffset($hunk->getOldOffset()); $dhunk->setOldLen($hunk->getOldLength()); $dhunk->setNewOffset($hunk->getNewOffset()); diff --git a/src/applications/differential/storage/DifferentialHunk.php b/src/applications/differential/storage/DifferentialHunk.php index a57d0035eb..3defb3566b 100644 --- a/src/applications/differential/storage/DifferentialHunk.php +++ b/src/applications/differential/storage/DifferentialHunk.php @@ -1,6 +1,6 @@ array( + 'data' => true, + ), + self::CONFIG_COLUMN_SCHEMA => array( + 'dataType' => 'bytes4', + 'dataEncoding' => 'text16?', + 'dataFormat' => 'bytes4', + 'oldOffset' => 'uint32', + 'oldLen' => 'uint32', + 'newOffset' => 'uint32', + 'newLen' => 'uint32', + ), + self::CONFIG_KEY_SCHEMA => array( + 'key_changeset' => array( + 'columns' => array('changesetID'), + ), + 'key_created' => array( + 'columns' => array('dateCreated'), + ), + ), + ) + parent::getConfiguration(); + } + public function getAddedLines() { return $this->makeContent($include = '+'); } @@ -214,6 +253,197 @@ abstract class DifferentialHunk } +/* -( Storage )------------------------------------------------------------ */ + + + public function setChanges($text) { + $this->rawData = $text; + + $this->dataEncoding = $this->detectEncodingForStorage($text); + $this->dataType = self::DATATYPE_TEXT; + + list($format, $data) = $this->formatDataForStorage($text); + + $this->dataFormat = $format; + $this->data = $data; + + return $this; + } + + public function getChanges() { + return $this->getUTF8StringFromStorage( + $this->getRawData(), + nonempty($this->forcedEncoding, $this->getDataEncoding())); + } + + public function forceEncoding($encoding) { + $this->forcedEncoding = $encoding; + return $this; + } + + private function formatDataForStorage($data) { + $deflated = PhabricatorCaches::maybeDeflateData($data); + if ($deflated !== null) { + return array(self::DATAFORMAT_DEFLATED, $deflated); + } + + return array(self::DATAFORMAT_RAW, $data); + } + + public function saveAsText() { + $old_type = $this->getDataType(); + $old_data = $this->getData(); + + if ($old_type == self::DATATYPE_TEXT) { + return $this; + } + + $raw_data = $this->getRawData(); + + $this->setDataType(self::DATATYPE_TEXT); + + list($format, $data) = $this->formatDataForStorage($raw_data); + $this->setDataFormat($format); + $this->setData($data); + + $result = $this->save(); + + $this->destroyData($old_type, $old_data); + + return $result; + } + + public function saveAsFile() { + $old_type = $this->getDataType(); + $old_data = $this->getData(); + + if ($old_type == self::DATATYPE_FILE) { + return $this; + } + + $raw_data = $this->getRawData(); + + list($format, $data) = $this->formatDataForStorage($raw_data); + $this->setDataFormat($format); + + $file = PhabricatorFile::newFromFileData( + $data, + array( + 'name' => 'differential-hunk', + 'mime-type' => 'application/octet-stream', + 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, + )); + + $this->setDataType(self::DATATYPE_FILE); + $this->setData($file->getPHID()); + + // NOTE: Because hunks don't have a PHID and we just load hunk data with + // the omnipotent viewer, we do not need to attach the file to anything. + + $result = $this->save(); + + $this->destroyData($old_type, $old_data); + + return $result; + } + + private function getRawData() { + if ($this->rawData === null) { + $type = $this->getDataType(); + $data = $this->getData(); + + switch ($type) { + case self::DATATYPE_TEXT: + // In this storage type, the changes are stored on the object. + $data = $data; + break; + case self::DATATYPE_FILE: + $data = $this->loadFileData(); + break; + default: + throw new Exception( + pht('Hunk has unsupported data type "%s"!', $type)); + } + + $format = $this->getDataFormat(); + switch ($format) { + case self::DATAFORMAT_RAW: + // In this format, the changes are stored as-is. + $data = $data; + break; + case self::DATAFORMAT_DEFLATED: + $data = PhabricatorCaches::inflateData($data); + break; + default: + throw new Exception( + pht('Hunk has unsupported data encoding "%s"!', $type)); + } + + $this->rawData = $data; + } + + return $this->rawData; + } + + private function loadFileData() { + if ($this->fileData === null) { + $type = $this->getDataType(); + if ($type !== self::DATATYPE_FILE) { + throw new Exception( + pht( + 'Unable to load file data for hunk with wrong data type ("%s").', + $type)); + } + + $file_phid = $this->getData(); + + $file = $this->loadRawFile($file_phid); + $data = $file->loadFileData(); + + $this->fileData = $data; + } + + return $this->fileData; + } + + private function loadRawFile($file_phid) { + $viewer = PhabricatorUser::getOmnipotentUser(); + + + $files = id(new PhabricatorFileQuery()) + ->setViewer($viewer) + ->withPHIDs(array($file_phid)) + ->execute(); + if (!$files) { + throw new Exception( + pht( + 'Failed to load file ("%s") with hunk data.', + $file_phid)); + } + + $file = head($files); + + return $file; + } + + private function destroyData( + $type, + $data, + PhabricatorDestructionEngine $engine = null) { + + if (!$engine) { + $engine = new PhabricatorDestructionEngine(); + } + + switch ($type) { + case self::DATATYPE_FILE: + $file = $this->loadRawFile($data); + $engine->destroyObject($file); + break; + } + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -237,8 +467,13 @@ abstract class DifferentialHunk public function destroyObjectPermanently( PhabricatorDestructionEngine $engine) { + + $type = $this->getDataType(); + $data = $this->getData(); + + $this->destroyData($type, $data, $engine); + $this->delete(); } - } diff --git a/src/applications/differential/storage/DifferentialLegacyHunk.php b/src/applications/differential/storage/DifferentialLegacyHunk.php deleted file mode 100644 index ae4673e46c..0000000000 --- a/src/applications/differential/storage/DifferentialLegacyHunk.php +++ /dev/null @@ -1,37 +0,0 @@ - array( - 'changes' => 'text?', - 'oldOffset' => 'uint32', - 'oldLen' => 'uint32', - 'newOffset' => 'uint32', - 'newLen' => 'uint32', - ), - self::CONFIG_KEY_SCHEMA => array( - 'changesetID' => array( - 'columns' => array('changesetID'), - ), - ), - ) + parent::getConfiguration(); - } - - public function getTableName() { - return 'differential_hunk'; - } - - public function getDataEncoding() { - return 'utf8'; - } - - public function forceEncoding($encoding) { - // Not supported, these are always utf8. - return $this; - } - -} diff --git a/src/applications/differential/storage/DifferentialModernHunk.php b/src/applications/differential/storage/DifferentialModernHunk.php deleted file mode 100644 index c3675c8adf..0000000000 --- a/src/applications/differential/storage/DifferentialModernHunk.php +++ /dev/null @@ -1,249 +0,0 @@ - array( - 'data' => true, - ), - self::CONFIG_COLUMN_SCHEMA => array( - 'dataType' => 'bytes4', - 'dataEncoding' => 'text16?', - 'dataFormat' => 'bytes4', - 'oldOffset' => 'uint32', - 'oldLen' => 'uint32', - 'newOffset' => 'uint32', - 'newLen' => 'uint32', - ), - self::CONFIG_KEY_SCHEMA => array( - 'key_changeset' => array( - 'columns' => array('changesetID'), - ), - 'key_created' => array( - 'columns' => array('dateCreated'), - ), - ), - ) + parent::getConfiguration(); - } - - public function setChanges($text) { - $this->rawData = $text; - - $this->dataEncoding = $this->detectEncodingForStorage($text); - $this->dataType = self::DATATYPE_TEXT; - - list($format, $data) = $this->formatDataForStorage($text); - - $this->dataFormat = $format; - $this->data = $data; - - return $this; - } - - public function getChanges() { - return $this->getUTF8StringFromStorage( - $this->getRawData(), - nonempty($this->forcedEncoding, $this->getDataEncoding())); - } - - public function forceEncoding($encoding) { - $this->forcedEncoding = $encoding; - return $this; - } - - private function formatDataForStorage($data) { - $deflated = PhabricatorCaches::maybeDeflateData($data); - if ($deflated !== null) { - return array(self::DATAFORMAT_DEFLATED, $deflated); - } - - return array(self::DATAFORMAT_RAW, $data); - } - - public function saveAsText() { - $old_type = $this->getDataType(); - $old_data = $this->getData(); - - if ($old_type == self::DATATYPE_TEXT) { - return $this; - } - - $raw_data = $this->getRawData(); - - $this->setDataType(self::DATATYPE_TEXT); - - list($format, $data) = $this->formatDataForStorage($raw_data); - $this->setDataFormat($format); - $this->setData($data); - - $result = $this->save(); - - $this->destroyData($old_type, $old_data); - - return $result; - } - - public function saveAsFile() { - $old_type = $this->getDataType(); - $old_data = $this->getData(); - - if ($old_type == self::DATATYPE_FILE) { - return $this; - } - - $raw_data = $this->getRawData(); - - list($format, $data) = $this->formatDataForStorage($raw_data); - $this->setDataFormat($format); - - $file = PhabricatorFile::newFromFileData( - $data, - array( - 'name' => 'differential-hunk', - 'mime-type' => 'application/octet-stream', - 'viewPolicy' => PhabricatorPolicies::POLICY_NOONE, - )); - - $this->setDataType(self::DATATYPE_FILE); - $this->setData($file->getPHID()); - - // NOTE: Because hunks don't have a PHID and we just load hunk data with - // the omnipotent viewer, we do not need to attach the file to anything. - - $result = $this->save(); - - $this->destroyData($old_type, $old_data); - - return $result; - } - - private function getRawData() { - if ($this->rawData === null) { - $type = $this->getDataType(); - $data = $this->getData(); - - switch ($type) { - case self::DATATYPE_TEXT: - // In this storage type, the changes are stored on the object. - $data = $data; - break; - case self::DATATYPE_FILE: - $data = $this->loadFileData(); - break; - default: - throw new Exception( - pht('Hunk has unsupported data type "%s"!', $type)); - } - - $format = $this->getDataFormat(); - switch ($format) { - case self::DATAFORMAT_RAW: - // In this format, the changes are stored as-is. - $data = $data; - break; - case self::DATAFORMAT_DEFLATED: - $data = PhabricatorCaches::inflateData($data); - break; - default: - throw new Exception( - pht('Hunk has unsupported data encoding "%s"!', $type)); - } - - $this->rawData = $data; - } - - return $this->rawData; - } - - private function loadFileData() { - if ($this->fileData === null) { - $type = $this->getDataType(); - if ($type !== self::DATATYPE_FILE) { - throw new Exception( - pht( - 'Unable to load file data for hunk with wrong data type ("%s").', - $type)); - } - - $file_phid = $this->getData(); - - $file = $this->loadRawFile($file_phid); - $data = $file->loadFileData(); - - $this->fileData = $data; - } - - return $this->fileData; - } - - private function loadRawFile($file_phid) { - $viewer = PhabricatorUser::getOmnipotentUser(); - - - $files = id(new PhabricatorFileQuery()) - ->setViewer($viewer) - ->withPHIDs(array($file_phid)) - ->execute(); - if (!$files) { - throw new Exception( - pht( - 'Failed to load file ("%s") with hunk data.', - $file_phid)); - } - - $file = head($files); - - return $file; - } - - - public function destroyObjectPermanently( - PhabricatorDestructionEngine $engine) { - - $type = $this->getDataType(); - $data = $this->getData(); - - $this->destroyData($type, $data, $engine); - - return parent::destroyObjectPermanently($engine); - } - - - private function destroyData( - $type, - $data, - PhabricatorDestructionEngine $engine = null) { - - if (!$engine) { - $engine = new PhabricatorDestructionEngine(); - } - - switch ($type) { - case self::DATATYPE_FILE: - $file = $this->loadRawFile($data); - $engine->destroyObject($file); - break; - } - } - -} diff --git a/src/applications/differential/storage/DifferentialRevision.php b/src/applications/differential/storage/DifferentialRevision.php index e8fdf7e514..6813c124d7 100644 --- a/src/applications/differential/storage/DifferentialRevision.php +++ b/src/applications/differential/storage/DifferentialRevision.php @@ -743,9 +743,10 @@ final class DifferentialRevision extends DifferentialDAO public function loadActiveBuilds(PhabricatorUser $viewer) { $diff = $this->getActiveDiff(); + // NOTE: We can't use `withContainerPHIDs()` here because the container + // update in Harbormaster is not synchronous. $buildables = id(new HarbormasterBuildableQuery()) ->setViewer($viewer) - ->withContainerPHIDs(array($this->getPHID())) ->withBuildablePHIDs(array($diff->getPHID())) ->withManualBuildables(false) ->execute(); diff --git a/src/applications/differential/storage/__tests__/DifferentialHunkTestCase.php b/src/applications/differential/storage/__tests__/DifferentialHunkTestCase.php index 49866ac038..e334891e6b 100644 --- a/src/applications/differential/storage/__tests__/DifferentialHunkTestCase.php +++ b/src/applications/differential/storage/__tests__/DifferentialHunkTestCase.php @@ -5,7 +5,7 @@ final class DifferentialHunkTestCase extends PhutilTestCase { public function testMakeChanges() { $root = dirname(__FILE__).'/hunk/'; - $hunk = new DifferentialModernHunk(); + $hunk = new DifferentialHunk(); $hunk->setChanges(Filesystem::readFile($root.'basic.diff')); $hunk->setOldOffset(1); $hunk->setNewOffset(11); @@ -23,7 +23,7 @@ final class DifferentialHunkTestCase extends PhutilTestCase { ); $this->assertEqual($added, $hunk->getAddedLines()); - $hunk = new DifferentialModernHunk(); + $hunk = new DifferentialHunk(); $hunk->setChanges(Filesystem::readFile($root.'newline.diff')); $hunk->setOldOffset(1); $hunk->setNewOffset(11); diff --git a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php index 231adc5bf8..e53426c8ba 100644 --- a/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php +++ b/src/applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php @@ -57,8 +57,19 @@ final class DifferentialRevisionPlanChangesTransaction protected function validateAction($object, PhabricatorUser $viewer) { if ($object->isDraft()) { - throw new Exception( - pht('You can not plan changes to a draft revision.')); + + // See PHI346. Until the "Draft" state fully unprototypes, allow drafts + // to be moved to "changes planned" via the API. This preserves the + // behavior of "arc diff --plan-changes". We still prevent this + // transition from the web UI. + // TODO: Remove this once drafts leave prototype. + + $editor = $this->getEditor(); + $type_web = PhabricatorWebContentSource::SOURCECONST; + if ($editor->getContentSource()->getSource() == $type_web) { + throw new Exception( + pht('You can not plan changes to a draft revision.')); + } } if ($object->isChangePlanned()) { diff --git a/src/applications/diffusion/engineextension/DiffusionDatasourceEngineExtension.php b/src/applications/diffusion/engineextension/DiffusionDatasourceEngineExtension.php new file mode 100644 index 0000000000..9678b627b1 --- /dev/null +++ b/src/applications/diffusion/engineextension/DiffusionDatasourceEngineExtension.php @@ -0,0 +1,82 @@ +getViewer(); + + // Send "r" to Diffusion. + if (preg_match('/^r\z/i', $query)) { + return '/diffusion/'; + } + + // Send "a" to the commit list ("Audit"). + if (preg_match('/^a\z/i', $query)) { + return '/diffusion/commit/'; + } + + // Send "r " to a search for a matching repository. + $matches = null; + if (preg_match('/^r\s+(.+)\z/i', $query, $matches)) { + $raw_query = $matches[1]; + + $engine = id(new PhabricatorRepository()) + ->newFerretEngine(); + + $compiler = id(new PhutilSearchQueryCompiler()) + ->setEnableFunctions(true); + + $raw_tokens = $compiler->newTokens($raw_query); + + $fulltext_tokens = array(); + foreach ($raw_tokens as $raw_token) { + $fulltext_token = id(new PhabricatorFulltextToken()) + ->setToken($raw_token); + $fulltext_tokens[] = $fulltext_token; + } + + $repositories = id(new PhabricatorRepositoryQuery()) + ->setViewer($viewer) + ->withFerretConstraint($engine, $fulltext_tokens) + ->execute(); + if (count($repositories) == 1) { + // Just one match, jump to repository. + return head($repositories)->getURI(); + } else { + // More than one match, jump to search. + return urisprintf( + '/diffusion/?order=relevance&query=%s#R', + $raw_query); + } + } + + // Send "s " to a symbol search. + $matches = null; + if (preg_match('/^s\s+(.+)\z/i', $query, $matches)) { + $symbol = $matches[1]; + + $parts = null; + if (preg_match('/(.*)(?:\\.|::|->)(.*)/', $symbol, $parts)) { + return urisprintf( + '/diffusion/symbol/%s/?jump=true&context=%s', + $parts[2], + $parts[1]); + } else { + return urisprintf( + '/diffusion/symbol/%s/?jump=true', + $symbol); + } + } + + return null; + } + +} diff --git a/src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php b/src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php deleted file mode 100644 index 80c705b4f8..0000000000 --- a/src/applications/diffusion/engineextension/DiffusionQuickSearchEngineExtension.php +++ /dev/null @@ -1,12 +0,0 @@ -getAdapter()->loadAffectedPackages(); + return mpull($packages, 'getPHID'); + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new PhabricatorOwnersPackageDatasource(); + } + +} diff --git a/src/applications/diffusion/herald/DiffusionPreCommitContentPackageOwnerHeraldField.php b/src/applications/diffusion/herald/DiffusionPreCommitContentPackageOwnerHeraldField.php new file mode 100644 index 0000000000..564ef36827 --- /dev/null +++ b/src/applications/diffusion/herald/DiffusionPreCommitContentPackageOwnerHeraldField.php @@ -0,0 +1,34 @@ +getAdapter()->loadAffectedPackages(); + if (!$packages) { + return array(); + } + + $owners = PhabricatorOwnersOwner::loadAllForPackages($packages); + return mpull($owners, 'getUserPHID'); + } + + protected function getHeraldFieldStandardType() { + return self::STANDARD_PHID_LIST; + } + + protected function getDatasource() { + return new PhabricatorProjectOrUserDatasource(); + } + +} diff --git a/src/applications/diffusion/herald/HeraldPreCommitAdapter.php b/src/applications/diffusion/herald/HeraldPreCommitAdapter.php index 3f1bc6bfd0..f63f647da6 100644 --- a/src/applications/diffusion/herald/HeraldPreCommitAdapter.php +++ b/src/applications/diffusion/herald/HeraldPreCommitAdapter.php @@ -87,4 +87,8 @@ abstract class HeraldPreCommitAdapter extends HeraldAdapter { $this->hookEngine->getRepository()->getProjectPHIDs()); } + public function supportsWebhooks() { + return false; + } + } diff --git a/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php b/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php index c93ba32345..cee5f97419 100644 --- a/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php +++ b/src/applications/diffusion/herald/HeraldPreCommitContentAdapter.php @@ -7,6 +7,8 @@ final class HeraldPreCommitContentAdapter extends HeraldPreCommitAdapter { private $fields; private $revision = false; + private $affectedPackages; + public function getAdapterContentName() { return pht('Commit Hook: Commit Content'); } @@ -223,4 +225,16 @@ final class HeraldPreCommitContentAdapter extends HeraldPreCommitAdapter { $this->getObject()->getRefNew()); } + public function loadAffectedPackages() { + if ($this->affectedPackages === null) { + $packages = PhabricatorOwnersPackage::loadAffectedPackages( + $this->getHookEngine()->getRepository(), + $this->getDiffContent('name')); + $this->affectedPackages = $packages; + } + + return $this->affectedPackages; + } + + } diff --git a/src/applications/diffusion/view/DiffusionView.php b/src/applications/diffusion/view/DiffusionView.php index eb43e49f3c..eb7f3eb72f 100644 --- a/src/applications/diffusion/view/DiffusionView.php +++ b/src/applications/diffusion/view/DiffusionView.php @@ -205,12 +205,11 @@ abstract class DiffusionView extends AphrontView { final protected function renderBuildable( HarbormasterBuildable $buildable, $type = null) { - $status = $buildable->getBuildableStatus(); Javelin::initBehavior('phabricator-tooltips'); - $icon = HarbormasterBuildable::getBuildableStatusIcon($status); - $color = HarbormasterBuildable::getBuildableStatusColor($status); - $name = HarbormasterBuildable::getBuildableStatusName($status); + $icon = $buildable->getStatusIcon(); + $color = $buildable->getStatusColor(); + $name = $buildable->getStatusDisplayName(); if ($type == 'button') { return id(new PHUIButtonView()) diff --git a/src/applications/draft/storage/PhabricatorVersionedDraft.php b/src/applications/draft/storage/PhabricatorVersionedDraft.php index 0daf13e8c5..58cdb67599 100644 --- a/src/applications/draft/storage/PhabricatorVersionedDraft.php +++ b/src/applications/draft/storage/PhabricatorVersionedDraft.php @@ -80,20 +80,17 @@ final class PhabricatorVersionedDraft extends PhabricatorDraftDAO { public static function purgeDrafts( $object_phid, - $viewer_phid, - $version) { + $viewer_phid) { $draft = new PhabricatorVersionedDraft(); $conn_w = $draft->establishConnection('w'); queryfx( $conn_w, - 'DELETE FROM %T WHERE objectPHID = %s AND authorPHID = %s - AND version <= %d', + 'DELETE FROM %T WHERE objectPHID = %s AND authorPHID = %s', $draft->getTableName(), $object_phid, - $viewer_phid, - $version); + $viewer_phid); } } diff --git a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php index a050a859e3..5acf63bf6b 100644 --- a/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockBlueprintImplementation.php @@ -396,16 +396,20 @@ abstract class DrydockBlueprintImplementation extends Phobject { } // For reasonable limits, actually check for an available slot. - $locks = DrydockSlotLock::loadLocks($blueprint_phid); - $locks = mpull($locks, null, 'getLockKey'); - $slots = range(0, $limit - 1); shuffle($slots); + $lock_names = array(); foreach ($slots as $slot) { - $slot_lock = "allocator({$blueprint_phid}).limit({$slot})"; - if (empty($locks[$slot_lock])) { - return $slot_lock; + $lock_names[] = "allocator({$blueprint_phid}).limit({$slot})"; + } + + $locks = DrydockSlotLock::loadHeldLocks($lock_names); + $locks = mpull($locks, null, 'getLockKey'); + + foreach ($lock_names as $lock_name) { + if (empty($locks[$lock_name])) { + return $lock_name; } } @@ -414,7 +418,8 @@ abstract class DrydockBlueprintImplementation extends Phobject { // lock will be free by the time we try to take it, but usually we'll just // fail to grab the lock, throw an appropriate lock exception, and get back // on the right path to retry later. - return $slot_lock; + + return $lock_name; } diff --git a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php index a5d61ec067..f45c4f2adb 100644 --- a/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php +++ b/src/applications/drydock/blueprint/DrydockWorkingCopyBlueprintImplementation.php @@ -206,7 +206,7 @@ final class DrydockWorkingCopyBlueprintImplementation } // Destroy the lease on the host. - $lease->releaseOnDestruction(); + $lease->setReleaseOnDestruction(true); if ($lease->isActive()) { // Destroy the working copy on disk. diff --git a/src/applications/drydock/constants/DrydockConstants.php b/src/applications/drydock/constants/DrydockConstants.php deleted file mode 100644 index 48d06329bf..0000000000 --- a/src/applications/drydock/constants/DrydockConstants.php +++ /dev/null @@ -1,3 +0,0 @@ -getStatusSpecification($key)); + } + public static function getStatusMap() { - return array( - self::STATUS_PENDING => pht('Pending'), - self::STATUS_ACQUIRED => pht('Acquired'), - self::STATUS_ACTIVE => pht('Active'), - self::STATUS_RELEASED => pht('Released'), - self::STATUS_BROKEN => pht('Broken'), - self::STATUS_DESTROYED => pht('Destroyed'), - ); + $map = id(new self())->getStatusSpecifications(); + return ipull($map, 'name', 'key'); } public static function getNameForStatus($status) { - $map = self::getStatusMap(); - return idx($map, $status, pht('Unknown')); + $map = id(new self())->getStatusSpecification($status); + return $map['name']; } public static function getAllStatuses() { - return array_keys(self::getStatusMap()); + return array_keys(id(new self())->getStatusSpecifications()); + } + + public function isActivating() { + return $this->getStatusProperty('isActivating'); + } + + public function isActive() { + return ($this->getKey() === self::STATUS_ACTIVE); + } + + public function canRelease() { + return $this->getStatusProperty('isReleasable'); + } + + public function canReceiveCommands() { + return $this->getStatusProperty('isCommandable'); + } + + protected function newStatusSpecifications() { + return array( + array( + 'key' => self::STATUS_PENDING, + 'name' => pht('Pending'), + 'icon' => 'fa-clock-o', + 'color' => 'blue', + 'isReleasable' => true, + 'isCommandable' => true, + 'isActivating' => true, + ), + array( + 'key' => self::STATUS_ACQUIRED, + 'name' => pht('Acquired'), + 'icon' => 'fa-refresh', + 'color' => 'blue', + 'isReleasable' => true, + 'isCommandable' => true, + 'isActivating' => true, + ), + array( + 'key' => self::STATUS_ACTIVE, + 'name' => pht('Active'), + 'icon' => 'fa-check', + 'color' => 'green', + 'isReleasable' => true, + 'isCommandable' => true, + 'isActivating' => false, + ), + array( + 'key' => self::STATUS_RELEASED, + 'name' => pht('Released'), + 'icon' => 'fa-circle-o', + 'color' => 'blue', + 'isReleasable' => false, + 'isCommandable' => false, + 'isActivating' => false, + ), + array( + 'key' => self::STATUS_BROKEN, + 'name' => pht('Broken'), + 'icon' => 'fa-times', + 'color' => 'indigo', + 'isReleasable' => true, + 'isCommandable' => true, + 'isActivating' => false, + ), + array( + 'key' => self::STATUS_DESTROYED, + 'name' => pht('Destroyed'), + 'icon' => 'fa-times', + 'color' => 'grey', + 'isReleasable' => false, + 'isCommandable' => false, + 'isActivating' => false, + ), + ); + } + + protected function newUnknownStatusSpecification($status) { + return array( + 'isReleasable' => false, + 'isCommandable' => false, + 'isActivating' => false, + ); } } diff --git a/src/applications/drydock/constants/DrydockResourceStatus.php b/src/applications/drydock/constants/DrydockResourceStatus.php index d8a860d6a0..197a220630 100644 --- a/src/applications/drydock/constants/DrydockResourceStatus.php +++ b/src/applications/drydock/constants/DrydockResourceStatus.php @@ -1,6 +1,7 @@ getStatusSpecification($key)); + } + public static function getStatusMap() { - return array( - self::STATUS_PENDING => pht('Pending'), - self::STATUS_ACTIVE => pht('Active'), - self::STATUS_RELEASED => pht('Released'), - self::STATUS_BROKEN => pht('Broken'), - self::STATUS_DESTROYED => pht('Destroyed'), - ); + $map = id(new self())->getStatusSpecifications(); + return ipull($map, 'name', 'key'); } public static function getNameForStatus($status) { - $map = self::getStatusMap(); - return idx($map, $status, pht('Unknown')); + $map = id(new self())->getStatusSpecification($status); + return $map['name']; } public static function getAllStatuses() { - return array_keys(self::getStatusMap()); + return array_keys(id(new self())->getStatusSpecifications()); + } + + public function isActive() { + return ($this->getKey() === self::STATUS_ACTIVE); + } + + public function canRelease() { + return $this->getStatusProperty('isReleasable'); + } + + public function canReceiveCommands() { + return $this->getStatusProperty('isCommandable'); + } + + protected function newStatusSpecifications() { + return array( + array( + 'key' => self::STATUS_PENDING, + 'name' => pht('Pending'), + 'icon' => 'fa-clock-o', + 'color' => 'blue', + 'isReleasable' => true, + 'isCommandable' => true, + ), + array( + 'key' => self::STATUS_ACTIVE, + 'name' => pht('Active'), + 'icon' => 'fa-check', + 'color' => 'green', + 'isReleasable' => true, + 'isCommandable' => true, + ), + array( + 'key' => self::STATUS_RELEASED, + 'name' => pht('Released'), + 'icon' => 'fa-circle-o', + 'color' => 'blue', + 'isReleasable' => false, + 'isCommandable' => false, + ), + array( + 'key' => self::STATUS_BROKEN, + 'name' => pht('Broken'), + 'icon' => 'fa-times', + 'color' => 'indigo', + 'isReleasable' => true, + 'isCommandable' => false, + ), + array( + 'key' => self::STATUS_DESTROYED, + 'name' => pht('Destroyed'), + 'icon' => 'fa-times', + 'color' => 'grey', + 'isReleasable' => false, + 'isCommandable' => false, + ), + ); + } + + protected function newUnknownStatusSpecification($status) { + return array( + 'isReleasable' => false, + 'isCommandable' => false, + ); } } diff --git a/src/applications/drydock/controller/DrydockLeaseViewController.php b/src/applications/drydock/controller/DrydockLeaseViewController.php index 1583d28df2..91a911277f 100644 --- a/src/applications/drydock/controller/DrydockLeaseViewController.php +++ b/src/applications/drydock/controller/DrydockLeaseViewController.php @@ -22,10 +22,19 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $header = id(new PHUIHeaderView()) ->setHeader($title) - ->setHeaderIcon('fa-link'); + ->setHeaderIcon('fa-link') + ->setStatus( + $lease->getStatusIcon(), + $lease->getStatusColor(), + $lease->getStatusDisplayName()); if ($lease->isReleasing()) { - $header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing')); + $header->addTag( + id(new PHUITagView()) + ->setType(PHUITagView::TYPE_SHADE) + ->setIcon('fa-exclamation-triangle') + ->setColor('red') + ->setName('Releasing')); } $curtain = $this->buildCurtain($lease); @@ -118,10 +127,6 @@ final class DrydockLeaseViewController extends DrydockLeaseController { $view = new PHUIPropertyListView(); - $view->addProperty( - pht('Status'), - DrydockLeaseStatus::getNameForStatus($lease->getStatus())); - $view->addProperty( pht('Resource Type'), $lease->getResourceType()); diff --git a/src/applications/drydock/controller/DrydockResourceViewController.php b/src/applications/drydock/controller/DrydockResourceViewController.php index c6771007ba..8718caa9e2 100644 --- a/src/applications/drydock/controller/DrydockResourceViewController.php +++ b/src/applications/drydock/controller/DrydockResourceViewController.php @@ -24,10 +24,19 @@ final class DrydockResourceViewController extends DrydockResourceController { ->setUser($viewer) ->setPolicyObject($resource) ->setHeader($title) - ->setHeaderIcon('fa-map'); + ->setHeaderIcon('fa-map') + ->setStatus( + $resource->getStatusIcon(), + $resource->getStatusColor(), + $resource->getStatusDisplayName()); if ($resource->isReleasing()) { - $header->setStatus('fa-exclamation-triangle', 'red', pht('Releasing')); + $header->addTag( + id(new PHUITagView()) + ->setType(PHUITagView::TYPE_SHADE) + ->setIcon('fa-exclamation-triangle') + ->setColor('red') + ->setName('Releasing')); } $curtain = $this->buildCurtain($resource); @@ -127,12 +136,6 @@ final class DrydockResourceViewController extends DrydockResourceController { $viewer = $this->getViewer(); $view = new PHUIPropertyListView(); - $status = $resource->getStatus(); - $status = DrydockResourceStatus::getNameForStatus($status); - - $view->addProperty( - pht('Status'), - $status); $until = $resource->getUntil(); if ($until) { diff --git a/src/applications/drydock/exception/DrydockAcquiredBrokenResourceException.php b/src/applications/drydock/exception/DrydockAcquiredBrokenResourceException.php new file mode 100644 index 0000000000..12b839d466 --- /dev/null +++ b/src/applications/drydock/exception/DrydockAcquiredBrokenResourceException.php @@ -0,0 +1,4 @@ +getViewer(); $authorizing_phid = idx($data, 'authorizingPHID'); return pht( 'The object which authorized this lease (%s) is not authorized to use '. 'any of the blueprints the lease lists. Approve the authorizations '. 'before using the lease.', - $viewer->renderHandle($authorizing_phid)->render()); + $this->renderHandle($authorizing_phid)); } } diff --git a/src/applications/drydock/logtype/DrydockLeaseReacquireLogType.php b/src/applications/drydock/logtype/DrydockLeaseReacquireLogType.php new file mode 100644 index 0000000000..13e759690c --- /dev/null +++ b/src/applications/drydock/logtype/DrydockLeaseReacquireLogType.php @@ -0,0 +1,26 @@ +getViewer(); - $resource_phids = idx($data, 'resourcePHIDs', array()); return pht( 'Reclaimed resource %s.', - $viewer->renderHandleList($resource_phids)->render()); + $this->renderHandleList($resource_phids)); } } diff --git a/src/applications/drydock/logtype/DrydockLeaseWaitingForResourcesLogType.php b/src/applications/drydock/logtype/DrydockLeaseWaitingForResourcesLogType.php index 46ab965b36..bb91acb030 100644 --- a/src/applications/drydock/logtype/DrydockLeaseWaitingForResourcesLogType.php +++ b/src/applications/drydock/logtype/DrydockLeaseWaitingForResourcesLogType.php @@ -13,13 +13,11 @@ final class DrydockLeaseWaitingForResourcesLogType extends DrydockLogType { } public function renderLog(array $data) { - $viewer = $this->getViewer(); - $blueprint_phids = idx($data, 'blueprintPHIDs', array()); return pht( 'Waiting for available resources from: %s.', - $viewer->renderHandleList($blueprint_phids)->render()); + $this->renderHandleList($blueprint_phids)); } } diff --git a/src/applications/drydock/logtype/DrydockLogType.php b/src/applications/drydock/logtype/DrydockLogType.php index 7faab42dfe..690ffc5670 100644 --- a/src/applications/drydock/logtype/DrydockLogType.php +++ b/src/applications/drydock/logtype/DrydockLogType.php @@ -4,17 +4,18 @@ abstract class DrydockLogType extends Phobject { private $viewer; private $log; + private $renderingMode = 'text'; abstract public function getLogTypeName(); abstract public function getLogTypeIcon(array $data); abstract public function renderLog(array $data); - public function setViewer(PhabricatorUser $viewer) { + final public function setViewer(PhabricatorUser $viewer) { $this->viewer = $viewer; return $this; } - public function getViewer() { + final public function getViewer() { return $this->viewer; } @@ -38,4 +39,36 @@ abstract class DrydockLogType extends Phobject { ->execute(); } + final public function renderLogForText($data) { + $this->renderingMode = 'text'; + return $this->renderLog($data); + } + + final public function renderLogForHTML($data) { + $this->renderingMode = 'html'; + return $this->renderLog($data); + } + + final protected function renderHandle($phid) { + $viewer = $this->getViewer(); + $handle = $viewer->renderHandle($phid); + + if ($this->renderingMode == 'html') { + return $handle->render(); + } else { + return $handle->setAsText(true)->render(); + } + } + + final protected function renderHandleList(array $phids) { + $viewer = $this->getViewer(); + $handle_list = $viewer->renderHandleList($phids); + + if ($this->renderingMode == 'html') { + return $handle_list->render(); + } else { + return $handle_list->setAsText(true)->render(); + } + } + } diff --git a/src/applications/drydock/logtype/DrydockResourceAllocationFailureLogType.php b/src/applications/drydock/logtype/DrydockResourceAllocationFailureLogType.php new file mode 100644 index 0000000000..f1e4cf4632 --- /dev/null +++ b/src/applications/drydock/logtype/DrydockResourceAllocationFailureLogType.php @@ -0,0 +1,26 @@ +getViewer(); $reclaimer_phid = idx($data, 'reclaimerPHID'); return pht( 'Resource reclaimed by %s.', - $viewer->renderHandle($reclaimer_phid)->render()); + $this->renderHandle($reclaimer_phid)); } } diff --git a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php index 4992833c50..75636dab09 100644 --- a/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php +++ b/src/applications/drydock/management/DrydockManagementLeaseWorkflow.php @@ -62,6 +62,10 @@ final class DrydockManagementLeaseWorkflow $drydock_phid = id(new PhabricatorDrydockApplication())->getPHID(); $lease->setAuthorizingPHID($drydock_phid); + if ($attributes) { + $lease->setAttributes($attributes); + } + // TODO: This is not hugely scalable, although this is a debugging workflow // so maybe it's fine. Do we even need `bin/drydock lease` in the long run? $all_blueprints = id(new DrydockBlueprintQuery()) @@ -76,27 +80,129 @@ final class DrydockManagementLeaseWorkflow } $lease->setAllowedBlueprintPHIDs($allowed_phids); - if ($attributes) { - $lease->setAttributes($attributes); - } - if ($until) { $lease->setUntil($until); } + // If something fatals or the user interrupts the process (for example, + // with "^C"), release the lease. We'll cancel this below, if the lease + // actually activates. + $lease->setReleaseOnDestruction(true); + + // TODO: This would probably be better handled with PhutilSignalRouter, + // but it currently doesn't route SIGINT. We're initializing it to setup + // SIGTERM handling and make eventual migration easier. + $router = PhutilSignalRouter::getRouter(); + pcntl_signal(SIGINT, array($this, 'didReceiveInterrupt')); + + $t_start = microtime(true); $lease->queueForActivation(); echo tsprintf( - "%s\n", + "%s\n\n __%s__\n\n%s\n", + pht('Queued lease for activation:'), + PhabricatorEnv::getProductionURI($lease->getURI()), pht('Waiting for daemons to activate lease...')); - $lease->waitUntilActive(); + $this->waitUntilActive($lease); + + // Now that we've survived activation and the lease is good, make it + // durable. + $lease->setReleaseOnDestruction(false); + $t_end = microtime(true); echo tsprintf( - "%s\n", - pht('Activated lease "%s".', $lease->getID())); + "%s\n\n %s\n\n%s\n", + pht( + 'Activation complete. This lease is permanent until manually '. + 'released with:'), + pht('$ ./bin/drydock release-lease --id %d', $lease->getID()), + pht( + 'Lease activated in %sms.', + new PhutilNumber((int)(($t_end - $t_start) * 1000)))); return 0; } + public function didReceiveInterrupt($signo) { + // Doing this makes us run destructors, particularly the "release on + // destruction" trigger on the lease. + exit(128 + $signo); + } + + private function waitUntilActive(DrydockLease $lease) { + $viewer = $this->getViewer(); + + $log_cursor = 0; + $log_types = DrydockLogType::getAllLogTypes(); + + $is_active = false; + while (!$is_active) { + $lease->reload(); + + // While we're waiting, show the user any logs which the daemons have + // generated to give them some clue about what's going on. + $logs = id(new DrydockLogQuery()) + ->setViewer($viewer) + ->withLeasePHIDs(array($lease->getPHID())) + ->setBeforeID($log_cursor) + ->execute(); + if ($logs) { + $logs = mpull($logs, null, 'getID'); + ksort($logs); + $log_cursor = last_key($logs); + } + + foreach ($logs as $log) { + $type_key = $log->getType(); + if (isset($log_types[$type_key])) { + $type_object = id(clone $log_types[$type_key]) + ->setLog($log) + ->setViewer($viewer); + + $log_data = $log->getData(); + + $type = $type_object->getLogTypeName(); + $data = $type_object->renderLogForText($log_data); + } else { + $type = pht('Unknown ("%s")', $type_key); + $data = null; + } + + echo tsprintf( + "<%s> %B\n", + $type, + $data); + } + + $status = $lease->getStatus(); + + switch ($status) { + case DrydockLeaseStatus::STATUS_ACTIVE: + $is_active = true; + break; + case DrydockLeaseStatus::STATUS_RELEASED: + throw new Exception(pht('Lease has already been released!')); + case DrydockLeaseStatus::STATUS_DESTROYED: + throw new Exception(pht('Lease has already been destroyed!')); + case DrydockLeaseStatus::STATUS_BROKEN: + throw new Exception(pht('Lease has been broken!')); + case DrydockLeaseStatus::STATUS_PENDING: + case DrydockLeaseStatus::STATUS_ACQUIRED: + break; + default: + throw new Exception( + pht( + 'Lease has unknown status "%s".', + $status)); + } + + if ($is_active) { + break; + } else { + sleep(1); + } + } + } + } diff --git a/src/applications/drydock/storage/DrydockLease.php b/src/applications/drydock/storage/DrydockLease.php index e60529afe4..4cee7a5f17 100644 --- a/src/applications/drydock/storage/DrydockLease.php +++ b/src/applications/drydock/storage/DrydockLease.php @@ -36,8 +36,8 @@ final class DrydockLease extends DrydockDAO * a lease, as you don't need to explicitly handle exceptions to properly * release the lease. */ - public function releaseOnDestruction() { - $this->releaseOnDestruction = true; + public function setReleaseOnDestruction($release) { + $this->releaseOnDestruction = $release; return $this; } @@ -175,57 +175,6 @@ final class DrydockLease extends DrydockDAO return $this; } - public function isActivating() { - switch ($this->getStatus()) { - case DrydockLeaseStatus::STATUS_PENDING: - case DrydockLeaseStatus::STATUS_ACQUIRED: - return true; - } - - return false; - } - - public function isActive() { - switch ($this->getStatus()) { - case DrydockLeaseStatus::STATUS_ACTIVE: - return true; - } - - return false; - } - - public function waitUntilActive() { - while (true) { - $lease = $this->reload(); - if (!$lease) { - throw new Exception(pht('Failed to reload lease.')); - } - - $status = $lease->getStatus(); - - switch ($status) { - case DrydockLeaseStatus::STATUS_ACTIVE: - return; - case DrydockLeaseStatus::STATUS_RELEASED: - throw new Exception(pht('Lease has already been released!')); - case DrydockLeaseStatus::STATUS_DESTROYED: - throw new Exception(pht('Lease has already been destroyed!')); - case DrydockLeaseStatus::STATUS_BROKEN: - throw new Exception(pht('Lease has been broken!')); - case DrydockLeaseStatus::STATUS_PENDING: - case DrydockLeaseStatus::STATUS_ACQUIRED: - break; - default: - throw new Exception( - pht( - 'Lease has unknown status "%s".', - $status)); - } - - sleep(1); - } - } - public function setActivateWhenAcquired($activate) { $this->activateWhenAcquired = true; return $this; @@ -264,30 +213,75 @@ final class DrydockLease extends DrydockDAO } } - $this->openTransaction(); + // Before we associate the lease with the resource, we lock the resource + // and reload it to make sure it is still pending or active. If we don't + // do this, the resource may have just been reclaimed. (Once we acquire + // the resource that stops it from being released, so we're nearly safe.) + + $resource_phid = $resource->getPHID(); + $hash = PhabricatorHash::digestForIndex($resource_phid); + $lock_key = 'drydock.resource:'.$hash; + $lock = PhabricatorGlobalLock::newLock($lock_key); try { - DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); - $this->slotLocks = array(); - } catch (DrydockSlotLockException $ex) { - $this->killTransaction(); - - $this->logEvent( - DrydockSlotLockFailureLogType::LOGCONST, - array( - 'locks' => $ex->getLockMap(), - )); - - throw $ex; + $lock->lock(15); + } catch (Exception $ex) { + throw new DrydockResourceLockException( + pht( + 'Failed to acquire lock for resource ("%s") while trying to '. + 'acquire lease ("%s").', + $resource->getPHID(), + $this->getPHID())); } - $this - ->setResourcePHID($resource->getPHID()) - ->attachResource($resource) - ->setStatus($new_status) - ->save(); + $resource->reload(); - $this->saveTransaction(); + if (($resource->getStatus() !== DrydockResourceStatus::STATUS_ACTIVE) && + ($resource->getStatus() !== DrydockResourceStatus::STATUS_PENDING)) { + throw new DrydockAcquiredBrokenResourceException( + pht( + 'Trying to acquire lease ("%s") on a resource ("%s") in the '. + 'wrong status ("%s").', + $this->getPHID(), + $resource->getPHID(), + $resource->getStatus())); + } + + $caught = null; + try { + $this->openTransaction(); + + try { + DrydockSlotLock::acquireLocks($this->getPHID(), $this->slotLocks); + $this->slotLocks = array(); + } catch (DrydockSlotLockException $ex) { + $this->killTransaction(); + + $this->logEvent( + DrydockSlotLockFailureLogType::LOGCONST, + array( + 'locks' => $ex->getLockMap(), + )); + + throw $ex; + } + + $this + ->setResourcePHID($resource->getPHID()) + ->attachResource($resource) + ->setStatus($new_status) + ->save(); + + $this->saveTransaction(); + } catch (Exception $ex) { + $caught = $ex; + } + + $lock->unlock(); + + if ($caught) { + throw $caught; + } $this->isAcquired = true; @@ -357,30 +351,6 @@ final class DrydockLease extends DrydockDAO return $this->isActivated; } - public function canRelease() { - if (!$this->getID()) { - return false; - } - - switch ($this->getStatus()) { - case DrydockLeaseStatus::STATUS_RELEASED: - case DrydockLeaseStatus::STATUS_DESTROYED: - return false; - default: - return true; - } - } - - public function canReceiveCommands() { - switch ($this->getStatus()) { - case DrydockLeaseStatus::STATUS_RELEASED: - case DrydockLeaseStatus::STATUS_DESTROYED: - return false; - default: - return true; - } - } - public function scheduleUpdate($epoch = null) { PhabricatorWorker::scheduleTask( 'DrydockLeaseUpdateWorker', @@ -468,6 +438,51 @@ final class DrydockLease extends DrydockDAO return $this; } + public function getURI() { + $id = $this->getID(); + return "/drydock/lease/{$id}/"; + } + + +/* -( Status )------------------------------------------------------------- */ + + + public function getStatusObject() { + return DrydockLeaseStatus::newStatusObject($this->getStatus()); + } + + public function getStatusIcon() { + return $this->getStatusObject()->getIcon(); + } + + public function getStatusColor() { + return $this->getStatusObject()->getColor(); + } + + public function getStatusDisplayName() { + return $this->getStatusObject()->getDisplayName(); + } + + public function isActivating() { + return $this->getStatusObject()->isActivating(); + } + + public function isActive() { + return $this->getStatusObject()->isActive(); + } + + public function canRelease() { + if (!$this->getID()) { + return false; + } + + return $this->getStatusObject()->canRelease(); + } + + public function canReceiveCommands() { + return $this->getStatusObject()->canReceiveCommands(); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/drydock/storage/DrydockResource.php b/src/applications/drydock/storage/DrydockResource.php index 3eceec8b77..8ec63cb097 100644 --- a/src/applications/drydock/storage/DrydockResource.php +++ b/src/applications/drydock/storage/DrydockResource.php @@ -235,16 +235,6 @@ final class DrydockResource extends DrydockDAO return $this->isActivated; } - public function canRelease() { - switch ($this->getStatus()) { - case DrydockResourceStatus::STATUS_RELEASED: - case DrydockResourceStatus::STATUS_DESTROYED: - return false; - default: - return true; - } - } - public function scheduleUpdate($epoch = null) { PhabricatorWorker::scheduleTask( 'DrydockResourceUpdateWorker', @@ -282,26 +272,6 @@ final class DrydockResource extends DrydockDAO } } - public function canReceiveCommands() { - switch ($this->getStatus()) { - case DrydockResourceStatus::STATUS_RELEASED: - case DrydockResourceStatus::STATUS_BROKEN: - case DrydockResourceStatus::STATUS_DESTROYED: - return false; - default: - return true; - } - } - - public function isActive() { - switch ($this->getStatus()) { - case DrydockResourceStatus::STATUS_ACTIVE: - return true; - } - - return false; - } - public function logEvent($type, array $data = array()) { $log = id(new DrydockLog()) ->setEpoch(PhabricatorTime::getNow()) @@ -315,6 +285,38 @@ final class DrydockResource extends DrydockDAO } +/* -( Status )------------------------------------------------------------- */ + + + public function getStatusObject() { + return DrydockResourceStatus::newStatusObject($this->getStatus()); + } + + public function getStatusIcon() { + return $this->getStatusObject()->getIcon(); + } + + public function getStatusColor() { + return $this->getStatusObject()->getColor(); + } + + public function getStatusDisplayName() { + return $this->getStatusObject()->getDisplayName(); + } + + public function canRelease() { + return $this->getStatusObject()->canRelease(); + } + + public function canReceiveCommands() { + return $this->getStatusObject()->canReceiveCommands(); + } + + public function isActive() { + return $this->getStatusObject()->isActive(); + } + + /* -( PhabricatorPolicyInterface )----------------------------------------- */ diff --git a/src/applications/drydock/view/DrydockLeaseListView.php b/src/applications/drydock/view/DrydockLeaseListView.php index 8c161b00ce..fb11457e86 100644 --- a/src/applications/drydock/view/DrydockLeaseListView.php +++ b/src/applications/drydock/view/DrydockLeaseListView.php @@ -26,20 +26,20 @@ final class DrydockLeaseListView extends AphrontView { if ($resource_phid) { $item->addAttribute( $viewer->renderHandle($resource_phid)); + } else { + $item->addAttribute( + pht( + 'Resource: %s', + $lease->getResourceType())); } - $status = DrydockLeaseStatus::getNameForStatus($lease->getStatus()); - $item->addAttribute($status); $item->setEpoch($lease->getDateCreated()); - // TODO: Tailor this for clarity. - if ($lease->isActivating()) { - $item->setStatusIcon('fa-dot-circle-o yellow'); - } else if ($lease->isActive()) { - $item->setStatusIcon('fa-dot-circle-o green'); - } else { - $item->setStatusIcon('fa-dot-circle-o red'); - } + $icon = $lease->getStatusIcon(); + $color = $lease->getStatusColor(); + $label = $lease->getStatusDisplayName(); + + $item->setStatusIcon("{$icon} {$color}", $label); $view->addItem($item); } diff --git a/src/applications/drydock/view/DrydockLogListView.php b/src/applications/drydock/view/DrydockLogListView.php index 845457f9bc..61fde7cee7 100644 --- a/src/applications/drydock/view/DrydockLogListView.php +++ b/src/applications/drydock/view/DrydockLogListView.php @@ -52,7 +52,8 @@ final class DrydockLogListView extends AphrontView { $type = $type_object->getLogTypeName(); $icon = $type_object->getLogTypeIcon($log_data); - $data = $type_object->renderLog($log_data); + $data = $type_object->renderLogForHTML($log_data); + $data = phutil_escape_html_newlines($data); } else { $type = pht('', $type_key); $data = null; diff --git a/src/applications/drydock/view/DrydockResourceListView.php b/src/applications/drydock/view/DrydockResourceListView.php index 739b464bdb..f2d105ecc8 100644 --- a/src/applications/drydock/view/DrydockResourceListView.php +++ b/src/applications/drydock/view/DrydockResourceListView.php @@ -23,23 +23,11 @@ final class DrydockResourceListView extends AphrontView { ->setObjectName(pht('Resource %d', $id)) ->setHeader($resource->getResourceName()); - $status = DrydockResourceStatus::getNameForStatus($resource->getStatus()); - $item->addAttribute($status); + $icon = $resource->getStatusIcon(); + $color = $resource->getStatusColor(); + $label = $resource->getStatusDisplayName(); - switch ($resource->getStatus()) { - case DrydockResourceStatus::STATUS_PENDING: - $item->setStatusIcon('fa-dot-circle-o yellow'); - break; - case DrydockResourceStatus::STATUS_ACTIVE: - $item->setStatusIcon('fa-dot-circle-o green'); - break; - case DrydockResourceStatus::STATUS_DESTROYED: - $item->setStatusIcon('fa-times-circle-o black'); - break; - default: - $item->setStatusIcon('fa-dot-circle-o red'); - break; - } + $item->setStatusIcon("{$icon} {$color}", $label); $view->addItem($item); } diff --git a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php index d73506f286..4a0cba8de2 100644 --- a/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockLeaseUpdateWorker.php @@ -43,6 +43,27 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { private function handleUpdate(DrydockLease $lease) { try { $this->updateLease($lease); + } catch (DrydockAcquiredBrokenResourceException $ex) { + // If this lease acquired a resource but failed to activate, we don't + // need to break the lease. We can throw it back in the pool and let + // it take another shot at acquiring a new resource. + + // Before we throw it back, release any locks the lease is holding. + DrydockSlotLock::releaseLocks($lease->getPHID()); + + $lease + ->setStatus(DrydockLeaseStatus::STATUS_PENDING) + ->setResourcePHID(null) + ->save(); + + $lease->logEvent( + DrydockLeaseReacquireLogType::LOGCONST, + array( + 'class' => get_class($ex), + 'message' => $ex->getMessage(), + )); + + $this->yieldLease($lease, $ex); } catch (Exception $ex) { if ($this->isTemporaryException($ex)) { $this->yieldLease($lease, $ex); @@ -216,17 +237,51 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { // this. break; } catch (Exception $ex) { + // This failure is not normally expected, so log it. It can be + // caused by something mundane and recoverable, however (see below + // for discussion). + + // We log to the blueprint separately from the log to the lease: + // the lease is not attached to a blueprint yet so the lease log + // will not show up on the blueprint; more than one blueprint may + // fail; and the lease is not really impacted (and won't log) if at + // least one blueprint actually works. + + $blueprint->logEvent( + DrydockResourceAllocationFailureLogType::LOGCONST, + array( + 'class' => get_class($ex), + 'message' => $ex->getMessage(), + )); + $exceptions[] = $ex; } } if (!$resources) { - throw new PhutilAggregateException( + // If one or more blueprints claimed that they would be able to + // allocate resources but none are actually able to allocate resources, + // log the failure and yield so we try again soon. + + // This can happen if some unexpected issue occurs during allocation + // (for example, a call to build a VM fails for some reason) or if we + // raced another allocator and the blueprint is now full. + + $ex = new PhutilAggregateException( pht( 'All blueprints failed to allocate a suitable new resource when '. - 'trying to allocate lease "%s".', + 'trying to allocate lease ("%s").', $lease->getPHID()), $exceptions); + + $lease->logEvent( + DrydockLeaseAllocationFailureLogType::LOGCONST, + array( + 'class' => get_class($ex), + 'message' => $ex->getMessage(), + )); + + throw new PhabricatorWorkerYieldException(15); } $resources = $this->removeUnacquirableResources($resources, $lease); @@ -247,23 +302,38 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { $resources = $this->rankResources($resources, $lease); $exceptions = array(); + $yields = array(); $allocated = false; foreach ($resources as $resource) { try { $this->acquireLease($resource, $lease); $allocated = true; break; + } catch (DrydockResourceLockException $ex) { + // We need to lock the resource to actually acquire it. If we aren't + // able to acquire the lock quickly enough, we can yield and try again + // later. + $yields[] = $ex; + } catch (DrydockAcquiredBrokenResourceException $ex) { + // If a resource was reclaimed or destroyed by the time we actually + // got around to acquiring it, we just got unlucky. We can yield and + // try again later. + $yields[] = $ex; } catch (Exception $ex) { $exceptions[] = $ex; } } if (!$allocated) { - throw new PhutilAggregateException( - pht( - 'Unable to acquire lease "%s" on any resource.', - $lease->getPHID()), - $exceptions); + if ($yields) { + throw new PhabricatorWorkerYieldException(15); + } else { + throw new PhutilAggregateException( + pht( + 'Unable to acquire lease "%s" on any resource.', + $lease->getPHID()), + $exceptions); + } } } @@ -715,9 +785,12 @@ final class DrydockLeaseUpdateWorker extends DrydockWorker { } if ($resource_status != DrydockResourceStatus::STATUS_ACTIVE) { - throw new Exception( + throw new DrydockAcquiredBrokenResourceException( pht( - 'Trying to activate lease on a dead resource (in status "%s").', + 'Trying to activate lease ("%s") on a resource ("%s") in '. + 'the wrong status ("%s").', + $lease->getPHID(), + $resource->getPHID(), $resource_status)); } diff --git a/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php b/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php index 9038b3b6d7..dfd603fd12 100644 --- a/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php +++ b/src/applications/drydock/worker/DrydockRepositoryOperationUpdateWorker.php @@ -62,7 +62,7 @@ final class DrydockRepositoryOperationUpdateWorker DrydockCommandInterface::INTERFACE_TYPE); // No matter what happens here, destroy the lease away once we're done. - $lease->releaseOnDestruction(true); + $lease->setReleaseOnDestruction(true); $operation->applyOperation($interface); diff --git a/src/applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php index 818b983538..cae06d47c7 100644 --- a/src/applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php +++ b/src/applications/harbormaster/conduit/HarbormasterQueryBuildablesConduitAPIMethod.php @@ -65,7 +65,7 @@ final class HarbormasterQueryBuildablesConduitAPIMethod $monogram = $buildable->getMonogram(); $status = $buildable->getBuildableStatus(); - $status_name = HarbormasterBuildable::getBuildableStatusName($status); + $status_name = $buildable->getStatusDisplayName(); $data[] = array( 'id' => $buildable->getID(), diff --git a/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php b/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php index dbf1f18cfa..fc83b7ab42 100644 --- a/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php +++ b/src/applications/harbormaster/conduit/HarbormasterSendMessageConduitAPIMethod.php @@ -245,7 +245,7 @@ final class HarbormasterSendMessageConduitAPIMethod } $save[] = HarbormasterBuildMessage::initializeNewMessage($viewer) - ->setBuildTargetPHID($build_target->getPHID()) + ->setReceiverPHID($build_target->getPHID()) ->setType($message_type); $build_target->openTransaction(); diff --git a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php index b0e3105d78..dc63b5fd95 100644 --- a/src/applications/harbormaster/constants/HarbormasterBuildStatus.php +++ b/src/applications/harbormaster/constants/HarbormasterBuildStatus.php @@ -2,51 +2,56 @@ final class HarbormasterBuildStatus extends Phobject { - /** - * Not currently being built. - */ const STATUS_INACTIVE = 'inactive'; - - /** - * Pending pick up by the Harbormaster daemon. - */ const STATUS_PENDING = 'pending'; - - /** - * Current building the buildable. - */ const STATUS_BUILDING = 'building'; - - /** - * The build has passed. - */ const STATUS_PASSED = 'passed'; - - /** - * The build has failed. - */ const STATUS_FAILED = 'failed'; - - /** - * The build has aborted. - */ const STATUS_ABORTED = 'aborted'; - - /** - * The build encountered an unexpected error. - */ const STATUS_ERROR = 'error'; - - /** - * The build has been paused. - */ const STATUS_PAUSED = 'paused'; - - /** - * The build has been deadlocked. - */ const STATUS_DEADLOCKED = 'deadlocked'; + private $key; + private $properties; + + public function __construct($key, array $properties) { + $this->key = $key; + $this->properties = $properties; + } + + public static function newBuildStatusObject($status) { + $spec = self::getBuildStatusSpec($status); + return new self($status, $spec); + } + + private function getProperty($key) { + if (!array_key_exists($key, $this->properties)) { + throw new Exception( + pht( + 'Attempting to access unknown build status property ("%s").', + $key)); + } + + return $this->properties[$key]; + } + + public function isBuilding() { + return $this->getProperty('isBuilding'); + } + + public function isPaused() { + return ($this->key === self::STATUS_PAUSED); + } + + public function isComplete() { + return $this->getProperty('isComplete'); + } + + public function isPassed() { + return ($this->key === self::STATUS_PASSED); + } + /** * Get a human readable name for a build status constant. @@ -56,7 +61,7 @@ final class HarbormasterBuildStatus extends Phobject { */ public static function getBuildStatusName($status) { $spec = self::getBuildStatusSpec($status); - return idx($spec, 'name', pht('Unknown ("%s")', $status)); + return $spec['name']; } public static function getBuildStatusMap() { @@ -66,17 +71,17 @@ final class HarbormasterBuildStatus extends Phobject { public static function getBuildStatusIcon($status) { $spec = self::getBuildStatusSpec($status); - return idx($spec, 'icon', 'fa-question-circle'); + return $spec['icon']; } public static function getBuildStatusColor($status) { $spec = self::getBuildStatusSpec($status); - return idx($spec, 'color', 'bluegrey'); + return $spec['color']; } public static function getBuildStatusANSIColor($status) { $spec = self::getBuildStatusSpec($status); - return idx($spec, 'color.ansi', 'magenta'); + return $spec['color.ansi']; } public static function getWaitingStatusConstants() { @@ -110,60 +115,90 @@ final class HarbormasterBuildStatus extends Phobject { 'icon' => 'fa-circle-o', 'color' => 'dark', 'color.ansi' => 'yellow', + 'isBuilding' => false, + 'isComplete' => false, ), self::STATUS_PENDING => array( 'name' => pht('Pending'), 'icon' => 'fa-circle-o', 'color' => 'blue', 'color.ansi' => 'yellow', + 'isBuilding' => true, + 'isComplete' => false, ), self::STATUS_BUILDING => array( 'name' => pht('Building'), 'icon' => 'fa-chevron-circle-right', 'color' => 'blue', 'color.ansi' => 'yellow', + 'isBuilding' => true, + 'isComplete' => false, ), self::STATUS_PASSED => array( 'name' => pht('Passed'), 'icon' => 'fa-check-circle', 'color' => 'green', 'color.ansi' => 'green', + 'isBuilding' => false, + 'isComplete' => true, ), self::STATUS_FAILED => array( 'name' => pht('Failed'), 'icon' => 'fa-times-circle', 'color' => 'red', 'color.ansi' => 'red', + 'isBuilding' => false, + 'isComplete' => true, ), self::STATUS_ABORTED => array( 'name' => pht('Aborted'), 'icon' => 'fa-minus-circle', 'color' => 'red', 'color.ansi' => 'red', + 'isBuilding' => false, + 'isComplete' => true, ), self::STATUS_ERROR => array( 'name' => pht('Unexpected Error'), 'icon' => 'fa-minus-circle', 'color' => 'red', 'color.ansi' => 'red', + 'isBuilding' => false, + 'isComplete' => true, ), self::STATUS_PAUSED => array( 'name' => pht('Paused'), 'icon' => 'fa-minus-circle', 'color' => 'dark', 'color.ansi' => 'yellow', + 'isBuilding' => false, + 'isComplete' => false, ), self::STATUS_DEADLOCKED => array( 'name' => pht('Deadlocked'), 'icon' => 'fa-exclamation-circle', 'color' => 'red', 'color.ansi' => 'red', + 'isBuilding' => false, + 'isComplete' => true, ), ); } private static function getBuildStatusSpec($status) { - return idx(self::getBuildStatusSpecMap(), $status, array()); + $map = self::getBuildStatusSpecMap(); + if (isset($map[$status])) { + return $map[$status]; + } + + return array( + 'name' => pht('Unknown ("%s")', $status), + 'icon' => 'fa-question-circle', + 'color' => 'bluegrey', + 'color.ansi' => 'magenta', + 'isBuilding' => false, + 'isComplete' => false, + ); } } diff --git a/src/applications/harbormaster/constants/HarbormasterBuildableStatus.php b/src/applications/harbormaster/constants/HarbormasterBuildableStatus.php new file mode 100644 index 0000000000..7ba2379f30 --- /dev/null +++ b/src/applications/harbormaster/constants/HarbormasterBuildableStatus.php @@ -0,0 +1,92 @@ +key = $key; + $this->properties = $properties; + } + + public static function newBuildableStatusObject($status) { + $spec = self::getSpecification($status); + return new self($status, $spec); + } + + private function getProperty($key) { + if (!array_key_exists($key, $this->properties)) { + throw new Exception( + pht( + 'Attempting to access unknown buildable status property ("%s").', + $key)); + } + + return $this->properties[$key]; + } + + public function getIcon() { + return $this->getProperty('icon'); + } + + public function getDisplayName() { + return $this->getProperty('name'); + } + + public function getColor() { + return $this->getProperty('color'); + } + + public function isPreparing() { + return ($this->key === self::STATUS_PREPARING); + } + + public static function getOptionMap() { + return ipull(self::getSpecifications(), 'name'); + } + + private static function getSpecifications() { + return array( + self::STATUS_PREPARING => array( + 'name' => pht('Preparing'), + 'color' => 'blue', + 'icon' => 'fa-hourglass-o', + ), + self::STATUS_BUILDING => array( + 'name' => pht('Building'), + 'color' => 'blue', + 'icon' => 'fa-chevron-circle-right', + ), + self::STATUS_PASSED => array( + 'name' => pht('Passed'), + 'color' => 'green', + 'icon' => 'fa-check-circle', + ), + self::STATUS_FAILED => array( + 'name' => pht('Failed'), + 'color' => 'red', + 'icon' => 'fa-times-circle', + ), + ); + } + + private static function getSpecification($status) { + $map = self::getSpecifications(); + if (isset($map[$status])) { + return $map[$status]; + } + + return array( + 'name' => pht('Unknown ("%s")', $status), + 'icon' => 'fa-question-circle', + 'color' => 'bluegrey', + ); + } + +} diff --git a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php index 5b7171a200..4507139d47 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildViewController.php @@ -65,9 +65,9 @@ final class HarbormasterBuildViewController if ($build_targets) { $messages = id(new HarbormasterBuildMessageQuery()) ->setViewer($viewer) - ->withBuildTargetPHIDs(mpull($build_targets, 'getPHID')) + ->withReceiverPHIDs(mpull($build_targets, 'getPHID')) ->execute(); - $messages = mgroup($messages, 'getBuildTargetPHID'); + $messages = mgroup($messages, 'getReceiverPHID'); } else { $messages = array(); } diff --git a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php index 6a47dae429..58e5cf8c49 100644 --- a/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php +++ b/src/applications/harbormaster/controller/HarbormasterBuildableViewController.php @@ -36,6 +36,10 @@ final class HarbormasterBuildableViewController ->setHeader($title) ->setUser($viewer) ->setPolicyObject($buildable) + ->setStatus( + $buildable->getStatusIcon(), + $buildable->getStatusColor(), + $buildable->getStatusDisplayName()) ->setHeaderIcon('fa-recycle'); $timeline = $this->buildTransactionTimeline( diff --git a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php index 85bdbd8b49..fd227ee554 100644 --- a/src/applications/harbormaster/controller/HarbormasterPlanRunController.php +++ b/src/applications/harbormaster/controller/HarbormasterPlanRunController.php @@ -59,6 +59,12 @@ final class HarbormasterPlanRunController extends HarbormasterPlanController { if (!$errors) { $buildable->save(); + + $buildable->sendMessage( + $viewer, + HarbormasterMessageType::BUILDABLE_BUILD, + false); + $buildable->applyPlan($plan, array(), $viewer->getPHID()); $buildable_uri = '/B'.$buildable->getID(); diff --git a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php index 0616e77b4e..454fec7f37 100644 --- a/src/applications/harbormaster/engine/HarbormasterBuildEngine.php +++ b/src/applications/harbormaster/engine/HarbormasterBuildEngine.php @@ -382,12 +382,12 @@ final class HarbormasterBuildEngine extends Phobject { $messages = id(new HarbormasterBuildMessageQuery()) ->setViewer($this->getViewer()) - ->withBuildTargetPHIDs(array_keys($waiting_targets)) + ->withReceiverPHIDs(array_keys($waiting_targets)) ->withConsumed(false) ->execute(); foreach ($messages as $message) { - $target = $waiting_targets[$message->getBuildTargetPHID()]; + $target = $waiting_targets[$message->getReceiverPHID()]; switch ($message->getType()) { case HarbormasterMessageType::MESSAGE_PASS: @@ -428,7 +428,7 @@ final class HarbormasterBuildEngine extends Phobject { * @param HarbormasterBuild The buildable to update. * @return void */ - private function updateBuildable(HarbormasterBuildable $buildable) { + public function updateBuildable(HarbormasterBuildable $buildable) { $viewer = $this->getViewer(); $lock_key = 'harbormaster.buildable:'.$buildable->getID(); @@ -440,39 +440,96 @@ final class HarbormasterBuildEngine extends Phobject { ->needBuilds(true) ->executeOne(); - $all_pass = true; - $any_fail = false; - foreach ($buildable->getBuilds() as $build) { - if ($build->getBuildStatus() != HarbormasterBuildStatus::STATUS_PASSED) { - $all_pass = false; - } - if (in_array($build->getBuildStatus(), array( - HarbormasterBuildStatus::STATUS_FAILED, - HarbormasterBuildStatus::STATUS_ERROR, - HarbormasterBuildStatus::STATUS_DEADLOCKED, - ))) { + $messages = id(new HarbormasterBuildMessageQuery()) + ->setViewer($viewer) + ->withReceiverPHIDs(array($buildable->getPHID())) + ->withConsumed(false) + ->execute(); - $any_fail = true; + $done_preparing = false; + $update_container = false; + foreach ($messages as $message) { + switch ($message->getType()) { + case HarbormasterMessageType::BUILDABLE_BUILD: + $done_preparing = true; + break; + case HarbormasterMessageType::BUILDABLE_CONTAINER: + $update_container = true; + break; + default: + break; + } + + $message + ->setIsConsumed(true) + ->save(); + } + + // If we received a "build" command, all builds are scheduled and we can + // move out of "preparing" into "building". + if ($done_preparing) { + if ($buildable->isPreparing()) { + $buildable + ->setBuildableStatus(HarbormasterBuildableStatus::STATUS_BUILDING) + ->save(); } } - if ($any_fail) { - $new_status = HarbormasterBuildable::STATUS_FAILED; - } else if ($all_pass) { - $new_status = HarbormasterBuildable::STATUS_PASSED; - } else { - $new_status = HarbormasterBuildable::STATUS_BUILDING; + // If we've been informed that the container for the buildable has + // changed, update it. + if ($update_container) { + $object = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withPHIDs(array($buildable->getBuildablePHID())) + ->executeOne(); + if ($object) { + $buildable + ->setContainerPHID($object->getHarbormasterContainerPHID()) + ->save(); + } } - $old_status = $buildable->getBuildableStatus(); - $did_update = ($old_status != $new_status); - if ($did_update) { - $buildable->setBuildableStatus($new_status); - $buildable->save(); + // Don't update the buildable status if we're still preparing builds: more + // builds may still be scheduled shortly, so even if every build we know + // about so far has passed, that doesn't mean the buildable has actually + // passed everything it needs to. + + if (!$buildable->isPreparing()) { + $all_pass = true; + $any_fail = false; + foreach ($buildable->getBuilds() as $build) { + if (!$build->isPassed()) { + $all_pass = false; + } + + if ($build->isComplete() && !$build->isPassed()) { + $any_fail = true; + } + } + + if ($any_fail) { + $new_status = HarbormasterBuildableStatus::STATUS_FAILED; + } else if ($all_pass) { + $new_status = HarbormasterBuildableStatus::STATUS_PASSED; + } else { + $new_status = HarbormasterBuildableStatus::STATUS_BUILDING; + } + + $old_status = $buildable->getBuildableStatus(); + $did_update = ($old_status != $new_status); + if ($did_update) { + $buildable->setBuildableStatus($new_status); + $buildable->save(); + } } $lock->unlock(); + // Don't publish anything if we're still preparing builds. + if ($buildable->isPreparing()) { + return; + } + // If we changed the buildable status, try to post a transaction to the // object about it. We can safely do this outside of the locked region. @@ -481,9 +538,10 @@ final class HarbormasterBuildEngine extends Phobject { // can look at the results themselves, and other users generally don't // care about the outcome. - $should_publish = $did_update && - $new_status != HarbormasterBuildable::STATUS_BUILDING && - !$buildable->getIsManualBuildable(); + $should_publish = + ($did_update) && + ($new_status != HarbormasterBuildableStatus::STATUS_BUILDING) && + (!$buildable->getIsManualBuildable()); if (!$should_publish) { return; diff --git a/src/applications/harbormaster/engine/HarbormasterMessageType.php b/src/applications/harbormaster/engine/HarbormasterMessageType.php index 5900817c69..135fb23ffc 100644 --- a/src/applications/harbormaster/engine/HarbormasterMessageType.php +++ b/src/applications/harbormaster/engine/HarbormasterMessageType.php @@ -6,6 +6,9 @@ final class HarbormasterMessageType extends Phobject { const MESSAGE_FAIL = 'fail'; const MESSAGE_WORK = 'work'; + const BUILDABLE_BUILD = 'build'; + const BUILDABLE_CONTAINER = 'container'; + public static function getAllMessages() { return array_keys(self::getMessageSpecifications()); } diff --git a/src/applications/harbormaster/event/HarbormasterUIEventListener.php b/src/applications/harbormaster/event/HarbormasterUIEventListener.php index 219f556b3a..1227fbbe31 100644 --- a/src/applications/harbormaster/event/HarbormasterUIEventListener.php +++ b/src/applications/harbormaster/event/HarbormasterUIEventListener.php @@ -87,13 +87,9 @@ final class HarbormasterUIEventListener $status_view = new PHUIStatusListView(); - $buildable_status = $buildable->getBuildableStatus(); - $buildable_icon = HarbormasterBuildable::getBuildableStatusIcon( - $buildable_status); - $buildable_color = HarbormasterBuildable::getBuildableStatusColor( - $buildable_status); - $buildable_name = HarbormasterBuildable::getBuildableStatusName( - $buildable_status); + $buildable_icon = $buildable->getStatusIcon(); + $buildable_color = $buildable->getStatusColor(); + $buildable_name = $buildable->getStatusDisplayName(); $target = phutil_tag( 'a', diff --git a/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php b/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php index 6fd3cf2ffd..2efd443a91 100644 --- a/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php +++ b/src/applications/harbormaster/management/HarbormasterManagementBuildWorkflow.php @@ -83,6 +83,11 @@ final class HarbormasterManagementBuildWorkflow ->setContainerPHID($buildable->getHarbormasterContainerPHID()) ->save(); + $buildable->sendMessage( + $viewer, + HarbormasterMessageType::BUILDABLE_BUILD, + false); + $console->writeOut( "%s\n", pht( diff --git a/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php b/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php index 749fdd76d4..134c68ce04 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php +++ b/src/applications/harbormaster/query/HarbormasterBuildMessageQuery.php @@ -4,7 +4,7 @@ final class HarbormasterBuildMessageQuery extends PhabricatorCursorPagedPolicyAwareQuery { private $ids; - private $buildTargetPHIDs; + private $receiverPHIDs; private $consumed; public function withIDs(array $ids) { @@ -12,8 +12,8 @@ final class HarbormasterBuildMessageQuery return $this; } - public function withBuildTargetPHIDs(array $phids) { - $this->buildTargetPHIDs = $phids; + public function withReceiverPHIDs(array $phids) { + $this->receiverPHIDs = $phids; return $this; } @@ -22,73 +22,67 @@ final class HarbormasterBuildMessageQuery return $this; } + public function newResultObject() { + return new HarbormasterBuildMessage(); + } + protected function loadPage() { - $table = new HarbormasterBuildMessage(); - $conn_r = $table->establishConnection('r'); - - $data = queryfx_all( - $conn_r, - 'SELECT * FROM %T %Q %Q %Q', - $table->getTableName(), - $this->buildWhereClause($conn_r), - $this->buildOrderClause($conn_r), - $this->buildLimitClause($conn_r)); - - return $table->loadAllFromArray($data); + return $this->loadStandardPage($this->newResultObject()); } protected function willFilterPage(array $page) { - $build_target_phids = array_filter(mpull($page, 'getBuildTargetPHID')); - if ($build_target_phids) { - $build_targets = id(new PhabricatorObjectQuery()) + $receiver_phids = array_filter(mpull($page, 'getReceiverPHID')); + if ($receiver_phids) { + $receivers = id(new PhabricatorObjectQuery()) ->setViewer($this->getViewer()) - ->withPHIDs($build_target_phids) + ->withPHIDs($receiver_phids) ->setParentQuery($this) ->execute(); - $build_targets = mpull($build_targets, null, 'getPHID'); + $receivers = mpull($receivers, null, 'getPHID'); } else { - $build_targets = array(); + $receivers = array(); } foreach ($page as $key => $message) { - $build_target_phid = $message->getBuildTargetPHID(); - if (empty($build_targets[$build_target_phid])) { + $receiver_phid = $message->getReceiverPHID(); + + if (empty($receivers[$receiver_phid])) { unset($page[$key]); + $this->didRejectResult($message); continue; } - $message->attachBuildTarget($build_targets[$build_target_phid]); + + $message->attachReceiver($receivers[$receiver_phid]); } return $page; } - protected function buildWhereClause(AphrontDatabaseConnection $conn_r) { - $where = array(); + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); - if ($this->ids) { + if ($this->ids !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'id IN (%Ld)', $this->ids); } - if ($this->buildTargetPHIDs) { + if ($this->receiverPHIDs !== null) { $where[] = qsprintf( - $conn_r, - 'buildTargetPHID IN (%Ls)', - $this->buildTargetPHIDs); + $conn, + 'receiverPHID IN (%Ls)', + $this->receiverPHIDs); } if ($this->consumed !== null) { $where[] = qsprintf( - $conn_r, + $conn, 'isConsumed = %d', (int)$this->consumed); } - $where[] = $this->buildPagingClause($conn_r); - - return $this->formatWhereClause($where); + return $where; } public function getQueryApplicationClass() { diff --git a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php index cfff27b1aa..36f5dc89c2 100644 --- a/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php +++ b/src/applications/harbormaster/query/HarbormasterBuildableSearchEngine.php @@ -33,7 +33,7 @@ final class HarbormasterBuildableSearchEngine id(new PhabricatorSearchCheckboxesField()) ->setKey('statuses') ->setLabel(pht('Statuses')) - ->setOptions(HarbormasterBuildable::getBuildStatusMap()) + ->setOptions(HarbormasterBuildableStatus::getOptionMap()) ->setDescription(pht('Search for builds by buildable status.')), id(new PhabricatorSearchThreeStateField()) ->setLabel(pht('Manual')) @@ -169,11 +169,9 @@ final class HarbormasterBuildableSearchEngine $item->addIcon('fa-wrench grey', pht('Manual')); } - $status = $buildable->getBuildableStatus(); - - $status_icon = HarbormasterBuildable::getBuildableStatusIcon($status); - $status_color = HarbormasterBuildable::getBuildableStatusColor($status); - $status_label = HarbormasterBuildable::getBuildableStatusName($status); + $status_icon = $buildable->getStatusIcon(); + $status_color = $buildable->getStatusColor(); + $status_label = $buildable->getStatusDisplayName(); $item->setStatusIcon("{$status_icon} {$status_color}", $status_label); diff --git a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php index ca6f30bd36..47af992630 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildStepImplementation.php @@ -295,6 +295,18 @@ abstract class HarbormasterBuildStepImplementation extends Phobject { ->append($body); } + protected function logSilencedCall( + HarbormasterBuild $build, + HarbormasterBuildTarget $build_target, + $label) { + + $build_target + ->newLog($label, 'silenced') + ->append( + pht( + 'Declining to make service call because `phabricator.silent` is '. + 'enabled in configuration.')); + } /* -( Automatic Targets )-------------------------------------------------- */ diff --git a/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php index 3ecd6553da..89d4002eef 100644 --- a/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterBuildkiteBuildStepImplementation.php @@ -72,6 +72,11 @@ EOTEXT HarbormasterBuildTarget $build_target) { $viewer = PhabricatorUser::getOmnipotentUser(); + if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { + $this->logSilencedCall($build, $build_target, pht('Buildkite')); + throw new HarbormasterBuildFailureException(); + } + $buildable = $build->getBuildable(); $object = $buildable->getBuildableObject(); diff --git a/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php index e74e25a395..f0104779ff 100644 --- a/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterCircleCIBuildStepImplementation.php @@ -84,6 +84,11 @@ EOTEXT HarbormasterBuildTarget $build_target) { $viewer = PhabricatorUser::getOmnipotentUser(); + if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { + $this->logSilencedCall($build, $build_target, pht('CircleCI')); + throw new HarbormasterBuildFailureException(); + } + $buildable = $build->getBuildable(); $object = $buildable->getBuildableObject(); diff --git a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php index 016f29642e..59866d3b73 100644 --- a/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php +++ b/src/applications/harbormaster/step/HarbormasterHTTPRequestBuildStepImplementation.php @@ -43,6 +43,12 @@ final class HarbormasterHTTPRequestBuildStepImplementation HarbormasterBuildTarget $build_target) { $viewer = PhabricatorUser::getOmnipotentUser(); + + if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { + $this->logSilencedCall($build, $build_target, pht('HTTP Request')); + throw new HarbormasterBuildFailureException(); + } + $settings = $this->getSettings(); $variables = $build_target->getVariables(); diff --git a/src/applications/harbormaster/storage/HarbormasterBuildMessage.php b/src/applications/harbormaster/storage/HarbormasterBuildMessage.php index 20d138ad02..1066a93610 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildMessage.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildMessage.php @@ -10,11 +10,11 @@ final class HarbormasterBuildMessage extends HarbormasterDAO implements PhabricatorPolicyInterface { protected $authorPHID; - protected $buildTargetPHID; + protected $receiverPHID; protected $type; protected $isConsumed; - private $buildTarget = self::ATTACHABLE; + private $receiver = self::ATTACHABLE; public static function initializeNewMessage(PhabricatorUser $actor) { $actor_phid = $actor->getPHID(); @@ -34,19 +34,19 @@ final class HarbormasterBuildMessage extends HarbormasterDAO 'isConsumed' => 'bool', ), self::CONFIG_KEY_SCHEMA => array( - 'key_buildtarget' => array( - 'columns' => array('buildTargetPHID'), + 'key_receiver' => array( + 'columns' => array('receiverPHID'), ), ), ) + parent::getConfiguration(); } - public function getBuildTarget() { - return $this->assertAttached($this->buildTarget); + public function getReceiver() { + return $this->assertAttached($this->receiver); } - public function attachBuildTarget(HarbormasterBuildTarget $target) { - $this->buildTarget = $target; + public function attachReceiver($receiver) { + $this->receiver = $receiver; return $this; } @@ -61,17 +61,17 @@ final class HarbormasterBuildMessage extends HarbormasterDAO } public function getPolicy($capability) { - return $this->getBuildTarget()->getPolicy($capability); + return $this->getReceiver()->getPolicy($capability); } public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { - return $this->getBuildTarget()->hasAutomaticCapability( + return $this->getReceiver()->hasAutomaticCapability( $capability, $viewer); } public function describeAutomaticCapability($capability) { - return pht('Build messages have the same policies as their targets.'); + return pht('Build messages have the same policies as their receivers.'); } } diff --git a/src/applications/harbormaster/storage/HarbormasterBuildable.php b/src/applications/harbormaster/storage/HarbormasterBuildable.php index 9761fb287f..5de2159e8d 100644 --- a/src/applications/harbormaster/storage/HarbormasterBuildable.php +++ b/src/applications/harbormaster/storage/HarbormasterBuildable.php @@ -15,53 +15,10 @@ final class HarbormasterBuildable extends HarbormasterDAO private $containerObject = self::ATTACHABLE; private $builds = self::ATTACHABLE; - const STATUS_BUILDING = 'building'; - const STATUS_PASSED = 'passed'; - const STATUS_FAILED = 'failed'; - - public static function getBuildableStatusName($status) { - $map = self::getBuildStatusMap(); - return idx($map, $status, pht('Unknown ("%s")', $status)); - } - - public static function getBuildStatusMap() { - return array( - self::STATUS_BUILDING => pht('Building'), - self::STATUS_PASSED => pht('Passed'), - self::STATUS_FAILED => pht('Failed'), - ); - } - - public static function getBuildableStatusIcon($status) { - switch ($status) { - case self::STATUS_BUILDING: - return PHUIStatusItemView::ICON_RIGHT; - case self::STATUS_PASSED: - return PHUIStatusItemView::ICON_ACCEPT; - case self::STATUS_FAILED: - return PHUIStatusItemView::ICON_REJECT; - default: - return PHUIStatusItemView::ICON_QUESTION; - } - } - - public static function getBuildableStatusColor($status) { - switch ($status) { - case self::STATUS_BUILDING: - return 'blue'; - case self::STATUS_PASSED: - return 'green'; - case self::STATUS_FAILED: - return 'red'; - default: - return 'bluegrey'; - } - } - public static function initializeNewBuildable(PhabricatorUser $actor) { return id(new HarbormasterBuildable()) ->setIsManualBuildable(0) - ->setBuildableStatus(self::STATUS_BUILDING); + ->setBuildableStatus(HarbormasterBuildableStatus::STATUS_PREPARING); } public function getMonogram() { @@ -250,6 +207,59 @@ final class HarbormasterBuildable extends HarbormasterDAO } +/* -( Status )------------------------------------------------------------- */ + + + public function getBuildableStatusObject() { + $status = $this->getBuildableStatus(); + return HarbormasterBuildableStatus::newBuildableStatusObject($status); + } + + public function getStatusIcon() { + return $this->getBuildableStatusObject()->getIcon(); + } + + public function getStatusDisplayName() { + return $this->getBuildableStatusObject()->getDisplayName(); + } + + public function getStatusColor() { + return $this->getBuildableStatusObject()->getColor(); + } + + public function isPreparing() { + return $this->getBuildableStatusObject()->isPreparing(); + } + + +/* -( Messages )----------------------------------------------------------- */ + + + public function sendMessage( + PhabricatorUser $viewer, + $message_type, + $queue_update) { + + $message = HarbormasterBuildMessage::initializeNewMessage($viewer) + ->setReceiverPHID($this->getPHID()) + ->setType($message_type) + ->save(); + + if ($queue_update) { + PhabricatorWorker::scheduleTask( + 'HarbormasterBuildWorker', + array( + 'buildablePHID' => $this->getPHID(), + ), + array( + 'objectPHID' => $this->getPHID(), + )); + } + + return $message; + } + + /* -( PhabricatorApplicationTransactionInterface )------------------------- */ diff --git a/src/applications/harbormaster/storage/build/HarbormasterBuild.php b/src/applications/harbormaster/storage/build/HarbormasterBuild.php index 92d4293913..d43b19d71f 100644 --- a/src/applications/harbormaster/storage/build/HarbormasterBuild.php +++ b/src/applications/harbormaster/storage/build/HarbormasterBuild.php @@ -108,9 +108,7 @@ final class HarbormasterBuild extends HarbormasterDAO } public function isBuilding() { - return - $this->getBuildStatus() === HarbormasterBuildStatus::STATUS_PENDING || - $this->getBuildStatus() === HarbormasterBuildStatus::STATUS_BUILDING; + return $this->getBuildStatusObject()->isBuilding(); } public function isAutobuild() { @@ -173,13 +171,15 @@ final class HarbormasterBuild extends HarbormasterDAO } public function isComplete() { - return in_array( - $this->getBuildStatus(), - HarbormasterBuildStatus::getCompletedStatusConstants()); + return $this->getBuildStatusObject()->isComplete(); } public function isPaused() { - return ($this->getBuildStatus() == HarbormasterBuildStatus::STATUS_PAUSED); + return $this->getBuildStatusObject()->isPaused(); + } + + public function isPassed() { + return $this->getBuildStatusObject()->isPassed(); } public function getURI() { @@ -187,6 +187,11 @@ final class HarbormasterBuild extends HarbormasterDAO return "/harbormaster/build/{$id}/"; } + protected function getBuildStatusObject() { + $status_key = $this->getBuildStatus(); + return HarbormasterBuildStatus::newBuildStatusObject($status_key); + } + /* -( Build Commands )----------------------------------------------------- */ diff --git a/src/applications/harbormaster/worker/HarbormasterBuildWorker.php b/src/applications/harbormaster/worker/HarbormasterBuildWorker.php index 67eb6b3e78..9ce9d50f13 100644 --- a/src/applications/harbormaster/worker/HarbormasterBuildWorker.php +++ b/src/applications/harbormaster/worker/HarbormasterBuildWorker.php @@ -17,12 +17,21 @@ final class HarbormasterBuildWorker extends HarbormasterWorker { protected function doWork() { $viewer = $this->getViewer(); - $build = $this->loadBuild(); - id(new HarbormasterBuildEngine()) - ->setViewer($viewer) - ->setBuild($build) - ->continueBuild(); + $engine = id(new HarbormasterBuildEngine()) + ->setViewer($viewer); + + $data = $this->getTaskData(); + $build_id = idx($data, 'buildID'); + + if ($build_id) { + $build = $this->loadBuild(); + $engine->setBuild($build); + $engine->continueBuild(); + } else { + $buildable = $this->loadBuildable(); + $engine->updateBuildable($buildable); + } } private function loadBuild() { @@ -42,4 +51,21 @@ final class HarbormasterBuildWorker extends HarbormasterWorker { return $build; } + private function loadBuildable() { + $data = $this->getTaskData(); + $phid = idx($data, 'buildablePHID'); + + $viewer = $this->getViewer(); + $buildable = id(new HarbormasterBuildableQuery()) + ->setViewer($viewer) + ->withPHIDs(array($phid)) + ->executeOne(); + if (!$buildable) { + throw new PhabricatorWorkerPermanentFailureException( + pht('Invalid buildable PHID "%s".', $phid)); + } + + return $buildable; + } + } diff --git a/src/applications/herald/action/HeraldCallWebhookAction.php b/src/applications/herald/action/HeraldCallWebhookAction.php index a2003f4f33..953958e5c6 100644 --- a/src/applications/herald/action/HeraldCallWebhookAction.php +++ b/src/applications/herald/action/HeraldCallWebhookAction.php @@ -14,6 +14,10 @@ final class HeraldCallWebhookAction extends HeraldAction { } public function supportsObject($object) { + if (!$this->getAdapter()->supportsWebhooks()) { + return false; + } + return true; } diff --git a/src/applications/herald/adapter/HeraldAdapter.php b/src/applications/herald/adapter/HeraldAdapter.php index 7764332f11..08a0ca7a52 100644 --- a/src/applications/herald/adapter/HeraldAdapter.php +++ b/src/applications/herald/adapter/HeraldAdapter.php @@ -1211,6 +1211,11 @@ abstract class HeraldAdapter extends Phobject { /* -( Webhooks )----------------------------------------------------------- */ + public function supportsWebhooks() { + return true; + } + + final public function queueWebhook($webhook_phid, $rule_phid) { $this->webhookMap[$webhook_phid][] = $rule_phid; return $this; diff --git a/src/applications/herald/worker/HeraldWebhookWorker.php b/src/applications/herald/worker/HeraldWebhookWorker.php index 837ec0bb23..150f98fd50 100644 --- a/src/applications/herald/worker/HeraldWebhookWorker.php +++ b/src/applications/herald/worker/HeraldWebhookWorker.php @@ -32,6 +32,13 @@ final class HeraldWebhookWorker $status)); } + // If we're in silent mode, permanently fail the webhook request and then + // return to complete this task. + if (PhabricatorEnv::getEnvConfig('phabricator.silent')) { + $this->failRequest($request, 'hook', 'silent'); + return; + } + $hook = $request->getWebhook(); if ($hook->isDisabled()) { diff --git a/src/applications/meta/engineextension/PhabricatorQuickSearchApplicationEngineExtension.php b/src/applications/meta/engineextension/PhabricatorDatasourceApplicationEngineExtension.php similarity index 54% rename from src/applications/meta/engineextension/PhabricatorQuickSearchApplicationEngineExtension.php rename to src/applications/meta/engineextension/PhabricatorDatasourceApplicationEngineExtension.php index 8161c863ba..f5b0082446 100644 --- a/src/applications/meta/engineextension/PhabricatorQuickSearchApplicationEngineExtension.php +++ b/src/applications/meta/engineextension/PhabricatorDatasourceApplicationEngineExtension.php @@ -1,7 +1,7 @@ getObject()->getID()); } + public function supportsWebhooks() { + return false; + } + } diff --git a/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php b/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php index 8a21a75cfb..6aaf4fb052 100644 --- a/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php +++ b/src/applications/metamta/view/PhabricatorMetaMTAMailBody.php @@ -13,6 +13,7 @@ final class PhabricatorMetaMTAMailBody extends Phobject { private $attachments = array(); private $viewer; + private $contextObject; public function getViewer() { return $this->viewer; @@ -23,6 +24,16 @@ final class PhabricatorMetaMTAMailBody extends Phobject { return $this; } + public function setContextObject($context_object) { + $this->contextObject = $context_object; + return $this; + } + + public function getContextObject() { + return $this->contextObject; + } + + /* -( Composition )-------------------------------------------------------- */ @@ -45,9 +56,9 @@ final class PhabricatorMetaMTAMailBody extends Phobject { public function addRemarkupSection($header, $text) { try { - $engine = PhabricatorMarkupEngine::newMarkupEngine(array()); - $engine->setConfig('viewer', $this->getViewer()); - $engine->setMode(PhutilRemarkupEngine::MODE_TEXT); + $engine = $this->newMarkupEngine() + ->setMode(PhutilRemarkupEngine::MODE_TEXT); + $styled_text = $engine->markupText($text); $this->addPlaintextSection($header, $styled_text); } catch (Exception $ex) { @@ -56,12 +67,9 @@ final class PhabricatorMetaMTAMailBody extends Phobject { } try { - $mail_engine = PhabricatorMarkupEngine::newMarkupEngine(array()); - $mail_engine->setConfig('viewer', $this->getViewer()); - $mail_engine->setMode(PhutilRemarkupEngine::MODE_HTML_MAIL); - $mail_engine->setConfig( - 'uri.base', - PhabricatorEnv::getProductionURI('/')); + $mail_engine = $this->newMarkupEngine() + ->setMode(PhutilRemarkupEngine::MODE_HTML_MAIL); + $html = $mail_engine->markupText($text); $this->addHTMLSection($header, $html); } catch (Exception $ex) { @@ -215,4 +223,19 @@ final class PhabricatorMetaMTAMailBody extends Phobject { private function indent($text) { return rtrim(" ".str_replace("\n", "\n ", $text)); } + + + private function newMarkupEngine() { + $engine = PhabricatorMarkupEngine::newMarkupEngine(array()) + ->setConfig('viewer', $this->getViewer()) + ->setConfig('uri.base', PhabricatorEnv::getProductionURI('/')); + + $context = $this->getContextObject(); + if ($context) { + $engine->setConfig('contextObject', $context); + } + + return $engine; + } + } diff --git a/src/applications/people/engineextension/PhabricatorPeopleDatasourceEngineExtension.php b/src/applications/people/engineextension/PhabricatorPeopleDatasourceEngineExtension.php new file mode 100644 index 0000000000..dc8599554e --- /dev/null +++ b/src/applications/people/engineextension/PhabricatorPeopleDatasourceEngineExtension.php @@ -0,0 +1,36 @@ +getViewer(); + + // Send "u" to the user directory. + if (preg_match('/^u\z/i', $query)) { + return '/people/'; + } + + // Send "u " to the user's profile page. + $matches = null; + if (preg_match('/^u\s+(.+)\z/i', $query, $matches)) { + $raw_query = $matches[1]; + + // TODO: We could test that this is a valid username and jump to + // a search in the user directory if it isn't. + + return urisprintf( + '/p/%s/', + $raw_query); + } + + return null; + } + +} diff --git a/src/applications/people/engineextension/PhabricatorPeopleQuickSearchEngineExtension.php b/src/applications/people/engineextension/PhabricatorPeopleQuickSearchEngineExtension.php deleted file mode 100644 index 565838de72..0000000000 --- a/src/applications/people/engineextension/PhabricatorPeopleQuickSearchEngineExtension.php +++ /dev/null @@ -1,11 +0,0 @@ -setActor($request->getUser()) ->setContentSource($request->newContentSource()) ->setContinueOnNoEffect(true) - ->setDescription($request->getValue('description')); + ->setDescription((string)$request->getValue('description')); try { $editor->applyTransactions($doc, $xactions); diff --git a/src/applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php new file mode 100644 index 0000000000..f51faede5f --- /dev/null +++ b/src/applications/phriction/conduit/PhrictionDocumentSearchConduitAPIMethod.php @@ -0,0 +1,18 @@ +setActor($request->getUser()) ->setContentSource($request->newContentSource()) ->setContinueOnNoEffect(true) - ->setDescription($request->getValue('description')); + ->setDescription((string)$request->getValue('description')); try { $editor->applyTransactions($doc, $xactions); diff --git a/src/applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php index 454af2934a..f030420594 100644 --- a/src/applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionHistoryConduitAPIMethod.php @@ -10,6 +10,16 @@ final class PhrictionHistoryConduitAPIMethod extends PhrictionConduitAPIMethod { return pht('Retrieve history about a Phriction document.'); } + public function getMethodStatus() { + return self::METHOD_STATUS_FROZEN; + } + + public function getMethodStatusDescription() { + return pht( + 'This method is frozen and will eventually be deprecated. New code '. + 'should use "phriction.content.search" instead.'); + } + protected function defineParamTypes() { return array( 'slug' => 'required string', diff --git a/src/applications/phriction/conduit/PhrictionInfoConduitAPIMethod.php b/src/applications/phriction/conduit/PhrictionInfoConduitAPIMethod.php index 360b3e7b93..d6da3d6aee 100644 --- a/src/applications/phriction/conduit/PhrictionInfoConduitAPIMethod.php +++ b/src/applications/phriction/conduit/PhrictionInfoConduitAPIMethod.php @@ -10,6 +10,16 @@ final class PhrictionInfoConduitAPIMethod extends PhrictionConduitAPIMethod { return pht('Retrieve information about a Phriction document.'); } + public function getMethodStatus() { + return self::METHOD_STATUS_FROZEN; + } + + public function getMethodStatusDescription() { + return pht( + 'This method is frozen and will eventually be deprecated. New code '. + 'should use "phriction.document.search" instead.'); + } + protected function defineParamTypes() { return array( 'slug' => 'required string', diff --git a/src/applications/phriction/constants/PhrictionDocumentStatus.php b/src/applications/phriction/constants/PhrictionDocumentStatus.php index 11e75ec8a3..1830f3ec95 100644 --- a/src/applications/phriction/constants/PhrictionDocumentStatus.php +++ b/src/applications/phriction/constants/PhrictionDocumentStatus.php @@ -1,11 +1,12 @@ getStatusSpecification($key)); + } + + public static function getStatusMap() { + $map = id(new self())->getStatusSpecifications(); + return ipull($map, 'name', 'key'); + } + + public function isActive() { + return ($this->getKey() == self::STATUS_EXISTS); + } + + protected function newStatusSpecifications() { + return array( + array( + 'key' => self::STATUS_EXISTS, + 'name' => pht('Active'), + 'icon' => 'fa-file-text-o', + 'color' => 'bluegrey', + ), + array( + 'key' => self::STATUS_DELETED, + 'name' => pht('Deleted'), + 'icon' => 'fa-file-text-o', + 'color' => 'grey', + ), + array( + 'key' => self::STATUS_MOVED, + 'name' => pht('Moved'), + 'icon' => 'fa-arrow-right', + 'color' => 'grey', + ), + array( + 'key' => self::STATUS_STUB, + 'name' => pht('Stub'), + 'icon' => 'fa-file-text-o', + 'color' => 'grey', + ), + ); + } } diff --git a/src/applications/phriction/controller/PhrictionController.php b/src/applications/phriction/controller/PhrictionController.php index 277692334f..51ffbcab1f 100644 --- a/src/applications/phriction/controller/PhrictionController.php +++ b/src/applications/phriction/controller/PhrictionController.php @@ -13,7 +13,7 @@ abstract class PhrictionController extends PhabricatorController { $nav->addFilter('/phriction/', pht('Index')); } - id(new PhrictionSearchEngine()) + id(new PhrictionDocumentSearchEngine()) ->setViewer($user) ->addNavigationItems($nav->getMenu()); diff --git a/src/applications/phriction/controller/PhrictionDiffController.php b/src/applications/phriction/controller/PhrictionDiffController.php index b66a3b3094..f443ad657b 100644 --- a/src/applications/phriction/controller/PhrictionDiffController.php +++ b/src/applications/phriction/controller/PhrictionDiffController.php @@ -29,10 +29,11 @@ final class PhrictionDiffController extends PhrictionController { list($l, $r) = explode(',', $ref); } - $content = id(new PhrictionContent())->loadAllWhere( - 'documentID = %d AND version IN (%Ld)', - $document->getID(), - array($l, $r)); + $content = id(new PhrictionContentQuery()) + ->setViewer($viewer) + ->withDocumentPHIDs(array($document->getPHID())) + ->withVersions(array($l, $r)) + ->execute(); $content = mpull($content, null, 'getVersion'); $content_l = idx($content, $l, null); diff --git a/src/applications/phriction/controller/PhrictionDocumentController.php b/src/applications/phriction/controller/PhrictionDocumentController.php index e455bd86d1..8377cf7d29 100644 --- a/src/applications/phriction/controller/PhrictionDocumentController.php +++ b/src/applications/phriction/controller/PhrictionDocumentController.php @@ -22,11 +22,6 @@ final class PhrictionDocumentController require_celerity_resource('phriction-document-css'); - $document = id(new PhrictionDocumentQuery()) - ->setViewer($viewer) - ->withSlugs(array($slug)) - ->executeOne(); - $version_note = null; $core_content = ''; $move_notice = ''; @@ -34,6 +29,11 @@ final class PhrictionDocumentController $content = null; $toc = null; + $document = id(new PhrictionDocumentQuery()) + ->setViewer($viewer) + ->withSlugs(array($slug)) + ->needContent(true) + ->executeOne(); if (!$document) { $document = PhrictionDocument::initializeNewDocument($viewer, $slug); @@ -69,25 +69,28 @@ final class PhrictionDocumentController $version = $request->getInt('v'); if ($version) { - $content = id(new PhrictionContent())->loadOneWhere( - 'documentID = %d AND version = %d', - $document->getID(), - $version); + $content = id(new PhrictionContentQuery()) + ->setViewer($viewer) + ->withDocumentPHIDs(array($document->getPHID())) + ->withVersions(array($version)) + ->executeOne(); if (!$content) { return new Aphront404Response(); } if ($content->getID() != $document->getContentID()) { - $vdate = phabricator_datetime($content->getDateCreated(), $viewer); - $version_note = new PHUIInfoView(); - $version_note->setSeverity(PHUIInfoView::SEVERITY_NOTICE); - $version_note->appendChild( - pht('You are viewing an older version of this document, as it '. - 'appeared on %s.', $vdate)); + $version_note = id(new PHUIInfoView()) + ->setSeverity(PHUIInfoView::SEVERITY_NOTICE) + ->appendChild( + pht( + 'You are viewing an older version of this document, as it '. + 'appeared on %s.', + phabricator_datetime($content->getDateCreated(), $viewer))); } } else { - $content = id(new PhrictionContent())->load($document->getContentID()); + $content = $document->getContent(); } + $page_title = $content->getTitle(); $properties = $this ->buildPropertyListView($document, $content, $slug); @@ -97,8 +100,12 @@ final class PhrictionDocumentController if ($current_status == PhrictionChangeType::CHANGE_EDIT || $current_status == PhrictionChangeType::CHANGE_MOVE_HERE) { - $core_content = $content->renderContent($viewer); - $toc = $this->getToc($content); + $remarkup_view = $content->newRemarkupView($viewer); + + $core_content = $remarkup_view->render(); + + $toc = $remarkup_view->getTableOfContents(); + $toc = $this->getToc($toc); } else if ($current_status == PhrictionChangeType::CHANGE_DELETE) { $notice = new PHUIInfoView(); @@ -474,8 +481,8 @@ final class PhrictionDocumentController return $this->slug; } - protected function getToc(PhrictionContent $content) { - $toc = $content->getRenderedTableOfContents(); + protected function getToc($toc) { + if ($toc) { $toc = phutil_tag_div('phui-document-toc-content', array( phutil_tag_div( @@ -484,6 +491,7 @@ final class PhrictionDocumentController $toc, )); } + return $toc; } diff --git a/src/applications/phriction/controller/PhrictionEditController.php b/src/applications/phriction/controller/PhrictionEditController.php index 0ac3fc35bb..e7e2b40d87 100644 --- a/src/applications/phriction/controller/PhrictionEditController.php +++ b/src/applications/phriction/controller/PhrictionEditController.php @@ -28,10 +28,11 @@ final class PhrictionEditController $revert = $request->getInt('revert'); if ($revert) { - $content = id(new PhrictionContent())->loadOneWhere( - 'documentID = %d AND version = %d', - $document->getID(), - $revert); + $content = id(new PhrictionContentQuery()) + ->setViewer($viewer) + ->withDocumentPHIDs(array($document->getPHID())) + ->withVersions(array($revert)) + ->executeOne(); if (!$content) { return new Aphront404Response(); } diff --git a/src/applications/phriction/controller/PhrictionHistoryController.php b/src/applications/phriction/controller/PhrictionHistoryController.php index 9b0d3188a2..432b1dfdef 100644 --- a/src/applications/phriction/controller/PhrictionHistoryController.php +++ b/src/applications/phriction/controller/PhrictionHistoryController.php @@ -22,16 +22,13 @@ final class PhrictionHistoryController $current = $document->getContent(); - $pager = new PHUIPagerView(); - $pager->setOffset($request->getInt('page')); - $pager->setURI($request->getRequestURI(), 'page'); + $pager = id(new AphrontCursorPagerView()) + ->readFromRequest($request); - $history = id(new PhrictionContent())->loadAllWhere( - 'documentID = %d ORDER BY version DESC LIMIT %d, %d', - $document->getID(), - $pager->getOffset(), - $pager->getPageSize() + 1); - $history = $pager->sliceResults($history); + $history = id(new PhrictionContentQuery()) + ->setViewer($viewer) + ->withDocumentPHIDs(array($document->getPHID())) + ->executeWithCursorPager($pager); $author_phids = mpull($history, 'getAuthorPHID'); $handles = $this->loadViewerHandles($author_phids); diff --git a/src/applications/phriction/controller/PhrictionListController.php b/src/applications/phriction/controller/PhrictionListController.php index 44ef95f178..7da29b297d 100644 --- a/src/applications/phriction/controller/PhrictionListController.php +++ b/src/applications/phriction/controller/PhrictionListController.php @@ -12,7 +12,7 @@ final class PhrictionListController $controller = id(new PhabricatorApplicationSearchController()) ->setQueryKey($querykey) - ->setSearchEngine(new PhrictionSearchEngine()) + ->setSearchEngine(new PhrictionDocumentSearchEngine()) ->setNavigation($this->buildSideNavView()); return $this->delegateToController($controller); diff --git a/src/applications/phriction/editor/PhrictionTransactionEditor.php b/src/applications/phriction/editor/PhrictionTransactionEditor.php index 73aee3fd4c..793c609517 100644 --- a/src/applications/phriction/editor/PhrictionTransactionEditor.php +++ b/src/applications/phriction/editor/PhrictionTransactionEditor.php @@ -596,10 +596,13 @@ final class PhrictionTransactionEditor ->setAuthorPHID($this->getActor()->getPHID()) ->setChangeType(PhrictionChangeType::CHANGE_EDIT) ->setTitle($this->getOldContent()->getTitle()) - ->setContent($this->getOldContent()->getContent()); + ->setContent($this->getOldContent()->getContent()) + ->setDescription(''); + if (strlen($this->getDescription())) { $new_content->setDescription($this->getDescription()); } + $new_content->setVersion($this->getOldContent()->getVersion() + 1); return $new_content; diff --git a/src/applications/phriction/engineextension/PhrictionContentSearchEngineAttachment.php b/src/applications/phriction/engineextension/PhrictionContentSearchEngineAttachment.php new file mode 100644 index 0000000000..3321f23eb0 --- /dev/null +++ b/src/applications/phriction/engineextension/PhrictionContentSearchEngineAttachment.php @@ -0,0 +1,31 @@ +getContent(); + } else { + $content = $object; + } + + return array( + 'title' => $content->getTitle(), + 'path' => $content->getSlug(), + 'authorPHID' => $content->getAuthorPHID(), + 'content' => array( + 'raw' => $content->getContent(), + ), + ); + } + +} diff --git a/src/applications/phriction/engineextension/PhrictionDatasourceEngineExtension.php b/src/applications/phriction/engineextension/PhrictionDatasourceEngineExtension.php new file mode 100644 index 0000000000..af262d541d --- /dev/null +++ b/src/applications/phriction/engineextension/PhrictionDatasourceEngineExtension.php @@ -0,0 +1,56 @@ +getViewer(); + + // Send "w" to Phriction. + if (preg_match('/^w\z/i', $query)) { + return '/w/'; + } + + // Send "w " to a search for similar wiki documents. + $matches = null; + if (preg_match('/^w\s+(.+)\z/i', $query, $matches)) { + $raw_query = $matches[1]; + + $engine = id(new PhrictionDocument()) + ->newFerretEngine(); + + $compiler = id(new PhutilSearchQueryCompiler()) + ->setEnableFunctions(true); + + $raw_tokens = $compiler->newTokens($raw_query); + + $fulltext_tokens = array(); + foreach ($raw_tokens as $raw_token) { + $fulltext_token = id(new PhabricatorFulltextToken()) + ->setToken($raw_token); + $fulltext_tokens[] = $fulltext_token; + } + + $documents = id(new PhrictionDocumentQuery()) + ->setViewer($viewer) + ->withFerretConstraint($engine, $fulltext_tokens) + ->execute(); + if (count($documents) == 1) { + return head($documents)->getURI(); + } else { + // More than one match, jump to search. + return urisprintf( + '/phriction/?order=relevance&query=%s#R', + $raw_query); + } + } + + return null; + } +} diff --git a/src/applications/phriction/markup/PhrictionRemarkupRule.php b/src/applications/phriction/markup/PhrictionRemarkupRule.php index 9c5649bbbe..d17de331e5 100644 --- a/src/applications/phriction/markup/PhrictionRemarkupRule.php +++ b/src/applications/phriction/markup/PhrictionRemarkupRule.php @@ -28,17 +28,7 @@ final class PhrictionRemarkupRule extends PhutilRemarkupRule { // Handle relative links. if ((substr($link, 0, 2) === './') || (substr($link, 0, 3) === '../')) { - $base = null; - $context = $this->getEngine()->getConfig('contextObject'); - if ($context !== null && $context instanceof PhrictionContent) { - // Handle content when it's being rendered in document view. - $base = $context->getSlug(); - } - if ($context !== null && is_array($context) && - idx($context, 'phriction.isPreview')) { - // Handle content when it's a preview for the Phriction editor. - $base = idx($context, 'phriction.slug'); - } + $base = $this->getRelativeBaseURI(); if ($base !== null) { $base_parts = explode('/', rtrim($base, '/')); $rel_parts = explode('/', rtrim($link, '/')); @@ -93,28 +83,97 @@ final class PhrictionRemarkupRule extends PhutilRemarkupRule { return; } + $viewer = $engine->getConfig('viewer'); + $slugs = ipull($metadata, 'link'); - foreach ($slugs as $key => $slug) { - $slugs[$key] = PhabricatorSlug::normalize($slug); + + $load_map = array(); + foreach ($slugs as $key => $raw_slug) { + $lookup = PhabricatorSlug::normalize($raw_slug); + $load_map[$lookup][] = $key; + + // Also try to lookup the slug with URL decoding applied. The right + // way to link to a page titled "$cash" is to write "[[ $cash ]]" (and + // not the URL encoded form "[[ %24cash ]]"), but users may reasonably + // have copied URL encoded variations out of their browser location + // bar or be skeptical that "[[ $cash ]]" will actually work. + + $lookup = phutil_unescape_uri_path_component($raw_slug); + $lookup = phutil_utf8ize($lookup); + $lookup = PhabricatorSlug::normalize($lookup); + $load_map[$lookup][] = $key; } - // We have to make two queries here to distinguish between - // documents the user can't see, and documents that don't - // exist. $visible_documents = id(new PhrictionDocumentQuery()) - ->setViewer($engine->getConfig('viewer')) - ->withSlugs($slugs) + ->setViewer($viewer) + ->withSlugs(array_keys($load_map)) ->needContent(true) ->execute(); - $existant_documents = id(new PhrictionDocumentQuery()) - ->setViewer(PhabricatorUser::getOmnipotentUser()) - ->withSlugs($slugs) - ->execute(); - $visible_documents = mpull($visible_documents, null, 'getSlug'); - $existant_documents = mpull($existant_documents, null, 'getSlug'); + $document_map = array(); + foreach ($load_map as $lookup => $keys) { + $visible = idx($visible_documents, $lookup); + if (!$visible) { + continue; + } - foreach ($metadata as $spec) { + foreach ($keys as $key) { + $document_map[$key] = array( + 'visible' => true, + 'document' => $visible, + ); + } + + unset($load_map[$lookup]); + } + + // For each document we found, remove all remaining requests for it from + // the load map. If we remove all requests for a slug, remove the slug. + // This stops us from doing unnecessary lookups on alternate names for + // documents we already found. + foreach ($load_map as $lookup => $keys) { + foreach ($keys as $lookup_key => $key) { + if (isset($document_map[$key])) { + unset($keys[$lookup_key]); + } + } + + if (!$keys) { + unset($load_map[$lookup]); + continue; + } + + $load_map[$lookup] = $keys; + } + + + // If we still have links we haven't found a document for, do another + // query with the omnipotent viewer so we can distinguish between pages + // which do not exist and pages which exist but which the viewer does not + // have permission to see. + if ($load_map) { + $existent_documents = id(new PhrictionDocumentQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withSlugs(array_keys($load_map)) + ->execute(); + $existent_documents = mpull($existent_documents, null, 'getSlug'); + + foreach ($load_map as $lookup => $keys) { + $existent = idx($existent_documents, $lookup); + if (!$existent) { + continue; + } + + foreach ($keys as $key) { + $document_map[$key] = array( + 'visible' => false, + 'document' => null, + ); + } + } + } + + foreach ($metadata as $key => $spec) { $link = $spec['link']; $slug = PhabricatorSlug::normalize($link); $name = $spec['explicitName']; @@ -124,14 +183,16 @@ final class PhrictionRemarkupRule extends PhutilRemarkupRule { // in text as: "Title" . Otherwise, we'll just render: . $is_interesting_name = (bool)strlen($name); - if (idx($existant_documents, $slug) === null) { + $target = idx($document_map, $key, null); + + if ($target === null) { // The target document doesn't exist. if ($name === null) { $name = explode('/', trim($link, '/')); $name = end($name); } $class = 'phriction-link-missing'; - } else if (idx($visible_documents, $slug) === null) { + } else if (!$target['visible']) { // The document exists, but the user can't see it. if ($name === null) { $name = explode('/', trim($link, '/')); @@ -141,7 +202,7 @@ final class PhrictionRemarkupRule extends PhutilRemarkupRule { } else { if ($name === null) { // Use the title of the document if no name is set. - $name = $visible_documents[$slug] + $name = $target['document'] ->getContent() ->getTitle(); @@ -195,4 +256,30 @@ final class PhrictionRemarkupRule extends PhutilRemarkupRule { } } + private function getRelativeBaseURI() { + $context = $this->getEngine()->getConfig('contextObject'); + + if (!$context) { + 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(); + } + + if ($context instanceof PhrictionDocument) { + return $context->getContent()->getSlug(); + } + + return null; + } + + } diff --git a/src/applications/phriction/phid/PhrictionContentPHIDType.php b/src/applications/phriction/phid/PhrictionContentPHIDType.php new file mode 100644 index 0000000000..b8f39c0ef4 --- /dev/null +++ b/src/applications/phriction/phid/PhrictionContentPHIDType.php @@ -0,0 +1,38 @@ +withPHIDs($phids); + } + + public function loadHandles( + PhabricatorHandleQuery $query, + array $handles, + array $objects) { + + foreach ($handles as $phid => $handle) { + $content = $objects[$phid]; + } + } + +} diff --git a/src/applications/phriction/query/PhrictionContentQuery.php b/src/applications/phriction/query/PhrictionContentQuery.php new file mode 100644 index 0000000000..053f40cf9c --- /dev/null +++ b/src/applications/phriction/query/PhrictionContentQuery.php @@ -0,0 +1,128 @@ +ids = $ids; + return $this; + } + + public function withPHIDs(array $phids) { + $this->phids = $phids; + return $this; + } + + public function withDocumentPHIDs(array $phids) { + $this->documentPHIDs = $phids; + return $this; + } + + public function withVersions(array $versions) { + $this->versions = $versions; + return $this; + } + + public function newResultObject() { + return new PhrictionContent(); + } + + protected function loadPage() { + return $this->loadStandardPage($this->newResultObject()); + } + + protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) { + $where = parent::buildWhereClauseParts($conn); + + if ($this->ids !== null) { + $where[] = qsprintf( + $conn, + 'c.id IN (%Ld)', + $this->ids); + } + + if ($this->phids !== null) { + $where[] = qsprintf( + $conn, + 'c.phid IN (%Ls)', + $this->phids); + } + + if ($this->versions !== null) { + $where[] = qsprintf( + $conn, + 'version IN (%Ld)', + $this->versions); + } + + if ($this->documentPHIDs !== null) { + $where[] = qsprintf( + $conn, + 'd.phid IN (%Ls)', + $this->documentPHIDs); + } + + return $where; + } + + protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) { + $joins = parent::buildJoinClauseParts($conn); + + if ($this->shouldJoinDocumentTable()) { + $joins[] = qsprintf( + $conn, + 'JOIN %T d ON d.id = c.documentID', + id(new PhrictionDocument())->getTableName()); + } + + return $joins; + } + + protected function willFilterPage(array $contents) { + $document_ids = mpull($contents, 'getDocumentID'); + + $documents = id(new PhrictionDocumentQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withIDs($document_ids) + ->execute(); + $documents = mpull($documents, null, 'getID'); + + foreach ($contents as $key => $content) { + $document_id = $content->getDocumentID(); + + $document = idx($documents, $document_id); + if (!$document) { + unset($contents[$key]); + $this->didRejectResult($content); + continue; + } + + $content->attachDocument($document); + } + + return $contents; + } + + private function shouldJoinDocumentTable() { + if ($this->documentPHIDs !== null) { + return true; + } + + return false; + } + + protected function getPrimaryTableAlias() { + return 'c'; + } + + public function getQueryApplicationClass() { + return 'PhabricatorPhrictionApplication'; + } + +} diff --git a/src/applications/phriction/query/PhrictionContentSearchEngine.php b/src/applications/phriction/query/PhrictionContentSearchEngine.php new file mode 100644 index 0000000000..f7cd9be2b6 --- /dev/null +++ b/src/applications/phriction/query/PhrictionContentSearchEngine.php @@ -0,0 +1,76 @@ +newQuery(); + + if ($map['documentPHIDs']) { + $query->withDocumentPHIDs($map['documentPHIDs']); + } + + if ($map['versions']) { + $query->withVersions($map['versions']); + } + + return $query; + } + + protected function buildCustomSearchFields() { + return array( + id(new PhabricatorPHIDsSearchField()) + ->setKey('documentPHIDs') + ->setAliases(array('document', 'documents', 'documentPHID')) + ->setLabel(pht('Documents')), + id(new PhabricatorIDsSearchField()) + ->setKey('versions') + ->setAliases(array('version')), + ); + } + + protected function getURI($path) { + // There's currently no web UI for this search interface, it exists purely + // to power the Conduit API. + throw new PhutilMethodNotImplementedException(); + } + + protected function getBuiltinQueryNames() { + return array( + 'all' => pht('All Content'), + ); + } + + public function buildSavedQueryFromBuiltin($query_key) { + $query = $this->newSavedQuery(); + $query->setQueryKey($query_key); + + switch ($query_key) { + case 'all': + return $query; + } + + return parent::buildSavedQueryFromBuiltin($query_key); + } + + protected function renderResultList( + array $contents, + PhabricatorSavedQuery $query, + array $handles) { + assert_instances_of($contents, 'PhrictionContent'); + throw new PhutilMethodNotImplementedException(); + } + +} diff --git a/src/applications/phriction/query/PhrictionDocumentQuery.php b/src/applications/phriction/query/PhrictionDocumentQuery.php index 2736d825b5..de3064fd42 100644 --- a/src/applications/phriction/query/PhrictionDocumentQuery.php +++ b/src/applications/phriction/query/PhrictionDocumentQuery.php @@ -12,12 +12,7 @@ final class PhrictionDocumentQuery private $needContent; - private $status = 'status-any'; - const STATUS_ANY = 'status-any'; - const STATUS_OPEN = 'status-open'; - const STATUS_NONSTUB = 'status-nonstub'; - - const ORDER_HIERARCHY = 'order-hierarchy'; + const ORDER_HIERARCHY = 'hierarchy'; public function withIDs(array $ids) { $this->ids = $ids; @@ -49,11 +44,6 @@ final class PhrictionDocumentQuery return $this; } - public function withStatus($status) { - $this->status = $status; - return $this; - } - public function needContent($need_content) { $this->needContent = $need_content; return $this; @@ -145,9 +135,12 @@ final class PhrictionDocumentQuery } if ($this->needContent) { - $contents = id(new PhrictionContent())->loadAllWhere( - 'id IN (%Ld)', - mpull($documents, 'getContentID')); + $contents = id(new PhrictionContentQuery()) + ->setViewer($this->getViewer()) + ->setParentQuery($this) + ->withIDs(mpull($documents, 'getContentID')) + ->execute(); + $contents = mpull($contents, null, 'getID'); foreach ($documents as $key => $document) { $content_id = $document->getContentID(); @@ -203,7 +196,7 @@ final class PhrictionDocumentQuery if ($this->statuses !== null) { $where[] = qsprintf( $conn, - 'd.status IN (%Ld)', + 'd.status IN (%Ls)', $this->statuses); } @@ -221,42 +214,16 @@ final class PhrictionDocumentQuery $this->depths); } - switch ($this->status) { - case self::STATUS_OPEN: - $where[] = qsprintf( - $conn, - 'd.status NOT IN (%Ld)', - array( - PhrictionDocumentStatus::STATUS_DELETED, - PhrictionDocumentStatus::STATUS_MOVED, - PhrictionDocumentStatus::STATUS_STUB, - )); - break; - case self::STATUS_NONSTUB: - $where[] = qsprintf( - $conn, - 'd.status NOT IN (%Ld)', - array( - PhrictionDocumentStatus::STATUS_MOVED, - PhrictionDocumentStatus::STATUS_STUB, - )); - break; - case self::STATUS_ANY: - break; - default: - throw new Exception(pht("Unknown status '%s'!", $this->status)); - } - return $where; } public function getBuiltinOrders() { - return array( + return parent::getBuiltinOrders() + array( self::ORDER_HIERARCHY => array( 'vector' => array('depth', 'title', 'updated'), 'name' => pht('Hierarchy'), ), - ) + parent::getBuiltinOrders(); + ); } public function getOrderableColumns() { diff --git a/src/applications/phriction/query/PhrictionSearchEngine.php b/src/applications/phriction/query/PhrictionDocumentSearchEngine.php similarity index 72% rename from src/applications/phriction/query/PhrictionSearchEngine.php rename to src/applications/phriction/query/PhrictionDocumentSearchEngine.php index 245500e0ff..e0781ec81f 100644 --- a/src/applications/phriction/query/PhrictionSearchEngine.php +++ b/src/applications/phriction/query/PhrictionDocumentSearchEngine.php @@ -1,6 +1,6 @@ needContent(true) - ->withStatus(PhrictionDocumentQuery::STATUS_NONSTUB); + ->needContent(true); } protected function buildQueryFromParameters(array $map) { $query = $this->newQuery(); - if ($map['status']) { - $query->withStatus($map['status']); + if ($map['statuses']) { + $query->withStatuses($map['statuses']); + } + + if ($map['paths']) { + $query->withSlugs($map['paths']); } return $query; @@ -29,10 +32,14 @@ final class PhrictionSearchEngine protected function buildCustomSearchFields() { return array( - id(new PhabricatorSearchSelectField()) - ->setKey('status') + id(new PhabricatorSearchCheckboxesField()) + ->setKey('statuses') ->setLabel(pht('Status')) - ->setOptions($this->getStatusOptions()), + ->setOptions(PhrictionDocumentStatus::getStatusMap()), + id(new PhabricatorSearchStringListField()) + ->setKey('paths') + ->setIsHidden(true) + ->setLabel(pht('Paths')), ); } @@ -59,19 +66,15 @@ final class PhrictionSearchEngine return $query; case 'active': return $query->setParameter( - 'status', PhrictionDocumentQuery::STATUS_OPEN); + 'statuses', + array( + PhrictionDocumentStatus::STATUS_EXISTS, + )); } return parent::buildSavedQueryFromBuiltin($query_key); } - private function getStatusOptions() { - return array( - PhrictionDocumentQuery::STATUS_OPEN => pht('Show Active Documents'), - PhrictionDocumentQuery::STATUS_NONSTUB => pht('Show All Documents'), - ); - } - protected function getRequiredHandlePHIDsForResultList( array $documents, PhabricatorSavedQuery $query) { @@ -118,15 +121,14 @@ final class PhrictionSearchEngine $item->addAttribute($slug_uri); - switch ($document->getStatus()) { - case PhrictionDocumentStatus::STATUS_DELETED: - $item->setDisabled(true); - $item->addIcon('delete', pht('Deleted')); - break; - case PhrictionDocumentStatus::STATUS_MOVED: - $item->setDisabled(true); - $item->addIcon('arrow-right', pht('Moved Away')); - break; + $icon = $document->getStatusIcon(); + $color = $document->getStatusColor(); + $label = $document->getStatusDisplayName(); + + $item->setStatusIcon("{$icon} {$color}", $label); + + if (!$document->isActive()) { + $item->setDisabled(true); } $list->addItem($item); diff --git a/src/applications/phriction/search/PhrictionDocumentFerretEngine.php b/src/applications/phriction/search/PhrictionDocumentFerretEngine.php index 76802391e7..498b9274f0 100644 --- a/src/applications/phriction/search/PhrictionDocumentFerretEngine.php +++ b/src/applications/phriction/search/PhrictionDocumentFerretEngine.php @@ -12,7 +12,7 @@ final class PhrictionDocumentFerretEngine } public function newSearchEngine() { - return new PhrictionSearchEngine(); + return new PhrictionDocumentSearchEngine(); } } diff --git a/src/applications/phriction/storage/PhrictionContent.php b/src/applications/phriction/storage/PhrictionContent.php index 0492534797..515fc6b7d5 100644 --- a/src/applications/phriction/storage/PhrictionContent.php +++ b/src/applications/phriction/storage/PhrictionContent.php @@ -1,14 +1,12 @@ true, self::CONFIG_COLUMN_SCHEMA => array( 'version' => 'uint32', 'title' => 'sort', @@ -40,10 +31,7 @@ final class PhrictionContent extends PhrictionDAO 'content' => 'text', 'changeType' => 'uint32', 'changeRef' => 'uint32?', - - // T6203/NULLABILITY - // This should just be empty if not provided? - 'description' => 'text?', + 'description' => 'text', ), self::CONFIG_KEY_SCHEMA => array( 'documentID' => array( @@ -60,68 +48,92 @@ final class PhrictionContent extends PhrictionDAO ) + parent::getConfiguration(); } + public function getPHIDType() { + return PhrictionContentPHIDType::TYPECONST; + } -/* -( Markup Interface )--------------------------------------------------- */ + public function newRemarkupView(PhabricatorUser $viewer) { + return id(new PHUIRemarkupView($viewer, $this->getContent())) + ->setContextObject($this) + ->setRemarkupOption(PHUIRemarkupView::OPTION_GENERATE_TOC, true) + ->setGenerateTableOfContents(true); + } + public function attachDocument(PhrictionDocument $document) { + $this->document = $document; + return $this; + } - /** - * @task markup - */ - public function getMarkupFieldKey($field) { - $content = $this->getMarkupText($field); - return PhabricatorMarkupEngine::digestRemarkupContent($this, $content); + public function getDocument() { + return $this->assertAttached($this->document); } - /** - * @task markup - */ - public function getMarkupText($field) { - return $this->getContent(); +/* -( PhabricatorPolicyInterface )----------------------------------------- */ + + + public function getCapabilities() { + return array( + PhabricatorPolicyCapability::CAN_VIEW, + ); + } + + public function getPolicy($capability) { + return PhabricatorPolicies::getMostOpenPolicy(); + } + + public function hasAutomaticCapability($capability, PhabricatorUser $viewer) { + return false; } - /** - * @task markup - */ - public function newMarkupEngine($field) { - return PhabricatorMarkupEngine::newPhrictionMarkupEngine(); +/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */ + + + public function getExtendedPolicy($capability, PhabricatorUser $viewer) { + return array( + array($this->getDocument(), PhabricatorPolicyCapability::CAN_VIEW), + ); } - /** - * @task markup - */ - public function didMarkupText( - $field, - $output, - PhutilMarkupEngine $engine) { +/* -( PhabricatorDestructibleInterface )----------------------------------- */ - $this->renderedTableOfContents = - PhutilRemarkupHeaderBlockRule::renderTableOfContents($engine); - return phutil_tag( - 'div', - array( - 'class' => 'phabricator-remarkup', - ), - $output); - } - - /** - * @task markup - */ - public function getRenderedTableOfContents() { - return $this->renderedTableOfContents; + public function destroyObjectPermanently( + PhabricatorDestructionEngine $engine) { + $this->delete(); } - /** - * @task markup - */ - public function shouldUseMarkupCache($field) { - return (bool)$this->getID(); +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('documentPHID') + ->setType('phid') + ->setDescription(pht('Document this content is for.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('version') + ->setType('int') + ->setDescription(pht('Content version.')), + ); } + public function getFieldValuesForConduit() { + return array( + 'documentPHID' => $this->getDocument()->getPHID(), + 'version' => (int)$this->getVersion(), + ); + } + + public function getConduitSearchAttachments() { + return array( + id(new PhrictionContentSearchEngineAttachment()) + ->setAttachmentKey('content'), + ); + } } diff --git a/src/applications/phriction/storage/PhrictionDocument.php b/src/applications/phriction/storage/PhrictionDocument.php index 7a3e178882..729d3e4ca5 100644 --- a/src/applications/phriction/storage/PhrictionDocument.php +++ b/src/applications/phriction/storage/PhrictionDocument.php @@ -10,7 +10,8 @@ final class PhrictionDocument extends PhrictionDAO PhabricatorFulltextInterface, PhabricatorFerretInterface, PhabricatorProjectInterface, - PhabricatorApplicationTransactionInterface { + PhabricatorApplicationTransactionInterface, + PhabricatorConduitResultInterface { protected $slug; protected $depth; @@ -31,7 +32,7 @@ final class PhrictionDocument extends PhrictionDAO 'slug' => 'sort128', 'depth' => 'uint32', 'contentID' => 'id?', - 'status' => 'uint32', + 'status' => 'text32', 'mailKey' => 'bytes20', ), self::CONFIG_KEY_SCHEMA => array( @@ -61,7 +62,7 @@ final class PhrictionDocument extends PhrictionDAO $document = new PhrictionDocument(); $document->setSlug($slug); - $content = new PhrictionContent(); + $content = new PhrictionContent(); $content->setSlug($slug); $default_title = PhabricatorSlug::getDefaultTitle($slug); @@ -148,6 +149,33 @@ final class PhrictionDocument extends PhrictionDAO return $this; } + public function getURI() { + return self::getSlugURI($this->getSlug()); + } + +/* -( Status )------------------------------------------------------------- */ + + + public function getStatusObject() { + return PhrictionDocumentStatus::newStatusObject($this->getStatus()); + } + + public function getStatusIcon() { + return $this->getStatusObject()->getIcon(); + } + + public function getStatusColor() { + return $this->getStatusObject()->getColor(); + } + + public function getStatusDisplayName() { + return $this->getStatusObject()->getDisplayName(); + } + + public function isActive() { + return $this->getStatusObject()->isActive(); + } + /* -( PhabricatorPolicyInterface )----------------------------------------- */ @@ -236,15 +264,16 @@ final class PhrictionDocument extends PhrictionDAO $this->openTransaction(); - $this->delete(); - - $contents = id(new PhrictionContent())->loadAllWhere( - 'documentID = %d', - $this->getID()); + $contents = id(new PhrictionContentQuery()) + ->setViewer($engine->getViewer()) + ->withDocumentPHIDs(array($this->getPHID())) + ->execute(); foreach ($contents as $content) { - $content->delete(); + $engine->destroyObject($content); } + $this->delete(); + $this->saveTransaction(); } @@ -264,4 +293,39 @@ final class PhrictionDocument extends PhrictionDAO return new PhrictionDocumentFerretEngine(); } + +/* -( PhabricatorConduitResultInterface )---------------------------------- */ + + + public function getFieldSpecificationsForConduit() { + return array( + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('path') + ->setType('string') + ->setDescription(pht('The path to the document.')), + id(new PhabricatorConduitSearchFieldSpecification()) + ->setKey('status') + ->setType('map') + ->setDescription(pht('Status information about the document.')), + ); + } + + public function getFieldValuesForConduit() { + $status = array( + 'value' => $this->getStatus(), + 'name' => $this->getStatusDisplayName(), + ); + + return array( + 'path' => $this->getSlug(), + 'status' => $status, + ); + } + + public function getConduitSearchAttachments() { + return array( + id(new PhrictionContentSearchEngineAttachment()) + ->setAttachmentKey('content'), + ); + } } diff --git a/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php b/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php new file mode 100644 index 0000000000..bb9608edb8 --- /dev/null +++ b/src/applications/phriction/typeahead/PhrictionDocumentDatasource.php @@ -0,0 +1,90 @@ +getViewer(); + + $raw_query = $this->getRawQuery(); + + $engine = id(new PhrictionDocument()) + ->newFerretEngine(); + + $compiler = id(new PhutilSearchQueryCompiler()) + ->setEnableFunctions(true); + + $raw_tokens = $compiler->newTokens($raw_query); + + $fulltext_tokens = array(); + foreach ($raw_tokens as $raw_token) { + + // This is a little hacky and could maybe be cleaner. We're treating + // every search term as though the user had entered "title:dog" insead + // of "dog". + + $alternate_token = PhutilSearchQueryToken::newFromDictionary( + array( + 'quoted' => $raw_token->isQuoted(), + 'value' => $raw_token->getValue(), + 'operator' => PhutilSearchQueryCompiler::OPERATOR_SUBSTRING, + 'function' => 'title', + )); + + $fulltext_token = id(new PhabricatorFulltextToken()) + ->setToken($alternate_token); + $fulltext_tokens[] = $fulltext_token; + } + + $documents = id(new PhrictionDocumentQuery()) + ->setViewer($viewer) + ->withFerretConstraint($engine, $fulltext_tokens) + ->needContent(true) + ->execute(); + + $results = array(); + foreach ($documents as $document) { + $content = $document->getContent(); + + if (!$document->isActive()) { + $closed = $document->getStatusDisplayName(); + } else { + $closed = null; + } + + $slug = $document->getSlug(); + $title = $content->getTitle(); + + $sprite = 'phabricator-search-icon phui-font-fa phui-icon-view fa-book'; + $autocomplete = '[[ '.$slug.' ]]'; + + $result = id(new PhabricatorTypeaheadResult()) + ->setName($title) + ->setDisplayName($title) + ->setURI($document->getURI()) + ->setPHID($document->getPHID()) + ->setDisplayType($slug) + ->setPriorityType('wiki') + ->setImageSprite($sprite) + ->setAutocomplete($autocomplete) + ->setClosed($closed); + + $results[] = $result; + } + + return $results; + } + +} diff --git a/src/applications/phurl/typeahead/PhabricatorPhurlURLDatasource.php b/src/applications/phurl/typeahead/PhabricatorPhurlURLDatasource.php index cd01c265c8..f8d3cda0cd 100644 --- a/src/applications/phurl/typeahead/PhabricatorPhurlURLDatasource.php +++ b/src/applications/phurl/typeahead/PhabricatorPhurlURLDatasource.php @@ -24,6 +24,7 @@ final class PhabricatorPhurlURLDatasource ->setDisplayName($url->getName()) ->setName($url->getName()." ".$url->getAlias()) ->setPHID($url->getPHID()) + ->setAutocomplete('(('.$url->getAlias().'))') ->addAttribute($url->getLongURL()); $results[] = $result; diff --git a/src/applications/project/engineextension/ProjectDatasourceEngineExtension.php b/src/applications/project/engineextension/ProjectDatasourceEngineExtension.php new file mode 100644 index 0000000000..b566d58f93 --- /dev/null +++ b/src/applications/project/engineextension/ProjectDatasourceEngineExtension.php @@ -0,0 +1,57 @@ +getViewer(); + + // Send "p" to Projects. + if (preg_match('/^p\z/i', $query)) { + return '/diffusion/'; + } + + // Send "p " to a search for similar projects. + $matches = null; + if (preg_match('/^p\s+(.+)\z/i', $query, $matches)) { + $raw_query = $matches[1]; + + $engine = id(new PhabricatorProject()) + ->newFerretEngine(); + + $compiler = id(new PhutilSearchQueryCompiler()) + ->setEnableFunctions(true); + + $raw_tokens = $compiler->newTokens($raw_query); + + $fulltext_tokens = array(); + foreach ($raw_tokens as $raw_token) { + $fulltext_token = id(new PhabricatorFulltextToken()) + ->setToken($raw_token); + $fulltext_tokens[] = $fulltext_token; + } + + $projects = id(new PhabricatorProjectQuery()) + ->setViewer($viewer) + ->withFerretConstraint($engine, $fulltext_tokens) + ->execute(); + if (count($projects) == 1) { + // Just one match, jump to project. + return head($projects)->getURI(); + } else { + // More than one match, jump to search. + return urisprintf( + '/project/?order=relevance&query=%s#R', + $raw_query); + } + } + + return null; + } +} diff --git a/src/applications/project/engineextension/ProjectQuickSearchEngineExtension.php b/src/applications/project/engineextension/ProjectQuickSearchEngineExtension.php deleted file mode 100644 index 6bb50e021e..0000000000 --- a/src/applications/project/engineextension/ProjectQuickSearchEngineExtension.php +++ /dev/null @@ -1,11 +0,0 @@ -getViewer(); + $query = $request->getStr('query'); - if ($request->getStr('jump') != 'no') { - $response = PhabricatorJumpNavHandler::getJumpResponse( - $viewer, - $request->getStr('query')); - if ($response) { - return $response; + if ($request->getStr('jump') != 'no' && strlen($query)) { + $jump_uri = id(new PhabricatorDatasourceEngine()) + ->setViewer($viewer) + ->newJumpURI($query); + + if ($jump_uri !== null) { + return id(new AphrontRedirectResponse())->setURI($jump_uri); } } @@ -29,7 +31,7 @@ final class PhabricatorSearchController if ($request->getBool('search:primary')) { // If there's no query, just take the user to advanced search. - if (!strlen($request->getStr('query'))) { + if (!strlen($query)) { $advanced_uri = '/search/query/advanced/'; return id(new AphrontRedirectResponse())->setURI($advanced_uri); } @@ -71,7 +73,7 @@ final class PhabricatorSearchController // Add the user's query, then save this as a new saved query and send // the user to the results page. - $saved->setParameter('query', $request->getStr('query')); + $saved->setParameter('query', $query); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); try { diff --git a/src/applications/search/engine/PhabricatorDatasourceEngine.php b/src/applications/search/engine/PhabricatorDatasourceEngine.php new file mode 100644 index 0000000000..a9cb766242 --- /dev/null +++ b/src/applications/search/engine/PhabricatorDatasourceEngine.php @@ -0,0 +1,55 @@ +viewer = $viewer; + return $this; + } + + public function getViewer() { + return $this->viewer; + } + + public function getAllQuickSearchDatasources() { + return PhabricatorDatasourceEngineExtension::getAllQuickSearchDatasources(); + } + + public function newJumpURI($query) { + $viewer = $this->getViewer(); + $extensions = PhabricatorDatasourceEngineExtension::getAllExtensions(); + + foreach ($extensions as $extension) { + $jump_uri = id(clone $extension) + ->setViewer($viewer) + ->newJumpURI($query); + + if ($jump_uri !== null) { + return $jump_uri; + } + } + + return null; + } + + public function newDatasourcesForCompositeDatasource( + PhabricatorTypeaheadCompositeDatasource $datasource) { + $viewer = $this->getViewer(); + $extensions = PhabricatorDatasourceEngineExtension::getAllExtensions(); + + $sources = array(); + foreach ($extensions as $extension) { + $extension_sources = id(clone $extension) + ->setViewer($viewer) + ->newDatasourcesForCompositeDatasource($datasource); + foreach ($extension_sources as $extension_source) { + $sources[] = $extension_source; + } + } + + return $sources; + } + +} diff --git a/src/applications/search/engine/PhabricatorJumpNavHandler.php b/src/applications/search/engine/PhabricatorJumpNavHandler.php deleted file mode 100644 index ddade36d84..0000000000 --- a/src/applications/search/engine/PhabricatorJumpNavHandler.php +++ /dev/null @@ -1,135 +0,0 @@ - 'uri:/diffusion/commit/', - '/^f$/i' => 'uri:/feed/', - '/^d$/i' => 'uri:/differential/', - '/^r$/i' => 'uri:/diffusion/', - '/^t$/i' => 'uri:/maniphest/', - '/^p$/i' => 'uri:/project/', - '/^u$/i' => 'uri:/people/', - '/^p\s+(.+)$/i' => 'project', - '/^u\s+(\S+)$/i' => 'user', - '/^(?:s)\s+(\S+)/i' => 'find-symbol', - '/^r\s+(.+)$/i' => 'find-repository', - ); - - foreach ($patterns as $pattern => $effect) { - $matches = null; - if (preg_match($pattern, $jump, $matches)) { - if (!strncmp($effect, 'uri:', 4)) { - return id(new AphrontRedirectResponse()) - ->setURI(substr($effect, 4)); - } else { - switch ($effect) { - case 'user': - return id(new AphrontRedirectResponse()) - ->setURI('/p/'.$matches[1].'/'); - case 'project': - $project = self::findCloselyNamedProject($matches[1]); - if ($project) { - return id(new AphrontRedirectResponse()) - ->setURI('/project/view/'.$project->getID().'/'); - } else { - $jump = $matches[1]; - } - break; - case 'find-symbol': - $context = ''; - $symbol = $matches[1]; - $parts = array(); - if (preg_match('/(.*)(?:\\.|::|->)(.*)/', $symbol, $parts)) { - $context = '&context='.phutil_escape_uri($parts[1]); - $symbol = $parts[2]; - } - return id(new AphrontRedirectResponse()) - ->setURI("/diffusion/symbol/$symbol/?jump=true$context"); - case 'find-repository': - $raw_query = $matches[1]; - - $engine = id(new PhabricatorRepository()) - ->newFerretEngine(); - - $compiler = id(new PhutilSearchQueryCompiler()) - ->setEnableFunctions(true); - - $raw_tokens = $compiler->newTokens($raw_query); - - $fulltext_tokens = array(); - foreach ($raw_tokens as $raw_token) { - $fulltext_token = id(new PhabricatorFulltextToken()) - ->setToken($raw_token); - $fulltext_tokens[] = $fulltext_token; - } - - $repositories = id(new PhabricatorRepositoryQuery()) - ->setViewer($viewer) - ->withFerretConstraint($engine, $fulltext_tokens) - ->execute(); - if (count($repositories) == 1) { - // Just one match, jump to repository. - $uri = head($repositories)->getURI(); - } else { - // More than one match, jump to search. - $uri = urisprintf( - '/diffusion/?order=name&query=%s', - $raw_query); - } - return id(new AphrontRedirectResponse())->setURI($uri); - default: - throw new Exception(pht("Unknown jump effect '%s'!", $effect)); - } - } - } - } - - // If none of the patterns matched, look for an object by name. - $objects = id(new PhabricatorObjectQuery()) - ->setViewer($viewer) - ->withNames(array($jump)) - ->execute(); - - if (count($objects) == 1) { - $handle = id(new PhabricatorHandleQuery()) - ->setViewer($viewer) - ->withPHIDs(mpull($objects, 'getPHID')) - ->executeOne(); - - return id(new AphrontRedirectResponse())->setURI($handle->getURI()); - } - - return null; - } - - private static function findCloselyNamedProject($name) { - $project = id(new PhabricatorProject())->loadOneWhere( - 'name = %s', - $name); - if ($project) { - return $project; - } else { // no exact match, try a fuzzy match - $projects = id(new PhabricatorProject())->loadAllWhere( - 'name LIKE %~', - $name); - if ($projects) { - $min_name_length = PHP_INT_MAX; - $best_project = null; - foreach ($projects as $project) { - $name_length = strlen($project->getName()); - if ($name_length <= $min_name_length) { - $min_name_length = $name_length; - $best_project = $project; - } - } - return $best_project; - } else { - return null; - } - } - } -} diff --git a/src/applications/search/engine/PhabricatorQuickSearchEngine.php b/src/applications/search/engine/PhabricatorQuickSearchEngine.php deleted file mode 100644 index 4b67dca659..0000000000 --- a/src/applications/search/engine/PhabricatorQuickSearchEngine.php +++ /dev/null @@ -1,8 +0,0 @@ -viewer = $viewer; + return $this; + } + + final public function getViewer() { + return $this->viewer; + } + + public function newQuickSearchDatasources() { + return array(); + } + + public function newJumpURI($query) { + return null; + } + + public function newDatasourcesForCompositeDatasource( + PhabricatorTypeaheadCompositeDatasource $datasource) { + return array(); + } + + final public static function getAllExtensions() { + return id(new PhutilClassMapQuery()) + ->setAncestorClass(__CLASS__) + ->execute(); + } + + final public static function getAllQuickSearchDatasources() { + $extensions = self::getAllExtensions(); + + $datasources = array(); + foreach ($extensions as $extension) { + $datasources[] = $extension->newQuickSearchDatasources(); + } + + return array_mergev($datasources); + } +} diff --git a/src/applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php b/src/applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php index 43f0b4c3ca..082eb77e70 100644 --- a/src/applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php +++ b/src/applications/search/engineextension/PhabricatorQuickSearchEngineExtension.php @@ -1,18 +1,6 @@ setAncestorClass(__CLASS__) - ->execute(); - - $datasources = array(); - foreach ($extensions as $extension) { - $datasources[] = $extension->newQuickSearchDatasources(); - } - return array_mergev($datasources); - } -} +// TODO: This is an older name "PhabricatorDatasourceEngineExtension" purely +// to preserve compatibility that should be removed soon. +abstract class PhabricatorQuickSearchEngineExtension + extends PhabricatorDatasourceEngineExtension {} diff --git a/src/applications/search/typeahead/PhabricatorSearchDatasource.php b/src/applications/search/typeahead/PhabricatorSearchDatasource.php index ed17a31185..2d8182a2a2 100644 --- a/src/applications/search/typeahead/PhabricatorSearchDatasource.php +++ b/src/applications/search/typeahead/PhabricatorSearchDatasource.php @@ -16,8 +16,8 @@ final class PhabricatorSearchDatasource } public function getComponentDatasources() { - $sources = id(new PhabricatorQuickSearchEngine()) - ->getAllDatasources(); + $sources = id(new PhabricatorDatasourceEngine()) + ->getAllQuickSearchDatasources(); // These results are always rendered in the full browse display mode, so // set the browse flag on all component sources. diff --git a/src/applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php b/src/applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php index bdc3c994b3..51ff40ed9d 100644 --- a/src/applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php +++ b/src/applications/settings/panel/PhabricatorEmailFormatSettingsPanel.php @@ -18,7 +18,9 @@ final class PhabricatorEmailFormatSettingsPanel } public function isManagementPanel() { - if (!$this->isUserPanel()) { + return false; +/* + if (!$this->isUserPanel()) { return false; } @@ -27,6 +29,7 @@ final class PhabricatorEmailFormatSettingsPanel } return false; +*/ } public function isTemplatePanel() { diff --git a/src/applications/transactions/editengine/PhabricatorEditEngine.php b/src/applications/transactions/editengine/PhabricatorEditEngine.php index ea3b987568..ea171a421c 100644 --- a/src/applications/transactions/editengine/PhabricatorEditEngine.php +++ b/src/applications/transactions/editengine/PhabricatorEditEngine.php @@ -1985,8 +1985,7 @@ abstract class PhabricatorEditEngine if (!$is_preview) { PhabricatorVersionedDraft::purgeDrafts( $object->getPHID(), - $viewer->getPHID(), - $this->loadDraftVersion($object)); + $viewer->getPHID()); $draft_engine = $this->newDraftEngine($object); if ($draft_engine) { diff --git a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php index d1413b49a6..1d02d0fdb0 100644 --- a/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php +++ b/src/applications/transactions/editor/PhabricatorApplicationTransactionEditor.php @@ -924,23 +924,13 @@ abstract class PhabricatorApplicationTransactionEditor if ($object->getID()) { $this->buildOldRecipientLists($object, $xactions); - foreach ($xactions as $xaction) { + $object->openTransaction(); + $transaction_open = true; - // If any of the transactions require a read lock, hold one and - // reload the object. We need to do this fairly early so that the - // call to `adjustTransactionValues()` (which populates old values) - // is based on the synchronized state of the object, which may differ - // from the state when it was originally loaded. + $object->beginReadLocking(); + $read_locking = true; - if ($this->shouldReadLock($object, $xaction)) { - $object->openTransaction(); - $object->beginReadLocking(); - $transaction_open = true; - $read_locking = true; - $object->reload(); - break; - } - } + $object->reload(); } if ($this->shouldApplyInitialEffects($object, $xactions)) { @@ -951,66 +941,57 @@ abstract class PhabricatorApplicationTransactionEditor } } - if ($this->shouldApplyInitialEffects($object, $xactions)) { - $this->applyInitialEffects($object, $xactions); - } - - foreach ($xactions as $xaction) { - $this->adjustTransactionValues($object, $xaction); - } - try { - $xactions = $this->filterTransactions($object, $xactions); - } catch (Exception $ex) { - if ($read_locking) { - $object->endReadLocking(); + if ($this->shouldApplyInitialEffects($object, $xactions)) { + $this->applyInitialEffects($object, $xactions); } - if ($transaction_open) { - $object->killTransaction(); - } - throw $ex; - } - // TODO: Once everything is on EditEngine, just use getIsNewObject() to - // figure this out instead. - $mark_as_create = false; - $create_type = PhabricatorTransactions::TYPE_CREATE; - foreach ($xactions as $xaction) { - if ($xaction->getTransactionType() == $create_type) { - $mark_as_create = true; - } - } - - if ($mark_as_create) { foreach ($xactions as $xaction) { - $xaction->setIsCreateTransaction(true); + $this->adjustTransactionValues($object, $xaction); } - } - // Now that we've merged, filtered, and combined transactions, check for - // required capabilities. - foreach ($xactions as $xaction) { - $this->requireCapabilities($object, $xaction); - } + $xactions = $this->filterTransactions($object, $xactions); - $xactions = $this->sortTransactions($xactions); - $file_phids = $this->extractFilePHIDs($object, $xactions); + // TODO: Once everything is on EditEngine, just use getIsNewObject() to + // figure this out instead. + $mark_as_create = false; + $create_type = PhabricatorTransactions::TYPE_CREATE; + foreach ($xactions as $xaction) { + if ($xaction->getTransactionType() == $create_type) { + $mark_as_create = true; + } + } - if ($is_preview) { - $this->loadHandles($xactions); - return $xactions; - } + if ($mark_as_create) { + foreach ($xactions as $xaction) { + $xaction->setIsCreateTransaction(true); + } + } - $comment_editor = id(new PhabricatorApplicationTransactionCommentEditor()) - ->setActor($actor) - ->setActingAsPHID($this->getActingAsPHID()) - ->setContentSource($this->getContentSource()); + // Now that we've merged, filtered, and combined transactions, check for + // required capabilities. + foreach ($xactions as $xaction) { + $this->requireCapabilities($object, $xaction); + } - if (!$transaction_open) { - $object->openTransaction(); - } + $xactions = $this->sortTransactions($xactions); + $file_phids = $this->extractFilePHIDs($object, $xactions); + + if ($is_preview) { + $this->loadHandles($xactions); + return $xactions; + } + + $comment_editor = id(new PhabricatorApplicationTransactionCommentEditor()) + ->setActor($actor) + ->setActingAsPHID($this->getActingAsPHID()) + ->setContentSource($this->getContentSource()); + + if (!$transaction_open) { + $object->openTransaction(); + $transaction_open = true; + } - try { foreach ($xactions as $xaction) { $this->applyInternalEffects($object, $xaction); } @@ -1070,14 +1051,27 @@ abstract class PhabricatorApplicationTransactionEditor } $xactions = $this->applyFinalEffects($object, $xactions); + if ($read_locking) { $object->endReadLocking(); $read_locking = false; } - $object->saveTransaction(); + if ($transaction_open) { + $object->saveTransaction(); + $transaction_open = false; + } } catch (Exception $ex) { - $object->killTransaction(); + if ($read_locking) { + $object->endReadLocking(); + $read_locking = false; + } + + if ($transaction_open) { + $object->killTransaction(); + $transaction_open = false; + } + throw $ex; } @@ -1319,46 +1313,6 @@ abstract class PhabricatorApplicationTransactionEditor return $xactions; } - - /** - * Determine if the editor should hold a read lock on the object while - * applying a transaction. - * - * If the editor does not hold a lock, two editors may read an object at the - * same time, then apply their changes without any synchronization. For most - * transactions, this does not matter much. However, it is important for some - * transactions. For example, if an object has a transaction count on it, both - * editors may read the object with `count = 23`, then independently update it - * and save the object with `count = 24` twice. This will produce the wrong - * state: the object really has 25 transactions, but the count is only 24. - * - * Generally, transactions fall into one of four buckets: - * - * - Append operations: Actions like adding a comment to an object purely - * add information to its state, and do not depend on the current object - * state in any way. These transactions never need to hold locks. - * - Overwrite operations: Actions like changing the title or description - * of an object replace the current value with a new value, so the end - * state is consistent without a lock. We currently do not lock these - * transactions, although we may in the future. - * - Edge operations: Edge and subscription operations have internal - * synchronization which limits the damage race conditions can cause. - * We do not currently lock these transactions, although we may in the - * future. - * - Update operations: Actions like incrementing a count on an object. - * These operations generally should use locks, unless it is not - * important that the state remain consistent in the presence of races. - * - * @param PhabricatorLiskDAO Object being updated. - * @param PhabricatorApplicationTransaction Transaction being applied. - * @return bool True to synchronize the edit with a lock. - */ - protected function shouldReadLock( - PhabricatorLiskDAO $object, - PhabricatorApplicationTransaction $xaction) { - return false; - } - private function loadHandles(array $xactions) { $phids = array(); foreach ($xactions as $key => $xaction) { @@ -2926,11 +2880,13 @@ abstract class PhabricatorApplicationTransactionEditor PhabricatorLiskDAO $object, array $xactions) { - $body = new PhabricatorMetaMTAMailBody(); - $body->setViewer($this->requireActor()); + $body = id(new PhabricatorMetaMTAMailBody()) + ->setViewer($this->requireActor()) + ->setContextObject($object); $this->addHeadersAndCommentsToMailBody($body, $xactions); $this->addCustomFieldsToMailBody($body, $object, $xactions); + return $body; } @@ -3308,10 +3264,32 @@ abstract class PhabricatorApplicationTransactionEditor $this->setHeraldTranscript($xscript); if ($adapter instanceof HarbormasterBuildableAdapterInterface) { + $buildable_phid = $adapter->getHarbormasterBuildablePHID(); + HarbormasterBuildable::applyBuildPlans( - $adapter->getHarbormasterBuildablePHID(), + $buildable_phid, $adapter->getHarbormasterContainerPHID(), $adapter->getQueuedHarbormasterBuildRequests()); + + // Whether we queued any builds or not, any automatic buildable for this + // object is now done preparing builds and can transition into a + // completed status. + $buildables = id(new HarbormasterBuildableQuery()) + ->setViewer(PhabricatorUser::getOmnipotentUser()) + ->withManualBuildables(false) + ->withBuildablePHIDs(array($buildable_phid)) + ->execute(); + foreach ($buildables as $buildable) { + // If this buildable has already moved beyond preparation, we don't + // need to nudge it again. + if (!$buildable->isPreparing()) { + continue; + } + $buildable->sendMessage( + $this->getActor(), + HarbormasterMessageType::BUILDABLE_BUILD, + true); + } } $this->mustEncrypt = $adapter->getMustEncryptReasons(); diff --git a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php index d27370cd44..015e601b02 100644 --- a/src/applications/transactions/storage/PhabricatorApplicationTransaction.php +++ b/src/applications/transactions/storage/PhabricatorApplicationTransaction.php @@ -512,9 +512,9 @@ abstract class PhabricatorApplicationTransaction break; case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { - case HarbormasterBuildable::STATUS_PASSED: + case HarbormasterBuildableStatus::STATUS_PASSED: return 'green'; - case HarbormasterBuildable::STATUS_FAILED: + case HarbormasterBuildableStatus::STATUS_FAILED: return 'red'; } break; @@ -676,7 +676,7 @@ abstract class PhabricatorApplicationTransaction return true; case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { - case HarbormasterBuildable::STATUS_FAILED: + case HarbormasterBuildableStatus::STATUS_FAILED: // For now, only ever send mail when builds fail. We might let // you customize this later, but in most cases this is probably // completely uninteresting. @@ -739,7 +739,7 @@ abstract class PhabricatorApplicationTransaction return true; case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { - case HarbormasterBuildable::STATUS_FAILED: + case HarbormasterBuildableStatus::STATUS_FAILED: // For now, don't notify on build passes either. These are pretty // high volume and annoying, with very little present value. We // might want to turn them back on in the specific case of @@ -1024,19 +1024,19 @@ abstract class PhabricatorApplicationTransaction case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { - case HarbormasterBuildable::STATUS_BUILDING: + case HarbormasterBuildableStatus::STATUS_BUILDING: return pht( '%s started building %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID'))); - case HarbormasterBuildable::STATUS_PASSED: + case HarbormasterBuildableStatus::STATUS_PASSED: return pht( '%s completed building %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID'))); - case HarbormasterBuildable::STATUS_FAILED: + case HarbormasterBuildableStatus::STATUS_FAILED: return pht( '%s failed to build %s!', $this->renderHandleLink($author_phid), @@ -1236,21 +1236,21 @@ abstract class PhabricatorApplicationTransaction } case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { - case HarbormasterBuildable::STATUS_BUILDING: + case HarbormasterBuildableStatus::STATUS_BUILDING: return pht( '%s started building %s for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID')), $this->renderHandleLink($object_phid)); - case HarbormasterBuildable::STATUS_PASSED: + case HarbormasterBuildableStatus::STATUS_PASSED: return pht( '%s completed building %s for %s.', $this->renderHandleLink($author_phid), $this->renderHandleLink( $this->getMetadataValue('harbormaster:buildablePHID')), $this->renderHandleLink($object_phid)); - case HarbormasterBuildable::STATUS_FAILED: + case HarbormasterBuildableStatus::STATUS_FAILED: return pht( '%s failed to build %s for %s.', $this->renderHandleLink($author_phid), @@ -1418,9 +1418,9 @@ abstract class PhabricatorApplicationTransaction return pht('Changed Subscribers'); case PhabricatorTransactions::TYPE_BUILDABLE: switch ($this->getNewValue()) { - case HarbormasterBuildable::STATUS_PASSED: + case HarbormasterBuildableStatus::STATUS_PASSED: return pht('Build Passed'); - case HarbormasterBuildable::STATUS_FAILED: + case HarbormasterBuildableStatus::STATUS_FAILED: return pht('Build Failed'); default: return pht('Build Status'); diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php index 33f06e4ae5..5594044c42 100644 --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadCompositeDatasource.php @@ -223,8 +223,17 @@ abstract class PhabricatorTypeaheadCompositeDatasource private function getUsableDatasources() { if ($this->usable === null) { + $viewer = $this->getViewer(); + $sources = $this->getComponentDatasources(); + $extension_sources = id(new PhabricatorDatasourceEngine()) + ->setViewer($viewer) + ->newDatasourcesForCompositeDatasource($this); + foreach ($extension_sources as $extension_source) { + $sources[] = $extension_source; + } + $usable = array(); foreach ($sources as $source) { $application_class = $source->getDatasourceApplicationClass(); @@ -239,7 +248,7 @@ abstract class PhabricatorTypeaheadCompositeDatasource } } - $source->setViewer($this->getViewer()); + $source->setViewer($viewer); $usable[] = $source; } $this->usable = $usable; diff --git a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php index 8b747281b3..2e369a3f67 100644 --- a/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php +++ b/src/applications/typeahead/datasource/PhabricatorTypeaheadDatasource.php @@ -406,7 +406,7 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject { $results = $this->evaluateValues($results); foreach ($evaluate as $result_key => $function) { - $function = self::parseFunction($function); + $function = $this->parseFunction($function); if (!$function) { throw new PhabricatorTypeaheadInvalidTokenException(); } @@ -459,28 +459,69 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject { /** * @task functions */ - public function parseFunction($token, $allow_partial = false) { + protected function parseFunction($token, $allow_partial = false) { $matches = null; if ($allow_partial) { - $ok = preg_match('/^([^(]+)\((.*?)\)?$/', $token, $matches); + $ok = preg_match('/^([^(]+)\((.*?)\)?\z/', $token, $matches); } else { - $ok = preg_match('/^([^(]+)\((.*)\)$/', $token, $matches); + $ok = preg_match('/^([^(]+)\((.*)\)\z/', $token, $matches); } if (!$ok) { + if (!$allow_partial) { + throw new PhabricatorTypeaheadInvalidTokenException( + pht( + 'Unable to parse function and arguments for token "%s".', + $token)); + } return null; } $function = trim($matches[1]); if (!$this->canEvaluateFunction($function)) { + if (!$allow_partial) { + throw new PhabricatorTypeaheadInvalidTokenException( + pht( + 'This datasource ("%s") can not evaluate the function "%s(...)".', + get_class($this), + $function)); + } + return null; } + // TODO: There is currently no way to quote characters in arguments, so + // some characters can't be argument characters. Replace this with a real + // parser once we get use cases. + + $argv = $matches[2]; + $argv = trim($argv); + if (!strlen($argv)) { + $argv = array(); + } else { + $argv = preg_split('/,/', $matches[2]); + foreach ($argv as $key => $arg) { + $argv[$key] = trim($arg); + } + } + + foreach ($argv as $key => $arg) { + if (self::isFunctionToken($arg)) { + $subfunction = $this->parseFunction($arg); + + $results = $this->evaluateFunction( + $subfunction['name'], + array($subfunction['argv'])); + + $argv[$key] = head($results); + } + } + return array( 'name' => $function, - 'argv' => array(trim($matches[2])), + 'argv' => $argv, ); } diff --git a/src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadDatasourceTestCase.php b/src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadDatasourceTestCase.php index 150275a332..c58596f025 100644 --- a/src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadDatasourceTestCase.php +++ b/src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadDatasourceTestCase.php @@ -48,4 +48,46 @@ final class PhabricatorTypeaheadDatasourceTestCase pht('Tokenization of "%s"', $input)); } + public function testFunctionEvaluation() { + $viewer = PhabricatorUser::getOmnipotentUser(); + + $datasource = id(new PhabricatorTypeaheadTestNumbersDatasource()) + ->setViewer($viewer); + + $constraints = $datasource->evaluateTokens( + array( + 9, + 'seven()', + 12, + 3, + )); + + $this->assertEqual( + array(9, 7, 12, 3), + $constraints); + + $map = array( + 'inc(3)' => 4, + 'sum(3, 4)' => 7, + 'inc(seven())' => 8, + 'inc(inc(3))' => 5, + 'inc(inc(seven()))' => 9, + 'sum(seven(), seven())' => 14, + 'sum(inc(seven()), inc(inc(9)))' => 19, + ); + + foreach ($map as $input => $expect) { + $constraints = $datasource->evaluateTokens( + array( + $input, + )); + + $this->assertEqual( + array($expect), + $constraints, + pht('Constraints for input "%s".', $input)); + } + } + + } diff --git a/src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php b/src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php new file mode 100644 index 0000000000..f16a64bb9b --- /dev/null +++ b/src/applications/typeahead/datasource/__tests__/PhabricatorTypeaheadTestNumbersDatasource.php @@ -0,0 +1,67 @@ + array(), + 'inc' => array(), + 'sum' => array(), + ); + } + + public function loadResults() { + return array(); + } + + public function renderFunctionTokens($function, array $argv_list) { + return array(); + } + + protected function evaluateFunction($function, array $argv_list) { + $results = array(); + + foreach ($argv_list as $argv) { + foreach ($argv as $k => $arg) { + if (!is_scalar($arg) || !preg_match('/^\d+\z/', $arg)) { + throw new PhabricatorTypeaheadInvalidTokenException( + pht( + 'All arguments to "%s(...)" must be integers, found '. + '"%s" in position %d.', + $function, + (is_scalar($arg) ? $arg : gettype($arg)), + $k + 1)); + } + $argv[$k] = (int)$arg; + } + + switch ($function) { + case 'seven': + $results[] = 7; + break; + case 'inc': + $results[] = $argv[0] + 1; + break; + case 'sum': + $results[] = array_sum($argv); + break; + } + } + + return $results; + } + +} diff --git a/src/applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php b/src/applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php new file mode 100644 index 0000000000..ec34538dd9 --- /dev/null +++ b/src/applications/typeahead/engineextension/PhabricatorMonogramDatasourceEngineExtension.php @@ -0,0 +1,53 @@ +getViewer(); + + // These first few rules are sort of random but don't fit anywhere else + // today and don't feel worth adding separate extensions for. + + // Send "f" to Feed. + if (preg_match('/^f\z/i', $query)) { + return '/feed/'; + } + + // Send "d" to Differential. + if (preg_match('/^d\z/i', $query)) { + return '/differential/'; + } + + // Send "t" to Maniphest. + if (preg_match('/^t\z/i', $query)) { + return '/maniphest/'; + } + + // Otherwise, if the user entered an object name, jump to that object. + $objects = id(new PhabricatorObjectQuery()) + ->setViewer($viewer) + ->withNames(array($query)) + ->execute(); + if (count($objects) == 1) { + $object = head($objects); + $object_phid = $object->getPHID(); + + $handles = $viewer->loadHandles(array($object_phid)); + $handle = $handles[$object_phid]; + + if ($handle->isComplete()) { + return $handle->getURI(); + } + } + + return null; + } + +} diff --git a/src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php b/src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php deleted file mode 100644 index 078cb44685..0000000000 --- a/src/applications/typeahead/engineextension/PhabricatorMonogramQuickSearchEngineExtension.php +++ /dev/null @@ -1,11 +0,0 @@ -engineRuleset = $engine_ruleset; @@ -54,7 +58,34 @@ final class PhabricatorMarkupOneOff return $this->disableCache; } + public function setGenerateTableOfContents($generate) { + $this->generateTableOfContents = $generate; + return $this; + } + + public function getGenerateTableOfContents() { + return $this->generateTableOfContents; + } + + public function getTableOfContents() { + return $this->tableOfContents; + } + + public function setContentCacheFragment($fragment) { + $this->contentCacheFragment = $fragment; + return $this; + } + + public function getContentCacheFragment() { + return $this->contentCacheFragment; + } + public function getMarkupFieldKey($field) { + $fragment = $this->getContentCacheFragment(); + if ($fragment !== null) { + return $fragment; + } + return PhabricatorHash::digestForIndex($this->getContent()).':oneoff'; } @@ -81,7 +112,13 @@ final class PhabricatorMarkupOneOff $output, PhutilMarkupEngine $engine) { + if ($this->getGenerateTableOfContents()) { + $toc = PhutilRemarkupHeaderBlockRule::renderTableOfContents($engine); + $this->tableOfContents = $toc; + } + require_celerity_resource('phabricator-remarkup-css'); + return phutil_tag( 'div', array( diff --git a/src/infrastructure/markup/view/PHUIRemarkupView.php b/src/infrastructure/markup/view/PHUIRemarkupView.php index e5772e9d84..d60d28b5d3 100644 --- a/src/infrastructure/markup/view/PHUIRemarkupView.php +++ b/src/infrastructure/markup/view/PHUIRemarkupView.php @@ -14,11 +14,14 @@ final class PHUIRemarkupView extends AphrontView { private $corpus; private $contextObject; private $options; + private $oneoff; + private $generateTableOfContents; // TODO: In the long run, rules themselves should define available options. // For now, just define constants here so we can more easily replace things // later once this is cleaned up. const OPTION_PRESERVE_LINEBREAKS = 'preserve-linebreaks'; + const OPTION_GENERATE_TOC = 'header.generate-toc'; public function __construct(PhabricatorUser $viewer, $corpus) { $this->setUser($viewer); @@ -46,6 +49,19 @@ final class PHUIRemarkupView extends AphrontView { return $this; } + public function setGenerateTableOfContents($generate) { + $this->generateTableOfContents = $generate; + return $this; + } + + public function getGenerateTableOfContents() { + return $this->generateTableOfContents; + } + + public function getTableOfContents() { + return $this->oneoff->getTableOfContents(); + } + public function render() { $viewer = $this->getViewer(); $corpus = $this->corpus; @@ -54,7 +70,8 @@ final class PHUIRemarkupView extends AphrontView { $options = $this->options; $oneoff = id(new PhabricatorMarkupOneOff()) - ->setContent($corpus); + ->setContent($corpus) + ->setContentCacheFragment($this->getContentCacheFragment()); if ($options) { $oneoff->setEngine($this->getEngine()); @@ -62,6 +79,10 @@ final class PHUIRemarkupView extends AphrontView { $oneoff->setPreserveLinebreaks(true); } + $generate_toc = $this->getGenerateTableOfContents(); + $oneoff->setGenerateTableOfContents($generate_toc); + $this->oneoff = $oneoff; + $content = PhabricatorMarkupEngine::renderOneObject( $oneoff, 'default', @@ -76,10 +97,7 @@ final class PHUIRemarkupView extends AphrontView { $viewer = $this->getViewer(); $viewer_key = $viewer->getCacheFragment(); - - ksort($options); - $engine_key = serialize($options); - $engine_key = PhabricatorHash::digestForIndex($engine_key); + $engine_key = $this->getEngineCacheFragment(); $cache = PhabricatorCaches::getRequestCache(); $cache_key = "remarkup.engine({$viewer_key}, {$engine_key})"; @@ -93,4 +111,28 @@ final class PHUIRemarkupView extends AphrontView { return $engine; } + private function getEngineCacheFragment() { + $options = $this->options; + + ksort($options); + + $engine_key = serialize($options); + $engine_key = PhabricatorHash::digestForIndex($engine_key); + + return $engine_key; + } + + private function getContentCacheFragment() { + $corpus = $this->corpus; + + $content_fragment = PhabricatorHash::digestForIndex($corpus); + $options_fragment = array( + 'toc' => $this->getGenerateTableOfContents(), + ); + $options_fragment = serialize($options_fragment); + $options_fragment = PhabricatorHash::digestForIndex($options_fragment); + + return "remarkup({$content_fragment}, {$options_fragment})"; + } + } diff --git a/src/infrastructure/status/PhabricatorObjectStatus.php b/src/infrastructure/status/PhabricatorObjectStatus.php new file mode 100644 index 0000000000..27dec48dc9 --- /dev/null +++ b/src/infrastructure/status/PhabricatorObjectStatus.php @@ -0,0 +1,84 @@ +key = $key; + $this->properties = $properties; + } + + protected function getStatusProperty($key) { + if (!array_key_exists($key, $this->properties)) { + throw new Exception( + pht( + 'Attempting to access unknown status property ("%s").', + $key)); + } + + return $this->properties[$key]; + } + + public function getKey() { + return $this->key; + } + + public function getIcon() { + return $this->getStatusProperty('icon'); + } + + public function getDisplayName() { + return $this->getStatusProperty('name'); + } + + public function getColor() { + return $this->getStatusProperty('color'); + } + + protected function getStatusSpecification($status) { + $map = self::getStatusSpecifications(); + if (isset($map[$status])) { + return $map[$status]; + } + + return array( + 'key' => $status, + 'name' => pht('Unknown ("%s")', $status), + 'icon' => 'fa-question-circle', + 'color' => 'indigo', + ) + $this->newUnknownStatusSpecification($status); + } + + protected function getStatusSpecifications() { + $map = $this->newStatusSpecifications(); + + $result = array(); + foreach ($map as $item) { + if (!array_key_exists('key', $item)) { + throw new Exception(pht('Status specification has no "key".')); + } + + $key = $item['key']; + if (isset($result[$key])) { + throw new Exception( + pht( + 'Multiple status definitions share the same key ("%s").', + $key)); + } + + $result[$key] = $item; + } + + return $result; + } + + abstract protected function newStatusSpecifications(); + + protected function newUnknownStatusSpecification($status) { + return array(); + } + +} diff --git a/src/view/form/control/PhabricatorRemarkupControl.php b/src/view/form/control/PhabricatorRemarkupControl.php index 5d0cc079e7..dba0ef0546 100644 --- a/src/view/form/control/PhabricatorRemarkupControl.php +++ b/src/view/form/control/PhabricatorRemarkupControl.php @@ -72,6 +72,9 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { 'autocomplete' => 1, )); + $phriction_datasource = new PhrictionDocumentDatasource(); + $phurl_datasource = new PhabricatorPhurlURLDatasource(); + Javelin::initBehavior( 'phabricator-remarkup-assist', array( @@ -118,6 +121,28 @@ final class PhabricatorRemarkupControl extends AphrontFormTextAreaControl { '/', ), ), + 91 => array( // "[" + 'datasourceURI' => $phriction_datasource->getDatasourceURI(), + 'headerIcon' => 'fa-book', + 'headerText' => pht('Find Document:'), + 'hintText' => $phriction_datasource->getPlaceholderText(), + 'cancel' => array( + ':', // Cancel on "http:" and similar. + '|', + ']', + ), + 'prefix' => '^\\[', + ), + 40 => array( // "(" + 'datasourceURI' => $phurl_datasource->getDatasourceURI(), + 'headerIcon' => 'fa-compress', + 'headerText' => pht('Find Phurl:'), + 'hintText' => $phurl_datasource->getPlaceholderText(), + 'cancel' => array( + ')', + ), + 'prefix' => '^\\(', + ), ), )); Javelin::initBehavior('phabricator-tooltips', array()); diff --git a/webroot/rsrc/js/application/diff/DiffChangesetList.js b/webroot/rsrc/js/application/diff/DiffChangesetList.js index e62d2f51dd..5eba051d93 100644 --- a/webroot/rsrc/js/application/diff/DiffChangesetList.js +++ b/webroot/rsrc/js/application/diff/DiffChangesetList.js @@ -128,6 +128,9 @@ JX.install('DiffChangesetList', { this._redrawFocus(); this._redrawSelection(); this.resetHover(); + + this._bannerChangeset = null; + this._redrawBanner(); }, wake: function() { @@ -136,6 +139,9 @@ JX.install('DiffChangesetList', { this._redrawFocus(); this._redrawSelection(); + this._bannerChangeset = null; + this._redrawBanner(); + if (this._initialized) { return; } @@ -1374,6 +1380,11 @@ JX.install('DiffChangesetList', { var node = this._getBannerNode(); var changeset = this._getVisibleChangeset(); + if (!changeset) { + JX.DOM.remove(node); + return; + } + // Don't do anything if nothing has changed. This seems to avoid some // flickering issues in Safari, at least. if (this._bannerChangeset === changeset) { @@ -1381,11 +1392,6 @@ JX.install('DiffChangesetList', { } this._bannerChangeset = changeset; - if (!changeset) { - JX.DOM.remove(node); - return; - } - var inlines = this._getInlinesByType(); var unsaved = inlines.unsaved; diff --git a/webroot/rsrc/js/core/behavior-search-typeahead.js b/webroot/rsrc/js/core/behavior-search-typeahead.js index ddcc2e6aaa..0f5b4d1639 100644 --- a/webroot/rsrc/js/core/behavior-search-typeahead.js +++ b/webroot/rsrc/js/core/behavior-search-typeahead.js @@ -70,6 +70,7 @@ JX.behavior('phabricator-search-typeahead', function(config) { 'proj', 'user', 'repo', + 'wiki', 'symb', 'misc' ]; diff --git a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js index a7f719dab4..8f062d900e 100644 --- a/webroot/rsrc/js/phuix/PHUIXAutocomplete.js +++ b/webroot/rsrc/js/phuix/PHUIXAutocomplete.js @@ -107,6 +107,12 @@ JX.install('PHUIXAutocomplete', { prior = ''; } + // If this is a repeating sequence and the previous character is the + // same as the one the user just typed, like "((", don't reactivate. + if (prior === String.fromCharCode(code)) { + return; + } + switch (prior) { case '': case ' ': @@ -335,7 +341,9 @@ JX.install('PHUIXAutocomplete', { _getCancelCharacters: function() { // The "." character does not cancel because of projects named // "node.js" or "blog.mycompany.com". - return ['#', '@', ',', '!', '?', '{', '}']; + var defaults = ['#', '@', ',', '!', '?', '{', '}']; + + return this._map[this._active].cancel || defaults; }, _getTerminators: function() { @@ -498,8 +506,6 @@ JX.install('PHUIXAutocomplete', { this._cursorHead, this._cursorTail); - this._value = text; - var pixels = JX.TextAreaUtils.getPixelDimensions( area, range.start, @@ -515,20 +521,38 @@ JX.install('PHUIXAutocomplete', { return; } - var trim = this._trim(text); - // Deactivate immediately if a user types a character that we are // reasonably sure means they don't want to use the autocomplete. For // example, "##" is almost certainly a header or monospaced text, not // a project autocompletion. var cancels = this._getCancelCharacters(); for (var ii = 0; ii < cancels.length; ii++) { - if (trim.indexOf(cancels[ii]) !== -1) { + if (text.indexOf(cancels[ii]) !== -1) { this._deactivate(); return; } } + var trim = this._trim(text); + + // If this rule has a prefix pattern, like the "[[ document ]]" rule, + // require it match and throw it away before we begin suggesting + // results. The autocomplete remains active, it's just dormant until + // the user gives us more to work with. + var prefix = this._map[this._active].prefix; + if (prefix) { + var pattern = new RegExp(prefix); + if (!trim.match(pattern)) { + return; + } + trim = trim.replace(pattern, ''); + trim = trim.trim(); + } + + // Store the current value now that we've finished mutating the text. + // This needs to match what we pass to the typeahead datasource. + this._value = trim; + // Deactivate immediately if the user types an ignored token like ":)", // the smiley face emoticon. Note that we test against "text", not // "trim", because the ignore list and suffix list can otherwise