diff --git a/resources/sql/patches/071.contentsource.sql b/resources/sql/patches/071.contentsource.sql
new file mode 100644
index 0000000000..eae1b7a608
--- /dev/null
+++ b/resources/sql/patches/071.contentsource.sql
@@ -0,0 +1,5 @@
+ALTER TABLE phabricator_differential.differential_comment
+  ADD contentSource VARCHAR(255);
+
+ALTER TABLE phabricator_maniphest.maniphest_transaction
+  ADD contentSource VARCHAR(255);
diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php
index 7ea524bddd..2d93ef3825 100644
--- a/src/__celerity_resource_map__.php
+++ b/src/__celerity_resource_map__.php
@@ -1,1548 +1,1557 @@
 <?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/3e2f3045/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/e4c5e4ed/rsrc/css/aphront/error-view.css',
     'type' => 'css',
     'requires' =>
     array(
     ),
     'disk' => '/rsrc/css/aphront/error-view.css',
   ),
   'aphront-form-view-css' =>
   array(
     'uri' => '/res/c79fd668/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/58da9c70/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/f92966bd/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/a05236a6/rsrc/css/aphront/typeahead.css',
     'type' => 'css',
     'requires' =>
     array(
     ),
     'disk' => '/rsrc/css/aphront/typeahead.css',
   ),
   'differential-changeset-view-css' =>
   array(
     'uri' => '/res/59279999/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/438fe316/rsrc/css/application/differential/core.css',
     'type' => 'css',
     'requires' =>
     array(
     ),
     'disk' => '/rsrc/css/application/differential/core.css',
   ),
   'differential-inline-comment-editor' =>
   array(
     'uri' => '/res/5e4f0aa4/rsrc/js/application/differential/DifferentialInlineCommentEditor.js',
     'type' => 'js',
     'requires' =>
     array(
       0 => 'javelin-dom',
       1 => 'javelin-workflow',
       2 => 'javelin-util',
       3 => 'javelin-stratcom',
       4 => 'javelin-install',
     ),
     'disk' => '/rsrc/js/application/differential/DifferentialInlineCommentEditor.js',
   ),
   '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/9fb8013b/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/015e5995/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/d173445b/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/bc39d876/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/db4566b6/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/5051f3ab/rsrc/css/application/herald/herald.css',
     'type' => 'css',
     'requires' =>
     array(
     ),
     'disk' => '/rsrc/css/application/herald/herald.css',
   ),
   'herald-rule-editor' =>
   array(
     'uri' => '/res/4d6dff2b/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-behavior' =>
   array(
     'uri' => '/res/24ce4647/rsrc/js/javelin/lib/behavior.js',
     'type' => 'js',
     'requires' =>
     array(
       0 => 'javelin-magical-init',
     ),
     'disk' => '/rsrc/js/javelin/lib/behavior.js',
   ),
   'javelin-behavior-aphront-basic-tokenizer' =>
   array(
     'uri' => '/res/5e183bd5/rsrc/js/application/core/behavior-tokenizer.js',
     'type' => 'js',
     'requires' =>
     array(
       0 => 'javelin-behavior',
       1 => 'javelin-typeahead',
       2 => 'javelin-tokenizer',
       3 => 'javelin-typeahead-preloaded-source',
       4 => 'javelin-dom',
       5 => 'javelin-stratcom',
     ),
     '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/fa7527f9/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',
     ),
     '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-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/fc352745/rsrc/js/application/differential/behavior-add-reviewers-and-ccs.js',
     'type' => 'js',
     'requires' =>
     array(
       0 => 'javelin-behavior',
       1 => 'javelin-dom',
       2 => 'javelin-tokenizer',
       3 => 'javelin-typeahead',
       4 => 'javelin-typeahead-preloaded-source',
     ),
     '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-edit-inline-comments' =>
   array(
     'uri' => '/res/9d4ca5d7/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/cb399257/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/e36415a2/rsrc/js/application/differential/behavior-keyboard-nav.js',
     'type' => 'js',
     'requires' =>
     array(
       0 => 'javelin-behavior',
       1 => 'javelin-dom',
       2 => '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',
     '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/bcc990f0/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/eaaccd7a/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-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/94a2a395/rsrc/js/application/maniphest/behavior-transaction-controls.js',
     'type' => 'js',
     'requires' =>
     array(
       0 => 'javelin-behavior',
       1 => 'javelin-dom',
       2 => 'javelin-tokenizer',
       3 => 'javelin-typeahead',
       4 => 'javelin-typeahead-preloaded-source',
     ),
     '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/34e22038/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',
   ),
+  0 =>
+  array(
+    'uri' => '/res/1da00bfe/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',
+  ),
   '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-keyboard-shortcuts' =>
   array(
     'uri' => '/res/94b009e2/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/1f7867ca/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/46298448/rsrc/js/application/core/behavior-watch-anchor.js',
     'type' => 'js',
     'requires' =>
     array(
       0 => 'javelin-behavior',
       1 => 'javelin-stratcom',
       2 => 'javelin-util',
       3 => 'javelin-dom',
     ),
     '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/a54d5616/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',
   ),
-  0 =>
-  array(
-    'uri' => '/res/1da00bfe/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',
-  ),
   '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-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-dom' =>
   array(
     'uri' => '/res/0fb82146/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/f14f00d7/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/2b609814/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-install' =>
   array(
     'uri' => '/res/a7a57bf8/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/6a069362/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/64d41874/rsrc/js/javelin/ext/reactor/core/Reactor.js',
     'type' => 'js',
     'requires' =>
     array(
       0 => 'javelin-install',
       1 => 'javelin-dynval',
       2 => 'javelin-reactornode',
       3 => 'javelin-util',
     ),
     'disk' => '/rsrc/js/javelin/ext/reactor/core/Reactor.js',
   ),
   'javelin-reactor-dom' =>
   array(
     'uri' => '/res/d8ab6a5f/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/b8a3ae50/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/80744b69/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',
     ),
     '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/da8a2726/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/5b541fb2/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/9169c000/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/8b6055d4/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/d82d7faf/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/57299d35/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/a6d12b2c/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/21ca8b6c/rsrc/js/javelin/core/util.js',
     'type' => 'js',
     'requires' =>
     array(
     ),
     'disk' => '/rsrc/js/javelin/core/util.js',
   ),
   'javelin-vector' =>
   array(
     'uri' => '/res/50535cb8/rsrc/js/javelin/lib/Vector.js',
     'type' => 'js',
     'requires' =>
     array(
       0 => 'javelin-install',
       1 => 'javelin-event',
     ),
     'disk' => '/rsrc/js/javelin/lib/Vector.js',
   ),
   'javelin-workflow' =>
   array(
     'uri' => '/res/76fa44f3/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',
   ),
   'mainphest-task-detail-css' =>
   array(
     'uri' => '/res/e2fbb468/rsrc/css/application/maniphest/task-detail.css',
     'type' => 'css',
     'requires' =>
     array(
     ),
     'disk' => '/rsrc/css/application/maniphest/task-detail.css',
   ),
   'maniphest-task-summary-css' =>
   array(
     'uri' => '/res/14cb4b5d/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/149fccab/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-content-source-view-css' =>
+  array(
+    'uri' => '/res/7147f14c/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/3059cf79/rsrc/css/core/buttons.css',
     'type' => 'css',
     'requires' =>
     array(
     ),
     'disk' => '/rsrc/css/core/buttons.css',
   ),
   'phabricator-core-css' =>
   array(
     'uri' => '/res/bd20d04b/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/a3d307c5/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-feed-css' =>
   array(
     'uri' => '/res/32e5879b/rsrc/css/application/feed/feed.css',
     'type' => 'css',
     'requires' =>
     array(
     ),
     'disk' => '/rsrc/css/application/feed/feed.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/04767571/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-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-prefab' =>
   array(
     'uri' => '/res/5784a112/rsrc/js/application/core/Prefab.js',
     'type' => 'js',
     'requires' =>
     array(
       0 => 'javelin-install',
       1 => 'javelin-util',
       2 => 'javelin-dom',
     ),
     'disk' => '/rsrc/js/application/core/Prefab.js',
   ),
   'phabricator-profile-css' =>
   array(
     'uri' => '/res/ebe1ac2f/rsrc/css/application/profile/profile-view.css',
     'type' => 'css',
     'requires' =>
     array(
     ),
     'disk' => '/rsrc/css/application/profile/profile-view.css',
   ),
   'phabricator-remarkup-css' =>
   array(
     'uri' => '/res/7ca89111/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/ee0667c4/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/325b5b0f/rsrc/css/application/base/standard-page-view.css',
     'type' => 'css',
     'requires' =>
     array(
     ),
     'disk' => '/rsrc/css/application/base/standard-page-view.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',
   ),
   'phriction-document-css' =>
   array(
     'uri' => '/res/a0eb9ea1/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',
   ),
   'syntax-highlighting-css' =>
   array(
     'uri' => '/res/14bc2ee1/rsrc/css/core/syntax.css',
     'type' => 'css',
     'requires' =>
     array(
     ),
     'disk' => '/rsrc/css/core/syntax.css',
   ),
 ), array(
   'packages' =>
   array(
     '03ef179e' =>
     array(
       'name' => 'diffusion.pkg.css',
       'symbols' =>
       array(
         0 => 'diffusion-commit-view-css',
       ),
       'uri' => '/res/pkg/03ef179e/diffusion.pkg.css',
       'type' => 'css',
     ),
     '3dbf4083' =>
     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/3dbf4083/javelin.pkg.js',
       'type' => 'js',
     ),
     '3f2092d7' =>
     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',
       ),
       'uri' => '/res/pkg/3f2092d7/differential.pkg.css',
       'type' => 'css',
     ),
     '66a154fc' =>
     array(
       'name' => 'differential.pkg.js',
       'symbols' =>
       array(
         0 => 'javelin-behavior-differential-feedback-preview',
         1 => 'javelin-behavior-differential-edit-inline-comments',
         2 => 'javelin-behavior-differential-populate',
         3 => 'javelin-behavior-differential-show-more',
         4 => 'javelin-behavior-differential-diff-radios',
       ),
       'uri' => '/res/pkg/66a154fc/differential.pkg.js',
       'type' => 'js',
     ),
     '95c67dcd' =>
     array(
       'name' => 'workflow.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',
       ),
       'uri' => '/res/pkg/95c67dcd/workflow.pkg.js',
       'type' => 'js',
     ),
     'ac869011' =>
     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/ac869011/typeahead.pkg.js',
       'type' => 'js',
     ),
     70966590 =>
     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-remarkup-css',
         14 => 'syntax-highlighting-css',
       ),
       'uri' => '/res/pkg/70966590/core.pkg.css',
       'type' => 'css',
     ),
   ),
   'reverse' =>
   array(
     'aphront-crumbs-view-css' => '70966590',
     'aphront-dialog-view-css' => '70966590',
     'aphront-form-view-css' => '70966590',
     'aphront-list-filter-view-css' => '70966590',
     'aphront-panel-view-css' => '70966590',
     'aphront-side-nav-view-css' => '70966590',
     'aphront-table-view-css' => '70966590',
     'aphront-tokenizer-control-css' => '70966590',
     'aphront-typeahead-control-css' => '70966590',
     'differential-changeset-view-css' => '3f2092d7',
     'differential-core-view-css' => '3f2092d7',
     'differential-revision-add-comment-css' => '3f2092d7',
     'differential-revision-comment-css' => '3f2092d7',
     'differential-revision-comment-list-css' => '3f2092d7',
     'differential-revision-detail-css' => '3f2092d7',
     'differential-revision-history-css' => '3f2092d7',
     'differential-table-of-contents-css' => '3f2092d7',
     'diffusion-commit-view-css' => '03ef179e',
     'javelin-behavior' => '3dbf4083',
     'javelin-behavior-aphront-basic-tokenizer' => 'ac869011',
     'javelin-behavior-aphront-form-disable-on-submit' => '95c67dcd',
     'javelin-behavior-differential-diff-radios' => '66a154fc',
     'javelin-behavior-differential-edit-inline-comments' => '66a154fc',
     'javelin-behavior-differential-feedback-preview' => '66a154fc',
     'javelin-behavior-differential-populate' => '66a154fc',
     'javelin-behavior-differential-show-more' => '66a154fc',
     'javelin-behavior-phabricator-keyboard-shortcuts' => '95c67dcd',
     'javelin-behavior-workflow' => '95c67dcd',
     'javelin-dom' => '3dbf4083',
     'javelin-event' => '3dbf4083',
     'javelin-install' => '3dbf4083',
     'javelin-json' => '3dbf4083',
     'javelin-mask' => '95c67dcd',
     'javelin-request' => '3dbf4083',
     'javelin-stratcom' => '3dbf4083',
     'javelin-tokenizer' => 'ac869011',
     'javelin-typeahead' => 'ac869011',
     'javelin-typeahead-normalizer' => 'ac869011',
     'javelin-typeahead-ondemand-source' => 'ac869011',
     'javelin-typeahead-preloaded-source' => 'ac869011',
     'javelin-typeahead-source' => 'ac869011',
     'javelin-uri' => '3dbf4083',
     'javelin-util' => '3dbf4083',
     'javelin-vector' => '3dbf4083',
     'javelin-workflow' => '95c67dcd',
     'phabricator-core-buttons-css' => '70966590',
     'phabricator-core-css' => '70966590',
     'phabricator-directory-css' => '70966590',
     'phabricator-keyboard-shortcut' => '95c67dcd',
     'phabricator-keyboard-shortcut-manager' => '95c67dcd',
     'phabricator-remarkup-css' => '70966590',
     'phabricator-standard-page-view' => '70966590',
     'syntax-highlighting-css' => '70966590',
   ),
 ));
diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php
index e30db127a6..ed33b347be 100644
--- a/src/__phutil_library_map__.php
+++ b/src/__phutil_library_map__.php
@@ -1,1241 +1,1244 @@
 <?php
 
 /**
  * This file is automatically generated. Use 'phutil_mapper.php' to rebuild it.
  * @generated
  */
 
 phutil_register_library_map(array(
   'class' =>
   array(
     'Aphront304Response' => 'aphront/response/304',
     'Aphront400Response' => 'aphront/response/400',
     'Aphront404Response' => 'aphront/response/404',
     'AphrontAjaxResponse' => 'aphront/response/ajax',
     'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration',
     'AphrontAttachedFileView' => 'view/control/attachedfile',
     'AphrontCSRFException' => 'aphront/exception/csrf',
     'AphrontCalendarMonthView' => 'applications/calendar/view/month',
     'AphrontContextBarView' => 'view/layout/contextbar',
     'AphrontController' => 'aphront/controller',
     'AphrontCrumbsView' => 'view/layout/crumbs',
     'AphrontDatabaseConnection' => 'storage/connection/base',
     'AphrontDefaultApplicationConfiguration' => 'aphront/default/configuration',
     'AphrontDefaultApplicationController' => 'aphront/default/controller',
     'AphrontDialogResponse' => 'aphront/response/dialog',
     'AphrontDialogView' => 'view/dialog',
     'AphrontErrorView' => 'view/form/error',
     'AphrontException' => 'aphront/exception/base',
     'AphrontFilePreviewView' => 'view/layout/filepreview',
     'AphrontFileResponse' => 'aphront/response/file',
     'AphrontFormCheckboxControl' => 'view/form/control/checkbox',
     'AphrontFormControl' => 'view/form/control/base',
     'AphrontFormDividerControl' => 'view/form/control/divider',
     'AphrontFormDragAndDropUploadControl' => 'view/form/control/draganddropupload',
     'AphrontFormFileControl' => 'view/form/control/file',
     'AphrontFormLayoutView' => 'view/form/layout',
     'AphrontFormMarkupControl' => 'view/form/control/markup',
     'AphrontFormPasswordControl' => 'view/form/control/password',
     'AphrontFormRecaptchaControl' => 'view/form/control/recaptcha',
     'AphrontFormSelectControl' => 'view/form/control/select',
     'AphrontFormStaticControl' => 'view/form/control/static',
     'AphrontFormSubmitControl' => 'view/form/control/submit',
     'AphrontFormTextAreaControl' => 'view/form/control/textarea',
     'AphrontFormTextControl' => 'view/form/control/text',
     'AphrontFormToggleButtonsControl' => 'view/form/control/togglebuttons',
     'AphrontFormTokenizerControl' => 'view/form/control/tokenizer',
     'AphrontFormView' => 'view/form/base',
     'AphrontHeadsupActionListView' => 'view/layout/headsup/actionlist',
     'AphrontHeadsupActionView' => 'view/layout/headsup/action',
     'AphrontIsolatedDatabaseConnection' => 'storage/connection/isolated',
     'AphrontIsolatedDatabaseConnectionTestCase' => 'storage/connection/isolated/__tests__',
     'AphrontKeyboardShortcutsAvailableView' => 'view/widget/keyboardshortcuts',
     'AphrontListFilterView' => 'view/layout/listfilter',
     'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql',
     'AphrontNullView' => 'view/null',
     'AphrontPageView' => 'view/page/base',
     'AphrontPagerView' => 'view/control/pager',
     'AphrontPanelView' => 'view/layout/panel',
     'AphrontQueryAccessDeniedException' => 'storage/exception/accessdenied',
     'AphrontQueryConnectionException' => 'storage/exception/connection',
     'AphrontQueryConnectionLostException' => 'storage/exception/connectionlost',
     'AphrontQueryCountException' => 'storage/exception/count',
     'AphrontQueryDuplicateKeyException' => 'storage/exception/duplicatekey',
     'AphrontQueryException' => 'storage/exception/base',
     'AphrontQueryObjectMissingException' => 'storage/exception/objectmissing',
     'AphrontQueryParameterException' => 'storage/exception/parameter',
     'AphrontQueryRecoverableException' => 'storage/exception/recoverable',
     'AphrontRedirectException' => 'aphront/exception/redirect',
     'AphrontRedirectResponse' => 'aphront/response/redirect',
     'AphrontReloadResponse' => 'aphront/response/reload',
     'AphrontRequest' => 'aphront/request',
     'AphrontRequestFailureView' => 'view/page/failure',
     'AphrontResponse' => 'aphront/response/base',
     'AphrontScopedUnguardedWriteCapability' => 'aphront/writeguard/scopeguard',
     'AphrontSideNavView' => 'view/layout/sidenav',
     'AphrontTableView' => 'view/control/table',
     'AphrontTokenizerTemplateView' => 'view/control/tokenizer',
     'AphrontTypeaheadTemplateView' => 'view/control/typeahead',
     'AphrontURIMapper' => 'aphront/mapper',
     'AphrontView' => 'view/base',
     'AphrontWebpageResponse' => 'aphront/response/webpage',
     'AphrontWriteGuard' => 'aphront/writeguard',
     'CelerityAPI' => 'infrastructure/celerity/api',
     'CelerityResourceController' => 'infrastructure/celerity/controller',
     'CelerityResourceMap' => 'infrastructure/celerity/map',
     'CelerityStaticResourceResponse' => 'infrastructure/celerity/response',
     'ConduitAPIMethod' => 'applications/conduit/method/base',
     'ConduitAPIRequest' => 'applications/conduit/protocol/request',
     'ConduitAPI_conduit_connect_Method' => 'applications/conduit/method/conduit/connect',
     'ConduitAPI_conduit_getcertificate_Method' => 'applications/conduit/method/conduit/getcertificate',
     'ConduitAPI_conduit_ping_Method' => 'applications/conduit/method/conduit/ping',
     'ConduitAPI_daemon_launched_Method' => 'applications/conduit/method/daemon/launched',
     'ConduitAPI_daemon_log_Method' => 'applications/conduit/method/daemon/log',
     'ConduitAPI_differential_creatediff_Method' => 'applications/conduit/method/differential/creatediff',
     'ConduitAPI_differential_createrevision_Method' => 'applications/conduit/method/differential/createrevision',
     'ConduitAPI_differential_find_Method' => 'applications/conduit/method/differential/find',
     'ConduitAPI_differential_getalldiffs_Method' => 'applications/conduit/method/differential/getalldiffs',
     'ConduitAPI_differential_getcommitmessage_Method' => 'applications/conduit/method/differential/getcommitmessage',
     'ConduitAPI_differential_getcommitpaths_Method' => 'applications/conduit/method/differential/getcommitpaths',
     'ConduitAPI_differential_getdiff_Method' => 'applications/conduit/method/differential/getdiff',
     'ConduitAPI_differential_getrevision_Method' => 'applications/conduit/method/differential/getrevision',
     'ConduitAPI_differential_getrevisionfeedback_Method' => 'applications/conduit/method/differential/getrevisionfeedback',
     'ConduitAPI_differential_markcommitted_Method' => 'applications/conduit/method/differential/markcommitted',
     'ConduitAPI_differential_parsecommitmessage_Method' => 'applications/conduit/method/differential/parsecommitmessage',
     'ConduitAPI_differential_setdiffproperty_Method' => 'applications/conduit/method/differential/setdiffproperty',
     'ConduitAPI_differential_updaterevision_Method' => 'applications/conduit/method/differential/updaterevision',
     'ConduitAPI_differential_updatetaskrevisionassoc_Method' => 'applications/conduit/method/differential/updatetaskrevisionassoc',
     'ConduitAPI_differential_updateunitresults_Method' => 'applications/conduit/method/differential/updateunitresults',
     'ConduitAPI_diffusion_getcommits_Method' => 'applications/conduit/method/diffusion/getcommits',
     'ConduitAPI_diffusion_getrecentcommitsbypath_Method' => 'applications/conduit/method/diffusion/getrecentcommitsbypath',
     'ConduitAPI_feed_publish_Method' => 'applications/conduit/method/feed/publish',
     'ConduitAPI_file_download_Method' => 'applications/conduit/method/file/download',
     'ConduitAPI_file_info_Method' => 'applications/conduit/method/file/info',
     'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload',
     'ConduitAPI_maniphest_Method' => 'applications/conduit/method/maniphest/base',
     'ConduitAPI_maniphest_createtask_Method' => 'applications/conduit/method/maniphest/createtask',
     'ConduitAPI_maniphest_info_Method' => 'applications/conduit/method/maniphest/info',
     'ConduitAPI_paste_Method' => 'applications/conduit/method/paste/base',
     'ConduitAPI_paste_create_Method' => 'applications/conduit/method/paste/create',
     'ConduitAPI_paste_info_Method' => 'applications/conduit/method/paste/info',
     'ConduitAPI_path_getowners_Method' => 'applications/conduit/method/path/getowners',
     'ConduitAPI_slowvote_info_Method' => 'applications/conduit/method/slowvote/info',
     'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find',
     'ConduitAPI_user_whoami_Method' => 'applications/conduit/method/user/whoami',
     'ConduitException' => 'applications/conduit/protocol/exception',
     'DarkConsole' => 'aphront/console/api',
     'DarkConsoleConfigPlugin' => 'aphront/console/plugin/config',
     'DarkConsoleController' => 'aphront/console/controller',
     'DarkConsoleCore' => 'aphront/console/core',
     'DarkConsoleErrorLogPlugin' => 'aphront/console/plugin/errorlog',
     'DarkConsoleErrorLogPluginAPI' => 'aphront/console/plugin/errorlog/api',
     'DarkConsolePlugin' => 'aphront/console/plugin/base',
     'DarkConsoleRequestPlugin' => 'aphront/console/plugin/request',
     'DarkConsoleServicesPlugin' => 'aphront/console/plugin/services',
     'DarkConsoleXHProfPlugin' => 'aphront/console/plugin/xhprof',
     'DarkConsoleXHProfPluginAPI' => 'aphront/console/plugin/xhprof/api',
     'DatabaseConfigurationProvider' => 'applications/base/storage/configuration',
     'DifferentialAction' => 'applications/differential/constants/action',
     'DifferentialAddCommentView' => 'applications/differential/view/addcomment',
     'DifferentialApplyPatchFieldSpecification' => 'applications/differential/field/specification/applypatch',
     'DifferentialArcanistProjectFieldSpecification' => 'applications/differential/field/specification/arcanistproject',
     'DifferentialAuthorFieldSpecification' => 'applications/differential/field/specification/author',
     'DifferentialAuxiliaryField' => 'applications/differential/storage/auxiliaryfield',
     'DifferentialBlameRevisionFieldSpecification' => 'applications/differential/field/specification/blamerev',
     'DifferentialCCWelcomeMail' => 'applications/differential/mail/ccwelcome',
     'DifferentialCCsFieldSpecification' => 'applications/differential/field/specification/ccs',
     'DifferentialChangeType' => 'applications/differential/constants/changetype',
     'DifferentialChangeset' => 'applications/differential/storage/changeset',
     'DifferentialChangesetDetailView' => 'applications/differential/view/changesetdetailview',
     'DifferentialChangesetListView' => 'applications/differential/view/changesetlistview',
     'DifferentialChangesetParser' => 'applications/differential/parser/changeset',
     'DifferentialChangesetViewController' => 'applications/differential/controller/changesetview',
     'DifferentialComment' => 'applications/differential/storage/comment',
     'DifferentialCommentEditor' => 'applications/differential/editor/comment',
     'DifferentialCommentMail' => 'applications/differential/mail/comment',
     'DifferentialCommentPreviewController' => 'applications/differential/controller/commentpreview',
     'DifferentialCommentSaveController' => 'applications/differential/controller/commentsave',
     'DifferentialCommitsFieldSpecification' => 'applications/differential/field/specification/commits',
     'DifferentialController' => 'applications/differential/controller/base',
     'DifferentialDAO' => 'applications/differential/storage/base',
     'DifferentialDefaultFieldSelector' => 'applications/differential/field/selector/default',
     'DifferentialDependenciesFieldSpecification' => 'applications/differential/field/specification/dependencies',
     'DifferentialDiff' => 'applications/differential/storage/diff',
     'DifferentialDiffContentMail' => 'applications/differential/mail/diffcontent',
     'DifferentialDiffCreateController' => 'applications/differential/controller/diffcreate',
     'DifferentialDiffProperty' => 'applications/differential/storage/diffproperty',
     'DifferentialDiffTableOfContentsView' => 'applications/differential/view/difftableofcontents',
     'DifferentialDiffViewController' => 'applications/differential/controller/diffview',
     'DifferentialExceptionMail' => 'applications/differential/mail/exception',
     'DifferentialExportPatchFieldSpecification' => 'applications/differential/field/specification/exportpatch',
     'DifferentialFieldDataNotAvailableException' => 'applications/differential/field/exception/notavailable',
     'DifferentialFieldParseException' => 'applications/differential/field/exception/parse',
     'DifferentialFieldSelector' => 'applications/differential/field/selector/base',
     'DifferentialFieldSpecification' => 'applications/differential/field/specification/base',
     'DifferentialFieldSpecificationIncompleteException' => 'applications/differential/field/exception/incomplete',
     'DifferentialFieldValidationException' => 'applications/differential/field/exception/validation',
     'DifferentialGitSVNIDFieldSpecification' => 'applications/differential/field/specification/gitsvnid',
     'DifferentialHostFieldSpecification' => 'applications/differential/field/specification/host',
     'DifferentialHunk' => 'applications/differential/storage/hunk',
     'DifferentialInlineComment' => 'applications/differential/storage/inlinecomment',
     'DifferentialInlineCommentEditController' => 'applications/differential/controller/inlinecommentedit',
     'DifferentialInlineCommentPreviewController' => 'applications/differential/controller/inlinecommentpreview',
     'DifferentialInlineCommentView' => 'applications/differential/view/inlinecomment',
     'DifferentialLinesFieldSpecification' => 'applications/differential/field/specification/lines',
     'DifferentialLintFieldSpecification' => 'applications/differential/field/specification/lint',
     'DifferentialLintStatus' => 'applications/differential/constants/lintstatus',
     'DifferentialMail' => 'applications/differential/mail/base',
     'DifferentialManiphestTasksFieldSpecification' => 'applications/differential/field/specification/maniphesttasks',
     'DifferentialNewDiffMail' => 'applications/differential/mail/newdiff',
     'DifferentialPathFieldSpecification' => 'applications/differential/field/specification/path',
     'DifferentialPrimaryPaneView' => 'applications/differential/view/primarypane',
     'DifferentialReplyHandler' => 'applications/differential/replyhandler',
     'DifferentialRevertPlanFieldSpecification' => 'applications/differential/field/specification/revertplan',
     'DifferentialReviewRequestMail' => 'applications/differential/mail/reviewrequest',
     'DifferentialReviewedByFieldSpecification' => 'applications/differential/field/specification/reviewedby',
     'DifferentialReviewersFieldSpecification' => 'applications/differential/field/specification/reviewers',
     'DifferentialRevision' => 'applications/differential/storage/revision',
     'DifferentialRevisionCommentListView' => 'applications/differential/view/revisioncommentlist',
     'DifferentialRevisionCommentView' => 'applications/differential/view/revisioncomment',
     'DifferentialRevisionControlSystem' => 'applications/differential/constants/revisioncontrolsystem',
     'DifferentialRevisionDetailRenderer' => 'applications/differential/controller/customrenderer',
     'DifferentialRevisionDetailView' => 'applications/differential/view/revisiondetail',
     'DifferentialRevisionEditController' => 'applications/differential/controller/revisionedit',
     'DifferentialRevisionEditor' => 'applications/differential/editor/revision',
     'DifferentialRevisionIDFieldSpecification' => 'applications/differential/field/specification/revisionid',
     'DifferentialRevisionListController' => 'applications/differential/controller/revisionlist',
     'DifferentialRevisionListData' => 'applications/differential/data/revisionlist',
     'DifferentialRevisionStatus' => 'applications/differential/constants/revisionstatus',
     'DifferentialRevisionStatusFieldSpecification' => 'applications/differential/field/specification/revisionstatus',
     'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/revisionupdatehistory',
     'DifferentialRevisionViewController' => 'applications/differential/controller/revisionview',
     'DifferentialSubscribeController' => 'applications/differential/controller/subscribe',
     'DifferentialSummaryFieldSpecification' => 'applications/differential/field/specification/summary',
     'DifferentialTasksAttacher' => 'applications/differential/tasks',
     'DifferentialTestPlanFieldSpecification' => 'applications/differential/field/specification/testplan',
     'DifferentialTitleFieldSpecification' => 'applications/differential/field/specification/title',
     'DifferentialUnitFieldSpecification' => 'applications/differential/field/specification/unit',
     'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus',
     'DifferentialUnitTestResult' => 'applications/differential/constants/unittestresult',
     'DifferentialViewTime' => 'applications/differential/storage/viewtime',
     'DiffusionBranchInformation' => 'applications/diffusion/data/branch',
     'DiffusionBranchQuery' => 'applications/diffusion/query/branch/base',
     'DiffusionBranchTableView' => 'applications/diffusion/view/branchtable',
     'DiffusionBrowseController' => 'applications/diffusion/controller/browse',
     'DiffusionBrowseFileController' => 'applications/diffusion/controller/file',
     'DiffusionBrowseQuery' => 'applications/diffusion/query/browse/base',
     'DiffusionBrowseTableView' => 'applications/diffusion/view/browsetable',
     'DiffusionChangeController' => 'applications/diffusion/controller/change',
     'DiffusionCommitChangeTableView' => 'applications/diffusion/view/commitchangetable',
     'DiffusionCommitController' => 'applications/diffusion/controller/commit',
     'DiffusionCommitListController' => 'applications/diffusion/controller/commitlist',
     'DiffusionController' => 'applications/diffusion/controller/base',
     'DiffusionDiffController' => 'applications/diffusion/controller/diff',
     'DiffusionDiffQuery' => 'applications/diffusion/query/diff/base',
     'DiffusionFileContent' => 'applications/diffusion/data/filecontent',
     'DiffusionFileContentQuery' => 'applications/diffusion/query/filecontent/base',
     'DiffusionGitBranchQuery' => 'applications/diffusion/query/branch/git',
     'DiffusionGitBrowseQuery' => 'applications/diffusion/query/browse/git',
     'DiffusionGitDiffQuery' => 'applications/diffusion/query/diff/git',
     'DiffusionGitFileContentQuery' => 'applications/diffusion/query/filecontent/git',
     'DiffusionGitHistoryQuery' => 'applications/diffusion/query/history/git',
     'DiffusionGitLastModifiedQuery' => 'applications/diffusion/query/lastmodified/git',
     'DiffusionGitPathIDQuery' => 'applications/diffusion/query/pathid/base',
     'DiffusionGitRequest' => 'applications/diffusion/request/git',
     'DiffusionHistoryController' => 'applications/diffusion/controller/history',
     'DiffusionHistoryQuery' => 'applications/diffusion/query/history/base',
     'DiffusionHistoryTableView' => 'applications/diffusion/view/historytable',
     'DiffusionHomeController' => 'applications/diffusion/controller/home',
     'DiffusionLastModifiedController' => 'applications/diffusion/controller/lastmodified',
     'DiffusionLastModifiedQuery' => 'applications/diffusion/query/lastmodified/base',
     'DiffusionPathChange' => 'applications/diffusion/data/pathchange',
     'DiffusionPathChangeQuery' => 'applications/diffusion/query/pathchange/base',
     'DiffusionPathCompleteController' => 'applications/diffusion/controller/pathcomplete',
     'DiffusionPathValidateController' => 'applications/diffusion/controller/pathvalidate',
     'DiffusionRepositoryController' => 'applications/diffusion/controller/repository',
     'DiffusionRepositoryPath' => 'applications/diffusion/data/repositorypath',
     'DiffusionRequest' => 'applications/diffusion/request/base',
     'DiffusionSvnBrowseQuery' => 'applications/diffusion/query/browse/svn',
     'DiffusionSvnDiffQuery' => 'applications/diffusion/query/diff/svn',
     'DiffusionSvnFileContentQuery' => 'applications/diffusion/query/filecontent/svn',
     'DiffusionSvnHistoryQuery' => 'applications/diffusion/query/history/svn',
     'DiffusionSvnLastModifiedQuery' => 'applications/diffusion/query/lastmodified/svn',
     'DiffusionSvnRequest' => 'applications/diffusion/request/svn',
     'DiffusionView' => 'applications/diffusion/view/base',
     'HeraldAction' => 'applications/herald/storage/action',
     'HeraldActionConfig' => 'applications/herald/config/action',
     'HeraldApplyTranscript' => 'applications/herald/storage/transcript/apply',
     'HeraldCommitAdapter' => 'applications/herald/adapter/commit',
     'HeraldCondition' => 'applications/herald/storage/condition',
     'HeraldConditionConfig' => 'applications/herald/config/condition',
     'HeraldConditionTranscript' => 'applications/herald/storage/transcript/condition',
     'HeraldContentTypeConfig' => 'applications/herald/config/contenttype',
     'HeraldController' => 'applications/herald/controller/base',
     'HeraldDAO' => 'applications/herald/storage/base',
     'HeraldDeleteController' => 'applications/herald/controller/delete',
     'HeraldDifferentialRevisionAdapter' => 'applications/herald/adapter/differential',
     'HeraldDryRunAdapter' => 'applications/herald/adapter/dryrun',
     'HeraldEffect' => 'applications/herald/engine/effect',
     'HeraldEngine' => 'applications/herald/engine/engine',
     'HeraldFieldConfig' => 'applications/herald/config/field',
     'HeraldHomeController' => 'applications/herald/controller/home',
     'HeraldInvalidConditionException' => 'applications/herald/engine/engine/exception',
     'HeraldInvalidFieldException' => 'applications/herald/engine/engine/exception',
     'HeraldNewController' => 'applications/herald/controller/new',
     'HeraldObjectAdapter' => 'applications/herald/adapter/base',
     'HeraldObjectTranscript' => 'applications/herald/storage/transcript/object',
     'HeraldRecursiveConditionsException' => 'applications/herald/engine/engine/exception',
     'HeraldRepetitionPolicyConfig' => 'applications/herald/config/repetitionpolicy',
     'HeraldRule' => 'applications/herald/storage/rule',
     'HeraldRuleController' => 'applications/herald/controller/rule',
     'HeraldRuleTranscript' => 'applications/herald/storage/transcript/rule',
     'HeraldTestConsoleController' => 'applications/herald/controller/test',
     'HeraldTranscript' => 'applications/herald/storage/transcript/base',
     'HeraldTranscriptController' => 'applications/herald/controller/transcript',
     'HeraldTranscriptListController' => 'applications/herald/controller/transcriptlist',
     'HeraldValueTypeConfig' => 'applications/herald/config/valuetype',
     'Javelin' => 'infrastructure/javelin/api',
     'LiskDAO' => 'storage/lisk/dao',
     'LiskIsolationTestCase' => 'storage/lisk/dao/__tests__',
     'LiskIsolationTestDAO' => 'storage/lisk/dao/__tests__',
     'LiskIsolationTestDAOException' => 'storage/lisk/dao/__tests__',
     'ManiphestAuxiliaryFieldDefaultSpecification' => 'applications/maniphest/auxiliaryfield/default',
     'ManiphestAuxiliaryFieldSpecification' => 'applications/maniphest/auxiliaryfield/base',
     'ManiphestAuxiliaryFieldTypeException' => 'applications/maniphest/auxiliaryfield/typeexception',
     'ManiphestAuxiliaryFieldValidationException' => 'applications/maniphest/auxiliaryfield/validationexception',
     'ManiphestConstants' => 'applications/maniphest/constants/base',
     'ManiphestController' => 'applications/maniphest/controller/base',
     'ManiphestDAO' => 'applications/maniphest/storage/base',
     'ManiphestDefaultTaskExtensions' => 'applications/maniphest/extensions/task',
     'ManiphestReplyHandler' => 'applications/maniphest/replyhandler',
     'ManiphestTask' => 'applications/maniphest/storage/task',
     'ManiphestTaskAuxiliaryStorage' => 'applications/maniphest/storage/auxiliary',
     'ManiphestTaskDescriptionChangeController' => 'applications/maniphest/controller/descriptionchange',
     'ManiphestTaskDetailController' => 'applications/maniphest/controller/taskdetail',
     'ManiphestTaskEditController' => 'applications/maniphest/controller/taskedit',
     'ManiphestTaskExtensions' => 'applications/maniphest/extensions/base',
     'ManiphestTaskListController' => 'applications/maniphest/controller/tasklist',
     'ManiphestTaskListView' => 'applications/maniphest/view/tasklist',
     'ManiphestTaskOwner' => 'applications/maniphest/constants/owner',
     'ManiphestTaskPriority' => 'applications/maniphest/constants/priority',
     'ManiphestTaskProject' => 'applications/maniphest/storage/taskproject',
     'ManiphestTaskQuery' => 'applications/maniphest/query',
     'ManiphestTaskStatus' => 'applications/maniphest/constants/status',
     'ManiphestTaskSubscriber' => 'applications/maniphest/storage/subscriber',
     'ManiphestTaskSummaryView' => 'applications/maniphest/view/tasksummary',
     'ManiphestTransaction' => 'applications/maniphest/storage/transaction',
     'ManiphestTransactionDetailView' => 'applications/maniphest/view/transactiondetail',
     'ManiphestTransactionEditor' => 'applications/maniphest/editor/transaction',
     'ManiphestTransactionListView' => 'applications/maniphest/view/transactionlist',
     'ManiphestTransactionPreviewController' => 'applications/maniphest/controller/transactionpreview',
     'ManiphestTransactionSaveController' => 'applications/maniphest/controller/transactionsave',
     'ManiphestTransactionType' => 'applications/maniphest/constants/transactiontype',
     'ManiphestView' => 'applications/maniphest/view/base',
     'Phabricator404Controller' => 'applications/base/controller/404',
     'PhabricatorAuthController' => 'applications/auth/controller/base',
     'PhabricatorCalendarBrowseController' => 'applications/calendar/controller/browse',
     'PhabricatorCalendarController' => 'applications/calendar/controller/base',
     'PhabricatorConduitAPIController' => 'applications/conduit/controller/api',
     'PhabricatorConduitCertificateToken' => 'applications/conduit/storage/token',
     'PhabricatorConduitConnectionLog' => 'applications/conduit/storage/connectionlog',
     'PhabricatorConduitConsoleController' => 'applications/conduit/controller/console',
     'PhabricatorConduitController' => 'applications/conduit/controller/base',
     'PhabricatorConduitDAO' => 'applications/conduit/storage/base',
     'PhabricatorConduitLogController' => 'applications/conduit/controller/log',
     'PhabricatorConduitMethodCallLog' => 'applications/conduit/storage/methodcalllog',
     'PhabricatorConduitTokenController' => 'applications/conduit/controller/token',
+    'PhabricatorContentSource' => 'applications/metamta/contentsource/source',
+    'PhabricatorContentSourceView' => 'applications/metamta/contentsource/view',
     'PhabricatorController' => 'applications/base/controller/base',
     'PhabricatorCountdownController' => 'applications/countdown/controller/base',
     'PhabricatorCountdownDAO' => 'applications/countdown/storage/base',
     'PhabricatorCountdownDeleteController' => 'applications/countdown/controller/delete',
     'PhabricatorCountdownEditController' => 'applications/countdown/controller/edit',
     'PhabricatorCountdownListController' => 'applications/countdown/controller/list',
     'PhabricatorCountdownViewController' => 'applications/countdown/controller/view',
     'PhabricatorDaemon' => 'infrastructure/daemon/base',
     'PhabricatorDaemonCombinedLogController' => 'applications/daemon/controller/combined',
     'PhabricatorDaemonConsoleController' => 'applications/daemon/controller/console',
     'PhabricatorDaemonControl' => 'infrastructure/daemon/control',
     'PhabricatorDaemonController' => 'applications/daemon/controller/base',
     'PhabricatorDaemonDAO' => 'infrastructure/daemon/storage/base',
     'PhabricatorDaemonLog' => 'infrastructure/daemon/storage/log',
     'PhabricatorDaemonLogEvent' => 'infrastructure/daemon/storage/event',
     'PhabricatorDaemonLogEventsView' => 'applications/daemon/view/daemonlogevents',
     'PhabricatorDaemonLogListController' => 'applications/daemon/controller/loglist',
     'PhabricatorDaemonLogListView' => 'applications/daemon/view/daemonloglist',
     'PhabricatorDaemonLogViewController' => 'applications/daemon/controller/logview',
     'PhabricatorDaemonReference' => 'infrastructure/daemon/control/reference',
     'PhabricatorDaemonTimelineConsoleController' => 'applications/daemon/controller/timeline',
     'PhabricatorDaemonTimelineEventController' => 'applications/daemon/controller/timelineevent',
     'PhabricatorDefaultFileStorageEngineSelector' => 'applications/files/engineselector/default',
     'PhabricatorDefaultSearchEngineSelector' => 'applications/search/selector/default',
     'PhabricatorDifferenceEngine' => 'infrastructure/diff/engine',
     'PhabricatorDirectoryCategory' => 'applications/directory/storage/category',
     'PhabricatorDirectoryCategoryDeleteController' => 'applications/directory/controller/categorydelete',
     'PhabricatorDirectoryCategoryEditController' => 'applications/directory/controller/categoryedit',
     'PhabricatorDirectoryCategoryListController' => 'applications/directory/controller/categorylist',
     'PhabricatorDirectoryController' => 'applications/directory/controller/base',
     'PhabricatorDirectoryDAO' => 'applications/directory/storage/base',
     'PhabricatorDirectoryItem' => 'applications/directory/storage/item',
     'PhabricatorDirectoryItemDeleteController' => 'applications/directory/controller/itemdelete',
     'PhabricatorDirectoryItemEditController' => 'applications/directory/controller/itemedit',
     'PhabricatorDirectoryItemListController' => 'applications/directory/controller/itemlist',
     'PhabricatorDirectoryMainController' => 'applications/directory/controller/main',
     'PhabricatorDisabledUserController' => 'applications/auth/controller/disabled',
     'PhabricatorDraft' => 'applications/draft/storage/draft',
     'PhabricatorDraftDAO' => 'applications/draft/storage/base',
     'PhabricatorEmailLoginController' => 'applications/auth/controller/email',
     'PhabricatorEmailTokenController' => 'applications/auth/controller/emailtoken',
     'PhabricatorEnv' => 'infrastructure/env',
     'PhabricatorFeedConstants' => 'applications/feed/constants/base',
     'PhabricatorFeedController' => 'applications/feed/controller/base',
     'PhabricatorFeedDAO' => 'applications/feed/storage/base',
     'PhabricatorFeedPublicStreamController' => 'applications/feed/controller/publicstream',
     'PhabricatorFeedQuery' => 'applications/feed/query',
     'PhabricatorFeedStory' => 'applications/feed/story/base',
     'PhabricatorFeedStoryData' => 'applications/feed/storage/story',
     'PhabricatorFeedStoryDifferential' => 'applications/feed/story/differential',
     'PhabricatorFeedStoryPhriction' => 'applications/feed/story/phriction',
     'PhabricatorFeedStoryPublisher' => 'applications/feed/publisher',
     'PhabricatorFeedStoryReference' => 'applications/feed/storage/storyreference',
     'PhabricatorFeedStoryStatus' => 'applications/feed/story/status',
     'PhabricatorFeedStoryTypeConstants' => 'applications/feed/constants/story',
     'PhabricatorFeedStoryUnknown' => 'applications/feed/story/unknown',
     'PhabricatorFeedStoryView' => 'applications/feed/view/story',
     'PhabricatorFeedStreamController' => 'applications/feed/controller/stream',
     'PhabricatorFeedView' => 'applications/feed/view/base',
     'PhabricatorFile' => 'applications/files/storage/file',
     'PhabricatorFileAltViewController' => 'applications/files/controller/altview',
     'PhabricatorFileController' => 'applications/files/controller/base',
     'PhabricatorFileDAO' => 'applications/files/storage/base',
     'PhabricatorFileDropUploadController' => 'applications/files/controller/dropupload',
     'PhabricatorFileImageMacro' => 'applications/files/storage/imagemacro',
     'PhabricatorFileListController' => 'applications/files/controller/list',
     'PhabricatorFileMacroDeleteController' => 'applications/files/controller/macrodelete',
     'PhabricatorFileMacroEditController' => 'applications/files/controller/macroedit',
     'PhabricatorFileMacroListController' => 'applications/files/controller/macrolist',
     'PhabricatorFileProxyController' => 'applications/files/controller/proxy',
     'PhabricatorFileProxyImage' => 'applications/files/storage/proxyimage',
     'PhabricatorFileStorageBlob' => 'applications/files/storage/storageblob',
     'PhabricatorFileStorageEngine' => 'applications/files/engine/base',
     'PhabricatorFileStorageEngineSelector' => 'applications/files/engineselector/base',
     'PhabricatorFileTransformController' => 'applications/files/controller/transform',
     'PhabricatorFileURI' => 'applications/files/uri',
     'PhabricatorFileUploadController' => 'applications/files/controller/upload',
     'PhabricatorFileUploadException' => 'applications/files/exception/upload',
     'PhabricatorFileViewController' => 'applications/files/controller/view',
     'PhabricatorGarbageCollectorDaemon' => 'infrastructure/daemon/garbagecollector',
     'PhabricatorGoodForNothingWorker' => 'infrastructure/daemon/workers/worker/goodfornothing',
     'PhabricatorHandleObjectSelectorDataView' => 'applications/phid/handle/view/selector',
     'PhabricatorHelpController' => 'applications/help/controller/base',
     'PhabricatorHelpKeyboardShortcutController' => 'applications/help/controller/keyboardshortcut',
     'PhabricatorIRCBot' => 'infrastructure/daemon/irc/bot',
     'PhabricatorIRCHandler' => 'infrastructure/daemon/irc/handler/base',
     'PhabricatorIRCMessage' => 'infrastructure/daemon/irc/message',
     'PhabricatorIRCObjectNameHandler' => 'infrastructure/daemon/irc/handler/objectname',
     'PhabricatorIRCProtocolHandler' => 'infrastructure/daemon/irc/handler/protocol',
     'PhabricatorImageTransformer' => 'applications/files/transform',
     'PhabricatorJavelinLinter' => 'infrastructure/lint/linter/javelin',
     'PhabricatorLintEngine' => 'infrastructure/lint/engine',
     'PhabricatorLiskDAO' => 'applications/base/storage/lisk',
     'PhabricatorLocalDiskFileStorageEngine' => 'applications/files/engine/localdisk',
     'PhabricatorLoginController' => 'applications/auth/controller/login',
     'PhabricatorLogoutController' => 'applications/auth/controller/logout',
     'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/base',
     'PhabricatorMailImplementationAmazonSESAdapter' => 'applications/metamta/adapter/amazonses',
     'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/phpmailerlite',
     'PhabricatorMailImplementationSendGridAdapter' => 'applications/metamta/adapter/sendgrid',
     'PhabricatorMailImplementationTestAdapter' => 'applications/metamta/adapter/test',
     'PhabricatorMailReplyHandler' => 'applications/metamta/replyhandler/base',
     'PhabricatorMarkupEngine' => 'applications/markup/engine',
     'PhabricatorMetaMTAController' => 'applications/metamta/controller/base',
     'PhabricatorMetaMTADAO' => 'applications/metamta/storage/base',
     'PhabricatorMetaMTADaemon' => 'applications/metamta/daemon/mta',
     'PhabricatorMetaMTAEmailBodyParser' => 'applications/metamta/parser',
     'PhabricatorMetaMTAEmailBodyParserTestCase' => 'applications/metamta/parser/__tests__',
     'PhabricatorMetaMTAListController' => 'applications/metamta/controller/list',
     'PhabricatorMetaMTAMail' => 'applications/metamta/storage/mail',
     'PhabricatorMetaMTAMailTestCase' => 'applications/metamta/storage/mail/__tests__',
     'PhabricatorMetaMTAMailingList' => 'applications/metamta/storage/mailinglist',
     'PhabricatorMetaMTAMailingListEditController' => 'applications/metamta/controller/mailinglistedit',
     'PhabricatorMetaMTAMailingListsController' => 'applications/metamta/controller/mailinglists',
     'PhabricatorMetaMTAReceiveController' => 'applications/metamta/controller/receive',
     'PhabricatorMetaMTAReceivedListController' => 'applications/metamta/controller/receivedlist',
     'PhabricatorMetaMTAReceivedMail' => 'applications/metamta/storage/receivedmail',
     'PhabricatorMetaMTASendController' => 'applications/metamta/controller/send',
     'PhabricatorMetaMTASendGridReceiveController' => 'applications/metamta/controller/sendgridreceive',
     'PhabricatorMetaMTAViewController' => 'applications/metamta/controller/view',
     'PhabricatorMySQLFileStorageEngine' => 'applications/files/engine/mysql',
     'PhabricatorOAuthDefaultRegistrationController' => 'applications/auth/controller/oauthregistration/default',
     'PhabricatorOAuthDiagnosticsController' => 'applications/auth/controller/oauthdiagnostics',
     'PhabricatorOAuthFailureView' => 'applications/auth/view/oauthfailure',
     'PhabricatorOAuthLoginController' => 'applications/auth/controller/oauth',
     'PhabricatorOAuthProvider' => 'applications/auth/oauth/provider/base',
     'PhabricatorOAuthProviderFacebook' => 'applications/auth/oauth/provider/facebook',
     'PhabricatorOAuthProviderGithub' => 'applications/auth/oauth/provider/github',
     'PhabricatorOAuthRegistrationController' => 'applications/auth/controller/oauthregistration/base',
     'PhabricatorOAuthUnlinkController' => 'applications/auth/controller/unlink',
     'PhabricatorObjectGraph' => 'applications/phid/graph',
     'PhabricatorObjectHandle' => 'applications/phid/handle',
     'PhabricatorObjectHandleConstants' => 'applications/phid/handle/const/base',
     'PhabricatorObjectHandleData' => 'applications/phid/handle/data',
     'PhabricatorObjectHandleStatus' => 'applications/phid/handle/const/status',
     'PhabricatorObjectSelectorDialog' => 'view/control/objectselector',
     'PhabricatorOwnersController' => 'applications/owners/controller/base',
     'PhabricatorOwnersDAO' => 'applications/owners/storage/base',
     'PhabricatorOwnersDeleteController' => 'applications/owners/controller/delete',
     'PhabricatorOwnersDetailController' => 'applications/owners/controller/detail',
     'PhabricatorOwnersEditController' => 'applications/owners/controller/edit',
     'PhabricatorOwnersListController' => 'applications/owners/controller/list',
     'PhabricatorOwnersOwner' => 'applications/owners/storage/owner',
     'PhabricatorOwnersPackage' => 'applications/owners/storage/package',
     'PhabricatorOwnersPath' => 'applications/owners/storage/path',
     'PhabricatorPHID' => 'applications/phid/storage/phid',
     'PhabricatorPHIDConstants' => 'applications/phid/constants',
     'PhabricatorPHIDController' => 'applications/phid/controller/base',
     'PhabricatorPHIDDAO' => 'applications/phid/storage/base',
     'PhabricatorPHIDListController' => 'applications/phid/controller/list',
     'PhabricatorPHIDLookupController' => 'applications/phid/controller/lookup',
     'PhabricatorPaste' => 'applications/paste/storage/paste',
     'PhabricatorPasteController' => 'applications/paste/controller/base',
     'PhabricatorPasteCreateController' => 'applications/paste/controller/create',
     'PhabricatorPasteDAO' => 'applications/paste/storage/base',
     'PhabricatorPasteListController' => 'applications/paste/controller/list',
     'PhabricatorPasteViewController' => 'applications/paste/controller/view',
     'PhabricatorPeopleController' => 'applications/people/controller/base',
     'PhabricatorPeopleEditController' => 'applications/people/controller/edit',
     'PhabricatorPeopleListController' => 'applications/people/controller/list',
     'PhabricatorPeopleLogsController' => 'applications/people/controller/logs',
     'PhabricatorPeopleProfileController' => 'applications/people/controller/profile',
     'PhabricatorProfileView' => 'view/layout/profile',
     'PhabricatorProject' => 'applications/project/storage/project',
     'PhabricatorProjectAffiliation' => 'applications/project/storage/affiliation',
     'PhabricatorProjectAffiliationEditController' => 'applications/project/controller/editaffiliation',
     'PhabricatorProjectController' => 'applications/project/controller/base',
     'PhabricatorProjectCreateController' => 'applications/project/controller/create',
     'PhabricatorProjectDAO' => 'applications/project/storage/base',
     'PhabricatorProjectListController' => 'applications/project/controller/list',
     'PhabricatorProjectProfile' => 'applications/project/storage/profile',
     'PhabricatorProjectProfileController' => 'applications/project/controller/profile',
     'PhabricatorProjectProfileEditController' => 'applications/project/controller/profileedit',
     'PhabricatorProjectStatus' => 'applications/project/constants/status',
     'PhabricatorProjectSubproject' => 'applications/project/storage/subproject',
     'PhabricatorRedirectController' => 'applications/base/controller/redirect',
     'PhabricatorRefreshCSRFController' => 'applications/auth/controller/refresh',
     'PhabricatorRemarkupRuleDifferential' => 'infrastructure/markup/remarkup/markuprule/differential',
     'PhabricatorRemarkupRuleDifferentialHandle' => 'infrastructure/markup/remarkup/markuprule/handle/differential',
     'PhabricatorRemarkupRuleDiffusion' => 'infrastructure/markup/remarkup/markuprule/diffusion',
     'PhabricatorRemarkupRuleEmbedFile' => 'infrastructure/markup/remarkup/markuprule/embedobject',
     'PhabricatorRemarkupRuleImageMacro' => 'infrastructure/markup/remarkup/markuprule/imagemacro',
     'PhabricatorRemarkupRuleManiphest' => 'infrastructure/markup/remarkup/markuprule/maniphest',
     'PhabricatorRemarkupRuleManiphestHandle' => 'infrastructure/markup/remarkup/markuprule/handle/maniphest',
     'PhabricatorRemarkupRuleMention' => 'infrastructure/markup/remarkup/markuprule/mention',
     'PhabricatorRemarkupRuleObjectHandle' => 'infrastructure/markup/remarkup/markuprule/handle',
     'PhabricatorRemarkupRuleObjectName' => 'infrastructure/markup/remarkup/markuprule/objectname',
     'PhabricatorRemarkupRulePaste' => 'infrastructure/markup/remarkup/markuprule/paste',
     'PhabricatorRemarkupRulePhriction' => 'infrastructure/markup/remarkup/markuprule/phriction',
     'PhabricatorRemarkupRuleProxyImage' => 'infrastructure/markup/remarkup/markuprule/proxyimage',
     'PhabricatorRemarkupRuleYoutube' => 'infrastructure/markup/remarkup/markuprule/youtube',
     'PhabricatorRepository' => 'applications/repository/storage/repository',
     'PhabricatorRepositoryArcanistProject' => 'applications/repository/storage/arcanistproject',
     'PhabricatorRepositoryArcanistProjectEditController' => 'applications/repository/controller/arcansistprojectedit',
     'PhabricatorRepositoryCommit' => 'applications/repository/storage/commit',
     'PhabricatorRepositoryCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/base',
     'PhabricatorRepositoryCommitData' => 'applications/repository/storage/commitdata',
     'PhabricatorRepositoryCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/base',
     'PhabricatorRepositoryCommitHeraldWorker' => 'applications/repository/worker/herald',
     'PhabricatorRepositoryCommitMessageDetailParser' => 'applications/repository/parser/base',
     'PhabricatorRepositoryCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/base',
     'PhabricatorRepositoryCommitParserWorker' => 'applications/repository/worker/base',
     'PhabricatorRepositoryCommitTaskDaemon' => 'applications/repository/daemon/committask',
     'PhabricatorRepositoryController' => 'applications/repository/controller/base',
     'PhabricatorRepositoryCreateController' => 'applications/repository/controller/create',
     'PhabricatorRepositoryDAO' => 'applications/repository/storage/base',
     'PhabricatorRepositoryDaemon' => 'applications/repository/daemon/base',
     'PhabricatorRepositoryDefaultCommitMessageDetailParser' => 'applications/repository/parser/default',
     'PhabricatorRepositoryDeleteController' => 'applications/repository/controller/delete',
     'PhabricatorRepositoryEditController' => 'applications/repository/controller/edit',
     'PhabricatorRepositoryGitCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/git',
     'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/git',
     'PhabricatorRepositoryGitCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/git',
     'PhabricatorRepositoryGitFetchDaemon' => 'applications/repository/daemon/gitfetch',
     'PhabricatorRepositoryGitHubNotification' => 'applications/repository/storage/githubnotification',
     'PhabricatorRepositoryGitHubPostReceiveController' => 'applications/repository/controller/github-post-receive',
     'PhabricatorRepositoryListController' => 'applications/repository/controller/list',
     'PhabricatorRepositoryShortcut' => 'applications/repository/storage/shortcut',
     'PhabricatorRepositorySvnCommitChangeParserWorker' => 'applications/repository/worker/commitchangeparser/svn',
     'PhabricatorRepositorySvnCommitDiscoveryDaemon' => 'applications/repository/daemon/commitdiscovery/svn',
     'PhabricatorRepositorySvnCommitMessageParserWorker' => 'applications/repository/worker/commitmessageparser/svn',
     'PhabricatorRepositoryType' => 'applications/repository/constants/repositorytype',
     'PhabricatorS3FileStorageEngine' => 'applications/files/engine/s3',
     'PhabricatorSQLPatchList' => 'infrastructure/setup/sql',
     'PhabricatorSearchAbstractDocument' => 'applications/search/index/abstractdocument',
     'PhabricatorSearchAttachController' => 'applications/search/controller/attach',
     'PhabricatorSearchBaseController' => 'applications/search/controller/base',
     'PhabricatorSearchCommitIndexer' => 'applications/search/index/indexer/repository',
     'PhabricatorSearchController' => 'applications/search/controller/search',
     'PhabricatorSearchDAO' => 'applications/search/storage/base',
     'PhabricatorSearchDifferentialIndexer' => 'applications/search/index/indexer/differential',
     'PhabricatorSearchDocument' => 'applications/search/storage/document/document',
     'PhabricatorSearchDocumentField' => 'applications/search/storage/document/field',
     'PhabricatorSearchDocumentIndexer' => 'applications/search/index/indexer/base',
     'PhabricatorSearchDocumentRelationship' => 'applications/search/storage/document/relationship',
     'PhabricatorSearchEngine' => 'applications/search/engine/base',
     'PhabricatorSearchEngineMySQL' => 'applications/search/engine/mysql',
     'PhabricatorSearchEngineSelector' => 'applications/search/selector/base',
     'PhabricatorSearchField' => 'applications/search/constants/field',
     'PhabricatorSearchIndexController' => 'applications/search/controller/index',
     'PhabricatorSearchManiphestIndexer' => 'applications/search/index/indexer/maniphest',
     'PhabricatorSearchPhrictionIndexer' => 'applications/search/index/indexer/phriction',
     'PhabricatorSearchQuery' => 'applications/search/storage/query',
     'PhabricatorSearchRelationship' => 'applications/search/constants/relationship',
     'PhabricatorSearchResultView' => 'applications/search/view/searchresult',
     'PhabricatorSearchSelectController' => 'applications/search/controller/select',
     'PhabricatorSearchUserIndexer' => 'applications/search/index/indexer/user',
     'PhabricatorSetup' => 'infrastructure/setup',
     'PhabricatorSlowvoteChoice' => 'applications/slowvote/storage/choice',
     'PhabricatorSlowvoteComment' => 'applications/slowvote/storage/comment',
     'PhabricatorSlowvoteController' => 'applications/slowvote/controller/base',
     'PhabricatorSlowvoteCreateController' => 'applications/slowvote/controller/create',
     'PhabricatorSlowvoteDAO' => 'applications/slowvote/storage/base',
     'PhabricatorSlowvoteListController' => 'applications/slowvote/controller/list',
     'PhabricatorSlowvoteOption' => 'applications/slowvote/storage/option',
     'PhabricatorSlowvotePoll' => 'applications/slowvote/storage/poll',
     'PhabricatorSlowvotePollController' => 'applications/slowvote/controller/poll',
     'PhabricatorStandardPageView' => 'view/page/standard',
     'PhabricatorStatusController' => 'applications/status/base',
     'PhabricatorSyntaxHighlighter' => 'applications/markup/syntax',
     'PhabricatorTaskmasterDaemon' => 'infrastructure/daemon/workers/taskmaster',
     'PhabricatorTestCase' => 'infrastructure/testing/testcase',
     'PhabricatorTimelineCursor' => 'infrastructure/daemon/timeline/storage/cursor',
     'PhabricatorTimelineDAO' => 'infrastructure/daemon/timeline/storage/base',
     'PhabricatorTimelineEvent' => 'infrastructure/daemon/timeline/storage/event',
     'PhabricatorTimelineEventData' => 'infrastructure/daemon/timeline/storage/eventdata',
     'PhabricatorTimelineIterator' => 'infrastructure/daemon/timeline/cursor/iterator',
     'PhabricatorTimer' => 'applications/countdown/storage/timer',
     'PhabricatorTransformedFile' => 'applications/files/storage/transformed',
     'PhabricatorTrivialTestCase' => 'infrastructure/testing/testcase/__tests__',
     'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/common',
     'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base',
     'PhabricatorUIExample' => 'applications/uiexample/examples/base',
     'PhabricatorUIExampleController' => 'applications/uiexample/controller/base',
     'PhabricatorUIExampleRenderController' => 'applications/uiexample/controller/render',
     'PhabricatorUIListFilterExample' => 'applications/uiexample/examples/listfilter',
     'PhabricatorUIPagerExample' => 'applications/uiexample/examples/pager',
     'PhabricatorUser' => 'applications/people/storage/user',
     'PhabricatorUserAccountSettingsPanelController' => 'applications/people/controller/settings/panels/account',
     'PhabricatorUserConduitSettingsPanelController' => 'applications/people/controller/settings/panels/conduit',
     'PhabricatorUserDAO' => 'applications/people/storage/base',
     'PhabricatorUserEmailSettingsPanelController' => 'applications/people/controller/settings/panels/email',
     'PhabricatorUserLog' => 'applications/people/storage/log',
     'PhabricatorUserOAuthInfo' => 'applications/people/storage/useroauthinfo',
     'PhabricatorUserOAuthSettingsPanelController' => 'applications/people/controller/settings/panels/oauth',
     'PhabricatorUserPreferenceSettingsPanelController' => 'applications/people/controller/settings/panels/preferences',
     'PhabricatorUserPreferences' => 'applications/people/storage/preferences',
     'PhabricatorUserProfile' => 'applications/people/storage/profile',
     'PhabricatorUserProfileSettingsPanelController' => 'applications/people/controller/settings/panels/profile',
     'PhabricatorUserSSHKey' => 'applications/people/storage/usersshkey',
     'PhabricatorUserSSHKeysSettingsPanelController' => 'applications/people/controller/settings/panels/sshkeys',
     'PhabricatorUserSettingsController' => 'applications/people/controller/settings',
     'PhabricatorUserSettingsPanelController' => 'applications/people/controller/settings/panels/base',
     'PhabricatorWorker' => 'infrastructure/daemon/workers/worker',
     'PhabricatorWorkerDAO' => 'infrastructure/daemon/workers/storage/base',
     'PhabricatorWorkerTask' => 'infrastructure/daemon/workers/storage/task',
     'PhabricatorWorkerTaskData' => 'infrastructure/daemon/workers/storage/taskdata',
     'PhabricatorWorkerTaskDetailController' => 'applications/daemon/controller/workertaskdetail',
     'PhabricatorXHPASTViewController' => 'applications/xhpastview/controller/base',
     'PhabricatorXHPASTViewDAO' => 'applications/xhpastview/storage/base',
     'PhabricatorXHPASTViewFrameController' => 'applications/xhpastview/controller/viewframe',
     'PhabricatorXHPASTViewFramesetController' => 'applications/xhpastview/controller/viewframeset',
     'PhabricatorXHPASTViewInputController' => 'applications/xhpastview/controller/viewinput',
     'PhabricatorXHPASTViewPanelController' => 'applications/xhpastview/controller/viewpanel',
     'PhabricatorXHPASTViewParseTree' => 'applications/xhpastview/storage/parsetree',
     'PhabricatorXHPASTViewRunController' => 'applications/xhpastview/controller/run',
     'PhabricatorXHPASTViewStreamController' => 'applications/xhpastview/controller/viewstream',
     'PhabricatorXHPASTViewTreeController' => 'applications/xhpastview/controller/viewtree',
     'PhabricatorXHProfController' => 'applications/xhprof/controller/base',
     'PhabricatorXHProfProfileController' => 'applications/xhprof/controller/profile',
     'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/symbol',
     'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/toplevel',
     'PhrictionActionConstants' => 'applications/phriction/constants/action',
     'PhrictionConstants' => 'applications/phriction/constants/base',
     'PhrictionContent' => 'applications/phriction/storage/content',
     'PhrictionController' => 'applications/phriction/controller/base',
     'PhrictionDAO' => 'applications/phriction/storage/base',
     'PhrictionDiffController' => 'applications/phriction/controller/diff',
     'PhrictionDocument' => 'applications/phriction/storage/document',
     'PhrictionDocumentController' => 'applications/phriction/controller/document',
     'PhrictionDocumentPreviewController' => 'applications/phriction/controller/documentpreview',
     'PhrictionDocumentTestCase' => 'applications/phriction/storage/document/__tests__',
     'PhrictionEditController' => 'applications/phriction/controller/edit',
     'PhrictionHistoryController' => 'applications/phriction/controller/history',
     'PhrictionListController' => 'applications/phriction/controller/list',
   ),
   'function' =>
   array(
     '_qsprintf_check_scalar_type' => 'storage/qsprintf',
     '_qsprintf_check_type' => 'storage/qsprintf',
     'celerity_generate_unique_node_id' => 'infrastructure/celerity/api',
     'celerity_register_resource_map' => 'infrastructure/celerity/map',
     'javelin_render_tag' => 'infrastructure/javelin/markup',
     'phabricator_date' => 'view/utils',
     'phabricator_datetime' => 'view/utils',
     'phabricator_format_relative_time' => 'view/utils',
     'phabricator_format_timestamp' => 'view/utils',
     'phabricator_format_units_generic' => 'view/utils',
     'phabricator_render_form' => 'infrastructure/javelin/markup',
     'phabricator_time' => 'view/utils',
     'qsprintf' => 'storage/qsprintf',
     'queryfx' => 'storage/queryfx',
     'queryfx_all' => 'storage/queryfx',
     'queryfx_one' => 'storage/queryfx',
     'require_celerity_resource' => 'infrastructure/celerity/api',
     'vqsprintf' => 'storage/qsprintf',
     'vqueryfx' => 'storage/queryfx',
     'vqueryfx_all' => 'storage/queryfx',
     'xsprintf_query' => 'storage/qsprintf',
   ),
   'requires_class' =>
   array(
     'Aphront304Response' => 'AphrontResponse',
     'Aphront400Response' => 'AphrontResponse',
     'Aphront404Response' => 'AphrontResponse',
     'AphrontAjaxResponse' => 'AphrontResponse',
     'AphrontAttachedFileView' => 'AphrontView',
     'AphrontCSRFException' => 'AphrontException',
     'AphrontCalendarMonthView' => 'AphrontView',
     'AphrontContextBarView' => 'AphrontView',
     'AphrontCrumbsView' => 'AphrontView',
     'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration',
     'AphrontDefaultApplicationController' => 'AphrontController',
     'AphrontDialogResponse' => 'AphrontResponse',
     'AphrontDialogView' => 'AphrontView',
     'AphrontErrorView' => 'AphrontView',
     'AphrontFilePreviewView' => 'AphrontView',
     'AphrontFileResponse' => 'AphrontResponse',
     'AphrontFormCheckboxControl' => 'AphrontFormControl',
     'AphrontFormControl' => 'AphrontView',
     'AphrontFormDividerControl' => 'AphrontFormControl',
     'AphrontFormDragAndDropUploadControl' => 'AphrontFormControl',
     'AphrontFormFileControl' => 'AphrontFormControl',
     'AphrontFormLayoutView' => 'AphrontView',
     'AphrontFormMarkupControl' => 'AphrontFormControl',
     'AphrontFormPasswordControl' => 'AphrontFormControl',
     'AphrontFormRecaptchaControl' => 'AphrontFormControl',
     'AphrontFormSelectControl' => 'AphrontFormControl',
     'AphrontFormStaticControl' => 'AphrontFormControl',
     'AphrontFormSubmitControl' => 'AphrontFormControl',
     'AphrontFormTextAreaControl' => 'AphrontFormControl',
     'AphrontFormTextControl' => 'AphrontFormControl',
     'AphrontFormToggleButtonsControl' => 'AphrontFormControl',
     'AphrontFormTokenizerControl' => 'AphrontFormControl',
     'AphrontFormView' => 'AphrontView',
     'AphrontHeadsupActionListView' => 'AphrontView',
     'AphrontHeadsupActionView' => 'AphrontView',
     'AphrontIsolatedDatabaseConnection' => 'AphrontDatabaseConnection',
     'AphrontIsolatedDatabaseConnectionTestCase' => 'PhabricatorTestCase',
     'AphrontKeyboardShortcutsAvailableView' => 'AphrontView',
     'AphrontListFilterView' => 'AphrontView',
     'AphrontMySQLDatabaseConnection' => 'AphrontDatabaseConnection',
     'AphrontNullView' => 'AphrontView',
     'AphrontPageView' => 'AphrontView',
     'AphrontPagerView' => 'AphrontView',
     'AphrontPanelView' => 'AphrontView',
     'AphrontQueryAccessDeniedException' => 'AphrontQueryRecoverableException',
     'AphrontQueryConnectionException' => 'AphrontQueryException',
     'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException',
     'AphrontQueryCountException' => 'AphrontQueryException',
     'AphrontQueryDuplicateKeyException' => 'AphrontQueryException',
     'AphrontQueryObjectMissingException' => 'AphrontQueryException',
     'AphrontQueryParameterException' => 'AphrontQueryException',
     'AphrontQueryRecoverableException' => 'AphrontQueryException',
     'AphrontRedirectException' => 'AphrontException',
     'AphrontRedirectResponse' => 'AphrontResponse',
     'AphrontReloadResponse' => 'AphrontRedirectResponse',
     'AphrontRequestFailureView' => 'AphrontView',
     'AphrontSideNavView' => 'AphrontView',
     'AphrontTableView' => 'AphrontView',
     'AphrontTokenizerTemplateView' => 'AphrontView',
     'AphrontTypeaheadTemplateView' => 'AphrontView',
     'AphrontWebpageResponse' => 'AphrontResponse',
     'CelerityResourceController' => 'AphrontController',
     'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod',
     'ConduitAPI_conduit_getcertificate_Method' => 'ConduitAPIMethod',
     'ConduitAPI_conduit_ping_Method' => 'ConduitAPIMethod',
     'ConduitAPI_daemon_launched_Method' => 'ConduitAPIMethod',
     'ConduitAPI_daemon_log_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_createrevision_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_find_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_getalldiffs_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_getcommitmessage_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_getcommitpaths_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_getdiff_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_getrevision_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_getrevisionfeedback_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_markcommitted_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_parsecommitmessage_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_updaterevision_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_updatetaskrevisionassoc_Method' => 'ConduitAPIMethod',
     'ConduitAPI_differential_updateunitresults_Method' => 'ConduitAPIMethod',
     'ConduitAPI_diffusion_getcommits_Method' => 'ConduitAPIMethod',
     'ConduitAPI_diffusion_getrecentcommitsbypath_Method' => 'ConduitAPIMethod',
     'ConduitAPI_feed_publish_Method' => 'ConduitAPIMethod',
     'ConduitAPI_file_download_Method' => 'ConduitAPIMethod',
     'ConduitAPI_file_info_Method' => 'ConduitAPIMethod',
     'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod',
     'ConduitAPI_maniphest_Method' => 'ConduitAPIMethod',
     'ConduitAPI_maniphest_createtask_Method' => 'ConduitAPI_maniphest_Method',
     'ConduitAPI_maniphest_info_Method' => 'ConduitAPI_maniphest_Method',
     'ConduitAPI_paste_Method' => 'ConduitAPIMethod',
     'ConduitAPI_paste_create_Method' => 'ConduitAPI_paste_Method',
     'ConduitAPI_paste_info_Method' => 'ConduitAPI_paste_Method',
     'ConduitAPI_path_getowners_Method' => 'ConduitAPIMethod',
     'ConduitAPI_slowvote_info_Method' => 'ConduitAPIMethod',
     'ConduitAPI_user_find_Method' => 'ConduitAPIMethod',
     'ConduitAPI_user_whoami_Method' => 'ConduitAPIMethod',
     'DarkConsoleConfigPlugin' => 'DarkConsolePlugin',
     'DarkConsoleController' => 'PhabricatorController',
     'DarkConsoleErrorLogPlugin' => 'DarkConsolePlugin',
     'DarkConsoleRequestPlugin' => 'DarkConsolePlugin',
     'DarkConsoleServicesPlugin' => 'DarkConsolePlugin',
     'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin',
     'DifferentialAddCommentView' => 'AphrontView',
     'DifferentialApplyPatchFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialArcanistProjectFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialAuthorFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialAuxiliaryField' => 'DifferentialDAO',
     'DifferentialBlameRevisionFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialCCWelcomeMail' => 'DifferentialReviewRequestMail',
     'DifferentialCCsFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialChangeset' => 'DifferentialDAO',
     'DifferentialChangesetDetailView' => 'AphrontView',
     'DifferentialChangesetListView' => 'AphrontView',
     'DifferentialChangesetViewController' => 'DifferentialController',
     'DifferentialComment' => 'DifferentialDAO',
     'DifferentialCommentMail' => 'DifferentialMail',
     'DifferentialCommentPreviewController' => 'DifferentialController',
     'DifferentialCommentSaveController' => 'DifferentialController',
     'DifferentialCommitsFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialController' => 'PhabricatorController',
     'DifferentialDAO' => 'PhabricatorLiskDAO',
     'DifferentialDefaultFieldSelector' => 'DifferentialFieldSelector',
     'DifferentialDependenciesFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialDiff' => 'DifferentialDAO',
     'DifferentialDiffContentMail' => 'DifferentialMail',
     'DifferentialDiffCreateController' => 'DifferentialController',
     'DifferentialDiffProperty' => 'DifferentialDAO',
     'DifferentialDiffTableOfContentsView' => 'AphrontView',
     'DifferentialDiffViewController' => 'DifferentialController',
     'DifferentialExceptionMail' => 'DifferentialMail',
     'DifferentialExportPatchFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialGitSVNIDFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialHostFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialHunk' => 'DifferentialDAO',
     'DifferentialInlineComment' => 'DifferentialDAO',
     'DifferentialInlineCommentEditController' => 'DifferentialController',
     'DifferentialInlineCommentPreviewController' => 'DifferentialController',
     'DifferentialInlineCommentView' => 'AphrontView',
     'DifferentialLinesFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialLintFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialManiphestTasksFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail',
     'DifferentialPathFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialPrimaryPaneView' => 'AphrontView',
     'DifferentialReplyHandler' => 'PhabricatorMailReplyHandler',
     'DifferentialRevertPlanFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialReviewRequestMail' => 'DifferentialMail',
     'DifferentialReviewedByFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialReviewersFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialRevision' => 'DifferentialDAO',
     'DifferentialRevisionCommentListView' => 'AphrontView',
     'DifferentialRevisionCommentView' => 'AphrontView',
     'DifferentialRevisionDetailView' => 'AphrontView',
     'DifferentialRevisionEditController' => 'DifferentialController',
     'DifferentialRevisionIDFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialRevisionListController' => 'DifferentialController',
     'DifferentialRevisionStatusFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialRevisionUpdateHistoryView' => 'AphrontView',
     'DifferentialRevisionViewController' => 'DifferentialController',
     'DifferentialSubscribeController' => 'DifferentialController',
     'DifferentialSummaryFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialTestPlanFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialTitleFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialUnitFieldSpecification' => 'DifferentialFieldSpecification',
     'DifferentialViewTime' => 'DifferentialDAO',
     'DiffusionBranchTableView' => 'DiffusionView',
     'DiffusionBrowseController' => 'DiffusionController',
     'DiffusionBrowseFileController' => 'DiffusionController',
     'DiffusionBrowseTableView' => 'DiffusionView',
     'DiffusionChangeController' => 'DiffusionController',
     'DiffusionCommitChangeTableView' => 'DiffusionView',
     'DiffusionCommitController' => 'DiffusionController',
     'DiffusionCommitListController' => 'DiffusionController',
     'DiffusionController' => 'PhabricatorController',
     'DiffusionDiffController' => 'DiffusionController',
     'DiffusionGitBranchQuery' => 'DiffusionBranchQuery',
     'DiffusionGitBrowseQuery' => 'DiffusionBrowseQuery',
     'DiffusionGitDiffQuery' => 'DiffusionDiffQuery',
     'DiffusionGitFileContentQuery' => 'DiffusionFileContentQuery',
     'DiffusionGitHistoryQuery' => 'DiffusionHistoryQuery',
     'DiffusionGitLastModifiedQuery' => 'DiffusionLastModifiedQuery',
     'DiffusionGitRequest' => 'DiffusionRequest',
     'DiffusionHistoryController' => 'DiffusionController',
     'DiffusionHistoryTableView' => 'DiffusionView',
     'DiffusionHomeController' => 'DiffusionController',
     'DiffusionLastModifiedController' => 'DiffusionController',
     'DiffusionPathCompleteController' => 'DiffusionController',
     'DiffusionPathValidateController' => 'DiffusionController',
     'DiffusionRepositoryController' => 'DiffusionController',
     'DiffusionSvnBrowseQuery' => 'DiffusionBrowseQuery',
     'DiffusionSvnDiffQuery' => 'DiffusionDiffQuery',
     'DiffusionSvnFileContentQuery' => 'DiffusionFileContentQuery',
     'DiffusionSvnHistoryQuery' => 'DiffusionHistoryQuery',
     'DiffusionSvnLastModifiedQuery' => 'DiffusionLastModifiedQuery',
     'DiffusionSvnRequest' => 'DiffusionRequest',
     'DiffusionView' => 'AphrontView',
     'HeraldAction' => 'HeraldDAO',
     'HeraldApplyTranscript' => 'HeraldDAO',
     'HeraldCommitAdapter' => 'HeraldObjectAdapter',
     'HeraldCondition' => 'HeraldDAO',
     'HeraldController' => 'PhabricatorController',
     'HeraldDAO' => 'PhabricatorLiskDAO',
     'HeraldDeleteController' => 'HeraldController',
     'HeraldDifferentialRevisionAdapter' => 'HeraldObjectAdapter',
     'HeraldDryRunAdapter' => 'HeraldObjectAdapter',
     'HeraldHomeController' => 'HeraldController',
     'HeraldNewController' => 'HeraldController',
     'HeraldRule' => 'HeraldDAO',
     'HeraldRuleController' => 'HeraldController',
     'HeraldTestConsoleController' => 'HeraldController',
     'HeraldTranscript' => 'HeraldDAO',
     'HeraldTranscriptController' => 'HeraldController',
     'HeraldTranscriptListController' => 'HeraldController',
     'LiskIsolationTestCase' => 'PhabricatorTestCase',
     'LiskIsolationTestDAO' => 'LiskDAO',
     'ManiphestAuxiliaryFieldDefaultSpecification' => 'ManiphestAuxiliaryFieldSpecification',
     'ManiphestController' => 'PhabricatorController',
     'ManiphestDAO' => 'PhabricatorLiskDAO',
     'ManiphestDefaultTaskExtensions' => 'ManiphestTaskExtensions',
     'ManiphestReplyHandler' => 'PhabricatorMailReplyHandler',
     'ManiphestTask' => 'ManiphestDAO',
     'ManiphestTaskAuxiliaryStorage' => 'ManiphestDAO',
     'ManiphestTaskDescriptionChangeController' => 'ManiphestController',
     'ManiphestTaskDetailController' => 'ManiphestController',
     'ManiphestTaskEditController' => 'ManiphestController',
     'ManiphestTaskListController' => 'ManiphestController',
     'ManiphestTaskListView' => 'ManiphestView',
     'ManiphestTaskOwner' => 'ManiphestConstants',
     'ManiphestTaskPriority' => 'ManiphestConstants',
     'ManiphestTaskProject' => 'ManiphestDAO',
     'ManiphestTaskStatus' => 'ManiphestConstants',
     'ManiphestTaskSubscriber' => 'ManiphestDAO',
     'ManiphestTaskSummaryView' => 'ManiphestView',
     'ManiphestTransaction' => 'ManiphestDAO',
     'ManiphestTransactionDetailView' => 'ManiphestView',
     'ManiphestTransactionListView' => 'ManiphestView',
     'ManiphestTransactionPreviewController' => 'ManiphestController',
     'ManiphestTransactionSaveController' => 'ManiphestController',
     'ManiphestTransactionType' => 'ManiphestConstants',
     'ManiphestView' => 'AphrontView',
     'Phabricator404Controller' => 'PhabricatorController',
     'PhabricatorAuthController' => 'PhabricatorController',
     'PhabricatorCalendarBrowseController' => 'PhabricatorCalendarController',
     'PhabricatorCalendarController' => 'PhabricatorController',
     'PhabricatorConduitAPIController' => 'PhabricatorConduitController',
     'PhabricatorConduitCertificateToken' => 'PhabricatorConduitDAO',
     'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO',
     'PhabricatorConduitConsoleController' => 'PhabricatorConduitController',
     'PhabricatorConduitController' => 'PhabricatorController',
     'PhabricatorConduitDAO' => 'PhabricatorLiskDAO',
     'PhabricatorConduitLogController' => 'PhabricatorConduitController',
     'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO',
     'PhabricatorConduitTokenController' => 'PhabricatorConduitController',
+    'PhabricatorContentSourceView' => 'AphrontView',
     'PhabricatorController' => 'AphrontController',
     'PhabricatorCountdownController' => 'PhabricatorController',
     'PhabricatorCountdownDAO' => 'PhabricatorLiskDAO',
     'PhabricatorCountdownDeleteController' => 'PhabricatorCountdownController',
     'PhabricatorCountdownEditController' => 'PhabricatorCountdownController',
     'PhabricatorCountdownListController' => 'PhabricatorCountdownController',
     'PhabricatorCountdownViewController' => 'PhabricatorCountdownController',
     'PhabricatorDaemon' => 'PhutilDaemon',
     'PhabricatorDaemonCombinedLogController' => 'PhabricatorDaemonController',
     'PhabricatorDaemonConsoleController' => 'PhabricatorDaemonController',
     'PhabricatorDaemonController' => 'PhabricatorController',
     'PhabricatorDaemonDAO' => 'PhabricatorLiskDAO',
     'PhabricatorDaemonLog' => 'PhabricatorDaemonDAO',
     'PhabricatorDaemonLogEvent' => 'PhabricatorDaemonDAO',
     'PhabricatorDaemonLogEventsView' => 'AphrontView',
     'PhabricatorDaemonLogListController' => 'PhabricatorDaemonController',
     'PhabricatorDaemonLogListView' => 'AphrontView',
     'PhabricatorDaemonLogViewController' => 'PhabricatorDaemonController',
     'PhabricatorDaemonTimelineConsoleController' => 'PhabricatorDaemonController',
     'PhabricatorDaemonTimelineEventController' => 'PhabricatorDaemonController',
     'PhabricatorDefaultFileStorageEngineSelector' => 'PhabricatorFileStorageEngineSelector',
     'PhabricatorDefaultSearchEngineSelector' => 'PhabricatorSearchEngineSelector',
     'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO',
     'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController',
     'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController',
     'PhabricatorDirectoryCategoryListController' => 'PhabricatorDirectoryController',
     'PhabricatorDirectoryController' => 'PhabricatorController',
     'PhabricatorDirectoryDAO' => 'PhabricatorLiskDAO',
     'PhabricatorDirectoryItem' => 'PhabricatorDirectoryDAO',
     'PhabricatorDirectoryItemDeleteController' => 'PhabricatorDirectoryController',
     'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController',
     'PhabricatorDirectoryItemListController' => 'PhabricatorDirectoryController',
     'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController',
     'PhabricatorDisabledUserController' => 'PhabricatorAuthController',
     'PhabricatorDraft' => 'PhabricatorDraftDAO',
     'PhabricatorDraftDAO' => 'PhabricatorLiskDAO',
     'PhabricatorEmailLoginController' => 'PhabricatorAuthController',
     'PhabricatorEmailTokenController' => 'PhabricatorAuthController',
     'PhabricatorFeedController' => 'PhabricatorController',
     'PhabricatorFeedDAO' => 'PhabricatorLiskDAO',
     'PhabricatorFeedPublicStreamController' => 'PhabricatorFeedController',
     'PhabricatorFeedStoryData' => 'PhabricatorFeedDAO',
     'PhabricatorFeedStoryDifferential' => 'PhabricatorFeedStory',
     'PhabricatorFeedStoryPhriction' => 'PhabricatorFeedStory',
     'PhabricatorFeedStoryReference' => 'PhabricatorFeedDAO',
     'PhabricatorFeedStoryStatus' => 'PhabricatorFeedStory',
     'PhabricatorFeedStoryTypeConstants' => 'PhabricatorFeedConstants',
     'PhabricatorFeedStoryUnknown' => 'PhabricatorFeedStory',
     'PhabricatorFeedStoryView' => 'PhabricatorFeedView',
     'PhabricatorFeedStreamController' => 'PhabricatorFeedController',
     'PhabricatorFeedView' => 'AphrontView',
     'PhabricatorFile' => 'PhabricatorFileDAO',
     'PhabricatorFileAltViewController' => 'PhabricatorFileController',
     'PhabricatorFileController' => 'PhabricatorController',
     'PhabricatorFileDAO' => 'PhabricatorLiskDAO',
     'PhabricatorFileDropUploadController' => 'PhabricatorFileController',
     'PhabricatorFileImageMacro' => 'PhabricatorFileDAO',
     'PhabricatorFileListController' => 'PhabricatorFileController',
     'PhabricatorFileMacroDeleteController' => 'PhabricatorFileController',
     'PhabricatorFileMacroEditController' => 'PhabricatorFileController',
     'PhabricatorFileMacroListController' => 'PhabricatorFileController',
     'PhabricatorFileProxyController' => 'PhabricatorFileController',
     'PhabricatorFileProxyImage' => 'PhabricatorFileDAO',
     'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO',
     'PhabricatorFileTransformController' => 'PhabricatorFileController',
     'PhabricatorFileUploadController' => 'PhabricatorFileController',
     'PhabricatorFileViewController' => 'PhabricatorFileController',
     'PhabricatorGarbageCollectorDaemon' => 'PhabricatorDaemon',
     'PhabricatorGoodForNothingWorker' => 'PhabricatorWorker',
     'PhabricatorHelpController' => 'PhabricatorController',
     'PhabricatorHelpKeyboardShortcutController' => 'PhabricatorHelpController',
     'PhabricatorIRCBot' => 'PhabricatorDaemon',
     'PhabricatorIRCObjectNameHandler' => 'PhabricatorIRCHandler',
     'PhabricatorIRCProtocolHandler' => 'PhabricatorIRCHandler',
     'PhabricatorJavelinLinter' => 'ArcanistLinter',
     'PhabricatorLintEngine' => 'PhutilLintEngine',
     'PhabricatorLiskDAO' => 'LiskDAO',
     'PhabricatorLocalDiskFileStorageEngine' => 'PhabricatorFileStorageEngine',
     'PhabricatorLoginController' => 'PhabricatorAuthController',
     'PhabricatorLogoutController' => 'PhabricatorAuthController',
     'PhabricatorMailImplementationAmazonSESAdapter' => 'PhabricatorMailImplementationPHPMailerLiteAdapter',
     'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter',
     'PhabricatorMailImplementationSendGridAdapter' => 'PhabricatorMailImplementationAdapter',
     'PhabricatorMailImplementationTestAdapter' => 'PhabricatorMailImplementationAdapter',
     'PhabricatorMetaMTAController' => 'PhabricatorController',
     'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO',
     'PhabricatorMetaMTADaemon' => 'PhabricatorDaemon',
     'PhabricatorMetaMTAEmailBodyParserTestCase' => 'PhabricatorTestCase',
     'PhabricatorMetaMTAListController' => 'PhabricatorMetaMTAController',
     'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO',
     'PhabricatorMetaMTAMailTestCase' => 'PhabricatorTestCase',
     'PhabricatorMetaMTAMailingList' => 'PhabricatorMetaMTADAO',
     'PhabricatorMetaMTAMailingListEditController' => 'PhabricatorMetaMTAController',
     'PhabricatorMetaMTAMailingListsController' => 'PhabricatorMetaMTAController',
     'PhabricatorMetaMTAReceiveController' => 'PhabricatorMetaMTAController',
     'PhabricatorMetaMTAReceivedListController' => 'PhabricatorMetaMTAController',
     'PhabricatorMetaMTAReceivedMail' => 'PhabricatorMetaMTADAO',
     'PhabricatorMetaMTASendController' => 'PhabricatorMetaMTAController',
     'PhabricatorMetaMTASendGridReceiveController' => 'PhabricatorMetaMTAController',
     'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController',
     'PhabricatorMySQLFileStorageEngine' => 'PhabricatorFileStorageEngine',
     'PhabricatorOAuthDefaultRegistrationController' => 'PhabricatorOAuthRegistrationController',
     'PhabricatorOAuthDiagnosticsController' => 'PhabricatorAuthController',
     'PhabricatorOAuthFailureView' => 'AphrontView',
     'PhabricatorOAuthLoginController' => 'PhabricatorAuthController',
     'PhabricatorOAuthProviderFacebook' => 'PhabricatorOAuthProvider',
     'PhabricatorOAuthProviderGithub' => 'PhabricatorOAuthProvider',
     'PhabricatorOAuthRegistrationController' => 'PhabricatorAuthController',
     'PhabricatorOAuthUnlinkController' => 'PhabricatorAuthController',
     'PhabricatorObjectGraph' => 'AbstractDirectedGraph',
     'PhabricatorObjectHandleStatus' => 'PhabricatorObjectHandleConstants',
     'PhabricatorOwnersController' => 'PhabricatorController',
     'PhabricatorOwnersDAO' => 'PhabricatorLiskDAO',
     'PhabricatorOwnersDeleteController' => 'PhabricatorOwnersController',
     'PhabricatorOwnersDetailController' => 'PhabricatorOwnersController',
     'PhabricatorOwnersEditController' => 'PhabricatorOwnersController',
     'PhabricatorOwnersListController' => 'PhabricatorOwnersController',
     'PhabricatorOwnersOwner' => 'PhabricatorOwnersDAO',
     'PhabricatorOwnersPackage' => 'PhabricatorOwnersDAO',
     'PhabricatorOwnersPath' => 'PhabricatorOwnersDAO',
     'PhabricatorPHID' => 'PhabricatorPHIDDAO',
     'PhabricatorPHIDController' => 'PhabricatorController',
     'PhabricatorPHIDDAO' => 'PhabricatorLiskDAO',
     'PhabricatorPHIDListController' => 'PhabricatorPHIDController',
     'PhabricatorPHIDLookupController' => 'PhabricatorPHIDController',
     'PhabricatorPaste' => 'PhabricatorPasteDAO',
     'PhabricatorPasteController' => 'PhabricatorController',
     'PhabricatorPasteCreateController' => 'PhabricatorPasteController',
     'PhabricatorPasteDAO' => 'PhabricatorLiskDAO',
     'PhabricatorPasteListController' => 'PhabricatorPasteController',
     'PhabricatorPasteViewController' => 'PhabricatorPasteController',
     'PhabricatorPeopleController' => 'PhabricatorController',
     'PhabricatorPeopleEditController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleListController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleLogsController' => 'PhabricatorPeopleController',
     'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController',
     'PhabricatorProfileView' => 'AphrontView',
     'PhabricatorProject' => 'PhabricatorProjectDAO',
     'PhabricatorProjectAffiliation' => 'PhabricatorProjectDAO',
     'PhabricatorProjectAffiliationEditController' => 'PhabricatorProjectController',
     'PhabricatorProjectController' => 'PhabricatorController',
     'PhabricatorProjectCreateController' => 'PhabricatorProjectController',
     'PhabricatorProjectDAO' => 'PhabricatorLiskDAO',
     'PhabricatorProjectListController' => 'PhabricatorProjectController',
     'PhabricatorProjectProfile' => 'PhabricatorProjectDAO',
     'PhabricatorProjectProfileController' => 'PhabricatorProjectController',
     'PhabricatorProjectProfileEditController' => 'PhabricatorProjectController',
     'PhabricatorProjectSubproject' => 'PhabricatorProjectDAO',
     'PhabricatorRedirectController' => 'PhabricatorController',
     'PhabricatorRefreshCSRFController' => 'PhabricatorAuthController',
     'PhabricatorRemarkupRuleDifferential' => 'PhabricatorRemarkupRuleObjectName',
     'PhabricatorRemarkupRuleDifferentialHandle' => 'PhabricatorRemarkupRuleObjectHandle',
     'PhabricatorRemarkupRuleDiffusion' => 'PhutilRemarkupRule',
     'PhabricatorRemarkupRuleEmbedFile' => 'PhutilRemarkupRule',
     'PhabricatorRemarkupRuleImageMacro' => 'PhutilRemarkupRule',
     'PhabricatorRemarkupRuleManiphest' => 'PhabricatorRemarkupRuleObjectName',
     'PhabricatorRemarkupRuleManiphestHandle' => 'PhabricatorRemarkupRuleObjectHandle',
     'PhabricatorRemarkupRuleMention' => 'PhutilRemarkupRule',
     'PhabricatorRemarkupRuleObjectHandle' => 'PhutilRemarkupRule',
     'PhabricatorRemarkupRuleObjectName' => 'PhutilRemarkupRule',
     'PhabricatorRemarkupRulePaste' => 'PhabricatorRemarkupRuleObjectName',
     'PhabricatorRemarkupRulePhriction' => 'PhutilRemarkupRule',
     'PhabricatorRemarkupRuleProxyImage' => 'PhutilRemarkupRule',
     'PhabricatorRemarkupRuleYoutube' => 'PhutilRemarkupRule',
     'PhabricatorRepository' => 'PhabricatorRepositoryDAO',
     'PhabricatorRepositoryArcanistProject' => 'PhabricatorRepositoryDAO',
     'PhabricatorRepositoryArcanistProjectEditController' => 'PhabricatorRepositoryController',
     'PhabricatorRepositoryCommit' => 'PhabricatorRepositoryDAO',
     'PhabricatorRepositoryCommitChangeParserWorker' => 'PhabricatorRepositoryCommitParserWorker',
     'PhabricatorRepositoryCommitData' => 'PhabricatorRepositoryDAO',
     'PhabricatorRepositoryCommitDiscoveryDaemon' => 'PhabricatorRepositoryDaemon',
     'PhabricatorRepositoryCommitHeraldWorker' => 'PhabricatorRepositoryCommitParserWorker',
     'PhabricatorRepositoryCommitMessageParserWorker' => 'PhabricatorRepositoryCommitParserWorker',
     'PhabricatorRepositoryCommitParserWorker' => 'PhabricatorWorker',
     'PhabricatorRepositoryCommitTaskDaemon' => 'PhabricatorRepositoryDaemon',
     'PhabricatorRepositoryController' => 'PhabricatorController',
     'PhabricatorRepositoryCreateController' => 'PhabricatorRepositoryController',
     'PhabricatorRepositoryDAO' => 'PhabricatorLiskDAO',
     'PhabricatorRepositoryDaemon' => 'PhabricatorDaemon',
     'PhabricatorRepositoryDefaultCommitMessageDetailParser' => 'PhabricatorRepositoryCommitMessageDetailParser',
     'PhabricatorRepositoryDeleteController' => 'PhabricatorRepositoryController',
     'PhabricatorRepositoryEditController' => 'PhabricatorRepositoryController',
     'PhabricatorRepositoryGitCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
     'PhabricatorRepositoryGitCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon',
     'PhabricatorRepositoryGitCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
     'PhabricatorRepositoryGitFetchDaemon' => 'PhabricatorRepositoryDaemon',
     'PhabricatorRepositoryGitHubNotification' => 'PhabricatorRepositoryDAO',
     'PhabricatorRepositoryGitHubPostReceiveController' => 'PhabricatorRepositoryController',
     'PhabricatorRepositoryListController' => 'PhabricatorRepositoryController',
     'PhabricatorRepositoryShortcut' => 'PhabricatorRepositoryDAO',
     'PhabricatorRepositorySvnCommitChangeParserWorker' => 'PhabricatorRepositoryCommitChangeParserWorker',
     'PhabricatorRepositorySvnCommitDiscoveryDaemon' => 'PhabricatorRepositoryCommitDiscoveryDaemon',
     'PhabricatorRepositorySvnCommitMessageParserWorker' => 'PhabricatorRepositoryCommitMessageParserWorker',
     'PhabricatorS3FileStorageEngine' => 'PhabricatorFileStorageEngine',
     'PhabricatorSearchAttachController' => 'PhabricatorSearchController',
     'PhabricatorSearchBaseController' => 'PhabricatorController',
     'PhabricatorSearchCommitIndexer' => 'PhabricatorSearchDocumentIndexer',
     'PhabricatorSearchController' => 'PhabricatorSearchBaseController',
     'PhabricatorSearchDAO' => 'PhabricatorLiskDAO',
     'PhabricatorSearchDifferentialIndexer' => 'PhabricatorSearchDocumentIndexer',
     'PhabricatorSearchDocument' => 'PhabricatorSearchDAO',
     'PhabricatorSearchDocumentField' => 'PhabricatorSearchDAO',
     'PhabricatorSearchDocumentRelationship' => 'PhabricatorSearchDAO',
     'PhabricatorSearchEngineMySQL' => 'PhabricatorSearchEngine',
     'PhabricatorSearchIndexController' => 'PhabricatorSearchBaseController',
     'PhabricatorSearchManiphestIndexer' => 'PhabricatorSearchDocumentIndexer',
     'PhabricatorSearchPhrictionIndexer' => 'PhabricatorSearchDocumentIndexer',
     'PhabricatorSearchQuery' => 'PhabricatorSearchDAO',
     'PhabricatorSearchResultView' => 'AphrontView',
     'PhabricatorSearchSelectController' => 'PhabricatorSearchController',
     'PhabricatorSearchUserIndexer' => 'PhabricatorSearchDocumentIndexer',
     'PhabricatorSlowvoteChoice' => 'PhabricatorSlowvoteDAO',
     'PhabricatorSlowvoteComment' => 'PhabricatorSlowvoteDAO',
     'PhabricatorSlowvoteController' => 'PhabricatorController',
     'PhabricatorSlowvoteCreateController' => 'PhabricatorSlowvoteController',
     'PhabricatorSlowvoteDAO' => 'PhabricatorLiskDAO',
     'PhabricatorSlowvoteListController' => 'PhabricatorSlowvoteController',
     'PhabricatorSlowvoteOption' => 'PhabricatorSlowvoteDAO',
     'PhabricatorSlowvotePoll' => 'PhabricatorSlowvoteDAO',
     'PhabricatorSlowvotePollController' => 'PhabricatorSlowvoteController',
     'PhabricatorStandardPageView' => 'AphrontPageView',
     'PhabricatorStatusController' => 'PhabricatorController',
     'PhabricatorTaskmasterDaemon' => 'PhabricatorDaemon',
     'PhabricatorTestCase' => 'ArcanistPhutilTestCase',
     'PhabricatorTimelineCursor' => 'PhabricatorTimelineDAO',
     'PhabricatorTimelineDAO' => 'PhabricatorLiskDAO',
     'PhabricatorTimelineEvent' => 'PhabricatorTimelineDAO',
     'PhabricatorTimelineEventData' => 'PhabricatorTimelineDAO',
     'PhabricatorTimer' => 'PhabricatorCountdownDAO',
     'PhabricatorTransformedFile' => 'PhabricatorFileDAO',
     'PhabricatorTrivialTestCase' => 'PhabricatorTestCase',
     'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController',
     'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController',
     'PhabricatorUIExampleController' => 'PhabricatorController',
     'PhabricatorUIExampleRenderController' => 'PhabricatorUIExampleController',
     'PhabricatorUIListFilterExample' => 'PhabricatorUIExample',
     'PhabricatorUIPagerExample' => 'PhabricatorUIExample',
     'PhabricatorUser' => 'PhabricatorUserDAO',
     'PhabricatorUserAccountSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
     'PhabricatorUserConduitSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
     'PhabricatorUserDAO' => 'PhabricatorLiskDAO',
     'PhabricatorUserEmailSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
     'PhabricatorUserLog' => 'PhabricatorUserDAO',
     'PhabricatorUserOAuthInfo' => 'PhabricatorUserDAO',
     'PhabricatorUserOAuthSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
     'PhabricatorUserPreferenceSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
     'PhabricatorUserPreferences' => 'PhabricatorUserDAO',
     'PhabricatorUserProfile' => 'PhabricatorUserDAO',
     'PhabricatorUserProfileSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
     'PhabricatorUserSSHKey' => 'PhabricatorUserDAO',
     'PhabricatorUserSSHKeysSettingsPanelController' => 'PhabricatorUserSettingsPanelController',
     'PhabricatorUserSettingsController' => 'PhabricatorPeopleController',
     'PhabricatorUserSettingsPanelController' => 'PhabricatorPeopleController',
     'PhabricatorWorkerDAO' => 'PhabricatorLiskDAO',
     'PhabricatorWorkerTask' => 'PhabricatorWorkerDAO',
     'PhabricatorWorkerTaskData' => 'PhabricatorWorkerDAO',
     'PhabricatorWorkerTaskDetailController' => 'PhabricatorDaemonController',
     'PhabricatorXHPASTViewController' => 'PhabricatorController',
     'PhabricatorXHPASTViewDAO' => 'PhabricatorLiskDAO',
     'PhabricatorXHPASTViewFrameController' => 'PhabricatorXHPASTViewController',
     'PhabricatorXHPASTViewFramesetController' => 'PhabricatorXHPASTViewController',
     'PhabricatorXHPASTViewInputController' => 'PhabricatorXHPASTViewPanelController',
     'PhabricatorXHPASTViewPanelController' => 'PhabricatorXHPASTViewController',
     'PhabricatorXHPASTViewParseTree' => 'PhabricatorXHPASTViewDAO',
     'PhabricatorXHPASTViewRunController' => 'PhabricatorXHPASTViewController',
     'PhabricatorXHPASTViewStreamController' => 'PhabricatorXHPASTViewPanelController',
     'PhabricatorXHPASTViewTreeController' => 'PhabricatorXHPASTViewPanelController',
     'PhabricatorXHProfController' => 'PhabricatorController',
     'PhabricatorXHProfProfileController' => 'PhabricatorXHProfController',
     'PhabricatorXHProfProfileSymbolView' => 'AphrontView',
     'PhabricatorXHProfProfileTopLevelView' => 'AphrontView',
     'PhrictionActionConstants' => 'PhrictionConstants',
     'PhrictionContent' => 'PhrictionDAO',
     'PhrictionController' => 'PhabricatorController',
     'PhrictionDAO' => 'PhabricatorLiskDAO',
     'PhrictionDiffController' => 'PhrictionController',
     'PhrictionDocument' => 'PhrictionDAO',
     'PhrictionDocumentController' => 'PhrictionController',
     'PhrictionDocumentPreviewController' => 'PhrictionController',
     'PhrictionDocumentTestCase' => 'PhabricatorTestCase',
     'PhrictionEditController' => 'PhrictionController',
     'PhrictionHistoryController' => 'PhrictionController',
     'PhrictionListController' => 'PhrictionController',
   ),
   'requires_interface' =>
   array(
   ),
 ));
