1
0
Fork 0
mirror of https://we.phorge.it/source/phorge.git synced 2025-02-23 20:19:03 +01:00

(stable) Promote 2021 Week 13

This commit is contained in:
epriestley 2021-03-26 11:04:48 -07:00
commit da8a579600
104 changed files with 1696 additions and 787 deletions

View file

@ -12,8 +12,8 @@ return array(
'core.pkg.css' => '0ae696de', 'core.pkg.css' => '0ae696de',
'core.pkg.js' => 'ab3502fe', 'core.pkg.js' => 'ab3502fe',
'dark-console.pkg.js' => '187792c2', 'dark-console.pkg.js' => '187792c2',
'differential.pkg.css' => '5c459f92', 'differential.pkg.css' => 'ffb69e3d',
'differential.pkg.js' => '5080baf4', 'differential.pkg.js' => '5986f349',
'diffusion.pkg.css' => '42c75c37', 'diffusion.pkg.css' => '42c75c37',
'diffusion.pkg.js' => '78c9885d', 'diffusion.pkg.js' => '78c9885d',
'maniphest.pkg.css' => '35995d6d', 'maniphest.pkg.css' => '35995d6d',
@ -67,7 +67,7 @@ return array(
'rsrc/css/application/differential/core.css' => '7300a73e', 'rsrc/css/application/differential/core.css' => '7300a73e',
'rsrc/css/application/differential/phui-inline-comment.css' => '9863a85e', 'rsrc/css/application/differential/phui-inline-comment.css' => '9863a85e',
'rsrc/css/application/differential/revision-comment.css' => '7dbc8d1d', 'rsrc/css/application/differential/revision-comment.css' => '7dbc8d1d',
'rsrc/css/application/differential/revision-history.css' => '8aa3eac5', 'rsrc/css/application/differential/revision-history.css' => '237a2979',
'rsrc/css/application/differential/revision-list.css' => '93d2df7d', 'rsrc/css/application/differential/revision-list.css' => '93d2df7d',
'rsrc/css/application/differential/table-of-contents.css' => 'bba788b9', 'rsrc/css/application/differential/table-of-contents.css' => 'bba788b9',
'rsrc/css/application/diffusion/diffusion-icons.css' => '23b31a1b', 'rsrc/css/application/diffusion/diffusion-icons.css' => '23b31a1b',
@ -437,7 +437,7 @@ return array(
'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68', 'rsrc/js/application/releeph/releeph-preview-branch.js' => '75184d68',
'rsrc/js/application/releeph/releeph-request-state-change.js' => '9f081f05', 'rsrc/js/application/releeph/releeph-request-state-change.js' => '9f081f05',
'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'aa3a100c', 'rsrc/js/application/releeph/releeph-request-typeahead.js' => 'aa3a100c',
'rsrc/js/application/repository/repository-crossreference.js' => '6337cf26', 'rsrc/js/application/repository/repository-crossreference.js' => '44d48cd1',
'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e5bdb730', 'rsrc/js/application/search/behavior-reorder-profile-menu-items.js' => 'e5bdb730',
'rsrc/js/application/search/behavior-reorder-queries.js' => 'b86f297f', 'rsrc/js/application/search/behavior-reorder-queries.js' => 'b86f297f',
'rsrc/js/application/transactions/behavior-comment-actions.js' => '4dffaeb2', 'rsrc/js/application/transactions/behavior-comment-actions.js' => '4dffaeb2',
@ -569,7 +569,7 @@ return array(
'differential-core-view-css' => '7300a73e', 'differential-core-view-css' => '7300a73e',
'differential-revision-add-comment-css' => '7e5900d9', 'differential-revision-add-comment-css' => '7e5900d9',
'differential-revision-comment-css' => '7dbc8d1d', 'differential-revision-comment-css' => '7dbc8d1d',
'differential-revision-history-css' => '8aa3eac5', 'differential-revision-history-css' => '237a2979',
'differential-revision-list-css' => '93d2df7d', 'differential-revision-list-css' => '93d2df7d',
'differential-table-of-contents-css' => 'bba788b9', 'differential-table-of-contents-css' => 'bba788b9',
'diffusion-css' => 'e46232d6', 'diffusion-css' => 'e46232d6',
@ -693,7 +693,7 @@ return array(
'javelin-behavior-reorder-applications' => 'aa371860', 'javelin-behavior-reorder-applications' => 'aa371860',
'javelin-behavior-reorder-columns' => '8ac32fd9', 'javelin-behavior-reorder-columns' => '8ac32fd9',
'javelin-behavior-reorder-profile-menu-items' => 'e5bdb730', 'javelin-behavior-reorder-profile-menu-items' => 'e5bdb730',
'javelin-behavior-repository-crossreference' => '6337cf26', 'javelin-behavior-repository-crossreference' => '44d48cd1',
'javelin-behavior-scrollbar' => '92388bae', 'javelin-behavior-scrollbar' => '92388bae',
'javelin-behavior-search-reorder-queries' => 'b86f297f', 'javelin-behavior-search-reorder-queries' => 'b86f297f',
'javelin-behavior-select-content' => 'e8240b50', 'javelin-behavior-select-content' => 'e8240b50',
@ -1309,6 +1309,12 @@ return array(
'43bc9360' => array( '43bc9360' => array(
'javelin-install', 'javelin-install',
), ),
'44d48cd1' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-uri',
),
'457f4d16' => array( '457f4d16' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-stratcom', 'javelin-stratcom',
@ -1536,12 +1542,6 @@ return array(
'javelin-request', 'javelin-request',
'javelin-uri', 'javelin-uri',
), ),
'6337cf26' => array(
'javelin-behavior',
'javelin-dom',
'javelin-stratcom',
'javelin-uri',
),
'65bb0011' => array( '65bb0011' => array(
'javelin-behavior', 'javelin-behavior',
'javelin-dom', 'javelin-dom',

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_differential.differential_affectedpath
DROP epoch;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_differential.differential_affectedpath
CHANGE repositoryID repositoryID INT UNSIGNED;

View file

@ -0,0 +1,28 @@
<?php
$device_table = new AlmanacDevice();
$device_conn = $device_table->establishConnection('w');
$properties_table = new PhabricatorMetaMTAMailProperties();
$conn = $properties_table->establishConnection('w');
$iterator = new LiskRawMigrationIterator(
$device_conn,
$device_table->getTableName());
foreach ($iterator as $row) {
queryfx(
$conn,
'INSERT IGNORE INTO %R
(objectPHID, mailProperties, dateCreated, dateModified)
VALUES
(%s, %s, %d, %d)',
$properties_table,
$row['phid'],
phutil_json_encode(
array(
'mailKey' => $row['mailKey'],
)),
PhabricatorTime::getNow(),
PhabricatorTime::getNow());
}

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_almanac.almanac_device
DROP mailKey;

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_almanac.almanac_device
ADD status VARCHAR(32) NOT NULL COLLATE {$COLLATE_TEXT};

View file

@ -0,0 +1,2 @@
UPDATE {$NAMESPACE}_almanac.almanac_device
SET status = 'active' WHERE status = '';

View file

@ -0,0 +1,28 @@
<?php
$service_table = new AlmanacService();
$service_conn = $service_table->establishConnection('w');
$properties_table = new PhabricatorMetaMTAMailProperties();
$conn = $properties_table->establishConnection('w');
$iterator = new LiskRawMigrationIterator(
$service_conn,
$service_table->getTableName());
foreach ($iterator as $row) {
queryfx(
$conn,
'INSERT IGNORE INTO %R
(objectPHID, mailProperties, dateCreated, dateModified)
VALUES
(%s, %s, %d, %d)',
$properties_table,
$row['phid'],
phutil_json_encode(
array(
'mailKey' => $row['mailKey'],
)),
PhabricatorTime::getNow(),
PhabricatorTime::getNow());
}

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_almanac.almanac_service
DROP mailKey;

View file

@ -0,0 +1,28 @@
<?php
$binding_table = new AlmanacBinding();
$binding_conn = $binding_table->establishConnection('w');
$properties_table = new PhabricatorMetaMTAMailProperties();
$conn = $properties_table->establishConnection('w');
$iterator = new LiskRawMigrationIterator(
$binding_conn,
$binding_table->getTableName());
foreach ($iterator as $row) {
queryfx(
$conn,
'INSERT IGNORE INTO %R
(objectPHID, mailProperties, dateCreated, dateModified)
VALUES
(%s, %s, %d, %d)',
$properties_table,
$row['phid'],
phutil_json_encode(
array(
'mailKey' => $row['mailKey'],
)),
PhabricatorTime::getNow(),
PhabricatorTime::getNow());
}

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_almanac.almanac_binding
DROP mailKey;

View file

@ -0,0 +1,28 @@
<?php
$namespace_table = new AlmanacNamespace();
$namespace_conn = $namespace_table->establishConnection('w');
$properties_table = new PhabricatorMetaMTAMailProperties();
$conn = $properties_table->establishConnection('w');
$iterator = new LiskRawMigrationIterator(
$namespace_conn,
$namespace_table->getTableName());
foreach ($iterator as $row) {
queryfx(
$conn,
'INSERT IGNORE INTO %R
(objectPHID, mailProperties, dateCreated, dateModified)
VALUES
(%s, %s, %d, %d)',
$properties_table,
$row['phid'],
phutil_json_encode(
array(
'mailKey' => $row['mailKey'],
)),
PhabricatorTime::getNow(),
PhabricatorTime::getNow());
}

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_almanac.almanac_namespace
DROP mailKey;

View file

@ -0,0 +1,28 @@
<?php
$network_table = new AlmanacNetwork();
$network_conn = $network_table->establishConnection('w');
$properties_table = new PhabricatorMetaMTAMailProperties();
$conn = $properties_table->establishConnection('w');
$iterator = new LiskRawMigrationIterator(
$network_conn,
$network_table->getTableName());
foreach ($iterator as $row) {
queryfx(
$conn,
'INSERT IGNORE INTO %R
(objectPHID, mailProperties, dateCreated, dateModified)
VALUES
(%s, %s, %d, %d)',
$properties_table,
$row['phid'],
phutil_json_encode(
array(
'mailKey' => $row['mailKey'],
)),
PhabricatorTime::getNow(),
PhabricatorTime::getNow());
}

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_almanac.almanac_network
DROP mailKey;

View file

@ -0,0 +1,28 @@
<?php
$event_table = new PhabricatorCalendarEvent();
$event_conn = $event_table->establishConnection('w');
$properties_table = new PhabricatorMetaMTAMailProperties();
$conn = $properties_table->establishConnection('w');
$iterator = new LiskRawMigrationIterator(
$event_conn,
$event_table->getTableName());
foreach ($iterator as $row) {
queryfx(
$conn,
'INSERT IGNORE INTO %R
(objectPHID, mailProperties, dateCreated, dateModified)
VALUES
(%s, %s, %d, %d)',
$properties_table,
$row['phid'],
phutil_json_encode(
array(
'mailKey' => $row['mailKey'],
)),
PhabricatorTime::getNow(),
PhabricatorTime::getNow());
}

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_calendar.calendar_event
DROP mailKey;

View file

@ -0,0 +1,28 @@
<?php
$initiative_table = new FundInitiative();
$initiative_conn = $initiative_table->establishConnection('w');
$properties_table = new PhabricatorMetaMTAMailProperties();
$conn = $properties_table->establishConnection('w');
$iterator = new LiskRawMigrationIterator(
$initiative_conn,
$initiative_table->getTableName());
foreach ($iterator as $row) {
queryfx(
$conn,
'INSERT IGNORE INTO %R
(objectPHID, mailProperties, dateCreated, dateModified)
VALUES
(%s, %s, %d, %d)',
$properties_table,
$row['phid'],
phutil_json_encode(
array(
'mailKey' => $row['mailKey'],
)),
PhabricatorTime::getNow(),
PhabricatorTime::getNow());
}

View file

@ -0,0 +1,2 @@
ALTER TABLE {$NAMESPACE}_fund.fund_initiative
DROP mailKey;

View file

@ -1,38 +0,0 @@
<?php
/**
* NOTE: This is an ADVANCED feature that improves performance but adds a lot
* of complexity! This is only suitable for production servers because workers
* won't pick up changes between when they spawn and when they handle a request.
*
* Phabricator spends a significant portion of its runtime loading classes
* and functions, even with APC enabled. Since we have very rigidly-defined
* rules about what can go in a module (specifically: no side effects), it
* is safe to load all the libraries *before* we receive a request.
*
* Normally, SAPIs don't provide a way to do this, but with a patched PHP-FPM
* SAPI you can provide a warmup file that it will execute before a request
* is received.
*
* We're limited in what we can do here, since request information won't
* exist yet, but we can load class and function definitions, which is what
* we're really interested in.
*
* Once this file exists, the FCGI process will drop into its normal accept loop
* and eventually process a request.
*/
function __warmup__() {
$root = dirname(dirname(dirname(dirname(__FILE__))));
require_once $root.'/libphutil/src/__phutil_library_init__.php';
require_once $root.'/arcanist/src/__phutil_library_init__.php';
require_once $root.'/phabricator/src/__phutil_library_init__.php';
// Load every symbol. We could possibly refine this -- we don't need to load
// every Controller, for instance.
$loader = new PhutilSymbolLoader();
$loader->selectAndLoadSymbols();
define('__WARMUP__', true);
}
__warmup__();

View file

@ -9,15 +9,12 @@ set -x
# to work without modifications. # to work without modifications.
# NOTE: This script assumes you are running it from a directory which contains # NOTE: This script assumes you are running it from a directory which contains
# arcanist/, libphutil/, and phabricator/. # arcanist/ and phabricator/.
ROOT=`pwd` # You can hard-code the path here instead. ROOT=`pwd` # You can hard-code the path here instead.
### UPDATE WORKING COPIES ###################################################### ### UPDATE WORKING COPIES ######################################################
cd $ROOT/libphutil
git pull
cd $ROOT/arcanist cd $ROOT/arcanist
git pull git pull

View file

@ -146,6 +146,14 @@ try {
$device_name)); $device_name));
} }
if ($device->isDisabled()) {
throw new Exception(
pht(
'This request has authenticated as a device ("%s"), but this '.
'device is disabled.',
$device->getName()));
}
// We're authenticated as a device, but we're going to read the user out of // We're authenticated as a device, but we're going to read the user out of
// the command below. // the command below.
$is_cluster_request = true; $is_cluster_request = true;

View file

@ -62,6 +62,8 @@ phutil_register_library_map(array(
'AlmanacDeviceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacDeviceSearchConduitAPIMethod.php', 'AlmanacDeviceSearchConduitAPIMethod' => 'applications/almanac/conduit/AlmanacDeviceSearchConduitAPIMethod.php',
'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php', 'AlmanacDeviceSearchEngine' => 'applications/almanac/query/AlmanacDeviceSearchEngine.php',
'AlmanacDeviceSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacDeviceSetPropertyTransaction.php', 'AlmanacDeviceSetPropertyTransaction' => 'applications/almanac/xaction/AlmanacDeviceSetPropertyTransaction.php',
'AlmanacDeviceStatus' => 'applications/almanac/constants/AlmanacDeviceStatus.php',
'AlmanacDeviceStatusTransaction' => 'applications/almanac/xaction/AlmanacDeviceStatusTransaction.php',
'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php', 'AlmanacDeviceTransaction' => 'applications/almanac/storage/AlmanacDeviceTransaction.php',
'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php', 'AlmanacDeviceTransactionQuery' => 'applications/almanac/query/AlmanacDeviceTransactionQuery.php',
'AlmanacDeviceTransactionType' => 'applications/almanac/xaction/AlmanacDeviceTransactionType.php', 'AlmanacDeviceTransactionType' => 'applications/almanac/xaction/AlmanacDeviceTransactionType.php',
@ -450,6 +452,7 @@ phutil_register_library_map(array(
'DifferentialActionEmailCommand' => 'applications/differential/command/DifferentialActionEmailCommand.php', 'DifferentialActionEmailCommand' => 'applications/differential/command/DifferentialActionEmailCommand.php',
'DifferentialAdjustmentMapTestCase' => 'applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php', 'DifferentialAdjustmentMapTestCase' => 'applications/differential/storage/__tests__/DifferentialAdjustmentMapTestCase.php',
'DifferentialAffectedPath' => 'applications/differential/storage/DifferentialAffectedPath.php', 'DifferentialAffectedPath' => 'applications/differential/storage/DifferentialAffectedPath.php',
'DifferentialAffectedPathEngine' => 'applications/differential/engine/DifferentialAffectedPathEngine.php',
'DifferentialAsanaRepresentationField' => 'applications/differential/customfield/DifferentialAsanaRepresentationField.php', 'DifferentialAsanaRepresentationField' => 'applications/differential/customfield/DifferentialAsanaRepresentationField.php',
'DifferentialAuditorsCommitMessageField' => 'applications/differential/field/DifferentialAuditorsCommitMessageField.php', 'DifferentialAuditorsCommitMessageField' => 'applications/differential/field/DifferentialAuditorsCommitMessageField.php',
'DifferentialAuditorsField' => 'applications/differential/customfield/DifferentialAuditorsField.php', 'DifferentialAuditorsField' => 'applications/differential/customfield/DifferentialAuditorsField.php',
@ -493,6 +496,7 @@ phutil_register_library_map(array(
'DifferentialCommitsSearchEngineAttachment' => 'applications/differential/engineextension/DifferentialCommitsSearchEngineAttachment.php', 'DifferentialCommitsSearchEngineAttachment' => 'applications/differential/engineextension/DifferentialCommitsSearchEngineAttachment.php',
'DifferentialConduitAPIMethod' => 'applications/differential/conduit/DifferentialConduitAPIMethod.php', 'DifferentialConduitAPIMethod' => 'applications/differential/conduit/DifferentialConduitAPIMethod.php',
'DifferentialConflictsCommitMessageField' => 'applications/differential/field/DifferentialConflictsCommitMessageField.php', 'DifferentialConflictsCommitMessageField' => 'applications/differential/field/DifferentialConflictsCommitMessageField.php',
'DifferentialConstantsModule' => 'applications/differential/constants/DifferentialConstantsModule.php',
'DifferentialController' => 'applications/differential/controller/DifferentialController.php', 'DifferentialController' => 'applications/differential/controller/DifferentialController.php',
'DifferentialCoreCustomField' => 'applications/differential/customfield/DifferentialCoreCustomField.php', 'DifferentialCoreCustomField' => 'applications/differential/customfield/DifferentialCoreCustomField.php',
'DifferentialCreateCommentConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php', 'DifferentialCreateCommentConduitAPIMethod' => 'applications/differential/conduit/DifferentialCreateCommentConduitAPIMethod.php',
@ -613,6 +617,7 @@ phutil_register_library_map(array(
'DifferentialRevisionAcceptTransaction' => 'applications/differential/xaction/DifferentialRevisionAcceptTransaction.php', 'DifferentialRevisionAcceptTransaction' => 'applications/differential/xaction/DifferentialRevisionAcceptTransaction.php',
'DifferentialRevisionActionTransaction' => 'applications/differential/xaction/DifferentialRevisionActionTransaction.php', 'DifferentialRevisionActionTransaction' => 'applications/differential/xaction/DifferentialRevisionActionTransaction.php',
'DifferentialRevisionAffectedFilesHeraldField' => 'applications/differential/herald/DifferentialRevisionAffectedFilesHeraldField.php', 'DifferentialRevisionAffectedFilesHeraldField' => 'applications/differential/herald/DifferentialRevisionAffectedFilesHeraldField.php',
'DifferentialRevisionAffectedPathsController' => 'applications/differential/controller/DifferentialRevisionAffectedPathsController.php',
'DifferentialRevisionAuthorHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorHeraldField.php', 'DifferentialRevisionAuthorHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorHeraldField.php',
'DifferentialRevisionAuthorPackagesHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorPackagesHeraldField.php', 'DifferentialRevisionAuthorPackagesHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorPackagesHeraldField.php',
'DifferentialRevisionAuthorProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php', 'DifferentialRevisionAuthorProjectsHeraldField' => 'applications/differential/herald/DifferentialRevisionAuthorProjectsHeraldField.php',
@ -2021,6 +2026,7 @@ phutil_register_library_map(array(
'PHUICalendarMonthView' => 'view/phui/calendar/PHUICalendarMonthView.php', 'PHUICalendarMonthView' => 'view/phui/calendar/PHUICalendarMonthView.php',
'PHUICalendarWeekView' => 'view/phui/calendar/PHUICalendarWeekView.php', 'PHUICalendarWeekView' => 'view/phui/calendar/PHUICalendarWeekView.php',
'PHUICalendarWidgetView' => 'view/phui/calendar/PHUICalendarWidgetView.php', 'PHUICalendarWidgetView' => 'view/phui/calendar/PHUICalendarWidgetView.php',
'PHUIColor' => 'view/phui/PHUIColor.php',
'PHUIColorPalletteExample' => 'applications/uiexample/examples/PHUIColorPalletteExample.php', 'PHUIColorPalletteExample' => 'applications/uiexample/examples/PHUIColorPalletteExample.php',
'PHUICrumbView' => 'view/phui/PHUICrumbView.php', 'PHUICrumbView' => 'view/phui/PHUICrumbView.php',
'PHUICrumbsView' => 'view/phui/PHUICrumbsView.php', 'PHUICrumbsView' => 'view/phui/PHUICrumbsView.php',
@ -6087,6 +6093,8 @@ phutil_register_library_map(array(
'AlmanacDeviceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod', 'AlmanacDeviceSearchConduitAPIMethod' => 'PhabricatorSearchEngineAPIMethod',
'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine', 'AlmanacDeviceSearchEngine' => 'PhabricatorApplicationSearchEngine',
'AlmanacDeviceSetPropertyTransaction' => 'AlmanacDeviceTransactionType', 'AlmanacDeviceSetPropertyTransaction' => 'AlmanacDeviceTransactionType',
'AlmanacDeviceStatus' => 'Phobject',
'AlmanacDeviceStatusTransaction' => 'AlmanacDeviceTransactionType',
'AlmanacDeviceTransaction' => 'AlmanacModularTransaction', 'AlmanacDeviceTransaction' => 'AlmanacModularTransaction',
'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery', 'AlmanacDeviceTransactionQuery' => 'PhabricatorApplicationTransactionQuery',
'AlmanacDeviceTransactionType' => 'AlmanacTransactionType', 'AlmanacDeviceTransactionType' => 'AlmanacTransactionType',
@ -6532,6 +6540,7 @@ phutil_register_library_map(array(
'DifferentialActionEmailCommand' => 'MetaMTAEmailTransactionCommand', 'DifferentialActionEmailCommand' => 'MetaMTAEmailTransactionCommand',
'DifferentialAdjustmentMapTestCase' => 'PhabricatorTestCase', 'DifferentialAdjustmentMapTestCase' => 'PhabricatorTestCase',
'DifferentialAffectedPath' => 'DifferentialDAO', 'DifferentialAffectedPath' => 'DifferentialDAO',
'DifferentialAffectedPathEngine' => 'Phobject',
'DifferentialAsanaRepresentationField' => 'DifferentialCustomField', 'DifferentialAsanaRepresentationField' => 'DifferentialCustomField',
'DifferentialAuditorsCommitMessageField' => 'DifferentialCommitMessageCustomField', 'DifferentialAuditorsCommitMessageField' => 'DifferentialCommitMessageCustomField',
'DifferentialAuditorsField' => 'DifferentialStoredCustomField', 'DifferentialAuditorsField' => 'DifferentialStoredCustomField',
@ -6580,6 +6589,7 @@ phutil_register_library_map(array(
'DifferentialCommitsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment', 'DifferentialCommitsSearchEngineAttachment' => 'PhabricatorSearchEngineAttachment',
'DifferentialConduitAPIMethod' => 'ConduitAPIMethod', 'DifferentialConduitAPIMethod' => 'ConduitAPIMethod',
'DifferentialConflictsCommitMessageField' => 'DifferentialCommitMessageField', 'DifferentialConflictsCommitMessageField' => 'DifferentialCommitMessageField',
'DifferentialConstantsModule' => 'PhabricatorConfigModule',
'DifferentialController' => 'PhabricatorController', 'DifferentialController' => 'PhabricatorController',
'DifferentialCoreCustomField' => 'DifferentialCustomField', 'DifferentialCoreCustomField' => 'DifferentialCustomField',
'DifferentialCreateCommentConduitAPIMethod' => 'DifferentialConduitAPIMethod', 'DifferentialCreateCommentConduitAPIMethod' => 'DifferentialConduitAPIMethod',
@ -6733,6 +6743,7 @@ phutil_register_library_map(array(
'DifferentialRevisionAcceptTransaction' => 'DifferentialRevisionReviewTransaction', 'DifferentialRevisionAcceptTransaction' => 'DifferentialRevisionReviewTransaction',
'DifferentialRevisionActionTransaction' => 'DifferentialRevisionTransactionType', 'DifferentialRevisionActionTransaction' => 'DifferentialRevisionTransactionType',
'DifferentialRevisionAffectedFilesHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionAffectedFilesHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionAffectedPathsController' => 'DifferentialController',
'DifferentialRevisionAuthorHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionAuthorHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionAuthorPackagesHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionAuthorPackagesHeraldField' => 'DifferentialRevisionHeraldField',
'DifferentialRevisionAuthorProjectsHeraldField' => 'DifferentialRevisionHeraldField', 'DifferentialRevisionAuthorProjectsHeraldField' => 'DifferentialRevisionHeraldField',
@ -8348,6 +8359,7 @@ phutil_register_library_map(array(
'PHUICalendarMonthView' => 'AphrontView', 'PHUICalendarMonthView' => 'AphrontView',
'PHUICalendarWeekView' => 'AphrontView', 'PHUICalendarWeekView' => 'AphrontView',
'PHUICalendarWidgetView' => 'AphrontTagView', 'PHUICalendarWidgetView' => 'AphrontTagView',
'PHUIColor' => 'Phobject',
'PHUIColorPalletteExample' => 'PhabricatorUIExample', 'PHUIColorPalletteExample' => 'PhabricatorUIExample',
'PHUICrumbView' => 'AphrontView', 'PHUICrumbView' => 'AphrontView',
'PHUICrumbsView' => 'AphrontView', 'PHUICrumbsView' => 'AphrontView',

View file

@ -0,0 +1,110 @@
<?php
final class AlmanacDeviceStatus
extends Phobject {
const ACTIVE = 'active';
const DISABLED = 'disabled';
private $value;
public static function newStatusFromValue($value) {
$status = new self();
$status->value = $value;
return $status;
}
public function getValue() {
return $this->value;
}
public function getName() {
$name = $this->getDeviceStatusProperty('name');
if ($name === null) {
$name = pht('Unknown Almanac Device Status ("%s")', $this->getValue());
}
return $name;
}
public function getIconIcon() {
return $this->getDeviceStatusProperty('icon.icon');
}
public function getIconColor() {
return $this->getDeviceStatusProperty('icon.color');
}
public function isDisabled() {
return ($this->getValue() === self::DISABLED);
}
public function hasStatusTag() {
return ($this->getStatusTagIcon() !== null);
}
public function getStatusTagIcon() {
return $this->getDeviceStatusProperty('status-tag.icon');
}
public function getStatusTagColor() {
return $this->getDeviceStatusProperty('status-tag.color');
}
public static function getStatusMap() {
$result = array();
foreach (self::newDeviceStatusMap() as $status_value => $ignored) {
$result[$status_value] = self::newStatusFromValue($status_value);
}
return $result;
}
public static function getActiveStatusList() {
$results = array();
foreach (self::newDeviceStatusMap() as $status_value => $status) {
if (empty($status['disabled'])) {
$results[] = $status_value;
}
}
return $results;
}
public static function getDisabledStatusList() {
$results = array();
foreach (self::newDeviceStatusMap() as $status_value => $status) {
if (!empty($status['disabled'])) {
$results[] = $status_value;
}
}
return $results;
}
private function getDeviceStatusProperty($key, $default = null) {
$map = self::newDeviceStatusMap();
$properties = idx($map, $this->getValue(), array());
return idx($properties, $key, $default);
}
private static function newDeviceStatusMap() {
return array(
self::ACTIVE => array(
'name' => pht('Active'),
'icon.icon' => 'fa-server',
'icon.color' => 'green',
),
self::DISABLED => array(
'name' => pht('Disabled'),
'icon.icon' => 'fa-times',
'icon.color' => 'grey',
'status-tag.icon' => 'fa-times',
'status-tag.color' => 'indigo',
'disabled' => true,
),
);
}
}

View file

@ -31,6 +31,14 @@ final class AlmanacDeviceViewController
->setPolicyObject($device) ->setPolicyObject($device)
->setHeaderIcon('fa-server'); ->setHeaderIcon('fa-server');
$status = $device->getStatusObject();
if ($status->hasStatusTag()) {
$header->setStatus(
$status->getStatusTagIcon(),
$status->getStatusTagColor(),
$status->getName());
}
$issue = null; $issue = null;
if ($device->isClusterDevice()) { if ($device->isClusterDevice()) {
$issue = $this->addClusterMessage( $issue = $this->addClusterMessage(

View file

@ -76,6 +76,8 @@ final class AlmanacDeviceEditEngine
} }
protected function buildCustomEditFields($object) { protected function buildCustomEditFields($object) {
$status_map = $this->getDeviceStatusMap($object);
return array( return array(
id(new PhabricatorTextEditField()) id(new PhabricatorTextEditField())
->setKey('name') ->setKey('name')
@ -84,7 +86,32 @@ final class AlmanacDeviceEditEngine
->setTransactionType(AlmanacDeviceNameTransaction::TRANSACTIONTYPE) ->setTransactionType(AlmanacDeviceNameTransaction::TRANSACTIONTYPE)
->setIsRequired(true) ->setIsRequired(true)
->setValue($object->getName()), ->setValue($object->getName()),
id(new PhabricatorSelectEditField())
->setKey('status')
->setLabel(pht('Status'))
->setDescription(pht('Device status.'))
->setTransactionType(AlmanacDeviceStatusTransaction::TRANSACTIONTYPE)
->setOptions($status_map)
->setValue($object->getStatus()),
); );
} }
private function getDeviceStatusMap(AlmanacDevice $device) {
$status_map = AlmanacDeviceStatus::getStatusMap();
// If the device currently has an unknown status, add it to the list for
// the dropdown.
$status_value = $device->getStatus();
if (!isset($status_map[$status_value])) {
$status_map = array(
$status_value => AlmanacDeviceStatus::newStatusFromValue($status_value),
) + $status_map;
}
$status_map = mpull($status_map, 'getName');
return $status_map;
}
} }

View file

@ -3,6 +3,17 @@
final class AlmanacBindingsSearchEngineAttachment final class AlmanacBindingsSearchEngineAttachment
extends AlmanacSearchEngineAttachment { extends AlmanacSearchEngineAttachment {
private $isActive;
public function setIsActive($is_active) {
$this->isActive = $is_active;
return $this;
}
public function getIsActive() {
return $this->isActive;
}
public function getAttachmentName() { public function getAttachmentName() {
return pht('Almanac Bindings'); return pht('Almanac Bindings');
} }
@ -13,12 +24,24 @@ final class AlmanacBindingsSearchEngineAttachment
public function willLoadAttachmentData($query, $spec) { public function willLoadAttachmentData($query, $spec) {
$query->needProperties(true); $query->needProperties(true);
$query->needBindings(true);
if ($this->getIsActive()) {
$query->needBindings(true);
} else {
$query->needActiveBindings(true);
}
} }
public function getAttachmentForObject($object, $data, $spec) { public function getAttachmentForObject($object, $data, $spec) {
$bindings = array(); $bindings = array();
foreach ($object->getBindings() as $binding) {
if ($this->getIsActive()) {
$service_bindings = $object->getActiveBindings();
} else {
$service_bindings = $object->getBindings();
}
foreach ($service_bindings as $binding) {
$bindings[] = $this->getAlmanacBindingDictionary($binding); $bindings[] = $this->getAlmanacBindingDictionary($binding);
} }

View file

@ -51,6 +51,8 @@ abstract class AlmanacSearchEngineAttachment
'phid' => $device->getPHID(), 'phid' => $device->getPHID(),
'name' => $device->getName(), 'name' => $device->getName(),
'properties' => $this->getAlmanacPropertyList($device), 'properties' => $this->getAlmanacPropertyList($device),
'status' => $device->getStatus(),
'disabled' => $device->isDisabled(),
); );
} }

View file

@ -34,7 +34,8 @@ final class AlmanacInterfacePHIDType extends PhabricatorPHIDType {
$id = $interface->getID(); $id = $interface->getID();
$device_name = $interface->getDevice()->getName(); $device = $interface->getDevice();
$device_name = $device->getName();
$address = $interface->getAddress(); $address = $interface->getAddress();
$port = $interface->getPort(); $port = $interface->getPort();
$network = $interface->getNetwork()->getName(); $network = $interface->getNetwork()->getName();
@ -48,6 +49,10 @@ final class AlmanacInterfacePHIDType extends PhabricatorPHIDType {
$handle->setObjectName(pht('Interface %d', $id)); $handle->setObjectName(pht('Interface %d', $id));
$handle->setName($name); $handle->setName($name);
if ($device->isDisabled()) {
$handle->setStatus(PhabricatorObjectHandle::STATUS_CLOSED);
}
} }
} }

View file

@ -8,6 +8,7 @@ final class AlmanacBindingQuery
private $servicePHIDs; private $servicePHIDs;
private $devicePHIDs; private $devicePHIDs;
private $interfacePHIDs; private $interfacePHIDs;
private $isActive;
public function withIDs(array $ids) { public function withIDs(array $ids) {
$this->ids = $ids; $this->ids = $ids;
@ -34,6 +35,11 @@ final class AlmanacBindingQuery
return $this; return $this;
} }
public function withIsActive($active) {
$this->isActive = $active;
return $this;
}
public function newResultObject() { public function newResultObject() {
return new AlmanacBinding(); return new AlmanacBinding();
} }
@ -95,39 +101,79 @@ final class AlmanacBindingQuery
if ($this->ids !== null) { if ($this->ids !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'id IN (%Ld)', 'binding.id IN (%Ld)',
$this->ids); $this->ids);
} }
if ($this->phids !== null) { if ($this->phids !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'phid IN (%Ls)', 'binding.phid IN (%Ls)',
$this->phids); $this->phids);
} }
if ($this->servicePHIDs !== null) { if ($this->servicePHIDs !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'servicePHID IN (%Ls)', 'binding.servicePHID IN (%Ls)',
$this->servicePHIDs); $this->servicePHIDs);
} }
if ($this->devicePHIDs !== null) { if ($this->devicePHIDs !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'devicePHID IN (%Ls)', 'binding.devicePHID IN (%Ls)',
$this->devicePHIDs); $this->devicePHIDs);
} }
if ($this->interfacePHIDs !== null) { if ($this->interfacePHIDs !== null) {
$where[] = qsprintf( $where[] = qsprintf(
$conn, $conn,
'interfacePHID IN (%Ls)', 'binding.interfacePHID IN (%Ls)',
$this->interfacePHIDs); $this->interfacePHIDs);
} }
if ($this->isActive !== null) {
if ($this->isActive) {
$where[] = qsprintf(
$conn,
'(binding.isDisabled = 0) AND (device.status IN (%Ls))',
AlmanacDeviceStatus::getActiveStatusList());
} else {
$where[] = qsprintf(
$conn,
'(binding.isDisabled = 1) OR (device.status IN (%Ls))',
AlmanacDeviceStatus::getDisabledStatusList());
}
}
return $where; return $where;
} }
protected function buildJoinClauseParts(AphrontDatabaseConnection $conn) {
$joins = parent::buildJoinClauseParts($conn);
if ($this->shouldJoinDeviceTable()) {
$device_table = new AlmanacDevice();
$joins[] = qsprintf(
$conn,
'JOIN %R device ON binding.devicePHID = device.phid',
$device_table);
}
return $joins;
}
private function shouldJoinDeviceTable() {
if ($this->isActive !== null) {
return true;
}
return false;
}
protected function getPrimaryTableAlias() {
return 'binding';
}
} }

View file

@ -9,6 +9,7 @@ final class AlmanacDeviceQuery
private $namePrefix; private $namePrefix;
private $nameSuffix; private $nameSuffix;
private $isClusterDevice; private $isClusterDevice;
private $statuses;
public function withIDs(array $ids) { public function withIDs(array $ids) {
$this->ids = $ids; $this->ids = $ids;
@ -35,6 +36,11 @@ final class AlmanacDeviceQuery
return $this; return $this;
} }
public function withStatuses(array $statuses) {
$this->statuses = $statuses;
return $this;
}
public function withNameNgrams($ngrams) { public function withNameNgrams($ngrams) {
return $this->withNgramsConstraint( return $this->withNgramsConstraint(
new AlmanacDeviceNameNgrams(), new AlmanacDeviceNameNgrams(),
@ -103,6 +109,13 @@ final class AlmanacDeviceQuery
(int)$this->isClusterDevice); (int)$this->isClusterDevice);
} }
if ($this->statuses !== null) {
$where[] = qsprintf(
$conn,
'device.status IN (%Ls)',
$this->statuses);
}
return $where; return $where;
} }

View file

@ -16,6 +16,9 @@ final class AlmanacDeviceSearchEngine
} }
protected function buildCustomSearchFields() { protected function buildCustomSearchFields() {
$status_options = AlmanacDeviceStatus::getStatusMap();
$status_options = mpull($status_options, 'getName');
return array( return array(
id(new PhabricatorSearchTextField()) id(new PhabricatorSearchTextField())
->setLabel(pht('Name Contains')) ->setLabel(pht('Name Contains'))
@ -25,6 +28,11 @@ final class AlmanacDeviceSearchEngine
->setLabel(pht('Exact Names')) ->setLabel(pht('Exact Names'))
->setKey('names') ->setKey('names')
->setDescription(pht('Search for devices with specific names.')), ->setDescription(pht('Search for devices with specific names.')),
id(new PhabricatorSearchCheckboxesField())
->setLabel(pht('Statuses'))
->setKey('statuses')
->setDescription(pht('Search for devices with given statuses.'))
->setOptions($status_options),
id(new PhabricatorSearchThreeStateField()) id(new PhabricatorSearchThreeStateField())
->setLabel(pht('Cluster Device')) ->setLabel(pht('Cluster Device'))
->setKey('isClusterDevice') ->setKey('isClusterDevice')
@ -50,6 +58,10 @@ final class AlmanacDeviceSearchEngine
$query->withIsClusterDevice($map['isClusterDevice']); $query->withIsClusterDevice($map['isClusterDevice']);
} }
if ($map['statuses']) {
$query->withStatuses($map['statuses']);
}
return $query; return $query;
} }
@ -59,6 +71,7 @@ final class AlmanacDeviceSearchEngine
protected function getBuiltinQueryNames() { protected function getBuiltinQueryNames() {
$names = array( $names = array(
'active' => pht('Active Devices'),
'all' => pht('All Devices'), 'all' => pht('All Devices'),
); );
@ -66,11 +79,13 @@ final class AlmanacDeviceSearchEngine
} }
public function buildSavedQueryFromBuiltin($query_key) { public function buildSavedQueryFromBuiltin($query_key) {
$query = $this->newSavedQuery(); $query = $this->newSavedQuery();
$query->setQueryKey($query_key); $query->setQueryKey($query_key);
switch ($query_key) { switch ($query_key) {
case 'active':
$active_statuses = AlmanacDeviceStatus::getActiveStatusList();
return $query->setParameter('statuses', $active_statuses);
case 'all': case 'all':
return $query; return $query;
} }
@ -99,6 +114,19 @@ final class AlmanacDeviceSearchEngine
$item->addIcon('fa-sitemap', pht('Cluster Device')); $item->addIcon('fa-sitemap', pht('Cluster Device'));
} }
if ($device->isDisabled()) {
$item->setDisabled(true);
}
$status = $device->getStatusObject();
$icon_icon = $status->getIconIcon();
$icon_color = $status->getIconColor();
$icon_label = $status->getName();
$item->setStatusIcon(
"{$icon_icon} {$icon_color}",
$icon_label);
$list->addItem($item); $list->addItem($item);
} }

View file

@ -12,6 +12,7 @@ final class AlmanacServiceQuery
private $nameSuffix; private $nameSuffix;
private $needBindings; private $needBindings;
private $needActiveBindings;
public function withIDs(array $ids) { public function withIDs(array $ids) {
$this->ids = $ids; $this->ids = $ids;
@ -59,6 +60,11 @@ final class AlmanacServiceQuery
return $this; return $this;
} }
public function needActiveBindings($need_active) {
$this->needActiveBindings = $need_active;
return $this;
}
public function newResultObject() { public function newResultObject() {
return new AlmanacService(); return new AlmanacService();
} }
@ -160,18 +166,35 @@ final class AlmanacServiceQuery
} }
protected function didFilterPage(array $services) { protected function didFilterPage(array $services) {
if ($this->needBindings) { $need_all = $this->needBindings;
$need_active = $this->needActiveBindings;
$need_any = ($need_all || $need_active);
$only_active = ($need_active && !$need_all);
if ($need_any) {
$service_phids = mpull($services, 'getPHID'); $service_phids = mpull($services, 'getPHID');
$bindings = id(new AlmanacBindingQuery())
$bindings_query = id(new AlmanacBindingQuery())
->setViewer($this->getViewer()) ->setViewer($this->getViewer())
->withServicePHIDs($service_phids) ->withServicePHIDs($service_phids)
->needProperties($this->getNeedProperties()) ->needProperties($this->getNeedProperties());
->execute();
if ($only_active) {
$bindings_query->withIsActive(true);
}
$bindings = $bindings_query->execute();
$bindings = mgroup($bindings, 'getServicePHID'); $bindings = mgroup($bindings, 'getServicePHID');
foreach ($services as $service) { foreach ($services as $service) {
$service_bindings = idx($bindings, $service->getPHID(), array()); $service_bindings = idx($bindings, $service->getPHID(), array());
$service->attachBindings($service_bindings);
if ($only_active) {
$service->attachActiveBindings($service_bindings);
} else {
$service->attachBindings($service_bindings);
}
} }
} }

View file

@ -13,7 +13,6 @@ final class AlmanacBinding
protected $servicePHID; protected $servicePHID;
protected $devicePHID; protected $devicePHID;
protected $interfacePHID; protected $interfacePHID;
protected $mailKey;
protected $isDisabled; protected $isDisabled;
private $service = self::ATTACHABLE; private $service = self::ATTACHABLE;
@ -33,7 +32,6 @@ final class AlmanacBinding
return array( return array(
self::CONFIG_AUX_PHID => true, self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'mailKey' => 'bytes20',
'isDisabled' => 'bool', 'isDisabled' => 'bool',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
@ -51,15 +49,8 @@ final class AlmanacBinding
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }
public function generatePHID() { public function getPHIDType() {
return PhabricatorPHID::generateNewPHID(AlmanacBindingPHIDType::TYPECONST); return AlmanacBindingPHIDType::TYPECONST;
}
public function save() {
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
return parent::save();
} }
public function getName() { public function getName() {
@ -67,7 +58,9 @@ final class AlmanacBinding
} }
public function getURI() { public function getURI() {
return '/almanac/binding/'.$this->getID().'/'; return urisprintf(
'/almanac/binding/%s/',
$this->getID());
} }
public function getService() { public function getService() {

View file

@ -15,9 +15,9 @@ final class AlmanacDevice
protected $name; protected $name;
protected $nameIndex; protected $nameIndex;
protected $mailKey;
protected $viewPolicy; protected $viewPolicy;
protected $editPolicy; protected $editPolicy;
protected $status;
protected $isBoundToClusterService; protected $isBoundToClusterService;
private $almanacProperties = self::ATTACHABLE; private $almanacProperties = self::ATTACHABLE;
@ -26,6 +26,7 @@ final class AlmanacDevice
return id(new AlmanacDevice()) return id(new AlmanacDevice())
->setViewPolicy(PhabricatorPolicies::POLICY_USER) ->setViewPolicy(PhabricatorPolicies::POLICY_USER)
->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN) ->setEditPolicy(PhabricatorPolicies::POLICY_ADMIN)
->setStatus(AlmanacDeviceStatus::ACTIVE)
->attachAlmanacProperties(array()) ->attachAlmanacProperties(array())
->setIsBoundToClusterService(0); ->setIsBoundToClusterService(0);
} }
@ -36,7 +37,7 @@ final class AlmanacDevice
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text128', 'name' => 'text128',
'nameIndex' => 'bytes12', 'nameIndex' => 'bytes12',
'mailKey' => 'bytes20', 'status' => 'text32',
'isBoundToClusterService' => 'bool', 'isBoundToClusterService' => 'bool',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
@ -51,8 +52,8 @@ final class AlmanacDevice
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }
public function generatePHID() { public function getPHIDType() {
return PhabricatorPHID::generateNewPHID(AlmanacDevicePHIDType::TYPECONST); return AlmanacDevicePHIDType::TYPECONST;
} }
public function save() { public function save() {
@ -60,15 +61,13 @@ final class AlmanacDevice
$this->nameIndex = PhabricatorHash::digestForIndex($this->getName()); $this->nameIndex = PhabricatorHash::digestForIndex($this->getName());
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
return parent::save(); return parent::save();
} }
public function getURI() { public function getURI() {
return '/almanac/device/view/'.$this->getName().'/'; return urisprintf(
'/almanac/device/view/%s/',
$this->getName());
} }
public function rebuildClusterBindingStatus() { public function rebuildClusterBindingStatus() {
@ -90,8 +89,8 @@ final class AlmanacDevice
$unguarded = AphrontWriteGuard::beginScopedUnguardedWrites(); $unguarded = AphrontWriteGuard::beginScopedUnguardedWrites();
queryfx( queryfx(
$this->establishConnection('w'), $this->establishConnection('w'),
'UPDATE %T SET isBoundToClusterService = %d WHERE id = %d', 'UPDATE %R SET isBoundToClusterService = %d WHERE id = %d',
$this->getTableName(), $this,
$this->getIsBoundToClusterService(), $this->getIsBoundToClusterService(),
$this->getID()); $this->getID());
unset($unguarded); unset($unguarded);
@ -104,6 +103,18 @@ final class AlmanacDevice
return $this->getIsBoundToClusterService(); return $this->getIsBoundToClusterService();
} }
public function getStatusObject() {
return $this->newStatusObject();
}
private function newStatusObject() {
return AlmanacDeviceStatus::newStatusFromValue($this->getStatus());
}
public function isDisabled() {
return $this->getStatusObject()->isDisabled();
}
/* -( AlmanacPropertyInterface )------------------------------------------- */ /* -( AlmanacPropertyInterface )------------------------------------------- */
@ -267,12 +278,27 @@ final class AlmanacDevice
->setKey('name') ->setKey('name')
->setType('string') ->setType('string')
->setDescription(pht('The name of the device.')), ->setDescription(pht('The name of the device.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('status')
->setType('map<string, wild>')
->setDescription(pht('Device status information.')),
id(new PhabricatorConduitSearchFieldSpecification())
->setKey('disabled')
->setType('bool')
->setDescription(pht('True if device is disabled.')),
); );
} }
public function getFieldValuesForConduit() { public function getFieldValuesForConduit() {
$status = $this->getStatusObject();
return array( return array(
'name' => $this->getName(), 'name' => $this->getName(),
'status' => array(
'value' => $status->getValue(),
'name' => $status->getName(),
),
'disabled' => $this->isDisabled(),
); );
} }

