Page MenuHomePhorge

No OneTemporary

diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php
index 112f78e245..2bd974dff5 100644
--- a/src/__celerity_resource_map__.php
+++ b/src/__celerity_resource_map__.php
@@ -1,2179 +1,2179 @@
<?php
/**
* This file is automatically generated. Use 'celerity_mapper.php' to rebuild
* it.
* @generated
*/
celerity_register_resource_map(array(
'aphront-attached-file-view-css' =>
array(
'uri' => '/res/a6ca5487/rsrc/css/aphront/attached-file-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/attached-file-view.css',
),
'aphront-calendar-view-css' =>
array(
'uri' => '/res/c86d9a4b/rsrc/css/aphront/calendar-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/calendar-view.css',
),
'aphront-contextbar-view-css' =>
array(
'uri' => '/res/9e0c2ed7/rsrc/css/aphront/context-bar.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/context-bar.css',
),
'aphront-crumbs-view-css' =>
array(
'uri' => '/res/9009e6bd/rsrc/css/aphront/crumbs-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/crumbs-view.css',
),
'aphront-dark-console-css' =>
array(
'uri' => '/res/1a9f84bb/rsrc/css/aphront/dark-console.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/dark-console.css',
),
'aphront-dialog-view-css' =>
array(
'uri' => '/res/1c0a5f75/rsrc/css/aphront/dialog-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/dialog-view.css',
),
'aphront-error-view-css' =>
array(
'uri' => '/res/038f6022/rsrc/css/aphront/error-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/error-view.css',
),
'aphront-form-view-css' =>
array(
'uri' => '/res/38bc1599/rsrc/css/aphront/form-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/form-view.css',
),
'aphront-headsup-action-list-view-css' =>
array(
'uri' => '/res/84743e20/rsrc/css/aphront/headsup-action-list-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/headsup-action-list-view.css',
),
'aphront-list-filter-view-css' =>
array(
'uri' => '/res/0f5ddaba/rsrc/css/aphront/list-filter-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/list-filter-view.css',
),
'aphront-pager-view-css' =>
array(
'uri' => '/res/43fb79f0/rsrc/css/aphront/pager-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/pager-view.css',
),
'aphront-panel-view-css' =>
array(
'uri' => '/res/9f08d754/rsrc/css/aphront/panel-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/panel-view.css',
),
'aphront-request-failure-view-css' =>
array(
'uri' => '/res/c9a43002/rsrc/css/aphront/request-failure-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/request-failure-view.css',
),
'aphront-side-nav-view-css' =>
array(
'uri' => '/res/ba0e18dd/rsrc/css/aphront/side-nav-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/side-nav-view.css',
),
'aphront-table-view-css' =>
array(
'uri' => '/res/f4f39a2e/rsrc/css/aphront/table-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/table-view.css',
),
'aphront-tokenizer-control-css' =>
array(
'uri' => '/res/f530af47/rsrc/css/aphront/tokenizer.css',
'type' => 'css',
'requires' =>
array(
0 => 'aphront-typeahead-control-css',
),
'disk' => '/rsrc/css/aphront/tokenizer.css',
),
'aphront-typeahead-control-css' =>
array(
'uri' => '/res/311e8830/rsrc/css/aphront/typeahead.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/typeahead.css',
),
'differential-changeset-view-css' =>
array(
'uri' => '/res/45e4ad43/rsrc/css/application/differential/changeset-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/differential/changeset-view.css',
),
'differential-core-view-css' =>
array(
'uri' => '/res/0bc9223a/rsrc/css/application/differential/core.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/differential/core.css',
),
'differential-inline-comment-editor' =>
array(
'uri' => '/res/c3be229c/rsrc/js/application/differential/DifferentialInlineCommentEditor.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-dom',
1 => 'javelin-util',
2 => 'javelin-stratcom',
3 => 'javelin-install',
4 => 'javelin-request',
5 => 'javelin-workflow',
),
'disk' => '/rsrc/js/application/differential/DifferentialInlineCommentEditor.js',
),
'differential-local-commits-view-css' =>
array(
'uri' => '/res/8cdacd82/rsrc/css/application/differential/local-commits-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/differential/local-commits-view.css',
),
'differential-revision-add-comment-css' =>
array(
'uri' => '/res/849748d3/rsrc/css/application/differential/add-comment.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/differential/add-comment.css',
),
'differential-revision-comment-css' =>
array(
'uri' => '/res/7a0002f1/rsrc/css/application/differential/revision-comment.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/differential/revision-comment.css',
),
'differential-revision-comment-list-css' =>
array(
'uri' => '/res/3b31faa3/rsrc/css/application/differential/revision-comment-list.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/differential/revision-comment-list.css',
),
'differential-revision-detail-css' =>
array(
'uri' => '/res/2433dbdc/rsrc/css/application/differential/revision-detail.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/differential/revision-detail.css',
),
'differential-revision-history-css' =>
array(
'uri' => '/res/0d7d515d/rsrc/css/application/differential/revision-history.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/differential/revision-history.css',
),
'differential-table-of-contents-css' =>
array(
- 'uri' => '/res/e4c089fe/rsrc/css/application/differential/table-of-contents.css',
+ 'uri' => '/res/a633259f/rsrc/css/application/differential/table-of-contents.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/differential/table-of-contents.css',
),
'diffusion-commit-view-css' =>
array(
'uri' => '/res/d486f79a/rsrc/css/application/diffusion/commit-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/diffusion/commit-view.css',
),
'diffusion-source-css' =>
array(
'uri' => '/res/5f5ac1d6/rsrc/css/application/diffusion/diffusion-source.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/diffusion/diffusion-source.css',
),
'files-css' =>
array(
'uri' => '/res/a265a77d/rsrc/css/application/files/files.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/files/files.css',
),
'herald-css' =>
array(
'uri' => '/res/ed5556e6/rsrc/css/application/herald/herald.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/herald/herald.css',
),
'herald-rule-editor' =>
array(
'uri' => '/res/745c9d43/rsrc/js/application/herald/HeraldRuleEditor.js',
'type' => 'js',
'requires' =>
array(
0 => 'multirow-row-manager',
1 => 'javelin-install',
2 => 'javelin-typeahead',
3 => 'javelin-util',
4 => 'javelin-dom',
5 => 'javelin-tokenizer',
6 => 'javelin-typeahead-preloaded-source',
7 => 'javelin-stratcom',
8 => 'javelin-json',
9 => 'phabricator-prefab',
),
'disk' => '/rsrc/js/application/herald/HeraldRuleEditor.js',
),
'herald-test-css' =>
array(
'uri' => '/res/c0cd6bdb/rsrc/css/application/herald/herald-test.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/herald/herald-test.css',
),
'javelin-aphlict' =>
array(
'uri' => '/res/50cae715/rsrc/js/application/aphlict/Aphlict.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
),
'disk' => '/rsrc/js/application/aphlict/Aphlict.js',
),
'javelin-behavior' =>
array(
'uri' => '/res/0017f840/rsrc/js/javelin/lib/behavior.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-magical-init',
),
'disk' => '/rsrc/js/javelin/lib/behavior.js',
),
'javelin-behavior-aphlict-listen' =>
array(
'uri' => '/res/6388e057/rsrc/js/application/aphlict/behavior-aphlict-listen.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-aphlict',
2 => 'javelin-util',
3 => 'javelin-stratcom',
),
'disk' => '/rsrc/js/application/aphlict/behavior-aphlict-listen.js',
),
'javelin-behavior-aphront-basic-tokenizer' =>
array(
'uri' => '/res/cf049052/rsrc/js/application/core/behavior-tokenizer.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'phabricator-prefab',
),
'disk' => '/rsrc/js/application/core/behavior-tokenizer.js',
),
'javelin-behavior-aphront-drag-and-drop' =>
array(
'uri' => '/res/ac21045a/rsrc/js/application/core/behavior-drag-and-drop.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-util',
3 => 'phabricator-drag-and-drop-file-upload',
),
'disk' => '/rsrc/js/application/core/behavior-drag-and-drop.js',
),
'javelin-behavior-aphront-drag-and-drop-textarea' =>
array(
'uri' => '/res/65980508/rsrc/js/application/core/behavior-drag-and-drop-textarea.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'phabricator-drag-and-drop-file-upload',
3 => 'phabricator-paste-file-upload',
),
'disk' => '/rsrc/js/application/core/behavior-drag-and-drop-textarea.js',
),
'javelin-behavior-aphront-form-disable-on-submit' =>
array(
'uri' => '/res/6c659ede/rsrc/js/application/core/behavior-form.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/core/behavior-form.js',
),
'javelin-behavior-audit-preview' =>
array(
'uri' => '/res/3048b073/rsrc/js/application/diffusion/behavior-audit-preview.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-util',
3 => 'phabricator-shaped-request',
),
'disk' => '/rsrc/js/application/diffusion/behavior-audit-preview.js',
),
'javelin-behavior-buoyant' =>
array(
'uri' => '/res/e7581db1/rsrc/js/application/core/behavior-buoyant.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-vector',
3 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/core/behavior-buoyant.js',
),
'javelin-behavior-burn-chart' =>
array(
'uri' => '/res/ed1bf018/rsrc/js/application/maniphest/behavior-burn-chart.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-vector',
),
'disk' => '/rsrc/js/application/maniphest/behavior-burn-chart.js',
),
'javelin-behavior-countdown-timer' =>
array(
'uri' => '/res/5ee9cb13/rsrc/js/application/countdown/timer.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-util',
),
'disk' => '/rsrc/js/application/countdown/timer.js',
),
'javelin-behavior-dark-console' =>
array(
'uri' => '/res/c80156c4/rsrc/js/application/core/behavior-dark-console.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-util',
3 => 'javelin-dom',
4 => 'javelin-request',
5 => 'phabricator-keyboard-shortcut',
),
'disk' => '/rsrc/js/application/core/behavior-dark-console.js',
),
'javelin-behavior-differential-accept-with-errors' =>
array(
'uri' => '/res/41c4685b/rsrc/js/application/differential/behavior-accept-with-errors.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/differential/behavior-accept-with-errors.js',
),
'javelin-behavior-differential-add-reviewers-and-ccs' =>
array(
'uri' => '/res/27be3f81/rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'phabricator-prefab',
),
'disk' => '/rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js',
),
'javelin-behavior-differential-comment-jump' =>
array(
'uri' => '/res/be77fced/rsrc/js/application/differential/behavior-comment-jump.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-util',
2 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/differential/behavior-comment-jump.js',
),
'javelin-behavior-differential-diff-radios' =>
array(
'uri' => '/res/004cb66f/rsrc/js/application/differential/behavior-diff-radios.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/differential/behavior-diff-radios.js',
),
'javelin-behavior-differential-dropdown-menus' =>
array(
'uri' => '/res/4bb3ae9a/rsrc/js/application/differential/behavior-dropdown-menus.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-util',
3 => 'javelin-stratcom',
4 => 'phabricator-dropdown-menu',
5 => 'phabricator-menu-item',
),
'disk' => '/rsrc/js/application/differential/behavior-dropdown-menus.js',
),
'javelin-behavior-differential-edit-inline-comments' =>
array(
'uri' => '/res/52ce0fe5/rsrc/js/application/differential/behavior-edit-inline-comments.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-dom',
3 => 'javelin-util',
4 => 'javelin-vector',
5 => 'differential-inline-comment-editor',
),
'disk' => '/rsrc/js/application/differential/behavior-edit-inline-comments.js',
),
'javelin-behavior-differential-feedback-preview' =>
array(
'uri' => '/res/768b60c9/rsrc/js/application/differential/behavior-comment-preview.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-dom',
3 => 'javelin-request',
4 => 'javelin-util',
5 => 'phabricator-shaped-request',
),
'disk' => '/rsrc/js/application/differential/behavior-comment-preview.js',
),
'javelin-behavior-differential-keyboard-navigation' =>
array(
'uri' => '/res/f5ce5987/rsrc/js/application/differential/behavior-keyboard-nav.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-stratcom',
3 => 'phabricator-keyboard-shortcut',
),
'disk' => '/rsrc/js/application/differential/behavior-keyboard-nav.js',
),
'javelin-behavior-differential-populate' =>
array(
- 'uri' => '/res/6efe5cd2/rsrc/js/application/differential/behavior-populate.js',
+ 'uri' => '/res/3c430bff/rsrc/js/application/differential/behavior-populate.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-workflow',
2 => 'javelin-util',
3 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/differential/behavior-populate.js',
),
'javelin-behavior-differential-show-all-comments' =>
array(
'uri' => '/res/eaa12efc/rsrc/js/application/differential/behavior-show-all-comments.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/differential/behavior-show-all-comments.js',
),
'javelin-behavior-differential-show-more' =>
array(
'uri' => '/res/68a8e485/rsrc/js/application/differential/behavior-show-more.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-workflow',
3 => 'javelin-util',
4 => 'javelin-stratcom',
),
'disk' => '/rsrc/js/application/differential/behavior-show-more.js',
),
'javelin-behavior-diffusion-jump-to' =>
array(
'uri' => '/res/7c42e1ba/rsrc/js/application/diffusion/behavior-jump-to.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-util',
2 => 'javelin-vector',
3 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/diffusion/behavior-jump-to.js',
),
'javelin-behavior-diffusion-pull-lastmodified' =>
array(
'uri' => '/res/29fe2790/rsrc/js/application/diffusion/behavior-pull-lastmodified.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-util',
3 => 'javelin-request',
),
'disk' => '/rsrc/js/application/diffusion/behavior-pull-lastmodified.js',
),
'javelin-behavior-error-log' =>
array(
'uri' => '/res/a5cb42a5/rsrc/js/application/core/behavior-error-log.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/core/behavior-error-log.js',
),
'javelin-behavior-files-drag-and-drop' =>
array(
'uri' => '/res/0e84cc42/rsrc/js/application/core/behavior-files-drag-and-drop.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-uri',
3 => 'phabricator-drag-and-drop-file-upload',
),
'disk' => '/rsrc/js/application/core/behavior-files-drag-and-drop.js',
),
'javelin-behavior-herald-rule-editor' =>
array(
'uri' => '/res/77a0c945/rsrc/js/application/herald/herald-rule-editor.js',
'type' => 'js',
'requires' =>
array(
0 => 'herald-rule-editor',
1 => 'javelin-behavior',
),
'disk' => '/rsrc/js/application/herald/herald-rule-editor.js',
),
'javelin-behavior-maniphest-batch-editor' =>
array(
'uri' => '/res/d7b7f061/rsrc/js/application/maniphest/behavior-batch-editor.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-util',
3 => 'phabricator-prefab',
4 => 'multirow-row-manager',
5 => 'javelin-tokenizer',
6 => 'javelin-typeahead-preloaded-source',
7 => 'javelin-typeahead',
8 => 'javelin-json',
),
'disk' => '/rsrc/js/application/maniphest/behavior-batch-editor.js',
),
'javelin-behavior-maniphest-batch-selector' =>
array(
'uri' => '/res/398cf8d7/rsrc/js/application/maniphest/behavior-batch-selector.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-stratcom',
),
'disk' => '/rsrc/js/application/maniphest/behavior-batch-selector.js',
),
'javelin-behavior-maniphest-description-preview' =>
array(
'uri' => '/res/8acd6f07/rsrc/js/application/maniphest/behavior-task-preview.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-util',
3 => 'phabricator-shaped-request',
),
'disk' => '/rsrc/js/application/maniphest/behavior-task-preview.js',
),
'javelin-behavior-maniphest-project-create' =>
array(
'uri' => '/res/85a0eaf9/rsrc/js/application/maniphest/behavior-project-create.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-stratcom',
3 => 'javelin-workflow',
),
'disk' => '/rsrc/js/application/maniphest/behavior-project-create.js',
),
'javelin-behavior-maniphest-transaction-controls' =>
array(
'uri' => '/res/62465554/rsrc/js/application/maniphest/behavior-transaction-controls.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'phabricator-prefab',
),
'disk' => '/rsrc/js/application/maniphest/behavior-transaction-controls.js',
),
'javelin-behavior-maniphest-transaction-expand' =>
array(
'uri' => '/res/966410de/rsrc/js/application/maniphest/behavior-transaction-expand.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-workflow',
3 => 'javelin-stratcom',
),
'disk' => '/rsrc/js/application/maniphest/behavior-transaction-expand.js',
),
'javelin-behavior-maniphest-transaction-preview' =>
array(
'uri' => '/res/44e86555/rsrc/js/application/maniphest/behavior-transaction-preview.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-util',
3 => 'phabricator-shaped-request',
),
'disk' => '/rsrc/js/application/maniphest/behavior-transaction-preview.js',
),
'javelin-behavior-owners-path-editor' =>
array(
'uri' => '/res/9cf78ffc/rsrc/js/application/owners/owners-path-editor.js',
'type' => 'js',
'requires' =>
array(
0 => 'owners-path-editor',
1 => 'javelin-behavior',
),
'disk' => '/rsrc/js/application/owners/owners-path-editor.js',
),
'javelin-behavior-phabricator-autofocus' =>
array(
'uri' => '/res/2946bb89/rsrc/js/application/core/behavior-autofocus.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/core/behavior-autofocus.js',
),
'javelin-behavior-phabricator-keyboard-pager' =>
array(
'uri' => '/res/56d64eff/rsrc/js/application/core/behavior-keyboard-pager.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-uri',
2 => 'phabricator-keyboard-shortcut',
),
'disk' => '/rsrc/js/application/core/behavior-keyboard-pager.js',
),
'javelin-behavior-phabricator-keyboard-shortcuts' =>
array(
'uri' => '/res/ea3ea05e/rsrc/js/application/core/behavior-keyboard-shortcuts.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-workflow',
2 => 'javelin-json',
3 => 'phabricator-keyboard-shortcut',
),
'disk' => '/rsrc/js/application/core/behavior-keyboard-shortcuts.js',
),
'javelin-behavior-phabricator-object-selector' =>
array(
'uri' => '/res/73f3fbcf/rsrc/js/application/core/behavior-object-selector.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-request',
3 => 'javelin-util',
4 => 'javelin-stratcom',
),
'disk' => '/rsrc/js/application/core/behavior-object-selector.js',
),
'javelin-behavior-phabricator-watch-anchor' =>
array(
'uri' => '/res/880e3de4/rsrc/js/application/core/behavior-watch-anchor.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-dom',
3 => 'javelin-vector',
),
'disk' => '/rsrc/js/application/core/behavior-watch-anchor.js',
),
'javelin-behavior-phriction-document-preview' =>
array(
'uri' => '/res/f1665ecd/rsrc/js/application/phriction/phriction-document-preview.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-util',
3 => 'phabricator-shaped-request',
),
'disk' => '/rsrc/js/application/phriction/phriction-document-preview.js',
),
'javelin-behavior-projects-resource-editor' =>
array(
'uri' => '/res/ffdde7d9/rsrc/js/application/projects/projects-resource-editor.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'phabricator-prefab',
2 => 'multirow-row-manager',
3 => 'javelin-tokenizer',
4 => 'javelin-typeahead-preloaded-source',
5 => 'javelin-typeahead',
6 => 'javelin-dom',
7 => 'javelin-json',
8 => 'javelin-util',
),
'disk' => '/rsrc/js/application/projects/projects-resource-editor.js',
),
'javelin-behavior-refresh-csrf' =>
array(
'uri' => '/res/88beba4c/rsrc/js/application/core/behavior-refresh-csrf.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-request',
1 => 'javelin-behavior',
2 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/core/behavior-refresh-csrf.js',
),
'javelin-behavior-repository-crossreference' =>
array(
'uri' => '/res/49472f48/rsrc/js/application/repository/repository-crossreference.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-uri',
),
'disk' => '/rsrc/js/application/repository/repository-crossreference.js',
),
'javelin-behavior-view-placeholder' =>
array(
'uri' => '/res/5b89bdf5/rsrc/js/javelin/ext/view/ViewPlaceholder.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-dom',
2 => 'javelin-view-renderer',
),
'disk' => '/rsrc/js/javelin/ext/view/ViewPlaceholder.js',
),
'javelin-behavior-workflow' =>
array(
'uri' => '/res/079f49c3/rsrc/js/application/core/behavior-workflow.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-behavior',
1 => 'javelin-stratcom',
2 => 'javelin-workflow',
),
'disk' => '/rsrc/js/application/core/behavior-workflow.js',
),
'javelin-color' =>
array(
'uri' => '/res/b0439fc9/rsrc/js/javelin/ext/fx/Color.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
),
'disk' => '/rsrc/js/javelin/ext/fx/Color.js',
),
'javelin-cookie' =>
array(
'uri' => '/res/a9cddab0/rsrc/js/javelin/lib/Cookie.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
),
'disk' => '/rsrc/js/javelin/lib/Cookie.js',
),
'javelin-dom' =>
array(
'uri' => '/res/4c86aaeb/rsrc/js/javelin/lib/DOM.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-magical-init',
1 => 'javelin-install',
2 => 'javelin-util',
3 => 'javelin-vector',
4 => 'javelin-stratcom',
),
'disk' => '/rsrc/js/javelin/lib/DOM.js',
),
'javelin-dynval' =>
array(
'uri' => '/res/d89c6f88/rsrc/js/javelin/ext/reactor/core/DynVal.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-reactornode',
2 => 'javelin-util',
3 => 'javelin-reactor',
),
'disk' => '/rsrc/js/javelin/ext/reactor/core/DynVal.js',
),
'javelin-event' =>
array(
'uri' => '/res/f42fa6ea/rsrc/js/javelin/core/Event.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
),
'disk' => '/rsrc/js/javelin/core/Event.js',
),
'javelin-fx' =>
array(
'uri' => '/res/97e25a7f/rsrc/js/javelin/ext/fx/FX.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-color',
1 => 'javelin-install',
2 => 'javelin-util',
),
'disk' => '/rsrc/js/javelin/ext/fx/FX.js',
),
'javelin-history' =>
array(
'uri' => '/res/9bb36651/rsrc/js/javelin/lib/History.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-stratcom',
1 => 'javelin-install',
2 => 'javelin-uri',
3 => 'javelin-util',
),
'disk' => '/rsrc/js/javelin/lib/History.js',
),
'javelin-install' =>
array(
'uri' => '/res/cab679ff/rsrc/js/javelin/core/install.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-util',
1 => 'javelin-magical-init',
),
'disk' => '/rsrc/js/javelin/core/install.js',
),
'javelin-json' =>
array(
'uri' => '/res/561b8056/rsrc/js/javelin/lib/JSON.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
),
'disk' => '/rsrc/js/javelin/lib/JSON.js',
),
'javelin-magical-init' =>
array(
'uri' => '/res/caa86a45/rsrc/js/javelin/core/init.js',
'type' => 'js',
'requires' =>
array(
),
'disk' => '/rsrc/js/javelin/core/init.js',
),
'javelin-mask' =>
array(
'uri' => '/res/03ef78b8/rsrc/js/javelin/lib/Mask.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-vector',
2 => 'javelin-dom',
),
'disk' => '/rsrc/js/javelin/lib/Mask.js',
),
'javelin-reactor' =>
array(
'uri' => '/res/dfd87f3c/rsrc/js/javelin/ext/reactor/core/Reactor.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
),
'disk' => '/rsrc/js/javelin/ext/reactor/core/Reactor.js',
),
'javelin-reactor-dom' =>
array(
'uri' => '/res/701b6f39/rsrc/js/javelin/ext/reactor/dom/RDOM.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-dom',
1 => 'javelin-dynval',
2 => 'javelin-reactornode',
3 => 'javelin-install',
4 => 'javelin-util',
),
'disk' => '/rsrc/js/javelin/ext/reactor/dom/RDOM.js',
),
'javelin-reactor-node-calmer' =>
array(
'uri' => '/res/5a35920a/rsrc/js/javelin/ext/reactor/core/ReactorNodeCalmer.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-reactor',
2 => 'javelin-util',
),
'disk' => '/rsrc/js/javelin/ext/reactor/core/ReactorNodeCalmer.js',
),
'javelin-reactornode' =>
array(
'uri' => '/res/f278cc27/rsrc/js/javelin/ext/reactor/core/ReactorNode.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-reactor',
2 => 'javelin-util',
3 => 'javelin-reactor-node-calmer',
),
'disk' => '/rsrc/js/javelin/ext/reactor/core/ReactorNode.js',
),
'javelin-request' =>
array(
'uri' => '/res/6ccc1d5a/rsrc/js/javelin/lib/Request.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-stratcom',
2 => 'javelin-util',
3 => 'javelin-behavior',
4 => 'javelin-json',
5 => 'javelin-dom',
),
'disk' => '/rsrc/js/javelin/lib/Request.js',
),
'javelin-resource' =>
array(
'uri' => '/res/1ebc5a0d/rsrc/js/javelin/lib/Resource.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-magical-init',
1 => 'javelin-stratcom',
2 => 'javelin-util',
3 => 'javelin-uri',
),
'disk' => '/rsrc/js/javelin/lib/Resource.js',
),
'javelin-stratcom' =>
array(
'uri' => '/res/3afdac66/rsrc/js/javelin/core/Stratcom.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-event',
2 => 'javelin-util',
3 => 'javelin-magical-init',
),
'disk' => '/rsrc/js/javelin/core/Stratcom.js',
),
'javelin-tokenizer' =>
array(
'uri' => '/res/a8275d0d/rsrc/js/javelin/lib/control/tokenizer/Tokenizer.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-dom',
1 => 'javelin-util',
2 => 'javelin-stratcom',
3 => 'javelin-install',
),
'disk' => '/rsrc/js/javelin/lib/control/tokenizer/Tokenizer.js',
),
'javelin-typeahead' =>
array(
'uri' => '/res/2f694700/rsrc/js/javelin/lib/control/typeahead/Typeahead.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-dom',
2 => 'javelin-vector',
3 => 'javelin-util',
),
'disk' => '/rsrc/js/javelin/lib/control/typeahead/Typeahead.js',
),
'javelin-typeahead-composite-source' =>
array(
'uri' => '/res/7c0d631f/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-typeahead-source',
2 => 'javelin-util',
),
'disk' => '/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadCompositeSource.js',
),
'javelin-typeahead-normalizer' =>
array(
'uri' => '/res/a9e97c0d/rsrc/js/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
),
'disk' => '/rsrc/js/javelin/lib/control/typeahead/normalizer/TypeaheadNormalizer.js',
),
'javelin-typeahead-ondemand-source' =>
array(
'uri' => '/res/81e531aa/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-stratcom',
3 => 'javelin-request',
4 => 'javelin-typeahead-source',
),
'disk' => '/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadOnDemandSource.js',
),
'javelin-typeahead-preloaded-source' =>
array(
'uri' => '/res/d464efd2/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-stratcom',
3 => 'javelin-request',
4 => 'javelin-typeahead-source',
),
'disk' => '/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadPreloadedSource.js',
),
'javelin-typeahead-source' =>
array(
'uri' => '/res/ff342bbb/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadSource.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-dom',
3 => 'javelin-typeahead-normalizer',
),
'disk' => '/rsrc/js/javelin/lib/control/typeahead/source/TypeaheadSource.js',
),
'javelin-uri' =>
array(
'uri' => '/res/393ace00/rsrc/js/javelin/lib/URI.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-stratcom',
),
'disk' => '/rsrc/js/javelin/lib/URI.js',
),
'javelin-util' =>
array(
'uri' => '/res/2180bc95/rsrc/js/javelin/core/util.js',
'type' => 'js',
'requires' =>
array(
),
'disk' => '/rsrc/js/javelin/core/util.js',
),
'javelin-vector' =>
array(
'uri' => '/res/f240bdb3/rsrc/js/javelin/lib/Vector.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-event',
),
'disk' => '/rsrc/js/javelin/lib/Vector.js',
),
'javelin-view' =>
array(
'uri' => '/res/b98657a7/rsrc/js/javelin/ext/view/View.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
),
'disk' => '/rsrc/js/javelin/ext/view/View.js',
),
'javelin-view-html' =>
array(
'uri' => '/res/7e5a2122/rsrc/js/javelin/ext/view/HTMLView.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-view',
),
'disk' => '/rsrc/js/javelin/ext/view/HTMLView.js',
),
'javelin-view-interpreter' =>
array(
'uri' => '/res/17e911ca/rsrc/js/javelin/ext/view/ViewInterpreter.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-view',
1 => 'javelin-install',
),
'disk' => '/rsrc/js/javelin/ext/view/ViewInterpreter.js',
),
'javelin-view-renderer' =>
array(
'uri' => '/res/db4ed5a2/rsrc/js/javelin/ext/view/ViewRenderer.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
),
'disk' => '/rsrc/js/javelin/ext/view/ViewRenderer.js',
),
'javelin-view-visitor' =>
array(
'uri' => '/res/0ef9dc43/rsrc/js/javelin/ext/view/ViewVisitor.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
),
'disk' => '/rsrc/js/javelin/ext/view/ViewVisitor.js',
),
'javelin-workflow' =>
array(
'uri' => '/res/519c4e1a/rsrc/js/javelin/lib/Workflow.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-stratcom',
1 => 'javelin-request',
2 => 'javelin-dom',
3 => 'javelin-vector',
4 => 'javelin-install',
5 => 'javelin-util',
6 => 'javelin-mask',
7 => 'javelin-uri',
),
'disk' => '/rsrc/js/javelin/lib/Workflow.js',
),
'maniphest-batch-editor' =>
array(
'uri' => '/res/fb15d744/rsrc/css/application/maniphest/batch-editor.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/maniphest/batch-editor.css',
),
'maniphest-report-css' =>
array(
'uri' => '/res/2e633fcf/rsrc/css/application/maniphest/report.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/maniphest/report.css',
),
'maniphest-task-detail-css' =>
array(
'uri' => '/res/15c28f68/rsrc/css/application/maniphest/task-detail.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/maniphest/task-detail.css',
),
'maniphest-task-edit-css' =>
array(
'uri' => '/res/68c7863e/rsrc/css/application/maniphest/task-edit.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/maniphest/task-edit.css',
),
'maniphest-task-summary-css' =>
array(
'uri' => '/res/9d8a5516/rsrc/css/application/maniphest/task-summary.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/maniphest/task-summary.css',
),
'maniphest-transaction-detail-css' =>
array(
'uri' => '/res/24e5862f/rsrc/css/application/maniphest/transaction-detail.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/maniphest/transaction-detail.css',
),
'multirow-row-manager' =>
array(
'uri' => '/res/0a9b3dee/rsrc/js/application/core/MultirowRowManager.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-stratcom',
2 => 'javelin-dom',
3 => 'javelin-util',
),
'disk' => '/rsrc/js/application/core/MultirowRowManager.js',
),
'owners-path-editor' =>
array(
'uri' => '/res/e6c51eb6/rsrc/js/application/owners/OwnersPathEditor.js',
'type' => 'js',
'requires' =>
array(
0 => 'multirow-row-manager',
1 => 'javelin-install',
2 => 'path-typeahead',
3 => 'javelin-dom',
4 => 'javelin-util',
),
'disk' => '/rsrc/js/application/owners/OwnersPathEditor.js',
),
'owners-path-editor-css' =>
array(
'uri' => '/res/9bc5332c/rsrc/css/application/owners/owners-path-editor.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/owners/owners-path-editor.css',
),
'path-typeahead' =>
array(
'uri' => '/res/50246fb6/rsrc/js/application/herald/PathTypeahead.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-typeahead',
2 => 'javelin-dom',
3 => 'javelin-request',
4 => 'javelin-typeahead-ondemand-source',
5 => 'javelin-util',
),
'disk' => '/rsrc/js/application/herald/PathTypeahead.js',
),
'phabricator-app-buttons-css' =>
array(
'uri' => '/res/7d97b5e0/rsrc/css/application/directory/phabricator-app-buttons.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/directory/phabricator-app-buttons.css',
),
'phabricator-chatlog-css' =>
array(
'uri' => '/res/f674f526/rsrc/css/application/chatlog/chatlog.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/chatlog/chatlog.css',
),
'phabricator-content-source-view-css' =>
array(
'uri' => '/res/8c738a93/rsrc/css/application/contentsource/content-source-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/contentsource/content-source-view.css',
),
'phabricator-core-buttons-css' =>
array(
'uri' => '/res/89b939ae/rsrc/css/core/buttons.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/core/buttons.css',
),
'phabricator-core-css' =>
array(
'uri' => '/res/f912ffab/rsrc/css/core/core.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/core/core.css',
),
'phabricator-countdown-css' =>
array(
'uri' => '/res/0f646281/rsrc/css/application/countdown/timer.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/countdown/timer.css',
),
'phabricator-directory-css' =>
array(
'uri' => '/res/61afca2b/rsrc/css/application/directory/phabricator-directory.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/directory/phabricator-directory.css',
),
'phabricator-drag-and-drop-file-upload' =>
array(
'uri' => '/res/63a06ad9/rsrc/js/application/core/DragAndDropFileUpload.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-request',
3 => 'javelin-dom',
4 => 'javelin-uri',
),
'disk' => '/rsrc/js/application/core/DragAndDropFileUpload.js',
),
'phabricator-dropdown-menu' =>
array(
'uri' => '/res/d55c3771/rsrc/js/application/core/DropdownMenu.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-dom',
3 => 'javelin-vector',
4 => 'javelin-stratcom',
5 => 'phabricator-menu-item',
),
'disk' => '/rsrc/js/application/core/DropdownMenu.js',
),
'phabricator-feed-css' =>
array(
'uri' => '/res/e4bf27b5/rsrc/css/application/feed/feed.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/feed/feed.css',
),
'phabricator-jump-nav' =>
array(
'uri' => '/res/8bdc0fc3/rsrc/css/application/directory/phabricator-jump-nav.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/directory/phabricator-jump-nav.css',
),
'phabricator-keyboard-shortcut' =>
array(
'uri' => '/res/beed38cd/rsrc/js/application/core/KeyboardShortcut.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'phabricator-keyboard-shortcut-manager',
),
'disk' => '/rsrc/js/application/core/KeyboardShortcut.js',
),
'phabricator-keyboard-shortcut-manager' =>
array(
'uri' => '/res/0be80136/rsrc/js/application/core/KeyboardShortcutManager.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-stratcom',
3 => 'javelin-dom',
4 => 'javelin-vector',
),
'disk' => '/rsrc/js/application/core/KeyboardShortcutManager.js',
),
'phabricator-menu-item' =>
array(
'uri' => '/res/32fc2325/rsrc/js/application/core/DropdownMenuItem.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/core/DropdownMenuItem.js',
),
'phabricator-object-selector-css' =>
array(
'uri' => '/res/608461d2/rsrc/css/application/objectselector/object-selector.css',
'type' => 'css',
'requires' =>
array(
0 => 'aphront-dialog-view-css',
),
'disk' => '/rsrc/css/application/objectselector/object-selector.css',
),
'phabricator-paste-file-upload' =>
array(
'uri' => '/res/cdc939bd/rsrc/js/application/core/PasteFileUpload.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-request',
3 => 'javelin-dom',
4 => 'javelin-uri',
),
'disk' => '/rsrc/js/application/core/PasteFileUpload.js',
),
'phabricator-prefab' =>
array(
'uri' => '/res/956c8474/rsrc/js/application/core/Prefab.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-dom',
3 => 'javelin-typeahead',
4 => 'javelin-tokenizer',
5 => 'javelin-typeahead-preloaded-source',
6 => 'javelin-typeahead-ondemand-source',
7 => 'javelin-dom',
8 => 'javelin-stratcom',
9 => 'javelin-util',
),
'disk' => '/rsrc/js/application/core/Prefab.js',
),
'phabricator-profile-css' =>
array(
'uri' => '/res/9869d10b/rsrc/css/application/profile/profile-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/profile/profile-view.css',
),
'phabricator-profile-header-css' =>
array(
'uri' => '/res/4b1cb23b/rsrc/css/application/profile/profile-header-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/profile/profile-header-view.css',
),
'phabricator-remarkup-css' =>
array(
'uri' => '/res/3a0eb9e5/rsrc/css/core/remarkup.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/core/remarkup.css',
),
'phabricator-search-results-css' =>
array(
'uri' => '/res/f8a86e27/rsrc/css/application/search/search-results.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/search/search-results.css',
),
'phabricator-shaped-request' =>
array(
'uri' => '/res/59029fa9/rsrc/js/application/core/ShapedRequest.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-util',
2 => 'javelin-request',
),
'disk' => '/rsrc/js/application/core/ShapedRequest.js',
),
'phabricator-slowvote-css' =>
array(
'uri' => '/res/94d20443/rsrc/css/application/slowvote/slowvote.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/slowvote/slowvote.css',
),
'phabricator-standard-page-view' =>
array(
'uri' => '/res/7e09bbfc/rsrc/css/application/base/standard-page-view.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/base/standard-page-view.css',
),
'phabricator-transaction-view-css' =>
array(
'uri' => '/res/731959fb/rsrc/css/aphront/transaction.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/aphront/transaction.css',
),
'phabricator-ui-example-css' =>
array(
'uri' => '/res/0cef078b/rsrc/css/application/uiexample/example.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/uiexample/example.css',
),
'phabricator-uiexample-javelin-view' =>
array(
'uri' => '/res/a2ce2cfc/rsrc/js/application/uiexample/JavelinViewExample.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-view',
2 => 'javelin-util',
3 => 'javelin-dom',
),
'disk' => '/rsrc/js/application/uiexample/JavelinViewExample.js',
),
'phabricator-uiexample-reactor-button' =>
array(
'uri' => '/res/142127f6/rsrc/js/application/uiexample/ReactorButtonExample.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-view',
2 => 'javelin-util',
3 => 'javelin-dom',
4 => 'javelin-reactor-dom',
),
'disk' => '/rsrc/js/application/uiexample/ReactorButtonExample.js',
),
'phabricator-uiexample-reactor-checkbox' =>
array(
'uri' => '/res/c75cb9e9/rsrc/js/application/uiexample/ReactorCheckboxExample.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-view',
2 => 'javelin-util',
3 => 'javelin-dom',
4 => 'javelin-reactor-dom',
),
'disk' => '/rsrc/js/application/uiexample/ReactorCheckboxExample.js',
),
'phabricator-uiexample-reactor-focus' =>
array(
'uri' => '/res/3cc992eb/rsrc/js/application/uiexample/ReactorFocusExample.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-view',
2 => 'javelin-util',
3 => 'javelin-dom',
4 => 'javelin-reactor-dom',
),
'disk' => '/rsrc/js/application/uiexample/ReactorFocusExample.js',
),
'phabricator-uiexample-reactor-input' =>
array(
'uri' => '/res/4953da16/rsrc/js/application/uiexample/ReactorInputExample.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-view',
2 => 'javelin-util',
3 => 'javelin-dom',
4 => 'javelin-reactor-dom',
5 => 'javelin-view-html',
6 => 'javelin-view-interpreter',
7 => 'javelin-view-renderer',
),
'disk' => '/rsrc/js/application/uiexample/ReactorInputExample.js',
),
'phabricator-uiexample-reactor-mouseover' =>
array(
'uri' => '/res/52a355b6/rsrc/js/application/uiexample/ReactorMouseoverExample.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-view',
2 => 'javelin-util',
3 => 'javelin-dom',
4 => 'javelin-reactor-dom',
),
'disk' => '/rsrc/js/application/uiexample/ReactorMouseoverExample.js',
),
'phabricator-uiexample-reactor-radio' =>
array(
'uri' => '/res/ae87f3af/rsrc/js/application/uiexample/ReactorRadioExample.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-view',
2 => 'javelin-util',
3 => 'javelin-dom',
4 => 'javelin-reactor-dom',
),
'disk' => '/rsrc/js/application/uiexample/ReactorRadioExample.js',
),
'phabricator-uiexample-reactor-select' =>
array(
'uri' => '/res/23cb448a/rsrc/js/application/uiexample/ReactorSelectExample.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-view',
2 => 'javelin-util',
3 => 'javelin-dom',
4 => 'javelin-reactor-dom',
),
'disk' => '/rsrc/js/application/uiexample/ReactorSelectExample.js',
),
'phabricator-uiexample-reactor-sendclass' =>
array(
'uri' => '/res/8cd34264/rsrc/js/application/uiexample/ReactorSendClassExample.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-view',
2 => 'javelin-util',
3 => 'javelin-dom',
4 => 'javelin-reactor-dom',
),
'disk' => '/rsrc/js/application/uiexample/ReactorSendClassExample.js',
),
'phabricator-uiexample-reactor-sendproperties' =>
array(
'uri' => '/res/18af54aa/rsrc/js/application/uiexample/ReactorSendPropertiesExample.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-install',
1 => 'javelin-view',
2 => 'javelin-util',
3 => 'javelin-dom',
4 => 'javelin-reactor-dom',
),
'disk' => '/rsrc/js/application/uiexample/ReactorSendPropertiesExample.js',
),
'phriction-document-css' =>
array(
'uri' => '/res/8d09bd7f/rsrc/css/application/phriction/phriction-document-css.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/phriction/phriction-document-css.css',
),
'project-edit-css' =>
array(
'uri' => '/res/c192b5f9/rsrc/css/application/projects/project-edit.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/application/projects/project-edit.css',
),
'raphael-core' =>
array(
'uri' => '/res/bae05d27/rsrc/js/raphael/raphael.js',
'type' => 'js',
'requires' =>
array(
),
'disk' => '/rsrc/js/raphael/raphael.js',
),
'raphael-g' =>
array(
'uri' => '/res/8bbdbea8/rsrc/js/raphael/g.raphael.js',
'type' => 'js',
'requires' =>
array(
),
'disk' => '/rsrc/js/raphael/g.raphael.js',
),
'raphael-g-line' =>
array(
'uri' => '/res/54504ae4/rsrc/js/raphael/g.raphael.line.js',
'type' => 'js',
'requires' =>
array(
),
'disk' => '/rsrc/js/raphael/g.raphael.line.js',
),
'syntax-highlighting-css' =>
array(
'uri' => '/res/5669beb6/rsrc/css/core/syntax.css',
'type' => 'css',
'requires' =>
array(
),
'disk' => '/rsrc/css/core/syntax.css',
),
0 =>
array(
'uri' => '/res/b6096fdd/rsrc/js/javelin/lib/__tests__/URI.js',
'type' => 'js',
'requires' =>
array(
0 => 'javelin-uri',
1 => 'javelin-php-serializer',
),
'disk' => '/rsrc/js/javelin/lib/__tests__/URI.js',
),
), array(
'packages' =>
array(
- '09c86840' =>
- array(
- 'name' => 'differential.pkg.css',
- 'symbols' =>
- array(
- 0 => 'differential-core-view-css',
- 1 => 'differential-changeset-view-css',
- 2 => 'differential-revision-detail-css',
- 3 => 'differential-revision-history-css',
- 4 => 'differential-table-of-contents-css',
- 5 => 'differential-revision-comment-css',
- 6 => 'differential-revision-add-comment-css',
- 7 => 'differential-revision-comment-list-css',
- 8 => 'phabricator-object-selector-css',
- 9 => 'aphront-headsup-action-list-view-css',
- 10 => 'phabricator-content-source-view-css',
- 11 => 'differential-local-commits-view-css',
- ),
- 'uri' => '/res/pkg/09c86840/differential.pkg.css',
- 'type' => 'css',
- ),
'2af849fb' =>
array(
'name' => 'typeahead.pkg.js',
'symbols' =>
array(
0 => 'javelin-typeahead',
1 => 'javelin-typeahead-normalizer',
2 => 'javelin-typeahead-source',
3 => 'javelin-typeahead-preloaded-source',
4 => 'javelin-typeahead-ondemand-source',
5 => 'javelin-tokenizer',
6 => 'javelin-behavior-aphront-basic-tokenizer',
),
'uri' => '/res/pkg/2af849fb/typeahead.pkg.js',
'type' => 'js',
),
'4fbae2af' =>
array(
'name' => 'javelin.pkg.js',
'symbols' =>
array(
0 => 'javelin-util',
1 => 'javelin-install',
2 => 'javelin-event',
3 => 'javelin-stratcom',
4 => 'javelin-behavior',
5 => 'javelin-request',
6 => 'javelin-vector',
7 => 'javelin-dom',
8 => 'javelin-json',
9 => 'javelin-uri',
),
'uri' => '/res/pkg/4fbae2af/javelin.pkg.js',
'type' => 'js',
),
'61f9d480' =>
array(
'name' => 'diffusion.pkg.css',
'symbols' =>
array(
0 => 'diffusion-commit-view-css',
),
'uri' => '/res/pkg/61f9d480/diffusion.pkg.css',
'type' => 'css',
),
'78e8854e' =>
array(
'name' => 'core.pkg.css',
'symbols' =>
array(
0 => 'phabricator-core-css',
1 => 'phabricator-core-buttons-css',
2 => 'phabricator-standard-page-view',
3 => 'aphront-dialog-view-css',
4 => 'aphront-form-view-css',
5 => 'aphront-panel-view-css',
6 => 'aphront-side-nav-view-css',
7 => 'aphront-table-view-css',
8 => 'aphront-crumbs-view-css',
9 => 'aphront-tokenizer-control-css',
10 => 'aphront-typeahead-control-css',
11 => 'aphront-list-filter-view-css',
12 => 'phabricator-directory-css',
13 => 'phabricator-jump-nav',
14 => 'phabricator-app-buttons-css',
15 => 'phabricator-remarkup-css',
16 => 'syntax-highlighting-css',
17 => 'aphront-pager-view-css',
18 => 'phabricator-transaction-view-css',
),
'uri' => '/res/pkg/78e8854e/core.pkg.css',
'type' => 'css',
),
'86fc0b0c' =>
array(
'name' => 'maniphest.pkg.js',
'symbols' =>
array(
0 => 'javelin-behavior-maniphest-batch-selector',
1 => 'javelin-behavior-maniphest-transaction-controls',
2 => 'javelin-behavior-maniphest-transaction-preview',
3 => 'javelin-behavior-maniphest-transaction-expand',
),
'uri' => '/res/pkg/86fc0b0c/maniphest.pkg.js',
'type' => 'js',
),
- 'e8b28c4a' =>
+ '9d02b654' =>
+ array(
+ 'name' => 'differential.pkg.css',
+ 'symbols' =>
+ array(
+ 0 => 'differential-core-view-css',
+ 1 => 'differential-changeset-view-css',
+ 2 => 'differential-revision-detail-css',
+ 3 => 'differential-revision-history-css',
+ 4 => 'differential-table-of-contents-css',
+ 5 => 'differential-revision-comment-css',
+ 6 => 'differential-revision-add-comment-css',
+ 7 => 'differential-revision-comment-list-css',
+ 8 => 'phabricator-object-selector-css',
+ 9 => 'aphront-headsup-action-list-view-css',
+ 10 => 'phabricator-content-source-view-css',
+ 11 => 'differential-local-commits-view-css',
+ ),
+ 'uri' => '/res/pkg/9d02b654/differential.pkg.css',
+ 'type' => 'css',
+ ),
+ 'ffca9dae' =>
array(
'name' => 'differential.pkg.js',
'symbols' =>
array(
0 => 'phabricator-drag-and-drop-file-upload',
1 => 'phabricator-shaped-request',
2 => 'javelin-behavior-differential-feedback-preview',
3 => 'javelin-behavior-differential-edit-inline-comments',
4 => 'javelin-behavior-differential-populate',
5 => 'javelin-behavior-differential-show-more',
6 => 'javelin-behavior-differential-diff-radios',
7 => 'javelin-behavior-differential-accept-with-errors',
8 => 'javelin-behavior-differential-comment-jump',
9 => 'javelin-behavior-differential-add-reviewers-and-ccs',
10 => 'javelin-behavior-differential-keyboard-navigation',
11 => 'javelin-behavior-aphront-drag-and-drop',
12 => 'javelin-behavior-aphront-drag-and-drop-textarea',
13 => 'javelin-behavior-phabricator-object-selector',
14 => 'differential-inline-comment-editor',
15 => 'javelin-behavior-differential-dropdown-menus',
16 => 'javelin-behavior-buoyant',
),
- 'uri' => '/res/pkg/e8b28c4a/differential.pkg.js',
+ 'uri' => '/res/pkg/ffca9dae/differential.pkg.js',
'type' => 'js',
),
31583232 =>
array(
'name' => 'maniphest.pkg.css',
'symbols' =>
array(
0 => 'maniphest-task-summary-css',
1 => 'maniphest-transaction-detail-css',
2 => 'maniphest-task-detail-css',
3 => 'aphront-attached-file-view-css',
),
'uri' => '/res/pkg/31583232/maniphest.pkg.css',
'type' => 'css',
),
95944588 =>
array(
'name' => 'core.pkg.js',
'symbols' =>
array(
0 => 'javelin-mask',
1 => 'javelin-workflow',
2 => 'javelin-behavior-workflow',
3 => 'javelin-behavior-aphront-form-disable-on-submit',
4 => 'phabricator-keyboard-shortcut-manager',
5 => 'phabricator-keyboard-shortcut',
6 => 'javelin-behavior-phabricator-keyboard-shortcuts',
7 => 'javelin-behavior-refresh-csrf',
8 => 'javelin-behavior-phabricator-watch-anchor',
9 => 'javelin-behavior-phabricator-autofocus',
10 => 'phabricator-paste-file-upload',
11 => 'phabricator-menu-item',
12 => 'phabricator-dropdown-menu',
),
'uri' => '/res/pkg/95944588/core.pkg.js',
'type' => 'js',
),
),
'reverse' =>
array(
'aphront-attached-file-view-css' => '31583232',
'aphront-crumbs-view-css' => '78e8854e',
'aphront-dialog-view-css' => '78e8854e',
'aphront-form-view-css' => '78e8854e',
- 'aphront-headsup-action-list-view-css' => '09c86840',
+ 'aphront-headsup-action-list-view-css' => '9d02b654',
'aphront-list-filter-view-css' => '78e8854e',
'aphront-pager-view-css' => '78e8854e',
'aphront-panel-view-css' => '78e8854e',
'aphront-side-nav-view-css' => '78e8854e',
'aphront-table-view-css' => '78e8854e',
'aphront-tokenizer-control-css' => '78e8854e',
'aphront-typeahead-control-css' => '78e8854e',
- 'differential-changeset-view-css' => '09c86840',
- 'differential-core-view-css' => '09c86840',
- 'differential-inline-comment-editor' => 'e8b28c4a',
- 'differential-local-commits-view-css' => '09c86840',
- 'differential-revision-add-comment-css' => '09c86840',
- 'differential-revision-comment-css' => '09c86840',
- 'differential-revision-comment-list-css' => '09c86840',
- 'differential-revision-detail-css' => '09c86840',
- 'differential-revision-history-css' => '09c86840',
- 'differential-table-of-contents-css' => '09c86840',
+ 'differential-changeset-view-css' => '9d02b654',
+ 'differential-core-view-css' => '9d02b654',
+ 'differential-inline-comment-editor' => 'ffca9dae',
+ 'differential-local-commits-view-css' => '9d02b654',
+ 'differential-revision-add-comment-css' => '9d02b654',
+ 'differential-revision-comment-css' => '9d02b654',
+ 'differential-revision-comment-list-css' => '9d02b654',
+ 'differential-revision-detail-css' => '9d02b654',
+ 'differential-revision-history-css' => '9d02b654',
+ 'differential-table-of-contents-css' => '9d02b654',
'diffusion-commit-view-css' => '61f9d480',
'javelin-behavior' => '4fbae2af',
'javelin-behavior-aphront-basic-tokenizer' => '2af849fb',
- 'javelin-behavior-aphront-drag-and-drop' => 'e8b28c4a',
- 'javelin-behavior-aphront-drag-and-drop-textarea' => 'e8b28c4a',
+ 'javelin-behavior-aphront-drag-and-drop' => 'ffca9dae',
+ 'javelin-behavior-aphront-drag-and-drop-textarea' => 'ffca9dae',
'javelin-behavior-aphront-form-disable-on-submit' => '95944588',
- 'javelin-behavior-buoyant' => 'e8b28c4a',
- 'javelin-behavior-differential-accept-with-errors' => 'e8b28c4a',
- 'javelin-behavior-differential-add-reviewers-and-ccs' => 'e8b28c4a',
- 'javelin-behavior-differential-comment-jump' => 'e8b28c4a',
- 'javelin-behavior-differential-diff-radios' => 'e8b28c4a',
- 'javelin-behavior-differential-dropdown-menus' => 'e8b28c4a',
- 'javelin-behavior-differential-edit-inline-comments' => 'e8b28c4a',
- 'javelin-behavior-differential-feedback-preview' => 'e8b28c4a',
- 'javelin-behavior-differential-keyboard-navigation' => 'e8b28c4a',
- 'javelin-behavior-differential-populate' => 'e8b28c4a',
- 'javelin-behavior-differential-show-more' => 'e8b28c4a',
+ 'javelin-behavior-buoyant' => 'ffca9dae',
+ 'javelin-behavior-differential-accept-with-errors' => 'ffca9dae',
+ 'javelin-behavior-differential-add-reviewers-and-ccs' => 'ffca9dae',
+ 'javelin-behavior-differential-comment-jump' => 'ffca9dae',
+ 'javelin-behavior-differential-diff-radios' => 'ffca9dae',
+ 'javelin-behavior-differential-dropdown-menus' => 'ffca9dae',
+ 'javelin-behavior-differential-edit-inline-comments' => 'ffca9dae',
+ 'javelin-behavior-differential-feedback-preview' => 'ffca9dae',
+ 'javelin-behavior-differential-keyboard-navigation' => 'ffca9dae',
+ 'javelin-behavior-differential-populate' => 'ffca9dae',
+ 'javelin-behavior-differential-show-more' => 'ffca9dae',
'javelin-behavior-maniphest-batch-selector' => '86fc0b0c',
'javelin-behavior-maniphest-transaction-controls' => '86fc0b0c',
'javelin-behavior-maniphest-transaction-expand' => '86fc0b0c',
'javelin-behavior-maniphest-transaction-preview' => '86fc0b0c',
'javelin-behavior-phabricator-autofocus' => '95944588',
'javelin-behavior-phabricator-keyboard-shortcuts' => '95944588',
- 'javelin-behavior-phabricator-object-selector' => 'e8b28c4a',
+ 'javelin-behavior-phabricator-object-selector' => 'ffca9dae',
'javelin-behavior-phabricator-watch-anchor' => '95944588',
'javelin-behavior-refresh-csrf' => '95944588',
'javelin-behavior-workflow' => '95944588',
'javelin-dom' => '4fbae2af',
'javelin-event' => '4fbae2af',
'javelin-install' => '4fbae2af',
'javelin-json' => '4fbae2af',
'javelin-mask' => '95944588',
'javelin-request' => '4fbae2af',
'javelin-stratcom' => '4fbae2af',
'javelin-tokenizer' => '2af849fb',
'javelin-typeahead' => '2af849fb',
'javelin-typeahead-normalizer' => '2af849fb',
'javelin-typeahead-ondemand-source' => '2af849fb',
'javelin-typeahead-preloaded-source' => '2af849fb',
'javelin-typeahead-source' => '2af849fb',
'javelin-uri' => '4fbae2af',
'javelin-util' => '4fbae2af',
'javelin-vector' => '4fbae2af',
'javelin-workflow' => '95944588',
'maniphest-task-detail-css' => '31583232',
'maniphest-task-summary-css' => '31583232',
'maniphest-transaction-detail-css' => '31583232',
'phabricator-app-buttons-css' => '78e8854e',
- 'phabricator-content-source-view-css' => '09c86840',
+ 'phabricator-content-source-view-css' => '9d02b654',
'phabricator-core-buttons-css' => '78e8854e',
'phabricator-core-css' => '78e8854e',
'phabricator-directory-css' => '78e8854e',
- 'phabricator-drag-and-drop-file-upload' => 'e8b28c4a',
+ 'phabricator-drag-and-drop-file-upload' => 'ffca9dae',
'phabricator-dropdown-menu' => '95944588',
'phabricator-jump-nav' => '78e8854e',
'phabricator-keyboard-shortcut' => '95944588',
'phabricator-keyboard-shortcut-manager' => '95944588',
'phabricator-menu-item' => '95944588',
- 'phabricator-object-selector-css' => '09c86840',
+ 'phabricator-object-selector-css' => '9d02b654',
'phabricator-paste-file-upload' => '95944588',
'phabricator-remarkup-css' => '78e8854e',
- 'phabricator-shaped-request' => 'e8b28c4a',
+ 'phabricator-shaped-request' => 'ffca9dae',
'phabricator-standard-page-view' => '78e8854e',
'phabricator-transaction-view-css' => '78e8854e',
'syntax-highlighting-css' => '78e8854e',
),
));
diff --git a/src/applications/differential/controller/changesetview/DifferentialChangesetViewController.php b/src/applications/differential/controller/changesetview/DifferentialChangesetViewController.php
index 599efa12a2..b99b24103f 100644
--- a/src/applications/differential/controller/changesetview/DifferentialChangesetViewController.php
+++ b/src/applications/differential/controller/changesetview/DifferentialChangesetViewController.php
@@ -1,338 +1,346 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class DifferentialChangesetViewController extends DifferentialController {
public function shouldRequireLogin() {
return !$this->allowsAnonymousAccess();
}
public function processRequest() {
$request = $this->getRequest();
$author_phid = $request->getUser()->getPHID();
$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;
$changeset = id(new DifferentialChangeset())->load($id);
if (!$changeset) {
return new Aphront404Response();
}
$view = $request->getStr('view');
if ($view) {
$changeset->attachHunks($changeset->loadHunks());
$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':
return $this->buildRawFileResponse($changeset, $is_new = false);
default:
return new Aphront400Response();
}
}
if ($vs && ($vs != -1)) {
$vs_changeset = id(new DifferentialChangeset())->load($vs);
if (!$vs_changeset) {
return new Aphront404Response();
}
}
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();
} 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;
} else {
$right = $changeset;
$left = $vs_changeset;
$right_source = $right->getID();
$right_new = true;
$left_source = $left->getID();
$left_new = true;
$render_cache_key = null;
}
if ($left) {
$left->attachHunks($left->loadHunks());
}
if ($right) {
$right->attachHunks($right->loadHunks());
}
if ($left) {
$left_data = $left->makeNewFile();
if ($right) {
$right_data = $right->makeNewFile();
} else {
$right_data = $left->makeOldFile();
}
$engine = new PhabricatorDifferenceEngine();
$synthetic = $engine->generateChangesetFromFileContent(
$left_data,
$right_data);
$choice = nonempty($left, $right);
$choice->attachHunks($synthetic->getHunks());
$changeset = $choice;
}
$coverage = null;
if ($right && $right->getDiffID()) {
$unit = id(new DifferentialDiffProperty())->loadOneWhere(
'diffID = %d AND name = %s',
$right->getDiffID(),
'arc:unit');
if ($unit) {
$coverage = array();
foreach ($unit->getData() as $result) {
$result_coverage = idx($result, 'coverage');
if (!$result_coverage) {
continue;
}
$file_coverage = idx($result_coverage, $right->getFileName());
if (!$file_coverage) {
continue;
}
$coverage[] = $file_coverage;
}
$coverage = ArcanistUnitTestResult::mergeCoverage($coverage);
}
}
$spec = $request->getStr('range');
list($range_s, $range_e, $mask) =
DifferentialChangesetParser::parseRangeSpecification($spec);
$parser = new DifferentialChangesetParser();
$parser->setCoverage($coverage);
$parser->setChangeset($changeset);
$parser->setRenderingReference($rendering_reference);
$parser->setRenderCacheKey($render_cache_key);
$parser->setRightSideCommentMapping($right_source, $right_new);
$parser->setLeftSideCommentMapping($left_source, $left_new);
$parser->setWhitespaceMode($request->getStr('whitespace'));
// Load both left-side and right-side inline comments.
$inlines = $this->loadInlineComments(
array($left_source, $right_source),
$author_phid);
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 = id(new PhabricatorObjectHandleData($phids))
->loadHandles();
$parser->setHandles($handles);
$engine = PhabricatorMarkupEngine::newDifferentialMarkupEngine();
$parser->setMarkupEngine($engine);
if ($request->isAjax()) {
// TODO: This is sort of lazy, the effect is just to not render "Edit"
// links on the "standalone view".
$parser->setUser($request->getUser());
}
$output = $parser->render($range_s, $range_e, $mask);
+ $mcov = $parser->renderModifiedCoverage();
+
if ($request->isAjax()) {
+ $content = array(
+ 'coverage' => array(
+ 'differential-mcoverage-'.md5($changeset->getFilename()) => $mcov,
+ ),
+ 'changeset' => $output,
+ );
return id(new AphrontAjaxResponse())
- ->setContent($output);
+ ->setContent($content);
}
Javelin::initBehavior('differential-show-more', array(
'uri' => '/differential/changeset/',
'whitespace' => $request->getStr('whitespace'),
));
Javelin::initBehavior('differential-comment-jump', array());
$detail = new DifferentialChangesetDetailView();
$detail->setChangeset($changeset);
$detail->appendChild($output);
$detail->setRevisionID($request->getInt('revision_id'));
$output =
id(new DifferentialPrimaryPaneView())
->setLineWidthFromChangesets(array($changeset))
->appendChild(
'<div class="differential-review-stage" '.
'id="differential-review-stage">'.
$detail->render().
'</div>');
return $this->buildStandardPageResponse(
array(
$output
),
array(
'title' => 'Changeset View',
));
}
private function loadInlineComments(array $changeset_ids, $author_phid) {
$changeset_ids = array_unique(array_filter($changeset_ids));
if (!$changeset_ids) {
return;
}
return id(new DifferentialInlineComment())->loadAllWhere(
'changesetID IN (%Ld) AND (commentID IS NOT NULL OR authorPHID = %s)',
$changeset_ids,
$author_phid);
}
private function buildRawFileResponse(
DifferentialChangeset $changeset,
$is_new) {
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 PhabricatorFile())->loadOneWhere(
'phid = %s',
$phid);
}
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();
}
$file = PhabricatorFile::newFromFileData(
$data,
array(
'name' => $changeset->getFilename(),
'mime-type' => 'text/plain',
));
$metadata[$key] = $file->getPHID();
$changeset->setMetadata($metadata);
$changeset->save();
unset($unguard);
}
return id(new AphrontRedirectResponse())
->setURI($file->getBestURI());
}
private function buildLintInlineComments($changeset) {
$lint = id(new DifferentialDiffProperty())->loadOneWhere(
'diffID = %d AND name = %s',
$changeset->getDiffID(),
'arc:lint');
if (!$lint) {
return array();
}
$lint = $lint->getData();
$inlines = array();
foreach ($lint as $msg) {
if ($msg['path'] != $changeset->getFilename()) {
continue;
}
$inline = new DifferentialInlineComment();
$inline->setChangesetID($changeset->getID());
$inline->setIsNewFile(true);
$inline->setSyntheticAuthor('Lint: '.$msg['name']);
$inline->setLineNumber($msg['line']);
$inline->setLineLength(0);
$inline->setContent('%%%'.$msg['description'].'%%%');
$inlines[] = $inline;
}
return $inlines;
}
}
diff --git a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php
index c36569fe1e..c9104b602b 100644
--- a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php
+++ b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php
@@ -1,678 +1,680 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class DifferentialRevisionViewController extends DifferentialController {
private $revisionID;
public function shouldRequireLogin() {
return !$this->allowsAnonymousAccess();
}
public function willProcessRequest(array $data) {
$this->revisionID = $data['id'];
}
public function processRequest() {
$request = $this->getRequest();
$user = $request->getUser();
$viewer_is_anonymous = !$user->isLoggedIn();
$revision = id(new DifferentialRevision())->load($this->revisionID);
if (!$revision) {
return new Aphront404Response();
}
$revision->loadRelationships();
$diffs = $revision->loadDiffs();
if (!$diffs) {
throw new Exception(
"This revision has no diffs. Something has gone quite wrong.");
}
$diff_vs = $request->getInt('vs');
$target = end($diffs);
$target_id = $request->getInt('id');
if ($target_id) {
if (isset($diffs[$target_id])) {
$target = $diffs[$target_id];
}
}
$diffs = mpull($diffs, null, 'getID');
if (empty($diffs[$diff_vs])) {
$diff_vs = null;
}
list($aux_fields, $props) = $this->loadAuxiliaryFieldsAndProperties(
$revision,
$target,
array(
'local:commits',
+ 'arc:unit',
));
list($changesets, $vs_map, $rendering_references) =
$this->loadChangesetsAndVsMap($diffs, $diff_vs, $target);
$comments = $revision->loadComments();
$comments = array_merge(
$this->getImplicitComments($revision),
$comments);
$all_changesets = $changesets;
$inlines = $this->loadInlineComments($comments, $all_changesets);
$object_phids = array_merge(
$revision->getReviewers(),
$revision->getCCPHIDs(),
$revision->loadCommitPHIDs(),
array(
$revision->getAuthorPHID(),
$user->getPHID(),
),
mpull($comments, 'getAuthorPHID'));
foreach ($comments as $comment) {
$metadata = $comment->getMetadata();
$added_reviewers = idx(
$metadata,
DifferentialComment::METADATA_ADDED_REVIEWERS);
if ($added_reviewers) {
foreach ($added_reviewers as $phid) {
$object_phids[] = $phid;
}
}
$added_ccs = idx(
$metadata,
DifferentialComment::METADATA_ADDED_CCS);
if ($added_ccs) {
foreach ($added_ccs as $phid) {
$object_phids[] = $phid;
}
}
}
foreach ($revision->getAttached() as $type => $phids) {
foreach ($phids as $phid => $info) {
$object_phids[] = $phid;
}
}
$aux_phids = array();
foreach ($aux_fields as $key => $aux_field) {
$aux_phids[$key] = $aux_field->getRequiredHandlePHIDsForRevisionView();
}
$object_phids = array_merge($object_phids, array_mergev($aux_phids));
$object_phids = array_unique($object_phids);
$handles = id(new PhabricatorObjectHandleData($object_phids))
->loadHandles();
foreach ($aux_fields as $key => $aux_field) {
// Make sure each field only has access to handles it specifically
// requested, not all handles. Otherwise you can get a field which works
// only in the presence of other fields.
$aux_field->setHandles(array_select_keys($handles, $aux_phids[$key]));
}
$reviewer_warning = null;
$has_live_reviewer = false;
foreach ($revision->getReviewers() as $reviewer) {
if (!$handles[$reviewer]->isDisabled()) {
$has_live_reviewer = true;
}
}
if (!$has_live_reviewer) {
$reviewer_warning = new AphrontErrorView();
$reviewer_warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$reviewer_warning->setTitle('No Active Reviewers');
if ($revision->getReviewers()) {
$reviewer_warning->appendChild(
'<p>All specified reviewers are disabled. You may want to add '.
'some new reviewers.</p>');
} else {
$reviewer_warning->appendChild(
'<p>This revision has no specified reviewers. You may want to '.
'add some.</p>');
}
}
$request_uri = $request->getRequestURI();
$limit = 100;
$large = $request->getStr('large');
if (count($changesets) > $limit && !$large) {
$count = number_format(count($changesets));
$warning = new AphrontErrorView();
$warning->setTitle('Very Large Diff');
$warning->setSeverity(AphrontErrorView::SEVERITY_WARNING);
$warning->setWidth(AphrontErrorView::WIDTH_WIDE);
$warning->appendChild(
"<p>This diff is very large and affects {$count} files. Use ".
"Table of Contents to open files in a standalone view. ".
"<strong>".
phutil_render_tag(
'a',
array(
'href' => $request_uri->alter('large', 'true'),
),
'Show All Files Inline').
"</strong>");
$warning = $warning->render();
$visible_changesets = array();
} else {
$warning = null;
$visible_changesets = $changesets;
}
$revision_detail = new DifferentialRevisionDetailView();
$revision_detail->setRevision($revision);
$revision_detail->setAuxiliaryFields($aux_fields);
$actions = $this->getRevisionActions($revision);
$custom_renderer_class = PhabricatorEnv::getEnvConfig(
'differential.revision-custom-detail-renderer');
if ($custom_renderer_class) {
// TODO: build a better version of the action links and deprecate the
// whole DifferentialRevisionDetailRenderer class.
PhutilSymbolLoader::loadClass($custom_renderer_class);
$custom_renderer =
newv($custom_renderer_class, array());
$actions = array_merge(
$actions,
$custom_renderer->generateActionLinks($revision, $target));
}
$whitespace = $request->getStr(
'whitespace',
DifferentialChangesetParser::WHITESPACE_IGNORE_ALL);
$arc_project = $target->loadArcanistProject();
if ($arc_project) {
$symbol_indexes = $this->buildSymbolIndexes(
$target,
$arc_project,
$visible_changesets);
$repository = $arc_project->loadRepository();
} else {
$symbol_indexes = array();
$repository = null;
}
$revision_detail->setActions($actions);
$revision_detail->setUser($user);
$comment_view = new DifferentialRevisionCommentListView();
$comment_view->setComments($comments);
$comment_view->setHandles($handles);
$comment_view->setInlineComments($inlines);
$comment_view->setChangesets($all_changesets);
$comment_view->setUser($user);
$comment_view->setTargetDiff($target);
$comment_view->setVersusDiffID($diff_vs);
$changeset_view = new DifferentialChangesetListView();
$changeset_view->setChangesets($visible_changesets);
$changeset_view->setEditable(!$viewer_is_anonymous);
$changeset_view->setStandaloneViews(true);
$changeset_view->setUser($user);
$changeset_view->setRevision($revision);
$changeset_view->setDiff($target);
$changeset_view->setRenderingReferences($rendering_references);
$changeset_view->setVsMap($vs_map);
$changeset_view->setWhitespace($whitespace);
if ($repository) {
$changeset_view->setRepository($repository, $target);
}
$changeset_view->setSymbolIndexes($symbol_indexes);
$diff_history = new DifferentialRevisionUpdateHistoryView();
$diff_history->setDiffs($diffs);
$diff_history->setSelectedVersusDiffID($diff_vs);
$diff_history->setSelectedDiffID($target->getID());
$diff_history->setSelectedWhitespace($whitespace);
$diff_history->setUser($user);
$local_view = new DifferentialLocalCommitsView();
$local_view->setUser($user);
$local_view->setLocalCommits(idx($props, 'local:commits'));
$toc_view = new DifferentialDiffTableOfContentsView();
$toc_view->setChangesets($changesets);
+ $toc_view->setUnitTestData(idx($props, 'arc:unit', array()));
if ($repository) {
$toc_view->setRepository($repository);
}
$toc_view->setDiff($target);
$toc_view->setUser($user);
$toc_view->setStandaloneViewLink(empty($visible_changesets));
$toc_view->setVsMap($vs_map);
$toc_view->setRevisionID($revision->getID());
$toc_view->setWhitespace($whitespace);
if (!$viewer_is_anonymous) {
$draft = id(new PhabricatorDraft())->loadOneWhere(
'authorPHID = %s AND draftKey = %s',
$user->getPHID(),
'differential-comment-'.$revision->getID());
if ($draft) {
$draft = $draft->getDraft();
} else {
$draft = null;
}
$comment_form = new DifferentialAddCommentView();
$comment_form->setRevision($revision);
$comment_form->setActions($this->getRevisionCommentActions($revision));
$comment_form->setActionURI('/differential/comment/save/');
$comment_form->setUser($user);
$comment_form->setDraft($draft);
}
$pane_id = celerity_generate_unique_node_id();
Javelin::initBehavior(
'differential-keyboard-navigation',
array(
'haunt' => $pane_id,
));
$page_pane = id(new DifferentialPrimaryPaneView())
->setLineWidthFromChangesets($changesets)
->setID($pane_id)
->appendChild($reviewer_warning)
->appendChild(
$revision_detail->render().
$comment_view->render().
$diff_history->render().
$warning.
$local_view->render().
$toc_view->render().
$changeset_view->render());
if ($comment_form) {
$page_pane->appendChild($comment_form->render());
}
return $this->buildStandardPageResponse(
$page_pane,
array(
'title' => 'D'.$revision->getID().' '.$revision->getTitle(),
));
}
private function getImplicitComments(DifferentialRevision $revision) {
$template = new DifferentialComment();
$template->setAuthorPHID($revision->getAuthorPHID());
$template->setRevisionID($revision->getID());
$template->setDateCreated($revision->getDateCreated());
$comments = array();
if (strlen($revision->getSummary())) {
$summary_comment = clone $template;
$summary_comment->setContent($revision->getSummary());
$summary_comment->setAction(DifferentialAction::ACTION_SUMMARIZE);
$comments[] = $summary_comment;
}
if (strlen($revision->getTestPlan())) {
$testplan_comment = clone $template;
$testplan_comment->setContent($revision->getTestPlan());
$testplan_comment->setAction(DifferentialAction::ACTION_TESTPLAN);
$comments[] = $testplan_comment;
}
return $comments;
}
private function getRevisionActions(DifferentialRevision $revision) {
$viewer_phid = $this->getRequest()->getUser()->getPHID();
$viewer_is_owner = ($revision->getAuthorPHID() == $viewer_phid);
$viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
$viewer_is_cc = in_array($viewer_phid, $revision->getCCPHIDs());
$viewer_is_anonymous = !$this->getRequest()->getUser()->isLoggedIn();
$status = $revision->getStatus();
$revision_id = $revision->getID();
$revision_phid = $revision->getPHID();
$links = array();
if ($viewer_is_owner) {
$links[] = array(
'class' => 'revision-edit',
'href' => "/differential/revision/edit/{$revision_id}/",
'name' => 'Edit Revision',
);
}
if (!$viewer_is_anonymous) {
if (!$viewer_is_owner && !$viewer_is_reviewer) {
$action = $viewer_is_cc ? 'rem' : 'add';
$links[] = array(
'class' => $viewer_is_cc ? 'subscribe-rem' : 'subscribe-add',
'href' => "/differential/subscribe/{$action}/{$revision_id}/",
'name' => $viewer_is_cc ? 'Unsubscribe' : 'Subscribe',
'instant' => true,
);
} else {
$links[] = array(
'class' => 'subscribe-rem unavailable',
'name' => 'Automatically Subscribed',
);
}
require_celerity_resource('phabricator-object-selector-css');
require_celerity_resource('javelin-behavior-phabricator-object-selector');
$links[] = array(
'class' => 'action-dependencies',
'name' => 'Edit Dependencies',
'href' => "/search/attach/{$revision_phid}/DREV/dependencies/",
'sigil' => 'workflow',
);
if (PhabricatorEnv::getEnvConfig('maniphest.enabled')) {
$links[] = array(
'class' => 'attach-maniphest',
'name' => 'Edit Maniphest Tasks',
'href' => "/search/attach/{$revision_phid}/TASK/",
'sigil' => 'workflow',
);
}
$links[] = array(
'class' => 'transcripts-metamta',
'name' => 'MetaMTA Transcripts',
'href' => "/mail/?phid={$revision_phid}",
);
$links[] = array(
'class' => 'transcripts-herald',
'name' => 'Herald Transcripts',
'href' => "/herald/transcript/?phid={$revision_phid}",
);
}
return $links;
}
private function getRevisionCommentActions(DifferentialRevision $revision) {
$actions = array(
DifferentialAction::ACTION_COMMENT => true,
);
$admin_actions = array();
$viewer = $this->getRequest()->getUser();
$viewer_phid = $viewer->getPHID();
$viewer_is_admin = $viewer->getIsAdmin();
$viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID());
$viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers());
$viewer_did_accept = ($viewer_phid === $revision->loadReviewedBy());
if ($viewer_is_owner) {
switch ($revision->getStatus()) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_RETHINK] = true;
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_REQUEST] = true;
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$actions[DifferentialAction::ACTION_ABANDON] = true;
$actions[DifferentialAction::ACTION_REQUEST] = true;
$actions[DifferentialAction::ACTION_RETHINK] = true;
$actions[DifferentialAction::ACTION_COMMIT] = true;
break;
case ArcanistDifferentialRevisionStatus::COMMITTED:
break;
case ArcanistDifferentialRevisionStatus::ABANDONED:
$actions[DifferentialAction::ACTION_RECLAIM] = true;
break;
}
} else {
switch ($revision->getStatus()) {
case ArcanistDifferentialRevisionStatus::NEEDS_REVIEW:
$admin_actions[DifferentialAction::ACTION_ABANDON] = $viewer_is_admin;
$actions[DifferentialAction::ACTION_ACCEPT] = true;
$actions[DifferentialAction::ACTION_REJECT] = true;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::NEEDS_REVISION:
$admin_actions[DifferentialAction::ACTION_ABANDON] = $viewer_is_admin;
$actions[DifferentialAction::ACTION_ACCEPT] = true;
$actions[DifferentialAction::ACTION_RESIGN] = $viewer_is_reviewer;
break;
case ArcanistDifferentialRevisionStatus::ACCEPTED:
$admin_actions[DifferentialAction::ACTION_ABANDON] = $viewer_is_admin;
$actions[DifferentialAction::ACTION_REJECT] = true;
$actions[DifferentialAction::ACTION_RESIGN] =
$viewer_is_reviewer && !$viewer_did_accept;
break;
case ArcanistDifferentialRevisionStatus::COMMITTED:
case ArcanistDifferentialRevisionStatus::ABANDONED:
break;
}
}
$actions[DifferentialAction::ACTION_ADDREVIEWERS] = true;
$actions[DifferentialAction::ACTION_ADDCCS] = true;
$actions = array_keys(array_filter($actions));
$admin_actions = array_keys(array_filter($admin_actions));
$actions_dict = array();
foreach ($actions as $action) {
$actions_dict[$action] = DifferentialAction::getActionVerb($action);
}
foreach ($admin_actions as $action) {
$actions_dict[$action] =
'(Admin) ' . DifferentialAction::getActionVerb($action);
}
return $actions_dict;
}
private function loadInlineComments(array $comments, array &$changesets) {
$inline_comments = array();
$comment_ids = array_filter(mpull($comments, 'getID'));
if (!$comment_ids) {
return $inline_comments;
}
$inline_comments = id(new DifferentialInlineComment())
->loadAllWhere(
'commentID in (%Ld)',
$comment_ids);
$load_changesets = array();
foreach ($inline_comments as $inline) {
$changeset_id = $inline->getChangesetID();
if (isset($changesets[$changeset_id])) {
continue;
}
$load_changesets[$changeset_id] = true;
}
$more_changesets = array();
if ($load_changesets) {
$changeset_ids = array_keys($load_changesets);
$more_changesets += id(new DifferentialChangeset())
->loadAllWhere(
'id IN (%Ld)',
$changeset_ids);
}
if ($more_changesets) {
$changesets += $more_changesets;
$changesets = msort($changesets, 'getSortKey');
}
return $inline_comments;
}
private function loadChangesetsAndVsMap(array $diffs, $diff_vs, $target) {
$load_ids = array();
if ($diff_vs) {
$load_ids[] = $diff_vs;
}
$load_ids[] = $target->getID();
$raw_changesets = id(new DifferentialChangeset())
->loadAllWhere(
'diffID IN (%Ld)',
$load_ids);
$changeset_groups = mgroup($raw_changesets, 'getDiffID');
$changesets = idx($changeset_groups, $target->getID(), array());
$changesets = mpull($changesets, null, 'getID');
$refs = array();
foreach ($changesets as $changeset) {
$refs[$changeset->getID()] = $changeset->getID();
}
$vs_map = array();
if ($diff_vs) {
$vs_changesets = idx($changeset_groups, $diff_vs, array());
$vs_changesets = mpull($vs_changesets, null, 'getFilename');
foreach ($changesets as $key => $changeset) {
$file = $changeset->getFilename();
if (isset($vs_changesets[$file])) {
$vs_map[$changeset->getID()] = $vs_changesets[$file]->getID();
$refs[$changeset->getID()] =
$changeset->getID().'/'.$vs_changesets[$file]->getID();
unset($vs_changesets[$file]);
} else {
$refs[$changeset->getID()] = $changeset->getID();
}
}
foreach ($vs_changesets as $changeset) {
$changesets[$changeset->getID()] = $changeset;
$vs_map[$changeset->getID()] = -1;
$refs[$changeset->getID()] = $changeset->getID().'/-1';
}
}
$changesets = msort($changesets, 'getSortKey');
return array($changesets, $vs_map, $refs);
}
private function loadAuxiliaryFieldsAndProperties(
DifferentialRevision $revision,
DifferentialDiff $diff,
array $special_properties) {
$aux_fields = DifferentialFieldSelector::newSelector()
->getFieldSpecifications();
foreach ($aux_fields as $key => $aux_field) {
if (!$aux_field->shouldAppearOnRevisionView()) {
unset($aux_fields[$key]);
}
}
$aux_fields = DifferentialAuxiliaryField::loadFromStorage(
$revision,
$aux_fields);
$aux_props = array();
foreach ($aux_fields as $key => $aux_field) {
$aux_field->setDiff($diff);
$aux_props[$key] = $aux_field->getRequiredDiffProperties();
}
$required_properties = array_mergev($aux_props);
$required_properties = array_merge(
$required_properties,
$special_properties);
$property_map = array();
if ($required_properties) {
$properties = id(new DifferentialDiffProperty())->loadAllWhere(
'diffID = %d AND name IN (%Ls)',
$diff->getID(),
$required_properties);
$property_map = mpull($properties, 'getData', 'getName');
}
foreach ($aux_fields as $key => $aux_field) {
// Give each field only the properties it specifically required, and
// set 'null' for each requested key which we didn't actually load a
// value for (otherwise, getDiffProperty() will throw).
if ($aux_props[$key]) {
$props = array_select_keys($property_map, $aux_props[$key]) +
array_fill_keys($aux_props[$key], null);
} else {
$props = array();
}
$aux_field->setDiffProperties($props);
}
return array(
$aux_fields,
array_select_keys(
$property_map,
$special_properties));
}
private function buildSymbolIndexes(
DifferentialDiff $target,
PhabricatorRepositoryArcanistProject $arc_project,
array $visible_changesets) {
$engine = PhabricatorSyntaxHighlighter::newEngine();
$langs = $arc_project->getSymbolIndexLanguages();
if (!$langs) {
return array();
}
$symbol_indexes = array();
$project_phids = array_merge(
array($arc_project->getPHID()),
nonempty($arc_project->getSymbolIndexProjects(), array()));
$indexed_langs = array_fill_keys($langs, true);
foreach ($visible_changesets as $key => $changeset) {
$lang = $engine->getLanguageFromFilename($changeset->getFilename());
if (isset($indexed_langs[$lang])) {
$symbol_indexes[$key] = array(
'lang' => $lang,
'projects' => $project_phids,
);
}
}
return $symbol_indexes;
}
}
diff --git a/src/applications/differential/parser/changeset/DifferentialChangesetParser.php b/src/applications/differential/parser/changeset/DifferentialChangesetParser.php
index 525010fa04..874b64c10e 100644
--- a/src/applications/differential/parser/changeset/DifferentialChangesetParser.php
+++ b/src/applications/differential/parser/changeset/DifferentialChangesetParser.php
@@ -1,1717 +1,1762 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
class DifferentialChangesetParser {
protected $visible = array();
protected $new = array();
protected $old = array();
protected $intra = array();
protected $newRender = null;
protected $oldRender = null;
protected $filename = null;
protected $missingOld = array();
protected $missingNew = array();
protected $comments = array();
protected $specialAttributes = array();
protected $changeset;
protected $whitespaceMode = null;
protected $subparser;
protected $renderCacheKey = null;
private $handles;
private $user;
private $leftSideChangesetID;
private $leftSideAttachesToNewFile;
private $rightSideChangesetID;
private $rightSideAttachesToNewFile;
private $renderingReference;
private $isSubparser;
private $lineWidth = 80;
private $isTopLevel;
private $coverage;
const CACHE_VERSION = 4;
const ATTR_GENERATED = 'attr:generated';
const ATTR_DELETED = 'attr:deleted';
const ATTR_UNCHANGED = 'attr:unchanged';
const ATTR_WHITELINES = 'attr:white';
const LINES_CONTEXT = 8;
const WHITESPACE_SHOW_ALL = 'show-all';
const WHITESPACE_IGNORE_TRAILING = 'ignore-trailing';
// TODO: This is now "Ignore Most" in the UI.
const WHITESPACE_IGNORE_ALL = 'ignore-all';
const WHITESPACE_IGNORE_FORCE = 'ignore-force';
/**
* 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;
}
/**
* 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;
}
/**
* Set the character width at which lines will be wrapped. Defaults to 80.
*
* @param int Hard-wrap line-width for diff display.
* @return this
*/
public function setLineWidth($width) {
$this->lineWidth = $width;
return $this;
}
private function getRenderCacheKey() {
return $this->renderCacheKey;
}
public function setChangeset($changeset) {
$this->changeset = $changeset;
$this->setFilename($changeset->getFilename());
$this->setLineWidth($changeset->getWordWrapWidth());
return $this;
}
public function setWhitespaceMode($whitespace_mode) {
$this->whitespaceMode = $whitespace_mode;
return $this;
}
public function setRenderingReference($ref) {
$this->renderingReference = $ref;
return $this;
}
public function getChangeset() {
return $this->changeset;
}
public function setFilename($filename) {
$this->filename = $filename;
return $this;
}
public function setHandles(array $handles) {
$this->handles = $handles;
return $this;
}
public function setMarkupEngine(PhutilMarkupEngine $engine) {
$this->markupEngine = $engine;
return $this;
}
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function setCoverage($coverage) {
$this->coverage = $coverage;
return $this;
}
public function parseHunk(DifferentialHunk $hunk) {
$lines = $hunk->getChanges();
$lines = str_replace(
array("\t", "\r\n", "\r"),
array(' ', "\n", "\n"),
$lines);
$lines = explode("\n", $lines);
$types = array();
foreach ($lines as $line_index => $line) {
if (isset($line[0])) {
$char = $line[0];
if ($char == ' ') {
$types[$line_index] = null;
} else if ($char == '\\' && $line_index > 0) {
$types[$line_index] = $types[$line_index - 1];
} else {
$types[$line_index] = $char;
}
} else {
$types[$line_index] = null;
}
}
$old_line = $hunk->getOldOffset();
$new_line = $hunk->getNewOffset();
$num_lines = count($lines);
if ($old_line > 1) {
$this->missingOld[$old_line] = true;
} else if ($new_line > 1) {
$this->missingNew[$new_line] = true;
}
for ($cursor = 0; $cursor < $num_lines; $cursor++) {
$type = $types[$cursor];
$data = array(
'type' => $type,
'text' => (string)substr($lines[$cursor], 1),
'line' => $new_line,
);
switch ($type) {
case '+':
$this->new[] = $data;
++$new_line;
break;
case '-':
$data['line'] = $old_line;
$this->old[] = $data;
++$old_line;
break;
default:
$this->new[] = $data;
$data['line'] = $old_line;
$this->old[] = $data;
++$new_line;
++$old_line;
break;
}
}
}
public function parseInlineComment(DifferentialInlineComment $comment) {
// Parse only comments which are actually visible.
if ($this->isCommentVisibleOnRenderedDiff($comment)) {
$this->comments[] = $comment;
}
return $this;
}
public function process() {
$old = array();
$new = array();
$n = 0;
$this->old = array_reverse($this->old);
$this->new = array_reverse($this->new);
$whitelines = false;
$changed = false;
$skip_intra = array();
while (count($this->old) || count($this->new)) {
$o_desc = array_pop($this->old);
$n_desc = array_pop($this->new);
if ($o_desc) {
$o_type = $o_desc['type'];
} else {
$o_type = null;
}
if ($n_desc) {
$n_type = $n_desc['type'];
} else {
$n_type = null;
}
if (($o_type != null) && ($n_type == null)) {
$old[] = $o_desc;
$new[] = null;
if ($n_desc) {
array_push($this->new, $n_desc);
}
$changed = true;
continue;
}
if (($n_type != null) && ($o_type == null)) {
$old[] = null;
$new[] = $n_desc;
if ($o_desc) {
array_push($this->old, $o_desc);
}
$changed = true;
continue;
}
if ($this->whitespaceMode != self::WHITESPACE_SHOW_ALL) {
$similar = false;
switch ($this->whitespaceMode) {
case self::WHITESPACE_IGNORE_TRAILING:
if (rtrim($o_desc['text']) == rtrim($n_desc['text'])) {
if ($o_desc['type']) {
// If we're converting this into an unchanged line because of
// a trailing whitespace difference, mark it as a whitespace
// change so we can show "This file was modified only by
// adding or removing trailing whitespace." instead of
// "This file was not modified.".
$whitelines = true;
}
$similar = true;
}
break;
default:
// In this case, the lines are similar if there is no change type
// (that is, just trust the diff algorithm).
if (!$o_desc['type']) {
$similar = true;
}
break;
}
if ($similar) {
$o_desc['type'] = null;
$n_desc['type'] = null;
$skip_intra[count($old)] = true;
} else {
$changed = true;
}
} else {
$changed = true;
}
$old[] = $o_desc;
$new[] = $n_desc;
}
$this->old = $old;
$this->new = $new;
$unchanged = false;
if ($this->subparser) {
$unchanged = $this->subparser->isUnchanged();
$whitelines = $this->subparser->isWhitespaceOnly();
} else if (!$changed) {
$filetype = $this->changeset->getFileType();
if ($filetype == DifferentialChangeType::FILE_TEXT ||
$filetype == DifferentialChangeType::FILE_SYMLINK) {
$unchanged = true;
}
}
$this->specialAttributes = array(
self::ATTR_UNCHANGED => $unchanged,
self::ATTR_DELETED => array_filter($this->old) &&
!array_filter($this->new),
self::ATTR_WHITELINES => $whitelines
);
if ($this->isSubparser) {
// The rest of this function deals with formatting the diff for display;
// we can exit early if we're a subparser and avoid doing extra work.
return;
}
if ($this->subparser) {
// Use this parser's side-by-side line information -- notably, the
// change types -- but replace all the line text with the subparser's.
// This lets us render whitespace-only changes without marking them as
// different.
$old = $this->old;
$new = $this->new;
$old_text = ipull($this->subparser->old, 'text', 'line');
$new_text = ipull($this->subparser->new, 'text', 'line');
foreach ($old as $k => $desc) {
if (empty($desc)) {
continue;
}
$old[$k]['text'] = idx($old_text, $desc['line']);
}
foreach ($new as $k => $desc) {
if (empty($desc)) {
continue;
}
$new[$k]['text'] = idx($new_text, $desc['line']);
if ($this->whitespaceMode == self::WHITESPACE_IGNORE_FORCE) {
// Under forced ignore mode, ignore even internal whitespace
// changes.
continue;
}
// If there's a corresponding "old" text and the line is marked as
// unchanged, test if there are internal whitespace changes between
// non-whitespace characters, e.g. spaces added to a string or spaces
// added around operators. If we find internal spaces, mark the line
// as changed.
//
// We only need to do this for "new" lines because any line that is
// missing either "old" or "new" text certainly can not have internal
// whitespace changes without also having non-whitespace changes,
// because characters had to be either added or removed to create the
// possibility of internal whitespace.
if (isset($old[$k]['text']) && empty($new[$k]['type'])) {
if (trim($old[$k]['text']) != trim($new[$k]['text'])) {
// The strings aren't the same when trimmed, so there are internal
// whitespace changes. Mark this line changed.
$old[$k]['type'] = '-';
$new[$k]['type'] = '+';
}
}
}
$this->old = $old;
$this->new = $new;
}
$min_length = min(count($this->old), count($this->new));
for ($ii = 0; $ii < $min_length; $ii++) {
if ($this->old[$ii] || $this->new[$ii]) {
if (isset($this->old[$ii]['text'])) {
$otext = $this->old[$ii]['text'];
} else {
$otext = '';
}
if (isset($this->new[$ii]['text'])) {
$ntext = $this->new[$ii]['text'];
} else {
$ntext = '';
}
if ($otext != $ntext && empty($skip_intra[$ii])) {
$this->intra[$ii] = ArcanistDiffUtils::generateIntralineDiff(
$otext,
$ntext);
}
}
}
$lines_context = self::LINES_CONTEXT;
$max_length = max(count($this->old), count($this->new));
$old = $this->old;
$new = $this->new;
$visible = false;
$last = 0;
for ($cursor = -$lines_context; $cursor < $max_length; $cursor++) {
$offset = $cursor + $lines_context;
if ((isset($old[$offset]) && $old[$offset]['type']) ||
(isset($new[$offset]) && $new[$offset]['type'])) {
$visible = true;
$last = $offset;
} else if ($cursor > $last + $lines_context) {
$visible = false;
}
if ($visible && $cursor > 0) {
$this->visible[$cursor] = 1;
}
}
// NOTE: Micro-optimize a couple of ipull()s here since it gives us a
// 10% performance improvement for certain types of large diffs like
// Phriction changes.
$old_corpus = array();
foreach ($this->old as $o) {
$old_corpus[] = $o['text'];
}
$old_corpus_block = implode("\n", $old_corpus);
$new_corpus = array();
foreach ($this->new as $n) {
$new_corpus[] = $n['text'];
}
$new_corpus_block = implode("\n", $new_corpus);
$old_future = $this->getHighlightFuture($old_corpus_block);
$new_future = $this->getHighlightFuture($new_corpus_block);
$futures = array(
'old' => $old_future,
'new' => $new_future,
);
foreach (Futures($futures) as $key => $future) {
try {
switch ($key) {
case 'old':
$this->oldRender = $this->processHighlightedSource(
$this->old,
$future->resolve());
break;
case 'new':
$this->newRender = $this->processHighlightedSource(
$this->new,
$future->resolve());
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);
$generated_guess = (strpos($new_corpus_block, '@'.'generated') !== false);
if (!$generated_guess) {
$config_key = 'differential.generated-paths';
$generated_path_regexps = PhabricatorEnv::getEnvConfig($config_key);
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');
$this->specialAttributes[self::ATTR_GENERATED] = $generated;
}
public 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;
}
$data = json_decode($data['cache'], true);
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;
}
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',
'newRender',
'oldRender',
'specialAttributes',
'missingOld',
'missingNew',
'cacheVersion',
'cacheHost',
);
}
public function saveCache() {
$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 = json_encode($cache);
try {
$changeset = new DifferentialChangeset();
$conn_w = $changeset->establishConnection('w');
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
queryfx(
$conn_w,
'INSERT INTO %T (id, cache, dateCreated) VALUES (%d, %s, %d)
ON DUPLICATE KEY UPDATE cache = VALUES(cache)',
DifferentialChangeset::TABLE_CACHE,
$render_cache_key,
$cache,
time());
} catch (AphrontQueryException $ex) {
// TODO: uhoh
}
}
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 isWhitespaceOnly() {
return idx($this->specialAttributes, self::ATTR_WHITELINES, false);
}
public function getLength() {
return max(count($this->old), count($this->new));
}
protected function applyIntraline(&$render, $intra, $corpus) {
$line_break = "<span class=\"over-the-line\">\xE2\xAC\x85</span><br />";
foreach ($render as $key => $text) {
if (isset($intra[$key])) {
$render[$key] = ArcanistDiffUtils::applyIntralineDiff(
$text,
$intra[$key]);
}
if (isset($corpus[$key]) && strlen($corpus[$key]) > $this->lineWidth) {
$lines = phutil_utf8_hard_wrap_html($render[$key], $this->lineWidth);
$render[$key] = implode($line_break, $lines);
}
}
}
protected function getHighlightFuture($corpus) {
return $this->highlightEngine->getHighlightFuture(
$this->highlightEngine->getLanguageFromFilename($this->filename),
$corpus);
}
protected function processHighlightedSource($data, $result) {
$result_lines = explode("\n", $result);
foreach ($data as $key => $info) {
if (!$info) {
unset($result_lines[$key]);
}
}
return $result_lines;
}
private function tryCacheStuff() {
$whitespace_mode = $this->whitespaceMode;
switch ($whitespace_mode) {
case self::WHITESPACE_SHOW_ALL:
case self::WHITESPACE_IGNORE_TRAILING:
case self::WHITESPACE_IGNORE_FORCE:
break;
default:
$whitespace_mode = self::WHITESPACE_IGNORE_ALL;
break;
}
$skip_cache = ($whitespace_mode != self::WHITESPACE_IGNORE_ALL);
$this->whitespaceMode = $whitespace_mode;
$changeset = $this->changeset;
if ($changeset->getFileType() == DifferentialChangeType::FILE_TEXT ||
$changeset->getFileType() == DifferentialChangeType::FILE_SYMLINK) {
if ($skip_cache || !$this->loadCache()) {
$ignore_all = (($whitespace_mode == self::WHITESPACE_IGNORE_ALL) ||
($whitespace_mode == self::WHITESPACE_IGNORE_FORCE));
$force_ignore = ($whitespace_mode == self::WHITESPACE_IGNORE_FORCE);
if (!$force_ignore) {
if ($ignore_all && $changeset->getWhitespaceMatters()) {
$ignore_all = false;
}
}
// The "ignore all whitespace" algorithm depends on rediffing the
// files, and we currently need complete representations of both
// files to do anything reasonable. If we only have parts of the files,
// don't use the "ignore all" algorithm.
if ($ignore_all) {
$hunks = $changeset->getHunks();
if (count($hunks) !== 1) {
$ignore_all = false;
} else {
$first_hunk = reset($hunks);
if ($first_hunk->getOldOffset() != 1 ||
$first_hunk->getNewOffset() != 1) {
$ignore_all = false;
}
}
}
if ($ignore_all) {
$old_file = $changeset->makeOldFile();
$new_file = $changeset->makeNewFile();
if ($old_file == $new_file) {
// If the old and new files are exactly identical, the synthetic
// diff below will give us nonsense and whitespace modes are
// irrelevant anyway. This occurs when you, e.g., copy a file onto
// itself in Subversion (see T271).
$ignore_all = false;
}
}
if ($ignore_all) {
// Huge mess. Generate a "-bw" (ignore all whitespace changes) diff,
// parse it out, and then play a shell game with the parsed format
// in process() so we highlight only changed lines but render
// whitespace differences. If we don't do this, we either fail to
// render whitespace changes (which is incredibly confusing,
// especially for python) or often produce a much larger set of
// differences than necessary.
$engine = new PhabricatorDifferenceEngine();
$engine->setIgnoreWhitespace(true);
$no_whitespace_changeset = $engine->generateChangesetFromFileContent(
$old_file,
$new_file);
// subparser takes over the current non-whitespace-ignoring changeset
$subparser = new DifferentialChangesetParser();
$subparser->isSubparser = true;
$subparser->setChangeset($changeset);
foreach ($changeset->getHunks() as $hunk) {
$subparser->parseHunk($hunk);
}
// We need to call process() so that the subparser's values for
// metadata (like 'unchanged') is correct.
$subparser->process();
$this->subparser = $subparser;
// While we aren't updating $this->changeset (since it has a bunch
// of metadata we need to preserve, so that headers like "this file
// was moved" render correctly), we're overwriting the local
// $changeset so that the block below will choose the synthetic
// hunks we've built instead of the original hunks.
$changeset = $no_whitespace_changeset;
}
// This either uses the real hunks, or synthetic hunks we built above.
foreach ($changeset->getHunks() as $hunk) {
$this->parseHunk($hunk);
}
$this->process();
if (!$skip_cache) {
$this->saveCache();
}
}
}
}
public function render(
$range_start = null,
$range_len = null,
$mask_force = array()) {
// "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();
$this->tryCacheStuff();
$feedback_mask = array();
switch ($this->changeset->getFileType()) {
case DifferentialChangeType::FILE_IMAGE:
$old = null;
$cur = null;
// TODO: Improve the architectural issue as discussed in D955
// https://secure.phabricator.com/D955
$reference = $this->renderingReference;
$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);
$vs_metadata = $vs_changeset->getMetadata();
$old_phid = idx($vs_metadata, 'new:binary-phid');
$changeset = id(new DifferentialChangeset())->load($id);
$metadata = $changeset->getMetadata();
$new_phid = idx($metadata, 'new:binary-phid');
}
if ($old_phid || $new_phid) {
// grab the files, (micro) optimization for 1 query not 2
$file_phids = array();
if ($old_phid) {
$file_phids[] = $old_phid;
}
if ($new_phid) {
$file_phids[] = $new_phid;
}
$files = id(new PhabricatorFile())->loadAllWhere(
'phid IN (%Ls)',
$file_phids);
foreach ($files as $file) {
if (empty($file)) {
continue;
}
if ($file->getPHID() == $old_phid) {
$old = phutil_render_tag(
'img',
array(
'src' => $file->getBestURI(),
));
} else {
$cur = phutil_render_tag(
'img',
array(
'src' => $file->getBestURI(),
));
}
}
}
$this->comments = msort($this->comments, 'getID');
$old_comments = array();
$new_comments = array();
foreach ($this->comments as $comment) {
if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
$new_comments[] = $comment;
} else {
$old_comments[] = $comment;
}
}
$html_old = array();
$html_new = array();
foreach ($old_comments as $comment) {
$xhp = $this->renderInlineComment($comment);
$html_old[] =
'<tr class="inline"><th /><td>'.
$xhp.
'</td><th /><td /></tr>';
}
foreach ($new_comments as $comment) {
$xhp = $this->renderInlineComment($comment);
$html_new[] =
'<tr class="inline"><th /><td /><th /><td>'.
$xhp.
'</td></tr>';
}
if (!$old) {
$th_old = '<th></th>';
}
else {
$th_old = '<th id="C'.$vs.'OL1">1</th>';
}
if (!$cur) {
$th_new = '<th></th>';
}
else {
$th_new = '<th id="C'.$id.'NL1">1</th>';
}
$output = $this->renderChangesetTable(
$this->changeset,
'<tr>'.
$th_old.
'<td class="differential-old-image">'.
'<div class="differential-image-stage">'.
$old.
'</div>'.
'</td>'.
$th_new.
'<td class="differential-new-image">'.
'<div class="differential-image-stage">'.
$cur.
'</div>'.
'</td>'.
'</tr>'.
implode('', $html_old).
implode('', $html_new));
return $output;
case DifferentialChangeType::FILE_DIRECTORY:
case DifferentialChangeType::FILE_BINARY:
$output = $this->renderChangesetTable($this->changeset, null);
return $output;
}
$shield = null;
if ($this->isTopLevel && !$this->comments) {
if ($this->isGenerated()) {
$shield = $this->renderShield(
"This file contains generated code, which does not normally need ".
"to be reviewed.",
true);
} else if ($this->isUnchanged()) {
if ($this->isWhitespaceOnly()) {
$shield = $this->renderShield(
"This file was changed only by adding or removing trailing ".
"whitespace.",
false);
} else {
$shield = $this->renderShield(
"The contents of this file were not changed.",
false);
}
} else if ($this->isDeleted()) {
$shield = $this->renderShield(
"This file was completely deleted.",
true);
} else if ($this->changeset->getAffectedLineCount() > 2500) {
$lines = number_format($this->changeset->getAffectedLineCount());
$shield = $this->renderShield(
"This file has a very large number of changes ({$lines} lines).",
true);
}
}
if ($shield) {
return $this->renderChangesetTable($this->changeset, $shield);
}
$old_comments = array();
$new_comments = array();
$old_mask = array();
$new_mask = array();
$feedback_mask = array();
if ($this->comments) {
foreach ($this->comments as $comment) {
$start = max($comment->getLineNumber() - self::LINES_CONTEXT, 0);
$end = $comment->getLineNumber() +
$comment->getLineLength() +
self::LINES_CONTEXT;
$new = $this->isCommentOnRightSideWhenDisplayed($comment);
for ($ii = $start; $ii <= $end; $ii++) {
if ($new) {
$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 = msort($this->comments, 'getID');
foreach ($this->comments as $comment) {
$final = $comment->getLineNumber() +
$comment->getLineLength();
if ($this->isCommentOnRightSideWhenDisplayed($comment)) {
$new_comments[$final][] = $comment;
} else {
$old_comments[$final][] = $comment;
}
}
}
$html = $this->renderTextChange(
$range_start,
$range_len,
$mask_force,
$feedback_mask,
$old_comments,
$new_comments);
return $this->renderChangesetTable($this->changeset, $html);
}
/**
* Determine if an inline comment will appear on the rendered diff,
* taking into consideration which halves of which changesets will actually
* be shown.
*
* @param DifferentialInlineComment Comment to test for visibility.
* @return bool True if the comment is visible on the rendered diff.
*/
private function isCommentVisibleOnRenderedDiff(
DifferentialInlineComment $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 DifferentialInlineComment Comment to test for display location.
* @return bool True for right, false for left.
*/
private function isCommentOnRightSideWhenDisplayed(
DifferentialInlineComment $comment) {
if (!$this->isCommentVisibleOnRenderedDiff($comment)) {
throw new Exception("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;
}
protected function renderShield($message, $more) {
if ($more) {
$end = $this->getLength();
$reference = $this->renderingReference;
$more =
' '.
javelin_render_tag(
'a',
array(
'mustcapture' => true,
'sigil' => 'show-more',
'class' => 'complete',
'href' => '#',
'meta' => array(
'ref' => $reference,
'range' => "0-{$end}",
),
),
'Show File Contents');
} else {
$more = null;
}
return javelin_render_tag(
'tr',
array(
'sigil' => 'context-target',
),
'<td class="differential-shield" colspan="5">'.
phutil_escape_html($message).
$more.
'</td>');
}
protected function renderTextChange(
$range_start,
$range_len,
$mask_force,
$feedback_mask,
array $old_comments,
array $new_comments) {
$context_not_available = null;
if ($this->missingOld || $this->missingNew) {
$context_not_available = javelin_render_tag(
'tr',
array(
'sigil' => 'context-target',
),
'<td colspan="5" class="show-more">'.
'Context not available.'.
'</td>');
}
$html = array();
$rows = max(
count($this->old),
count($this->new));
if ($range_start === null) {
$range_start = 0;
}
if ($range_len === null) {
$range_len = $rows;
}
$range_len = min($range_len, $rows - $range_start);
// Gaps - compute gaps in the visible display diff, where we will render
// "Show more context" spacers. This builds an aggregate $mask of all the
// lines we must show (because they are near changed lines, near inline
// comments, or the request has explicitly asked for them, i.e. resulting
// from the user clicking "show more") and then finds all the gaps between
// visible lines. 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.
$gaps = array();
$gap_start = 0;
$in_gap = false;
$mask = $this->visible + $mask_force + $feedback_mask;
$mask[$range_start + $range_len] = true;
for ($ii = $range_start; $ii <= $range_start + $range_len; $ii++) {
if (isset($mask[$ii])) {
if ($in_gap) {
$gap_length = $ii - $gap_start;
if ($gap_length <= self::LINES_CONTEXT) {
for ($jj = $gap_start; $jj <= $gap_start + $gap_length; $jj++) {
$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);
$reference = $this->renderingReference;
$left_id = $this->leftSideChangesetID;
$right_id = $this->rightSideChangesetID;
// "N" stands for 'new' and means the comment should attach to the new file
// when stored, i.e. DifferentialInlineComment->setIsNewFile().
// "O" stands for 'old' and means the comment should attach to the old file.
$left_char = $this->leftSideAttachesToNewFile
? 'N'
: 'O';
$right_char = $this->rightSideAttachesToNewFile
? 'N'
: 'O';
for ($ii = $range_start; $ii < $range_start + $range_len; $ii++) {
if (empty($mask[$ii])) {
// If we aren't going to show this line, we've just entered a gap.
// Pop information about the next gap off the $gaps stack and render
// an appropriate "Show more context" element. This branch eventually
// increments $ii by the entire size of the gap and then continues
// the loop.
$gap = array_pop($gaps);
$top = $gap[0];
$len = $gap[1];
$end = $top + $len - 20;
$contents = array();
if ($len > 40) {
$is_first_block = false;
if ($ii == 0) {
$is_first_block = true;
}
$contents[] = javelin_render_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'ref' => $reference,
'range' => "{$top}-{$len}/{$top}-20",
),
),
$is_first_block
? "Show First 20 Lines"
: "\xE2\x96\xB2 Show 20 Lines");
}
$contents[] = javelin_render_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'type' => 'all',
'ref' => $reference,
'range' => "{$top}-{$len}/{$top}-{$len}",
),
),
'Show All '.$len.' Lines');
if ($len > 40) {
$is_last_block = false;
if ($ii + $len >= $rows) {
$is_last_block = true;
}
$contents[] = javelin_render_tag(
'a',
array(
'href' => '#',
'mustcapture' => true,
'sigil' => 'show-more',
'meta' => array(
'ref' => $reference,
'range' => "{$top}-{$len}/{$end}-20",
),
),
$is_last_block
? "Show Last 20 Lines"
: "\xE2\x96\xBC Show 20 Lines");
};
$container = javelin_render_tag(
'tr',
array(
'sigil' => 'context-target',
),
'<td colspan="5" class="show-more">'.
implode(' &bull; ', $contents).
'</td>');
$html[] = $container;
$ii += ($len - 1);
continue;
}
if (isset($this->old[$ii])) {
$o_num = $this->old[$ii]['line'];
$o_text = isset($this->oldRender[$ii]) ? $this->oldRender[$ii] : null;
$o_attr = null;
if ($this->old[$ii]['type']) {
if (empty($this->new[$ii])) {
$o_attr = ' class="old old-full"';
} else {
$o_attr = ' class="old"';
}
}
} else {
$o_num = null;
$o_text = null;
$o_attr = null;
}
if (isset($this->new[$ii])) {
$n_num = $this->new[$ii]['line'];
$n_text = isset($this->newRender[$ii]) ? $this->newRender[$ii] : null;
$n_attr = null;
$cov_class = null;
if ($this->coverage !== null) {
if (empty($this->coverage[$n_num - 1])) {
$cov_class = 'N';
} else {
$cov_class = $this->coverage[$n_num - 1];
}
$cov_class = 'cov-'.$cov_class;
}
$n_cov = '<td class="cov '.$cov_class.'"></td>';
if ($this->new[$ii]['type']) {
if (empty($this->old[$ii])) {
$n_attr = ' class="new new-full"';
} else {
$n_attr = ' class="new"';
}
}
} else {
$n_num = null;
$n_text = null;
$n_attr = null;
$n_cov = null;
}
if (($o_num && !empty($this->missingOld[$o_num])) ||
($n_num && !empty($this->missingNew[$n_num]))) {
$html[] = $context_not_available;
}
if ($o_num && $left_id) {
$o_id = ' id="C'.$left_id.$left_char.'L'.$o_num.'"';
} else {
$o_id = null;
}
if ($n_num && $right_id) {
$n_id = ' id="C'.$right_id.$right_char.'L'.$n_num.'"';
} else {
$n_id = null;
}
// NOTE: The Javascript is sensitive to whitespace changes in this
// block!
$html[] =
'<tr>'.
'<th'.$o_id.'>'.$o_num.'</th>'.
'<td'.$o_attr.'>'.$o_text.'</td>'.
'<th'.$n_id.'>'.$n_num.'</th>'.
'<td'.$n_attr.'>'.$n_text.'</td>'.
$n_cov.
'</tr>';
if ($context_not_available && ($ii == $rows - 1)) {
$html[] = $context_not_available;
}
if ($o_num && isset($old_comments[$o_num])) {
foreach ($old_comments[$o_num] as $comment) {
$xhp = $this->renderInlineComment($comment);
$new = '';
if ($n_num && isset($new_comments[$n_num])) {
foreach ($new_comments[$n_num] as $key => $new_comment) {
if ($comment->isCompatible($new_comment)) {
$new = $this->renderInlineComment($new_comment);
unset($new_comments[$n_num][$key]);
}
}
}
$html[] =
'<tr class="inline"><th /><td>'.
$xhp.
'</td><th /><td>'.
$new.
'</td><td class="cov" /></tr>';
}
}
if ($n_num && isset($new_comments[$n_num])) {
foreach ($new_comments[$n_num] as $comment) {
$xhp = $this->renderInlineComment($comment);
$html[] =
'<tr class="inline"><th /><td /><th /><td>'.
$xhp.
'</td><td class="cov" /></tr>';
}
}
}
return implode('', $html);
}
private function renderInlineComment(DifferentialInlineComment $comment) {
$user = $this->user;
$edit = $user &&
($comment->getAuthorPHID() == $user->getPHID()) &&
(!$comment->getCommentID());
$on_right = $this->isCommentOnRightSideWhenDisplayed($comment);
return id(new DifferentialInlineCommentView())
->setInlineComment($comment)
->setOnRight($on_right)
->setHandles($this->handles)
->setMarkupEngine($this->markupEngine)
->setEditable($edit)
->render();
}
protected function renderPropertyChangeHeader($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 null;
}
$old = $changeset->getOldProperties();
$new = $changeset->getNewProperties();
if ($old === $new) {
return null;
}
if ($changeset->getChangeType() == DifferentialChangeType::TYPE_ADD &&
$new == array('unix:filemode' => '100644')) {
return null;
}
if ($changeset->getChangeType() == DifferentialChangeType::TYPE_DELETE &&
$old == array('unix:filemode' => '100644')) {
return null;
}
$keys = array_keys($old + $new);
sort($keys);
$rows = array();
foreach ($keys as $key) {
$oval = idx($old, $key);
$nval = idx($new, $key);
if ($oval !== $nval) {
if ($oval === null) {
$oval = '<em>null</em>';
} else {
$oval = phutil_escape_html($oval);
}
if ($nval === null) {
$nval = '<em>null</em>';
} else {
$nval = phutil_escape_html($nval);
}
$rows[] =
'<tr>'.
'<th>'.phutil_escape_html($key).'</th>'.
'<td class="oval">'.$oval.'</td>'.
'<td class="nval">'.$nval.'</td>'.
'</tr>';
}
}
return
'<table class="differential-property-table">'.
'<tr class="property-table-header">'.
'<th>Property Changes</th>'.
'<td class="oval">Old Value</td>'.
'<td class="nval">New Value</td>'.
'</tr>'.
implode('', $rows).
'</table>';
}
protected function renderChangesetTable($changeset, $contents) {
$props = $this->renderPropertyChangeHeader($this->changeset);
$table = null;
if ($contents) {
$table =
'<table class="differential-diff remarkup-code PhabricatorMonospaced">'.
$contents.
'</table>';
}
if (!$table && !$props) {
$notice = $this->renderChangeTypeHeader($this->changeset, true);
} else {
$notice = $this->renderChangeTypeHeader($this->changeset, false);
}
return implode(
"\n",
array(
$notice,
$props,
$table,
));
}
protected function renderChangeTypeHeader($changeset, $force) {
static $articles = array(
DifferentialChangeType::FILE_IMAGE => 'an',
);
static $files = array(
DifferentialChangeType::FILE_TEXT => 'file',
DifferentialChangeType::FILE_IMAGE => 'image',
DifferentialChangeType::FILE_DIRECTORY => 'directory',
DifferentialChangeType::FILE_BINARY => 'binary file',
DifferentialChangeType::FILE_SYMLINK => 'symlink',
DifferentialChangeType::FILE_SUBMODULE => 'submodule',
);
static $changes = array(
DifferentialChangeType::TYPE_ADD => 'added',
DifferentialChangeType::TYPE_CHANGE => 'changed',
DifferentialChangeType::TYPE_DELETE => 'deleted',
DifferentialChangeType::TYPE_MOVE_HERE => 'moved from',
DifferentialChangeType::TYPE_COPY_HERE => 'copied from',
DifferentialChangeType::TYPE_MOVE_AWAY => 'moved to',
DifferentialChangeType::TYPE_COPY_AWAY => 'copied to',
DifferentialChangeType::TYPE_MULTICOPY
=> 'deleted after being copied to',
);
$change = $changeset->getChangeType();
$file = $changeset->getFileType();
$message = null;
if ($change == DifferentialChangeType::TYPE_CHANGE &&
$file == DifferentialChangeType::FILE_TEXT) {
if ($force) {
// We have to force something to render because there were no changes
// of other kinds.
$message = "This {$files[$file]} was not modified.";
} else {
// Default case of changes to a text file, no metadata.
return null;
}
} else {
$verb = idx($changes, $change, 'changed');
switch ($change) {
default:
$message = "This {$files[$file]} was <strong>{$verb}</strong>.";
break;
case DifferentialChangeType::TYPE_MOVE_HERE:
case DifferentialChangeType::TYPE_COPY_HERE:
$message =
"This {$files[$file]} was {$verb} ".
"<strong>".
phutil_escape_html($changeset->getOldFile()).
"</strong>.";
break;
case DifferentialChangeType::TYPE_MOVE_AWAY:
case DifferentialChangeType::TYPE_COPY_AWAY:
case DifferentialChangeType::TYPE_MULTICOPY:
$paths = $changeset->getAwayPaths();
if (count($paths) > 1) {
$message =
"This {$files[$file]} was {$verb}: ".
"<strong>".phutil_escape_html(implode(', ', $paths))."</strong>.";
} else {
$message =
"This {$files[$file]} was {$verb} ".
"<strong>".phutil_escape_html(reset($paths))."</strong>.";
}
break;
case DifferentialChangeType::TYPE_CHANGE:
$message = "This is ".idx($articles, $file, 'a')." {$files[$file]}.";
break;
}
}
return
'<div class="differential-meta-notice">'.
$message.
'</div>';
}
public function renderForEmail() {
$ret = '';
$min = min(count($this->old), count($this->new));
for ($i = 0; $i < $min; $i++) {
$o = $this->old[$i];
$n = $this->new[$i];
if (!isset($this->visible[$i])) {
continue;
}
if ($o['line'] && $n['line']) {
// It is quite possible there are better ways to achieve this. For
// example, "white-space: pre;" can do a better job, WERE IT NOT for
// broken email clients like OWA which use newlines to do weird
// wrapping. So dont give them newlines.
if (isset($this->intra[$i])) {
$ret .= sprintf(
"<font color=\"red\">-&nbsp;%s</font><br/>",
str_replace(" ", "&nbsp;", phutil_escape_html($o['text']))
);
$ret .= sprintf(
"<font color=\"green\">+&nbsp;%s</font><br/>",
str_replace(" ", "&nbsp;", phutil_escape_html($n['text']))
);
} else {
$ret .= sprintf("&nbsp;&nbsp;%s<br/>",
str_replace(" ", "&nbsp;", phutil_escape_html($n['text']))
);
}
} else if ($o['line'] && !$n['line']) {
$ret .= sprintf(
"<font color=\"red\">-&nbsp;%s</font><br/>",
str_replace(" ", "&nbsp;", phutil_escape_html($o['text']))
);
} else {
$ret .= sprintf(
"<font color=\"green\">+&nbsp;%s</font><br/>",
str_replace(" ", "&nbsp;", phutil_escape_html($n['text']))
);
}
}
return $ret;
}
/**
* 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 = '<em>-</em>';
+
+ if (!$this->coverage) {
+ return $na;
+ }
+
+ $covered = 0;
+ $not_covered = 0;
+
+ foreach ($this->new as $k => $new) {
+ if (!$new['line']) {
+ continue;
+ }
+
+ if (!$new['type']) {
+ continue;
+ }
+
+ if (empty($this->coverage[$new['line'] - 1])) {
+ continue;
+ }
+
+ switch ($this->coverage[$new['line']]) {
+ case 'C':
+ $covered++;
+ break;
+ case 'U':
+ $not_covered++;
+ break;
+ }
+ }
+
+ if (!$covered && !$not_covered) {
+ return $na;
+ }
+
+ return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered)));
+ }
+
}
diff --git a/src/applications/differential/view/difftableofcontents/DifferentialDiffTableOfContentsView.php b/src/applications/differential/view/difftableofcontents/DifferentialDiffTableOfContentsView.php
index 54519fc0a0..9a46626011 100644
--- a/src/applications/differential/view/difftableofcontents/DifferentialDiffTableOfContentsView.php
+++ b/src/applications/differential/view/difftableofcontents/DifferentialDiffTableOfContentsView.php
@@ -1,211 +1,275 @@
<?php
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
final class DifferentialDiffTableOfContentsView extends AphrontView {
private $changesets = array();
private $repository;
private $diff;
private $user;
private $standaloneViewLink = null;
private $renderURI = '/differential/changeset/';
private $revisionID;
private $whitespace;
+ private $unitTestData;
public function setChangesets($changesets) {
$this->changesets = $changesets;
return $this;
}
public function setRepository(PhabricatorRepository $repository) {
$this->repository = $repository;
return $this;
}
public function setDiff(DifferentialDiff $diff) {
$this->diff = $diff;
return $this;
}
+ public function setUnitTestData($unit_test_data) {
+ $this->unitTestData = $unit_test_data;
+ return $this;
+ }
+
public function setUser(PhabricatorUser $user) {
$this->user = $user;
return $this;
}
public function setStandaloneViewLink($standalone_view_link) {
$this->standaloneViewLink = $standalone_view_link;
return $this;
}
public function setVsMap(array $vs_map) {
$this->vsMap = $vs_map;
return $this;
}
public function setRevisionID($revision_id) {
$this->revisionID = $revision_id;
return $this;
}
public function setWhitespace($whitespace) {
$this->whitespace = $whitespace;
return $this;
}
public function render() {
require_celerity_resource('differential-core-view-css');
require_celerity_resource('differential-table-of-contents-css');
$rows = array();
+ $coverage = array();
+ if ($this->unitTestData) {
+ $coverage_by_file = array();
+ foreach ($this->unitTestData as $result) {
+ $test_coverage = idx($result, 'coverage');
+ if (!$test_coverage) {
+ continue;
+ }
+ foreach ($test_coverage as $file => $results) {
+ $coverage_by_file[$file][] = $results;
+ }
+ }
+ foreach ($coverage_by_file as $file => $coverages) {
+ $coverage[$file] = ArcanistUnitTestResult::mergeCoverage($coverages);
+ }
+ }
+
$changesets = $this->changesets;
$paths = array();
foreach ($changesets as $changeset) {
$type = $changeset->getChangeType();
$ftype = $changeset->getFileType();
$link = $this->renderChangesetLink($changeset);
if (DifferentialChangeType::isOldLocationChangeType($type)) {
$away = $changeset->getAwayPaths();
if (count($away) > 1) {
$meta = array();
if ($type == DifferentialChangeType::TYPE_MULTICOPY) {
$meta[] = 'Deleted after being copied to multiple locations:';
} else {
$meta[] = 'Copied to multiple locations:';
}
foreach ($away as $path) {
$meta[] = phutil_escape_html($path);
}
$meta = implode('<br />', $meta);
} else {
if ($type == DifferentialChangeType::TYPE_MOVE_AWAY) {
$meta = 'Moved to '.phutil_escape_html(reset($away));
} else {
$meta = 'Copied to '.phutil_escape_html(reset($away));
}
}
} else if ($type == DifferentialChangeType::TYPE_MOVE_HERE) {
$meta = 'Moved from '.phutil_escape_html($changeset->getOldFile());
} else if ($type == DifferentialChangeType::TYPE_COPY_HERE) {
$meta = 'Copied from '.phutil_escape_html($changeset->getOldFile());
} else {
$meta = null;
}
$line_count = $changeset->getAffectedLineCount();
if ($line_count == 0) {
$lines = null;
} else if ($line_count == 1) {
$lines = ' (1 line)';
} else {
$lines = ' ('.$line_count.' lines)';
}
$char = DifferentialChangeType::getSummaryCharacterForChangeType($type);
$chartitle = DifferentialChangeType::getFullNameForChangeType($type);
$desc = DifferentialChangeType::getShortNameForFileType($ftype);
if ($desc) {
$desc = '('.$desc.')';
}
$pchar =
($changeset->getOldProperties() === $changeset->getNewProperties())
? null
: '<span title="Properties Changed">M</span>';
+ $fname = $changeset->getFilename();
+ $cov = $this->renderCoverage($coverage, $fname);
+ if ($cov === null) {
+ $mcov = $cov = '<em>-</em>';
+ } else {
+ $mcov = phutil_render_tag(
+ 'div',
+ array(
+ 'id' => 'differential-mcoverage-'.md5($fname),
+ 'class' => 'differential-mcoverage-loading',
+ ),
+ 'Loading...');
+ }
+
$rows[] =
'<tr>'.
'<td class="differential-toc-char" title='.$chartitle.'>'.$char.
'</td>'.
'<td class="differential-toc-prop">'.$pchar.'</td>'.
'<td class="differential-toc-ftype">'.$desc.'</td>'.
'<td class="differential-toc-file">'.$link.$lines.'</td>'.
+ '<td class="differential-toc-cov">'.$cov.'</td>'.
+ '<td class="differential-toc-mcov">'.$mcov.'</td>'.
'</tr>';
if ($meta) {
$rows[] =
'<tr>'.
'<td colspan="3"></td>'.
'<td class="differential-toc-meta">'.$meta.'</td>'.
'</tr>';
}
if ($this->diff && $this->repository) {
$paths[] =
$changeset->getAbsoluteRepositoryPath($this->diff, $this->repository);
}
}
$editor_link = null;
if ($paths && $this->user) {
$editor_link = $this->user->loadEditorLink(
implode(' ', $paths),
1, // line number
$this->repository);
if ($editor_link) {
$editor_link = phutil_render_tag(
'a',
array(
'href' => $editor_link,
'class' => 'button differential-toc-edit-all',
),
'Open All in Editor');
}
}
return
'<div class="differential-toc differential-panel">'.
$editor_link.
'<h1>Table of Contents</h1>'.
'<table>'.
+ '<tr>'.
+ '<th></th>'.
+ '<th></th>'.
+ '<th></th>'.
+ '<th>Path</th>'.
+ '<th class="differential-toc-cov">Coverage (All)</th>'.
+ '<th class="differential-toc-mcov">Coverage (Touched)</th>'.
+ '</tr>'.
implode("\n", $rows).
'</table>'.
'</div>';
}
+ private function renderCoverage(array $coverage, $file) {
+ $info = idx($coverage, $file);
+ if (!$info) {
+ return null;
+ }
+
+ $not_covered = substr_count($info, 'U');
+ $covered = substr_count($info, 'C');
+
+ if (!$not_covered && !$covered) {
+ return null;
+ }
+
+ return sprintf('%d%%', 100 * ($covered / ($covered + $not_covered)));
+ }
+
+
private function renderChangesetLink(DifferentialChangeset $changeset) {
$display_file = $changeset->getDisplayFilename();
if ($this->standaloneViewLink) {
$id = $changeset->getID();
$vs_id = idx($this->vsMap, $id);
$ref = $vs_id ? $id.'/'.$vs_id : $id;
$detail_uri = new PhutilURI($this->renderURI);
$detail_uri->setQueryParams(
array(
'ref' => $ref,
'whitespace' => $this->whitespace,
'revision_id' => $this->revisionID,
));
return phutil_render_tag(
'a',
array(
'href' => $detail_uri,
'target' => '_blank',
),
phutil_escape_html($display_file));
}
return phutil_render_tag(
'a',
array(
'href' => '#'.$changeset->getAnchorName(),
),
phutil_escape_html($display_file));
}
}
diff --git a/src/applications/differential/view/difftableofcontents/__init__.php b/src/applications/differential/view/difftableofcontents/__init__.php
index 9d378f861d..cc2333699b 100644
--- a/src/applications/differential/view/difftableofcontents/__init__.php
+++ b/src/applications/differential/view/difftableofcontents/__init__.php
@@ -1,18 +1,20 @@
<?php
/**
* This file is automatically generated. Lint this module to rebuild it.
* @generated
*/
+phutil_require_module('arcanist', 'unit/result');
+
phutil_require_module('phabricator', 'applications/differential/constants/changetype');
phutil_require_module('phabricator', 'infrastructure/celerity/api');
phutil_require_module('phabricator', 'view/base');
phutil_require_module('phutil', 'markup');
phutil_require_module('phutil', 'parser/uri');
phutil_require_module('phutil', 'utils');
phutil_require_source('DifferentialDiffTableOfContentsView.php');
diff --git a/webroot/rsrc/css/application/differential/table-of-contents.css b/webroot/rsrc/css/application/differential/table-of-contents.css
index 72b72cbdd5..cf25e9b1be 100644
--- a/webroot/rsrc/css/application/differential/table-of-contents.css
+++ b/webroot/rsrc/css/application/differential/table-of-contents.css
@@ -1,51 +1,82 @@
/**
* @provides differential-table-of-contents-css
*/
.differential-toc-meta {
color: #666666;
padding-left: 1em;
}
.differential-toc-char,
.differential-toc-prop {
width: 1.25em;
text-align: center;
font-weight: bold;
}
.differential-toc-ftype {
padding: 0 .5em;
text-align: center;
color: #666666;
}
.differential-toc-file {
color: #444444;
}
.differential-toc-edit-all {
float: right;
}
.diff-star-none {
color: #666666;
}
.diff-star-okay {
color: #ff9700;
}
/* TODO: 'warn' and 'fail' are both red, but we can't make 'warn' yellow since
'okay' is a "gold star". */
.diff-star-warn {
color: #aa0000;
}
.diff-star-fail {
color: #aa0000;
}
.diff-star-skip {
color: #ff00aa;
}
+
+.differential-toc table {
+ width: 100%;
+}
+
+.differential-toc table td.differential-toc-cov,
+.differential-toc table td.differential-toc-mcov {
+ width: 120px;
+ text-align: right;
+ padding-right: 6px;
+}
+
+.differential-toc table th {
+ color: #666666;
+ font-size: 11px;
+ padding: 0 4px 4px;
+ white-space: nowrap;
+}
+
+.differential-toc table th.differential-toc-cov,
+.differential-toc table th.differential-toc-mcov {
+ text-align: right;
+}
+
+.differential-toc table td em {
+ color: #666666;
+}
+
+.differential-mcoverage-loading {
+ color: #888888;
+}
diff --git a/webroot/rsrc/js/application/differential/behavior-populate.js b/webroot/rsrc/js/application/differential/behavior-populate.js
index ca68aea483..0430136feb 100644
--- a/webroot/rsrc/js/application/differential/behavior-populate.js
+++ b/webroot/rsrc/js/application/differential/behavior-populate.js
@@ -1,26 +1,35 @@
/**
* @provides javelin-behavior-differential-populate
* @requires javelin-behavior
* javelin-workflow
* javelin-util
* javelin-dom
*/
JX.behavior('differential-populate', function(config) {
function onresponse(target, response) {
- JX.DOM.replace(JX.$(target), JX.$H(response));
+ JX.DOM.replace(JX.$(target), JX.$H(response.changeset));
+ if (response.coverage) {
+ for (var k in response.coverage) {
+ try {
+ JX.DOM.replace(JX.$(k), JX.$H(response.coverage[k]));
+ } catch (ignored) {
+ // Not terribly important.
+ }
+ }
+ }
}
for (var k in config.registry) {
var data = {
ref : config.registry[k],
whitespace: config.whitespace
};
new JX.Workflow(config.uri, data)
.setHandler(JX.bind(null, onresponse, k))
.start();
}
});

File Metadata

Mime Type
text/x-diff
Expires
Jan 19 2025, 23:15 (6 w, 3 d ago)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1129786
Default Alt Text
(166 KB)

Event Timeline