diff --git a/src/aphront/request/AphrontRequest.php b/src/aphront/request/AphrontRequest.php
index 09250a4875..25eb1fb677 100644
--- a/src/aphront/request/AphrontRequest.php
+++ b/src/aphront/request/AphrontRequest.php
@@ -1,255 +1,259 @@
 <?php
 
 /*
  * Copyright 2011 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.
  */
 
 
 /**
  * @group aphront
  */
 class AphrontRequest {
 
   const TYPE_AJAX = '__ajax__';
   const TYPE_FORM = '__form__';
 
   private $host;
   private $path;
   private $requestData;
   private $user;
   private $env;
   private $applicationConfiguration;
 
   final public function __construct($host, $path) {
     $this->host = $host;
     $this->path = $path;
   }
 
   final public function setApplicationConfiguration(
     $application_configuration) {
     $this->applicationConfiguration = $application_configuration;
     return $this;
   }
 
   final public function getApplicationConfiguration() {
     return $this->applicationConfiguration;
   }
 
   final public function setRequestData(array $request_data) {
     $this->requestData = $request_data;
     return $this;
   }
 
   final public function getPath() {
     return $this->path;
   }
 
   final public function getHost() {
     return $this->host;
   }
 
   final public function getInt($name, $default = null) {
     if (isset($this->requestData[$name])) {
       return (int)$this->requestData[$name];
     } else {
       return $default;
     }
   }
 
   final public function getBool($name, $default = null) {
     if (isset($this->requestData[$name])) {
       if ($this->requestData[$name] === 'true') {
         return true;
       } else if ($this->requestData[$name] === 'false') {
         return false;
       } else {
         return (bool)$this->requestData[$name];
       }
     } else {
       return $default;
     }
   }
 
   final public function getStr($name, $default = null) {
     if (isset($this->requestData[$name])) {
       $str = (string)$this->requestData[$name];
       // Normalize newline craziness.
       $str = str_replace(
         array("\r\n", "\r"),
         array("\n", "\n"),
         $str);
       return $str;
     } else {
       return $default;
     }
   }
 
   final public function getArr($name, $default = array()) {
     if (isset($this->requestData[$name]) &&
         is_array($this->requestData[$name])) {
       return $this->requestData[$name];
     } else {
       return $default;
     }
   }
 
   final public function getExists($name) {
     return array_key_exists($name, $this->requestData);
   }
 
   final public function isHTTPPost() {
     return ($_SERVER['REQUEST_METHOD'] == 'POST');
   }
 
   final public function isAjax() {
     return $this->getExists(self::TYPE_AJAX);
   }
 
   public static function getCSRFTokenName() {
     return '__csrf__';
   }
 
   public static function getCSRFHeaderName() {
     return 'X-Phabricator-Csrf';
   }
 
   final public function validateCSRF() {
     $token_name = self::getCSRFTokenName();
     $token = $this->getStr($token_name);
 
     // No token in the request, check the HTTP header which is added for Ajax
     // requests.
     if (empty($token)) {
 
       // PHP mangles HTTP headers by uppercasing them and replacing hyphens with
       // underscores, then prepending 'HTTP_'.
       $php_index = self::getCSRFHeaderName();
       $php_index = strtoupper($php_index);
       $php_index = str_replace('-', '_', $php_index);
       $php_index = 'HTTP_'.$php_index;
 
       $token = idx($_SERVER, $php_index);
     }
 
     $valid = $this->getUser()->validateCSRFToken($token);
     if (!$valid) {
       // This should only be able to happen if you load a form, pull your
       // internet for 6 hours, and then reconnect and immediately submit,
       // but give the user some indication of what happened since the workflow
       // is incredibly confusing otherwise.
       throw new AphrontCSRFException(
         "The form you just submitted did not include a valid CSRF token. ".
         "This token is a technical security measure which prevents a ".
         "certain type of login hijacking attack. However, the token can ".
         "become invalid if you leave a page open for more than six hours ".
         "without a connection to the internet. To fix this problem: reload ".
         "the page, and then resubmit it.");
     }
 
     return true;
   }
 
   final public function isFormPost() {
     $post = $this->getExists(self::TYPE_FORM) &&
             $this->isHTTPPost();
 
     if (!$post) {
       return false;
     }
 
     return $this->validateCSRF();
   }
 
   final public function getCookie($name, $default = null) {
     return idx($_COOKIE, $name, $default);
   }
 
   final public function clearCookie($name) {
     $this->setCookie($name, '', time() - (60 * 60 * 24 * 30));
   }
 
   final public function setCookie($name, $value, $expire = null) {
 
     // Ensure cookies are only set on the configured domain.
 
     $base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
     $base_uri = new PhutilURI($base_uri);
 
     $base_domain = $base_uri->getDomain();
     $base_protocol = $base_uri->getProtocol();
 
     // The "Host" header may include a port number; if so, ignore it. We can't
     // use PhutilURI since there's no URI scheme.
     list($actual_host) = explode(':', $this->getHost(), 2);
     if ($base_domain != $actual_host) {
       throw new Exception(
         "This install of Phabricator is configured as '{$base_domain}' but ".
         "you are accessing it via '{$actual_host}'. Access Phabricator via ".
         "the primary configured domain.");
     }
 
     if ($expire === null) {
       $expire = time() + (60 * 60 * 24 * 365 * 5);
     }
 
     if ($value == '') {
       // NOTE: If we're clearing the cookie, also clear it on the entire
       // domain and both HTTP/HTTPS versions. This allows us to clear older
       // cookies which we didn't scope as tightly. Eventually we could remove
       // this, although it doesn't really hurt us. Basically, we're just making
       // really sure that cookies get cleared when we try to clear them.
       $secure_options = array(true, false);
       $domain_options = array('', $base_domain);
     } else {
       // Otherwise, when setting cookies, set only one tightly-scoped cookie.
       $is_secure = ($base_protocol == 'https');
       $secure_options = array($is_secure);
       $domain_options = array($base_domain);
     }
 
     foreach ($secure_options as $cookie_secure) {
       foreach ($domain_options as $cookie_domain) {
         setcookie(
           $name,
           $value,
           $expire,
           $path = '/',
           $cookie_domain,
           $cookie_secure,
           $http_only = true);
       }
     }
   }
 
   final public function setUser($user) {
     $this->user = $user;
     return $this;
   }
 
   final public function getUser() {
     return $this->user;
   }
 
   final public function getRequestURI() {
     $get = $_GET;
     unset($get['__path__']);
     return id(new PhutilURI($this->getPath()))->setQueryParams($get);
   }
 
   final public function isDialogFormPost() {
     return $this->isFormPost() && $this->getStr('__dialog__');
   }
 
+  final public function getRemoteAddr() {
+    return $_SERVER['REMOTE_ADDR'];
+  }
+
 }