View file

@ -12,7 +12,6 @@ final class AlmanacNamespace
protected $name; protected $name;
protected $nameIndex; protected $nameIndex;
protected $mailKey;
protected $viewPolicy; protected $viewPolicy;
protected $editPolicy; protected $editPolicy;
@ -28,7 +27,6 @@ final class AlmanacNamespace
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text128', 'name' => 'text128',
'nameIndex' => 'bytes12', 'nameIndex' => 'bytes12',
'mailKey' => 'bytes20',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_nameindex' => array( 'key_nameindex' => array(
@ -42,9 +40,8 @@ final class AlmanacNamespace
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }
public function generatePHID() { public function getPHIDType() {
return PhabricatorPHID::generateNewPHID( return AlmanacNamespacePHIDType::TYPECONST;
AlmanacNamespacePHIDType::TYPECONST);
} }
public function save() { public function save() {
@ -52,15 +49,13 @@ final class AlmanacNamespace
$this->nameIndex = PhabricatorHash::digestForIndex($this->getName()); $this->nameIndex = PhabricatorHash::digestForIndex($this->getName());
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
return parent::save(); return parent::save();
} }
public function getURI() { public function getURI() {
return '/almanac/namespace/view/'.$this->getName().'/'; return urisprintf(
'/almanac/namespace/view/%s/',
$this->getName());
} }
public function getNameLength() { public function getNameLength() {

View file

@ -10,7 +10,6 @@ final class AlmanacNetwork
PhabricatorConduitResultInterface { PhabricatorConduitResultInterface {
protected $name; protected $name;
protected $mailKey;
protected $viewPolicy; protected $viewPolicy;
protected $editPolicy; protected $editPolicy;
@ -25,8 +24,6 @@ final class AlmanacNetwork
self::CONFIG_AUX_PHID => true, self::CONFIG_AUX_PHID => true,
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'sort128', 'name' => 'sort128',
'mailKey' => 'bytes20',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'key_name' => array( 'key_name' => array(
@ -37,20 +34,14 @@ final class AlmanacNetwork
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }
public function generatePHID() { public function getPHIDType() {
return PhabricatorPHID::generateNewPHID(AlmanacNetworkPHIDType::TYPECONST); return AlmanacNetworkPHIDType::TYPECONST;
}
public function save() {
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
return parent::save();
} }
public function getURI() { public function getURI() {
return '/almanac/network/'.$this->getID().'/'; return urisprintf(
'/almanac/network/%s/',
$this->getID());
} }

View file

@ -14,13 +14,13 @@ final class AlmanacService
protected $name; protected $name;
protected $nameIndex; protected $nameIndex;
protected $mailKey;
protected $viewPolicy; protected $viewPolicy;
protected $editPolicy; protected $editPolicy;
protected $serviceType; protected $serviceType;
private $almanacProperties = self::ATTACHABLE; private $almanacProperties = self::ATTACHABLE;
private $bindings = self::ATTACHABLE; private $bindings = self::ATTACHABLE;
private $activeBindings = self::ATTACHABLE;
private $serviceImplementation = self::ATTACHABLE; private $serviceImplementation = self::ATTACHABLE;
public static function initializeNewService($type) { public static function initializeNewService($type) {
@ -48,7 +48,6 @@ final class AlmanacService
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'name' => 'text128', 'name' => 'text128',
'nameIndex' => 'bytes12', 'nameIndex' => 'bytes12',
'mailKey' => 'bytes20',
'serviceType' => 'text64', 'serviceType' => 'text64',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
@ -66,8 +65,8 @@ final class AlmanacService
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }
public function generatePHID() { public function getPHIDType() {
return PhabricatorPHID::generateNewPHID(AlmanacServicePHIDType::TYPECONST); return AlmanacServicePHIDType::TYPECONST;
} }
public function save() { public function save() {
@ -75,10 +74,6 @@ final class AlmanacService
$this->nameIndex = PhabricatorHash::digestForIndex($this->getName()); $this->nameIndex = PhabricatorHash::digestForIndex($this->getName());
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
return parent::save(); return parent::save();
} }
@ -91,23 +86,36 @@ final class AlmanacService
} }
public function getActiveBindings() { public function getActiveBindings() {
$bindings = $this->getBindings(); return $this->assertAttached($this->activeBindings);
// Filter out disabled bindings.
foreach ($bindings as $key => $binding) {
if ($binding->getIsDisabled()) {
unset($bindings[$key]);
}
}
return $bindings;
} }
public function attachBindings(array $bindings) { public function attachBindings(array $bindings) {
$active_bindings = array();
foreach ($bindings as $key => $binding) {
// Filter out disabled bindings.
if ($binding->getIsDisabled()) {
continue;
}
// Filter out bindings to disabled devices.
if ($binding->getDevice()->isDisabled()) {
continue;
}
$active_bindings[$key] = $binding;
}
$this->attachActiveBindings($active_bindings);
$this->bindings = $bindings; $this->bindings = $bindings;
return $this; return $this;
} }
public function attachActiveBindings(array $bindings) {
$this->activeBindings = $bindings;
return $this;
}
public function getServiceImplementation() { public function getServiceImplementation() {
return $this->assertAttached($this->serviceImplementation); return $this->assertAttached($this->serviceImplementation);
} }
@ -289,6 +297,9 @@ final class AlmanacService
->setAttachmentKey('properties'), ->setAttachmentKey('properties'),
id(new AlmanacBindingsSearchEngineAttachment()) id(new AlmanacBindingsSearchEngineAttachment())
->setAttachmentKey('bindings'), ->setAttachmentKey('bindings'),
id(new AlmanacBindingsSearchEngineAttachment())
->setIsActive(true)
->setAttachmentKey('activeBindings'),
); );
} }

