diff --git a/src/__celerity_resource_map__.php b/src/__celerity_resource_map__.php index 30e05c36b6..041ae521fc 100644 --- a/src/__celerity_resource_map__.php +++ b/src/__celerity_resource_map__.php @@ -1,415 +1,415 @@ array( 'uri' => '/res/ac3fc983/rsrc/css/aphront/dark-console.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/dark-console.css', ), 'aphront-dialog-view-css' => array( 'uri' => '/res/a05107ae/rsrc/css/aphront/dialog-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/dialog-view.css', ), 'aphront-form-view-css' => array( 'uri' => '/res/785ac1c6/rsrc/css/aphront/form-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/form-view.css', ), 'aphront-panel-view-css' => array( 'uri' => '/res/63672373/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/97b8337a/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/0fc0545c/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/de3a1e4c/rsrc/css/aphront/table-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/table-view.css', ), 'aphront-tokenizer-control-css' => array( 'uri' => '/res/a3d23074/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/928df9f0/rsrc/css/aphront/typeahead.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/aphront/typeahead.css', ), 'phabricator-standard-page-view' => array( 'uri' => '/res/fb02fb0e/rsrc/css/application/base/standard-page-view.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/base/standard-page-view.css', ), 'differential-revision-add-comment-css' => array( - 'uri' => '/res/623fef21/rsrc/css/application/differential/add-comment.css', + 'uri' => '/res/d7f8719e/rsrc/css/application/differential/add-comment.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/add-comment.css', ), 'differential-changeset-view-css' => array( - 'uri' => '/res/11e7232a/rsrc/css/application/differential/changeset-view.css', + 'uri' => '/res/4e0295a9/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/525d1a12/rsrc/css/application/differential/core.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/core.css', ), 'differential-revision-comment-list-css' => array( 'uri' => '/res/a1c117db/rsrc/css/application/differential/revision-comment-list.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/revision-comment-list.css', ), 'differential-revision-comment-css' => array( - 'uri' => '/res/bf6369c6/rsrc/css/application/differential/revision-comment.css', + 'uri' => '/res/368bd612/rsrc/css/application/differential/revision-comment.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/revision-comment.css', ), 'differential-revision-detail-css' => array( 'uri' => '/res/230a67c6/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/755f3da3/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/a4a7b2b5/rsrc/css/application/differential/table-of-contents.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/differential/table-of-contents.css', ), 'phabricator-directory-css' => array( 'uri' => '/res/6a000601/rsrc/css/application/directory/phabricator-directory.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/application/directory/phabricator-directory.css', ), 'phabricator-core-buttons-css' => array( 'uri' => '/res/6e348ba4/rsrc/css/core/buttons.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/core/buttons.css', ), 'phabricator-core-css' => array( 'uri' => '/res/39ce37c2/rsrc/css/core/core.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/core/core.css', ), 'phabricator-core-dialog-css' => array( 'uri' => '/res/f66cec41/rsrc/css/core/dialog.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/core/dialog.css', ), 'phabricator-remarkup-css' => array( 'uri' => '/res/786989c3/rsrc/css/core/remarkup.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/core/remarkup.css', ), 'syntax-highlighting-css' => array( 'uri' => '/res/fb673ece/rsrc/css/core/syntax.css', 'type' => 'css', 'requires' => array( ), 'disk' => '/rsrc/css/core/syntax.css', ), 'javelin-behavior-dark-console' => array( 'uri' => '/res/453503f4/rsrc/js/application/core/behavior-dark-console.js', 'type' => 'js', 'requires' => array( ), 'disk' => '/rsrc/js/application/core/behavior-dark-console.js', ), 'javelin-behavior-aphront-basic-tokenizer' => array( 'uri' => '/res/8317d761/rsrc/js/application/core/behavior-tokenizer.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-lib-dev', ), 'disk' => '/rsrc/js/application/core/behavior-tokenizer.js', ), 'javelin-behavior-differential-feedback-preview' => array( - 'uri' => '/res/34fbb670/rsrc/js/application/differential/behavior-comment-preview.js', + 'uri' => '/res/8695d8b8/rsrc/js/application/differential/behavior-comment-preview.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-lib-dev', ), 'disk' => '/rsrc/js/application/differential/behavior-comment-preview.js', ), 'javelin-behavior-differential-edit-inline-comments' => array( - 'uri' => '/res/f5b54891/rsrc/js/application/differential/behavior-edit-inline-comments.js', + 'uri' => '/res/74747b2e/rsrc/js/application/differential/behavior-edit-inline-comments.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-lib-dev', ), 'disk' => '/rsrc/js/application/differential/behavior-edit-inline-comments.js', ), 'javelin-behavior-differential-populate' => array( 'uri' => '/res/f7efbf62/rsrc/js/application/differential/behavior-populate.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-lib-dev', ), 'disk' => '/rsrc/js/application/differential/behavior-populate.js', ), 'javelin-behavior-differential-show-more' => array( - 'uri' => '/res/d26ebcae/rsrc/js/application/differential/behavior-show-more.js', + 'uri' => '/res/ea998002/rsrc/js/application/differential/behavior-show-more.js', 'type' => 'js', 'requires' => array( 0 => 'javelin-lib-dev', ), 'disk' => '/rsrc/js/application/differential/behavior-show-more.js', ), 'javelin-magical-init' => array( 'uri' => '/res/76614f84/rsrc/js/javelin/init.dev.js', 'type' => 'js', 'requires' => array( ), 'disk' => '/rsrc/js/javelin/init.dev.js', ), 'javelin-init-prod' => array( 'uri' => '/res/1267c868/rsrc/js/javelin/init.min.js', 'type' => 'js', 'requires' => array( ), 'disk' => '/rsrc/js/javelin/init.min.js', ), 'javelin-lib-dev' => array( - 'uri' => '/res/53784c9a/rsrc/js/javelin/javelin.dev.js', + 'uri' => '/res/a0e7a5e9/rsrc/js/javelin/javelin.dev.js', 'type' => 'js', 'requires' => array( ), 'disk' => '/rsrc/js/javelin/javelin.dev.js', ), 'javelin-lib-prod' => array( 'uri' => '/res/2f2b3b2e/rsrc/js/javelin/javelin.min.js', 'type' => 'js', 'requires' => array( ), 'disk' => '/rsrc/js/javelin/javelin.min.js', ), 'javelin-typeahead-dev' => array( 'uri' => '/res/6de6ae59/rsrc/js/javelin/typeahead.dev.js', 'type' => 'js', 'requires' => array( ), 'disk' => '/rsrc/js/javelin/typeahead.dev.js', ), 'javelin-typeahead-prod' => array( 'uri' => '/res/69d5fad1/rsrc/js/javelin/typeahead.min.js', 'type' => 'js', 'requires' => array( ), 'disk' => '/rsrc/js/javelin/typeahead.min.js', ), 'javelin-workflow-dev' => array( 'uri' => '/res/7e690e16/rsrc/js/javelin/workflow.dev.js', 'type' => 'js', 'requires' => array( ), 'disk' => '/rsrc/js/javelin/workflow.dev.js', ), 'javelin-workflow-prod' => array( 'uri' => '/res/b758e0a0/rsrc/js/javelin/workflow.min.js', 'type' => 'js', 'requires' => array( ), 'disk' => '/rsrc/js/javelin/workflow.min.js', ), ), array ( 'packages' => array ( 'c5efa388' => 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-tokenizer-control-css', 9 => 'aphront-typeahead-control-css', 10 => 'phabricator-directory-css', ), 'uri' => '/res/pkg/c5efa388/core.pkg.css', 'type' => 'css', ), - 'f399aad7' => + '9d9c881c' => 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', ), - 'uri' => '/res/pkg/f399aad7/differential.pkg.css', + 'uri' => '/res/pkg/9d9c881c/differential.pkg.css', 'type' => 'css', ), ), 'reverse' => array ( 'phabricator-core-css' => 'c5efa388', 'phabricator-core-buttons-css' => 'c5efa388', 'phabricator-standard-page-view' => 'c5efa388', 'aphront-dialog-view-css' => 'c5efa388', 'aphront-form-view-css' => 'c5efa388', 'aphront-panel-view-css' => 'c5efa388', 'aphront-side-nav-view-css' => 'c5efa388', 'aphront-table-view-css' => 'c5efa388', 'aphront-tokenizer-control-css' => 'c5efa388', 'aphront-typeahead-control-css' => 'c5efa388', 'phabricator-directory-css' => 'c5efa388', - 'differential-core-view-css' => 'f399aad7', - 'differential-changeset-view-css' => 'f399aad7', - 'differential-revision-detail-css' => 'f399aad7', - 'differential-revision-history-css' => 'f399aad7', - 'differential-table-of-contents-css' => 'f399aad7', + 'differential-core-view-css' => '9d9c881c', + 'differential-changeset-view-css' => '9d9c881c', + 'differential-revision-detail-css' => '9d9c881c', + 'differential-revision-history-css' => '9d9c881c', + 'differential-table-of-contents-css' => '9d9c881c', ), )); diff --git a/src/__phutil_library_map__.php b/src/__phutil_library_map__.php index 3ef9c0e99b..4d7055953b 100644 --- a/src/__phutil_library_map__.php +++ b/src/__phutil_library_map__.php @@ -1,372 +1,374 @@ array( 'Aphront400Response' => 'aphront/response/400', 'Aphront404Response' => 'aphront/response/404', 'AphrontAjaxResponse' => 'aphront/response/ajax', 'AphrontApplicationConfiguration' => 'aphront/applicationconfiguration', 'AphrontController' => 'aphront/controller', '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', 'AphrontFileResponse' => 'aphront/response/file', 'AphrontFormCheckboxControl' => 'view/form/control/checkbox', 'AphrontFormControl' => 'view/form/control/base', 'AphrontFormFileControl' => 'view/form/control/file', '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', 'AphrontFormTokenizerControl' => 'view/form/control/tokenizer', 'AphrontFormView' => 'view/form/base', 'AphrontMySQLDatabaseConnection' => 'storage/connection/mysql', 'AphrontNullView' => 'view/null', 'AphrontPageView' => 'view/page/base', 'AphrontPanelView' => 'view/layout/panel', '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', 'AphrontRequest' => 'aphront/request', 'AphrontRequestFailureView' => 'view/page/failure', 'AphrontResponse' => 'aphront/response/base', 'AphrontSideNavView' => 'view/layout/sidenav', 'AphrontTableView' => 'view/control/table', 'AphrontURIMapper' => 'aphront/mapper', 'AphrontView' => 'view/base', 'AphrontWebpageResponse' => 'aphront/response/webpage', '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_differential_creatediff_Method' => 'applications/conduit/method/differential/creatediff', 'ConduitAPI_differential_setdiffproperty_Method' => 'applications/conduit/method/differential/setdiffproperty', 'ConduitAPI_file_upload_Method' => 'applications/conduit/method/file/upload', 'ConduitAPI_user_find_Method' => 'applications/conduit/method/user/find', 'ConduitException' => 'applications/conduit/protocol/exception', 'DarkConsole' => 'aphront/console/api', 'DarkConsoleController' => 'aphront/console/controller', 'DarkConsoleCore' => 'aphront/console/core', 'DarkConsoleErrorLogPlugin' => 'aphront/console/plugin/errorlog', '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', 'DifferentialAction' => 'applications/differential/constants/action', 'DifferentialAddCommentView' => 'applications/differential/view/addcomment', 'DifferentialCCWelcomeMail' => 'applications/differential/mail/ccwelcome', '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', 'DifferentialController' => 'applications/differential/controller/base', 'DifferentialDAO' => 'applications/differential/storage/base', 'DifferentialDiff' => 'applications/differential/storage/diff', 'DifferentialDiffContentMail' => 'applications/differential/mail/diffcontent', 'DifferentialDiffProperty' => 'applications/differential/storage/diffproperty', 'DifferentialDiffTableOfContentsView' => 'applications/differential/view/difftableofcontents', 'DifferentialDiffViewController' => 'applications/differential/controller/diffview', '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', 'DifferentialLintStatus' => 'applications/differential/constants/lintstatus', 'DifferentialMail' => 'applications/differential/mail/base', 'DifferentialMarkupEngineFactory' => 'applications/differential/parser/markup', 'DifferentialNewDiffMail' => 'applications/differential/mail/newdiff', 'DifferentialReviewRequestMail' => 'applications/differential/mail/reviewrequest', 'DifferentialRevision' => 'applications/differential/storage/revision', 'DifferentialRevisionCommentListView' => 'applications/differential/view/revisioncommentlist', 'DifferentialRevisionCommentView' => 'applications/differential/view/revisioncomment', 'DifferentialRevisionControlSystem' => 'applications/differential/constants/revisioncontrolsystem', 'DifferentialRevisionDetailView' => 'applications/differential/view/revisiondetail', 'DifferentialRevisionEditController' => 'applications/differential/controller/revisionedit', 'DifferentialRevisionEditor' => 'applications/differential/editor/revision', 'DifferentialRevisionListController' => 'applications/differential/controller/revisionlist', 'DifferentialRevisionListData' => 'applications/differential/data/revisionlist', 'DifferentialRevisionStatus' => 'applications/differential/constants/revisionstatus', 'DifferentialRevisionUpdateHistoryView' => 'applications/differential/view/revisionupdatehistory', 'DifferentialRevisionViewController' => 'applications/differential/controller/revisionview', 'DifferentialUnitStatus' => 'applications/differential/constants/unitstatus', 'Javelin' => 'infrastructure/javelin/api', 'LiskDAO' => 'storage/lisk/dao', 'Phabricator404Controller' => 'applications/base/controller/404', 'PhabricatorAuthController' => 'applications/auth/controller/base', 'PhabricatorConduitAPIController' => 'applications/conduit/controller/api', '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', 'PhabricatorController' => 'applications/base/controller/base', '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', 'PhabricatorEmailLoginController' => 'applications/auth/controller/email', 'PhabricatorEmailTokenController' => 'applications/auth/controller/emailtoken', 'PhabricatorEnv' => 'infrastructure/env', 'PhabricatorFacebookAuthController' => 'applications/auth/controller/facebookauth', 'PhabricatorFacebookAuthDiagnosticsController' => 'applications/auth/controller/facebookauth/diagnostics', 'PhabricatorFile' => 'applications/files/storage/file', 'PhabricatorFileController' => 'applications/files/controller/base', 'PhabricatorFileDAO' => 'applications/files/storage/base', 'PhabricatorFileListController' => 'applications/files/controller/list', 'PhabricatorFileStorageBlob' => 'applications/files/storage/storageblob', 'PhabricatorFileURI' => 'applications/files/uri', 'PhabricatorFileUploadController' => 'applications/files/controller/upload', 'PhabricatorFileViewController' => 'applications/files/controller/view', 'PhabricatorLiskDAO' => 'applications/base/storage/lisk', 'PhabricatorLoginController' => 'applications/auth/controller/login', 'PhabricatorLogoutController' => 'applications/auth/controller/logout', 'PhabricatorMailImplementationAdapter' => 'applications/metamta/adapter/base', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'applications/metamta/adapter/phpmailerlite', 'PhabricatorMetaMTAController' => 'applications/metamta/controller/base', 'PhabricatorMetaMTADAO' => 'applications/metamta/storage/base', 'PhabricatorMetaMTAListController' => 'applications/metamta/controller/list', 'PhabricatorMetaMTAMail' => 'applications/metamta/storage/mail', 'PhabricatorMetaMTAMailingList' => 'applications/metamta/storage/mailinglist', 'PhabricatorMetaMTAMailingListEditController' => 'applications/metamta/controller/mailinglistedit', 'PhabricatorMetaMTAMailingListsController' => 'applications/metamta/controller/mailinglists', 'PhabricatorMetaMTASendController' => 'applications/metamta/controller/send', 'PhabricatorMetaMTAViewController' => 'applications/metamta/controller/view', 'PhabricatorObjectHandle' => 'applications/phid/handle', 'PhabricatorObjectHandleData' => 'applications/phid/handle/data', 'PhabricatorPHID' => 'applications/phid/storage/phid', 'PhabricatorPHIDAllocateController' => 'applications/phid/controller/allocate', 'PhabricatorPHIDController' => 'applications/phid/controller/base', 'PhabricatorPHIDDAO' => 'applications/phid/storage/base', 'PhabricatorPHIDListController' => 'applications/phid/controller/list', 'PhabricatorPHIDLookupController' => 'applications/phid/controller/lookup', 'PhabricatorPHIDType' => 'applications/phid/storage/type', 'PhabricatorPHIDTypeEditController' => 'applications/phid/controller/typeedit', 'PhabricatorPHIDTypeListController' => 'applications/phid/controller/typelist', 'PhabricatorPeopleController' => 'applications/people/controller/base', 'PhabricatorPeopleEditController' => 'applications/people/controller/edit', 'PhabricatorPeopleListController' => 'applications/people/controller/list', 'PhabricatorPeopleProfileController' => 'applications/people/controller/profile', 'PhabricatorStandardPageView' => 'view/page/standard', 'PhabricatorTypeaheadCommonDatasourceController' => 'applications/typeahead/controller/common', 'PhabricatorTypeaheadDatasourceController' => 'applications/typeahead/controller/base', 'PhabricatorUser' => 'applications/people/storage/user', 'PhabricatorUserDAO' => 'applications/people/storage/base', 'PhabricatorXHProfController' => 'applications/xhprof/controller/base', 'PhabricatorXHProfProfileController' => 'applications/xhprof/controller/profile', 'PhabricatorXHProfProfileSymbolView' => 'applications/xhprof/view/symbol', 'PhabricatorXHProfProfileTopLevelView' => 'applications/xhprof/view/toplevel', ), '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_format_relative_time' => 'view/utils', 'phabricator_format_timestamp' => 'view/utils', 'phabricator_format_units_generic' => '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( 'Aphront400Response' => 'AphrontResponse', 'Aphront404Response' => 'AphrontResponse', 'AphrontAjaxResponse' => 'AphrontResponse', 'AphrontDefaultApplicationConfiguration' => 'AphrontApplicationConfiguration', 'AphrontDefaultApplicationController' => 'AphrontController', 'AphrontDialogResponse' => 'AphrontResponse', 'AphrontDialogView' => 'AphrontView', 'AphrontErrorView' => 'AphrontView', 'AphrontFileResponse' => 'AphrontResponse', 'AphrontFormCheckboxControl' => 'AphrontFormControl', 'AphrontFormControl' => 'AphrontView', 'AphrontFormFileControl' => 'AphrontFormControl', 'AphrontFormMarkupControl' => 'AphrontFormControl', 'AphrontFormPasswordControl' => 'AphrontFormControl', 'AphrontFormRecaptchaControl' => 'AphrontFormControl', 'AphrontFormSelectControl' => 'AphrontFormControl', 'AphrontFormStaticControl' => 'AphrontFormControl', 'AphrontFormSubmitControl' => 'AphrontFormControl', 'AphrontFormTextAreaControl' => 'AphrontFormControl', 'AphrontFormTextControl' => 'AphrontFormControl', 'AphrontFormTokenizerControl' => 'AphrontFormControl', 'AphrontFormView' => 'AphrontView', 'AphrontMySQLDatabaseConnection' => 'AphrontDatabaseConnection', 'AphrontNullView' => 'AphrontView', 'AphrontPageView' => 'AphrontView', 'AphrontPanelView' => 'AphrontView', 'AphrontQueryConnectionException' => 'AphrontQueryException', 'AphrontQueryConnectionLostException' => 'AphrontQueryRecoverableException', 'AphrontQueryCountException' => 'AphrontQueryException', 'AphrontQueryDuplicateKeyException' => 'AphrontQueryException', 'AphrontQueryObjectMissingException' => 'AphrontQueryException', 'AphrontQueryParameterException' => 'AphrontQueryException', 'AphrontQueryRecoverableException' => 'AphrontQueryException', 'AphrontRedirectException' => 'AphrontException', 'AphrontRedirectResponse' => 'AphrontResponse', 'AphrontRequestFailureView' => 'AphrontView', 'AphrontSideNavView' => 'AphrontView', 'AphrontTableView' => 'AphrontView', 'AphrontWebpageResponse' => 'AphrontResponse', 'CelerityResourceController' => 'AphrontController', 'ConduitAPI_conduit_connect_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_creatediff_Method' => 'ConduitAPIMethod', 'ConduitAPI_differential_setdiffproperty_Method' => 'ConduitAPIMethod', 'ConduitAPI_file_upload_Method' => 'ConduitAPIMethod', 'ConduitAPI_user_find_Method' => 'ConduitAPIMethod', 'DarkConsoleController' => 'AliteController', 'DarkConsoleErrorLogPlugin' => 'DarkConsolePlugin', 'DarkConsoleRequestPlugin' => 'DarkConsolePlugin', 'DarkConsoleServicesPlugin' => 'DarkConsolePlugin', 'DarkConsoleXHProfPlugin' => 'DarkConsolePlugin', 'DifferentialAddCommentView' => 'AphrontView', 'DifferentialCCWelcomeMail' => 'DifferentialReviewRequestMail', 'DifferentialChangeset' => 'DifferentialDAO', 'DifferentialChangesetDetailView' => 'AphrontView', 'DifferentialChangesetListView' => 'AphrontView', 'DifferentialChangesetViewController' => 'DifferentialController', 'DifferentialComment' => 'DifferentialDAO', 'DifferentialCommentMail' => 'DifferentialMail', 'DifferentialCommentPreviewController' => 'DifferentialController', 'DifferentialCommentSaveController' => 'DifferentialController', 'DifferentialController' => 'PhabricatorController', 'DifferentialDAO' => 'PhabricatorLiskDAO', 'DifferentialDiff' => 'DifferentialDAO', 'DifferentialDiffContentMail' => 'DifferentialMail', 'DifferentialDiffProperty' => 'DifferentialDAO', 'DifferentialDiffTableOfContentsView' => 'AphrontView', 'DifferentialDiffViewController' => 'DifferentialController', 'DifferentialHunk' => 'DifferentialDAO', 'DifferentialInlineComment' => 'DifferentialDAO', 'DifferentialInlineCommentEditController' => 'DifferentialController', + 'DifferentialInlineCommentPreviewController' => 'DifferentialController', 'DifferentialInlineCommentView' => 'AphrontView', 'DifferentialNewDiffMail' => 'DifferentialReviewRequestMail', 'DifferentialReviewRequestMail' => 'DifferentialMail', 'DifferentialRevision' => 'DifferentialDAO', 'DifferentialRevisionCommentListView' => 'AphrontView', 'DifferentialRevisionCommentView' => 'AphrontView', 'DifferentialRevisionDetailView' => 'AphrontView', 'DifferentialRevisionEditController' => 'DifferentialController', 'DifferentialRevisionListController' => 'DifferentialController', 'DifferentialRevisionUpdateHistoryView' => 'AphrontView', 'DifferentialRevisionViewController' => 'DifferentialController', 'Phabricator404Controller' => 'PhabricatorController', 'PhabricatorAuthController' => 'PhabricatorController', 'PhabricatorConduitAPIController' => 'PhabricatorConduitController', 'PhabricatorConduitConnectionLog' => 'PhabricatorConduitDAO', 'PhabricatorConduitConsoleController' => 'PhabricatorConduitController', 'PhabricatorConduitController' => 'PhabricatorController', 'PhabricatorConduitDAO' => 'PhabricatorLiskDAO', 'PhabricatorConduitLogController' => 'PhabricatorConduitController', 'PhabricatorConduitMethodCallLog' => 'PhabricatorConduitDAO', 'PhabricatorController' => 'AphrontController', 'PhabricatorDirectoryCategory' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryCategoryDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryCategoryListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryController' => 'PhabricatorController', 'PhabricatorDirectoryDAO' => 'PhabricatorLiskDAO', 'PhabricatorDirectoryItem' => 'PhabricatorDirectoryDAO', 'PhabricatorDirectoryItemDeleteController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemEditController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryItemListController' => 'PhabricatorDirectoryController', 'PhabricatorDirectoryMainController' => 'PhabricatorDirectoryController', 'PhabricatorEmailLoginController' => 'PhabricatorAuthController', 'PhabricatorEmailTokenController' => 'PhabricatorAuthController', 'PhabricatorFacebookAuthController' => 'PhabricatorAuthController', 'PhabricatorFacebookAuthDiagnosticsController' => 'PhabricatorAuthController', 'PhabricatorFile' => 'PhabricatorFileDAO', 'PhabricatorFileController' => 'PhabricatorController', 'PhabricatorFileDAO' => 'PhabricatorLiskDAO', 'PhabricatorFileListController' => 'PhabricatorFileController', 'PhabricatorFileStorageBlob' => 'PhabricatorFileDAO', 'PhabricatorFileUploadController' => 'PhabricatorFileController', 'PhabricatorFileViewController' => 'PhabricatorFileController', 'PhabricatorLiskDAO' => 'LiskDAO', 'PhabricatorLoginController' => 'PhabricatorAuthController', 'PhabricatorLogoutController' => 'PhabricatorAuthController', 'PhabricatorMailImplementationPHPMailerLiteAdapter' => 'PhabricatorMailImplementationAdapter', 'PhabricatorMetaMTAController' => 'PhabricatorController', 'PhabricatorMetaMTADAO' => 'PhabricatorLiskDAO', 'PhabricatorMetaMTAListController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMail' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingList' => 'PhabricatorMetaMTADAO', 'PhabricatorMetaMTAMailingListEditController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAMailingListsController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTASendController' => 'PhabricatorMetaMTAController', 'PhabricatorMetaMTAViewController' => 'PhabricatorMetaMTAController', 'PhabricatorPHID' => 'PhabricatorPHIDDAO', 'PhabricatorPHIDAllocateController' => 'PhabricatorPHIDController', 'PhabricatorPHIDController' => 'PhabricatorController', 'PhabricatorPHIDDAO' => 'PhabricatorLiskDAO', 'PhabricatorPHIDListController' => 'PhabricatorPHIDController', 'PhabricatorPHIDLookupController' => 'PhabricatorPHIDController', 'PhabricatorPHIDType' => 'PhabricatorPHIDDAO', 'PhabricatorPHIDTypeEditController' => 'PhabricatorPHIDController', 'PhabricatorPHIDTypeListController' => 'PhabricatorPHIDController', 'PhabricatorPeopleController' => 'PhabricatorController', 'PhabricatorPeopleEditController' => 'PhabricatorPeopleController', 'PhabricatorPeopleListController' => 'PhabricatorPeopleController', 'PhabricatorPeopleProfileController' => 'PhabricatorPeopleController', 'PhabricatorStandardPageView' => 'AphrontPageView', 'PhabricatorTypeaheadCommonDatasourceController' => 'PhabricatorTypeaheadDatasourceController', 'PhabricatorTypeaheadDatasourceController' => 'PhabricatorController', 'PhabricatorUser' => 'PhabricatorUserDAO', 'PhabricatorUserDAO' => 'PhabricatorLiskDAO', 'PhabricatorXHProfController' => 'PhabricatorController', 'PhabricatorXHProfProfileController' => 'PhabricatorXHProfController', 'PhabricatorXHProfProfileSymbolView' => 'AphrontView', 'PhabricatorXHProfProfileTopLevelView' => 'AphrontView', ), 'requires_interface' => array( ), )); diff --git a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php index 09474c10ad..ebee81637b 100644 --- a/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php +++ b/src/aphront/default/configuration/AphrontDefaultApplicationConfiguration.php @@ -1,207 +1,208 @@ array( '$' => 'RepositoryListController', 'new/$' => 'RepositoryEditController', 'edit/(?\d+)/$' => 'RepositoryEditController', 'delete/(?\d+)/$' => 'RepositoryDeleteController', ), '/' => array( '$' => 'PhabricatorDirectoryMainController', ), '/directory/' => array( 'item/$' => 'PhabricatorDirectoryItemListController', 'item/edit/(?:(?\d+)/)?$' => 'PhabricatorDirectoryItemEditController', 'item/delete/(?\d+)/' => 'PhabricatorDirectoryItemDeleteController', 'category/$' => 'PhabricatorDirectoryCategoryListController', 'category/edit/(?:(?\d+)/)?$' => 'PhabricatorDirectoryCategoryEditController', 'category/delete/(?\d+)/' => 'PhabricatorDirectoryCategoryDeleteController', ), '/file/' => array( '$' => 'PhabricatorFileListController', 'upload/$' => 'PhabricatorFileUploadController', '(?info)/(?[^/]+)/' => 'PhabricatorFileViewController', '(?view)/(?[^/]+)/' => 'PhabricatorFileViewController', '(?download)/(?[^/]+)/' => 'PhabricatorFileViewController', ), '/phid/' => array( '$' => 'PhabricatorPHIDLookupController', 'list/$' => 'PhabricatorPHIDListController', 'type/$' => 'PhabricatorPHIDTypeListController', 'type/edit/(?:(?\d+)/)?$' => 'PhabricatorPHIDTypeEditController', 'new/$' => 'PhabricatorPHIDAllocateController', ), '/people/' => array( '$' => 'PhabricatorPeopleListController', 'edit/(?:(?\w+)/)?$' => 'PhabricatorPeopleEditController', ), '/p/(?\w+)/$' => 'PhabricatorPeopleProfileController', '/conduit/' => array( '$' => 'PhabricatorConduitConsoleController', 'method/(?[^/]+)$' => 'PhabricatorConduitConsoleController', 'log/$' => 'PhabricatorConduitLogController', ), '/api/(?[^/]+)$' => 'PhabricatorConduitAPIController', '/D(?\d+)' => 'DifferentialRevisionViewController', '/differential/' => array( '$' => 'DifferentialRevisionListController', 'filter/(?\w+)/$' => 'DifferentialRevisionListController', 'diff/(?\d+)/$' => 'DifferentialDiffViewController', 'changeset/$' => 'DifferentialChangesetViewController', 'revision/edit/(?:(?\d+)/)?$' => 'DifferentialRevisionEditController', 'comment/' => array( 'preview/(?\d+)/$' => 'DifferentialCommentPreviewController', 'save/$' => 'DifferentialCommentSaveController', 'inline/' => array( - 'preview/$' => 'DifferentialInlineCommentPreviewController', + 'preview/(?\d+)/$' => + 'DifferentialInlineCommentPreviewController', 'edit/(?\d+)/$' => 'DifferentialInlineCommentEditController', ), ), ), '/res/' => array( '(?pkg/)?(?[a-f0-9]{8})/(?.+\.(?:css|js))$' => 'CelerityResourceController', ), '/typeahead/' => array( 'common/(?\w+)/$' => 'PhabricatorTypeaheadCommonDatasourceController', ), '/mail/' => array( '$' => 'PhabricatorMetaMTAListController', 'send/$' => 'PhabricatorMetaMTASendController', 'view/(?\d+)/$' => 'PhabricatorMetaMTAViewController', 'lists/$' => 'PhabricatorMetaMTAMailingListsController', 'lists/edit/(?:(?\d+)/)?$' => 'PhabricatorMetaMTAMailingListEditController', ), '/login/' => array( '$' => 'PhabricatorLoginController', 'email/$' => 'PhabricatorEmailLoginController', 'etoken/(?\w+)/$' => 'PhabricatorEmailTokenController', ), '/logout/$' => 'PhabricatorLogoutController', '/facebook-auth/' => array( '$' => 'PhabricatorFacebookAuthController', 'diagnose/$' => 'PhabricatorFacebookAuthDiagnosticsController', ), '/xhprof/' => array( 'profile/(?[^/]+)/$' => 'PhabricatorXHProfProfileController', ), ); } public function buildRequest() { $request = new AphrontRequest($this->getHost(), $this->getPath()); $request->setRequestData($_GET + $_POST); $request->setApplicationConfiguration($this); return $request; } public function handleException(Exception $ex) { $class = phutil_escape_html(get_class($ex)); $message = phutil_escape_html($ex->getMessage()); $content = '
'. '