diff --git a/src/applications/conduit/method/differential/updaterevision/ConduitAPI_differential_updaterevision_Method.php b/src/applications/conduit/method/differential/updaterevision/ConduitAPI_differential_updaterevision_Method.php
index 428d5ee97b..f123ed3146 100644
--- a/src/applications/conduit/method/differential/updaterevision/ConduitAPI_differential_updaterevision_Method.php
+++ b/src/applications/conduit/method/differential/updaterevision/ConduitAPI_differential_updaterevision_Method.php
@@ -1,84 +1,89 @@
 <?php
 
 /*
  * Copyright 2011 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.
  */
 
 /**
  * @group conduit
  */
 class ConduitAPI_differential_updaterevision_Method extends ConduitAPIMethod {
 
   public function getMethodDescription() {
     return "Update a Differential revision.";
   }
 
   public function defineParamTypes() {
     return array(
       'id'        => 'required revisionid',
       'diffid'    => 'required diffid',
       'fields'    => 'required dict',
       'message'   => 'required string',
     );
   }
 
   public function defineReturnType() {
     return 'nonempty dict';
   }
 
   public function defineErrorTypes() {
     return array(
       'ERR_BAD_DIFF' => 'Bad diff ID.',
       'ERR_BAD_REVISION' => 'Bad revision ID.',
       'ERR_WRONG_USER' => 'You are not the author of this revision.',
       'ERR_COMMITTED' => 'This revision has already been committed.',
     );
   }
 
   protected function execute(ConduitAPIRequest $request) {
     $diff = id(new DifferentialDiff())->load($request->getValue('diffid'));
     if (!$diff) {
       throw new ConduitException('ERR_BAD_DIFF');
     }
 
     $revision = id(new DifferentialRevision())->load($request->getValue('id'));
     if (!$revision) {
       throw new ConduitException('ERR_BAD_REVISION');
     }
 
     if ($request->getUser()->getPHID() !== $revision->getAuthorPHID()) {
       throw new ConduitException('ERR_WRONG_USER');
     }
 
     if ($revision->getStatus() == DifferentialRevisionStatus::COMMITTED) {
       throw new ConduitException('ERR_COMMITTED');
     }
 
+    $content_source = PhabricatorContentSource::newForSource(
+      PhabricatorContentSource::SOURCE_CONDUIT,
+      array());
+
     $editor = new DifferentialRevisionEditor(
       $revision,
       $revision->getAuthorPHID());
+    $editor->setContentSource($content_source);
     $fields = $request->getValue('fields');
     $editor->copyFieldsFromConduit($fields);
 
     $editor->addDiff($diff, $request->getValue('message'));
     $editor->save();
 
     return array(
       'revisionid'  => $revision->getID(),
       'uri'         => PhabricatorEnv::getURI('/D'.$revision->getID()),
     );
   }
 
 }
diff --git a/src/applications/conduit/method/differential/updaterevision/__init__.php b/src/applications/conduit/method/differential/updaterevision/__init__.php
index 96f0fe8ce3..e139660974 100644
--- a/src/applications/conduit/method/differential/updaterevision/__init__.php
+++ b/src/applications/conduit/method/differential/updaterevision/__init__.php
@@ -1,20 +1,21 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
 phutil_require_module('phabricator', 'applications/conduit/method/base');
 phutil_require_module('phabricator', 'applications/conduit/protocol/exception');
 phutil_require_module('phabricator', 'applications/differential/constants/revisionstatus');
 phutil_require_module('phabricator', 'applications/differential/editor/revision');
 phutil_require_module('phabricator', 'applications/differential/storage/diff');
 phutil_require_module('phabricator', 'applications/differential/storage/revision');
+phutil_require_module('phabricator', 'applications/metamta/contentsource/source');
 phutil_require_module('phabricator', 'infrastructure/env');
 
 phutil_require_module('phutil', 'utils');
 
 
 phutil_require_source('ConduitAPI_differential_updaterevision_Method.php');
diff --git a/src/applications/conduit/method/maniphest/createtask/ConduitAPI_maniphest_createtask_Method.php b/src/applications/conduit/method/maniphest/createtask/ConduitAPI_maniphest_createtask_Method.php
index 567b16692c..985eccd0a5 100644
--- a/src/applications/conduit/method/maniphest/createtask/ConduitAPI_maniphest_createtask_Method.php
+++ b/src/applications/conduit/method/maniphest/createtask/ConduitAPI_maniphest_createtask_Method.php
@@ -1,107 +1,112 @@
 <?php
 
 /*
  * Copyright 2011 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.
  */
 
 /**
  * @group conduit
  */
 final class ConduitAPI_maniphest_createtask_Method
   extends ConduitAPI_maniphest_Method {
 
   public function getMethodDescription() {
     return "Create a new Maniphest task.";
   }
 
   public function defineParamTypes() {
     return array(
       'title'         => 'required string',
       'description'   => 'optional string',
       'ownerPHID'     => 'optional phid',
       'ccPHIDs'       => 'optional list<phid>',
       'priority'      => 'optional int',
       'projectPHIDs'  => 'optional list<phid>',
       'filePHIDs'     => 'optional list<phid>',
     );
   }
 
   public function defineReturnType() {
     return 'nonempty dict';
   }
 
   public function defineErrorTypes() {
     return array(
     );
   }
 
   protected function execute(ConduitAPIRequest $request) {
     $task = new ManiphestTask();
     $task->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
     $task->setAuthorPHID($request->getUser()->getPHID());
 
     $task->setTitle((string)$request->getValue('title'));
     $task->setDescription((string)$request->getValue('description'));
 
     $changes = array();
     $changes[ManiphestTransactionType::TYPE_STATUS] =
       ManiphestTaskStatus::STATUS_OPEN;
 
     $priority = $request->getValue('priority');
     if ($priority !== null) {
       $changes[ManiphestTransactionType::TYPE_PRIORITY] = $priority;
     }
 
     $owner_phid = $request->getValue('ownerPHID');
     if ($owner_phid !== null) {
       $changes[ManiphestTransactionType::TYPE_OWNER] = $owner_phid;
     }
 
     $ccs = $request->getValue('ccPHIDs');
     if ($ccs !== null) {
       $changes[ManiphestTransactionType::TYPE_CCS] = $ccs;
     }
 
     $project_phids = $request->getValue('projectPHIDs');
     if ($project_phids !== null) {
       $changes[ManiphestTransactionType::TYPE_PROJECTS] = $project_phids;
     }
 
     $file_phids = $request->getValue('filePHIDs');
     if ($file_phids !== null) {
       $file_map = array_fill_keys($file_phids, true);
       $changes[ManiphestTransactionType::TYPE_ATTACH] = array(
         PhabricatorPHIDConstants::PHID_TYPE_FILE => $file_map,
       );
     }
 
+    $content_source = PhabricatorContentSource::newForSource(
+      PhabricatorContentSource::SOURCE_CONDUIT,
+      array());
+
     $template = new ManiphestTransaction();
+    $template->setContentSource($content_source);
     $template->setAuthorPHID($request->getUser()->getPHID());
 
     $transactions = array();
     foreach ($changes as $type => $value) {
       $transaction = clone $template;
       $transaction->setTransactionType($type);
       $transaction->setNewValue($value);
       $transactions[] = $transaction;
     }
 
     $editor = new ManiphestTransactionEditor();
     $editor->applyTransactions($task, $transactions);
 
     return $this->buildTaskInfoDictionary($task);
   }
 
 }
