1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-11-28 09:42: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(
'names' => array(
'core.pkg.css' => '6913fe66',
'core.pkg.css' => 'c7fc5aec',
'core.pkg.js' => '10275c16',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => 'b3eea3f5',
@ -81,7 +81,7 @@ return array(
'rsrc/css/application/owners/owners-path-editor.css' => '2f00933b',
'rsrc/css/application/paste/paste.css' => '1898e534',
'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-inline-comments.css' => '8e545e49',
'rsrc/css/application/pholio/pholio.css' => 'ca89d380',
@ -128,7 +128,7 @@ return array(
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
'rsrc/css/phui/phui-crumbs-view.css' => '6b813619',
'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.css' => '715aedfb',
'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-status.css' => 'd5263e49',
'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/workboards/phui-workboard-color.css' => 'ac6fe6a7',
'rsrc/css/phui/workboards/phui-workboard.css' => 'e6d89647',
@ -808,7 +808,7 @@ return array(
'phabricator-uiexample-reactor-sendclass' => '1def2711',
'phabricator-uiexample-reactor-sendproperties' => 'b1f0ccee',
'phabricator-zindex-css' => '5b6fcf3f',
'phame-css' => '7448a969',
'phame-css' => 'bf6a743f',
'pholio-css' => 'ca89d380',
'pholio-edit-css' => '07676f51',
'pholio-inline-comments-css' => '8e545e49',
@ -831,7 +831,7 @@ return array(
'phui-curtain-view-css' => '7148ae25',
'phui-document-summary-view-css' => '9ca48bdf',
'phui-document-view-css' => '715aedfb',
'phui-document-view-pro-css' => '8419560b',
'phui-document-view-pro-css' => 'a3730b94',
'phui-feed-story-css' => 'aa49845d',
'phui-font-icon-base-css' => '6449bce8',
'phui-fontkit-css' => '9cda225e',
@ -860,7 +860,7 @@ return array(
'phui-status-list-view-css' => 'd5263e49',
'phui-tag-view-css' => '6bbd83e2',
'phui-theme-css' => '027ba77e',
'phui-timeline-view-css' => '6e342216',
'phui-timeline-view-css' => '8ea41b25',
'phui-two-column-view-css' => '9fb86c85',
'phui-workboard-color-css' => 'ac6fe6a7',
'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
$table = new PhabricatorPaste();
$x_table = new PhabricatorPasteTransaction();
$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";
// Long ago, this migration populated initial "create" transactions for old
// pastes from before transactions came into existence. It was removed after
// about three years.

View file

@ -4,6 +4,11 @@
// This is a wrapper script for Git, Mercurial, and Subversion. It primarily
// 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__)));
require_once $root.'/scripts/__init_script__.php';
@ -21,6 +26,16 @@ $args->parsePartial(
));
$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();
$arguments = array();

View file

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

View file

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

View file

@ -30,9 +30,11 @@ final class PhabricatorAuthAuthProviderPHIDType extends PhabricatorPHIDType {
array $objects) {
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 '.
'to simplify the interface.');
$global_settings_reason = pht(
'The "Re: Prefix" and "Vary Subjects" settings are now configured '.
'in global settings.');
$ancient_config += array(
'phid.external-loaders' =>
pht(
@ -321,6 +325,9 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
'differential.days-fresh' => $stale_reason,
'differential.days-stale' => $stale_reason,
'metamta.re-prefix' => $global_settings_reason,
'metamta.vary-subjects' => $global_settings_reason,
);
return $ancient_config;

View file

@ -276,22 +276,6 @@ EODOC
))
->setSummary(pht('Show email preferences link in email.'))
->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)
->setBoolOptions(
array(

View file

@ -43,6 +43,14 @@ final class PhabricatorSecurityConfigOptions
'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(
$this->newOption('security.alternate-file-domain', 'string', null)
->setLocked(true)
@ -276,6 +284,10 @@ final class PhabricatorSecurityConfigOptions
'unsecured content over plain HTTP. It is very difficult to '.
'undo this change once users\' browsers have accepted the '.
'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(
PhabricatorLiskDAO $object,
array $xactions,
$blocks,
array $changes,
PhutilMarkupEngine $engine) {
$flat_blocks = array_mergev($blocks);
$flat_blocks = mpull($changes, 'getNewValue');
$huge_block = implode("\n\n", $flat_blocks);
$task_map = array();

View file

@ -75,7 +75,7 @@ final class DifferentialRevisionSearchEngine
->setLabel(pht('Repositories'))
->setKey('repositoryPHIDs')
->setAliases(array('repository', 'repositories', 'repositoryPHID'))
->setDatasource(new DiffusionRepositoryDatasource())
->setDatasource(new DifferentialRepositoryDatasource())
->setDescription(
pht('Find revisions from specific repositories.')),
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));
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()) {
$error_panel = id(new PHUIInfoView())
->appendChild($this->getCommitErrors())

View file

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

View file

@ -7,6 +7,7 @@ final class DiffusionRepositoryRef extends Phobject {
private $shortName;
private $commitIdentifier;
private $refType;
private $rawFields = array();
public function setRawFields(array $raw_fields) {
@ -36,6 +37,25 @@ final class DiffusionRepositoryRef extends Phobject {
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 )------------------------------------------------------ */
@ -44,6 +64,7 @@ final class DiffusionRepositoryRef extends Phobject {
return array(
'shortName' => $this->shortName,
'commitIdentifier' => $this->commitIdentifier,
'refType' => $this->refType,
'rawFields' => $this->rawFields,
);
}
@ -52,6 +73,7 @@ final class DiffusionRepositoryRef extends Phobject {
return id(new DiffusionRepositoryRef())
->setShortName($dict['shortName'])
->setCommitIdentifier($dict['commitIdentifier'])
->setRefType($dict['refType'])
->setRawFields($dict['rawFields']);
}

View file

@ -14,96 +14,121 @@ final class DiffusionLowLevelGitRefQuery extends DiffusionLowLevelQuery {
}
protected function executeQuery() {
$type_branch = PhabricatorRepositoryRefCursor::TYPE_BRANCH;
$type_tag = PhabricatorRepositoryRefCursor::TYPE_TAG;
$type_ref = PhabricatorRepositoryRefCursor::TYPE_REF;
$ref_types = $this->refTypes;
if ($ref_types) {
$type_branch = PhabricatorRepositoryRefCursor::TYPE_BRANCH;
$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;
if (!$ref_types) {
$ref_types = array($type_branch, $type_tag, $type_ref);
}
$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();
$prefixes = array();
if ($with_branches) {
if ($repository->isWorkingCopyBare()) {
$prefix = 'refs/heads/';
} else {
$remote = DiffusionGitBranch::DEFAULT_GIT_REMOTE;
$prefix = 'refs/remotes/'.$remote.'/';
if ($repository->isWorkingCopyBare()) {
$branch_prefix = 'refs/heads/';
} else {
$remote = DiffusionGitBranch::DEFAULT_GIT_REMOTE;
$branch_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) {
$prefixes[] = 'refs/tags/';
$branch_len = strlen($branch_prefix);
$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();
foreach ($prefixes as $prefix) {
$futures[$prefix] = $repository->getLocalCommandFuture(
'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();
}
// NOTE: Although git supports --count, we can't apply any offset or
// limit logic until the very end because we may encounter a HEAD which
// we want to discard.
$lines = explode("\n", $stdout);
$results = array();
foreach ($futures as $prefix => $future) {
list($stdout) = $future->resolvex();
foreach ($lines as $line) {
$fields = $this->extractFields($line);
$stdout = rtrim($stdout);
if (!strlen($stdout)) {
$refname = $fields['refname'];
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;
}
// NOTE: Although git supports --count, we can't apply any offset or
// limit logic until the very end because we may encounter a HEAD which
// we want to discard.
$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;
// If this is the local HEAD, skip it.
if ($short == 'HEAD') {
continue;
}
$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;

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())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withUserPHIDs($phids)
->needSyntheticPreferences(true)
->execute();
$all_prefs = mpull($all_prefs, null, 'getUserPHID');
}

View file

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

View file

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

View file

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

View file

@ -325,10 +325,10 @@ abstract class PhabricatorFileStorageEngine extends Phobject {
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
// upfront.
$data = $file->loadFileData();
$data = $this->readFile($file->getStorageHandle());
if ($begin !== null && $end !== null) {
$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,
PhabricatorDestructibleInterface {
const STORAGE_FORMAT_RAW = 'raw';
const METADATA_IMAGE_WIDTH = 'width';
const METADATA_IMAGE_HEIGHT = 'height';
const METADATA_CAN_CDN = 'canCDN';
const METADATA_BUILTIN = 'builtin';
const METADATA_PARTIAL = 'partial';
const METADATA_PROFILE = 'profile';
const METADATA_STORAGE = 'storage';
protected $name;
protected $mimeType;
@ -233,10 +232,10 @@ final class PhabricatorFile extends PhabricatorFileDAO
$hash);
if ($file) {
// copy storageEngine, storageHandle, storageFormat
$copy_of_storage_engine = $file->getStorageEngine();
$copy_of_storage_handle = $file->getStorageHandle();
$copy_of_storage_format = $file->getStorageFormat();
$copy_of_storage_properties = $file->getStorageProperties();
$copy_of_byte_size = $file->getByteSize();
$copy_of_mime_type = $file->getMimeType();
@ -248,6 +247,7 @@ final class PhabricatorFile extends PhabricatorFileDAO
$new_file->setStorageEngine($copy_of_storage_engine);
$new_file->setStorageHandle($copy_of_storage_handle);
$new_file->setStorageFormat($copy_of_storage_format);
$new_file->setStorageProperties($copy_of_storage_properties);
$new_file->setMimeType($copy_of_mime_type);
$new_file->copyDimensions($file);
@ -290,7 +290,11 @@ final class PhabricatorFile extends PhabricatorFileDAO
$file->setStorageEngine($engine->getEngineIdentifier());
$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->readPropertiesFromParameters($params);
@ -322,6 +326,29 @@ final class PhabricatorFile extends PhabricatorFileDAO
$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;
$engine_identifier = null;
$exceptions = array();
@ -361,10 +388,6 @@ final class PhabricatorFile extends PhabricatorFileDAO
$file->setStorageEngine($engine_identifier);
$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);
if (!$file->getMimeType()) {
@ -427,6 +450,53 @@ final class PhabricatorFile extends PhabricatorFileDAO
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(
PhabricatorFileStorageEngine $engine,
$data,
@ -434,7 +504,15 @@ final class PhabricatorFile extends PhabricatorFileDAO
$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) {
// This indicates an improperly implemented storage engine.
@ -663,19 +741,8 @@ final class PhabricatorFile extends PhabricatorFileDAO
}
public function loadFileData() {
$engine = $this->instantiateStorageEngine();
$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;
$iterator = $this->getFileDataIterator();
return $this->loadDataFromIterator($iterator);
}
@ -688,7 +755,14 @@ final class PhabricatorFile extends PhabricatorFileDAO
*/
public function getFileDataIterator($begin = null, $end = null) {
$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);
}
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) {
if (!$this->isViewableImage()) {
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 $viewer;
private $newBuildTargets = array();
private $artifactReleaseQueue = array();
private $forceBuildableUpdate;
public function setForceBuildableUpdate($force_buildable_update) {
@ -94,6 +95,8 @@ final class HarbormasterBuildEngine extends Phobject {
$this->updateBuildable($build->getBuildable());
}
$this->releaseQueuedArtifacts();
// If we are no longer building for any reason, release all artifacts.
if (!$build->isBuilding()) {
$this->releaseAllArtifacts($build);
@ -149,20 +152,21 @@ final class HarbormasterBuildEngine extends Phobject {
}
private function updateBuildSteps(HarbormasterBuild $build) {
$targets = id(new HarbormasterBuildTargetQuery())
$all_targets = id(new HarbormasterBuildTargetQuery())
->setViewer($this->getViewer())
->withBuildPHIDs(array($build->getPHID()))
->withBuildGenerations(array($build->getBuildGeneration()))
->execute();
$this->updateWaitingTargets($targets);
$this->updateWaitingTargets($all_targets);
$targets = mgroup($targets, 'getBuildStepPHID');
$targets = mgroup($all_targets, 'getBuildStepPHID');
$steps = id(new HarbormasterBuildStepQuery())
->setViewer($this->getViewer())
->withBuildPlanPHIDs(array($build->getBuildPlan()->getPHID()))
->execute();
$steps = mpull($steps, null, 'getPHID');
// Identify steps which are in various states.
@ -252,6 +256,12 @@ final class HarbormasterBuildEngine extends Phobject {
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
// 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
* targets out of "Waiting" and into either "Passed" or "Failed".
@ -488,12 +551,18 @@ final class HarbormasterBuildEngine extends Phobject {
$artifacts = id(new HarbormasterBuildArtifactQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withBuildTargetPHIDs($target_phids)
->withIsReleased(false)
->execute();
foreach ($artifacts as $artifact) {
$artifact->releaseArtifact();
}
}
private function releaseQueuedArtifacts() {
foreach ($this->artifactReleaseQueue as $key => $artifact) {
$artifact->releaseArtifact();
unset($this->artifactReleaseQueue[$key]);
}
}
}

View file

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

View file

@ -103,11 +103,6 @@ abstract class HarbormasterBuildStepImplementation extends Phobject {
/**
* 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
* before them and filter on the type.
*

View file

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

View file

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

View file

@ -877,6 +877,7 @@ final class PhabricatorMetaMTAMail
$all_prefs = id(new PhabricatorUserPreferencesQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->withUserPHIDs($actor_phids)
->needSyntheticPreferences(true)
->execute();
$all_prefs = mpull($all_prefs, null, 'getUserPHID');
@ -1105,53 +1106,33 @@ final class PhabricatorMetaMTAMail
private function loadPreferences($target_phid) {
if (!self::shouldMultiplexAllMail()) {
$target_phid = null;
}
$viewer = PhabricatorUser::getOmnipotentUser();
if ($target_phid) {
if (self::shouldMultiplexAllMail()) {
$preferences = id(new PhabricatorUserPreferencesQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())
->setViewer($viewer)
->withUserPHIDs(array($target_phid))
->needSyntheticPreferences(true)
->executeOne();
} else {
$preferences = null;
if ($preferences) {
return $preferences;
}
}
// TODO: Here, we would load global preferences once they exist.
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;
return PhabricatorUserPreferences::loadGlobalPreferences($viewer);
}
private function shouldAddRePrefix(PhabricatorUserPreferences $preferences) {
$default_value = PhabricatorEnv::getEnvConfig('metamta.re-prefix');
$value = $preferences->getPreference(
$value = $preferences->getSettingValue(
PhabricatorEmailRePrefixSetting::SETTINGKEY);
if ($value === null) {
return $default_value;
}
return ($value == PhabricatorEmailRePrefixSetting::VALUE_RE_PREFIX);
}
private function shouldVarySubject(PhabricatorUserPreferences $preferences) {
$default_value = PhabricatorEnv::getEnvConfig('metamta.vary-subjects');
$value = $preferences->getPreference(
$value = $preferences->getSettingValue(
PhabricatorEmailVarySubjectsSetting::SETTINGKEY);
if ($value === null) {
return $default_value;
}
return ($value == PhabricatorEmailVarySubjectsSetting::VALUE_VARY_SUBJECTS);
}

View file

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

View file

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

View file

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

View file

@ -3,8 +3,6 @@
final class PhabricatorPasteEditor
extends PhabricatorApplicationTransactionEditor {
private $fileName;
public function getEditorApplicationClass() {
return 'PhabricatorPasteApplication';
}
@ -13,29 +11,17 @@ final class PhabricatorPasteEditor
return pht('Pastes');
}
public static function initializeFileForPaste(
PhabricatorUser $actor,
$name,
$data) {
public function getCreateObjectTitle($author, $object) {
return pht('%s created this paste.', $author);
}
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 getCreateObjectTitleForFeed($author, $object) {
return pht('%s created %s.', $author, $object);
}
public function 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_EDIT_POLICY;
$types[] = PhabricatorTransactions::TYPE_COMMENT;
@ -43,163 +29,14 @@ final class PhabricatorPasteEditor
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(
PhabricatorLiskDAO $object,
array $xactions) {
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorPasteTransaction::TYPE_CONTENT:
return false;
default:
break;
}
if ($this->getIsNewObject()) {
return false;
}
return true;
}
@ -258,8 +95,4 @@ final class PhabricatorPasteEditor
return true;
}
protected function supportsSearch() {
return false;
}
}

View file

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

View file

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

View file

@ -1,12 +1,7 @@
<?php
final class PhabricatorPasteTransaction
extends PhabricatorApplicationTransaction {
const TYPE_CONTENT = 'paste.create';
const TYPE_TITLE = 'paste.title';
const TYPE_LANGUAGE = 'paste.language';
const TYPE_STATUS = 'paste.status';
extends PhabricatorModularTransaction {
const MAILTAG_CONTENT = 'paste-content';
const MAILTAG_OTHER = 'paste-other';
@ -24,226 +19,16 @@ final class PhabricatorPasteTransaction
return new PhabricatorPasteTransactionComment();
}
public function getRequiredHandlePHIDs() {
$phids = parent::getRequiredHandlePHIDs();
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 getBaseTransactionClass() {
return 'PhabricatorPasteTransactionType';
}
public function getMailTags() {
$tags = array();
switch ($this->getTransactionType()) {
case self::TYPE_TITLE:
case self::TYPE_CONTENT:
case self::TYPE_LANGUAGE:
case PhabricatorPasteTitleTransaction::TRANSACTIONTYPE:
case PhabricatorPasteContentTransaction::TRANSACTIONTYPE:
case PhabricatorPasteLanguageTransaction::TRANSACTIONTYPE:
$tags[] = self::MAILTAG_CONTENT;
break;
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())
->setViewer($viewer)
->withUserPHIDs($user_phids)
->withUsers($users)
->needSyntheticPreferences(true)
->execute();
$preferences = mpull($preferences, null, 'getUserPHID');
$all_settings = PhabricatorSetting::getAllSettings();
$settings = array();
foreach ($preferences as $preference) {
$user_phid = $preference->getUserPHID();
foreach ($users as $user_phid => $user) {
$preference = idx($preferences, $user_phid);
if (!$preference) {
continue;
}
foreach ($all_settings as $key => $setting) {
$value = $preference->getSettingValue($key);
// As an optimization, we omit the value from the cache if it is
// exactly the same as the hardcoded default.
$default_value = id(clone $setting)
->setViewer($users[$user_phid])
->setViewer($user)
->getSettingDefaultValue();
if ($value === $default_value) {
continue;

View file

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

View file

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

View file

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

View file

@ -58,6 +58,7 @@ abstract class PhameLiveController extends PhameController {
$blog_query = id(new PhameBlogQuery())
->setViewer($viewer)
->needProfileImage(true)
->needHeaderImage(true)
->withIDs(array($blog_id));
// 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.
if ($is_external) {
$post_query->withVisibility(PhameConstants::VISIBILITY_PUBLISHED);
$post_query->withVisibility(
array(PhameConstants::VISIBILITY_PUBLISHED));
}
$post = $post_query->executeOne();
@ -210,6 +212,7 @@ abstract class PhameLiveController extends PhameController {
if ($this->getIsLive()) {
$page
->addClass('phame-live-view')
->setShowChrome(false)
->setShowFooter(false);
}

View file

@ -21,7 +21,7 @@ final class PhameBlogFeedController extends PhameBlogController {
$posts = id(new PhamePostQuery())
->setViewer($viewer)
->withBlogPHIDs(array($blog->getPHID()))
->withVisibility(PhameConstants::VISIBILITY_PUBLISHED)
->withVisibility(array(PhameConstants::VISIBILITY_PUBLISHED))
->execute();
$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)
->withIDs(array($id))
->needProfileImage(true)
->needHeaderImage(true)
->executeOne();
if (!$blog) {
return new Aphront404Response();
@ -31,22 +32,33 @@ final class PhameBlogManageController extends PhameBlogController {
$picture = $blog->getProfileImageURI();
$view = id(new PHUIButtonView())
->setTag('a')
->setText(pht('View Live'))
->setIcon('fa-external-link')
->setHref($blog->getLiveURI());
$header = id(new PHUIHeaderView())
->setHeader($blog->getName())
->setUser($viewer)
->setPolicyObject($blog)
->setImage($picture)
->setStatus($header_icon, $header_color, $header_name);
->setStatus($header_icon, $header_color, $header_name)
->addActionLink($view);
$actions = $this->renderActions($blog, $viewer);
$properties = $this->renderProperties($blog, $viewer, $actions);
$curtain = $this->buildCurtain($blog);
$properties = $this->buildPropertyView($blog);
$file = $this->buildFileView($blog);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb(
pht('Blogs'),
$this->getApplicationURI('blog/'));
$crumbs->addTextCrumb(
$blog->getName());
$blog->getName(),
$this->getApplicationURI('blog/view/'.$id));
$crumbs->addTextCrumb(pht('Manage Blog'));
$crumbs->setBorder(true);
$object_box = id(new PHUIObjectBoxView())
->setHeader($header)
@ -57,28 +69,34 @@ final class PhameBlogManageController extends PhameBlogController {
new PhameBlogTransactionQuery());
$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()
->setTitle($blog->getName())
->setCrumbs($crumbs)
->appendChild(
array(
$object_box,
$timeline,
$view,
));
}
private function renderProperties(
PhameBlog $blog,
PhabricatorUser $viewer,
PhabricatorActionListView $actions) {
private function buildPropertyView(PhameBlog $blog) {
$viewer = $this->getViewer();
require_celerity_resource('aphront-tooltip-css');
Javelin::initBehavior('phabricator-tooltips');
$properties = id(new PHUIPropertyListView())
->setUser($viewer)
->setObject($blog)
->setActionList($actions);
->setObject($blog);
$domain = $blog->getDomain();
if (!$domain) {
@ -129,7 +147,11 @@ final class PhameBlogManageController extends PhameBlogController {
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())
->setObject($blog)
->setUser($viewer);
@ -139,7 +161,7 @@ final class PhameBlogManageController extends PhameBlogController {
$blog,
PhabricatorPolicyCapability::CAN_EDIT);
$actions->addAction(
$curtain->addAction(
id(new PhabricatorActionView())
->setIcon('fa-pencil')
->setHref($this->getApplicationURI('blog/edit/'.$blog->getID().'/'))
@ -147,7 +169,15 @@ final class PhameBlogManageController extends PhameBlogController {
->setDisabled(!$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())
->setIcon('fa-picture-o')
->setHref($this->getApplicationURI('blog/picture/'.$blog->getID().'/'))
@ -156,7 +186,7 @@ final class PhameBlogManageController extends PhameBlogController {
->setWorkflow(!$can_edit));
if ($blog->isArchived()) {
$actions->addAction(
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Activate Blog'))
->setIcon('fa-check')
@ -165,7 +195,7 @@ final class PhameBlogManageController extends PhameBlogController {
->setDisabled(!$can_edit)
->setWorkflow(true));
} else {
$actions->addAction(
$curtain->addAction(
id(new PhabricatorActionView())
->setName(pht('Archive Blog'))
->setIcon('fa-ban')
@ -175,7 +205,27 @@ final class PhameBlogManageController extends PhameBlogController {
->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
extends PhameBlogController {
public function shouldRequireAdmin() {
return false;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $request->getViewer();
$id = $request->getURIData('id');
@ -175,6 +171,7 @@ final class PhameBlogProfilePictureController
$form_box = id(new PHUIObjectBoxView())
->setHeaderText($title)
->setFormErrors($errors)
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($form);
$upload_form = id(new AphrontFormView())
@ -194,6 +191,7 @@ final class PhameBlogProfilePictureController
$upload_box = id(new PHUIObjectBoxView())
->setHeaderText(pht('Upload New Picture'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setForm($upload_form);
$crumbs = $this->buildApplicationCrumbs();
@ -204,14 +202,25 @@ final class PhameBlogProfilePictureController
$blog->getName(),
$this->getApplicationURI('blog/view/'.$id));
$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()
->setTitle($title)
->setCrumbs($crumbs)
->appendChild(
array(
$form_box,
$upload_box,
$view,
));
}

View file

@ -19,16 +19,23 @@ final class PhameBlogViewController extends PhameLiveController {
$post_query = id(new PhamePostQuery())
->setViewer($viewer)
->withBlogPHIDs(array($blog->getPHID()));
->withBlogPHIDs(array($blog->getPHID()))
->setOrder('datePublished')
->withVisibility(array(
PhameConstants::VISIBILITY_PUBLISHED,
PhameConstants::VISIBILITY_DRAFT,
));
if ($is_live) {
$post_query->withVisibility(PhameConstants::VISIBILITY_PUBLISHED);
$post_query->withVisibility(array(PhameConstants::VISIBILITY_PUBLISHED));
}
$posts = $post_query->executeWithCursorPager($pager);
$hero = $this->buildPhameHeader($blog);
$header = id(new PHUIHeaderView())
->setHeader($blog->getName())
->addClass('phame-header-bar')
->setUser($viewer);
if (!$is_external) {
@ -104,6 +111,7 @@ final class PhameBlogViewController extends PhameLiveController {
->setCrumbs($crumbs)
->appendChild(
array(
$hero,
$page,
$about,
));
@ -147,4 +155,33 @@ final class PhameBlogViewController extends PhameLiveController {
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.')));
}
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()) {
$document->appendChild(
id(new PHUIInfoView())
@ -92,6 +102,8 @@ final class PhamePostViewController
$date = phabricator_datetime($post->getDatePublished(), $viewer);
if ($post->isDraft()) {
$subtitle = pht('Unpublished draft by %s.', $author);
} else if ($post->isArchived()) {
$subtitle = pht('Archived post by %s.', $author);
} else {
$subtitle = pht('Written by %s on %s.', $author, $date);
}
@ -207,6 +219,21 @@ final class PhamePostViewController
->setName(pht('Publish'))
->setDisabled(!$can_edit)
->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 {
$actions->addAction(
id(new PhabricatorActionView())
@ -215,6 +242,13 @@ final class PhamePostViewController
->setName(pht('Unpublish'))
->setDisabled(!$can_edit)
->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()) {
@ -223,12 +257,14 @@ final class PhamePostViewController
$live_name = pht('View Live');
}
$actions->addAction(
id(new PhabricatorActionView())
->setUser($viewer)
->setIcon('fa-globe')
->setHref($post->getLiveURI())
->setName($live_name));
if (!$post->isArchived()) {
$actions->addAction(
id(new PhabricatorActionView())
->setUser($viewer)
->setIcon('fa-globe')
->setHref($post->getLiveURI())
->setName($live_name));
}
return $actions;
}
@ -255,7 +291,7 @@ final class PhamePostViewController
$query = id(new PhamePostQuery())
->setViewer($viewer)
->withVisibility(PhameConstants::VISIBILITY_PUBLISHED)
->withVisibility(array(PhameConstants::VISIBILITY_PUBLISHED))
->withBlogPHIDs(array($post->getBlog()->getPHID()))
->setLimit(1);

View file

@ -77,6 +77,14 @@ final class PhameBlogEditEngine
->setConduitTypeDescription(pht('New blog title.'))
->setTransactionType(PhameBlogTransaction::TYPE_NAME)
->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())
->setKey('description')
->setLabel(pht('Description'))

View file

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

View file

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

View file

@ -34,6 +34,11 @@ final class PhabricatorPhameBlogPHIDType extends PhabricatorPHIDType {
$handle->setName($blog->getName());
$handle->setFullName($blog->getName());
$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->setFullName($post->getTitle());
$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 $needProfileImage;
private $needHeaderImage;
public function withIDs(array $ids) {
$this->ids = $ids;
@ -35,6 +36,11 @@ final class PhameBlogQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $this;
}
public function needHeaderImage($need) {
$this->needHeaderImage = $need;
return $this;
}
public function newResultObject() {
return new PhameBlog();
}
@ -107,6 +113,28 @@ final class PhameBlogQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$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;
}

View file

@ -29,7 +29,7 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery {
return $this;
}
public function withVisibility($visibility) {
public function withVisibility(array $visibility) {
$this->visibility = $visibility;
return $this;
}
@ -98,10 +98,10 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery {
$this->bloggerPHIDs);
}
if ($this->visibility !== null) {
if ($this->visibility) {
$where[] = qsprintf(
$conn,
'visibility = %d',
'visibility IN (%Ld)',
$this->visibility);
}
@ -122,6 +122,36 @@ final class PhamePostQuery extends PhabricatorCursorPagedPolicyAwareQuery {
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() {
// TODO: Does setting this break public blogs?
return null;

View file

@ -19,7 +19,7 @@ final class PhamePostSearchEngine
$query = $this->newQuery();
if (strlen($map['visibility'])) {
$query->withVisibility($map['visibility']);
$query->withVisibility(array($map['visibility']));
}
return $query;
@ -35,6 +35,7 @@ final class PhamePostSearchEngine
'' => pht('All'),
PhameConstants::VISIBILITY_PUBLISHED => pht('Published'),
PhameConstants::VISIBILITY_DRAFT => pht('Draft'),
PhameConstants::VISIBILITY_ARCHIVED => pht('Archived'),
)),
);
}
@ -48,6 +49,7 @@ final class PhamePostSearchEngine
'all' => pht('All Posts'),
'live' => pht('Published Posts'),
'draft' => pht('Draft Posts'),
'archived' => pht('Archived Posts'),
);
return $names;
}
@ -65,6 +67,9 @@ final class PhamePostSearchEngine
case 'draft':
return $query->setParameter(
'visibility', PhameConstants::VISIBILITY_DRAFT);
case 'archived':
return $query->setParameter(
'visibility', PhameConstants::VISIBILITY_ARCHIVED);
}
return parent::buildSavedQueryFromBuiltin($query_key);
@ -99,11 +104,19 @@ final class PhamePostSearchEngine
if ($post->isDraft()) {
$item->setStatusIcon('fa-star-o grey');
$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 {
$date = $post->getDatePublished();
$item->setEpoch($date);
}
$item->addAction(
id(new PHUIListItemView())
->setIcon('fa-pencil')
->setHref($post->getEditURI()));
$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,
PhabricatorDestructibleInterface,
PhabricatorApplicationTransactionInterface,
PhabricatorConduitResultInterface {
PhabricatorConduitResultInterface,
PhabricatorFulltextInterface {
const MARKUP_FIELD_DESCRIPTION = 'markup:description';
protected $name;
protected $subtitle;
protected $description;
protected $domain;
protected $configData;
@ -23,8 +25,10 @@ final class PhameBlog extends PhameDAO
protected $status;
protected $mailKey;
protected $profileImagePHID;
protected $headerImagePHID;
private $profileImageFile = self::ATTACHABLE;
private $headerImageFile = self::ATTACHABLE;
const STATUS_ACTIVE = 'active';
const STATUS_ARCHIVED = 'archived';
@ -37,11 +41,13 @@ final class PhameBlog extends PhameDAO
),
self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text64',
'subtitle' => 'text64',
'description' => 'text',
'domain' => 'text128?',
'status' => 'text32',
'mailKey' => 'bytes20',
'profileImagePHID' => 'phid?',
'headerImagePHID' => 'phid?',
// T6203/NULLABILITY
// These policies should always be non-null.
@ -211,6 +217,19 @@ final class PhameBlog extends PhameDAO
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 )-------------------------- */
@ -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 {
const TYPE_NAME = 'phame.blog.name';
const TYPE_SUBTITLE = 'phame.blog.subtitle';
const TYPE_DESCRIPTION = 'phame.blog.description';
const TYPE_DOMAIN = 'phame.blog.domain';
const TYPE_STATUS = 'phame.blog.status';
@ -80,6 +81,7 @@ final class PhameBlogTransaction
$tags[] = self::MAILTAG_SUBSCRIBERS;
break;
case self::TYPE_NAME:
case self::TYPE_SUBTITLE:
case self::TYPE_DESCRIPTION:
case self::TYPE_DOMAIN:
$tags[] = self::MAILTAG_DETAILS;
@ -116,6 +118,19 @@ final class PhameBlogTransaction
$new);
}
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:
return pht(
'%s updated the blog\'s description.',
@ -166,6 +181,19 @@ final class PhameBlogTransaction
$this->renderHandleLink($object_phid));
}
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:
return pht(
'%s updated the description for %s.',

View file

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

View file

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

View file

@ -1099,6 +1099,18 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
$column = $this->refreshColumn($user, $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(
$user,
$column,

View file

@ -11,14 +11,20 @@ final class PhabricatorGitGraphStream
public function __construct(
PhabricatorRepository $repository,
$start_commit) {
$start_commit = null) {
$this->repository = $repository;
$future = $repository->getLocalCommandFuture(
'log --format=%s %s --',
'%H%x01%P%x01%ct',
$start_commit);
if ($start_commit !== null) {
$future = $repository->getLocalCommandFuture(
'log --format=%s %s --',
'%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->setDelimiter("\n");

View file

@ -105,6 +105,8 @@ final class PhabricatorRepositoryDiscoveryEngine
$this->commitCache[$ref->getIdentifier()] = true;
}
$this->markUnreachableCommits($repository);
$version = $this->getObservedVersion($repository);
if ($version !== null) {
id(new DiffusionRepositoryClusterEngine())
@ -130,39 +132,35 @@ final class PhabricatorRepositoryDiscoveryEngine
$this->verifyGitOrigin($repository);
}
// TODO: This should also import tags, but some of the logic is still
// branch-specific today.
$branches = id(new DiffusionLowLevelGitRefQuery())
$heads = id(new DiffusionLowLevelGitRefQuery())
->setRepository($repository)
->withRefTypes(
array(
PhabricatorRepositoryRefCursor::TYPE_BRANCH,
))
->execute();
if (!$branches) {
// This repository has no branches at all, so we don't need to do
if (!$heads) {
// This repository has no heads at all, so we don't need to do
// anything. Generally, this means the repository is empty.
return array();
}
$branches = $this->sortBranches($branches);
$branches = mpull($branches, 'getCommitIdentifier', 'getShortName');
$heads = $this->sortRefs($heads);
$head_commits = mpull($heads, 'getCommitIdentifier');
$this->log(
pht(
'Discovering commits in repository "%s".',
$repository->getDisplayName()));
$this->fillCommitCache(array_values($branches));
$this->fillCommitCache($head_commits);
$refs = array();
foreach ($branches as $name => $commit) {
$this->log(pht('Examining branch "%s", at "%s".', $name, $commit));
foreach ($heads as $ref) {
$name = $ref->getShortName();
$commit = $ref->getCommitIdentifier();
if (!$repository->shouldTrackBranch($name)) {
$this->log(pht('Skipping, branch is untracked.'));
$this->log(pht('Examining ref "%s", at "%s".', $name, $commit));
if (!$repository->shouldTrackRef($ref)) {
$this->log(pht('Skipping, ref is untracked.'));
continue;
}
@ -173,14 +171,14 @@ final class PhabricatorRepositoryDiscoveryEngine
$this->log(pht('Looking for new commits.'));
$branch_refs = $this->discoverStreamAncestry(
$head_refs = $this->discoverStreamAncestry(
new PhabricatorGitGraphStream($repository, $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);
@ -448,10 +446,17 @@ final class PhabricatorRepositoryDiscoveryEngine
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(
'repositoryID = %d AND commitIdentifier IN (%Ls)',
'repositoryID = %d AND commitIdentifier IN (%Ls)
AND (importStatus & %d) != %d',
$this->getRepository()->getID(),
$identifiers);
$identifiers,
PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE,
PhabricatorRepositoryCommit::IMPORTED_UNREACHABLE);
foreach ($commits as $commit) {
$this->commitCache[$commit->getCommitIdentifier()] = true;
@ -469,25 +474,23 @@ final class PhabricatorRepositoryDiscoveryEngine
*
* @task internal
*
* @param list<DiffusionRepositoryRef> List of branch heads.
* @return list<DiffusionRepositoryRef> Sorted list of branch heads.
* @param list<DiffusionRepositoryRef> List of refs.
* @return list<DiffusionRepositoryRef> Sorted list of refs.
*/
private function sortBranches(array $branches) {
private function sortRefs(array $refs) {
$repository = $this->getRepository();
$head_branches = array();
$tail_branches = array();
foreach ($branches as $branch) {
$name = $branch->getShortName();
if ($repository->shouldAutocloseBranch($name)) {
$head_branches[] = $branch;
$head_refs = array();
$tail_refs = array();
foreach ($refs as $ref) {
if ($repository->shouldAutocloseRef($ref)) {
$head_refs[] = $ref;
} 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) {
$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->setCommitIdentifier($commit_identifier);
$commit->setEpoch($epoch);
@ -508,10 +535,7 @@ final class PhabricatorRepositoryDiscoveryEngine
$data = new PhabricatorRepositoryCommitData();
$conn_w = $repository->establishConnection('w');
try {
// If this commit has parents, look up their IDs. The parent commits
// should always exist already.
@ -559,21 +583,7 @@ final class PhabricatorRepositoryDiscoveryEngine
}
$commit->saveTransaction();
$this->insertTask($repository, $commit);
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);
$this->didDiscoverCommit($repository, $commit, $epoch);
if ($this->repairMode) {
// Normally, the query should throw a duplicate key exception. If we
@ -589,8 +599,6 @@ final class PhabricatorRepositoryDiscoveryEngine
'commit' => $commit,
)));
} catch (AphrontDuplicateKeyQueryException $ex) {
$commit->killTransaction();
// 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) {
foreach ($refs as $ref) {
$this->workingSet[$ref->getIdentifier()] = true;
@ -702,4 +733,136 @@ final class PhabricatorRepositoryDiscoveryEngine
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.
$future = $repository->getRemoteCommandFuture(
'fetch origin %s --prune',
'+refs/heads/*:refs/heads/*');
'+refs/*:refs/*');
} else {
$future = $repository->getRemoteCommandFuture(
'fetch --all --prune');

View file

@ -25,29 +25,31 @@ final class PhabricatorRepositoryRefEngine
switch ($vcs) {
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
// No meaningful refs of any type in Subversion.
$branches = array();
$bookmarks = array();
$tags = array();
$maps = array();
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_MERCURIAL:
$branches = $this->loadMercurialBranchPositions($repository);
$bookmarks = $this->loadMercurialBookmarkPositions($repository);
$tags = array();
$maps = array(
PhabricatorRepositoryRefCursor::TYPE_BRANCH => $branches,
PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => $bookmarks,
);
$branches_may_close = true;
break;
case PhabricatorRepositoryType::REPOSITORY_TYPE_GIT:
$branches = $this->loadGitBranchPositions($repository);
$bookmarks = array();
$tags = $this->loadGitTagPositions($repository);
$maps = $this->loadGitRefPositions($repository);
break;
default:
throw new Exception(pht('Unknown VCS "%s"!', $vcs));
}
$maps = array(
PhabricatorRepositoryRefCursor::TYPE_BRANCH => $branches,
PhabricatorRepositoryRefCursor::TYPE_TAG => $tags,
PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => $bookmarks,
// Fill in any missing types with empty lists.
$maps = $maps + array(
PhabricatorRepositoryRefCursor::TYPE_BRANCH => array(),
PhabricatorRepositoryRefCursor::TYPE_TAG => array(),
PhabricatorRepositoryRefCursor::TYPE_BOOKMARK => array(),
PhabricatorRepositoryRefCursor::TYPE_REF => array(),
);
$all_cursors = id(new PhabricatorRepositoryRefCursorQuery())
@ -83,6 +85,13 @@ final class PhabricatorRepositoryRefEngine
$ref->save();
}
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();
}
$repository->saveTransaction();
@ -91,6 +100,7 @@ final class PhabricatorRepositoryRefEngine
$this->deadRefs = array();
}
$branches = $maps[PhabricatorRepositoryRefCursor::TYPE_BRANCH];
if ($branches && $branches_may_close) {
$this->updateBranchStates($repository, $branches);
}
@ -449,28 +459,12 @@ final class PhabricatorRepositoryRefEngine
/**
* @task git
*/
private function loadGitBranchPositions(PhabricatorRepository $repository) {
return id(new DiffusionLowLevelGitRefQuery())
private function loadGitRefPositions(PhabricatorRepository $repository) {
$refs = id(new DiffusionLowLevelGitRefQuery())
->setRepository($repository)
->withRefTypes(
array(
PhabricatorRepositoryRefCursor::TYPE_BRANCH,
))
->execute();
}
/**
* @task git
*/
private function loadGitTagPositions(PhabricatorRepository $repository) {
return id(new DiffusionLowLevelGitRefQuery())
->setRepository($repository)
->withRefTypes(
array(
PhabricatorRepositoryRefCursor::TYPE_TAG,
))
->execute();
return mgroup($refs, 'getRefType');
}

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->setURI($commit->getURI());
$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;
}
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) {
return $this->isBranchInFilter($branch, 'branch-filter');
}
@ -1020,6 +1035,14 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
/* -( Autoclose )---------------------------------------------------------- */
public function shouldAutocloseRef(DiffusionRepositoryRef $ref) {
if (!$ref->isBranch()) {
return false;
}
return $this->shouldAutocloseBranch($ref->getShortName());
}
/**
* Determine if autoclose is active for a branch.
*

View file

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

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_TAG = 'tag';
const TYPE_BOOKMARK = 'bookmark';
const TYPE_REF = 'ref';
protected $repositoryPHID;
protected $refType;

View file

@ -3,6 +3,10 @@
final class PhabricatorRepositoryCommitHeraldWorker
extends PhabricatorRepositoryCommitParserWorker {
protected function getImportStepFlag() {
return PhabricatorRepositoryCommit::IMPORTED_HERALD;
}
public function getRequiredLeaseTime() {
// Herald rules may take a long time to process.
return phutil_units('4 hours in seconds');
@ -12,6 +16,12 @@ final class PhabricatorRepositoryCommitHeraldWorker
PhabricatorRepository $repository,
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.
$commit = id(new DiffusionCommitQuery())
->setViewer(PhabricatorUser::getOmnipotentUser())

View file

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

View file

@ -26,6 +26,14 @@ abstract class PhabricatorRepositoryCommitParserWorker
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;
return $commit;
@ -44,6 +52,42 @@ abstract class PhabricatorRepositoryCommitParserWorker
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(
PhabricatorRepository $repository,
PhabricatorRepositoryCommit $commit);

View file

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

View file

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

View file

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

View file

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

View file

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

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