mirror of
https://we.phorge.it/source/phorge.git
synced 2024-12-04 20:52:43 +01:00
(stable) Promote 2016 Week 50
This commit is contained in:
commit
3c511adb6f
64 changed files with 2013 additions and 412 deletions
|
@ -9,7 +9,7 @@ return array(
|
|||
'names' => array(
|
||||
'conpherence.pkg.css' => '0b64e988',
|
||||
'conpherence.pkg.js' => '6249a1cf',
|
||||
'core.pkg.css' => '94090cab',
|
||||
'core.pkg.css' => '35e6c1ed',
|
||||
'core.pkg.js' => 'e4260032',
|
||||
'darkconsole.pkg.js' => 'e7393ebb',
|
||||
'differential.pkg.css' => 'a4ba74b5',
|
||||
|
@ -122,7 +122,7 @@ return array(
|
|||
'rsrc/css/phui/calendar/phui-calendar-list.css' => 'fcc9fb41',
|
||||
'rsrc/css/phui/calendar/phui-calendar-month.css' => '8e10e92c',
|
||||
'rsrc/css/phui/calendar/phui-calendar.css' => '477acfaa',
|
||||
'rsrc/css/phui/phui-action-list.css' => 'c5eba19d',
|
||||
'rsrc/css/phui/phui-action-list.css' => 'e1d48300',
|
||||
'rsrc/css/phui/phui-action-panel.css' => '91c7b835',
|
||||
'rsrc/css/phui/phui-badge.css' => '3baef8db',
|
||||
'rsrc/css/phui/phui-basic-nav-view.css' => '7093573b',
|
||||
|
@ -131,7 +131,7 @@ return array(
|
|||
'rsrc/css/phui/phui-button.css' => '4a5fbe3d',
|
||||
'rsrc/css/phui/phui-chart.css' => '6bf6f78e',
|
||||
'rsrc/css/phui/phui-cms.css' => 'be43c8a8',
|
||||
'rsrc/css/phui/phui-comment-form.css' => '4ecc56ef',
|
||||
'rsrc/css/phui/phui-comment-form.css' => 'c953b75e',
|
||||
'rsrc/css/phui/phui-comment-panel.css' => 'f50152ad',
|
||||
'rsrc/css/phui/phui-crumbs-view.css' => '195ac419',
|
||||
'rsrc/css/phui/phui-curtain-view.css' => '947bf1a4',
|
||||
|
@ -140,7 +140,7 @@ return array(
|
|||
'rsrc/css/phui/phui-document.css' => 'c32e8dec',
|
||||
'rsrc/css/phui/phui-feed-story.css' => '44a9c8e9',
|
||||
'rsrc/css/phui/phui-fontkit.css' => '9cda225e',
|
||||
'rsrc/css/phui/phui-form-view.css' => '3fadd537',
|
||||
'rsrc/css/phui/phui-form-view.css' => 'cd79ff6a',
|
||||
'rsrc/css/phui/phui-form.css' => '2342b0e5',
|
||||
'rsrc/css/phui/phui-head-thing.css' => 'fd311e5f',
|
||||
'rsrc/css/phui/phui-header-view.css' => '6ec8f155',
|
||||
|
@ -165,12 +165,12 @@ return array(
|
|||
'rsrc/css/phui/phui-status.css' => 'd5263e49',
|
||||
'rsrc/css/phui/phui-tag-view.css' => '6bbd83e2',
|
||||
'rsrc/css/phui/phui-timeline-view.css' => 'bc523970',
|
||||
'rsrc/css/phui/phui-two-column-view.css' => 'bbe32c23',
|
||||
'rsrc/css/phui/phui-two-column-view.css' => 'ee61d056',
|
||||
'rsrc/css/phui/workboards/phui-workboard-color.css' => 'b60ef38a',
|
||||
'rsrc/css/phui/workboards/phui-workboard.css' => '16441d5e',
|
||||
'rsrc/css/phui/workboards/phui-workcard.css' => '0c62d7c5',
|
||||
'rsrc/css/phui/workboards/phui-workpanel.css' => '92197373',
|
||||
'rsrc/css/sprite-login.css' => '6dbbbd97',
|
||||
'rsrc/css/sprite-login.css' => '587d92d7',
|
||||
'rsrc/css/sprite-tokens.css' => '9cdfd599',
|
||||
'rsrc/css/syntax/syntax-default.css' => '9923583c',
|
||||
'rsrc/externals/d3/d3.min.js' => 'a11a5ff2',
|
||||
|
@ -373,7 +373,6 @@ return array(
|
|||
'rsrc/js/application/aphlict/behavior-aphlict-listen.js' => 'fb20ac8d',
|
||||
'rsrc/js/application/aphlict/behavior-aphlict-status.js' => '5e2634b9',
|
||||
'rsrc/js/application/aphlict/behavior-desktop-notifications-control.js' => 'edd1ba66',
|
||||
'rsrc/js/application/auth/behavior-persona-login.js' => '9414ff18',
|
||||
'rsrc/js/application/calendar/behavior-day-view.js' => '4b3c4443',
|
||||
'rsrc/js/application/calendar/behavior-event-all-day.js' => 'b41537c9',
|
||||
'rsrc/js/application/calendar/behavior-month-view.js' => 'fe33e256',
|
||||
|
@ -660,7 +659,6 @@ return array(
|
|||
'javelin-behavior-maniphest-subpriority-editor' => '71237763',
|
||||
'javelin-behavior-owners-path-editor' => '7a68dda3',
|
||||
'javelin-behavior-passphrase-credential-control' => '3cb0b2fc',
|
||||
'javelin-behavior-persona-login' => '9414ff18',
|
||||
'javelin-behavior-phabricator-active-nav' => 'e379b58e',
|
||||
'javelin-behavior-phabricator-autofocus' => '7319e029',
|
||||
'javelin-behavior-phabricator-busy-example' => '60479091',
|
||||
|
@ -777,7 +775,7 @@ return array(
|
|||
'paste-css' => '1898e534',
|
||||
'path-typeahead' => 'f7fc67ec',
|
||||
'people-profile-css' => '2473d929',
|
||||
'phabricator-action-list-view-css' => 'c5eba19d',
|
||||
'phabricator-action-list-view-css' => 'e1d48300',
|
||||
'phabricator-application-launch-view-css' => '95351601',
|
||||
'phabricator-busy' => '59a7976a',
|
||||
'phabricator-chatlog-css' => 'd295b020',
|
||||
|
@ -846,7 +844,7 @@ return array(
|
|||
'phui-calendar-month-css' => '8e10e92c',
|
||||
'phui-chart-css' => '6bf6f78e',
|
||||
'phui-cms-css' => 'be43c8a8',
|
||||
'phui-comment-form-css' => '4ecc56ef',
|
||||
'phui-comment-form-css' => 'c953b75e',
|
||||
'phui-comment-panel-css' => 'f50152ad',
|
||||
'phui-crumbs-view-css' => '195ac419',
|
||||
'phui-curtain-view-css' => '947bf1a4',
|
||||
|
@ -857,7 +855,7 @@ return array(
|
|||
'phui-font-icon-base-css' => '870a7360',
|
||||
'phui-fontkit-css' => '9cda225e',
|
||||
'phui-form-css' => '2342b0e5',
|
||||
'phui-form-view-css' => '3fadd537',
|
||||
'phui-form-view-css' => 'cd79ff6a',
|
||||
'phui-head-thing-view-css' => 'fd311e5f',
|
||||
'phui-header-view-css' => '6ec8f155',
|
||||
'phui-hovercard' => '1bd28176',
|
||||
|
@ -884,7 +882,7 @@ return array(
|
|||
'phui-tag-view-css' => '6bbd83e2',
|
||||
'phui-theme-css' => '798c69b8',
|
||||
'phui-timeline-view-css' => 'bc523970',
|
||||
'phui-two-column-view-css' => 'bbe32c23',
|
||||
'phui-two-column-view-css' => 'ee61d056',
|
||||
'phui-workboard-color-css' => 'b60ef38a',
|
||||
'phui-workboard-view-css' => '16441d5e',
|
||||
'phui-workcard-view-css' => '0c62d7c5',
|
||||
|
@ -906,7 +904,7 @@ return array(
|
|||
'releeph-request-differential-create-dialog' => '8d8b92cd',
|
||||
'releeph-request-typeahead-css' => '667a48ae',
|
||||
'setup-issue-css' => 'f794cfc3',
|
||||
'sprite-login-css' => '6dbbbd97',
|
||||
'sprite-login-css' => '587d92d7',
|
||||
'sprite-tokens-css' => '9cdfd599',
|
||||
'syntax-default-css' => '9923583c',
|
||||
'syntax-highlighting-css' => '769d3498',
|
||||
|
@ -1666,13 +1664,6 @@ return array(
|
|||
'javelin-workflow',
|
||||
'javelin-dom',
|
||||
),
|
||||
'9414ff18' => array(
|
||||
'javelin-behavior',
|
||||
'javelin-resource',
|
||||
'javelin-stratcom',
|
||||
'javelin-workflow',
|
||||
'javelin-util',
|
||||
),
|
||||
'949c0fe5' => array(
|
||||
'javelin-install',
|
||||
),
|
||||
|
|
Binary file not shown.
Before Width: | Height: | Size: 1.2 KiB |
Binary file not shown.
Before Width: | Height: | Size: 2.8 KiB |
|
@ -81,11 +81,6 @@
|
|||
"rule": ".login-PayPal",
|
||||
"hash": "dfa09f45369c93bb0fd82a333b0fe927"
|
||||
},
|
||||
"login-Persona": {
|
||||
"name": "login-Persona",
|
||||
"rule": ".login-Persona",
|
||||
"hash": "14cc5b479b14abe16261c01fc432ffd1"
|
||||
},
|
||||
"login-Phabricator": {
|
||||
"name": "login-Phabricator",
|
||||
"rule": ".login-Phabricator",
|
||||
|
|
|
@ -22,6 +22,7 @@ phutil_register_library_map(array(
|
|||
'AlmanacBindingTransactionQuery' => 'applications/almanac/query/AlmanacBindingTransactionQuery.php',
|
||||
'AlmanacBindingViewController' => 'applications/almanac/controller/AlmanacBindingViewController.php',
|
||||
'AlmanacBindingsSearchEngineAttachment' => 'applications/almanac/engineextension/AlmanacBindingsSearchEngineAttachment.php',
|
||||
'AlmanacCacheEngineExtension' => 'applications/almanac/engineextension/AlmanacCacheEngineExtension.php',
|
||||
'AlmanacClusterDatabaseServiceType' => 'applications/almanac/servicetype/AlmanacClusterDatabaseServiceType.php',
|
||||
'AlmanacClusterRepositoryServiceType' => 'applications/almanac/servicetype/AlmanacClusterRepositoryServiceType.php',
|
||||
'AlmanacClusterServiceType' => 'applications/almanac/servicetype/AlmanacClusterServiceType.php',
|
||||
|
@ -584,6 +585,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionBrowseQueryConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionBrowseQueryConduitAPIMethod.php',
|
||||
'DiffusionBrowseResultSet' => 'applications/diffusion/data/DiffusionBrowseResultSet.php',
|
||||
'DiffusionBrowseTableView' => 'applications/diffusion/view/DiffusionBrowseTableView.php',
|
||||
'DiffusionCacheEngineExtension' => 'applications/diffusion/engineextension/DiffusionCacheEngineExtension.php',
|
||||
'DiffusionCachedResolveRefsQuery' => 'applications/diffusion/query/DiffusionCachedResolveRefsQuery.php',
|
||||
'DiffusionChangeController' => 'applications/diffusion/controller/DiffusionChangeController.php',
|
||||
'DiffusionChangeHeraldFieldGroup' => 'applications/diffusion/herald/DiffusionChangeHeraldFieldGroup.php',
|
||||
|
@ -635,6 +637,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionCommitRevisionReviewersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionReviewersHeraldField.php',
|
||||
'DiffusionCommitRevisionSubscribersHeraldField' => 'applications/diffusion/herald/DiffusionCommitRevisionSubscribersHeraldField.php',
|
||||
'DiffusionCommitTagsController' => 'applications/diffusion/controller/DiffusionCommitTagsController.php',
|
||||
'DiffusionCompareController' => 'applications/diffusion/controller/DiffusionCompareController.php',
|
||||
'DiffusionConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionConduitAPIMethod.php',
|
||||
'DiffusionController' => 'applications/diffusion/controller/DiffusionController.php',
|
||||
'DiffusionCreateCommentConduitAPIMethod' => 'applications/diffusion/conduit/DiffusionCreateCommentConduitAPIMethod.php',
|
||||
|
@ -1784,6 +1787,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationEmailCommandsController' => 'applications/meta/controller/PhabricatorApplicationEmailCommandsController.php',
|
||||
'PhabricatorApplicationLaunchView' => 'applications/meta/view/PhabricatorApplicationLaunchView.php',
|
||||
'PhabricatorApplicationPanelController' => 'applications/meta/controller/PhabricatorApplicationPanelController.php',
|
||||
'PhabricatorApplicationProfilePanel' => 'applications/search/profilepanel/PhabricatorApplicationProfilePanel.php',
|
||||
'PhabricatorApplicationQuery' => 'applications/meta/query/PhabricatorApplicationQuery.php',
|
||||
'PhabricatorApplicationSearchController' => 'applications/search/controller/PhabricatorApplicationSearchController.php',
|
||||
'PhabricatorApplicationSearchEngine' => 'applications/search/engine/PhabricatorApplicationSearchEngine.php',
|
||||
|
@ -2022,6 +2026,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorBulkContentSource' => 'infrastructure/daemon/contentsource/PhabricatorBulkContentSource.php',
|
||||
'PhabricatorBusyUIExample' => 'applications/uiexample/examples/PhabricatorBusyUIExample.php',
|
||||
'PhabricatorCacheDAO' => 'applications/cache/storage/PhabricatorCacheDAO.php',
|
||||
'PhabricatorCacheEngine' => 'applications/system/engine/PhabricatorCacheEngine.php',
|
||||
'PhabricatorCacheEngineExtension' => 'applications/system/engine/PhabricatorCacheEngineExtension.php',
|
||||
'PhabricatorCacheGeneralGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheGeneralGarbageCollector.php',
|
||||
'PhabricatorCacheManagementPurgeWorkflow' => 'applications/cache/management/PhabricatorCacheManagementPurgeWorkflow.php',
|
||||
'PhabricatorCacheManagementWorkflow' => 'applications/cache/management/PhabricatorCacheManagementWorkflow.php',
|
||||
|
@ -2030,6 +2036,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCacheSetupCheck' => 'applications/config/check/PhabricatorCacheSetupCheck.php',
|
||||
'PhabricatorCacheSpec' => 'applications/cache/spec/PhabricatorCacheSpec.php',
|
||||
'PhabricatorCacheTTLGarbageCollector' => 'applications/cache/garbagecollector/PhabricatorCacheTTLGarbageCollector.php',
|
||||
'PhabricatorCachedClassMapQuery' => 'applications/cache/PhabricatorCachedClassMapQuery.php',
|
||||
'PhabricatorCaches' => 'applications/cache/PhabricatorCaches.php',
|
||||
'PhabricatorCachesTestCase' => 'applications/cache/__tests__/PhabricatorCachesTestCase.php',
|
||||
'PhabricatorCalendarApplication' => 'applications/calendar/application/PhabricatorCalendarApplication.php',
|
||||
|
@ -2811,6 +2818,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorJiraIssueHasObjectEdgeType' => 'applications/doorkeeper/edge/PhabricatorJiraIssueHasObjectEdgeType.php',
|
||||
'PhabricatorJumpNavHandler' => 'applications/search/engine/PhabricatorJumpNavHandler.php',
|
||||
'PhabricatorKeyValueDatabaseCache' => 'applications/cache/PhabricatorKeyValueDatabaseCache.php',
|
||||
'PhabricatorKeyValueSerializingCacheProxy' => 'applications/cache/PhabricatorKeyValueSerializingCacheProxy.php',
|
||||
'PhabricatorKeyboardRemarkupRule' => 'infrastructure/markup/rule/PhabricatorKeyboardRemarkupRule.php',
|
||||
'PhabricatorKeyring' => 'applications/files/keyring/PhabricatorKeyring.php',
|
||||
'PhabricatorKeyringConfigOptionType' => 'applications/files/keyring/PhabricatorKeyringConfigOptionType.php',
|
||||
|
@ -3268,7 +3276,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPeopleUserFunctionDatasource' => 'applications/people/typeahead/PhabricatorPeopleUserFunctionDatasource.php',
|
||||
'PhabricatorPeopleUserPHIDType' => 'applications/people/phid/PhabricatorPeopleUserPHIDType.php',
|
||||
'PhabricatorPeopleWelcomeController' => 'applications/people/controller/PhabricatorPeopleWelcomeController.php',
|
||||
'PhabricatorPersonaAuthProvider' => 'applications/auth/provider/PhabricatorPersonaAuthProvider.php',
|
||||
'PhabricatorPhabricatorAuthProvider' => 'applications/auth/provider/PhabricatorPhabricatorAuthProvider.php',
|
||||
'PhabricatorPhameApplication' => 'applications/phame/application/PhabricatorPhameApplication.php',
|
||||
'PhabricatorPhameBlogPHIDType' => 'applications/phame/phid/PhabricatorPhameBlogPHIDType.php',
|
||||
|
@ -3988,6 +3995,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorViewerDatasource' => 'applications/people/typeahead/PhabricatorViewerDatasource.php',
|
||||
'PhabricatorWatcherHasObjectEdgeType' => 'applications/transactions/edges/PhabricatorWatcherHasObjectEdgeType.php',
|
||||
'PhabricatorWebContentSource' => 'infrastructure/contentsource/PhabricatorWebContentSource.php',
|
||||
'PhabricatorWebServerSetupCheck' => 'applications/config/check/PhabricatorWebServerSetupCheck.php',
|
||||
'PhabricatorWeekStartDaySetting' => 'applications/settings/setting/PhabricatorWeekStartDaySetting.php',
|
||||
'PhabricatorWordPressAuthProvider' => 'applications/auth/provider/PhabricatorWordPressAuthProvider.php',
|
||||
'PhabricatorWorker' => 'infrastructure/daemon/workers/PhabricatorWorker.php',
|
||||
|
@ -4569,6 +4577,7 @@ phutil_register_library_map(array(
|
|||
'AlmanacBindingTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
|
||||
'AlmanacBindingViewController' => 'AlmanacServiceController',
|
||||
'AlmanacBindingsSearchEngineAttachment' => 'AlmanacSearchEngineAttachment',
|
||||
'AlmanacCacheEngineExtension' => 'PhabricatorCacheEngineExtension',
|
||||
'AlmanacClusterDatabaseServiceType' => 'AlmanacClusterServiceType',
|
||||
'AlmanacClusterRepositoryServiceType' => 'AlmanacClusterServiceType',
|
||||
'AlmanacClusterServiceType' => 'AlmanacServiceType',
|
||||
|
@ -5223,6 +5232,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionBrowseQueryConduitAPIMethod' => 'DiffusionQueryConduitAPIMethod',
|
||||
'DiffusionBrowseResultSet' => 'Phobject',
|
||||
'DiffusionBrowseTableView' => 'DiffusionView',
|
||||
'DiffusionCacheEngineExtension' => 'PhabricatorCacheEngineExtension',
|
||||
'DiffusionCachedResolveRefsQuery' => 'DiffusionLowLevelQuery',
|
||||
'DiffusionChangeController' => 'DiffusionController',
|
||||
'DiffusionChangeHeraldFieldGroup' => 'HeraldFieldGroup',
|
||||
|
@ -5274,6 +5284,7 @@ phutil_register_library_map(array(
|
|||
'DiffusionCommitRevisionReviewersHeraldField' => 'DiffusionCommitHeraldField',
|
||||
'DiffusionCommitRevisionSubscribersHeraldField' => 'DiffusionCommitHeraldField',
|
||||
'DiffusionCommitTagsController' => 'DiffusionController',
|
||||
'DiffusionCompareController' => 'DiffusionController',
|
||||
'DiffusionConduitAPIMethod' => 'ConduitAPIMethod',
|
||||
'DiffusionController' => 'PhabricatorController',
|
||||
'DiffusionCreateCommentConduitAPIMethod' => 'DiffusionConduitAPIMethod',
|
||||
|
@ -6599,6 +6610,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorApplicationEmailCommandsController' => 'PhabricatorApplicationsController',
|
||||
'PhabricatorApplicationLaunchView' => 'AphrontTagView',
|
||||
'PhabricatorApplicationPanelController' => 'PhabricatorApplicationsController',
|
||||
'PhabricatorApplicationProfilePanel' => 'PhabricatorProfilePanel',
|
||||
'PhabricatorApplicationQuery' => 'PhabricatorCursorPagedPolicyAwareQuery',
|
||||
'PhabricatorApplicationSearchController' => 'PhabricatorSearchBaseController',
|
||||
'PhabricatorApplicationSearchEngine' => 'Phobject',
|
||||
|
@ -6880,6 +6892,8 @@ phutil_register_library_map(array(
|
|||
'PhabricatorBulkContentSource' => 'PhabricatorContentSource',
|
||||
'PhabricatorBusyUIExample' => 'PhabricatorUIExample',
|
||||
'PhabricatorCacheDAO' => 'PhabricatorLiskDAO',
|
||||
'PhabricatorCacheEngine' => 'Phobject',
|
||||
'PhabricatorCacheEngineExtension' => 'Phobject',
|
||||
'PhabricatorCacheGeneralGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||
'PhabricatorCacheManagementPurgeWorkflow' => 'PhabricatorCacheManagementWorkflow',
|
||||
'PhabricatorCacheManagementWorkflow' => 'PhabricatorManagementWorkflow',
|
||||
|
@ -6888,6 +6902,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorCacheSetupCheck' => 'PhabricatorSetupCheck',
|
||||
'PhabricatorCacheSpec' => 'Phobject',
|
||||
'PhabricatorCacheTTLGarbageCollector' => 'PhabricatorGarbageCollector',
|
||||
'PhabricatorCachedClassMapQuery' => 'Phobject',
|
||||
'PhabricatorCaches' => 'Phobject',
|
||||
'PhabricatorCachesTestCase' => 'PhabricatorTestCase',
|
||||
'PhabricatorCalendarApplication' => 'PhabricatorApplication',
|
||||
|
@ -7795,6 +7810,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorJiraIssueHasObjectEdgeType' => 'PhabricatorEdgeType',
|
||||
'PhabricatorJumpNavHandler' => 'Phobject',
|
||||
'PhabricatorKeyValueDatabaseCache' => 'PhutilKeyValueCache',
|
||||
'PhabricatorKeyValueSerializingCacheProxy' => 'PhutilKeyValueCacheProxy',
|
||||
'PhabricatorKeyboardRemarkupRule' => 'PhutilRemarkupRule',
|
||||
'PhabricatorKeyring' => 'Phobject',
|
||||
'PhabricatorKeyringConfigOptionType' => 'PhabricatorConfigJSONOptionType',
|
||||
|
@ -8326,7 +8342,6 @@ phutil_register_library_map(array(
|
|||
'PhabricatorPeopleUserFunctionDatasource' => 'PhabricatorTypeaheadCompositeDatasource',
|
||||
'PhabricatorPeopleUserPHIDType' => 'PhabricatorPHIDType',
|
||||
'PhabricatorPeopleWelcomeController' => 'PhabricatorPeopleController',
|
||||
'PhabricatorPersonaAuthProvider' => 'PhabricatorAuthProvider',
|
||||
'PhabricatorPhabricatorAuthProvider' => 'PhabricatorOAuth2AuthProvider',
|
||||
'PhabricatorPhameApplication' => 'PhabricatorApplication',
|
||||
'PhabricatorPhameBlogPHIDType' => 'PhabricatorPHIDType',
|
||||
|
@ -9204,6 +9219,7 @@ phutil_register_library_map(array(
|
|||
'PhabricatorViewerDatasource' => 'PhabricatorTypeaheadDatasource',
|
||||
'PhabricatorWatcherHasObjectEdgeType' => 'PhabricatorEdgeType',
|
||||
'PhabricatorWebContentSource' => 'PhabricatorContentSource',
|
||||
'PhabricatorWebServerSetupCheck' => 'PhabricatorSetupCheck',
|
||||
'PhabricatorWeekStartDaySetting' => 'PhabricatorSelectSetting',
|
||||
'PhabricatorWordPressAuthProvider' => 'PhabricatorOAuth2AuthProvider',
|
||||
'PhabricatorWorker' => 'Phobject',
|
||||
|
|
|
@ -557,11 +557,13 @@ final class AphrontRequest extends Phobject {
|
|||
}
|
||||
|
||||
public function getRemoteAddress() {
|
||||
$address = $_SERVER['REMOTE_ADDR'];
|
||||
if (!strlen($address)) {
|
||||
$address = PhabricatorEnv::getRemoteAddress();
|
||||
|
||||
if (!$address) {
|
||||
return null;
|
||||
}
|
||||
return substr($address, 0, 64);
|
||||
|
||||
return $address->getAddress();
|
||||
}
|
||||
|
||||
public function isHTTPS() {
|
||||
|
|
|
@ -59,6 +59,11 @@ abstract class AphrontApplicationConfiguration extends Phobject {
|
|||
* @phutil-external-symbol class PhabricatorStartup
|
||||
*/
|
||||
public static function runHTTPRequest(AphrontHTTPSink $sink) {
|
||||
if (isset($_SERVER['HTTP_X_PHABRICATOR_SELFCHECK'])) {
|
||||
$response = self::newSelfCheckResponse();
|
||||
return self::writeResponse($sink, $response);
|
||||
}
|
||||
|
||||
PhabricatorStartup::beginStartupPhase('multimeter');
|
||||
$multimeter = MultimeterControl::newInstance();
|
||||
$multimeter->setEventContext('<http-init>');
|
||||
|
@ -106,10 +111,18 @@ abstract class AphrontApplicationConfiguration extends Phobject {
|
|||
PhabricatorAccessLog::init();
|
||||
$access_log = PhabricatorAccessLog::getLog();
|
||||
PhabricatorStartup::setAccessLog($access_log);
|
||||
|
||||
$address = PhabricatorEnv::getRemoteAddress();
|
||||
if ($address) {
|
||||
$address_string = $address->getAddress();
|
||||
} else {
|
||||
$address_string = '-';
|
||||
}
|
||||
|
||||
$access_log->setData(
|
||||
array(
|
||||
'R' => AphrontRequest::getHTTPHeader('Referer', '-'),
|
||||
'r' => idx($_SERVER, 'REMOTE_ADDR', '-'),
|
||||
'r' => $address_string,
|
||||
'M' => idx($_SERVER, 'REQUEST_METHOD', '-'),
|
||||
));
|
||||
|
||||
|
@ -682,5 +695,36 @@ abstract class AphrontApplicationConfiguration extends Phobject {
|
|||
throw $ex;
|
||||
}
|
||||
|
||||
private static function newSelfCheckResponse() {
|
||||
$path = idx($_REQUEST, '__path__', '');
|
||||
$query = idx($_SERVER, 'QUERY_STRING', '');
|
||||
|
||||
$pairs = id(new PhutilQueryStringParser())
|
||||
->parseQueryStringToPairList($query);
|
||||
|
||||
$params = array();
|
||||
foreach ($pairs as $v) {
|
||||
$params[] = array(
|
||||
'name' => $v[0],
|
||||
'value' => $v[1],
|
||||
);
|
||||
}
|
||||
|
||||
$result = array(
|
||||
'path' => $path,
|
||||
'params' => $params,
|
||||
'user' => idx($_SERVER, 'PHP_AUTH_USER'),
|
||||
'pass' => idx($_SERVER, 'PHP_AUTH_PW'),
|
||||
|
||||
// This just makes sure that the response compresses well, so reasonable
|
||||
// algorithms should want to gzip or deflate it.
|
||||
'filler' => str_repeat('Q', 1024 * 16),
|
||||
);
|
||||
|
||||
|
||||
return id(new AphrontJSONResponse())
|
||||
->setAddJSONShield(false)
|
||||
->setContent($result);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,53 @@
|
|||
<?php
|
||||
|
||||
final class AlmanacCacheEngineExtension
|
||||
extends PhabricatorCacheEngineExtension {
|
||||
|
||||
const EXTENSIONKEY = 'almanac';
|
||||
|
||||
public function getExtensionName() {
|
||||
return pht('Almanac Core Objects');
|
||||
}
|
||||
|
||||
public function discoverLinkedObjects(
|
||||
PhabricatorCacheEngine $engine,
|
||||
array $objects) {
|
||||
$viewer = $engine->getViewer();
|
||||
|
||||
$results = array();
|
||||
foreach ($this->selectObjects($objects, 'AlmanacBinding') as $object) {
|
||||
$results[] = $object->getServicePHID();
|
||||
$results[] = $object->getDevicePHID();
|
||||
$results[] = $object->getInterfacePHID();
|
||||
}
|
||||
|
||||
$devices = $this->selectObjects($objects, 'AlmanacDevice');
|
||||
if ($devices) {
|
||||
$interfaces = id(new AlmanacInterfaceQuery())
|
||||
->setViewer($viewer)
|
||||
->withDevicePHIDs(mpull($devices, 'getPHID'))
|
||||
->execute();
|
||||
foreach ($interfaces as $interface) {
|
||||
$results[] = $interface;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($this->selectObjects($objects, 'AlmanacInterface') as $iface) {
|
||||
$results[] = $iface->getDevicePHID();
|
||||
$results[] = $iface->getNetworkPHID();
|
||||
}
|
||||
|
||||
foreach ($this->selectObjects($objects, 'AlmanacProperty') as $object) {
|
||||
$results[] = $object->getObjectPHID();
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function deleteCaches(
|
||||
PhabricatorCacheEngine $engine,
|
||||
array $objects) {
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
|
@ -259,8 +259,12 @@ abstract class PhabricatorAuthController extends PhabricatorController {
|
|||
protected function renderInviteHeader(PhabricatorAuthInvite $invite) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
// Since the user hasn't registered yet, they may not be able to see other
|
||||
// user accounts. Load the inviting user with the omnipotent viewer.
|
||||
$omnipotent_viewer = PhabricatorUser::getOmnipotentUser();
|
||||
|
||||
$invite_author = id(new PhabricatorPeopleQuery())
|
||||
->setViewer($viewer)
|
||||
->setViewer($omnipotent_viewer)
|
||||
->withPHIDs(array($invite->getAuthorPHID()))
|
||||
->needProfileImage(true)
|
||||
->executeOne();
|
||||
|
|
|
@ -1,81 +0,0 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorPersonaAuthProvider extends PhabricatorAuthProvider {
|
||||
|
||||
private $adapter;
|
||||
|
||||
public function getProviderName() {
|
||||
return pht('Persona');
|
||||
}
|
||||
|
||||
public function getDescriptionForCreate() {
|
||||
return pht('Allow users to login or register using Mozilla Persona.');
|
||||
}
|
||||
|
||||
public function getAdapter() {
|
||||
if (!$this->adapter) {
|
||||
$adapter = new PhutilPersonaAuthAdapter();
|
||||
$this->adapter = $adapter;
|
||||
}
|
||||
return $this->adapter;
|
||||
}
|
||||
|
||||
protected function renderLoginForm(
|
||||
AphrontRequest $request,
|
||||
$mode) {
|
||||
|
||||
Javelin::initBehavior(
|
||||
'persona-login',
|
||||
array(
|
||||
'loginURI' => PhabricatorEnv::getURI($this->getLoginURI()),
|
||||
));
|
||||
|
||||
return $this->renderStandardLoginButton(
|
||||
$request,
|
||||
$mode,
|
||||
array(
|
||||
'uri' => $this->getLoginURI(),
|
||||
'sigil' => 'persona-login-form',
|
||||
));
|
||||
}
|
||||
|
||||
public function isLoginFormAButton() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function processLoginRequest(
|
||||
PhabricatorAuthLoginController $controller) {
|
||||
|
||||
$request = $controller->getRequest();
|
||||
$adapter = $this->getAdapter();
|
||||
|
||||
$account = null;
|
||||
$response = null;
|
||||
|
||||
if (!$request->isAjax()) {
|
||||
throw new Exception(pht('Expected this request to come via Ajax.'));
|
||||
}
|
||||
|
||||
$assertion = $request->getStr('assertion');
|
||||
if (!$assertion) {
|
||||
throw new Exception(pht('Expected identity assertion.'));
|
||||
}
|
||||
|
||||
$adapter->setAssertion($assertion);
|
||||
$adapter->setAudience(PhabricatorEnv::getURI('/'));
|
||||
|
||||
try {
|
||||
$account_id = $adapter->getAccountID();
|
||||
} catch (Exception $ex) {
|
||||
// TODO: Handle this in a more user-friendly way.
|
||||
throw $ex;
|
||||
}
|
||||
|
||||
return array($this->loadOrCreateAccount($account_id), $response);
|
||||
}
|
||||
|
||||
protected function getLoginIcon() {
|
||||
return 'Persona';
|
||||
}
|
||||
|
||||
}
|
|
@ -26,10 +26,6 @@ final class PhabricatorBadgesApplication extends PhabricatorApplication {
|
|||
return self::GROUP_UTILITIES;
|
||||
}
|
||||
|
||||
public function canUninstall() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function isPrototype() {
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -436,12 +436,21 @@ abstract class PhabricatorApplication
|
|||
if ($result === null) {
|
||||
if (!self::isClassInstalled($class)) {
|
||||
$result = false;
|
||||
} else {
|
||||
$application = self::getByClass($class);
|
||||
if (!$application->canUninstall()) {
|
||||
// If the application can not be uninstalled, always allow viewers
|
||||
// to see it. In particular, this allows logged-out viewers to see
|
||||
// Settings and load global default settings even if the install
|
||||
// does not allow public viewers.
|
||||
$result = true;
|
||||
} else {
|
||||
$result = PhabricatorPolicyFilter::hasCapability(
|
||||
$viewer,
|
||||
self::getByClass($class),
|
||||
PhabricatorPolicyCapability::CAN_VIEW);
|
||||
}
|
||||
}
|
||||
|
||||
$cache->setKey($key, $result);
|
||||
}
|
||||
|
|
129
src/applications/cache/PhabricatorCachedClassMapQuery.php
vendored
Normal file
129
src/applications/cache/PhabricatorCachedClassMapQuery.php
vendored
Normal file
|
@ -0,0 +1,129 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Cached @{class:PhutilClassMapQuery} which can perform lookups for single
|
||||
* classes efficiently.
|
||||
*
|
||||
* Some class trees (like Conduit methods and PHID types) contain a huge number
|
||||
* of classes but are frequently accessed by looking for a specific class by
|
||||
* a known identifier (like a Conduit method name or a PHID type constant).
|
||||
*
|
||||
* Loading the entire class map for these cases has a small but measurable
|
||||
* performance cost. Instead, we can build a cache from each Conduit method
|
||||
* name to just the class required to serve that request. This means that we
|
||||
* load fewer classes and have less overhead to execute API calls.
|
||||
*/
|
||||
final class PhabricatorCachedClassMapQuery
|
||||
extends Phobject {
|
||||
|
||||
private $query;
|
||||
private $queryCacheKey;
|
||||
private $mapKeyMethod;
|
||||
private $objectMap;
|
||||
|
||||
public function setClassMapQuery(PhutilClassMapQuery $query) {
|
||||
$this->query = $query;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function setMapKeyMethod($method) {
|
||||
$this->mapKeyMethod = $method;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function loadClasses(array $values) {
|
||||
$cache = PhabricatorCaches::getRuntimeCache();
|
||||
|
||||
$cache_keys = $this->getCacheKeys($values);
|
||||
$cache_map = $cache->getKeys($cache_keys);
|
||||
|
||||
$results = array();
|
||||
$writes = array();
|
||||
foreach ($cache_keys as $value => $cache_key) {
|
||||
if (isset($cache_map[$cache_key])) {
|
||||
$class_name = $cache_map[$cache_key];
|
||||
try {
|
||||
$result = $this->newObject($class_name);
|
||||
if ($this->getObjectMapKey($result) === $value) {
|
||||
$results[$value] = $result;
|
||||
continue;
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
// Keep going, we'll handle this immediately below.
|
||||
}
|
||||
|
||||
// If we didn't "continue;" above, there was either a direct issue with
|
||||
// the cache or the cached class did not generate the correct map key.
|
||||
// Wipe the cache and pretend we missed.
|
||||
$cache->deleteKey($cache_key);
|
||||
}
|
||||
|
||||
if ($this->objectMap === null) {
|
||||
$this->objectMap = $this->newObjectMap();
|
||||
}
|
||||
|
||||
if (isset($this->objectMap[$value])) {
|
||||
$results[$value] = $this->objectMap[$value];
|
||||
$writes[$cache_key] = get_class($results[$value]);
|
||||
}
|
||||
}
|
||||
|
||||
if ($writes) {
|
||||
$cache->setKeys($writes);
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function loadClass($value) {
|
||||
$result = $this->loadClasses(array($value));
|
||||
return idx($result, $value);
|
||||
}
|
||||
|
||||
private function getCacheKeys(array $values) {
|
||||
if ($this->queryCacheKey === null) {
|
||||
$this->queryCacheKey = $this->query->getCacheKey();
|
||||
}
|
||||
|
||||
$key = $this->queryCacheKey;
|
||||
$method = $this->mapKeyMethod;
|
||||
|
||||
$keys = array();
|
||||
foreach ($values as $value) {
|
||||
$keys[$value] = "classmap({$key}).{$method}({$value})";
|
||||
}
|
||||
|
||||
return $keys;
|
||||
}
|
||||
|
||||
private function newObject($class_name) {
|
||||
return newv($class_name, array());
|
||||
}
|
||||
|
||||
private function newObjectMap() {
|
||||
$map = $this->query->execute();
|
||||
|
||||
$result = array();
|
||||
foreach ($map as $object) {
|
||||
$value = $this->getObjectMapKey($object);
|
||||
if (isset($result[$value])) {
|
||||
$other = $result[$value];
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Two objects (of classes "%s" and "%s") generate the same map '.
|
||||
'value ("%s"). Each object must generate a unique map value.',
|
||||
get_class($object),
|
||||
get_class($other),
|
||||
$value));
|
||||
}
|
||||
$result[$value] = $object;
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
private function getObjectMapKey($object) {
|
||||
return call_user_func(array($object, $this->mapKeyMethod));
|
||||
}
|
||||
|
||||
}
|
54
src/applications/cache/PhabricatorCaches.php
vendored
54
src/applications/cache/PhabricatorCaches.php
vendored
|
@ -116,6 +116,60 @@ final class PhabricatorCaches extends Phobject {
|
|||
return $caches;
|
||||
}
|
||||
|
||||
public static function getMutableStructureCache() {
|
||||
static $cache;
|
||||
if (!$cache) {
|
||||
$caches = self::buildMutableStructureCaches();
|
||||
$cache = self::newStackFromCaches($caches);
|
||||
}
|
||||
return $cache;
|
||||
}
|
||||
|
||||
private static function buildMutableStructureCaches() {
|
||||
$caches = array();
|
||||
|
||||
$cache = new PhabricatorKeyValueDatabaseCache();
|
||||
$cache = new PhabricatorKeyValueSerializingCacheProxy($cache);
|
||||
$caches[] = $cache;
|
||||
|
||||
return $caches;
|
||||
}
|
||||
|
||||
/* -( Runtime Cache )------------------------------------------------------ */
|
||||
|
||||
|
||||
/**
|
||||
* Get a runtime cache stack.
|
||||
*
|
||||
* This stack is just APC. It's fast, it's effectively immutable, and it
|
||||
* gets thrown away when the webserver restarts.
|
||||
*
|
||||
* This cache is suitable for deriving runtime caches, like a map of Conduit
|
||||
* method names to provider classes.
|
||||
*
|
||||
* @return PhutilKeyValueCacheStack Best runtime stack available.
|
||||
*/
|
||||
public static function getRuntimeCache() {
|
||||
static $cache;
|
||||
if (!$cache) {
|
||||
$caches = self::buildRuntimeCaches();
|
||||
$cache = self::newStackFromCaches($caches);
|
||||
}
|
||||
return $cache;
|
||||
}
|
||||
|
||||
|
||||
private static function buildRuntimeCaches() {
|
||||
$caches = array();
|
||||
|
||||
$apc = new PhutilAPCKeyValueCache();
|
||||
if ($apc->isAvailable()) {
|
||||
$caches[] = $apc;
|
||||
}
|
||||
|
||||
return $caches;
|
||||
}
|
||||
|
||||
|
||||
/* -( Repository Graph Cache )--------------------------------------------- */
|
||||
|
||||
|
|
55
src/applications/cache/PhabricatorKeyValueSerializingCacheProxy.php
vendored
Normal file
55
src/applications/cache/PhabricatorKeyValueSerializingCacheProxy.php
vendored
Normal file
|
@ -0,0 +1,55 @@
|
|||
<?php
|
||||
|
||||
/**
|
||||
* Proxies another cache and serializes values.
|
||||
*
|
||||
* This allows more complex data to be stored in a cache which can only store
|
||||
* strings.
|
||||
*/
|
||||
final class PhabricatorKeyValueSerializingCacheProxy
|
||||
extends PhutilKeyValueCacheProxy {
|
||||
|
||||
public function getKeys(array $keys) {
|
||||
$results = parent::getKeys($keys);
|
||||
|
||||
$reads = array();
|
||||
foreach ($results as $key => $result) {
|
||||
$structure = @unserialize($result);
|
||||
|
||||
// The unserialize() function returns false when unserializing a
|
||||
// literal `false`, and also when it fails. If we get a literal
|
||||
// `false`, test if the serialized form is the same as the
|
||||
// serialization of `false` and miss the cache otherwise.
|
||||
if ($structure === false) {
|
||||
static $serialized_false;
|
||||
if ($serialized_false === null) {
|
||||
$serialized_false = serialize(false);
|
||||
}
|
||||
if ($result !== $serialized_false) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$reads[$key] = $structure;
|
||||
}
|
||||
|
||||
return $reads;
|
||||
}
|
||||
|
||||
public function setKeys(array $keys, $ttl = null) {
|
||||
$writes = array();
|
||||
foreach ($keys as $key => $value) {
|
||||
if (is_object($value)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Serializing cache can not write objects (for key "%s")!',
|
||||
$key));
|
||||
}
|
||||
$writes[$key] = serialize($value);
|
||||
}
|
||||
|
||||
return parent::setKeys($writes, $ttl);
|
||||
}
|
||||
|
||||
|
||||
}
|
|
@ -158,15 +158,20 @@ abstract class ConduitAPIMethod
|
|||
}
|
||||
|
||||
public static function loadAllConduitMethods() {
|
||||
return self::newClassMapQuery()->execute();
|
||||
}
|
||||
|
||||
private static function newClassMapQuery() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->setUniqueMethod('getAPIMethodName')
|
||||
->execute();
|
||||
->setUniqueMethod('getAPIMethodName');
|
||||
}
|
||||
|
||||
public static function getConduitMethod($method_name) {
|
||||
$method_map = self::loadAllConduitMethods();
|
||||
return idx($method_map, $method_name);
|
||||
return id(new PhabricatorCachedClassMapQuery())
|
||||
->setClassMapQuery(self::newClassMapQuery())
|
||||
->setMapKeyMethod('getAPIMethodName')
|
||||
->loadClass($method_name);
|
||||
}
|
||||
|
||||
public function shouldRequireAuthentication() {
|
||||
|
|
|
@ -34,48 +34,41 @@ final class PhabricatorConduitTokenQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
protected function loadPage() {
|
||||
$table = new PhabricatorConduitToken();
|
||||
$conn_r = $table->establishConnection('r');
|
||||
|
||||
$data = queryfx_all(
|
||||
$conn_r,
|
||||
'SELECT * FROM %T %Q %Q %Q',
|
||||
$table->getTableName(),
|
||||
$this->buildWhereClause($conn_r),
|
||||
$this->buildOrderClause($conn_r),
|
||||
$this->buildLimitClause($conn_r));
|
||||
|
||||
return $table->loadAllFromArray($data);
|
||||
public function newResultObject() {
|
||||
return new PhabricatorConduitToken();
|
||||
}
|
||||
|
||||
protected function buildWhereClause(AphrontDatabaseConnection $conn_r) {
|
||||
$where = array();
|
||||
protected function loadPage() {
|
||||
return $this->loadStandardPage($this->newResultObject());
|
||||
}
|
||||
|
||||
protected function buildWhereClauseParts(AphrontDatabaseConnection $conn) {
|
||||
$where = parent::buildWhereClauseParts($conn);
|
||||
|
||||
if ($this->ids !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'id IN (%Ld)',
|
||||
$this->ids);
|
||||
}
|
||||
|
||||
if ($this->objectPHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'objectPHID IN (%Ls)',
|
||||
$this->objectPHIDs);
|
||||
}
|
||||
|
||||
if ($this->tokens !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'token IN (%Ls)',
|
||||
$this->tokens);
|
||||
}
|
||||
|
||||
if ($this->tokenTypes !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'tokenType IN (%Ls)',
|
||||
$this->tokenTypes);
|
||||
}
|
||||
|
@ -83,20 +76,18 @@ final class PhabricatorConduitTokenQuery
|
|||
if ($this->expired !== null) {
|
||||
if ($this->expired) {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'expires <= %d',
|
||||
PhabricatorTime::getNow());
|
||||
} else {
|
||||
$where[] = qsprintf(
|
||||
$conn_r,
|
||||
$conn,
|
||||
'expires IS NULL OR expires > %d',
|
||||
PhabricatorTime::getNow());
|
||||
}
|
||||
}
|
||||
|
||||
$where[] = $this->buildPagingClause($conn_r);
|
||||
|
||||
return $this->formatWhereClause($where);
|
||||
return $where;
|
||||
}
|
||||
|
||||
protected function willFilterPage(array $tokens) {
|
||||
|
|
|
@ -42,6 +42,10 @@ final class PhabricatorConduitToken
|
|||
return null;
|
||||
}
|
||||
|
||||
if ($user->hasConduitClusterToken()) {
|
||||
return $user->getConduitClusterToken();
|
||||
}
|
||||
|
||||
$tokens = id(new PhabricatorConduitTokenQuery())
|
||||
->setViewer($user)
|
||||
->withObjectPHIDs(array($user->getPHID()))
|
||||
|
@ -55,23 +59,28 @@ final class PhabricatorConduitToken
|
|||
$now = PhabricatorTime::getNow();
|
||||
$must_expire_after = $now + phutil_units('5 minutes in seconds');
|
||||
|
||||
$valid_token = null;
|
||||
foreach ($tokens as $token) {
|
||||
if ($token->getExpires() > $must_expire_after) {
|
||||
return $token;
|
||||
$valid_token = $token;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// We didn't find any existing tokens (or the existing tokens are all about
|
||||
// to expire) so generate a new token.
|
||||
|
||||
if (!$valid_token) {
|
||||
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
|
||||
$token = self::initializeNewToken(
|
||||
$valid_token = self::initializeNewToken(
|
||||
$user->getPHID(),
|
||||
self::TYPE_CLUSTER);
|
||||
$token->save();
|
||||
$valid_token->save();
|
||||
unset($unguarded);
|
||||
}
|
||||
|
||||
return $token;
|
||||
$user->attachConduitClusterToken($valid_token);
|
||||
|
||||
return $valid_token;
|
||||
}
|
||||
|
||||
public static function initializeNewToken($object_phid, $token_type) {
|
||||
|
|
265
src/applications/config/check/PhabricatorWebServerSetupCheck.php
Normal file
265
src/applications/config/check/PhabricatorWebServerSetupCheck.php
Normal file
|
@ -0,0 +1,265 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorWebServerSetupCheck extends PhabricatorSetupCheck {
|
||||
|
||||
public function getDefaultGroup() {
|
||||
return self::GROUP_OTHER;
|
||||
}
|
||||
|
||||
protected function executeChecks() {
|
||||
// The documentation says these headers exist, but it's not clear if they
|
||||
// are entirely reliable in practice.
|
||||
if (isset($_SERVER['HTTP_X_MOD_PAGESPEED']) ||
|
||||
isset($_SERVER['HTTP_X_PAGE_SPEED'])) {
|
||||
$this->newIssue('webserver.pagespeed')
|
||||
->setName(pht('Disable Pagespeed'))
|
||||
->setSummary(pht('Pagespeed is enabled, but should be disabled.'))
|
||||
->setMessage(
|
||||
pht(
|
||||
'Phabricator received an "X-Mod-Pagespeed" or "X-Page-Speed" '.
|
||||
'HTTP header on this request, which indicates that you have '.
|
||||
'enabled "mod_pagespeed" on this server. This module is not '.
|
||||
'compatible with Phabricator. You should disable it.'));
|
||||
}
|
||||
|
||||
$base_uri = PhabricatorEnv::getEnvConfig('phabricator.base-uri');
|
||||
if (!strlen($base_uri)) {
|
||||
// If `phabricator.base-uri` is not set then we can't really do
|
||||
// anything.
|
||||
return;
|
||||
}
|
||||
|
||||
$expect_user = 'alincoln';
|
||||
$expect_pass = 'hunter2';
|
||||
|
||||
$send_path = '/test-%252A/';
|
||||
$expect_path = '/test-%2A/';
|
||||
|
||||
$expect_key = 'duck-sound';
|
||||
$expect_value = 'quack';
|
||||
|
||||
$base_uri = id(new PhutilURI($base_uri))
|
||||
->setPath($send_path)
|
||||
->setQueryParam($expect_key, $expect_value);
|
||||
|
||||
$self_future = id(new HTTPSFuture($base_uri))
|
||||
->addHeader('X-Phabricator-SelfCheck', 1)
|
||||
->addHeader('Accept-Encoding', 'gzip')
|
||||
->setHTTPBasicAuthCredentials(
|
||||
$expect_user,
|
||||
new PhutilOpaqueEnvelope($expect_pass))
|
||||
->setTimeout(5);
|
||||
|
||||
// Make a request to the metadata service available on EC2 instances,
|
||||
// to test if we're running on a T2 instance in AWS so we can warn that
|
||||
// this is a bad idea. Outside of AWS, this request will just fail.
|
||||
$ec2_uri = 'http://169.254.169.254/latest/meta-data/instance-type';
|
||||
$ec2_future = id(new HTTPSFuture($ec2_uri))
|
||||
->setTimeout(1);
|
||||
|
||||
$futures = array(
|
||||
$self_future,
|
||||
$ec2_future,
|
||||
);
|
||||
$futures = new FutureIterator($futures);
|
||||
foreach ($futures as $future) {
|
||||
// Just resolve the futures here.
|
||||
}
|
||||
|
||||
|
||||
try {
|
||||
list($body) = $ec2_future->resolvex();
|
||||
$body = trim($body);
|
||||
if (preg_match('/^t2/', $body)) {
|
||||
$message = pht(
|
||||
'Phabricator appears to be installed on a very small EC2 instance '.
|
||||
'(of class "%s") with burstable CPU. This is strongly discouraged. '.
|
||||
'Phabricator regularly needs CPU, and these instances are often '.
|
||||
'choked to death by CPU throttling. Use an instance with a normal '.
|
||||
'CPU instead.',
|
||||
$body);
|
||||
|
||||
$this->newIssue('ec2.burstable')
|
||||
->setName(pht('Installed on Burstable CPU Instance'))
|
||||
->setSummary(
|
||||
pht(
|
||||
'Do not install Phabricator on an instance class with '.
|
||||
'burstable CPU.'))
|
||||
->setMessage($message);
|
||||
}
|
||||
} catch (Exception $ex) {
|
||||
// If this fails, just continue. We're probably not running in EC2.
|
||||
}
|
||||
|
||||
try {
|
||||
list($body, $headers) = $self_future->resolvex();
|
||||
} catch (Exception $ex) {
|
||||
// If this fails for whatever reason, just ignore it. Hopefully, the
|
||||
// error is obvious and the user can correct it on their own, but we
|
||||
// can't do much to offer diagnostic advice.
|
||||
return;
|
||||
}
|
||||
|
||||
if (BaseHTTPFuture::getHeader($headers, 'Content-Encoding') != 'gzip') {
|
||||
$message = pht(
|
||||
'Phabricator sent itself a request with "Accept-Encoding: gzip", '.
|
||||
'but received an uncompressed response.'.
|
||||
"\n\n".
|
||||
'This may indicate that your webserver is not configured to '.
|
||||
'compress responses. If so, you should enable compression. '.
|
||||
'Compression can dramatically improve performance, especially '.
|
||||
'for clients with less bandwidth.');
|
||||
|
||||
$this->newIssue('webserver.gzip')
|
||||
->setName(pht('GZip Compression May Not Be Enabled'))
|
||||
->setSummary(pht('Your webserver may have compression disabled.'))
|
||||
->setMessage($message);
|
||||
} else {
|
||||
if (function_exists('gzdecode')) {
|
||||
$body = gzdecode($body);
|
||||
} else {
|
||||
$body = null;
|
||||
}
|
||||
if (!$body) {
|
||||
// For now, just bail if we can't decode the response.
|
||||
// This might need to use the stronger magic in "AphrontRequestStream"
|
||||
// to decode more reliably.
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$structure = null;
|
||||
$caught = null;
|
||||
$extra_whitespace = ($body !== trim($body));
|
||||
|
||||
if (!$extra_whitespace) {
|
||||
try {
|
||||
$structure = phutil_json_decode($body);
|
||||
} catch (Exception $ex) {
|
||||
$caught = $ex;
|
||||
}
|
||||
}
|
||||
|
||||
if (!$structure) {
|
||||
if ($extra_whitespace) {
|
||||
$message = pht(
|
||||
'Phabricator sent itself a test request and expected to get a bare '.
|
||||
'JSON response back, but the response had extra whitespace at '.
|
||||
'the beginning or end.'.
|
||||
"\n\n".
|
||||
'This usually means you have edited a file and left whitespace '.
|
||||
'characters before the opening %s tag, or after a closing %s tag. '.
|
||||
'Remove any leading whitespace, and prefer to omit closing tags.',
|
||||
phutil_tag('tt', array(), '<?php'),
|
||||
phutil_tag('tt', array(), '?>'));
|
||||
} else {
|
||||
$short = id(new PhutilUTF8StringTruncator())
|
||||
->setMaximumGlyphs(1024)
|
||||
->truncateString($body);
|
||||
|
||||
$message = pht(
|
||||
'Phabricator sent itself a test request with the '.
|
||||
'"X-Phabricator-SelfCheck" header and expected to get a valid JSON '.
|
||||
'response back. Instead, the response begins:'.
|
||||
"\n\n".
|
||||
'%s'.
|
||||
"\n\n".
|
||||
'Something is misconfigured or otherwise mangling responses.',
|
||||
phutil_tag('pre', array(), $short));
|
||||
}
|
||||
|
||||
$this->newIssue('webserver.mangle')
|
||||
->setName(pht('Mangled Webserver Response'))
|
||||
->setSummary(pht('Your webserver produced an unexpected response.'))
|
||||
->setMessage($message);
|
||||
|
||||
// We can't run the other checks if we could not decode the response.
|
||||
return;
|
||||
}
|
||||
|
||||
$actual_user = idx($structure, 'user');
|
||||
$actual_pass = idx($structure, 'pass');
|
||||
if (($expect_user != $actual_user) || ($actual_pass != $expect_pass)) {
|
||||
$message = pht(
|
||||
'Phabricator sent itself a test request with an "Authorization" HTTP '.
|
||||
'header, and expected those credentials to be transmitted. However, '.
|
||||
'they were absent or incorrect when received. Phabricator sent '.
|
||||
'username "%s" with password "%s"; received username "%s" and '.
|
||||
'password "%s".'.
|
||||
"\n\n".
|
||||
'Your webserver may not be configured to forward HTTP basic '.
|
||||
'authentication. If you plan to use basic authentication (for '.
|
||||
'example, to access repositories) you should reconfigure it.',
|
||||
$expect_user,
|
||||
$expect_pass,
|
||||
$actual_user,
|
||||
$actual_pass);
|
||||
|
||||
$this->newIssue('webserver.basic-auth')
|
||||
->setName(pht('HTTP Basic Auth Not Configured'))
|
||||
->setSummary(pht('Your webserver is not forwarding credentials.'))
|
||||
->setMessage($message);
|
||||
}
|
||||
|
||||
$actual_path = idx($structure, 'path');
|
||||
if ($expect_path != $actual_path) {
|
||||
$message = pht(
|
||||
'Phabricator sent itself a test request with an unusual path, to '.
|
||||
'test if your webserver is rewriting paths correctly. The path was '.
|
||||
'not transmitted correctly.'.
|
||||
"\n\n".
|
||||
'Phabricator sent a request to path "%s", and expected the webserver '.
|
||||
'to decode and rewrite that path so that it received a request for '.
|
||||
'"%s". However, it received a request for "%s" instead.'.
|
||||
"\n\n".
|
||||
'Verify that your rewrite rules are configured correctly, following '.
|
||||
'the instructions in the documentation. If path encoding is not '.
|
||||
'working properly you will be unable to access files with unusual '.
|
||||
'names in repositories, among other issues.'.
|
||||
"\n\n".
|
||||
'(This problem can be caused by a missing "B" in your RewriteRule.)',
|
||||
$send_path,
|
||||
$expect_path,
|
||||
$actual_path);
|
||||
|
||||
$this->newIssue('webserver.rewrites')
|
||||
->setName(pht('HTTP Path Rewriting Incorrect'))
|
||||
->setSummary(pht('Your webserver is rewriting paths improperly.'))
|
||||
->setMessage($message);
|
||||
}
|
||||
|
||||
$actual_key = pht('<none>');
|
||||
$actual_value = pht('<none>');
|
||||
foreach (idx($structure, 'params', array()) as $pair) {
|
||||
if (idx($pair, 'name') == $expect_key) {
|
||||
$actual_key = idx($pair, 'name');
|
||||
$actual_value = idx($pair, 'value');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (($expect_key !== $actual_key) || ($expect_value !== $actual_value)) {
|
||||
$message = pht(
|
||||
'Phabricator sent itself a test request with an HTTP GET parameter, '.
|
||||
'but the parameter was not transmitted. Sent "%s" with value "%s", '.
|
||||
'got "%s" with value "%s".'.
|
||||
"\n\n".
|
||||
'Your webserver is configured incorrectly and large parts of '.
|
||||
'Phabricator will not work until this issue is corrected.'.
|
||||
"\n\n".
|
||||
'(This problem can be caused by a missing "QSA" in your RewriteRule.)',
|
||||
$expect_key,
|
||||
$expect_value,
|
||||
$actual_key,
|
||||
$actual_value);
|
||||
|
||||
$this->newIssue('webserver.parameters')
|
||||
->setName(pht('HTTP Parameters Not Transmitting'))
|
||||
->setSummary(
|
||||
pht('Your webserver is not handling GET parameters properly.'))
|
||||
->setMessage($message);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
|
@ -23,8 +23,8 @@ final class PhabricatorSecurityConfigOptions
|
|||
$doc_href = PhabricatorEnv::getDoclink('Configuring a File Domain');
|
||||
$doc_name = pht('Configuration Guide: Configuring a File Domain');
|
||||
|
||||
// This is all of the IANA special/reserved blocks in IPv4 space.
|
||||
$default_address_blacklist = array(
|
||||
// This is all of the IANA special/reserved blocks in IPv4 space.
|
||||
'0.0.0.0/8',
|
||||
'10.0.0.0/8',
|
||||
'100.64.0.0/10',
|
||||
|
@ -41,6 +41,21 @@ final class PhabricatorSecurityConfigOptions
|
|||
'224.0.0.0/4',
|
||||
'240.0.0.0/4',
|
||||
'255.255.255.255/32',
|
||||
|
||||
// And these are the IANA special/reserved blocks in IPv6 space.
|
||||
'::/128',
|
||||
'::1/128',
|
||||
'::ffff:0:0/96',
|
||||
'100::/64',
|
||||
'64:ff9b::/96',
|
||||
'2001::/32',
|
||||
'2001:10::/28',
|
||||
'2001:20::/28',
|
||||
'2001:db8::/32',
|
||||
'2002::/16',
|
||||
'fc00::/7',
|
||||
'fe80::/10',
|
||||
'ff00::/8',
|
||||
);
|
||||
|
||||
$keyring_type = 'custom:PhabricatorKeyringConfigOptionType';
|
||||
|
|
|
@ -65,6 +65,7 @@ final class PhabricatorDiffusionApplication extends PhabricatorApplication {
|
|||
=> 'DiffusionCommitTagsController',
|
||||
'commit/(?P<commit>[a-z0-9]+)/edit/'
|
||||
=> 'DiffusionCommitEditController',
|
||||
'compare/' => 'DiffusionCompareController',
|
||||
'manage/(?:(?P<panel>[^/]+)/)?'
|
||||
=> 'DiffusionRepositoryManagePanelsController',
|
||||
'uri/' => array(
|
||||
|
|
|
@ -22,6 +22,7 @@ final class DiffusionHistoryQueryConduitAPIMethod
|
|||
protected function defineCustomParamTypes() {
|
||||
return array(
|
||||
'commit' => 'required string',
|
||||
'against' => 'optional string',
|
||||
'path' => 'required string',
|
||||
'offset' => 'required int',
|
||||
'limit' => 'required int',
|
||||
|
@ -43,10 +44,17 @@ final class DiffusionHistoryQueryConduitAPIMethod
|
|||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
$commit_hash = $request->getValue('commit');
|
||||
$against_hash = $request->getValue('against');
|
||||
$path = $request->getValue('path');
|
||||
$offset = $request->getValue('offset');
|
||||
$limit = $request->getValue('limit');
|
||||
|
||||
if (strlen($against_hash)) {
|
||||
$commit_range = "${against_hash}..${commit_hash}";
|
||||
} else {
|
||||
$commit_range = $commit_hash;
|
||||
}
|
||||
|
||||
list($stdout) = $repository->execxLocalCommand(
|
||||
'log '.
|
||||
'--skip=%d '.
|
||||
|
@ -56,15 +64,12 @@ final class DiffusionHistoryQueryConduitAPIMethod
|
|||
$offset,
|
||||
$limit,
|
||||
'%H:%P',
|
||||
$commit_hash,
|
||||
$commit_range,
|
||||
// Git omits merge commits if the path is provided, even if it is empty.
|
||||
(strlen($path) ? csprintf('%s', $path) : ''));
|
||||
|
||||
$lines = explode("\n", trim($stdout));
|
||||
$lines = array_filter($lines);
|
||||
if (!$lines) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$hash_list = array();
|
||||
$parent_map = array();
|
||||
|
@ -76,6 +81,10 @@ final class DiffusionHistoryQueryConduitAPIMethod
|
|||
|
||||
$this->parents = $parent_map;
|
||||
|
||||
if (!$hash_list) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return DiffusionQuery::loadHistoryForCommitIdentifiers(
|
||||
$hash_list,
|
||||
$drequest);
|
||||
|
|
|
@ -55,7 +55,6 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
}
|
||||
|
||||
private function browseSearch() {
|
||||
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$header = $this->buildHeaderView($drequest);
|
||||
|
||||
|
@ -77,7 +76,8 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
|
||||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setFooter(array(
|
||||
->setFooter(
|
||||
array(
|
||||
$search_form,
|
||||
$search_results,
|
||||
));
|
||||
|
@ -337,6 +337,7 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
|
||||
$empty_result = null;
|
||||
$browse_panel = null;
|
||||
$branch_panel = null;
|
||||
if (!$results->isValidResults()) {
|
||||
$empty_result = new DiffusionEmptyResultView();
|
||||
$empty_result->setDiffusionRequest($drequest);
|
||||
|
@ -376,6 +377,12 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
pht('Hide Search'),
|
||||
$search_form,
|
||||
'#');
|
||||
|
||||
$path = $drequest->getPath();
|
||||
$is_branch = (!strlen($path) && $repository->supportsBranchComparison());
|
||||
if ($is_branch) {
|
||||
$branch_panel = $this->buildBranchTable();
|
||||
}
|
||||
}
|
||||
|
||||
$open_revisions = $this->buildOpenRevisions();
|
||||
|
@ -394,11 +401,14 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
$view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setCurtain($curtain)
|
||||
->setMainColumn(array(
|
||||
->setMainColumn(
|
||||
array(
|
||||
$branch_panel,
|
||||
$empty_result,
|
||||
$browse_panel,
|
||||
))
|
||||
->setFooter(array(
|
||||
->setFooter(
|
||||
array(
|
||||
$open_revisions,
|
||||
$readme,
|
||||
$pager_box,
|
||||
|
@ -1651,6 +1661,7 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
|
||||
protected function buildCurtain(DiffusionRequest $drequest) {
|
||||
$viewer = $this->getViewer();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
$curtain = $this->newCurtainView($drequest);
|
||||
|
||||
|
@ -1666,6 +1677,21 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
->setIcon('fa-list'));
|
||||
|
||||
$behind_head = $drequest->getSymbolicCommit();
|
||||
|
||||
if ($repository->supportsBranchComparison()) {
|
||||
$compare_uri = $drequest->generateURI(
|
||||
array(
|
||||
'action' => 'compare',
|
||||
));
|
||||
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Compare Against...'))
|
||||
->setIcon('fa-code-fork')
|
||||
->setWorkflow(true)
|
||||
->setHref($compare_uri));
|
||||
}
|
||||
|
||||
$head_uri = $drequest->generateURI(
|
||||
array(
|
||||
'commit' => '',
|
||||
|
@ -1935,5 +1961,62 @@ final class DiffusionBrowseController extends DiffusionController {
|
|||
return $file;
|
||||
}
|
||||
|
||||
private function buildBranchTable() {
|
||||
$viewer = $this->getViewer();
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
$branch = $drequest->getBranch();
|
||||
$default_branch = $repository->getDefaultBranch();
|
||||
|
||||
if ($branch === $default_branch) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$pager = id(new PHUIPagerView())
|
||||
->setPageSize(10);
|
||||
|
||||
try {
|
||||
$results = $this->callConduitWithDiffusionRequest(
|
||||
'diffusion.historyquery',
|
||||
array(
|
||||
'commit' => $branch,
|
||||
'against' => $default_branch,
|
||||
'path' => $drequest->getPath(),
|
||||
'offset' => $pager->getOffset(),
|
||||
'limit' => $pager->getPageSize() + 1,
|
||||
));
|
||||
} catch (Exception $ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$history = DiffusionPathChange::newFromConduit($results['pathChanges']);
|
||||
$history = $pager->sliceResults($history);
|
||||
|
||||
if (!$history) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$history_table = id(new DiffusionHistoryTableView())
|
||||
->setViewer($viewer)
|
||||
->setDiffusionRequest($drequest)
|
||||
->setHistory($history);
|
||||
|
||||
$history_table->loadRevisions();
|
||||
|
||||
$history_table
|
||||
->setParents($results['parents'])
|
||||
->setFilterParents(true)
|
||||
->setIsHead(true)
|
||||
->setIsTail(!$pager->getHasMorePages());
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('%s vs %s', $branch, $default_branch));
|
||||
|
||||
return id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($history_table);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -0,0 +1,325 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionCompareController extends DiffusionController {
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function handleRequest(AphrontRequest $request) {
|
||||
$response = $this->loadDiffusionContext();
|
||||
if ($response) {
|
||||
return $response;
|
||||
}
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
if (!$repository->supportsBranchComparison()) {
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Not Supported'))
|
||||
->appendParagraph(
|
||||
pht(
|
||||
'Branch comparison is not supported for this version control '.
|
||||
'system.'))
|
||||
->addCancelButton($this->getApplicationURI(), pht('Okay'));
|
||||
}
|
||||
|
||||
$head_ref = $request->getStr('head');
|
||||
$against_ref = $request->getStr('against');
|
||||
|
||||
$must_prompt = false;
|
||||
if (!$request->isFormPost()) {
|
||||
if (!strlen($head_ref)) {
|
||||
$head_ref = $drequest->getSymbolicCommit();
|
||||
if (!strlen($head_ref)) {
|
||||
$head_ref = $drequest->getBranch();
|
||||
}
|
||||
}
|
||||
|
||||
if (!strlen($against_ref)) {
|
||||
$default_branch = $repository->getDefaultBranch();
|
||||
if ($default_branch != $head_ref) {
|
||||
$against_ref = $default_branch;
|
||||
|
||||
// If we filled this in by default, we want to prompt the user to
|
||||
// confirm that this is really what they want.
|
||||
$must_prompt = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$refs = $drequest->resolveRefs(
|
||||
array_filter(
|
||||
array(
|
||||
$head_ref,
|
||||
$against_ref,
|
||||
)));
|
||||
|
||||
$identical = false;
|
||||
if ($head_ref === $against_ref) {
|
||||
$identical = true;
|
||||
} else {
|
||||
if (count($refs) == 2) {
|
||||
if ($refs[$head_ref] === $refs[$against_ref]) {
|
||||
$identical = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($must_prompt || count($refs) != 2 || $identical) {
|
||||
return $this->buildCompareDialog(
|
||||
$head_ref,
|
||||
$against_ref,
|
||||
$refs,
|
||||
$identical);
|
||||
}
|
||||
|
||||
if ($request->isFormPost()) {
|
||||
// Redirect to a stable URI that can be copy/pasted.
|
||||
$compare_uri = $drequest->generateURI(
|
||||
array(
|
||||
'action' => 'compare',
|
||||
'head' => $head_ref,
|
||||
'against' => $against_ref,
|
||||
));
|
||||
|
||||
return id(new AphrontRedirectResponse())->setURI($compare_uri);
|
||||
}
|
||||
|
||||
$crumbs = $this->buildCrumbs(
|
||||
array(
|
||||
'view' => 'compare',
|
||||
));
|
||||
|
||||
$pager = id(new PHUIPagerView())
|
||||
->readFromRequest($request);
|
||||
|
||||
$history = null;
|
||||
try {
|
||||
$history_results = $this->callConduitWithDiffusionRequest(
|
||||
'diffusion.historyquery',
|
||||
array(
|
||||
'commit' => $head_ref,
|
||||
'against' => $against_ref,
|
||||
'path' => $drequest->getPath(),
|
||||
'offset' => $pager->getOffset(),
|
||||
'limit' => $pager->getPageSize() + 1,
|
||||
));
|
||||
$history = DiffusionPathChange::newFromConduit(
|
||||
$history_results['pathChanges']);
|
||||
$history = $pager->sliceResults($history);
|
||||
|
||||
$history_view = $this->newHistoryView(
|
||||
$history_results,
|
||||
$history,
|
||||
$pager,
|
||||
$head_ref,
|
||||
$against_ref);
|
||||
|
||||
} catch (Exception $ex) {
|
||||
if ($repository->isImporting()) {
|
||||
$history_view = $this->renderStatusMessage(
|
||||
pht('Still Importing...'),
|
||||
pht(
|
||||
'This repository is still importing. History is not yet '.
|
||||
'available.'));
|
||||
} else {
|
||||
$history_view = $this->renderStatusMessage(
|
||||
pht('Unable to Retrieve History'),
|
||||
$ex->getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(
|
||||
pht(
|
||||
'Changes on %s but not %s',
|
||||
phutil_tag('em', array(), $head_ref),
|
||||
phutil_tag('em', array(), $against_ref)));
|
||||
|
||||
$curtain = $this->buildCurtain($head_ref, $against_ref);
|
||||
|
||||
$column_view = id(new PHUITwoColumnView())
|
||||
->setHeader($header)
|
||||
->setCurtain($curtain)
|
||||
->setMainColumn(
|
||||
array(
|
||||
$history_view,
|
||||
));
|
||||
|
||||
return $this->newPage()
|
||||
->setTitle(
|
||||
array(
|
||||
$repository->getName(),
|
||||
$repository->getDisplayName(),
|
||||
))
|
||||
->setCrumbs($crumbs)
|
||||
->appendChild($column_view);
|
||||
}
|
||||
|
||||
private function buildCompareDialog(
|
||||
$head_ref,
|
||||
$against_ref,
|
||||
array $resolved,
|
||||
$identical) {
|
||||
|
||||
$viewer = $this->getViewer();
|
||||
$request = $this->getRequest();
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
$e_head = null;
|
||||
$e_against = null;
|
||||
$errors = array();
|
||||
if ($request->isFormPost()) {
|
||||
if (!strlen($head_ref)) {
|
||||
$e_head = pht('Required');
|
||||
$errors[] = pht(
|
||||
'You must provide two different commits to compare.');
|
||||
} else if (!isset($resolved[$head_ref])) {
|
||||
$e_head = pht('Not Found');
|
||||
$errors[] = pht(
|
||||
'Commit "%s" is not a valid commit in this repository.',
|
||||
$head_ref);
|
||||
}
|
||||
|
||||
if (!strlen($against_ref)) {
|
||||
$e_against = pht('Required');
|
||||
$errors[] = pht(
|
||||
'You must provide two different commits to compare.');
|
||||
} else if (!isset($resolved[$against_ref])) {
|
||||
$e_against = pht('Not Found');
|
||||
$errors[] = pht(
|
||||
'Commit "%s" is not a valid commit in this repository.',
|
||||
$against_ref);
|
||||
}
|
||||
|
||||
if ($identical) {
|
||||
$e_head = pht('Identical');
|
||||
$e_against = pht('Identical');
|
||||
$errors[] = pht(
|
||||
'Both references identify the same commit. You can not compare a '.
|
||||
'commit against itself.');
|
||||
}
|
||||
}
|
||||
|
||||
$form = id(new AphrontFormView())
|
||||
->setViewer($viewer)
|
||||
->appendControl(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel(pht('Head'))
|
||||
->setName('head')
|
||||
->setError($e_head)
|
||||
->setValue($head_ref))
|
||||
->appendControl(
|
||||
id(new AphrontFormTextControl())
|
||||
->setLabel(pht('Against'))
|
||||
->setName('against')
|
||||
->setError($e_against)
|
||||
->setValue($against_ref));
|
||||
|
||||
$cancel_uri = $repository->generateURI(
|
||||
array(
|
||||
'action' => 'browse',
|
||||
));
|
||||
|
||||
return $this->newDialog()
|
||||
->setTitle(pht('Compare Against'))
|
||||
->setWidth(AphrontDialogView::WIDTH_FORM)
|
||||
->setErrors($errors)
|
||||
->appendForm($form)
|
||||
->addSubmitButton(pht('Compare'))
|
||||
->addCancelButton($cancel_uri, pht('Cancel'));
|
||||
}
|
||||
|
||||
private function buildCurtain($head_ref, $against_ref) {
|
||||
$viewer = $this->getViewer();
|
||||
$request = $this->getRequest();
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
$curtain = $this->newCurtainView(null);
|
||||
|
||||
$reverse_uri = $drequest->generateURI(
|
||||
array(
|
||||
'action' => 'compare',
|
||||
'head' => $against_ref,
|
||||
'against' => $head_ref,
|
||||
));
|
||||
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Reverse Comparison'))
|
||||
->setHref($reverse_uri)
|
||||
->setIcon('fa-refresh'));
|
||||
|
||||
$compare_uri = $drequest->generateURI(
|
||||
array(
|
||||
'action' => 'compare',
|
||||
'head' => $head_ref,
|
||||
));
|
||||
|
||||
$curtain->addAction(
|
||||
id(new PhabricatorActionView())
|
||||
->setName(pht('Compare Against...'))
|
||||
->setIcon('fa-code-fork')
|
||||
->setWorkflow(true)
|
||||
->setHref($compare_uri));
|
||||
|
||||
// TODO: Provide a "Show Diff" action.
|
||||
|
||||
return $curtain;
|
||||
}
|
||||
|
||||
private function newHistoryView(
|
||||
array $results,
|
||||
array $history,
|
||||
PHUIPagerView $pager,
|
||||
$head_ref,
|
||||
$against_ref) {
|
||||
|
||||
$request = $this->getRequest();
|
||||
$viewer = $this->getViewer();
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
if (!$history) {
|
||||
return $this->renderStatusMessage(
|
||||
pht('Up To Date'),
|
||||
pht(
|
||||
'There are no commits on %s that are not already on %s.',
|
||||
phutil_tag('strong', array(), $head_ref),
|
||||
phutil_tag('strong', array(), $against_ref)));
|
||||
}
|
||||
|
||||
$history_table = id(new DiffusionHistoryTableView())
|
||||
->setUser($viewer)
|
||||
->setDiffusionRequest($drequest)
|
||||
->setHistory($history);
|
||||
|
||||
$history_table->loadRevisions();
|
||||
|
||||
$history_table
|
||||
->setParents($results['parents'])
|
||||
->setFilterParents(true)
|
||||
->setIsHead(!$pager->getOffset())
|
||||
->setIsTail(!$pager->getHasMorePages());
|
||||
|
||||
$header = id(new PHUIHeaderView())
|
||||
->setHeader(pht('Commits'));
|
||||
|
||||
$object_box = id(new PHUIObjectBoxView())
|
||||
->setHeader($header)
|
||||
->setBackground(PHUIObjectBoxView::BLUE_PROPERTY)
|
||||
->setTable($history_table);
|
||||
|
||||
$pager_box = $this->renderTablePagerBox($pager);
|
||||
|
||||
return array(
|
||||
$object_box,
|
||||
$pager_box,
|
||||
);
|
||||
}
|
||||
}
|
|
@ -213,6 +213,9 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
case 'change':
|
||||
$view_name = pht('Change');
|
||||
break;
|
||||
case 'compare':
|
||||
$view_name = pht('Compare');
|
||||
break;
|
||||
}
|
||||
|
||||
$crumb = id(new PHUICrumbView())
|
||||
|
@ -236,6 +239,18 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
$params);
|
||||
}
|
||||
|
||||
protected function callConduitMethod($method, array $params = array()) {
|
||||
$user = $this->getViewer();
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
|
||||
return DiffusionQuery::callConduitWithDiffusionRequest(
|
||||
$user,
|
||||
$drequest,
|
||||
$method,
|
||||
$params,
|
||||
true);
|
||||
}
|
||||
|
||||
protected function getRepositoryControllerURI(
|
||||
PhabricatorRepository $repository,
|
||||
$path) {
|
||||
|
@ -337,7 +352,21 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$viewer = $this->getViewer();
|
||||
$repository = $drequest->getRepository();
|
||||
$repository_phid = $repository->getPHID();
|
||||
$stable_commit = $drequest->getStableCommit();
|
||||
|
||||
$stable_commit_hash = PhabricatorHash::digestForIndex($stable_commit);
|
||||
$readme_path_hash = PhabricatorHash::digestForindex($readme_path);
|
||||
|
||||
$cache = PhabricatorCaches::getMutableStructureCache();
|
||||
$cache_key = "diffusion".
|
||||
".repository({$repository_phid})".
|
||||
".commit({$stable_commit_hash})".
|
||||
".readme({$readme_path_hash})";
|
||||
|
||||
$readme_cache = $cache->getKey($cache_key);
|
||||
if (!$readme_cache) {
|
||||
try {
|
||||
$result = $this->callConduitWithDiffusionRequest(
|
||||
'diffusion.filecontentquery',
|
||||
|
@ -364,14 +393,22 @@ abstract class DiffusionController extends PhabricatorController {
|
|||
|
||||
$corpus = $file->loadFileData();
|
||||
|
||||
if (!strlen($corpus)) {
|
||||
$readme_cache = array(
|
||||
'corpus' => $corpus,
|
||||
);
|
||||
|
||||
$cache->setKey($cache_key, $readme_cache);
|
||||
}
|
||||
|
||||
$readme_corpus = $readme_cache['corpus'];
|
||||
if (!strlen($readme_corpus)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return id(new DiffusionReadmeView())
|
||||
->setUser($this->getViewer())
|
||||
->setPath($readme_path)
|
||||
->setContent($corpus);
|
||||
->setContent($readme_corpus);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -2,6 +2,11 @@
|
|||
|
||||
final class DiffusionRepositoryController extends DiffusionController {
|
||||
|
||||
private $historyFuture;
|
||||
private $browseFuture;
|
||||
private $tagFuture;
|
||||
private $branchFuture;
|
||||
|
||||
public function shouldAllowPublic() {
|
||||
return true;
|
||||
}
|
||||
|
@ -106,18 +111,67 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
$request = $this->getRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
$commit = $drequest->getCommit();
|
||||
$path = $drequest->getPath();
|
||||
|
||||
$this->historyFuture = $this->callConduitMethod(
|
||||
'diffusion.historyquery',
|
||||
array(
|
||||
'commit' => $commit,
|
||||
'path' => $path,
|
||||
'offset' => 0,
|
||||
'limit' => 15,
|
||||
));
|
||||
|
||||
$browse_pager = id(new PHUIPagerView())
|
||||
->readFromRequest($request);
|
||||
|
||||
$this->browseFuture = $this->callConduitMethod(
|
||||
'diffusion.browsequery',
|
||||
array(
|
||||
'commit' => $commit,
|
||||
'path' => $path,
|
||||
'limit' => $browse_pager->getPageSize() + 1,
|
||||
));
|
||||
|
||||
if ($this->needTagFuture()) {
|
||||
$tag_limit = $this->getTagLimit();
|
||||
$this->tagFuture = $this->callConduitMethod(
|
||||
'diffusion.tagsquery',
|
||||
array(
|
||||
// On the home page, we want to find tags on any branch.
|
||||
'commit' => null,
|
||||
'limit' => $tag_limit + 1,
|
||||
));
|
||||
}
|
||||
|
||||
if ($this->needBranchFuture()) {
|
||||
$branch_limit = $this->getBranchLimit();
|
||||
$this->branchFuture = $this->callConduitMethod(
|
||||
'diffusion.branchquery',
|
||||
array(
|
||||
'closed' => false,
|
||||
'limit' => $branch_limit + 1,
|
||||
));
|
||||
}
|
||||
|
||||
$futures = array(
|
||||
$this->historyFuture,
|
||||
$this->browseFuture,
|
||||
$this->tagFuture,
|
||||
$this->branchFuture,
|
||||
);
|
||||
$futures = array_filter($futures);
|
||||
$futures = new FutureIterator($futures);
|
||||
foreach ($futures as $future) {
|
||||
// Just resolve all the futures before continuing.
|
||||
}
|
||||
|
||||
$phids = array();
|
||||
$content = array();
|
||||
|
||||
try {
|
||||
$history_results = $this->callConduitWithDiffusionRequest(
|
||||
'diffusion.historyquery',
|
||||
array(
|
||||
'commit' => $drequest->getCommit(),
|
||||
'path' => $drequest->getPath(),
|
||||
'offset' => 0,
|
||||
'limit' => 15,
|
||||
));
|
||||
$history_results = $this->historyFuture->resolve();
|
||||
$history = DiffusionPathChange::newFromConduit(
|
||||
$history_results['pathChanges']);
|
||||
|
||||
|
@ -139,18 +193,11 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
$history_exception = $ex;
|
||||
}
|
||||
|
||||
$browse_pager = id(new PHUIPagerView())
|
||||
->readFromRequest($request);
|
||||
|
||||
try {
|
||||
$browse_results = $this->browseFuture->resolve();
|
||||
$browse_results = DiffusionBrowseResultSet::newFromConduit(
|
||||
$this->callConduitWithDiffusionRequest(
|
||||
'diffusion.browsequery',
|
||||
array(
|
||||
'path' => $drequest->getPath(),
|
||||
'commit' => $drequest->getCommit(),
|
||||
'limit' => $browse_pager->getPageSize() + 1,
|
||||
)));
|
||||
$browse_results);
|
||||
|
||||
$browse_paths = $browse_results->getPaths();
|
||||
$browse_paths = $browse_pager->sliceResults($browse_paths);
|
||||
|
||||
|
@ -366,22 +413,16 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
private function buildBranchListTable(DiffusionRequest $drequest) {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
if ($drequest->getBranch() === null) {
|
||||
if (!$this->needBranchFuture()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$limit = 15;
|
||||
|
||||
$branches = $this->callConduitWithDiffusionRequest(
|
||||
'diffusion.branchquery',
|
||||
array(
|
||||
'closed' => false,
|
||||
'limit' => $limit + 1,
|
||||
));
|
||||
$branches = $this->branchFuture->resolve();
|
||||
if (!$branches) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$limit = $this->getBranchLimit();
|
||||
$more_branches = (count($branches) > $limit);
|
||||
$branches = array_slice($branches, 0, $limit);
|
||||
|
||||
|
@ -428,26 +469,17 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
$viewer = $this->getViewer();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
switch ($repository->getVersionControlSystem()) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
||||
// no tags in SVN
|
||||
if (!$this->needTagFuture()) {
|
||||
return null;
|
||||
}
|
||||
$tag_limit = 15;
|
||||
$tags = array();
|
||||
$tags = DiffusionRepositoryTag::newFromConduit(
|
||||
$this->callConduitWithDiffusionRequest(
|
||||
'diffusion.tagsquery',
|
||||
array(
|
||||
// On the home page, we want to find tags on any branch.
|
||||
'commit' => null,
|
||||
'limit' => $tag_limit + 1,
|
||||
)));
|
||||
|
||||
$tags = $this->tagFuture->resolve();
|
||||
$tags = DiffusionRepositoryTag::newFromConduit($tags);
|
||||
if (!$tags) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$tag_limit = $this->getTagLimit();
|
||||
$more_tags = (count($tags) > $tag_limit);
|
||||
$tags = array_slice($tags, 0, $tag_limit);
|
||||
|
||||
|
@ -688,4 +720,35 @@ final class DiffusionRepositoryController extends DiffusionController {
|
|||
->setDisplayURI($display);
|
||||
}
|
||||
|
||||
private function needTagFuture() {
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
switch ($repository->getVersionControlSystem()) {
|
||||
case PhabricatorRepositoryType::REPOSITORY_TYPE_SVN:
|
||||
// No tags in SVN.
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getTagLimit() {
|
||||
return 15;
|
||||
}
|
||||
|
||||
private function needBranchFuture() {
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
|
||||
if ($drequest->getBranch() === null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function getBranchLimit() {
|
||||
return 15;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -610,7 +610,11 @@ final class DiffusionCommitHookEngine extends Phobject {
|
|||
self::ENV_REMOTE_ADDRESS => $this->getRemoteAddress(),
|
||||
);
|
||||
|
||||
$directories = $this->getRepository()->getHookDirectories();
|
||||
$repository = $this->getRepository();
|
||||
|
||||
$env += $repository->getPassthroughEnvironmentalVariables();
|
||||
|
||||
$directories = $repository->getHookDirectories();
|
||||
foreach ($directories as $directory) {
|
||||
$hooks = $this->getExecutablesInDirectory($directory);
|
||||
sort($hooks);
|
||||
|
|
|
@ -0,0 +1,52 @@
|
|||
<?php
|
||||
|
||||
final class DiffusionCacheEngineExtension
|
||||
extends PhabricatorCacheEngineExtension {
|
||||
|
||||
const EXTENSIONKEY = 'diffusion';
|
||||
|
||||
public function getExtensionName() {
|
||||
return pht('Diffusion Repositories');
|
||||
}
|
||||
|
||||
public function discoverLinkedObjects(
|
||||
PhabricatorCacheEngine $engine,
|
||||
array $objects) {
|
||||
$viewer = $engine->getViewer();
|
||||
$results = array();
|
||||
|
||||
// When an Almanac Service changes, update linked repositories.
|
||||
|
||||
$services = $this->selectObjects($objects, 'AlmanacService');
|
||||
if ($services) {
|
||||
$repositories = id(new PhabricatorRepositoryQuery())
|
||||
->setViewer($viewer)
|
||||
->withAlmanacServicePHIDs(mpull($services, 'getPHID'))
|
||||
->execute();
|
||||
foreach ($repositories as $repository) {
|
||||
$results[] = $repository;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
public function deleteCaches(
|
||||
PhabricatorCacheEngine $engine,
|
||||
array $objects) {
|
||||
|
||||
$keys = array();
|
||||
$repositories = $this->selectObjects($objects, 'PhabricatorRepository');
|
||||
foreach ($repositories as $repository) {
|
||||
$keys[] = $repository->getAlmanacServiceCacheKey();
|
||||
}
|
||||
|
||||
$keys = array_filter($keys);
|
||||
|
||||
if ($keys) {
|
||||
$cache = PhabricatorCaches::getMutableStructureCache();
|
||||
$cache->deleteKeys($keys);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -209,6 +209,8 @@ abstract class DiffusionCommandEngine extends Phobject {
|
|||
}
|
||||
}
|
||||
|
||||
$env += $repository->getPassthroughEnvironmentalVariables();
|
||||
|
||||
return $env;
|
||||
}
|
||||
|
||||
|
|
|
@ -48,7 +48,8 @@ abstract class DiffusionQuery extends PhabricatorQuery {
|
|||
PhabricatorUser $user,
|
||||
DiffusionRequest $drequest,
|
||||
$method,
|
||||
array $params = array()) {
|
||||
array $params = array(),
|
||||
$return_future = false) {
|
||||
|
||||
$repository = $drequest->getRepository();
|
||||
|
||||
|
@ -76,12 +77,19 @@ abstract class DiffusionQuery extends PhabricatorQuery {
|
|||
$user,
|
||||
$drequest->getIsClusterRequest());
|
||||
if (!$client) {
|
||||
return id(new ConduitCall($method, $params))
|
||||
$result = id(new ConduitCall($method, $params))
|
||||
->setUser($user)
|
||||
->execute();
|
||||
$future = new ImmediateFuture($result);
|
||||
} else {
|
||||
return $client->callMethodSynchronous($method, $params);
|
||||
$future = $client->callMethod($method, $params);
|
||||
}
|
||||
|
||||
if (!$return_future) {
|
||||
return $future->resolve();
|
||||
}
|
||||
|
||||
return $future;
|
||||
}
|
||||
|
||||
public function execute() {
|
||||
|
|
|
@ -644,7 +644,7 @@ abstract class DiffusionRequest extends Phobject {
|
|||
return $match;
|
||||
}
|
||||
|
||||
private function resolveRefs(array $refs, array $types) {
|
||||
public function resolveRefs(array $refs, array $types = array()) {
|
||||
// First, try to resolve refs from fast cache sources.
|
||||
$cached_query = id(new DiffusionCachedResolveRefsQuery())
|
||||
->setRepository($this->getRepository())
|
||||
|
|
|
@ -8,6 +8,7 @@ final class DiffusionHistoryTableView extends DiffusionView {
|
|||
private $isHead;
|
||||
private $isTail;
|
||||
private $parents;
|
||||
private $filterParents;
|
||||
|
||||
public function setHistory(array $history) {
|
||||
assert_instances_of($history, 'DiffusionPathChange');
|
||||
|
@ -66,6 +67,15 @@ final class DiffusionHistoryTableView extends DiffusionView {
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function setFilterParents($filter_parents) {
|
||||
$this->filterParents = $filter_parents;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getFilterParents() {
|
||||
return $this->filterParents;
|
||||
}
|
||||
|
||||
public function render() {
|
||||
$drequest = $this->getDiffusionRequest();
|
||||
|
||||
|
@ -82,10 +92,26 @@ final class DiffusionHistoryTableView extends DiffusionView {
|
|||
|
||||
$graph = null;
|
||||
if ($this->parents) {
|
||||
$parents = $this->parents;
|
||||
|
||||
// If we're filtering parents, remove relationships which point to
|
||||
// commits that are not part of the visible graph. Otherwise, we get
|
||||
// a big tree of nonsense when viewing release branches like "stable"
|
||||
// versus "master".
|
||||
if ($this->filterParents) {
|
||||
foreach ($parents as $key => $nodes) {
|
||||
foreach ($nodes as $nkey => $node) {
|
||||
if (empty($parents[$node])) {
|
||||
unset($parents[$key][$nkey]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$graph = id(new PHUIDiffGraphView())
|
||||
->setIsHead($this->isHead)
|
||||
->setIsTail($this->isTail)
|
||||
->renderGraph($this->parents);
|
||||
->renderGraph($parents);
|
||||
}
|
||||
|
||||
$show_builds = PhabricatorApplication::isClassInstalledForViewer(
|
||||
|
|
|
@ -38,6 +38,10 @@ final class HeraldRulePHIDType extends PhabricatorPHIDType {
|
|||
$handle->setName($monogram);
|
||||
$handle->setFullName("{$monogram} {$name}");
|
||||
$handle->setURI("/{$monogram}");
|
||||
|
||||
if ($rule->getIsDisabled()) {
|
||||
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -23,10 +23,6 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication {
|
|||
return false;
|
||||
}
|
||||
|
||||
public function canUninstall() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getBaseURI() {
|
||||
return '/nuance/';
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ final class PhabricatorUserPreferencesCacheType
|
|||
|
||||
const CACHETYPE = 'preferences';
|
||||
|
||||
const KEY_PREFERENCES = 'user.preferences.v1';
|
||||
const KEY_PREFERENCES = 'user.preferences.v2';
|
||||
|
||||
public function getAutoloadKeys() {
|
||||
return array(
|
||||
|
@ -47,6 +47,16 @@ final class PhabricatorUserPreferencesCacheType
|
|||
foreach ($all_settings as $key => $setting) {
|
||||
$value = $preference->getSettingValue($key);
|
||||
|
||||
try {
|
||||
id(clone $setting)
|
||||
->setViewer($viewer)
|
||||
->assertValidValue($value);
|
||||
} catch (Exception $ex) {
|
||||
// If the saved value isn't valid, don't cache it: we'll use the
|
||||
// default value instead.
|
||||
continue;
|
||||
}
|
||||
|
||||
// As an optimization, we omit the value from the cache if it is
|
||||
// exactly the same as the hardcoded default.
|
||||
$default_value = id(clone $setting)
|
||||
|
|
|
@ -64,6 +64,7 @@ final class PhabricatorUser
|
|||
private $settingCacheKeys = array();
|
||||
private $settingCache = array();
|
||||
private $allowInlineCacheGeneration;
|
||||
private $conduitClusterToken = self::ATTACHABLE;
|
||||
|
||||
protected function readField($field) {
|
||||
switch ($field) {
|
||||
|
@ -490,32 +491,19 @@ final class PhabricatorUser
|
|||
$settings = $this->loadGlobalSettings();
|
||||
}
|
||||
|
||||
// NOTE: To slightly improve performance, we're using all settings here,
|
||||
// not just settings that are enabled for the current viewer. It's fine to
|
||||
// get the value of a setting that we wouldn't let the user edit in the UI.
|
||||
$defaults = PhabricatorSetting::getAllSettings();
|
||||
|
||||
if (array_key_exists($key, $settings)) {
|
||||
$value = $settings[$key];
|
||||
|
||||
// Make sure the value is valid before we return it. This makes things
|
||||
// more robust when options are changed or removed.
|
||||
if (isset($defaults[$key])) {
|
||||
try {
|
||||
id(clone $defaults[$key])
|
||||
->setViewer($this)
|
||||
->assertValidValue($value);
|
||||
|
||||
return $this->writeUserSettingCache($key, $value);
|
||||
} catch (Exception $ex) {
|
||||
// Fall through below and return the default value.
|
||||
}
|
||||
|
||||
$cache = PhabricatorCaches::getRuntimeCache();
|
||||
$cache_key = "settings.defaults({$key})";
|
||||
$cache_map = $cache->getKeys(array($cache_key));
|
||||
|
||||
if ($cache_map) {
|
||||
$value = $cache_map[$cache_key];
|
||||
} else {
|
||||
// This is an ad-hoc setting with no controlling object.
|
||||
return $this->writeUserSettingCache($key, $value);
|
||||
}
|
||||
}
|
||||
|
||||
$defaults = PhabricatorSetting::getAllSettings();
|
||||
if (isset($defaults[$key])) {
|
||||
$value = id(clone $defaults[$key])
|
||||
->setViewer($this)
|
||||
|
@ -524,6 +512,9 @@ final class PhabricatorUser
|
|||
$value = null;
|
||||
}
|
||||
|
||||
$cache->setKey($cache_key, $value);
|
||||
}
|
||||
|
||||
return $this->writeUserSettingCache($key, $value);
|
||||
}
|
||||
|
||||
|
@ -555,12 +546,16 @@ final class PhabricatorUser
|
|||
return $this->getUserSetting(PhabricatorTimezoneSetting::SETTINGKEY);
|
||||
}
|
||||
|
||||
private function loadGlobalSettings() {
|
||||
$cache_key = 'user.settings.global';
|
||||
$cache = PhabricatorCaches::getRequestCache();
|
||||
$settings = $cache->getKey($cache_key);
|
||||
public static function getGlobalSettingsCacheKey() {
|
||||
return 'user.settings.globals.v1';
|
||||
}
|
||||
|
||||
if ($settings === null) {
|
||||
private function loadGlobalSettings() {
|
||||
$cache_key = self::getGlobalSettingsCacheKey();
|
||||
$cache = PhabricatorCaches::getMutableStructureCache();
|
||||
|
||||
$settings = $cache->getKey($cache_key);
|
||||
if (!$settings) {
|
||||
$preferences = PhabricatorUserPreferences::loadGlobalPreferences($this);
|
||||
$settings = $preferences->getPreferences();
|
||||
$cache->setKey($cache_key, $settings);
|
||||
|
@ -943,6 +938,19 @@ final class PhabricatorUser
|
|||
return $this->authorities;
|
||||
}
|
||||
|
||||
public function hasConduitClusterToken() {
|
||||
return ($this->conduitClusterToken !== self::ATTACHABLE);
|
||||
}
|
||||
|
||||
public function attachConduitClusterToken(PhabricatorConduitToken $token) {
|
||||
$this->conduitClusterToken = $token;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function getConduitClusterToken() {
|
||||
return $this->assertAttached($this->conduitClusterToken);
|
||||
}
|
||||
|
||||
|
||||
/* -( Availability )------------------------------------------------------- */
|
||||
|
||||
|
@ -1495,9 +1503,27 @@ final class PhabricatorUser
|
|||
throw new PhabricatorDataNotAttachedException($this);
|
||||
}
|
||||
|
||||
$user_phid = $this->getPHID();
|
||||
|
||||
// Try to read the actual cache before we generate a new value. We can
|
||||
// end up here via Conduit, which does not use normal sessions and can
|
||||
// not pick up a free cache load during session identification.
|
||||
if ($user_phid) {
|
||||
$raw_data = PhabricatorUserCache::readCaches(
|
||||
$type,
|
||||
$key,
|
||||
array($user_phid));
|
||||
if (array_key_exists($user_phid, $raw_data)) {
|
||||
$raw_value = $raw_data[$user_phid];
|
||||
$usable_value = $type->getValueFromStorage($raw_value);
|
||||
$this->rawCacheData[$key] = $raw_value;
|
||||
$this->usableCacheData[$key] = $usable_value;
|
||||
return $usable_value;
|
||||
}
|
||||
}
|
||||
|
||||
$usable_value = $type->getDefaultValue();
|
||||
|
||||
$user_phid = $this->getPHID();
|
||||
if ($user_phid) {
|
||||
$map = $type->newValueForUsers($key, array($this));
|
||||
if (array_key_exists($user_phid, $map)) {
|
||||
|
|
|
@ -96,6 +96,26 @@ final class PhabricatorUserCache extends PhabricatorUserDAO {
|
|||
unset($unguarded);
|
||||
}
|
||||
|
||||
public static function readCaches(
|
||||
PhabricatorUserCacheType $type,
|
||||
$key,
|
||||
array $user_phids) {
|
||||
|
||||
$table = new self();
|
||||
$conn = $table->establishConnection('r');
|
||||
|
||||
$rows = queryfx_all(
|
||||
$conn,
|
||||
'SELECT userPHID, cacheData FROM %T WHERE userPHID IN (%Ls)
|
||||
AND cacheType = %s AND cacheIndex = %s',
|
||||
$table->getTableName(),
|
||||
$user_phids,
|
||||
$type->getUserCacheType(),
|
||||
PhabricatorHash::digestForIndex($key));
|
||||
|
||||
return ipull($rows, 'cacheData', 'userPHID');
|
||||
}
|
||||
|
||||
public static function clearCache($key, $user_phid) {
|
||||
return self::clearCaches($key, array($user_phid));
|
||||
}
|
||||
|
|
|
@ -108,18 +108,28 @@ final class PhabricatorUserLog extends PhabricatorUserDAO
|
|||
$log->setUserPHID((string)$object_phid);
|
||||
$log->setAction($action);
|
||||
|
||||
$log->remoteAddr = (string)idx($_SERVER, 'REMOTE_ADDR', '');
|
||||
$address = PhabricatorEnv::getRemoteAddress();
|
||||
if ($address) {
|
||||
$log->remoteAddr = $address->getAddress();
|
||||
} else {
|
||||
$log->remoteAddr = '';
|
||||
}
|
||||
|
||||
return $log;
|
||||
}
|
||||
|
||||
public static function loadRecentEventsFromThisIP($action, $timespan) {
|
||||
$address = PhabricatorEnv::getRemoteAddress();
|
||||
if (!$address) {
|
||||
return array();
|
||||
}
|
||||
|
||||
return id(new PhabricatorUserLog())->loadAllWhere(
|
||||
'action = %s AND remoteAddr = %s AND dateCreated > %d
|
||||
ORDER BY dateCreated DESC',
|
||||
$action,
|
||||
idx($_SERVER, 'REMOTE_ADDR'),
|
||||
time() - $timespan);
|
||||
$address->getAddress(),
|
||||
PhabricatorTime::getNow() - $timespan);
|
||||
}
|
||||
|
||||
public function save() {
|
||||
|
|
|
@ -29,11 +29,6 @@ final class PhabricatorObjectQuery
|
|||
$this->namedResults = array();
|
||||
}
|
||||
|
||||
$types = PhabricatorPHIDType::getAllTypes();
|
||||
if ($this->types) {
|
||||
$types = array_select_keys($types, $this->types);
|
||||
}
|
||||
|
||||
$names = array_unique($this->names);
|
||||
$phids = $this->phids;
|
||||
|
||||
|
@ -51,15 +46,30 @@ final class PhabricatorObjectQuery
|
|||
}
|
||||
}
|
||||
|
||||
$phids = array_unique($phids);
|
||||
|
||||
if ($names) {
|
||||
$types = PhabricatorPHIDType::getAllTypes();
|
||||
if ($this->types) {
|
||||
$types = array_select_keys($types, $this->types);
|
||||
}
|
||||
$name_results = $this->loadObjectsByName($types, $names);
|
||||
} else {
|
||||
$name_results = array();
|
||||
}
|
||||
|
||||
if ($phids) {
|
||||
$phids = array_unique($phids);
|
||||
|
||||
$phid_types = array();
|
||||
foreach ($phids as $phid) {
|
||||
$phid_type = phid_get_type($phid);
|
||||
$phid_types[$phid_type] = $phid_type;
|
||||
}
|
||||
|
||||
$types = PhabricatorPHIDType::getTypes($phid_types);
|
||||
if ($this->types) {
|
||||
$types = array_select_keys($types, $this->types);
|
||||
}
|
||||
|
||||
$phid_results = $this->loadObjectsByPHID($types, $phids);
|
||||
} else {
|
||||
$phid_results = array();
|
||||
|
|
|
@ -144,10 +144,21 @@ abstract class PhabricatorPHIDType extends Phobject {
|
|||
* @return dict<string, PhabricatorPHIDType> Map of type constants to types.
|
||||
*/
|
||||
final public static function getAllTypes() {
|
||||
return self::newClassMapQuery()
|
||||
->execute();
|
||||
}
|
||||
|
||||
final public static function getTypes(array $types) {
|
||||
return id(new PhabricatorCachedClassMapQuery())
|
||||
->setClassMapQuery(self::newClassMapQuery())
|
||||
->setMapKeyMethod('getTypeConstant')
|
||||
->loadClasses($types);
|
||||
}
|
||||
|
||||
private static function newClassMapQuery() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->setUniqueMethod('getTypeConstant')
|
||||
->execute();
|
||||
->setUniqueMethod('getTypeConstant');
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -30,10 +30,6 @@ final class PhabricatorPhragmentApplication extends PhabricatorApplication {
|
|||
return true;
|
||||
}
|
||||
|
||||
public function canUninstall() {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getRoutes() {
|
||||
return array(
|
||||
'/phragment/' => array(
|
||||
|
|
|
@ -102,10 +102,21 @@ final class PhabricatorProjectDatasource
|
|||
}
|
||||
|
||||
$all_strings = array();
|
||||
$all_strings[] = $proj->getDisplayName();
|
||||
|
||||
// NOTE: We list the project's name first because results will be
|
||||
// sorted into prefix vs content phases incorrectly if we don't: it
|
||||
// will look like "Parent (Milestone)" matched "Parent" as a prefix,
|
||||
// but it did not.
|
||||
$all_strings[] = $proj->getName();
|
||||
|
||||
if ($proj->isMilestone()) {
|
||||
$all_strings[] = $proj->getParentProject()->getName();
|
||||
}
|
||||
|
||||
foreach ($proj->getSlugs() as $project_slug) {
|
||||
$all_strings[] = $project_slug->getSlug();
|
||||
}
|
||||
|
||||
$all_strings = implode("\n", $all_strings);
|
||||
|
||||
$proj_result = id(new PhabricatorTypeaheadResult())
|
||||
|
|
|
@ -12,6 +12,7 @@ final class PhabricatorRepositoryQuery
|
|||
private $uris;
|
||||
private $datasourceQuery;
|
||||
private $slugs;
|
||||
private $almanacServicePHIDs;
|
||||
|
||||
private $numericIdentifiers;
|
||||
private $callsignIdentifiers;
|
||||
|
@ -134,6 +135,11 @@ final class PhabricatorRepositoryQuery
|
|||
return $this;
|
||||
}
|
||||
|
||||
public function withAlmanacServicePHIDs(array $phids) {
|
||||
$this->almanacServicePHIDs = $phids;
|
||||
return $this;
|
||||
}
|
||||
|
||||
public function needCommitCounts($need_counts) {
|
||||
$this->needCommitCounts = $need_counts;
|
||||
return $this;
|
||||
|
@ -659,6 +665,13 @@ final class PhabricatorRepositoryQuery
|
|||
$try_uris);
|
||||
}
|
||||
|
||||
if ($this->almanacServicePHIDs !== null) {
|
||||
$where[] = qsprintf(
|
||||
$conn,
|
||||
'r.almanacServicePHID IN (%Ls)',
|
||||
$this->almanacServicePHIDs);
|
||||
}
|
||||
|
||||
return $where;
|
||||
}
|
||||
|
||||
|
|
|
@ -689,6 +689,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
case 'lint':
|
||||
case 'pathtree':
|
||||
case 'refs':
|
||||
case 'compare':
|
||||
break;
|
||||
case 'branch':
|
||||
// NOTE: This does not actually require a branch, and won't have one
|
||||
|
@ -710,6 +711,9 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
$commit = idx($params, 'commit');
|
||||
$line = idx($params, 'line');
|
||||
|
||||
$head = idx($params, 'head');
|
||||
$against = idx($params, 'against');
|
||||
|
||||
if ($req_commit && !strlen($commit)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
|
@ -734,11 +738,13 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
$path = phutil_escape_uri($path);
|
||||
}
|
||||
|
||||
$raw_branch = $branch;
|
||||
if (strlen($branch)) {
|
||||
$branch = phutil_escape_uri_path_component($branch);
|
||||
$path = "{$branch}/{$path}";
|
||||
}
|
||||
|
||||
$raw_commit = $commit;
|
||||
if (strlen($commit)) {
|
||||
$commit = str_replace('$', '$$', $commit);
|
||||
$commit = ';'.phutil_escape_uri($commit);
|
||||
|
@ -748,6 +754,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
$line = '$'.phutil_escape_uri($line);
|
||||
}
|
||||
|
||||
$query = array();
|
||||
switch ($action) {
|
||||
case 'change':
|
||||
case 'history':
|
||||
|
@ -760,6 +767,20 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
case 'refs':
|
||||
$uri = $this->getPathURI("/{$action}/{$path}{$commit}{$line}");
|
||||
break;
|
||||
case 'compare':
|
||||
$uri = $this->getPathURI("/{$action}/");
|
||||
if (strlen($head)) {
|
||||
$query['head'] = $head;
|
||||
} else if (strlen($raw_commit)) {
|
||||
$query['commit'] = $raw_commit;
|
||||
} else if (strlen($raw_branch)) {
|
||||
$query['head'] = $raw_branch;
|
||||
}
|
||||
|
||||
if (strlen($against)) {
|
||||
$query['against'] = $against;
|
||||
}
|
||||
break;
|
||||
case 'branch':
|
||||
if (strlen($path)) {
|
||||
$uri = $this->getPathURI("/repository/{$path}");
|
||||
|
@ -791,8 +812,10 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
);
|
||||
}
|
||||
|
||||
if (idx($params, 'params')) {
|
||||
$uri->setQueryParams($params['params']);
|
||||
$query = idx($params, 'params', array()) + $query;
|
||||
|
||||
if ($query) {
|
||||
$uri->setQueryParams($query);
|
||||
}
|
||||
|
||||
return $uri;
|
||||
|
@ -1842,21 +1865,25 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
$never_proxy,
|
||||
array $protocols) {
|
||||
|
||||
$service = $this->loadAlmanacService();
|
||||
if (!$service) {
|
||||
$cache_key = $this->getAlmanacServiceCacheKey();
|
||||
if (!$cache_key) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$bindings = $service->getActiveBindings();
|
||||
if (!$bindings) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'The Almanac service for this repository is not bound to any '.
|
||||
'interfaces.'));
|
||||
$cache = PhabricatorCaches::getMutableStructureCache();
|
||||
$uris = $cache->getKey($cache_key, false);
|
||||
|
||||
// If we haven't built the cache yet, build it now.
|
||||
if ($uris === false) {
|
||||
$uris = $this->buildAlmanacServiceURIs();
|
||||
$cache->setKey($cache_key, $uris);
|
||||
}
|
||||
|
||||
if ($uris === null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$local_device = AlmanacKeys::getDeviceID();
|
||||
|
||||
if ($never_proxy && !$local_device) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
|
@ -1867,10 +1894,8 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
|
||||
$protocol_map = array_fuse($protocols);
|
||||
|
||||
$uris = array();
|
||||
foreach ($bindings as $binding) {
|
||||
$iface = $binding->getInterface();
|
||||
|
||||
$results = array();
|
||||
foreach ($uris as $uri) {
|
||||
// If we're never proxying this and it's locally satisfiable, return
|
||||
// `null` to tell the caller to handle it locally. If we're allowed to
|
||||
// proxy, we skip this check and may proxy the request to ourselves.
|
||||
|
@ -1878,22 +1903,17 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
// return `null`, and then the request will actually run.)
|
||||
|
||||
if ($local_device && $never_proxy) {
|
||||
if ($iface->getDevice()->getName() == $local_device) {
|
||||
if ($uri['device'] == $local_device) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
$uri = $this->getClusterRepositoryURIFromBinding($binding);
|
||||
|
||||
$protocol = $uri->getProtocol();
|
||||
if (empty($protocol_map[$protocol])) {
|
||||
continue;
|
||||
if (isset($protocol_map[$uri['protocol']])) {
|
||||
$results[] = new PhutilURI($uri['uri']);
|
||||
}
|
||||
}
|
||||
|
||||
$uris[] = $uri;
|
||||
}
|
||||
|
||||
if (!$uris) {
|
||||
if (!$results) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'The Almanac service for this repository is not bound to any '.
|
||||
|
@ -1908,10 +1928,51 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
'Cluster hosts must correctly route their intracluster requests.'));
|
||||
}
|
||||
|
||||
shuffle($uris);
|
||||
return head($uris);
|
||||
shuffle($results);
|
||||
return head($results);
|
||||
}
|
||||
|
||||
public function getAlmanacServiceCacheKey() {
|
||||
$service_phid = $this->getAlmanacServicePHID();
|
||||
if (!$service_phid) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$repository_phid = $this->getPHID();
|
||||
return "diffusion.repository({$repository_phid}).service({$service_phid})";
|
||||
}
|
||||
|
||||
private function buildAlmanacServiceURIs() {
|
||||
$service = $this->loadAlmanacService();
|
||||
if (!$service) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$bindings = $service->getActiveBindings();
|
||||
if (!$bindings) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'The Almanac service for this repository is not bound to any '.
|
||||
'interfaces.'));
|
||||
}
|
||||
|
||||
$uris = array();
|
||||
foreach ($bindings as $binding) {
|
||||
$iface = $binding->getInterface();
|
||||
|
||||
$uri = $this->getClusterRepositoryURIFromBinding($binding);
|
||||
$protocol = $uri->getProtocol();
|
||||
$device_name = $iface->getDevice()->getName();
|
||||
|
||||
$uris[] = array(
|
||||
'protocol' => $protocol,
|
||||
'uri' => (string)$uri,
|
||||
'device' => $device_name,
|
||||
);
|
||||
}
|
||||
|
||||
return $uris;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build a new Conduit client in order to make a service call to this
|
||||
|
@ -2015,6 +2076,61 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
|
|||
return $client;
|
||||
}
|
||||
|
||||
public function getPassthroughEnvironmentalVariables() {
|
||||
$env = $_ENV;
|
||||
|
||||
if ($this->isGit()) {
|
||||
// $_ENV does not populate in CLI contexts if "E" is missing from
|
||||
// "variables_order" in PHP config. Currently, we do not require this
|
||||
// to be configured. Since it may not be, explictitly bring expected Git
|
||||
// environmental variables into scope. This list is not exhaustive, but
|
||||
// only lists variables with a known impact on commit hook behavior.
|
||||
|
||||
// This can be removed if we later require "E" in "variables_order".
|
||||
|
||||
$git_env = array(
|
||||
'GIT_OBJECT_DIRECTORY',
|
||||
'GIT_ALTERNATE_OBJECT_DIRECTORIES',
|
||||
'GIT_QUARANTINE_PATH',
|
||||
);
|
||||
foreach ($git_env as $key) {
|
||||
$value = getenv($key);
|
||||
if (strlen($value)) {
|
||||
$env[$key] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
$key = 'GIT_PUSH_OPTION_COUNT';
|
||||
$git_count = getenv($key);
|
||||
if (strlen($git_count)) {
|
||||
$git_count = (int)$git_count;
|
||||
$env[$key] = $git_count;
|
||||
for ($ii = 0; $ii < $git_count; $ii++) {
|
||||
$key = 'GIT_PUSH_OPTION_'.$ii;
|
||||
$env[$key] = getenv($key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$result = array();
|
||||
foreach ($env as $key => $value) {
|
||||
// In Git, pass anything matching "GIT_*" though. Some of these variables
|
||||
// need to be preserved to allow `git` operations to work properly when
|
||||
// running from commit hooks.
|
||||
if ($this->isGit()) {
|
||||
if (preg_match('/^GIT_/', $key)) {
|
||||
$result[$key] = $value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
public function supportsBranchComparison() {
|
||||
return $this->isGit();
|
||||
}
|
||||
|
||||
/* -( Repository URIs )---------------------------------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -0,0 +1,79 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorApplicationProfilePanel
|
||||
extends PhabricatorProfilePanel {
|
||||
|
||||
const PANELKEY = 'application';
|
||||
|
||||
public function getPanelTypeIcon() {
|
||||
return 'fa-globe';
|
||||
}
|
||||
|
||||
public function getPanelTypeName() {
|
||||
return pht('Application');
|
||||
}
|
||||
|
||||
public function canAddToObject($object) {
|
||||
return true;
|
||||
}
|
||||
|
||||
public function getDisplayName(
|
||||
PhabricatorProfilePanelConfiguration $config) {
|
||||
$app = $this->getApplication($config);
|
||||
if ($app) {
|
||||
return $app->getName();
|
||||
} else {
|
||||
return pht('(Uninstalled Application)');
|
||||
}
|
||||
return $app->getName();
|
||||
}
|
||||
|
||||
public function buildEditEngineFields(
|
||||
PhabricatorProfilePanelConfiguration $config) {
|
||||
return array(
|
||||
id(new PhabricatorDatasourceEditField())
|
||||
->setKey('application')
|
||||
->setLabel(pht('Application'))
|
||||
->setDatasource(new PhabricatorApplicationDatasource())
|
||||
->setSingleValue($config->getPanelProperty('application')),
|
||||
);
|
||||
}
|
||||
|
||||
private function getApplication(
|
||||
PhabricatorProfilePanelConfiguration $config) {
|
||||
$viewer = $this->getViewer();
|
||||
$phid = $config->getPanelProperty('application');
|
||||
$app = id(new PhabricatorApplicationQuery())
|
||||
->setViewer($viewer)
|
||||
->withPHIDs(array($phid))
|
||||
->executeOne();
|
||||
|
||||
return $app;
|
||||
}
|
||||
|
||||
protected function newNavigationMenuItems(
|
||||
PhabricatorProfilePanelConfiguration $config) {
|
||||
$viewer = $this->getViewer();
|
||||
$app = $this->getApplication($config);
|
||||
if (!$app) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$is_installed = PhabricatorApplication::isClassInstalledForViewer(
|
||||
get_class($app),
|
||||
$viewer);
|
||||
if (!$is_installed) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$item = $this->newItem()
|
||||
->setHref($app->getApplicationURI())
|
||||
->setName($app->getName())
|
||||
->setIcon($app->getIcon());
|
||||
|
||||
return array(
|
||||
$item,
|
||||
);
|
||||
}
|
||||
|
||||
}
|
|
@ -158,6 +158,9 @@ final class PhabricatorUserPreferencesEditor
|
|||
PhabricatorUserPreferencesCacheType::KEY_PREFERENCES,
|
||||
$user_phid);
|
||||
} else {
|
||||
$cache = PhabricatorCaches::getMutableStructureCache();
|
||||
$cache->deleteKey(PhabricatorUserPreferences::getGlobalCacheKey());
|
||||
|
||||
PhabricatorUserCache::clearCacheForAllUsers(
|
||||
PhabricatorUserPreferencesCacheType::KEY_PREFERENCES);
|
||||
}
|
||||
|
|
|
@ -10,12 +10,7 @@ final class PhabricatorPinnedApplicationsSetting
|
|||
}
|
||||
|
||||
public function getSettingDefaultValue() {
|
||||
$viewer = $this->getViewer();
|
||||
|
||||
// If we're editing a template, just show every available application.
|
||||
if (!$viewer) {
|
||||
$viewer = PhabricatorUser::getOmnipotentUser();
|
||||
}
|
||||
|
||||
$applications = id(new PhabricatorApplicationQuery())
|
||||
->setViewer($viewer)
|
||||
|
|
|
@ -219,6 +219,13 @@ final class PhabricatorUserPreferences
|
|||
}
|
||||
}
|
||||
|
||||
switch ($this->getBuiltinKey()) {
|
||||
case self::BUILTIN_GLOBAL_DEFAULT:
|
||||
// NOTE: Without this policy exception, the logged-out viewer can not
|
||||
// see global preferences.
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
94
src/applications/system/engine/PhabricatorCacheEngine.php
Normal file
94
src/applications/system/engine/PhabricatorCacheEngine.php
Normal file
|
@ -0,0 +1,94 @@
|
|||
<?php
|
||||
|
||||
final class PhabricatorCacheEngine extends Phobject {
|
||||
|
||||
public function getViewer() {
|
||||
return PhabricatorUser::getOmnipotentUser();
|
||||
}
|
||||
|
||||
public function updateObject($object) {
|
||||
$objects = array(
|
||||
$object->getPHID() => $object,
|
||||
);
|
||||
|
||||
$new_objects = $objects;
|
||||
while (true) {
|
||||
$discovered_objects = array();
|
||||
$load = array();
|
||||
|
||||
$extensions = PhabricatorCacheEngineExtension::getAllExtensions();
|
||||
foreach ($extensions as $key => $extension) {
|
||||
$discoveries = $extension->discoverLinkedObjects($this, $new_objects);
|
||||
if (!is_array($discoveries)) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Cache engine extension "%s" did not return a list of linked '.
|
||||
'objects.',
|
||||
get_class($extension)));
|
||||
}
|
||||
|
||||
foreach ($discoveries as $discovery) {
|
||||
if ($discovery === null) {
|
||||
// This is allowed because it makes writing extensions a lot
|
||||
// easier if they don't have to check that related PHIDs are
|
||||
// actually set to something.
|
||||
continue;
|
||||
}
|
||||
|
||||
$is_phid = is_string($discovery);
|
||||
if ($is_phid) {
|
||||
$phid = $discovery;
|
||||
} else {
|
||||
$phid = $discovery->getPHID();
|
||||
if (!$phid) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Cache engine extension "%s" returned object (of class '.
|
||||
'"%s") with no PHID.',
|
||||
get_class($extension),
|
||||
get_class($discovery)));
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($objects[$phid])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($is_phid) {
|
||||
$load[$phid] = $phid;
|
||||
} else {
|
||||
$objects[$phid] = $discovery;
|
||||
$discovered_objects[$phid] = $discovery;
|
||||
|
||||
// If another extension only knew about the PHID of this object,
|
||||
// we don't need to load it any more.
|
||||
unset($load[$phid]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($load) {
|
||||
$load_objects = id(new PhabricatorObjectQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withPHIDs($load)
|
||||
->execute();
|
||||
foreach ($load_objects as $phid => $loaded_object) {
|
||||
$objects[$phid] = $loaded_object;
|
||||
$discovered_objects[$phid] = $loaded_object;
|
||||
}
|
||||
}
|
||||
|
||||
// If we didn't find anything new to update, we're all set.
|
||||
if (!$discovered_objects) {
|
||||
break;
|
||||
}
|
||||
|
||||
$new_objects = $discovered_objects;
|
||||
}
|
||||
|
||||
foreach ($extensions as $extension) {
|
||||
$extension->deleteCaches($this, $objects);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
|
@ -0,0 +1,42 @@
|
|||
<?php
|
||||
|
||||
abstract class PhabricatorCacheEngineExtension extends Phobject {
|
||||
|
||||
final public function getExtensionKey() {
|
||||
return $this->getPhobjectClassConstant('EXTENSIONKEY');
|
||||
}
|
||||
|
||||
abstract public function getExtensionName();
|
||||
|
||||
public function discoverLinkedObjects(
|
||||
PhabricatorCacheEngine $engine,
|
||||
array $objects) {
|
||||
return array();
|
||||
}
|
||||
|
||||
public function deleteCaches(
|
||||
PhabricatorCacheEngine $engine,
|
||||
array $objects) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final public static function getAllExtensions() {
|
||||
return id(new PhutilClassMapQuery())
|
||||
->setAncestorClass(__CLASS__)
|
||||
->setUniqueMethod('getExtensionKey')
|
||||
->execute();
|
||||
}
|
||||
|
||||
final public function selectObjects(array $objects, $class_name) {
|
||||
$results = array();
|
||||
|
||||
foreach ($objects as $phid => $object) {
|
||||
if ($object instanceof $class_name) {
|
||||
$results[$phid] = $object;
|
||||
}
|
||||
}
|
||||
|
||||
return $results;
|
||||
}
|
||||
|
||||
}
|
|
@ -987,6 +987,10 @@ abstract class PhabricatorApplicationTransactionEditor
|
|||
throw $ex;
|
||||
}
|
||||
|
||||
// If we need to perform cache engine updates, execute them now.
|
||||
id(new PhabricatorCacheEngine())
|
||||
->updateObject($object);
|
||||
|
||||
// Now that we've completely applied the core transaction set, try to apply
|
||||
// Herald rules. Herald rules are allowed to either take direct actions on
|
||||
// the database (like writing flags), or take indirect actions (like saving
|
||||
|
|
|
@ -141,7 +141,9 @@ abstract class PhabricatorTypeaheadDatasource extends Phobject {
|
|||
return array();
|
||||
}
|
||||
|
||||
$tokens = preg_split('/[\s\[\]-]+/u', $string);
|
||||
// NOTE: Splitting on "(" and ")" is important for milestones.
|
||||
|
||||
$tokens = preg_split('/[\s\[\]\(\)-]+/u', $string);
|
||||
$tokens = array_unique($tokens);
|
||||
|
||||
// Make sure we don't return the empty token, as this will boil down to a
|
||||
|
|
|
@ -27,6 +27,18 @@ final class PhabricatorTypeaheadDatasourceTestCase
|
|||
$this->assertTokenization(
|
||||
'[[ brackets ]] [-] ]-[ tie-fighters',
|
||||
array('brackets', 'tie', 'fighters'));
|
||||
|
||||
$this->assertTokenization(
|
||||
'viewer()',
|
||||
array('viewer'));
|
||||
|
||||
$this->assertTokenization(
|
||||
'Work (Done)',
|
||||
array('work', 'done'));
|
||||
|
||||
$this->assertTokenization(
|
||||
'A (B C D)',
|
||||
array('a', 'b', 'c', 'd'));
|
||||
}
|
||||
|
||||
private function assertTokenization($input, $expect) {
|
||||
|
|
|
@ -6,8 +6,6 @@ Guide to partitioning Phabricator applications across multiple database hosts.
|
|||
Overview
|
||||
========
|
||||
|
||||
WARNING: Partitioning is a prototype.
|
||||
|
||||
You can partition Phabricator's applications across multiple databases. For
|
||||
example, you can move an application like Files or Maniphest to a dedicated
|
||||
database host.
|
||||
|
@ -19,17 +17,18 @@ The advantages of doing this are:
|
|||
- you can match application workloads to hardware or configuration to make
|
||||
operating the cluster easier.
|
||||
|
||||
This configuration is complex, and very few installs need to pursue it.
|
||||
Phabricator will normally run comfortably with a single database master even
|
||||
for large organizations.
|
||||
This configuration is complex, and very few installs will benefit from pursuing
|
||||
it. Phabricator will normally run comfortably with a single database master
|
||||
even for large organizations.
|
||||
|
||||
Partitioning generally does not do much to increase resiliance or make it
|
||||
easier to recover from disasters, and is primarily a mechanism for scaling.
|
||||
easier to recover from disasters, and is primarily a mechanism for scaling and
|
||||
operational convenience.
|
||||
|
||||
If you are considering partitioning, you likely want to configure replication
|
||||
with a single master first. Even if you choose not to deploy replication, you
|
||||
should review and understand how replication works before you partition. For
|
||||
details, see @{Cluster:Databases}.
|
||||
details, see @{article:Cluster: Databases}.
|
||||
|
||||
Databases also support some advanced configuration options. Briefly:
|
||||
|
||||
|
|
31
src/infrastructure/env/PhabricatorEnv.php
vendored
31
src/infrastructure/env/PhabricatorEnv.php
vendored
|
@ -745,10 +745,10 @@ final class PhabricatorEnv extends Phobject {
|
|||
* @task uri
|
||||
*/
|
||||
public static function requireValidRemoteURIForFetch(
|
||||
$uri,
|
||||
$raw_uri,
|
||||
array $protocols) {
|
||||
|
||||
$uri = new PhutilURI($uri);
|
||||
$uri = new PhutilURI($raw_uri);
|
||||
|
||||
$proto = $uri->getProtocol();
|
||||
if (!strlen($proto)) {
|
||||
|
@ -756,7 +756,7 @@ final class PhabricatorEnv extends Phobject {
|
|||
pht(
|
||||
'URI "%s" is not a valid fetchable resource. A valid fetchable '.
|
||||
'resource URI must specify a protocol.',
|
||||
$uri));
|
||||
$raw_uri));
|
||||
}
|
||||
|
||||
$protocols = array_fuse($protocols);
|
||||
|
@ -765,7 +765,7 @@ final class PhabricatorEnv extends Phobject {
|
|||
pht(
|
||||
'URI "%s" is not a valid fetchable resource. A valid fetchable '.
|
||||
'resource URI must use one of these protocols: %s.',
|
||||
$uri,
|
||||
$raw_uri,
|
||||
implode(', ', array_keys($protocols))));
|
||||
}
|
||||
|
||||
|
@ -775,7 +775,7 @@ final class PhabricatorEnv extends Phobject {
|
|||
pht(
|
||||
'URI "%s" is not a valid fetchable resource. A valid fetchable '.
|
||||
'resource URI must specify a domain.',
|
||||
$uri));
|
||||
$raw_uri));
|
||||
}
|
||||
|
||||
$addresses = gethostbynamel($domain);
|
||||
|
@ -784,7 +784,7 @@ final class PhabricatorEnv extends Phobject {
|
|||
pht(
|
||||
'URI "%s" is not a valid fetchable resource. The domain "%s" could '.
|
||||
'not be resolved.',
|
||||
$uri,
|
||||
$raw_uri,
|
||||
$domain));
|
||||
}
|
||||
|
||||
|
@ -795,7 +795,7 @@ final class PhabricatorEnv extends Phobject {
|
|||
'URI "%s" is not a valid fetchable resource. The domain "%s" '.
|
||||
'resolves to the address "%s", which is blacklisted for '.
|
||||
'outbound requests.',
|
||||
$uri,
|
||||
$raw_uri,
|
||||
$domain,
|
||||
$address));
|
||||
}
|
||||
|
@ -826,12 +826,12 @@ final class PhabricatorEnv extends Phobject {
|
|||
return false;
|
||||
}
|
||||
|
||||
$address = idx($_SERVER, 'REMOTE_ADDR');
|
||||
$address = self::getRemoteAddress();
|
||||
if (!$address) {
|
||||
throw new Exception(
|
||||
pht(
|
||||
'Unable to test remote address against cluster whitelist: '.
|
||||
'REMOTE_ADDR is not defined.'));
|
||||
'REMOTE_ADDR is not defined or not valid.'));
|
||||
}
|
||||
|
||||
return self::isClusterAddress($address);
|
||||
|
@ -852,6 +852,19 @@ final class PhabricatorEnv extends Phobject {
|
|||
->containsAddress($address);
|
||||
}
|
||||
|
||||
public static function getRemoteAddress() {
|
||||
$address = idx($_SERVER, 'REMOTE_ADDR');
|
||||
if (!$address) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
return PhutilIPAddress::newAddress($address);
|
||||
} catch (Exception $ex) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/* -( Internals )---------------------------------------------------------- */
|
||||
|
||||
|
||||
|
|
|
@ -95,7 +95,13 @@ abstract class PhabricatorSSHWorkflow extends PhabricatorManagementWorkflow {
|
|||
// This has the format "<ip> <remote-port> <local-port>". Grab the IP.
|
||||
$remote_address = head(explode(' ', $ssh_client));
|
||||
|
||||
return $remote_address;
|
||||
try {
|
||||
$address = PhutilIPAddress::newAddress($remote_address);
|
||||
} catch (Exception $ex) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $address->getAddress();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -317,20 +317,27 @@ final class PhabricatorMainMenuView extends AphrontView {
|
|||
|
||||
$logo_uri = $cache->getKey($cache_key_logo);
|
||||
if (!$logo_uri) {
|
||||
$file = id(new PhabricatorFileQuery())
|
||||
// NOTE: If the file policy has been changed to be restrictive, we'll
|
||||
// miss here and just show the default logo. The cache will fill later
|
||||
// when someone who can see the file loads the page. This might be a
|
||||
// little spooky, see T11982.
|
||||
$files = id(new PhabricatorFileQuery())
|
||||
->setViewer($this->getViewer())
|
||||
->withPHIDs(array($custom_header))
|
||||
->executeOne();
|
||||
->execute();
|
||||
$file = head($files);
|
||||
if ($file) {
|
||||
$logo_uri = $file->getViewURI();
|
||||
$cache->setKey($cache_key_logo, $logo_uri);
|
||||
}
|
||||
}
|
||||
|
||||
if ($logo_uri) {
|
||||
$logo_style[] = 'background-size: 40px 40px;';
|
||||
$logo_style[] = 'background-position: 0 0;';
|
||||
$logo_style[] = 'background-image: url('.$logo_uri.')';
|
||||
}
|
||||
}
|
||||
|
||||
$logo_node = phutil_tag(
|
||||
'span',
|
||||
|
|
|
@ -395,6 +395,11 @@ final class PhabricatorStartup {
|
|||
if (function_exists('libxml_disable_entity_loader')) {
|
||||
libxml_disable_entity_loader(true);
|
||||
}
|
||||
|
||||
// Enable automatic compression here. Webservers sometimes do this for
|
||||
// us, but we now detect the absence of compression and warn users about
|
||||
// it so try to cover our bases more thoroughly.
|
||||
ini_set('zlib.output_compression', 1);
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -87,8 +87,8 @@
|
|||
width: 14px;
|
||||
height: 14px;
|
||||
position: absolute;
|
||||
top: 6px;
|
||||
left: 9px;
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
|
|
|
@ -107,7 +107,7 @@ body.device .phui-box.phui-object-box.phui-comment-form-view {
|
|||
.phui-comment-form-view .phui-comment-action-bar {
|
||||
border-bottom: 1px solid {$thinblueborder};
|
||||
background-color: rgba(239, 243, 252, .75);
|
||||
padding: 2px 12px 4px 12px;
|
||||
padding: 4px 12px 4px 12px;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
|
||||
|
|
|
@ -154,10 +154,6 @@
|
|||
height: 24em;
|
||||
}
|
||||
|
||||
.aphront-form-control-select .aphront-form-input {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
.phui-form-view .aphront-form-caption {
|
||||
font-size: {$smallerfontsize};
|
||||
color: {$bluetext};
|
||||
|
@ -198,12 +194,14 @@
|
|||
margin: .5em 2em;
|
||||
}
|
||||
|
||||
.aphront-form-control-static .aphront-form-input,
|
||||
.aphront-form-control-markup .aphront-form-input {
|
||||
padding-top: 6px;
|
||||
font-size: {$normalfontsize};
|
||||
}
|
||||
|
||||
.aphront-form-control-static .aphront-form-input {
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.aphront-form-control-togglebuttons .aphront-form-input {
|
||||
padding: 2px 0 0 0;
|
||||
}
|
||||
|
|
|
@ -159,11 +159,6 @@
|
|||
padding: 0 12px;
|
||||
}
|
||||
|
||||
.phui-two-column-properties .phabricator-action-view-icon {
|
||||
top: 8px;
|
||||
left: 8px;
|
||||
}
|
||||
|
||||
.phabricator-action-view button.phabricator-action-view-item,
|
||||
.phabricator-action-view-item {
|
||||
padding: 5px 4px 5px 28px;
|
||||
|
|
|
@ -83,10 +83,6 @@ only screen and (min-resolution: 1.5dppx) {
|
|||
background-position: 0px -87px;
|
||||
}
|
||||
|
||||
.login-Persona {
|
||||
background-position: -29px -87px;
|
||||
}
|
||||
|
||||
.login-Phabricator {
|
||||
background-position: -58px -87px;
|
||||
}
|
||||
|
|
|
@ -1,41 +0,0 @@
|
|||
/**
|
||||
* @provides javelin-behavior-persona-login
|
||||
* @requires javelin-behavior
|
||||
* javelin-resource
|
||||
* javelin-stratcom
|
||||
* javelin-workflow
|
||||
* javelin-util
|
||||
*/
|
||||
|
||||
JX.behavior('persona-login', function(config) {
|
||||
|
||||
JX.Stratcom.listen(
|
||||
'submit',
|
||||
'persona-login-form',
|
||||
function(e) {
|
||||
e.kill();
|
||||
navigator.id.request();
|
||||
});
|
||||
|
||||
var onloaded = function() {
|
||||
// Before installing watch(), log the user out, because we know they don't
|
||||
// have a valid session if they're hitting this page. If we don't do this,
|
||||
// Persona may immediately trigger a login event, which prevents the user
|
||||
// from selecting another authentication mechanism.
|
||||
navigator.id.logout();
|
||||
|
||||
navigator.id.watch({
|
||||
loggedInUser: null,
|
||||
onlogin: onlogin,
|
||||
onlogout: JX.bag
|
||||
});
|
||||
};
|
||||
|
||||
var onlogin = function(assertion) {
|
||||
new JX.Workflow(config.loginURI, {assertion: assertion})
|
||||
.start();
|
||||
};
|
||||
|
||||
var persona_library = 'https://login.persona.org/include.js';
|
||||
JX.Resource.load(persona_library, onloaded);
|
||||
});
|
Loading…
Reference in a new issue