diff --git a/src/applications/conduit/method/maniphest/createtask/__init__.php b/src/applications/conduit/method/maniphest/createtask/__init__.php
index 26dcd306c8..06390dfb64 100644
--- a/src/applications/conduit/method/maniphest/createtask/__init__.php
+++ b/src/applications/conduit/method/maniphest/createtask/__init__.php
@@ -1,19 +1,20 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
 phutil_require_module('phabricator', 'applications/conduit/method/maniphest/base');
 phutil_require_module('phabricator', 'applications/maniphest/constants/priority');
 phutil_require_module('phabricator', 'applications/maniphest/constants/status');
 phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype');
 phutil_require_module('phabricator', 'applications/maniphest/editor/transaction');
 phutil_require_module('phabricator', 'applications/maniphest/storage/task');
 phutil_require_module('phabricator', 'applications/maniphest/storage/transaction');
+phutil_require_module('phabricator', 'applications/metamta/contentsource/source');
 phutil_require_module('phabricator', 'applications/phid/constants');
 
 
 phutil_require_source('ConduitAPI_maniphest_createtask_Method.php');
diff --git a/src/applications/differential/controller/commentsave/DifferentialCommentSaveController.php b/src/applications/differential/controller/commentsave/DifferentialCommentSaveController.php
index 6355d44240..9f589951c9 100644
--- a/src/applications/differential/controller/commentsave/DifferentialCommentSaveController.php
+++ b/src/applications/differential/controller/commentsave/DifferentialCommentSaveController.php
@@ -1,65 +1,72 @@
 <?php
 
 /*
  * Copyright 2011 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 DifferentialCommentSaveController extends DifferentialController {
 
   public function processRequest() {
     $request = $this->getRequest();
     if (!$request->isFormPost()) {
       return new Aphront400Response();
     }
 
     $revision_id = $request->getInt('revision_id');
     $revision = id(new DifferentialRevision())->load($revision_id);
     if (!$revision) {
       return new Aphront400Response();
     }
 
     $comment    = $request->getStr('comment');
     $action     = $request->getStr('action');
     $reviewers  = $request->getArr('reviewers');
     $ccs        = $request->getArr('ccs');
 
     $editor = new DifferentialCommentEditor(
       $revision,
       $request->getUser()->getPHID(),
       $action);
 
+    $content_source = PhabricatorContentSource::newForSource(
+      PhabricatorContentSource::SOURCE_WEB,
+      array(
+        'ip' => $request->getRemoteAddr(),
+      ));
+
     $editor
       ->setMessage($comment)
+      ->setContentSource($content_source)
       ->setAttachInlineComments(true)
       ->setAddCC($action != DifferentialAction::ACTION_RESIGN)
       ->setAddedReviewers($reviewers)
       ->setAddedCCs($ccs)
       ->save();
 
     // TODO: Diff change detection?
 
     $draft = id(new PhabricatorDraft())->loadOneWhere(
       'authorPHID = %s AND draftKey = %s',
       $request->getUser()->getPHID(),
       'differential-comment-'.$revision->getID());
     if ($draft) {
       $draft->delete();
     }
 
     return id(new AphrontRedirectResponse())
       ->setURI('/D'.$revision->getID());
   }
 
 }
diff --git a/src/applications/differential/controller/commentsave/__init__.php b/src/applications/differential/controller/commentsave/__init__.php
index 20dddcc7dc..7392c007a0 100644
--- a/src/applications/differential/controller/commentsave/__init__.php
+++ b/src/applications/differential/controller/commentsave/__init__.php
@@ -1,20 +1,21 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
 phutil_require_module('phabricator', 'aphront/response/400');
 phutil_require_module('phabricator', 'aphront/response/redirect');
 phutil_require_module('phabricator', 'applications/differential/constants/action');
 phutil_require_module('phabricator', 'applications/differential/controller/base');
 phutil_require_module('phabricator', 'applications/differential/editor/comment');
 phutil_require_module('phabricator', 'applications/differential/storage/revision');
 phutil_require_module('phabricator', 'applications/draft/storage/draft');
+phutil_require_module('phabricator', 'applications/metamta/contentsource/source');
 
 phutil_require_module('phutil', 'utils');
 
 
 phutil_require_source('DifferentialCommentSaveController.php');
diff --git a/src/applications/differential/editor/comment/DifferentialCommentEditor.php b/src/applications/differential/editor/comment/DifferentialCommentEditor.php
index 362b3567cb..61e7ca55b5 100644
--- a/src/applications/differential/editor/comment/DifferentialCommentEditor.php
+++ b/src/applications/differential/editor/comment/DifferentialCommentEditor.php
@@ -1,434 +1,445 @@
 <?php
 
 /*
  * Copyright 2011 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 DifferentialCommentEditor {
 
   protected $revision;
   protected $actorPHID;
   protected $action;
 
   protected $attachInlineComments;
   protected $message;
   protected $addCC;
   protected $changedByCommit;
   protected $addedReviewers = array();
   private $addedCCs = array();
 
   private $parentMessageID;
+  private $contentSource;
 
   public function __construct(
     DifferentialRevision $revision,
     $actor_phid,
     $action) {
 
     $this->revision = $revision;
     $this->actorPHID  = $actor_phid;
     $this->action   = $action;
   }
 
   public function setParentMessageID($parent_message_id) {
     $this->parentMessageID = $parent_message_id;
     return $this;
   }
 
   public function setMessage($message) {
     $this->message = $message;
     return $this;
   }
 
   public function setAttachInlineComments($attach) {
     $this->attachInlineComments = $attach;
     return $this;
   }
 
   public function setAddCC($add) {
     $this->addCC = $add;
     return $this;
   }
 
   public function setChangedByCommit($changed_by_commit) {
     $this->changedByCommit = $changed_by_commit;
     return $this;
   }
 
   public function getChangedByCommit() {
     return $this->changedByCommit;
   }
 
   public function setAddedReviewers($added_reviewers) {
     $this->addedReviewers = $added_reviewers;
     return $this;
   }
 
   public function getAddedReviewers() {
     return $this->addedReviewers;
   }
 
   public function setAddedCCs($added_ccs) {
     $this->addedCCs = $added_ccs;
     return $this;
   }
 
   public function getAddedCCs() {
     return $this->addedCCs;
   }
 
+  public function setContentSource(PhabricatorContentSource $content_source) {
+    $this->contentSource = $content_source;
+    return $this;
+  }
+
   public function save() {
     $revision = $this->revision;
     $action = $this->action;
     $actor_phid = $this->actorPHID;
     $actor_is_author = ($actor_phid == $revision->getAuthorPHID());
     $revision_status = $revision->getStatus();
 
     $revision->loadRelationships();
     $reviewer_phids = $revision->getReviewers();
     if ($reviewer_phids) {
       $reviewer_phids = array_combine($reviewer_phids, $reviewer_phids);
     }
 
     $metadata = array();
 
     switch ($action) {
       case DifferentialAction::ACTION_COMMENT:
         break;
 
       case DifferentialAction::ACTION_RESIGN:
         if ($actor_is_author) {
           throw new Exception('You can not resign from your own revision!');
         }
         if (isset($reviewer_phids[$actor_phid])) {
           DifferentialRevisionEditor::alterReviewers(
             $revision,
             $reviewer_phids,
             $rem = array($actor_phid),
             $add = array(),
             $actor_phid);
         }
         break;
 
       case DifferentialAction::ACTION_ABANDON:
         if (!$actor_is_author) {
           throw new Exception('You can only abandon your revisions.');
         }
         if ($revision_status == DifferentialRevisionStatus::COMMITTED) {
           throw new Exception('You can not abandon a committed revision.');
         }
         if ($revision_status == DifferentialRevisionStatus::ABANDONED) {
           $action = DifferentialAction::ACTION_COMMENT;
           break;
         }
 
         $revision
           ->setStatus(DifferentialRevisionStatus::ABANDONED)
           ->save();
         break;
 
       case DifferentialAction::ACTION_ACCEPT:
         if ($actor_is_author) {
           throw new Exception('You can not accept your own revision.');
         }
         if (($revision_status != DifferentialRevisionStatus::NEEDS_REVIEW) &&
             ($revision_status != DifferentialRevisionStatus::NEEDS_REVISION)) {
           $action = DifferentialAction::ACTION_COMMENT;
           break;
         }
 
         $revision
           ->setStatus(DifferentialRevisionStatus::ACCEPTED)
           ->save();
 
         if (!isset($reviewer_phids[$actor_phid])) {
           DifferentialRevisionEditor::alterReviewers(
             $revision,
             $reviewer_phids,
             $rem = array(),
             $add = array($actor_phid),
             $actor_phid);
         }
         break;
 
       case DifferentialAction::ACTION_REQUEST:
         if (!$actor_is_author) {
           throw new Exception('You must own a revision to request review.');
         }
         if (($revision_status != DifferentialRevisionStatus::NEEDS_REVISION) &&
             ($revision_status != DifferentialRevisionStatus::ACCEPTED)) {
           $action = DifferentialAction::ACTION_COMMENT;
           break;
         }
 
         $revision
           ->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW)
           ->save();
         break;
 
       case DifferentialAction::ACTION_REJECT:
         if ($actor_is_author) {
           throw new Exception(
             'You can not request changes to your own revision.');
         }
         if (($revision_status != DifferentialRevisionStatus::NEEDS_REVIEW) &&
             ($revision_status != DifferentialRevisionStatus::ACCEPTED)) {
           $action = DifferentialAction::ACTION_COMMENT;
           break;
         }
 
         if (!isset($reviewer_phids[$actor_phid])) {
           DifferentialRevisionEditor::alterReviewers(
             $revision,
             $reviewer_phids,
             $rem = array(),
             $add = array($actor_phid),
             $actor_phid);
         }
 
         $revision
           ->setStatus(DifferentialRevisionStatus::NEEDS_REVISION)
           ->save();
         break;
 
       case DifferentialAction::ACTION_RETHINK:
         if (!$actor_is_author) {
           throw new Exception(
             "You can not plan changes to somebody else's revision");
         }
         if (($revision_status != DifferentialRevisionStatus::NEEDS_REVIEW) &&
             ($revision_status != DifferentialRevisionStatus::ACCEPTED)) {
           $action = DifferentialAction::ACTION_COMMENT;
           break;
         }
 
         $revision
           ->setStatus(DifferentialRevisionStatus::NEEDS_REVISION)
           ->save();
         break;
 
       case DifferentialAction::ACTION_RECLAIM:
         if (!$actor_is_author) {
           throw new Exception('You can not reclaim a revision you do not own.');
         }
         if ($revision_status != DifferentialRevisionStatus::ABANDONED) {
           $action = DifferentialAction::ACTION_COMMENT;
           break;
         }
         $revision
           ->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW)
           ->save();
         break;
 
       case DifferentialAction::ACTION_COMMIT:
         if (!$actor_is_author) {
           throw new Exception('You can not commit a revision you do not own.');
         }
         $revision
           ->setStatus(DifferentialRevisionStatus::COMMITTED)
           ->save();
         break;
 
       case DifferentialAction::ACTION_ADDREVIEWERS:
         $added_reviewers = $this->getAddedReviewers();
         foreach ($added_reviewers as $k => $user_phid) {
           if ($user_phid == $revision->getAuthorPHID()) {
             unset($added_reviewers[$k]);
           }
           if (!empty($reviewer_phids[$user_phid])) {
             unset($added_reviewers[$k]);
           }
         }
 
         $added_reviewers = array_unique($added_reviewers);
 
         if ($added_reviewers) {
           DifferentialRevisionEditor::alterReviewers(
             $revision,
             $reviewer_phids,
             $rem = array(),
             $add = $added_reviewers,
             $actor_phid);
 
           $key = DifferentialComment::METADATA_ADDED_REVIEWERS;
           $metadata[$key] = $added_reviewers;
 
         } else {
           $action = DifferentialAction::ACTION_COMMENT;
         }
         break;
       case DifferentialAction::ACTION_ADDCCS:
         $added_ccs = $this->getAddedCCs();
 
         $current_ccs = $revision->getCCPHIDs();
         if ($current_ccs) {
           $current_ccs = array_fill_keys($current_ccs, true);
           foreach ($added_ccs as $k => $cc) {
             if (isset($current_ccs[$cc])) {
               unset($added_ccs[$k]);
             }
           }
         }
 
         if ($added_ccs) {
           foreach ($added_ccs as $cc) {
             DifferentialRevisionEditor::addCC(
               $revision,
               $cc,
               $this->actorPHID);
           }
 
           $key = DifferentialComment::METADATA_ADDED_CCS;
           $metadata[$key] = $added_ccs;
 
         } else {
           $action = DifferentialAction::ACTION_COMMENT;
         }
         break;
       default:
         throw new Exception('Unsupported action.');
     }
 
     if ($this->addCC) {
       DifferentialRevisionEditor::addCC(
         $revision,
         $this->actorPHID,
         $this->actorPHID);
     }
 
     $inline_comments = array();
     if ($this->attachInlineComments) {
       $inline_comments = id(new DifferentialInlineComment())->loadAllWhere(
         'authorPHID = %s AND revisionID = %d AND commentID IS NULL',
         $this->actorPHID,
         $revision->getID());
     }
 
     $comment = id(new DifferentialComment())
       ->setAuthorPHID($this->actorPHID)
       ->setRevisionID($revision->getID())
       ->setAction($action)
       ->setContent((string)$this->message)
-      ->setMetadata($metadata)
-      ->save();
+      ->setMetadata($metadata);
+
+    if ($this->contentSource) {
+      $comment->setContentSource($this->contentSource);
+    }
+
+    $comment->save();
 
     $changesets = array();
     if ($inline_comments) {
       $load_ids = mpull($inline_comments, 'getChangesetID');
       if ($load_ids) {
         $load_ids = array_unique($load_ids);
         $changesets = id(new DifferentialChangeset())->loadAllWhere(
           'id in (%Ld)',
           $load_ids);
       }
       foreach ($inline_comments as $inline) {
         $inline->setCommentID($comment->getID());
         $inline->save();
       }
     }
 
     // Find any "@mentions" in the comment blocks.
     $content_blocks = array($comment->getContent());
     foreach ($inline_comments as $inline) {
       $content_blocks[] = $inline->getContent();
     }
     $mention_ccs = PhabricatorMarkupEngine::extractPHIDsFromMentions(
       $content_blocks);
     if ($mention_ccs) {
       $current_ccs = $revision->getCCPHIDs();
       if ($current_ccs) {
         $current_ccs = array_fill_keys($current_ccs, true);
         foreach ($mention_ccs as $key => $mention_cc) {
           if (isset($current_ccs[$mention_cc])) {
             unset($mention_ccs[$key]);
           }
         }
       }
       if ($mention_ccs) {
         $metadata = $comment->getMetadata();
         $metacc = idx(
           $metadata,
           DifferentialComment::METADATA_ADDED_CCS,
           array());
         foreach ($mention_ccs as $cc_phid) {
           DifferentialRevisionEditor::addCC(
             $revision,
             $cc_phid,
             $this->actorPHID);
           $metacc[] = $cc_phid;
         }
         $metadata[DifferentialComment::METADATA_ADDED_CCS] = $metacc;
 
         $comment->setMetadata($metadata);
         $comment->save();
       }
     }
 
     $phids = array($this->actorPHID);
     $handles = id(new PhabricatorObjectHandleData($phids))
       ->loadHandles();
     $actor_handle = $handles[$this->actorPHID];
 
     $xherald_header = HeraldTranscript::loadXHeraldRulesHeader(
       $revision->getPHID());
 
     id(new DifferentialCommentMail(
       $revision,
       $actor_handle,
       $comment,
       $changesets,
       $inline_comments))
       ->setToPHIDs(
         array_merge(
           $revision->getReviewers(),
           array($revision->getAuthorPHID())))
       ->setCCPHIDs($revision->getCCPHIDs())
       ->setChangedByCommit($this->getChangedByCommit())
       ->setXHeraldRulesHeader($xherald_header)
       ->setParentMessageID($this->parentMessageID)
       ->send();
 
     $event_data = array(
       'revision_id'          => $revision->getID(),
       'revision_phid'        => $revision->getPHID(),
       'revision_name'        => $revision->getTitle(),
       'revision_author_phid' => $revision->getAuthorPHID(),
       'action'               => $comment->getAction(),
       'feedback_content'     => $comment->getContent(),
       'actor_phid'           => $this->actorPHID,
     );
     id(new PhabricatorTimelineEvent('difx', $event_data))
       ->recordEvent();
 
     // TODO: Move to a daemon?
     id(new PhabricatorFeedStoryPublisher())
       ->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_DIFFERENTIAL)
       ->setStoryData($event_data)
       ->setStoryTime(time())
       ->setStoryAuthorPHID($this->actorPHID)
       ->setRelatedPHIDs(
         array(
           $revision->getPHID(),
           $this->actorPHID,
           $revision->getAuthorPHID(),
         ))
       ->publish();
 
     // TODO: Move to a daemon?
     PhabricatorSearchDifferentialIndexer::indexRevision($revision);
 
     return $comment;
   }
 
 }
diff --git a/src/applications/differential/editor/revision/DifferentialRevisionEditor.php b/src/applications/differential/editor/revision/DifferentialRevisionEditor.php
index e570881dc8..20a3fab1f9 100644
--- a/src/applications/differential/editor/revision/DifferentialRevisionEditor.php
+++ b/src/applications/differential/editor/revision/DifferentialRevisionEditor.php
@@ -1,718 +1,729 @@
 <?php
 
 /*
  * Copyright 2011 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.
  */
 
 /**
  * Handle major edit operations to DifferentialRevision -- adding and removing
  * reviewers, diffs, and CCs. Unlike simple edits, these changes trigger
  * complicated email workflows.
  */
 class DifferentialRevisionEditor {
 
   protected $revision;
   protected $actorPHID;
 
   protected $cc         = null;
   protected $reviewers  = null;
   protected $diff;
   protected $comments;
   protected $silentUpdate;
 
   private $auxiliaryFields = array();
+  private $contentSource;
 
   public function __construct(DifferentialRevision $revision, $actor_phid) {
     $this->revision = $revision;
     $this->actorPHID = $actor_phid;
   }
 
   public static function newRevisionFromConduitWithDiff(
     array $fields,
     DifferentialDiff $diff,
     $user_phid) {
 
     $revision = new DifferentialRevision();
     $revision->setPHID($revision->generatePHID());
 
     $revision->setAuthorPHID($user_phid);
     $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW);
 
     $editor = new DifferentialRevisionEditor($revision, $user_phid);
 
     $editor->copyFieldsFromConduit($fields);
 
     $editor->addDiff($diff, null);
     $editor->save();
 
     return $revision;
   }
 
   public function copyFieldsFromConduit(array $fields) {
 
     $revision = $this->revision;
     $revision->loadRelationships();
 
     $aux_fields = DifferentialFieldSelector::newSelector()
       ->getFieldSpecifications();
 
     foreach ($aux_fields as $key => $aux_field) {
       $aux_field->setRevision($revision);
       if (!$aux_field->shouldAppearOnCommitMessage()) {
         unset($aux_fields[$key]);
       }
     }
 
     $aux_fields = mpull($aux_fields, null, 'getCommitMessageKey');
 
     foreach ($fields as $field => $value) {
       if (empty($aux_fields[$field])) {
         throw new Exception(
           "Parsed commit message contains unrecognized field '{$field}'.");
       }
       $aux_fields[$field]->setValueFromParsedCommitMessage($value);
     }
 
     $aux_fields = array_values($aux_fields);
     $this->setAuxiliaryFields($aux_fields);
   }
 
   public function setAuxiliaryFields(array $auxiliary_fields) {
     $this->auxiliaryFields = $auxiliary_fields;
     return $this;
   }
 
   public function getRevision() {
     return $this->revision;
   }
 
   public function setReviewers(array $reviewers) {
     $this->reviewers = $reviewers;
     return $this;
   }
 
   public function setCCPHIDs(array $cc) {
     $this->cc = $cc;
     return $this;
   }
 
+  public function setContentSource(PhabricatorContentSource $content_source) {
+    $this->contentSource = $content_source;
+    return $this;
+  }
+
   public function addDiff(DifferentialDiff $diff, $comments) {
     if ($diff->getRevisionID() &&
         $diff->getRevisionID() != $this->getRevision()->getID()) {
       $diff_id = (int)$diff->getID();
       $targ_id = (int)$this->getRevision()->getID();
       $real_id = (int)$diff->getRevisionID();
       throw new Exception(
         "Can not attach diff #{$diff_id} to Revision D{$targ_id}, it is ".
         "already attached to D{$real_id}.");
     }
     $this->diff = $diff;
     $this->comments = $comments;
     return $this;
   }
 
   protected function getDiff() {
     return $this->diff;
   }
 
   protected function getComments() {
     return $this->comments;
   }
 
   protected function getActorPHID() {
     return $this->actorPHID;
   }
 
   public function isNewRevision() {
     return !$this->getRevision()->getID();
   }
 
   /**
    * A silent update does not trigger Herald rules or send emails. This is used
    * for auto-amends at commit time.
    */
   public function setSilentUpdate($silent) {
     $this->silentUpdate = $silent;
     return $this;
   }
 
   public function save() {
     $revision = $this->getRevision();
 
 // TODO
 //    $revision->openTransaction();
 
     $is_new = $this->isNewRevision();
     if ($is_new) {
       // These fields aren't nullable; set them to sensible defaults if they
       // haven't been configured. We're just doing this so we can generate an
       // ID for the revision if we don't have one already.
       $revision->setLineCount(0);
       if ($revision->getStatus() === null) {
         $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW);
       }
       if ($revision->getTitle() === null) {
         $revision->setTitle('Untitled Revision');
       }
       if ($revision->getAuthorPHID() === null) {
         $revision->setAuthorPHID($this->getActorPHID());
       }
       if ($revision->getRevertPlan() === null) {
         $revision->setRevertPlan('');
       }
       if ($revision->getBlameRevision() === null) {
         $revision->setBlameRevision('');
       }
       if ($revision->getSummary() === null) {
         $revision->setSummary('');
       }
       if ($revision->getTestPlan() === null) {
         $revision->setTestPlan('');
       }
       $revision->save();
     }
 
     $revision->loadRelationships();
 
     $this->willWriteRevision();
 
     if ($this->reviewers === null) {
       $this->reviewers = $revision->getReviewers();
     }
 
     if ($this->cc === null) {
       $this->cc = $revision->getCCPHIDs();
     }
 
     // We're going to build up three dictionaries: $add, $rem, and $stable. The
     // $add dictionary has added reviewers/CCs. The $rem dictionary has
     // reviewers/CCs who have been removed, and the $stable array is
     // reviewers/CCs who haven't changed. We're going to send new reviewers/CCs
     // a different ("welcome") email than we send stable reviewers/CCs.
 
     $old = array(
       'rev' => array_fill_keys($revision->getReviewers(), true),
       'ccs' => array_fill_keys($revision->getCCPHIDs(), true),
     );
 
     $diff = $this->getDiff();
 
     $xscript_header = null;
     $xscript_uri = null;
 
     $new = array(
       'rev' => array_fill_keys($this->reviewers, true),
       'ccs' => array_fill_keys($this->cc, true),
     );
 
 
     $rem_ccs = array();
     if ($diff) {
       $diff->setRevisionID($revision->getID());
       $revision->setLineCount($diff->getLineCount());
 
       $adapter = new HeraldDifferentialRevisionAdapter(
         $revision,
         $diff);
       $adapter->setExplicitCCs($new['ccs']);
       $adapter->setExplicitReviewers($new['rev']);
       $adapter->setForbiddenCCs($revision->getUnsubscribedPHIDs());
 
       $xscript = HeraldEngine::loadAndApplyRules($adapter);
       $xscript_uri = PhabricatorEnv::getProductionURI(
         '/herald/transcript/'.$xscript->getID().'/');
       $xscript_phid = $xscript->getPHID();
       $xscript_header = $xscript->getXHeraldRulesHeader();
 
       $xscript_header = HeraldTranscript::saveXHeraldRulesHeader(
         $revision->getPHID(),
         $xscript_header);
 
       $sub = array(
         'rev' => array(),
         'ccs' => $adapter->getCCsAddedByHerald(),
       );
       $rem_ccs = $adapter->getCCsRemovedByHerald();
     } else {
       $sub = array(
         'rev' => array(),
         'ccs' => array(),
       );
     }
 
     // Remove any CCs which are prevented by Herald rules.
     $sub['ccs'] = array_diff_key($sub['ccs'], $rem_ccs);
     $new['ccs'] = array_diff_key($new['ccs'], $rem_ccs);
 
     $add = array();
     $rem = array();
     $stable = array();
     foreach (array('rev', 'ccs') as $key) {
       $add[$key] = array();
       if ($new[$key] !== null) {
         $add[$key] += array_diff_key($new[$key], $old[$key]);
       }
       $add[$key] += array_diff_key($sub[$key], $old[$key]);
 
       $combined = $sub[$key];
       if ($new[$key] !== null) {
         $combined += $new[$key];
       }
       $rem[$key] = array_diff_key($old[$key], $combined);
 
       $stable[$key] = array_diff_key($old[$key], $add[$key] + $rem[$key]);
     }
 
     self::alterReviewers(
       $revision,
       $this->reviewers,
       array_keys($rem['rev']),
       array_keys($add['rev']),
       $this->actorPHID);
 
 /*
 
     // TODO: When Herald is brought over, run through this stuff to figure
     // out which adds are Herald's fault.
 
     // TODO: Still need to do this.
 
     if ($add['ccs'] || $rem['ccs']) {
       foreach (array_keys($add['ccs']) as $id) {
         if (empty($new['ccs'][$id])) {
           $reason_phid = 'TODO';//$xscript_phid;
         } else {
           $reason_phid = $this->getActorPHID();
         }
       }
       foreach (array_keys($rem['ccs']) as $id) {
         if (empty($new['ccs'][$id])) {
           $reason_phid = $this->getActorPHID();
         } else {
           $reason_phid = 'TODO';//$xscript_phid;
         }
       }
     }
 */
     self::alterCCs(
       $revision,
       $this->cc,
       array_keys($rem['ccs']),
       array_keys($add['ccs']),
       $this->actorPHID);
 
     $this->updateAuxiliaryFields();
 
     // Add the author and users included from Herald rules to the relevant set
     // of users so they get a copy of the email.
     if (!$this->silentUpdate) {
       if ($is_new) {
         $add['rev'][$this->getActorPHID()] = true;
         if ($diff) {
           $add['rev'] += $adapter->getEmailPHIDsAddedByHerald();
         }
       } else {
         $stable['rev'][$this->getActorPHID()] = true;
         if ($diff) {
           $stable['rev'] += $adapter->getEmailPHIDsAddedByHerald();
         }
       }
     }
 
     $mail = array();
 
     $phids = array($this->getActorPHID());
 
     $handles = id(new PhabricatorObjectHandleData($phids))
       ->loadHandles();
     $actor_handle = $handles[$this->getActorPHID()];
 
     $changesets = null;
     $comment = null;
     if ($diff) {
       $changesets = $diff->loadChangesets();
       // TODO: This should probably be in DifferentialFeedbackEditor?
       if (!$is_new) {
         $comment = $this->createComment();
       }
       if ($comment) {
         $mail[] = id(new DifferentialNewDiffMail(
             $revision,
             $actor_handle,
             $changesets))
           ->setIsFirstMailAboutRevision($is_new)
           ->setIsFirstMailToRecipients($is_new)
           ->setComments($this->getComments())
           ->setToPHIDs(array_keys($stable['rev']))
           ->setCCPHIDs(array_keys($stable['ccs']));
       }
 
       // Save the changes we made above.
 
       $diff->setDescription(substr($this->getComments(), 0, 80));
       $diff->save();
 
       // An updated diff should require review, as long as it's not committed
       // or accepted. The "accepted" status is "sticky" to encourage courtesy
       // re-diffs after someone accepts with minor changes/suggestions.
 
       $status = $revision->getStatus();
       if ($status != DifferentialRevisionStatus::COMMITTED &&
           $status != DifferentialRevisionStatus::ACCEPTED) {
         $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW);
       }
 
     } else {
       $diff = $revision->loadActiveDiff();
       if ($diff) {
         $changesets = $diff->loadChangesets();
       } else {
         $changesets = array();
       }
     }
 
     $revision->save();
 
     $this->didWriteRevision();
 
     $event_data = array(
       'revision_id'          => $revision->getID(),
       'revision_phid'        => $revision->getPHID(),
       'revision_name'        => $revision->getTitle(),
       'revision_author_phid' => $revision->getAuthorPHID(),
       'action'               => $is_new
         ? DifferentialAction::ACTION_CREATE
         : DifferentialAction::ACTION_UPDATE,
       'feedback_content'     => $is_new
         ? phutil_utf8_shorten($revision->getSummary(), 140)
         : $this->getComments(),
       'actor_phid'           => $revision->getAuthorPHID(),
     );
     id(new PhabricatorTimelineEvent('difx', $event_data))
       ->recordEvent();
 
     id(new PhabricatorFeedStoryPublisher())
       ->setStoryType(PhabricatorFeedStoryTypeConstants::STORY_DIFFERENTIAL)
       ->setStoryData($event_data)
       ->setStoryTime(time())
       ->setStoryAuthorPHID($revision->getAuthorPHID())
       ->setRelatedPHIDs(
         array(
           $revision->getPHID(),
           $revision->getAuthorPHID(),
         ))
       ->publish();
 
 // TODO
 //    $revision->saveTransaction();
 
 //  TODO: Move this into a worker task thing.
     PhabricatorSearchDifferentialIndexer::indexRevision($revision);
 
     if ($this->silentUpdate) {
       return;
     }
 
     $revision->loadRelationships();
 
     if ($add['rev']) {
       $message = id(new DifferentialNewDiffMail(
           $revision,
           $actor_handle,
           $changesets))
         ->setIsFirstMailAboutRevision($is_new)
         ->setIsFirstMailToRecipients(true)
         ->setToPHIDs(array_keys($add['rev']));
 
       if ($is_new) {
         // The first time we send an email about a revision, put the CCs in
         // the "CC:" field of the same "Review Requested" email that reviewers
         // get, so you don't get two initial emails if you're on a list that
         // is CC'd.
         $message->setCCPHIDs(array_keys($add['ccs']));
       }
 
       $mail[] = $message;
     }
 
     // If you were added as a reviewer and a CC, just give you the reviewer
     // email. We could go to greater lengths to prevent this, but there's
     // bunch of stuff with list subscriptions anyway. You can still get two
     // emails, but only if a revision is updated and you are added as a reviewer
     // at the same time a list you are on is added as a CC, which is rare and
     // reasonable.
     $add['ccs'] = array_diff_key($add['ccs'], $add['rev']);
 
     if (!$is_new && $add['ccs']) {
       $mail[] = id(new DifferentialCCWelcomeMail(
           $revision,
           $actor_handle,
           $changesets))
         ->setIsFirstMailToRecipients(true)
         ->setToPHIDs(array_keys($add['ccs']));
     }
 
     foreach ($mail as $message) {
       $message->setHeraldTranscriptURI($xscript_uri);
       $message->setXHeraldRulesHeader($xscript_header);
       $message->send();
     }
   }
 
   public static function addCCAndUpdateRevision(
     $revision,
     $phid,
     $reason) {
 
     self::addCC($revision, $phid, $reason);
 
     $unsubscribed = $revision->getUnsubscribed();
     if (isset($unsubscribed[$phid])) {
       unset($unsubscribed[$phid]);
       $revision->setUnsubscribed($unsubscribed);
       $revision->save();
     }
   }
 
   public static function removeCCAndUpdateRevision(
     $revision,
     $phid,
     $reason) {
 
     self::removeCC($revision, $phid, $reason);
 
     $unsubscribed = $revision->getUnsubscribed();
     if (empty($unsubscribed[$phid])) {
       $unsubscribed[$phid] = true;
       $revision->setUnsubscribed($unsubscribed);
       $revision->save();
     }
   }
 
   public static function addCC(
     DifferentialRevision $revision,
     $phid,
     $reason) {
     return self::alterCCs(
       $revision,
       $revision->getCCPHIDs(),
       $rem = array(),
       $add = array($phid),
       $reason);
   }
 
   public static function removeCC(
     DifferentialRevision $revision,
     $phid,
     $reason) {
     return self::alterCCs(
       $revision,
       $revision->getCCPHIDs(),
       $rem = array($phid),
       $add = array(),
       $reason);
   }
 
   protected static function alterCCs(
     DifferentialRevision $revision,
     array $stable_phids,
     array $rem_phids,
     array $add_phids,
     $reason_phid) {
 
     return self::alterRelationships(
       $revision,
       $stable_phids,
       $rem_phids,
       $add_phids,
       $reason_phid,
       DifferentialRevision::RELATION_SUBSCRIBED);
   }
 
 
   public static function alterReviewers(
     DifferentialRevision $revision,
     array $stable_phids,
     array $rem_phids,
     array $add_phids,
     $reason_phid) {
 
     return self::alterRelationships(
       $revision,
       $stable_phids,
       $rem_phids,
       $add_phids,
       $reason_phid,
       DifferentialRevision::RELATION_REVIEWER);
   }
 
   private static function alterRelationships(
     DifferentialRevision $revision,
     array $stable_phids,
     array $rem_phids,
     array $add_phids,
     $reason_phid,
     $relation_type) {
 
     $rem_map = array_fill_keys($rem_phids, true);
     $add_map = array_fill_keys($add_phids, true);
 
     $seq_map = array_values($stable_phids);
     $seq_map = array_flip($seq_map);
     foreach ($rem_map as $phid => $ignored) {
       if (!isset($seq_map[$phid])) {
         $seq_map[$phid] = count($seq_map);
       }
     }
     foreach ($add_map as $phid => $ignored) {
       if (!isset($seq_map[$phid])) {
         $seq_map[$phid] = count($seq_map);
       }
     }
 
     $raw = $revision->getRawRelations($relation_type);
     $raw = ipull($raw, null, 'objectPHID');
 
     $sequence = count($seq_map);
     foreach ($raw as $phid => $ignored) {
       if (isset($seq_map[$phid])) {
         $raw[$phid]['sequence'] = $seq_map[$phid];
       } else {
         $raw[$phid]['sequence'] = $sequence++;
       }
     }
     $raw = isort($raw, 'sequence');
 
     foreach ($raw as $phid => $ignored) {
       if (isset($rem_map[$phid])) {
         unset($raw[$phid]);
       }
     }
 
     foreach ($add_phids as $add) {
       $raw[$add] = array(
         'objectPHID'  => $add,
         'sequence'    => idx($seq_map, $add, $sequence++),
         'reasonPHID'  => $reason_phid,
       );
     }
 
     $conn_w = $revision->establishConnection('w');
 
     $sql = array();
     foreach ($raw as $relation) {
       $sql[] = qsprintf(
         $conn_w,
         '(%d, %s, %s, %d, %s)',
         $revision->getID(),
         $relation_type,
         $relation['objectPHID'],
         $relation['sequence'],
         $relation['reasonPHID']);
     }
 
     $conn_w->openTransaction();
       queryfx(
         $conn_w,
         'DELETE FROM %T WHERE revisionID = %d AND relation = %s',
         DifferentialRevision::RELATIONSHIP_TABLE,
         $revision->getID(),
         $relation_type);
       if ($sql) {
         queryfx(
           $conn_w,
           'INSERT INTO %T
             (revisionID, relation, objectPHID, sequence, reasonPHID)
           VALUES %Q',
           DifferentialRevision::RELATIONSHIP_TABLE,
           implode(', ', $sql));
       }
     $conn_w->saveTransaction();
 
     $revision->loadRelationships();
   }
 
 
   private function createComment() {
     $revision_id = $this->revision->getID();
     $comment = id(new DifferentialComment())
       ->setAuthorPHID($this->getActorPHID())
       ->setRevisionID($revision_id)
       ->setContent($this->getComments())
       ->setAction('update');
+
+    if ($this->contentSource) {
+      $comment->setContentSource($this->contentSource);
+    }
+
     $comment->save();
 
     return $comment;
   }
 
   private function updateAuxiliaryFields() {
     $aux_map = array();
     foreach ($this->auxiliaryFields as $aux_field) {
       $key = $aux_field->getStorageKey();
       if ($key !== null) {
         $val = $aux_field->getValueForStorage();
         $aux_map[$key] = $val;
       }
     }
 
     if (!$aux_map) {
       return;
     }
 
     $revision = $this->revision;
 
     $fields = id(new DifferentialAuxiliaryField())->loadAllWhere(
       'revisionPHID = %s AND name IN (%Ls)',
       $revision->getPHID(),
       array_keys($aux_map));
     $fields = mpull($fields, null, 'getName');
 
     foreach ($aux_map as $key => $val) {
       $obj = idx($fields, $key);
       if (!strlen($val)) {
         // If the new value is empty, just delete the old row if one exists and
         // don't add a new row if it doesn't.
         if ($obj) {
           $obj->delete();
         }
       } else {
         if (!$obj) {
           $obj = new DifferentialAuxiliaryField();
           $obj->setRevisionPHID($revision->getPHID());
           $obj->setName($key);
         }
 
         if ($obj->getValue() !== $val) {
           $obj->setValue($val);
           $obj->save();
         }
       }
     }
   }
 
   private function willWriteRevision() {
     foreach ($this->auxiliaryFields as $aux_field) {
       $aux_field->willWriteRevision($this);
     }
   }
 
   private function didWriteRevision() {
     foreach ($this->auxiliaryFields as $aux_field) {
       $aux_field->didWriteRevision($this);
     }
   }
 
 }
 
