1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-24 15:52:41 +01:00

(stable) Promote 2016 Week 25

This commit is contained in:
epriestley 2016-06-18 09:23:48 -07:00
commit 59bc6adc0e
124 changed files with 4008 additions and 945 deletions

View file

@ -7,7 +7,7 @@
*/ */
return array( return array(
'names' => array( 'names' => array(
'core.pkg.css' => '6913fe66', 'core.pkg.css' => 'c7fc5aec',
'core.pkg.js' => '10275c16', 'core.pkg.js' => '10275c16',
'darkconsole.pkg.js' => 'e7393ebb', 'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => 'b3eea3f5', 'differential.pkg.css' => 'b3eea3f5',
@ -81,7 +81,7 @@ return array(
'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b', 'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b',
'rsrc/css/application/paste/paste.css' => '1898e534', 'rsrc/css/application/paste/paste.css' => '1898e534',
'rsrc/css/application/people/people-profile.css' => '2473d929', 'rsrc/css/application/people/people-profile.css' => '2473d929',
'rsrc/css/application/phame/phame.css' => '7448a969', 'rsrc/css/application/phame/phame.css' => 'bf6a743f',
'rsrc/css/application/pholio/pholio-edit.css' => '07676f51', 'rsrc/css/application/pholio/pholio-edit.css' => '07676f51',
'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49', 'rsrc/css/application/pholio/pholio-inline-comments.css' => '8e545e49',
'rsrc/css/application/pholio/pholio.css' => 'ca89d380', 'rsrc/css/application/pholio/pholio.css' => 'ca89d380',
@ -128,7 +128,7 @@ return array(
'rsrc/css/phui/phui-chart.css' => '6bf6f78e', 'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
'rsrc/css/phui/phui-crumbs-view.css' => '6b813619', 'rsrc/css/phui/phui-crumbs-view.css' => '6b813619',
'rsrc/css/phui/phui-curtain-view.css' => '7148ae25', 'rsrc/css/phui/phui-curtain-view.css' => '7148ae25',
'rsrc/css/phui/phui-document-pro.css' => '8419560b', 'rsrc/css/phui/phui-document-pro.css' => 'a3730b94',
'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf', 'rsrc/css/phui/phui-document-summary.css' => '9ca48bdf',
'rsrc/css/phui/phui-document.css' => '715aedfb', 'rsrc/css/phui/phui-document.css' => '715aedfb',
'rsrc/css/phui/phui-feed-story.css' => 'aa49845d', 'rsrc/css/phui/phui-feed-story.css' => 'aa49845d',
@ -155,7 +155,7 @@ return array(
'rsrc/css/phui/phui-spacing.css' => '042804d6', 'rsrc/css/phui/phui-spacing.css' => '042804d6',
'rsrc/css/phui/phui-status.css' => 'd5263e49', 'rsrc/css/phui/phui-status.css' => 'd5263e49',
'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2', 'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2',
'rsrc/css/phui/phui-timeline-view.css' => '6e342216', 'rsrc/css/phui/phui-timeline-view.css' => '8ea41b25',
'rsrc/css/phui/phui-two-column-view.css' => '9fb86c85', 'rsrc/css/phui/phui-two-column-view.css' => '9fb86c85',
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7', 'rsrc/css/phui/workboards/phui-workboard-color.css' => 'ac6fe6a7',
'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647', 'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647',
@ -808,7 +808,7 @@ return array(
'phabricator-uiexample-reactor-sendclass' => '1def2711', 'phabricator-uiexample-reactor-sendclass' => '1def2711',
'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee', 'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
'phabricator-zindex-css' => '5b6fcf3f', 'phabricator-zindex-css' => '5b6fcf3f',
'phame-css' => '7448a969', 'phame-css' => 'bf6a743f',
'pholio-css' => 'ca89d380', 'pholio-css' => 'ca89d380',
'pholio-edit-css' => '07676f51', 'pholio-edit-css' => '07676f51',
'pholio-inline-comments-css' => '8e545e49', 'pholio-inline-comments-css' => '8e545e49',
@ -831,7 +831,7 @@ return array(
'phui-curtain-view-css' => '7148ae25', 'phui-curtain-view-css' => '7148ae25',
'phui-document-summary-view-css' => '9ca48bdf', 'phui-document-summary-view-css' => '9ca48bdf',
'phui-document-view-css' => '715aedfb', 'phui-document-view-css' => '715aedfb',
'phui-document-view-pro-css' => '8419560b', 'phui-document-view-pro-css' => 'a3730b94',
'phui-feed-story-css' => 'aa49845d', 'phui-feed-story-css' => 'aa49845d',
'phui-font-icon-base-css' => '6449bce8', 'phui-font-icon-base-css' => '6449bce8',
'phui-fontkit-css' => '9cda225e', 'phui-fontkit-css' => '9cda225e',
@ -860,7 +860,7 @@ return array(
'phui-status-list-view-css' => 'd5263e49', 'phui-status-list-view-css' => 'd5263e49',
'phui-tag-view-css' => '6bbd83e2', 'phui-tag-view-css' => '6bbd83e2',
'phui-theme-css' => '027ba77e', 'phui-theme-css' => '027ba77e',
'phui-timeline-view-css' => '6e342216', 'phui-timeline-view-css' => '8ea41b25',
'phui-two-column-view-css' => '9fb86c85', 'phui-two-column-view-css' => '9fb86c85',
'phui-workboard-color-css' => 'ac6fe6a7', 'phui-workboard-color-css' => 'ac6fe6a7',
'phui-workboard-view-css' => 'e6d89647', 'phui-workboard-view-css' => 'e6d89647',

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phame.phame_blog
ADD headerImagePHID VARBINARY(64);

View file

@ -0,0 +1,6 @@
CREATE TABLE {$NAMESPACE}_repository.repository_oldref (
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
repositoryPHID VARBINARY(64) NOT NULL,
commitIdentifier VARCHAR(40) NOT NULL COLLATE {$COLLATE_TEXT},
KEY `key_repository` (repositoryPHID)
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildartifact
ADD isReleased BOOL NOT NULL;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_phame.phame_blog
ADD subtitle VARCHAR(64) NOT NULL COLLATE {$COLLATE_TEXT};

View file

@ -1,47 +1,5 @@
<?php <?php
$table = new PhabricatorPaste(); // Long ago, this migration populated initial "create" transactions for old
$x_table = new PhabricatorPasteTransaction(); // pastes from before transactions came into existence. It was removed after
// about three years.
$conn_w = $table->establishConnection('w');
$conn_w->openTransaction();
echo pht('Adding transactions for existing paste objects...')."\n";
$rows = new LiskRawMigrationIterator($conn_w, 'pastebin_paste');
foreach ($rows as $row) {
$id = $row['id'];
echo pht('Adding transactions for paste id %d...', $id)."\n";
$xaction_phid = PhabricatorPHID::generateNewPHID(
PhabricatorApplicationTransactionTransactionPHIDType::TYPECONST);
queryfx(
$conn_w,
'INSERT INTO %T (phid, authorPHID, objectPHID, viewPolicy, editPolicy,
transactionType, oldValue, newValue,
contentSource, metadata, dateCreated, dateModified,
commentVersion)
VALUES (%s, %s, %s, %s, %s, %s, %ns, %ns, %s, %s, %d, %d, %d)',
$x_table->getTableName(),
$xaction_phid,
$row['authorPHID'],
$row['phid'],
'public',
$row['authorPHID'],
PhabricatorPasteTransaction::TYPE_CONTENT,
'null',
$row['filePHID'],
PhabricatorContentSource::newForSource(
PhabricatorOldWorldContentSource::SOURCECONST)->serialize(),
'[]',
$row['dateCreated'],
$row['dateCreated'],
0);
}
$conn_w->saveTransaction();
echo pht('Done.')."\n";

View file

@ -4,6 +4,11 @@
// This is a wrapper script for Git, Mercurial, and Subversion. It primarily // This is a wrapper script for Git, Mercurial, and Subversion. It primarily
// serves to inject "-o StrictHostKeyChecking=no" into the SSH arguments. // serves to inject "-o StrictHostKeyChecking=no" into the SSH arguments.
// In some cases, Subversion sends us SIGTERM. If we don't catch the signal and
// react to it, we won't run object destructors by default and thus won't clean
// up temporary files. Declare ticks so we can install a signal handler.
declare(ticks=1);
$root = dirname(dirname(dirname(__FILE__))); $root = dirname(dirname(dirname(__FILE__)));
require_once $root.'/scripts/__init_script__.php'; require_once $root.'/scripts/__init_script__.php';
@ -21,6 +26,16 @@ $args->parsePartial(
)); ));
$unconsumed_argv = $args->getUnconsumedArgumentVector(); $unconsumed_argv = $args->getUnconsumedArgumentVector();
if (function_exists('pcntl_signal')) {
pcntl_signal(SIGTERM, 'ssh_connect_signal');
}
function ssh_connect_signal($signo) {
// This is just letting destructors fire. In particular, we want to clean
// up any temporary files we wrote. See T10547.
exit(128 + $signo);
}
$pattern = array(); $pattern = array();
$arguments = array(); $arguments = array();

View file

@ -489,6 +489,7 @@ phutil_register_library_map(array(
'DifferentialReleephRequestFieldSpecification' => 'applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php', 'DifferentialReleephRequestFieldSpecification' => 'applications/releeph/differential/DifferentialReleephRequestFieldSpecification.php',
'DifferentialRemarkupRule' => 'applications/differential/remarkup/DifferentialRemarkupRule.php', 'DifferentialRemarkupRule' => 'applications/differential/remarkup/DifferentialRemarkupRule.php',
'DifferentialReplyHandler' => 'applications/differential/mail/DifferentialReplyHandler.php', 'DifferentialReplyHandler' => 'applications/differential/mail/DifferentialReplyHandler.php',
'DifferentialRepositoryDatasource' => 'applications/differential/typeahead/DifferentialRepositoryDatasource.php',
'DifferentialRepositoryField' => 'applications/differential/customfield/DifferentialRepositoryField.php', 'DifferentialRepositoryField' => 'applications/differential/customfield/DifferentialRepositoryField.php',
'DifferentialRepositoryLookup' => 'applications/differential/query/DifferentialRepositoryLookup.php', 'DifferentialRepositoryLookup' => 'applications/differential/query/DifferentialRepositoryLookup.php',
'DifferentialRequiredSignaturesField' => 'applications/differential/customfield/DifferentialRequiredSignaturesField.php', 'DifferentialRequiredSignaturesField' => 'applications/differential/customfield/DifferentialRequiredSignaturesField.php',
@ -821,6 +822,7 @@ phutil_register_library_map(array(
'DiffusionSymbolQuery' => 'applications/diffusion/query/DiffusionSymbolQuery.php', 'DiffusionSymbolQuery' => 'applications/diffusion/query/DiffusionSymbolQuery.php',
'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php', 'DiffusionTagListController' => 'applications/diffusion/controller/DiffusionTagListController.php',
'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php', 'DiffusionTagListView' => 'applications/diffusion/view/DiffusionTagListView.php',
'DiffusionTaggedRepositoriesFunctionDatasource' => 'applications/diffusion/typeahead/DiffusionTaggedRepositoriesFunctionDatasource.php',
'DiffusionTagsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php', 'DiffusionTagsQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionTagsQueryConduitAPIMethod.php',
'DiffusionURIEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionURIEditConduitAPIMethod.php', 'DiffusionURIEditConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionURIEditConduitAPIMethod.php',
'DiffusionURIEditEngine' => 'applications/diffusion/editor/DiffusionURIEditEngine.php', 'DiffusionURIEditEngine' => 'applications/diffusion/editor/DiffusionURIEditEngine.php',
@ -2158,6 +2160,9 @@ phutil_register_library_map(array(
'PhabricatorController' => 'applications/base/controller/PhabricatorController.php', 'PhabricatorController' => 'applications/base/controller/PhabricatorController.php',
'PhabricatorCookies' => 'applications/auth/constants/PhabricatorCookies.php', 'PhabricatorCookies' => 'applications/auth/constants/PhabricatorCookies.php',
'PhabricatorCoreConfigOptions' => 'applications/config/option/PhabricatorCoreConfigOptions.php', 'PhabricatorCoreConfigOptions' => 'applications/config/option/PhabricatorCoreConfigOptions.php',
'PhabricatorCoreCreateTransaction' => 'applications/transactions/xaction/PhabricatorCoreCreateTransaction.php',
'PhabricatorCoreTransactionType' => 'applications/transactions/xaction/PhabricatorCoreTransactionType.php',
'PhabricatorCoreVoidTransaction' => 'applications/transactions/xaction/PhabricatorCoreVoidTransaction.php',
'PhabricatorCountdown' => 'applications/countdown/storage/PhabricatorCountdown.php', 'PhabricatorCountdown' => 'applications/countdown/storage/PhabricatorCountdown.php',
'PhabricatorCountdownApplication' => 'applications/countdown/application/PhabricatorCountdownApplication.php', 'PhabricatorCountdownApplication' => 'applications/countdown/application/PhabricatorCountdownApplication.php',
'PhabricatorCountdownController' => 'applications/countdown/controller/PhabricatorCountdownController.php', 'PhabricatorCountdownController' => 'applications/countdown/controller/PhabricatorCountdownController.php',
@ -2463,6 +2468,7 @@ phutil_register_library_map(array(
'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php', 'PhabricatorFeedStoryPublisher' => 'applications/feed/PhabricatorFeedStoryPublisher.php',
'PhabricatorFeedStoryReference' => 'applications/feed/storage/PhabricatorFeedStoryReference.php', 'PhabricatorFeedStoryReference' => 'applications/feed/storage/PhabricatorFeedStoryReference.php',
'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php', 'PhabricatorFile' => 'applications/files/storage/PhabricatorFile.php',
'PhabricatorFileAES256StorageFormat' => 'applications/files/format/PhabricatorFileAES256StorageFormat.php',
'PhabricatorFileBundleLoader' => 'applications/files/query/PhabricatorFileBundleLoader.php', 'PhabricatorFileBundleLoader' => 'applications/files/query/PhabricatorFileBundleLoader.php',
'PhabricatorFileChunk' => 'applications/files/storage/PhabricatorFileChunk.php', 'PhabricatorFileChunk' => 'applications/files/storage/PhabricatorFileChunk.php',
'PhabricatorFileChunkIterator' => 'applications/files/engine/PhabricatorFileChunkIterator.php', 'PhabricatorFileChunkIterator' => 'applications/files/engine/PhabricatorFileChunkIterator.php',
@ -2485,12 +2491,16 @@ phutil_register_library_map(array(
'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php', 'PhabricatorFileLinkView' => 'view/layout/PhabricatorFileLinkView.php',
'PhabricatorFileListController' => 'applications/files/controller/PhabricatorFileListController.php', 'PhabricatorFileListController' => 'applications/files/controller/PhabricatorFileListController.php',
'PhabricatorFileQuery' => 'applications/files/query/PhabricatorFileQuery.php', 'PhabricatorFileQuery' => 'applications/files/query/PhabricatorFileQuery.php',
'PhabricatorFileROT13StorageFormat' => 'applications/files/format/PhabricatorFileROT13StorageFormat.php',
'PhabricatorFileRawStorageFormat' => 'applications/files/format/PhabricatorFileRawStorageFormat.php',
'PhabricatorFileSchemaSpec' => 'applications/files/storage/PhabricatorFileSchemaSpec.php', 'PhabricatorFileSchemaSpec' => 'applications/files/storage/PhabricatorFileSchemaSpec.php',
'PhabricatorFileSearchEngine' => 'applications/files/query/PhabricatorFileSearchEngine.php', 'PhabricatorFileSearchEngine' => 'applications/files/query/PhabricatorFileSearchEngine.php',
'PhabricatorFileStorageBlob' => 'applications/files/storage/PhabricatorFileStorageBlob.php', 'PhabricatorFileStorageBlob' => 'applications/files/storage/PhabricatorFileStorageBlob.php',
'PhabricatorFileStorageConfigurationException' => 'applications/files/exception/PhabricatorFileStorageConfigurationException.php', 'PhabricatorFileStorageConfigurationException' => 'applications/files/exception/PhabricatorFileStorageConfigurationException.php',
'PhabricatorFileStorageEngine' => 'applications/files/engine/PhabricatorFileStorageEngine.php', 'PhabricatorFileStorageEngine' => 'applications/files/engine/PhabricatorFileStorageEngine.php',
'PhabricatorFileStorageEngineTestCase' => 'applications/files/engine/__tests__/PhabricatorFileStorageEngineTestCase.php', 'PhabricatorFileStorageEngineTestCase' => 'applications/files/engine/__tests__/PhabricatorFileStorageEngineTestCase.php',
'PhabricatorFileStorageFormat' => 'applications/files/format/PhabricatorFileStorageFormat.php',
'PhabricatorFileStorageFormatTestCase' => 'applications/files/format/__tests__/PhabricatorFileStorageFormatTestCase.php',
'PhabricatorFileTemporaryGarbageCollector' => 'applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php', 'PhabricatorFileTemporaryGarbageCollector' => 'applications/files/garbagecollector/PhabricatorFileTemporaryGarbageCollector.php',
'PhabricatorFileTestCase' => 'applications/files/storage/__tests__/PhabricatorFileTestCase.php', 'PhabricatorFileTestCase' => 'applications/files/storage/__tests__/PhabricatorFileTestCase.php',
'PhabricatorFileTestDataGenerator' => 'applications/files/lipsum/PhabricatorFileTestDataGenerator.php', 'PhabricatorFileTestDataGenerator' => 'applications/files/lipsum/PhabricatorFileTestDataGenerator.php',
@ -2514,7 +2524,10 @@ phutil_register_library_map(array(
'PhabricatorFilesConfigOptions' => 'applications/files/config/PhabricatorFilesConfigOptions.php', 'PhabricatorFilesConfigOptions' => 'applications/files/config/PhabricatorFilesConfigOptions.php',
'PhabricatorFilesManagementCatWorkflow' => 'applications/files/management/PhabricatorFilesManagementCatWorkflow.php', 'PhabricatorFilesManagementCatWorkflow' => 'applications/files/management/PhabricatorFilesManagementCatWorkflow.php',
'PhabricatorFilesManagementCompactWorkflow' => 'applications/files/management/PhabricatorFilesManagementCompactWorkflow.php', 'PhabricatorFilesManagementCompactWorkflow' => 'applications/files/management/PhabricatorFilesManagementCompactWorkflow.php',
'PhabricatorFilesManagementCycleWorkflow' => 'applications/files/management/PhabricatorFilesManagementCycleWorkflow.php',
'PhabricatorFilesManagementEncodeWorkflow' => 'applications/files/management/PhabricatorFilesManagementEncodeWorkflow.php',
'PhabricatorFilesManagementEnginesWorkflow' => 'applications/files/management/PhabricatorFilesManagementEnginesWorkflow.php', 'PhabricatorFilesManagementEnginesWorkflow' => 'applications/files/management/PhabricatorFilesManagementEnginesWorkflow.php',
'PhabricatorFilesManagementGenerateKeyWorkflow' => 'applications/files/management/PhabricatorFilesManagementGenerateKeyWorkflow.php',
'PhabricatorFilesManagementMigrateWorkflow' => 'applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php', 'PhabricatorFilesManagementMigrateWorkflow' => 'applications/files/management/PhabricatorFilesManagementMigrateWorkflow.php',
'PhabricatorFilesManagementPurgeWorkflow' => 'applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php', 'PhabricatorFilesManagementPurgeWorkflow' => 'applications/files/management/PhabricatorFilesManagementPurgeWorkflow.php',
'PhabricatorFilesManagementRebuildWorkflow' => 'applications/files/management/PhabricatorFilesManagementRebuildWorkflow.php', 'PhabricatorFilesManagementRebuildWorkflow' => 'applications/files/management/PhabricatorFilesManagementRebuildWorkflow.php',
@ -2617,6 +2630,8 @@ phutil_register_library_map(array(
'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php', 'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php',
'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php', 'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php',
'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php', 'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php',
'PhabricatorKeyring' => 'applications/files/keyring/PhabricatorKeyring.php',
'PhabricatorKeyringConfigOptionType' => 'applications/files/keyring/PhabricatorKeyringConfigOptionType.php',
'PhabricatorLDAPAuthProvider' => 'applications/auth/provider/PhabricatorLDAPAuthProvider.php', 'PhabricatorLDAPAuthProvider' => 'applications/auth/provider/PhabricatorLDAPAuthProvider.php',
'PhabricatorLegalpadApplication' => 'applications/legalpad/application/PhabricatorLegalpadApplication.php', 'PhabricatorLegalpadApplication' => 'applications/legalpad/application/PhabricatorLegalpadApplication.php',
'PhabricatorLegalpadConfigOptions' => 'applications/legalpad/config/PhabricatorLegalpadConfigOptions.php', 'PhabricatorLegalpadConfigOptions' => 'applications/legalpad/config/PhabricatorLegalpadConfigOptions.php',
@ -2755,6 +2770,8 @@ phutil_register_library_map(array(
'PhabricatorMetaMTASendGridReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTASendGridReceiveController.php', 'PhabricatorMetaMTASendGridReceiveController' => 'applications/metamta/controller/PhabricatorMetaMTASendGridReceiveController.php',
'PhabricatorMetaMTAWorker' => 'applications/metamta/PhabricatorMetaMTAWorker.php', 'PhabricatorMetaMTAWorker' => 'applications/metamta/PhabricatorMetaMTAWorker.php',
'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php', 'PhabricatorMetronomicTriggerClock' => 'infrastructure/daemon/workers/clock/PhabricatorMetronomicTriggerClock.php',
'PhabricatorModularTransaction' => 'applications/transactions/storage/PhabricatorModularTransaction.php',
'PhabricatorModularTransactionType' => 'applications/transactions/storage/PhabricatorModularTransactionType.php',
'PhabricatorMonospacedFontSetting' => 'applications/settings/setting/PhabricatorMonospacedFontSetting.php', 'PhabricatorMonospacedFontSetting' => 'applications/settings/setting/PhabricatorMonospacedFontSetting.php',
'PhabricatorMonospacedTextareasSetting' => 'applications/settings/setting/PhabricatorMonospacedTextareasSetting.php', 'PhabricatorMonospacedTextareasSetting' => 'applications/settings/setting/PhabricatorMonospacedTextareasSetting.php',
'PhabricatorMotivatorProfilePanel' => 'applications/search/profilepanel/PhabricatorMotivatorProfilePanel.php', 'PhabricatorMotivatorProfilePanel' => 'applications/search/profilepanel/PhabricatorMotivatorProfilePanel.php',
@ -2914,12 +2931,14 @@ phutil_register_library_map(array(
'PhabricatorPasteArchiveController' => 'applications/paste/controller/PhabricatorPasteArchiveController.php', 'PhabricatorPasteArchiveController' => 'applications/paste/controller/PhabricatorPasteArchiveController.php',
'PhabricatorPasteConfigOptions' => 'applications/paste/config/PhabricatorPasteConfigOptions.php', 'PhabricatorPasteConfigOptions' => 'applications/paste/config/PhabricatorPasteConfigOptions.php',
'PhabricatorPasteContentSearchEngineAttachment' => 'applications/paste/engineextension/PhabricatorPasteContentSearchEngineAttachment.php', 'PhabricatorPasteContentSearchEngineAttachment' => 'applications/paste/engineextension/PhabricatorPasteContentSearchEngineAttachment.php',
'PhabricatorPasteContentTransaction' => 'applications/paste/xaction/PhabricatorPasteContentTransaction.php',
'PhabricatorPasteController' => 'applications/paste/controller/PhabricatorPasteController.php', 'PhabricatorPasteController' => 'applications/paste/controller/PhabricatorPasteController.php',
'PhabricatorPasteDAO' => 'applications/paste/storage/PhabricatorPasteDAO.php', 'PhabricatorPasteDAO' => 'applications/paste/storage/PhabricatorPasteDAO.php',
'PhabricatorPasteEditController' => 'applications/paste/controller/PhabricatorPasteEditController.php', 'PhabricatorPasteEditController' => 'applications/paste/controller/PhabricatorPasteEditController.php',
'PhabricatorPasteEditEngine' => 'applications/paste/editor/PhabricatorPasteEditEngine.php', 'PhabricatorPasteEditEngine' => 'applications/paste/editor/PhabricatorPasteEditEngine.php',
'PhabricatorPasteEditor' => 'applications/paste/editor/PhabricatorPasteEditor.php', 'PhabricatorPasteEditor' => 'applications/paste/editor/PhabricatorPasteEditor.php',
'PhabricatorPasteFilenameContextFreeGrammar' => 'applications/paste/lipsum/PhabricatorPasteFilenameContextFreeGrammar.php', 'PhabricatorPasteFilenameContextFreeGrammar' => 'applications/paste/lipsum/PhabricatorPasteFilenameContextFreeGrammar.php',
'PhabricatorPasteLanguageTransaction' => 'applications/paste/xaction/PhabricatorPasteLanguageTransaction.php',
'PhabricatorPasteListController' => 'applications/paste/controller/PhabricatorPasteListController.php', 'PhabricatorPasteListController' => 'applications/paste/controller/PhabricatorPasteListController.php',
'PhabricatorPastePastePHIDType' => 'applications/paste/phid/PhabricatorPastePastePHIDType.php', 'PhabricatorPastePastePHIDType' => 'applications/paste/phid/PhabricatorPastePastePHIDType.php',
'PhabricatorPasteQuery' => 'applications/paste/query/PhabricatorPasteQuery.php', 'PhabricatorPasteQuery' => 'applications/paste/query/PhabricatorPasteQuery.php',
@ -2928,10 +2947,13 @@ phutil_register_library_map(array(
'PhabricatorPasteSchemaSpec' => 'applications/paste/storage/PhabricatorPasteSchemaSpec.php', 'PhabricatorPasteSchemaSpec' => 'applications/paste/storage/PhabricatorPasteSchemaSpec.php',
'PhabricatorPasteSearchEngine' => 'applications/paste/query/PhabricatorPasteSearchEngine.php', 'PhabricatorPasteSearchEngine' => 'applications/paste/query/PhabricatorPasteSearchEngine.php',
'PhabricatorPasteSnippet' => 'applications/paste/snippet/PhabricatorPasteSnippet.php', 'PhabricatorPasteSnippet' => 'applications/paste/snippet/PhabricatorPasteSnippet.php',
'PhabricatorPasteStatusTransaction' => 'applications/paste/xaction/PhabricatorPasteStatusTransaction.php',
'PhabricatorPasteTestDataGenerator' => 'applications/paste/lipsum/PhabricatorPasteTestDataGenerator.php', 'PhabricatorPasteTestDataGenerator' => 'applications/paste/lipsum/PhabricatorPasteTestDataGenerator.php',
'PhabricatorPasteTitleTransaction' => 'applications/paste/xaction/PhabricatorPasteTitleTransaction.php',
'PhabricatorPasteTransaction' => 'applications/paste/storage/PhabricatorPasteTransaction.php', 'PhabricatorPasteTransaction' => 'applications/paste/storage/PhabricatorPasteTransaction.php',
'PhabricatorPasteTransactionComment' => 'applications/paste/storage/PhabricatorPasteTransactionComment.php', 'PhabricatorPasteTransactionComment' => 'applications/paste/storage/PhabricatorPasteTransactionComment.php',
'PhabricatorPasteTransactionQuery' => 'applications/paste/query/PhabricatorPasteTransactionQuery.php', 'PhabricatorPasteTransactionQuery' => 'applications/paste/query/PhabricatorPasteTransactionQuery.php',
'PhabricatorPasteTransactionType' => 'applications/paste/xaction/PhabricatorPasteTransactionType.php',
'PhabricatorPasteViewController' => 'applications/paste/controller/PhabricatorPasteViewController.php', 'PhabricatorPasteViewController' => 'applications/paste/controller/PhabricatorPasteViewController.php',
'PhabricatorPathSetupCheck' => 'applications/config/check/PhabricatorPathSetupCheck.php', 'PhabricatorPathSetupCheck' => 'applications/config/check/PhabricatorPathSetupCheck.php',
'PhabricatorPeopleAnyOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleAnyOwnerDatasource.php', 'PhabricatorPeopleAnyOwnerDatasource' => 'applications/people/typeahead/PhabricatorPeopleAnyOwnerDatasource.php',
@ -3241,6 +3263,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php', 'PhabricatorRepositoryManagementListWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementListWorkflow.php',
'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php', 'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementLookupUsersWorkflow.php',
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php', 'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkImportedWorkflow.php',
'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMarkReachableWorkflow.php',
'PhabricatorRepositoryManagementMirrorWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php', 'PhabricatorRepositoryManagementMirrorWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMirrorWorkflow.php',
'PhabricatorRepositoryManagementMovePathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php', 'PhabricatorRepositoryManagementMovePathsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementMovePathsWorkflow.php',
'PhabricatorRepositoryManagementParentsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php', 'PhabricatorRepositoryManagementParentsWorkflow' => 'applications/repository/management/PhabricatorRepositoryManagementParentsWorkflow.php',
@ -3254,6 +3277,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryMercurialCommitMessageParserWorker.php', 'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/PhabricatorRepositoryMercurialCommitMessageParserWorker.php',
'PhabricatorRepositoryMirror' => 'applications/repository/storage/PhabricatorRepositoryMirror.php', 'PhabricatorRepositoryMirror' => 'applications/repository/storage/PhabricatorRepositoryMirror.php',
'PhabricatorRepositoryMirrorEngine' => 'applications/repository/engine/PhabricatorRepositoryMirrorEngine.php', 'PhabricatorRepositoryMirrorEngine' => 'applications/repository/engine/PhabricatorRepositoryMirrorEngine.php',
'PhabricatorRepositoryOldRef' => 'applications/repository/storage/PhabricatorRepositoryOldRef.php',
'PhabricatorRepositoryParsedChange' => 'applications/repository/data/PhabricatorRepositoryParsedChange.php', 'PhabricatorRepositoryParsedChange' => 'applications/repository/data/PhabricatorRepositoryParsedChange.php',
'PhabricatorRepositoryPullEngine' => 'applications/repository/engine/PhabricatorRepositoryPullEngine.php', 'PhabricatorRepositoryPullEngine' => 'applications/repository/engine/PhabricatorRepositoryPullEngine.php',
'PhabricatorRepositoryPullEvent' => 'applications/repository/storage/PhabricatorRepositoryPullEvent.php', 'PhabricatorRepositoryPullEvent' => 'applications/repository/storage/PhabricatorRepositoryPullEvent.php',
@ -3589,6 +3613,8 @@ phutil_register_library_map(array(
'PhabricatorTokensCurtainExtension' => 'applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php', 'PhabricatorTokensCurtainExtension' => 'applications/tokens/engineextension/PhabricatorTokensCurtainExtension.php',
'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php', 'PhabricatorTokensSettingsPanel' => 'applications/settings/panel/PhabricatorTokensSettingsPanel.php',
'PhabricatorTooltipUIExample' => 'applications/uiexample/examples/PhabricatorTooltipUIExample.php', 'PhabricatorTooltipUIExample' => 'applications/uiexample/examples/PhabricatorTooltipUIExample.php',
'PhabricatorTransactionChange' => 'applications/transactions/data/PhabricatorTransactionChange.php',
'PhabricatorTransactionRemarkupChange' => 'applications/transactions/data/PhabricatorTransactionRemarkupChange.php',
'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php', 'PhabricatorTransactions' => 'applications/transactions/constants/PhabricatorTransactions.php',
'PhabricatorTransactionsApplication' => 'applications/transactions/application/PhabricatorTransactionsApplication.php', 'PhabricatorTransactionsApplication' => 'applications/transactions/application/PhabricatorTransactionsApplication.php',
'PhabricatorTransactionsDestructionEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsDestructionEngineExtension.php', 'PhabricatorTransactionsDestructionEngineExtension' => 'applications/transactions/engineextension/PhabricatorTransactionsDestructionEngineExtension.php',
@ -3750,6 +3776,8 @@ phutil_register_library_map(array(
'PhameBlogEditEngine' => 'applications/phame/editor/PhameBlogEditEngine.php', 'PhameBlogEditEngine' => 'applications/phame/editor/PhameBlogEditEngine.php',
'PhameBlogEditor' => 'applications/phame/editor/PhameBlogEditor.php', 'PhameBlogEditor' => 'applications/phame/editor/PhameBlogEditor.php',
'PhameBlogFeedController' => 'applications/phame/controller/blog/PhameBlogFeedController.php', 'PhameBlogFeedController' => 'applications/phame/controller/blog/PhameBlogFeedController.php',
'PhameBlogFulltextEngine' => 'applications/phame/search/PhameBlogFulltextEngine.php',
'PhameBlogHeaderPictureController' => 'applications/phame/controller/blog/PhameBlogHeaderPictureController.php',
'PhameBlogListController' => 'applications/phame/controller/blog/PhameBlogListController.php', 'PhameBlogListController' => 'applications/phame/controller/blog/PhameBlogListController.php',
'PhameBlogListView' => 'applications/phame/view/PhameBlogListView.php', 'PhameBlogListView' => 'applications/phame/view/PhameBlogListView.php',
'PhameBlogManageController' => 'applications/phame/controller/blog/PhameBlogManageController.php', 'PhameBlogManageController' => 'applications/phame/controller/blog/PhameBlogManageController.php',
@ -3771,12 +3799,14 @@ phutil_register_library_map(array(
'PhameLiveController' => 'applications/phame/controller/PhameLiveController.php', 'PhameLiveController' => 'applications/phame/controller/PhameLiveController.php',
'PhameNextPostView' => 'applications/phame/view/PhameNextPostView.php', 'PhameNextPostView' => 'applications/phame/view/PhameNextPostView.php',
'PhamePost' => 'applications/phame/storage/PhamePost.php', 'PhamePost' => 'applications/phame/storage/PhamePost.php',
'PhamePostArchiveController' => 'applications/phame/controller/post/PhamePostArchiveController.php',
'PhamePostCommentController' => 'applications/phame/controller/post/PhamePostCommentController.php', 'PhamePostCommentController' => 'applications/phame/controller/post/PhamePostCommentController.php',
'PhamePostController' => 'applications/phame/controller/post/PhamePostController.php', 'PhamePostController' => 'applications/phame/controller/post/PhamePostController.php',
'PhamePostEditConduitAPIMethod' => 'applications/phame/conduit/PhamePostEditConduitAPIMethod.php', 'PhamePostEditConduitAPIMethod' => 'applications/phame/conduit/PhamePostEditConduitAPIMethod.php',
'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php', 'PhamePostEditController' => 'applications/phame/controller/post/PhamePostEditController.php',
'PhamePostEditEngine' => 'applications/phame/editor/PhamePostEditEngine.php', 'PhamePostEditEngine' => 'applications/phame/editor/PhamePostEditEngine.php',
'PhamePostEditor' => 'applications/phame/editor/PhamePostEditor.php', 'PhamePostEditor' => 'applications/phame/editor/PhamePostEditor.php',
'PhamePostFulltextEngine' => 'applications/phame/search/PhamePostFulltextEngine.php',
'PhamePostHistoryController' => 'applications/phame/controller/post/PhamePostHistoryController.php', 'PhamePostHistoryController' => 'applications/phame/controller/post/PhamePostHistoryController.php',
'PhamePostListController' => 'applications/phame/controller/post/PhamePostListController.php', 'PhamePostListController' => 'applications/phame/controller/post/PhamePostListController.php',
'PhamePostListView' => 'applications/phame/view/PhamePostListView.php', 'PhamePostListView' => 'applications/phame/view/PhamePostListView.php',
@ -4787,6 +4817,7 @@ phutil_register_library_map(array(
'DifferentialReleephRequestFieldSpecification' => 'Phobject', 'DifferentialReleephRequestFieldSpecification' => 'Phobject',
'DifferentialRemarkupRule' => 'PhabricatorObjectRemarkupRule', 'DifferentialRemarkupRule' => 'PhabricatorObjectRemarkupRule',
'DifferentialReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler', 'DifferentialReplyHandler' => 'PhabricatorApplicationTransactionReplyHandler',
'DifferentialRepositoryDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DifferentialRepositoryField' => 'DifferentialCoreCustomField', 'DifferentialRepositoryField' => 'DifferentialCoreCustomField',
'DifferentialRepositoryLookup' => 'Phobject', 'DifferentialRepositoryLookup' => 'Phobject',
'DifferentialRequiredSignaturesField' => 'DifferentialCoreCustomField', 'DifferentialRequiredSignaturesField' => 'DifferentialCoreCustomField',
@ -5137,6 +5168,7 @@ phutil_register_library_map(array(
'DiffusionSymbolQuery' => 'PhabricatorOffsetPagedQuery', 'DiffusionSymbolQuery' => 'PhabricatorOffsetPagedQuery',
'DiffusionTagListController' => 'DiffusionController', 'DiffusionTagListController' => 'DiffusionController',
'DiffusionTagListView' => 'DiffusionView', 'DiffusionTagListView' => 'DiffusionView',
'DiffusionTaggedRepositoriesFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod', 'DiffusionTagsQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
'DiffusionURIEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'DiffusionURIEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'DiffusionURIEditEngine' => 'PhabricatorEditEngine', 'DiffusionURIEditEngine' => 'PhabricatorEditEngine',
@ -6722,6 +6754,9 @@ phutil_register_library_map(array(
'PhabricatorController' => 'AphrontController', 'PhabricatorController' => 'AphrontController',
'PhabricatorCookies' => 'Phobject', 'PhabricatorCookies' => 'Phobject',
'PhabricatorCoreConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorCoreConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorCoreCreateTransaction' => 'PhabricatorCoreTransactionType',
'PhabricatorCoreTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorCoreVoidTransaction' => 'PhabricatorModularTransactionType',
'PhabricatorCountdown' => array( 'PhabricatorCountdown' => array(
'PhabricatorCountdownDAO', 'PhabricatorCountdownDAO',
'PhabricatorPolicyInterface', 'PhabricatorPolicyInterface',
@ -7076,6 +7111,7 @@ phutil_register_library_map(array(
'PhabricatorPolicyInterface', 'PhabricatorPolicyInterface',
'PhabricatorDestructibleInterface', 'PhabricatorDestructibleInterface',
), ),
'PhabricatorFileAES256StorageFormat' => 'PhabricatorFileStorageFormat',
'PhabricatorFileBundleLoader' => 'Phobject', 'PhabricatorFileBundleLoader' => 'Phobject',
'PhabricatorFileChunk' => array( 'PhabricatorFileChunk' => array(
'PhabricatorFileDAO', 'PhabricatorFileDAO',
@ -7112,12 +7148,16 @@ phutil_register_library_map(array(
'PhabricatorFileLinkView' => 'AphrontView', 'PhabricatorFileLinkView' => 'AphrontView',
'PhabricatorFileListController' => 'PhabricatorFileController', 'PhabricatorFileListController' => 'PhabricatorFileController',
'PhabricatorFileQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorFileQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorFileROT13StorageFormat' => 'PhabricatorFileStorageFormat',
'PhabricatorFileRawStorageFormat' => 'PhabricatorFileStorageFormat',
'PhabricatorFileSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorFileSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorFileSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorFileSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO', 'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO',
'PhabricatorFileStorageConfigurationException' => 'Exception', 'PhabricatorFileStorageConfigurationException' => 'Exception',
'PhabricatorFileStorageEngine' => 'Phobject', 'PhabricatorFileStorageEngine' => 'Phobject',
'PhabricatorFileStorageEngineTestCase' => 'PhabricatorTestCase', 'PhabricatorFileStorageEngineTestCase' => 'PhabricatorTestCase',
'PhabricatorFileStorageFormat' => 'Phobject',
'PhabricatorFileStorageFormatTestCase' => 'PhabricatorTestCase',
'PhabricatorFileTemporaryGarbageCollector' => 'PhabricatorGarbageCollector', 'PhabricatorFileTemporaryGarbageCollector' => 'PhabricatorGarbageCollector',
'PhabricatorFileTestCase' => 'PhabricatorTestCase', 'PhabricatorFileTestCase' => 'PhabricatorTestCase',
'PhabricatorFileTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorFileTestDataGenerator' => 'PhabricatorTestDataGenerator',
@ -7141,7 +7181,10 @@ phutil_register_library_map(array(
'PhabricatorFilesConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorFilesConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorFilesManagementCatWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementCatWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementCompactWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementCompactWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementCycleWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementEncodeWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementEnginesWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementEnginesWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementGenerateKeyWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementMigrateWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementMigrateWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementPurgeWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementPurgeWorkflow' => 'PhabricatorFilesManagementWorkflow',
'PhabricatorFilesManagementRebuildWorkflow' => 'PhabricatorFilesManagementWorkflow', 'PhabricatorFilesManagementRebuildWorkflow' => 'PhabricatorFilesManagementWorkflow',
@ -7251,6 +7294,8 @@ phutil_register_library_map(array(
'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType', 'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType',
'PhabricatorJumpNavHandler' => 'Phobject', 'PhabricatorJumpNavHandler' => 'Phobject',
'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache', 'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache',
'PhabricatorKeyring' => 'Phobject',
'PhabricatorKeyringConfigOptionType' => 'PhabricatorConfigJSONOptionType',
'PhabricatorLDAPAuthProvider' => 'PhabricatorAuthProvider', 'PhabricatorLDAPAuthProvider' => 'PhabricatorAuthProvider',
'PhabricatorLegalpadApplication' => 'PhabricatorApplication', 'PhabricatorLegalpadApplication' => 'PhabricatorApplication',
'PhabricatorLegalpadConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorLegalpadConfigOptions' => 'PhabricatorApplicationConfigOptions',
@ -7399,6 +7444,8 @@ phutil_register_library_map(array(
'PhabricatorMetaMTASendGridReceiveController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTASendGridReceiveController' => 'PhabricatorMetaMTAController',
'PhabricatorMetaMTAWorker' => 'PhabricatorWorker', 'PhabricatorMetaMTAWorker' => 'PhabricatorWorker',
'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock', 'PhabricatorMetronomicTriggerClock' => 'PhabricatorTriggerClock',
'PhabricatorModularTransaction' => 'PhabricatorApplicationTransaction',
'PhabricatorModularTransactionType' => 'Phobject',
'PhabricatorMonospacedFontSetting' => 'PhabricatorStringSetting', 'PhabricatorMonospacedFontSetting' => 'PhabricatorStringSetting',
'PhabricatorMonospacedTextareasSetting' => 'PhabricatorSelectSetting', 'PhabricatorMonospacedTextareasSetting' => 'PhabricatorSelectSetting',
'PhabricatorMotivatorProfilePanel' => 'PhabricatorProfilePanel', 'PhabricatorMotivatorProfilePanel' => 'PhabricatorProfilePanel',
@ -7594,12 +7641,14 @@ phutil_register_library_map(array(
'PhabricatorPasteArchiveController' => 'PhabricatorPasteController', 'PhabricatorPasteArchiveController' => 'PhabricatorPasteController',
'PhabricatorPasteConfigOptions' => 'PhabricatorApplicationConfigOptions', 'PhabricatorPasteConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorPasteContentSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'PhabricatorPasteContentSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'PhabricatorPasteContentTransaction' => 'PhabricatorPasteTransactionType',
'PhabricatorPasteController' => 'PhabricatorController', 'PhabricatorPasteController' => 'PhabricatorController',
'PhabricatorPasteDAO' => 'PhabricatorLiskDAO', 'PhabricatorPasteDAO' => 'PhabricatorLiskDAO',
'PhabricatorPasteEditController' => 'PhabricatorPasteController', 'PhabricatorPasteEditController' => 'PhabricatorPasteController',
'PhabricatorPasteEditEngine' => 'PhabricatorEditEngine', 'PhabricatorPasteEditEngine' => 'PhabricatorEditEngine',
'PhabricatorPasteEditor' => 'PhabricatorApplicationTransactionEditor', 'PhabricatorPasteEditor' => 'PhabricatorApplicationTransactionEditor',
'PhabricatorPasteFilenameContextFreeGrammar' => 'PhutilContextFreeGrammar', 'PhabricatorPasteFilenameContextFreeGrammar' => 'PhutilContextFreeGrammar',
'PhabricatorPasteLanguageTransaction' => 'PhabricatorPasteTransactionType',
'PhabricatorPasteListController' => 'PhabricatorPasteController', 'PhabricatorPasteListController' => 'PhabricatorPasteController',
'PhabricatorPastePastePHIDType' => 'PhabricatorPHIDType', 'PhabricatorPastePastePHIDType' => 'PhabricatorPHIDType',
'PhabricatorPasteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery', 'PhabricatorPasteQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
@ -7608,10 +7657,13 @@ phutil_register_library_map(array(
'PhabricatorPasteSchemaSpec' => 'PhabricatorConfigSchemaSpec', 'PhabricatorPasteSchemaSpec' => 'PhabricatorConfigSchemaSpec',
'PhabricatorPasteSearchEngine' => 'PhabricatorApplicationSearchEngine', 'PhabricatorPasteSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorPasteSnippet' => 'Phobject', 'PhabricatorPasteSnippet' => 'Phobject',
'PhabricatorPasteStatusTransaction' => 'PhabricatorPasteTransactionType',
'PhabricatorPasteTestDataGenerator' => 'PhabricatorTestDataGenerator', 'PhabricatorPasteTestDataGenerator' => 'PhabricatorTestDataGenerator',
'PhabricatorPasteTransaction' => 'PhabricatorApplicationTransaction', 'PhabricatorPasteTitleTransaction' => 'PhabricatorPasteTransactionType',
'PhabricatorPasteTransaction' => 'PhabricatorModularTransaction',
'PhabricatorPasteTransactionComment' => 'PhabricatorApplicationTransactionComment', 'PhabricatorPasteTransactionComment' => 'PhabricatorApplicationTransactionComment',
'PhabricatorPasteTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'PhabricatorPasteTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'PhabricatorPasteTransactionType' => 'PhabricatorModularTransactionType',
'PhabricatorPasteViewController' => 'PhabricatorPasteController', 'PhabricatorPasteViewController' => 'PhabricatorPasteController',
'PhabricatorPathSetupCheck' => 'PhabricatorSetupCheck', 'PhabricatorPathSetupCheck' => 'PhabricatorSetupCheck',
'PhabricatorPeopleAnyOwnerDatasource' => 'PhabricatorTypeaheadDatasource', 'PhabricatorPeopleAnyOwnerDatasource' => 'PhabricatorTypeaheadDatasource',
@ -8003,6 +8055,7 @@ phutil_register_library_map(array(
'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementListWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementLookupUsersWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMarkImportedWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMarkReachableWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMirrorWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMirrorWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementMovePathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementMovePathsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
'PhabricatorRepositoryManagementParentsWorkflow' => 'PhabricatorRepositoryManagementWorkflow', 'PhabricatorRepositoryManagementParentsWorkflow' => 'PhabricatorRepositoryManagementWorkflow',
@ -8016,6 +8069,10 @@ phutil_register_library_map(array(
'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker', 'PhabricatorRepositoryMercurialCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
'PhabricatorRepositoryMirror' => 'PhabricatorRepositoryDAO', 'PhabricatorRepositoryMirror' => 'PhabricatorRepositoryDAO',
'PhabricatorRepositoryMirrorEngine' => 'PhabricatorRepositoryEngine', 'PhabricatorRepositoryMirrorEngine' => 'PhabricatorRepositoryEngine',
'PhabricatorRepositoryOldRef' => array(
'PhabricatorRepositoryDAO',
'PhabricatorPolicyInterface',
),
'PhabricatorRepositoryParsedChange' => 'Phobject', 'PhabricatorRepositoryParsedChange' => 'Phobject',
'PhabricatorRepositoryPullEngine' => 'PhabricatorRepositoryEngine', 'PhabricatorRepositoryPullEngine' => 'PhabricatorRepositoryEngine',
'PhabricatorRepositoryPullEvent' => array( 'PhabricatorRepositoryPullEvent' => array(
@ -8392,6 +8449,8 @@ phutil_register_library_map(array(
'PhabricatorTokensCurtainExtension' => 'PHUICurtainExtension', 'PhabricatorTokensCurtainExtension' => 'PHUICurtainExtension',
'PhabricatorTokensSettingsPanel' => 'PhabricatorSettingsPanel', 'PhabricatorTokensSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorTooltipUIExample' => 'PhabricatorUIExample', 'PhabricatorTooltipUIExample' => 'PhabricatorUIExample',
'PhabricatorTransactionChange' => 'Phobject',
'PhabricatorTransactionRemarkupChange' => 'PhabricatorTransactionChange',
'PhabricatorTransactions' => 'Phobject', 'PhabricatorTransactions' => 'Phobject',
'PhabricatorTransactionsApplication' => 'PhabricatorApplication', 'PhabricatorTransactionsApplication' => 'PhabricatorApplication',
'PhabricatorTransactionsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension', 'PhabricatorTransactionsDestructionEngineExtension' => 'PhabricatorDestructionEngineExtension',
@ -8585,6 +8644,7 @@ phutil_register_library_map(array(
'PhabricatorDestructibleInterface', 'PhabricatorDestructibleInterface',
'PhabricatorApplicationTransactionInterface', 'PhabricatorApplicationTransactionInterface',
'PhabricatorConduitResultInterface', 'PhabricatorConduitResultInterface',
'PhabricatorFulltextInterface',
), ),
'PhameBlog404Controller' => 'PhameLiveController', 'PhameBlog404Controller' => 'PhameLiveController',
'PhameBlogArchiveController' => 'PhameBlogController', 'PhameBlogArchiveController' => 'PhameBlogController',
@ -8595,6 +8655,8 @@ phutil_register_library_map(array(
'PhameBlogEditEngine' => 'PhabricatorEditEngine', 'PhameBlogEditEngine' => 'PhabricatorEditEngine',
'PhameBlogEditor' => 'PhabricatorApplicationTransactionEditor', 'PhameBlogEditor' => 'PhabricatorApplicationTransactionEditor',
'PhameBlogFeedController' => 'PhameBlogController', 'PhameBlogFeedController' => 'PhameBlogController',
'PhameBlogFulltextEngine' => 'PhabricatorFulltextEngine',
'PhameBlogHeaderPictureController' => 'PhameBlogController',
'PhameBlogListController' => 'PhameBlogController', 'PhameBlogListController' => 'PhameBlogController',
'PhameBlogListView' => 'AphrontTagView', 'PhameBlogListView' => 'AphrontTagView',
'PhameBlogManageController' => 'PhameBlogController', 'PhameBlogManageController' => 'PhameBlogController',
@ -8626,13 +8688,16 @@ phutil_register_library_map(array(
'PhabricatorDestructibleInterface', 'PhabricatorDestructibleInterface',
'PhabricatorTokenReceiverInterface', 'PhabricatorTokenReceiverInterface',
'PhabricatorConduitResultInterface', 'PhabricatorConduitResultInterface',
'PhabricatorFulltextInterface',
), ),
'PhamePostArchiveController' => 'PhamePostController',
'PhamePostCommentController' => 'PhamePostController', 'PhamePostCommentController' => 'PhamePostController',
'PhamePostController' => 'PhameController', 'PhamePostController' => 'PhameController',
'PhamePostEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod', 'PhamePostEditConduitAPIMethod' => 'PhabricatorEditEngineAPIMethod',
'PhamePostEditController' => 'PhamePostController', 'PhamePostEditController' => 'PhamePostController',
'PhamePostEditEngine' => 'PhabricatorEditEngine', 'PhamePostEditEngine' => 'PhabricatorEditEngine',
'PhamePostEditor' => 'PhabricatorApplicationTransactionEditor', 'PhamePostEditor' => 'PhabricatorApplicationTransactionEditor',
'PhamePostFulltextEngine' => 'PhabricatorFulltextEngine',
'PhamePostHistoryController' => 'PhamePostController', 'PhamePostHistoryController' => 'PhamePostController',
'PhamePostListController' => 'PhamePostController', 'PhamePostListController' => 'PhamePostController',
'PhamePostListView' => 'AphrontTagView', 'PhamePostListView' => 'AphrontTagView',

View file

@ -542,7 +542,7 @@ final class PhabricatorAuditEditor
protected function expandCustomRemarkupBlockTransactions( protected function expandCustomRemarkupBlockTransactions(
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
array $xactions, array $xactions,
$blocks, array $changes,
PhutilMarkupEngine $engine) { PhutilMarkupEngine $engine) {
// we are only really trying to find unmentionable phids here... // we are only really trying to find unmentionable phids here...
@ -563,7 +563,7 @@ final class PhabricatorAuditEditor
return $result; return $result;
} }
$flat_blocks = array_mergev($blocks); $flat_blocks = mpull($changes, 'getNewValue');
$huge_block = implode("\n\n", $flat_blocks); $huge_block = implode("\n\n", $flat_blocks);
$phid_map = array(); $phid_map = array();
$phid_map[] = $this->getUnmentionablePHIDMap(); $phid_map[] = $this->getUnmentionablePHIDMap();

View file

@ -30,9 +30,11 @@ final class PhabricatorAuthAuthProviderPHIDType extends PhabricatorPHIDType {
array $objects) { array $objects) {
foreach ($handles as $phid => $handle) { foreach ($handles as $phid => $handle) {
$provider = $objects[$phid]; $provider = $objects[$phid]->getProvider();
$handle->setName($provider->getProviderName()); if ($provider) {
$handle->setName($provider->getProviderName());
}
} }
} }

View file

@ -190,6 +190,10 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'The Differential revision list view age UI elements have been removed '. 'The Differential revision list view age UI elements have been removed '.
'to simplify the interface.'); 'to simplify the interface.');
$global_settings_reason = pht(
'The "Re: Prefix" and "Vary Subjects" settings are now configured '.
'in global settings.');
$ancient_config += array( $ancient_config += array(
'phid.external-loaders' => 'phid.external-loaders' =>
pht( pht(
@ -321,6 +325,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'differential.days-fresh' => $stale_reason, 'differential.days-fresh' => $stale_reason,
'differential.days-stale' => $stale_reason, 'differential.days-stale' => $stale_reason,
'metamta.re-prefix' => $global_settings_reason,
'metamta.vary-subjects' => $global_settings_reason,
); );
return $ancient_config; return $ancient_config;

View file

@ -276,22 +276,6 @@ EODOC
)) ))
->setSummary(pht('Show email preferences link in email.')) ->setSummary(pht('Show email preferences link in email.'))
->setDescription($email_preferences_description), ->setDescription($email_preferences_description),
$this->newOption('metamta.re-prefix', 'bool', false)
->setBoolOptions(
array(
pht('Force "Re:" Subject Prefix'),
pht('No "Re:" Subject Prefix'),
))
->setSummary(pht('Control "Re:" subject prefix, for Mail.app.'))
->setDescription($re_prefix_description),
$this->newOption('metamta.vary-subjects', 'bool', true)
->setBoolOptions(
array(
pht('Allow Varied Subjects'),
pht('Always Use the Same Thread Subject'),
))
->setSummary(pht('Control subject variance, for some mail clients.'))
->setDescription($vary_subjects_description),
$this->newOption('metamta.insecure-auth-with-reply-to', 'bool', false) $this->newOption('metamta.insecure-auth-with-reply-to', 'bool', false)
->setBoolOptions( ->setBoolOptions(
array( array(

View file

@ -43,6 +43,14 @@ final class PhabricatorSecurityConfigOptions
'255.255.255.255/32', '255.255.255.255/32',
); );
$keyring_type = 'custom:PhabricatorKeyringConfigOptionType';
$keyring_description = $this->deformat(pht(<<<EOTEXT
The keyring stores master encryption keys. For help with configuring a keyring
and encryption, see **[[ %s | Configuring Encryption ]]**.
EOTEXT
,
PhabricatorEnv::getDoclink('Configuring Encryption')));
return array( return array(
$this->newOption('security.alternate-file-domain', 'string', null) $this->newOption('security.alternate-file-domain', 'string', null)
->setLocked(true) ->setLocked(true)
@ -276,6 +284,10 @@ final class PhabricatorSecurityConfigOptions
'unsecured content over plain HTTP. It is very difficult to '. 'unsecured content over plain HTTP. It is very difficult to '.
'undo this change once users\' browsers have accepted the '. 'undo this change once users\' browsers have accepted the '.
'setting.')), 'setting.')),
$this->newOption('keyring', $keyring_type, array())
->setHidden(true)
->setSummary(pht('Configure master encryption keys.'))
->setDescription($keyring_description),
); );
} }

View file

@ -1303,10 +1303,10 @@ final class DifferentialTransactionEditor
protected function expandCustomRemarkupBlockTransactions( protected function expandCustomRemarkupBlockTransactions(
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
array $xactions, array $xactions,
$blocks, array $changes,
PhutilMarkupEngine $engine) { PhutilMarkupEngine $engine) {
$flat_blocks = array_mergev($blocks); $flat_blocks = mpull($changes, 'getNewValue');
$huge_block = implode("\n\n", $flat_blocks); $huge_block = implode("\n\n", $flat_blocks);
$task_map = array(); $task_map = array();

View file

@ -75,7 +75,7 @@ final class DifferentialRevisionSearchEngine
->setLabel(pht('Repositories')) ->setLabel(pht('Repositories'))
->setKey('repositoryPHIDs') ->setKey('repositoryPHIDs')
->setAliases(array('repository', 'repositories', 'repositoryPHID')) ->setAliases(array('repository', 'repositories', 'repositoryPHID'))
->setDatasource(new DiffusionRepositoryDatasource()) ->setDatasource(new DifferentialRepositoryDatasource())
->setDescription( ->setDescription(
pht('Find revisions from specific repositories.')), pht('Find revisions from specific repositories.')),
id(new PhabricatorSearchSelectField()) id(new PhabricatorSearchSelectField())

View file

@ -0,0 +1,25 @@
<?php
final class DifferentialRepositoryDatasource
extends PhabricatorTypeaheadCompositeDatasource {
public function getBrowseTitle() {
return pht('Browse Repositories');
}
public function getPlaceholderText() {
return pht('Type a repository name or function...');
}
public function getDatasourceApplicationClass() {
return 'PhabricatorDifferentialApplication';
}
public function getComponentDatasources() {
return array(
new DiffusionTaggedRepositoriesFunctionDatasource(),
new DiffusionRepositoryDatasource(),
);
}
}

View file

@ -129,6 +129,12 @@ final class DiffusionCommitController extends DiffusionController {
), ),
$message)); $message));
if ($commit->isUnreachable()) {
$this->commitErrors[] = pht(
'This commit has been deleted in the repository: it is no longer '.
'reachable from any branch, tag, or ref.');
}
if ($this->getCommitErrors()) { if ($this->getCommitErrors()) {
$error_panel = id(new PHUIInfoView()) $error_panel = id(new PHUIInfoView())
->appendChild($this->getCommitErrors()) ->appendChild($this->getCommitErrors())

View file

@ -113,9 +113,7 @@ final class DiffusionPathChange extends Phobject {
if (!$this->getCommitData()) { if (!$this->getCommitData()) {
return null; return null;
} }
$message = $this->getCommitData()->getCommitMessage(); return $this->getCommitData()->getSummary();
$first = idx(explode("\n", $message), 0);
return substr($first, 0, 80);
} }
public static function convertToArcanistChanges(array $changes) { public static function convertToArcanistChanges(array $changes) {

View file

@ -7,6 +7,7 @@ final class DiffusionRepositoryRef extends Phobject {
private $shortName; private $shortName;
private $commitIdentifier; private $commitIdentifier;
private $refType;
private $rawFields = array(); private $rawFields = array();
public function setRawFields(array $raw_fields) { public function setRawFields(array $raw_fields) {
@ -36,6 +37,25 @@ final class DiffusionRepositoryRef extends Phobject {
return $this->shortName; return $this->shortName;
} }
public function setRefType($ref_type) {
$this->refType = $ref_type;
return $this;
}
public function getRefType() {
return $this->refType;
}
public function isBranch() {
$type_branch = PhabricatorRepositoryRefCursor::TYPE_BRANCH;
return ($this->getRefType() === $type_branch);
}
public function isTag() {
$type_tag = PhabricatorRepositoryRefCursor::TYPE_TAG;
return ($this->getRefType() === $type_tag);
}
/* -( Serialization )------------------------------------------------------ */ /* -( Serialization )------------------------------------------------------ */
@ -44,6 +64,7 @@ final class DiffusionRepositoryRef extends Phobject {
return array( return array(
'shortName' => $this->shortName, 'shortName' => $this->shortName,
'commitIdentifier' => $this->commitIdentifier, 'commitIdentifier' => $this->commitIdentifier,
'refType' => $this->refType,
'rawFields' => $this->rawFields, 'rawFields' => $this->rawFields,
); );
} }
@ -52,6 +73,7 @@ final class DiffusionRepositoryRef extends Phobject {
return id(new DiffusionRepositoryRef()) return id(new DiffusionRepositoryRef())
->setShortName($dict['shortName']) ->setShortName($dict['shortName'])
->setCommitIdentifier($dict['commitIdentifier']) ->setCommitIdentifier($dict['commitIdentifier'])
->setRefType($dict['refType'])
->setRawFields($dict['rawFields']); ->setRawFields($dict['rawFields']);
} }

View file

@ -14,96 +14,121 @@ final class DiffusionLowLevelGitRefQuery extends DiffusionLowLevelQuery {
} }
protected function executeQuery() { protected function executeQuery() {
$type_branch = PhabricatorRepositoryRefCursor::TYPE_BRANCH;
$type_tag = PhabricatorRepositoryRefCursor::TYPE_TAG;
$type_ref = PhabricatorRepositoryRefCursor::TYPE_REF;
$ref_types = $this->refTypes; $ref_types = $this->refTypes;
if ($ref_types) { if (!$ref_types) {
$type_branch = PhabricatorRepositoryRefCursor::TYPE_BRANCH; $ref_types = array($type_branch, $type_tag, $type_ref);
$type_tag = PhabricatorRepositoryRefCursor::TYPE_TAG;
$ref_types = array_fuse($ref_types);
$with_branches = isset($ref_types[$type_branch]);
$with_tags = isset($ref_types[$type_tag]);
} else {
$with_branches = true;
$with_tags = true;
} }
$ref_types = array_fuse($ref_types);
$with_branches = isset($ref_types[$type_branch]);
$with_tags = isset($ref_types[$type_tag]);
$with_refs = isset($refs_types[$type_ref]);
$repository = $this->getRepository(); $repository = $this->getRepository();
$prefixes = array(); $prefixes = array();
if ($with_branches) { if ($repository->isWorkingCopyBare()) {
if ($repository->isWorkingCopyBare()) { $branch_prefix = 'refs/heads/';
$prefix = 'refs/heads/'; } else {
} else { $remote = DiffusionGitBranch::DEFAULT_GIT_REMOTE;
$remote = DiffusionGitBranch::DEFAULT_GIT_REMOTE; $branch_prefix = 'refs/remotes/'.$remote.'/';
$prefix = 'refs/remotes/'.$remote.'/'; }
$tag_prefix = 'refs/tags/';
if ($with_refs || count($ref_types) > 1) {
// If we're loading refs or more than one type of ref, just query
// everything.
$prefix = 'refs/';
} else {
if ($with_branches) {
$prefix = $branch_prefix;
}
if ($with_tags) {
$prefix = $tag_prefix;
} }
$prefixes[] = $prefix;
} }
if ($with_tags) { $branch_len = strlen($branch_prefix);
$prefixes[] = 'refs/tags/'; $tag_len = strlen($tag_prefix);
list($stdout) = $repository->execxLocalCommand(
'for-each-ref --sort=%s --format=%s -- %s',
'-creatordate',
$this->getFormatString(),
$prefix);
$stdout = rtrim($stdout);
if (!strlen($stdout)) {
return array();
} }
$order = '-creatordate'; $remote_prefix = 'refs/remotes/';
$remote_len = strlen($remote_prefix);
$futures = array(); // NOTE: Although git supports --count, we can't apply any offset or
foreach ($prefixes as $prefix) { // limit logic until the very end because we may encounter a HEAD which
$futures[$prefix] = $repository->getLocalCommandFuture( // we want to discard.
'for-each-ref --sort=%s --format=%s %s',
$order,
$this->getFormatString(),
$prefix);
}
// Resolve all the futures first. We want to iterate over them in prefix
// order, not resolution order.
foreach (new FutureIterator($futures) as $prefix => $future) {
$future->resolvex();
}
$lines = explode("\n", $stdout);
$results = array(); $results = array();
foreach ($futures as $prefix => $future) { foreach ($lines as $line) {
list($stdout) = $future->resolvex(); $fields = $this->extractFields($line);
$stdout = rtrim($stdout); $refname = $fields['refname'];
if (!strlen($stdout)) { if (!strncmp($refname, $branch_prefix, $branch_len)) {
$short = substr($refname, $branch_len);
$type = $type_branch;
} else if (!strncmp($refname, $tag_prefix, $tag_len)) {
$short = substr($refname, $tag_len);
$type = $type_tag;
} else if (!strncmp($refname, $remote_prefix, $remote_len)) {
// If we've found a remote ref that we didn't recognize as naming a
// branch, just ignore it. This can happen if we're observing a remote,
// and that remote has its own remotes. We don't care about their
// state and they may be out of date, so ignore them.
continue;
} else {
$short = $refname;
$type = $type_ref;
}
// If this isn't a type of ref we care about, skip it.
if (empty($ref_types[$type])) {
continue; continue;
} }
// NOTE: Although git supports --count, we can't apply any offset or // If this is the local HEAD, skip it.
// limit logic until the very end because we may encounter a HEAD which if ($short == 'HEAD') {
// we want to discard. continue;
$lines = explode("\n", $stdout);
foreach ($lines as $line) {
$fields = $this->extractFields($line);
$creator = $fields['creator'];
$matches = null;
if (preg_match('/^(.*) ([0-9]+) ([0-9+-]+)$/', $creator, $matches)) {
$fields['author'] = $matches[1];
$fields['epoch'] = (int)$matches[2];
} else {
$fields['author'] = null;
$fields['epoch'] = null;
}
$commit = nonempty($fields['*objectname'], $fields['objectname']);
$short = substr($fields['refname'], strlen($prefix));
if ($short == 'HEAD') {
continue;
}
$ref = id(new DiffusionRepositoryRef())
->setShortName($short)
->setCommitIdentifier($commit)
->setRawFields($fields);
$results[] = $ref;
} }
$creator = $fields['creator'];
$matches = null;
if (preg_match('/^(.*) ([0-9]+) ([0-9+-]+)$/', $creator, $matches)) {
$fields['author'] = $matches[1];
$fields['epoch'] = (int)$matches[2];
} else {
$fields['author'] = null;
$fields['epoch'] = null;
}
$commit = nonempty($fields['*objectname'], $fields['objectname']);
$ref = id(new DiffusionRepositoryRef())
->setRefType($type)
->setShortName($short)
->setCommitIdentifier($commit)
->setRawFields($fields);
$results[] = $ref;
} }
return $results; return $results;

View file

@ -0,0 +1,100 @@
<?php
final class DiffusionTaggedRepositoriesFunctionDatasource
extends PhabricatorTypeaheadCompositeDatasource {
public function getBrowseTitle() {
return pht('Browse Repositories');
}
public function getPlaceholderText() {
return pht('Type tagged(<project>)...');
}
public function getDatasourceApplicationClass() {
return 'PhabricatorProjectApplication';
}
public function getComponentDatasources() {
return array(
new PhabricatorProjectDatasource(),
);
}
public function getDatasourceFunctions() {
return array(
'tagged' => array(
'name' => pht('Repositories: ...'),
'arguments' => pht('project'),
'summary' => pht('Find results for repositories of a project.'),
'description' => pht(
'This function allows you to find results for any of the `.
`repositories of a project:'.
"\n\n".
'> tagged(engineering)'),
),
);
}
protected function didLoadResults(array $results) {
foreach ($results as $result) {
$result
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION)
->setColor(null)
->setPHID('tagged('.$result->getPHID().')')
->setDisplayName(pht('Tagged: %s', $result->getDisplayName()))
->setName('tagged '.$result->getName());
}
return $results;
}
protected function evaluateFunction($function, array $argv_list) {
$phids = array();
foreach ($argv_list as $argv) {
$phids[] = head($argv);
}
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($this->getViewer())
->withEdgeLogicPHIDs(
PhabricatorProjectObjectHasProjectEdgeType::EDGECONST,
PhabricatorQueryConstraint::OPERATOR_OR,
$phids)
->execute();
$results = array();
foreach ($repositories as $repository) {
$results[] = $repository->getPHID();
}
return $results;
}
public function renderFunctionTokens($function, array $argv_list) {
$phids = array();
foreach ($argv_list as $argv) {
$phids[] = head($argv);
}
$tokens = $this->renderTokens($phids);
foreach ($tokens as $token) {
// Remove any project color on this token.
$token->setColor(null);
if ($token->isInvalid()) {
$token
->setValue(pht('Repositories: Invalid Project'));
} else {
$token
->setTokenType(PhabricatorTypeaheadTokenView::TYPE_FUNCTION)
->setKey('tagged('.$token->getKey().')')
->setValue(pht('Tagged: %s', $token->getValue()));
}
}
return $tokens;
}
}

View file

@ -215,6 +215,7 @@ final class PhabricatorFeedStoryPublisher extends Phobject {
$all_prefs = id(new PhabricatorUserPreferencesQuery()) $all_prefs = id(new PhabricatorUserPreferencesQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer(PhabricatorUser::getOmnipotentUser())
->withUserPHIDs($phids) ->withUserPHIDs($phids)
->needSyntheticPreferences(true)
->execute(); ->execute();
$all_prefs = mpull($all_prefs, null, 'getUserPHID'); $all_prefs = mpull($all_prefs, null, 'getUserPHID');
} }

View file

@ -44,6 +44,7 @@ final class PhabricatorFilesConfigOptions
'video/mp4' => 'video/mp4', 'video/mp4' => 'video/mp4',
'video/ogg' => 'video/ogg', 'video/ogg' => 'video/ogg',
'video/webm' => 'video/webm', 'video/webm' => 'video/webm',
'video/quicktime' => 'video/quicktime',
); );
$image_default = array( $image_default = array(
@ -71,6 +72,7 @@ final class PhabricatorFilesConfigOptions
// to set the mood for your task without distracting viewers.) // to set the mood for your task without distracting viewers.)
'video/mp4' => true, 'video/mp4' => true,
'video/ogg' => true, 'video/ogg' => true,
'video/quicktime' => true,
'application/ogg' => true, 'application/ogg' => true,
); );
@ -78,6 +80,7 @@ final class PhabricatorFilesConfigOptions
'video/mp4' => true, 'video/mp4' => true,
'video/ogg' => true, 'video/ogg' => true,
'video/webm' => true, 'video/webm' => true,
'video/quicktime' => true,
'application/ogg' => true, 'application/ogg' => true,
); );

View file

@ -256,8 +256,10 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
$types[] = pht('Profile'); $types[] = pht('Profile');
} }
$types = implode(', ', $types); if ($types) {
$finfo->addProperty(pht('Attributes'), $types); $types = implode(', ', $types);
$finfo->addProperty(pht('Attributes'), $types);
}
$storage_properties = new PHUIPropertyListView(); $storage_properties = new PHUIPropertyListView();
$box->addPropertyList($storage_properties, pht('Storage')); $box->addPropertyList($storage_properties, pht('Storage'));
@ -266,9 +268,14 @@ final class PhabricatorFileInfoController extends PhabricatorFileController {
pht('Engine'), pht('Engine'),
$file->getStorageEngine()); $file->getStorageEngine());
$storage_properties->addProperty( $format_key = $file->getStorageFormat();
pht('Format'), $format = PhabricatorFileStorageFormat::getFormat($format_key);
$file->getStorageFormat()); if ($format) {
$format_name = $format->getStorageFormatName();
} else {
$format_name = pht('Unknown ("%s")', $format_key);
}
$storage_properties->addProperty(pht('Format'), $format_name);
$storage_properties->addProperty( $storage_properties->addProperty(
pht('Handle'), pht('Handle'),

View file

@ -174,7 +174,7 @@ final class PhabricatorChunkedFileStorageEngine
return (4 * 1024 * 1024); return (4 * 1024 * 1024);
} }
public function getFileDataIterator(PhabricatorFile $file, $begin, $end) { public function getRawFileDataIterator(PhabricatorFile $file, $begin, $end) {
$chunks = id(new PhabricatorFileChunkQuery()) $chunks = id(new PhabricatorFileChunkQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer(PhabricatorUser::getOmnipotentUser())
->withChunkHandles(array($file->getStorageHandle())) ->withChunkHandles(array($file->getStorageHandle()))

View file

@ -325,10 +325,10 @@ abstract class PhabricatorFileStorageEngine extends Phobject {
return $engine->getChunkSize(); return $engine->getChunkSize();
} }
public function getFileDataIterator(PhabricatorFile $file, $begin, $end) { public function getRawFileDataIterator(PhabricatorFile $file, $begin, $end) {
// The default implementation is trivial and just loads the entire file // The default implementation is trivial and just loads the entire file
// upfront. // upfront.
$data = $file->loadFileData(); $data = $this->readFile($file->getStorageHandle());
if ($begin !== null && $end !== null) { if ($begin !== null && $end !== null) {
$data = substr($data, $begin, ($end - $begin)); $data = substr($data, $begin, ($end - $begin));

View file

@ -0,0 +1,199 @@
<?php
/**
* At-rest encryption format using AES256 CBC.
*/
final class PhabricatorFileAES256StorageFormat
extends PhabricatorFileStorageFormat {
const FORMATKEY = 'aes-256-cbc';
private $keyName;
public function getStorageFormatName() {
return pht('Encrypted (AES-256-CBC)');
}
public function canGenerateNewKeyMaterial() {
return true;
}
public function generateNewKeyMaterial() {
$envelope = self::newAES256Key();
$material = $envelope->openEnvelope();
return base64_encode($material);
}
public function canCycleMasterKey() {
return true;
}
public function cycleStorageProperties() {
$file = $this->getFile();
list($key, $iv) = $this->extractKeyAndIV($file);
return $this->formatStorageProperties($key, $iv);
}
public function newReadIterator($raw_iterator) {
$file = $this->getFile();
$data = $file->loadDataFromIterator($raw_iterator);
list($key, $iv) = $this->extractKeyAndIV($file);
$data = $this->decryptData($data, $key, $iv);
return array($data);
}
public function newWriteIterator($raw_iterator) {
$file = $this->getFile();
$data = $file->loadDataFromIterator($raw_iterator);
list($key, $iv) = $this->extractKeyAndIV($file);
$data = $this->encryptData($data, $key, $iv);
return array($data);
}
public function newStorageProperties() {
// Generate a unique key and IV for this block of data.
$key_envelope = self::newAES256Key();
$iv_envelope = self::newAES256IV();
return $this->formatStorageProperties($key_envelope, $iv_envelope);
}
private function formatStorageProperties(
PhutilOpaqueEnvelope $key_envelope,
PhutilOpaqueEnvelope $iv_envelope) {
// Encode the raw binary data with base64 so we can wrap it in JSON.
$data = array(
'iv.base64' => base64_encode($iv_envelope->openEnvelope()),
'key.base64' => base64_encode($key_envelope->openEnvelope()),
);
// Encode the base64 data with JSON.
$data_clear = phutil_json_encode($data);
// Encrypt the block key with the master key, using a unique IV.
$data_iv = self::newAES256IV();
$key_name = $this->getMasterKeyName();
$master_key = $this->getMasterKeyMaterial($key_name);
$data_cipher = $this->encryptData($data_clear, $master_key, $data_iv);
return array(
'key.name' => $key_name,
'iv.base64' => base64_encode($data_iv->openEnvelope()),
'payload.base64' => base64_encode($data_cipher),
);
}
private function extractKeyAndIV(PhabricatorFile $file) {
$outer_iv = $file->getStorageProperty('iv.base64');
$outer_iv = base64_decode($outer_iv);
$outer_iv = new PhutilOpaqueEnvelope($outer_iv);
$outer_payload = $file->getStorageProperty('payload.base64');
$outer_payload = base64_decode($outer_payload);
$outer_key_name = $file->getStorageProperty('key.name');
$outer_key = $this->getMasterKeyMaterial($outer_key_name);
$payload = $this->decryptData($outer_payload, $outer_key, $outer_iv);
$payload = phutil_json_decode($payload);
$inner_iv = $payload['iv.base64'];
$inner_iv = base64_decode($inner_iv);
$inner_iv = new PhutilOpaqueEnvelope($inner_iv);
$inner_key = $payload['key.base64'];
$inner_key = base64_decode($inner_key);
$inner_key = new PhutilOpaqueEnvelope($inner_key);
return array($inner_key, $inner_iv);
}
private function encryptData(
$data,
PhutilOpaqueEnvelope $key,
PhutilOpaqueEnvelope $iv) {
$method = 'aes-256-cbc';
$key = $key->openEnvelope();
$iv = $iv->openEnvelope();
$result = openssl_encrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
if ($result === false) {
throw new Exception(
pht(
'Failed to openssl_encrypt() data: %s',
openssl_error_string()));
}
return $result;
}
private function decryptData(
$data,
PhutilOpaqueEnvelope $key,
PhutilOpaqueEnvelope $iv) {
$method = 'aes-256-cbc';
$key = $key->openEnvelope();
$iv = $iv->openEnvelope();
$result = openssl_decrypt($data, $method, $key, OPENSSL_RAW_DATA, $iv);
if ($result === false) {
throw new Exception(
pht(
'Failed to openssl_decrypt() data: %s',
openssl_error_string()));
}
return $result;
}
public static function newAES256Key() {
// Unsurprisingly, AES256 uses a 256 bit key.
$key = Filesystem::readRandomBytes(phutil_units('256 bits in bytes'));
return new PhutilOpaqueEnvelope($key);
}
public static function newAES256IV() {
// AES256 uses a 256 bit key, but the initialization vector length is
// only 128 bits.
$iv = Filesystem::readRandomBytes(phutil_units('128 bits in bytes'));
return new PhutilOpaqueEnvelope($iv);
}
public function selectMasterKey($key_name) {
// Require that the key exist on the key ring.
$this->getMasterKeyMaterial($key_name);
$this->keyName = $key_name;
return $this;
}
private function getMasterKeyName() {
if ($this->keyName !== null) {
return $this->keyName;
}
$default = PhabricatorKeyring::getDefaultKeyName(self::FORMATKEY);
if ($default !== null) {
return $default;
}
throw new Exception(
pht(
'No AES256 key is specified in the keyring as a default encryption '.
'key, and no encryption key has been explicitly selected.'));
}
private function getMasterKeyMaterial($key_name) {
return PhabricatorKeyring::getKey($key_name, self::FORMATKEY);
}
}

View file

@ -0,0 +1,44 @@
<?php
/**
* Trivial example of a file storage format for at-rest encryption.
*
* This format applies ROT13 encoding to file data as it is stored and
* reverses it on the way out. This encoding is trivially reversible. This
* format is for testing, developing, and understanding encoding formats and
* is not intended for production use.
*/
final class PhabricatorFileROT13StorageFormat
extends PhabricatorFileStorageFormat {
const FORMATKEY = 'rot13';
public function getStorageFormatName() {
return pht('Encoded (ROT13)');
}
public function newReadIterator($raw_iterator) {
$file = $this->getFile();
$iterations = $file->getStorageProperty('iterations', 1);
$value = $file->loadDataFromIterator($raw_iterator);
for ($ii = 0; $ii < $iterations; $ii++) {
$value = str_rot13($value);
}
return array($value);
}
public function newWriteIterator($raw_iterator) {
return $this->newReadIterator($raw_iterator);
}
public function newStorageProperties() {
// For extreme security, repeatedly encode the data using a random (odd)
// number of iterations.
return array(
'iterations' => (mt_rand(1, 3) * 2) - 1,
);
}
}

View file

@ -0,0 +1,20 @@
<?php
final class PhabricatorFileRawStorageFormat
extends PhabricatorFileStorageFormat {
const FORMATKEY = 'raw';
public function getStorageFormatName() {
return pht('Raw Data');
}
public function newReadIterator($raw_iterator) {
return $raw_iterator;
}
public function newWriteIterator($raw_iterator) {
return $raw_iterator;
}
}

View file

@ -0,0 +1,81 @@
<?php
abstract class PhabricatorFileStorageFormat
extends Phobject {
private $file;
final public function setFile(PhabricatorFile $file) {
$this->file = $file;
return $this;
}
final public function getFile() {
if (!$this->file) {
throw new PhutilInvalidStateException('setFile');
}
return $this->file;
}
abstract public function getStorageFormatName();
abstract public function newReadIterator($raw_iterator);
abstract public function newWriteIterator($raw_iterator);
public function newStorageProperties() {
return array();
}
public function canGenerateNewKeyMaterial() {
return false;
}
public function generateNewKeyMaterial() {
throw new PhutilMethodNotImplementedException();
}
public function canCycleMasterKey() {
return false;
}
public function cycleStorageProperties() {
throw new PhutilMethodNotImplementedException();
}
public function selectMasterKey($key_name) {
throw new Exception(
pht(
'This storage format ("%s") does not support key selection.',
$this->getStorageFormatName()));
}
final public function getStorageFormatKey() {
return $this->getPhobjectClassConstant('FORMATKEY');
}
final public static function getAllFormats() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getStorageFormatKey')
->execute();
}
final public static function getFormat($key) {
$formats = self::getAllFormats();
return idx($formats, $key);
}
final public static function requireFormat($key) {
$format = self::getFormat($key);
if (!$format) {
throw new Exception(
pht(
'No file storage format with key "%s" exists.',
$key));
}
return $format;
}
}

View file

@ -0,0 +1,77 @@
<?php
final class PhabricatorFileStorageFormatTestCase extends PhabricatorTestCase {
protected function getPhabricatorTestCaseConfiguration() {
return array(
self::PHABRICATOR_TESTCONFIG_BUILD_STORAGE_FIXTURES => true,
);
}
public function testRot13Storage() {
$engine = new PhabricatorTestStorageEngine();
$rot13_format = PhabricatorFileROT13StorageFormat::FORMATKEY;
$data = 'The cow jumped over the full moon.';
$expect = 'Gur pbj whzcrq bire gur shyy zbba.';
$params = array(
'name' => 'test.dat',
'storageEngines' => array(
$engine,
),
'format' => $rot13_format,
);
$file = PhabricatorFile::newFromFileData($data, $params);
// We should have a file stored as rot13, which reads back the input
// data correctly.
$this->assertEqual($rot13_format, $file->getStorageFormat());
$this->assertEqual($data, $file->loadFileData());
// The actual raw data in the storage engine should be encoded.
$raw_data = $engine->readFile($file->getStorageHandle());
$this->assertEqual($expect, $raw_data);
}
public function testAES256Storage() {
$engine = new PhabricatorTestStorageEngine();
$key_name = 'test.abcd';
$key_text = 'abcdefghijklmnopABCDEFGHIJKLMNOP';
PhabricatorKeyring::addKey(
array(
'name' => $key_name,
'type' => 'aes-256-cbc',
'material.base64' => base64_encode($key_text),
));
$format = id(new PhabricatorFileAES256StorageFormat())
->selectMasterKey($key_name);
$data = 'The cow jumped over the full moon.';
$params = array(
'name' => 'test.dat',
'storageEngines' => array(
$engine,
),
'format' => $format,
);
$file = PhabricatorFile::newFromFileData($data, $params);
// We should have a file stored as AES256.
$format_key = $format->getStorageFormatKey();
$this->assertEqual($format_key, $file->getStorageFormat());
$this->assertEqual($data, $file->loadFileData());
// The actual raw data in the storage engine should be encrypted. We
// can't really test this, but we can make sure it's not the same as the
// input data.
$raw_data = $engine->readFile($file->getStorageHandle());
$this->assertTrue($data !== $raw_data);
}
}

View file

@ -0,0 +1,52 @@
<?php
final class PhabricatorKeyring extends Phobject {
private static $hasReadConfiguration;
private static $keyRing = array();
public static function addKey($spec) {
self::$keyRing[$spec['name']] = $spec;
}
public static function getKey($name, $type) {
self::readConfiguration();
if (empty(self::$keyRing[$name])) {
throw new Exception(
pht(
'No key "%s" exists in keyring.',
$name));
}
$spec = self::$keyRing[$name];
$material = base64_decode($spec['material.base64'], true);
return new PhutilOpaqueEnvelope($material);
}
public static function getDefaultKeyName($type) {
self::readConfiguration();
foreach (self::$keyRing as $name => $key) {
if (!empty($key['default'])) {
return $name;
}
}
return null;
}
private static function readConfiguration() {
if (self::$hasReadConfiguration) {
return true;
}
self::$hasReadConfiguration = true;
foreach (PhabricatorEnv::getEnvConfig('keyring') as $spec) {
self::addKey($spec);
}
}
}

View file

@ -0,0 +1,111 @@
<?php
final class PhabricatorKeyringConfigOptionType
extends PhabricatorConfigJSONOptionType {
public function validateOption(PhabricatorConfigOption $option, $value) {
if (!is_array($value)) {
throw new Exception(
pht(
'Keyring configuration is not valid: value must be a '.
'list of encryption keys.'));
}
foreach ($value as $index => $spec) {
if (!is_array($spec)) {
throw new Exception(
pht(
'Keyring configuration is not valid: each entry in the list must '.
'be a dictionary describing an encryption key, but the value '.
'with index "%s" is not a dictionary.',
$index));
}
}
$map = array();
$defaults = array();
foreach ($value as $index => $spec) {
try {
PhutilTypeSpec::checkMap(
$spec,
array(
'name' => 'string',
'type' => 'string',
'material.base64' => 'string',
'default' => 'optional bool',
));
} catch (Exception $ex) {
throw new Exception(
pht(
'Keyring configuration has an invalid key specification (at '.
'index "%s"): %s.',
$index,
$ex->getMessage()));
}
$name = $spec['name'];
if (isset($map[$name])) {
throw new Exception(
pht(
'Keyring configuration is invalid: it describes multiple keys '.
'with the same name ("%s"). Each key must have a unique name.',
$name));
}
$map[$name] = true;
if (idx($spec, 'default')) {
$defaults[] = $name;
}
$type = $spec['type'];
switch ($type) {
case 'aes-256-cbc':
if (!function_exists('openssl_encrypt')) {
throw new Exception(
pht(
'Keyring is configured with a "%s" key, but the PHP OpenSSL '.
'extension is not installed. Install the OpenSSL extension '.
'to enable encryption.',
$type));
}
$material = $spec['material.base64'];
$material = base64_decode($material, true);
if ($material === false) {
throw new Exception(
pht(
'Keyring specifies an invalid key ("%s"): key material '.
'should be base64 encoded.',
$name));
}
if (strlen($material) != 32) {
throw new Exception(
pht(
'Keyring specifies an invalid key ("%s"): key material '.
'should be 32 bytes (256 bits) but has length %s.',
$name,
new PhutilNumber(strlen($material))));
}
break;
default:
throw new Exception(
pht(
'Keyring configuration is invalid: it describes a key with '.
'type "%s", but this type is unknown.',
$type));
}
}
if (count($defaults) > 1) {
throw new Exception(
pht(
'Keyring configuration is invalid: it describes multiple default '.
'encryption keys. No more than one key may be the default key. '.
'Keys currently configured as defaults: %s.',
implode(', ', $defaults)));
}
}
}

View file

@ -0,0 +1,132 @@
<?php
final class PhabricatorFilesManagementCycleWorkflow
extends PhabricatorFilesManagementWorkflow {
protected function didConstruct() {
$this
->setName('cycle')
->setSynopsis(
pht('Cycle master key for encrypted files.'))
->setArguments(
array(
array(
'name' => 'key',
'param' => 'keyname',
'help' => pht('Select a specific storage key to cycle to.'),
),
array(
'name' => 'all',
'help' => pht('Change encoding for all files.'),
),
array(
'name' => 'names',
'wildcard' => true,
),
));
}
public function execute(PhutilArgumentParser $args) {
$iterator = $this->buildIterator($args);
if (!$iterator) {
throw new PhutilArgumentUsageException(
pht(
'Either specify a list of files to cycle, or use --all to cycle '.
'all files.'));
}
$format_map = PhabricatorFileStorageFormat::getAllFormats();
$engines = PhabricatorFileStorageEngine::loadAllEngines();
$key_name = $args->getArg('key');
$failed = array();
foreach ($iterator as $file) {
$monogram = $file->getMonogram();
$engine_key = $file->getStorageEngine();
$engine = idx($engines, $engine_key);
if (!$engine) {
echo tsprintf(
"%s\n",
pht(
'%s: Uses unknown storage engine "%s".',
$monogram,
$engine_key));
$failed[] = $file;
continue;
}
if ($engine->isChunkEngine()) {
echo tsprintf(
"%s\n",
pht(
'%s: Stored as chunks, declining to cycle directly.',
$monogram));
continue;
}
$format_key = $file->getStorageFormat();
if (empty($format_map[$format_key])) {
echo tsprintf(
"%s\n",
pht(
'%s: Uses unknown storage format "%s".',
$monogram,
$format_key));
$failed[] = $file;
continue;
}
$format = clone $format_map[$format_key];
$format->setFile($file);
if (!$format->canCycleMasterKey()) {
echo tsprintf(
"%s\n",
pht(
'%s: Storage format ("%s") does not support key cycling.',
$monogram,
$format->getStorageFormatName()));
continue;
}
echo tsprintf(
"%s\n",
pht(
'%s: Cycling master key.',
$monogram));
try {
if ($key_name) {
$format->selectMasterKey($key_name);
}
$file->cycleMasterStorageKey($format);
echo tsprintf(
"%s\n",
pht('Done.'));
} catch (Exception $ex) {
echo tsprintf(
"%B\n",
pht('Failed! %s', (string)$ex));
$failed[] = $file;
}
}
if ($failed) {
$monograms = mpull($failed, 'getMonogram');
echo tsprintf(
"%s\n",
pht('Failures: %s.', implode(', ', $monograms)));
return 1;
}
return 0;
}
}

View file

@ -0,0 +1,151 @@
<?php
final class PhabricatorFilesManagementEncodeWorkflow
extends PhabricatorFilesManagementWorkflow {
protected function didConstruct() {
$this
->setName('encode')
->setSynopsis(
pht('Change the storage encoding of files.'))
->setArguments(
array(
array(
'name' => 'as',
'param' => 'format',
'help' => pht('Select the storage format to use.'),
),
array(
'name' => 'key',
'param' => 'keyname',
'help' => pht('Select a specific storage key.'),
),
array(
'name' => 'all',
'help' => pht('Change encoding for all files.'),
),
array(
'name' => 'force',
'help' => pht(
'Re-encode files which are already stored in the target '.
'encoding.'),
),
array(
'name' => 'names',
'wildcard' => true,
),
));
}
public function execute(PhutilArgumentParser $args) {
$iterator = $this->buildIterator($args);
if (!$iterator) {
throw new PhutilArgumentUsageException(
pht(
'Either specify a list of files to encode, or use --all to '.
'encode all files.'));
}
$force = (bool)$args->getArg('force');
$format_list = PhabricatorFileStorageFormat::getAllFormats();
$format_list = array_keys($format_list);
$format_list = implode(', ', $format_list);
$format_key = $args->getArg('as');
if (!strlen($format_key)) {
throw new PhutilArgumentUsageException(
pht(
'Use --as <format> to select a target encoding format. Available '.
'formats are: %s.',
$format_list));
}
$format = PhabricatorFileStorageFormat::getFormat($format_key);
if (!$format) {
throw new PhutilArgumentUsageException(
pht(
'Storage format "%s" is not valid. Available formats are: %s.',
$format_key,
$format_list));
}
$key_name = $args->getArg('key');
if (strlen($key_name)) {
$format->selectMasterKey($key_name);
}
$engines = PhabricatorFileStorageEngine::loadAllEngines();
$failed = array();
foreach ($iterator as $file) {
$monogram = $file->getMonogram();
$engine_key = $file->getStorageEngine();
$engine = idx($engines, $engine_key);
if (!$engine) {
echo tsprintf(
"%s\n",
pht(
'%s: Uses unknown storage engine "%s".',
$monogram,
$engine_key));
$failed[] = $file;
continue;
}
if ($engine->isChunkEngine()) {
echo tsprintf(
"%s\n",
pht(
'%s: Stored as chunks, no data to encode directly.',
$monogram));
continue;
}
if (($file->getStorageFormat() == $format_key) && !$force) {
echo tsprintf(
"%s\n",
pht(
'%s: Already encoded in target format.',
$monogram));
continue;
}
echo tsprintf(
"%s\n",
pht(
'%s: Changing encoding from "%s" to "%s".',
$monogram,
$file->getStorageFormat(),
$format_key));
try {
$file->migrateToStorageFormat($format);
echo tsprintf(
"%s\n",
pht('Done.'));
} catch (Exception $ex) {
echo tsprintf(
"%B\n",
pht('Failed! %s', (string)$ex));
$failed[] = $file;
}
}
if ($failed) {
$monograms = mpull($failed, 'getMonogram');
echo tsprintf(
"%s\n",
pht('Failures: %s.', implode(', ', $monograms)));
return 1;
}
return 0;
}
}

View file

@ -0,0 +1,63 @@
<?php
final class PhabricatorFilesManagementGenerateKeyWorkflow
extends PhabricatorFilesManagementWorkflow {
protected function didConstruct() {
$this
->setName('generate-key')
->setSynopsis(
pht('Generate an encryption key.'))
->setArguments(
array(
array(
'name' => 'type',
'param' => 'keytype',
'help' => pht('Select the type of key to generate.'),
),
));
}
public function execute(PhutilArgumentParser $args) {
$type = $args->getArg('type');
if (!strlen($type)) {
throw new PhutilArgumentUsageException(
pht(
'Specify the type of key to generate with --type.'));
}
$format = PhabricatorFileStorageFormat::getFormat($type);
if (!$format) {
throw new PhutilArgumentUsageException(
pht(
'No key type "%s" exists.',
$type));
}
if (!$format->canGenerateNewKeyMaterial()) {
throw new PhutilArgumentUsageException(
pht(
'Storage format "%s" can not generate keys.',
$format->getStorageFormatName()));
}
$material = $format->generateNewKeyMaterial();
$structure = array(
'name' => 'generated-key-'.Filesystem::readRandomCharacters(12),
'type' => $type,
'material.base64' => $material,
);
$json = id(new PhutilJSON())->encodeFormatted($structure);
echo tsprintf(
"%s: %s\n\n%B\n",
pht('Key Material'),
$format->getStorageFormatName(),
$json);
return 0;
}
}

View file

@ -26,14 +26,13 @@ final class PhabricatorFile extends PhabricatorFileDAO
PhabricatorPolicyInterface, PhabricatorPolicyInterface,
PhabricatorDestructibleInterface { PhabricatorDestructibleInterface {
const STORAGE_FORMAT_RAW = 'raw';
const METADATA_IMAGE_WIDTH = 'width'; const METADATA_IMAGE_WIDTH = 'width';
const METADATA_IMAGE_HEIGHT = 'height'; const METADATA_IMAGE_HEIGHT = 'height';
const METADATA_CAN_CDN = 'canCDN'; const METADATA_CAN_CDN = 'canCDN';
const METADATA_BUILTIN = 'builtin'; const METADATA_BUILTIN = 'builtin';
const METADATA_PARTIAL = 'partial'; const METADATA_PARTIAL = 'partial';
const METADATA_PROFILE = 'profile'; const METADATA_PROFILE = 'profile';
const METADATA_STORAGE = 'storage';
protected $name; protected $name;
protected $mimeType; protected $mimeType;
@ -233,10 +232,10 @@ final class PhabricatorFile extends PhabricatorFileDAO
$hash); $hash);
if ($file) { if ($file) {
// copy storageEngine, storageHandle, storageFormat
$copy_of_storage_engine = $file->getStorageEngine(); $copy_of_storage_engine = $file->getStorageEngine();
$copy_of_storage_handle = $file->getStorageHandle(); $copy_of_storage_handle = $file->getStorageHandle();
$copy_of_storage_format = $file->getStorageFormat(); $copy_of_storage_format = $file->getStorageFormat();
$copy_of_storage_properties = $file->getStorageProperties();
$copy_of_byte_size = $file->getByteSize(); $copy_of_byte_size = $file->getByteSize();
$copy_of_mime_type = $file->getMimeType(); $copy_of_mime_type = $file->getMimeType();
@ -248,6 +247,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
$new_file->setStorageEngine($copy_of_storage_engine); $new_file->setStorageEngine($copy_of_storage_engine);
$new_file->setStorageHandle($copy_of_storage_handle); $new_file->setStorageHandle($copy_of_storage_handle);
$new_file->setStorageFormat($copy_of_storage_format); $new_file->setStorageFormat($copy_of_storage_format);
$new_file->setStorageProperties($copy_of_storage_properties);
$new_file->setMimeType($copy_of_mime_type); $new_file->setMimeType($copy_of_mime_type);
$new_file->copyDimensions($file); $new_file->copyDimensions($file);
@ -290,7 +290,11 @@ final class PhabricatorFile extends PhabricatorFileDAO
$file->setStorageEngine($engine->getEngineIdentifier()); $file->setStorageEngine($engine->getEngineIdentifier());
$file->setStorageHandle(PhabricatorFileChunk::newChunkHandle()); $file->setStorageHandle(PhabricatorFileChunk::newChunkHandle());
$file->setStorageFormat(self::STORAGE_FORMAT_RAW);
// Chunked files are always stored raw because they do not actually store
// data. The chunks do, and can be individually formatted.
$file->setStorageFormat(PhabricatorFileRawStorageFormat::FORMATKEY);
$file->setIsPartial(1); $file->setIsPartial(1);
$file->readPropertiesFromParameters($params); $file->readPropertiesFromParameters($params);
@ -322,6 +326,29 @@ final class PhabricatorFile extends PhabricatorFileDAO
$file = self::initializeNewFile(); $file = self::initializeNewFile();
$aes_type = PhabricatorFileAES256StorageFormat::FORMATKEY;
$has_aes = PhabricatorKeyring::getDefaultKeyName($aes_type);
if ($has_aes !== null) {
$default_key = PhabricatorFileAES256StorageFormat::FORMATKEY;
} else {
$default_key = PhabricatorFileRawStorageFormat::FORMATKEY;
}
$key = idx($params, 'format', $default_key);
// Callers can pass in an object explicitly instead of a key. This is
// primarily useful for unit tests.
if ($key instanceof PhabricatorFileStorageFormat) {
$format = clone $key;
} else {
$format = clone PhabricatorFileStorageFormat::requireFormat($key);
}
$format->setFile($file);
$properties = $format->newStorageProperties();
$file->setStorageFormat($format->getStorageFormatKey());
$file->setStorageProperties($properties);
$data_handle = null; $data_handle = null;
$engine_identifier = null; $engine_identifier = null;
$exceptions = array(); $exceptions = array();
@ -361,10 +388,6 @@ final class PhabricatorFile extends PhabricatorFileDAO
$file->setStorageEngine($engine_identifier); $file->setStorageEngine($engine_identifier);
$file->setStorageHandle($data_handle); $file->setStorageHandle($data_handle);
// TODO: This is probably YAGNI, but allows for us to do encryption or
// compression later if we want.
$file->setStorageFormat(self::STORAGE_FORMAT_RAW);
$file->readPropertiesFromParameters($params); $file->readPropertiesFromParameters($params);
if (!$file->getMimeType()) { if (!$file->getMimeType()) {
@ -427,6 +450,53 @@ final class PhabricatorFile extends PhabricatorFileDAO
return $this; return $this;
} }
public function migrateToStorageFormat(PhabricatorFileStorageFormat $format) {
if (!$this->getID() || !$this->getStorageHandle()) {
throw new Exception(
pht("You can not migrate a file which hasn't yet been saved."));
}
$data = $this->loadFileData();
$params = array(
'name' => $this->getName(),
);
$engine = $this->instantiateStorageEngine();
$old_handle = $this->getStorageHandle();
$properties = $format->newStorageProperties();
$this->setStorageFormat($format->getStorageFormatKey());
$this->setStorageProperties($properties);
list($identifier, $new_handle) = $this->writeToEngine(
$engine,
$data,
$params);
$this->setStorageHandle($new_handle);
$this->save();
$this->deleteFileDataIfUnused(
$engine,
$identifier,
$old_handle);
return $this;
}
public function cycleMasterStorageKey(PhabricatorFileStorageFormat $format) {
if (!$this->getID() || !$this->getStorageHandle()) {
throw new Exception(
pht("You can not cycle keys for a file which hasn't yet been saved."));
}
$properties = $format->cycleStorageProperties();
$this->setStorageProperties($properties);
$this->save();
return $this;
}
private function writeToEngine( private function writeToEngine(
PhabricatorFileStorageEngine $engine, PhabricatorFileStorageEngine $engine,
$data, $data,
@ -434,7 +504,15 @@ final class PhabricatorFile extends PhabricatorFileDAO
$engine_class = get_class($engine); $engine_class = get_class($engine);
$data_handle = $engine->writeFile($data, $params); $key = $this->getStorageFormat();
$format = id(clone PhabricatorFileStorageFormat::requireFormat($key))
->setFile($this);
$data_iterator = array($data);
$formatted_iterator = $format->newWriteIterator($data_iterator);
$formatted_data = $this->loadDataFromIterator($formatted_iterator);
$data_handle = $engine->writeFile($formatted_data, $params);
if (!$data_handle || strlen($data_handle) > 255) { if (!$data_handle || strlen($data_handle) > 255) {
// This indicates an improperly implemented storage engine. // This indicates an improperly implemented storage engine.
@ -663,19 +741,8 @@ final class PhabricatorFile extends PhabricatorFileDAO
} }
public function loadFileData() { public function loadFileData() {
$iterator = $this->getFileDataIterator();
$engine = $this->instantiateStorageEngine(); return $this->loadDataFromIterator($iterator);
$data = $engine->readFile($this->getStorageHandle());
switch ($this->getStorageFormat()) {
case self::STORAGE_FORMAT_RAW:
$data = $data;
break;
default:
throw new Exception(pht('Unknown storage format.'));
}
return $data;
} }
@ -688,7 +755,14 @@ final class PhabricatorFile extends PhabricatorFileDAO
*/ */
public function getFileDataIterator($begin = null, $end = null) { public function getFileDataIterator($begin = null, $end = null) {
$engine = $this->instantiateStorageEngine(); $engine = $this->instantiateStorageEngine();
return $engine->getFileDataIterator($this, $begin, $end); $raw_iterator = $engine->getRawFileDataIterator($this, $begin, $end);
$key = $this->getStorageFormat();
$format = id(clone PhabricatorFileStorageFormat::requireFormat($key))
->setFile($this);
return $format->newReadIterator($raw_iterator);
} }
@ -917,6 +991,30 @@ final class PhabricatorFile extends PhabricatorFileDAO
return Filesystem::readRandomCharacters(20); return Filesystem::readRandomCharacters(20);
} }
public function setStorageProperties(array $properties) {
$this->metadata[self::METADATA_STORAGE] = $properties;
return $this;
}
public function getStorageProperties() {
return idx($this->metadata, self::METADATA_STORAGE, array());
}
public function getStorageProperty($key, $default = null) {
$properties = $this->getStorageProperties();
return idx($properties, $key, $default);
}
public function loadDataFromIterator($iterator) {
$result = '';
foreach ($iterator as $chunk) {
$result .= $chunk;
}
return $result;
}
public function updateDimensions($save = true) { public function updateDimensions($save = true) {
if (!$this->isViewableImage()) { if (!$this->isViewableImage()) {
throw new Exception(pht('This file is not a viewable image.')); throw new Exception(pht('This file is not a viewable image.'));

View file

@ -9,6 +9,7 @@ final class HarbormasterBuildEngine extends Phobject {
private $build; private $build;
private $viewer; private $viewer;
private $newBuildTargets = array(); private $newBuildTargets = array();
private $artifactReleaseQueue = array();
private $forceBuildableUpdate; private $forceBuildableUpdate;
public function setForceBuildableUpdate($force_buildable_update) { public function setForceBuildableUpdate($force_buildable_update) {
@ -94,6 +95,8 @@ final class HarbormasterBuildEngine extends Phobject {
$this->updateBuildable($build->getBuildable()); $this->updateBuildable($build->getBuildable());
} }
$this->releaseQueuedArtifacts();
// If we are no longer building for any reason, release all artifacts. // If we are no longer building for any reason, release all artifacts.
if (!$build->isBuilding()) { if (!$build->isBuilding()) {
$this->releaseAllArtifacts($build); $this->releaseAllArtifacts($build);
@ -149,20 +152,21 @@ final class HarbormasterBuildEngine extends Phobject {
} }
private function updateBuildSteps(HarbormasterBuild $build) { private function updateBuildSteps(HarbormasterBuild $build) {
$targets = id(new HarbormasterBuildTargetQuery()) $all_targets = id(new HarbormasterBuildTargetQuery())
->setViewer($this->getViewer()) ->setViewer($this->getViewer())
->withBuildPHIDs(array($build->getPHID())) ->withBuildPHIDs(array($build->getPHID()))
->withBuildGenerations(array($build->getBuildGeneration())) ->withBuildGenerations(array($build->getBuildGeneration()))
->execute(); ->execute();
$this->updateWaitingTargets($targets); $this->updateWaitingTargets($all_targets);
$targets = mgroup($targets, 'getBuildStepPHID'); $targets = mgroup($all_targets, 'getBuildStepPHID');
$steps = id(new HarbormasterBuildStepQuery()) $steps = id(new HarbormasterBuildStepQuery())
->setViewer($this->getViewer()) ->setViewer($this->getViewer())
->withBuildPlanPHIDs(array($build->getBuildPlan()->getPHID())) ->withBuildPlanPHIDs(array($build->getBuildPlan()->getPHID()))
->execute(); ->execute();
$steps = mpull($steps, null, 'getPHID');
// Identify steps which are in various states. // Identify steps which are in various states.
@ -252,6 +256,12 @@ final class HarbormasterBuildEngine extends Phobject {
return; return;
} }
// Release any artifacts which are not inputs to any remaining build
// step. We're done with these, so something else is free to use them.
$ongoing_phids = array_keys($queued + $waiting + $underway);
$ongoing_steps = array_select_keys($steps, $ongoing_phids);
$this->releaseUnusedArtifacts($all_targets, $ongoing_steps);
// Identify all the steps which are ready to run (because all their // Identify all the steps which are ready to run (because all their
// dependencies are complete). // dependencies are complete).
@ -294,6 +304,59 @@ final class HarbormasterBuildEngine extends Phobject {
} }
/**
* Release any artifacts which aren't used by any running or waiting steps.
*
* This releases artifacts as soon as they're no longer used. This can be
* particularly relevant when a build uses multiple hosts since it returns
* hosts to the pool more quickly.
*
* @param list<HarbormasterBuildTarget> Targets in the build.
* @param list<HarbormasterBuildStep> List of running and waiting steps.
* @return void
*/
private function releaseUnusedArtifacts(array $targets, array $steps) {
assert_instances_of($targets, 'HarbormasterBuildTarget');
assert_instances_of($steps, 'HarbormasterBuildStep');
if (!$targets || !$steps) {
return;
}
$target_phids = mpull($targets, 'getPHID');
$artifacts = id(new HarbormasterBuildArtifactQuery())
->setViewer($this->getViewer())
->withBuildTargetPHIDs($target_phids)
->withIsReleased(false)
->execute();
if (!$artifacts) {
return;
}
// Collect all the artifacts that remaining build steps accept as inputs.
$must_keep = array();
foreach ($steps as $step) {
$inputs = $step->getStepImplementation()->getArtifactInputs();
foreach ($inputs as $input) {
$artifact_key = $input['key'];
$must_keep[$artifact_key] = true;
}
}
// Queue unreleased artifacts which no remaining step uses for immediate
// release.
foreach ($artifacts as $artifact) {
$key = $artifact->getArtifactKey();
if (isset($must_keep[$key])) {
continue;
}
$this->artifactReleaseQueue[] = $artifact;
}
}
/** /**
* Process messages which were sent to these targets, kicking applicable * Process messages which were sent to these targets, kicking applicable
* targets out of "Waiting" and into either "Passed" or "Failed". * targets out of "Waiting" and into either "Passed" or "Failed".
@ -488,12 +551,18 @@ final class HarbormasterBuildEngine extends Phobject {
$artifacts = id(new HarbormasterBuildArtifactQuery()) $artifacts = id(new HarbormasterBuildArtifactQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuildTargetPHIDs($target_phids) ->withBuildTargetPHIDs($target_phids)
->withIsReleased(false)
->execute(); ->execute();
foreach ($artifacts as $artifact) { foreach ($artifacts as $artifact) {
$artifact->releaseArtifact(); $artifact->releaseArtifact();
} }
}
private function releaseQueuedArtifacts() {
foreach ($this->artifactReleaseQueue as $key => $artifact) {
$artifact->releaseArtifact();
unset($this->artifactReleaseQueue[$key]);
}
} }
} }

View file

@ -9,6 +9,7 @@ final class HarbormasterBuildArtifactQuery
private $artifactIndexes; private $artifactIndexes;
private $keyBuildPHID; private $keyBuildPHID;
private $keyBuildGeneration; private $keyBuildGeneration;
private $isReleased;
public function withIDs(array $ids) { public function withIDs(array $ids) {
$this->ids = $ids; $this->ids = $ids;
@ -30,6 +31,11 @@ final class HarbormasterBuildArtifactQuery
return $this; return $this;
} }
public function withIsReleased($released) {
$this->isReleased = $released;
return $this;
}
public function newResultObject() { public function newResultObject() {
return new HarbormasterBuildArtifact(); return new HarbormasterBuildArtifact();
} }
@ -94,6 +100,13 @@ final class HarbormasterBuildArtifactQuery
$this->artifactIndexes); $this->artifactIndexes);
} }
if ($this->isReleased !== null) {
$where[] = qsprintf(
$conn,
'isReleased = %d',
(int)$this->isReleased);
}
return $where; return $where;
} }

View file

@ -103,11 +103,6 @@ abstract class HarbormasterBuildStepImplementation extends Phobject {
/** /**
* Return the name of artifacts produced by this command. * Return the name of artifacts produced by this command.
* *
* Something like:
*
* return array(
* 'some_name_input_by_user' => 'host');
*
* Future steps will calculate all available artifact mappings * Future steps will calculate all available artifact mappings
* before them and filter on the type. * before them and filter on the type.
* *

View file

@ -8,6 +8,7 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO
protected $artifactIndex; protected $artifactIndex;
protected $artifactKey; protected $artifactKey;
protected $artifactData = array(); protected $artifactData = array();
protected $isReleased = 0;
private $buildTarget = self::ATTACHABLE; private $buildTarget = self::ATTACHABLE;
private $artifactImplementation; private $artifactImplementation;
@ -29,6 +30,7 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO
'artifactType' => 'text32', 'artifactType' => 'text32',
'artifactIndex' => 'bytes12', 'artifactIndex' => 'bytes12',
'artifactKey' => 'text255', 'artifactKey' => 'text255',
'isReleased' => 'bool',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_artifact' => array( 'key_artifact' => array(
@ -83,13 +85,18 @@ final class HarbormasterBuildArtifact extends HarbormasterDAO
} }
public function releaseArtifact() { public function releaseArtifact() {
$impl = $this->getArtifactImplementation(); if ($this->getIsReleased()) {
return $this;
}
$impl = $this->getArtifactImplementation();
if ($impl) { if ($impl) {
$impl->releaseArtifact(PhabricatorUser::getOmnipotentUser()); $impl->releaseArtifact(PhabricatorUser::getOmnipotentUser());
} }
return null; return $this
->setIsReleased(1)
->save();
} }
public function getArtifactImplementation() { public function getArtifactImplementation() {

View file

@ -54,16 +54,18 @@ final class ManiphestTransaction
return parent::shouldGenerateOldValue(); return parent::shouldGenerateOldValue();
} }
public function getRemarkupBlocks() { protected function newRemarkupChanges() {
$blocks = parent::getRemarkupBlocks(); $changes = array();
switch ($this->getTransactionType()) { switch ($this->getTransactionType()) {
case self::TYPE_DESCRIPTION: case self::TYPE_DESCRIPTION:
$blocks[] = $this->getNewValue(); $changes[] = $this->newRemarkupChange()
->setOldValue($this->getOldValue())
->setNewValue($this->getNewValue());
break; break;
} }
return $blocks; return $changes;
} }
public function getRequiredHandlePHIDs() { public function getRequiredHandlePHIDs() {

View file

@ -877,6 +877,7 @@ final class PhabricatorMetaMTAMail
$all_prefs = id(new PhabricatorUserPreferencesQuery()) $all_prefs = id(new PhabricatorUserPreferencesQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer(PhabricatorUser::getOmnipotentUser())
->withUserPHIDs($actor_phids) ->withUserPHIDs($actor_phids)
->needSyntheticPreferences(true)
->execute(); ->execute();
$all_prefs = mpull($all_prefs, null, 'getUserPHID'); $all_prefs = mpull($all_prefs, null, 'getUserPHID');
@ -1105,53 +1106,33 @@ final class PhabricatorMetaMTAMail
private function loadPreferences($target_phid) { private function loadPreferences($target_phid) {
if (!self::shouldMultiplexAllMail()) { $viewer = PhabricatorUser::getOmnipotentUser();
$target_phid = null;
}
if ($target_phid) { if (self::shouldMultiplexAllMail()) {
$preferences = id(new PhabricatorUserPreferencesQuery()) $preferences = id(new PhabricatorUserPreferencesQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer($viewer)
->withUserPHIDs(array($target_phid)) ->withUserPHIDs(array($target_phid))
->needSyntheticPreferences(true)
->executeOne(); ->executeOne();
} else { if ($preferences) {
$preferences = null; return $preferences;
}
} }
// TODO: Here, we would load global preferences once they exist. return PhabricatorUserPreferences::loadGlobalPreferences($viewer);
if (!$preferences) {
// If we haven't found suitable preferences yet, return an empty object
// which implicitly has all the default values.
$preferences = id(new PhabricatorUserPreferences())
->attachUser(new PhabricatorUser());
}
return $preferences;
} }
private function shouldAddRePrefix(PhabricatorUserPreferences $preferences) { private function shouldAddRePrefix(PhabricatorUserPreferences $preferences) {
$default_value = PhabricatorEnv::getEnvConfig('metamta.re-prefix'); $value = $preferences->getSettingValue(
$value = $preferences->getPreference(
PhabricatorEmailRePrefixSetting::SETTINGKEY); PhabricatorEmailRePrefixSetting::SETTINGKEY);
if ($value === null) {
return $default_value;
}
return ($value == PhabricatorEmailRePrefixSetting::VALUE_RE_PREFIX); return ($value == PhabricatorEmailRePrefixSetting::VALUE_RE_PREFIX);
} }
private function shouldVarySubject(PhabricatorUserPreferences $preferences) { private function shouldVarySubject(PhabricatorUserPreferences $preferences) {
$default_value = PhabricatorEnv::getEnvConfig('metamta.vary-subjects'); $value = $preferences->getSettingValue(
$value = $preferences->getPreference(
PhabricatorEmailVarySubjectsSetting::SETTINGKEY); PhabricatorEmailVarySubjectsSetting::SETTINGKEY);
if ($value === null) {
return $default_value;
}
return ($value == PhabricatorEmailVarySubjectsSetting::VALUE_VARY_SUBJECTS); return ($value == PhabricatorEmailVarySubjectsSetting::VALUE_VARY_SUBJECTS);
} }

View file

@ -47,15 +47,15 @@ final class PasteCreateConduitAPIMethod extends PasteConduitAPIMethod {
$xactions = array(); $xactions = array();
$xactions[] = id(new PhabricatorPasteTransaction()) $xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) ->setTransactionType(PhabricatorPasteContentTransaction::TRANSACTIONTYPE)
->setNewValue($content); ->setNewValue($content);
$xactions[] = id(new PhabricatorPasteTransaction()) $xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE) ->setTransactionType(PhabricatorPasteTitleTransaction::TRANSACTIONTYPE)
->setNewValue($title); ->setNewValue($title);
$xactions[] = id(new PhabricatorPasteTransaction()) $xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE) ->setTransactionType(PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE)
->setNewValue($language); ->setNewValue($language);
$editor = id(new PhabricatorPasteEditor()) $editor = id(new PhabricatorPasteEditor())

View file

@ -20,7 +20,7 @@ final class PhabricatorPasteArchiveController
return new Aphront404Response(); return new Aphront404Response();
} }
$view_uri = '/P'.$paste->getID(); $view_uri = $paste->getURI();
if ($request->isFormPost()) { if ($request->isFormPost()) {
if ($paste->isArchived()) { if ($paste->isArchived()) {
@ -32,7 +32,7 @@ final class PhabricatorPasteArchiveController
$xactions = array(); $xactions = array();
$xactions[] = id(new PhabricatorPasteTransaction()) $xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS) ->setTransactionType(PhabricatorPasteStatusTransaction::TRANSACTIONTYPE)
->setNewValue($new_status); ->setNewValue($new_status);
id(new PhabricatorPasteEditor()) id(new PhabricatorPasteEditor())

View file

@ -71,7 +71,7 @@ final class PhabricatorPasteEditEngine
id(new PhabricatorTextEditField()) id(new PhabricatorTextEditField())
->setKey('title') ->setKey('title')
->setLabel(pht('Title')) ->setLabel(pht('Title'))
->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE) ->setTransactionType(PhabricatorPasteTitleTransaction::TRANSACTIONTYPE)
->setDescription(pht('The title of the paste.')) ->setDescription(pht('The title of the paste.'))
->setConduitDescription(pht('Retitle the paste.')) ->setConduitDescription(pht('Retitle the paste.'))
->setConduitTypeDescription(pht('New paste title.')) ->setConduitTypeDescription(pht('New paste title.'))
@ -79,7 +79,8 @@ final class PhabricatorPasteEditEngine
id(new PhabricatorSelectEditField()) id(new PhabricatorSelectEditField())
->setKey('language') ->setKey('language')
->setLabel(pht('Language')) ->setLabel(pht('Language'))
->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE) ->setTransactionType(
PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE)
->setAliases(array('lang')) ->setAliases(array('lang'))
->setIsCopyable(true) ->setIsCopyable(true)
->setOptions($langs) ->setOptions($langs)
@ -94,7 +95,8 @@ final class PhabricatorPasteEditEngine
id(new PhabricatorTextAreaEditField()) id(new PhabricatorTextAreaEditField())
->setKey('text') ->setKey('text')
->setLabel(pht('Text')) ->setLabel(pht('Text'))
->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) ->setTransactionType(
PhabricatorPasteContentTransaction::TRANSACTIONTYPE)
->setMonospaced(true) ->setMonospaced(true)
->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL) ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_TALL)
->setDescription(pht('The main body text of the paste.')) ->setDescription(pht('The main body text of the paste.'))
@ -104,7 +106,8 @@ final class PhabricatorPasteEditEngine
id(new PhabricatorSelectEditField()) id(new PhabricatorSelectEditField())
->setKey('status') ->setKey('status')
->setLabel(pht('Status')) ->setLabel(pht('Status'))
->setTransactionType(PhabricatorPasteTransaction::TYPE_STATUS) ->setTransactionType(
PhabricatorPasteStatusTransaction::TRANSACTIONTYPE)
->setIsConduitOnly(true) ->setIsConduitOnly(true)
->setOptions(PhabricatorPaste::getStatusNameMap()) ->setOptions(PhabricatorPaste::getStatusNameMap())
->setDescription(pht('Active or archived status.')) ->setDescription(pht('Active or archived status.'))

View file

@ -3,8 +3,6 @@
final class PhabricatorPasteEditor final class PhabricatorPasteEditor
extends PhabricatorApplicationTransactionEditor { extends PhabricatorApplicationTransactionEditor {
private $fileName;
public function getEditorApplicationClass() { public function getEditorApplicationClass() {
return 'PhabricatorPasteApplication'; return 'PhabricatorPasteApplication';
} }
@ -13,29 +11,17 @@ final class PhabricatorPasteEditor
return pht('Pastes'); return pht('Pastes');
} }
public static function initializeFileForPaste( public function getCreateObjectTitle($author, $object) {
PhabricatorUser $actor, return pht('%s created this paste.', $author);
$name, }
$data) {
return PhabricatorFile::newFromFileData( public function getCreateObjectTitleForFeed($author, $object) {
$data, return pht('%s created %s.', $author, $object);
array(
'name' => $name,
'mime-type' => 'text/plain; charset=utf-8',
'authorPHID' => $actor->getPHID(),
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
'editPolicy' => PhabricatorPolicies::POLICY_NOONE,
));
} }
public function getTransactionTypes() { public function getTransactionTypes() {
$types = parent::getTransactionTypes(); $types = parent::getTransactionTypes();
$types[] = PhabricatorPasteTransaction::TYPE_CONTENT;
$types[] = PhabricatorPasteTransaction::TYPE_TITLE;
$types[] = PhabricatorPasteTransaction::TYPE_LANGUAGE;
$types[] = PhabricatorPasteTransaction::TYPE_STATUS;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY; $types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY; $types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
$types[] = PhabricatorTransactions::TYPE_COMMENT; $types[] = PhabricatorTransactions::TYPE_COMMENT;
@ -43,163 +29,14 @@ final class PhabricatorPasteEditor
return $types; return $types;
} }
protected function shouldApplyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
return true;
}
protected function applyInitialEffects(
PhabricatorLiskDAO $object,
array $xactions) {
// Find the most user-friendly filename we can by examining the title of
// the paste and the pending transactions. We'll use this if we create a
// new file to store raw content later.
$name = $object->getTitle();
if (!strlen($name)) {
$name = 'paste.raw';
}
$type_title = PhabricatorPasteTransaction::TYPE_TITLE;
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() == $type_title) {
$name = $xaction->getNewValue();
}
}
$this->fileName = $name;
}
protected function validateTransaction(
PhabricatorLiskDAO $object,
$type,
array $xactions) {
$errors = parent::validateTransaction($object, $type, $xactions);
switch ($type) {
case PhabricatorPasteTransaction::TYPE_CONTENT:
if (!$object->getFilePHID() && !$xactions) {
$error = new PhabricatorApplicationTransactionValidationError(
$type,
pht('Required'),
pht('You must provide content to create a paste.'),
null);
$error->setIsMissingFieldError(true);
$errors[] = $error;
}
break;
}
return $errors;
}
protected function getCustomTransactionOldValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorPasteTransaction::TYPE_CONTENT:
return $object->getFilePHID();
case PhabricatorPasteTransaction::TYPE_TITLE:
return $object->getTitle();
case PhabricatorPasteTransaction::TYPE_LANGUAGE:
return $object->getLanguage();
case PhabricatorPasteTransaction::TYPE_STATUS:
return $object->getStatus();
}
}
protected function getCustomTransactionNewValue(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorPasteTransaction::TYPE_TITLE:
case PhabricatorPasteTransaction::TYPE_LANGUAGE:
case PhabricatorPasteTransaction::TYPE_STATUS:
return $xaction->getNewValue();
case PhabricatorPasteTransaction::TYPE_CONTENT:
// If this transaction does not really change the paste content, return
// the current file PHID so this transaction no-ops.
$new_content = $xaction->getNewValue();
$old_content = $object->getRawContent();
$file_phid = $object->getFilePHID();
if (($new_content === $old_content) && $file_phid) {
return $file_phid;
}
$file = self::initializeFileForPaste(
$this->getActor(),
$this->fileName,
$xaction->getNewValue());
return $file->getPHID();
}
}
protected function applyCustomInternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorPasteTransaction::TYPE_CONTENT:
$object->setFilePHID($xaction->getNewValue());
return;
case PhabricatorPasteTransaction::TYPE_TITLE:
$object->setTitle($xaction->getNewValue());
return;
case PhabricatorPasteTransaction::TYPE_LANGUAGE:
$object->setLanguage($xaction->getNewValue());
return;
case PhabricatorPasteTransaction::TYPE_STATUS:
$object->setStatus($xaction->getNewValue());
return;
}
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function applyCustomExternalTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorPasteTransaction::TYPE_CONTENT:
case PhabricatorPasteTransaction::TYPE_TITLE:
case PhabricatorPasteTransaction::TYPE_LANGUAGE:
case PhabricatorPasteTransaction::TYPE_STATUS:
return;
}
return parent::applyCustomExternalTransaction($object, $xaction);
}
protected function extractFilePHIDsFromCustomTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorPasteTransaction::TYPE_CONTENT:
return array($xaction->getNewValue());
}
return parent::extractFilePHIDsFromCustomTransaction($object, $xaction);
}
protected function shouldSendMail( protected function shouldSendMail(
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
array $xactions) { array $xactions) {
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) { if ($this->getIsNewObject()) {
case PhabricatorPasteTransaction::TYPE_CONTENT: return false;
return false;
default:
break;
}
} }
return true; return true;
} }
@ -258,8 +95,4 @@ final class PhabricatorPasteEditor
return true; return true;
} }
protected function supportsSearch() {
return false;
}
} }

View file

@ -17,15 +17,15 @@ final class PhabricatorPasteTestDataGenerator
$xactions = array(); $xactions = array();
$xactions[] = $this->newTransaction( $xactions[] = $this->newTransaction(
PhabricatorPasteTransaction::TYPE_TITLE, PhabricatorPasteTitleTransaction::TRANSACTIONTYPE,
$name); $name);
$xactions[] = $this->newTransaction( $xactions[] = $this->newTransaction(
PhabricatorPasteTransaction::TYPE_LANGUAGE, PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE,
$language); $language);
$xactions[] = $this->newTransaction( $xactions[] = $this->newTransaction(
PhabricatorPasteTransaction::TYPE_CONTENT, PhabricatorPasteContentTransaction::TRANSACTIONTYPE,
$content); $content);
$editor = id(new PhabricatorPasteEditor()) $editor = id(new PhabricatorPasteEditor())

View file

@ -24,17 +24,13 @@ final class PasteCreateMailReceiver extends PhabricatorMailReceiver {
$xactions = array(); $xactions = array();
$xactions[] = id(new PhabricatorPasteTransaction()) $xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_CONTENT) ->setTransactionType(PhabricatorPasteContentTransaction::TRANSACTIONTYPE)
->setNewValue($mail->getCleanTextBody()); ->setNewValue($mail->getCleanTextBody());
$xactions[] = id(new PhabricatorPasteTransaction()) $xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_TITLE) ->setTransactionType(PhabricatorPasteTitleTransaction::TRANSACTIONTYPE)
->setNewValue($title); ->setNewValue($title);
$xactions[] = id(new PhabricatorPasteTransaction())
->setTransactionType(PhabricatorPasteTransaction::TYPE_LANGUAGE)
->setNewValue(''); // auto-detect
$paste = PhabricatorPaste::initializeNewPaste($sender); $paste = PhabricatorPaste::initializeNewPaste($sender);
$content_source = $mail->newContentSource(); $content_source = $mail->newContentSource();

View file

@ -1,12 +1,7 @@
<?php <?php
final class PhabricatorPasteTransaction final class PhabricatorPasteTransaction
extends PhabricatorApplicationTransaction { extends PhabricatorModularTransaction {
const TYPE_CONTENT = 'paste.create';
const TYPE_TITLE = 'paste.title';
const TYPE_LANGUAGE = 'paste.language';
const TYPE_STATUS = 'paste.status';
const MAILTAG_CONTENT = 'paste-content'; const MAILTAG_CONTENT = 'paste-content';
const MAILTAG_OTHER = 'paste-other'; const MAILTAG_OTHER = 'paste-other';
@ -24,226 +19,16 @@ final class PhabricatorPasteTransaction
return new PhabricatorPasteTransactionComment(); return new PhabricatorPasteTransactionComment();
} }
public function getRequiredHandlePHIDs() { public function getBaseTransactionClass() {
$phids = parent::getRequiredHandlePHIDs(); return 'PhabricatorPasteTransactionType';
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
$phids[] = $this->getObjectPHID();
break;
}
return $phids;
}
public function shouldHide() {
$old = $this->getOldValue();
switch ($this->getTransactionType()) {
case self::TYPE_TITLE:
case self::TYPE_LANGUAGE:
if ($old === null) {
return true;
}
break;
}
return parent::shouldHide();
}
public function getIcon() {
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
return 'fa-plus';
break;
case self::TYPE_TITLE:
case self::TYPE_LANGUAGE:
return 'fa-pencil';
break;
case self::TYPE_STATUS:
$new = $this->getNewValue();
switch ($new) {
case PhabricatorPaste::STATUS_ACTIVE:
return 'fa-check';
case PhabricatorPaste::STATUS_ARCHIVED:
return 'fa-ban';
}
break;
}
return parent::getIcon();
}
public function getTitle() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
$type = $this->getTransactionType();
switch ($type) {
case PhabricatorTransactions::TYPE_CREATE:
return pht(
'%s created this paste.',
$this->renderHandleLink($author_phid));
case self::TYPE_CONTENT:
if ($old === null) {
return pht(
'%s created this paste.',
$this->renderHandleLink($author_phid));
} else {
return pht(
'%s edited the content of this paste.',
$this->renderHandleLink($author_phid));
}
break;
case self::TYPE_TITLE:
return pht(
'%s updated the paste\'s title to "%s".',
$this->renderHandleLink($author_phid),
$new);
break;
case self::TYPE_LANGUAGE:
return pht(
"%s updated the paste's language.",
$this->renderHandleLink($author_phid));
break;
case self::TYPE_STATUS:
switch ($new) {
case PhabricatorPaste::STATUS_ACTIVE:
return pht(
'%s activated this paste.',
$this->renderHandleLink($author_phid));
case PhabricatorPaste::STATUS_ARCHIVED:
return pht(
'%s archived this paste.',
$this->renderHandleLink($author_phid));
}
break;
}
return parent::getTitle();
}
public function getTitleForFeed() {
$author_phid = $this->getAuthorPHID();
$object_phid = $this->getObjectPHID();
$old = $this->getOldValue();
$new = $this->getNewValue();
$type = $this->getTransactionType();
switch ($type) {
case self::TYPE_CONTENT:
if ($old === null) {
return pht(
'%s created %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else {
return pht(
'%s edited %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
break;
case self::TYPE_TITLE:
return pht(
'%s updated the title for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
case self::TYPE_LANGUAGE:
return pht(
'%s updated the language for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
break;
case self::TYPE_STATUS:
switch ($new) {
case PhabricatorPaste::STATUS_ACTIVE:
return pht(
'%s activated %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
case PhabricatorPaste::STATUS_ARCHIVED:
return pht(
'%s archived %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
break;
}
return parent::getTitleForFeed();
}
public function getColor() {
$old = $this->getOldValue();
$new = $this->getNewValue();
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
return PhabricatorTransactions::COLOR_GREEN;
case self::TYPE_STATUS:
switch ($new) {
case PhabricatorPaste::STATUS_ACTIVE:
return 'green';
case PhabricatorPaste::STATUS_ARCHIVED:
return 'indigo';
}
break;
}
return parent::getColor();
}
public function hasChangeDetails() {
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
return ($this->getOldValue() !== null);
}
return parent::hasChangeDetails();
}
public function renderChangeDetails(PhabricatorUser $viewer) {
switch ($this->getTransactionType()) {
case self::TYPE_CONTENT:
$old = $this->getOldValue();
$new = $this->getNewValue();
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array_filter(array($old, $new)))
->execute();
$files = mpull($files, null, 'getPHID');
$old_text = '';
if (idx($files, $old)) {
$old_text = $files[$old]->loadFileData();
}
$new_text = '';
if (idx($files, $new)) {
$new_text = $files[$new]->loadFileData();
}
return $this->renderTextCorpusChangeDetails(
$viewer,
$old_text,
$new_text);
}
return parent::renderChangeDetails($viewer);
} }
public function getMailTags() { public function getMailTags() {
$tags = array(); $tags = array();
switch ($this->getTransactionType()) { switch ($this->getTransactionType()) {
case self::TYPE_TITLE: case PhabricatorPasteTitleTransaction::TRANSACTIONTYPE:
case self::TYPE_CONTENT: case PhabricatorPasteContentTransaction::TRANSACTIONTYPE:
case self::TYPE_LANGUAGE: case PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE:
$tags[] = self::MAILTAG_CONTENT; $tags[] = self::MAILTAG_CONTENT;
break; break;
case PhabricatorTransactions::TYPE_COMMENT: case PhabricatorTransactions::TYPE_COMMENT:

View file

@ -0,0 +1,135 @@
<?php
final class PhabricatorPasteContentTransaction
extends PhabricatorPasteTransactionType {
const TRANSACTIONTYPE = 'paste.create';
private $fileName;
public function generateOldValue($object) {
return $object->getFilePHID();
}
public function applyInternalEffects($object, $value) {
$object->setFilePHID($value);
}
public function extractFilePHIDs($object, $value) {
return array($value);
}
public function validateTransactions($object, array $xactions) {
if ($object->getFilePHID() || $xactions) {
return array();
}
$error = $this->newError(
pht('Required'),
pht('You must provide content to create a paste.'));
$error->setIsMissingFieldError(true);
return array($error);
}
public function willApplyTransactions($object, array $xactions) {
// Find the most user-friendly filename we can by examining the title of
// the paste and the pending transactions. We'll use this if we create a
// new file to store raw content later.
$name = $object->getTitle();
if (!strlen($name)) {
$name = 'paste.raw';
}
$type_title = PhabricatorPasteTitleTransaction::TRANSACTIONTYPE;
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() == $type_title) {
$name = $xaction->getNewValue();
}
}
$this->fileName = $name;
}
public function generateNewValue($object, $value) {
// If this transaction does not really change the paste content, return
// the current file PHID so this transaction no-ops.
$old_content = $object->getRawContent();
if ($value === $old_content) {
$file_phid = $object->getFilePHID();
if ($file_phid) {
return $file_phid;
}
}
$editor = $this->getEditor();
$actor = $editor->getActor();
$file = $this->newFileForPaste($actor, $this->fileName, $value);
return $file->getPHID();
}
private function newFileForPaste(PhabricatorUser $actor, $name, $data) {
return PhabricatorFile::newFromFileData(
$data,
array(
'name' => $name,
'mime-type' => 'text/plain; charset=utf-8',
'authorPHID' => $actor->getPHID(),
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
'editPolicy' => PhabricatorPolicies::POLICY_NOONE,
));
}
public function getIcon() {
return 'fa-plus';
}
public function getTitle() {
return pht(
'%s edited the content of this paste.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s edited %s.',
$this->renderAuthor(),
$this->renderObject());
}
public function hasChangeDetailView() {
return true;
}
public function newChangeDetailView() {
$viewer = $this->getViewer();
$old = $this->getOldValue();
$new = $this->getNewValue();
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($old, $new))
->execute();
$files = mpull($files, null, 'getPHID');
$old_text = '';
if (idx($files, $old)) {
$old_text = $files[$old]->loadFileData();
}
$new_text = '';
if (idx($files, $new)) {
$new_text = $files[$new]->loadFileData();
}
return id(new PhabricatorApplicationTransactionTextDiffDetailView())
->setViewer($viewer)
->setOldText($old_text)
->setNewText($new_text);
}
}

View file

@ -0,0 +1,29 @@
<?php
final class PhabricatorPasteLanguageTransaction
extends PhabricatorPasteTransactionType {
const TRANSACTIONTYPE = 'paste.language';
public function generateOldValue($object) {
return $object->getLanguage();
}
public function applyInternalEffects($object, $value) {
$object->setLanguage($value);
}
public function getTitle() {
return pht(
"%s updated the paste's language.",
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s updated the language for %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -0,0 +1,62 @@
<?php
final class PhabricatorPasteStatusTransaction
extends PhabricatorPasteTransactionType {
const TRANSACTIONTYPE = 'paste.status';
public function generateOldValue($object) {
return $object->getStatus();
}
public function applyInternalEffects($object, $value) {
$object->setStatus($value);
}
private function isActivate() {
return ($this->getNewValue() == PhabricatorPaste::STATUS_ARCHIVED);
}
public function getIcon() {
if ($this->isActivate()) {
return 'fa-check';
} else {
return 'fa-ban';
}
}
public function getColor() {
if ($this->isActivate()) {
return 'green';
} else {
return 'indigo';
}
}
public function getTitle() {
if ($this->isActivate()) {
return pht(
'%s activated this paste.',
$this->renderAuthor());
} else {
return pht(
'%s archived this paste.',
$this->renderAuthor());
}
}
public function getTitleForFeed() {
if ($this->isActivate()) {
return pht(
'%s activated %s.',
$this->renderAuthor(),
$this->renderObject());
} else {
return pht(
'%s archived %s.',
$this->renderAuthor(),
$this->renderObject());
}
}
}

View file

@ -0,0 +1,33 @@
<?php
final class PhabricatorPasteTitleTransaction
extends PhabricatorPasteTransactionType {
const TRANSACTIONTYPE = 'paste.title';
public function generateOldValue($object) {
return $object->getTitle();
}
public function applyInternalEffects($object, $value) {
$object->setTitle($value);
}
public function getTitle() {
return pht(
'%s updated the paste\'s title from "%s" to "%s".',
$this->renderAuthor(),
$this->getOldValue(),
$this->getNewValue());
}
public function getTitleForFeed() {
return pht(
'%s updated the title for %s from "%s" to "%s".',
$this->renderAuthor(),
$this->renderObject(),
$this->getOldValue(),
$this->getNewValue());
}
}

View file

@ -0,0 +1,4 @@
<?php
abstract class PhabricatorPasteTransactionType
extends PhabricatorModularTransactionType {}

View file

@ -29,21 +29,28 @@ final class PhabricatorUserPreferencesCacheType
$preferences = id(new PhabricatorUserPreferencesQuery()) $preferences = id(new PhabricatorUserPreferencesQuery())
->setViewer($viewer) ->setViewer($viewer)
->withUserPHIDs($user_phids) ->withUsers($users)
->needSyntheticPreferences(true)
->execute(); ->execute();
$preferences = mpull($preferences, null, 'getUserPHID');
$all_settings = PhabricatorSetting::getAllSettings(); $all_settings = PhabricatorSetting::getAllSettings();
$settings = array(); $settings = array();
foreach ($preferences as $preference) { foreach ($users as $user_phid => $user) {
$user_phid = $preference->getUserPHID(); $preference = idx($preferences, $user_phid);
if (!$preference) {
continue;
}
foreach ($all_settings as $key => $setting) { foreach ($all_settings as $key => $setting) {
$value = $preference->getSettingValue($key); $value = $preference->getSettingValue($key);
// As an optimization, we omit the value from the cache if it is // As an optimization, we omit the value from the cache if it is
// exactly the same as the hardcoded default. // exactly the same as the hardcoded default.
$default_value = id(clone $setting) $default_value = id(clone $setting)
->setViewer($users[$user_phid]) ->setViewer($user)
->getSettingDefaultValue(); ->getSettingDefaultValue();
if ($value === $default_value) { if ($value === $default_value) {
continue; continue;

View file

@ -55,6 +55,7 @@ final class PhabricatorPhameApplication extends PhabricatorApplication {
'preview/' => 'PhabricatorMarkupPreviewController', 'preview/' => 'PhabricatorMarkupPreviewController',
'framed/(?P<id>\d+)/' => 'PhamePostFramedController', 'framed/(?P<id>\d+)/' => 'PhamePostFramedController',
'move/(?P<id>\d+)/' => 'PhamePostMoveController', 'move/(?P<id>\d+)/' => 'PhamePostMoveController',
'archive/(?P<id>\d+)/' => 'PhamePostArchiveController',
'comment/(?P<id>[1-9]\d*)/' => 'PhamePostCommentController', 'comment/(?P<id>[1-9]\d*)/' => 'PhamePostCommentController',
), ),
'blog/' => array( 'blog/' => array(
@ -66,6 +67,7 @@ final class PhabricatorPhameApplication extends PhabricatorApplication {
'manage/(?P<id>[^/]+)/' => 'PhameBlogManageController', 'manage/(?P<id>[^/]+)/' => 'PhameBlogManageController',
'feed/(?P<id>[^/]+)/' => 'PhameBlogFeedController', 'feed/(?P<id>[^/]+)/' => 'PhameBlogFeedController',
'picture/(?P<id>[1-9]\d*)/' => 'PhameBlogProfilePictureController', 'picture/(?P<id>[1-9]\d*)/' => 'PhameBlogProfilePictureController',
'header/(?P<id>[1-9]\d*)/' => 'PhameBlogHeaderPictureController',
), ),
) + $this->getResourceSubroutes(), ) + $this->getResourceSubroutes(),
); );

View file

@ -2,13 +2,15 @@
final class PhameConstants extends Phobject { final class PhameConstants extends Phobject {
const VISIBILITY_DRAFT = 0; const VISIBILITY_DRAFT = 0;
const VISIBILITY_PUBLISHED = 1; const VISIBILITY_PUBLISHED = 1;
const VISIBILITY_ARCHIVED = 2;
public static function getPhamePostStatusMap() { public static function getPhamePostStatusMap() {
return array( return array(
self::VISIBILITY_PUBLISHED => pht('Published'), self::VISIBILITY_PUBLISHED => pht('Published'),
self::VISIBILITY_DRAFT => pht('Draft'), self::VISIBILITY_DRAFT => pht('Draft'),
self::VISIBILITY_ARCHIVED => pht('Archived'),
); );
} }
@ -16,6 +18,7 @@ final class PhameConstants extends Phobject {
$map = array( $map = array(
self::VISIBILITY_PUBLISHED => pht('Published'), self::VISIBILITY_PUBLISHED => pht('Published'),
self::VISIBILITY_DRAFT => pht('Draft'), self::VISIBILITY_DRAFT => pht('Draft'),
self::VISIBILITY_ARCHIVED => pht('Archived'),
); );
return idx($map, $status, pht('Unknown')); return idx($map, $status, pht('Unknown'));
} }

View file

@ -35,7 +35,8 @@ final class PhameHomeController extends PhamePostController {
$posts = id(new PhamePostQuery()) $posts = id(new PhamePostQuery())
->setViewer($viewer) ->setViewer($viewer)
->withBlogPHIDs($blog_phids) ->withBlogPHIDs($blog_phids)
->withVisibility(PhameConstants::VISIBILITY_PUBLISHED) ->withVisibility(array(PhameConstants::VISIBILITY_PUBLISHED))
->setOrder('datePublished')
->executeWithCursorPager($pager); ->executeWithCursorPager($pager);
if ($posts) { if ($posts) {
@ -97,7 +98,7 @@ final class PhameHomeController extends PhamePostController {
->setViewer($viewer) ->setViewer($viewer)
->withBloggerPHIDs(array($viewer->getPHID())) ->withBloggerPHIDs(array($viewer->getPHID()))
->withBlogPHIDs(mpull($blogs, 'getPHID')) ->withBlogPHIDs(mpull($blogs, 'getPHID'))
->withVisibility(PhameConstants::VISIBILITY_DRAFT) ->withVisibility(array(PhameConstants::VISIBILITY_DRAFT))
->setLimit(5) ->setLimit(5)
->execute(); ->execute();

View file

@ -58,6 +58,7 @@ abstract class PhameLiveController extends PhameController {
$blog_query = id(new PhameBlogQuery()) $blog_query = id(new PhameBlogQuery())
->setViewer($viewer) ->setViewer($viewer)
->needProfileImage(true) ->needProfileImage(true)
->needHeaderImage(true)
->withIDs(array($blog_id)); ->withIDs(array($blog_id));
// If this is a live view, only show active blogs. // If this is a live view, only show active blogs.
@ -97,7 +98,8 @@ abstract class PhameLiveController extends PhameController {
// Only show published posts on external domains. // Only show published posts on external domains.
if ($is_external) { if ($is_external) {
$post_query->withVisibility(PhameConstants::VISIBILITY_PUBLISHED); $post_query->withVisibility(
array(PhameConstants::VISIBILITY_PUBLISHED));
} }
$post = $post_query->executeOne(); $post = $post_query->executeOne();
@ -210,6 +212,7 @@ abstract class PhameLiveController extends PhameController {
if ($this->getIsLive()) { if ($this->getIsLive()) {
$page $page
->addClass('phame-live-view')
->setShowChrome(false) ->setShowChrome(false)
->setShowFooter(false); ->setShowFooter(false);
} }

View file

@ -21,7 +21,7 @@ final class PhameBlogFeedController extends PhameBlogController {
$posts = id(new PhamePostQuery()) $posts = id(new PhamePostQuery())
->setViewer($viewer) ->setViewer($viewer)
->withBlogPHIDs(array($blog->getPHID())) ->withBlogPHIDs(array($blog->getPHID()))
->withVisibility(PhameConstants::VISIBILITY_PUBLISHED) ->withVisibility(array(PhameConstants::VISIBILITY_PUBLISHED))
->execute(); ->execute();
$blog_uri = PhabricatorEnv::getProductionURI( $blog_uri = PhabricatorEnv::getProductionURI(

View file

@ -0,0 +1,126 @@
<?php
final class PhameBlogHeaderPictureController
extends PhameBlogController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$blog = id(new PhameBlogQuery())
->setViewer($viewer)
->withIDs(array($id))
->needHeaderImage(true)
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$blog) {
return new Aphront404Response();
}
$blog_uri = '/phame/blog/manage/'.$id;
$supported_formats = PhabricatorFile::getTransformableImageFormats();
$e_file = true;
$errors = array();
$delete_header = ($request->getInt('delete') == 1);
if ($request->isFormPost()) {
if ($request->getFileExists('header')) {
$file = PhabricatorFile::newFromPHPUpload(
$_FILES['header'],
array(
'authorPHID' => $viewer->getPHID(),
'canCDN' => true,
));
} else if (!$delete_header) {
$e_file = pht('Required');
$errors[] = pht(
'You must choose a file when uploading a new blog header.');
}
if (!$errors && !$delete_header) {
if (!$file->isTransformableImage()) {
$e_file = pht('Not Supported');
$errors[] = pht(
'This server only supports these image formats: %s.',
implode(', ', $supported_formats));
}
}
if (!$errors) {
if ($delete_header) {
$blog->setHeaderImagePHID(null);
} else {
$blog->setHeaderImagePHID($file->getPHID());
$file->attachToObject($blog->getPHID());
}
$blog->save();
return id(new AphrontRedirectResponse())->setURI($blog_uri);
}
}
$title = pht('Edit Blog Header');
$upload_form = id(new AphrontFormView())
->setUser($viewer)
->setEncType('multipart/form-data')
->appendChild(
id(new AphrontFormFileControl())
->setName('header')
->setLabel(pht('Upload Header'))
->setError($e_file)
->setCaption(
pht('Supported formats: %s', implode(', ', $supported_formats))))
->appendChild(
id(new AphrontFormCheckboxControl())
->setName('delete')
->setLabel(pht('Delete Header'))
->addCheckbox(
'delete',
1,
null,
null))
->appendChild(
id(new AphrontFormSubmitControl())
->addCancelButton($blog_uri)
->setValue(pht('Upload Header')));
$upload_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Upload New Header'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($upload_form);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Blogs'),
$this->getApplicationURI('blog/'));
$crumbs->addTextCrumb(
$blog->getName(),
$this->getApplicationURI('blog/view/'.$id));
$crumbs->addTextCrumb(pht('Blog Header'));
$crumbs->setBorder(true);
$header = id(new PHUIHeaderView())
->setHeader(pht('Edit Blog Header'))
->setHeaderIcon('fa-camera');
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$upload_box,
));
return $this->newPage()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$view,
));
}
}

View file

@ -14,6 +14,7 @@ final class PhameBlogManageController extends PhameBlogController {
->setViewer($viewer) ->setViewer($viewer)
->withIDs(array($id)) ->withIDs(array($id))
->needProfileImage(true) ->needProfileImage(true)
->needHeaderImage(true)
->executeOne(); ->executeOne();
if (!$blog) { if (!$blog) {
return new Aphront404Response(); return new Aphront404Response();
@ -31,22 +32,33 @@ final class PhameBlogManageController extends PhameBlogController {
$picture = $blog->getProfileImageURI(); $picture = $blog->getProfileImageURI();
$view = id(new PHUIButtonView())
->setTag('a')
->setText(pht('View Live'))
->setIcon('fa-external-link')
->setHref($blog->getLiveURI());
$header = id(new PHUIHeaderView()) $header = id(new PHUIHeaderView())
->setHeader($blog->getName()) ->setHeader($blog->getName())
->setUser($viewer) ->setUser($viewer)
->setPolicyObject($blog) ->setPolicyObject($blog)
->setImage($picture) ->setImage($picture)
->setStatus($header_icon, $header_color, $header_name); ->setStatus($header_icon, $header_color, $header_name)
->addActionLink($view);
$actions = $this->renderActions($blog, $viewer); $curtain = $this->buildCurtain($blog);
$properties = $this->renderProperties($blog, $viewer, $actions); $properties = $this->buildPropertyView($blog);
$file = $this->buildFileView($blog);
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb( $crumbs->addTextCrumb(
pht('Blogs'), pht('Blogs'),
$this->getApplicationURI('blog/')); $this->getApplicationURI('blog/'));
$crumbs->addTextCrumb( $crumbs->addTextCrumb(
$blog->getName()); $blog->getName(),
$this->getApplicationURI('blog/view/'.$id));
$crumbs->addTextCrumb(pht('Manage Blog'));
$crumbs->setBorder(true);
$object_box = id(new PHUIObjectBoxView()) $object_box = id(new PHUIObjectBoxView())
->setHeader($header) ->setHeader($header)
@ -57,28 +69,34 @@ final class PhameBlogManageController extends PhameBlogController {
new PhameBlogTransactionQuery()); new PhameBlogTransactionQuery());
$timeline->setShouldTerminate(true); $timeline->setShouldTerminate(true);
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->addPropertySection(pht('Details'), $properties)
->addPropertySection(pht('Header'), $file)
->setMainColumn(
array(
$timeline,
));
return $this->newPage() return $this->newPage()
->setTitle($blog->getName()) ->setTitle($blog->getName())
->setCrumbs($crumbs) ->setCrumbs($crumbs)
->appendChild( ->appendChild(
array( array(
$object_box, $view,
$timeline,
)); ));
} }
private function renderProperties( private function buildPropertyView(PhameBlog $blog) {
PhameBlog $blog, $viewer = $this->getViewer();
PhabricatorUser $viewer,
PhabricatorActionListView $actions) {
require_celerity_resource('aphront-tooltip-css'); require_celerity_resource('aphront-tooltip-css');
Javelin::initBehavior('phabricator-tooltips'); Javelin::initBehavior('phabricator-tooltips');
$properties = id(new PHUIPropertyListView()) $properties = id(new PHUIPropertyListView())
->setUser($viewer) ->setUser($viewer)
->setObject($blog) ->setObject($blog);
->setActionList($actions);
$domain = $blog->getDomain(); $domain = $blog->getDomain();
if (!$domain) { if (!$domain) {
@ -129,7 +147,11 @@ final class PhameBlogManageController extends PhameBlogController {
return $properties; return $properties;
} }
private function renderActions(PhameBlog $blog, PhabricatorUser $viewer) { private function buildCurtain(PhameBlog $blog) {
$viewer = $this->getViewer();
$curtain = $this->newCurtainView($viewer);
$actions = id(new PhabricatorActionListView()) $actions = id(new PhabricatorActionListView())
->setObject($blog) ->setObject($blog)
->setUser($viewer); ->setUser($viewer);
@ -139,7 +161,7 @@ final class PhameBlogManageController extends PhameBlogController {
$blog, $blog,
PhabricatorPolicyCapability::CAN_EDIT); PhabricatorPolicyCapability::CAN_EDIT);
$actions->addAction( $curtain->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setIcon('fa-pencil') ->setIcon('fa-pencil')
->setHref($this->getApplicationURI('blog/edit/'.$blog->getID().'/')) ->setHref($this->getApplicationURI('blog/edit/'.$blog->getID().'/'))
@ -147,7 +169,15 @@ final class PhameBlogManageController extends PhameBlogController {
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
->setWorkflow(!$can_edit)); ->setWorkflow(!$can_edit));
$actions->addAction( $curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-camera')
->setHref($this->getApplicationURI('blog/header/'.$blog->getID().'/'))
->setName(pht('Edit Blog Header'))
->setDisabled(!$can_edit)
->setWorkflow(!$can_edit));
$curtain->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setIcon('fa-picture-o') ->setIcon('fa-picture-o')
->setHref($this->getApplicationURI('blog/picture/'.$blog->getID().'/')) ->setHref($this->getApplicationURI('blog/picture/'.$blog->getID().'/'))
@ -156,7 +186,7 @@ final class PhameBlogManageController extends PhameBlogController {
->setWorkflow(!$can_edit)); ->setWorkflow(!$can_edit));
if ($blog->isArchived()) { if ($blog->isArchived()) {
$actions->addAction( $curtain->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName(pht('Activate Blog')) ->setName(pht('Activate Blog'))
->setIcon('fa-check') ->setIcon('fa-check')
@ -165,7 +195,7 @@ final class PhameBlogManageController extends PhameBlogController {
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
->setWorkflow(true)); ->setWorkflow(true));
} else { } else {
$actions->addAction( $curtain->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
->setName(pht('Archive Blog')) ->setName(pht('Archive Blog'))
->setIcon('fa-ban') ->setIcon('fa-ban')
@ -175,7 +205,27 @@ final class PhameBlogManageController extends PhameBlogController {
->setWorkflow(true)); ->setWorkflow(true));
} }
return $actions; return $curtain;
}
private function buildFileView(
PhameBlog $blog) {
$viewer = $this->getViewer();
$view = id(new PHUIPropertyListView())
->setUser($viewer);
if ($blog->getHeaderImagePHID()) {
$view->addImageContent(
phutil_tag(
'img',
array(
'src' => $blog->getHeaderImageURI(),
'class' => 'phabricator-image-macro-hero',
)));
return $view;
}
return null;
} }
} }

View file

@ -3,10 +3,6 @@
final class PhameBlogProfilePictureController final class PhameBlogProfilePictureController
extends PhameBlogController { extends PhameBlogController {
public function shouldRequireAdmin() {
return false;
}
public function handleRequest(AphrontRequest $request) { public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer(); $viewer = $request->getViewer();
$id = $request->getURIData('id'); $id = $request->getURIData('id');
@ -175,6 +171,7 @@ final class PhameBlogProfilePictureController
$form_box = id(new PHUIObjectBoxView()) $form_box = id(new PHUIObjectBoxView())
->setHeaderText($title) ->setHeaderText($title)
->setFormErrors($errors) ->setFormErrors($errors)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form); ->setForm($form);
$upload_form = id(new AphrontFormView()) $upload_form = id(new AphrontFormView())
@ -194,6 +191,7 @@ final class PhameBlogProfilePictureController
$upload_box = id(new PHUIObjectBoxView()) $upload_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Upload New Picture')) ->setHeaderText(pht('Upload New Picture'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($upload_form); ->setForm($upload_form);
$crumbs = $this->buildApplicationCrumbs(); $crumbs = $this->buildApplicationCrumbs();
@ -204,14 +202,25 @@ final class PhameBlogProfilePictureController
$blog->getName(), $blog->getName(),
$this->getApplicationURI('blog/view/'.$id)); $this->getApplicationURI('blog/view/'.$id));
$crumbs->addTextCrumb(pht('Blog Picture')); $crumbs->addTextCrumb(pht('Blog Picture'));
$crumbs->setBorder(true);
$header = id(new PHUIHeaderView())
->setHeader(pht('Edit Blog Picture'))
->setHeaderIcon('fa-camera');
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter(array(
$form_box,
$upload_box,
));
return $this->newPage() return $this->newPage()
->setTitle($title) ->setTitle($title)
->setCrumbs($crumbs) ->setCrumbs($crumbs)
->appendChild( ->appendChild(
array( array(
$form_box, $view,
$upload_box,
)); ));
} }

View file

@ -19,16 +19,23 @@ final class PhameBlogViewController extends PhameLiveController {
$post_query = id(new PhamePostQuery()) $post_query = id(new PhamePostQuery())
->setViewer($viewer) ->setViewer($viewer)
->withBlogPHIDs(array($blog->getPHID())); ->withBlogPHIDs(array($blog->getPHID()))
->setOrder('datePublished')
->withVisibility(array(
PhameConstants::VISIBILITY_PUBLISHED,
PhameConstants::VISIBILITY_DRAFT,
));
if ($is_live) { if ($is_live) {
$post_query->withVisibility(PhameConstants::VISIBILITY_PUBLISHED); $post_query->withVisibility(array(PhameConstants::VISIBILITY_PUBLISHED));
} }
$posts = $post_query->executeWithCursorPager($pager); $posts = $post_query->executeWithCursorPager($pager);
$hero = $this->buildPhameHeader($blog);
$header = id(new PHUIHeaderView()) $header = id(new PHUIHeaderView())
->setHeader($blog->getName()) ->addClass('phame-header-bar')
->setUser($viewer); ->setUser($viewer);
if (!$is_external) { if (!$is_external) {
@ -104,6 +111,7 @@ final class PhameBlogViewController extends PhameLiveController {
->setCrumbs($crumbs) ->setCrumbs($crumbs)
->appendChild( ->appendChild(
array( array(
$hero,
$page, $page,
$about, $about,
)); ));
@ -147,4 +155,33 @@ final class PhameBlogViewController extends PhameLiveController {
return $actions; return $actions;
} }
private function buildPhameHeader(
PhameBlog $blog) {
$image = null;
if ($blog->getHeaderImagePHID()) {
$image = phutil_tag(
'div',
array(
'class' => 'phame-header-hero',
),
phutil_tag(
'img',
array(
'src' => $blog->getHeaderImageURI(),
'class' => 'phame-header-image',
)));
}
$title = phutil_tag_div('phame-header-title', $blog->getName());
$subtitle = null;
if ($blog->getSubtitle()) {
$subtitle = phutil_tag_div('phame-header-subtitle', $blog->getSubtitle());
}
return phutil_tag_div(
'phame-mega-header', array($image, $title, $subtitle));
}
} }

View file

@ -0,0 +1,56 @@
<?php
final class PhamePostArchiveController extends PhamePostController {
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
$post = id(new PhamePostQuery())
->setViewer($viewer)
->withIDs(array($id))
->requireCapabilities(
array(
PhabricatorPolicyCapability::CAN_VIEW,
PhabricatorPolicyCapability::CAN_EDIT,
))
->executeOne();
if (!$post) {
return new Aphront404Response();
}
$cancel_uri = $post->getViewURI();
if ($request->isFormPost()) {
$xactions = array();
$new_value = PhameConstants::VISIBILITY_ARCHIVED;
$xactions[] = id(new PhamePostTransaction())
->setTransactionType(PhamePostTransaction::TYPE_VISIBILITY)
->setNewValue($new_value);
id(new PhamePostEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnNoEffect(true)
->setContinueOnMissingFields(true)
->applyTransactions($post, $xactions);
return id(new AphrontRedirectResponse())
->setURI($cancel_uri);
}
$title = pht('Archive Post');
$body = pht(
'This post will revert to archived status and no longer be visible '.
'to other users or members of this blog.');
$button = pht('Archive Post');
return $this->newDialog()
->setTitle($title)
->appendParagraph($body)
->addSubmitButton($button)
->addCancelButton($cancel_uri);
}
}

View file

@ -48,6 +48,16 @@ final class PhamePostViewController
'Use "Publish" to publish this post.'))); 'Use "Publish" to publish this post.')));
} }
if ($post->isArchived()) {
$document->appendChild(
id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_ERROR)
->setTitle(pht('Archived Post'))
->appendChild(
pht('Only you can see this archived post until you publish it. '.
'Use "Publish" to publish this post.')));
}
if (!$post->getBlog()) { if (!$post->getBlog()) {
$document->appendChild( $document->appendChild(
id(new PHUIInfoView()) id(new PHUIInfoView())
@ -92,6 +102,8 @@ final class PhamePostViewController
$date = phabricator_datetime($post->getDatePublished(), $viewer); $date = phabricator_datetime($post->getDatePublished(), $viewer);
if ($post->isDraft()) { if ($post->isDraft()) {
$subtitle = pht('Unpublished draft by %s.', $author); $subtitle = pht('Unpublished draft by %s.', $author);
} else if ($post->isArchived()) {
$subtitle = pht('Archived post by %s.', $author);
} else { } else {
$subtitle = pht('Written by %s on %s.', $author, $date); $subtitle = pht('Written by %s on %s.', $author, $date);
} }
@ -207,6 +219,21 @@ final class PhamePostViewController
->setName(pht('Publish')) ->setName(pht('Publish'))
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
->setWorkflow(true)); ->setWorkflow(true));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('fa-ban')
->setHref($this->getApplicationURI('post/archive/'.$id.'/'))
->setName(pht('Archive'))
->setDisabled(!$can_edit)
->setWorkflow(true));
} else if ($post->isArchived()) {
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('fa-eye')
->setHref($this->getApplicationURI('post/publish/'.$id.'/'))
->setName(pht('Publish'))
->setDisabled(!$can_edit)
->setWorkflow(true));
} else { } else {
$actions->addAction( $actions->addAction(
id(new PhabricatorActionView()) id(new PhabricatorActionView())
@ -215,6 +242,13 @@ final class PhamePostViewController
->setName(pht('Unpublish')) ->setName(pht('Unpublish'))
->setDisabled(!$can_edit) ->setDisabled(!$can_edit)
->setWorkflow(true)); ->setWorkflow(true));
$actions->addAction(
id(new PhabricatorActionView())
->setIcon('fa-ban')
->setHref($this->getApplicationURI('post/archive/'.$id.'/'))
->setName(pht('Archive'))
->setDisabled(!$can_edit)
->setWorkflow(true));
} }
if ($post->isDraft()) { if ($post->isDraft()) {
@ -223,12 +257,14 @@ final class PhamePostViewController
$live_name = pht('View Live'); $live_name = pht('View Live');
} }
$actions->addAction( if (!$post->isArchived()) {
id(new PhabricatorActionView()) $actions->addAction(
->setUser($viewer) id(new PhabricatorActionView())
->setIcon('fa-globe') ->setUser($viewer)
->setHref($post->getLiveURI()) ->setIcon('fa-globe')
->setName($live_name)); ->setHref($post->getLiveURI())
->setName($live_name));
}
return $actions; return $actions;
} }
@ -255,7 +291,7 @@ final class PhamePostViewController
$query = id(new PhamePostQuery()) $query = id(new PhamePostQuery())
->setViewer($viewer) ->setViewer($viewer)
->withVisibility(PhameConstants::VISIBILITY_PUBLISHED) ->withVisibility(array(PhameConstants::VISIBILITY_PUBLISHED))
->withBlogPHIDs(array($post->getBlog()->getPHID())) ->withBlogPHIDs(array($post->getBlog()->getPHID()))
->setLimit(1); ->setLimit(1);

View file

@ -77,6 +77,14 @@ final class PhameBlogEditEngine
->setConduitTypeDescription(pht('New blog title.')) ->setConduitTypeDescription(pht('New blog title.'))
->setTransactionType(PhameBlogTransaction::TYPE_NAME) ->setTransactionType(PhameBlogTransaction::TYPE_NAME)
->setValue($object->getName()), ->setValue($object->getName()),
id(new PhabricatorTextEditField())
->setKey('subtitle')
->setLabel(pht('Subtitle'))
->setDescription(pht('Blog subtitle.'))
->setConduitDescription(pht('Change the blog subtitle.'))
->setConduitTypeDescription(pht('New blog subtitle.'))
->setTransactionType(PhameBlogTransaction::TYPE_SUBTITLE)
->setValue($object->getSubtitle()),
id(new PhabricatorRemarkupEditField()) id(new PhabricatorRemarkupEditField())
->setKey('description') ->setKey('description')
->setLabel(pht('Description')) ->setLabel(pht('Description'))

View file

@ -15,6 +15,7 @@ final class PhameBlogEditor
$types = parent::getTransactionTypes(); $types = parent::getTransactionTypes();
$types[] = PhameBlogTransaction::TYPE_NAME; $types[] = PhameBlogTransaction::TYPE_NAME;
$types[] = PhameBlogTransaction::TYPE_SUBTITLE;
$types[] = PhameBlogTransaction::TYPE_DESCRIPTION; $types[] = PhameBlogTransaction::TYPE_DESCRIPTION;
$types[] = PhameBlogTransaction::TYPE_DOMAIN; $types[] = PhameBlogTransaction::TYPE_DOMAIN;
$types[] = PhameBlogTransaction::TYPE_STATUS; $types[] = PhameBlogTransaction::TYPE_STATUS;
@ -31,6 +32,8 @@ final class PhameBlogEditor
switch ($xaction->getTransactionType()) { switch ($xaction->getTransactionType()) {
case PhameBlogTransaction::TYPE_NAME: case PhameBlogTransaction::TYPE_NAME:
return $object->getName(); return $object->getName();
case PhameBlogTransaction::TYPE_SUBTITLE:
return $object->getSubtitle();
case PhameBlogTransaction::TYPE_DESCRIPTION: case PhameBlogTransaction::TYPE_DESCRIPTION:
return $object->getDescription(); return $object->getDescription();
case PhameBlogTransaction::TYPE_DOMAIN: case PhameBlogTransaction::TYPE_DOMAIN:
@ -46,6 +49,7 @@ final class PhameBlogEditor
switch ($xaction->getTransactionType()) { switch ($xaction->getTransactionType()) {
case PhameBlogTransaction::TYPE_NAME: case PhameBlogTransaction::TYPE_NAME:
case PhameBlogTransaction::TYPE_SUBTITLE:
case PhameBlogTransaction::TYPE_DESCRIPTION: case PhameBlogTransaction::TYPE_DESCRIPTION:
case PhameBlogTransaction::TYPE_STATUS: case PhameBlogTransaction::TYPE_STATUS:
return $xaction->getNewValue(); return $xaction->getNewValue();
@ -65,6 +69,8 @@ final class PhameBlogEditor
switch ($xaction->getTransactionType()) { switch ($xaction->getTransactionType()) {
case PhameBlogTransaction::TYPE_NAME: case PhameBlogTransaction::TYPE_NAME:
return $object->setName($xaction->getNewValue()); return $object->setName($xaction->getNewValue());
case PhameBlogTransaction::TYPE_SUBTITLE:
return $object->setSubtitle($xaction->getNewValue());
case PhameBlogTransaction::TYPE_DESCRIPTION: case PhameBlogTransaction::TYPE_DESCRIPTION:
return $object->setDescription($xaction->getNewValue()); return $object->setDescription($xaction->getNewValue());
case PhameBlogTransaction::TYPE_DOMAIN: case PhameBlogTransaction::TYPE_DOMAIN:
@ -82,6 +88,7 @@ final class PhameBlogEditor
switch ($xaction->getTransactionType()) { switch ($xaction->getTransactionType()) {
case PhameBlogTransaction::TYPE_NAME: case PhameBlogTransaction::TYPE_NAME:
case PhameBlogTransaction::TYPE_SUBTITLE:
case PhameBlogTransaction::TYPE_DESCRIPTION: case PhameBlogTransaction::TYPE_DESCRIPTION:
case PhameBlogTransaction::TYPE_DOMAIN: case PhameBlogTransaction::TYPE_DOMAIN:
case PhameBlogTransaction::TYPE_STATUS: case PhameBlogTransaction::TYPE_STATUS:
@ -227,7 +234,7 @@ final class PhameBlogEditor
protected function supportsSearch() { protected function supportsSearch() {
return false; return true;
} }
protected function shouldApplyHeraldRules( protected function shouldApplyHeraldRules(

View file

@ -66,6 +66,9 @@ final class PhamePostEditor
case PhamePostTransaction::TYPE_VISIBILITY: case PhamePostTransaction::TYPE_VISIBILITY:
if ($xaction->getNewValue() == PhameConstants::VISIBILITY_DRAFT) { if ($xaction->getNewValue() == PhameConstants::VISIBILITY_DRAFT) {
$object->setDatePublished(0); $object->setDatePublished(0);
} else if ($xaction->getNewValue() ==
PhameConstants::VISIBILITY_ARCHIVED) {
$object->setDatePublished(0);
} else { } else {
$object->setDatePublished(PhabricatorTime::getNow()); $object->setDatePublished(PhabricatorTime::getNow());
} }
@ -168,7 +171,7 @@ final class PhamePostEditor
protected function shouldSendMail( protected function shouldSendMail(
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
array $xactions) { array $xactions) {
if ($object->isDraft()) { if ($object->isDraft() || ($object->isArchived())) {
return false; return false;
} }
return true; return true;
@ -177,7 +180,7 @@ final class PhamePostEditor
protected function shouldPublishFeedStory( protected function shouldPublishFeedStory(
PhabricatorLiskDAO $object, PhabricatorLiskDAO $object,
array $xactions) { array $xactions) {
if ($object->isDraft()) { if ($object->isDraft() || $object->isArchived()) {
return false; return false;
} }
return true; return true;
@ -228,7 +231,7 @@ final class PhamePostEditor
foreach ($xactions as $xaction) { foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) { switch ($xaction->getTransactionType()) {
case PhamePostTransaction::TYPE_VISIBILITY: case PhamePostTransaction::TYPE_VISIBILITY:
if (!$object->isDraft()) { if (!$object->isDraft() && !$object->isArchived()) {
$body->addRemarkupSection(null, $object->getBody()); $body->addRemarkupSection(null, $object->getBody());
} }
break; break;
@ -261,7 +264,7 @@ final class PhamePostEditor
} }
protected function supportsSearch() { protected function supportsSearch() {
return false; return true;
} }
protected function shouldApplyHeraldRules( protected function shouldApplyHeraldRules(

View file

@ -34,6 +34,11 @@ final class PhabricatorPhameBlogPHIDType extends PhabricatorPHIDType {
$handle->setName($blog->getName()); $handle->setName($blog->getName());
$handle->setFullName($blog->getName()); $handle->setFullName($blog->getName());
$handle->setURI('/phame/blog/view/'.$blog->getID().'/'); $handle->setURI('/phame/blog/view/'.$blog->getID().'/');
if ($blog->isArchived()) {
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
}
} }
} }

View file

@ -34,7 +34,13 @@ final class PhabricatorPhamePostPHIDType extends PhabricatorPHIDType {
$handle->setName($post->getTitle()); $handle->setName($post->getTitle());
$handle->setFullName($post->getTitle()); $handle->setFullName($post->getTitle());
$handle->setURI('/phame/post/view/'.$post->getID().'/'); $handle->setURI('/phame/post/view/'.$post->getID().'/');
if ($post->isArchived()) {
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
}
} }
} }
} }

View file

@ -9,6 +9,7 @@ final class PhameBlogQuery extends PhabricatorCursorPagedPolicyAwareQuery {
private $needBloggers; private $needBloggers;
private $needProfileImage; private $needProfileImage;
private $needHeaderImage;
public function withIDs(array $ids) { public function withIDs(array $ids) {
$this->ids = $ids; $this->ids = $ids;
@ -35,6 +36,11 @@ final class PhameBlogQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $this; return $this;
} }
public function needHeaderImage($need) {
$this->needHeaderImage = $need;
return $this;
}
public function newResultObject() { public function newResultObject() {
return new PhameBlog(); return new PhameBlog();
} }
@ -107,6 +113,28 @@ final class PhameBlogQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$blog->attachProfileImageFile($file); $blog->attachProfileImageFile($file);
} }
} }
if ($this->needHeaderImage) {
$file_phids = mpull($blogs, 'getHeaderImagePHID');
$file_phids = array_filter($file_phids);
if ($file_phids) {
$files = id(new PhabricatorFileQuery())
->setParentQuery($this)
->setViewer($this->getViewer())
->withPHIDs($file_phids)
->execute();
$files = mpull($files, null, 'getPHID');
} else {
$files = array();
}
foreach ($blogs as $blog) {
$file = idx($files, $blog->getHeaderImagePHID());
if ($file) {
$blog->attachHeaderImageFile($file);
}
}
}
return $blogs; return $blogs;
} }

View file

@ -29,7 +29,7 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $this; return $this;
} }
public function withVisibility($visibility) { public function withVisibility(array $visibility) {
$this->visibility = $visibility; $this->visibility = $visibility;
return $this; return $this;
} }
@ -98,10 +98,10 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$this->bloggerPHIDs); $this->bloggerPHIDs);
} }
if ($this->visibility !== null) { if ($this->visibility) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'visibility = %d', 'visibility IN (%Ld)',
$this->visibility); $this->visibility);
} }
@ -122,6 +122,36 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $where; return $where;
} }
public function getBuiltinOrders() {
return array(
'datePublished' => array(
'vector' => array('datePublished', 'id'),
'name' => pht('Publish Date'),
),
) + parent::getBuiltinOrders();
}
public function getOrderableColumns() {
return parent::getOrderableColumns() + array(
'datePublished' => array(
'column' => 'datePublished',
'type' => 'int',
'reverse' => false,
),
);
}
protected function getPagingValueMap($cursor, array $keys) {
$post = $this->loadCursorObject($cursor);
$map = array(
'datePublished' => $post->getDatePublished(),
'id' => $post->getID(),
);
return $map;
}
public function getQueryApplicationClass() { public function getQueryApplicationClass() {
// TODO: Does setting this break public blogs? // TODO: Does setting this break public blogs?
return null; return null;

View file

@ -19,7 +19,7 @@ final class PhamePostSearchEngine
$query = $this->newQuery(); $query = $this->newQuery();
if (strlen($map['visibility'])) { if (strlen($map['visibility'])) {
$query->withVisibility($map['visibility']); $query->withVisibility(array($map['visibility']));
} }
return $query; return $query;
@ -35,6 +35,7 @@ final class PhamePostSearchEngine
'' => pht('All'), '' => pht('All'),
PhameConstants::VISIBILITY_PUBLISHED => pht('Published'), PhameConstants::VISIBILITY_PUBLISHED => pht('Published'),
PhameConstants::VISIBILITY_DRAFT => pht('Draft'), PhameConstants::VISIBILITY_DRAFT => pht('Draft'),
PhameConstants::VISIBILITY_ARCHIVED => pht('Archived'),
)), )),
); );
} }
@ -48,6 +49,7 @@ final class PhamePostSearchEngine
'all' => pht('All Posts'), 'all' => pht('All Posts'),
'live' => pht('Published Posts'), 'live' => pht('Published Posts'),
'draft' => pht('Draft Posts'), 'draft' => pht('Draft Posts'),
'archived' => pht('Archived Posts'),
); );
return $names; return $names;
} }
@ -65,6 +67,9 @@ final class PhamePostSearchEngine
case 'draft': case 'draft':
return $query->setParameter( return $query->setParameter(
'visibility', PhameConstants::VISIBILITY_DRAFT); 'visibility', PhameConstants::VISIBILITY_DRAFT);
case 'archived':
return $query->setParameter(
'visibility', PhameConstants::VISIBILITY_ARCHIVED);
} }
return parent::buildSavedQueryFromBuiltin($query_key); return parent::buildSavedQueryFromBuiltin($query_key);
@ -99,11 +104,19 @@ final class PhamePostSearchEngine
if ($post->isDraft()) { if ($post->isDraft()) {
$item->setStatusIcon('fa-star-o grey'); $item->setStatusIcon('fa-star-o grey');
$item->setDisabled(true); $item->setDisabled(true);
$item->addIcon('none', pht('Draft Post')); $item->addIcon('fa-star-o', pht('Draft Post'));
} else if ($post->isArchived()) {
$item->setStatusIcon('fa-ban grey');
$item->setDisabled(true);
$item->addIcon('fa-ban', pht('Archived Post'));
} else { } else {
$date = $post->getDatePublished(); $date = $post->getDatePublished();
$item->setEpoch($date); $item->setEpoch($date);
} }
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-pencil')
->setHref($post->getEditURI()));
$list->addItem($item); $list->addItem($item);
} }

View file

@ -0,0 +1,28 @@
<?php
final class PhameBlogFulltextEngine
extends PhabricatorFulltextEngine {
protected function buildAbstractDocument(
PhabricatorSearchAbstractDocument $document,
$object) {
$blog = $object;
$document->setDocumentTitle($blog->getName());
$document->addField(
PhabricatorSearchDocumentFieldType::FIELD_BODY,
$blog->getDescription());
$document->addRelationship(
$blog->isArchived()
? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED
: PhabricatorSearchRelationship::RELATIONSHIP_OPEN,
$blog->getPHID(),
PhabricatorPhameBlogPHIDType::TYPECONST,
PhabricatorTime::getNow());
}
}

View file

@ -0,0 +1,34 @@
<?php
final class PhamePostFulltextEngine
extends PhabricatorFulltextEngine {
protected function buildAbstractDocument(
PhabricatorSearchAbstractDocument $document,
$object) {
$post = $object;
$document->setDocumentTitle($post->getTitle());
$document->addField(
PhabricatorSearchDocumentFieldType::FIELD_BODY,
$post->getBody());
$document->addRelationship(
PhabricatorSearchRelationship::RELATIONSHIP_AUTHOR,
$post->getBloggerPHID(),
PhabricatorPeopleUserPHIDType::TYPECONST,
$post->getDateCreated());
$document->addRelationship(
$post->isArchived()
? PhabricatorSearchRelationship::RELATIONSHIP_CLOSED
: PhabricatorSearchRelationship::RELATIONSHIP_OPEN,
$post->getPHID(),
PhabricatorPhamePostPHIDType::TYPECONST,
PhabricatorTime::getNow());
}
}

View file

@ -9,11 +9,13 @@ final class PhameBlog extends PhameDAO
PhabricatorProjectInterface, PhabricatorProjectInterface,
PhabricatorDestructibleInterface, PhabricatorDestructibleInterface,
PhabricatorApplicationTransactionInterface, PhabricatorApplicationTransactionInterface,
PhabricatorConduitResultInterface { PhabricatorConduitResultInterface,
PhabricatorFulltextInterface {
const MARKUP_FIELD_DESCRIPTION = 'markup:description'; const MARKUP_FIELD_DESCRIPTION = 'markup:description';
protected $name; protected $name;
protected $subtitle;
protected $description; protected $description;
protected $domain; protected $domain;
protected $configData; protected $configData;
@ -23,8 +25,10 @@ final class PhameBlog extends PhameDAO
protected $status; protected $status;
protected $mailKey; protected $mailKey;
protected $profileImagePHID; protected $profileImagePHID;
protected $headerImagePHID;
private $profileImageFile = self::ATTACHABLE; private $profileImageFile = self::ATTACHABLE;
private $headerImageFile = self::ATTACHABLE;
const STATUS_ACTIVE = 'active'; const STATUS_ACTIVE = 'active';
const STATUS_ARCHIVED = 'archived'; const STATUS_ARCHIVED = 'archived';
@ -37,11 +41,13 @@ final class PhameBlog extends PhameDAO
), ),
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text64', 'name' => 'text64',
'subtitle' => 'text64',
'description' => 'text', 'description' => 'text',
'domain' => 'text128?', 'domain' => 'text128?',
'status' => 'text32', 'status' => 'text32',
'mailKey' => 'bytes20', 'mailKey' => 'bytes20',
'profileImagePHID' => 'phid?', 'profileImagePHID' => 'phid?',
'headerImagePHID' => 'phid?',
// T6203/NULLABILITY // T6203/NULLABILITY
// These policies should always be non-null. // These policies should always be non-null.
@ -211,6 +217,19 @@ final class PhameBlog extends PhameDAO
return $this->assertAttached($this->profileImageFile); return $this->assertAttached($this->profileImageFile);
} }
public function getHeaderImageURI() {
return $this->getHeaderImageFile()->getBestURI();
}
public function attachHeaderImageFile(PhabricatorFile $file) {
$this->headerImageFile = $file;
return $this;
}
public function getHeaderImageFile() {
return $this->assertAttached($this->headerImageFile);
}
/* -( PhabricatorPolicyInterface Implementation )-------------------------- */ /* -( PhabricatorPolicyInterface Implementation )-------------------------- */
@ -370,4 +389,10 @@ final class PhameBlog extends PhameDAO
} }
/* -( PhabricatorFulltextInterface )--------------------------------------- */
public function newFulltextEngine() {
return new PhameBlogFulltextEngine();
}
} }

View file

@ -4,6 +4,7 @@ final class PhameBlogTransaction
extends PhabricatorApplicationTransaction { extends PhabricatorApplicationTransaction {
const TYPE_NAME = 'phame.blog.name'; const TYPE_NAME = 'phame.blog.name';
const TYPE_SUBTITLE = 'phame.blog.subtitle';
const TYPE_DESCRIPTION = 'phame.blog.description'; const TYPE_DESCRIPTION = 'phame.blog.description';
const TYPE_DOMAIN = 'phame.blog.domain'; const TYPE_DOMAIN = 'phame.blog.domain';
const TYPE_STATUS = 'phame.blog.status'; const TYPE_STATUS = 'phame.blog.status';
@ -80,6 +81,7 @@ final class PhameBlogTransaction
$tags[] = self::MAILTAG_SUBSCRIBERS; $tags[] = self::MAILTAG_SUBSCRIBERS;
break; break;
case self::TYPE_NAME: case self::TYPE_NAME:
case self::TYPE_SUBTITLE:
case self::TYPE_DESCRIPTION: case self::TYPE_DESCRIPTION:
case self::TYPE_DOMAIN: case self::TYPE_DOMAIN:
$tags[] = self::MAILTAG_DETAILS; $tags[] = self::MAILTAG_DETAILS;
@ -116,6 +118,19 @@ final class PhameBlogTransaction
$new); $new);
} }
break; break;
case self::TYPE_SUBTITLE:
if ($old === null) {
return pht(
'%s set this blog\'s subtitle to "%s".',
$this->renderHandleLink($author_phid),
$new);
} else {
return pht(
'%s updated the blog\'s subtitle to "%s".',
$this->renderHandleLink($author_phid),
$new);
}
break;
case self::TYPE_DESCRIPTION: case self::TYPE_DESCRIPTION:
return pht( return pht(
'%s updated the blog\'s description.', '%s updated the blog\'s description.',
@ -166,6 +181,19 @@ final class PhameBlogTransaction
$this->renderHandleLink($object_phid)); $this->renderHandleLink($object_phid));
} }
break; break;
case self::TYPE_SUBTITLE:
if ($old === null) {
return pht(
'%s set the subtitle for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else {
return pht(
'%s updated the subtitle for %s.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
}
break;
case self::TYPE_DESCRIPTION: case self::TYPE_DESCRIPTION:
return pht( return pht(
'%s updated the description for %s.', '%s updated the description for %s.',

View file

@ -10,7 +10,8 @@ final class PhamePost extends PhameDAO
PhabricatorSubscribableInterface, PhabricatorSubscribableInterface,
PhabricatorDestructibleInterface, PhabricatorDestructibleInterface,
PhabricatorTokenReceiverInterface, PhabricatorTokenReceiverInterface,
PhabricatorConduitResultInterface { PhabricatorConduitResultInterface,
PhabricatorFulltextInterface {
const MARKUP_FIELD_BODY = 'markup:body'; const MARKUP_FIELD_BODY = 'markup:body';
const MARKUP_FIELD_SUMMARY = 'markup:summary'; const MARKUP_FIELD_SUMMARY = 'markup:summary';
@ -53,7 +54,8 @@ final class PhamePost extends PhameDAO
public function getLiveURI() { public function getLiveURI() {
$blog = $this->getBlog(); $blog = $this->getBlog();
$is_draft = $this->isDraft(); $is_draft = $this->isDraft();
if (strlen($blog->getDomain()) && !$is_draft) { $is_archived = $this->isArchived();
if (strlen($blog->getDomain()) && !$is_draft && !$is_archived) {
return $this->getExternalLiveURI(); return $this->getExternalLiveURI();
} else { } else {
return $this->getInternalLiveURI(); return $this->getInternalLiveURI();
@ -92,6 +94,10 @@ final class PhamePost extends PhameDAO
return ($this->getVisibility() == PhameConstants::VISIBILITY_DRAFT); return ($this->getVisibility() == PhameConstants::VISIBILITY_DRAFT);
} }
public function isArchived() {
return ($this->getVisibility() == PhameConstants::VISIBILITY_ARCHIVED);
}
protected function getConfiguration() { protected function getConfiguration() {
return array( return array(
self::CONFIG_AUX_PHID => true, self::CONFIG_AUX_PHID => true,
@ -165,7 +171,7 @@ final class PhamePost extends PhameDAO
switch ($capability) { switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW: case PhabricatorPolicyCapability::CAN_VIEW:
if (!$this->isDraft() && $this->getBlog()) { if (!$this->isDraft() && !$this->isArchived() && $this->getBlog()) {
return $this->getBlog()->getViewPolicy(); return $this->getBlog()->getViewPolicy();
} else if ($this->getBlog()) { } else if ($this->getBlog()) {
return $this->getBlog()->getEditPolicy(); return $this->getBlog()->getEditPolicy();
@ -319,6 +325,8 @@ final class PhamePost extends PhameDAO
public function getFieldValuesForConduit() { public function getFieldValuesForConduit() {
if ($this->isDraft()) { if ($this->isDraft()) {
$date_published = null; $date_published = null;
} else if ($this->isArchived()) {
$date_published = null;
} else { } else {
$date_published = (int)$this->getDatePublished(); $date_published = (int)$this->getDatePublished();
} }
@ -337,4 +345,11 @@ final class PhamePost extends PhameDAO
return array(); return array();
} }
/* -( PhabricatorFulltextInterface )--------------------------------------- */
public function newFulltextEngine() {
return new PhamePostFulltextEngine();
}
} }

View file

@ -73,6 +73,8 @@ final class PhamePostTransaction
case self::TYPE_VISIBILITY: case self::TYPE_VISIBILITY:
if ($new == PhameConstants::VISIBILITY_PUBLISHED) { if ($new == PhameConstants::VISIBILITY_PUBLISHED) {
return 'fa-globe'; return 'fa-globe';
} else if ($new == PhameConstants::VISIBILITY_ARCHIVED) {
return 'fa-ban';
} else { } else {
return 'fa-eye-slash'; return 'fa-eye-slash';
} }
@ -144,6 +146,10 @@ final class PhamePostTransaction
return pht( return pht(
'%s marked this post as a draft.', '%s marked this post as a draft.',
$this->renderHandleLink($author_phid)); $this->renderHandleLink($author_phid));
} else if ($new == PhameConstants::VISIBILITY_ARCHIVED) {
return pht(
'%s archived this post.',
$this->renderHandleLink($author_phid));
} else { } else {
return pht( return pht(
'%s published this post.', '%s published this post.',
@ -201,6 +207,11 @@ final class PhamePostTransaction
'%s marked %s as a draft.', '%s marked %s as a draft.',
$this->renderHandleLink($author_phid), $this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid)); $this->renderHandleLink($object_phid));
} else if ($new == PhameConstants::VISIBILITY_ARCHIVED) {
return pht(
'%s marked %s as archived.',
$this->renderHandleLink($author_phid),
$this->renderHandleLink($object_phid));
} else { } else {
return pht( return pht(
'%s published %s.', '%s published %s.',

View file

@ -1099,6 +1099,18 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
$column = $this->refreshColumn($user, $column); $column = $this->refreshColumn($user, $column);
$this->assertTrue((bool)$column); $this->assertTrue((bool)$column);
// This test has been failing randomly in a way that doesn't reproduce
// on any host, so add some extra assertions to try to nail it down.
$board = $this->refreshProject($board, $user, true);
$this->assertTrue((bool)$board);
$this->assertTrue($board->isUserMember($user->getPHID()));
$can_view = PhabricatorPolicyFilter::hasCapability(
$user,
$column,
PhabricatorPolicyCapability::CAN_VIEW);
$this->assertTrue($can_view);
$can_edit = PhabricatorPolicyFilter::hasCapability( $can_edit = PhabricatorPolicyFilter::hasCapability(
$user, $user,
$column, $column,

View file

@ -11,14 +11,20 @@ final class PhabricatorGitGraphStream
public function __construct( public function __construct(
PhabricatorRepository $repository, PhabricatorRepository $repository,
$start_commit) { $start_commit = null) {
$this->repository = $repository; $this->repository = $repository;
$future = $repository->getLocalCommandFuture( if ($start_commit !== null) {
'log --format=%s %s --', $future = $repository->getLocalCommandFuture(
'%H%x01%P%x01%ct', 'log --format=%s %s --',
$start_commit); '%H%x01%P%x01%ct',
$start_commit);
} else {
$future = $repository->getLocalCommandFuture(
'log --format=%s --all --',
'%H%x01%P%x01%ct');
}
$this->iterator = new LinesOfALargeExecFuture($future); $this->iterator = new LinesOfALargeExecFuture($future);
$this->iterator->setDelimiter("\n"); $this->iterator->setDelimiter("\n");

View file

@ -105,6 +105,8 @@ final class PhabricatorRepositoryDiscoveryEngine
$this->commitCache[$ref->getIdentifier()] = true; $this->commitCache[$ref->getIdentifier()] = true;
} }
$this->markUnreachableCommits($repository);
$version = $this->getObservedVersion($repository); $version = $this->getObservedVersion($repository);
if ($version !== null) { if ($version !== null) {
id(new DiffusionRepositoryClusterEngine()) id(new DiffusionRepositoryClusterEngine())
@ -130,39 +132,35 @@ final class PhabricatorRepositoryDiscoveryEngine
$this->verifyGitOrigin($repository); $this->verifyGitOrigin($repository);
} }
// TODO: This should also import tags, but some of the logic is still $heads = id(new DiffusionLowLevelGitRefQuery())
// branch-specific today.
$branches = id(new DiffusionLowLevelGitRefQuery())
->setRepository($repository) ->setRepository($repository)
->withRefTypes(
array(
PhabricatorRepositoryRefCursor::TYPE_BRANCH,
))
->execute(); ->execute();
if (!$branches) { if (!$heads) {
// This repository has no branches at all, so we don't need to do // This repository has no heads at all, so we don't need to do
// anything. Generally, this means the repository is empty. // anything. Generally, this means the repository is empty.
return array(); return array();
} }
$branches = $this->sortBranches($branches); $heads = $this->sortRefs($heads);
$branches = mpull($branches, 'getCommitIdentifier', 'getShortName'); $head_commits = mpull($heads, 'getCommitIdentifier');
$this->log( $this->log(
pht( pht(
'Discovering commits in repository "%s".', 'Discovering commits in repository "%s".',
$repository->getDisplayName())); $repository->getDisplayName()));
$this->fillCommitCache(array_values($branches)); $this->fillCommitCache($head_commits);
$refs = array(); $refs = array();
foreach ($branches as $name => $commit) { foreach ($heads as $ref) {
$this->log(pht('Examining branch "%s", at "%s".', $name, $commit)); $name = $ref->getShortName();
$commit = $ref->getCommitIdentifier();
if (!$repository->shouldTrackBranch($name)) { $this->log(pht('Examining ref "%s", at "%s".', $name, $commit));
$this->log(pht('Skipping, branch is untracked.'));
if (!$repository->shouldTrackRef($ref)) {
$this->log(pht('Skipping, ref is untracked.'));
continue; continue;
} }
@ -173,14 +171,14 @@ final class PhabricatorRepositoryDiscoveryEngine
$this->log(pht('Looking for new commits.')); $this->log(pht('Looking for new commits.'));
$branch_refs = $this->discoverStreamAncestry( $head_refs = $this->discoverStreamAncestry(
new PhabricatorGitGraphStream($repository, $commit), new PhabricatorGitGraphStream($repository, $commit),
$commit, $commit,
$repository->shouldAutocloseBranch($name)); $repository->shouldAutocloseRef($ref));
$this->didDiscoverRefs($branch_refs); $this->didDiscoverRefs($head_refs);
$refs[] = $branch_refs; $refs[] = $head_refs;
} }
return array_mergev($refs); return array_mergev($refs);
@ -448,10 +446,17 @@ final class PhabricatorRepositoryDiscoveryEngine
return; return;
} }
// When filling the cache we ignore commits which have been marked as
// unreachable, treating them as though they do not exist. When recording
// commits later we'll revive commits that exist but are unreachable.
$commits = id(new PhabricatorRepositoryCommit())->loadAllWhere( $commits = id(new PhabricatorRepositoryCommit())->loadAllWhere(
'repositoryID = %d AND commitIdentifier IN (%Ls)', 'repositoryID = %d AND commitIdentifier IN (%Ls)
AND (importStatus & %d) != %d',
$this->getRepository()->getID(), $this->getRepository()->getID(),
$identifiers); $identifiers,
PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE,
PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE);
foreach ($commits as $commit) { foreach ($commits as $commit) {
$this->commitCache[$commit->getCommitIdentifier()] = true; $this->commitCache[$commit->getCommitIdentifier()] = true;
@ -469,25 +474,23 @@ final class PhabricatorRepositoryDiscoveryEngine
* *
* @task internal * @task internal
* *
* @param list<DiffusionRepositoryRef> List of branch heads. * @param list<DiffusionRepositoryRef> List of refs.
* @return list<DiffusionRepositoryRef> Sorted list of branch heads. * @return list<DiffusionRepositoryRef> Sorted list of refs.
*/ */
private function sortBranches(array $branches) { private function sortRefs(array $refs) {
$repository = $this->getRepository(); $repository = $this->getRepository();
$head_branches = array(); $head_refs = array();
$tail_branches = array(); $tail_refs = array();
foreach ($branches as $branch) { foreach ($refs as $ref) {
$name = $branch->getShortName(); if ($repository->shouldAutocloseRef($ref)) {
$head_refs[] = $ref;
if ($repository->shouldAutocloseBranch($name)) {
$head_branches[] = $branch;
} else { } else {
$tail_branches[] = $branch; $tail_refs[] = $ref;
} }
} }
return array_merge($head_branches, $tail_branches); return array_merge($head_refs, $tail_refs);
} }
@ -499,6 +502,30 @@ final class PhabricatorRepositoryDiscoveryEngine
array $parents) { array $parents) {
$commit = new PhabricatorRepositoryCommit(); $commit = new PhabricatorRepositoryCommit();
$conn_w = $repository->establishConnection('w');
// First, try to revive an existing unreachable commit (if one exists) by
// removing the "unreachable" flag. If we succeed, we don't need to do
// anything else: we already discovered this commit some time ago.
queryfx(
$conn_w,
'UPDATE %T SET importStatus = (importStatus & ~%d)
WHERE repositoryID = %d AND commitIdentifier = %s',
$commit->getTableName(),
PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE,
$repository->getID(),
$commit_identifier);
if ($conn_w->getAffectedRows()) {
$commit = $commit->loadOneWhere(
'repositoryID = %d AND commitIdentifier = %s',
$repository->getID(),
$commit_identifier);
// After reviving a commit, schedule new daemons for it.
$this->didDiscoverCommit($repository, $commit, $epoch);
return;
}
$commit->setRepositoryID($repository->getID()); $commit->setRepositoryID($repository->getID());
$commit->setCommitIdentifier($commit_identifier); $commit->setCommitIdentifier($commit_identifier);
$commit->setEpoch($epoch); $commit->setEpoch($epoch);
@ -508,10 +535,7 @@ final class PhabricatorRepositoryDiscoveryEngine
$data = new PhabricatorRepositoryCommitData(); $data = new PhabricatorRepositoryCommitData();
$conn_w = $repository->establishConnection('w');
try { try {
// If this commit has parents, look up their IDs. The parent commits // If this commit has parents, look up their IDs. The parent commits
// should always exist already. // should always exist already.
@ -559,21 +583,7 @@ final class PhabricatorRepositoryDiscoveryEngine
} }
$commit->saveTransaction(); $commit->saveTransaction();
$this->insertTask($repository, $commit); $this->didDiscoverCommit($repository, $commit, $epoch);
queryfx(
$conn_w,
'INSERT INTO %T (repositoryID, size, lastCommitID, epoch)
VALUES (%d, 1, %d, %d)
ON DUPLICATE KEY UPDATE
size = size + 1,
lastCommitID =
IF(VALUES(epoch) > epoch, VALUES(lastCommitID), lastCommitID),
epoch = IF(VALUES(epoch) > epoch, VALUES(epoch), epoch)',
PhabricatorRepository::TABLE_SUMMARY,
$repository->getID(),
$commit->getID(),
$epoch);
if ($this->repairMode) { if ($this->repairMode) {
// Normally, the query should throw a duplicate key exception. If we // Normally, the query should throw a duplicate key exception. If we
@ -589,8 +599,6 @@ final class PhabricatorRepositoryDiscoveryEngine
'commit' => $commit, 'commit' => $commit,
))); )));
} catch (AphrontDuplicateKeyQueryException $ex) { } catch (AphrontDuplicateKeyQueryException $ex) {
$commit->killTransaction(); $commit->killTransaction();
// Ignore. This can happen because we discover the same new commit // Ignore. This can happen because we discover the same new commit
@ -600,6 +608,29 @@ final class PhabricatorRepositoryDiscoveryEngine
} }
} }
private function didDiscoverCommit(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit,
$epoch) {
$this->insertTask($repository, $commit);
// Update the repository summary table.
queryfx(
$commit->establishConnection('w'),
'INSERT INTO %T (repositoryID, size, lastCommitID, epoch)
VALUES (%d, 1, %d, %d)
ON DUPLICATE KEY UPDATE
size = size + 1,
lastCommitID =
IF(VALUES(epoch) > epoch, VALUES(lastCommitID), lastCommitID),
epoch = IF(VALUES(epoch) > epoch, VALUES(epoch), epoch)',
PhabricatorRepository::TABLE_SUMMARY,
$repository->getID(),
$commit->getID(),
$epoch);
}
private function didDiscoverRefs(array $refs) { private function didDiscoverRefs(array $refs) {
foreach ($refs as $ref) { foreach ($refs as $ref) {
$this->workingSet[$ref->getIdentifier()] = true; $this->workingSet[$ref->getIdentifier()] = true;
@ -702,4 +733,136 @@ final class PhabricatorRepositoryDiscoveryEngine
return (int)$version['version']; return (int)$version['version'];
} }
private function markUnreachableCommits(PhabricatorRepository $repository) {
// For now, this is only supported for Git.
if (!$repository->isGit()) {
return;
}
// Find older versions of refs which we haven't processed yet. We're going
// to make sure their commits are still reachable.
$old_refs = id(new PhabricatorRepositoryOldRef())->loadAllWhere(
'repositoryPHID = %s',
$repository->getPHID());
// We can share a single graph stream across all the checks we need to do.
$stream = new PhabricatorGitGraphStream($repository);
foreach ($old_refs as $old_ref) {
$identifier = $old_ref->getCommitIdentifier();
$this->markUnreachableFrom($repository, $stream, $identifier);
// If nothing threw an exception, we're all done with this ref.
$old_ref->delete();
}
}
private function markUnreachableFrom(
PhabricatorRepository $repository,
PhabricatorGitGraphStream $stream,
$identifier) {
$unreachable = array();
$commit = id(new PhabricatorRepositoryCommit())->loadOneWhere(
'repositoryID = %s AND commitIdentifier = %s',
$repository->getID(),
$identifier);
if (!$commit) {
return;
}
$look = array($commit);
$seen = array();
while ($look) {
$target = array_pop($look);
// If we've already checked this commit (for example, because history
// branches and then merges) we don't need to check it again.
$target_identifier = $target->getCommitIdentifier();
if (isset($seen[$target_identifier])) {
continue;
}
$seen[$target_identifier] = true;
try {
$stream->getCommitDate($target_identifier);
$reachable = true;
} catch (Exception $ex) {
$reachable = false;
}
if ($reachable) {
// This commit is reachable, so we don't need to go any further
// down this road.
continue;
}
$unreachable[] = $target;
// Find the commit's parents and check them for reachability, too. We
// have to look in the database since we no may longer have the commit
// in the repository.
$rows = queryfx_all(
$commit->establishConnection('w'),
'SELECT commit.* FROM %T commit
JOIN %T parents ON commit.id = parents.parentCommitID
WHERE parents.childCommitID = %d',
$commit->getTableName(),
PhabricatorRepository::TABLE_PARENTS,
$target->getID());
if (!$rows) {
continue;
}
$parents = id(new PhabricatorRepositoryCommit())
->loadAllFromArray($rows);
foreach ($parents as $parent) {
$look[] = $parent;
}
}
$unreachable = array_reverse($unreachable);
$flag = PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE;
foreach ($unreachable as $unreachable_commit) {
$unreachable_commit->writeImportStatusFlag($flag);
}
// If anything was unreachable, just rebuild the whole summary table.
// We can't really update it incrementally when a commit becomes
// unreachable.
if ($unreachable) {
$this->rebuildSummaryTable($repository);
}
}
private function rebuildSummaryTable(PhabricatorRepository $repository) {
$conn_w = $repository->establishConnection('w');
$data = queryfx_one(
$conn_w,
'SELECT COUNT(*) N, MAX(id) id, MAX(epoch) epoch
FROM %T WHERE repositoryID = %d AND (importStatus & %d) != %d',
id(new PhabricatorRepositoryCommit())->getTableName(),
$repository->getID(),
PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE,
PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE);
queryfx(
$conn_w,
'INSERT INTO %T (repositoryID, size, lastCommitID, epoch)
VALUES (%d, %d, %d, %d)
ON DUPLICATE KEY UPDATE
size = VALUES(size),
lastCommitID = VALUES(lastCommitID),
epoch = VALUES(epoch)',
PhabricatorRepository::TABLE_SUMMARY,
$repository->getID(),
$data['N'],
$data['id'],
$data['epoch']);
}
} }

View file

@ -347,7 +347,7 @@ final class PhabricatorRepositoryPullEngine
// For bare working copies, we need this magic incantation. // For bare working copies, we need this magic incantation.
$future = $repository->getRemoteCommandFuture( $future = $repository->getRemoteCommandFuture(
'fetch origin %s --prune', 'fetch origin %s --prune',
'+refs/heads/*:refs/heads/*'); '+refs/*:refs/*');
} else { } else {
$future = $repository->getRemoteCommandFuture( $future = $repository->getRemoteCommandFuture(
'fetch --all --prune'); 'fetch --all --prune');

View file

@ -25,29 +25,31 @@ final class PhabricatorRepositoryRefEngine
switch ($vcs) { switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN: case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
// No meaningful refs of any type in Subversion. // No meaningful refs of any type in Subversion.
$branches = array(); $maps = array();
$bookmarks = array();
$tags = array();
break; break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL: case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$branches = $this->loadMercurialBranchPositions($repository); $branches = $this->loadMercurialBranchPositions($repository);
$bookmarks = $this->loadMercurialBookmarkPositions($repository); $bookmarks = $this->loadMercurialBookmarkPositions($repository);
$tags = array(); $maps = array(
PhabricatorRepositoryRefCursor::TYPE_BRANCH => $branches,
PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => $bookmarks,
);
$branches_may_close = true; $branches_may_close = true;
break; break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT: case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$branches = $this->loadGitBranchPositions($repository); $maps = $this->loadGitRefPositions($repository);
$bookmarks = array();
$tags = $this->loadGitTagPositions($repository);
break; break;
default: default:
throw new Exception(pht('Unknown VCS "%s"!', $vcs)); throw new Exception(pht('Unknown VCS "%s"!', $vcs));
} }
$maps = array( // Fill in any missing types with empty lists.
PhabricatorRepositoryRefCursor::TYPE_BRANCH => $branches, $maps = $maps + array(
PhabricatorRepositoryRefCursor::TYPE_TAG => $tags, PhabricatorRepositoryRefCursor::TYPE_BRANCH => array(),
PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => $bookmarks, PhabricatorRepositoryRefCursor::TYPE_TAG => array(),
PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => array(),
PhabricatorRepositoryRefCursor::TYPE_REF => array(),
); );
$all_cursors = id(new PhabricatorRepositoryRefCursorQuery()) $all_cursors = id(new PhabricatorRepositoryRefCursorQuery())
@ -83,6 +85,13 @@ final class PhabricatorRepositoryRefEngine
$ref->save(); $ref->save();
} }
foreach ($this->deadRefs as $ref) { foreach ($this->deadRefs as $ref) {
// Shove this ref into the old refs table so the discovery engine
// can check if any commits have been rendered unreachable.
id(new PhabricatorRepositoryOldRef())
->setRepositoryPHID($repository->getPHID())
->setCommitIdentifier($ref->getCommitIdentifier())
->save();
$ref->delete(); $ref->delete();
} }
$repository->saveTransaction(); $repository->saveTransaction();
@ -91,6 +100,7 @@ final class PhabricatorRepositoryRefEngine
$this->deadRefs = array(); $this->deadRefs = array();
} }
$branches = $maps[PhabricatorRepositoryRefCursor::TYPE_BRANCH];
if ($branches && $branches_may_close) { if ($branches && $branches_may_close) {
$this->updateBranchStates($repository, $branches); $this->updateBranchStates($repository, $branches);
} }
@ -449,28 +459,12 @@ final class PhabricatorRepositoryRefEngine
/** /**
* @task git * @task git
*/ */
private function loadGitBranchPositions(PhabricatorRepository $repository) { private function loadGitRefPositions(PhabricatorRepository $repository) {
return id(new DiffusionLowLevelGitRefQuery()) $refs = id(new DiffusionLowLevelGitRefQuery())
->setRepository($repository) ->setRepository($repository)
->withRefTypes(
array(
PhabricatorRepositoryRefCursor::TYPE_BRANCH,
))
->execute(); ->execute();
}
return mgroup($refs, 'getRefType');
/**
* @task git
*/
private function loadGitTagPositions(PhabricatorRepository $repository) {
return id(new DiffusionLowLevelGitRefQuery())
->setRepository($repository)
->withRefTypes(
array(
PhabricatorRepositoryRefCursor::TYPE_TAG,
))
->execute();
} }

View file

@ -0,0 +1,103 @@
<?php
final class PhabricatorRepositoryManagementMarkReachableWorkflow
extends PhabricatorRepositoryManagementWorkflow {
private $untouchedCount = 0;
protected function didConstruct() {
$this
->setName('mark-reachable')
->setExamples('**mark-reachable** [__options__] __repository__ ...')
->setSynopsis(
pht(
'Rebuild "unreachable" flags for commits in __repository__.'))
->setArguments(
array(
array(
'name' => 'repos',
'wildcard' => true,
),
));
}
public function execute(PhutilArgumentParser $args) {
$repos = $this->loadRepositories($args, 'repos');
if (!$repos) {
throw new PhutilArgumentUsageException(
pht(
'Specify one or more repositories to correct reachability status '.
'for.'));
}
foreach ($repos as $repo) {
$this->markReachable($repo);
}
echo tsprintf(
"%s\n",
pht(
'Examined %s commits already in the correct state.',
new PhutilNumber($this->untouchedCount)));
echo tsprintf(
"%s\n",
pht('Done.'));
return 0;
}
private function markReachable(PhabricatorRepository $repository) {
if (!$repository->isGit()) {
throw new PhutilArgumentUsageException(
pht(
'Only Git repositories are supported, this repository ("%s") is '.
'not a Git repository.',
$repository->getDisplayName()));
}
$viewer = $this->getViewer();
$commits = id(new DiffusionCommitQuery())
->setViewer($viewer)
->withRepository($repository)
->execute();
$flag = PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE;
$graph = new PhabricatorGitGraphStream($repository);
foreach ($commits as $commit) {
$identifier = $commit->getCommitIdentifier();
try {
$graph->getCommitDate($identifier);
$unreachable = false;
} catch (Exception $ex) {
$unreachable = true;
}
// The commit has proper reachability, so do nothing.
if ($commit->isUnreachable() === $unreachable) {
$this->untouchedCount++;
continue;
}
if ($unreachable) {
echo tsprintf(
"%s: %s\n",
$commit->getMonogram(),
pht('Marking commit unreachable.'));
$commit->writeImportStatusFlag($flag);
} else {
echo tsprintf(
"%s: %s\n",
$commit->getMonogram(),
pht('Marking commit reachable.'));
$commit->clearImportStatusFlag($flag);
}
}
}
}

View file

@ -46,6 +46,10 @@ final class PhabricatorRepositoryCommitPHIDType extends PhabricatorPHIDType {
$handle->setFullName($full_name); $handle->setFullName($full_name);
$handle->setURI($commit->getURI()); $handle->setURI($commit->getURI());
$handle->setTimestamp($commit->getEpoch()); $handle->setTimestamp($commit->getEpoch());
if ($commit->isUnreachable()) {
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
}
} }
} }

View file

@ -910,6 +910,21 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
return null; return null;
} }
public function shouldTrackRef(DiffusionRepositoryRef $ref) {
// At least for now, don't track the staging area tags.
if ($ref->isTag()) {
if (preg_match('(^phabricator/)', $ref->getShortName())) {
return false;
}
}
if (!$ref->isBranch()) {
return true;
}
return $this->shouldTrackBranch($ref->getShortName());
}
public function shouldTrackBranch($branch) { public function shouldTrackBranch($branch) {
return $this->isBranchInFilter($branch, 'branch-filter'); return $this->isBranchInFilter($branch, 'branch-filter');
} }
@ -1020,6 +1035,14 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
/* -( Autoclose )---------------------------------------------------------- */ /* -( Autoclose )---------------------------------------------------------- */
public function shouldAutocloseRef(DiffusionRepositoryRef $ref) {
if (!$ref->isBranch()) {
return false;
}
return $this->shouldAutocloseBranch($ref->getShortName());
}
/** /**
* Determine if autoclose is active for a branch. * Determine if autoclose is active for a branch.
* *

View file

@ -32,6 +32,7 @@ final class PhabricatorRepositoryCommit
const IMPORTED_ALL = 15; const IMPORTED_ALL = 15;
const IMPORTED_CLOSEABLE = 1024; const IMPORTED_CLOSEABLE = 1024;
const IMPORTED_UNREACHABLE = 2048;
private $commitData = self::ATTACHABLE; private $commitData = self::ATTACHABLE;
private $audits = self::ATTACHABLE; private $audits = self::ATTACHABLE;
@ -58,14 +59,43 @@ final class PhabricatorRepositoryCommit
return $this->isPartiallyImported(self::IMPORTED_ALL); return $this->isPartiallyImported(self::IMPORTED_ALL);
} }
public function isUnreachable() {
return $this->isPartiallyImported(self::IMPORTED_UNREACHABLE);
}
public function writeImportStatusFlag($flag) { public function writeImportStatusFlag($flag) {
queryfx( return $this->adjustImportStatusFlag($flag, true);
$this->establishConnection('w'), }
'UPDATE %T SET importStatus = (importStatus | %d) WHERE id = %d',
$this->getTableName(), public function clearImportStatusFlag($flag) {
$flag, return $this->adjustImportStatusFlag($flag, false);
$this->getID()); }
$this->setImportStatus($this->getImportStatus() | $flag);
private function adjustImportStatusFlag($flag, $set) {
$conn_w = $this->establishConnection('w');
$table_name = $this->getTableName();
$id = $this->getID();
if ($set) {
queryfx(
$conn_w,
'UPDATE %T SET importStatus = (importStatus | %d) WHERE id = %d',
$table_name,
$flag,
$id);
$this->setImportStatus($this->getImportStatus() | $flag);
} else {
queryfx(
$conn_w,
'UPDATE %T SET importStatus = (importStatus & ~%d) WHERE id = %d',
$table_name,
$flag,
$id);
$this->setImportStatus($this->getImportStatus() & ~$flag);
}
return $this; return $this;
} }

View file

@ -0,0 +1,52 @@
<?php
/**
* Stores outdated refs which need to be checked for reachability.
*
* When a branch is deleted, the old HEAD ends up here and the discovery
* engine marks all the commits that previously appeared on it as unreachable.
*/
final class PhabricatorRepositoryOldRef
extends PhabricatorRepositoryDAO
implements PhabricatorPolicyInterface {
protected $repositoryPHID;
protected $commitIdentifier;
protected function getConfiguration() {
return array(
self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array(
'commitIdentifier' => 'text40',
),
self::CONFIG_KEY_SCHEMA => array(
'key_repository' => array(
'columns' => array('repositoryPHID'),
),
),
) + parent::getConfiguration();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::getMostOpenPolicy();
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return false;
}
public function describeAutomaticCapability($capability) {
return null;
}
}

View file

@ -12,6 +12,7 @@ final class PhabricatorRepositoryRefCursor
const TYPE_BRANCH = 'branch'; const TYPE_BRANCH = 'branch';
const TYPE_TAG = 'tag'; const TYPE_TAG = 'tag';
const TYPE_BOOKMARK = 'bookmark'; const TYPE_BOOKMARK = 'bookmark';
const TYPE_REF = 'ref';
protected $repositoryPHID; protected $repositoryPHID;
protected $refType; protected $refType;

View file

@ -3,6 +3,10 @@
final class PhabricatorRepositoryCommitHeraldWorker final class PhabricatorRepositoryCommitHeraldWorker
extends PhabricatorRepositoryCommitParserWorker { extends PhabricatorRepositoryCommitParserWorker {
protected function getImportStepFlag() {
return PhabricatorRepositoryCommit::IMPORTED_HERALD;
}
public function getRequiredLeaseTime() { public function getRequiredLeaseTime() {
// Herald rules may take a long time to process. // Herald rules may take a long time to process.
return phutil_units('4 hours in seconds'); return phutil_units('4 hours in seconds');
@ -12,6 +16,12 @@ final class PhabricatorRepositoryCommitHeraldWorker
PhabricatorRepository $repository, PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) { PhabricatorRepositoryCommit $commit) {
if ($this->shouldSkipImportStep()) {
// This worker has no followup tasks, so we can just bail out
// right away without queueing anything.
return;
}
// Reload the commit to pull commit data and audit requests. // Reload the commit to pull commit data and audit requests.
$commit = id(new DiffusionCommitQuery()) $commit = id(new DiffusionCommitQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer(PhabricatorUser::getOmnipotentUser())

View file

@ -3,14 +3,18 @@
final class PhabricatorRepositoryCommitOwnersWorker final class PhabricatorRepositoryCommitOwnersWorker
extends PhabricatorRepositoryCommitParserWorker { extends PhabricatorRepositoryCommitParserWorker {
protected function getImportStepFlag() {
return PhabricatorRepositoryCommit::IMPORTED_OWNERS;
}
protected function parseCommit( protected function parseCommit(
PhabricatorRepository $repository, PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) { PhabricatorRepositoryCommit $commit) {
$this->triggerOwnerAudits($repository, $commit); if (!$this->shouldSkipImportStep()) {
$this->triggerOwnerAudits($repository, $commit);
$commit->writeImportStatusFlag( $commit->writeImportStatusFlag($this->getImportStepFlag());
PhabricatorRepositoryCommit::IMPORTED_OWNERS); }
if ($this->shouldQueueFollowupTasks()) { if ($this->shouldQueueFollowupTasks()) {
$this->queueTask( $this->queueTask(

View file

@ -26,6 +26,14 @@ abstract class PhabricatorRepositoryCommitParserWorker
pht('Commit "%s" does not exist.', $commit_id)); pht('Commit "%s" does not exist.', $commit_id));
} }
if ($commit->isUnreachable()) {
throw new PhabricatorWorkerPermanentFailureException(
pht(
'Commit "%s" has been deleted: it is no longer reachable from '.
'any ref.',
$commit_id));
}
$this->commit = $commit; $this->commit = $commit;
return $commit; return $commit;
@ -44,6 +52,42 @@ abstract class PhabricatorRepositoryCommitParserWorker
return !idx($this->getTaskData(), 'only'); return !idx($this->getTaskData(), 'only');
} }
protected function getImportStepFlag() {
return null;
}
final protected function shouldSkipImportStep() {
// If this step has already been performed and this is a "natural" task
// which was queued by the normal daemons, decline to do the work again.
// This mitigates races if commits are rapidly deleted and revived.
$flag = $this->getImportStepFlag();
if (!$flag) {
// This step doesn't have an associated flag.
return false;
}
$commit = $this->commit;
if (!$commit->isPartiallyImported($flag)) {
// This commit doesn't have the flag set yet.
return false;
}
if (!$this->shouldQueueFollowupTasks()) {
// This task was queued by administrative tools, so do the work even
// if it duplicates existing work.
return false;
}
$this->log(
"%s\n",
pht(
'Skipping import step; this step was previously completed for '.
'this commit.'));
return true;
}
abstract protected function parseCommit( abstract protected function parseCommit(
PhabricatorRepository $repository, PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit); PhabricatorRepositoryCommit $commit);

View file

@ -3,6 +3,10 @@
abstract class PhabricatorRepositoryCommitChangeParserWorker abstract class PhabricatorRepositoryCommitChangeParserWorker
extends PhabricatorRepositoryCommitParserWorker { extends PhabricatorRepositoryCommitParserWorker {
protected function getImportStepFlag() {
return PhabricatorRepositoryCommit::IMPORTED_CHANGE;
}
public function getRequiredLeaseTime() { public function getRequiredLeaseTime() {
// It can take a very long time to parse commits; some commits in the // It can take a very long time to parse commits; some commits in the
// Facebook repository affect many millions of paths. Acquire 24h leases. // Facebook repository affect many millions of paths. Acquire 24h leases.
@ -23,9 +27,15 @@ abstract class PhabricatorRepositoryCommitChangeParserWorker
return; return;
} }
$results = $this->parseCommitChanges($repository, $commit); if (!$this->shouldSkipImportStep()) {
if ($results) { $results = $this->parseCommitChanges($repository, $commit);
$this->writeCommitChanges($repository, $commit, $results); if ($results) {
$this->writeCommitChanges($repository, $commit, $results);
}
$commit->writeImportStatusFlag($this->getImportStepFlag());
PhabricatorSearchWorker::queueDocumentForIndexing($commit->getPHID());
} }
$this->finishParse(); $this->finishParse();
@ -85,12 +95,6 @@ abstract class PhabricatorRepositoryCommitChangeParserWorker
protected function finishParse() { protected function finishParse() {
$commit = $this->commit; $commit = $this->commit;
$commit->writeImportStatusFlag(
PhabricatorRepositoryCommit::IMPORTED_CHANGE);
PhabricatorSearchWorker::queueDocumentForIndexing($commit->getPHID());
if ($this->shouldQueueFollowupTasks()) { if ($this->shouldQueueFollowupTasks()) {
$this->queueTask( $this->queueTask(
'PhabricatorRepositoryCommitOwnersWorker', 'PhabricatorRepositoryCommitOwnersWorker',

View file

@ -3,42 +3,52 @@
abstract class PhabricatorRepositoryCommitMessageParserWorker abstract class PhabricatorRepositoryCommitMessageParserWorker
extends PhabricatorRepositoryCommitParserWorker { extends PhabricatorRepositoryCommitParserWorker {
abstract protected function parseCommitWithRef( protected function getImportStepFlag() {
PhabricatorRepository $repository, return PhabricatorRepositoryCommit::IMPORTED_MESSAGE;
PhabricatorRepositoryCommit $commit, }
DiffusionCommitRef $ref);
abstract protected function getFollowupTaskClass();
final protected function parseCommit( final protected function parseCommit(
PhabricatorRepository $repository, PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit) { PhabricatorRepositoryCommit $commit) {
$viewer = PhabricatorUser::getOmnipotentUser(); if (!$this->shouldSkipImportStep()) {
$viewer = PhabricatorUser::getOmnipotentUser();
$refs_raw = DiffusionQuery::callConduitWithDiffusionRequest( $refs_raw = DiffusionQuery::callConduitWithDiffusionRequest(
$viewer, $viewer,
DiffusionRequest::newFromDictionary( DiffusionRequest::newFromDictionary(
array(
'repository' => $repository,
'user' => $viewer,
)),
'diffusion.querycommits',
array( array(
'repository' => $repository, 'repositoryPHID' => $repository->getPHID(),
'user' => $viewer, 'phids' => array($commit->getPHID()),
)), 'bypassCache' => true,
'diffusion.querycommits', 'needMessages' => true,
array( ));
'repositoryPHID' => $repository->getPHID(),
'phids' => array($commit->getPHID()),
'bypassCache' => true,
'needMessages' => true,
));
if (empty($refs_raw['data'])) { if (empty($refs_raw['data'])) {
throw new Exception( throw new Exception(
pht( pht(
'Unable to retrieve details for commit "%s"!', 'Unable to retrieve details for commit "%s"!',
$commit->getPHID())); $commit->getPHID()));
}
$ref = DiffusionCommitRef::newFromConduitResult(head($refs_raw['data']));
$this->updateCommitData($ref);
} }
$ref = DiffusionCommitRef::newFromConduitResult(head($refs_raw['data'])); if ($this->shouldQueueFollowupTasks()) {
$this->queueTask(
$this->parseCommitWithRef($repository, $commit, $ref); $this->getFollowupTaskClass(),
array(
'commitID' => $commit->getID(),
));
}
} }
final protected function updateCommitData(DiffusionCommitRef $ref) { final protected function updateCommitData(DiffusionCommitRef $ref) {

View file

@ -3,20 +3,8 @@
final class PhabricatorRepositoryGitCommitMessageParserWorker final class PhabricatorRepositoryGitCommitMessageParserWorker
extends PhabricatorRepositoryCommitMessageParserWorker { extends PhabricatorRepositoryCommitMessageParserWorker {
protected function parseCommitWithRef( protected function getFollowupTaskClass() {
PhabricatorRepository $repository, return 'PhabricatorRepositoryGitCommitChangeParserWorker';
PhabricatorRepositoryCommit $commit,
DiffusionCommitRef $ref) {
$this->updateCommitData($ref);
if ($this->shouldQueueFollowupTasks()) {
$this->queueTask(
'PhabricatorRepositoryGitCommitChangeParserWorker',
array(
'commitID' => $commit->getID(),
));
}
} }
} }

View file

@ -3,20 +3,8 @@
final class PhabricatorRepositoryMercurialCommitMessageParserWorker final class PhabricatorRepositoryMercurialCommitMessageParserWorker
extends PhabricatorRepositoryCommitMessageParserWorker { extends PhabricatorRepositoryCommitMessageParserWorker {
protected function parseCommitWithRef( protected function getFollowupTaskClass() {
PhabricatorRepository $repository, return 'PhabricatorRepositoryMercurialCommitChangeParserWorker';
PhabricatorRepositoryCommit $commit,
DiffusionCommitRef $ref) {
$this->updateCommitData($ref);
if ($this->shouldQueueFollowupTasks()) {
$this->queueTask(
'PhabricatorRepositoryMercurialCommitChangeParserWorker',
array(
'commitID' => $commit->getID(),
));
}
} }
} }

View file

@ -3,20 +3,8 @@
final class PhabricatorRepositorySvnCommitMessageParserWorker final class PhabricatorRepositorySvnCommitMessageParserWorker
extends PhabricatorRepositoryCommitMessageParserWorker { extends PhabricatorRepositoryCommitMessageParserWorker {
protected function parseCommitWithRef( protected function getFollowupTaskClass() {
PhabricatorRepository $repository, return 'PhabricatorRepositorySvnCommitChangeParserWorker';
PhabricatorRepositoryCommit $commit,
DiffusionCommitRef $ref) {
$this->updateCommitData($ref);
if ($this->shouldQueueFollowupTasks()) {
$this->queueTask(
'PhabricatorRepositorySvnCommitChangeParserWorker',
array(
'commitID' => $commit->getID(),
));
}
} }
} }

Some files were not shown because too many files have changed in this diff Show more