View file

@ -46,9 +46,16 @@ final class AlmanacInterfaceDatasource
$results = array(); $results = array();
foreach ($handles as $handle) { foreach ($handles as $handle) {
if ($handle->isClosed()) {
$closed = pht('Disabled');
} else {
$closed = null;
}
$results[] = id(new PhabricatorTypeaheadResult()) $results[] = id(new PhabricatorTypeaheadResult())
->setName($handle->getName()) ->setName($handle->getName())
->setPHID($handle->getPHID()); ->setPHID($handle->getPHID())
->setClosed($closed);
} }
return $results; return $results;

View file

@ -56,21 +56,41 @@ final class AlmanacBindingTableView extends AphrontView {
$icon_active = id(new PHUIIconView()) $icon_active = id(new PHUIIconView())
->setIcon('fa-check') ->setIcon('fa-check')
->setColor('green')
->addSigil('has-tooltip') ->addSigil('has-tooltip')
->setMetadata( ->setMetadata(
array( array(
'tip' => pht('Active'), 'tip' => pht('Active'),
)); ));
$icon_device_disabled = id(new PHUIIconView())
->setIcon('fa-times')
->setColor('grey')
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => pht('Device Disabled'),
));
$rows = array(); $rows = array();
foreach ($bindings as $binding) { foreach ($bindings as $binding) {
$addr = $binding->getInterface()->getAddress(); $addr = $binding->getInterface()->getAddress();
$port = $binding->getInterface()->getPort(); $port = $binding->getInterface()->getPort();
$device = $binding->getDevice();
if ($device->isDisabled()) {
$binding_icon = $icon_device_disabled;
} else if ($binding->getIsDisabled()) {
$binding_icon = $icon_disabled;
} else {
$binding_icon = $icon_active;
}
$rows[] = array( $rows[] = array(
$binding->getID(), $binding->getID(),
($binding->getIsDisabled() ? $icon_disabled : $icon_active), $binding_icon,
$handles->renderHandle($binding->getServicePHID()), $handles->renderHandle($binding->getServicePHID()),
$handles->renderHandle($binding->getDevicePHID()), $handles->renderHandle($binding->getDevicePHID()),
$handles->renderHandle($binding->getInterface()->getNetworkPHID()), $handles->renderHandle($binding->getInterface()->getNetworkPHID()),
$binding->getInterface()->renderDisplayAddress(), $binding->getInterface()->renderDisplayAddress(),

View file

@ -0,0 +1,61 @@
<?php
final class AlmanacDeviceStatusTransaction
extends AlmanacDeviceTransactionType {
const TRANSACTIONTYPE = 'almanac:device:status';
public function generateOldValue($object) {
return $object->getStatus();
}
public function applyInternalEffects($object, $value) {
$object->setStatus($value);
}
public function getTitle() {
$old_value = $this->getOldValue();
$new_value = $this->getNewValue();
$old_status = AlmanacDeviceStatus::newStatusFromValue($old_value);
$new_status = AlmanacDeviceStatus::newStatusFromValue($new_value);
$old_name = $old_status->getName();
$new_name = $new_status->getName();
return pht(
'%s changed the status of this device from %s to %s.',
$this->renderAuthor(),
$this->renderValue($old_name),
$this->renderValue($new_name));
}
public function validateTransactions($object, array $xactions) {
$errors = array();
$status_map = AlmanacDeviceStatus::getStatusMap();
$old_value = $this->generateOldValue($object);
foreach ($xactions as $xaction) {
$new_value = $xaction->getNewValue();
if ($new_value === $old_value) {
continue;
}
if (!isset($status_map[$new_value])) {
$errors[] = $this->newInvalidError(
pht(
'Almanac device status "%s" is unrecognized. Valid status '.
'values are: %s.',
$new_value,
implode(', ', array_keys($status_map))),
$xaction);
continue;
}
}
return $errors;
}
}

View file

@ -24,7 +24,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
protected $isCancelled; protected $isCancelled;
protected $isAllDay; protected $isAllDay;
protected $icon; protected $icon;
protected $mailKey;
protected $isStub; protected $isStub;
protected $isRecurring = 0; protected $isRecurring = 0;
@ -360,10 +359,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
} }
public function save() { public function save() {
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
$import_uid = $this->getImportUID(); $import_uid = $this->getImportUID();
if ($import_uid !== null) { if ($import_uid !== null) {
$index = PhabricatorHash::digestForIndex($import_uid); $index = PhabricatorHash::digestForIndex($import_uid);
@ -405,7 +400,6 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
'isCancelled' => 'bool', 'isCancelled' => 'bool',
'isAllDay' => 'bool', 'isAllDay' => 'bool',
'icon' => 'text32', 'icon' => 'text32',
'mailKey' => 'bytes20',
'isRecurring' => 'bool', 'isRecurring' => 'bool',
'seriesParentPHID' => 'phid?', 'seriesParentPHID' => 'phid?',
'instanceOfEventPHID' => 'phid?', 'instanceOfEventPHID' => 'phid?',
@ -442,9 +436,8 @@ final class PhabricatorCalendarEvent extends PhabricatorCalendarDAO
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }
public function generatePHID() { public function getPHIDType() {
return PhabricatorPHID::generateNewPHID( return PhabricatorCalendarEventPHIDType::TYPECONST;
PhabricatorCalendarEventPHIDType::TYPECONST);
} }
public function getMonogram() { public function getMonogram() {

View file

@ -238,6 +238,16 @@ final class PhabricatorConduitAPIController
if ($object instanceof PhabricatorUser) { if ($object instanceof PhabricatorUser) {
$user = $object; $user = $object;
} else { } else {
if ($object->isDisabled()) {
return array(
'ERR-INVALID-AUTH',
pht(
'The key which signed this request is associated with a '.
'disabled device ("%s").',
$object->getName()),
);
}
if (!$stored_key->getIsTrusted()) { if (!$stored_key->getIsTrusted()) {
return array( return array(
'ERR-INVALID-AUTH', 'ERR-INVALID-AUTH',

View file

@ -156,7 +156,7 @@ abstract class PhabricatorConduitController extends PhabricatorController {
$parts = array(); $parts = array();
$libphutil_path = 'path/to/libphutil/src/__phutil_library_init__.php'; $libphutil_path = 'path/to/arcanist/support/init/init-script.php';
$parts[] = '<?php'; $parts[] = '<?php';
$parts[] = "\n\n"; $parts[] = "\n\n";

View file

@ -38,7 +38,11 @@ final class PhabricatorConfigModuleController
$nav->selectFilter($key); $nav->selectFilter($key);
$header = $this->buildHeaderView($title); $header = $this->buildHeaderView($title);
$view = $this->buildConfigBoxView($title, $content); if ($content instanceof AphrontTableView) {
$view = $this->buildConfigBoxView($title, $content);
} else {
$view = $content;
}
$crumbs = $this->buildApplicationCrumbs() $crumbs = $this->buildApplicationCrumbs()
->addTextCrumb(pht('Extensions/Modules'), $modules_uri) ->addTextCrumb(pht('Extensions/Modules'), $modules_uri)

View file

@ -74,6 +74,8 @@ final class PhabricatorDifferentialApplication
=> 'DifferentialRevisionOperationController', => 'DifferentialRevisionOperationController',
'inlines/(?P<id>[1-9]\d*)/' 'inlines/(?P<id>[1-9]\d*)/'
=> 'DifferentialRevisionInlinesController', => 'DifferentialRevisionInlinesController',
'paths/(?P<id>[1-9]\d*)/'
=> 'DifferentialRevisionAffectedPathsController',
), ),
'comment/' => array( 'comment/' => array(
'inline/' => array( 'inline/' => array(

View file

@ -38,7 +38,7 @@ final class DifferentialQueryConduitAPIMethod
'authors' => 'optional list<phid>', 'authors' => 'optional list<phid>',
'ccs' => 'optional list<phid>', 'ccs' => 'optional list<phid>',
'reviewers' => 'optional list<phid>', 'reviewers' => 'optional list<phid>',
'paths' => 'optional list<pair<callsign, path>>', 'paths' => 'unsupported',
'commitHashes' => 'optional list<pair<'.$hash_const.', string>>', 'commitHashes' => 'optional list<pair<'.$hash_const.', string>>',
'status' => 'optional '.$status_const, 'status' => 'optional '.$status_const,
'order' => 'optional '.$order_const, 'order' => 'optional '.$order_const,
@ -92,48 +92,11 @@ final class DifferentialQueryConduitAPIMethod
} }
if ($path_pairs) { if ($path_pairs) {
$paths = array(); throw new Exception(
foreach ($path_pairs as $pair) { pht(
list($callsign, $path) = $pair; 'Parameter "paths" to Conduit API method "differential.query" is '.
$paths[] = $path; 'no longer supported. Use the "paths" constraint to '.
} '"differential.revision.search" instead. See T13639.'));
$path_map = id(new DiffusionPathIDQuery($paths))->loadPathIDs();
if (count($path_map) != count($paths)) {
$unknown_paths = array();
foreach ($paths as $p) {
if (!idx($path_map, $p)) {
$unknown_paths[] = $p;
}
}
throw id(new ConduitException('ERR-INVALID-PARAMETER'))
->setErrorDescription(
pht(
'Unknown paths: %s',
implode(', ', $unknown_paths)));
}
$repos = array();
foreach ($path_pairs as $pair) {
list($callsign, $path) = $pair;
if (!idx($repos, $callsign)) {
$repos[$callsign] = id(new PhabricatorRepositoryQuery())
->setViewer($request->getUser())
->withCallsigns(array($callsign))
->executeOne();
if (!$repos[$callsign]) {
throw id(new ConduitException('ERR-INVALID-PARAMETER'))
->setErrorDescription(
pht(
'Unknown repo callsign: %s',
$callsign));
}
}
$repo = $repos[$callsign];
$query->withPath($repo->getID(), idx($path_map, $path));
}
} }
if ($commit_hashes) { if ($commit_hashes) {

View file

@ -0,0 +1,180 @@
<?php
final class DifferentialConstantsModule
extends PhabricatorConfigModule {
public function getModuleKey() {
return 'constants.differential';
}
public function getModuleName() {
return pht('Constants: Differential');
}
public function renderModuleStatus(AphrontRequest $request) {
$viewer = $request->getViewer();
return array(
$this->renderRevisionStatuses($viewer),
$this->renderUnitStatuses($viewer),
$this->renderLintStatuses($viewer),
);
}
private function renderRevisionStatuses(PhabricatorUser $viewer) {
$statuses = DifferentialRevisionStatus::getAll();
$rows = array();
foreach ($statuses as $status) {
$icon = id(new PHUIIconView())
->setIcon(
$status->getIcon(),
$status->getIconColor());
$timeline_icon = $status->getTimelineIcon();
if ($timeline_icon !== null) {
$timeline_view = id(new PHUIIconView())
->setIcon(
$status->getTimelineIcon(),
$status->getTimelineColor());
} else {
$timeline_view = null;
}
if ($status->isClosedStatus()) {
$is_open = pht('Closed');
} else {
$is_open = pht('Open');
}
$tag_color = $status->getTagColor();
if ($tag_color !== null) {
$tag_view = id(new PHUIIconView())
->seticon('fa-tag', $tag_color);
} else {
$tag_view = null;
}
$ansi_color = $status->getAnsiColor();
if ($ansi_color !== null) {
$web_color = PHUIColor::getWebColorFromANSIColor($ansi_color);
$ansi_view = id(new PHUIIconView())
->setIcon('fa-stop', $web_color);
} else {
$ansi_view = null;
}
$rows[] = array(
$status->getKey(),
$status->getLegacyKey(),
$icon,
$timeline_view,
$tag_view,
$ansi_view,
$is_open,
$status->getDisplayName(),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Value'),
pht('Legacy Value'),
pht('Icon'),
pht('Timeline Icon'),
pht('Tag Color'),
pht('ANSI Color'),
pht('Open/Closed'),
pht('Name'),
))
->setColumnClasses(
array(
null,
null,
null,
null,
null,
null,
null,
'wide pri',
));
$view = id(new PHUIObjectBoxView())
->setHeaderText(pht('Differential Revision Statuses'))
->setTable($table);
return $view;
}
private function renderUnitStatuses(PhabricatorUser $viewer) {
$statuses = DifferentialUnitStatus::getStatusMap();
$rows = array();
foreach ($statuses as $status) {
$rows[] = array(
$status->getValue(),
id(new PHUIIconView())
->setIcon($status->getIconIcon(), $status->getIconColor()),
$status->getName(),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Value'),
pht('Icon'),
pht('Name'),
))
->setColumnClasses(
array(
null,
null,
'wide pri',
));
$view = id(new PHUIObjectBoxView())
->setHeaderText(pht('Differential Unit Statuses'))
->setTable($table);
return $view;
}
private function renderLintStatuses(PhabricatorUser $viewer) {
$statuses = DifferentialLintStatus::getStatusMap();
$rows = array();
foreach ($statuses as $status) {
$rows[] = array(
$status->getValue(),
id(new PHUIIconView())
->setIcon($status->getIconIcon(), $status->getIconColor()),
$status->getName(),
);
}
$table = id(new AphrontTableView($rows))
->setHeaders(
array(
pht('Value'),
pht('Icon'),
pht('Name'),
))
->setColumnClasses(
array(
null,
null,
'wide pri',
));
$view = id(new PHUIObjectBoxView())
->setHeaderText(pht('Differential Lint Statuses'))
->setTable($table);
return $view;
}
}

View file

@ -9,4 +9,85 @@ final class DifferentialLintStatus extends Phobject {
const LINT_SKIP = 4; const LINT_SKIP = 4;
const LINT_AUTO_SKIP = 6; const LINT_AUTO_SKIP = 6;
private $value;
public static function newStatusFromValue($value) {
$status = new self();
$status->value = $value;
return $status;
}
public function getValue() {
return $this->value;
}
public function getName() {
$name = $this->getLintStatusProperty('name');
if ($name === null) {
$name = pht('Unknown Lint Status ("%s")', $this->getValue());
}
return $name;
}
public function getIconIcon() {
return $this->getLintStatusProperty('icon.icon');
}
public function getIconColor() {
return $this->getLintStatusProperty('icon.color');
}
public static function getStatusMap() {
$results = array();
foreach (self::newLintStatusMap() as $value => $ignored) {
$results[$value] = self::newStatusFromValue($value);
}
return $results;
}
private function getLintStatusProperty($key, $default = null) {
$map = self::newLintStatusMap();
$properties = idx($map, $this->getValue(), array());
return idx($properties, $key, $default);
}
private static function newLintStatusMap() {
return array(
self::LINT_NONE => array(
'name' => pht('No Lint Coverage'),
'icon.icon' => 'fa-ban',
'icon.color' => 'grey',
),
self::LINT_OKAY => array(
'name' => pht('Lint Passed'),
'icon.icon' => 'fa-check',
'icon.color' => 'green',
),
self::LINT_WARN => array(
'name' => pht('Lint Warnings'),
'icon.icon' => 'fa-exclamation-triangle',
'icon.color' => 'yellow',
),
self::LINT_FAIL => array(
'name' => pht('Lint Errors'),
'icon.icon' => 'fa-times',
'icon.color' => 'red',
),
self::LINT_SKIP => array(
'name' => pht('Lint Skipped'),
'icon.icon' => 'fa-fast-forward',
'icon.color' => 'blue',
),
self::LINT_AUTO_SKIP => array(
'name' => pht('Lint Not Applicable'),
'icon.icon' => 'fa-code',
'icon.color' => 'grey',
),
);
}
} }

View file

@ -9,4 +9,85 @@ final class DifferentialUnitStatus extends Phobject {
const UNIT_SKIP = 4; const UNIT_SKIP = 4;
const UNIT_AUTO_SKIP = 6; const UNIT_AUTO_SKIP = 6;
private $value;
public static function newStatusFromValue($value) {
$status = new self();
$status->value = $value;
return $status;
}
public function getValue() {
return $this->value;
}
public function getName() {
$name = $this->getUnitStatusProperty('name');
if ($name === null) {
$name = pht('Unknown Unit Status ("%s")', $this->getValue());
}
return $name;
}
public function getIconIcon() {
return $this->getUnitStatusProperty('icon.icon');
}
public function getIconColor() {
return $this->getUnitStatusProperty('icon.color');
}
public static function getStatusMap() {
$results = array();
foreach (self::newUnitStatusMap() as $value => $ignored) {
$results[$value] = self::newStatusFromValue($value);
}
return $results;
}
private function getUnitStatusProperty($key, $default = null) {
$map = self::newUnitStatusMap();
$properties = idx($map, $this->getValue(), array());
return idx($properties, $key, $default);
}
private static function newUnitStatusMap() {
return array(
self::UNIT_NONE => array(
'name' => pht('No Test Coverage'),
'icon.icon' => 'fa-ban',
'icon.color' => 'grey',
),
self::UNIT_OKAY => array(
'name' => pht('Tests Passed'),
'icon.icon' => 'fa-check',
'icon.color' => 'green',
),
self::UNIT_WARN => array(
'name' => pht('Test Warnings'),
'icon.icon' => 'fa-exclamation-triangle',
'icon.color' => 'yellow',
),
self::UNIT_FAIL => array(
'name' => pht('Test Failures'),
'icon.icon' => 'fa-times',
'icon.color' => 'red',
),
self::UNIT_SKIP => array(
'name' => pht('Tests Skipped'),
'icon.icon' => 'fa-fast-forward',
'icon.color' => 'blue',
),
self::UNIT_AUTO_SKIP => array(
'name' => pht('Tests Not Applicable'),
'icon.icon' => 'fa-code',
'icon.color' => 'grey',
),
);
}
} }

View file

@ -27,7 +27,13 @@ final class DifferentialDiffCreateController extends DifferentialController {
$diff = null; $diff = null;
// This object is just for policy stuff // This object is just for policy stuff
$diff_object = DifferentialDiff::initializeNewDiff($viewer); $diff_object = DifferentialDiff::initializeNewDiff($viewer);
$repository_phid = null;
if ($revision) {
$repository_phid = $revision->getRepositoryPHID();
} else {
$repository_phid = null;
}
$errors = array(); $errors = array();
$e_diff = null; $e_diff = null;
$e_file = null; $e_file = null;

View file

@ -0,0 +1,127 @@
<?php
final class DifferentialRevisionAffectedPathsController
extends DifferentialController {
public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer();
$id = $request->getURIData('id');
$revision = id(new DifferentialRevisionQuery())
->withIDs(array($id))
->setViewer($viewer)
->executeOne();
if (!$revision) {
return new Aphront404Response();
}
$table = new DifferentialAffectedPath();
$conn = $table->establishConnection('r');
$paths = queryfx_all(
$conn,
'SELECT * FROM %R WHERE revisionID = %d',
$table,
$revision->getID());
$repository_ids = array();
$path_ids = array();
foreach ($paths as $path) {
$repository_id = $path['repositoryID'];
$path_id = $path['pathID'];
$repository_ids[] = $repository_id;
$path_ids[] = $path_id;
}
$repository_ids = array_fuse($repository_ids);
if ($repository_ids) {
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->withIDs($repository_ids)
->execute();
$repositories = mpull($repositories, null, 'getID');
} else {
$repositories = array();
}
$handles = $viewer->loadHandles(mpull($repositories, 'getPHID'));
$path_ids = array_fuse($path_ids);
if ($path_ids) {
$path_names = id(new DiffusionPathQuery())
->withPathIDs($path_ids)
->execute();
} else {
$path_names = array();
}
$rows = array();
foreach ($paths as $path) {
$repository_id = $path['repositoryID'];
$path_id = $path['pathID'];
$repository = idx($repositories, $repository_id);
if ($repository) {
$repository_phid = $repository->getPHID();
$repository_link = $handles[$repository_phid]->renderLink();
} else {
$repository_link = null;
}
$path_name = idx($path_names, $path_id);
if ($path_name !== null) {
$path_view = $path_name['path'];
} else {
$path_view = null;
}
$rows[] = array(
$repository_id,
$repository_link,
$path_id,
$path_view,
);
}
// Sort rows by path name.
$rows = isort($rows, 3);
$table_view = id(new AphrontTableView($rows))
->setNoDataString(pht('This revision has no indexed affected paths.'))
->setHeaders(
array(
pht('Repository ID'),
pht('Repository'),
pht('Path ID'),
pht('Path'),
))
->setColumnClasses(
array(
null,
null,
null,
'wide',
));
$box_view = id(new PHUIObjectBoxView())
->setHeaderText(pht('Affected Path Index'))
->setTable($table_view);
$crumbs = $this->buildApplicationCrumbs()
->addTextCrumb($revision->getMonogram(), $revision->getURI())
->addTextCrumb(pht('Affected Path Index'));
return $this->newPage()
->setCrumbs($crumbs)
->setTitle(
array(
$revision->getMonogram(),
pht('Affected Path Index'),
))
->appendChild($box_view);
}
}

View file

@ -986,6 +986,8 @@ final class DifferentialRevisionViewController
PhabricatorRepository $repository) { PhabricatorRepository $repository) {
assert_instances_of($changesets, 'DifferentialChangeset'); assert_instances_of($changesets, 'DifferentialChangeset');
$viewer = $this->getViewer();
$paths = array(); $paths = array();
foreach ($changesets as $changeset) { foreach ($changesets as $changeset) {
$paths[] = $changeset->getAbsoluteRepositoryPath( $paths[] = $changeset->getAbsoluteRepositoryPath(
@ -997,34 +999,30 @@ final class DifferentialRevisionViewController
return array(); return array();
} }
$path_map = id(new DiffusionPathIDQuery($paths))->loadPathIDs();
if (!$path_map) {
return array();
}
$recent = (PhabricatorTime::getNow() - phutil_units('30 days in seconds')); $recent = (PhabricatorTime::getNow() - phutil_units('30 days in seconds'));
$query = id(new DifferentialRevisionQuery()) $query = id(new DifferentialRevisionQuery())
->setViewer($this->getRequest()->getUser()) ->setViewer($viewer)
->withIsOpen(true) ->withIsOpen(true)
->withUpdatedEpochBetween($recent, null) ->withUpdatedEpochBetween($recent, null)
->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED) ->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED)
->setLimit(10) ->setLimit(10)
->needFlags(true) ->needFlags(true)
->needDrafts(true) ->needDrafts(true)
->needReviewers(true); ->needReviewers(true)
->withRepositoryPHIDs(
foreach ($path_map as $path => $path_id) { array(
$query->withPath($repository->getID(), $path_id); $repository->getPHID(),
} ))
->withPaths($paths);
$results = $query->execute(); $results = $query->execute();
// Strip out *this* revision. // Strip out *this* revision.
foreach ($results as $key => $result) { foreach ($results as $key => $result) {
if ($result->getID() == $this->revisionID) { if ($result->getID() == $this->revisionID) {
unset($results[$key]); unset($results[$key]);
break;
} }
} }

View file

@ -38,7 +38,6 @@ final class DifferentialLintField
protected function getDiffPropertyKeys() { protected function getDiffPropertyKeys() {
return array( return array(
'arc:lint', 'arc:lint',
'arc:lint-excuse',
); );
} }
@ -84,33 +83,18 @@ final class DifferentialLintField
DifferentialDiff $diff, DifferentialDiff $diff,
array $messages) { array $messages) {
$colors = array( $status_value = $diff->getLintStatus();
DifferentialLintStatus::LINT_NONE => 'grey', $status = DifferentialLintStatus::newStatusFromValue($status_value);
DifferentialLintStatus::LINT_OKAY => 'green',
DifferentialLintStatus::LINT_WARN => 'yellow',
DifferentialLintStatus::LINT_FAIL => 'red',
DifferentialLintStatus::LINT_SKIP => 'blue',
DifferentialLintStatus::LINT_AUTO_SKIP => 'blue',
);
$icon_color = idx($colors, $diff->getLintStatus(), 'grey');
$message = DifferentialRevisionUpdateHistoryView::getDiffLintMessage($diff); $status_icon = $status->getIconIcon();
$status_color = $status->getIconColor();
$excuse = $diff->getProperty('arc:lint-excuse'); $status_name = $status->getName();
if (strlen($excuse)) {
$excuse = array(
phutil_tag('strong', array(), pht('Excuse:')),
' ',
phutil_escape_html_newlines($excuse),
);
}
$status = id(new PHUIStatusListView()) $status = id(new PHUIStatusListView())
->addItem( ->addItem(
id(new PHUIStatusItemView()) id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_STAR, $icon_color) ->setIcon($status_icon, $status_color)
->setTarget($message) ->setTarget($status_name));
->setNote($excuse));
return $status; return $status;
} }

View file

@ -72,29 +72,20 @@ final class DifferentialUnitField
} }
public function renderDiffPropertyViewValue(DifferentialDiff $diff) { public function renderDiffPropertyViewValue(DifferentialDiff $diff) {
$status_value = $diff->getUnitStatus();
$status = DifferentialUnitStatus::newStatusFromValue($status_value);
$colors = array( $status_icon = $status->getIconIcon();
DifferentialUnitStatus::UNIT_NONE => 'grey', $status_color = $status->getIconColor();
DifferentialUnitStatus::UNIT_OKAY => 'green', $status_name = $status->getName();
DifferentialUnitStatus::UNIT_WARN => 'yellow',
DifferentialUnitStatus::UNIT_FAIL => 'red',
DifferentialUnitStatus::UNIT_SKIP => 'blue',
DifferentialUnitStatus::UNIT_AUTO_SKIP => 'blue',
);
$icon_color = idx($colors, $diff->getUnitStatus(), 'grey');
$message = DifferentialRevisionUpdateHistoryView::getDiffUnitMessage(
$diff->getUnitStatus());
$status = id(new PHUIStatusListView()) $status = id(new PHUIStatusListView())
->addItem( ->addItem(
id(new PHUIStatusItemView()) id(new PHUIStatusItemView())
->setIcon(PHUIStatusItemView::ICON_STAR, $icon_color) ->setIcon($status_icon, $status_color)
->setTarget($message)); ->setTarget($status_name));
return $status; return $status;
} }
} }

View file

@ -340,21 +340,57 @@ final class DifferentialTransactionEditor
pht('Failed to load revision from transaction finalization.')); pht('Failed to load revision from transaction finalization.'));
} }
$active_diff = $new_revision->getActiveDiff();
$new_diff_phid = $active_diff->getPHID();
$object->attachReviewers($new_revision->getReviewers()); $object->attachReviewers($new_revision->getReviewers());
$object->attachActiveDiff($new_revision->getActiveDiff()); $object->attachActiveDiff($active_diff);
$object->attachRepository($new_revision->getRepository()); $object->attachRepository($new_revision->getRepository());
$has_new_diff = false;
$should_index_paths = false;
$should_index_hashes = false;
$need_changesets = false;
foreach ($xactions as $xaction) { foreach ($xactions as $xaction) {
switch ($xaction->getTransactionType()) { switch ($xaction->getTransactionType()) {
case DifferentialRevisionUpdateTransaction::TRANSACTIONTYPE: case DifferentialRevisionUpdateTransaction::TRANSACTIONTYPE:
$diff = $this->requireDiff($xaction->getNewValue(), true); $need_changesets = true;
// Update these denormalized index tables when we attach a new $new_diff_phid = $xaction->getNewValue();
// diff to a revision. $has_new_diff = true;
$this->updateRevisionHashTable($object, $diff); $should_index_paths = true;
$this->updateAffectedPathTable($object, $diff); $should_index_hashes = true;
break; break;
case DifferentialRevisionRepositoryTransaction::TRANSACTIONTYPE:
// The "AffectedPath" table denormalizes the repository, so we
// want to update the index if the repository changes.
$need_changesets = true;
$should_index_paths = true;
break;
}
}
if ($need_changesets) {
$new_diff = $this->requireDiff($new_diff_phid, true);
if ($should_index_paths) {
id(new DifferentialAffectedPathEngine())
->setRevision($object)
->setDiff($new_diff)
->updateAffectedPaths();
}
if ($should_index_hashes) {
$this->updateRevisionHashTable($object, $new_diff);
}
if ($has_new_diff) {
$this->ownersDiff = $new_diff;
$this->ownersChangesets = $new_diff->getChangesets();
} }
} }
@ -1255,99 +1291,6 @@ final class DifferentialTransactionEditor
return $adapter; return $adapter;
} }
/**
* Update the table which links Differential revisions to paths they affect,
* so Diffusion can efficiently find pending revisions for a given file.
*/
private function updateAffectedPathTable(
DifferentialRevision $revision,
DifferentialDiff $diff) {
$repository = $revision->getRepository();
if (!$repository) {
// The repository where the code lives is untracked.
return;
}
$path_prefix = null;
$local_root = $diff->getSourceControlPath();
if ($local_root) {
// We're in a working copy which supports subdirectory checkouts (e.g.,
// SVN) so we need to figure out what prefix we should add to each path
// (e.g., trunk/projects/example/) to get the absolute path from the
// root of the repository. DVCS systems like Git and Mercurial are not
// affected.
// Normalize both paths and check if the repository root is a prefix of
// the local root. If so, throw it away. Note that this correctly handles
// the case where the remote path is "/".
$local_root = id(new PhutilURI($local_root))->getPath();
$local_root = rtrim($local_root, '/');
$repo_root = id(new PhutilURI($repository->getRemoteURI()))->getPath();
$repo_root = rtrim($repo_root, '/');
if (!strncmp($repo_root, $local_root, strlen($repo_root))) {
$path_prefix = substr($local_root, strlen($repo_root));
}
}
$changesets = $diff->getChangesets();
$paths = array();
foreach ($changesets as $changeset) {
$paths[] = $path_prefix.'/'.$changeset->getFilename();
}
// If this change affected paths, save the changesets so we can apply
// Owners rules to them later.
if ($paths) {
$this->ownersDiff = $diff;
$this->ownersChangesets = $changesets;
}
// Mark this as also touching all parent paths, so you can see all pending
// changes to any file within a directory.
$all_paths = array();
foreach ($paths as $local) {
foreach (DiffusionPathIDQuery::expandPathToRoot($local) as $path) {
$all_paths[$path] = true;
}
}
$all_paths = array_keys($all_paths);
$path_ids =
PhabricatorRepositoryCommitChangeParserWorker::lookupOrCreatePaths(
$all_paths);
$table = new DifferentialAffectedPath();
$conn_w = $table->establishConnection('w');
$sql = array();
foreach ($path_ids as $path_id) {
$sql[] = qsprintf(
$conn_w,
'(%d, %d, %d, %d)',
$repository->getID(),
$path_id,
time(),
$revision->getID());
}
queryfx(
$conn_w,
'DELETE FROM %T WHERE revisionID = %d',
$table->getTableName(),
$revision->getID());
foreach (array_chunk($sql, 256) as $chunk) {
queryfx(
$conn_w,
'INSERT INTO %T (repositoryID, pathID, epoch, revisionID) VALUES %LQ',
$table->getTableName(),
$chunk);
}
}
/** /**
* Update the table connecting revisions to DVCS local hashes, so we can * Update the table connecting revisions to DVCS local hashes, so we can
* identify revisions by commit/tree hashes. * identify revisions by commit/tree hashes.

View file

@ -0,0 +1,136 @@
<?php
final class DifferentialAffectedPathEngine
extends Phobject {
private $revision;
private $diff;
public function setRevision(DifferentialRevision $revision) {
$this->revision = $revision;
return $this;
}
public function getRevision() {
return $this->revision;
}
public function setDiff(DifferentialDiff $diff) {
$this->diff = $diff;
return $this;
}
public function getDiff() {
return $this->diff;
}
public function updateAffectedPaths() {
$revision = $this->getRevision();
$diff = $this->getDiff();
$repository = $revision->getRepository();
if ($repository) {
$repository_id = $repository->getID();
} else {
$repository_id = null;
}
$paths = $this->getAffectedPaths();
$path_ids =
PhabricatorRepositoryCommitChangeParserWorker::lookupOrCreatePaths(
$paths);
$table = new DifferentialAffectedPath();
$conn = $table->establishConnection('w');
$sql = array();
foreach ($path_ids as $path_id) {
$sql[] = qsprintf(
$conn,
'(%nd, %d, %d)',
$repository_id,
$path_id,
$revision->getID());
}
queryfx(
$conn,
'DELETE FROM %R WHERE revisionID = %d',
$table,
$revision->getID());
if ($sql) {
foreach (PhabricatorLiskDAO::chunkSQL($sql) as $chunk) {
queryfx(
$conn,
'INSERT INTO %R (repositoryID, pathID, revisionID) VALUES %LQ',
$table,
$chunk);
}
}
}
public function destroyAffectedPaths() {
$revision = $this->getRevision();
$table = new DifferentialAffectedPath();
$conn = $table->establishConnection('w');
queryfx(
$conn,
'DELETE FROM %R WHERE revisionID = %d',
$table,
$revision->getID());
}
public function getAffectedPaths() {
$revision = $this->getRevision();
$diff = $this->getDiff();
$repository = $revision->getRepository();
$path_prefix = null;
if ($repository) {
$local_root = $diff->getSourceControlPath();
if ($local_root) {
// We're in a working copy which supports subdirectory checkouts (e.g.,
// SVN) so we need to figure out what prefix we should add to each path
// (e.g., trunk/projects/example/) to get the absolute path from the
// root of the repository. DVCS systems like Git and Mercurial are not
// affected.
// Normalize both paths and check if the repository root is a prefix of
// the local root. If so, throw it away. Note that this correctly
// handles the case where the remote path is "/".
$local_root = id(new PhutilURI($local_root))->getPath();
$local_root = rtrim($local_root, '/');
$repo_root = id(new PhutilURI($repository->getRemoteURI()))->getPath();
$repo_root = rtrim($repo_root, '/');
if (!strncmp($repo_root, $local_root, strlen($repo_root))) {
$path_prefix = substr($local_root, strlen($repo_root));
}
}
}
$changesets = $diff->getChangesets();
$paths = array();
foreach ($changesets as $changeset) {
$paths[] = $path_prefix.'/'.$changeset->getFilename();
}
// Mark this as also touching all parent paths, so you can see all pending
// changes to any file within a directory.
$all_paths = array();
foreach ($paths as $local) {
foreach (DiffusionPathIDQuery::expandPathToRoot($local) as $path) {
$all_paths[$path] = true;
}
}
$all_paths = array_keys($all_paths);
return $all_paths;
}
}

View file

@ -8,8 +8,6 @@
final class DifferentialRevisionQuery final class DifferentialRevisionQuery
extends PhabricatorCursorPagedPolicyAwareQuery { extends PhabricatorCursorPagedPolicyAwareQuery {
private $pathIDs = array();
private $authors = array(); private $authors = array();
private $draftAuthors = array(); private $draftAuthors = array();
private $ccs = array(); private $ccs = array();
@ -27,6 +25,7 @@ final class DifferentialRevisionQuery
private $createdEpochMin; private $createdEpochMin;
private $createdEpochMax; private $createdEpochMax;
private $noReviewers; private $noReviewers;
private $paths;
const ORDER_MODIFIED = 'order-modified'; const ORDER_MODIFIED = 'order-modified';
const ORDER_CREATED = 'order-created'; const ORDER_CREATED = 'order-created';
@ -43,22 +42,15 @@ final class DifferentialRevisionQuery
/* -( Query Configuration )------------------------------------------------ */ /* -( Query Configuration )------------------------------------------------ */
/** /**
* Filter results to revisions which affect a Diffusion path ID in a given * Find revisions affecting one or more items in a list of paths.
* repository. You can call this multiple times to select revisions for
* several paths.
* *
* @param int Diffusion repository ID. * @param list<string> List of file paths.
* @param int Diffusion path ID.
* @return this * @return this
* @task config * @task config
*/ */
public function withPath($repository_id, $path_id) { public function withPaths(array $paths) {
$this->pathIDs[] = array( $this->paths = $paths;
'repositoryID' => $repository_id,
'pathID' => $path_id,
);
return $this; return $this;
} }
@ -568,12 +560,13 @@ final class DifferentialRevisionQuery
*/ */
private function buildJoinsClause(AphrontDatabaseConnection $conn) { private function buildJoinsClause(AphrontDatabaseConnection $conn) {
$joins = array(); $joins = array();
if ($this->pathIDs) {
if ($this->paths) {
$path_table = new DifferentialAffectedPath(); $path_table = new DifferentialAffectedPath();
$joins[] = qsprintf( $joins[] = qsprintf(
$conn, $conn,
'JOIN %T p ON p.revisionID = r.id', 'JOIN %R paths ON paths.revisionID = r.id',
$path_table->getTableName()); $path_table);
} }
if ($this->commitHashes) { if ($this->commitHashes) {
@ -635,20 +628,46 @@ final class DifferentialRevisionQuery
* @task internal * @task internal
*/ */
protected function buildWhereClause(AphrontDatabaseConnection $conn) { protected function buildWhereClause(AphrontDatabaseConnection $conn) {
$viewer = $this->getViewer();
$where = array(); $where = array();
if ($this->pathIDs) { if ($this->paths !== null) {
$path_clauses = array(); $paths = $this->paths;
$repo_info = igroup($this->pathIDs, 'repositoryID');
foreach ($repo_info as $repository_id => $paths) { $path_map = id(new DiffusionPathIDQuery($paths))
$path_clauses[] = qsprintf( ->loadPathIDs();
$conn,
'(p.repositoryID = %d AND p.pathID IN (%Ld))', if (!$path_map) {
$repository_id, // If none of the paths have entries in the PathID table, we can not
ipull($paths, 'pathID')); // possibly find any revisions affecting them.
throw new PhabricatorEmptyQueryException();
}
$where[] = qsprintf(
$conn,
'paths.pathID IN (%Ld)',
array_fuse($path_map));
// If we have repository PHIDs, additionally constrain this query to
// try to help MySQL execute it efficiently.
if ($this->repositoryPHIDs !== null) {
$repositories = id(new PhabricatorRepositoryQuery())
->setViewer($viewer)
->setParentQuery($this)
->withPHIDs($this->repositoryPHIDs)
->execute();
if (!$repositories) {
throw new PhabricatorEmptyQueryException();
}
$repository_ids = mpull($repositories, 'getID');
$where[] = qsprintf(
$conn,
'paths.repositoryID IN (%Ld)',
$repository_ids);
} }
$path_clauses = qsprintf($conn, '%LO', $path_clauses);
$where[] = $path_clauses;
} }
if ($this->authors) { if ($this->authors) {
@ -778,7 +797,9 @@ final class DifferentialRevisionQuery
*/ */
protected function shouldGroupQueryResultRows() { protected function shouldGroupQueryResultRows() {
if (count($this->pathIDs) > 1) { if ($this->paths) {
// (If we have exactly one repository and exactly one path, we don't
// technically need to group, but it's simpler to always group.)
return true; return true;
} }

View file

@ -57,6 +57,10 @@ final class DifferentialRevisionSearchEngine
$map['modifiedEnd']); $map['modifiedEnd']);
} }
if ($map['affectedPaths']) {
$query->withPaths($map['affectedPaths']);
}
return $query; return $query;
} }
@ -118,6 +122,12 @@ final class DifferentialRevisionSearchEngine
->setIsHidden(true) ->setIsHidden(true)
->setDescription( ->setDescription(
pht('Find revisions modified at or before a particular time.')), pht('Find revisions modified at or before a particular time.')),
id(new PhabricatorSearchStringListField())
->setKey('affectedPaths')
->setLabel(pht('Affected Paths'))
->setDescription(
pht('Search for revisions affecting particular paths.'))
->setIsHidden(true),
); );
} }

View file

@ -8,7 +8,6 @@ final class DifferentialAffectedPath extends DifferentialDAO {
protected $repositoryID; protected $repositoryID;
protected $pathID; protected $pathID;
protected $epoch;
protected $revisionID; protected $revisionID;
protected function getConfiguration() { protected function getConfiguration() {
@ -16,15 +15,16 @@ final class DifferentialAffectedPath extends DifferentialDAO {
self::CONFIG_TIMESTAMPS => false, self::CONFIG_TIMESTAMPS => false,
self::CONFIG_COLUMN_SCHEMA => array( self::CONFIG_COLUMN_SCHEMA => array(
'id' => null, 'id' => null,
'repositoryID' => 'id?',
), ),
self::CONFIG_KEY_SCHEMA => array( self::CONFIG_KEY_SCHEMA => array(
'PRIMARY' => null, 'PRIMARY' => null,
'repositoryID' => array(
'columns' => array('repositoryID', 'pathID', 'epoch'),
),
'revisionID' => array( 'revisionID' => array(
'columns' => array('revisionID'), 'columns' => array('revisionID'),
), ),
'key_path' => array(
'columns' => array('pathID', 'repositoryID'),
),
), ),
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }

View file

@ -1022,16 +1022,9 @@ final class DifferentialRevision extends DifferentialDAO
$engine->destroyObject($diff); $engine->destroyObject($diff);
} }
$conn_w = $this->establishConnection('w'); id(new DifferentialAffectedPathEngine())
->setRevision($this)
// we have to do paths a little differently as they do not have ->destroyAffectedPaths();
// an id or phid column for delete() to act on
$dummy_path = new DifferentialAffectedPath();
queryfx(
$conn_w,
'DELETE FROM %T WHERE revisionID = %d',
$dummy_path->getTableName(),
$this->getID());
$viewstate_query = id(new DifferentialViewStateQuery()) $viewstate_query = id(new DifferentialViewStateQuery())
->setViewer($viewer) ->setViewer($viewer)

View file

@ -246,6 +246,7 @@ final class DifferentialChangesetDetailView extends AphrontView {
'displayPath' => hsprintf('%s', $display_parts), 'displayPath' => hsprintf('%s', $display_parts),
'icon' => $display_icon, 'icon' => $display_icon,
'pathParts' => $path_parts, 'pathParts' => $path_parts,
'symbolPath' => $display_filename,
'pathIconIcon' => $changeset->getPathIconIcon(), 'pathIconIcon' => $changeset->getPathIconIcon(),
'pathIconColor' => $changeset->getPathIconColor(), 'pathIconColor' => $changeset->getPathIconColor(),

View file

@ -139,29 +139,8 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView {
} }
if ($diff) { if ($diff) {
$unit_status = idx( $lint = $this->newLintStatusView($diff);
$this->unitStatus, $unit = $this->newUnitStatusView($diff);
$diff->getPHID(),
$diff->getUnitStatus());
$lint = self::renderDiffLintStar($row['obj']);
$lint = phutil_tag(
'div',
array(
'class' => 'lintunit-star',
'title' => self::getDiffLintMessage($diff),
),
$lint);
$unit = self::renderDiffUnitStar($unit_status);
$unit = phutil_tag(
'div',
array(
'class' => 'lintunit-star',
'title' => self::getDiffUnitMessage($unit_status),
),
$unit);
$base = $this->renderBaseRevision($diff); $base = $this->renderBaseRevision($diff);
} else { } else {
$lint = null; $lint = null;
@ -282,86 +261,6 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView {
return $content; return $content;
} }
const STAR_NONE = 'none';
const STAR_OKAY = 'okay';
const STAR_WARN = 'warn';
const STAR_FAIL = 'fail';
const STAR_SKIP = 'skip';
private static function renderDiffLintStar(DifferentialDiff $diff) {
static $map = array(
DifferentialLintStatus::LINT_NONE => self::STAR_NONE,
DifferentialLintStatus::LINT_OKAY => self::STAR_OKAY,
DifferentialLintStatus::LINT_WARN => self::STAR_WARN,
DifferentialLintStatus::LINT_FAIL => self::STAR_FAIL,
DifferentialLintStatus::LINT_SKIP => self::STAR_SKIP,
DifferentialLintStatus::LINT_AUTO_SKIP => self::STAR_SKIP,
);
$star = idx($map, $diff->getLintStatus(), self::STAR_FAIL);
return self::renderDiffStar($star);
}
private static function renderDiffUnitStar($unit_status) {
static $map = array(
DifferentialUnitStatus::UNIT_NONE => self::STAR_NONE,
DifferentialUnitStatus::UNIT_OKAY => self::STAR_OKAY,
DifferentialUnitStatus::UNIT_WARN => self::STAR_WARN,
DifferentialUnitStatus::UNIT_FAIL => self::STAR_FAIL,
DifferentialUnitStatus::UNIT_SKIP => self::STAR_SKIP,
DifferentialUnitStatus::UNIT_AUTO_SKIP => self::STAR_SKIP,
);
$star = idx($map, $unit_status, self::STAR_FAIL);
return self::renderDiffStar($star);
}
public static function getDiffLintMessage(DifferentialDiff $diff) {
switch ($diff->getLintStatus()) {
case DifferentialLintStatus::LINT_NONE:
return pht('No Linters Available');
case DifferentialLintStatus::LINT_OKAY:
return pht('Lint OK');
case DifferentialLintStatus::LINT_WARN:
return pht('Lint Warnings');
case DifferentialLintStatus::LINT_FAIL:
return pht('Lint Errors');
case DifferentialLintStatus::LINT_SKIP:
return pht('Lint Skipped');
case DifferentialLintStatus::LINT_AUTO_SKIP:
return pht('Automatic diff as part of commit; lint not applicable.');
}
return pht('Unknown');
}
public static function getDiffUnitMessage($unit_status) {
switch ($unit_status) {
case DifferentialUnitStatus::UNIT_NONE:
return pht('No Unit Test Coverage');
case DifferentialUnitStatus::UNIT_OKAY:
return pht('Unit Tests OK');
case DifferentialUnitStatus::UNIT_WARN:
return pht('Unit Test Warnings');
case DifferentialUnitStatus::UNIT_FAIL:
return pht('Unit Test Errors');
case DifferentialUnitStatus::UNIT_SKIP:
return pht('Unit Tests Skipped');
case DifferentialUnitStatus::UNIT_AUTO_SKIP:
return pht(
'Automatic diff as part of commit; unit tests not applicable.');
}
return pht('Unknown');
}
private static function renderDiffStar($star) {
$class = 'diff-star-'.$star;
return phutil_tag(
'span',
array('class' => $class),
"\xE2\x98\x85");
}
private function renderBaseRevision(DifferentialDiff $diff) { private function renderBaseRevision(DifferentialDiff $diff) {
switch ($diff->getSourceControlSystem()) { switch ($diff->getSourceControlSystem()) {
case 'git': case 'git':
@ -401,4 +300,42 @@ final class DifferentialRevisionUpdateHistoryView extends AphrontView {
} }
return $link; return $link;
} }
private function newLintStatusView(DifferentialDiff $diff) {
$value = $diff->getLintStatus();
$status = DifferentialLintStatus::newStatusFromValue($value);
$icon = $status->getIconIcon();
$color = $status->getIconColor();
$name = $status->getName();
return $this->newDiffStatusIconView($icon, $color, $name);
}
private function newUnitStatusView(DifferentialDiff $diff) {
$value = $diff->getUnitStatus();
// NOTE: We may be overriding the value on the diff with a value from
// Harbormaster.
$value = idx($this->unitStatus, $diff->getPHID(), $value);
$status = DifferentialUnitStatus::newStatusFromValue($value);
$icon = $status->getIconIcon();
$color = $status->getIconColor();
$name = $status->getName();
return $this->newDiffStatusIconView($icon, $color, $name);
}
private function newDiffStatusIconView($icon, $color, $name) {
return id(new PHUIIconView())
->setIcon($icon, $color)
->addSigil('has-tooltip')
->setMetadata(
array(
'tip' => $name,
));
}
} }

View file

@ -938,17 +938,12 @@ final class DiffusionBrowseController extends DiffusionController {
$repository = $drequest->getRepository(); $repository = $drequest->getRepository();
$path = $drequest->getPath(); $path = $drequest->getPath();
$path_map = id(new DiffusionPathIDQuery(array($path)))->loadPathIDs();
$path_id = idx($path_map, $path);
if (!$path_id) {
return null;
}
$recent = (PhabricatorTime::getNow() - phutil_units('30 days in seconds')); $recent = (PhabricatorTime::getNow() - phutil_units('30 days in seconds'));
$revisions = id(new DifferentialRevisionQuery()) $revisions = id(new DifferentialRevisionQuery())
->setViewer($viewer) ->setViewer($viewer)
->withPath($repository->getID(), $path_id) ->withPaths(array($path))
->withRepositoryPHIDs(array($repository->getPHID()))
->withIsOpen(true) ->withIsOpen(true)
->withUpdatedEpochBetween($recent, null) ->withUpdatedEpochBetween($recent, null)
->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED) ->setOrder(DifferentialRevisionQuery::ORDER_MODIFIED)
@ -963,7 +958,7 @@ final class DiffusionBrowseController extends DiffusionController {
} }
$header = id(new PHUIHeaderView()) $header = id(new PHUIHeaderView())
->setHeader(pht('Recently Open Revisions')); ->setHeader(pht('Recent Open Revisions'));
$list = id(new DifferentialRevisionListView()) $list = id(new DifferentialRevisionListView())
->setViewer($viewer) ->setViewer($viewer)

View file

@ -4,7 +4,10 @@ final class DiffusionSymbolController extends DiffusionController {
public function handleRequest(AphrontRequest $request) { public function handleRequest(AphrontRequest $request) {
$viewer = $this->getViewer(); $viewer = $this->getViewer();
// See T13638 for discussion of escaping.
$name = $request->getURIData('name'); $name = $request->getURIData('name');
$name = phutil_unescape_uri_path_component($name);
$query = id(new DiffusionSymbolQuery()) $query = id(new DiffusionSymbolQuery())
->setViewer($viewer) ->setViewer($viewer)

View file

@ -66,12 +66,12 @@ final class DiffusionDatasourceEngineExtension
$parts = null; $parts = null;
if (preg_match('/(.*)(?:\\.|::|->)(.*)/', $symbol, $parts)) { if (preg_match('/(.*)(?:\\.|::|->)(.*)/', $symbol, $parts)) {
return urisprintf( return urisprintf(
'/diffusion/symbol/%s/?jump=true&context=%s', '/diffusion/symbol/%p/?jump=true&context=%s',
$parts[2], $parts[2],
$parts[1]); $parts[1]);
} else { } else {
return urisprintf( return urisprintf(
'/diffusion/symbol/%s/?jump=true', '/diffusion/symbol/%p/?jump=true',
$symbol); $symbol);
} }
} }

View file

@ -89,7 +89,7 @@ final class DiffusionRepositoryStorageManagementPanel
AlmanacClusterRepositoryServiceType::SERVICETYPE, AlmanacClusterRepositoryServiceType::SERVICETYPE,
)) ))
->withPHIDs(array($service_phid)) ->withPHIDs(array($service_phid))
->needBindings(true) ->needActiveBindings(true)
->executeOne(); ->executeOne();
if (!$service) { if (!$service) {
// TODO: Viewer may not have permission to see the service, or it may // TODO: Viewer may not have permission to see the service, or it may
@ -104,7 +104,7 @@ final class DiffusionRepositoryStorageManagementPanel
$rows = array(); $rows = array();
if ($service) { if ($service) {
$bindings = $service->getBindings(); $bindings = $service->getActiveBindings();
$bindings = mgroup($bindings, 'getDevicePHID'); $bindings = mgroup($bindings, 'getDevicePHID');
// This is an unusual read which always comes from the master. // This is an unusual read which always comes from the master.
@ -117,29 +117,19 @@ final class DiffusionRepositoryStorageManagementPanel
$versions = mpull($versions, null, 'getDevicePHID'); $versions = mpull($versions, null, 'getDevicePHID');
// List enabled devices first, then sort devices in each group by name.
$sort = array(); $sort = array();
foreach ($bindings as $key => $binding_group) { foreach ($bindings as $key => $binding_group) {
$all_disabled = $this->isDisabledGroup($binding_group);
$sort[$key] = id(new PhutilSortVector()) $sort[$key] = id(new PhutilSortVector())
->addInt($all_disabled ? 1 : 0)
->addString(head($binding_group)->getDevice()->getName()); ->addString(head($binding_group)->getDevice()->getName());
} }
$sort = msortv($sort, 'getSelf'); $sort = msortv($sort, 'getSelf');
$bindings = array_select_keys($bindings, array_keys($sort)) + $bindings; $bindings = array_select_keys($bindings, array_keys($sort)) + $bindings;
foreach ($bindings as $binding_group) { foreach ($bindings as $binding_group) {
$all_disabled = $this->isDisabledGroup($binding_group);
$any_binding = head($binding_group); $any_binding = head($binding_group);
if ($all_disabled) { $binding_icon = 'fa-folder-open green';
$binding_icon = 'fa-times grey'; $binding_tip = pht('Active');
$binding_tip = pht('Disabled');
} else {
$binding_icon = 'fa-folder-open green';
$binding_tip = pht('Active');
}
$binding_icon = id(new PHUIIconView()) $binding_icon = id(new PHUIIconView())
->setIcon($binding_icon) ->setIcon($binding_icon)
@ -376,17 +366,4 @@ final class DiffusionRepositoryStorageManagementPanel
return $box_view; return $box_view;
} }
private function isDisabledGroup(array $binding_group) {
assert_instances_of($binding_group, 'AlmanacBinding');
foreach ($binding_group as $binding) {
if (!$binding->getIsDisabled()) {
return false;
}
}
return true;
}
} }

View file

@ -34,7 +34,7 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
DrydockBlueprint $blueprint, DrydockBlueprint $blueprint,
DrydockLease $lease) { DrydockLease $lease) {
$services = $this->loadServices($blueprint); $services = $this->loadServices($blueprint);
$bindings = $this->loadAllBindings($services); $bindings = $this->getActiveBindings($services);
if (!$bindings) { if (!$bindings) {
// If there are no devices bound to the services for this blueprint, // If there are no devices bound to the services for this blueprint,
@ -222,7 +222,7 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
->setViewer($viewer) ->setViewer($viewer)
->withPHIDs($service_phids) ->withPHIDs($service_phids)
->withServiceTypes($this->getAlmanacServiceTypes()) ->withServiceTypes($this->getAlmanacServiceTypes())
->needBindings(true) ->needActiveBindings(true)
->execute(); ->execute();
$services = mpull($services, null, 'getPHID'); $services = mpull($services, null, 'getPHID');
@ -242,9 +242,9 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
return $this->services; return $this->services;
} }
private function loadAllBindings(array $services) { private function getActiveBindings(array $services) {
assert_instances_of($services, 'AlmanacService'); assert_instances_of($services, 'AlmanacService');
$bindings = array_mergev(mpull($services, 'getBindings')); $bindings = array_mergev(mpull($services, 'getActiveBindings'));
return mpull($bindings, null, 'getPHID'); return mpull($bindings, null, 'getPHID');
} }
@ -271,15 +271,10 @@ final class DrydockAlmanacServiceHostBlueprintImplementation
$allocated_phids = array_fuse($allocated_phids); $allocated_phids = array_fuse($allocated_phids);
$services = $this->loadServices($blueprint); $services = $this->loadServices($blueprint);
$bindings = $this->loadAllBindings($services); $bindings = $this->getActiveBindings($services);
$free = array(); $free = array();
foreach ($bindings as $binding) { foreach ($bindings as $binding) {
// Don't consider disabled bindings to be available.
if ($binding->getIsDisabled()) {
continue;
}
if (empty($allocated_phids[$binding->getPHID()])) { if (empty($allocated_phids[$binding->getPHID()])) {
$free[] = $binding; $free[] = $binding;
} }

View file

@ -22,7 +22,6 @@ final class FundInitiative extends FundDAO
protected $editPolicy; protected $editPolicy;
protected $status; protected $status;
protected $totalAsCurrency; protected $totalAsCurrency;
protected $mailKey;
private $projectPHIDs = self::ATTACHABLE; private $projectPHIDs = self::ATTACHABLE;
@ -62,7 +61,6 @@ final class FundInitiative extends FundDAO
'status' => 'text32', 'status' => 'text32',
'merchantPHID' => 'phid?', 'merchantPHID' => 'phid?',
'totalAsCurrency' => 'text64', 'totalAsCurrency' => 'text64',
'mailKey' => 'bytes20',
), ),
self::CONFIG_APPLICATION_SERIALIZERS => array( self::CONFIG_APPLICATION_SERIALIZERS => array(
'totalAsCurrency' => new PhortuneCurrencySerializer(), 'totalAsCurrency' => new PhortuneCurrencySerializer(),
@ -78,8 +76,8 @@ final class FundInitiative extends FundDAO
) + parent::getConfiguration(); ) + parent::getConfiguration();
} }
public function generatePHID() { public function getPHIDType() {
return PhabricatorPHID::generateNewPHID(FundInitiativePHIDType::TYPECONST); return FundInitiativePHIDType::TYPECONST;
} }
public function getMonogram() { public function getMonogram() {
@ -103,13 +101,6 @@ final class FundInitiative extends FundDAO
return ($this->getStatus() == self::STATUS_CLOSED); return ($this->getStatus() == self::STATUS_CLOSED);
} }
public function save() {
if (!$this->mailKey) {
$this->mailKey = Filesystem::readRandomCharacters(20);
}
return parent::save();
}
/* -( PhabricatorPolicyInterface )----------------------------------------- */ /* -( PhabricatorPolicyInterface )----------------------------------------- */