diff --git a/src/applications/differential/replyhandler/DifferentialReplyHandler.php b/src/applications/differential/replyhandler/DifferentialReplyHandler.php
index 516ebd453c..a23b28f50e 100644
--- a/src/applications/differential/replyhandler/DifferentialReplyHandler.php
+++ b/src/applications/differential/replyhandler/DifferentialReplyHandler.php
@@ -1,175 +1,181 @@
 <?php
 
 /*
  * Copyright 2011 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 DifferentialReplyHandler extends PhabricatorMailReplyHandler {
 
   private $receivedMail;
 
   public function validateMailReceiver($mail_receiver) {
     if (!($mail_receiver instanceof DifferentialRevision)) {
       throw new Exception("Receiver is not a DifferentialRevision!");
     }
   }
 
   public function getPrivateReplyHandlerEmailAddress(
     PhabricatorObjectHandle $handle) {
     return $this->getDefaultPrivateReplyHandlerEmailAddress($handle, 'D');
   }
 
   public function getPublicReplyHandlerEmailAddress() {
     return $this->getDefaultPublicReplyHandlerEmailAddress('D');
   }
 
   public function getReplyHandlerDomain() {
     return PhabricatorEnv::getEnvConfig(
       'metamta.differential.reply-handler-domain');
   }
 
   /*
    * Generate text like the following from the supported commands.
    * "
    *
    * ACTIONS
    * Reply to comment, or !accept, !reject, !abandon, !resign, !reclaim.
    *
    * "
    */
   public function getReplyHandlerInstructions() {
     if (!$this->supportsReplies()) {
       return null;
     }
 
     $supported_commands = $this->getSupportedCommands();
     $text = '';
     if (empty($supported_commands)) {
       return $text;
     }
 
     $comment_command_printed = false;
     if (in_array(DifferentialAction::ACTION_COMMENT, $supported_commands)) {
       $text .= 'Reply to comment';
       $comment_command_printed = true;
 
       $supported_commands = array_diff(
         $supported_commands, array(DifferentialAction::ACTION_COMMENT));
     }
 
     if (!empty($supported_commands)) {
       if ($comment_command_printed) {
         $text .= ', or ';
       }
 
       $modified_commands = array();
       foreach ($supported_commands as $command) {
         $modified_commands[] = '!'.$command;
       }
 
       $text .= implode(', ', $modified_commands);
     }
 
     $text .= ".";
 
     return $text;
   }
 
   public function getSupportedCommands() {
     return array(
       DifferentialAction::ACTION_COMMENT,
       DifferentialAction::ACTION_REJECT,
       DifferentialAction::ACTION_ABANDON,
       DifferentialAction::ACTION_RECLAIM,
       DifferentialAction::ACTION_RESIGN,
       DifferentialAction::ACTION_RETHINK,
       'unsubscribe',
     );
   }
 
   public function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) {
     $this->receivedMail = $mail;
     $this->handleAction($mail->getCleanTextBody());
   }
 
   public function handleAction($body) {
     // all commands start with a bang and separated from the body by a newline
     // to make sure that actual feedback text couldn't trigger an action.
     // unrecognized commands will be parsed as part of the comment.
     $command = DifferentialAction::ACTION_COMMENT;
     $supported_commands = $this->getSupportedCommands();
     $regex = "/\A\n*!(" . implode('|', $supported_commands) . ")\n*/";
     $matches = array();
     if (preg_match($regex, $body, $matches)) {
       $command = $matches[1];
       $body = trim(str_replace('!' . $command, '', $body));
     }
 
     $actor = $this->getActor();
     if (!$actor) {
       throw new Exception('No actor is set for the reply action.');
     }
 
     switch ($command) {
       case 'unsubscribe':
         $this->unsubscribeUser($this->getMailReceiver(), $actor);
         // TODO: Send the user a confirmation email?
         return null;
     }
 
     try {
       $editor = new DifferentialCommentEditor(
         $this->getMailReceiver(),
         $actor->getPHID(),
         $command);
 
       // NOTE: We have to be careful about this because Facebook's
       // implementation jumps straight into handleAction() and will not have
       // a PhabricatorMetaMTAReceivedMail object.
       if ($this->receivedMail) {
+        $content_source = PhabricatorContentSource::newForSource(
+          PhabricatorContentSource::SOURCE_EMAIL,
+          array(
+            'id' => $this->receivedMail->getID(),
+          ));
+        $editor->setContentSource($content_source);
         $editor->setParentMessageID($this->receivedMail->getMessageID());
       }
       $editor->setMessage($body);
       $editor->setAddCC(($command != DifferentialAction::ACTION_RESIGN));
       $comment = $editor->save();
 
       return $comment->getID();
 
     } catch (Exception $ex) {
       $exception_mail = new DifferentialExceptionMail(
         $this->getMailReceiver(),
         $ex,
         $body);
 
       $exception_mail->setToPHIDs(array($this->getActor()->getPHID()));
       $exception_mail->send();
 
       throw $ex;
     }
   }
 
   private function unsubscribeUser(
     DifferentialRevision $revision,
     PhabricatorUser $user) {
 
     $revision->loadRelationships();
     DifferentialRevisionEditor::removeCCAndUpdateRevision(
       $revision,
       $user->getPHID(),
       $user->getPHID());
   }
 
 
 }
diff --git a/src/applications/differential/replyhandler/__init__.php b/src/applications/differential/replyhandler/__init__.php
index c857d47389..dcf9ca1d33 100644
--- a/src/applications/differential/replyhandler/__init__.php
+++ b/src/applications/differential/replyhandler/__init__.php
@@ -1,17 +1,18 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
 phutil_require_module('phabricator', 'applications/differential/constants/action');
 phutil_require_module('phabricator', 'applications/differential/editor/comment');
 phutil_require_module('phabricator', 'applications/differential/editor/revision');
 phutil_require_module('phabricator', 'applications/differential/mail/exception');
+phutil_require_module('phabricator', 'applications/metamta/contentsource/source');
 phutil_require_module('phabricator', 'applications/metamta/replyhandler/base');
 phutil_require_module('phabricator', 'infrastructure/env');
 
 
 phutil_require_source('DifferentialReplyHandler.php');
diff --git a/src/applications/differential/storage/comment/DifferentialComment.php b/src/applications/differential/storage/comment/DifferentialComment.php
index 674ea034f3..bb814240a5 100644
--- a/src/applications/differential/storage/comment/DifferentialComment.php
+++ b/src/applications/differential/storage/comment/DifferentialComment.php
@@ -1,39 +1,49 @@
 <?php
 
 /*
  * Copyright 2011 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 DifferentialComment extends DifferentialDAO {
 
   const METADATA_ADDED_REVIEWERS  = 'added-reviewers';
   const METADATA_ADDED_CCS        = 'added-ccs';
 
   protected $authorPHID;
   protected $revisionID;
   protected $action;
   protected $content;
   protected $cache;
   protected $metadata = array();
+  protected $contentSource;
 
   public function getConfiguration() {
     return array(
       self::CONFIG_SERIALIZATION => array(
         'metadata' => self::SERIALIZATION_JSON,
       ),
     ) + parent::getConfiguration();
   }
 
+  public function setContentSource(PhabricatorContentSource $content_source) {
+    $this->contentSource = $content_source->serialize();
+    return $this;
+  }
+
+  public function getContentSource() {
+    return PhabricatorContentSource::newFromSerialized($this->contentSource);
+  }
+
 }
diff --git a/src/applications/differential/storage/comment/__init__.php b/src/applications/differential/storage/comment/__init__.php
index 8f477dca99..dbdbe480a5 100644
--- a/src/applications/differential/storage/comment/__init__.php
+++ b/src/applications/differential/storage/comment/__init__.php
@@ -1,12 +1,13 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
 phutil_require_module('phabricator', 'applications/differential/storage/base');
+phutil_require_module('phabricator', 'applications/metamta/contentsource/source');
 
 
 phutil_require_source('DifferentialComment.php');
diff --git a/src/applications/differential/view/revisioncomment/DifferentialRevisionCommentView.php b/src/applications/differential/view/revisioncomment/DifferentialRevisionCommentView.php
index 74c7a853c6..64fb56ee0d 100644
--- a/src/applications/differential/view/revisioncomment/DifferentialRevisionCommentView.php
+++ b/src/applications/differential/view/revisioncomment/DifferentialRevisionCommentView.php
@@ -1,292 +1,300 @@
 <?php
 
 /*
  * Copyright 2011 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 DifferentialRevisionCommentView extends AphrontView {
 
   private $comment;
   private $handles;
   private $markupEngine;
   private $preview;
   private $inlines;
   private $changesets;
   private $target;
   private $commentNumber;
   private $user;
 
   public function setComment($comment) {
     $this->comment = $comment;
     return $this;
   }
 
   public function setHandles(array $handles) {
     $this->handles = $handles;
     return $this;
   }
 
   public function setMarkupEngine($markup_engine) {
     $this->markupEngine = $markup_engine;
     return $this;
   }
 
   public function setPreview($preview) {
     $this->preview = $preview;
     return $this;
   }
 
   public function setInlineComments(array $inline_comments) {
     $this->inlines = $inline_comments;
     return $this;
   }
 
   public function setChangesets(array $changesets) {
     // Ship these in sorted by getSortKey() and keyed by ID... or else!
     $this->changesets = $changesets;
     return $this;
   }
 
   public function setTargetDiff($target) {
     $this->target = $target;
   }
 
   public function setCommentNumber($comment_number) {
     $this->commentNumber = $comment_number;
     return $this;
   }
 
   public function setUser(PhabricatorUser $user) {
     $this->user = $user;
     return $this;
   }
 
   public function render() {
 
     if (!$this->user) {
       throw new Exception("Call setUser() before rendering!");
     }
 
     require_celerity_resource('phabricator-remarkup-css');
     require_celerity_resource('differential-revision-comment-css');
 
     $comment = $this->comment;
 
     $action = $comment->getAction();
 
     $action_class = 'differential-comment-action-'.phutil_escape_html($action);
 
     if ($this->preview) {
       $date = 'COMMENT PREVIEW';
     } else {
       $date = phabricator_datetime($comment->getDateCreated(), $this->user);
     }
 
-    $info = array($date);
+    $info = array();
+
+    $content_source = new PhabricatorContentSourceView();
+    $content_source->setContentSource($comment->getContentSource());
+    $content_source->setUser($this->user);
+    $info[] = $content_source->render();
+
+    $info[] = $date;
 
     $comment_anchor = null;
     $num = $this->commentNumber;
     if ($num && !$this->preview) {
       Javelin::initBehavior('phabricator-watch-anchor');
       $info[] = phutil_render_tag(
         'a',
         array(
           'name' => 'comment-'.$num,
           'href' => '#comment-'.$num,
         ),
         'Comment D'.$comment->getRevisionID().'#'.$num);
       $comment_anchor = 'anchor-comment-'.$num;
     }
 
-    $info = implode(' &middot; ', $info);
+    $info = implode(' &middot; ', array_filter($info));
 
     $author = $this->handles[$comment->getAuthorPHID()];
     $author_link = $author->renderLink();
 
     $verb = DifferentialAction::getActionPastTenseVerb($comment->getAction());
     $verb = phutil_escape_html($verb);
 
     $content = $comment->getContent();
     $head_content = null;
     if (strlen(rtrim($content))) {
       $title = "{$author_link} {$verb} this revision:";
       $cache = $comment->getCache();
       if (strlen($cache)) {
         $content = $cache;
       } else {
         $content = $this->markupEngine->markupText($content);
         if ($comment->getID()) {
           $comment->setCache($content);
 
           $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
           $comment->save();
           unset($unguarded);
         }
       }
       $content =
         '<div class="phabricator-remarkup">'.
           $content.
         '</div>';
     } else {
       $title = null;
       $head_content =
         '<div class="differential-comment-nocontent">'.
           "<p>{$author_link} {$verb} this revision.</p>".
         '</div>';
       $content = null;
     }
 
     if ($this->inlines) {
       $inline_render = array();
       $inlines = $this->inlines;
       $changesets = $this->changesets;
       $inlines_by_changeset = mgroup($inlines, 'getChangesetID');
       $inlines_by_changeset = array_select_keys(
         $inlines_by_changeset,
         array_keys($this->changesets));
       $inline_render[] = '<table class="differential-inline-summary">';
       foreach ($inlines_by_changeset as $changeset_id => $inlines) {
         $changeset = $changesets[$changeset_id];
         $inlines = msort($inlines, 'getLineNumber');
         $inline_render[] =
           '<tr>'.
             '<th colspan="2">'.
               phutil_escape_html($changeset->getFileName()).
             '</th>'.
           '</tr>';
         foreach ($inlines as $inline) {
           if (!$inline->getLineLength()) {
             $lines = $inline->getLineNumber();
           } else {
             $lines = $inline->getLineNumber()."\xE2\x80\x93".
                      ($inline->getLineNumber() + $inline->getLineLength());
           }
 
           if (!$this->target ||
               $changeset->getDiffID() === $this->target->getID()) {
             $lines = phutil_render_tag(
               'a',
               array(
                 'href' => '#inline-'.$inline->getID(),
                 'class' => 'num',
               ),
               $lines);
           }
 
           $inline_content = $inline->getContent();
           if (strlen($inline_content)) {
             $inline_cache = $inline->getCache();
             if ($inline_cache) {
               $inline_content = $inline_cache;
             } else {
               $inline_content = $this->markupEngine->markupText(
                 $inline_content);
               if ($inline->getID()) {
                 $inline->setCache($inline_content);
                 $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
                 $inline->save();
                 unset($unguarded);
               }
             }
           }
 
           $inline_render[] =
             '<tr>'.
               '<td class="inline-line-number">'.$lines.'</td>'.
               '<td>'.
                 '<div class="phabricator-remarkup">'.
                   $inline_content.
                 '</div>'.
               '</td>'.
             '</tr>';
         }
       }
       $inline_render[] = '</table>';
       $inline_render = implode("\n", $inline_render);
       $inline_render =
         '<div class="differential-inline-summary-section">'.
           'Inline Comments'.
         '</div>'.
         $inline_render;
     } else {
       $inline_render = null;
     }
 
     $background = null;
     $uri = $author->getImageURI();
     if ($uri) {
       $background = "background-image: url('{$uri}');";
     }
 
     $metadata_blocks = array();
     $metadata = $comment->getMetadata();
     $added_reviewers = idx(
       $metadata,
       DifferentialComment::METADATA_ADDED_REVIEWERS);
     if ($added_reviewers) {
       $reviewers = 'Added reviewers: '.$this->renderHandleList(
         $added_reviewers);
       $metadata_blocks[] = $reviewers;
     }
 
     $added_ccs = idx(
       $metadata,
       DifferentialComment::METADATA_ADDED_CCS);
     if ($added_ccs) {
       $ccs = 'Added CCs: '.$this->renderHandleList($added_ccs);
       $metadata_blocks[] = $ccs;
     }
 
     if ($metadata_blocks) {
       $metadata_blocks =
         '<div class="differential-comment-metadata">'.
           implode("\n", $metadata_blocks).
         '</div>';
     } else {
       $metadata_blocks = null;
     }
 
     return phutil_render_tag(
       'div',
       array(
         'class' => "differential-comment {$action_class}",
         'id'    => $comment_anchor,
       ),
       '<div class="differential-comment-head">'.
         '<span class="differential-comment-info">'.$info.'</span>'.
         '<span class="differential-comment-title">'.$title.'</span>'.
         '<div style="clear: both;"></div>'.
       '</div>'.
       '<div class="differential-comment-body" style="'.$background.'">'.
         '<div class="differential-comment-content">'.
           $head_content.
           $metadata_blocks.
           '<div class="differential-comment-core">'.
             $content.
           '</div>'.
           $inline_render.
         '</div>'.
+        $content_source->render().
       '</div>');
   }
 
   private function renderHandleList(array $phids) {
     $result = array();
     foreach ($phids as $phid) {
       $result[] = $this->handles[$phid]->renderLink();
     }
     return implode(', ', $result);
   }
 
 }
diff --git a/src/applications/differential/view/revisioncomment/__init__.php b/src/applications/differential/view/revisioncomment/__init__.php
index 8dee4bedd6..c09a1245b9 100644
--- a/src/applications/differential/view/revisioncomment/__init__.php
+++ b/src/applications/differential/view/revisioncomment/__init__.php
@@ -1,21 +1,22 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
 phutil_require_module('phabricator', 'aphront/writeguard');
 phutil_require_module('phabricator', 'applications/differential/constants/action');
 phutil_require_module('phabricator', 'applications/differential/storage/comment');
+phutil_require_module('phabricator', 'applications/metamta/contentsource/view');
 phutil_require_module('phabricator', 'infrastructure/celerity/api');
 phutil_require_module('phabricator', 'infrastructure/javelin/api');
 phutil_require_module('phabricator', 'view/base');
 phutil_require_module('phabricator', 'view/utils');
 
 phutil_require_module('phutil', 'markup');
 phutil_require_module('phutil', 'utils');
 
 
 phutil_require_source('DifferentialRevisionCommentView.php');
diff --git a/src/applications/maniphest/controller/taskedit/ManiphestTaskEditController.php b/src/applications/maniphest/controller/taskedit/ManiphestTaskEditController.php
index c181e71998..9181ca03c2 100644
--- a/src/applications/maniphest/controller/taskedit/ManiphestTaskEditController.php
+++ b/src/applications/maniphest/controller/taskedit/ManiphestTaskEditController.php
@@ -1,483 +1,490 @@
 <?php
 
 /*
  * Copyright 2011 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.
  */
 
 /**
  * @group maniphest
  */
 class ManiphestTaskEditController extends ManiphestController {
 
   private $id;
 
   public function willProcessRequest(array $data) {
     $this->id = idx($data, 'id');
   }
 
   public function processRequest() {
 
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $files = array();
     $parent_task = null;
     $template_id = null;
 
     if ($this->id) {
       $task = id(new ManiphestTask())->load($this->id);
       if (!$task) {
         return new Aphront404Response();
       }
     } else {
       $task = new ManiphestTask();
       $task->setPriority(ManiphestTaskPriority::PRIORITY_TRIAGE);
       $task->setAuthorPHID($user->getPHID());
 
       // These allow task creation with defaults.
       if (!$request->isFormPost()) {
         $task->setTitle($request->getStr('title'));
 
         $default_projects = $request->getStr('projects');
         if ($default_projects) {
           $task->setProjectPHIDs(explode(';', $default_projects));
         }
       }
 
       $file_phids = $request->getArr('files', array());
       if (!$file_phids) {
         // Allow a single 'file' key instead, mostly since Mac OS X urlencodes
         // square brackets in URLs when passed to 'open', so you can't 'open'
         // a URL like '?files[]=xyz' and have PHP interpret it correctly.
         $phid = $request->getStr('file');
         if ($phid) {
           $file_phids = array($phid);
         }
       }
 
       if ($file_phids) {
         $files = id(new PhabricatorFile())->loadAllWhere(
           'phid IN (%Ls)',
           $file_phids);
       }
 
       $template_id = $request->getInt('template');
 
       // You can only have a parent task if you're creating a new task.
       $parent_id = $request->getInt('parent');
       if ($parent_id) {
         $parent_task = id(new ManiphestTask())->load($parent_id);
       }
     }
 
     $errors = array();
     $e_title = true;
 
     $extensions = ManiphestTaskExtensions::newExtensions();
     $aux_fields = $extensions->getAuxiliaryFieldSpecifications();
 
     if ($request->isFormPost()) {
 
       $changes = array();
 
       $new_title = $request->getStr('title');
       $new_desc = $request->getStr('description');
       $new_status = $request->getStr('status');
 
       $workflow = '';
 
       if ($task->getID()) {
         if ($new_title != $task->getTitle()) {
           $changes[ManiphestTransactionType::TYPE_TITLE] = $new_title;
         }
         if ($new_desc != $task->getDescription()) {
           $changes[ManiphestTransactionType::TYPE_DESCRIPTION] = $new_desc;
         }
         if ($new_status != $task->getStatus()) {
           $changes[ManiphestTransactionType::TYPE_STATUS] = $new_status;
         }
       } else {
         $task->setTitle($new_title);
         $task->setDescription($new_desc);
         $changes[ManiphestTransactionType::TYPE_STATUS] =
           ManiphestTaskStatus::STATUS_OPEN;
 
         $workflow = 'create';
       }
 
       $owner_tokenizer = $request->getArr('assigned_to');
       $owner_phid = reset($owner_tokenizer);
 
       if (!strlen($new_title)) {
         $e_title = 'Required';
         $errors[] = 'Title is required.';
       }
 
       foreach ($aux_fields as $aux_field) {
         $aux_field->setValueFromRequest($request);
 
         if ($aux_field->isRequired() && !strlen($aux_field->getValue())) {
           $errors[] = $aux_field->getLabel() . ' is required.';
           $aux_field->setError('Required');
         }
 
         if (strlen($aux_field->getValue())) {
           try {
             $aux_field->validate();
           } catch (Exception $e) {
             $errors[] = $e->getMessage();
             $aux_field->setError('Invalid');
           }
         }
       }
 
       if ($errors) {
         $task->setPriority($request->getInt('priority'));
         $task->setOwnerPHID($owner_phid);
         $task->setCCPHIDs($request->getArr('cc'));
         $task->setProjectPHIDs($request->getArr('projects'));
       } else {
         if ($request->getInt('priority') != $task->getPriority()) {
           $changes[ManiphestTransactionType::TYPE_PRIORITY] =
             $request->getInt('priority');
         }
 
         if ($owner_phid != $task->getOwnerPHID()) {
           $changes[ManiphestTransactionType::TYPE_OWNER] = $owner_phid;
         }
 
         if ($request->getArr('cc') != $task->getCCPHIDs()) {
           $changes[ManiphestTransactionType::TYPE_CCS] = $request->getArr('cc');
         }
 
         $new_proj_arr = $request->getArr('projects');
         $new_proj_arr = array_values($new_proj_arr);
         sort($new_proj_arr);
 
         $cur_proj_arr = $task->getProjectPHIDs();
         $cur_proj_arr = array_values($cur_proj_arr);
         sort($cur_proj_arr);
 
         if ($new_proj_arr != $cur_proj_arr) {
           $changes[ManiphestTransactionType::TYPE_PROJECTS] = $new_proj_arr;
         }
 
         if ($files) {
           $file_map = mpull($files, 'getPHID');
           $file_map = array_fill_keys($file_map, true);
           $changes[ManiphestTransactionType::TYPE_ATTACH] = array(
             PhabricatorPHIDConstants::PHID_TYPE_FILE => $file_map,
           );
         }
 
+        $content_source = PhabricatorContentSource::newForSource(
+          PhabricatorContentSource::SOURCE_WEB,
+          array(
+            'ip' => $request->getRemoteAddr(),
+          ));
+
         $template = new ManiphestTransaction();
         $template->setAuthorPHID($user->getPHID());
+        $template->setContentSource($content_source);
         $transactions = array();
 
         foreach ($changes as $type => $value) {
           $transaction = clone $template;
           $transaction->setTransactionType($type);
           $transaction->setNewValue($value);
           $transactions[] = $transaction;
         }
 
         if ($transactions) {
           $editor = new ManiphestTransactionEditor();
           $editor->applyTransactions($task, $transactions);
         }
 
         // TODO: Capture auxiliary field changes in a transaction
         foreach ($aux_fields as $aux_field) {
           $task->setAuxiliaryAttribute(
             $aux_field->getAuxiliaryKey(),
             $aux_field->getValueForStorage()
           );
         }
 
         if ($parent_task) {
           $type_task = PhabricatorPHIDConstants::PHID_TYPE_TASK;
 
           // NOTE: It's safe to simply apply this transaction without doing
           // cycle detection because we know the new task has no children.
           $new_value = $parent_task->getAttached();
           $new_value[$type_task][$task->getPHID()] = true;
 
           $parent_xaction = clone $template;
           $attach_type = ManiphestTransactionType::TYPE_ATTACH;
           $parent_xaction->setTransactionType($attach_type);
           $parent_xaction->setNewValue($new_value);
 
           $editor = new ManiphestTransactionEditor();
           $editor->applyTransactions($parent_task, array($parent_xaction));
 
           $workflow = $parent_task->getID();
         }
 
         $redirect_uri = '/T'.$task->getID();
 
         if ($workflow) {
           $redirect_uri .= '?workflow='.$workflow;
         }
 
         return id(new AphrontRedirectResponse())
           ->setURI($redirect_uri);
       }
     } else {
       if (!$task->getID()) {
         $task->setCCPHIDs(array(
           $user->getPHID(),
         ));
         if ($template_id) {
           $template_task = id(new ManiphestTask())->load($template_id);
           if ($template_task) {
             $task->setCCPHIDs($template_task->getCCPHIDs());
             $task->setProjectPHIDs($template_task->getProjectPHIDs());
             $task->setOwnerPHID($template_task->getOwnerPHID());
           }
         }
       }
     }
 
     $phids = array_merge(
       array($task->getOwnerPHID()),
       $task->getCCPHIDs(),
       $task->getProjectPHIDs());
 
     if ($parent_task) {
       $phids[] = $parent_task->getPHID();
     }
 
     $phids = array_filter($phids);
     $phids = array_unique($phids);
 
     $handles = id(new PhabricatorObjectHandleData($phids))
       ->loadHandles($phids);
 
     $tvalues = mpull($handles, 'getFullName', 'getPHID');
 
     $error_view = null;
     if ($errors) {
       $error_view = new AphrontErrorView();
       $error_view->setErrors($errors);
       $error_view->setTitle('Form Errors');
     }
 
     $priority_map = ManiphestTaskPriority::getTaskPriorityMap();
 
     if ($task->getOwnerPHID()) {
       $assigned_value = array(
         $task->getOwnerPHID() => $handles[$task->getOwnerPHID()]->getFullName(),
       );
     } else {
       $assigned_value = array();
     }
 
     if ($task->getCCPHIDs()) {
       $cc_value = array_select_keys($tvalues, $task->getCCPHIDs());
     } else {
       $cc_value = array();
     }
 
     if ($task->getProjectPHIDs()) {
       $projects_value = array_select_keys($tvalues, $task->getProjectPHIDs());
     } else {
       $projects_value = array();
     }
 
     $cancel_id = nonempty($task->getID(), $template_id);
     if ($cancel_id) {
       $cancel_uri = '/T'.$cancel_id;
     } else {
       $cancel_uri = '/maniphest/';
     }
 
     if ($task->getID()) {
       $button_name = 'Save Task';
       $header_name = 'Edit Task';
     } else if ($parent_task) {
       $cancel_uri = '/T'.$parent_task->getID();
       $button_name = 'Create Task';
       $header_name = 'Create New Subtask';
     } else {
       $button_name = 'Create Task';
       $header_name = 'Create New Task';
     }
 
     $project_tokenizer_id = celerity_generate_unique_node_id();
 
     $form = new AphrontFormView();
     $form
       ->setUser($user)
       ->setAction($request->getRequestURI()->getPath())
       ->addHiddenInput('template', $template_id);
 
     if ($parent_task) {
       $form
         ->appendChild(
           id(new AphrontFormStaticControl())
             ->setLabel('Parent Task')
             ->setValue($handles[$parent_task->getPHID()]->getFullName()))
         ->addHiddenInput('parent', $parent_task->getID());
     }
 
     $form
       ->appendChild(
         id(new AphrontFormTextAreaControl())
           ->setLabel('Title')
           ->setName('title')
           ->setError($e_title)
           ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT)
           ->setValue($task->getTitle()));
 
     if ($task->getID()) {
       // Only show this in "edit" mode, not "create" mode, since creating a
       // non-open task is kind of silly and it would just clutter up the
       // "create" interface.
       $form
         ->appendChild(
           id(new AphrontFormSelectControl())
             ->setLabel('Status')
             ->setName('status')
             ->setValue($task->getStatus())
             ->setOptions(ManiphestTaskStatus::getTaskStatusMap()));
     }
 
     $form
       ->appendChild(
         id(new AphrontFormTokenizerControl())
           ->setLabel('Assigned To')
           ->setName('assigned_to')
           ->setValue($assigned_value)
           ->setDatasource('/typeahead/common/users/')
           ->setLimit(1))
       ->appendChild(
         id(new AphrontFormTokenizerControl())
           ->setLabel('CC')
           ->setName('cc')
           ->setValue($cc_value)
           ->setDatasource('/typeahead/common/mailable/'))
       ->appendChild(
         id(new AphrontFormSelectControl())
           ->setLabel('Priority')
           ->setName('priority')
           ->setOptions($priority_map)
           ->setValue($task->getPriority()))
       ->appendChild(
         id(new AphrontFormTokenizerControl())
           ->setLabel('Projects')
           ->setName('projects')
           ->setValue($projects_value)
           ->setID($project_tokenizer_id)
           ->setCaption(
             javelin_render_tag(
               'a',
               array(
                 'href'        => '/project/create/',
                 'mustcapture' => true,
                 'sigil'       => 'project-create',
               ),
               'Create New Project'))
           ->setDatasource('/typeahead/common/projects/'));
 
     $attributes = $task->loadAuxiliaryAttributes();
     $attributes = mpull($attributes, 'getValue', 'getName');
 
     foreach ($aux_fields as $aux_field) {
       if (!$request->isFormPost()) {
         $attribute = null;
 
         if (isset($attributes[$aux_field->getAuxiliaryKey()])) {
           $attribute = $attributes[$aux_field->getAuxiliaryKey()];
           $aux_field->setValueFromStorage($attribute);
         }
       }
 
       if ($aux_field->isRequired() && !$aux_field->getError()
         && !$aux_field->getValue()) {
         $aux_field->setError(true);
       }
 
       $aux_control = $aux_field->renderControl();
 
       $form->appendChild($aux_control);
     }
 
     require_celerity_resource('aphront-error-view-css');
 
     Javelin::initBehavior('maniphest-project-create', array(
       'tokenizerID' => $project_tokenizer_id,
     ));
 
     if ($files) {
       $file_display = array();
       foreach ($files as $file) {
         $file_display[] = phutil_escape_html($file->getName());
       }
       $file_display = implode('<br />', $file_display);
 
       $form->appendChild(
         id(new AphrontFormMarkupControl())
           ->setLabel('Files')
           ->setValue($file_display));
 
       foreach ($files as $ii => $file) {
         $form->addHiddenInput('files['.$ii.']', $file->getPHID());
       }
     }
 
     $email_create = PhabricatorEnv::getEnvConfig(
       'metamta.maniphest.public-create-email');
     $email_hint = null;
     if (!$task->getID() && $email_create) {
       $email_hint = 'You can also create tasks by sending an email to: '.
                     '<tt>'.phutil_escape_html($email_create).'</tt>';
     }
 
     $panel_id = celerity_generate_unique_node_id();
 
     $form
       ->appendChild(
         id(new AphrontFormTextAreaControl())
           ->setLabel('Description')
           ->setName('description')
           ->setCaption($email_hint)
           ->setValue($task->getDescription()))
       ->appendChild(
         id(new AphrontFormDragAndDropUploadControl())
           ->setLabel('Attached Files')
           ->setName('files')
           ->setDragAndDropTarget($panel_id)
           ->setActivatedClass('aphront-panel-view-drag-and-drop'))
       ->appendChild(
         id(new AphrontFormSubmitControl())
           ->addCancelButton($cancel_uri)
           ->setValue($button_name));
 
     $panel = new AphrontPanelView();
     $panel->setWidth(AphrontPanelView::WIDTH_FULL);
     $panel->setHeader($header_name);
     $panel->setID($panel_id);
     $panel->appendChild($form);
 
     return $this->buildStandardPageResponse(
       array(
         $error_view,
         $panel,
       ),
       array(
         'title' => 'Create Task',
       ));
   }
 }