Unhandled Exception "'.$class.'": '.$message.'

'. ''.phutil_escape_html((string)$ex).''. '
'; $view = new PhabricatorStandardPageView(); $view->setRequest($this->getRequest()); $view->appendChild($content); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } public function willSendResponse(AphrontResponse $response) { $request = $this->getRequest(); if ($response instanceof AphrontDialogResponse) { if (!$request->isAjax()) { $view = new PhabricatorStandardPageView(); $view->appendChild( '
'. $response->buildResponseString(). '
'); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); return $response; } else { return id(new AphrontAjaxResponse()) ->setContent(array( 'dialog' => $response->buildResponseString(), )); } } else if ($response instanceof Aphront404Response) { $failure = new AphrontRequestFailureView(); $failure->setHeader('404 Not Found'); $failure->appendChild( '

The page you requested was not found.

'); $view = new PhabricatorStandardPageView(); $view->setTitle('404 Not Found'); $view->setRequest($this->getRequest()); $view->appendChild($failure); $response = new AphrontWebpageResponse(); $response->setContent($view->render()); $response->setHTTPResponseCode(404); return $response; } return $response; } public function build404Controller() { return array(new Phabricator404Controller($this->getRequest()), array()); } } diff --git a/src/applications/differential/controller/inlinecommentedit/DifferentialInlineCommentEditController.php b/src/applications/differential/controller/inlinecommentedit/DifferentialInlineCommentEditController.php index 1eb44e9c07..7134a83220 100644 --- a/src/applications/differential/controller/inlinecommentedit/DifferentialInlineCommentEditController.php +++ b/src/applications/differential/controller/inlinecommentedit/DifferentialInlineCommentEditController.php @@ -1,192 +1,193 @@ revisionID = $data['id']; } public function processRequest() { $request = $this->getRequest(); $changeset = $request->getInt('changeset'); $is_new = $request->getInt('is_new'); $on_right = $request->getInt('on_right'); $number = $request->getInt('number'); $length = $request->getInt('length'); $text = $request->getStr('text'); $op = $request->getStr('op'); $inline_id = $request->getInt('id'); $user = $request->getUser(); $submit_uri = '/differential/comment/inline/edit/'.$this->revisionID.'/'; $edit_dialog = new AphrontDialogView(); $edit_dialog->setUser($user); $edit_dialog->setSubmitURI($submit_uri); $edit_dialog->addHiddenInput('on_right', $on_right); $edit_dialog->addSubmitButton(); $edit_dialog->addCancelButton('#'); $inline = null; if ($inline_id) { $inline = id(new DifferentialInlineComment()) ->load($inline_id); if (!$inline || $inline->getAuthorPHID() != $user->getPHID() || $inline->getCommentID() || $inline->getRevisionID() != $this->revisionID) { throw new Exception("That comment is not editable!"); } } switch ($op) { case 'delete': if (!$inline) { return new Aphront400Response(); } if ($request->isFormPost()) { $inline->delete(); return $this->buildDeletedResponse(); } $edit_dialog->setTitle('Really delete this comment?'); $edit_dialog->addHiddenInput('id', $inline_id); $edit_dialog->addHiddenInput('op', 'delete'); $edit_dialog->appendChild( '

