1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2024-12-04 20:52:43 +01:00

(stable) Promote 2017 Week 1

This commit is contained in:
epriestley 2017-01-06 17:01:27 -08:00
commit ea9c0607e1
76 changed files with 2760 additions and 1122 deletions

View file

@ -9,11 +9,11 @@ return array(
'names' => array(
'conpherence.pkg.css' => '0b64e988',
'conpherence.pkg.js' => '6249a1cf',
'core.pkg.css' => '404132bb',
'core.pkg.js' => '28e8cda8',
'core.pkg.css' => '9c725fa0',
'core.pkg.js' => 'a2ead3fe',
'darkconsole.pkg.js' => 'e7393ebb',
'differential.pkg.css' => 'a4ba74b5',
'differential.pkg.js' => '634399e9',
'differential.pkg.css' => 'f69afb45',
'differential.pkg.js' => '40b18f35',
'diffusion.pkg.css' => '91c5d3a6',
'diffusion.pkg.js' => '84c8f8fd',
'favicon.ico' => '30672e08',
@ -59,7 +59,7 @@ return array(
'rsrc/css/application/dashboard/dashboard.css' => 'bc6f2127',
'rsrc/css/application/diff/inline-comment-summary.css' => '51efda3a',
'rsrc/css/application/differential/add-comment.css' => 'c47f8c40',
'rsrc/css/application/differential/changeset-view.css' => 'b158cc46',
'rsrc/css/application/differential/changeset-view.css' => '11395d9c',
'rsrc/css/application/differential/core.css' => '5b7b8ff4',
'rsrc/css/application/differential/phui-inline-comment.css' => '5953c28e',
'rsrc/css/application/differential/revision-comment.css' => '14b8565a',
@ -108,7 +108,7 @@ return array(
'rsrc/css/application/tokens/tokens.css' => '3d0f239e',
'rsrc/css/application/uiexample/example.css' => '528b19de',
'rsrc/css/core/core.css' => 'd0801452',
'rsrc/css/core/remarkup.css' => '43e704eb',
'rsrc/css/core/remarkup.css' => 'aebc1180',
'rsrc/css/core/syntax.css' => '769d3498',
'rsrc/css/core/z-index.css' => '5e72c4e0',
'rsrc/css/diviner/diviner-shared.css' => 'aa3656aa',
@ -137,7 +137,7 @@ return array(
'rsrc/css/phui/phui-button.css' => '43f4912e',
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
'rsrc/css/phui/phui-cms.css' => 'be43c8a8',
'rsrc/css/phui/phui-comment-form.css' => 'c953b75e',
'rsrc/css/phui/phui-comment-form.css' => '48fbd65d',
'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad',
'rsrc/css/phui/phui-crumbs-view.css' => 'f82868f2',
'rsrc/css/phui/phui-curtain-view.css' => '947bf1a4',
@ -146,7 +146,7 @@ return array(
'rsrc/css/phui/phui-document.css' => 'c32e8dec',
'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9',
'rsrc/css/phui/phui-fontkit.css' => '9cda225e',
'rsrc/css/phui/phui-form-view.css' => 'cd79ff6a',
'rsrc/css/phui/phui-form-view.css' => '04cc4771',
'rsrc/css/phui/phui-form.css' => '2342b0e5',
'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
'rsrc/css/phui/phui-header-view.css' => '6ec8f155',
@ -396,8 +396,9 @@ return array(
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '019f36c4',
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '453c5375',
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => 'd4eecc63',
'rsrc/js/application/diff/behavior-preview-link.js' => '051c7832',
'rsrc/js/application/differential/ChangesetViewManager.js' => 'a2828756',
'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '64a5550f',
'rsrc/js/application/differential/DifferentialInlineCommentEditor.js' => '2e3f9738',
'rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js' => 'e10f8e18',
'rsrc/js/application/differential/behavior-comment-jump.js' => '4fdb476d',
'rsrc/js/application/differential/behavior-comment-preview.js' => 'b064af76',
@ -453,7 +454,7 @@ return array(
'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e2e0a072',
'rsrc/js/application/search/behavior-reorder-queries.js' => 'e9581f08',
'rsrc/js/application/slowvote/behavior-slowvote-embed.js' => '887ad43f',
'rsrc/js/application/transactions/behavior-comment-actions.js' => '1be09f3f',
'rsrc/js/application/transactions/behavior-comment-actions.js' => 'b52947eb',
'rsrc/js/application/transactions/behavior-reorder-configs.js' => 'd7a74243',
'rsrc/js/application/transactions/behavior-reorder-fields.js' => 'b59e1e96',
'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '94c65b72',
@ -488,7 +489,7 @@ return array(
'rsrc/js/core/ShapedRequest.js' => '7cbe244b',
'rsrc/js/core/TextAreaUtils.js' => '320810c8',
'rsrc/js/core/Title.js' => '485aaa6c',
'rsrc/js/core/ToolTip.js' => '6323f942',
'rsrc/js/core/ToolTip.js' => 'b5c62c3b',
'rsrc/js/core/behavior-active-nav.js' => 'e379b58e',
'rsrc/js/core/behavior-audio-source.js' => '59b251eb',
'rsrc/js/core/behavior-autofocus.js' => '7319e029',
@ -509,7 +510,7 @@ return array(
'rsrc/js/core/behavior-hovercard.js' => 'bcaccd64',
'rsrc/js/core/behavior-keyboard-pager.js' => 'a8da01f0',
'rsrc/js/core/behavior-keyboard-shortcuts.js' => '01fca1f0',
'rsrc/js/core/behavior-lightbox-attachments.js' => 'ddcd41cf',
'rsrc/js/core/behavior-lightbox-attachments.js' => 'a5c57c24',
'rsrc/js/core/behavior-line-linker.js' => '1499a8cb',
'rsrc/js/core/behavior-more.js' => 'a80d0378',
'rsrc/js/core/behavior-object-selector.js' => 'e0ec7f2f',
@ -542,7 +543,7 @@ return array(
'rsrc/js/phuix/PHUIXActionView.js' => '8cf6d262',
'rsrc/js/phuix/PHUIXAutocomplete.js' => '6d86ce8b',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => '82e270da',
'rsrc/js/phuix/PHUIXFormControl.js' => '301b7812',
'rsrc/js/phuix/PHUIXFormControl.js' => 'bbece68d',
'rsrc/js/phuix/PHUIXIconView.js' => 'bff6884b',
),
'symbols' => array(
@ -573,9 +574,9 @@ return array(
'conpherence-thread-manager' => 'c8b5ee6f',
'conpherence-transaction-css' => '85129c68',
'd3' => 'a11a5ff2',
'differential-changeset-view-css' => 'b158cc46',
'differential-changeset-view-css' => '11395d9c',
'differential-core-view-css' => '5b7b8ff4',
'differential-inline-comment-editor' => '64a5550f',
'differential-inline-comment-editor' => '2e3f9738',
'differential-revision-add-comment-css' => 'c47f8c40',
'differential-revision-comment-css' => '14b8565a',
'differential-revision-history-css' => '0e8eb855',
@ -609,7 +610,7 @@ return array(
'javelin-behavior-bulk-job-reload' => 'edf8a145',
'javelin-behavior-calendar-month-view' => 'fe33e256',
'javelin-behavior-choose-control' => '327a00d1',
'javelin-behavior-comment-actions' => '1be09f3f',
'javelin-behavior-comment-actions' => 'b52947eb',
'javelin-behavior-config-reorder-fields' => 'b6993408',
'javelin-behavior-conpherence-menu' => '7524fcfa',
'javelin-behavior-conpherence-participant-pane' => '8604caa8',
@ -625,6 +626,7 @@ return array(
'javelin-behavior-desktop-notifications-control' => 'edd1ba66',
'javelin-behavior-detect-timezone' => '4c193c96',
'javelin-behavior-device' => 'bb1dd507',
'javelin-behavior-diff-preview-link' => '051c7832',
'javelin-behavior-differential-add-reviewers-and-ccs' => 'e10f8e18',
'javelin-behavior-differential-comment-jump' => '4fdb476d',
'javelin-behavior-differential-diff-radios' => 'e1ff79b1',
@ -655,7 +657,7 @@ return array(
'javelin-behavior-history-install' => '7ee2b591',
'javelin-behavior-icon-composer' => '8499b6ab',
'javelin-behavior-launch-icon-composer' => '48086888',
'javelin-behavior-lightbox-attachments' => 'ddcd41cf',
'javelin-behavior-lightbox-attachments' => 'a5c57c24',
'javelin-behavior-line-chart' => 'e4232876',
'javelin-behavior-load-blame' => '42126667',
'javelin-behavior-maniphest-batch-editor' => '782ab6e7',
@ -806,7 +808,7 @@ return array(
'phabricator-object-selector-css' => '85ee8ce6',
'phabricator-phtize' => 'd254d646',
'phabricator-prefab' => '8d40ae75',
'phabricator-remarkup-css' => '43e704eb',
'phabricator-remarkup-css' => 'aebc1180',
'phabricator-search-results-css' => '64ad079a',
'phabricator-shaped-request' => '7cbe244b',
'phabricator-slowvote-css' => 'a94b7230',
@ -814,7 +816,7 @@ return array(
'phabricator-standard-page-view' => '894d8a25',
'phabricator-textareautils' => '320810c8',
'phabricator-title' => '485aaa6c',
'phabricator-tooltip' => '6323f942',
'phabricator-tooltip' => 'b5c62c3b',
'phabricator-ui-example-css' => '528b19de',
'phabricator-uiexample-javelin-view' => 'd4a14807',
'phabricator-uiexample-reactor-button' => 'd19198c8',
@ -849,7 +851,7 @@ return array(
'phui-calendar-month-css' => '8e10e92c',
'phui-chart-css' => '6bf6f78e',
'phui-cms-css' => 'be43c8a8',
'phui-comment-form-css' => 'c953b75e',
'phui-comment-form-css' => '48fbd65d',
'phui-comment-panel-css' => 'f50152ad',
'phui-crumbs-view-css' => 'f82868f2',
'phui-curtain-view-css' => '947bf1a4',
@ -860,7 +862,7 @@ return array(
'phui-font-icon-base-css' => '870a7360',
'phui-fontkit-css' => '9cda225e',
'phui-form-css' => '2342b0e5',
'phui-form-view-css' => 'cd79ff6a',
'phui-form-view-css' => '04cc4771',
'phui-head-thing-view-css' => 'fd311e5f',
'phui-header-view-css' => '6ec8f155',
'phui-hovercard' => '1bd28176',
@ -901,7 +903,7 @@ return array(
'phuix-action-view' => '8cf6d262',
'phuix-autocomplete' => '6d86ce8b',
'phuix-dropdown-menu' => '82e270da',
'phuix-form-control-view' => '301b7812',
'phuix-form-control-view' => 'bbece68d',
'phuix-icon-view' => 'bff6884b',
'policy-css' => '957ea14c',
'policy-edit-css' => '815c66f7',
@ -944,6 +946,11 @@ return array(
'javelin-dom',
'phabricator-keyboard-shortcut',
),
'051c7832' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'05270951' => array(
'javelin-util',
'javelin-magical-init',
@ -1025,6 +1032,9 @@ return array(
'javelin-dom',
'javelin-typeahead-normalizer',
),
'11395d9c' => array(
'phui-inline-comment-view-css',
),
'12884df9' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1083,15 +1093,6 @@ return array(
'javelin-request',
'javelin-uri',
),
'1be09f3f' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phuix-form-control-view',
'phuix-icon-view',
'javelin-behavior-phabricator-gesture',
),
'1def2711' => array(
'javelin-install',
'javelin-dom',
@ -1156,13 +1157,17 @@ return array(
'javelin-install',
'javelin-event',
),
'2e3f9738' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
'javelin-request',
'javelin-workflow',
),
'2ee659ce' => array(
'javelin-install',
),
'301b7812' => array(
'javelin-install',
'javelin-dom',
),
'320810c8' => array(
'javelin-install',
'javelin-dom',
@ -1413,26 +1418,12 @@ return array(
'javelin-install',
'javelin-util',
),
'6323f942' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-vector',
),
'635de1ec' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
),
'64a5550f' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
'javelin-request',
'javelin-workflow',
),
'680ea2c8' => array(
'javelin-install',
'javelin-dom',
@ -1768,6 +1759,15 @@ return array(
'javelin-uri',
'phabricator-notification',
),
'a5c57c24' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-mask',
'javelin-util',
'phuix-icon-view',
'phabricator-busy',
),
'a6f7a73b' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1846,9 +1846,6 @@ return array(
'javelin-util',
'phabricator-shaped-request',
),
'b158cc46' => array(
'phui-inline-comment-view-css',
),
'b1f0ccee' => array(
'javelin-install',
'javelin-dom',
@ -1880,6 +1877,15 @@ return array(
'javelin-typeahead-preloaded-source',
'javelin-util',
),
'b52947eb' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phuix-form-control-view',
'phuix-icon-view',
'javelin-behavior-phabricator-gesture',
),
'b59e1e96' => array(
'javelin-behavior',
'javelin-stratcom',
@ -1891,6 +1897,12 @@ return array(
'javelin-install',
'javelin-dom',
),
'b5c62c3b' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-vector',
),
'b5d57730' => array(
'javelin-install',
'javelin-stratcom',
@ -1916,6 +1928,10 @@ return array(
'javelin-vector',
'javelin-install',
),
'bbece68d' => array(
'javelin-install',
'javelin-dom',
),
'bcaccd64' => array(
'javelin-behavior',
'javelin-behavior-device',
@ -2060,15 +2076,6 @@ return array(
'javelin-util',
'phabricator-shaped-request',
),
'ddcd41cf' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-mask',
'javelin-util',
'phuix-icon-view',
'phabricator-busy',
),
'de2e896f' => array(
'javelin-behavior',
'javelin-dom',

View file

@ -348,7 +348,6 @@ phutil_register_library_map(array(
'DarkConsoleXHProfPluginAPI' => 'applications/console/plugin/xhprof/DarkConsoleXHProfPluginAPI.php',
'DifferentialAction' => 'applications/differential/constants/DifferentialAction.php',
'DifferentialActionEmailCommand' => 'applications/differential/command/DifferentialActionEmailCommand.php',
'DifferentialAddCommentView' => 'applications/differential/view/DifferentialAddCommentView.php',
'DifferentialAdjustmentMapTestCase' => 'applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php',
'DifferentialAffectedPath' => 'applications/differential/storage/DifferentialAffectedPath.php',
'DifferentialAsanaRepresentationField' => 'applications/differential/customfield/DifferentialAsanaRepresentationField.php',
@ -380,10 +379,9 @@ phutil_register_library_map(array(
'DifferentialChangesetTwoUpTestRenderer' => 'applications/differential/render/DifferentialChangesetTwoUpTestRenderer.php',
'DifferentialChangesetViewController' => 'applications/differential/controller/DifferentialChangesetViewController.php',
'DifferentialCloseConduitAPIMethod' => 'applications/differential/conduit/DifferentialCloseConduitAPIMethod.php',
'DifferentialCommentPreviewController' => 'applications/differential/controller/DifferentialCommentPreviewController.php',
'DifferentialCommentSaveController' => 'applications/differential/controller/DifferentialCommentSaveController.php',
'DifferentialCommitMessageCustomField' => 'applications/differential/field/DifferentialCommitMessageCustomField.php',
'DifferentialCommitMessageField' => 'applications/differential/field/DifferentialCommitMessageField.php',
'DifferentialCommitMessageFieldTestCase' => 'applications/differential/field/__tests__/DifferentialCommitMessageFieldTestCase.php',
'DifferentialCommitMessageParser' => 'applications/differential/parser/DifferentialCommitMessageParser.php',
'DifferentialCommitMessageParserTestCase' => 'applications/differential/parser/__tests__/DifferentialCommitMessageParserTestCase.php',
'DifferentialCommitsField' => 'applications/differential/customfield/DifferentialCommitsField.php',
@ -459,7 +457,6 @@ phutil_register_library_map(array(
'DifferentialInlineComment' => 'applications/differential/storage/DifferentialInlineComment.php',
'DifferentialInlineCommentEditController' => 'applications/differential/controller/DifferentialInlineCommentEditController.php',
'DifferentialInlineCommentMailView' => 'applications/differential/mail/DifferentialInlineCommentMailView.php',
'DifferentialInlineCommentPreviewController' => 'applications/differential/controller/DifferentialInlineCommentPreviewController.php',
'DifferentialInlineCommentQuery' => 'applications/differential/query/DifferentialInlineCommentQuery.php',
'DifferentialJIRAIssuesCommitMessageField' => 'applications/differential/field/DifferentialJIRAIssuesCommitMessageField.php',
'DifferentialJIRAIssuesField' => 'applications/differential/customfield/DifferentialJIRAIssuesField.php',
@ -507,10 +504,15 @@ phutil_register_library_map(array(
'DifferentialReviewersHeraldAction' => 'applications/differential/herald/DifferentialReviewersHeraldAction.php',
'DifferentialReviewersView' => 'applications/differential/view/DifferentialReviewersView.php',
'DifferentialRevision' => 'applications/differential/storage/DifferentialRevision.php',
'DifferentialRevisionAbandonTransaction' => 'applications/differential/xaction/DifferentialRevisionAbandonTransaction.php',
'DifferentialRevisionAcceptTransaction' => 'applications/differential/xaction/DifferentialRevisionAcceptTransaction.php',
'DifferentialRevisionActionTransaction' => 'applications/differential/xaction/DifferentialRevisionActionTransaction.php',
'DifferentialRevisionAffectedFilesHeraldField' => 'applications/differential/herald/DifferentialRevisionAffectedFilesHeraldField.php',
'DifferentialRevisionAuthorHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorHeraldField.php',
'DifferentialRevisionAuthorProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php',
'DifferentialRevisionCloseDetailsController' => 'applications/differential/controller/DifferentialRevisionCloseDetailsController.php',
'DifferentialRevisionCloseTransaction' => 'applications/differential/xaction/DifferentialRevisionCloseTransaction.php',
'DifferentialRevisionCommandeerTransaction' => 'applications/differential/xaction/DifferentialRevisionCommandeerTransaction.php',
'DifferentialRevisionContentAddedHeraldField' => 'applications/differential/herald/DifferentialRevisionContentAddedHeraldField.php',
'DifferentialRevisionContentHeraldField' => 'applications/differential/herald/DifferentialRevisionContentHeraldField.php',
'DifferentialRevisionContentRemovedHeraldField' => 'applications/differential/herald/DifferentialRevisionContentRemovedHeraldField.php',
@ -540,14 +542,21 @@ phutil_register_library_map(array(
'DifferentialRevisionPHIDType' => 'applications/differential/phid/DifferentialRevisionPHIDType.php',
'DifferentialRevisionPackageHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageHeraldField.php',
'DifferentialRevisionPackageOwnerHeraldField' => 'applications/differential/herald/DifferentialRevisionPackageOwnerHeraldField.php',
'DifferentialRevisionPlanChangesTransaction' => 'applications/differential/xaction/DifferentialRevisionPlanChangesTransaction.php',
'DifferentialRevisionQuery' => 'applications/differential/query/DifferentialRevisionQuery.php',
'DifferentialRevisionReclaimTransaction' => 'applications/differential/xaction/DifferentialRevisionReclaimTransaction.php',
'DifferentialRevisionRejectTransaction' => 'applications/differential/xaction/DifferentialRevisionRejectTransaction.php',
'DifferentialRevisionRelationship' => 'applications/differential/relationships/DifferentialRevisionRelationship.php',
'DifferentialRevisionRelationshipSource' => 'applications/search/relationship/DifferentialRevisionRelationshipSource.php',
'DifferentialRevisionReopenTransaction' => 'applications/differential/xaction/DifferentialRevisionReopenTransaction.php',
'DifferentialRevisionRepositoryHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryHeraldField.php',
'DifferentialRevisionRepositoryProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionRepositoryProjectsHeraldField.php',
'DifferentialRevisionRepositoryTransaction' => 'applications/differential/xaction/DifferentialRevisionRepositoryTransaction.php',
'DifferentialRevisionRequestReviewTransaction' => 'applications/differential/xaction/DifferentialRevisionRequestReviewTransaction.php',
'DifferentialRevisionRequiredActionResultBucket' => 'applications/differential/query/DifferentialRevisionRequiredActionResultBucket.php',
'DifferentialRevisionResignTransaction' => 'applications/differential/xaction/DifferentialRevisionResignTransaction.php',
'DifferentialRevisionResultBucket' => 'applications/differential/query/DifferentialRevisionResultBucket.php',
'DifferentialRevisionReviewTransaction' => 'applications/differential/xaction/DifferentialRevisionReviewTransaction.php',
'DifferentialRevisionReviewersHeraldField' => 'applications/differential/herald/DifferentialRevisionReviewersHeraldField.php',
'DifferentialRevisionReviewersTransaction' => 'applications/differential/xaction/DifferentialRevisionReviewersTransaction.php',
'DifferentialRevisionSearchConduitAPIMethod' => 'applications/differential/conduit/DifferentialRevisionSearchConduitAPIMethod.php',
@ -1631,6 +1640,7 @@ phutil_register_library_map(array(
'PHUIDiffGraphViewTestCase' => 'infrastructure/diff/view/__tests__/PHUIDiffGraphViewTestCase.php',
'PHUIDiffInlineCommentDetailView' => 'infrastructure/diff/view/PHUIDiffInlineCommentDetailView.php',
'PHUIDiffInlineCommentEditView' => 'infrastructure/diff/view/PHUIDiffInlineCommentEditView.php',
'PHUIDiffInlineCommentPreviewListView' => 'infrastructure/diff/view/PHUIDiffInlineCommentPreviewListView.php',
'PHUIDiffInlineCommentRowScaffold' => 'infrastructure/diff/view/PHUIDiffInlineCommentRowScaffold.php',
'PHUIDiffInlineCommentTableScaffold' => 'infrastructure/diff/view/PHUIDiffInlineCommentTableScaffold.php',
'PHUIDiffInlineCommentUndoView' => 'infrastructure/diff/view/PHUIDiffInlineCommentUndoView.php',
@ -1842,6 +1852,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationsApplication' => 'applications/meta/application/PhabricatorApplicationsApplication.php',
'PhabricatorApplicationsController' => 'applications/meta/controller/PhabricatorApplicationsController.php',
'PhabricatorApplicationsListController' => 'applications/meta/controller/PhabricatorApplicationsListController.php',
'PhabricatorApplyEditField' => 'applications/transactions/editfield/PhabricatorApplyEditField.php',
'PhabricatorAsanaAuthProvider' => 'applications/auth/provider/PhabricatorAsanaAuthProvider.php',
'PhabricatorAsanaConfigOptions' => 'applications/doorkeeper/option/PhabricatorAsanaConfigOptions.php',
'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorAsanaSubtaskHasObjectEdgeType.php',
@ -2544,6 +2555,7 @@ phutil_register_library_map(array(
'PhabricatorEditEngineAPIMethod' => 'applications/transactions/editengine/PhabricatorEditEngineAPIMethod.php',
'PhabricatorEditEngineColumnsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineColumnsCommentAction.php',
'PhabricatorEditEngineCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineCommentAction.php',
'PhabricatorEditEngineCommentActionGroup' => 'applications/transactions/commentaction/PhabricatorEditEngineCommentActionGroup.php',
'PhabricatorEditEngineConfiguration' => 'applications/transactions/storage/PhabricatorEditEngineConfiguration.php',
'PhabricatorEditEngineConfigurationDefaultCreateController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultCreateController.php',
'PhabricatorEditEngineConfigurationDefaultsController' => 'applications/transactions/controller/PhabricatorEditEngineConfigurationDefaultsController.php',
@ -2570,10 +2582,12 @@ phutil_register_library_map(array(
'PhabricatorEditEngineExtensionModule' => 'applications/transactions/engineextension/PhabricatorEditEngineExtensionModule.php',
'PhabricatorEditEngineListController' => 'applications/transactions/controller/PhabricatorEditEngineListController.php',
'PhabricatorEditEnginePointsCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEnginePointsCommentAction.php',
'PhabricatorEditEngineProfileMenuItem' => 'applications/search/menuitem/PhabricatorEditEngineProfileMenuItem.php',
'PhabricatorEditEngineQuery' => 'applications/transactions/query/PhabricatorEditEngineQuery.php',
'PhabricatorEditEngineSearchEngine' => 'applications/transactions/query/PhabricatorEditEngineSearchEngine.php',
'PhabricatorEditEngineSelectCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineSelectCommentAction.php',
'PhabricatorEditEngineSettingsPanel' => 'applications/settings/panel/PhabricatorEditEngineSettingsPanel.php',
'PhabricatorEditEngineStaticCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineStaticCommentAction.php',
'PhabricatorEditEngineTokenizerCommentAction' => 'applications/transactions/commentaction/PhabricatorEditEngineTokenizerCommentAction.php',
'PhabricatorEditField' => 'applications/transactions/editfield/PhabricatorEditField.php',
'PhabricatorEditPage' => 'applications/transactions/editengine/PhabricatorEditPage.php',
@ -4982,7 +4996,6 @@ phutil_register_library_map(array(
'DarkConsoleXHProfPluginAPI' => 'Phobject',
'DifferentialAction' => 'Phobject',
'DifferentialActionEmailCommand' => 'MetaMTAEmailTransactionCommand',
'DifferentialAddCommentView' => 'AphrontView',
'DifferentialAdjustmentMapTestCase' => 'PhutilTestCase',
'DifferentialAffectedPath' => 'DifferentialDAO',
'DifferentialAsanaRepresentationField' => 'DifferentialCustomField',
@ -5017,10 +5030,9 @@ phutil_register_library_map(array(
'DifferentialChangesetTwoUpTestRenderer' => 'DifferentialChangesetTestRenderer',
'DifferentialChangesetViewController' => 'DifferentialController',
'DifferentialCloseConduitAPIMethod' => 'DifferentialConduitAPIMethod',
'DifferentialCommentPreviewController' => 'DifferentialController',
'DifferentialCommentSaveController' => 'DifferentialController',
'DifferentialCommitMessageCustomField' => 'DifferentialCommitMessageField',
'DifferentialCommitMessageField' => 'Phobject',
'DifferentialCommitMessageFieldTestCase' => 'PhabricatorTestCase',
'DifferentialCommitMessageParser' => 'Phobject',
'DifferentialCommitMessageParserTestCase' => 'PhabricatorTestCase',
'DifferentialCommitsField' => 'DifferentialCustomField',
@ -5047,6 +5059,7 @@ phutil_register_library_map(array(
'DifferentialDiff' => array(
'DifferentialDAO',
'PhabricatorPolicyInterface',
'PhabricatorExtendedPolicyInterface',
'HarbormasterBuildableInterface',
'HarbormasterCircleCIBuildableInterface',
'PhabricatorApplicationTransactionInterface',
@ -5109,7 +5122,6 @@ phutil_register_library_map(array(
),
'DifferentialInlineCommentEditController' => 'PhabricatorInlineCommentController',
'DifferentialInlineCommentMailView' => 'DifferentialMailView',
'DifferentialInlineCommentPreviewController' => 'PhabricatorInlineCommentPreviewController',
'DifferentialInlineCommentQuery' => 'PhabricatorOffsetPagedQuery',
'DifferentialJIRAIssuesCommitMessageField' => 'DifferentialCommitMessageCustomField',
'DifferentialJIRAIssuesField' => 'DifferentialStoredCustomField',
@ -5173,10 +5185,15 @@ phutil_register_library_map(array(
'PhabricatorFulltextInterface',
'PhabricatorConduitResultInterface',
),
'DifferentialRevisionAbandonTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionAcceptTransaction' => 'DifferentialRevisionReviewTransaction',
'DifferentialRevisionActionTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionAffectedFilesHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionAuthorHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionAuthorProjectsHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionCloseDetailsController' => 'DifferentialController',
'DifferentialRevisionCloseTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionCommandeerTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionContentAddedHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionContentHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionContentRemovedHeraldField' => 'DifferentialRevisionHeraldField',
@ -5206,14 +5223,21 @@ phutil_register_library_map(array(
'DifferentialRevisionPHIDType' => 'PhabricatorPHIDType',
'DifferentialRevisionPackageHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionPackageOwnerHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionPlanChangesTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'DifferentialRevisionReclaimTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionRejectTransaction' => 'DifferentialRevisionReviewTransaction',
'DifferentialRevisionRelationship' => 'PhabricatorObjectRelationship',
'DifferentialRevisionRelationshipSource' => 'PhabricatorObjectRelationshipSource',
'DifferentialRevisionReopenTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionRepositoryHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionRepositoryProjectsHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionRepositoryTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionRequestReviewTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionRequiredActionResultBucket' => 'DifferentialRevisionResultBucket',
'DifferentialRevisionResignTransaction' => 'DifferentialRevisionReviewTransaction',
'DifferentialRevisionResultBucket' => 'PhabricatorSearchResultBucket',
'DifferentialRevisionReviewTransaction' => 'DifferentialRevisionActionTransaction',
'DifferentialRevisionReviewersHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionReviewersTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
@ -6461,6 +6485,7 @@ phutil_register_library_map(array(
'PHUIDiffGraphViewTestCase' => 'PhabricatorTestCase',
'PHUIDiffInlineCommentDetailView' => 'PHUIDiffInlineCommentView',
'PHUIDiffInlineCommentEditView' => 'PHUIDiffInlineCommentView',
'PHUIDiffInlineCommentPreviewListView' => 'AphrontView',
'PHUIDiffInlineCommentRowScaffold' => 'AphrontView',
'PHUIDiffInlineCommentTableScaffold' => 'AphrontView',
'PHUIDiffInlineCommentUndoView' => 'PHUIDiffInlineCommentView',
@ -6692,6 +6717,7 @@ phutil_register_library_map(array(
'PhabricatorApplicationsApplication' => 'PhabricatorApplication',
'PhabricatorApplicationsController' => 'PhabricatorController',
'PhabricatorApplicationsListController' => 'PhabricatorApplicationsController',
'PhabricatorApplyEditField' => 'PhabricatorEditField',
'PhabricatorAsanaAuthProvider' => 'PhabricatorOAuth2AuthProvider',
'PhabricatorAsanaConfigOptions' => 'PhabricatorApplicationConfigOptions',
'PhabricatorAsanaSubtaskHasObjectEdgeType' => 'PhabricatorEdgeType',
@ -7515,6 +7541,7 @@ phutil_register_library_map(array(
'PhabricatorEditEngineAPIMethod' => 'ConduitAPIMethod',
'PhabricatorEditEngineColumnsCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEngineCommentAction' => 'Phobject',
'PhabricatorEditEngineCommentActionGroup' => 'Phobject',
'PhabricatorEditEngineConfiguration' => array(
'PhabricatorSearchDAO',
'PhabricatorApplicationTransactionInterface',
@ -7545,10 +7572,12 @@ phutil_register_library_map(array(
'PhabricatorEditEngineExtensionModule' => 'PhabricatorConfigModule',
'PhabricatorEditEngineListController' => 'PhabricatorEditEngineController',
'PhabricatorEditEnginePointsCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEngineProfileMenuItem' => 'PhabricatorProfileMenuItem',
'PhabricatorEditEngineQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorEditEngineSearchEngine' => 'PhabricatorApplicationSearchEngine',
'PhabricatorEditEngineSelectCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEngineSettingsPanel' => 'PhabricatorSettingsPanel',
'PhabricatorEditEngineStaticCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditEngineTokenizerCommentAction' => 'PhabricatorEditEngineCommentAction',
'PhabricatorEditField' => 'Phobject',
'PhabricatorEditPage' => 'Phobject',
@ -7688,7 +7717,7 @@ phutil_register_library_map(array(
'PhabricatorFileImageTransform' => 'PhabricatorFileTransform',
'PhabricatorFileInfoController' => 'PhabricatorFileController',
'PhabricatorFileLightboxController' => 'PhabricatorFileController',
'PhabricatorFileLinkView' => 'AphrontView',
'PhabricatorFileLinkView' => 'AphrontTagView',
'PhabricatorFileListController' => 'PhabricatorFileController',
'PhabricatorFileQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
'PhabricatorFileROT13StorageFormat' => 'PhabricatorFileStorageFormat',

View file

@ -548,7 +548,31 @@ final class AphrontRequest extends Phobject {
public function getAbsoluteRequestURI() {
$uri = $this->getRequestURI();
$uri->setDomain($this->getHost());
$uri->setProtocol($this->isHTTPS() ? 'https' : 'http');
if ($this->isHTTPS()) {
$protocol = 'https';
} else {
$protocol = 'http';
}
$uri->setProtocol($protocol);
// If the request used a nonstandard port, preserve it while building the
// absolute URI.
// First, get the default port for the request protocol.
$default_port = id(new PhutilURI($protocol.'://example.com/'))
->getPortWithProtocolDefault();
// NOTE: See note in getHost() about malicious "Host" headers. This
// construction defuses some obscure potential attacks.
$port = id(new PhutilURI($protocol.'://'.$this->host))
->getPort();
if (($port !== null) && ($port !== $default_port)) {
$uri->setPort($port);
}
return $uri;
}

View file

@ -429,8 +429,8 @@ abstract class PhabricatorApplication
}
$cache = PhabricatorCaches::getRequestCache();
$viewer_phid = $viewer->getPHID();
$key = 'app.'.$class.'.installed.'.$viewer_phid;
$viewer_fragment = $viewer->getCacheFragment();
$key = 'app.'.$class.'.installed.'.$viewer_fragment;
$result = $cache->getKey($key);
if ($result === null) {

View file

@ -23,6 +23,14 @@ abstract class PhabricatorCalendarEventDateTransaction
}
public function getTransactionHasEffect($object, $old, $new) {
// If either value is `null` (for example, when setting a recurring event
// end date for the first time) and the other value is not `null`, this
// transaction has an effect.
$has_null = (($old === null) || ($new === null));
if ($has_null) {
return ($old !== $new);
}
$editor = $this->getEditor();
$actor = $this->getActor();

View file

@ -55,65 +55,28 @@ final class DifferentialActionEmailCommand
}
public function getCommandObjects() {
$actions = array(
DifferentialAction::ACTION_REJECT => 'request',
DifferentialAction::ACTION_ABANDON => 'abandon',
DifferentialAction::ACTION_RECLAIM => 'reclaim',
DifferentialAction::ACTION_RESIGN => 'resign',
DifferentialAction::ACTION_RETHINK => 'planchanges',
DifferentialAction::ACTION_CLAIM => 'commandeer',
);
if (PhabricatorEnv::getEnvConfig('differential.enable-email-accept')) {
$actions[DifferentialAction::ACTION_ACCEPT] = 'accept';
}
$aliases = array(
DifferentialAction::ACTION_REJECT => array('reject'),
DifferentialAction::ACTION_CLAIM => array('claim'),
DifferentialAction::ACTION_RETHINK => array('rethink'),
);
$summaries = array(
DifferentialAction::ACTION_REJECT =>
pht('Request changes to a revision.'),
DifferentialAction::ACTION_ABANDON =>
pht('Abandon a revision.'),
DifferentialAction::ACTION_RECLAIM =>
pht('Reclaim a revision.'),
DifferentialAction::ACTION_RESIGN =>
pht('Resign from a revision.'),
DifferentialAction::ACTION_RETHINK =>
pht('Plan changes to a revision.'),
DifferentialAction::ACTION_CLAIM =>
pht('Commandeer a revision.'),
DifferentialAction::ACTION_ACCEPT =>
pht('Accept a revision.'),
);
$descriptions = array(
);
$actions = DifferentialRevisionActionTransaction::loadAllActions();
$actions = msort($actions, 'getRevisionActionOrderVector');
$objects = array();
foreach ($actions as $action => $keyword) {
$object = id(new DifferentialActionEmailCommand())
foreach ($actions as $action) {
$keyword = $action->getCommandKeyword();
if ($keyword === null) {
continue;
}
$aliases = $action->getCommandAliases();
$summary = $action->getCommandSummary();
$object = id(new self())
->setCommand($keyword)
->setAction($action)
->setCommandSummary($summaries[$action]);
if (isset($aliases[$action])) {
$object->setCommandAliases($aliases[$action]);
}
if (isset($descriptions[$action])) {
$object->setCommandDescription($descriptions[$action]);
}
->setCommandAliases($aliases)
->setAction($action->getTransactionTypeConstant())
->setCommandSummary($summary);
$objects[] = $object;
}
return $objects;
}
@ -131,8 +94,8 @@ final class DifferentialActionEmailCommand
$xactions = array();
$xactions[] = $object->getApplicationTransactionTemplate()
->setTransactionType(DifferentialTransaction::TYPE_ACTION)
->setNewValue($this->getAction());
->setTransactionType($this->getAction())
->setNewValue(true);
return $xactions;
}

View file

@ -9,6 +9,7 @@ final class DifferentialReviewerStatus extends Phobject {
const STATUS_COMMENTED = 'commented';
const STATUS_ACCEPTED_OLDER = 'accepted-older';
const STATUS_REJECTED_OLDER = 'rejected-older';
const STATUS_RESIGNED = 'resigned';
/**
* Returns the relative strength of a status, used to pick a winner when a
@ -34,6 +35,7 @@ final class DifferentialReviewerStatus extends Phobject {
self::STATUS_ACCEPTED => 5,
self::STATUS_REJECTED => 5,
self::STATUS_RESIGNED => 5,
);
return idx($map, $constant, 0);

View file

@ -1,139 +0,0 @@
<?php
final class DifferentialCommentPreviewController
extends DifferentialController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$revision = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withIDs(array($id))
->executeOne();
if (!$revision) {
return new Aphront404Response();
}
$type_comment = PhabricatorTransactions::TYPE_COMMENT;
$type_action = DifferentialTransaction::TYPE_ACTION;
$type_edge = PhabricatorTransactions::TYPE_EDGE;
$type_subscribers = PhabricatorTransactions::TYPE_SUBSCRIBERS;
$xactions = array();
$action = $request->getStr('action');
switch ($action) {
case DifferentialAction::ACTION_COMMENT:
case DifferentialAction::ACTION_ADDREVIEWERS:
case DifferentialAction::ACTION_ADDCCS:
break;
default:
$xactions[] = id(new DifferentialTransaction())
->setTransactionType($type_action)
->setNewValue($action);
break;
}
$edge_reviewer = DifferentialRevisionHasReviewerEdgeType::EDGECONST;
$reviewers = $request->getStrList('reviewers');
if (DifferentialAction::allowReviewers($action) && $reviewers) {
$faux_edges = array();
foreach ($reviewers as $phid) {
$faux_edges[$phid] = array(
'src' => $revision->getPHID(),
'type' => $edge_reviewer,
'dst' => $phid,
);
}
$xactions[] = id(new DifferentialTransaction())
->setTransactionType($type_edge)
->setMetadataValue('edge:type', $edge_reviewer)
->setOldValue(array())
->setNewValue($faux_edges);
}
$ccs = $request->getStrList('ccs');
if ($ccs) {
$xactions[] = id(new DifferentialTransaction())
->setTransactionType($type_subscribers)
->setOldValue(array())
->setNewValue(array_fuse($ccs));
}
// Add a comment transaction if there's nothing, so we'll generate a
// nonempty result.
if (strlen($request->getStr('content')) || !$xactions) {
$xactions[] = id(new DifferentialTransaction())
->setTransactionType($type_comment)
->attachComment(
id(new ManiphestTransactionComment())
->setContent($request->getStr('content')));
}
foreach ($xactions as $xaction) {
$xaction->setAuthorPHID($viewer->getPHID());
}
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($request->getUser());
foreach ($xactions as $xaction) {
if ($xaction->hasComment()) {
$engine->addObject(
$xaction->getComment(),
PhabricatorApplicationTransactionComment::MARKUP_FIELD_COMMENT);
}
}
$engine->process();
$phids = mpull($xactions, 'getRequiredHandlePHIDs');
$phids = array_mergev($phids);
$handles = id(new PhabricatorHandleQuery())
->setViewer($viewer)
->withPHIDs($phids)
->execute();
foreach ($xactions as $xaction) {
$xaction->setHandles($handles);
}
$view = id(new DifferentialTransactionView())
->setUser($viewer)
->setTransactions($xactions)
->setIsPreview(true);
$metadata = array(
'reviewers' => $reviewers,
'ccs' => $ccs,
);
if ($action != DifferentialAction::ACTION_COMMENT) {
$metadata['action'] = $action;
}
$draft_key = 'differential-comment-'.$id;
$draft = id(new PhabricatorDraft())
->setAuthorPHID($viewer->getPHID())
->setDraftKey($draft_key)
->setDraft($request->getStr('content'))
->setMetadata($metadata)
->replaceOrDelete();
if ($draft->isDeleted()) {
DifferentialDraft::deleteHasDraft(
$viewer->getPHID(),
$revision->getPHID(),
$draft_key);
} else {
DifferentialDraft::markHasDraft(
$viewer->getPHID(),
$revision->getPHID(),
$draft_key);
}
return id(new AphrontAjaxResponse())
->setContent((string)phutil_implode_html('', $view->buildEvents()));
}
}

View file

@ -1,143 +0,0 @@
<?php
final class DifferentialCommentSaveController
extends DifferentialController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
if (!$request->isFormPost()) {
return new Aphront400Response();
}
$revision = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withIDs(array($id))
->needReviewerStatus(true)
->needReviewerAuthority(true)
->executeOne();
if (!$revision) {
return new Aphront404Response();
}
$type_action = DifferentialTransaction::TYPE_ACTION;
$type_subscribers = PhabricatorTransactions::TYPE_SUBSCRIBERS;
$type_edge = PhabricatorTransactions::TYPE_EDGE;
$type_comment = PhabricatorTransactions::TYPE_COMMENT;
$type_inline = DifferentialTransaction::TYPE_INLINE;
$edge_reviewer = DifferentialRevisionHasReviewerEdgeType::EDGECONST;
$xactions = array();
$action = $request->getStr('action');
switch ($action) {
case DifferentialAction::ACTION_COMMENT:
case DifferentialAction::ACTION_ADDREVIEWERS:
case DifferentialAction::ACTION_ADDCCS:
// These transaction types have no direct effect, they just
// accompany other transaction types which can have an effect.
break;
default:
$xactions[] = id(new DifferentialTransaction())
->setTransactionType($type_action)
->setNewValue($request->getStr('action'));
break;
}
$ccs = $request->getArr('ccs');
if ($ccs) {
$xactions[] = id(new DifferentialTransaction())
->setTransactionType($type_subscribers)
->setNewValue(array('+' => $ccs));
}
$current_reviewers = mpull(
$revision->getReviewerStatus(),
null,
'getReviewerPHID');
$reviewer_edges = array();
$add_reviewers = $request->getArr('reviewers');
foreach ($add_reviewers as $reviewer_phid) {
if (isset($current_reviewers[$reviewer_phid])) {
continue;
}
$reviewer = new DifferentialReviewerProxy(
$reviewer_phid,
array(
'status' => DifferentialReviewerStatus::STATUS_ADDED,
));
$reviewer_edges[$reviewer_phid] = array(
'data' => $reviewer->getEdgeData(),
);
}
if ($add_reviewers) {
$xactions[] = id(new DifferentialTransaction())
->setTransactionType($type_edge)
->setMetadataValue('edge:type', $edge_reviewer)
->setNewValue(array('+' => $reviewer_edges));
}
$inlines = DifferentialTransactionQuery::loadUnsubmittedInlineComments(
$viewer,
$revision);
foreach ($inlines as $inline) {
$xactions[] = id(new DifferentialTransaction())
->setTransactionType($type_inline)
->attachComment($inline);
}
// NOTE: If there are no other transactions, add an empty comment
// transaction so that we'll raise a more user-friendly error message,
// to the effect of "you can not post an empty comment".
$no_xactions = !$xactions;
$comment = $request->getStr('comment');
if (strlen($comment) || $no_xactions) {
$xactions[] = id(new DifferentialTransaction())
->setTransactionType($type_comment)
->attachComment(
id(new DifferentialTransactionComment())
->setRevisionPHID($revision->getPHID())
->setContent($comment));
}
$editor = id(new DifferentialTransactionEditor())
->setActor($viewer)
->setContentSourceFromRequest($request)
->setContinueOnMissingFields(true)
->setContinueOnNoEffect($request->isContinueRequest());
$revision_uri = '/D'.$revision->getID();
try {
$editor->applyTransactions($revision, $xactions);
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
return id(new PhabricatorApplicationTransactionNoEffectResponse())
->setCancelURI($revision_uri)
->setException($ex);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
return id(new PhabricatorApplicationTransactionValidationResponse())
->setCancelURI($revision_uri)
->setException($ex);
}
$user = $request->getUser();
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
'differential-comment-'.$revision->getID());
if ($draft) {
$draft->delete();
}
DifferentialDraft::deleteAllDrafts($user->getPHID(), $revision->getPHID());
return id(new AphrontRedirectResponse())
->setURI('/D'.$revision->getID());
}
}

View file

@ -1,44 +0,0 @@
<?php
final class DifferentialInlineCommentPreviewController
extends PhabricatorInlineCommentPreviewController {
protected function loadInlineComments() {
$viewer = $this->getViewer();
$revision = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withIDs(array($this->getRevisionID()))
->executeOne();
if (!$revision) {
return array();
}
return id(new DifferentialInlineCommentQuery())
->setViewer($this->getViewer())
->withDrafts(true)
->withAuthorPHIDs(array($viewer->getPHID()))
->withRevisionPHIDs(array($revision->getPHID()))
->needHidden(true)
->execute();
}
protected function loadObjectOwnerPHID() {
$viewer = $this->getViewer();
$revision = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withIDs(array($this->getRevisionID()))
->executeOne();
if (!$revision) {
return null;
}
return $revision->getAuthorPHID();
}
private function getRevisionID() {
return $this->getRequest()->getURIData('id');
}
}

View file

@ -16,18 +16,17 @@ final class DifferentialRevisionViewController extends DifferentialController {
$revision = id(new DifferentialRevisionQuery())
->withIDs(array($this->revisionID))
->setViewer($request->getUser())
->setViewer($viewer)
->needRelationships(true)
->needReviewerStatus(true)
->needReviewerAuthority(true)
->executeOne();
if (!$revision) {
return new Aphront404Response();
}
$diffs = id(new DifferentialDiffQuery())
->setViewer($request->getUser())
->setViewer($viewer)
->withRevisionIDs(array($this->revisionID))
->execute();
$diffs = array_reverse($diffs, $preserve_keys = true);
@ -255,17 +254,14 @@ final class DifferentialRevisionViewController extends DifferentialController {
$target,
$revision);
$comment_view = $this->buildTransactions(
$timeline = $this->buildTransactions(
$revision,
$diff_vs ? $diffs[$diff_vs] : $target,
$target,
$old_ids,
$new_ids);
if (!$viewer_is_anonymous) {
$comment_view->setQuoteRef('D'.$revision->getID());
$comment_view->setQuoteTargetID('comment-content');
}
$timeline->setQuoteRef($revision->getMonogram());
$changeset_view = id(new DifferentialChangesetListView())
->setChangesets($changesets)
@ -390,11 +386,6 @@ final class DifferentialRevisionViewController extends DifferentialController {
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->addTabGroup($tab_group);
$comment_form = null;
if (!$viewer_is_anonymous) {
$comment_form = $this->buildCommentForm($revision, $field_list);
}
$signatures = DifferentialRequiredSignaturesField::loadForRevision(
$revision);
$missing_signatures = false;
@ -426,20 +417,33 @@ final class DifferentialRevisionViewController extends DifferentialController {
);
}
if ($comment_form) {
$footer[] = $comment_form;
} else {
// TODO: For now, just use this to get "Login to Comment".
$footer[] = id(new PhabricatorApplicationTransactionCommentView())
->setUser($viewer)
->setRequestURI($request->getRequestURI());
$comment_view = id(new DifferentialRevisionEditEngine())
->setViewer($viewer)
->buildEditEngineCommentView($revision);
$comment_view->setTransactionTimeline($timeline);
$review_warnings = array();
foreach ($field_list->getFields() as $field) {
$review_warnings[] = $field->getWarningsForDetailView();
}
$review_warnings = array_mergev($review_warnings);
if ($review_warnings) {
$warnings_view = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors($review_warnings);
$comment_view->setInfoView($warnings_view);
}
$object_id = 'D'.$revision->getID();
$footer[] = $comment_view;
$monogram = $revision->getMonogram();
$operations_box = $this->buildOperationsBox($revision);
$crumbs = $this->buildApplicationCrumbs();
$crumbs->addTextCrumb($object_id, '/'.$object_id);
$crumbs->addTextCrumb($monogram, $revision->getURI());
$crumbs->setBorder(true);
$filetree_on = $viewer->compareUserSetting(
@ -452,39 +456,32 @@ final class DifferentialRevisionViewController extends DifferentialController {
$collapsed_value = $viewer->getUserSetting($collapsed_key);
$nav = id(new DifferentialChangesetFileTreeSideNavBuilder())
->setTitle('D'.$revision->getID())
->setBaseURI(new PhutilURI('/D'.$revision->getID()))
->setTitle($monogram)
->setBaseURI(new PhutilURI($revision->getURI()))
->setCollapsed((bool)$collapsed_value)
->build($changesets);
}
// Haunt Mode
$pane_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'differential-keyboard-navigation',
array(
'haunt' => $pane_id,
));
Javelin::initBehavior('differential-user-select');
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setSubheader($subheader)
->setCurtain($curtain)
->setID($pane_id)
->setMainColumn(array(
$operations_box,
$info_view,
$details,
$diff_detail_box,
$unit_box,
$comment_view,
$signature_message,
))
->setMainColumn(
array(
$operations_box,
$info_view,
$details,
$diff_detail_box,
$unit_box,
$timeline,
$signature_message,
))
->setFooter($footer);
$page = $this->newPage()
->setTitle($object_id.' '.$revision->getTitle())
->setTitle($monogram.' '.$revision->getTitle())
->setCrumbs($crumbs)
->setPageObjectPHIDs(array($revision->getPHID()))
->appendChild($view);
@ -613,172 +610,6 @@ final class DifferentialRevisionViewController extends DifferentialController {
return $curtain;
}
private function buildCommentForm(
DifferentialRevision $revision,
$field_list) {
$viewer = $this->getViewer();
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$viewer->getPHID(),
'differential-comment-'.$revision->getID());
$reviewers = array();
$ccs = array();
if ($draft) {
$reviewers = idx($draft->getMetadata(), 'reviewers', array());
$ccs = idx($draft->getMetadata(), 'ccs', array());
if ($reviewers || $ccs) {
$handles = $this->loadViewerHandles(array_merge($reviewers, $ccs));
$reviewers = array_select_keys($handles, $reviewers);
$ccs = array_select_keys($handles, $ccs);
}
}
$comment_form = id(new DifferentialAddCommentView())
->setRevision($revision);
$review_warnings = array();
foreach ($field_list->getFields() as $field) {
$review_warnings[] = $field->getWarningsForDetailView();
}
$review_warnings = array_mergev($review_warnings);
if ($review_warnings) {
$review_warnings_panel = id(new PHUIInfoView())
->setSeverity(PHUIInfoView::SEVERITY_WARNING)
->setErrors($review_warnings);
$comment_form->setInfoView($review_warnings_panel);
}
$action_uri = $this->getApplicationURI(
'comment/save/'.$revision->getID().'/');
$comment_form->setActions($this->getRevisionCommentActions($revision))
->setActionURI($action_uri)
->setUser($viewer)
->setDraft($draft)
->setReviewers(mpull($reviewers, 'getFullName', 'getPHID'))
->setCCs(mpull($ccs, 'getFullName', 'getPHID'));
// TODO: This just makes the "Z" key work. Generalize this and remove
// it at some point.
$comment_form = phutil_tag(
'div',
array(
'class' => 'differential-add-comment-panel',
),
$comment_form);
return $comment_form;
}
private function getRevisionCommentActions(DifferentialRevision $revision) {
$actions = array(
DifferentialAction::ACTION_COMMENT => true,
);
$viewer = $this->getViewer();
$viewer_phid = $viewer->getPHID();
$viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID());
$viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
$status = $revision->getStatus();
$viewer_has_accepted = false;
$viewer_has_rejected = false;
$status_accepted = DifferentialReviewerStatus::STATUS_ACCEPTED;
$status_rejected = DifferentialReviewerStatus::STATUS_REJECTED;
foreach ($revision->getReviewerStatus() as $reviewer) {
if ($reviewer->getReviewerPHID() == $viewer_phid) {
if ($reviewer->getStatus() == $status_accepted) {
$viewer_has_accepted = true;
}
if ($reviewer->getStatus() == $status_rejected) {
$viewer_has_rejected = true;
}
break;
}
}
$allow_self_accept = PhabricatorEnv::getEnvConfig(
'differential.allow-self-accept');
$always_allow_abandon = PhabricatorEnv::getEnvConfig(
'differential.always-allow-abandon');
$always_allow_close = PhabricatorEnv::getEnvConfig(
'differential.always-allow-close');
$allow_reopen = PhabricatorEnv::getEnvConfig(
'differential.allow-reopen');
if ($viewer_is_owner) {
switch ($status) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
$actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept;
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_RETHINK] = true;
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED:
$actions[DifferentialAction::ACTION_ACCEPT] = $allow_self_accept;
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_REQUEST] = true;
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_REQUEST] = true;
$actions[DifferentialAction::ACTION_RETHINK] = true;
$actions[DifferentialAction::ACTION_CLOSE] = true;
break;
case ArcanistDifferentialRevisionStatus::CLOSED:
break;
case ArcanistDifferentialRevisionStatus::ABANDONED:
$actions[DifferentialAction::ACTION_RECLAIM] = true;
break;
}
} else {
switch ($status) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
$actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon;
$actions[DifferentialAction::ACTION_ACCEPT] = true;
$actions[DifferentialAction::ACTION_REJECT] = true;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
case ArcanistDifferentialRevisionStatus::CHANGES_PLANNED:
$actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon;
$actions[DifferentialAction::ACTION_ACCEPT] = true;
$actions[DifferentialAction::ACTION_REJECT] = !$viewer_has_rejected;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$actions[DifferentialAction::ACTION_ABANDON] = $always_allow_abandon;
$actions[DifferentialAction::ACTION_ACCEPT] = !$viewer_has_accepted;
$actions[DifferentialAction::ACTION_REJECT] = true;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::CLOSED:
case ArcanistDifferentialRevisionStatus::ABANDONED:
break;
}
if ($status != ArcanistDifferentialRevisionStatus::CLOSED) {
$actions[DifferentialAction::ACTION_CLAIM] = true;
$actions[DifferentialAction::ACTION_CLOSE] = $always_allow_close;
}
}
$actions[DifferentialAction::ACTION_ADDREVIEWERS] = true;
$actions[DifferentialAction::ACTION_ADDCCS] = true;
$actions[DifferentialAction::ACTION_REOPEN] = $allow_reopen &&
($status == ArcanistDifferentialRevisionStatus::CLOSED);
$actions = array_keys(array_filter($actions));
$actions_dict = array();
foreach ($actions as $action) {
$actions_dict[$action] = DifferentialAction::getActionVerb($action);
}
return $actions_dict;
}
private function loadHistoryDiffStatus(array $diffs) {
assert_instances_of($diffs, 'DifferentialDiff');

View file

@ -32,9 +32,31 @@ final class DifferentialUnitField
}
public function getWarningsForDetailView() {
$status = $this->getObject()->getActiveDiff()->getUnitStatus();
$warnings = array();
$viewer = $this->getViewer();
$diff = $this->getObject()->getActiveDiff();
$buildable = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withBuildablePHIDs(array($diff->getPHID()))
->withManualBuildables(false)
->executeOne();
if ($buildable) {
switch ($buildable->getBuildableStatus()) {
case HarbormasterBuildable::STATUS_BUILDING:
$warnings[] = pht(
'These changes have not finished building yet and may have build '.
'failures.');
break;
case HarbormasterBuildable::STATUS_FAILED:
$warnings[] = pht(
'These changes have failed to build.');
break;
}
}
$status = $this->getObject()->getActiveDiff()->getUnitStatus();
if ($status < DifferentialUnitStatus::UNIT_WARN) {
// Don't show any warnings.
} else if ($status == DifferentialUnitStatus::UNIT_AUTO_SKIP) {

View file

@ -9,6 +9,9 @@ final class DifferentialRevisionEditEngine
const KEY_UPDATE = 'update';
const ACTIONGROUP_REVIEW = 'review';
const ACTIONGROUP_REVISION = 'revision';
public function getEngineName() {
return pht('Revisions');
}
@ -38,7 +41,8 @@ final class DifferentialRevisionEditEngine
protected function newObjectQuery() {
return id(new DifferentialRevisionQuery())
->needActiveDiffs(true)
->needReviewerStatus(true);
->needReviewerStatus(true)
->needReviewerAuthority(true);
}
protected function getObjectCreateTitleText($object) {
@ -73,6 +77,10 @@ final class DifferentialRevisionEditEngine
return $object->getURI();
}
protected function getEditorURI() {
return $this->getApplication()->getApplicationURI('revision/edit/');
}
public function setDiff(DifferentialDiff $diff) {
$this->diff = $diff;
return $this;
@ -82,7 +90,19 @@ final class DifferentialRevisionEditEngine
return $this->diff;
}
protected function newCommentActionGroups() {
return array(
id(new PhabricatorEditEngineCommentActionGroup())
->setKey(self::ACTIONGROUP_REVIEW)
->setLabel(pht('Review Actions')),
id(new PhabricatorEditEngineCommentActionGroup())
->setKey(self::ACTIONGROUP_REVISION)
->setLabel(pht('Revision Actions')),
);
}
protected function buildCustomEditFields($object) {
$viewer = $this->getViewer();
$plan_required = PhabricatorEnv::getEnvConfig(
'differential.require-test-plan-field');
@ -176,6 +196,7 @@ final class DifferentialRevisionEditEngine
->setUseEdgeTransactions(true)
->setTransactionType(
DifferentialRevisionReviewersTransaction::TRANSACTIONTYPE)
->setCommentActionLabel(pht('Change Reviewers'))
->setDescription(pht('Reviewers for this revision.'))
->setConduitDescription(pht('Change the reviewers for this revision.'))
->setConduitTypeDescription(pht('New reviewers.'))
@ -207,6 +228,13 @@ final class DifferentialRevisionEditEngine
->setConduitTypeDescription(pht('List of tasks.'))
->setValue(array());
$actions = DifferentialRevisionActionTransaction::loadAllActions();
$actions = msortv($actions, 'getRevisionActionOrderVector');
foreach ($actions as $key => $action) {
$fields[] = $action->newEditField($object, $viewer);
}
return $fields;
}
@ -219,4 +247,77 @@ final class DifferentialRevisionEditEngine
return isset($fields[$key]);
}
protected function newAutomaticCommentTransactions($object) {
$viewer = $this->getViewer();
$xactions = array();
$inlines = DifferentialTransactionQuery::loadUnsubmittedInlineComments(
$viewer,
$object);
$inlines = msort($inlines, 'getID');
foreach ($inlines as $inline) {
$xactions[] = id(new DifferentialTransaction())
->setTransactionType(DifferentialTransaction::TYPE_INLINE)
->attachComment($inline);
}
$viewer_phid = $viewer->getPHID();
$viewer_is_author = ($object->getAuthorPHID() == $viewer_phid);
if ($viewer_is_author) {
$state_map = PhabricatorTransactions::getInlineStateMap();
$inlines = id(new DifferentialDiffInlineCommentQuery())
->setViewer($viewer)
->withRevisionPHIDs(array($object->getPHID()))
->withFixedStates(array_keys($state_map))
->execute();
if ($inlines) {
$old_value = mpull($inlines, 'getFixedState', 'getPHID');
$new_value = array();
foreach ($old_value as $key => $state) {
$new_value[$key] = $state_map[$state];
}
$xactions[] = id(new DifferentialTransaction())
->setTransactionType(PhabricatorTransactions::TYPE_INLINESTATE)
->setIgnoreOnNoEffect(true)
->setOldValue($old_value)
->setNewValue($new_value);
}
}
return $xactions;
}
protected function newCommentPreviewContent($object, array $xactions) {
$viewer = $this->getViewer();
$type_inline = DifferentialTransaction::TYPE_INLINE;
$inlines = array();
foreach ($xactions as $xaction) {
if ($xaction->getTransactionType() === $type_inline) {
$inlines[] = $xaction->getComment();
}
}
$content = array();
if ($inlines) {
$inline_preview = id(new PHUIDiffInlineCommentPreviewListView())
->setViewer($viewer)
->setInlineComments($inlines);
$content[] = phutil_tag(
'div',
array(
'id' => 'inline-comment-preview',
),
$inline_preview);
}
return $content;
}
}

View file

@ -7,6 +7,7 @@ final class DifferentialTransactionEditor
private $isCloseByCommit;
private $repositoryPHIDOverride = false;
private $didExpandInlineState = false;
private $hasReviewTransaction = false;
private $affectedPaths;
public function getEditorApplicationClass() {
@ -58,6 +59,7 @@ final class DifferentialTransactionEditor
$types[] = PhabricatorTransactions::TYPE_COMMENT;
$types[] = PhabricatorTransactions::TYPE_VIEW_POLICY;
$types[] = PhabricatorTransactions::TYPE_EDIT_POLICY;
$types[] = PhabricatorTransactions::TYPE_INLINESTATE;
$types[] = DifferentialTransaction::TYPE_ACTION;
$types[] = DifferentialTransaction::TYPE_INLINE;
@ -256,6 +258,30 @@ final class DifferentialTransactionEditor
return parent::applyCustomInternalTransaction($object, $xaction);
}
protected function expandTransactions(
PhabricatorLiskDAO $object,
array $xactions) {
foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_INLINESTATE:
// If we have an "Inline State" transaction already, the caller
// built it for us so we don't need to expand it again.
$this->didExpandInlineState = true;
break;
case DifferentialRevisionAcceptTransaction::TRANSACTIONTYPE:
case DifferentialRevisionRejectTransaction::TRANSACTIONTYPE:
case DifferentialRevisionResignTransaction::TRANSACTIONTYPE:
// If we have a review transaction, we'll skip marking the user
// as "Commented" later. This should get cleaner after T10967.
$this->hasReviewTransaction = true;
break;
}
}
return parent::expandTransactions($object, $xactions);
}
protected function expandTransaction(
PhabricatorLiskDAO $object,
PhabricatorApplicationTransaction $xaction) {
@ -288,16 +314,26 @@ final class DifferentialTransactionEditor
$downgrade_accepts = true;
}
break;
case DifferentialRevisionRequestReviewTransaction::TRANSACTIONTYPE:
$downgrade_rejects = true;
if ((!$is_sticky_accept) ||
($object->getStatus() != $status_plan)) {
// If the old state isn't "changes planned", downgrade the accepts.
// This exception allows an accepted revision to go through
// "Plan Changes" -> "Request Review" and return to "accepted" if
// the author didn't update the revision, essentially undoing the
// "Plan Changes".
$downgrade_accepts = true;
}
break;
// TODO: Remove this, obsoleted by ModularTransactions above.
case DifferentialTransaction::TYPE_ACTION:
switch ($xaction->getNewValue()) {
case DifferentialAction::ACTION_REQUEST:
$downgrade_rejects = true;
if ((!$is_sticky_accept) ||
($object->getStatus() != $status_plan)) {
// If the old state isn't "changes planned", downgrade the
// accepts. This exception allows an accepted revision to
// go through Plan Changes -> Request Review to return to
// "accepted" if the author didn't update the revision.
$downgrade_accepts = true;
}
break;
@ -353,6 +389,7 @@ final class DifferentialTransactionEditor
}
}
$is_commandeer = false;
switch ($xaction->getTransactionType()) {
case DifferentialTransaction::TYPE_UPDATE:
if ($this->getIsCloseByCommit()) {
@ -397,6 +434,11 @@ final class DifferentialTransactionEditor
// "added" to "commented" if they're also a reviewer. We may further
// upgrade this based on other actions in the transaction group.
if ($this->hasReviewTransaction) {
// If we're also applying a review transaction, skip this.
break;
}
$status_added = DifferentialReviewerStatus::STATUS_ADDED;
$status_commented = DifferentialReviewerStatus::STATUS_COMMENTED;
@ -424,6 +466,10 @@ final class DifferentialTransactionEditor
}
break;
case DifferentialRevisionCommandeerTransaction::TRANSACTIONTYPE:
$is_commandeer = true;
break;
case DifferentialTransaction::TYPE_ACTION:
$action_type = $xaction->getNewValue();
@ -463,41 +509,7 @@ final class DifferentialTransactionEditor
break;
case DifferentialAction::ACTION_CLAIM:
// If the user is commandeering, add the previous owner as a
// reviewer and remove the actor.
$edits = array(
'-' => array(
$actor_phid => $actor_phid,
),
);
$owner_phid = $object->getAuthorPHID();
if ($owner_phid) {
$reviewer = new DifferentialReviewerProxy(
$owner_phid,
array(
'status' => DifferentialReviewerStatus::STATUS_ADDED,
));
$edits['+'] = array(
$owner_phid => array(
'data' => $reviewer->getEdgeData(),
),
);
}
// NOTE: We're setting setIsCommandeerSideEffect() on this because
// normally you can't add a revision's author as a reviewer, but
// this action swaps them after validation executes.
$results[] = id(new DifferentialTransaction())
->setTransactionType($type_edge)
->setMetadataValue('edge:type', $edge_reviewer)
->setIgnoreOnNoEffect(true)
->setIsCommandeerSideEffect(true)
->setNewValue($edits);
$is_commandeer = true;
break;
case DifferentialAction::ACTION_RESIGN:
// If the user is resigning, add a separate reviewer edit
@ -519,6 +531,10 @@ final class DifferentialTransactionEditor
break;
}
if ($is_commandeer) {
$results[] = $this->newCommandeerReviewerTransaction($object);
}
if (!$this->didExpandInlineState) {
switch ($xaction->getTransactionType()) {
case PhabricatorTransactions::TYPE_COMMENT:
@ -1499,6 +1515,10 @@ final class DifferentialTransactionEditor
return true;
}
break;
case DifferentialRevisionCommandeerTransaction::TRANSACTIONTYPE:
// When users commandeer revisions, we may need to trigger
// signatures or author-based rules.
return true;
case DifferentialTransaction::TYPE_ACTION:
switch ($xaction->getNewValue()) {
case DifferentialAction::ACTION_CLAIM:
@ -1922,4 +1942,35 @@ final class DifferentialTransactionEditor
return $this;
}
private function newCommandeerReviewerTransaction(
DifferentialRevision $revision) {
$actor_phid = $this->getActingAsPHID();
$owner_phid = $revision->getAuthorPHID();
// If the user is commandeering, add the previous owner as a
// reviewer and remove the actor.
$edits = array(
'-' => array(
$actor_phid,
),
'+' => array(
$owner_phid,
),
);
// NOTE: We're setting setIsCommandeerSideEffect() on this because normally
// you can't add a revision's author as a reviewer, but this action swaps
// them after validation executes.
$xaction_type = DifferentialRevisionReviewersTransaction::TRANSACTIONTYPE;
return id(new DifferentialTransaction())
->setTransactionType($xaction_type)
->setIgnoreOnNoEffect(true)
->setIsCommandeerSideEffect(true)
->setNewValue($edits);
}
}

View file

@ -18,8 +18,25 @@ final class DifferentialRevisionIDCommitMessageField
}
public function parseFieldValue($value) {
// If the value is just "D123" or similar, parse the ID from it directly.
// If the complete commit message we are parsing has unrecognized custom
// fields at the end, they can end up parsed into the field value for this
// field. For example, if the message looks like this:
// Differential Revision: xyz
// Some-Other-Field: abc
// ...we will receive "xyz\nSome-Other-Field: abc" as the field value for
// this field. Ideally, the install would define these fields so they can
// parse formally, but we can reasonably assume that only the first line
// of any value we encounter actually contains a revision identifier, so
// start by throwing away any other lines.
$value = trim($value);
$value = phutil_split_lines($value, false);
$value = head($value);
$value = trim($value);
// If the value is just "D123" or similar, parse the ID from it directly.
$matches = null;
if (preg_match('/^[dD]([1-9]\d*)\z/', $value, $matches)) {
return (int)$matches[1];

View file

@ -0,0 +1,30 @@
<?php
final class DifferentialCommitMessageFieldTestCase
extends PhabricatorTestCase {
public function testRevisionCommitMessageFieldParsing() {
$base_uri = 'https://www.example.com/';
$tests = array(
'D123' => 123,
'd123' => 123,
" \n d123 \n " => 123,
"D123\nSome-Custom-Field: The End" => 123,
"{$base_uri}D123" => 123,
"{$base_uri}D123\nSome-Custom-Field: The End" => 123,
);
$env = PhabricatorEnv::beginScopedEnv();
$env->overrideEnvConfig('phabricator.base-uri', $base_uri);
foreach ($tests as $input => $expect) {
$actual = id(new DifferentialRevisionIDCommitMessageField())
->parseFieldValue($input);
$this->assertEqual($expect, $actual, pht('Parse of: %s', $input));
}
unset($env);
}
}

View file

@ -155,20 +155,33 @@ final class DifferentialCommitMessageParser extends Phobject {
$field = $key_title;
$seen = array();
$lines = explode("\n", trim($corpus));
$lines = trim($corpus);
$lines = phutil_split_lines($lines, false);
$field_map = array();
foreach ($lines as $key => $line) {
$match = null;
if (preg_match($label_regexp, $line, $match)) {
$lines[$key] = trim($match['text']);
$field = $label_map[self::normalizeFieldLabel($match['field'])];
if (!empty($seen[$field])) {
$this->errors[] = pht(
'Field "%s" occurs twice in commit message!',
$field);
}
// We always parse the first line of the message as a title, even if it
// contains something we recognize as a field header.
if (!isset($seen[$key_title])) {
$field = $key_title;
$lines[$key] = trim($line);
$seen[$field] = true;
} else {
$match = null;
if (preg_match($label_regexp, $line, $match)) {
$lines[$key] = trim($match['text']);
$field = $label_map[self::normalizeFieldLabel($match['field'])];
if (!empty($seen[$field])) {
$this->errors[] = pht(
'Field "%s" occurs twice in commit message!',
$match['field']);
}
$seen[$field] = true;
}
}
$field_map[$key] = $field;
}

View file

@ -0,0 +1,11 @@
color: orange
~~~~~~~~~~
{
"color": "color"
}
~~~~~~~~~~
{
"title": "color: orange"
}
~~~~~~~~~~
[]

View file

@ -97,16 +97,22 @@ final class DifferentialChangesetOneUpRenderer
$line = array($hidden, $line);
}
$cells[] = phutil_tag('th', array('id' => $left_id), $line);
$cells[] = phutil_tag(
'th',
array(
'id' => $left_id,
'class' => $class,
),
$line);
$cells[] = phutil_tag('th', array());
$cells[] = phutil_tag('th', array('class' => $class));
$cells[] = $no_copy;
$cells[] = phutil_tag('td', array('class' => $class), $p['render']);
$cells[] = $no_coverage;
} else {
if ($p['htype']) {
$class = 'right new';
$cells[] = phutil_tag('th', array());
$cells[] = phutil_tag('th', array('class' => $class));
} else {
$class = 'right';
if ($left_prefix) {
@ -138,7 +144,13 @@ final class DifferentialChangesetOneUpRenderer
$line = array($hidden, $line);
}
$cells[] = phutil_tag('th', array('id' => $right_id), $line);
$cells[] = phutil_tag(
'th',
array(
'id' => $right_id,
'class' => $class,
),
$line);
$cells[] = $no_copy;
$cells[] = phutil_tag('td', array('class' => $class), $p['render']);

View file

@ -306,9 +306,9 @@ final class DifferentialChangesetTwoUpRenderer
$zero_space = "\xE2\x80\x8B";
$html[] = phutil_tag('tr', array(), array(
phutil_tag('th', array('id' => $o_id), $o_num),
phutil_tag('th', array('id' => $o_id, 'class' => $o_classes), $o_num),
phutil_tag('td', array('class' => $o_classes), $o_text),
phutil_tag('th', array('id' => $n_id), $n_num),
phutil_tag('th', array('id' => $n_id, 'class' => $n_classes), $n_num),
$n_copy,
phutil_tag(
'td',

View file

@ -4,6 +4,7 @@ final class DifferentialDiff
extends DifferentialDAO
implements
PhabricatorPolicyInterface,
PhabricatorExtendedPolicyInterface,
HarbormasterBuildableInterface,
HarbormasterCircleCIBuildableInterface,
PhabricatorApplicationTransactionInterface,
@ -429,7 +430,7 @@ final class DifferentialDiff
public function getPolicy($capability) {
if ($this->hasRevision()) {
return $this->getRevision()->getPolicy($capability);
return PhabricatorPolicies::getMostOpenPolicy();
}
return $this->viewPolicy;
@ -440,7 +441,7 @@ final class DifferentialDiff
return $this->getRevision()->hasAutomaticCapability($capability, $viewer);
}
return ($this->getAuthorPHID() == $viewer->getPhid());
return ($this->getAuthorPHID() == $viewer->getPHID());
}
public function describeAutomaticCapability($capability) {
@ -448,10 +449,31 @@ final class DifferentialDiff
return pht(
'This diff is attached to a revision, and inherits its policies.');
}
return pht('The author of a diff can see it.');
}
/* -( PhabricatorExtendedPolicyInterface )--------------------------------- */
public function getExtendedPolicy($capability, PhabricatorUser $viewer) {
$extended = array();
switch ($capability) {
case PhabricatorPolicyCapability::CAN_VIEW:
if ($this->hasRevision()) {
$extended[] = array(
$this->getRevision(),
PhabricatorPolicyCapability::CAN_VIEW,
);
}
break;
}
return $extended;
}
/* -( HarbormasterBuildableInterface )------------------------------------- */
@ -480,6 +502,10 @@ final class DifferentialDiff
return null;
}
public function getHarbormasterPublishablePHID() {
return $this->getHarbormasterContainerPHID();
}
public function getBuildVariables() {
$results = array();

View file

@ -442,6 +442,16 @@ final class DifferentialRevision extends DifferentialDAO
return DifferentialRevisionStatus::isClosedStatus($this->getStatus());
}
public function isAbandoned() {
$status_abandoned = ArcanistDifferentialRevisionStatus::ABANDONED;
return ($this->getStatus() == $status_abandoned);
}
public function isAccepted() {
$status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED;
return ($this->getStatus() == $status_accepted);
}
public function getStatusIcon() {
$map = array(
ArcanistDifferentialRevisionStatus::NEEDS_REVIEW
@ -503,6 +513,10 @@ final class DifferentialRevision extends DifferentialDAO
return $this->getPHID();
}
public function getHarbormasterPublishablePHID() {
return $this->getPHID();
}
public function getBuildVariables() {
return array();
}

View file

@ -223,6 +223,12 @@ final class DifferentialTransaction
case self::TYPE_INLINE:
$tags[] = self::MAILTAG_COMMENT;
break;
case DifferentialRevisionReviewersTransaction::TRANSACTIONTYPE:
$tags[] = self::MAILTAG_REVIEWERS;
break;
case DifferentialRevisionCloseTransaction::TRANSACTIONTYPE:
$tags[] = self::MAILTAG_CLOSED;
break;
}
if (!$tags) {

View file

@ -14,6 +14,7 @@ final class DifferentialTransactionComment
private $replyToComment = self::ATTACHABLE;
private $isHidden = self::ATTACHABLE;
private $changeset = self::ATTACHABLE;
public function getApplicationTransactionObject() {
return new DifferentialTransaction();

View file

@ -1,201 +0,0 @@
<?php
final class DifferentialAddCommentView extends AphrontView {
private $revision;
private $actions;
private $actionURI;
private $draft;
private $reviewers = array();
private $ccs = array();
private $errorView;
public function setInfoView(PHUIInfoView $error_view) {
$this->errorView = $error_view;
return $this;
}
public function getErrorView() {
return $this->errorView;
}
public function setRevision($revision) {
$this->revision = $revision;
return $this;
}
public function setActions(array $actions) {
$this->actions = $actions;
return $this;
}
public function setActionURI($uri) {
$this->actionURI = $uri;
return $this;
}
public function setDraft(PhabricatorDraft $draft = null) {
$this->draft = $draft;
return $this;
}
public function setReviewers(array $names) {
$this->reviewers = $names;
return $this;
}
public function setCCs(array $names) {
$this->ccs = $names;
return $this;
}
public function render() {
$viewer = $this->getViewer();
$this->requireResource('differential-revision-add-comment-css');
$revision = $this->revision;
$action = null;
if ($this->draft) {
$action = idx($this->draft->getMetadata(), 'action');
}
$enable_reviewers = DifferentialAction::allowReviewers($action);
$enable_ccs = ($action == DifferentialAction::ACTION_ADDCCS);
$add_reviewers_labels = array(
'add_reviewers' => pht('Add Reviewers'),
'request_review' => pht('Add Reviewers'),
'resign' => pht('Suggest Reviewers'),
);
$mailable_source = new PhabricatorMetaMTAMailableDatasource();
// TODO: This should be a reviewers datasource, but it's a mess.
$reviewer_source = new PhabricatorMetaMTAMailableDatasource();
$form = new AphrontFormView();
$form
->setWorkflow(true)
->setViewer($viewer)
->setAction($this->actionURI)
->addHiddenInput('revision_id', $revision->getID())
->appendChild(
id(new AphrontFormSelectControl())
->setLabel(pht('Action'))
->setName('action')
->setValue($action)
->setID('comment-action')
->setOptions($this->actions))
->appendControl(
id(new AphrontFormTokenizerControl())
->setLabel($enable_reviewers ? $add_reviewers_labels[$action] :
$add_reviewers_labels['add_reviewers'])
->setName('reviewers')
->setControlID('add-reviewers')
->setControlStyle($enable_reviewers ? null : 'display: none')
->setID('add-reviewers-tokenizer')
->setDisableBehavior(true)
->setDatasource($reviewer_source))
->appendControl(
id(new AphrontFormTokenizerControl())
->setLabel(pht('Add Subscribers'))
->setName('ccs')
->setControlID('add-ccs')
->setControlStyle($enable_ccs ? null : 'display: none')
->setID('add-ccs-tokenizer')
->setDisableBehavior(true)
->setDatasource($mailable_source))
->appendChild(
id(new PhabricatorRemarkupControl())
->setName('comment')
->setID('comment-content')
->setLabel(pht('Comment'))
->setValue($this->draft ? $this->draft->getDraft() : null)
->setViewer($viewer))
->appendChild(
id(new AphrontFormSubmitControl())
->setValue(pht('Submit')));
Javelin::initBehavior(
'differential-add-reviewers-and-ccs',
array(
'dynamic' => array(
'add-reviewers-tokenizer' => array(
'actions' => array(
'request_review' => 1,
'add_reviewers' => 1,
'resign' => 1,
),
'src' => $reviewer_source->getDatasourceURI(),
'value' => $this->reviewers,
'row' => 'add-reviewers',
'labels' => $add_reviewers_labels,
'placeholder' => $reviewer_source->getPlaceholderText(),
),
'add-ccs-tokenizer' => array(
'actions' => array('add_ccs' => 1),
'src' => $mailable_source->getDatasourceURI(),
'value' => $this->ccs,
'row' => 'add-ccs',
'placeholder' => $mailable_source->getPlaceholderText(),
),
),
'select' => 'comment-action',
));
$diff = $revision->loadActiveDiff();
$rev_id = $revision->getID();
Javelin::initBehavior(
'differential-feedback-preview',
array(
'uri' => '/differential/comment/preview/'.$rev_id.'/',
'preview' => 'comment-preview',
'action' => 'comment-action',
'content' => 'comment-content',
'previewTokenizers' => array(
'reviewers' => 'add-reviewers-tokenizer',
'ccs' => 'add-ccs-tokenizer',
),
'inlineuri' => '/differential/comment/inline/preview/'.$rev_id.'/',
'inline' => 'inline-comment-preview',
));
$is_serious = PhabricatorEnv::getEnvConfig('phabricator.serious-business');
$header_text = $is_serious
? pht('Add Comment')
: pht('Leap Into Action!');
$header = id(new PHUIHeaderView())
->setHeader($header_text);
$anchor = id(new PhabricatorAnchorView())
->setAnchorName('comment')
->setNavigationMarker(true);
$loading = phutil_tag(
'span',
array('class' => 'aphront-panel-preview-loading-text'),
pht('Loading comment preview...'));
$preview = phutil_tag_div(
'aphront-panel-preview aphront-panel-flush',
array(
phutil_tag('div', array('id' => 'comment-preview'), $loading),
phutil_tag('div', array('id' => 'inline-comment-preview')),
));
$comment_box = id(new PHUIObjectBoxView())
->setHeader($header)
->appendChild($anchor)
->appendChild($form);
if ($this->errorView) {
$comment_box->setInfoView($this->errorView);
}
return array($comment_box, $preview);
}
}

View file

@ -3,7 +3,7 @@
final class DifferentialTransactionView
extends PhabricatorApplicationTransactionView {
private $changesets;
private $changesets = array();
private $revision;
private $rightDiff;
private $leftDiff;
@ -93,6 +93,13 @@ final class DifferentialTransactionView
$out[] = parent::renderTransactionContent($xaction);
}
// If we're rendering a preview, we show the inline comments in a separate
// section underneath the main transaction preview, so we skip rendering
// them in the preview body.
if ($this->getIsPreview()) {
return $out;
}
if (!$group) {
return $out;
}

View file

@ -0,0 +1,83 @@
<?php
final class DifferentialRevisionAbandonTransaction
extends DifferentialRevisionActionTransaction {
const TRANSACTIONTYPE = 'differential.revision.abandon';
const ACTIONKEY = 'abandon';
protected function getRevisionActionLabel() {
return pht('Abandon Revision');
}
protected function getRevisionActionDescription() {
return pht('This revision will be abandoned and closed.');
}
public function getIcon() {
return 'fa-plane';
}
public function getColor() {
return 'indigo';
}
protected function getRevisionActionOrder() {
return 500;
}
public function getCommandKeyword() {
return 'abandon';
}
public function getCommandAliases() {
return array();
}
public function getCommandSummary() {
return pht('Abandon a revision.');
}
public function generateOldValue($object) {
return $object->isAbandoned();
}
public function applyInternalEffects($object, $value) {
$object->setStatus(ArcanistDifferentialRevisionStatus::ABANDONED);
}
protected function validateAction($object, PhabricatorUser $viewer) {
if ($object->isClosed()) {
throw new Exception(
pht(
'You can not abandon this revision because it has already been '.
'closed. Only open revisions can be abandoned.'));
}
$config_key = 'differential.always-allow-abandon';
if (!PhabricatorEnv::getEnvConfig($config_key)) {
if (!$this->isViewerRevisionAuthor($object, $viewer)) {
throw new Exception(
pht(
'You can not abandon this revision because you are not the '.
'author. You can only abandon revisions you own. You can change '.
'this behavior by adjusting the "%s" setting in Config.',
$config_key));
}
}
}
public function getTitle() {
return pht(
'%s abandoned this revision.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s abandoned %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -0,0 +1,99 @@
<?php
final class DifferentialRevisionAcceptTransaction
extends DifferentialRevisionReviewTransaction {
const TRANSACTIONTYPE = 'differential.revision.accept';
const ACTIONKEY = 'accept';
protected function getRevisionActionLabel() {
return pht("Accept Revision \xE2\x9C\x94");
}
protected function getRevisionActionDescription() {
return pht('These changes will be approved.');
}
public function getIcon() {
return 'fa-check-circle-o';
}
public function getColor() {
return 'green';
}
protected function getRevisionActionOrder() {
return 500;
}
public function getCommandKeyword() {
$accept_key = 'differential.enable-email-accept';
$allow_email_accept = PhabricatorEnv::getEnvConfig($accept_key);
if (!$allow_email_accept) {
return null;
}
return 'accept';
}
public function getCommandAliases() {
return array();
}
public function getCommandSummary() {
return pht('Accept a revision.');
}
public function generateOldValue($object) {
$actor = $this->getActor();
return $this->isViewerAcceptingReviewer($object, $actor);
}
public function applyExternalEffects($object, $value) {
$status = DifferentialReviewerStatus::STATUS_ACCEPTED;
$actor = $this->getActor();
$this->applyReviewerEffect($object, $actor, $value, $status);
}
protected function validateAction($object, PhabricatorUser $viewer) {
if ($object->isClosed()) {
throw new Exception(
pht(
'You can not accept this revision because it has already been '.
'closed. Only open revisions can be accepted.'));
}
$config_key = 'differential.allow-self-accept';
if (!PhabricatorEnv::getEnvConfig($config_key)) {
if ($this->isViewerRevisionAuthor($object, $viewer)) {
throw new Exception(
pht(
'You can not accept this revision because you are the revision '.
'author. You can only accept revisions you do not own. You can '.
'change this behavior by adjusting the "%s" setting in Config.',
$config_key));
}
}
if ($this->isViewerAcceptingReviewer($object, $viewer)) {
throw new Exception(
pht(
'You can not accept this revision because you have already '.
'accepted it.'));
}
}
public function getTitle() {
return pht(
'%s accepted this revision.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s accepted %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -0,0 +1,128 @@
<?php
abstract class DifferentialRevisionActionTransaction
extends DifferentialRevisionTransactionType {
final public function getRevisionActionKey() {
return $this->getPhobjectClassConstant('ACTIONKEY', 32);
}
public function isActionAvailable($object, PhabricatorUser $viewer) {
try {
$this->validateAction($object, $viewer);
return true;
} catch (Exception $ex) {
return false;
}
}
abstract protected function validateAction($object, PhabricatorUser $viewer);
abstract protected function getRevisionActionLabel();
public function getCommandKeyword() {
return null;
}
public function getCommandAliases() {
return array();
}
public function getCommandSummary() {
return null;
}
protected function getRevisionActionOrder() {
return 1000;
}
public function getRevisionActionOrderVector() {
return id(new PhutilSortVector())
->addInt($this->getRevisionActionOrder());
}
protected function getRevisionActionGroupKey() {
return DifferentialRevisionEditEngine::ACTIONGROUP_REVISION;
}
protected function getRevisionActionDescription() {
return null;
}
public static function loadAllActions() {
return id(new PhutilClassMapQuery())
->setAncestorClass(__CLASS__)
->setUniqueMethod('getRevisionActionKey')
->execute();
}
protected function isViewerRevisionAuthor(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
if (!$viewer->getPHID()) {
return false;
}
return ($viewer->getPHID() === $revision->getAuthorPHID());
}
public function newEditField(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
$field = id(new PhabricatorApplyEditField())
->setKey($this->getRevisionActionKey())
->setTransactionType($this->getTransactionTypeConstant())
->setValue(true);
if ($this->isActionAvailable($revision, $viewer)) {
$label = $this->getRevisionActionLabel();
if ($label !== null) {
$field->setCommentActionLabel($label);
$description = $this->getRevisionActionDescription();
$field->setActionDescription($description);
$group_key = $this->getRevisionActionGroupKey();
$field->setCommentActionGroupKey($group_key);
// Currently, every revision action conflicts with every other
// revision action: for example, you can not simultaneously Accept and
// Reject a revision.
// Under some configurations, some combinations of actions are sort of
// technically permissible. For example, you could reasonably Reject
// and Abandon a revision if "anyone can abandon anything" is enabled.
// It's not clear that these combinations are actually useful, so just
// keep things simple for now.
$field->setActionConflictKey('revision.action');
}
}
return $field;
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$actor = $this->getActor();
$action_exception = null;
try {
$this->validateAction($object, $actor);
} catch (Exception $ex) {
$action_exception = $ex;
}
foreach ($xactions as $xaction) {
if ($action_exception) {
$errors[] = $this->newInvalidError(
$action_exception->getMessage(),
$xaction);
}
}
return $errors;
}
}

View file

@ -0,0 +1,89 @@
<?php
final class DifferentialRevisionCloseTransaction
extends DifferentialRevisionActionTransaction {
const TRANSACTIONTYPE = 'differential.revision.close';
const ACTIONKEY = 'close';
protected function getRevisionActionLabel() {
return pht('Close Revision');
}
protected function getRevisionActionDescription() {
return pht('This revision will be closed.');
}
public function getIcon() {
return 'fa-check';
}
public function getColor() {
return 'indigo';
}
protected function getRevisionActionOrder() {
return 300;
}
public function generateOldValue($object) {
return $object->isClosed();
}
public function applyInternalEffects($object, $value) {
$status_closed = ArcanistDifferentialRevisionStatus::CLOSED;
$status_accepted = ArcanistDifferentialRevisionStatus::ACCEPTED;
$old_status = $object->getStatus();
$object->setStatus($status_closed);
$was_accepted = ($old_status == $status_accepted);
$object->setProperty(
DifferentialRevision::PROPERTY_CLOSED_FROM_ACCEPTED,
$was_accepted);
}
protected function validateAction($object, PhabricatorUser $viewer) {
if ($object->isClosed()) {
throw new Exception(
pht(
'You can not close this revision because it has already been '.
'closed. Only open revisions can be closed.'));
}
if (!$object->isAccepted()) {
throw new Exception(
pht(
'You can not close this revision because it has not been accepted. '.
'Revisions must be accepted before they can be closed.'));
}
$config_key = 'differential.always-allow-close';
if (!PhabricatorEnv::getEnvConfig($config_key)) {
if (!$this->isViewerRevisionAuthor($object, $viewer)) {
throw new Exception(
pht(
'You can not close this revision because you are not the '.
'author. You can only close revisions you own. You can change '.
'this behavior by adjusting the "%s" setting in Config.',
$config_key));
}
}
}
public function getTitle() {
return pht(
'%s closed this revision.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s closed %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -0,0 +1,85 @@
<?php
final class DifferentialRevisionCommandeerTransaction
extends DifferentialRevisionActionTransaction {
const TRANSACTIONTYPE = 'differential.revision.commandeer';
const ACTIONKEY = 'commandeer';
protected function getRevisionActionLabel() {
return pht('Commandeer Revision');
}
protected function getRevisionActionDescription() {
return pht('You will take control of this revision and become its author.');
}
public function getIcon() {
return 'fa-flag';
}
public function getColor() {
return 'sky';
}
protected function getRevisionActionOrder() {
return 700;
}
public function getCommandKeyword() {
return 'commandeer';
}
public function getCommandAliases() {
return array(
'claim',
);
}
public function getCommandSummary() {
return pht('Commadeer a revision.');
}
public function generateOldValue($object) {
return $object->getAuthorPHID();
}
public function generateNewValue($object, $value) {
$actor = $this->getActor();
return $actor->getPHID();
}
public function applyInternalEffects($object, $value) {
$object->setAuthorPHID($value);
}
protected function validateAction($object, PhabricatorUser $viewer) {
if ($object->isClosed()) {
throw new Exception(
pht(
'You can not commandeer this revision because it has already '.
'been closed. You can only commandeer open revisions.'));
}
if ($this->isViewerRevisionAuthor($object, $viewer)) {
throw new Exception(
pht(
'You can not commandeer this revision because you are already '.
'the author.'));
}
}
public function getTitle() {
return pht(
'%s commandeered this revision.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s commandeered %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -0,0 +1,93 @@
<?php
final class DifferentialRevisionPlanChangesTransaction
extends DifferentialRevisionActionTransaction {
const TRANSACTIONTYPE = 'differential.revision.plan';
const ACTIONKEY = 'plan-changes';
protected function getRevisionActionLabel() {
return pht('Plan Changes');
}
protected function getRevisionActionDescription() {
return pht(
'This revision will be removed from review queues until it is revised.');
}
public function getIcon() {
return 'fa-headphones';
}
public function getColor() {
return 'red';
}
protected function getRevisionActionOrder() {
return 200;
}
public function getCommandKeyword() {
return 'planchanges';
}
public function getCommandAliases() {
return array(
'rethink',
);
}
public function getCommandSummary() {
return pht('Plan changes to a revision.');
}
public function generateOldValue($object) {
$status_planned = ArcanistDifferentialRevisionStatus::CHANGES_PLANNED;
return ($object->getStatus() == $status_planned);
}
public function applyInternalEffects($object, $value) {
$status_planned = ArcanistDifferentialRevisionStatus::CHANGES_PLANNED;
$object->setStatus($status_planned);
}
protected function validateAction($object, PhabricatorUser $viewer) {
$status_planned = ArcanistDifferentialRevisionStatus::CHANGES_PLANNED;
if ($object->getStatus() == $status_planned) {
throw new Exception(
pht(
'You can not request review of this revision because this '.
'revision is already under review and the action would have '.
'no effect.'));
}
if ($object->isClosed()) {
throw new Exception(
pht(
'You can not plan changes to this this revision because it has '.
'already been closed.'));
}
if (!$this->isViewerRevisionAuthor($object, $viewer)) {
throw new Exception(
pht(
'You can not plan changes to this revision because you do not '.
'own it. Only the author of a revision can plan changes to it.'));
}
}
public function getTitle() {
return pht(
'%s planned changes to this revision.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s planned changes to %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -0,0 +1,78 @@
<?php
final class DifferentialRevisionReclaimTransaction
extends DifferentialRevisionActionTransaction {
const TRANSACTIONTYPE = 'differential.revision.reclaim';
const ACTIONKEY = 'reclaim';
protected function getRevisionActionLabel() {
return pht('Reclaim Revision');
}
protected function getRevisionActionDescription() {
return pht('This revision will be reclaimed and reopened.');
}
public function getIcon() {
return 'fa-bullhorn';
}
public function getColor() {
return 'sky';
}
protected function getRevisionActionOrder() {
return 600;
}
public function getCommandKeyword() {
return 'reclaim';
}
public function getCommandAliases() {
return array();
}
public function getCommandSummary() {
return pht('Reclaim a revision.');
}
public function generateOldValue($object) {
return !$object->isAbandoned();
}
public function applyInternalEffects($object, $value) {
$object->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
}
protected function validateAction($object, PhabricatorUser $viewer) {
if (!$object->isAbandoned()) {
throw new Exception(
pht(
'You can not reclaim this revision because it has not been '.
'abandoned. Only abandoned revisions can be reclaimed.'));
}
if (!$this->isViewerRevisionAuthor($object, $viewer)) {
throw new Exception(
pht(
'You can not reclaim this revision because you are not the '.
'revision author. You can only reclaim revisions you own.'));
}
}
public function getTitle() {
return pht(
'%s reclaimed this revision.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s reclaimed %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -0,0 +1,92 @@
<?php
final class DifferentialRevisionRejectTransaction
extends DifferentialRevisionReviewTransaction {
const TRANSACTIONTYPE = 'differential.revision.reject';
const ACTIONKEY = 'reject';
protected function getRevisionActionLabel() {
return pht("Request Changes \xE2\x9C\x98");
}
protected function getRevisionActionDescription() {
return pht('This revision will be returned to the author for updates.');
}
public function getIcon() {
return 'fa-times-circle-o';
}
public function getColor() {
return 'red';
}
protected function getRevisionActionOrder() {
return 600;
}
public function getCommandKeyword() {
return 'request';
}
public function getCommandAliases() {
return array(
'reject',
);
}
public function getCommandSummary() {
return pht('Request changes to a revision.');
}
public function generateOldValue($object) {
$actor = $this->getActor();
return $this->isViewerRejectingReviewer($object, $actor);
}
public function applyExternalEffects($object, $value) {
$status = DifferentialReviewerStatus::STATUS_REJECTED;
$actor = $this->getActor();
$this->applyReviewerEffect($object, $actor, $value, $status);
}
protected function validateAction($object, PhabricatorUser $viewer) {
if ($object->isClosed()) {
throw new Exception(
pht(
'You can not request changes to this revision because it has '.
'already been closed. You can only request changes to open '.
'revisions.'));
}
if ($this->isViewerRevisionAuthor($object, $viewer)) {
throw new Exception(
pht(
'You can not request changes to this revision because you are the '.
'revision author. You can only request changes to revisions you do '.
'not own.'));
}
if ($this->isViewerRejectingReviewer($object, $viewer)) {
throw new Exception(
pht(
'You can not request changes to this revision because you have '.
'already requested changes.'));
}
}
public function getTitle() {
return pht(
'%s requested changes to this revision.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s requested changes to %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -0,0 +1,72 @@
<?php
final class DifferentialRevisionReopenTransaction
extends DifferentialRevisionActionTransaction {
const TRANSACTIONTYPE = 'differential.revision.reopen';
const ACTIONKEY = 'reopen';
protected function getRevisionActionLabel() {
return pht('Reopen Revision');
}
protected function getRevisionActionDescription() {
return pht('This revision will be reopened for review.');
}
public function getIcon() {
return 'fa-bullhorn';
}
public function getColor() {
return 'sky';
}
protected function getRevisionActionOrder() {
return 400;
}
public function generateOldValue($object) {
return !$object->isClosed();
}
public function applyInternalEffects($object, $value) {
$object->setStatus(ArcanistDifferentialRevisionStatus::NEEDS_REVIEW);
}
protected function validateAction($object, PhabricatorUser $viewer) {
// Note that we're testing for "Closed", exactly, not just any closed
// status.
$status_closed = ArcanistDifferentialRevisionStatus::CLOSED;
if ($object->getStatus() != $status_closed) {
throw new Exception(
pht(
'You can not reopen this revision because it is not closed. '.
'Only closed revisions can be reopened.'));
}
$config_key = 'differential.allow-reopen';
if (!PhabricatorEnv::getEnvConfig($config_key)) {
throw new Exception(
pht(
'You can not reopen this revision because configuration prevents '.
'any revision from being reopened. You can change this behavior '.
'by adjusting the "%s" setting in Config.',
$config_key));
}
}
public function getTitle() {
return pht(
'%s reopened this revision.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s reopened %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -0,0 +1,74 @@
<?php
final class DifferentialRevisionRequestReviewTransaction
extends DifferentialRevisionActionTransaction {
const TRANSACTIONTYPE = 'differential.revision.request';
const ACTIONKEY = 'request-review';
protected function getRevisionActionLabel() {
return pht('Request Review');
}
protected function getRevisionActionDescription() {
return pht('This revision will be returned to reviewers for feedback.');
}
public function getColor() {
return 'sky';
}
protected function getRevisionActionOrder() {
return 200;
}
public function generateOldValue($object) {
$status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
return ($object->getStatus() == $status_review);
}
public function applyInternalEffects($object, $value) {
$status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
$object->setStatus($status_review);
}
protected function validateAction($object, PhabricatorUser $viewer) {
$status_review = ArcanistDifferentialRevisionStatus::NEEDS_REVIEW;
if ($object->getStatus() == $status_review) {
throw new Exception(
pht(
'You can not request review of this revision because this '.
'revision is already under review and the action would have '.
'no effect.'));
}
if ($object->isClosed()) {
throw new Exception(
pht(
'You can not request review of this revision because it has '.
'already been closed. You can only request review of open '.
'revisions.'));
}
if (!$this->isViewerRevisionAuthor($object, $viewer)) {
throw new Exception(
pht(
'You can not request review of this revision because you are not '.
'the author of the revision.'));
}
}
public function getTitle() {
return pht(
'%s requested review of this revision.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s requested review of %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -0,0 +1,82 @@
<?php
final class DifferentialRevisionResignTransaction
extends DifferentialRevisionReviewTransaction {
const TRANSACTIONTYPE = 'differential.revision.resign';
const ACTIONKEY = 'resign';
protected function getRevisionActionLabel() {
return pht('Resign as Reviewer');
}
protected function getRevisionActionDescription() {
return pht('You will resign as a reviewer for this change.');
}
public function getIcon() {
return 'fa-flag';
}
public function getColor() {
return 'orange';
}
protected function getRevisionActionOrder() {
return 700;
}
public function getCommandKeyword() {
return 'resign';
}
public function getCommandAliases() {
return array();
}
public function getCommandSummary() {
return pht('Resign from a revision.');
}
public function generateOldValue($object) {
$actor = $this->getActor();
return !$this->isViewerAnyReviewer($object, $actor);
}
public function applyExternalEffects($object, $value) {
$status = DifferentialReviewerStatus::STATUS_RESIGNED;
$actor = $this->getActor();
$this->applyReviewerEffect($object, $actor, $value, $status);
}
protected function validateAction($object, PhabricatorUser $viewer) {
if ($object->isClosed()) {
throw new Exception(
pht(
'You can not resign from this revision because it has already '.
'been closed. You can only resign from open revisions.'));
}
if (!$this->isViewerAnyReviewer($object, $viewer)) {
throw new Exception(
pht(
'You can not resign from this revision because you are not a '.
'reviewer. You can only resign from revisions where you are a '.
'reviewer.'));
}
}
public function getTitle() {
return pht(
'%s resigned from this revision.',
$this->renderAuthor());
}
public function getTitleForFeed() {
return pht(
'%s resigned from %s.',
$this->renderAuthor(),
$this->renderObject());
}
}

View file

@ -0,0 +1,120 @@
<?php
abstract class DifferentialRevisionReviewTransaction
extends DifferentialRevisionActionTransaction {
protected function getRevisionActionGroupKey() {
return DifferentialRevisionEditEngine::ACTIONGROUP_REVIEW;
}
protected function isViewerAnyReviewer(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return ($this->getViewerReviewerStatus($revision, $viewer) !== null);
}
protected function isViewerAcceptingReviewer(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return $this->isViewerReviewerStatusAmong(
$revision,
$viewer,
array(
DifferentialReviewerStatus::STATUS_ACCEPTED,
));
}
protected function isViewerRejectingReviewer(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
return $this->isViewerReviewerStatusAmong(
$revision,
$viewer,
array(
DifferentialReviewerStatus::STATUS_REJECTED,
));
}
protected function getViewerReviewerStatus(
DifferentialRevision $revision,
PhabricatorUser $viewer) {
if (!$viewer->getPHID()) {
return null;
}
foreach ($revision->getReviewerStatus() as $reviewer) {
if ($reviewer->getReviewerPHID() != $viewer->getPHID()) {
continue;
}
return $reviewer->getStatus();
}
return null;
}
protected function isViewerReviewerStatusAmong(
DifferentialRevision $revision,
PhabricatorUser $viewer,
array $status_list) {
$status = $this->getViewerReviewerStatus($revision, $viewer);
if ($status === null) {
return false;
}
$status_map = array_fuse($status_list);
return isset($status_map[$status]);
}
protected function applyReviewerEffect(
DifferentialRevision $revision,
PhabricatorUser $viewer,
$value,
$status) {
$map = array();
// When you accept or reject, you may accept or reject on behalf of all
// reviewers you have authority for. When you resign, you only affect
// yourself.
$with_authority = ($status != DifferentialReviewerStatus::STATUS_RESIGNED);
if ($with_authority) {
foreach ($revision->getReviewerStatus() as $reviewer) {
if ($reviewer->hasAuthority($viewer)) {
$map[$reviewer->getReviewerPHID()] = $status;
}
}
}
// In all cases, you affect yourself.
$map[$viewer->getPHID()] = $status;
// Convert reviewer statuses into edge data.
foreach ($map as $reviewer_phid => $reviewer_status) {
$map[$reviewer_phid] = array(
'data' => array(
'status' => $reviewer_status,
),
);
}
$src_phid = $revision->getPHID();
$edge_type = DifferentialRevisionHasReviewerEdgeType::EDGECONST;
$editor = new PhabricatorEdgeEditor();
foreach ($map as $dst_phid => $edge_data) {
if ($status == DifferentialReviewerStatus::STATUS_RESIGNED) {
// TODO: For now, we just remove these reviewers. In the future, we will
// store resignations explicitly.
$editor->removeEdge($src_phid, $edge_type, $dst_phid);
} else {
$editor->addEdge($src_phid, $edge_type, $dst_phid, $edge_data);
}
}
$editor->save();
}
}

View file

@ -3,6 +3,8 @@
final class PhabricatorEmbedFileRemarkupRule
extends PhabricatorObjectRemarkupRule {
private $viewer;
const KEY_EMBED_FILE_PHIDS = 'phabricator.embedded-file-phids';
protected function getObjectNamePrefix() {
@ -12,9 +14,9 @@ final class PhabricatorEmbedFileRemarkupRule
protected function loadObjects(array $ids) {
$engine = $this->getEngine();
$viewer = $engine->getConfig('viewer');
$this->viewer = $engine->getConfig('viewer');
$objects = id(new PhabricatorFileQuery())
->setViewer($viewer)
->setViewer($this->viewer)
->withIDs($ids)
->needTransforms(
array(
@ -282,6 +284,7 @@ final class PhabricatorEmbedFileRemarkupRule
array $options) {
return id(new PhabricatorFileLinkView())
->setViewer($this->viewer)
->setFilePHID($file->getPHID())
->setFileName($this->assertFlatText($options['name']))
->setFileDownloadURI($file->getDownloadURI())

View file

@ -497,17 +497,28 @@ final class HarbormasterBuildEngine extends Phobject {
return;
}
if (!($object instanceof PhabricatorApplicationTransactionInterface)) {
$publish_phid = $object->getHarbormasterPublishablePHID();
if (!$publish_phid) {
return;
}
// TODO: Publishing these transactions is causing a race. See T8650.
// We shouldn't be publishing to diffs anyway.
if ($object instanceof DifferentialDiff) {
if ($publish_phid === $object->getPHID()) {
$publish = $object;
} else {
$publish = id(new PhabricatorObjectQuery())
->setViewer($viewer)
->withPHIDs(array($publish_phid))
->executeOne();
if (!$publish) {
return;
}
}
if (!($publish instanceof PhabricatorApplicationTransactionInterface)) {
return;
}
$template = $object->getApplicationTransactionTemplate();
$template = $publish->getApplicationTransactionTemplate();
if (!$template) {
return;
}
@ -526,7 +537,7 @@ final class HarbormasterBuildEngine extends Phobject {
$daemon_source = PhabricatorContentSource::newForSource(
PhabricatorDaemonContentSource::SOURCECONST);
$editor = $object->getApplicationTransactionEditor()
$editor = $publish->getApplicationTransactionEditor()
->setActor($viewer)
->setActingAsPHID($harbormaster_phid)
->setContentSource($daemon_source)
@ -534,7 +545,7 @@ final class HarbormasterBuildEngine extends Phobject {
->setContinueOnMissingFields(true);
$editor->applyTransactions(
$object->getApplicationTransactionObject(),
$publish->getApplicationTransactionObject(),
array($template));
}

View file

@ -18,6 +18,21 @@ interface HarbormasterBuildableInterface {
public function getHarbormasterBuildablePHID();
public function getHarbormasterContainerPHID();
/**
* Get the object PHID which build status should be published to.
*
* In some cases (like commits), this is the object itself. In other cases,
* it is a different object: for example, diffs publish builds to revisions.
*
* This method can return `null` to disable publishing.
*
* @return phid|null Build status updates will be published to this object's
* transaction timeline.
*/
public function getHarbormasterPublishablePHID();
public function getBuildVariables();
public function getAvailableBuildVariables();

View file

@ -317,6 +317,10 @@ final class HarbormasterBuildable extends HarbormasterDAO
return $this->getContainerPHID();
}
public function getHarbormasterPublishablePHID() {
return $this->getBuildableObject()->getHarbormasterPublishablePHID();
}
public function getBuildVariables() {
return array();
}

View file

@ -22,9 +22,18 @@ final class PhabricatorOwnersPackageDatasource
$results = array();
$query = id(new PhabricatorOwnersPackageQuery())
->withNameNgrams($raw_query)
->setOrder('name');
// If the user is querying by monogram explicitly, like "O123", do an ID
// search. Otherwise, do an ngram substring search.
if (preg_match('/^[oO]\d+\z/', $raw_query)) {
$id = trim($raw_query, 'oO');
$id = (int)$id;
$query->withIDs(array($id));
} else {
$query->withNameNgrams($raw_query);
}
$packages = $this->executeQuery($query);
foreach ($packages as $package) {
$name = $package->getName();

View file

@ -14,20 +14,52 @@ final class PhabricatorPasteTitleTransaction
}
public function getTitle() {
return pht(
'%s changed the title of this paste from %s to %s.',
$this->renderAuthor(),
$this->renderOldValue(),
$this->renderNewValue());
$old = $this->getOldValue();
$new = $this->getNeWValue();
if (strlen($old) && strlen($new)) {
return pht(
'%s changed the title of this paste from %s to %s.',
$this->renderAuthor(),
$this->renderOldValue(),
$this->renderNewValue());
} else if (strlen($new)) {
return pht(
'%s changed the title of this paste from untitled to %s.',
$this->renderAuthor(),
$this->renderNewValue());
} else {
return pht(
'%s changed the title of this paste from %s to untitled.',
$this->renderAuthor(),
$this->renderOldValue());
}
}
public function getTitleForFeed() {
return pht(
'%s updated the title for %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderOldValue(),
$this->renderNewValue());
$old = $this->getOldValue();
$new = $this->getNeWValue();
if (strlen($old) && strlen($new)) {
return pht(
'%s updated the title for %s from %s to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderOldValue(),
$this->renderNewValue());
} else if (strlen($new)) {
return pht(
'%s updated the title for %s from untitled to %s.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderNewValue());
} else {
return pht(
'%s updated the title for %s from %s to untitled.',
$this->renderAuthor(),
$this->renderObject(),
$this->renderOldValue());
}
}
}

View file

@ -149,7 +149,22 @@ final class PhabricatorObjectListQuery extends Phobject {
$missing = array();
foreach ($name_map as $key => $name) {
if (empty($objects[$key])) {
$missing[] = $name;
$missing[$key] = $name;
}
}
$result = array_unique(mpull($objects, 'getPHID'));
// For values which are plain PHIDs of allowed types, let them through
// unchecked. This can happen occur if subscribers or reviewers which the
// revision author does not have permission to see are added by Herald
// rules. Any actual edits will be checked later: users are not allowed
// to add new reviewers they can't see, but they can touch a field which
// contains them.
foreach ($missing as $key => $value) {
if (isset($allowed[phid_get_type($value)])) {
unset($missing[$key]);
$result[$key] = $value;
}
}
@ -181,8 +196,6 @@ final class PhabricatorObjectListQuery extends Phobject {
}
}
$result = array_unique(mpull($objects, 'getPHID'));
if ($suffixes) {
foreach ($result as $key => $phid) {
$result[$key] = array(

View file

@ -123,6 +123,12 @@ final class PhabricatorPolicyFilter extends Phobject {
return $objects;
}
// Before doing any actual object checks, make sure the viewer can see
// the applications that these objects belong to. This is normally enforced
// in the Query layer before we reach object filtering, but execution
// sometimes reaches policy filtering without running application checks.
$objects = $this->applyApplicationChecks($objects);
$filtered = array();
$viewer_phid = $viewer->getPHID();
@ -864,4 +870,49 @@ final class PhabricatorPolicyFilter extends Phobject {
throw $exception;
}
private function applyApplicationChecks(array $objects) {
$viewer = $this->viewer;
foreach ($objects as $key => $object) {
$phid = $object->getPHID();
if (!$phid) {
continue;
}
$application_class = $this->getApplicationForPHID($phid);
if ($application_class === null) {
continue;
}
$can_see = PhabricatorApplication::isClassInstalledForViewer(
$application_class,
$viewer);
if ($can_see) {
continue;
}
unset($objects[$key]);
$application = newv($application_class, array());
$this->rejectObject(
$application,
$application->getPolicy(PhabricatorPolicyCapability::CAN_VIEW),
PhabricatorPolicyCapability::CAN_VIEW);
}
return $objects;
}
private function getApplicationForPHID($phid) {
$phid_type = phid_get_type($phid);
$type_objects = PhabricatorPHIDType::getTypes(array($phid_type));
$type_object = idx($type_objects, $phid_type);
if (!$type_object) {
return null;
}
return $type_object->getPHIDTypeApplicationClass();
}
}

View file

@ -39,6 +39,46 @@ final class PhabricatorProjectCoreTestCase extends PhabricatorTestCase {
$this->assertFalse((bool)$this->refreshProject($proj, $user2));
}
public function testApplicationPolicy() {
$user = $this->createUser()
->save();
$proj = $this->createProject($user);
$this->assertTrue(
PhabricatorPolicyFilter::hasCapability(
$user,
$proj,
PhabricatorPolicyCapability::CAN_VIEW));
// Change the "Can Use Application" policy for Projecs to "No One". This
// should cause filtering checks to fail even when they are executed
// directly rather than via a Query.
$env = PhabricatorEnv::beginScopedEnv();
$env->overrideEnvConfig(
'phabricator.application-settings',
array(
'PHID-APPS-PhabricatorProjectApplication' => array(
'policy' => array(
'view' => PhabricatorPolicies::POLICY_NOONE,
),
),
));
// Application visibility is cached because it does not normally change
// over the course of a single request. Drop the cache so the next filter
// test uses the new visibility.
PhabricatorCaches::destroyRequestCache();
$this->assertFalse(
PhabricatorPolicyFilter::hasCapability(
$user,
$proj,
PhabricatorPolicyCapability::CAN_VIEW));
unset($env);
}
public function testIsViewerMemberOrWatcher() {
$user1 = $this->createUser()
->save();

View file

@ -414,6 +414,13 @@ final class PhabricatorRepositoryDiscoveryEngine
$epoch = PhabricatorTime::getNow();
}
// If the epoch is not present at all, treat it as though it stores the
// value "0". For discussion, see T12062. This behavior is consistent
// with the behavior of "git show".
if (!strlen($epoch)) {
$epoch = 0;
}
$refs[] = id(new PhabricatorRepositoryCommitRef())
->setIdentifier($commit)
->setEpoch($epoch)

View file

@ -413,6 +413,10 @@ final class PhabricatorRepositoryCommit
return $this->getRepository()->getPHID();
}
public function getHarbormasterPublishablePHID() {
return $this->getPHID();
}
public function getBuildVariables() {
$results = array();

View file

@ -0,0 +1,122 @@
<?php
final class PhabricatorEditEngineProfileMenuItem
extends PhabricatorProfileMenuItem {
const MENUITEMKEY = 'editengine';
private $form;
public function getMenuItemTypeIcon() {
return 'fa-plus';
}
public function getMenuItemTypeName() {
return pht('Forms');
}
public function canAddToObject($object) {
return true;
}
public function attachForm($form) {
$this->form = $form;
return $this;
}
public function getForm() {
$form = $this->form;
if (!$form) {
return null;
}
return $form;
}
public function willBuildNavigationItems(array $items) {
$viewer = $this->getViewer();
$engines = PhabricatorEditEngine::getAllEditEngines();
$engine_keys = array_keys($engines);
$forms = id(new PhabricatorEditEngineConfigurationQuery())
->setViewer($viewer)
->withEngineKeys($engine_keys)
->withIsDisabled(false)
->execute();
$form_engines = mgroup($forms, 'getEngineKey');
$form_ids = $forms;
$builtin_map = array();
foreach ($form_engines as $engine_key => $form_engine) {
$builtin_map[$engine_key] = mpull($form_engine, null, 'getBuiltinKey');
}
foreach ($items as $item) {
$key = $item->getMenuItemProperty('formKey');
list($engine_key, $form_key) = explode('/', $key);
if (is_numeric($form_key)) {
$form = idx($form_ids, $form_key, null);
$item->getMenuItem()->attachForm($form);
} else if (isset($builtin_map[$engine_key][$form_key])) {
$form = $builtin_map[$engine_key][$form_key];
$item->getMenuItem()->attachForm($form);
}
}
}
public function getDisplayName(
PhabricatorProfileMenuItemConfiguration $config) {
$form = $this->getForm();
if (!$form) {
return pht('(Restricted/Invalid Form)');
}
if (strlen($this->getName($config))) {
return $this->getName($config);
} else {
return $form->getName();
}
}
public function buildEditEngineFields(
PhabricatorProfileMenuItemConfiguration $config) {
return array(
id(new PhabricatorTextEditField())
->setKey('name')
->setLabel(pht('Name'))
->setValue($this->getName($config)),
id(new PhabricatorDatasourceEditField())
->setKey('formKey')
->setLabel(pht('Form'))
->setDatasource(new PhabricatorEditEngineDatasource())
->setSingleValue($config->getMenuItemProperty('formKey')),
);
}
private function getName(
PhabricatorProfileMenuItemConfiguration $config) {
return $config->getMenuItemProperty('name');
}
protected function newNavigationMenuItems(
PhabricatorProfileMenuItemConfiguration $config) {
$form = $this->getForm();
if (!$form) {
return array();
}
$engine = $form->getEngine();
$form_key = $form->getIdentifier();
$icon = $form->getIcon();
$name = $this->getDisplayName($config);
$href = $engine->getEditURI(null, "form/{$form_key}/");
$item = $this->newItem()
->setHref($href)
->setName($name)
->setIcon($icon);
return array(
$item,
);
}
}

View file

@ -7,6 +7,8 @@ abstract class PhabricatorEditEngineCommentAction extends Phobject {
private $value;
private $initialValue;
private $order;
private $groupKey;
private $conflictKey;
abstract public function getPHUIXControlType();
abstract public function getPHUIXControlSpecification();
@ -20,6 +22,24 @@ abstract class PhabricatorEditEngineCommentAction extends Phobject {
return $this->key;
}
public function setGroupKey($group_key) {
$this->groupKey = $group_key;
return $this;
}
public function getGroupKey() {
return $this->groupKey;
}
public function setConflictKey($conflict_key) {
$this->conflictKey = $conflict_key;
return $this;
}
public function getConflictKey() {
return $this->conflictKey;
}
public function setLabel($label) {
$this->label = $label;
return $this;

View file

@ -0,0 +1,27 @@
<?php
final class PhabricatorEditEngineCommentActionGroup
extends Phobject {
private $key;
private $label;
public function setKey($key) {
$this->key = $key;
return $this;
}
public function getKey() {
return $this->key;
}
public function setLabel($label) {
$this->label = $label;
return $this;
}
public function getLabel() {
return $this->label;
}
}

View file

@ -0,0 +1,28 @@
<?php
final class PhabricatorEditEngineStaticCommentAction
extends PhabricatorEditEngineCommentAction {
private $description;
public function setDescription($description) {
$this->description = $description;
return $this;
}
public function getDescription() {
return $this->description;
}
public function getPHUIXControlType() {
return 'static';
}
public function getPHUIXControlSpecification() {
return array(
'value' => $this->getValue(),
'description' => $this->getDescription(),
);
}
}

View file

@ -48,7 +48,7 @@ final class PhabricatorApplicationTransactionCommentRawController
$title = pht('Email Body Text');
$body = $message->getRawTextBody();
$details_text = pht(
'For full details, run `/bin/mail show-outbound --id %d`',
'For full details, run `/bin/mail show-inbound --id %d`',
$source_id);
$addendum = new PHUIRemarkupView($viewer, $details_text);
}

View file

@ -49,7 +49,14 @@ abstract class PhabricatorEditEngine
}
final public function getEngineKey() {
return $this->getPhobjectClassConstant('ENGINECONST', 64);
$key = $this->getPhobjectClassConstant('ENGINECONST', 64);
if (strpos($key, '/') !== false) {
throw new Exception(
pht(
'EditEngine ("%s") contains an invalid key character "/".',
get_class($this)));
}
return $key;
}
final public function getApplication() {
@ -1549,6 +1556,9 @@ abstract class PhabricatorEditEngine
$view->setCommentActions($comment_actions);
$comment_groups = $this->newCommentActionGroups();
$view->setCommentActionGroups($comment_groups);
return $view;
}
@ -1780,6 +1790,11 @@ abstract class PhabricatorEditEngine
}
}
$auto_xactions = $this->newAutomaticCommentTransactions($object);
foreach ($auto_xactions as $xaction) {
$xactions[] = $xaction;
}
if (strlen($comment_text) || !$xactions) {
$xactions[] = id(clone $template)
->setTransactionType(PhabricatorTransactions::TYPE_COMMENT)
@ -1797,6 +1812,10 @@ abstract class PhabricatorEditEngine
try {
$xactions = $editor->applyTransactions($object, $xactions);
} catch (PhabricatorApplicationTransactionValidationException $ex) {
return id(new PhabricatorApplicationTransactionValidationResponse())
->setCancelURI($view_uri)
->setException($ex);
} catch (PhabricatorApplicationTransactionNoEffectException $ex) {
return id(new PhabricatorApplicationTransactionNoEffectResponse())
->setCancelURI($view_uri)
@ -1811,10 +1830,13 @@ abstract class PhabricatorEditEngine
}
if ($request->isAjax() && $is_preview) {
$preview_content = $this->newCommentPreviewContent($object, $xactions);
return id(new PhabricatorApplicationTransactionResponse())
->setViewer($viewer)
->setTransactions($xactions)
->setIsPreview($is_preview);
->setIsPreview($is_preview)
->setPreviewContent($preview_content);
} else {
return id(new AphrontRedirectResponse())
->setURI($view_uri);
@ -2157,6 +2179,18 @@ abstract class PhabricatorEditEngine
return $request->getURIData('editAction');
}
protected function newCommentActionGroups() {
return array();
}
protected function newAutomaticCommentTransactions($object) {
return array();
}
protected function newCommentPreviewContent($object, array $xactions) {
return null;
}
/* -( Form Pages )--------------------------------------------------------- */

View file

@ -0,0 +1,51 @@
<?php
final class PhabricatorApplyEditField
extends PhabricatorEditField {
private $actionDescription;
private $actionConflictKey;
protected function newControl() {
return null;
}
public function setActionDescription($action_description) {
$this->actionDescription = $action_description;
return $this;
}
public function getActionDescription() {
return $this->actionDescription;
}
public function setActionConflictKey($action_conflict_key) {
$this->actionConflictKey = $action_conflict_key;
return $this;
}
public function getActionConflictKey() {
return $this->actionConflictKey;
}
protected function newHTTPParameterType() {
return new AphrontBoolHTTPParameterType();
}
protected function newConduitParameterType() {
return new ConduitBoolParameterType();
}
public function shouldGenerateTransactionsFromSubmit() {
// This type of edit field just applies a prebuilt action, like "Accept
// Revision", and can not be submitted as part of an "Edit Object" form.
return false;
}
protected function newCommentAction() {
return id(new PhabricatorEditEngineStaticCommentAction())
->setDescription($this->getActionDescription())
->setConflictKey($this->getActionConflictKey());
}
}

View file

@ -25,6 +25,7 @@ abstract class PhabricatorEditField extends Phobject {
private $commentActionLabel;
private $commentActionValue;
private $commentActionGroupKey;
private $commentActionOrder = 1000;
private $hasCommentActionValue;
@ -245,6 +246,15 @@ abstract class PhabricatorEditField extends Phobject {
return $this->commentActionLabel;
}
public function setCommentActionGroupKey($key) {
$this->commentActionGroupKey = $key;
return $this;
}
public function getCommentActionGroupKey() {
return $this->commentActionGroupKey;
}
public function setCommentActionOrder($order) {
$this->commentActionOrder = $order;
return $this;
@ -719,7 +729,8 @@ abstract class PhabricatorEditField extends Phobject {
->setKey($this->getKey())
->setLabel($label)
->setValue($this->getValueForCommentAction($value))
->setOrder($this->getCommentActionOrder());
->setOrder($this->getCommentActionOrder())
->setGroupKey($this->getCommentActionGroupKey());
return $action;
}

View file

@ -7,6 +7,7 @@ final class PhabricatorApplicationTransactionResponse
private $transactions;
private $isPreview;
private $transactionView;
private $previewContent;
public function setTransactionView($transaction_view) {
$this->transactionView = $transaction_view;
@ -46,6 +47,15 @@ final class PhabricatorApplicationTransactionResponse
return $this;
}
public function setPreviewContent($preview_content) {
$this->previewContent = $preview_content;
return $this;
}
public function getPreviewContent() {
return $this->previewContent;
}
public function reduceProxyResponse() {
if ($this->transactionView) {
$view = $this->transactionView;
@ -75,7 +85,8 @@ final class PhabricatorApplicationTransactionResponse
$content = array(
'xactions' => $xactions,
'spacer' => PHUITimelineView::renderSpacer(),
'spacer' => PHUITimelineView::renderSpacer(),
'previewContent' => hsprintf('%s', $this->getPreviewContent()),
);
return $this->getProxy()->setContent($content);

View file

@ -109,6 +109,14 @@ final class PhabricatorEditEngineConfiguration
return $this;
}
public function setBuiltinKey($key) {
if (strpos($key, '/') !== false) {
throw new Exception(
pht('EditEngine BuiltinKey contains an invalid key character "/".'));
}
return parent::setBuiltinKey($key);
}
public function attachEngine(PhabricatorEditEngine $engine) {
$this->engine = $engine;
return $this;

View file

@ -15,25 +15,47 @@ final class PhabricatorEditEngineDatasource
return 'PhabricatorTransactionsApplication';
}
protected function renderSpecialTokens(array $values) {
return $this->renderTokensFromResults($this->buildResults(), $values);
}
public function loadResults() {
$results = $this->buildResults();
return $this->filterResultsAgainstTokens($results);
}
private function buildResults() {
$query = id(new PhabricatorEditEngineConfigurationQuery());
$forms = $this->executeQuery($query);
$results = array();
foreach ($forms as $form) {
if ($form->getID()) {
$key = $form->getEngineKey().'/'.$form->getID();
} else {
$key = $form->getEngineKey().'/'.$form->getBuiltinKey();
}
$result = id(new PhabricatorTypeaheadResult())
->setName($form->getName())
->setPHID($form->getPHID());
->setPHID($key)
->setIcon($form->getIcon());
if ($form->getIsDisabled()) {
$result->setClosed(pht('Archived'));
}
$results[] = $result;
if ($form->getIsDefault()) {
$result->addAttribute(pht('Create Form'));
}
if ($form->getIsEdit()) {
$result->addAttribute(pht('Edit Form'));
}
$results[$key] = $result;
}
return $this->filterResultsAgainstTokens($results);
return $results;
}
}

View file

@ -20,10 +20,12 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
private $headerText;
private $noPermission;
private $fullWidth;
private $infoView;
private $currentVersion;
private $versionedDraft;
private $commentActions;
private $commentActionGroups = array();
private $transactionTimeline;
public function setObjectPHID($object_phid) {
@ -108,6 +110,15 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
return $this;
}
public function setInfoView(PHUIInfoView $info_view) {
$this->infoView = $info_view;
return $this;
}
public function getInfoView() {
return $this->infoView;
}
public function setCommentActions(array $comment_actions) {
assert_instances_of($comment_actions, 'PhabricatorEditEngineCommentAction');
$this->commentActions = $comment_actions;
@ -118,6 +129,16 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
return $this->commentActions;
}
public function setCommentActionGroups(array $groups) {
assert_instances_of($groups, 'PhabricatorEditEngineCommentActionGroup');
$this->commentActionGroups = $groups;
return $this;
}
public function getCommentActionGroups() {
return $this->commentActionGroups;
}
public function setNoPermission($no_permission) {
$this->noPermission = $no_permission;
return $this;
@ -279,16 +300,14 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
'type' => $comment_action->getPHUIXControlType(),
'spec' => $comment_action->getPHUIXControlSpecification(),
'initialValue' => $comment_action->getInitialValue(),
'groupKey' => $comment_action->getGroupKey(),
'conflictKey' => $comment_action->getConflictKey(),
);
$type_map[$key] = $comment_action;
}
$options = array();
$options['+'] = pht('Add Action...');
foreach ($action_map as $key => $item) {
$options[$key] = $item['label'];
}
$options = $this->newCommentActionOptions($action_map);
$action_id = celerity_generate_unique_node_id();
$input_id = celerity_generate_unique_node_id();
@ -326,6 +345,12 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
));
$form->appendChild($action_bar);
$info_view = $this->getInfoView();
if ($info_view) {
$form->appendChild($info_view);
}
$form->appendChild($invisi_bar);
$form->addClass('phui-comment-has-actions');
@ -424,4 +449,46 @@ class PhabricatorApplicationTransactionCommentView extends AphrontView {
return $this->commentID;
}
private function newCommentActionOptions(array $action_map) {
$options = array();
$options['+'] = pht('Add Action...');
// Merge options into groups.
$groups = array();
foreach ($action_map as $key => $item) {
$group_key = $item['groupKey'];
if (!isset($groups[$group_key])) {
$groups[$group_key] = array();
}
$groups[$group_key][$key] = $item;
}
$group_specs = $this->getCommentActionGroups();
$group_labels = mpull($group_specs, 'getLabel', 'getKey');
// Reorder groups to put them in the same order as the recognized
// group definitions.
$groups = array_select_keys($groups, array_keys($group_labels)) + $groups;
// Move options with no group to the end.
$default_group = idx($groups, '');
if ($default_group) {
unset($groups['']);
$groups[''] = $default_group;
}
foreach ($groups as $group_key => $group_items) {
if (strlen($group_key)) {
$group_label = idx($group_labels, $group_key, $group_key);
$options[$group_label] = ipull($group_items, 'label');
} else {
foreach ($group_items as $key => $item) {
$options[$key] = $item['label'];
}
}
}
return $options;
}
}

View file

@ -21,15 +21,15 @@ final class PhabricatorApplicationTransactionTextDiffDetailView
$old_styles = array(
'padding: 0 2px;',
'color: #802b2b;',
'background: rgba(251, 175, 175, .7);',
'color: #333333;',
'background: #f8cbcb;',
);
$old_styles = implode(' ', $old_styles);
$new_styles = array(
'padding: 0 2px;',
'color: #3e6d35;',
'background: rgba(151, 234, 151, .6);',
'color: #333333;',
'background: #a6f3a6;',
);
$new_styles = implode(' ', $new_styles);

View file

@ -55,6 +55,10 @@ class PhabricatorApplicationTransactionView extends AphrontView {
return $this;
}
public function getIsPreview() {
return $this->isPreview;
}
public function setShowEditActions($show_edit_actions) {
$this->showEditActions = $show_edit_actions;
return $this;

View file

@ -65,7 +65,7 @@ In most cases, you can find the upstream commit you've branched from like this:
```
$ git merge-base HEAD origin/master
````
```
Note that if you report a bug and have local commits, we will almost always ask
you to reproduce the issue against a clean copy of Phabricator before we

View file

@ -0,0 +1,75 @@
<?php
final class PHUIDiffInlineCommentPreviewListView
extends AphrontView {
private $inlineComments = array();
private $ownerPHID;
public function setInlineComments(array $comments) {
assert_instances_of($comments, 'PhabricatorApplicationTransactionComment');
$this->inlineComments = $comments;
return $this;
}
public function getInlineComments() {
return $this->inlineComments;
}
public function setOwnerPHID($owner_phid) {
$this->ownerPHID = $owner_phid;
return $this;
}
public function getOwnerPHID() {
return $this->ownerPHID;
}
public function render() {
$viewer = $this->getViewer();
$config = array(
'pht' => array(
'view' => pht('View'),
),
);
Javelin::initBehavior('diff-preview-link', $config);
$inlines = $this->getInlineComments();
foreach ($inlines as $key => $inline) {
$inlines[$key] = DifferentialInlineComment::newFromModernComment(
$inline);
}
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($viewer);
foreach ($inlines as $inline) {
$engine->addObject(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
}
$engine->process();
$owner_phid = $this->getOwnerPHID();
$handles = $viewer->loadHandles(array($viewer->getPHID()));
$handles = iterator_to_array($handles);
$views = array();
foreach ($inlines as $inline) {
$views[] = id(new PHUIDiffInlineCommentDetailView())
->setUser($viewer)
->setInlineComment($inline)
->setMarkupEngine($engine)
->setHandles($handles)
->setEditable(false)
->setPreview(true)
->setCanMarkDone(false)
->setObjectOwnerPHID($owner_phid);
}
return $views;
}
}

View file

@ -1,6 +1,6 @@
<?php
final class PhabricatorFileLinkView extends AphrontView {
final class PhabricatorFileLinkView extends AphrontTagView {
private $fileName;
private $fileDownloadURI;
@ -87,7 +87,7 @@ final class PhabricatorFileLinkView extends AphrontView {
return FileTypeIcon::getFileIcon($this->getFileName());
}
public function getMetadata() {
public function getMeta() {
return array(
'phid' => $this->getFilePHID(),
'viewable' => $this->getFileViewable(),
@ -100,21 +100,55 @@ final class PhabricatorFileLinkView extends AphrontView {
);
}
public function render() {
require_celerity_resource('phabricator-remarkup-css');
require_celerity_resource('phui-lightbox-css');
protected function getTagName() {
return 'div';
}
protected function getTagAttributes() {
$mustcapture = true;
$sigil = 'lightboxable';
$meta = $this->getMetadata();
$meta = $this->getMeta();
$class = 'phabricator-remarkup-embed-layout-link';
if ($this->getCustomClass()) {
$class = $this->getCustomClass();
}
return array(
'href' => $this->getFileViewURI(),
'class' => $class,
'sigil' => $sigil,
'meta' => $meta,
'mustcapture' => $mustcapture,
);
}
protected function getTagContent() {
require_celerity_resource('phabricator-remarkup-css');
require_celerity_resource('phui-lightbox-css');
$icon = id(new PHUIIconView())
->setIcon($this->getFileIcon());
->setIcon($this->getFileIcon())
->addClass('phabricator-remarkup-embed-layout-icon');
$dl_icon = id(new PHUIIconView())
->setIcon('fa-download');
$download_form = phabricator_form(
$this->getViewer(),
array(
'action' => $this->getFileDownloadURI(),
'method' => 'POST',
'class' => 'embed-download-form',
'sigil' => 'embed-download-form download',
),
phutil_tag(
'button',
array(
'class' => 'phabricator-remarkup-embed-layout-download',
'type' => 'submit',
),
pht('Download')));
$info = phutil_tag(
'span',
@ -140,18 +174,10 @@ final class PhabricatorFileLinkView extends AphrontView {
$info,
));
return javelin_tag(
'a',
array(
'href' => $this->getFileViewURI(),
'class' => $class,
'sigil' => $sigil,
'meta' => $meta,
'mustcapture' => $mustcapture,
),
array(
$icon,
$inner,
));
return array(
$icon,
$inner,
$download_form,
);
}
}

View file

@ -108,12 +108,9 @@
padding: 0 2px;
}
.prose-diff span.old {
color: {$redtext};
}
.prose-diff span.old,
.prose-diff span.new {
color: {$greentext};
color: {$darkgreytext};
}
.differential-diff th.selected {
@ -124,12 +121,24 @@
cursor: auto;
}
.differential-diff td.old {
background: rgba(251, 175, 175, .3);
.differential-diff th.old {
background: #ffdddd;
border-right-color: #f8cbcb;
}
.differential-diff td.new {
background: rgba(151, 234, 151, .3);
.differential-diff th.new {
background: #dbffdb;
border-right-color: #a6f3a6;
}
.differential-diff td.old,
.differential-diff td.old-full {
background: #ffecec;
}
.differential-diff td.new,
.differential-diff td.new-full {
background: #eaffea;
}
.differential-diff td.old-rebase {
@ -140,16 +149,14 @@
background: #eeffee;
}
.differential-diff td.old-full,
.differential-diff td.old span.bright,
.prose-diff span.old {
background: rgba(251, 175, 175, .7);
background: #f8cbcb;
}
.differential-diff td.new-full,
.differential-diff td.new span.bright,
.prose-diff span.new {
background: rgba(151, 234, 151, .6);
background: #a6f3a6;
}
.differential-diff td.copy {

View file

@ -66,7 +66,7 @@
border-radius: 3px;
box-shadow: inset 0 -1px 0 rgba({$alphablue},0.08);
user-select: none;
background: #f7f7f7;
background: {$lightgreybackground};
border: 1px solid {$lightgreyborder};
}
@ -370,7 +370,7 @@ video.phabricator-media {
}
.phabricator-remarkup-embed-layout-link {
padding: 8px 8px 8px 32px;
padding: 6px 6px 6px 42px;
border-radius: 3px;
margin: 0 0 4px;
display: inline-block;
@ -379,18 +379,17 @@ video.phabricator-media {
border: 1px solid {$lightblueborder};
border-radius: 3px;
color: #000;
min-width: 240px;
min-width: 256px;
position: relative;
height: 22px;
/*height: 22px;*/
line-height: 20px;
}
.phabricator-remarkup-embed-layout-link .phui-icon-view {
margin-right: 8px;
font-size: 20px;
.phabricator-remarkup-embed-layout-icon {
font-size: 28px;
position: absolute;
top: 8px;
left: 8px;
top: 10px;
left: 10px;
}
.phabricator-remarkup-embed-layout-info {
@ -402,11 +401,46 @@ video.phabricator-media {
.phabricator-remarkup-embed-layout-link:hover {
border-color: {$violet};
cursor: pointer;
text-decoration: none;
}
.phabricator-remarkup-embed-layout-link:hover .phui-icon-view {
color: {$violet};
.phabricator-remarkup-embed-layout-link:hover
.phabricator-remarkup-embed-layout-icon {
color: {$violet};
}
.phabricator-remarkup-embed-layout-info-block {
display: block;
}
.embed-download-form {
display: inline-block;
padding: 0;
margin: 0;
}
.phabricator-remarkup-embed-layout-link
.phabricator-remarkup-embed-layout-download {
color: {$lightgreytext};
border: none;
background: rgba(0, 0, 0, 0);
box-shadow: none;
outline: 0;
padding: 0;
margin: 0;
text-align: left;
text-shadow: none;
border-radius: 0;
font: inherit;
display: inline;
min-width: 0;
font-weight: normal !important;
}
.phabricator-remarkup-embed-layout-download:hover {
color: {$anchor};
text-decoration: underline;
}
.phabricator-remarkup-embed-float-left {

View file

@ -163,3 +163,11 @@ body.device .phui-box.phui-object-box.phui-comment-form-view {
.phui-comment-form-view .aphront-form-error .phui-icon-view {
padding: 4px;
}
.phui-comment-form-view .phui-form-view .phui-info-view {
margin: 16px;
}
.device-phone .phui-comment-form-view .phui-form-view .phui-info-view {
margin: 8px;
}

View file

@ -545,3 +545,8 @@ properly, and submit values. */
.device-desktop .aphront-form-error .phui-icon-view:hover {
color: {$red};
}
.phui-form-static-action {
padding: 4px;
color: {$bluetext};
}

View file

@ -0,0 +1,36 @@
/**
* @provides javelin-behavior-diff-preview-link
* @requires javelin-behavior
* javelin-stratcom
* javelin-dom
*/
JX.behavior('diff-preview-link', function(config, statics) {
if (statics.initialized) {
return;
}
statics.initialized = true;
var pht = JX.phtize(config.pht);
// After inline comment previews are rendered, hook up the links to the
// comments that are visible on the current page.
function link_inline_preview(e) {
var root = e.getData().rootNode;
var links = JX.DOM.scry(root, 'a', 'differential-inline-preview-jump');
for (var ii = 0; ii < links.length; ii++) {
var data = JX.Stratcom.getData(links[ii]);
try {
JX.$(data.anchor);
links[ii].href = '#' + data.anchor;
JX.DOM.setContent(links[ii], pht('view'));
} catch (ignored) {
// This inline comment isn't visible, e.g. on some other diff.
}
}
}
JX.Stratcom.listen('EditEngine.didCommentPreview', null, link_inline_preview);
});

View file

@ -183,7 +183,7 @@ JX.install('DifferentialInlineCommentEditor', {
this._completed = true;
JX.Stratcom.invoke('differential-inline-comment-update');
this._didUpdate();
this.invoke('done');
},
@ -335,7 +335,15 @@ JX.install('DifferentialInlineCommentEditor', {
},
_didUpdate: function() {
JX.Stratcom.invoke('differential-inline-comment-update');
// After making changes to inline comments, refresh the transaction
// preview at the bottom of the page.
// TODO: This isn't the cleanest way to find the preview form, but
// rendering no longer has direct access to it.
var forms = JX.DOM.scry(document.body, 'form', 'transaction-append');
if (forms.length) {
JX.DOM.invoke(forms[0], 'shouldRefresh');
}
}
},

View file

@ -43,59 +43,13 @@ JX.behavior('comment-actions', function(config) {
return null;
}
function add_row(option) {
var action = action_map[option.value];
if (!action) {
return;
function remove_action(key) {
var row = rows[key];
if (row) {
JX.DOM.remove(row.node);
row.option.disabled = false;
delete rows[key];
}
option.disabled = true;
var icon = new JX.PHUIXIconView()
.setIcon('fa-times-circle');
var remove = JX.$N('a', {href: '#'}, icon.getNode());
var control = new JX.PHUIXFormControl()
.setLabel(action.label)
.setError(remove)
.setControl(action.type, action.spec)
.setClass('phui-comment-action');
var node = control.getNode();
JX.Stratcom.addSigil(node, 'touchable');
var remove_action = function() {
JX.DOM.remove(node);
delete rows[action.key];
option.disabled = false;
};
JX.DOM.listen(node, 'gesture.swipe.end', null, function(e) {
var data = e.getData();
if (data.direction != 'left') {
// Didn't swipe left.
return;
}
if (data.length <= (JX.Vector.getDim(node).x / 2)) {
// Didn't swipe far enough.
return;
}
remove_action();
});
rows[action.key] = control;
JX.DOM.listen(remove, 'click', null, function(e) {
e.kill();
remove_action();
});
place_node.parentNode.insertBefore(node, place_node);
return control;
}
function serialize_actions() {
@ -104,7 +58,7 @@ JX.behavior('comment-actions', function(config) {
for (var k in rows) {
data.push({
type: k,
value: rows[k].getValue(),
value: rows[k].control.getValue(),
initialValue: action_map[k].initialValue || null
});
}
@ -147,13 +101,115 @@ JX.behavior('comment-actions', function(config) {
if (!response.xactions.length) {
JX.DOM.hide(panel);
} else {
var preview_root = JX.$(config.timelineID);
JX.DOM.setContent(
JX.$(config.timelineID),
JX.$H(response.xactions.join('')));
preview_root,
[
JX.$H(response.xactions.join('')),
JX.$H(response.previewContent)
]);
JX.DOM.show(panel);
// NOTE: Resonses are currently processed before associated behaviors are
// registered. We need to defer invoking this event so that any behaviors
// accompanying the response are registered.
var invoke_preview = function() {
JX.Stratcom.invoke(
'EditEngine.didCommentPreview',
null,
{
rootNode: preview_root
});
};
setTimeout(invoke_preview, 0);
}
}
function force_preview() {
if (!config.shouldPreview) {
return;
}
new JX.Request(config.actionURI, onresponse)
.setData(get_data())
.send();
}
function add_row(option) {
var action = action_map[option.value];
if (!action) {
return;
}
// Remove any conflicting actions. For example, "Accept Revision" conflicts
// with "Reject Revision".
var conflict_key = action.conflictKey || null;
if (conflict_key !== null) {
for (var k in action_map) {
if (k === action.key) {
continue;
}
if (action_map[k].conflictKey !== conflict_key) {
continue;
}
if (!(k in rows)) {
continue;
}
remove_action(k);
}
}
option.disabled = true;
var icon = new JX.PHUIXIconView()
.setIcon('fa-times-circle');
var remove = JX.$N('a', {href: '#'}, icon.getNode());
var control = new JX.PHUIXFormControl()
.setLabel(action.label)
.setError(remove)
.setControl(action.type, action.spec)
.setClass('phui-comment-action');
var node = control.getNode();
JX.Stratcom.addSigil(node, 'touchable');
JX.DOM.listen(node, 'gesture.swipe.end', null, function(e) {
var data = e.getData();
if (data.direction != 'left') {
// Didn't swipe left.
return;
}
if (data.length <= (JX.Vector.getDim(node).x / 2)) {
// Didn't swipe far enough.
return;
}
remove_action(action.key);
});
rows[action.key] = {
control: control,
node: node,
option: option
};
JX.DOM.listen(remove, 'click', null, function(e) {
e.kill();
remove_action(action.key);
});
place_node.parentNode.insertBefore(node, place_node);
force_preview();
return control;
}
JX.DOM.listen(form_node, ['submit', 'didSyntheticSubmit'], null, function() {
input_node.value = serialize_actions();
});
@ -168,13 +224,7 @@ JX.behavior('comment-actions', function(config) {
JX.DOM.listen(form_node, 'keydown', null, trigger);
var always_trigger = function() {
new JX.Request(config.actionURI, onresponse)
.setData(get_data())
.send();
};
JX.DOM.listen(form_node, 'shouldRefresh', null, always_trigger);
JX.DOM.listen(form_node, 'shouldRefresh', null, force_preview);
request.start();
var old_device = JX.Device.getDevice();
@ -189,7 +239,7 @@ JX.behavior('comment-actions', function(config) {
// Force an immediate refresh if we switched from another device type
// to desktop.
if (old_device != new_device) {
always_trigger();
force_preview();
}
} else {
// On mobile, don't show live previews and only save drafts every

View file

@ -65,15 +65,48 @@ JX.install('Tooltip', {
_getSmartPosition: function (align, root, node) {
var self = JX.Tooltip;
var pos = self._proposePosition(align, root, node);
// If toolip is offscreen, try to be clever
if (!JX.Tooltip.isOnScreen(pos, node)) {
align = self._getImprovedOrientation(pos, node);
pos = self._proposePosition(align, root, node);
// Figure out how to position the tooltip on screen. We will try the
// configured aligment first.
var try_alignments = [align];
// If the configured alignment does not fit, we'll try the opposite
// alignment.
var opposites = {
N: 'S',
S: 'N',
E: 'W',
W: 'E'
};
try_alignments.push(opposites[align]);
// Then we'll try the other alignments, in arbitrary order.
for (var k in opposites) {
try_alignments.push(k);
}
self._setAnchor(align);
var use_alignment = null;
var use_pos = null;
for (var ii = 0; ii < try_alignments.length; ii++) {
var try_alignment = try_alignments[ii];
var pos = self._proposePosition(try_alignment, root, node);
if (self.isOnScreen(pos, node)) {
use_alignment = try_alignment;
use_pos = pos;
break;
}
}
// If we don't come up with a good answer, default to the configured
// alignment.
if (use_alignment === null) {
use_alignment = align;
use_pos = self._proposePosition(use_alignment, root, node);
}
self._setAnchor(use_alignment);
return pos;
},
@ -108,56 +141,24 @@ JX.install('Tooltip', {
},
isOnScreen: function (a, node) {
var s = JX.Vector.getScroll();
var v = JX.Vector.getViewport();
var max_x = s.x + v.x;
var max_y = s.y + v.y;
var view = this._getViewBoundaries();
var corners = this._getNodeCornerPositions(a, node);
// Check if any of the corners are offscreen
// Check if any of the corners are offscreen.
for (var i = 0; i < corners.length; i++) {
var corner = corners[i];
if (corner.x < s.x ||
corner.y < s.y ||
corner.x > max_x ||
corner.y > max_y) {
if (corner.x < view.w ||
corner.y < view.n ||
corner.x > view.e ||
corner.y > view.s) {
return false;
}
}
return true;
},
_getImprovedOrientation: function (a, node) {
// Try to predict the "more correct" orientation
var s = JX.Vector.getScroll();
var v = JX.Vector.getViewport();
var max_x = s.x + v.x;
var max_y = s.y + v.y;
var corners = this._getNodeCornerPositions(a, node);
for (var i = 0; i < corners.length; i++) {
var corner = corners[i];
if (corner.y < v.y) {
return 'S';
} else
if (corner.x < v.x) {
return 'E';
} else
if (corner.y > max_y) {
return 'N';
} else
if (corner.x > max_x) {
return 'W';
} else {
return 'N';
}
}
},
_getNodeCornerPositions: function(pos, node) {
// Get positions of all four corners of a node
// Get positions of all four corners of a node.
var n = JX.Vector.getDim(node);
return [new JX.Vector(pos.x, pos.y),
new JX.Vector(pos.x + n.x, pos.y),
@ -165,6 +166,24 @@ JX.install('Tooltip', {
new JX.Vector(pos.x + n.x, pos.y + n.y)];
},
_getViewBoundaries: function() {
var s = JX.Vector.getScroll();
var v = JX.Vector.getViewport();
var max_x = s.x + v.x;
var max_y = s.y + v.y;
// Even if the corner is technically on the screen, don't allow the
// tip to display too close to the edge of the screen.
var margin = 16;
return {
w: s.x + margin,
e: max_x - margin,
n: s.y + margin,
s: max_y - margin
};
},
_setAnchor: function (align) {
// Orient the little tail
JX.DOM.alterClass(this._node, 'jx-tooltip-align-' + align, true);

View file

@ -51,7 +51,7 @@ JX.behavior('lightbox-attachments', function (config) {
e.kill();
var mainFrame = JX.$('main-page-frame');
var links = JX.DOM.scry(mainFrame, 'a', 'lightboxable');
var links = JX.DOM.scry(mainFrame, '*', 'lightboxable');
var phids = {};
var data;
for (var i = 0; i < links.length; i++) {
@ -371,9 +371,20 @@ JX.behavior('lightbox-attachments', function (config) {
form.submit();
};
var _startPageDownload = function(e) {
e.kill();
var form = e.getNode('tag:form');
form.submit();
};
JX.Stratcom.listen(
'click',
'lightbox-download-submit',
_startDownload);
JX.Stratcom.listen(
'click',
'embed-download-form',
_startPageDownload);
});

View file

@ -47,6 +47,9 @@ JX.install('PHUIXFormControl', {
case 'optgroups':
input = this._newOptgroups(spec);
break;
case 'static':
input = this._newStatic(spec);
break;
default:
// TODO: Default or better error?
JX.$E('Bad Input Type');
@ -172,6 +175,25 @@ JX.install('PHUIXFormControl', {
};
},
_newStatic: function(spec) {
var node = JX.$N(
'div',
{
className: 'phui-form-static-action'
},
spec.description || '');
return {
node: node,
get: function() {
return true;
},
set: function() {
return;
}
};
},
_newPoints: function(spec) {
var attrs = {
type: 'text',