diff --git a/src/applications/maniphest/controller/taskedit/__init__.php b/src/applications/maniphest/controller/taskedit/__init__.php
index 83478c7560..79f39fff09 100644
--- a/src/applications/maniphest/controller/taskedit/__init__.php
+++ b/src/applications/maniphest/controller/taskedit/__init__.php
@@ -1,41 +1,42 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
 phutil_require_module('phabricator', 'aphront/response/404');
 phutil_require_module('phabricator', 'aphront/response/redirect');
 phutil_require_module('phabricator', 'applications/files/storage/file');
 phutil_require_module('phabricator', 'applications/maniphest/constants/priority');
 phutil_require_module('phabricator', 'applications/maniphest/constants/status');
 phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype');
 phutil_require_module('phabricator', 'applications/maniphest/controller/base');
 phutil_require_module('phabricator', 'applications/maniphest/editor/transaction');
 phutil_require_module('phabricator', 'applications/maniphest/extensions/base');
 phutil_require_module('phabricator', 'applications/maniphest/storage/task');
 phutil_require_module('phabricator', 'applications/maniphest/storage/transaction');
+phutil_require_module('phabricator', 'applications/metamta/contentsource/source');
 phutil_require_module('phabricator', 'applications/phid/constants');
 phutil_require_module('phabricator', 'applications/phid/handle/data');
 phutil_require_module('phabricator', 'infrastructure/celerity/api');
 phutil_require_module('phabricator', 'infrastructure/env');
 phutil_require_module('phabricator', 'infrastructure/javelin/api');
 phutil_require_module('phabricator', 'infrastructure/javelin/markup');
 phutil_require_module('phabricator', 'view/form/base');
 phutil_require_module('phabricator', 'view/form/control/draganddropupload');
 phutil_require_module('phabricator', 'view/form/control/markup');
 phutil_require_module('phabricator', 'view/form/control/select');
 phutil_require_module('phabricator', 'view/form/control/static');
 phutil_require_module('phabricator', 'view/form/control/submit');
 phutil_require_module('phabricator', 'view/form/control/textarea');
 phutil_require_module('phabricator', 'view/form/control/tokenizer');
 phutil_require_module('phabricator', 'view/form/error');
 phutil_require_module('phabricator', 'view/layout/panel');
 
 phutil_require_module('phutil', 'markup');
 phutil_require_module('phutil', 'utils');
 
 
 phutil_require_source('ManiphestTaskEditController.php');
diff --git a/src/applications/maniphest/controller/transactionsave/ManiphestTransactionSaveController.php b/src/applications/maniphest/controller/transactionsave/ManiphestTransactionSaveController.php
index 0e5aae986e..7d130c5d06 100644
--- a/src/applications/maniphest/controller/transactionsave/ManiphestTransactionSaveController.php
+++ b/src/applications/maniphest/controller/transactionsave/ManiphestTransactionSaveController.php
@@ -1,237 +1,247 @@
 <?php
 
 /*
  * Copyright 2011 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.
  */
 
 /**
  * @group maniphest
  */
 class ManiphestTransactionSaveController extends ManiphestController {
 
   public function processRequest() {
     $request = $this->getRequest();
     $user = $request->getUser();
 
     $task = id(new ManiphestTask())->load($request->getStr('taskID'));
     if (!$task) {
       return new Aphront404Response();
     }
 
     $transactions = array();
 
     $action = $request->getStr('action');
 
     // If we have drag-and-dropped files, attach them first in a separate
     // transaction. These can come in on any transaction type, which is why we
     // handle them separately.
     $files = array();
 
     // Look for drag-and-drop uploads first.
     $file_phids = $request->getArr('files');
     if ($file_phids) {
       $files = id(new PhabricatorFile())->loadAllWhere(
         'phid in (%Ls)',
         $file_phids);
     }
 
     // This means "attach a file" even though we store other types of data
     // as 'attached'.
     if ($action == ManiphestTransactionType::TYPE_ATTACH) {
       if (!empty($_FILES['file'])) {
         $err = idx($_FILES['file'], 'error');
         if ($err != UPLOAD_ERR_NO_FILE) {
           $file = PhabricatorFile::newFromPHPUpload(
             $_FILES['file'],
             array(
               'authorPHID' => $user->getPHID(),
             ));
           $files[] = $file;
         }
       }
     }
 
     // If we had explicit or drag-and-drop files, create a transaction
     // for those before we deal with whatever else might have happened.
     $file_transaction = null;
     if ($files) {
       $files = mpull($files, 'getPHID', 'getPHID');
       $new = $task->getAttached();
       foreach ($files as $phid) {
         if (empty($new[PhabricatorPHIDConstants::PHID_TYPE_FILE])) {
           $new[PhabricatorPHIDConstants::PHID_TYPE_FILE] = array();
         }
         $new[PhabricatorPHIDConstants::PHID_TYPE_FILE][$phid] = array();
       }
       $transaction = new ManiphestTransaction();
       $transaction
         ->setAuthorPHID($user->getPHID())
         ->setTransactionType(ManiphestTransactionType::TYPE_ATTACH);
       $transaction->setNewValue($new);
       $transactions[] = $transaction;
       $file_transaction = $transaction;
     }
 
     // Compute new CCs added by @mentions. Several things can cause CCs to
     // be added as side effects: mentions, explicit CCs, users who aren't
     // CC'd interacting with the task, and ownership changes. We build up a
     // list of all the CCs and then construct a transaction for them at the
     // end if necessary.
     $added_ccs = PhabricatorMarkupEngine::extractPHIDsFromMentions(
       array(
         $request->getStr('comments'),
       ));
 
     $cc_transaction = new ManiphestTransaction();
     $cc_transaction
       ->setAuthorPHID($user->getPHID())
       ->setTransactionType(ManiphestTransactionType::TYPE_CCS);
     $force_cc_transaction = false;
 
     $transaction = new ManiphestTransaction();
     $transaction
       ->setAuthorPHID($user->getPHID())
       ->setComments($request->getStr('comments'))
       ->setTransactionType($action);
 
     switch ($action) {
       case ManiphestTransactionType::TYPE_STATUS:
         $transaction->setNewValue($request->getStr('resolution'));
         break;
       case ManiphestTransactionType::TYPE_OWNER:
         $assign_to = $request->getArr('assign_to');
         $assign_to = reset($assign_to);
         $transaction->setNewValue($assign_to);
         break;
       case ManiphestTransactionType::TYPE_PROJECTS:
         $projects = $request->getArr('projects');
         $projects = array_merge($projects, $task->getProjectPHIDs());
         $projects = array_filter($projects);
         $projects = array_unique($projects);
         $transaction->setNewValue($projects);
         break;
       case ManiphestTransactionType::TYPE_CCS:
         // Accumulate the new explicit CCs into the array that we'll add in
         // the CC transaction later.
         $added_ccs = array_merge($added_ccs, $request->getArr('ccs'));
 
         // Transfer any comments over to the CC transaction.
         $cc_transaction->setComments($transaction->getComments());
 
         // Make sure we include this transaction, even if the user didn't
         // actually add any CC's, because we'll discard their comment otherwise.
         $force_cc_transaction = true;
 
         // Throw away the primary transaction.
         $transaction = null;
         break;
       case ManiphestTransactionType::TYPE_PRIORITY:
         $transaction->setNewValue($request->getInt('priority'));
         break;
       case ManiphestTransactionType::TYPE_NONE:
       case ManiphestTransactionType::TYPE_ATTACH:
         // If we have a file transaction, just get rid of this secondary
         // transaction and put the comments on it instead.
         if ($file_transaction) {
           $file_transaction->setComments($transaction->getComments());
           $transaction = null;
         }
         break;
       default:
         throw new Exception('unknown action');
     }
 
     if ($transaction) {
       $transactions[] = $transaction;
     }
 
     // When you interact with a task, we add you to the CC list so you get
     // further updates, and possibly assign the task to you if you took an
     // ownership action (closing it) but it's currently unowned. We also move
     // previous owners to CC if ownership changes. Detect all these conditions
     // and create side-effect transactions for them.
 
     $implicitly_claimed = false;
     switch ($action) {
       case ManiphestTransactionType::TYPE_OWNER:
         if ($task->getOwnerPHID() == $transaction->getNewValue()) {
           // If this is actually no-op, don't generate the side effect.
           break;
         }
         // Otherwise, when a task is reassigned, move the previous owner to CC.
         $added_ccs[] = $task->getOwnerPHID();
         break;
       case ManiphestTransactionType::TYPE_STATUS:
         if (!$task->getOwnerPHID() &&
             $request->getStr('resolution') !=
             ManiphestTaskStatus::STATUS_OPEN) {
           // Closing an unassigned task. Assign the user as the owner of
           // this task.
           $assign = new ManiphestTransaction();
           $assign->setAuthorPHID($user->getPHID());
           $assign->setTransactionType(ManiphestTransactionType::TYPE_OWNER);
           $assign->setNewValue($user->getPHID());
           $transactions[] = $assign;
 
           $implicitly_claimed = true;
         }
         break;
     }
 
 
     $user_owns_task = false;
     if ($implicitly_claimed) {
       $user_owns_task = true;
     } else {
       if ($action == ManiphestTransactionType::TYPE_OWNER) {
         if ($transaction->getNewValue() == $user->getPHID()) {
           $user_owns_task = true;
         }
       } else if ($task->getOwnerPHID() == $user->getPHID()) {
         $user_owns_task = true;
       }
     }
 
     if (!$user_owns_task) {
       // If we aren't making the user the new task owner and they aren't the
       // existing task owner, add them to CC.
       $added_ccs[] = $user->getPHID();
     }
 
     if ($added_ccs || $force_cc_transaction) {
       // We've added CCs, so include a CC transaction. It's safe to do this even
       // if we're just "adding" CCs which already exist, because the
       // ManiphestTransactionEditor is smart enough to ignore them.
       $all_ccs = array_merge($task->getCCPHIDs(), $added_ccs);
       $cc_transaction->setNewValue($all_ccs);
       $transactions[] = $cc_transaction;
     }
 
+    $content_source = PhabricatorContentSource::newForSource(
+      PhabricatorContentSource::SOURCE_WEB,
+      array(
+        'ip' => $request->getRemoteAddr(),
+      ));
+
+    foreach ($transactions as $transaction) {
+      $transaction->setContentSource($content_source);
+    }
+
     $editor = new ManiphestTransactionEditor();
     $editor->applyTransactions($task, $transactions);
 
     $draft = id(new PhabricatorDraft())->loadOneWhere(
       'authorPHID = %s AND draftKey = %s',
       $user->getPHID(),
       $task->getPHID());
     if ($draft) {
       $draft->delete();
     }
 
     return id(new AphrontRedirectResponse())
       ->setURI('/T'.$task->getID());
   }
 
 }
diff --git a/src/applications/maniphest/controller/transactionsave/__init__.php b/src/applications/maniphest/controller/transactionsave/__init__.php
index 2967a0a491..43e8b529a3 100644
--- a/src/applications/maniphest/controller/transactionsave/__init__.php
+++ b/src/applications/maniphest/controller/transactionsave/__init__.php
@@ -1,25 +1,26 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
 phutil_require_module('phabricator', 'aphront/response/404');
 phutil_require_module('phabricator', 'aphront/response/redirect');
 phutil_require_module('phabricator', 'applications/draft/storage/draft');
 phutil_require_module('phabricator', 'applications/files/storage/file');
 phutil_require_module('phabricator', 'applications/maniphest/constants/status');
 phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype');
 phutil_require_module('phabricator', 'applications/maniphest/controller/base');
 phutil_require_module('phabricator', 'applications/maniphest/editor/transaction');
 phutil_require_module('phabricator', 'applications/maniphest/storage/task');
 phutil_require_module('phabricator', 'applications/maniphest/storage/transaction');
 phutil_require_module('phabricator', 'applications/markup/engine');
+phutil_require_module('phabricator', 'applications/metamta/contentsource/source');
 phutil_require_module('phabricator', 'applications/phid/constants');
 
 phutil_require_module('phutil', 'utils');
 
 
 phutil_require_source('ManiphestTransactionSaveController.php');
diff --git a/src/applications/maniphest/replyhandler/ManiphestReplyHandler.php b/src/applications/maniphest/replyhandler/ManiphestReplyHandler.php
index 374da6c953..6bb47a8f8e 100644
--- a/src/applications/maniphest/replyhandler/ManiphestReplyHandler.php
+++ b/src/applications/maniphest/replyhandler/ManiphestReplyHandler.php
@@ -1,151 +1,158 @@
 <?php
 
 /*
  * Copyright 2011 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.
  */
 
 /**
  * @group maniphest
  */
 class ManiphestReplyHandler extends PhabricatorMailReplyHandler {
 
   public function validateMailReceiver($mail_receiver) {
     if (!($mail_receiver instanceof ManiphestTask)) {
       throw new Exception("Mail receiver is not a ManiphestTask!");
     }
   }
 
   public function getPrivateReplyHandlerEmailAddress(
     PhabricatorObjectHandle $handle) {
     return $this->getDefaultPrivateReplyHandlerEmailAddress($handle, 'T');
   }
 
   public function getPublicReplyHandlerEmailAddress() {
     return $this->getDefaultPublicReplyHandlerEmailAddress('T');
   }
 
   public function getReplyHandlerDomain() {
     return PhabricatorEnv::getEnvConfig(
       'metamta.maniphest.reply-handler-domain');
   }
 
   public function getReplyHandlerInstructions() {
     if ($this->supportsReplies()) {
       return "Reply to comment or attach files, or !close, !claim, or ".
              "!unsubscribe.";
     } else {
       return null;
     }
   }
 
   public function receiveEmail(PhabricatorMetaMTAReceivedMail $mail) {
 
     // NOTE: We'll drop in here on both the "reply to a task" and "create a
     // new task" workflows! Make sure you test both if you make changes!
 
     $task = $this->getMailReceiver();
 
     $is_new_task = !$task->getID();
 
     $user = $this->getActor();
 
     $body = $mail->getCleanTextBody();
     $body = trim($body);
 
     $xactions = array();
 
+    $content_source = PhabricatorContentSource::newForSource(
+      PhabricatorContentSource::SOURCE_EMAIL,
+      array(
+        'id' => $mail->getID(),
+      ));
+
     $template = new ManiphestTransaction();
+    $template->setContentSource($content_source);
     $template->setAuthorPHID($user->getPHID());
 
     if ($is_new_task) {
       // If this is a new task, create a "User created this task." transaction
       // and then set the title and description.
       $xaction = clone $template;
       $xaction->setTransactionType(ManiphestTransactionType::TYPE_STATUS);
       $xaction->setNewValue(ManiphestTaskStatus::STATUS_OPEN);
       $xactions[] = $xaction;
 
       $task->setAuthorPHID($user->getPHID());
       $task->setTitle(nonempty($mail->getSubject(), 'Untitled Task'));
       $task->setDescription($body);
 
     } else {
       $lines = explode("\n", trim($body));
       $first_line = head($lines);
 
       $command = null;
       $matches = null;
       if (preg_match('/^!(\w+)/', $first_line, $matches)) {
         $lines = array_slice($lines, 1);
         $body = implode("\n", $lines);
         $body = trim($body);
 
         $command = $matches[1];
       }
 
       $ttype = ManiphestTransactionType::TYPE_NONE;
       $new_value = null;
       switch ($command) {
         case 'close':
           $ttype = ManiphestTransactionType::TYPE_STATUS;
           $new_value = ManiphestTaskStatus::STATUS_CLOSED_RESOLVED;
           break;
         case 'claim':
           $ttype = ManiphestTransactionType::TYPE_OWNER;
           $new_value = $user->getPHID();
           break;
         case 'unsubscribe':
           $ttype = ManiphestTransactionType::TYPE_CCS;
           $ccs = $task->getCCPHIDs();
           foreach ($ccs as $k => $phid) {
             if ($phid == $user->getPHID()) {
               unset($ccs[$k]);
             }
           }
           $new_value = array_values($ccs);
           break;
       }
 
       $xaction = clone $template;
       $xaction->setTransactionType($ttype);
       $xaction->setNewValue($new_value);
       $xaction->setComments($body);
 
       $xactions[] = $xaction;
     }
 
     // TODO: We should look at CCs on the mail and add them as CCs.
 
     $files = $mail->getAttachments();
     if ($files) {
       $file_xaction = clone $template;
       $file_xaction->setTransactionType(ManiphestTransactionType::TYPE_ATTACH);
 
       $phid_type = PhabricatorPHIDConstants::PHID_TYPE_FILE;
       $new = $task->getAttached();
       foreach ($files as $file_phid) {
         $new[$phid_type][$file_phid] = array();
       }
 
       $file_xaction->setNewValue($new);
       $xactions[] = $file_xaction;
     }
 
     $editor = new ManiphestTransactionEditor();
     $editor->setParentMessageID($mail->getMessageID());
     $editor->applyTransactions($task, $xactions);
   }
 
 }
diff --git a/src/applications/maniphest/replyhandler/__init__.php b/src/applications/maniphest/replyhandler/__init__.php
index 376777c40f..0f8e28bc54 100644
--- a/src/applications/maniphest/replyhandler/__init__.php
+++ b/src/applications/maniphest/replyhandler/__init__.php
@@ -1,20 +1,21 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
 phutil_require_module('phabricator', 'applications/maniphest/constants/status');
 phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype');
 phutil_require_module('phabricator', 'applications/maniphest/editor/transaction');
 phutil_require_module('phabricator', 'applications/maniphest/storage/transaction');
+phutil_require_module('phabricator', 'applications/metamta/contentsource/source');
 phutil_require_module('phabricator', 'applications/metamta/replyhandler/base');
 phutil_require_module('phabricator', 'applications/phid/constants');
 phutil_require_module('phabricator', 'infrastructure/env');
 
 phutil_require_module('phutil', 'utils');
 
 
 phutil_require_source('ManiphestReplyHandler.php');
