Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F2892584
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Award Token
Flag For Later
Advanced/Developer...
View Handle
View Hovercard
Size
209 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/resources/celerity/map.php b/resources/celerity/map.php
index 70f48e222e..8969ef414c 100644
--- a/resources/celerity/map.php
+++ b/resources/celerity/map.php
@@ -1,2425 +1,2425 @@
<?php
/**
* This file is automatically generated. Use 'bin/celerity map' to rebuild it.
*
* @generated
*/
return array(
'names' => array(
'conpherence.pkg.css' => '3c8a0668',
'conpherence.pkg.js' => '020aebcf',
'core.pkg.css' => 'dd5f04a3',
'core.pkg.js' => '544bc792',
'differential.pkg.css' => 'cb99cd21',
- 'differential.pkg.js' => '6c315e3f',
+ 'differential.pkg.js' => '54613dd5',
'diffusion.pkg.css' => '42c75c37',
'diffusion.pkg.js' => 'a98c0bf7',
'maniphest.pkg.css' => '35995d6d',
'maniphest.pkg.js' => 'c9308721',
'rsrc/audio/basic/alert.mp3' => '17889334',
'rsrc/audio/basic/bing.mp3' => 'a817a0c3',
'rsrc/audio/basic/pock.mp3' => '0fa843d0',
'rsrc/audio/basic/tap.mp3' => '02d16994',
'rsrc/audio/basic/ting.mp3' => 'a6b6540e',
'rsrc/css/aphront/aphront-bars.css' => '4a327b4a',
'rsrc/css/aphront/dark-console.css' => '7f06cda2',
'rsrc/css/aphront/dialog-view.css' => '874f5c06',
'rsrc/css/aphront/list-filter-view.css' => 'feb64255',
'rsrc/css/aphront/multi-column.css' => 'fbc00ba3',
'rsrc/css/aphront/notification.css' => '30240bd2',
'rsrc/css/aphront/panel-view.css' => '46923d46',
'rsrc/css/aphront/phabricator-nav-view.css' => '423f92cc',
'rsrc/css/aphront/table-view.css' => '0bb61df1',
'rsrc/css/aphront/tokenizer.css' => '34e2a838',
'rsrc/css/aphront/tooltip.css' => 'e3f2412f',
'rsrc/css/aphront/typeahead-browse.css' => 'b7ed02d2',
'rsrc/css/aphront/typeahead.css' => '8779483d',
'rsrc/css/application/almanac/almanac.css' => '2e050f4f',
'rsrc/css/application/auth/auth.css' => 'c2f23d74',
'rsrc/css/application/base/main-menu-view.css' => 'bcec20f0',
'rsrc/css/application/base/notification-menu.css' => '4df1ee30',
'rsrc/css/application/base/phui-theme.css' => '35883b37',
'rsrc/css/application/base/standard-page-view.css' => 'a374f94c',
'rsrc/css/application/chatlog/chatlog.css' => 'abdc76ee',
'rsrc/css/application/conduit/conduit-api.css' => 'ce2cfc41',
'rsrc/css/application/config/config-options.css' => '16c920ae',
'rsrc/css/application/config/config-template.css' => '20babf50',
'rsrc/css/application/config/setup-issue.css' => '5eed85b2',
'rsrc/css/application/config/unhandled-exception.css' => '9ecfc00d',
'rsrc/css/application/conpherence/color.css' => 'b17746b0',
'rsrc/css/application/conpherence/durable-column.css' => '2d57072b',
'rsrc/css/application/conpherence/header-pane.css' => 'c9a3db8e',
'rsrc/css/application/conpherence/menu.css' => '67f4680d',
'rsrc/css/application/conpherence/message-pane.css' => 'd244db1e',
'rsrc/css/application/conpherence/notification.css' => '6a3d4e58',
'rsrc/css/application/conpherence/participant-pane.css' => '69e0058a',
'rsrc/css/application/conpherence/transaction.css' => '3a3f5e7e',
'rsrc/css/application/contentsource/content-source-view.css' => 'cdf0d579',
'rsrc/css/application/countdown/timer.css' => 'bff8012f',
'rsrc/css/application/daemon/bulk-job.css' => '73af99f5',
'rsrc/css/application/dashboard/dashboard.css' => '5a205b9d',
- 'rsrc/css/application/diff/diff-tree-view.css' => '26fb4a0d',
+ 'rsrc/css/application/diff/diff-tree-view.css' => 'e2d3e222',
'rsrc/css/application/diff/inline-comment-summary.css' => '81eb368d',
'rsrc/css/application/differential/add-comment.css' => '7e5900d9',
'rsrc/css/application/differential/changeset-view.css' => '489b6995',
'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',
'rsrc/css/application/differential/revision-list.css' => '93d2df7d',
'rsrc/css/application/differential/table-of-contents.css' => '0e3364c7',
'rsrc/css/application/diffusion/diffusion-icons.css' => '23b31a1b',
'rsrc/css/application/diffusion/diffusion-readme.css' => 'b68a76e4',
'rsrc/css/application/diffusion/diffusion-repository.css' => 'b89e8c6c',
'rsrc/css/application/diffusion/diffusion.css' => 'b54c77b0',
'rsrc/css/application/feed/feed.css' => 'd8b6e3f8',
'rsrc/css/application/files/global-drag-and-drop.css' => '1d2713a4',
'rsrc/css/application/flag/flag.css' => '2b77be8d',
'rsrc/css/application/harbormaster/harbormaster.css' => '8dfe16b2',
'rsrc/css/application/herald/herald-test.css' => 'e004176f',
'rsrc/css/application/herald/herald.css' => '648d39e2',
'rsrc/css/application/maniphest/report.css' => '3d53188b',
'rsrc/css/application/maniphest/task-edit.css' => '272daa84',
'rsrc/css/application/maniphest/task-summary.css' => '61d1667e',
'rsrc/css/application/objectselector/object-selector.css' => 'ee77366f',
'rsrc/css/application/owners/owners-path-editor.css' => 'fa7c13ef',
'rsrc/css/application/paste/paste.css' => 'b37bcd38',
'rsrc/css/application/people/people-picture-menu-item.css' => 'fe8e07cf',
'rsrc/css/application/people/people-profile.css' => '2ea2daa1',
'rsrc/css/application/phame/phame.css' => 'bb442327',
'rsrc/css/application/pholio/pholio-edit.css' => '4df55b3b',
'rsrc/css/application/pholio/pholio-inline-comments.css' => '722b48c2',
'rsrc/css/application/pholio/pholio.css' => '88ef5ef1',
'rsrc/css/application/phortune/phortune-credit-card-form.css' => '3b9868a8',
'rsrc/css/application/phortune/phortune-invoice.css' => '4436b241',
'rsrc/css/application/phortune/phortune.css' => '508a1a5e',
'rsrc/css/application/phrequent/phrequent.css' => 'bd79cc67',
'rsrc/css/application/phriction/phriction-document-css.css' => '03380da0',
'rsrc/css/application/policy/policy-edit.css' => '8794e2ed',
'rsrc/css/application/policy/policy-transaction-detail.css' => 'c02b8384',
'rsrc/css/application/policy/policy.css' => 'ceb56a08',
'rsrc/css/application/ponder/ponder-view.css' => '05a09d0a',
'rsrc/css/application/project/project-card-view.css' => '4e7371cd',
'rsrc/css/application/project/project-triggers.css' => 'cd9c8bb9',
'rsrc/css/application/project/project-view.css' => '567858b3',
'rsrc/css/application/releeph/releeph-core.css' => 'f81ff2db',
'rsrc/css/application/releeph/releeph-preview-branch.css' => '22db5c07',
'rsrc/css/application/releeph/releeph-request-differential-create-dialog.css' => '0ac1ea31',
'rsrc/css/application/releeph/releeph-request-typeahead.css' => 'bce37359',
'rsrc/css/application/search/application-search-view.css' => '0f7c06d8',
'rsrc/css/application/search/search-results.css' => '9ea70ace',
'rsrc/css/application/slowvote/slowvote.css' => '1694baed',
'rsrc/css/application/tokens/tokens.css' => 'ce5a50bd',
'rsrc/css/application/uiexample/example.css' => 'b4795059',
'rsrc/css/core/core.css' => '1b29ed61',
'rsrc/css/core/remarkup.css' => 'c286eaef',
'rsrc/css/core/syntax.css' => '220b85f9',
'rsrc/css/core/z-index.css' => '612e9522',
'rsrc/css/diviner/diviner-shared.css' => '4bd263b0',
'rsrc/css/font/font-awesome.css' => '3883938a',
'rsrc/css/font/font-lato.css' => '23631304',
'rsrc/css/font/phui-font-icon-base.css' => '303c9b87',
'rsrc/css/layout/phabricator-source-code-view.css' => '03d7ac28',
'rsrc/css/phui/button/phui-button-bar.css' => 'a4aa75c4',
'rsrc/css/phui/button/phui-button-simple.css' => '1ff278aa',
'rsrc/css/phui/button/phui-button.css' => 'ea704902',
'rsrc/css/phui/calendar/phui-calendar-day.css' => '9597d706',
'rsrc/css/phui/calendar/phui-calendar-list.css' => 'ccd7e4e2',
'rsrc/css/phui/calendar/phui-calendar-month.css' => 'cb758c42',
'rsrc/css/phui/calendar/phui-calendar.css' => 'f11073aa',
'rsrc/css/phui/object-item/phui-oi-big-ui.css' => 'fa74cc35',
'rsrc/css/phui/object-item/phui-oi-color.css' => 'b517bfa0',
'rsrc/css/phui/object-item/phui-oi-drag-ui.css' => 'da15d3dc',
'rsrc/css/phui/object-item/phui-oi-flush-ui.css' => '490e2e2e',
'rsrc/css/phui/object-item/phui-oi-list-view.css' => 'd7723ecc',
'rsrc/css/phui/object-item/phui-oi-simple-ui.css' => '6a30fa46',
'rsrc/css/phui/phui-action-list.css' => 'e820263c',
'rsrc/css/phui/phui-action-panel.css' => '6c386cbf',
'rsrc/css/phui/phui-badge.css' => '666e25ad',
'rsrc/css/phui/phui-basic-nav-view.css' => '56ebd66d',
'rsrc/css/phui/phui-big-info-view.css' => '362ad37b',
'rsrc/css/phui/phui-box.css' => '5ed3b8cb',
'rsrc/css/phui/phui-bulk-editor.css' => '374d5e30',
'rsrc/css/phui/phui-chart.css' => '14df9ae3',
'rsrc/css/phui/phui-cms.css' => '8c05c41e',
'rsrc/css/phui/phui-comment-form.css' => '68a2d99a',
'rsrc/css/phui/phui-comment-panel.css' => 'ec4e31c0',
'rsrc/css/phui/phui-crumbs-view.css' => '614f43cf',
'rsrc/css/phui/phui-curtain-object-ref-view.css' => '12404744',
'rsrc/css/phui/phui-curtain-view.css' => '68c5efb6',
'rsrc/css/phui/phui-document-pro.css' => 'b9613a10',
'rsrc/css/phui/phui-document-summary.css' => 'b068eed1',
'rsrc/css/phui/phui-document.css' => '52b748a5',
'rsrc/css/phui/phui-feed-story.css' => 'a0c05029',
'rsrc/css/phui/phui-fontkit.css' => '1ec937e5',
'rsrc/css/phui/phui-form-view.css' => '01b796c0',
'rsrc/css/phui/phui-form.css' => '1f177cb7',
'rsrc/css/phui/phui-formation-view.css' => '82a3b73e',
'rsrc/css/phui/phui-head-thing.css' => 'd7f293df',
'rsrc/css/phui/phui-header-view.css' => '36c86a58',
'rsrc/css/phui/phui-hovercard.css' => '6ca90fa0',
'rsrc/css/phui/phui-icon-set-selector.css' => '7aa5f3ec',
'rsrc/css/phui/phui-icon.css' => '4cbc684a',
'rsrc/css/phui/phui-image-mask.css' => '62c7f4d2',
'rsrc/css/phui/phui-info-view.css' => 'a10a909b',
'rsrc/css/phui/phui-invisible-character-view.css' => 'c694c4a4',
'rsrc/css/phui/phui-left-right.css' => '68513c34',
'rsrc/css/phui/phui-lightbox.css' => '4ebf22da',
'rsrc/css/phui/phui-list.css' => '2f253c22',
'rsrc/css/phui/phui-object-box.css' => 'b8d7eea0',
'rsrc/css/phui/phui-pager.css' => 'd022c7ad',
'rsrc/css/phui/phui-pinboard-view.css' => '1f08f5d8',
'rsrc/css/phui/phui-policy-section-view.css' => '139fdc64',
'rsrc/css/phui/phui-property-list-view.css' => '9c477af1',
'rsrc/css/phui/phui-remarkup-preview.css' => '91767007',
'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' => '8519160a',
'rsrc/css/phui/phui-timeline-view.css' => '1e348e4b',
'rsrc/css/phui/phui-two-column-view.css' => 'f96d319f',
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'e86de308',
'rsrc/css/phui/workboards/phui-workboard.css' => '74fc9d98',
'rsrc/css/phui/workboards/phui-workcard.css' => '913441b6',
'rsrc/css/phui/workboards/phui-workpanel.css' => '3ae89b20',
'rsrc/css/sprite-login.css' => '18b368a6',
'rsrc/css/sprite-tokens.css' => 'f1896dc5',
'rsrc/css/syntax/syntax-default.css' => '055fc231',
'rsrc/externals/d3/d3.min.js' => '9d068042',
'rsrc/externals/font/fontawesome/fontawesome-webfont.eot' => '23f8c698',
'rsrc/externals/font/fontawesome/fontawesome-webfont.ttf' => '70983df0',
'rsrc/externals/font/fontawesome/fontawesome-webfont.woff' => 'cd02f93b',
'rsrc/externals/font/fontawesome/fontawesome-webfont.woff2' => '351fd46a',
'rsrc/externals/font/lato/lato-bold.eot' => '7367aa5e',
'rsrc/externals/font/lato/lato-bold.svg' => '681aa4f5',
'rsrc/externals/font/lato/lato-bold.ttf' => '66d3c296',
'rsrc/externals/font/lato/lato-bold.woff' => '89d9fba7',
'rsrc/externals/font/lato/lato-bold.woff2' => '389fcdb1',
'rsrc/externals/font/lato/lato-bolditalic.eot' => '03eeb4da',
'rsrc/externals/font/lato/lato-bolditalic.svg' => 'f56fa11c',
'rsrc/externals/font/lato/lato-bolditalic.ttf' => '9c3aec21',
'rsrc/externals/font/lato/lato-bolditalic.woff' => 'bfbd0616',
'rsrc/externals/font/lato/lato-bolditalic.woff2' => 'bc7d1274',
'rsrc/externals/font/lato/lato-italic.eot' => '7db5b247',
'rsrc/externals/font/lato/lato-italic.svg' => 'b1ae496f',
'rsrc/externals/font/lato/lato-italic.ttf' => '43eed813',
'rsrc/externals/font/lato/lato-italic.woff' => 'c28975e1',
'rsrc/externals/font/lato/lato-italic.woff2' => 'fffc0d8c',
'rsrc/externals/font/lato/lato-regular.eot' => '06e0c291',
'rsrc/externals/font/lato/lato-regular.svg' => '3ad95f53',
'rsrc/externals/font/lato/lato-regular.ttf' => 'e2e9c398',
'rsrc/externals/font/lato/lato-regular.woff' => '0b13d332',
'rsrc/externals/font/lato/lato-regular.woff2' => '8f846797',
'rsrc/externals/javelin/core/Event.js' => 'c03f2fb4',
'rsrc/externals/javelin/core/Stratcom.js' => '0889b835',
'rsrc/externals/javelin/core/__tests__/event-stop-and-kill.js' => '048472d2',
'rsrc/externals/javelin/core/__tests__/install.js' => '14a7e671',
'rsrc/externals/javelin/core/__tests__/stratcom.js' => 'a28464bb',
'rsrc/externals/javelin/core/__tests__/util.js' => 'e29a4354',
'rsrc/externals/javelin/core/init.js' => '98e6504a',
'rsrc/externals/javelin/core/init_node.js' => '16961339',
'rsrc/externals/javelin/core/install.js' => '5902260c',
'rsrc/externals/javelin/core/util.js' => 'edb4d8c9',
'rsrc/externals/javelin/docs/Base.js' => '5a401d7d',
'rsrc/externals/javelin/docs/onload.js' => 'ee58fb62',
'rsrc/externals/javelin/ext/fx/Color.js' => '78f811c9',
'rsrc/externals/javelin/ext/fx/FX.js' => '34450586',
'rsrc/externals/javelin/ext/reactor/core/DynVal.js' => '202a2e85',
'rsrc/externals/javelin/ext/reactor/core/Reactor.js' => '1c850a26',
'rsrc/externals/javelin/ext/reactor/core/ReactorNode.js' => '72960bc1',
'rsrc/externals/javelin/ext/reactor/core/ReactorNodeCalmer.js' => '225bbb98',
'rsrc/externals/javelin/ext/reactor/dom/RDOM.js' => '6cfa0008',
'rsrc/externals/javelin/ext/view/HTMLView.js' => 'f8c4e135',
'rsrc/externals/javelin/ext/view/View.js' => '289bf236',
'rsrc/externals/javelin/ext/view/ViewInterpreter.js' => '876506b6',
'rsrc/externals/javelin/ext/view/ViewPlaceholder.js' => 'a9942052',
'rsrc/externals/javelin/ext/view/ViewRenderer.js' => '9aae2b66',
'rsrc/externals/javelin/ext/view/ViewVisitor.js' => '308f9fe4',
'rsrc/externals/javelin/ext/view/__tests__/HTMLView.js' => '6e50a13f',
'rsrc/externals/javelin/ext/view/__tests__/View.js' => 'd284be5d',
'rsrc/externals/javelin/ext/view/__tests__/ViewInterpreter.js' => 'a9f35511',
'rsrc/externals/javelin/ext/view/__tests__/ViewRenderer.js' => '3a1b81f6',
'rsrc/externals/javelin/lib/Cookie.js' => '05d290ef',
'rsrc/externals/javelin/lib/DOM.js' => '94681e22',
'rsrc/externals/javelin/lib/History.js' => '030b4f7a',
'rsrc/externals/javelin/lib/JSON.js' => '541f81c3',
'rsrc/externals/javelin/lib/Leader.js' => '0d2490ce',
'rsrc/externals/javelin/lib/Mask.js' => '7c4d8998',
'rsrc/externals/javelin/lib/Quicksand.js' => 'd3799cb4',
'rsrc/externals/javelin/lib/Request.js' => '84e6891f',
'rsrc/externals/javelin/lib/Resource.js' => '740956e1',
'rsrc/externals/javelin/lib/Routable.js' => '6a18c42e',
'rsrc/externals/javelin/lib/Router.js' => '32755edb',
'rsrc/externals/javelin/lib/Scrollbar.js' => 'a43ae2ae',
'rsrc/externals/javelin/lib/Sound.js' => 'd4cc2d2a',
'rsrc/externals/javelin/lib/URI.js' => '2e255291',
'rsrc/externals/javelin/lib/Vector.js' => 'e9c80beb',
'rsrc/externals/javelin/lib/WebSocket.js' => 'fdc13e4e',
'rsrc/externals/javelin/lib/Workflow.js' => '945ff654',
'rsrc/externals/javelin/lib/__tests__/Cookie.js' => 'ca686f71',
'rsrc/externals/javelin/lib/__tests__/DOM.js' => '4566e249',
'rsrc/externals/javelin/lib/__tests__/JSON.js' => '710377ae',
'rsrc/externals/javelin/lib/__tests__/URI.js' => '6fff0c2b',
'rsrc/externals/javelin/lib/__tests__/behavior.js' => '8426ebeb',
'rsrc/externals/javelin/lib/behavior.js' => '1b6acc2a',
'rsrc/externals/javelin/lib/control/tokenizer/Tokenizer.js' => '89a1ae3a',
'rsrc/externals/javelin/lib/control/typeahead/Typeahead.js' => 'a4356cde',
'rsrc/externals/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js' => 'a241536a',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js' => '22ee68a5',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js' => '23387297',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js' => '5a79f6c3',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadSource.js' => '8badee71',
'rsrc/externals/javelin/lib/control/typeahead/source/TypeaheadStaticSource.js' => '80bff3af',
'rsrc/favicons/favicon-16x16.png' => '4c51a03a',
'rsrc/favicons/mask-icon.svg' => 'db699fe1',
'rsrc/image/BFCFDA.png' => '74b5c88b',
'rsrc/image/actions/edit.png' => 'fd987dff',
'rsrc/image/avatar.png' => '0d17c6c4',
'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',
'rsrc/image/darkload.gif' => '5bd41a89',
'rsrc/image/divot.png' => '0fbe2453',
'rsrc/image/examples/hero.png' => '5d8c4b21',
'rsrc/image/grippy_texture.png' => 'a7d222b5',
'rsrc/image/icon/fatcow/arrow_branch.png' => '98149d9f',
'rsrc/image/icon/fatcow/arrow_merge.png' => 'e142f4f8',
'rsrc/image/icon/fatcow/calendar_edit.png' => '5ff44a08',
'rsrc/image/icon/fatcow/document_black.png' => 'd3515fa5',
'rsrc/image/icon/fatcow/flag_blue.png' => '54db2e5c',
'rsrc/image/icon/fatcow/flag_finish.png' => '2953a51b',
'rsrc/image/icon/fatcow/flag_ghost.png' => '7d9ada92',
'rsrc/image/icon/fatcow/flag_green.png' => '010f7161',
'rsrc/image/icon/fatcow/flag_orange.png' => '6c384ca5',
'rsrc/image/icon/fatcow/flag_pink.png' => '11ac6b12',
'rsrc/image/icon/fatcow/flag_purple.png' => 'c4f423a4',
'rsrc/image/icon/fatcow/flag_red.png' => '9e6d8817',
'rsrc/image/icon/fatcow/flag_yellow.png' => '906733f4',
'rsrc/image/icon/fatcow/key_question.png' => 'c10c26db',
'rsrc/image/icon/fatcow/link.png' => '8edbf327',
'rsrc/image/icon/fatcow/page_white_edit.png' => '17ef5625',
'rsrc/image/icon/fatcow/page_white_put.png' => '82430c91',
'rsrc/image/icon/fatcow/source/conduit.png' => '5b55130c',
'rsrc/image/icon/fatcow/source/email.png' => '8a32b77f',
'rsrc/image/icon/fatcow/source/fax.png' => '8bc2a49b',
'rsrc/image/icon/fatcow/source/mobile.png' => '0a918412',
'rsrc/image/icon/fatcow/source/tablet.png' => 'fc50b050',
'rsrc/image/icon/fatcow/source/web.png' => '70433af3',
'rsrc/image/icon/subscribe.png' => '07ef454e',
'rsrc/image/icon/tango/attachment.png' => 'bac9032d',
'rsrc/image/icon/tango/edit.png' => 'e6296206',
'rsrc/image/icon/tango/go-down.png' => '0b903712',
'rsrc/image/icon/tango/log.png' => '86b6a6f4',
'rsrc/image/icon/tango/upload.png' => '3fe6b92d',
'rsrc/image/icon/unsubscribe.png' => 'db04378a',
'rsrc/image/lightblue-header.png' => 'e6d483c6',
'rsrc/image/logo/light-eye.png' => '72337472',
'rsrc/image/main_texture.png' => '894d03c4',
'rsrc/image/menu_texture.png' => '896c9ade',
'rsrc/image/people/harding.png' => '95b2db63',
'rsrc/image/people/jefferson.png' => 'e883a3a2',
'rsrc/image/people/lincoln.png' => 'be2c07c5',
'rsrc/image/people/mckinley.png' => '6af510a0',
'rsrc/image/people/taft.png' => 'b15ab07e',
'rsrc/image/people/user0.png' => '4bc64b40',
'rsrc/image/people/user1.png' => '8063f445',
'rsrc/image/people/user2.png' => 'd28246c0',
'rsrc/image/people/user3.png' => 'fb1ac12d',
'rsrc/image/people/user4.png' => 'fe4fac8f',
'rsrc/image/people/user5.png' => '3d07065c',
'rsrc/image/people/user6.png' => 'e4bd47c8',
'rsrc/image/people/user7.png' => '71d8fe8b',
'rsrc/image/people/user8.png' => '85f86bf7',
'rsrc/image/people/user9.png' => '523db8aa',
'rsrc/image/people/washington.png' => '86159e68',
'rsrc/image/phrequent_active.png' => 'de66dc50',
'rsrc/image/phrequent_inactive.png' => '79c61baf',
'rsrc/image/resize.png' => '9cc83373',
'rsrc/image/sprite-login-X2.png' => '604545f6',
'rsrc/image/sprite-login.png' => '7a001a9a',
'rsrc/image/sprite-tokens-X2.png' => '21621dd9',
'rsrc/image/sprite-tokens.png' => 'bede2580',
'rsrc/image/texture/card-gradient.png' => 'e6892cb4',
'rsrc/image/texture/dark-menu-hover.png' => '390a4fa1',
'rsrc/image/texture/dark-menu.png' => '542f699c',
'rsrc/image/texture/grip.png' => 'bc80753a',
'rsrc/image/texture/panel-header-gradient.png' => '65004dbf',
'rsrc/image/texture/phlnx-bg.png' => '6c9cd31d',
'rsrc/image/texture/pholio-background.gif' => '84910bfc',
'rsrc/image/texture/table_header.png' => '7652d1ad',
'rsrc/image/texture/table_header_hover.png' => '12ea5236',
'rsrc/image/texture/table_header_tall.png' => '5cc420c4',
'rsrc/js/application/aphlict/Aphlict.js' => '022516b4',
'rsrc/js/application/aphlict/behavior-aphlict-dropdown.js' => 'e9a2940f',
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => '4e61fa88',
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => 'c3703a16',
'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => '070679fe',
'rsrc/js/application/calendar/behavior-day-view.js' => '727a5a61',
'rsrc/js/application/calendar/behavior-event-all-day.js' => '0b1bc990',
'rsrc/js/application/calendar/behavior-month-view.js' => '158c64e0',
'rsrc/js/application/config/behavior-reorder-fields.js' => '2539f834',
'rsrc/js/application/conpherence/ConpherenceThreadManager.js' => 'aec8e38c',
'rsrc/js/application/conpherence/behavior-conpherence-search.js' => '91befbcc',
'rsrc/js/application/conpherence/behavior-durable-column.js' => 'fa6f30b2',
'rsrc/js/application/conpherence/behavior-menu.js' => '8c2ed2bf',
'rsrc/js/application/conpherence/behavior-participant-pane.js' => '43ba89a2',
'rsrc/js/application/conpherence/behavior-pontificate.js' => '4ae58b5a',
'rsrc/js/application/conpherence/behavior-quicksand-blacklist.js' => '5a6f6a06',
'rsrc/js/application/conpherence/behavior-toggle-widget.js' => '8f959ad0',
'rsrc/js/application/countdown/timer.js' => '6a162524',
'rsrc/js/application/daemon/behavior-bulk-job-reload.js' => '3829a3cf',
'rsrc/js/application/dashboard/behavior-dashboard-async-panel.js' => '9c01e364',
'rsrc/js/application/dashboard/behavior-dashboard-move-panels.js' => 'a2ab19be',
'rsrc/js/application/dashboard/behavior-dashboard-query-panel-select.js' => '1e413dc9',
'rsrc/js/application/dashboard/behavior-dashboard-tab-panel.js' => '0116d3e8',
- 'rsrc/js/application/diff/DiffChangeset.js' => 'a1a5dc46',
+ 'rsrc/js/application/diff/DiffChangeset.js' => 'dd1a6f34',
'rsrc/js/application/diff/DiffChangesetList.js' => '57035863',
'rsrc/js/application/diff/DiffInline.js' => '16e97ebc',
- 'rsrc/js/application/diff/DiffPathView.js' => 'ceb66010',
+ 'rsrc/js/application/diff/DiffPathView.js' => '8207abf9',
'rsrc/js/application/diff/DiffTreeView.js' => '5d83623b',
'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' => 'b86ef6c2',
'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',
'rsrc/js/application/diffusion/behavior-commit-graph.js' => 'ef836bf2',
'rsrc/js/application/diffusion/behavior-locate-file.js' => '87428eb2',
'rsrc/js/application/diffusion/behavior-pull-lastmodified.js' => 'c715c123',
'rsrc/js/application/doorkeeper/behavior-doorkeeper-tag.js' => '6a85bc5a',
'rsrc/js/application/drydock/drydock-live-operation-status.js' => '47a0728b',
'rsrc/js/application/fact/Chart.js' => '52e3ff03',
'rsrc/js/application/fact/ChartCurtainView.js' => '86954222',
'rsrc/js/application/fact/ChartFunctionLabel.js' => '81de1dab',
'rsrc/js/application/files/behavior-document-engine.js' => '243d6c22',
'rsrc/js/application/files/behavior-icon-composer.js' => '38a6cedb',
'rsrc/js/application/files/behavior-launch-icon-composer.js' => 'a17b84f1',
'rsrc/js/application/harbormaster/behavior-harbormaster-log.js' => 'b347a301',
'rsrc/js/application/herald/HeraldRuleEditor.js' => '2633bef7',
'rsrc/js/application/herald/PathTypeahead.js' => 'ad486db3',
'rsrc/js/application/herald/herald-rule-editor.js' => '0922e81d',
'rsrc/js/application/maniphest/behavior-batch-selector.js' => '139ef688',
'rsrc/js/application/maniphest/behavior-line-chart.js' => 'ad258e28',
'rsrc/js/application/maniphest/behavior-list-edit.js' => 'c687e867',
'rsrc/js/application/owners/OwnersPathEditor.js' => '2a8b62d9',
'rsrc/js/application/owners/owners-path-editor.js' => 'ff688a7a',
'rsrc/js/application/passphrase/passphrase-credential-control.js' => '48fe33d0',
'rsrc/js/application/pholio/behavior-pholio-mock-edit.js' => '3eed1f2b',
'rsrc/js/application/pholio/behavior-pholio-mock-view.js' => '5aa1544e',
'rsrc/js/application/phortune/behavior-stripe-payment-form.js' => '02cb4398',
'rsrc/js/application/phortune/behavior-test-payment-form.js' => '4a7fb02b',
'rsrc/js/application/phortune/phortune-credit-card-form.js' => 'd12d214f',
'rsrc/js/application/policy/behavior-policy-control.js' => '0eaa33a9',
'rsrc/js/application/policy/behavior-policy-rule-editor.js' => '9347f172',
'rsrc/js/application/projects/WorkboardBoard.js' => 'b46d88c5',
'rsrc/js/application/projects/WorkboardCard.js' => '0392a5d8',
'rsrc/js/application/projects/WorkboardCardTemplate.js' => '84f82dad',
'rsrc/js/application/projects/WorkboardColumn.js' => 'c3d24e63',
'rsrc/js/application/projects/WorkboardController.js' => 'b9d0c2f3',
'rsrc/js/application/projects/WorkboardDropEffect.js' => '8e0aa661',
'rsrc/js/application/projects/WorkboardHeader.js' => '111bfd2d',
'rsrc/js/application/projects/WorkboardHeaderTemplate.js' => 'ebe83a6b',
'rsrc/js/application/projects/WorkboardOrderTemplate.js' => '03e8891f',
'rsrc/js/application/projects/behavior-project-boards.js' => '58cb6a88',
'rsrc/js/application/projects/behavior-project-create.js' => '34c53422',
'rsrc/js/application/projects/behavior-reorder-columns.js' => '8ac32fd9',
'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' => '1c95ea63',
'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',
'rsrc/js/application/transactions/behavior-reorder-configs.js' => '4842f137',
'rsrc/js/application/transactions/behavior-reorder-fields.js' => '0ad8d31f',
'rsrc/js/application/transactions/behavior-show-older-transactions.js' => '8b5c7d65',
'rsrc/js/application/transactions/behavior-transaction-comment-form.js' => '2bdadf1a',
'rsrc/js/application/transactions/behavior-transaction-list.js' => '9cec214e',
'rsrc/js/application/trigger/TriggerRule.js' => '41b7b4f6',
'rsrc/js/application/trigger/TriggerRuleControl.js' => '5faf27b9',
'rsrc/js/application/trigger/TriggerRuleEditor.js' => 'b49fd60c',
'rsrc/js/application/trigger/TriggerRuleType.js' => '4feea7d3',
'rsrc/js/application/trigger/trigger-rule-editor.js' => '398fdf13',
'rsrc/js/application/typeahead/behavior-typeahead-browse.js' => '70245195',
'rsrc/js/application/typeahead/behavior-typeahead-search.js' => '7b139193',
'rsrc/js/application/uiexample/gesture-example.js' => '242dedd0',
'rsrc/js/application/uiexample/notification-example.js' => '29819b75',
'rsrc/js/core/Busy.js' => '5202e831',
'rsrc/js/core/DragAndDropFileUpload.js' => '4370900d',
'rsrc/js/core/DraggableList.js' => '0169e425',
'rsrc/js/core/Favicon.js' => '7930776a',
'rsrc/js/core/FileUpload.js' => 'ab85e184',
'rsrc/js/core/Hovercard.js' => '074f0783',
'rsrc/js/core/KeyboardShortcut.js' => '1a844c06',
'rsrc/js/core/KeyboardShortcutManager.js' => '81debc48',
'rsrc/js/core/MultirowRowManager.js' => '5b54c823',
'rsrc/js/core/Notification.js' => 'a9b91e3f',
'rsrc/js/core/Prefab.js' => '5793d835',
'rsrc/js/core/ShapedRequest.js' => 'abf88db8',
'rsrc/js/core/TextAreaUtils.js' => 'f340a484',
'rsrc/js/core/Title.js' => '43bc9360',
'rsrc/js/core/ToolTip.js' => '83754533',
'rsrc/js/core/behavior-audio-source.js' => '3dc5ad43',
'rsrc/js/core/behavior-autofocus.js' => '65bb0011',
'rsrc/js/core/behavior-badge-view.js' => '92cdd7b6',
'rsrc/js/core/behavior-bulk-editor.js' => 'aa6d2308',
'rsrc/js/core/behavior-choose-control.js' => '04f8a1e3',
'rsrc/js/core/behavior-copy.js' => 'cf32921f',
'rsrc/js/core/behavior-detect-timezone.js' => '78bc5d94',
'rsrc/js/core/behavior-device.js' => '0cf79f45',
'rsrc/js/core/behavior-drag-and-drop-textarea.js' => '7ad020a5',
'rsrc/js/core/behavior-fancy-datepicker.js' => '956f3eeb',
'rsrc/js/core/behavior-form.js' => '55d7b788',
'rsrc/js/core/behavior-gesture.js' => 'b58d1a2a',
'rsrc/js/core/behavior-global-drag-and-drop.js' => '1cab0e9a',
'rsrc/js/core/behavior-high-security-warning.js' => 'dae2d55b',
'rsrc/js/core/behavior-history-install.js' => '6a1583a8',
'rsrc/js/core/behavior-hovercard.js' => '6c379000',
'rsrc/js/core/behavior-keyboard-pager.js' => '1325b731',
'rsrc/js/core/behavior-keyboard-shortcuts.js' => '42c44e8b',
'rsrc/js/core/behavior-lightbox-attachments.js' => 'c7e748bf',
'rsrc/js/core/behavior-line-linker.js' => '590e6527',
'rsrc/js/core/behavior-linked-container.js' => '74446546',
'rsrc/js/core/behavior-more.js' => '506aa3f4',
'rsrc/js/core/behavior-object-selector.js' => '98ef467f',
'rsrc/js/core/behavior-oncopy.js' => 'ff7b3f22',
'rsrc/js/core/behavior-phabricator-remarkup-assist.js' => '54262396',
'rsrc/js/core/behavior-read-only-warning.js' => 'b9109f8f',
'rsrc/js/core/behavior-redirect.js' => '407ee861',
'rsrc/js/core/behavior-refresh-csrf.js' => '46116c01',
'rsrc/js/core/behavior-remarkup-load-image.js' => '202bfa3f',
'rsrc/js/core/behavior-remarkup-preview.js' => 'd8a86cfb',
'rsrc/js/core/behavior-reorder-applications.js' => 'aa371860',
'rsrc/js/core/behavior-reveal-content.js' => 'b105a3a6',
'rsrc/js/core/behavior-scrollbar.js' => '92388bae',
'rsrc/js/core/behavior-search-typeahead.js' => '1cb7d027',
'rsrc/js/core/behavior-select-content.js' => 'e8240b50',
'rsrc/js/core/behavior-select-on-click.js' => '66365ee2',
'rsrc/js/core/behavior-setup-check-https.js' => '01384686',
'rsrc/js/core/behavior-time-typeahead.js' => '5803b9e7',
'rsrc/js/core/behavior-toggle-class.js' => '32db8374',
'rsrc/js/core/behavior-tokenizer.js' => '3b4899b0',
'rsrc/js/core/behavior-tooltip.js' => '73ecc1f8',
'rsrc/js/core/behavior-user-menu.js' => '60cd9241',
'rsrc/js/core/behavior-watch-anchor.js' => '3972dadb',
'rsrc/js/core/behavior-workflow.js' => '9623adc1',
'rsrc/js/core/darkconsole/DarkLog.js' => '3b869402',
'rsrc/js/core/darkconsole/DarkMessage.js' => '26cd4b73',
'rsrc/js/core/darkconsole/behavior-dark-console.js' => '457f4d16',
'rsrc/js/core/phtize.js' => '2f1db1ed',
'rsrc/js/phui/behavior-phui-dropdown-menu.js' => '5cf0501a',
'rsrc/js/phui/behavior-phui-file-upload.js' => 'e150bd50',
'rsrc/js/phui/behavior-phui-selectable-list.js' => 'b26a41e4',
'rsrc/js/phui/behavior-phui-submenu.js' => 'b5e9bff9',
'rsrc/js/phui/behavior-phui-tab-group.js' => '242aa08b',
'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' => '2fbe234d',
'rsrc/js/phuix/PHUIXButtonView.js' => '55a24e84',
'rsrc/js/phuix/PHUIXDropdownMenu.js' => '7acfd98b',
'rsrc/js/phuix/PHUIXExample.js' => 'c2c500a7',
'rsrc/js/phuix/PHUIXFormControl.js' => '38c1f3fb',
'rsrc/js/phuix/PHUIXFormationColumnView.js' => '4bcc1f78',
'rsrc/js/phuix/PHUIXFormationFlankView.js' => '6648270a',
'rsrc/js/phuix/PHUIXFormationView.js' => 'cef53b3e',
'rsrc/js/phuix/PHUIXIconView.js' => 'a5257c4e',
),
'symbols' => array(
'almanac-css' => '2e050f4f',
'aphront-bars' => '4a327b4a',
'aphront-dark-console-css' => '7f06cda2',
'aphront-dialog-view-css' => '874f5c06',
'aphront-list-filter-view-css' => 'feb64255',
'aphront-multi-column-view-css' => 'fbc00ba3',
'aphront-panel-view-css' => '46923d46',
'aphront-table-view-css' => '0bb61df1',
'aphront-tokenizer-control-css' => '34e2a838',
'aphront-tooltip-css' => 'e3f2412f',
'aphront-typeahead-control-css' => '8779483d',
'application-search-view-css' => '0f7c06d8',
'auth-css' => 'c2f23d74',
'bulk-job-css' => '73af99f5',
'conduit-api-css' => 'ce2cfc41',
'config-options-css' => '16c920ae',
'conpherence-color-css' => 'b17746b0',
'conpherence-durable-column-view' => '2d57072b',
'conpherence-header-pane-css' => 'c9a3db8e',
'conpherence-menu-css' => '67f4680d',
'conpherence-message-pane-css' => 'd244db1e',
'conpherence-notification-css' => '6a3d4e58',
'conpherence-participant-pane-css' => '69e0058a',
'conpherence-thread-manager' => 'aec8e38c',
'conpherence-transaction-css' => '3a3f5e7e',
'd3' => '9d068042',
- 'diff-tree-view-css' => '26fb4a0d',
+ 'diff-tree-view-css' => 'e2d3e222',
'differential-changeset-view-css' => '489b6995',
'differential-core-view-css' => '7300a73e',
'differential-revision-add-comment-css' => '7e5900d9',
'differential-revision-comment-css' => '7dbc8d1d',
'differential-revision-history-css' => '8aa3eac5',
'differential-revision-list-css' => '93d2df7d',
'differential-table-of-contents-css' => '0e3364c7',
'diffusion-css' => 'b54c77b0',
'diffusion-icons-css' => '23b31a1b',
'diffusion-readme-css' => 'b68a76e4',
'diffusion-repository-css' => 'b89e8c6c',
'diviner-shared-css' => '4bd263b0',
'font-fontawesome' => '3883938a',
'font-lato' => '23631304',
'global-drag-and-drop-css' => '1d2713a4',
'harbormaster-css' => '8dfe16b2',
'herald-css' => '648d39e2',
'herald-rule-editor' => '2633bef7',
'herald-test-css' => 'e004176f',
'inline-comment-summary-css' => '81eb368d',
'javelin-aphlict' => '022516b4',
'javelin-behavior' => '1b6acc2a',
'javelin-behavior-aphlict-dropdown' => 'e9a2940f',
'javelin-behavior-aphlict-listen' => '4e61fa88',
'javelin-behavior-aphlict-status' => 'c3703a16',
'javelin-behavior-aphront-basic-tokenizer' => '3b4899b0',
'javelin-behavior-aphront-drag-and-drop-textarea' => '7ad020a5',
'javelin-behavior-aphront-form-disable-on-submit' => '55d7b788',
'javelin-behavior-aphront-more' => '506aa3f4',
'javelin-behavior-audio-source' => '3dc5ad43',
'javelin-behavior-audit-preview' => 'b7b73831',
'javelin-behavior-badge-view' => '92cdd7b6',
'javelin-behavior-bulk-editor' => 'aa6d2308',
'javelin-behavior-bulk-job-reload' => '3829a3cf',
'javelin-behavior-calendar-month-view' => '158c64e0',
'javelin-behavior-choose-control' => '04f8a1e3',
'javelin-behavior-comment-actions' => '4dffaeb2',
'javelin-behavior-config-reorder-fields' => '2539f834',
'javelin-behavior-conpherence-menu' => '8c2ed2bf',
'javelin-behavior-conpherence-participant-pane' => '43ba89a2',
'javelin-behavior-conpherence-pontificate' => '4ae58b5a',
'javelin-behavior-conpherence-search' => '91befbcc',
'javelin-behavior-countdown-timer' => '6a162524',
'javelin-behavior-dark-console' => '457f4d16',
'javelin-behavior-dashboard-async-panel' => '9c01e364',
'javelin-behavior-dashboard-move-panels' => 'a2ab19be',
'javelin-behavior-dashboard-query-panel-select' => '1e413dc9',
'javelin-behavior-dashboard-tab-panel' => '0116d3e8',
'javelin-behavior-day-view' => '727a5a61',
'javelin-behavior-desktop-notifications-control' => '070679fe',
'javelin-behavior-detect-timezone' => '78bc5d94',
'javelin-behavior-device' => '0cf79f45',
'javelin-behavior-diff-preview-link' => 'f51e9c17',
'javelin-behavior-differential-diff-radios' => '925fe8cd',
'javelin-behavior-differential-populate' => 'b86ef6c2',
'javelin-behavior-diffusion-commit-branches' => '4b671572',
'javelin-behavior-diffusion-commit-graph' => 'ef836bf2',
'javelin-behavior-diffusion-locate-file' => '87428eb2',
'javelin-behavior-diffusion-pull-lastmodified' => 'c715c123',
'javelin-behavior-document-engine' => '243d6c22',
'javelin-behavior-doorkeeper-tag' => '6a85bc5a',
'javelin-behavior-drydock-live-operation-status' => '47a0728b',
'javelin-behavior-durable-column' => 'fa6f30b2',
'javelin-behavior-editengine-reorder-configs' => '4842f137',
'javelin-behavior-editengine-reorder-fields' => '0ad8d31f',
'javelin-behavior-event-all-day' => '0b1bc990',
'javelin-behavior-fancy-datepicker' => '956f3eeb',
'javelin-behavior-global-drag-and-drop' => '1cab0e9a',
'javelin-behavior-harbormaster-log' => 'b347a301',
'javelin-behavior-herald-rule-editor' => '0922e81d',
'javelin-behavior-high-security-warning' => 'dae2d55b',
'javelin-behavior-history-install' => '6a1583a8',
'javelin-behavior-icon-composer' => '38a6cedb',
'javelin-behavior-launch-icon-composer' => 'a17b84f1',
'javelin-behavior-lightbox-attachments' => 'c7e748bf',
'javelin-behavior-line-chart' => 'ad258e28',
'javelin-behavior-linked-container' => '74446546',
'javelin-behavior-maniphest-batch-selector' => '139ef688',
'javelin-behavior-maniphest-list-editor' => 'c687e867',
'javelin-behavior-owners-path-editor' => 'ff688a7a',
'javelin-behavior-passphrase-credential-control' => '48fe33d0',
'javelin-behavior-phabricator-autofocus' => '65bb0011',
'javelin-behavior-phabricator-clipboard-copy' => 'cf32921f',
'javelin-behavior-phabricator-gesture' => 'b58d1a2a',
'javelin-behavior-phabricator-gesture-example' => '242dedd0',
'javelin-behavior-phabricator-keyboard-pager' => '1325b731',
'javelin-behavior-phabricator-keyboard-shortcuts' => '42c44e8b',
'javelin-behavior-phabricator-line-linker' => '590e6527',
'javelin-behavior-phabricator-notification-example' => '29819b75',
'javelin-behavior-phabricator-object-selector' => '98ef467f',
'javelin-behavior-phabricator-oncopy' => 'ff7b3f22',
'javelin-behavior-phabricator-remarkup-assist' => '54262396',
'javelin-behavior-phabricator-reveal-content' => 'b105a3a6',
'javelin-behavior-phabricator-search-typeahead' => '1cb7d027',
'javelin-behavior-phabricator-show-older-transactions' => '8b5c7d65',
'javelin-behavior-phabricator-tooltips' => '73ecc1f8',
'javelin-behavior-phabricator-transaction-comment-form' => '2bdadf1a',
'javelin-behavior-phabricator-transaction-list' => '9cec214e',
'javelin-behavior-phabricator-watch-anchor' => '3972dadb',
'javelin-behavior-pholio-mock-edit' => '3eed1f2b',
'javelin-behavior-pholio-mock-view' => '5aa1544e',
'javelin-behavior-phui-dropdown-menu' => '5cf0501a',
'javelin-behavior-phui-file-upload' => 'e150bd50',
'javelin-behavior-phui-hovercards' => '6c379000',
'javelin-behavior-phui-selectable-list' => 'b26a41e4',
'javelin-behavior-phui-submenu' => 'b5e9bff9',
'javelin-behavior-phui-tab-group' => '242aa08b',
'javelin-behavior-phui-timer-control' => 'f84bcbf4',
'javelin-behavior-phuix-example' => 'c2c500a7',
'javelin-behavior-policy-control' => '0eaa33a9',
'javelin-behavior-policy-rule-editor' => '9347f172',
'javelin-behavior-project-boards' => '58cb6a88',
'javelin-behavior-project-create' => '34c53422',
'javelin-behavior-quicksand-blacklist' => '5a6f6a06',
'javelin-behavior-read-only-warning' => 'b9109f8f',
'javelin-behavior-redirect' => '407ee861',
'javelin-behavior-refresh-csrf' => '46116c01',
'javelin-behavior-releeph-preview-branch' => '75184d68',
'javelin-behavior-releeph-request-state-change' => '9f081f05',
'javelin-behavior-releeph-request-typeahead' => 'aa3a100c',
'javelin-behavior-remarkup-load-image' => '202bfa3f',
'javelin-behavior-remarkup-preview' => 'd8a86cfb',
'javelin-behavior-reorder-applications' => 'aa371860',
'javelin-behavior-reorder-columns' => '8ac32fd9',
'javelin-behavior-reorder-profile-menu-items' => 'e5bdb730',
'javelin-behavior-repository-crossreference' => '1c95ea63',
'javelin-behavior-scrollbar' => '92388bae',
'javelin-behavior-search-reorder-queries' => 'b86f297f',
'javelin-behavior-select-content' => 'e8240b50',
'javelin-behavior-select-on-click' => '66365ee2',
'javelin-behavior-setup-check-https' => '01384686',
'javelin-behavior-stripe-payment-form' => '02cb4398',
'javelin-behavior-test-payment-form' => '4a7fb02b',
'javelin-behavior-time-typeahead' => '5803b9e7',
'javelin-behavior-toggle-class' => '32db8374',
'javelin-behavior-toggle-widget' => '8f959ad0',
'javelin-behavior-trigger-rule-editor' => '398fdf13',
'javelin-behavior-typeahead-browse' => '70245195',
'javelin-behavior-typeahead-search' => '7b139193',
'javelin-behavior-user-menu' => '60cd9241',
'javelin-behavior-view-placeholder' => 'a9942052',
'javelin-behavior-workflow' => '9623adc1',
'javelin-chart' => '52e3ff03',
'javelin-chart-curtain-view' => '86954222',
'javelin-chart-function-label' => '81de1dab',
'javelin-color' => '78f811c9',
'javelin-cookie' => '05d290ef',
'javelin-diffusion-locate-file-source' => '94243d89',
'javelin-dom' => '94681e22',
'javelin-dynval' => '202a2e85',
'javelin-event' => 'c03f2fb4',
'javelin-fx' => '34450586',
'javelin-history' => '030b4f7a',
'javelin-install' => '5902260c',
'javelin-json' => '541f81c3',
'javelin-leader' => '0d2490ce',
'javelin-magical-init' => '98e6504a',
'javelin-mask' => '7c4d8998',
'javelin-quicksand' => 'd3799cb4',
'javelin-reactor' => '1c850a26',
'javelin-reactor-dom' => '6cfa0008',
'javelin-reactor-node-calmer' => '225bbb98',
'javelin-reactornode' => '72960bc1',
'javelin-request' => '84e6891f',
'javelin-resource' => '740956e1',
'javelin-routable' => '6a18c42e',
'javelin-router' => '32755edb',
'javelin-scrollbar' => 'a43ae2ae',
'javelin-sound' => 'd4cc2d2a',
'javelin-stratcom' => '0889b835',
'javelin-tokenizer' => '89a1ae3a',
'javelin-typeahead' => 'a4356cde',
'javelin-typeahead-composite-source' => '22ee68a5',
'javelin-typeahead-normalizer' => 'a241536a',
'javelin-typeahead-ondemand-source' => '23387297',
'javelin-typeahead-preloaded-source' => '5a79f6c3',
'javelin-typeahead-source' => '8badee71',
'javelin-typeahead-static-source' => '80bff3af',
'javelin-uri' => '2e255291',
'javelin-util' => 'edb4d8c9',
'javelin-vector' => 'e9c80beb',
'javelin-view' => '289bf236',
'javelin-view-html' => 'f8c4e135',
'javelin-view-interpreter' => '876506b6',
'javelin-view-renderer' => '9aae2b66',
'javelin-view-visitor' => '308f9fe4',
'javelin-websocket' => 'fdc13e4e',
'javelin-workboard-board' => 'b46d88c5',
'javelin-workboard-card' => '0392a5d8',
'javelin-workboard-card-template' => '84f82dad',
'javelin-workboard-column' => 'c3d24e63',
'javelin-workboard-controller' => 'b9d0c2f3',
'javelin-workboard-drop-effect' => '8e0aa661',
'javelin-workboard-header' => '111bfd2d',
'javelin-workboard-header-template' => 'ebe83a6b',
'javelin-workboard-order-template' => '03e8891f',
'javelin-workflow' => '945ff654',
'maniphest-report-css' => '3d53188b',
'maniphest-task-edit-css' => '272daa84',
'maniphest-task-summary-css' => '61d1667e',
'multirow-row-manager' => '5b54c823',
'owners-path-editor' => '2a8b62d9',
'owners-path-editor-css' => 'fa7c13ef',
'paste-css' => 'b37bcd38',
'path-typeahead' => 'ad486db3',
'people-picture-menu-item-css' => 'fe8e07cf',
'people-profile-css' => '2ea2daa1',
'phabricator-action-list-view-css' => 'e820263c',
'phabricator-busy' => '5202e831',
'phabricator-chatlog-css' => 'abdc76ee',
'phabricator-content-source-view-css' => 'cdf0d579',
'phabricator-core-css' => '1b29ed61',
'phabricator-countdown-css' => 'bff8012f',
'phabricator-darklog' => '3b869402',
'phabricator-darkmessage' => '26cd4b73',
'phabricator-dashboard-css' => '5a205b9d',
- 'phabricator-diff-changeset' => 'a1a5dc46',
+ 'phabricator-diff-changeset' => 'dd1a6f34',
'phabricator-diff-changeset-list' => '57035863',
'phabricator-diff-inline' => '16e97ebc',
- 'phabricator-diff-path-view' => 'ceb66010',
+ 'phabricator-diff-path-view' => '8207abf9',
'phabricator-diff-tree-view' => '5d83623b',
'phabricator-drag-and-drop-file-upload' => '4370900d',
'phabricator-draggable-list' => '0169e425',
'phabricator-fatal-config-template-css' => '20babf50',
'phabricator-favicon' => '7930776a',
'phabricator-feed-css' => 'd8b6e3f8',
'phabricator-file-upload' => 'ab85e184',
'phabricator-flag-css' => '2b77be8d',
'phabricator-keyboard-shortcut' => '1a844c06',
'phabricator-keyboard-shortcut-manager' => '81debc48',
'phabricator-main-menu-view' => 'bcec20f0',
'phabricator-nav-view-css' => '423f92cc',
'phabricator-notification' => 'a9b91e3f',
'phabricator-notification-css' => '30240bd2',
'phabricator-notification-menu-css' => '4df1ee30',
'phabricator-object-selector-css' => 'ee77366f',
'phabricator-phtize' => '2f1db1ed',
'phabricator-prefab' => '5793d835',
'phabricator-remarkup-css' => 'c286eaef',
'phabricator-search-results-css' => '9ea70ace',
'phabricator-shaped-request' => 'abf88db8',
'phabricator-slowvote-css' => '1694baed',
'phabricator-source-code-view-css' => '03d7ac28',
'phabricator-standard-page-view' => 'a374f94c',
'phabricator-textareautils' => 'f340a484',
'phabricator-title' => '43bc9360',
'phabricator-tooltip' => '83754533',
'phabricator-ui-example-css' => 'b4795059',
'phabricator-zindex-css' => '612e9522',
'phame-css' => 'bb442327',
'pholio-css' => '88ef5ef1',
'pholio-edit-css' => '4df55b3b',
'pholio-inline-comments-css' => '722b48c2',
'phortune-credit-card-form' => 'd12d214f',
'phortune-credit-card-form-css' => '3b9868a8',
'phortune-css' => '508a1a5e',
'phortune-invoice-css' => '4436b241',
'phrequent-css' => 'bd79cc67',
'phriction-document-css' => '03380da0',
'phui-action-panel-css' => '6c386cbf',
'phui-badge-view-css' => '666e25ad',
'phui-basic-nav-view-css' => '56ebd66d',
'phui-big-info-view-css' => '362ad37b',
'phui-box-css' => '5ed3b8cb',
'phui-bulk-editor-css' => '374d5e30',
'phui-button-bar-css' => 'a4aa75c4',
'phui-button-css' => 'ea704902',
'phui-button-simple-css' => '1ff278aa',
'phui-calendar-css' => 'f11073aa',
'phui-calendar-day-css' => '9597d706',
'phui-calendar-list-css' => 'ccd7e4e2',
'phui-calendar-month-css' => 'cb758c42',
'phui-chart-css' => '14df9ae3',
'phui-cms-css' => '8c05c41e',
'phui-comment-form-css' => '68a2d99a',
'phui-comment-panel-css' => 'ec4e31c0',
'phui-crumbs-view-css' => '614f43cf',
'phui-curtain-object-ref-view-css' => '12404744',
'phui-curtain-view-css' => '68c5efb6',
'phui-document-summary-view-css' => 'b068eed1',
'phui-document-view-css' => '52b748a5',
'phui-document-view-pro-css' => 'b9613a10',
'phui-feed-story-css' => 'a0c05029',
'phui-font-icon-base-css' => '303c9b87',
'phui-fontkit-css' => '1ec937e5',
'phui-form-css' => '1f177cb7',
'phui-form-view-css' => '01b796c0',
'phui-formation-view-css' => '82a3b73e',
'phui-head-thing-view-css' => 'd7f293df',
'phui-header-view-css' => '36c86a58',
'phui-hovercard' => '074f0783',
'phui-hovercard-view-css' => '6ca90fa0',
'phui-icon-set-selector-css' => '7aa5f3ec',
'phui-icon-view-css' => '4cbc684a',
'phui-image-mask-css' => '62c7f4d2',
'phui-info-view-css' => 'a10a909b',
'phui-inline-comment-view-css' => '48acce5b',
'phui-invisible-character-view-css' => 'c694c4a4',
'phui-left-right-css' => '68513c34',
'phui-lightbox-css' => '4ebf22da',
'phui-list-view-css' => '2f253c22',
'phui-object-box-css' => 'b8d7eea0',
'phui-oi-big-ui-css' => 'fa74cc35',
'phui-oi-color-css' => 'b517bfa0',
'phui-oi-drag-ui-css' => 'da15d3dc',
'phui-oi-flush-ui-css' => '490e2e2e',
'phui-oi-list-view-css' => 'd7723ecc',
'phui-oi-simple-ui-css' => '6a30fa46',
'phui-pager-css' => 'd022c7ad',
'phui-pinboard-view-css' => '1f08f5d8',
'phui-policy-section-view-css' => '139fdc64',
'phui-property-list-view-css' => '9c477af1',
'phui-remarkup-preview-css' => '91767007',
'phui-segment-bar-view-css' => '5166b370',
'phui-spacing-css' => 'b05cadc3',
'phui-status-list-view-css' => 'e5ff8be0',
'phui-tag-view-css' => '8519160a',
'phui-theme-css' => '35883b37',
'phui-timeline-view-css' => '1e348e4b',
'phui-two-column-view-css' => 'f96d319f',
'phui-workboard-color-css' => 'e86de308',
'phui-workboard-view-css' => '74fc9d98',
'phui-workcard-view-css' => '913441b6',
'phui-workpanel-view-css' => '3ae89b20',
'phuix-action-list-view' => 'c68f183f',
'phuix-action-view' => 'aaa08f3b',
'phuix-autocomplete' => '2fbe234d',
'phuix-button-view' => '55a24e84',
'phuix-dropdown-menu' => '7acfd98b',
'phuix-form-control-view' => '38c1f3fb',
'phuix-formation-column-view' => '4bcc1f78',
'phuix-formation-flank-view' => '6648270a',
'phuix-formation-view' => 'cef53b3e',
'phuix-icon-view' => 'a5257c4e',
'policy-css' => 'ceb56a08',
'policy-edit-css' => '8794e2ed',
'policy-transaction-detail-css' => 'c02b8384',
'ponder-view-css' => '05a09d0a',
'project-card-view-css' => '4e7371cd',
'project-triggers-css' => 'cd9c8bb9',
'project-view-css' => '567858b3',
'releeph-core' => 'f81ff2db',
'releeph-preview-branch' => '22db5c07',
'releeph-request-differential-create-dialog' => '0ac1ea31',
'releeph-request-typeahead-css' => 'bce37359',
'setup-issue-css' => '5eed85b2',
'sprite-login-css' => '18b368a6',
'sprite-tokens-css' => 'f1896dc5',
'syntax-default-css' => '055fc231',
'syntax-highlighting-css' => '220b85f9',
'tokens-css' => 'ce5a50bd',
'trigger-rule' => '41b7b4f6',
'trigger-rule-control' => '5faf27b9',
'trigger-rule-editor' => 'b49fd60c',
'trigger-rule-type' => '4feea7d3',
'typeahead-browse-css' => 'b7ed02d2',
'unhandled-exception-css' => '9ecfc00d',
),
'requires' => array(
'0116d3e8' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'01384686' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-notification',
),
'0169e425' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
'javelin-vector',
'javelin-magical-init',
),
'022516b4' => array(
'javelin-install',
'javelin-util',
'javelin-websocket',
'javelin-leader',
'javelin-json',
),
'02cb4398' => array(
'javelin-behavior',
'javelin-dom',
'phortune-credit-card-form',
),
'030b4f7a' => array(
'javelin-stratcom',
'javelin-install',
'javelin-uri',
'javelin-util',
),
'0392a5d8' => array(
'javelin-install',
),
'03e8891f' => array(
'javelin-install',
),
'04f8a1e3' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-workflow',
),
'05d290ef' => array(
'javelin-install',
'javelin-util',
),
'070679fe' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-uri',
'phabricator-notification',
),
'074f0783' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
'javelin-request',
'javelin-uri',
),
'0889b835' => array(
'javelin-install',
'javelin-event',
'javelin-util',
'javelin-magical-init',
),
'0922e81d' => array(
'herald-rule-editor',
'javelin-behavior',
),
'0ad8d31f' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'0cf79f45' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
'javelin-install',
),
'0d2490ce' => array(
'javelin-install',
),
'0eaa33a9' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phuix-dropdown-menu',
'phuix-action-list-view',
'phuix-action-view',
'javelin-workflow',
'phuix-icon-view',
),
'111bfd2d' => array(
'javelin-install',
),
'1325b731' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-keyboard-shortcut',
),
'139ef688' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
),
'16e97ebc' => array(
'javelin-dom',
),
'1a844c06' => array(
'javelin-install',
'javelin-util',
'phabricator-keyboard-shortcut-manager',
),
'1b6acc2a' => array(
'javelin-magical-init',
'javelin-util',
),
'1c850a26' => array(
'javelin-install',
'javelin-util',
),
'1c95ea63' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-uri',
),
'1cab0e9a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-uri',
'javelin-mask',
'phabricator-drag-and-drop-file-upload',
),
'1cb7d027' => array(
'javelin-behavior',
'javelin-typeahead-ondemand-source',
'javelin-typeahead',
'javelin-dom',
'javelin-uri',
'javelin-util',
'javelin-stratcom',
'phabricator-prefab',
'phuix-icon-view',
),
'1e413dc9' => array(
'javelin-behavior',
'javelin-dom',
),
'1ff278aa' => array(
'phui-button-css',
),
'202a2e85' => array(
'javelin-install',
'javelin-reactornode',
'javelin-util',
'javelin-reactor',
),
'202bfa3f' => array(
'javelin-behavior',
'javelin-request',
),
'220b85f9' => array(
'syntax-default-css',
),
'225bbb98' => array(
'javelin-install',
'javelin-reactor',
'javelin-util',
),
'22ee68a5' => array(
'javelin-install',
'javelin-typeahead-source',
'javelin-util',
),
23387297 => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-typeahead-source',
),
23631304 => array(
'phui-fontkit-css',
),
'242aa08b' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'242dedd0' => array(
'javelin-stratcom',
'javelin-behavior',
'javelin-vector',
'javelin-dom',
),
'243d6c22' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'2539f834' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-json',
'phabricator-draggable-list',
),
'2633bef7' => array(
'multirow-row-manager',
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-stratcom',
'javelin-json',
'phabricator-prefab',
),
'289bf236' => array(
'javelin-install',
'javelin-util',
),
'29819b75' => array(
'phabricator-notification',
'javelin-stratcom',
'javelin-behavior',
),
'2a8b62d9' => array(
'multirow-row-manager',
'javelin-install',
'path-typeahead',
'javelin-dom',
'javelin-util',
'phabricator-prefab',
'phuix-form-control-view',
),
'2bdadf1a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-request',
'phabricator-shaped-request',
),
'2e255291' => array(
'javelin-install',
'javelin-util',
'javelin-stratcom',
),
'2f1db1ed' => array(
'javelin-util',
),
'2fbe234d' => array(
'javelin-install',
'javelin-dom',
'phuix-icon-view',
'phabricator-prefab',
),
'308f9fe4' => array(
'javelin-install',
'javelin-util',
),
'32755edb' => array(
'javelin-install',
'javelin-util',
),
'32db8374' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
34450586 => array(
'javelin-color',
'javelin-install',
'javelin-util',
),
'34c53422' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
),
'34e2a838' => array(
'aphront-typeahead-control-css',
'phui-tag-view-css',
),
'3829a3cf' => array(
'javelin-behavior',
'javelin-uri',
),
'38a6cedb' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'38c1f3fb' => array(
'javelin-install',
'javelin-dom',
),
'3972dadb' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
),
'398fdf13' => array(
'javelin-behavior',
'trigger-rule-editor',
'trigger-rule',
'trigger-rule-type',
),
'3ae89b20' => array(
'phui-workcard-view-css',
),
'3b4899b0' => array(
'javelin-behavior',
'phabricator-prefab',
),
'3dc5ad43' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-vector',
'javelin-dom',
),
'3eed1f2b' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-workflow',
'javelin-quicksand',
'phabricator-phtize',
'phabricator-drag-and-drop-file-upload',
'phabricator-draggable-list',
),
'407ee861' => array(
'javelin-behavior',
'javelin-uri',
),
'42c44e8b' => array(
'javelin-behavior',
'javelin-workflow',
'javelin-json',
'javelin-dom',
'phabricator-keyboard-shortcut',
),
'4370900d' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-dom',
'javelin-uri',
'phabricator-file-upload',
),
'43ba89a2' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'phabricator-notification',
'conpherence-thread-manager',
),
'43bc9360' => array(
'javelin-install',
),
'457f4d16' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-util',
'javelin-dom',
'javelin-request',
'phabricator-keyboard-shortcut',
'phabricator-darklog',
'phabricator-darkmessage',
),
'46116c01' => array(
'javelin-request',
'javelin-behavior',
'javelin-dom',
'javelin-router',
'javelin-util',
'phabricator-busy',
),
'47a0728b' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
),
'4842f137' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'489b6995' => array(
'phui-inline-comment-view-css',
),
'48fe33d0' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'javelin-uri',
),
'490e2e2e' => array(
'phui-oi-list-view-css',
),
'4a7fb02b' => array(
'javelin-behavior',
'javelin-dom',
'phortune-credit-card-form',
),
'4ae58b5a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-stratcom',
'conpherence-thread-manager',
),
'4b671572' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-request',
),
'4bcc1f78' => array(
'javelin-install',
'javelin-dom',
),
'4dffaeb2' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phuix-form-control-view',
'phuix-icon-view',
'javelin-behavior-phabricator-gesture',
),
'4e61fa88' => array(
'javelin-behavior',
'javelin-aphlict',
'javelin-stratcom',
'javelin-request',
'javelin-uri',
'javelin-dom',
'javelin-json',
'javelin-router',
'javelin-util',
'javelin-leader',
'javelin-sound',
'phabricator-notification',
),
'4feea7d3' => array(
'trigger-rule-control',
),
'506aa3f4' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'5202e831' => array(
'javelin-install',
'javelin-dom',
'javelin-fx',
),
'52e3ff03' => array(
'phui-chart-css',
'd3',
'javelin-chart-curtain-view',
'javelin-chart-function-label',
),
'541f81c3' => array(
'javelin-install',
),
54262396 => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-phtize',
'phabricator-textareautils',
'javelin-workflow',
'javelin-vector',
'phuix-autocomplete',
'javelin-mask',
),
'55a24e84' => array(
'javelin-install',
'javelin-dom',
),
'55d7b788' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
57035863 => array(
'javelin-install',
'phuix-button-view',
'phabricator-diff-tree-view',
),
'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',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
'javelin-typeahead-static-source',
),
'58cb6a88' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'javelin-workboard-controller',
'javelin-workboard-drop-effect',
),
'5902260c' => array(
'javelin-util',
'javelin-magical-init',
),
'590e6527' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-history',
),
'5a6f6a06' => array(
'javelin-behavior',
'javelin-quicksand',
),
'5a79f6c3' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-typeahead-source',
),
'5aa1544e' => array(
'javelin-behavior',
'javelin-util',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
'javelin-magical-init',
'javelin-request',
'javelin-history',
'javelin-workflow',
'javelin-mask',
'javelin-behavior-device',
'phabricator-keyboard-shortcut',
),
'5b54c823' => array(
'javelin-install',
'javelin-stratcom',
'javelin-dom',
'javelin-util',
),
'5cf0501a' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phuix-dropdown-menu',
),
'5d83623b' => array(
'javelin-dom',
),
'5faf27b9' => array(
'phuix-form-control-view',
),
'60cd9241' => array(
'javelin-behavior',
),
'65bb0011' => array(
'javelin-behavior',
'javelin-dom',
),
'66365ee2' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'6648270a' => array(
'javelin-install',
'javelin-dom',
),
'6a1583a8' => array(
'javelin-behavior',
'javelin-history',
),
'6a162524' => array(
'javelin-behavior',
'javelin-dom',
),
'6a18c42e' => array(
'javelin-install',
),
'6a30fa46' => array(
'phui-oi-list-view-css',
),
'6a85bc5a' => array(
'javelin-behavior',
'javelin-dom',
'javelin-json',
'javelin-workflow',
'javelin-magical-init',
),
'6c379000' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'javelin-vector',
'phui-hovercard',
),
'6cfa0008' => array(
'javelin-dom',
'javelin-dynval',
'javelin-reactor',
'javelin-reactornode',
'javelin-install',
'javelin-util',
),
70245195 => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
),
'727a5a61' => array(
'phuix-icon-view',
),
'72960bc1' => array(
'javelin-install',
'javelin-reactor',
'javelin-util',
'javelin-reactor-node-calmer',
),
'73ecc1f8' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'phabricator-tooltip',
),
'740956e1' => array(
'javelin-util',
'javelin-uri',
'javelin-install',
),
74446546 => array(
'javelin-behavior',
'javelin-dom',
),
'75184d68' => array(
'javelin-behavior',
'javelin-dom',
'javelin-uri',
'javelin-request',
),
'78bc5d94' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-notification',
),
'78f811c9' => array(
'javelin-install',
),
'7930776a' => array(
'javelin-install',
'javelin-dom',
),
'7acfd98b' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-vector',
'javelin-stratcom',
),
'7ad020a5' => array(
'javelin-behavior',
'javelin-dom',
'phabricator-drag-and-drop-file-upload',
'phabricator-textareautils',
),
'7b139193' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
),
'7c4d8998' => array(
'javelin-install',
'javelin-dom',
),
'80bff3af' => array(
'javelin-install',
'javelin-typeahead-source',
),
'81debc48' => array(
'javelin-install',
'javelin-util',
'javelin-stratcom',
'javelin-dom',
'javelin-vector',
),
+ '8207abf9' => array(
+ 'javelin-dom',
+ ),
83754533 => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-vector',
),
'84e6891f' => array(
'javelin-install',
'javelin-stratcom',
'javelin-util',
'javelin-behavior',
'javelin-json',
'javelin-dom',
'javelin-resource',
'javelin-routable',
),
'84f82dad' => array(
'javelin-install',
),
'87428eb2' => array(
'javelin-behavior',
'javelin-diffusion-locate-file-source',
'javelin-dom',
'javelin-typeahead',
'javelin-uri',
),
'876506b6' => array(
'javelin-view',
'javelin-install',
'javelin-dom',
),
'89a1ae3a' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
),
'8ac32fd9' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'8b5c7d65' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phabricator-busy',
),
'8badee71' => array(
'javelin-install',
'javelin-util',
'javelin-dom',
'javelin-typeahead-normalizer',
),
'8c2ed2bf' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'javelin-behavior-device',
'javelin-history',
'javelin-vector',
'javelin-scrollbar',
'phabricator-title',
'phabricator-shaped-request',
'conpherence-thread-manager',
),
'8e0aa661' => array(
'javelin-install',
'javelin-dom',
),
'8f959ad0' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-stratcom',
),
'91befbcc' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-stratcom',
),
'92388bae' => array(
'javelin-behavior',
'javelin-scrollbar',
),
'925fe8cd' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'92cdd7b6' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'9347f172' => array(
'javelin-behavior',
'multirow-row-manager',
'javelin-dom',
'javelin-util',
'phabricator-prefab',
'javelin-json',
),
'94243d89' => array(
'javelin-install',
'javelin-dom',
'javelin-typeahead-preloaded-source',
'javelin-util',
),
'945ff654' => array(
'javelin-stratcom',
'javelin-request',
'javelin-dom',
'javelin-vector',
'javelin-install',
'javelin-util',
'javelin-mask',
'javelin-uri',
'javelin-routable',
),
'94681e22' => array(
'javelin-magical-init',
'javelin-install',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
),
'956f3eeb' => array(
'javelin-behavior',
'javelin-util',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
),
'9623adc1' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'javelin-router',
),
'98ef467f' => array(
'javelin-behavior',
'javelin-dom',
'javelin-request',
'javelin-util',
),
'9aae2b66' => array(
'javelin-install',
'javelin-util',
),
'9c01e364' => array(
'javelin-behavior',
'javelin-dom',
'javelin-workflow',
),
'9cec214e' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'javelin-uri',
'phabricator-textareautils',
),
'9f081f05' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-util',
'phabricator-keyboard-shortcut',
),
'a17b84f1' => array(
'javelin-behavior',
'javelin-dom',
'javelin-workflow',
),
- 'a1a5dc46' => array(
- 'javelin-dom',
- 'javelin-util',
- 'javelin-stratcom',
- 'javelin-install',
- 'javelin-workflow',
- 'javelin-router',
- 'javelin-behavior-device',
- 'javelin-vector',
- 'phabricator-diff-inline',
- 'phabricator-diff-path-view',
- ),
'a241536a' => array(
'javelin-install',
),
'a2ab19be' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
),
'a4356cde' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
'javelin-util',
),
'a43ae2ae' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-vector',
),
'a4aa75c4' => array(
'phui-button-css',
'phui-button-simple-css',
),
'a5257c4e' => array(
'javelin-install',
'javelin-dom',
),
'a9942052' => array(
'javelin-behavior',
'javelin-dom',
'javelin-view-renderer',
'javelin-install',
),
'a9b91e3f' => array(
'javelin-install',
'javelin-dom',
'javelin-stratcom',
'javelin-util',
'phabricator-notification-css',
),
'aa371860' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'aa3a100c' => array(
'javelin-behavior',
'javelin-dom',
'javelin-typeahead',
'javelin-typeahead-ondemand-source',
'javelin-dom',
),
'aa6d2308' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'multirow-row-manager',
'javelin-json',
'phuix-form-control-view',
),
'aaa08f3b' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
),
'ab85e184' => array(
'javelin-install',
'javelin-dom',
'phabricator-notification',
),
'abf88db8' => array(
'javelin-install',
'javelin-util',
'javelin-request',
'javelin-router',
),
'ad258e28' => array(
'javelin-behavior',
'javelin-dom',
'javelin-chart',
),
'ad486db3' => array(
'javelin-install',
'javelin-typeahead',
'javelin-dom',
'javelin-request',
'javelin-typeahead-ondemand-source',
'javelin-util',
),
'aec8e38c' => array(
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-install',
'javelin-aphlict',
'javelin-workflow',
'javelin-router',
'javelin-behavior-device',
'javelin-vector',
),
'b105a3a6' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'b26a41e4' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'b347a301' => array(
'javelin-behavior',
),
'b46d88c5' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-stratcom',
'javelin-workflow',
'phabricator-draggable-list',
'javelin-workboard-column',
'javelin-workboard-header-template',
'javelin-workboard-card-template',
'javelin-workboard-order-template',
),
'b49fd60c' => array(
'multirow-row-manager',
'trigger-rule',
),
'b517bfa0' => array(
'phui-oi-list-view-css',
),
'b58d1a2a' => array(
'javelin-behavior',
'javelin-behavior-device',
'javelin-stratcom',
'javelin-vector',
'javelin-dom',
'javelin-magical-init',
),
'b5e9bff9' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'b7b73831' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phabricator-shaped-request',
),
'b86ef6c2' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'phabricator-tooltip',
'phabricator-diff-changeset-list',
'phabricator-diff-changeset',
'phuix-formation-view',
),
'b86f297f' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'b9109f8f' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-notification',
),
'b9d0c2f3' => array(
'javelin-install',
'javelin-dom',
'javelin-util',
'javelin-vector',
'javelin-stratcom',
'javelin-workflow',
'phabricator-drag-and-drop-file-upload',
'javelin-workboard-board',
),
'bcec20f0' => array(
'phui-theme-css',
),
'c03f2fb4' => array(
'javelin-install',
),
'c2c500a7' => array(
'javelin-install',
'javelin-dom',
'phuix-button-view',
),
'c3703a16' => array(
'javelin-behavior',
'javelin-aphlict',
'phabricator-phtize',
'javelin-dom',
),
'c3d24e63' => array(
'javelin-install',
'javelin-workboard-card',
'javelin-workboard-header',
),
'c687e867' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-workflow',
'javelin-fx',
'javelin-util',
),
'c68f183f' => array(
'javelin-install',
'javelin-dom',
),
'c715c123' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'javelin-workflow',
'javelin-json',
),
'c7e748bf' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'javelin-mask',
'javelin-util',
'phuix-icon-view',
'phabricator-busy',
),
- 'ceb66010' => array(
- 'javelin-dom',
- ),
'cef53b3e' => array(
'javelin-install',
'javelin-dom',
'phuix-formation-column-view',
'phuix-formation-flank-view',
),
'cf32921f' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'd12d214f' => array(
'javelin-install',
'javelin-dom',
'javelin-json',
'javelin-workflow',
'javelin-util',
),
'd3799cb4' => array(
'javelin-install',
),
'd4cc2d2a' => array(
'javelin-install',
),
'd8a86cfb' => array(
'javelin-behavior',
'javelin-dom',
'javelin-util',
'phabricator-shaped-request',
),
'da15d3dc' => array(
'phui-oi-list-view-css',
),
'dae2d55b' => array(
'javelin-behavior',
'javelin-uri',
'phabricator-notification',
),
+ 'dd1a6f34' => array(
+ 'javelin-dom',
+ 'javelin-util',
+ 'javelin-stratcom',
+ 'javelin-install',
+ 'javelin-workflow',
+ 'javelin-router',
+ 'javelin-behavior-device',
+ 'javelin-vector',
+ 'phabricator-diff-inline',
+ 'phabricator-diff-path-view',
+ ),
'e150bd50' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
'phuix-dropdown-menu',
),
'e5bdb730' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-workflow',
'javelin-dom',
'phabricator-draggable-list',
),
'e8240b50' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'e9a2940f' => array(
'javelin-behavior',
'javelin-request',
'javelin-stratcom',
'javelin-vector',
'javelin-dom',
'javelin-uri',
'javelin-behavior-device',
'phabricator-title',
'phabricator-favicon',
),
'e9c80beb' => array(
'javelin-install',
'javelin-event',
),
'ebe83a6b' => array(
'javelin-install',
),
'ec4e31c0' => array(
'phui-timeline-view-css',
),
'ee77366f' => array(
'aphront-dialog-view-css',
),
'ef836bf2' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
),
'f340a484' => array(
'javelin-install',
'javelin-dom',
'javelin-vector',
),
'f51e9c17' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'f84bcbf4' => array(
'javelin-behavior',
'javelin-stratcom',
'javelin-dom',
),
'f8c4e135' => array(
'javelin-install',
'javelin-dom',
'javelin-view-visitor',
'javelin-util',
),
'fa6f30b2' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-behavior-device',
'javelin-scrollbar',
'javelin-quicksand',
'phabricator-keyboard-shortcut',
'conpherence-thread-manager',
),
'fa74cc35' => array(
'phui-oi-list-view-css',
),
'fdc13e4e' => array(
'javelin-install',
),
'ff688a7a' => array(
'owners-path-editor',
'javelin-behavior',
),
'ff7b3f22' => array(
'javelin-behavior',
'javelin-dom',
),
),
'packages' => array(
'conpherence.pkg.css' => array(
'conpherence-durable-column-view',
'conpherence-menu-css',
'conpherence-color-css',
'conpherence-message-pane-css',
'conpherence-notification-css',
'conpherence-transaction-css',
'conpherence-participant-pane-css',
'conpherence-header-pane-css',
),
'conpherence.pkg.js' => array(
'javelin-behavior-conpherence-menu',
'javelin-behavior-conpherence-participant-pane',
'javelin-behavior-conpherence-pontificate',
'javelin-behavior-toggle-widget',
),
'core.pkg.css' => array(
'phabricator-core-css',
'phabricator-zindex-css',
'phui-button-css',
'phui-button-simple-css',
'phui-theme-css',
'phabricator-standard-page-view',
'aphront-dialog-view-css',
'phui-form-view-css',
'aphront-panel-view-css',
'aphront-table-view-css',
'aphront-tokenizer-control-css',
'aphront-typeahead-control-css',
'aphront-list-filter-view-css',
'application-search-view-css',
'phabricator-remarkup-css',
'syntax-highlighting-css',
'syntax-default-css',
'phui-pager-css',
'aphront-tooltip-css',
'phabricator-flag-css',
'phui-info-view-css',
'phabricator-main-menu-view',
'phabricator-notification-css',
'phabricator-notification-menu-css',
'phui-lightbox-css',
'phui-comment-panel-css',
'phui-header-view-css',
'phabricator-nav-view-css',
'phui-basic-nav-view-css',
'phui-crumbs-view-css',
'phui-oi-list-view-css',
'phui-oi-color-css',
'phui-oi-big-ui-css',
'phui-oi-drag-ui-css',
'phui-oi-simple-ui-css',
'phui-oi-flush-ui-css',
'global-drag-and-drop-css',
'phui-spacing-css',
'phui-form-css',
'phui-icon-view-css',
'phabricator-action-list-view-css',
'phui-property-list-view-css',
'phui-tag-view-css',
'phui-list-view-css',
'font-fontawesome',
'font-lato',
'phui-font-icon-base-css',
'phui-fontkit-css',
'phui-box-css',
'phui-object-box-css',
'phui-timeline-view-css',
'phui-two-column-view-css',
'phui-curtain-view-css',
'sprite-login-css',
'sprite-tokens-css',
'tokens-css',
'auth-css',
'phui-status-list-view-css',
'phui-feed-story-css',
'phabricator-feed-css',
'phabricator-dashboard-css',
'aphront-multi-column-view-css',
),
'core.pkg.js' => array(
'javelin-util',
'javelin-install',
'javelin-event',
'javelin-stratcom',
'javelin-behavior',
'javelin-resource',
'javelin-request',
'javelin-vector',
'javelin-dom',
'javelin-json',
'javelin-uri',
'javelin-workflow',
'javelin-mask',
'javelin-typeahead',
'javelin-typeahead-normalizer',
'javelin-typeahead-source',
'javelin-typeahead-preloaded-source',
'javelin-typeahead-ondemand-source',
'javelin-tokenizer',
'javelin-history',
'javelin-router',
'javelin-routable',
'javelin-behavior-aphront-basic-tokenizer',
'javelin-behavior-workflow',
'javelin-behavior-aphront-form-disable-on-submit',
'phabricator-keyboard-shortcut-manager',
'phabricator-keyboard-shortcut',
'javelin-behavior-phabricator-keyboard-shortcuts',
'javelin-behavior-refresh-csrf',
'javelin-behavior-phabricator-watch-anchor',
'javelin-behavior-phabricator-autofocus',
'phuix-dropdown-menu',
'phuix-action-list-view',
'phuix-action-view',
'phuix-icon-view',
'phabricator-phtize',
'javelin-behavior-phabricator-oncopy',
'phabricator-tooltip',
'javelin-behavior-phabricator-tooltips',
'phabricator-prefab',
'javelin-behavior-device',
'javelin-behavior-toggle-class',
'javelin-behavior-lightbox-attachments',
'phabricator-busy',
'javelin-sound',
'javelin-aphlict',
'phabricator-notification',
'javelin-behavior-aphlict-listen',
'javelin-behavior-phabricator-search-typeahead',
'javelin-behavior-aphlict-dropdown',
'javelin-behavior-history-install',
'javelin-behavior-phabricator-gesture',
'javelin-behavior-phabricator-remarkup-assist',
'phabricator-textareautils',
'phabricator-file-upload',
'javelin-behavior-global-drag-and-drop',
'javelin-behavior-phabricator-reveal-content',
'phui-hovercard',
'javelin-behavior-phui-hovercards',
'javelin-color',
'javelin-fx',
'phabricator-draggable-list',
'javelin-behavior-phabricator-transaction-list',
'javelin-behavior-phabricator-show-older-transactions',
'javelin-behavior-phui-dropdown-menu',
'javelin-behavior-doorkeeper-tag',
'phabricator-title',
'javelin-leader',
'javelin-websocket',
'javelin-behavior-dashboard-async-panel',
'javelin-behavior-dashboard-tab-panel',
'javelin-quicksand',
'javelin-behavior-quicksand-blacklist',
'javelin-behavior-high-security-warning',
'javelin-behavior-read-only-warning',
'javelin-scrollbar',
'javelin-behavior-scrollbar',
'javelin-behavior-durable-column',
'conpherence-thread-manager',
'javelin-behavior-detect-timezone',
'javelin-behavior-setup-check-https',
'javelin-behavior-aphlict-status',
'javelin-behavior-user-menu',
'phabricator-favicon',
),
'differential.pkg.css' => array(
'differential-core-view-css',
'differential-changeset-view-css',
'differential-revision-history-css',
'differential-revision-list-css',
'differential-table-of-contents-css',
'differential-revision-comment-css',
'differential-revision-add-comment-css',
'phabricator-object-selector-css',
'phabricator-content-source-view-css',
'inline-comment-summary-css',
'phui-inline-comment-view-css',
),
'differential.pkg.js' => array(
'phabricator-drag-and-drop-file-upload',
'phabricator-shaped-request',
'javelin-behavior-differential-populate',
'javelin-behavior-differential-diff-radios',
'javelin-behavior-aphront-drag-and-drop-textarea',
'javelin-behavior-phabricator-object-selector',
'javelin-behavior-repository-crossreference',
'javelin-behavior-aphront-more',
'phabricator-diff-inline',
'phabricator-diff-changeset',
'phabricator-diff-changeset-list',
),
'diffusion.pkg.css' => array(
'diffusion-icons-css',
),
'diffusion.pkg.js' => array(
'javelin-behavior-diffusion-pull-lastmodified',
'javelin-behavior-diffusion-commit-graph',
'javelin-behavior-audit-preview',
),
'maniphest.pkg.css' => array(
'maniphest-task-summary-css',
),
'maniphest.pkg.js' => array(
'javelin-behavior-maniphest-batch-selector',
'javelin-behavior-maniphest-list-editor',
),
),
);
diff --git a/src/applications/differential/controller/DifferentialChangesetViewController.php b/src/applications/differential/controller/DifferentialChangesetViewController.php
index 80283e9eea..1dac4c137d 100644
--- a/src/applications/differential/controller/DifferentialChangesetViewController.php
+++ b/src/applications/differential/controller/DifferentialChangesetViewController.php
@@ -1,476 +1,480 @@
<?php
final class DifferentialChangesetViewController extends DifferentialController {
public function shouldAllowPublic() {
return true;
}
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$rendering_reference = $request->getStr('ref');
$parts = explode('/', $rendering_reference);
if (count($parts) == 2) {
list($id, $vs) = $parts;
} else {
$id = $parts[0];
$vs = 0;
}
$id = (int)$id;
$vs = (int)$vs;
$load_ids = array($id);
if ($vs && ($vs != -1)) {
$load_ids[] = $vs;
}
$changesets = id(new DifferentialChangesetQuery())
->setViewer($viewer)
->withIDs($load_ids)
->needHunks(true)
->execute();
$changesets = mpull($changesets, null, 'getID');
$changeset = idx($changesets, $id);
if (!$changeset) {
return new Aphront404Response();
}
$vs_changeset = null;
if ($vs && ($vs != -1)) {
$vs_changeset = idx($changesets, $vs);
if (!$vs_changeset) {
return new Aphront404Response();
}
}
$view = $request->getStr('view');
if ($view) {
$phid = idx($changeset->getMetadata(), "$view:binary-phid");
if ($phid) {
return id(new AphrontRedirectResponse())->setURI("/file/info/$phid/");
}
switch ($view) {
case 'new':
return $this->buildRawFileResponse($changeset, $is_new = true);
case 'old':
if ($vs_changeset) {
return $this->buildRawFileResponse($vs_changeset, $is_new = true);
}
return $this->buildRawFileResponse($changeset, $is_new = false);
default:
return new Aphront400Response();
}
}
$old = array();
$new = array();
if (!$vs) {
$right = $changeset;
$left = null;
$right_source = $right->getID();
$right_new = true;
$left_source = $right->getID();
$left_new = false;
$render_cache_key = $right->getID();
$old[] = $changeset;
$new[] = $changeset;
} else if ($vs == -1) {
$right = null;
$left = $changeset;
$right_source = $left->getID();
$right_new = false;
$left_source = $left->getID();
$left_new = true;
$render_cache_key = null;
$old[] = $changeset;
$new[] = $changeset;
} else {
$right = $changeset;
$left = $vs_changeset;
$right_source = $right->getID();
$right_new = true;
$left_source = $left->getID();
$left_new = true;
$render_cache_key = null;
$new[] = $left;
$new[] = $right;
}
if ($left) {
$left_data = $left->makeNewFile();
$left_properties = $left->getNewProperties();
if ($right) {
$right_data = $right->makeNewFile();
$right_properties = $right->getNewProperties();
} else {
$right_data = $left->makeOldFile();
$right_properties = $left->getOldProperties();
}
$engine = new PhabricatorDifferenceEngine();
$synthetic = $engine->generateChangesetFromFileContent(
$left_data,
$right_data);
$choice = clone nonempty($left, $right);
$choice->attachHunks($synthetic->getHunks());
$choice->setOldProperties($left_properties);
$choice->setNewProperties($right_properties);
$changeset = $choice;
}
if ($left_new || $right_new) {
$diff_map = array();
if ($left) {
$diff_map[] = $left->getDiff();
}
if ($right) {
$diff_map[] = $right->getDiff();
}
$diff_map = mpull($diff_map, null, 'getPHID');
$buildables = id(new HarbormasterBuildableQuery())
->setViewer($viewer)
->withBuildablePHIDs(array_keys($diff_map))
->withManualBuildables(false)
->needBuilds(true)
->needTargets(true)
->execute();
$buildables = mpull($buildables, null, 'getBuildablePHID');
foreach ($diff_map as $diff_phid => $changeset_diff) {
$changeset_diff->attachBuildable(idx($buildables, $diff_phid));
}
}
$coverage = null;
if ($right_new) {
$coverage = $this->loadCoverage($right);
}
$spec = $request->getStr('range');
list($range_s, $range_e, $mask) =
DifferentialChangesetParser::parseRangeSpecification($spec);
$diff = $changeset->getDiff();
$revision_id = $diff->getRevisionID();
$can_mark = false;
$object_owner_phid = null;
$revision = null;
if ($revision_id) {
$revision = id(new DifferentialRevisionQuery())
->setViewer($viewer)
->withIDs(array($revision_id))
->executeOne();
if ($revision) {
$can_mark = ($revision->getAuthorPHID() == $viewer->getPHID());
$object_owner_phid = $revision->getAuthorPHID();
}
}
if ($revision) {
$container_phid = $revision->getPHID();
} else {
$container_phid = $diff->getPHID();
}
$viewstate_engine = id(new PhabricatorChangesetViewStateEngine())
->setViewer($viewer)
->setObjectPHID($container_phid)
->setChangeset($changeset);
$viewstate = $viewstate_engine->newViewStateFromRequest($request);
+ if ($viewstate->getDiscardResponse()) {
+ return new AphrontAjaxResponse();
+ }
+
$parser = id(new DifferentialChangesetParser())
->setViewer($viewer)
->setViewState($viewstate)
->setCoverage($coverage)
->setChangeset($changeset)
->setRenderingReference($rendering_reference)
->setRenderCacheKey($render_cache_key)
->setRightSideCommentMapping($right_source, $right_new)
->setLeftSideCommentMapping($left_source, $left_new);
if ($left && $right) {
$parser->setOriginals($left, $right);
}
// Load both left-side and right-side inline comments.
if ($revision) {
$query = id(new DifferentialInlineCommentQuery())
->setViewer($viewer)
->needHidden(true)
->withRevisionPHIDs(array($revision->getPHID()));
$inlines = $query->execute();
$inlines = $query->adjustInlinesForChangesets(
$inlines,
$old,
$new,
$revision);
} else {
$inlines = array();
}
if ($left_new) {
$inlines = array_merge(
$inlines,
$this->buildLintInlineComments($left));
}
if ($right_new) {
$inlines = array_merge(
$inlines,
$this->buildLintInlineComments($right));
}
$phids = array();
foreach ($inlines as $inline) {
$parser->parseInlineComment($inline);
if ($inline->getAuthorPHID()) {
$phids[$inline->getAuthorPHID()] = true;
}
}
$phids = array_keys($phids);
$handles = $this->loadViewerHandles($phids);
$parser->setHandles($handles);
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($viewer);
foreach ($inlines as $inline) {
$engine->addObject(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
}
$engine->process();
$parser
->setViewer($viewer)
->setMarkupEngine($engine)
->setShowEditAndReplyLinks(true)
->setCanMarkDone($can_mark)
->setObjectOwnerPHID($object_owner_phid)
->setRange($range_s, $range_e)
->setMask($mask);
if ($request->isAjax()) {
// NOTE: We must render the changeset before we render coverage
// information, since it builds some caches.
$response = $parser->newChangesetResponse();
$mcov = $parser->renderModifiedCoverage();
$coverage_data = array(
'differential-mcoverage-'.md5($changeset->getFilename()) => $mcov,
);
$response->setCoverage($coverage_data);
return $response;
}
$detail = id(new DifferentialChangesetListView())
->setUser($this->getViewer())
->setChangesets(array($changeset))
->setVisibleChangesets(array($changeset))
->setRenderingReferences(array($rendering_reference))
->setRenderURI('/differential/changeset/')
->setDiff($diff)
->setTitle(pht('Standalone View'))
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
->setIsStandalone(true)
->setParser($parser);
if ($revision_id) {
$detail->setInlineCommentControllerURI(
'/differential/comment/inline/edit/'.$revision_id.'/');
}
$crumbs = $this->buildApplicationCrumbs();
if ($revision_id) {
$crumbs->addTextCrumb('D'.$revision_id, '/D'.$revision_id);
}
$diff_id = $diff->getID();
if ($diff_id) {
$crumbs->addTextCrumb(
pht('Diff %d', $diff_id),
$this->getApplicationURI('diff/'.$diff_id));
}
$crumbs->addTextCrumb($changeset->getDisplayFilename());
$crumbs->setBorder(true);
$header = id(new PHUIHeaderView())
->setHeader(pht('Changeset View'))
->setHeaderIcon('fa-gear');
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setFooter($detail);
return $this->newPage()
->setTitle(pht('Changeset View'))
->setCrumbs($crumbs)
->appendChild($view);
}
private function buildRawFileResponse(
DifferentialChangeset $changeset,
$is_new) {
$viewer = $this->getViewer();
if ($is_new) {
$key = 'raw:new:phid';
} else {
$key = 'raw:old:phid';
}
$metadata = $changeset->getMetadata();
$file = null;
$phid = idx($metadata, $key);
if ($phid) {
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($phid))
->execute();
if ($file) {
$file = head($file);
}
}
if (!$file) {
// This is just building a cache of the changeset content in the file
// tool, and is safe to run on a read pathway.
$unguard = AphrontWriteGuard::beginScopedUnguardedWrites();
if ($is_new) {
$data = $changeset->makeNewFile();
} else {
$data = $changeset->makeOldFile();
}
$diff = $changeset->getDiff();
$file = PhabricatorFile::newFromFileData(
$data,
array(
'name' => $changeset->getFilename(),
'mime-type' => 'text/plain',
'ttl.relative' => phutil_units('24 hours in seconds'),
'viewPolicy' => PhabricatorPolicies::POLICY_NOONE,
));
$file->attachToObject($diff->getPHID());
$metadata[$key] = $file->getPHID();
$changeset->setMetadata($metadata);
$changeset->save();
unset($unguard);
}
return $file->getRedirectResponse();
}
private function buildLintInlineComments($changeset) {
$diff = $changeset->getDiff();
$target_phids = $diff->getBuildTargetPHIDs();
if (!$target_phids) {
return array();
}
$messages = id(new HarbormasterBuildLintMessage())->loadAllWhere(
'buildTargetPHID IN (%Ls) AND path = %s',
$target_phids,
$changeset->getFilename());
if (!$messages) {
return array();
}
$change_type = $changeset->getChangeType();
if (DifferentialChangeType::isDeleteChangeType($change_type)) {
// If this is a lint message on a deleted file, show it on the left
// side of the UI because there are no source code lines on the right
// side of the UI so inlines don't have anywhere to render. See PHI416.
$is_new = 0;
} else {
$is_new = 1;
}
$template = id(new DifferentialInlineComment())
->setChangesetID($changeset->getID())
->setIsNewFile($is_new)
->setLineLength(0);
$inlines = array();
foreach ($messages as $message) {
$description = $message->getProperty('description');
$inlines[] = id(clone $template)
->setSyntheticAuthor(pht('Lint: %s', $message->getName()))
->setLineNumber($message->getLine())
->setContent($description);
}
return $inlines;
}
private function loadCoverage(DifferentialChangeset $changeset) {
$viewer = $this->getViewer();
$target_phids = $changeset->getDiff()->getBuildTargetPHIDs();
if (!$target_phids) {
return null;
}
$unit = id(new HarbormasterBuildUnitMessageQuery())
->setViewer($viewer)
->withBuildTargetPHIDs($target_phids)
->execute();
if (!$unit) {
return null;
}
$coverage = array();
foreach ($unit as $message) {
$test_coverage = $message->getProperty('coverage');
if ($test_coverage === null) {
continue;
}
$coverage_data = idx($test_coverage, $changeset->getFileName());
if (!strlen($coverage_data)) {
continue;
}
$coverage[] = $coverage_data;
}
if (!$coverage) {
return null;
}
return ArcanistUnitTestResult::mergeCoverage($coverage);
}
}
diff --git a/src/applications/differential/parser/DifferentialChangesetParser.php b/src/applications/differential/parser/DifferentialChangesetParser.php
index e9ae71d63e..870fd9af6b 100644
--- a/src/applications/differential/parser/DifferentialChangesetParser.php
+++ b/src/applications/differential/parser/DifferentialChangesetParser.php
@@ -1,1905 +1,1906 @@
<?php
final class DifferentialChangesetParser extends Phobject {
const HIGHLIGHT_BYTE_LIMIT = 262144;
protected $visible = array();
protected $new = array();
protected $old = array();
protected $intra = array();
protected $depthOnlyLines = array();
protected $newRender = null;
protected $oldRender = null;
protected $filename = null;
protected $hunkStartLines = array();
protected $comments = array();
protected $specialAttributes = array();
protected $changeset;
protected $renderCacheKey = null;
private $handles = array();
private $user;
private $leftSideChangesetID;
private $leftSideAttachesToNewFile;
private $rightSideChangesetID;
private $rightSideAttachesToNewFile;
private $originalLeft;
private $originalRight;
private $renderingReference;
private $isSubparser;
private $isTopLevel;
private $coverage;
private $markupEngine;
private $highlightErrors;
private $disableCache;
private $renderer;
private $highlightingDisabled;
private $showEditAndReplyLinks = true;
private $canMarkDone;
private $objectOwnerPHID;
private $offsetMode;
private $rangeStart;
private $rangeEnd;
private $mask;
private $linesOfContext = 8;
private $highlightEngine;
private $viewer;
private $viewState;
public function setRange($start, $end) {
$this->rangeStart = $start;
$this->rangeEnd = $end;
return $this;
}
public function setMask(array $mask) {
$this->mask = $mask;
return $this;
}
public function renderChangeset() {
return $this->render($this->rangeStart, $this->rangeEnd, $this->mask);
}
public function setShowEditAndReplyLinks($bool) {
$this->showEditAndReplyLinks = $bool;
return $this;
}
public function getShowEditAndReplyLinks() {
return $this->showEditAndReplyLinks;
}
public function setViewState(PhabricatorChangesetViewState $view_state) {
$this->viewState = $view_state;
return $this;
}
public function getViewState() {
return $this->viewState;
}
public function setRenderer(DifferentialChangesetRenderer $renderer) {
$this->renderer = $renderer;
return $this;
}
public function getRenderer() {
return $this->renderer;
}
public function setDisableCache($disable_cache) {
$this->disableCache = $disable_cache;
return $this;
}
public function getDisableCache() {
return $this->disableCache;
}
public function setCanMarkDone($can_mark_done) {
$this->canMarkDone = $can_mark_done;
return $this;
}
public function getCanMarkDone() {
return $this->canMarkDone;
}
public function setObjectOwnerPHID($phid) {
$this->objectOwnerPHID = $phid;
return $this;
}
public function getObjectOwnerPHID() {
return $this->objectOwnerPHID;
}
public function setOffsetMode($offset_mode) {
$this->offsetMode = $offset_mode;
return $this;
}
public function getOffsetMode() {
return $this->offsetMode;
}
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
private function newRenderer() {
$viewer = $this->getViewer();
$viewstate = $this->getViewstate();
$renderer_key = $viewstate->getRendererKey();
if ($renderer_key === null) {
$is_unified = $viewer->compareUserSetting(
PhabricatorUnifiedDiffsSetting::SETTINGKEY,
PhabricatorUnifiedDiffsSetting::VALUE_ALWAYS_UNIFIED);
if ($is_unified) {
$renderer_key = '1up';
} else {
$renderer_key = $viewstate->getDefaultDeviceRendererKey();
}
}
switch ($renderer_key) {
case '1up':
$renderer = new DifferentialChangesetOneUpRenderer();
break;
default:
$renderer = new DifferentialChangesetTwoUpRenderer();
break;
}
return $renderer;
}
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_MOVEAWAY = 'attr:moveaway';
public function setOldLines(array $lines) {
$this->old = $lines;
return $this;
}
public function setNewLines(array $lines) {
$this->new = $lines;
return $this;
}
public function setSpecialAttributes(array $attributes) {
$this->specialAttributes = $attributes;
return $this;
}
public function setIntraLineDiffs(array $diffs) {
$this->intra = $diffs;
return $this;
}
public function setDepthOnlyLines(array $lines) {
$this->depthOnlyLines = $lines;
return $this;
}
public function getDepthOnlyLines() {
return $this->depthOnlyLines;
}
public function setVisibleLinesMask(array $mask) {
$this->visible = $mask;
return $this;
}
public function setLinesOfContext($lines_of_context) {
$this->linesOfContext = $lines_of_context;
return $this;
}
public function getLinesOfContext() {
return $this->linesOfContext;
}
/**
* Configure which Changeset comments added to the right side of the visible
* diff will be attached to. The ID must be the ID of a real Differential
* Changeset.
*
* The complexity here is that we may show an arbitrary side of an arbitrary
* changeset as either the left or right part of a diff. This method allows
* the left and right halves of the displayed diff to be correctly mapped to
* storage changesets.
*
* @param id The Differential Changeset ID that comments added to the right
* side of the visible diff should be attached to.
* @param bool If true, attach new comments to the right side of the storage
* changeset. Note that this may be false, if the left side of
* some storage changeset is being shown as the right side of
* a display diff.
* @return this
*/
public function setRightSideCommentMapping($id, $is_new) {
$this->rightSideChangesetID = $id;
$this->rightSideAttachesToNewFile = $is_new;
return $this;
}
/**
* See setRightSideCommentMapping(), but this sets information for the left
* side of the display diff.
*/
public function setLeftSideCommentMapping($id, $is_new) {
$this->leftSideChangesetID = $id;
$this->leftSideAttachesToNewFile = $is_new;
return $this;
}
public function setOriginals(
DifferentialChangeset $left,
DifferentialChangeset $right) {
$this->originalLeft = $left;
$this->originalRight = $right;
return $this;
}
public function diffOriginals() {
$engine = new PhabricatorDifferenceEngine();
$changeset = $engine->generateChangesetFromFileContent(
implode('', mpull($this->originalLeft->getHunks(), 'getChanges')),
implode('', mpull($this->originalRight->getHunks(), 'getChanges')));
$parser = new DifferentialHunkParser();
return $parser->parseHunksForHighlightMasks(
$changeset->getHunks(),
$this->originalLeft->getHunks(),
$this->originalRight->getHunks());
}
/**
* Set a key for identifying this changeset in the render cache. If set, the
* parser will attempt to use the changeset render cache, which can improve
* performance for frequently-viewed changesets.
*
* By default, there is no render cache key and parsers do not use the cache.
* This is appropriate for rarely-viewed changesets.
*
* NOTE: Currently, this key must be a valid Differential Changeset ID.
*
* @param string Key for identifying this changeset in the render cache.
* @return this
*/
public function setRenderCacheKey($key) {
$this->renderCacheKey = $key;
return $this;
}
private function getRenderCacheKey() {
return $this->renderCacheKey;
}
public function setChangeset(DifferentialChangeset $changeset) {
$this->changeset = $changeset;
$this->setFilename($changeset->getFilename());
return $this;
}
public function setRenderingReference($ref) {
$this->renderingReference = $ref;
return $this;
}
private function getRenderingReference() {
return $this->renderingReference;
}
public function getChangeset() {
return $this->changeset;
}
public function setFilename($filename) {
$this->filename = $filename;
return $this;
}
public function setHandles(array $handles) {
assert_instances_of($handles, 'PhabricatorObjectHandle');
$this->handles = $handles;
return $this;
}
public function setMarkupEngine(PhabricatorMarkupEngine $engine) {
$this->markupEngine = $engine;
return $this;
}
public function setCoverage($coverage) {
$this->coverage = $coverage;
return $this;
}
private function getCoverage() {
return $this->coverage;
}
public function parseInlineComment(
PhabricatorInlineCommentInterface $comment) {
// Parse only comments which are actually visible.
if ($this->isCommentVisibleOnRenderedDiff($comment)) {
$this->comments[] = $comment;
}
return $this;
}
private function loadCache() {
$render_cache_key = $this->getRenderCacheKey();
if (!$render_cache_key) {
return false;
}
$data = null;
$changeset = new DifferentialChangeset();
$conn_r = $changeset->establishConnection('r');
$data = queryfx_one(
$conn_r,
'SELECT * FROM %T WHERE id = %d',
$changeset->getTableName().'_parse_cache',
$render_cache_key);
if (!$data) {
return false;
}
if ($data['cache'][0] == '{') {
// This is likely an old-style JSON cache which we will not be able to
// deserialize.
return false;
}
$data = unserialize($data['cache']);
if (!is_array($data) || !$data) {
return false;
}
foreach (self::getCacheableProperties() as $cache_key) {
if (!array_key_exists($cache_key, $data)) {
// If we're missing a cache key, assume we're looking at an old cache
// and ignore it.
return false;
}
}
if ($data['cacheVersion'] !== self::CACHE_VERSION) {
return false;
}
// Someone displays contents of a partially cached shielded file.
if (!isset($data['newRender']) && (!$this->isTopLevel || $this->comments)) {
return false;
}
unset($data['cacheVersion'], $data['cacheHost']);
$cache_prop = array_select_keys($data, self::getCacheableProperties());
foreach ($cache_prop as $cache_key => $v) {
$this->$cache_key = $v;
}
return true;
}
protected static function getCacheableProperties() {
return array(
'visible',
'new',
'old',
'intra',
'depthOnlyLines',
'newRender',
'oldRender',
'specialAttributes',
'hunkStartLines',
'cacheVersion',
'cacheHost',
'highlightingDisabled',
);
}
public function saveCache() {
if (PhabricatorEnv::isReadOnly()) {
return false;
}
if ($this->highlightErrors) {
return false;
}
$render_cache_key = $this->getRenderCacheKey();
if (!$render_cache_key) {
return false;
}
$cache = array();
foreach (self::getCacheableProperties() as $cache_key) {
switch ($cache_key) {
case 'cacheVersion':
$cache[$cache_key] = self::CACHE_VERSION;
break;
case 'cacheHost':
$cache[$cache_key] = php_uname('n');
break;
default:
$cache[$cache_key] = $this->$cache_key;
break;
}
}
$cache = serialize($cache);
// We don't want to waste too much space by a single changeset.
if (strlen($cache) > self::CACHE_MAX_SIZE) {
return;
}
$changeset = new DifferentialChangeset();
$conn_w = $changeset->establishConnection('w');
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
try {
queryfx(
$conn_w,
'INSERT INTO %T (id, cache, dateCreated) VALUES (%d, %B, %d)
ON DUPLICATE KEY UPDATE cache = VALUES(cache)',
DifferentialChangeset::TABLE_CACHE,
$render_cache_key,
$cache,
time());
} catch (AphrontQueryException $ex) {
// Ignore these exceptions. A common cause is that the cache is
// larger than 'max_allowed_packet', in which case we're better off
// not writing it.
// TODO: It would be nice to tailor this more narrowly.
}
unset($unguarded);
}
private function markGenerated($new_corpus_block = '') {
$generated_guess = (strpos($new_corpus_block, '@'.'generated') !== false);
if (!$generated_guess) {
$generated_path_regexps = PhabricatorEnv::getEnvConfig(
'differential.generated-paths');
foreach ($generated_path_regexps as $regexp) {
if (preg_match($regexp, $this->changeset->getFilename())) {
$generated_guess = true;
break;
}
}
}
$event = new PhabricatorEvent(
PhabricatorEventType::TYPE_DIFFERENTIAL_WILLMARKGENERATED,
array(
'corpus' => $new_corpus_block,
'is_generated' => $generated_guess,
)
);
PhutilEventEngine::dispatchEvent($event);
$generated = $event->getValue('is_generated');
$attribute = $this->changeset->isGeneratedChangeset();
if ($attribute) {
$generated = true;
}
$this->specialAttributes[self::ATTR_GENERATED] = $generated;
}
public function isGenerated() {
return idx($this->specialAttributes, self::ATTR_GENERATED, false);
}
public function isDeleted() {
return idx($this->specialAttributes, self::ATTR_DELETED, false);
}
public function isUnchanged() {
return idx($this->specialAttributes, self::ATTR_UNCHANGED, false);
}
public function isMoveAway() {
return idx($this->specialAttributes, self::ATTR_MOVEAWAY, false);
}
private function applyIntraline(&$render, $intra, $corpus) {
foreach ($render as $key => $text) {
$result = $text;
if (isset($intra[$key])) {
$result = PhabricatorDifferenceEngine::applyIntralineDiff(
$result,
$intra[$key]);
}
$result = $this->adjustRenderedLineForDisplay($result);
$render[$key] = $result;
}
}
private function getHighlightFuture($corpus) {
$language = $this->getViewState()->getHighlightLanguage();
if (!$language) {
$language = $this->highlightEngine->getLanguageFromFilename(
$this->filename);
if (($language != 'txt') &&
(strlen($corpus) > self::HIGHLIGHT_BYTE_LIMIT)) {
$this->highlightingDisabled = true;
$language = 'txt';
}
}
return $this->highlightEngine->getHighlightFuture(
$language,
$corpus);
}
protected function processHighlightedSource($data, $result) {
$result_lines = phutil_split_lines($result);
foreach ($data as $key => $info) {
if (!$info) {
unset($result_lines[$key]);
}
}
return $result_lines;
}
private function tryCacheStuff() {
$viewstate = $this->getViewState();
$skip_cache = false;
if ($this->disableCache) {
$skip_cache = true;
}
$character_encoding = $viewstate->getCharacterEncoding();
if ($character_encoding !== null) {
$skip_cache = true;
}
$highlight_language = $viewstate->getHighlightLanguage();
if ($highlight_language !== null) {
$skip_cache = true;
}
$changeset = $this->changeset;
if ($changeset->getFileType() != DifferentialChangeType::FILE_TEXT &&
$changeset->getFileType() != DifferentialChangeType::FILE_SYMLINK) {
$this->markGenerated();
} else {
if ($skip_cache || !$this->loadCache()) {
$this->process();
if (!$skip_cache) {
$this->saveCache();
}
}
}
}
private function process() {
$changeset = $this->changeset;
$hunk_parser = new DifferentialHunkParser();
$hunk_parser->parseHunksForLineData($changeset->getHunks());
$this->realignDiff($changeset, $hunk_parser);
$hunk_parser->reparseHunksForSpecialAttributes();
$unchanged = false;
if (!$hunk_parser->getHasAnyChanges()) {
$filetype = $this->changeset->getFileType();
if ($filetype == DifferentialChangeType::FILE_TEXT ||
$filetype == DifferentialChangeType::FILE_SYMLINK) {
$unchanged = true;
}
}
$moveaway = false;
$changetype = $this->changeset->getChangeType();
if ($changetype == DifferentialChangeType::TYPE_MOVE_AWAY) {
$moveaway = true;
}
$this->setSpecialAttributes(array(
self::ATTR_UNCHANGED => $unchanged,
self::ATTR_DELETED => $hunk_parser->getIsDeleted(),
self::ATTR_MOVEAWAY => $moveaway,
));
$lines_context = $this->getLinesOfContext();
$hunk_parser->generateIntraLineDiffs();
$hunk_parser->generateVisibleLinesMask($lines_context);
$this->setOldLines($hunk_parser->getOldLines());
$this->setNewLines($hunk_parser->getNewLines());
$this->setIntraLineDiffs($hunk_parser->getIntraLineDiffs());
$this->setDepthOnlyLines($hunk_parser->getDepthOnlyLines());
$this->setVisibleLinesMask($hunk_parser->getVisibleLinesMask());
$this->hunkStartLines = $hunk_parser->getHunkStartLines(
$changeset->getHunks());
$new_corpus = $hunk_parser->getNewCorpus();
$new_corpus_block = implode('', $new_corpus);
$this->markGenerated($new_corpus_block);
if ($this->isTopLevel &&
!$this->comments &&
($this->isGenerated() ||
$this->isUnchanged() ||
$this->isDeleted())) {
return;
}
$old_corpus = $hunk_parser->getOldCorpus();
$old_corpus_block = implode('', $old_corpus);
$old_future = $this->getHighlightFuture($old_corpus_block);
$new_future = $this->getHighlightFuture($new_corpus_block);
$futures = array(
'old' => $old_future,
'new' => $new_future,
);
$corpus_blocks = array(
'old' => $old_corpus_block,
'new' => $new_corpus_block,
);
$this->highlightErrors = false;
foreach (new FutureIterator($futures) as $key => $future) {
try {
try {
$highlighted = $future->resolve();
} catch (PhutilSyntaxHighlighterException $ex) {
$this->highlightErrors = true;
$highlighted = id(new PhutilDefaultSyntaxHighlighter())
->getHighlightFuture($corpus_blocks[$key])
->resolve();
}
switch ($key) {
case 'old':
$this->oldRender = $this->processHighlightedSource(
$this->old,
$highlighted);
break;
case 'new':
$this->newRender = $this->processHighlightedSource(
$this->new,
$highlighted);
break;
}
} catch (Exception $ex) {
phlog($ex);
throw $ex;
}
}
$this->applyIntraline(
$this->oldRender,
ipull($this->intra, 0),
$old_corpus);
$this->applyIntraline(
$this->newRender,
ipull($this->intra, 1),
$new_corpus);
}
private function shouldRenderPropertyChangeHeader($changeset) {
if (!$this->isTopLevel) {
// We render properties only at top level; otherwise we get multiple
// copies of them when a user clicks "Show More".
return false;
}
return true;
}
public function render(
$range_start = null,
$range_len = null,
$mask_force = array()) {
$renderer = $this->getRenderer();
if (!$renderer) {
$renderer = $this->newRenderer();
$this->setRenderer($renderer);
}
// "Top level" renders are initial requests for the whole file, versus
// requests for a specific range generated by clicking "show more". We
// generate property changes and "shield" UI elements only for toplevel
// requests.
$this->isTopLevel = (($range_start === null) && ($range_len === null));
$this->highlightEngine = PhabricatorSyntaxHighlighter::newEngine();
$viewstate = $this->getViewState();
$encoding = null;
$character_encoding = $viewstate->getCharacterEncoding();
if ($character_encoding) {
// We are forcing this changeset to be interpreted with a specific
// character encoding, so force all the hunks into that encoding and
// propagate it to the renderer.
$encoding = $character_encoding;
foreach ($this->changeset->getHunks() as $hunk) {
$hunk->forceEncoding($character_encoding);
}
} else {
// We're just using the default, so tell the renderer what that is
// (by reading the encoding from the first hunk).
foreach ($this->changeset->getHunks() as $hunk) {
$encoding = $hunk->getDataEncoding();
break;
}
}
$this->tryCacheStuff();
// If we're rendering in an offset mode, treat the range numbers as line
// numbers instead of rendering offsets.
$offset_mode = $this->getOffsetMode();
if ($offset_mode) {
if ($offset_mode == 'new') {
$offset_map = $this->new;
} else {
$offset_map = $this->old;
}
// NOTE: Inline comments use zero-based lengths. For example, a comment
// that starts and ends on line 123 has length 0. Rendering considers
// this range to have length 1. Probably both should agree, but that
// ship likely sailed long ago. Tweak things here to get the two systems
// to agree. See PHI985, where this affected mail rendering of inline
// comments left on the final line of a file.
$range_end = $this->getOffset($offset_map, $range_start + $range_len);
$range_start = $this->getOffset($offset_map, $range_start);
$range_len = ($range_end - $range_start) + 1;
}
$render_pch = $this->shouldRenderPropertyChangeHeader($this->changeset);
$rows = max(
count($this->old),
count($this->new));
$renderer = $this->getRenderer()
->setUser($this->getViewer())
->setChangeset($this->changeset)
->setRenderPropertyChangeHeader($render_pch)
->setIsTopLevel($this->isTopLevel)
->setOldRender($this->oldRender)
->setNewRender($this->newRender)
->setHunkStartLines($this->hunkStartLines)
->setOldChangesetID($this->leftSideChangesetID)
->setNewChangesetID($this->rightSideChangesetID)
->setOldAttachesToNewFile($this->leftSideAttachesToNewFile)
->setNewAttachesToNewFile($this->rightSideAttachesToNewFile)
->setCodeCoverage($this->getCoverage())
->setRenderingReference($this->getRenderingReference())
->setMarkupEngine($this->markupEngine)
->setHandles($this->handles)
->setOldLines($this->old)
->setNewLines($this->new)
->setOriginalCharacterEncoding($encoding)
->setShowEditAndReplyLinks($this->getShowEditAndReplyLinks())
->setCanMarkDone($this->getCanMarkDone())
->setObjectOwnerPHID($this->getObjectOwnerPHID())
->setHighlightingDisabled($this->highlightingDisabled)
->setDepthOnlyLines($this->getDepthOnlyLines());
list($engine, $old_ref, $new_ref) = $this->newDocumentEngine();
if ($engine) {
$engine_blocks = $engine->newEngineBlocks(
$old_ref,
$new_ref);
} else {
$engine_blocks = null;
}
$has_document_engine = ($engine_blocks !== null);
// See T13515. Sometimes, we collapse file content by default: for
// example, if the file is marked as containing generated code.
// If a file has inline comments, that normally means we never collapse
// it. However, if the viewer has already collapsed all of the inlines,
// it's fine to collapse the file.
$expanded_comments = array();
foreach ($this->comments as $comment) {
if ($comment->isHidden()) {
continue;
}
$expanded_comments[] = $comment;
}
$collapsed_count = (count($this->comments) - count($expanded_comments));
$shield_raw = null;
$shield_text = null;
$shield_type = null;
if ($this->isTopLevel && !$expanded_comments && !$has_document_engine) {
if ($this->isGenerated()) {
$shield_text = pht(
'This file contains generated code, which does not normally '.
'need to be reviewed.');
} else if ($this->isMoveAway()) {
// We put an empty shield on these files. Normally, they do not have
// any diff content anyway. However, if they come through `arc`, they
// may have content. We don't want to show it (it's not useful) and
// we bailed out of fully processing it earlier anyway.
// We could show a message like "this file was moved", but we show
// that as a change header anyway, so it would be redundant. Instead,
// just render an empty shield to skip rendering the diff body.
$shield_raw = '';
} else if ($this->isUnchanged()) {
$type = 'text';
if (!$rows) {
// NOTE: Normally, diffs which don't change files do not include
// file content (for example, if you "chmod +x" a file and then
// run "git show", the file content is not available). Similarly,
// if you move a file from A to B without changing it, diffs normally
// do not show the file content. In some cases `arc` is able to
// synthetically generate content for these diffs, but for raw diffs
// we'll never have it so we need to be prepared to not render a link.
$type = 'none';
}
$shield_type = $type;
$type_add = DifferentialChangeType::TYPE_ADD;
if ($this->changeset->getChangeType() == $type_add) {
// Although the generic message is sort of accurate in a technical
// sense, this more-tailored message is less confusing.
$shield_text = pht('This is an empty file.');
} else {
$shield_text = pht('The contents of this file were not changed.');
}
} else if ($this->isDeleted()) {
$shield_text = pht('This file was completely deleted.');
} else if ($this->changeset->getAffectedLineCount() > 2500) {
$shield_text = pht(
'This file has a very large number of changes (%s lines).',
new PhutilNumber($this->changeset->getAffectedLineCount()));
}
}
$shield = null;
if ($shield_raw !== null) {
$shield = $shield_raw;
} else if ($shield_text !== null) {
if ($shield_type === null) {
$shield_type = 'default';
}
// If we have inlines and the shield would normally show the whole file,
// downgrade it to show only text around the inlines.
if ($collapsed_count) {
if ($shield_type === 'text') {
$shield_type = 'default';
}
$shield_text = array(
$shield_text,
' ',
pht(
'This file has %d collapsed inline comment(s).',
new PhutilNumber($collapsed_count)),
);
}
$shield = $renderer->renderShield($shield_text, $shield_type);
}
if ($shield !== null) {
return $renderer->renderChangesetTable($shield);
}
// This request should render the "undershield" headers if it's a top-level
// request which made it this far (indicating the changeset has no shield)
// or it's a request with no mask information (indicating it's the request
// that removes the rendering shield). Possibly, this second class of
// request might need to be made more explicit.
$is_undershield = (empty($mask_force) || $this->isTopLevel);
$renderer->setIsUndershield($is_undershield);
$old_comments = array();
$new_comments = array();
$old_mask = array();
$new_mask = array();
$feedback_mask = array();
$lines_context = $this->getLinesOfContext();
if ($this->comments) {
// If there are any comments which appear in sections of the file which
// we don't have, we're going to move them backwards to the closest
// earlier line. Two cases where this may happen are:
//
// - Porting ghost comments forward into a file which was mostly
// deleted.
// - Porting ghost comments forward from a full-context diff to a
// partial-context diff.
list($old_backmap, $new_backmap) = $this->buildLineBackmaps();
foreach ($this->comments as $comment) {
$new_side = $this->isCommentOnRightSideWhenDisplayed($comment);
$line = $comment->getLineNumber();
if ($new_side) {
$back_line = $new_backmap[$line];
} else {
$back_line = $old_backmap[$line];
}
if ($back_line != $line) {
// TODO: This should probably be cleaner, but just be simple and
// obvious for now.
$ghost = $comment->getIsGhost();
if ($ghost) {
$moved = pht(
'This comment originally appeared on line %s, but that line '.
'does not exist in this version of the diff. It has been '.
'moved backward to the nearest line.',
new PhutilNumber($line));
$ghost['reason'] = $ghost['reason']."\n\n".$moved;
$comment->setIsGhost($ghost);
}
$comment->setLineNumber($back_line);
$comment->setLineLength(0);
}
$start = max($comment->getLineNumber() - $lines_context, 0);
$end = $comment->getLineNumber() +
$comment->getLineLength() +
$lines_context;
for ($ii = $start; $ii <= $end; $ii++) {
if ($new_side) {
$new_mask[$ii] = true;
} else {
$old_mask[$ii] = true;
}
}
}
foreach ($this->old as $ii => $old) {
if (isset($old['line']) && isset($old_mask[$old['line']])) {
$feedback_mask[$ii] = true;
}
}
foreach ($this->new as $ii => $new) {
if (isset($new['line']) && isset($new_mask[$new['line']])) {
$feedback_mask[$ii] = true;
}
}
$this->comments = id(new PHUIDiffInlineThreader())
->reorderAndThreadCommments($this->comments);
foreach ($this->comments as $comment) {
$final = $comment->getLineNumber() +
$comment->getLineLength();
$final = max(1, $final);
if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
$new_comments[$final][] = $comment;
} else {
$old_comments[$final][] = $comment;
}
}
}
$renderer
->setOldComments($old_comments)
->setNewComments($new_comments);
if ($engine_blocks !== null) {
$reference = $this->getRenderingReference();
$parts = explode('/', $reference);
if (count($parts) == 2) {
list($id, $vs) = $parts;
} else {
$id = $parts[0];
$vs = 0;
}
// If we don't have an explicit "vs" changeset, it's the left side of
// the "id" changeset.
if (!$vs) {
$vs = $id;
}
$renderer
->setDocumentEngine($engine)
->setDocumentEngineBlocks($engine_blocks);
return $renderer->renderDocumentEngineBlocks(
$engine_blocks,
(string)$id,
(string)$vs);
}
// If we've made it here with a type of file we don't know how to render,
// bail out with a default empty rendering. Normally, we'd expect a
// document engine to catch these changes before we make it this far.
switch ($this->changeset->getFileType()) {
case DifferentialChangeType::FILE_DIRECTORY:
case DifferentialChangeType::FILE_BINARY:
case DifferentialChangeType::FILE_IMAGE:
$output = $renderer->renderChangesetTable(null);
return $output;
}
if ($this->originalLeft && $this->originalRight) {
list($highlight_old, $highlight_new) = $this->diffOriginals();
$highlight_old = array_flip($highlight_old);
$highlight_new = array_flip($highlight_new);
$renderer
->setHighlightOld($highlight_old)
->setHighlightNew($highlight_new);
}
$renderer
->setOriginalOld($this->originalLeft)
->setOriginalNew($this->originalRight);
if ($range_start === null) {
$range_start = 0;
}
if ($range_len === null) {
$range_len = $rows;
}
$range_len = min($range_len, $rows - $range_start);
list($gaps, $mask) = $this->calculateGapsAndMask(
$mask_force,
$feedback_mask,
$range_start,
$range_len);
$renderer
->setGaps($gaps)
->setMask($mask);
$html = $renderer->renderTextChange(
$range_start,
$range_len,
$rows);
return $renderer->renderChangesetTable($html);
}
/**
* This function calculates a lot of stuff we need to know to display
* the diff:
*
* Gaps - compute gaps in the visible display diff, where we will render
* "Show more context" spacers. If a gap is smaller than the context size,
* we just display it. Otherwise, we record it into $gaps and will render a
* "show more context" element instead of diff text below. A given $gap
* is a tuple of $gap_line_number_start and $gap_length.
*
* Mask - compute the actual lines that need to be shown (because they
* are near changes lines, near inline comments, or the request has
* explicitly asked for them, i.e. resulting from the user clicking
* "show more"). The $mask returned is a sparsely populated dictionary
* of $visible_line_number => true.
*
* @return array($gaps, $mask)
*/
private function calculateGapsAndMask(
$mask_force,
$feedback_mask,
$range_start,
$range_len) {
$lines_context = $this->getLinesOfContext();
$gaps = array();
$gap_start = 0;
$in_gap = false;
$base_mask = $this->visible + $mask_force + $feedback_mask;
$base_mask[$range_start + $range_len] = true;
for ($ii = $range_start; $ii <= $range_start + $range_len; $ii++) {
if (isset($base_mask[$ii])) {
if ($in_gap) {
$gap_length = $ii - $gap_start;
if ($gap_length <= $lines_context) {
for ($jj = $gap_start; $jj <= $gap_start + $gap_length; $jj++) {
$base_mask[$jj] = true;
}
} else {
$gaps[] = array($gap_start, $gap_length);
}
$in_gap = false;
}
} else {
if (!$in_gap) {
$gap_start = $ii;
$in_gap = true;
}
}
}
$gaps = array_reverse($gaps);
$mask = $base_mask;
return array($gaps, $mask);
}
/**
* Determine if an inline comment will appear on the rendered diff,
* taking into consideration which halves of which changesets will actually
* be shown.
*
* @param PhabricatorInlineCommentInterface Comment to test for visibility.
* @return bool True if the comment is visible on the rendered diff.
*/
private function isCommentVisibleOnRenderedDiff(
PhabricatorInlineCommentInterface $comment) {
$changeset_id = $comment->getChangesetID();
$is_new = $comment->getIsNewFile();
if ($changeset_id == $this->rightSideChangesetID &&
$is_new == $this->rightSideAttachesToNewFile) {
return true;
}
if ($changeset_id == $this->leftSideChangesetID &&
$is_new == $this->leftSideAttachesToNewFile) {
return true;
}
return false;
}
/**
* Determine if a comment will appear on the right side of the display diff.
* Note that the comment must appear somewhere on the rendered changeset, as
* per isCommentVisibleOnRenderedDiff().
*
* @param PhabricatorInlineCommentInterface Comment to test for display
* location.
* @return bool True for right, false for left.
*/
private function isCommentOnRightSideWhenDisplayed(
PhabricatorInlineCommentInterface $comment) {
if (!$this->isCommentVisibleOnRenderedDiff($comment)) {
throw new Exception(pht('Comment is not visible on changeset!'));
}
$changeset_id = $comment->getChangesetID();
$is_new = $comment->getIsNewFile();
if ($changeset_id == $this->rightSideChangesetID &&
$is_new == $this->rightSideAttachesToNewFile) {
return true;
}
return false;
}
/**
* Parse the 'range' specification that this class and the client-side JS
* emit to indicate that a user clicked "Show more..." on a diff. Generally,
* use is something like this:
*
* $spec = $request->getStr('range');
* $parsed = DifferentialChangesetParser::parseRangeSpecification($spec);
* list($start, $end, $mask) = $parsed;
* $parser->render($start, $end, $mask);
*
* @param string Range specification, indicating the range of the diff that
* should be rendered.
* @return tuple List of <start, end, mask> suitable for passing to
* @{method:render}.
*/
public static function parseRangeSpecification($spec) {
$range_s = null;
$range_e = null;
$mask = array();
if ($spec) {
$match = null;
if (preg_match('@^(\d+)-(\d+)(?:/(\d+)-(\d+))?$@', $spec, $match)) {
$range_s = (int)$match[1];
$range_e = (int)$match[2];
if (count($match) > 3) {
$start = (int)$match[3];
$len = (int)$match[4];
for ($ii = $start; $ii < $start + $len; $ii++) {
$mask[$ii] = true;
}
}
}
}
return array($range_s, $range_e, $mask);
}
/**
* Render "modified coverage" information; test coverage on modified lines.
* This synthesizes diff information with unit test information into a useful
* indicator of how well tested a change is.
*/
public function renderModifiedCoverage() {
$na = phutil_tag('em', array(), '-');
$coverage = $this->getCoverage();
if (!$coverage) {
return $na;
}
$covered = 0;
$not_covered = 0;
foreach ($this->new as $k => $new) {
if (!$new['line']) {
continue;
}
if (!$new['type']) {
continue;
}
if (empty($coverage[$new['line'] - 1])) {
continue;
}
switch ($coverage[$new['line'] - 1]) {
case 'C':
$covered++;
break;
case 'U':
$not_covered++;
break;
}
}
if (!$covered && !$not_covered) {
return $na;
}
return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered)));
}
/**
* Build maps from lines comments appear on to actual lines.
*/
private function buildLineBackmaps() {
$old_back = array();
$new_back = array();
foreach ($this->old as $ii => $old) {
$old_back[$old['line']] = $old['line'];
}
foreach ($this->new as $ii => $new) {
$new_back[$new['line']] = $new['line'];
}
$max_old_line = 0;
$max_new_line = 0;
foreach ($this->comments as $comment) {
if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
$max_new_line = max($max_new_line, $comment->getLineNumber());
} else {
$max_old_line = max($max_old_line, $comment->getLineNumber());
}
}
$cursor = 1;
for ($ii = 1; $ii <= $max_old_line; $ii++) {
if (empty($old_back[$ii])) {
$old_back[$ii] = $cursor;
} else {
$cursor = $old_back[$ii];
}
}
$cursor = 1;
for ($ii = 1; $ii <= $max_new_line; $ii++) {
if (empty($new_back[$ii])) {
$new_back[$ii] = $cursor;
} else {
$cursor = $new_back[$ii];
}
}
return array($old_back, $new_back);
}
private function getOffset(array $map, $line) {
if (!$map) {
return null;
}
$line = (int)$line;
foreach ($map as $key => $spec) {
if ($spec && isset($spec['line'])) {
if ((int)$spec['line'] >= $line) {
return $key;
}
}
}
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);
// TODO: This should be flexible, eventually.
$tab_width = 2;
$line = self::replaceTabsWithSpaces($line, $tab_width);
$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;
}
public static function replaceTabsWithSpaces($line, $tab_width) {
static $tags = array();
if (empty($tags[$tab_width])) {
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;
}
}
// Expand all prefix tabs until we encounter any non-tab character. This
// is cheap and often immediately produces the correct result with no
// further work (and, particularly, no need to handle any unicode cases).
$len = strlen($line);
$head = 0;
for ($head = 0; $head < $len; $head++) {
$char = $line[$head];
if ($char !== "\t") {
break;
}
}
if ($head) {
if (empty($tags[$tab_width * $head])) {
$tags[$tab_width * $head] = str_repeat($tags[$tab_width], $head);
}
$prefix = $tags[$tab_width * $head];
$line = substr($line, $head);
} else {
$prefix = '';
}
// If we have no remaining tabs elsewhere in the string after taking care
// of all the prefix tabs, we're done.
if (strpos($line, "\t") === false) {
return $prefix.$line;
}
$len = strlen($line);
// If the line is particularly long, don't try to do anything special with
// it. Use a faster approximation of the correct tabstop expansion instead.
// This usually still arrives at the right result.
if ($len > 256) {
return $prefix.str_replace("\t", $tags[$tab_width], $line);
}
$in_tag = false;
$pos = 0;
// See PHI1210. If the line only has single-byte characters, we don't need
// to vectorize it and can avoid an expensive UTF8 call.
$fast_path = preg_match('/^[\x01-\x7F]*\z/', $line);
if ($fast_path) {
$replace = array();
for ($ii = 0; $ii < $len; $ii++) {
$char = $line[$ii];
if ($char === '>') {
$in_tag = false;
continue;
}
if ($in_tag) {
continue;
}
if ($char === '<') {
$in_tag = true;
continue;
}
if ($char === "\t") {
$count = $tab_width - ($pos % $tab_width);
$pos += $count;
$replace[$ii] = $tags[$count];
continue;
}
$pos++;
}
if ($replace) {
// Apply replacements starting at the end of the string so they
// don't mess up the offsets for following replacements.
$replace = array_reverse($replace, true);
foreach ($replace as $replace_pos => $replacement) {
$line = substr_replace($line, $replacement, $replace_pos, 1);
}
}
} else {
$line = phutil_utf8v_combined($line);
foreach ($line as $key => $char) {
if ($char === '>') {
$in_tag = false;
continue;
}
if ($in_tag) {
continue;
}
if ($char === '<') {
$in_tag = true;
continue;
}
if ($char === "\t") {
$count = $tab_width - ($pos % $tab_width);
$pos += $count;
$line[$key] = $tags[$count];
continue;
}
$pos++;
}
$line = implode('', $line);
}
return $prefix.$line;
}
private function newDocumentEngine() {
$changeset = $this->changeset;
$viewer = $this->getViewer();
// TODO: This should probably be made non-optional in the future.
if (!$viewer) {
return null;
}
$old_file = null;
$new_file = null;
switch ($changeset->getFileType()) {
case DifferentialChangeType::FILE_IMAGE:
case DifferentialChangeType::FILE_BINARY:
list($old_file, $new_file) = $this->loadFileObjectsForChangeset();
break;
}
$type_delete = DifferentialChangeType::TYPE_DELETE;
$type_add = DifferentialChangeType::TYPE_ADD;
$change_type = $changeset->getChangeType();
$no_old = ($change_type == $type_add);
$no_new = ($change_type == $type_delete);
if ($no_old) {
$old_ref = null;
} else {
$old_ref = id(new PhabricatorDocumentRef())
->setName($changeset->getOldFile());
if ($old_file) {
$old_ref->setFile($old_file);
} else {
$old_data = $this->old;
$old_data = ipull($old_data, 'text');
$old_data = implode('', $old_data);
$old_ref->setData($old_data);
}
}
if ($no_new) {
$new_ref = null;
} else {
$new_ref = id(new PhabricatorDocumentRef())
->setName($changeset->getFilename());
if ($new_file) {
$new_ref->setFile($new_file);
} else {
$new_data = $this->new;
$new_data = ipull($new_data, 'text');
$new_data = implode('', $new_data);
$new_ref->setData($new_data);
}
}
$old_engines = null;
if ($old_ref) {
$old_engines = PhabricatorDocumentEngine::getEnginesForRef(
$viewer,
$old_ref);
}
$new_engines = null;
if ($new_ref) {
$new_engines = PhabricatorDocumentEngine::getEnginesForRef(
$viewer,
$new_ref);
}
if ($new_engines !== null && $old_engines !== null) {
$shared_engines = array_intersect_key($new_engines, $old_engines);
$default_engine = head_key($new_engines);
} else if ($new_engines !== null) {
$shared_engines = $new_engines;
$default_engine = head_key($shared_engines);
} else if ($old_engines !== null) {
$shared_engines = $old_engines;
$default_engine = head_key($shared_engines);
} else {
return null;
}
foreach ($shared_engines as $key => $shared_engine) {
if (!$shared_engine->canDiffDocuments($old_ref, $new_ref)) {
unset($shared_engines[$key]);
}
}
$viewstate = $this->getViewState();
$engine_key = $viewstate->getDocumentEngineKey();
if (strlen($engine_key)) {
if (isset($shared_engines[$engine_key])) {
$document_engine = $shared_engines[$engine_key];
} else {
$document_engine = null;
}
} else {
// If we aren't rendering with a specific engine, only use a default
// engine if the best engine for the new file is a shared engine which
// can diff files. If we're less picky (for example, by accepting any
// shared engine) we can end up with silly behavior (like ".json" files
// rendering as Jupyter documents).
if (isset($shared_engines[$default_engine])) {
$document_engine = $shared_engines[$default_engine];
} else {
$document_engine = null;
}
}
if ($document_engine) {
return array(
$document_engine,
$old_ref,
$new_ref);
}
return null;
}
private function loadFileObjectsForChangeset() {
$changeset = $this->changeset;
$viewer = $this->getViewer();
$old_file = null;
$new_file = null;
// TODO: Improve the architectural issue as discussed in D955
// https://secure.phabricator.com/D955
$reference = $this->getRenderingReference();
$parts = explode('/', $reference);
if (count($parts) == 2) {
list($id, $vs) = $parts;
} else {
$id = $parts[0];
$vs = 0;
}
$id = (int)$id;
$vs = (int)$vs;
if (!$vs) {
$metadata = $this->changeset->getMetadata();
$data = idx($metadata, 'attachment-data');
$old_phid = idx($metadata, 'old:binary-phid');
$new_phid = idx($metadata, 'new:binary-phid');
} else {
$vs_changeset = id(new DifferentialChangeset())->load($vs);
$old_phid = null;
$new_phid = null;
// TODO: This is spooky, see D6851
if ($vs_changeset) {
$vs_metadata = $vs_changeset->getMetadata();
$old_phid = idx($vs_metadata, 'new:binary-phid');
}
$changeset = id(new DifferentialChangeset())->load($id);
if ($changeset) {
$metadata = $changeset->getMetadata();
$new_phid = idx($metadata, 'new:binary-phid');
}
}
if ($old_phid || $new_phid) {
$file_phids = array();
if ($old_phid) {
$file_phids[] = $old_phid;
}
if ($new_phid) {
$file_phids[] = $new_phid;
}
$files = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs($file_phids)
->execute();
foreach ($files as $file) {
if ($file->getPHID() == $old_phid) {
$old_file = $file;
} else if ($file->getPHID() == $new_phid) {
$new_file = $file;
}
}
}
return array($old_file, $new_file);
}
public function newChangesetResponse() {
// NOTE: This has to happen first because it has side effects. Yuck.
$rendered_changeset = $this->renderChangeset();
$renderer = $this->getRenderer();
$renderer_key = $renderer->getRendererKey();
$viewstate = $this->getViewState();
$undo_templates = $renderer->renderUndoTemplates();
foreach ($undo_templates as $key => $undo_template) {
$undo_templates[$key] = hsprintf('%s', $undo_template);
}
$state = array(
'undoTemplates' => $undo_templates,
'rendererKey' => $renderer_key,
'highlight' => $viewstate->getHighlightLanguage(),
'characterEncoding' => $viewstate->getCharacterEncoding(),
'documentEngine' => $viewstate->getDocumentEngineKey(),
+ 'isHidden' => $viewstate->getHidden(),
);
return id(new PhabricatorChangesetResponse())
->setRenderedChangeset($rendered_changeset)
->setChangesetState($state);
}
}
diff --git a/src/applications/differential/storage/DifferentialViewState.php b/src/applications/differential/storage/DifferentialViewState.php
index adf23b6abd..763edd8460 100644
--- a/src/applications/differential/storage/DifferentialViewState.php
+++ b/src/applications/differential/storage/DifferentialViewState.php
@@ -1,132 +1,142 @@
<?php
final class DifferentialViewState
extends DifferentialDAO
implements PhabricatorPolicyInterface {
protected $viewerPHID;
protected $objectPHID;
protected $viewState = array();
private $hasModifications;
protected function getConfiguration() {
return array(
self::CONFIG_SERIALIZATION => array(
'viewState' => self::SERIALIZATION_JSON,
),
self::CONFIG_KEY_SCHEMA => array(
'key_viewer' => array(
'columns' => array('viewerPHID', 'objectPHID'),
'unique' => true,
),
'key_object' => array(
'columns' => array('objectPHID'),
),
),
) + parent::getConfiguration();
}
public function setChangesetProperty(
DifferentialChangeset $changeset,
$key,
$value) {
if ($this->getChangesetProperty($changeset, $key) === $value) {
return;
}
$properties = array(
'value' => $value,
'epoch' => PhabricatorTime::getNow(),
);
$diff_id = $changeset->getDiffID();
if ($diff_id !== null) {
$properties['diffID'] = (int)$diff_id;
}
$path_hash = $this->getChangesetPathHash($changeset);
$changeset_phid = $this->getChangesetKey($changeset);
$this->hasModifications = true;
$this->viewState['changesets'][$path_hash][$key][$changeset_phid] =
$properties;
}
public function getChangesetProperty(
DifferentialChangeset $changeset,
$key,
$default = null) {
- $path_hash = $this->getChangesetPathHash($changeset);
-
- $entries = idxv($this->viewState, array('changesets', $path_hash, $key));
- if (!is_array($entries)) {
- $entries = array();
- }
+ $entries = $this->getChangesetPropertyEntries(
+ $changeset,
+ $key);
$entries = isort($entries, 'epoch');
$entry = last($entries);
if (!is_array($entry)) {
$entry = array();
}
return idx($entry, 'value', $default);
}
+ public function getChangesetPropertyEntries(
+ DifferentialChangeset $changeset,
+ $key) {
+ $path_hash = $this->getChangesetPathHash($changeset);
+
+ $entries = idxv($this->viewState, array('changesets', $path_hash, $key));
+ if (!is_array($entries)) {
+ $entries = array();
+ }
+
+ return $entries;
+ }
+
public function getHasModifications() {
return $this->hasModifications;
}
private function getChangesetPathHash(DifferentialChangeset $changeset) {
$path = $changeset->getFilename();
return PhabricatorHash::digestForIndex($path);
}
private function getChangesetKey(DifferentialChangeset $changeset) {
$key = $changeset->getID();
if ($key === null) {
return '*';
}
return (string)$key;
}
public static function copyViewStatesToObject($src_phid, $dst_phid) {
$table = new self();
$conn = $table->establishConnection('w');
queryfx(
$conn,
'INSERT IGNORE INTO %R
(viewerPHID, objectPHID, viewState, dateCreated, dateModified)
SELECT viewerPHID, %s, viewState, dateCreated, dateModified
FROM %R WHERE objectPHID = %s',
$table,
$dst_phid,
$table,
$src_phid);
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */
public function getCapabilities() {
return array(
PhabricatorPolicyCapability::CAN_VIEW,
);
}
public function getPolicy($capability) {
return PhabricatorPolicies::POLICY_NOONE;
}
public function hasAutomaticCapability($capability, PhabricatorUser $viewer) {
return ($viewer->getPHID() === $this->getViewerPHID());
}
}
diff --git a/src/applications/diffusion/controller/DiffusionDiffController.php b/src/applications/diffusion/controller/DiffusionDiffController.php
index d85c292cb7..17d6b85af1 100644
--- a/src/applications/diffusion/controller/DiffusionDiffController.php
+++ b/src/applications/diffusion/controller/DiffusionDiffController.php
@@ -1,135 +1,139 @@
<?php
final class DiffusionDiffController extends DiffusionController {
public function shouldAllowPublic() {
return true;
}
protected function getDiffusionBlobFromRequest(AphrontRequest $request) {
return $request->getStr('ref');
}
public function handleRequest(AphrontRequest $request) {
$response = $this->loadDiffusionContext();
if ($response) {
return $response;
}
$viewer = $this->getViewer();
$drequest = $this->getDiffusionRequest();
if (!$request->isAjax()) {
// This request came out of the dropdown menu, either "View Standalone"
// or "View Raw File".
$view = $request->getStr('view');
if ($view == 'r') {
$uri = $drequest->generateURI(
array(
'action' => 'browse',
'params' => array(
'view' => 'raw',
),
));
} else {
$uri = $drequest->generateURI(
array(
'action' => 'change',
));
}
return id(new AphrontRedirectResponse())->setURI($uri);
}
$data = $this->callConduitWithDiffusionRequest(
'diffusion.diffquery',
array(
'commit' => $drequest->getCommit(),
'path' => $drequest->getPath(),
));
$drequest->updateSymbolicCommit($data['effectiveCommit']);
$raw_changes = ArcanistDiffChange::newFromConduit($data['changes']);
$diff = DifferentialDiff::newEphemeralFromRawChanges(
$raw_changes);
$changesets = $diff->getChangesets();
$changeset = reset($changesets);
if (!$changeset) {
return new Aphront404Response();
}
$commit = $drequest->loadCommit();
$viewstate_engine = id(new PhabricatorChangesetViewStateEngine())
->setViewer($viewer)
->setObjectPHID($commit->getPHID())
->setChangeset($changeset);
$viewstate = $viewstate_engine->newViewStateFromRequest($request);
+ if ($viewstate->getDiscardResponse()) {
+ return new AphrontAjaxResponse();
+ }
+
$parser = id(new DifferentialChangesetParser())
->setViewer($viewer)
->setChangeset($changeset)
->setViewState($viewstate);
$parser->setRenderingReference($drequest->generateURI(
array(
'action' => 'rendering-ref',
)));
$coverage = $drequest->loadCoverage();
if ($coverage) {
$parser->setCoverage($coverage);
}
$pquery = new DiffusionPathIDQuery(array($changeset->getFilename()));
$ids = $pquery->loadPathIDs();
$path_id = $ids[$changeset->getFilename()];
$parser->setLeftSideCommentMapping($path_id, false);
$parser->setRightSideCommentMapping($path_id, true);
$parser->setCanMarkDone(
($commit->getAuthorPHID()) &&
($viewer->getPHID() == $commit->getAuthorPHID()));
$parser->setObjectOwnerPHID($commit->getAuthorPHID());
$inlines = PhabricatorAuditInlineComment::loadDraftAndPublishedComments(
$viewer,
$commit->getPHID(),
$path_id);
if ($inlines) {
foreach ($inlines as $inline) {
$parser->parseInlineComment($inline);
}
$phids = mpull($inlines, 'getAuthorPHID');
$handles = $this->loadViewerHandles($phids);
$parser->setHandles($handles);
}
$engine = new PhabricatorMarkupEngine();
$engine->setViewer($viewer);
foreach ($inlines as $inline) {
$engine->addObject(
$inline,
PhabricatorInlineCommentInterface::MARKUP_FIELD_BODY);
}
$engine->process();
$parser->setMarkupEngine($engine);
$spec = $request->getStr('range');
list($range_s, $range_e, $mask) =
DifferentialChangesetParser::parseRangeSpecification($spec);
$parser->setRange($range_s, $range_e);
$parser->setMask($mask);
return $parser->newChangesetResponse();
}
}
diff --git a/src/infrastructure/diff/viewstate/PhabricatorChangesetViewState.php b/src/infrastructure/diff/viewstate/PhabricatorChangesetViewState.php
index 4d29a8fd59..a0324cef9c 100644
--- a/src/infrastructure/diff/viewstate/PhabricatorChangesetViewState.php
+++ b/src/infrastructure/diff/viewstate/PhabricatorChangesetViewState.php
@@ -1,57 +1,87 @@
<?php
final class PhabricatorChangesetViewState
extends Phobject {
private $highlightLanguage;
private $characterEncoding;
private $documentEngineKey;
private $rendererKey;
private $defaultDeviceRendererKey;
+ private $hidden;
+ private $modifiedSinceHide;
+ private $discardResponse;
public function setHighlightLanguage($highlight_language) {
$this->highlightLanguage = $highlight_language;
return $this;
}
public function getHighlightLanguage() {
return $this->highlightLanguage;
}
public function setCharacterEncoding($character_encoding) {
$this->characterEncoding = $character_encoding;
return $this;
}
public function getCharacterEncoding() {
return $this->characterEncoding;
}
public function setDocumentEngineKey($document_engine_key) {
$this->documentEngineKey = $document_engine_key;
return $this;
}
public function getDocumentEngineKey() {
return $this->documentEngineKey;
}
public function setRendererKey($renderer_key) {
$this->rendererKey = $renderer_key;
return $this;
}
public function getRendererKey() {
return $this->rendererKey;
}
public function setDefaultDeviceRendererKey($renderer_key) {
$this->defaultDeviceRendererKey = $renderer_key;
return $this;
}
public function getDefaultDeviceRendererKey() {
return $this->defaultDeviceRendererKey;
}
+ public function setHidden($hidden) {
+ $this->hidden = $hidden;
+ return $this;
+ }
+
+ public function getHidden() {
+ return $this->hidden;
+ }
+
+ public function setModifiedSinceHide($modified_since_hide) {
+ $this->modifiedSinceHide = $modified_since_hide;
+ return $this;
+ }
+
+ public function getModifiedSinceHide() {
+ return $this->modifiedSinceHide;
+ }
+
+ public function setDiscardResponse($discard_response) {
+ $this->discardResponse = $discard_response;
+ return $this;
+ }
+
+ public function getDiscardResponse() {
+ return $this->discardResponse;
+ }
+
}
diff --git a/src/infrastructure/diff/viewstate/PhabricatorChangesetViewStateEngine.php b/src/infrastructure/diff/viewstate/PhabricatorChangesetViewStateEngine.php
index 96e544f560..a506ec38dc 100644
--- a/src/infrastructure/diff/viewstate/PhabricatorChangesetViewStateEngine.php
+++ b/src/infrastructure/diff/viewstate/PhabricatorChangesetViewStateEngine.php
@@ -1,177 +1,235 @@
<?php
final class PhabricatorChangesetViewStateEngine
extends Phobject {
private $viewer;
private $objectPHID;
private $changeset;
private $storage;
public function setViewer(PhabricatorUser $viewer) {
$this->viewer = $viewer;
return $this;
}
public function getViewer() {
return $this->viewer;
}
public function setObjectPHID($object_phid) {
$this->objectPHID = $object_phid;
return $this;
}
public function getObjectPHID() {
return $this->objectPHID;
}
public function setChangeset(DifferentialChangeset $changeset) {
$this->changeset = $changeset;
return $this;
}
public function getChangeset() {
return $this->changeset;
}
public function newViewStateFromRequest(AphrontRequest $request) {
$storage = $this->loadViewStateStorage();
$this->setStorage($storage);
$highlight = $request->getStr('highlight');
if ($highlight !== null) {
$this->setChangesetProperty('highlight', $highlight);
}
$encoding = $request->getStr('encoding');
if ($encoding !== null) {
$this->setChangesetProperty('encoding', $encoding);
}
$engine = $request->getStr('engine');
if ($engine !== null) {
$this->setChangesetProperty('engine', $engine);
}
$renderer = $request->getStr('renderer');
if ($renderer !== null) {
$this->setChangesetProperty('renderer', $renderer);
}
+ $hidden = $request->getStr('hidden');
+ if ($hidden !== null) {
+ $this->setChangesetProperty('hidden', (int)$hidden);
+ }
+
$this->saveViewStateStorage();
$state = new PhabricatorChangesetViewState();
$highlight_language = $this->getChangesetProperty('highlight');
$state->setHighlightLanguage($highlight_language);
$encoding = $this->getChangesetProperty('encoding');
$state->setCharacterEncoding($encoding);
$document_engine = $this->getChangesetProperty('engine');
$state->setDocumentEngineKey($document_engine);
$renderer = $this->getChangesetProperty('renderer');
$state->setRendererKey($renderer);
+ $this->updateHiddenState($state);
+
// This is the client-selected default renderer based on viewport
// dimensions.
$device_key = $request->getStr('device');
- if ($device_key !== null && strlen($device_key)) {
+ if ($device_key !== null) {
$state->setDefaultDeviceRendererKey($device_key);
}
+ $discard_response = $request->getStr('discard');
+ if ($discard_response !== null) {
+ $state->setDiscardResponse(true);
+ }
+
return $state;
}
private function setStorage(DifferentialViewState $storage) {
$this->storage = $storage;
return $this;
}
private function getStorage() {
return $this->storage;
}
private function setChangesetProperty(
$key,
$value) {
$storage = $this->getStorage();
$changeset = $this->getChangeset();
$storage->setChangesetProperty($changeset, $key, $value);
}
private function getChangesetProperty(
$key,
$default = null) {
$storage = $this->getStorage();
$changeset = $this->getChangeset();
return $storage->getChangesetProperty($changeset, $key, $default);
}
private function loadViewStateStorage() {
$viewer = $this->getViewer();
$object_phid = $this->getObjectPHID();
$viewer_phid = $viewer->getPHID();
$storage = null;
if ($viewer_phid !== null) {
$storage = id(new DifferentialViewStateQuery())
->setViewer($viewer)
->withViewerPHIDs(array($viewer_phid))
->withObjectPHIDs(array($object_phid))
->executeOne();
}
if ($storage === null) {
$storage = id(new DifferentialViewState())
->setObjectPHID($object_phid);
if ($viewer_phid !== null) {
$storage->setViewerPHID($viewer_phid);
} else {
$storage->makeEphemeral();
}
}
return $storage;
}
private function saveViewStateStorage() {
if (PhabricatorEnv::isReadOnly()) {
return;
}
$storage = $this->getStorage();
$viewer_phid = $storage->getViewerPHID();
if ($viewer_phid === null) {
return;
}
if (!$storage->getHasModifications()) {
return;
}
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
try {
$storage->save();
} catch (AphrontDuplicateKeyQueryException $ex) {
// We may race another process to save view state. For now, just discard
// our state if we do.
}
unset($unguarded);
}
+ private function updateHiddenState(PhabricatorChangesetViewState $state) {
+ $is_hidden = false;
+ $was_modified = false;
+
+ $storage = $this->getStorage();
+ $changeset = $this->getChangeset();
+
+ $entries = $storage->getChangesetPropertyEntries($changeset, 'hidden');
+ $entries = isort($entries, 'epoch');
+
+ if ($entries) {
+ $other_phid = last_key($entries);
+ $other_spec = last($entries);
+
+ $this_version = (int)$changeset->getDiffID();
+ $other_version = (int)idx($other_spec, 'diffID');
+ $other_value = (bool)idx($other_spec, 'value', false);
+
+ if ($other_value === false) {
+ $is_hidden = false;
+ } else if ($other_version >= $this_version) {
+ $is_hidden = $other_value;
+ } else {
+ $viewer = $this->getViewer();
+
+ $other_changeset = id(new DifferentialChangesetQuery())
+ ->setViewer($viewer)
+ ->withPHIDs(array($other_phid))
+ ->executeOne();
+
+ $is_modified = false;
+ if ($other_changeset) {
+ if (!$changeset->hasSameEffectAs($other_changeset)) {
+ $is_modified = true;
+ }
+ }
+
+ $is_hidden = false;
+ $was_modified = true;
+ }
+ }
+
+ $state->setHidden($is_hidden);
+ $state->setModifiedSinceHide($was_modified);
+ }
+
}
diff --git a/webroot/rsrc/css/application/diff/diff-tree-view.css b/webroot/rsrc/css/application/diff/diff-tree-view.css
index cc6ac934b5..33174d88d7 100644
--- a/webroot/rsrc/css/application/diff/diff-tree-view.css
+++ b/webroot/rsrc/css/application/diff/diff-tree-view.css
@@ -1,90 +1,103 @@
/**
* @provides diff-tree-view-css
*/
.diff-tree-view {
margin: 0 4px;
}
.diff-tree-path {
height: 20px;
line-height: 20px;
color: {$greytext};
border-left: 4px solid transparent;
}
.diff-tree-path-indent {
position: relative;
}
.diff-tree-path-icon {
position: absolute;
width: 20px;
height: 20px;
text-align: center;
}
.diff-tree-path-icon .phui-icon-view {
color: {$greytext};
}
.diff-tree-path-name {
margin-left: 22px;
margin-right: 44px;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.diff-tree-path-changeset {
cursor: pointer;
color: {$darkbluetext};
}
.diff-tree-path-focused {
background: {$darkgreybackground};
}
.device-desktop .diff-tree-path-changeset:hover {
background: {$lightblueborder};
transition: 0.1s;
}
.diff-tree-view .diff-tree-path-changeset.diff-tree-path-selected {
/* Rule is intentionally more specific than "hover". */
background: {$yellow};
}
.diff-tree-path-low-importance .diff-tree-path-name {
opacity: 0.5;
}
+.diff-tree-path-hidden {
+ opacity: 0.25;
+}
+
+.diff-tree-path-icon-hidden,
+.diff-tree-path-hidden .diff-tree-path-icon-kind {
+ display: none;
+}
+
+.diff-tree-path-hidden .diff-tree-path-icon-hidden {
+ display: block;
+}
+
.diff-tree-path-owned {
border-left-color: {$orange};
box-shadow: inset 2px 0 {$lightorange};
}
.diff-tree-path-inlines {
display: none;
position: absolute;
right: 4px;
border-radius: 4px;
text-align: center;
top: 2px;
height: 16px;
line-height: 14px;
width: 36px;
font-size: {$smallerfontsize};
color: {$greytext};
}
.diff-tree-path-inlines-visible {
display: block;
background: {$lightblueborder};
}
.diff-tree-path-inlines-completed {
background: {$darkgreybackground};
color: {$lightgreytext};
opacity: 0.75;
}
diff --git a/webroot/rsrc/js/application/diff/DiffChangeset.js b/webroot/rsrc/js/application/diff/DiffChangeset.js
index a6772a034a..6afefcf7d9 100644
--- a/webroot/rsrc/js/application/diff/DiffChangeset.js
+++ b/webroot/rsrc/js/application/diff/DiffChangeset.js
@@ -1,936 +1,968 @@
/**
* @provides phabricator-diff-changeset
* @requires javelin-dom
* javelin-util
* javelin-stratcom
* javelin-install
* javelin-workflow
* javelin-router
* javelin-behavior-device
* javelin-vector
* phabricator-diff-inline
* phabricator-diff-path-view
* @javelin
*/
JX.install('DiffChangeset', {
construct : function(node) {
this._node = node;
var data = this._getNodeData();
this._renderURI = data.renderURI;
this._ref = data.ref;
this._loaded = data.loaded;
this._treeNodeID = data.treeNodeID;
this._leftID = data.left;
this._rightID = data.right;
this._displayPath = JX.$H(data.displayPath);
this._pathParts = data.pathParts;
this._icon = data.icon;
this._editorURI = data.editorURI;
this._editorConfigureURI = data.editorConfigureURI;
this._pathIconIcon = data.pathIconIcon;
this._pathIconColor = data.pathIconColor;
this._isLowImportance = data.isLowImportance;
this._isOwned = data.isOwned;
+ this._isLoading = true;
this._inlines = [];
if (data.changesetState) {
this._loadChangesetState(data.changesetState);
}
},
members: {
_node: null,
_loaded: false,
_sequence: 0,
_stabilize: false,
_renderURI: null,
_ref: null,
_rendererKey: null,
_highlight: null,
_documentEngine: null,
_characterEncoding: null,
_undoTemplates: null,
_leftID: null,
_rightID: null,
_inlines: null,
_visible: true,
_undoNode: null,
_displayPath: null,
_changesetList: null,
_icon: null,
_editorURI: null,
_editorConfigureURI: null,
_pathView: null,
_pathIconIcon: null,
_pathIconColor: null,
_isLowImportance: null,
_isOwned: null,
+ _isHidden: null,
getEditorURI: function() {
return this._editorURI;
},
getEditorConfigureURI: function() {
return this._editorConfigureURI;
},
getLeftChangesetID: function() {
return this._leftID;
},
getRightChangesetID: function() {
return this._rightID;
},
setChangesetList: function(list) {
this._changesetList = list;
return this;
},
getIcon: function() {
if (!this._visible) {
return 'fa-file-o';
}
return this._icon;
},
getColor: function() {
if (!this._visible) {
return 'grey';
}
return 'blue';
},
getChangesetList: function() {
return this._changesetList;
},
/**
* Has the content of this changeset been loaded?
*
* This method returns `true` if a request has been fired, even if the
* response has not returned yet.
*
* @return bool True if the content has been loaded.
*/
isLoaded: function() {
return this._loaded;
},
/**
* Configure stabilization of the document position on content load.
*
* When we dump the changeset into the document, we can try to stabilize
* the document scroll position so that the user doesn't feel like they
* are jumping around as things load in. This is generally useful when
* populating initial changes.
*
* However, if a user explicitly requests a content load by clicking a
* "Load" link or using the dropdown menu, this stabilization generally
* feels unnatural, so we don't use it in response to explicit user action.
*
* @param bool True to stabilize the next content fill.
* @return this
*/
setStabilize: function(stabilize) {
this._stabilize = stabilize;
return this;
},
/**
* Should this changeset load immediately when the page loads?
*
* Normally, changes load immediately, but if a diff or commit is very
* large we stop doing this and have the user load files explicitly, or
* choose to load everything.
*
* @return bool True if the changeset should load automatically when the
* page loads.
*/
shouldAutoload: function() {
return this._getNodeData().autoload;
},
/**
* Load this changeset, if it isn't already loading.
*
* This fires a request to fill the content of this changeset, provided
* there isn't already a request in flight. To force a reload, use
* @{method:reload}.
*
* @return this
*/
load: function() {
if (this._loaded) {
return this;
}
return this.reload();
},
/**
* Reload the changeset content.
*
* This method always issues a request, even if the content is already
* loading. To load conditionally, use @{method:load}.
*
* @return this
*/
reload: function(state) {
this._loaded = true;
this._sequence++;
- var params = this._getViewParameters(state);
- var pht = this.getChangesetList().getTranslations();
-
- var workflow = new JX.Workflow(this._renderURI, params)
+ var workflow = this._newReloadWorkflow(state)
.setHandler(JX.bind(this, this._onresponse, this._sequence));
this._startContentWorkflow(workflow);
+ var pht = this.getChangesetList().getTranslations();
+
JX.DOM.setContent(
this._getContentFrame(),
JX.$N(
'div',
{className: 'differential-loading'},
pht('Loading...')));
return this;
},
+ _newReloadWorkflow: function(state) {
+ var params = this._getViewParameters(state);
+ return new JX.Workflow(this._renderURI, params);
+ },
+
/**
* Load missing context in a changeset.
*
* We do this when the user clicks "Show X Lines". We also expand all of
* the missing context when they "Show All Context".
*
* @param string Line range specification, like "0-40/0-20".
* @param node Row where the context should be rendered after loading.
* @param bool True if this is a bulk load of multiple context blocks.
* @return this
*/
loadContext: function(range, target, bulk) {
var params = this._getViewParameters();
params.range = range;
var pht = this.getChangesetList().getTranslations();
var container = JX.DOM.scry(target, 'td')[0];
JX.DOM.setContent(container, pht('Loading...'));
JX.DOM.alterClass(target, 'differential-show-more-loading', true);
var workflow = new JX.Workflow(this._renderURI, params)
.setHandler(JX.bind(this, this._oncontext, target));
if (bulk) {
// If we're loading a bunch of these because the viewer clicked
// "Show All Context" or similar, use lower-priority requests
// and draw a progress bar.
this._startContentWorkflow(workflow);
} else {
// If this is a single click on a context link, use a higher priority
// load without a chrome change.
workflow.start();
}
return this;
},
loadAllContext: function() {
var nodes = JX.DOM.scry(this._node, 'tr', 'context-target');
for (var ii = 0; ii < nodes.length; ii++) {
var show = JX.DOM.scry(nodes[ii], 'a', 'show-more');
for (var jj = 0; jj < show.length; jj++) {
var data = JX.Stratcom.getData(show[jj]);
if (data.type != 'all') {
continue;
}
this.loadContext(data.range, nodes[ii], true);
}
}
},
_startContentWorkflow: function(workflow) {
var routable = workflow.getRoutable();
routable
.setPriority(500)
.setType('content')
.setKey(this._getRoutableKey());
JX.Router.getInstance().queue(routable);
},
getDisplayPath: function() {
return this._displayPath;
},
/**
* Receive a response to a context request.
*/
_oncontext: function(target, response) {
// TODO: This should be better structured.
// If the response comes back with several top-level nodes, the last one
// is the actual context; the others are headers. Add any headers first,
// then copy the new rows into the document.
var markup = JX.$H(response.changeset).getFragment();
var len = markup.childNodes.length;
var diff = JX.DOM.findAbove(target, 'table', 'differential-diff');
for (var ii = 0; ii < len - 1; ii++) {
diff.parentNode.insertBefore(markup.firstChild, diff);
}
var table = markup.firstChild;
var root = target.parentNode;
this._moveRows(table, root, target);
root.removeChild(target);
this._onchangesetresponse(response);
},
_moveRows: function(src, dst, before) {
var rows = JX.DOM.scry(src, 'tr');
for (var ii = 0; ii < rows.length; ii++) {
// Find the table this <tr /> belongs to. If it's a sub-table, like a
// table in an inline comment, don't copy it.
if (JX.DOM.findAbove(rows[ii], 'table') !== src) {
continue;
}
if (before) {
dst.insertBefore(rows[ii], before);
} else {
dst.appendChild(rows[ii]);
}
}
},
/**
* Get parameters which define the current rendering options.
*/
_getViewParameters: function(state) {
var parameters = {
ref: this._ref,
device: this._getDefaultDeviceRenderer()
};
if (state) {
JX.copy(parameters, state);
}
return parameters;
},
/**
* Get the active @{class:JX.Routable} for this changeset.
*
* After issuing a request with @{method:load} or @{method:reload}, you
* can adjust routable settings (like priority) by querying the routable
* with this method. Note that there may not be a current routable.
*
* @return JX.Routable|null Active routable, if one exists.
*/
getRoutable: function() {
return JX.Router.getInstance().getRoutableByKey(this._getRoutableKey());
},
getRendererKey: function() {
return this._rendererKey;
},
_getDefaultDeviceRenderer: function() {
// NOTE: If you load the page at one device resolution and then resize to
// a different one we don't re-render the diffs, because it's a
// complicated mess and you could lose inline comments, cursor positions,
// etc.
return (JX.Device.getDevice() == 'desktop') ? '2up' : '1up';
},
getUndoTemplates: function() {
return this._undoTemplates;
},
getCharacterEncoding: function() {
return this._characterEncoding;
},
getHighlight: function() {
return this._highlight;
},
getDocumentEngine: function(engine) {
return this._documentEngine;
},
getSelectableItems: function() {
var items = [];
items.push({
type: 'file',
changeset: this,
target: this,
nodes: {
begin: this._node,
end: null
}
});
if (!this._visible) {
return items;
}
var rows = JX.DOM.scry(this._node, 'tr');
var blocks = [];
var block;
var ii;
for (ii = 0; ii < rows.length; ii++) {
var type = this._getRowType(rows[ii]);
if (!block || (block.type !== type)) {
block = {
type: type,
items: []
};
blocks.push(block);
}
block.items.push(rows[ii]);
}
var last_inline = null;
var last_inline_item = null;
for (ii = 0; ii < blocks.length; ii++) {
block = blocks[ii];
if (block.type == 'change') {
items.push({
type: block.type,
changeset: this,
target: block.items[0],
nodes: {
begin: block.items[0],
end: block.items[block.items.length - 1]
}
});
}
if (block.type == 'comment') {
for (var jj = 0; jj < block.items.length; jj++) {
var inline = this.getInlineForRow(block.items[jj]);
// When comments are being edited, they have a hidden row with
// the actual comment and then a visible row with the editor.
// In this case, we only want to generate one item, but it should
// use the editor as a scroll target. To accomplish this, check if
// this row has the same inline as the previous row. If so, update
// the last item to use this row's nodes.
if (inline === last_inline) {
last_inline_item.nodes.begin = block.items[jj];
last_inline_item.nodes.end = block.items[jj];
continue;
} else {
last_inline = inline;
}
var is_saved = (!inline.isDraft() && !inline.isEditing());
last_inline_item = {
type: block.type,
changeset: this,
target: inline,
hidden: inline.isHidden(),
collapsed: inline.isCollapsed(),
deleted: !inline.getID() && !inline.isEditing(),
nodes: {
begin: block.items[jj],
end: block.items[jj]
},
attributes: {
unsaved: inline.isEditing(),
anyDraft: inline.isDraft() || inline.isDraftDone(),
undone: (is_saved && !inline.isDone()),
done: (is_saved && inline.isDone())
}
};
items.push(last_inline_item);
}
}
}
return items;
},
_getRowType: function(row) {
// NOTE: Don't do "className.indexOf()" elsewhere. This is evil legacy
// magic.
if (row.className.indexOf('inline') !== -1) {
return 'comment';
}
var cells = JX.DOM.scry(row, 'td');
for (var ii = 0; ii < cells.length; ii++) {
if (cells[ii].className.indexOf('old') !== -1 ||
cells[ii].className.indexOf('new') !== -1) {
return 'change';
}
}
},
_getNodeData: function() {
return JX.Stratcom.getData(this._node);
},
getVectors: function() {
return {
pos: JX.$V(this._node),
dim: JX.Vector.getDim(this._node)
};
},
_onresponse: function(sequence, response) {
if (sequence != this._sequence) {
// If this isn't the most recent request, ignore it. This normally
// means the user changed view settings between the time the page loaded
// and the content filled.
return;
}
// As we populate the changeset list, we try to hold the document scroll
// position steady, so that, e.g., users who want to leave a comment on a
// diff with a large number of changes don't constantly have the text
// area scrolled off the bottom of the screen until the entire diff loads.
//
// There are several major cases here:
//
// - If we're near the top of the document, never scroll.
// - If we're near the bottom of the document, always scroll, unless
// we have an anchor.
// - Otherwise, scroll if the changes were above (or, at least,
// almost entirely above) the viewport.
//
// We don't scroll if the changes were just near the top of the viewport
// because this makes us scroll incorrectly when an anchored change is
// visible. See T12779.
var target = this._node;
var old_pos = JX.Vector.getScroll();
var old_view = JX.Vector.getViewport();
var old_dim = JX.Vector.getDocument();
// Number of pixels away from the top or bottom of the document which
// count as "nearby".
var sticky = 480;
var near_top = (old_pos.y <= sticky);
var near_bot = ((old_pos.y + old_view.y) >= (old_dim.y - sticky));
// If we have an anchor in the URL, never stick to the bottom of the
// page. See T11784 for discussion.
if (window.location.hash) {
near_bot = false;
}
var target_pos = JX.Vector.getPos(target);
var target_dim = JX.Vector.getDim(target);
var target_bot = (target_pos.y + target_dim.y);
// Detect if the changeset is entirely (or, at least, almost entirely)
// above us. The height here is roughly the height of the persistent
// banner.
var above_screen = (target_bot < old_pos.y + 64);
// If we have a URL anchor and are currently nearby, stick to it
// no matter what.
var on_target = null;
if (window.location.hash) {
try {
var anchor = JX.$(window.location.hash.replace('#', ''));
if (anchor) {
var anchor_pos = JX.$V(anchor);
if ((anchor_pos.y > old_pos.y) &&
(anchor_pos.y < old_pos.y + 96)) {
on_target = anchor;
}
}
} catch (ignored) {
// If we have a bogus anchor, just ignore it.
}
}
var frame = this._getContentFrame();
JX.DOM.setContent(frame, JX.$H(response.changeset));
if (this._stabilize) {
if (on_target) {
JX.DOM.scrollToPosition(old_pos.x, JX.$V(on_target).y - 60);
} else if (!near_top) {
if (near_bot || above_screen) {
// Figure out how much taller the document got.
var delta = (JX.Vector.getDocument().y - old_dim.y);
JX.DOM.scrollToPosition(old_pos.x, old_pos.y + delta);
}
}
this._stabilize = false;
}
this._onchangesetresponse(response);
},
_onchangesetresponse: function(response) {
// Code shared by autoload and context responses.
this._loadChangesetState(response);
JX.Stratcom.invoke('differential-inline-comment-refresh');
this._rebuildAllInlines();
JX.Stratcom.invoke('resize');
},
_loadChangesetState: function(state) {
if (state.coverage) {
for (var k in state.coverage) {
try {
JX.DOM.replace(JX.$(k), JX.$H(state.coverage[k]));
} catch (ignored) {
// Not terribly important.
}
}
}
if (state.undoTemplates) {
this._undoTemplates = state.undoTemplates;
}
this._rendererKey = state.rendererKey;
this._highlight = state.highlight;
this._characterEncoding = state.characterEncoding;
this._documentEngine = state.documentEngine;
+ this._isHidden = state.isHidden;
+
+ var is_hidden = !this.isVisible();
+ if (this._isHidden != is_hidden) {
+ this.setVisible(!this._isHidden);
+ }
+
+ this._isLoading = false;
+ this.getPathView().setIsLoading(this._isLoading);
},
_getContentFrame: function() {
return JX.DOM.find(this._node, 'div', 'changeset-view-content');
},
_getRoutableKey: function() {
return 'changeset-view.' + this._ref + '.' + this._sequence;
},
getInlineForRow: function(node) {
var data = JX.Stratcom.getData(node);
if (!data.inline) {
var inline = new JX.DiffInline()
.setChangeset(this)
.bindToRow(node);
this._inlines.push(inline);
}
return data.inline;
},
newInlineForRange: function(origin, target) {
var list = this.getChangesetList();
var src = list.getLineNumberFromHeader(origin);
var dst = list.getLineNumberFromHeader(target);
var changeset_id = null;
var side = list.getDisplaySideFromHeader(origin);
if (side == 'right') {
changeset_id = this.getRightChangesetID();
} else {
changeset_id = this.getLeftChangesetID();
}
var is_new = false;
if (side == 'right') {
is_new = true;
} else if (this.getRightChangesetID() != this.getLeftChangesetID()) {
is_new = true;
}
var data = {
origin: origin,
target: target,
number: src,
length: dst - src,
changesetID: changeset_id,
displaySide: side,
isNewFile: is_new
};
var inline = new JX.DiffInline()
.setChangeset(this)
.bindToRange(data);
this._inlines.push(inline);
inline.create();
return inline;
},
newInlineReply: function(original, text) {
var inline = new JX.DiffInline()
.setChangeset(this)
.bindToReply(original);
this._inlines.push(inline);
inline.create(text);
return inline;
},
getInlineByID: function(id) {
return this._queryInline('id', id);
},
getInlineByPHID: function(phid) {
return this._queryInline('phid', phid);
},
_queryInline: function(field, value) {
// First, look for the inline in the objects we've already built.
var inline = this._findInline(field, value);
if (inline) {
return inline;
}
// If we haven't found a matching inline yet, rebuild all the inlines
// present in the document, then look again.
this._rebuildAllInlines();
return this._findInline(field, value);
},
_findInline: function(field, value) {
for (var ii = 0; ii < this._inlines.length; ii++) {
var inline = this._inlines[ii];
var target;
switch (field) {
case 'id':
target = inline.getID();
break;
case 'phid':
target = inline.getPHID();
break;
}
if (target == value) {
return inline;
}
}
return null;
},
getInlines: function() {
this._rebuildAllInlines();
return this._inlines;
},
_rebuildAllInlines: function() {
var rows = JX.DOM.scry(this._node, 'tr');
var ii;
for (ii = 0; ii < rows.length; ii++) {
var row = rows[ii];
if (this._getRowType(row) != 'comment') {
continue;
}
// As a side effect, this builds any missing inline objects and adds
// them to this Changeset's list of inlines.
this.getInlineForRow(row);
}
},
redrawFileTree: function() {
var inlines = this._inlines;
var done = [];
var undone = [];
var inline;
for (var ii = 0; ii < inlines.length; ii++) {
inline = inlines[ii];
if (inline.isDeleted()) {
continue;
}
if (inline.isSynthetic()) {
continue;
}
if (inline.isEditing()) {
continue;
}
if (!inline.getID()) {
// These are new comments which have been cancelled, and do not
// count as anything.
continue;
}
if (inline.isDraft()) {
continue;
}
if (!inline.isDone()) {
undone.push(inline);
} else {
done.push(inline);
}
}
var total = done.length + undone.length;
var hint;
var is_visible;
var is_completed;
if (total) {
if (done.length) {
hint = [done.length, '/', total];
} else {
hint = total;
}
is_visible = true;
is_completed = (done.length == total);
} else {
hint = '-';
is_visible = false;
is_completed = false;
}
var node = this.getPathView().getInlineNode();
JX.DOM.setContent(node, hint);
JX.DOM.alterClass(node, 'diff-tree-path-inlines-visible', is_visible);
JX.DOM.alterClass(node, 'diff-tree-path-inlines-completed', is_completed);
},
toggleVisibility: function() {
- this._visible = !this._visible;
+ this.setVisible(!this._visible);
+
+ var attrs = {
+ hidden: this.isVisible() ? 0 : 1,
+ discard: 1
+ };
+
+ var workflow = this._newReloadWorkflow(attrs)
+ .setHandler(JX.bag);
+
+ this._startContentWorkflow(workflow);
+ },
+
+ setVisible: function(visible) {
+ this._visible = visible;
var diff = JX.DOM.find(this._node, 'table', 'differential-diff');
var undo = this._getUndoNode();
if (this._visible) {
JX.DOM.show(diff);
JX.DOM.remove(undo);
} else {
JX.DOM.hide(diff);
JX.DOM.appendContent(diff.parentNode, undo);
}
JX.Stratcom.invoke('resize');
+
+ this.getPathView().setIsHidden(!this._visible);
},
isVisible: function() {
return this._visible;
},
_getUndoNode: function() {
if (!this._undoNode) {
var pht = this.getChangesetList().getTranslations();
var link_attributes = {
href: '#'
};
var undo_link = JX.$N('a', link_attributes, pht('Show Content'));
var onundo = JX.bind(this, this._onundo);
JX.DOM.listen(undo_link, 'click', null, onundo);
var node_attributes = {
className: 'differential-collapse-undo'
};
var node_content = [
pht('This file content has been collapsed.'),
' ',
undo_link
];
var undo_node = JX.$N('div', node_attributes, node_content);
this._undoNode = undo_node;
}
return this._undoNode;
},
_onundo: function(e) {
e.kill();
this.toggleVisibility();
},
getPathView: function() {
if (!this._pathView) {
var view = new JX.DiffPathView()
.setChangeset(this)
.setPath(this._pathParts)
.setIsLowImportance(this._isLowImportance)
- .setIsOwned(this._isOwned);
+ .setIsOwned(this._isOwned)
+ .setIsLoading(this._isLoading);
view.getIcon()
.setIcon(this._pathIconIcon)
.setColor(this._pathIconColor);
this._pathView = view;
}
return this._pathView;
},
select: function(scroll) {
this.getChangesetList().selectChangeset(this, scroll);
return this;
}
},
statics: {
getForNode: function(node) {
var data = JX.Stratcom.getData(node);
if (!data.changesetViewManager) {
data.changesetViewManager = new JX.DiffChangeset(node);
}
return data.changesetViewManager;
}
}
});
diff --git a/webroot/rsrc/js/application/diff/DiffPathView.js b/webroot/rsrc/js/application/diff/DiffPathView.js
index 92441254a9..58c8866c21 100644
--- a/webroot/rsrc/js/application/diff/DiffPathView.js
+++ b/webroot/rsrc/js/application/diff/DiffPathView.js
@@ -1,225 +1,265 @@
/**
* @provides phabricator-diff-path-view
* @requires javelin-dom
* @javelin
*/
JX.install('DiffPathView', {
construct: function() {
},
members: {
_node: null,
_path: null,
_depth: 0,
_selected: false,
_focused: false,
_icon: null,
_indentNode: null,
_pathNode: null,
_changeset: null,
_inlineNode: null,
_isDirectory: false,
_displayPath: null,
- _isOwned: false,
_isLowImportance: false,
+ _isOwned: false,
+ _isHidden: false,
+ _isLoading: false,
getNode: function() {
if (!this._node) {
var attrs = {
className: 'diff-tree-path'
};
this._node = JX.$N('li', attrs, this._getIndentNode());
var onclick = JX.bind(this, this._onclick);
JX.DOM.listen(this._node, 'click', null, onclick);
}
return this._node;
},
getIcon: function() {
if (!this._icon) {
this._icon = new JX.PHUIXIconView();
}
return this._icon;
},
setPath: function(path) {
this._path = path;
this._redrawPath();
return this;
},
setDisplayPath: function(path) {
this._displayPath = path;
this._redrawPath();
return this;
},
setIsDirectory: function(is_directory) {
this._isDirectory = is_directory;
this._redrawPath();
return this;
},
setChangeset: function(changeset) {
this._changeset = changeset;
var node = this.getNode();
JX.DOM.alterClass(node, 'diff-tree-path-changeset', !!changeset);
return this;
},
getChangeset: function() {
return this._changeset;
},
getPath: function() {
return this._path;
},
setHidden: function(hidden) {
this._hidden = hidden;
var node = this.getNode();
if (this._hidden) {
JX.DOM.hide(node);
} else {
JX.DOM.show(node);
}
return this;
},
setDepth: function(depth) {
this._depth = depth;
this._getIndentNode().style.marginLeft = (8 * this._depth) + 'px';
return this;
},
setIsSelected: function(selected) {
this._selected = selected;
var node = this.getNode();
JX.DOM.alterClass(node, 'diff-tree-path-selected', this._selected);
return this;
},
setIsFocused: function(focused) {
this._focused = focused;
var node = this.getNode();
JX.DOM.alterClass(node, 'diff-tree-path-focused', this._focused);
return this;
},
setIsLowImportance: function(low_importance) {
this._isLowImportance = low_importance;
var node = this.getNode();
JX.DOM.alterClass(
node,
'diff-tree-path-low-importance',
this._isLowImportance);
return this;
},
setIsOwned: function(owned) {
this._isOwned = owned;
var node = this.getNode();
JX.DOM.alterClass(node, 'diff-tree-path-owned', this._isOwned);
return this;
},
+ setIsHidden: function(hidden) {
+ this._isHidden = hidden;
+
+ var node = this.getNode();
+ JX.DOM.alterClass(node, 'diff-tree-path-hidden', this._isHidden);
+
+ return this;
+ },
+
+ setIsLoading: function(loading) {
+ this._isLoading = loading;
+
+ var node = this.getNode();
+ JX.DOM.alterClass(node, 'diff-tree-path-loading', this._isLoading);
+
+ return this;
+ },
+
_onclick: function(e) {
if (!e.isNormalClick()) {
return;
}
var changeset = this.getChangeset();
if (changeset) {
changeset.select(true);
}
e.kill();
},
_getIndentNode: function() {
if (!this._indentNode) {
var attrs = {
className: 'diff-tree-path-indent'
};
var content = [
this.getInlineNode(),
+ this._getHiddenIconNode(),
this._getIconNode(),
this._getPathNode(),
];
this._indentNode = JX.$N('div', attrs, content);
}
return this._indentNode;
},
_getPathNode: function() {
if (!this._pathNode) {
var attrs = {
className: 'diff-tree-path-name'
};
this._pathNode = JX.$N('div', attrs);
}
return this._pathNode;
},
_getIconNode: function() {
if (!this._iconNode) {
var attrs = {
- className: 'diff-tree-path-icon',
+ className: 'diff-tree-path-icon diff-tree-path-icon-kind',
};
this._iconNode = JX.$N('div', attrs, this.getIcon().getNode());
}
return this._iconNode;
},
+ _getHiddenIconNode: function() {
+ if (!this._hiddenIconNode) {
+ var attrs = {
+ className: 'diff-tree-path-icon diff-tree-path-icon-hidden',
+ };
+ this._hiddenIconNode =
+ JX.$N('div', attrs, this._getHiddenIcon().getNode());
+ }
+ return this._hiddenIconNode;
+ },
+
+ _getHiddenIcon: function() {
+ if (!this._hiddenIcon) {
+ this._hiddenIcon = new JX.PHUIXIconView()
+ .setIcon('fa-times-circle-o');
+ }
+ return this._hiddenIcon;
+ },
+
getInlineNode: function() {
if (!this._inlineNode) {
var attrs = {
className: 'diff-tree-path-inlines',
};
this._inlineNode = JX.$N('div', attrs, '-');
}
return this._inlineNode;
},
_redrawPath: function() {
var display;
if (this._displayPath) {
display = this._displayPath;
} else {
display = this._path[this._path.length - 1];
}
var is_directory = this._isDirectory;
if (is_directory) {
display = display + '/';
}
JX.DOM.setContent(this._getPathNode(), display);
}
}
});
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sun, Jan 19, 17:02 (2 w, 5 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1126755
Default Alt Text
(209 KB)
Attached To
Mode
rP Phorge
Attached
Detach File
Event Timeline
Log In to Comment