mirror of
https://we.phorge.it/source/phorge.git
synced 2025-01-07 05:11:05 +01:00
(stable) Promote 2019 Week 8
This commit is contained in:
commit
5d88eef26b
93 changed files with 2432 additions and 986 deletions
|
@ -9,10 +9,10 @@ return array(
|
|||
'names' => array(
|
||||
'conpherence.pkg.css' => '3c8a0668',
|
||||
'conpherence.pkg.js' => '020aebcf',
|
||||
'core.pkg.css' => '85a1da99',
|
||||
'core.pkg.js' => '5c737607',
|
||||
'differential.pkg.css' => 'b8df73d4',
|
||||
'differential.pkg.js' => '67c9ea4c',
|
||||
'core.pkg.css' => 'e3c1a8f2',
|
||||
'core.pkg.js' => '2cda17a4',
|
||||
'differential.pkg.css' => 'ab23bd75',
|
||||
'differential.pkg.js' => '67e02996',
|
||||
'diffusion.pkg.css' => '42c75c37',
|
||||
'diffusion.pkg.js' => '91192d85',
|
||||
'maniphest.pkg.css' => '35995d6d',
|
||||
|
@ -61,8 +61,8 @@ return array(
|
|||
'rsrc/css/application/dashboard/dashboard.css' => '4267d6c6',
|
||||
'rsrc/css/application/diff/inline-comment-summary.css' => '81eb368d',
|
||||
'rsrc/css/application/differential/add-comment.css' => '7e5900d9',
|
||||
'rsrc/css/application/differential/changeset-view.css' => '73660575',
|
||||
'rsrc/css/application/differential/core.css' => 'bdb93065',
|
||||
'rsrc/css/application/differential/changeset-view.css' => 'd92bed0d',
|
||||
'rsrc/css/application/differential/core.css' => '7300a73e',
|
||||
'rsrc/css/application/differential/phui-inline-comment.css' => '48acce5b',
|
||||
'rsrc/css/application/differential/revision-comment.css' => '7dbc8d1d',
|
||||
'rsrc/css/application/differential/revision-history.css' => '8aa3eac5',
|
||||
|
@ -112,7 +112,7 @@ return array(
|
|||
'rsrc/css/application/uiexample/example.css' => 'b4795059',
|
||||
'rsrc/css/core/core.css' => '1b29ed61',
|
||||
'rsrc/css/core/remarkup.css' => '9e627d41',
|
||||
'rsrc/css/core/syntax.css' => '8a16f91b',
|
||||
'rsrc/css/core/syntax.css' => '4234f572',
|
||||
'rsrc/css/core/z-index.css' => '99c0f5eb',
|
||||
'rsrc/css/diviner/diviner-shared.css' => '4bd263b0',
|
||||
'rsrc/css/font/font-awesome.css' => '3883938a',
|
||||
|
@ -154,7 +154,7 @@ return array(
|
|||
'rsrc/css/phui/phui-form-view.css' => '01b796c0',
|
||||
'rsrc/css/phui/phui-form.css' => '159e2d9c',
|
||||
'rsrc/css/phui/phui-head-thing.css' => 'd7f293df',
|
||||
'rsrc/css/phui/phui-header-view.css' => '93cea4ec',
|
||||
'rsrc/css/phui/phui-header-view.css' => '285c9139',
|
||||
'rsrc/css/phui/phui-hovercard.css' => '6ca90fa0',
|
||||
'rsrc/css/phui/phui-icon-set-selector.css' => '7aa5f3ec',
|
||||
'rsrc/css/phui/phui-icon.css' => '4cbc684a',
|
||||
|
@ -172,7 +172,7 @@ return array(
|
|||
'rsrc/css/phui/phui-segment-bar-view.css' => '5166b370',
|
||||
'rsrc/css/phui/phui-spacing.css' => 'b05cadc3',
|
||||
'rsrc/css/phui/phui-status.css' => 'e5ff8be0',
|
||||
'rsrc/css/phui/phui-tag-view.css' => 'a42fe34f',
|
||||
'rsrc/css/phui/phui-tag-view.css' => '29409667',
|
||||
'rsrc/css/phui/phui-timeline-view.css' => '1e348e4b',
|
||||
'rsrc/css/phui/phui-two-column-view.css' => '01e6991e',
|
||||
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'e86de308',
|
||||
|
@ -275,6 +275,8 @@ return array(
|
|||
'rsrc/image/checker_dark.png' => '7fc8fa7b',
|
||||
'rsrc/image/checker_light.png' => '3157a202',
|
||||
'rsrc/image/checker_lighter.png' => 'c45928c1',
|
||||
'rsrc/image/chevron-in.png' => '1aa2f88f',
|
||||
'rsrc/image/chevron-out.png' => 'c815e272',
|
||||
'rsrc/image/controls/checkbox-checked.png' => '1770d7a0',
|
||||
'rsrc/image/controls/checkbox-unchecked.png' => 'e1deba0a',
|
||||
'rsrc/image/d5d8e1.png' => '6764616e',
|
||||
|
@ -372,13 +374,12 @@ return array(
|
|||
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => '076bd092',
|
||||
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9',
|
||||
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '9b1cbd76',
|
||||
'rsrc/js/application/diff/DiffChangeset.js' => 'e7cf10d6',
|
||||
'rsrc/js/application/diff/DiffChangesetList.js' => 'b91204e9',
|
||||
'rsrc/js/application/diff/DiffChangeset.js' => 'd0a85a85',
|
||||
'rsrc/js/application/diff/DiffChangesetList.js' => '04023d82',
|
||||
'rsrc/js/application/diff/DiffInline.js' => 'a4a14a94',
|
||||
'rsrc/js/application/diff/behavior-preview-link.js' => 'f51e9c17',
|
||||
'rsrc/js/application/differential/behavior-diff-radios.js' => '925fe8cd',
|
||||
'rsrc/js/application/differential/behavior-populate.js' => 'dfa1d313',
|
||||
'rsrc/js/application/differential/behavior-user-select.js' => 'e18685c0',
|
||||
'rsrc/js/application/diffusion/DiffusionLocateFileSource.js' => '94243d89',
|
||||
'rsrc/js/application/diffusion/behavior-audit-preview.js' => 'b7b73831',
|
||||
'rsrc/js/application/diffusion/behavior-commit-branches.js' => '4b671572',
|
||||
|
@ -418,7 +419,7 @@ return array(
|
|||
'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68',
|
||||
'rsrc/js/application/releeph/releeph-request-state-change.js' => '9f081f05',
|
||||
'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'aa3a100c',
|
||||
'rsrc/js/application/repository/repository-crossreference.js' => 'db0c0214',
|
||||
'rsrc/js/application/repository/repository-crossreference.js' => 'c15122b4',
|
||||
'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e5bdb730',
|
||||
'rsrc/js/application/search/behavior-reorder-queries.js' => 'b86f297f',
|
||||
'rsrc/js/application/transactions/behavior-comment-actions.js' => '4dffaeb2',
|
||||
|
@ -441,7 +442,7 @@ return array(
|
|||
'rsrc/js/core/KeyboardShortcutManager.js' => '37b8a04a',
|
||||
'rsrc/js/core/MultirowRowManager.js' => '5b54c823',
|
||||
'rsrc/js/core/Notification.js' => 'a9b91e3f',
|
||||
'rsrc/js/core/Prefab.js' => 'bf457520',
|
||||
'rsrc/js/core/Prefab.js' => '5793d835',
|
||||
'rsrc/js/core/ShapedRequest.js' => 'abf88db8',
|
||||
'rsrc/js/core/TextAreaUtils.js' => 'f340a484',
|
||||
'rsrc/js/core/Title.js' => '43bc9360',
|
||||
|
@ -471,7 +472,7 @@ return array(
|
|||
'rsrc/js/core/behavior-linked-container.js' => '74446546',
|
||||
'rsrc/js/core/behavior-more.js' => '506aa3f4',
|
||||
'rsrc/js/core/behavior-object-selector.js' => 'a4af0b4a',
|
||||
'rsrc/js/core/behavior-oncopy.js' => '418f6684',
|
||||
'rsrc/js/core/behavior-oncopy.js' => 'ff7b3f22',
|
||||
'rsrc/js/core/behavior-phabricator-nav.js' => 'f166c949',
|
||||
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '2f80333f',
|
||||
'rsrc/js/core/behavior-read-only-warning.js' => 'b9109f8f',
|
||||
|
@ -505,7 +506,7 @@ return array(
|
|||
'rsrc/js/phui/behavior-phui-timer-control.js' => 'f84bcbf4',
|
||||
'rsrc/js/phuix/PHUIXActionListView.js' => 'c68f183f',
|
||||
'rsrc/js/phuix/PHUIXActionView.js' => 'aaa08f3b',
|
||||
'rsrc/js/phuix/PHUIXAutocomplete.js' => '58cc4ab8',
|
||||
'rsrc/js/phuix/PHUIXAutocomplete.js' => '8f139ef0',
|
||||
'rsrc/js/phuix/PHUIXButtonView.js' => '55a24e84',
|
||||
'rsrc/js/phuix/PHUIXDropdownMenu.js' => 'bdce4d78',
|
||||
'rsrc/js/phuix/PHUIXExample.js' => 'c2c500a7',
|
||||
|
@ -539,8 +540,8 @@ return array(
|
|||
'conpherence-thread-manager' => 'aec8e38c',
|
||||
'conpherence-transaction-css' => '3a3f5e7e',
|
||||
'd3' => 'd67475f5',
|
||||
'differential-changeset-view-css' => '73660575',
|
||||
'differential-core-view-css' => 'bdb93065',
|
||||
'differential-changeset-view-css' => 'd92bed0d',
|
||||
'differential-core-view-css' => '7300a73e',
|
||||
'differential-revision-add-comment-css' => '7e5900d9',
|
||||
'differential-revision-comment-css' => '7dbc8d1d',
|
||||
'differential-revision-history-css' => '8aa3eac5',
|
||||
|
@ -594,7 +595,6 @@ return array(
|
|||
'javelin-behavior-diff-preview-link' => 'f51e9c17',
|
||||
'javelin-behavior-differential-diff-radios' => '925fe8cd',
|
||||
'javelin-behavior-differential-populate' => 'dfa1d313',
|
||||
'javelin-behavior-differential-user-select' => 'e18685c0',
|
||||
'javelin-behavior-diffusion-commit-branches' => '4b671572',
|
||||
'javelin-behavior-diffusion-commit-graph' => '1c88f154',
|
||||
'javelin-behavior-diffusion-locate-file' => '87428eb2',
|
||||
|
@ -634,7 +634,7 @@ return array(
|
|||
'javelin-behavior-phabricator-nav' => 'f166c949',
|
||||
'javelin-behavior-phabricator-notification-example' => '29819b75',
|
||||
'javelin-behavior-phabricator-object-selector' => 'a4af0b4a',
|
||||
'javelin-behavior-phabricator-oncopy' => '418f6684',
|
||||
'javelin-behavior-phabricator-oncopy' => 'ff7b3f22',
|
||||
'javelin-behavior-phabricator-remarkup-assist' => '2f80333f',
|
||||
'javelin-behavior-phabricator-reveal-content' => 'b105a3a6',
|
||||
'javelin-behavior-phabricator-search-typeahead' => '1cb7d027',
|
||||
|
@ -669,7 +669,7 @@ return array(
|
|||
'javelin-behavior-reorder-applications' => 'aa371860',
|
||||
'javelin-behavior-reorder-columns' => '8ac32fd9',
|
||||
'javelin-behavior-reorder-profile-menu-items' => 'e5bdb730',
|
||||
'javelin-behavior-repository-crossreference' => 'db0c0214',
|
||||
'javelin-behavior-repository-crossreference' => 'c15122b4',
|
||||
'javelin-behavior-scrollbar' => '92388bae',
|
||||
'javelin-behavior-search-reorder-queries' => 'b86f297f',
|
||||
'javelin-behavior-select-content' => 'e8240b50',
|
||||
|
@ -751,8 +751,8 @@ return array(
|
|||
'phabricator-darklog' => '3b869402',
|
||||
'phabricator-darkmessage' => '26cd4b73',
|
||||
'phabricator-dashboard-css' => '4267d6c6',
|
||||
'phabricator-diff-changeset' => 'e7cf10d6',
|
||||
'phabricator-diff-changeset-list' => 'b91204e9',
|
||||
'phabricator-diff-changeset' => 'd0a85a85',
|
||||
'phabricator-diff-changeset-list' => '04023d82',
|
||||
'phabricator-diff-inline' => 'a4a14a94',
|
||||
'phabricator-drag-and-drop-file-upload' => '4370900d',
|
||||
'phabricator-draggable-list' => '3c6bd549',
|
||||
|
@ -771,7 +771,7 @@ return array(
|
|||
'phabricator-notification-menu-css' => 'e6962e89',
|
||||
'phabricator-object-selector-css' => 'ee77366f',
|
||||
'phabricator-phtize' => '2f1db1ed',
|
||||
'phabricator-prefab' => 'bf457520',
|
||||
'phabricator-prefab' => '5793d835',
|
||||
'phabricator-remarkup-css' => '9e627d41',
|
||||
'phabricator-search-results-css' => '9ea70ace',
|
||||
'phabricator-shaped-request' => 'abf88db8',
|
||||
|
@ -821,7 +821,7 @@ return array(
|
|||
'phui-form-css' => '159e2d9c',
|
||||
'phui-form-view-css' => '01b796c0',
|
||||
'phui-head-thing-view-css' => 'd7f293df',
|
||||
'phui-header-view-css' => '93cea4ec',
|
||||
'phui-header-view-css' => '285c9139',
|
||||
'phui-hovercard' => '074f0783',
|
||||
'phui-hovercard-view-css' => '6ca90fa0',
|
||||
'phui-icon-set-selector-css' => '7aa5f3ec',
|
||||
|
@ -847,7 +847,7 @@ return array(
|
|||
'phui-segment-bar-view-css' => '5166b370',
|
||||
'phui-spacing-css' => 'b05cadc3',
|
||||
'phui-status-list-view-css' => 'e5ff8be0',
|
||||
'phui-tag-view-css' => 'a42fe34f',
|
||||
'phui-tag-view-css' => '29409667',
|
||||
'phui-theme-css' => '35883b37',
|
||||
'phui-timeline-view-css' => '1e348e4b',
|
||||
'phui-two-column-view-css' => '01e6991e',
|
||||
|
@ -857,7 +857,7 @@ return array(
|
|||
'phui-workpanel-view-css' => 'bd546a49',
|
||||
'phuix-action-list-view' => 'c68f183f',
|
||||
'phuix-action-view' => 'aaa08f3b',
|
||||
'phuix-autocomplete' => '58cc4ab8',
|
||||
'phuix-autocomplete' => '8f139ef0',
|
||||
'phuix-button-view' => '55a24e84',
|
||||
'phuix-dropdown-menu' => 'bdce4d78',
|
||||
'phuix-form-control-view' => '38c1f3fb',
|
||||
|
@ -876,7 +876,7 @@ return array(
|
|||
'sprite-login-css' => '18b368a6',
|
||||
'sprite-tokens-css' => 'f1896dc5',
|
||||
'syntax-default-css' => '055fc231',
|
||||
'syntax-highlighting-css' => '8a16f91b',
|
||||
'syntax-highlighting-css' => '4234f572',
|
||||
'tokens-css' => 'ce5a50bd',
|
||||
'typeahead-browse-css' => 'b7ed02d2',
|
||||
'unhandled-exception-css' => '9ecfc00d',
|
||||
|
@ -905,6 +905,10 @@ return array(
|
|||
'javelin-uri',
|
||||
'javelin-util',
|
||||
),
|
||||
'04023d82' => array(
|
||||
'javelin-install',
|
||||
'phuix-button-view',
|
||||
),
|
||||
'04f8a1e3' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1216,9 +1220,8 @@ return array(
|
|||
'javelin-behavior',
|
||||
'javelin-uri',
|
||||
),
|
||||
'418f6684' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'4234f572' => array(
|
||||
'syntax-default-css',
|
||||
),
|
||||
'42c7a5a7' => array(
|
||||
'javelin-install',
|
||||
|
@ -1354,6 +1357,18 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-dom',
|
||||
),
|
||||
'5793d835' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
'javelin-dom',
|
||||
'javelin-typeahead',
|
||||
'javelin-tokenizer',
|
||||
'javelin-typeahead-preloaded-source',
|
||||
'javelin-typeahead-ondemand-source',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
),
|
||||
'5803b9e7' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-util',
|
||||
|
@ -1362,12 +1377,6 @@ return array(
|
|||
'javelin-vector',
|
||||
'javelin-typeahead-static-source',
|
||||
),
|
||||
'58cc4ab8' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'phuix-icon-view',
|
||||
'phabricator-prefab',
|
||||
),
|
||||
'5902260c' => array(
|
||||
'javelin-util',
|
||||
'javelin-magical-init',
|
||||
|
@ -1484,9 +1493,6 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-uri',
|
||||
),
|
||||
73660575 => array(
|
||||
'phui-inline-comment-view-css',
|
||||
),
|
||||
'73ecc1f8' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-behavior-device',
|
||||
|
@ -1575,9 +1581,6 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-install',
|
||||
),
|
||||
'8a16f91b' => array(
|
||||
'syntax-default-css',
|
||||
),
|
||||
'8ac32fd9' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -1608,6 +1611,12 @@ return array(
|
|||
'8e2d9a28' => array(
|
||||
'phui-theme-css',
|
||||
),
|
||||
'8f139ef0' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
'phuix-icon-view',
|
||||
'phabricator-prefab',
|
||||
),
|
||||
'8f959ad0' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -1881,10 +1890,6 @@ return array(
|
|||
'javelin-uri',
|
||||
'phabricator-notification',
|
||||
),
|
||||
'b91204e9' => array(
|
||||
'javelin-install',
|
||||
'phuix-button-view',
|
||||
),
|
||||
'bd546a49' => array(
|
||||
'phui-workcard-view-css',
|
||||
),
|
||||
|
@ -1895,21 +1900,15 @@ return array(
|
|||
'javelin-vector',
|
||||
'javelin-stratcom',
|
||||
),
|
||||
'bf457520' => array(
|
||||
'javelin-install',
|
||||
'javelin-util',
|
||||
'javelin-dom',
|
||||
'javelin-typeahead',
|
||||
'javelin-tokenizer',
|
||||
'javelin-typeahead-preloaded-source',
|
||||
'javelin-typeahead-ondemand-source',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
),
|
||||
'c03f2fb4' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
'c15122b4' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-uri',
|
||||
),
|
||||
'c2c500a7' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
|
@ -1971,6 +1970,17 @@ return array(
|
|||
'javelin-stratcom',
|
||||
'javelin-util',
|
||||
),
|
||||
'd0a85a85' => array(
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
'javelin-install',
|
||||
'javelin-workflow',
|
||||
'javelin-router',
|
||||
'javelin-behavior-device',
|
||||
'javelin-vector',
|
||||
'phabricator-diff-inline',
|
||||
),
|
||||
'd12d214f' => array(
|
||||
'javelin-install',
|
||||
'javelin-dom',
|
||||
|
@ -1987,6 +1997,9 @@ return array(
|
|||
'javelin-util',
|
||||
'phabricator-shaped-request',
|
||||
),
|
||||
'd92bed0d' => array(
|
||||
'phui-inline-comment-view-css',
|
||||
),
|
||||
'da15d3dc' => array(
|
||||
'phui-oi-list-view-css',
|
||||
),
|
||||
|
@ -1995,12 +2008,6 @@ return array(
|
|||
'javelin-uri',
|
||||
'phabricator-notification',
|
||||
),
|
||||
'db0c0214' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
'javelin-uri',
|
||||
),
|
||||
'dfa1d313' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
|
@ -2021,11 +2028,6 @@ return array(
|
|||
'javelin-dom',
|
||||
'javelin-history',
|
||||
),
|
||||
'e18685c0' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
'javelin-stratcom',
|
||||
),
|
||||
'e562708c' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
|
@ -2036,17 +2038,6 @@ return array(
|
|||
'javelin-dom',
|
||||
'phabricator-draggable-list',
|
||||
),
|
||||
'e7cf10d6' => array(
|
||||
'javelin-dom',
|
||||
'javelin-util',
|
||||
'javelin-stratcom',
|
||||
'javelin-install',
|
||||
'javelin-workflow',
|
||||
'javelin-router',
|
||||
'javelin-behavior-device',
|
||||
'javelin-vector',
|
||||
'phabricator-diff-inline',
|
||||
),
|
||||
'e8240b50' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-stratcom',
|
||||
|
@ -2145,6 +2136,10 @@ return array(
|
|||
'owners-path-editor',
|
||||
'javelin-behavior',
|
||||
),
|
||||
'ff7b3f22' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-dom',
|
||||
),
|
||||
),
|
||||
'packages' => array(
|
||||
'conpherence.pkg.css' => array(
|
||||
|
@ -2337,7 +2332,6 @@ return array(
|
|||
'javelin-behavior-aphront-drag-and-drop-textarea',
|
||||
'javelin-behavior-phabricator-object-selector',
|
||||
'javelin-behavior-repository-crossreference',
|
||||
'javelin-behavior-differential-user-select',
|
||||
'javelin-behavior-aphront-more',
|
||||
'phabricator-diff-inline',
|
||||
'phabricator-diff-changeset',
|
||||
|
|
|
@ -199,7 +199,6 @@ return array(
|
|||
'javelin-behavior-phabricator-object-selector',
|
||||
'javelin-behavior-repository-crossreference',
|
||||
|
||||
'javelin-behavior-differential-user-select',
|
||||
'javelin-behavior-aphront-more',
|
||||
|
||||
'phabricator-diff-inline',
|
||||
|
|
21
resources/sql/autopatches/20190215.daemons.01.dropdataid.php
Normal file
21
resources/sql/autopatches/20190215.daemons.01.dropdataid.php
Normal file
|
@ -0,0 +1,21 @@
|
|||
<?php
|
||||
|
||||
// See T6615. We're about to change the nullability on the "dataID" column,
|
||||
// but it may have a UNIQUE KEY on it. Make sure we get rid of this key first
|
||||
// so we don't run into trouble.
|
||||
|
||||
// There's no "IF EXISTS" modifier for "ALTER TABLE" so run this as a PHP patch
|
||||
// instead of an SQL patch.
|
||||
|
||||
$table = new PhabricatorWorkerActiveTask();
|
||||
$conn = $table->establishConnection('w');
|
||||
|
||||
try {
|
||||
queryfx(
|
||||
$conn,
|
||||
'ALTER TABLE %R DROP KEY %T',
|
||||
$table,
|
||||
'dataID');
|
||||
} catch (AphrontQueryException $ex) {
|
||||
// Ignore.
|
||||
}
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_worker.worker_activetask
|
||||
CHANGE dataID dataID INT UNSIGNED NOT NULL;
|
|
@ -0,0 +1,6 @@
|
|||
CREATE TABLE {$NAMESPACE}_harbormaster.harbormaster_string (
|
||||
id INT UNSIGNED NOT NULL AUTO_INCREMENT PRIMARY KEY,
|
||||
stringIndex BINARY(12) NOT NULL,
|
||||
stringValue LONGTEXT NOT NULL,
|
||||
UNIQUE KEY `key_string` (stringIndex)
|
||||
) ENGINE=InnoDB, COLLATE {$COLLATE_TEXT};
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_harbormaster.harbormaster_buildunitmessage
|
||||
ADD nameIndex BINARY(12) NOT NULL;
|
|
@ -0,0 +1,2 @@
|
|||
ALTER TABLE {$NAMESPACE}_worker.worker_archivetask
|
||||
ADD archivedEpoch INT UNSIGNED NULL;
|
|
@ -0,0 +1,3 @@
|
|||
ALTER TABLE {$NAMESPACE}_worker.worker_activetask
|
||||
ADD dateCreated int unsigned NOT NULL,
|
||||
ADD dateModified int unsigned NOT NULL;
|
|
@ -1365,6 +1365,7 @@ phutil_register_library_map(array(
|
|||
'HarbormasterBuildTransactionEditor' => 'applications/harbormaster/editor/HarbormasterBuildTransactionEditor.php',
|
||||
'HarbormasterBuildTransactionQuery' => 'applications/harbormaster/query/HarbormasterBuildTransactionQuery.php',
|
||||
'HarbormasterBuildUnitMessage' => 'applications/harbormaster/storage/build/HarbormasterBuildUnitMessage.php',
|
||||
'HarbormasterBuildUnitMessageQuery' => 'applications/harbormaster/query/HarbormasterBuildUnitMessageQuery.php',
|
||||
'HarbormasterBuildViewController' => 'applications/harbormaster/controller/HarbormasterBuildViewController.php',
|
||||
'HarbormasterBuildWorker' => 'applications/harbormaster/worker/HarbormasterBuildWorker.php',
|
||||
'HarbormasterBuildable' => 'applications/harbormaster/storage/HarbormasterBuildable.php',
|
||||
|
@ -1440,6 +1441,7 @@ phutil_register_library_map(array(
|
|||
'HarbormasterStepDeleteController' => 'applications/harbormaster/controller/HarbormasterStepDeleteController.php',
|
||||
'HarbormasterStepEditController' => 'applications/harbormaster/controller/HarbormasterStepEditController.php',
|
||||
'HarbormasterStepViewController' => 'applications/harbormaster/controller/HarbormasterStepViewController.php',
|
||||
'HarbormasterString' => 'applications/harbormaster/storage/HarbormasterString.php',
|
||||
'HarbormasterTargetEngine' => 'applications/harbormaster/engine/HarbormasterTargetEngine.php',
|
||||
'HarbormasterTargetSearchAPIMethod' => 'applications/harbormaster/conduit/HarbormasterTargetSearchAPIMethod.php',
|
||||
'HarbormasterTargetWorker' => 'applications/harbormaster/worker/HarbormasterTargetWorker.php',
|
||||
|
@ -1742,6 +1744,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskParentTransaction' => 'applications/maniphest/xaction/ManiphestTaskParentTransaction.php',
|
||||
'ManiphestTaskPoints' => 'applications/maniphest/constants/ManiphestTaskPoints.php',
|
||||
'ManiphestTaskPointsTransaction' => 'applications/maniphest/xaction/ManiphestTaskPointsTransaction.php',
|
||||
'ManiphestTaskPolicyCodex' => 'applications/maniphest/policy/ManiphestTaskPolicyCodex.php',
|
||||
'ManiphestTaskPriority' => 'applications/maniphest/constants/ManiphestTaskPriority.php',
|
||||
'ManiphestTaskPriorityDatasource' => 'applications/maniphest/typeahead/ManiphestTaskPriorityDatasource.php',
|
||||
'ManiphestTaskPriorityHeraldAction' => 'applications/maniphest/herald/ManiphestTaskPriorityHeraldAction.php',
|
||||
|
@ -2857,8 +2860,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDaemonLog' => 'applications/daemon/storage/PhabricatorDaemonLog.php',
|
||||
'PhabricatorDaemonLogEvent' => 'applications/daemon/storage/PhabricatorDaemonLogEvent.php',
|
||||
'PhabricatorDaemonLogEventGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonLogEventGarbageCollector.php',
|
||||
'PhabricatorDaemonLogEventViewController' => 'applications/daemon/controller/PhabricatorDaemonLogEventViewController.php',
|
||||
'PhabricatorDaemonLogEventsView' => 'applications/daemon/view/PhabricatorDaemonLogEventsView.php',
|
||||
'PhabricatorDaemonLogGarbageCollector' => 'applications/daemon/garbagecollector/PhabricatorDaemonLogGarbageCollector.php',
|
||||
'PhabricatorDaemonLogListController' => 'applications/daemon/controller/PhabricatorDaemonLogListController.php',
|
||||
'PhabricatorDaemonLogListView' => 'applications/daemon/view/PhabricatorDaemonLogListView.php',
|
||||
|
@ -2969,6 +2970,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDeveloperPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorDeveloperPreferencesSettingsPanel.php',
|
||||
'PhabricatorDiffInlineCommentQuery' => 'infrastructure/diff/query/PhabricatorDiffInlineCommentQuery.php',
|
||||
'PhabricatorDiffPreferencesSettingsPanel' => 'applications/settings/panel/PhabricatorDiffPreferencesSettingsPanel.php',
|
||||
'PhabricatorDiffScopeEngine' => 'infrastructure/diff/PhabricatorDiffScopeEngine.php',
|
||||
'PhabricatorDiffScopeEngineTestCase' => 'infrastructure/diff/__tests__/PhabricatorDiffScopeEngineTestCase.php',
|
||||
'PhabricatorDifferenceEngine' => 'infrastructure/diff/PhabricatorDifferenceEngine.php',
|
||||
'PhabricatorDifferentialApplication' => 'applications/differential/application/PhabricatorDifferentialApplication.php',
|
||||
'PhabricatorDifferentialAttachCommitWorkflow' => 'applications/differential/management/PhabricatorDifferentialAttachCommitWorkflow.php',
|
||||
|
@ -6983,7 +6986,11 @@ phutil_register_library_map(array(
|
|||
'HarbormasterBuildTransaction' => 'PhabricatorApplicationTransaction',
|
||||
'HarbormasterBuildTransactionEditor' => 'PhabricatorApplicationTransactionEditor',
|
||||
'HarbormasterBuildTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'HarbormasterBuildUnitMessage' => 'HarbormasterDAO',
|
||||
'HarbormasterBuildUnitMessage' => array(
|
||||
'HarbormasterDAO',
|
||||
'PhabricatorPolicyInterface',
|
||||
),
|
||||
'HarbormasterBuildUnitMessageQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'HarbormasterBuildViewController' => 'HarbormasterController',
|
||||
'HarbormasterBuildWorker' => 'HarbormasterWorker',
|
||||
'HarbormasterBuildable' => array(
|
||||
|
@ -7062,6 +7069,7 @@ phutil_register_library_map(array(
|
|||
'HarbormasterStepDeleteController' => 'HarbormasterPlanController',
|
||||
'HarbormasterStepEditController' => 'HarbormasterPlanController',
|
||||
'HarbormasterStepViewController' => 'HarbormasterPlanController',
|
||||
'HarbormasterString' => 'HarbormasterDAO',
|
||||
'HarbormasterTargetEngine' => 'Phobject',
|
||||
'HarbormasterTargetSearchAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
|
||||
'HarbormasterTargetWorker' => 'HarbormasterWorker',
|
||||
|
@ -7379,6 +7387,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorEditEngineSubtypeInterface',
|
||||
'PhabricatorEditEngineLockableInterface',
|
||||
'PhabricatorEditEngineMFAInterface',
|
||||
'PhabricatorPolicyCodexInterface',
|
||||
),
|
||||
'ManiphestTaskAssignHeraldAction' => 'HeraldAction',
|
||||
'ManiphestTaskAssignOtherHeraldAction' => 'ManiphestTaskAssignHeraldAction',
|
||||
|
@ -7430,6 +7439,7 @@ phutil_register_library_map(array(
|
|||
'ManiphestTaskParentTransaction' => 'ManiphestTaskTransactionType',
|
||||
'ManiphestTaskPoints' => 'Phobject',
|
||||
'ManiphestTaskPointsTransaction' => 'ManiphestTaskTransactionType',
|
||||
'ManiphestTaskPolicyCodex' => 'PhabricatorPolicyCodex',
|
||||
'ManiphestTaskPriority' => 'ManiphestConstants',
|
||||
'ManiphestTaskPriorityDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'ManiphestTaskPriorityHeraldAction' => 'HeraldAction',
|
||||
|
@ -8713,8 +8723,6 @@ phutil_register_library_map(array(
|
|||
),
|
||||
'PhabricatorDaemonLogEvent' => 'PhabricatorDaemonDAO',
|
||||
'PhabricatorDaemonLogEventGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||
'PhabricatorDaemonLogEventViewController' => 'PhabricatorDaemonController',
|
||||
'PhabricatorDaemonLogEventsView' => 'AphrontView',
|
||||
'PhabricatorDaemonLogGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||
'PhabricatorDaemonLogListController' => 'PhabricatorDaemonController',
|
||||
'PhabricatorDaemonLogListView' => 'AphrontView',
|
||||
|
@ -8842,6 +8850,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorDeveloperPreferencesSettingsPanel' => 'PhabricatorEditEngineSettingsPanel',
|
||||
'PhabricatorDiffInlineCommentQuery' => 'PhabricatorApplicationTransactionCommentQuery',
|
||||
'PhabricatorDiffPreferencesSettingsPanel' => 'PhabricatorEditEngineSettingsPanel',
|
||||
'PhabricatorDiffScopeEngine' => 'Phobject',
|
||||
'PhabricatorDiffScopeEngineTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorDifferenceEngine' => 'Phobject',
|
||||
'PhabricatorDifferentialApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorDifferentialAttachCommitWorkflow' => 'PhabricatorDifferentialManagementWorkflow',
|
||||
|
|
|
@ -47,6 +47,14 @@ final class PhabricatorFacebookAuthProvider
|
|||
return 'Facebook';
|
||||
}
|
||||
|
||||
protected function getContentSecurityPolicyFormActions() {
|
||||
return array(
|
||||
// See T13254. After login with a mobile device, Facebook may redirect
|
||||
// to the mobile site.
|
||||
'https://m.facebook.com/',
|
||||
);
|
||||
}
|
||||
|
||||
public function readFormValuesFromProvider() {
|
||||
$require_secure = $this->getProviderConfig()->getProperty(
|
||||
self::KEY_REQUIRE_SECURE);
|
||||
|
@ -114,15 +122,4 @@ final class PhabricatorFacebookAuthProvider
|
|||
return parent::renderConfigPropertyTransactionTitle($xaction);
|
||||
}
|
||||
|
||||
public static function getFacebookApplicationID() {
|
||||
$providers = PhabricatorAuthProvider::getAllProviders();
|
||||
$fb_provider = idx($providers, 'facebook:facebook.com');
|
||||
if (!$fb_provider) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $fb_provider->getProviderConfig()->getProperty(
|
||||
self::PROPERTY_APP_ID);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -533,6 +533,8 @@ final class PhabricatorExtraConfigSetupCheck extends PhabricatorSetupCheck {
|
|||
'This ancient extension point has been replaced with other '.
|
||||
'mechanisms, including "AphrontSite".'),
|
||||
|
||||
'differential.whitespace-matters' => pht(
|
||||
'Whitespace rendering is now handled automatically.'),
|
||||
);
|
||||
|
||||
return $ancient_config;
|
||||
|
|
|
@ -45,7 +45,6 @@ final class PhabricatorDaemonsApplication extends PhabricatorApplication {
|
|||
'' => 'PhabricatorDaemonLogListController',
|
||||
'(?P<id>[1-9]\d*)/' => 'PhabricatorDaemonLogViewController',
|
||||
),
|
||||
'event/(?P<id>[1-9]\d*)/' => 'PhabricatorDaemonLogEventViewController',
|
||||
'bulk/' => array(
|
||||
'(?:query/(?P<queryKey>[^/]+)/)?' =>
|
||||
'PhabricatorDaemonBulkJobListController',
|
||||
|
|
|
@ -31,6 +31,7 @@ final class PhabricatorDaemonConsoleController
|
|||
$completed_info[$class] = array(
|
||||
'n' => 0,
|
||||
'duration' => 0,
|
||||
'queueTime' => 0,
|
||||
);
|
||||
}
|
||||
$completed_info[$class]['n']++;
|
||||
|
@ -41,16 +42,33 @@ final class PhabricatorDaemonConsoleController
|
|||
// compute utilization.
|
||||
$usage_total += $lease_overhead + ($duration / 1000000);
|
||||
$usage_start = min($usage_start, $completed_task->getDateModified());
|
||||
|
||||
$date_archived = $completed_task->getArchivedEpoch();
|
||||
$queue_seconds = $date_archived - $completed_task->getDateCreated();
|
||||
|
||||
// Don't measure queue time for tasks that completed in the same
|
||||
// epoch-second they were created in.
|
||||
if ($queue_seconds > 0) {
|
||||
$sec_in_us = phutil_units('1 second in microseconds');
|
||||
$queue_us = $queue_seconds * $sec_in_us;
|
||||
$queue_exclusive_us = $queue_us - $duration;
|
||||
$queue_exclusive_seconds = $queue_exclusive_us / $sec_in_us;
|
||||
$rounded = floor($queue_exclusive_seconds);
|
||||
$completed_info[$class]['queueTime'] += $rounded;
|
||||
}
|
||||
}
|
||||
|
||||
$completed_info = isort($completed_info, 'n');
|
||||
|
||||
$rows = array();
|
||||
foreach ($completed_info as $class => $info) {
|
||||
$duration_avg = new PhutilNumber((int)($info['duration'] / $info['n']));
|
||||
$queue_avg = new PhutilNumber((int)($info['queueTime'] / $info['n']));
|
||||
$rows[] = array(
|
||||
$class,
|
||||
number_format($info['n']),
|
||||
pht('%s us', new PhutilNumber((int)($info['duration'] / $info['n']))),
|
||||
pht('%s us', $duration_avg),
|
||||
pht('%s s', $queue_avg),
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -98,6 +116,7 @@ final class PhabricatorDaemonConsoleController
|
|||
phutil_tag('em', array(), pht('Queue Utilization (Approximate)')),
|
||||
sprintf('%.1f%%', 100 * $used_time),
|
||||
null,
|
||||
null,
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -108,13 +127,15 @@ final class PhabricatorDaemonConsoleController
|
|||
array(
|
||||
pht('Class'),
|
||||
pht('Count'),
|
||||
pht('Avg'),
|
||||
pht('Average Duration'),
|
||||
pht('Average Queue Time'),
|
||||
));
|
||||
$completed_table->setColumnClasses(
|
||||
array(
|
||||
'wide',
|
||||
'n',
|
||||
'n',
|
||||
'n',
|
||||
));
|
||||
|
||||
$completed_panel = id(new PHUIObjectBoxView())
|
||||
|
|
|
@ -1,47 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDaemonLogEventViewController
|
||||
extends PhabricatorDaemonController {
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$id = $request->getURIData('id');
|
||||
|
||||
$event = id(new PhabricatorDaemonLogEvent())->load($id);
|
||||
if (!$event) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
||||
$event_view = id(new PhabricatorDaemonLogEventsView())
|
||||
->setEvents(array($event))
|
||||
->setUser($request->getUser())
|
||||
->setCombinedLog(true)
|
||||
->setShowFullMessage(true);
|
||||
|
||||
$log_panel = id(new PHUIObjectBoxView())
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->appendChild($event_view);
|
||||
|
||||
$daemon_id = $event->getLogID();
|
||||
|
||||
$crumbs = $this->buildApplicationCrumbs()
|
||||
->addTextCrumb(
|
||||
pht('Daemon %s', $daemon_id),
|
||||
$this->getApplicationURI("log/{$daemon_id}/"))
|
||||
->addTextCrumb(pht('Event %s', $event->getID()))
|
||||
->setBorder(true);
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Combined Log'))
|
||||
->setHeaderIcon('fa-file-text');
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setFooter($log_panel);
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle(pht('Combined Daemon Log'))
|
||||
->appendChild($view);
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -1,131 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDaemonLogEventsView extends AphrontView {
|
||||
|
||||
private $events;
|
||||
private $combinedLog;
|
||||
private $showFullMessage;
|
||||
|
||||
|
||||
public function setShowFullMessage($show_full_message) {
|
||||
$this->showFullMessage = $show_full_message;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setEvents(array $events) {
|
||||
assert_instances_of($events, 'PhabricatorDaemonLogEvent');
|
||||
$this->events = $events;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCombinedLog($is_combined) {
|
||||
$this->combinedLog = $is_combined;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$viewer = $this->getViewer();
|
||||
$rows = array();
|
||||
|
||||
foreach ($this->events as $event) {
|
||||
|
||||
// Limit display log size. If a daemon gets stuck in an output loop this
|
||||
// page can be like >100MB if we don't truncate stuff. Try to do cheap
|
||||
// line-based truncation first, and fall back to expensive UTF-8 character
|
||||
// truncation if that doesn't get things short enough.
|
||||
|
||||
$message = $event->getMessage();
|
||||
$more = null;
|
||||
|
||||
if (!$this->showFullMessage) {
|
||||
$more_lines = null;
|
||||
$more_chars = null;
|
||||
$line_limit = 12;
|
||||
if (substr_count($message, "\n") > $line_limit) {
|
||||
$message = explode("\n", $message);
|
||||
$more_lines = count($message) - $line_limit;
|
||||
$message = array_slice($message, 0, $line_limit);
|
||||
$message = implode("\n", $message);
|
||||
}
|
||||
|
||||
$char_limit = 8192;
|
||||
if (strlen($message) > $char_limit) {
|
||||
$message = phutil_utf8v($message);
|
||||
$more_chars = count($message) - $char_limit;
|
||||
$message = array_slice($message, 0, $char_limit);
|
||||
$message = implode('', $message);
|
||||
}
|
||||
|
||||
if ($more_chars) {
|
||||
$more = new PhutilNumber($more_chars);
|
||||
$more = pht('Show %d more character(s)...', $more);
|
||||
} else if ($more_lines) {
|
||||
$more = new PhutilNumber($more_lines);
|
||||
$more = pht('Show %d more line(s)...', $more);
|
||||
}
|
||||
|
||||
if ($more) {
|
||||
$id = $event->getID();
|
||||
$more = array(
|
||||
"\n...\n",
|
||||
phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => "/daemon/event/{$id}/",
|
||||
),
|
||||
$more),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$row = array(
|
||||
$event->getLogType(),
|
||||
phabricator_date($event->getEpoch(), $viewer),
|
||||
phabricator_time($event->getEpoch(), $viewer),
|
||||
array(
|
||||
$message,
|
||||
$more,
|
||||
),
|
||||
);
|
||||
|
||||
if ($this->combinedLog) {
|
||||
array_unshift(
|
||||
$row,
|
||||
phutil_tag(
|
||||
'a',
|
||||
array(
|
||||
'href' => '/daemon/log/'.$event->getLogID().'/',
|
||||
),
|
||||
pht('Daemon %s', $event->getLogID())));
|
||||
}
|
||||
|
||||
$rows[] = $row;
|
||||
}
|
||||
|
||||
$classes = array(
|
||||
'',
|
||||
'',
|
||||
'right',
|
||||
'wide prewrap',
|
||||
);
|
||||
|
||||
$headers = array(
|
||||
'Type',
|
||||
'Date',
|
||||
'Time',
|
||||
'Message',
|
||||
);
|
||||
|
||||
if ($this->combinedLog) {
|
||||
array_unshift($classes, 'pri');
|
||||
array_unshift($headers, 'Daemon');
|
||||
}
|
||||
|
||||
$log_table = new AphrontTableView($rows);
|
||||
$log_table->setHeaders($headers);
|
||||
$log_table->setColumnClasses($classes);
|
||||
|
||||
return $log_table->render();
|
||||
}
|
||||
|
||||
}
|
|
@ -14,6 +14,10 @@ final class DifferentialParseRenderTestCase extends PhabricatorTestCase {
|
|||
}
|
||||
$data = Filesystem::readFile($dir.$file);
|
||||
|
||||
// Strip trailing "~" characters from inputs so they may contain
|
||||
// trailing whitespace.
|
||||
$data = preg_replace('/~$/m', '', $data);
|
||||
|
||||
$opt_file = $dir.$file.'.options';
|
||||
if (Filesystem::pathExists($opt_file)) {
|
||||
$options = Filesystem::readFile($opt_file);
|
||||
|
@ -31,7 +35,6 @@ final class DifferentialParseRenderTestCase extends PhabricatorTestCase {
|
|||
foreach (array('one', 'two') as $type) {
|
||||
$this->runParser($type, $data, $file, 'expect');
|
||||
$this->runParser($type, $data, $file, 'unshielded');
|
||||
$this->runParser($type, $data, $file, 'whitespace');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -44,25 +47,20 @@ final class DifferentialParseRenderTestCase extends PhabricatorTestCase {
|
|||
}
|
||||
|
||||
$unshielded = false;
|
||||
$whitespace = false;
|
||||
switch ($extension) {
|
||||
case 'unshielded':
|
||||
$unshielded = true;
|
||||
break;
|
||||
case 'whitespace';
|
||||
$unshielded = true;
|
||||
$whitespace = true;
|
||||
break;
|
||||
}
|
||||
|
||||
$parsers = $this->buildChangesetParsers($type, $data, $file);
|
||||
$actual = $this->renderParsers($parsers, $unshielded, $whitespace);
|
||||
$actual = $this->renderParsers($parsers, $unshielded);
|
||||
$expect = Filesystem::readFile($test_file);
|
||||
|
||||
$this->assertEqual($expect, $actual, basename($test_file));
|
||||
}
|
||||
|
||||
private function renderParsers(array $parsers, $unshield, $whitespace) {
|
||||
private function renderParsers(array $parsers, $unshield) {
|
||||
$result = array();
|
||||
foreach ($parsers as $parser) {
|
||||
if ($unshield) {
|
||||
|
@ -73,11 +71,6 @@ final class DifferentialParseRenderTestCase extends PhabricatorTestCase {
|
|||
$e_range = null;
|
||||
}
|
||||
|
||||
if ($whitespace) {
|
||||
$parser->setWhitespaceMode(
|
||||
DifferentialChangesetParser::WHITESPACE_SHOW_ALL);
|
||||
}
|
||||
|
||||
$result[] = $parser->render($s_range, $e_range, array());
|
||||
}
|
||||
return implode(str_repeat('~', 80)."\n", $result);
|
||||
|
|
|
@ -4,7 +4,7 @@ index 5dcff7f..eff82ef 100644
|
|||
+++ b/GENERATED
|
||||
@@ -1,4 +1,4 @@
|
||||
@generated
|
||||
|
||||
~
|
||||
-This is a generated file.
|
||||
+This is a generated file, full of generated code.
|
||||
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
N 1 . @generated\n~
|
||||
O 2 - \n~
|
||||
N 2 . \n~
|
||||
O 3 - This is a generated file.\n~
|
||||
N 2 + \n~
|
||||
N 3 + This is a generated file{(, full of generated code)}.\n~
|
||||
N 4 . \n~
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
O 1 . @generated\n~
|
||||
N 1 . @generated\n~
|
||||
O 2 - \n~
|
||||
N 2 + \n~
|
||||
O 2 . \n~
|
||||
N 2 . \n~
|
||||
O 3 - This is a generated file.\n~
|
||||
N 3 + This is a generated file{(, full of generated code)}.\n~
|
||||
O 4 . \n~
|
||||
|
|
|
@ -2,4 +2,5 @@ CTYPE 2 1 (unforced)
|
|||
WHITESPACE
|
||||
WHITESPACE
|
||||
-
|
||||
SHIELD (whitespace) This file was changed only by adding or removing whitespace.
|
||||
O 1 - -=[-Rocket-Ship>\n~
|
||||
N 1 + {> )}-=[-Rocket-Ship>\n~
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
O 1 - -=[-Rocket-Ship>\n~
|
||||
N 1 + {( )}-=[-Rocket-Ship>\n~
|
||||
N 1 + {> )}-=[-Rocket-Ship>\n~
|
||||
|
|
|
@ -2,4 +2,5 @@ CTYPE 2 1 (unforced)
|
|||
WHITESPACE
|
||||
WHITESPACE
|
||||
-
|
||||
SHIELD (whitespace) This file was changed only by adding or removing whitespace.
|
||||
O 1 - -=[-Rocket-Ship>\n~
|
||||
N 1 + {> )}-=[-Rocket-Ship>\n~
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
O 1 - -=[-Rocket-Ship>\n~
|
||||
N 1 + {( )}-=[-Rocket-Ship>\n~
|
||||
N 1 + {> )}-=[-Rocket-Ship>\n~
|
||||
|
|
|
@ -80,18 +80,6 @@ EOHELP
|
|||
"Select and reorder revision fields.\n\n".
|
||||
"NOTE: This feature is under active development and subject ".
|
||||
"to change.")),
|
||||
$this->newOption(
|
||||
'differential.whitespace-matters',
|
||||
'list<regex>',
|
||||
array(
|
||||
'/\.py$/',
|
||||
'/\.l?hs$/',
|
||||
'/\.ya?ml$/',
|
||||
))
|
||||
->setDescription(
|
||||
pht(
|
||||
"List of file regexps where whitespace is meaningful and should ".
|
||||
"not use 'ignore-all' by default")),
|
||||
$this->newOption('differential.require-test-plan-field', 'bool', true)
|
||||
->setBoolOptions(
|
||||
array(
|
||||
|
|
|
@ -420,15 +420,17 @@ final class DifferentialChangesetViewController extends DifferentialController {
|
|||
}
|
||||
|
||||
private function loadCoverage(DifferentialChangeset $changeset) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
$target_phids = $changeset->getDiff()->getBuildTargetPHIDs();
|
||||
if (!$target_phids) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$unit = id(new HarbormasterBuildUnitMessage())->loadAllWhere(
|
||||
'buildTargetPHID IN (%Ls)',
|
||||
$target_phids);
|
||||
|
||||
$unit = id(new HarbormasterBuildUnitMessageQuery())
|
||||
->setViewer($viewer)
|
||||
->withBuildTargetPHIDs($target_phids)
|
||||
->execute();
|
||||
if (!$unit) {
|
||||
return null;
|
||||
}
|
||||
|
|
|
@ -192,9 +192,10 @@ abstract class DifferentialController extends PhabricatorController {
|
|||
$all_target_phids = array_mergev($target_map);
|
||||
|
||||
if ($all_target_phids) {
|
||||
$unit_messages = id(new HarbormasterBuildUnitMessage())->loadAllWhere(
|
||||
'buildTargetPHID IN (%Ls)',
|
||||
$all_target_phids);
|
||||
$unit_messages = id(new HarbormasterBuildUnitMessageQuery())
|
||||
->setViewer($viewer)
|
||||
->withBuildTargetPHIDs($all_target_phids)
|
||||
->execute();
|
||||
$unit_messages = mgroup($unit_messages, 'getBuildTargetPHID');
|
||||
} else {
|
||||
$unit_messages = array();
|
||||
|
|
|
@ -305,10 +305,6 @@ final class DifferentialRevisionViewController
|
|||
$details = $this->buildDetails($revision, $field_list);
|
||||
$curtain = $this->buildCurtain($revision);
|
||||
|
||||
$whitespace = $request->getStr(
|
||||
'whitespace',
|
||||
DifferentialChangesetParser::WHITESPACE_IGNORE_MOST);
|
||||
|
||||
$repository = $revision->getRepository();
|
||||
if ($repository) {
|
||||
$symbol_indexes = $this->buildSymbolIndexes(
|
||||
|
@ -383,7 +379,6 @@ final class DifferentialRevisionViewController
|
|||
->setDiff($target)
|
||||
->setRenderingReferences($rendering_references)
|
||||
->setVsMap($vs_map)
|
||||
->setWhitespace($whitespace)
|
||||
->setSymbolIndexes($symbol_indexes)
|
||||
->setTitle(pht('Diff %s', $target->getID()))
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY);
|
||||
|
@ -412,7 +407,6 @@ final class DifferentialRevisionViewController
|
|||
->setDiffUnitStatuses($broken_diffs)
|
||||
->setSelectedVersusDiffID($diff_vs)
|
||||
->setSelectedDiffID($target->getID())
|
||||
->setSelectedWhitespace($whitespace)
|
||||
->setCommitsForLinks($commits_for_links);
|
||||
|
||||
$local_table = id(new DifferentialLocalCommitsView())
|
||||
|
@ -627,8 +621,6 @@ final class DifferentialRevisionViewController
|
|||
->build($changesets);
|
||||
}
|
||||
|
||||
Javelin::initBehavior('differential-user-select');
|
||||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setSubheader($subheader)
|
||||
|
@ -1095,7 +1087,7 @@ final class DifferentialRevisionViewController
|
|||
// this ends up being something like
|
||||
// D123.diff
|
||||
// or the verbose
|
||||
// D123.vs123.id123.whitespaceignore-all.diff
|
||||
// D123.vs123.id123.highlightjs.diff
|
||||
// lame but nice to include these options
|
||||
$file_name = ltrim($request_uri->getPath(), '/').'.';
|
||||
foreach ($request_uri->getQueryParamsAsPairList() as $pair) {
|
||||
|
|
|
@ -8,6 +8,7 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
protected $new = array();
|
||||
protected $old = array();
|
||||
protected $intra = array();
|
||||
protected $depthOnlyLines = array();
|
||||
protected $newRender = null;
|
||||
protected $oldRender = null;
|
||||
|
||||
|
@ -18,7 +19,6 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
protected $specialAttributes = array();
|
||||
|
||||
protected $changeset;
|
||||
protected $whitespaceMode = null;
|
||||
|
||||
protected $renderCacheKey = null;
|
||||
|
||||
|
@ -162,7 +162,6 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
}
|
||||
|
||||
public function readParametersFromRequest(AphrontRequest $request) {
|
||||
$this->setWhitespaceMode($request->getStr('whitespace'));
|
||||
$this->setCharacterEncoding($request->getStr('encoding'));
|
||||
$this->setHighlightAs($request->getStr('highlight'));
|
||||
|
||||
|
@ -190,20 +189,14 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
return $this;
|
||||
}
|
||||
|
||||
const CACHE_VERSION = 11;
|
||||
const CACHE_VERSION = 14;
|
||||
const CACHE_MAX_SIZE = 8e6;
|
||||
|
||||
const ATTR_GENERATED = 'attr:generated';
|
||||
const ATTR_DELETED = 'attr:deleted';
|
||||
const ATTR_UNCHANGED = 'attr:unchanged';
|
||||
const ATTR_WHITELINES = 'attr:white';
|
||||
const ATTR_MOVEAWAY = 'attr:moveaway';
|
||||
|
||||
const WHITESPACE_SHOW_ALL = 'show-all';
|
||||
const WHITESPACE_IGNORE_TRAILING = 'ignore-trailing';
|
||||
const WHITESPACE_IGNORE_MOST = 'ignore-most';
|
||||
const WHITESPACE_IGNORE_ALL = 'ignore-all';
|
||||
|
||||
public function setOldLines(array $lines) {
|
||||
$this->old = $lines;
|
||||
return $this;
|
||||
|
@ -224,6 +217,15 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setDepthOnlyLines(array $lines) {
|
||||
$this->depthOnlyLines = $lines;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDepthOnlyLines() {
|
||||
return $this->depthOnlyLines;
|
||||
}
|
||||
|
||||
public function setVisibileLinesMask(array $mask) {
|
||||
$this->visible = $mask;
|
||||
return $this;
|
||||
|
@ -326,11 +328,6 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setWhitespaceMode($whitespace_mode) {
|
||||
$this->whitespaceMode = $whitespace_mode;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setRenderingReference($ref) {
|
||||
$this->renderingReference = $ref;
|
||||
return $this;
|
||||
|
@ -450,6 +447,7 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
'new',
|
||||
'old',
|
||||
'intra',
|
||||
'depthOnlyLines',
|
||||
'newRender',
|
||||
'oldRender',
|
||||
'specialAttributes',
|
||||
|
@ -563,10 +561,6 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
return idx($this->specialAttributes, self::ATTR_UNCHANGED, false);
|
||||
}
|
||||
|
||||
public function isWhitespaceOnly() {
|
||||
return idx($this->specialAttributes, self::ATTR_WHITELINES, false);
|
||||
}
|
||||
|
||||
public function isMoveAway() {
|
||||
return idx($this->specialAttributes, self::ATTR_MOVEAWAY, false);
|
||||
}
|
||||
|
@ -574,11 +568,17 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
private function applyIntraline(&$render, $intra, $corpus) {
|
||||
|
||||
foreach ($render as $key => $text) {
|
||||
$result = $text;
|
||||
|
||||
if (isset($intra[$key])) {
|
||||
$render[$key] = ArcanistDiffUtils::applyIntralineDiff(
|
||||
$text,
|
||||
$result = ArcanistDiffUtils::applyIntralineDiff(
|
||||
$result,
|
||||
$intra[$key]);
|
||||
}
|
||||
|
||||
$result = $this->adjustRenderedLineForDisplay($result);
|
||||
|
||||
$render[$key] = $result;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -613,18 +613,8 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
}
|
||||
|
||||
private function tryCacheStuff() {
|
||||
$whitespace_mode = $this->whitespaceMode;
|
||||
switch ($whitespace_mode) {
|
||||
case self::WHITESPACE_SHOW_ALL:
|
||||
case self::WHITESPACE_IGNORE_TRAILING:
|
||||
case self::WHITESPACE_IGNORE_ALL:
|
||||
break;
|
||||
default:
|
||||
$whitespace_mode = self::WHITESPACE_IGNORE_MOST;
|
||||
break;
|
||||
}
|
||||
$skip_cache = false;
|
||||
|
||||
$skip_cache = ($whitespace_mode != self::WHITESPACE_IGNORE_MOST);
|
||||
if ($this->disableCache) {
|
||||
$skip_cache = true;
|
||||
}
|
||||
|
@ -637,8 +627,6 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
$skip_cache = true;
|
||||
}
|
||||
|
||||
$this->whitespaceMode = $whitespace_mode;
|
||||
|
||||
$changeset = $this->changeset;
|
||||
|
||||
if ($changeset->getFileType() != DifferentialChangeType::FILE_TEXT &&
|
||||
|
@ -657,70 +645,12 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
}
|
||||
|
||||
private function process() {
|
||||
$whitespace_mode = $this->whitespaceMode;
|
||||
$changeset = $this->changeset;
|
||||
|
||||
$ignore_all = (($whitespace_mode == self::WHITESPACE_IGNORE_MOST) ||
|
||||
($whitespace_mode == self::WHITESPACE_IGNORE_ALL));
|
||||
|
||||
$force_ignore = ($whitespace_mode == self::WHITESPACE_IGNORE_ALL);
|
||||
|
||||
if (!$force_ignore) {
|
||||
if ($ignore_all && $changeset->getWhitespaceMatters()) {
|
||||
$ignore_all = false;
|
||||
}
|
||||
}
|
||||
|
||||
// The "ignore all whitespace" algorithm depends on rediffing the
|
||||
// files, and we currently need complete representations of both
|
||||
// files to do anything reasonable. If we only have parts of the files,
|
||||
// don't use the "ignore all" algorithm.
|
||||
if ($ignore_all) {
|
||||
$hunks = $changeset->getHunks();
|
||||
if (count($hunks) !== 1) {
|
||||
$ignore_all = false;
|
||||
} else {
|
||||
$first_hunk = reset($hunks);
|
||||
if ($first_hunk->getOldOffset() != 1 ||
|
||||
$first_hunk->getNewOffset() != 1) {
|
||||
$ignore_all = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($ignore_all) {
|
||||
$old_file = $changeset->makeOldFile();
|
||||
$new_file = $changeset->makeNewFile();
|
||||
if ($old_file == $new_file) {
|
||||
// If the old and new files are exactly identical, the synthetic
|
||||
// diff below will give us nonsense and whitespace modes are
|
||||
// irrelevant anyway. This occurs when you, e.g., copy a file onto
|
||||
// itself in Subversion (see T271).
|
||||
$ignore_all = false;
|
||||
}
|
||||
}
|
||||
|
||||
$hunk_parser = new DifferentialHunkParser();
|
||||
$hunk_parser->setWhitespaceMode($whitespace_mode);
|
||||
$hunk_parser->parseHunksForLineData($changeset->getHunks());
|
||||
|
||||
// Depending on the whitespace mode, we may need to compute a different
|
||||
// set of changes than the set of changes in the hunk data (specifically,
|
||||
// we might want to consider changed lines which have only whitespace
|
||||
// changes as unchanged).
|
||||
if ($ignore_all) {
|
||||
$engine = new PhabricatorDifferenceEngine();
|
||||
$engine->setIgnoreWhitespace(true);
|
||||
$no_whitespace_changeset = $engine->generateChangesetFromFileContent(
|
||||
$old_file,
|
||||
$new_file);
|
||||
|
||||
$type_parser = new DifferentialHunkParser();
|
||||
$type_parser->parseHunksForLineData($no_whitespace_changeset->getHunks());
|
||||
|
||||
$hunk_parser->setOldLineTypeMap($type_parser->getOldLineTypeMap());
|
||||
$hunk_parser->setNewLineTypeMap($type_parser->getNewLineTypeMap());
|
||||
}
|
||||
$this->realignDiff($changeset, $hunk_parser);
|
||||
|
||||
$hunk_parser->reparseHunksForSpecialAttributes();
|
||||
|
||||
|
@ -742,7 +672,6 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
$this->setSpecialAttributes(array(
|
||||
self::ATTR_UNCHANGED => $unchanged,
|
||||
self::ATTR_DELETED => $hunk_parser->getIsDeleted(),
|
||||
self::ATTR_WHITELINES => !$hunk_parser->getHasTextChanges(),
|
||||
self::ATTR_MOVEAWAY => $moveaway,
|
||||
));
|
||||
|
||||
|
@ -754,6 +683,7 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
$this->setOldLines($hunk_parser->getOldLines());
|
||||
$this->setNewLines($hunk_parser->getNewLines());
|
||||
$this->setIntraLineDiffs($hunk_parser->getIntraLineDiffs());
|
||||
$this->setDepthOnlyLines($hunk_parser->getDepthOnlyLines());
|
||||
$this->setVisibileLinesMask($hunk_parser->getVisibleLinesMask());
|
||||
$this->hunkStartLines = $hunk_parser->getHunkStartLines(
|
||||
$changeset->getHunks());
|
||||
|
@ -914,7 +844,8 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
->setShowEditAndReplyLinks($this->getShowEditAndReplyLinks())
|
||||
->setCanMarkDone($this->getCanMarkDone())
|
||||
->setObjectOwnerPHID($this->getObjectOwnerPHID())
|
||||
->setHighlightingDisabled($this->highlightingDisabled);
|
||||
->setHighlightingDisabled($this->highlightingDisabled)
|
||||
->setDepthOnlyLines($this->getDepthOnlyLines());
|
||||
|
||||
$shield = null;
|
||||
if ($this->isTopLevel && !$this->comments) {
|
||||
|
@ -958,10 +889,6 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
pht('The contents of this file were not changed.'),
|
||||
$type);
|
||||
}
|
||||
} else if ($this->isWhitespaceOnly()) {
|
||||
$shield = $renderer->renderShield(
|
||||
pht('This file was changed only by adding or removing whitespace.'),
|
||||
'whitespace');
|
||||
} else if ($this->isDeleted()) {
|
||||
$shield = $renderer->renderShield(
|
||||
pht('This file was completely deleted.'));
|
||||
|
@ -1173,7 +1100,7 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
}
|
||||
$range_len = min($range_len, $rows - $range_start);
|
||||
|
||||
list($gaps, $mask, $depths) = $this->calculateGapsMaskAndDepths(
|
||||
list($gaps, $mask) = $this->calculateGapsAndMask(
|
||||
$mask_force,
|
||||
$feedback_mask,
|
||||
$range_start,
|
||||
|
@ -1181,8 +1108,7 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
|
||||
$renderer
|
||||
->setGaps($gaps)
|
||||
->setMask($mask)
|
||||
->setDepths($depths);
|
||||
->setMask($mask);
|
||||
|
||||
$html = $renderer->renderTextChange(
|
||||
$range_start,
|
||||
|
@ -1208,15 +1134,9 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
* "show more"). The $mask returned is a sparsely populated dictionary
|
||||
* of $visible_line_number => true.
|
||||
*
|
||||
* Depths - compute how indented any given line is. The $depths returned
|
||||
* is a sparsely populated dictionary of $visible_line_number => $depth.
|
||||
*
|
||||
* This function also has the side effect of modifying member variable
|
||||
* new such that tabs are normalized to spaces for each line of the diff.
|
||||
*
|
||||
* @return array($gaps, $mask, $depths)
|
||||
* @return array($gaps, $mask)
|
||||
*/
|
||||
private function calculateGapsMaskAndDepths(
|
||||
private function calculateGapsAndMask(
|
||||
$mask_force,
|
||||
$feedback_mask,
|
||||
$range_start,
|
||||
|
@ -1224,7 +1144,6 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
|
||||
$lines_context = $this->getLinesOfContext();
|
||||
|
||||
// Calculate gaps and mask first
|
||||
$gaps = array();
|
||||
$gap_start = 0;
|
||||
$in_gap = false;
|
||||
|
@ -1253,38 +1172,7 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
$gaps = array_reverse($gaps);
|
||||
$mask = $base_mask;
|
||||
|
||||
// Time to calculate depth.
|
||||
// We need to go backwards to properly indent whitespace in this code:
|
||||
//
|
||||
// 0: class C {
|
||||
// 1:
|
||||
// 1: function f() {
|
||||
// 2:
|
||||
// 2: return;
|
||||
// 1:
|
||||
// 1: }
|
||||
// 0:
|
||||
// 0: }
|
||||
//
|
||||
$depths = array();
|
||||
$last_depth = 0;
|
||||
$range_end = $range_start + $range_len;
|
||||
if (!isset($this->new[$range_end])) {
|
||||
$range_end--;
|
||||
}
|
||||
for ($ii = $range_end; $ii >= $range_start; $ii--) {
|
||||
// We need to expand tabs to process mixed indenting and to round
|
||||
// correctly later.
|
||||
$line = str_replace("\t", ' ', $this->new[$ii]['text']);
|
||||
$trimmed = ltrim($line);
|
||||
if ($trimmed != '') {
|
||||
// We round down to flatten "/**" and " *".
|
||||
$last_depth = floor((strlen($line) - strlen($trimmed)) / 2);
|
||||
}
|
||||
$depths[$ii] = $last_depth;
|
||||
}
|
||||
|
||||
return array($gaps, $mask, $depths);
|
||||
return array($gaps, $mask);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1487,4 +1375,229 @@ final class DifferentialChangesetParser extends Phobject {
|
|||
return $key;
|
||||
}
|
||||
|
||||
private function realignDiff(
|
||||
DifferentialChangeset $changeset,
|
||||
DifferentialHunkParser $hunk_parser) {
|
||||
// Normalizing and realigning the diff depends on rediffing the files, and
|
||||
// we currently need complete representations of both files to do anything
|
||||
// reasonable. If we only have parts of the files, skip realignment.
|
||||
|
||||
// We have more than one hunk, so we're definitely missing part of the file.
|
||||
$hunks = $changeset->getHunks();
|
||||
if (count($hunks) !== 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// The first hunk doesn't start at the beginning of the file, so we're
|
||||
// missing some context.
|
||||
$first_hunk = head($hunks);
|
||||
if ($first_hunk->getOldOffset() != 1 || $first_hunk->getNewOffset() != 1) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$old_file = $changeset->makeOldFile();
|
||||
$new_file = $changeset->makeNewFile();
|
||||
if ($old_file === $new_file) {
|
||||
// If the old and new files are exactly identical, the synthetic
|
||||
// diff below will give us nonsense and whitespace modes are
|
||||
// irrelevant anyway. This occurs when you, e.g., copy a file onto
|
||||
// itself in Subversion (see T271).
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
$engine = id(new PhabricatorDifferenceEngine())
|
||||
->setNormalize(true);
|
||||
|
||||
$normalized_changeset = $engine->generateChangesetFromFileContent(
|
||||
$old_file,
|
||||
$new_file);
|
||||
|
||||
$type_parser = new DifferentialHunkParser();
|
||||
$type_parser->parseHunksForLineData($normalized_changeset->getHunks());
|
||||
|
||||
$hunk_parser->setNormalized(true);
|
||||
$hunk_parser->setOldLineTypeMap($type_parser->getOldLineTypeMap());
|
||||
$hunk_parser->setNewLineTypeMap($type_parser->getNewLineTypeMap());
|
||||
}
|
||||
|
||||
private function adjustRenderedLineForDisplay($line) {
|
||||
// IMPORTANT: We're using "str_replace()" against raw HTML here, which can
|
||||
// easily become unsafe. The input HTML has already had syntax highlighting
|
||||
// and intraline diff highlighting applied, so it's full of "<span />" tags.
|
||||
|
||||
static $search;
|
||||
static $replace;
|
||||
if ($search === null) {
|
||||
$rules = $this->newSuspiciousCharacterRules();
|
||||
|
||||
$map = array();
|
||||
foreach ($rules as $key => $spec) {
|
||||
$tag = phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'data-copy-text' => $key,
|
||||
'class' => $spec['class'],
|
||||
'title' => $spec['title'],
|
||||
),
|
||||
$spec['replacement']);
|
||||
$map[$key] = phutil_string_cast($tag);
|
||||
}
|
||||
|
||||
$search = array_keys($map);
|
||||
$replace = array_values($map);
|
||||
}
|
||||
|
||||
$is_html = false;
|
||||
if ($line instanceof PhutilSafeHTML) {
|
||||
$is_html = true;
|
||||
$line = hsprintf('%s', $line);
|
||||
}
|
||||
|
||||
$line = phutil_string_cast($line);
|
||||
|
||||
if (strpos($line, "\t") !== false) {
|
||||
$line = $this->replaceTabsWithSpaces($line);
|
||||
}
|
||||
$line = str_replace($search, $replace, $line);
|
||||
|
||||
if ($is_html) {
|
||||
$line = phutil_safe_html($line);
|
||||
}
|
||||
|
||||
return $line;
|
||||
}
|
||||
|
||||
private function newSuspiciousCharacterRules() {
|
||||
// The "title" attributes are cached in the database, so they're
|
||||
// intentionally not wrapped in "pht(...)".
|
||||
|
||||
$rules = array(
|
||||
"\xE2\x80\x8B" => array(
|
||||
'title' => 'ZWS',
|
||||
'class' => 'suspicious-character',
|
||||
'replacement' => '!',
|
||||
),
|
||||
"\xC2\xA0" => array(
|
||||
'title' => 'NBSP',
|
||||
'class' => 'suspicious-character',
|
||||
'replacement' => '!',
|
||||
),
|
||||
"\x7F" => array(
|
||||
'title' => 'DEL (0x7F)',
|
||||
'class' => 'suspicious-character',
|
||||
'replacement' => "\xE2\x90\xA1",
|
||||
),
|
||||
);
|
||||
|
||||
// Unicode defines special pictures for the control characters in the
|
||||
// range between "0x00" and "0x1F".
|
||||
|
||||
$control = array(
|
||||
'NULL',
|
||||
'SOH',
|
||||
'STX',
|
||||
'ETX',
|
||||
'EOT',
|
||||
'ENQ',
|
||||
'ACK',
|
||||
'BEL',
|
||||
'BS',
|
||||
null, // "\t" Tab
|
||||
null, // "\n" New Line
|
||||
'VT',
|
||||
'FF',
|
||||
null, // "\r" Carriage Return,
|
||||
'SO',
|
||||
'SI',
|
||||
'DLE',
|
||||
'DC1',
|
||||
'DC2',
|
||||
'DC3',
|
||||
'DC4',
|
||||
'NAK',
|
||||
'SYN',
|
||||
'ETB',
|
||||
'CAN',
|
||||
'EM',
|
||||
'SUB',
|
||||
'ESC',
|
||||
'FS',
|
||||
'GS',
|
||||
'RS',
|
||||
'US',
|
||||
);
|
||||
|
||||
foreach ($control as $idx => $label) {
|
||||
if ($label === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$rules[chr($idx)] = array(
|
||||
'title' => sprintf('%s (0x%02X)', $label, $idx),
|
||||
'class' => 'suspicious-character',
|
||||
'replacement' => "\xE2\x90".chr(0x80 + $idx),
|
||||
);
|
||||
}
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
private function replaceTabsWithSpaces($line) {
|
||||
// TODO: This should be flexible, eventually.
|
||||
$tab_width = 2;
|
||||
|
||||
static $tags;
|
||||
if ($tags === null) {
|
||||
$tags = array();
|
||||
for ($ii = 1; $ii <= $tab_width; $ii++) {
|
||||
$tag = phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'data-copy-text' => "\t",
|
||||
),
|
||||
str_repeat(' ', $ii));
|
||||
$tag = phutil_string_cast($tag);
|
||||
$tags[$ii] = $tag;
|
||||
}
|
||||
}
|
||||
|
||||
// If the line is particularly long, don't try to vectorize it. Use a
|
||||
// faster approximation of the correct tabstop expansion instead. This
|
||||
// usually still arrives at the right result.
|
||||
if (strlen($line) > 256) {
|
||||
return str_replace("\t", $tags[$tab_width], $line);
|
||||
}
|
||||
|
||||
$line = phutil_utf8v_combined($line);
|
||||
$in_tag = false;
|
||||
$pos = 0;
|
||||
foreach ($line as $key => $char) {
|
||||
if ($char === '<') {
|
||||
$in_tag = true;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($char === '>') {
|
||||
$in_tag = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($in_tag) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($char === "\t") {
|
||||
$count = $tab_width - ($pos % $tab_width);
|
||||
$pos += $count;
|
||||
$line[$key] = $tags[$count];
|
||||
continue;
|
||||
}
|
||||
|
||||
$pos++;
|
||||
}
|
||||
|
||||
return implode('', $line);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -5,8 +5,9 @@ final class DifferentialHunkParser extends Phobject {
|
|||
private $oldLines;
|
||||
private $newLines;
|
||||
private $intraLineDiffs;
|
||||
private $depthOnlyLines;
|
||||
private $visibleLinesMask;
|
||||
private $whitespaceMode;
|
||||
private $normalized;
|
||||
|
||||
/**
|
||||
* Get a map of lines on which hunks start, other than line 1. This
|
||||
|
@ -115,20 +116,22 @@ final class DifferentialHunkParser extends Phobject {
|
|||
return $this;
|
||||
}
|
||||
|
||||
|
||||
public function setWhitespaceMode($white_space_mode) {
|
||||
$this->whitespaceMode = $white_space_mode;
|
||||
public function setDepthOnlyLines(array $map) {
|
||||
$this->depthOnlyLines = $map;
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function getWhitespaceMode() {
|
||||
if ($this->whitespaceMode === null) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'You must %s before accessing this data.',
|
||||
'setWhitespaceMode'));
|
||||
}
|
||||
return $this->whitespaceMode;
|
||||
public function getDepthOnlyLines() {
|
||||
return $this->depthOnlyLines;
|
||||
}
|
||||
|
||||
public function setNormalized($normalized) {
|
||||
$this->normalized = $normalized;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNormalized() {
|
||||
return $this->normalized;
|
||||
}
|
||||
|
||||
public function getIsDeleted() {
|
||||
|
@ -150,13 +153,6 @@ final class DifferentialHunkParser extends Phobject {
|
|||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the hunks change any text, not just whitespace.
|
||||
*/
|
||||
public function getHasTextChanges() {
|
||||
return $this->getHasChanges('text');
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the hunks change anything, including whitespace.
|
||||
*/
|
||||
|
@ -184,9 +180,6 @@ final class DifferentialHunkParser extends Phobject {
|
|||
}
|
||||
|
||||
if ($o['type'] !== $n['type']) {
|
||||
// The types are different, so either the underlying text is actually
|
||||
// different or whatever whitespace rules we're using consider them
|
||||
// different.
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -269,62 +262,7 @@ final class DifferentialHunkParser extends Phobject {
|
|||
$this->setOldLines($rebuild_old);
|
||||
$this->setNewLines($rebuild_new);
|
||||
|
||||
$this->updateChangeTypesForWhitespaceMode();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
private function updateChangeTypesForWhitespaceMode() {
|
||||
$mode = $this->getWhitespaceMode();
|
||||
|
||||
$mode_show_all = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
|
||||
if ($mode === $mode_show_all) {
|
||||
// If we're showing all whitespace, we don't need to perform any updates.
|
||||
return;
|
||||
}
|
||||
|
||||
$mode_trailing = DifferentialChangesetParser::WHITESPACE_IGNORE_TRAILING;
|
||||
$is_trailing = ($mode === $mode_trailing);
|
||||
|
||||
$new = $this->getNewLines();
|
||||
$old = $this->getOldLines();
|
||||
foreach ($old as $key => $o) {
|
||||
$n = $new[$key];
|
||||
|
||||
if (!$o || !$n) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($is_trailing) {
|
||||
// In "trailing" mode, we need to identify lines which are marked
|
||||
// changed but differ only by trailing whitespace. We mark these lines
|
||||
// unchanged.
|
||||
if ($o['type'] != $n['type']) {
|
||||
if (rtrim($o['text']) === rtrim($n['text'])) {
|
||||
$old[$key]['type'] = null;
|
||||
$new[$key]['type'] = null;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// In "ignore most" and "ignore all" modes, we need to identify lines
|
||||
// which are marked unchanged but have internal whitespace changes.
|
||||
// We want to ignore leading and trailing whitespace changes only, not
|
||||
// internal whitespace changes (`diff` doesn't have a mode for this, so
|
||||
// we have to fix it here). If the text is marked unchanged but the
|
||||
// old and new text differs by internal space, mark the lines changed.
|
||||
if ($o['type'] === null && $n['type'] === null) {
|
||||
if ($o['text'] !== $n['text']) {
|
||||
if (trim($o['text']) !== trim($n['text'])) {
|
||||
$old[$key]['type'] = '-';
|
||||
$new[$key]['type'] = '+';
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->setOldLines($old);
|
||||
$this->setNewLines($new);
|
||||
$this->updateChangeTypesForNormalization();
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -334,6 +272,7 @@ final class DifferentialHunkParser extends Phobject {
|
|||
$new = $this->getNewLines();
|
||||
|
||||
$diffs = array();
|
||||
$depth_only = array();
|
||||
foreach ($old as $key => $o) {
|
||||
$n = $new[$key];
|
||||
|
||||
|
@ -342,13 +281,75 @@ final class DifferentialHunkParser extends Phobject {
|
|||
}
|
||||
|
||||
if ($o['type'] != $n['type']) {
|
||||
$diffs[$key] = ArcanistDiffUtils::generateIntralineDiff(
|
||||
$o['text'],
|
||||
$n['text']);
|
||||
$o_segments = array();
|
||||
$n_segments = array();
|
||||
$tab_width = 2;
|
||||
|
||||
$o_text = $o['text'];
|
||||
$n_text = $n['text'];
|
||||
|
||||
if ($o_text !== $n_text && (ltrim($o_text) === ltrim($n_text))) {
|
||||
$o_depth = $this->getIndentDepth($o_text, $tab_width);
|
||||
$n_depth = $this->getIndentDepth($n_text, $tab_width);
|
||||
|
||||
if ($o_depth < $n_depth) {
|
||||
$segment_type = '>';
|
||||
$segment_width = $this->getCharacterCountForVisualWhitespace(
|
||||
$n_text,
|
||||
($n_depth - $o_depth),
|
||||
$tab_width);
|
||||
if ($segment_width) {
|
||||
$n_text = substr($n_text, $segment_width);
|
||||
$n_segments[] = array(
|
||||
$segment_type,
|
||||
$segment_width,
|
||||
);
|
||||
}
|
||||
} else if ($o_depth > $n_depth) {
|
||||
$segment_type = '<';
|
||||
$segment_width = $this->getCharacterCountForVisualWhitespace(
|
||||
$o_text,
|
||||
($o_depth - $n_depth),
|
||||
$tab_width);
|
||||
if ($segment_width) {
|
||||
$o_text = substr($o_text, $segment_width);
|
||||
$o_segments[] = array(
|
||||
$segment_type,
|
||||
$segment_width,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// If there are no remaining changes to this line after we've marked
|
||||
// off the indent depth changes, this line was only modified by
|
||||
// changing the indent depth. Mark it for later so we can change how
|
||||
// it is displayed.
|
||||
if ($o_text === $n_text) {
|
||||
$depth_only[$key] = $segment_type;
|
||||
}
|
||||
}
|
||||
|
||||
$intraline_segments = ArcanistDiffUtils::generateIntralineDiff(
|
||||
$o_text,
|
||||
$n_text);
|
||||
|
||||
foreach ($intraline_segments[0] as $o_segment) {
|
||||
$o_segments[] = $o_segment;
|
||||
}
|
||||
|
||||
foreach ($intraline_segments[1] as $n_segment) {
|
||||
$n_segments[] = $n_segment;
|
||||
}
|
||||
|
||||
$diffs[$key] = array(
|
||||
$o_segments,
|
||||
$n_segments,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$this->setIntraLineDiffs($diffs);
|
||||
$this->setDepthOnlyLines($depth_only);
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
@ -671,4 +672,148 @@ final class DifferentialHunkParser extends Phobject {
|
|||
|
||||
return $offsets;
|
||||
}
|
||||
|
||||
private function getIndentDepth($text, $tab_width) {
|
||||
$len = strlen($text);
|
||||
|
||||
$depth = 0;
|
||||
for ($ii = 0; $ii < $len; $ii++) {
|
||||
$c = $text[$ii];
|
||||
|
||||
// If this is a space, increase the indent depth by 1.
|
||||
if ($c == ' ') {
|
||||
$depth++;
|
||||
continue;
|
||||
}
|
||||
|
||||
// If this is a tab, increase the indent depth to the next tabstop.
|
||||
|
||||
// For example, if the tab width is 4, these sequences both lead us to
|
||||
// a visual width of 8, i.e. the cursor will be in the 8th column:
|
||||
//
|
||||
// <tab><tab>
|
||||
// <space><tab><space><space><space><tab>
|
||||
|
||||
if ($c == "\t") {
|
||||
$depth = ($depth + $tab_width);
|
||||
$depth = $depth - ($depth % $tab_width);
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $depth;
|
||||
}
|
||||
|
||||
private function getCharacterCountForVisualWhitespace(
|
||||
$text,
|
||||
$depth,
|
||||
$tab_width) {
|
||||
|
||||
// Here, we know the visual indent depth of a line has been increased by
|
||||
// some amount (for example, 6 characters).
|
||||
|
||||
// We want to find the largest whitespace prefix of the string we can
|
||||
// which still fits into that amount of visual space.
|
||||
|
||||
// In most cases, this is very easy. For example, if the string has been
|
||||
// indented by two characters and the string begins with two spaces, that's
|
||||
// a perfect match.
|
||||
|
||||
// However, if the string has been indented by 7 characters, the tab width
|
||||
// is 8, and the string begins with "<space><space><tab>", we can only
|
||||
// mark the two spaces as an indent change. These cases are unusual.
|
||||
|
||||
$character_depth = 0;
|
||||
$visual_depth = 0;
|
||||
|
||||
$len = strlen($text);
|
||||
for ($ii = 0; $ii < $len; $ii++) {
|
||||
if ($visual_depth >= $depth) {
|
||||
break;
|
||||
}
|
||||
|
||||
$c = $text[$ii];
|
||||
|
||||
if ($c == ' ') {
|
||||
$character_depth++;
|
||||
$visual_depth++;
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($c == "\t") {
|
||||
// Figure out how many visual spaces we have until the next tabstop.
|
||||
$tab_visual = ($visual_depth + $tab_width);
|
||||
$tab_visual = $tab_visual - ($tab_visual % $tab_width);
|
||||
$tab_visual = ($tab_visual - $visual_depth);
|
||||
|
||||
// If this tab would take us over the limit, we're all done.
|
||||
$remaining_depth = ($depth - $visual_depth);
|
||||
if ($remaining_depth < $tab_visual) {
|
||||
break;
|
||||
}
|
||||
|
||||
$character_depth++;
|
||||
$visual_depth += $tab_visual;
|
||||
continue;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
return $character_depth;
|
||||
}
|
||||
|
||||
private function updateChangeTypesForNormalization() {
|
||||
if (!$this->getNormalized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If we've parsed based on a normalized diff alignment, we may currently
|
||||
// believe some lines are unchanged when they have actually changed. This
|
||||
// happens when:
|
||||
//
|
||||
// - a line changes;
|
||||
// - the change is a kind of change we normalize away when aligning the
|
||||
// diff, like an indentation change;
|
||||
// - we normalize the change away to align the diff; and so
|
||||
// - the old and new copies of the line are now aligned in the new
|
||||
// normalized diff.
|
||||
//
|
||||
// Then we end up with an alignment where the two lines that differ only
|
||||
// in some some trivial way are aligned. This is great, and exactly what
|
||||
// we're trying to accomplish by doing all this alignment stuff in the
|
||||
// first place.
|
||||
//
|
||||
// However, in this case the correctly-aligned lines will be incorrectly
|
||||
// marked as unchanged because the diff alorithm was fed normalized copies
|
||||
// of the lines, and these copies truly weren't any different.
|
||||
//
|
||||
// When lines are aligned and marked identical, but they're not actually
|
||||
// identcal, we now mark them as changed. The rest of the processing will
|
||||
// figure out how to render them appropritely.
|
||||
|
||||
$new = $this->getNewLines();
|
||||
$old = $this->getOldLines();
|
||||
foreach ($old as $key => $o) {
|
||||
$n = $new[$key];
|
||||
|
||||
if (!$o || !$n) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($o['type'] === null && $n['type'] === null) {
|
||||
if ($o['text'] !== $n['text']) {
|
||||
$old[$key]['type'] = '-';
|
||||
$new[$key]['type'] = '+';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->setOldLines($old);
|
||||
$this->setNewLines($new);
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
|
|
@ -359,7 +359,6 @@ final class DifferentialLineAdjustmentMap extends Phobject {
|
|||
}
|
||||
|
||||
$changeset = id(new PhabricatorDifferenceEngine())
|
||||
->setIgnoreWhitespace(true)
|
||||
->generateChangesetFromFileContent($u_old, $v_old);
|
||||
|
||||
$results[$u][$v] = self::newFromHunks(
|
||||
|
|
|
@ -367,7 +367,6 @@ abstract class DifferentialChangesetHTMLRenderer
|
|||
$reference = $this->getRenderingReference();
|
||||
|
||||
if ($force !== 'text' &&
|
||||
$force !== 'whitespace' &&
|
||||
$force !== 'none' &&
|
||||
$force !== 'default') {
|
||||
throw new Exception(
|
||||
|
@ -388,10 +387,6 @@ abstract class DifferentialChangesetHTMLRenderer
|
|||
'range' => $range,
|
||||
);
|
||||
|
||||
if ($force == 'whitespace') {
|
||||
$meta['whitespace'] = DifferentialChangesetParser::WHITESPACE_SHOW_ALL;
|
||||
}
|
||||
|
||||
$content = array();
|
||||
$content[] = $message;
|
||||
if ($force !== 'none') {
|
||||
|
@ -437,11 +432,17 @@ abstract class DifferentialChangesetHTMLRenderer
|
|||
$classes[] = 'PhabricatorMonospaced';
|
||||
$classes[] = $this->getRendererTableClass();
|
||||
|
||||
$sigils = array();
|
||||
$sigils[] = 'differential-diff';
|
||||
foreach ($this->getTableSigils() as $sigil) {
|
||||
$sigils[] = $sigil;
|
||||
}
|
||||
|
||||
return javelin_tag(
|
||||
'table',
|
||||
array(
|
||||
'class' => implode(' ', $classes),
|
||||
'sigil' => 'differential-diff',
|
||||
'sigil' => implode(' ', $sigils),
|
||||
),
|
||||
array(
|
||||
$this->renderColgroup(),
|
||||
|
@ -449,6 +450,10 @@ abstract class DifferentialChangesetHTMLRenderer
|
|||
));
|
||||
}
|
||||
|
||||
protected function getTableSigils() {
|
||||
return array();
|
||||
}
|
||||
|
||||
protected function buildInlineComment(
|
||||
PhabricatorInlineCommentInterface $comment,
|
||||
$on_right = false) {
|
||||
|
|
|
@ -92,19 +92,23 @@ final class DifferentialChangesetOneUpRenderer
|
|||
$line = $p['line'];
|
||||
|
||||
$cells[] = phutil_tag(
|
||||
'th',
|
||||
'td',
|
||||
array(
|
||||
'id' => $left_id,
|
||||
'class' => $class,
|
||||
),
|
||||
$line);
|
||||
'class' => $class.' n',
|
||||
'data-n' => $line,
|
||||
));
|
||||
|
||||
$render = $p['render'];
|
||||
if ($aural !== null) {
|
||||
$render = array($aural, $render);
|
||||
}
|
||||
|
||||
$cells[] = phutil_tag('th', array('class' => $class));
|
||||
$cells[] = phutil_tag(
|
||||
'td',
|
||||
array(
|
||||
'class' => $class.' n',
|
||||
));
|
||||
$cells[] = $no_copy;
|
||||
$cells[] = phutil_tag('td', array('class' => $class), $render);
|
||||
$cells[] = $no_coverage;
|
||||
|
@ -115,7 +119,11 @@ final class DifferentialChangesetOneUpRenderer
|
|||
} else {
|
||||
$class = 'right new';
|
||||
}
|
||||
$cells[] = phutil_tag('th', array('class' => $class));
|
||||
$cells[] = phutil_tag(
|
||||
'td',
|
||||
array(
|
||||
'class' => $class.' n',
|
||||
));
|
||||
$aural = $aural_plus;
|
||||
} else {
|
||||
$class = 'right';
|
||||
|
@ -127,7 +135,13 @@ final class DifferentialChangesetOneUpRenderer
|
|||
|
||||
$oline = $p['oline'];
|
||||
|
||||
$cells[] = phutil_tag('th', array('id' => $left_id), $oline);
|
||||
$cells[] = phutil_tag(
|
||||
'td',
|
||||
array(
|
||||
'id' => $left_id,
|
||||
'class' => 'n',
|
||||
'data-n' => $oline,
|
||||
));
|
||||
$aural = null;
|
||||
}
|
||||
|
||||
|
@ -144,12 +158,12 @@ final class DifferentialChangesetOneUpRenderer
|
|||
$line = $p['line'];
|
||||
|
||||
$cells[] = phutil_tag(
|
||||
'th',
|
||||
'td',
|
||||
array(
|
||||
'id' => $right_id,
|
||||
'class' => $class,
|
||||
),
|
||||
$line);
|
||||
'class' => $class.' n',
|
||||
'data-n' => $line,
|
||||
));
|
||||
|
||||
$render = $p['render'];
|
||||
if ($aural !== null) {
|
||||
|
|
|
@ -28,12 +28,13 @@ abstract class DifferentialChangesetRenderer extends Phobject {
|
|||
private $originalNew;
|
||||
private $gaps;
|
||||
private $mask;
|
||||
private $depths;
|
||||
private $originalCharacterEncoding;
|
||||
private $showEditAndReplyLinks;
|
||||
private $canMarkDone;
|
||||
private $objectOwnerPHID;
|
||||
private $highlightingDisabled;
|
||||
private $scopeEngine = false;
|
||||
private $depthOnlyLines;
|
||||
|
||||
private $oldFile = false;
|
||||
private $newFile = false;
|
||||
|
@ -76,14 +77,6 @@ abstract class DifferentialChangesetRenderer extends Phobject {
|
|||
return $this->isUndershield;
|
||||
}
|
||||
|
||||
public function setDepths($depths) {
|
||||
$this->depths = $depths;
|
||||
return $this;
|
||||
}
|
||||
protected function getDepths() {
|
||||
return $this->depths;
|
||||
}
|
||||
|
||||
public function setMask($mask) {
|
||||
$this->mask = $mask;
|
||||
return $this;
|
||||
|
@ -100,6 +93,15 @@ abstract class DifferentialChangesetRenderer extends Phobject {
|
|||
return $this->gaps;
|
||||
}
|
||||
|
||||
public function setDepthOnlyLines(array $lines) {
|
||||
$this->depthOnlyLines = $lines;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getDepthOnlyLines() {
|
||||
return $this->depthOnlyLines;
|
||||
}
|
||||
|
||||
public function attachOldFile(PhabricatorFile $old = null) {
|
||||
$this->oldFile = $old;
|
||||
return $this;
|
||||
|
@ -361,14 +363,14 @@ abstract class DifferentialChangesetRenderer extends Phobject {
|
|||
$undershield = $this->renderUndershieldHeader();
|
||||
}
|
||||
|
||||
$result = $notice.$props.$undershield.$content;
|
||||
$result = array(
|
||||
$notice,
|
||||
$props,
|
||||
$undershield,
|
||||
$content,
|
||||
);
|
||||
|
||||
// TODO: Let the user customize their tab width / display style.
|
||||
// TODO: We should possibly post-process "\r" as well.
|
||||
// TODO: Both these steps should happen earlier.
|
||||
$result = str_replace("\t", ' ', $result);
|
||||
|
||||
return phutil_safe_html($result);
|
||||
return hsprintf('%s', $result);
|
||||
}
|
||||
|
||||
abstract public function isOneUpRenderer();
|
||||
|
@ -404,9 +406,6 @@ abstract class DifferentialChangesetRenderer extends Phobject {
|
|||
* important (e.g., generated code).
|
||||
* - `"text"`: Force the text to be shown. This is probably only relevant
|
||||
* when a file is not changed.
|
||||
* - `"whitespace"`: Force the text to be shown, and the diff to be
|
||||
* rendered with all whitespace shown. This is probably only relevant
|
||||
* when a file is changed only by altering whitespace.
|
||||
* - `"none"`: Don't show the link (e.g., text not available).
|
||||
*
|
||||
* @param string Message explaining why the diff is hidden.
|
||||
|
@ -678,4 +677,42 @@ abstract class DifferentialChangesetRenderer extends Phobject {
|
|||
return $views;
|
||||
}
|
||||
|
||||
final protected function getScopeEngine() {
|
||||
if ($this->scopeEngine === false) {
|
||||
$hunk_starts = $this->getHunkStartLines();
|
||||
|
||||
// If this change is missing context, don't try to identify scopes, since
|
||||
// we won't really be able to get anywhere.
|
||||
$has_multiple_hunks = (count($hunk_starts) > 1);
|
||||
$has_offset_hunks = (head_key($hunk_starts) != 1);
|
||||
$missing_context = ($has_multiple_hunks || $has_offset_hunks);
|
||||
|
||||
if ($missing_context) {
|
||||
$scope_engine = null;
|
||||
} else {
|
||||
$line_map = $this->getNewLineTextMap();
|
||||
$scope_engine = id(new PhabricatorDiffScopeEngine())
|
||||
->setLineTextMap($line_map);
|
||||
}
|
||||
|
||||
$this->scopeEngine = $scope_engine;
|
||||
}
|
||||
|
||||
return $this->scopeEngine;
|
||||
}
|
||||
|
||||
private function getNewLineTextMap() {
|
||||
$new = $this->getNewLines();
|
||||
|
||||
$text_map = array();
|
||||
foreach ($new as $new_line) {
|
||||
if (!isset($new_line['line'])) {
|
||||
continue;
|
||||
}
|
||||
$text_map[$new_line['line']] = $new_line['text'];
|
||||
}
|
||||
|
||||
return $text_map;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -96,10 +96,14 @@ abstract class DifferentialChangesetTestRenderer
|
|||
array(
|
||||
'<span class="bright">',
|
||||
'</span>',
|
||||
'<span class="depth-out">',
|
||||
'<span class="depth-in">',
|
||||
),
|
||||
array(
|
||||
'{(',
|
||||
')}',
|
||||
'{<',
|
||||
'{>',
|
||||
),
|
||||
$render);
|
||||
|
||||
|
@ -127,7 +131,7 @@ abstract class DifferentialChangesetTestRenderer
|
|||
}
|
||||
|
||||
$out = implode("\n", $out)."\n";
|
||||
return $out;
|
||||
return phutil_safe_html($out);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -3,6 +3,8 @@
|
|||
final class DifferentialChangesetTwoUpRenderer
|
||||
extends DifferentialChangesetHTMLRenderer {
|
||||
|
||||
private $newOffsetMap;
|
||||
|
||||
public function isOneUpRenderer() {
|
||||
return false;
|
||||
}
|
||||
|
@ -66,9 +68,12 @@ final class DifferentialChangesetTwoUpRenderer
|
|||
$new_render = $this->getNewRender();
|
||||
$original_left = $this->getOriginalOld();
|
||||
$original_right = $this->getOriginalNew();
|
||||
$depths = $this->getDepths();
|
||||
$mask = $this->getMask();
|
||||
|
||||
$scope_engine = $this->getScopeEngine();
|
||||
$offset_map = null;
|
||||
$depth_only = $this->getDepthOnlyLines();
|
||||
|
||||
for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) {
|
||||
if (empty($mask[$ii])) {
|
||||
// If we aren't going to show this line, we've just entered a gap.
|
||||
|
@ -87,16 +92,19 @@ final class DifferentialChangesetTwoUpRenderer
|
|||
$is_last_block = true;
|
||||
}
|
||||
|
||||
$context = null;
|
||||
$context_text = null;
|
||||
$context_line = null;
|
||||
if (!$is_last_block && $depths[$ii + $len]) {
|
||||
for ($l = $ii + $len - 1; $l >= $ii; $l--) {
|
||||
$line = $new_lines[$l]['text'];
|
||||
if ($depths[$l] < $depths[$ii + $len] && trim($line) != '') {
|
||||
$context = $new_render[$l];
|
||||
$context_line = $new_lines[$l]['line'];
|
||||
break;
|
||||
if (!$is_last_block && $scope_engine) {
|
||||
$target_line = $new_lines[$ii + $len]['line'];
|
||||
$context_line = $scope_engine->getScopeStart($target_line);
|
||||
if ($context_line !== null) {
|
||||
// The scope engine returns a line number in the file. We need
|
||||
// to map that back to a display offset in the diff.
|
||||
if (!$offset_map) {
|
||||
$offset_map = $this->getNewLineToOffsetMap();
|
||||
}
|
||||
$offset = $offset_map[$context_line];
|
||||
$context_text = $new_render[$offset];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -109,16 +117,20 @@ final class DifferentialChangesetTwoUpRenderer
|
|||
phutil_tag(
|
||||
'td',
|
||||
array(
|
||||
'colspan' => 2,
|
||||
'class' => 'show-context-line n left-context',
|
||||
)),
|
||||
phutil_tag(
|
||||
'td',
|
||||
array(
|
||||
'class' => 'show-more',
|
||||
),
|
||||
$contents),
|
||||
phutil_tag(
|
||||
'th',
|
||||
'td',
|
||||
array(
|
||||
'class' => 'show-context-line',
|
||||
),
|
||||
$context_line ? (int)$context_line : null),
|
||||
'class' => 'show-context-line n',
|
||||
'data-n' => $context_line,
|
||||
)),
|
||||
phutil_tag(
|
||||
'td',
|
||||
array(
|
||||
|
@ -126,7 +138,7 @@ final class DifferentialChangesetTwoUpRenderer
|
|||
'class' => 'show-context',
|
||||
),
|
||||
// TODO: [HTML] Escaping model here isn't ideal.
|
||||
phutil_safe_html($context)),
|
||||
phutil_safe_html($context_text)),
|
||||
));
|
||||
|
||||
$html[] = $container;
|
||||
|
@ -188,12 +200,30 @@ final class DifferentialChangesetTwoUpRenderer
|
|||
} else if (empty($old_lines[$ii])) {
|
||||
$n_class = 'new new-full';
|
||||
} else {
|
||||
$n_class = 'new';
|
||||
|
||||
// NOTE: At least for the moment, I'm intentionally clearing the
|
||||
// line highlighting only on the right side of the diff when a
|
||||
// line has only depth changes. When a block depth is decreased,
|
||||
// this gives us a large color block on the left (to make it easy
|
||||
// to see the depth change) but a clean diff on the right (to make
|
||||
// it easy to pick out actual code changes).
|
||||
|
||||
if (isset($depth_only[$ii])) {
|
||||
$n_class = '';
|
||||
} else {
|
||||
$n_class = 'new';
|
||||
}
|
||||
}
|
||||
$n_classes = $n_class;
|
||||
|
||||
if ($new_lines[$ii]['type'] == '\\' || !isset($copy_lines[$n_num])) {
|
||||
$n_copy = phutil_tag('td', array('class' => "copy {$n_class}"));
|
||||
$not_copied =
|
||||
// If this line only changed depth, copy markers are pointless.
|
||||
(!isset($copy_lines[$n_num])) ||
|
||||
(isset($depth_only[$ii])) ||
|
||||
($new_lines[$ii]['type'] == '\\');
|
||||
|
||||
if ($not_copied) {
|
||||
$n_copy = phutil_tag('td', array('class' => 'copy'));
|
||||
} else {
|
||||
list($orig_file, $orig_line, $orig_type) = $copy_lines[$n_num];
|
||||
$title = ($orig_type == '-' ? 'Moved' : 'Copied').' from ';
|
||||
|
@ -213,8 +243,7 @@ final class DifferentialChangesetTwoUpRenderer
|
|||
'msg' => $title,
|
||||
),
|
||||
'class' => 'copy '.$class,
|
||||
),
|
||||
'');
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -275,23 +304,41 @@ final class DifferentialChangesetTwoUpRenderer
|
|||
}
|
||||
}
|
||||
|
||||
// NOTE: This is a unicode zero-width space, which we use as a hint when
|
||||
// intercepting 'copy' events to make sure sensible text ends up on the
|
||||
// clipboard. See the 'phabricator-oncopy' behavior.
|
||||
$zero_space = "\xE2\x80\x8B";
|
||||
$old_number = phutil_tag(
|
||||
'td',
|
||||
array(
|
||||
'id' => $o_id,
|
||||
'class' => $o_classes.' n',
|
||||
'data-n' => $o_num,
|
||||
));
|
||||
|
||||
$new_number = phutil_tag(
|
||||
'td',
|
||||
array(
|
||||
'id' => $n_id,
|
||||
'class' => $n_classes.' n',
|
||||
'data-n' => $n_num,
|
||||
));
|
||||
|
||||
$html[] = phutil_tag('tr', array(), array(
|
||||
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, 'class' => $n_classes), $n_num),
|
||||
$old_number,
|
||||
phutil_tag(
|
||||
'td',
|
||||
array(
|
||||
'class' => $o_classes,
|
||||
'data-copy-mode' => 'copy-l',
|
||||
),
|
||||
$o_text),
|
||||
$new_number,
|
||||
$n_copy,
|
||||
phutil_tag(
|
||||
'td',
|
||||
array('class' => $n_classes, 'colspan' => $n_colspan),
|
||||
array(
|
||||
phutil_tag('span', array('class' => 'zwsp'), $zero_space),
|
||||
$n_text,
|
||||
)),
|
||||
'class' => $n_classes,
|
||||
'colspan' => $n_colspan,
|
||||
'data-copy-mode' => 'copy-r',
|
||||
),
|
||||
$n_text),
|
||||
$n_cov,
|
||||
));
|
||||
|
||||
|
@ -386,4 +433,28 @@ final class DifferentialChangesetTwoUpRenderer
|
|||
->addInlineView($view);
|
||||
}
|
||||
|
||||
private function getNewLineToOffsetMap() {
|
||||
if ($this->newOffsetMap === null) {
|
||||
$new = $this->getNewLines();
|
||||
|
||||
$map = array();
|
||||
foreach ($new as $offset => $new_line) {
|
||||
if ($new_line['line'] === null) {
|
||||
continue;
|
||||
}
|
||||
$map[$new_line['line']] = $offset;
|
||||
}
|
||||
|
||||
$this->newOffsetMap = $map;
|
||||
}
|
||||
|
||||
return $this->newOffsetMap;
|
||||
}
|
||||
|
||||
protected function getTableSigils() {
|
||||
return array(
|
||||
'intercept-copy',
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -249,17 +249,6 @@ final class DifferentialChangeset
|
|||
return $path;
|
||||
}
|
||||
|
||||
public function getWhitespaceMatters() {
|
||||
$config = PhabricatorEnv::getEnvConfig('differential.whitespace-matters');
|
||||
foreach ($config as $regexp) {
|
||||
if (preg_match($regexp, $this->getFilename())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public function attachDiff(DifferentialDiff $diff) {
|
||||
$this->diff = $diff;
|
||||
return $this;
|
||||
|
|
|
@ -387,9 +387,10 @@ final class DifferentialDiff
|
|||
return array();
|
||||
}
|
||||
|
||||
$unit = id(new HarbormasterBuildUnitMessage())->loadAllWhere(
|
||||
'buildTargetPHID IN (%Ls)',
|
||||
$target_phids);
|
||||
$unit = id(new HarbormasterBuildUnitMessageQuery())
|
||||
->setViewer($viewer)
|
||||
->withBuildTargetPHIDs($target_phids)
|
||||
->execute();
|
||||
|
||||
$map = array();
|
||||
foreach ($unit as $message) {
|
||||
|
|
|
@ -9,7 +9,6 @@ final class DifferentialChangesetDetailView extends AphrontView {
|
|||
private $id;
|
||||
private $vsChangesetID;
|
||||
private $renderURI;
|
||||
private $whitespace;
|
||||
private $renderingRef;
|
||||
private $autoload;
|
||||
private $loaded;
|
||||
|
@ -42,15 +41,6 @@ final class DifferentialChangesetDetailView extends AphrontView {
|
|||
return $this->renderingRef;
|
||||
}
|
||||
|
||||
public function setWhitespace($whitespace) {
|
||||
$this->whitespace = $whitespace;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getWhitespace() {
|
||||
return $this->whitespace;
|
||||
}
|
||||
|
||||
public function setRenderURI($render_uri) {
|
||||
$this->renderURI = $render_uri;
|
||||
return $this;
|
||||
|
@ -196,7 +186,6 @@ final class DifferentialChangesetDetailView extends AphrontView {
|
|||
'left' => $left_id,
|
||||
'right' => $right_id,
|
||||
'renderURI' => $this->getRenderURI(),
|
||||
'whitespace' => $this->getWhitespace(),
|
||||
'highlight' => null,
|
||||
'renderer' => $this->getRenderer(),
|
||||
'ref' => $this->getRenderingRef(),
|
||||
|
|
|
@ -7,7 +7,6 @@ final class DifferentialChangesetListView extends AphrontView {
|
|||
private $references = array();
|
||||
private $inlineURI;
|
||||
private $renderURI = '/differential/changeset/';
|
||||
private $whitespace;
|
||||
private $background;
|
||||
private $header;
|
||||
private $isStandalone;
|
||||
|
@ -100,11 +99,6 @@ final class DifferentialChangesetListView extends AphrontView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setWhitespace($whitespace) {
|
||||
$this->whitespace = $whitespace;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setVsMap(array $vs_map) {
|
||||
$this->vsMap = $vs_map;
|
||||
return $this;
|
||||
|
@ -180,7 +174,6 @@ final class DifferentialChangesetListView extends AphrontView {
|
|||
$detail->setRenderingRef($ref);
|
||||
|
||||
$detail->setRenderURI($this->renderURI);
|
||||
$detail->setWhitespace($this->whitespace);
|
||||
$detail->setRenderer($renderer);
|
||||
|
||||
if ($this->getParser()) {
|
||||
|
@ -352,8 +345,7 @@ final class DifferentialChangesetListView extends AphrontView {
|
|||
$meta = array();
|
||||
|
||||
$qparams = array(
|
||||
'ref' => $ref,
|
||||
'whitespace' => $this->whitespace,
|
||||
'ref' => $ref,
|
||||
);
|
||||
|
||||
if ($this->standaloneURI) {
|
||||
|
|
|
@ -5,7 +5,6 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView {
|
|||
private $diffs = array();
|
||||
private $selectedVersusDiffID;
|
||||
private $selectedDiffID;
|
||||
private $selectedWhitespace;
|
||||
private $commitsForLinks = array();
|
||||
private $unitStatus = array();
|
||||
|
||||
|
@ -25,11 +24,6 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setSelectedWhitespace($whitespace) {
|
||||
$this->selectedWhitespace = $whitespace;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setCommitsForLinks(array $commits) {
|
||||
assert_instances_of($commits, 'PhabricatorRepositoryCommit');
|
||||
$this->commitsForLinks = $commits;
|
||||
|
@ -224,28 +218,6 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView {
|
|||
'radios' => $radios,
|
||||
));
|
||||
|
||||
$options = array(
|
||||
DifferentialChangesetParser::WHITESPACE_IGNORE_ALL => pht('Ignore All'),
|
||||
DifferentialChangesetParser::WHITESPACE_IGNORE_MOST => pht('Ignore Most'),
|
||||
DifferentialChangesetParser::WHITESPACE_IGNORE_TRAILING =>
|
||||
pht('Ignore Trailing'),
|
||||
DifferentialChangesetParser::WHITESPACE_SHOW_ALL => pht('Show All'),
|
||||
);
|
||||
|
||||
foreach ($options as $value => $label) {
|
||||
$options[$value] = phutil_tag(
|
||||
'option',
|
||||
array(
|
||||
'value' => $value,
|
||||
'selected' => ($value == $this->selectedWhitespace)
|
||||
? 'selected'
|
||||
: null,
|
||||
),
|
||||
$label);
|
||||
}
|
||||
$select = phutil_tag('select', array('name' => 'whitespace'), $options);
|
||||
|
||||
|
||||
$table = id(new AphrontTableView($rows));
|
||||
$table->setHeaders(
|
||||
array(
|
||||
|
@ -291,13 +263,6 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView {
|
|||
'class' => 'differential-update-history-footer',
|
||||
),
|
||||
array(
|
||||
phutil_tag(
|
||||
'label',
|
||||
array(),
|
||||
array(
|
||||
pht('Whitespace Changes:'),
|
||||
$select,
|
||||
)),
|
||||
phutil_tag(
|
||||
'button',
|
||||
array(),
|
||||
|
|
|
@ -64,9 +64,6 @@ final class DiffusionChangeController extends DiffusionController {
|
|||
$changeset_view->setRawFileURIs($left_uri, $right_uri);
|
||||
|
||||
$changeset_view->setRenderURI($repository->getPathURI('diff/'));
|
||||
|
||||
$changeset_view->setWhitespace(
|
||||
DifferentialChangesetParser::WHITESPACE_SHOW_ALL);
|
||||
$changeset_view->setUser($viewer);
|
||||
$changeset_view->setHeader($changeset_header);
|
||||
|
||||
|
|
|
@ -88,9 +88,6 @@ final class DiffusionDiffController extends DiffusionController {
|
|||
($viewer->getPHID() == $commit->getAuthorPHID()));
|
||||
$parser->setObjectOwnerPHID($commit->getAuthorPHID());
|
||||
|
||||
$parser->setWhitespaceMode(
|
||||
DifferentialChangesetParser::WHITESPACE_SHOW_ALL);
|
||||
|
||||
$inlines = PhabricatorAuditInlineComment::loadDraftAndPublishedComments(
|
||||
$viewer,
|
||||
$commit->getPHID(),
|
||||
|
|
|
@ -312,9 +312,10 @@ final class HarbormasterBuildableViewController
|
|||
'buildTargetPHID IN (%Ls)',
|
||||
$target_phids);
|
||||
|
||||
$unit_data = id(new HarbormasterBuildUnitMessage())->loadAllWhere(
|
||||
'buildTargetPHID IN (%Ls)',
|
||||
$target_phids);
|
||||
$unit_data = id(new HarbormasterBuildUnitMessageQuery())
|
||||
->setViewer($viewer)
|
||||
->withBuildTargetPHIDs($target_phids)
|
||||
->execute();
|
||||
|
||||
if ($lint_data) {
|
||||
$lint_table = id(new HarbormasterLintPropertyView())
|
||||
|
|
|
@ -31,9 +31,10 @@ final class HarbormasterUnitMessageListController
|
|||
|
||||
$unit_data = array();
|
||||
if ($target_phids) {
|
||||
$unit_data = id(new HarbormasterBuildUnitMessage())->loadAllWhere(
|
||||
'buildTargetPHID IN (%Ls)',
|
||||
$target_phids);
|
||||
$unit_data = id(new HarbormasterBuildUnitMessageQuery())
|
||||
->setViewer($viewer)
|
||||
->withBuildTargetPHIDs($target_phids)
|
||||
->execute();
|
||||
} else {
|
||||
$unit_data = array();
|
||||
}
|
||||
|
|
|
@ -12,7 +12,10 @@ final class HarbormasterUnitMessageViewController
|
|||
|
||||
$message_id = $request->getURIData('id');
|
||||
|
||||
$message = id(new HarbormasterBuildUnitMessage())->load($message_id);
|
||||
$message = id(new HarbormasterBuildUnitMessageQuery())
|
||||
->setViewer($viewer)
|
||||
->withIDs(array($message_id))
|
||||
->executeOne();
|
||||
if (!$message) {
|
||||
return new Aphront404Response();
|
||||
}
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterBuildUnitMessageQuery
|
||||
extends PhabricatorCursorPagedPolicyAwareQuery {
|
||||
|
||||
private $ids;
|
||||
private $phids;
|
||||
private $targetPHIDs;
|
||||
|
||||
public function withIDs(array $ids) {
|
||||
$this->ids = $ids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withPHIDs(array $phids) {
|
||||
$this->phids = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function withBuildTargetPHIDs(array $target_phids) {
|
||||
$this->targetPHIDs = $target_phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function newResultObject() {
|
||||
return new HarbormasterBuildUnitMessage();
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->phids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'phid in (%Ls)',
|
||||
$this->phids);
|
||||
}
|
||||
|
||||
if ($this->targetPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'buildTargetPHID in (%Ls)',
|
||||
$this->targetPHIDs);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
protected function didFilterPage(array $messages) {
|
||||
$indexes = array();
|
||||
foreach ($messages as $message) {
|
||||
$index = $message->getNameIndex();
|
||||
if (strlen($index)) {
|
||||
$indexes[$index] = $index;
|
||||
}
|
||||
}
|
||||
|
||||
if ($indexes) {
|
||||
$map = HarbormasterString::newIndexMap($indexes);
|
||||
|
||||
foreach ($messages as $message) {
|
||||
$index = $message->getNameIndex();
|
||||
|
||||
if (!strlen($index)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$name = idx($map, $index);
|
||||
if ($name === null) {
|
||||
$name = pht('Unknown Unit Message ("%s")', $index);
|
||||
}
|
||||
|
||||
$message->setName($name);
|
||||
}
|
||||
}
|
||||
|
||||
return $messages;
|
||||
}
|
||||
|
||||
public function getQueryApplicationClass() {
|
||||
return 'PhabricatorHarbormasterApplication';
|
||||
}
|
||||
|
||||
}
|
54
src/applications/harbormaster/storage/HarbormasterString.php
Normal file
54
src/applications/harbormaster/storage/HarbormasterString.php
Normal file
|
@ -0,0 +1,54 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterString
|
||||
extends HarbormasterDAO {
|
||||
|
||||
protected $stringIndex;
|
||||
protected $stringValue;
|
||||
|
||||
protected function getConfiguration() {
|
||||
return array(
|
||||
self::CONFIG_TIMESTAMPS => false,
|
||||
self::CONFIG_COLUMN_SCHEMA => array(
|
||||
'stringIndex' => 'bytes12',
|
||||
'stringValue' => 'text',
|
||||
),
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'key_string' => array(
|
||||
'columns' => array('stringIndex'),
|
||||
'unique' => true,
|
||||
),
|
||||
),
|
||||
) + parent::getConfiguration();
|
||||
}
|
||||
|
||||
public static function newIndex($string) {
|
||||
$index = PhabricatorHash::digestForIndex($string);
|
||||
|
||||
$table = new self();
|
||||
$conn = $table->establishConnection('w');
|
||||
|
||||
queryfx(
|
||||
$conn,
|
||||
'INSERT IGNORE INTO %R (stringIndex, stringValue) VALUES (%s, %s)',
|
||||
$table,
|
||||
$index,
|
||||
$string);
|
||||
|
||||
return $index;
|
||||
}
|
||||
|
||||
public static function newIndexMap(array $indexes) {
|
||||
$table = new self();
|
||||
$conn = $table->establishConnection('r');
|
||||
|
||||
$rows = queryfx_all(
|
||||
$conn,
|
||||
'SELECT stringIndex, stringValue FROM %R WHERE stringIndex IN (%Ls)',
|
||||
$table,
|
||||
$indexes);
|
||||
|
||||
return ipull($rows, 'stringValue', 'stringIndex');
|
||||
}
|
||||
|
||||
}
|
|
@ -1,12 +1,14 @@
|
|||
<?php
|
||||
|
||||
final class HarbormasterBuildUnitMessage
|
||||
extends HarbormasterDAO {
|
||||
extends HarbormasterDAO
|
||||
implements PhabricatorPolicyInterface {
|
||||
|
||||
protected $buildTargetPHID;
|
||||
protected $engine;
|
||||
protected $namespace;
|
||||
protected $name;
|
||||
protected $nameIndex;
|
||||
protected $result;
|
||||
protected $duration;
|
||||
protected $properties = array();
|
||||
|
@ -131,6 +133,7 @@ final class HarbormasterBuildUnitMessage
|
|||
'engine' => 'text255',
|
||||
'namespace' => 'text255',
|
||||
'name' => 'text255',
|
||||
'nameIndex' => 'bytes12',
|
||||
'result' => 'text32',
|
||||
'duration' => 'double?',
|
||||
),
|
||||
|
@ -259,4 +262,52 @@ final class HarbormasterBuildUnitMessage
|
|||
return implode("\0", $parts);
|
||||
}
|
||||
|
||||
public function save() {
|
||||
if ($this->nameIndex === null) {
|
||||
$this->nameIndex = HarbormasterString::newIndex($this->getName());
|
||||
}
|
||||
|
||||
// See T13088. While we're letting installs do online migrations to avoid
|
||||
// downtime, don't populate the "name" column for new writes. New writes
|
||||
// use the "HarbormasterString" table instead.
|
||||
$old_name = $this->name;
|
||||
$this->name = '';
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
$result = parent::save();
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
$this->name = $old_name;
|
||||
|
||||
if ($caught) {
|
||||
throw $caught;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyInterface )----------------------------------------- */
|
||||
|
||||
|
||||
public function getCapabilities() {
|
||||
return array(
|
||||
PhabricatorPolicyCapability::CAN_VIEW,
|
||||
);
|
||||
}
|
||||
|
||||
public function getPolicy($capability) {
|
||||
switch ($capability) {
|
||||
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||
return PhabricatorPolicies::getMostOpenPolicy();
|
||||
}
|
||||
}
|
||||
|
||||
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -210,8 +210,9 @@ The keys you can provide in a specification are:
|
|||
- `claim` //Optional bool.// By default, closing an unassigned task claims
|
||||
it. You can set this to `false` to disable this behavior for a particular
|
||||
status.
|
||||
- `locked` //Optional bool.// Lock tasks in this status, preventing users
|
||||
from commenting.
|
||||
- `locked` //Optional string.// Lock tasks in this status. Specify "comments"
|
||||
to lock comments (users who can edit the task may override this lock).
|
||||
Specify "edits" to prevent anyone except the task owner from making edits.
|
||||
- `mfa` //Optional bool.// Require all edits to this task to be signed with
|
||||
multi-factor authentication.
|
||||
|
||||
|
@ -342,6 +343,7 @@ dictionary with these keys:
|
|||
- `icon` //Optional string.// Icon for the subtype.
|
||||
- `children` //Optional map.// Configure options shown to the user when
|
||||
they "Create Subtask". See below.
|
||||
- `fields` //Optional map.// Configure field behaviors. See below.
|
||||
|
||||
Each subtype must have a unique key, and you must define a subtype with
|
||||
the key "%s", which is used as a default subtype.
|
||||
|
@ -397,6 +399,28 @@ be used when presenting options to the user.
|
|||
|
||||
If only one option would be presented, the user will be taken directly to the
|
||||
appropriate form instead of being prompted to choose a form.
|
||||
|
||||
The `fields` key can configure the behavior of custom fields on specific
|
||||
task subtypes. For example:
|
||||
|
||||
```
|
||||
{
|
||||
...
|
||||
"fields": {
|
||||
"custom.some-field": {
|
||||
"disabled": true
|
||||
}
|
||||
}
|
||||
...
|
||||
}
|
||||
```
|
||||
|
||||
Each field supports these options:
|
||||
|
||||
- `disabled` //Optional bool.// Allows you to disable fields on certain
|
||||
subtypes.
|
||||
- `name` //Optional string.// Custom name of this field for the subtype.
|
||||
|
||||
EOTEXT
|
||||
,
|
||||
$subtype_default_key));
|
||||
|
|
|
@ -16,6 +16,9 @@ final class ManiphestTaskStatus extends ManiphestConstants {
|
|||
const SPECIAL_CLOSED = 'closed';
|
||||
const SPECIAL_DUPLICATE = 'duplicate';
|
||||
|
||||
const LOCKED_COMMENTS = 'comments';
|
||||
const LOCKED_EDITS = 'edits';
|
||||
|
||||
private static function getStatusConfig() {
|
||||
return PhabricatorEnv::getEnvConfig('maniphest.statuses');
|
||||
}
|
||||
|
@ -156,8 +159,13 @@ final class ManiphestTaskStatus extends ManiphestConstants {
|
|||
return !self::isOpenStatus($status);
|
||||
}
|
||||
|
||||
public static function isLockedStatus($status) {
|
||||
return self::getStatusAttribute($status, 'locked', false);
|
||||
public static function areCommentsLockedInStatus($status) {
|
||||
return (bool)self::getStatusAttribute($status, 'locked', false);
|
||||
}
|
||||
|
||||
public static function areEditsLockedInStatus($status) {
|
||||
$locked = self::getStatusAttribute($status, 'locked');
|
||||
return ($locked === self::LOCKED_EDITS);
|
||||
}
|
||||
|
||||
public static function isMFAStatus($status) {
|
||||
|
@ -285,11 +293,35 @@ final class ManiphestTaskStatus extends ManiphestConstants {
|
|||
'keywords' => 'optional list<string>',
|
||||
'disabled' => 'optional bool',
|
||||
'claim' => 'optional bool',
|
||||
'locked' => 'optional bool',
|
||||
'locked' => 'optional bool|string',
|
||||
'mfa' => 'optional bool',
|
||||
));
|
||||
}
|
||||
|
||||
// Supported values are "comments" or "edits". For backward compatibility,
|
||||
// "true" is an alias of "comments".
|
||||
|
||||
foreach ($config as $key => $value) {
|
||||
$locked = idx($value, 'locked', false);
|
||||
if ($locked === true || $locked === false) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($locked === self::LOCKED_EDITS ||
|
||||
$locked === self::LOCKED_COMMENTS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Task status ("%s") has unrecognized value for "locked" '.
|
||||
'configuration ("%s"). Supported values are: "%s", "%s".',
|
||||
$key,
|
||||
$locked,
|
||||
self::LOCKED_COMMENTS,
|
||||
self::LOCKED_EDITS));
|
||||
}
|
||||
|
||||
$special_map = array();
|
||||
foreach ($config as $key => $value) {
|
||||
$special = idx($value, 'special');
|
||||
|
|
|
@ -552,6 +552,10 @@ final class ManiphestTransactionEditor
|
|||
$errors = array_merge($errors, $this->moreValidationErrors);
|
||||
}
|
||||
|
||||
foreach ($this->getLockValidationErrors($object, $xactions) as $error) {
|
||||
$errors[] = $error;
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
|
@ -1011,5 +1015,86 @@ final class ManiphestTransactionEditor
|
|||
}
|
||||
|
||||
|
||||
private function getLockValidationErrors($object, array $xactions) {
|
||||
$errors = array();
|
||||
|
||||
$old_owner = $object->getOwnerPHID();
|
||||
$old_status = $object->getStatus();
|
||||
|
||||
$new_owner = $old_owner;
|
||||
$new_status = $old_status;
|
||||
|
||||
$owner_xaction = null;
|
||||
$status_xaction = null;
|
||||
|
||||
foreach ($xactions as $xaction) {
|
||||
switch ($xaction->getTransactionType()) {
|
||||
case ManiphestTaskOwnerTransaction::TRANSACTIONTYPE:
|
||||
$new_owner = $xaction->getNewValue();
|
||||
$owner_xaction = $xaction;
|
||||
break;
|
||||
case ManiphestTaskStatusTransaction::TRANSACTIONTYPE:
|
||||
$new_status = $xaction->getNewValue();
|
||||
$status_xaction = $xaction;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
$actor_phid = $this->getActingAsPHID();
|
||||
|
||||
$was_locked = ManiphestTaskStatus::areEditsLockedInStatus(
|
||||
$old_status);
|
||||
$now_locked = ManiphestTaskStatus::areEditsLockedInStatus(
|
||||
$new_status);
|
||||
|
||||
if (!$now_locked) {
|
||||
// If we're not ending in an edit-locked status, everything is good.
|
||||
} else if ($new_owner !== null) {
|
||||
// If we ending the edit with some valid owner, this is allowed for
|
||||
// now. We might need to revisit this.
|
||||
} else {
|
||||
// The edits end with the task locked and unowned. No one will be able
|
||||
// to edit it, so we forbid this. We try to be specific about what the
|
||||
// user did wrong.
|
||||
|
||||
$owner_changed = ($old_owner && !$new_owner);
|
||||
$status_changed = ($was_locked !== $now_locked);
|
||||
$message = null;
|
||||
|
||||
if ($status_changed && $owner_changed) {
|
||||
$message = pht(
|
||||
'You can not lock this task and unassign it at the same time '.
|
||||
'because no one will be able to edit it anymore. Lock the task '.
|
||||
'or remove the owner, but not both.');
|
||||
$problem_xaction = $status_xaction;
|
||||
} else if ($status_changed) {
|
||||
$message = pht(
|
||||
'You can not lock this task because it does not have an owner. '.
|
||||
'No one would be able to edit the task. Assign the task to an '.
|
||||
'owner before locking it.');
|
||||
$problem_xaction = $status_xaction;
|
||||
} else if ($owner_changed) {
|
||||
$message = pht(
|
||||
'You can not remove the owner of this task because it is locked '.
|
||||
'and no one would be able to edit the task. Reassign the task or '.
|
||||
'unlock it before removing the owner.');
|
||||
$problem_xaction = $owner_xaction;
|
||||
} else {
|
||||
// If the task was already broken, we don't have a transaction to
|
||||
// complain about so just let it through. In theory, this is
|
||||
// impossible since policy rules should kick in before we get here.
|
||||
}
|
||||
|
||||
if ($message) {
|
||||
$errors[] = new PhabricatorApplicationTransactionValidationError(
|
||||
$problem_xaction->getTransactionType(),
|
||||
pht('Lock Error'),
|
||||
$message,
|
||||
$problem_xaction);
|
||||
}
|
||||
}
|
||||
|
||||
return $errors;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,70 @@
|
|||
<?php
|
||||
|
||||
final class ManiphestTaskPolicyCodex
|
||||
extends PhabricatorPolicyCodex {
|
||||
|
||||
public function getPolicyShortName() {
|
||||
$object = $this->getObject();
|
||||
|
||||
if ($object->areEditsLocked()) {
|
||||
return pht('Edits Locked');
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getPolicyIcon() {
|
||||
$object = $this->getObject();
|
||||
|
||||
if ($object->areEditsLocked()) {
|
||||
return 'fa-lock';
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getPolicyTagClasses() {
|
||||
$object = $this->getObject();
|
||||
$classes = array();
|
||||
|
||||
if ($object->areEditsLocked()) {
|
||||
$classes[] = 'policy-adjusted-locked';
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
|
||||
public function getPolicySpecialRuleDescriptions() {
|
||||
$object = $this->getObject();
|
||||
|
||||
$rules = array();
|
||||
|
||||
$rules[] = $this->newRule()
|
||||
->setCapabilities(
|
||||
array(
|
||||
PhabricatorPolicyCapability::CAN_EDIT,
|
||||
))
|
||||
->setIsActive($object->areEditsLocked())
|
||||
->setDescription(
|
||||
pht(
|
||||
'Tasks with edits locked may only be edited by their owner.'));
|
||||
|
||||
return $rules;
|
||||
}
|
||||
|
||||
public function getPolicyForEdit($capability) {
|
||||
|
||||
// When a task has its edits locked, the effective edit policy is locked
|
||||
// to "No One". However, the task owner may still bypass the lock and edit
|
||||
// the task. When they do, we want the control in the UI to have the
|
||||
// correct value. Return the real value stored on the object.
|
||||
|
||||
switch ($capability) {
|
||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||
return $this->getObject()->getEditPolicy();
|
||||
}
|
||||
|
||||
return parent::getPolicyForEdit($capability);
|
||||
}
|
||||
|
||||
}
|
|
@ -618,9 +618,9 @@ final class ManiphestTaskQuery extends PhabricatorCursorPagedPolicyAwareQuery {
|
|||
$joins = array();
|
||||
if ($this->hasOpenParents !== null) {
|
||||
if ($this->hasOpenParents) {
|
||||
$join_type = 'JOIN';
|
||||
$join_type = qsprintf($conn, 'JOIN');
|
||||
} else {
|
||||
$join_type = 'LEFT JOIN';
|
||||
$join_type = qsprintf($conn, 'LEFT JOIN');
|
||||
}
|
||||
|
||||
$joins[] = qsprintf(
|
||||
|
|
|
@ -20,7 +20,8 @@ final class ManiphestTask extends ManiphestDAO
|
|||
DoorkeeperBridgedObjectInterface,
|
||||
PhabricatorEditEngineSubtypeInterface,
|
||||
PhabricatorEditEngineLockableInterface,
|
||||
PhabricatorEditEngineMFAInterface {
|
||||
PhabricatorEditEngineMFAInterface,
|
||||
PhabricatorPolicyCodexInterface {
|
||||
|
||||
const MARKUP_FIELD_DESCRIPTION = 'markup:desc';
|
||||
|
||||
|
@ -217,8 +218,16 @@ final class ManiphestTask extends ManiphestDAO
|
|||
return ManiphestTaskStatus::isClosedStatus($this->getStatus());
|
||||
}
|
||||
|
||||
public function isLocked() {
|
||||
return ManiphestTaskStatus::isLockedStatus($this->getStatus());
|
||||
public function areCommentsLocked() {
|
||||
if ($this->areEditsLocked()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return ManiphestTaskStatus::areCommentsLockedInStatus($this->getStatus());
|
||||
}
|
||||
|
||||
public function areEditsLocked() {
|
||||
return ManiphestTaskStatus::areEditsLockedInStatus($this->getStatus());
|
||||
}
|
||||
|
||||
public function setProperty($key, $value) {
|
||||
|
@ -371,13 +380,17 @@ final class ManiphestTask extends ManiphestDAO
|
|||
case PhabricatorPolicyCapability::CAN_VIEW:
|
||||
return $this->getViewPolicy();
|
||||
case PhabricatorPolicyCapability::CAN_INTERACT:
|
||||
if ($this->isLocked()) {
|
||||
if ($this->areCommentsLocked()) {
|
||||
return PhabricatorPolicies::POLICY_NOONE;
|
||||
} else {
|
||||
return $this->getViewPolicy();
|
||||
}
|
||||
case PhabricatorPolicyCapability::CAN_EDIT:
|
||||
return $this->getEditPolicy();
|
||||
if ($this->areEditsLocked()) {
|
||||
return PhabricatorPolicies::POLICY_NOONE;
|
||||
} else {
|
||||
return $this->getEditPolicy();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -628,4 +641,12 @@ final class ManiphestTask extends ManiphestDAO
|
|||
return new ManiphestTaskMFAEngine();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorPolicyCodexInterface )------------------------------------ */
|
||||
|
||||
|
||||
public function newPolicyCodex() {
|
||||
return new ManiphestTaskPolicyCodex();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -19,7 +19,8 @@ final class PhabricatorPeopleDatasource
|
|||
$viewer = $this->getViewer();
|
||||
|
||||
$query = id(new PhabricatorPeopleQuery())
|
||||
->setOrderVector(array('username'));
|
||||
->setOrderVector(array('username'))
|
||||
->needAvailability(true);
|
||||
|
||||
if ($this->getPhase() == self::PHASE_PREFIX) {
|
||||
$prefix = $this->getPrefixQuery();
|
||||
|
@ -96,6 +97,14 @@ final class PhabricatorPeopleDatasource
|
|||
$result->setDisplayType($display_type);
|
||||
}
|
||||
|
||||
$until = $user->getAwayUntil();
|
||||
if ($until) {
|
||||
$availability = $user->getDisplayAvailability();
|
||||
$color = PhabricatorCalendarEventInvitee::getAvailabilityColor(
|
||||
$availability);
|
||||
$result->setAvailabilityColor($color);
|
||||
}
|
||||
|
||||
$results[] = $result;
|
||||
}
|
||||
|
||||
|
|
|
@ -29,6 +29,10 @@ abstract class PhabricatorPolicyCodex
|
|||
return array();
|
||||
}
|
||||
|
||||
public function getPolicyForEdit($capability) {
|
||||
return $this->getObject()->getPolicy($capability);
|
||||
}
|
||||
|
||||
public function getDefaultPolicy() {
|
||||
return PhabricatorPolicyQuery::getDefaultPolicyForObject(
|
||||
$this->viewer,
|
||||
|
|
|
@ -68,6 +68,14 @@ final class PhabricatorPolicyEditEngineExtension
|
|||
),
|
||||
);
|
||||
|
||||
if ($object instanceof PhabricatorPolicyCodexInterface) {
|
||||
$codex = PhabricatorPolicyCodex::newFromObject(
|
||||
$object,
|
||||
$viewer);
|
||||
} else {
|
||||
$codex = null;
|
||||
}
|
||||
|
||||
$fields = array();
|
||||
foreach ($map as $type => $spec) {
|
||||
if (empty($types[$type])) {
|
||||
|
@ -82,6 +90,18 @@ final class PhabricatorPolicyEditEngineExtension
|
|||
$conduit_description = $spec['description.conduit'];
|
||||
$edit = $spec['edit'];
|
||||
|
||||
// Objects may present a policy value to the edit workflow that is
|
||||
// different from their nominal policy value: for example, when tasks
|
||||
// are locked, they appear as "Editable By: No One" to other applications
|
||||
// but we still want to edit the actual policy stored in the database
|
||||
// when we show the user a form with a policy control in it.
|
||||
|
||||
if ($codex) {
|
||||
$policy_value = $codex->getPolicyForEdit($capability);
|
||||
} else {
|
||||
$policy_value = $object->getPolicy($capability);
|
||||
}
|
||||
|
||||
$policy_field = id(new PhabricatorPolicyEditField())
|
||||
->setKey($key)
|
||||
->setLabel($label)
|
||||
|
@ -94,7 +114,7 @@ final class PhabricatorPolicyEditEngineExtension
|
|||
->setDescription($description)
|
||||
->setConduitDescription($conduit_description)
|
||||
->setConduitTypeDescription(pht('New policy PHID or constant.'))
|
||||
->setValue($object->getPolicy($capability));
|
||||
->setValue($policy_value);
|
||||
$fields[] = $policy_field;
|
||||
|
||||
if ($object instanceof PhabricatorSpacesInterface) {
|
||||
|
|
|
@ -116,6 +116,10 @@ final class PhabricatorSearchManagementIndexWorkflow
|
|||
// them a hint that they might want to use "--force".
|
||||
$track_skips = (!$is_background && !$is_force);
|
||||
|
||||
// Activate "strict" error reporting if we're running in the foreground
|
||||
// so we'll report a wider range of conditions as errors.
|
||||
$is_strict = !$is_background;
|
||||
|
||||
$count_updated = 0;
|
||||
$count_skipped = 0;
|
||||
|
||||
|
@ -125,7 +129,10 @@ final class PhabricatorSearchManagementIndexWorkflow
|
|||
$old_versions = $this->loadIndexVersions($phid);
|
||||
}
|
||||
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing($phid, $parameters);
|
||||
PhabricatorSearchWorker::queueDocumentForIndexing(
|
||||
$phid,
|
||||
$parameters,
|
||||
$is_strict);
|
||||
|
||||
if ($track_skips) {
|
||||
$new_versions = $this->loadIndexVersions($phid);
|
||||
|
|
|
@ -2,7 +2,11 @@
|
|||
|
||||
final class PhabricatorSearchWorker extends PhabricatorWorker {
|
||||
|
||||
public static function queueDocumentForIndexing($phid, $parameters = null) {
|
||||
public static function queueDocumentForIndexing(
|
||||
$phid,
|
||||
$parameters = null,
|
||||
$is_strict = false) {
|
||||
|
||||
if ($parameters === null) {
|
||||
$parameters = array();
|
||||
}
|
||||
|
@ -12,6 +16,7 @@ final class PhabricatorSearchWorker extends PhabricatorWorker {
|
|||
array(
|
||||
'documentPHID' => $phid,
|
||||
'parameters' => $parameters,
|
||||
'strict' => $is_strict,
|
||||
),
|
||||
array(
|
||||
'priority' => parent::PRIORITY_INDEX,
|
||||
|
@ -23,7 +28,25 @@ final class PhabricatorSearchWorker extends PhabricatorWorker {
|
|||
$data = $this->getTaskData();
|
||||
$object_phid = idx($data, 'documentPHID');
|
||||
|
||||
$object = $this->loadObjectForIndexing($object_phid);
|
||||
// See T12425. By the time we run an indexing task, the object it indexes
|
||||
// may have been deleted. This is unusual, but not concerning, and failing
|
||||
// to index these objects is correct.
|
||||
|
||||
// To avoid showing these non-actionable errors to users, don't report
|
||||
// indexing exceptions unless we're in "strict" mode. This mode is set by
|
||||
// the "bin/search index" tool.
|
||||
|
||||
$is_strict = idx($data, 'strict', false);
|
||||
|
||||
try {
|
||||
$object = $this->loadObjectForIndexing($object_phid);
|
||||
} catch (PhabricatorWorkerPermanentFailureException $ex) {
|
||||
if ($is_strict) {
|
||||
throw $ex;
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$engine = id(new PhabricatorIndexEngine())
|
||||
->setObject($object);
|
||||
|
@ -35,8 +58,11 @@ final class PhabricatorSearchWorker extends PhabricatorWorker {
|
|||
return;
|
||||
}
|
||||
|
||||
$key = "index.{$object_phid}";
|
||||
$lock = PhabricatorGlobalLock::newLock($key);
|
||||
$lock = PhabricatorGlobalLock::newLock(
|
||||
'index',
|
||||
array(
|
||||
'objectPHID' => $object_phid,
|
||||
));
|
||||
|
||||
try {
|
||||
$lock->lock(1);
|
||||
|
@ -48,29 +74,34 @@ final class PhabricatorSearchWorker extends PhabricatorWorker {
|
|||
throw new PhabricatorWorkerYieldException(15);
|
||||
}
|
||||
|
||||
$caught = null;
|
||||
try {
|
||||
// Reload the object now that we have a lock, to make sure we have the
|
||||
// most current version.
|
||||
$object = $this->loadObjectForIndexing($object->getPHID());
|
||||
|
||||
$engine->setObject($object);
|
||||
|
||||
$engine->indexObject();
|
||||
} catch (Exception $ex) {
|
||||
$lock->unlock();
|
||||
$caught = $ex;
|
||||
}
|
||||
|
||||
if (!($ex instanceof PhabricatorWorkerPermanentFailureException)) {
|
||||
$ex = new PhabricatorWorkerPermanentFailureException(
|
||||
// Release the lock before we deal with the exception.
|
||||
$lock->unlock();
|
||||
|
||||
if ($caught) {
|
||||
if (!($caught instanceof PhabricatorWorkerPermanentFailureException)) {
|
||||
$caught = new PhabricatorWorkerPermanentFailureException(
|
||||
pht(
|
||||
'Failed to update search index for document "%s": %s',
|
||||
$object_phid,
|
||||
$ex->getMessage()));
|
||||
$caught->getMessage()));
|
||||
}
|
||||
|
||||
throw $ex;
|
||||
if ($is_strict) {
|
||||
throw $caught;
|
||||
}
|
||||
}
|
||||
|
||||
$lock->unlock();
|
||||
}
|
||||
|
||||
private function loadObjectForIndexing($phid) {
|
||||
|
|
|
@ -47,15 +47,6 @@ final class PhabricatorSubscriptionsEditController
|
|||
$handle->getURI());
|
||||
}
|
||||
|
||||
if (!PhabricatorPolicyFilter::canInteract($viewer, $object)) {
|
||||
$lock = PhabricatorEditEngineLock::newForObject($viewer, $object);
|
||||
|
||||
$dialog = $this->newDialog()
|
||||
->addCancelButton($handle->getURI());
|
||||
|
||||
return $lock->willBlockUserInteractionWithDialog($dialog);
|
||||
}
|
||||
|
||||
if ($object instanceof PhabricatorApplicationTransactionInterface) {
|
||||
if ($is_add) {
|
||||
$xaction_value = array(
|
||||
|
|
|
@ -73,24 +73,20 @@ final class PhabricatorSubscriptionsUIEventListener
|
|||
->setName(pht('Automatically Subscribed'))
|
||||
->setIcon('fa-check-circle lightgreytext');
|
||||
} else {
|
||||
$can_interact = PhabricatorPolicyFilter::canInteract($user, $object);
|
||||
|
||||
if ($is_subscribed) {
|
||||
$sub_action = id(new PhabricatorActionView())
|
||||
->setWorkflow(true)
|
||||
->setRenderAsForm(true)
|
||||
->setHref('/subscriptions/delete/'.$object->getPHID().'/')
|
||||
->setName(pht('Unsubscribe'))
|
||||
->setIcon('fa-minus-circle')
|
||||
->setDisabled(!$can_interact);
|
||||
->setIcon('fa-minus-circle');
|
||||
} else {
|
||||
$sub_action = id(new PhabricatorActionView())
|
||||
->setWorkflow(true)
|
||||
->setRenderAsForm(true)
|
||||
->setHref('/subscriptions/add/'.$object->getPHID().'/')
|
||||
->setName(pht('Subscribe'))
|
||||
->setIcon('fa-plus-circle')
|
||||
->setDisabled(!$can_interact);
|
||||
->setIcon('fa-plus-circle');
|
||||
}
|
||||
|
||||
if (!$user->isLoggedIn()) {
|
||||
|
|
|
@ -165,14 +165,29 @@ abstract class PhabricatorEditEngine
|
|||
$extensions = array();
|
||||
}
|
||||
|
||||
// See T13248. Create a template object to provide to extensions. We
|
||||
// adjust the template to have the intended subtype, so that extensions
|
||||
// may change behavior based on the form subtype.
|
||||
|
||||
$template_object = clone $object;
|
||||
if ($this->getIsCreate()) {
|
||||
if ($this->supportsSubtypes()) {
|
||||
$config = $this->getEditEngineConfiguration();
|
||||
$subtype = $config->getSubtype();
|
||||
$template_object->setSubtype($subtype);
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($extensions as $extension) {
|
||||
$extension->setViewer($viewer);
|
||||
|
||||
if (!$extension->supportsObject($this, $object)) {
|
||||
if (!$extension->supportsObject($this, $template_object)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$extension_fields = $extension->buildCustomEditFields($this, $object);
|
||||
$extension_fields = $extension->buildCustomEditFields(
|
||||
$this,
|
||||
$template_object);
|
||||
|
||||
// TODO: Validate this in more detail with a more tailored error.
|
||||
assert_instances_of($extension_fields, 'PhabricatorEditField');
|
||||
|
|
|
@ -13,6 +13,7 @@ final class PhabricatorEditEngineSubtype
|
|||
private $color;
|
||||
private $childSubtypes = array();
|
||||
private $childIdentifiers = array();
|
||||
private $fieldConfiguration = array();
|
||||
|
||||
public function setKey($key) {
|
||||
$this->key = $key;
|
||||
|
@ -94,6 +95,17 @@ final class PhabricatorEditEngineSubtype
|
|||
return $view;
|
||||
}
|
||||
|
||||
public function setSubtypeFieldConfiguration(
|
||||
$subtype_key,
|
||||
array $configuration) {
|
||||
$this->fieldConfiguration[$subtype_key] = $configuration;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getSubtypeFieldConfiguration($subtype_key) {
|
||||
return idx($this->fieldConfiguration, $subtype_key);
|
||||
}
|
||||
|
||||
public static function validateSubtypeKey($subtype) {
|
||||
if (strlen($subtype) > 64) {
|
||||
throw new Exception(
|
||||
|
@ -139,6 +151,7 @@ final class PhabricatorEditEngineSubtype
|
|||
'color' => 'optional string',
|
||||
'icon' => 'optional string',
|
||||
'children' => 'optional map<string, wild>',
|
||||
'fields' => 'optional map<string, wild>',
|
||||
));
|
||||
|
||||
$key = $value['key'];
|
||||
|
@ -183,6 +196,18 @@ final class PhabricatorEditEngineSubtype
|
|||
'or the other, but not both.'));
|
||||
}
|
||||
}
|
||||
|
||||
$fields = idx($value, 'fields');
|
||||
if ($fields) {
|
||||
foreach ($fields as $field_key => $configuration) {
|
||||
PhutilTypeSpec::checkMap(
|
||||
$configuration,
|
||||
array(
|
||||
'disabled' => 'optional bool',
|
||||
'name' => 'optional string',
|
||||
));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!isset($map[self::SUBTYPE_DEFAULT])) {
|
||||
|
@ -233,6 +258,15 @@ final class PhabricatorEditEngineSubtype
|
|||
$subtype->setChildFormIdentifiers($child_forms);
|
||||
}
|
||||
|
||||
$field_configurations = idx($entry, 'fields');
|
||||
if ($field_configurations) {
|
||||
foreach ($field_configurations as $field_key => $field_configuration) {
|
||||
$subtype->setSubtypeFieldConfiguration(
|
||||
$field_key,
|
||||
$field_configuration);
|
||||
}
|
||||
}
|
||||
|
||||
$map[$key] = $subtype;
|
||||
}
|
||||
|
||||
|
|
|
@ -1648,9 +1648,48 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
// don't enforce it here.
|
||||
return null;
|
||||
case PhabricatorTransactions::TYPE_SUBSCRIBERS:
|
||||
// TODO: Removing subscribers other than yourself should probably
|
||||
// require CAN_EDIT permission. You can do this via the API but
|
||||
// generally can not via the web interface.
|
||||
// Anyone can subscribe to or unsubscribe from anything they can view,
|
||||
// with no other permissions.
|
||||
|
||||
$old = array_fuse($xaction->getOldValue());
|
||||
$new = array_fuse($xaction->getNewValue());
|
||||
|
||||
// To remove users other than yourself, you must be able to edit the
|
||||
// object.
|
||||
$rem = array_diff_key($old, $new);
|
||||
foreach ($rem as $phid) {
|
||||
if ($phid !== $this->getActingAsPHID()) {
|
||||
return PhabricatorPolicyCapability::CAN_EDIT;
|
||||
}
|
||||
}
|
||||
|
||||
// To add users other than yourself, you must be able to interact.
|
||||
// This allows "@mentioning" users to work as long as you can comment
|
||||
// on objects.
|
||||
|
||||
// If you can edit, we return that policy instead so that you can
|
||||
// override a soft lock and still make edits.
|
||||
|
||||
// TODO: This is a little bit hacky. We really want to be able to say
|
||||
// "this requires either interact or edit", but there's currently no
|
||||
// way to specify this kind of requirement.
|
||||
|
||||
$can_edit = PhabricatorPolicyFilter::hasCapability(
|
||||
$this->getActor(),
|
||||
$this->object,
|
||||
PhabricatorPolicyCapability::CAN_EDIT);
|
||||
|
||||
$add = array_diff_key($new, $old);
|
||||
foreach ($add as $phid) {
|
||||
if ($phid !== $this->getActingAsPHID()) {
|
||||
if ($can_edit) {
|
||||
return PhabricatorPolicyCapability::CAN_EDIT;
|
||||
} else {
|
||||
return PhabricatorPolicyCapability::CAN_INTERACT;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
case PhabricatorTransactions::TYPE_TOKEN:
|
||||
// TODO: This technically requires CAN_INTERACT, like comments.
|
||||
|
|
|
@ -19,6 +19,7 @@ final class PhabricatorTypeaheadResult extends Phobject {
|
|||
private $autocomplete;
|
||||
private $attributes = array();
|
||||
private $phase;
|
||||
private $availabilityColor;
|
||||
|
||||
public function setIcon($icon) {
|
||||
$this->icon = $icon;
|
||||
|
@ -156,6 +157,7 @@ final class PhabricatorTypeaheadResult extends Phobject {
|
|||
$this->unique ? 1 : null,
|
||||
$this->autocomplete,
|
||||
$this->phase,
|
||||
$this->availabilityColor,
|
||||
);
|
||||
while (end($data) === null) {
|
||||
array_pop($data);
|
||||
|
@ -222,4 +224,13 @@ final class PhabricatorTypeaheadResult extends Phobject {
|
|||
return $this->phase;
|
||||
}
|
||||
|
||||
public function setAvailabilityColor($availability_color) {
|
||||
$this->availabilityColor = $availability_color;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAvailabilityColor() {
|
||||
return $this->availabilityColor;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -14,6 +14,7 @@ final class PhabricatorTypeaheadTokenView
|
|||
private $inputName;
|
||||
private $value;
|
||||
private $tokenType = self::TYPE_OBJECT;
|
||||
private $availabilityColor;
|
||||
|
||||
public static function newFromTypeaheadResult(
|
||||
PhabricatorTypeaheadResult $result) {
|
||||
|
@ -41,6 +42,21 @@ final class PhabricatorTypeaheadTokenView
|
|||
$token->setColor($handle->getTagColor());
|
||||
}
|
||||
|
||||
$availability = $handle->getAvailability();
|
||||
$color = null;
|
||||
switch ($availability) {
|
||||
case PhabricatorObjectHandle::AVAILABILITY_PARTIAL:
|
||||
$color = PHUITagView::COLOR_ORANGE;
|
||||
break;
|
||||
case PhabricatorObjectHandle::AVAILABILITY_NONE:
|
||||
$color = PHUITagView::COLOR_RED;
|
||||
break;
|
||||
}
|
||||
|
||||
if ($color !== null) {
|
||||
$token->setAvailabilityColor($color);
|
||||
}
|
||||
|
||||
return $token;
|
||||
}
|
||||
|
||||
|
@ -106,6 +122,15 @@ final class PhabricatorTypeaheadTokenView
|
|||
return 'a';
|
||||
}
|
||||
|
||||
public function setAvailabilityColor($availability_color) {
|
||||
$this->availabilityColor = $availability_color;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getAvailabilityColor() {
|
||||
return $this->availabilityColor;
|
||||
}
|
||||
|
||||
protected function getTagAttributes() {
|
||||
$classes = array();
|
||||
$classes[] = 'jx-tokenizer-token';
|
||||
|
@ -139,20 +164,32 @@ final class PhabricatorTypeaheadTokenView
|
|||
|
||||
$value = $this->getValue();
|
||||
|
||||
$availability = null;
|
||||
$availability_color = $this->getAvailabilityColor();
|
||||
if ($availability_color) {
|
||||
$availability = phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'phui-tag-dot phui-tag-color-'.$availability_color,
|
||||
));
|
||||
}
|
||||
|
||||
$icon_view = null;
|
||||
$icon = $this->getIcon();
|
||||
if ($icon) {
|
||||
$value = array(
|
||||
phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'phui-icon-view phui-font-fa '.$icon,
|
||||
)),
|
||||
$value,
|
||||
);
|
||||
$icon_view = phutil_tag(
|
||||
'span',
|
||||
array(
|
||||
'class' => 'phui-icon-view phui-font-fa '.$icon,
|
||||
));
|
||||
}
|
||||
|
||||
return array(
|
||||
$value,
|
||||
array(
|
||||
$icon_view,
|
||||
$availability,
|
||||
$value,
|
||||
),
|
||||
phutil_tag(
|
||||
'input',
|
||||
array(
|
||||
|
|
|
@ -51,22 +51,6 @@ You need to install and configure **Pygments** to highlight anything else than
|
|||
PHP. See the `pygments.enabled` configuration setting.
|
||||
|
||||
|
||||
= What do the whitespace options mean? =
|
||||
|
||||
Most of these are pretty straightforward, but "Ignore Most" is not:
|
||||
|
||||
- **Show All**: Show all whitespace.
|
||||
- **Ignore Trailing**: Ignore changes which only affect trailing whitespace.
|
||||
- **Ignore Most**: Ignore changes which only affect leading or trailing
|
||||
whitespace (but not whitespace changes between non-whitespace characters)
|
||||
in files which are not marked as having significant whitespace.
|
||||
In those files, show whitespace changes. By default, Python (.py) and
|
||||
Haskell (.lhs, .hs) are marked as having significant whitespace, but this
|
||||
can be changed in the `differential.whitespace-matters` configuration
|
||||
setting.
|
||||
- **Ignore All**: Ignore all whitespace changes in all files.
|
||||
|
||||
|
||||
= What do the very light green and red backgrounds mean? =
|
||||
|
||||
Differential uses these colors to mark changes coming from rebase: they are
|
||||
|
|
|
@ -74,9 +74,22 @@ abstract class PhabricatorCustomField extends Phobject {
|
|||
$spec,
|
||||
$object);
|
||||
|
||||
$fields = self::adjustCustomFieldsForObjectSubtype(
|
||||
$object,
|
||||
$role,
|
||||
$fields);
|
||||
|
||||
foreach ($fields as $key => $field) {
|
||||
// NOTE: We perform this filtering in "buildFieldList()", but may need
|
||||
// to filter again after subtype adjustment.
|
||||
if (!$field->isFieldEnabled()) {
|
||||
unset($fields[$key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$field->shouldEnableForRole($role)) {
|
||||
unset($fields[$key]);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1622,4 +1635,78 @@ abstract class PhabricatorCustomField extends Phobject {
|
|||
return null;
|
||||
}
|
||||
|
||||
private static function adjustCustomFieldsForObjectSubtype(
|
||||
PhabricatorCustomFieldInterface $object,
|
||||
$role,
|
||||
array $fields) {
|
||||
assert_instances_of($fields, __CLASS__);
|
||||
|
||||
// We only apply subtype adjustment for some roles. For example, when
|
||||
// writing Herald rules or building a Search interface, we always want to
|
||||
// show all the fields in their default state, so we do not apply any
|
||||
// adjustments.
|
||||
$subtype_roles = array(
|
||||
self::ROLE_EDITENGINE,
|
||||
self::ROLE_VIEW,
|
||||
);
|
||||
|
||||
$subtype_roles = array_fuse($subtype_roles);
|
||||
if (!isset($subtype_roles[$role])) {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
// If the object doesn't support subtypes, we can't possibly make
|
||||
// any adjustments based on subtype.
|
||||
if (!($object instanceof PhabricatorEditEngineSubtypeInterface)) {
|
||||
return $fields;
|
||||
}
|
||||
|
||||
$subtype_map = $object->newEditEngineSubtypeMap();
|
||||
$subtype_key = $object->getEditEngineSubtype();
|
||||
$subtype_object = $subtype_map->getSubtype($subtype_key);
|
||||
|
||||
$map = array();
|
||||
foreach ($fields as $field) {
|
||||
$modern_key = $field->getModernFieldKey();
|
||||
if (!strlen($modern_key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$map[$modern_key] = $field;
|
||||
}
|
||||
|
||||
foreach ($map as $field_key => $field) {
|
||||
// For now, only support overriding standard custom fields. In the
|
||||
// future there's no technical or product reason we couldn't let you
|
||||
// override (some properites of) other fields like "Title", but they
|
||||
// don't usually support appropriate "setX()" methods today.
|
||||
if (!($field instanceof PhabricatorStandardCustomField)) {
|
||||
// For fields that are proxies on top of StandardCustomField, which
|
||||
// is how most application custom fields work today, we can reconfigure
|
||||
// the proxied field instead.
|
||||
$field = $field->getProxy();
|
||||
if (!$field || !($field instanceof PhabricatorStandardCustomField)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$subtype_config = $subtype_object->getSubtypeFieldConfiguration(
|
||||
$field_key);
|
||||
|
||||
if (!$subtype_config) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (isset($subtype_config['disabled'])) {
|
||||
$field->setIsEnabled(!$subtype_config['disabled']);
|
||||
}
|
||||
|
||||
if (isset($subtype_config['name'])) {
|
||||
$field->setFieldName($subtype_config['name']);
|
||||
}
|
||||
}
|
||||
|
||||
return $fields;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -18,6 +18,7 @@ abstract class PhabricatorStandardCustomField
|
|||
private $isCopyable;
|
||||
private $hasStorageValue;
|
||||
private $isBuiltin;
|
||||
private $isEnabled = true;
|
||||
|
||||
abstract public function getFieldType();
|
||||
|
||||
|
@ -175,6 +176,19 @@ abstract class PhabricatorStandardCustomField
|
|||
return $this->rawKey;
|
||||
}
|
||||
|
||||
public function setIsEnabled($is_enabled) {
|
||||
$this->isEnabled = $is_enabled;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getIsEnabled() {
|
||||
return $this->isEnabled;
|
||||
}
|
||||
|
||||
public function isFieldEnabled() {
|
||||
return $this->getIsEnabled();
|
||||
}
|
||||
|
||||
|
||||
/* -( PhabricatorCustomField )--------------------------------------------- */
|
||||
|
||||
|
|
|
@ -12,37 +12,22 @@ final class PhabricatorWorkerActiveTask extends PhabricatorWorkerTask {
|
|||
|
||||
$config = array(
|
||||
self::CONFIG_IDS => self::IDS_COUNTER,
|
||||
self::CONFIG_TIMESTAMPS => false,
|
||||
self::CONFIG_KEY_SCHEMA => array(
|
||||
'dataID' => array(
|
||||
'columns' => array('dataID'),
|
||||
'unique' => true,
|
||||
),
|
||||
'taskClass' => array(
|
||||
'columns' => array('taskClass'),
|
||||
),
|
||||
'leaseExpires' => array(
|
||||
'columns' => array('leaseExpires'),
|
||||
),
|
||||
'leaseOwner' => array(
|
||||
'columns' => array('leaseOwner(16)'),
|
||||
),
|
||||
'key_failuretime' => array(
|
||||
'columns' => array('failureTime'),
|
||||
),
|
||||
'leaseOwner_2' => array(
|
||||
'key_owner' => array(
|
||||
'columns' => array('leaseOwner', 'priority', 'id'),
|
||||
),
|
||||
) + $parent[self::CONFIG_KEY_SCHEMA],
|
||||
);
|
||||
|
||||
$config[self::CONFIG_COLUMN_SCHEMA] = array(
|
||||
// T6203/NULLABILITY
|
||||
// This isn't nullable in the archive table, so at a minimum these
|
||||
// should be the same.
|
||||
'dataID' => 'uint32?',
|
||||
) + $parent[self::CONFIG_COLUMN_SCHEMA];
|
||||
|
||||
return $config + $parent;
|
||||
}
|
||||
|
||||
|
@ -74,7 +59,7 @@ final class PhabricatorWorkerActiveTask extends PhabricatorWorkerTask {
|
|||
$this->failureCount = 0;
|
||||
}
|
||||
|
||||
if ($is_new && ($this->getData() !== null)) {
|
||||
if ($is_new) {
|
||||
$data = new PhabricatorWorkerTaskData();
|
||||
$data->setData($this->getData());
|
||||
$data->save();
|
||||
|
@ -132,7 +117,9 @@ final class PhabricatorWorkerActiveTask extends PhabricatorWorkerTask {
|
|||
->setPriority($this->getPriority())
|
||||
->setObjectPHID($this->getObjectPHID())
|
||||
->setResult($result)
|
||||
->setDuration($duration);
|
||||
->setDuration($duration)
|
||||
->setDateCreated($this->getDateCreated())
|
||||
->setArchivedEpoch(PhabricatorTime::getNow());
|
||||
|
||||
// NOTE: This deletes the active task (this object)!
|
||||
$archive->save();
|
||||
|
|
|
@ -8,6 +8,7 @@ final class PhabricatorWorkerArchiveTask extends PhabricatorWorkerTask {
|
|||
|
||||
protected $duration;
|
||||
protected $result;
|
||||
protected $archivedEpoch;
|
||||
|
||||
protected function getConfiguration() {
|
||||
$parent = parent::getConfiguration();
|
||||
|
@ -22,15 +23,13 @@ final class PhabricatorWorkerArchiveTask extends PhabricatorWorkerTask {
|
|||
$config[self::CONFIG_COLUMN_SCHEMA] = array(
|
||||
'result' => 'uint32',
|
||||
'duration' => 'uint64',
|
||||
'archivedEpoch' => 'epoch?',
|
||||
) + $config[self::CONFIG_COLUMN_SCHEMA];
|
||||
|
||||
$config[self::CONFIG_KEY_SCHEMA] = array(
|
||||
'dateCreated' => array(
|
||||
'columns' => array('dateCreated'),
|
||||
),
|
||||
'leaseOwner' => array(
|
||||
'columns' => array('leaseOwner', 'priority', 'id'),
|
||||
),
|
||||
'key_modified' => array(
|
||||
'columns' => array('dateModified'),
|
||||
),
|
||||
|
@ -88,6 +87,7 @@ final class PhabricatorWorkerArchiveTask extends PhabricatorWorkerTask {
|
|||
->setDataID($this->getDataID())
|
||||
->setPriority($this->getPriority())
|
||||
->setObjectPHID($this->getObjectPHID())
|
||||
->setDateCreated($this->getDateCreated())
|
||||
->insert();
|
||||
|
||||
$this->setDataID(null);
|
||||
|
|
156
src/infrastructure/diff/PhabricatorDiffScopeEngine.php
Normal file
156
src/infrastructure/diff/PhabricatorDiffScopeEngine.php
Normal file
|
@ -0,0 +1,156 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDiffScopeEngine
|
||||
extends Phobject {
|
||||
|
||||
private $lineTextMap;
|
||||
private $lineDepthMap;
|
||||
|
||||
public function setLineTextMap(array $map) {
|
||||
if (array_key_exists(0, $map)) {
|
||||
throw new Exception(
|
||||
pht('ScopeEngine text map must be a 1-based map of lines.'));
|
||||
}
|
||||
|
||||
$expect = 1;
|
||||
foreach ($map as $key => $value) {
|
||||
if ($key === $expect) {
|
||||
$expect++;
|
||||
continue;
|
||||
}
|
||||
|
||||
throw new Exception(
|
||||
pht(
|
||||
'ScopeEngine text map must be a contiguous map of '.
|
||||
'lines, but is not: found key "%s" where key "%s" was expected.',
|
||||
$key,
|
||||
$expect));
|
||||
}
|
||||
|
||||
$this->lineTextMap = $map;
|
||||
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getLineTextMap() {
|
||||
if ($this->lineTextMap === null) {
|
||||
throw new PhutilInvalidStateException('setLineTextMap');
|
||||
}
|
||||
return $this->lineTextMap;
|
||||
}
|
||||
|
||||
public function getScopeStart($line) {
|
||||
$text_map = $this->getLineTextMap();
|
||||
$depth_map = $this->getLineDepthMap();
|
||||
$length = count($text_map);
|
||||
|
||||
// Figure out the effective depth of the line we're getting scope for.
|
||||
// If the line is just whitespace, it may have no depth on its own. In
|
||||
// this case, we look for the next line.
|
||||
$line_depth = null;
|
||||
for ($ii = $line; $ii <= $length; $ii++) {
|
||||
if ($depth_map[$ii] !== null) {
|
||||
$line_depth = $depth_map[$ii];
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// If we can't find a line depth for the target line, just bail.
|
||||
if ($line_depth === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Limit the maximum number of lines we'll examine. If a user has a
|
||||
// million-line diff of nonsense, scanning the whole thing is a waste
|
||||
// of time.
|
||||
$search_range = 1000;
|
||||
$search_until = max(0, $ii - $search_range);
|
||||
|
||||
for ($ii = $line - 1; $ii > $search_until; $ii--) {
|
||||
$line_text = $text_map[$ii];
|
||||
|
||||
// This line is in missing context: the diff was diffed with partial
|
||||
// context, and we ran out of context before finding a good scope line.
|
||||
// Bail out, we don't want to jump across missing context blocks.
|
||||
if ($line_text === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$depth = $depth_map[$ii];
|
||||
|
||||
// This line is all whitespace. This isn't a possible match.
|
||||
if ($depth === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// The depth is the same as (or greater than) the depth we started with,
|
||||
// so this isn't a possible match.
|
||||
if ($depth >= $line_depth) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Reject lines which begin with "}" or "{". These lines are probably
|
||||
// never good matches.
|
||||
if (preg_match('/^\s*[{}]/i', $line_text)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return $ii;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getLineDepthMap() {
|
||||
if (!$this->lineDepthMap) {
|
||||
$this->lineDepthMap = $this->newLineDepthMap();
|
||||
}
|
||||
|
||||
return $this->lineDepthMap;
|
||||
}
|
||||
|
||||
private function newLineDepthMap() {
|
||||
$text_map = $this->getLineTextMap();
|
||||
|
||||
// TODO: This should be configurable once we handle tab widths better.
|
||||
$tab_width = 2;
|
||||
|
||||
$depth_map = array();
|
||||
foreach ($text_map as $line_number => $line_text) {
|
||||
if ($line_text === null) {
|
||||
$depth_map[$line_number] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
$len = strlen($line_text);
|
||||
|
||||
// If the line has no actual text, don't assign it a depth.
|
||||
if (!$len || !strlen(trim($line_text))) {
|
||||
$depth_map[$line_number] = null;
|
||||
continue;
|
||||
}
|
||||
|
||||
$count = 0;
|
||||
for ($ii = 0; $ii < $len; $ii++) {
|
||||
$c = $line_text[$ii];
|
||||
if ($c == ' ') {
|
||||
$count++;
|
||||
} else if ($c == "\t") {
|
||||
$count += $tab_width;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Round down to cheat our way through the " *" parts of docblock
|
||||
// comments. This is generally a reasonble heuristic because odd tab
|
||||
// widths are exceptionally rare.
|
||||
$depth = ($count >> 1);
|
||||
|
||||
$depth_map[$line_number] = $depth;
|
||||
}
|
||||
|
||||
return $depth_map;
|
||||
}
|
||||
|
||||
}
|
|
@ -10,27 +10,14 @@
|
|||
final class PhabricatorDifferenceEngine extends Phobject {
|
||||
|
||||
|
||||
private $ignoreWhitespace;
|
||||
private $oldName;
|
||||
private $newName;
|
||||
private $normalize;
|
||||
|
||||
|
||||
/* -( Configuring the Engine )--------------------------------------------- */
|
||||
|
||||
|
||||
/**
|
||||
* If true, ignore whitespace when computing differences.
|
||||
*
|
||||
* @param bool Ignore whitespace?
|
||||
* @return this
|
||||
* @task config
|
||||
*/
|
||||
public function setIgnoreWhitespace($ignore_whitespace) {
|
||||
$this->ignoreWhitespace = $ignore_whitespace;
|
||||
return $this;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Set the name to identify the old file with. Primarily cosmetic.
|
||||
*
|
||||
|
@ -57,6 +44,16 @@ final class PhabricatorDifferenceEngine extends Phobject {
|
|||
}
|
||||
|
||||
|
||||
public function setNormalize($normalize) {
|
||||
$this->normalize = $normalize;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getNormalize() {
|
||||
return $this->normalize;
|
||||
}
|
||||
|
||||
|
||||
/* -( Generating Diffs )--------------------------------------------------- */
|
||||
|
||||
|
||||
|
@ -73,9 +70,6 @@ final class PhabricatorDifferenceEngine extends Phobject {
|
|||
public function generateRawDiffFromFileContent($old, $new) {
|
||||
|
||||
$options = array();
|
||||
if ($this->ignoreWhitespace) {
|
||||
$options[] = '-bw';
|
||||
}
|
||||
|
||||
// Generate diffs with full context.
|
||||
$options[] = '-U65535';
|
||||
|
@ -88,6 +82,12 @@ final class PhabricatorDifferenceEngine extends Phobject {
|
|||
$options[] = '-L';
|
||||
$options[] = $new_name;
|
||||
|
||||
$normalize = $this->getNormalize();
|
||||
if ($normalize) {
|
||||
$old = $this->normalizeFile($old);
|
||||
$new = $this->normalizeFile($new);
|
||||
}
|
||||
|
||||
$old_tmp = new TempFile();
|
||||
$new_tmp = new TempFile();
|
||||
|
||||
|
@ -100,12 +100,10 @@ final class PhabricatorDifferenceEngine extends Phobject {
|
|||
$new_tmp);
|
||||
|
||||
if (!$err) {
|
||||
// This indicates that the two files are the same (or, possibly, the
|
||||
// same modulo whitespace differences, which is why we can't do this
|
||||
// check trivially before running `diff`). Build a synthetic, changeless
|
||||
// diff so that we can still render the raw, unchanged file instead of
|
||||
// being forced to just say "this file didn't change" since we don't have
|
||||
// the content.
|
||||
// This indicates that the two files are the same. Build a synthetic,
|
||||
// changeless diff so that we can still render the raw, unchanged file
|
||||
// instead of being forced to just say "this file didn't change" since we
|
||||
// don't have the content.
|
||||
|
||||
$entire_file = explode("\n", $old);
|
||||
foreach ($entire_file as $k => $line) {
|
||||
|
@ -123,26 +121,6 @@ final class PhabricatorDifferenceEngine extends Phobject {
|
|||
"+++ {$new_name}\n".
|
||||
"@@ -1,{$len} +1,{$len} @@\n".
|
||||
$entire_file."\n";
|
||||
} else {
|
||||
if ($this->ignoreWhitespace) {
|
||||
|
||||
// Under "-bw", `diff` is inconsistent about emitting "\ No newline
|
||||
// at end of file". For instance, a long file with a change in the
|
||||
// middle will emit a contextless "\ No newline..." at the end if a
|
||||
// newline is removed, but not if one is added. A file with a change
|
||||
// at the end will emit the "old" "\ No newline..." block only, even
|
||||
// if the newline was not removed. Since we're ostensibly ignoring
|
||||
// whitespace changes, just drop these lines if they appear anywhere
|
||||
// in the diff.
|
||||
|
||||
$lines = explode("\n", $diff);
|
||||
foreach ($lines as $key => $line) {
|
||||
if (isset($line[0]) && $line[0] == '\\') {
|
||||
unset($lines[$key]);
|
||||
}
|
||||
}
|
||||
$diff = implode("\n", $lines);
|
||||
}
|
||||
}
|
||||
|
||||
return $diff;
|
||||
|
@ -168,4 +146,27 @@ final class PhabricatorDifferenceEngine extends Phobject {
|
|||
return head($diff->getChangesets());
|
||||
}
|
||||
|
||||
private function normalizeFile($corpus) {
|
||||
// We can freely apply any other transformations we want to here: we have
|
||||
// no constraints on what we need to preserve. If we normalize every line
|
||||
// to "cat", the diff will still work, the alignment of the "-" / "+"
|
||||
// lines will just be very hard to read.
|
||||
|
||||
// In general, we'll make the diff better if we normalize two lines that
|
||||
// humans think are the same.
|
||||
|
||||
// We'll make the diff worse if we normalize two lines that humans think
|
||||
// are different.
|
||||
|
||||
|
||||
// Strip all whitespace present anywhere in the diff, since humans never
|
||||
// consider whitespace changes to alter the line into a "different line"
|
||||
// even when they're semantic (e.g., in a string constant). This covers
|
||||
// indentation changes, trailing whitepspace, and formatting changes
|
||||
// like "+/-".
|
||||
$corpus = preg_replace('/[ \t]/', '', $corpus);
|
||||
|
||||
return $corpus;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,51 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorDiffScopeEngineTestCase
|
||||
extends PhabricatorTestCase {
|
||||
|
||||
private $engines = array();
|
||||
|
||||
public function testScopeEngine() {
|
||||
$this->assertScopeStart('zebra.c', 4, 2);
|
||||
}
|
||||
|
||||
private function assertScopeStart($file, $line, $expect) {
|
||||
$engine = $this->getScopeTestEngine($file);
|
||||
|
||||
$actual = $engine->getScopeStart($line);
|
||||
$this->assertEqual(
|
||||
$expect,
|
||||
$actual,
|
||||
pht(
|
||||
'Expect scope for line %s to start on line %s (actual: %s) in "%s".',
|
||||
$line,
|
||||
$expect,
|
||||
$actual,
|
||||
$file));
|
||||
}
|
||||
|
||||
private function getScopeTestEngine($file) {
|
||||
if (!isset($this->engines[$file])) {
|
||||
$this->engines[$file] = $this->newScopeTestEngine($file);
|
||||
}
|
||||
|
||||
return $this->engines[$file];
|
||||
}
|
||||
|
||||
private function newScopeTestEngine($file) {
|
||||
$path = dirname(__FILE__).'/data/'.$file;
|
||||
$data = Filesystem::readFile($path);
|
||||
|
||||
$lines = phutil_split_lines($data);
|
||||
$map = array();
|
||||
foreach ($lines as $key => $line) {
|
||||
$map[$key + 1] = $line;
|
||||
}
|
||||
|
||||
$engine = id(new PhabricatorDiffScopeEngine())
|
||||
->setLineTextMap($map);
|
||||
|
||||
return $engine;
|
||||
}
|
||||
|
||||
}
|
5
src/infrastructure/diff/__tests__/data/zebra.c
Normal file
5
src/infrastructure/diff/__tests__/data/zebra.c
Normal file
|
@ -0,0 +1,5 @@
|
|||
void
|
||||
ZebraTamer::TameAZebra(nsPoint where, const nsRect& zone, nsAtom* material)
|
||||
{
|
||||
zebra.tame = true;
|
||||
}
|
|
@ -18,7 +18,6 @@ final class PHUIDiffOneUpInlineCommentRowScaffold
|
|||
|
||||
$attrs = array(
|
||||
'colspan' => 3,
|
||||
'class' => 'right3',
|
||||
'id' => $inline->getScaffoldCellID(),
|
||||
);
|
||||
|
||||
|
@ -31,8 +30,8 @@ final class PHUIDiffOneUpInlineCommentRowScaffold
|
|||
}
|
||||
|
||||
$cells = array(
|
||||
phutil_tag('th', array(), $left_hidden),
|
||||
phutil_tag('th', array(), $right_hidden),
|
||||
phutil_tag('td', array('class' => 'n'), $left_hidden),
|
||||
phutil_tag('td', array('class' => 'n'), $right_hidden),
|
||||
phutil_tag('td', $attrs, $inline),
|
||||
);
|
||||
|
||||
|
|
|
@ -65,15 +65,15 @@ final class PHUIDiffTwoUpInlineCommentRowScaffold
|
|||
);
|
||||
|
||||
$right_attrs = array(
|
||||
'colspan' => 3,
|
||||
'class' => 'right3',
|
||||
'colspan' => 2,
|
||||
'id' => ($right_side ? $right_side->getScaffoldCellID() : null),
|
||||
);
|
||||
|
||||
$cells = array(
|
||||
phutil_tag('th', array(), $left_hidden),
|
||||
phutil_tag('td', array('class' => 'n'), $left_hidden),
|
||||
phutil_tag('td', $left_attrs, $left_side),
|
||||
phutil_tag('th', array(), $right_hidden),
|
||||
phutil_tag('td', array('class' => 'n'), $right_hidden),
|
||||
phutil_tag('td', array('class' => 'copy')),
|
||||
phutil_tag('td', $right_attrs, $right_side),
|
||||
);
|
||||
|
||||
|
|
|
@ -108,6 +108,10 @@ final class AphrontFormTokenizerControl extends AphrontFormControl {
|
|||
'icons' => mpull($tokens, 'getIcon', 'getKey'),
|
||||
'types' => mpull($tokens, 'getTokenType', 'getKey'),
|
||||
'colors' => mpull($tokens, 'getColor', 'getKey'),
|
||||
'availabilityColors' => mpull(
|
||||
$tokens,
|
||||
'getAvailabilityColor',
|
||||
'getKey'),
|
||||
'limit' => $this->limit,
|
||||
'username' => $username,
|
||||
'placeholder' => $placeholder,
|
||||
|
|
|
@ -4,12 +4,12 @@
|
|||
"freeze": true,
|
||||
"immed": true,
|
||||
"indent": 2,
|
||||
"latedef": true,
|
||||
"latedef": "nofunc",
|
||||
"newcap": true,
|
||||
"noarg": true,
|
||||
"quotmark": "single",
|
||||
"undef": true,
|
||||
"unused": true,
|
||||
"unused": "vars",
|
||||
|
||||
"expr": true,
|
||||
"loopfunc": true,
|
||||
|
|
|
@ -67,28 +67,6 @@
|
|||
padding: 1px 4px;
|
||||
}
|
||||
|
||||
.differential-diff td .zwsp {
|
||||
position: absolute;
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.differential-diff th {
|
||||
text-align: right;
|
||||
padding: 1px 6px 1px 0;
|
||||
vertical-align: top;
|
||||
background: {$lightbluebackground};
|
||||
color: {$bluetext};
|
||||
cursor: pointer;
|
||||
border-right: 1px solid {$thinblueborder};
|
||||
overflow: hidden;
|
||||
|
||||
-moz-user-select: -moz-none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.prose-diff {
|
||||
padding: 12px 0;
|
||||
white-space: pre-wrap;
|
||||
|
@ -109,7 +87,7 @@
|
|||
color: {$darkgreytext};
|
||||
}
|
||||
|
||||
.differential-changeset-immutable .differential-diff th {
|
||||
.differential-changeset-immutable .differential-diff td {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
|
@ -135,16 +113,38 @@
|
|||
background: {$old-bright};
|
||||
}
|
||||
|
||||
|
||||
.differential-diff td.new span.bright,
|
||||
.differential-diff td.new-full,
|
||||
.prose-diff span.new {
|
||||
background: {$new-bright};
|
||||
}
|
||||
|
||||
.differential-diff td span.depth-out,
|
||||
.differential-diff td span.depth-in {
|
||||
padding: 2px 0;
|
||||
background-size: 12px 12px;
|
||||
background-repeat: no-repeat;
|
||||
background-position: left center;
|
||||
}
|
||||
|
||||
.differential-diff td span.depth-out {
|
||||
background-image: url(/rsrc/image/chevron-out.png);
|
||||
background-color: {$old-bright};
|
||||
}
|
||||
|
||||
.differential-diff td span.depth-in {
|
||||
background-position: 1px center;
|
||||
background-image: url(/rsrc/image/chevron-in.png);
|
||||
background-color: {$new-bright};
|
||||
}
|
||||
|
||||
|
||||
.differential-diff td.copy {
|
||||
min-width: 0.5%;
|
||||
width: 0.5%;
|
||||
padding: 0;
|
||||
background: {$lightbluebackground};
|
||||
}
|
||||
|
||||
.differential-diff td.new-copy,
|
||||
|
@ -161,6 +161,36 @@
|
|||
background: #dddddd;
|
||||
}
|
||||
|
||||
.differential-diff .inline > td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
/* Specify line number behaviors after other behaviors because line numbers
|
||||
should always have a boring grey background. */
|
||||
|
||||
.differential-diff td.n {
|
||||
text-align: right;
|
||||
padding: 1px 6px 1px 0;
|
||||
vertical-align: top;
|
||||
background: {$lightbluebackground};
|
||||
color: {$bluetext};
|
||||
cursor: pointer;
|
||||
border-right: 1px solid {$thinblueborder};
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.differential-diff td + td.n {
|
||||
border-left: 1px solid {$thinblueborder};
|
||||
}
|
||||
|
||||
.differential-diff td.n::before {
|
||||
content: attr(data-n);
|
||||
}
|
||||
|
||||
.differential-diff td.show-context-line.n {
|
||||
cursor: auto;
|
||||
}
|
||||
|
||||
.differential-diff td.cov {
|
||||
padding: 0;
|
||||
}
|
||||
|
@ -201,7 +231,7 @@ td.cov-I {
|
|||
}
|
||||
|
||||
.differential-diff td.show-more,
|
||||
.differential-diff th.show-context-line,
|
||||
.differential-diff td.show-context-line,
|
||||
.differential-diff td.show-context,
|
||||
.differential-diff td.differential-shield {
|
||||
background: {$lightbluebackground};
|
||||
|
@ -211,7 +241,7 @@ td.cov-I {
|
|||
}
|
||||
|
||||
.device .differential-diff td.show-more,
|
||||
.device .differential-diff th.show-context-line,
|
||||
.device .differential-diff td.show-context-line,
|
||||
.device .differential-diff td.show-context,
|
||||
.device .differential-diff td.differential-shield {
|
||||
padding: 6px 0;
|
||||
|
@ -229,10 +259,14 @@ td.cov-I {
|
|||
color: {$bluetext};
|
||||
}
|
||||
|
||||
.differential-diff th.show-context-line {
|
||||
.differential-diff td.show-context-line {
|
||||
padding-right: 6px;
|
||||
}
|
||||
|
||||
.differential-diff td.show-context-line.left-context {
|
||||
border-right: none;
|
||||
}
|
||||
|
||||
.differential-diff td.show-context {
|
||||
padding-left: 14px;
|
||||
}
|
||||
|
@ -295,10 +329,6 @@ td.cov-I {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.differential-diff .inline > td {
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
.differential-loading {
|
||||
border-top: 1px solid {$gentle.highlight.border};
|
||||
border-bottom: 1px solid {$gentle.highlight.border};
|
||||
|
@ -407,3 +437,49 @@ tr.differential-inline-loading {
|
|||
.diff-banner-buttons {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/* In Firefox, making the table unselectable and then making cells selectable
|
||||
does not work: the cells remain unselectable. Narrowly mark the cells as
|
||||
unselectable. */
|
||||
|
||||
.differential-diff.copy-l > tbody > tr > td,
|
||||
.differential-diff.copy-r > tbody > tr > td {
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.differential-diff.copy-l > tbody > tr > td:nth-child(2) {
|
||||
-moz-user-select: auto;
|
||||
-ms-user-select: auto;
|
||||
-webkit-user-select: auto;
|
||||
user-select: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.differential-diff.copy-l > tbody > tr > td.show-more:nth-child(2) {
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
||||
.differential-diff.copy-r > tbody > tr > td:nth-child(5) {
|
||||
-moz-user-select: auto;
|
||||
-ms-user-select: auto;
|
||||
-webkit-user-select: auto;
|
||||
user-select: auto;
|
||||
opacity: 1;
|
||||
}
|
||||
|
||||
.differential-diff.copy-l > tbody > tr.inline > td,
|
||||
.differential-diff.copy-r > tbody > tr.inline > td {
|
||||
-moz-user-select: none;
|
||||
-ms-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
opacity: 0.5;
|
||||
}
|
||||
|
|
|
@ -16,14 +16,6 @@
|
|||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.differential-unselectable tr td:nth-of-type(1) {
|
||||
-moz-user-select: -moz-none;
|
||||
-khtml-user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-ms-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.differential-content-hidden {
|
||||
margin: 0 0 24px 0;
|
||||
}
|
||||
|
|
|
@ -29,3 +29,9 @@ span.crossreference-item {
|
|||
color: #222222;
|
||||
background: #dddddd;
|
||||
}
|
||||
|
||||
.suspicious-character {
|
||||
background: #ff7700;
|
||||
color: #ffffff;
|
||||
cursor: default;
|
||||
}
|
||||
|
|
|
@ -249,6 +249,16 @@ body .phui-header-shell.phui-bleed-header
|
|||
color: {$sh-indigotext};
|
||||
}
|
||||
|
||||
.policy-header-callout.policy-adjusted-locked {
|
||||
background: {$sh-pinkbackground};
|
||||
}
|
||||
|
||||
.policy-header-callout.policy-adjusted-locked .policy-link,
|
||||
.policy-header-callout.policy-adjusted-locked .phui-icon-view {
|
||||
color: {$sh-pinktext};
|
||||
}
|
||||
|
||||
|
||||
.policy-header-callout .policy-space-container {
|
||||
font-weight: bold;
|
||||
color: {$sh-redtext};
|
||||
|
|
|
@ -54,6 +54,14 @@ a.phui-tag-view:hover {
|
|||
border: 1px solid transparent;
|
||||
}
|
||||
|
||||
.tokenizer-result .phui-tag-dot {
|
||||
margin-right: 6px;
|
||||
}
|
||||
|
||||
.jx-tokenizer-token .phui-tag-dot {
|
||||
margin-left: 2px;
|
||||
}
|
||||
|
||||
.phui-tag-type-state {
|
||||
color: #ffffff;
|
||||
text-shadow: rgba(100, 100, 100, 0.40) 0px -1px 1px;
|
||||
|
|
BIN
webroot/rsrc/image/chevron-in.png
Normal file
BIN
webroot/rsrc/image/chevron-in.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
BIN
webroot/rsrc/image/chevron-out.png
Normal file
BIN
webroot/rsrc/image/chevron-out.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.4 KiB |
|
@ -22,7 +22,6 @@ JX.install('DiffChangeset', {
|
|||
|
||||
this._renderURI = data.renderURI;
|
||||
this._ref = data.ref;
|
||||
this._whitespace = data.whitespace;
|
||||
this._renderer = data.renderer;
|
||||
this._highlight = data.highlight;
|
||||
this._encoding = data.encoding;
|
||||
|
@ -46,7 +45,6 @@ JX.install('DiffChangeset', {
|
|||
|
||||
_renderURI: null,
|
||||
_ref: null,
|
||||
_whitespace: null,
|
||||
_renderer: null,
|
||||
_highlight: null,
|
||||
_encoding: null,
|
||||
|
@ -310,7 +308,6 @@ JX.install('DiffChangeset', {
|
|||
_getViewParameters: function() {
|
||||
return {
|
||||
ref: this._ref,
|
||||
whitespace: this._whitespace || '',
|
||||
renderer: this.getRenderer() || '',
|
||||
highlight: this._highlight || '',
|
||||
encoding: this._encoding || ''
|
||||
|
|
|
@ -70,13 +70,13 @@ JX.install('DiffChangesetList', {
|
|||
var onrangedown = JX.bind(this, this._ifawake, this._onrangedown);
|
||||
JX.Stratcom.listen(
|
||||
'mousedown',
|
||||
['differential-changeset', 'tag:th'],
|
||||
['differential-changeset', 'tag:td'],
|
||||
onrangedown);
|
||||
|
||||
var onrangemove = JX.bind(this, this._ifawake, this._onrangemove);
|
||||
JX.Stratcom.listen(
|
||||
['mouseover', 'mouseout'],
|
||||
['differential-changeset', 'tag:th'],
|
||||
['differential-changeset', 'tag:td'],
|
||||
onrangemove);
|
||||
|
||||
var onrangeup = JX.bind(this, this._ifawake, this._onrangeup);
|
||||
|
@ -360,7 +360,7 @@ JX.install('DiffChangesetList', {
|
|||
while (row) {
|
||||
var header = row.firstChild;
|
||||
while (header) {
|
||||
if (JX.DOM.isType(header, 'th')) {
|
||||
if (this.getLineNumberFromHeader(header)) {
|
||||
if (header.className.indexOf('old') !== -1) {
|
||||
old_list.push(header);
|
||||
} else if (header.className.indexOf('new') !== -1) {
|
||||
|
@ -1246,12 +1246,24 @@ JX.install('DiffChangesetList', {
|
|||
return changeset.getInlineForRow(inline_row);
|
||||
},
|
||||
|
||||
getLineNumberFromHeader: function(th) {
|
||||
try {
|
||||
return parseInt(th.id.match(/^C\d+[ON]L(\d+)$/)[1], 10);
|
||||
} catch (x) {
|
||||
getLineNumberFromHeader: function(node) {
|
||||
var n = parseInt(node.getAttribute('data-n'));
|
||||
|
||||
if (!n) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// If this is a line number that's part of a row showing more context,
|
||||
// we don't want to let users leave inlines here.
|
||||
|
||||
try {
|
||||
JX.DOM.findAbove(node, 'tr', 'context-target');
|
||||
return null;
|
||||
} catch (ex) {
|
||||
// Ignore.
|
||||
}
|
||||
|
||||
return n;
|
||||
},
|
||||
|
||||
getDisplaySideFromHeader: function(th) {
|
||||
|
@ -1299,7 +1311,7 @@ JX.install('DiffChangesetList', {
|
|||
},
|
||||
|
||||
_updateRange: function(target, is_out) {
|
||||
// Don't update the range if this "<th />" doesn't correspond to a line
|
||||
// Don't update the range if this target doesn't correspond to a line
|
||||
// number. For instance, this may be a dead line number, like the empty
|
||||
// line numbers on the left hand side of a newly added file.
|
||||
var number = this.getLineNumberFromHeader(target);
|
||||
|
|
|
@ -1,33 +0,0 @@
|
|||
/**
|
||||
* @provides javelin-behavior-differential-user-select
|
||||
* @requires javelin-behavior
|
||||
* javelin-dom
|
||||
* javelin-stratcom
|
||||
*/
|
||||
|
||||
JX.behavior('differential-user-select', function() {
|
||||
|
||||
var unselectable;
|
||||
|
||||
function isOnRight(node) {
|
||||
return node.previousSibling &&
|
||||
node.parentNode.firstChild != node.previousSibling;
|
||||
}
|
||||
|
||||
JX.Stratcom.listen(
|
||||
'mousedown',
|
||||
null,
|
||||
function(e) {
|
||||
var key = 'differential-unselectable';
|
||||
if (unselectable) {
|
||||
JX.DOM.alterClass(unselectable, key, false);
|
||||
}
|
||||
var diff = e.getNode('differential-diff');
|
||||
var td = e.getNode('tag:td');
|
||||
if (diff && td && isOnRight(td)) {
|
||||
unselectable = diff;
|
||||
JX.DOM.alterClass(diff, key, true);
|
||||
}
|
||||
});
|
||||
|
||||
});
|
|
@ -237,11 +237,6 @@ JX.behavior('repository-crossreference', function(config, statics) {
|
|||
}
|
||||
|
||||
var content = '' + node.textContent;
|
||||
|
||||
// Strip off any ZWS characters. These are marker characters used to
|
||||
// improve copy/paste behavior.
|
||||
content = content.replace(/\u200B/g, '');
|
||||
|
||||
char += content.length;
|
||||
}
|
||||
|
||||
|
|
|
@ -125,15 +125,18 @@ JX.install('Prefab', {
|
|||
var icon;
|
||||
var type;
|
||||
var color;
|
||||
var availability_color;
|
||||
if (result) {
|
||||
icon = result.icon;
|
||||
value = result.displayName;
|
||||
type = result.tokenType;
|
||||
color = result.color;
|
||||
availability_color = result.availabilityColor;
|
||||
} else {
|
||||
icon = (config.icons || {})[key];
|
||||
type = (config.types || {})[key];
|
||||
color = (config.colors || {})[key];
|
||||
availability_color = (config.availabilityColors || {})[key];
|
||||
}
|
||||
|
||||
if (icon) {
|
||||
|
@ -147,7 +150,16 @@ JX.install('Prefab', {
|
|||
JX.DOM.alterClass(container, color, true);
|
||||
}
|
||||
|
||||
return [icon, value];
|
||||
var dot;
|
||||
if (availability_color) {
|
||||
dot = JX.$N(
|
||||
'span',
|
||||
{
|
||||
className: 'phui-tag-dot phui-tag-color-' + availability_color
|
||||
});
|
||||
}
|
||||
|
||||
return [icon, dot, value];
|
||||
});
|
||||
|
||||
if (config.placeholder) {
|
||||
|
@ -275,10 +287,20 @@ JX.install('Prefab', {
|
|||
icon_ui = JX.Prefab._renderIcon(icon);
|
||||
}
|
||||
|
||||
var availability_ui;
|
||||
var availability_color = fields[16];
|
||||
if (availability_color) {
|
||||
availability_ui = JX.$N(
|
||||
'span',
|
||||
{
|
||||
className: 'phui-tag-dot phui-tag-color-' + availability_color
|
||||
});
|
||||
}
|
||||
|
||||
var display = JX.$N(
|
||||
'div',
|
||||
{className: 'tokenizer-result'},
|
||||
[icon_ui, fields[4] || fields[0], closed_ui]);
|
||||
[icon_ui, availability_ui, fields[4] || fields[0], closed_ui]);
|
||||
if (closed) {
|
||||
JX.DOM.alterClass(display, 'tokenizer-result-closed', true);
|
||||
}
|
||||
|
@ -300,7 +322,8 @@ JX.install('Prefab', {
|
|||
tokenType: fields[12],
|
||||
unique: fields[13] || false,
|
||||
autocomplete: fields[14],
|
||||
sort: JX.TypeaheadNormalizer.normalize(fields[0])
|
||||
sort: JX.TypeaheadNormalizer.normalize(fields[0]),
|
||||
availabilityColor: availability_color
|
||||
};
|
||||
},
|
||||
|
||||
|
|
|
@ -4,62 +4,319 @@
|
|||
* javelin-dom
|
||||
*/
|
||||
|
||||
/**
|
||||
* Tools like Paste and Differential don't normally respond to the clipboard
|
||||
* 'copy' operation well, because when a user copies text they'll get line
|
||||
* numbers and other metadata.
|
||||
*
|
||||
* To improve this behavior, applications can embed markers that delimit
|
||||
* metadata (left of the marker) from content (right of the marker). When
|
||||
* we get a copy event, we strip out all the metadata and just copy the
|
||||
* actual text.
|
||||
*/
|
||||
JX.behavior('phabricator-oncopy', function() {
|
||||
var copy_root;
|
||||
var copy_mode;
|
||||
|
||||
var zws = '\u200B'; // Unicode Zero-Width Space
|
||||
function onstartselect(e) {
|
||||
var target = e.getTarget();
|
||||
|
||||
JX.enableDispatch(document.body, 'copy');
|
||||
JX.Stratcom.listen(
|
||||
['copy'],
|
||||
null,
|
||||
function(e) {
|
||||
var container;
|
||||
try {
|
||||
// NOTE: For now, all elements with custom oncopy behavior are tables,
|
||||
// so this tag selection will hit everything we need it to.
|
||||
container = JX.DOM.findAbove(target, 'table', 'intercept-copy');
|
||||
} catch (ex) {
|
||||
container = null;
|
||||
}
|
||||
|
||||
var selection;
|
||||
var text;
|
||||
if (window.getSelection) {
|
||||
selection = window.getSelection();
|
||||
text = selection.toString();
|
||||
var old_mode = copy_mode;
|
||||
clear_selection_mode();
|
||||
|
||||
if (!container) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the potential selection is starting inside an inline comment,
|
||||
// don't do anything special.
|
||||
try {
|
||||
if (JX.DOM.findAbove(target, 'div', 'differential-inline-comment')) {
|
||||
return;
|
||||
}
|
||||
} catch (ex) {
|
||||
// Continue.
|
||||
}
|
||||
|
||||
// Find the row and cell we're copying from. If we don't find anything,
|
||||
// don't do anything special.
|
||||
var row;
|
||||
var cell;
|
||||
try {
|
||||
// The target may be the cell we're after, particularly if you click
|
||||
// in the white area to the right of the text, towards the end of a line.
|
||||
if (JX.DOM.isType(target, 'td')) {
|
||||
cell = target;
|
||||
} else {
|
||||
selection = document.selection;
|
||||
text = selection.createRange().text;
|
||||
cell = JX.DOM.findAbove(target, 'td');
|
||||
}
|
||||
row = JX.DOM.findAbove(target, 'tr');
|
||||
} catch (ex) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the row doesn't have enough nodes, bail out. Note that it's okay
|
||||
// to begin a selection in the whitespace on the opposite side of an inline
|
||||
// comment. For example, if there's an inline comment on the right side of
|
||||
// a diff, it's okay to start selecting the left side of the diff by
|
||||
// clicking the corresponding empty space on the left side.
|
||||
if (row.childNodes.length < 4) {
|
||||
return;
|
||||
}
|
||||
|
||||
// If the selection's cell is in the "old" diff or the "new" diff, we'll
|
||||
// activate an appropriate copy mode.
|
||||
var mode;
|
||||
if (cell === row.childNodes[1]) {
|
||||
mode = 'copy-l';
|
||||
} else if ((row.childNodes.length >= 4) && (cell === row.childNodes[4])) {
|
||||
mode = 'copy-r';
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
|
||||
// We found a copy mode, so set it as the current active mode.
|
||||
copy_root = container;
|
||||
copy_mode = mode;
|
||||
|
||||
// If the user makes a selection, then clicks again inside the same
|
||||
// selection, browsers retain the selection. This is because the user may
|
||||
// want to drag-and-drop the text to another window.
|
||||
|
||||
// Handle special cases when the click is inside an existing selection.
|
||||
|
||||
var ranges = get_selected_ranges();
|
||||
if (ranges.length) {
|
||||
// We'll have an existing selection if the user selects text on the right
|
||||
// side of a diff, then clicks the selection on the left side of the
|
||||
// diff, even if the second click is clicking part of the selection
|
||||
// range where the selection highlight is currently invisible because
|
||||
// of CSS rules.
|
||||
|
||||
// This behavior looks and feels glitchy: an invisible selection range
|
||||
// suddenly pops into existence and there's a bunch of flicker. If we're
|
||||
// switching selection modes, clear the old selection to avoid this:
|
||||
// assume the user is not trying to drag-and-drop text which is not
|
||||
// visually selected.
|
||||
|
||||
if (old_mode !== copy_mode) {
|
||||
window.getSelection().removeAllRanges();
|
||||
}
|
||||
|
||||
if (text.indexOf(zws) == -1) {
|
||||
// If there's no marker in the text, just let it copy normally.
|
||||
// In the more mundane case, if the user selects some text on one side
|
||||
// of a diff and then clicks that same selection in a normal way (in
|
||||
// the visible part of the highlighted text), we may either be altering
|
||||
// the selection range or may be initiating a text drag depending on how
|
||||
// long they hold the button for. Regardless of what we're doing, we're
|
||||
// still in a selection mode, so keep the visual hints active.
|
||||
|
||||
JX.DOM.alterClass(copy_root, copy_mode, true);
|
||||
}
|
||||
|
||||
// We've chosen a mode and saved it now, but we don't actually update to
|
||||
// apply any visual changes until the user actually starts making some
|
||||
// kind of selection.
|
||||
}
|
||||
|
||||
// When the selection range changes, apply CSS classes if the selection is
|
||||
// nonempty. We don't want to make visual changes to the document immediately
|
||||
// when the user press the mouse button, since we aren't yet sure that
|
||||
// they are starting a selection: instead, wait for them to actually select
|
||||
// something.
|
||||
function onchangeselect() {
|
||||
if (!copy_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ranges = get_selected_ranges();
|
||||
JX.DOM.alterClass(copy_root, copy_mode, !!ranges.length);
|
||||
}
|
||||
|
||||
// When the user releases the mouse, get rid of the selection mode if we
|
||||
// don't have a selection.
|
||||
function onendselect(e) {
|
||||
if (!copy_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ranges = get_selected_ranges();
|
||||
if (!ranges.length) {
|
||||
clear_selection_mode();
|
||||
}
|
||||
}
|
||||
|
||||
function get_selected_ranges() {
|
||||
var ranges = [];
|
||||
|
||||
if (!window.getSelection) {
|
||||
return ranges;
|
||||
}
|
||||
|
||||
var selection = window.getSelection();
|
||||
for (var ii = 0; ii < selection.rangeCount; ii++) {
|
||||
var range = selection.getRangeAt(ii);
|
||||
if (range.collapsed) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ranges.push(range);
|
||||
}
|
||||
|
||||
return ranges;
|
||||
}
|
||||
|
||||
function clear_selection_mode() {
|
||||
if (!copy_root) {
|
||||
return;
|
||||
}
|
||||
|
||||
JX.DOM.alterClass(copy_root, copy_mode, false);
|
||||
copy_root = null;
|
||||
copy_mode = null;
|
||||
}
|
||||
|
||||
function oncopy(e) {
|
||||
// If we aren't in a special copy mode, just fall back to default
|
||||
// behavior.
|
||||
if (!copy_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
var ranges = get_selected_ranges();
|
||||
if (!ranges.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var text = [];
|
||||
for (var ii = 0; ii < ranges.length; ii++) {
|
||||
var range = ranges[ii];
|
||||
|
||||
var fragment = range.cloneContents();
|
||||
if (!fragment.childNodes.length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// In Chrome and Firefox, because we've already applied "user-select"
|
||||
// CSS to everything we don't intend to copy, the text in the selection
|
||||
// range is correct, and the range will include only the correct text
|
||||
// nodes.
|
||||
|
||||
// However, in Safari, "user-select" does not apply to clipboard
|
||||
// operations, so we get everything in the document between the beginning
|
||||
// and end of the selection, even if it isn't visibly selected.
|
||||
|
||||
// Even in Chrome and Firefox, we can get partial empty nodes: for
|
||||
// example, where a "<tr>" is selectable but no content in the node is
|
||||
// selectable. (We have to leave the "<tr>" itself selectable because
|
||||
// of how Firefox applies "user-select" rules.)
|
||||
|
||||
// The nodes we get here can also start and end more or less anywhere.
|
||||
|
||||
// One saving grace is that we use "content: attr(data-n);" to render
|
||||
// the line numbers and no browsers copy this content, so we don't have
|
||||
// to worry about figuring out when text is line numbers.
|
||||
|
||||
for (var jj = 0; jj < fragment.childNodes.length; jj++) {
|
||||
var node = fragment.childNodes[jj];
|
||||
text.push(extract_text(node));
|
||||
}
|
||||
}
|
||||
|
||||
text = flatten_list(text);
|
||||
text = text.join('');
|
||||
|
||||
var rawEvent = e.getRawEvent();
|
||||
var data;
|
||||
if ('clipboardData' in rawEvent) {
|
||||
data = rawEvent.clipboardData;
|
||||
} else {
|
||||
data = window.clipboardData;
|
||||
}
|
||||
data.setData('Text', text);
|
||||
|
||||
e.prevent();
|
||||
}
|
||||
|
||||
function extract_text(node) {
|
||||
var ii;
|
||||
var text = [];
|
||||
|
||||
if (JX.DOM.isType(node, 'tr')) {
|
||||
// This is an inline comment row, so we never want to copy any
|
||||
// content inside of it.
|
||||
if (JX.Stratcom.hasSigil(node, 'inline-row')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// This is a "Show More Context" row, so we never want to copy any
|
||||
// of the content inside.
|
||||
if (JX.Stratcom.hasSigil(node, 'context-target')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Assume anything else is a source code row. Keep only "<td>" cells
|
||||
// with the correct mode.
|
||||
for (ii = 0; ii < node.childNodes.length; ii++) {
|
||||
text.push(extract_text(node.childNodes[ii]));
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
if (JX.DOM.isType(node, 'td')) {
|
||||
var node_mode = node.getAttribute('data-copy-mode');
|
||||
if (node_mode !== copy_mode) {
|
||||
return;
|
||||
}
|
||||
|
||||
var result = [];
|
||||
// Otherwise, fall through and extract this node's text normally.
|
||||
}
|
||||
|
||||
// Strip everything before the marker (and the marker itself) out of the
|
||||
// text. If a line doesn't have the marker, throw it away (the assumption
|
||||
// is that it's a line number or part of some other meta-text).
|
||||
var lines = text.split('\n');
|
||||
var pos;
|
||||
for (var ii = 0; ii < lines.length; ii++) {
|
||||
pos = lines[ii].indexOf(zws);
|
||||
if (pos == -1 && ii !== 0) {
|
||||
continue;
|
||||
}
|
||||
result.push(lines[ii].substring(pos + 1));
|
||||
if (node.getAttribute) {
|
||||
var copy_text = node.getAttribute('data-copy-text');
|
||||
if (copy_text) {
|
||||
return copy_text;
|
||||
}
|
||||
result = result.join('\n');
|
||||
}
|
||||
|
||||
var rawEvent = e.getRawEvent();
|
||||
var clipboardData = 'clipboardData' in rawEvent ?
|
||||
rawEvent.clipboardData :
|
||||
window.clipboardData;
|
||||
clipboardData.setData('Text', result);
|
||||
e.prevent();
|
||||
});
|
||||
if (!node.childNodes || !node.childNodes.length) {
|
||||
return node.textContent;
|
||||
}
|
||||
|
||||
for (ii = 0; ii < node.childNodes.length; ii++) {
|
||||
var child = node.childNodes[ii];
|
||||
text.push(extract_text(child));
|
||||
}
|
||||
|
||||
return text;
|
||||
}
|
||||
|
||||
function flatten_list(list) {
|
||||
var stack = [list];
|
||||
var result = [];
|
||||
while (stack.length) {
|
||||
var next = stack.pop();
|
||||
if (JX.isArray(next)) {
|
||||
for (var ii = 0; ii < next.length; ii++) {
|
||||
stack.push(next[ii]);
|
||||
}
|
||||
} else if (next === null) {
|
||||
continue;
|
||||
} else if (next === undefined) {
|
||||
continue;
|
||||
} else {
|
||||
result.push(next);
|
||||
}
|
||||
}
|
||||
|
||||
return result.reverse();
|
||||
}
|
||||
|
||||
JX.enableDispatch(document.body, 'copy');
|
||||
JX.enableDispatch(window, 'selectionchange');
|
||||
|
||||
JX.Stratcom.listen('mousedown', null, onstartselect);
|
||||
JX.Stratcom.listen('selectionchange', null, onchangeselect);
|
||||
JX.Stratcom.listen('mouseup', null, onendselect);
|
||||
|
||||
JX.Stratcom.listen('copy', null, oncopy);
|
||||
});
|
||||
|
|
|
@ -185,7 +185,16 @@ JX.install('PHUIXAutocomplete', {
|
|||
.getNode();
|
||||
}
|
||||
|
||||
var display = JX.$N('span', {}, [icon, map.displayName]);
|
||||
var dot;
|
||||
if (map.availabilityColor) {
|
||||
dot = JX.$N(
|
||||
'span',
|
||||
{
|
||||
className: 'phui-tag-dot phui-tag-color-' + map.availabilityColor
|
||||
});
|
||||
}
|
||||
|
||||
var display = JX.$N('span', {}, [icon, dot, map.displayName]);
|
||||
JX.DOM.alterClass(display, 'tokenizer-result-closed', !!map.closed);
|
||||
|
||||
map.display = display;
|
||||
|
|
Loading…
Reference in a new issue