1
0
Fork 0
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:
epriestley 2016-12-09 16:56:15 -08:00
commit 3c511adb6f
64 changed files with 2013 additions and 412 deletions

View file

@ -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

View file

@ -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",

View file

@ -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',

View file

@ -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() {

View file

@ -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);
}
}

View file

@ -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;
}
}

View file

@ -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();

View file

@ -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';
}
}

View file

@ -26,10 +26,6 @@ final class PhabricatorBadgesApplication extends PhabricatorApplication {
return self::GROUP_UTILITIES;
}
public function canUninstall() {
return true;
}
public function isPrototype() {
return true;
}

View file

@ -437,10 +437,19 @@ abstract class PhabricatorApplication
if (!self::isClassInstalled($class)) {
$result = false;
} else {
$result = PhabricatorPolicyFilter::hasCapability(
$viewer,
self::getByClass($class),
PhabricatorPolicyCapability::CAN_VIEW);
$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);

View 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));
}
}

View file

@ -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 )--------------------------------------------- */

View 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);
}
}

View file

@ -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() {

View file

@ -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) {

View file

@ -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();
$valid_token = self::initializeNewToken(
$user->getPHID(),
self::TYPE_CLUSTER);
$valid_token->save();
unset($unguarded);
}
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
$token = self::initializeNewToken(
$user->getPHID(),
self::TYPE_CLUSTER);
$token->save();
unset($unguarded);
$user->attachConduitClusterToken($valid_token);
return $token;
return $valid_token;
}
public static function initializeNewToken($object_phid, $token_type) {

View 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);
}
}
}

View file

@ -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';

View file

@ -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(

View file

@ -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);

View file

@ -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,15 +401,18 @@ final class DiffusionBrowseController extends DiffusionController {
$view = id(new PHUITwoColumnView())
->setHeader($header)
->setCurtain($curtain)
->setMainColumn(array(
$empty_result,
$browse_panel,
))
->setFooter(array(
->setMainColumn(
array(
$branch_panel,
$empty_result,
$browse_panel,
))
->setFooter(
array(
$open_revisions,
$readme,
$pager_box,
));
));
if ($details) {
$view->addPropertySection(pht('Details'), $details);
@ -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);
}
}

View file

@ -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,
);
}
}

View file

@ -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,41 +352,63 @@ abstract class DiffusionController extends PhabricatorController {
$drequest = $this->getDiffusionRequest();
$viewer = $this->getViewer();
$repository = $drequest->getRepository();
$repository_phid = $repository->getPHID();
$stable_commit = $drequest->getStableCommit();
try {
$result = $this->callConduitWithDiffusionRequest(
'diffusion.filecontentquery',
array(
'path' => $readme_path,
'commit' => $drequest->getStableCommit(),
));
} catch (Exception $ex) {
return null;
$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',
array(
'path' => $readme_path,
'commit' => $drequest->getStableCommit(),
));
} catch (Exception $ex) {
return null;
}
$file_phid = $result['filePHID'];
if (!$file_phid) {
return null;
}
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($file_phid))
->executeOne();
if (!$file) {
return null;
}
$corpus = $file->loadFileData();
$readme_cache = array(
'corpus' => $corpus,
);
$cache->setKey($cache_key, $readme_cache);
}
$file_phid = $result['filePHID'];
if (!$file_phid) {
return null;
}
$file = id(new PhabricatorFileQuery())
->setViewer($viewer)
->withPHIDs(array($file_phid))
->executeOne();
if (!$file) {
return null;
}
$corpus = $file->loadFileData();
if (!strlen($corpus)) {
$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);
}
}

View file

@ -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
return null;
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;
}
}

View file

@ -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);

View file

@ -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);
}
}
}

View file

@ -209,6 +209,8 @@ abstract class DiffusionCommandEngine extends Phobject {
}
}
$env += $repository->getPassthroughEnvironmentalVariables();
return $env;
}

View file

@ -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() {

View file

@ -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())

View file

@ -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(

View file

@ -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);
}
}
}

View file

@ -23,10 +23,6 @@ final class PhabricatorNuanceApplication extends PhabricatorApplication {
return false;
}
public function canUninstall() {
return true;
}
public function getBaseURI() {
return '/nuance/';
}

View file

@ -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)

View file

@ -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,38 +491,28 @@ 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.
}
} else {
// This is an ad-hoc setting with no controlling object.
return $this->writeUserSettingCache($key, $value);
}
return $this->writeUserSettingCache($key, $value);
}
if (isset($defaults[$key])) {
$value = id(clone $defaults[$key])
->setViewer($this)
->getSettingDefaultValue();
$cache = PhabricatorCaches::getRuntimeCache();
$cache_key = "settings.defaults({$key})";
$cache_map = $cache->getKeys(array($cache_key));
if ($cache_map) {
$value = $cache_map[$cache_key];
} else {
$value = null;
$defaults = PhabricatorSetting::getAllSettings();
if (isset($defaults[$key])) {
$value = id(clone $defaults[$key])
->setViewer($this)
->getSettingDefaultValue();
} else {
$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)) {

View file

@ -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));
}

View file

@ -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() {

View file

@ -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();

View file

@ -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');
}

View file

@ -30,10 +30,6 @@ final class PhabricatorPhragmentApplication extends PhabricatorApplication {
return true;
}
public function canUninstall() {
return true;
}
public function getRoutes() {
return array(
'/phragment/' => array(

View file

@ -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())

View file

@ -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;
}

View file

@ -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 )---------------------------------------------------- */

View file

@ -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,
);
}
}

View file

@ -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);
}

View file

@ -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();
}
$viewer = PhabricatorUser::getOmnipotentUser();
$applications = id(new PhabricatorApplicationQuery())
->setViewer($viewer)

View file

@ -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;
}

View 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);
}
}
}

View file

@ -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;
}
}

View file

@ -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

View file

@ -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

View file

@ -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) {

View file

@ -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:

View file

@ -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 )---------------------------------------------------------- */

View file

@ -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();
}
}

View file

@ -317,19 +317,26 @@ 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);
}
}
$logo_style[] = 'background-size: 40px 40px;';
$logo_style[] = 'background-position: 0 0;';
$logo_style[] = 'background-image: url('.$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(

View file

@ -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);
}

View file

@ -87,8 +87,8 @@
width: 14px;
height: 14px;
position: absolute;
top: 6px;
left: 9px;
top: 8px;
left: 8px;
text-align: center;
}

View file

@ -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;
}

View file

@ -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;
}

View file

@ -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;

View file

@ -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;
}

View file

@ -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);
});