diff --git a/src/applications/maniphest/storage/transaction/ManiphestTransaction.php b/src/applications/maniphest/storage/transaction/ManiphestTransaction.php
index 153e46f375..fbe5ded867 100644
--- a/src/applications/maniphest/storage/transaction/ManiphestTransaction.php
+++ b/src/applications/maniphest/storage/transaction/ManiphestTransaction.php
@@ -1,108 +1,117 @@
 <?php
 
 /*
  * Copyright 2011 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.
  */
 
 /**
  * @group maniphest
  */
 class ManiphestTransaction extends ManiphestDAO {
 
   protected $taskID;
   protected $authorPHID;
   protected $transactionType;
   protected $oldValue;
   protected $newValue;
   protected $comments;
   protected $cache;
   protected $metadata = array();
+  protected $contentSource;
 
   public function getConfiguration() {
     return array(
       self::CONFIG_SERIALIZATION => array(
         'oldValue' => self::SERIALIZATION_JSON,
         'newValue' => self::SERIALIZATION_JSON,
         'metadata' => self::SERIALIZATION_JSON,
       ),
     ) + parent::getConfiguration();
   }
 
   public function extractPHIDs() {
     $phids = array();
 
     switch ($this->getTransactionType()) {
       case ManiphestTransactionType::TYPE_CCS:
       case ManiphestTransactionType::TYPE_PROJECTS:
         foreach ($this->getOldValue() as $phid) {
           $phids[] = $phid;
         }
         foreach ($this->getNewValue() as $phid) {
           $phids[] = $phid;
         }
         break;
       case ManiphestTransactionType::TYPE_OWNER:
         $phids[] = $this->getOldValue();
         $phids[] = $this->getNewValue();
         break;
       case ManiphestTransactionType::TYPE_ATTACH:
         $old = $this->getOldValue();
         $new = $this->getNewValue();
         if (!is_array($old)) {
           $old = array();
         }
         if (!is_array($new)) {
           $new = array();
         }
         $val = array_merge(array_values($old), array_values($new));
         foreach ($val as $stuff) {
           foreach ($stuff as $phid => $ignored) {
             $phids[] = $phid;
           }
         }
         break;
     }
 
     $phids[] = $this->getAuthorPHID();
 
     return $phids;
   }
 
   public function canGroupWith($target) {
     if ($target->getAuthorPHID() != $this->getAuthorPHID()) {
       return false;
     }
     if ($target->hasComments() && $this->hasComments()) {
       return false;
     }
     $ttime = $target->getDateCreated();
     $stime = $this->getDateCreated();
     if (abs($stime - $ttime) > 60) {
       return false;
     }
 
     if ($target->getTransactionType() == $this->getTransactionType()) {
       return false;
     }
 
     return true;
   }
 
   public function hasComments() {
     return (bool)strlen(trim($this->getComments()));
   }
 
+  public function setContentSource(PhabricatorContentSource $content_source) {
+    $this->contentSource = $content_source->serialize();
+    return $this;
+  }
+
+  public function getContentSource() {
+    return PhabricatorContentSource::newFromSerialized($this->contentSource);
+  }
 
 }
diff --git a/src/applications/maniphest/storage/transaction/__init__.php b/src/applications/maniphest/storage/transaction/__init__.php
index c4e475868c..f095c1223e 100644
--- a/src/applications/maniphest/storage/transaction/__init__.php
+++ b/src/applications/maniphest/storage/transaction/__init__.php
@@ -1,13 +1,14 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
 phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype');
 phutil_require_module('phabricator', 'applications/maniphest/storage/base');
+phutil_require_module('phabricator', 'applications/metamta/contentsource/source');
 
 
 phutil_require_source('ManiphestTransaction.php');
diff --git a/src/applications/maniphest/view/transactiondetail/ManiphestTransactionDetailView.php b/src/applications/maniphest/view/transactiondetail/ManiphestTransactionDetailView.php
index e11852f786..61145b902f 100644
--- a/src/applications/maniphest/view/transactiondetail/ManiphestTransactionDetailView.php
+++ b/src/applications/maniphest/view/transactiondetail/ManiphestTransactionDetailView.php
@@ -1,589 +1,597 @@
 <?php
 
 /*
  * Copyright 2011 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.
  */
 
 /**
  * @group maniphest
  */
 class ManiphestTransactionDetailView extends ManiphestView {
 
   private $transactions;
   private $handles;
   private $markupEngine;
   private $forEmail;
   private $preview;
   private $commentNumber;
 
   private $renderSummaryOnly;
   private $renderFullSummary;
   private $user;
 
   public function setTransactionGroup(array $transactions) {
     $this->transactions = $transactions;
     return $this;
   }
 
   public function setHandles(array $handles) {
     $this->handles = $handles;
     return $this;
   }
 
   public function setMarkupEngine(PhutilMarkupEngine $engine) {
     $this->markupEngine = $engine;
     return $this;
   }
 
   public function setPreview($preview) {
     $this->preview = $preview;
     return $this;
   }
 
   public function setRenderSummaryOnly($render_summary_only) {
     $this->renderSummaryOnly = $render_summary_only;
     return $this;
   }
 
   public function getRenderSummaryOnly() {
     return $this->renderSummaryOnly;
   }
 
   public function setRenderFullSummary($render_full_summary) {
     $this->renderFullSummary = $render_full_summary;
     return $this;
   }
 
   public function getRenderFullSummary() {
     return $this->renderFullSummary;
   }
 
   public function setCommentNumber($comment_number) {
     $this->commentNumber = $comment_number;
     return $this;
   }
 
   public function setUser(PhabricatorUser $user) {
     $this->user = $user;
     return $this;
   }
 
   public function renderForEmail($with_date) {
     $this->forEmail = true;
 
     $transaction = reset($this->transactions);
     $author = $this->renderHandles(array($transaction->getAuthorPHID()));
 
     $action = null;
     $descs = array();
     $comments = null;
     foreach ($this->transactions as $transaction) {
       list($verb, $desc, $classes) = $this->describeAction($transaction);
       if ($action === null) {
         $action = $verb;
       }
       $desc = $author.' '.$desc.'.';
       if ($with_date) {
         // NOTE: This is going into a (potentially multi-recipient) email so
         // we can't use a single user's timezone preferences. Use the server's
         // instead, but make the timezone explicit.
         $datetime = date('M jS \a\t g:i A T', $transaction->getDateCreated());
         $desc = "On {$datetime}, {$desc}";
       }
       $descs[] = $desc;
       if ($transaction->hasComments()) {
         $comments = $transaction->getComments();
       }
     }
 
     $descs = implode("\n", $descs);
 
     if ($comments) {
       $descs .= "\n".$comments;
     }
 
     foreach ($this->transactions as $transaction) {
       $supplemental = $this->renderSupplementalInfoForEmail($transaction);
       if ($supplemental) {
         $descs .= "\n\n".$supplemental;
       }
     }
 
     $this->forEmail = false;
     return array($action, $descs);
   }
 
   public function render() {
 
     if (!$this->user) {
       throw new Exception("Call setUser() before render()!");
     }
 
     $handles = $this->handles;
     $transactions = $this->transactions;
 
     require_celerity_resource('maniphest-transaction-detail-css');
 
     $comment_transaction = null;
     foreach ($this->transactions as $transaction) {
       if ($transaction->hasComments()) {
         $comment_transaction = $transaction;
         break;
       }
     }
     $any_transaction = reset($transactions);
 
     $author = $this->handles[$any_transaction->getAuthorPHID()];
 
     $more_classes = array();
     $descs = array();
     foreach ($transactions as $transaction) {
       list($verb, $desc, $classes) = $this->describeAction($transaction);
       $more_classes = array_merge($more_classes, $classes);
       $full_summary = null;
       if ($this->getRenderFullSummary()) {
         $full_summary = $this->renderFullSummary($transaction);
       }
       $descs[] = javelin_render_tag(
         'div',
         array(
           'sigil' => 'maniphest-transaction-description',
         ),
         $author->renderLink().' '.$desc.'.'.$full_summary);
     }
     $descs = implode("\n", $descs);
 
     if ($this->getRenderSummaryOnly()) {
       return $descs;
     }
 
     $more_classes = implode(' ', $more_classes);
 
     if ($comment_transaction && $comment_transaction->hasComments()) {
       $comments = $comment_transaction->getCache();
       if (!strlen($comments)) {
         $comments = $comment_transaction->getComments();
         if (strlen($comments)) {
           $comments = $this->markupEngine->markupText($comments);
           $comment_transaction->setCache($comments);
           if ($comment_transaction->getID() && !$this->preview) {
             $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
             $comment_transaction->save();
             unset($unguarded);
           }
         }
       }
       $comment_block =
         '<div class="maniphest-transaction-comments phabricator-remarkup">'.
           $comments.
         '</div>';
     } else {
       $comment_block = null;
     }
 
     if ($this->preview) {
       $timestamp = 'COMMENT PREVIEW';
     } else {
       $timestamp = phabricator_datetime(
         $transaction->getDateCreated(),
         $this->user);
     }
 
     $info = array();
+
+    $source_transaction = nonempty($comment_transaction, $any_transaction);
+    $content_source = new PhabricatorContentSourceView();
+    $content_source->setContentSource($source_transaction->getContentSource());
+    $content_source->setUser($this->user);
+    $info[] = $content_source->render();
+
     $info[] = $timestamp;
 
     $comment_anchor = null;
     $num = $this->commentNumber;
     if ($num && !$this->preview) {
       Javelin::initBehavior('phabricator-watch-anchor');
       $info[] = javelin_render_tag(
         'a',
         array(
           'name' => 'comment-'.$num,
           'href' => '#comment-'.$num,
         ),
         'Comment T'.$any_transaction->getTaskID().'#'.$num);
       $comment_anchor = 'anchor-comment-'.$num;
     }
 
-    $info = implode(' &middot; ', $info);
+
+    $info = implode(' &middot; ', array_filter($info));
 
     return phutil_render_tag(
       'div',
       array(
         'class' => "maniphest-transaction-detail-container",
         'style' => "background-image: url('".$author->getImageURI()."')",
         'id'    => $comment_anchor,
       ),
       '<div class="maniphest-transaction-detail-view '.$more_classes.'">'.
         '<div class="maniphest-transaction-header">'.
           '<div class="maniphest-transaction-timestamp">'.
             $info.
           '</div>'.
           $descs.
         '</div>'.
         $comment_block.
       '</div>');
   }
 
   private function renderSupplementalInfoForEmail($transaction) {
     $handles = $this->handles;
 
     $type = $transaction->getTransactionType();
     $new = $transaction->getNewValue();
     $old = $transaction->getOldValue();
 
     switch ($type) {
       case ManiphestTransactionType::TYPE_DESCRIPTION:
         return "NEW DESCRIPTION\n  ".trim($new)."\n\n".
                "PREVIOUS DESCRIPTION\n  ".trim($old);
       case ManiphestTransactionType::TYPE_ATTACH:
         $old_raw = nonempty($old, array());
         $new_raw = nonempty($new, array());
 
         $attach_types = array(
           PhabricatorPHIDConstants::PHID_TYPE_DREV,
           PhabricatorPHIDConstants::PHID_TYPE_FILE,
         );
 
         foreach ($attach_types as $type) {
           $old = array_keys(idx($old_raw, $type, array()));
           $new = array_keys(idx($new_raw, $type, array()));
           if ($old != $new) {
             break;
           }
         }
 
         $added = array_diff($new, $old);
         if (!$added) {
           break;
         }
 
         $links = array();
         foreach (array_select_keys($handles, $added) as $handle) {
           $links[] = '  '.PhabricatorEnv::getProductionURI($handle->getURI());
         }
         $links = implode("\n", $links);
 
         switch ($type) {
           case PhabricatorPHIDConstants::PHID_TYPE_DREV:
             $title = 'ATTACHED REVISIONS';
             break;
           case PhabricatorPHIDConstants::PHID_TYPE_FILE:
             $title = 'ATTACHED FILES';
             break;
         }
 
         return $title."\n".$links;
       default:
         break;
     }
 
     return null;
   }
 
   private function describeAction($transaction) {
     $verb = null;
     $desc = null;
     $classes = array();
 
     $handles = $this->handles;
 
     $type = $transaction->getTransactionType();
     $author_phid = $transaction->getAuthorPHID();
     $new = $transaction->getNewValue();
     $old = $transaction->getOldValue();
     switch ($type) {
       case ManiphestTransactionType::TYPE_TITLE:
         $verb = 'Retitled';
         $desc = 'changed the title from '.$this->renderString($old).
                                    ' to '.$this->renderString($new);
         break;
       case ManiphestTransactionType::TYPE_DESCRIPTION:
         $verb = 'Edited';
         if ($this->forEmail || $this->getRenderFullSummary()) {
           $desc = 'updated the task description';
         } else {
           $desc = 'updated the task description; '.
                   $this->renderExpandLink($transaction);
         }
         break;
       case ManiphestTransactionType::TYPE_NONE:
         $verb = 'Commented On';
         $desc = 'added a comment';
         break;
       case ManiphestTransactionType::TYPE_OWNER:
         if ($transaction->getAuthorPHID() == $new) {
           $verb = 'Claimed';
           $desc = 'claimed this task';
           $classes[] = 'claimed';
         } else if (!$new) {
           $verb = 'Up For Grabs';
           $desc = 'placed this task up for grabs';
           $classes[] = 'upforgrab';
         } else if (!$old) {
           $verb = 'Assigned';
           $desc = 'assigned this task to '.$this->renderHandles(array($new));
           $classes[] = 'assigned';
         } else {
           $verb = 'Reassigned';
           $desc = 'reassigned this task from '.
                   $this->renderHandles(array($old)).
                   ' to '.
                   $this->renderHandles(array($new));
           $classes[] = 'reassigned';
         }
         break;
       case ManiphestTransactionType::TYPE_CCS:
         if ($this->preview) {
           $verb = 'Changed CC';
           $desc = 'changed CCs..';
           break;
         }
         $added = array_diff($new, $old);
         $removed = array_diff($old, $new);
         if ($added && !$removed) {
           $verb = 'Added CC';
           if (count($added) == 1) {
             $desc = 'added '.$this->renderHandles($added).' to CC';
           } else {
             $desc = 'added CCs: '.$this->renderHandles($added);
           }
         } else if ($removed && !$added) {
           $verb = 'Removed CC';
           if (count($removed) == 1) {
             $desc = 'removed '.$this->renderHandles($removed).' from CC';
           } else {
             $desc = 'removed CCs: '.$this->renderHandles($removed);
           }
         } else {
           $verb = 'Changed CC';
           $desc = 'changed CCs, added: '.$this->renderHandles($added).'; '.
                              'removed: '.$this->renderHandles($removed);
         }
         break;
       case ManiphestTransactionType::TYPE_PROJECTS:
         if ($this->preview) {
           $verb = 'Changed Projects';
           $desc = 'changed projects..';
           break;
         }
         $added = array_diff($new, $old);
         $removed = array_diff($old, $new);
         if ($added && !$removed) {
           $verb = 'Added Project';
           if (count($added) == 1) {
             $desc = 'added project '.$this->renderHandles($added);
           } else {
             $desc = 'added projects: '.$this->renderHandles($added);
           }
         } else if ($removed && !$added) {
           $verb = 'Removed Project';
           if (count($removed) == 1) {
             $desc = 'removed project '.$this->renderHandles($removed);
           } else {
             $desc = 'removed projectss: '.$this->renderHandles($removed);
           }
         } else {
           $verb = 'Changed Projects';
           $desc = 'changed projects, added: '.$this->renderHandles($added).'; '.
                                   'removed: '.$this->renderHandles($removed);
         }
         break;
       case ManiphestTransactionType::TYPE_STATUS:
         if ($new == ManiphestTaskStatus::STATUS_OPEN) {
           if ($old) {
             $verb = 'Reopened';
             $desc = 'reopened this task';
             $classes[] = 'reopened';
           } else {
             $verb = 'Created';
             $desc = 'created this task';
             $classes[] = 'created';
           }
         } else if ($new == ManiphestTaskStatus::STATUS_CLOSED_SPITE) {
           $verb = 'Spited';
           $desc = 'closed this task out of spite';
           $classes[] = 'spited';
         } else if ($new == ManiphestTaskStatus::STATUS_CLOSED_DUPLICATE) {
           $verb = 'Merged';
           $desc = 'closed this task as a duplicate';
           $classes[] = 'duplicate';
         } else {
           $verb = 'Closed';
           $full = idx(ManiphestTaskStatus::getTaskStatusMap(), $new, '???');
           $desc = 'closed this task as "'.$full.'"';
           $classes[] = 'closed';
         }
         break;
       case ManiphestTransactionType::TYPE_PRIORITY:
         $old_name = ManiphestTaskPriority::getTaskPriorityName($old);
         $new_name = ManiphestTaskPriority::getTaskPriorityName($new);
 
         if ($old == ManiphestTaskPriority::PRIORITY_TRIAGE) {
           $verb = 'Triaged';
           $desc = 'triaged this task as "'.$new_name.'" priority';
         } else if ($old > $new) {
           $verb = 'Lowered Priority';
           $desc = 'lowered the priority of this task from "'.$old_name.'" to '.
                   '"'.$new_name.'"';
         } else {
           $verb = 'Raised Priority';
           $desc = 'raised the priority of this task from "'.$old_name.'" to '.
                   '"'.$new_name.'"';
         }
         if ($new == ManiphestTaskPriority::PRIORITY_UNBREAK_NOW) {
           $classes[] = 'unbreaknow';
         }
         break;
       case ManiphestTransactionType::TYPE_ATTACH:
         if ($this->preview) {
           $verb = 'Changed Attached';
           $desc = 'changed attachments..';
           break;
         }
 
         $old_raw = nonempty($old, array());
         $new_raw = nonempty($new, array());
 
         foreach (array(
           PhabricatorPHIDConstants::PHID_TYPE_DREV,
           PhabricatorPHIDConstants::PHID_TYPE_TASK,
           PhabricatorPHIDConstants::PHID_TYPE_FILE) as $type) {
           $old = array_keys(idx($old_raw, $type, array()));
           $new = array_keys(idx($new_raw, $type, array()));
           if ($old != $new) {
             break;
           }
         }
 
         $added = array_diff($new, $old);
         $removed = array_diff($old, $new);
 
         $add_desc = $this->renderHandles($added);
         $rem_desc = $this->renderHandles($removed);
 
         switch ($type) {
           case PhabricatorPHIDConstants::PHID_TYPE_DREV:
             $singular = 'Differential Revision';
             $plural = 'Differential Revisions';
             break;
           case PhabricatorPHIDConstants::PHID_TYPE_FILE:
             $singular = 'file';
             $plural = 'files';
             break;
           case PhabricatorPHIDConstants::PHID_TYPE_TASK:
             $singular = 'Maniphest Task';
             $plural = 'Maniphest Tasks';
             $dependency = true;
             break;
         }
 
         if ($added && !$removed) {
           $verb = 'Attached';
           if (count($added) == 1) {
             $desc = 'attached '.$singular.': '.$add_desc;
           } else {
             $desc = 'attached '.$plural.': '.$add_desc;
           }
         } else if ($removed && !$added) {
           $verb = 'Detached';
           if (count($removed) == 1) {
             $desc = 'detached '.$singular.': '.$rem_desc;
           } else {
             $desc = 'detached '.$plural.': '.$rem_desc;
           }
         } else {
           $verb = 'Changed Attached';
           $desc = 'changed attached '.$plural.', added: '.$add_desc.
                                               'removed: '.$rem_desc;
         }
         break;
       default:
         return array($type, ' brazenly '.$type."'d", $classes);
     }
 
     return array($verb, $desc, $classes);
   }
 
   private function renderFullSummary($transaction) {
     switch ($transaction->getTransactionType()) {
       case ManiphestTransactionType::TYPE_DESCRIPTION:
         $engine = $this->markupEngine;
 
         $old = $transaction->getOldValue();
         $new = $transaction->getNewValue();
         $table =
           '<table class="maniphest-change-table">
             <tr>
               <th>Previous Description</th>
               <th>New Description</th>
             </tr>
             <tr>
               <td>
                 <div class="phabricator-remarkup">'.
                   $engine->markupText($old).
                 '</div>
               </td>
               <td>
                 <div class="phabricator-remarkup">'.
                   $engine->markupText($new).
                 '</div>
               </td>
             </tr>
           </table>';
 
         return $table;
     }
 
     return null;
   }
 
   private function renderExpandLink($transaction) {
     $id = $transaction->getID();
 
     Javelin::initBehavior('maniphest-transaction-expand');
 
     return javelin_render_tag(
       'a',
       array(
         'href'          => '/maniphest/task/descriptionchange/'.$id.'/',
         'sigil'         => 'maniphest-expand-transaction',
         'mustcapture'   => true,
       ),
       'show details');
   }
 
   private function renderHandles($phids) {
     $links = array();
     foreach ($phids as $phid) {
       if ($this->forEmail) {
         $links[] = $this->handles[$phid]->getName();
       } else {
         $links[] = $this->handles[$phid]->renderLink();
       }
     }
     return implode(', ', $links);
   }
 
   private function renderString($string) {
     if ($this->forEmail) {
       return '"'.$string.'"';
     } else {
       return '"'.phutil_escape_html($string).'"';
     }
   }
 
 }
diff --git a/src/applications/maniphest/view/transactiondetail/__init__.php b/src/applications/maniphest/view/transactiondetail/__init__.php
index b1c34a4630..e47dde0c10 100644
--- a/src/applications/maniphest/view/transactiondetail/__init__.php
+++ b/src/applications/maniphest/view/transactiondetail/__init__.php
@@ -1,25 +1,26 @@
 <?php
 /**
  * This file is automatically generated. Lint this module to rebuild it.
  * @generated
  */
 
 
 
 phutil_require_module('phabricator', 'aphront/writeguard');
 phutil_require_module('phabricator', 'applications/maniphest/constants/priority');
 phutil_require_module('phabricator', 'applications/maniphest/constants/status');
 phutil_require_module('phabricator', 'applications/maniphest/constants/transactiontype');
 phutil_require_module('phabricator', 'applications/maniphest/view/base');
+phutil_require_module('phabricator', 'applications/metamta/contentsource/view');
 phutil_require_module('phabricator', 'applications/phid/constants');
 phutil_require_module('phabricator', 'infrastructure/celerity/api');
 phutil_require_module('phabricator', 'infrastructure/env');
 phutil_require_module('phabricator', 'infrastructure/javelin/api');
 phutil_require_module('phabricator', 'infrastructure/javelin/markup');
 phutil_require_module('phabricator', 'view/utils');
 
 phutil_require_module('phutil', 'markup');
 phutil_require_module('phutil', 'utils');
 
 
 phutil_require_source('ManiphestTransactionDetailView.php');
diff --git a/src/applications/metamta/contentsource/source/PhabricatorContentSource.php b/src/applications/metamta/contentsource/source/PhabricatorContentSource.php
new file mode 100644
index 0000000000..e058dfdb1d
--- /dev/null
+++ b/src/applications/metamta/contentsource/source/PhabricatorContentSource.php
@@ -0,0 +1,75 @@
+<?php
+
+/*
+ * Copyright 2011 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 PhabricatorContentSource {
+
+  const SOURCE_UNKNOWN  = 'unknown';
+  const SOURCE_WEB      = 'web';
+  const SOURCE_EMAIL    = 'email';
+  const SOURCE_CONDUIT  = 'conduit';
+  const SOURCE_MOBILE   = 'mobile';
+  const SOURCE_TABLET   = 'tablet';
+
+  private $source;
+  private $params = array();
+
+  private function __construct() {
+    // <empty>
+  }
+
+  public static function newForSource($source, array $params) {
+    $obj = new PhabricatorContentSource();
+    $obj->source = $source;
+    $obj->params = $params;
+
+    return $obj;
+  }
+
+  public static function newFromSerialized($serialized) {
+    $dict = json_decode($serialized, true);
+    if (!is_array($dict)) {
+      $dict = array();
+    }
+
+    $obj = new PhabricatorContentSource();
+    $obj->source = idx($dict, 'source', self::SOURCE_UNKNOWN);
+    $obj->params = idx($dict, 'params', array());
+
+    return $obj;
+  }
+
+  public function serialize() {
+    return json_encode(array(
+      'source' => $this->getSource(),
+      'params' => $this->getParams(),
+    ));
+  }
+
+  public function getSource() {
+    return $this->source;
+  }
+
+  public function getParams() {
+    return $this->params;
+  }
+
+  public function getParam($key, $default = null) {
+    return idx($this->params, $key, $default);
+  }
+
+}
diff --git a/src/applications/metamta/contentsource/source/__init__.php b/src/applications/metamta/contentsource/source/__init__.php
new file mode 100644
index 0000000000..96cacc81bc
--- /dev/null
+++ b/src/applications/metamta/contentsource/source/__init__.php
@@ -0,0 +1,12 @@
+<?php
+/**
+ * This file is automatically generated. Lint this module to rebuild it.
+ * @generated
+ */
+
+
+
+phutil_require_module('phutil', 'utils');
+
+
+phutil_require_source('PhabricatorContentSource.php');
diff --git a/src/applications/metamta/contentsource/view/PhabricatorContentSourceView.php b/src/applications/metamta/contentsource/view/PhabricatorContentSourceView.php
new file mode 100644
index 0000000000..02b83c7d68
--- /dev/null
+++ b/src/applications/metamta/contentsource/view/PhabricatorContentSourceView.php
@@ -0,0 +1,64 @@
+<?php
+
+/*
+ * Copyright 2011 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 PhabricatorContentSourceView extends AphrontView {
+
+  private $contentSource;
+  private $user;
+
+  public function setContentSource(PhabricatorContentSource $content_source) {
+    $this->contentSource = $content_source;
+    return $this;
+  }
+
+  public function setUser(PhabricatorUser $user) {
+    $this->user = $user;
+    return $this;
+  }
+
+
+  public function render() {
+    require_celerity_resource('phabricator-content-source-view-css');
+
+    $type = null;
+    $map = array(
+      PhabricatorContentSource::SOURCE_WEB      => 'web',
+      PhabricatorContentSource::SOURCE_CONDUIT  => 'conduit',
+      PhabricatorContentSource::SOURCE_EMAIL    => 'email',
+      PhabricatorContentSource::SOURCE_MOBILE   => 'mobile',
+      PhabricatorContentSource::SOURCE_TABLET   => 'tablet',
+    );
+
+    $source = $this->contentSource->getSource();
+    $type = idx($map, $source, null);
+
+    if (!$type) {
+      return;
+    }
+
+    $type_class = 'phabricator-content-source-'.$type;
+
+    return phutil_render_tag(
+      'span',
+      array(
+        'class' => "phabricator-content-source-view {$type_class}",
+      ),
+      'Via');
+  }
+
+}
diff --git a/src/applications/metamta/contentsource/view/__init__.php b/src/applications/metamta/contentsource/view/__init__.php
new file mode 100644
index 0000000000..fbbe1f68c5
--- /dev/null
+++ b/src/applications/metamta/contentsource/view/__init__.php
@@ -0,0 +1,17 @@
+<?php
+/**
+ * This file is automatically generated. Lint this module to rebuild it.
+ * @generated
+ */
+
+
+
+phutil_require_module('phabricator', 'applications/metamta/contentsource/source');
+phutil_require_module('phabricator', 'infrastructure/celerity/api');
+phutil_require_module('phabricator', 'view/base');
+
+phutil_require_module('phutil', 'markup');
+phutil_require_module('phutil', 'utils');
+
+
+phutil_require_source('PhabricatorContentSourceView.php');
diff --git a/webroot/rsrc/css/application/contentsource/content-source-view.css b/webroot/rsrc/css/application/contentsource/content-source-view.css
new file mode 100644
index 0000000000..9ab0da3c07
--- /dev/null
+++ b/webroot/rsrc/css/application/contentsource/content-source-view.css
@@ -0,0 +1,32 @@
+/**
+ * @provides phabricator-content-source-view-css
+ */
+
+.phabricator-content-source-view {
+  padding: 2px 20px 2px 0;
+  font-size: 11px;
+  color: #888888;
+  font-weight: normal;
+  background: no-repeat right center;
+  position: relative;
+}
+
+.phabricator-content-source-web {
+  background-image: url(/rsrc/image/icon/fatcow/source/web.png);
+}
+
+.phabricator-content-source-email {
+  background-image: url(/rsrc/image/icon/fatcow/source/email.png);
+}
+
+.phabricator-content-source-conduit {
+  background-image: url(/rsrc/image/icon/fatcow/source/conduit.png);
+}
+
+.phabricator-content-source-mobile {
+  background-image: url(/rsrc/image/icon/fatcow/source/mobile.png);
+}
+
+.phabricator-content-source-tablet {
+  background-image: url(/rsrc/image/icon/fatcow/source/tablet.png);
+}
diff --git a/webroot/rsrc/image/icon/fatcow/README b/webroot/rsrc/image/icon/fatcow/README
index eb6a114001..5d3ea5ed31 100644
--- a/webroot/rsrc/image/icon/fatcow/README
+++ b/webroot/rsrc/image/icon/fatcow/README
@@ -1,12 +1,16 @@
 These icons come from the FatCow icon set:
 
   http://www.fatcow.com/free-icons
 
 They are available under the Creative Commons Attribution 3.0 License:
 
   http://creativecommons.org/licenses/by/3.0/us/
 
 Some icons have been adapted from the FatCow set for use in Phabricator:
 
-  key_question.png
-
+  key_question.png      (from key_*.png)
+  source/web.png        (from world.png)
+  source/email.png      (from email.png)
+  source/conduit.png    (from satellite_dish.png)
+  source/mobile.png     (from phone.png)
+  source/tablet.png     (from tablet.png)
\ No newline at end of file
diff --git a/webroot/rsrc/image/icon/fatcow/source/conduit.png b/webroot/rsrc/image/icon/fatcow/source/conduit.png
new file mode 100644
index 0000000000..8e7b336aae
Binary files /dev/null and b/webroot/rsrc/image/icon/fatcow/source/conduit.png differ
diff --git a/webroot/rsrc/image/icon/fatcow/source/email.png b/webroot/rsrc/image/icon/fatcow/source/email.png
new file mode 100644
index 0000000000..7737c4f03c
Binary files /dev/null and b/webroot/rsrc/image/icon/fatcow/source/email.png differ
diff --git a/webroot/rsrc/image/icon/fatcow/source/mobile.png b/webroot/rsrc/image/icon/fatcow/source/mobile.png
new file mode 100644
index 0000000000..d0db086b13
Binary files /dev/null and b/webroot/rsrc/image/icon/fatcow/source/mobile.png differ
diff --git a/webroot/rsrc/image/icon/fatcow/source/tablet.png b/webroot/rsrc/image/icon/fatcow/source/tablet.png
new file mode 100644
index 0000000000..f6772c900c
Binary files /dev/null and b/webroot/rsrc/image/icon/fatcow/source/tablet.png differ
diff --git a/webroot/rsrc/image/icon/fatcow/source/web.png b/webroot/rsrc/image/icon/fatcow/source/web.png
new file mode 100644
index 0000000000..fb67c8b5c7
Binary files /dev/null and b/webroot/rsrc/image/icon/fatcow/source/web.png differ