View file

@ -197,6 +197,10 @@ final class PhabricatorObjectHandle
return $this->status; return $this->status;
} }
public function isClosed() {
return ($this->status === self::STATUS_CLOSED);
}
public function setFullName($full_name) { public function setFullName($full_name) {
$this->fullName = $full_name; $this->fullName = $full_name;
return $this; return $this;

View file

@ -61,7 +61,7 @@ final class PhabricatorRepositoryManagementClusterizeWorkflow
array( array(
AlmanacClusterRepositoryServiceType::SERVICETYPE, AlmanacClusterRepositoryServiceType::SERVICETYPE,
)) ))
->needBindings(true) ->needActiveBindings(true)
->executeOne(); ->executeOne();
if (!$service) { if (!$service) {
throw new PhutilArgumentUsageException( throw new PhutilArgumentUsageException(

View file

@ -1151,7 +1151,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
/** /**
* Get a parsed object representation of the repository's remote URI.. * Get a parsed object representation of the repository's remote URI..
* *
* @return wild A @{class@libphutil:PhutilURI}. * @return wild A @{class@arcanist:PhutilURI}.
* @task uri * @task uri
*/ */
public function getRemoteURIObject() { public function getRemoteURIObject() {
@ -2109,7 +2109,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
throw new Exception( throw new Exception(
pht( pht(
'The Almanac service for this repository is not bound to any '. 'The Almanac service for this repository is not bound to any '.
'interfaces.')); 'active interfaces.'));
} }
$uris = array(); $uris = array();
@ -2531,7 +2531,7 @@ final class PhabricatorRepository extends PhabricatorRepositoryDAO
$service = id(new AlmanacServiceQuery()) $service = id(new AlmanacServiceQuery())
->setViewer(PhabricatorUser::getOmnipotentUser()) ->setViewer(PhabricatorUser::getOmnipotentUser())
->withPHIDs(array($service_phid)) ->withPHIDs(array($service_phid))
->needBindings(true) ->needActiveBindings(true)
->needProperties(true) ->needProperties(true)
->executeOne(); ->executeOne();
if (!$service) { if (!$service) {

View file

@ -169,8 +169,8 @@ this:
{ {
... ...
"constraints": { "constraints": {
"authors": ["PHID-USER-1111", "PHID-USER-2222"], "authorPHIDs": ["PHID-USER-1111", "PHID-USER-2222"],
"statuses": ["open", "closed"], "flavors": ["cherry", "orange"],
... ...
}, },
... ...

View file

@ -44,6 +44,16 @@ final class PhabricatorSystemDebugUIEventListener
->setName(pht('View Hovercard')) ->setName(pht('View Hovercard'))
->setHref(urisprintf('/search/hovercard/?names=%s', $phid)); ->setHref(urisprintf('/search/hovercard/?names=%s', $phid));
if ($object instanceof DifferentialRevision) {
$submenu[] = id(new PhabricatorActionView())
->setIcon('fa-database')
->setName(pht('View Affected Path Index'))
->setHref(
urisprintf(
'/differential/revision/paths/%s/',
$object->getID()));
}
$developer_action = id(new PhabricatorActionView()) $developer_action = id(new PhabricatorActionView())
->setName(pht('Advanced/Developer...')) ->setName(pht('Advanced/Developer...'))
->setIcon('fa-magic') ->setIcon('fa-magic')

View file

@ -21,7 +21,7 @@ available by looking at all of the subclasses of
@{class@phabricator:PhabricatorApplication}. It @{class@phabricator:PhabricatorApplication}. It
discovers available workflows in `arc` by looking at all of the subclasses of discovers available workflows in `arc` by looking at all of the subclasses of
@{class@arcanist:ArcanistWorkflow}. It discovers available locales @{class@arcanist:ArcanistWorkflow}. It discovers available locales
by looking at all of the subclasses of @{class@libphutil:PhutilLocale}. by looking at all of the subclasses of @{class@arcanist:PhutilLocale}.
This pattern holds in many cases, so you can often add functionality by adding This pattern holds in many cases, so you can often add functionality by adding
new classes with no other work. Phabricator will automatically discover and new classes with no other work. Phabricator will automatically discover and
@ -49,8 +49,8 @@ This is intended as a quick way to add small pieces of functionality, test new
features, or get started on a larger project. Extending Phabricator like this features, or get started on a larger project. Extending Phabricator like this
imposes a small performance penalty compared to using a library. imposes a small performance penalty compared to using a library.
This directory exists in all libphutil libraries, so you can find similar This directory exists in all libphutil libraries, so you can find a similar
directories in `arcanist/src/extensions/` and `libphutil/src/extensions/`. directory in `arcanist/src/extensions/`.
For example, to add a new application, create a file like this one and add it For example, to add a new application, create a file like this one and add it
to `phabricator/src/extensions/`. to `phabricator/src/extensions/`.
@ -171,8 +171,8 @@ performing static analysis.
NOTE: If Phabricator isn't located next to your custom library, specify a NOTE: If Phabricator isn't located next to your custom library, specify a
path which actually points to the `phabricator/` directory. path which actually points to the `phabricator/` directory.
You do not need to declare dependencies on `arcanist` or `libphutil`, You do not need to declare dependencies on `arcanist`, since `arc liberate`
since `arc liberate` automatically loads them. automatically loads them.
Finally, edit your Phabricator config to tell it to load your library at Finally, edit your Phabricator config to tell it to load your library at
runtime, by adding it to `load-libraries`: runtime, by adding it to `load-libraries`:
@ -206,8 +206,8 @@ This will automatically regenerate the static map of the library.
What You Can Extend And Invoke What You Can Extend And Invoke
============================== ==============================
libphutil, Arcanist and Phabricator are strict about extensibility of classes Arcanist and Phabricator are strict about extensibility of classes and
and visibility of methods and properties. Most classes are marked `final`, and visibility of methods and properties. Most classes are marked `final`, and
methods have the minimum required visibility (protected or private). The goal methods have the minimum required visibility (protected or private). The goal
of this strictness is to make it clear what you can safely extend, access, and of this strictness is to make it clear what you can safely extend, access, and
invoke, so your code will keep working as the upstream changes. invoke, so your code will keep working as the upstream changes.
@ -215,8 +215,8 @@ invoke, so your code will keep working as the upstream changes.
IMPORTANT: We'll still break APIs frequently. The upstream does not support IMPORTANT: We'll still break APIs frequently. The upstream does not support
extension development, and none of these APIs are stable. extension development, and none of these APIs are stable.
When developing libraries to work with libphutil, Arcanist and Phabricator, you When developing libraries to work with Arcanist and Phabricator, you should
should respect method and property visibility. respect method and property visibility.
If you want to add features but can't figure out how to do it without changing If you want to add features but can't figure out how to do it without changing
Phabricator code, here are some approaches you may be able to take: Phabricator code, here are some approaches you may be able to take:

View file

@ -47,10 +47,9 @@ Before you file a report, here are some common solutions to problems:
issues to be fixed in less than 24 hours, so even if you've updated recently issues to be fixed in less than 24 hours, so even if you've updated recently
you should update again. If you aren't sure how to update, see the next you should update again. If you aren't sure how to update, see the next
section. section.
- **Update Libraries**: Make sure `libphutil/`, `arcanist/` and - **Update Libraries**: Make sure `arcanist/` and `phabricator/` are all up
`phabricator/` are all up to date. Users often update `phabricator/` but to date. Users often update `phabricator/` but forget to update `arcanist/`.
forget to update `arcanist/` or `libphutil/`. When you update, make sure you When you update, make sure you update all three libraries.
update all three libraries.
- **Restart Apache or PHP-FPM**: Phabricator uses caches which don't get - **Restart Apache or PHP-FPM**: Phabricator uses caches which don't get
reset until you restart Apache or PHP-FPM. After updating, make sure you reset until you restart Apache or PHP-FPM. After updating, make sure you
restart. restart.

View file

@ -1,7 +1,7 @@
@title Contributor Introduction @title Contributor Introduction
@group contrib @group contrib
Introduction to contributing to Phabricator, Arcanist and libphutil. Introduction to contributing to Phabricator and Arcanist.
Overview Overview
======== ========

View file

@ -212,9 +212,9 @@ outside of our comfort zone.
Writing and Submitting Patches Writing and Submitting Patches
================== ==================
To actually submit a patch, run `arc diff` in `phabricator/`, `arcanist/`, or To actually submit a patch, run `arc diff` in `phabricator/` or `arcanist/`.
`libphutil/`. When executed in these directories, `arc` should automatically When executed in these directories, `arc` should automatically talk to the
talk to the upstream install. You can add `epriestley` as a reviewer. upstream install. You can add `epriestley` as a reviewer.
You should read the relevant coding convention documents before you submit a You should read the relevant coding convention documents before you submit a
change. If you're a new contributor, you don't need to worry about this too change. If you're a new contributor, you don't need to worry about this too

View file

@ -2,7 +2,7 @@
@group standards @group standards
This document is a general coding standard for contributing to Phabricator, This document is a general coding standard for contributing to Phabricator,
Arcanist, libphutil and Diviner. Arcanist, and Diviner.
= Overview = = Overview =
@ -136,7 +136,7 @@ handling and makes it easier to get right than wrong:
Filesystem::writeFile('file.bak', $data); // Best Filesystem::writeFile('file.bak', $data); // Best
do_something_dangerous(); do_something_dangerous();
See @{article@libphutil:Command Execution} for details on the APIs used in this See @{article@arcanist:Command Execution} for details on the APIs used in this
example. example.
= Documentation, Comments and Formatting = = Documentation, Comments and Formatting =

View file

@ -44,7 +44,7 @@ For instructions on adding new classes, see
Writing Translatable Code Writing Translatable Code
========================= =========================
Strings are marked for translation with @{function@libphutil:pht}. Strings are marked for translation with @{function@arcanist:pht}.
The `pht()` function takes a string (and possibly some parameters) and returns The `pht()` function takes a string (and possibly some parameters) and returns
the translated version of that string in the current viewer's locale, if a the translated version of that string in the current viewer's locale, if a
@ -68,7 +68,7 @@ the major rules are:
- Use parameters to create strings containing user names, object names, etc. - Use parameters to create strings containing user names, object names, etc.
- Translate full sentences, not sentence fragments. - Translate full sentences, not sentence fragments.
- Let the translation framework handle plural rules. - Let the translation framework handle plural rules.
- Use @{class@libphutil:PhutilNumber} for numbers. - Use @{class@arcanist:PhutilNumber} for numbers.
- Let the translation framework handle subject gender rules. - Let the translation framework handle subject gender rules.
- Translate all human-readable text, even exceptions and error messages. - Translate all human-readable text, even exceptions and error messages.
@ -323,7 +323,7 @@ pronouns (like "he" and "she") but there are more complex rules in other
languages, and languages like Czech also require verb agreement. languages, and languages like Czech also require verb agreement.
When a parameter refers to a gendered person, pass an object which implements When a parameter refers to a gendered person, pass an object which implements
@{interface@libphutil:PhutilPerson} to `pht()` so translators can provide @{interface@arcanist:PhutilPerson} to `pht()` so translators can provide
gendered translation variants. gendered translation variants.
```lang=php ```lang=php
@ -361,8 +361,8 @@ all human-readable text. This rule is unambiguous and easy to follow.
In cases where similar error or exception text is often repeated, it is In cases where similar error or exception text is often repeated, it is
probably appropriate to define an exception for that category of error rather probably appropriate to define an exception for that category of error rather
than write the text out repeatedly, anyway. Two examples are than write the text out repeatedly, anyway. Two examples are
@{class@libphutil:PhutilInvalidStateException} and @{class@arcanist:PhutilInvalidStateException} and
@{class@libphutil:PhutilMethodNotImplementedException}, which mostly exist to @{class@arcanist:PhutilMethodNotImplementedException}, which mostly exist to
produce a consistent message about a common error state in a convenient way. produce a consistent message about a common error state in a convenient way.
There are a handful of error strings in the codebase which may be used before There are a handful of error strings in the codebase which may be used before

View file

@ -2,13 +2,13 @@
@group standards @group standards
This document describes PHP coding standards for Phabricator and related This document describes PHP coding standards for Phabricator and related
projects (like Arcanist and libphutil). projects (like Arcanist).
= Overview = = Overview =
This document outlines technical and style guidelines which are followed in This document outlines technical and style guidelines which are followed in
libphutil. Contributors should also follow these guidelines. Many of these Phabricator and Arcanist. Contributors should also follow these guidelines.
guidelines are automatically enforced by lint. Many of these guidelines are automatically enforced by lint.
These guidelines are essentially identical to the Facebook guidelines, since I These guidelines are essentially identical to the Facebook guidelines, since I
basically copy-pasted them. If you are already familiar with the Facebook basically copy-pasted them. If you are already familiar with the Facebook

View file

@ -13,13 +13,13 @@ pipeline, and the browser will treat it as plain text, not HTML.
This document describes the right way to build HTML components so they are safe This document describes the right way to build HTML components so they are safe
from XSS and render correctly. Broadly: from XSS and render correctly. Broadly:
- Use @{function@libphutil:phutil_tag} (and @{function:javelin_tag}) to build - Use @{function@arcanist:phutil_tag} (and @{function:javelin_tag}) to build
tags. tags.
- Use @{function@libphutil:hsprintf} where @{function@libphutil:phutil_tag} - Use @{function@arcanist:hsprintf} where @{function@arcanist:phutil_tag}
is awkward. is awkward.
- Combine elements with arrays, not string concatenation. - Combine elements with arrays, not string concatenation.
- @{class:AphrontView} subclasses should return a - @{class:AphrontView} subclasses should return a
@{class@libphutil:PhutilSafeHTML} object from their `render()` method. @{class@arcanist:PhutilSafeHTML} object from their `render()` method.
- @{class:AphrontView} subclasses act like tags when rendering. - @{class:AphrontView} subclasses act like tags when rendering.
- @{function:pht} has some special rules. - @{function:pht} has some special rules.
- There are some other things that you should be aware of. - There are some other things that you should be aware of.
@ -28,7 +28,7 @@ See below for discussion.
= Building Tags: phutil_tag() = = Building Tags: phutil_tag() =
Build HTML tags with @{function@libphutil:phutil_tag}. For example: Build HTML tags with @{function@arcanist:phutil_tag}. For example:
phutil_tag( phutil_tag(
'div', 'div',
@ -37,10 +37,10 @@ Build HTML tags with @{function@libphutil:phutil_tag}. For example:
), ),
$content); $content);
@{function@libphutil:phutil_tag} will properly escape the content and all the @{function@arcanist:phutil_tag} will properly escape the content and all the
attributes, and return a @{class@libphutil:PhutilSafeHTML} object. The rendering attributes, and return a @{class@arcanist:PhutilSafeHTML} object. The rendering
pipeline knows that this object represents a properly escaped HTML tag. This pipeline knows that this object represents a properly escaped HTML tag. This
allows @{function@libphutil:phutil_tag} to render tags with other tags as allows @{function@arcanist:phutil_tag} to render tags with other tags as
content correctly (without double-escaping): content correctly (without double-escaping):
phutil_tag( phutil_tag(
@ -52,14 +52,14 @@ content correctly (without double-escaping):
$content)); $content));
In Phabricator, the @{function:javelin_tag} function is similar to In Phabricator, the @{function:javelin_tag} function is similar to
@{function@libphutil:phutil_tag}, but provides special handling for the @{function@arcanist:phutil_tag}, but provides special handling for the
`sigil` and `meta` attributes. `sigil` and `meta` attributes.
= Building Blocks: hsprintf() = = Building Blocks: hsprintf() =
Sometimes, @{function@libphutil:phutil_tag} can be particularly awkward to Sometimes, @{function@arcanist:phutil_tag} can be particularly awkward to
use. You can use @{function@libphutil:hsprintf} to build larger and more use. You can use @{function@arcanist:hsprintf} to build larger and more
complex blocks of HTML, when @{function@libphutil:phutil_tag} is a poor fit. complex blocks of HTML, when @{function@arcanist:phutil_tag} is a poor fit.
@{function:hsprintf} has `sprintf()` semantics, but `%s` escapes HTML: @{function:hsprintf} has `sprintf()` semantics, but `%s` escapes HTML:
// Safely build fragments or unwieldy blocks. // Safely build fragments or unwieldy blocks.
@ -72,13 +72,13 @@ complex blocks of HTML, when @{function@libphutil:phutil_tag} is a poor fit.
- You need to build a block with a lot of tags, like a table with rows and - You need to build a block with a lot of tags, like a table with rows and
cells. cells.
- You need to build part of a tag (usually you should avoid this, but if you - You need to build part of a tag (usually you should avoid this, but if you
do need to, @{function@libphutil:phutil_tag} can not do it). do need to, @{function@arcanist:phutil_tag} can not do it).
Note that it is unsafe to provide any user-controlled data to the first Note that it is unsafe to provide any user-controlled data to the first
parameter of @{function@libphutil:hsprintf} (the `sprintf()`-style pattern). parameter of @{function@arcanist:hsprintf} (the `sprintf()`-style pattern).
Like @{function@libphutil:phutil_tag}, this function returns a Like @{function@arcanist:phutil_tag}, this function returns a
@{class@libphutil:PhutilSafeHTML} object. @{class@arcanist:PhutilSafeHTML} object.
= Composing Tags = = Composing Tags =
@ -99,7 +99,7 @@ Instead, use an array:
// Render a tag containing other tags safely. // Render a tag containing other tags safely.
phutil_tag('div', array(), array($header, $body)); phutil_tag('div', array(), array($header, $body));
If you concatenate @{class@libphutil:PhutilSafeHTML} objects, they revert to If you concatenate @{class@arcanist:PhutilSafeHTML} objects, they revert to
normal strings and are no longer marked as properly escaped tags. normal strings and are no longer marked as properly escaped tags.
(In the future, these objects may stop converting to strings, but for now they (In the future, these objects may stop converting to strings, but for now they
@ -118,7 +118,7 @@ If you need to build a list of items with some element in between each of them
= AphrontView Classes = = AphrontView Classes =
Subclasses of @{class:AphrontView} in Phabricator should return a Subclasses of @{class:AphrontView} in Phabricator should return a
@{class@libphutil:PhutilSafeHTML} object. The easiest way to do this is to @{class@arcanist:PhutilSafeHTML} object. The easiest way to do this is to
return `phutil_tag()` or `javelin_tag()`: return `phutil_tag()` or `javelin_tag()`:
return phutil_tag('div', ...); return phutil_tag('div', ...);
@ -130,8 +130,8 @@ You can use an @{class:AphrontView} subclass like you would a tag:
= Internationalization: pht() = = Internationalization: pht() =
The @{function:pht} function has some special rules. If any input to The @{function:pht} function has some special rules. If any input to
@{function:pht} is a @{class@libphutil:PhutilSafeHTML} object, @{function:pht} @{function:pht} is a @{class@arcanist:PhutilSafeHTML} object, @{function:pht}
returns a @{class@libphutil:PhutilSafeHTML} object itself. Otherwise, it returns returns a @{class@arcanist:PhutilSafeHTML} object itself. Otherwise, it returns
normal text. normal text.
This is generally safe because translations are not permitted to have more tags This is generally safe because translations are not permitted to have more tags
@ -146,23 +146,23 @@ like you would expect, but it is worth being aware of.
NOTE: This section describes dangerous methods which can bypass XSS protections. NOTE: This section describes dangerous methods which can bypass XSS protections.
If possible, do not use them. If possible, do not use them.
You can build @{class@libphutil:PhutilSafeHTML} out of a string explicitly by You can build @{class@arcanist:PhutilSafeHTML} out of a string explicitly by
calling @{function:phutil_safe_html} on it. This is **dangerous**, because if calling @{function:phutil_safe_html} on it. This is **dangerous**, because if
you are wrong and the string is not actually safe, you have introduced an XSS you are wrong and the string is not actually safe, you have introduced an XSS
vulnerability. Consequently, you should avoid calling this if possible. vulnerability. Consequently, you should avoid calling this if possible.
You can use @{function@libphutil:phutil_escape_html_newlines} to escape HTML You can use @{function@arcanist:phutil_escape_html_newlines} to escape HTML
while converting newlines to `<br />`. You should not need to explicitly use while converting newlines to `<br />`. You should not need to explicitly use
@{function@libphutil:phutil_escape_html} anywhere. @{function@arcanist:phutil_escape_html} anywhere.
If you need to apply a string function (such as `trim()`) to safe HTML, use If you need to apply a string function (such as `trim()`) to safe HTML, use
@{method@libphutil:PhutilSafeHTML::applyFunction}. @{method@arcanist:PhutilSafeHTML::applyFunction}.
If you need to extract the content of a @{class@libphutil:PhutilSafeHTML} If you need to extract the content of a @{class@arcanist:PhutilSafeHTML}
object, you should call `getHTMLContent()`, not cast it to a string. Eventually, object, you should call `getHTMLContent()`, not cast it to a string. Eventually,
we would like to remove the string cast entirely. we would like to remove the string cast entirely.
Functions @{function@libphutil:phutil_tag} and @{function@libphutil:hsprintf} Functions @{function@arcanist:phutil_tag} and @{function@arcanist:hsprintf}
are not safe if you pass the user input for the tag or attribute name. All the are not safe if you pass the user input for the tag or attribute name. All the
following examples are dangerous: following examples are dangerous:

View file

@ -1,13 +1,13 @@
@title Writing Unit Tests @title Writing Unit Tests
@group developer @group developer
Simple guide to libphutil, Arcanist and Phabricator unit tests. Simple guide to Arcanist and Phabricator unit tests.
= Overview = = Overview =
libphutil, Arcanist and Phabricator provide and use a simple unit test Arcanist and Phabricator provide and use a simple unit test framework. This
framework. This document is aimed at project contributors and describes how to document is aimed at project contributors and describes how to use it to add
use it to add and run tests in these projects or other libphutil libraries. and run tests in these projects or other libphutil libraries.
In the general case, you can integrate `arc` with a custom unit test engine In the general case, you can integrate `arc` with a custom unit test engine
(like PHPUnit or any other unit testing library) to run tests in other projects. (like PHPUnit or any other unit testing library) to run tests in other projects.
@ -16,7 +16,7 @@ for information on customizing engines.
= Adding Tests = = Adding Tests =
To add new tests to a libphutil, Arcanist or Phabricator module: To add new tests to a Arcanist or Phabricator module:
- Create a `__tests__/` directory in the module if it doesn't exist yet. - Create a `__tests__/` directory in the module if it doesn't exist yet.
- Add classes to the `__tests__/` directory which extend from - Add classes to the `__tests__/` directory which extend from

View file

@ -18,7 +18,7 @@ If you merge a list of arrays like this:
intermediate arrays and copies every element it has previously seen each time intermediate arrays and copies every element it has previously seen each time
you iterate. you iterate.
In a libphutil environment, you can use @{function@libphutil:array_mergev} In a libphutil environment, you can use @{function@arcanist:array_mergev}
instead. instead.
= `var_export()` Hates Baby Animals = = `var_export()` Hates Baby Animals =
@ -147,7 +147,7 @@ keys that are naturally sortable with a function that uses native comparison
instead, and use it to reorder the original array. instead, and use it to reorder the original array.
In a libphutil environment, you can often do this easily with In a libphutil environment, you can often do this easily with
@{function@libphutil:isort} or @{function@libphutil:msort}. @{function@arcanist:isort} or @{function@arcanist:msort}.
= `array_intersect()` and `array_diff()` are Also Slow = = `array_intersect()` and `array_diff()` are Also Slow =
@ -270,7 +270,7 @@ new $class_name($argv[0], $argv[1], ...);
...you'll probably invent a very interesting, very novel solution that is very ...you'll probably invent a very interesting, very novel solution that is very
wrong. In a libphutil environment, solve this problem with wrong. In a libphutil environment, solve this problem with
@{function@libphutil:newv}. Elsewhere, copy `newv()`'s implementation. @{function@arcanist:newv}. Elsewhere, copy `newv()`'s implementation.
= Equality is not Transitive = = Equality is not Transitive =

View file

@ -65,7 +65,7 @@ daemonizing it, so you can see output in your console.
You can get a list of launchable daemons with **phd list**: You can get a list of launchable daemons with **phd list**:
- **libphutil test daemons** are not generally useful unless you are - **test daemons** are not generally useful unless you are
developing daemon infrastructure or debugging a daemon problem; developing daemon infrastructure or debugging a daemon problem;
- **PhabricatorTaskmasterDaemon** performs work from a task queue; - **PhabricatorTaskmasterDaemon** performs work from a task queue;
- **PhabricatorRepositoryPullLocalDaemon** daemons track repositories, for - **PhabricatorRepositoryPullLocalDaemon** daemons track repositories, for

View file

@ -32,7 +32,7 @@ authority and clients have a list of trusted authorities.
You can self-sign a certificate by creating your own CA, but clients will not You can self-sign a certificate by creating your own CA, but clients will not
trust it by default. They need to add the CA as a trusted authority. trust it by default. They need to add the CA as a trusted authority.
For instructions on adding CAs, see `libphutil/resources/ssl/README`. For instructions on adding CAs, see `arcanist/resources/ssl/README`.
If you'd prefer that `arc` not verify the identity of the server whatsoever, you If you'd prefer that `arc` not verify the identity of the server whatsoever, you
can use the `https.blindly-trust-domains` setting. This will make it can use the `https.blindly-trust-domains` setting. This will make it

View file

@ -48,7 +48,7 @@ Plugin: Error Log
The "Error Log" plugin shows errors that occurred while generating the page, The "Error Log" plugin shows errors that occurred while generating the page,
similar to the httpd `error.log`. You can send information to the error log similar to the httpd `error.log`. You can send information to the error log
explicitly with the @{function@libphutil:phlog} function. explicitly with the @{function@arcanist:phlog} function.
If errors occurred, a red dot will appear on the plugin tab. If errors occurred, a red dot will appear on the plugin tab.

View file

@ -90,15 +90,8 @@ have PHP installed, you can download it from <http://www.php.net/>.
To install Arcanist, pick an install directory and clone the code from GitHub: To install Arcanist, pick an install directory and clone the code from GitHub:
some_install_path/ $ git clone https://github.com/phacility/libphutil.git
some_install_path/ $ git clone https://github.com/phacility/arcanist.git some_install_path/ $ git clone https://github.com/phacility/arcanist.git
This should leave you with a directory structure like this
some_install_path/ # Wherever you chose to install it.
arcanist/ # Arcanist-specific code and libraries.
libphutil/ # A shared library Arcanist depends upon.
Now add `some_install_path/arcanist/bin/` to your PATH environment variable. Now add `some_install_path/arcanist/bin/` to your PATH environment variable.
When you type "arc", you should see something like this: When you type "arc", you should see something like this:
@ -110,8 +103,7 @@ trouble getting this far, see these detailed guides:
- On Windows: @{article:Arcanist User Guide: Windows} - On Windows: @{article:Arcanist User Guide: Windows}
- On Mac OS X: @{article:Arcanist User Guide: Mac OS X} - On Mac OS X: @{article:Arcanist User Guide: Mac OS X}
You can later upgrade Arcanist and libphutil to the latest versions with You can later upgrade Arcanist to the latest version with `arc upgrade`:
`arc upgrade`:
$ arc upgrade $ arc upgrade
@ -122,7 +114,7 @@ installed and keep people up to date. Here are some approaches you might be
able to use: able to use:
- Facebook does most development on development servers, which have a standard - Facebook does most development on development servers, which have a standard
environment and NFS mounts. Arcanist and libphutil themselves live on an environment and NFS mounts. Arcanist lives on an
NFS mount, and the default `.bashrc` adds them to the PATH. Updating the NFS mount, and the default `.bashrc` adds them to the PATH. Updating the
mount source updates everyone's versions, and new employees have a working mount source updates everyone's versions, and new employees have a working
`arc` when they first log in. `arc` when they first log in.

View file

@ -27,9 +27,9 @@ to `src/some/file.php` and give you a detailed coverage report.
If the test engine enables coverage by default, it will be uploaded to If the test engine enables coverage by default, it will be uploaded to
Differential and displayed in the right gutter when viewing diffs. Differential and displayed in the right gutter when viewing diffs.
= Enabling Coverage for libphutil, Arcanist and Phabricator = = Enabling Coverage for Arcanist and Phabricator =
If you're contributing, libphutil, Arcanist and Phabricator support coverage if If you're contributing, Arcanist and Phabricator support coverage if
you install Xdebug: you install Xdebug:
http://xdebug.org/ http://xdebug.org/

View file

@ -20,9 +20,6 @@ First, install dependencies:
Then install Arcanist itself: Then install Arcanist itself:
$ mkdir somewhere/
$ cd somewhere/
somewhere/ $ git clone https://github.com/phacility/libphutil.git
somewhere/ $ git clone https://github.com/phacility/arcanist.git somewhere/ $ git clone https://github.com/phacility/arcanist.git
Add `arc` to your path: Add `arc` to your path:

View file

@ -19,8 +19,7 @@ The primary ways to make Conduit calls are:
the API and making calls. This is the best starting point for learning about the API and making calls. This is the best starting point for learning about
the API. See the next section for details. the API. See the next section for details.
`ConduitClient`: This is the official client available in `libphutil`, and `ConduitClient`: This is the official client available in `arcanist`.
the one used by `arc`.
`arc call-conduit`: You can use this `arc` command to execute low-level `arc call-conduit`: You can use this `arc` command to execute low-level
Conduit calls by piping JSON in to stdin. This can provide a simple way Conduit calls by piping JSON in to stdin. This can provide a simple way

View file

@ -55,10 +55,9 @@ If you choose to assign a callsign to a repository, it must be unique within an
install but do not need to be globally unique, so you are free to use the install but do not need to be globally unique, so you are free to use the
single-letter callsigns for brevity. For example, Facebook uses "E" for the single-letter callsigns for brevity. For example, Facebook uses "E" for the
Engineering repository, "O" for the Ops repository, "Y" for a Yum package Engineering repository, "O" for the Ops repository, "Y" for a Yum package
repository, and so on, while Phabricator uses "P", "ARC", "PHU" for libphutil, repository, and so on, while Phabricator uses "P" and Arcanist uses "ARC".
and "J" for Javelin. Keeping callsigns brief will make them easier to use, and Keeping callsigns brief will make them easier to use, and the use of
the use of one-character callsigns is encouraged if they are reasonably one-character callsigns is encouraged if they are reasonably evocative.
evocative.
If you configure a callsign like `XYZ`, Phabricator will activate callsign URIs If you configure a callsign like `XYZ`, Phabricator will activate callsign URIs
and activate the callsign identifier (like `rXYZ`) for the repository. These and activate the callsign identifier (like `rXYZ`) for the repository. These

View file

@ -83,8 +83,8 @@ You can configure some more options by going to {nav Diffusion > (Select
You can leave this blank for "All languages". You can leave this blank for "All languages".
- **Uses Symbols From**: If this project depends on other repositories, add - **Uses Symbols From**: If this project depends on other repositories, add
the other repositories which symbols should be looked for here. For example, the other repositories which symbols should be looked for here. For example,
Phabricator lists "Arcanist" and "libphutil" because it uses classes and Phabricator lists "Arcanist" because it uses classes and functions defined
functions from these repositories. in `arcanist/`.
== External Symbols == == External Symbols ==

View file

@ -41,7 +41,7 @@ installing software on hosts. You'll need to make sure any hosts are configured
properly with any software you need, and have tools like `git`, `hg` or `svn` properly with any software you need, and have tools like `git`, `hg` or `svn`
that may be required to interact with working copies. that may be required to interact with working copies.
You do **not** need to install PHP, arcanist, libphutil or Phabricator on the You do **not** need to install PHP, arcanist, or Phabricator on the
hosts unless you are specifically running `arc` commands. hosts unless you are specifically running `arc` commands.
**You must configure authentication.** Drydock also does not handle credentials **You must configure authentication.** Drydock also does not handle credentials

View file

@ -23,7 +23,7 @@ the most direct and powerful way to respond to events.
To install event listeners in Phabricator, follow these steps: To install event listeners in Phabricator, follow these steps:
- Write a listener class which extends @{class@libphutil:PhutilEventListener}. - Write a listener class which extends @{class@arcanist:PhutilEventListener}.
- Add it to a libphutil library, or create a new library (for instructions, - Add it to a libphutil library, or create a new library (for instructions,
see @{article@phabcontrib:Adding New Classes}. see @{article@phabcontrib:Adding New Classes}.
- Configure Phabricator to load the library by adding it to `load-libraries` - Configure Phabricator to load the library by adding it to `load-libraries`
@ -40,7 +40,7 @@ see any events the page emitted there. For details on DarkConsole, see
To install event listeners in Arcanist, follow these steps: To install event listeners in Arcanist, follow these steps:
- Write a listener class which extends @{class@libphutil:PhutilEventListener}. - Write a listener class which extends @{class@arcanist:PhutilEventListener}.
- Add it to a libphutil library, or create a new library (for instructions, - Add it to a libphutil library, or create a new library (for instructions,
see @{article@phabcontrib:Adding New Classes}. see @{article@phabcontrib:Adding New Classes}.
- Configure Phabricator to load the library by adding it to `load` - Configure Phabricator to load the library by adding it to `load`

View file

@ -22,48 +22,6 @@ options:
Encodings" below). This is not completely supported, and repositories with Encodings" below). This is not completely supported, and repositories with
files that have multiple encodings are not supported. files that have multiple encodings are not supported.
= Detecting and Repairing Files =
It is recommended that you write source files only in ASCII text, but
Phabricator fully supports UTF-8 source files.
If you have a project which isn't valid UTF-8 because a few files have random
binary nonsense in them, there is a script in libphutil which can help you
identify and fix them:
project/ $ libphutil/scripts/utils/utf8.php
Generally, run this script on all source files with "-t" to find files with bad
byte ranges, and then run it without "-t" on each file to identify where there
are problems. For example:
project/ $ find . -type f -name '*.c' -print0 | xargs -0 -n256 ./utf8 -t
./hello_world.c
If this script exits without output, you're in good shape and all the files that
were identified are valid UTF-8. If it found some problems, you need to repair
them. You can identify the specific problems by omitting the "-t" flag:
project/ $ ./utf8.php hello_world.c
FAIL hello_world.c
3 main()
4 {
5 printf ("Hello World<0xE9><0xD6>!\n");
6 }
7
This shows the offending bytes on line 5 (in the actual console display, they'll
be highlighted). Often a codebase will mostly be valid UTF-8 but have a few
scattered files that have other things in them, like curly quotes which someone
copy-pasted from Word into a comment. In these cases, you can just manually
identify and fix the problems pretty easily.
If you have a prohibitively large number of UTF-8 issues in your source code,
Phabricator doesn't include any default tools to help you process them in a
systematic way. You could hack up `utf8.php` as a starting point, or use other
tools to batch-process your source files.
= Support for Alternate Encodings = = Support for Alternate Encodings =
Phabricator has some support for encodings other than UTF-8. Phabricator has some support for encodings other than UTF-8.

View file

@ -66,7 +66,8 @@ abstract class PhabricatorWorkerTask extends PhabricatorWorkerDAO {
$class = $this->getTaskClass(); $class = $this->getTaskClass();
try { try {
// NOTE: If the class does not exist, libphutil will throw an exception. // NOTE: If the class does not exist, the autoloader will throw an
// exception.
class_exists($class); class_exists($class);
} catch (PhutilMissingSymbolException $ex) { } catch (PhutilMissingSymbolException $ex) {
throw new PhabricatorWorkerPermanentFailureException( throw new PhabricatorWorkerPermanentFailureException(

View file

@ -116,7 +116,7 @@
* $pugs = $dog->loadAllWhere('breed = %s', 'Pug'); * $pugs = $dog->loadAllWhere('breed = %s', 'Pug');
* $sawyer = $dog->loadOneWhere('name = %s', 'Sawyer'); * $sawyer = $dog->loadOneWhere('name = %s', 'Sawyer');
* *
* These methods work like @{function@libphutil:queryfx}, but only take half of * These methods work like @{function@arcanist:queryfx}, but only take half of
* a query (the part after the WHERE keyword). Lisk will handle the connection, * a query (the part after the WHERE keyword). Lisk will handle the connection,
* columns, and object construction; you are responsible for the rest of it. * columns, and object construction; you are responsible for the rest of it.
* @{method:loadAllWhere} returns a list of objects, while * @{method:loadAllWhere} returns a list of objects, while

View file

@ -0,0 +1,13 @@
<?php
final class PHUIColor extends Phobject {
public static function getWebColorFromANSIColor($ansi_color) {
$map = array(
'cyan' => 'sky',
'magenta' => 'pink',
);
return idx($map, $ansi_color, $ansi_color);
}
}

View file

@ -67,7 +67,7 @@ final class PhabricatorStartup {
*/ */
public static function getMicrosecondsSinceStart() { public static function getMicrosecondsSinceStart() {
// This is the same as "phutil_microseconds_since()", but we may not have // This is the same as "phutil_microseconds_since()", but we may not have
// loaded libphutil yet. // loaded libraries yet.
return (int)(1000000 * (microtime(true) - self::getStartTime())); return (int)(1000000 * (microtime(true) - self::getStartTime()));
} }

Some files were not shown because too many files have changed in this diff Show more