Delete this inline comment?

'); return id(new AphrontDialogResponse())->setDialog($edit_dialog); case 'edit': if (!$inline) { return new Aphront400Response(); } if ($request->isFormPost()) { if (strlen($text)) { $inline->setContent($text); + $inline->setCache(null); $inline->save(); return $this->buildRenderedCommentResponse( $inline, $on_right); } else { $inline->delete(); return $this->buildDeletedResponse(); } } $edit_dialog->setTitle('Edit Inline Comment'); $edit_dialog->addHiddenInput('id', $inline_id); $edit_dialog->addHiddenInput('op', 'edit'); $edit_dialog->appendChild( $this->renderTextArea( $inline->getContent())); return id(new AphrontDialogResponse())->setDialog($edit_dialog); case 'create': if (!$request->isFormPost() || !strlen($text)) { return new AphrontAjaxResponse(); } $inline = id(new DifferentialInlineComment()) ->setRevisionID($this->revisionID) ->setChangesetID($changeset) ->setCommentID(null) ->setAuthorPHID($user->getPHID()) ->setLineNumber($number) ->setLineLength($length) ->setIsNewFile($is_new) ->setContent($text) ->save(); return $this->buildRenderedCommentResponse($inline, $on_right); default: $edit_dialog->setTitle('New Inline Comment'); $edit_dialog->addHiddenInput('op', 'create'); $edit_dialog->addHiddenInput('changeset', $changeset); $edit_dialog->addHiddenInput('is_new', $is_new); $edit_dialog->addHiddenInput('number', $number); $edit_dialog->addHiddenInput('length', $length); $edit_dialog->appendChild($this->renderTextArea('')); return id(new AphrontDialogResponse())->setDialog($edit_dialog); } } private function buildRenderedCommentResponse( DifferentialInlineComment $inline, $on_right) { $request = $this->getRequest(); $user = $request->getUser(); $factory = new DifferentialMarkupEngineFactory(); $engine = $factory->newDifferentialCommentMarkupEngine(); $phids = array($user->getPHID()); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $view = new DifferentialInlineCommentView(); $view->setInlineComment($inline); $view->setOnRight($on_right); $view->setBuildScaffolding(true); $view->setMarkupEngine($engine); $view->setHandles($handles); $view->setEditable(true); return id(new AphrontAjaxResponse()) ->setContent( array( 'inlineCommentID' => $inline->getID(), 'markup' => $view->render(), )); } private function buildDeletedResponse() { return id(new AphrontAjaxResponse()) ->setContent( array( 'markup' => '', )); } private function renderTextArea($text) { return phutil_render_tag( 'textarea', array( 'name' => 'text', ), $text); } } diff --git a/src/applications/differential/controller/inlinecommentpreview/DifferentialInlineCommentPreviewController.php b/src/applications/differential/controller/inlinecommentpreview/DifferentialInlineCommentPreviewController.php new file mode 100644 index 0000000000..b3a3e168b9 --- /dev/null +++ b/src/applications/differential/controller/inlinecommentpreview/DifferentialInlineCommentPreviewController.php @@ -0,0 +1,65 @@ +revisionID = $data['id']; + } + + public function processRequest() { + + $request = $this->getRequest(); + $user = $request->getUser(); + + // TODO: This is a reasonable approximation of the feature as it exists + // in Facebook trunk but we should probably pull filename data, sort these, + // figure out next/prev/edit/delete, deal with out-of-date inlines, etc. + + $inlines = id(new DifferentialInlineComment())->loadAllWhere( + 'authorPHID = %s AND revisionID = %d AND commentID IS NULL', + $user->getPHID(), + $this->revisionID); + + $factory = new DifferentialMarkupEngineFactory(); + $engine = $factory->newDifferentialCommentMarkupEngine(); + + $phids = array($user->getPHID()); + $handles = id(new PhabricatorObjectHandleData($phids)) + ->loadHandles(); + + $views = array(); + foreach ($inlines as $inline) { + $view = new DifferentialInlineCommentView(); + $view->setInlineComment($inline); + $view->setMarkupEngine($engine); + $view->setHandles($handles); + $view->setEditable(false); + $views[] = $view->render(); + } + $views = implode("\n", $views); + + return id(new AphrontAjaxResponse()) + ->setContent($views); + } + + +} diff --git a/src/applications/differential/editor/comment/__init__.php b/src/applications/differential/controller/inlinecommentpreview/__init__.php similarity index 58% copy from src/applications/differential/editor/comment/__init__.php copy to src/applications/differential/controller/inlinecommentpreview/__init__.php index 5eb988d3ae..900156b3d4 100644 --- a/src/applications/differential/editor/comment/__init__.php +++ b/src/applications/differential/controller/inlinecommentpreview/__init__.php @@ -1,18 +1,19 @@ id = idx($data, 'id'); } public function processRequest() { if ($this->id) { $revision = id(new DifferentialRevision())->load($this->id); if (!$revision) { return new Aphront404Response(); } } else { $revision = new DifferentialRevision(); } $request = $this->getRequest(); $diff_id = $request->getInt('diffID'); if ($diff_id) { $diff = id(new DifferentialDiff())->load($diff_id); if (!$diff) { return new Aphront404Response(); } if ($diff->getRevisionID()) { // TODO: Redirect? throw new Exception("This diff is already attached to a revision!"); } } else { $diff = null; } $e_title = true; $e_testplan = true; $errors = array(); - + $revision->loadRelationships(); - if ($request->isFormPost() && !$request->getStr('viaDiffView')) { + if ($request->isFormPost() && !$request->getStr('viaDiffView')) { $revision->setTitle($request->getStr('title')); $revision->setSummary($request->getStr('summary')); $revision->setTestPlan($request->getStr('testplan')); $revision->setBlameRevision($request->getStr('blame')); $revision->setRevertPlan($request->getStr('revert')); if (!strlen(trim($revision->getTitle()))) { $errors[] = 'You must provide a title.'; $e_title = 'Required'; } if (!strlen(trim($revision->getTestPlan()))) { $errors[] = 'You must provide a test plan.'; $e_testplan = 'Required'; } $user_phid = $request->getUser()->getPHID(); if (in_array($user_phid, $request->getArr('reviewers'))) { $errors[] = 'You may not review your own revision.'; } if (!$errors) { $editor = new DifferentialRevisionEditor($revision, $user_phid); if ($diff) { $editor->addDiff($diff, $request->getStr('comments')); } $editor->setCCPHIDs($request->getArr('cc')); $editor->setReviewers($request->getArr('reviewers')); $editor->save(); return id(new AphrontRedirectResponse()) ->setURI('/D'.$revision->getID()); } $reviewer_phids = $request->getArr('reviewers'); $cc_phids = $request->getArr('cc'); } else { $reviewer_phids = $revision->getReviewers(); $cc_phids = $revision->getCCPHIDs(); } - + $phids = array_merge($reviewer_phids, $cc_phids); $phids = array_unique($phids); - + $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); $handles = mpull($handles, 'getFullName', 'getPHID'); - + $reviewer_map = array_select_keys($handles, $reviewer_phids); $cc_map = array_select_keys($handles, $cc_phids); $form = new AphrontFormView(); $form->setUser($request->getUser()); if ($diff) { $form->addHiddenInput('diffID', $diff->getID()); } if ($revision->getID()) { $form->setAction('/differential/revision/edit/'.$revision->getID().'/'); } else { $form->setAction('/differential/revision/edit/'); } $error_view = null; if ($errors) { $error_view = id(new AphrontErrorView()) ->setTitle('Form Errors') ->setErrors($errors); } $form ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Title') ->setName('title') ->setHeight(AphrontFormTextAreaControl::HEIGHT_VERY_SHORT) ->setValue($revision->getTitle()) ->setError($e_title)) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Summary') ->setName('summary') ->setValue($revision->getSummary())) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Test Plan') ->setName('testplan') ->setValue($revision->getTestPlan()) ->setError($e_testplan)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('Reviewers') ->setName('reviewers') ->setDatasource('/typeahead/common/users/') ->setValue($reviewer_map)) ->appendChild( id(new AphrontFormTokenizerControl()) ->setLabel('CC') ->setName('cc') ->setDatasource('/typeahead/common/mailable/') ->setValue($cc_map)) ->appendChild( id(new AphrontFormTextControl()) ->setLabel('Blame Revision') ->setName('blame') ->setValue($revision->getBlameRevision()) ->setCaption('Revision which broke the stuff which this '. 'change fixes.')) ->appendChild( id(new AphrontFormTextAreaControl()) ->setLabel('Revert Plan') ->setName('revert') ->setValue($revision->getRevertPlan()) ->setCaption('Special steps required to safely revert this change.')); $submit = id(new AphrontFormSubmitControl()) ->setValue('Save'); if ($diff) { $submit->addCancelButton('/differential/diff/'.$diff->getID().'/'); } else { $submit->addCancelButton('/D'.$revision->getID()); } $form->appendChild($submit); $panel = new AphrontPanelView(); if ($revision->getID()) { $panel->setHeader('Edit Differential Revision'); } else { $panel->setHeader('Create New Differential Revision'); } $panel->appendChild($form); $panel->setWidth(AphrontPanelView::WIDTH_FORM); return $this->buildStandardPageResponse( array($error_view, $panel), array( 'title' => 'Edit Differential Revision', )); } } diff --git a/src/applications/differential/controller/revisionedit/__init__.php b/src/applications/differential/controller/revisionedit/__init__.php index 872135cb60..805b2c0977 100644 --- a/src/applications/differential/controller/revisionedit/__init__.php +++ b/src/applications/differential/controller/revisionedit/__init__.php @@ -1,24 +1,25 @@ filter = idx($data, 'filter'); } public function processRequest() { $filters = array( 'active' => array( 'name' => 'Active Revisions', 'queries' => array( array( 'query' => DifferentialRevisionListData::QUERY_NEED_ACTION_FROM_SELF, 'header' => 'Action Required', 'nodata' => 'You have no revisions requiring action.', ), array( 'query' => DifferentialRevisionListData::QUERY_NEED_ACTION_FROM_OTHERS, 'header' => 'Waiting on Others', 'nodata' => 'You have no revisions waiting on others', ), ), ), 'open' => array( 'name' => 'Open Revisions', 'queries' => array( array( 'query' => DifferentialRevisionListData::QUERY_OPEN_OWNED, 'header' => 'Open Revisions', ), ), ), 'reviews' => array( 'name' => 'Open Reviews', 'queries' => array( array( 'query' => DifferentialRevisionListData::QUERY_OPEN_REVIEWER, 'header' => 'Open Reviews', ), ), ), 'all' => array( 'name' => 'All Revisions', 'queries' => array( array( 'query' => DifferentialRevisionListData::QUERY_OWNED, 'header' => 'All Revisions', ), ), ), 'related' => array( 'name' => 'All Revisions and Reviews', 'queries' => array( array( 'query' => DifferentialRevisionListData::QUERY_OWNED_OR_REVIEWER, 'header' => 'All Revisions and Reviews', ), ), ), ); if (empty($filters[$this->filter])) { $this->filter = key($filters); } $request = $this->getRequest(); $user = $request->getUser(); $queries = array(); $filter = $filters[$this->filter]; foreach ($filter['queries'] as $query) { $query_object = new DifferentialRevisionListData( $query['query'], array($user->getPHID())); $queries[] = array( 'object' => $query_object, ) + $query; } $side_nav = new AphrontSideNavView(); foreach ($filters as $filter_name => $filter_desc) { $selected = ($filter_name == $this->filter); $side_nav->addNavItem( phutil_render_tag( 'a', array( 'href' => '/differential/filter/'.$filter_name.'/', 'class' => $selected ? 'aphront-side-nav-selected' : null, ), phutil_escape_html($filter_desc['name']))); } $phids = array(); $rev_ids = array(); foreach ($queries as $key => $query) { $revisions = $query['object']->loadRevisions(); foreach ($revisions as $revision) { $phids[$revision->getAuthorPHID()] = true; $rev_ids[$revision->getID()] = true; } $queries[$key]['revisions'] = $revisions; } $rev = new DifferentialRevision(); if ($rev_ids) { $rev_ids = array_keys($rev_ids); $reviewers = queryfx_all( $rev->establishConnection('r'), 'SELECT revisionID, objectPHID FROM %T revision JOIN %T relationship ON revision.id = relationship.revisionID WHERE revision.id IN (%Ld) AND relationship.relation = %s - AND relationship.forbidden = 0 ORDER BY sequence', $rev->getTableName(), DifferentialRevision::RELATIONSHIP_TABLE, $rev_ids, DifferentialRevision::RELATION_REVIEWER); $reviewer_map = array(); foreach ($reviewers as $reviewer) { $reviewer_map[$reviewer['revisionID']][] = $reviewer['objectPHID']; } foreach ($reviewer_map as $revision_id => $reviewer_ids) { $phids[reset($reviewer_ids)] = true; } } else { $reviewer_map = array(); } if ($phids) { $phids = array_keys($phids); $handles = id(new PhabricatorObjectHandleData($phids)) ->loadHandles(); } else { $handles = array(); } foreach ($queries as $query) { $table = $this->renderRevisionTable( $query['revisions'], $query['header'], idx($query, 'nodata'), $handles, $reviewer_map); $side_nav->appendChild($table); } return $this->buildStandardPageResponse( $side_nav, array( 'title' => 'Differential Home', )); } private function renderRevisionTable( array $revisions, $header, $nodata, array $handles, array $reviewer_map) { $rows = array(); foreach ($revisions as $revision) { $status = DifferentialRevisionStatus::getNameForRevisionStatus( $revision->getStatus()); $reviewers = idx($reviewer_map, $revision->getID(), array()); if ($reviewers) { $first = reset($reviewers); if (count($reviewers) > 1) { $suffix = ' (+'.(count($reviewers) - 1).')'; } else { $suffix = null; } $reviewers = $handles[$first]->renderLink().$suffix; } else { $reviewers = 'None'; } $rows[] = array( 'D'.$revision->getID(), ''.phutil_render_tag( 'a', array( 'href' => '/D'.$revision->getID(), ), phutil_escape_html($revision->getTitle())).'', phutil_escape_html($status), number_format($revision->getLineCount()), $handles[$revision->getAuthorPHID()]->renderLink(), $reviewers, phabricator_format_timestamp($revision->getDateModified()), phabricator_format_timestamp($revision->getDateCreated()), ); } $table = new AphrontTableView($rows); $table->setHeaders( array( 'ID', 'Revision', 'Status', 'Lines', 'Author', 'Reviewers', 'Updated', 'Created', )); $table->setColumnClasses( array( null, 'wide', null, null, null, null, null, null, )); if ($nodata !== null) { $table->setNoDataString($nodata); } $panel = new AphrontPanelView(); $panel->setHeader($header); $panel->appendChild($table); return $panel; } } diff --git a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php index 75bac79d0e..78cf947987 100644 --- a/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php +++ b/src/applications/differential/controller/revisionview/DifferentialRevisionViewController.php @@ -1,1731 +1,1776 @@ revisionID = $data['id']; } public function processRequest() { $request = $this->getRequest(); $revision = id(new DifferentialRevision())->load($this->revisionID); if (!$revision) { return new Aphront404Response(); } $revision->loadRelationships(); $diffs = $revision->loadDiffs(); $target = end($diffs); $changesets = $target->loadChangesets(); $comments = $revision->loadComments(); $comments = array_merge( $this->getImplicitComments($revision), $comments); + $inlines = $this->loadInlineComments($comments, $changesets); + $object_phids = array_merge( $revision->getReviewers(), $revision->getCCPHIDs(), array( $revision->getAuthorPHID(), $request->getUser()->getPHID(), ), mpull($comments, 'getAuthorPHID')); + $object_phids = array_unique($object_phids); $handles = id(new PhabricatorObjectHandleData($object_phids)) ->loadHandles(); $revision_detail = new DifferentialRevisionDetailView(); $revision_detail->setRevision($revision); $properties = $this->getRevisionProperties($revision, $target, $handles); $revision_detail->setProperties($properties); $actions = $this->getRevisionActions($revision); $revision_detail->setActions($actions); $comment_view = new DifferentialRevisionCommentListView(); $comment_view->setComments($comments); $comment_view->setHandles($handles); + $comment_view->setInlineComments($inlines); + $comment_view->setChangesets($changesets); $diff_history = new DifferentialRevisionUpdateHistoryView(); $diff_history->setDiffs($diffs); $toc_view = new DifferentialDiffTableOfContentsView(); $toc_view->setChangesets($changesets); $changeset_view = new DifferentialChangesetListView(); $changeset_view->setChangesets($changesets); $changeset_view->setEditable(true); $changeset_view->setRevision($revision); $comment_form = new DifferentialAddCommentView(); $comment_form->setRevision($revision); $comment_form->setActions($this->getRevisionCommentActions($revision)); $comment_form->setActionURI('/differential/comment/save/'); $comment_form->setUser($request->getUser()); return $this->buildStandardPageResponse( '
'. $revision_detail->render(). $comment_view->render(). $diff_history->render(). $toc_view->render(). $changeset_view->render(). $comment_form->render(). '
', array( 'title' => $revision->getTitle(), )); } private function getImplicitComments(DifferentialRevision $revision) { $template = new DifferentialComment(); $template->setAuthorPHID($revision->getAuthorPHID()); $template->setRevisionID($revision->getID()); $template->setDateCreated($revision->getDateCreated()); $comments = array(); if (strlen($revision->getSummary())) { $summary_comment = clone $template; $summary_comment->setContent($revision->getSummary()); $summary_comment->setAction(DifferentialAction::ACTION_SUMMARIZE); $comments[] = $summary_comment; } if (strlen($revision->getTestPlan())) { $testplan_comment = clone $template; $testplan_comment->setContent($revision->getTestPlan()); $testplan_comment->setAction(DifferentialAction::ACTION_TESTPLAN); $comments[] = $testplan_comment; } return $comments; } private function getRevisionProperties( DifferentialRevision $revision, DifferentialDiff $diff, array $handles) { $properties = array(); $status = $revision->getStatus(); $status = DifferentialRevisionStatus::getNameForRevisionStatus($status); $properties['Revision Status'] = ''.$status.''; $author = $handles[$revision->getAuthorPHID()]; $properties['Author'] = $author->renderLink(); $properties['Reviewers'] = $this->renderHandleLinkList( array_select_keys( $handles, $revision->getReviewers())); $properties['CCs'] = $this->renderHandleLinkList( array_select_keys( $handles, $revision->getCCPHIDs())); $path = $diff->getSourcePath(); if ($path) { $branch = $diff->getBranch() ? ' (' . $diff->getBranch() . ')' : ''; $host = $diff->getSourceMachine(); if ($host) { $host .= ':'; } $properties['Path'] = phutil_escape_html("{$host}{$path} {$branch}"); } $properties['Lint'] = 'TODO'; $properties['Unit'] = 'TODO'; return $properties; } private function getRevisionActions(DifferentialRevision $revision) { $viewer_phid = $this->getRequest()->getUser()->getPHID(); $viewer_is_owner = ($revision->getAuthorPHID() == $viewer_phid); $viewer_is_reviewer = in_array($viewer_phid, $revision->getReviewers()); $viewer_is_cc = in_array($viewer_phid, $revision->getCCPHIDs()); $status = $revision->getStatus(); $revision_id = $revision->getID(); $revision_phid = $revision->getPHID(); $links = array(); if ($viewer_is_owner) { $links[] = array( 'class' => 'revision-edit', 'href' => "/differential/revision/edit/{$revision_id}/", 'name' => 'Edit Revision', ); } if (!$viewer_is_owner && !$viewer_is_reviewer) { $action = $viewer_is_cc ? 'rem' : 'add'; $links[] = array( 'class' => $viewer_is_cc ? 'subscribe-rem' : 'subscribe-add', 'href' => "/differential/subscribe/{$action}/{$revision_id}/", 'name' => $viewer_is_cc ? 'Unsubscribe' : 'Subscribe', ); } else { $links[] = array( 'class' => 'subscribe-rem unavailable', 'name' => 'Automatically Subscribed', ); } $links[] = array( 'class' => 'transcripts-metamta', 'name' => 'MetaMTA Transcripts', 'href' => "/mail/?phid={$revision_phid}", ); return $links; } private function renderHandleLinkList(array $list) { if (empty($list)) { return 'None'; } return implode(', ', mpull($list, 'renderLink')); } private function getRevisionCommentActions(DifferentialRevision $revision) { $actions = array( DifferentialAction::ACTION_COMMENT => true, ); $viewer_phid = $this->getRequest()->getUser()->getPHID(); $viewer_is_owner = ($viewer_phid == $revision->getAuthorPHID()); if ($viewer_is_owner) { switch ($revision->getStatus()) { case DifferentialRevisionStatus::NEEDS_REVIEW: $actions[DifferentialAction::ACTION_ABANDON] = true; break; case DifferentialRevisionStatus::NEEDS_REVISION: case DifferentialRevisionStatus::ACCEPTED: $actions[DifferentialAction::ACTION_ABANDON] = true; $actions[DifferentialAction::ACTION_REQUEST] = true; break; case DifferentialRevisionStatus::COMMITTED: break; case DifferentialRevisionStatus::ABANDONED: $actions[DifferentialAction::ACTION_RECLAIM] = true; break; } } else { switch ($revision->getStatus()) { case DifferentialRevisionStatus::NEEDS_REVIEW: $actions[DifferentialAction::ACTION_ACCEPT] = true; $actions[DifferentialAction::ACTION_REJECT] = true; break; case DifferentialRevisionStatus::NEEDS_REVISION: $actions[DifferentialAction::ACTION_ACCEPT] = true; break; case DifferentialRevisionStatus::ACCEPTED: $actions[DifferentialAction::ACTION_REJECT] = true; break; case DifferentialRevisionStatus::COMMITTED: case DifferentialRevisionStatus::ABANDONED: break; } } $actions[DifferentialAction::ACTION_ADDREVIEWERS] = true; return array_keys($actions); } + private function loadInlineComments(array $comments, array &$changesets) { + + $inline_comments = array(); + + $comment_ids = array_filter(mpull($comments, 'getID')); + if (!$comment_ids) { + return $inline_comments; + } + + $inline_comments = id(new DifferentialInlineComment()) + ->loadAllWhere( + 'commentID in (%Ld)', + $comment_ids); + + $load_changesets = array(); + foreach ($inline_comments as $inline) { + $changeset_id = $inline->getChangesetID(); + if (isset($changesets[$changeset_id])) { + continue; + } + $load_changesets[$changeset_id] = true; + } + + $more_changesets = array(); + if ($load_changesets) { + $changeset_ids = array_keys($load_changesets); + $more_changesets += id(new DifferentialChangeset()) + ->loadAllWhere( + 'id IN (%Ld)', + $changeset_ids); + } + + if ($more_changesets) { + $changesets += $more_changesets; + $changesets = msort($changesets, 'getSortKey'); + } + + return $inline_comments; + } + } /* protected function getRevisionActions(DifferentialRevision $revision) { $viewer_id = $this->getRequest()->getViewerContext()->getUserID(); $viewer_is_owner = ($viewer_id == $revision->getOwnerID()); $viewer_is_reviewer = ((array_search($viewer_id, $revision->getReviewers())) !== false); $viewer_is_cc = ((array_search($viewer_id, $revision->getCCFBIDs())) !== false); $status = $revision->getStatus(); $links = array(); if (!$viewer_is_owner && !$viewer_is_reviewer) { $action = $viewer_is_cc ? 'rem' : 'add'; $revision_id = $revision->getID(); $href = "/differential/subscribe/{$action}/{$revision_id}"; $links[] = array( $viewer_is_cc ? 'subscribe-disabled' : 'subscribe-enabled', {$viewer_is_cc ? 'Unsubscribe' : 'Subscribe'}, ); } else { $links[] = array( 'subscribe-disabled unavailable', Automatically Subscribed, ); } $blast_uri = RedirectURI( '/intern/differential/?action=tasks&fbid='.$revision->getFBID()) ->setTier('intern'); $links[] = array( 'tasks', Edit Tasks, ); $engineering_repository_id = RepositoryRef::getByCallsign('E')->getID(); $svn_revision = $revision->getSVNRevision(); if ($status == DifferentialConstants::COMMITTED && $svn_revision && $revision->getRepositoryID() == $engineering_repository_id) { $href = '/intern/push/request.php?rev='.$svn_revision; $href = RedirectURI($href)->setTier('intern'); $links[] = array( 'merge', Ask for Merge, ); } $links[] = array( 'herald-transcript', getFBID()} >Herald Transcripts, ); $links[] = array( 'metamta-transcript', getFBID()} >MetaMTA Transcripts, ); $list =
    ; foreach ($links as $link) { list($class, $tag) = $link; $list->appendChild(
  • {$tag}
  • ); } return $list; /* // TODO // $sandcastle = $this->getSandcastleURI($diff); // if ($sandcastle) { // $fields['Sandcastle'] = {$sandcastle}; // } $path = $diff->getSourcePath(); if ($path) { $host = $diff->getSourceMachine(); $branch = $diff->getGitBranch() ? ' (' . $diff->getGitBranch() . ')' : ''; if ($host) { // TODO // $user = $handles[$this->getRequest()->getViewerContext()->getUserID()] // ->getName(); $user = 'TODO'; $fields['Path'] = {$host}:{$path}{$branch} ; } else { $fields['Path'] = $path; } } $reviewer_links = array(); foreach ($revision->getReviewers() as $reviewer) { $reviewer_links[] = ; } if ($reviewer_links) { $fields['Reviewers'] = array_implode(', ', $reviewer_links); } else { $fields['Reviewers'] = None; } $ccs = $revision->getCCFBIDs(); if ($ccs) { $links = array(); foreach ($ccs as $cc) { $links[] = ; } $fields['CCs'] = array_implode(', ', $links); } $blame_rev = $revision->getSvnBlameRevision(); if ($blame_rev) { if ($revision->getRepositoryRef() && is_numeric($blame_rev)) { $ref = new RevisionRef($revision->getRepositoryRef(), $blame_rev); $fields['Blame Revision'] = getDetailURL())}> {$ref->getName()} ; } else { $fields['Blame Revision'] = $blame_rev; } } $tasks = $revision->getTaskHandles(); if ($tasks) { $links = array(); foreach ($tasks as $task) { $links[] = ; } $fields['Tasks'] = array_implode(
    , $links); } $bugzilla_id = $revision->getBugzillaID(); if ($bugzilla_id) { $href = 'http://bugs.developers.facebook.com/show_bug.cgi?id='. $bugzilla_id; $fields['Bugzilla'] = {'#'.$bugzilla_id}; } $fields['Apply Patch'] = arc patch --revision {$revision->getID()}; if ($diff->getParentRevisionID()) { $parent = id(new DifferentialRevision())->load( $diff->getParentRevisionID()); if ($parent) { $fields['Depends On'] = getURI()}> D{$parent->getID()}: {$parent->getName()} ; } } $star = {"\xE2\x98\x85"}; Javelin::initBehavior('differential-star-more'); switch ($diff->getLinted()) { case Diff::LINT_FAIL: $more = $this->renderDiffPropertyMoreLink($diff, 'lint'); $fields['Lint'] = {$star} Lint Failures {$more} ; break; case Diff::LINT_WARNINGS: $more = $this->renderDiffPropertyMoreLink($diff, 'lint'); $fields['Lint'] = {$star} Lint Warnings {$more} ; break; case Diff::LINT_OKAY: $fields['Lint'] = {$star} Lint Free; break; default: case Diff::LINT_NO: $fields['Lint'] = {$star} Not Linted; break; } $unit_details = false; switch ($diff->getUnitTested()) { case Diff::UNIT_FAIL: $fields['Unit Tests'] = {$star} Unit Test Failures; $unit_details = true; break; case Diff::UNIT_WARN: $fields['Unit Tests'] = {$star} Unit Test Warnings; $unit_details = true; break; case Diff::UNIT_OKAY: $fields['Unit Tests'] = {$star} Unit Tests Passed; $unit_details = true; break; case Diff::UNIT_NO_TESTS: $fields['Unit Tests'] = {$star} No Test Coverage; break; case Diff::UNIT_NO: default: $fields['Unit Tests'] = {$star} Not Unit Tested; break; } if ($unit_details) { $fields['Unit Tests'] = {$fields['Unit Tests']} {$this->renderDiffPropertyMoreLink($diff, 'unit')} ; } $platform_impact = $revision->getPlatformImpact(); if ($platform_impact) { $fields['Platform Impact'] = {$platform_impact}; } return $fields; } } /* protected function getSandcastleURI(Diff $diff) { $uri = $this->getDiffProperty($diff, 'facebook:sandcastle_uri'); if (!$uri) { $uri = $diff->getSandboxURL(); } return $uri; } protected function getDiffProperty(Diff $diff, $property, $default = null) { $diff_id = $diff->getID(); if (empty($this->diffProperties[$diff_id])) { $props = id(new DifferentialDiffProperty()) ->loadAllWhere('diffID = %s', $diff_id); $dict = array_pull($props, 'getData', 'getName'); $this->diffProperties[$diff_id] = $dict; } return idx($this->diffProperties[$diff_id], $property, $default); } public function process() { $uri = $this->getRequest()->getPath(); if (starts_with($uri, '/d')) { return ; } $revision = id(new DifferentialRevision())->load($this->revisionID); if (!$revision) { throw new Exception("Bad revision ID."); } $diffs = id(new Diff())->loadAllWhere( 'revisionID = %d', $revision->getID()); $diffs = array_psort($diffs, 'getID'); $request = $this->getRequest(); $new = $request->getInt('new'); $old = $request->getInt('old'); if (($new || $old) && $new <= $old) { throw new Exception( "You can only view the diff of an older update relative to a newer ". "update."); } if ($new && empty($diffs[$new])) { throw new Exception( "The 'new' diff does not exist."); } else if ($new) { $diff = $diffs[$new]; } else { $diff = end($diffs); if (!$diff) { throw new Exception("No diff attached to this revision?"); } $new = $diff->getID(); } $target_diff = $diff; if ($old && empty($diffs[$old])) { throw new Exception( "The 'old' diff does not exist."); } $rows = array(array('Base', '', true, false, null, $diff->getSourceControlBaseRevision() ? $diff->getSourceControlBaseRevision() : Master)); $idx = 0; foreach ($diffs as $cdiff) { $rows[] = array( 'Diff '.(++$idx), $cdiff->getID(), $cdiff->getID() != max(array_pull($diffs, 'getID')), true, $cdiff->getDateCreated(), $cdiff->getDescription() ? $cdiff->getDescription() : No description available., $cdiff->getUnitTested(), $cdiff->getLinted()); } $diff_table =
    Diff Diff ID Description Age Lint Unit
    ; $ii = 0; $old_ids = array(); foreach ($rows as $row) { $xold = null; if ($row[2]) { $lradio = = $new} checked={$old == $row[1]} />; if ($old == $row[1]) { $xold = 'old-now'; } $old_ids[] = $lradio->requireUniqueID(); } else { $lradio = null; } $xnew = null; if ($row[3]) { $rradio = ; if ($new == $row[1]) { $xnew = 'new-now'; } } else { $rradio = null; } if ($row[3]) { $unit_star = 'star-none'; switch ($row[6]) { case Diff::UNIT_FAIL: case Diff::UNIT_WARN: $unit_star = 'star-warn'; break; case Diff::UNIT_OKAY: $unit_star = 'star-okay'; break; } $lint_star = 'star-none'; switch ($row[7]) { case Diff::LINT_FAIL: case Diff::LINT_WARNINGS: $lint_star = 'star-warn'; break; case Diff::LINT_OKAY: $lint_star = 'star-okay'; break; } $star = "\xE2\x98\x85"; $unit_star = {$star} ; $lint_star = {$star} ; } else { $unit_star = null; $lint_star = null; } $diff_table->appendChild( {$row[0]} {$row[1]} {$row[5]} {$row[4] ? ago(time() - $row[4]) : null} {$lint_star} {$unit_star} {$lradio} {$rradio} ); } Javelin::initBehavior('differential-diff-radios', array( 'radios' => $old_ids, )); $diff_table->appendChild( {id() ->setOptions($actions)} {$content} {$preview} ; $notice = null; if ($this->getRequest()->getBool('diff_changed')) { $notice = This revision was updated with a new diff while you were providing feedback. Your inline comments appear on the old diff. ; } return getName()}>
    {$warning} {$notice} {$info} {$diff_table} {$table_of_contents} {$against_warn} {$detail_view} {$feedback_form}
    ; } protected function getQuickLinks(DifferentialRevision $revision) { $viewer_id = $this->getRequest()->getViewerContext()->getUserID(); $viewer_is_owner = ($viewer_id == $revision->getOwnerID()); $viewer_is_reviewer = ((array_search($viewer_id, $revision->getReviewers())) !== false); $viewer_is_cc = ((array_search($viewer_id, $revision->getCCFBIDs())) !== false); $status = $revision->getStatus(); $links = array(); if (!$viewer_is_owner && !$viewer_is_reviewer) { $action = $viewer_is_cc ? 'rem' : 'add'; $revision_id = $revision->getID(); $href = "/differential/subscribe/{$action}/{$revision_id}"; $links[] = array( $viewer_is_cc ? 'subscribe-disabled' : 'subscribe-enabled', {$viewer_is_cc ? 'Unsubscribe' : 'Subscribe'}, ); } else { $links[] = array( 'subscribe-disabled unavailable', Automatically Subscribed, ); } $blast_uri = RedirectURI( '/intern/differential/?action=blast&fbid='.$revision->getFBID()) ->setTier('intern'); $links[] = array( 'blast', Blast Revision, ); $blast_uri = RedirectURI( '/intern/differential/?action=tasks&fbid='.$revision->getFBID()) ->setTier('intern'); $links[] = array( 'tasks', Edit Tasks, ); if ($viewer_is_owner && false) { $perflab_uri = RedirectURI( '/intern/differential/?action=perflab&fbid='.$revision->getFBID()) ->setTier('intern'); $links[] = array( 'perflab', Run in Perflab, ); } $engineering_repository_id = RepositoryRef::getByCallsign('E')->getID(); $svn_revision = $revision->getSVNRevision(); if ($status == DifferentialConstants::COMMITTED && $svn_revision && $revision->getRepositoryID() == $engineering_repository_id) { $href = '/intern/push/request.php?rev='.$svn_revision; $href = RedirectURI($href)->setTier('intern'); $links[] = array( 'merge', Ask for Merge, ); } $links[] = array( 'herald-transcript', getFBID()} >Herald Transcripts, ); $links[] = array( 'metamta-transcript', getFBID()} >MetaMTA Transcripts, ); $list =
      ; foreach ($links as $link) { list($class, $tag) = $link; $list->appendChild(
    • {$tag}
    • ); } return $list; } protected function getDetailFields( DifferentialRevision $revision, Diff $diff, array $handles) { $fields = array(); $fields['Revision Status'] = $this->getRevisionStatusDisplay($revision); $author = $revision->getOwnerID(); $fields['Author'] = ; $sandcastle = $this->getSandcastleURI($diff); if ($sandcastle) { $fields['Sandcastle'] = {$sandcastle}; } $path = $diff->getSourcePath(); if ($path) { $host = $diff->getSourceMachine(); $branch = $diff->getGitBranch() ? ' (' . $diff->getGitBranch() . ')' : ''; if ($host) { $user = $handles[$this->getRequest()->getViewerContext()->getUserID()] ->getName(); $fields['Path'] = {$host}:{$path}{$branch} ; } else { $fields['Path'] = $path; } } $reviewer_links = array(); foreach ($revision->getReviewers() as $reviewer) { $reviewer_links[] = ; } if ($reviewer_links) { $fields['Reviewers'] = array_implode(', ', $reviewer_links); } else { $fields['Reviewers'] = None; } $ccs = $revision->getCCFBIDs(); if ($ccs) { $links = array(); foreach ($ccs as $cc) { $links[] = ; } $fields['CCs'] = array_implode(', ', $links); } $blame_rev = $revision->getSvnBlameRevision(); if ($blame_rev) { if ($revision->getRepositoryRef() && is_numeric($blame_rev)) { $ref = new RevisionRef($revision->getRepositoryRef(), $blame_rev); $fields['Blame Revision'] = getDetailURL())}> {$ref->getName()} ; } else { $fields['Blame Revision'] = $blame_rev; } } $tasks = $revision->getTaskHandles(); if ($tasks) { $links = array(); foreach ($tasks as $task) { $links[] = ; } $fields['Tasks'] = array_implode(
      , $links); } $bugzilla_id = $revision->getBugzillaID(); if ($bugzilla_id) { $href = 'http://bugs.developers.facebook.com/show_bug.cgi?id='. $bugzilla_id; $fields['Bugzilla'] = {'#'.$bugzilla_id}; } $fields['Apply Patch'] = arc patch --revision {$revision->getID()}; if ($diff->getParentRevisionID()) { $parent = id(new DifferentialRevision())->load( $diff->getParentRevisionID()); if ($parent) { $fields['Depends On'] = getURI()}> D{$parent->getID()}: {$parent->getName()} ; } } $star = {"\xE2\x98\x85"}; Javelin::initBehavior('differential-star-more'); switch ($diff->getLinted()) { case Diff::LINT_FAIL: $more = $this->renderDiffPropertyMoreLink($diff, 'lint'); $fields['Lint'] = {$star} Lint Failures {$more} ; break; case Diff::LINT_WARNINGS: $more = $this->renderDiffPropertyMoreLink($diff, 'lint'); $fields['Lint'] = {$star} Lint Warnings {$more} ; break; case Diff::LINT_OKAY: $fields['Lint'] = {$star} Lint Free; break; default: case Diff::LINT_NO: $fields['Lint'] = {$star} Not Linted; break; } $unit_details = false; switch ($diff->getUnitTested()) { case Diff::UNIT_FAIL: $fields['Unit Tests'] = {$star} Unit Test Failures; $unit_details = true; break; case Diff::UNIT_WARN: $fields['Unit Tests'] = {$star} Unit Test Warnings; $unit_details = true; break; case Diff::UNIT_OKAY: $fields['Unit Tests'] = {$star} Unit Tests Passed; $unit_details = true; break; case Diff::UNIT_NO_TESTS: $fields['Unit Tests'] = {$star} No Test Coverage; break; case Diff::UNIT_NO: default: $fields['Unit Tests'] = {$star} Not Unit Tested; break; } if ($unit_details) { $fields['Unit Tests'] = {$fields['Unit Tests']} {$this->renderDiffPropertyMoreLink($diff, 'unit')} ; } $platform_impact = $revision->getPlatformImpact(); if ($platform_impact) { $fields['Platform Impact'] = {$platform_impact}; } return $fields; } protected function renderDiffPropertyMoreLink(Diff $diff, $name) { $target = ; $meta = array( 'target' => $target->requireUniqueID(), 'uri' => '/differential/diffprop/'.$diff->getID().'/'.$name.'/', ); $more = · Show Details ; return {$more}{$target}; } protected function loadInlineComments(array $feedback, array &$changesets) { $inline_comments = array(); $feedback_ids = array_filter(array_pull($feedback, 'getID')); if (!$feedback_ids) { return $inline_comments; } $inline_comments = id(new DifferentialInlineComment()) ->loadAllWhere('feedbackID in (%Ld)', $feedback_ids); $load_changesets = array(); $load_hunks = array(); foreach ($inline_comments as $inline) { $changeset_id = $inline->getChangesetID(); if (isset($changesets[$changeset_id])) { continue; } $load_changesets[$changeset_id] = true; } $more_changesets = array(); if ($load_changesets) { $changeset_ids = array_keys($load_changesets); $more_changesets += id(new DifferentialChangeset()) ->loadAllWithIDs($changeset_ids); } if ($more_changesets) { $changesets += $more_changesets; $changesets = array_psort($changesets, 'getSortKey'); } return $inline_comments; } protected function getRevisionStatusDisplay(DifferentialRevision $revision) { $viewer_id = $this->getRequest()->getViewerContext()->getUserID(); $viewer_is_owner = ($viewer_id == $revision->getOwnerID()); $status = $revision->getStatus(); $more = null; switch ($status) { case DifferentialConstants::NEEDS_REVIEW: $message = 'Pending Review'; break; case DifferentialConstants::NEEDS_REVISION: $message = 'Awaiting Revision'; if ($viewer_is_owner) { $more = 'Make the requested changes and update the revision.'; } break; case DifferentialConstants::ACCEPTED: $message = 'Ready for Commit'; if ($viewer_is_owner) { $more = Run arc commit (svn) or arc amend (git) to proceed. ; } break; case DifferentialConstants::COMMITTED: $message = 'Committed'; $ref = $revision->getRevisionRef(); $more = $ref ? (getDetailURL())}> {$ref->getName()} ) : null; $engineering_repository_id = RepositoryRef::getByCallsign('E')->getID(); if ($revision->getSVNRevision() && $revision->getRepositoryID() == $engineering_repository_id) { Javelin::initBehavior( 'differential-revtracker-status', array( 'uri' => '/differential/revtracker/'.$revision->getID().'/', 'statusId' => 'revtracker_status', 'mergeLinkId' => 'ask_for_merge_link', )); } break; case DifferentialConstants::ABANDONED: $message = 'Abandoned'; break; default: throw new Exception("Unknown revision status."); } if ($more) { $message = {$message} · {$more} ; } else { $message = {$message}; } return $message; } protected function renderFeedbackList(array $xhp, array $obj, $viewer_id) { // Use magical heuristics to try to hide older comments. $obj = array_reverse($obj); $obj = array_values($obj); $xhp = array_reverse($xhp); $xhp = array_values($xhp); $last_comment = null; foreach ($obj as $position => $feedback) { if ($feedback->getUserID() == $viewer_id) { if ($last_comment === null) { $last_comment = $position; } else if ($last_comment == $position - 1) { // If you made consecuitive comments, show them all. This is a spaz // rule for epriestley comments. $last_comment = $position; } } } $header = array(); $hide = array(); if ($last_comment !== null) { foreach ($obj as $position => $feedback) { $action = $feedback->getAction(); if ($action == 'testplan' || $action == 'summarize') { // Always show summary and test plan. $header[] = $xhp[$position]; unset($xhp[$position]); continue; } if ($position <= $last_comment) { // Always show comments after your last comment. continue; } if ($position < 3) { // Always show the most recent 3 comments. continue; } // Hide everything else. $hide[] = $position; } } if (count($hide) <= 3) { // Don't hide if there's not much to hide. $hide = array(); } $header = array_reverse($header); $hidden = array_select_keys($xhp, $hide); $visible = array_diff_key($xhp, $hidden); $visible = array_reverse($visible); $hidden = array_reverse($hidden); if ($hidden) { Javelin::initBehavior( 'differential-show-all-feedback', array( 'markup' => id({$hidden})->toString(), )); $hidden =
      {number_format(count($hidden))} older replies are hidden. Show all feedback.
      ; } else { $hidden = null; } return {$header} {$hidden} {$visible} ; } } protected function getDetailFields( DifferentialRevision $revision, Diff $diff, array $handles) { $fields = array(); $fields['Revision Status'] = $this->getRevisionStatusDisplay($revision); $author = $revision->getOwnerID(); $fields['Author'] = ; $sandcastle = $this->getSandcastleURI($diff); if ($sandcastle) { $fields['Sandcastle'] = {$sandcastle}; } $path = $diff->getSourcePath(); if ($path) { $host = $diff->getSourceMachine(); $branch = $diff->getGitBranch() ? ' (' . $diff->getGitBranch() . ')' : ''; if ($host) { $user = $handles[$this->getRequest()->getViewerContext()->getUserID()] ->getName(); $fields['Path'] = {$host}:{$path}{$branch} ; } else { $fields['Path'] = $path; } } $reviewer_links = array(); foreach ($revision->getReviewers() as $reviewer) { $reviewer_links[] = ; } if ($reviewer_links) { $fields['Reviewers'] = array_implode(', ', $reviewer_links); } else { $fields['Reviewers'] = None; } $ccs = $revision->getCCFBIDs(); if ($ccs) { $links = array(); foreach ($ccs as $cc) { $links[] = ; } $fields['CCs'] = array_implode(', ', $links); } $blame_rev = $revision->getSvnBlameRevision(); if ($blame_rev) { if ($revision->getRepositoryRef() && is_numeric($blame_rev)) { $ref = new RevisionRef($revision->getRepositoryRef(), $blame_rev); $fields['Blame Revision'] = getDetailURL())}> {$ref->getName()} ; } else { $fields['Blame Revision'] = $blame_rev; } } $tasks = $revision->getTaskHandles(); if ($tasks) { $links = array(); foreach ($tasks as $task) { $links[] = ; } $fields['Tasks'] = array_implode(
      , $links); } $bugzilla_id = $revision->getBugzillaID(); if ($bugzilla_id) { $href = 'http://bugs.developers.facebook.com/show_bug.cgi?id='. $bugzilla_id; $fields['Bugzilla'] = {'#'.$bugzilla_id}; } $fields['Apply Patch'] = arc patch --revision {$revision->getID()}; if ($diff->getParentRevisionID()) { $parent = id(new DifferentialRevision())->load( $diff->getParentRevisionID()); if ($parent) { $fields['Depends On'] = getURI()}> D{$parent->getID()}: {$parent->getName()} ; } } $star = {"\xE2\x98\x85"}; Javelin::initBehavior('differential-star-more'); switch ($diff->getLinted()) { case Diff::LINT_FAIL: $more = $this->renderDiffPropertyMoreLink($diff, 'lint'); $fields['Lint'] = {$star} Lint Failures {$more} ; break; case Diff::LINT_WARNINGS: $more = $this->renderDiffPropertyMoreLink($diff, 'lint'); $fields['Lint'] = {$star} Lint Warnings {$more} ; break; case Diff::LINT_OKAY: $fields['Lint'] = {$star} Lint Free; break; default: case Diff::LINT_NO: $fields['Lint'] = {$star} Not Linted; break; } $unit_details = false; switch ($diff->getUnitTested()) { case Diff::UNIT_FAIL: $fields['Unit Tests'] = {$star} Unit Test Failures; $unit_details = true; break; case Diff::UNIT_WARN: $fields['Unit Tests'] = {$star} Unit Test Warnings; $unit_details = true; break; case Diff::UNIT_OKAY: $fields['Unit Tests'] = {$star} Unit Tests Passed; $unit_details = true; break; case Diff::UNIT_NO_TESTS: $fields['Unit Tests'] = {$star} No Test Coverage; break; case Diff::UNIT_NO: default: $fields['Unit Tests'] = {$star} Not Unit Tested; break; } if ($unit_details) { $fields['Unit Tests'] = {$fields['Unit Tests']} {$this->renderDiffPropertyMoreLink($diff, 'unit')} ; } $platform_impact = $revision->getPlatformImpact(); if ($platform_impact) { $fields['Platform Impact'] = {$platform_impact}; } return $fields; } */ diff --git a/src/applications/differential/controller/revisionview/__init__.php b/src/applications/differential/controller/revisionview/__init__.php index 04ea6dd3ac..d5e441eefd 100644 --- a/src/applications/differential/controller/revisionview/__init__.php +++ b/src/applications/differential/controller/revisionview/__init__.php @@ -1,27 +1,29 @@ filter = $filter; $this->ids = $ids; } public function getRevisions() { return $this->revisions; } public function setOrder($order) { $this->order = $order; return $this; } public function loadRevisions() { switch ($this->filter) { case self::QUERY_CC: $this->revisions = $this->loadAllOpenWithCCs($this->ids); break; case self::QUERY_ALL_OPEN: $this->revisions = $this->loadAllOpen(); break; case self::QUERY_OPEN_OWNED: $this->revisions = $this->loadAllWhere( 'revision.status in (%Ld) AND revision.authorPHID in (%Ls)', $this->getOpenStatuses(), $this->ids); break; case self::QUERY_COMMITTABLE: $this->revisions = $this->loadAllWhere( 'revision.status in (%Ld) AND revision.authorPHID in (%Ls)', array( DifferentialRevisionStatus::ACCEPTED, ), $this->ids); break; case self::QUERY_REVISION_IDS: $this->revisions = $this->loadAllWhere( 'id in (%Ld)', $this->ids); break; case self::QUERY_OPEN_REVIEWER: $this->revisions = $this->loadAllWhereJoinReview( 'revision.status in (%Ld) AND relationship.objectPHID in (%Ls)', $this->getOpenStatuses(), $this->ids); break; case self::QUERY_OWNED: $this->revisions = $this->loadAllWhere( 'revision.authorPHID in (%Ls)', $this->ids); break; case self::QUERY_OWNED_OR_REVIEWER: $this->revisions = $this->loadAllWhereJoinReview( 'revision.authorPHID in (%Ls) OR relationship.objectPHID in (%Ls)', $this->ids, $this->ids); break; case self::QUERY_NEED_ACTION_FROM_SELF: $rev = new DifferentialRevision(); $data = queryfx_all( $rev->establishConnection('r'), 'SELECT revision.* FROM %T revision WHERE revision.authorPHID in (%Ls) AND revision.status in (%Ld) UNION ALL SELECT revision.* FROM %T revision JOIN %T relationship ON relationship.revisionID = revision.id AND relationship.relation = %s - AND relationship.forbidden = 0 WHERE relationship.objectPHID IN (%Ls) AND revision.status in (%Ld) %Q', $rev->getTableName(), $this->ids, array( DifferentialRevisionStatus::NEEDS_REVISION, DifferentialRevisionStatus::ACCEPTED, ), $rev->getTableName(), DifferentialRevision::RELATIONSHIP_TABLE, DifferentialRevision::RELATION_REVIEWER, $this->ids, array( DifferentialRevisionStatus::NEEDS_REVIEW, ), $this->getOrderClause()); $data = ipull($data, null, 'id'); $this->revisions = $rev->loadAllFromArray($data); break; case self::QUERY_NEED_ACTION_FROM_OTHERS: $rev = new DifferentialRevision(); $data = queryfx_all( $rev->establishConnection('r'), 'SELECT revision.* FROM %T revision WHERE revision.authorPHID in (%Ls) AND revision.status IN (%Ld) UNION ALL SELECT revision.* FROM %T revision JOIN %T relationship ON relationship.revisionID = revision.id AND relationship.relation = %s - AND relationship.forbidden = 0 WHERE relationship.objectPHID IN (%Ls) AND revision.status in (%Ld) %Q', $rev->getTableName(), $this->ids, array( DifferentialRevisionStatus::NEEDS_REVIEW, ), $rev->getTableName(), DifferentialRevision::RELATIONSHIP_TABLE, DifferentialRevision::RELATION_REVIEWER, $this->ids, array( DifferentialRevisionStatus::NEEDS_REVISION, DifferentialRevisionStatus::ACCEPTED, ), $this->getOrderClause()); $data = ipull($data, null, 'id'); $this->revisions = $rev->loadAllFromArray($data); break; case self::QUERY_BY_PHID: $this->revisions = $this->loadAllWhere( 'revision.phid in (%Ls)', $this->ids); break; } return $this->revisions; } private function getOpenStatuses() { return array( DifferentialRevisionStatus::NEEDS_REVIEW, DifferentialRevisionStatus::NEEDS_REVISION, DifferentialRevisionStatus::ACCEPTED, ); } private function loadAllOpen() { return $this->loadAllWhere('status in (%Ld)', $this->getOpenStatuses()); } private function loadAllWhereJoinReview($pattern) { $reviewer = DifferentialRevision::RELATION_REVIEWER; $argv = func_get_args(); $rev = new DifferentialRevision(); $pattern = array_shift($argv); $pattern = 'SELECT revision.* FROM %T revision LEFT JOIN %T relationship ON revision.id = relationship.revisionID AND relationship.relation = %s - AND relationship.forbidden = 0 WHERE '.$pattern.' GROUP BY revision.id '.$this->getOrderClause(); array_unshift( $argv, $rev->getTableName(), DifferentialRevision::RELATIONSHIP_TABLE, DifferentialRevision::RELATION_REVIEWER); $data = vqueryfx_all( $rev->establishConnection('r'), $pattern, $argv); return $rev->loadAllFromArray($data); } private function loadAllWhere($pattern) { $rev = new DifferentialRevision(); $argv = func_get_args(); array_shift($argv); array_unshift($argv, $rev->getTableName()); $data = vqueryfx_all( $rev->establishConnection('r'), 'SELECT * FROM %T revision WHERE '.$pattern.' '.$this->getOrderClause(), $argv); return $rev->loadAllFromArray($data); } private function loadAllOpenWithCCs(array $ccphids) { $revision = new DifferentialRevision(); $data = queryfx_all( 'SELECT revision.* FROM %T revision JOIN %T relationship ON relationship.revisionID = revision.id AND relationship.relation = %s - AND relationship.forbidden = 0 AND relationship.objectPHID in (%Ls) WHERE revision.status in (%Ld) %Q', $revision->getTableName(), DifferentialRevision::RELATIONSHIP_TABLE, DifferentialRevision::RELATION_SUBSCRIBED, $ccphids, $this->getOpenStatuses(), $this->getOrderClause()); return $revision->loadAllFromArray($data); } private function getOrderClause() { $reverse = false; $order = $this->order; if (strlen($order) && $order[0] == '-') { $reverse = true; $order = substr($order, 1); } $asc = $reverse ? 'DESC' : 'ASC'; switch ($order) { case 'ID': $clause = 'id'; break; case 'Revision': $clause = 'name'; break; case 'Status': $clause = 'status'; break; case 'Lines': $clause = 'lineCount'; break; case 'Created': $clause = 'dateCreated'; $asc = $reverse ? 'ASC' : 'DESC'; break; case '': case 'Modified': $clause = 'dateModified'; $asc = $reverse ? 'ASC' : 'DESC'; break; default: throw new Exception("Invalid order '{$order}'."); } return "ORDER BY {$clause} {$asc}"; } } diff --git a/src/applications/differential/editor/comment/DifferentialCommentEditor.php b/src/applications/differential/editor/comment/DifferentialCommentEditor.php index b9e23528ad..6a3e22960c 100755 --- a/src/applications/differential/editor/comment/DifferentialCommentEditor.php +++ b/src/applications/differential/editor/comment/DifferentialCommentEditor.php @@ -1,329 +1,327 @@ revision = $revision; $this->actorPHID = $actor_phid; $this->action = $action; } 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 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); } 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::addReviewers( $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::addReviewers( $revision, $reviewer_phids, $rem = array(), $add = array($actor_phid), $actor_phid); } $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: // This is handled externally. (TODO) 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::addReviewers( $revision, $reviewer_phids, $rem = array(), $add = $added_reviewers, $actor_phid); // TODO // $unixnames = unixname_multi($added_reviewers); $usernames = $added_reviewers; $this->message = 'Added reviewers: '.implode(', ', $usernames)."\n\n". $this->message; } else { $action = DifferentialAction::ACTION_COMMENT; } break; default: throw new Exception('Unsupported action.'); } // Reload relationships to pick up any reviewer changes. $revision->loadRelationships(); -/* - TODO - $inline_comments = array(); if ($this->attachInlineComments) { - $inline_comments = id(new DifferentialInlineComment()) - ->loadAllUnsaved($revision, $this->actorPHID); + $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) ->save(); -/* - $diff = id(new Diff())->loadActiveWithRevision($revision); - $changesets = id(new DifferentialChangeset())->loadAllWithDiff($diff); +// $diff = id(new Diff())->loadActiveWithRevision($revision); +// $changesets = id(new DifferentialChangeset())->loadAllWithDiff($diff); if ($inline_comments) { +/* // We may have feedback on non-current changesets. Rather than orphaning // it, just submit it. This is non-ideal but not horrible. $inline_changeset_ids = array_pull($inline_comments, 'getChangesetID'); $load = array(); foreach ($inline_changeset_ids as $id) { if (empty($changesets[$id])) { $load[] = $id; } } if ($load) { $changesets += id(new DifferentialChangeset())->loadAllWithIDs($load); } +*/ foreach ($inline_comments as $inline) { - $inline->setFeedbackID($feedback->getID()); + $inline->setCommentID($comment->getID()); $inline->save(); } } -*/ id(new DifferentialCommentMail( $revision, $this->actorPHID, $comment, /* $changesets TODO */ array(), /* $inline_comments TODO */ array())) ->setToPHIDs( array_merge( $revision->getReviewers(), array($revision->getAuthorPHID()))) ->setCCPHIDs($revision->getCCPHIDs()) ->setChangedByCommit($this->getChangedByCommit()) ->send(); /* tODO if ($this->addCC) { require_module_lazy('site/tools/differential/lib/editor/revision'); DifferentialRevisionEditor::addCCFBID( $revision, $this->actorPHID, $this->actorPHID); } */ /* TODO $event = array( 'revision_id' => $revision->getID(), 'fbid' => $revision->getFBID(), 'feedback_id' => $feedback->getID(), 'action' => $feedback->getAction(), 'actor' => $this->actorPHID, ); id(new ToolsTimelineEvent('difx', fb_json_encode($event)))->record(); */ return $comment; } } diff --git a/src/applications/differential/editor/comment/__init__.php b/src/applications/differential/editor/comment/__init__.php index 5eb988d3ae..1d14ca7092 100644 --- a/src/applications/differential/editor/comment/__init__.php +++ b/src/applications/differential/editor/comment/__init__.php @@ -1,18 +1,19 @@ revision = $revision; $this->actorPHID = $actor_phid; } /* public static function newRevisionFromRawMessageWithDiff( DifferentialRawMessage $message, Diff $diff, $user) { if ($message->getRevisionID()) { throw new Exception( "The provided commit message is already associated with a ". "Differential revision."); } if ($message->getReviewedByNames()) { throw new Exception( "The provided commit message contains a 'Reviewed By:' field."); } $revision = new DifferentialRevision(); $revision->setPHID($revision->generatePHID()); $revision->setOwnerID($user); $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW); $revision->attachReviewers(array()); $revision->attachCCPHIDs(array()); $editor = new DifferentialRevisionEditor($revision, $user); self::copyFields($editor, $revision, $message, $user); $editor->addDiff($diff, null); $editor->save(); return $revision; } public static function newRevisionFromConduitWithDiff( array $fields, Diff $diff, $user) { $revision = new DifferentialRevision(); $revision->setPHID($revision->generatePHID()); $revision->setOwnerID($user); $revision->setStatus(DifferentialRevisionStatus::NEEDS_REVIEW); $revision->attachReviewers(array()); $revision->attachCCPHIDs(array()); $editor = new DifferentialRevisionEditor($revision, $user); $editor->copyFieldFromConduit($fields); $editor->addDiff($diff, null); $editor->save(); return $revision; } public static function copyFields( DifferentialRevisionEditor $editor, DifferentialRevision $revision, DifferentialRawMessage $message, $user) { $revision->setName($message->getTitle()); $revision->setSummary($message->getSummary()); $revision->setTestPlan($message->getTestPlan()); $revision->setSVNBlameRevision($message->getBlameRevision()); $revision->setRevert($message->getRevertPlan()); $revision->setPlatformImpact($message->getPlatformImpact()); $revision->setBugzillaID($message->getBugzillaID()); $editor->setReviewers($message->getReviewerPHIDs()); $editor->setCCPHIDs($message->getCCPHIDs()); } public function copyFieldFromConduit(array $fields) { $user = $this->actorPHID; $revision = $this->revision; $revision->setName($fields['title']); $revision->setSummary($fields['summary']); $revision->setTestPlan($fields['testPlan']); $revision->setSVNBlameRevision($fields['blameRevision']); $revision->setRevert($fields['revertPlan']); $revision->setPlatformImpact($fields['platformImpact']); $revision->setBugzillaID($fields['bugzillaID']); $this->setReviewers($fields['reviewerGUIDs']); $this->setCCPHIDs($fields['ccGUIDs']); } */ 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 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()); } $revision->save(); } $revision->loadRelationships(); 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()); // TODO! // $revision->setRepositoryID($diff->getRepositoryID()); /* $iface = new DifferentialRevisionHeraldable($revision); $iface->setExplicitCCs($new['ccs']); $iface->setExplicitReviewers($new['rev']); $iface->setForbiddenCCs($revision->getForbiddenCCPHIDs()); $iface->setForbiddenReviewers($revision->getForbiddenReviewers()); $iface->setDiff($diff); $xscript = HeraldEngine::loadAndApplyRules($iface); $xscript_uri = $xscript->getURI(); $xscript_phid = $xscript->getPHID(); $xscript_header = $xscript->getXHeraldRulesHeader(); $sub = array( 'rev' => array(), 'ccs' => $iface->getCCsAddedByHerald(), ); $rem_ccs = $iface->getCCsRemovedByHerald(); */ // TODO! $sub = array( 'rev' => array(), 'ccs' => array(), ); } 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); // Add the author 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; } else { $stable['rev'][$this->getActorPHID()] = true; } } $mail = array(); $changesets = null; $feedback = null; if ($diff) { $changesets = $diff->loadChangesets(); // TODO: move to DifferentialFeedbackEditor if (!$is_new) { // TODO // $feedback = $this->createFeedback(); } if ($feedback) { $mail[] = id(new DifferentialNewDiffMail( $revision, $this->getActorPHID(), $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. // TODO // $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(); // TODO // $revision->saveTransaction(); $event = array( 'revision_id' => $revision->getID(), 'PHID' => $revision->getPHID(), 'action' => $is_new ? 'create' : 'update', 'actor' => $this->getActorPHID(), ); // TODO // id(new ToolsTimelineEvent('difx', fb_json_encode($event)))->record(); if ($this->silentUpdate) { return; } // TODO // $revision->attachReviewers(array_keys($new['rev'])); // $revision->attachCCPHIDs(array_keys($new['ccs'])); 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(); } self::addCCPHID($revision, $id, $reason_phid); } foreach (array_keys($rem['ccs']) as $id) { if (empty($new['ccs'][$id])) { $reason_phid = $this->getActorPHID(); } else { $reason_phid = 'TODO';//$xscript_phid; } self::removeCCPHID($revision, $id, $reason_phid); } } if ($add['rev']) { $message = id(new DifferentialNewDiffMail( $revision, $this->getActorPHID(), $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, $this->getActorPHID(), $changesets)) ->setIsFirstMailToRecipients(true) ->setToPHIDs(array_keys($add['ccs'])); } foreach ($mail as $message) { // TODO // $message->setHeraldTranscriptURI($xscript_uri); // $message->setXHeraldRulesHeader($xscript_header); $message->send(); } } public function addCCPHID( DifferentialRevision $revision, $phid, $reason_phid) { self::alterCCPHID($revision, $phid, true, $reason_phid); } public function removeCCPHID( DifferentialRevision $revision, $phid, $reason_phid) { self::alterCCPHID($revision, $phid, false, $reason_phid); } protected static function alterCCPHID( DifferentialRevision $revision, $phid, $add, $reason_phid) { /* $relationship = new DifferentialRelationship(); $relationship->setRevisionID($revision->getID()); $relationship->setRelation(DifferentialRelationship::RELATION_SUBSCRIBED); $relationship->setRelatedPHID($phid); $relationship->setForbidden(!$add); $relationship->setReasonPHID($reason_phid); $relationship->replace(); */ } public static function alterReviewers( DifferentialRevision $revision, array $stable_phids, array $rem_phids, array $add_phids, $reason_phid) { - + $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(DifferentialRevision::RELATION_REVIEWER); $raw = ipull($raw, null, 'objectPHID'); - + $sequence = count($seq_map); foreach ($raw as $phid => $relation) { if (isset($seq_map[$phid])) { $raw[$phid]['sequence'] = $seq_map[$phid]; } else { $raw[$phid]['sequence'] = $sequence++; } } $raw = isort($raw, 'sequence'); foreach ($raw as $phid => $relation) { 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(), DifferentialRevision::RELATION_REVIEWER, $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(), DifferentialRevision::RELATION_REVIEWER); if ($sql) { queryfx( $conn_w, 'INSERT INTO %T (revisionID, relation, objectPHID, sequence, reasonPHID) VALUES %Q', DifferentialRevision::RELATIONSHIP_TABLE, implode(', ', $sql)); } $conn_w->saveTransaction(); } /* protected function createFeedback() { $revision = $this->getRevision(); $feedback = id(new DifferentialFeedback()) ->setUserID($this->getActorPHID()) ->setRevision($revision) ->setContent($this->getComments()) ->setAction('update'); $feedback->save(); return $feedback; } */ } diff --git a/src/applications/differential/storage/revision/DifferentialRevision.php b/src/applications/differential/storage/revision/DifferentialRevision.php index 0c0a04d512..82834b2402 100755 --- a/src/applications/differential/storage/revision/DifferentialRevision.php +++ b/src/applications/differential/storage/revision/DifferentialRevision.php @@ -1,114 +1,114 @@ true, ) + parent::getConfiguration(); } public function generatePHID() { return PhabricatorPHID::generateNewPHID('DREV'); } public function loadDiffs() { if (!$this->getID()) { return array(); } return id(new DifferentialDiff())->loadAllWhere( 'revisionID = %d', $this->getID()); } public function loadComments() { if (!$this->getID()) { return array(); } return id(new DifferentialComment())->loadAllWhere( 'revisionID = %d', $this->getID()); } public function loadActiveDiff() { return id(new DifferentialDiff())->loadOneWhere( 'revisionID = %d ORDER BY id DESC LIMIT 1', $this->getID()); } public function loadRelationships() { if (!$this->getID()) { $this->relationships = array(); return; } $data = queryfx_all( $this->establishConnection('r'), 'SELECT * FROM %T WHERE revisionID = %d ORDER BY sequence', self::RELATIONSHIP_TABLE, $this->getID()); $this->relationships = igroup($data, 'relation'); return $this; } public function getReviewers() { return $this->getRelatedPHIDs(self::RELATION_REVIEWER); } public function getCCPHIDs() { return $this->getRelatedPHIDs(self::RELATION_SUBSCRIBED); } private function getRelatedPHIDs($relation) { if ($this->relationships === null) { throw new Exception("Must load relationships!"); } - + return ipull($this->getRawRelations($relation), 'objectPHID'); } public function getRawRelations($relation) { return idx($this->relationships, $relation, array()); } } diff --git a/src/applications/differential/storage/revision/__init__.php b/src/applications/differential/storage/revision/__init__.php index 9a227dd210..3720822856 100644 --- a/src/applications/differential/storage/revision/__init__.php +++ b/src/applications/differential/storage/revision/__init__.php @@ -1,19 +1,18 @@ revision = $revision; return $this; } public function setActions(array $actions) { $this->actions = $actions; return $this; } public function setActionURI($uri) { $this->actionURI = $uri; } public function setUser(PhabricatorUser $user) { $this->user = $user; } public function render() { require_celerity_resource('differential-revision-add-comment-css'); $revision = $this->revision; $actions = array(); foreach ($this->actions as $action) { $actions[$action] = DifferentialAction::getActionVerb($action); } $form = new AphrontFormView(); $form ->setUser($this->user) ->setAction($this->actionURI) ->addHiddenInput('revision_id', $revision->getID()) ->appendChild( id(new AphrontFormSelectControl()) ->setLabel('Action') ->setName('action') ->setID('comment-action') ->setOptions($actions)) ->appendChild( id(new AphrontFormTextAreaControl()) ->setName('comment') ->setID('comment-content') ->setLabel('Comment')) ->appendChild( id(new AphrontFormSubmitControl()) ->setValue('Comment')); + $rev_id = $revision->getID(); + Javelin::initBehavior( 'differential-feedback-preview', array( - 'uri' => '/differential/comment/preview/'.$revision->getID().'/', - 'preview' => 'comment-preview', - 'action' => 'comment-action', - 'content' => 'comment-content', + 'uri' => '/differential/comment/preview/'.$rev_id.'/', + 'preview' => 'comment-preview', + 'action' => 'comment-action', + 'content' => 'comment-content', + + 'inlineuri' => '/differential/comment/inline/preview/'.$rev_id.'/', + 'inline' => 'inline-comment-preview', )); return '
      '. '
      '. '

      Add Comment

      '. $form->render(). '
      '. '
      '. '
      '. ''. 'Loading comment preview...'. ''. '
      '. + '
      '. + '
      '. '
      '. '
      '; } } diff --git a/src/applications/differential/view/inlinecomment/DifferentialInlineCommentView.php b/src/applications/differential/view/inlinecomment/DifferentialInlineCommentView.php index a51dd7663a..ca5b6ad5dd 100644 --- a/src/applications/differential/view/inlinecomment/DifferentialInlineCommentView.php +++ b/src/applications/differential/view/inlinecomment/DifferentialInlineCommentView.php @@ -1,159 +1,168 @@ inlineComment = $comment; return $this; } public function setOnRight($on_right) { $this->onRight = $on_right; return $this; } public function setBuildScaffolding($scaffold) { $this->buildScaffolding = $scaffold; return $this; } public function setHandles(array $handles) { $this->handles = $handles; return $this; } public function setMarkupEngine(PhutilMarkupEngine $engine) { $this->markupEngine = $engine; return $this; } public function setEditable($editable) { $this->editable = $editable; return $this; } public function render() { $inline = $this->inlineComment; $start = $inline->getLineNumber(); $length = $inline->getLineLength(); if ($length) { $end = $start + $length; $line = 'Lines '.number_format($start).'-'.number_format($end); } else { $line = 'Line '.number_format($start); } $metadata = array( 'id' => $inline->getID(), 'number' => $inline->getLineNumber(), 'length' => $inline->getLineLength(), 'on_right' => $this->onRight, ); $sigil = 'differential-inline-comment'; $content = $inline->getContent(); $handles = $this->handles; $links = array(); if ($this->editable) { $links[] = javelin_render_tag( 'a', array( 'href' => '#', 'mustcapture' => true, 'sigil' => 'differential-inline-edit', ), 'Edit'); $links[] = javelin_render_tag( 'a', array( 'href' => '#', 'mustcapture' => true, 'sigil' => 'differential-inline-delete', ), 'Delete'); } if ($links) { $links = ''. implode(' · ', $links). ''; } else { $links = null; } - $content = $this->markupEngine->markupText($content); + $cache = $inline->getCache(); + if (strlen($cache)) { + $content = $cache; + } else { + $content = $this->markupEngine->markupText($content); + if ($inline->getID()) { + $inline->setCache($content); + $inline->save(); + } + } $markup = javelin_render_tag( 'div', array( 'class' => 'differential-inline-comment', 'sigil' => $sigil, 'meta' => $metadata, ), '
      '. $links. ''.$line.''. phutil_escape_html($handles[$inline->getAuthorPHID()]->getName()). '
      '. $content); return $this->scaffoldMarkup($markup); } private function scaffoldMarkup($markup) { if (!$this->buildScaffolding) { return $markup; } if ($this->onRight) { return ''. ''. ''. ''. ''. ''. ''. '
      '.$markup.'
      '; } else { return ''. ''. ''. ''. ''. ''. ''. '
      '.$markup.'
      '; } } } diff --git a/src/applications/differential/view/revisioncomment/DifferentialRevisionCommentView.php b/src/applications/differential/view/revisioncomment/DifferentialRevisionCommentView.php index 9c174583fd..6149632244 100644 --- a/src/applications/differential/view/revisioncomment/DifferentialRevisionCommentView.php +++ b/src/applications/differential/view/revisioncomment/DifferentialRevisionCommentView.php @@ -1,116 +1,191 @@ 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 render() { 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 = date('F jS, Y g:i:s A', $comment->getDateCreated()); } $author = $this->handles[$comment->getAuthorPHID()]; $author_link = $author->renderLink(); $verb = DifferentialAction::getActionPastTenseVerb($comment->getAction()); $verb = phutil_escape_html($verb); $content = $comment->getContent(); 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); $comment->save(); } } $content = '
      '. $content. '
      '; } else { $title = null; $content = '
      '. "

      {$author_link} {$verb} this revision.

      ". '
      '; } + 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[] = ''; + foreach ($inlines_by_changeset as $changeset_id => $inlines) { + $changeset = $changesets[$changeset_id]; + $inlines = msort($inlines, 'getLineNumber'); + $inline_render[] = + ''. + ''. + ''; + foreach ($inlines as $inline) { + if (!$inline->getLineLength()) { + $lines = $inline->getLineNumber(); + } else { + $lines = $inline->getLineNumber()."\xE2\x80\x93". + ($inline->getLineNumber() + $inline->getLineLength()); + } + + $lines = phutil_render_tag( + 'a', + array( + 'href' => '#', + 'class' => 'num', + ), + $lines); + + $content = $inline->getCache(); + if (!strlen($content)) { + $content = $this->markupEngine->markupText($content); + if ($inline->getID()) { + $inline->setCache($content); + $inline->save(); + } + } + + $inline_render[] = + ''. + ''. + ''. + ''; + } + } + $inline_render[] = '
      '. + $changeset->getFileName(). + '
      '.$lines.''.$content.'
      '; + $inline_render = implode("\n", $inline_render); + $inline_render = + '
      '. + 'Inline Comments'. + '
      '. + $inline_render; + } else { + $inline_render = null; + } + $background = null; $uri = $author->getImageURI(); if ($uri) { $background = "background-image: url('{$uri}');"; } return '
      '. '
      '. '
      '.$date.'
      '. '
      '.$title.'
      '. '
      '. '
      '. - '
      '. - '
      '. + '
      '. + '
      '. $content. '
      '. + $inline_render. '
      '. '
      '. '
      '; } } diff --git a/src/applications/differential/view/revisioncomment/__init__.php b/src/applications/differential/view/revisioncomment/__init__.php index 2f19fad814..e6fd906027 100644 --- a/src/applications/differential/view/revisioncomment/__init__.php +++ b/src/applications/differential/view/revisioncomment/__init__.php @@ -1,16 +1,17 @@ comments = $comments; return $this; } + public function setInlineComments(array $inline_comments) { + $this->inlines = $inline_comments; + return $this; + } + public function setHandles(array $handles) { $this->handles = $handles; return $this; } + public function setChangesets(array $changesets) { + $this->changesets = $changesets; + return $this; + } + public function render() { require_celerity_resource('differential-revision-comment-list-css'); $factory = new DifferentialMarkupEngineFactory(); $engine = $factory->newDifferentialCommentMarkupEngine(); + $inlines = mgroup($this->inlines, 'getCommentID'); + + $comments = array(); foreach ($this->comments as $comment) { $view = new DifferentialRevisionCommentView(); $view->setComment($comment); $view->setHandles($this->handles); $view->setMarkupEngine($engine); + $view->setInlineComments(idx($inlines, $comment->getID(), array())); + $view->setChangesets($this->changesets); $comments[] = $view->render(); } return '
      '. implode("\n", $comments). '
      '; } } diff --git a/src/applications/differential/view/revisioncommentlist/__init__.php b/src/applications/differential/view/revisioncommentlist/__init__.php index 446ab1fa36..1fe4c1067a 100644 --- a/src/applications/differential/view/revisioncommentlist/__init__.php +++ b/src/applications/differential/view/revisioncommentlist/__init__.php @@ -1,15 +1,17 @@ uri = $uri; return $this; } public function getURI() { return $this->uri; } public function setPHID($phid) { $this->phid = $phid; return $this; } public function getPHID() { return $this->phid; } public function setName($name) { $this->name = $name; return $this; } public function getName() { return $this->name; } public function setFullName($full_name) { $this->fullName = $full_name; return $this; } - + public function getFullName() { return $this->fullName; } public function setType($type) { $this->type = $type; return $this; } public function getType() { return $this->type; } public function setEmail($email) { $this->email = $email; return $this; } public function getEmail() { return $this->email; } public function setImageURI($uri) { $this->imageURI = $uri; return $this; } public function getImageURI() { return $this->imageURI; } public function renderLink() { return phutil_render_tag( 'a', array( 'href' => $this->getURI(), ), phutil_escape_html($this->getName())); } } diff --git a/webroot/rsrc/css/application/differential/add-comment.css b/webroot/rsrc/css/application/differential/add-comment.css index 99be1e9f7f..8ee1dce3d2 100644 --- a/webroot/rsrc/css/application/differential/add-comment.css +++ b/webroot/rsrc/css/application/differential/add-comment.css @@ -1,23 +1,30 @@ /** * @provides differential-revision-add-comment-css */ .differential-add-comment-panel .aphront-form-view { background: #fffff5; } .differential-add-comment-panel .differential-panel { margin-bottom: 0; } .differential-comment-preview { background: #f0f0f0; border-bottom: 1px solid #aaaaaa; margin-bottom: 2em; max-width: 1120px; padding: 15px 20px; } .differential-comment-preview .differential-loading-text { color: #aaaaaa; } + +#inline-comment-preview { + margin-left: 60px; + + width: 88ex; + width: 81ch; +} diff --git a/webroot/rsrc/css/application/differential/revision-comment.css b/webroot/rsrc/css/application/differential/revision-comment.css index 9a3190b425..10277bb499 100644 --- a/webroot/rsrc/css/application/differential/revision-comment.css +++ b/webroot/rsrc/css/application/differential/revision-comment.css @@ -1,94 +1,148 @@ /** * @provides differential-revision-comment-css */ .differential-comment-date { color: #666666; float: right; font-size: 11px; margin: 0em; padding-top: 6px; } .differential-comment-title { font-weight: bold; height: 16px; margin: 4px 0em 0em 0em; padding: 4px 0em; position: relative; } .differential-comment-body { min-height: 56px; padding-left: 62px; background-repeat: no-repeat; background-position: 6px 0px; margin-bottom: 14px; } .differential-comment-content { background: #fbfbfb; border-color: #c3c3c3; border-style: solid; border-width: 1px 10px 1px 10px; clear: both; line-height: 1.4em; margin: 0em; padding: .3em 5px .4em 1.25em; } -.differential-comment-content code { +.differential-comment-core p { + margin: 0.35em 0; +} + +.differential-comment-core code { width: 88ex; width: 81ch; } .differential-comment-nocontent { font-weight: bold; } .differential-comment-action-testplan .differential-comment-content { border-color: #660099; background: #f6f3ff; } .differential-comment-action-abandon .differential-comment-content { border-color: #222222; background: #f3f3f3; } .differential-comment-action-accept .differential-comment-content { border-color: #009966; background: #f3fff3; } .differential-comment-action-reject .differential-comment-content { border-color: #aa0000; background: #fff3f3; } .differential-comment-action-commit .differential-comment-content { border-color: #006699; background: #f3f3ff; } .differential-comment-action-reclaim .differential-comment-content { border-color: #0099aa; background: #f3fff9; } .differential-comment-action-update .differential-comment-content { border-color: #6699cc; background: #f3f9ff; } .differential-comment-action-add_reviewers .differential-comment-content { border-color: #aa99cc; background: #f9f3ff; } .differential-comment-action-request_review .differential-comment-content { border-color: #cc9966; background: #fff9f3; } + + + +.differential-inline-summary th, +.differential-inline-summary td { + vertical-align: top; + padding: 0 0 6px; +} + +.differential-inline-summary th { + padding-top: 16px; + color: #666666; + font-weight: bold; +} + +.differential-inline-summary tr > th:first-child { + padding-top: 2px; +} + + +.differential-inline-summary td.inline-line-number { + color: #444444; + white-space: nowrap; + text-align: left; + font-weight: bold; + padding: 0 4px; + width: 90px; +} + +.differential-inline-summary td.inline-line-number .num { + display: block; + position: relative; + padding-left: 15px; + padding-right: 5px; + width: 70px; /* Need lots of width for 23,950-23,951 */ +} + +.differential-inline-summary td.inline-line-number a:hover { + background: #3b5998; + color: white; + text-decoration: none; +} + + +.differential-inline-summary-section { + margin: 1em 0 .5em; + font-size: 11px; + border-bottom: 1px solid #dddddd; + color: #666666; +} diff --git a/webroot/rsrc/js/application/differential/behavior-comment-preview.js b/webroot/rsrc/js/application/differential/behavior-comment-preview.js index 41bdec070b..3644bf6727 100644 --- a/webroot/rsrc/js/application/differential/behavior-comment-preview.js +++ b/webroot/rsrc/js/application/differential/behavior-comment-preview.js @@ -1,50 +1,66 @@ /** * @provides javelin-behavior-differential-feedback-preview * @requires javelin-lib-dev */ JX.behavior('differential-feedback-preview', function(config) { var action = JX.$(config.action); var content = JX.$(config.content); var preview = JX.$(config.preview); var aval = null;//action.value; var cval = null;//content.value; var defer = null; var min = null; var request = null; function check() { if (request || (min && (new Date().getTime() < min))) { // Waiting on an async or just got one back, rate-limit. return; } defer && defer.stop(); if (action.value !== aval || content.value !== cval) { aval = action.value; cval = content.value; request = new JX.Request(config.uri, function(r) { preview && JX.DOM.setContent(preview, JX.HTML(r)); min = new Date().getTime() + 500; defer && defer.stop(); defer = JX.defer(check, 500); }); request.listen('finally', function() { request = null; }); request.setData({action : aval, content : cval}); // If we don't get a response back soon, retry on the next action. request.setTimeout(2000); request.send(); } else { defer = JX.defer(check, 2000); } } JX.DOM.listen(content, 'keydown', null, check); JX.DOM.listen(action, 'change', null, check); check(); + + + function refreshInlinePreview() { + new JX.Request(config.inlineuri, function(r) { + JX.DOM.setContent(JX.$(config.inline), JX.HTML(r)); + }) + .setTimeout(5000) + .send(); + } + + JX.Stratcom.listen( + 'differential-inline-comment-update', + null, + refreshInlinePreview); + + refreshInlinePreview(); });